From 3506133188281271f6a6b23702b5e6f6ae0ad7e5 Mon Sep 17 00:00:00 2001 From: Stephen Hurd Date: Fri, 23 Jun 2023 01:00:35 -0400 Subject: [PATCH] Overhaul of Kenwood D7 Driver Removes dependency on kenwood_live, and implements a separate radio class for D7 series radios. Moves globals into the classes - This allows finer control with subclassing Clean up I/O - Don't require a timeout after each command, speeding up access significantly - Read all "garbage" bytes at the start rather than "just" 25 Add generic support for special channels - Used for scan memories and call memories in the D7s Makes settings easier to implement - Data-driven description of settings with built-in support for bools, ints, strings, lists, and maps, which is enough for most settings - Also supports custom classes for more complex settings, used for positions, DTMF memories, and programmable VFOs currently - Allows for settings to depend on other settings Used for automatic simplex check and tone alert settings Add full menu structure from the manuals. - All settings listed in the manuals that can be controled via serial port are now exposed in Chirp --- chirp/drivers/kenwood_d7.py | 2521 +++++++++++++++++++++++++++++++---- 1 file changed, 2254 insertions(+), 267 deletions(-) diff --git a/chirp/drivers/kenwood_d7.py b/chirp/drivers/kenwood_d7.py index 0769ee07d..ebad169e4 100644 --- a/chirp/drivers/kenwood_d7.py +++ b/chirp/drivers/kenwood_d7.py @@ -13,332 +13,2319 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from chirp.drivers.kenwood_live import KenwoodOldLiveRadio, MODES, STEPS, \ - DUPLEX, LOG, get_tmode -from chirp import chirp_common, directory, util +import logging +import math +import os +import threading + +from chirp import chirp_common, directory, errors, util from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueInteger, RadioSettingValueBoolean, \ - RadioSettingValueString, RadioSettingValueList, RadioSettings + RadioSettingValueString, RadioSettingValueList, RadioSettingValueMap, \ + RadioSettings, RadioSettingValueFloat +LOG = logging.getLogger(__name__) -@directory.register -class THD7Radio(KenwoodOldLiveRadio): - """Kenwood TH-D7""" - MODEL = "TH-D7" + +class KenwoodD7Position(RadioSettingGroup): + def __init__(self, name, shortname, radio, *args, **kwargs): + super().__init__(name, shortname, *args, **kwargs) + self._radio = radio + if ' ' in name: + index = name.split(" ", 1)[1] + else: + index = '' + rs = RadioSetting("latd%s" % index, "Latitude degrees", + RadioSettingValueInteger(0, 90, 0)) + self.append(rs) + rs = RadioSetting("latm%s" % index, "Latitude minutes", + RadioSettingValueFloat(0, 59.99, 0, + resolution=0.01, + precision=2)) + self.append(rs) + rs = RadioSetting("south%s" % index, "South", + RadioSettingValueBoolean(False)) + self.append(rs) + rs = RadioSetting("longd%s" % index, "Longitude degrees", + RadioSettingValueInteger(0, 180, 0)) + self.append(rs) + rs = RadioSetting("longm%s" % index, "Longitude minutes", + RadioSettingValueFloat(0, 59.99, 0, + resolution=0.01, precision=2)) + self.append(rs) + rs = RadioSetting("west%s" % index, "West", + RadioSettingValueBoolean(False)) + self.append(rs) + + def kenwood_d7_read_value(self): + index = self._get_index() + name = self.get_name() + rawval = self._radio._kenwood_get(name)[1] + curr_value = int(rawval[0:2]) + self["latd%s" % index].value.set_value(curr_value) + curr_value = int(rawval[2:6]) / 100 + self["latm%s" % index].value.set_value(curr_value) + curr_value = int(rawval[6:8]) + self["south%s" % index].value.set_value(curr_value) + curr_value = int(rawval[8:11]) + self["longd%s" % index].value.set_value(curr_value) + curr_value = int(rawval[11:15]) / 100 + self["longm%s" % index].value.set_value(curr_value) + curr_value = int(rawval[15:17]) + self["west%s" % index].value.set_value(curr_value) + self['latd%s' % index].value._has_changed = False + self['latm%s' % index].value._has_changed = False + self['south%s' % index].value._has_changed = False + self['longd%s' % index].value._has_changed = False + self['longm%s' % index].value._has_changed = False + self['west%s' % index].value._has_changed = False + + def _get_index(self): + name = self.get_name() + if ' ' in name: + return name.split(" ", 1)[1] + return '' + + def changed(self): + index = self._get_index() + if self['latd%s' % index].changed(): + return True + if self['latm%s' % index].changed(): + return True + if self['south%s' % index].changed(): + return True + if self['longd%s' % index].changed(): + return True + if self['longm%s' % index].changed(): + return True + if self['west%s' % index].changed(): + return True + return False + + def kenwood_d7_set_value(self): + index = self._get_index() + latd = self['latd%s' % index].value.get_value() + latm = self['latm%s' % index].value.get_value() + latmd = int(round(math.modf(latm)[0] * 100)) + latm = int(round(latm)) + south = int(self['south%s' % index].value.get_value()) + longd = self['longd%s' % index].value.get_value() + longm = self['longm%s' % index].value.get_value() + longmd = int(round(math.modf(longm)[0] * 100)) + longm = int(round(longm)) + west = int(self['west%s' % index].value.get_value()) + args = '%s%02d%02d%02d%02d%03d%02d%02d%02d' % (index, + latd, latm, latmd, + south, + longd, longm, longmd, + west) + if index != '': + index = ' ' + index + self._radio._kenwood_set('MP%s' % index, args) + # TODO: Hacking up _has_changed is bad... but we don't want + # to re-send all the set commands each time either. + self['latd%s' % index].value._has_changed = False + self['latm%s' % index].value._has_changed = False + self['south%s' % index].value._has_changed = False + self['longd%s' % index].value._has_changed = False + self['longm%s' % index].value._has_changed = False + self['west%s' % index].value._has_changed = False + + +class KenwoodD7DTMFMemory(RadioSettingGroup): + DTMF_CHARSET = "ABCD#*0123456789 " + + def __init__(self, name, shortname, radio, *args, **kwargs): + super().__init__(name, shortname, *args, **kwargs) + self._radio = radio + self._name = name + index = self._get_index() + rs = RadioSetting("DMN %s" % index, "Memory Name", + RadioSettingValueString(0, 8, '', False)) + self.append(rs) + rs = RadioSetting("DM %s" % index, "Memory Value", + RadioSettingValueString(0, 16, '', False, + self.DTMF_CHARSET)) + self.append(rs) + + def _get_index(self): + return "%02d" % int(self._name.split(" ", 1)[1]) + + def kenwood_d7_read_value(self): + index = self._get_index() + value = self._radio._kenwood_get("DM %s" % index)[1] + value = value.replace("E", "*").replace("F", "#") + vname = self._radio._kenwood_get("DMN %s" % index)[1] + self["DMN %s" % index].value.set_value(vname) + self["DMN %s" % index].value._has_changed = False + self["DM %s" % index].value.set_value(value) + self["DM %s" % index].value._has_changed = False + + def changed(self): + for element in self: + if element.changed(): + return True + return False + + def kenwood_d7_set_value(self): + for element in self: + if not element.changed(): + continue + newval = element.value.get_value() + newval = newval.replace("*", "E").replace("#", "F") + self._radio._kenwood_set(element.get_name(), + newval) + element.value._has_changed = False + + +class KenwoodD7ProgrammableVFOs(RadioSettingGroup): + def __init__(self, name, shortname, radio, *args, **kwargs): + self._radio = radio + super().__init__(name, shortname, *args, **kwargs) + for optname, index, minimum, maximum in self._radio._PROGRAMMABLE_VFOS: + group = RadioSettingGroup("PV %d" % index, optname) + self.append(group) + rs = RadioSetting("PV %dL" % index, "Lower Limit", + RadioSettingValueInteger(minimum, maximum, + minimum)) + group.append(rs) + rs = RadioSetting("PV %dU" % index, "Upper Limit", + RadioSettingValueInteger(minimum, maximum, + maximum)) + group.append(rs) + + def _get_index(self, element): + name = element.get_name() + return int(name.split(" ", 1)[1]) + + def kenwood_d7_read_value(self): + for element in self: + index = self._get_index(element) + rv = self._radio._kenwood_get("PV %d" % index)[1] + lower, upper = rv.split(',', 1) + lower = int(lower) + upper = int(upper) + pvdl = "PV %dL" % index + pvdu = "PV %dU" % index + element[pvdl].value.set_value(lower) + element[pvdl].value._has_changed = False + element[pvdu].value.set_value(upper) + element[pvdu].value._has_changed = False + + def changed(self): + for element in self: + for bound in element: + if bound.changed(): + return True + return False + + # TODO: Custom validator things... + + def kenwood_d7_set_value(self): + for element in self: + index = self._get_index(element) + pvdl = "PV %dL" % index + pvdu = "PV %dU" % index + if element[pvdl].changed() or element[pvdu].changed(): + lower = element[pvdl].value.get_value() + upper = element[pvdu].value.get_value() + args = "%05d,%05d" % (lower, upper) + self._radio._kenwood_set(element.get_name(), args) + element[pvdl].value._has_changed = False + element[pvdu].value._has_changed = False + + +class KenwoodD7Family(chirp_common.LiveRadio): + VENDOR = "Kenwood" + MODEL = "" + NEEDS_COMPAT_SERIAL = False HARDWARE_FLOW = False - _kenwood_split = True - _upper = 199 - - _BEP_OPTIONS = ["Off", "Key", "Key+Data", "All"] - _POSC_OPTIONS = ["Off Duty", "Enroute", "In Service", "Returning", - "Committed", "Special", "Priority", "Emergency"] - - _SETTINGS_OPTIONS = { - "BAL": ["4:0", "3:1", "2:2", "1:3", "0:4"], - "BEP": None, - "BEPT": ["Off", "Mine", "All New"], # D700 has fourth "All" - "DS": ["Data Band", "Both Bands"], - "DTB": ["A", "B"], - "DTBA": ["A", "B", "A:TX/B:RX"], # D700 has fourth A:RX/B:TX - "DTX": ["Manual", "PTT", "Auto"], - "ICO": ["Kenwood", "Runner", "House", "Tent", "Boat", "SSTV", - "Plane", "Speedboat", "Car", "Bicycle"], - "MNF": ["Name", "Frequency"], - "PKSA": ["1200", "9600"], - "POSC": None, - "PT": ["100ms", "200ms", "500ms", "750ms", - "1000ms", "1500ms", "2000ms"], - "SCR": ["Time", "Carrier", "Seek"], - "SV": ["Off", "0.2s", "0.4s", "0.6s", "0.8s", "1.0s", - "2s", "3s", "4s", "5s"], - "TEMP": ["F", "C"], - "TXI": ["30sec", "1min", "2min", "3min", "4min", "5min", - "10min", "20min", "30min"], - "UNIT": ["English", "Metric"], - "WAY": ["Off", "6 digit NMEA", "7 digit NMEA", "8 digit NMEA", - "9 digit NMEA", "6 digit Magellan", "DGPS"], - } - - def get_features(self): - rf = chirp_common.RadioFeatures() - rf.has_settings = True - rf.has_dtcs = False - rf.has_dtcs_polarity = False - rf.has_bank = False - rf.has_mode = True - rf.has_tuning_step = False - rf.can_odd_split = True - rf.valid_duplexes = ["", "-", "+", "split"] - rf.valid_modes = list(MODES.values()) - rf.valid_tmodes = ["", "Tone", "TSQL"] - rf.valid_characters = \ - chirp_common.CHARSET_ALPHANUMERIC + "/.-+*)('&%$#! ~}|{" - rf.valid_name_length = 7 - rf.valid_tuning_steps = STEPS - rf.memory_bounds = (0, self._upper) - return rf + _ARG_DELIMITER = " " + _BAUDS = [9600] + _CMD_DELIMITER = "\r" + _DISABLE_AI_CMD = ('AI', '0') + _DUPLEX = {0: "", 1: "+", 2: "-"} + _HAS_NAME = True + _LOCK = threading.Lock() + _MODES = {0: "FM", 1: "AM"} + _NOCACHE = "CHIRP_NOCACHE" in os.environ + _SPLIT = True + _STEPS = (5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0) + _TONES = chirp_common.OLD_TONES + _LOWER = 0 + _UPPER = 199 + + _CALL_CHANS = ("VHF Call", "UHF Call") + _SPECIAL_CHANS = ("L0", "U0", + "L1", "U1", + "L2", "U2", + "L3", "U3", + "L4", "U4", + "L5", "U5", + "L6", "U6", + "L7", "U7", + "L8", "U8", + "L9", "U9", + *_CALL_CHANS) + + # These are used more than once in _COMMON_SETTINGS + _BOOL = {'type': 'bool'} + _DATA_BAND = {'type': 'list', + 'values': ("A", "B", "A:TX/B:RX", "A:RX/B:TX")} + _DTMF_MEMORY = {'type': KenwoodD7DTMFMemory} + _ON_OFF_DUAL_MAP = {'type': 'map', + 'map': (("Off", "0"), ("On", "1,0")), + 'set_requires': (('DL', True),), + 'get_requires': (('DL', True),)} + _POSITION = {'type': KenwoodD7Position} + _SSTV_CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 !?-/" + _SSTV_COLOURS = {'type': 'list', + 'values': ("Black", "Blue", "Red", "Magenta", "Green", + "Cyan", "Yellow", "White")} + _STATUS_TEXT = {'type': 'string', 'max_len': 32} + _SQUELCH = {'type': 'list', 'digits': 2, + 'values': ("Open", "1", "2", "3", "4", "5")} + # Settings that are shared by at least two radios. + # If a third radio does not have the setting at all, the presence + # here won't matter. If it has it but with differrent parameters, + # it can be overridden in the subclass _SETTINGS dict. + _COMMON_SETTINGS = { + "AIP": _BOOL, + "ARL": {'type': 'map', + 'map': (('Off', '0000'),) + tuple( + [('%i' % x, '%04i' % x) + for x in range(10, 2510, 10)])}, + "ARLM": {'type': 'string', 'max_len': 45}, + "AMGG": {'type': 'string', 'max_len': 45, + 'charset': "ABCDEFGHIJKLMNOPQRSTUVWXYZ*,-0123456789"}, + "AMR": _BOOL, + "APO": {'type': 'list', + 'values': ("Off", "30min", "60min")}, + "ARO": _BOOL, + # ASC x requires that the specified VFO is enabled on TH-D7G + # (ie: dual is on or VFO is current) for both fetch and set + # Easiest to just enable DL before set/read + "ASC 0": _ON_OFF_DUAL_MAP, + "ASC 1": _ON_OFF_DUAL_MAP, + "BAL": {'type': 'list', + 'values': ("4:0", "3:1", "2:2", "1:3", "0:4")}, + "BC": {'type': 'list', + 'values': ("A", "B")}, + "BCN": _BOOL, + # BEL x requires that the specified VFO is enabled on TH-D7G + # (ie: dual is on or VFO is current) for both fetch and set + # Easiest to just enable DL before set/read + "BEL 0": _ON_OFF_DUAL_MAP, + "BEL 1": _ON_OFF_DUAL_MAP, + "BEP": {'type': 'list', + 'values': ("Off", "Key", "Key+Data", "All")}, + "BEPT": {'type': 'list', + 'values': ("Off", "Mine", "All New", "All")}, + "CH": _BOOL, + "CKEY": {'type': 'list', + 'values': ("Call", "1750Hz")}, + "CNT": {'type': 'integer', 'min': 1, 'max': 16}, + "DL": _BOOL, + "DS": {'type': 'list', + 'values': ("Data Band", "Both Bands")}, + "DTB": _DATA_BAND, + "DTBA": _DATA_BAND, + "dtmfmem 00": _DTMF_MEMORY, + "dtmfmem 01": _DTMF_MEMORY, + "dtmfmem 02": _DTMF_MEMORY, + "dtmfmem 03": _DTMF_MEMORY, + "dtmfmem 04": _DTMF_MEMORY, + "dtmfmem 05": _DTMF_MEMORY, + "dtmfmem 06": _DTMF_MEMORY, + "dtmfmem 07": _DTMF_MEMORY, + "dtmfmem 08": _DTMF_MEMORY, + "dtmfmem 09": _DTMF_MEMORY, + "DTX": {'type': 'list', + 'values': ("Manual", "PTT", "Auto")}, + "ELK": {'type': 'bool'}, + "GU": {'type': 'list', + 'values': ("Not Used", "NMEA", "NMEA96")}, + # This isn't exactly shared, but each radio adds to this list + "ICO": {'type': 'map', + 'map': (('Kenwood', '0,0'), + ('Runner', '0,1'), + ('House ', '0,2'), + ('Tent', '0,3'), + ('Boat', '0,4'), + ('SSTV', '0,5'), + ('Plane', '0,6'), + ('Speedboat', '0,7'), + ('Car', '0,8'), + ('Bicycle', '0,9'), + ('TRIANGLE(DF station)', '0,A'), + ('Jeep', '0,B'), + ('Recreational Vehicle', '0,C'), + ('Truck ', '0,D'), + ('Van', '0,E'))}, + "KILO": {'type': 'list', + 'values': ("Miles", "Kilometers")}, + "LK": _BOOL, + "LMP": _BOOL, + "MAC": _SSTV_COLOURS, + "MES": {'type': 'string', 'max_len': 8}, + # NOTE: Requires memory mode to be active + "MNF": {'type': 'list', + 'values': ("Name", "Frequency")}, + "MP 1": _POSITION, + "MP 2": _POSITION, + "MP 3": _POSITION, + "MYC": {'type': 'string', 'max_len': 9}, + "PAMB": {'type': 'list', + 'values': ("Off", "1 Digit", "2 Digits", "3 Digits", + "4 Digits")}, + "PKSA": {'type': 'list', + 'values': ("1200", "9600")}, + "POSC": {'type': 'list', + 'values': ("Off Duty", "Enroute", "In Service", "Returning", + "Committed", "Special", "Priority", "CUSTOM 0", + "CUSTOM 1", "CUSTOM 2", "CUSTOM 4", "CUSTOM 5", + "CUSTOM 6", "Emergency")}, + "PP": {'type': 'string', 'max_len': 32}, + "PT": {'type': 'list', + 'values': ("100ms", "200ms", "500ms", "750ms", + "1000ms", "1500ms", "2000ms")}, + "PV": {'type': KenwoodD7ProgrammableVFOs}, + "RSC": _SSTV_COLOURS, + "RSV": {'type': 'string', 'max_len': 10, 'charset': _SSTV_CHARSET}, + "SCC": {'type': 'string', 'max_len': 8}, + "SCR": {'type': 'list', + 'values': ("Time", "Carrier", "Seek")}, + "SCT": {'type': 'string', 'max_len': 8}, + "SKTN": {'type': 'map', + 'map': tuple([(str(x), "%02d" % (ind + 1)) + for ind, x in enumerate(_TONES) if x != 69.3])}, + "SMC": _SSTV_COLOURS, + "SMSG": {'type': 'string', 'max_len': 9, 'charset': _SSTV_CHARSET}, + "SMY": {'type': 'string', 'max_len': 8, 'charset': _SSTV_CHARSET}, + "SQ 0": _SQUELCH, + "SQ 1": _SQUELCH, + "STAT 1": _STATUS_TEXT, + "STAT 2": _STATUS_TEXT, + "STAT 3": _STATUS_TEXT, + "STXR": {'type': 'list', + 'values': ("Off", "1/1", "1/2", "1/3", "1/4", "1/5", "1/6", + "1/7", "1/8")}, + "SV": {'type': 'list', + 'values': ("Off", "0.2s", "0.4s", "0.6s", "0.8s", "1.0s", + "2s", "3s", "4s", "5s")}, + "TEMP": {'type': 'list', + 'values': ("°F", "°C")}, + "TH": _BOOL, + "TNC": _BOOL, + "TSP": _BOOL, + "TXD": {'type': 'map', + 'map': (("100ms", "1"), ("200ms", "2"), ("300ms", "3"), + ("400ms", "4"), ("500ms", "5"), ("750ms", "6"), + ("1000ms", "7"))}, + "TXH": _BOOL, + "TXI": {'type': 'list', + 'values': ("30sec", "1min", "2min", "3min", "4min", "5min", + "10min", "20min", "30min")}, + "TXS": _BOOL, + "TZ": {'type': 'list', + 'values': ("UTC - 12:00", "UTC - 11:30", "UTC - 11:00", + "UTC - 10:30", "UTC - 10:00", "UTC - 9:30", + "UTC - 9:00", "UTC - 8:30", "UTC - 8:00", + "UTC - 7:30", "UTC - 7:00", "UTC - 6:30", + "UTC - 6:00", "UTC - 5:30", "UTC - 5:00", + "UTC - 4:30", "UTC - 4:00", "UTC - 3:30", + "UTC - 3:00", "UTC - 2:30", "UTC - 2:00", + "UTC - 1:30", "UTC - 1:00", "UTC - 0:30", + "UTC", "UTC + 0:30", "UTC + 1:00", "UTC + 1:30", + "UTC + 2:00", "UTC + 2:30", "UTC + 3:00", + "UTC + 3:30", "UTC + 4:00", "UTC + 4:30", + "UTC + 5:00", "UTC + 5:30", "UTC + 6:00", + "UTC + 6:30", "UTC + 7:00", "UTC + 7:30", + "UTC + 8:00", "UTC + 8:30", "UTC + 9:00", + "UTC + 9:30", "UTC + 10:00", "UTC + 10:30", + "UTC + 11:00", "UTC + 11:30", "UTC + 12:00")}, + "UPR": {'type': 'string', 'max_len': 9, + 'charset': "ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789"}, + "VCS": _BOOL, + "WAY": {'type': 'list', + 'values': ("Off", "6 digit NMEA", "7 digit NMEA", + "8 digit NMEA", "9 digit NMEA", "6 digit Magellan", + "DGPS")}} + + _PROGRAMMABLE_VFOS = ( + ("Band A, 118MHz Sub-Band", 1, 118, 135), + ("Band A, VHF Sub-Band", 2, 136, 173), + ("Band B, VHF Sub-Band", 3, 144, 147), + ("Band B, UHF Sub-Band", 6, 400, 479)) + + def __init__(self, *args, **kwargs): + chirp_common.LiveRadio.__init__(self, *args, **kwargs) + + # Clear the caches + self._memcache = {} + self._setcache = {} + self._baud = 9600 + self._cur_setting = 0 + self._setting_count = 0 + self._vfo = 0 + if self.pipe: + self.pipe.timeout = 0.5 + radio_id = self._get_id() + if hasattr(self, '_ID_STRING'): + id_str = self._ID_STRING + else: + id_str = self.MODEL.split(" ")[0] + if radio_id != id_str: + raise Exception("Radio reports %s (not %s)" % (radio_id, + id_str)) + + self._command(*self._DISABLE_AI_CMD) + + # This returns the index value for CR/CW commands, not the index + # in the memory list grid + def _call_index(self, memid_or_index): + if isinstance(memid_or_index, int): + memid = self._index_to_memid(memid_or_index) + else: + memid = memid_or_index + return self._CALL_CHANS.index(memid) + + # This returns the memid from the index value in CR/CW commands, not + # the index in the memory list grid + def _call_index_to_memid(self, index): + return self._CALL_CHANS[index] + + def _cmd_get_memory_name(self, memid): + return "MNA", "%i,%s" % (self._vfo, memid) + + def _cmd_get_memory_or_split(self, memid, split): + sd = split and 1 or 0 + if self._is_call(memid): + return "CR", "%d,%d" % (self._call_index(memid), split) + return "MR", "%i,%d,%s" % (self._vfo, sd, memid) + + def _cmd_set_memory_name(self, memid, name): + if self._is_call(memid): + return None + return "MNA", "%i,%s,%s" % (self._vfo, memid, name) + + def _cmd_set_memory_or_split(self, memid, memory, split): + if not memory.empty: + spec = "," + ",".join(self._make_mem_spec(memid, memory)) + else: + spec = '' + sd = split and 1 or 0 + if (self._is_call(memid)): + return "CW", "%d,%d%s" % (self._call_index(memid), sd, spec) + return "MW", "%i,%d,%s%s" % (self._vfo, sd, memid, spec) + + def _count_settings(self, entries): + for entry in entries: + if (type(entry[1]) is tuple): + self._count_settings(entry[1]) + else: + self._setting_count += 1 + + def _create_group(self, entries, grp=None): + if grp is None: + grp = RadioSettings() + for entry in entries: + if (type(entry[1]) is tuple): + subgrp = RadioSettingGroup(entry[0].lower(), entry[0]) + grp.append(self._create_group(entry[1], subgrp)) + else: + setting = self._get_setting_data(entry[0]) + if setting['type'] == 'undefined': + continue + if setting['type'] == 'list': + rsvl = RadioSettingValueList(setting['values']) + rs = RadioSetting(entry[0], entry[1], rsvl) + elif setting['type'] == 'bool': + rs = RadioSetting(entry[0], entry[1], + RadioSettingValueBoolean(False)) + elif setting['type'] == 'string': + params = {} + if 'charset' in setting: + params['charset'] = setting['charset'] + else: + params['charset'] = chirp_common.CHARSET_ASCII + minlen = 'min_len' in setting and setting['min_len'] or 0 + dummy = params['charset'][0] * minlen + rsvs = RadioSettingValueString(minlen, + setting['max_len'], + dummy, + False, **params) + rs = RadioSetting(entry[0], entry[1], rsvs) + elif setting['type'] == 'integer': + rs = RadioSetting(entry[0], entry[1], + RadioSettingValueInteger(setting['min'], + setting['max'], + setting['min'])) + elif setting['type'] == 'map': + rsvs = RadioSettingValueMap(setting['map'], + setting['map'][0][1]) + rs = RadioSetting(entry[0], entry[1], rsvs) + else: + rs = setting['type'](entry[0], entry[1], self) + rs.kenwood_d7_loaded = False + grp.append(rs) + self._setcache[entry[0]] = rs + return grp + + def _do_prerequisite(self, cmd, get, do): + if get: + arg = 'get_requires' + else: + arg = 'set_requires' + setting = self._get_setting_data(cmd) + if arg not in setting: + return + # Undo prerequisites in the reverse order they were applied + if not do: + it = reversed(setting[arg]) + else: + it = iter(setting[arg]) + for prereq, value in it: + entry = self._setcache[prereq] + if not entry.kenwood_d7_loaded: + self._refresh_setting(entry) + if do: + entry.kenwood_d7_old_value = entry.value.get_value() + entry.value.set_value(value) + else: + entry.value.set_value(entry.kenwood_d7_old_value) + sdata = self._get_setting_data(prereq) + self._set_setting(prereq, sdata, entry) + + def _drain_input(self): + oldto = self.pipe.timeout + self.pipe.timeout = 0 + # Read until a timeout + while len(self.pipe.read(1)): + pass + self.pipe.timeout = oldto + + def _empty_memory(self, memid): + mem = chirp_common.Memory() + if self._is_special(memid): + mem.extd_number = memid + mem.number = self._memid_to_index(memid) + mem.empty = True + mem.immutable = self._get_immutable(memid) + return mem + + def _get_id(self): + """Get the ID of the radio attached to @ser""" + + for i in self._BAUDS: + LOG.info("Trying ID at baud %i" % i) + self.pipe.baudrate = i + # Try to force an error + self.pipe.write(self._CMD_DELIMITER.encode('cp1252')) + # Wait for it to be sent + self.pipe.flush() + self._drain_input() + try: + resp = self._command("ID") + except UnicodeDecodeError: + # If we got binary here, we are using the wrong rate + # or not talking to a kenwood live radio. + continue + + ret = self._parse_id_response(resp) + if ret is not None: + self._baud = i + return ret + + # Radio responded in the right baud rate, + # but threw an error because of all the crap + # we have been hurling at it. Retry the ID at this + # baud rate, which will almost definitely work. + if "?" in resp: + resp = self._command("ID") + ret = self._parse_id_response(resp) + if ret is not None: + self._baud = i + return ret + raise errors.RadioError("No response from radio") + + def _get_immutable(self, memid_or_index): + if self._is_call(memid_or_index): + return ['name', 'skip'] + if self._is_pscan(memid_or_index): + return ['skip'] + return [] + + def _get_setting_data(self, setting): + if setting in self._SETTINGS: + return self._SETTINGS[setting] + if setting in self._COMMON_SETTINGS: + return self._COMMON_SETTINGS[setting] + if (setting.upper() == setting): + LOG.debug("Undefined setting: %s" % setting) + return {'type': 'undefined'} + + def _get_setting_digits(self, setting_data): + if 'digits' in setting_data: + return setting_data['digits'] + if setting_data['type'] == 'integer': + return len(str(setting_data['max'])) + if setting_data['type'] == 'list': + return len(str(len(setting_data['values']))) + raise TypeError('Setting data should not have digits') + + def _get_tmode(self, tone, ctcss, dcs): + """Get the tone mode based on the values of the tone, ctcss, dcs""" + if dcs and int(dcs) == 1: + return "DTCS" + elif int(ctcss): + return "TSQL" + elif int(tone): + return "Tone" + else: + return "" + + def _index_to_memid(self, index): + if not isinstance(index, int): + raise errors.RadioError("%s passed as mememory index" + % str(type(index))) + if index <= self._UPPER: + return "%03d" % index + index -= self._UPPER + index -= 1 + return self._SPECIAL_CHANS[index] + + def _is_call(self, memid_or_index): + if isinstance(memid_or_index, int): + memid = self._index_to_memid(memid_or_index) + else: + memid = memid_or_index + return memid in self._CALL_CHANS + + def _is_pscan(self, memid_or_index): + if isinstance(memid_or_index, int): + memid = self._index_to_memid(memid_or_index) + else: + memid = memid_or_index + if memid[0] == 'L' or memid[0] == 'U': + if memid[1:].isdigit(): + return True + return False + + def _is_special(self, memid_or_index): + if isinstance(memid_or_index, str): + index = self._memid_to_index(memid_or_index) + else: + index = memid_or_index + return index > self._UPPER + + def _iserr(self, result): + """Returns True if the @result from a radio is an error""" + return result in ["N", "?"] + + def _keep_reading(self, result): + return False + + def _kenwood_get(self, cmd): + self._do_prerequisite(cmd, True, True) + ret = None + if " " in cmd: + suffix = cmd.split(" ", 1)[1] + resp = self._kenwood_simple_get(cmd) + if resp[1][0:len(suffix)+1] == suffix + ',': + resp = (cmd, resp[1][len(suffix)+1:]) + ret = resp + else: + raise errors.RadioError("Command %s response value '%s' " + "unusable" % (cmd, resp[1])) + else: + ret = self._kenwood_simple_get(cmd) + self._do_prerequisite(cmd, True, False) + return ret + + def _kenwood_get_bool(self, cmd): + _cmd, result = self._kenwood_get(cmd) + return result == "1" + + def _kenwood_get_int(self, cmd): + _cmd, result = self._kenwood_get(cmd) + return int(result) + + def _kenwood_set_success(self, cmd, modcmd, value, response): + if response[:len(modcmd)] == modcmd: + return True + return False + + def _kenwood_set(self, cmd, value): + self._do_prerequisite(cmd, False, True) + if " " in cmd: + resp = self._command(cmd + "," + value) + modcmd = cmd.split(" ", 1)[0] + else: + resp = self._command(cmd, value) + modcmd = cmd + self._do_prerequisite(cmd, False, False) + if self._kenwood_set_success(cmd, modcmd, value, resp): + return + raise errors.RadioError("Radio refused to set %s" % cmd) + + def _kenwood_set_bool(self, cmd, value): + return self._kenwood_set(cmd, str(int(value))) - def _make_mem_spec(self, mem): + def _kenwood_set_int(self, cmd, value, digits=1): + return self._kenwood_set(cmd, ("%%0%ii" % digits) % value) + + def _kenwood_simple_get(self, cmd): + resp = self._command(cmd) + if " " in resp: + return resp.split(" ", 1) + else: + if resp == cmd: + return [resp, ""] + else: + raise errors.RadioError("Radio refused to return %s" % cmd) + + def _mem_spec_fixup(self, spec, memid, mem): + pass + + def _make_mem_spec(self, memid, mem): if mem.duplex in " -+": - duplex = util.get_dict_rev(DUPLEX, mem.duplex) + duplex = util.get_dict_rev(self._DUPLEX, mem.duplex) offset = mem.offset else: duplex = 0 offset = 0 - spec = ( + spec = [ "%011i" % mem.freq, - "%X" % STEPS.index(mem.tuning_step), + "%X" % self._STEPS.index(mem.tuning_step), "%i" % duplex, "0", "%i" % (mem.tmode == "Tone"), "%i" % (mem.tmode == "TSQL"), "", # DCS Flag - "%02i" % (self._kenwood_valid_tones.index(mem.rtone) + 1), + "%02i" % (self._TONES.index(mem.rtone) + 1), "", # DCS Code - "%02i" % (self._kenwood_valid_tones.index(mem.ctone) + 1), + "%02i" % (self._TONES.index(mem.ctone) + 1), "%09i" % offset, - "%i" % util.get_dict_rev(MODES, mem.mode), - "%i" % ((mem.skip == "S") and 1 or 0)) + "%i" % util.get_dict_rev(self._MODES, mem.mode)] + if not self._is_call(memid): + spec.append("%i" % ((mem.skip == "S") and 1 or 0)) + self._mem_spec_fixup(spec, memid, mem) + + return tuple(spec) + + def _make_split_spec(self, mem): + return ("%011i" % mem.offset, "0") - return spec + def _memid_to_index(self, memid): + if not isinstance(memid, str): + raise errors.RadioError("%s passed as mememory id" + % str(type(memid))) + if memid in self._SPECIAL_CHANS: + return self._SPECIAL_CHANS.index(memid) + self._UPPER + 1 + return int(memid) - def _parse_mem_spec(self, spec): + def _parse_id_response(self, resp): + # most kenwood VHF+ radios + if " " in resp: + return resp.split(" ")[1] + return None + + def _parse_mem_fixup(self, mem, spec): + pass + + def _parse_mem(self, result): mem = chirp_common.Memory() - mem.number = int(spec[2]) + value = result.split(" ")[1] + spec = value.split(",") + if len(spec) == 14: # This is a Call memory + spec.insert(1, 0) # Add "bank" + spec.insert(15, 0) # Add "skip" + memid = self._call_index_to_memid(int(spec[0])) + mem.extd_number = memid + mem.number = self._memid_to_index(mem.extd_number) + elif spec[2].isdigit(): # Normal memory + mem.number = int(spec[2]) + memid = self._index_to_memid(mem.number) + else: # Program Scan memory + memid = spec[2] + mem.extd_number = memid + mem.number = self._memid_to_index(mem.extd_number) + mem.immutable = self._get_immutable(memid) mem.freq = int(spec[3], 10) - mem.tuning_step = STEPS[int(spec[4], 16)] - mem.duplex = DUPLEX[int(spec[5])] - mem.tmode = get_tmode(spec[7], spec[8], spec[9]) - mem.rtone = self._kenwood_valid_tones[int(spec[10]) - 1] - mem.ctone = self._kenwood_valid_tones[int(spec[12]) - 1] - if spec[11] and spec[11].isdigit(): - mem.dtcs = chirp_common.DTCS_CODES[int(spec[11][:-1]) - 1] - elif spec[11] == '': - pass - else: - LOG.warn("Unknown or invalid DCS: %s" % spec[11]) + mem.tuning_step = self._STEPS[int(spec[4], 16)] + mem.duplex = self._DUPLEX[int(spec[5])] + mem.tmode = self._get_tmode(spec[7], spec[8], spec[9]) + mem.rtone = self._TONES[int(spec[10]) - 1] + mem.ctone = self._TONES[int(spec[12]) - 1] if spec[13]: mem.offset = int(spec[13]) else: mem.offset = 0 - mem.mode = MODES[int(spec[14])] - mem.skip = int(spec[15]) and "S" or "" - + mem.mode = self._MODES[int(spec[14])] + if 'skip' not in mem.immutable: + mem.skip = int(spec[15]) and "S" or "" + self._parse_mem_fixup(mem, spec) return mem - EXTRA_BOOL_SETTINGS = { - 'main': [("LMP", "Lamp")], - 'dtmf': [("TXH", "TX Hold")], - } - EXTRA_LIST_SETTINGS = { - 'main': [("BAL", "Balance"), - ("MNF", "Memory Display Mode")], - 'save': [("SV", "Battery Save")], - } - - def _get_setting_options(self, setting): - opts = self._SETTINGS_OPTIONS[setting] - if opts is None: - opts = getattr(self, '_%s_OPTIONS' % setting) - return opts + def _parse_split(self, mem, result): + value = result.split(" ")[1] + spec = value.split(",") + mem.duplex = "split" + # TODO: There's another parameter here... not sure what it is + if len(spec) == 14: # Call memory + mem.offset = int(spec[2]) + else: + mem.offset = int(spec[3]) + + def _refresh_setting(self, entry): + name = entry.get_name() + setting = self._get_setting_data(name) + # TODO: It would be nice to bump_wait_dialog here... + # Also, this is really only useful for the cli. :( + if self.status_fn: + status = chirp_common.Status() + status.cur = self._cur_setting + status.max = self._setting_count + status.msg = "Fetching %-30s" % entry.get_shortname() + # self.bump_wait_dialog(int(status.cur * 100 / status.max), + # "Fetching %s" % entry[1]) + self.status_fn(status) + self._cur_setting += 1 + if setting['type'] == 'list': + value = self._kenwood_get_int(name) + entry.value.set_index(value) + elif setting['type'] == 'bool': + value = self._kenwood_get_bool(name) + entry.value.set_value(value) + elif setting['type'] == 'string': + value = self._kenwood_get(name)[1] + entry.value.set_value(value) + elif setting['type'] == 'integer': + value = self._kenwood_get_int(name) + entry.value.set_value(value) + elif setting['type'] == 'map': + value = self._kenwood_get(name)[1] + entry.value.set_mem_val(value) + else: + entry.kenwood_d7_read_value() + if hasattr(entry, 'value'): + if hasattr(entry.value, '_has_changed'): + entry.value._has_changed = False + entry.kenwood_d7_loaded = True + + def _refresh_settings(self): + for entry in self._setcache.values(): + if not entry.kenwood_d7_loaded: + self._refresh_setting(entry) + + def _validate_memid(self, memid_or_index): + if isinstance(memid_or_index, int): + index = memid_or_index + memid = self._index_to_memid(memid_or_index) + else: + memid = memid_or_index + index = self._memid_to_index(memid_or_index) + if memid in self._SPECIAL_CHANS: + pass + elif index < self._LOWER or index > self._UPPER: + raise errors.InvalidMemoryLocation( + "Number must be between %i and %i" % (self._LOWER, + self._UPPER)) + return index, memid + + def _validate_memory(self, memory): + if memory.number is None: + return self._validate_memid(memory.extd_number) + return self._validate_memid(memory.number) + + def _set_setting(self, name, sdata, element): + if sdata['type'] == 'bool': + value = element.value.get_value() + self._kenwood_set_bool(name, value) + element.value._has_changed = False + elif sdata['type'] == 'integer': + value = element.value.get_value() + digits = self._get_setting_digits(sdata) + self._kenwood_set_int(name, value, digits) + element.value._has_changed = False + elif sdata['type'] == 'string': + value = element.value.get_value() + self._kenwood_set(name, value) + element.value._has_changed = False + elif sdata['type'] == 'list': + value = element.value.get_value() + digits = self._get_setting_digits(sdata) + self._kenwood_set_int(name, sdata['values'].index(value), + digits) + element.value._has_changed = False + elif sdata['type'] == 'map': + self._kenwood_set(name, element.value.get_mem_val()) + element.value._has_changed = False + # TODO: I would like to have tried this first then fetched + # value, but we can't use hasattr() on settings due to the + # Magic foo.value attribute in chirp/settings.py. Instead, + # I need to get_value() each time. :( + elif hasattr(element, 'kenwood_d7_set_value'): + element.kenwood_d7_set_value() + else: + raise TypeError('No way to set %s value' % name) + + def _command(self, cmd, *args): + """Send @cmd to radio via @ser""" + + # This lock is needed to allow clicking the settings tab while + # the memories are still loading. Most important with the TH-D7A + # and TH-D7A(G) with the 9600bps maximum. + with self._LOCK: + if args: + cmd += self._ARG_DELIMITER + self._ARG_DELIMITER.join(args) + cmd += self._CMD_DELIMITER + self._drain_input() + + LOG.debug("PC->RADIO: %s" % cmd.strip()) + self.pipe.write(cmd.encode('cp1252')) + cd = self._CMD_DELIMITER.encode('cp1252') + keep_reading = True + while keep_reading: + result = self.pipe.read_until(cd).decode('cp1252') + if result.endswith(self._CMD_DELIMITER): + keep_reading = self._keep_reading(result) + LOG.debug("RADIO->PC: %r" % result.strip()) + result = result[:-1] + else: + keep_reading = False + LOG.error("Timeout waiting for data") + + return result.strip() + + def erase_memory(self, memid_or_index): + index, memid = self._validate_memid(memid_or_index) + if memid not in self._memcache: + return + # TODO: Can't disable the menu item? + if self._is_call(memid): + # Raising an error just makes the memory errored + return + cmd = self._cmd_set_memory_or_split(memid, self._empty_memory(memid), + False) + resp = self._command(*cmd) + if self._iserr(resp): + raise errors.RadioError("Radio refused delete of %s" % memid) + del self._memcache[memid] + + def get_features(self, *args, **kwargs): + rf = chirp_common.RadioFeatures(*args, **kwargs) + rf.valid_tones = tuple([x for x in self._TONES if x != 69.3]) + rf.has_settings = True + rf.has_dtcs = False + rf.has_dtcs_polarity = False + rf.has_bank = False + rf.has_mode = True + rf.has_tuning_step = True + rf.can_odd_split = True + rf.valid_name_length = 8 + rf.valid_duplexes = ["", "-", "+", "split"] + rf.valid_modes = list(self._MODES.values()) + rf.valid_tmodes = ["", "Tone", "TSQL"] + rf.valid_characters = \ + chirp_common.CHARSET_ALPHANUMERIC + "/.-+*)('&%$#! ~}|{" + rf.valid_tuning_steps = self._STEPS + rf.memory_bounds = (self._LOWER, self._UPPER) + rf.valid_special_chans = self._SPECIAL_CHANS + return rf + + def get_memory(self, memid_or_index): + index, memid = self._validate_memid(memid_or_index) + if memid in self._memcache and not self._NOCACHE: + return self._memcache[memid] + + result = self._command(*self._cmd_get_memory_or_split(memid, + False)) + if result == "N": + mem = self._empty_memory(memid) + self._memcache[memid] = mem + return mem + elif self._ARG_DELIMITER not in result: + LOG.error("Not sure what to do with this: `%s'" % result) + raise errors.RadioError("Unexpected result returned from radio") + + mem = self._parse_mem(result) + self._memcache[memid] = mem + + if self._HAS_NAME: + cmd = self._cmd_get_memory_name(memid) + if cmd is not None: + result = self._command(*cmd) + if " " in result: + value = result.split(" ", 1)[1] + mem.name = value.split(",", 2)[2] + if mem.duplex == "" and self._SPLIT: + cmd = self._cmd_get_memory_or_split(memid, True) + if cmd is not None: + result = self._command(*cmd) + if " " in result: + self._parse_split(mem, result) + + return mem def get_settings(self): - main = RadioSettingGroup("main", "Main") - aux = RadioSettingGroup("aux", "Aux") - tnc = RadioSettingGroup("tnc", "TNC") - save = RadioSettingGroup("save", "Save") - display = RadioSettingGroup("display", "Display") - dtmf = RadioSettingGroup("dtmf", "DTMF") - radio = RadioSettingGroup("radio", "Radio", - aux, tnc, save, display, dtmf) - sky = RadioSettingGroup("sky", "SkyCommand") - aprs = RadioSettingGroup("aprs", "APRS") - - top = RadioSettings(main, radio, aprs, sky) - - bools = [("AMR", aprs, "APRS Message Auto-Reply"), - ("AIP", aux, "Advanced Intercept Point"), - ("ARO", aux, "Automatic Repeater Offset"), - ("BCN", aprs, "Beacon"), - ("CH", radio, "Channel Mode Display"), - # ("DIG", aprs, "APRS Digipeater"), - ("DL", main, "Dual"), - ("LK", main, "Lock"), - ("TSP", dtmf, "DTMF Fast Transmission"), - ] - - for setting, group, name in bools: - value = self._kenwood_get_bool(setting) - rs = RadioSetting(setting, name, - RadioSettingValueBoolean(value)) - group.append(rs) + if self._setting_count == 0: + self._count_settings(self._SETTINGS_MENUS) + ret = self._create_group(self._SETTINGS_MENUS) + self._cur_setting = 0 + self._refresh_settings() + status = chirp_common.Status() + status.cur = self._setting_count + status.max = self._setting_count + status.msg = "%-40s" % "Done" + self.status_fn(status) + return ret - lists = [("BEP", aux, "Beep"), - ("BEPT", aprs, "APRS Beep"), - ("DS", tnc, "Data Sense"), - ("DTB", tnc, "Data Band"), - ("DTBA", aprs, "APRS Data Band"), - ("DTX", aprs, "APRS Data TX"), - # ("ICO", aprs, "APRS Icon"), - ("PKSA", aprs, "APRS Packet Speed"), - ("POSC", aprs, "APRS Position Comment"), - ("PT", dtmf, "DTMF Speed"), - ("TEMP", aprs, "APRS Temperature Units"), - ("TXI", aprs, "APRS Transmit Interval"), - # ("UNIT", aprs, "APRS Display Units"), - ("WAY", aprs, "Waypoint Mode"), - ] - - for setting, group, name in lists: - value = self._kenwood_get_int(setting) - options = self._get_setting_options(setting) - rs = RadioSetting(setting, name, - RadioSettingValueList(options, - options[value])) - group.append(rs) + def set_memory(self, memory): + index, memid = self._validate_memory(memory) + if memory.empty: + self.erase_memory(memid) + LOG.debug("set_memory passed an empty memory, perhaps " + "you wanted erase_memory()?") + return + r1 = self._command(*self._cmd_set_memory_or_split(memid, memory, + False)) + if self._iserr(r1): + raise errors.InvalidDataError("Radio refused %s" % memid) + self._memcache[memid] = memory.dupe() + if self._HAS_NAME: + r2cmd = self._cmd_set_memory_name(memid, memory.name) + if r2cmd is not None: + r2 = self._command(*r2cmd) + if self._iserr(r2): + raise errors.InvalidDataError("Radio refused name %s: %s" % + (memid, repr(memory.name))) + if memory.duplex == "split" and self._SPLIT: + result = self._command(*self._cmd_set_memory_or_split(memid, + memory, + True)) + if self._iserr(result): + raise errors.InvalidDataError("Radio refused %s" % + memid) - for group_name, settings in self.EXTRA_BOOL_SETTINGS.items(): - group = locals()[group_name] - for setting, name in settings: - value = self._kenwood_get_bool(setting) - rs = RadioSetting(setting, name, - RadioSettingValueBoolean(value)) - group.append(rs) - - for group_name, settings in self.EXTRA_LIST_SETTINGS.items(): - group = locals()[group_name] - for setting, name in settings: - value = self._kenwood_get_int(setting) - options = self._get_setting_options(setting) - rs = RadioSetting(setting, name, - RadioSettingValueBoolean(value)) - group.append(rs) - - ints = [("CNT", display, "Contrast", 1, 16), - ] - for setting, group, name, minv, maxv in ints: - value = self._kenwood_get_int(setting) - rs = RadioSetting(setting, name, - RadioSettingValueInteger(minv, maxv, value)) - group.append(rs) + def set_settings(self, settings): + for element in settings: + name = element.get_name() + sdata = self._get_setting_data(name) + if sdata['type'] == 'undefined': + if isinstance(element, RadioSettingGroup): + self.set_settings(element) + continue + if not element.changed(): + continue + self._set_setting(name, sdata, element) - strings = [("MES", display, "Power-on Message", 8), - ("MYC", aprs, "APRS Callsign", 9), - ("PP", aprs, "APRS Path", 32), - ("SCC", sky, "SkyCommand Callsign", 8), - ("SCT", sky, "SkyCommand To Callsign", 8), - # ("STAT", aprs, "APRS Status Text", 32), - ] - for setting, group, name, length in strings: - _cmd, value = self._kenwood_get(setting) - rs = RadioSetting(setting, name, - RadioSettingValueString(0, length, value, False)) - group.append(rs) - return top +@directory.register +class THD7Radio(KenwoodD7Family): + """Kenwood TH-D7""" + MODEL = "TH-D7" + + _SETTINGS = { + "BEPT": {'type': 'list', + 'values': ("Off", "Mine", "All New")}, + "DTB": {'type': 'list', + 'values': ("A", "B")}, + "DTBA": {'type': 'list', + 'values': ("A", "B", "A:TX/B:RX", "A:RX/B:TX")}, + "GU": {'type': 'list', + 'values': ("Not Used", "NMEA")}, + "MP": {'type': 'position'}, + "STAT": {'type': 'string', 'max_len': 20}, + "TEMP": {'type': 'list', + 'values': ("Miles / °F", "Kilometers / °C")}} + + _SETTINGS_MENUS = ( + ('Main', + (("ASC 0", "Automatic Simplex Check Band A"), + ("ASC 1", "Automatic Simplex Check Band B"), + ("BAL", "Balance"), + ("BC", "Band"), + ("BEL 0", "Tone Alert Band A"), + ("BEL 1", "Tone Alert Band B"), + ("CH", "Channel Mode Display"), + ("DL", "Dual"), + ("LMP", "Lamp"), + ("LK", "Lock"), + # MNF requires that the *current* VFO be in MR mode + # Also, it has a direct key + # ("MNF", "Memory Display Mode"), + ("SQ 0", "Band A Squelch"), + ("SQ 1", "Band B Squelch"), + # We likely don't want to enable this from Chirp... + # ("TNC", "Packet Mode"))), + )), + ('Radio', + (('Display', + (("MES", "Power-ON Message"), + ("CNT", "Contrast"))), + ('Save', + (("SV", "Battery Saver Interval"), + ("APO", "Automatic Power Off (APO)"))), + ('DTMF', + (("dtmfmem 00", "Number Store #0"), + ("dtmfmem 01", "Number Store #1"), + ("dtmfmem 02", "Number Store #2"), + ("dtmfmem 03", "Number Store #3"), + ("dtmfmem 04", "Number Store #4"), + ("dtmfmem 05", "Number Store #5"), + ("dtmfmem 06", "Number Store #6"), + ("dtmfmem 07", "Number Store #7"), + ("dtmfmem 08", "Number Store #8"), + ("dtmfmem 09", "Number Store #9"), + ("TSP", "TX Speed"), + ("TXH", "TX Hold"), + ("PT", "Pause"))), + ('TNC', + (("DTB", "Data band select"), + ("DS", "DCD sense"))), + ('AUX', + (("ARO", "Automatic Repeater Offset"), + ("SCR", "Scan Resume"), + ("BEP", "Key Beep"), + ("ELK", "Tuning Enable"), + ("TXS", "TX Inhibit"), + ("AIP", "Advanced Intercept Point"), + ("TH", "TX Hold, 1750 Hz"), + ("XXX", "VHF band narrow TX deviation"))))), + ('APRS', + (("MYC", "My call sign"), + ("GU", "GPS receiver"), + # Untested... D7G and D700 take an index, but since D7A only has + # one position and is older, it may not. + # ("MP", "Latitude / longitude data"), + ("POSC", "Position comment"), + ("ICO", "Station icon"), + ("STAT", "Status text"), + ("TXI", "Beacon transmit interval"), + ("PP", "Packet path"), + ("DTX", "Beacon transmit method"), + ("UPR", "Group code"), + ("ARL", "Reception restriction distance"), + ("TEMP", "Unit for temperature"))), + ('SSTV', + (("SMY", "My call sign"), + ("MAC", "Color for call sign"), + ("SMSG", "Message"), + ("SMC", "Color for message"), + ("RSV", "RSV report"), + ("RSC", "Color for RSV report"), + ("VCS", "VC-H1 Control"))), + ('SkyCommand', + (("SCC", "Commander call sign"), + ("SCT", "Transporter call sign"), + ("SKTN", "Tone frequency select")))) @directory.register -class THD7GRadio(THD7Radio): +class THD7GRadio(KenwoodD7Family): """Kenwood TH-D7G""" MODEL = "TH-D7G" - _SETTINGS_OPTIONS = { - "BEPT": ["Off", "Mine", "All New", "All"], # Added "All" - } - - def _get_setting_options(self, setting): - if setting in self._SETTINGS_OPTIONS: - opts = self._SETTINGS_OPTIONS[setting] - else: - opts = super()._SETTINGS_OPTIONS[setting] - if opts is None: - opts = getattr(self, '_%s_OPTIONS' % setting) - return opts + _APRS_EXTRA = (('WEATHER Station (blue)', '1,/#'), + ('House QTH (VHF)', '1,/-'), + ('Boy Scouts', '1,/,'), + ('Campground (Portable ops)', '1,/;'), + ('FIRE', '1,/:'), + ('Police, Sheriff', '1,/!'), + ('SERVER for Files', '1,/?'), + ('X', '1,/.'), + ('Small AIRCRAFT (SSID-11)', "1,/'"), + ('reserved (was rain)', '1,/"'), + ('Mobile Satellite Station', '1,/('), + ('Wheelchair (handicapped)', '1,/)'), + ('Human/Person (SSID-7)', '1,/['), + ('MAIL/PostOffice(was PBBS)', '1,/]'), + ('/{ (J1)', '1,/{'), + ('/} (J3)', '1,/}'), + ('HC FUTURE predict (dot)', '1,/@'), + ('SnowMobile', '1,/*'), + ('Red Dot', '1,//'), + ('TRIANGLE(DF station)', '1,/\\'), + ('HF GATEway', '1,/&'), + ('DIGI (white center)', '1,/#'), + ('DX CLUSTER', '1,/%'), + ('Dish Antenna', '1,/`'), + ('LARGE AIRCRAFT', '1,/^'), + ('Red Cross', '1,/+'), + ('Motorcycle (SSID-10)', '1,/<'), + ('RAILROAD ENGINE', '1,/='), + ('CAR (SSID-9)', '1,/>'), + ('TNC Stream Switch', '1,/|'), + ('TNC Stream Switch', '1,/~'), + ('PHONE', '1,/$'), + ('# circle (obsolete)', '1,/0'), + ('TBD (these were numbered)', '1,/1'), + ('TBD (circles like pool)', '1,/2'), + ('TBD (balls. But with)', '1,/3'), + ('TBD (overlays, we can)', '1,/4'), + ("TBD (put all #'s on one)", '1,/5'), + ('TBD (So 1-9 are available)', '1,/6'), + ('TBD (for new uses?)', '1,/7'), + ('TBD (They are often used)', '1,/8'), + ('TBD (as mobiles at events)', '1,/9'), + ('Aid Station', '1,/A'), + ('AMBULANCE (SSID-1)', '1,/a'), + ('BBS or PBBS', '1,/B'), + ('BIKE (SSID-4)', '1,/b'), + ('Canoe', '1,/C'), + ('Incident Command Post', '1,/c'), + ('/D (PD)', '1,/D'), + ('Fire dept', '1,/d'), + ('EYEBALL (Events, etc!)', '1,/E'), + ('HORSE (equestrian)', '1,/e'), + ('Farm Vehicle (tractor)', '1,/F'), + ('FIRE TRUCK (SSID-3)', '1,/f'), + ('Grid Square (6 digit)', '1,/G'), + ('Glider', '1,/g'), + ('HOTEL (blue bed symbol)', '1,/H'), + ('HOSPITAL', '1,/h'), + ('TcpIp on air network stn', '1,/I'), + ('IOTA (islands on the air)', '1,/i'), + ('/J (PJ)', '1,/J'), + ('JEEP (SSID-12)', '1,/j'), + ('School', '1,/K'), + ('TRUCK (SSID-14)', '1,/k'), + ('PC user (Jan 03)', '1,/L'), + ('Laptop (Jan 03) (Feb 07)', '1,/l'), + ('MacAPRS', '1,/M'), + ('Mic-E Repeater', '1,/m'), + ('NTS Station', '1,/N'), + ('Node (black bulls-eye)', '1,/n'), + ('BALLOON (SSID-11)', '1,/O'), + ('EOC', '1,/o'), + ('Police', '1,/P'), + ('ROVER (puppy, or dog)', '1,/p'), + ('TBD', '1,/Q'), + ('GRID SQ shown above 128 m', '1,/q'), + ('REC. VEHICLE (SSID-13)', '1,/R'), + ('Repeater (Feb 07)', '1,/r'), + ('SHUTTLE', '1,/S'), + ('SHIP (pwr boat) (SSID-8)', '1,/s'), + ('SSTV', '1,/T'), + ('TRUCK STOP', '1,/t'), + ('BUS (SSID-2)', '1,/U'), + ('TRUCK (18 wheeler)', '1,/u'), + ('ATV', '1,/V'), + ('VAN (SSID-15)', '1,/v'), + ('National WX Service Site', '1,/W'), + ('WATER station', '1,/w'), + ('HELO (SSID-6)', '1,/X'), + ('xAPRS (Unix)', '1,/x'), + ('YACHT (sail) (SSID-5)', '1,/Y'), + ('YAGI @ QTH', '1,/y'), + ('WinAPRS', '1,/Z'), + ('TBD', '1,/z'), + ('# WX site (green digi)', '1,\\_'), + ('# WX site (green digi) with Zero overlaid', '1,0_'), + ('# WX site (green digi) with One overlaid', '1,1_'), + ('# WX site (green digi) with Two overlaid', '1,2_'), + ('# WX site (green digi) with Three overlaid', '1,3_'), + ('# WX site (green digi) with Four overlaid', '1,4_'), + ('# WX site (green digi) with Five overlaid', '1,5_'), + ('# WX site (green digi) with Six overlaid', '1,6_'), + ('# WX site (green digi) with Seven overlaid', '1,7_'), + ('# WX site (green digi) with Eight overlaid', '1,8_'), + ('# WX site (green digi) with Nine overlaid', '1,9_'), + ('# WX site (green digi) with Letter A overlaid', '1,A_'), + ('# WX site (green digi) with Letter B overlaid', '1,B_'), + ('# WX site (green digi) with Letter C overlaid', '1,C_'), + ('# WX site (green digi) with Letter D overlaid', '1,D_'), + ('# WX site (green digi) with Letter E overlaid', '1,E_'), + ('# WX site (green digi) with Letter F overlaid', '1,F_'), + ('# WX site (green digi) with Letter G overlaid', '1,G_'), + ('# WX site (green digi) with Letter H overlaid', '1,H_'), + ('# WX site (green digi) with Letter I overlaid', '1,I_'), + ('# WX site (green digi) with Letter J overlaid', '1,J_'), + ('# WX site (green digi) with Letter K overlaid', '1,K_'), + ('# WX site (green digi) with Letter L overlaid', '1,L_'), + ('# WX site (green digi) with Letter M overlaid', '1,M_'), + ('# WX site (green digi) with Letter N overlaid', '1,N_'), + ('# WX site (green digi) with Letter O overlaid', '1,O_'), + ('# WX site (green digi) with Letter P overlaid', '1,P_'), + ('# WX site (green digi) with Letter Q overlaid', '1,Q_'), + ('# WX site (green digi) with Letter R overlaid', '1,R_'), + ('# WX site (green digi) with Letter S overlaid', '1,S_'), + ('# WX site (green digi) with Letter T overlaid', '1,T_'), + ('# WX site (green digi) with Letter U overlaid', '1,U_'), + ('# WX site (green digi) with Letter V overlaid', '1,V_'), + ('# WX site (green digi) with Letter W overlaid', '1,W_'), + ('# WX site (green digi) with Letter X overlaid', '1,X_'), + ('# WX site (green digi) with Letter Y overlaid', '1,Y_'), + ('# WX site (green digi) with Letter Z overlaid', '1,Z_'), + ('House (H=HF) (O = Op Present)', '1,\\-'), + ('Girl Scouts', '1,\\,'), + ('Park/Picnic + overlay events', '1,\\;'), + ('AVAIL (Hail ==> ` ovly H)', '1,\\:'), + ('EMERGENCY (and overlays)', '1,\\!'), + ('INFO Kiosk (Blue box with ?)', '1,\\?'), + ('Ambiguous (Big Question mark)', '1,\\.'), + ('Crash (& now Incident sites)', "1,\\'"), + ('reserved', '1,\\"'), + ('CLOUDY (other clouds w ovrly)', '1,\\('), + ('Firenet MEO, MODIS Earth Obs.', '1,\\)'), + ('W.Cloud (& humans w Ovrly)', '1,\\['), + ('AVAIL', '1,\\]'), + ('AVAIL? (Fog ==> E ovly F)', '1,\\{'), + ('AVAIL? (maybe)', '1,\\}'), + ('HURICANE/Trop-Storm', '1,\\@'), + ('AVAIL (SNOW moved to ` ovly S)', '1,\\*'), + ('Waypoint Destination See APRSdos MOBILE.txt', '1,\\/'), + ('New overlayable GPS symbol', '1,\\\\'), + ('I=Igte R=RX T=1hopTX 2=2hopTX', '1,\\&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Zero overlaid', + '1,0&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with One overlaid', '1,1&'), + ('TX igate with path set to 2 hops', '1,2&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Three overlaid', + '1,3&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Four overlaid', + '1,4&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Five overlaid', + '1,5&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Six overlaid', '1,6&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Seven overlaid', + '1,7&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Eight overlaid', + '1,8&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Nine overlaid', + '1,9&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter A overlaid', + '1,A&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter B overlaid', + '1,B&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter C overlaid', + '1,C&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter D overlaid', + '1,D&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter E overlaid', + '1,E&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter F overlaid', + '1,F&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter G overlaid', + '1,G&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter H overlaid', + '1,H&'), + ('Igate Generic', '1,I&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter J overlaid', + '1,J&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter K overlaid', + '1,K&'), + ('Lora Igate', '1,L&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter M overlaid', + '1,M&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter N overlaid', + '1,N&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter O overlaid', + '1,O&'), + ('PSKmail node', '1,P&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter Q overlaid', + '1,Q&'), + ('Receive only Igate', '1,R&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter S overlaid', + '1,S&'), + ('TX igate with path set to 1 hop only', '1,T&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter U overlaid', + '1,U&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter V overlaid', + '1,V&'), + ('WIRES-X', '1,W&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter X overlaid', + '1,X&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter Y overlaid', + '1,Y&'), + ('I=Igte R=RX T=1hopTX 2=2hopTX with Letter Z overlaid', + '1,Z&'), + ('OVERLAY DIGI (green star)', '1,\\#'), + ('OVERLAY DIGI (green star) with Zero overlaid', '1,0#'), + ('OVERLAY DIGI (green star) with One overlaid', '1,1#'), + ('OVERLAY DIGI (green star) with Two overlaid', '1,2#'), + ('OVERLAY DIGI (green star) with Three overlaid', '1,3#'), + ('OVERLAY DIGI (green star) with Four overlaid', '1,4#'), + ('OVERLAY DIGI (green star) with Five overlaid', '1,5#'), + ('OVERLAY DIGI (green star) with Six overlaid', '1,6#'), + ('OVERLAY DIGI (green star) with Seven overlaid', '1,7#'), + ('OVERLAY DIGI (green star) with Eight overlaid', '1,8#'), + ('OVERLAY DIGI (green star) with Nine overlaid', '1,9#'), + ('OVERLAY DIGI (green star) with Letter A overlaid', + '1,A#'), + ('OVERLAY DIGI (green star) with Letter B overlaid', + '1,B#'), + ('OVERLAY DIGI (green star) with Letter C overlaid', + '1,C#'), + ('OVERLAY DIGI (green star) with Letter D overlaid', + '1,D#'), + ('OVERLAY DIGI (green star) with Letter E overlaid', + '1,E#'), + ('OVERLAY DIGI (green star) with Letter F overlaid', + '1,F#'), + ('OVERLAY DIGI (green star) with Letter G overlaid', + '1,G#'), + ('OVERLAY DIGI (green star) with Letter H overlaid', + '1,H#'), + ('OVERLAY DIGI (green star) with Letter I overlaid', + '1,I#'), + ('OVERLAY DIGI (green star) with Letter J overlaid', + '1,J#'), + ('OVERLAY DIGI (green star) with Letter K overlaid', + '1,K#'), + ('OVERLAY DIGI (green star) with Letter L overlaid', + '1,L#'), + ('OVERLAY DIGI (green star) with Letter M overlaid', + '1,M#'), + ('OVERLAY DIGI (green star) with Letter N overlaid', + '1,N#'), + ('OVERLAY DIGI (green star) with Letter O overlaid', + '1,O#'), + ('OVERLAY DIGI (green star) with Letter P overlaid', + '1,P#'), + ('OVERLAY DIGI (green star) with Letter Q overlaid', + '1,Q#'), + ('OVERLAY DIGI (green star) with Letter R overlaid', + '1,R#'), + ('OVERLAY DIGI (green star) with Letter S overlaid', + '1,S#'), + ('OVERLAY DIGI (green star) with Letter T overlaid', + '1,T#'), + ('OVERLAY DIGI (green star) with Letter U overlaid', + '1,U#'), + ('OVERLAY DIGI (green star) with Letter V overlaid', + '1,V#'), + ('OVERLAY DIGI (green star) with Letter W overlaid', + '1,W#'), + ('OVERLAY DIGI (green star) with Letter X overlaid', + '1,X#'), + ('OVERLAY DIGI (green star) with Letter Y overlaid', + '1,Y#'), + ('OVERLAY DIGI (green star) with Letter Z overlaid', + '1,Z#'), + ('Power Plant with overlay', '1,\\%'), + ('Rain (all types w ovrly)', '1,\\`'), + ('other Aircraft ovrlys (2014)', '1,\\^'), + ('other Aircraft ovrlys (2014) with Zero overlaid', '1,0^'), + ('other Aircraft ovrlys (2014) with One overlaid', '1,1^'), + ('other Aircraft ovrlys (2014) with Two overlaid', '1,2^'), + ('other Aircraft ovrlys (2014) with Three overlaid', + '1,3^'), + ('other Aircraft ovrlys (2014) with Four overlaid', '1,4^'), + ('other Aircraft ovrlys (2014) with Five overlaid', '1,5^'), + ('other Aircraft ovrlys (2014) with Six overlaid', '1,6^'), + ('other Aircraft ovrlys (2014) with Seven overlaid', + '1,7^'), + ('other Aircraft ovrlys (2014) with Eight overlaid', + '1,8^'), + ('other Aircraft ovrlys (2014) with Nine overlaid', '1,9^'), + ('Autonomous', '1,A^'), + ('other Aircraft ovrlys (2014) with Letter B overlaid', + '1,B^'), + ('other Aircraft ovrlys (2014) with Letter C overlaid', + '1,C^'), + ('Drone', '1,D^'), + ('Electric aircraft', '1,E^'), + ('other Aircraft ovrlys (2014) with Letter F overlaid', + '1,F^'), + ('other Aircraft ovrlys (2014) with Letter G overlaid', + '1,G^'), + ('Hovercraft', '1,H^'), + ('other Aircraft ovrlys (2014) with Letter I overlaid', + '1,I^'), + ('JET', '1,J^'), + ('other Aircraft ovrlys (2014) with Letter K overlaid', + '1,K^'), + ('other Aircraft ovrlys (2014) with Letter L overlaid', + '1,L^'), + ('Missle', '1,M^'), + ('other Aircraft ovrlys (2014) with Letter N overlaid', + '1,N^'), + ('other Aircraft ovrlys (2014) with Letter O overlaid', + '1,O^'), + ('Prop', '1,P^'), + ('other Aircraft ovrlys (2014) with Letter Q overlaid', + '1,Q^'), + ('Remotely Piloted', '1,R^'), + ('Solar Powered', '1,S^'), + ('other Aircraft ovrlys (2014) with Letter T overlaid', + '1,T^'), + ('other Aircraft ovrlys (2014) with Letter U overlaid', + '1,U^'), + ('Vertical takeoff', '1,V^'), + ('other Aircraft ovrlys (2014) with Letter W overlaid', + '1,W^'), + ('Experimental', '1,X^'), + ('other Aircraft ovrlys (2014) with Letter Y overlaid', + '1,Y^'), + ('other Aircraft ovrlys (2014) with Letter Z overlaid', + '1,Z^'), + ('Church', '1,\\+'), + ('ADVISORY (one WX flag)', '1,\\<'), + ('avail. symbol overlay group', '1,\\='), + ('OVERLAYED CARs & Vehicles', '1,\\>'), + ('OVERLAYED CARs & Vehicles with Zero overlaid', '1,0>'), + ('OVERLAYED CARs & Vehicles with One overlaid', '1,1>'), + ('OVERLAYED CARs & Vehicles with Two overlaid', '1,2>'), + ('Model 3 (Tesla)', '1,3>'), + ('OVERLAYED CARs & Vehicles with Four overlaid', '1,4>'), + ('OVERLAYED CARs & Vehicles with Five overlaid', '1,5>'), + ('OVERLAYED CARs & Vehicles with Six overlaid', '1,6>'), + ('OVERLAYED CARs & Vehicles with Seven overlaid', '1,7>'), + ('OVERLAYED CARs & Vehicles with Eight overlaid', '1,8>'), + ('OVERLAYED CARs & Vehicles with Nine overlaid', '1,9>'), + ('OVERLAYED CARs & Vehicles with Letter A overlaid', + '1,A>'), + ('BEV - Battery EV', '1,B>'), + ('OVERLAYED CARs & Vehicles with Letter C overlaid', + '1,C>'), + ('DIY - Do it yourself ', '1,D>'), + ('Ethanol (was electric)', '1,E>'), + ('Fuelcell or hydrogen', '1,F>'), + ('OVERLAYED CARs & Vehicles with Letter G overlaid', + '1,G>'), + ('Hybrid', '1,H>'), + ('OVERLAYED CARs & Vehicles with Letter I overlaid', + '1,I>'), + ('OVERLAYED CARs & Vehicles with Letter J overlaid', + '1,J>'), + ('OVERLAYED CARs & Vehicles with Letter K overlaid', + '1,K>'), + ('Leaf', '1,L>'), + ('OVERLAYED CARs & Vehicles with Letter M overlaid', + '1,M>'), + ('OVERLAYED CARs & Vehicles with Letter N overlaid', + '1,N>'), + ('OVERLAYED CARs & Vehicles with Letter O overlaid', + '1,O>'), + ('PHEV - Plugin-hybrid', '1,P>'), + ('OVERLAYED CARs & Vehicles with Letter Q overlaid', + '1,Q>'), + ('OVERLAYED CARs & Vehicles with Letter R overlaid', + '1,R>'), + ('Solar powered', '1,S>'), + ('Tesla (temporary)', '1,T>'), + ('OVERLAYED CARs & Vehicles with Letter U overlaid', + '1,U>'), + ('Volt (temporary)', '1,V>'), + ('OVERLAYED CARs & Vehicles with Letter W overlaid', + '1,W>'), + ('Model X', '1,X>'), + ('OVERLAYED CARs & Vehicles with Letter Y overlaid', + '1,Y>'), + ('OVERLAYED CARs & Vehicles with Letter Z overlaid', + '1,Z>'), + ('TNC Stream Switch', '1,\\|'), + ('TNC Stream Switch', '1,\\~'), + ('Bank or ATM (green box)', '1,\\$'), + ('CIRCLE (IRLP/Echolink/WIRES)', '1,\\0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Zero overlaid', '1,00'), + ('CIRCLE (IRLP/Echolink/WIRES) with One overlaid', '1,10'), + ('CIRCLE (IRLP/Echolink/WIRES) with Two overlaid', '1,20'), + ('CIRCLE (IRLP/Echolink/WIRES) with Three overlaid', + '1,30'), + ('CIRCLE (IRLP/Echolink/WIRES) with Four overlaid', '1,40'), + ('CIRCLE (IRLP/Echolink/WIRES) with Five overlaid', '1,50'), + ('CIRCLE (IRLP/Echolink/WIRES) with Six overlaid', '1,60'), + ('CIRCLE (IRLP/Echolink/WIRES) with Seven overlaid', + '1,70'), + ('CIRCLE (IRLP/Echolink/WIRES) with Eight overlaid', + '1,80'), + ('CIRCLE (IRLP/Echolink/WIRES) with Nine overlaid', '1,90'), + ('Allstar Node', '1,A0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter B overlaid', + '1,B0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter C overlaid', + '1,C0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter D overlaid', + '1,D0'), + ('Echolink Node', '1,E0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter F overlaid', + '1,F0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter G overlaid', + '1,G0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter H overlaid', + '1,H0'), + ('IRLP repeater', '1,I0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter J overlaid', + '1,J0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter K overlaid', + '1,K0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter L overlaid', + '1,L0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter M overlaid', + '1,M0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter N overlaid', + '1,N0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter O overlaid', + '1,O0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter P overlaid', + '1,P0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter Q overlaid', + '1,Q0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter R overlaid', + '1,R0'), + ('Staging Area', '1,S0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter T overlaid', + '1,T0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter U overlaid', + '1,U0'), + ('Echolink and IRLP', '1,V0'), + ('WIRES (Yaesu VOIP)', '1,W0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter X overlaid', + '1,X0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter Y overlaid', + '1,Y0'), + ('CIRCLE (IRLP/Echolink/WIRES) with Letter Z overlaid', + '1,Z0'), + ('AVAIL', '1,\\1'), + ('AVAIL', '1,\\2'), + ('AVAIL', '1,\\3'), + ('AVAIL', '1,\\4'), + ('AVAIL', '1,\\5'), + ('AVAIL', '1,\\6'), + ('AVAIL', '1,\\7'), + ('802.11 or other network node', '1,\\8'), + ('Gas Station (blue pump)', '1,\\9'), + ('overlayBOX DTMF & RFID & XO', '1,\\A'), + ('overlayBOX DTMF & RFID & XO with Zero overlaid', '1,0A'), + ('overlayBOX DTMF & RFID & XO with One overlaid', '1,1A'), + ('overlayBOX DTMF & RFID & XO with Two overlaid', '1,2A'), + ('overlayBOX DTMF & RFID & XO with Three overlaid', '1,3A'), + ('overlayBOX DTMF & RFID & XO with Four overlaid', '1,4A'), + ('overlayBOX DTMF & RFID & XO with Five overlaid', '1,5A'), + ('overlayBOX DTMF & RFID & XO with Six overlaid', '1,6A'), + ('HT DTMF user', '1,7A'), + ('overlayBOX DTMF & RFID & XO with Eight overlaid', '1,8A'), + ('Mobile DTMF user', '1,9A'), + ('AllStar DTMF report', '1,AA'), + ('overlayBOX DTMF & RFID & XO with Letter B overlaid', + '1,BA'), + ('overlayBOX DTMF & RFID & XO with Letter C overlaid', + '1,CA'), + ('D-Star report', '1,DA'), + ('Echolink DTMF report', '1,EA'), + ('overlayBOX DTMF & RFID & XO with Letter F overlaid', + '1,FA'), + ('overlayBOX DTMF & RFID & XO with Letter G overlaid', + '1,GA'), + ('House DTMF user', '1,HA'), + ('IRLP DTMF report', '1,IA'), + ('overlayBOX DTMF & RFID & XO with Letter J overlaid', + '1,JA'), + ('overlayBOX DTMF & RFID & XO with Letter K overlaid', + '1,KA'), + ('overlayBOX DTMF & RFID & XO with Letter L overlaid', + '1,LA'), + ('overlayBOX DTMF & RFID & XO with Letter M overlaid', + '1,MA'), + ('overlayBOX DTMF & RFID & XO with Letter N overlaid', + '1,NA'), + ('overlayBOX DTMF & RFID & XO with Letter O overlaid', + '1,OA'), + ('overlayBOX DTMF & RFID & XO with Letter P overlaid', + '1,PA'), + ('overlayBOX DTMF & RFID & XO with Letter Q overlaid', + '1,QA'), + ('RFID report', '1,RA'), + ('overlayBOX DTMF & RFID & XO with Letter S overlaid', + '1,SA'), + ('overlayBOX DTMF & RFID & XO with Letter T overlaid', + '1,TA'), + ('overlayBOX DTMF & RFID & XO with Letter U overlaid', + '1,UA'), + ('overlayBOX DTMF & RFID & XO with Letter V overlaid', + '1,VA'), + ('overlayBOX DTMF & RFID & XO with Letter W overlaid', + '1,WA'), + ('OLPC Laptop XO', '1,XA'), + ('overlayBOX DTMF & RFID & XO with Letter Y overlaid', + '1,YA'), + ('overlayBOX DTMF & RFID & XO with Letter Z overlaid', + '1,ZA'), + (' ARRL,ARES,WinLINK,Dstar, etc', '1,\\a'), + ('AVAIL (BlwngSnow ==> E ovly B', '1,\\B'), + ('AVAIL(Blwng Dst/Snd => E ovly)', '1,\\b'), + ('Coast Guard', '1,\\C'), + (' CD triangle RACES/SATERN/etc', '1,\\c'), + ('DX spot by callsign', '1,\\d'), + ("DEPOTS (Drizzle ==> ' ovly D)", '1,\\D A'), + ('Smoke (& other vis codes)', '1,\\E'), + ('Sleet (& future ovrly codes)', '1,\\e'), + ('AVAIL (FrzngRain ==> `F)', '1,\\F'), + ('Funnel Cloud', '1,\\f'), + ('AVAIL (Snow Shwr ==> I ovly S)', '1,\\G'), + ('Gale Flags', '1,\\g'), + ('\\Haze (& Overlay Hazards)', '1,\\H'), + ('Store. or HAMFST Hh=HAM store', '1,\\h'), + ('Rain Shower', '1,\\I'), + ('BOX or points of Interest', '1,\\i'), + ('AVAIL (Lightening ==> I ovly L)', '1,\\J'), + ('WorkZone (Steam Shovel)', '1,\\j'), + ('Kenwood HT (W)', '1,\\K'), + ('Special Vehicle SUV,ATV,4x4', '1,\\k'), + ('Lighthouse', '1,\\L'), + ('Areas (box,circles,etc)', '1,\\l'), + ('MARS (A=Army,N=Navy,F=AF)', '1,\\M'), + ('Value Sign (3 digit display)', '1,\\m'), + ('Navigation Buoy', '1,\\N'), + ('OVERLAY TRIANGLE', '1,\\n'), + ('OVERLAY TRIANGLE with Zero overlaid', '1,0n'), + ('OVERLAY TRIANGLE with One overlaid', '1,1n'), + ('OVERLAY TRIANGLE with Two overlaid', '1,2n'), + ('OVERLAY TRIANGLE with Three overlaid', '1,3n'), + ('OVERLAY TRIANGLE with Four overlaid', '1,4n'), + ('OVERLAY TRIANGLE with Five overlaid', '1,5n'), + ('OVERLAY TRIANGLE with Six overlaid', '1,6n'), + ('OVERLAY TRIANGLE with Seven overlaid', '1,7n'), + ('OVERLAY TRIANGLE with Eight overlaid', '1,8n'), + ('OVERLAY TRIANGLE with Nine overlaid', '1,9n'), + ('OVERLAY TRIANGLE with Letter A overlaid', '1,An'), + ('OVERLAY TRIANGLE with Letter B overlaid', '1,Bn'), + ('OVERLAY TRIANGLE with Letter C overlaid', '1,Cn'), + ('OVERLAY TRIANGLE with Letter D overlaid', '1,Dn'), + ('OVERLAY TRIANGLE with Letter E overlaid', '1,En'), + ('OVERLAY TRIANGLE with Letter F overlaid', '1,Fn'), + ('OVERLAY TRIANGLE with Letter G overlaid', '1,Gn'), + ('OVERLAY TRIANGLE with Letter H overlaid', '1,Hn'), + ('OVERLAY TRIANGLE with Letter I overlaid', '1,In'), + ('OVERLAY TRIANGLE with Letter J overlaid', '1,Jn'), + ('OVERLAY TRIANGLE with Letter K overlaid', '1,Kn'), + ('OVERLAY TRIANGLE with Letter L overlaid', '1,Ln'), + ('OVERLAY TRIANGLE with Letter M overlaid', '1,Mn'), + ('OVERLAY TRIANGLE with Letter N overlaid', '1,Nn'), + ('OVERLAY TRIANGLE with Letter O overlaid', '1,On'), + ('OVERLAY TRIANGLE with Letter P overlaid', '1,Pn'), + ('OVERLAY TRIANGLE with Letter Q overlaid', '1,Qn'), + ('OVERLAY TRIANGLE with Letter R overlaid', '1,Rn'), + ('OVERLAY TRIANGLE with Letter S overlaid', '1,Sn'), + ('OVERLAY TRIANGLE with Letter T overlaid', '1,Tn'), + ('OVERLAY TRIANGLE with Letter U overlaid', '1,Un'), + ('OVERLAY TRIANGLE with Letter V overlaid', '1,Vn'), + ('OVERLAY TRIANGLE with Letter W overlaid', '1,Wn'), + ('OVERLAY TRIANGLE with Letter X overlaid', '1,Xn'), + ('OVERLAY TRIANGLE with Letter Y overlaid', '1,Yn'), + ('OVERLAY TRIANGLE with Letter Z overlaid', '1,Zn'), + ('Overlay Balloon (Rocket = \\O)', '1,\\O'), + ('small circle', '1,\\o'), + ('Parking', '1,\\P'), + ('AVAIL (PrtlyCldy => ( ovly P', '1,\\p'), + ('QUAKE', '1,\\Q'), + ('AVAIL', '1,\\q'), + ('Restaurant', '1,\\R'), + ('Restrooms', '1,\\r'), + ('Satellite/Pacsat', '1,\\S'), + ('OVERLAY SHIP/boats', '1,\\s'), + ('OVERLAY SHIP/boats with Zero overlaid', '1,0s'), + ('OVERLAY SHIP/boats with One overlaid', '1,1s'), + ('OVERLAY SHIP/boats with Two overlaid', '1,2s'), + ('OVERLAY SHIP/boats with Three overlaid', '1,3s'), + ('OVERLAY SHIP/boats with Four overlaid', '1,4s'), + ('OVERLAY SHIP/boats with Five overlaid', '1,5s'), + ('Shipwreck ("deep6")', '1,6s'), + ('OVERLAY SHIP/boats with Seven overlaid', '1,7s'), + ('OVERLAY SHIP/boats with Eight overlaid', '1,8s'), + ('OVERLAY SHIP/boats with Nine overlaid', '1,9s'), + ('OVERLAY SHIP/boats with Letter A overlaid', '1,As'), + ('Pleasure Boat', '1,Bs'), + ('Cargo', '1,Cs'), + ('Diving', '1,Ds'), + ('Emergency or Medical transport', '1,Es'), + ('Fishing', '1,Fs'), + ('OVERLAY SHIP/boats with Letter G overlaid', '1,Gs'), + ('High-speed Craft', '1,Hs'), + ('OVERLAY SHIP/boats with Letter I overlaid', '1,Is'), + ('Jet Ski', '1,Js'), + ('OVERLAY SHIP/boats with Letter K overlaid', '1,Ks'), + ('Law enforcement', '1,Ls'), + ('Miltary', '1,Ms'), + ('OVERLAY SHIP/boats with Letter N overlaid', '1,Ns'), + ('Oil Rig', '1,Os'), + ('Pilot Boat', '1,Ps'), + ('Torpedo', '1,Qs'), + ('OVERLAY SHIP/boats with Letter R overlaid', '1,Rs'), + ('Search and Rescue', '1,Ss'), + ('Tug', '1,Ts'), + ('Underwater ops or submarine', '1,Us'), + ('OVERLAY SHIP/boats with Letter V overlaid', '1,Vs'), + ('Wing-in-Ground effect (or Hovercraft)', '1,Ws'), + ('Passenger (paX)(ferry)', '1,Xs'), + ('Sailing (large ship)', '1,Ys'), + ('OVERLAY SHIP/boats with Letter Z overlaid', '1,Zs'), + ('Thunderstorm', '1,\\T'), + ('Tornado', '1,\\t'), + ('SUNNY', '1,\\U'), + ('OVERLAYED TRUCK', '1,\\u'), + ('OVERLAYED TRUCK with Zero overlaid', '1,0u'), + ('OVERLAYED TRUCK with One overlaid', '1,1u'), + ('OVERLAYED TRUCK with Two overlaid', '1,2u'), + ('OVERLAYED TRUCK with Three overlaid', '1,3u'), + ('OVERLAYED TRUCK with Four overlaid', '1,4u'), + ('OVERLAYED TRUCK with Five overlaid', '1,5u'), + ('OVERLAYED TRUCK with Six overlaid', '1,6u'), + ('OVERLAYED TRUCK with Seven overlaid', '1,7u'), + ('OVERLAYED TRUCK with Eight overlaid', '1,8u'), + ('OVERLAYED TRUCK with Nine overlaid', '1,9u'), + ('OVERLAYED TRUCK with Letter A overlaid', '1,Au'), + ('Buldozer/construction/Backhoe', '1,Bu'), + ('Chlorine Tanker', '1,Cu'), + ('OVERLAYED TRUCK with Letter D overlaid', '1,Du'), + ('OVERLAYED TRUCK with Letter E overlaid', '1,Eu'), + ('OVERLAYED TRUCK with Letter F overlaid', '1,Fu'), + ('Gas', '1,Gu'), + ('Hazardous', '1,Hu'), + ('OVERLAYED TRUCK with Letter I overlaid', '1,Iu'), + ('OVERLAYED TRUCK with Letter J overlaid', '1,Ju'), + ('OVERLAYED TRUCK with Letter K overlaid', '1,Ku'), + ('OVERLAYED TRUCK with Letter L overlaid', '1,Lu'), + ('OVERLAYED TRUCK with Letter M overlaid', '1,Mu'), + ('OVERLAYED TRUCK with Letter N overlaid', '1,Nu'), + ('OVERLAYED TRUCK with Letter O overlaid', '1,Ou'), + ('Plow or SnowPlow', '1,Pu'), + ('OVERLAYED TRUCK with Letter Q overlaid', '1,Qu'), + ('OVERLAYED TRUCK with Letter R overlaid', '1,Ru'), + ('OVERLAYED TRUCK with Letter S overlaid', '1,Su'), + ('Tanker', '1,Tu'), + ('OVERLAYED TRUCK with Letter U overlaid', '1,Uu'), + ('OVERLAYED TRUCK with Letter V overlaid', '1,Vu'), + ('OVERLAYED TRUCK with Letter W overlaid', '1,Wu'), + ('OVERLAYED TRUCK with Letter X overlaid', '1,Xu'), + ('OVERLAYED TRUCK with Letter Y overlaid', '1,Yu'), + ('OVERLAYED TRUCK with Letter Z overlaid', '1,Zu'), + ('VORTAC Nav Aid', '1,\\V'), + ('OVERLAYED Van', '1,\\v'), + ('OVERLAYED Van with Zero overlaid', '1,0v'), + ('OVERLAYED Van with One overlaid', '1,1v'), + ('OVERLAYED Van with Two overlaid', '1,2v'), + ('OVERLAYED Van with Three overlaid', '1,3v'), + ('OVERLAYED Van with Four overlaid', '1,4v'), + ('OVERLAYED Van with Five overlaid', '1,5v'), + ('OVERLAYED Van with Six overlaid', '1,6v'), + ('OVERLAYED Van with Seven overlaid', '1,7v'), + ('OVERLAYED Van with Eight overlaid', '1,8v'), + ('OVERLAYED Van with Nine overlaid', '1,9v'), + ('OVERLAYED Van with Letter A overlaid', '1,Av'), + ('OVERLAYED Van with Letter B overlaid', '1,Bv'), + ('OVERLAYED Van with Letter C overlaid', '1,Cv'), + ('OVERLAYED Van with Letter D overlaid', '1,Dv'), + ('OVERLAYED Van with Letter E overlaid', '1,Ev'), + ('OVERLAYED Van with Letter F overlaid', '1,Fv'), + ('OVERLAYED Van with Letter G overlaid', '1,Gv'), + ('OVERLAYED Van with Letter H overlaid', '1,Hv'), + ('OVERLAYED Van with Letter I overlaid', '1,Iv'), + ('OVERLAYED Van with Letter J overlaid', '1,Jv'), + ('OVERLAYED Van with Letter K overlaid', '1,Kv'), + ('OVERLAYED Van with Letter L overlaid', '1,Lv'), + ('OVERLAYED Van with Letter M overlaid', '1,Mv'), + ('OVERLAYED Van with Letter N overlaid', '1,Nv'), + ('OVERLAYED Van with Letter O overlaid', '1,Ov'), + ('OVERLAYED Van with Letter P overlaid', '1,Pv'), + ('OVERLAYED Van with Letter Q overlaid', '1,Qv'), + ('OVERLAYED Van with Letter R overlaid', '1,Rv'), + ('OVERLAYED Van with Letter S overlaid', '1,Sv'), + ('OVERLAYED Van with Letter T overlaid', '1,Tv'), + ('OVERLAYED Van with Letter U overlaid', '1,Uv'), + ('OVERLAYED Van with Letter V overlaid', '1,Vv'), + ('OVERLAYED Van with Letter W overlaid', '1,Wv'), + ('OVERLAYED Van with Letter X overlaid', '1,Xv'), + ('OVERLAYED Van with Letter Y overlaid', '1,Yv'), + ('OVERLAYED Van with Letter Z overlaid', '1,Zv'), + ('# NWS site (NWS options)', '1,\\W'), + ('# NWS site (NWS options) with Zero overlaid', '1,0W'), + ('# NWS site (NWS options) with One overlaid', '1,1W'), + ('# NWS site (NWS options) with Two overlaid', '1,2W'), + ('# NWS site (NWS options) with Three overlaid', '1,3W'), + ('# NWS site (NWS options) with Four overlaid', '1,4W'), + ('# NWS site (NWS options) with Five overlaid', '1,5W'), + ('# NWS site (NWS options) with Six overlaid', '1,6W'), + ('# NWS site (NWS options) with Seven overlaid', '1,7W'), + ('# NWS site (NWS options) with Eight overlaid', '1,8W'), + ('# NWS site (NWS options) with Nine overlaid', '1,9W'), + ('# NWS site (NWS options) with Letter A overlaid', '1,AW'), + ('# NWS site (NWS options) with Letter B overlaid', '1,BW'), + ('# NWS site (NWS options) with Letter C overlaid', '1,CW'), + ('# NWS site (NWS options) with Letter D overlaid', '1,DW'), + ('# NWS site (NWS options) with Letter E overlaid', '1,EW'), + ('# NWS site (NWS options) with Letter F overlaid', '1,FW'), + ('# NWS site (NWS options) with Letter G overlaid', '1,GW'), + ('# NWS site (NWS options) with Letter H overlaid', '1,HW'), + ('# NWS site (NWS options) with Letter I overlaid', '1,IW'), + ('# NWS site (NWS options) with Letter J overlaid', '1,JW'), + ('# NWS site (NWS options) with Letter K overlaid', '1,KW'), + ('# NWS site (NWS options) with Letter L overlaid', '1,LW'), + ('# NWS site (NWS options) with Letter M overlaid', '1,MW'), + ('# NWS site (NWS options) with Letter N overlaid', '1,NW'), + ('# NWS site (NWS options) with Letter O overlaid', '1,OW'), + ('# NWS site (NWS options) with Letter P overlaid', '1,PW'), + ('# NWS site (NWS options) with Letter Q overlaid', '1,QW'), + ('# NWS site (NWS options) with Letter R overlaid', '1,RW'), + ('# NWS site (NWS options) with Letter S overlaid', '1,SW'), + ('# NWS site (NWS options) with Letter T overlaid', '1,TW'), + ('# NWS site (NWS options) with Letter U overlaid', '1,UW'), + ('# NWS site (NWS options) with Letter V overlaid', '1,VW'), + ('# NWS site (NWS options) with Letter W overlaid', '1,WW'), + ('# NWS site (NWS options) with Letter X overlaid', '1,XW'), + ('# NWS site (NWS options) with Letter Y overlaid', '1,YW'), + ('# NWS site (NWS options) with Letter Z overlaid', '1,ZW'), + ('Flooding (Avalanches/Slides)', '1,\\w'), + ('Pharmacy Rx (Apothicary)', '1,\\X'), + ('Wreck or Obstruction ->X<-', '1,\\x'), + ('Radios and devices', '1,\\Y'), + ('Skywarn', '1,\\y'), + ('AVAIL', '1,\\Z'), + ('OVERLAYED Shelter', '1,\\z')) + _SETTINGS = { + "DS": {'type': 'list', + 'values': ("Data Band", "Both Bands", "Ignore DCD")}, + "DSPA": {'type': 'list', + 'values': ("Entire Display", "One Line")}, + "ICO": {'type': 'map', + 'map': (KenwoodD7Family._COMMON_SETTINGS["ICO"]["map"] + + _APRS_EXTRA)}} - def get_features(self): - rf = super(THD7GRadio, self).get_features() - rf.valid_name_length = 8 - return rf + _SETTINGS_MENUS = ( + ('Main', + (("ASC 0", "Automatic Simplex Check Band A"), + ("ASC 1", "Automatic Simplex Check Band B"), + ("BAL", "Balance"), + ("BC", "Band"), + ("BEL 0", "Tone Alert Band A"), + ("BEL 1", "Tone Alert Band B"), + ("CH", "Channel Mode Display"), + ("DL", "Dual"), + ("LK", "Lock"), + ("LMP", "Lamp"), + # MNF requires that the *current* VFO be in MR mode + # Also, it has a direct key + # ("MNF", "Memory Display Mode"), + ("PV", "Programmable VFOs"), + ("SQ 0", "Band A Squelch"), + ("SQ 1", "Band B Squelch"), + # We likely don't want to enable this from Chirp... + # ("TNC", "Packet Mode"))), + )), + ('Radio', + (('Display', + (("MES", "Power-ON Message"), + ("CNT", "Contrast"))), + ('Save', + (("SV", "Battery Saver Interval"), + ("APO", "Automatic Power Off (APO)"))), + ('DTMF', + (("dtmfmem 00", "Number Store #0"), + ("dtmfmem 01", "Number Store #1"), + ("dtmfmem 02", "Number Store #2"), + ("dtmfmem 03", "Number Store #3"), + ("dtmfmem 04", "Number Store #4"), + ("dtmfmem 05", "Number Store #5"), + ("dtmfmem 06", "Number Store #6"), + ("dtmfmem 07", "Number Store #7"), + ("dtmfmem 08", "Number Store #8"), + ("dtmfmem 09", "Number Store #9"), + ("TSP", "TX Speed"), + ("TXH", "TX Hold"), + ("PT", "Pause"))), + ('TNC', + (("DTB", "Data band select"), + ("DS", "DCD sense"))), + ('AUX', + (("ARO", "Automatic Repeater Offset"), + ("SCR", "Scan Resume"), + ("BEP", "Key Beep"), + ("ELK", "Tuning Enable"), + ("TXS", "TX Inhibit"), + ("AIP", "Advanced Intercept Point"), + ("CKEY", "Call Key function"), + ("TH", "TX Hold, 1750 Hz"))))), + ('APRS', + (("MYC", "My call sign"), + ("GU", "GPS receiver"), + ("WAY", "Waypoint"), + ("MP 1", "My position #1"), + ("MP 2", "My position #2"), + ("MP 3", "My position #3"), + ("PAMB", "Position Ambiguity"), + ("POSC", "Position comment"), + ("ARL", "Reception restriction distance"), + ("ICO", "Station icon"), + ("STAT 1", "Status text #1"), + ("STAT 2", "Status text #2"), + ("STAT 3", "Status text #3"), + ("STXR", "Status text transmit rate"), + ("PP", "Packet path"), + ("DTX", "Packet transmit method"), + ("TXI", "Packet transmit interval"), + ("UPR", "Group code"), + ("BEPT", "Beep"), + ("DSPA", "Display area"), + ("KILO", "Unit for distance"), + ("TEMP", "Unit for temperature"), + ("AMR", "Auto Answer Reply"), + ("ARLM", "Reply message"), + ("AMGG", "Message group"), + ("DTBA", "Data band"), + ("PKSA", "Packet transfer rate"), + ("TZ", "Time Zone"), + ("TXD", "Packet transmit delay"))), + ('SSTV', + (("SMY", "My call sign"), + ("MAC", "Color for call sign"), + ("SMSG", "Message"), + ("SMC", "Color for message"), + ("RSV", "RSV report"), + ("RSC", "Color for RSV report"), + ("VCS", "VC-H1 Control"))), + ('SkyCommand', + (("SCC", "Commander call sign"), + ("SCT", "Transporter call sign"), + ("SKTN", "Tone frequency select")))) @directory.register -class TMD700Radio(THD7Radio): +class TMD700Radio(KenwoodD7Family): """Kenwood TH-D700""" MODEL = "TM-D700" + HARDWARE_FLOW = True - _kenwood_split = True + # Details taken from https://www.qsl.net/k7jar/pages/D700Cmds.html + # And https://www.qsl.net/k/kb9rku/aprs/ (continued on next line) + # PC%20Command%20Specifications%20for%20TM-D700_Rev1.pdf + _BAUDS = [57600, 38400, 19200, 9600] + _LOWER = 1 + _UPPER = 200 - _BEP_OPTIONS = ["Off", "Key"] - _POSC_OPTIONS = ["Off Duty", "Enroute", "In Service", "Returning", - "Committed", "Special", "Priority", "CUSTOM 0", - "CUSTOM 1", "CUSTOM 2", "CUSTOM 4", "CUSTOM 5", - "CUSTOM 6", "Emergency"] - EXTRA_BOOL_SETTINGS = {} - EXTRA_LIST_SETTINGS = {} + _BOOL = {'type': 'bool'} + _PKEY_FUNCTION = {'type': 'list', + 'values': ('Power', 'A/B', 'Monitor', 'Enter', 'Voice', + '1750', 'PM', 'Menu', 'VFO', 'MR', 'CALL', + 'MHz', 'Tone', 'Rev', 'Low', 'Mute', 'Ctrl', + 'PM.In', 'A.B.C', 'M>V', 'M.In', 'C.In', + 'Lock', 'T.Sel', 'Shift', 'Step', 'Visual', + 'Dim', 'Sub-Band Select', 'DX', 'TNC', 'List', + 'P.Mon', 'BCon', 'Msg', 'Pos')} + _VOLUME = {'type': 'list', + 'values': ('Off', '1', '2', '3', '4', '5', '6', 'Maximum')} + _SETTINGS = { + "ABLG": {'type': 'string', 'max_len': 29}, + "AD": _BOOL, + "BEP": {'type': 'list', + 'values': ("Off", "Key")}, + "BVOL": _VOLUME, + "CP": {'type': 'list', + 'values': ('9600bps', '19200bps', '38400bps', '57600bps')}, + "DATP": {'type': 'list', + 'values': ('1200bps', '9600bps')}, + "DIG": _BOOL, + "DTM": _BOOL, + "FUNC": {'type': 'map', + 'map': (("Mode 1", "1"), ("Mode 2", "2"), ("Mode 3", "3"))}, + "ICO": {'type': 'map', + 'map': (KenwoodD7Family._COMMON_SETTINGS["ICO"]["map"] + + (('Digipeater', '0,F'),) + + THD7GRadio._APRS_EXTRA)}, + "MCL 0": _BOOL, + "MCL 1": _BOOL, + "MCNT": _BOOL, + "MD": {'type': 'list', 'values': ('FM', 'AM')}, + "NP": _BOOL, + "OS": {'type': 'integer', 'min': -99999999, 'max': 999999999, + 'digits': 9}, + "PF 1": _PKEY_FUNCTION, + "PF 2": _PKEY_FUNCTION, + "PF 3": _PKEY_FUNCTION, + "PF 4": _PKEY_FUNCTION, + "PMM": _BOOL, + "RCA": _BOOL, + "RC": _BOOL, + "RCC": {'type': 'integer', 'min': 0, 'max': 999}, + "REP": {'type': 'list', + 'values': ('Off', 'Locked-Band', 'Cross-Band')}, + "REPL": _BOOL, + "SHT": {'type': 'list', + 'values': ('Off', '125ms', '250ms', '500ms')}, + "SSEL": {'type': 'map', + 'map': (("MODE1", "1"), ("MODE2", "2"))}, + "SSQ 0": _BOOL, + "SSQ 1": _BOOL, + "TOT": {'type': 'list', + 'values': ('3 minutes', '5 minutes', '10 minutes')}, + "UDIG": {'type': 'string', 'max_len': 39, 'charset': '0123456789' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ,-'}, + "VOM": {'type': 'list', + 'values': ('Off', 'English', 'APRS only', 'Japanese')}, + "VS3": _VOLUME, + "VSM": {'type': 'map', + 'map': (('31 Channels', '1'), ('61 Channels', '2'), + ('91 Channels', '3'), ('181 Channels', '4'))}} - def get_features(self): - rf = chirp_common.RadioFeatures() - rf.has_settings = True - rf.has_dtcs = True - rf.has_dtcs_polarity = False - rf.has_bank = False - rf.has_mode = True - rf.has_tuning_step = False - rf.can_odd_split = True - rf.valid_duplexes = ["", "-", "+", "split"] - rf.valid_modes = ["FM", "AM"] - rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] - rf.valid_characters = chirp_common.CHARSET_ALPHANUMERIC - rf.valid_name_length = 8 - rf.valid_tuning_steps = STEPS - rf.memory_bounds = (1, self._upper) - return rf + _PROGRAMMABLE_VFOS = ( + ("Band A 118MHz Sub-Band", 1, 118, 135), + ("Band A VHF Sub-Band", 2, 136, 199), + ("Band B VHF Sub-Band", 3, 136, 174), + ("Band A 220MHz Sub-Band", 4, 200, 299), + ("Band B 300MHz Sub-Band", 5, 300, 399), + ("Band A 300MHz Sub-Band", 6, 300, 399), + ("Band B UHF Sub-Band", 7, 400, 523), + ("Band A UHF Sub-Band", 8, 400, 469), + ("Band B 1.2GHz Sub-Band", 9, 800, 1299)) - def _make_mem_spec(self, mem): - if mem.duplex in " -+": - duplex = util.get_dict_rev(DUPLEX, mem.duplex) - else: - duplex = 0 - spec = ( - "%011i" % mem.freq, - "%X" % STEPS.index(mem.tuning_step), - "%i" % duplex, - "0", - "%i" % (mem.tmode == "Tone"), - "%i" % (mem.tmode == "TSQL"), - "%i" % (mem.tmode == "DTCS"), - "%02i" % (self._kenwood_valid_tones.index(mem.rtone) + 1), - "%03i0" % (chirp_common.DTCS_CODES.index(mem.dtcs) + 1), - "%02i" % (self._kenwood_valid_tones.index(mem.ctone) + 1), - "%09i" % mem.offset, - "%i" % util.get_dict_rev(MODES, mem.mode), - "%i" % ((mem.skip == "S") and 1 or 0)) + _SETTINGS_MENUS = ( + ('Main', + (("ASC 0", "Automatic Simplex Check Band A"), + ("ASC 1", "Automatic Simplex Check Band B"), + ("DL", "Dual"), + ("LK", "Lock"), + # MNF requires that the *current* VFO be in MR mode + # ("MNF", "Memory Display Mode"), + # We likely don't want to enable this from Chirp... + # ("TNC", "Packet Mode"))), + )), + ('Radio', + (('Display', + (("MES", "Power-ON Message"), + ("CNT", "Contrast"), + ("NP", "Reverse mode"), + ("AD", "Auto Dimmer Change"), + ("FUNC", "Multi-function button"))), + ('Audio', + (("BVOL", "Beep volume"), + ("BEP", "Key Beep"), + ("SSEL", "Speaker configuration"), + ("VOM", "Voice Synthesizer"), + ("VS3", "Voice volume"))), + ('TX/RX', + (("PV", "Programmable VFO"), + ("SSQ 0", "Band A S-meter Squelch"), + ("SSQ 1", "Band B S-meter Squelch"), + ("SHT", "Squelch hang time"), + ("MD", "FM / AM mode"), + ("AIP", "Advanced Intercept Point"), + ("XXX", "TX/ RX deviation"))), + ('Memory', + (("PMM", "Auto PM Channel Store"), + ("CH", "Channel Display"), + ("MCL 0", "Band A Memory Channel Lockout"), + ("MCL 1", "Band B Memory Channel Lockout"), + ("XXX", "Memory channel name"))), + ('DTMF', + (("dtmfmem 00", "Number Store #0"), + ("dtmfmem 01", "Number Store #1"), + ("dtmfmem 02", "Number Store #2"), + ("dtmfmem 03", "Number Store #3"), + ("dtmfmem 04", "Number Store #4"), + ("dtmfmem 05", "Number Store #5"), + ("dtmfmem 06", "Number Store #6"), + ("dtmfmem 07", "Number Store #7"), + ("dtmfmem 08", "Number Store #8"), + ("dtmfmem 09", "Number Store #9"), + ("TSP", "TX Speed"), + ("PT", "Pause"))), + ('TNC', + (("DTB", "Data band"), + ("DS", "DCD sense"), + ("XXX", "Time"), + ("XXX", "Date"), + ("TZ", "Time zone"))), + ('Repeater', + (("OS", "Offset frequency"), + ("ARO", "Automatic Repeater Offset"), + ("CKEY", "Call Button Function"), + ("TH", "TX Hold"), + ("REPH", "Repeater Hold"), + ("REP", "Repeater function"))), + ('Mic', + (("PF 1", "Mic PF Key"), + ("PF 2", "Mic MR Key"), + ("PF 3", "Mic VFO Key"), + ("PF 4", "Mic CALL Key"), + ("MCNT", "Microphone Control"), + ("DTM", "DTMF Monitor"))), + ('Aux', + (("SCR", "Scan Resume"), + ("VSM", "Number of Channels for Visual Scan"), + ("APO", "Automatic Power Off (APO)"), + ("TOT", "Time-Out Timer (TOT)"), + ("CP", "COM port"), + ("DATP", "Data port"))), + ('Remote Control', + (("RCC", "Secret code"), + ("RCA", "Acknowledgement"), + ("RC", "Remote Control"))))), + ('SSTV', + (("SMY", "My call sign"), + ("MAC", "Color for call sign"), + ("SMSG", "Message"), + ("SMC", "Color for message"), + ("RSV", "RSV report"), + ("RSC", "Color for RSV report"), + ("VCS", "VC-H1 Control"))), + ('APRS', + (("MYC", "My call sign"), + ("GU", "GPS receiver"), + ("WAY", "Waypoint"), + ("MP 1", "My position #1"), + ("MP 2", "My position #2"), + ("MP 3", "My position #3"), + ("PAMB", "Position Ambiguity"), + ("POSC", "Position comment"), + ("ARL", "Reception restriction distance"), + ("ICO", "Station icon"), + ("STAT 1", "Status text #1"), + ("STAT 2", "Status text #2"), + ("STAT 3", "Status text #3"), + ("STXR", "Status text transmit rate"), + ("PP", "Packet path"), + ("DTX", "Packet transmit method"), + ("TXI", "Packet transmit interval"), + ("UPR", "Group code"), + ("BEPT", "Beep"), + ("KILO", "Unit for distance"), + ("TEMP", "Unit for temperature"), + ("DTBA", "Data band"), + ("PKSA", "Packet transfer rate"), + ("DIG", "Digipeater"), + ("UDIG", "Digipeating path"), + ("AMR", "Auto Answer Reply"), + ("ARLM", "Reply message"), + ("ABLG", "Bulletin group"))), + ('SkyCommand', + (("SCC", "Commander call sign"), + ("SCT", "Transporter call sign"), + ("SKTN", "Tone frequency select")))) - return spec + # Apparently, when sent "CH 1", the radio will return "SM 0,00" + # which is the current value of the Band A S-Meter. We'll ignore that + # and read the next line (which is hopefully "CH 1") + def _keep_reading(self, result): + if result[0:3] == 'SM ': + return True + return False - def _parse_mem_spec(self, spec): - mem = chirp_common.Memory() + # It also seems that "CH 1" can return "N" when the current memory + # is invalid (but still enters CH mode) + def _kenwood_set_success(self, cmd, modcmd, value, response): + if cmd == 'CH' and value == "1": + if response == 'N': + return True + return super()._kenwood_set_success(cmd, modcmd, value, response) + + def _mem_spec_fixup(self, spec, memid, mem): + spec[6] = "%i" % (mem.tmode == "DTCS") + spec[8] = "%03i0" % (chirp_common.DTCS_CODES.index(mem.dtcs) + 1) - mem.number = int(spec[2]) - mem.freq = int(spec[3]) - mem.tuning_step = STEPS[int(spec[4], 16)] - mem.duplex = DUPLEX[int(spec[5])] - mem.tmode = get_tmode(spec[7], spec[8], spec[9]) - mem.rtone = self._kenwood_valid_tones[int(spec[10]) - 1] - mem.ctone = self._kenwood_valid_tones[int(spec[12]) - 1] + def _parse_mem_fixup(self, mem, spec): if spec[11] and spec[11].isdigit(): mem.dtcs = chirp_common.DTCS_CODES[int(spec[11][:-1]) - 1] else: LOG.warn("Unknown or invalid DCS: %s" % spec[11]) - if spec[13]: - mem.offset = int(spec[13]) - else: - mem.offset = 0 - mem.mode = MODES[int(spec[14])] - mem.skip = int(spec[15]) and "S" or "" - return mem + def get_features(self, *args, **kwargs): + rf = super().get_features(*args, **kwargs) + rf.has_dtcs = True + rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"] + return rf