# HG changeset patch # User Rick DeWitt # Date 1602857091 25200 # Fri Oct 16 07:04:51 2020 -0700 # Node ID 4d596e517a55f3e8df3508de6a145e348816f9a0 # Parent 98b8a850b0f136c77fe09a4922c32a2a28f708be [ic7300] New driver for Icom IC-7300. Issue #4013 diff -r 98b8a850b0f1 -r 4d596e517a55 chirp/drivers/ic7300.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chirp/drivers/ic7300.py Fri Oct 16 07:04:51 2020 -0700 @@ -0,0 +1,734 @@ +# Copyright 2020 Rick DeWitt +# Icom IC-7300 Vers 1.1: Channel memory and some important settings +# Using CI-V data requests to generate a clone-style memory block +# Vers 1.1 uses py3 bytes() +# Vers 1.2 Moved BAUD from global to radio property +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import time +import struct +import logging +import re +import math +from chirp import chirp_common, directory, memmap +from chirp import bitwise, errors, util +from chirp.settings import RadioSettingGroup, RadioSetting, \ + RadioSettingValueBoolean, RadioSettingValueList, \ + RadioSettingValueString, RadioSettingValueInteger, \ + RadioSettingValueFloat, RadioSettings, InvalidValueError +from textwrap import dedent + +LOG = logging.getLogger(__name__) + +HAS_FUTURE = True +try: # PY3 compliance + from builtins import bytes +except ImportError: + HAS_FUTURE = False + LOG.warning('python-future package is not ' + 'available; %s requires it' % __name__) + + +MEM_FORMAT = """ +#seekto 0x0000; +struct { // 32 bytes per chan + u8 split:4, // My CI-V format, not an image + selmem:4; + ul32 rxfreq; + ul32 txfreq; + u8 tmode; + u8 fltr; + u8 data:4, + tone:4; + u8 rcts; + u8 tsql; + char name[10]; + u8 chpad[8]; +} ch_mem[99]; + +struct { + u8 rfpwr; + u8 beepv; + ul16 hf_ofst; + ul16 m6_ofst; + u8 scrnsav; + char msg[10]; + u8 uprflgs:3, + rfpwron:1, + cfmbeep:1, + opnmsg:1, + hfsign:1, + m6sign:1; +} settings; + +""" +# === Globals ==== +TMODES = ["", "Tone", "TSQL"] +DUPLEX = ["", "-", "+"] +MODES = ["LSB", "USB", "AM", "CW", "RTTY", "FM", "CWR", "RTTYR", + "Data+LSB", "Data+USB"] +TONES = list(chirp_common.TONES) +FILTER = ["FIL1", "FIL2", "FIL3"] +STIMEOUT = 0.6 +BAUDRATES = [115200, 57600, 38400, 19200, 9600, 4800, 2400] + + +def _setbaud(radio): + """ Determine fastest valid baud rate """ + radio.pipe.timeout = 0.1 + for radio.BAUD in BAUDRATES: # Attempt to read CI-V Transceiver status + radio.pipe.baudrate = radio.BAUD + radio.pipe.write("\xFE\xFE\x94\xE0\x1A\x05\x00\x71\xFD") + resp = radio.pipe.read(55) + if len(resp) > 17: + if resp[17] == "\x01": + break # exit For loop + if radio.BAUD < 4800: + msg = "Unable to coomunicate with radio! Cable? Power?" + raise errors.RadioError(msg) + LOG.debug("Baud Rate is %d" % radio.BAUD) + radio.pipe.timeout = STIMEOUT + return + + +def _rhcd_decode(stx, nbyt, lgw=False): + """ Decode decimal value from reverse-hex stx backwards starting + at nbyt index. 4 for freq, 3 for offset. + Returns 4-byte UL32 string. Ignore upper 0's if format is less""" + vstr = "" + for ix in range(nbyt-1, -1, -1): + vstr += "%02x" % ord(stx[ix]) + vx = int(vstr) + if lgw: + LOG.warning("vstr: %s, vs: %04x" % (vstr, vx)) + vstr = chr(vx & 255) + vx = vx >> 8 + vstr += chr(vx & 255) + vx = vx >> 8 + vstr += chr(vx & 255) + vx = vx >> 8 + vstr += chr(vx & 255) + return vstr + + +def _fhdc_decode(stx, nbyt, lgw=False): + """ Decode decimal value from hex string stx, not reversed. + nbyt is length """ + vstr = "" + for ix in range(0, nbyt): # Decode hcd rcts + vstr += "%02x" % ord(stx[ix]) + vx = int(vstr) + if lgw: # print values + LOG.warning("fhdc vstr: %s, vx: %i" % (vstr, vx)) + return vx + + +def _encode_chn(chnum): + """ Channel pair must be encoded as hex coded decimal """ + c1 = 0 + if chnum < 10: + c2 = chnum + elif chnum > 9 and chnum < 20: + c2 = chnum + 6 + elif chnum > 19 and chnum < 30: + c2 = chnum + 12 + elif chnum > 29 and chnum < 40: + c2 = chnum + 18 + elif chnum > 39 and chnum < 50: + c2 = chnum + 24 + elif chnum > 49 and chnum < 60: + c2 = chnum + 30 + elif chnum > 59 and chnum < 70: + c2 = chnum + 36 + elif chnum > 69 and chnum < 80: + c2 = chnum + 42 + elif chnum > 79 and chnum < 90: + c2 = chnum + 48 + elif chnum > 89 and chnum < 100: + c2 = chnum + 54 + elif chnum > 99: + c1 = 1 + c2 = chnum - 100 + chstr = chr(c1) + chr(c2) + return chstr + + +def _read_mem(radio): + """Generate the memory map. """ + radio.pipe.baudrate = radio.BAUD + + status = chirp_common.Status() + status.cur = 0 + status.max = radio._upper + 10 # 10 settings + status.msg = "Reading Memory..." + radio.status_fn(status) + + mtx = "" + for ix in range(0, 32): # blank channel block + mtx += chr(0) + memdat = "" + preamble = "\xFE\xFE\x94\xE0\x1A\x00" + for chn in range(1, 100): # 1-99 + vstr = _encode_chn(chn) + cmdx = preamble + vstr + "\xFD" + radio.pipe.write(cmdx) + resp = radio.pipe.read(18) # read only the echo and Split bytes + if resp[17] == "\xFF": # This chan is empty + memdat += mtx + resp = radio.pipe.read(1) # the trailing FD + else: # read the rest and decode + split = resp[17] + resp = radio.pipe.read(39) + resp = split + resp[:38] # strip trailing FD + memdat += resp[0] # split & selmem + memdat += _rhcd_decode(resp[1:6], 4) # rxfreq string + memdat += _rhcd_decode(resp[15:20], 4) # txfreq + memdat += resp[6] # tmode + vstr = resp[7] # IF Filter + if vstr == "\x00": # Not valid, must be 1 - 3 + vstr = "\x01" + memdat += vstr + memdat += resp[8] # data & tone + v2 = _fhdc_decode(resp[9:], 3) # Decode hcd rcts + vx = float(v2) / 10 + ix = TONES.index(vx) + memdat += chr(ix) + v2 = _fhdc_decode(resp[12:], 3) # Decode hcd tsql + vx = float(v2) / 10 + ix = TONES.index(vx) + memdat += chr(ix) + memdat += resp[29:] # name + memdat += mtx[0:8] # pad + # UI Update + status.cur = chn + radio.status_fn(status) + # End for chn loop + if len(memdat) == 0: # To satisfy run_tests + raise errors.RadioError('No data received.') + # Read some settings + vbits = 0 + radio.pipe.write("\xFE\xFE\x94\xE0\x14\x0A\xFD") # 14 0A: RF Power + resp = radio.pipe.read(16) + rfp = _fhdc_decode(resp[13:], 2) # Decode rf power 0-255 + memdat += chr(rfp) + status.cur += 1 + radio.status_fn(status) + radio.pipe.write("\xFE\xFE\x94\xE0\x1A\x05\x00\x21\xFD") + resp = radio.pipe.read(20) # Beep volume 00-255 + bpv = _fhdc_decode(resp[17:], 2) + memdat += chr(bpv) + radio.pipe.write("\xFE\xFE\x94\xE0\x1A\x05\x00\x31\xFD") + resp = radio.pipe.read(22) # HF Offset + ohfs = _rhcd_decode(resp[17:], 3) # 4-byte RHCD string + memdat += ohfs[:2] # only store as 16 bit + status.cur += 1 + radio.status_fn(status) + if resp[20] == "\x01": # sign bit set + vbits += 2 + radio.pipe.write("\xFE\xFE\x94\xE0\x1A\x05\x00\x32\xFD") + resp = radio.pipe.read(22) # 6m Offset + o6ms = _rhcd_decode(resp[17:], 3) + memdat += o6ms[:2] + status.cur += 1 + radio.status_fn(status) + if resp[20] == "\x01": + vbits += 1 + radio.pipe.write("\xFE\xFE\x94\xE0\x1A\x05\x00\x89\xFD") + resp = radio.pipe.read(19) # Screen Saver Timeout + memdat += resp[17] + status.cur += 1 + radio.status_fn(status) + radio.pipe.write("\xFE\xFE\x94\xE0\x1A\x05\x00\x23\xFD") + resp = radio.pipe.read(19) # Confirmation Beep + if resp[17] == "\x01": + vbits += 8 + status.cur += 1 + radio.status_fn(status) + radio.pipe.write("\xFE\xFE\x94\xE0\x1A\x05\x00\x90\xFD") + resp = radio.pipe.read(19) # Opening Msg on/off + if resp[17] == "\x01": + vbits += 4 + status.cur += 1 + radio.status_fn(status) + radio.pipe.write("\xFE\xFE\x94\xE0\x1A\x05\x00\x92\xFD") + resp = radio.pipe.read(19) # Show RF power at power on + if resp[17] == "\x01": + vbits += 16 + status.cur += 1 + radio.status_fn(status) + radio.pipe.write("\xFE\xFE\x94\xE0\x1A\x05\x00\x91\xFD") + resp = radio.pipe.read(30) # Opening Msg contents: 10 chars + memdat += resp[17:27] + status.cur += 1 + radio.status_fn(status) + memdat += chr(vbits) + return memdat + + +def _rhcd_encode(inum): + """ Convert integer inum into reverse hex coded decimal string """ + sval = "%010d" % inum + hval = chr(int(sval[8:], 16)) + chr(int(sval[6:8], 16)) + hval += chr(int(sval[4:6], 16)) + chr(int(sval[2:4], 16)) + hval += chr(int(sval[:2], 16)) + return hval # 5 bytes + + +def _fhcd_encode(inum): + """ Convert integer inum into forward hex coded decimal string """ + sval = "%010d" % inum + hval = chr(int(sval[:2], 16)) + chr(int(sval[2:4], 16)) + hval += chr(int(sval[4:6], 16)) + chr(int(sval[6:8], 16)) + hval += chr(int(sval[8:], 16)) + return hval # 5 bytes + + +def _sendcmd(radio, stat, cmds): + """ Send the cmds command string to the radio """ + radio.pipe.write(cmds) + kx = len(cmds) + 6 # Cmds echo and status + stx = radio.pipe.read(kx) # Read status + slx = len(stx) + if slx < kx: + msg = "No response from radio. Cable? Power?" + raise errors.RadioError(msg) + # Raise error if next to last status char is not \xFB + if stx[slx - 2] != "\xFB": + msg = "Verification error writing to radio." + raise errors.RadioError(msg) + stat.cur += 1 + radio.status_fn(stat) + return + + +def _write_mem(radio): + """ Send CI-V data to radio """ + radio.pipe.baudrate = radio.BAUD + + status = chirp_common.Status() + status.cur = 0 + status.max = radio._upper + 7 # 8 settings + status.msg = "Writing Memory..." + radio.status_fn(status) + + preamble = "\xFE\xFE\x94\xE0\x1A\x00" + for chn in range(1, 100): + _mem = radio._memobj.ch_mem[chn - 1] + vstr = _encode_chn(chn) + cmdx = preamble + vstr + if _mem.rxfreq == 0: + cmdx += "\xFF\xFD" + else: + datstr = chr((_mem.split << 4) + _mem.selmem) + datstr += _rhcd_encode(_mem.rxfreq) + datstr += chr(_mem.tmode) + if _mem.fltr < 1: # Not allowed + _mem.fltr = 1 + datstr += chr(_mem.fltr) + datstr += chr((_mem.data << 4) + _mem.tone) + datstr += _fhcd_encode(TONES[_mem.rcts] * 10.0)[2:] + datstr += _fhcd_encode(TONES[_mem.tsql] * 10.0)[2:] + # Repeat tx freq and same tmode, fltr, data, tone, rcts, tsql + datstr += _rhcd_encode(_mem.txfreq) + datstr += chr(_mem.tmode) + datstr += chr(_mem.fltr) + datstr += chr((_mem.data << 4) + _mem.tone) + datstr += _fhcd_encode(TONES[_mem.rcts] * 10.0)[2:] + datstr += _fhcd_encode(TONES[_mem.tsql] * 10.0)[2:] + datstr += str(_mem.name) + cmdx += datstr + "\xFD" + _sendcmd(radio, status, cmdx) + # Send Settings + _sets = radio._memobj.settings + preamble = "\xFE\xFE\x94\xE0" + vstr = _fhcd_encode(int(_sets.rfpwr))[4:] # RF Power 0-255 hcd + cmdx = "%s\x14\x0A%s\xFD" % (preamble, vstr) + _sendcmd(radio, status, cmdx) + vstr = chr(int(_sets.rfpwron)) # RF power msg at poweron + cmdx = "%s\x1A\x05\x00\x92%s\xFD" % (preamble, vstr) + _sendcmd(radio, status, cmdx) + vstr = str(_sets.msg).upper() # Requires uppercase + cmdx = "%s\x1A\x05\x00\x91%s\xFD" % (preamble, vstr) # opening msg + _sendcmd(radio, status, cmdx) + vstr = chr(int(_sets.cfmbeep)) # Confirmation beep + cmdx = "%s\x1A\x05\x00\x92%s\xFD" % (preamble, vstr) + _sendcmd(radio, status, cmdx) + vstr = _fhcd_encode(int(_sets.beepv))[4:] # Beep volume 0-255 + cmdx = "%s\x1A\x05\x00\x21%s\xFD" % (preamble, vstr) + _sendcmd(radio, status, cmdx) + vstr = chr(int(_sets.scrnsav)) # Screen saver timeout + cmdx = "%s\x1A\x05\x00\x89%s\xFD" % (preamble, vstr) + _sendcmd(radio, status, cmdx) + vstr = _rhcd_encode(_sets.hf_ofst)[:3] + chr(int(_sets.hfsign)) + cmdx = "%s\x1A\x05\x00\x31%s\xFD" % (preamble, vstr) + _sendcmd(radio, status, cmdx) + vstr = _rhcd_encode(_sets.m6_ofst)[:3] + chr(int(_sets.m6sign)) + cmdx = "%s\x1A\x05\x00\x32%s\xFD" % (preamble, vstr) + _sendcmd(radio, status, cmdx) + return + + +@directory.register +class IC7300Radio(chirp_common.CloneModeRadio): + """Icom IC-7300""" + VENDOR = "Icom" + MODEL = "IC-7300" + BAUD = 115200 + + _upper = 99 + + 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.memory_bounds = (1, 99) + rf.valid_modes = list(MODES) + rf.valid_tmodes = list(TMODES) + rf.valid_duplexes = list(set(DUPLEX)) + rf.valid_tuning_steps = [2.5, 5.0, 10.0, 15.0, 20.0, 25.0, + 30.0, 50.0, 100.0] + rf.valid_bands = [(5000, 74800000)] # Requires validate_memory + rf.valid_skips = ["", "S", "P"] + rf_valid_tones = list(TONES) + rf.valid_characters = chirp_common.CHARSET_ASCII + rf.valid_name_length = 10 + return rf + + @classmethod + def get_prompts(cls): + rp = chirp_common.RadioPrompts() + rp.info = _(dedent("""\ + Use the channel 'Properties' window, 'Other' tab to + set the IF Filter bandwidth and assign the Selected Memory + scan group. + """)) + rp.pre_download = _(dedent("""\ + Follow these instructions to download your config: + + 1 - Connect a USB type B (Printer) cable to the rear USB jack. + 2 - In the radio MENU > SET > Connectors > CI-V + Set/Verify the CI-V Baud Rate is "Auto" + Set/Verify the CI-V Address is "94h" + Set/Verify the CI-V Transeive is "ON" + 3 - In the radio MENU > SET > Connectors > USB Serial Function + Set/Verify the mode is "CI-V" + 4 - Radio > Download from radio + """)) + rp.pre_upload = _(dedent("""\ + Follow these instructions to upload your config: + + 1 - Verify the MENU configuration is the same as for download. + 2 - Connect a USB type B cable to the rear USB jack. + 3 - Radio > Upload to radio + """)) + return rp + + def sync_in(self): + """Download from radio""" + try: + _setbaud(self) + data = bytes(_read_mem(self)) + except errors.RadioError: + # Pass through any real errors we raise + raise + except Exception: + # If anything unexpected happens, make sure we raise + # a RadioError and log the problem + LOG.exception('Unexpected error during download') + raise errors.RadioError('Unexpected error communicating ' + 'with the radio') + + self._mmap = memmap.MemoryMapBytes(data) + self.process_mmap() + return + + def sync_out(self): + """Upload to radio""" + try: + _setbaud(self) + _write_mem(self) + except errors.RadioError: + # Pass through any real errors we raise + raise + except Exception: + # If anything unexpected happens, make sure we raise + # a RadioError and log the problem + LOG.exception('Unexpected error during upload') + raise errors.RadioError('Unexpected error communicating ' + 'with the radio') + return + + def process_mmap(self): + """Process the mem map into the mem object""" + self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) + return + + def validate_memory(self, mem): + """ Overide the run_tests.py Brute_Force memory validation test """ + if mem.freq >= 3000 and mem.freq <= 74800000: + return True + else: + return False + + def get_memory(self, number): + """Convert raw channel data (_mem) into UI columns (mem)""" + mem = chirp_common.Memory() + _mem = self._memobj.ch_mem[number - 1] + mem.number = number + mnx = "" + if _mem.rxfreq == 0: + mem.empty = True + # mem.name = "" + # mem.mode = MODES[0] + return mem + for char in _mem.name: + mnx += chr(char) + mem.name = mnx.strip() + mem.empty = False + mem.freq = int(_mem.rxfreq) + mem.duplex = DUPLEX[0] # None by default + mem.offset = 0 + if _mem.rxfreq < _mem.txfreq: # + shift + mem.duplex = DUPLEX[2] + mem.offset = _mem.txfreq - _mem.rxfreq + if _mem.rxfreq > _mem.txfreq: # - shift + mem.duplex = DUPLEX[1] + mem.offset = _mem.rxfreq - _mem.txfreq + if _mem.txfreq == 0: + # leave offset alone, or run_tests will bomb + mem.duplex = DUPLEX[0] + mem.mode = MODES[_mem.tmode + (_mem.data * 8)] + mem.tmode = TMODES[_mem.tone] + mem.ctone = TONES[_mem.tsql] + mem.rtone = TONES[_mem.rcts] + + # Extra + mem.extra = RadioSettingGroup("extra", "Extra") + + options = ["N/A", "Fil 1: Wide", "Fil 2: Mid", "Fil 3: Narrow"] + vx = int(_mem.fltr) + if vx == 0: + vx = 1 + rx = RadioSettingValueList(options, options[vx]) + rset = RadioSetting("fltr", "IF Filter BW", rx) + mem.extra.append(rset) + + options = ["None", "*1", "*2", "*3"] + vx = int(_mem.selmem) + rx = RadioSettingValueList(options, options[vx]) + rset = RadioSetting("selmem", "Selected Memory Scan Group", rx) + mem.extra.append(rset) + + return mem + + def set_memory(self, mem): + """Convert UI column data (mem) into MEM_FORMAT memory (_mem)""" + _mem = self._memobj.ch_mem[mem.number - 1] + + if mem.empty: + _mem.split = 0 + _mem.selmem = 0 + _mem.txfreq = 0 + _mem.rxfreq = 0 + _mem.tmode = 0 + _mem.fltr = 1 # fltr can't be <1 + _mem.data = 0 + _mem.tone = 0 + _mem.rcts = 8 + _mem.tsql = 8 + _mem.name = " " + return + + _mem.rxfreq = mem.freq + if mem.duplex == "": + _mem.split = 0 + _mem.txfreq = _mem.rxfreq + elif mem.duplex == "+": + _mem.txfreq = mem.freq + mem.offset + _mem.split = 1 + else: + _mem.txfreq = mem.freq - mem.offset + _mem.split = 1 + if mem.mode == "Data+LSB" or mem.mode == "Data+USB": + _mem.tmode = MODES.index(mem.mode) - 8 + _mem.data = 1 + else: + _mem.tmode = MODES.index(mem.mode) + _mem.data = 0 + _mem.tone = 0 + if mem.tmode == "Tone": + _mem.tone = 1 + if mem.tmode == "TSQL": + _mem.tone = 2 + _mem.rcts = chirp_common.TONES.index(mem.rtone) + _mem.tsql = chirp_common.TONES.index(mem.ctone) + _mem.name = mem.name.ljust(10) + + # Extra settings + for setting in mem.extra: + setattr(_mem, setting.get_name(), setting.value) + + return + + def get_settings(self): + """Translate the MEM_FORMAT structs into settings in the UI""" + # Define mem struct write-back shortcuts + _sets = self._memobj.settings + basic = RadioSettingGroup("basic", "Basic Settings") + groups = RadioSettings(basic) + + def chars2str(cary, knt): + """Convert raw memory char array to a string. + NOT a callback.""" + stx = "" + for char in cary[:knt]: + stx += chr(char) + stx = stx.ljust(10) + return stx + + def _do_ofst(setting, obj, atrb, arg): + """ Adjust the offset freq and sset sign bit """ + sv = str(setting.value) + vz = float(sv) + if atrb == "hf_ofst": + if vz < 0: + setattr(obj, "hfsign", 1) + vz = abs(vz) + else: + setattr(obj, "hfsign", 0) + else: + if vz < 0: + setattr(obj, "m6sign", 1) + vz = abs(vz) + else: + setattr(obj, "m6sign", 0) + vz = vz * arg + setattr(obj, atrb, int(vz)) + return + + def _domath(setting, obj, atrb, opr, arg): + """ Apply a math operation to the modified atrb + If opr = "M" then multiply by arg, else add arg. """ + sv = str(setting.value) + v1 = float(sv) + if opr == "M": + v2 = v1 * arg + # Need to round up or down for percent settings + if math.modf(v2)[0] < 0.5: + v3 = math.floor(v2) + else: + v3 = math.ceil(v2) + else: + v3 = v1 + arg + setattr(obj, atrb, int(v3)) + return + + # --- Basic + vlu = int(_sets.rfpwr) // 2.55 + rx = RadioSettingValueInteger(0, 100, vlu) + rset = RadioSetting("settings.rfpwr", + "RF Power (%)", rx) + rset.set_apply_callback(_domath, _sets, "rfpwr", "M", 2.55) + basic.append(rset) + + rx = RadioSettingValueBoolean(bool(_sets.rfpwron)) + rset = RadioSetting("settings.rfpwron", + "Show RF Power at power on", rx) + basic.append(rset) + + rx = RadioSettingValueBoolean(bool(_sets.opnmsg)) + rset = RadioSetting("settings.opnmsg", + "Show MyCall message at power on", rx) + basic.append(rset) + + tmp = chars2str(_sets.msg, 10) + rx = RadioSettingValueString(0, 10, tmp) + rset = RadioSetting("settings.msg", + "MyCall Power-on message", rx) + basic.append(rset) + + rx = RadioSettingValueBoolean(bool(_sets.cfmbeep)) + rset = RadioSetting("settings.cfmbeep", "Confirmation Beep", rx) + basic.append(rset) + + vlu = int(_sets.beepv / 2.55) + rx = RadioSettingValueInteger(0, 255, vlu) + rset = RadioSetting("settings.beepv", + "Confirmation Beep Volume (%)", rx) + rset.set_apply_callback(_domath, _sets, "beepv", "M", 2.55) + basic.append(rset) + + options = ["Off", "15 Mins", "30 Mins", "60 Mins"] + rx = RadioSettingValueList(options, options[_sets.scrnsav]) + rset = RadioSetting("settings.scrnsav", + "Screensaver Timeout", rx) + basic.append(rset) + + vlu = int(_sets.hf_ofst) + vlu = vlu / 10000.0 + if _sets.hfsign: + vlu = -vlu + rx = RadioSettingValueFloat(-9.999, 9.999, vlu, 0.001, 4) + rset = RadioSetting("settings.hf_ofst", + "HF Band default offset (Mhz)", rx) + rset.set_apply_callback(_do_ofst, _sets, "hf_ofst", 10000.0) + basic.append(rset) + + vlu = int(_sets.m6_ofst) + vlu = vlu / 10000.0 + if _sets.m6sign: + vlu = -vlu + rx = RadioSettingValueFloat(-9.999, 9.999, vlu, 0.001, 4) + rset = RadioSetting("settings.m6_ofst", + "HF Band default offset (Mhz)", rx) + rset.set_apply_callback(_do_ofst, _sets, "m6_ofst", 10000.0) + basic.append(rset) + + return groups + + def set_settings(self, settings): + _settings = self._memobj.settings + for element in settings: + if not isinstance(element, RadioSetting): + self.set_settings(element) + continue + else: + try: + name = element.get_name() + if "." in name: + bits = name.split(".") + obj = self._memobj + for bit in bits[:-1]: + if "/" in bit: + bit, index = bit.split("/", 1) + index = int(index) + obj = getattr(obj, bit)[index] + else: + obj = getattr(obj, bit) + setting = bits[-1] + else: + obj = _settings + setting = element.get_name() + + if element.has_apply_callback(): + LOG.debug("Using apply callback") + element.run_apply_callback() + elif element.value.get_mutable(): + LOG.debug("Setting %s = %s" % (setting, element.value)) + setattr(obj, setting, element.value) + except Exception, e: + LOG.debug(element.get_name()) + raise diff -r 98b8a850b0f1 -r 4d596e517a55 tools/cpep8.manifest --- a/tools/cpep8.manifest Sun Oct 11 15:39:26 2020 -0400 +++ b/tools/cpep8.manifest Fri Oct 16 07:04:51 2020 -0700 @@ -43,6 +43,7 @@ ./chirp/drivers/ic2720.py ./chirp/drivers/ic2730.py ./chirp/drivers/ic2820.py +./chirp/drivers/ic7300.py ./chirp/drivers/ic9x.py ./chirp/drivers/ic9x_icf.py ./chirp/drivers/ic9x_icf_ll.py