# HG changeset patch # User Rick DeWitt # Date 1573163817 28800 # Thu Nov 07 13:56:57 2019 -0800 # Node ID b5f040349251c09265f07f2747ae073e838798f9 # Parent 60b0754e45de1557ca2597c41a2966129653eeef [kenwood_live] Added TS-480 and TS-590 live mode, per issues #7077, #7079 Added Kenwood HF rigs to Kenwood_Live per #7077 and #7079 diff -r 60b0754e45de -r b5f040349251 chirp/drivers/kenwood_live.py --- a/chirp/drivers/kenwood_live.py Thu Oct 24 15:46:01 2019 +0200 +++ b/chirp/drivers/kenwood_live.py Thu Nov 07 13:56:57 2019 -0800 @@ -45,9 +45,13 @@ THF6_MODES = ["FM", "WFM", "AM", "LSB", "USB", "CW"] + RADIO_IDS = { "ID019": "TS-2000", "ID009": "TS-850", + "ID020": "TS-480_LiveMode", + "ID021": "TS-590S/SG_LiveMode", # S-model uses same class + "ID023": "TS-590S/SG_LiveMode" # as SG } LOCK = threading.Lock() @@ -55,8 +59,9 @@ LAST_BAUD = 4800 LAST_DELIMITER = ("\r", " ") -# The Kenwood TS-2000 & TS-850 uses ";" as a CAT command message delimiter, -# and all others use "\n". Also, TS-2000 doesn't space delimite the command +# The Kenwood TS-2000, TS-480, TS-590 & TS-850 use ";" +# as a CAT command message delimiter, and all others use "\n". +# Also, TS-2000 and TS-590 don't space delimite the command # fields, but others do. @@ -96,7 +101,7 @@ def get_id(ser): """Get the ID of the radio attached to @ser""" global LAST_BAUD - bauds = [4800, 9600, 19200, 38400, 57600] + bauds = [4800, 9600, 19200, 38400, 57600, 115200] bauds.remove(LAST_BAUD) bauds.insert(0, LAST_BAUD) @@ -994,6 +999,7 @@ """Kenwood TH-F7""" MODEL = "TH-F7" + D710_DUPLEX = ["", "+", "-", "split"] D710_MODES = ["FM", "NFM", "AM"] D710_SKIP = ["", "S"] @@ -1335,3 +1341,360 @@ def _cmd_set_memory_name(self, number, name): return "MN", "%03i,%s" % (number, name) + + +@directory.register +class TS590Radio(KenwoodLiveRadio): + """Kenwood TS-590S/SG""" + MODEL = "TS-590S/SG_LiveMode" + + _kenwood_valid_tones = list(KENWOOD_TONES) + _kenwood_valid_tones.append(1750) + + _upper = 99 + _duplex = ["", "-", "+"] + _skip = ["", "S"] + _modes = ["LSB", "USB", "CW", "FM", "AM", "FSK", "CW-R", + "FSK-R", "Data+LSB", "Data+USB", "Data+FM"] + _bands = [(1800000, 2000000), # 160M Band + (3500000, 4000000), # 80M Band + (5167500, 5450000), # 60M Band + (7000000, 7300000), # 40M Band + (10100000, 10150000), # 30M Band + (14000000, 14350000), # 20M Band + (18068000, 18168000), # 17M Band + (21000000, 21450000), # 15M Band + (24890000, 24990000), # 12M Band + (28000000, 29700000), # 10M Band + (50000000, 54000000)] # 6M Band + + def get_features(self): + rf = chirp_common.RadioFeatures() + + rf.can_odd_split = False + rf.has_bank = False + rf.has_ctone = True + rf.has_dtcs = False + rf.has_dtcs_polarity = False + rf.has_name = True + rf.has_settings = False + rf.has_offset = True + rf.has_mode = True + rf.has_tuning_step = False + rf.has_nostep_tuning = True + rf.has_cross = True + rf.has_comment = False + + rf.memory_bounds = (0, self._upper) + + rf.valid_bands = self._bands + rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "*+-/" + rf.valid_duplexes = ["", "-", "+"] + rf.valid_modes = self._modes + rf.valid_skips = ["", "S"] + rf.valid_tmodes = ["", "Tone", "TSQL", "Cross"] + rf.valid_cross_modes = ["Tone->Tone", "->Tone"] + rf.valid_name_length = 8 # 8 character channel names + + return rf + + def _my_val_list(setting, opts, obj, atrb): + """Callback:from ValueList. Set the integer index.""" + value = opts.index(str(setting.value)) + setattr(obj, atrb, value) + return + + def get_memory(self, number): + """Convert ascii channel data spec into UI columns (mem)""" + mem = chirp_common.Memory() + mem.extra = RadioSettingGroup("extra", "Extra") + # Read the base and split MR strings + mem.number = number + spec0 = command(self.pipe, "MR0 %02i" % mem.number) + spec1 = command(self.pipe, "MR1 %02i" % mem.number) + mem.name = spec0[41:49] # Max 8-Char Name if assigned + mem.name = mem.name.strip() + mem.name = mem.name.upper() + _p4 = int(spec0[6:17]) # Rx Frequency + _p4s = int(spec1[6:17]) # Offset freq (Tx) + _p5 = int(spec0[17]) # Mode + _p6 = int(spec0[18]) # Data Mode + _p7 = int(spec0[19]) # Tone Mode + _p8 = int(spec0[20:22]) # Tone Frequency Index + _p9 = int(spec0[22:24]) # CTCSS Frequency Index + _p11 = int(spec0[27]) # Filter A/B + _p14 = int(spec0[38:40]) # FM Mode + _p15 = int(spec0[40]) # Chan Lockout (Skip) + if _p4 == 0: + mem.empty = True + return mem + mem.empty = False + mem.freq = _p4 + mem.duplex = self._duplex[0] # None by default + mem.offset = 0 + if _p4 < _p4s: # + shift + mem.duplex = self._duplex[2] + mem.offset = _p4s - _p4 + if _p4 > _p4s: # - shift + mem.duplex = self._duplex[1] + mem.offset = _p4 - _p4s + mx = _p5 - 1 # CAT modes start at 1 + if _p5 == 9: # except CAT FSK-R is 9, there is no 8 + mx = 7 + if _p6: # LSB+Data= 8, USB+Data= 9, FM+Data= 10 + if _p5 == 1: # CAT LSB + mx = 8 + elif _p5 == 2: # CAT USB + mx = 9 + elif _p5 == 4: # CAT FM + mx = 10 + mem.mode = self._modes[mx] + mem.tmode = "" + mem.cross_mode = "Tone->Tone" + mem.ctone = self._kenwood_valid_tones[_p9] + mem.rtone = self._kenwood_valid_tones[_p8] + if _p7 == 1: + mem.tmode = "Tone" + elif _p7 == 2: + mem.tmode = "TSQL" + elif _p7 == 3: + mem.tmode = "Cross" + mem.skip = self._skip[_p15] + + rx = RadioSettingValueBoolean(bool(_p14)) + rset = RadioSetting("fmnrw", "FM Narrow mode (off = Wide)", rx) + mem.extra.append(rset) + return mem + + def erase_memory(self, number): + """ Send the blank string to MW0 """ + mem = chirp_common.Memory() + mem.empty = True + mem.freq = 0 + mem.offset = 0 + spx = "MW0%03i00000000000000000000000000000000000" % number + rx = command(self.pipe, spx) # Send MW0 + return mem + + def set_memory(self, mem): + """Send UI column data (mem) to radio""" + pfx = "MW0%03i" % mem.number + xmode = 0 + xtmode = 0 + xrtone = 8 + xctone = 8 + xdata = 0 + xfltr = 0 + xfm = 0 + xskip = 0 + xfreq = mem.freq + if xfreq > 0: # if empty; use those defaults + ix = self._modes.index(mem.mode) + xmode = ix + 1 # stored as CAT values, LSB= 1 + if ix == 7: # FSK-R + xmode = 9 # There is no CAT 8 + if ix > 7: # a Data mode + xdata = 1 + if ix == 8: + xmode = 1 # LSB + elif ix == 9: + xmode = 2 # USB + elif ix == 10: + xmode = 4 # FM + if mem.tmode == "Tone": + xtmode = 1 + xrtone = self._kenwood_valid_tones.index(mem.rtone) + if mem.tmode == "TSQL" or mem.tmode == "Cross": + xtmode = 2 + if mem.tmode == "Cross": + xtmode = 3 + xctone = self._kenwood_valid_tones.index(mem.ctone) + for setting in mem.extra: + if setting.get_name() == "fmnrw": + xfm = setting.value + if mem.skip == "S": + xskip = 1 + spx = "%011i%1i%1i%1i%02i%02i000%1i0000000000%02i%1i%s" \ + % (xfreq, xmode, xdata, xtmode, xrtone, + xctone, xfltr, xfm, xskip, mem.name) + rx = command(self.pipe, pfx, spx) # Send MW0 + if mem.offset != 0: + pfx = "MW1%03i" % mem.number + xfreq = mem.freq - mem.offset + if mem.duplex == "+": + xfreq = mem.freq + mem.offset + spx = "%011i%1i%1i%1i%02i%02i000%1i0000000000%02i%1i%s" \ + % (xfreq, xmode, xdata, xtmode, xrtone, + xctone, xfltr, xfm, xskip, mem.name) + rx = command(self.pipe, pfx, spx) # Send MW1 + + +@directory.register +class TS480Radio(KenwoodLiveRadio): + """Kenwood TS-480""" + MODEL = "TS-480_LiveMode" + + _kenwood_valid_tones = list(KENWOOD_TONES) + _kenwood_valid_tones.append(1750) + + _upper = 99 + _duplex = ["", "-", "+"] + _skip = ["", "S"] + _modes = ["LSB", "USB", "CW", "FM", "AM", "FSK", "CW-R", "N/A", + "FSK-R"] + _bands = [(1800000, 2000000), # 160M Band + (3500000, 4000000), # 80M Band + (5167500, 5450000), # 60M Band + (7000000, 7300000), # 40M Band + (10100000, 10150000), # 30M Band + (14000000, 14350000), # 20M Band + (18068000, 18168000), # 17M Band + (21000000, 21450000), # 15M Band + (24890000, 24990000), # 12M Band + (28000000, 29700000), # 10M Band + (50000000, 54000000)] # 6M Band + + _tsteps = [0.5, 1.0, 2.5, 5.0, 6.25, 10.0, 12.5, + 15.0, 20.0, 25.0, 30.0, 50.0, 100.0] + + def get_features(self): + rf = chirp_common.RadioFeatures() + + rf.can_odd_split = False + rf.has_bank = False + rf.has_ctone = True + rf.has_dtcs = False + rf.has_dtcs_polarity = False + rf.has_name = True + rf.has_settings = False + rf.has_offset = True + rf.has_mode = True + rf.has_tuning_step = True + rf.has_nostep_tuning = True + rf.has_cross = True + rf.has_comment = False + + rf.memory_bounds = (0, self._upper) + + rf.valid_bands = self._bands + rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "*+-/" + rf.valid_duplexes = ["", "-", "+"] + rf.valid_modes = self._modes + rf.valid_skips = ["", "S"] + rf.valid_tmodes = ["", "Tone", "TSQL", "Cross"] + rf.valid_cross_modes = ["Tone->Tone", "->Tone"] + rf.valid_name_length = 8 # 8 character channel names + rf.valid_tuning_steps = self._tsteps + + return rf + + def _my_val_list(setting, opts, obj, atrb): + """Callback:from ValueList. Set the integer index.""" + value = opts.index(str(setting.value)) + setattr(obj, atrb, value) + return + + def get_memory(self, number): + """Convert ascii channel data spec into UI columns (mem)""" + mem = chirp_common.Memory() + # Read the base and split MR strings + mem.number = number + spec0 = command(self.pipe, "MR0%03i" % mem.number) + spec1 = command(self.pipe, "MR1%03i" % mem.number) + # Add 1 to string idecis if refering to CAT manual + mem.name = spec0[41:49] # Max 8-Char Name if assigned + mem.name = mem.name.strip() + mem.name = mem.name.upper() + _p4 = int(spec0[6:17]) # Rx Frequency + _p4s = int(spec1[6:17]) # Offset freq (Tx) + _p5 = int(spec0[17]) # Mode + _p6 = int(spec0[18]) # Chan Lockout (Skip) + _p7 = int(spec0[19]) # Tone Mode + _p8 = int(spec0[20:22]) # Tone Frequency Index + _p9 = int(spec0[22:24]) # CTCSS Frequency Index + _p14 = int(spec0[38:40]) # Tune Step + if _p4 == 0: + mem.empty = True + return mem + mem.empty = False + mem.freq = _p4 + mem.duplex = self._duplex[0] # None by default + mem.offset = 0 + if _p4 < _p4s: # + shift + mem.duplex = self._duplex[2] + mem.offset = _p4s - _p4 + if _p4 > _p4s: # - shift + mem.duplex = self._duplex[1] + mem.offset = _p4 - _p4s + mx = _p5 - 1 # CAT modes start at 1 + mem.mode = self._modes[mx] + mem.tmode = "" + mem.cross_mode = "Tone->Tone" + mem.ctone = self._kenwood_valid_tones[_p9] + mem.rtone = self._kenwood_valid_tones[_p8] + if _p7 == 1: + mem.tmode = "Tone" + elif _p7 == 2: + mem.tmode = "TSQL" + elif _p7 == 3: + mem.tmode = "Cross" + mem.skip = self._skip[_p6] + # Tuning step depends on mode + options = [0.5, 1.0, 2.5, 5.0, 10.0] # SSB/CS/FSK + if _p14 == 4 or _p14 == 5: # AM/FM + options = self._tsteps[3:] + mem.tuning_step = options[_p14] + + return mem + + def erase_memory(self, number): + mem = chirp_common.Memory() + mem.empty = True + mem.freq = 0 + mem.offset = 0 + spx = "MW0%03i00000000000000000000000000000000000" % number + rx = command(self.pipe, spx) # Send MW0 + return mem + + def set_memory(self, mem): + """Send UI column data (mem) to radio""" + pfx = "MW0%03i" % mem.number + xtmode = 0 + xdata = 0 + xrtone = 8 + xctone = 8 + xskip = 0 + xstep = 0 + xfreq = mem.freq + if xfreq > 0: # if empty, use those defaults + ix = self._modes.index(mem.mode) + xmode = ix + 1 # stored as CAT values, LSB= 1 + if ix == 7: # FSK-R + xmode = 9 # There is no CAT 8 + if mem.tmode == "Tone": + xtmode = 1 + xrtone = self._kenwood_valid_tones.index(mem.rtone) + if mem.tmode == "TSQL" or mem.tmode == "Cross": + xtmode = 2 + if mem.tmode == "Cross": + xtmode = 3 + xctone = self._kenwood_valid_tones.index(mem.ctone) + if mem.skip == "S": + xskip = 1 + options = [0.5, 1.0, 2.5, 5.0, 10.0] # SSB/CS/FSK + if xmode == 4 or xmode == 5: + options = self._tsteps[3:] + xstep = options.index(mem.tuning_step) + spx = "%011i%1i%1i%1i%02i%02i00000000000000%02i%s" \ + % (xfreq, xmode, xskip, xtmode, xrtone, + xctone, xstep, mem.name) + rx = command(self.pipe, pfx, spx) # Send MW0 + if mem.offset != 0: # Don't send MW1 if empty + pfx = "MW1%03i" % mem.number + xfreq = mem.freq - mem.offset + if mem.duplex == "+": + xfreq = mem.freq + mem.offset + spx = "%011i%1i%1i%1i%02i%02i00000000000000%02i%s" \ + % (xfreq, xmode, xskip, xtmode, xrtone, + xctone, xstep, mem.name) + rx = command(self.pipe, pfx, spx) # Send MW1