[chirp_devel] Kenwood TM-D710 Again

Rick DeWitt AA0RD
Tue Apr 28 11:18:31 PDT 2020


Here is the Kenwood TM-D710/G Clone Mode driver again.
The img file is an exact duplicate of what the MCP program actually 
downloads
The img and mc2/mc6 files are identical minus the MCP preamble and some 
dummy blocks.
The upload is an exact duplicate of the MCP USB blocks.
The download and upload processes have been tested on both a D710G and 
the non-GPS D710 version,
The driver does NOT bother with the GPS or APRS data blocks, just like 
the MCP s/w.
I have written a Windows app that translates mc2 <> img and mc6 <> img 
files, for whatever that is worth.

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

-------------- next part --------------
# Copyright 2011 Dan Smith <dsmith at danplanet.com>
# --        2019 Rick DeWitt <aa0rd at yahoo.com>
# -- Implementing Kenwood TM-D710G as MCP Clone Mode for Python 2.7
# -- Thanks to Herm Halbach, W7HRM, for the 710 model testing.
# 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
from chirp.drivers import kenwood_live

LOG = logging.getLogger(__name__)

HAS_FUTURE = True
try:                         # PY3 compliance
    from builtins import bytes
except ImportError:
    HAS_FUTURE = False
    LOG.warning('python-future package is not '
                'available; %s requires it' % __name__)

BAUD = 0
STIMEOUT = 0.2
TERM = chr(13)         # Cmd write terminator (CR)
ACK = chr(6)           # Data write acknowledge char
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_ASCII
TMD710_CHARS += chr(34)     # "


def _command(ser, cmd, rsplen, w8t=0.01):
    """Send cmd to radio via ser port
    cmd is output string with possible terminator
    rsplen is expected response char count, NOT incl prefix and term
    If rsplen = 0 then do not0 read after write """
    ser.write(cmd)
    LOG.debug(" Out ->: " + util.hexprint(cmd[0: 32].decode("latin-1")))
    time.sleep(w8t)
    result = ""
    if rsplen > 0:  # read response
        result = ser.read(rsplen)
        LOG.debug(" In <-: " + util.hexprint(result[0: 32]))
    return result


def _connect_radio(radio):
    """Determine baud rate and verify radio on-line"""
    global BAUD
    xid = "D710" + radio.SHORT
    resp = kenwood_live.get_id(radio.pipe)
    resp = resp.decode("latin-1")
    BAUD = radio.pipe.baudrate      # As detected by kenwood_live
    LOG.debug("Got [%s] at %i Baud." % (resp, BAUD))
    resp = resp[3:]     # Strip "ID " prefix
    if len(resp) > 2:   # Got something from "ID"
        if resp == xid:     # Good comms
            return
        else:
            stx = "Radio responded as %s, not %s." % (resp, xid)
            raise errors.RadioError(stx)
    raise errors.RadioError("No response from radio")


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 _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


class KenwoodTMx710Radio(chirp_common.CloneModeRadio):
    """ Base class for TMD-710 """
    VENDOR = "Kenwood"
    MODEL = "TM-x710"
    SHORT = "X"       # Short model ID code

    _upper = 999         # Number of normal chans

    # 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,
                        "WX-1": 1020, "WX-2": 1021,
                        "WX-3": 1022, "WX-4": 1023,
                        "WX-5": 1024, "WX-6": 1025,
                        "WX-7": 1026, "WX-8": 1027,
                        "WX-9": 1028, "WX-10": 1029,
                        "Call C0": 1030, "Call C1": 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_dtcs_polarity = False
        if self.SHORT == "G":             # NOT for D710
            rf.has_rx_dtcs = True       # Enable DTCS Rx Code column
            rf.has_cross = True
            rf.valid_cross_modes = TMD710_CROSS
        rf.has_bank = False
        rf.has_settings = 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
        rf.valid_dtcs_codes = TMD710_DTSC
        # 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.memory_bounds = (0, 1029)        # including special chans 1000-1029
        rf.valid_special_chans = sorted(self.SPECIAL_MEMORIES.keys())
        return rf

    @classmethod
    def get_prompts(cls):
        rp = chirp_common.RadioPrompts()
        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)
            data = bytes(self._read_mem())
        except errors.RadioError:
            # Pass through any real errors we raise
            raise
        except Exception:
            # If anything unexpected happens, make sure we raise
            # a RadioError and log the problem
            LOG.exception('Unexpected error during download')
            raise errors.RadioError('Unexpected error communicating '
                                    'with the radio')
        self._mmap = memmap.MemoryMapBytes(data)
        self.process_mmap()

    def sync_out(self):
        """Upload to radio"""
        try:
            _connect_radio(self)
            self._write_mem()
        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(self.MEM_FORMAT, self._mmap)

    def get_memory(self, number):
        """Convert raw channel data (_mem) into UI columns (mem)"""
        mem = chirp_common.Memory()
        if self.SHORT == "G":
            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"]
            if mem.number < 1030:       # Scan edges, WX
                _mem = self._memobj.ch_mem[mem.number]
                _map = self._memobj.chmap[mem.number]
            else:                       # Call chans
                _mem = self._memobj.call[mem.number - 1030]
        else:       # Normal mem chans
            _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 _mem.rxfreq == 0x0ffffffff or _mem.rxfreq == 0:
            mem.empty = True
            return mem
        mem.empty = False
        if mem.number < 1030 and _map.skip != 0x0ff:      # empty
            mem.skip = TMD710_SKIP[_map.skip]
        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[_mem.dtcs]
        mem.dtcs = TMD710_DTSC[_mem.dtcs]
        if self.SHORT == "G":
            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]
                if _mem.cross == 2:     # DTCS->Tone
                    mem.cross_mode = TMD710_CROSS[2]
        else:           # D710; may have bit 8 set
            if _mem.tmode & 4:     # Tone
                mx = 1
            if _mem.tmode & 2:     # Tsql
                mx = 2
            if _mem.tmode & 1:     # Dtcs
                mx = 3
                mem.dtcs = TMD710_DTSC[_mem.dtcs]
        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]

        if self.SHORT == "G":         # Only the 710G
            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 < 1030:        # Scan, Wx
                _mem = self._memobj.ch_mem[mem.number]
                _map = self._memobj.chmap[mem.number]
            else:                        # Call chans
                _mem = self._memobj.call[mem.number - 1030]
        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]
                else:
                    _nam.name[ix] = chr(0x0ff)    # needs 8 chrs
        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.mode = TMD710_MODES.index(mem.mode)
        try:
            _tone = mem.rtone
            _mem.rtone = TMD710_TONES.index(mem.rtone)
            _tone = mem.ctone
            _mem.ctone = TMD710_TONES.index(mem.ctone)
        except ValueError:
            raise errors.UnsupportedToneError("This radio does not support " +
                                              "tone %.1fHz" % _tone)
        _mem.dtcs = TMD710_DTSC.index(mem.dtcs)
        _mem.tmode = 0      # None
        _mem.cross = 0
        if self.SHORT == "G":
            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
                    _mem.dtcs = TMD710_DTSC.index(mem.dtcs)
        else:
            _mem.tmode = 0x80       # None
            if mem.tmode == "Tone":
                _mem.tmode = 0x0c
            if mem.tmode == "TSQL":
                _mem.tmode = 0x0a
            if mem.tmode == "DTCS":
                _mem.tmode = 0x09
        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)
        # Set _map.band for this bank. Not Calls!
        if mem.number < 1030:
            _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)
        # 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
        if self.SHORT == "G":
            _bmp = self._memobj.bitmap
        _blk1 = self._memobj.block1
        _blk1a = self._memobj.block1a
        _pmg = self._memobj.pmg     # array[6] of settings
        _dtmc = self._memobj.dtmc
        _dtmn = self._memobj.dtmn
        _com = self._memobj.mcpcom
        _skyc = self._memobj.skycmd
        basic = RadioSettingGroup("basic", "Basic")
        disp = RadioSettingGroup("disp", "PM0: Display")    # PM[0] settings
        aud = RadioSettingGroup("aud", "PM0: Audio")
        aux = RadioSettingGroup("aux", "PM0: Aux")
        txrx = RadioSettingGroup("txrc", "PM0: Transmit/Receive")
        memz = RadioSettingGroup("memz", "PM0: Memory")
        pfk = RadioSettingGroup("pfk", "PM0: PF Keys")
        pvfo = RadioSettingGroup("pvfo", "PM0: Programmable VFO")
        bmsk = RadioSettingGroup("bmsk", "PM0: Band Masks")    # end PM[0]
        rptr = RadioSettingGroup("rptr", "Repeater")
        dtmf = RadioSettingGroup("dtmf", "DTMF")
        skyk = RadioSettingGroup("skyk", "Sky Command")
        pmm = RadioSettingGroup("pmm", "PM Groups 1-5(Partial)")
        group = RadioSettings(basic, disp, aud, aux, txrx, memz, pvfo, pfk,
                              bmsk, rptr, dtmf, skyk, pmm)

        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, ndy=-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:
                if atrb[0:7] == "progvfo":      # 2-deep
                    stx = atrb.split(".")
                    setattr(obj[ndx].progvfo[ndy], stx[1], 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
            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

        # ===== BASIC GROUP =====
        sx = _char_to_str(_com.comnt)
        rx = RadioSettingValueString(0, 32, sx)
        sx = "Comment"
        rset = RadioSetting("mcpcom.comnt", sx, rx)
        basic.append(rset)

        rx = RadioSettingValueInteger(0, 5, _blk1.pmrecall)
        sx = "Current PM Select"
        rset = RadioSetting("block1.pmrecall", sx, rx)
        basic.append(rset)

        rx = RadioSettingValueBoolean(bool(_blk1.pwdon))
        sx = "Password"
        rset = RadioSetting("block1.pwdon", sx, rx)
        basic.append(rset)

        sx = _char_to_str(_blk1.pswd).strip()
        rx = RadioSettingValueString(0, 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")
        basic.append(rset)

        # ===== PM0 (off) DISPLAY GROUP =====
        rx = RadioSettingValueString(0, 8, _char_to_str(_pmg[0].pwron))
        sx = "Power-On message"
        rset = RadioSetting("pmg/0.pwron", sx, rx)
        disp.append(rset)

        if self.SHORT == "G":         # TMD-710G
            rx = RadioSettingValueBoolean(bool(_bmp.bmpon))
            sx = "PM0: 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)

        opts = ["VFO", "Mem Recall"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].a_mr])
        sx = "A: Left Side VFO/MR"
        rset = RadioSetting("pmg/0.a_mr", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "a_mr")
        disp.append(rset)

        rx = RadioSettingValueInteger(0, 999, _pmg[0].a_chn)
        sx = "A: Left Side MR Channel"
        rset = RadioSetting("pmg/0.a_chn", sx, rx)
        disp.append(rset)

        rx = RadioSettingValueList(opts, opts[_pmg[0].b_mr])
        sx = "B: Right Side VFO/MR"
        rset = RadioSetting("pmg/0.b_mr", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "b_mr")
        disp.append(rset)

        rx = RadioSettingValueInteger(0, 999, _pmg[0].b_chn)
        sx = "B: Right Side MR Channel"
        rset = RadioSetting("pmg/0.b_chn", sx, rx)
        disp.append(rset)

        rx = RadioSettingValueInteger(0, 8, _pmg[0].bright)
        sx = "Brightness level"
        rset = RadioSetting("pmg/0.bright", sx, rx)
        disp.append(rset)

        opts = ["Amber", "Green"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].bkltclr])
        sx = "Backlight color"
        rset = RadioSetting("pmg/0.bkltclr", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "bkltclr")
        disp.append(rset)

        val = _pmg[0].bkltcont + 1
        rx = RadioSettingValueInteger(1, 16, val)
        sx = "Contrast level"
        rset = RadioSetting("pmg/0.bkltcont", sx, rx)
        rset.set_apply_callback(_adjraw, _pmg[0], "bkltcont", -1)
        disp.append(rset)

        opts = ["Positive", "Negative"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].dsprev])
        sx = "Color mode"
        rset = RadioSetting("pmg/0.dsprev", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "dsprev")
        disp.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].autobri))
        sx = "Auto brightness"
        rset = RadioSetting("pmg/0.autobri", sx, rx)
        disp.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].dispbar))
        sx = "Display partition bar"
        rset = RadioSetting("pmg/0.dispbar", sx, rx)
        disp.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].single))
        sx = "Single band display"
        rset = RadioSetting("pmg/0.single", sx, rx)
        disp.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].autopm))
        sx = "Auto PM Store"
        rset = RadioSetting("pmg/0.autopm", sx, rx)
        disp.append(rset)

        # ===== AUDIO GROUP =====
        rx = RadioSettingValueBoolean(bool(_pmg[0].beepon))
        sx = "Beep On"
        rset = RadioSetting("pmg/0.beepon", sx, rx)
        aud.append(rset)

        val = _pmg[0].beepvol + 1     # 1-7 downloads as 0-6
        rx = RadioSettingValueInteger(1, 7, val)
        sx = "Beep volume (1 - 7)"
        rset = RadioSetting("pmg/0.beepvol", sx, rx)
        rset.set_apply_callback(_adjraw, _pmg[0], "beepvol", -1)
        aud.append(rset)

        opts = ["Mode1", "Mode2"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].extspkr])
        sx = "External Speaker"
        rset = RadioSetting("pmg/0.extspkr", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "extspkr")
        aud.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].pbkrpt))
        sx = "VGS Plugin: Playback repeat"
        rset = RadioSetting("pmg/0.pbkrpt", sx, rx)
        aud.append(rset)

        rx = RadioSettingValueInteger(0, 60, _pmg[0].pbkint)
        sx = "     Playback repeat interval (0 - 60 secs)"
        rset = RadioSetting("pmg/0.pbkint", sx, rx)
        aud.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].cntrec))
        sx = "     Continuous recording"
        rset = RadioSetting("pmg/0.cntrec", sx, rx)
        aud.append(rset)

        opts = ["Off", "Auto", "Manual"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].ance])
        sx = "     Announce mode"
        rset = RadioSetting("pmg/0.ance", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "ance")
        aud.append(rset)

        opts = ["English", "Japanese"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].lang])
        sx = "     Announce language"
        rset = RadioSetting("pmg/0.lang", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "lang")
        aud.append(rset)

        rx = RadioSettingValueInteger(1, 7, _pmg[0].vcvol + 1)
        sx = "     Voice volume (1 - 7)"
        rset = RadioSetting("pmg/0.vcvol", sx, rx)
        rset.set_apply_callback(_adjraw, _pmg[0], "vcvol", -1)
        aud.append(rset)

        rx = RadioSettingValueInteger(0, 4, _pmg[0].vcspd)
        sx = "     Voice speed (0 - 4)"
        rset = RadioSetting("pmg/0.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(_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[_pmg[0].intband])
        sx = "Internal TNC band"
        rset = RadioSetting("pmg/0.intband", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "intband")
        aux.append(rset)

        opts = ["A-Band", "B-Band", "TX-A / RX-B", "RX-A / TX-B"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].extband])
        sx = "External TNC band"
        rset = RadioSetting("pmg/0.extband", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "extband")
        aux.append(rset)

        opts = ["1200", "9600"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].extbaud])
        sx = "External TNC baud"
        rset = RadioSetting("pmg/0.extbaud", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "extbaud")
        aux.append(rset)

        opts = ["Off", "BUSY", "SQL", "TX", "BUSY/TX", "SQL/TX"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].sqcsrc])
        sx = "SQC output source"
        rset = RadioSetting("pmg/0.sqcsrc", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "sqcsrc")
        aux.append(rset)

        opts = ["Low", "High"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].sqclogic])
        sx = "SQC logic"
        rset = RadioSetting("pmg/0.sqclogic", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "sqclogic")
        aux.append(rset)

        opts = ["Off", "30", "60", "90", "120", "180"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].apo])
        sx = "APO: Auto Power Off (Mins)"
        rset = RadioSetting("pmg/0.apo", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "apo")
        aux.append(rset)

        opts = ["Time Operate (TO)", "Carrier Operate (CO)", "Seek"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].scnrsm])
        sx = "Scan resume mode"
        rset = RadioSetting("pmg/0.scnrsm", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "scnrsm")
        aux.append(rset)

        rx = RadioSettingValueInteger(1, 10, _pmg[0].scntot + 1)
        sx = "   Scan TO delay (Secs)"
        rset = RadioSetting("pmg/0.scntot", sx, rx)
        rset.set_apply_callback(_adjraw, _pmg[0], "scntot", -1)
        aux.append(rset)

        rx = RadioSettingValueInteger(1, 10, _pmg[0].scncot + 1)
        sx = "   Scan CO delay (Secs)"
        rset = RadioSetting("pmg/0.scncot", sx, rx)
        rset.set_apply_callback(_adjraw, _pmg[0], "scncot", -1)
        aux.append(rset)

        opts = ["Mode 1: 1ch", "Mode 2: 61ch", "Mode 3: 91ch",
                "Mode 4: 181ch"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].vsmode])
        sx = "Visual scan"
        rset = RadioSetting("pmg/0.vsmode", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "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[_pmg[0].txband])
        sx = "TX Side (PTT)"
        rset = RadioSetting("pmg/0.txband", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "txband")
        txrx.append(rset)

        opts = ["High (50W)", "Medium (10W)", "Low (5W)"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].a_pwr])
        sx = "A-Band transmit power"
        rset = RadioSetting("pmg/0.a_pwr", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "a_pwr")
        txrx.append(rset)

        rx = RadioSettingValueList(opts, opts[_pmg[0].b_pwr])
        sx = "B-Band transmit power"
        rset = RadioSetting("pmg/0.b_pwr", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "b_pwr")
        txrx.append(rset)

        opts = ["Off", "125", "250", "500", "750", "1000"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].mutehu])
        sx = "Rx Mute hangup time (ms)"
        rset = RadioSetting("pmg/0.mutehu", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "mutehu")
        txrx.append(rset)

        opts = ["Off", "125", "250", "500"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].ssqlhu])
        sx = "S-meter SQL hangup time (ms)"
        rset = RadioSetting("pmg/0.ssqlhu", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "ssqlhu")
        txrx.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].beatshft))
        sx = "Beat shift"
        rset = RadioSetting("pmg/0.beatshft", sx, rx)
        txrx.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].asmsql))
        sx = "A-Band S-meter SQL"
        rset = RadioSetting("pmg/0.asmsql", sx, rx)
        txrx.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].bsmsql))
        sx = "B-Band S-meter SQL"
        rset = RadioSetting("pmg/0.bsmsql", sx, rx)
        txrx.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].vhfaip))
        sx = "VHF band AIP"
        rset = RadioSetting("pmg/0.vhfaip", sx, rx)
        txrx.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].uhfaip))
        sx = "UHF band AIP"
        rset = RadioSetting("pmg/0.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[_pmg[0].tot])
        sx = "Time-Out timer (Mins)"
        rset = RadioSetting("pmg/0.tot", sx, rx)
        #  rset.set_apply_callback(_val_list, opts, _pmg[0], "tot")
        txrx.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].wxalerta))
        sx = "WX Alert A-band"
        rset = RadioSetting("pmg/0.wxalerta", sx, rx)
        txrx.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].wxalertb))
        sx = "WX Alert B-band"
        rset = RadioSetting("pmg/0.wxalertb", sx, rx)
        txrx.append(rset)

        opts = ["Off", "15", "30", "60"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].wxscntm])
        sx = "WX alert scan memory time (Mins)"
        rset = RadioSetting("pmg/0.wxscntm", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "wxscntm")
        txrx.append(rset)

        # ===== DTMF GROUP =====
        rx = RadioSettingValueBoolean(bool(_pmg[0].dtmfhld))
        sx = "DTMF hold"
        rset = RadioSetting("pmg/0.dtmfhld", sx, rx)
        dtmf.append(rset)

        opts = ["100", "250", "500", "750", "1000", "1500", "2000"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].dtmfpau])
        sx = "DTMF pause duration (mS)"
        rset = RadioSetting("pmg/0.dtmfpau", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "dtmfpau")
        dtmf.append(rset)

        opts = ["Fast", "Slow"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].dtmfspd])
        sx = "DTMF speed"
        rset = RadioSetting("pmg/0.dtmfspd", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "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[_pmg[0].recall])
        sx = "Memory recall method"
        rset = RadioSetting("pmg/0.recall", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "recall")
        memz.append(rset)

        rx = RadioSettingValueString(0, 10, _char_to_str(_pmg[0].memgrplk))
        sx = "Group link"
        rset = RadioSetting("pmg/0.memgrplk", sx, rx)
        memz.append(rset)

        opts = ["Fast", "Slow"]
        rx = RadioSettingValueList(opts, opts[_pmg[0].eclnkspd])
        sx = "Echolink speed"
        rset = RadioSetting("pmg/0.eclnkspd", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "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(_pmg[0].rptr1750))
        sx = "1750 Hz transmit hold"
        rset = RadioSetting("pmg/0.rptr1750", sx, rx)
        rptr.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].rptrofst))
        sx = "Auto repeater offset"
        rset = RadioSetting("pmg/0.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(_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(_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(_pmg[0].progvfo[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("pmg/0.progvfo/%d.blow" % mx, sx, rx)
            rset.set_apply_callback(_mhz_val, _pmg, "progvfo.blow", 0, mx)
            pvfo.append(rset)

            vfx = int(_pmg[0].progvfo[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("pmg/0.progvfo/%d.bhigh" % mx, sx, rx)
            rset.set_apply_callback(_mhz_val, _pmg, "progvfo.bhigh", 0, 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[_pmg[0].pf1key])
        sx = "Front panel PF1 key"
        rset = RadioSetting("pmg/0.pf1key", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "pf1key")
        pfk.append(rset)

        rx = RadioSettingValueList(opts, opts[_pmg[0].pf2key])
        sx = "Front panel PF2 key"
        rset = RadioSetting("pmg/0.pf2key", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "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[_pmg[0].micpf1])
        sx = "Microphone PF1 key"
        rset = RadioSetting("pmg/0.micpf1", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "micpf1")
        pfk.append(rset)

        rx = RadioSettingValueList(opts, opts[_pmg[0].micpf2])
        sx = "Microphone PF2 key"
        rset = RadioSetting("pmg/0.micpf2", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "micpf2")
        pfk.append(rset)

        rx = RadioSettingValueList(opts, opts[_pmg[0].micpf3])
        sx = "Microphone PF3 key"
        rset = RadioSetting("pmg/0.micpf3", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "micpf3")
        pfk.append(rset)

        rx = RadioSettingValueList(opts, opts[_pmg[0].micpf4])
        sx = "Microphone PF4 key"
        rset = RadioSetting("pmg/0.micpf4", sx, rx)
        rset.set_apply_callback(_val_list, opts, _pmg[0], "micpf4")
        pfk.append(rset)

        # ===== BMSK GROUP =====
        rx = RadioSettingValueBoolean(bool(_pmg[0].abnd118))
        sx = "A/Left: 118Mhz Band"
        rset = RadioSetting("pmg/0.abnd118", sx, rx)
        bmsk.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].abnd144))
        sx = "A/Left: 144Mhz Band"
        rset = RadioSetting("pmg/0.abnd144", sx, rx)
        bmsk.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].abnd220))
        sx = "A/Left: 220Mhz Band"
        rset = RadioSetting("pmg/0.abnd220", sx, rx)
        bmsk.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].abnd300))
        sx = "A/Left: 300Mhz Band"
        rset = RadioSetting("pmg/0.abnd300", sx, rx)
        bmsk.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].abnd430))
        sx = "A/Left: 430Mhz Band"
        rset = RadioSetting("pmg/0.abnd430", sx, rx)
        bmsk.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].bbnd144))
        sx = "B/Right: 144Mhz Band"
        rset = RadioSetting("pmg/0.bbnd144", sx, rx)
        bmsk.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].bbnd220))
        sx = "B/Right: 220Mhz Band"
        rset = RadioSetting("pmg/0.bbnd220", sx, rx)
        bmsk.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].bbnd300))
        sx = "B/Right: 300Mhz Band"
        rset = RadioSetting("pmg/0.bbnd300", sx, rx)
        bmsk.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].bbnd430))
        sx = "B/Right: 430Mhz Band"
        rset = RadioSetting("pmg/0.bbnd430", sx, rx)
        bmsk.append(rset)

        rx = RadioSettingValueBoolean(bool(_pmg[0].bbnd800))
        sx = "B/Right: 800Mhz Band"
        rset = RadioSetting("pmg/0.bbnd800", sx, rx)
        bmsk.append(rset)

        # ===== Sky command Group =============
        rx = RadioSettingValueString(0, 10, _char_to_str(_skyc.cmdr))
        sx = "Commandr 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(_val_list, opts, _skyc, "skytone")
        skyk.append(rset)

        # ===== PM MEMORY GROUP =====
        """ These 5 blocks of 512 bytes are repeats of the major settings """
        # Only showing limited settings for now...
        _pmn = self._memobj.pm_name
        for ix in range(1, 6):
            nx = ix - 1          # Names are [0-4]
            rx = RadioSettingValueString(0, 16, _char_to_str(_pmn[nx].pmname))
            sx = "PM Group %i Name" % ix
            rset = RadioSetting("pm_name/%i.pmname" % nx, sx, rx)
            rset.set_apply_callback(_pad_str, 16, chr(0xff), _pmn,
                                    "pmname", nx)
            pmm.append(rset)

            rx = RadioSettingValueString(0, 8, _char_to_str(_pmg[ix].pwron))
            sx = "-   Power-On Message"
            rset = RadioSetting("pmg/%i.pwron" % ix, sx, rx)
            rset.set_apply_callback(_pad_str, 8, chr(0xff), _pmg, "pwron", ix)
            pmm.append(rset)

            opts = ["VFO", "Mem Recall"]
            rx = RadioSettingValueList(opts, opts[_pmg[ix].a_mr])
            sx = "-   A: Left Side VFO/MR"
            rset = RadioSetting("pmg/%i.a_mr" % ix, sx, rx)
            rset.set_apply_callback(_val_list, opts, _pmg[ix], "a_mr")
            pmm.append(rset)

            rx = RadioSettingValueInteger(0, 999, _pmg[ix].a_chn)
            sx = "-   A: Left Side MR Channel"
            rset = RadioSetting("pmg/%i.a_chn" % ix, sx, rx)
            pmm.append(rset)

            rx = RadioSettingValueList(opts, opts[_pmg[ix].b_mr])
            sx = "-   B: Right Side VFO/MR"
            rset = RadioSetting("pmg/%i.b_mr" % ix, sx, rx)
            rset.set_apply_callback(_val_list, opts, _pmg[ix], "b_mr")
            pmm.append(rset)

            rx = RadioSettingValueInteger(0, 999, _pmg[ix].b_chn)
            sx = "-   B: Right Side MR Channel"
            rset = RadioSetting("pmg/%i.b_chn" % ix, sx, rx)
            pmm.append(rset)

            rx = RadioSettingValueInteger(0, 8, _pmg[ix].bright)
            sx = "-   Brightness level"
            rset = RadioSetting("pmg/%i.bright" % ix, sx, rx)
            pmm.append(rset)

            opts = ["Amber", "Green"]
            rx = RadioSettingValueList(opts, opts[_pmg[ix].bkltclr])
            sx = "-   Backlight color"
            rset = RadioSetting("pmg/%i.bkltclr" % ix, sx, rx)
            rset.set_apply_callback(_val_list, opts, _pmg[ix], "bkltclr")
            pmm.append(rset)

            val = _pmg[ix].bkltcont + 1
            rx = RadioSettingValueInteger(1, 16, val)
            sx = "-   Contrast level"
            rset = RadioSetting("pmg/%i.bkltcont" % ix, sx, rx)
            rset.set_apply_callback(_adjraw, _pmg[ix], "bkltcont", -1)
            pmm.append(rset)

            opts = ["Positive", "Negative"]
            rx = RadioSettingValueList(opts, opts[_pmg[ix].dsprev])
            sx = "-   Color mode"
            rset = RadioSetting("pmg/%i.dsprev" % ix, sx, rx)
            rset.set_apply_callback(_val_list, opts, _pmg[ix], "dsprev")
            pmm.append(rset)

            rx = RadioSettingValueBoolean(bool(_pmg[ix].beepon))
            sx = "-   Beep On"
            rset = RadioSetting("pmg/%i.beepon" % ix, sx, rx)
            pmm.append(rset)

            val = _pmg[ix].beepvol + 1     # 1-7 downloads as 0-6
            rx = RadioSettingValueInteger(1, 7, val)
            sx = "-   Beep volume (1 - 7)"
            rset = RadioSetting("pmg/%i.beepvol" % ix, sx, rx)
            rset.set_apply_callback(_adjraw, _pmg[ix], "beepvol", -1)
            pmm.append(rset)

            rx = RadioSettingValueBoolean(bool(_pmg[ix].autopm))
            sx = "-   Auto PM Store"
            rset = RadioSetting("pmg/%i.autopm" % ix, sx, rx)
            pmm.append(rset)

            opts = ["A: Left", "B: Right"]
            rx = RadioSettingValueList(opts, opts[_pmg[ix].txband])
            sx = "-   X Side (PTT)"
            rset = RadioSetting("pmg/%i.txband" % ix, sx, rx)
            rset.set_apply_callback(_val_list, opts, _pmg[ix], "txband")
            pmm.append(rset)

            opts = ["High (50W)", "Medium (10W)", "Low (5W)"]
            rx = RadioSettingValueList(opts, opts[_pmg[ix].a_pwr])
            sx = "-   A-Band transmit power"
            rset = RadioSetting("pmg/%i.a_pwr" % ix, sx, rx)
            rset.set_apply_callback(_val_list, opts, _pmg[ix], "a_pwr")
            pmm.append(rset)

            rx = RadioSettingValueList(opts, opts[_pmg[ix].b_pwr])
            sx = "-   B-Band transmit power"
            rset = RadioSetting("pmg/%i.b_pwr" % ix, sx, rx)
            rset.set_apply_callback(_val_list, opts, _pmg[ix], "b_pwr")
            pmm.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.pmg, self._memobj.pm_name)
        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 drivers if environment is PY3 compliant
    @directory.register
    class KenwoodTMD710Radio(KenwoodTMx710Radio):
        """ Kenwood TM-D710 VHF/UHF/APRS Radio model. """
        VENDOR = "Kenwood"
        MODEL = "TM-D710_CloneMode"
        SHORT = ""       # Quick model code

        _num_blocks = 3
        _num_packets = [0x9c, 1, 1]

        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;
        ul32 offset;          // or Split mode TX freq
        u8   splitstep;
        u8   cross;           // not used
        };

        struct pm_grp {         // 512 bytes per group
        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;
        struct chns vfo[10];         // 0x0240 - 0x02df
        char pwron[8];
        u8   unk02e8;
        u8   unk02e9;
        u8   unk02ea;
        u8   unk02eb;
        u8   unk02ec;
        u8   unk02ed;
        u8   unk02ee;
        u8   unk02ef;
        char memgrplk[10];
        u8   unk02fa;
        u8   unk02fb;
        u8   unk02fc;
        u8   unk02fd;
        u8   unk02fe;
        u8   unk02ff;
        struct {
            ul32 blow;
            ul32 bhigh;
        } progvfo[10];
        u8   beepon;
        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;
        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;
        u8   miclck;
        u8   unk0372;
        u8   scnrsm;
        u8   apo;
        u8   extband;
        u8   extbaud;
        u8   sqcsrc;
        u8   autopm;
        u8   dispbar;
        u8   unk037a;
        u8   bkltcont;
        u8   dsprev;
        u8   vsmode;
        u8   intband;
        u8   wxscntm;
        u8   scntot;
        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;
        u8   abnd144;
        u8   abnd220;
        u8   abnd300;
        u8   abnd430;
        u8   bbnd144;
        u8   bbnd220;
        u8   bbnd300;
        u8   bbnd430;
        u8   bbnd800;
        u8   unk039a;
        u8   unk039b;
        u8   unk039c;
        u8   unk039d;
        u8   unk039e;
        u8   unk039f;
        u8   unk03a0[96];       // to 0x03ff
        };                        // end of struct pm

        #seekto 0x0000;         // block1: x000 - x023f
        struct {
        u8   unk000[16];
        u8   unk010;
        u8   unk011;
        char unk012[3];
        u8   ansbck;
        u8   pmrecall;            // 0x0016
        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];             // 0x023a - 23f
        } block1;

        #seekto 0x0030;
        struct {
        char code[16];            // @ 0x0030
        } dtmc[10];

        struct {
        char id[8];               // 0x00d0 - 0x011f
        } dtmn[10];

        struct {                    // block1a: 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
        } block1a;

        struct pm_grp pmg[6];       // 0x0200 - 0x0dff

        #seekto 0x0e00;
        struct {
        u8   band;
        u8   skip;
        } chmap[1030];              // to 0x0160b

        #seekto 0x01700;            // 0x01700 - 0x0575f
        struct chns ch_mem[1030];   // 0-999 MR and 1000 -1029 Specials

        #seekto 0x05760;
        struct chns call[2];

        #seekto 0x05800;
        struct {
        char name[8];
        } ch_nam[1020];         // ends @ 0x07e0

        #seekto 0x077e0;        // 0x077e0 - 0x07830
        struct {
        char name[8];
        } wxnam[10];

        #seekto 0x07da0;
        struct {
        char pmname[16];
        } pm_name[5];

        #seekto 0x07df0;
        struct {
        char comnt[32];
        } mcpcom;

        #seekto 0x08660;
        struct {
        char cmdr[10];
        char tptr[10];
        u8  skytone;          // 0x08674
        } skycmd;
                            // data stops at 0x09b98
        """

        def _read_mem(radio):
            """ Load the memory map """
            global BAUD
            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)

            data = ""

            radio.pipe.baudrate = BAUD
            cmc = "0M PROGRAM" + TERM
            resp0 = _command(radio.pipe, cmc, 3, W8S)
            junk = radio.pipe.read(16)       # flushit
            for bkx in range(0, 0x09c):
                if bkx != 0x07f:            # Skip block 7f !!??
                    cmc = "R" + chr(bkx) + chr(0) + chr(0)
                    resp0 = _command(radio.pipe, cmc, 260, W8S)
                    junk = _command(radio.pipe, ACK, 1, W8S)
                    if len(resp0) < 260:
                        junk = _command(radio.pipe, "E", 2, W8S)
                        sx = "Block 0x%x read error: " % bkx
                        sx += "Got %i bytes, expected 260." % len(resp0)
                        LOG.error(sx)
                        sx = "Block read errror! Check debug.log"
                        raise errors.RadioError(sx)
                    if bkx == 0:   # 1st packet of 1st block
                        mht = resp0[4:7]   # [57 00 00 00] 03 4b 01 ff ff ...
                        data = resp0[5:6]  # 2nd byte (4b) replaces 1st
                        data += resp0[5:]  # then bytes 2 on (4b 4b 01 ff ...)
                    else:
                        data += resp0[4:]       # skip cmd echo
                    _update_status(radio, status)        # UI Update
            cmc = "R" + chr(0x0fe) + chr(0x0f0) + chr(0x010)
            resp0 = _command(radio.pipe, cmc, 0x014, W8S)
            data += resp0[4:]
            junk = _command(radio.pipe, ACK, 1, W8S)
            _update_status(radio, status)
            cmc = "R" + chr(0x0ff) + chr(0) + chr(0x090)
            resp0 = _command(radio.pipe, cmc, 0x094, W8S)
            data += resp0[4:]
            junk = _command(radio.pipe, ACK, 1, W8S)
            _update_status(radio, status)
            # Exit Prog mode, no TERM
            resp = _command(radio.pipe, "E", 2, W8S)     # Rtns 06 0d
            radio.pipe.baudrate = BAUD
            return data

        def _write_mem(radio):
            """ PROG MCP Blocks Send """
            global BAUD
            # 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
            radio.pipe.baudrate = BAUD
            resp0 = _command(radio.pipe, "0M PROGRAM" + TERM, 3, W8S)
            # Read block 0 magic header thingy, save it
            cmc = "R" + chr(0) + chr(0) + chr(4)
            resp0 = _command(radio.pipe, cmc, 8, W8S)
            mht0 = resp0[4:]    # Expecting [57 00 00 04] 03 4b 01 ff
            junk = _command(radio.pipe, ACK, 1, W8S)
            cmc = "W" + chr(0) + chr(0) + chr(1) + chr(0x0ff)
            junk = _command(radio.pipe, cmc, 1, W8S)     # responds ACK
            cmc = "R" + chr(0x080) + chr(0) + chr(3)
            resp = _command(radio.pipe, cmc, 7, W8S)   # [57 80 00 03] 00 33 00
            mht1 = resp[4:]
            junk = _command(radio.pipe, ACK, 1, W8S)
            cmc = "W" + chr(0x080) + chr(0) + chr(1) + chr(0x0ff)
            junk = _command(radio.pipe, cmc, 1, W8S)
            imgadr = 4      # After 03 4b 01 ff
            for bkx in range(0, radio._num_packets[0]):
                cmc = "W" + chr(bkx) + chr(0) + chr(0)
                imgstep = 256
                if bkx == 0:
                    imgstep = 0x0fc
                    cmc = "W" + chr(0) + chr(4) + chr(imgstep)
                    cmc += radio.get_mmap()[imgadr:imgadr + imgstep]
                else:       # after first packet
                    cmc += radio.get_mmap()[imgadr:imgadr + imgstep]
                if bkx != 0x07f:        # don't send 7f !
                    resp0 = _command(radio.pipe, cmc, 1, W8S)
                    if resp0 != ACK:
                        LOG.error("Packet 0x%x Write error, no ACK." % bkx)
                        sx = "Radio failed to acknowledge upload packet!"
                        raise errors.RadioError(sx)
                    imgadr += imgstep
                _update_status(radio, status)        # UI Update
            # write fe and ff blocks
            cmc = "W" + chr(0x0fe) + chr(0x0f0) + chr(16)
            cmc += radio.get_mmap()[imgadr:imgadr + 16]
            resp0 = _command(radio.pipe, cmc, 1, W8S)
            if resp0 != ACK:
                LOG.error("Packet 0xfe Write error, no ACK.")
                sx = "Radio failed to acknowledge upload packet!"
                raise errors.RadioError(sx)
            imgadr += 16
            cmc = "W" + chr(0x0ff) + chr(0) + chr(0x090)
            cmc += radio.get_mmap()[imgadr:imgadr + 0x090]
            resp0 = _command(radio.pipe, cmc, 1, W8S)
            if resp0 != ACK:
                LOG.error("Packet 0xff Write error, no ACK.")
                sx = "Radio failed to acknowledge upload packet!"
                raise errors.RadioError(sx)
            # Write mht1
            cmc = "W" + chr(0x080) + chr(0) + chr(3) + mht1
            resp0 = _command(radio.pipe, cmc, 1, W8S)
            if resp0 != ACK:
                LOG.error("Mht1 Write error at 0x080 00 03 , no ACK.")
                sx = "Radio failed to acknowledge upload packet!"
                raise errors.RadioError(sx)
            # and mht0
            cmc = "W" + chr(0) + chr(0) + chr(4) + mht0
            resp0 = _command(radio.pipe, cmc, 1, W8S)
            if resp0 != ACK:
                LOG.error("Mht0 Write error at 00 00 04 , no ACK.")
                sx = "Radio failed to acknowledge upload packet!"
                raise errors.RadioError(sx)
            # Write E to Exit PROG mode
            resp = _command(radio.pipe, "E", 2, W8S)
            return

    @directory.register
    class KenwoodTMD710GRadio(KenwoodTMx710Radio):
        """ Kenwood TM-D710G VHF/UHF/GPS/APRS Radio model. """
        VENDOR = "Kenwood"
        MODEL = "TM-D710G_CloneMode"
        SHORT = "G"       # Quick model code 1 for G

        _num_blocks = 2                # Only reading first 2, not GPS logs
        _packet_size = [261, 261, 261]
        _block_addr = [0, 0x100, 0x200]       # starting addr, each block
        _num_packets = [0x7f, 0x0fe, 0x200]   # num packets per block, 0-based

        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;
        };

        struct pm_grp {         // 512 bytes per group
        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;
        struct chns vfo[10];         // 0x0240 - 0x02df
        char pwron[8];
        u8   unk02e8;
        u8   unk02e9;
        u8   unk02ea;
        u8   unk02eb;
        u8   unk02ec;
        u8   unk02ed;
        u8   unk02ee;
        u8   unk02ef;
        char memgrplk[10];
        u8   unk02fa;
        u8   unk02fb;
        u8   unk02fc;
        u8   unk02fd;
        u8   unk02fe;
        u8   unk02ff;
        struct {
            ul32 blow;
            ul32 bhigh;
        } progvfo[10];
        u8   beepon;
        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;
        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;
        u8   miclck;
        u8   unk0372;
        u8   scnrsm;
        u8   apo;
        u8   extband;
        u8   extbaud;
        u8   sqcsrc;
        u8   autopm;
        u8   dispbar;
        u8   unk037a;
        u8   bkltcont;
        u8   dsprev;
        u8   vsmode;
        u8   intband;
        u8   wxscntm;
        u8   scntot;
        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;
        u8   abnd144;
        u8   abnd220;
        u8   abnd300;
        u8   abnd430;
        u8   bbnd144;
        u8   bbnd220;
        u8   bbnd300;
        u8   bbnd430;
        u8   bbnd800;
        u8   unk039a;
        u8   unk039b;
        u8   unk039c;
        u8   unk039d;
        u8   unk039e;
        u8   unk039f;
        u8   unk03a0[96];       // to 0x03ff
        };                        // end of struct pm

        #seekto 0x0000;         // block1: x000 - x023f
        struct {
        u8   unk000[16];
        u8   unk010;
        u8   unk011;
        char unk012[3];
        u8   ansbck;
        u8   pmrecall;            // 0x0016
        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];             // 0x023a - 23f
        } block1;

        #seekto 0x0030;
        struct {
        char code[16];            // @ 0x0030
        } dtmc[10];

        struct {
        char id[8];               // 0x00d0 - 0x011f
        } dtmn[10];

        struct {                    // block1a: 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
        } block1a;

        struct pm_grp pmg[6];       // 0x0200 - 0x0dff

        #seekto 0x0e00;
        struct {
        u8   band;
        u8   skip;
        } chmap[1030];              // to 0x0160b

        #seekto 0x01700;            // 0x01700 - 0x0575f
        struct chns ch_mem[1030];   // 0-999 MR and 1000 -1029 Specials

        #seekto 0x058a0;
        struct chns call[2];

        #seekto 0x05900;
        struct {
        char name[8];
        } ch_nam[1020];         // ends @ 0x07840

        #seekto 0x078e0;        // 0x078e0 - 0x0792f
        struct {
        char name[8];
        } wxnam[10];

        #seekto 0x07da0;
        struct {
        char pmname[16];
        } pm_name[5];

        #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
        """

        def _read_mem(radio):
            """ Load the memory map """
            global BAUD
            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)

            data = ""

            radio.pipe.baudrate = BAUD
            resp0 = radio.pipe.read(16)     # flush
            cmc = "0M PROGRAM" + TERM
            resp0 = _command(radio.pipe, cmc, 3, W8S)
            if resp0[:1] == "?":        # try once more
                resp0 = _command(radio.pipe, cmc, 3, W8S)
            radio.pipe.baudrate = 57600     # PROG mode is always 57.6
            LOG.debug("Switching to 57600 baud download.")
            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 = "R" + _make_address(radio._block_addr[blkn], bkx,
                                              0, 0)
                    resp0 = _command(radio.pipe, cmc,
                                     radio._packet_size[blkn], W8S)
                    if len(resp0) < radio._packet_size[blkn]:
                        junk = _command(radio.pipe, "E", 0, W8S)
                        lb = len(resp0)
                        xb = radio._packet_size[blkn]
                        sx = "Block 0x%x, 0x%x read error: " % (blkn, bkx)
                        sx += "Got %i bytes, expected %i." % (lb, xb)
                        LOG.error(sx)
                        sx = "Block read errror! Check debug.log"
                        raise errors.RadioError(sx)
                    if blkn == 0 and bkx == 0:   # 1st packet of 1st block
                        mht = resp0[5:9]   # Magic Header Thingy after cmd echo
                        data += mht[0:1]
                        data += chr(255) + chr(255) + chr(255)
                        data += resp0[9:]
                    else:
                        data += resp0[5:]       # skip cmd echo
                    _update_status(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):
            """ PROG MCP Blocks Send """
            global BAUD
            # 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
            radio.pipe.baudrate = BAUD
            resp0 = _command(radio.pipe, "0M PROGRAM" + TERM, 3, W8S)
            radio.pipe.baudrate = 57600
            LOG.debug("Switching to 57600 baud upload.")
            junk = radio.pipe.read(1)
            # Read block 0 magic header thingy, save it
            cmc = "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 = "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 = "W" + _make_address(radio._block_addr[blkn], bkx,
                                              0, 0)
                    if bkx == 0:    # First packet of the block includes mht
                        if blkn == 0:
                            cmc += chr(255) + chr(0x04b) + chr(1) + \
                                  chr(0x032) + radio.get_mmap()[4:imgadr + 256]
                        elif blkn == 1:
                            cmc += mht1 + radio.get_mmap()[imgadr + 5:imgadr
                                                           + 256]
                    else:       # after first packet
                        cmc += radio.get_mmap()[imgadr:imgadr + 256]
                    resp0 = _command(radio.pipe, cmc, 6, W8S)
                    if bkx > 0 and resp0 != ACK:
                        LOG.error("Packet 0x%x Write error, no ACK!" % bkx)
                        sx = "Radio failed to acknowledge upload. "
                        sx += "See debug.log"
                        raise errors.RadioError(sx)
                    imgadr += 256
                    _update_status(radio, status)        # UI Update
            # Re-write magic headers
            cmc = "W" + _make_address(radio._block_addr[0], 0, 1, 3)
            cmc += mht0[1:3] + chr(0x032)
            resp0 = _command(radio.pipe, cmc, 1, W8S)
            cmc = "W" + _make_address(radio._block_addr[1], 0, 0, 5)
            cmc += mht1
            resp0 = _command(radio.pipe, cmc, 1, W8S)
            cmc = "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, "E", 0, W8S)
            radio.pipe.baudrate = BAUD
            return
-------------- next part --------------
./chirp/__init__.py
./chirp/bandplan.py
./chirp/bandplan_au.py
./chirp/bandplan_iaru_r1.py
./chirp/bandplan_iaru_r2.py
./chirp/bandplan_iaru_r3.py
./chirp/bandplan_na.py
./chirp/bitwise.py
./chirp/bitwise_grammar.py
./chirp/chirp_common.py
./chirp/detect.py
./chirp/directory.py
./chirp/drivers/__init__.py
./chirp/drivers/alinco.py
./chirp/drivers/anytone.py
./chirp/drivers/ap510.py
./chirp/drivers/baofeng_uv3r.py
./chirp/drivers/baofeng_wp970i.py
./chirp/drivers/bjuv55.py
./chirp/drivers/btech.py
./chirp/drivers/ft1500m.py
./chirp/drivers/ft1802.py
./chirp/drivers/ft1d.py
./chirp/drivers/ft2800.py
./chirp/drivers/ft2900.py
./chirp/drivers/ft4.py
./chirp/drivers/ft50.py
./chirp/drivers/ft60.py
./chirp/drivers/ft7100.py
./chirp/drivers/ft7800.py
./chirp/drivers/ft817.py
./chirp/drivers/ft818.py
./chirp/drivers/ft857.py
./chirp/drivers/ft90.py
./chirp/drivers/ftm350.py
./chirp/drivers/generic_csv.py
./chirp/drivers/generic_tpe.py
./chirp/drivers/generic_xml.py
./chirp/drivers/h777.py
./chirp/drivers/ic208.py
./chirp/drivers/ic2100.py
./chirp/drivers/ic2200.py
./chirp/drivers/ic2720.py
./chirp/drivers/ic2730.py
./chirp/drivers/ic2820.py
./chirp/drivers/ic9x.py
./chirp/drivers/ic9x_icf.py
./chirp/drivers/ic9x_icf_ll.py
./chirp/drivers/ic9x_ll.py
./chirp/drivers/icf.py
./chirp/drivers/icomciv.py
./chirp/drivers/icq7.py
./chirp/drivers/ict70.py
./chirp/drivers/ict7h.py
./chirp/drivers/ict8.py
./chirp/drivers/icw32.py
./chirp/drivers/icx8x.py
./chirp/drivers/icx8x_ll.py
./chirp/drivers/id31.py
./chirp/drivers/id51.py
./chirp/drivers/id800.py
./chirp/drivers/id880.py
./chirp/drivers/idrp.py
./chirp/drivers/kenwood_hmk.py
./chirp/drivers/kenwood_itm.py
./chirp/drivers/kenwood_live.py
./chirp/drivers/kguv8d.py
./chirp/drivers/kguv9dplus.py
./chirp/drivers/kyd.py
./chirp/drivers/leixen.py
./chirp/drivers/puxing.py
./chirp/drivers/radioddity_r2.py
./chirp/drivers/rfinder.py
./chirp/drivers/template.py
./chirp/drivers/th350.py
./chirp/drivers/th9800.py
./chirp/drivers/th_uv3r.py
./chirp/drivers/th_uv3r25.py
./chirp/drivers/th_uv8000.py
./chirp/drivers/th_uvf8d.py
./chirp/drivers/thd72.py
./chirp/drivers/thuv1f.py
./chirp/drivers/tk8102.py
./chirp/drivers/tk8180.py
./chirp/drivers/tmd710.py
./chirp/drivers/tmv71.py
./chirp/drivers/tmv71_ll.py
./chirp/drivers/ts480.py
./chirp/drivers/ts590.py
./chirp/drivers/uv5r.py
./chirp/drivers/uvb5.py
./chirp/drivers/vx170.py
./chirp/drivers/vx2.py
./chirp/drivers/vx3.py
./chirp/drivers/vx5.py
./chirp/drivers/vx510.py
./chirp/drivers/vx6.py
./chirp/drivers/vx7.py
./chirp/drivers/vx8.py
./chirp/drivers/vxa700.py
./chirp/drivers/wouxun.py
./chirp/drivers/wouxun_common.py
./chirp/drivers/yaesu_clone.py
./chirp/elib_intl.py
./chirp/errors.py
./chirp/import_logic.py
./chirp/logger.py
./chirp/memmap.py
./chirp/platform.py
./chirp/pyPEG.py
./chirp/radioreference.py
./chirp/settings.py
./chirp/ui/__init__.py
./chirp/ui/bandplans.py
./chirp/ui/bankedit.py
./chirp/ui/clone.py
./chirp/ui/cloneprog.py
./chirp/ui/common.py
./chirp/ui/config.py
./chirp/ui/dstaredit.py
./chirp/ui/editorset.py
./chirp/ui/fips.py
./chirp/ui/importdialog.py
./chirp/ui/inputdialog.py
./chirp/ui/mainapp.py
./chirp/ui/memdetail.py
./chirp/ui/memedit.py
./chirp/ui/miscwidgets.py
./chirp/ui/radiobrowser.py
./chirp/ui/reporting.py
./chirp/ui/settingsedit.py
./chirp/ui/shiftdialog.py
./chirp/util.py
./chirp/xml_ll.py
./chirpc
./chirpw
./locale/check_parameters.py
./rpttool
./setup.py
./share/make_supported.py
./tests/__init__.py
./tests/run_tests
./tests/unit/__init__.py
./tests/unit/base.py
./tests/unit/test_bitwise.py
./tests/unit/test_chirp_common.py
./tests/unit/test_import_logic.py
./tests/unit/test_mappingmodel.py
./tests/unit/test_memedit_edits.py
./tests/unit/test_platform.py
./tests/unit/test_settings.py
./tests/unit/test_shiftdialog.py
./tools/bitdiff.py
./tools/cpep8.py
./tools/img2thd72.py
-------------- next part --------------
A non-text attachment was scrubbed...
Name: Kenwood_TM-D710G_CloneMode.img
Type: application/octet-stream
Size: 97721 bytes
Desc: not available
Url : http://intrepid.danplanet.com/pipermail/chirp_devel/attachments/20200428/d27e3457/attachment-0002.img 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: Kenwood_TM-D710_CloneMode.img
Type: application/octet-stream
Size: 40021 bytes
Desc: not available
Url : http://intrepid.danplanet.com/pipermail/chirp_devel/attachments/20200428/d27e3457/attachment-0003.img 


More information about the chirp_devel mailing list