# HG changeset patch # User DanClemmensen DanClemmensen@gmail.com # Date 1551210139 28800 # Tue Feb 26 11:42:19 2019 -0800 # Node ID b0ed770692e8e00ea9d3cec5bf9cf58c3f7367da # Parent 607b8b18a90e46231a2818304107f3f8d8a7173a [ft4] rework tones [#4787] cleanup last tone bugs and slightly refactor to remove redundant lists. There is some minor cosmetic stuff also
diff -r 607b8b18a90e -r b0ed770692e8 chirp/drivers/ft4.py --- a/chirp/drivers/ft4.py Mon Feb 25 17:43:10 2019 -0800 +++ b/chirp/drivers/ft4.py Tue Feb 26 11:42:19 2019 -0800 @@ -192,7 +192,6 @@ # the serial port. The code runs on either Python 2 or Python3, so some # constructs could be better optimized for one or the other, but not both.
- def get_mmap_data(radio): """ horrible kludge needed until we convert entirely to Python 3 OR we add a @@ -217,7 +216,7 @@ return sum(x for x in bytearray(data)) & 0xFF
-def variable_len_resp(pipe): +def variable_len_resp(pipe, cmd): """ when length of expected reply is not known, read byte at a time until the ack character is found. @@ -233,8 +232,11 @@ response += b i += 1 if i > toolong: - LOG.debug("Response too long. got" + util.hexprint(response)) - raise errors.RadioError("Response too long.") + msg = "received too many bytes from radio before ACK. " + msg += "Sent " + util.hexprint(cmd) + msg += ". Got " + util.hexprint(response) + LOG.debug(msg) + raise errors.RadioError(msg) return(response)
@@ -253,19 +255,19 @@ pipe.write(cmd) echo = pipe.read(len(cmd)) if echo != cmd: - msg = "Bad echo. Sent:" + util.hexprint(cmd) + ", " + msg = "Bad echo on serial port. Sent:" + util.hexprint(cmd) + ", " msg += "Received:" + util.hexprint(echo) LOG.debug(msg) - raise errors.RadioError("Incorrect echo on serial port.") + raise errors.RadioError(msg) if response_len is None: - return variable_len_resp(pipe) + return variable_len_resp(pipe, cmd) if response_len > 0: response = pipe.read(response_len) else: response = b"" ack = pipe.read(1) if ack != b'\x06': - LOG.debug("missing ack: expected 0x06, got" + util.hexprint(ack)) + LOG.debug("missing ACK: expected 0x06, got" + util.hexprint(ack)) raise errors.RadioError("Incorrect ACK on serial port.") return response
@@ -282,12 +284,13 @@ raise errors.RadioError("expected QX from radio.") id_response = sendcmd(radio.pipe, b'\x02', None) if id_response != radio.id_str: - substr0=radio.id_str[:radio.id_str.find('\x00')] + substr0 = radio.id_str[:radio.id_str.find('\x00')] if id_response[:id_response.find('\x00')] != substr0: - msg = "ID mismatch. Expected" + util.hexprint(radio.id_str) + msg = "Unknown ID string received from radio" + msg += "Expected:" + util.hexprint(radio.id_str) msg += ", Received:" + util.hexprint(id_response) LOG.warning(msg) - raise errors.RadioError("Incorrect ID.") + raise errors.RadioError(msg) else: msg = "ID suspect. Expected" + util.hexprint(radio.id_str) msg += ", Received:" + util.hexprint(id_response) @@ -447,8 +450,59 @@ # on the US versions (FT-4XR) STEP_CODE = [0, 5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0]
-TONE_MODES = ["", "Tone", "TSQL", "DTCS", "DTCS-R", "TSQL-R", "Cross"] -CROSS_MODES = ["DTCS->", "DTCS->DTCS"] # only the extras we need +# Yaesu sql_type field codes +SQL_TYPE = ["off", "R-TONE", "T-TONE", "TSQL", "REV-TN", "DCS", "PAGER"] + +# map a CHIRP tone mode to a FT-4 sql and which if any code to set to 0. +# The keys are provided to RadioFeatures as a list, so these are the +# only values we expect to see in mem.tmode. This map as the identical +# structure as the one below to simplify the code. It permits stricter +# zeroing of the unused fields later if we find that it is needed, +# but the CHIRP unit tests want us to leave them alone. +MODES_TONE = { + "": ("off", None), + "Tone": ("T-TONE", None), # chirp means "xmit, not rcv" + "TSQL": ("TSQL", None), # chirp means "xmit and rcv, tx==rx" + "DTCS": ("DCS", None), # chirp means "xmt and rcv, tx==rx" + "TSQL-R": ("REV-TN", None), # chirp means reverse R-Tone + "Cross": () # not used in lookup, needed in list + } + +# Map a CHIRP Cross type if the CHIRP mem.tmode is "Cross". The keys +# are provided to RadioFeatures as a list, so these are the +# only values we expect to see in mem.cross. +MODES_CROSS = { + "DTCS->": ("DCS", "rx_dcs"), + "->DTCS": ("DCS", "tx_dcs"), + "DTCS->DTCS": ("DCS", None), + "->Tone": ("R-TONE", None), + "Tone->Tone": ("TSQL", None) + } + +# Map the radio image sql_type (0-6) back to the CHIRP mem values +# Types "TSQL" and "DCS" each map to different CHIRP values depending +# on the radio values on the tx and rx tone codes. +RADIO_TMODES = [ + ("(None)", ["", ""]), # sql_type= 0. off + ("(None)", ["Cross", "->Tone"]), # sql_type= 1. R-TONE + ("(None)", ["Tone", ""]), # sql_type= 2. T-TONE + ("(None)", None, "tx_ctcss", "rx_ctcss", [ # sql_type= 3. TSQL + ["", None], # tx==0, rx==0 : not valid + ["TSQL", ""], # tx==0 + ["Tone", ""], # rx==0 + ["Cross", "Tone->Tone"], # tx!=rx + ["TSQL", ""] # tx==rx + ]), + ("(None)", ["TSQL-R", ""]), # sql_type= 4. REV TN + ("(None)", None, "tx_dcs", "rx_dcs", [ # sql_type= 5.DCS + ["", None], # tx==0, rx==0 : not valid + ["Cross", "->DTCS"], # tx==0 + ["Cross", "DTCS->"], # rx==0 + ["Cross", "DTCS->DTCS"], # tx!=rx + ["DTCS", ""] # tx==rx + ]), + ("PAGER", ["", None]) # sql_type= 6. handled as a CHIRP "extra" + ]
DTMF_CHARS = "0123456789ABCD*#- " CW_ID_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ " @@ -491,6 +545,9 @@ 612, 624, 627, 631, 632, 654, 662, 664, 703, 712, 723, 731, 732, 734, 743, 754 ] + +# The legal PAGER codes are exactly the same as the CTCSS codes, +# but we pass them to the UI as a lsit of strings to display. EPCS_CODES = [format(flt) for flt in [0] + TONE_MAP[1:]]
@@ -534,8 +591,8 @@ rf.valid_special_chans = specials rf.memory_bounds = (1, self.MAX_MEM_SLOT) rf.valid_duplexes = DUPLEX - rf.valid_tmodes = TONE_MODES - rf.valid_cross_modes = CROSS_MODES + rf.valid_tmodes = list(MODES_TONE.keys()) + rf.valid_cross_modes = list(MODES_CROSS.keys()) rf.valid_power_levels = POWER_LEVELS rf.valid_tuning_steps = self.legal_steps rf.valid_skips = SKIPS @@ -559,6 +616,8 @@ def sync_in(self): try: self._mmap = do_download(self) + except errors.RadioError: + raise except Exception as e: raise errors.RadioError("Failed to communicate with radio: %s" % e) self.process_mmap() @@ -567,6 +626,8 @@ def sync_out(self): try: do_upload(self) + except errors.RadioError: + raise except Exception as e: raise errors.RadioError("Failed to communicate with radio: %s" % e)
@@ -707,7 +768,7 @@ # ----------------end of group_descriptions
# allow a child class to add a param. - def add_paramdesc(self,group, param): + def add_paramdesc(self, group, param): for description in self.group_descriptions: groupname, title, parms = description if group == groupname: @@ -770,35 +831,14 @@ LOG.debug(element.get_name()) raise
- RADIO_TMODES = [ - ("(None)", ["", ""]), # off - ("(None)", ["TSQL-R", ""]), # R-TONE - ("(None)", ["Tone", ""]), # T-TONE - ("(None)", None, "tx_ctcss", "rx_ctcss", [ # TSQL - ["", None], # x==0, r==0 : not valid - ["TSQL-R", ""], # x==0 - ["Tone", ""], # r==0 - ["TSQL", ""], # x!=r - ["TSQL", ""] # x==r - ]), - ("REV-TN", ["TSQL-R", ""]), - ("(None)", None, "tx_dcs", "rx_dcs", [ # DCS - ["", None], # x==0, r==0 : not valid - ["DTCS-R", ""], # x==0 - ["Cross", "DTCS->"], # r==0 - ["Cross", "DTCS->DTCS"], # x!=r - ["DTCS", ""] # x==r - ]), - ("PAGER", ["", None]) # handled as a CHIRP "extra" - ] LOOKUP = [[True, True], [True, False], [False, True], [False, False]]
def decode_sql(self, mem, chan): """ examine the radio channel fields and determine the correct - CHIRP CSV values for tmode, cross_mode, and dcts_polarity + CHIRP CSV values for tmode, cross_mode, and sql_override """ - mode = self.RADIO_TMODES[chan.sql_type] + mode = RADIO_TMODES[chan.sql_type] chirpvals = mode[1] if not chirpvals: x = getattr(chan, mode[2]) @@ -820,32 +860,10 @@ mem.rx_dtcs = DTCS_MAP[chan.rx_dcs] LOG.debug(" setting sql_override to <%s>" % mode[0]) mem.extra = RadioSettingGroup("Extra", "extra") - extra_modes = ["(None)", "REV-TN", "PAGER"] + extra_modes = ["(None)", "PAGER"] valuelist = RadioSettingValueList(extra_modes, mode[0]) rs = RadioSetting("sql_override", "Squelch override", valuelist) mem.extra.append(rs) - # Yaesu sql_type field codes - SQL_TYPE = ["off", "R-TONE", "T-TONE", "TSQL", "REV-TN", "DCS", "PAGER"] - # map a CHIRP tone mode to a FT-4 sql and which if any code to set to 0. - MODE_TONE = { - "": ("off", None), - "Tone": ("T-TONE", "rx_ctcss"), - "TSQL": ("TSQL", None), - "DTCS": ("DCS", None), # must set rx_dcs to tx_dcs? - "DTCS-R": ("DCS", "tx_dcs"), - "TSQL-R": ("R-TONE", "tx_ctcss"), # not documented on wiki - "Cross": () # not used in lookup - } - - # map a CHIRP Cross type if the CHIRP sql type is "cross" - MODE_CROSS = { - "DTCS->": ("DCS", "rx_dcs"), - "DTCS->DTCS": ("DCS", None) - # "Tone->Tone": ("TSQL", None), - # "->DTCS": ("DCS", "tx_dcs"), - # "->Tone": ("R-TONE", None), - # "Tone->": ("T-Tone", None) - }
def encode_sql(self, mem, chan): """ @@ -858,22 +876,23 @@ chan.tx_dcs = DTCS_MAP.index(mem.dtcs) chan.rx_ctcss = TONE_MAP.index(mem.ctone) chan.rx_dcs = DTCS_MAP.index(mem.rx_dtcs) + if mem.tmode == "TSQL": + chan.tx_ctcss = chan.rx_ctcss # CHIRP uses ctone for TSQL + if mem.tmode == "DTCS": + chan.tx_dcs = chan.rx_dcs # CHIRP uses rx_dtcs for DTCS tbl, ndx = [ - (self.MODE_TONE, mem.tmode), - (self.MODE_CROSS, mem.cross_mode) + (MODES_TONE, mem.tmode), + (MODES_CROSS, mem.cross_mode) ][mem.tmode == "Cross"] - row = tbl[ndx] - if ndx == "DTCS": - chan.rx_dcs = chan.tx_dcs - chan.sql_type = self.SQL_TYPE.index(row[0]) - if row[1]: - setattr(chan, row[1], 0) + sql_type, suppress = tbl[ndx] + chan.sql_type = SQL_TYPE.index(sql_type) + if suppress: + setattr(chan, suppress, 0) for setting in mem.extra: if (setting.get_name() == 'sql_override'): value = str(setting.value) if value != "(None)": - chan.sql_type = self.SQL_TYPE.index(value) - + chan.sql_type = SQL_TYPE.index(value) return
# given a CHIRP memory ref, get the radio memobj for it. @@ -962,7 +981,6 @@ mem.empty = False mem.extd_number = sname mem.immutable = ["number", "extd_number", "name", "skip"] - return mem
# modify a radio channel in memobj based on info in CHIRP canonical form @@ -995,7 +1013,6 @@ duplex = "off" # radio ignores when tx != rx self._memobj.txfreqs[num-1].freq = txfreq _mem.duplex = DUPLEX.index(duplex) - return
@@ -1018,22 +1035,23 @@ namelen = 6 # length of the mem name display on the FT-4 front-panel id_str = b'IFT-35R\x00\x00V100\x00\x00' legal_steps = list(STEP_CODE) - legal_steps.remove(6.25) #should not remove if euro version + legal_steps.remove(6.25) # should not remove if euro version # names for the setmode function for the programmable keys. Mode zero means # that the key is programmed for a memory not a setmode. SETMODES = [ - "mem", "apo", "ar bep", "ar int", "beclo", #00-04 - "beep", "bell", "cw id", "cw wrt", "dc vlt", #05-09 - "dcs cod", "dt dly", "dt set", "dtc spd", "edg.bep", #10-14 - "lamp", "led.bsy", "led.tx", "lock", "m/t-cl", #15-19 - "mem.del", "mem.tag", "pag.abk", "pag.cdr", "pag.cdt", #20-24 - "pri.rvt", "pswd", "pswdwt", "rf sql", "rpt.ars", #25-29 - "rpt.frq", "rpt.sft", "rxsave", "scn.lmp", "scn.rsm", #30-34 - "skip", "sql.typ", "step", "tn frq", "tot", #35-39 - "tx pwr", "tx save", "vfo.spl", "vox", "wfm.rcv", #40-44 - "w/n.dev", "wx.alert" #45-46 + "mem", "apo", "ar bep", "ar int", "beclo", # 00-04 + "beep", "bell", "cw id", "cw wrt", "dc vlt", # 05-09 + "dcs cod", "dt dly", "dt set", "dtc spd", "edg.bep", # 10-14 + "lamp", "led.bsy", "led.tx", "lock", "m/t-cl", # 15-19 + "mem.del", "mem.tag", "pag.abk", "pag.cdr", "pag.cdt", # 20-24 + "pri.rvt", "pswd", "pswdwt", "rf sql", "rpt.ars", # 25-29 + "rpt.frq", "rpt.sft", "rxsave", "scn.lmp", "scn.rsm", # 30-34 + "skip", "sql.typ", "step", "tn frq", "tot", # 35-39 + "tx pwr", "tx save", "vfo.spl", "vox", "wfm.rcv", # 40-44 + "w/n.dev", "wx.alert" # 45-46 ]
+ # don't register the FT-65 in the production version until it is tested # @directory.register class YaesuFT65Radio(YaesuSC35GenericRadio): @@ -1052,9 +1070,9 @@ MAX_MEM_SLOT = 200 Pkeys = 4 # number of programmable keys on the FT-65 namelen = 8 # length of the mem name display on the FT-65 front panel - id_str=b'IH-420\x00\x00\x00V100\x00\x00' + id_str = b'IH-420\x00\x00\x00V100\x00\x00' legal_steps = list(STEP_CODE) - legal_steps.remove(6.25) #should not remove if euro version + legal_steps.remove(6.25) # should not remove if euro version # names for the setmode function for the programmable keys. Mode zero means # that the key is programmed for a memory not a setmode. SETMODES = [ @@ -1063,10 +1081,10 @@ "dc volt", "dcs code", "dtmf set", "dtmf wrt", "edg bep", # 10-14 "key lock", "lamp", "ledbsy", "mem del", "mon/t-cl", # 15-19 "name tag", "pager", "password", "pri.rvt", "repeater", # 20-24 - "resume", "rf.sql", "scn.lamp", "skip", "sql type", # 25-29 + "resume", "rf.sql", "scn.lamp", "skip", "sql type", # 25-29 "step", "tot", "tx pwr", "tx save", "vfo.spl", # 30-34 "vox", "wfm.rcv", "wide/nar", "wx alert", "scramble" # 35-39 ] + def __init__(self): self.add_paramdesc("misc", ("compander", "Compander", ["ON", "OFF"])) -