[chirp_devel] New driver for Kenwood TM-D710G in true clone mode

Rick DeWitt AA0RD
Sun Dec 15 06:10:12 PST 2019


New true clone-mode driver for Kenwood TMD-710G. Uses PROG MCP mode for 
binary image download/upload.

-- 
Rick DeWitt
AA0RD
Sequim, Washington, USA 98382
(360) 681-3494


-------------- next part --------------
# HG changeset patch
# User Rick DeWitt <aa0rd at yahoo.com>
# 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 <dsmith at danplanet.com>
+# --        2019 Rick DeWitt <aa0rd at yahoo.com>
+# -- 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 <http://www.gnu.org/licenses/>.
+
+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
+
+
+ at 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


More information about the chirp_devel mailing list