[hamradio-commits] [chirp] 01/03: New upstream version 20161102

Iain R. Learmonth irl at moszumanska.debian.org
Tue Nov 8 11:59:57 UTC 2016


This is an automated email from the git hooks/post-receive script.

irl pushed a commit to branch master
in repository chirp.

commit 1a7fa89d8f9b61411109178b7e5e5bbb1b0d9e04
Author: Iain R. Learmonth <irl at fsfe.org>
Date:   Tue Nov 8 11:52:18 2016 +0000

    New upstream version 20161102
---
 PKG-INFO                                  |    2 +-
 chirp/__init__.py                         |    2 +-
 chirp/chirp_common.py                     |   21 +-
 chirp/drivers/baofeng_common.py           |  623 ++++++++++
 chirp/drivers/baofeng_wp970i.py           |  867 +++++++++++++
 chirp/drivers/btech.py                    |  108 +-
 chirp/drivers/ft2900.py                   |   60 +-
 chirp/drivers/ft60.py                     |   75 +-
 chirp/drivers/ft817.py                    |   24 +-
 chirp/drivers/gmrsuv1.py                  |  828 +++++++++++++
 chirp/drivers/h777.py                     |   19 +-
 chirp/drivers/icomciv.py                  |  102 +-
 chirp/drivers/kguv8d.py                   |  146 +--
 chirp/drivers/leixen.py                   |   74 +-
 chirp/drivers/lt725uv.py                  |  763 ++++++++++++
 chirp/drivers/puxing_px888k.py            | 1877 +++++++++++++++++++++++++++++
 chirp/drivers/retevis_rt21.py             |  526 ++++++++
 chirp/drivers/thd72.py                    |   31 +-
 chirp/drivers/tk760g.py                   |  171 ++-
 chirp/drivers/uv5r.py                     |   50 +-
 chirp/drivers/uv5x3.py                    | 1173 ++++++++++++++++++
 chirp/drivers/uv6r.py                     |  871 +++++++++++++
 chirp/drivers/wouxun.py                   |    1 +
 chirp/radioreference.py                   |    5 +-
 chirp/settings.py                         |   21 +-
 chirp/ui/editorset.py                     |    1 +
 chirp/ui/mainapp.py                       |   22 +-
 stock_configs/EU LPD and PMR Channels.csv |   24 +-
 stock_configs/KDR444.csv                  |    9 +
 stock_configs/NOAA Weather Alert.csv      |    2 +-
 30 files changed, 8218 insertions(+), 280 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index a334b87..cfa0b9c 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.0
 Name: chirp
-Version: daily-20160717
+Version: daily-20161102
 Summary: UNKNOWN
 Home-page: UNKNOWN
 Author: UNKNOWN
diff --git a/chirp/__init__.py b/chirp/__init__.py
index 54e0067..9cd6026 100644
--- a/chirp/__init__.py
+++ b/chirp/__init__.py
@@ -17,7 +17,7 @@ import os
 import sys
 from glob import glob
 
-CHIRP_VERSION="daily-20160717"
+CHIRP_VERSION="daily-20161102"
 
 module_dir = os.path.dirname(sys.modules["chirp"].__file__)
 __all__ = []
diff --git a/chirp/chirp_common.py b/chirp/chirp_common.py
index 4291dec..008222e 100644
--- a/chirp/chirp_common.py
+++ b/chirp/chirp_common.py
@@ -29,7 +29,8 @@ TONES = [67.0, 69.3, 71.9, 74.4, 77.0, 79.7, 82.5,
          225.7, 229.1, 233.6, 241.8, 250.3, 254.1,
          ]
 
-TONES_EXTRA = [62.5]
+TONES_EXTRA = [56.0, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0,
+               62.5, 63.0, 64.0]
 
 OLD_TONES = list(TONES)
 [OLD_TONES.remove(x) for x in [159.8, 165.5, 171.3, 177.3, 183.5, 189.9,
@@ -47,7 +48,7 @@ DTCS_CODES = [
     465, 466, 503, 506, 516, 523, 526, 532, 546, 565, 606,
     612, 624, 627, 631, 632, 654, 662, 664, 703, 712, 723,
     731, 732, 734, 743, 754,
-    ]
+]
 
 # 512 Possible DTCS Codes
 ALL_DTCS_CODES = []
@@ -93,7 +94,7 @@ SKIP_VALUES = ["", "S", "P"]
 CHARSET_UPPER_NUMERIC = "ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890"
 CHARSET_ALPHANUMERIC = \
     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 1234567890"
-CHARSET_ASCII = "".join([chr(x) for x in range(ord(" "), ord("~")+1)])
+CHARSET_ASCII = "".join([chr(x) for x in range(ord(" "), ord("~") + 1)])
 
 # http://aprs.org/aprs11/SSIDs.txt
 APRS_SSID = (
@@ -154,6 +155,7 @@ def dBm_to_watts(dBm):
 
 class PowerLevel:
     """Represents a power level supported by a radio"""
+
     def __init__(self, label, watts=0, dBm=0):
         if watts:
             dBm = watts_to_dBm(watts)
@@ -296,7 +298,7 @@ class Memory:
         "skip":           SKIP_VALUES,
         "empty":          [True, False],
         "dv_code":        [x for x in range(0, 100)],
-        }
+    }
 
     def __repr__(self):
         return "Memory[%i]" % self.number
@@ -552,6 +554,7 @@ class DVMemory(Memory):
 
 class MemoryMapping(object):
     """Base class for a memory mapping"""
+
     def __init__(self, model, index, name):
         self._model = model
         self._index = index
@@ -618,6 +621,7 @@ class Bank(MemoryMapping):
 
 class NamedBank(Bank):
     """A bank that can have a name"""
+
     def set_name(self, name):
         """Changes the user-adjustable bank name"""
         self._name = name
@@ -625,12 +629,14 @@ class NamedBank(Bank):
 
 class BankModel(MappingModel):
     """A bank model where one memory is in zero or one banks at any point"""
+
     def __init__(self, radio, name='Banks'):
         super(BankModel, self).__init__(radio, name)
 
 
 class MappingModelIndexInterface:
     """Interface for mappings with index capabilities"""
+
     def get_index_bounds(self):
         """Returns a tuple (lo,hi) of the min and max mapping indices"""
         raise NotImplementedError()
@@ -721,7 +727,7 @@ class RadioFeatures:
         # D-STAR
         "requires_call_lists":  BOOLEAN,
         "has_implicit_calls":   BOOLEAN,
-        }
+    }
 
     def __setattr__(self, name, val):
         if name.startswith("_"):
@@ -941,8 +947,8 @@ class RadioFeatures:
                     break
             if not valid:
                 msg = ValidationError(
-                        ("Tx freq {freq} is out "
-                         "of supported range").format(freq=format_freq(freq)))
+                    ("Tx freq {freq} is out "
+                     "of supported range").format(freq=format_freq(freq)))
                 msgs.append(msg)
 
         if mem.power and \
@@ -1190,6 +1196,7 @@ class LiveRadio(Radio):
 
 class NetworkSourceRadio(Radio):
     """Base class for all radios based on a network source"""
+
     def do_fetch(self):
         """Fetch the source data from the network"""
         pass
diff --git a/chirp/drivers/baofeng_common.py b/chirp/drivers/baofeng_common.py
new file mode 100644
index 0000000..bb2d9b7
--- /dev/null
+++ b/chirp/drivers/baofeng_common.py
@@ -0,0 +1,623 @@
+# Copyright 2016:
+# * Jim Unroe KC9HI, <rock.unroe at gmail.com>
+#
+# 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 2 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/>.
+
+"""common functions for Baofeng (or similar) handheld radios"""
+
+import time
+import struct
+import logging
+from chirp import chirp_common, directory, memmap
+from chirp import bitwise, errors, util
+from chirp.settings import RadioSettingGroup, RadioSetting, \
+    RadioSettingValueBoolean, RadioSettingValueList
+
+LOG = logging.getLogger(__name__)
+
+STIMEOUT = 1.5
+
+
+def _clean_buffer(radio):
+    radio.pipe.timeout = 0.005
+    junk = radio.pipe.read(256)
+    radio.pipe.timeout = STIMEOUT
+    if junk:
+        LOG.debug("Got %i bytes of junk before starting" % len(junk))
+
+
+def _rawrecv(radio, amount):
+    """Raw read from the radio device"""
+    data = ""
+    try:
+        data = radio.pipe.read(amount)
+    except:
+        msg = "Generic error reading data from radio; check your cable."
+        raise errors.RadioError(msg)
+
+    if len(data) != amount:
+        msg = "Error reading data from radio: not the amount of data we want."
+        raise errors.RadioError(msg)
+
+    return data
+
+
+def _rawsend(radio, data):
+    """Raw send to the radio device"""
+    try:
+        radio.pipe.write(data)
+    except:
+        raise errors.RadioError("Error sending data to radio")
+
+
+def _make_frame(cmd, addr, length, data=""):
+    """Pack the info in the headder format"""
+    frame = struct.pack(">BHB", ord(cmd), addr, length)
+    # add the data if set
+    if len(data) != 0:
+        frame += data
+    # return the data
+    return frame
+
+
+def _recv(radio, addr, length):
+    """Get data from the radio """
+    # read 4 bytes of header
+    hdr = _rawrecv(radio, 4)
+
+    # read data
+    data = _rawrecv(radio, length)
+
+    # DEBUG
+    LOG.info("Response:")
+    LOG.debug(util.hexprint(hdr + data))
+
+    c, a, l = struct.unpack(">BHB", hdr)
+    if a != addr or l != length or c != ord("X"):
+        LOG.error("Invalid answer for block 0x%04x:" % addr)
+        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
+        raise errors.RadioError("Unknown response from the radio")
+
+    return data
+
+
+def _get_radio_firmware_version(radio):
+    msg = struct.pack(">BHB", ord("S"), radio._fw_ver_start,
+                      radio._recv_block_size)
+    radio.pipe.write(msg)
+    block = _recv(radio, radio._fw_ver_start, radio._recv_block_size)
+    _rawsend(radio, "\x06")
+    time.sleep(0.05)
+    version = block[0:16]
+    return version
+
+
+def _image_ident_from_data(data, start, stop):
+    return data[start:stop]
+
+
+def _get_image_firmware_version(radio):
+    return _image_ident_from_data(radio.get_mmap(), radio._fw_ver_start,
+                                  radio._fw_ver_start + 0x10)
+
+
+def _do_ident(radio, magic):
+    """Put the radio in PROGRAM mode"""
+    #  set the serial discipline
+    radio.pipe.baudrate = 9600
+    radio.pipe.parity = "N"
+    radio.pipe.timeout = STIMEOUT
+
+    # flush input buffer
+    _clean_buffer(radio)
+
+    # send request to enter program mode
+    _rawsend(radio, magic)
+
+    ack = _rawrecv(radio, 1)
+    if ack != "\x06":
+        if ack:
+            LOG.debug(repr(ack))
+        raise errors.RadioError("Radio did not respond")
+
+    _rawsend(radio, "\x02")
+
+    # Ok, get the response
+    ident = _rawrecv(radio, radio._magic_response_length)
+
+    # check if response is OK
+    if not ident.startswith("\xaa") or not ident.endswith("\xdd"):
+        # bad response
+        msg = "Unexpected response, got this:"
+        msg +=  util.hexprint(ident)
+        LOG.debug(msg)
+        raise errors.RadioError("Unexpected response from radio.")
+
+    # DEBUG
+    LOG.info("Valid response, got this:")
+    LOG.debug(util.hexprint(ident))
+
+    _rawsend(radio, "\x06")
+    ack = _rawrecv(radio, 1)
+    if ack != "\x06":
+        if ack:
+            LOG.debug(repr(ack))
+        raise errors.RadioError("Radio refused clone")
+
+    return ident
+
+
+def _ident_radio(radio):
+    for magic in radio._magic:
+        error = None
+        try:
+            data = _do_ident(radio, magic)
+            return data
+        except errors.RadioError, e:
+            print e
+            error = e
+            time.sleep(2)
+    if error:
+        raise error
+    raise errors.RadioError("Radio did not respond")
+
+
+def _download(radio):
+    """Get the memory map"""
+    # put radio in program mode
+    ident = _ident_radio(radio)
+
+    # identify radio
+    radio_ident = _get_radio_firmware_version(radio)
+    LOG.info("Radio firmware version:")
+    LOG.debug(util.hexprint(radio_ident))
+
+    if radio_ident == "\xFF" * 16:
+        ident += radio.MODEL.ljust(8)
+
+    # UI progress
+    status = chirp_common.Status()
+    status.cur = 0
+    status.max = radio._mem_size / radio._recv_block_size
+    status.msg = "Cloning from radio..."
+    radio.status_fn(status)
+
+    data = ""
+    for addr in range(0, radio._mem_size, radio._recv_block_size):
+        frame = _make_frame("S", addr, radio._recv_block_size)
+        # DEBUG
+        LOG.info("Request sent:")
+        LOG.debug(util.hexprint(frame))
+
+        # sending the read request
+        _rawsend(radio, frame)
+
+        if radio._ack_block:
+            ack = _rawrecv(radio, 1)
+            if ack != "\x06":
+                raise errors.RadioError(
+                    "Radio refused to send block 0x%04x" % addr)
+
+        # now we read
+        d = _recv(radio, addr, radio._recv_block_size)
+
+        _rawsend(radio, "\x06")
+        time.sleep(0.05)
+
+        # aggregate the data
+        data += d
+
+        # UI Update
+        status.cur = addr / radio._recv_block_size
+        status.msg = "Cloning from radio..."
+        radio.status_fn(status)
+
+    data += ident
+
+    return data
+
+
+def _upload(radio):
+    """Upload procedure"""
+    # put radio in program mode
+    _ident_radio(radio)
+
+    # identify radio
+    radio_ident = _get_radio_firmware_version(radio)
+    LOG.info("Radio firmware version:")
+    LOG.debug(util.hexprint(radio_ident))
+    # identify image
+    image_ident = _get_image_firmware_version(radio)
+    LOG.info("Image firmware version:")
+    LOG.debug(util.hexprint(image_ident))
+
+    if radio_ident != "0xFF" * 16 and image_ident == radio_ident:
+        _ranges = radio._ranges
+    else:
+        _ranges = [(0x0000, 0x0DF0),
+                   (0x0E00, 0x1800)]
+
+    # UI progress
+    status = chirp_common.Status()
+    status.cur = 0
+    status.max = radio._mem_size / radio._send_block_size
+    status.msg = "Cloning to radio..."
+    radio.status_fn(status)
+
+    # the fun start here
+    for start, end in _ranges:
+        for addr in range(start, end, radio._send_block_size):
+            # sending the data
+            data = radio.get_mmap()[addr:addr + radio._send_block_size]
+
+            frame = _make_frame("X", addr, radio._send_block_size, data)
+
+            _rawsend(radio, frame)
+            time.sleep(0.05)
+
+            # receiving the response
+            ack = _rawrecv(radio, 1)
+            if ack != "\x06":
+                msg = "Bad ack writing block 0x%04x" % addr
+                raise errors.RadioError(msg)
+
+            # UI Update
+            status.cur = addr / radio._send_block_size
+            status.msg = "Cloning to radio..."
+            radio.status_fn(status)
+
+
+def _split(rf, f1, f2):
+    """Returns False if the two freqs are in the same band (no split)
+    or True otherwise"""
+
+    # determine if the two freqs are in the same band
+    for low, high in rf.valid_bands:
+        if f1 >= low and f1 <= high and \
+                f2 >= low and f2 <= high:
+            # if the two freqs are on the same Band this is not a split
+            return False
+
+    # if you get here is because the freq pairs are split
+    return True
+
+class BaofengCommonHT(chirp_common.CloneModeRadio,
+                      chirp_common.ExperimentalRadio):
+    """Baofeng HT Sytle Radios"""
+    VENDOR = "Baofeng"
+    MODEL = ""
+    IDENT = ""
+
+    def sync_in(self):
+        """Download from radio"""
+        try:
+            data = _download(self)
+        except errors.RadioError:
+            # Pass through any real errors we raise
+            raise
+        except:
+            # 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()
+
+    def sync_out(self):
+        """Upload to radio"""
+        try:
+            _upload(self)
+        except:
+            # 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 get_features(self):
+        """Get the radio's features"""
+
+        rf = chirp_common.RadioFeatures()
+        rf.has_settings = True
+        rf.has_bank = False
+        rf.has_tuning_step = False
+        rf.can_odd_split = True
+        rf.has_name = True
+        rf.has_offset = True
+        rf.has_mode = True
+        rf.has_dtcs = True
+        rf.has_rx_dtcs = True
+        rf.has_dtcs_polarity = True
+        rf.has_ctone = True
+        rf.has_cross = True
+        rf.valid_modes = self.MODES
+        rf.valid_characters = self.VALID_CHARS
+        rf.valid_name_length = self.LENGTH_NAME
+        rf.valid_duplexes = ["", "-", "+", "split", "off"]
+        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
+        rf.valid_cross_modes = [
+            "Tone->Tone",
+            "DTCS->",
+            "->DTCS",
+            "Tone->DTCS",
+            "DTCS->Tone",
+            "->Tone",
+            "DTCS->DTCS"]
+        rf.valid_skips = self.SKIP_VALUES
+        rf.valid_dtcs_codes = self.DTCS_CODES
+        rf.memory_bounds = (0, 127)
+        rf.valid_power_levels = self.POWER_LEVELS
+        rf.valid_bands = self.VALID_BANDS
+
+        return rf
+        
+    def _is_txinh(self, _mem):
+        raw_tx = ""
+        for i in range(0, 4):
+            raw_tx += _mem.txfreq[i].get_raw()
+        return raw_tx == "\xFF\xFF\xFF\xFF"
+
+    def get_memory(self, number):
+        _mem = self._memobj.memory[number]
+        _nam = self._memobj.names[number]
+
+        mem = chirp_common.Memory()
+        mem.number = number
+
+        if _mem.get_raw()[0] == "\xff":
+            mem.empty = True
+            return mem
+
+        mem.freq = int(_mem.rxfreq) * 10
+
+        if self._is_txinh(_mem):
+            # TX freq not set
+            mem.duplex = "off"
+            mem.offset = 0
+        else:
+            # TX freq set
+            offset = (int(_mem.txfreq) * 10) - mem.freq
+            if offset != 0:
+                if _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10):
+                    mem.duplex = "split"
+                    mem.offset = int(_mem.txfreq) * 10
+                elif offset < 0:
+                    mem.offset = abs(offset)
+                    mem.duplex = "-"
+                elif offset > 0:
+                    mem.offset = offset
+                    mem.duplex = "+"
+            else:
+                mem.offset = 0
+
+        for char in _nam.name:
+            if str(char) == "\xFF":
+                char = " "  # The OEM software may have 0xFF mid-name
+            mem.name += str(char)
+        mem.name = mem.name.rstrip()
+
+        dtcs_pol = ["N", "N"]
+
+        if _mem.txtone in [0, 0xFFFF]:
+            txmode = ""
+        elif _mem.txtone >= 0x0258:
+            txmode = "Tone"
+            mem.rtone = int(_mem.txtone) / 10.0
+        elif _mem.txtone <= 0x0258:
+            txmode = "DTCS"
+            if _mem.txtone > 0x69:
+                index = _mem.txtone - 0x6A
+                dtcs_pol[0] = "R"
+            else:
+                index = _mem.txtone - 1
+            mem.dtcs = self.DTCS_CODES[index]
+        else:
+            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
+
+        if _mem.rxtone in [0, 0xFFFF]:
+            rxmode = ""
+        elif _mem.rxtone >= 0x0258:
+            rxmode = "Tone"
+            mem.ctone = int(_mem.rxtone) / 10.0
+        elif _mem.rxtone <= 0x0258:
+            rxmode = "DTCS"
+            if _mem.rxtone >= 0x6A:
+                index = _mem.rxtone - 0x6A
+                dtcs_pol[1] = "R"
+            else:
+                index = _mem.rxtone - 1
+            mem.rx_dtcs = self.DTCS_CODES[index]
+        else:
+            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
+
+        if txmode == "Tone" and not rxmode:
+            mem.tmode = "Tone"
+        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
+            mem.tmode = "TSQL"
+        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
+            mem.tmode = "DTCS"
+        elif rxmode or txmode:
+            mem.tmode = "Cross"
+            mem.cross_mode = "%s->%s" % (txmode, rxmode)
+
+        mem.dtcs_polarity = "".join(dtcs_pol)
+
+        if not _mem.scan:
+            mem.skip = "S"
+
+        levels = self.POWER_LEVELS
+        try:
+            mem.power = levels[_mem.lowpower]
+        except IndexError:
+            LOG.error("Radio reported invalid power level %s (in %s)" %
+                      (_mem.power, levels))
+            mem.power = levels[0]
+
+        mem.mode = _mem.wide and "FM" or "NFM"
+
+        mem.extra = RadioSettingGroup("Extra", "extra")
+
+        rs = RadioSetting("bcl", "BCL",
+                          RadioSettingValueBoolean(_mem.bcl))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("pttid", "PTT ID",
+                          RadioSettingValueList(self.PTTID_LIST,
+                                                self.PTTID_LIST[_mem.pttid]))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("scode", "S-CODE",
+                          RadioSettingValueList(self.SCODE_LIST,
+                                                self.SCODE_LIST[_mem.scode]))
+        mem.extra.append(rs)
+
+        return mem
+
+    def set_memory(self, mem):
+        _mem = self._memobj.memory[mem.number]
+        _nam = self._memobj.names[mem.number]
+
+        if mem.empty:
+            _mem.set_raw("\xff" * 16)
+            _nam.set_raw("\xff" * 16)
+            return
+
+        _mem.set_raw("\x00" * 16)
+
+        _mem.rxfreq = mem.freq / 10
+
+        if mem.duplex == "off":
+            for i in range(0, 4):
+                _mem.txfreq[i].set_raw("\xFF")
+        elif mem.duplex == "split":
+            _mem.txfreq = mem.offset / 10
+        elif mem.duplex == "+":
+            _mem.txfreq = (mem.freq + mem.offset) / 10
+        elif mem.duplex == "-":
+            _mem.txfreq = (mem.freq - mem.offset) / 10
+        else:
+            _mem.txfreq = mem.freq / 10
+
+        _namelength = self.get_features().valid_name_length
+        for i in range(_namelength):
+            try:
+                _nam.name[i] = mem.name[i]
+            except IndexError:
+                _nam.name[i] = "\xFF"
+
+        rxmode = txmode = ""
+        if mem.tmode == "Tone":
+            _mem.txtone = int(mem.rtone * 10)
+            _mem.rxtone = 0
+        elif mem.tmode == "TSQL":
+            _mem.txtone = int(mem.ctone * 10)
+            _mem.rxtone = int(mem.ctone * 10)
+        elif mem.tmode == "DTCS":
+            rxmode = txmode = "DTCS"
+            _mem.txtone = self.DTCS_CODES.index(mem.dtcs) + 1
+            _mem.rxtone = self.DTCS_CODES.index(mem.dtcs) + 1
+        elif mem.tmode == "Cross":
+            txmode, rxmode = mem.cross_mode.split("->", 1)
+            if txmode == "Tone":
+                _mem.txtone = int(mem.rtone * 10)
+            elif txmode == "DTCS":
+                _mem.txtone = self.DTCS_CODES.index(mem.dtcs) + 1
+            else:
+                _mem.txtone = 0
+            if rxmode == "Tone":
+                _mem.rxtone = int(mem.ctone * 10)
+            elif rxmode == "DTCS":
+                _mem.rxtone = self.DTCS_CODES.index(mem.rx_dtcs) + 1
+            else:
+                _mem.rxtone = 0
+        else:
+            _mem.rxtone = 0
+            _mem.txtone = 0
+
+        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
+            _mem.txtone += 0x69
+        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
+            _mem.rxtone += 0x69
+
+        _mem.scan = mem.skip != "S"
+        _mem.wide = mem.mode == "FM"
+
+        if mem.power:
+            _mem.lowpower = self.POWER_LEVELS.index(mem.power)
+        else:
+            _mem.lowpower = 0
+
+        # extra settings
+        if len(mem.extra) > 0:
+            # there are setting, parse
+            for setting in mem.extra:
+                setattr(_mem, setting.get_name(), setting.value)
+        else:
+            # there are no extra settings, load defaults
+            _mem.bcl = 0
+            _mem.pttid = 0
+            _mem.scode = 0
+
+    def set_settings(self, settings):
+        _settings = self._memobj.settings
+        _mem = self._memobj
+        for element in settings:
+            if not isinstance(element, RadioSetting):
+                if element.get_name() == "fm_preset":
+                    self._set_fm_preset(element)
+                else:
+                    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
+
+    def _set_fm_preset(self, settings):
+        for element in settings:
+            try:
+                val = element.value
+                if self._memobj.fm_presets <= 108.0 * 10 - 650:
+                    value = int(val.get_value() * 10 - 650)
+                else:
+                    value = int(val.get_value() * 10)
+                LOG.debug("Setting fm_presets = %s" % (value))
+                self._memobj.fm_presets = value
+            except Exception, e:
+                LOG.debug(element.get_name())
+                raise
diff --git a/chirp/drivers/baofeng_wp970i.py b/chirp/drivers/baofeng_wp970i.py
new file mode 100644
index 0000000..76c8d92
--- /dev/null
+++ b/chirp/drivers/baofeng_wp970i.py
@@ -0,0 +1,867 @@
+# Copyright 2016:
+# * Jim Unroe KC9HI, <rock.unroe at gmail.com>
+#
+# 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 2 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
+
+LOG = logging.getLogger(__name__)
+
+from chirp.drivers import baofeng_common
+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
+
+##### MAGICS #########################################################
+
+# Baofeng WP970I magic string
+MSTRING_WP970I = "\x50\xBB\xFF\x20\x14\x04\x13"
+
+
+DTMF_CHARS = "0123456789 *#ABCD"
+STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0]
+
+LIST_AB = ["A", "B"]
+LIST_ALMOD = ["Site", "Tone", "Code"]
+LIST_BANDWIDTH = ["Wide", "Narrow"]
+LIST_COLOR = ["Off", "Blue", "Orange", "Purple"]
+LIST_DTMFSPEED = ["%s ms" % x for x in range(50, 2010, 10)]
+LIST_DTMFST = ["Off", "DT-ST", "ANI-ST", "DT+ANI"]
+LIST_MODE = ["Channel", "Name", "Frequency"]
+LIST_OFF1TO9 = ["Off"] + list("123456789")
+LIST_OFF1TO10 = LIST_OFF1TO9 + ["10"]
+LIST_OFFAB = ["Off"] + LIST_AB
+LIST_RESUME = ["TO", "CO", "SE"]
+LIST_PONMSG = ["Full", "Message"]
+LIST_PTTID = ["Off", "BOT", "EOT", "Both"]
+LIST_SCODE = ["%s" % x for x in range(1, 16)]
+LIST_RPSTE = ["Off"] + ["%s" % x for x in range(1, 11)]
+LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"]
+LIST_SHIFTD = ["Off", "+", "-"]
+LIST_STEDELAY = ["Off"] + ["%s ms" % x for x in range(100, 1100, 100)]
+LIST_STEP = [str(x) for x in STEPS]
+LIST_TIMEOUT = ["%s sec" % x for x in range(15, 615, 15)]
+LIST_TXPOWER = ["High", "Mid", "Low"]
+LIST_VOICE = ["Off", "English", "Chinese"]
+LIST_WORKMODE = ["Frequency", "Channel"]
+
+def model_match(cls, data):
+    """Match the opened/downloaded image to the correct version"""
+
+    if len(data) > 0x2008:
+        rid = data[0x2008:0x2010]
+        return rid.startswith(cls.MODEL)
+    elif len(data) == 0x2008:
+        rid = data[0x1EF0:0x1EF7]
+        return rid in cls._fileid
+    else:
+        return False
+
+
+class WP970I(baofeng_common.BaofengCommonHT):
+    """Baofeng WP970I"""
+    VENDOR = "Baofeng"
+    MODEL = "WP970I"
+
+    _fileid = []
+    _magic = [MSTRING_WP970I, ]
+    _magic_response_length = 8
+    _fw_ver_start = 0x1EF0
+    _recv_block_size = 0x40
+    _mem_size = 0x2000
+    _ack_block = True
+
+    _ranges = [(0x0000, 0x0DF0),
+               (0x0E00, 0x1800),
+               (0x1EE0, 0x1EF0),
+               (0x1F60, 0x1F70),
+               (0x1F80, 0x1F90),
+               (0x1FC0, 0x1FD0)]
+    _send_block_size = 0x10
+
+    MODES = ["NFM", "FM"]
+    VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
+        "!@#$%^&*()+-=[]:\";'<>?,./"
+    LENGTH_NAME = 6
+    SKIP_VALUES = ["", "S"]
+    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
+    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
+                    chirp_common.PowerLevel("Med",  watts=3.00),
+                    chirp_common.PowerLevel("Low",  watts=1.00)]
+    VALID_BANDS = [(130000000, 180000000),
+                   (400000000, 521000000)]
+    PTTID_LIST = LIST_PTTID
+    SCODE_LIST = LIST_SCODE
+
+
+    MEM_FORMAT = """
+    #seekto 0x0000;
+    struct {
+      lbcd rxfreq[4];
+      lbcd txfreq[4];
+      ul16 rxtone;
+      ul16 txtone;
+      u8 unused1:3,
+         isuhf:1,
+         scode:4;
+      u8 unknown1:7,
+         txtoneicon:1;
+      u8 mailicon:3,
+         unknown2:3,
+         lowpower:2;
+      u8 unknown3:1,
+         wide:1,
+         unknown4:2,
+         bcl:1,
+         scan:1,
+         pttid:2;
+    } memory[128];
+
+    #seekto 0x0B00;
+    struct {
+      u8 code[5];
+      u8 unused[11];
+    } pttid[15];
+    
+    #seekto 0x0CAA;
+    struct {
+      u8 code[5];      
+      u8 unused1:6,    
+         aniid:2;      
+      u8 unknown[2];   
+      u8 dtmfon;       
+      u8 dtmfoff;      
+    } ani;             
+
+    #seekto 0x0E20;    
+    struct {           
+      u8 squelch;
+      u8 step;
+      u8 unknown1;
+      u8 save;
+      u8 vox;
+      u8 unknown2;
+      u8 abr;
+      u8 tdr;
+      u8 beep;
+      u8 timeout;
+      u8 unknown3[4];
+      u8 voice;
+      u8 unknown4;
+      u8 dtmfst;
+      u8 unknown5;
+      u8 unknown12:6,
+         screv:2;
+      u8 pttid;
+      u8 pttlt;
+      u8 mdfa;
+      u8 mdfb;
+      u8 bcl;
+      u8 autolk;
+      u8 sftd;
+      u8 unknown6[3];
+      u8 wtled;
+      u8 rxled;
+      u8 txled;
+      u8 almod;
+      u8 band;
+      u8 tdrab;
+      u8 ste;
+      u8 rpste;
+      u8 rptrl;
+      u8 ponmsg;
+      u8 roger;
+      u8 rogerrx;
+      u8 tdrch;
+      u8 displayab:1,
+         unknown1:2,
+         fmradio:1,
+         alarm:1,
+         unknown2:1,
+         reset:1,
+         menu:1;
+      u8 unknown1:6,
+         singleptt:1,
+         vfomrlock:1;
+      u8 workmode;
+      u8 keylock;
+    } settings;
+
+    #seekto 0x0E76;    
+    struct {           
+      u8 unused1:1,    
+         mrcha:7;      
+      u8 unused2:1,    
+         mrchb:7;      
+    } wmchannel;       
+                       
+    struct vfo {       
+      u8 unknown0[8];  
+      u8 freq[8];      
+      u8 offset[6];
+      ul16 rxtone;
+      ul16 txtone;
+      u8 unused1:7,
+         band:1;
+      u8 unknown3;
+      u8 unused2:2,
+         sftd:2,       
+         scode:4;
+      u8 unknown4;     
+      u8 unused3:1
+         step:3,       
+         unused4:4;    
+      u8 unused5:1,
+         widenarr:1,
+         unused6:4,   
+         txpower3:2;   
+    };                 
+                       
+    #seekto 0x0F00;    
+    struct {           
+      struct vfo a;
+      struct vfo b;
+    } vfo;
+    
+    #seekto 0x0F4E;
+    u16 fm_presets;
+    
+    #seekto 0x1000;
+    struct {
+      char name[7];
+      u8 unknown1[9];
+    } names[128];
+    
+    #seekto 0x1ED0;
+    struct {
+      char line1[7];
+      char line2[7];
+    } sixpoweron_msg;
+    
+    #seekto 0x1EE0;
+    struct {
+      char line1[7];
+      char line2[7];
+    } poweron_msg;
+    
+    #seekto 0x1EF0;
+    struct {
+      char line1[7];
+      char line2[7];
+    } firmware_msg;
+
+    struct squelch {
+      u8 sql0;
+      u8 sql1;
+      u8 sql2;
+      u8 sql3;
+      u8 sql4;
+      u8 sql5;
+      u8 sql6;
+      u8 sql7;
+      u8 sql8;
+      u8 sql9;
+    };
+    
+    #seekto 0x1F60;
+    struct {
+      struct squelch vhf;
+      u8 unknown1[6];
+      u8 unknown2[16];
+      struct squelch uhf;
+    } squelch;
+
+    struct limit {
+      u8 enable;
+      bbcd lower[2];
+      bbcd upper[2];
+    };
+
+    #seekto 0x1FC0;
+    struct {
+      struct limit vhf;
+      struct limit uhf;
+    } limits;
+
+    """
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.experimental = \
+            ('This driver is a beta version.\n'
+             '\n'
+             'Please save an unedited copy of your first successful\n'
+             'download to a CHIRP Radio Images(*.img) file.'
+             )
+        rp.pre_download = _(dedent("""\
+            Follow these instructions to download your info:
+
+            1 - Turn off your radio
+            2 - Connect your interface cable
+            3 - Turn on your radio
+            4 - Do the download of your radio data
+            """))
+        rp.pre_upload = _(dedent("""\
+            Follow this instructions to upload your info:
+
+            1 - Turn off your radio
+            2 - Connect your interface cable
+            3 - Turn on your radio
+            4 - Do the upload of your radio data
+            """))
+        return rp
+
+    def process_mmap(self):
+        """Process the mem map into the mem object"""
+        self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap)
+
+    def get_settings(self):
+        """Translate the bit in the mem_struct into settings in the UI"""
+        _mem = self._memobj
+        basic = RadioSettingGroup("basic", "Basic Settings")
+        advanced = RadioSettingGroup("advanced", "Advanced Settings")
+        other = RadioSettingGroup("other", "Other Settings")
+        work = RadioSettingGroup("work", "Work Mode Settings")
+        fm_preset = RadioSettingGroup("fm_preset", "FM Preset")
+        dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings")
+        service = RadioSettingGroup("service", "Service Settings")
+        top = RadioSettings(basic, advanced, other, work, fm_preset, dtmfe,
+                            service)
+
+        # Basic settings
+        if _mem.settings.squelch > 0x09:
+            val = 0x00
+        else:
+            val = _mem.settings.squelch
+        rs = RadioSetting("settings.squelch", "Squelch",
+                          RadioSettingValueList(
+                              LIST_OFF1TO9, LIST_OFF1TO9[val]))
+        basic.append(rs)
+
+        if _mem.settings.save > 0x04:
+            val = 0x00
+        else:
+            val = _mem.settings.save
+        rs = RadioSetting("settings.save", "Battery Saver",
+                          RadioSettingValueList(
+                              LIST_SAVE, LIST_SAVE[val]))
+        basic.append(rs)
+
+        if _mem.settings.vox > 0x0A:
+            val = 0x00
+        else:
+            val = _mem.settings.vox
+        rs = RadioSetting("settings.vox", "Vox",
+                          RadioSettingValueList(
+                              LIST_OFF1TO10, LIST_OFF1TO10[val]))
+        basic.append(rs)
+
+        if _mem.settings.abr > 0x0A:
+            val = 0x00
+        else:
+            val = _mem.settings.abr
+        rs = RadioSetting("settings.abr", "Backlight Timeout",
+                          RadioSettingValueList(
+                              LIST_OFF1TO10, LIST_OFF1TO10[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.tdr", "Dual Watch",
+                          RadioSettingValueBoolean(_mem.settings.tdr))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.beep", "Beep",
+                           RadioSettingValueBoolean(_mem.settings.beep))
+        basic.append(rs)
+
+        if _mem.settings.timeout > 0x27:
+            val = 0x03
+        else:
+            val = _mem.settings.timeout
+        rs = RadioSetting("settings.timeout", "Timeout Timer",
+                          RadioSettingValueList(
+                              LIST_TIMEOUT, LIST_TIMEOUT[val]))
+        basic.append(rs)
+
+        if _mem.settings.voice > 0x02:
+            val = 0x01
+        else:
+            val = _mem.settings.voice
+        rs = RadioSetting("settings.voice", "Voice Prompt",
+                          RadioSettingValueList(
+                              LIST_VOICE, LIST_VOICE[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.dtmfst", "DTMF Sidetone",
+                          RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
+                              _mem.settings.dtmfst]))
+        basic.append(rs)
+
+        if _mem.settings.screv > 0x02:
+            val = 0x01
+        else:
+            val = _mem.settings.screv
+        rs = RadioSetting("settings.screv", "Scan Resume",
+                          RadioSettingValueList(
+                              LIST_RESUME, LIST_RESUME[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.pttid", "When to send PTT ID",
+                          RadioSettingValueList(LIST_PTTID, LIST_PTTID[
+                              _mem.settings.pttid]))
+        basic.append(rs)
+
+        if _mem.settings.pttlt > 0x1E:
+            val = 0x05
+        else:
+            val = _mem.settings.pttlt
+        rs = RadioSetting("pttlt", "PTT ID Delay",
+                          RadioSettingValueInteger(0, 50, val))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.mdfa", "Display Mode (A)",
+                          RadioSettingValueList(LIST_MODE, LIST_MODE[
+                              _mem.settings.mdfa]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.mdfb", "Display Mode (B)",
+                          RadioSettingValueList(LIST_MODE, LIST_MODE[
+                              _mem.settings.mdfb]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.autolk", "Automatic Key Lock",
+                          RadioSettingValueBoolean(_mem.settings.autolk))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.wtled", "Standby LED Color",
+                          RadioSettingValueList(
+                              LIST_COLOR, LIST_COLOR[_mem.settings.wtled]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.rxled", "RX LED Color",
+                          RadioSettingValueList(
+                              LIST_COLOR, LIST_COLOR[_mem.settings.rxled]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.txled", "TX LED Color",
+                          RadioSettingValueList(
+                              LIST_COLOR, LIST_COLOR[_mem.settings.txled]))
+        basic.append(rs)
+
+        val = _mem.settings.almod
+        rs = RadioSetting("settings.almod", "Alarm Mode",
+                          RadioSettingValueList(
+                              LIST_ALMOD, LIST_ALMOD[val]))
+        basic.append(rs)
+
+        if _mem.settings.tdrab > 0x02:
+            val = 0x00
+        else:
+            val = _mem.settings.tdrab
+        rs = RadioSetting("settings.tdrab", "Dual Watch TX Priority",
+                          RadioSettingValueList(
+                              LIST_OFFAB, LIST_OFFAB[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.ste", "Squelch Tail Eliminate (HT to HT)",
+                          RadioSettingValueBoolean(_mem.settings.ste))
+        basic.append(rs)
+
+        if _mem.settings.rpste > 0x0A:
+            val = 0x00
+        else:
+            val = _mem.settings.rpste
+        rs = RadioSetting("settings.rpste",
+                          "Squelch Tail Eliminate (repeater)",
+                              RadioSettingValueList(
+                              LIST_RPSTE, LIST_RPSTE[val]))
+        basic.append(rs)
+
+        if _mem.settings.rptrl > 0x0A:
+            val = 0x00
+        else:
+            val = _mem.settings.rptrl
+        rs = RadioSetting("settings.rptrl", "STE Repeater Delay",
+                          RadioSettingValueList(
+                              LIST_STEDELAY, LIST_STEDELAY[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.ponmsg", "Power-On Message",
+                          RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
+                              _mem.settings.ponmsg]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.roger", "Roger Beep",
+                          RadioSettingValueBoolean(_mem.settings.roger))
+        basic.append(rs)
+
+        # Advanced settings
+        rs = RadioSetting("settings.reset", "RESET Menu",
+                          RadioSettingValueBoolean(_mem.settings.reset))
+        advanced.append(rs)
+
+        rs = RadioSetting("settings.menu", "All Menus",
+                          RadioSettingValueBoolean(_mem.settings.menu))
+        advanced.append(rs)
+
+        rs = RadioSetting("settings.fmradio", "Broadcast FM Radio",
+                          RadioSettingValueBoolean(_mem.settings.fmradio))
+        advanced.append(rs)
+
+        rs = RadioSetting("settings.alarm", "Alarm Sound",
+                          RadioSettingValueBoolean(_mem.settings.alarm))
+        advanced.append(rs)
+
+        # Other settings
+        def _filter(name):
+            filtered = ""
+            for char in str(name):
+                if char in chirp_common.CHARSET_ASCII:
+                    filtered += char
+                else:
+                    filtered += " "
+            return filtered
+
+        _msg = _mem.firmware_msg
+        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
+        val.set_mutable(False)
+        rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
+        other.append(rs)
+
+        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
+        val.set_mutable(False)
+        rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
+        other.append(rs)
+
+        _msg = _mem.sixpoweron_msg
+        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
+        val.set_mutable(False)
+        rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", val)
+        other.append(rs)
+        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
+        val.set_mutable(False)
+        rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", val)
+        other.append(rs)
+
+        _msg = _mem.poweron_msg
+        rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
+                          RadioSettingValueString(
+                              0, 7, _filter(_msg.line1)))
+        other.append(rs)
+        rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
+                          RadioSettingValueString(
+                              0, 7, _filter(_msg.line2)))
+        other.append(rs)
+
+        lower = 130
+        upper = 179
+        rs = RadioSetting("limits.vhf.lower", "VHF Lower Limit (MHz)",
+                          RadioSettingValueInteger(
+                              lower, upper, _mem.limits.vhf.lower))
+        other.append(rs)
+
+        rs = RadioSetting("limits.vhf.upper", "VHF Upper Limit (MHz)",
+                          RadioSettingValueInteger(
+                              lower, upper, _mem.limits.vhf.upper))
+        other.append(rs)
+
+        lower = 400
+        upper = 520
+        rs = RadioSetting("limits.uhf.lower", "UHF Lower Limit (MHz)",
+                          RadioSettingValueInteger(
+                              lower, upper, _mem.limits.uhf.lower))
+        other.append(rs)
+
+        rs = RadioSetting("limits.uhf.upper", "UHF Upper Limit (MHz)",
+                          RadioSettingValueInteger(
+                              lower, upper, _mem.limits.uhf.upper))
+        other.append(rs)
+
+        # Work mode settings
+        rs = RadioSetting("settings.displayab", "Display",
+                          RadioSettingValueList(
+                              LIST_AB, LIST_AB[_mem.settings.displayab]))
+        work.append(rs)
+
+        rs = RadioSetting("settings.workmode", "VFO/MR Mode",
+                          RadioSettingValueList(
+                              LIST_WORKMODE,
+                              LIST_WORKMODE[_mem.settings.workmode]))
+        work.append(rs)
+
+        rs = RadioSetting("settings.keylock", "Keypad Lock",
+                          RadioSettingValueBoolean(_mem.settings.keylock))
+        work.append(rs)
+
+        rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
+                          RadioSettingValueInteger(0, 127,
+                                                      _mem.wmchannel.mrcha))
+        work.append(rs)
+
+        rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
+                          RadioSettingValueInteger(0, 127,
+                                                      _mem.wmchannel.mrchb))
+        work.append(rs)
+
+        def convert_bytes_to_freq(bytes):
+            real_freq = 0
+            for byte in bytes:
+                real_freq = (real_freq * 10) + byte
+            return chirp_common.format_freq(real_freq * 10)
+
+        def my_validate(value):
+            value = chirp_common.parse_freq(value)
+            msg = ("Can't be less than %i.0000")
+            if value > 99000000 and value < 130 * 1000000:
+                raise InvalidValueError(msg % (130))
+            msg = ("Can't be between %i.9975-%i.0000")
+            if (179 + 1) * 1000000 <= value and value < 400 * 1000000:
+                raise InvalidValueError(msg % (179, 400))
+            msg = ("Can't be greater than %i.9975")
+            if value > 99000000 and value > (520 + 1) * 1000000:
+                raise InvalidValueError(msg % (520))
+            return chirp_common.format_freq(value)
+
+        def apply_freq(setting, obj):
+            value = chirp_common.parse_freq(str(setting.value)) / 10
+            for i in range(7, -1, -1):
+                obj.freq[i] = value % 10
+                value /= 10
+
+        val1a = RadioSettingValueString(0, 10,
+                                        convert_bytes_to_freq(_mem.vfo.a.freq))
+        val1a.set_validate_callback(my_validate)
+        rs = RadioSetting("vfo.a.freq", "VFO A Frequency", val1a)
+        rs.set_apply_callback(apply_freq, _mem.vfo.a)
+        work.append(rs)
+
+        val1b = RadioSettingValueString(0, 10,
+                                        convert_bytes_to_freq(_mem.vfo.b.freq))
+        val1b.set_validate_callback(my_validate)
+        rs = RadioSetting("vfo.b.freq", "VFO B Frequency", val1b)
+        rs.set_apply_callback(apply_freq, _mem.vfo.b)
+        work.append(rs)
+
+        rs = RadioSetting("vfo.a.sftd", "VFO A Shift",
+                          RadioSettingValueList(
+                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.a.sftd]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.b.sftd", "VFO B Shift",
+                          RadioSettingValueList(
+                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.b.sftd]))
+        work.append(rs)
+
+        def convert_bytes_to_offset(bytes):
+            real_offset = 0
+            for byte in bytes:
+                real_offset = (real_offset * 10) + byte
+            return chirp_common.format_freq(real_offset * 1000)
+
+        def apply_offset(setting, obj):
+            value = chirp_common.parse_freq(str(setting.value)) / 1000
+            for i in range(5, -1, -1):
+                obj.offset[i] = value % 10
+                value /= 10
+
+        val1a = RadioSettingValueString(
+                    0, 10, convert_bytes_to_offset(_mem.vfo.a.offset))
+        rs = RadioSetting("vfo.a.offset",
+                          "VFO A Offset", val1a)
+        rs.set_apply_callback(apply_offset, _mem.vfo.a)
+        work.append(rs)
+
+        val1b = RadioSettingValueString(
+                    0, 10, convert_bytes_to_offset(_mem.vfo.b.offset))
+        rs = RadioSetting("vfo.b.offset",
+                          "VFO B Offset", val1b)
+        rs.set_apply_callback(apply_offset, _mem.vfo.b)
+        work.append(rs)
+
+        rs = RadioSetting("vfo.a.txpower3", "VFO A Power",
+                          RadioSettingValueList(
+                              LIST_TXPOWER,
+                              LIST_TXPOWER[_mem.vfo.a.txpower3]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.b.txpower3", "VFO B Power",
+                          RadioSettingValueList(
+                              LIST_TXPOWER,
+                              LIST_TXPOWER[_mem.vfo.b.txpower3]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.a.widenarr", "VFO A Bandwidth",
+                          RadioSettingValueList(
+                              LIST_BANDWIDTH,
+                              LIST_BANDWIDTH[_mem.vfo.a.widenarr]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.b.widenarr", "VFO B Bandwidth",
+                          RadioSettingValueList(
+                              LIST_BANDWIDTH,
+                              LIST_BANDWIDTH[_mem.vfo.b.widenarr]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.a.scode", "VFO A S-CODE",
+                          RadioSettingValueList(
+                              LIST_SCODE,
+                              LIST_SCODE[_mem.vfo.a.scode]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.b.scode", "VFO B S-CODE",
+                          RadioSettingValueList(
+                              LIST_SCODE,
+                              LIST_SCODE[_mem.vfo.b.scode]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.a.step", "VFO A Tuning Step",
+                          RadioSettingValueList(
+                              LIST_STEP, LIST_STEP[_mem.vfo.a.step]))
+        work.append(rs)
+        rs = RadioSetting("vfo.b.step", "VFO B Tuning Step",
+                          RadioSettingValueList(
+                              LIST_STEP, LIST_STEP[_mem.vfo.b.step]))
+        work.append(rs)
+
+        # broadcast FM settings
+        _fm_presets = self._memobj.fm_presets
+        if _fm_presets <= 108.0 * 10 - 650:
+            preset = _fm_presets / 10.0 + 65
+        elif _fm_presets >= 65.0 * 10 and _fm_presets <= 108.0 * 10:
+            preset = _fm_presets / 10.0
+        else:
+            preset = 76.0
+        rs = RadioSetting("fm_presets", "FM Preset(MHz)",
+                          RadioSettingValueFloat(65, 108.0, preset, 0.1, 1))
+        fm_preset.append(rs)
+
+        # DTMF settings
+        def apply_code(setting, obj, length):
+            code = []
+            for j in range(0, length):
+                try:
+                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
+                except IndexError:
+                    code.append(0xFF)
+            obj.code = code
+
+        for i in range(0, 15):
+            _codeobj = self._memobj.pttid[i].code
+            _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+            val = RadioSettingValueString(0, 5, _code, False)
+            val.set_charset(DTMF_CHARS)
+            pttid = RadioSetting("pttid/%i.code" % i,
+                                 "Signal Code %i" % (i + 1), val)
+            pttid.set_apply_callback(apply_code, self._memobj.pttid[i], 5)
+            dtmfe.append(pttid)
+
+        if _mem.ani.dtmfon > 0xC3:
+            val = 0x03
+        else:
+            val = _mem.ani.dtmfon
+        rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
+                          RadioSettingValueList(LIST_DTMFSPEED,
+                                                LIST_DTMFSPEED[val]))
+        dtmfe.append(rs)
+
+        if _mem.ani.dtmfoff > 0xC3:
+            val = 0x03
+        else:
+            val = _mem.ani.dtmfoff
+        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
+                          RadioSettingValueList(LIST_DTMFSPEED,
+                                                LIST_DTMFSPEED[val]))
+        dtmfe.append(rs)
+
+        _codeobj = self._memobj.ani.code
+        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+        val = RadioSettingValueString(0, 5, _code, False)
+        val.set_charset(DTMF_CHARS)
+        rs = RadioSetting("ani.code", "ANI Code", val)
+        rs.set_apply_callback(apply_code, self._memobj.ani, 5)
+        dtmfe.append(rs)
+
+        rs = RadioSetting("ani.aniid", "When to send ANI ID",
+                          RadioSettingValueList(LIST_PTTID,
+                                                LIST_PTTID[_mem.ani.aniid]))
+        dtmfe.append(rs)
+
+        # Service settings
+        for band in ["vhf", "uhf"]:
+            for index in range(0, 10):
+                key = "squelch.%s.sql%i" % (band, index)
+                if band == "vhf":
+                    _obj = self._memobj.squelch.vhf
+                elif band == "uhf":
+                    _obj = self._memobj.squelch.uhf
+                val = RadioSettingValueInteger(0, 123,
+                          getattr(_obj, "sql%i" % (index)))
+                if index == 0:
+                    val.set_mutable(False)
+                name = "%s Squelch %i" % (band.upper(), index)
+                rs = RadioSetting(key, name, val)
+                service.append(rs)
+
+        return top
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        match_size = False
+        match_model = False
+
+        # testing the file data size
+        if len(filedata) in [0x2008, 0x2010]:
+            match_size = True
+
+        # testing the firmware model fingerprint
+        match_model = model_match(cls, filedata)
+
+        if match_size and match_model:
+            return True
+        else:
+            return False
+
+ at directory.register
+class BFA58(WP970I):
+    """Baofeng BF-A58"""
+    VENDOR = "Baofeng"
+    MODEL = "BF-A58"
+
+    _fileid = ["BFT515 ", "BFT517 "]
+
+ at directory.register
+class UV82WP(WP970I):
+    """Baofeng UV82-WP"""
+    VENDOR = "Baofeng"
+    MODEL = "UV-82WP"
+
+ at directory.register
+class GT3WP(WP970I):
+    """Baofeng GT-3WP"""
+    VENDOR = "Baofeng"
+    MODEL = "GT-3WP"
+
+ at directory.register
+class RT6(WP970I):
+    """Retevis RT6"""
+    VENDOR = "Retevis"
+    MODEL = "RT6"
diff --git a/chirp/drivers/btech.py b/chirp/drivers/btech.py
index bed353f..3d634fa 100644
--- a/chirp/drivers/btech.py
+++ b/chirp/drivers/btech.py
@@ -21,12 +21,13 @@ import logging
 
 LOG = logging.getLogger(__name__)
 
+from time import sleep
 from chirp import chirp_common, directory, memmap
 from chirp import bitwise, errors, util
 from chirp.settings import RadioSettingGroup, RadioSetting, \
     RadioSettingValueBoolean, RadioSettingValueList, \
     RadioSettingValueString, RadioSettingValueInteger, \
-    RadioSettings, InvalidValueError
+    RadioSettingValueFloat, RadioSettings, InvalidValueError
 from textwrap import dedent
 
 MEM_FORMAT = """
@@ -154,6 +155,13 @@ struct {
   u8 unknown1[10];
 } names[200];
 
+#seekto 0x3000;
+struct {
+  u8 freq[8];
+  char broadcast_station_name[6];
+  u8 unknown[2];
+} fm_radio_preset[16];
+
 #seekto 0x3C90;
 struct {
   u8 vhf_low[3];
@@ -304,6 +312,7 @@ KT8900_fp1 = "M2C234"
 KT8900_fp2 = "M2G1F4"
 KT8900_fp3 = "M2G2F4"
 KT8900_fp4 = "M2G304"
+KT8900_fp5 = "M2G314"
 # this radio has an extra ID
 KT8900_id = "      303688"
 
@@ -313,6 +322,10 @@ KT8900R_fp = "M3G1F4"
 KT8900R_fp1 = "M3G214"
 # another model
 KT8900R_fp2 = "M3C234"
+# another model G4?
+KT8900R_fp3 = "M39164"
+# another model
+KT8900R_fp4 = "M3G314"
 # this radio has an extra ID
 KT8900R_id = "280528"
 
@@ -394,6 +407,15 @@ def _send(radio, data):
     try:
         for byte in data:
             radio.pipe.write(byte)
+            # Some OS (mainly Linux ones) are too fast on the serial and
+            # get the MCU inside the radio stuck in the early stages, this
+            # hits some models more than others.
+            #
+            # To cope with that we introduce a delay on the writes.
+            # Many option have been tested (delaying only after error occures, after short reads, only for linux, ...)
+            # Finally, a static delay was chosen as simplest of all solutions (Michael Wagner, OE4AMW)
+            # (for details, see issue 3993)
+            sleep(0.002)
 
         # DEBUG
         if debug is True:
@@ -1021,6 +1043,13 @@ class BTech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
         _mem = self._memobj.memory[mem.number]
         _names = self._memobj.names[mem.number]
 
+        mem_was_empty = False
+        # same method as used in get_memory for determining if mem is empty
+        # doing this BEFORE overwriting it with new values ...
+        if _mem.get_raw()[0] == "\xFF":
+            LOG.debug("This mem was empty before")
+            mem_was_empty = True
+        
         # if empty memmory
         if mem.empty:
             # the channel itself
@@ -1078,16 +1107,24 @@ class BTech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
         # extra settings
         if len(mem.extra) > 0:
             # there are setting, parse
+            LOG.debug("Extra-Setting supplied. Setting them.")
             for setting in mem.extra:
                 setattr(_mem, setting.get_name(), setting.value)
         else:
-            # there is no extra settings, load defaults
-            _mem.spmute = 0
-            _mem.optsig = 0
-            _mem.scramble = 0
-            _mem.bcl = 0
-            _mem.pttid = 0
-            _mem.scode = 0
+            if mem.empty:
+                LOG.debug("New mem is empty.")
+            else:
+                LOG.debug("New mem is NOT empty")
+                # set extra-settings to default ONLY when apreviously empty or
+                # deleted memory was edited to prevent errors such as #4121
+                if mem_was_empty :
+                    LOG.debug("old mem was empty. Setting default for extras.")
+                    _mem.spmute = 0
+                    _mem.optsig = 0
+                    _mem.scramble = 0
+                    _mem.bcl = 0
+                    _mem.pttid = 0
+                    _mem.scode = 0
 
         return mem
 
@@ -1098,7 +1135,8 @@ class BTech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
         advanced = RadioSettingGroup("advanced", "Advanced Settings")
         other = RadioSettingGroup("other", "Other Settings")
         work = RadioSettingGroup("work", "Work Mode Settings")
-        top = RadioSettings(basic, advanced, other, work)
+        fm_presets = RadioSettingGroup("fm_presets", "FM Presets")
+        top = RadioSettings(basic, advanced, other, work, fm_presets)
 
         # Basic
         tdr = RadioSetting("settings.tdr", "Transceiver dual receive",
@@ -1282,7 +1320,7 @@ class BTech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
         if self.MODEL in ("UV-2501", "UV-5001"):
             vfomren = RadioSetting("settings2.vfomren", "VFO/MR switching",
                                    RadioSettingValueBoolean(
-                                       not _mem.settings2.vfomren))
+                                       _mem.settings2.vfomren))
             advanced.append(vfomren)
 
             reseten = RadioSetting("settings2.reseten", "RESET",
@@ -1535,6 +1573,47 @@ class BTech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
                                  PTTID_LIST[_mem.settings.pttid]))
         work.append(pttid)
 
+        #FM presets
+        def fm_validate(value):
+            if value == 0:
+                return chirp_common.format_freq(value)
+            if not (87.5 <= value and value <= 108.0):  # 87.5-108MHz
+                msg = ("FM-Preset-Frequency: Must be between 87.5 and 108 MHz")
+                raise InvalidValueError(msg)
+            return value
+
+        def apply_fm_preset_name(setting, obj):
+            valstring = str (setting.value)
+            for i in range(0,6):
+                if valstring[i] in VALID_CHARS:
+                    obj[i] = valstring[i]
+                else:
+                    obj[i] = '0xff'
+
+        def apply_fm_freq(setting, obj):
+            value = chirp_common.parse_freq(str(setting.value)) / 10
+            for i in range(7, -1, -1):
+                obj.freq[i] = value % 10
+                value /= 10
+        
+        _presets = self._memobj.fm_radio_preset
+        i = 1
+        for preset in _presets:
+            line = RadioSetting("fm_presets_"+ str(i), "Station name " + str(i),
+                                RadioSettingValueString(0, 6, _filter(
+                                    preset.broadcast_station_name)))
+            line.set_apply_callback(apply_fm_preset_name, 
+                                    preset.broadcast_station_name)
+            
+            val = RadioSettingValueFloat(0, 108, convert_bytes_to_freq(preset.freq))
+            fmfreq = RadioSetting("fm_presets_"+ str(i) + "_freq", "Frequency "+ str(i), val)
+            val.set_validate_callback(fm_validate)
+            fmfreq.set_apply_callback(apply_fm_freq, preset)
+            fm_presets.append(line)
+            fm_presets.append(fmfreq)
+            
+            i = i + 1
+            
         return top
 
     def set_settings(self, settings):
@@ -1567,8 +1646,6 @@ class BTech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
                     if element.has_apply_callback():
                         LOG.debug("Using apply callback")
                         element.run_apply_callback()
-                    elif setting == "vfomren":
-                        setattr(obj, setting, not int(element.value))
                     elif element.value.get_mutable():
                         LOG.debug("Setting %s = %s" % (setting, element.value))
                         setattr(obj, setting, element.value)
@@ -1691,7 +1768,8 @@ class KT9800(BTech):
                KT8900_fp1,
                KT8900_fp2,
                KT8900_fp3,
-               KT8900_fp4]
+               KT8900_fp4,
+               KT8900_fp5]
     _id2 = KT8900_id
     # Clones
     ALIASES = [JT6188Mini, SSGT890, ZastoneMP300]
@@ -1708,7 +1786,9 @@ class KT9800R(BTech):
     _magic = MSTRING_KT8900R
     _fileid = [KT8900R_fp,
                KT8900R_fp1,
-               KT8900R_fp2]
+               KT8900R_fp2,
+               KT8900R_fp3,
+               KT8900R_fp4]
     _id2 = KT8900R_id
 
 
diff --git a/chirp/drivers/ft2900.py b/chirp/drivers/ft2900.py
index 5712163..7175733 100644
--- a/chirp/drivers/ft2900.py
+++ b/chirp/drivers/ft2900.py
@@ -146,7 +146,7 @@ def _upload(radio):
         cs += ord(byte)
 
     while block < (radio.get_memsize() / 32):
-        data = radio.get_mmap()[block*32:(block+1)*32]
+        data = radio.get_mmap()[block * 32:(block + 1) * 32]
 
         LOG.debug("Writing block %i:\n%s" % (block, util.hexprint(data)))
 
@@ -381,9 +381,9 @@ def _decode_name(mem):
 
 def _encode_name(mem):
     if(mem.strip() == ""):
-        return [0xff]*6
+        return [0xff] * 6
 
-    name = [None]*6
+    name = [None] * 6
     for i in range(0, 6):
         try:
             name[i] = CHARSET.index(mem[i])
@@ -399,6 +399,7 @@ def _wipe_memory(mem):
 
 
 class FT2900Bank(chirp_common.NamedBank):
+
     def get_name(self):
         _bank = self._model._radio._memobj.bank_names[self.index]
         name = ""
@@ -416,6 +417,7 @@ class FT2900Bank(chirp_common.NamedBank):
 
 
 class FT2900BankModel(chirp_common.BankModel):
+
     def get_num_mappings(self):
         return 8
 
@@ -558,7 +560,7 @@ class FT2900Radio(YaesuCloneModeRadio):
 
     def get_memory(self, number):
         _mem = self._memobj.memory[number]
-        _flag = self._memobj.flags[(number)/2]
+        _flag = self._memobj.flags[(number) / 2]
 
         nibble = ((number) % 2) and "even" or "odd"
         used = _flag["%s_masked" % nibble]
@@ -622,7 +624,7 @@ class FT2900Radio(YaesuCloneModeRadio):
 
     def set_memory(self, mem):
         _mem = self._memobj.memory[mem.number]
-        _flag = self._memobj.flags[(mem.number)/2]
+        _flag = self._memobj.flags[(mem.number) / 2]
 
         nibble = ((mem.number) % 2) and "even" or "odd"
 
@@ -764,19 +766,19 @@ class FT2900Radio(YaesuCloneModeRadio):
         # 5 BCLO
         opts = ["Off", "On"]
         misc.append(RadioSetting(
-                "busy_lockout", "Busy Channel Lock-Out",
-                RadioSettingValueList(opts, opts[_settings.busy_lockout])))
+            "busy_lockout", "Busy Channel Lock-Out",
+            RadioSettingValueList(opts, opts[_settings.busy_lockout])))
 
         # 6 BEEP
         opts = ["Off", "Key+Scan", "Key"]
         switch.append(RadioSetting(
-                "beep", "Enable the Beeper",
-                RadioSettingValueList(opts, opts[_settings.beep])))
+            "beep", "Enable the Beeper",
+            RadioSettingValueList(opts, opts[_settings.beep])))
 
         # 7 BELL
         opts = ["Off", "1", "3", "5", "8", "Continuous"]
         ctcss.append(RadioSetting("bell", "Bell Repetitions",
-                     RadioSettingValueList(opts, opts[_settings.bell])))
+                                  RadioSettingValueList(opts, opts[_settings.bell])))
 
         # 8 BNK.LNK
         for i in range(0, 8):
@@ -800,7 +802,7 @@ class FT2900Radio(YaesuCloneModeRadio):
         # 11 CW.ID
         opts = ["Off", "On"]
         arts.append(RadioSetting("cw_id", "CW ID Enable",
-                    RadioSettingValueList(opts, opts[_settings.cw_id])))
+                                 RadioSettingValueList(opts, opts[_settings.cw_id])))
 
         cw_id_text = ""
         for i in _settings.cw_id_string:
@@ -830,14 +832,14 @@ class FT2900Radio(YaesuCloneModeRadio):
                 "10WPM", "11WPM", "12WPM", "13WPM", "15WPM", "17WPM",
                 "20WPM", "24WPM", "30WPM", "40WPM"]
         misc.append(RadioSetting("cw_trng", "CW Training",
-                    RadioSettingValueList(opts, opts[_settings.cw_trng])))
+                                 RadioSettingValueList(opts, opts[_settings.cw_trng])))
 
         # todo: make the setting of the units here affect the display
         # of the speed.  Not critical, but would be slick.
         opts = ["CPM", "WPM"]
         misc.append(RadioSetting("cw_trng_units", "CW Training Units",
-                    RadioSettingValueList(opts,
-                                          opts[_settings.cw_trng_units])))
+                                 RadioSettingValueList(opts,
+                                                       opts[_settings.cw_trng_units])))
 
         # 13 DC VLT - a read-only status, so nothing to do here
 
@@ -918,7 +920,7 @@ class FT2900Radio(YaesuCloneModeRadio):
         # 22 INT.CD
         opts = ["DTMF %X" % (x) for x in range(0, 16)]
         wires.append(RadioSetting("int_cd", "Access Number for WiRES(TM)",
-                     RadioSettingValueList(opts, opts[_settings.int_cd])))
+                                  RadioSettingValueList(opts, opts[_settings.int_cd])))
 
         # 23 ING MD
         opts = ["Sister Radio Group", "Friends Radio Group"]
@@ -1053,26 +1055,26 @@ class FT2900Radio(YaesuCloneModeRadio):
         # 36 PRG P1
         opts = button_opts + ["DC Volts"]
         switch.append(RadioSetting(
-                "prog_p1", "P1 Button",
-                RadioSettingValueList(opts, opts[_settings.prog_p1])))
+            "prog_p1", "P1 Button",
+            RadioSettingValueList(opts, opts[_settings.prog_p1])))
 
         # 37 PRG P2
         opts = button_opts + ["Dimmer"]
         switch.append(RadioSetting(
-                "prog_p2", "P2 Button",
-                RadioSettingValueList(opts, opts[_settings.prog_p2])))
+            "prog_p2", "P2 Button",
+            RadioSettingValueList(opts, opts[_settings.prog_p2])))
 
         # 38 PRG P3
         opts = button_opts + ["Mic Gain"]
         switch.append(RadioSetting(
-                "prog_p3", "P3 Button",
-                RadioSettingValueList(opts, opts[_settings.prog_p3])))
+            "prog_p3", "P3 Button",
+            RadioSettingValueList(opts, opts[_settings.prog_p3])))
 
         # 39 PRG P4
         opts = button_opts + ["Skip"]
         switch.append(RadioSetting(
-                "prog_p4", "P4 Button",
-                RadioSettingValueList(opts, opts[_settings.prog_p4])))
+            "prog_p4", "P4 Button",
+            RadioSettingValueList(opts, opts[_settings.prog_p4])))
 
         # 40 PSWD
         password = ""
@@ -1101,24 +1103,24 @@ class FT2900Radio(YaesuCloneModeRadio):
         # 41 RESUME
         opts = ["3 Sec", "5 Sec", "10 Sec", "Busy", "Hold"]
         scan.append(RadioSetting("resume", "Scan Resume Mode",
-                    RadioSettingValueList(opts, opts[_settings.resume])))
+                                 RadioSettingValueList(opts, opts[_settings.resume])))
 
         # 42 RF.SQL
         opts = ["Off"] + ["S-%d" % (x) for x in range(1, 10)]
         misc.append(RadioSetting("rf_sql", "RF Squelch Threshold",
-                    RadioSettingValueList(opts, opts[_settings.rf_sql])))
+                                 RadioSettingValueList(opts, opts[_settings.rf_sql])))
 
         # 43 RPT - per channel attribute, nothing to do here
 
         # 44 RVRT
         opts = ["Off", "On"]
         misc.append(RadioSetting("revert", "Priority Revert",
-                    RadioSettingValueList(opts, opts[_settings.revert])))
+                                 RadioSettingValueList(opts, opts[_settings.revert])))
 
         # 45 S.SRCH
         opts = ["Single", "Continuous"]
         misc.append(RadioSetting("s_search", "Smart Search Sweep Mode",
-                    RadioSettingValueList(opts, opts[_settings.s_search])))
+                                 RadioSettingValueList(opts, opts[_settings.s_search])))
 
         # 46 SHIFT - per channel setting, nothing to do here
 
@@ -1164,12 +1166,12 @@ class FT2900Radio(YaesuCloneModeRadio):
         # 57 WX.ALT
         opts = ["Off", "On"]
         misc.append(RadioSetting("wx_alert", "Weather Alert Scan",
-                    RadioSettingValueList(opts, opts[_settings.wx_alert])))
+                                 RadioSettingValueList(opts, opts[_settings.wx_alert])))
 
         # 58 WX.VOL
         opts = ["Normal", "Maximum"]
         misc.append(RadioSetting("wx_vol_max", "Weather Alert Volume",
-                    RadioSettingValueList(opts, opts[_settings.wx_vol_max])))
+                                 RadioSettingValueList(opts, opts[_settings.wx_vol_max])))
 
         # 59 W/N DV - this is a per-channel attribute, nothing to do here
 
diff --git a/chirp/drivers/ft60.py b/chirp/drivers/ft60.py
index 9b7020e..39c8f44 100644
--- a/chirp/drivers/ft60.py
+++ b/chirp/drivers/ft60.py
@@ -82,14 +82,14 @@ def _upload(radio):
 
     for i in range(0, 448):
         offset = 8 + (i * 64)
-        _send(radio.pipe, radio.get_mmap()[offset:offset+64])
+        _send(radio.pipe, radio.get_mmap()[offset:offset + 64])
         ack = radio.pipe.read(1)
         if ack != ACK:
             raise Exception(_("Radio did not ack block %i") % i)
 
         if radio.status_fn:
             status = chirp_common.Status()
-            status.cur = offset+64
+            status.cur = offset + 64
             status.max = radio.get_memsize()
             status.msg = "Cloning to radio"
             radio.status_fn(status)
@@ -133,7 +133,7 @@ def _decode_name(mem):
 
 
 def _encode_name(mem):
-    name = [None]*6
+    name = [None] * 6
     for i in range(0, 6):
         try:
             name[i] = CHARSET.index(mem[i])
@@ -291,6 +291,7 @@ SPECIALS = ["%s%d" % (c, i + 1) for i in range(0, 50) for c in ('L', 'U')]
 
 
 class FT60BankModel(chirp_common.BankModel):
+
     def get_num_mappings(self):
         return 10
 
@@ -357,7 +358,8 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
      radio on.
 4. Rotate the DIAL job to select "F8 CLONE".
 5. Press the [F/W] key momentarily.
-6. <b>After clicking OK</b>, press the [PTT] switch to send image."""))
+6. <b>After clicking OK</b>, hold the [PTT] switch
+     for one second to send image."""))
         rp.pre_upload = _(dedent("""\
 1. Turn radio off.
 2. Connect cable to MIC/SP jack.
@@ -435,7 +437,7 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
                                 wires, eai, switch, misc, mbls)
 
         # APO
-        opts = ["OFF"] + ["%0.1f" % (x * 0.5) for x in range(1, 24+1)]
+        opts = ["OFF"] + ["%0.1f" % (x * 0.5) for x in range(1, 24 + 1)]
         misc.append(
             RadioSetting(
                 "apo", "Automatic Power Off",
@@ -465,15 +467,15 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
         # BCLO
         opts = ["OFF", "ON"]
         misc.append(RadioSetting(
-                "bclo", "Busy Channel Lock-Out",
-                RadioSettingValueList(opts, opts[_settings.bclo])))
+            "bclo", "Busy Channel Lock-Out",
+            RadioSettingValueList(opts, opts[_settings.bclo])))
 
         # BEEP
         opts = ["OFF", "KEY", "KEY+SC"]
         rs = RadioSetting(
-                "beep_key", "Enable the Beeper",
-                RadioSettingValueList(
-                    opts, opts[_settings.beep_key + _settings.beep_sc]))
+            "beep_key", "Enable the Beeper",
+            RadioSettingValueList(
+                opts, opts[_settings.beep_key + _settings.beep_sc]))
 
         def apply_beep(s, obj):
             setattr(obj, "beep_key",
@@ -485,27 +487,27 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
         # BELL
         opts = ["OFF", "1T", "3T", "5T", "8T", "CONT"]
         ctcss.append(RadioSetting("bell", "Bell Repetitions",
-                     RadioSettingValueList(opts, opts[_settings.bell])))
+                                  RadioSettingValueList(opts, opts[_settings.bell])))
 
         # BSY.LED
         opts = ["ON", "OFF"]
         misc.append(RadioSetting("bsy_led", "Busy LED",
-                    RadioSettingValueList(opts, opts[_settings.bsy_led])))
+                                 RadioSettingValueList(opts, opts[_settings.bsy_led])))
 
         # DCS.NR
         opts = ["TR/X N", "RX R", "TX R", "T/RX R"]
         ctcss.append(RadioSetting("dcs_nr", "\"Inverted\" DCS Code Decoding",
-                     RadioSettingValueList(opts, opts[_settings.dcs_nr])))
+                                  RadioSettingValueList(opts, opts[_settings.dcs_nr])))
 
         # DT.DLY
         opts = ["50 MS", "100 MS", "250 MS", "450 MS", "750 MS", "1000 MS"]
         ctcss.append(RadioSetting("dt_dly", "DTMF Autodialer Delay Time",
-                     RadioSettingValueList(opts, opts[_settings.dt_dly])))
+                                  RadioSettingValueList(opts, opts[_settings.dt_dly])))
 
         # DT.SPD
         opts = ["50 MS", "100 MS"]
         ctcss.append(RadioSetting("dt_spd", "DTMF Autodialer Sending Speed",
-                     RadioSettingValueList(opts, opts[_settings.dt_spd])))
+                                  RadioSettingValueList(opts, opts[_settings.dt_spd])))
 
         # DT.WRT
         for i in range(0, 9):
@@ -518,8 +520,9 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
                     str += DTMF_CHARS[c]
             val = RadioSettingValueString(0, 16, str, False)
             val.set_charset(DTMF_CHARS + list("abcd"))
-            rs = RadioSetting("dtmf_%i" % i, 
-                        "DTMF Autodialer Memory %i" % (i + 1), val)
+            rs = RadioSetting("dtmf_%i" % i,
+                              "DTMF Autodialer Memory %i" % (i + 1), val)
+
             def apply_dtmf(s, obj):
                 str = s.value.get_value().upper().rstrip()
                 val = [DTMF_CHARS.index(x) for x in str]
@@ -532,7 +535,7 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
         # EDG.BEP
         opts = ["OFF", "ON"]
         misc.append(RadioSetting("edg_bep", "Band Edge Beeper",
-                    RadioSettingValueList(opts, opts[_settings.edg_bep])))
+                                 RadioSettingValueList(opts, opts[_settings.edg_bep])))
 
         # I.NET
         opts = ["OFF", "COD", "MEM"]
@@ -550,18 +553,18 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
                 "CODE 5", "CODE 6", "CODE 7", "CODE 8", "CODE 9",
                 "CODE A", "CODE B", "CODE C", "CODE D", "CODE E", "CODE F"]
         wires.append(RadioSetting("int_cd", "Access Number for WiRES(TM)",
-                     RadioSettingValueList(opts, opts[_settings.int_cd])))
+                                  RadioSettingValueList(opts, opts[_settings.int_cd])))
 
         # INT.MR
         opts = ["d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"]
         wires.append(RadioSetting(
-                    "int_mr", "Access Number (DTMF) for Non-WiRES(TM)",
+            "int_mr", "Access Number (DTMF) for Non-WiRES(TM)",
                      RadioSettingValueList(opts, opts[_settings.int_mr])))
 
         # LAMP
         opts = ["KEY", "5SEC", "TOGGLE"]
         switch.append(RadioSetting("lamp", "Lamp Mode",
-                      RadioSettingValueList(opts, opts[_settings.lamp])))
+                                   RadioSettingValueList(opts, opts[_settings.lamp])))
 
         # LOCK
         opts = ["LK KEY", "LKDIAL", "LK K+D", "LK PTT",
@@ -578,44 +581,44 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
         # M/T-CL
         opts = ["MONI", "T-CALL"]
         switch.append(RadioSetting("mt_cl", "MONI Switch Function",
-                      RadioSettingValueList(opts, opts[_settings.mt_cl])))
+                                   RadioSettingValueList(opts, opts[_settings.mt_cl])))
 
         # PAG.ABK
         opts = ["OFF", "ON"]
         eai.append(RadioSetting("pag_abk", "Paging Answer Back",
-                   RadioSettingValueList(opts, opts[_settings.pag_abk])))
+                                RadioSettingValueList(opts, opts[_settings.pag_abk])))
 
         # RESUME
         opts = ["TIME", "HOLD", "BUSY"]
         scan.append(RadioSetting("resume", "Scan Resume Mode",
-                    RadioSettingValueList(opts, opts[_settings.resume])))
+                                 RadioSettingValueList(opts, opts[_settings.resume])))
 
         # REV/HM
         opts = ["REV", "HOME"]
         switch.append(RadioSetting("rev_hm", "HM/RV Key Function",
-                      RadioSettingValueList(opts, opts[_settings.rev_hm])))
+                                   RadioSettingValueList(opts, opts[_settings.rev_hm])))
 
         # RF.SQL
         opts = ["OFF", "S-1", "S-2", "S-3", "S-4", "S-5", "S-6",
                 "S-7", "S-8", "S-FULL"]
         misc.append(RadioSetting("rf_sql", "RF Squelch Threshold",
-                    RadioSettingValueList(opts, opts[_settings.rf_sql])))
+                                 RadioSettingValueList(opts, opts[_settings.rf_sql])))
 
         # PRI.RVT
         opts = ["OFF", "ON"]
         scan.append(RadioSetting("pri_rvt", "Priority Revert",
-                    RadioSettingValueList(opts, opts[_settings.pri_rvt])))
+                                 RadioSettingValueList(opts, opts[_settings.pri_rvt])))
 
         # RXSAVE
         opts = ["OFF", "200 MS", "300 MS", "500 MS", "1 S", "2 S"]
         power.append(RadioSetting(
-                    "rxsave", "Receive Mode Batery Savery Interval",
+            "rxsave", "Receive Mode Batery Savery Interval",
                      RadioSettingValueList(opts, opts[_settings.rxsave])))
 
         # S.SRCH
         opts = ["SINGLE", "CONT"]
         misc.append(RadioSetting("ssrch", "Smart Search Sweep Mode",
-                    RadioSettingValueList(opts, opts[_settings.ssrch])))
+                                 RadioSettingValueList(opts, opts[_settings.ssrch])))
 
         # SCN.MD
         opts = ["MEM", "ONLY"]
@@ -626,32 +629,32 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
         # SCN.LMP
         opts = ["OFF", "ON"]
         scan.append(RadioSetting("scn_lmp", "Scan Lamp",
-                    RadioSettingValueList(opts, opts[_settings.scn_lmp])))
+                                 RadioSettingValueList(opts, opts[_settings.scn_lmp])))
 
         # TOT
-        opts = ["OFF"] + ["%dMIN" % (x) for x in range(1, 30+1)]
+        opts = ["OFF"] + ["%dMIN" % (x) for x in range(1, 30 + 1)]
         misc.append(RadioSetting("tot", "Timeout Timer",
-                    RadioSettingValueList(opts, opts[_settings.tot])))
+                                 RadioSettingValueList(opts, opts[_settings.tot])))
 
         # TX.LED
         opts = ["ON", "OFF"]
         misc.append(RadioSetting("tx_led", "TX LED",
-                    RadioSettingValueList(opts, opts[_settings.tx_led])))
+                                 RadioSettingValueList(opts, opts[_settings.tx_led])))
 
         # TXSAVE
         opts = ["OFF", "ON"]
         power.append(RadioSetting("txsave", "Transmitter Battery Saver",
-                     RadioSettingValueList(opts, opts[_settings.txsave])))
+                                  RadioSettingValueList(opts, opts[_settings.txsave])))
 
         # VFO.BND
         opts = ["BAND", "ALL"]
         misc.append(RadioSetting("vfo_bnd", "VFO Band Edge Limiting",
-                    RadioSettingValueList(opts, opts[_settings.vfo_bnd])))
+                                 RadioSettingValueList(opts, opts[_settings.vfo_bnd])))
 
         # WX.ALT
         opts = ["OFF", "ON"]
         scan.append(RadioSetting("wx_alt", "Weather Alert Scan",
-                    RadioSettingValueList(opts, opts[_settings.wx_alt])))
+                                 RadioSettingValueList(opts, opts[_settings.wx_alt])))
 
         # MBS
         for i in range(0, 10):
diff --git a/chirp/drivers/ft817.py b/chirp/drivers/ft817.py
index 3016538..28cd2a2 100644
--- a/chirp/drivers/ft817.py
+++ b/chirp/drivers/ft817.py
@@ -357,10 +357,10 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
             moredata = self.pipe.read(2)
             if moredata:
                 raise Exception(
-                        _("Radio sent data after the last awaited block, "
-                            "this happens when the selected model is a non-US "
-                            "but the radio is a US one. "
-                            "Please choose the correct model and try again."))
+                    _("Radio sent data after the last awaited block, "
+                      "this happens when the selected model is a non-US "
+                      "but the radio is a US one. "
+                      "Please choose the correct model and try again."))
 
         LOG.info("Clone completed in %i seconds" % (time.time() - start))
 
@@ -389,7 +389,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
                 LOG.debug(util.hexprint(chr(blocks)))
                 LOG.debug(util.hexprint(self.get_mmap()[pos:pos + block]))
                 LOG.debug(util.hexprint(chr(checksum.get_calculated(
-                            self.get_mmap()))))
+                    self.get_mmap()))))
                 self.pipe.write(chr(blocks))
                 self.pipe.write(self.get_mmap()[pos:pos + block])
                 self.pipe.write(chr(checksum.get_calculated(self.get_mmap())))
@@ -584,7 +584,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
             if key != "extd_number":
                 if cur_mem.__dict__[key] != mem.__dict__[key]:
                     raise errors.RadioError("Editing field `%s' " % key +
-                                        "is not supported on this channel")
+                                            "is not supported on this channel")
 
         self._set_memory(mem, _mem)
 
@@ -746,7 +746,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         if mem.freq >= lo and mem.freq <= hi:
             if mem.mode != "FM":
                 msgs.append(chirp_common.ValidationError(
-                        "Only FM is supported in this band"))
+                    "Only FM is supported in this band"))
         # TODO check that step is valid in current mode
         return msgs
 
@@ -782,7 +782,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
                           RadioSettingValueList(options,
                                                 options[
                                                     _settings.disable_amfm_dial
-                                                    ]))
+                                                ]))
         panel.append(rs)
         rs = RadioSetting("am_mic", "AM mic level",
                           RadioSettingValueInteger(0, 100, _settings.am_mic))
@@ -1007,7 +1007,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         antenna.append(rs)
 
         st = RadioSettingValueString(0, 7, ''.join([self._CALLSIGN_CHARSET[x]
-                                     for x in self._memobj.callsign]))
+                                                    for x in self._memobj.callsign]))
         st.set_charset(self._CALLSIGN_CHARSET)
         rs = RadioSetting("callsign", "Callsign", st)
         cw.append(rs)
@@ -1042,7 +1042,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
                           RadioSettingValueList(options,
                                                 options[
                                                     _settings.pwr_meter_mode
-                                                        ]))
+                                                ]))
         panelcontr.append(rs)
         rs = RadioSetting("vox", "Vox",
                           RadioSettingValueBoolean(_settings.vox))
@@ -1084,7 +1084,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
                     setting = element.get_name()
                 try:
                     LOG.debug("Setting %s(%s) <= %s" % (setting,
-                              getattr(obj, setting), element.value))
+                                                        getattr(obj, setting), element.value))
                 except AttributeError:
                     LOG.debug("Setting %s <= %s" % (setting, element.value))
                 if setting == "contrast":
@@ -1130,7 +1130,7 @@ class FT817NDUSRadio(FT817Radio):
         "M-603": -40,
         "M-604": -39,
         "M-605": -38,
-        }
+    }
     LAST_SPECIAL60M_INDEX = -42
 
     SPECIAL_MEMORIES = dict(FT817Radio.SPECIAL_MEMORIES)
diff --git a/chirp/drivers/gmrsuv1.py b/chirp/drivers/gmrsuv1.py
new file mode 100644
index 0000000..ea197c6
--- /dev/null
+++ b/chirp/drivers/gmrsuv1.py
@@ -0,0 +1,828 @@
+# Copyright 2016:
+# * Jim Unroe KC9HI, <rock.unroe at gmail.com>
+#
+# 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 2 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
+
+LOG = logging.getLogger(__name__)
+
+from chirp.drivers import baofeng_common
+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
+
+##### MAGICS #########################################################
+
+# BTECH GMRS-V1 magic string
+MSTRING_GMRSV1 = "\x50\x5F\x20\x15\x12\x15\x4D"
+
+##### ID strings #####################################################
+
+# BTECH GMRS-V1
+GMRSV1_fp1 = "US32411"
+
+DTMF_CHARS = "0123456789 *#ABCD"
+STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0]
+
+LIST_AB = ["A", "B"]
+LIST_ALMOD = ["Off", "Site", "Tone", "Code"]
+LIST_BANDWIDTH = ["Wide", "Narrow"]
+LIST_COLOR = ["Off", "Blue", "Orange", "Purple"]
+LIST_DTMFSPEED = ["%s ms" % x for x in range(50, 2010, 10)]
+LIST_DTMFST = ["Off", "DT-ST", "ANI-ST", "DT+ANI"]
+LIST_MODE = ["Channel", "Name", "Frequency"]
+LIST_OFF1TO9 = ["Off"] + list("123456789")
+LIST_OFF1TO10 = LIST_OFF1TO9 + ["10"]
+LIST_OFFAB = ["Off"] + LIST_AB
+LIST_RESUME = ["TO", "CO", "SE"]
+LIST_PONMSG = ["Full", "Message"]
+LIST_PTTID = ["Off", "BOT", "EOT", "Both"]
+LIST_SCODE = ["%s" % x for x in range(1, 16)]
+LIST_RPSTE = ["Off"] + ["%s" % x for x in range(1, 11)]
+LIST_RTONE = ["1000 Hz", "1450 Hz", "1750 Hz", "2100 Hz"]
+LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"]
+LIST_SHIFTD = ["Off", "+", "-"]
+LIST_STEDELAY = ["Off"] + ["%s ms" % x for x in range(100, 1100, 100)]
+LIST_STEP = [str(x) for x in STEPS]
+LIST_TIMEOUT = ["%s sec" % x for x in range(15, 615, 15)]
+LIST_TXPOWER = ["High", "Low"]
+LIST_VOICE = ["Off", "English", "Chinese"]
+LIST_WORKMODE = ["Frequency", "Channel"]
+
+def model_match(cls, data):
+    """Match the opened/downloaded image to the correct version"""
+    rid = data[0x1EF0:0x1EF7]
+
+    if rid in cls._fileid:
+        return True
+
+    return False
+
+
+class MyRadioFeatures(chirp_common.RadioFeatures):
+    def validate_memory(self, mem):
+        # Run the normal validation
+        msgs = chirp_common.RadioFeatures.validate_memory(self, mem)
+
+        # Run my validation
+        if mem.number <= 6 and mem.mode != "NFM":
+            msgs.append(chirp_common.ValidationError(
+                        'Only NFM is supported on this channel'))
+
+        return msgs
+
+
+ at directory.register
+class GMRSV1(baofeng_common.BaofengCommonHT):
+    """BTech GMRS-V1"""
+    VENDOR = "BTECH"
+    MODEL = "GMRS-V1"
+
+    _fileid = [GMRSV1_fp1, ]
+
+    _magic = [MSTRING_GMRSV1, ]
+    _magic_response_length = 8
+    _fw_ver_start = 0x1EF0
+    _recv_block_size = 0x40
+    _mem_size = 0x2000
+    _ack_block = True
+
+    _ranges = [(0x0000, 0x0DF0),
+               (0x0E00, 0x1800),
+               (0x1EE0, 0x1EF0),
+               (0x1F60, 0x1F70),
+               (0x1F80, 0x1F90),
+               (0x1FC0, 0x1FD0)]
+    _send_block_size = 0x10
+
+    MODES = ["NFM", "FM"]
+    VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
+        "!@#$%^&*()+-=[]:\";'<>?,./"
+    LENGTH_NAME = 7
+    SKIP_VALUES = ["", "S"]
+    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
+    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
+                    chirp_common.PowerLevel("Low", watts=2.00)]
+    VALID_BANDS = [(130000000, 180000000),
+                   (400000000, 521000000)]
+    PTTID_LIST = LIST_PTTID
+    SCODE_LIST = LIST_SCODE
+
+
+    def get_features(self):
+        """Get the radio's features"""
+
+        rf = MyRadioFeatures()
+        rf.has_settings = True
+        rf.has_bank = False
+        rf.has_tuning_step = False
+        rf.can_odd_split = False
+        rf.has_name = True
+        rf.has_offset = False
+        rf.has_mode = True
+        rf.has_dtcs = True
+        rf.has_rx_dtcs = True
+        rf.has_dtcs_polarity = True
+        rf.has_ctone = True
+        rf.has_cross = True
+        rf.valid_modes = self.MODES
+        rf.valid_characters = self.VALID_CHARS
+        rf.valid_name_length = self.LENGTH_NAME
+        rf.valid_duplexes = []
+        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
+        rf.valid_cross_modes = [
+            "Tone->Tone",
+            "DTCS->",
+            "->DTCS",
+            "Tone->DTCS",
+            "DTCS->Tone",
+            "->Tone",
+            "DTCS->DTCS"]
+        rf.valid_skips = self.SKIP_VALUES
+        rf.valid_dtcs_codes = self.DTCS_CODES
+        rf.memory_bounds = (0, 127)
+        rf.valid_power_levels = self.POWER_LEVELS
+        rf.valid_bands = self.VALID_BANDS
+
+        return rf
+
+
+    MEM_FORMAT = """
+    #seekto 0x0000;
+    struct {
+      lbcd rxfreq[4];
+      lbcd txfreq[4];
+      ul16 rxtone;
+      ul16 txtone;
+      u8 unknown0:4,
+         scode:4;
+      u8 unknown1;
+      u8 unknown2:7,
+         lowpower:1;
+      u8 unknown3:1,
+         wide:1,
+         unknown4:2,
+         bcl:1,
+         scan:1,
+         pttid:2;
+    } memory[128];
+
+    #seekto 0x0B00;
+    struct {
+      u8 code[5];
+      u8 unused[11];
+    } pttid[15];
+    
+    #seekto 0x0CAA;
+    struct {
+      u8 code[5];      
+      u8 unused1:6,    
+         aniid:2;      
+      u8 unknown[2];   
+      u8 dtmfon;       
+      u8 dtmfoff;      
+    } ani;             
+                       
+    #seekto 0x0E20;    
+    struct {           
+      u8 unused01:4,   
+         squelch:4;    
+      u8 unused02;     
+      u8 unused03;     
+      u8 unused04:5,   
+         save:3;       
+      u8 unused05:4,   
+         vox:4;        
+      u8 unused06;     
+      u8 unused07:4,   
+         abr:4;        
+      u8 unused08:7,   
+         tdr:1;        
+      u8 unused09:7,   
+         beep:1;       
+      u8 unused10:2,   
+         timeout:6;    
+      u8 unused11[4];  
+      u8 unused12:6,   
+         voice:2;      
+      u8 unused13;     
+      u8 unused14:6,   
+         dtmfst:2;     
+      u8 unused15;     
+      u8 unused16:6,   
+         screv:2;      
+      u8 unused17:6,   
+         pttid:2;      
+      u8 unused18:2,   
+         pttlt:6;      
+      u8 unused19:6,   
+         mdfa:2;       
+      u8 unused20:6,   
+         mdfb:2;       
+      u8 unused21;     
+      u8 unused22:7,   
+         sync:1;       
+      u8 unused23[4];  
+      u8 unused24:6,   
+         wtled:2;      
+      u8 unused25:6,   
+         rxled:2;      
+      u8 unused26:6,   
+         txled:2;      
+      u8 unused27:6,   
+         almod:2;      
+      u8 unused28:7,   
+         dbptt:1;      
+      u8 unused29:6,   
+         tdrab:2;      
+      u8 unused30:7,   
+         ste:1;        
+      u8 unused31:4,   
+         rpste:4;      
+      u8 unused32:4,   
+         rptrl:4;      
+      u8 unused33:7,   
+         ponmsg:1;     
+      u8 unused34:7,   
+         roger:1;      
+      u8 unused35:6,   
+         rtone:2;      
+      u8 unused36;     
+      u8 unused37:6,   
+         rogerrx:2;    
+      u8 unused38;     
+      u8 displayab:1,  
+         unknown1:2,   
+         fmradio:1,    
+         alarm:1,      
+         unknown2:1,   
+         reset:1,      
+         menu:1;       
+      u8 unused39;     
+      u8 workmode;     
+      u8 keylock;      
+      u8 cht;          
+    } settings;        
+                       
+    #seekto 0x0E76;    
+    struct {           
+      u8 unused1:1,    
+         mrcha:7;      
+      u8 unused2:1,    
+         mrchb:7;      
+    } wmchannel;       
+                       
+    struct vfo {       
+      u8 unknown0[8];  
+      u8 freq[8];      
+      u8 unknown1;     
+      u8 offset[4];    
+      u8 unknown2;     
+      ul16 rxtone;     
+      ul16 txtone;     
+      u8 unused1:7,    
+         band:1;       
+      u8 unknown3;     
+      u8 unused2:2,    
+         sftd:2,       
+         scode:4;      
+      u8 unknown4;     
+      u8 unused3:1     
+         step:3,       
+         unused4:4;    
+      u8 txpower:1,    
+         widenarr:1,   
+         unknown5:4,   
+         txpower3:2;   
+    };                 
+                       
+    #seekto 0x0F00;    
+    struct {           
+      struct vfo a;
+      struct vfo b;
+    } vfo;
+    
+    #seekto 0x0F4E;
+    u16 fm_presets;
+    
+    #seekto 0x1000;
+    struct {
+      char name[7];
+      u8 unknown1[9];
+    } names[128];
+    
+    #seekto 0x1ED0;
+    struct {
+      char line1[7];
+      char line2[7];
+    } sixpoweron_msg;
+    
+    #seekto 0x1EE0;
+    struct {
+      char line1[7];
+      char line2[7];
+    } poweron_msg;
+    
+    #seekto 0x1EF0;
+    struct {
+      char line1[7];
+      char line2[7];
+    } firmware_msg;
+
+    struct squelch {
+      u8 sql0;
+      u8 sql1;
+      u8 sql2;
+      u8 sql3;
+      u8 sql4;
+      u8 sql5;
+      u8 sql6;
+      u8 sql7;
+      u8 sql8;
+      u8 sql9;
+    };
+    
+    #seekto 0x1F60;
+    struct {
+      struct squelch vhf;
+      u8 unknown1[6];
+      u8 unknown2[16];
+      struct squelch uhf;
+    } squelch;
+
+    """
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.experimental = \
+            ('The BTech GMRS-V1 driver is a beta version.\n'
+             '\n'
+             'Please save an unedited copy of your first successful\n'
+             'download to a CHIRP Radio Images(*.img) file.'
+             )
+        rp.pre_download = _(dedent("""\
+            Follow these instructions to download your info:
+
+            1 - Turn off your radio
+            2 - Connect your interface cable
+            3 - Turn on your radio
+            4 - Do the download of your radio data
+            """))
+        rp.pre_upload = _(dedent("""\
+            Follow this instructions to upload your info:
+
+            1 - Turn off your radio
+            2 - Connect your interface cable
+            3 - Turn on your radio
+            4 - Do the upload of your radio data
+            """))
+        return rp
+
+    def process_mmap(self):
+        """Process the mem map into the mem object"""
+        self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap)
+
+    def get_settings(self):
+        """Translate the bit in the mem_struct into settings in the UI"""
+        _mem = self._memobj
+        basic = RadioSettingGroup("basic", "Basic Settings")
+        advanced = RadioSettingGroup("advanced", "Advanced Settings")
+        other = RadioSettingGroup("other", "Other Settings")
+        work = RadioSettingGroup("work", "Work Mode Settings")
+        fm_preset = RadioSettingGroup("fm_preset", "FM Preset")
+        dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings")
+        service = RadioSettingGroup("service", "Service Settings")
+        top = RadioSettings(basic, advanced, other, work, fm_preset, dtmfe,
+                            service)
+
+        # Basic settings
+        if _mem.settings.squelch > 0x09:
+            val = 0x00
+        else:
+            val = _mem.settings.squelch
+        rs = RadioSetting("settings.squelch", "Squelch",
+                          RadioSettingValueList(
+                              LIST_OFF1TO9, LIST_OFF1TO9[val]))
+        basic.append(rs)
+
+        if _mem.settings.save > 0x04:
+            val = 0x00
+        else:
+            val = _mem.settings.save
+        rs = RadioSetting("settings.save", "Battery Saver",
+                          RadioSettingValueList(
+                              LIST_SAVE, LIST_SAVE[val]))
+        basic.append(rs)
+
+        if _mem.settings.vox > 0x0A:
+            val = 0x00
+        else:
+            val = _mem.settings.vox
+        rs = RadioSetting("settings.vox", "Vox",
+                          RadioSettingValueList(
+                              LIST_OFF1TO10, LIST_OFF1TO10[val]))
+        basic.append(rs)
+
+        if _mem.settings.abr > 0x0A:
+            val = 0x00
+        else:
+            val = _mem.settings.abr
+        rs = RadioSetting("settings.abr", "Backlight Timeout",
+                          RadioSettingValueList(
+                              LIST_OFF1TO10, LIST_OFF1TO10[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.tdr", "Dual Watch",
+                          RadioSettingValueBoolean(_mem.settings.tdr))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.beep", "Beep",
+                           RadioSettingValueBoolean(_mem.settings.beep))
+        basic.append(rs)
+
+        if _mem.settings.timeout > 0x27:
+            val = 0x03
+        else:
+            val = _mem.settings.timeout
+        rs = RadioSetting("settings.timeout", "Timeout Timer",
+                          RadioSettingValueList(
+                              LIST_TIMEOUT, LIST_TIMEOUT[val]))
+        basic.append(rs)
+
+        if _mem.settings.voice > 0x02:
+            val = 0x01
+        else:
+            val = _mem.settings.voice
+        rs = RadioSetting("settings.voice", "Voice Prompt",
+                          RadioSettingValueList(
+                              LIST_VOICE, LIST_VOICE[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.dtmfst", "DTMF Sidetone",
+                          RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
+                              _mem.settings.dtmfst]))
+        basic.append(rs)
+
+        if _mem.settings.screv > 0x02:
+            val = 0x01
+        else:
+            val = _mem.settings.screv
+        rs = RadioSetting("settings.screv", "Scan Resume",
+                          RadioSettingValueList(
+                              LIST_RESUME, LIST_RESUME[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.pttid", "When to send PTT ID",
+                          RadioSettingValueList(LIST_PTTID, LIST_PTTID[
+                              _mem.settings.pttid]))
+        basic.append(rs)
+
+        if _mem.settings.pttlt > 0x1E:
+            val = 0x05
+        else:
+            val = _mem.settings.pttlt
+        rs = RadioSetting("pttlt", "PTT ID Delay",
+                          RadioSettingValueInteger(0, 50, val))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.mdfa", "Display Mode (A)",
+                          RadioSettingValueList(LIST_MODE, LIST_MODE[
+                              _mem.settings.mdfa]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.mdfb", "Display Mode (B)",
+                          RadioSettingValueList(LIST_MODE, LIST_MODE[
+                              _mem.settings.mdfb]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.sync", "Sync A & B",
+                          RadioSettingValueBoolean(_mem.settings.sync))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.wtled", "Standby LED Color",
+                          RadioSettingValueList(
+                              LIST_COLOR, LIST_COLOR[_mem.settings.wtled]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.rxled", "RX LED Color",
+                          RadioSettingValueList(
+                              LIST_COLOR, LIST_COLOR[_mem.settings.rxled]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.txled", "TX LED Color",
+                          RadioSettingValueList(
+                              LIST_COLOR, LIST_COLOR[_mem.settings.txled]))
+        basic.append(rs)
+
+        val = _mem.settings.almod
+        rs = RadioSetting("settings.almod", "Alarm Mode",
+                          RadioSettingValueList(
+                              LIST_ALMOD, LIST_ALMOD[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.dbptt", "Double PTT",
+                          RadioSettingValueBoolean(_mem.settings.dbptt))
+        basic.append(rs)
+
+        if _mem.settings.tdrab > 0x02:
+            val = 0x00
+        else:
+            val = _mem.settings.tdrab
+        rs = RadioSetting("settings.tdrab", "Dual Watch TX Priority",
+                          RadioSettingValueList(
+                              LIST_OFFAB, LIST_OFFAB[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.ste", "Squelch Tail Eliminate (HT to HT)",
+                          RadioSettingValueBoolean(_mem.settings.ste))
+        basic.append(rs)
+
+        if _mem.settings.rpste > 0x0A:
+            val = 0x00
+        else:
+            val = _mem.settings.rpste
+        rs = RadioSetting("settings.rpste",
+                          "Squelch Tail Eliminate (repeater)",
+                              RadioSettingValueList(
+                              LIST_RPSTE, LIST_RPSTE[val]))
+        basic.append(rs)
+
+        if _mem.settings.rptrl > 0x0A:
+            val = 0x00
+        else:
+            val = _mem.settings.rptrl
+        rs = RadioSetting("settings.rptrl", "STE Repeater Delay",
+                          RadioSettingValueList(
+                              LIST_STEDELAY, LIST_STEDELAY[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.ponmsg", "Power-On Message",
+                          RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
+                              _mem.settings.ponmsg]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.roger", "Roger Beep",
+                          RadioSettingValueBoolean(_mem.settings.roger))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.rtone", "Tone Burst Frequency",
+                          RadioSettingValueList(LIST_RTONE, LIST_RTONE[
+                              _mem.settings.rtone]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.rogerrx", "Roger Beep (RX)",
+                          RadioSettingValueList(
+                             LIST_OFFAB, LIST_OFFAB[
+                             _mem.settings.rogerrx]))
+        basic.append(rs)
+
+        # Advanced settings
+        rs = RadioSetting("settings.reset", "RESET Menu",
+                          RadioSettingValueBoolean(_mem.settings.reset))
+        advanced.append(rs)
+
+        rs = RadioSetting("settings.menu", "All Menus",
+                          RadioSettingValueBoolean(_mem.settings.menu))
+        advanced.append(rs)
+
+        rs = RadioSetting("settings.fmradio", "Broadcast FM Radio",
+                          RadioSettingValueBoolean(_mem.settings.fmradio))
+        advanced.append(rs)
+
+        rs = RadioSetting("settings.alarm", "Alarm Sound",
+                          RadioSettingValueBoolean(_mem.settings.alarm))
+        advanced.append(rs)
+
+        # Other settings
+        def _filter(name):
+            filtered = ""
+            for char in str(name):
+                if char in chirp_common.CHARSET_ASCII:
+                    filtered += char
+                else:
+                    filtered += " "
+            return filtered
+
+        _msg = _mem.firmware_msg
+        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
+        val.set_mutable(False)
+        rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
+        other.append(rs)
+
+        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
+        val.set_mutable(False)
+        rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
+        other.append(rs)
+
+        _msg = _mem.sixpoweron_msg
+        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
+        val.set_mutable(False)
+        rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", val)
+        other.append(rs)
+        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
+        val.set_mutable(False)
+        rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", val)
+        other.append(rs)
+
+        _msg = _mem.poweron_msg
+        rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
+                          RadioSettingValueString(
+                              0, 7, _filter(_msg.line1)))
+        other.append(rs)
+        rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
+                          RadioSettingValueString(
+                              0, 7, _filter(_msg.line2)))
+        other.append(rs)
+
+        # Work mode settings
+        rs = RadioSetting("settings.displayab", "Display",
+                          RadioSettingValueList(
+                              LIST_AB, LIST_AB[_mem.settings.displayab]))
+        work.append(rs)
+
+        rs = RadioSetting("settings.workmode", "VFO/MR Mode",
+                          RadioSettingValueList(
+                              LIST_WORKMODE,
+                              LIST_WORKMODE[_mem.settings.workmode]))
+        work.append(rs)
+
+        rs = RadioSetting("settings.keylock", "Keypad Lock",
+                          RadioSettingValueBoolean(_mem.settings.keylock))
+        work.append(rs)
+
+        rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
+                          RadioSettingValueInteger(0, 127,
+                                                      _mem.wmchannel.mrcha))
+        work.append(rs)
+
+        rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
+                          RadioSettingValueInteger(0, 127,
+                                                      _mem.wmchannel.mrchb))
+        work.append(rs)
+
+        def convert_bytes_to_freq(bytes):
+            real_freq = 0
+            for byte in bytes:
+                real_freq = (real_freq * 10) + byte
+            return chirp_common.format_freq(real_freq * 10)
+
+        def my_validate(value):
+            value = chirp_common.parse_freq(value)
+            msg = ("Can't be less than %i.0000")
+            if value > 99000000 and value < 130 * 1000000:
+                raise InvalidValueError(msg % (130))
+            msg = ("Can't be between %i.9975-%i.0000")
+            if (179 + 1) * 1000000 <= value and value < 400 * 1000000:
+                raise InvalidValueError(msg % (179, 400))
+            msg = ("Can't be greater than %i.9975")
+            if value > 99000000 and value > (520 + 1) * 1000000:
+                raise InvalidValueError(msg % (520))
+            return chirp_common.format_freq(value)
+
+        def apply_freq(setting, obj):
+            value = chirp_common.parse_freq(str(setting.value)) / 10
+            for i in range(7, -1, -1):
+                obj.freq[i] = value % 10
+                value /= 10
+
+        val1a = RadioSettingValueString(0, 10,
+                                        convert_bytes_to_freq(_mem.vfo.a.freq))
+        val1a.set_validate_callback(my_validate)
+        rs = RadioSetting("vfo.a.freq", "VFO A Frequency", val1a)
+        rs.set_apply_callback(apply_freq, _mem.vfo.a)
+        work.append(rs)
+
+        val1b = RadioSettingValueString(0, 10,
+                                        convert_bytes_to_freq(_mem.vfo.b.freq))
+        val1b.set_validate_callback(my_validate)
+        rs = RadioSetting("vfo.b.freq", "VFO B Frequency", val1b)
+        rs.set_apply_callback(apply_freq, _mem.vfo.b)
+        work.append(rs)
+
+        rs = RadioSetting("vfo.a.step", "VFO A Tuning Step",
+                          RadioSettingValueList(
+                              LIST_STEP, LIST_STEP[_mem.vfo.a.step]))
+        work.append(rs)
+        rs = RadioSetting("vfo.b.step", "VFO B Tuning Step",
+                          RadioSettingValueList(
+                              LIST_STEP, LIST_STEP[_mem.vfo.b.step]))
+        work.append(rs)
+
+        # broadcast FM settings
+        _fm_presets = self._memobj.fm_presets
+        if _fm_presets <= 108.0 * 10 - 650:
+            preset = _fm_presets / 10.0 + 65
+        elif _fm_presets >= 65.0 * 10 and _fm_presets <= 108.0 * 10:
+            preset = _fm_presets / 10.0
+        else:
+            preset = 76.0
+        rs = RadioSetting("fm_presets", "FM Preset(MHz)",
+                          RadioSettingValueFloat(65, 108.0, preset, 0.1, 1))
+        fm_preset.append(rs)
+
+        # DTMF settings
+        def apply_code(setting, obj, length):
+            code = []
+            for j in range(0, length):
+                try:
+                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
+                except IndexError:
+                    code.append(0xFF)
+            obj.code = code
+
+        for i in range(0, 15):
+            _codeobj = self._memobj.pttid[i].code
+            _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+            val = RadioSettingValueString(0, 5, _code, False)
+            val.set_charset(DTMF_CHARS)
+            pttid = RadioSetting("pttid/%i.code" % i,
+                                 "Signal Code %i" % (i + 1), val)
+            pttid.set_apply_callback(apply_code, self._memobj.pttid[i], 5)
+            dtmfe.append(pttid)
+
+        if _mem.ani.dtmfon > 0xC3:
+            val = 0x03
+        else:
+            val = _mem.ani.dtmfon
+        rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
+                          RadioSettingValueList(LIST_DTMFSPEED,
+                                                LIST_DTMFSPEED[val]))
+        dtmfe.append(rs)
+
+        if _mem.ani.dtmfoff > 0xC3:
+            val = 0x03
+        else:
+            val = _mem.ani.dtmfoff
+        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
+                          RadioSettingValueList(LIST_DTMFSPEED,
+                                                LIST_DTMFSPEED[val]))
+        dtmfe.append(rs)
+
+        _codeobj = self._memobj.ani.code
+        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+        val = RadioSettingValueString(0, 5, _code, False)
+        val.set_charset(DTMF_CHARS)
+        rs = RadioSetting("ani.code", "ANI Code", val)
+        rs.set_apply_callback(apply_code, self._memobj.ani, 5)
+        dtmfe.append(rs)
+
+        rs = RadioSetting("ani.aniid", "When to send ANI ID",
+                          RadioSettingValueList(LIST_PTTID,
+                                                LIST_PTTID[_mem.ani.aniid]))
+        dtmfe.append(rs)
+
+        # Service settings
+        for band in ["vhf", "uhf"]:
+            for index in range(0, 10):
+                key = "squelch.%s.sql%i" % (band, index)
+                if band == "vhf":
+                    _obj = self._memobj.squelch.vhf
+                elif band == "uhf":
+                    _obj = self._memobj.squelch.uhf
+                val = RadioSettingValueInteger(0, 123,
+                          getattr(_obj, "sql%i" % (index)))
+                if index == 0:
+                    val.set_mutable(False)
+                name = "%s Squelch %i" % (band.upper(), index)
+                rs = RadioSetting(key, name, val)
+                service.append(rs)
+
+        return top
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        match_size = False
+        match_model = False
+
+        # testing the file data size
+        if len(filedata) == 0x2008:
+            match_size = True
+
+        # testing the firmware model fingerprint
+        match_model = model_match(cls, filedata)
+
+        if match_size and match_model:
+            return True
+        else:
+            return False
diff --git a/chirp/drivers/h777.py b/chirp/drivers/h777.py
index d48d34c..48c657f 100644
--- a/chirp/drivers/h777.py
+++ b/chirp/drivers/h777.py
@@ -91,7 +91,7 @@ SCANMODE_LIST = ["Carrier", "Time"]
 
 SETTING_LISTS = {
     "voice": VOICE_LIST,
-    }
+}
 
 
 def _h777_enter_programming_mode(radio):
@@ -250,7 +250,7 @@ class H777Radio(chirp_common.CloneModeRadio):
         (0x0000, 0x0110),
         (0x02B0, 0x02C0),
         (0x0380, 0x03E0),
-        ]
+    ]
     _memsize = 0x03E0
 
     def get_features(self):
@@ -259,6 +259,8 @@ class H777Radio(chirp_common.CloneModeRadio):
         rf.valid_modes = ["NFM", "FM"]  # 12.5 KHz, 25 kHz.
         rf.valid_skips = ["", "S"]
         rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
+        rf.valid_duplexes = ["", "-", "+", "split", "off"]
+        rf.can_odd_split = True
         rf.has_rx_dtcs = True
         rf.has_ctone = True
         rf.has_cross = True
@@ -334,7 +336,10 @@ class H777Radio(chirp_common.CloneModeRadio):
             mem.empty = True
             return mem
 
-        if int(_mem.rxfreq) == int(_mem.txfreq):
+        if _mem.txfreq.get_raw() == "\xFF\xFF\xFF\xFF":
+            mem.duplex = "off"
+            mem.offset = 0
+        elif int(_mem.rxfreq) == int(_mem.txfreq):
             mem.duplex = ""
             mem.offset = 0
         else:
@@ -394,6 +399,13 @@ class H777Radio(chirp_common.CloneModeRadio):
             # NOTE: Only two settings right now, both are inverted
             setattr(_mem, setting.get_name(), not int(setting.value))
 
+        # When set to one, official programming software (BF-480) shows always
+        # "WFM", even if we choose "NFM". Therefore, for compatibility
+        # purposes, we will set these to zero.
+        _mem.unknown1 = 0
+        _mem.unknown2 = 0
+        _mem.unknown3 = 0
+
     def get_settings(self):
         _settings = self._memobj.settings
         basic = RadioSettingGroup("basic", "Basic Settings")
@@ -515,6 +527,7 @@ class H777Radio(chirp_common.CloneModeRadio):
 
 
 class H777TestCase(unittest.TestCase):
+
     def setUp(self):
         self.driver = H777Radio(None)
         self.testdata = bitwise.parse("lbcd foo[2];",
diff --git a/chirp/drivers/icomciv.py b/chirp/drivers/icomciv.py
index 8d128ce..24fb297 100644
--- a/chirp/drivers/icomciv.py
+++ b/chirp/drivers/icomciv.py
@@ -51,6 +51,35 @@ u8   dtcs_polarity_tx;
 bbcd dtcs_tx[2];
 char name[9];
 """
+
+MEM_IC7100_FORMAT = """
+u8   bank;                 // 1 bank number
+bbcd number[2];            // 2,3
+u8   splitSelect;          // 4 split and select memory settings
+lbcd freq[5];              // 5-9 operating freq
+u8   mode;                 // 10 operating mode
+u8   filter;               // 11 filter
+u8   dataMode;             // 12 data mode setting (on or off)
+u8   duplex:4,             // 13 duplex on/-/+
+     tmode:4;              // 13 tone
+u8   dsql:4,               // 14 digital squelch
+     unknown1:4;           // 14 zero
+bbcd rtone[3];             // 15-17 repeater tone freq
+bbcd ctone[3];             // 18-20 tone squelch setting
+u8   dtcsPolarity;         // 21 DTCS polarity
+u8   unknown2:4,           // 22 zero
+     firstDtcs:4;          // 22 first digit of DTCS code
+u8   secondDtcs:4,         // 23 second digit DTCS
+     thirdDtcs:4;          // 23 third digit DTCS
+u8   digitalSquelch;       // 24 Digital code squelch setting
+u8   duplexOffset[3];      // 25-27 duplex offset freq
+char destCall[8];          // 28-35 destination call sign
+char accessRepeaterCall[8];// 36-43 access repeater call sign
+char linkRepeaterCall[8];  // 44-51 gateway/link repeater call sign
+bbcd duplexSettings[47];   // repeat of 5-51 for duplex
+char name[16];             // 52-60 Name of station
+"""
+
 mem_duptone_format = """
 bbcd number[2];
 u8   unknown1;
@@ -169,6 +198,7 @@ class MemFrame(Frame):
 
 class BankMemFrame(MemFrame):
     """A memory frame for radios with multiple banks"""
+    FORMAT = MEM_IC7000_FORMAT
     _bnk = 0
 
     def set_location(self, loc, bank=1):
@@ -185,7 +215,11 @@ class BankMemFrame(MemFrame):
 
     def get_obj(self):
         self._data = MemoryMap(str(self._data))  # Make sure we're assignable
-        return bitwise.parse(MEM_IC7000_FORMAT, self._data)
+        return bitwise.parse(self.FORMAT, self._data)
+
+
+class IC7100MemFrame(BankMemFrame):
+    FORMAT = MEM_IC7100_FORMAT
 
 
 class DupToneMemFrame(MemFrame):
@@ -204,8 +238,12 @@ class IcomCIVRadio(icf.IcomLiveRadio):
     # complete list of modes from CI-V documentation
     # each radio supports a subset
     # WARNING: "S-AM" and "PSK" are not valid (yet) for chirp
-    _MODES = ["LSB", "USB", "AM", "CW", "RTTY",
-              "FM", "WFM", "CWR", "RTTYR", "S-AM", "PSK"]
+    _MODES = [
+        "LSB", "USB", "AM", "CW", "RTTY", "FM", "WFM", "CWR"
+        "RTTYR", "S-AM", "PSK", None, None, None, None, None,
+        None, None, None, None, None, None, None, None,
+        "DV",
+    ]
 
     def mem_to_ch_bnk(self, mem):
         l, h = self._bank_index_bounds
@@ -332,7 +370,24 @@ class IcomCIVRadio(icf.IcomLiveRadio):
             pass
 
         mem.freq = int(memobj.freq)
-        mem.mode = self._MODES[memobj.mode]
+        try:
+            mem.mode = self._MODES[memobj.mode]
+
+            # We do not know what a variety of the positions between
+            # PSK and DV mean, so let's behave as if those values
+            # are not set to maintain consistency between known-unknown
+            # values and unknown-unknown ones.
+            if mem.mode is None:
+                raise IndexError(memobj.mode)
+        except IndexError:
+            LOG.error(
+                "Bank %s location %s is set for mode %s, but no known "
+                "mode matches that value.",
+                int(memobj.bank),
+                int(memobj.number),
+                repr(memobj.mode),
+            )
+            raise
 
         if self._rf.has_name:
             mem.name = str(memobj.name).rstrip()
@@ -435,7 +490,8 @@ class IcomCIVRadio(icf.IcomLiveRadio):
         memobj.freq = int(mem.freq)
         memobj.mode = self._MODES.index(mem.mode)
         if self._rf.has_name:
-            memobj.name = mem.name.ljust(9)[:9]
+            name_length = len(memobj.name.get_value())
+            memobj.name = mem.name.ljust(name_length)[:name_length]
 
         if self._rf.valid_tmodes:
             memobj.tmode = self._rf.valid_tmodes.index(mem.tmode)
@@ -541,6 +597,41 @@ class Icom7000Radio(IcomCIVRadio):
 
 
 @directory.register
+class Icom7100Radio(IcomCIVRadio):
+    """Icom IC-7100"""
+    MODEL = "IC-7100"
+    _model = "\x88"
+    _template = 102
+
+    _num_banks = 5
+    _bank_index_bounds = (1, 99)
+    _bank_class = icf.IcomBank
+
+    def _initialize(self):
+        self._classes["mem"] = IC7100MemFrame
+        self._rf.has_bank = True
+        self._rf.has_bank_index = False
+        self._rf.has_bank_names = False
+        self._rf.has_dtcs_polarity = False
+        self._rf.has_dtcs = False
+        self._rf.has_ctone = True
+        self._rf.has_offset = False
+        self._rf.has_name = True
+        self._rf.has_tuning_step = False
+        self._rf.valid_modes = [
+            "LSB", "USB", "AM", "CW", "RTTY", "FM", "WFM", "CWR", "RTTYR", "DV"
+        ]
+        self._rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
+        self._rf.valid_duplexes = ["", "-", "+"]
+        self._rf.valid_bands = [(30000, 199999999), (400000000, 470000000)]
+        self._rf.valid_tuning_steps = []
+        self._rf.valid_skips = []
+        self._rf.valid_name_length = 16
+        self._rf.valid_characters = chirp_common.CHARSET_ASCII
+        self._rf.memory_bounds = (0, 99 * self._num_banks - 1)
+
+
+ at directory.register
 class Icom746Radio(IcomCIVRadio):
     """Icom IC-746"""
     MODEL = "746"
@@ -571,6 +662,7 @@ class Icom746Radio(IcomCIVRadio):
 
 CIV_MODELS = {
     (0x76, 0xE0): Icom7200Radio,
+    (0x88, 0xE0): Icom7100Radio,
     (0x70, 0xE0): Icom7000Radio,
     (0x46, 0xE0): Icom746Radio,
 }
diff --git a/chirp/drivers/kguv8d.py b/chirp/drivers/kguv8d.py
index 785d2f5..a119c86 100644
--- a/chirp/drivers/kguv8d.py
+++ b/chirp/drivers/kguv8d.py
@@ -20,9 +20,9 @@ import os
 import logging
 from chirp import util, chirp_common, bitwise, memmap, errors, directory
 from chirp.settings import RadioSetting, RadioSettingGroup, \
-                RadioSettingValueBoolean, RadioSettingValueList, \
-                RadioSettingValueInteger, RadioSettingValueString, \
-                RadioSettings
+    RadioSettingValueBoolean, RadioSettingValueList, \
+    RadioSettingValueInteger, RadioSettingValueString, \
+    RadioSettings
 
 LOG = logging.getLogger(__name__)
 
@@ -47,7 +47,7 @@ PF1KEY_LIST = ["Call", "VFTX"]
 PF3KEY_LIST = ["Scan", "Lamp", "Tele Alarm", "SOS-CH", "Radio", "Disable"]
 WORKMODE_LIST = ["VFO", "Channel No.", "Ch. No.+Freq.", "Ch. No.+Name"]
 BACKLIGHT_LIST = ["Always On"] + [str(x) + "s" for x in range(1, 21)] + \
-                ["Always Off"]
+    ["Always Off"]
 OFFSET_LIST = ["+", "-"]
 PONMSG_LIST = ["Bitmap", "Battery Volts"]
 SPMUTE_LIST = ["QT", "QT+DTMF", "QT*DTMF"]
@@ -464,14 +464,14 @@ class KGUV8DRadio(chirp_common.CloneModeRadio,
         rf.valid_skips = ["", "S"]
         rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
         rf.valid_cross_modes = [
-                        "Tone->Tone",
-                        "Tone->DTCS",
-                        "DTCS->Tone",
-                        "DTCS->",
-                        "->Tone",
-                        "->DTCS",
-                        "DTCS->DTCS",
-                    ]
+            "Tone->Tone",
+            "Tone->DTCS",
+            "DTCS->Tone",
+            "DTCS->",
+            "->Tone",
+            "->DTCS",
+            "DTCS->DTCS",
+        ]
         rf.valid_modes = ["FM", "NFM"]
         rf.valid_power_levels = self.POWER_LEVELS
         rf.valid_name_length = 8
@@ -681,117 +681,117 @@ class KGUV8DRadio(chirp_common.CloneModeRadio,
                           RadioSettingValueBoolean(_settings.channel_menu))
         cfg_grp.append(rs)
         rs = RadioSetting("ponmsg", "Poweron message",
-                        RadioSettingValueList(
-                            PONMSG_LIST, PONMSG_LIST[_settings.ponmsg]))
+                          RadioSettingValueList(
+                              PONMSG_LIST, PONMSG_LIST[_settings.ponmsg]))
         cfg_grp.append(rs)
         rs = RadioSetting("voice", "Voice Guide",
-                        RadioSettingValueBoolean(_settings.voice))
+                          RadioSettingValueBoolean(_settings.voice))
         cfg_grp.append(rs)
         rs = RadioSetting("language", "Language",
-                        RadioSettingValueList(LANGUAGE_LIST,
-                            LANGUAGE_LIST[_settings.language]))
+                          RadioSettingValueList(LANGUAGE_LIST,
+                                                LANGUAGE_LIST[_settings.language]))
         cfg_grp.append(rs)
         rs = RadioSetting("timeout", "Timeout Timer",
-                        RadioSettingValueInteger(15, 900,
-                            _settings.timeout * 15, 15))
+                          RadioSettingValueInteger(15, 900,
+                                                   _settings.timeout * 15, 15))
         cfg_grp.append(rs)
         rs = RadioSetting("toalarm", "Timeout Alarm",
-                        RadioSettingValueInteger(0, 10, _settings.toalarm))
+                          RadioSettingValueInteger(0, 10, _settings.toalarm))
         cfg_grp.append(rs)
         rs = RadioSetting("roger_beep", "Roger Beep",
-                        RadioSettingValueBoolean(_settings.roger_beep))
+                          RadioSettingValueBoolean(_settings.roger_beep))
         cfg_grp.append(rs)
         rs = RadioSetting("power_save", "Power save",
-                        RadioSettingValueBoolean(_settings.power_save))
+                          RadioSettingValueBoolean(_settings.power_save))
         cfg_grp.append(rs)
         rs = RadioSetting("autolock", "Autolock",
-                        RadioSettingValueBoolean(_settings.autolock))
+                          RadioSettingValueBoolean(_settings.autolock))
         cfg_grp.append(rs)
         rs = RadioSetting("keylock", "Keypad Lock",
-                        RadioSettingValueBoolean(_settings.keylock))
+                          RadioSettingValueBoolean(_settings.keylock))
         cfg_grp.append(rs)
         rs = RadioSetting("beep", "Keypad Beep",
-                        RadioSettingValueBoolean(_settings.beep))
+                          RadioSettingValueBoolean(_settings.beep))
         cfg_grp.append(rs)
         rs = RadioSetting("stopwatch", "Stopwatch",
-                        RadioSettingValueBoolean(_settings.stopwatch))
+                          RadioSettingValueBoolean(_settings.stopwatch))
         cfg_grp.append(rs)
         rs = RadioSetting("backlight", "Backlight",
-                        RadioSettingValueList(BACKLIGHT_LIST,
-                            BACKLIGHT_LIST[_settings.backlight]))
+                          RadioSettingValueList(BACKLIGHT_LIST,
+                                                BACKLIGHT_LIST[_settings.backlight]))
         cfg_grp.append(rs)
         rs = RadioSetting("dtmf_st", "DTMF Sidetone",
-                        RadioSettingValueList(DTMFST_LIST,
-                            DTMFST_LIST[_settings.dtmf_st]))
+                          RadioSettingValueList(DTMFST_LIST,
+                                                DTMFST_LIST[_settings.dtmf_st]))
         cfg_grp.append(rs)
         rs = RadioSetting("ani-id_sw", "ANI-ID Switch",
-                        RadioSettingValueBoolean(_settings.ani_sw))
+                          RadioSettingValueBoolean(_settings.ani_sw))
         cfg_grp.append(rs)
         rs = RadioSetting("ptt-id_delay", "PTT-ID Delay",
-                        RadioSettingValueList(PTTID_LIST,
-                            PTTID_LIST[_settings.ptt_id]))
+                          RadioSettingValueList(PTTID_LIST,
+                                                PTTID_LIST[_settings.ptt_id]))
         cfg_grp.append(rs)
         rs = RadioSetting("ring_time", "Ring Time",
-                        RadioSettingValueList(LIST_10,
-                            LIST_10[_settings.ring_time]))
+                          RadioSettingValueList(LIST_10,
+                                                LIST_10[_settings.ring_time]))
         cfg_grp.append(rs)
         rs = RadioSetting("scan_rev", "Scan Mode",
-                        RadioSettingValueList(SCANMODE_LIST,
-                            SCANMODE_LIST[_settings.scan_rev]))
+                          RadioSettingValueList(SCANMODE_LIST,
+                                                SCANMODE_LIST[_settings.scan_rev]))
         cfg_grp.append(rs)
         rs = RadioSetting("vox", "VOX",
-                        RadioSettingValueList(LIST_10,
-                            LIST_10[_settings.vox]))
+                          RadioSettingValueList(LIST_10,
+                                                LIST_10[_settings.vox]))
         cfg_grp.append(rs)
         rs = RadioSetting("prich_sw", "Priority Channel Switch",
-                        RadioSettingValueBoolean(_settings.prich_sw))
+                          RadioSettingValueBoolean(_settings.prich_sw))
         cfg_grp.append(rs)
         rs = RadioSetting("pri_ch", "Priority Channel",
-                        RadioSettingValueInteger(1, 999, _settings.pri_ch))
+                          RadioSettingValueInteger(1, 999, _settings.pri_ch))
         cfg_grp.append(rs)
         rs = RadioSetting("rpt_mode", "Radio Mode",
-                        RadioSettingValueList(RPTMODE_LIST,
-                            RPTMODE_LIST[_settings.rpt_mode]))
+                          RadioSettingValueList(RPTMODE_LIST,
+                                                RPTMODE_LIST[_settings.rpt_mode]))
         cfg_grp.append(rs)
         rs = RadioSetting("rpt_set", "Repeater Setting",
-                        RadioSettingValueList(RPTSET_LIST,
-                            RPTSET_LIST[_settings.rpt_set]))
+                          RadioSettingValueList(RPTSET_LIST,
+                                                RPTSET_LIST[_settings.rpt_set]))
         cfg_grp.append(rs)
         rs = RadioSetting("rpt_spk", "Repeater Mode Speaker",
-                        RadioSettingValueBoolean(_settings.rpt_spk))
+                          RadioSettingValueBoolean(_settings.rpt_spk))
         cfg_grp.append(rs)
         rs = RadioSetting("rpt_ptt", "Repeater PTT",
-                        RadioSettingValueBoolean(_settings.rpt_ptt))
+                          RadioSettingValueBoolean(_settings.rpt_ptt))
         cfg_grp.append(rs)
         rs = RadioSetting("dtmf_tx_time", "DTMF Tx Duration",
-                        RadioSettingValueList(DTMF_TIMES,
-                            DTMF_TIMES[_settings.dtmf_tx_time]))
+                          RadioSettingValueList(DTMF_TIMES,
+                                                DTMF_TIMES[_settings.dtmf_tx_time]))
         cfg_grp.append(rs)
         rs = RadioSetting("dtmf_interval", "DTMF Interval",
-                        RadioSettingValueList(DTMF_TIMES,
-                            DTMF_TIMES[_settings.dtmf_interval]))
+                          RadioSettingValueList(DTMF_TIMES,
+                                                DTMF_TIMES[_settings.dtmf_interval]))
         cfg_grp.append(rs)
         rs = RadioSetting("alert", "Alert Tone",
-                        RadioSettingValueList(ALERTS_LIST,
-                            ALERTS_LIST[_settings.alert]))
+                          RadioSettingValueList(ALERTS_LIST,
+                                                ALERTS_LIST[_settings.alert]))
         cfg_grp.append(rs)
         rs = RadioSetting("rpt_tone", "Repeater Tone",
-                        RadioSettingValueBoolean(_settings.rpt_tone))
+                          RadioSettingValueBoolean(_settings.rpt_tone))
         cfg_grp.append(rs)
         rs = RadioSetting("rpt_hold", "Repeater Hold Time",
-                        RadioSettingValueList(HOLD_TIMES,
-                            HOLD_TIMES[_settings.rpt_hold]))
+                          RadioSettingValueList(HOLD_TIMES,
+                                                HOLD_TIMES[_settings.rpt_hold]))
         cfg_grp.append(rs)
         rs = RadioSetting("scan_det", "Scan DET",
-                        RadioSettingValueBoolean(_settings.scan_det))
+                          RadioSettingValueBoolean(_settings.scan_det))
         cfg_grp.append(rs)
         rs = RadioSetting("sc_qt", "SC-QT",
-                        RadioSettingValueList(SCQT_LIST,
-                            SCQT_LIST[_settings.smuteset]))
+                          RadioSettingValueList(SCQT_LIST,
+                                                SCQT_LIST[_settings.smuteset]))
         cfg_grp.append(rs)
         rs = RadioSetting("smuteset", "SubFreq Mute",
-                        RadioSettingValueList(SMUTESET_LIST,
-                            SMUTESET_LIST[_settings.smuteset]))
+                          RadioSettingValueList(SMUTESET_LIST,
+                                                SMUTESET_LIST[_settings.smuteset]))
         cfg_grp.append(rs)
         _pwd = "".join(map(chr, _settings.mode_sw_pwd))
         val = RadioSettingValueString(0, 6, _pwd)
@@ -807,45 +807,45 @@ class KGUV8DRadio(chirp_common.CloneModeRadio,
         # VFO A Settings
         #
         rs = RadioSetting("vfoa_mode", "VFO A Workmode",
-                        RadioSettingValueList(WORKMODE_LIST,
-                              WORKMODE_LIST[_settings.workmode_a]))
+                          RadioSettingValueList(WORKMODE_LIST,
+                                                WORKMODE_LIST[_settings.workmode_a]))
         vfoa_grp.append(rs)
         rs = RadioSetting("vfoa_chan", "VFO A Channel",
-                        RadioSettingValueInteger(1, 999, _settings.work_cha))
+                          RadioSettingValueInteger(1, 999, _settings.work_cha))
         vfoa_grp.append(rs)
         rs = RadioSetting("rxfreqa", "VFO A Rx Frequency",
-                        RadioSettingValueInteger(
+                          RadioSettingValueInteger(
                               134000000, 520000000, _vfoa.rxfreq * 10, 5000))
         vfoa_grp.append(rs)
         rs = RadioSetting("txoffa", "VFO A Tx Offset",
-                        RadioSettingValueInteger(
+                          RadioSettingValueInteger(
                               0, 520000000, _vfoa.txoffset * 10, 5000))
         vfoa_grp.append(rs)
         #   u16   rxtone;
         #   u16   txtone;
         rs = RadioSetting("vfoa_power", "VFO A Power",
-                        RadioSettingValueList(
+                          RadioSettingValueList(
                               POWER_LIST, POWER_LIST[_vfoa.power]))
         vfoa_grp.append(rs)
         #         shift_dir:2
         rs = RadioSetting("vfoa_iswide", "VFO A NBFM",
-                        RadioSettingValueList(
+                          RadioSettingValueList(
                               BANDWIDTH_LIST, BANDWIDTH_LIST[_vfoa.iswide]))
         vfoa_grp.append(rs)
         rs = RadioSetting("vfoa_mute_mode", "VFO A Mute",
-                        RadioSettingValueList(
+                          RadioSettingValueList(
                               SPMUTE_LIST, SPMUTE_LIST[_vfoa.mute_mode]))
         vfoa_grp.append(rs)
         rs = RadioSetting("vfoa_step", "VFO A Step (kHz)",
-                        RadioSettingValueList(
+                          RadioSettingValueList(
                               STEP_LIST, STEP_LIST[_vfoa.step]))
         vfoa_grp.append(rs)
         rs = RadioSetting("vfoa_squelch", "VFO A Squelch",
-                        RadioSettingValueList(
+                          RadioSettingValueList(
                               LIST_10, LIST_10[_vfoa.squelch]))
         vfoa_grp.append(rs)
         rs = RadioSetting("bcl_a", "Busy Channel Lock-out A",
-                        RadioSettingValueBoolean(_settings.bcl_a))
+                          RadioSettingValueBoolean(_settings.bcl_a))
         vfoa_grp.append(rs)
         #
         # VFO B Settings
@@ -890,7 +890,7 @@ class KGUV8DRadio(chirp_common.CloneModeRadio,
                               LIST_10, LIST_10[_vfob.squelch]))
         vfob_grp.append(rs)
         rs = RadioSetting("bcl_b", "Busy Channel Lock-out B",
-                        RadioSettingValueBoolean(_settings.bcl_b))
+                          RadioSettingValueBoolean(_settings.bcl_b))
         vfob_grp.append(rs)
         #
         # Key Settings
diff --git a/chirp/drivers/leixen.py b/chirp/drivers/leixen.py
index caa34dd..c484495 100644
--- a/chirp/drivers/leixen.py
+++ b/chirp/drivers/leixen.py
@@ -135,14 +135,13 @@ struct channel {
   u8 unknown5;
   u8 pttidoff:1,
      dtmfoff:1,
-     unknown6:1,
+     %(unknownormode)s,
      tailcut:1,
      aliasop:1,
      talkaroundoff:1,
      voxoff:1,
      skip:1;
-  u8 power:1,
-     mode:1
+  u8 %(modeorpower)s,
      reverseoff:1,
      blckoff:1,
      unknown7:1,
@@ -223,8 +222,6 @@ PFKEYSHORT_LIST = ["OFF",
                    "Reverse"
                    ]
 
-POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=4),
-                chirp_common.PowerLevel("High", watts=10)]
 MODES = ["NFM", "FM"]
 WTFTONES = map(float, xrange(56, 64))
 TONES = WTFTONES + chirp_common.TONES
@@ -279,7 +276,7 @@ def recv(radio, readdata=True):
         #           util.hexprint(hdr + data).replace("\n", "\n          "))
         if len(data) != length:
             raise errors.RadioError("Radio sent %i bytes (expected %i)" % (
-                    len(data), length))
+                len(data), length))
         chk = radio.pipe.read(1)
     else:
         data = ""
@@ -326,7 +323,8 @@ def do_upload(radio):
     _ranges = [(0x0d00, 0x2000)]
 
     image_ident = _image_ident_from_image(radio)
-    if image_ident.startswith(radio._file_ident) and "LX-" in image_ident:
+    if image_ident.startswith(radio._file_ident) and \
+       radio._model_ident in image_ident:
         _ranges = radio._ranges
 
     do_ident(radio)
@@ -356,14 +354,23 @@ def finish(radio):
     ack = radio.pipe.read(8)
 
 
+# Declaring Aliases
+class LT898UV(chirp_common.Alias):
+    VENDOR = "LUITON"
+    MODEL = "LT-898UV"
+
+
 @directory.register
 class LeixenVV898Radio(chirp_common.CloneModeRadio):
     """Leixen VV-898"""
     VENDOR = "Leixen"
     MODEL = "VV-898"
+    ALIASES = [LT898UV, ]
     BAUD_RATE = 9600
 
     _file_ident = "Leixen"
+    _model_ident = 'LX-\x89\x85\x63'
+
     _memsize = 0x2000
     _ranges = [
         (0x0000, 0x013f),
@@ -375,6 +382,11 @@ class LeixenVV898Radio(chirp_common.CloneModeRadio):
         (0x0d00, 0x2000),
     ]
 
+    _mem_formatter = {'unknownormode': 'unknown6:1',
+                      'modeorpower': 'mode:1, power:1'}
+    _power_levels = [chirp_common.PowerLevel("Low", watts=4),
+                     chirp_common.PowerLevel("High", watts=10)]
+
     def get_features(self):
         rf = chirp_common.RadioFeatures()
         rf.has_settings = True
@@ -395,7 +407,7 @@ class LeixenVV898Radio(chirp_common.CloneModeRadio):
             "DTCS->DTCS"]
         rf.valid_characters = chirp_common.CHARSET_ASCII
         rf.valid_name_length = 7
-        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_power_levels = self._power_levels
         rf.valid_duplexes = ["", "-", "+", "split", "off"]
         rf.valid_skips = ["", "S"]
         rf.valid_bands = [(136000000, 174000000),
@@ -412,7 +424,8 @@ class LeixenVV898Radio(chirp_common.CloneModeRadio):
         self.process_mmap()
 
     def process_mmap(self):
-        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+        self._memobj = bitwise.parse(
+            MEM_FORMAT % self._mem_formatter, self._mmap)
 
     def sync_out(self):
         try:
@@ -425,7 +438,7 @@ class LeixenVV898Radio(chirp_common.CloneModeRadio):
 
     def get_raw_memory(self, number):
         return repr(self._memobj.name[number - 1]) + \
-               repr(self._memobj.memory[number - 1])
+            repr(self._memobj.memory[number - 1])
 
     def _get_tone(self, mem, _mem):
         rx_tone = tx_tone = None
@@ -447,7 +460,7 @@ class LeixenVV898Radio(chirp_common.CloneModeRadio):
         rx_pol = _mem.rx_tmode == 0x03 and "R" or "N"
 
         chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
-                                            (rx_tmode, rx_tone, rx_pol))
+                                       (rx_tmode, rx_tone, rx_pol))
 
     def _is_txinh(self, _mem):
         raw_tx = ""
@@ -485,12 +498,8 @@ class LeixenVV898Radio(chirp_common.CloneModeRadio):
 
         self._get_tone(mem, _mem)
         mem.mode = MODES[_mem.mode]
-        mem.power = POWER_LEVELS[_mem.power]
-        mem.skip = _mem.skip and "S" or ""
-
-        self._get_tone(mem, _mem)
-        mem.mode = MODES[_mem.mode]
-        mem.power = POWER_LEVELS[_mem.power]
+        powerindex = _mem.power if _mem.power < len(self._power_levels) else -1
+        mem.power = self._power_levels[powerindex]
         mem.skip = _mem.skip and "S" or ""
 
         mem.extra = RadioSettingGroup("Extra", "extra")
@@ -505,11 +514,12 @@ class LeixenVV898Radio(chirp_common.CloneModeRadio):
                           RadioSettingValueList(
                               opts, opts[_mem.tailcut]))
         mem.extra.append(rs)
+        apro = _mem.apro if _mem.apro < 0x5 else 0
         opts = ["Off", "Compander", "Scrambler", "TX Scrambler",
                 "RX Scrambler"]
         rs = RadioSetting("apro", "Audio Processing",
                           RadioSettingValueList(
-                              opts, opts[_mem.apro]))
+                              opts, opts[apro]))
         mem.extra.append(rs)
         opts = ["On", "Off"]
         rs = RadioSetting("voxoff", "VOX",
@@ -587,7 +597,7 @@ class LeixenVV898Radio(chirp_common.CloneModeRadio):
 
         self._set_tone(mem, _mem)
 
-        _mem.power = mem.power and POWER_LEVELS.index(mem.power) or 0
+        _mem.power = mem.power and self._power_levels.index(mem.power) or 0
         _mem.mode = MODES.index(mem.mode)
         _mem.skip = mem.skip == "S"
         _name.name = mem.name.ljust(7)
@@ -926,9 +936,7 @@ class LeixenVV898Radio(chirp_common.CloneModeRadio):
     @classmethod
     def match_model(cls, filedata, filename):
         if filedata[0x168:0x170].startswith(cls._file_ident) and \
-                               filedata[0x170:0x178].startswith("LX-\x89\x85"):
-            return True
-        elif filedata[0x900:0x906] == cls.MODEL:
+           filedata[0x170:0x178].startswith(cls._model_ident):
             return True
         else:
             return False
@@ -941,3 +949,25 @@ class JetstreamJT270MRadio(LeixenVV898Radio):
     MODEL = "JT270M"
 
     _file_ident = "JET"
+    _model_ident = 'LX-\x89\x85\x53'
+
+
+class VV898E(chirp_common.Alias):
+    '''Leixen has called this radio both 898E and S historically, ident is identical'''
+    VENDOR = "Leixen"
+    MODEL = "VV-898E"
+
+
+ at directory.register
+class LeixenVV898SRadio(LeixenVV898Radio):
+    """Leixen VV-898S, also VV-898E which is identical"""
+    VENDOR = "Leixen"
+    MODEL = "VV-898S"
+    ALIASES = [VV898E, ]
+
+    _model_ident = 'LX-\x89\x85\x75'
+    _mem_formatter = {'unknownormode': 'mode:1',
+                      'modeorpower': 'power:2'}
+    _power_levels = [chirp_common.PowerLevel("Low", watts=5),
+                     chirp_common.PowerLevel("Med", watts=10),
+                     chirp_common.PowerLevel("High", watts=25)]
diff --git a/chirp/drivers/lt725uv.py b/chirp/drivers/lt725uv.py
new file mode 100644
index 0000000..2f3f757
--- /dev/null
+++ b/chirp/drivers/lt725uv.py
@@ -0,0 +1,763 @@
+# Copyright 2016:
+# * Jim Unroe KC9HI, <rock.unroe at gmail.com>
+#
+# 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 2 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
+
+LOG = logging.getLogger(__name__)
+
+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
+from textwrap import dedent
+
+MEM_FORMAT = """
+#seekto 0x0200;
+struct {
+  u8  unknown1;
+  u8  volume;
+  u8  unknown2[2];
+  u8  wtled;
+  u8  rxled;
+  u8  txled;
+  u8  ledsw;
+  u8  beep;
+  u8  ring;
+  u8  bcl;
+  u8  tot;
+} settings;
+
+struct vfo {
+  u8  unknown1[2];
+  u32 rxfreq;
+  u8  unknown2[8];
+  u8  power;
+  u8  unknown3[3];
+  u24 offset;
+  u32 step;
+  u8  sql;
+};
+
+#seekto 0x0300;
+struct {
+  struct vfo vfoa;
+} upper;
+
+#seekto 0x0380;
+struct {
+  struct vfo vfob;
+} lower;
+
+struct mem {
+  u32 rxfreq;
+  u16 is_rxdigtone:1,
+      rxdtcs_pol:1,
+      rxtone:14;
+  u8  recvmode;
+  u32 txfreq;
+  u16 is_txdigtone:1,
+      txdtcs_pol:1,
+      txtone:14;
+  u8  botsignal;
+  u8  eotsignal;
+  u8  power:1,
+      wide:1,
+      compandor:1
+      scrambler:1
+      unknown:4;
+  u8  namelen;
+  u8  name[6];
+  u8  unused;
+};
+
+#seekto 0x0400;
+struct mem upper_memory[128];
+
+#seekto 0x1000;
+struct mem lower_memory[128];
+
+"""
+
+MEM_SIZE = 0x1C00
+BLOCK_SIZE = 0x40
+STIMEOUT = 2
+
+LIST_RECVMODE = ["", "QT/DQT", "QT/DQT + Signaling"]
+LIST_SIGNAL = ["Off"] + ["DTMF%s" % x for x in range(1, 9)] + \
+              ["DTMF%s + Identity" % x for x in range(1, 9)] + \
+              ["Identity code"]
+LIST_POWER = ["Low", "Mid", "High"]
+LIST_COLOR = ["Off", "Orange", "Blue", "Purple"]
+LIST_LEDSW = ["Auto", "On"]
+LIST_RING = ["Off"] + ["%s seconds" % x for x in range(1, 10)]
+LIST_TIMEOUT = ["Off"] + ["%s seconds" % x for x in range(30, 630, 30)]
+
+
+def _clean_buffer(radio):
+    radio.pipe.timeout = 0.005
+    junk = radio.pipe.read(256)
+    radio.pipe.timeout = STIMEOUT
+    if junk:
+        Log.debug("Got %i bytes of junk before starting" % len(junk))
+
+
+def _rawrecv(radio, amount):
+    """Raw read from the radio device"""
+    data = ""
+    try:
+        data = radio.pipe.read(amount)
+    except:
+        _exit_program_mode(radio)
+        msg = "Generic error reading data from radio; check your cable."
+        raise errors.RadioError(msg)
+
+    if len(data) != amount:
+        _exit_program_mode(radio)
+        msg = "Error reading data from radio: not the amount of data we want."
+        raise errors.RadioError(msg)
+
+    return data
+
+
+def _rawsend(radio, data):
+    """Raw send to the radio device"""
+    try:
+        radio.pipe.write(data)
+    except:
+        raise errors.RadioError("Error sending data to radio")
+
+
+def _make_frame(cmd, addr, length, data=""):
+    """Pack the info in the headder format"""
+    frame = struct.pack(">4sHH", cmd, addr, length)
+    # add the data if set
+    if len(data) != 0:
+        frame += data
+    # return the data
+    return frame
+
+
+def _recv(radio, addr, length):
+    """Get data from the radio """
+
+    data = _rawrecv(radio, length)
+
+    # DEBUG
+    LOG.info("Response:")
+    LOG.debug(util.hexprint(data))
+
+    return data
+
+
+def _do_ident(radio):
+    """Put the radio in PROGRAM mode & identify it"""
+    #  set the serial discipline
+    radio.pipe.baudrate = 19200
+    radio.pipe.parity = "N"
+    radio.pipe.timeout = STIMEOUT
+
+    # flush input buffer
+    _clean_buffer(radio)
+
+    magic = "PROM_LIN"
+
+    _rawsend(radio, magic)
+
+    ack = _rawrecv(radio, 1)
+    if ack != "\x06":
+        _exit_program_mode(radio)
+        if ack:
+            LOG.debug(repr(ack))
+        raise errors.RadioError("Radio did not respond")
+
+    return True
+
+
+def _exit_program_mode(radio):
+    endframe = "EXIT"
+    _rawsend(radio, endframe)
+
+
+def _download(radio):
+    """Get the memory map"""
+
+    # put radio in program mode and identify it
+    _do_ident(radio)
+
+    # UI progress
+    status = chirp_common.Status()
+    status.cur = 0
+    status.max = MEM_SIZE / BLOCK_SIZE
+    status.msg = "Cloning from radio..."
+    radio.status_fn(status)
+
+    data = ""
+    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
+        frame = _make_frame("READ", addr, BLOCK_SIZE)
+        # DEBUG
+        LOG.info("Request sent:")
+        LOG.debug(util.hexprint(frame))
+
+        # sending the read request
+        _rawsend(radio, frame)
+
+        # now we read
+        d = _recv(radio, addr, BLOCK_SIZE)
+
+        # aggregate the data
+        data += d
+
+        # UI Update
+        status.cur = addr / BLOCK_SIZE
+        status.msg = "Cloning from radio..."
+        radio.status_fn(status)
+
+    _exit_program_mode(radio)
+
+    data += "LT-725UV"
+
+    return data
+
+
+def _upload(radio):
+    """Upload procedure"""
+
+    # put radio in program mode and identify it
+    _do_ident(radio)
+
+    # UI progress
+    status = chirp_common.Status()
+    status.cur = 0
+    status.max = MEM_SIZE / BLOCK_SIZE
+    status.msg = "Cloning to radio..."
+    radio.status_fn(status)
+
+    # the fun starts here
+    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
+        # sending the data
+        data = radio.get_mmap()[addr:addr + BLOCK_SIZE]
+
+        frame = _make_frame("WRIE", addr, BLOCK_SIZE, data)
+
+        _rawsend(radio, frame)
+
+        # receiving the response
+        ack = _rawrecv(radio, 1)
+        if ack != "\x06":
+            _exit_program_mode(radio)
+            msg = "Bad ack writing block 0x%04x" % addr
+            raise errors.RadioError(msg)
+
+        # UI Update
+        status.cur = addr / BLOCK_SIZE
+        status.msg = "Cloning to radio..."
+        radio.status_fn(status)
+
+    _exit_program_mode(radio)
+
+
+def model_match(cls, data):
+    """Match the opened/downloaded image to the correct version"""
+    rid = data[0x1C00:0x1C08]
+
+    if rid == cls.MODEL:
+        return True
+
+    return False
+
+
+def _split(rf, f1, f2):
+    """Returns False if the two freqs are in the same band (no split)
+    or True otherwise"""
+
+    # determine if the two freqs are in the same band
+    for low, high in rf.valid_bands:
+        if f1 >= low and f1 <= high and \
+                f2 >= low and f2 <= high:
+            # if the two freqs are on the same Band this is not a split
+            return False
+
+    # if you get here is because the freq pairs are split
+    return True
+
+
+ at directory.register
+class LT725UV(chirp_common.CloneModeRadio,
+              chirp_common.ExperimentalRadio):
+    """LUITON LT-725UV Radio"""
+    VENDOR = "LUITON"
+    MODEL = "LT-725UV"
+    MODES = ["NFM", "FM"]
+    TONES = chirp_common.TONES
+    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
+    NAME_LENGTH = 6
+    DTMF_CHARS = list("0123456789ABCD*#")
+
+    VALID_BANDS = [(136000000, 176000000),
+                   (400000000, 480000000)]
+
+    # valid chars on the LCD
+    VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
+        "`{|}!\"#$%&'()*+,-./:;<=>?@[]^_"
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.experimental = \
+            ('The UV-50X3 driver is a beta version.\n'
+             '\n'
+             'Please save an unedited copy of your first successful\n'
+             'download to a CHIRP Radio Images(*.img) file.'
+             )
+        rp.pre_download = _(dedent("""\
+            Follow this instructions to download your info:
+
+            1 - Turn off your radio
+            2 - Connect your interface cable
+            3 - Turn on your radio
+            4 - Do the download of your radio data
+            """))
+        rp.pre_upload = _(dedent("""\
+            Follow this instructions to upload your info:
+
+            1 - Turn off your radio
+            2 - Connect your interface cable
+            3 - Turn on your radio
+            4 - Do the upload of your radio data
+            """))
+        return rp
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.has_settings = True
+        rf.has_bank = False
+        rf.has_tuning_step = False
+        rf.can_odd_split = True
+        rf.has_name = True
+        rf.has_offset = True
+        rf.has_mode = True
+        rf.has_dtcs = True
+        rf.has_rx_dtcs = True
+        rf.has_dtcs_polarity = True
+        rf.has_ctone = True
+        rf.has_cross = True
+        rf.has_sub_devices = self.VARIANT == ""
+        rf.valid_modes = self.MODES
+        rf.valid_characters = self.VALID_CHARS
+        rf.valid_duplexes = ["", "-", "+", "split", "off"]
+        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
+        rf.valid_cross_modes = [
+            "Tone->Tone",
+            "DTCS->",
+            "->DTCS",
+            "Tone->DTCS",
+            "DTCS->Tone",
+            "->Tone",
+            "DTCS->DTCS"]
+        rf.valid_skips = []
+        rf.valid_name_length = self.NAME_LENGTH
+        rf.valid_dtcs_codes = self.DTCS_CODES
+        rf.valid_bands = self.VALID_BANDS
+        rf.memory_bounds = (1, 128)
+        return rf
+
+    def get_sub_devices(self):
+        return [LT725UVUpper(self._mmap), LT725UVLower(self._mmap)]
+
+    def sync_in(self):
+        """Download from radio"""
+        try:
+            data = _download(self)
+        except errors.RadioError:
+            # Pass through any real errors we raise
+            raise
+        except:
+            # 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()
+
+    def sync_out(self):
+        """Upload to radio"""
+        try:
+            _upload(self)
+        except:
+            # If anything unexpected happens, make sure we raise
+            # a RadioError and log the problem
+            LOG.exception('Unexpected error during upload')
+            raise errors.RadioError('Unexpected error communicating '
+                                    'with the radio')
+
+    def process_mmap(self):
+        """Process the mem map into the mem object"""
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+    def get_raw_memory(self, number):
+        return repr(self._memobj.memory[number - 1])
+
+    def _memory_obj(self, suffix=""):
+        return getattr(self._memobj, "%s_memory%s" % (self._vfo, suffix))
+
+    def _get_dcs(self, val):
+        return int(str(val)[2:-18])
+
+    def _set_dcs(self, val):
+        return int(str(val), 16)
+
+    def get_memory(self, number):
+        _mem = self._memory_obj()[number - 1]
+
+        mem = chirp_common.Memory()
+        mem.number = number
+
+        if _mem.get_raw()[0] == "\xff":
+            mem.empty = True
+            return mem
+
+        mem.freq = int(_mem.rxfreq) * 10
+
+        if _mem.txfreq == 0xFFFFFFFF:
+            # TX freq not set
+            mem.duplex = "off"
+            mem.offset = 0
+        elif int(_mem.rxfreq) == int(_mem.txfreq):
+            mem.duplex = ""
+            mem.offset = 0
+        elif _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10):
+            mem.duplex = "split"
+            mem.offset = int(_mem.txfreq) * 10
+        else:
+            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
+            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
+
+        for char in _mem.name[:_mem.namelen]:
+            mem.name += chr(char)
+
+        dtcs_pol = ["N", "N"]
+
+        if _mem.rxtone == 0x3FFF:
+            rxmode = ""
+        elif _mem.is_rxdigtone == 0:
+            # ctcss
+            rxmode = "Tone"
+            mem.ctone = int(_mem.rxtone) / 10.0
+        else:
+            # digital
+            rxmode = "DTCS"
+            mem.rx_dtcs = self._get_dcs(_mem.rxtone)
+            if _mem.rxdtcs_pol == 1:
+                dtcs_pol[1] = "R"
+
+        if _mem.txtone == 0x3FFF:
+            txmode = ""
+        elif _mem.is_txdigtone == 0:
+            # ctcss
+            txmode = "Tone"
+            mem.rtone = int(_mem.txtone) / 10.0
+        else:
+            # digital
+            txmode = "DTCS"
+            mem.dtcs = self._get_dcs(_mem.txtone)
+            if _mem.txdtcs_pol == 1:
+                dtcs_pol[0] = "R"
+
+        if txmode == "Tone" and not rxmode:
+            mem.tmode = "Tone"
+        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
+            mem.tmode = "TSQL"
+        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
+            mem.tmode = "DTCS"
+        elif rxmode or txmode:
+            mem.tmode = "Cross"
+            mem.cross_mode = "%s->%s" % (txmode, rxmode)
+
+        mem.dtcs_polarity = "".join(dtcs_pol)
+
+        mem.mode = self.MODES[_mem.wide]
+
+        # Extra
+        mem.extra = RadioSettingGroup("extra", "Extra")
+
+        if _mem.recvmode == 0xFF:
+            val = 0x00
+        else:
+            val = _mem.recvmode
+        recvmode = RadioSetting("recvmode", "Receiving mode",
+                                 RadioSettingValueList(LIST_RECVMODE,
+                                     LIST_RECVMODE[val]))
+        mem.extra.append(recvmode)
+
+        if _mem.botsignal == 0xFF:
+            val = 0x00
+        else:
+            val = _mem.botsignal
+        botsignal = RadioSetting("botsignal", "Launch signaling",
+                                 RadioSettingValueList(LIST_SIGNAL,
+                                     LIST_SIGNAL[val]))
+        mem.extra.append(botsignal)
+
+        if _mem.eotsignal == 0xFF:
+            val = 0x00
+        else:
+            val = _mem.eotsignal
+        eotsignal = RadioSetting("eotsignal", "Transmit end signaling",
+                                 RadioSettingValueList(LIST_SIGNAL,
+                                     LIST_SIGNAL[val]))
+        mem.extra.append(eotsignal)
+
+        compandor = RadioSetting("compandor", "Compandor",
+                                 RadioSettingValueBoolean(bool(_mem.compandor)))
+        mem.extra.append(compandor)
+
+        scrambler = RadioSetting("scrambler", "Scrambler",
+                                 RadioSettingValueBoolean(bool(_mem.scrambler)))
+        mem.extra.append(scrambler)
+
+        return mem
+
+    def set_memory(self, mem):
+        _mem = self._memory_obj()[mem.number - 1]
+
+        if mem.empty:
+            _mem.set_raw("\xff" * 24)
+            _mem.namelen = 0
+            return
+
+        _mem.set_raw("\xFF" * 15 + "\x00\x00" + "\xFF" * 7)
+
+        _mem.rxfreq = mem.freq / 10
+        if mem.duplex == "off":
+            _mem.txfreq = 0xFFFFFFFF
+        elif mem.duplex == "split":
+            _mem.txfreq = mem.offset / 10
+        elif mem.duplex == "+":
+            _mem.txfreq = (mem.freq + mem.offset) / 10
+        elif mem.duplex == "-":
+            _mem.txfreq = (mem.freq - mem.offset) / 10
+        else:
+            _mem.txfreq = mem.freq / 10
+
+        _mem.namelen = len(mem.name)
+        _namelength = self.get_features().valid_name_length
+        for i in range(_namelength):
+            try:
+                _mem.name[i] = ord(mem.name[i])
+            except IndexError:
+                _mem.name[i] = 0xFF
+
+        rxmode = ""
+        txmode = ""
+
+        if mem.tmode == "Tone":
+            txmode = "Tone"
+        elif mem.tmode == "TSQL":
+            rxmode = "Tone"
+            txmode = "TSQL"
+        elif mem.tmode == "DTCS":
+            rxmode = "DTCSSQL"
+            txmode = "DTCS"
+        elif mem.tmode == "Cross":
+            txmode, rxmode = mem.cross_mode.split("->", 1)
+
+        if rxmode == "":
+            _mem.rxdtcs_pol = 1
+            _mem.is_rxdigtone = 1
+            _mem.rxtone = 0x3FFF
+        elif rxmode == "Tone":
+            _mem.rxdtcs_pol = 0
+            _mem.is_rxdigtone = 0
+            _mem.rxtone = int(mem.ctone * 10)
+        elif rxmode == "DTCSSQL":
+            _mem.rxdtcs_pol = 1 if mem.dtcs_polarity[1] == "R" else 0
+            _mem.is_rxdigtone = 1
+            _mem.rxtone = self._set_dcs(mem.dtcs)
+        elif rxmode == "DTCS":
+            _mem.rxdtcs_pol = 1 if mem.dtcs_polarity[1] == "R" else 0
+            _mem.is_rxdigtone = 1
+            _mem.rxtone = self._set_dcs(mem.rx_dtcs)
+
+        if txmode == "":
+            _mem.txdtcs_pol = 1
+            _mem.is_txdigtone = 1
+            _mem.txtone = 0x3FFF
+        elif txmode == "Tone":
+            _mem.txdtcs_pol = 0
+            _mem.is_txdigtone = 0
+            _mem.txtone = int(mem.rtone * 10)
+        elif txmode == "TSQL":
+            _mem.txdtcs_pol = 0
+            _mem.is_txdigtone = 0
+            _mem.txtone = int(mem.ctone * 10)
+        elif txmode == "DTCS":
+            _mem.txdtcs_pol = 1 if mem.dtcs_polarity[0] == "R" else 0
+            _mem.is_txdigtone = 1
+            _mem.txtone = self._set_dcs(mem.dtcs)
+
+        _mem.wide = self.MODES.index(mem.mode)
+
+        # extra settings
+        for setting in mem.extra:
+            setattr(_mem, setting.get_name(), setting.value)
+
+    def get_settings(self):
+        """Translate the bit in the mem_struct into settings in the UI"""
+        _mem = self._memobj
+        basic = RadioSettingGroup("basic", "Basic Settings")
+        top = RadioSettings(basic)
+
+        # Basic
+
+        volume = RadioSetting("settings.volume", "Volume",
+                              RadioSettingValueInteger(0, 20,
+                                  _mem.settings.volume))
+        basic.append(volume)
+
+        powera = RadioSetting("upper.vfoa.power", "Power (Upper)",
+                              RadioSettingValueList(LIST_POWER, LIST_POWER[
+                                  _mem.upper.vfoa.power]))
+        basic.append(powera)
+
+        powerb = RadioSetting("lower.vfob.power", "Power (Lower)",
+                              RadioSettingValueList(LIST_POWER, LIST_POWER[
+                                  _mem.lower.vfob.power]))
+        basic.append(powerb)
+
+        wtled = RadioSetting("settings.wtled", "Standby LED Color",
+                             RadioSettingValueList(LIST_COLOR, LIST_COLOR[
+                                 _mem.settings.wtled]))
+        basic.append(wtled)
+
+        rxled = RadioSetting("settings.rxled", "RX LED Color",
+                             RadioSettingValueList(LIST_COLOR, LIST_COLOR[
+                                 _mem.settings.rxled]))
+        basic.append(rxled)
+
+        txled = RadioSetting("settings.txled", "TX LED Color",
+                             RadioSettingValueList(LIST_COLOR, LIST_COLOR[
+                                 _mem.settings.txled]))
+        basic.append(txled)
+
+        ledsw = RadioSetting("settings.ledsw", "Back light mode",
+                             RadioSettingValueList(LIST_LEDSW, LIST_LEDSW[
+                                 _mem.settings.ledsw]))
+        basic.append(ledsw)
+
+        beep = RadioSetting("settings.beep", "Beep",
+                            RadioSettingValueBoolean(bool(_mem.settings.beep)))
+        basic.append(beep)
+
+        ring = RadioSetting("settings.ring", "Ring",
+                            RadioSettingValueList(LIST_RING, LIST_RING[
+                                _mem.settings.ring]))
+        basic.append(ring)
+
+        bcl = RadioSetting("settings.bcl", "Busy channel lockout",
+                           RadioSettingValueBoolean(bool(_mem.settings.bcl)))
+        basic.append(bcl)
+
+        tot = RadioSetting("settings.tot", "Timeout Timer",
+                           RadioSettingValueList(LIST_TIMEOUT, LIST_TIMEOUT[
+                               _mem.settings.tot]))
+        basic.append(tot)
+
+        if _mem.upper.vfoa.sql == 0xFF:
+            val = 0x04
+        else:
+            val = _mem.upper.vfoa.sql
+        sqla = RadioSetting("upper.vfoa.sql", "Squelch (Upper)",
+                            RadioSettingValueInteger(0, 9, val))
+        basic.append(sqla)
+
+        if _mem.lower.vfob.sql == 0xFF:
+            val = 0x04
+        else:
+            val = _mem.lower.vfob.sql
+        sqlb = RadioSetting("lower.vfob.sql", "Squelch (Lower)",
+                            RadioSettingValueInteger(0, 9, val))
+        basic.append(sqlb)
+
+        return top
+
+    def set_settings(self, settings):
+        _settings = self._memobj.settings
+        _mem = self._memobj
+        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
+
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        match_size = False
+        match_model = False
+
+        # testing the file data size
+        if len(filedata) == MEM_SIZE + 8:
+            match_size = True
+
+        # testing the firmware model fingerprint
+        match_model = model_match(cls, filedata)
+
+        if match_size and match_model:
+            return True
+        else:
+            return False
+
+
+class LT725UVUpper(LT725UV):
+    VARIANT = "Upper"
+    _vfo = "upper"
+
+
+class LT725UVLower(LT725UV):
+    VARIANT = "Lower"
+    _vfo = "lower"
diff --git a/chirp/drivers/puxing_px888k.py b/chirp/drivers/puxing_px888k.py
new file mode 100644
index 0000000..cc6f3c5
--- /dev/null
+++ b/chirp/drivers/puxing_px888k.py
@@ -0,0 +1,1877 @@
+# Copyright 2016 Leo Barring <leo.barring at protonmail.com>
+#
+# 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 2 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/>.
+
+from chirp import chirp_common, directory, memmap, \
+                  bitwise, settings, errors
+from struct import pack
+import logging
+
+LOG = logging.getLogger(__name__)
+
+SUPPORT_NONSPLIT_DUPLEX_ONLY = False
+SUPPORT_SPLIT_BUT_DEFAULT_TO_NONSPLIT_ALWAYS = True
+UNAMBIGUOUS_CROSS_MODES_ONLY = True
+
+# With this setting enabled, some CHIRP settings are stored in
+# thought-to-be junk/padding data of the channel memories.
+# Enabling this feature while using CHIRP with an actual radio
+# and any effects thereof is entirely the responsibility of the user.
+ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES = False
+
+MEM_FORMAT = """
+// data fields are generally written 0xff if they are unset
+struct {
+
+#seekto 0x0000;
+    struct {
+        struct {
+            // 0-3
+            bbcd rx_freq[4];
+
+            // 4-7
+            bbcd tx_freq[4];
+
+            // 8-9 A-B
+            struct {
+                u8 digital:1,
+                   invert:1,
+                   high:6;
+                u8 low;
+            } tone[2];
+            // tx_squelch on 0, rx_squelch on 1
+
+            // C
+            // the duplex sign is not used for memories,
+            // but is kept for interface consistency
+            u8 duplex_sign:2,
+               compander:1,
+               txpower:1,
+               modulation_width:1,
+               txrx_reverse:1,
+               bcl:2;
+
+            // D
+            u8 scrambler_type:3,
+               use_scrambler:1,
+               opt_signal:2,
+               ptt_id_edge:2;
+
+            // E-F
+            // u8 _unknown_000E[2];
+            %s
+        } data[128];
+        struct {
+            // 0-5, alt 8-D
+            char entry[6];
+
+            // 6-8, alt E-F
+            char _unknown_0806[2];
+        } names[128];
+
+#seekto 0x0c20;
+        bit present[128];
+
+#seekto 0x0c30;
+        bit priority[128];
+    } channel_memory;
+
+#seekto 0x0c00;
+    struct {
+        // 0-3
+        bbcd rx_freq[4];
+
+        // 4-7
+        bbcd tx_freq[4]; // actually offset, but kept for name consistency
+
+        // 8
+        struct {
+            u8 digital:1,
+               invert:1,
+               high:6;
+            u8 low;
+        } tone[2]; // tx_squelch on 0, rx_squelch on 1
+
+        // C
+        u8 duplex_sign:2,
+           compander:1,
+           txpower:1,
+           modulation_width:1,
+           txrx_reverse:1,
+           bcl:2;
+
+        // D
+        u8 scrambler_type:3,
+           use_scrambler:1,
+           opt_signal:2,
+           ptt_id_edge:2;
+
+        // E-F
+        // u8 _unknown_0C0E[2];
+        %s
+
+    } vfo_data[2];
+
+#seekto 0xc40;
+    struct {
+        // 0-5
+        char model_string[6]; // typically PX888D, unknown if rw or ro
+
+        // 6-7
+        u8 _unknown_0C46[2];
+
+        // 8-9
+        struct {
+            bbcd lower_freq[2];
+            bbcd upper_freq[2];
+        } band_limits[2];
+    } model_information;
+
+#seekto 0x0c50;
+    char radio_information_string[16];
+
+#seekto 0x0c60;
+    struct {
+        // 0
+        u8 ptt_cancel_sq:1,
+           dis_ptt_id:1,
+           workmode_b:2,
+           use_roger_beep:1,
+           msk_reverse:1,
+           workmode_a:2;
+
+        // 1
+        u8 backlight_color:2,
+           backlight_mode:2,
+           dual_single_watch:1,
+           auto_keylock:1,
+           scan_mode:2;
+
+        // 2
+        u8 rx_stun:1,
+           tx_stun:1,
+           boot_message_mode:2,
+           battery_save:1,
+           key_beep:1,
+           voice_announce:2;
+
+        // 3
+        bbcd squelch_level;
+
+        // 4
+        bbcd tx_timeout;
+
+        // 5
+        u8 allow_keypad:1,
+           relay_without_disable_tail:1
+           _unknown_0C65:1,
+           call_channel_active:1,
+           vox_gain:4;
+
+        // 6
+        bbcd vox_delay;
+
+        // 7
+        bbcd vfo_step;
+
+        // 8
+        bbcd ptt_id_type;
+
+        // 9
+        u8 keypad_lock:1,
+            _unknown_0C69_1:1,
+            side_button_hold_mode:2,
+            dtmf_sidetone:1,
+            _unknown_0C69_2:1,
+            side_button_click_mode:2;
+
+        // A
+        u8 roger_beep:4,
+           main_watch:1,
+           _unknown_0C6A:3;
+
+        // B
+        u8 channel_a;
+
+        // C
+        u8 channel_b;
+
+        // D
+        u8 priority_channel;
+
+        // E
+        u8 wait_time;
+
+        // F
+        u8 _unknown_0C6F;
+
+        // 0-7 on next block
+        u8 _unknown_0C70[8];
+
+        // 8-D on next block
+        char boot_message[6];
+    } opt_settings;
+
+#seekto 0x0c80;
+    struct {
+        // these fields are used for all ptt id forms (msk/dtmf/5t)
+        // (only one can be active and stored at a time)
+        // and different constraints are applied depending
+        // on the ptt id type
+        u8 entry[7];
+        u8 length;
+    } ptt_id_data[2];
+    // 0 is BOT, 1 is EOT
+
+#seekto 0x0c90;
+    struct {
+        // 0
+        u8 _unknown_0C90;
+
+        // 1
+        u8 _unknown_0C91_1:3,
+           channel_stepping:1,
+           unknown_0C91_2:1
+           receive_range:2
+           unknown_0C91_3:1;
+
+        // 2-3
+        u8 _unknown_0C92[2];
+
+        // 4-7
+        u8 vfo_freq[4];
+
+        // 8-F and two more blocks
+        struct {
+            u8 entry[4];
+        } memory[10];
+    } fm_radio;
+
+#seekto 0x0cc0;
+    struct {
+        char id_code[4];
+        struct {
+            char entry[4];
+        } phone_book[9];
+    } msk_settings;
+
+#seekto 0x0cf0;
+    struct {
+        // 0-3
+        bbcd rx_freq[4];
+
+        // 4-7
+        bbcd tx_freq[4];
+
+        // 8
+        struct {
+            u8 digital:1,
+               invert:1,
+               high:6;
+            u8 low;
+        } tone[2];
+        // tx_squelch on 0, rx_squelch on 1
+
+
+        // C
+        // the duplex sign is not used for the CALL,
+        // channel but is kept for interface consistency
+        u8 duplex_sign:2,
+           compander:1,
+           txpower:1,
+           modulation_width:1,
+           txrx_reverse:1
+           bcl:2;
+
+        // D
+        u8 scrambler_type:3,
+           use_scrambler:1,
+           opt_signal:2,
+           ptt_id_edge:2;
+
+        // E-F
+        // u8 _unknown_0CFE[2];
+        %s
+    } call_channel;
+
+#seekto 0x0d00;
+    struct {
+
+        // DTMF codes are stored as hex half-bytes,
+        // 0-9 A-D are mapped straight
+        // DTMF '*' is HEX E, DTMF '#' is HEX F
+
+        // 0x0d00
+        struct {
+            u8 digit_length;       // 0x05 to 0x14 corresponding to 50-200ms
+            u8 inter_digit_pause;  // same
+            u8 first_digit_length; // same
+            u8 first_digit_delay;  // 0x02 to 0x14 corresponding to 100-1000ms
+        } timing;
+
+#seekto 0x0d30;
+        u8 _unknown_0D30[2]; // 0-1
+        u8 group_code;       // 2
+        u8 reset_time;       // 3
+        u8 alert_transpond;  // 4
+        u8 id_code[4];       // 5-8
+        u8 _unknown_0D39[4]; // 9-C
+        u8 id_code_length;   // D
+        u8 _unknown_0d3e[2]; // E-F
+
+// 0x0d40
+        u8 tx_stun_code[4];
+        u8 _unknown_0D44[4];
+        u8 tx_stun_code_length;
+        u8 cancel_tx_stun_code_length;
+        u8 cancel_tx_stun_code[4];
+        u8 _unknown_0D4E[2];
+
+// 0x0d50
+        u8 rxtx_stun_code[4];
+        u8 _unknown_0D54[4];
+        u8 rxtx_stun_code_length;
+        u8 cancel_rxtx_stun_code_length;
+        u8 cancel_rxtx_stun_code[4];
+        u8 _unknown_0D4E[2];
+
+// 0x0d60
+        struct {
+            u8 entry[5];
+            u8 _unknown_0D65[3];
+            u8 length;
+            u8 _unknown_0D69[7];
+        } phone_book[9];
+    } dtmf_settings;
+
+#seekto 0x0e00;
+    struct {
+        u8 delay;
+        u8 _unknown_0E01[5];
+        u8 alert_transpond;
+        u8 reset_time;
+        u8 tone_standard;
+        u8 id_code[3];
+
+#seekto 0x0e20;
+        struct {
+            u8 period;
+            u8 group_code:4,
+               repeat_code:4;
+        } tone_settings[4];
+        // the order is ZVEI1 ZVEI2 CCIR1 CCITT
+
+#seekto 0x0e40;
+        // 5-Tone tone standard frequency table
+        // unknown use, changing the values does not seem to have
+        // any effect on the produced sound, but the values are not
+        // overwritten either.
+        il16 tone_frequency_table[16];
+
+// 0xe60
+        u8 tx_stun_code[5];
+        u8 _unknown_0E65[3];
+        u8 tx_stun_code_length;
+        u8 cancel_tx_stun_code_length;
+        u8 cancel_tx_stun_code[5];
+        u8 _unknown_0E6F;
+
+// 0xe70
+        u8 rxtx_stun_code[5];
+        u8 _unknown_0E75[3];
+        u8 rxtx_stun_code_length;
+        u8 cancel_rxtx_stun_code_length;
+        u8 cancel_rxtx_stun_code[5];
+        u8 _unknown_0E7F;
+
+// 0xe80
+        struct {
+            u8 entry[3];
+        } phone_book[9];
+    } five_tone_settings;
+} mem;"""
+
+# various magic numbers and strings, apart from the memory format
+if ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES:
+    LOG.warn("ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES"
+             "AND/OR DANGEROUS FEATURES ENABLED")
+    MEM_FORMAT = MEM_FORMAT % (
+            "u8 _unknown_000E_1: 6,\n"
+            "   experimental_duplex_mode_indicator: 1,\n"
+            "   experimental_cross_mode_indicator: 1;\n"
+            "u8 _unknown_000F;",
+            "u8 _unknown_0C0E_1: 6,\n"
+            "   experimental_duplex_mode_indicator: 1,\n"
+            "   experimental_cross_mode_indicator: 1;\n"
+            "u8 _unknown_0C0F;",
+            "u8 _unknown_0CFE_1: 6,\n"
+            "   experimental_duplex_mode_indicator: 1,\n"
+            "   experimental_cross_mode_indicator: 1;\n"
+            "u8 _unknown_0CFF;")
+    # we don't need these settings anymore, because it's exactly what the
+    # experimental features are about
+    SUPPORT_SPLIT_BUT_DEFAULT_TO_NONSPLIT_ALWAYS = False
+    SUPPORT_NONSPLIT_DUPLEX_ONLY = False
+    UNAMBIGUOUS_CROSS_MODES_ONLY = False
+
+else:
+    MEM_FORMAT = MEM_FORMAT % (
+            "u8 _unknown_000E[2];",
+            "u8 _unknown_0C0E[2];",
+            "u8 _unknown_0CFE[2];")
+
+FILE_MAGIC = [0xc40, 0xc50,
+              '\x50\x58\x38\x38\x38\x44\x00\xff'
+              '\x13\x40\x17\x60\x40\x00\x48\x00']
+HANDSHAKE_OUT = b'XONLINE'
+HANDSHAKE_IN = [b'PX888D\x00\xff']
+
+LOWER_READ_BOUND = 0
+UPPER_READ_BOUND = 0x1000
+LOWER_WRITE_BOUND = 0
+UPPER_WRITE_BOUND = 0x0fc0
+BLOCKSIZE = 64
+
+OFF_INT = ["Off"] + [str(x+1) for x in range(100)]
+OFF_ON = ["Off", "On"]
+INACTIVE_ACTIVE = ["Inactive", "Active"]
+NO_YES = ["No", "Yes"]
+YES_NO = ["Yes", "No"]
+
+BANDS = [(134000000, 176000000),  # VHF
+         (400000000, 480000000)]  # UHF
+
+SPECIAL_CHANNELS = {'VFO-A': -2, 'VFO-B': -1, 'CALL': 0}
+SPECIAL_NUMBERS = {-2: 'VFO-A', -1: 'VFO-B', 0: 'CALL'}
+
+DUPLEX_MODES = ['', '+', '-', 'split']
+if SUPPORT_NONSPLIT_DUPLEX_ONLY:
+    DUPLEX_MODES = ['', '+', '-']
+
+TONE_MODES = ["", "Tone", "TSQL", "DTCS", "Cross"]
+
+CROSS_MODES = ["Tone->Tone",
+               "DTCS->",
+               "->DTCS",
+               "Tone->DTCS",
+               "DTCS->Tone",
+               "->Tone",
+               "DTCS->DTCS",
+               "Tone->"]
+if UNAMBIGUOUS_CROSS_MODES_ONLY:
+    CROSS_MODES = ["Tone->Tone",
+                   "DTCS->",
+                   "->DTCS",
+                   "Tone->DTCS",
+                   "DTCS->Tone",
+                   "->Tone",
+                   "DTCS->DTCS"]
+
+MODES = ["NFM", "FM"]
+
+# Only the 'High' power level is quantified in the manual for
+# the radio (4W for VHF, 5W for UHF), a web search turned
+# up numbers for the 'Low' power level (0.5W for VHF and
+# 0.7W for UHF), but they are not official to my knowledge
+# and should be taken with a grain of salt or two.
+# Numbers used in code is the averages
+POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=0.6),
+                chirp_common.PowerLevel("High", watts=4.5)]
+
+SKIP_MODES = ["", "S"]
+BCL_MODES = ["Off", "Carrier", "QT/DQT"]
+SCRAMBLER_MODES = OFF_INT[0:9]
+PTT_ID_EDGES = ["Off", "BOT", "EOT", "Both"]
+OPTSIGN_MODES = ["None", "DTMF", "5-Tone", "MSK"]
+
+VFO_STRIDE = ['5kHz', '6.25kHz', '10kHz', '12.5kHz', '25kHz']
+AB = ['A', 'B']
+WATCH_MODES = ['Single watch', 'Dual watch']
+AB_MODES = ['VFO', 'Memory index', 'Memory name', 'Memory frequency']
+SCAN_MODES = ["Time", "Carrier", "Seek"]
+WAIT_TIMES = [("0.3s", 6), ("0.5s", 10)] +\
+             [("%ds" % t, t*20) for t in range(1, 13)]
+
+BUTTON_MODES = ["Send call list data",
+                "Emergency alarm",
+                "Send 1750Hz signal",
+                "Open squelch"]
+BOOT_MESSAGE_TYPES = ["Off", "Battery voltage", "Custom message"]
+TALKBACK = ['Off', 'Chinese', 'English']
+BACKLIGHT_COLORS = zip(["Blue", "Orange", "Purple"], range(1, 4))
+VOX_GAIN = OFF_INT[0:10]
+VOX_DELAYS = ['1s', '2s', '3s', '4s']
+TRANSMIT_ALARMS = ['Off', '30s', '60s', '90s', '120s',
+                   '150s', '180s', '210s', '240s', '270s']
+
+DATA_MODES = ['MSK', 'DTMF', '5-Tone']
+
+ASCIIPART = ''.join([chr(x) for x in range(0x20, 0x7f)])
+DTMF = "0123456789ABCD*#"
+HEXADECIMAL = "0123456789ABCDEF"
+
+ROGER_BEEP = OFF_INT[0:11]
+BACKLIGHT_MODES = ["Off", "Auto", "On"]
+
+TONE_RESET_TIME = ['Off'] + ['%ds' % x for x in range(1, 256)]
+DTMF_TONE_RESET_TIME = TONE_RESET_TIME[0:16]
+
+DTMF_GROUPS = zip(["Off", "A", "B", "C", "D", "*", "#"], [255]+range(10, 16))
+FIVE_TONE_STANDARDS = ['ZVEI1', 'ZVEI2', 'CCIR1', 'CCITT']
+
+# should mimic the defaults in the memedit MemoryEditor somewhat
+#                          0   1   2   3   4   5   6   7
+SANE_MEMORY_DEFAULT = b"\x14\x61\x00\x00\x14\x61\x00\x00" + \
+                      b"\xff\xff\xff\xff\xc8\x00\xff\xff"
+#                          8   9   A   B   C   D   E   F
+
+
+# these two option sets are listed differently like this in the stock software,
+# so I'm keeping them separate for now if they are in fact identical
+# in behaviour, that should probably be amended
+DTMF_ALERT_TRANSPOND = zip(['Off', 'Call alert',
+                            'Transpond-alert',
+                            'Transpond-ID code'],
+                           [255]+range(1, 4))
+FIVE_TONE_ALERT_TRANSPOND = zip(['Off', 'Alert tone',
+                                 'Transpond', 'Transpond-ID code'],
+                                [255]+range(1, 4))
+
+BFM_BANDS = ['87.5-108MHz', '76.0-91.0MHz', '76.0-108.0MHz', '65.0-76.0MHz']
+BFM_STRIDE = ['100kHz', '50kHz']
+
+
+def piperead(pipe, amount):
+    """read some data, catch exceptions, validate length of data read"""
+    try:
+        d = pipe.read(amount)
+    except Exception as e:
+        raise errors.RadioError(
+                "Tried to read %d bytes, but got an exception: %s" %
+                (amount, repr(e)))
+    if d is None:
+        raise errors.RadioError(
+                "Tried to read %d bytes, but read operation returned <None>." %
+                (amount))
+    if d is None or len(d) != amount:
+        raise errors.RadioError(
+                "Tried to read %d bytes, but got %d bytes instead." %
+                (amount, len(d)))
+    return d
+
+
+def pipewrite(pipe, data):
+    """write some data, catch exceptions, validate length of data written"""
+    try:
+        n = pipe.write(data)
+    except Exception as e:
+        raise errors.RadioError(
+                "Tried to write %d bytes, but got an exception: %s." %
+                (len(data), repr(e)))
+    if n is None:
+        raise errors.RadioError(
+                "Tried to write %d bytes, but operation returned <None>." %
+                (len(data)))
+    if n != len(data):
+        raise errors.RadioError(
+                "Tried to write %d bytes, but wrote %d bytes instead." %
+                (len(data), n))
+
+
+def attempt_initial_handshake(pipe):
+    """try to do the initial handshake"""
+    pipewrite(pipe, HANDSHAKE_OUT)
+    x = piperead(pipe, len(HANDSHAKE_IN[0]))
+    if x in HANDSHAKE_IN:
+        return True
+    LOG.debug("Handshake failed: received: %s expected one of: %s" %
+              (repr(x), repr(HANDSHAKE_IN)))
+    return False
+
+
+def initial_handshake(pipe, tries):
+    """do an initial handshake attempt up to tries times"""
+    x = False
+    for i in range(tries):
+        x = attempt_initial_handshake(pipe)
+        if x:
+            break
+    if not x:
+        raise errors.RadioError("Initial handshake failed all ten tries.")
+
+
+def mk_writecommand(addr):
+    """makes a write command from an address specification"""
+    return pack('>cHc', b'W', addr, b'@')
+
+
+def mk_readcommand(addr):
+    """makes a read command from an address specification"""
+    return pack('>cHc', b'R', addr, b'@')
+
+
+def expect_ack(pipe):
+    x = piperead(pipe, 1)
+    if x != b'\x06':
+        LOG.debug(
+                "Did not get ACK. received: %s, expected: '\\x06'" %
+                repr(x))
+        raise errors.RadioError("Did not get ACK when expected.")
+
+
+def end_communications(pipe):
+    """tell the radio that we are done"""
+    pipewrite(pipe, b'E')
+    expect_ack(pipe)
+
+
+def read_block(pipe, addr):
+    """read and return a chunk of data at specified address"""
+    r = mk_readcommand(addr)
+    w = mk_writecommand(addr)
+    pipewrite(pipe, r)
+    x = piperead(pipe, len(w))
+    if x != w:
+        raise errors.RadioError("Received data not following protocol.")
+    block = piperead(pipe, BLOCKSIZE)
+    return block
+
+
+def write_block(pipe, addr, block):
+    """write a chunk of data at specified address"""
+    w = mk_writecommand(addr)
+    pipewrite(pipe, w)
+    pipewrite(pipe, block)
+    expect_ack(pipe)
+
+
+def show_progress(radio, blockaddr, upper, msg):
+    """relay read/write information to the user through the gui"""
+    if radio.status_fn:
+        status = chirp_common.Status()
+        status.cur = blockaddr
+        status.max = upper
+        status.msg = msg
+        radio.status_fn(status)
+
+
+def do_download(radio):
+    """download from the radio to the memory map"""
+    initial_handshake(radio.pipe, 10)
+    memory = memmap.MemoryMap(b'\xff'*0x1000)
+    for blockaddr in range(LOWER_READ_BOUND, UPPER_READ_BOUND, BLOCKSIZE):
+        LOG.debug("Reading block "+str(blockaddr))
+        block = read_block(radio.pipe, blockaddr)
+        memory.set(blockaddr, block)
+        show_progress(radio, blockaddr, UPPER_READ_BOUND,
+                      "Reading radio memory... %04x" % blockaddr)
+    end_communications(radio.pipe)
+    return memory
+
+
+def do_upload(radio):
+    """upload from the memory map to the radio"""
+    memory = radio.get_mmap()
+    initial_handshake(radio.pipe, 10)
+    for blockaddr in range(LOWER_WRITE_BOUND, UPPER_WRITE_BOUND, BLOCKSIZE):
+        LOG.debug("Writing block "+str(blockaddr))
+        block = memory[blockaddr:blockaddr+BLOCKSIZE]
+        write_block(radio.pipe, blockaddr, block)
+        show_progress(radio, blockaddr, UPPER_WRITE_BOUND,
+                      "Writing radio memory...  % 04x" % blockaddr)
+    end_communications(radio.pipe)
+
+
+def parse_tone(t):
+    """
+    parse the tone (ctss, dtcs) part of the mmap
+    into more easily handled data types
+    """
+    # [ mode, value, polarity ]
+    if int(t.high) == 0x3f and int(t.low) == 0xff:
+        return [None, None, None]
+    elif bool(t.digital):
+        t = ['DTCS',
+             (int(t.high) & 0x0f)*100 +
+             ((int(t.low) & 0xf0) >> 4)*10 +
+             (int(t.low) & 0x0f),
+             ['N', 'R'][bool(t.invert)]]
+        if t[1] not in chirp_common.DTCS_CODES:
+            return [None, None, None]
+    else:
+        t = ['Tone',
+             ((int(t.high) & 0xf0) >> 4)*100 +
+             (int(t.high) & 0x0f)*10 +
+             ((int(t.low) & 0xf0) >> 4) +
+             (int(t.low) & 0x0f)/10.0,
+             None]
+        if t[1] not in chirp_common.TONES:
+            return [None, None, None]
+    return t
+
+
+def unparse_tone(t):
+    """parse tone data back into the format used by the radio"""
+    # [ mode, value, polarity ]
+    if t[0] == 'Tone':
+        tint = int(t[1]*10)
+        t0, tint = tint % 10, tint // 10
+        t1, tint = tint % 10, tint // 10
+        t2, tint = tint % 10, tint // 10
+        high = (tint << 4) | t2
+        low = (t1 << 4) | t0
+        digital = False
+        invert = False
+        return digital, invert, high, low
+    elif t[0] == 'DTCS':
+        tint = int(t[1])
+        t0, tint = tint % 10, tint // 10
+        t1, tint = tint % 10, tint // 10
+        high = tint
+        low = (t1 << 4) | t0
+        digital = True
+        invert = t[2] == 'R'
+        return digital, invert, high, low
+    return None
+
+
+def decode_halfbytes(data, mapping, length):
+    """
+    construct a string from a datatype
+    where each half-byte maps to a character
+    """
+    s = ''
+    for i in range(length):
+        if i & 1 == 0:
+            s += mapping[(int(data[i >> 1]) & 0xf0) >> 4]
+        else:
+            s += mapping[int(data[i >> 1]) & 0x0f]
+    return s
+
+
+def encode_halfbytes(data, datapad, mapping, fillvalue, fieldlen):
+    """encode data from a string where each character maps to a half-byte"""
+    if len(data) & 1:
+        # pad to an even length
+        data += datapad
+    o = [fillvalue] * fieldlen
+    for i in range(0, len(data), 2):
+        v = (mapping.index(data[i]) << 4) | mapping.index(data[i+1])
+        o[i >> 1] = v
+    return bytearray(o)
+
+
+def decode_ffstring(data):
+    """decode a string delimited by 0xff"""
+    s = ''
+    for b in data:
+        if int(b) == 0xff:
+            break
+        s += chr(int(b))
+    return s
+
+
+def encode_ffstring(data, fieldlen):
+    """right-pad to specified length with 0xff bytes"""
+    extra = fieldlen-len(data)
+    if extra > 0:
+        data += '\xff'*extra
+    return bytearray(data)
+
+
+def decode_dtmf(data, length):
+    """decode a field containing dtmf data into a string"""
+    if length == 0xff:
+        return ''
+    return decode_halfbytes(data, DTMF, length)
+
+
+def encode_dtmf(data, length, fieldlen):
+    """encode a string containing dtmf characters into a data field"""
+    return encode_halfbytes(data, '0', DTMF, b'\xff', fieldlen)
+
+
+def decode_5tone(data):
+    """decode a field containing 5-tone data into a string"""
+    if (int(data[2]) & 0x0f) != 0:
+        return ''
+    return decode_halfbytes(data, HEXADECIMAL, 5)
+
+
+def encode_5tone(data, fieldlen):
+    """encode a string containing 5-tone characters into a data field"""
+    return encode_halfbytes(data, '0', HEXADECIMAL, b'\xff', fieldlen)
+
+
+def decode_freq(data):
+    """decode frequency data for the broadcast fm radio memories"""
+    data_out = ''
+    if data[0] != 0xff:
+        data_out = chirp_common.format_freq(
+                int(decode_halfbytes(data, "0123456789", len(data)))*100000)
+    return data_out
+
+
+def encode_freq(data, fieldlen):
+    """encode frequency data for the broadcast fm radio memories"""
+    data_out = bytearray('\xff')*fieldlen
+    if data != '':
+        data_out = encode_halfbytes((('%%0%di' % (fieldlen << 1)) %
+                                     int(chirp_common.parse_freq(data)/10)),
+                                    '', '0123456789', '', fieldlen)
+    return data_out
+
+
+def sbyn(s, n):
+    """setting by name"""
+    return filter(lambda x: x.get_name() == n, s)[0]
+
+
+# These helper classes provide a direct link between the value
+# of the widget shown in the ui, and the setting in the memory
+# map of the radio, lessening the need to write large chunks
+# of code, first for populating the ui from the memory map,
+# then secondly for parsing the values back.
+# By supplying the memory map entry to the setting instance,
+# it is possible to automatically 1) initialize the value of
+# the setting, as well as 2) automatically update the memory
+# value when the user changes it in the ui, without adding
+# any code outside the class.
+class MappedIntegerSettingValue(settings.RadioSettingValueInteger):
+    """"
+    Integer setting, with the possibility to add translation
+    functions between memory map <-> integer setting
+    """
+    def __init__(self, val_mem, minval, maxval, step=1,
+                 int_from_mem=lambda x: int(x),
+                 mem_from_int=lambda x: x,
+                 autowrite=True):
+        """
+        val_mem      - memory map entry for the value
+        minval       - the minimum value allowed
+        maxval       - maximum value allowed
+        step         - value stepping
+        int_from_mem - function to convert memory entry to integer
+        mem_from_int - function to convert integer to memory entry
+        autowrite    - automatically write the memory map entry
+                       when the value is changed
+        """
+        self._val_mem = val_mem
+        self._int_from_mem = int_from_mem
+        self._mem_from_int = mem_from_int
+        self._autowrite = autowrite
+        settings.RadioSettingValueInteger.__init__(
+                self,
+                minval, maxval, self._int_from_mem(val_mem), step)
+
+    def set_value(self, x):
+        settings.RadioSettingValueInteger.set_value(self, x)
+        if self._autowrite:
+            self.write_mem()
+
+    def write_mem(self):
+        if self.get_mutable() and self._mem_from_int is not None:
+            self._val_mem.set_value(self._mem_from_int(
+                settings.RadioSettingValueInteger.get_value(self)))
+
+
+class MappedListSettingValue(settings.RadioSettingValueMap):
+    """Mapped list setting"""
+    def __init__(self, val_mem, options, autowrite=True):
+        """
+        val_mem      - memory map entry for the value
+        options      - either a list of strings options to present,
+                       mapped to integers 0...n
+                       in the memory map entry, or a list of tuples
+                       ("option description", memory map value)
+        int_from_mem - function to convert memory entry to integer
+        mem_from_int - function to convert integer to memory entry
+        autowrite    - automatically write the memory map entry when
+                       the value is changed
+        """
+        self._val_mem = val_mem
+        self._autowrite = autowrite
+        if not isinstance(options[0], tuple):
+            options = zip(options, range(len(options)))
+        settings.RadioSettingValueMap.__init__(
+                self,
+                options, mem_val=int(val_mem))
+
+    def set_value(self, value):
+        settings.RadioSettingValueMap.set_value(self, value)
+        if self._autowrite:
+            self.write_mem()
+
+    def write_mem(self):
+        if self.get_mutable():
+            self._val_mem.set_value(
+                    settings.RadioSettingValueMap.get_mem_val(self))
+
+
+class MappedCodedStringSettingValue(settings.RadioSettingValueString):
+    """
+    generic base class for a number of mapped presented-as-strings
+    values which may need conversion between mem and string,
+    and may store a length value in a separate mem field
+    """
+    def __init__(self, val_mem, len_mem, min_length, max_length,
+                 charset=ASCIIPART, padchar=' ', autowrite=True,
+                 str_from_mem=lambda mve, lve: str(mve[0:int(lve)]),
+                 mem_val_from_str=lambda s, fl: s[0:fl],
+                 mem_len_from_int=lambda l: l):
+        """
+        val_mem          - memory map entry for the value
+        len_mem          - memory map entry for the length (or None)
+        min_length       - length that the string will be right-padded to
+        max_length       - maximum length of the string, set as maxlength
+                           for the RadioSettingValueString
+        charset          - the allowed charset
+        padchar          - the character that will be used to pad short
+                           strings, if not in the charset, charset[0] is used
+        autowrite        - automatically call write_mem when the ui value
+                           change
+        str_from_mem     - function to convert from memory entry to string
+                           value, form:
+                           func(value_entry, length_entry or none) -> string
+        mem_val_from_str - function to convert from string value to
+                           memory-fitting value, form:
+                           func(string, value_entry_length) ->
+                           value to store in value entry
+        mem_len_from_int - function to convert from string length to
+                           memory-fitting value, form:
+                           func(stringlength) -> value to store in length entry
+        """
+        self._min_length = min_length
+        self._val_mem = val_mem
+        self._len_mem = len_mem
+        self._padchar = padchar
+        if padchar not in charset:
+            self._padchar = charset[0]
+        self._autowrite = autowrite
+        self._str_from_mem = str_from_mem
+        self._mem_val_from_str = mem_val_from_str
+        self._mem_len_from_int = mem_len_from_int
+        settings.RadioSettingValueString.__init__(
+                self,
+                0, max_length,
+                self._str_from_mem(self._val_mem, self._len_mem),
+                charset=charset, autopad=False)
+
+    def set_value(self, value):
+        """
+        Set the value of the string, pad if below minimum length,
+        unless it's '' to provide a distinction between
+        uninitialized/reset data and needs-to-be-padded data
+        """
+        while len(value) < self._min_length and len(value) != 0:
+            value += self._padchar
+        settings.RadioSettingValueString.set_value(self, value)
+        if self._autowrite:
+            self.write_mem()
+
+    def write_mem(self):
+        """update the memory"""
+        if not self.get_mutable() or self._mem_val_from_str is None:
+            return
+        v = self.get_value()
+        l = len(v)
+        self._val_mem.set_value(self._mem_val_from_str(v, len(self._val_mem)))
+        if self._len_mem is not None and self._mem_len_from_int is not None:
+            self._len_mem.set_value(self._mem_len_from_int(l))
+
+
+class MappedFFStringSettingValue(MappedCodedStringSettingValue):
+    """
+    Mapped string setting, tailored for the puxing px888k,
+    which uses 0xff terminated strings.
+    """
+    def __init__(self, val_mem, min_length, max_length,
+                 charset=ASCIIPART, padchar=' ', autowrite=True):
+        MappedCodedStringSettingValue.__init__(
+                self,
+                val_mem, None, min_length, max_length,
+                charset=charset, padchar=padchar, autowrite=autowrite,
+                str_from_mem=lambda mve, lve: decode_ffstring(mve),
+                mem_val_from_str=lambda s, fl: encode_ffstring(s, fl),
+                mem_len_from_int=None)
+
+
+class MappedDTMFStringSettingValue(MappedCodedStringSettingValue):
+    """
+    Mapped string setting, tailored for the puxing px888k
+    field pairs (value and length) storing DTMF codes
+    """
+    def __init__(self, val_mem, len_mem, min_length, max_length,
+                 autowrite=True):
+        MappedCodedStringSettingValue.__init__(
+                self,
+                val_mem, len_mem, min_length, max_length,
+                charset=DTMF, padchar='0', autowrite=autowrite,
+                str_from_mem=lambda mve, lve: decode_dtmf(mve, lve),
+                mem_val_from_str=lambda s, fl: encode_dtmf(s, len(s), fl))
+
+
+class MappedFiveToneStringSettingValue(MappedCodedStringSettingValue):
+    """
+    Mapped string setting, tailored for the puxing px888k
+    fields storing 5-Tone codes
+    """
+    def __init__(self, val_mem, autowrite=True):
+        MappedCodedStringSettingValue.__init__(
+                self,
+                val_mem, None, 0, 5, charset=HEXADECIMAL, padchar='0',
+                autowrite=autowrite,
+                str_from_mem=lambda mve, lve: decode_5tone(mve),
+                mem_val_from_str=lambda s, fl: encode_5tone(s, fl),
+                mem_len_from_int=None)
+
+
+class MappedFreqStringSettingValue(MappedCodedStringSettingValue):
+    """
+    Mapped string setting, tailored for the puxing px888k
+    fields for the broadcast FM radio frequencies
+    """
+    def __init__(self, val_mem, autowrite=True):
+        MappedCodedStringSettingValue.__init__(
+                self,
+                val_mem, None, 0, 128, charset=ASCIIPART, padchar=' ',
+                autowrite=autowrite,
+                str_from_mem=lambda mve, lve: decode_freq(mve),
+                mem_val_from_str=lambda s, fl: encode_freq(s, fl))
+
+
+# These functions lessen the amount of boilerplate on the form
+# x = RadioSetting("AAA", "BBB", SomeKindOfRadioSettingValue( ... ))
+# to
+# x = some_kind_of_setting("AAA", "BBB", ... )
+def integer_setting(k, n, *args, **kwargs):
+    return settings.RadioSetting(
+            k, n,
+            MappedIntegerSettingValue(*args, **kwargs))
+
+
+def list_setting(k, n, *args, **kwargs):
+    return settings.RadioSetting(
+            k, n,
+            MappedListSettingValue(*args, **kwargs))
+
+
+def ff_string_setting(k, n, *args, **kwargs):
+    return settings.RadioSetting(
+            k, n,
+            MappedFFStringSettingValue(*args, **kwargs))
+
+
+def dtmf_string_setting(k, n, *args, **kwargs):
+    return settings.RadioSetting(
+            k, n,
+            MappedDTMFStringSettingValue(*args, **kwargs))
+
+
+def five_tone_string_setting(k, n, *args, **kwargs):
+    return settings.RadioSetting(
+            k, n,
+            MappedFiveToneStringSettingValue(*args, **kwargs))
+
+
+def frequency_setting(k, n, *args, **kwargs):
+    return settings.RadioSetting(
+            k, n,
+            MappedFreqStringSettingValue(*args, **kwargs))
+
+
+ at directory.register
+class Puxing_PX888K_Radio(chirp_common.CloneModeRadio):
+    """Puxing PX-888K"""
+    VENDOR = "Puxing"
+    MODEL = "PX-888K"
+    BAUD_RATE = 9600
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        if len(filedata) == UPPER_READ_BOUND:
+            if filedata[FILE_MAGIC[0]:FILE_MAGIC[1]] == FILE_MAGIC[2]:
+                return True
+            else:
+                LOG.debug("The data at 0x0c40 does not match the PX-888K")
+        else:
+            LOG.debug("The file size does not match.")
+        return False
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.has_bank_index = False
+        rf.has_dtcs = True
+        rf.has_ctone = True
+        rf.has_rx_dtcs = True
+        rf.has_dtcs_polarity = True
+        rf.has_mode = True
+        rf.has_offset = True
+        rf.has_name = True
+        rf.has_bank = False
+        rf.has_bank_names = False
+        rf.has_tuning_step = False
+        rf.has_cross = True
+        rf.has_infinite_number = False
+        rf.has_nostep_tuning = False
+        rf.has_comment = False
+        rf.has_settings = True
+        if SUPPORT_NONSPLIT_DUPLEX_ONLY:
+            rf.can_odd_split = False
+        else:
+            rf.can_odd_split = True
+
+        rf.valid_modes = MODES
+        rf.valid_tmodes = TONE_MODES
+        rf.valid_duplexes = DUPLEX_MODES
+        rf.valid_bands = BANDS
+        rf.valid_skips = SKIP_MODES
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_characters = ASCIIPART
+        rf.valid_name_length = 6
+        rf.valid_cross_modes = CROSS_MODES
+        rf.memory_bounds = (1, 128)
+        rf.valid_special_chans = SPECIAL_CHANNELS.keys()
+        return rf
+
+    def sync_in(self):
+        self._mmap = do_download(self)
+        self.process_mmap()
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+    def sync_out(self):
+        do_upload(self)
+
+    def _set_sane_defaults(self, data):
+        # thanks thayward!
+        data.set_raw(SANE_MEMORY_DEFAULT)
+
+    def _uninitialize(self, data, n):
+        if isinstance(data, bitwise.arrayDataElement):
+            data.set_value(b"\xff"*n)
+        else:
+            data.set_raw(b"\xff"*n)
+
+    def _get_memory_structs(self, number):
+        """
+        fetch the correct data structs no matter
+        if its regular or special channels,
+        no matter if they're referred by name or channel index
+        """
+        index = 2501
+        i = -42
+        designator = 'INVALID'
+        isregular = False
+        iscall = False
+        isvfo = False
+        _data = None
+        _name = None
+        _present = None
+        _priority = None
+        if number in SPECIAL_NUMBERS.keys():
+            index = number
+            # speical by index
+            designator = SPECIAL_NUMBERS[number]
+        elif number in SPECIAL_CHANNELS.keys():
+            # special by name
+            index = SPECIAL_CHANNELS[number]
+            designator = number
+        elif number > 0:
+            # regular by number
+            index = number
+            designator = number
+
+        if index < 0:
+            isvfo = True
+            _data = self._memobj.mem.vfo_data[index+2]
+        elif index == 0:
+            iscall = True
+            _data = self._memobj.mem.call_channel
+        elif index > 0:
+            isregular = True
+            i = number - 1
+            _data = self._memobj.mem.channel_memory.data[i]
+            _name = self._memobj.mem.channel_memory.names[i].entry
+            _present = self._memobj.mem.channel_memory.present[
+                    (i & 0x78) | (7-(i & 0x07))]
+            _priority = self._memobj.mem.channel_memory.priority[
+                    (i & 0x78) | (7-(i & 0x07))]
+
+        if _data == bytearray(0xff)*16:
+            self._set_sane_defaults(_data)
+
+        return (index, designator,
+                _data, _name, _present, _priority,
+                isregular, isvfo, iscall)
+
+    def get_raw_memory(self, number):
+        x = self._get_memory_structs(number)
+        return repr(x[2])
+
+    def get_memory(self, number):
+        mem = chirp_common.Memory()
+        (index, designator,
+            _data, _name, _present, _priority,
+            isregular, isvfo, iscall) = self._get_memory_structs(number)
+
+        mem.number = index
+        mem.extd_number = designator
+
+        # handle empty channels
+        if isregular:
+            if bool(_present):
+                mem.empty = False
+                mem.name = str(decode_ffstring(_name))
+                mem.skip = SKIP_MODES[1-int(_priority)]
+            else:
+                mem.empty = True
+                mem.name = ''
+                return mem
+        else:
+            mem.empty = False
+            mem.name = ''
+
+        # get frequency data
+        mem.freq = int(_data.rx_freq)*10
+        mem.offset = int(_data.tx_freq)*10
+
+        # interpret frequency data
+        # only the vfo channels support duplex,
+        # memory channels operate in split mode all the time
+        if isvfo:
+            mem.duplex = DUPLEX_MODES[int(_data.duplex_sign)]
+            if mem.duplex == '-':
+                mem.offset = mem.freq - mem.offset
+            elif mem.duplex == '':
+                mem.offset = 0
+            elif mem.duplex == '+':
+                mem.offset = mem.offset - mem.freq
+        else:
+            if mem.freq == mem.offset:
+                mem.duplex = ''
+                mem.offset = 0
+            elif SUPPORT_NONSPLIT_DUPLEX_ONLY or \
+                    SUPPORT_SPLIT_BUT_DEFAULT_TO_NONSPLIT_ALWAYS:
+                if mem.freq > mem.offset:
+                    mem.offset = mem.freq - mem.offset
+                    mem.duplex = '-'
+                elif mem.freq < mem.offset:
+                    mem.offset = mem.offset - mem.freq
+                    mem.duplex = '+'
+            else:
+                mem.duplex = 'split'
+
+        # get tone data
+        txtone = parse_tone(_data.tone[0])
+        rxtone = parse_tone(_data.tone[1])
+        chirp_common.split_tone_decode(mem, txtone, rxtone)
+
+######################################################################
+        if ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES:
+            # override certain settings based on flags
+            # that we have set in junk areas of the memory
+            # or basically, we BELIEVE this to be junk memory,
+            # hence why it's experimental and dangerous
+            if bool(_data.experimental_cross_mode_indicator) is False:
+                if mem.tmode == 'Tone':
+                    mem.cross_mode = 'Tone->'
+                elif mem.tmode == 'TSQL':
+                    mem.cross_mode = 'Tone->Tone'
+                elif mem.tmode == 'DTCS':
+                    mem.cross_mode = 'DTCS->DTCS'
+                mem.tmode = 'Cross'
+######################################################################
+
+        # transmit mode and power level
+        mem.mode = MODES[bool(_data.modulation_width)]
+        mem.power = POWER_LEVELS[_data.txpower]
+
+        # extra channel settings
+        mem.extra = settings.RadioSettingGroup(
+                "extra",
+                "extra",
+                list_setting("Busy channel lockout",
+                             "BCL",
+                             _data.bcl,
+                             BCL_MODES),
+                list_setting("Swap transmit and receive frequencies",
+                             "Tx Rx freq swap",
+                             _data.txrx_reverse,
+                             OFF_ON),
+                list_setting("Use compander",
+                             "Use compander",
+                             _data.compander,
+                             OFF_ON),
+                list_setting("Use scrambler", "Use scrambler",
+                             _data.use_scrambler,
+                             NO_YES),
+                list_setting("Scrambler selection",
+                             "Voice Scrambler",
+                             _data.scrambler_type,
+                             SCRAMBLER_MODES),
+                list_setting("Send ID code before and/or after transmitting",
+                             "PTT ID",
+                             _data.ptt_id_edge,
+                             PTT_ID_EDGES),
+                list_setting("Optional signal before/after transmission, " +
+                             "this setting overrides the PTT ID setting.",
+                             "Opt Signal",
+                             _data.opt_signal,
+                             OPTSIGN_MODES))
+
+######################################################################
+        if ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES:
+            # override certain settings based on flags
+            # that we have set in junk areas of the memory
+            # or basically, we BELIEVE this to be junk memory,
+            # hence why it's experimental and dangerous
+            if bool(_data.experimental_duplex_mode_indicator) is False:
+                # if this flag is set, this means that we in the gui
+                # have set the duplex mode to something
+                # the channel does not really support,
+                # such as split modes for vfo channels,
+                # and non-split modes for the memory channels
+                mem.duplex = DUPLEX_MODES[int(_data.duplex_sign)]
+                mem.freq = int(_data.rx_freq)*10
+                mem.offset = int(_data.tx_freq)*10
+                if isvfo:
+                    # we want split, so we have to reconstruct it
+                    # from -/0/+ modes
+                    if mem.duplex == '-':
+                        mem.offset = mem.freq - mem.offset
+                    elif mem.duplex == '':
+                        mem.offset = mem.freq
+                    elif mem.duplex == '+':
+                        mem.offset = mem.freq + mem.offset
+                    mem.duplex = 'split'
+                else:
+                    # we want -/0/+, so we have to reconstruct it
+                    # from split modes
+                    if mem.freq > mem.offset:
+                        mem.offset = mem.freq - mem.offset
+                        mem.duplex = '-'
+                    elif mem.freq < mem.offset:
+                        mem.offset = mem.offset - mem.freq
+                        mem.duplex = '+'
+                    else:
+                        mem.offset = 0
+                        mem.duplex = ''
+######################################################################
+
+        return mem
+
+    def set_memory(self, mem):
+        (index, designator,
+         _data, _name, _present, _priority,
+         isregular, isvfo, iscall) = self._get_memory_structs(mem.number)
+        mem.number = index
+        mem.extd_number = designator
+
+        # handle empty channels
+        if mem.empty:
+            if isregular:
+                _present.set_value(False)
+                _priority.set_value(False)
+                self._uninitialize(_data, 16)
+                self._uninitialize(_name, 6)
+            else:
+                raise errors.InvalidValueError(
+                        "Can't remove CALL and/or VFO channels!")
+            return
+
+        # handle regular channel stuff like name and present+priority flags
+        if isregular:
+            if not bool(_present):
+                self._set_sane_defaults(_data)
+            _name.set_value(
+                    encode_ffstring(self.filter_name(mem.name), len(_name)))
+            _present.set_value(True)
+            _priority.set_value(1-SKIP_MODES.index(mem.skip))
+
+        # frequency data
+        rxf = int(mem.freq/10)
+        txf = int(mem.offset/10)
+
+        _data.rx_freq.set_value(rxf)
+
+        if isvfo:
+            # fake split modes on write, for channels
+            # that do not support it, which are some
+            # (the two vfo channels)
+            if mem.duplex == 'split':
+                for band in BANDS:
+                    rb = mem.freq in range(band[0], band[1])
+                    tb = mem.offset in range(band[0], band[1])
+                    if rb != tb:
+                        raise errors.InvalidValueError(
+                                "VFO frequencies should be on the same band")
+                if rxf < txf:
+                    _data.duplex_sign.set_value(1)
+                    _data.tx_freq.set_value(txf - rxf)
+                elif rxf > txf:
+                    _data.duplex_sign.set_value(2)
+                    _data.tx_freq.set_value(rxf-txf)
+                else:
+                    _data.duplex_sign.set_value(0)
+                    _data.tx_freq.set_value(0)
+            else:
+                _data.duplex_sign.set_value(DUPLEX_MODES.index(mem.duplex))
+                _data.tx_freq.set_value(txf)
+
+######################################################################
+            if ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES:
+                if mem.duplex == 'split':
+                    _data.experimental_duplex_mode_indicator.set_value(0)
+                else:
+                    _data.experimental_duplex_mode_indicator.set_value(1)
+######################################################################
+
+        else:
+            # fake duplex modes on write, for channels
+            # that do not support it, which are most
+            # (all the memory channels)
+            if mem.duplex == '' or mem.duplex is None:
+                _data.tx_freq.set_value(rxf)
+            elif mem.duplex == '+':
+                _data.tx_freq.set_value(rxf + txf)
+            elif mem.duplex == '-':
+                _data.tx_freq.set_value(rxf - txf)
+            else:
+                _data.tx_freq.set_value(txf)
+
+######################################################################
+            if ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES:
+                if mem.duplex != 'split':
+                    _data.experimental_duplex_mode_indicator.set_value(0)
+                else:
+                    _data.experimental_duplex_mode_indicator.set_value(1)
+######################################################################
+
+        # tone data
+        tonedata = chirp_common.split_tone_encode(mem)
+        for i in range(2):
+            dihl = unparse_tone(tonedata[i])
+            if dihl is not None:
+                _data.tone[i].digital.set_value(dihl[0])
+                _data.tone[i].invert.set_value(dihl[1])
+                _data.tone[i].high.set_value(dihl[2])
+                _data.tone[i].low.set_value(dihl[3])
+            else:
+                _data.tone[i].digital.set_value(1)
+                _data.tone[i].invert.set_value(1)
+                _data.tone[i].high.set_value(0x3f)
+                _data.tone[i].low.set_value(0xff)
+
+######################################################################
+        if ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES:
+            if mem.tmode == 'Cross' and mem.cross_mode in ['Tone->',
+                                                           'Tone->Tone',
+                                                           'DTCS->DTCS']:
+                _data.experimental_cross_mode_indicator.set_value(0)
+            else:
+                _data.experimental_cross_mode_indicator.set_value(1)
+######################################################################
+
+        # transmit mode and power level
+        _data.modulation_width.set_value(MODES.index(mem.mode))
+        if str(mem.power) == 'High':
+            _data.txpower.set_value(1)
+        else:
+            _data.txpower = 0
+
+    def get_settings(self):
+        _model = self._memobj.mem.model_information
+        _settings = self._memobj.mem.opt_settings
+        _ptt_id_data = self._memobj.mem.ptt_id_data
+        _msk_settings = self._memobj.mem.msk_settings
+        _dtmf_settings = self._memobj.mem.dtmf_settings
+        _5tone_settings = self._memobj.mem.five_tone_settings
+        _broadcast = self._memobj.mem.fm_radio
+
+        # for safety reasons we are showing these as read-only
+        model_unit_settings = [
+            integer_setting("vhflo", "VHF lower bound",
+                            _model.band_limits[0].lower_freq,
+                            134, 176,
+                            int_from_mem=lambda x:int(int(x)/10),
+                            mem_from_int=None),
+            integer_setting("vhfhi", "VHF upper bound",
+                            _model.band_limits[0].upper_freq,
+                            134, 176,
+                            int_from_mem=lambda x:int(int(x)/10),
+                            mem_from_int=None),
+            integer_setting("uhflo", "UHF lower bound",
+                            _model.band_limits[1].lower_freq,
+                            400, 480,
+                            int_from_mem=lambda x:int(int(x)/10),
+                            mem_from_int=None),
+            integer_setting("uhfhi", "UHF upper bound",
+                            _model.band_limits[1].upper_freq,
+                            400, 480,
+                            int_from_mem=lambda x:int(int(x)/10),
+                            mem_from_int=None),
+            ff_string_setting("model", "Model string",
+                              _model.model_string,
+                              0, 6)
+
+        ]
+        for s in model_unit_settings:
+            s.value.set_mutable(False)
+        model_unit_settings.append(ff_string_setting(
+            "info", "Unit Information",
+            self._memobj.mem.radio_information_string,
+            0, 16))
+
+        # tx/rx related settings
+        radio_channel_settings = [
+            list_setting("vfostep", "VFO step size",
+                         _settings.vfo_step,
+                         VFO_STRIDE),
+            list_setting("abwatch", "Main watch",
+                         _settings.main_watch,
+                         AB),
+            list_setting("watchmade", "Watch mode",
+                         _settings.main_watch,
+                         WATCH_MODES),
+            list_setting("amode", "A mode",
+                         _settings.workmode_a,
+                         AB_MODES),
+            list_setting("bmode", "B mode",
+                         _settings.workmode_b,
+                         AB_MODES),
+            integer_setting("achan", "A channel index",
+                            _settings.channel_a,
+                            1, 128,
+                            int_from_mem=lambda i:i+1,
+                            mem_from_int=lambda i:i-1),
+            integer_setting("bchan", "B channel index",
+                            _settings.channel_b,
+                            1, 128,
+                            int_from_mem=lambda i:i+1,
+                            mem_from_int=lambda i:i-1),
+            integer_setting("pchan", "Priority channel index",
+                            _settings.priority_channel,
+                            1, 128, int_from_mem=lambda i:i+1,
+                            mem_from_int=lambda i:i-1),
+            list_setting("cactive", "Call channel active?",
+                         _settings.call_channel_active,
+                         NO_YES),
+            list_setting("scanm", "Scan mode",
+                         _settings.scan_mode,
+                         SCAN_MODES),
+            list_setting("swait", "Wait time",
+                         _settings.wait_time,
+                         WAIT_TIMES),
+            # it is unclear what this option below does,
+            # possibly squelch tail elimination?
+            list_setting("tail", "Relay without disable tail (?)",
+                         _settings.relay_without_disable_tail,
+                         NO_YES),
+            list_setting("batsav", "Battery saving mode",
+                         _settings.battery_save,
+                         OFF_ON),
+            ]
+
+        # user interface related settings
+        interface_settings = [
+            list_setting("sidehold", "Side button hold action",
+                         _settings.side_button_hold_mode,
+                         BUTTON_MODES),
+            list_setting("sideclick", "Side button click action",
+                         _settings.side_button_click_mode,
+                         BUTTON_MODES),
+            list_setting("bootmt", "Boot message type",
+                         _settings.boot_message_mode,
+                         BOOT_MESSAGE_TYPES),
+            ff_string_setting("bootm", "Boot message",
+                              _settings.boot_message,
+                              0, 6),
+            list_setting("beep", "Key beep",
+                         _settings.key_beep,
+                         OFF_ON),
+            list_setting("talkback", "Menu talkback",
+                         _settings.voice_announce,
+                         TALKBACK),
+            list_setting("sidetone", "DTMF sidetone",
+                         _settings.dtmf_sidetone,
+                         OFF_ON),
+            list_setting("roger", "Roger beep",
+                         _settings.use_roger_beep,
+                         ROGER_BEEP),
+            list_setting("backlm", "Backlight mode",
+                         _settings.backlight_mode,
+                         BACKLIGHT_MODES),
+            list_setting("backlc", "Backlight color",
+                         _settings.backlight_color,
+                         BACKLIGHT_COLORS),
+            integer_setting("squelch", "Squelch level",
+                            _settings.squelch_level,
+                            0, 9),
+            list_setting("voxg", "Vox gain",
+                         _settings.vox_gain,
+                         VOX_GAIN),
+            list_setting("voxd", "Vox delay",
+                         _settings.vox_delay,
+                         VOX_DELAYS),
+            list_setting("txal", "Trinsmit time alarm",
+                         _settings.tx_timeout,
+                         TRANSMIT_ALARMS),
+            ]
+
+        # settings related to tone/data sending and interpretation
+        data_general_settings = [
+            list_setting("disptt", "Display PTT ID",
+                         _settings.dis_ptt_id,
+                         NO_YES),
+            list_setting("pttidt", "PTT ID signal type",
+                         _settings.ptt_id_type,
+                         DATA_MODES)
+            ]
+
+        data_msk_settings = [
+            ff_string_setting("bot", "MSK PTT ID (BOT)",
+                              _ptt_id_data[0].entry,
+                              0, 6, autowrite=False),
+            ff_string_setting("eot", "MSK PTT ID (EOT)",
+                              _ptt_id_data[1].entry,
+                              0, 6, autowrite=False),
+            ff_string_setting("id", "MSK ID code",
+                              _msk_settings.id_code,
+                              0, 4, charset=HEXADECIMAL),
+            list_setting("mskr", "MSK reverse",
+                         _settings.msk_reverse,
+                         NO_YES)
+            ]
+
+        data_dtmf_settings = [
+            dtmf_string_setting("bot", "DTMF PTT ID (BOT)",
+                                _ptt_id_data[0].entry,
+                                _ptt_id_data[0].length,
+                                0, 8, autowrite=False),
+            dtmf_string_setting("eot", "DTMF PTT ID (EOT)",
+                                _ptt_id_data[1].entry,
+                                _ptt_id_data[1].length,
+                                0, 8, autowrite=False),
+            dtmf_string_setting("id",  "DTMF ID code",
+                                _dtmf_settings.id_code,
+                                _dtmf_settings.id_code_length,
+                                3, 8),
+
+            integer_setting("time",   "Digit time (ms)",
+                            _dtmf_settings.timing.digit_length,
+                            50,  200, step=10,
+                            int_from_mem=lambda x:x*10,
+                            mem_from_int=lambda x:int(x/10)),
+            integer_setting("pause",  "Inter digit time (ms)",
+                            _dtmf_settings.timing.digit_length,
+                            50,  200, step=10,
+                            int_from_mem=lambda x:x*10,
+                            mem_from_int=lambda x:int(x/10)),
+            integer_setting("time1",  "First digit time (ms)",
+                            _dtmf_settings.timing.digit_length,
+                            50,  200, step=10,
+                            int_from_mem=lambda x:x*10,
+                            mem_from_int=lambda x:int(x/10)),
+            integer_setting("pause1", "First digit delay (ms)",
+                            _dtmf_settings.timing.digit_length,
+                            100, 1000, step=50,
+                            int_from_mem=lambda x:x*50,
+                            mem_from_int=lambda x:int(x/50)),
+
+            list_setting("arst", "Auto reset time",
+                         _dtmf_settings.reset_time,
+                         DTMF_TONE_RESET_TIME),
+            list_setting("grp", "Group code",
+                         _dtmf_settings.group_code,
+                         DTMF_GROUPS),
+            dtmf_string_setting("stunt", "TX Stun code",
+                                _dtmf_settings.tx_stun_code,
+                                _dtmf_settings.tx_stun_code_length,
+                                3, 8),
+            dtmf_string_setting("cstunt", "TX Stun cancel code",
+                                _dtmf_settings.cancel_tx_stun_code,
+                                _dtmf_settings.cancel_tx_stun_code_length,
+                                3, 8),
+            dtmf_string_setting("stunrt", "RX/TX Stun code",
+                                _dtmf_settings.rxtx_stun_code,
+                                _dtmf_settings.rxtx_stun_code_length,
+                                3, 8),
+            dtmf_string_setting("cstunrt", "RX/TX Stun cancel code",
+                                _dtmf_settings.cancel_rxtx_stun_code,
+                                _dtmf_settings.cancel_rxtx_stun_code_length,
+                                3, 8),
+            list_setting("altr", "Alert/Transpond",
+                         _dtmf_settings.alert_transpond,
+                         DTMF_ALERT_TRANSPOND),
+            ]
+
+        data_5tone_settings = [
+            five_tone_string_setting("bot",
+                                     "5-Tone PTT ID (BOT)",
+                                     _ptt_id_data[0].entry,
+                                     autowrite=False),
+            five_tone_string_setting("eot",
+                                     "5-Tone PTT ID (EOT)",
+                                     _ptt_id_data[1].entry,
+                                     autowrite=False),
+            five_tone_string_setting("id",
+                                     "5-tone ID code",
+                                     _5tone_settings.id_code),
+            list_setting("arst", "Auto reset time",
+                         _5tone_settings.reset_time,
+                         TONE_RESET_TIME),
+            five_tone_string_setting("stunt",
+                                     "TX Stun code",
+                                     _5tone_settings.tx_stun_code),
+            five_tone_string_setting("cstunt",
+                                     "TX Stun cancel code",
+                                     _5tone_settings.cancel_tx_stun_code),
+            five_tone_string_setting("stunrt",
+                                     "RX/TX Stun code",
+                                     _5tone_settings.rxtx_stun_code),
+            five_tone_string_setting("cstunrt",
+                                     "RX/TX Stun cancel code",
+                                     _5tone_settings.cancel_rxtx_stun_code),
+            list_setting("altr", "Alert/Transpond",
+                         _5tone_settings.alert_transpond,
+                         FIVE_TONE_ALERT_TRANSPOND),
+            list_setting("std", "5-Tone standard",
+                         _5tone_settings.tone_standard,
+                         FIVE_TONE_STANDARDS),
+            ]
+        for i in range(4):
+            s = ['z1', 'z2', 'c1', 'ct'][i]
+            l = FIVE_TONE_STANDARDS[i]
+            data_5tone_settings.append(
+                settings.RadioSettingGroup(
+                    s, '%s settings' % l,
+                    integer_setting("%speriod" % s, "%s Period (ms)" % l,
+                                    _5tone_settings.tone_settings[i].period,
+                                    20, 255),
+                    list_setting("%sgrp" % s, "%s Group code" % l,
+                                 _5tone_settings.tone_settings[i].group_code,
+                                 HEXADECIMAL),
+                    list_setting("%srpt" % s, "%s Repeat code" % l,
+                                 _5tone_settings.tone_settings[i].repeat_code,
+                                 HEXADECIMAL)))
+
+        data_msk_call_list = []
+        data_dtmf_call_list = []
+        data_5tone_call_list = []
+        for i in range(9):
+            j = i+1
+            data_msk_call_list.append(
+                ff_string_setting("ce%d" % i,
+                                  "MSK call entry %d" % j,
+                                  _msk_settings.phone_book[i].entry,
+                                  0, 4,
+                                  charset=HEXADECIMAL))
+
+            data_dtmf_call_list.append(
+                dtmf_string_setting("ce%d" % i,
+                                    "DTMF call entry %d" % j,
+                                    _dtmf_settings.phone_book[i].entry,
+                                    _dtmf_settings.phone_book[i].length,
+                                    0, 10))
+
+            data_5tone_call_list.append(
+                five_tone_string_setting("ce%d" % i,
+                                         "5-Tone call entry %d" % j,
+                                         _5tone_settings.phone_book[i].entry)),
+
+        data_settings = data_general_settings
+        data_settings.extend([
+                settings.RadioSettingGroup("MSK_s", "MSK settings",
+                                           *data_msk_settings),
+                settings.RadioSettingGroup("MSK_c", "MSK call list",
+                                           *data_msk_call_list),
+                settings.RadioSettingGroup("DTMF_s", "DTMF settings",
+                                           *data_dtmf_settings),
+                settings.RadioSettingGroup("DTMF_c", "DTMF call list",
+                                           *data_dtmf_call_list),
+                settings.RadioSettingGroup("5-Tone_s", "5-tone settings",
+                                           *data_5tone_settings),
+                settings.RadioSettingGroup("5-Tone_c", "5-tone call list",
+                                           *data_5tone_call_list)
+            ])
+
+        # settings related to the various ways the radio can be locked down
+        locking_settings = [
+            list_setting("autolock", "Automatic timed keypad lock",
+                         _settings.auto_keylock,
+                         OFF_ON),
+            list_setting("lockon", "Current status of keypad lock",
+                         _settings.keypad_lock,
+                         INACTIVE_ACTIVE),
+            list_setting("nokeypad", "Disable keypad",
+                         _settings.allow_keypad,
+                         YES_NO),
+            list_setting("rxstun", "Disable receiver (rx stun)",
+                         _settings.rx_stun,
+                         NO_YES),
+            list_setting("txstun", "Disable transmitter (tx stun)",
+                         _settings.tx_stun,
+                         NO_YES),
+            ]
+
+        # broadcast fm radio settings
+        broadcast_settings = [
+            list_setting("band", "Frequency interval",
+                         _broadcast.receive_range,
+                         BFM_BANDS),
+            list_setting("stride", "VFO step",
+                         _broadcast.channel_stepping,
+                         BFM_STRIDE),
+            frequency_setting("vfo", "VFO frequency (MHz)",
+                              _broadcast.vfo_freq)
+        ]
+
+        for i in range(10):
+            broadcast_settings.append(
+                frequency_setting("bcd%d" % i, "Memory %d frequency" % i,
+                                  _broadcast.memory[i].entry))
+
+        return settings.RadioSettings(
+            settings.RadioSettingGroup("model", "Model/Unit information",
+                                       *model_unit_settings),
+            settings.RadioSettingGroup("radio", "Radio/Channel settings",
+                                       *radio_channel_settings),
+            settings.RadioSettingGroup("interface", "Interface",
+                                       *interface_settings),
+            settings.RadioSettingGroup("data", "Data",
+                                       *data_settings),
+            settings.RadioSettingGroup("locking", "Locking",
+                                       *locking_settings),
+            settings.RadioSettingGroup("broadcast",
+                                       "Broadcast FM radio settings",
+                                       *broadcast_settings)
+        )
+
+    def set_settings(self, s, parent=''):
+        # The helper classes take care of all settings except these below,
+        # since it is a single instance of settings having interdependencies,
+        # i.e., which value that gets written to the memory depends on
+        # the value of another setting. The value of the ptt id type setting
+        # decides which of the msk/dtmf/5-tone ptt id strings are
+        # actually written to memory.
+        ds = sbyn(s, 'data')
+        idts = sbyn(ds, 'pttidt').value
+        idtv = idts.get_value()
+        cs = sbyn(ds, idtv+'_s')
+        tss = [sbyn(cs, e).value for e in ['bot', 'eot']]
+
+        for ts in tss:
+            ts.write_mem()
diff --git a/chirp/drivers/retevis_rt21.py b/chirp/drivers/retevis_rt21.py
new file mode 100644
index 0000000..e1ed491
--- /dev/null
+++ b/chirp/drivers/retevis_rt21.py
@@ -0,0 +1,526 @@
+# Copyright 2016 Jim Unroe <rock.unroe at gmail.com>
+#
+# 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 2 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 os
+import struct
+import logging
+
+from chirp import chirp_common, directory, memmap
+from chirp import bitwise, errors, util
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettings
+
+LOG = logging.getLogger(__name__)
+
+MEM_FORMAT = """
+#seekto 0x0010;
+struct {
+  lbcd rxfreq[4];
+  lbcd txfreq[4];
+  ul16 rx_tone;
+  ul16 tx_tone;
+  u8 unknown1:3,
+     bcl:2,       // Busy Lock
+     unknown2:3;
+  u8 unknown3:2,
+     highpower:1, // Power Level
+     wide:1,      // Bandwidth   
+     unknown4:4;
+  u8 scramble_type:4,
+     unknown5:4;
+  u8 unknown6:4,
+     scramble_type2:4;
+} memory[16];
+
+#seekto 0x012C;
+struct {
+  u8 use_scramble; // Scramble Enable
+  u8 unknown1[2];
+  u8 voice;        // Voice Annunciation
+  u8 tot;          // Time-out Timer              
+  u8 totalert;     // Time-out Timer Pre-alert    
+  u8 unknown2[2];
+  u8 squelch;      // Squelch Level               
+  u8 save;         // Battery Saver               
+  u8 unknown3[3];
+  u8 use_vox;      // VOX Enable                  
+  u8 vox;          // VOX Gain                    
+} settings;
+
+#seekto 0x017E;
+u8 skipflags[2];  // SCAN_ADD
+"""
+
+CMD_ACK = "\x06"
+BLOCK_SIZE = 0x10
+
+RT21_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00),
+                     chirp_common.PowerLevel("High", watts=2.50)]
+
+
+RT21_DTCS = sorted(chirp_common.DTCS_CODES + 
+                   [17, 50, 55, 135, 217, 254, 305, 645, 765])
+
+BCL_LIST = ["Off", "Carrier", "QT/DQT"]
+SCRAMBLE_LIST = ["Scramble 1", "Scramble 2", "Scramble 3", "Scramble 4",
+                 "Scramble 5", "Scramble 6", "Scramble 7", "Scramble 8"]
+TIMEOUTTIMER_LIST = ["%s seconds" % x for x in range(15, 615, 15)]
+TOTALERT_LIST = ["Off"] + ["%s seconds" % x for x in range(1, 11)]
+VOICE_LIST = ["Off", "Chinese", "English"]
+VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 17)]
+
+SETTING_LISTS = {
+    "bcl": BCL_LIST,
+    "scramble": SCRAMBLE_LIST,
+    "tot": TIMEOUTTIMER_LIST,
+    "totalert": TOTALERT_LIST,
+    "voice": VOICE_LIST,
+    "vox": VOX_LIST,
+    }
+
+
+def _rt21_enter_programming_mode(radio):
+    serial = radio.pipe
+
+    try:
+        serial.write("PRMZUNE")
+        ack = serial.read(1)
+    except:
+        raise errors.RadioError("Error communicating with radio")
+
+    if not ack:
+        raise errors.RadioError("No response from radio")
+    elif ack != CMD_ACK:
+        raise errors.RadioError("Radio refused to enter programming mode")
+
+    try:
+        serial.write("\x02")
+        ident = serial.read(8)
+    except:
+        raise errors.RadioError("Error communicating with radio")
+
+    if not ident.startswith("P3207"):
+        LOG.debug(util.hexprint(ident))
+        raise errors.RadioError("Radio returned unknown identification string")
+
+    try:
+        serial.write(CMD_ACK)
+        ack = serial.read(1)
+    except:
+        raise errors.RadioError("Error communicating with radio")
+
+    if ack != CMD_ACK:
+        raise errors.RadioError("Radio refused to enter programming mode")
+
+
+def _rt21_exit_programming_mode(radio):
+    serial = radio.pipe
+    try:
+        serial.write("E")
+    except:
+        raise errors.RadioError("Radio refused to exit programming mode")
+
+
+def _rt21_read_block(radio, block_addr, block_size):
+    serial = radio.pipe
+
+    cmd = struct.pack(">cHb", 'R', block_addr, BLOCK_SIZE)
+    expectedresponse = "W" + cmd[1:]
+    LOG.debug("Reading block %04x..." % (block_addr))
+
+    try:
+        serial.write(cmd)
+        response = serial.read(4 + BLOCK_SIZE)
+        if response[:4] != expectedresponse:
+            raise Exception("Error reading block %04x." % (block_addr))
+
+        block_data = response[4:]
+
+        serial.write(CMD_ACK)
+        ack = serial.read(1)
+    except:
+        raise errors.RadioError("Failed to read block at %04x" % block_addr)
+
+    if ack != CMD_ACK:
+        raise Exception("No ACK reading block %04x." % (block_addr))
+
+    return block_data
+
+
+def _rt21_write_block(radio, block_addr, block_size):
+    serial = radio.pipe
+
+    cmd = struct.pack(">cHb", 'W', block_addr, BLOCK_SIZE)
+    data = radio.get_mmap()[block_addr:block_addr + BLOCK_SIZE]
+
+    LOG.debug("Writing Data:")
+    LOG.debug(util.hexprint(cmd + data))
+
+    try:
+        serial.write(cmd + data)
+        if serial.read(1) != CMD_ACK:
+            raise Exception("No ACK")
+    except:
+        raise errors.RadioError("Failed to send block "
+                                "to radio at %04x" % block_addr)
+
+
+def do_download(radio):
+    LOG.debug("download")
+    _rt21_enter_programming_mode(radio)
+
+    data = ""
+
+    status = chirp_common.Status()
+    status.msg = "Cloning from radio"
+
+    status.cur = 0
+    status.max = radio._memsize
+
+    for addr in range(0, radio._memsize, BLOCK_SIZE):
+        status.cur = addr + BLOCK_SIZE
+        radio.status_fn(status)
+
+        block = _rt21_read_block(radio, addr, BLOCK_SIZE)
+        data += block
+
+        LOG.debug("Address: %04x" % addr)
+        LOG.debug(util.hexprint(block))
+
+    _rt21_exit_programming_mode(radio)
+
+    return memmap.MemoryMap(data)
+
+
+def do_upload(radio):
+    status = chirp_common.Status()
+    status.msg = "Uploading to radio"
+
+    _rt21_enter_programming_mode(radio)
+
+    status.cur = 0
+    status.max = radio._memsize
+
+    for start_addr, end_addr in radio._ranges:
+        for addr in range(start_addr, end_addr, BLOCK_SIZE):
+            status.cur = addr + BLOCK_SIZE
+            radio.status_fn(status)
+            _rt21_write_block(radio, addr, BLOCK_SIZE)
+
+    _rt21_exit_programming_mode(radio)
+
+
+ at directory.register
+class RT21Radio(chirp_common.CloneModeRadio):
+    """RETEVIS RT21"""
+    VENDOR = "Retevis"
+    MODEL = "RT21"
+    BAUD_RATE = 9600
+
+    _ranges = [
+               (0x0000, 0x0400),
+              ]
+    _memsize = 0x0400
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.has_settings = True
+        rf.has_bank = False
+        rf.has_ctone = True
+        rf.has_cross = True
+        rf.has_rx_dtcs = True
+        rf.has_tuning_step = False
+        rf.can_odd_split = True
+        rf.has_name = False
+        rf.valid_skips = ["", "S"]
+        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
+        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
+                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
+        rf.valid_power_levels = RT21_POWER_LEVELS
+        rf.valid_duplexes = ["", "-", "+", "split", "off"]
+        rf.valid_modes = ["NFM", "FM"]  # 12.5 KHz, 25 kHz.
+        rf.memory_bounds = (1, 16)
+        rf.valid_bands = [(400000000, 480000000)]
+
+        return rf
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+    def sync_in(self):
+        self._mmap = do_download(self)
+        self.process_mmap()
+
+    def sync_out(self):
+        do_upload(self)
+
+    def get_raw_memory(self, number):
+        return repr(self._memobj.memory[number - 1])
+
+    def _get_tone(self, _mem, mem):
+        def _get_dcs(val):
+            code = int("%03o" % (val & 0x07FF))
+            pol = (val & 0x8000) and "R" or "N"
+            return code, pol
+
+        if _mem.tx_tone != 0xFFFF and _mem.tx_tone > 0x2000:
+            tcode, tpol = _get_dcs(_mem.tx_tone)
+            mem.dtcs = tcode
+            txmode = "DTCS"
+        elif _mem.tx_tone != 0xFFFF:
+            mem.rtone = _mem.tx_tone / 10.0
+            txmode = "Tone"
+        else:
+            txmode = ""
+
+        if _mem.rx_tone != 0xFFFF and _mem.rx_tone > 0x2000:
+            rcode, rpol = _get_dcs(_mem.rx_tone)
+            mem.rx_dtcs = rcode
+            rxmode = "DTCS"
+        elif _mem.rx_tone != 0xFFFF:
+            mem.ctone = _mem.rx_tone / 10.0
+            rxmode = "Tone"
+        else:
+            rxmode = ""
+
+        if txmode == "Tone" and not rxmode:
+            mem.tmode = "Tone"
+        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
+            mem.tmode = "TSQL"
+        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
+            mem.tmode = "DTCS"
+        elif rxmode or txmode:
+            mem.tmode = "Cross"
+            mem.cross_mode = "%s->%s" % (txmode, rxmode)
+
+        if mem.tmode == "DTCS":
+            mem.dtcs_polarity = "%s%s" % (tpol, rpol)
+
+        LOG.debug("Got TX %s (%i) RX %s (%i)" %
+                  (txmode, _mem.tx_tone, rxmode, _mem.rx_tone))
+
+    def get_memory(self, number):
+        bitpos = (1 << ((number - 1) % 8))
+        bytepos = ((number - 1) / 8)
+        LOG.debug("bitpos %s" % bitpos)
+        LOG.debug("bytepos %s" % bytepos)
+
+        _mem = self._memobj.memory[number - 1]
+        _skp = self._memobj.skipflags[bytepos]
+
+        mem = chirp_common.Memory()
+
+        mem.number = number
+        mem.freq = int(_mem.rxfreq) * 10
+
+        # We'll consider any blank (i.e. 0MHz frequency) to be empty
+        if mem.freq == 0:
+            mem.empty = True
+            return mem
+
+        if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF":
+            mem.freq = 0
+            mem.empty = True
+            return mem
+
+        if _mem.get_raw() == ("\xFF" * 16):
+            LOG.debug("Initializing empty memory")
+            _mem.set_raw("\x00" * 13 + "\x30\x8F\xF8")
+
+        if int(_mem.rxfreq) == int(_mem.txfreq):
+            mem.duplex = ""
+            mem.offset = 0
+        else:
+            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
+            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
+
+        mem.mode = _mem.wide and "FM" or "NFM"
+
+        self._get_tone(_mem, mem)
+
+        mem.power = RT21_POWER_LEVELS[_mem.highpower]
+
+        mem.skip = "" if (_skp & bitpos) else "S"
+        LOG.debug("mem.skip %s" % mem.skip)
+
+        mem.extra = RadioSettingGroup("Extra", "extra")
+
+        rs = RadioSetting("bcl", "Busy Channel Lockout",
+                          RadioSettingValueList(
+                              BCL_LIST, BCL_LIST[_mem.bcl]))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("scramble_type", "Scramble Type",
+                          RadioSettingValueList(SCRAMBLE_LIST,
+                              SCRAMBLE_LIST[_mem.scramble_type - 8]))
+        mem.extra.append(rs)
+
+        return mem
+
+    def _set_tone(self, mem, _mem):
+        def _set_dcs(code, pol):
+            val = int("%i" % code, 8) + 0x2800
+            if pol == "R":
+                val += 0x8000
+            return val
+
+        if mem.tmode == "Cross":
+            tx_mode, rx_mode = mem.cross_mode.split("->")
+        elif mem.tmode == "Tone":
+            tx_mode = mem.tmode
+            rx_mode = None
+        else:
+            tx_mode = rx_mode = mem.tmode
+
+        if tx_mode == "DTCS":
+            _mem.tx_tone = mem.tmode != "DTCS" and \
+                           _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) or \
+                           _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[0])
+        elif tx_mode:
+            _mem.tx_tone = tx_mode == "Tone" and \
+                int(mem.rtone * 10) or int(mem.ctone * 10)
+        else:
+            _mem.tx_tone = 0xFFFF
+
+        if rx_mode == "DTCS":
+            _mem.rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
+        elif rx_mode:
+            _mem.rx_tone = int(mem.ctone * 10)
+        else:
+            _mem.rx_tone = 0xFFFF
+
+        LOG.debug("Set TX %s (%i) RX %s (%i)" %
+                  (tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))
+
+    def set_memory(self, mem):
+        bitpos = (1 << ((mem.number - 1) % 8))
+        bytepos = ((mem.number - 1) / 8)
+        LOG.debug("bitpos %s" % bitpos)
+        LOG.debug("bytepos %s" % bytepos)
+
+        _mem = self._memobj.memory[mem.number - 1]
+        _skp = self._memobj.skipflags[bytepos]
+
+        if mem.empty:
+            _mem.set_raw("\xFF" * (_mem.size() / 8))
+            return
+
+        _mem.set_raw("\x00" * 13 + "\x00\x8F\xF8")
+
+        _mem.rxfreq = mem.freq / 10
+
+        if mem.duplex == "off":
+            for i in range(0, 4):
+                _mem.txfreq[i].set_raw("\xFF")
+        elif mem.duplex == "split":
+            _mem.txfreq = mem.offset / 10
+        elif mem.duplex == "+":
+            _mem.txfreq = (mem.freq + mem.offset) / 10
+        elif mem.duplex == "-":
+            _mem.txfreq = (mem.freq - mem.offset) / 10
+        else:
+            _mem.txfreq = mem.freq / 10
+
+        _mem.wide = mem.mode == "FM"
+
+        self._set_tone(mem, _mem)
+
+        _mem.highpower = mem.power == RT21_POWER_LEVELS[1]
+
+        if mem.skip != "S":
+            _skp |= bitpos
+        else:
+            _skp &= ~bitpos
+        LOG.debug("_skp %s" % _skp)
+
+        for setting in mem.extra:
+            if setting.get_name() == "scramble_type":
+                setattr(_mem, setting.get_name(), int(setting.value) + 8)
+                setattr(_mem, "scramble_type2", int(setting.value) + 8)
+            else:
+                setattr(_mem, setting.get_name(), setting.value)
+
+    def get_settings(self):
+        _settings = self._memobj.settings
+        basic = RadioSettingGroup("basic", "Basic Settings")
+        top = RadioSettings(basic)
+
+        rs = RadioSetting("tot", "Time-out timer",
+                          RadioSettingValueList(
+                              TIMEOUTTIMER_LIST,
+                              TIMEOUTTIMER_LIST[_settings.tot - 1]))
+        basic.append(rs)
+
+        rs = RadioSetting("totalert", "TOT Pre-alert",
+                          RadioSettingValueList(
+                              TOTALERT_LIST,
+                              TOTALERT_LIST[_settings.totalert]))
+        basic.append(rs)
+
+        rs = RadioSetting("squelch", "Squelch Level",
+                          RadioSettingValueInteger(0, 9, _settings.squelch))
+        basic.append(rs)
+
+        rs = RadioSetting("voice", "Voice Annumciation",
+                          RadioSettingValueList(
+                              VOICE_LIST, VOICE_LIST[_settings.voice]))
+        basic.append(rs)
+
+        rs = RadioSetting("save", "Battery Saver",
+                          RadioSettingValueBoolean(_settings.save))
+        basic.append(rs)
+
+        rs = RadioSetting("use_scramble", "Scramble",
+                          RadioSettingValueBoolean(_settings.use_scramble))
+        basic.append(rs)
+
+        rs = RadioSetting("use_vox", "VOX",
+                          RadioSettingValueBoolean(_settings.use_vox))
+        basic.append(rs)
+
+        rs = RadioSetting("vox", "VOX Gain",
+                          RadioSettingValueList(
+                              VOX_LIST, VOX_LIST[_settings.vox]))
+        basic.append(rs)
+
+        return top
+
+    def set_settings(self, settings):
+        for element in settings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+            else:
+                try:
+                    if "." in element.get_name():
+                        bits = element.get_name().split(".")
+                        obj = self._memobj
+                        for bit in bits[:-1]:
+                            obj = getattr(obj, bit)
+                        setting = bits[-1]
+                    else:
+                        obj = self._memobj.settings
+                        setting = element.get_name()
+
+                    if setting == "tot":
+                        setattr(obj, setting, int(element.value) + 1)
+                    elif element.value.get_mutable():
+                        LOG.debug("Setting %s = %s" % (setting, element.value))
+                        setattr(obj, setting, element.value)
+                except Exception, e:
+                    LOG.debug(element.get_name())
+                    raise
diff --git a/chirp/drivers/thd72.py b/chirp/drivers/thd72.py
index 1cae042..c0a0cc7 100644
--- a/chirp/drivers/thd72.py
+++ b/chirp/drivers/thd72.py
@@ -185,6 +185,8 @@ EXCH_W = "W\x00\x00\x00\x00"
 
 # Uploads result in "MCP Error" and garbage data in memory
 # Clone driver disabled in favor of error-checking live driver.
+
+
 @directory.register
 class THD72Radio(chirp_common.CloneModeRadio):
     BAUD_RATE = 9600
@@ -200,10 +202,11 @@ class THD72Radio(chirp_common.CloneModeRadio):
     _LCD_CONTRAST = ["Level %d" % x for x in range(1, 16)]
     _LAMP_CONTROL = ["Manual", "Auto"]
     _LAMP_TIMER = ["Seconds %d" % x for x in range(2, 11)]
-    _BATTERY_SAVER = [ "OFF", "0.03 Seconds", "0.2 Seconds", "0.4 Seconds", "0.6 Seconds", "0.8 Seconds", "1 Seconds", "2 Seconds", "3 Seconds", "4 Seconds", "5 Seconds" ]
-    _APO = [ "OFF", "15 Minutes", "30 Minutes", "60 Minutes" ]
-    _AUDIO_BALANCE = [ "Center", "A +50%", "A +100%", "B +50%", "B +100%" ]
-    _KEY_BEEP = [ "OFF", "Radio & GPS", "Radio Only", "GPS Only" ]
+    _BATTERY_SAVER = ["OFF", "0.03 Seconds", "0.2 Seconds", "0.4 Seconds", "0.6 Seconds",
+                      "0.8 Seconds", "1 Seconds", "2 Seconds", "3 Seconds", "4 Seconds", "5 Seconds"]
+    _APO = ["OFF", "15 Minutes", "30 Minutes", "60 Minutes"]
+    _AUDIO_BALANCE = ["Center", "A +50%", "A +100%", "B +50%", "B +100%"]
+    _KEY_BEEP = ["OFF", "Radio & GPS", "Radio Only", "GPS Only"]
 
     def get_features(self):
         rf = chirp_common.RadioFeatures()
@@ -267,7 +270,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
         return name[:name.index('\xff')].rstrip()
 
     def set_channel_name(self, number, name):
-        name = name[:8] + '\xff'*8
+        name = name[:8] + '\xff' * 8
         if number < 999:
             self._memobj.channel_name[number].name = name[:8]
             self.add_dirty_block(self._memobj.channel_name[number])
@@ -290,7 +293,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
 
         if number < 0 or number > (max(THD72_SPECIAL.values()) + 1):
             raise errors.InvalidMemoryLocation(
-                    "Number must be between 0 and 999")
+                "Number must be between 0 and 999")
 
         _mem = self._memobj.memory[number]
         flag = self._memobj.flag[number]
@@ -406,7 +409,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
     def write_block(self, block, map):
         self.pipe.write(struct.pack("<cBHB", "W", 0, block, 0))
         base = block * 256
-        self.pipe.write(map[base:base+256])
+        self.pipe.write(map[base:base + 256])
 
         ack = self.pipe.read(1)
 
@@ -416,12 +419,12 @@ class THD72Radio(chirp_common.CloneModeRadio):
         if blocks is None:
             blocks = range(self._memsize / 256)
         else:
-            blocks = [b for b in blocks if b < self._memsize/256]
+            blocks = [b for b in blocks if b < self._memsize / 256]
 
         if self.command("0M PROGRAM") != "0M":
             raise errors.RadioError("No response from self")
 
-        allblocks = range(self._memsize/256)
+        allblocks = range(self._memsize / 256)
         self.pipe.baudrate = 57600
         try:
             self.pipe.setRTS()
@@ -434,7 +437,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
         count = 0
         for i in allblocks:
             if i not in blocks:
-                data += 256*'\xff'
+                data += 256 * '\xff'
                 continue
             data += self.read_block(i)
             count += 1
@@ -455,7 +458,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
         if blocks is None:
             blocks = range((self._memsize / 256) - 2)
         else:
-            blocks = [b for b in blocks if b < self._memsize/256]
+            blocks = [b for b in blocks if b < self._memsize / 256]
 
         if self.command("0M PROGRAM") != "0M":
             raise errors.RadioError("No response from self")
@@ -546,7 +549,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
                 try:
                     old_val = getattr(obj, setting)
                     LOG.debug("Setting %s(%r) <= %s" % (
-                            element.get_name(), old_val, element.value))
+                        element.get_name(), old_val, element.value))
                     setattr(obj, setting, element.value)
                 except AttributeError as e:
                     LOG.error("Setting %s is not in the memory map: %s" %
@@ -592,7 +595,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
         rs = RadioSetting("display.power_on_msg", "Power on message", val)
         rs.set_apply_callback(self.apply_power_on_msg, display_settings)
         menu.append(rs)
-        
+
         val = RadioSettingValueList(
             self._LCD_CONTRAST,
             self._LCD_CONTRAST[display_settings.contrast - 1])
@@ -600,7 +603,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
                           val)
         rs.set_apply_callback(self.apply_lcd_contrast, display_settings)
         menu.append(rs)
-        
+
         val = RadioSettingValueList(
             self._LAMP_CONTROL,
             self._LAMP_CONTROL[display_settings.lamp_control])
diff --git a/chirp/drivers/tk760g.py b/chirp/drivers/tk760g.py
index 00cd57d..6563e06 100644
--- a/chirp/drivers/tk760g.py
+++ b/chirp/drivers/tk760g.py
@@ -1,4 +1,4 @@
-# Copyright 2016 Pavel Milanes CO7WT, <co7wt at frcuba.co.cu> <pavelmc at gmail.com>
+# Copyright 2016 Pavel Milanes, CO7WT, <pavelmc at gmail.com>
 #
 # 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
@@ -295,6 +295,9 @@ SQL = ["off"] + ["%s" % x for x in range(1, 10)]
 ## BOT = 0, EOT = 1, Both = 2, NONE = 3
 #PTTID = ["BOT", "EOT", "Both", "none"]
 
+# For debugging purposes
+debug = False
+
 KEYS = {
     0x33: "Display character",
     0x35: "Home Channel",                   # Posible portable only, chek it
@@ -336,6 +339,10 @@ def _raw_recv(radio, amount):
     except:
         raise errors.RadioError("Error reading data from radio")
 
+    # DEBUG
+    if debug is True:
+        LOG.debug("<== (%d) bytes:\n\n%s" % (len(data), util.hexprint(data)))
+
     return data
 
 
@@ -346,6 +353,10 @@ def _raw_send(radio, data):
     except:
         raise errors.RadioError("Error sending data to radio")
 
+    # DEBUG
+    if debug is True:
+        LOG.debug("==> (%d) bytes:\n\n%s" % (len(data), util.hexprint(data)))
+
 
 def _close_radio(radio):
     """Get the radio out of program mode"""
@@ -441,7 +452,7 @@ def _recv(radio):
 def _open_radio(radio, status):
     """Open the radio into program mode and check if it's the correct model"""
     # linux min is 0.13, win min is 0.25; set to bigger to be safe
-    radio.pipe.timeout = 0.25
+    radio.pipe.timeout = 0.4
     radio.pipe.parity = "E"
 
     # DEBUG
@@ -462,7 +473,7 @@ def _open_radio(radio, status):
 
         if ack != ACK_CMD:
             # DEBUG
-            LOG.debug("Try %s failed, traying again...")
+            LOG.debug("Try %s failed, traying again..." % i)
             time.sleep(0.25)
         else:
             exito = True
@@ -474,7 +485,7 @@ def _open_radio(radio, status):
 
     if exito is False:
         _close_radio(radio)
-        LOG.debug("Radio did not accepted PROGRAM command in five atempts")
+        LOG.debug("Radio did not accepted PROGRAM command in %s atempts" % tries)
         raise errors.RadioError("The radio doesn't accept program mode")
 
     # DEBUG
@@ -717,6 +728,13 @@ class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
              'and raise and issue on the chirp site about it and maybe'
              'it will be implemented in the future.'
              ''
+             'The band limits on some of this radios are set here far from'
+             'the vendor limits to cover most of the ham bands. It is a'
+             'known fact that this radios can work safely outside the OEM'
+             'limits, but each radio is different, you may find in trouble'
+             'with some particular radios, but this is also possible with'
+             'the OEM software; as usual YMMV.'
+             ''
              'A detail: this driver is slow reading from the radio in'
              'Windows, due to the way windows serial works.'
              )
@@ -1513,23 +1531,33 @@ class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
 # This kenwwood family is known as "60-G Serie"
 # all this radios ending in G are compatible:
 #
-# Portables VHF TK-260/270/272/278
-# Portables UHF TK-360/370/372/378/388
+# Portables VHF TK-260G/270G/272G/278G
+# Portables UHF TK-360G/370G/372G/378G/388G
 #
-# Mobiles VHF TK-760/762/768
-# Mobiles VHF TK-860/862/868
+# Mobiles VHF TK-760G/762G/768G
+# Mobiles VHF TK-860G/862G/868G
 #
-# Just dealing with VHF models at moment,
-# this are the radios I can get in hand
-
-# WARNING !!!! Radios With Password in the data section <=###############
+# WARNING !!!! Radios With Password in the data section ###############
 #
 # when a radio has a data password (aka to program it) the last byte (#8)
 # in the id code change from \xf1 to \xb1; so we remove this last byte
 # from the identification procedures and variants.
 #
-# this effectively render the data password USELESS even if set.
-# this can change if user request it with high priority
+# This effectively render the data password USELESS even if set.
+# This can change if user request it with high priority
+#
+# WARNING !!!! the band limits on some of this radios are set here far from the
+# vendor limits to cover most of the ham bands. It's a known fact that this
+# radios can work safely outside the OEM limits, but each radio is different,
+# you may find in trouble with some particular radios, but this is also
+# possible with the OEM software, as usual YMMV.
+#
+# Thanks to Stephen (GN5SWP) with his work characterizing some of the members of
+# this family about the safe band limits.
+#
+# In the radios with mod band limits you will find a comment next to it with
+# the OEM limits just for the record.
+
 
 @directory.register
 class TK868G_Radios(Kenwood_Serie_60G):
@@ -1539,8 +1567,8 @@ class TK868G_Radios(Kenwood_Serie_60G):
     VARIANTS = {
         "M8680\x18\xff":    (8, 400, 490, "M"),
         "M8680;\xff":       (128, 350, 390, "C1"),
-        "M86808\xff":       (128, 400, 440, "C2"),   # 400-430Mhz original
-        "M86806\xff":       (128, 450, 490, "C3"),
+        "M86808\xff":       (128, 400, 450, "C2"),   # 400-430Mhz original
+        "M86806\xff":       (128, 430, 490, "C3"),   # 450-490Mhz original
         }
 
 
@@ -1552,8 +1580,8 @@ class TK862G_Radios(Kenwood_Serie_60G):
     VARIANTS = {
         "M8620\x06\xff":    (8, 450, 490, "K"),
         "M8620\x07\xff":    (8, 485, 512, "K2"),
-        "M8620&\xff":       (8, 440, 470, "E"),
-        "M8620V\xff":       (8, 440, 470, "(N)E"),
+        "M8620&\xff":       (8, 430, 470, "E"),     # 440-470Mhz original
+        "M8620V\xff":       (8, 430, 470, "(N)E"),  # 440-470Mhz original
         }
 
 
@@ -1563,11 +1591,11 @@ class TK860G_Radios(Kenwood_Serie_60G):
     MODEL = "TK-860G"
     TYPE = "M8600"
     VARIANTS = {
-        "M8600\x08\xff":    (128, 400, 440, "K"),   # 400-430Mhz original
-        "M8600\x06\xff":    (128, 450, 490, "K1"),
+        "M8600\x08\xff":    (128, 400, 450, "K"),   # 400-430Mhz original
+        "M8600\x06\xff":    (128, 430, 490, "K1"),  # 450-490Mhz original
         "M8600\x07\xff":    (128, 485, 512, "K2"),
-        "M8600\x18\xff":    (128, 400, 440, "M"),   # 400-430Mhz original
-        "M8600\x16\xff":    (128, 450, 490, "M1"),
+        "M8600\x18\xff":    (128, 400, 450, "M"),   # 400-430Mhz original
+        "M8600\x16\xff":    (128, 430, 490, "M1"),  # 450-490Mhz original
         "M8600\x17\xff":    (128, 485, 520, "M2"),
         }
 
@@ -1607,9 +1635,84 @@ class TK760G_Radios(Kenwood_Serie_60G):
     TYPE = "M7600"
     VARIANTS = {
         "M7600\x05\xff": (128, 136, 162, "K2"),
-        "M7600\x04\xff": (128, 144, 174, "K"),   # 148-174 original
-        "M7600\x14\xff": (128, 144, 174, "M"),   # 148-174 original
-        "M7600T\xff":    (128, 144, 174, "NE")   # 148-174 original
+        "M7600\x04\xff": (128, 138, 174, "K"),   # 148-174 original
+        "M7600\x14\xff": (128, 138, 174, "M"),   # 148-174 original
+        "M7600T\xff":    (128, 138, 174, "NE")   # 148-174 original
+        }
+
+
+ at directory.register
+class TK388G_Radios(Kenwood_Serie_60G):
+    """Kenwood TK-388 Radio [K/E/M/NE]"""
+    MODEL = "TK-388G"
+    TYPE = "P3880"
+    VARIANTS = {
+        "P3880\x1b\xff": (128, 350, 370, "M")
+        }
+
+
+ at directory.register
+class TK378G_Radios(Kenwood_Serie_60G):
+    """Kenwood TK-378 Radio [K/E/M/NE]"""
+    MODEL = "TK-378G"
+    TYPE = "P3780"
+    VARIANTS = {
+        "P3780\x16\xff": (16, 440, 470, "M"),          # 450-470 Mhz original
+        "P3780\x17\xff": (16, 400, 430, "M1"),         # 400-420 Mhz original
+        "P3780\x36\xff": (128, 490, 512, "C"),
+        "P3780\x39\xff": (128, 403, 440, "C1")         # 403-430 Mhz original
+        }
+
+
+ at directory.register
+class TK372G_Radios(Kenwood_Serie_60G):
+    """Kenwood TK-372 Radio [K/E/M/NE]"""
+    MODEL = "TK-372G"
+    TYPE = "P3720"
+    VARIANTS = {
+        "P3720\x06\xff": (32, 440, 470, "K"),        # 450-470 Mhz original
+        "P3720\x07\xff": (32, 470, 490, "K1"),
+        "P3720\x08\xff": (32, 490, 512, "K2"),
+        "P3720\x09\xff": (32, 403, 440, "K3")        # 403-430 Mhz original
+        }
+
+
+ at directory.register
+class TK370G_Radios(Kenwood_Serie_60G):
+    """Kenwood TK-370 Radio [K/E/M/NE]"""
+    MODEL = "TK-370G"
+    TYPE = "P3700"
+    VARIANTS = {
+        "P3700\x06\xff": (128, 440, 470, "K"),      # 450-470 Mhz original
+        "P3700\x07\xff": (128, 470, 490, "K1"),
+        "P3700\x08\xff": (128, 490, 512, "K2"),
+        "P3700\x09\xff": (128, 403, 440, "K3"),     # 403-430 Mhz original
+        "P3700\x16\xff": (128, 440, 470, "M"),      # 450-470 Mhz original
+        "P3700\x17\xff": (128, 470, 490, "M1"),
+        "P3700\x18\xff": (128, 490, 520, "M2"),
+        "P3700\x19\xff": (128, 403, 440, "M3"),     # 403-430 Mhz original
+        "P3700&\xff": (128, 430, 470, "E"),         # 440-470 Mhz original
+        "P3700V\xff": (128, 430, 470, "NE")         # 440-470 Mhz original
+        }
+
+
+ at directory.register
+class TK360G_Radios(Kenwood_Serie_60G):
+    """Kenwood TK-360 Radio [K/E/M/NE]"""
+    MODEL = "TK-360G"
+    TYPE = "P3600"
+    VARIANTS = {
+        "P3600\x06\xff": (8, 440, 470, "K"),        # 450-470 Mhz original
+        "P3600\x07\xff": (8, 470, 490, "K1"),
+        "P3600\x08\xff": (8, 490, 512, "K2"),
+        "P3600\x09\xff": (8, 403, 440, "K3"),       # 403-430 Mhz original
+        "P3600&\xff": (8, 430, 470, "E"),           # 440-470 Mhz original
+        "P3600)\xff": (8, 406, 440, "E1"),          # 403-430 Mhz original
+        "P3600\x16\xff": (8, 440, 470, "M"),        # 450-470 Mhz original
+        "P3600\x17\xff": (8, 470, 490, "M1"),
+        "P3600\x19\xff": (8, 403, 440, "M2"),       # 403-430 Mhz original
+        "P3600V\xff": (8, 430, 470, "NE"),          # 440-470 Mhz original
+        "P3600Y\xff": (8, 403, 440, "NE1")          # 403-430 Mhz original
         }
 
 
@@ -1621,9 +1724,9 @@ class TK278G_Radios(Kenwood_Serie_60G):
     # Note that 16 CH don't have banks
     VARIANTS = {
         "P27805\xff":    (128, 136, 150, "C1"),
-        "P27804\xff":    (128, 150, 174, "C"),
+        "P27804\xff":    (128, 144, 174, "C"),      # 150-174 original
         "P2780\x15\xff": (16,  136, 150, "M1"),
-        "P2780\x14\xff": (16,  150, 174, "M")
+        "P2780\x14\xff": (16,  144, 174, "M")       # 150-174 original
         }
 
 
@@ -1634,7 +1737,7 @@ class TK272G_Radios(Kenwood_Serie_60G):
     TYPE = "P2720"
     VARIANTS = {
         "P2720\x05\xfb": (32, 136, 150, "K1"),
-        "P2720\x04\xfb": (32, 150, 174, "K")
+        "P2720\x04\xfb": (32, 144, 174, "K")        # 150-174 original
         }
 
 
@@ -1646,9 +1749,9 @@ class TK270G_Radios(Kenwood_Serie_60G):
     VARIANTS = {
         "P2700T\xff":    (128, 144, 174, "NE/NT"),   # 146-174 original
         "P2700$\xff":    (128, 144, 174, "E"),       # 146-174 original
-        "P2700\x14\xff": (128, 150, 174, "M"),
+        "P2700\x14\xff": (128, 144, 174, "M"),       # 150-174 original
         "P2700\x05\xff": (128, 136, 150, "K1"),
-        "P2700\x04\xff": (128, 150, 174, "K"),
+        "P2700\x04\xff": (128, 144, 174, "K")        # 150-174 original
         }
 
 
@@ -1660,9 +1763,9 @@ class TK260G_Radios(Kenwood_Serie_60G):
     TYPE = "P2600"
     VARIANTS = {
         "P2600U\xff":    (8, 136, 150, "N1"),
-        "P2600T\xff":    (8, 144, 174, "N"),   # 146-174 original
-        "P2600$\xff":    (8, 144, 174, "E"),   # 144-174 original
-        "P2600\x14\xff": (8, 150, 174, "M"),
+        "P2600T\xff":    (8, 144, 174, "N"),        # 146-174 original
+        "P2600$\xff":    (8, 144, 174, "E"),        # 144-174 original
+        "P2600\x14\xff": (8, 144, 174, "M"),        # 150-174 original
         "P2600\x05\xff": (8, 136, 150, "K1"),
-        "P2600\x04\xff": (8, 150, 174, "K")
+        "P2600\x04\xff": (8, 144, 174, "K")         # 150-174 original
         }
diff --git a/chirp/drivers/uv5r.py b/chirp/drivers/uv5r.py
index 9510831..e2ebc7f 100644
--- a/chirp/drivers/uv5r.py
+++ b/chirp/drivers/uv5r.py
@@ -284,7 +284,7 @@ BASETYPE_UV5R = ["BFS", "BFB", "N5R-2", "N5R2", "N5RV", "BTS", "D5R2"]
 BASETYPE_F11 = ["USA"]
 BASETYPE_UV82 = ["US2S", "B82S", "BF82", "N82-2", "N822"]
 BASETYPE_BJ55 = ["BJ55"]  # needed for for the Baojie UV-55 in bjuv55.py
-BASETYPE_UV6 = ["BF1"]
+BASETYPE_UV6 = ["BF1", "UV6"]
 BASETYPE_KT980HP = ["BFP3V3 B"]
 BASETYPE_F8HP = ["BFP3V3 F", "N5R-3", "N5R3", "F5R3", "BFT"]
 BASETYPE_UV82HP = ["N82-3", "N823"]
@@ -411,9 +411,42 @@ def _do_ident(radio, magic):
         raise errors.RadioError("Radio did not respond")
 
     serial.write("\x02")
-    ident = serial.read(8)
 
-    LOG.info("Ident: %s" % util.hexprint(ident))
+    # Until recently, the "ident" returned by the radios supported by this
+    # driver have always been 8 bytes long. The image sturcture is the 8 byte
+    # "ident" followed by the downloaded memory data. So all of the settings
+    # structures are offset by 8 bytes. The ident returned from a UV-6 radio
+    # can be 8 bytes (original model) or now 12 bytes.
+    #
+    # To accomodate this, the "ident" is now read one byte at a time until the
+    # last byte ("\xdd") is encountered. The bytes containing the value "\x01"
+    # are discarded to shrink the "ident" length down to 8 bytes to keep the
+    # image data aligned with the existing settings structures.
+
+    # Ok, get the response
+    response = ""
+    for i in range(1, 13):
+        byte = serial.read(1)
+        response += byte
+        # stop reading once the last byte ("\xdd") is encountered
+        if byte == "\xdd":
+            break
+
+    # check if response is OK
+    if len(response) in [8, 12]:
+        # DEBUG
+        LOG.info("Valid response, got this:")
+        LOG.debug(util.hexprint(response))
+        if len(response) == 12:
+            ident = response[0] + response[3] + response[5] + response[7:]
+        else:
+            ident = response
+    else:
+        # bad response
+        msg = "Unexpected response, got this:"
+        msg += util.hexprint(response)
+        LOG.debug(msg)
+        raise errors.RadioError("Unexpected response from radio.")
 
     serial.write("\x06")
     ack = serial.read(1)
@@ -789,7 +822,7 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
             mem.power = levels[_mem.lowpower]
         except IndexError:
             LOG.error("Radio reported invalid power level %s (in %s)" %
-                      (_mem.power, levels))
+                      (_mem.lowpower, levels))
             mem.power = levels[0]
 
         mem.mode = _mem.wide and "FM" or "NFM"
@@ -1486,7 +1519,7 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
         dtmf.append(rs)
 
         rs = RadioSetting("pttlt", "PTT ID Delay",
-                          RadioSettingValueInteger(0, 30, _settings.pttlt))
+                          RadioSettingValueInteger(0, 50, _settings.pttlt))
         dtmf.append(rs)
 
         if not self._is_orig():
@@ -1574,9 +1607,14 @@ class UV5XAlias(chirp_common.Alias):
     MODEL = "UV-5X"
 
 
+class RT5RAlias(chirp_common.Alias):
+    VENDOR = "Retevis"
+    MODEL = "RT-5R"
+
+
 @directory.register
 class BaofengUV5RGeneric(BaofengUV5R):
-    ALIASES = [UV5XAlias]
+    ALIASES = [UV5XAlias, RT5RAlias]
 
 
 @directory.register
diff --git a/chirp/drivers/uv5x3.py b/chirp/drivers/uv5x3.py
new file mode 100644
index 0000000..9246121
--- /dev/null
+++ b/chirp/drivers/uv5x3.py
@@ -0,0 +1,1173 @@
+# Copyright 2016:
+# * Jim Unroe KC9HI, <rock.unroe at gmail.com>
+#
+# 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 2 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
+
+LOG = logging.getLogger(__name__)
+
+from chirp.drivers import baofeng_common
+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
+
+##### MAGICS #########################################################
+
+# BTECH UV-5X3 magic string
+MSTRING_UV5X3 = "\x50\x0D\x0C\x20\x16\x03\x28"
+
+##### ID strings #####################################################
+
+# BTECH UV-5X3
+UV5X3_fp1 = "UVVG302"  # BFB300 original
+UV5X3_fp2 = "UVVG301"  # UVV300 original
+UV5X3_fp3 = "UVVG306"  # UVV306 original
+
+DTMF_CHARS = " 1234567890*#ABCD"
+STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0]
+
+LIST_AB = ["A", "B"]
+LIST_ALMOD = ["Site", "Tone", "Code"]
+LIST_BANDWIDTH = ["Wide", "Narrow"]
+LIST_COLOR = ["Off", "Blue", "Orange", "Purple"]
+LIST_DELAYPROCTIME = ["%s ms" % x for x in range(100, 4100, 100)]
+LIST_DTMFSPEED = ["%s ms" % x for x in range(50, 2010, 10)]
+LIST_DTMFST = ["Off", "DT-ST", "ANI-ST", "DT+ANI"]
+LIST_MODE = ["Channel", "Name", "Frequency"]
+LIST_OFF1TO9 = ["Off"] + list("123456789")
+LIST_OFF1TO10 = LIST_OFF1TO9 + ["10"]
+LIST_OFFAB = ["Off"] + LIST_AB
+LIST_RESETTIME = ["%s ms" % x for x in range(100, 16100, 100)]
+LIST_RESUME = ["TO", "CO", "SE"]
+LIST_PONMSG = ["Full", "Message"]
+LIST_PTTID = ["Off", "BOT", "EOT", "Both"]
+LIST_SCODE = ["%s" % x for x in range(1, 16)]
+LIST_RPSTE = ["Off"] + ["%s" % x for x in range(1, 11)]
+LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"]
+LIST_SHIFTD = ["Off", "+", "-"]
+LIST_STEDELAY = ["Off"] + ["%s ms" % x for x in range(100, 1100, 100)]
+LIST_STEP = [str(x) for x in STEPS]
+LIST_TIMEOUT = ["%s sec" % x for x in range(15, 615, 15)]
+LIST_TXPOWER = ["High", "Low"]
+LIST_VOICE = ["Off", "English", "Chinese"]
+LIST_WORKMODE = ["Frequency", "Channel"]
+
+def model_match(cls, data):
+    """Match the opened/downloaded image to the correct version"""
+    match_rid1 = False
+    match_rid2 = False
+
+    rid1 = data[0x1EF0:0x1EF7]
+
+    if rid1 in cls._fileid:
+        match_rid1 = True
+
+    if match_rid1:
+        return True
+    else:
+        return False
+
+
+ at directory.register
+class UV5X3(baofeng_common.BaofengCommonHT):
+    """BTech UV-5X3"""
+    VENDOR = "BTECH"
+    MODEL = "UV-5X3"
+
+    _fileid = [UV5X3_fp3,
+               UV5X3_fp2,
+               UV5X3_fp1]
+
+    _magic = [MSTRING_UV5X3, ]
+    _magic_response_length = 14
+    _fw_ver_start = 0x1EF0
+    _recv_block_size = 0x40
+    _mem_size = 0x2000
+    _ack_block = True
+
+    _ranges = [(0x0000, 0x0DF0),
+               (0x0E00, 0x1800),
+               (0x1EE0, 0x1EF0),
+               (0x1F80, 0x1F90),
+               (0x1FA0, 0x1FB0),
+               (0x1FE0, 0x2000)]
+    _send_block_size = 0x10
+
+    MODES = ["FM", "NFM"]
+    VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
+        "!@#$%^&*()+-=[]:\";'<>?,./"
+    LENGTH_NAME = 7
+    SKIP_VALUES = ["", "S"]
+    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
+    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
+                    chirp_common.PowerLevel("Low", watts=1.00)]
+    VALID_BANDS = [(130000000, 180000000),
+                   (220000000, 226000000),
+                   (400000000, 521000000)]
+    PTTID_LIST = LIST_PTTID
+    SCODE_LIST = LIST_SCODE
+
+
+    MEM_FORMAT = """
+    #seekto 0x0000;
+    struct {
+      lbcd rxfreq[4];
+      lbcd txfreq[4];
+      ul16 rxtone;
+      ul16 txtone;
+      u8 unknown0:4,
+         scode:4;
+      u8 unknown1;
+      u8 unknown2:7,
+         lowpower:1;
+      u8 unknown3:1,
+         wide:1,
+         unknown4:2,
+         bcl:1,
+         scan:1,
+         pttid:2;
+    } memory[128];
+
+    #seekto 0x0B00;
+    struct {
+      u8 code[16];
+    } pttid[15];
+
+    #seekto 0x0C80;
+    struct {
+      u8 inspection[8];
+      u8 monitor[8];        
+      u8 alarmcode[8];      
+      u8 stun[8];           
+      u8 kill[8];           
+      u8 revive[8];         
+      u8 code[7];           
+      u8 unknown06;
+      u8 dtmfon;            
+      u8 dtmfoff;           
+      u8 unused00:6,        
+         aniid:2;
+      u8 unknown07[5];
+      u8 masterid[5];       
+      u8 unknown08[3];
+      u8 viceid[5];
+      u8 unknown09[3];
+      u8 unused01:7,
+         mastervice:1;
+      u8 unused02:3,     
+         mrevive:1,
+         mkill:1,
+         mstun:1,
+         mmonitor:1,
+         minspection:1;
+      u8 unused03:3,     
+         vrevive:1,
+         vkill:1,
+         vstun:1,
+         vmonitor:1,
+         vinspection:1;
+      u8 unused04:6,
+         txdisable:1,
+         rxdisable:1;
+      u8 groupcode;
+      u8 spacecode;
+      u8 delayproctime;
+      u8 resettime;
+    } ani;
+
+    #seekto 0x0E20;
+    struct {
+      u8 unused00:4,
+         squelch:4;
+      u8 unused01:5,     
+         step:3;
+      u8 unknown00;      
+      u8 unused02:5,
+         save:3;
+      u8 unused03:4,
+         vox:4;
+      u8 unknown01;      
+      u8 unused04:4,     
+         abr:4;
+      u8 unused05:7,     
+         tdr:1;
+      u8 unused06:7,     
+         beep:1;
+      u8 unused07:2,     
+         timeout:6;
+      u8 unknown02[4];
+      u8 unused09:6,
+         voice:2;
+      u8 unknown03;      
+      u8 unused10:6,
+         dtmfst:2;
+      u8 unknown04;
+      u8 unused11:6,
+         screv:2;
+      u8 unused12:6,
+         pttid:2;
+      u8 unused13:2,
+         pttlt:6;
+      u8 unused14:6,
+         mdfa:2;
+      u8 unused15:6,
+         mdfb:2;
+      u8 unknown05;
+      u8 unused16:7,
+         sync:1;
+      u8 unknown06[4];
+      u8 unused17:6,
+         wtled:2;
+      u8 unused18:6,
+         rxled:2;
+      u8 unused19:6,
+         txled:2;
+      u8 unused20:6,
+         almod:2;
+      u8 unknown07;
+      u8 unused21:6,
+         tdrab:2;
+      u8 unused22:7,
+         ste:1;
+      u8 unused23:4,
+         rpste:4;
+      u8 unused24:4,
+         rptrl:4;
+      u8 unused25:7,
+         ponmsg:1;
+      u8 unused26:7,
+         roger:1;
+      u8 unused27:7,
+         dani:1;
+      u8 unused28:2,
+         dtmfg:6;
+      u8 unknown08:6,    
+         reset:1,
+         unknown09:1;
+      u8 unknown10[3];   
+      u8 cht;            
+      u8 unknown11[13];  
+      u8 displayab:1,
+         unknown12:2,
+         fmradio:1,
+         alarm:1,
+         unknown13:2,
+         menu:1;
+      u8 unknown14;      
+      u8 unused29:7,
+         workmode:1;
+      u8 unused30:7,
+         keylock:1;
+    } settings;
+    
+    #seekto 0x0E76;
+    struct {
+      u8 unused0:1,
+         mrcha:7;
+      u8 unused1:1,
+         mrchb:7;
+    } wmchannel;
+    
+    struct vfo {
+      u8 unknown0[8];
+      u8 freq[8];
+      u8 offset[6];
+      ul16 rxtone;
+      ul16 txtone;
+      u8 unused0:7,
+         band:1;
+      u8 unknown3;
+      u8 unknown4:2,
+         sftd:2,
+         scode:4;
+      u8 unknown5;
+      u8 unknown6:1,
+         step:3,
+         unknown7:4;
+      u8 txpower:1,
+         widenarr:1,
+         unknown8:6;
+    };
+    
+    #seekto 0x0F00;
+    struct {
+      struct vfo a;
+      struct vfo b;
+    } vfo;
+    
+    #seekto 0x0F4E;
+    u16 fm_presets;
+
+    #seekto 0x1000;
+    struct {
+      char name[7];
+      u8 unknown[9];
+    } names[128];
+    
+    #seekto 0x1ED0;
+    struct {
+      char line1[7];
+      char line2[7];
+    } sixpoweron_msg;
+    
+    #seekto 0x1EF0;
+    struct {
+      char line1[7];
+      char line2[7];
+    } firmware_msg;
+    
+    struct squelch {
+      u8 sql0;
+      u8 sql1;
+      u8 sql2;
+      u8 sql3;
+      u8 sql4;
+      u8 sql5;
+      u8 sql6;
+      u8 sql7;
+      u8 sql8;
+      u8 sql9;
+    };
+    
+    #seekto 0x1F80;
+    struct {
+      struct squelch vhf;
+      u8 unknown0[6];
+      u8 unknown1[16];
+      struct squelch uhf;
+    } squelch;
+
+    #seekto 0x1FE0;
+    struct {
+      char line1[7];
+      char line2[7];
+    } poweron_msg;
+    
+    struct limit {
+      u8 enable;
+      bbcd lower[2];
+      bbcd upper[2];
+    };
+
+    #seekto 0x1FF0;
+    struct {
+      struct limit vhf;
+      struct limit vhf2;
+      struct limit uhf;
+    } limits;
+
+    """
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.experimental = \
+            ('The BTech UV-5X3 driver is a beta version.\n'
+             '\n'
+             'Please save an unedited copy of your first successful\n'
+             'download to a CHIRP Radio Images(*.img) file.'
+             )
+        rp.pre_download = _(dedent("""\
+            Follow these instructions to download your info:
+
+            1 - Turn off your radio
+            2 - Connect your interface cable
+            3 - Turn on your radio
+            4 - Do the download of your radio data
+            """))
+        rp.pre_upload = _(dedent("""\
+            Follow this instructions to upload your info:
+
+            1 - Turn off your radio
+            2 - Connect your interface cable
+            3 - Turn on your radio
+            4 - Do the upload of your radio data
+            """))
+        return rp
+
+    def process_mmap(self):
+        """Process the mem map into the mem object"""
+        self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap)
+
+    def get_settings(self):
+        """Translate the bit in the mem_struct into settings in the UI"""
+        _mem = self._memobj
+        basic = RadioSettingGroup("basic", "Basic Settings")
+        advanced = RadioSettingGroup("advanced", "Advanced Settings")
+        other = RadioSettingGroup("other", "Other Settings")
+        work = RadioSettingGroup("work", "Work Mode Settings")
+        fm_preset = RadioSettingGroup("fm_preset", "FM Preset")
+        dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings")
+        dtmfd = RadioSettingGroup("dtmfd", "DTMF Decode Settings")
+        service = RadioSettingGroup("service", "Service Settings")
+        top = RadioSettings(basic, advanced, other, work, fm_preset, dtmfe,
+                            dtmfd, service)
+
+        # Basic settings
+        if _mem.settings.squelch > 0x09:
+            val = 0x00
+        else:
+            val = _mem.settings.squelch
+        rs = RadioSetting("settings.squelch", "Squelch",
+                          RadioSettingValueList(
+                              LIST_OFF1TO9, LIST_OFF1TO9[val]))
+        basic.append(rs)
+
+        if _mem.settings.save > 0x04:
+            val = 0x00
+        else:
+            val = _mem.settings.save
+        rs = RadioSetting("settings.save", "Battery Saver",
+                          RadioSettingValueList(
+                              LIST_SAVE, LIST_SAVE[val]))
+        basic.append(rs)
+
+        if _mem.settings.vox > 0x0A:
+            val = 0x00
+        else:
+            val = _mem.settings.vox
+        rs = RadioSetting("settings.vox", "Vox",
+                          RadioSettingValueList(
+                              LIST_OFF1TO10, LIST_OFF1TO10[val]))
+        basic.append(rs)
+
+        if _mem.settings.abr > 0x0A:
+            val = 0x00
+        else:
+            val = _mem.settings.abr
+        rs = RadioSetting("settings.abr", "Backlight Timeout",
+                          RadioSettingValueList(
+                              LIST_OFF1TO10, LIST_OFF1TO10[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.tdr", "Dual Watch",
+                          RadioSettingValueBoolean(_mem.settings.tdr))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.beep", "Beep",
+                           RadioSettingValueBoolean(_mem.settings.beep))
+        basic.append(rs)
+
+        if _mem.settings.timeout > 0x27:
+            val = 0x03
+        else:
+            val = _mem.settings.timeout
+        rs = RadioSetting("settings.timeout", "Timeout Timer",
+                          RadioSettingValueList(
+                              LIST_TIMEOUT, LIST_TIMEOUT[val]))
+        basic.append(rs)
+
+        if _mem.settings.voice > 0x02:
+            val = 0x01
+        else:
+            val = _mem.settings.voice
+        rs = RadioSetting("settings.voice", "Voice Prompt",
+                          RadioSettingValueList(
+                              LIST_VOICE, LIST_VOICE[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.dtmfst", "DTMF Sidetone",
+                          RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
+                              _mem.settings.dtmfst]))
+        basic.append(rs)
+
+        if _mem.settings.screv > 0x02:
+            val = 0x01
+        else:
+            val = _mem.settings.screv
+        rs = RadioSetting("settings.screv", "Scan Resume",
+                          RadioSettingValueList(
+                              LIST_RESUME, LIST_RESUME[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.pttid", "When to send PTT ID",
+                          RadioSettingValueList(LIST_PTTID, LIST_PTTID[
+                              _mem.settings.pttid]))
+        basic.append(rs)
+
+        if _mem.settings.pttlt > 0x1E:
+            val = 0x05
+        else:
+            val = _mem.settings.pttlt
+        rs = RadioSetting("pttlt", "PTT ID Delay",
+                          RadioSettingValueInteger(0, 50, val))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.mdfa", "Display Mode (A)",
+                          RadioSettingValueList(LIST_MODE, LIST_MODE[
+                              _mem.settings.mdfa]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.mdfb", "Display Mode (B)",
+                          RadioSettingValueList(LIST_MODE, LIST_MODE[
+                              _mem.settings.mdfb]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.sync", "Sync A & B",
+                          RadioSettingValueBoolean(_mem.settings.sync))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.wtled", "Standby LED Color",
+                          RadioSettingValueList(
+                              LIST_COLOR, LIST_COLOR[_mem.settings.wtled]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.rxled", "RX LED Color",
+                          RadioSettingValueList(
+                              LIST_COLOR, LIST_COLOR[_mem.settings.rxled]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.txled", "TX LED Color",
+                          RadioSettingValueList(
+                              LIST_COLOR, LIST_COLOR[_mem.settings.txled]))
+        basic.append(rs)
+
+        if _mem.settings.almod > 0x02:
+            val = 0x00
+        else:
+            val = _mem.settings.almod
+        rs = RadioSetting("settings.almod", "Alarm Mode",
+                          RadioSettingValueList(
+                              LIST_ALMOD, LIST_ALMOD[val]))
+        basic.append(rs)
+
+        if _mem.settings.tdrab > 0x02:
+            val = 0x00
+        else:
+            val = _mem.settings.tdrab
+        rs = RadioSetting("settings.tdrab", "Dual Watch TX Priority",
+                          RadioSettingValueList(
+                              LIST_OFFAB, LIST_OFFAB[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.ste", "Squelch Tail Eliminate (HT to HT)",
+                          RadioSettingValueBoolean(_mem.settings.ste))
+        basic.append(rs)
+
+        if _mem.settings.rpste > 0x0A:
+            val = 0x00
+        else:
+            val = _mem.settings.rpste
+        rs = RadioSetting("settings.rpste",
+                          "Squelch Tail Eliminate (repeater)",
+                              RadioSettingValueList(
+                              LIST_RPSTE, LIST_RPSTE[val]))
+        basic.append(rs)
+
+        if _mem.settings.rptrl > 0x0A:
+            val = 0x00
+        else:
+            val = _mem.settings.rptrl
+        rs = RadioSetting("settings.rptrl", "STE Repeater Delay",
+                          RadioSettingValueList(
+                              LIST_STEDELAY, LIST_STEDELAY[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.ponmsg", "Power-On Message",
+                          RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
+                              _mem.settings.ponmsg]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.roger", "Roger Beep",
+                          RadioSettingValueBoolean(_mem.settings.roger))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.dani", "Decode ANI",
+                          RadioSettingValueBoolean(_mem.settings.dani))
+        basic.append(rs)
+
+        if _mem.settings.dtmfg > 0x3C:
+            val = 0x14
+        else:
+            val = _mem.settings.dtmfg
+        rs = RadioSetting("settings.dtmfg", "DTMF Gain",
+                          RadioSettingValueInteger(0, 60, val))
+        basic.append(rs)
+
+        # Advanced settings
+        rs = RadioSetting("settings.reset", "RESET Menu",
+                          RadioSettingValueBoolean(_mem.settings.reset))
+        advanced.append(rs)
+
+        rs = RadioSetting("settings.menu", "All Menus",
+                          RadioSettingValueBoolean(_mem.settings.menu))
+        advanced.append(rs)
+
+        rs = RadioSetting("settings.fmradio", "Broadcast FM Radio",
+                          RadioSettingValueBoolean(_mem.settings.fmradio))
+        advanced.append(rs)
+
+        rs = RadioSetting("settings.alarm", "Alarm Sound",
+                          RadioSettingValueBoolean(_mem.settings.alarm))
+        advanced.append(rs)
+
+        # Other settings
+        def _filter(name):
+            filtered = ""
+            for char in str(name):
+                if char in chirp_common.CHARSET_ASCII:
+                    filtered += char
+                else:
+                    filtered += " "
+            return filtered
+
+        _msg = _mem.firmware_msg
+        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
+        val.set_mutable(False)
+        rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
+        other.append(rs)
+
+        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
+        val.set_mutable(False)
+        rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
+        other.append(rs)
+
+        _msg = _mem.sixpoweron_msg
+        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
+        val.set_mutable(False)
+        rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", val)
+        other.append(rs)
+        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
+        val.set_mutable(False)
+        rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", val)
+        other.append(rs)
+
+        _msg = _mem.poweron_msg
+        rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
+                          RadioSettingValueString(
+                              0, 7, _filter(_msg.line1)))
+        other.append(rs)
+        rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
+                          RadioSettingValueString(
+                              0, 7, _filter(_msg.line2)))
+        other.append(rs)
+
+        if str(_mem.firmware_msg.line1) == "UVVG302":
+            lower = 136
+            upper = 174
+        else:
+            lower = 130
+            upper = 179
+        rs = RadioSetting("limits.vhf.lower", "VHF Lower Limit (MHz)",
+                          RadioSettingValueInteger(
+                              lower, upper, _mem.limits.vhf.lower))
+        other.append(rs)
+
+        rs = RadioSetting("limits.vhf.upper", "VHF Upper Limit (MHz)",
+                          RadioSettingValueInteger(
+                              lower, upper, _mem.limits.vhf.upper))
+        other.append(rs)
+
+        if str(_mem.firmware_msg.line1) == "UVVG302":
+            lower = 200
+            upper = 230
+        else:
+            lower = 220
+            upper = 225
+        rs = RadioSetting("limits.vhf2.lower", "VHF2 Lower Limit (MHz)",
+                          RadioSettingValueInteger(
+                              lower, upper, _mem.limits.vhf2.lower))
+        other.append(rs)
+
+        rs = RadioSetting("limits.vhf2.upper", "VHF2 Upper Limit (MHz)",
+                          RadioSettingValueInteger(
+                              lower, upper, _mem.limits.vhf2.upper))
+        other.append(rs)
+
+        if str(_mem.firmware_msg.line1) == "UVVG302":
+            lower = 400
+            upper = 480
+        else:
+            lower = 400
+            upper = 520
+        rs = RadioSetting("limits.uhf.lower", "UHF Lower Limit (MHz)",
+                          RadioSettingValueInteger(
+                              lower, upper, _mem.limits.uhf.lower))
+        other.append(rs)
+
+        rs = RadioSetting("limits.uhf.upper", "UHF Upper Limit (MHz)",
+                          RadioSettingValueInteger(
+                              lower, upper, _mem.limits.uhf.upper))
+        other.append(rs)
+
+        # Work mode settings
+        rs = RadioSetting("settings.displayab", "Display",
+                          RadioSettingValueList(
+                              LIST_AB, LIST_AB[_mem.settings.displayab]))
+        work.append(rs)
+
+        rs = RadioSetting("settings.workmode", "VFO/MR Mode",
+                          RadioSettingValueList(
+                              LIST_WORKMODE,
+                              LIST_WORKMODE[_mem.settings.workmode]))
+        work.append(rs)
+
+        rs = RadioSetting("settings.keylock", "Keypad Lock",
+                          RadioSettingValueBoolean(_mem.settings.keylock))
+        work.append(rs)
+
+        rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
+                          RadioSettingValueInteger(0, 127,
+                                                      _mem.wmchannel.mrcha))
+        work.append(rs)
+
+        rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
+                          RadioSettingValueInteger(0, 127,
+                                                      _mem.wmchannel.mrchb))
+        work.append(rs)
+
+        def convert_bytes_to_freq(bytes):
+            real_freq = 0
+            for byte in bytes:
+                real_freq = (real_freq * 10) + byte
+            return chirp_common.format_freq(real_freq * 10)
+
+        def my_validate(value):
+            _vhf_lower = int(_mem.limits.vhf.lower)
+            _vhf_upper = int(_mem.limits.vhf.upper)
+            _vhf2_lower = int(_mem.limits.vhf2.lower)
+            _vhf2_upper = int(_mem.limits.vhf2.upper)
+            _uhf_lower = int(_mem.limits.uhf.lower)
+            _uhf_upper = int(_mem.limits.uhf.upper)
+            value = chirp_common.parse_freq(value)
+            msg = ("Can't be less than %i.0000")
+            if value > 99000000 and value < _vhf_lower * 1000000:
+                raise InvalidValueError(msg % (_vhf_lower))
+            msg = ("Can't be between %i.9975-%i.0000")
+            if (_vhf_upper + 1) * 1000000 <= value and \
+                value < _vhf2_lower * 1000000:
+                raise InvalidValueError(msg % (_vhf_upper, _vhf2_lower))
+            if (_vhf2_upper + 1) * 1000000 <= value and \
+                value < _uhf_lower * 1000000:
+                raise InvalidValueError(msg % (_vhf2_upper, _uhf_lower))
+            msg = ("Can't be greater than %i.9975")
+            if value > 99000000 and value >= (_uhf_upper + 1) * 1000000:
+                raise InvalidValueError(msg % (_uhf_upper))
+            return chirp_common.format_freq(value)
+
+        def apply_freq(setting, obj):
+            value = chirp_common.parse_freq(str(setting.value)) / 10
+            for i in range(7, -1, -1):
+                obj.freq[i] = value % 10
+                value /= 10
+
+        val1a = RadioSettingValueString(0, 10,
+                                        convert_bytes_to_freq(_mem.vfo.a.freq))
+        val1a.set_validate_callback(my_validate)
+        rs = RadioSetting("vfo.a.freq", "VFO A Frequency", val1a)
+        rs.set_apply_callback(apply_freq, _mem.vfo.a)
+        work.append(rs)
+
+        val1b = RadioSettingValueString(0, 10,
+                                        convert_bytes_to_freq(_mem.vfo.b.freq))
+        val1b.set_validate_callback(my_validate)
+        rs = RadioSetting("vfo.b.freq", "VFO B Frequency", val1b)
+        rs.set_apply_callback(apply_freq, _mem.vfo.b)
+        work.append(rs)
+
+        rs = RadioSetting("vfo.a.sftd", "VFO A Shift",
+                          RadioSettingValueList(
+                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.a.sftd]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.b.sftd", "VFO B Shift",
+                          RadioSettingValueList(
+                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.b.sftd]))
+        work.append(rs)
+
+        def convert_bytes_to_offset(bytes):
+            real_offset = 0
+            for byte in bytes:
+                real_offset = (real_offset * 10) + byte
+            return chirp_common.format_freq(real_offset * 1000)
+
+        def apply_offset(setting, obj):
+            value = chirp_common.parse_freq(str(setting.value)) / 1000
+            for i in range(5, -1, -1):
+                obj.offset[i] = value % 10
+                value /= 10
+
+        val1a = RadioSettingValueString(
+                    0, 10, convert_bytes_to_offset(_mem.vfo.a.offset))
+        rs = RadioSetting("vfo.a.offset",
+                          "VFO A Offset", val1a)
+        rs.set_apply_callback(apply_offset, _mem.vfo.a)
+        work.append(rs)
+
+        val1b = RadioSettingValueString(
+                    0, 10, convert_bytes_to_offset(_mem.vfo.b.offset))
+        rs = RadioSetting("vfo.b.offset",
+                          "VFO B Offset", val1b)
+        rs.set_apply_callback(apply_offset, _mem.vfo.b)
+        work.append(rs)
+
+        rs = RadioSetting("vfo.a.txpower", "VFO A Power",
+                          RadioSettingValueList(
+                              LIST_TXPOWER,
+                              LIST_TXPOWER[_mem.vfo.a.txpower]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.b.txpower", "VFO B Power",
+                          RadioSettingValueList(
+                              LIST_TXPOWER,
+                              LIST_TXPOWER[_mem.vfo.b.txpower]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.a.widenarr", "VFO A Bandwidth",
+                          RadioSettingValueList(
+                              LIST_BANDWIDTH,
+                              LIST_BANDWIDTH[_mem.vfo.a.widenarr]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.b.widenarr", "VFO B Bandwidth",
+                          RadioSettingValueList(
+                              LIST_BANDWIDTH,
+                              LIST_BANDWIDTH[_mem.vfo.b.widenarr]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.a.scode", "VFO A S-CODE",
+                          RadioSettingValueList(
+                              LIST_SCODE,
+                              LIST_SCODE[_mem.vfo.a.scode]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.b.scode", "VFO B S-CODE",
+                          RadioSettingValueList(
+                              LIST_SCODE,
+                              LIST_SCODE[_mem.vfo.b.scode]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.a.step", "VFO A Tuning Step",
+                          RadioSettingValueList(
+                              LIST_STEP, LIST_STEP[_mem.vfo.a.step]))
+        work.append(rs)
+        rs = RadioSetting("vfo.b.step", "VFO B Tuning Step",
+                          RadioSettingValueList(
+                              LIST_STEP, LIST_STEP[_mem.vfo.b.step]))
+        work.append(rs)
+
+        # broadcast FM settings
+        _fm_presets = self._memobj.fm_presets
+        if _fm_presets <= 108.0 * 10 - 650:
+            preset = _fm_presets / 10.0 + 65
+        elif _fm_presets >= 65.0 * 10 and _fm_presets <= 108.0 * 10:
+            preset = _fm_presets / 10.0
+        else:
+            preset = 76.0
+        rs = RadioSetting("fm_presets", "FM Preset(MHz)",
+                          RadioSettingValueFloat(65, 108.0, preset, 0.1, 1))
+        fm_preset.append(rs)
+
+        # DTMF encode settings
+        for i in range(0, 15):
+            _codeobj = self._memobj.pttid[i].code
+            _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+            val = RadioSettingValueString(0, 16, _code, False)
+            val.set_charset(DTMF_CHARS)
+            rs = RadioSetting("pttid/%i.code" % i,
+                              "Signal Code %i" % (i + 1), val)
+
+            def apply_code(setting, obj):
+                code = []
+                for j in range(0, 16):
+                    try:
+                        code.append(DTMF_CHARS.index(str(setting.value)[j]))
+                    except IndexError:
+                        code.append(0xFF)
+                obj.code = code
+            rs.set_apply_callback(apply_code, self._memobj.pttid[i])
+            dtmfe.append(rs)
+
+        if _mem.ani.dtmfon > 0xC3:
+            val = 0x03
+        else:
+            val = _mem.ani.dtmfon
+        rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
+                          RadioSettingValueList(LIST_DTMFSPEED,
+                                                LIST_DTMFSPEED[val]))
+        dtmfe.append(rs)
+
+        if _mem.ani.dtmfoff > 0xC3:
+            val = 0x03
+        else:
+            val = _mem.ani.dtmfoff
+        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
+                          RadioSettingValueList(LIST_DTMFSPEED,
+                                                LIST_DTMFSPEED[val]))
+        dtmfe.append(rs)
+
+        _codeobj = self._memobj.ani.code
+        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+        val = RadioSettingValueString(0, 7, _code, False)
+        val.set_charset(DTMF_CHARS)
+        rs = RadioSetting("ani.code", "ANI Code", val)
+
+        def apply_code(setting, obj):
+            code = []
+            for j in range(0, 7):
+                try:
+                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
+                except IndexError:
+                    code.append(0xFF)
+            obj.code = code
+        rs.set_apply_callback(apply_code, self._memobj.ani)
+        dtmfe.append(rs)
+
+        rs = RadioSetting("ani.aniid", "When to send ANI ID",
+                          RadioSettingValueList(LIST_PTTID,
+                                                LIST_PTTID[_mem.ani.aniid]))
+        dtmfe.append(rs)
+
+        # DTMF decode settings
+        rs = RadioSetting("ani.mastervice", "Master and Vice ID",
+                          RadioSettingValueBoolean(_mem.ani.mastervice))
+        dtmfd.append(rs)
+
+        _codeobj = _mem.ani.masterid
+        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+        val = RadioSettingValueString(0, 5, _code, False)
+        val.set_charset(DTMF_CHARS)
+        rs = RadioSetting("ani.masterid", "Master Control ID", val)
+
+        def apply_code(setting, obj):
+            code = []
+            for j in range(0, 5):
+                try:
+                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
+                except IndexError:
+                    code.append(0xFF)
+            obj.masterid = code
+        rs.set_apply_callback(apply_code, self._memobj.ani)
+        dtmfd.append(rs)
+
+        rs = RadioSetting("ani.minspection", "Master Inspection",
+                          RadioSettingValueBoolean(_mem.ani.minspection))
+        dtmfd.append(rs)
+
+        rs = RadioSetting("ani.mmonitor", "Master Monitor",
+                          RadioSettingValueBoolean(_mem.ani.mmonitor))
+        dtmfd.append(rs)
+
+        rs = RadioSetting("ani.mstun", "Master Stun",
+                          RadioSettingValueBoolean(_mem.ani.mstun))
+        dtmfd.append(rs)
+
+        rs = RadioSetting("ani.mkill", "Master Kill",
+                          RadioSettingValueBoolean(_mem.ani.mkill))
+        dtmfd.append(rs)
+
+        rs = RadioSetting("ani.mrevive", "Master Revive",
+                          RadioSettingValueBoolean(_mem.ani.mrevive))
+        dtmfd.append(rs)
+
+        _codeobj = _mem.ani.viceid
+        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+        val = RadioSettingValueString(0, 5, _code, False)
+        val.set_charset(DTMF_CHARS)
+        rs = RadioSetting("ani.viceid", "Vice Control ID", val)
+
+        def apply_code(setting, obj):
+            code = []
+            for j in range(0, 5):
+                try:
+                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
+                except IndexError:
+                    code.append(0xFF)
+            obj.viceid = code
+        rs.set_apply_callback(apply_code, self._memobj.ani)
+        dtmfd.append(rs)
+
+        rs = RadioSetting("ani.vinspection", "Vice Inspection",
+                          RadioSettingValueBoolean(_mem.ani.vinspection))
+        dtmfd.append(rs)
+
+        rs = RadioSetting("ani.vmonitor", "Vice Monitor",
+                          RadioSettingValueBoolean(_mem.ani.vmonitor))
+        dtmfd.append(rs)
+
+        rs = RadioSetting("ani.vstun", "Vice Stun",
+                          RadioSettingValueBoolean(_mem.ani.vstun))
+        dtmfd.append(rs)
+
+        rs = RadioSetting("ani.vkill", "Vice Kill",
+                          RadioSettingValueBoolean(_mem.ani.vkill))
+        dtmfd.append(rs)
+
+        rs = RadioSetting("ani.vrevive", "Vice Revive",
+                          RadioSettingValueBoolean(_mem.ani.vrevive))
+        dtmfd.append(rs)
+
+        _codeobj = _mem.ani.inspection
+        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+        val = RadioSettingValueString(0, 8, _code, False)
+        val.set_charset(DTMF_CHARS)
+        rs = RadioSetting("ani.inspection", "Inspection Code", val)
+
+        def apply_code(setting, obj):
+            code = []
+            for j in range(0, 8):
+                try:
+                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
+                except IndexError:
+                    code.append(0xFF)
+            obj.inspection = code
+        rs.set_apply_callback(apply_code, self._memobj.ani)
+        dtmfd.append(rs)
+
+        _codeobj = _mem.ani.monitor
+        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+        val = RadioSettingValueString(0, 8, _code, False)
+        val.set_charset(DTMF_CHARS)
+        rs = RadioSetting("ani.monitor", "Monitor Code", val)
+
+        def apply_code(setting, obj):
+            code = []
+            for j in range(0, 8):
+                try:
+                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
+                except IndexError:
+                    code.append(0xFF)
+            obj.monitor = code
+        rs.set_apply_callback(apply_code, self._memobj.ani)
+        dtmfd.append(rs)
+
+        _codeobj = _mem.ani.alarmcode
+        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+        val = RadioSettingValueString(0, 8, _code, False)
+        val.set_charset(DTMF_CHARS)
+        rs = RadioSetting("ani.alarm", "Alarm Code", val)
+
+        def apply_code(setting, obj):
+            code = []
+            for j in range(0, 8):
+                try:
+                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
+                except IndexError:
+                    code.append(0xFF)
+            obj.alarmcode = code
+        rs.set_apply_callback(apply_code, self._memobj.ani)
+        dtmfd.append(rs)
+
+        _codeobj = _mem.ani.stun
+        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+        val = RadioSettingValueString(0, 8, _code, False)
+        val.set_charset(DTMF_CHARS)
+        rs = RadioSetting("ani.stun", "Stun Code", val)
+
+        def apply_code(setting, obj):
+            code = []
+            for j in range(0, 8):
+                try:
+                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
+                except IndexError:
+                    code.append(0xFF)
+            obj.stun = code
+        rs.set_apply_callback(apply_code, self._memobj.ani)
+        dtmfd.append(rs)
+
+        _codeobj = _mem.ani.kill
+        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+        val = RadioSettingValueString(0, 8, _code, False)
+        val.set_charset(DTMF_CHARS)
+        rs = RadioSetting("ani.kill", "Kill Code", val)
+
+        def apply_code(setting, obj):
+            code = []
+            for j in range(0, 8):
+                try:
+                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
+                except IndexError:
+                    code.append(0xFF)
+            obj.kill = code
+        rs.set_apply_callback(apply_code, self._memobj.ani)
+        dtmfd.append(rs)
+
+        _codeobj = _mem.ani.revive
+        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+        val = RadioSettingValueString(0, 8, _code, False)
+        val.set_charset(DTMF_CHARS)
+        rs = RadioSetting("ani.revive", "Revive Code", val)
+
+        def apply_code(setting, obj):
+            code = []
+            for j in range(0, 8):
+                try:
+                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
+                except IndexError:
+                    code.append(0xFF)
+            obj.revive = code
+        rs.set_apply_callback(apply_code, self._memobj.ani)
+        dtmfd.append(rs)
+
+        if _mem.ani.resettime > 0x9F:
+            val = 0x4F
+        else:
+            val = _mem.ani.resettime
+        rs = RadioSetting("ani.resettime", "Reset Time",
+                          RadioSettingValueList(LIST_RESETTIME,
+                                                LIST_RESETTIME[val]))
+        dtmfd.append(rs)
+
+        if _mem.ani.delayproctime > 0x27:
+            val = 0x04
+        else:
+            val = _mem.ani.delayproctime
+        rs = RadioSetting("ani.delayproctime", "Delay Processing Time",
+                          RadioSettingValueList(LIST_DELAYPROCTIME,
+                                                LIST_DELAYPROCTIME[val]))
+        dtmfd.append(rs)
+
+        # Service settings
+        for band in ["vhf", "uhf"]:
+            for index in range(0, 10):
+                key = "squelch.%s.sql%i" % (band, index)
+                if band == "vhf":
+                    _obj = self._memobj.squelch.vhf
+                elif band == "uhf":
+                    _obj = self._memobj.squelch.uhf
+                val = RadioSettingValueInteger(0, 123,
+                          getattr(_obj, "sql%i" % (index)))
+                if index == 0:
+                    val.set_mutable(False)
+                name = "%s Squelch %i" % (band.upper(), index)
+                rs = RadioSetting(key, name, val)
+                service.append(rs)
+
+        return top
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        match_size = False
+        match_model = False
+
+        # testing the file data size
+        if len(filedata) == 0x200E:
+            match_size = True
+
+        # testing the firmware model fingerprint
+        match_model = model_match(cls, filedata)
+
+        if match_size and match_model:
+            return True
+        else:
+            return False
diff --git a/chirp/drivers/uv6r.py b/chirp/drivers/uv6r.py
new file mode 100644
index 0000000..10fedad
--- /dev/null
+++ b/chirp/drivers/uv6r.py
@@ -0,0 +1,871 @@
+# Copyright 2016:
+# * Jim Unroe KC9HI, <rock.unroe at gmail.com>
+#
+# 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 2 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
+
+LOG = logging.getLogger(__name__)
+
+from chirp.drivers import baofeng_common
+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
+
+##### MAGICS #########################################################
+                                                                             
+# Baofeng UV-6R magic string
+MSTRING_UV6R = "\x50\xBB\xFF\x20\x14\x11\x22"                                
+                                                                             
+##### ID strings #####################################################       
+                                                                             
+# Baofeng UV-6R                                                              
+UV6R_fp1 = " BF230#1"
+
+DTMF_CHARS = "0123456789 *#ABCD"
+STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0]
+
+LIST_AB = ["A", "B"]
+LIST_ALMOD = ["Site", "Tone", "Code"]
+LIST_BANDWIDTH = ["Wide", "Narrow"]
+LIST_COLOR = ["Off", "Blue", "Orange", "Purple"]
+LIST_DTMFSPEED = ["%s ms" % x for x in range(50, 2010, 10)]
+LIST_DTMFST = ["Off", "DT-ST", "ANI-ST", "DT+ANI"]
+LIST_MODE = ["Channel", "Name", "Frequency"]
+LIST_OFF1TO9 = ["Off"] + list("123456789")
+LIST_OFF1TO10 = LIST_OFF1TO9 + ["10"]
+LIST_OFFAB = ["Off"] + LIST_AB
+LIST_RESUME = ["TO", "CO", "SE"]
+LIST_PONMSG = ["Full", "Message"]
+LIST_PTTID = ["Off", "BOT", "EOT", "Both"]
+LIST_SCODE = ["%s" % x for x in range(1, 16)]
+LIST_RPSTE = ["Off"] + ["%s" % x for x in range(1, 11)]
+LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"]
+LIST_SHIFTD = ["Off", "+", "-"]
+LIST_STEDELAY = ["Off"] + ["%s ms" % x for x in range(100, 1100, 100)]
+LIST_STEP = [str(x) for x in STEPS]
+LIST_TCALL = ["Off", "1000 Hz", "1450 Hz", "1750 Hz", "2100 Hz"]
+LIST_TIMEOUT = ["%s sec" % x for x in range(15, 615, 15)]
+LIST_TXPOWER = ["High", "Low"]
+LIST_VOICE = ["Off", "English", "Chinese"]
+LIST_WORKMODE = ["Frequency", "Channel"]
+
+def model_match(cls, data):
+    """Match the opened/downloaded image to the correct version"""
+    match_rid1 = False
+    match_rid2 = False
+
+    rid1 = data[0x1FF8:0x2000]
+
+    if rid1 in cls._fileid:
+        match_rid1 = True
+
+    rid2 = data[0x1FD0:0x1FD5]
+
+    if rid2 == cls.MODEL:
+        match_rid2 = True
+
+    if match_rid1 and match_rid2:
+        return True
+    else:
+        return False
+
+
+ at directory.register
+class UV6R(baofeng_common.BaofengCommonHT):
+    """Baofeng UV-6R"""
+    VENDOR = "Baofeng"
+    MODEL = "UV-6R"
+
+    _fileid = [UV6R_fp1, ]
+
+    _magic = [MSTRING_UV6R, ]
+    _magic_response_length = 8
+    _fw_ver_start = 0x1FF0
+    _recv_block_size = 0x40
+    _mem_size = 0x2000
+    _ack_block = False
+
+    _ranges = [(0x0000, 0x1800),
+               (0x1F40, 0x1F50),
+               (0x1FC0, 0x1FD0),
+               (0x1FE0, 0x1FF0)]
+    _send_block_size = 0x10
+
+    MODES = ["FM", "NFM"]
+    VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
+        "!@#$%^&*()+-=[]:\";'<>?,./"
+    LENGTH_NAME = 6
+    SKIP_VALUES = ["", "S"]
+    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
+    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
+                    chirp_common.PowerLevel("Low", watts=1.00)]
+    VALID_BANDS = [(136000000, 174000000),
+                   (400000000, 520000000)]
+    PTTID_LIST = LIST_PTTID
+    SCODE_LIST = LIST_SCODE
+
+
+    MEM_FORMAT = """
+    #seekto 0x0000;
+    struct {
+      lbcd rxfreq[4];
+      lbcd txfreq[4];
+      ul16 rxtone;
+      ul16 txtone;
+      u8 unknown0:4,
+         scode:4;
+      u8 unknown1;
+      u8 unknown2:7,
+         lowpower:1;
+      u8 unknown3:1,
+         wide:1,
+         unknown4:2,
+         bcl:1,
+         scan:1,
+         pttid:2;
+    } memory[128];
+
+    #seekto 0x0B00;
+    struct {
+      u8 code[5];
+      u8 unused[11];
+    } pttid[15];
+
+    #seekto 0x0CAA;
+    struct {
+      u8 code[5];
+      u8 unused:6,
+         aniid:2;
+      u8 unknown[2];
+      u8 dtmfon;
+      u8 dtmfoff;
+    } ani;
+
+    #seekto 0x0E20;
+    struct {
+      u8 unused00:4,
+     squelch:4;
+      u8 unused01:5,
+         step:3;
+      u8 unknown00;
+      u8 unused02:5,
+         save:3;
+      u8 unused03:4,
+         vox:4;
+      u8 unknown01;
+      u8 unused04:4,
+         abr:4;
+      u8 unused05:7,
+         tdr:1;
+      u8 unused06:7,
+         beep:1;
+      u8 unused07:2,
+         timeout:6;
+      u8 unused08:6,
+         tcall:2;
+      u8 unknown02[3];
+      u8 unused09:6,
+         voice:2;
+      u8 unknown03;
+      u8 unused10:6,
+         dtmfst:2;
+      u8 unknown04;
+      u8 unused11:6,
+         screv:2;
+      u8 unused12:6,
+         pttid:2;
+      u8 unused13:2,
+         pttlt:6;
+      u8 unused14:6,
+         mdfa:2;
+      u8 unused15:6,
+         mdfb:2;
+      u8 unknown05;
+      u8 unused16:7,
+         autolk:1;
+      u8 unknown06[4];
+      u8 unused17:6,
+         wtled:2;
+      u8 unused18:6,
+         rxled:2;
+      u8 unused19:6,
+         txled:2;
+      u8 unused20:6,
+         almod:2;
+      u8 unknown07[2];
+      u8 unused22:7,
+         ste:1;
+      u8 unused23:4,
+         rpste:4;
+      u8 unused24:4,
+         rptrl:4;
+      u8 unused25:7,
+         ponmsg:1;
+      u8 unused26:7,
+         roger:1;
+      u8 unused27:7,
+         reset:1;
+      u8 unknown08;
+      u8 displayab:1,
+         unknown09:2,
+         fmradio:1,
+         alarm:1,
+         unknown10:1,
+         reset:1,
+         menu:1;
+      u8 unknown11;
+      u8 unused29:7,
+         workmode:1;
+      u8 unused30:7,
+         keylock:1;
+      u8 cht;
+    } settings;
+
+    #seekto 0x0E76;
+    struct {
+      u8 unused0:1,
+         mrcha:7;
+      u8 unused1:1,
+         mrchb:7;
+    } wmchannel;
+
+    struct vfo {
+      u8 unknown0[8];
+      u8 freq[8];
+      u8 offset[6];
+      ul16 rxtone;
+      ul16 txtone;
+      u8 unused0:7,
+         band:1;
+      u8 unknown3;
+      u8 unknown4:2,
+         sftd:2,
+         scode:4;
+      u8 unknown5;
+      u8 unknown6:1,
+         step:3,
+         unknown7:4;
+      u8 txpower:1,
+         widenarr:1,
+         unknown8:6;
+    };
+
+    #seekto 0x0F00;
+    struct {
+      struct vfo a;
+      struct vfo b;
+    } vfo;
+
+    #seekto 0x0F4E;
+    u16 fm_presets;
+
+    #seekto 0x1000;
+    struct {
+      char name[6];
+      u8 unknown[10];
+    } names[128];
+
+    #seekto 0x1F40;
+    struct {
+      u8 sql0;
+      u8 sql1;
+      u8 sql2;
+      u8 sql3;
+      u8 sql4;
+      u8 sql5;
+      u8 sql6;
+      u8 sql7;
+      u8 sql8;
+      u8 sql9;
+    } squelch;
+
+    struct limit {
+      u8 enable;
+      bbcd lower[2];
+      bbcd upper[2];
+    };
+
+    #seekto 0x1FC0;
+    struct {
+      struct limit vhf;
+      struct limit uhf;
+    } limits;
+
+    #seekto 0x1FD0;
+    struct {
+      char line1[8];
+      char line2[8];
+    } sixpoweron_msg;
+
+    #seekto 0x1FE0;
+    struct {
+      char line1[7];
+      char line2[7];
+    } poweron_msg;
+
+    #seekto 0x1FF0;
+    struct {
+      char line1[8];
+      char line2[8];
+    } firmware_msg;
+
+    """
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.experimental = \
+            ('The BTech UV-6R driver is a beta version.\n'
+             '\n'
+             'Please save an unedited copy of your first successful\n'
+             'download to a CHIRP Radio Images(*.img) file.'
+             )
+        rp.pre_download = _(dedent("""\
+            Follow these instructions to download your info:
+
+            1 - Turn off your radio
+            2 - Connect your interface cable
+            3 - Turn on your radio
+            4 - Do the download of your radio data
+            """))
+        rp.pre_upload = _(dedent("""\
+            Follow this instructions to upload your info:
+
+            1 - Turn off your radio
+            2 - Connect your interface cable
+            3 - Turn on your radio
+            4 - Do the upload of your radio data
+            """))
+        return rp
+
+    def process_mmap(self):
+        """Process the mem map into the mem object"""
+        self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap)
+
+    def get_settings(self):
+        """Translate the bit in the mem_struct into settings in the UI"""
+        _mem = self._memobj
+        basic = RadioSettingGroup("basic", "Basic Settings")
+        advanced = RadioSettingGroup("advanced", "Advanced Settings")
+        other = RadioSettingGroup("other", "Other Settings")
+        work = RadioSettingGroup("work", "Work Mode Settings")
+        fm_preset = RadioSettingGroup("fm_preset", "FM Preset")
+        dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings")
+        service = RadioSettingGroup("service", "Service Settings")
+        top = RadioSettings(basic, advanced, other, work, fm_preset, dtmfe,
+                            service)
+
+        # Basic settings
+        if _mem.settings.squelch > 0x09:
+            val = 0x00
+        else:
+            val = _mem.settings.squelch
+        rs = RadioSetting("settings.squelch", "Squelch",
+                          RadioSettingValueList(
+                              LIST_OFF1TO9, LIST_OFF1TO9[val]))
+        basic.append(rs)
+
+        if _mem.settings.save > 0x04:
+            val = 0x00
+        else:
+            val = _mem.settings.save
+        rs = RadioSetting("settings.save", "Battery Saver",
+                          RadioSettingValueList(
+                              LIST_SAVE, LIST_SAVE[val]))
+        basic.append(rs)
+
+        if _mem.settings.vox > 0x0A:
+            val = 0x00
+        else:
+            val = _mem.settings.vox
+        rs = RadioSetting("settings.vox", "Vox",
+                          RadioSettingValueList(
+                              LIST_OFF1TO10, LIST_OFF1TO10[val]))
+        basic.append(rs)
+
+        if _mem.settings.abr > 0x0A:
+            val = 0x00
+        else:
+            val = _mem.settings.abr
+        rs = RadioSetting("settings.abr", "Backlight Timeout",
+                          RadioSettingValueList(
+                              LIST_OFF1TO10, LIST_OFF1TO10[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.tdr", "Dual Watch",
+                          RadioSettingValueBoolean(_mem.settings.tdr))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.beep", "Beep",
+                           RadioSettingValueBoolean(_mem.settings.beep))
+        basic.append(rs)
+
+        if _mem.settings.timeout > 0x27:
+            val = 0x03
+        else:
+            val = _mem.settings.timeout
+        rs = RadioSetting("settings.timeout", "Timeout Timer",
+                          RadioSettingValueList(
+                              LIST_TIMEOUT, LIST_TIMEOUT[val]))
+        basic.append(rs)
+
+        if _mem.settings.voice > 0x02:
+            val = 0x01
+        else:
+            val = _mem.settings.voice
+        rs = RadioSetting("settings.voice", "Voice Prompt",
+                          RadioSettingValueList(
+                              LIST_VOICE, LIST_VOICE[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.dtmfst", "DTMF Sidetone",
+                          RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
+                              _mem.settings.dtmfst]))
+        basic.append(rs)
+
+        if _mem.settings.screv > 0x02:
+            val = 0x01
+        else:
+            val = _mem.settings.screv
+        rs = RadioSetting("settings.screv", "Scan Resume",
+                          RadioSettingValueList(
+                              LIST_RESUME, LIST_RESUME[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.pttid", "When to send PTT ID",
+                          RadioSettingValueList(LIST_PTTID, LIST_PTTID[
+                              _mem.settings.pttid]))
+        basic.append(rs)
+
+        if _mem.settings.pttlt > 0x1E:
+            val = 0x05
+        else:
+            val = _mem.settings.pttlt
+        rs = RadioSetting("pttlt", "PTT ID Delay",
+                          RadioSettingValueInteger(0, 50, val))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.mdfa", "Display Mode (A)",
+                          RadioSettingValueList(LIST_MODE, LIST_MODE[
+                              _mem.settings.mdfa]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.mdfb", "Display Mode (B)",
+                          RadioSettingValueList(LIST_MODE, LIST_MODE[
+                              _mem.settings.mdfb]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.autolk", "Auto Lock Keypad",
+                          RadioSettingValueBoolean(_mem.settings.autolk))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.wtled", "Standby LED Color",
+                          RadioSettingValueList(
+                              LIST_COLOR, LIST_COLOR[_mem.settings.wtled]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.rxled", "RX LED Color",
+                          RadioSettingValueList(
+                              LIST_COLOR, LIST_COLOR[_mem.settings.rxled]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.txled", "TX LED Color",
+                          RadioSettingValueList(
+                              LIST_COLOR, LIST_COLOR[_mem.settings.txled]))
+        basic.append(rs)
+
+        if _mem.settings.almod > 0x02:
+            val = 0x00
+        else:
+            val = _mem.settings.almod
+        rs = RadioSetting("settings.almod", "Alarm Mode",
+                          RadioSettingValueList(
+                              LIST_ALMOD, LIST_ALMOD[val]))
+        basic.append(rs)
+
+        if _mem.settings.tcall > 0x05:
+            val = 0x00
+        else:
+            val = _mem.settings.tcall
+        rs = RadioSetting("settings.tcall", "Tone Burst Frequency",
+                          RadioSettingValueList(
+                              LIST_TCALL, LIST_TCALL[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.ste", "Squelch Tail Eliminate (HT to HT)",
+                          RadioSettingValueBoolean(_mem.settings.ste))
+        basic.append(rs)
+
+        if _mem.settings.rpste > 0x0A:
+            val = 0x00
+        else:
+            val = _mem.settings.rpste
+        rs = RadioSetting("settings.rpste",
+                          "Squelch Tail Eliminate (repeater)",
+                              RadioSettingValueList(
+                              LIST_RPSTE, LIST_RPSTE[val]))
+        basic.append(rs)
+
+        if _mem.settings.rptrl > 0x0A:
+            val = 0x00
+        else:
+            val = _mem.settings.rptrl
+        rs = RadioSetting("settings.rptrl", "STE Repeater Delay",
+                          RadioSettingValueList(
+                              LIST_STEDELAY, LIST_STEDELAY[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.ponmsg", "Power-On Message",
+                          RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
+                              _mem.settings.ponmsg]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.roger", "Roger Beep",
+                          RadioSettingValueBoolean(_mem.settings.roger))
+        basic.append(rs)
+
+        # Advanced settings
+        rs = RadioSetting("settings.reset", "RESET Menu",
+                          RadioSettingValueBoolean(_mem.settings.reset))
+        advanced.append(rs)
+
+        rs = RadioSetting("settings.menu", "All Menus",
+                          RadioSettingValueBoolean(_mem.settings.menu))
+        advanced.append(rs)
+
+        rs = RadioSetting("settings.fmradio", "Broadcast FM Radio",
+                          RadioSettingValueBoolean(_mem.settings.fmradio))
+        advanced.append(rs)
+
+        rs = RadioSetting("settings.alarm", "Alarm Sound",
+                          RadioSettingValueBoolean(_mem.settings.alarm))
+        advanced.append(rs)
+
+        # Other settings
+        def _filter(name):
+            filtered = ""
+            for char in str(name):
+                if char in chirp_common.CHARSET_ASCII:
+                    filtered += char
+                else:
+                    filtered += " "
+            return filtered
+
+        _msg = _mem.firmware_msg
+        val = RadioSettingValueString(0, 8, _filter(_msg.line1))
+        val.set_mutable(False)
+        rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
+        other.append(rs)
+
+        val = RadioSettingValueString(0, 8, _filter(_msg.line2))
+        val.set_mutable(False)
+        rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
+        other.append(rs)
+
+        _msg = _mem.sixpoweron_msg
+        val = RadioSettingValueString(0, 8, _filter(_msg.line1))
+        val.set_mutable(False)
+        rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", val)
+        other.append(rs)
+        val = RadioSettingValueString(0, 8, _filter(_msg.line2))
+        val.set_mutable(False)
+        rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", val)
+        other.append(rs)
+
+        _msg = _mem.poweron_msg
+        rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
+                          RadioSettingValueString(
+                              0, 7, _filter(_msg.line1)))
+        other.append(rs)
+        rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
+                          RadioSettingValueString(
+                              0, 7, _filter(_msg.line2)))
+        other.append(rs)
+
+        lower = 136
+        upper = 174
+        rs = RadioSetting("limits.vhf.lower", "VHF Lower Limit (MHz)",
+                          RadioSettingValueInteger(
+                              lower, upper, _mem.limits.vhf.lower))
+        other.append(rs)
+
+        rs = RadioSetting("limits.vhf.upper", "VHF Upper Limit (MHz)",
+                          RadioSettingValueInteger(
+                              lower, upper, _mem.limits.vhf.upper))
+        other.append(rs)
+
+        lower = 400
+        upper = 520
+        rs = RadioSetting("limits.uhf.lower", "UHF Lower Limit (MHz)",
+                          RadioSettingValueInteger(
+                              lower, upper, _mem.limits.uhf.lower))
+        other.append(rs)
+
+        rs = RadioSetting("limits.uhf.upper", "UHF Upper Limit (MHz)",
+                          RadioSettingValueInteger(
+                              lower, upper, _mem.limits.uhf.upper))
+        other.append(rs)
+
+        # Work mode settings
+        rs = RadioSetting("settings.displayab", "Display",
+                          RadioSettingValueList(
+                              LIST_AB, LIST_AB[_mem.settings.displayab]))
+        work.append(rs)
+
+        rs = RadioSetting("settings.workmode", "VFO/MR Mode",
+                          RadioSettingValueList(
+                              LIST_WORKMODE,
+                              LIST_WORKMODE[_mem.settings.workmode]))
+        work.append(rs)
+
+        rs = RadioSetting("settings.keylock", "Keypad Lock",
+                          RadioSettingValueBoolean(_mem.settings.keylock))
+        work.append(rs)
+
+        rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
+                          RadioSettingValueInteger(0, 127,
+                                                      _mem.wmchannel.mrcha))
+        work.append(rs)
+
+        rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
+                          RadioSettingValueInteger(0, 127,
+                                                      _mem.wmchannel.mrchb))
+        work.append(rs)
+
+        def convert_bytes_to_freq(bytes):
+            real_freq = 0
+            for byte in bytes:
+                real_freq = (real_freq * 10) + byte
+            return chirp_common.format_freq(real_freq * 10)
+
+        def my_validate(value):
+            _vhf_lower = int(_mem.limits.vhf.lower)
+            _vhf_upper = int(_mem.limits.vhf.upper)
+            _uhf_lower = int(_mem.limits.uhf.lower)
+            _uhf_upper = int(_mem.limits.uhf.upper)
+            value = chirp_common.parse_freq(value)
+            msg = ("Can't be less than %i.0000")
+            if value > 99000000 and value < _vhf_lower * 1000000:
+                raise InvalidValueError(msg % _vhf_lower)
+            msg = ("Can't be between %i.9975-%i.0000")
+            if _vhf_upper * 1000000 <= value and value < _uhf_lower * 1000000:
+                raise InvalidValueError(msg % (_vhf_upper - 1, _uhf_lower))
+            msg = ("Can't be greater than %i.9975")
+            if value > 99000000 and value >= _uhf_upper * 1000000:
+                raise InvalidValueError(msg % (_uhf_upper - 1))
+            return chirp_common.format_freq(value)
+
+        def apply_freq(setting, obj):
+            value = chirp_common.parse_freq(str(setting.value)) / 10
+            for i in range(7, -1, -1):
+                obj.freq[i] = value % 10
+                value /= 10
+
+        val1a = RadioSettingValueString(0, 10,
+                                        convert_bytes_to_freq(_mem.vfo.a.freq))
+        val1a.set_validate_callback(my_validate)
+        rs = RadioSetting("vfo.a.freq", "VFO A Frequency", val1a)
+        rs.set_apply_callback(apply_freq, _mem.vfo.a)
+        work.append(rs)
+
+        val1b = RadioSettingValueString(0, 10,
+                                        convert_bytes_to_freq(_mem.vfo.b.freq))
+        val1b.set_validate_callback(my_validate)
+        rs = RadioSetting("vfo.b.freq", "VFO B Frequency", val1b)
+        rs.set_apply_callback(apply_freq, _mem.vfo.b)
+        work.append(rs)
+
+        rs = RadioSetting("vfo.a.sftd", "VFO A Shift",
+                          RadioSettingValueList(
+                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.a.sftd]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.b.sftd", "VFO B Shift",
+                          RadioSettingValueList(
+                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.b.sftd]))
+        work.append(rs)
+
+        def convert_bytes_to_offset(bytes):
+            real_offset = 0
+            for byte in bytes:
+                real_offset = (real_offset * 10) + byte
+            return chirp_common.format_freq(real_offset * 1000)
+
+        def apply_offset(setting, obj):
+            value = chirp_common.parse_freq(str(setting.value)) / 1000
+            for i in range(5, -1, -1):
+                obj.offset[i] = value % 10
+                value /= 10
+
+        val1a = RadioSettingValueString(
+                    0, 10, convert_bytes_to_offset(_mem.vfo.a.offset))
+        rs = RadioSetting("vfo.a.offset",
+                          "VFO A Offset", val1a)
+        rs.set_apply_callback(apply_offset, _mem.vfo.a)
+        work.append(rs)
+
+        val1b = RadioSettingValueString(
+                    0, 10, convert_bytes_to_offset(_mem.vfo.b.offset))
+        rs = RadioSetting("vfo.b.offset",
+                          "VFO B Offset", val1b)
+        rs.set_apply_callback(apply_offset, _mem.vfo.b)
+        work.append(rs)
+
+        rs = RadioSetting("vfo.a.txpower", "VFO A Power",
+                          RadioSettingValueList(
+                              LIST_TXPOWER,
+                              LIST_TXPOWER[_mem.vfo.a.txpower]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.b.txpower", "VFO B Power",
+                          RadioSettingValueList(
+                              LIST_TXPOWER,
+                              LIST_TXPOWER[_mem.vfo.b.txpower]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.a.widenarr", "VFO A Bandwidth",
+                          RadioSettingValueList(
+                              LIST_BANDWIDTH,
+                              LIST_BANDWIDTH[_mem.vfo.a.widenarr]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.b.widenarr", "VFO B Bandwidth",
+                          RadioSettingValueList(
+                              LIST_BANDWIDTH,
+                              LIST_BANDWIDTH[_mem.vfo.b.widenarr]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.a.scode", "VFO A S-CODE",
+                          RadioSettingValueList(
+                              LIST_SCODE,
+                              LIST_SCODE[_mem.vfo.a.scode]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.b.scode", "VFO B S-CODE",
+                          RadioSettingValueList(
+                              LIST_SCODE,
+                              LIST_SCODE[_mem.vfo.b.scode]))
+        work.append(rs)
+
+        rs = RadioSetting("vfo.a.step", "VFO A Tuning Step",
+                          RadioSettingValueList(
+                              LIST_STEP, LIST_STEP[_mem.vfo.a.step]))
+        work.append(rs)
+        rs = RadioSetting("vfo.b.step", "VFO B Tuning Step",
+                          RadioSettingValueList(
+                              LIST_STEP, LIST_STEP[_mem.vfo.b.step]))
+        work.append(rs)
+
+        # broadcast FM settings
+        _fm_presets = self._memobj.fm_presets
+        if _fm_presets <= 108.0 * 10 - 650:
+            preset = _fm_presets / 10.0 + 65
+        elif _fm_presets >= 65.0 * 10 and _fm_presets <= 108.0 * 10:
+            preset = _fm_presets / 10.0
+        else:
+            preset = 76.0
+        rs = RadioSetting("fm_presets", "FM Preset(MHz)",
+                          RadioSettingValueFloat(65, 108.0, preset, 0.1, 1))
+        fm_preset.append(rs)
+
+        # DTMF settings
+        def apply_code(setting, obj, length):
+            code = []
+            for j in range(0, length):
+                try:
+                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
+                except IndexError:
+                    code.append(0xFF)
+            obj.code = code
+
+        for i in range(0, 15):
+            _codeobj = self._memobj.pttid[i].code
+            _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+            val = RadioSettingValueString(0, 5, _code, False)
+            val.set_charset(DTMF_CHARS)
+            pttid = RadioSetting("pttid/%i.code" % i,
+                                 "Signal Code %i" % (i + 1), val)
+            pttid.set_apply_callback(apply_code, self._memobj.pttid[i], 5)
+            dtmfe.append(pttid)
+
+        if _mem.ani.dtmfon > 0xC3:
+            val = 0x03
+        else:
+            val = _mem.ani.dtmfon
+        rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
+                          RadioSettingValueList(LIST_DTMFSPEED,
+                                                LIST_DTMFSPEED[val]))
+        dtmfe.append(rs)
+
+        if _mem.ani.dtmfoff > 0xC3:
+            val = 0x03
+        else:
+            val = _mem.ani.dtmfoff
+        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
+                          RadioSettingValueList(LIST_DTMFSPEED,
+                                                LIST_DTMFSPEED[val]))
+        dtmfe.append(rs)
+
+        _codeobj = self._memobj.ani.code
+        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+        val = RadioSettingValueString(0, 5, _code, False)
+        val.set_charset(DTMF_CHARS)
+        rs = RadioSetting("ani.code", "ANI Code", val)
+        rs.set_apply_callback(apply_code, self._memobj.ani, 5)
+        dtmfe.append(rs)
+
+        rs = RadioSetting("ani.aniid", "When to send ANI ID",
+                          RadioSettingValueList(LIST_PTTID,
+                                                LIST_PTTID[_mem.ani.aniid]))
+        dtmfe.append(rs)
+
+        # Service settings
+        for index in range(0, 10):
+            key = "squelch.sql%i" % (index)
+            _obj = self._memobj.squelch
+            val = RadioSettingValueInteger(0, 123,
+                      getattr(_obj, "sql%i" % (index)))
+            if index == 0:
+                val.set_mutable(False)
+            name = "Squelch %i" % (index)
+            rs = RadioSetting(key, name, val)
+            service.append(rs)
+
+        return top
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        match_size = False
+        match_model = False
+
+        # testing the file data size
+        if len(filedata) == 0x2008:
+            match_size = True
+
+        # testing the firmware model fingerprint
+        match_model = model_match(cls, filedata)
+
+        if match_size and match_model:
+            return True
+        else:
+            return False
diff --git a/chirp/drivers/wouxun.py b/chirp/drivers/wouxun.py
index 23eb3a7..58536de 100644
--- a/chirp/drivers/wouxun.py
+++ b/chirp/drivers/wouxun.py
@@ -887,6 +887,7 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
         # New-style image (CHIRP 0.1.12)
         if len(filedata) == 8192 and \
                 filedata[0x60:0x64] != "2009" and \
+                filedata[0x170:0x173] != "LX-" and \
                 filedata[0x1f77:0x1f7d] == "\xff\xff\xff\xff\xff\xff" and \
                 filedata[0x0d70:0x0d80] == "\xff\xff\xff\xff\xff\xff\xff\xff" \
                                            "\xff\xff\xff\xff\xff\xff\xff\xff":
diff --git a/chirp/radioreference.py b/chirp/radioreference.py
index d52284e..515e96f 100644
--- a/chirp/radioreference.py
+++ b/chirp/radioreference.py
@@ -98,7 +98,10 @@ class RadioReferenceRadio(chirp_common.NetworkSourceRadio):
             for cat in agency.cats:
                 LOG.debug("Fetching category:", cat.cName)
                 for subcat in cat.subcats:
-                    LOG.debug("\t", subcat.scName)
+                    try:
+                        LOG.debug("\t", subcat.scName)
+                    except AttributeError:
+                        pass
                     result = self._client.service.getSubcatFreqs(subcat.scid,
                                                                  self._auth)
                     self._freqs += result
diff --git a/chirp/settings.py b/chirp/settings.py
index b03cd4f..dd690ad 100644
--- a/chirp/settings.py
+++ b/chirp/settings.py
@@ -28,6 +28,7 @@ class InternalError(Exception):
 
 class RadioSettingValue:
     """Base class for a single radio setting"""
+
     def __init__(self):
         self._current = None
         self._has_changed = False
@@ -69,6 +70,7 @@ class RadioSettingValue:
 
 class RadioSettingValueInteger(RadioSettingValue):
     """An integer setting"""
+
     def __init__(self, minval, maxval, current, step=1):
         RadioSettingValue.__init__(self)
         self._min = minval
@@ -101,6 +103,7 @@ class RadioSettingValueInteger(RadioSettingValue):
 
 class RadioSettingValueFloat(RadioSettingValue):
     """A floating-point setting"""
+
     def __init__(self, minval, maxval, current, resolution=0.001, precision=4):
         RadioSettingValue.__init__(self)
         self._min = minval
@@ -123,8 +126,8 @@ class RadioSettingValueFloat(RadioSettingValue):
             raise InvalidValueError("A floating point value is required")
         if value > self._max or value < self._min:
             raise InvalidValueError("Value %s not in range %s-%s" % (
-                    self.format(value),
-                    self.format(self._min), self.format(self._max)))
+                self.format(value),
+                self.format(self._min), self.format(self._max)))
 
         # FIXME: honor resolution
 
@@ -140,6 +143,7 @@ class RadioSettingValueFloat(RadioSettingValue):
 
 class RadioSettingValueBoolean(RadioSettingValue):
     """A boolean setting"""
+
     def __init__(self, current):
         RadioSettingValue.__init__(self)
         self.set_value(current)
@@ -157,6 +161,7 @@ class RadioSettingValueBoolean(RadioSettingValue):
 
 class RadioSettingValueList(RadioSettingValue):
     """A list-of-strings setting"""
+
     def __init__(self, options, current):
         RadioSettingValue.__init__(self)
         self._options = options
@@ -177,6 +182,7 @@ class RadioSettingValueList(RadioSettingValue):
 
 class RadioSettingValueString(RadioSettingValue):
     """A string setting"""
+
     def __init__(self, minlength, maxlength, current,
                  autopad=True, charset=chirp_common.CHARSET_ASCII):
         RadioSettingValue.__init__(self)
@@ -214,6 +220,7 @@ class RadioSettingValueMap(RadioSettingValueList):
     conversions not needed.
 
     """
+
     def __init__(self, map_entries, mem_val=None, user_option=None):
         """Create new map
 
@@ -242,7 +249,12 @@ class RadioSettingValueMap(RadioSettingValueList):
             index = self._mem_vals.index(mem_val)
             self.set_value(self._options[index])
         else:
-            raise InvalidValueError("%s is not valid for this setting" % value)
+            raise InvalidValueError(
+                "%s is not valid for this setting" % mem_val)
+
+    def get_mem_val(self):
+        """Get the mem val corresponding to the currently selected user option"""
+        return self._mem_vals[self._options.index(self.get_value())]
 
     def __trunc__(self):
         """Return memory value that matches current user option"""
@@ -264,6 +276,7 @@ def zero_indexed_seq_map(user_options):
 
 
 class RadioSettings(list):
+
     def __init__(self, *groups):
         list.__init__(self, groups)
 
@@ -274,6 +287,7 @@ class RadioSettings(list):
 
 class RadioSettingGroup(object):
     """A group of settings"""
+
     def _validate(self, element):
         # RadioSettingGroup can only contain RadioSettingGroup objects
         if not isinstance(element, RadioSettingGroup):
@@ -364,6 +378,7 @@ class RadioSettingGroup(object):
 
 class RadioSetting(RadioSettingGroup):
     """A single setting, which could be an array of items like a group"""
+
     def __init__(self, *args):
         super(RadioSetting, self).__init__(*args)
         self._apply_callback = None
diff --git a/chirp/ui/editorset.py b/chirp/ui/editorset.py
index 5c5d62e..2866dc9 100644
--- a/chirp/ui/editorset.py
+++ b/chirp/ui/editorset.py
@@ -70,6 +70,7 @@ class EditorSet(gtk.VBox):
                 members.connect("changed", lambda x: names.mappings_changed())
                 names.connect("changed", lambda x: members.mappings_changed())
                 names.connect("changed", self.editor_changed)
+            sub_index += 1
 
     def _make_device_editors(self, device, devrthread, index):
         if isinstance(device, chirp_common.IcomDstarSupport):
diff --git a/chirp/ui/mainapp.py b/chirp/ui/mainapp.py
index b3ca4ea..1d0156c 100644
--- a/chirp/ui/mainapp.py
+++ b/chirp/ui/mainapp.py
@@ -81,6 +81,7 @@ class ModifiedError(Exception):
 
 
 class ChirpMain(gtk.Window):
+
     def get_current_editorset(self):
         page = self.tabs.get_current_page()
         if page is not None:
@@ -137,7 +138,7 @@ class ChirpMain(gtk.Window):
     def ev_editor_selected(self, editorset, editortype):
         mappings = {
             "memedit": ["view", "edit"],
-            }
+        }
 
         for _editortype, actions in mappings.items():
             for _action in actions:
@@ -443,7 +444,7 @@ of file.
         ).replace('/', '_')
 
         types = [(label + " (*.%s)" % eset.radio.FILE_EXTENSION,
-                 eset.radio.FILE_EXTENSION)]
+                  eset.radio.FILE_EXTENSION)]
 
         if isinstance(eset.radio, vx7.VX7Radio):
             types += [(_("VX7 Commander") + " (*.vx7)", "vx7")]
@@ -517,7 +518,7 @@ of file.
 
             file_basename = os.path.basename(fname).replace("_", "__")
             action = gtk.Action(
-                action_name, "_%i. %s" % (i+1, file_basename),
+                action_name, "_%i. %s" % (i + 1, file_basename),
                 _("Open recent file {name}").format(name=fname), "")
             action.connect("activate", lambda a, f: self.do_open(f), fname)
             mid = self.menu_uim.new_merge_id()
@@ -644,7 +645,7 @@ of file.
             _("Don't show instructions for any radio again"))
         again.show()
         again.connect("toggled", lambda action:
-            self.clonemenu.set_active(not action.get_active()))
+                      self.clonemenu.set_active(not action.get_active()))
         d.vbox.pack_start(again, 0, 0, 0)
         h_button_box = d.vbox.get_children()[2]
         try:
@@ -657,7 +658,6 @@ of file.
         d.run()
         d.destroy()
 
-
     def do_download(self, port=None, rtype=None):
         d = clone.CloneSettingsDialog(parent=self)
         settings = d.run()
@@ -989,7 +989,7 @@ of file.
             "Latitude": (gtk.Entry(), lambda x: float(x.get_text())),
             "Longitude": (gtk.Entry(), lambda x: float(x.get_text())),
             "Range": (gtk.Entry(), lambda x: int(x.get_text())),
-            }
+        }
         for name in sorted(fields.keys()):
             value, fn = fields[name]
             d.add_field(name, value)
@@ -1371,7 +1371,8 @@ of file.
             devaction.set_visible(action.get_active())
 
     def do_toggle_clone_instructions(self, action):
-        CONF.set_bool("clone_instructions", not action.get_active(), "noconfirm")
+        CONF.set_bool("clone_instructions",
+                      not action.get_active(), "noconfirm")
 
     def do_change_language(self):
         langs = ["Auto", "English", "Polish", "Italian", "Dutch", "German",
@@ -1629,7 +1630,7 @@ of file.
             ('help', None, _('Help'), None, None, self.mh),
             ('about', gtk.STOCK_ABOUT, None, None, None, self.mh),
             ('gethelp', None, _("Get Help Online..."), None, None, self.mh),
-            ]
+        ]
 
         conf = config.get()
         re = not conf.get_bool("no_report")
@@ -1660,7 +1661,8 @@ of file.
 
         self.add_accel_group(self.menu_uim.get_accel_group())
 
-        self.clonemenu = self.menu_uim.get_widget("/MenuBar/help/clone_instructions")
+        self.clonemenu = self.menu_uim.get_widget(
+            "/MenuBar/help/clone_instructions")
 
         # Initialize
         self.do_toggle_developer(self.menu_ag.get_action("developer"))
@@ -1719,7 +1721,7 @@ of file.
 
         actions = [
             # ("action_name", "key", function)
-            ]
+        ]
 
         for name, key, fn in actions:
             a = gtk.Action(name, name, name, "")
diff --git a/stock_configs/EU LPD and PMR Channels.csv b/stock_configs/EU LPD and PMR Channels.csv
index 57eab2e..19d9e14 100644
--- a/stock_configs/EU LPD and PMR Channels.csv	
+++ b/stock_configs/EU LPD and PMR Channels.csv	
@@ -68,11 +68,19 @@ Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPola
 67,LPD 67,434.725000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
 68,LPD 68,434.750000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
 69,LPD 69,434.775000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
-71,PMR 1,446.006250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
-72,PMR 2,446.018750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
-73,PMR 3,446.031250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
-74,PMR 4,446.043750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
-75,PMR 5,446.056250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
-76,PMR 6,446.068750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
-77,PMR 7,446.081250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
-78,PMR 8,446.093750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+71,PMR 01,446.006250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+72,PMR 02,446.018750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+73,PMR 03,446.031250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+74,PMR 04,446.043750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+75,PMR 05,446.056250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+76,PMR 06,446.068750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+77,PMR 07,446.081250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+78,PMR 08,446.093750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+81,PMR 09,446.106250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+82,PMR 10,446.118750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+83,PMR 11,446.131250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+84,PMR 12,446.143750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+85,PMR 13,446.156250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+86,PMR 14,446.168750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+87,PMR 15,446.181250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+88,PMR 16,446.193750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
diff --git a/stock_configs/KDR444.csv b/stock_configs/KDR444.csv
new file mode 100644
index 0000000..7ab0e42
--- /dev/null
+++ b/stock_configs/KDR444.csv
@@ -0,0 +1,9 @@
+Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
+1,KDR444 1,444.600,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+2,KDR444 2,444.650,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+3,KDR444 3,444.800,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+4,KDR444 4,444.825,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+5,KDR444 5,444.850,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+6,KDR444 6,444.875,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+7,KDR444 7,444.925,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+8,KDR444 8,444.975,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
diff --git a/stock_configs/NOAA Weather Alert.csv b/stock_configs/NOAA Weather Alert.csv
index 41cc764..9e355b5 100644
--- a/stock_configs/NOAA Weather Alert.csv	
+++ b/stock_configs/NOAA Weather Alert.csv	
@@ -1,4 +1,4 @@
-Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL

+Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
 1,WX1PA7,162.550000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
 2,WX2PA1,162.400000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
 3,WX3PA4,162.475000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-hamradio/chirp.git



More information about the pkg-hamradio-commits mailing list