# HG changeset patch # User Rick DeWitt # Date 1578941905 28800 # Mon Jan 13 10:58:25 2020 -0800 # Node ID a8473f138daa40953e0a3e070c0de2445c3fcaae # Parent 53cd045bbf8253d32b43a528b245caff243e4c02 [tmd710g] New Clone-Mode driver for Kenwood TM-D710GA/GE. Issue #7467 Implements true clone mode using PROG MCP binary image. diff -r 53cd045bbf82 -r a8473f138daa chirp/drivers/tmd710g.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chirp/drivers/tmd710g.py Mon Jan 13 10:58:25 2020 -0800 @@ -0,0 +1,1554 @@ +# Copyright 2011 Dan Smith +# -- 2020 Rick DeWitt +# -- Implementing Kenwood TM-D710G as MCP Clone Mode +# -- With Py3 compatibility updates +# 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 +import sys # For py3 version detect +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__) + +svers = sys.version_info[0] # saved for debugging +HAS_FUTURE = True +if svers < 3: # Don't import bytes if vers >= 3 + try: # PY3 compliance + from builtins import bytes + except ImportError: + HAS_FUTURE = False +if not HAS_FUTURE: # Also disables registration later + LOG.warning('python-future package is not ' + 'available; %s requires it' % __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 a_mr; + u8 unk0202; + u8 unk0203; + u8 unk0204; + u8 unk0205; + u8 unk0206; + u8 a_pwr; + u8 wxalerta; + u8 asmsql; + u8 a_chn; + u8 unk020b; + u8 unk020c; + u8 b_mr; + u8 unk020e; + u8 unk020f; + u8 unk0210; + u8 unk0211; + u8 unk0212; + u8 b_pwr; + u8 wxalertb; + u8 bsmsql; + u8 b_chn; + 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 unk0382; + 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 abnd118; // 0x0390 + u8 abnd144; + u8 abnd220; + u8 abnd300; + u8 abnd430; + u8 bbnd144; + u8 bbnd220; + u8 bbnd300; + u8 bbnd430; + u8 bbnd800; // 0x0399 +} block3; + +struct { + u8 unk039a[230]; +} block4; + +#seekto 0x0e00; +struct { + u8 band; + u8 skip; +} chmap[1020]; + +#seekto 0x01700; +struct chns ch_mem[1020]; + +#seekto 0x058a0; +struct chns call[2]; + +#seekto 0x05900; +struct { + char name[8]; +} ch_nam[1020]; // ends @ 0x07840 + +#seekto 0x07df0; +struct { + char comnt[32]; +} mcpcom; + // Block 1 ends @ 0x07eff + // Block 2 starts @ 0x07f00 +#seekto 0x08660; +struct { + char cmdr[10]; + char tptr[10]; + u8 skytone; // 0x08674 +} skycmd; + +#seekto 0x10ef0; +struct { + u8 bmp[1896]; + u8 unk11658[8]; // 0x11658 + char bmpfyl[64]; // 0x11660 + u8 unk116a0[95]; + u8 bmpon; // 0x116ff +} bitmap; + + // 2nd block ends @ 0x017cff +""" + +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.001 # 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 term + + +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) + radio.pipe.write(TERM) + radio.pipe.read(25) + resp = command(radio.pipe, bytes("ID" + TERM), 24, W8S) + if resp.find("?") >= 0: # repeat it + resp = command(radio.pipe, bytes("ID" + TERM), 24, W8S) + resp = resp[:-1] # strip term + LOG.debug("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 update_status(self, status, step=1): + """ Increment status bar """ + status.cur += step + self.status_fn(status) + return + + +def make_address(upperbyte, lowerbyte, uppercount, lowercount): + """ Create 4-char TMD710G hex address + count string """ + # upperbyte lowerbyte uppercount lowercount + blockn = upperbyte + lowerbyte + addr = chr((blockn & 0xFF00) >> 8) + chr(blockn & 0xFF) + addr += chr((uppercount & 0xFF)) + chr(lowercount & 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._num_blocks): + val += radio._num_packets[mx] + status.max = val + status.msg = "Reading %i packets" % val + radio.status_fn(status) + + bdata = bytes() + cmc = bytes("0M PROGRAM" + TERM) + resp0 = command(radio.pipe, cmc, 3, W8S) + radio.pipe.baudrate = 57600 # PROG mode is always 57.6 + junk = radio.pipe.read(1) # trailing byte + for blkn in range(0, radio._num_blocks): + for bkx in range(0, radio._num_packets[blkn]): + cmc = bytes("R" + make_address(radio._block_addr[blkn], + bkx, 0, 0)) + resp0 = command(radio.pipe, cmc, + radio._packet_size, W8S) + if len(resp0) < radio._packet_size: + junk = command(radio.pipe, bytes("E"), 0, W8S) + sx = "Block %i%i read error, %i bytes." % \ + (blkn, bkx, len(resp0)) + raise errors.RadioError(sx) + return "" + if blkn == 0 and bkx == 0: # 1st packet of 1st block + mht = resp0[5:9] # Magic Header Thingy after cmd echo + bdata += bytes(mht[0:1]) + bdata += bytes(chr(255) + chr(255) + chr(255)) + bdata += bytes(resp0[9:]) + else: + bdata += bytes(resp0[5:]) # skip cmd echo + update_status(radio, status) # UI Update + # Exit Prog mode, no TERM + resp = command(radio.pipe, bytes("E"), 0, W8S) + radio.pipe.baudrate = BAUD + + return bdata + + +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._num_blocks): + val += radio._num_packets[mx] + status.max = val + status.msg = "Writing %i packets" % val + radio.status_fn(status) + + imgadr = 0 + resp0 = command(radio.pipe, bytes("0M PROGRAM" + TERM), 3, W8S) + radio.pipe.baudrate = 57600 + junk = radio.pipe.read(1) + # Read block 0 magic header thingy, save it + cmc = bytes("R" + make_address(radio._block_addr[0], 0, 0, 4)) + resp0 = command(radio.pipe, cmc, 16, W8S) + mht0 = resp0[5:] + # Now get block 1 mht + cmc = bytes("R" + make_address(radio._block_addr[1], 0, 0, 5)) + resp0 = command(radio.pipe, cmc, 16, W8S) + mht1 = resp0[5:] + for blkn in range(0, radio._num_blocks): + for bkx in range(0, radio._num_packets[blkn]): + cmc = bytes("W" + make_address(radio._block_addr[blkn], + bkx, 0, 0)) + if bkx == 0: # First packt of the block includes mht + if blkn == 0: + cmc += bytes(chr(255) + chr(0x04b) + chr(1) + + chr(0x032) + + radio.get_mmap()[4:imgadr + 256]) + elif blkn == 1: + cmc += bytes(mht1 + radio.get_mmap() + [imgadr + 5:imgadr + 256]) + else: # after first packet + cmc += bytes(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 + update_status(radio, status) # UI Update + # Re-write magic headers + cmc = bytes("W" + make_address(radio._block_addr[0], 0, 1, 3)) + cmc += bytes(mht0[1:3] + chr(0x032)) + resp0 = command(radio.pipe, cmc, 1, W8S) + cmc = bytes("W" + make_address(radio._block_addr[1], 0, 0, 5)) + cmc += bytes(mht1) + resp0 = command(radio.pipe, cmc, 1, W8S) + cmc = bytes("Z" + make_address(radio._block_addr[0], 0, 0, 1) + + mht0[0:1]) + resp0 = command(radio.pipe, cmc, 16, W8S) + # Write E to Exit PROG mode + resp = command(radio.pipe, bytes("E"), 0, W8S) + radio.pipe.baudrate = BAUD + return + + +# Use a base class to allow for HAS_FUTURE registration at EOF +class KenwoodTMx710Radio(chirp_common.CloneModeRadio): + """ Base class for TMD-710G """ + VENDOR = "Kenwood" + MODEL = "TM-x710" + ID = "TM-D710G" + _upper = 999 # Number of normal chans + + _num_blocks = 2 + _packet_size = 261 + _block_addr = [0, 0x100, 0x200] # starting addr, each block + _num_packets = [0x7f, 0x0fe, 0x200] # num packets per block + + # Put Special memory channels after normal ones + SPECIAL_MEMORIES = {"Scan-0Lo": 1000, "Scan-0Hi": 1001, + "Scan-1Lo": 1002, "Scan-1Hi": 1003, + "Scan-2Lo": 1004, "Scan-2Hi": 1005, + "Scan-3Lo": 1006, "Scan-3Hi": 1007, + "Scan-4Lo": 1008, "Scan-4Hi": 1009, + "Scan-5Lo": 1010, "Scan-5Hi": 1011, + "Scan-6Lo": 1012, "Scan-6Hi": 1013, + "Scan-7Lo": 1014, "Scan-7Hi": 1015, + "Scan-8Lo": 1016, "Scan-8Hi": 1017, + "Scan-9Lo": 1018, "Scan-9Hi": 1019, + "Call 144Mhz": 1020, "Call 430Mhz": 1021, + "VFOA:118": 1022, "VFOA:144": 1023, + "VFOA:220": 1024, "VFOA:300": 1025, + "VFOA:430": 1026, "VFOB:144": 1027, + "VFOB:220": 1028, "VFOB:300": 1029, + "VFOB:430": 1030, "VFOB:800": 1031 + } + # _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("""\ + When using the internal TNC: Connect the interface + cable to the COM port on the radio head. Note that the + TNC port can have a different baud rate than the PC port + (menu 919: 1200 / 9600). + """)) + rp.pre_download = _(dedent("""\ + Connect your interface cable to the PC port on the + back of the 'TX/RX' unit. NOT the Com port on the head. + """)) + rp.pre_upload = _(dedent("""\ + Connect your interface cable to the PC port on the + back of the 'TX/RX' unit. NOT the Com port on the head. + """)) + return rp + + def sync_in(self): + """Download from radio""" + try: + _connect_radio(self) + bdata = _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.MemoryMapBytes(bdata) + self.process_mmap() + + 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') + + def process_mmap(self): + """Process the mem map into the mem object""" + self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) + + def get_memory(self, number): + """Convert raw channel data (_mem) into UI columns (mem)""" + mem = chirp_common.Memory() + mem.extra = RadioSettingGroup("extra", "Extra") + # If called from 'Properties', spcl chans number is integer + propflg = False + if isinstance(number, int): + if number > 999: + propflg = True + if isinstance(number, str) or propflg: + if propflg: + mem.number = number + mem.name = self.SPECIAL_MEMORIES_REV[number] + mem.extd_number = mem.name + else: + mem.name = number # Spcl chns 1st var + mem.number = self.SPECIAL_MEMORIES[number] + mem.extd_number = number # Uses name as LOC + mem.immutable = ["name", "skip"] + if mem.number < 1020: # Scan edges + _mem = self._memobj.ch_mem[mem.number] + _nam = self._memobj.ch_nam[mem.number] + _map = self._memobj.chmap[mem.number] + elif mem.number < 1022: # Call chans + _mem = self._memobj.call[mem.number - 1020] + else: # VFO bands + _mem = self._memobj.vfo[mem.number - 1022] + 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 > 999: # Special chans + if mem.number < 1020: + _mem = self._memobj.ch_mem[mem.number] + _nam = self._memobj.ch_nam[mem.number] + _map = self._memobj.chmap[mem.number] + elif mem.number < 1022: + _mem = self._memobj.call[mem.number - 1020] + else: + _mem = self._memobj.vfo[mem.number - 1022] + 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 + _bmp = self._memobj.bitmap + _skyc = self._memobj.skycmd + 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") + bmsk = RadioSettingGroup("bmsk", "Band Masks") + skyk = RadioSettingGroup("skyk", "Sky Command") + group = RadioSettings(disp, aud, aux, txrx, memz, dtmf, rptr, + pvfo, pfk, bmsk, skyk) + + mhz1 = 1000000. # Raw freq is stored with 0.1 Htz resolution + + def _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 _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 _char_to_str(chrx): + """ Remove ff pads from char array """ + # chrx is char array + str1 = "" + for sx in chrx: + if int(sx) > 31 and int(sx) < 127: + str1 += chr(sx) + return str1 + + def _pswd_vfy(setting, obj, atrb): + """ Verify password is 1-6 chars, numbers 1-5 """ + str1 = str(setting.value).strip() # initial string + str2 = filter(lambda c: c in '12345', str1) # valid chars + if str1 != str2: + # Two lines due to python 73 char limit + sx = "Bad characters in Password" + raise errors.RadioError(sx) + str2 = str1.ljust(6, chr(255)) # pad to 6 with ff's + setattr(obj, atrb, str2) + return + + def _pad_str(setting, lenstr, padchr, obj, atrb, ndx=-1): + """ pad string to lenstr with padchr """ + str1 = str(setting.value).strip() # initial string + str2 = str1.ljust(lenstr, padchr) + if ndx < 0: + setattr(obj, atrb, str2) + else: + setattr(obj[ndx], atrb, str2) + return + + # ===== DISPLAY GROUP ===== + sx = _char_to_str(_com.comnt) + rx = RadioSettingValueString(0, 32, sx) + sx = "Comment" + rset = RadioSetting("mcpcom.comnt", sx, rx) + disp.append(rset) + + rx = RadioSettingValueString(0, 8, _char_to_str(_blk2.pwron)) + sx = "Power-On message" + rset = RadioSetting("block2.pwron", sx, rx) + disp.append(rset) + + rx = RadioSettingValueBoolean(bool(_bmp.bmpon)) + sx = "Custom display bitmap" + rset = RadioSetting("bitmap.bmpon", sx, rx) + disp.append(rset) + + rx = RadioSettingValueString(0, 64, _char_to_str(_bmp.bmpfyl)) + rx.set_mutable(False) + sx = " Custom bitmap filename" + rset = RadioSetting("bitmap.bmpfyl", sx, rx) + rset.set_doc("Read-only: To modify, use MCP-6 s/w") + disp.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk1.pwdon)) + sx = "Password" + rset = RadioSetting("block1.pwdon", sx, rx) + disp.append(rset) + + sx = _char_to_str(_blk1.pswd).strip() + rx = RadioSettingValueString(1, 6, sx) + # rx.set_charset("12345") # Keeps finding `' + sx = " Password (numerals 1-5)" + rset = RadioSetting("block1.pswd", sx, rx) + rset.set_apply_callback(_pswd_vfy, _blk1, "pswd") + disp.append(rset) + + opts = ["VFO", "Mem Recall"] + rx = RadioSettingValueList(opts, opts[_blk1a.a_mr]) + sx = "A: Left Side VFO/MR" + rset = RadioSetting("block1a.a_mr", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk1a, "a_mr") + disp.append(rset) + + rx = RadioSettingValueInteger(0, 999, _blk1a.a_chn) + sx = "A: Left Side MR Channel" + rset = RadioSetting("block1a.a_chn", sx, rx) + disp.append(rset) + + rx = RadioSettingValueList(opts, opts[_blk1a.b_mr]) + sx = "B: Right Side VFO/MR" + rset = RadioSetting("block1a.b_mr", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk1a, "b_mr") + disp.append(rset) + + rx = RadioSettingValueInteger(0, 999, _blk1a.b_chn) + sx = "B: Right Side MR Channel" + rset = RadioSetting("block1a.b_chn", sx, rx) + 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) + rset.set_apply_callback(my_val_list, opts, _blk3, "bkltclr") + 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(_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(_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(_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(_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 = ["A: Left", "B: Right"] + rx = RadioSettingValueList(opts, opts[_blk1a.txband]) + sx = "TX Side (PTT)" + rset = RadioSetting("block1a.txband", sx, rx) + rset.set_apply_callback(my_val_list, opts, _blk1a, "txband") + txrx.append(rset) + + 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 = _char_to_str(_dtmn[mx].id).strip() + rx = RadioSettingValueString(0, 8, csx) + sx = "DTMF %i Name (8 chars)" % mx + rset = RadioSetting("dtmn.id/%d" % mx, sx, rx) + rset.set_apply_callback(_pad_str, 8, chr(255), _dtmn, "id", mx) + dtmf.append(rset) + + csx = _char_to_str(_dtmc[mx].code).strip() + rx = RadioSettingValueString(0, 16, csx) + sx = " Code %i (16 chars)" % mx + rset = RadioSetting("dtmc.code/%d" % mx, sx, rx) + rset.set_apply_callback(_pad_str, 16, chr(255), _dtmc, "code", mx) + 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, _char_to_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, _char_to_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): + # Raw freq is 0.1 Mhz resolution + vfx = int(_pvf[mx].blow) / mhz1 + if vfx == 0: + vfx = 118 + rx = RadioSettingValueFloat(118.0, 1299.9, vfx, 0.005, 3) + sx = "VFO-%i Low Limit (MHz)" % mx + rset = RadioSetting("progvfo.blow/%d" % mx, sx, rx) + rset.set_apply_callback(_mhz_val, _pvf, "blow", mx) + pvfo.append(rset) + + vfx = int(_pvf[mx].bhigh) / mhz1 + if vfx == 0: + vfx = 118 + rx = RadioSettingValueFloat(118.0, 1300.0, vfx, 0.005, 3) + sx = " VFO-%i High Limit (MHz)" % mx + rset = RadioSetting("progvfo.bhigh/%d" % mx, sx, rx) + rset.set_apply_callback(_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) + + # ===== BMSK GROUP ===== + rx = RadioSettingValueBoolean(bool(_blk3.abnd118)) + sx = "A/Left: 118Mhz Band" + rset = RadioSetting("block3.abnd118", sx, rx) + bmsk.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk3.abnd144)) + sx = "A/Left: 144Mhz Band" + rset = RadioSetting("block3.abnd144", sx, rx) + bmsk.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk3.abnd220)) + sx = "A/Left: 220Mhz Band" + rset = RadioSetting("block3.abnd220", sx, rx) + bmsk.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk3.abnd300)) + sx = "A/Left: 300Mhz Band" + rset = RadioSetting("block3.abnd300", sx, rx) + bmsk.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk3.abnd430)) + sx = "A/Left: 430Mhz Band" + rset = RadioSetting("block3.abnd430", sx, rx) + bmsk.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk3.bbnd144)) + sx = "B/Right: 144Mhz Band" + rset = RadioSetting("block3.bbnd144", sx, rx) + bmsk.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk3.bbnd220)) + sx = "B/Right: 220Mhz Band" + rset = RadioSetting("block3.bbnd220", sx, rx) + bmsk.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk3.bbnd300)) + sx = "B/Right: 300Mhz Band" + rset = RadioSetting("block3.bbnd300", sx, rx) + bmsk.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk3.bbnd430)) + sx = "B/Right: 430Mhz Band" + rset = RadioSetting("block3.bbnd430", sx, rx) + bmsk.append(rset) + + rx = RadioSettingValueBoolean(bool(_blk3.bbnd800)) + sx = "B/Right: 800Mhz Band" + rset = RadioSetting("block3.bbnd800", sx, rx) + bmsk.append(rset) + + # ===== Sky command Group ============= + rx = RadioSettingValueString(0, 10, _char_to_str(_skyc.cmdr)) + sx = "Commander call sign" + rset = RadioSetting("skycmd.cmdr", sx, rx) + rset.set_apply_callback(_pad_str, 10, chr(0), _skyc, "cmdr") + skyk.append(rset) + + rx = RadioSettingValueString(0, 10, _char_to_str(_skyc.tptr)) + sx = "Transporter call sign" + rset = RadioSetting("skycmd.tptr", sx, rx) + rset.set_apply_callback(_pad_str, 10, chr(0), _skyc, "tptr") + skyk.append(rset) + + opts = [] + for val in TMD710_TONES: + opts.append(str(val)) + rx = RadioSettingValueList(opts, opts[_skyc.skytone]) + sx = "Tone frequency" + rset = RadioSetting("skycmd.skytone", sx, rx) + rset.set_apply_callback(my_val_list, opts, _skyc, "skytone") + skyk.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 as e: + LOG.debug(element.get_name()) + raise + return + + @classmethod + def match_model(cls, fdata, fyle): + """ Included to prevent 'File > New' error """ + return False + + +if HAS_FUTURE: # Only register driver if environment is PY3 compliant + @directory.register + class KenwoodTMx710Radio(KenwoodTMx710Radio): + """ Kenwood TM-D710G VHF/UHF/GPS/APRS Radio """ + MODEL = "TM-D710G_CloneMode" diff -r 53cd045bbf82 -r a8473f138daa tools/cpep8.manifest --- a/tools/cpep8.manifest Fri Jan 03 17:37:19 2020 -0800 +++ b/tools/cpep8.manifest Mon Jan 13 10:58:25 2020 -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