# HG changeset patch # User Rick DeWitt # Date 1619446451 25200 # Mon Apr 26 07:14:11 2021 -0700 # Node ID 8f5228c4a5d87cce3b151d975af63ae77f3c817d # Parent c04f4296f628b927cfb7a732f42062b32f785e00 [icomciv.py] Add settings to IC-7300 live mode. Feature request #8987 diff -r c04f4296f628 -r 8f5228c4a5d8 chirp/drivers/icomciv.py --- a/chirp/drivers/icomciv.py Mon Apr 26 07:07:38 2021 -0700 +++ b/chirp/drivers/icomciv.py Mon Apr 26 07:14:11 2021 -0700 @@ -1,11 +1,12 @@ -# Latest update: March, 2021 RJ DeWitt added IC-7300 +# Latest update: Apr, 2021 Rick DeWitt added IC7300 Settings import struct import logging from chirp.drivers import icf from chirp import chirp_common, util, errors, bitwise, directory from chirp.memmap import MemoryMap from chirp.settings import RadioSetting, RadioSettingGroup, \ - RadioSettingValueList, RadioSettingValueBoolean + RadioSettingValueList, RadioSettingValueBoolean, RadioSettingValueFloat, \ + RadioSettingValueString, RadioSettingValueInteger, RadioSettings LOG = logging.getLogger(__name__) @@ -80,6 +81,31 @@ char name[16]; // 52-60 Name of station """ +MEM_IC7300_FORMAT = """ +bbcd number[2]; // 1,2 +u8 spl:4, // 3 split and select memory settings + select:4; +lbcd freq[5]; // 4-8 receive freq +u8 mode; // 9 operating mode +u8 filter; // 10 filter 1-3 (undocumented) +u8 dataMode:4, // 11 data mode setting (on or off) + tmode:4; // 11 tone type +u8 duplex; // Dummy duplex === +bbcd rtone[2]; // 12-14 tx tone freq +u8 pad2; +bbcd ctone[2]; // 15-17 tone rx squelch setting +lbcd freq_tx[5]; // 4-8 transmit freq +u8 mode_tx; // 9 tx operating mode +u8 filter_tx; // 10 +u8 dataMode_tx:4, // 11 tx data mode setting (on or off) + tmode_tx:4; // 11 tx tone type +u8 pad3; +bbcd rtone_tx[2]; // 12-14 repeater tone freq +u8 pad4; +bbcd ctone_tx[2]; // 15-17 tone squelch setting +char name[10]; // 18-27 Callsign +""" + MEM_IC910_FORMAT = """ u8 bank; // 1 bank number bbcd number[2]; // 2,3 @@ -114,31 +140,6 @@ char name[9]; """ -MEM_IC7300_FORMAT = """ -bbcd number[2]; // 1,2 -u8 spl:4, // 3 split and select memory settings - select:4; -lbcd freq[5]; // 4-8 receive freq -u8 mode; // 9 operating mode -u8 filter; // 10 filter 1-3 (undocumented) -u8 dataMode:4, // 11 data mode setting (on or off) - tmode:4; // 11 tone type -char pad1; -bbcd rtone[2]; // 12-14 tx tone freq -char pad2; -bbcd ctone[2]; // 15-17 tone rx squelch setting -lbcd freq_tx[5]; // 4-8 transmit freq -u8 mode_tx; // 9 tx operating mode -u8 filter_tx; // 10 -u8 dataMode_tx:4, // 11 tx data mode setting (on or off) - tmode_tx:4; // 11 tx tone type -char pad3; -bbcd rtone_tx[2]; // 12-14 repeater tone freq -char pad4; -bbcd ctone_tx[2]; // 15-17 tone squelch setting -char name[10]; // 18-27 Callsign -""" - SPLIT = ["", "spl"] @@ -260,6 +261,14 @@ FORMAT = MEM_IC7100_FORMAT +class IC7300MemFrame(MemFrame): + FORMAT = MEM_IC7300_FORMAT + + def get_obj(self): + self._data = MemoryMap(str(self._data)) + return bitwise.parse(self.FORMAT, self._data) + + class IC910MemFrame(BankMemFrame): FORMAT = MEM_IC910_FORMAT @@ -270,14 +279,6 @@ return bitwise.parse(mem_duptone_format, self._data) -class IC7300MemFrame(MemFrame): - FORMAT = MEM_IC7300_FORMAT - - def get_obj(self): - self._data = MemoryMap(str(self._data)) - return bitwise.parse(self.FORMAT, self._data) - - class SpecialChannel(object): """Info for special (named) channels""" @@ -561,7 +562,8 @@ mem.duplex = self._rf.valid_duplexes[memobj.duplex] if self._rf.can_odd_split and memobj.spl: - mem.duplex = "split" + if hasattr(memobj, "duplex"): + mem.duplex = "split" mem.offset = int(memobj.freq_tx) mem.immutable = [] elif hasattr(memobj, "duplexOffset"): @@ -802,6 +804,173 @@ @directory.register +class Icom7300Radio(IcomCIVRadio): + """Icom IC-7300""" + MODEL = "IC-7300" + _model = "\x94" + _template = 100 # Use P1 as blank template + + _SPECIAL_CHANNELS = { + "P1": 100, + "P2": 101, + } + _SPECIAL_CHANNELS_REV = dict(zip(_SPECIAL_CHANNELS.values(), + _SPECIAL_CHANNELS.keys())) + + def _is_special(self, number): + return number > 99 or isinstance(number, str) + + def _get_special_info(self, number): + info = SpecialChannel() + if isinstance(number, str): + info.name = "ScanEdge" + number + info.channel = self._SPECIAL_CHANNELS[number] + info.location = info.channel + else: + info.location = number + info.name = "ScanEdge" + self._SPECIAL_CHANNELS_REV[number] + info.channel = info.location + return info + + def _initialize(self): + self._classes["mem"] = IC7300MemFrame + self._rf.has_name = True + self._rf.has_settings = True + self._rf.has_dtcs = False + self._rf.has_dtcs_polarity = False + self._rf.has_bank = False + self._rf.has_tuning_step = False + self._rf.has_nostep_tuning = True + self._rf.can_odd_split = True + self._rf.memory_bounds = (1, 99) + self._rf.valid_modes = [ + "LSB", "USB", "AM", "CW", "RTTY", "FM", "CWR", "RTTYR", + "Data+LSB", "Data+USB", "Data+AM", "N/A", "N/A", "Data+FM" + ] + self._rf.valid_tmodes = ["", "Tone", "TSQL"] + # 7300 uses "split", not duplex: memobj.spl + self._rf.valid_duplexes = ["", "split"] # prevent using memobj.duplex + self._rf.valid_bands = [(1800000, 70500000)] + self._rf.valid_skips = [] + self._rf.valid_name_length = 10 + self._rf.valid_characters = chirp_common.CHARSET_ASCII + self._rf.valid_special_chans = sorted(self._SPECIAL_CHANNELS.keys()) + + def get_settings(self): + bas = RadioSettingGroup("basic", "BASIC") + grps = RadioSettings(bas) + + global scrsav + scrsav = ["Off", "15 Mins", "30 Mins", "60 Mins"] + global bas_list # Used by set_settings + # name, display string, subcmd, len, type, v1, v2 + # type: 1=bool, 2=int, 3=int %, 4=list, 5=string, 6= Offset freq + bas_list = [("PON", "Show RF Power at power on", "\x92", 1, 1, 0, 0), + ("SHMSG", "Show MyCall message at power on", "\x90", 1, + 1, 0, 0), + ("MSG", "MyCall Power-on message", "\x91", 10, 5, 0, 0), + ("BPON", "Confirmation Beep", "\x23", 1, 1, 0, 0), + ("BPV", "Beep Volume %", "\x21", 2, 3, 0, 255), + ("SCRN", "Screensaver Timeout", "\x89", 1, 4, scrsav, 0), + ("6MO", "6m FM Offset (MHz)", "\x32", 3, 6, -9.999, 9.999), + ("HFO", "HF FM Offset (MHz)", "\x31", 3, 6, -9.999, + 9.999), ] + + f = Frame() + f.set_command(0x1A, 5) + + global bas_elements # Used by set_settings + bas_elements = [] + dpx = 2 # resp index to 1st data byte + for name, disp, sbc, lnx, typ, v1, v2 in bas_list: + bas_elements.append(name) + f.set_data("\x00" + sbc) + LOG.debug("Get '%s' --" % name) + f.send(0x94, 0xE0, self.pipe) + f.read(self.pipe) + resp = f.get_data()[dpx:] + if typ == 1: # Boolean + vlu = ord(resp) + rs = RadioSetting(name, disp, + RadioSettingValueBoolean(vlu)) + elif typ == 2 or typ == 3: # Integer + if typ == 2: # 1 byte + vlu = int("%02x" % ord(resp)) + else: # 2 bytes, 0-255 as 0-100 %, encodes 138 as 01 38 + vlu = int("%02x" % ord(resp[0])) * 100 + vlu += int("%02x" % ord(resp[1])) + vlu = round(vlu / 2.55) + rs = RadioSetting(name, disp, + RadioSettingValueInteger(v1, v2, vlu)) + elif typ == 4: # List + vlu = int("%02x" % ord(resp)) + rs = RadioSetting(name, disp, + RadioSettingValueList(v1, v1[vlu])) + elif typ == 5: # String + stx = resp + rs = RadioSetting(name, disp, + RadioSettingValueString(0, lnx, stx)) + elif typ == 6: # Offset Freq + vlu = int("%02x" % ord(resp[0])) * 1000.0 + vlu += int("%02x" % ord(resp[1])) * 100000.0 + vlu += int("%02x" % ord(resp[2])) * 10000000.0 + vlu = vlu / 10000000.0 + if ord(resp[3]) == 1: + vlu = -vlu + rs = RadioSetting(name, disp, + RadioSettingValueFloat(v1, v2, vlu)) + else: + continue + bas.append(rs) + return grps + + def set_settings(self, settings): + for element in settings: + elname = element.get_name() + if not isinstance(element, RadioSetting): + self.set_settings(element) + continue + if not element.changed(): + continue + if (elname in bas_elements): + ndx = bas_elements.index(elname) + sbc = bas_list[ndx][2] + typ = bas_list[ndx][4] + f = Frame() + f.set_command(0x1A, 5) + if typ == 1: # Boolean + vlu = int(element.value) + stx = chr(vlu) + elif typ == 3: # 2-byte integer % + vlu = int(element.value) + vsx = "%04d" % (vlu * 2.55) + stx = chr(int(vsx[1:2], 16)) + chr(int(vsx[2:4], 16)) + elif typ == 4: # list index + vlu = scrsav.index(str(element.value)) + stx = chr(int(vlu), 16) + elif typ == 5: # string + vlu = str(element.value) + stx = vlu + elif typ == 6: # freq + offset. 3 bytes + sign 0/1 + vlu = abs(float(str(element.value))) # Ex: 9.876543 + vlu = int(vlu * 1000.0) # 9876 (no <=100 hz digit) + vsx = str(vlu) + "0" # '98760', Fixed 100 hz digit + b1 = int(vsx[3:5], 16) # evaluate as hex + b2 = int(vsx[1:3], 16) + b3 = int(vsx[0], 16) + stx = chr(b1) + chr(b2) + chr(b3) # 60 87 09 + if float(str(element.value)) >= 0: + stx += chr(0) + else: + stx += chr(1) + stx = chr(0) + sbc + stx + f.set_data(stx) + f.send(0x94, 0xE0, self.pipe) + else: + LOG.error("Unknown element %s" % elname) + + +@directory.register class Icom746Radio(IcomCIVRadio): """Icom IC-746""" MODEL = "746" @@ -934,60 +1103,6 @@ # Use Chirp locations starting with 1 self._adjust_bank_loc_start = True - -@directory.register -class Icom7300Radio(IcomCIVRadio): # Added March, 2021 by Rick DeWitt - """Icom IC-7300""" - MODEL = "IC-7300" - _model = "\x94" - _template = 100 # Use P1 as blank template - - _SPECIAL_CHANNELS = { - "P1": 100, - "P2": 101, - } - _SPECIAL_CHANNELS_REV = dict(zip(_SPECIAL_CHANNELS.values(), - _SPECIAL_CHANNELS.keys())) - - def _is_special(self, number): - return number > 99 or isinstance(number, str) - - def _get_special_info(self, number): - info = SpecialChannel() - if isinstance(number, str): - info.name = number - info.channel = self._SPECIAL_CHANNELS[number] - info.location = info.channel - else: - info.location = number - info.name = self._SPECIAL_CHANNELS_REV[number] - info.channel = info.location - return info - - def _initialize(self): - self._classes["mem"] = IC7300MemFrame - self._rf.has_name = True - self._rf.has_dtcs = False - self._rf.has_dtcs_polarity = False - self._rf.has_bank = False - self._rf.has_tuning_step = False - self._rf.has_nostep_tuning = True - self._rf.can_odd_split = True - self._rf.memory_bounds = (1, 99) - self._rf.valid_modes = [ - "LSB", "USB", "AM", "CW", "RTTY", "FM", "CWR", "RTTYR", - "Data+LSB", "Data+USB", "Data+AM", "N/A", "N/A", "Data+FM" - ] - self._rf.valid_tmodes = ["", "Tone", "TSQL"] - # self._rf.valid_duplexes = ["", "-", "+", "split"] - self._rf.valid_duplexes = [] # To prevent using memobj.duplex - self._rf.valid_bands = [(1800000, 70500000)] - self._rf.valid_skips = [] - self._rf.valid_name_length = 10 - self._rf.valid_characters = chirp_common.CHARSET_ASCII - self._rf.valid_special_chans = sorted(self._SPECIAL_CHANNELS.keys()) - - CIV_MODELS = { (0x76, 0xE0): Icom7200Radio, (0x88, 0xE0): Icom7100Radio,