# HG changeset patch # User Rick DeWitt # Date 1576418432 28800 # Sun Dec 15 06:00:32 2019 -0800 # Node ID 598027f8a1a088e2a57c054a0bbfde47d88a7290 # Parent 51c55c47f12a1a57961e1135a69627ff730ec0d0 [tmd710g] New Clone-Mode driver for Kenwood TM-D710GA/GE. Issue #7467 Implements true clone mode using PROG MCP binary image. diff -r 51c55c47f12a -r 598027f8a1a0 chirp/drivers/tmd710g.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chirp/drivers/tmd710g.py Sun Dec 15 06:00:32 2019 -0800 @@ -0,0 +1,1398 @@ +# Copyright 2011 Dan Smith +# -- 2019 Rick DeWitt +# -- Implementing Kenwood TM-D710G as MCP Clone Mode +# 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__) + +MEM_FORMAT = """ +struct chns { // 16 bytes channel structure + ul32 rxfreq; + u8 tstep; + u8 mode; + u8 tmode:4, + duplex:4; // 4 = split + u8 rtone; + u8 ctone; + u8 dtcs; + u8 cross; + ul32 offset; // or Split mode TX freq + u8 splitstep; +}; + +#seekto 0x0000; +struct { + u8 unk000[16]; + u8 unk010; + u8 unk011; + char unk012[3]; + u8 ansbck; + u8 unk016; + u8 pnlklk; + u8 dspmemch; + u8 m10mz; + u8 micsens; + u8 opband; + u8 unk01c; + u8 rptrmode; + u8 rptrhold; + u8 rptridx; + u8 unk020; + u8 pcbaud; + u8 unk022; + u8 pwdon; // 0x0023 + u8 unk024; + u8 unk025; + u8 unk026; + u8 unk027; + u8 unk028; + u8 unk029; + char pswd[6]; // 0x002a - 2f +} block1; + +#seekto 0x0030; +struct { + char code[16]; // @ 0x0030 +} dtmc[10]; + +struct { + char id[8]; // 0x00d0 - 0x011f +} dtmn[10]; + +struct { // 0x0120 - 0x023f + u8 unk0120; + u8 unk0121; + u8 unk0122[78]; + char rptrid[12]; // 0x0170 - 017b + u8 unk017c; + u8 unk017d; + u8 unk017e; + u8 unk017f; + u8 unk0180[128]; // 0x0180 - 0x01ff + u8 unk0200; + u8 unk0201; + u8 unk0202; + u8 unk0203; + u8 unk0204; + u8 unk0205; + u8 unk0206; + u8 a_pwr; + u8 wxalerta; + u8 asmsql; + u8 unk020a; + u8 unk020b; + u8 unk020c; + u8 unk020d; + u8 unk020e; + u8 unk020f; + u8 unk0210; + u8 unk0211; + u8 unk0212; + u8 b_pwr; + u8 wxalertb; + u8 bsmsql; + u8 unk0216; + u8 unk0217; + u8 unk0218; + u8 unk0219; + u8 unk021a; + u8 unk021b; + u8 unk021c; + u8 unk021d; + u8 unk021e; + u8 unk021f; + u8 unk0220; + u8 unk0221; + u8 unk0222; + u8 unk0223; + u8 unk0224; + u8 unk0225; + u8 unk0226; + u8 unk0227; + u8 unk0228; + u8 unk0229; + u8 unk022a; + u8 unk022b; + u8 unk022c; + u8 unk022d; + u8 unk022e; + u8 unk022f; + u8 unk0230; + u8 unk0231; + u8 sqclogic; + u8 txband; + u8 single; + u8 unk0235; + u8 mute; + u8 unk0237; + u8 unk0238; + u8 unk0239; + u8 unk0237a; + u8 unk023b; + u8 unk023c; + u8 unk023d; + u8 unk023e; + u8 unk023f; +} block1a; + +struct chns vfo[10]; // 0x0240 - 0x02df + +struct { + char pwron[8]; // @ 0x02e0 - 0x02e7 + u8 unk02e8; + u8 unk02e9; + u8 unk02ea; + u8 unk02eb; + u8 unk02ec; + u8 unk02ed; + u8 unk02ee; + u8 unk02ef; + char memgrplk[10]; // 0x02f0 - 02f9 + u8 unk02fa; + u8 unk02fb; + u8 unk02fc; + u8 unk02fd; + u8 unk02fe; + u8 unk02ff; +} block2; + +struct { + ul32 blow; // 0x0300 - 0x034f + ul32 bhigh; +} progvfo[10]; + +struct { + u8 beepon; // 0x0350 + u8 beepvol; + u8 extspkr; + u8 ance; + u8 lang; + u8 vcvol; + u8 vcspd; + u8 pbkrpt; + u8 pbkint; + u8 cntrec; + u8 vhfaip; + u8 uhfaip; + u8 ssqlhu; + u8 mutehu; + u8 beatshft; + u8 tot; + u8 recall; // 0x0360 + u8 eclnkspd; + u8 dtmfhld; + u8 dtmfspd; + u8 dtmfpau; + u8 dtmflck; + u8 rptrofst; + u8 rptr1750; + u8 bright; + u8 autobri; + u8 bkltclr; + u8 pf1key; + u8 pf2key; + u8 micpf1; + u8 micpf2; + u8 micpf3; + u8 micpf4; // 0x0370 + u8 miclck; + u8 unk0372; + u8 scnrsm; + u8 apo; + u8 extband; + u8 extbaud; + u8 sqcsrc; + u8 dispbar; + u8 bkltcont; + u8 unk037a; + u8 unk037b; + u8 dsprev; + u8 vsmode; + u8 intband; + u8 wxscntm; + u8 scntot; // 0x0380 + u8 scncot; + u8 unk037f; + u8 unk0383; + u8 unk0384; + u8 unk0385; + u8 unk0386; + u8 unk0387; + u8 unk0388; + u8 unk0389; + u8 unk038a; + u8 unk038b; + u8 unk038c; + u8 unk038d; + u8 unk038e; + u8 unk038f; + u8 unk0390; // 0x0390 + u8 unk0391; + u8 unk0392; + } block3; + + struct { + u8 unk0393[236]; +} block4; + +#seekto 0x0480; +struct chns call[2]; + +#seekto 0x0e00; +struct { + u8 band; + u8 skip; +} chmap[1020]; + +#seekto 0x01700; +struct chns ch_mem[1020]; + +#seekto 0x05900; +struct { + char name[8]; +} ch_nam[1020]; // ends @ 0x07840 + +#seekto 0x07df0; +struct { + char comnt[32]; +} mcpcom; + +""" + +MEMSIZE = 0x07eff # img file size without metadata +STIMEOUT = 0.1 +TERM = chr(13) # Cmd write terminator +ACK = chr(6) # Data write acknowledge char +BAUD = 0 +W8S = 0.01 # short wait, secs +W8L = 0.1 # long wait +TMD710_DUPLEX = ["", "+", "-", "n/a", "split"] +TMD710_SKIP = ["", "S"] +TMD710_MODES = ["FM", "NFM", "AM"] +TMD710_BANDS = [(118000000, 135995000), + (136000000, 199995000), + (200000000, 299995000), + (300000000, 399995000), + (400000000, 523995000), + (800000000, 1299995000)] +TMD710_STEPS = [5.0, 6.25, 8.33, 10.0, 12.5, 15.0, 20.0, 25.0, + 30.0, 50.0, 100.0] +# Need string list of those steps for mem.extra value list +STEPS_STR = [] +for val in TMD710_STEPS: + STEPS_STR.append("%3.2f" % val) +TMD710_TONE_MODES = ["", "Tone", "TSQL", "DTCS", "Cross"] +TMD710_CROSS = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone"] +TMD710_DTSC = list(chirp_common.DTCS_CODES) +TMD710_TONES = list(chirp_common.TONES) +TMD710_TONES.remove(159.8) +TMD710_TONES.remove(165.5) +TMD710_TONES.remove(171.3) +TMD710_TONES.remove(177.3) +TMD710_TONES.remove(183.5) +TMD710_TONES.remove(189.9) +TMD710_TONES.remove(196.6) +TMD710_TONES.remove(199.5) +TMD710_CHARS = chirp_common.CHARSET_ALPHANUMERIC + "?!'.,-/&#%()<>;:@" +TMD710_CHARS += chr(34) # " + + +def read_str(radio): + """ Read chars until terminator """ + stq = "" + ctq = "" + while ctq != TERM: + ctq = radio.pipe.read(1) + stq += ctq + LOG.debug(" + [%s]" % stq) + return stq[:-1] # Return without trm + + +def _dly(tdly): + """ Pause for tdly (float) secs """ + ts = time.time() + while (time.time() - ts) < tdly: + xx = 0 # NOP + return + + +def command(ser, cmd, rsplen, w8t=0.01): + """Send cmd to radio via ser""" + # cmd is output string with possible terminator + # rsplen is expected response char count, NOT incl prefix and term + # If rsplen = 0 then do not read after write + ser.write(cmd) + LOG.debug("PC->RADIO [%s]" % cmd) + _dly(w8t) + result = "" + if rsplen > 0: # read response + result = ser.read(rsplen) + LOG.debug("RADIO->PC [%s]" % result) + return result + + +def _connect_radio(radio): + """Determine baud rate and verify radio on-line""" + global BAUD # Declaration allows modification + bauds = [57600, 38400, 19200, 9600] + if BAUD > 0: + bauds.insert(0, BAUD) # Make the detected one first + + for bd in bauds: + radio.pipe.baudrate = bd + BAUD = bd + # Flush the input buffer + radio.pipe.timeout = 0.005 + junk = radio.pipe.read(256) + radio.pipe.timeout = STIMEOUT + + LOG.debug("Trying %i baud ID..." % BAUD) + # LOG.warning("Trying %i baud ID..." % BAUD) # @@@ + radio.pipe.write(TERM) + radio.pipe.read(25) + resp = command(radio.pipe, "ID" + TERM, 24, W8S) + if resp.find("?") >= 0: # repeat it + resp = command(radio.pipe, "ID" + TERM, 24, W8S) + resp = resp[:-1] # strip term + LOG.debug("Got [%s] at %i Baud." % (resp, BAUD)) + # LOG.warning("Got [%s] at %i Baud." % (resp, BAUD)) # @@@ + if resp.find(radio.ID) > 0: # Good comms + return + raise errors.RadioError("No response from radio") + return + + +def upd8_stat(self, stat, stp=1): + """ Increment status bar """ + knt = stat.cur + stp + stat.cur = knt + self.status_fn(stat) + return + + +def mak_addr(bk1, bka, sbn, knt): + """ Create 4-char TMD710G hex address + count string """ + # bk1 bka sbn knt + bkn = bk1 + bka + addr = chr((bkn & 0xFF00) >> 8) + chr(bkn & 0xFF) + addr += chr((sbn & 0xFF)) + chr(knt & 0x0ff) + return addr + + +def my_val_list(setting, opts, obj, atrb, fix=0, ndx=-1): + """Callback:from ValueList. Set the integer index.""" + # This function is here to be available to get_mem and get_set + # fix is optional additive offset to the list index + # ndx is optional obj[ndx] array index + value = opts.index(str(setting.value)) + value += fix + if ndx >= 0: # indexed obj + setattr(obj[ndx], atrb, value) + else: + setattr(obj, atrb, value) + return + + +def _read_mem(radio): + """ Load the memory map """ + # UI progress + status = chirp_common.Status() + status.cur = 0 + val = 0 + for mx in range(0, radio._blks["nblks"]): + val += radio._blks["numpkts"][mx] + status.max = val + status.msg = "Reading %i Blocks" % val + radio.status_fn(status) + + data = "" + rdcnt = 261 + cmc = "0M PROGRAM" + TERM + resp0 = command(radio.pipe, cmc, 3, W8S) + radio.pipe.baudrate = 57600 # PROG mode is always 57.6 + junk = radio.pipe.read(16) # trailing byte + blk0 = 0 + for bkx in range(0, 0x07f): + cmc = "R" + mak_addr(blk0, bkx, 0, 0) + resp0 = command(radio.pipe, cmc, rdcnt, W8S) + if len(resp0) < rdcnt: + junk = command(radio.pipe, "E", 0, W8S) + raise errors.RadioError("Packet %i read error, %i bytes." + % (bkx, len(resp0))) + return data + if bkx == 0: # 1st packet + mht = resp0[5:9] # Magic Header Thingy after cmd echo + data += mht[0:1] + chr(255) + chr(255) + chr(255) + resp0[9:] + else: + data += resp0[5:] # skip cmd echo + upd8_stat(radio, status) # UI Update + # Exit Prog mode, no TERM + resp = command(radio.pipe, "E", 0, W8S) + radio.pipe.baudrate = BAUD + return data + + +def _write_mem(radio): + """ Send MW commands for each channel """ + # UI progress + status = chirp_common.Status() + status.cur = 0 + val = 0 + for mx in range(0, radio._blks["nblks"]): + val += radio._blks["numpkts"][mx] + status.max = val + status.msg = "Writing %i Blocks" % val + radio.status_fn(status) + + imgadr = 0 + resp0 = command(radio.pipe, "0M PROGRAM" + TERM, 3, W8S) + radio.pipe.baudrate = 57600 + # ?? raise.error if resp0 != "0M 0d" ?? + junk = radio.pipe.read(16) + # Read magic header thingy, save it + blk0 = radio._blks["pktadr"][0] + cmc = "R" + mak_addr(blk0, 0, 0, 4) + resp0 = command(radio.pipe, cmc, 16, W8S) + mht = resp0[5:] + # LOG.warning("Magic Header Thingy: %02x %02x %02x %02x" + # % (ord(mht[0:1]), ord(mht[1:2]), ord(mht[2:3]), ord(mht[3:4]))) + for blkn in range(0, radio._blks["nblks"]): + blk0 = radio._blks["pktadr"][blkn] + for bkx in range(0, radio._blks["numpkts"][blkn]): + cmc = "W" + mak_addr(blk0, bkx, 0, 0) + if bkx == 0: + cmc += mht[3:4] + mht[1:3] + \ + radio.get_mmap()[3:imgadr + 256] + else: + cmc += radio.get_mmap()[imgadr:imgadr + 256] + resp0 = command(radio.pipe, cmc, 6, W8S) + if bkx > 0 and resp0 != ACK: + raise errors.RadioError("Packet %i Write error." + % bkx) + imgadr += 256 + upd8_stat(radio, status) # UI Update + # Re-write magic header + cmc = "W" + mak_addr(blk0, 0, 1, 3) + cmc += mht[1:] # Last 3 bytes + resp0 = command(radio.pipe, cmc, 1, W8S) + cmc = "Z" + mak_addr(blk0, 0, 0, 1) + mht[0:1] # 1st byte of mht + resp0 = command(radio.pipe, cmc, 16, W8S) + # Write E to Exit PROG mode + resp = command(radio.pipe, "E", 0, W8S) + radio.pipe.baudrate = BAUD + return + + +@directory.register +class TMD710G_CRadio(chirp_common.CloneModeRadio): + """ Kenwood TM-D710G VHF/UHF/GPS/APRS Radio """ + VENDOR = "Kenwood" + MODEL = "TM-D710G_CloneMode" + ID = "TM-D710G" + _upper = 999 # Number of chans + # Only reading first block, up to 7e for now + _blks = {"nblks": 1, # Number of addressed blocks + "pktsz": 261, # packet size + "pktadr": [0, 0x100, 0x200], # starting addr, each block + "numpkts": [0x7f, 0x0fe, 0x200]} # num packets per block + + SPECIAL_MEMORIES = {"Scan-0Lo": -32, "Scan-0Hi": -31, + "Scan-1Lo": -30, "Scan-1Hi": -29, + "Scan-2Lo": -28, "Scan-2Hi": -27, + "Scan-3Lo": -26, "Scan-3Hi": -25, + "Scan-4Lo": -24, "Scan-4Hi": -23, + "Scan-5Lo": -22, "Scan-5Hi": -21, + "Scan-6Lo": -20, "Scan-6Hi": -19, + "Scan-7Lo": -18, "Scan-7Hi": -17, + "Scan-8Lo": -16, "Scan-8Hi": -15, + "Scan-9Lo": -14, "Scan-9Hi": -13, + "Call A": -12, "Call B": -11, + "VFOA:118": -10, "VFOA:144": -9, + "VFOA:220": -8, "VFOA:300": -7, + "VFOA:430": -6, "VFOB:144": -5, + "VFOB:220": -4, "VFOB:300": -3, + "VFOB:430": -2, "VFOB:800": -1, + } + # _REV dict is used to retrieve name given number + SPECIAL_MEMORIES_REV = dict(zip(SPECIAL_MEMORIES.values(), + SPECIAL_MEMORIES.keys())) + + def get_features(self): + rf = chirp_common.RadioFeatures() + rf.can_odd_split = True + rf.has_dtcs = True + rf.has_rx_dtcs = True # Enable DTCS Rx Code column + rf.has_dtcs_polarity = False + rf.has_bank = False + rf.has_settings = True + rf.has_cross = True + rf.has_ctone = True + rf.has_mode = True + rf.has_comment = False + rf.valid_tmodes = TMD710_TONE_MODES + rf.valid_modes = TMD710_MODES + rf.valid_duplexes = TMD710_DUPLEX + rf.valid_tuning_steps = TMD710_STEPS + # Supports upper and lower case text + rf.valid_characters = TMD710_CHARS + rf.valid_name_length = 8 + rf.valid_skips = TMD710_SKIP + rf.valid_bands = TMD710_BANDS + rf.valid_dtcs_codes = TMD710_DTSC + rf.valid_cross_modes = TMD710_CROSS + rf.memory_bounds = (0, 999) + rf.valid_special_chans = sorted(self.SPECIAL_MEMORIES.keys()) + return rf + + @classmethod + def get_prompts(cls): + rp = chirp_common.RadioPrompts() + rp.info = _(dedent(""" + This version implements the 'Clone' mode using the MCP + data transfer method.. + The 2 Call and 10 VFO memory channels are displayed when + the 'Special Chans' tab is toggled. + For duplex 'Split' mode: enter the TX freq in Offset, and + the TX step in the Properties > Other tab. + Note: Window updates take a while, due to the 1000 channel + memory size, so set the Memory Range values in the menu bar + to your desired channels. + """)) + rp.pre_download = _(dedent("""\ + Follow these instructions to download the radio memory: + 1 - Connect your interface cable to the PC Port on the + back of the 'TX/RX' unit. NOT the Com Port on the head. + 2 - Radio > Download from radio: Don't adjust any settings + on the radio head! + """)) + rp.pre_upload = _(dedent("""\ + Follow these instructions to upload the radio memory: + 1 - Connect your interface cable to the PC Port on the + back of the 'TX/RX' unit. NOT the Com Port on the head. + 2 - Radio > Upload to radio: Don't adjust any settings + on the radio head! + """)) + return rp + + def sync_in(self): + """Download from radio""" + try: + _connect_radio(self) + data = _read_mem(self) + except errors.RadioError: + # Pass through any real errors we raise + resp = command(self.pipe, "E", 0, W8S) # Exit Program mode + 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.MemoryMap(data) + self.process_mmap() + return + + def sync_out(self): + """Upload to radio""" + try: + _connect_radio(self) + _write_mem(self) + 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 get_memory(self, number): + """Convert raw channel data (_mem) into UI columns (mem)""" + mem = chirp_common.Memory() + mem.extra = RadioSettingGroup("extra", "Extra") + if isinstance(number, str): + mem.name = number # Spcl chns 1st var + mem.number = self.SPECIAL_MEMORIES[number] + mem.extd_number = number # Uses name as LOC + if mem.number < -12: + _mem = self._memobj.ch_mem[mem.number + 1032] + _nam = self._memobj.ch_nam[mem.number + 1032] + _map = self._memobj.chmap[mem.number + 1032] + elif mem.number < -10: + _mem = self._memobj.call[mem.number + 12] + else: + _mem = self._memobj.vfo[mem.number + 10] + else: # Normal mem chans and VFO edges + _mem = self._memobj.ch_mem[number] + _nam = self._memobj.ch_nam[number] + _map = self._memobj.chmap[number] + mem.number = number + mnx = "" + for char in _nam.name: + if int(char) < 127: + mnx += chr(char) + mem.name = mnx + if _map.skip != 0x0ff: # empty + mem.skip = TMD710_SKIP[_map.skip] + mem.name = mem.name.strip() + if _mem.rxfreq == 0x0ffffffff or _mem.rxfreq == 0: + mem.empty = True + return mem + mem.empty = False + mem.freq = int(_mem.rxfreq) + mem.duplex = TMD710_DUPLEX[_mem.duplex] + mem.offset = int(_mem.offset) + # Duplex = 4 (split); offset contains the TX freq + mem.mode = TMD710_MODES[_mem.mode] + # _mem.tmode is 4-bit pattern, not number + mx = 0 # No tone + mem.cross_mode = TMD710_CROSS[0] + mem.rx_dtcs = TMD710_DTSC[0] + mem.dtcs = TMD710_DTSC[0] + if _mem.tmode == 8: # Tone + mx = 1 + if _mem.tmode == 4: # Tsql + mx = 2 + if _mem.tmode == 2: # Dtcs + mx = 3 + if _mem.tmode == 1: # Cross + mx = 4 + if _mem.cross == 1: # Tone->DTCS + mem.cross_mode = TMD710_CROSS[1] + mem.rx_dtcs = TMD710_DTSC[_mem.dtcs] + mem.dtcs = TMD710_DTSC[0] + if _mem.cross == 2: # DTCS->Tone + mem.cross_mode = TMD710_CROSS[2] + mem.dtcs = TMD710_DTSC[_mem.dtcs] + mem.rx_dtcs = TMD710_DTSC[0] + mem.tmode = TMD710_TONE_MODES[mx] + mem.ctone = TMD710_TONES[_mem.ctone] + mem.rtone = TMD710_TONES[_mem.rtone] + mem.tuning_step = TMD710_STEPS[_mem.tstep] + + rx = RadioSettingValueList(STEPS_STR, STEPS_STR[_mem.splitstep]) + sx = "Split TX step (KHz)" + rset = RadioSetting("splitstep", sx, rx) + mem.extra.append(rset) + + return mem + + def set_memory(self, mem): + """Convert UI column data (mem) into MEM_FORMAT memory (_mem)""" + if mem.number < 0: # Special chans + # This line is required to prevent name column being blank + mem.name = self.SPECIAL_MEMORIES_REV[mem.number] + if mem.number < -12: + _mem = self._memobj.ch_mem[mem.number + 1032] + _nam = self._memobj.ch_nam[mem.number + 1032] + _map = self._memobj.chmap[mem.number + 1032] + elif mem.number < -10: + _mem = self._memobj.call[mem.number + 12] + else: + _mem = self._memobj.vfo[mem.number + 10] + else: + _mem = self._memobj.ch_mem[mem.number] + _nam = self._memobj.ch_nam[mem.number] + _map = self._memobj.chmap[mem.number] + nx = len(mem.name) + for ix in range(8): + if ix < nx: + _nam.name[ix] = mem.name[ix].upper() + else: + _nam.name[ix] = chr(0x0ff) # needs 8 chrs + # Set _map.band for this bank. NOT Scan, VFO or Call! + _map.band = 5 + val = mem.freq + for mx in range(6): # Band codes are 0, 5, 6, 7, 8, 9 + if val >= TMD710_BANDS[mx][0] and \ + val <= TMD710_BANDS[mx][1]: + _map.band = mx + if mx > 0: + _map.band = mx + 4 + _map.skip = TMD710_SKIP.index(mem.skip) + if mem.empty: + _mem.rxfreq = 0x0ffffffff + _mem.offset = 0x0ffffff + _mem.duplex = 0x0f + _mem.tstep = 0x0ff + _mem.tmode = 0x0f + _mem.mode = 0x0ff + _mem.rtone = 0x0ff + _mem.ctone = 0x0ff + _mem.dtcs = 0x0ff + _map.skip = 0x0ff + _map.band = 0x0ff + for ix in range(8): + _nam.name[ix] = chr(0x0ff) + return + if _mem.rxfreq == 0x0ffffffff: # New Channel needs defaults + _mem.rxfreq = 144000000 + _map.band = 5 + _map.skip = 0 + _mem.mode = 0 + _mem.duplex = 0 + _mem.offset = 0 + _mem.rtone = 8 + _mem.ctone = 8 + _mem.dtcs = 0 + _mem.tstep = 0 + _mem.splitstep = 0 + # Now use the UI values entered so far + _mem.rxfreq = mem.freq + _mem.rtone = TMD710_TONES.index(mem.rtone) + _mem.ctone = TMD710_TONES.index(mem.ctone) + _mem.dtcs = TMD710_DTSC.index(mem.dtcs) + _mem.tmode = 0 # None + _mem.cross = 0 + if mem.tmode == "Tone": + _mem.tmode = 8 + if mem.tmode == "TSQL": + _mem.tmode = 4 + if mem.tmode == "DTCS": + _mem.tmode = 2 + if mem.tmode == "Cross": + _mem.tmode = 1 + mx = TMD710_CROSS.index(mem.cross_mode) + _mem.cross = 3 # t -t + if mx == 1: + _mem.cross = 1 # t-d + _mem.dtcs = TMD710_DTSC.index(mem.rx_dtcs) + if mx == 2: + _mem.cross = 2 # d-t + if mem.duplex == "n/a": # Not valid + mem.duplex = "" + _mem.duplex = TMD710_DUPLEX.index(mem.duplex) + _mem.offset = mem.offset + _mem.tstep = TMD710_STEPS.index(mem.tuning_step) + # Only 1 mem.extra entry now + for ext in mem.extra: + if ext.get_name() == "splitstep": + val = STEPS_STR.index(str(ext.value)) + setattr(_mem, "splitstep", val) + else: + setattr(_mem, ext.get_name(), ext.value) + return + + def get_settings(self): + """Translate the MEM_FORMAT structs into settings in the UI""" + # Define mem struct write-back shortcuts + _blk1 = self._memobj.block1 + _blk1a = self._memobj.block1a + _blk2 = self._memobj.block2 + _blk3 = self._memobj.block3 + _dtmc = self._memobj.dtmc + _dtmn = self._memobj.dtmn + _pvf = self._memobj.progvfo + _com = self._memobj.mcpcom + disp = RadioSettingGroup("disp", "Display") + aud = RadioSettingGroup("aud", "Audio") + aux = RadioSettingGroup("aux", "Aux") + txrx = RadioSettingGroup("txrc", "Transmit/Receive") + memz = RadioSettingGroup("memz", "Memory") + rptr = RadioSettingGroup("rptr", "Repeater") + pfk = RadioSettingGroup("pfk", "PF Keys") + dtmf = RadioSettingGroup("dtmf", "DTMF") + pvfo = RadioSettingGroup("pvfo", "Programmable VFO") + group = RadioSettings(disp, aud, aux, txrx, memz, dtmf, rptr, + pvfo, pfk) + + mhz1 = 1000000. + + # Callback functions + def _my_readonly(setting, obj, atrb): + """NOP callback, prevents writing the setting""" + vx = 0 + return + + def my_adjraw(setting, obj, atrb, fix=0, ndx=-1): + """Callback for Integer add or subtract fix from value.""" + vx = int(str(setting.value)) + value = vx + int(fix) + if value < 0: + value = 0 + if ndx < 0: + setattr(obj, atrb, value) + else: + setattr(obj[ndx], atrb, value) + return + + def my_mhz_val(setting, obj, atrb, ndx=-1): + """ Callback to set freq back to Htz""" + vx = float(str(setting.value)) + vx = int(vx * mhz1) + if ndx < 0: + setattr(obj, atrb, vx) + else: + setattr(obj[ndx], atrb, vx) + return + + def my_bool(setting, obj, atrb, ndx=-1): + """ Callback to properly set boolean """ + # set_settings is not setting [indexed] booleans??? + vx = 0 + if str(setting.value) == "True": + vx = 1 + if ndx < 0: + setattr(obj, atrb, vx) + else: + setattr(obj[ndx], atrb, vx) + return + + def mak_str(chrx): + """ Python wierdness: convert char array to string """ + # chrx is char array + stx = "" + for sx in chrx: + if int(sx) > 31 and int(sx) < 127: + stx += chr(sx) + return stx + + def pswd_vfy(setting, obj, atrb): + """ Verify password is 1-6 chars, numbers 1-5 """ + stx = str(setting.value)[0:6].strip() + sty = "" + for chx in stx: + if ord(chx) < 49 or ord(chx) > 53: + raise errors.RadioError("Bad characters in Password") + for ix in range(1, 6): # append ff + stx += chr(255) + sty = stx[0:6] + setattr(obj, atrb, sty) + return + + # ===== DISPLAY GROUP ===== + sx = mak_str(_com.comnt) + rx = RadioSettingValueString(0, 32, sx) + sx = "Comment" + rset = RadioSetting("mcpcom.comnt", sx, rx) + disp.append(rset) + + rx = RadioSettingValueString(0, 8, mak_str(_blk2.pwron)) + sx = "Power-On message" + rset = RadioSetting("block2.pwron", sx, rx) + disp.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk1.pwdon)) + sx = "Password" + rset = RadioSetting("block1.pwdon", sx, rx) + disp.append(rset) + + sx = mak_str(_blk1.pswd) + rx = RadioSettingValueString(1, 6, sx) + sx = " Password (numerals 1-5)" + rset = RadioSetting("block1.pswd", sx, rx) + rset.set_apply_callback(pswd_vfy, _blk1, "pswd") + disp.append(rset) + + rx = RadioSettingValueInteger(0, 8, _blk3.bright) + sx = "Brightness level" + rset = RadioSetting("block3.bright", sx, rx) + disp.append(rset) + + opts = ["Amber", "Green"] + rx = RadioSettingValueList(opts, opts[_blk3.bkltclr]) + sx = "Backlight color" + rset = RadioSetting("block3.bkltclr", sx, rx) + disp.append(rset) + + rx = RadioSettingValueInteger(1, 16, _blk3.bkltcont) + sx = "Contrast level" + rset = RadioSetting("block3.bkltcont", sx, rx) + disp.append(rset) + + opts = ["Positive", "Negative"] + rx = RadioSettingValueList(opts, opts[_blk3.dsprev]) + sx = "Color mode" + rset = RadioSetting("block3.dsprev", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "dsprev") + disp.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk3.autobri)) + sx = "Auto brightness" + rset = RadioSetting("block3.autobri", sx, rx) + disp.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk3.dispbar)) + sx = "Display partition bar" + rset = RadioSetting("block3.dispbar", sx, rx) + disp.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk1a.single)) + sx = "Single band display" + rset = RadioSetting("block1a.single", sx, rx) + disp.append(rset) + + # ===== AUDIO GROUP ===== + rx = RadioSettingValueBoolean(bool(_blk3.beepon)) + sx = "Beep On" + rset = RadioSetting("block3.beepon", sx, rx) + aud.append(rset) + + val = _blk3.beepvol + 1 # 1-7 downloads as 0-6 + rx = RadioSettingValueInteger(1, 7, val) + sx = "Beep volume (1 - 7)" + rset = RadioSetting("block3.beepvol", sx, rx) + rset.set_apply_callback(my_adjraw, _blk3, "beepvol", -1) + aud.append(rset) + + opts = ["Mode1", "Mode2"] + rx = RadioSettingValueList(opts, opts[_blk3.extspkr]) + sx = "External Speaker" + rset = RadioSetting("block3.extspkr", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "extspkr") + aud.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk3.pbkrpt)) + sx = "VGS Plugin: Playback repeat" + rset = RadioSetting("block3.pbkrpt", sx, rx) + aud.append(rset) + + rx = RadioSettingValueInteger(0, 60, _blk3.pbkint) + sx = " Playback repeat interval (0 - 60 secs)" + rset = RadioSetting("block3.pbkint", sx, rx) + aud.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk3.cntrec)) + sx = " Continuous recording" + rset = RadioSetting("block3.cntrec", sx, rx) + aud.append(rset) + + opts = ["Off", "Auto", "Manual"] + rx = RadioSettingValueList(opts, opts[_blk3.ance]) + sx = " Announce mode" + rset = RadioSetting("block3.ance", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "ance") + aud.append(rset) + + opts = ["English", "Japanese"] + rx = RadioSettingValueList(opts, opts[_blk3.lang]) + sx = " Announce language" + rset = RadioSetting("block3.lang", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "lang") + aud.append(rset) + + rx = RadioSettingValueInteger(1, 7, _blk3.vcvol + 1) + sx = " Voice volume (1 - 7)" + rset = RadioSetting("block3.vcvol", sx, rx) + rset.set_apply_callback(my_adjraw, _blk3, "vcvol", -1) + aud.append(rset) + + rx = RadioSettingValueInteger(0, 4, _blk3.vcspd) + sx = " Voice speed (0 - 4)" + rset = RadioSetting("block3.vcspd", sx, rx) + aud.append(rset) + + # ===== AUX GROUP ===== + opts = ["9600", "19200", "38400", "57600"] + rx = RadioSettingValueList(opts, opts[_blk1.pcbaud]) + sx = "PC port baud rate" + rset = RadioSetting("block1.pcbaud", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk1, "pcbaud") + aux.append(rset) + + opts = ["A-Band", "B-Band", "TX-A / RX-B", "RX-A / TX-B"] + rx = RadioSettingValueList(opts, opts[_blk3.intband]) + sx = "Internal TNC band" + rset = RadioSetting("block3.intband", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "intband") + aux.append(rset) + + opts = ["A-Band", "B-Band", "TX-A / RX-B", "RX-A / TX-B"] + rx = RadioSettingValueList(opts, opts[_blk3.extband]) + sx = "External TNC band" + rset = RadioSetting("block3.extband", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "extband") + aux.append(rset) + + opts = ["1200", "9600"] + rx = RadioSettingValueList(opts, opts[_blk3.extbaud]) + sx = "External TNC baud" + rset = RadioSetting("block3.extbaud", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "extbaud") + aux.append(rset) + + opts = ["Off", "BUSY", "SQL", "TX", "BUSY/TX", "SQL/TX"] + rx = RadioSettingValueList(opts, opts[_blk3.sqcsrc]) + sx = "SQC output source" + rset = RadioSetting("block3.sqcsrc", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "sqcsrc") + aux.append(rset) + + opts = ["Low", "High"] + rx = RadioSettingValueList(opts, opts[_blk1a.sqclogic]) + sx = "SQC logic" + rset = RadioSetting("block1a.sqclogic", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk1a, "sqclogic") + aux.append(rset) + + opts = ["Off", "30", "60", "90", "120", "180"] + rx = RadioSettingValueList(opts, opts[_blk3.apo]) + sx = "APO: Auto Power Off (Mins)" + rset = RadioSetting("block3.apo", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "apo") + aux.append(rset) + + opts = ["Time Operate (TO)", "Carrier Operate (CO)", "Seek"] + rx = RadioSettingValueList(opts, opts[_blk3.scnrsm]) + sx = "Scan resume mode" + rset = RadioSetting("block3.scnrsm", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "scnrsm") + aux.append(rset) + + rx = RadioSettingValueInteger(1, 10, _blk3.scntot + 1) + sx = " Scan TO delay (Secs)" + rset = RadioSetting("block3.scntot", sx, rx) + rset.set_apply_callback(my_adjraw, _blk3, "scntot", -1) + aux.append(rset) + + rx = RadioSettingValueInteger(1, 10, _blk3.scncot + 1) + sx = " Scan CO delay (Secs)" + rset = RadioSetting("block3.scncot", sx, rx) + rset.set_apply_callback(my_adjraw, _blk3, "scncot", -1) + aux.append(rset) + + opts = ["Mode 1: 1ch", "Mode 2: 61ch", "Mode 3: 91ch", + "Mode 4: 181ch"] + rx = RadioSettingValueList(opts, opts[_blk3.vsmode]) + sx = "Visual scan" + rset = RadioSetting("block3.vsmode", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "vsmode") + aux.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk1.m10mz)) + sx = "10 Mhz mode" + rset = RadioSetting("block1.m10mz", sx, rx) + aux.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk1.ansbck)) + sx = "Remote control answerback" + rset = RadioSetting("block1.ansbck", sx, rx) + aux.append(rset) + + # ===== TX / RX Group ========= + opts = ["High (50W)", "Medium (10W)", "Low (5W)"] + rx = RadioSettingValueList(opts, opts[_blk1a.a_pwr]) + sx = "A-Band transmit power" + rset = RadioSetting("block1a.a_pwr", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk1a, "a_pwr") + txrx.append(rset) + + rx = RadioSettingValueList(opts, opts[_blk1a.b_pwr]) + sx = "B-Band transmit power" + rset = RadioSetting("block1a.b_pwr", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk1a, "b_pwr") + txrx.append(rset) + + opts = ["Off", "125", "250", "500", "750", "1000"] + rx = RadioSettingValueList(opts, opts[_blk3.mutehu]) + sx = "Rx Mute hangup time (ms)" + rset = RadioSetting("block3.mutehu", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "mutehu") + txrx.append(rset) + + opts = ["Off", "125", "250", "500"] + rx = RadioSettingValueList(opts, opts[_blk3.ssqlhu]) + sx = "S-meter SQL hangup time (ms)" + rset = RadioSetting("block3.ssqlhu", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "ssqlhu") + txrx.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk3.beatshft)) + sx = "Beat shift" + rset = RadioSetting("block3.beatshft", sx, rx) + txrx.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk1a.asmsql)) + sx = "A-Band S-meter SQL" + rset = RadioSetting("block1a.asmsql", sx, rx) + txrx.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk1a.bsmsql)) + sx = "B-Band S-meter SQL" + rset = RadioSetting("block1a.bsmsql", sx, rx) + txrx.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk3.vhfaip)) + sx = "VHF band AIP" + rset = RadioSetting("block3.vhfaip", sx, rx) + txrx.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk3.uhfaip)) + sx = "UHF band AIP" + rset = RadioSetting("block3.uhfaip", sx, rx) + txrx.append(rset) + + opts = ["High", "Medium", "Low"] + rx = RadioSettingValueList(opts, opts[_blk1.micsens]) + sx = "Microphone sensitivity (gain)" + rset = RadioSetting("block1.micsens", sx, rx) + txrx.append(rset) + + opts = ["3", "5", "10"] + rx = RadioSettingValueList(opts, opts[_blk3.tot]) + sx = "Time-Out timer (Mins)" + rset = RadioSetting("block3.tot", sx, rx) + # rset.set_apply_callback(my_val_list, opts, _blk3, "tot") + txrx.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk1a.wxalerta)) + sx = "WX Alert A-band" + rset = RadioSetting("block1a.wxalerta", sx, rx) + txrx.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk1a.wxalertb)) + sx = "WX Alert B-band" + rset = RadioSetting("block1a.wxalertb", sx, rx) + txrx.append(rset) + + opts = ["Off", "15", "30", "60"] + rx = RadioSettingValueList(opts, opts[_blk3.wxscntm]) + sx = "WX alert scan memory time (Mins)" + rset = RadioSetting("block3.wxscntm", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "wxscntm") + txrx.append(rset) + + # ===== DTMF GROUP ===== + rx = RadioSettingValueBoolean(bool(_blk3.dtmfhld)) + sx = "DTMF hold" + rset = RadioSetting("block3.dtmfhld", sx, rx) + dtmf.append(rset) + + opts = ["100", "250", "500", "750", "1000", "1500", "2000"] + rx = RadioSettingValueList(opts, opts[_blk3.dtmfpau]) + sx = "DTMF pause duration (mS)" + rset = RadioSetting("block3.dtmfpau", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "dtmfpau") + dtmf.append(rset) + + opts = ["Fast", "Slow"] + rx = RadioSettingValueList(opts, opts[_blk3.dtmfspd]) + sx = "DTMF speed" + rset = RadioSetting("block3.dtmfspd", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "dtmfspd") + dtmf.append(rset) + + for mx in range(0, 10): + csx = mak_str(_dtmn[mx].id) + nx = len(csx) + for ix in range(nx, 8): # pad + csx += " " + rx = RadioSettingValueString(0, 8, csx) + sx = "DTMF %i Name (8 chars)" % mx + rset = RadioSetting("dtmn.id/%d" % mx, sx, rx) + dtmf.append(rset) + + csx = mak_str(_dtmc[mx].code) + nx = len(csx) + for ix in range(nx, 16): # pad to 16 with spaces + csx += " " + rx = RadioSettingValueString(0, 16, csx) + sx = " Code %i (16 chars)" % mx + rset = RadioSetting("dtmc.code/%d" % mx, sx, rx) + dtmf.append(rset) + + # ===== MEMORY GROUP ===== + opts = ["All Bands", "Current Band"] + rx = RadioSettingValueList(opts, opts[_blk3.recall]) + sx = "Memory recall method" + rset = RadioSetting("block3.recall", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "recall") + memz.append(rset) + + rx = RadioSettingValueString(0, 10, mak_str(_blk2.memgrplk)) + sx = "Group link" + rset = RadioSetting("block2.memgrplk", sx, rx) + memz.append(rset) + + opts = ["Fast", "Slow"] + rx = RadioSettingValueList(opts, opts[_blk3.eclnkspd]) + sx = "Echolink speed" + rset = RadioSetting("block3.eclnkspd", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "eclnkspd") + memz.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk1.dspmemch)) + sx = "Display memory channel number" + rset = RadioSetting("block1.dspmemch", sx, rx) + memz.append(rset) + + # ===== REPEATER GROUP ===== + rx = RadioSettingValueBoolean(bool(_blk3.rptr1750)) + sx = "1750 Hz transmit hold" + rset = RadioSetting("block3.rptr1750", sx, rx) + rptr.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk3.rptrofst)) + sx = "Auto repeater offset" + rset = RadioSetting("block3.rptrofst", sx, rx) + rptr.append(rset) + + opts = ["Cross Band", "TX:A-Band / RX:B-Band", "RX:A-Band / TX:B-Band"] + rx = RadioSettingValueList(opts, opts[_blk1.rptrmode]) + sx = "Repeater Mode" + rset = RadioSetting("block1.rptrmode", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk1, "rptrmode") + rptr.append(rset) + + opts = ["Off", "Morse", "Voice"] + rx = RadioSettingValueList(opts, opts[_blk1.rptridx]) + sx = "Repeater ID transmit" + rset = RadioSetting("block1.rptridx", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk1, "rptridx") + rptr.append(rset) + + rx = RadioSettingValueString(0, 12, mak_str(_blk1a.rptrid)) + sx = "Repeater ID" + rset = RadioSetting("block1a.rptrid", sx, rx) + rptr.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk1.rptrhold)) + sx = "Repeater transmit hold" + rset = RadioSetting("block1.rptrhold", sx, rx) + rptr.append(rset) + + # ===== Prog VFO Group ============= + for mx in range(0, 10): + val = _pvf[mx].blow / mhz1 + if val == 0: + val = 118 + rx = RadioSettingValueFloat(118.0, 1299.9, val, 0.005, 3) + sx = "VFO-%i Low Limit (MHz)" % mx + rset = RadioSetting("progvfo.blow/%d" % mx, sx, rx) + rset.set_apply_callback(my_mhz_val, _pvf, "blow", mx) + pvfo.append(rset) + + val = _pvf[mx].bhigh / mhz1 + if val == 0: + val = 118 + rx = RadioSettingValueFloat(118.0, 1300.0, val, 0.005, 3) + sx = " VFO-%i High Limit (MHz)" % mx + rset = RadioSetting("progvfo.bhigh/%d" % mx, sx, rx) + rset.set_apply_callback(my_mhz_val, _pvf, "bhigh", mx) + pvfo.append(rset) + + # ===== PFK GROUP ===== + opts = ["WX CH", "FRQ.BAND", "CTRL", "MONITOR", "VGS", "VOICE", + "GROUP UP", "MENU", "MUTE", "SHIFT", "DUAL", "M>V", + "1750 Tone"] + rx = RadioSettingValueList(opts, opts[_blk3.pf1key]) + sx = "Front panel PF1 key" + rset = RadioSetting("block3.pf1key", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "pf1key") + pfk.append(rset) + + rx = RadioSettingValueList(opts, opts[_blk3.pf2key]) + sx = "Front panel PF2 key" + rset = RadioSetting("block3.pf2key", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "pf2key") + pfk.append(rset) + + opts = ["WX CH", "FRQ.BAND", "CTRL", "MONITOR", "VGS", "VOICE", + "GROUP UP", "MENU", "MUTE", "SHIFT", "DUAL", "M>V", + "VFO", "MR", "CALL", "MHz", "TONE", "REV", "LOW", + "LOCK", "A/B", "ENTER", "1750 Tone", "M.LIST", + "S.LIST", "MSG.NEW", "REPLY", "POS", "P.MONI", + "BEACON", "DX", "WX"] + rx = RadioSettingValueList(opts, opts[_blk3.micpf1]) + sx = "Microphone PF1 key" + rset = RadioSetting("block3.micpf1", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "micpf1") + pfk.append(rset) + + rx = RadioSettingValueList(opts, opts[_blk3.micpf2]) + sx = "Microphone PF2 key" + rset = RadioSetting("block3.micpf2", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "micpf2") + pfk.append(rset) + + rx = RadioSettingValueList(opts, opts[_blk3.micpf3]) + sx = "Microphone PF3 key" + rset = RadioSetting("block3.micpf3", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "micpf3") + pfk.append(rset) + + rx = RadioSettingValueList(opts, opts[_blk3.micpf4]) + sx = "Microphone PF4 key" + rset = RadioSetting("block3.micpf4", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk3, "micpf4") + pfk.append(rset) + + return group # END get_settings() + + def set_settings(self, settings): + """ Convert UI modified changes into mem_format values """ + blks = (self._memobj.block1, self._memobj.block1a, + self._memobj.block2, self._memobj.block3) + for _settings in blks: + 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 + return + + @classmethod + def match_model(cls, fdata, fyle): + """ Included to prevent 'File > New' error """ + # Test the file data size + if len(fdata) == MEMSIZE: + return True + else: + return False diff -r 51c55c47f12a -r 598027f8a1a0 tools/cpep8.manifest --- a/tools/cpep8.manifest Sat Dec 14 14:53:17 2019 -0800 +++ b/tools/cpep8.manifest Sun Dec 15 06:00:32 2019 -0800 @@ -82,6 +82,7 @@ ./chirp/drivers/thuv1f.py ./chirp/drivers/tk8102.py ./chirp/drivers/tk8180.py +./chirp/drivers/tmd710g.py ./chirp/drivers/tmv71.py ./chirp/drivers/tmv71_ll.py ./chirp/drivers/ts480.py