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

Iain R. Learmonth irl at moszumanska.debian.org
Sat Jul 15 23:46:05 UTC 2017


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

irl pushed a commit to branch master
in repository chirp.

commit 13e802fb673ce97dd3cca5b5af67fff0ccebd75e
Author: Iain R. Learmonth <irl at fsfe.org>
Date:   Sun Jul 16 00:34:18 2017 +0100

    New upstream version 20170714
---
 PKG-INFO                                |    2 +-
 chirp/__init__.py                       |    2 +-
 chirp/bandplan_na.py                    |   20 +-
 chirp/drivers/alinco.py                 |   41 +-
 chirp/drivers/baofeng_wp970i.py         |    5 +
 chirp/drivers/btech.py                  | 5498 ++++++++++++++++++-------------
 chirp/drivers/ft1d.py                   |  380 ++-
 chirp/drivers/ft2d.py                   |  128 +
 chirp/drivers/ft857.py                  |    4 +-
 chirp/drivers/ftm3200d.py               |  201 ++
 chirp/drivers/generic_xml.py            |    5 +-
 chirp/drivers/ic2300.py                 |  385 +++
 chirp/drivers/icf.py                    |    3 +
 chirp/drivers/icp7.py                   |  243 ++
 chirp/drivers/icx8x.py                  |    3 +-
 chirp/drivers/id880.py                  |   40 +-
 chirp/drivers/kguv8d.py                 |   53 +-
 chirp/drivers/kyd.py                    |   49 +-
 chirp/drivers/kyd_IP620.py              |   55 +-
 chirp/drivers/lt725uv.py                |    2 +-
 chirp/drivers/{kyd.py => radtel_t18.py} |  425 ++-
 chirp/drivers/retevis_rt21.py           |   49 +-
 chirp/drivers/retevis_rt22.py           |   54 +-
 chirp/drivers/retevis_rt23.py           |  867 +++++
 chirp/drivers/rh5r_v2.py                |  290 ++
 chirp/drivers/tdxone_tdq8a.py           | 1154 +++++++
 chirp/drivers/th9000.py                 |   16 +-
 chirp/drivers/th_uv3r.py                |    2 +-
 chirp/drivers/thd72.py                  |   47 +-
 chirp/drivers/tk270.py                  |   46 +-
 chirp/drivers/tk760.py                  |   46 +-
 chirp/drivers/tk760g.py                 |  167 +-
 chirp/drivers/tk8102.py                 |   49 +-
 chirp/drivers/uv5r.py                   |  169 +-
 chirp/drivers/uvb5.py                   |   24 +-
 chirp/drivers/vx3.py                    |   21 +-
 chirp/drivers/vx5.py                    |   31 +-
 chirp/drivers/vx7.py                    |    6 +-
 chirp/drivers/vx8.py                    |  498 +--
 chirp/drivers/wouxun.py                 |   49 +-
 chirp/platform.py                       |   17 +
 chirp/ui/mainapp.py                     |   35 +-
 chirp/ui/memedit.py                     |    8 +-
 chirp/ui/radiobrowser.py                |    2 +-
 chirp/ui/settingsedit.py                |    3 +-
 chirpw                                  |    5 +-
 46 files changed, 8028 insertions(+), 3171 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index 95f827c..215ab77 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.0
 Name: chirp
-Version: daily-20170124
+Version: daily-20170714
 Summary: UNKNOWN
 Home-page: UNKNOWN
 Author: UNKNOWN
diff --git a/chirp/__init__.py b/chirp/__init__.py
index 6f4f7bd..1e08ef5 100644
--- a/chirp/__init__.py
+++ b/chirp/__init__.py
@@ -17,7 +17,7 @@ import os
 import sys
 from glob import glob
 
-CHIRP_VERSION="daily-20170124"
+CHIRP_VERSION="daily-20170714"
 
 module_dir = os.path.dirname(sys.modules["chirp"].__file__)
 __all__ = []
diff --git a/chirp/bandplan_na.py b/chirp/bandplan_na.py
index 50bbb27..0ddc781 100644
--- a/chirp/bandplan_na.py
+++ b/chirp/bandplan_na.py
@@ -82,9 +82,9 @@ BANDS_10M = (
   bandplan.Band((29000000, 29200000), "AM", mode="AM"),
   bandplan.Band((29300000, 29510000), "Satellite Downlinks"),
   bandplan.Band((29520000, 29590000), "Repeater Inputs",
-                step_khz=10, mode="NFM"),
+                step_khz=10, mode="FM"),
   bandplan.Band((29610000, 29700000), "Repeater Outputs",
-                step_khz=10, mode="NFM", input_offset=-890000),
+                step_khz=10, mode="FM", input_offset=-890000),
 )
 
 BANDS_6M = (
@@ -103,10 +103,10 @@ BANDS_6M = (
                 input_offset=-500000),
   bandplan.Band((51620000, 51680000), "Digital repeater outputs",
                 input_offset=-500000),
-  bandplan.Band((52020000, 52040000), "FM simplex", mode="NFM"),
+  bandplan.Band((52020000, 52040000), "FM simplex", mode="FM"),
   bandplan.Band((52500000, 52980000), "Repeater outputs B",
-                input_offset=-500000, step_khz=20, mode="NFM"),
-  bandplan.Band((53000000, 53100000), "FM simplex", mode="NFM"),
+                input_offset=-500000, step_khz=20, mode="FM"),
+  bandplan.Band((53000000, 53100000), "FM simplex", mode="FM"),
   bandplan.Band((53100000, 53400000), "Radio remote control", step_khz=100),
   bandplan.Band((53500000, 53980000), "Repeater outputs C",
                 input_offset=-500000),
@@ -125,13 +125,13 @@ BANDS_2M = (
                 mode="USB"),
   bandplan.Band((144275000, 144300000), "Propagation beacons", mode="CW"),
   bandplan.Band((144300000, 144500000), "OSCAR subband"),
-  bandplan.Band((144600000, 144900000), "FM repeater inputs", mode="NFM"),
+  bandplan.Band((144600000, 144900000), "FM repeater inputs", mode="FM"),
   bandplan.Band((144900000, 145100000), "Weak signal and FM simplex",
-                mode="NFM", step_khz=10),
+                mode="FM", step_khz=10),
   bandplan.Band((145100000, 145200000), "Linear translator outputs",
                 input_offset=-600000),
   bandplan.Band((145200000, 145500000), "FM repeater outputs",
-                input_offset=-600000, mode="NFM",),
+                input_offset=-600000, mode="FM",),
   bandplan.Band((145500000, 145800000), "Misc and experimental modes"),
   bandplan.Band((145800000, 146000000), "OSCAR subband"),
   bandplan.Band((146400000, 146580000), "Simplex"),
@@ -149,12 +149,12 @@ BANDS_1_25M = (
   bandplan.Band((222050000, 222060000), "Propagation beacons"),
   bandplan.Band((222100000, 222150000), "Weak-signal CW & SSB"),
   bandplan.Band((222150000, 222250000), "Local coordinator's option"),
-  bandplan.Band((223400000, 223520000), "FM simplex", mode="NFM"),
+  bandplan.Band((223400000, 223520000), "FM simplex", mode="FM"),
   bandplan.Band((223520000, 223640000), "Digital, packet"),
   bandplan.Band((223640000, 223700000), "Links, control"),
   bandplan.Band((223710000, 223850000), "Local coordinator's option"),
   bandplan.Band((223850000, 224980000), "Repeater outputs only",
-                mode="NFM", input_offset=-1600000),
+                mode="FM", input_offset=-1600000),
 )
 
 BANDS_70CM = (
diff --git a/chirp/drivers/alinco.py b/chirp/drivers/alinco.py
index f1b4225..9ff6425 100644
--- a/chirp/drivers/alinco.py
+++ b/chirp/drivers/alinco.py
@@ -18,6 +18,8 @@ from chirp import chirp_common, bitwise, memmap, errors, directory, util
 from chirp.settings import RadioSettingGroup, RadioSetting
 from chirp.settings import RadioSettingValueBoolean, RadioSettings
 
+from textwrap import dedent
+
 import time
 import logging
 
@@ -612,6 +614,31 @@ class AlincoDJG7EG(AlincoStyleRadio):
     _memsize = 0x1a7c0
     _range = [(500000, 1300000000)]
 
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.pre_download = _(dedent("""\
+            1. Ensure your firmware version is 4_10 or higher
+            2. Turn radio off
+            3. Connect your interface cable
+            4. Turn radio on
+            5. Press and release PTT 3 times while holding MONI key
+            6. Supported baud rates: 57600 (default) and 19200
+               (rotate dial while holding MONI to change)
+            7. Click OK
+            """))
+        rp.pre_upload = _(dedent("""\
+            1. Ensure your firmware version is 4_10 or higher
+            2. Turn radio off
+            3. Connect your interface cable
+            4. Turn radio on
+            5. Press and release PTT 3 times while holding MONI key
+            6. Supported baud rates: 57600 (default) and 19200
+               (rotate dial while holding MONI to change)
+            7. Click OK
+            """))
+        return rp
+
     def get_features(self):
         rf = chirp_common.RadioFeatures()
         rf.has_dtcs_polarity = False
@@ -655,8 +682,18 @@ class AlincoDJG7EG(AlincoStyleRadio):
 
         return data
 
+    def _detect_baudrate_and_identify(self):
+        if self._identify():
+            return True
+        else:
+            # Apparenly Alinco support suggests to try again at a lower baud
+            # rate if their cable fails with the default rate. See #4355.
+            LOG.info("Could not talk to radio. Trying again at 19200 baud")
+            self.pipe.baudrate = 19200
+            return self._identify()
+
     def _download(self, limit):
-        self._identify()
+        self._detect_baudrate_and_identify()
 
         data = "\x00"*0x200
 
@@ -688,7 +725,7 @@ class AlincoDJG7EG(AlincoStyleRadio):
             raise Exception("Unexpected response from radio: %s" % resp)
 
     def _upload(self, limit):
-        if not self._identify():
+        if not self._detect_baudrate_and_identify():
             raise Exception("I can't talk to this model")
 
         for addr in range(0x200, self._memsize, 0x40):
diff --git a/chirp/drivers/baofeng_wp970i.py b/chirp/drivers/baofeng_wp970i.py
index 76c8d92..74b25ef 100644
--- a/chirp/drivers/baofeng_wp970i.py
+++ b/chirp/drivers/baofeng_wp970i.py
@@ -840,11 +840,16 @@ class WP970I(baofeng_common.BaofengCommonHT):
         else:
             return False
 
+class RH5XAlias(chirp_common.Alias):
+    VENDOR = "Rugged"
+    MODEL = "RH5X"
+
 @directory.register
 class BFA58(WP970I):
     """Baofeng BF-A58"""
     VENDOR = "Baofeng"
     MODEL = "BF-A58"
+    ALIASES = [RH5XAlias]
 
     _fileid = ["BFT515 ", "BFT517 "]
 
diff --git a/chirp/drivers/btech.py b/chirp/drivers/btech.py
index 0d62ebd..b5e1155 100644
--- a/chirp/drivers/btech.py
+++ b/chirp/drivers/btech.py
@@ -1,4 +1,4 @@
-# Copyright 2016:
+# Copyright 2016-2017:
 # * Pavel Milanes CO7WT, <pavelmc at gmail.com>
 # * Jim Unroe KC9HI, <rock.unroe at gmail.com>
 #
@@ -30,2693 +30,3645 @@ from chirp.settings import RadioSettingGroup, RadioSetting, \
     RadioSettingValueFloat, RadioSettings, InvalidValueError
 from textwrap import dedent
 
-MEM_FORMAT = """
-#seekto 0x0000;
-struct {
-  lbcd rxfreq[4];
-  lbcd txfreq[4];
-  ul16 rxtone;
-  ul16 txtone;
-  u8 unknown0:4,
-     scode:4;
-  u8 unknown1:2,
-     spmute:1,
-     unknown2:3,
-     optsig:2;
-  u8 unknown3:3,
-     scramble:1,
-     unknown4:3,
-     power:1;
-  u8 unknown5:1,
-     wide:1,
-     unknown6:2,
-     bcl:1,
-     add:1,
-     pttid:2;
-} memory[200];
+# A note about the memmory in these radios
+#
+# The real memory of these radios extends to 0x4000
+# On read the factory software only uses up to 0x3200
+# On write it just uploads the contents up to 0x3100
+#
+# The mem beyond 0x3200 holds the ID data
 
-#seekto 0x0E00;
-struct {
-  u8 tdr;
-  u8 unknown1;
-  u8 sql;
-  u8 unknown2[2];
-  u8 tot;
-  u8 apo;           // BTech radios use this as the Auto Power Off time
-                    // other radios use this as pre-Time Out Alert
-  u8 unknown3;
-  u8 abr;
-  u8 beep;
-  u8 unknown4[4];
-  u8 dtmfst;
-  u8 unknown5[2];
-  u8 prisc;
-  u8 prich;
-  u8 screv;
-  u8 unknown6[2];
-  u8 pttid;
-  u8 pttlt;
-  u8 unknown7;
-  u8 emctp;
-  u8 emcch;
-  u8 ringt;
-  u8 unknown8;
-  u8 camdf;
-  u8 cbmdf;
-  u8 sync;          // BTech radios use this as the display sync setting
-                    // other radios use this as the auto keypad lock setting
-  u8 ponmsg;
-  u8 wtled;
-  u8 rxled;
-  u8 txled;
-  u8 unknown9[5];
-  u8 anil;
-  u8 reps;
-  u8 repm;
-  u8 tdrab;
-  u8 ste;
-  u8 rpste;
-  u8 rptdl;
-  u8 mgain;
-  u8 dtmfg;
-} settings;
+MEM_SIZE = 0x4000
+BLOCK_SIZE = 0x40
+TX_BLOCK_SIZE = 0x10
+ACK_CMD = "\x06"
+MODES = ["FM", "NFM"]
+SKIP_VALUES = ["S", ""]
+TONES = chirp_common.TONES
+DTCS = sorted(chirp_common.DTCS_CODES + [645])
+
+# lists related to "extra" settings
+PTTID_LIST = ["OFF", "BOT", "EOT", "BOTH"]
+PTTIDCODE_LIST = ["%s" % x for x in range(1, 16)]
+OPTSIG_LIST = ["OFF", "DTMF", "2TONE", "5TONE"]
+SPMUTE_LIST = ["Tone/DTCS", "Tone/DTCS and Optsig", "Tone/DTCS or Optsig"]
+
+# lists
+LIST_AB = ["A", "B"]
+LIST_ABCD = LIST_AB + ["C", "D"]
+LIST_ANIL = ["3", "4", "5"]
+LIST_APO = ["Off"] + ["%s minutes" % x for x in range(30, 330, 30)]
+LIST_COLOR4 = ["Off", "Blue", "Orange", "Purple"]
+LIST_COLOR8 = ["Black", "White", "Red", "Blue", "Green", "Yellow", "Indego",
+               "Purple", "Gray"]
+LIST_DTMFST = ["OFF", "Keyboard", "ANI", "Keyboad + ANI"]
+LIST_EMCTP = ["TX alarm sound", "TX ANI", "Both"]
+LIST_EMCTPX = ["Off"] + LIST_EMCTP
+LIST_LANGUA = ["English", "Chinese"]
+LIST_MDF = ["Frequency", "Channel", "Name"]
+LIST_OFF1TO9 = ["Off"] + ["%s seconds" % x for x in range(1, 10)]
+LIST_OFF1TO10 = ["Off"] + ["%s seconds" % x for x in range(1, 11)]
+LIST_OFF1TO50 = ["Off"] + ["%s seconds" % x for x in range(1, 51)]
+LIST_PONMSG = ["Full", "Message", "Battery voltage"]
+LIST_REPM = ["Off", "Carrier", "CTCSS or DCS", "Tone", "DTMF"]
+LIST_REPS = ["1000 Hz", "1450 Hz", "1750 Hz", "2100Hz"]
+LIST_RPTDL = ["Off"] + ["%s ms" % x for x in range(1, 10)]
+LIST_SCMODE = ["Off", "PTT-SC", "MEM-SC", "PON-SC"]
+LIST_SHIFT = ["Off", "+", "-"]
+LIST_SKIPTX = ["Off", "Skip 1", "Skip 2"]
+STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0]
+LIST_STEP = [str(x) for x in STEPS]
+LIST_SYNC = ["Off", "AB", "CD", "AB+CD"]
+LIST_TMR = ["OFF", "M+A", "M+B", "M+C", "M+D", "M+A+B", "M+A+C", "M+A+D",
+            "M+B+C", "M+B+D", "M+C+D", "M+A+B+C", "M+A+B+D", "M+A+C+D",
+            "M+B+C+D", "A+B+C+D"]
+LIST_TOT = ["%s sec" % x for x in range(15, 615, 15)]
+LIST_TXDISP = ["Power", "Mic Volume"]
+LIST_TXP = ["High", "Low"]
+LIST_SCREV = ["TO (timeout)", "CO (carrier operated)", "SE (search)"]
+LIST_VFOMR = ["Frequency", "Channel"]
+LIST_WIDE = ["Wide", "Narrow"]
+
+# lists related to DTMF, 2TONE and 5TONE settings
+LIST_5TONE_STANDARDS = ["CCIR1", "CCIR2", "PCCIR", "ZVEI1", "ZVEI2", "ZVEI3",
+                        "PZVEI", "DZVEI", "PDZVEI", "EEA", "EIA", "EURO",
+                        "CCITT", "NATEL", "MODAT", "none"]
+LIST_5TONE_STANDARDS_without_none = ["CCIR1", "CCIR2", "PCCIR", "ZVEI1",
+                                     "ZVEI2", "ZVEI3",
+                                     "PZVEI", "DZVEI", "PDZVEI", "EEA", "EIA",
+                                     "EURO", "CCITT", "NATEL", "MODAT"]
+LIST_5TONE_STANDARD_PERIODS = ["20", "30", "40", "50", "60", "70", "80", "90",
+                               "100", "110", "120", "130", "140", "150", "160",
+                               "170", "180", "190", "200"]
+LIST_5TONE_DIGITS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A",
+                     "B", "C", "D", "E", "F"]
+LIST_5TONE_DELAY = ["%s ms" % x for x in range(0, 1010, 10)]
+LIST_5TONE_RESET = ["%s ms" % x for x in range(100, 8100, 100)]
+LIST_5TONE_RESET_COLOR = ["%s ms" % x for x in range(100, 20100, 100)]
+LIST_DTMF_SPEED = ["%s ms" % x for x in range(50, 2010, 10)]
+LIST_DTMF_DIGITS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B",
+                    "C", "D", "#", "*"]
+LIST_DTMF_VALUES = [0x0A, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+                    0x0D, 0x0E, 0x0F, 0x00, 0x0C, 0x0B ]
+LIST_DTMF_SPECIAL_DIGITS = [ "*", "#", "A", "B", "C", "D"]
+LIST_DTMF_SPECIAL_VALUES = [ 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00]
+LIST_DTMF_DELAY = ["%s ms" % x for x in range(100, 4100, 100)]
+CHARSET_DTMF_DIGITS = "0123456789AaBbCcDd#*"
+LIST_2TONE_DEC = ["A-B", "A-C", "A-D",
+                  "B-A", "B-C", "B-D",
+                  "C-A", "C-B", "C-D",
+                  "D-A", "D-B", "D-C"]
+LIST_2TONE_RESPONSE = ["None", "Alert", "Transpond", "Alert+Transpond"]
+
+# This is a general serial timeout for all serial read functions.
+# Practice has show that about 0.7 sec will be enough to cover all radios.
+STIMEOUT = 0.7
+
+# this var controls the verbosity in the debug and by default it's low (False)
+# make it True and you will to get a very verbose debug.log
+debug = False
+
+# valid chars on the LCD, Note that " " (space) is stored as "\xFF"
+VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
+    "`{|}!\"#$%&'()*+,-./:;<=>?@[]^_"
+
+
+##### ID strings #####################################################
+
+# BTECH UV2501 pre-production units
+UV2501pp_fp = "M2C294"
+# BTECH UV2501 pre-production units 2 + and 1st Gen radios
+UV2501pp2_fp = "M29204"
+# B-TECH UV-2501 second generation (2G) radios
+UV2501G2_fp = "BTG214"
+# B-TECH UV-2501 third generation (3G) radios
+UV2501G3_fp = "BTG324"
+
+# B-TECH UV-2501+220 pre-production units
+UV2501_220pp_fp = "M3C281"
+# extra block read for the 2501+220 pre-production units
+# the same for all of this radios so far
+UV2501_220pp_id = "      280528"
+# B-TECH UV-2501+220
+UV2501_220_fp = "M3G201"
+# new variant, let's call it Generation 2
+UV2501_220G2_fp = "BTG211"
+# B-TECH UV-2501+220 third generation (3G)
+UV2501_220G3_fp = "BTG311"
+
+# B-TECH UV-5001 pre-production units + 1st Gen radios
+UV5001pp_fp = "V19204"
+# B-TECH UV-5001 alpha units
+UV5001alpha_fp = "V28204"
+# B-TECH UV-5001 second generation (2G) radios
+UV5001G2_fp = "BTG214"
+# B-TECH UV-5001 second generation (2G2)
+UV5001G22_fp = "V2G204"
+# B-TECH UV-5001 third generation (3G)
+UV5001G3_fp = "BTG304"
+
+# B-TECH UV-25X2
+UV25X2_fp = "UC2012"
+
+# B-TECH UV-25X4
+UV25X4_fp = "UC4014"
+
+# B-TECH UV-50X2
+UV50X2_fp = "UC2M12"
+
+# special var to know when we found a BTECH Gen 3
+BTECH3 = [UV2501G3_fp, UV2501_220G3_fp, UV5001G3_fp]
+
+
+# WACCOM Mini-8900
+MINI8900_fp = "M28854"
+
+
+# QYT KT-UV980
+KTUV980_fp = "H28854"
+
+# QYT KT8900
+KT8900_fp = "M29154"
+# New generations KT8900
+KT8900_fp1 = "M2C234"
+KT8900_fp2 = "M2G1F4"
+KT8900_fp3 = "M2G2F4"
+KT8900_fp4 = "M2G304"
+KT8900_fp5 = "M2G314"
+# this radio has an extra ID
+KT8900_id = "303688"
+
+# KT8900R
+KT8900R_fp = "M3G1F4"
+# Second Generation
+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"
+
+# KT7900D (quad band)
+KT7900D_fp = "VC4004"
+
+# KT8900D (dual band)
+KT8900D_fp = "VC2002"
+
+# LUITON LT-588UV
+LT588UV_fp = "V2G1F4"
+# Added by rstrickoff gen 2 id
+LT588UV_fp1 = "V2G214"
+
+
+#### MAGICS
+# for the Waccom Mini-8900
+MSTRING_MINI8900 = "\x55\xA5\xB5\x45\x55\x45\x4d\x02"
+# for the B-TECH UV-2501+220 (including pre production ones)
+MSTRING_220 = "\x55\x20\x15\x12\x12\x01\x4d\x02"
+# for the QYT KT8900 & R
+MSTRING_KT8900 = "\x55\x20\x15\x09\x16\x45\x4D\x02"
+MSTRING_KT8900R = "\x55\x20\x15\x09\x25\x01\x4D\x02"
+# magic string for all other models
+MSTRING = "\x55\x20\x15\x09\x20\x45\x4d\x02"
+# for the QYT KT7900D & KT8900D
+MSTRING_KT8900D = "\x55\x20\x16\x08\x01\xFF\xDC\x02"
+# for the BTECH UV-25X2 and UV-50X2
+MSTRING_UV25X2 = "\x55\x20\x16\x12\x28\xFF\xDC\x02"
+# for the BTECH UV-25X4
+MSTRING_UV25X4 = "\x55\x20\x16\x11\x18\xFF\xDC\x02"
+
+
+def _clean_buffer(radio):
+    """Cleaning the read serial buffer, hard timeout to survive an infinite
+    data stream"""
+
+    # touching the serial timeout to optimize the flushing
+    # restored at the end to the default value
+    radio.pipe.timeout = 0.1
+    dump = "1"
+    datacount = 0
+
+    try:
+        while len(dump) > 0:
+            dump = radio.pipe.read(100)
+            datacount += len(dump)
+            # hard limit to survive a infinite serial data stream
+            # 5 times bigger than a normal rx block (69 bytes)
+            if datacount > 345:
+                seriale = "Please check your serial port selection."
+                raise errors.RadioError(seriale)
+
+        # restore the default serial timeout
+        radio.pipe.timeout = STIMEOUT
+
+    except Exception:
+        raise errors.RadioError("Unknown error cleaning the serial buffer")
+
+
+def _rawrecv(radio, amount):
+    """Raw read from the radio device, less intensive way"""
+
+    data = ""
+
+    try:
+        data = radio.pipe.read(amount)
+
+        # DEBUG
+        if debug is True:
+            LOG.debug("<== (%d) bytes:\n\n%s" %
+                      (len(data), util.hexprint(data)))
+
+        # fail if no data is received
+        if len(data) == 0:
+            raise errors.RadioError("No data received from radio")
+
+        # notice on the logs if short
+        if len(data) < amount:
+            LOG.warn("Short reading %d bytes from the %d requested." %
+                     (len(data), amount))
+
+    except:
+        raise errors.RadioError("Error reading data from radio")
+
+    return data
+
+
+def _send(radio, data):
+    """Send data to the radio device"""
+
+    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:
+            LOG.debug("==> (%d) bytes:\n\n%s" %
+                      (len(data), util.hexprint(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 = "\x06" + struct.pack(">BHB", ord(cmd), addr, length)
+    # add the data if set
+    if len(data) != 0:
+        frame += data
+
+    return frame
+
+
+def _recv(radio, addr):
+    """Get data from the radio all at once to lower syscalls load"""
+
+    # Get the full 69 bytes at a time to reduce load
+    # 1 byte ACK + 4 bytes header + 64 bytes of data (BLOCK_SIZE)
+
+    # get the whole block
+    block = _rawrecv(radio, BLOCK_SIZE + 5)
+
+    # basic check
+    if len(block) < (BLOCK_SIZE + 5):
+        raise errors.RadioError("Short read of the block 0x%04x" % addr)
+
+    # checking for the ack
+    if block[0] != ACK_CMD:
+        raise errors.RadioError("Bad ack from radio in block 0x%04x" % addr)
+
+    # header validation
+    c, a, l = struct.unpack(">BHB", block[1:5])
+    if a != addr or l != BLOCK_SIZE or c != ord("X"):
+        LOG.debug("Invalid header for block 0x%04x" % addr)
+        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
+        raise errors.RadioError("Invalid header for block 0x%04x:" % addr)
+
+    # return the data
+    return block[5:]
+
+
+def _start_clone_mode(radio, status):
+    """Put the radio in clone mode and get the ident string, 3 tries"""
+
+    # cleaning the serial buffer
+    _clean_buffer(radio)
+
+    # prep the data to show in the UI
+    status.cur = 0
+    status.msg = "Identifying the radio..."
+    status.max = 3
+    radio.status_fn(status)
+
+    try:
+        for a in range(0, status.max):
+            # Update the UI
+            status.cur = a + 1
+            radio.status_fn(status)
+
+            # send the magic word
+            _send(radio, radio._magic)
+
+            # Now you get a x06 of ACK if all goes well
+            ack = radio.pipe.read(1)
+
+            if ack == "\x06":
+                # DEBUG
+                LOG.info("Magic ACK received")
+                status.cur = status.max
+                radio.status_fn(status)
+
+                return True
 
-#seekto 0x0E80;
-struct {
-  u8 unknown1;
-  u8 vfomr;
-  u8 keylock;
-  u8 unknown2;
-  u8 unknown3:4,
-     vfomren:1,
-     unknown4:1,
-     reseten:1,
-     menuen:1;
-  u8 unknown5[11];
-  u8 dispab;
-  u8 mrcha;
-  u8 mrchb;
-  u8 menu;
-} settings2;
+        return False
 
-#seekto 0x0EC0;
-struct {
-  char line1[6];
-  char line2[6];
-} poweron_msg;
+    except errors.RadioError:
+        raise
+    except Exception, e:
+        raise errors.RadioError("Error sending Magic to radio:\n%s" % e)
 
-struct settings_vfo {
-  u8 freq[8];
-  u8 unknown1;
-  u8 offset[4];
-  u8 unknown2[3];
-  ul16 rxtone;
-  ul16 txtone;
-  u8 scode;
-  u8 spmute;
-  u8 optsig;
-  u8 scramble;
-  u8 wide;
-  u8 power;
-  u8 shiftd;
-  u8 step;
-  u8 unknown3[4];
-};
 
-#seekto 0x0F00;
-struct {
-  struct settings_vfo a;
-  struct settings_vfo b;
-} vfo;
+def _do_ident(radio, status, upload=False):
+    """Put the radio in PROGRAM mode & identify it"""
+    #  set the serial discipline
+    radio.pipe.baudrate = 9600
+    radio.pipe.parity = "N"
 
-#seekto 0x1000;
-struct {
-  char name[6];
-  u8 unknown1[10];
-} names[200];
+    # open the radio into program mode
+    if _start_clone_mode(radio, status) is False:
+        msg = "Radio did not enter clone mode"
+        # warning about old versions of QYT KT8900
+        if radio.MODEL == "KT8900":
+            msg += ". You may want to try it as a WACCOM MINI-8900, there is a"
+            msg += " known variant of this radios that is a clone of it."
+        raise errors.RadioError(msg)
 
-#seekto 0x2400;
-struct {
-  u8 period; // one out of LIST_5TONE_STANDARD_PERIODS
-  u8 group_tone;
-  u8 repeat_tone;
-  u8 unused[13];
-} _5tone_std_settings[15];
+    # Ok, get the ident string
+    ident = _rawrecv(radio, 49)
 
-#seekto 0x2500;
-struct {
-  u8 frame1[5];
-  u8 frame2[5];
-  u8 frame3[5];
-  u8 standard;   // one out of LIST_5TONE_STANDARDS
-} _5tone_codes[15];
+    # basic check for the ident
+    if len(ident) != 49:
+        raise errors.RadioError("Radio send a short ident block.")
 
-#seekto 0x25F0;
-struct {
-  u8 _5tone_delay1; // * 10ms
-  u8 _5tone_delay2; // * 10ms
-  u8 _5tone_delay3; // * 10ms
-  u8 _5tone_first_digit_ext_length;
-  u8 unknown1;
-  u8 unknown2;
-  u8 unknown3;
-  u8 unknown4;
-  u8 decode_standard;
-  u8 unknown5:5,
-     _5tone_decode_call_frame3:1,
-     _5tone_decode_call_frame2:1,
-     _5tone_decode_call_frame1:1;
-  u8 unknown6:5,
-     _5tone_decode_disp_frame3:1,
-     _5tone_decode_disp_frame2:1,
-     _5tone_decode_disp_frame1:1;
-  u8 decode_reset_time; // * 100 + 100ms
-} _5tone_settings;
+    # check if ident is OK
+    itis = False
+    for fp in radio._fileid:
+        if fp in ident:
+            # got it!
+            itis = True
+            # checking if we are dealing with a Gen 3 BTECH
+            if radio.VENDOR == "BTECH" and fp in BTECH3:
+                radio.btech3 = True
 
-#seekto 0x2900;
-struct {
-  u8 code[16]; // 0=x0A, A=0x0D, B=0x0E, C=0x0F, D=0x00, #=0x0C *=0x0B
-} dtmf_codes[15];
+            break
 
-#seekto 0x29F0;
-struct {
-  u8 dtmfspeed_on;  //list with 50..2000ms in steps of 10
-  u8 dtmfspeed_off; //list with 50..2000ms in steps of 10
-  u8 unknown0[14];
-  u8 inspection[16];
-  u8 monitor[16];
-  u8 alarmcode[16];
-  u8 stun[16];
-  u8 kill[16];
-  u8 revive[16];
-  u8 unknown1[16];
-  u8 unknown2[16];
-  u8 unknown3[16];
-  u8 unknown4[16];
-  u8 unknown5[16];
-  u8 unknown6[16];
-  u8 unknown7[16];
-  u8 masterid[16];
-  u8 viceid[16];
-  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; // * 100 + 100ms
-  u8 resettime;     // * 100 + 100ms
-} dtmf_settings;
+    if itis is False:
+        LOG.debug("Incorrect model ID, got this:\n\n" + util.hexprint(ident))
+        raise errors.RadioError("Radio identification failed.")
 
-#seekto 0x2D00;
-struct {
-  struct {
-    ul16 freq1;
-    u8 unused01[6];
-    ul16 freq2;
-    u8 unused02[6];
-  } _2tone_encode[15];
-  u8 duration_1st_tone; // *10ms
-  u8 duration_2nd_tone; // *10ms
-  u8 duration_gap;      // *10ms
-  u8 unused03[13];
-  struct {
-    struct {
-      u8 dec;      // one out of LIST_2TONE_DEC
-      u8 response; // one out of LIST_2TONE_RESPONSE
-      u8 alert;    // 1-16
-    } decs[4];
-    u8 unused04[4];
-  } _2tone_decode[15];
-  u8 unused05[16];
+    # some radios needs a extra read and check for a code on it, this ones
+    # has the check value in the _id2 var, others simply False
+    if radio._id2 is not False:
+        # lower the timeout here as this radios are reseting due to timeout
+        radio.pipe.timeout = 0.05
 
-  struct {
-    ul16 freqA;
-    ul16 freqB;
-    ul16 freqC;
-    ul16 freqD;
-    // unknown what those values mean, but they are
-    // derived from configured frequencies
-    ul16 derived_from_freqA; // 2304000/freqA
-    ul16 derived_from_freqB; // 2304000/freqB
-    ul16 derived_from_freqC; // 2304000/freqC
-    ul16 derived_from_freqD; // 2304000/freqD
-  }freqs[15];
-  u8 reset_time;  // * 100 + 100ms - 100-8000ms
-} _2tone;
+        # query & receive the extra ID
+        _send(radio, _make_frame("S", 0x3DF0, 16))
+        id2 = _rawrecv(radio, 21)
+
+        # WARNING !!!!!!
+        # different radios send a response with a different amount of data
+        # it seems that it's padded with \xff, \x20 and some times with \x00
+        # we just care about the first 16, our magic string is in there
+        if len(id2) < 16:
+            raise errors.RadioError("The extra ID is short, aborting.")
+
+        # ok, the correct string must be in the received data
+        if radio._id2 not in id2:
+            LOG.debug("Full *BAD* extra ID on the %s is: \n%s" %
+                      (radio.MODEL, util.hexprint(id2)))
+            raise errors.RadioError("The extra ID is wrong, aborting.")
+
+        # this radios need a extra request/answer here on the upload
+        # the amount of data received depends of the radio type
+        #
+        # also the first block of TX must no have the ACK at the beginning
+        # see _upload for this.
+        if upload is True:
+            # send an ACK
+            _send(radio, ACK_CMD)
+
+            # the amount of data depend on the radio, so far we have two radios
+            # reading two bytes with an ACK at the end and just ONE with just
+            # one byte (QYT KT8900)
+            # the JT-6188 appears a clone of the last, but reads TWO bytes.
+            #
+            # we will read two bytes with a custom timeout to not penalize the
+            # users for this.
+            #
+            # we just check for a response and last byte being a ACK, that is
+            # the common stone for all radios (3 so far)
+            ack = _rawrecv(radio, 2)
+
+            # checking
+            if len(ack) == 0 or ack[-1:] != ACK_CMD:
+                raise errors.RadioError("Radio didn't ACK the upload")
+
+            # restore the default serial timeout
+            radio.pipe.timeout = STIMEOUT
+
+    # DEBUG
+    LOG.info("Positive ident, this is a %s %s" % (radio.VENDOR, radio.MODEL))
+
+    return True
+
+
+def _download(radio):
+    """Get the memory map"""
 
-#seekto 0x3000;
-struct {
-  u8 freq[8];
-  char broadcast_station_name[6];
-  u8 unknown[2];
-} fm_radio_preset[16];
+    # UI progress
+    status = chirp_common.Status()
 
-#seekto 0x3C90;
-struct {
-  u8 vhf_low[3];
-  u8 vhf_high[3];
-  u8 uhf_low[3];
-  u8 uhf_high[3];
-} ranges;
+    # put radio in program mode and identify it
+    _do_ident(radio, status)
 
-// the UV-2501+220 & KT8900R has different zones for storing ranges
+    # the models that doesn't have the extra ID have to make a dummy read here
+    if radio._id2 is False:
+        _send(radio, _make_frame("S", 0, BLOCK_SIZE))
+        discard = _rawrecv(radio, BLOCK_SIZE + 5)
 
-#seekto 0x3CD0;
-struct {
-  u8 vhf_low[3];
-  u8 vhf_high[3];
-  u8 unknown1[4];
-  u8 unknown2[6];
-  u8 vhf2_low[3];
-  u8 vhf2_high[3];
-  u8 unknown3[4];
-  u8 unknown4[6];
-  u8 uhf_low[3];
-  u8 uhf_high[3];
-} ranges220;
+        if debug is True:
+            LOG.info("Dummy first block read done, got this:\n\n %s",
+                     util.hexprint(discard))
 
-#seekto 0x3F70;
-struct {
-  char fp[6];
-} fingerprint;
+    # reset the progress bar in the UI
+    status.max = MEM_SIZE / BLOCK_SIZE
+    status.msg = "Cloning from radio..."
+    status.cur = 0
+    radio.status_fn(status)
 
-"""
+    # cleaning the serial buffer
+    _clean_buffer(radio)
 
-# A note about the memmory in these radios
-#
-# The real memory of these radios extends to 0x4000
-# On read the factory software only uses up to 0x3200
-# On write it just uploads the contents up to 0x3100
-#
-# The mem beyond 0x3200 holds the ID data
+    data = ""
+    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
+        # sending the read request
+        _send(radio, _make_frame("S", addr, BLOCK_SIZE))
 
-MEM_SIZE = 0x4000
-BLOCK_SIZE = 0x40
-TX_BLOCK_SIZE = 0x10
-ACK_CMD = "\x06"
-MODES = ["FM", "NFM"]
-SKIP_VALUES = ["S", ""]
-TONES = chirp_common.TONES
-DTCS = sorted(chirp_common.DTCS_CODES + [645])
-NAME_LENGTH = 6
-PTTID_LIST = ["OFF", "BOT", "EOT", "BOTH"]
-PTTIDCODE_LIST = ["%s" % x for x in range(1, 16)]
-OPTSIG_LIST = ["OFF", "DTMF", "2TONE", "5TONE"]
-SPMUTE_LIST = ["Tone/DTCS", "Tone/DTCS and Optsig", "Tone/DTCS or Optsig"]
+        # read
+        d = _recv(radio, addr)
 
-LIST_TOT = ["%s sec" % x for x in range(15, 615, 15)]
-LIST_TOA = ["Off"] + ["%s seconds" % x for x in range(1, 11)]
-LIST_APO = ["Off"] + ["%s minutes" % x for x in range(30, 330, 30)]
-LIST_ABR = ["Off"] + ["%s seconds" % x for x in range(1, 51)]
-LIST_DTMFST = ["OFF", "Keyboard", "ANI", "Keyboad + ANI"]
-LIST_SCREV = ["TO (timeout)", "CO (carrier operated)", "SE (search)"]
-LIST_EMCTP = ["TX alarm sound", "TX ANI", "Both"]
-LIST_RINGT = ["Off"] + ["%s seconds" % x for x in range(1, 10)]
-LIST_MDF = ["Frequency", "Channel", "Name"]
-LIST_PONMSG = ["Full", "Message", "Battery voltage"]
-LIST_COLOR = ["Off", "Blue", "Orange", "Purple"]
-LIST_REPS = ["1000 Hz", "1450 Hz", "1750 Hz", "2100Hz"]
-LIST_REPM = ["Off", "Carrier", "CTCSS or DCS", "Tone", "DTMF"]
-LIST_RPTDL = ["Off"] + ["%s ms" % x for x in range(1, 10)]
-LIST_ANIL = ["3", "4", "5"]
-LIST_AB = ["A", "B"]
-LIST_VFOMR = ["Frequency", "Channel"]
-LIST_SHIFT = ["Off", "+", "-"]
-LIST_TXP = ["High", "Low"]
-LIST_WIDE = ["Wide", "Narrow"]
-STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0]
-LIST_STEP = [str(x) for x in STEPS]
-LIST_5TONE_STANDARDS = ["CCIR1", "CCIR2", "PCCIR", "ZVEI1", "ZVEI2", "ZVEI3",
-                        "PZVEI", "DZVEI", "PDZVEI", "EEA", "EIA", "EURO",
-                        "CCITT", "NATEL", "MODAT", "none"]
-LIST_5TONE_STANDARDS_without_none = ["CCIR1", "CCIR2", "PCCIR", "ZVEI1",
-                                     "ZVEI2", "ZVEI3",
-                                     "PZVEI", "DZVEI", "PDZVEI", "EEA", "EIA",
-                                     "EURO", "CCITT", "NATEL", "MODAT"]
-LIST_5TONE_STANDARD_PERIODS = ["20", "30", "40", "50", "60", "70", "80", "90",
-                               "100", "110", "120", "130", "140", "150", "160",
-                               "170", "180", "190", "200"]
-LIST_5TONE_DIGITS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A",
-                     "B", "C", "D", "E", "F"]
-LIST_5TONE_DELAY = ["%s ms" % x for x in range(0, 1010, 10)]
-LIST_5TONE_RESET = ["%s ms" % x for x in range(100, 8100, 100)]
-LIST_DTMF_SPEED = ["%s ms" % x for x in range(50, 2010, 10)]
-LIST_DTMF_DIGITS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B",
-                    "C", "D", "#", "*"]
-LIST_DTMF_VALUES = [0x0A, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
-                    0x0D, 0x0E, 0x0F, 0x00, 0x0C, 0x0B ]
-LIST_DTMF_SPECIAL_DIGITS = [ "*", "#", "A", "B", "C", "D"]
-LIST_DTMF_SPECIAL_VALUES = [ 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00]
-LIST_DTMF_DELAY = ["%s ms" % x for x in range(100, 4100, 100)]
-CHARSET_DTMF_DIGITS = "0123456789AaBbCcDd#*"
-LIST_2TONE_DEC = ["A-B", "A-C", "A-D",
-                  "B-A", "B-C", "B-D",
-                  "C-A", "C-B", "C-D",
-                  "D-A", "D-B", "D-C"]
-LIST_2TONE_RESPONSE = ["None", "Alert", "Transpond", "Alert+Transpond"]
+        # aggregate the data
+        data += d
 
-# This is a general serial timeout for all serial read functions.
-# Practice has show that about 0.7 sec will be enough to cover all radios.
-STIMEOUT = 0.7
+        # UI Update
+        status.cur = addr / BLOCK_SIZE
+        status.msg = "Cloning from radio..."
+        radio.status_fn(status)
 
-# this var controls the verbosity in the debug and by default it's low (False)
-# make it True and you will to get a very verbose debug.log
-debug = False
+    return data
 
-# Power Levels
-NORMAL_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=25),
-                       chirp_common.PowerLevel("Low", watts=10)]
-UV5001_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=50),
-                       chirp_common.PowerLevel("Low", watts=10)]
 
-# this must be defined globaly
-POWER_LEVELS = None
+def _upload(radio):
+    """Upload procedure"""
 
-# valid chars on the LCD, Note that " " (space) is stored as "\xFF"
-VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
-    "`{|}!\"#$%&'()*+,-./:;<=>?@[]^_"
+    # The UPLOAD mem is restricted to lower than 0x3100,
+    # so we will overide that here localy
+    MEM_SIZE = 0x3100
 
+    # UI progress
+    status = chirp_common.Status()
 
-##### ID strings #####################################################
+    # put radio in program mode and identify it
+    _do_ident(radio, status, True)
 
-# BTECH UV2501 pre-production units
-UV2501pp_fp = "M2C294"
-# BTECH UV2501 pre-production units 2 + and 1st Gen radios
-UV2501pp2_fp = "M29204"
-# B-TECH UV-2501 second generation (2G) radios
-UV2501G2_fp = "BTG214"
-# B-TECH UV-2501 third generation (3G) radios
-UV2501G3_fp = "BTG324"
+    # get the data to upload to radio
+    data = radio.get_mmap()
 
-# B-TECH UV-2501+220 pre-production units
-UV2501_220pp_fp = "M3C281"
-# extra block read for the 2501+220 pre-production units
-# the same for all of this radios so far
-UV2501_220pp_id = "      280528"
-# B-TECH UV-2501+220
-UV2501_220_fp = "M3G201"
-# new variant, let's call it Generation 2
-UV2501_220G2_fp = "BTG211"
-# B-TECH UV-2501+220 third generation (3G)
-UV2501_220G3_fp = "BTG311"
+    # Reset the UI progress
+    status.max = MEM_SIZE / TX_BLOCK_SIZE
+    status.cur = 0
+    status.msg = "Cloning to radio..."
+    radio.status_fn(status)
 
-# B-TECH UV-5001 pre-production units + 1st Gen radios
-UV5001pp_fp = "V19204"
-# B-TECH UV-5001 alpha units
-UV5001alpha_fp = "V28204"
-# B-TECH UV-5001 second generation (2G) radios
-UV5001G2_fp = "BTG214"
-# B-TECH UV-5001 second generation (2G2)
-UV5001G22_fp = "V2G204"
-# B-TECH UV-5001 third generation (3G)
-UV5001G3_fp = "BTG304"
+    # the radios that doesn't have the extra ID 'may' do a dummy write, I found
+    # that leveraging the bad ACK and NOT doing the dummy write is ok, as the
+    # dummy write is accepted (it actually writes to the mem!) by the radio.
 
-# special var to know when we found a BTECH Gen 3
-BTECH3 = [UV2501G3_fp, UV2501_220G3_fp, UV5001G3_fp]
+    # cleaning the serial buffer
+    _clean_buffer(radio)
 
+    # the fun start here
+    for addr in range(0, MEM_SIZE, TX_BLOCK_SIZE):
+        # getting the block of data to send
+        d = data[addr:addr + TX_BLOCK_SIZE]
 
-# WACCOM Mini-8900
-MINI8900_fp = "M28854"
+        # build the frame to send
+        frame = _make_frame("X", addr, TX_BLOCK_SIZE, d)
 
+        # first block must not send the ACK at the beginning for the
+        # ones that has the extra id, since this have to do a extra step
+        if addr == 0 and radio._id2 is not False:
+            frame = frame[1:]
 
-# QYT KT-UV980
-KTUV980_fp = "H28854"
+        # send the frame
+        _send(radio, frame)
 
-# QYT KT8900
-KT8900_fp = "M29154"
-# New generations KT8900
-KT8900_fp1 = "M2C234"
-KT8900_fp2 = "M2G1F4"
-KT8900_fp3 = "M2G2F4"
-KT8900_fp4 = "M2G304"
-KT8900_fp5 = "M2G314"
-# this radio has an extra ID
-KT8900_id = "      303688"
+        # receiving the response
+        ack = _rawrecv(radio, 1)
 
-# KT8900R
-KT8900R_fp = "M3G1F4"
-# Second Generation
-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"
+        # basic check
+        if len(ack) != 1:
+            raise errors.RadioError("No ACK when writing block 0x%04x" % addr)
 
+        if not ack in "\x06\x05":
+            raise errors.RadioError("Bad ACK writing block 0x%04x:" % addr)
 
-# LUITON LT-588UV
-LT588UV_fp = "V2G1F4"
-# Added by rstrickoff gen 2 id
-LT588UV_fp1 = "V2G214"
+         # UI Update
+        status.cur = addr / TX_BLOCK_SIZE
+        status.msg = "Cloning to radio..."
+        radio.status_fn(status)
 
 
-#### MAGICS
-# for the Waccom Mini-8900
-MSTRING_MINI8900 = "\x55\xA5\xB5\x45\x55\x45\x4d\x02"
-# for the B-TECH UV-2501+220 (including pre production ones)
-MSTRING_220 = "\x55\x20\x15\x12\x12\x01\x4d\x02"
-# for the QYT KT8900 & R
-MSTRING_KT8900 = "\x55\x20\x15\x09\x16\x45\x4D\x02"
-MSTRING_KT8900R = "\x55\x20\x15\x09\x25\x01\x4D\x02"
-# magic string for all other models
-MSTRING = "\x55\x20\x15\x09\x20\x45\x4d\x02"
+def model_match(cls, data):
+    """Match the opened/downloaded image to the correct version"""
+    rid = data[0x3f70:0x3f76]
 
+    if rid in cls._fileid:
+        return True
 
-def _clean_buffer(radio):
-    """Cleaning the read serial buffer, hard timeout to survive an infinite
-    data stream"""
+    return False
 
-    # touching the serial timeout to optimize the flushing
-    # restored at the end to the default value
-    radio.pipe.timeout = 0.1
-    dump = "1"
-    datacount = 0
 
-    try:
-        while len(dump) > 0:
-            dump = radio.pipe.read(100)
-            datacount += len(dump)
-            # hard limit to survive a infinite serial data stream
-            # 5 times bigger than a normal rx block (69 bytes)
-            if datacount > 345:
-                seriale = "Please check your serial port selection."
-                raise errors.RadioError(seriale)
+def _decode_ranges(low, high):
+    """Unpack the data in the ranges zones in the memmap and return
+    a tuple with the integer corresponding to the Mhz it means"""
+    ilow = int(low[0]) * 100 + int(low[1]) * 10 + int(low[2])
+    ihigh = int(high[0]) * 100 + int(high[1]) * 10 + int(high[2])
+    ilow *= 1000000
+    ihigh *= 1000000
 
-        # restore the default serial timeout
-        radio.pipe.timeout = STIMEOUT
+    return (ilow, ihigh)
 
-    except Exception:
-        raise errors.RadioError("Unknown error cleaning the serial buffer")
 
+def _split(rf, f1, f2):
+    """Returns False if the two freqs are in the same band (no split)
+    or True otherwise"""
 
-def _rawrecv(radio, amount):
-    """Raw read from the radio device, less intensive way"""
+    # 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
 
-    data = ""
+    # if you get here is because the freq pairs are split
+    return True
 
-    try:
-        data = radio.pipe.read(amount)
 
-        # DEBUG
-        if debug is True:
-            LOG.debug("<== (%d) bytes:\n\n%s" %
-                      (len(data), util.hexprint(data)))
+class BTechMobileCommon(chirp_common.CloneModeRadio,
+                        chirp_common.ExperimentalRadio):
+    """BTECH's UV-5001 and alike radios"""
+    VENDOR = "BTECH"
+    MODEL = ""
+    IDENT = ""
+    BANDS = 2
+    COLOR_LCD = False
+    NAME_LENGTH = 6
+    _power_levels = [chirp_common.PowerLevel("High", watts=25),
+                     chirp_common.PowerLevel("Low", watts=10)]
+    _vhf_range = (130000000, 180000000)
+    _220_range = (200000000, 271000000)
+    _uhf_range = (400000000, 521000000)
+    _350_range = (350000000, 391000000)
+    _upper = 199
+    _magic = MSTRING
+    _fileid = None
+    _id2 = False
+    btech3 = False
 
-        # fail if no data is received
-        if len(data) == 0:
-            raise errors.RadioError("No data received from radio")
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.experimental = \
+            ('This driver is experimental.\n'
+             '\n'
+             'Please keep a copy of your memories with the original software '
+             'if you treasure them, this driver is new and may contain'
+             ' bugs.\n'
+             '\n'
+             )
+        rp.pre_download = _(dedent("""\
+            Follow these instructions to download your info:
 
-        # notice on the logs if short
-        if len(data) < amount:
-            LOG.warn("Short reading %d bytes from the %d requested." %
-                     (len(data), amount))
+            1 - Turn off your radio
+            2 - Connect your interface cable
+            3 - Turn on your radio
+            4 - Do the download of your radio data
 
-    except:
-        raise errors.RadioError("Error reading data from radio")
+            """))
+        rp.pre_upload = _(dedent("""\
+            Follow these instructions to upload your info:
 
-    return data
+            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 _send(radio, data):
-    """Send data to the radio device"""
+    def get_features(self):
+        """Get the radio's features"""
 
-    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)
+        # we will use the following var as global
+        global POWER_LEVELS
 
-        # DEBUG
-        if debug is True:
-            LOG.debug("==> (%d) bytes:\n\n%s" %
-                      (len(data), util.hexprint(data)))
-    except:
-        raise errors.RadioError("Error sending data to radio")
+        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 = MODES
+        rf.valid_characters = VALID_CHARS
+        rf.valid_name_length = self.NAME_LENGTH
+        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 = SKIP_VALUES
+        rf.valid_dtcs_codes = DTCS
+        rf.memory_bounds = (0, self._upper)
 
+        # power levels
+        POWER_LEVELS = self._power_levels
+        rf.valid_power_levels = POWER_LEVELS
 
-def _make_frame(cmd, addr, length, data=""):
-    """Pack the info in the headder format"""
-    frame = "\x06" + struct.pack(">BHB", ord(cmd), addr, length)
-    # add the data if set
-    if len(data) != 0:
-        frame += data
+        # normal dual bands
+        rf.valid_bands = [self._vhf_range, self._uhf_range]
 
-    return frame
+        # 220 band
+        if self.BANDS == 3 or self.BANDS == 4:
+            rf.valid_bands.append(self._220_range)
 
+        # 350 band
+        if self.BANDS == 4:
+            rf.valid_bands.append(self._350_range)
 
-def _recv(radio, addr):
-    """Get data from the radio all at once to lower syscalls load"""
+        return rf
 
-    # Get the full 69 bytes at a time to reduce load
-    # 1 byte ACK + 4 bytes header + 64 bytes of data (BLOCK_SIZE)
+    def sync_in(self):
+        """Download from radio"""
+        data = _download(self)
+        self._mmap = memmap.MemoryMap(data)
+        self.process_mmap()
 
-    # get the whole block
-    block = _rawrecv(radio, BLOCK_SIZE + 5)
+    def sync_out(self):
+        """Upload to radio"""
+        try:
+            _upload(self)
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            raise errors.RadioError("Error: %s" % e)
 
-    # basic check
-    if len(block) < (BLOCK_SIZE + 5):
-        raise errors.RadioError("Short read of the block 0x%04x" % addr)
+    def get_raw_memory(self, number):
+        return repr(self._memobj.memory[number])
 
-    # checking for the ack
-    if block[0] != ACK_CMD:
-        raise errors.RadioError("Bad ack from radio in block 0x%04x" % addr)
+    def _decode_tone(self, val):
+        """Parse the tone data to decode from mem, it returns:
+        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
+        pol = None
 
-    # header validation
-    c, a, l = struct.unpack(">BHB", block[1:5])
-    if a != addr or l != BLOCK_SIZE or c != ord("X"):
-        LOG.debug("Invalid header for block 0x%04x" % addr)
-        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
-        raise errors.RadioError("Invalid header for block 0x%04x:" % addr)
+        if val in [0, 65535]:
+            return '', None, None
+        elif val > 0x0258:
+            a = val / 10.0
+            return 'Tone', a, pol
+        else:
+            if val > 0x69:
+                index = val - 0x6A
+                pol = "R"
+            else:
+                index = val - 1
+                pol = "N"
 
-    # return the data
-    return block[5:]
+            tone = DTCS[index]
+            return 'DTCS', tone, pol
+
+    def _encode_tone(self, memval, mode, val, pol):
+        """Parse the tone data to encode from UI to mem"""
+        if mode == '' or mode is None:
+            memval.set_raw("\x00\x00")
+        elif mode == 'Tone':
+            memval.set_value(val * 10)
+        elif mode == 'DTCS':
+            # detect the index in the DTCS list
+            try:
+                index = DTCS.index(val)
+                if pol == "N":
+                    index += 1
+                else:
+                    index += 0x6A
+                memval.set_value(index)
+            except:
+                msg = "Digital Tone '%d' is not supported" % value
+                LOG.error(msg)
+                raise errors.RadioError(msg)
+        else:
+            msg = "Internal error: invalid mode '%s'" % mode
+            LOG.error(msg)
+            raise errors.InvalidDataError(msg)
 
+    def get_memory(self, number):
+        """Get the mem representation from the radio image"""
+        _mem = self._memobj.memory[number]
+        _names = self._memobj.names[number]
 
-def _start_clone_mode(radio, status):
-    """Put the radio in clone mode and get the ident string, 3 tries"""
+        # Create a high-level memory object to return to the UI
+        mem = chirp_common.Memory()
 
-    # cleaning the serial buffer
-    _clean_buffer(radio)
+        # Memory number
+        mem.number = number
 
-    # prep the data to show in the UI
-    status.cur = 0
-    status.msg = "Identifying the radio..."
-    status.max = 3
-    radio.status_fn(status)
+        if _mem.get_raw()[0] == "\xFF":
+            mem.empty = True
+            return mem
 
-    try:
-        for a in range(0, status.max):
-            # Update the UI
-            status.cur = a + 1
-            radio.status_fn(status)
+        # Freq and offset
+        mem.freq = int(_mem.rxfreq) * 10
+        # tx freq can be blank
+        if _mem.get_raw()[4] == "\xFF":
+            # TX freq not set
+            mem.offset = 0
+            mem.duplex = "off"
+        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
 
-            # send the magic word
-            _send(radio, radio._magic)
+        # name TAG of the channel
+        mem.name = str(_names.name).rstrip("\xFF").replace("\xFF", " ")
 
-            # Now you get a x06 of ACK if all goes well
-            ack = radio.pipe.read(1)
+        # power
+        mem.power = POWER_LEVELS[int(_mem.power)]
 
-            if ack == "\x06":
-                # DEBUG
-                LOG.info("Magic ACK received")
-                status.cur = status.max
-                radio.status_fn(status)
+        # wide/narrow
+        mem.mode = MODES[int(_mem.wide)]
 
-                return True
+        # skip
+        mem.skip = SKIP_VALUES[_mem.add]
 
-        return False
+        # tone data
+        rxtone = txtone = None
+        txtone = self._decode_tone(_mem.txtone)
+        rxtone = self._decode_tone(_mem.rxtone)
+        chirp_common.split_tone_decode(mem, txtone, rxtone)
 
-    except errors.RadioError:
-        raise
-    except Exception, e:
-        raise errors.RadioError("Error sending Magic to radio:\n%s" % e)
+        # Extra
+        mem.extra = RadioSettingGroup("extra", "Extra")
 
+        if not self.COLOR_LCD or \
+            (self.COLOR_LCD and not self.VENDOR == "BTECH"):
+            scramble = RadioSetting("scramble", "Scramble",
+                                    RadioSettingValueBoolean(bool(
+                                        _mem.scramble)))
+            mem.extra.append(scramble)
 
-def _do_ident(radio, status, upload=False):
-    """Put the radio in PROGRAM mode & identify it"""
-    #  set the serial discipline
-    radio.pipe.baudrate = 9600
-    radio.pipe.parity = "N"
+        bcl = RadioSetting("bcl", "Busy channel lockout",
+                           RadioSettingValueBoolean(bool(_mem.bcl)))
+        mem.extra.append(bcl)
 
-    # open the radio into program mode
-    if _start_clone_mode(radio, status) is False:
-        msg = "Radio did not enter clone mode"
-        # warning about old versions of QYT KT8900
-        if radio.MODEL == "KT8900":
-            msg += ". You may want to try it as a WACCOM MINI-8900, there is a"
-            msg += " known variant of this radios that is a clone of it."
-        raise errors.RadioError(msg)
+        pttid = RadioSetting("pttid", "PTT ID",
+                             RadioSettingValueList(PTTID_LIST,
+                                                   PTTID_LIST[_mem.pttid]))
+        mem.extra.append(pttid)
 
-    # Ok, get the ident string
-    ident = _rawrecv(radio, 49)
+        # validating scode
+        scode = _mem.scode if _mem.scode != 15 else 0
+        pttidcode = RadioSetting("scode", "PTT ID signal code",
+                                 RadioSettingValueList(
+                                     PTTIDCODE_LIST,
+                                     PTTIDCODE_LIST[scode]))
+        mem.extra.append(pttidcode)
 
-    # basic check for the ident
-    if len(ident) != 49:
-        raise errors.RadioError("Radio send a short ident block.")
+        optsig = RadioSetting("optsig", "Optional signaling",
+                              RadioSettingValueList(
+                                  OPTSIG_LIST,
+                                  OPTSIG_LIST[_mem.optsig]))
+        mem.extra.append(optsig)
 
-    # check if ident is OK
-    itis = False
-    for fp in radio._fileid:
-        if fp in ident:
-            # got it!
-            itis = True
-            # checking if we are dealing with a Gen 3 BTECH
-            if radio.VENDOR == "BTECH" and fp in BTECH3:
-                radio.btech3 = True
+        spmute = RadioSetting("spmute", "Speaker mute",
+                              RadioSettingValueList(
+                                  SPMUTE_LIST,
+                                  SPMUTE_LIST[_mem.spmute]))
+        mem.extra.append(spmute)
 
-            break
+        return mem
 
-    if itis is False:
-        LOG.debug("Incorrect model ID, got this:\n\n" + util.hexprint(ident))
-        raise errors.RadioError("Radio identification failed.")
+    def set_memory(self, mem):
+        """Set the memory data in the eeprom img from the UI"""
+        # get the eprom representation of this channel
+        _mem = self._memobj.memory[mem.number]
+        _names = self._memobj.names[mem.number]
 
-    # some radios needs a extra read and check for a code on it, this ones
-    # has the check value in the _id2 var, others simply False
-    if radio._id2 is not False:
-        # lower the timeout here as this radios are reseting due to timeout
-        radio.pipe.timeout = 0.05
+        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
+            _mem.set_raw("\xFF" * 16)
+            # the name tag
+            _names.set_raw("\xFF" * 16)
+            return
 
-        # query & receive the extra ID
-        _send(radio, _make_frame("S", 0x3DF0, 16))
-        id2 = _rawrecv(radio, 21)
+        # frequency
+        _mem.rxfreq = mem.freq / 10
 
-        # WARNING !!!!!!
-        # different radios send a response with a different amount of data
-        # it seems that it's padded with \xff, \x20 and some times with \x00
-        # we just care about the first 16, our magic string is in there
-        if len(id2) < 16:
-            raise errors.RadioError("The extra ID is short, aborting.")
+        # duplex
+        if mem.duplex == "+":
+            _mem.txfreq = (mem.freq + mem.offset) / 10
+        elif mem.duplex == "-":
+            _mem.txfreq = (mem.freq - mem.offset) / 10
+        elif mem.duplex == "off":
+            for i in _mem.txfreq:
+                i.set_raw("\xFF")
+        elif mem.duplex == "split":
+            _mem.txfreq = mem.offset / 10
+        else:
+            _mem.txfreq = mem.freq / 10
 
-        # ok, the correct string must be in the received data
-        if radio._id2 not in id2:
-            LOG.debug("Full *BAD* extra ID on the %s is: \n%s" %
-                      (radio.MODEL, util.hexprint(id2)))
-            raise errors.RadioError("The extra ID is wrong, aborting.")
+        # tone data
+        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
+            chirp_common.split_tone_encode(mem)
+        self._encode_tone(_mem.txtone, txmode, txtone, txpol)
+        self._encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
 
-        # this radios need a extra request/answer here on the upload
-        # the amount of data received depends of the radio type
-        #
-        # also the first block of TX must no have the ACK at the beginning
-        # see _upload for this.
-        if upload is True:
-            # send an ACK
-            _send(radio, ACK_CMD)
+        # name TAG of the channel
+        if len(mem.name) < self.NAME_LENGTH:
+            # we must pad to self.NAME_LENGTH chars, " " = "\xFF"
+            mem.name = str(mem.name).ljust(self.NAME_LENGTH, " ")
+        _names.name = str(mem.name).replace(" ", "\xFF")
 
-            # the amount of data depend on the radio, so far we have two radios
-            # reading two bytes with an ACK at the end and just ONE with just
-            # one byte (QYT KT8900)
-            # the JT-6188 appears a clone of the last, but reads TWO bytes.
-            #
-            # we will read two bytes with a custom timeout to not penalize the
-            # users for this.
-            #
-            # we just check for a response and last byte being a ACK, that is
-            # the common stone for all radios (3 so far)
-            ack = _rawrecv(radio, 2)
+        # power, # default power level is high
+        _mem.power = 0 if mem.power is None else POWER_LEVELS.index(mem.power)
 
-            # checking
-            if len(ack) == 0 or ack[-1:] != ACK_CMD:
-                raise errors.RadioError("Radio didn't ACK the upload")
+        # wide/narrow
+        _mem.wide = MODES.index(mem.mode)
 
-            # restore the default serial timeout
-            radio.pipe.timeout = STIMEOUT
+        # scan add property
+        _mem.add = SKIP_VALUES.index(mem.skip)
 
-    # DEBUG
-    LOG.info("Positive ident, this is a %s %s" % (radio.VENDOR, radio.MODEL))
+        # reseting unknowns, this have to be set by hand
+        _mem.unknown0 = 0
+        _mem.unknown1 = 0
+        _mem.unknown2 = 0
+        _mem.unknown3 = 0
+        _mem.unknown4 = 0
+        _mem.unknown5 = 0
+        _mem.unknown6 = 0
 
-    return True
+        # 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:
+            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
 
-def _download(radio):
-    """Get the memory map"""
+    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")
+        top = RadioSettings(basic, advanced, other, work)
 
-    # UI progress
-    status = chirp_common.Status()
+        # Basic
+        if self.COLOR_LCD:
+            tmr = RadioSetting("settings.tmr", "Transceiver multi-receive",
+                               RadioSettingValueList(
+                                   LIST_TMR,
+                                   LIST_TMR[_mem.settings.tmr]))
+            basic.append(tmr)
+        else:
+            tdr = RadioSetting("settings.tdr", "Transceiver dual receive",
+                               RadioSettingValueBoolean(_mem.settings.tdr))
+            basic.append(tdr)
 
-    # put radio in program mode and identify it
-    _do_ident(radio, status)
+        sql = RadioSetting("settings.sql", "Squelch level",
+                           RadioSettingValueInteger(0, 9, _mem.settings.sql))
+        basic.append(sql)
 
-    # the models that doesn't have the extra ID have to make a dummy read here
-    if radio._id2 is False:
-        _send(radio, _make_frame("S", 0, BLOCK_SIZE))
-        discard = _rawrecv(radio, BLOCK_SIZE + 5)
+        tot = RadioSetting("settings.tot", "Time out timer",
+                           RadioSettingValueList(
+                               LIST_TOT,
+                               LIST_TOT[_mem.settings.tot]))
+        basic.append(tot)
 
-        if debug is True:
-            LOG.info("Dummy first block read done, got this:\n\n %s",
-                     util.hexprint(discard))
+        if self.VENDOR == "BTECH" or self.COLOR_LCD:
+            apo = RadioSetting("settings.apo", "Auto power off timer",
+                               RadioSettingValueList(
+                                   LIST_APO,
+                                   LIST_APO[_mem.settings.apo]))
+            basic.append(apo)
+        else:
+            toa = RadioSetting("settings.apo", "Time out alert timer",
+                               RadioSettingValueList(
+                                   LIST_OFF1TO10, 
+                                   LIST_OFF1TO10[_mem.settings.apo]))
+            basic.append(toa)
 
-    # reset the progress bar in the UI
-    status.max = MEM_SIZE / BLOCK_SIZE
-    status.msg = "Cloning from radio..."
-    status.cur = 0
-    radio.status_fn(status)
+        abr = RadioSetting("settings.abr", "Backlight timer",
+                           RadioSettingValueList(
+                               LIST_OFF1TO50,
+                               LIST_OFF1TO50[_mem.settings.abr]))
+        basic.append(abr)
 
-    # cleaning the serial buffer
-    _clean_buffer(radio)
+        beep = RadioSetting("settings.beep", "Key beep",
+                            RadioSettingValueBoolean(_mem.settings.beep))
+        basic.append(beep)
 
-    data = ""
-    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
-        # sending the read request
-        _send(radio, _make_frame("S", addr, BLOCK_SIZE))
+        dtmfst = RadioSetting("settings.dtmfst", "DTMF side tone",
+                              RadioSettingValueList(
+                                  LIST_DTMFST,
+                                  LIST_DTMFST[_mem.settings.dtmfst]))
+        basic.append(dtmfst)
 
-        # read
-        d = _recv(radio, addr)
+        if not self.COLOR_LCD:
+            prisc = RadioSetting("settings.prisc", "Priority scan",
+                                 RadioSettingValueBoolean(
+                                     _mem.settings.prisc))
+            basic.append(prisc)
 
-        # aggregate the data
-        data += d
+            prich = RadioSetting("settings.prich", "Priority channel",
+                                 RadioSettingValueInteger(0, 199,
+                                     _mem.settings.prich))
+            basic.append(prich)
 
-        # UI Update
-        status.cur = addr / BLOCK_SIZE
-        status.msg = "Cloning from radio..."
-        radio.status_fn(status)
+        screv = RadioSetting("settings.screv", "Scan resume method",
+                             RadioSettingValueList(
+                                 LIST_SCREV,
+                                 LIST_SCREV[_mem.settings.screv]))
+        basic.append(screv)
 
-    return data
+        pttlt = RadioSetting("settings.pttlt", "PTT transmit delay",
+                             RadioSettingValueInteger(0, 30,
+                                 _mem.settings.pttlt))
+        basic.append(pttlt)
 
+        if self.VENDOR == "BTECH" and self.COLOR_LCD:
+            emctp = RadioSetting("settings.emctp", "Alarm mode",
+                                 RadioSettingValueList(
+                                     LIST_EMCTPX,
+                                     LIST_EMCTPX[_mem.settings.emctp]))
+            basic.append(emctp)
+        else:
+            emctp = RadioSetting("settings.emctp", "Alarm mode",
+                                 RadioSettingValueList(
+                                     LIST_EMCTP,
+                                     LIST_EMCTP[_mem.settings.emctp]))
+            basic.append(emctp)
 
-def _upload(radio):
-    """Upload procedure"""
+        emcch = RadioSetting("settings.emcch", "Alarm channel",
+                             RadioSettingValueInteger(0, 199,
+                                 _mem.settings.emcch))
+        basic.append(emcch)
 
-    # The UPLOAD mem is restricted to lower than 0x3100,
-    # so we will overide that here localy
-    MEM_SIZE = 0x3100
+        if self.COLOR_LCD:
+            if _mem.settings.sigbp > 0x01:
+                val = 0x00
+            else:
+                val = _mem.settings.sigbp
+            sigbp = RadioSetting("settings.sigbp", "Roger beep",
+                                 RadioSettingValueBoolean(val))
+            basic.append(sigbp)
+        else:
+            ringt = RadioSetting("settings.ringt", "Ring time",
+                                 RadioSettingValueList(
+                                     LIST_OFF1TO9,
+                                     LIST_OFF1TO9[_mem.settings.ringt]))
+            basic.append(ringt)
 
-    # UI progress
-    status = chirp_common.Status()
+        camdf = RadioSetting("settings.camdf", "Display mode A",
+                             RadioSettingValueList(
+                                 LIST_MDF,
+                                 LIST_MDF[_mem.settings.camdf]))
+        basic.append(camdf)
 
-    # put radio in program mode and identify it
-    _do_ident(radio, status, True)
+        cbmdf = RadioSetting("settings.cbmdf", "Display mode B",
+                             RadioSettingValueList(
+                                 LIST_MDF,
+                                 LIST_MDF[_mem.settings.cbmdf]))
+        basic.append(cbmdf)
 
-    # get the data to upload to radio
-    data = radio.get_mmap()
+        if self.COLOR_LCD:
+            ccmdf = RadioSetting("settings.ccmdf", "Display mode C",
+                                 RadioSettingValueList(
+                                     LIST_MDF,
+                                     LIST_MDF[_mem.settings.ccmdf]))
+            basic.append(ccmdf)
 
-    # Reset the UI progress
-    status.max = MEM_SIZE / TX_BLOCK_SIZE
-    status.cur = 0
-    status.msg = "Cloning to radio..."
-    radio.status_fn(status)
+            cdmdf = RadioSetting("settings.cdmdf", "Display mode D",
+                                 RadioSettingValueList(
+                                     LIST_MDF,
+                                     LIST_MDF[_mem.settings.cdmdf]))
+            basic.append(cdmdf)
+
+            langua = RadioSetting("settings.langua", "Language",
+                                  RadioSettingValueList(
+                                      LIST_LANGUA,
+                                      LIST_LANGUA[_mem.settings.langua]))
+            basic.append(langua)
+
+        if self.VENDOR == "BTECH":
+            if self.COLOR_LCD:
+                sync = RadioSetting("settings.sync", "Channel display sync",
+                                    RadioSettingValueList(
+                                        LIST_SYNC,
+                                        LIST_SYNC[_mem.settings.sync]))
+                basic.append(sync)
+            else:
+                sync = RadioSetting("settings.sync", "A/B channel sync",
+                                    RadioSettingValueBoolean(
+                                        _mem.settings.sync))
+                basic.append(sync)
+        else:
+            autolk = RadioSetting("settings.sync", "Auto keylock",
+                                  RadioSettingValueBoolean(
+                                      _mem.settings.sync))
+            basic.append(autolk)
+
+        if not self.COLOR_LCD:
+            ponmsg = RadioSetting("settings.ponmsg", "Power-on message",
+                                  RadioSettingValueList(
+                                      LIST_PONMSG,
+                                      LIST_PONMSG[_mem.settings.ponmsg]))
+            basic.append(ponmsg)
+
+        if self.COLOR_LCD:
+            mainfc = RadioSetting("settings.mainfc", 
+                                  "Main LCD foreground color",
+                                      RadioSettingValueList(
+                                          LIST_COLOR8,
+                                          LIST_COLOR8[_mem.settings.mainfc]))
+            basic.append(mainfc)
+
+            mainbc = RadioSetting("settings.mainbc",
+                                  "Main LCD background color",
+                                      RadioSettingValueList(
+                                          LIST_COLOR8,
+                                          LIST_COLOR8[_mem.settings.mainbc]))
+            basic.append(mainbc)
+
+            menufc = RadioSetting("settings.menufc", "Menu foreground color",
+                                  RadioSettingValueList(
+                                      LIST_COLOR8,
+                                      LIST_COLOR8[_mem.settings.menufc]))
+            basic.append(menufc)
+
+            menubc = RadioSetting("settings.menubc", "Menu background color",
+                                  RadioSettingValueList(
+                                      LIST_COLOR8,
+                                      LIST_COLOR8[_mem.settings.menubc]))
+            basic.append(menubc)
+
+            stafc = RadioSetting("settings.stafc",
+                                 "Top status foreground color",
+                                     RadioSettingValueList(
+                                         LIST_COLOR8,
+                                         LIST_COLOR8[_mem.settings.stafc]))
+            basic.append(stafc)
+
+            stabc = RadioSetting("settings.stabc",
+                                 "Top status background color",
+                                     RadioSettingValueList(
+                                         LIST_COLOR8,
+                                         LIST_COLOR8[_mem.settings.stabc]))
+            basic.append(stabc)
+
+            sigfc = RadioSetting("settings.sigfc",
+                                 "Bottom status foreground color",
+                                     RadioSettingValueList(
+                                         LIST_COLOR8,
+                                         LIST_COLOR8[_mem.settings.sigfc]))
+            basic.append(sigfc)
+
+            sigbc = RadioSetting("settings.sigbc",
+                                 "Bottom status background color",
+                                     RadioSettingValueList(
+                                         LIST_COLOR8,
+                                         LIST_COLOR8[_mem.settings.sigbc]))
+            basic.append(sigbc)
+
+            rxfc = RadioSetting("settings.rxfc", "Receiving character color",
+                                RadioSettingValueList(
+                                    LIST_COLOR8,
+                                    LIST_COLOR8[_mem.settings.rxfc]))
+            basic.append(rxfc)
+
+            txfc = RadioSetting("settings.txfc",
+                                "Transmitting character color",
+                                    RadioSettingValueList(
+                                        LIST_COLOR8,
+                                        LIST_COLOR8[_mem.settings.txfc]))
+            basic.append(txfc)
+
+            txdisp = RadioSetting("settings.txdisp",
+                                  "Transmitting status display",
+                                      RadioSettingValueList(
+                                          LIST_TXDISP,
+                                          LIST_TXDISP[_mem.settings.txdisp]))
+            basic.append(txdisp)
+        else:
+            wtled = RadioSetting("settings.wtled", "Standby backlight Color",
+                                 RadioSettingValueList(
+                                     LIST_COLOR4,
+                                     LIST_COLOR4[_mem.settings.wtled]))
+            basic.append(wtled)
 
-    # the radios that doesn't have the extra ID 'may' do a dummy write, I found
-    # that leveraging the bad ACK and NOT doing the dummy write is ok, as the
-    # dummy write is accepted (it actually writes to the mem!) by the radio.
+            rxled = RadioSetting("settings.rxled", "RX backlight Color",
+                                 RadioSettingValueList(
+                                     LIST_COLOR4,
+                                     LIST_COLOR4[_mem.settings.rxled]))
+            basic.append(rxled)
 
-    # cleaning the serial buffer
-    _clean_buffer(radio)
+            txled = RadioSetting("settings.txled", "TX backlight Color",
+                                 RadioSettingValueList(
+                                     LIST_COLOR4,
+                                     LIST_COLOR4[_mem.settings.txled]))
+            basic.append(txled)
 
-    # the fun start here
-    for addr in range(0, MEM_SIZE, TX_BLOCK_SIZE):
-        # getting the block of data to send
-        d = data[addr:addr + TX_BLOCK_SIZE]
+        anil = RadioSetting("settings.anil", "ANI length",
+                            RadioSettingValueList(
+                                LIST_ANIL,
+                                LIST_ANIL[_mem.settings.anil]))
+        basic.append(anil)
 
-        # build the frame to send
-        frame = _make_frame("X", addr, TX_BLOCK_SIZE, d)
+        reps = RadioSetting("settings.reps", "Relay signal (tone burst)",
+                            RadioSettingValueList(
+                                LIST_REPS,
+                                LIST_REPS[_mem.settings.reps]))
+        basic.append(reps)
 
-        # first block must not send the ACK at the beginning for the
-        # ones that has the extra id, since this have to do a extra step
-        if addr == 0 and radio._id2 is not False:
-            frame = frame[1:]
+        repm = RadioSetting("settings.repm", "Relay condition",
+                            RadioSettingValueList(
+                                LIST_REPM,
+                                LIST_REPM[_mem.settings.repm]))
+        basic.append(repm)
 
-        # send the frame
-        _send(radio, frame)
+        if self.VENDOR == "BTECH" or self.COLOR_LCD:
+            if self.COLOR_LCD:
+                tmrmr = RadioSetting("settings.tmrmr", "TMR return time",
+                                     RadioSettingValueList(
+                                         LIST_OFF1TO50,
+                                         LIST_OFF1TO50[_mem.settings.tmrmr]))
+                basic.append(tmrmr)
+            else:
+                tdrab = RadioSetting("settings.tdrab", "TDR return time",
+                                     RadioSettingValueList(
+                                         LIST_OFF1TO50, 
+                                         LIST_OFF1TO50[_mem.settings.tdrab]))
+                basic.append(tdrab)
 
-        # receiving the response
-        ack = _rawrecv(radio, 1)
+            ste = RadioSetting("settings.ste", "Squelch tail eliminate",
+                               RadioSettingValueBoolean(_mem.settings.ste))
+            basic.append(ste)
 
-        # basic check
-        if len(ack) != 1:
-            raise errors.RadioError("No ACK when writing block 0x%04x" % addr)
+            rpste = RadioSetting("settings.rpste", "Repeater STE",
+                                 RadioSettingValueList(
+                                     LIST_OFF1TO9,
+                                     LIST_OFF1TO9[_mem.settings.rpste]))
+            basic.append(rpste)
 
-        if not ack in "\x06\x05":
-            raise errors.RadioError("Bad ACK writing block 0x%04x:" % addr)
+            rptdl = RadioSetting("settings.rptdl", "Repeater STE delay",
+                                 RadioSettingValueList(
+                                     LIST_RPTDL,
+                                     LIST_RPTDL[_mem.settings.rptdl]))
+            basic.append(rptdl)
 
-         # UI Update
-        status.cur = addr / TX_BLOCK_SIZE
-        status.msg = "Cloning to radio..."
-        radio.status_fn(status)
+        if str(_mem.fingerprint.fp) in BTECH3:
+            mgain = RadioSetting("settings.mgain", "Mic gain",
+                                 RadioSettingValueInteger(0, 120,
+                                     _mem.settings.mgain))
+            basic.append(mgain)
 
+        if str(_mem.fingerprint.fp) in BTECH3 or self.COLOR_LCD:
+            dtmfg = RadioSetting("settings.dtmfg", "DTMF gain",
+                                 RadioSettingValueInteger(0, 60,
+                                     _mem.settings.dtmfg))
+            basic.append(dtmfg)
 
-def model_match(cls, data):
-    """Match the opened/downloaded image to the correct version"""
-    rid = data[0x3f70:0x3f76]
+        if self.VENDOR == "BTECH" and self.COLOR_LCD:
+            mgain = RadioSetting("settings.mgain", "Mic gain",
+                                 RadioSettingValueInteger(0, 120,
+                                     _mem.settings.mgain))
+            basic.append(mgain)
 
-    if rid in cls._fileid:
-        return True
+            skiptx = RadioSetting("settings.skiptx", "Skip TX",
+                                  RadioSettingValueList(
+                                      LIST_SKIPTX,
+                                      LIST_SKIPTX[_mem.settings.skiptx]))
+            basic.append(skiptx)
 
-    return False
+            scmode = RadioSetting("settings.scmode", "Scan mode",
+                                  RadioSettingValueList(
+                                      LIST_SCMODE,
+                                      LIST_SCMODE[_mem.settings.scmode]))
+            basic.append(scmode)
 
+        # Advanced
+        def _filter(name):
+            filtered = ""
+            for char in str(name):
+                if char in VALID_CHARS:
+                    filtered += char
+                else:
+                    filtered += " "
+            return filtered
 
-def _decode_ranges(low, high):
-    """Unpack the data in the ranges zones in the memmap and return
-    a tuple with the integer corresponding to the Mhz it means"""
-    ilow = int(low[0]) * 100 + int(low[1]) * 10 + int(low[2])
-    ihigh = int(high[0]) * 100 + int(high[1]) * 10 + int(high[2])
-    ilow *= 1000000
-    ihigh *= 1000000
+        _msg = self._memobj.poweron_msg
+        if self.COLOR_LCD:
+            line1 = RadioSetting("poweron_msg.line1",
+                                 "Power-on message line 1",
+                                     RadioSettingValueString(0, 8, _filter(
+                                         _msg.line1)))
+            advanced.append(line1)
+            line2 = RadioSetting("poweron_msg.line2",
+                                 "Power-on message line 2",
+                                     RadioSettingValueString(0, 8, _filter(
+                                         _msg.line2)))
+            advanced.append(line2)
+            line3 = RadioSetting("poweron_msg.line3",
+                                 "Power-on message line 3",
+                                     RadioSettingValueString(0, 8, _filter(
+                                         _msg.line3)))
+            advanced.append(line3)
+            line4 = RadioSetting("poweron_msg.line4",
+                                 "Power-on message line 4",
+                                     RadioSettingValueString(0, 8, _filter(
+                                         _msg.line4)))
+            advanced.append(line4)
+            line5 = RadioSetting("poweron_msg.line5",
+                                 "Power-on message line 5",
+                                     RadioSettingValueString(0, 8, _filter(
+                                         _msg.line5)))
+            advanced.append(line5)
+            line6 = RadioSetting("poweron_msg.line6",
+                                 "Power-on message line 6",
+                                     RadioSettingValueString(0, 8, _filter(
+                                         _msg.line6)))
+            advanced.append(line6)
+            line7 = RadioSetting("poweron_msg.line7",
+                                 "Power-on message line 7",
+                                     RadioSettingValueString(0, 8, _filter(
+                                         _msg.line7)))
+            advanced.append(line7)
+            line8 = RadioSetting("poweron_msg.line8", "Static message",
+                                 RadioSettingValueString(0, 8, _filter(
+                                     _msg.line8)))
+            advanced.append(line8)
+        else:
+            line1 = RadioSetting("poweron_msg.line1",
+                                 "Power-on message line 1",
+                                     RadioSettingValueString(0, 6, _filter(
+                                         _msg.line1)))
+            advanced.append(line1)
+            line2 = RadioSetting("poweron_msg.line2",
+                                 "Power-on message line 2",
+                                     RadioSettingValueString(0, 6, _filter(
+                                         _msg.line2)))
+            advanced.append(line2)
 
-    return (ilow, ihigh)
+        if self.MODEL in ("UV-2501", "UV-5001"):
+            vfomren = RadioSetting("settings2.vfomren", "VFO/MR switching",
+                                   RadioSettingValueBoolean(
+                                       _mem.settings2.vfomren))
+            advanced.append(vfomren)
 
+            reseten = RadioSetting("settings2.reseten", "RESET",
+                                   RadioSettingValueBoolean(
+                                       _mem.settings2.reseten))
+            advanced.append(reseten)
 
-def _split(rf, f1, f2):
-    """Returns False if the two freqs are in the same band (no split)
-    or True otherwise"""
+            menuen = RadioSetting("settings2.menuen", "Menu",
+                                  RadioSettingValueBoolean(
+                                      _mem.settings2.menuen))
+            advanced.append(menuen)
 
-    # 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
+        # Other
+        def convert_bytes_to_limit(bytes):
+            limit = ""
+            for byte in bytes:
+                if byte < 10:
+                    limit += chr(byte + 0x30)
+                else:
+                    break
+            return limit
 
-    # if you get here is because the freq pairs are split
-    return False
+        if self.MODEL in ["UV-2501+220", "KT8900R"]:
+            _ranges = self._memobj.ranges220
+            ranges = "ranges220"
+        else:
+            _ranges = self._memobj.ranges
+            ranges = "ranges"
 
+        _limit = convert_bytes_to_limit(_ranges.vhf_low)
+        val = RadioSettingValueString(0, 3, _limit)
+        val.set_mutable(False)
+        vhf_low = RadioSetting("%s.vhf_low" % ranges, "VHF low", val)
+        other.append(vhf_low)
 
-class BTech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
-    """BTECH's UV-5001 and alike radios"""
-    VENDOR = "BTECH"
-    MODEL = ""
-    IDENT = ""
-    _vhf_range = (130000000, 180000000)
-    _220_range = (210000000, 231000000)
-    _uhf_range = (400000000, 521000000)
-    _upper = 199
-    _magic = MSTRING
-    _fileid = None
-    _id2 = False
-    btech3 = False
+        _limit = convert_bytes_to_limit(_ranges.vhf_high)
+        val = RadioSettingValueString(0, 3, _limit)
+        val.set_mutable(False)
+        vhf_high = RadioSetting("%s.vhf_high" % ranges, "VHF high", val)
+        other.append(vhf_high)
 
-    @classmethod
-    def get_prompts(cls):
-        rp = chirp_common.RadioPrompts()
-        rp.experimental = \
-            ('This driver is experimental.\n'
-             '\n'
-             'Please keep a copy of your memories with the original software '
-             'if you treasure them, this driver is new and may contain'
-             ' bugs.\n'
-             '\n'
-             )
-        rp.pre_download = _(dedent("""\
-            Follow these instructions to download your info:
+        if self.BANDS == 3 or self.BANDS == 4:
+            _limit = convert_bytes_to_limit(_ranges.vhf2_low)
+            val = RadioSettingValueString(0, 3, _limit)
+            val.set_mutable(False)
+            vhf2_low = RadioSetting("%s.vhf2_low" % ranges, "VHF2 low", val)
+            other.append(vhf2_low)
 
-            1 - Turn off your radio
-            2 - Connect your interface cable
-            3 - Turn on your radio
-            4 - Do the download of your radio data
+            _limit = convert_bytes_to_limit(_ranges.vhf2_high)
+            val = RadioSettingValueString(0, 3, _limit)
+            val.set_mutable(False)
+            vhf2_high = RadioSetting("%s.vhf2_high" % ranges, "VHF2 high", val)
+            other.append(vhf2_high)
 
-            """))
-        rp.pre_upload = _(dedent("""\
-            Follow these instructions to upload your info:
+        _limit = convert_bytes_to_limit(_ranges.uhf_low)
+        val = RadioSettingValueString(0, 3, _limit)
+        val.set_mutable(False)
+        uhf_low = RadioSetting("%s.uhf_low" % ranges, "UHF low", val)
+        other.append(uhf_low)
 
-            1 - Turn off your radio
-            2 - Connect your interface cable
-            3 - Turn on your radio
-            4 - Do the upload of your radio data
+        _limit = convert_bytes_to_limit(_ranges.uhf_high)
+        val = RadioSettingValueString(0, 3, _limit)
+        val.set_mutable(False)
+        uhf_high = RadioSetting("%s.uhf_high" % ranges, "UHF high", val)
+        other.append(uhf_high)
 
-            """))
-        return rp
+        if self.BANDS == 4:
+            _limit = convert_bytes_to_limit(_ranges.uhf2_low)
+            val = RadioSettingValueString(0, 3, _limit)
+            val.set_mutable(False)
+            uhf2_low = RadioSetting("%s.uhf2_low" % ranges, "UHF2 low", val)
+            other.append(uhf2_low)
 
-    def get_features(self):
-        """Get the radio's features"""
+            _limit = convert_bytes_to_limit(_ranges.uhf2_high)
+            val = RadioSettingValueString(0, 3, _limit)
+            val.set_mutable(False)
+            uhf2_high = RadioSetting("%s.uhf2_high" % ranges, "UHF2 high", val)
+            other.append(uhf2_high)
 
-        # we will use the following var as global
-        global POWER_LEVELS
+        val = RadioSettingValueString(0, 6, _filter(_mem.fingerprint.fp))
+        val.set_mutable(False)
+        fp = RadioSetting("fingerprint.fp", "Fingerprint", val)
+        other.append(fp)
 
-        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 = MODES
-        rf.valid_characters = VALID_CHARS
-        rf.valid_name_length = NAME_LENGTH
-        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 = SKIP_VALUES
-        rf.valid_dtcs_codes = DTCS
-        rf.memory_bounds = (0, self._upper)
 
-        # power levels
-        if self.MODEL == "UV-5001":
-            POWER_LEVELS = UV5001_POWER_LEVELS  # Higher power (50W)
+        # Work
+        if self.COLOR_LCD:
+            dispab = RadioSetting("settings2.dispab", "Display",
+                                  RadioSettingValueList(
+                                      LIST_ABCD,
+                                      LIST_ABCD[_mem.settings2.dispab]))
+            work.append(dispab)
+        else:
+            dispab = RadioSetting("settings2.dispab", "Display",
+                                  RadioSettingValueList(
+                                      LIST_AB,
+                                      LIST_AB[_mem.settings2.dispab]))
+            work.append(dispab)
+
+        if self.COLOR_LCD:
+            vfomra = RadioSetting("settings2.vfomra", "VFO/MR A mode",
+                                  RadioSettingValueList(
+                                      LIST_VFOMR,
+                                      LIST_VFOMR[_mem.settings2.vfomra]))
+            work.append(vfomra)
+
+            vfomrb = RadioSetting("settings2.vfomrb", "VFO/MR B mode",
+                                  RadioSettingValueList(
+                                      LIST_VFOMR,
+                                      LIST_VFOMR[_mem.settings2.vfomrb]))
+            work.append(vfomrb)
+
+            vfomrc = RadioSetting("settings2.vfomrc", "VFO/MR C mode",
+                                  RadioSettingValueList(
+                                      LIST_VFOMR,
+                                      LIST_VFOMR[_mem.settings2.vfomrc]))
+            work.append(vfomrc)
+
+            vfomrd = RadioSetting("settings2.vfomrd", "VFO/MR D mode",
+                                  RadioSettingValueList(
+                                      LIST_VFOMR,
+                                      LIST_VFOMR[_mem.settings2.vfomrd]))
+            work.append(vfomrd)
         else:
-            POWER_LEVELS = NORMAL_POWER_LEVELS  # Lower power (25W)
+            vfomr = RadioSetting("settings2.vfomr", "VFO/MR mode",
+                                 RadioSettingValueList(
+                                     LIST_VFOMR,
+                                     LIST_VFOMR[_mem.settings2.vfomr]))
+            work.append(vfomr)
 
-        rf.valid_power_levels = POWER_LEVELS
 
-        # bands
-        rf.valid_bands = [self._vhf_range, self._uhf_range]
+        keylock = RadioSetting("settings2.keylock", "Keypad lock",
+                           RadioSettingValueBoolean(_mem.settings2.keylock))
+        work.append(keylock)
 
-        # 2501+220 & KT8900R
-        if self.MODEL in ["UV-2501+220", "KT8900R"]:
-            rf.valid_bands.append(self._220_range)
+        mrcha = RadioSetting("settings2.mrcha", "MR A channel",
+                             RadioSettingValueInteger(0, 199,
+                                 _mem.settings2.mrcha))
+        work.append(mrcha)
 
-        return rf
+        mrchb = RadioSetting("settings2.mrchb", "MR B channel",
+                             RadioSettingValueInteger(0, 199,
+                                 _mem.settings2.mrchb))
+        work.append(mrchb)
 
-    def sync_in(self):
-        """Download from radio"""
-        data = _download(self)
-        self._mmap = memmap.MemoryMap(data)
-        self.process_mmap()
+        if self.COLOR_LCD:
+            mrchc = RadioSetting("settings2.mrchc", "MR C channel",
+                                 RadioSettingValueInteger(0, 199,
+                                     _mem.settings2.mrchc))
+            work.append(mrchc)
 
-    def sync_out(self):
-        """Upload to radio"""
-        try:
-            _upload(self)
-        except errors.RadioError:
-            raise
-        except Exception, e:
-            raise errors.RadioError("Error: %s" % e)
+            mrchd = RadioSetting("settings2.mrchd", "MR D channel",
+                                 RadioSettingValueInteger(0, 199,
+                                     _mem.settings2.mrchd))
+            work.append(mrchd)
 
-    def set_options(self):
-        """This is to read the options from the image and set it in the
-        environment, for now just the limits of the freqs in the VHF/UHF
-        ranges"""
+        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)
 
-        # setting the correct ranges for each radio type
-        if self.MODEL in ["UV-2501+220", "KT8900R"]:
-            # the model 2501+220 has a segment in 220
-            # and a different position in the memmap
-            # also the QYT KT8900R
-            ranges = self._memobj.ranges220
-        else:
-            ranges = self._memobj.ranges
+        def my_validate(value):
+            _vhf_lower = int(convert_bytes_to_limit(_ranges.vhf_low))
+            _vhf_upper = int(convert_bytes_to_limit(_ranges.vhf_high))
+            _uhf_lower = int(convert_bytes_to_limit(_ranges.uhf_low))
+            _uhf_upper = int(convert_bytes_to_limit(_ranges.uhf_high))
+            if self.BANDS == 3 or self.BANDS == 4:
+                _vhf2_lower = int(convert_bytes_to_limit(_ranges.vhf2_low))
+                _vhf2_upper = int(convert_bytes_to_limit(_ranges.vhf2_high))
+            if self.BANDS == 4:
+                _uhf2_lower = int(convert_bytes_to_limit(_ranges.uhf2_low))
+                _uhf2_upper = int(convert_bytes_to_limit(_ranges.uhf2_high))
 
-        # the normal dual bands
-        vhf = _decode_ranges(ranges.vhf_low, ranges.vhf_high)
-        uhf = _decode_ranges(ranges.uhf_low, ranges.uhf_high)
+            value = chirp_common.parse_freq(value)
+            msg = ("Can't be less then %i.0000")
+            if value > 99000000 and value < _vhf_lower * 1000000:
+                raise InvalidValueError(msg % (_vhf_lower))
+            msg = ("Can't be betweeb %i.9975-%i.0000")
+            if self.BANDS == 2:
+                if (_vhf_upper + 1) * 1000000 <= value and \
+                    value < _uhf_lower * 1000000:
+                    raise InvalidValueError(msg % (_vhf_upper, _uhf_lower))
+            if self.BANDS == 3:
+                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))
+            if self.BANDS == 4:
+                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 < _uhf2_lower * 1000000:
+                    raise InvalidValueError(msg % (_vhf2_upper, _uhf2_lower))
+                if (_uhf2_upper + 1) * 1000000 <= value and \
+                    value < _uhf_lower * 1000000:
+                    raise InvalidValueError(msg % (_uhf2_upper, _uhf_lower))
+            msg = ("Can't be greater then %i.9975")
+            if value > 99000000 and value >= _uhf_upper * 1000000:
+                raise InvalidValueError(msg % (_uhf_upper))
+            return chirp_common.format_freq(value)
 
-        # DEBUG
-        LOG.info("Radio ranges: VHF %d to %d" % vhf)
-        LOG.info("Radio ranges: UHF %d to %d" % uhf)
+        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
 
-        # 220Mhz radios case
-        if self.MODEL in ["UV-2501+220", "KT8900R"]:
-            vhf2 = _decode_ranges(ranges.vhf2_low, ranges.vhf2_high)
-            LOG.info("Radio ranges: VHF(220) %d to %d" % vhf2)
-            self._220_range = vhf2
+        val1a = RadioSettingValueString(0, 10, convert_bytes_to_freq(
+                                        _mem.vfo.a.freq))
+        val1a.set_validate_callback(my_validate)
+        vfoafreq = RadioSetting("vfo.a.freq", "VFO A frequency", val1a)
+        vfoafreq.set_apply_callback(apply_freq, _mem.vfo.a)
+        work.append(vfoafreq)
 
-        # set the class with the real data
-        self._vhf_range = vhf
-        self._uhf_range = uhf
+        val1b = RadioSettingValueString(0, 10, convert_bytes_to_freq(
+                                        _mem.vfo.b.freq))
+        val1b.set_validate_callback(my_validate)
+        vfobfreq = RadioSetting("vfo.b.freq", "VFO B frequency", val1b)
+        vfobfreq.set_apply_callback(apply_freq, _mem.vfo.b)
+        work.append(vfobfreq)
 
-    def process_mmap(self):
-        """Process the mem map into the mem object"""
+        if self.COLOR_LCD:
+            val1c = RadioSettingValueString(0, 10, convert_bytes_to_freq(
+                                            _mem.vfo.c.freq))
+            val1c.set_validate_callback(my_validate)
+            vfocfreq = RadioSetting("vfo.c.freq", "VFO C frequency", val1c)
+            vfocfreq.set_apply_callback(apply_freq, _mem.vfo.c)
+            work.append(vfocfreq)
+
+            val1d = RadioSettingValueString(0, 10, convert_bytes_to_freq(
+                                            _mem.vfo.d.freq))
+            val1d.set_validate_callback(my_validate)
+            vfodfreq = RadioSetting("vfo.d.freq", "VFO D frequency", val1d)
+            vfodfreq.set_apply_callback(apply_freq, _mem.vfo.d)
+            work.append(vfodfreq)
 
-        # Get it
-        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+        vfoashiftd = RadioSetting("vfo.a.shiftd", "VFO A shift",
+                                  RadioSettingValueList(
+                                      LIST_SHIFT,
+                                      LIST_SHIFT[_mem.vfo.a.shiftd]))
+        work.append(vfoashiftd)
 
-        # load specific parameters from the radio image
-        self.set_options()
+        vfobshiftd = RadioSetting("vfo.b.shiftd", "VFO B shift",
+                                  RadioSettingValueList(
+                                      LIST_SHIFT,
+                                      LIST_SHIFT[_mem.vfo.b.shiftd]))
+        work.append(vfobshiftd)
 
-    def get_raw_memory(self, number):
-        return repr(self._memobj.memory[number])
+        if self.COLOR_LCD:
+            vfocshiftd = RadioSetting("vfo.c.shiftd", "VFO C shift",
+                                      RadioSettingValueList(
+                                          LIST_SHIFT,
+                                          LIST_SHIFT[_mem.vfo.c.shiftd]))
+            work.append(vfocshiftd)
 
-    def _decode_tone(self, val):
-        """Parse the tone data to decode from mem, it returns:
-        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
-        pol = None
+            vfodshiftd = RadioSetting("vfo.d.shiftd", "VFO D shift",
+                                      RadioSettingValueList(
+                                          LIST_SHIFT,
+                                          LIST_SHIFT[_mem.vfo.d.shiftd]))
+            work.append(vfodshiftd)
 
-        if val in [0, 65535]:
-            return '', None, None
-        elif val > 0x0258:
-            a = val / 10.0
-            return 'Tone', a, pol
-        else:
-            if val > 0x69:
-                index = val - 0x6A
-                pol = "R"
-            else:
-                index = val - 1
-                pol = "N"
+        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)
 
-            tone = DTCS[index]
-            return 'DTCS', tone, pol
+        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
 
-    def _encode_tone(self, memval, mode, val, pol):
-        """Parse the tone data to encode from UI to mem"""
-        if mode == '' or mode is None:
-            memval.set_raw("\x00\x00")
-        elif mode == 'Tone':
-            memval.set_value(val * 10)
-        elif mode == 'DTCS':
-            # detect the index in the DTCS list
-            try:
-                index = DTCS.index(val)
-                if pol == "N":
-                    index += 1
-                else:
-                    index += 0x6A
-                memval.set_value(index)
-            except:
-                msg = "Digital Tone '%d' is not supported" % value
-                LOG.error(msg)
-                raise errors.RadioError(msg)
+        if self.COLOR_LCD:
+            val1a = RadioSettingValueString(0, 10, convert_bytes_to_offset(
+                                            _mem.vfo.a.offset))
+            vfoaoffset = RadioSetting("vfo.a.offset",
+                                      "VFO A offset (0.000-999.999)", val1a)
+            vfoaoffset.set_apply_callback(apply_offset, _mem.vfo.a)
+            work.append(vfoaoffset)
+
+            val1b = RadioSettingValueString(0, 10, convert_bytes_to_offset(
+                                            _mem.vfo.b.offset))
+            vfoboffset = RadioSetting("vfo.b.offset",
+                                      "VFO B offset (0.000-999.999)", val1b)
+            vfoboffset.set_apply_callback(apply_offset, _mem.vfo.b)
+            work.append(vfoboffset)
+
+            val1c = RadioSettingValueString(0, 10, convert_bytes_to_offset(
+                                            _mem.vfo.c.offset))
+            vfocoffset = RadioSetting("vfo.c.offset",
+                                      "VFO C offset (0.000-999.999)", val1c)
+            vfocoffset.set_apply_callback(apply_offset, _mem.vfo.c)
+            work.append(vfocoffset)
+
+            val1d = RadioSettingValueString(0, 10, convert_bytes_to_offset(
+                                            _mem.vfo.d.offset))
+            vfodoffset = RadioSetting("vfo.d.offset",
+                                      "VFO D offset (0.000-999.999)", val1d)
+            vfodoffset.set_apply_callback(apply_offset, _mem.vfo.d)
+            work.append(vfodoffset)
         else:
-            msg = "Internal error: invalid mode '%s'" % mode
-            LOG.error(msg)
-            raise errors.InvalidDataError(msg)
+            val1a = RadioSettingValueString(0, 10, convert_bytes_to_offset(
+                                            _mem.vfo.a.offset))
+            vfoaoffset = RadioSetting("vfo.a.offset",
+                                      "VFO A offset (0.000-99.999)", val1a)
+            vfoaoffset.set_apply_callback(apply_offset, _mem.vfo.a)
+            work.append(vfoaoffset)
 
-    def get_memory(self, number):
-        """Get the mem representation from the radio image"""
-        _mem = self._memobj.memory[number]
-        _names = self._memobj.names[number]
+            val1b = RadioSettingValueString(0, 10, convert_bytes_to_offset(
+                                            _mem.vfo.b.offset))
+            vfoboffset = RadioSetting("vfo.b.offset",
+                                      "VFO B offset (0.000-99.999)", val1b)
+            vfoboffset.set_apply_callback(apply_offset, _mem.vfo.b)
+            work.append(vfoboffset)
 
-        # Create a high-level memory object to return to the UI
-        mem = chirp_common.Memory()
 
-        # Memory number
-        mem.number = number
+        vfoatxp = RadioSetting("vfo.a.power", "VFO A power",
+                               RadioSettingValueList(
+                                   LIST_TXP,
+                                   LIST_TXP[_mem.vfo.a.power]))
+        work.append(vfoatxp)
 
-        if _mem.get_raw()[0] == "\xFF":
-            mem.empty = True
-            return mem
+        vfobtxp = RadioSetting("vfo.b.power", "VFO B power",
+                               RadioSettingValueList(
+                                   LIST_TXP,
+                                   LIST_TXP[_mem.vfo.b.power]))
+        work.append(vfobtxp)
 
-        # Freq and offset
-        mem.freq = int(_mem.rxfreq) * 10
-        # tx freq can be blank
-        if _mem.get_raw()[4] == "\xFF":
-            # TX freq not set
-            mem.offset = 0
-            mem.duplex = "off"
-        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
+        if self.COLOR_LCD:
+            vfoctxp = RadioSetting("vfo.c.power", "VFO C power",
+                                   RadioSettingValueList(
+                                       LIST_TXP,
+                                       LIST_TXP[_mem.vfo.c.power]))
+            work.append(vfoctxp)
 
-        # name TAG of the channel
-        mem.name = str(_names.name).rstrip("\xFF").replace("\xFF", " ")
+            vfodtxp = RadioSetting("vfo.d.power", "VFO D power",
+                                   RadioSettingValueList(
+                                       LIST_TXP,
+                                       LIST_TXP[_mem.vfo.d.power]))
+            work.append(vfodtxp)
 
-        # power
-        mem.power = POWER_LEVELS[int(_mem.power)]
+        vfoawide = RadioSetting("vfo.a.wide", "VFO A bandwidth",
+                                RadioSettingValueList(
+                                    LIST_WIDE,
+                                    LIST_WIDE[_mem.vfo.a.wide]))
+        work.append(vfoawide)
 
-        # wide/narrow
-        mem.mode = MODES[int(_mem.wide)]
+        vfobwide = RadioSetting("vfo.b.wide", "VFO B bandwidth",
+                                RadioSettingValueList(
+                                    LIST_WIDE,
+                                    LIST_WIDE[_mem.vfo.b.wide]))
+        work.append(vfobwide)
+
+        if self.COLOR_LCD:
+            vfocwide = RadioSetting("vfo.c.wide", "VFO C bandwidth",
+                                    RadioSettingValueList(
+                                        LIST_WIDE,
+                                        LIST_WIDE[_mem.vfo.c.wide]))
+            work.append(vfocwide)
+
+            vfodwide = RadioSetting("vfo.d.wide", "VFO D bandwidth",
+                                    RadioSettingValueList(
+                                        LIST_WIDE,
+                                        LIST_WIDE[_mem.vfo.d.wide]))
+            work.append(vfodwide)
+
+        vfoastep = RadioSetting("vfo.a.step", "VFO A step",
+                                RadioSettingValueList(
+                                    LIST_STEP,
+                                    LIST_STEP[_mem.vfo.a.step]))
+        work.append(vfoastep)
 
-        # skip
-        mem.skip = SKIP_VALUES[_mem.add]
+        vfobstep = RadioSetting("vfo.b.step", "VFO B step",
+                                RadioSettingValueList(
+                                    LIST_STEP,
+                                    LIST_STEP[_mem.vfo.b.step]))
+        work.append(vfobstep)
 
-        # tone data
-        rxtone = txtone = None
-        txtone = self._decode_tone(_mem.txtone)
-        rxtone = self._decode_tone(_mem.rxtone)
-        chirp_common.split_tone_decode(mem, txtone, rxtone)
+        if self.COLOR_LCD:
+            vfocstep = RadioSetting("vfo.c.step", "VFO C step",
+                                    RadioSettingValueList(
+                                        LIST_STEP,
+                                        LIST_STEP[_mem.vfo.c.step]))
+            work.append(vfocstep)
 
-        # Extra
-        mem.extra = RadioSettingGroup("extra", "Extra")
+            vfodstep = RadioSetting("vfo.d.step", "VFO D step",
+                                    RadioSettingValueList(
+                                        LIST_STEP,
+                                        LIST_STEP[_mem.vfo.d.step]))
+            work.append(vfodstep)
 
-        scramble = RadioSetting("scramble", "Scramble",
-                                RadioSettingValueBoolean(bool(_mem.scramble)))
-        mem.extra.append(scramble)
+        vfoaoptsig = RadioSetting("vfo.a.optsig", "VFO A optional signal",
+                                  RadioSettingValueList(
+                                      OPTSIG_LIST,
+                                      OPTSIG_LIST[_mem.vfo.a.optsig]))
+        work.append(vfoaoptsig)
 
-        bcl = RadioSetting("bcl", "Busy channel lockout",
-                           RadioSettingValueBoolean(bool(_mem.bcl)))
-        mem.extra.append(bcl)
+        vfoboptsig = RadioSetting("vfo.b.optsig", "VFO B optional signal",
+                                  RadioSettingValueList(
+                                      OPTSIG_LIST,
+                                      OPTSIG_LIST[_mem.vfo.b.optsig]))
+        work.append(vfoboptsig)
 
-        pttid = RadioSetting("pttid", "PTT ID",
-                             RadioSettingValueList(PTTID_LIST,
-                                                   PTTID_LIST[_mem.pttid]))
-        mem.extra.append(pttid)
+        if self.COLOR_LCD:
+            vfocoptsig = RadioSetting("vfo.c.optsig", "VFO C optional signal",
+                                      RadioSettingValueList(
+                                          OPTSIG_LIST,
+                                          OPTSIG_LIST[_mem.vfo.c.optsig]))
+            work.append(vfocoptsig)
 
-        # validating scode
-        scode = _mem.scode if _mem.scode != 15 else 0
-        pttidcode = RadioSetting("scode", "PTT ID signal code",
-                                 RadioSettingValueList(
-                                     PTTIDCODE_LIST,
-                                     PTTIDCODE_LIST[scode]))
-        mem.extra.append(pttidcode)
+            vfodoptsig = RadioSetting("vfo.d.optsig", "VFO D optional signal",
+                                      RadioSettingValueList(
+                                          OPTSIG_LIST,
+                                          OPTSIG_LIST[_mem.vfo.d.optsig]))
+            work.append(vfodoptsig)
 
-        optsig = RadioSetting("optsig", "Optional signaling",
-                              RadioSettingValueList(
-                                  OPTSIG_LIST,
-                                  OPTSIG_LIST[_mem.optsig]))
-        mem.extra.append(optsig)
+        vfoaspmute = RadioSetting("vfo.a.spmute", "VFO A speaker mute",
+                                  RadioSettingValueList(
+                                      SPMUTE_LIST,
+                                      SPMUTE_LIST[_mem.vfo.a.spmute]))
+        work.append(vfoaspmute)
 
-        spmute = RadioSetting("spmute", "Speaker mute",
-                              RadioSettingValueList(
-                                  SPMUTE_LIST,
-                                  SPMUTE_LIST[_mem.spmute]))
-        mem.extra.append(spmute)
+        vfobspmute = RadioSetting("vfo.b.spmute", "VFO B speaker mute",
+                                  RadioSettingValueList(
+                                      SPMUTE_LIST,
+                                      SPMUTE_LIST[_mem.vfo.b.spmute]))
+        work.append(vfobspmute)
 
-        return mem
+        if self.COLOR_LCD:
+            vfocspmute = RadioSetting("vfo.c.spmute", "VFO C speaker mute",
+                                      RadioSettingValueList(
+                                          SPMUTE_LIST,
+                                          SPMUTE_LIST[_mem.vfo.c.spmute]))
+            work.append(vfocspmute)
+
+            vfodspmute = RadioSetting("vfo.d.spmute", "VFO D speaker mute",
+                                      RadioSettingValueList(
+                                          SPMUTE_LIST,
+                                          SPMUTE_LIST[_mem.vfo.d.spmute]))
+            work.append(vfodspmute)
+
+        if not self.COLOR_LCD or \
+            (self.COLOR_LCD and not self.VENDOR == "BTECH"):
+            vfoascr = RadioSetting("vfo.a.scramble", "VFO A scramble",
+                                   RadioSettingValueBoolean(
+                                       _mem.vfo.a.scramble))
+            work.append(vfoascr)
 
-    def set_memory(self, mem):
-        """Set the memory data in the eeprom img from the UI"""
-        # get the eprom representation of this channel
-        _mem = self._memobj.memory[mem.number]
-        _names = self._memobj.names[mem.number]
+            vfobscr = RadioSetting("vfo.b.scramble", "VFO B scramble",
+                                   RadioSettingValueBoolean(
+                                       _mem.vfo.b.scramble))
+            work.append(vfobscr)
 
-        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
-            _mem.set_raw("\xFF" * 16)
-            # the name tag
-            _names.set_raw("\xFF" * 16)
-            return
+        if self.COLOR_LCD and not self.VENDOR == "BTECH":
+            vfocscr = RadioSetting("vfo.c.scramble", "VFO C scramble",
+                                   RadioSettingValueBoolean(
+                                       _mem.vfo.c.scramble))
+            work.append(vfocscr)
 
-        # frequency
-        _mem.rxfreq = mem.freq / 10
+            vfodscr = RadioSetting("vfo.d.scramble", "VFO D scramble",
+                                   RadioSettingValueBoolean(
+                                       _mem.vfo.d.scramble))
+            work.append(vfodscr)
 
-        # duplex
-        if mem.duplex == "+":
-            _mem.txfreq = (mem.freq + mem.offset) / 10
-        elif mem.duplex == "-":
-            _mem.txfreq = (mem.freq - mem.offset) / 10
-        elif mem.duplex == "off":
-            for i in _mem.txfreq:
-                i.set_raw("\xFF")
-        elif mem.duplex == "split":
-            _mem.txfreq = mem.offset / 10
-        else:
-            _mem.txfreq = mem.freq / 10
+        vfoascode = RadioSetting("vfo.a.scode", "VFO A PTT-ID",
+                                 RadioSettingValueList(
+                                     PTTIDCODE_LIST,
+                                     PTTIDCODE_LIST[_mem.vfo.a.scode]))
+        work.append(vfoascode)
 
-        # tone data
-        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
-            chirp_common.split_tone_encode(mem)
-        self._encode_tone(_mem.txtone, txmode, txtone, txpol)
-        self._encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
+        vfobscode = RadioSetting("vfo.b.scode", "VFO B PTT-ID",
+                                 RadioSettingValueList(
+                                     PTTIDCODE_LIST,
+                                     PTTIDCODE_LIST[_mem.vfo.b.scode]))
+        work.append(vfobscode)
 
-        # name TAG of the channel
-        if len(mem.name) < NAME_LENGTH:
-            # we must pad to NAME_LENGTH chars, " " = "\xFF"
-            mem.name = str(mem.name).ljust(NAME_LENGTH, " ")
-        _names.name = str(mem.name).replace(" ", "\xFF")
+        if self.COLOR_LCD:
+            vfocscode = RadioSetting("vfo.c.scode", "VFO C PTT-ID",
+                                     RadioSettingValueList(
+                                         PTTIDCODE_LIST,
+                                         PTTIDCODE_LIST[_mem.vfo.c.scode]))
+            work.append(vfocscode)
 
-        # power, # default power level is high
-        _mem.power = 0 if mem.power is None else POWER_LEVELS.index(mem.power)
+            vfodscode = RadioSetting("vfo.d.scode", "VFO D PTT-ID",
+                                     RadioSettingValueList(
+                                         PTTIDCODE_LIST,
+                                         PTTIDCODE_LIST[_mem.vfo.d.scode]))
+            work.append(vfodscode)
 
-        # wide/narrow
-        _mem.wide = MODES.index(mem.mode)
+        pttid = RadioSetting("settings.pttid", "PTT ID",
+                             RadioSettingValueList(
+                                 PTTID_LIST,
+                                 PTTID_LIST[_mem.settings.pttid]))
+        work.append(pttid)
 
-        # scan add property
-        _mem.add = SKIP_VALUES.index(mem.skip)
+        if not self.COLOR_LCD:
+            #FM presets
+            fm_presets = RadioSettingGroup("fm_presets", "FM Presets")
+            top.append(fm_presets)
 
-        # reseting unknowns, this have to be set by hand
-        _mem.unknown0 = 0
-        _mem.unknown1 = 0
-        _mem.unknown2 = 0
-        _mem.unknown3 = 0
-        _mem.unknown4 = 0
-        _mem.unknown5 = 0
-        _mem.unknown6 = 0
+            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
 
-        # 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:
-            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
+            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'
 
-        return mem
+            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
 
-    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_presets = RadioSettingGroup("fm_presets", "FM Presets")
-        top = RadioSettings(basic, advanced, other, work, fm_presets)
+         # DTMF-Setting
+        dtmf_enc_settings = RadioSettingGroup ("dtmf_enc_settings",
+                                               "DTMF Encoding Settings")
+        dtmf_dec_settings = RadioSettingGroup ("dtmf_dec_settings",
+                                               "DTMF Decoding Settings")
+        top.append(dtmf_enc_settings)
+        top.append(dtmf_dec_settings)
+        txdisable = RadioSetting("dtmf_settings.txdisable", 
+                                 "TX-Disable",
+                                 RadioSettingValueBoolean(
+                                     _mem.dtmf_settings.txdisable))
+        dtmf_enc_settings.append(txdisable)
 
-        # Basic
-        tdr = RadioSetting("settings.tdr", "Transceiver dual receive",
-                           RadioSettingValueBoolean(_mem.settings.tdr))
-        basic.append(tdr)
+        rxdisable = RadioSetting("dtmf_settings.rxdisable", 
+                                 "RX-Disable",
+                                 RadioSettingValueBoolean(
+                                     _mem.dtmf_settings.rxdisable))
+        dtmf_enc_settings.append(rxdisable)
 
-        sql = RadioSetting("settings.sql", "Squelch level",
-                           RadioSettingValueInteger(0, 9, _mem.settings.sql))
-        basic.append(sql)
+        dtmfspeed_on = RadioSetting(
+            "dtmf_settings.dtmfspeed_on",
+            "DTMF Speed (On Time)",
+            RadioSettingValueList(LIST_DTMF_SPEED,
+                                  LIST_DTMF_SPEED[
+                                      _mem.dtmf_settings.dtmfspeed_on]))
+        dtmf_enc_settings.append(dtmfspeed_on)
 
-        tot = RadioSetting("settings.tot", "Time out timer",
-                           RadioSettingValueList(LIST_TOT, LIST_TOT[
-                               _mem.settings.tot]))
-        basic.append(tot)
+        dtmfspeed_off = RadioSetting(
+            "dtmf_settings.dtmfspeed_off",
+            "DTMF Speed (Off Time)",
+            RadioSettingValueList(LIST_DTMF_SPEED,
+                                  LIST_DTMF_SPEED[
+                                      _mem.dtmf_settings.dtmfspeed_off]))
+        dtmf_enc_settings.append(dtmfspeed_off)
 
-        if self.MODEL in ("UV-2501", "UV-2501+220", "UV-5001"):
-            apo = RadioSetting("settings.apo", "Auto power off timer",
-                               RadioSettingValueList(LIST_APO, LIST_APO[
-                                   _mem.settings.apo]))
-            basic.append(apo)
-        else:
-            toa = RadioSetting("settings.apo", "Time out alert timer",
-                               RadioSettingValueList(LIST_TOA, LIST_TOA[
-                                   _mem.settings.apo]))
-            basic.append(toa)
+        def memory2string(dmtf_mem):
+            dtmf_string = ""
+            for digit in dmtf_mem:
+                if digit != 255:
+                    index = LIST_DTMF_VALUES.index(digit)
+                    dtmf_string = dtmf_string + LIST_DTMF_DIGITS[index]
+            return dtmf_string
 
-        abr = RadioSetting("settings.abr", "Backlight timer",
-                           RadioSettingValueList(LIST_ABR, LIST_ABR[
-                               _mem.settings.abr]))
-        basic.append(abr)
+        def apply_dmtf_frame(setting, obj):
+            LOG.debug("Setting DTMF-Code: " + str(setting.value) )
+            val_string = str(setting.value)
+            for i in range(0,16):
+                obj[i] = 255
+            i = 0
+            for current_char in val_string:
+                current_char = current_char.upper()
+                index = LIST_DTMF_DIGITS.index(current_char)
+                obj[i] = LIST_DTMF_VALUES[ index ]
+                i = i + 1
 
-        beep = RadioSetting("settings.beep", "Key beep",
-                            RadioSettingValueBoolean(_mem.settings.beep))
-        basic.append(beep)
+        codes = self._memobj.dtmf_codes
+        i = 1
+        for dtmfcode in codes:
+            val = RadioSettingValueString(0, 16, 
+                                          memory2string(dtmfcode.code),
+                                          False, CHARSET_DTMF_DIGITS)
+            line = RadioSetting("dtmf_code_" + str(i) + "_code",
+                                "DMTF Code " + str(i), val)
+            line.set_apply_callback(apply_dmtf_frame, dtmfcode.code)
+            dtmf_enc_settings.append(line)
+            i = i + 1
 
-        dtmfst = RadioSetting("settings.dtmfst", "DTMF side tone",
-                              RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
-                                  _mem.settings.dtmfst]))
-        basic.append(dtmfst)
+        line = RadioSetting("dtmf_settings.mastervice", 
+                            "Master and Vice ID",
+                            RadioSettingValueBoolean(
+                                _mem.dtmf_settings.mastervice))
+        dtmf_dec_settings.append(line)
 
-        prisc = RadioSetting("settings.prisc", "Priority scan",
-                             RadioSettingValueBoolean(_mem.settings.prisc))
-        basic.append(prisc)
+        val = RadioSettingValueString(0, 16, 
+                                      memory2string(
+                                          _mem.dtmf_settings.masterid),
+                                          False, CHARSET_DTMF_DIGITS)
+        line = RadioSetting("dtmf_settings.masterid",
+                            "Master Control ID ", val)
+        line.set_apply_callback(apply_dmtf_frame,
+                                _mem.dtmf_settings.masterid)
+        dtmf_dec_settings.append(line)
 
-        prich = RadioSetting("settings.prich", "Priority channel",
-                             RadioSettingValueInteger(0, 199,
-                                 _mem.settings.prich))
-        basic.append(prich)
+        line = RadioSetting("dtmf_settings.minspection", 
+                            "Master Inspection",
+                            RadioSettingValueBoolean(
+                                _mem.dtmf_settings.minspection))
+        dtmf_dec_settings.append(line)
 
-        screv = RadioSetting("settings.screv", "Scan resume method",
-                             RadioSettingValueList(LIST_SCREV, LIST_SCREV[
-                                 _mem.settings.screv]))
-        basic.append(screv)
+        line = RadioSetting("dtmf_settings.mmonitor", 
+                            "Master Monitor",
+                            RadioSettingValueBoolean(
+                                _mem.dtmf_settings.mmonitor))
+        dtmf_dec_settings.append(line)
 
-        pttlt = RadioSetting("settings.pttlt", "PTT transmit delay",
-                             RadioSettingValueInteger(0, 30,
-                                 _mem.settings.pttlt))
-        basic.append(pttlt)
+        line = RadioSetting("dtmf_settings.mstun", 
+                            "Master Stun",
+                            RadioSettingValueBoolean(
+                                _mem.dtmf_settings.mstun))
+        dtmf_dec_settings.append(line)
 
-        emctp = RadioSetting("settings.emctp", "Alarm mode",
-                             RadioSettingValueList(LIST_EMCTP, LIST_EMCTP[
-                                 _mem.settings.emctp]))
-        basic.append(emctp)
+        line = RadioSetting("dtmf_settings.mkill", 
+                            "Master Kill",
+                            RadioSettingValueBoolean(
+                                _mem.dtmf_settings.mkill))
+        dtmf_dec_settings.append(line)
 
-        emcch = RadioSetting("settings.emcch", "Alarm channel",
-                             RadioSettingValueInteger(0, 199,
-                                 _mem.settings.emcch))
-        basic.append(emcch)
+        line = RadioSetting("dtmf_settings.mrevive", 
+                            "Master Revive",
+                            RadioSettingValueBoolean(
+                                _mem.dtmf_settings.mrevive))
+        dtmf_dec_settings.append(line)
 
-        ringt = RadioSetting("settings.ringt", "Ring time",
-                             RadioSettingValueList(LIST_RINGT, LIST_RINGT[
-                                 _mem.settings.ringt]))
-        basic.append(ringt)
+        val = RadioSettingValueString(0, 16, 
+                                      memory2string(
+                                          _mem.dtmf_settings.viceid),
+                                          False, CHARSET_DTMF_DIGITS)
+        line = RadioSetting("dtmf_settings.viceid",
+                            "Vice Control ID ", val)
+        line.set_apply_callback(apply_dmtf_frame,
+                                _mem.dtmf_settings.viceid)
+        dtmf_dec_settings.append(line)
 
-        camdf = RadioSetting("settings.camdf", "Display mode A",
-                             RadioSettingValueList(LIST_MDF, LIST_MDF[
-                                 _mem.settings.camdf]))
-        basic.append(camdf)
+        line = RadioSetting("dtmf_settings.vinspection", 
+                            "Vice Inspection",
+                            RadioSettingValueBoolean(
+                                _mem.dtmf_settings.vinspection))
+        dtmf_dec_settings.append(line)
 
-        cbmdf = RadioSetting("settings.cbmdf", "Display mode B",
-                             RadioSettingValueList(LIST_MDF, LIST_MDF[
-                                 _mem.settings.cbmdf]))
-        basic.append(cbmdf)
+        line = RadioSetting("dtmf_settings.vmonitor", 
+                            "Vice Monitor",
+                            RadioSettingValueBoolean(
+                                _mem.dtmf_settings.vmonitor))
+        dtmf_dec_settings.append(line)
 
-        if self.MODEL in ("UV-2501", "UV-2501+220", "UV-5001"):
-           sync = RadioSetting("settings.sync", "A/B channel sync",
-                               RadioSettingValueBoolean(_mem.settings.sync))
-           basic.append(sync)
-        else:
-           autolk = RadioSetting("settings.sync", "Auto keylock",
-                                 RadioSettingValueBoolean(_mem.settings.sync))
-           basic.append(autolk)
-
-        ponmsg = RadioSetting("settings.ponmsg", "Power-on message",
-                              RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
-                                  _mem.settings.ponmsg]))
-        basic.append(ponmsg)
-
-        wtled = RadioSetting("settings.wtled", "Standby backlight Color",
-                             RadioSettingValueList(LIST_COLOR, LIST_COLOR[
-                                 _mem.settings.wtled]))
-        basic.append(wtled)
-
-        rxled = RadioSetting("settings.rxled", "RX backlight Color",
-                             RadioSettingValueList(LIST_COLOR, LIST_COLOR[
-                                 _mem.settings.rxled]))
-        basic.append(rxled)
-
-        txled = RadioSetting("settings.txled", "TX backlight Color",
-                             RadioSettingValueList(LIST_COLOR, LIST_COLOR[
-                                 _mem.settings.txled]))
-        basic.append(txled)
+        line = RadioSetting("dtmf_settings.vstun", 
+                            "Vice Stun",
+                            RadioSettingValueBoolean(
+                                _mem.dtmf_settings.vstun))
+        dtmf_dec_settings.append(line)
 
-        anil = RadioSetting("settings.anil", "ANI length",
-                            RadioSettingValueList(LIST_ANIL, LIST_ANIL[
-                                _mem.settings.anil]))
-        basic.append(anil)
+        line = RadioSetting("dtmf_settings.vkill", 
+                            "Vice Kill",
+                            RadioSettingValueBoolean(
+                                _mem.dtmf_settings.vkill))
+        dtmf_dec_settings.append(line)
 
-        reps = RadioSetting("settings.reps", "Relay signal (tone burst)",
-                            RadioSettingValueList(LIST_REPS, LIST_REPS[
-                                _mem.settings.reps]))
-        basic.append(reps)
+        line = RadioSetting("dtmf_settings.vrevive", 
+                            "Vice Revive",
+                            RadioSettingValueBoolean(
+                                _mem.dtmf_settings.vrevive))
+        dtmf_dec_settings.append(line)
 
-        repm = RadioSetting("settings.repm", "Relay condition",
-                            RadioSettingValueList(LIST_REPM, LIST_REPM[
-                                _mem.settings.repm]))
-        basic.append(repm)
+        val = RadioSettingValueString(0, 16, 
+                                      memory2string(
+                                          _mem.dtmf_settings.inspection),
+                                          False, CHARSET_DTMF_DIGITS)
+        line = RadioSetting("dtmf_settings.inspection",
+                            "Inspection", val)
+        line.set_apply_callback(apply_dmtf_frame,
+                                _mem.dtmf_settings.inspection)
+        dtmf_dec_settings.append(line)
 
-        if self.MODEL in ("UV-2501", "UV-2501+220", "UV-5001"):
-            tdrab = RadioSetting("settings.tdrab", "TDR return time",
-                                 RadioSettingValueList(LIST_ABR, LIST_ABR[
-                                     _mem.settings.tdrab]))
-            basic.append(tdrab)
+        val = RadioSettingValueString(0, 16, 
+                                      memory2string(
+                                          _mem.dtmf_settings.alarmcode),
+                                          False, CHARSET_DTMF_DIGITS)
+        line = RadioSetting("dtmf_settings.alarmcode",
+                            "Alarm", val)
+        line.set_apply_callback(apply_dmtf_frame,
+                                _mem.dtmf_settings.alarmcode)
+        dtmf_dec_settings.append(line)
 
-            ste = RadioSetting("settings.ste", "Squelch tail eliminate",
-                               RadioSettingValueBoolean(_mem.settings.ste))
-            basic.append(ste)
+        val = RadioSettingValueString(0, 16, 
+                                      memory2string(
+                                          _mem.dtmf_settings.kill),
+                                          False, CHARSET_DTMF_DIGITS)
+        line = RadioSetting("dtmf_settings.kill",
+                            "Kill", val)
+        line.set_apply_callback(apply_dmtf_frame,
+                                _mem.dtmf_settings.kill)
+        dtmf_dec_settings.append(line)
 
-            rpste = RadioSetting("settings.rpste", "Repeater STE",
-                                 RadioSettingValueList(LIST_RINGT, LIST_RINGT[
-                                     _mem.settings.rpste]))
-            basic.append(rpste)
+        val = RadioSettingValueString(0, 16, 
+                                      memory2string(
+                                          _mem.dtmf_settings.monitor),
+                                          False, CHARSET_DTMF_DIGITS)
+        line = RadioSetting("dtmf_settings.monitor",
+                            "Monitor", val)
+        line.set_apply_callback(apply_dmtf_frame,
+                                _mem.dtmf_settings.monitor)
+        dtmf_dec_settings.append(line)
 
-            rptdl = RadioSetting("settings.rptdl", "Repeater STE delay",
-                                 RadioSettingValueList(LIST_RPTDL, LIST_RPTDL[
-                                     _mem.settings.rptdl]))
-            basic.append(rptdl)
+        val = RadioSettingValueString(0, 16, 
+                                      memory2string(
+                                          _mem.dtmf_settings.stun),
+                                          False, CHARSET_DTMF_DIGITS)
+        line = RadioSetting("dtmf_settings.stun",
+                            "Stun", val)
+        line.set_apply_callback(apply_dmtf_frame,
+                                _mem.dtmf_settings.stun)
+        dtmf_dec_settings.append(line)
 
-        if str(_mem.fingerprint.fp) in BTECH3:
+        val = RadioSettingValueString(0, 16, 
+                                      memory2string(
+                                          _mem.dtmf_settings.revive),
+                                          False, CHARSET_DTMF_DIGITS)
+        line = RadioSetting("dtmf_settings.revive",
+                            "Revive", val)
+        line.set_apply_callback(apply_dmtf_frame,
+                                _mem.dtmf_settings.revive)
+        dtmf_dec_settings.append(line)
 
-            mgain = RadioSetting("settings.mgain", "Mic gain",
-                                 RadioSettingValueInteger(0, 120,
-                                     _mem.settings.mgain))
-            basic.append(mgain)
+        def apply_dmtf_listvalue(setting, obj):
+            LOG.debug("Setting value: "+ str(setting.value) + " from list")
+            val = str(setting.value)
+            index = LIST_DTMF_SPECIAL_DIGITS.index(val)
+            val = LIST_DTMF_SPECIAL_VALUES[index]
+            obj.set_value(val)
 
-            dtmfg = RadioSetting("settings.dtmfg", "DTMF gain",
-                                 RadioSettingValueInteger(0, 60,
-                                     _mem.settings.dtmfg))
-            basic.append(dtmfg)
+        idx = LIST_DTMF_SPECIAL_VALUES.index(_mem.dtmf_settings.groupcode)
+        line = RadioSetting(
+            "dtmf_settings.groupcode",
+            "Group Code",
+            RadioSettingValueList(LIST_DTMF_SPECIAL_DIGITS,
+                                  LIST_DTMF_SPECIAL_DIGITS[idx]))
+        line.set_apply_callback(apply_dmtf_listvalue,
+                                _mem.dtmf_settings.groupcode)
+        dtmf_dec_settings.append(line)
 
-        # Advanced
-        def _filter(name):
-            filtered = ""
-            for char in str(name):
-                if char in VALID_CHARS:
-                    filtered += char
-                else:
-                    filtered += " "
-            return filtered
+        idx = LIST_DTMF_SPECIAL_VALUES.index(_mem.dtmf_settings.spacecode)
+        line = RadioSetting(
+            "dtmf_settings.spacecode",
+            "Space Code",
+            RadioSettingValueList(LIST_DTMF_SPECIAL_DIGITS,
+                                  LIST_DTMF_SPECIAL_DIGITS[idx]))
+        line.set_apply_callback(apply_dmtf_listvalue,
+                                _mem.dtmf_settings.spacecode)
+        dtmf_dec_settings.append(line)
 
-        _msg = self._memobj.poweron_msg
-        line1 = RadioSetting("poweron_msg.line1", "Power-on message line 1",
-                             RadioSettingValueString(0, 6, _filter(
-                                 _msg.line1)))
-        advanced.append(line1)
-        line2 = RadioSetting("poweron_msg.line2", "Power-on message line 2",
-                             RadioSettingValueString(0, 6, _filter(
-                                 _msg.line2)))
-        advanced.append(line2)
+        if self.COLOR_LCD:
+            line = RadioSetting(
+                "dtmf_settings.resettime",
+                "Reset time",
+                RadioSettingValueList(LIST_5TONE_RESET_COLOR,
+                                      LIST_5TONE_RESET_COLOR[
+                                          _mem.dtmf_settings.resettime]))
+            dtmf_dec_settings.append(line)
+        else:
+            line = RadioSetting(
+                "dtmf_settings.resettime",
+                "Reset time",
+                RadioSettingValueList(LIST_5TONE_RESET,
+                                      LIST_5TONE_RESET[
+                                          _mem.dtmf_settings.resettime]))
+            dtmf_dec_settings.append(line)
 
-        if self.MODEL in ("UV-2501", "UV-5001"):
-            vfomren = RadioSetting("settings2.vfomren", "VFO/MR switching",
-                                   RadioSettingValueBoolean(
-                                       _mem.settings2.vfomren))
-            advanced.append(vfomren)
+        line = RadioSetting(
+            "dtmf_settings.delayproctime",
+            "Delay processing time",
+            RadioSettingValueList(LIST_DTMF_DELAY,
+                                  LIST_DTMF_DELAY[
+                                      _mem.dtmf_settings.delayproctime]))
+        dtmf_dec_settings.append(line)
 
-            reseten = RadioSetting("settings2.reseten", "RESET",
-                                   RadioSettingValueBoolean(
-                                       _mem.settings2.reseten))
-            advanced.append(reseten)
 
-            menuen = RadioSetting("settings2.menuen", "Menu",
-                                  RadioSettingValueBoolean(
-                                      _mem.settings2.menuen))
-            advanced.append(menuen)
+        # 5 Tone Settings
+        stds_5tone = RadioSettingGroup ("stds_5tone", "Standards")
+        codes_5tone = RadioSettingGroup ("codes_5tone", "Codes")
 
-        # Other
-        def convert_bytes_to_limit(bytes):
-            limit = ""
-            for byte in bytes:
-                if byte < 10:
-                    limit += chr(byte + 0x30)
-                else:
-                    break
-            return limit
+        group_5tone = RadioSettingGroup ("group_5tone", "5 Tone Settings")
+        group_5tone.append(stds_5tone)
+        group_5tone.append(codes_5tone)
 
-        if self.MODEL in ["UV-2501+220", "KT8900R"]:
-            _ranges = self._memobj.ranges220
-            ranges = "ranges220"
-        else:
-            _ranges = self._memobj.ranges
-            ranges = "ranges"
+        top.append(group_5tone)
 
-        _limit = convert_bytes_to_limit(_ranges.vhf_low)
-        val = RadioSettingValueString(0, 3, _limit)
-        val.set_mutable(False)
-        vhf_low = RadioSetting("%s.vhf_low" % ranges, "VHF low", val)
-        other.append(vhf_low)
+        def apply_list_value(setting, obj):
+            options = setting.value.get_options()
+            obj.set_value ( options.index(str(setting.value)) )
 
-        _limit = convert_bytes_to_limit(_ranges.vhf_high)
-        val = RadioSettingValueString(0, 3, _limit)
-        val.set_mutable(False)
-        vhf_high = RadioSetting("%s.vhf_high" % ranges, "VHF high", val)
-        other.append(vhf_high)
+        _5tone_standards = self._memobj._5tone_std_settings
+        i = 0
+        for standard in _5tone_standards:
+            std_5tone = RadioSettingGroup ("std_5tone_" + str(i), 
+                                           LIST_5TONE_STANDARDS[i])
+            stds_5tone.append(std_5tone)
+ 
+            period = standard.period
+            if period == 255:
+                LOG.debug("Period for " + LIST_5TONE_STANDARDS[i] + 
+                          " is not yet configured. Setting to 70ms.")
+                period = 5
 
-        if self.MODEL in ["UV-2501+220", "KT8900R"]:
-            _limit = convert_bytes_to_limit(_ranges.vhf2_low)
-            val = RadioSettingValueString(0, 3, _limit)
-            val.set_mutable(False)
-            vhf2_low = RadioSetting("%s.vhf2_low" % ranges, "VHF2 low", val)
-            other.append(vhf2_low)
+            if period <= len( LIST_5TONE_STANDARD_PERIODS ):
+                line = RadioSetting(
+                    "_5tone_std_settings_" + str(i) + "_period",
+                    "Period (ms)", RadioSettingValueList
+                    (LIST_5TONE_STANDARD_PERIODS,
+                     LIST_5TONE_STANDARD_PERIODS[period]))
+                line.set_apply_callback(apply_list_value, standard.period)
+                std_5tone.append(line)
+            else:
+                LOG.debug("Invalid value for 5tone period! Disabling.")
 
-            _limit = convert_bytes_to_limit(_ranges.vhf2_high)
-            val = RadioSettingValueString(0, 3, _limit)
-            val.set_mutable(False)
-            vhf2_high = RadioSetting("%s.vhf2_high" % ranges, "VHF2 high", val)
-            other.append(vhf2_high)
+            group_tone = standard.group_tone
+            if group_tone == 255:
+                LOG.debug("Group-Tone for " + LIST_5TONE_STANDARDS[i] +
+                          " is not yet configured. Setting to A.")
+                group_tone = 10
 
-        _limit = convert_bytes_to_limit(_ranges.uhf_low)
-        val = RadioSettingValueString(0, 3, _limit)
-        val.set_mutable(False)
-        uhf_low = RadioSetting("%s.uhf_low" % ranges, "UHF low", val)
-        other.append(uhf_low)
+            if group_tone <= len( LIST_5TONE_DIGITS ):
+                line = RadioSetting(
+                    "_5tone_std_settings_" + str(i) + "_grouptone",
+                    "Group Tone",
+                    RadioSettingValueList(LIST_5TONE_DIGITS,
+                                          LIST_5TONE_DIGITS[
+                                              group_tone]))
+                line.set_apply_callback(apply_list_value,
+                                        standard.group_tone)
+                std_5tone.append(line)
+            else:
+                LOG.debug("Invalid value for 5tone digit! Disabling.")
 
-        _limit = convert_bytes_to_limit(_ranges.uhf_high)
-        val = RadioSettingValueString(0, 3, _limit)
-        val.set_mutable(False)
-        uhf_high = RadioSetting("%s.uhf_high" % ranges, "UHF high", val)
-        other.append(uhf_high)
+            repeat_tone = standard.repeat_tone
+            if repeat_tone == 255:
+                LOG.debug("Repeat-Tone for " + LIST_5TONE_STANDARDS[i] + 
+                          " is not yet configured. Setting to E.")
+                repeat_tone = 14
 
-        val = RadioSettingValueString(0, 6, _filter(_mem.fingerprint.fp))
-        val.set_mutable(False)
-        fp = RadioSetting("fingerprint.fp", "Fingerprint", val)
-        other.append(fp)
+            if repeat_tone <= len( LIST_5TONE_DIGITS ):
+                line = RadioSetting(
+                    "_5tone_std_settings_" + str(i) + "_repttone",
+                    "Repeat Tone",
+                    RadioSettingValueList(LIST_5TONE_DIGITS,
+                                          LIST_5TONE_DIGITS[
+                                              repeat_tone]))
+                line.set_apply_callback(apply_list_value,
+                                        standard.repeat_tone)
+                std_5tone.append(line)
+            else:
+                LOG.debug("Invalid value for 5tone digit! Disabling.")
+            i = i + 1
 
-        # Work
-        dispab = RadioSetting("settings2.dispab", "Display",
-                              RadioSettingValueList(LIST_AB,LIST_AB[
-                                  _mem.settings2.dispab]))
-        work.append(dispab)
+        def my_apply_5tonestdlist_value(setting, obj):
+            if LIST_5TONE_STANDARDS.index(str(setting.value)) == 15:
+                obj.set_value(0xFF)
+            else:
+                obj.set_value( LIST_5TONE_STANDARDS.
+                              index(str(setting.value)) )
 
-        vfomr = RadioSetting("settings2.vfomr", "VFO/MR mode",
-                             RadioSettingValueList(LIST_VFOMR,LIST_VFOMR[
-                                 _mem.settings2.vfomr]))
-        work.append(vfomr)
+        def apply_5tone_frame(setting, obj):
+            LOG.debug("Setting 5 Tone: " + str(setting.value) )
+            valstring = str(setting.value)
+            if len(valstring) == 0:
+                for i in range(0,5):
+                    obj[i] = 255
+            else:
+                validFrame = True
+                for i in range(0,5):
+                    currentChar = valstring[i].upper()
+                    if currentChar in LIST_5TONE_DIGITS:
+                        obj[i] = LIST_5TONE_DIGITS.index(currentChar)
+                    else:
+                        validFrame = False
+                        LOG.debug("invalid char: " + str(currentChar))
+                if not validFrame:
+                    LOG.debug("setting whole frame to FF" )
+                    for i in range(0,5):
+                        obj[i] = 255
 
-        keylock = RadioSetting("settings2.keylock", "Keypad lock",
-                           RadioSettingValueBoolean(_mem.settings2.keylock))
-        work.append(keylock)
+        def validate_5tone_frame(value):
+            if (len(str(value)) != 5) and (len(str(value)) != 0) :
+                msg = ("5 Tone must have 5 digits or 0 digits")
+                raise InvalidValueError(msg)
+            for digit in str(value):
+                if digit.upper() not in LIST_5TONE_DIGITS:
+                    msg = (str(digit) + " is not a valid digit for 5tones")
+                    raise InvalidValueError(msg)
+            return value
 
-        mrcha = RadioSetting("settings2.mrcha", "MR A channel",
-                             RadioSettingValueInteger(0, 199,
-                                 _mem.settings2.mrcha))
-        work.append(mrcha)
+        def frame2string(frame):
+            frameString = ""
+            for digit in frame:
+                if digit != 255:
+                    frameString = frameString + LIST_5TONE_DIGITS[digit]
+            return frameString
 
-        mrchb = RadioSetting("settings2.mrchb", "MR B channel",
-                             RadioSettingValueInteger(0, 199,
-                                 _mem.settings2.mrchb))
-        work.append(mrchb)
+        _5tone_codes = self._memobj._5tone_codes
+        i = 1
+        for code in _5tone_codes:
+            code_5tone = RadioSettingGroup ("code_5tone_" + str(i),
+                                            "5 Tone code " + str(i))
+            codes_5tone.append(code_5tone)
+            if (code.standard == 255 ):
+                currentVal = 15
+            else:
+                currentVal = code.standard
+            line = RadioSetting("_5tone_code_" + str(i) + "_std", 
+                                " Standard",
+                                RadioSettingValueList(LIST_5TONE_STANDARDS,
+                                                      LIST_5TONE_STANDARDS[
+                                                          currentVal]) )
+            line.set_apply_callback(my_apply_5tonestdlist_value,
+                                    code.standard)
+            code_5tone.append(line)
 
-        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)
+            val = RadioSettingValueString(0, 6,
+                                          frame2string(code.frame1), False)
+            line = RadioSetting("_5tone_code_" + str(i) + "_frame1", 
+                                " Frame 1", val)
+            val.set_validate_callback(validate_5tone_frame)
+            line.set_apply_callback(apply_5tone_frame, code.frame1)
+            code_5tone.append(line)
 
-        def my_validate(value):
-            value = chirp_common.parse_freq(value)
-            if "+220" in self.MODEL:
-                if 180000000 <= value and value < 210000000:
-                    msg = ("Can't be between 180.00000-210.00000")
-                    raise InvalidValueError(msg)
-                elif 231000000 <= value and value < 400000000:
-                    msg = ("Can't be between 231.00000-400.00000")
-                    raise InvalidValueError(msg)
-            elif "8900R" in self.MODEL:
-                if 180000000 <= value and value < 240000000:
-                    msg = ("Can't be between 180.00000-240.00000")
-                    raise InvalidValueError(msg)
-                elif 271000000 <= value and value < 400000000:
-                    msg = ("Can't be between 271.00000-400.00000")
-                    raise InvalidValueError(msg)
-            elif 180000000 <= value and value < 400000000:
-                msg = ("Can't be between 180.00000-400.00000")
-                raise InvalidValueError(msg)
-            return chirp_common.format_freq(value)
+            val = RadioSettingValueString(0, 6,
+                                          frame2string(code.frame2), False)
+            line = RadioSetting("_5tone_code_" + str(i) + "_frame2",
+                                " Frame 2", val)
+            val.set_validate_callback(validate_5tone_frame)
+            line.set_apply_callback(apply_5tone_frame, code.frame2)
+            code_5tone.append(line)
 
-        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
+            val = RadioSettingValueString(0, 6,
+                                          frame2string(code.frame3), False)
+            line = RadioSetting("_5tone_code_" + str(i) + "_frame3",
+                                " Frame 3", val)
+            val.set_validate_callback(validate_5tone_frame)
+            line.set_apply_callback(apply_5tone_frame, code.frame3)
+            code_5tone.append(line)
+            i = i + 1
 
-        val1a = RadioSettingValueString(0, 10, convert_bytes_to_freq(
-                                        _mem.vfo.a.freq))
-        val1a.set_validate_callback(my_validate)
-        vfoafreq = RadioSetting("vfo.a.freq", "VFO A frequency", val1a)
-        vfoafreq.set_apply_callback(apply_freq, _mem.vfo.a)
-        work.append(vfoafreq)
+        _5_tone_decode1 = RadioSetting(
+            "_5tone_settings._5tone_decode_call_frame1",
+            "5 Tone decode call Frame 1",
+            RadioSettingValueBoolean(
+                _mem._5tone_settings._5tone_decode_call_frame1))
+        group_5tone.append(_5_tone_decode1)
 
-        val1b = RadioSettingValueString(0, 10, convert_bytes_to_freq(
-                                        _mem.vfo.b.freq))
-        val1b.set_validate_callback(my_validate)
-        vfobfreq = RadioSetting("vfo.b.freq", "VFO B frequency", val1b)
-        vfobfreq.set_apply_callback(apply_freq, _mem.vfo.b)
-        work.append(vfobfreq)
+        _5_tone_decode2 = RadioSetting(
+            "_5tone_settings._5tone_decode_call_frame2",
+            "5 Tone decode call Frame 2",
+            RadioSettingValueBoolean(
+                _mem._5tone_settings._5tone_decode_call_frame2))
+        group_5tone.append(_5_tone_decode2)
 
-        vfoashiftd = RadioSetting("vfo.a.shiftd", "VFO A shift",
-                                  RadioSettingValueList(LIST_SHIFT, LIST_SHIFT[
-                                      _mem.vfo.a.shiftd]))
-        work.append(vfoashiftd)
+        _5_tone_decode3 = RadioSetting(
+            "_5tone_settings._5tone_decode_call_frame3",
+            "5 Tone decode call Frame 3",
+            RadioSettingValueBoolean(
+                _mem._5tone_settings._5tone_decode_call_frame3))
+        group_5tone.append(_5_tone_decode3)
 
-        vfobshiftd = RadioSetting("vfo.b.shiftd", "VFO B shift",
-                                  RadioSettingValueList(LIST_SHIFT, LIST_SHIFT[
-                                      _mem.vfo.b.shiftd]))
-        work.append(vfobshiftd)
+        _5_tone_decode_disp1 = RadioSetting(
+            "_5tone_settings._5tone_decode_disp_frame1",
+            "5 Tone decode disp Frame 1",
+            RadioSettingValueBoolean(
+                _mem._5tone_settings._5tone_decode_disp_frame1))
+        group_5tone.append(_5_tone_decode_disp1)
 
-        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 * 10000)
+        _5_tone_decode_disp2 = RadioSetting(
+            "_5tone_settings._5tone_decode_disp_frame2",
+            "5 Tone decode disp Frame 2",
+            RadioSettingValueBoolean(
+                _mem._5tone_settings._5tone_decode_disp_frame2))
+        group_5tone.append(_5_tone_decode_disp2)
 
-        def apply_offset(setting, obj):
-            value = chirp_common.parse_freq(str(setting.value)) / 10000
-            for i in range(3, -1, -1):
-                obj.offset[i] = value % 10
-                value /= 10
+        _5_tone_decode_disp3 = RadioSetting(
+            "_5tone_settings._5tone_decode_disp_frame3",
+            "5 Tone decode disp Frame 3",
+            RadioSettingValueBoolean(
+                _mem._5tone_settings._5tone_decode_disp_frame3))
+        group_5tone.append(_5_tone_decode_disp3)
 
-        val1a = RadioSettingValueString(0, 10, convert_bytes_to_offset(
-                                        _mem.vfo.a.offset))
-        vfoaoffset = RadioSetting("vfo.a.offset",
-                                  "VFO A offset (0.00-99.95)", val1a)
-        vfoaoffset.set_apply_callback(apply_offset, _mem.vfo.a)
-        work.append(vfoaoffset)
+        decode_standard = _mem._5tone_settings.decode_standard
+        if decode_standard == 255:
+            decode_standard = 0
+        if decode_standard <= len (LIST_5TONE_STANDARDS_without_none) :
+            line = RadioSetting("_5tone_settings.decode_standard", 
+                                "5 Tone-decode Standard",
+                                RadioSettingValueList(
+                                    LIST_5TONE_STANDARDS_without_none,
+                                    LIST_5TONE_STANDARDS_without_none[
+                                        decode_standard]))
+            group_5tone.append(line)
+        else:
+            LOG.debug("Invalid decode std...")
+            
+        _5tone_delay1 = _mem._5tone_settings._5tone_delay1
+        if _5tone_delay1 == 255:
+            _5tone_delay1 = 20
 
-        val1b = RadioSettingValueString(0, 10, convert_bytes_to_offset(
-                                        _mem.vfo.b.offset))
-        vfoboffset = RadioSetting("vfo.b.offset",
-                                  "VFO B offset (0.00-99.95)", val1b)
-        vfoboffset.set_apply_callback(apply_offset, _mem.vfo.b)
-        work.append(vfoboffset)
+        if _5tone_delay1 <= len( LIST_5TONE_DELAY ):
+            list = RadioSettingValueList(LIST_5TONE_DELAY, 
+                                         LIST_5TONE_DELAY[
+                                             _5tone_delay1])
+            line = RadioSetting("_5tone_settings._5tone_delay1",
+                                "5 Tone Delay Frame 1", list)
+            group_5tone.append(line)
+        else:
+            LOG.debug("Invalid value for 5tone delay (frame1) ! Disabling.")
 
-        vfoatxp = RadioSetting("vfo.a.power", "VFO A power",
-                                RadioSettingValueList(LIST_TXP,LIST_TXP[
-                                    _mem.vfo.a.power]))
-        work.append(vfoatxp)
+        _5tone_delay2 = _mem._5tone_settings._5tone_delay2
+        if _5tone_delay2 == 255:
+            _5tone_delay2 = 20
+            LOG.debug("5 Tone delay unconfigured! Resetting to 200ms.")
 
-        vfobtxp = RadioSetting("vfo.b.power", "VFO B power",
-                                RadioSettingValueList(LIST_TXP,LIST_TXP[
-                                    _mem.vfo.b.power]))
-        work.append(vfobtxp)
+        if _5tone_delay2 <= len( LIST_5TONE_DELAY ):
+            list = RadioSettingValueList(LIST_5TONE_DELAY,
+                                         LIST_5TONE_DELAY[
+                                             _5tone_delay2])
+            line = RadioSetting("_5tone_settings._5tone_delay2",
+                                "5 Tone Delay Frame 2", list)
+            group_5tone.append(line)
+        else:
+            LOG.debug("Invalid value for 5tone delay (frame2)! Disabling.")
 
-        vfoawide = RadioSetting("vfo.a.wide", "VFO A bandwidth",
-                                RadioSettingValueList(LIST_WIDE,LIST_WIDE[
-                                    _mem.vfo.a.wide]))
-        work.append(vfoawide)
+        _5tone_delay3 = _mem._5tone_settings._5tone_delay3
+        if _5tone_delay3 == 255:
+            _5tone_delay3 = 20
+            LOG.debug("5 Tone delay unconfigured! Resetting to 200ms.")
 
-        vfobwide = RadioSetting("vfo.b.wide", "VFO B bandwidth",
-                                RadioSettingValueList(LIST_WIDE,LIST_WIDE[
-                                    _mem.vfo.b.wide]))
-        work.append(vfobwide)
+        if _5tone_delay3 <= len( LIST_5TONE_DELAY ):
+            list = RadioSettingValueList(LIST_5TONE_DELAY,
+                                         LIST_5TONE_DELAY[
+                                             _5tone_delay3])
+            line = RadioSetting("_5tone_settings._5tone_delay3",
+                                "5 Tone Delay Frame 3", list )
+            group_5tone.append(line)
+        else:
+            LOG.debug("Invalid value for 5tone delay (frame3)! Disabling.")
 
-        vfoastep = RadioSetting("vfo.a.step", "VFO A step",
-                                RadioSettingValueList(LIST_STEP,LIST_STEP[
-                                    _mem.vfo.a.step]))
-        work.append(vfoastep)
+        ext_length = _mem._5tone_settings._5tone_first_digit_ext_length
+        if ext_length == 255:
+            ext_length = 0
+            LOG.debug("1st Tone ext lenght unconfigured! Resetting to 0")
 
-        vfobstep = RadioSetting("vfo.b.step", "VFO B step",
-                                RadioSettingValueList(LIST_STEP,LIST_STEP[
-                                    _mem.vfo.b.step]))
-        work.append(vfobstep)
+        if ext_length <= len(
+            LIST_5TONE_DELAY ):
+            list = RadioSettingValueList(
+                LIST_5TONE_DELAY, 
+                LIST_5TONE_DELAY[
+                    ext_length])
+            line = RadioSetting(
+                "_5tone_settings._5tone_first_digit_ext_length",
+                "First digit extend length", list)
+            group_5tone.append(line)
+        else:
+            LOG.debug("Invalid value for 5tone ext length! Disabling.")
 
-        vfoaoptsig = RadioSetting("vfo.a.optsig", "VFO A optional signal",
-                                  RadioSettingValueList(OPTSIG_LIST,
-                                      OPTSIG_LIST[_mem.vfo.a.optsig]))
-        work.append(vfoaoptsig)
+        decode_reset_time = _mem._5tone_settings.decode_reset_time
+        if decode_reset_time == 255:
+            decode_reset_time = 59
+            LOG.debug("Decode reset time unconfigured. resetting.")
+        if decode_reset_time <= len(LIST_5TONE_RESET):
+            list = RadioSettingValueList(
+                LIST_5TONE_RESET,
+                LIST_5TONE_RESET[
+                    decode_reset_time])
+            line = RadioSetting("_5tone_settings.decode_reset_time",
+                                "Decode reset time", list)
+            group_5tone.append(line)
+        else:
+            LOG.debug("Invalid value decode reset time! Disabling.")
 
-        vfoboptsig = RadioSetting("vfo.b.optsig", "VFO B optional signal",
-                                  RadioSettingValueList(OPTSIG_LIST,
-                                      OPTSIG_LIST[_mem.vfo.b.optsig]))
-        work.append(vfoboptsig)
+        # 2 Tone
+        encode_2tone = RadioSettingGroup ("encode_2tone", "2 Tone Encode")
+        decode_2tone = RadioSettingGroup ("decode_2tone", "2 Code Decode")
 
-        vfoaspmute = RadioSetting("vfo.a.spmute", "VFO A speaker mute",
-                                  RadioSettingValueList(SPMUTE_LIST,
-                                      SPMUTE_LIST[_mem.vfo.a.spmute]))
-        work.append(vfoaspmute)
+        top.append(encode_2tone)
+        top.append(decode_2tone)
 
-        vfobspmute = RadioSetting("vfo.b.spmute", "VFO B speaker mute",
-                                  RadioSettingValueList(SPMUTE_LIST,
-                                      SPMUTE_LIST[_mem.vfo.b.spmute]))
-        work.append(vfobspmute)
+        duration_1st_tone = self._memobj._2tone.duration_1st_tone
+        if duration_1st_tone == 255:
+            LOG.debug("Duration of first 2 Tone digit is not yet " +
+                      "configured. Setting to 600ms")
+            duration_1st_tone = 60
 
-        vfoascr = RadioSetting("vfo.a.scramble", "VFO A scramble",
-                               RadioSettingValueBoolean(_mem.vfo.a.scramble))
-        work.append(vfoascr)
+        if duration_1st_tone <= len( LIST_5TONE_DELAY ):
+            line = RadioSetting("_2tone.duration_1st_tone", 
+                                "Duration 1st Tone",
+                                RadioSettingValueList(LIST_5TONE_DELAY,
+                                                      LIST_5TONE_DELAY[
+                                                          duration_1st_tone]))
+            encode_2tone.append(line)
 
-        vfobscr = RadioSetting("vfo.b.scramble", "VFO B scramble",
-                               RadioSettingValueBoolean(_mem.vfo.b.scramble))
-        work.append(vfobscr)
+        duration_2nd_tone = self._memobj._2tone.duration_2nd_tone
+        if duration_2nd_tone == 255:
+            LOG.debug("Duration of second 2 Tone digit is not yet " +
+                      "configured. Setting to 600ms")
+            duration_2nd_tone = 60
 
-        vfoascode = RadioSetting("vfo.a.scode", "VFO A PTT-ID",
-                                 RadioSettingValueList(PTTIDCODE_LIST,
-                                     PTTIDCODE_LIST[_mem.vfo.a.scode]))
-        work.append(vfoascode)
+        if duration_2nd_tone <= len( LIST_5TONE_DELAY ):
+            line = RadioSetting("_2tone.duration_2nd_tone", 
+                                "Duration 2nd Tone",
+                                RadioSettingValueList(LIST_5TONE_DELAY,
+                                                      LIST_5TONE_DELAY[
+                                                          duration_2nd_tone]))
+            encode_2tone.append(line)
 
-        vfobscode = RadioSetting("vfo.b.scode", "VFO B PTT-ID",
-                                 RadioSettingValueList(PTTIDCODE_LIST,
-                                     PTTIDCODE_LIST[_mem.vfo.b.scode]))
-        work.append(vfobscode)
+        duration_gap = self._memobj._2tone.duration_gap
+        if duration_gap == 255:
+            LOG.debug("Duration of gap is not yet " +
+                      "configured. Setting to 300ms")
+            duration_gap = 30
 
-        pttid = RadioSetting("settings.pttid", "PTT ID",
-                             RadioSettingValueList(PTTID_LIST,
-                                 PTTID_LIST[_mem.settings.pttid]))
-        work.append(pttid)
+        if duration_gap <= len( LIST_5TONE_DELAY ):
+            line = RadioSetting("_2tone.duration_gap", "Duration of gap",
+                                RadioSettingValueList(LIST_5TONE_DELAY,
+                                                      LIST_5TONE_DELAY[
+                                                          duration_gap]))
+            encode_2tone.append(line)
 
-        #FM presets
-        def fm_validate(value):
+        def _2tone_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")
+                return 65535
+            if value == 65535:
+                return value
+            if not (300 <= value and value <= 3000):
+                msg = ("2 Tone Frequency: Must be between 300 and 3000 Hz")
                 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_2tone_freq(setting, obj):
+            val = int(setting.value)
+            if (val == 0) or (val == 65535):
+                obj.set_value(65535)
+            else:
+                obj.set_value(val)
 
-        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
-
-        # DTMF-Setting
-        dtmf_enc_settings = RadioSettingGroup ("dtmf_enc_settings",
-                                               "DTMF Encoding Settings")
-        dtmf_dec_settings = RadioSettingGroup ("dtmf_dec_settings",
-                                               "DTMF Decoding Settings")
-        top.append(dtmf_enc_settings)
-        top.append(dtmf_dec_settings)
-        txdisable = RadioSetting("dtmf_settings.txdisable", 
-                                 "TX-Disable",
-                                 RadioSettingValueBoolean(
-                                     _mem.dtmf_settings.txdisable))
-        dtmf_enc_settings.append(txdisable)
-
-        rxdisable = RadioSetting("dtmf_settings.rxdisable", 
-                                 "RX-Disable",
-                                 RadioSettingValueBoolean(
-                                     _mem.dtmf_settings.rxdisable))
-        dtmf_enc_settings.append(rxdisable)
+        for code in  self._memobj._2tone._2tone_encode:
+            code_2tone = RadioSettingGroup ("code_2tone_" + str(i), 
+                                           "Encode Code " + str(i))
+            encode_2tone.append(code_2tone)
 
-        dtmfspeed_on = RadioSetting(
-            "dtmf_settings.dtmfspeed_on",
-            "DTMF Speed (On Time)",
-            RadioSettingValueList(LIST_DTMF_SPEED,
-                                  LIST_DTMF_SPEED[
-                                      _mem.dtmf_settings.dtmfspeed_on]))
-        dtmf_enc_settings.append(dtmfspeed_on)
+            tmp = code.freq1
+            if tmp == 65535:
+                tmp = 0
+            val1 = RadioSettingValueInteger(0, 65535, tmp)
+            freq1 = RadioSetting("2tone_code_"+ str(i) + "_freq1", 
+                                 "Frequency 1", val1)
+            val1.set_validate_callback(_2tone_validate)
+            freq1.set_apply_callback(apply_2tone_freq, code.freq1)
+            code_2tone.append(freq1)
 
-        dtmfspeed_off = RadioSetting(
-            "dtmf_settings.dtmfspeed_off",
-            "DTMF Speed (Off Time)",
-            RadioSettingValueList(LIST_DTMF_SPEED,
-                                  LIST_DTMF_SPEED[
-                                      _mem.dtmf_settings.dtmfspeed_off]))
-        dtmf_enc_settings.append(dtmfspeed_off)
+            tmp = code.freq2
+            if tmp == 65535:
+                tmp = 0
+            val2 = RadioSettingValueInteger(0, 65535, tmp)
+            freq2 = RadioSetting("2tone_code_"+ str(i) + "_freq2", 
+                                 "Frequency 2", val2)
+            val2.set_validate_callback(_2tone_validate)
+            freq2.set_apply_callback(apply_2tone_freq, code.freq2)
+            code_2tone.append(freq2)
 
-        def memory2string(dmtf_mem):
-            dtmf_string = ""
-            for digit in dmtf_mem:
-                if digit != 255:
-                    index = LIST_DTMF_VALUES.index(digit)
-                    dtmf_string = dtmf_string + LIST_DTMF_DIGITS[index]
-            return dtmf_string
+            i = i + 1
 
-        def apply_dmtf_frame(setting, obj):
-            LOG.debug("Setting DTMF-Code: " + str(setting.value) )
-            val_string = str(setting.value)
-            for i in range(0,16):
-                obj[i] = 255
-            i = 0
-            for current_char in val_string:
-                current_char = current_char.upper()
-                index = LIST_DTMF_DIGITS.index(current_char)
-                obj[i] = LIST_DTMF_VALUES[ index ]
-                i = i + 1
+        decode_reset_time = _mem._2tone.reset_time
+        if decode_reset_time == 255:
+            decode_reset_time = 59
+            LOG.debug("Decode reset time unconfigured. resetting.")
+        if decode_reset_time <= len(LIST_5TONE_RESET):
+            list = RadioSettingValueList(
+                LIST_5TONE_RESET,
+                LIST_5TONE_RESET[
+                    decode_reset_time])
+            line = RadioSetting("_2tone.reset_time",
+                                "Decode reset time", list)
+            decode_2tone.append(line)
+        else:
+            LOG.debug("Invalid value decode reset time! Disabling.")
 
-        codes = self._memobj.dtmf_codes
-        i = 1
-        for dtmfcode in codes:
-            val = RadioSettingValueString(0, 16, 
-                                          memory2string(dtmfcode.code),
-                                          False, CHARSET_DTMF_DIGITS)
-            line = RadioSetting("dtmf_code_" + str(i) + "_code",
-                                "DMTF Code " + str(i), val)
-            line.set_apply_callback(apply_dmtf_frame, dtmfcode.code)
-            dtmf_enc_settings.append(line)
-            i = i + 1
+        def apply_2tone_freq_pair(setting, obj):
+            val = int(setting.value)
+            derived_val = 65535
+            frqname = str(setting._name[-5:])
+            derivedname = "derived_from_" + frqname
 
-        line = RadioSetting("dtmf_settings.mastervice", 
-                            "Master and Vice ID",
-                            RadioSettingValueBoolean(
-                                _mem.dtmf_settings.mastervice))
-        dtmf_dec_settings.append(line)
+            if (val == 0):
+                val = 65535
+                derived_val = 65535
+            else:
+                derived_val = int(round(2304000.0/val))
 
-        val = RadioSettingValueString(0, 16, 
-                                      memory2string(
-                                          _mem.dtmf_settings.masterid),
-                                          False, CHARSET_DTMF_DIGITS)
-        line = RadioSetting("dtmf_settings.masterid",
-                            "Master Control ID ", val)
-        line.set_apply_callback(apply_dmtf_frame,
-                                _mem.dtmf_settings.masterid)
-        dtmf_dec_settings.append(line)
+            obj[frqname].set_value( val )
+            obj[derivedname].set_value( derived_val )
 
-        line = RadioSetting("dtmf_settings.minspection", 
-                            "Master Inspection",
-                            RadioSettingValueBoolean(
-                                _mem.dtmf_settings.minspection))
-        dtmf_dec_settings.append(line)
+            LOG.debug("Apply " + frqname + ": " + str(val) + " | " 
+                      + derivedname + ": " + str(derived_val))
 
-        line = RadioSetting("dtmf_settings.mmonitor", 
-                            "Master Monitor",
-                            RadioSettingValueBoolean(
-                                _mem.dtmf_settings.mmonitor))
-        dtmf_dec_settings.append(line)
+        i = 1
+        for decode_code in  self._memobj._2tone._2tone_decode:
+            _2tone_dec_code = RadioSettingGroup ("code_2tone_" + str(i),
+                                           "Decode Code " + str(i))
+            decode_2tone.append(_2tone_dec_code)
 
-        line = RadioSetting("dtmf_settings.mstun", 
-                            "Master Stun",
-                            RadioSettingValueBoolean(
-                                _mem.dtmf_settings.mstun))
-        dtmf_dec_settings.append(line)
+            j = 1
+            for dec in decode_code.decs:
+                val = dec.dec
+                if val == 255:
+                    LOG.debug("Dec for Code " + str(i) + " Dec " + str(j) +  
+                              " is not yet configured. Setting to 0.")
+                    val = 0
 
-        line = RadioSetting("dtmf_settings.mkill", 
-                            "Master Kill",
-                            RadioSettingValueBoolean(
-                                _mem.dtmf_settings.mkill))
-        dtmf_dec_settings.append(line)
+                if val <= len( LIST_2TONE_DEC ):
+                    line = RadioSetting(
+                        "_2tone_dec_settings_" + str(i) + "_dec_" + str(j),
+                        "Dec " + str(j), RadioSettingValueList
+                        (LIST_2TONE_DEC,
+                         LIST_2TONE_DEC[val]))
+                    line.set_apply_callback(apply_list_value, dec.dec)
+                    _2tone_dec_code.append(line)
+                else:
+                    LOG.debug("Invalid value for 2tone dec! Disabling.")
 
-        line = RadioSetting("dtmf_settings.mrevive", 
-                            "Master Revive",
-                            RadioSettingValueBoolean(
-                                _mem.dtmf_settings.mrevive))
-        dtmf_dec_settings.append(line)
+                val = dec.response
+                if val == 255:
+                    LOG.debug("Response for Code " + str(i) + " Dec " + str(j)+
+                              " is not yet configured. Setting to 0.")
+                    val = 0
 
-        val = RadioSettingValueString(0, 16, 
-                                      memory2string(
-                                          _mem.dtmf_settings.viceid),
-                                          False, CHARSET_DTMF_DIGITS)
-        line = RadioSetting("dtmf_settings.viceid",
-                            "Vice Control ID ", val)
-        line.set_apply_callback(apply_dmtf_frame,
-                                _mem.dtmf_settings.viceid)
-        dtmf_dec_settings.append(line)
+                if val <= len( LIST_2TONE_RESPONSE ):
+                    line = RadioSetting(
+                        "_2tone_dec_settings_" + str(i) + "_resp_" + str(j),
+                        "Response " + str(j), RadioSettingValueList
+                        (LIST_2TONE_RESPONSE,
+                         LIST_2TONE_RESPONSE[val]))
+                    line.set_apply_callback(apply_list_value, dec.response)
+                    _2tone_dec_code.append(line)
+                else:
+                    LOG.debug("Invalid value for 2tone response! Disabling.")
 
-        line = RadioSetting("dtmf_settings.vinspection", 
-                            "Vice Inspection",
-                            RadioSettingValueBoolean(
-                                _mem.dtmf_settings.vinspection))
-        dtmf_dec_settings.append(line)
+                val = dec.alert
+                if val == 255:
+                    LOG.debug("Alert for Code " + str(i) + " Dec " + str(j) +  
+                              " is not yet configured. Setting to 0.")
+                    val = 0
 
-        line = RadioSetting("dtmf_settings.vmonitor", 
-                            "Vice Monitor",
-                            RadioSettingValueBoolean(
-                                _mem.dtmf_settings.vmonitor))
-        dtmf_dec_settings.append(line)
+                if val <= len( PTTIDCODE_LIST ):
+                    line = RadioSetting(
+                        "_2tone_dec_settings_" + str(i) + "_alert_" + str(j),
+                        "Alert " + str(j), RadioSettingValueList
+                        (PTTIDCODE_LIST,
+                         PTTIDCODE_LIST[val]))
+                    line.set_apply_callback(apply_list_value, dec.alert)
+                    _2tone_dec_code.append(line)
+                else:
+                    LOG.debug("Invalid value for 2tone alert! Disabling.")
+                j = j + 1
 
-        line = RadioSetting("dtmf_settings.vstun", 
-                            "Vice Stun",
-                            RadioSettingValueBoolean(
-                                _mem.dtmf_settings.vstun))
-        dtmf_dec_settings.append(line)
+            freq = self._memobj._2tone.freqs[i-1]
+            for char in ['A', 'B', 'C', 'D']:
+                setting_name = "freq" + str(char)
 
-        line = RadioSetting("dtmf_settings.vkill", 
-                            "Vice Kill",
-                            RadioSettingValueBoolean(
-                                _mem.dtmf_settings.vkill))
-        dtmf_dec_settings.append(line)
+                tmp = freq[setting_name]
+                if tmp == 65535:
+                    tmp = 0
+                if tmp != 0:
+                    expected = int(round(2304000.0/tmp))
+                    from_mem = freq["derived_from_" + setting_name]
+                    if expected != from_mem:
+                        LOG.error("Expected " + str(expected) + 
+                                  " but read " + str(from_mem ) + 
+                                  ". Disabling 2Tone Decode Freqs!")
+                        break
+                val = RadioSettingValueInteger(0, 65535, tmp)
+                frq = RadioSetting("2tone_dec_"+ str(i) + "_freq" + str(char),
+                                         ("Decode Frequency " +str(char)), val)
+                val.set_validate_callback(_2tone_validate)
+                frq.set_apply_callback(apply_2tone_freq_pair, freq)
+                _2tone_dec_code.append(frq)
 
-        line = RadioSetting("dtmf_settings.vrevive", 
-                            "Vice Revive",
-                            RadioSettingValueBoolean(
-                                _mem.dtmf_settings.vrevive))
-        dtmf_dec_settings.append(line)
+            i = i + 1
 
-        val = RadioSettingValueString(0, 16, 
-                                      memory2string(
-                                          _mem.dtmf_settings.inspection),
-                                          False, CHARSET_DTMF_DIGITS)
-        line = RadioSetting("dtmf_settings.inspection",
-                            "Inspection", val)
-        line.set_apply_callback(apply_dmtf_frame,
-                                _mem.dtmf_settings.inspection)
-        dtmf_dec_settings.append(line)
+        return top
 
-        val = RadioSettingValueString(0, 16, 
-                                      memory2string(
-                                          _mem.dtmf_settings.alarmcode),
-                                          False, CHARSET_DTMF_DIGITS)
-        line = RadioSetting("dtmf_settings.alarmcode",
-                            "Alarm", val)
-        line.set_apply_callback(apply_dmtf_frame,
-                                _mem.dtmf_settings.alarmcode)
-        dtmf_dec_settings.append(line)
+    def set_settings(self, settings):
+        _settings = self._memobj.settings
+        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()
 
-        val = RadioSettingValueString(0, 16, 
-                                      memory2string(
-                                          _mem.dtmf_settings.kill),
-                                          False, CHARSET_DTMF_DIGITS)
-        line = RadioSetting("dtmf_settings.kill",
-                            "Kill", val)
-        line.set_apply_callback(apply_dmtf_frame,
-                                _mem.dtmf_settings.kill)
-        dtmf_dec_settings.append(line)
+                    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
 
-        val = RadioSettingValueString(0, 16, 
-                                      memory2string(
-                                          _mem.dtmf_settings.monitor),
-                                          False, CHARSET_DTMF_DIGITS)
-        line = RadioSetting("dtmf_settings.monitor",
-                            "Monitor", val)
-        line.set_apply_callback(apply_dmtf_frame,
-                                _mem.dtmf_settings.monitor)
-        dtmf_dec_settings.append(line)
+    @classmethod
+    def match_model(cls, filedata, filename):
+        match_size = False
+        match_model = False
 
-        val = RadioSettingValueString(0, 16, 
-                                      memory2string(
-                                          _mem.dtmf_settings.stun),
-                                          False, CHARSET_DTMF_DIGITS)
-        line = RadioSetting("dtmf_settings.stun",
-                            "Stun", val)
-        line.set_apply_callback(apply_dmtf_frame,
-                                _mem.dtmf_settings.stun)
-        dtmf_dec_settings.append(line)
+        # testing the file data size
+        if len(filedata) == MEM_SIZE:
+            match_size = True
 
-        val = RadioSettingValueString(0, 16, 
-                                      memory2string(
-                                          _mem.dtmf_settings.revive),
-                                          False, CHARSET_DTMF_DIGITS)
-        line = RadioSetting("dtmf_settings.revive",
-                            "Revive", val)
-        line.set_apply_callback(apply_dmtf_frame,
-                                _mem.dtmf_settings.revive)
-        dtmf_dec_settings.append(line)
+        # testing the firmware model fingerprint
+        match_model = model_match(cls, filedata)
+
+        if match_size and match_model:
+            return True
+        else:
+            return False
 
-        def apply_dmtf_listvalue(setting, obj):
-            LOG.debug("Setting value: "+ str(setting.value) + " from list")
-            val = str(setting.value)
-            index = LIST_DTMF_SPECIAL_DIGITS.index(val)
-            val = LIST_DTMF_SPECIAL_VALUES[index]
-            obj.set_value(val)
 
-        idx = LIST_DTMF_SPECIAL_VALUES.index(_mem.dtmf_settings.groupcode)
-        line = RadioSetting(
-            "dtmf_settings.groupcode",
-            "Group Code",
-            RadioSettingValueList(LIST_DTMF_SPECIAL_DIGITS,
-                                  LIST_DTMF_SPECIAL_DIGITS[idx]))
-        line.set_apply_callback(apply_dmtf_listvalue,
-                                _mem.dtmf_settings.groupcode)
-        dtmf_dec_settings.append(line)
+MEM_FORMAT = """
+#seekto 0x0000;
+struct {
+  lbcd rxfreq[4];
+  lbcd txfreq[4];
+  ul16 rxtone;
+  ul16 txtone;
+  u8 unknown0:4,
+     scode:4;
+  u8 unknown1:2,
+     spmute:1,
+     unknown2:3,
+     optsig:2;
+  u8 unknown3:3,
+     scramble:1,
+     unknown4:3,
+     power:1;
+  u8 unknown5:1,
+     wide:1,
+     unknown6:2,
+     bcl:1,
+     add:1,
+     pttid:2;
+} memory[200];
 
-        idx = LIST_DTMF_SPECIAL_VALUES.index(_mem.dtmf_settings.spacecode)
-        line = RadioSetting(
-            "dtmf_settings.spacecode",
-            "Space Code",
-            RadioSettingValueList(LIST_DTMF_SPECIAL_DIGITS,
-                                  LIST_DTMF_SPECIAL_DIGITS[idx]))
-        line.set_apply_callback(apply_dmtf_listvalue,
-                                _mem.dtmf_settings.spacecode)
-        dtmf_dec_settings.append(line)
+#seekto 0x0E00;
+struct {
+  u8 tdr;
+  u8 unknown1;
+  u8 sql;
+  u8 unknown2[2];
+  u8 tot;
+  u8 apo;           // BTech radios use this as the Auto Power Off time
+                    // other radios use this as pre-Time Out Alert
+  u8 unknown3;
+  u8 abr;
+  u8 beep;
+  u8 unknown4[4];
+  u8 dtmfst;
+  u8 unknown5[2];
+  u8 prisc;
+  u8 prich;
+  u8 screv;
+  u8 unknown6[2];
+  u8 pttid;
+  u8 pttlt;
+  u8 unknown7;
+  u8 emctp;
+  u8 emcch;
+  u8 ringt;
+  u8 unknown8;
+  u8 camdf;
+  u8 cbmdf;
+  u8 sync;          // BTech radios use this as the display sync setting
+                    // other radios use this as the auto keypad lock setting
+  u8 ponmsg;
+  u8 wtled;
+  u8 rxled;
+  u8 txled;
+  u8 unknown9[5];
+  u8 anil;
+  u8 reps;
+  u8 repm;
+  u8 tdrab;
+  u8 ste;
+  u8 rpste;
+  u8 rptdl;
+  u8 mgain;
+  u8 dtmfg;
+} settings;
 
-        line = RadioSetting(
-            "dtmf_settings.resettime",
-            "Reset time",
-            RadioSettingValueList(LIST_5TONE_RESET,
-                                  LIST_5TONE_RESET[
-                                      _mem.dtmf_settings.resettime]))
-        dtmf_dec_settings.append(line)
+#seekto 0x0E80;
+struct {
+  u8 unknown1;
+  u8 vfomr;
+  u8 keylock;
+  u8 unknown2;
+  u8 unknown3:4,
+     vfomren:1,
+     unknown4:1,
+     reseten:1,
+     menuen:1;
+  u8 unknown5[11];
+  u8 dispab;
+  u8 mrcha;
+  u8 mrchb;
+  u8 menu;
+} settings2;
 
-        line = RadioSetting(
-            "dtmf_settings.delayproctime",
-            "Delay processing time",
-            RadioSettingValueList(LIST_DTMF_DELAY,
-                                  LIST_DTMF_DELAY[
-                                      _mem.dtmf_settings.delayproctime]))
-        dtmf_dec_settings.append(line)
+#seekto 0x0EC0;
+struct {
+  char line1[6];
+  char line2[6];
+} poweron_msg;
 
+struct settings_vfo {
+  u8 freq[8];
+  u8 offset[6];
+  u8 unknown2[2];
+  ul16 rxtone;
+  ul16 txtone;
+  u8 scode;
+  u8 spmute;
+  u8 optsig;
+  u8 scramble;
+  u8 wide;
+  u8 power;
+  u8 shiftd;
+  u8 step;
+  u8 unknown3[4];
+};
 
-        # 5 Tone Settings
-        stds_5tone = RadioSettingGroup ("stds_5tone", "Standards")
-        codes_5tone = RadioSettingGroup ("codes_5tone", "Codes")
+#seekto 0x0F00;
+struct {
+  struct settings_vfo a;
+  struct settings_vfo b;
+} vfo;
 
-        group_5tone = RadioSettingGroup ("group_5tone", "5 Tone Settings")
-        group_5tone.append(stds_5tone)
-        group_5tone.append(codes_5tone)
+#seekto 0x1000;
+struct {
+  char name[6];
+  u8 unknown1[10];
+} names[200];
 
-        top.append(group_5tone)
+#seekto 0x2400;
+struct {
+  u8 period; // one out of LIST_5TONE_STANDARD_PERIODS
+  u8 group_tone;
+  u8 repeat_tone;
+  u8 unused[13];
+} _5tone_std_settings[15];
 
-        def apply_list_value(setting, obj):
-            options = setting.value.get_options()
-            obj.set_value ( options.index(str(setting.value)) )
+#seekto 0x2500;
+struct {
+  u8 frame1[5];
+  u8 frame2[5];
+  u8 frame3[5];
+  u8 standard;   // one out of LIST_5TONE_STANDARDS
+} _5tone_codes[15];
 
-        _5tone_standards = self._memobj._5tone_std_settings
-        i = 0
-        for standard in _5tone_standards:
-            std_5tone = RadioSettingGroup ("std_5tone_" + str(i), 
-                                           LIST_5TONE_STANDARDS[i])
-            stds_5tone.append(std_5tone)
- 
-            period = standard.period
-            if period == 255:
-                LOG.debug("Period for " + LIST_5TONE_STANDARDS[i] + 
-                          " is not yet configured. Setting to 70ms.")
-                period = 5
+#seekto 0x25F0;
+struct {
+  u8 _5tone_delay1; // * 10ms
+  u8 _5tone_delay2; // * 10ms
+  u8 _5tone_delay3; // * 10ms
+  u8 _5tone_first_digit_ext_length;
+  u8 unknown1;
+  u8 unknown2;
+  u8 unknown3;
+  u8 unknown4;
+  u8 decode_standard;
+  u8 unknown5:5,
+     _5tone_decode_call_frame3:1,
+     _5tone_decode_call_frame2:1,
+     _5tone_decode_call_frame1:1;
+  u8 unknown6:5,
+     _5tone_decode_disp_frame3:1,
+     _5tone_decode_disp_frame2:1,
+     _5tone_decode_disp_frame1:1;
+  u8 decode_reset_time; // * 100 + 100ms
+} _5tone_settings;
+
+#seekto 0x2900;
+struct {
+  u8 code[16]; // 0=x0A, A=0x0D, B=0x0E, C=0x0F, D=0x00, #=0x0C *=0x0B
+} dtmf_codes[15];
+
+#seekto 0x29F0;
+struct {
+  u8 dtmfspeed_on;  //list with 50..2000ms in steps of 10
+  u8 dtmfspeed_off; //list with 50..2000ms in steps of 10
+  u8 unknown0[14];
+  u8 inspection[16];
+  u8 monitor[16];
+  u8 alarmcode[16];
+  u8 stun[16];
+  u8 kill[16];
+  u8 revive[16];
+  u8 unknown1[16];
+  u8 unknown2[16];
+  u8 unknown3[16];
+  u8 unknown4[16];
+  u8 unknown5[16];
+  u8 unknown6[16];
+  u8 unknown7[16];
+  u8 masterid[16];
+  u8 viceid[16];
+  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; // * 100 + 100ms
+  u8 resettime;     // * 100 + 100ms
+} dtmf_settings;
 
-            if period <= len( LIST_5TONE_STANDARD_PERIODS ):
-                line = RadioSetting(
-                    "_5tone_std_settings_" + str(i) + "_period",
-                    "Period (ms)", RadioSettingValueList
-                    (LIST_5TONE_STANDARD_PERIODS,
-                     LIST_5TONE_STANDARD_PERIODS[period]))
-                line.set_apply_callback(apply_list_value, standard.period)
-                std_5tone.append(line)
-            else:
-                LOG.debug("Invalid value for 5tone period! Disabling.")
+#seekto 0x2D00;
+struct {
+  struct {
+    ul16 freq1;
+    u8 unused01[6];
+    ul16 freq2;
+    u8 unused02[6];
+  } _2tone_encode[15];
+  u8 duration_1st_tone; // *10ms
+  u8 duration_2nd_tone; // *10ms
+  u8 duration_gap;      // *10ms
+  u8 unused03[13];
+  struct {
+    struct {
+      u8 dec;      // one out of LIST_2TONE_DEC
+      u8 response; // one out of LIST_2TONE_RESPONSE
+      u8 alert;    // 1-16
+    } decs[4];
+    u8 unused04[4];
+  } _2tone_decode[15];
+  u8 unused05[16];
 
-            group_tone = standard.group_tone
-            if group_tone == 255:
-                LOG.debug("Group-Tone for " + LIST_5TONE_STANDARDS[i] +
-                          " is not yet configured. Setting to A.")
-                group_tone = 10
+  struct {
+    ul16 freqA;
+    ul16 freqB;
+    ul16 freqC;
+    ul16 freqD;
+    // unknown what those values mean, but they are
+    // derived from configured frequencies
+    ul16 derived_from_freqA; // 2304000/freqA
+    ul16 derived_from_freqB; // 2304000/freqB
+    ul16 derived_from_freqC; // 2304000/freqC
+    ul16 derived_from_freqD; // 2304000/freqD
+  }freqs[15];
+  u8 reset_time;  // * 100 + 100ms - 100-8000ms
+} _2tone;
 
-            if group_tone <= len( LIST_5TONE_DIGITS ):
-                line = RadioSetting(
-                    "_5tone_std_settings_" + str(i) + "_grouptone",
-                    "Group Tone",
-                    RadioSettingValueList(LIST_5TONE_DIGITS,
-                                          LIST_5TONE_DIGITS[
-                                              group_tone]))
-                line.set_apply_callback(apply_list_value,
-                                        standard.group_tone)
-                std_5tone.append(line)
-            else:
-                LOG.debug("Invalid value for 5tone digit! Disabling.")
+#seekto 0x3000;
+struct {
+  u8 freq[8];
+  char broadcast_station_name[6];
+  u8 unknown[2];
+} fm_radio_preset[16];
 
-            repeat_tone = standard.repeat_tone
-            if repeat_tone == 255:
-                LOG.debug("Repeat-Tone for " + LIST_5TONE_STANDARDS[i] + 
-                          " is not yet configured. Setting to E.")
-                repeat_tone = 14
+#seekto 0x3C90;
+struct {
+  u8 vhf_low[3];
+  u8 vhf_high[3];
+  u8 uhf_low[3];
+  u8 uhf_high[3];
+} ranges;
 
-            if repeat_tone <= len( LIST_5TONE_DIGITS ):
-                line = RadioSetting(
-                    "_5tone_std_settings_" + str(i) + "_repttone",
-                    "Repeat Tone",
-                    RadioSettingValueList(LIST_5TONE_DIGITS,
-                                          LIST_5TONE_DIGITS[
-                                              repeat_tone]))
-                line.set_apply_callback(apply_list_value,
-                                        standard.repeat_tone)
-                std_5tone.append(line)
-            else:
-                LOG.debug("Invalid value for 5tone digit! Disabling.")
-            i = i + 1
+// the UV-2501+220 & KT8900R has different zones for storing ranges
 
-        def my_apply_5tonestdlist_value(setting, obj):
-            if LIST_5TONE_STANDARDS.index(str(setting.value)) == 15:
-                obj.set_value(0xFF)
-            else:
-                obj.set_value( LIST_5TONE_STANDARDS.
-                              index(str(setting.value)) )
+#seekto 0x3CD0;
+struct {
+  u8 vhf_low[3];
+  u8 vhf_high[3];
+  u8 unknown1[4];
+  u8 unknown2[6];
+  u8 vhf2_low[3];
+  u8 vhf2_high[3];
+  u8 unknown3[4];
+  u8 unknown4[6];
+  u8 uhf_low[3];
+  u8 uhf_high[3];
+} ranges220;
 
-        def apply_5tone_frame(setting, obj):
-            LOG.debug("Setting 5 Tone: " + str(setting.value) )
-            valstring = str(setting.value)
-            if len(valstring) == 0:
-                for i in range(0,5):
-                    obj[i] = 255
-            else:
-                validFrame = True
-                for i in range(0,5):
-                    currentChar = valstring[i].upper()
-                    if currentChar in LIST_5TONE_DIGITS:
-                        obj[i] = LIST_5TONE_DIGITS.index(currentChar)
-                    else:
-                        validFrame = False
-                        LOG.debug("invalid char: " + str(currentChar))
-                if not validFrame:
-                    LOG.debug("setting whole frame to FF" )
-                    for i in range(0,5):
-                        obj[i] = 255
+#seekto 0x3F70;
+struct {
+  char fp[6];
+} fingerprint;
 
-        def validate_5tone_frame(value):
-            if (len(str(value)) != 5) and (len(str(value)) != 0) :
-                msg = ("5 Tone must have 5 digits or 0 digits")
-                raise InvalidValueError(msg)
-            for digit in str(value):
-                if digit.upper() not in LIST_5TONE_DIGITS:
-                    msg = (str(digit) + " is not a valid digit for 5tones")
-                    raise InvalidValueError(msg)
-            return value
+"""
 
-        def frame2string(frame):
-            frameString = ""
-            for digit in frame:
-                if digit != 255:
-                    frameString = frameString + LIST_5TONE_DIGITS[digit]
-            return frameString
 
-        _5tone_codes = self._memobj._5tone_codes
-        i = 1
-        for code in _5tone_codes:
-            code_5tone = RadioSettingGroup ("code_5tone_" + str(i),
-                                            "5 Tone code " + str(i))
-            codes_5tone.append(code_5tone)
-            if (code.standard == 255 ):
-                currentVal = 15
-            else:
-                currentVal = code.standard
-            line = RadioSetting("_5tone_code_" + str(i) + "_std", 
-                                " Standard",
-                                RadioSettingValueList(LIST_5TONE_STANDARDS,
-                                                      LIST_5TONE_STANDARDS[
-                                                          currentVal]) )
-            line.set_apply_callback(my_apply_5tonestdlist_value,
-                                    code.standard)
-            code_5tone.append(line)
+class BTech(BTechMobileCommon):
+    """BTECH's UV-5001 and alike radios"""
+    BANDS = 2
+    COLOR_LCD = False
+    NAME_LENGTH = 6
 
-            val = RadioSettingValueString(0, 6,
-                                          frame2string(code.frame1), False)
-            line = RadioSetting("_5tone_code_" + str(i) + "_frame1", 
-                                " Frame 1", val)
-            val.set_validate_callback(validate_5tone_frame)
-            line.set_apply_callback(apply_5tone_frame, code.frame1)
-            code_5tone.append(line)
+    def set_options(self):
+        """This is to read the options from the image and set it in the
+        environment, for now just the limits of the freqs in the VHF/UHF
+        ranges"""
 
-            val = RadioSettingValueString(0, 6,
-                                          frame2string(code.frame2), False)
-            line = RadioSetting("_5tone_code_" + str(i) + "_frame2",
-                                " Frame 2", val)
-            val.set_validate_callback(validate_5tone_frame)
-            line.set_apply_callback(apply_5tone_frame, code.frame2)
-            code_5tone.append(line)
+        # setting the correct ranges for each radio type
+        if self.MODEL in ["UV-2501+220", "KT8900R"]:
+            # the model 2501+220 has a segment in 220
+            # and a different position in the memmap
+            # also the QYT KT8900R
+            ranges = self._memobj.ranges220
+        else:
+            ranges = self._memobj.ranges
 
-            val = RadioSettingValueString(0, 6,
-                                          frame2string(code.frame3), False)
-            line = RadioSetting("_5tone_code_" + str(i) + "_frame3",
-                                " Frame 3", val)
-            val.set_validate_callback(validate_5tone_frame)
-            line.set_apply_callback(apply_5tone_frame, code.frame3)
-            code_5tone.append(line)
-            i = i + 1
+        # the normal dual bands
+        vhf = _decode_ranges(ranges.vhf_low, ranges.vhf_high)
+        uhf = _decode_ranges(ranges.uhf_low, ranges.uhf_high)
 
-        _5_tone_decode1 = RadioSetting(
-            "_5tone_settings._5tone_decode_call_frame1",
-            "5 Tone decode call Frame 1",
-            RadioSettingValueBoolean(
-                _mem._5tone_settings._5tone_decode_call_frame1))
-        group_5tone.append(_5_tone_decode1)
+        # DEBUG
+        LOG.info("Radio ranges: VHF %d to %d" % vhf)
+        LOG.info("Radio ranges: UHF %d to %d" % uhf)
 
-        _5_tone_decode2 = RadioSetting(
-            "_5tone_settings._5tone_decode_call_frame2",
-            "5 Tone decode call Frame 2",
-            RadioSettingValueBoolean(
-                _mem._5tone_settings._5tone_decode_call_frame2))
-        group_5tone.append(_5_tone_decode2)
+        # 220Mhz radios case
+        if self.MODEL in ["UV-2501+220", "KT8900R"]:
+            vhf2 = _decode_ranges(ranges.vhf2_low, ranges.vhf2_high)
+            LOG.info("Radio ranges: VHF(220) %d to %d" % vhf2)
+            self._220_range = vhf2
 
-        _5_tone_decode3 = RadioSetting(
-            "_5tone_settings._5tone_decode_call_frame3",
-            "5 Tone decode call Frame 3",
-            RadioSettingValueBoolean(
-                _mem._5tone_settings._5tone_decode_call_frame3))
-        group_5tone.append(_5_tone_decode3)
+        # set the class with the real data
+        self._vhf_range = vhf
+        self._uhf_range = uhf
 
-        _5_tone_decode_disp1 = RadioSetting(
-            "_5tone_settings._5tone_decode_disp_frame1",
-            "5 Tone decode disp Frame 1",
-            RadioSettingValueBoolean(
-                _mem._5tone_settings._5tone_decode_disp_frame1))
-        group_5tone.append(_5_tone_decode_disp1)
+    def process_mmap(self):
+        """Process the mem map into the mem object"""
 
-        _5_tone_decode_disp2 = RadioSetting(
-            "_5tone_settings._5tone_decode_disp_frame2",
-            "5 Tone decode disp Frame 2",
-            RadioSettingValueBoolean(
-                _mem._5tone_settings._5tone_decode_disp_frame2))
-        group_5tone.append(_5_tone_decode_disp2)
+        # Get it
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+        # load specific parameters from the radio image
+        self.set_options()
 
-        _5_tone_decode_disp3 = RadioSetting(
-            "_5tone_settings._5tone_decode_disp_frame3",
-            "5 Tone decode disp Frame 3",
-            RadioSettingValueBoolean(
-                _mem._5tone_settings._5tone_decode_disp_frame3))
-        group_5tone.append(_5_tone_decode_disp3)
 
-        decode_standard = _mem._5tone_settings.decode_standard
-        if decode_standard == 255:
-            decode_standard = 0
-        if decode_standard <= len (LIST_5TONE_STANDARDS_without_none) :
-            line = RadioSetting("_5tone_settings.decode_standard", 
-                                "5 Tone-decode Standard",
-                                RadioSettingValueList(
-                                    LIST_5TONE_STANDARDS_without_none,
-                                    LIST_5TONE_STANDARDS_without_none[
-                                        decode_standard]))
-            group_5tone.append(line)
-        else:
-            LOG.debug("Invalid decode std...")
-            
-        _5tone_delay1 = _mem._5tone_settings._5tone_delay1
-        if _5tone_delay1 == 255:
-            _5tone_delay1 = 20
+# Declaring Aliases (Clones of the real radios)
+class JT2705M(chirp_common.Alias):
+    VENDOR = "Jetstream"
+    MODEL = "JT2705M"
 
-        if _5tone_delay1 <= len( LIST_5TONE_DELAY ):
-            list = RadioSettingValueList(LIST_5TONE_DELAY, 
-                                         LIST_5TONE_DELAY[
-                                             _5tone_delay1])
-            line = RadioSetting("_5tone_settings._5tone_delay1",
-                                "5 Tone Delay Frame 1", list)
-            group_5tone.append(line)
-        else:
-            LOG.debug("Invalid value for 5tone delay (frame1) ! Disabling.")
 
-        _5tone_delay2 = _mem._5tone_settings._5tone_delay2
-        if _5tone_delay2 == 255:
-            _5tone_delay2 = 20
-            LOG.debug("5 Tone delay unconfigured! Resetting to 200ms.")
+class JT6188Mini(chirp_common.Alias):
+    VENDOR = "Juentai"
+    MODEL = "JT-6188 Mini"
 
-        if _5tone_delay2 <= len( LIST_5TONE_DELAY ):
-            list = RadioSettingValueList(LIST_5TONE_DELAY,
-                                         LIST_5TONE_DELAY[
-                                             _5tone_delay2])
-            line = RadioSetting("_5tone_settings._5tone_delay2",
-                                "5 Tone Delay Frame 2", list)
-            group_5tone.append(line)
-        else:
-            LOG.debug("Invalid value for 5tone delay (frame2)! Disabling.")
 
-        _5tone_delay3 = _mem._5tone_settings._5tone_delay3
-        if _5tone_delay3 == 255:
-            _5tone_delay3 = 20
-            LOG.debug("5 Tone delay unconfigured! Resetting to 200ms.")
+class JT6188Plus(chirp_common.Alias):
+    VENDOR = "Juentai"
+    MODEL = "JT-6188 Plus"
 
-        if _5tone_delay3 <= len( LIST_5TONE_DELAY ):
-            list = RadioSettingValueList(LIST_5TONE_DELAY,
-                                         LIST_5TONE_DELAY[
-                                             _5tone_delay3])
-            line = RadioSetting("_5tone_settings._5tone_delay3",
-                                "5 Tone Delay Frame 3", list )
-            group_5tone.append(line)
-        else:
-            LOG.debug("Invalid value for 5tone delay (frame3)! Disabling.")
 
-        ext_length = _mem._5tone_settings._5tone_first_digit_ext_length
-        if ext_length == 255:
-            ext_length = 0
-            LOG.debug("1st Tone ext lenght unconfigured! Resetting to 0")
+class SSGT890(chirp_common.Alias):
+    VENDOR = "Sainsonic"
+    MODEL = "GT-890"
 
-        if ext_length <= len(
-            LIST_5TONE_DELAY ):
-            list = RadioSettingValueList(
-                LIST_5TONE_DELAY, 
-                LIST_5TONE_DELAY[
-                    ext_length])
-            line = RadioSetting(
-                "_5tone_settings._5tone_first_digit_ext_length",
-                "First digit extend length", list)
-            group_5tone.append(line)
-        else:
-            LOG.debug("Invalid value for 5tone ext length! Disabling.")
 
-        decode_reset_time = _mem._5tone_settings.decode_reset_time
-        if decode_reset_time == 255:
-            decode_reset_time = 59
-            LOG.debug("Decode reset time unconfigured. resetting.")
-        if decode_reset_time <= len(LIST_5TONE_RESET):
-            list = RadioSettingValueList(
-                LIST_5TONE_RESET,
-                LIST_5TONE_RESET[
-                    decode_reset_time])
-            line = RadioSetting("_5tone_settings.decode_reset_time",
-                                "Decode reset time", list)
-            group_5tone.append(line)
-        else:
-            LOG.debug("Invalid value decode reset time! Disabling.")
+class ZastoneMP300(chirp_common.Alias):
+    VENDOR = "Zastone"
+    MODEL = "MP-300"
 
-        # 2 Tone
-        encode_2tone = RadioSettingGroup ("encode_2tone", "2 Tone Encode")
-        decode_2tone = RadioSettingGroup ("decode_2tone", "2 Code Decode")
 
-        top.append(encode_2tone)
-        top.append(decode_2tone)
+# real radios
+ at directory.register
+class UV2501(BTech):
+    """Baofeng Tech UV2501"""
+    MODEL = "UV-2501"
+    _fileid = [UV2501G3_fp,
+               UV2501G2_fp,
+               UV2501pp2_fp,
+               UV2501pp_fp]
 
-        duration_1st_tone = self._memobj._2tone.duration_1st_tone
-        if duration_1st_tone == 255:
-            LOG.debug("Duration of first 2 Tone digit is not yet " +
-                      "configured. Setting to 600ms")
-            duration_1st_tone = 60
 
-        if duration_1st_tone <= len( LIST_5TONE_DELAY ):
-            line = RadioSetting("_2tone.duration_1st_tone", 
-                                "Duration 1st Tone",
-                                RadioSettingValueList(LIST_5TONE_DELAY,
-                                                      LIST_5TONE_DELAY[
-                                                          duration_1st_tone]))
-            encode_2tone.append(line)
+ at directory.register
+class UV2501_220(BTech):
+    """Baofeng Tech UV2501+220"""
+    MODEL = "UV-2501+220"
+    BANDS = 3
+    _magic = MSTRING_220
+    _id2 = UV2501_220pp_id
+    _fileid = [UV2501_220G3_fp,
+               UV2501_220G2_fp,
+               UV2501_220_fp,
+               UV2501_220pp_fp]
 
-        duration_2nd_tone = self._memobj._2tone.duration_2nd_tone
-        if duration_2nd_tone == 255:
-            LOG.debug("Duration of second 2 Tone digit is not yet " +
-                      "configured. Setting to 600ms")
-            duration_2nd_tone = 60
 
-        if duration_2nd_tone <= len( LIST_5TONE_DELAY ):
-            line = RadioSetting("_2tone.duration_2nd_tone", 
-                                "Duration 2nd Tone",
-                                RadioSettingValueList(LIST_5TONE_DELAY,
-                                                      LIST_5TONE_DELAY[
-                                                          duration_2nd_tone]))
-            encode_2tone.append(line)
+ at directory.register
+class UV5001(BTech):
+    """Baofeng Tech UV5001"""
+    MODEL = "UV-5001"
+    _fileid = [UV5001G3_fp,
+               UV5001G22_fp,
+               UV5001G2_fp,
+               UV5001alpha_fp,
+               UV5001pp_fp]
+    _power_levels = [chirp_common.PowerLevel("High", watts=50),
+                     chirp_common.PowerLevel("Low", watts=10)]
 
-        duration_gap = self._memobj._2tone.duration_gap
-        if duration_gap == 255:
-            LOG.debug("Duration of gap is not yet " +
-                      "configured. Setting to 300ms")
-            duration_gap = 30
 
-        if duration_gap <= len( LIST_5TONE_DELAY ):
-            line = RadioSetting("_2tone.duration_gap", "Duration of gap",
-                                RadioSettingValueList(LIST_5TONE_DELAY,
-                                                      LIST_5TONE_DELAY[
-                                                          duration_gap]))
-            encode_2tone.append(line)
+ at directory.register
+class MINI8900(BTech):
+    """WACCOM MINI-8900"""
+    VENDOR = "WACCOM"
+    MODEL = "MINI-8900"
+    _magic = MSTRING_MINI8900
+    _fileid = [MINI8900_fp, ]
+    # Clones
+    ALIASES = [JT6188Plus, ]
 
-        def _2tone_validate(value):
-            if value == 0:
-                return 65535
-            if value == 65535:
-                return value
-            if not (300 <= value and value <= 3000):
-                msg = ("2 Tone Frequency: Must be between 300 and 3000 Hz")
-                raise InvalidValueError(msg)
-            return value
 
-        def apply_2tone_freq(setting, obj):
-            val = int(setting.value)
-            if (val == 0) or (val == 65535):
-                obj.set_value(65535)
-            else:
-                obj.set_value(val)
+ at directory.register
+class KTUV980(BTech):
+    """QYT KT-UV980"""
+    VENDOR = "QYT"
+    MODEL = "KT-UV980"
+    _vhf_range = (136000000, 175000000)
+    _uhf_range = (400000000, 481000000)
+    _magic = MSTRING_MINI8900
+    _fileid = [KTUV980_fp, ]
+    # Clones
+    ALIASES = [JT2705M, ]
 
-        i = 1
-        for code in  self._memobj._2tone._2tone_encode:
-            code_2tone = RadioSettingGroup ("code_2tone_" + str(i), 
-                                           "Encode Code " + str(i))
-            encode_2tone.append(code_2tone)
+# Please note that there is a version of this radios that is a clone of the
+# Waccom Mini8900, maybe an early version?
+ at directory.register
+class KT9800(BTech):
+    """QYT KT8900"""
+    VENDOR = "QYT"
+    MODEL = "KT8900"
+    _vhf_range = (136000000, 175000000)
+    _uhf_range = (400000000, 481000000)
+    _magic = MSTRING_KT8900
+    _fileid = [KT8900_fp,
+               KT8900_fp1,
+               KT8900_fp2,
+               KT8900_fp3,
+               KT8900_fp4,
+               KT8900_fp5]
+    _id2 = KT8900_id
+    # Clones
+    ALIASES = [JT6188Mini, SSGT890, ZastoneMP300]
 
-            tmp = code.freq1
-            if tmp == 65535:
-                tmp = 0
-            val1 = RadioSettingValueInteger(0, 65535, tmp)
-            freq1 = RadioSetting("2tone_code_"+ str(i) + "_freq1", 
-                                 "Frequency 1", val1)
-            val1.set_validate_callback(_2tone_validate)
-            freq1.set_apply_callback(apply_2tone_freq, code.freq1)
-            code_2tone.append(freq1)
 
-            tmp = code.freq2
-            if tmp == 65535:
-                tmp = 0
-            val2 = RadioSettingValueInteger(0, 65535, tmp)
-            freq2 = RadioSetting("2tone_code_"+ str(i) + "_freq2", 
-                                 "Frequency 2", val2)
-            val2.set_validate_callback(_2tone_validate)
-            freq2.set_apply_callback(apply_2tone_freq, code.freq2)
-            code_2tone.append(freq2)
+ at directory.register
+class KT9800R(BTech):
+    """QYT KT8900R"""
+    VENDOR = "QYT"
+    MODEL = "KT8900R"
+    BANDS = 3
+    _vhf_range = (136000000, 175000000)
+    _220_range = (240000000, 271000000)
+    _uhf_range = (400000000, 481000000)
+    _magic = MSTRING_KT8900R
+    _fileid = [KT8900R_fp,
+               KT8900R_fp1,
+               KT8900R_fp2,
+               KT8900R_fp3,
+               KT8900R_fp4]
+    _id2 = KT8900R_id
 
-            i = i + 1
 
-        decode_reset_time = _mem._2tone.reset_time
-        if decode_reset_time == 255:
-            decode_reset_time = 59
-            LOG.debug("Decode reset time unconfigured. resetting.")
-        if decode_reset_time <= len(LIST_5TONE_RESET):
-            list = RadioSettingValueList(
-                LIST_5TONE_RESET,
-                LIST_5TONE_RESET[
-                    decode_reset_time])
-            line = RadioSetting("_2tone.reset_time",
-                                "Decode reset time", list)
-            decode_2tone.append(line)
-        else:
-            LOG.debug("Invalid value decode reset time! Disabling.")
+ at directory.register
+class LT588UV(BTech):
+    """LUITON LT-588UV"""
+    VENDOR = "LUITON"
+    MODEL = "LT-588UV"
+    _vhf_range = (136000000, 175000000)
+    _uhf_range = (400000000, 481000000)
+    _magic = MSTRING_KT8900
+    _fileid = [LT588UV_fp,
+               LT588UV_fp1]
+    _power_levels = [chirp_common.PowerLevel("High", watts=60),
+                     chirp_common.PowerLevel("Low", watts=10)]
 
-        def apply_2tone_freq_pair(setting, obj):
-            val = int(setting.value)
-            derived_val = 65535
-            frqname = str(setting._name[-5:])
-            derivedname = "derived_from_" + frqname
 
-            if (val == 0):
-                val = 65535
-                derived_val = 65535
-            else:
-                derived_val = int(round(2304000.0/val))
+COLOR_MEM_FORMAT = """
+#seekto 0x0000;
+struct {
+  lbcd rxfreq[4];
+  lbcd txfreq[4];
+  ul16 rxtone;
+  ul16 txtone;
+  u8 unknown0:4,
+     scode:4;
+  u8 unknown1:2,
+     spmute:1,
+     unknown2:3,
+     optsig:2;
+  u8 unknown3:3,
+     scramble:1,
+     unknown4:3,
+     power:1;
+  u8 unknown5:1,
+     wide:1,
+     unknown6:2,
+     bcl:1,
+     add:1,
+     pttid:2;
+} memory[200];
 
-            obj[frqname].set_value( val )
-            obj[derivedname].set_value( derived_val )
+#seekto 0x0E00;
+struct {
+  u8 tmr;
+  u8 unknown1;      
+  u8 sql;           
+  u8 unknown2[2];   
+  u8 tot;           
+  u8 apo;           
+  u8 unknown3;      
+  u8 abr;           
+  u8 beep;          
+  u8 unknown4[4];   
+  u8 dtmfst;        
+  u8 unknown5[2];   
+  u8 screv;         
+  u8 unknown6[2];
+  u8 pttid;
+  u8 pttlt;
+  u8 unknown7;
+  u8 emctp;         
+  u8 emcch;         
+  u8 sigbp;         
+  u8 unknown8;
+  u8 camdf;         
+  u8 cbmdf;         
+  u8 ccmdf;         
+  u8 cdmdf;         
+  u8 langua;
+  u8 sync;          // BTech radios use this as the display sync
+                    // setting, other radios use this as the auto
+                    // keypad lock setting
+  u8 mainfc;
+  u8 mainbc;        
+  u8 menufc;        
+  u8 menubc;        
+  u8 stafc;         
+  u8 stabc;         
+  u8 sigfc;         
+  u8 sigbc;         
+  u8 rxfc;          
+  u8 txfc;          
+  u8 txdisp;
+  u8 unknown9[5];
+  u8 anil;
+  u8 reps;
+  u8 repm;
+  u8 tmrmr;
+  u8 ste;
+  u8 rpste;
+  u8 rptdl;
+  u8 dtmfg;
+  u8 mgain;
+  u8 skiptx;
+  u8 scmode;
+} settings;
 
-            LOG.debug("Apply " + frqname + ": " + str(val) + " | " 
-                      + derivedname + ": " + str(derived_val))
+#seekto 0x0E80;
+struct {
+  u8 unknown1;
+  u8 vfomr;
+  u8 keylock;
+  u8 unknown2;
+  u8 unknown3:4,
+     vfomren:1,
+     unknown4:1,
+     reseten:1,
+     menuen:1;
+  u8 unknown5[11];
+  u8 dispab;        
+  u8 unknown6[2];   
+  u8 menu;          
+  u8 unknown7[7];   
+  u8 vfomra;        
+  u8 vfomrb;        
+  u8 vfomrc;        
+  u8 vfomrd;        
+  u8 mrcha;         
+  u8 mrchb;         
+  u8 mrchc;         
+  u8 mrchd;
+} settings2;
 
-        i = 1
-        for decode_code in  self._memobj._2tone._2tone_decode:
-            _2tone_dec_code = RadioSettingGroup ("code_2tone_" + str(i),
-                                           "Decode Code " + str(i))
-            decode_2tone.append(_2tone_dec_code)
+struct settings_vfo {
+  u8 freq[8];
+  u8 offset[6];
+  u8 unknown2[2];
+  ul16 rxtone;
+  ul16 txtone;
+  u8 scode;
+  u8 spmute;
+  u8 optsig;
+  u8 scramble;
+  u8 wide;
+  u8 power;
+  u8 shiftd;
+  u8 step;
+  u8 unknown3[4];
+};
 
-            j = 1
-            for dec in decode_code.decs:
-                val = dec.dec
-                if val == 255:
-                    LOG.debug("Dec for Code " + str(i) + " Dec " + str(j) +  
-                              " is not yet configured. Setting to 0.")
-                    val = 0
+#seekto 0x0F00;
+struct {
+  struct settings_vfo a;
+  struct settings_vfo b;
+  struct settings_vfo c;
+  struct settings_vfo d;
+} vfo;
 
-                if val <= len( LIST_2TONE_DEC ):
-                    line = RadioSetting(
-                        "_2tone_dec_settings_" + str(i) + "_dec_" + str(j),
-                        "Dec " + str(j), RadioSettingValueList
-                        (LIST_2TONE_DEC,
-                         LIST_2TONE_DEC[val]))
-                    line.set_apply_callback(apply_list_value, dec.dec)
-                    _2tone_dec_code.append(line)
-                else:
-                    LOG.debug("Invalid value for 2tone dec! Disabling.")
+#seekto 0x0F80;
+struct {
+  char line1[8];
+  char line2[8];
+  char line3[8];
+  char line4[8];
+  char line5[8];
+  char line6[8];
+  char line7[8];
+  char line8[8];
+} poweron_msg;
 
-                val = dec.response
-                if val == 255:
-                    LOG.debug("Response for Code " + str(i) + " Dec " + str(j)+
-                              " is not yet configured. Setting to 0.")
-                    val = 0
+#seekto 0x1000;
+struct {
+  char name[8];
+  u8 unknown1[8];
+} names[200];
 
-                if val <= len( LIST_2TONE_RESPONSE ):
-                    line = RadioSetting(
-                        "_2tone_dec_settings_" + str(i) + "_resp_" + str(j),
-                        "Response " + str(j), RadioSettingValueList
-                        (LIST_2TONE_RESPONSE,
-                         LIST_2TONE_RESPONSE[val]))
-                    line.set_apply_callback(apply_list_value, dec.response)
-                    _2tone_dec_code.append(line)
-                else:
-                    LOG.debug("Invalid value for 2tone response! Disabling.")
+#seekto 0x2400;
+struct {
+  u8 period; // one out of LIST_5TONE_STANDARD_PERIODS
+  u8 group_tone;
+  u8 repeat_tone;
+  u8 unused[13];
+} _5tone_std_settings[15];
 
-                val = dec.alert
-                if val == 255:
-                    LOG.debug("Alert for Code " + str(i) + " Dec " + str(j) +  
-                              " is not yet configured. Setting to 0.")
-                    val = 0
+#seekto 0x2500;
+struct {
+  u8 frame1[5];
+  u8 frame2[5];
+  u8 frame3[5];
+  u8 standard;   // one out of LIST_5TONE_STANDARDS
+} _5tone_codes[15];
 
-                if val <= len( PTTIDCODE_LIST ):
-                    line = RadioSetting(
-                        "_2tone_dec_settings_" + str(i) + "_alert_" + str(j),
-                        "Alert " + str(j), RadioSettingValueList
-                        (PTTIDCODE_LIST,
-                         PTTIDCODE_LIST[val]))
-                    line.set_apply_callback(apply_list_value, dec.alert)
-                    _2tone_dec_code.append(line)
-                else:
-                    LOG.debug("Invalid value for 2tone alert! Disabling.")
-                j = j + 1
+#seekto 0x25F0;
+struct {
+  u8 _5tone_delay1; // * 10ms
+  u8 _5tone_delay2; // * 10ms
+  u8 _5tone_delay3; // * 10ms
+  u8 _5tone_first_digit_ext_length;
+  u8 unknown1;
+  u8 unknown2;
+  u8 unknown3;
+  u8 unknown4;
+  u8 decode_standard;
+  u8 unknown5:5,
+     _5tone_decode_call_frame3:1,
+     _5tone_decode_call_frame2:1,
+     _5tone_decode_call_frame1:1;
+  u8 unknown6:5,
+     _5tone_decode_disp_frame3:1,
+     _5tone_decode_disp_frame2:1,
+     _5tone_decode_disp_frame1:1;
+  u8 decode_reset_time; // * 100 + 100ms
+} _5tone_settings;
 
-            freq = self._memobj._2tone.freqs[i-1]
-            for char in ['A', 'B', 'C', 'D']:
-                setting_name = "freq" + str(char)
+#seekto 0x2900;
+struct {
+  u8 code[16]; // 0=x0A, A=0x0D, B=0x0E, C=0x0F, D=0x00, #=0x0C *=0x0B
+} dtmf_codes[15];
 
-                tmp = freq[setting_name]
-                if tmp == 65535:
-                    tmp = 0
-                if tmp != 0:
-                    expected = int(round(2304000.0/tmp))
-                    from_mem = freq["derived_from_" + setting_name]
-                    if expected != from_mem:
-                        LOG.error("Expected " + str(expected) + 
-                                  " but read " + str(from_mem ) + 
-                                  ". Disabling 2Tone Decode Freqs!")
-                        break
-                val = RadioSettingValueInteger(0, 65535, tmp)
-                frq = RadioSetting("2tone_dec_"+ str(i) + "_freq" + str(char),
-                                         ("Decode Frequency " +str(char)), val)
-                val.set_validate_callback(_2tone_validate)
-                frq.set_apply_callback(apply_2tone_freq_pair, freq)
-                _2tone_dec_code.append(frq)
+#seekto 0x29F0;
+struct {
+  u8 dtmfspeed_on;  //list with 50..2000ms in steps of 10
+  u8 dtmfspeed_off; //list with 50..2000ms in steps of 10
+  u8 unknown0[14];
+  u8 inspection[16];
+  u8 monitor[16];
+  u8 alarmcode[16];
+  u8 stun[16];
+  u8 kill[16];
+  u8 revive[16];
+  u8 unknown1[16];
+  u8 unknown2[16];
+  u8 unknown3[16];
+  u8 unknown4[16];
+  u8 unknown5[16];
+  u8 unknown6[16];
+  u8 unknown7[16];
+  u8 masterid[16];
+  u8 viceid[16];
+  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; // * 100 + 100ms
+  u8 resettime;     // * 100 + 100ms
+} dtmf_settings;
 
-            i = i + 1
+#seekto 0x2D00;
+struct {
+  struct {
+    ul16 freq1;
+    u8 unused01[6];
+    ul16 freq2;
+    u8 unused02[6];
+  } _2tone_encode[15];
+  u8 duration_1st_tone; // *10ms
+  u8 duration_2nd_tone; // *10ms
+  u8 duration_gap;      // *10ms
+  u8 unused03[13];
+  struct {
+    struct {
+      u8 dec;      // one out of LIST_2TONE_DEC
+      u8 response; // one out of LIST_2TONE_RESPONSE
+      u8 alert;    // 1-16
+    } decs[4];
+    u8 unused04[4];
+  } _2tone_decode[15];
+  u8 unused05[16];
 
-        return top
+  struct {
+    ul16 freqA;
+    ul16 freqB;
+    ul16 freqC;
+    ul16 freqD;
+    // unknown what those values mean, but they are
+    // derived from configured frequencies
+    ul16 derived_from_freqA; // 2304000/freqA
+    ul16 derived_from_freqB; // 2304000/freqB
+    ul16 derived_from_freqC; // 2304000/freqC
+    ul16 derived_from_freqD; // 2304000/freqD
+  }freqs[15];
+  u8 reset_time;  // * 100 + 100ms - 100-8000ms
+} _2tone;
 
-    def set_settings(self, settings):
-        _settings = self._memobj.settings
-        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()
+#seekto 0x3D80;
+struct {
+  u8 vhf_low[3];
+  u8 vhf_high[3];
+  u8 unknown1[4];
+  u8 unknown2[6];
+  u8 vhf2_low[3];
+  u8 vhf2_high[3];
+  u8 unknown3[4];
+  u8 unknown4[6];
+  u8 uhf_low[3];
+  u8 uhf_high[3];
+  u8 unknown5[4];
+  u8 unknown6[6];
+  u8 uhf2_low[3];
+  u8 uhf2_high[3];
+} ranges;
 
-                    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
+#seekto 0x3F70;
+struct {
+  char fp[6];
+} fingerprint;
 
-    @classmethod
-    def match_model(cls, filedata, filename):
-        match_size = False
-        match_model = False
+"""
 
-        # testing the file data size
-        if len(filedata) == MEM_SIZE:
-            match_size = True
 
-        # testing the firmware model fingerprint
-        match_model = model_match(cls, filedata)
+class BTechColor(BTechMobileCommon):
+    """BTECH's Color LCD Mobile and alike radios"""
+    COLOR_LCD = True
+    NAME_LENGTH = 8
 
-        if match_size and match_model:
-            return True
-        else:
-            return False
+    def process_mmap(self):
+        """Process the mem map into the mem object"""
 
+        # Get it
+        self._memobj = bitwise.parse(COLOR_MEM_FORMAT, self._mmap)
 
-# Declaring Aliases (Clones of the real radios)
-class JT2705M(chirp_common.Alias):
-    VENDOR = "Jetstream"
-    MODEL = "JT2705M"
+        # load specific parameters from the radio image
+        self.set_options()
 
+    def set_options(self):
+        """This is to read the options from the image and set it in the
+        environment, for now just the limits of the freqs in the VHF/UHF
+        ranges"""
 
-class JT6188Mini(chirp_common.Alias):
-    VENDOR = "Juentai"
-    MODEL = "JT-6188 Mini"
+        # setting the correct ranges for each radio type
+        ranges = self._memobj.ranges
 
+        # the normal dual bands
+        vhf = _decode_ranges(ranges.vhf_low, ranges.vhf_high)
+        uhf = _decode_ranges(ranges.uhf_low, ranges.uhf_high)
 
-class JT6188Plus(chirp_common.Alias):
-    VENDOR = "Juentai"
-    MODEL = "JT-6188 Plus"
+        # DEBUG
+        LOG.info("Radio ranges: VHF %d to %d" % vhf)
+        LOG.info("Radio ranges: UHF %d to %d" % uhf)
 
+        # the additional bands
+        if self.MODEL in ["UV-25X4", "KT7900D"]:
+            # 200Mhz band
+            vhf2 = _decode_ranges(ranges.vhf2_low, ranges.vhf2_high)
+            LOG.info("Radio ranges: VHF(220) %d to %d" % vhf2)
+            self._220_range = vhf2
 
-class SSGT890(chirp_common.Alias):
-    VENDOR = "Sainsonic"
-    MODEL = "GT-890"
+            # 350Mhz band
+            uhf2 = _decode_ranges(ranges.uhf2_low, ranges.uhf2_high)
+            LOG.info("Radio ranges: UHF(350) %d to %d" % uhf2)
+            self._350_range = uhf2
 
+        # set the class with the real data
+        self._vhf_range = vhf
+        self._uhf_range = uhf
+    
 
-class ZastoneMP300(chirp_common.Alias):
-    VENDOR = "Zastone"
-    MODEL = "MP-300"
+# Declaring Aliases (Clones of the real radios)
+class SKT8900D(chirp_common.Alias):
+    VENDOR = "Surecom"
+    MODEL = "S-KT8900D"
 
 
 # real radios
 @directory.register
-class UV2501(BTech):
-    """Baofeng Tech UV2501"""
-    MODEL = "UV-2501"
-    _fileid = [UV2501G3_fp,
-               UV2501G2_fp,
-               UV2501pp2_fp,
-               UV2501pp_fp]
-
-
- at directory.register
-class UV2501_220(BTech):
-    """Baofeng Tech UV2501+220"""
-    MODEL = "UV-2501+220"
-    _magic = MSTRING_220
-    _id2 = UV2501_220pp_id
-    _fileid = [UV2501_220G3_fp,
-               UV2501_220G2_fp,
-               UV2501_220_fp,
-               UV2501_220pp_fp]
+class UV25X2(BTechColor):
+    """Baofeng Tech UV25X2"""
+    MODEL = "UV-25X2"
+    BANDS = 2
+    _vhf_range = (130000000, 180000000)
+    _uhf_range = (400000000, 521000000)
+    _magic = MSTRING_UV25X2
+    _fileid = [UV25X2_fp, ]
 
 
 @directory.register
-class UV5001(BTech):
-    """Baofeng Tech UV5001"""
-    MODEL = "UV-5001"
-    _fileid = [UV5001G3_fp,
-               UV5001G22_fp,
-               UV5001G2_fp,
-               UV5001alpha_fp,
-               UV5001pp_fp]
+class UV25X4(BTechColor):
+    """Baofeng Tech UV25X4"""
+    MODEL = "UV-25X4"
+    BANDS = 4
+    _vhf_range = (130000000, 180000000)
+    _220_range = (200000000, 271000000)
+    _uhf_range = (400000000, 521000000)
+    _350_range = (350000000, 391000000)
+    _magic = MSTRING_UV25X4
+    _fileid = [UV25X4_fp, ]
 
 
 @directory.register
-class MINI8900(BTech):
-    """WACCOM MINI-8900"""
-    VENDOR = "WACCOM"
-    MODEL = "MINI-8900"
-    _magic = MSTRING_MINI8900
-    _fileid = [MINI8900_fp, ]
-    # Clones
-    ALIASES = [JT6188Plus, ]
-
+class UV50X2(BTechColor):
+    """Baofeng Tech UV50X2"""
+    MODEL = "UV-50X2"
+    BANDS = 2
+    _vhf_range = (130000000, 180000000)
+    _uhf_range = (400000000, 521000000)
+    _magic = MSTRING_UV25X2
+    _fileid = [UV50X2_fp, ]
+    _power_levels = [chirp_common.PowerLevel("High", watts=50),
+                     chirp_common.PowerLevel("Low", watts=10)]
 
- at directory.register
-class KTUV980(BTech):
-    """QYT KT-UV980"""
-    VENDOR = "QYT"
-    MODEL = "KT-UV980"
-    _vhf_range = (136000000, 175000000)
-    _uhf_range = (400000000, 481000000)
-    _magic = MSTRING_MINI8900
-    _fileid = [KTUV980_fp, ]
-    # Clones
-    ALIASES = [JT2705M, ]
 
-# Please note that there is a version of this radios that is a clone of the
-# Waccom Mini8900, maybe an early version?
 @directory.register
-class KT9800(BTech):
-    """QYT KT8900"""
+class KT7900D(BTechColor):
+    """QYT KT7900D"""
     VENDOR = "QYT"
-    MODEL = "KT8900"
+    MODEL = "KT7900D"
+    BANDS = 4
     _vhf_range = (136000000, 175000000)
+    _220_range = (200000000, 271000000)
     _uhf_range = (400000000, 481000000)
-    _magic = MSTRING_KT8900
-    _fileid = [KT8900_fp,
-               KT8900_fp1,
-               KT8900_fp2,
-               KT8900_fp3,
-               KT8900_fp4,
-               KT8900_fp5]
-    _id2 = KT8900_id
+    _350_range = (350000000, 371000000)
+    _magic = MSTRING_KT8900D
+    _fileid = [KT7900D_fp, ]
     # Clones
-    ALIASES = [JT6188Mini, SSGT890, ZastoneMP300]
+    ALIASES = [SKT8900D, ]
 
 
 @directory.register
-class KT9800R(BTech):
-    """QYT KT8900R"""
+class KT8900D(BTechColor):
+    """QYT KT8900D"""
     VENDOR = "QYT"
-    MODEL = "KT8900R"
-    _vhf_range = (136000000, 175000000)
-    _220_range = (240000000, 271000000)
-    _uhf_range = (400000000, 481000000)
-    _magic = MSTRING_KT8900R
-    _fileid = [KT8900R_fp,
-               KT8900R_fp1,
-               KT8900R_fp2,
-               KT8900R_fp3,
-               KT8900R_fp4]
-    _id2 = KT8900R_id
-
-
- at directory.register
-class LT588UV(BTech):
-    """LUITON LT-588UV"""
-    VENDOR = "LUITON"
-    MODEL = "LT-588UV"
+    MODEL = "KT8900D"
+    BANDS = 2
     _vhf_range = (136000000, 175000000)
     _uhf_range = (400000000, 481000000)
-    _magic = MSTRING_KT8900
-    _fileid = [LT588UV_fp,
-               LT588UV_fp1]
+    _magic = MSTRING_KT8900D
+    _fileid = [KT8900D_fp, ]
diff --git a/chirp/drivers/ft1d.py b/chirp/drivers/ft1d.py
index 8616d3c..1c3550a 100644
--- a/chirp/drivers/ft1d.py
+++ b/chirp/drivers/ft1d.py
@@ -20,14 +20,15 @@ import logging
 
 from chirp.drivers import yaesu_clone
 from chirp import chirp_common, directory, bitwise
-from chirp.settings import RadioSettingGroup, RadioSetting, RadioSettings
-from chirp.settings import RadioSettingValueInteger, RadioSettingValueString
-from chirp.settings import RadioSettingValueList, RadioSettingValueBoolean
+from chirp.settings import RadioSettingGroup, RadioSetting, RadioSettings, \
+            RadioSettingValueInteger, RadioSettingValueString, \
+            RadioSettingValueList, RadioSettingValueBoolean, \
+            InvalidValueError
 from textwrap import dedent
 
 LOG = logging.getLogger(__name__)
 
-MEM_FORMAT = """
+MEM_SETTINGS_FORMAT = """
 #seekto 0x049a;
 struct {
   u8 vfo_a;
@@ -141,17 +142,21 @@ struct {
   u8 unknown[2];
   u8 name[16];
 } bank_info[24];
+"""
 
+MEM_FORMAT = """
 #seekto 0x2D4A;
 struct {
-  u8 unknown1;
+  u8 unknown0:2,
+     mode_alt:1,  // mode for FTM-3200D
+     unknown1:5;
   u8 mode:2,
      duplex:2,
      tune_step:4;
   bbcd freq[3];
   u8 power:2,
-     unknown2:4,
-     tone_mode:2;
+     unknown2:2,
+     tone_mode:4;
   u8 charsetbits[2];
   char label[16];
   bbcd offset[3];
@@ -160,7 +165,7 @@ struct {
   u8 unknown6:1,
      dcs:7;
   u8 unknown7[3];
-} memory[900];
+} memory[%d];
 
 #seekto 0x280A;
 struct {
@@ -170,8 +175,10 @@ struct {
      skip:1,
      used:1,
      valid:1;
-} flag[900];
+} flag[%d];
+"""
 
+MEM_APRS_FORMAT = """
 #seekto 0xbeca;
 struct {
   u8 rx_baud;
@@ -334,7 +341,33 @@ struct {
   char path_and_body[66];
   u8 unknown[70];
 } aprs_message_pkt[60];
+"""
 
+MEM_BACKTRACK_FORMAT = """
+#seekto 0xdf06;
+struct {
+  u8 status; // 01 full 08 empty
+  u8 reserved0; // 00
+  bbcd year; // 17
+  bbcd mon; // 06
+  bbcd day; // 01
+  u8 reserved1; // 06
+  bbcd hour; // 21
+  bbcd min; // xx
+  u8 reserved2; // 00
+  u8 reserved3; // 00
+  char NShemi[1];
+  char lat[3];
+  char lat_min[2];
+  char lat_dec_sec[4];
+  char WEhemi[1];
+  char lon[3];
+  char lon_min[2];
+  char lon_dec_sec[4];
+} backtrack[3];
+
+"""
+MEM_CHECKSUM_FORMAT = """
 #seekto 0x1FDC9;
 u8 checksum;
 """
@@ -500,11 +533,7 @@ class FT1BankModel(chirp_common.BankModel):
         return banks
 
 
-def _wipe_memory(mem):
-    mem.set_raw("\x00" * (mem.size() / 8))
-    mem.unknown1 = 0x05
-
-
+# Note: other radios like FTM3200Radio subclass this radio
 @directory.register
 class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
     """Yaesu FT1DR"""
@@ -517,7 +546,9 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
     _memsize = 130507
     _block_lengths = [10, 130497]
     _block_size = 32
-    _mem_params = (0xFECA,         # APRS beacon metadata address.
+    _mem_params = (900,            # size of memories array
+                   900,            # size of flags array
+                   0xFECA,         # APRS beacon metadata address.
                    60,             # Number of beacons stored.
                    0x1064A,        # APRS beacon content address.
                    134,            # Length of beacon data stored.
@@ -590,6 +621,9 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
     _DTMF_SPEED = ("50ms", "100ms")
     _DTMF_DELAY = ("50ms", "250ms", "450ms", "750ms", "1000ms")
     _MY_SYMBOL = ("/[ Person", "/b Bike", "/> Car", "User selected")
+    _BACKTRACK_STATUS = ("Valid", "Invalid")
+    _NS_HEMI = ("N", "S")
+    _WE_HEMI = ("W", "E")
 
     @classmethod
     def get_prompts(cls):
@@ -610,7 +644,9 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
         return rp
 
     def process_mmap(self):
-        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
+        mem_format = MEM_SETTINGS_FORMAT + MEM_FORMAT + MEM_APRS_FORMAT + \
+                MEM_BACKTRACK_FORMAT + MEM_CHECKSUM_FORMAT
+        self._memobj = bitwise.parse(mem_format % self._mem_params, self._mmap)
 
     def get_features(self):
         rf = chirp_common.RadioFeatures()
@@ -632,7 +668,8 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
         return rf
 
     def get_raw_memory(self, number):
-        return repr(self._memobj.memory[number])
+        return "\n".join([repr(self._memobj.memory[number - 1]),
+                          repr(self._memobj.flag[number - 1])])
 
     def _checksums(self):
         return [yaesu_clone.YaesuChecksum(0x064A, 0x06C8),
@@ -666,21 +703,53 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
         mem.freq = chirp_common.fix_rounded_step(int(_mem.freq) * 1000)
         mem.offset = int(_mem.offset) * 1000
         mem.rtone = mem.ctone = chirp_common.TONES[_mem.tone]
-        mem.tmode = TMODES[_mem.tone_mode]
+        self._get_tmode(mem, _mem)
         mem.duplex = DUPLEX[_mem.duplex]
         if mem.duplex == "split":
             mem.offset = chirp_common.fix_rounded_step(mem.offset)
-        mem.mode = MODES[_mem.mode]
+        mem.mode = self._decode_mode(_mem)
         mem.dtcs = chirp_common.DTCS_CODES[_mem.dcs]
         mem.tuning_step = STEPS[_mem.tune_step]
-        mem.power = POWER_LEVELS[3 - _mem.power]
+        mem.power = self._decode_power_level(_mem)
         mem.skip = flag.pskip and "P" or flag.skip and "S" or ""
 
-        charset = ''.join(CHARSET).ljust(256, '.')
-        mem.name = str(_mem.label).rstrip("\xFF").translate(charset)
+        mem.name = self._decode_label(_mem)
 
         return mem
 
+    def _decode_label(self, mem):
+        charset = ''.join(CHARSET).ljust(256, '.')
+        return str(mem.label).rstrip("\xFF").translate(charset)
+
+    def _encode_label(self, mem):
+        label = "".join([chr(CHARSET.index(x)) for x in mem.name.rstrip()])
+        return self._add_ff_pad(label, 16)
+
+    def _encode_charsetbits(self, mem):
+        # We only speak english here in chirpville
+        return [0x00, 0x00]
+
+    def _decode_power_level(self, mem):
+        return POWER_LEVELS[3 - mem.power]
+
+    def _encode_power_level(self, mem):
+        return 3 - POWER_LEVELS.index(mem.power)
+
+    def _decode_mode(self, mem):
+        return MODES[mem.mode]
+
+    def _encode_mode(self, mem):
+        return MODES.index(mem.mode)
+
+    def _get_tmode(self, mem, _mem):
+        mem.tmode = TMODES[_mem.tone_mode]
+
+    def _set_tmode(self, _mem, mem):
+        _mem.tone_mode = TMODES.index(mem.tmode)
+
+    def _set_mode(self, _mem, mem):
+        _mem.mode = self._encode_mode(mem)
+
     def _debank(self, mem):
         bm = self.get_bank_model()
         for bank in bm.get_memory_mappings(mem):
@@ -693,7 +762,7 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
         self._debank(mem)
 
         if not mem.empty and not flag.valid:
-            _wipe_memory(_mem)
+            self._wipe_memory(_mem)
 
         if mem.empty and flag.valid and not flag.used:
             flag.valid = False
@@ -714,25 +783,28 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
         _mem.freq = int(mem.freq / 1000)
         _mem.offset = int(mem.offset / 1000)
         _mem.tone = chirp_common.TONES.index(mem.rtone)
-        _mem.tone_mode = TMODES.index(mem.tmode)
+        self._set_tmode(_mem, mem)
         _mem.duplex = DUPLEX.index(mem.duplex)
-        _mem.mode = MODES.index(mem.mode)
+        self._set_mode(_mem, mem)
         _mem.dcs = chirp_common.DTCS_CODES.index(mem.dtcs)
         _mem.tune_step = STEPS.index(mem.tuning_step)
         if mem.power:
-            _mem.power = 3 - POWER_LEVELS.index(mem.power)
+            _mem.power = self._encode_power_level(mem)
         else:
             _mem.power = 0
 
-        label = "".join([chr(CHARSET.index(x)) for x in mem.name.rstrip()])
-        _mem.label = self._add_ff_pad(label, 16)
-        # We only speak english here in chirpville
-        _mem.charsetbits[0] = 0x00
-        _mem.charsetbits[1] = 0x00
+        _mem.label = self._encode_label(mem)
+        charsetbits = self._encode_charsetbits(mem)
+        _mem.charsetbits[0], _mem.charsetbits[1] = charsetbits
 
         flag.skip = mem.skip == "S"
         flag.pskip = mem.skip == "P"
 
+    @classmethod
+    def _wipe_memory(cls, mem):
+        mem.set_raw("\x00" * (mem.size() / 8))
+        mem.unknown1 = 0x05
+
     def get_bank_model(self):
         return FT1BankModel(self)
 
@@ -1412,6 +1484,183 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
 
         return menu
 
+    def backtrack_ll_validate(self, number, min, max):
+        if str(number).lstrip('0').strip().isdigit() and \
+                int(str(number).lstrip('0')) <= max and \
+                int(str(number).lstrip('0')) >= min:
+            return True
+
+        return False
+
+    def backtrack_zero_pad(self, number, l):
+        number = str(number).strip()
+        while len(number) < l:
+            number = '0' + number
+
+        return str(number)
+
+    def _get_backtrack_settings(self):
+
+        menu = RadioSettingGroup("backtrack", "Backtrack")
+
+        for i in range(3):
+            prefix = ''
+            if i == 0:
+                prefix = "Star "
+            if i == 1:
+                prefix = "L1 "
+            if i == 2:
+                prefix = "L2 "
+
+            bt_idx = "backtrack[%d]" % i
+
+            bt = self._memobj.backtrack[i]
+
+            val = RadioSettingValueList(
+                self._BACKTRACK_STATUS,
+                self._BACKTRACK_STATUS[0 if bt.status == 1 else 1])
+            rs = RadioSetting(
+                    "%s.status" % bt_idx,
+                    prefix + "status", val)
+            rs.set_apply_callback(self.apply_backtrack_status, bt)
+            menu.append(rs)
+
+            if bt.status == 1 and int(bt.year) < 100:
+                val = RadioSettingValueInteger(0, 99, bt.year)
+            else:
+                val = RadioSettingValueInteger(0, 99, 0)
+            rs = RadioSetting(
+                    "%s.year" % bt_idx,
+                    prefix + "year", val)
+            menu.append(rs)
+
+            if bt.status == 1 and int(bt.mon) <= 12:
+                val = RadioSettingValueInteger(0, 12, bt.mon)
+            else:
+                val = RadioSettingValueInteger(0, 12, 0)
+            rs = RadioSetting(
+                    "%s.mon" % bt_idx,
+                    prefix + "month", val)
+            menu.append(rs)
+
+            if bt.status == 1:
+                val = RadioSettingValueInteger(0, 31, bt.day)
+            else:
+                val = RadioSettingValueInteger(0, 31, 0)
+            rs = RadioSetting(
+                    "%s.day" % bt_idx,
+                    prefix + "day", val)
+            menu.append(rs)
+
+            if bt.status == 1:
+                val = RadioSettingValueInteger(0, 23, bt.hour)
+            else:
+                val = RadioSettingValueInteger(0, 23, 0)
+            rs = RadioSetting(
+                    "%s.hour" % bt_idx,
+                    prefix + "hour", val)
+            menu.append(rs)
+
+            if bt.status == 1:
+                val = RadioSettingValueInteger(0, 59, bt.min)
+            else:
+                val = RadioSettingValueInteger(0, 59, 0)
+            rs = RadioSetting(
+                    "%s.min" % bt_idx,
+                    prefix + "min", val)
+            menu.append(rs)
+
+            if bt.status == 1 and \
+                    (str(bt.NShemi) == 'N' or str(bt.NShemi) == 'S'):
+                val = RadioSettingValueString(0, 1, str(bt.NShemi))
+            else:
+                val = RadioSettingValueString(0, 1, ' ')
+            rs = RadioSetting(
+                    "%s.NShemi" % bt_idx,
+                    prefix + "NS hemisphere", val)
+            rs.set_apply_callback(self.apply_NShemi, bt)
+            menu.append(rs)
+
+            if bt.status == 1 and self.backtrack_ll_validate(bt.lat, 0, 90):
+                val = RadioSettingValueString(
+                        0, 3, self.backtrack_zero_pad(bt.lat, 3))
+            else:
+                val = RadioSettingValueString(0, 3, '   ')
+            rs = RadioSetting("%s.lat" % bt_idx, prefix + "Latitude", val)
+            rs.set_apply_callback(self.apply_bt_lat, bt)
+            menu.append(rs)
+
+            if bt.status == 1 and \
+                    self.backtrack_ll_validate(bt.lat_min, 0, 59):
+                val = RadioSettingValueString(
+                    0, 2, self.backtrack_zero_pad(bt.lat_min, 2))
+            else:
+                val = RadioSettingValueString(0, 2, '  ')
+            rs = RadioSetting(
+                    "%s.lat_min" % bt_idx,
+                    prefix + "Latitude Minutes", val)
+            rs.set_apply_callback(self.apply_bt_lat_min, bt)
+            menu.append(rs)
+
+            if bt.status == 1 and \
+                    self.backtrack_ll_validate(bt.lat_dec_sec, 0, 9999):
+                val = RadioSettingValueString(
+                    0, 4, self.backtrack_zero_pad(bt.lat_dec_sec, 4))
+            else:
+                val = RadioSettingValueString(0, 4, '    ')
+            rs = RadioSetting(
+                    "%s.lat_dec_sec" % bt_idx,
+                    prefix + "Latitude Decimal Seconds", val)
+            rs.set_apply_callback(self.apply_bt_lat_dec_sec, bt)
+            menu.append(rs)
+
+            if bt.status == 1 and \
+                    (str(bt.WEhemi) == 'W' or str(bt.WEhemi) == 'E'):
+                val = RadioSettingValueString(
+                    0, 1, str(bt.WEhemi))
+            else:
+                val = RadioSettingValueString(0, 1, ' ')
+            rs = RadioSetting(
+                    "%s.WEhemi" % bt_idx,
+                    prefix + "WE hemisphere", val)
+            rs.set_apply_callback(self.apply_WEhemi, bt)
+            menu.append(rs)
+
+            if bt.status == 1 and self.backtrack_ll_validate(bt.lon, 0, 180):
+                val = RadioSettingValueString(
+                    0, 3, self.backtrack_zero_pad(bt.lon, 3))
+            else:
+                val = RadioSettingValueString(0, 3, '   ')
+            rs = RadioSetting("%s.lon" % bt_idx, prefix + "Longitude", val)
+            rs.set_apply_callback(self.apply_bt_lon, bt)
+            menu.append(rs)
+
+            if bt.status == 1 and \
+                    self.backtrack_ll_validate(bt.lon_min, 0, 59):
+                val = RadioSettingValueString(
+                    0, 2, self.backtrack_zero_pad(bt.lon_min, 2))
+            else:
+                val = RadioSettingValueString(0, 2, '  ')
+            rs = RadioSetting(
+                    "%s.lon_min" % bt_idx,
+                    prefix + "Longitude Minutes", val)
+            rs.set_apply_callback(self.apply_bt_lon_min, bt)
+            menu.append(rs)
+
+            if bt.status == 1 and \
+                    self.backtrack_ll_validate(bt.lon_dec_sec, 0, 9999):
+                val = RadioSettingValueString(
+                    0, 4, self.backtrack_zero_pad(bt.lon_dec_sec, 4))
+            else:
+                val = RadioSettingValueString(0, 4, '    ')
+            rs = RadioSetting(
+                "%s.lon_dec_sec" % bt_idx,
+                prefix + "Longitude Decimal Seconds", val)
+            rs.set_apply_callback(self.apply_bt_lon_dec_sec, bt)
+            menu.append(rs)
+
+        return menu
+
     def _get_scan_settings(self):
         menu = RadioSettingGroup("scan_settings", "Scan")
         scan_settings = self._memobj.scan_settings
@@ -1492,7 +1741,8 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
                             self._get_aprs_beacons(),
                             self._get_dtmf_settings(),
                             self._get_misc_settings(),
-                            self._get_scan_settings())
+                            self._get_scan_settings(),
+                            self._get_backtrack_settings())
         return top
 
     def get_settings(self):
@@ -1647,3 +1897,69 @@ class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
         for x in range(len(val), 16):
             val.append(0xFF)
         cls._memobj.dtmf[i].memory = val
+
+    def apply_backtrack_status(cls, setting, obj):
+        status = setting.value.get_value()
+
+        if status == 'Valid':
+            val = 1
+        else:
+            val = 8
+        setattr(obj, "status", val)
+
+    def apply_NShemi(cls, setting, obj):
+        hemi = setting.value.get_value().upper()
+
+        if hemi != 'N' and hemi != 'S':
+            hemi = ' '
+        setattr(obj, "NShemi", hemi)
+
+    def apply_WEhemi(cls, setting, obj):
+        hemi = setting.value.get_value().upper()
+
+        if hemi != 'W' and hemi != 'E':
+            hemi = ' '
+        setattr(obj, "WEhemi", hemi)
+
+    def apply_WEhemi(cls, setting, obj):
+        hemi = setting.value.get_value().upper()
+
+        if hemi != 'W' and hemi != 'E':
+            hemi = ' '
+        setattr(obj, "WEhemi", hemi)
+
+    def apply_bt_lat(cls, setting, obj):
+        val = setting.value.get_value()
+        val = cls.backtrack_zero_pad(val, 3)
+
+        setattr(obj, "lat", val)
+
+    def apply_bt_lat_min(cls, setting, obj):
+        val = setting.value.get_value()
+        val = cls.backtrack_zero_pad(val, 2)
+
+        setattr(obj, "lat_min", val)
+
+    def apply_bt_lat_dec_sec(cls, setting, obj):
+        val = setting.value.get_value()
+        val = cls.backtrack_zero_pad(val, 4)
+
+        setattr(obj, "lat_dec_sec", val)
+
+    def apply_bt_lon(cls, setting, obj):
+        val = setting.value.get_value()
+        val = cls.backtrack_zero_pad(val, 3)
+
+        setattr(obj, "lon", val)
+
+    def apply_bt_lon_min(cls, setting, obj):
+        val = setting.value.get_value()
+        val = cls.backtrack_zero_pad(val, 2)
+
+        setattr(obj, "lon_min", val)
+
+    def apply_bt_lon_dec_sec(cls, setting, obj):
+        val = setting.value.get_value()
+        val = cls.backtrack_zero_pad(val, 4)
+
+        setattr(obj, "lon_dec_sec", val)
diff --git a/chirp/drivers/ft2d.py b/chirp/drivers/ft2d.py
new file mode 100644
index 0000000..50ff482
--- /dev/null
+++ b/chirp/drivers/ft2d.py
@@ -0,0 +1,128 @@
+# Copyright 2010 Dan Smith <dsmith at danplanet.com>
+# Portions Copyright 2017 Wade Simmons <wade at wades.im>
+# Copyright 2017 Declan Rieb <darieb at comcast.net>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+from textwrap import dedent
+
+from chirp.drivers import yaesu_clone, ft1d
+from chirp import chirp_common, directory, bitwise
+from chirp.settings import RadioSettings
+
+# Differences from Yaesu FT1D
+#  999 memories, but 901-999 are only for skipping VFO frequencies
+#  Text in memory and memory bank structures is ASCII encoded
+#  Expanded modes
+#  Slightly different clone-mode instructions
+
+LOG = logging.getLogger(__name__)
+
+TMODES = ["", "Tone", "TSQL", "DTCS", "RTone", "JRfrq", "PRSQL", "Pager"]
+
+class FT2Bank(chirp_common.NamedBank): # Like FT1D except for name in ASCII
+    def get_name(self):
+        _bank = self._model._radio._memobj.bank_info[self.index]
+        name = ""
+        for i in _bank.name:
+            if i == 0xff:
+                break
+            name += chr(i & 0xFF)
+        return name.rstrip()
+
+    def set_name(self, name):
+        _bank = self._model._radio._memobj.bank_info[self.index]
+        _bank.name = [ord(x) for x in name.ljust(16, chr(0xFF))[:16]]
+
+class FT2BankModel(ft1d.FT1BankModel): #just need this one to launch FT2Bank
+    """A FT1D bank model"""
+    def __init__(self, radio, name='Banks'):
+        super(FT2BankModel, self).__init__(radio, name)
+
+        _banks = self._radio._memobj.bank_info
+        self._bank_mappings = []
+        for index, _bank in enumerate(_banks):
+            bank = FT2Bank(self, "%i" % index, "BANK-%i" % index)
+            bank.index = index
+            self._bank_mappings.append(bank)
+
+ at directory.register
+class FT2D(ft1d.FT1Radio):
+    """Yaesu FT-2D"""
+    BAUD_RATE = 38400
+    VENDOR = "Yaesu"
+    MODEL = "FT2D" # Yaesu doesn't use a hyphen in its documents
+    VARIANT = "R"
+
+    _model = "AH60M" # Get this from chirp .img file after saving once
+    _has_vibrate = True
+    _mem_params = (999,            # size of memories array
+                   999,            # size of flags array
+                   0xFECA,         # APRS beacon metadata address.
+                   60,             # Number of beacons stored.
+                   0x1064A,        # APRS beacon content address.
+                   134,            # Length of beacon data stored.
+                   60)             # Number of beacons stored.
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.pre_download = _(dedent("""\
+         1. Turn radio off.
+         2. Connect cable to DATA terminal.
+         3. Press and hold [DISP] key while turning on radio
+              ("CLONE" will appear on the display).
+         4. <b>After clicking OK here in chirp</b>, 
+              press the [Send] screen button."""))
+        rp.pre_upload = _(dedent("""\
+         1. Turn radio off.
+         2. Connect cable to DATA terminal.
+         3. Press and hold in [DISP] key while turning on radio
+              ("CLONE" will appear on radio LCD).
+         4. Press [RECEIVE] screen button
+              ("-WAIT-" will appear on radio LCD).
+        5. Finally, press OK button below."""))
+        return rp
+
+    def get_features(self): # AFAICT only TMODES & memory bounds are different
+        rf = super(FT2D, self).get_features()
+        rf.valid_tmodes = list(TMODES)
+        rf.memory_bounds = (1, 999)
+        return rf
+
+    def get_bank_model(self):   # here only to launch the bank model
+        return FT2BankModel(self)
+
+    def get_memory(self, number):
+        mem = super(FT2D, self).get_memory(number)
+        flag = self._memobj.flag[number - 1]
+        if number >= 901 and number <= 999: # for FT2D; enforces skip
+            mem.skip = "S"
+            flag.skip = True
+        return mem
+
+    def _decode_label(self, mem):
+        return str(mem.label).rstrip("\xFF").decode('ascii', 'replace')
+
+    def _encode_label(self, mem):
+        label = mem.name.rstrip().encode('ascii', 'ignore')
+        return self._add_ff_pad(label, 16)
+
+    def set_memory(self, mem):
+        flag = self._memobj.flag[mem.number - 1]
+        if mem.number >= 901 and mem.number <= 999: # for FT2D; enforces skip
+            flag.skip = True
+            mem.skip = "S"
+        super(FT2D, self).set_memory(mem)
\ No newline at end of file
diff --git a/chirp/drivers/ft857.py b/chirp/drivers/ft857.py
index dee850e..5a8fe32 100644
--- a/chirp/drivers/ft857.py
+++ b/chirp/drivers/ft857.py
@@ -189,8 +189,8 @@ class FT857Radio(ft817.FT817Radio):
                 pkt1200:7;
             u8  unknown15:1,
                 pkt9600:7;
-            il16 dig_shift;
-            il16 dig_disp;
+            i16 dig_shift;
+            i16 dig_disp;
             i8  r_lsb_car;
             i8  r_usb_car;
             i8  t_lsb_car;
diff --git a/chirp/drivers/ftm3200d.py b/chirp/drivers/ftm3200d.py
new file mode 100644
index 0000000..1b27012
--- /dev/null
+++ b/chirp/drivers/ftm3200d.py
@@ -0,0 +1,201 @@
+# Copyright 2010 Dan Smith <dsmith at danplanet.com>
+# Copyright 2017 Wade Simmons <wade at wades.im>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+from textwrap import dedent
+
+from chirp.drivers import yaesu_clone, ft1d
+from chirp import chirp_common, directory, bitwise
+from chirp.settings import RadioSettings
+
+LOG = logging.getLogger(__name__)
+
+POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5),
+                chirp_common.PowerLevel("Mid", watts=30),
+                chirp_common.PowerLevel("Hi", watts=65)]
+
+TMODES = ["", "Tone", "TSQL", "DTCS", "TSQL-R", None, None, "Pager", "Cross"]
+CROSS_MODES = [None, "DTCS->", "Tone->DTCS", "DTCS->Tone"]
+
+MODES = ["FM", "NFM"]
+STEPS = [0, 5, 6.25, 10, 12.5, 15, 20, 25, 50, 100]  # 0 = auto
+RFSQUELCH = ["OFF", "S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8"]
+
+# Charset is subset of ASCII + some unknown chars \x80-\x86
+VALID_CHARS = ["%i" % int(x) for x in range(0, 10)] + \
+    list(":>=<?@") + \
+    [chr(x) for x in range(ord("A"), ord("Z") + 1)] + \
+    list("[\\]_") + \
+    [chr(x) for x in range(ord("a"), ord("z") + 1)] + \
+    list("%*+,-/=$ ")
+
+MEM_FORMAT = """
+#seekto 0xceca;
+struct {
+  u8 unknown5;
+  u8 unknown3;
+  u8 unknown4:6,
+     dsqtype:2;
+  u8 dsqcode;
+  u8 unknown1[2];
+  char mycall[10];
+  u8 unknown2[368];
+} settings;
+
+#seekto 0xfec9;
+u8 checksum;
+"""
+
+
+ at directory.register
+class FTM3200Radio(ft1d.FT1Radio):
+    """Yaesu FTM-3200D"""
+    BAUD_RATE = 38400
+    VENDOR = "Yaesu"
+    MODEL = "FTM-3200D"
+    VARIANT = "R"
+
+    _model = "AH52N"
+    _memsize = 65227
+    _block_lengths = [10, 65217]
+    _has_vibrate = False
+    _has_af_dual = False
+
+    _mem_params = (199,            # size of memories array
+                   199)            # size of flags array
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.pre_download = _(dedent("""\
+            1. Turn radio off.
+            2. Connect cable to DATA terminal.
+            3. Press and hold in the [MHz(SETUP)] key while turning the radio
+                 on ("CLONE" will appear on the display).
+            4. <b>After clicking OK</b>, press the [REV(DW)] key
+                 to send image."""))
+        rp.pre_upload = _(dedent("""\
+            1. Turn radio off.
+            2. Connect cable to DATA terminal.
+            3. Press and hold in the [MHz(SETUP)] key while turning the radio
+                 on ("CLONE" will appear on the display).
+            4. Press the [MHz(SETUP)] key
+                 ("-WAIT-" will appear on the LCD)."""))
+        return rp
+
+    def process_mmap(self):
+        mem_format = ft1d.MEM_FORMAT + MEM_FORMAT
+        self._memobj = bitwise.parse(mem_format % self._mem_params, self._mmap)
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.has_dtcs_polarity = False
+        rf.valid_modes = list(MODES)
+        rf.valid_tmodes = [x for x in TMODES if x is not None]
+        rf.valid_cross_modes = [x for x in CROSS_MODES if x is not None]
+        rf.valid_duplexes = list(ft1d.DUPLEX)
+        rf.valid_tuning_steps = list(STEPS)
+        rf.valid_bands = [(136000000, 174000000)]
+        # rf.valid_skips = SKIPS
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_characters = "".join(VALID_CHARS)
+        rf.valid_name_length = 8
+        rf.memory_bounds = (1, 199)
+        rf.can_odd_split = True
+        rf.has_ctone = False
+        rf.has_bank = False
+        rf.has_bank_names = False
+        # disable until implemented
+        rf.has_settings = False
+        return rf
+
+    def _decode_label(self, mem):
+        # TODO preserve the unknown \x80-x86 chars?
+        return str(mem.label).rstrip("\xFF").decode('ascii', 'replace')
+
+    def _encode_label(self, mem):
+        label = mem.name.rstrip().encode('ascii', 'ignore')
+        return self._add_ff_pad(label, 16)
+
+    def _encode_charsetbits(self, mem):
+        # TODO this is a setting to decide if the memory should be displayed
+        # as a name or frequency. Should we expose this setting to the user
+        # instead of autoselecting it (and losing their preference)?
+        if mem.name.rstrip() == '':
+            return [0x00, 0x00]
+        return [0x00, 0x80]
+
+    def _decode_power_level(self, mem):
+        return POWER_LEVELS[mem.power - 1]
+
+    def _encode_power_level(self, mem):
+        return POWER_LEVELS.index(mem.power) + 1
+
+    def _decode_mode(self, mem):
+        return MODES[mem.mode_alt]
+
+    def _encode_mode(self, mem):
+        return MODES.index(mem.mode)
+
+    def _get_tmode(self, mem, _mem):
+        if _mem.tone_mode > 8:
+            tmode = "Cross"
+            mem.cross_mode = CROSS_MODES[_mem.tone_mode - 8]
+        else:
+            tmode = TMODES[_mem.tone_mode]
+
+        if tmode == "Pager":
+            # TODO chirp_common does not allow 'Pager'
+            #   Expose as a different setting?
+            mem.tmode = ""
+        else:
+            mem.tmode = tmode
+
+    def _set_tmode(self, _mem, mem):
+        if mem.tmode == "Cross":
+            _mem.tone_mode = 8 + CROSS_MODES.index(mem.cross_mode)
+        else:
+            _mem.tone_mode = TMODES.index(mem.tmode)
+
+    def _set_mode(self, _mem, mem):
+        _mem.mode_alt = self._encode_mode(mem)
+
+    def get_bank_model(self):
+        return None
+
+    def _debank(self, mem):
+        return
+
+    def _checksums(self):
+        return [yaesu_clone.YaesuChecksum(0x064A, 0x06C8),
+                yaesu_clone.YaesuChecksum(0x06CA, 0x0748),
+                yaesu_clone.YaesuChecksum(0x074A, 0x07C8),
+                yaesu_clone.YaesuChecksum(0x07CA, 0x0848),
+                yaesu_clone.YaesuChecksum(0x0000, 0xFEC9)]
+
+    def _get_settings(self):
+        # TODO
+        top = RadioSettings()
+        return top
+
+    @classmethod
+    def _wipe_memory(cls, mem):
+        mem.set_raw("\x00" * (mem.size() / 8))
+
+    def sync_out(self):
+        # Need to give enough time for the radio to ACK after writes
+        self.pipe.timeout = 1
+        return super(FTM3200Radio, self).sync_out()
diff --git a/chirp/drivers/generic_xml.py b/chirp/drivers/generic_xml.py
index 06c5da9..adb993d 100644
--- a/chirp/drivers/generic_xml.py
+++ b/chirp/drivers/generic_xml.py
@@ -24,10 +24,7 @@ LOG = logging.getLogger(__name__)
 
 def validate_doc(doc):
     """Validate the document"""
-    basepath = platform.get_platform().executable_path()
-    path = os.path.abspath(os.path.join(basepath, "chirp.xsd"))
-    if not os.path.exists(path):
-        path = "/usr/share/chirp/chirp.xsd"
+    path = platform.get_platform().find_resource("chirp.xsd")
 
     try:
         ctx = libxml2.schemaNewParserCtxt(path)
diff --git a/chirp/drivers/ic2300.py b/chirp/drivers/ic2300.py
new file mode 100644
index 0000000..d39cc44
--- /dev/null
+++ b/chirp/drivers/ic2300.py
@@ -0,0 +1,385 @@
+# Copyright 2017 Windsor Schmidt <windsor.schmidt 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 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from chirp import chirp_common, directory, bitwise
+from chirp.drivers import icf
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueList, RadioSettingValueBoolean, RadioSettings
+
+# The Icom IC-2300H is a 65W, 144MHz mobile transceiver based on the IC-2200H.
+# Unlike the IC-2200H, this model does not accept Icom's UT-118 D-STAR board.
+#
+# A simple USB interface based on a typical FT232RL breakout board was used
+# during development of this module. A schematic diagram is as follows:
+#
+#
+# 3.5mm plug from IC-2300H
+# sleeve / ring / tip
+# --.______________
+#   |      |   |   \
+#   |______|___|___/    FT232RL breakout
+# --'    |   |        .------------------.
+#        |   +--------| RXD              |
+#        |   |   D1   |                  |
+#        |   +--|>|---| TXD              | USB/PC
+#        |   |   R1   |                  |-------->
+#        |   +--[_]---| VCC (5V)         |
+#        |            |                  |
+#        +------------| GND              |
+#                     `------------------'
+#
+# D1: 1N4148 shottky diode
+# R1: 10K ohm resistor
+
+MEM_FORMAT = """
+#seekto 0x0000; // channel memories
+struct {
+  ul16 frequency;
+  ul16 offset;
+  char name[6];
+  u8   repeater_tone;
+  u8   ctcss_tone;
+  u8   dtcs_code;
+  u8   tuning_step:4,
+       tone_mode:4;
+  u8   unknown1:3,
+       mode_narrow:1,
+       unknown2:4;
+  u8   dtcs_polarity:2,
+       duplex:2,
+       unknown3:1,
+       reverse_duplex:1,
+       unknown4:1,
+       display_style:1;
+} memory[200];
+#seekto 0x1340; // channel memory flags
+struct {
+  u8   unknown5:2,
+       empty:1,
+       skip:1,
+       bank:4;
+} flags[200];
+#seekto 0x1660; // power-on and regular set menu items
+struct {
+  u8   key_beep;
+  u8   tx_timeout;
+  u8   auto_repeater;
+  u8   auto_power_off;
+  u8   repeater_lockout;
+  u8   squelch_delay;
+  u8   squelch_type;
+  u8   dtmf_speed;
+  u8   display_type;
+  u8   unknown6;
+  u8   tone_burst;
+  u8   voltage_display;
+  u8   unknown7;
+  u8   display_brightness;
+  u8   display_color;
+  u8   auto_dimmer;
+  u8   display_contrast;
+  u8   scan_pause_timer;
+  u8   mic_gain;
+  u8   scan_resume_timer;
+  u8   weather_alert;
+  u8   bank_link_enable;
+  u8   bank_link[10];
+} settings;
+"""
+
+TUNING_STEPS = [5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0]
+TONE_MODES = ["", "Tone", "TSQL", "DTCS"]
+DUPLEX = ["", "-", "+"]
+DTCSP = ["NN", "NR", "RN", "RR"]
+DTCS_POLARITY = ["NN", "NR", "RN", "RR"]
+
+POWER_LEVELS = [chirp_common.PowerLevel("High", watts=65),
+                chirp_common.PowerLevel("Mid", watts=25),
+                chirp_common.PowerLevel("MidLow", watts=10),
+                chirp_common.PowerLevel("Low", watts=5)]
+
+
+def _wipe_memory(mem, char):
+    mem.set_raw(char * (mem.size() / 8))
+
+
+ at directory.register
+class IC2300Radio(icf.IcomCloneModeRadio):
+    """Icom IC-2300"""
+    VENDOR = "Icom"
+    MODEL = "IC-2300H"
+
+    _model = "\x32\x51\x00\x01"
+    _memsize = 6304
+    _endframe = "Icom Inc.C5\xfd"
+    _can_hispeed = True
+    _ranges = [(0x0000, 0x18a0, 32)]  # upload entire memory for now
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.memory_bounds = (0, 199)
+        rf.valid_modes = ["FM", "NFM"]
+        rf.valid_tmodes = list(TONE_MODES)
+        rf.valid_duplexes = list(DUPLEX)
+        rf.valid_tuning_steps = list(TUNING_STEPS)
+        rf.valid_bands = [(136000000, 174000000)]  # USA tx range: 144-148MHz
+        rf.valid_skips = ["", "S"]
+        rf.valid_power_levels = POWER_LEVELS
+        rf.has_settings = True
+        return rf
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+    def _get_bank(self, loc):
+        _flag = self._memobj.flags[loc]
+        if _flag.bank == 0x0a:
+            return None
+        else:
+            return _flag.bank
+
+    def _set_bank(self, loc, bank):
+        _flag = self._memobj.flags[loc]
+        if bank is None:
+            _flag.bank = 0x0a
+        else:
+            _flag.bank = bank
+
+    def get_memory(self, number):
+        _mem = self._memobj.memory[number]
+        _flag = self._memobj.flags[number]
+        mem = chirp_common.Memory()
+        mem.number = number
+        if _flag.empty:
+            mem.empty = True
+            return mem
+        mult = int(TUNING_STEPS[_mem.tuning_step] * 1000)
+        mem.freq = (_mem.frequency * mult)
+        mem.offset = (_mem.offset * mult)
+        mem.name = str(_mem.name).rstrip()
+        mem.rtone = chirp_common.TONES[_mem.repeater_tone]
+        mem.ctone = chirp_common.TONES[_mem.ctcss_tone]
+        mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs_code]
+        mem.tuning_step = TUNING_STEPS[_mem.tuning_step]
+        mem.tmode = TONE_MODES[_mem.tone_mode]
+        mem.mode = "NFM" if _mem.mode_narrow else "FM"
+        mem.dtcs_polarity = DTCS_POLARITY[_mem.dtcs_polarity]
+        mem.duplex = DUPLEX[_mem.duplex]
+        mem.skip = "S" if _flag.skip else ""
+
+        # Reverse duplex
+        mem.extra = RadioSettingGroup("extra", "Extra")
+        rev = RadioSetting("reverse_duplex", "Reverse duplex",
+                           RadioSettingValueBoolean(bool(_mem.reverse_duplex)))
+        rev.set_doc("Reverse duplex")
+        mem.extra.append(rev)
+
+        # Memory display style
+        opt = ["Frequency", "Label"]
+        dsp = RadioSetting("display_style", "Display style",
+                           RadioSettingValueList(opt, opt[_mem.display_style]))
+        dsp.set_doc("Memory display style")
+        mem.extra.append(dsp)
+
+        return mem
+
+    def get_raw_memory(self, number):
+        return repr(self._memobj.memory[number])
+
+    def set_memory(self, mem):
+        number = mem.number
+        _mem = self._memobj.memory[number]
+        _flag = self._memobj.flags[number]
+        was_empty = int(_flag.empty)
+        _flag.empty = mem.empty
+        if mem.empty:
+            _wipe_memory(_mem, "\xff")
+            return
+        if was_empty:
+            _wipe_memory(_mem, "\x00")
+        mult = mem.tuning_step * 1000
+        _mem.frequency = (mem.freq / mult)
+        _mem.offset = mem.offset / mult
+        _mem.name = mem.name.ljust(6)
+        _mem.repeater_tone = chirp_common.TONES.index(mem.rtone)
+        _mem.ctcss_tone = chirp_common.TONES.index(mem.ctone)
+        _mem.dtcs_code = chirp_common.DTCS_CODES.index(mem.dtcs)
+        _mem.tuning_step = TUNING_STEPS.index(mem.tuning_step)
+        _mem.tone_mode = TONE_MODES.index(mem.tmode)
+        _mem.mode_narrow = mem.mode.startswith("N")
+        _mem.dtcs_polarity = DTCSP.index(mem.dtcs_polarity)
+        _mem.duplex = DUPLEX.index(mem.duplex)
+        _flag.skip = mem.skip != ""
+
+        for setting in mem.extra:
+            setattr(_mem, setting.get_name(), setting.value)
+
+    def get_settings(self):
+        _settings = self._memobj.settings
+        basic = RadioSettingGroup("basic", "Basic Settings")
+        front_panel = RadioSettingGroup("front_panel", "Front Panel Settings")
+        top = RadioSettings(basic, front_panel)
+
+        # Transmit timeout
+        opt = ['Disabled', '1 minute'] + \
+              [s + ' minutes' for s in map(str, range(2, 31))]
+        rs = RadioSetting("tx_timeout", "Transmit timeout (min)",
+                          RadioSettingValueList(opt, opt[
+                              _settings.tx_timeout
+                          ]))
+        basic.append(rs)
+
+        # Auto Repeater (USA model only)
+        opt = ["Disabled", "Duplex Only", "Duplex and tone"]
+        rs = RadioSetting("auto_repeater", "Auto repeater",
+                          RadioSettingValueList(opt, opt[
+                              _settings.auto_repeater
+                          ]))
+        basic.append(rs)
+
+        # Auto Power Off
+        opt = ["Disabled", "30 minutes", "60 minutes", "120 minutes"]
+        rs = RadioSetting("auto_power_off", "Auto power off",
+                          RadioSettingValueList(opt, opt[
+                              _settings.auto_power_off
+                          ]))
+        basic.append(rs)
+
+        # Squelch Delay
+        opt = ["Short", "Long"]
+        rs = RadioSetting("squelch_delay", "Squelch delay",
+                          RadioSettingValueList(opt, opt[
+                              _settings.squelch_delay
+                          ]))
+        basic.append(rs)
+
+        # Squelch Type
+        opt = ["Noise squelch", "S-meter squelch", "Squelch attenuator"]
+        rs = RadioSetting("squelch_type", "Squelch type",
+                          RadioSettingValueList(opt, opt[
+                              _settings.squelch_type
+                          ]))
+        basic.append(rs)
+
+        # Repeater Lockout
+        opt = ["Disabled", "Repeater lockout", "Busy lockout"]
+        rs = RadioSetting("repeater_lockout", "Repeater lockout",
+                          RadioSettingValueList(opt, opt[
+                              _settings.repeater_lockout
+                          ]))
+        basic.append(rs)
+
+        # DTMF Speed
+        opt = ["100ms interval, 5.0 cps",
+               "200ms interval, 2.5 cps",
+               "300ms interval, 1.6 cps",
+               "500ms interval, 1.0 cps"]
+        rs = RadioSetting("dtmf_speed", "DTMF speed",
+                          RadioSettingValueList(opt, opt[
+                              _settings.dtmf_speed
+                          ]))
+        basic.append(rs)
+
+        # Scan pause timer
+        opt = [s + ' seconds' for s in map(str, range(2, 22, 2))] + ['Hold']
+        rs = RadioSetting("scan_pause_timer", "Scan pause timer",
+                          RadioSettingValueList(
+                              opt, opt[_settings.scan_pause_timer]))
+        basic.append(rs)
+
+        # Scan Resume Timer
+        opt = ['Immediate'] + \
+              [s + ' seconds' for s in map(str, range(1, 6))] + ['Hold']
+        rs = RadioSetting("scan_resume_timer", "Scan resume timer",
+                          RadioSettingValueList(
+                              opt, opt[_settings.scan_resume_timer]))
+        basic.append(rs)
+
+        # Weather Alert (USA model only)
+        rs = RadioSetting("weather_alert", "Weather alert",
+                          RadioSettingValueBoolean(_settings.weather_alert))
+        basic.append(rs)
+
+        # Tone Burst
+        rs = RadioSetting("tone_burst", "Tone burst",
+                          RadioSettingValueBoolean(_settings.tone_burst))
+        basic.append(rs)
+
+        # Memory Display Type
+        opt = ["Frequency", "Channel", "Name"]
+        rs = RadioSetting("display_type", "Memory display",
+                          RadioSettingValueList(opt,
+                                                opt[_settings.display_type]))
+        front_panel.append(rs)
+
+        # Display backlight brightness;
+        opt = ["1 (dimmest)", "2", "3", "4 (brightest)"]
+        rs = RadioSetting("display_brightness", "Backlight brightness",
+                          RadioSettingValueList(
+                              opt,
+                              opt[_settings.display_brightness]))
+        front_panel.append(rs)
+
+        # Display backlight color
+        opt = ["Amber", "Yellow", "Green"]
+        rs = RadioSetting("display_color", "Backlight color",
+                          RadioSettingValueList(opt,
+                                                opt[_settings.display_color]))
+        front_panel.append(rs)
+
+        # Display contrast
+        opt = ["1 (lightest)", "2", "3", "4 (darkest)"]
+        rs = RadioSetting("display_contrast", "Display contrast",
+                          RadioSettingValueList(
+                              opt,
+                              opt[_settings.display_contrast]))
+        front_panel.append(rs)
+
+        # Auto dimmer
+        opt = ["Disabled", "Backlight off", "1 (dimmest)", "2", "3"]
+        rs = RadioSetting("auto_dimmer", "Auto dimmer",
+                          RadioSettingValueList(opt,
+                                                opt[_settings.auto_dimmer]))
+        front_panel.append(rs)
+
+        # Microphone gain
+        opt = ["Low", "High"]
+        rs = RadioSetting("mic_gain", "Microphone gain",
+                          RadioSettingValueList(opt,
+                                                opt[_settings.mic_gain]))
+        front_panel.append(rs)
+
+        # Key press beep
+        rs = RadioSetting("key_beep", "Key press beep",
+                          RadioSettingValueBoolean(_settings.key_beep))
+        front_panel.append(rs)
+
+        # Voltage Display;
+        rs = RadioSetting("voltage_display", "Voltage display",
+                          RadioSettingValueBoolean(_settings.voltage_display))
+        front_panel.append(rs)
+
+        # TODO: Add Bank Links settings to GUI
+
+        return top
+
+    def set_settings(self, settings):
+        _settings = self._memobj.settings
+        for element in settings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+            setting = element.get_name()
+            setattr(_settings, setting, element.value)
diff --git a/chirp/drivers/icf.py b/chirp/drivers/icf.py
index 1feab74..e544571 100644
--- a/chirp/drivers/icf.py
+++ b/chirp/drivers/icf.py
@@ -198,6 +198,8 @@ def send_clone_frame(pipe, cmd, data, raw=False, checksum=False):
         pass
 
     pipe.write(frame)
+    pipe.flush()
+    pipe.read(len(frame))  # discard echoback
 
     return frame
 
@@ -261,6 +263,7 @@ def start_hispeed_clone(radio, cmd):
     LOG.debug("Starting HiSpeed Clone:\n%s" % util.hexprint(buf))
     radio.pipe.write(buf)
     radio.pipe.flush()
+    radio.pipe.read(len(buf))  # discard echoback
 
 
 def _clone_from_radio(radio):
diff --git a/chirp/drivers/icp7.py b/chirp/drivers/icp7.py
new file mode 100644
index 0000000..04db27e
--- /dev/null
+++ b/chirp/drivers/icp7.py
@@ -0,0 +1,243 @@
+# Copyright 2017 SASANO Takayoshi (JG1UAA) <uaa at uaa.org.uk>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from chirp.drivers import icf
+from chirp import chirp_common, directory, bitwise
+
+# memory nuber:
+#	 000 -  999	regular memory channels (supported, others not)
+#	1000 - 1049	scan edges
+#	1050 - 1249	auto write channels
+#	1250		call channel (C0)
+#	1251		call channel (C1)
+
+
+MEM_FORMAT = """
+struct {
+  ul32 freq;
+  ul32 offset;
+  ul16 train_sql:2,
+       tmode:3,
+       duplex:2,
+       train_tone:9;
+  ul16 tuning_step:4,
+       rtone:6,
+       ctone:6;
+  ul16 unknown0:6,
+       mode:3,
+       dtcs:7;
+  u8   unknown1:6,
+       dtcs_polarity:2;
+  char name[6];
+} memory[1251];
+
+#seekto 0x6b1e;
+struct {
+  u8 bank;
+  u8 index;
+} banks[1050];
+
+#seekto 0x689e;
+u8 used[132];
+
+#seekto 0x6922;
+u8 skips[132];
+
+#seekto 0x69a6;
+u8 pskips[132];
+
+#seekto 0x7352;
+struct {
+  char name[6];
+} bank_names[18];
+
+"""
+
+MODES = ["FM", "WFM", "AM", "Auto"]
+TMODES = ["", "Tone", "TSQL", "", "DTCS"]
+DUPLEX = ["", "-", "+"]
+DTCS_POLARITY = ["NN", "NR", "RN", "RR"]
+TUNING_STEPS = [5.0, 6.25, 8.33, 9.0, 10.0, 12.5, 15.0, 20.0,
+                25.0, 30.0, 50.0, 100.0, 200.0, 0.0]	# 0.0 as "Auto"
+
+
+class ICP7Bank(icf.IcomBank):
+    """ICP7 bank"""
+    def get_name(self):
+        _bank = self._model._radio._memobj.bank_names[self.index]
+        return str(_bank.name).rstrip()
+
+    def set_name(self, name):
+        _bank = self._model._radio._memobj.bank_names[self.index]
+        _bank.name = name.ljust(6)[:6]
+
+
+ at directory.register
+class ICP7Radio(icf.IcomCloneModeRadio):
+    """Icom IC-P7"""
+    VENDOR = "Icom"
+    MODEL = "IC-P7"
+
+    _model = "\x28\x69\x00\x01"
+    _memsize = 0x7500
+    _endframe = "Icom Inc\x2e\x41\x38"
+
+    _ranges = [(0x0000, 0x7500, 32)]
+
+    _num_banks = 18
+    _bank_class = ICP7Bank
+    _can_hispeed = True
+
+    def _get_bank(self, loc):
+        _bank = self._memobj.banks[loc]
+        if _bank.bank != 0xff:
+            return _bank.bank
+        else:
+            return None
+
+    def _set_bank(self, loc, bank):
+        _bank = self._memobj.banks[loc]
+        if bank is None:
+            _bank.bank = 0xff
+        else:
+            _bank.bank = bank
+
+    def _get_bank_index(self, loc):
+        _bank = self._memobj.banks[loc]
+        return _bank.index
+
+    def _set_bank_index(self, loc, index):
+        _bank = self._memobj.banks[loc]
+        _bank.index = index
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.memory_bounds = (0, 999)
+        rf.valid_tmodes = TMODES
+        rf.valid_duplexes = DUPLEX
+        rf.valid_modes = MODES
+        rf.valid_bands = [(495000, 999990000)]
+        rf.valid_skips = ["", "S", "P"]
+        rf.valid_tuning_steps = TUNING_STEPS
+        rf.valid_name_length = 6
+        rf.has_settings = True
+        rf.has_ctone = True
+        rf.has_bank = True
+        rf.has_bank_index = True
+        rf.has_bank_names = True
+        return rf
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+    def get_raw_memory(self, number):
+        return repr(self._memobj.memory[number])
+
+    def get_memory(self, number):
+        bit = 1 << (number % 8)
+        byte = int(number / 8)
+
+        _mem = self._memobj.memory[number]
+        _usd = self._memobj.used[byte]
+        _skp = self._memobj.skips[byte]
+        _psk = self._memobj.pskips[byte]
+
+        mem = chirp_common.Memory()
+        mem.number = number
+
+        if _usd & bit:
+            mem.empty = True
+            return mem
+
+        mem.freq = _mem.freq / 3
+        mem.offset = _mem.offset / 3
+        mem.tmode = TMODES[_mem.tmode]
+        mem.duplex = DUPLEX[_mem.duplex]
+        mem.tuning_step = TUNING_STEPS[_mem.tuning_step]
+        mem.rtone = chirp_common.TONES[_mem.rtone]
+        mem.ctone = chirp_common.TONES[_mem.ctone]
+        mem.mode = MODES[_mem.mode]
+        mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
+        mem.dtcs_polarity = DTCS_POLARITY[_mem.dtcs_polarity]
+        mem.name = str(_mem.name).rstrip()
+
+        if _skp & bit:
+            mem.skip = "P" if _psk & bit else "S"
+        else:
+            mem.skip = ""
+
+        return mem
+
+    def set_memory(self, mem):
+        bit = 1 << (mem.number % 8)
+        byte = int(mem.number / 8)
+
+        _mem = self._memobj.memory[mem.number]
+        _usd = self._memobj.used[byte]
+        _skp = self._memobj.skips[byte]
+        _psk = self._memobj.pskips[byte]
+
+        if mem.empty:
+            _usd |= bit
+
+            # We use default value instead of zero-fill
+            # to avoid unexpected behavior.
+            _mem.freq = 15000
+            _mem.offset = 479985000
+            _mem.train_sql = ~0
+            _mem.tmode = ~0
+            _mem.duplex = ~0
+            _mem.train_tone = ~0
+            _mem.tuning_step = ~0
+            _mem.rtone = ~0
+            _mem.ctone = ~0
+            _mem.unknown0 = 0
+            _mem.mode = ~0
+            _mem.dtcs = ~0
+            _mem.unknown1 = ~0
+            _mem.dtcs_polarity = ~0
+            _mem.name = "      "
+
+            _skp |= bit
+            _psk |= bit
+
+        else:
+            _usd &= ~bit
+
+            _mem.freq = mem.freq * 3
+            _mem.offset = mem.offset * 3
+            _mem.train_sql = 0		# Train SQL mode (0:off 1:Tone 2:MSK)
+            _mem.tmode = TMODES.index(mem.tmode)
+            _mem.duplex = DUPLEX.index(mem.duplex)
+            _mem.train_tone = 228	# Train SQL Tone (x10Hz)
+            _mem.tuning_step = TUNING_STEPS.index(mem.tuning_step)
+            _mem.rtone = chirp_common.TONES.index(mem.rtone)
+            _mem.ctone = chirp_common.TONES.index(mem.ctone)
+            _mem.unknown0 = 0		# unknown (always zero)
+            _mem.mode = MODES.index(mem.mode)
+            _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
+            _mem.unknown1 = ~0		# unknown (always one)
+            _mem.dtcs_polarity = DTCS_POLARITY.index(mem.dtcs_polarity)
+            _mem.name = mem.name.ljust(6)[:6]
+
+            if mem.skip == "S":
+                _skp |= bit
+                _psk &= ~bit
+            elif mem.skip == "P":
+                _skp |= bit
+                _psk |= bit
+            else:
+                _skp &= ~bit
+                _psk &= ~bit
diff --git a/chirp/drivers/icx8x.py b/chirp/drivers/icx8x.py
index 6a5c8b0..4564f89 100644
--- a/chirp/drivers/icx8x.py
+++ b/chirp/drivers/icx8x.py
@@ -75,8 +75,7 @@ class ICx8xRadio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
         rf.valid_modes = ["FM", "NFM", "DV"]
         rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
         rf.valid_duplexes = ["", "-", "+"]
-        rf.valid_tuning_steps = [x for x in chirp_common.TUNING_STEPS
-                                 if x != 6.25]
+        rf.valid_tuning_steps = [5., 10., 12.5, 15., 20., 25., 30., 50.]
         if self._isuhf:
             rf.valid_bands = [(420000000, 470000000)]
         else:
diff --git a/chirp/drivers/id880.py b/chirp/drivers/id880.py
index 713258d..c3b8ebb 100644
--- a/chirp/drivers/id880.py
+++ b/chirp/drivers/id880.py
@@ -18,7 +18,9 @@ from chirp import chirp_common, directory, bitwise
 
 MEM_FORMAT = """
 struct {
-  u24  freq;
+  u24  rxmult:3,
+       txmult:3,
+       freq:18;
   u16  offset;
   u16  rtone:6,
        ctone:6,
@@ -84,6 +86,7 @@ DTCSP = ["NN", "NR", "RN", "RR"]
 MODES = ["FM", "NFM", "?2", "AM", "NAM", "DV"]
 STEPS = [5.0, 6.25, 8.33, 9.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0,
          100.0, 125.0, 200.0]
+FREQ_MULTIPLIER = [5000, 6250, 6250, 8333, 9000]
 
 
 def decode_call(sevenbytes):
@@ -136,28 +139,15 @@ def encode_call(call):
     return "".join([chr(x) for x in buf[:7]])
 
 
-def _get_freq(_mem):
-    val = int(_mem.freq)
+def _decode_freq(freq, mult):
+    return int(freq) * FREQ_MULTIPLIER[mult]
 
-    if val & 0x00200000:
-        mult = 6250
-    else:
-        mult = 5000
 
-    val &= 0x0003FFFF
-
-    return (val * mult)
-
-
-def _set_freq(_mem, freq):
-    if chirp_common.is_fractional_step(freq):
-        mult = 6250
-        flag = 0x00200000
-    else:
-        mult = 5000
-        flag = 0x00000000
-
-    _mem.freq = (freq / mult) | flag
+def _encode_freq(freq):
+    for i, step in reversed(list(enumerate(FREQ_MULTIPLIER))):
+        if freq % step == 0:
+            return freq / step, i
+    raise ValueError("%d cannot be factored by multiplier table." % freq)
 
 
 def _wipe_memory(mem, char):
@@ -279,8 +269,8 @@ class ID880Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
             mem.empty = True
             return mem
 
-        mem.freq = _get_freq(_mem)
-        mem.offset = (_mem.offset * 5) * 1000
+        mem.freq = _decode_freq(_mem.freq, _mem.rxmult)
+        mem.offset = _decode_freq(_mem.offset, _mem.txmult)
         mem.rtone = chirp_common.TONES[_mem.rtone]
         mem.ctone = chirp_common.TONES[_mem.ctone]
         mem.tmode = TMODES[_mem.tmode]
@@ -317,8 +307,8 @@ class ID880Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
         if was_empty:
             _wipe_memory(_mem, "\x00")
 
-        _set_freq(_mem, mem.freq)
-        _mem.offset = int((mem.offset / 1000) / 5)
+        _mem.freq, _mem.rxmult = _encode_freq(mem.freq)
+        _mem.offset, _mem.txmult = _encode_freq(mem.offset)
         _mem.rtone = chirp_common.TONES.index(mem.rtone)
         _mem.ctone = chirp_common.TONES.index(mem.ctone)
         _mem.tmode = TMODES.index(mem.tmode)
diff --git a/chirp/drivers/kguv8d.py b/chirp/drivers/kguv8d.py
index bf92856..bd7aa84 100644
--- a/chirp/drivers/kguv8d.py
+++ b/chirp/drivers/kguv8d.py
@@ -589,34 +589,33 @@ class KGUV8DRadio(chirp_common.CloneModeRadio,
                 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 = tx_mode = None
+        rxtone = txtone = 0xFFFF
+
+        if mem.tmode == "Tone":
+            tx_mode = "Tone"
             rx_mode = None
-        else:
-            tx_mode = rx_mode = mem.tmode
-
-        if tx_mode == "DTCS":
-            _mem.txtone = mem.tmode != "DTCS" and \
-                _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) or \
-                _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[0])
-            _mem.txtone += 0x4000
-        elif tx_mode:
-            _mem.txtone = tx_mode == "Tone" and \
-                int(mem.rtone * 10) or int(mem.ctone * 10)
-            _mem.txtone += 0x8000
-        else:
-            _mem.txtone = 0
-
-        if rx_mode == "DTCS":
-            _mem.rxtone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
-            _mem.rxtone += 0x4000
-        elif rx_mode:
-            _mem.rxtone = int(mem.ctone * 10)
-            _mem.rxtone += 0x8000
-        else:
-            _mem.rxtone = 0
+            txtone = int(mem.rtone * 10)
+        elif mem.tmode == "TSQL":
+            rx_mode = tx_mode = "Tone"
+            rxtone = txtone = int(mem.ctone * 10)
+        elif mem.tmode == "DTCS":
+            tx_mode = rx_mode = "DTCS"
+            txtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+            rxtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
+        elif mem.tmode == "Cross":
+            tx_mode, rx_mode = mem.cross_mode.split("->")
+            if tx_mode == "DTCS":
+                txtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+            elif tx_mode == "Tone":
+                txtone = int(mem.rtone * 10)
+            if rx_mode == "DTCS":
+                rxtone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
+            elif rx_mode == "Tone":
+                rxtone = int(mem.ctone * 10)
+
+        _mem.rxtone = rxtone
+        _mem.txtone = txtone
 
         LOG.debug("Set TX %s (%i) RX %s (%i)" %
                   (tx_mode, _mem.txtone, rx_mode, _mem.rxtone))
diff --git a/chirp/drivers/kyd.py b/chirp/drivers/kyd.py
index c14af0b..7a44c34 100644
--- a/chirp/drivers/kyd.py
+++ b/chirp/drivers/kyd.py
@@ -358,30 +358,33 @@ class NC630aRadio(chirp_common.CloneModeRadio):
                 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
+        rx_mode = tx_mode = None
+        rx_tone = 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
+        if mem.tmode == "Tone":
+            tx_mode = "Tone"
+            rx_mode = None
+            tx_tone = int(mem.rtone * 10)
+        elif mem.tmode == "TSQL":
+            rx_mode = tx_mode = "Tone"
+            rx_tone = tx_tone = int(mem.ctone * 10)
+        elif mem.tmode == "DTCS":
+            tx_mode = rx_mode = "DTCS"
+            tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+            rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
+        elif mem.tmode == "Cross":
+            tx_mode, rx_mode = mem.cross_mode.split("->")
+            if tx_mode == "DTCS":
+                tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+            elif tx_mode == "Tone":
+                tx_tone = int(mem.rtone * 10)
+            if rx_mode == "DTCS":
+                rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
+            elif rx_mode == "Tone":
+                rx_tone = int(mem.ctone * 10)
+
+        _mem.rx_tone = rx_tone
+        _mem.tx_tone = tx_tone
 
         LOG.debug("Set TX %s (%i) RX %s (%i)" %
                   (tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))
diff --git a/chirp/drivers/kyd_IP620.py b/chirp/drivers/kyd_IP620.py
index 4bff0d6..5200a40 100644
--- a/chirp/drivers/kyd_IP620.py
+++ b/chirp/drivers/kyd_IP620.py
@@ -436,33 +436,36 @@ class IP620Radio(chirp_common.CloneModeRadio,
                 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
+        rx_mode = tx_mode = None
+        rx_tone = 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))
+        if mem.tmode == "Tone":
+            tx_mode = "Tone"
+            rx_mode = None
+            tx_tone = int(mem.rtone * 10)
+        elif mem.tmode == "TSQL":
+            rx_mode = tx_mode = "Tone"
+            rx_tone = tx_tone = int(mem.ctone * 10)
+        elif mem.tmode == "DTCS":
+            tx_mode = rx_mode = "DTCS"
+            tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+            rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
+        elif mem.tmode == "Cross":
+            tx_mode, rx_mode = mem.cross_mode.split("->")
+            if tx_mode == "DTCS":
+                tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+            elif tx_mode == "Tone":
+                tx_tone = int(mem.rtone * 10)
+            if rx_mode == "DTCS":
+                rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
+            elif rx_mode == "Tone":
+                rx_tone = int(mem.ctone * 10)
+
+        _mem.rx_tone = rx_tone
+        _mem.tx_tone = tx_tone
+
+        LOG.debug("Set TX %s (%i) RX %s (%i)" %
+                  (tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))
 
     def set_memory(self, mem):
         _mem = self._memobj.memory[mem.number - 1]
diff --git a/chirp/drivers/lt725uv.py b/chirp/drivers/lt725uv.py
index 2f3f757..2940cbb 100644
--- a/chirp/drivers/lt725uv.py
+++ b/chirp/drivers/lt725uv.py
@@ -322,7 +322,7 @@ class LT725UV(chirp_common.CloneModeRadio,
     def get_prompts(cls):
         rp = chirp_common.RadioPrompts()
         rp.experimental = \
-            ('The UV-50X3 driver is a beta version.\n'
+            ('The LT725UV 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.'
diff --git a/chirp/drivers/kyd.py b/chirp/drivers/radtel_t18.py
similarity index 50%
copy from chirp/drivers/kyd.py
copy to chirp/drivers/radtel_t18.py
index c14af0b..b5eea5c 100644
--- a/chirp/drivers/kyd.py
+++ b/chirp/drivers/radtel_t18.py
@@ -1,5 +1,4 @@
-# Copyright 2014 Jim Unroe <rock.unroe at gmail.com>
-# Copyright 2014 Dan Smith <dsmith at danplanet.com>
+# Copyright 2017 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
@@ -17,6 +16,7 @@
 import time
 import os
 import struct
+import unittest
 import logging
 
 from chirp import chirp_common, directory, memmap
@@ -30,67 +30,67 @@ 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 unknown5[2];
+    lbcd rxfreq[4];
+    lbcd txfreq[4];
+    lbcd rxtone[2];
+    lbcd txtone[2];
+    u8 unknown1:1,
+       compander:1,
+       scramble:1,
+       skip:1,
+       highpower:1,
+       narrow:1,
+       unknown2:1,
+       bcl:1;
+    u8 unknown3[3];
 } memory[16];
-
-#seekto 0x012F;
+#seekto 0x03C0;
 struct {
-  u8 voice;       // Voice Annunciation
-  u8 tot;         // Time-out Timer
-  u8 totalert;    // Time-out Timer Pre-alert
-  u8 unknown1[2];
-  u8 squelch;     // Squelch Level
-  u8 save;        // Battery Saver
-  u8 beep;        // Beep
-  u8 unknown2[3];
-  u8 vox;         // VOX Gain
-  u8 voxdelay;    // VOX Delay
+    u8 unknown1:1,
+       scanmode:1,
+       unknown2:2,
+       voiceprompt:2,
+       batterysaver:1,
+       beep:1;
+    u8 squelchlevel;
+    u8 unused2;
+    u8 timeouttimer;
+    u8 voxlevel;
+    u8 unknown3;
+    u8 unused;
+    u8 voxdelay;
 } settings;
-
-#seekto 0x017E;
-u8 skipflags[2];  // SCAN_ADD
 """
 
 CMD_ACK = "\x06"
+BLOCK_SIZE = 0x08
 
-NC630A_POWER_LEVELS = [chirp_common.PowerLevel("Low",  watts=1.00),
-                       chirp_common.PowerLevel("High", watts=5.00)]
-
-NC630A_DTCS = sorted(chirp_common.DTCS_CODES + [645])
-
-BCL_LIST = ["Off", "Carrier", "QT/DQT"]
-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)]
-VOXDELAY_LIST = ["0.3", "0.5", "1.0", "1.5", "2.0", "3.0"]
+TIMEOUTTIMER_LIST = ["Off", "30 seconds", "60 seconds", "90 seconds",
+                     "120 seconds", "150 seconds", "180 seconds",
+                     "210 seconds", "240 seconds", "270 seconds",
+                     "300 seconds"]
+SCANMODE_LIST = ["Carrier", "Time"]
+VOXLEVEL_LIST = ["Off", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
+VOXDELAY_LIST = ["0.5 seconds", "1.0 seconds", "1.5 seconds",
+                 "2.0 seconds", "2.5 seconds", "3.0 seconds"]
 
 SETTING_LISTS = {
-    "bcl": BCL_LIST,
-    "tot": TIMEOUTTIMER_LIST,
-    "totalert": TOTALERT_LIST,
     "voice": VOICE_LIST,
-    "vox": VOX_LIST,
-    "voxdelay": VOXDELAY_LIST,
-    }
+    "timeouttimer": TIMEOUTTIMER_LIST,
+    "scanmode": SCANMODE_LIST,
+    "voxlevel": VOXLEVEL_LIST,
+    "voxdelay": VOXDELAY_LIST
+}
 
 
-def _nc630a_enter_programming_mode(radio):
+def _t18_enter_programming_mode(radio):
     serial = radio.pipe
 
     try:
-        serial.write("PROGRAM")
+        serial.write("\x02")
+        time.sleep(0.1)
+        serial.write("1ROGRAM")
         ack = serial.read(1)
     except:
         raise errors.RadioError("Error communicating with radio")
@@ -106,7 +106,7 @@ def _nc630a_enter_programming_mode(radio):
     except:
         raise errors.RadioError("Error communicating with radio")
 
-    if not ident.startswith(radio._fileid):
+    if not ident.startswith("SMP558"):
         LOG.debug(util.hexprint(ident))
         raise errors.RadioError("Radio returned unknown identification string")
 
@@ -119,17 +119,44 @@ def _nc630a_enter_programming_mode(radio):
     if ack != CMD_ACK:
         raise errors.RadioError("Radio refused to enter programming mode")
 
+    try:
+        serial.write("\x05")
+        response = serial.read(6)
+    except:
+        raise errors.RadioError("Error communicating with radio")
+
+    if not response == ("\xFF" * 6):
+        LOG.debug(util.hexprint(response))
+        raise errors.RadioError("Radio returned unexpected response")
+
+    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 _nc630a_read_block(radio, block_addr, block_size):
+def _t18_exit_programming_mode(radio):
     serial = radio.pipe
+    try:
+        serial.write("b")
+    except:
+        raise errors.RadioError("Radio refused to exit programming mode")
 
-    cmd = struct.pack(">cHb", 'R', block_addr, block_size)
+
+def _t18_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)
+        response = serial.read(4 + BLOCK_SIZE)
         if response[:4] != expectedresponse:
             raise Exception("Error reading block %04x." % (block_addr))
 
@@ -146,11 +173,11 @@ def _nc630a_read_block(radio, block_addr, block_size):
     return block_data
 
 
-def _nc630a_write_block(radio, block_addr, block_size):
+def _t18_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]
+    cmd = struct.pack(">cHb", 'W', block_addr, BLOCK_SIZE)
+    data = radio.get_mmap()[block_addr:block_addr + 8]
 
     LOG.debug("Writing Data:")
     LOG.debug(util.hexprint(cmd + data))
@@ -166,7 +193,7 @@ def _nc630a_write_block(radio, block_addr, block_size):
 
 def do_download(radio):
     LOG.debug("download")
-    _nc630a_enter_programming_mode(radio)
+    _t18_enter_programming_mode(radio)
 
     data = ""
 
@@ -176,16 +203,18 @@ def do_download(radio):
     status.cur = 0
     status.max = radio._memsize
 
-    for addr in range(0, radio._memsize, radio._block_size):
-        status.cur = addr + radio._block_size
+    for addr in range(0, radio._memsize, BLOCK_SIZE):
+        status.cur = addr + BLOCK_SIZE
         radio.status_fn(status)
 
-        block = _nc630a_read_block(radio, addr, radio._block_size)
+        block = _t18_read_block(radio, addr, BLOCK_SIZE)
         data += block
 
         LOG.debug("Address: %04x" % addr)
         LOG.debug(util.hexprint(block))
 
+    _t18_exit_programming_mode(radio)
+
     return memmap.MemoryMap(data)
 
 
@@ -193,57 +222,66 @@ def do_upload(radio):
     status = chirp_common.Status()
     status.msg = "Uploading to radio"
 
-    _nc630a_enter_programming_mode(radio)
+    _t18_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, radio._block_size):
-            status.cur = addr + radio._block_size
+        for addr in range(start_addr, end_addr, BLOCK_SIZE):
+            status.cur = addr + BLOCK_SIZE
             radio.status_fn(status)
-            _nc630a_write_block(radio, addr, radio._block_size)
+            _t18_write_block(radio, addr, BLOCK_SIZE)
+
+    _t18_exit_programming_mode(radio)
+
 
+def model_match(cls, data):
+    """Match the opened/downloaded image to the correct version"""
 
-class MT700Alias(chirp_common.Alias):
-    VENDOR = "Plant-Tours"
-    MODEL = "MT-700"
+    if len(data) == cls._memsize:
+        rid = data[0x03D0:0x03D8]
+        return "P558" in rid
+    else:
+        return False
 
 
 @directory.register
-class NC630aRadio(chirp_common.CloneModeRadio):
-    """KYD NC-630A"""
-    VENDOR = "KYD"
-    MODEL = "NC-630A"
-    ALIASES = [MT700Alias]
+class T18Radio(chirp_common.CloneModeRadio):
+    """radtel T18"""
+    VENDOR = "Radtel"
+    MODEL = "T18"
     BAUD_RATE = 9600
 
     _ranges = [
-               (0x0000, 0x0330),
-              ]
-    _memsize = 0x03C8
-    _block_size = 0x08
-    _fileid = "P32073"
+        (0x0000, 0x03F0),
+    ]
+    _memsize = 0x03F0
 
     def get_features(self):
         rf = chirp_common.RadioFeatures()
         rf.has_settings = True
-        rf.has_bank = False
+        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
-        rf.has_rx_dtcs = True
+        rf.valid_cross_modes = [
+            "Tone->Tone",
+            "DTCS->",
+            "->DTCS",
+            "Tone->DTCS",
+            "DTCS->Tone",
+            "->Tone",
+            "DTCS->DTCS"]
         rf.has_tuning_step = False
-        rf.can_odd_split = True
+        rf.has_bank = False
         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 = NC630A_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, 520000000)]
+        rf.valid_bands = [(400000000, 470000000)]
 
         return rf
 
@@ -260,56 +298,32 @@ class NC630aRadio(chirp_common.CloneModeRadio):
     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 > 0x2800:
-            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"
+    def _decode_tone(self, val):
+        val = int(val)
+        if val == 16665:
+            return '', None, None
+        elif val >= 12000:
+            return 'DTCS', val - 12000, 'R'
+        elif val >= 8000:
+            return 'DTCS', val - 8000, 'N'
         else:
-            txmode = ""
-
-        if _mem.rx_tone != 0xFFFF and _mem.rx_tone > 0x2800:
-            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"
+            return 'Tone', val / 10.0, None
+
+    def _encode_tone(self, memval, mode, value, pol):
+        if mode == '':
+            memval[0].set_raw(0xFF)
+            memval[1].set_raw(0xFF)
+        elif mode == 'Tone':
+            memval.set_value(int(value * 10))
+        elif mode == 'DTCS':
+            flag = 0x80 if pol == 'N' else 0xC0
+            memval.set_value(value)
+            memval[1].set_bits(flag)
         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))
+            raise Exception("Internal error: invalid mode `%s'" % mode)
 
     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()
 
@@ -326,81 +340,45 @@ class NC630aRadio(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:
             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.mode = not _mem.narrow and "FM" or "NFM"
 
-        mem.power = NC630A_POWER_LEVELS[_mem.highpower]
+        mem.skip = _mem.skip and "S" or ""
 
-        mem.skip = "" if (_skp & bitpos) else "S"
-        LOG.debug("mem.skip %s" % mem.skip)
+        txtone = self._decode_tone(_mem.txtone)
+        rxtone = self._decode_tone(_mem.rxtone)
+        chirp_common.split_tone_decode(mem, txtone, rxtone)
 
         mem.extra = RadioSettingGroup("Extra", "extra")
-
         rs = RadioSetting("bcl", "Busy Channel Lockout",
-                          RadioSettingValueList(
-                              BCL_LIST, BCL_LIST[_mem.bcl]))
+                          RadioSettingValueBoolean(not _mem.bcl))
+        mem.extra.append(rs)
+        rs = RadioSetting("scramble", "Scramble",
+                          RadioSettingValueBoolean(not _mem.scramble))
+        mem.extra.append(rs)
+        rs = RadioSetting("compander", "Compander",
+                          RadioSettingValueBoolean(not _mem.compander))
         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)
-
+        # Get a low-level memory object mapped to the image
         _mem = self._memobj.memory[mem.number - 1]
-        _skp = self._memobj.skipflags[bytepos]
 
         if mem.empty:
-            _mem.set_raw("\xFF" * 16)
+            _mem.set_raw("\xFF" * (_mem.size() / 8))
             return
 
-        _mem.set_raw("\x00" * 14 + "\xFF" * 2)
-
         _mem.rxfreq = mem.freq / 10
 
         if mem.duplex == "off":
@@ -415,64 +393,64 @@ class NC630aRadio(chirp_common.CloneModeRadio):
         else:
             _mem.txfreq = mem.freq / 10
 
-        _mem.wide = mem.mode == "FM"
+        txtone, rxtone = chirp_common.split_tone_encode(mem)
+        self._encode_tone(_mem.txtone, *txtone)
+        self._encode_tone(_mem.rxtone, *rxtone)
 
-        self._set_tone(mem, _mem)
-
-        _mem.highpower = mem.power == NC630A_POWER_LEVELS[1]
-
-        if mem.skip != "S":
-            _skp |= bitpos
-        else:
-            _skp &= ~bitpos
-        LOG.debug("_skp %s" % _skp)
+        _mem.narrow = 'N' in mem.mode
+        _mem.skip = mem.skip == "S"
 
         for setting in mem.extra:
-            setattr(_mem, setting.get_name(), setting.value)
+            # NOTE: Only three settings right now, all are inverted
+            setattr(_mem, setting.get_name(), not int(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]))
+        rs = RadioSetting("squelchlevel", "Squelch level",
+                          RadioSettingValueInteger(
+                              0, 9, _settings.squelchlevel))
         basic.append(rs)
 
-        rs = RadioSetting("totalert", "TOT Pre-alert",
+        rs = RadioSetting("timeouttimer", "Timeout timer",
                           RadioSettingValueList(
-                              TOTALERT_LIST,
-                              TOTALERT_LIST[_settings.totalert]))
+                              TIMEOUTTIMER_LIST,
+                              TIMEOUTTIMER_LIST[
+                                  _settings.timeouttimer]))
         basic.append(rs)
 
-        rs = RadioSetting("vox", "VOX Gain",
+        rs = RadioSetting("scanmode", "Scan mode",
                           RadioSettingValueList(
-                              VOX_LIST, VOX_LIST[_settings.vox]))
+                              SCANMODE_LIST,
+                              SCANMODE_LIST[_settings.scanmode]))
         basic.append(rs)
 
-        rs = RadioSetting("voice", "Voice Annumciation",
+        rs = RadioSetting("voiceprompt", "Voice prompt",
                           RadioSettingValueList(
-                              VOICE_LIST, VOICE_LIST[_settings.voice]))
+                              VOICE_LIST,
+                              VOICE_LIST[_settings.voiceprompt]))
         basic.append(rs)
 
-        rs = RadioSetting("squelch", "Squelch Level",
-                          RadioSettingValueInteger(0, 9, _settings.squelch))
+        rs = RadioSetting("voxlevel", "Vox level",
+                          RadioSettingValueList(
+                              VOXLEVEL_LIST,
+                              VOXLEVEL_LIST[_settings.voxlevel]))
         basic.append(rs)
 
-        rs = RadioSetting("voxdelay", "VOX Delay",
+        rs = RadioSetting("voxdelay", "VOX delay",
                           RadioSettingValueList(
                               VOXDELAY_LIST,
                               VOXDELAY_LIST[_settings.voxdelay]))
         basic.append(rs)
 
-        rs = RadioSetting("beep", "Beep",
-                          RadioSettingValueBoolean(_settings.beep))
+        rs = RadioSetting("batterysaver", "Battery saver",
+                          RadioSettingValueBoolean(_settings.batterysaver))
         basic.append(rs)
 
-        rs = RadioSetting("save", "Battery Saver",
-                          RadioSettingValueBoolean(_settings.save))
+        rs = RadioSetting("beep", "Beep",
+                          RadioSettingValueBoolean(_settings.beep))
         basic.append(rs)
 
         return top
@@ -494,23 +472,28 @@ class NC630aRadio(chirp_common.CloneModeRadio):
                         obj = self._memobj.settings
                         setting = element.get_name()
 
-                    LOG.debug("Setting %s = %s" % (setting, element.value))
-                    setattr(obj, setting, element.value)
+                    if element.has_apply_callback():
+                        LOG.debug("Using apply callback")
+                        element.run_apply_callback()
+                    else:
+                        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 = match_model = False
+        match_size = False
+        match_model = False
 
         # testing the file data size
-        if len(filedata) in [0x338, 0x3C8]:
+        if len(filedata) == cls._memsize:
             match_size = True
 
-        # testing model fingerprint
-        if filedata[0x01B8:0x01BE] == cls._fileid:
-            match_model = True
+        # testing the model fingerprint
+        match_model = model_match(cls, filedata)
 
         if match_size and match_model:
             return True
diff --git a/chirp/drivers/retevis_rt21.py b/chirp/drivers/retevis_rt21.py
index 29f83c7..326fb03 100644
--- a/chirp/drivers/retevis_rt21.py
+++ b/chirp/drivers/retevis_rt21.py
@@ -386,30 +386,33 @@ class RT21Radio(chirp_common.CloneModeRadio):
                 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
+        rx_mode = tx_mode = None
+        rx_tone = 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
+        if mem.tmode == "Tone":
+            tx_mode = "Tone"
+            rx_mode = None
+            tx_tone = int(mem.rtone * 10)
+        elif mem.tmode == "TSQL":
+            rx_mode = tx_mode = "Tone"
+            rx_tone = tx_tone = int(mem.ctone * 10)
+        elif mem.tmode == "DTCS":
+            tx_mode = rx_mode = "DTCS"
+            tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+            rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
+        elif mem.tmode == "Cross":
+            tx_mode, rx_mode = mem.cross_mode.split("->")
+            if tx_mode == "DTCS":
+                tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+            elif tx_mode == "Tone":
+                tx_tone = int(mem.rtone * 10)
+            if rx_mode == "DTCS":
+                rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
+            elif rx_mode == "Tone":
+                rx_tone = int(mem.ctone * 10)
+
+        _mem.rx_tone = rx_tone
+        _mem.tx_tone = tx_tone
 
         LOG.debug("Set TX %s (%i) RX %s (%i)" %
                   (tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))
diff --git a/chirp/drivers/retevis_rt22.py b/chirp/drivers/retevis_rt22.py
index 9c0e2aa..0aa1370 100644
--- a/chirp/drivers/retevis_rt22.py
+++ b/chirp/drivers/retevis_rt22.py
@@ -436,30 +436,33 @@ class RT22Radio(chirp_common.CloneModeRadio):
                 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
+        rx_mode = tx_mode = None
+        rx_tone = 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
+        if mem.tmode == "Tone":
+            tx_mode = "Tone"
+            rx_mode = None
+            tx_tone = int(mem.rtone * 10)
+        elif mem.tmode == "TSQL":
+            rx_mode = tx_mode = "Tone"
+            rx_tone = tx_tone = int(mem.ctone * 10)
+        elif mem.tmode == "DTCS":
+            tx_mode = rx_mode = "DTCS"
+            tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+            rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
+        elif mem.tmode == "Cross":
+            tx_mode, rx_mode = mem.cross_mode.split("->")
+            if tx_mode == "DTCS":
+                tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+            elif tx_mode == "Tone":
+                tx_tone = int(mem.rtone * 10)
+            if rx_mode == "DTCS":
+                rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
+            elif rx_mode == "Tone":
+                rx_tone = int(mem.ctone * 10)
+
+        _mem.rx_tone = rx_tone
+        _mem.tx_tone = tx_tone
 
         LOG.debug("Set TX %s (%i) RX %s (%i)" %
                   (tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))
@@ -630,3 +633,8 @@ class LT316(RT22Radio):
     """Luiton LT-316"""
     VENDOR = "LUITON"
     MODEL = "LT-316"
+
+ at directory.register
+class TDM8(RT22Radio):
+    VENDOR = "TID"
+    MODEL = "TD-M8"
diff --git a/chirp/drivers/retevis_rt23.py b/chirp/drivers/retevis_rt23.py
new file mode 100644
index 0000000..0ccb088
--- /dev/null
+++ b/chirp/drivers/retevis_rt23.py
@@ -0,0 +1,867 @@
+# Copyright 2017 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 re
+import logging
+
+from chirp import chirp_common, directory, memmap
+from chirp import bitwise, errors, util
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettingValueString, \
+    RadioSettings
+
+LOG = logging.getLogger(__name__)
+
+MEM_FORMAT = """
+struct memory {
+  lbcd rxfreq[4];
+  lbcd txfreq[4];
+  lbcd rxtone[2];
+  lbcd txtone[2];
+  u8 unknown1;
+  u8 pttid:2,     // PTT-ID
+     unknown2:1,
+     signaling:1, // Signaling(ANI)
+     unknown3:1,
+     bcl:1,       // Busy Channel Lockout
+     unknown4:2;
+  u8 unknown5:3,
+     highpower:1, // Power Level
+     isnarrow:1,  // Bandwidth
+     scan:1,      // Scan Add
+     unknown6:2;
+  u8 unknown7;
+};
+
+#seekto 0x0010;
+struct memory channels[128];
+
+#seekto 0x0810;
+struct memory vfo_a;
+struct memory vfo_b;
+
+#seekto 0x0830;
+struct {
+  u8 unknown_0830_1:4,
+     color:2,               // Background Color
+     dst:1,                 // DTMF Side Tone
+     txsel:1;               // Priority TX Channel Select
+  u8 scans:2,               // Scan Mode
+     unknown_0831:1,
+     autolk:1,              // Auto Key Lock
+     save:1,                // Battery Save
+     beep:1,                // Key Beep
+     voice:2;               // Voice Prompt
+  u8 vfomr_fm:1,            // FM Radio Display Mode
+     led:2,                 // Background Light
+     unknown_0832_2:1,
+     dw:1,                  // FM Radio Dual Watch
+     name:1,                // Display Names
+     vfomr_a:2;             // Display Mode A
+  u8 opnset:2,              // Power On Message
+     unknown_0833_1:3,
+     dwait:1,               // Dual Standby
+     vfomr_b:2;             // Display Mode B
+  u8 mrcha;                 // mr a ch num
+  u8 mrchb;                 // mr b ch num
+  u8 fmch;                  // fm radio ch num
+  u8 unknown_0837_1:1,
+     ste:1,                 // Squelch Tail Eliminate
+     roger:1,               // Roger Beep
+     unknown_0837_2:1,
+     vox:4;                 // VOX
+  u8 step:4,                // Step
+     unknown_0838_1:4;
+  u8 squelch;               // Squelch
+  u8 tot;                   // Time Out Timer
+  u8 rptmod:1,              // Repeater Mode
+     volmod:2,              // Volume Mode
+     rptptt:1,              // Repeater PTT Switch
+     rptspk:1,              // Repeater Speaker
+     relay:3;               // Cross Band Repeater Enable
+  u8 unknown_083C:4,        // 0x083C
+     rptrl:4;               // Repeater TX Delay
+  u8 pf1:4,                 // Function Key 1
+     pf2:4;                 // Function Key 2
+  u8 vot;                   // VOX Delay Time
+} settings;
+
+#seekto 0x0848;
+struct {
+  char line1[7];
+} poweron_msg;
+
+struct limit {
+  bbcd lower[2];
+  bbcd upper[2];
+};
+
+#seekto 0x0850;
+struct {
+  struct limit vhf;
+  struct limit uhf;
+} limits;
+
+#seekto 0x08D0;
+struct {
+  char name[7];
+  u8 unknown2[1];
+} names[128];
+
+#seekto 0x0D20;
+u8 usedflags[16];
+u8 scanflags[16];
+
+#seekto 0x0FA0;
+struct {
+  u8 unknown_0FA0_1:4,
+     dispab:1,              // select a/b
+     unknown_0FA0_2:3;
+} settings2;
+"""
+
+CMD_ACK = "\x06"
+BLOCK_SIZE = 0x10
+
+RT23_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00),
+                     chirp_common.PowerLevel("High", watts=2.50)]
+
+
+RT23_DTCS = sorted(chirp_common.DTCS_CODES +
+                   [17, 50, 55, 135, 217, 254, 305, 645, 765])
+
+RT23_CHARSET = chirp_common.CHARSET_UPPER_NUMERIC + \
+    ":;<=>?@ !\"#$%&'()*+,-./"
+
+LIST_COLOR = ["Blue", "Orange", "Purple"]
+LIST_LED = ["Off", "On", "Auto"]
+LIST_OPNSET = ["Full", "Voltage", "Message"]
+LIST_PFKEY = [
+    "Radio",
+    "Sub-channel Sent",
+    "Scan",
+    "Alarm",
+    "DTMF",
+    "Squelch Off Momentarily",
+    "Battery Power Indicator",
+    "Tone 1750",
+    "Tone 2100",
+    "Tone 1000",
+    "Tone 1450"]
+LIST_PTTID = ["Off", "BOT", "EOT", "Both"]
+LIST_RPTMOD = ["Single", "Double"]
+LIST_RPTRL = ["0.5S", "1.0S", "1.5S", "2.0S", "2.5S", "3.0S", "3.5S", "4.0S",
+              "4.5S"]
+LIST_SCANS = ["Time Operated", "Carrier Operated", "Search"]
+LIST_SIGNALING = ["No", "DTMF"]
+LIST_TOT = ["OFF"] + ["%s seconds" % x for x in range(30, 300, 30)]
+LIST_TXSEL = ["Edit", "Busy"]
+LIST_STEP = ["2.50K", "5.00K", "6.25K", "10.00K", "12,50K", "20.00K", "25.00K",
+             "50.00K"]
+LIST_VFOMR = ["VFO", "MR(Frequency)", "MR(Channel #/Name)"]
+LIST_VFOMRFM = ["VFO", "Channel"]
+LIST_VOICE = ["Off", "Chinese", "English"]
+LIST_VOLMOD = ["Off", "Sub", "Main"]
+LIST_VOT = ["0.5S", "1.0S", "1.5S", "2.0S", "3.0S"]
+LIST_VOX = ["OFF"] + ["%s" % x for x in range(1, 6)]
+
+
+def _rt23_enter_programming_mode(radio):
+    serial = radio.pipe
+
+    magic = "PROIUAM"
+    exito = False
+    for i in range(0, 5):
+        for j in range(0, len(magic)):
+            time.sleep(0.005)
+            serial.write(magic[j])
+        ack = serial.read(1)
+
+        try:
+            if ack == CMD_ACK:
+                exito = True
+                break
+        except:
+            LOG.debug("Attempt #%s, failed, trying again" % i)
+            pass
+
+    # check if we had EXITO
+    if exito is False:
+        msg = "The radio did not accept program mode after five tries.\n"
+        msg += "Check you interface cable and power cycle your radio."
+        raise errors.RadioError(msg)
+
+    try:
+        serial.write("\x02")
+        ident = serial.read(8)
+    except:
+        raise errors.RadioError("Error communicating with radio")
+
+    if not ident.startswith("P31183"):
+        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 _rt23_exit_programming_mode(radio):
+    serial = radio.pipe
+    try:
+        serial.write("E")
+    except:
+        raise errors.RadioError("Radio refused to exit programming mode")
+
+
+def _rt23_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 + 1)
+        if response[:4] != expectedresponse:
+            raise Exception("Error reading block %04x." % (block_addr))
+
+        chunk = response[4:]
+
+        cs = 0
+        for byte in chunk[:-1]:
+            cs += ord(byte)
+        if ord(chunk[-1]) != (cs & 0xFF):
+            raise Exception("Block failed checksum!")
+
+        block_data = chunk[:-1]
+    except:
+        raise errors.RadioError("Failed to read block at %04x" % block_addr)
+
+    return block_data
+
+
+def _rt23_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]
+    cs = 0
+    for byte in data:
+        cs += ord(byte)
+    data += chr(cs & 0xFF)
+
+    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")
+    _rt23_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 = _rt23_read_block(radio, addr, BLOCK_SIZE)
+        if addr == 0 and block.startswith("\xFF" * 6):
+            block = "P31183" + "\xFF" * 10
+        data += block
+
+        LOG.debug("Address: %04x" % addr)
+        LOG.debug(util.hexprint(block))
+
+    _rt23_exit_programming_mode(radio)
+
+    return memmap.MemoryMap(data)
+
+
+def do_upload(radio):
+    status = chirp_common.Status()
+    status.msg = "Uploading to radio"
+
+    _rt23_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)
+            _rt23_write_block(radio, addr, BLOCK_SIZE)
+
+
+def model_match(cls, data):
+    """Match the opened/downloaded image to the correct version"""
+
+    if len(data) == 0x1000:
+        rid = data[0x0000:0x0006]
+        return rid == "P31183"
+    else:
+        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 RT23Radio(chirp_common.CloneModeRadio):
+    """RETEVIS RT23"""
+    VENDOR = "Retevis"
+    MODEL = "RT23"
+    BAUD_RATE = 9600
+
+    _ranges = [
+               (0x0000, 0x0EC0),
+              ]
+    _memsize = 0x1000
+
+    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.valid_name_length = 7
+        rf.valid_characters = RT23_CHARSET
+        rf.has_name = True
+        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 = RT23_POWER_LEVELS
+        rf.valid_duplexes = ["", "-", "+", "split", "off"]
+        rf.valid_modes = ["FM", "NFM"]  # 25 KHz, 12.5 KHz.
+        rf.memory_bounds = (1, 128)
+        rf.valid_bands = [
+            (136000000, 174000000),
+            (400000000, 480000000)]
+
+        return rf
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+    def sync_in(self):
+        """Download from radio"""
+        try:
+            data = do_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 = data
+        self.process_mmap()
+
+    def sync_out(self):
+        """Upload to radio"""
+        try:
+            do_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_raw_memory(self, number):
+        return repr(self._memobj.memory[number - 1])
+
+    def decode_tone(self, val):
+        """Parse the tone data to decode from mem, it returns:
+        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
+        if val.get_raw() == "\xFF\xFF":
+            return '', None, None
+
+        val = int(val)
+        if val >= 12000:
+            a = val - 12000
+            return 'DTCS', a, 'R'
+        elif val >= 8000:
+            a = val - 8000
+            return 'DTCS', a, 'N'
+        else:
+            a = val / 10.0
+            return 'Tone', a, None
+
+    def encode_tone(self, memval, mode, value, pol):
+        """Parse the tone data to encode from UI to mem"""
+        if mode == '':
+            memval[0].set_raw(0xFF)
+            memval[1].set_raw(0xFF)
+        elif mode == 'Tone':
+            memval.set_value(int(value * 10))
+        elif mode == 'DTCS':
+            flag = 0x80 if pol == 'N' else 0xC0
+            memval.set_value(value)
+            memval[1].set_bits(flag)
+        else:
+            raise Exception("Internal error: invalid mode `%s'" % mode)
+
+    def get_memory(self, number):
+        mem = chirp_common.Memory()
+        _mem = self._memobj.channels[number-1]
+        _nam = self._memobj.names[number - 1]
+        mem.number = number
+        bitpos = (1 << ((number - 1) % 8))
+        bytepos = ((number - 1) / 8)
+        _scn = self._memobj.scanflags[bytepos]
+        _usd = self._memobj.usedflags[bytepos]
+        isused = bitpos & int(_usd)
+        isscan = bitpos & int(_scn)
+
+        if not isused:
+            mem.empty = True
+            return mem
+
+        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.empty = True
+            return mem
+
+        if _mem.get_raw() == ("\xFF" * 16):
+            LOG.debug("Initializing empty memory")
+            _mem.set_raw("\x00" * 16)
+
+        # Freq and offset
+        mem.freq = int(_mem.rxfreq) * 10
+        # tx freq can be blank
+        if _mem.get_raw()[4] == "\xFF":
+            # TX freq not set
+            mem.offset = 0
+            mem.duplex = "off"
+        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 = " "
+            mem.name += str(char)
+        mem.name = mem.name.rstrip()
+
+        mem.mode = _mem.isnarrow and "NFM" or "FM"
+
+        rxtone = txtone = None
+        txtone = self.decode_tone(_mem.txtone)
+        rxtone = self.decode_tone(_mem.rxtone)
+        chirp_common.split_tone_decode(mem, txtone, rxtone)
+
+        mem.power = RT23_POWER_LEVELS[_mem.highpower]
+
+        if not isscan:
+            mem.skip = "S"
+
+        mem.extra = RadioSettingGroup("Extra", "extra")
+
+        rs = RadioSetting("bcl", "BCL",
+                          RadioSettingValueBoolean(_mem.bcl))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("pttid", "PTT ID",
+                          RadioSettingValueList(
+                              LIST_PTTID, LIST_PTTID[_mem.pttid]))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("signaling", "Optional Signaling",
+                          RadioSettingValueList(LIST_SIGNALING,
+                              LIST_SIGNALING[_mem.signaling]))
+        mem.extra.append(rs)
+
+        return mem
+
+    def set_memory(self, mem):
+        LOG.debug("Setting %i(%s)" % (mem.number, mem.extd_number))
+        _mem = self._memobj.channels[mem.number - 1]
+        _nam = self._memobj.names[mem.number - 1]
+        bitpos = (1 << ((mem.number - 1) % 8))
+        bytepos = ((mem.number - 1) / 8)
+        _scn = self._memobj.scanflags[bytepos]
+        _usd = self._memobj.usedflags[bytepos]
+
+        if mem.empty:
+            _mem.set_raw("\xFF" * 16)
+            _nam.name = ("\xFF" * 7)
+            _usd &= ~bitpos
+            _scn &= ~bitpos
+            return
+        else:
+            _usd |= bitpos
+
+        if _mem.get_raw() == ("\xFF" * 16):
+            LOG.debug("Initializing empty memory")
+            _mem.set_raw("\x00" * 16)
+            _scn |= bitpos
+
+        _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"
+
+        _mem.scan = mem.skip != "S"
+        if mem.skip == "S":
+            _scn &= ~bitpos
+        else:
+            _scn |= bitpos
+        _mem.isnarrow = mem.mode == "NFM"
+
+        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
+            chirp_common.split_tone_encode(mem)
+        self.encode_tone(_mem.txtone, txmode, txtone, txpol)
+        self.encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
+
+        _mem.highpower = mem.power == RT23_POWER_LEVELS[1]
+
+        for setting in mem.extra:
+            setattr(_mem, setting.get_name(), setting.value)
+
+    def get_settings(self):
+        _settings = self._memobj.settings
+        _mem = self._memobj
+        basic = RadioSettingGroup("basic", "Basic Settings")
+        advanced = RadioSettingGroup("advanced", "Advanced Settings")
+        other = RadioSettingGroup("other", "Other Settings")
+        workmode = RadioSettingGroup("workmode", "Workmode Settings")
+        fmradio = RadioSettingGroup("fmradio", "FM Radio Settings")
+        top = RadioSettings(basic, advanced, other, workmode, fmradio)
+
+        save = RadioSetting("save", "Battery Saver",
+                            RadioSettingValueBoolean(_settings.save))
+        basic.append(save)
+
+        vox = RadioSetting("vox", "VOX Gain",
+                           RadioSettingValueList(
+                               LIST_VOX, LIST_VOX[_settings.vox]))
+        basic.append(vox)
+
+        squelch = RadioSetting("squelch", "Squelch Level",
+                               RadioSettingValueInteger(
+                                   0, 9, _settings.squelch))
+        basic.append(squelch)
+
+        relay = RadioSetting("relay", "Repeater",
+                             RadioSettingValueBoolean(_settings.relay))
+        basic.append(relay)
+
+        tot = RadioSetting("tot", "Time-out timer", RadioSettingValueList(
+                           LIST_TOT, LIST_TOT[_settings.tot]))
+        basic.append(tot)
+
+        beep = RadioSetting("beep", "Key Beep",
+                            RadioSettingValueBoolean(_settings.beep))
+        basic.append(beep)
+
+        color = RadioSetting("color", "Background Color", RadioSettingValueList(
+                             LIST_COLOR, LIST_COLOR[_settings.color - 1]))
+        basic.append(color)
+
+        vot = RadioSetting("vot", "VOX Delay Time", RadioSettingValueList(
+                           LIST_VOT, LIST_VOT[_settings.vot]))
+        basic.append(vot)
+
+        dwait = RadioSetting("dwait", "Dual Standby",
+                             RadioSettingValueBoolean(_settings.dwait))
+        basic.append(dwait)
+
+        led = RadioSetting("led", "Background Light", RadioSettingValueList(
+                           LIST_LED, LIST_LED[_settings.led]))
+        basic.append(led)
+
+        voice = RadioSetting("voice", "Voice Prompt", RadioSettingValueList(
+                             LIST_VOICE, LIST_VOICE[_settings.voice]))
+        basic.append(voice)
+
+        roger = RadioSetting("roger", "Roger Beep",
+                             RadioSettingValueBoolean(_settings.roger))
+        basic.append(roger)
+
+        autolk = RadioSetting("autolk", "Auto Key Lock",
+                              RadioSettingValueBoolean(_settings.autolk))
+        basic.append(autolk)
+
+        opnset = RadioSetting("opnset", "Open Mode Set",
+                              RadioSettingValueList(
+                                  LIST_OPNSET, LIST_OPNSET[_settings.opnset]))
+        basic.append(opnset)
+
+        def _filter(name):
+            filtered = ""
+            for char in str(name):
+                if char in chirp_common.CHARSET_ASCII:
+                    filtered += char
+                else:
+                    filtered += " "
+            return filtered
+
+        _msg = self._memobj.poweron_msg
+        ponmsg = RadioSetting("poweron_msg.line1", "Power-On Message",
+                              RadioSettingValueString(
+                                  0, 7, _filter(_msg.line1)))
+        basic.append(ponmsg)
+
+
+        scans = RadioSetting("scans", "Scan Mode", RadioSettingValueList(
+                             LIST_SCANS, LIST_SCANS[_settings.scans]))
+        basic.append(scans)
+
+        dw = RadioSetting("dw", "FM Radio Dual Watch",
+                          RadioSettingValueBoolean(_settings.dw))
+        basic.append(dw)
+
+        name = RadioSetting("name", "Display Names",
+                            RadioSettingValueBoolean(_settings.name))
+        basic.append(name)
+
+        rptrl = RadioSetting("rptrl", "Repeater TX Delay", 
+                             RadioSettingValueList(LIST_RPTRL, LIST_RPTRL[
+                                 _settings.rptrl]))
+        basic.append(rptrl)
+
+        rptspk = RadioSetting("rptspk", "Repeater Speaker",
+                              RadioSettingValueBoolean(_settings.rptspk))
+        basic.append(rptspk)
+
+        rptptt = RadioSetting("rptptt", "Repeater PTT Switch",
+                            RadioSettingValueBoolean(_settings.rptptt))
+        basic.append(rptptt)
+
+        rptmod = RadioSetting("rptmod", "Repeater Mode",
+                              RadioSettingValueList(
+                                  LIST_RPTMOD, LIST_RPTMOD[_settings.rptmod]))
+        basic.append(rptmod)
+
+        volmod = RadioSetting("volmod", "Volume Mode",
+                              RadioSettingValueList(
+                                  LIST_VOLMOD, LIST_VOLMOD[_settings.volmod]))
+        basic.append(volmod)
+
+        dst = RadioSetting("dst", "DTMF Side Tone",
+                            RadioSettingValueBoolean(_settings.dst))
+        basic.append(dst)
+
+        txsel = RadioSetting("txsel", "Priority TX Channel",
+                             RadioSettingValueList(
+                                 LIST_TXSEL, LIST_TXSEL[_settings.txsel]))
+        basic.append(txsel)
+
+        ste = RadioSetting("ste", "Squelch Tail Eliminate",
+                           RadioSettingValueBoolean(_settings.ste))
+        basic.append(ste)
+
+        #advanced
+        if _settings.pf1 > 0x0A:
+            val = 0x00
+        else:
+            val = _settings.pf1
+        pf1 = RadioSetting("pf1", "PF1 Key",
+                           RadioSettingValueList(
+                               LIST_PFKEY, LIST_PFKEY[val]))
+        advanced.append(pf1)
+
+        if _settings.pf2 > 0x0A:
+            val = 0x00
+        else:
+            val = _settings.pf2
+        pf2 = RadioSetting("pf2", "PF2 Key",
+                           RadioSettingValueList(
+                               LIST_PFKEY, LIST_PFKEY[val]))
+        advanced.append(pf2)
+
+        # other
+        _limit = str(int(_mem.limits.vhf.lower) / 10)
+        val = RadioSettingValueString(0, 3, _limit)
+        val.set_mutable(False)
+        rs = RadioSetting("limits.vhf.lower", "VHF low", val)
+        other.append(rs)
+
+        _limit = str(int(_mem.limits.vhf.upper) / 10)
+        val = RadioSettingValueString(0, 3, _limit)
+        val.set_mutable(False)
+        rs = RadioSetting("limits.vhf.upper", "VHF high", val)
+        other.append(rs)
+
+        _limit = str(int(_mem.limits.uhf.lower) / 10)
+        val = RadioSettingValueString(0, 3, _limit)
+        val.set_mutable(False)
+        rs = RadioSetting("limits.uhf.lower", "UHF low", val)
+        other.append(rs)
+
+        _limit = str(int(_mem.limits.uhf.upper) / 10)
+        val = RadioSettingValueString(0, 3, _limit)
+        val.set_mutable(False)
+        rs = RadioSetting("limits.uhf.upper", "UHF high", val)
+        other.append(rs)
+
+        #work mode
+        vfomr_a = RadioSetting("vfomr_a", "Display Mode A",
+                               RadioSettingValueList(
+                                   LIST_VFOMR, LIST_VFOMR[_settings.vfomr_a]))
+        workmode.append(vfomr_a)
+
+        vfomr_b = RadioSetting("vfomr_b", "Display Mode B",
+                               RadioSettingValueList(
+                                   LIST_VFOMR, LIST_VFOMR[_settings.vfomr_b]))
+        workmode.append(vfomr_b)
+
+        mrcha = RadioSetting("mrcha", "Channel # A",
+                             RadioSettingValueInteger(
+                                 1, 128, _settings.mrcha))
+        workmode.append(mrcha)
+
+        mrchb = RadioSetting("mrchb", "Channel # B",
+                             RadioSettingValueInteger(
+                                 1, 128, _settings.mrchb))
+        workmode.append(mrchb)
+
+        #fm radio
+        vfomr_fm = RadioSetting("vfomr_fm", "FM Radio Display Mode",
+                                RadioSettingValueList(
+                                    LIST_VFOMRFM, LIST_VFOMRFM[
+                                        _settings.vfomr_fm]))
+        fmradio.append(vfomr_fm)
+
+        fmch = RadioSetting("fmch", "FM Radio Channel #",
+                            RadioSettingValueInteger(
+                                 1, 25, _settings.fmch))
+        fmradio.append(fmch)
+
+        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 element.has_apply_callback():
+                        LOG.debug("Using apply callback")
+                        element.run_apply_callback()
+                    elif setting == "color":
+                        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
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        match_size = False
+        match_model = False
+
+        # testing the file data size
+        if len(filedata) in [0x1000, ]:
+            match_size = True
+
+        # testing the model fingerprint
+        match_model = model_match(cls, filedata)
+
+        if match_size and match_model:
+            return True
+        else:
+            return False
diff --git a/chirp/drivers/rh5r_v2.py b/chirp/drivers/rh5r_v2.py
new file mode 100644
index 0000000..01ffc96
--- /dev/null
+++ b/chirp/drivers/rh5r_v2.py
@@ -0,0 +1,290 @@
+# Copyright 2017 Dan Smith <dsmith at danplanet.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 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Rugged RH5R V2 radio management module"""
+
+import struct
+import logging
+
+from chirp import chirp_common, bitwise, errors, directory, memmap, util
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettingValueString, \
+    RadioSettings
+
+
+LOG = logging.getLogger(__name__)
+
+
+def _identify(radio):
+    try:
+        radio.pipe.write("PGM2015")
+        ack = radio.pipe.read(2)
+        if ack != "\x06\x30":
+            raise errors.RadioError("Radio did not ACK first command: %r" %
+                                    ack)
+    except:
+        LOG.exception('')
+        raise errors.RadioError("Unable to communicate with the radio")
+
+
+def _download(radio):
+    _identify(radio)
+    data = []
+    for i in range(0, 0x2000, 0x40):
+        msg = struct.pack('>cHb', 'R', i, 0x40)
+        radio.pipe.write(msg)
+        block = radio.pipe.read(0x40 + 4)
+        if len(block) != (0x40 + 4):
+            raise errors.RadioError("Radio sent a short block (%02x/%02x)" % (
+                len(block), 0x44))
+        data += block[4:]
+
+        if radio.status_fn:
+            status = chirp_common.Status()
+            status.cur = i
+            status.max = 0x2000
+            status.msg = "Cloning from radio"
+            radio.status_fn(status)
+
+    radio.pipe.write("E")
+    data += 'PGM2015'
+
+    return memmap.MemoryMap(data)
+
+
+def _upload(radio):
+    _identify(radio)
+    for i in range(0, 0x2000, 0x40):
+        msg = struct.pack('>cHb', 'W', i, 0x40)
+        msg += radio._mmap[i:(i + 0x40)]
+        radio.pipe.write(msg)
+        ack = radio.pipe.read(1)
+        if ack != '\x06':
+            raise errors.RadioError('Radio did not ACK block %i (0x%04x)' % (
+                i, i))
+
+        if radio.status_fn:
+            status = chirp_common.Status()
+            status.cur = i
+            status.max = 0x2000
+            status.msg = "Cloning from radio"
+            radio.status_fn(status)
+
+    radio.pipe.write("E")
+
+
+MEM_FORMAT = """
+struct memory {
+  bbcd rx_freq[4];
+  bbcd tx_freq[4];
+  lbcd rx_tone[2];
+  lbcd tx_tone[2];
+
+  u8 unknown10:5,
+     highpower:1,
+     unknown11:2;
+  u8 unknown20:4,
+     narrow:1,
+     unknown21:3;
+  u8 unknown31:1,
+     scanadd:1,
+     unknown32:6;
+  u8 unknown4;
+};
+
+struct name {
+  char name[7];
+};
+
+#seekto 0x0010;
+struct memory channels[128];
+
+#seekto 0x08C0;
+struct name names[128];
+
+#seekto 0x2020;
+struct memory vfo1;
+struct memory vfo2;
+"""
+
+
+POWER_LEVELS = [chirp_common.PowerLevel('Low', watts=1),
+                chirp_common.PowerLevel('High', watts=5)]
+
+
+class TYTTHUVF8_V2(chirp_common.CloneModeRadio):
+    VENDOR = "TYT"
+    MODEL = "TH-UVF8F"
+    BAUD_RATE = 9600
+    _FILEID = 'OEMOEM \XFF'
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.memory_bounds = (1, 128)
+        rf.has_bank = False
+        rf.has_ctone = True
+        rf.has_tuning_step = False
+        rf.has_cross = True
+        rf.has_rx_dtcs = True
+        rf.has_settings = False
+        rf.can_odd_split = False
+        rf.valid_duplexes = ['', '-', '+']
+        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
+        rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "-"
+        rf.valid_bands = [(136000000, 174000000),
+                          (400000000, 480000000)]
+        rf.valid_skips = ["", "S"]
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_modes = ["FM", "NFM"]
+        rf.valid_name_length = 7
+        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
+                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
+        return rf
+
+    def sync_in(self):
+        self._mmap = _download(self)
+        self.process_mmap()
+
+    def sync_out(self):
+        _upload(self)
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return (filedata.endswith("PGM2015") and
+                filedata[0x840:0x848] == cls._FILEID)
+
+    def process_mmap(self):
+        print MEM_FORMAT
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+    def get_raw_memory(self, number):
+        return (repr(self._memobj.channels[number - 1]) +
+                repr(self._memobj.names[number - 1]))
+
+    def _get_memobjs(self, number):
+        return (self._memobj.channels[number - 1],
+                self._memobj.names[number - 1])
+
+    def _decode_tone(self, toneval):
+        pol = "N"
+        rawval = (toneval[1].get_bits(0xFF) << 8) | toneval[0].get_bits(0xFF)
+
+        if toneval[0].get_bits(0xFF) == 0xFF:
+            mode = ""
+            val = 0
+        elif toneval[1].get_bits(0xC0) == 0xC0:
+            mode = "DTCS"
+            val = int("%x" % (rawval & 0x3FFF))
+            pol = "R"
+        elif toneval[1].get_bits(0x80):
+            mode = "DTCS"
+            val = int("%x" % (rawval & 0x3FFF))
+        else:
+            mode = "Tone"
+            val = int(toneval) / 10.0
+
+        return mode, val, pol
+
+    def _encode_tone(self, _toneval, mode, val, pol):
+        toneval = 0
+        if mode == "Tone":
+            toneval = int("%i" % (val * 10), 16)
+        elif mode == "DTCS":
+            toneval = int("%i" % val, 16)
+            toneval |= 0x8000
+            if pol == "R":
+                toneval |= 0x4000
+        else:
+            toneval = 0xFFFF
+
+        _toneval[0].set_raw(toneval & 0xFF)
+        _toneval[1].set_raw((toneval >> 8) & 0xFF)
+
+    def get_memory(self, number):
+        _mem, _name = self._get_memobjs(number)
+
+        mem = chirp_common.Memory()
+
+        if isinstance(number, str):
+            mem.number = SPECIALS[number]
+            mem.extd_number = number
+        else:
+            mem.number = number
+
+        if _mem.get_raw().startswith("\xFF\xFF\xFF\xFF"):
+            mem.empty = True
+            return mem
+
+        mem.freq = int(_mem.rx_freq) * 10
+        offset = (int(_mem.tx_freq) - int(_mem.rx_freq)) * 10
+        if not offset:
+            mem.offset = 0
+            mem.duplex = ''
+        elif offset < 0:
+            mem.offset = abs(offset)
+            mem.duplex = '-'
+        else:
+            mem.offset = offset
+            mem.duplex = '+'
+
+        txmode, txval, txpol = self._decode_tone(_mem.tx_tone)
+        rxmode, rxval, rxpol = self._decode_tone(_mem.rx_tone)
+
+        chirp_common.split_tone_decode(mem,
+                                       (txmode, txval, txpol),
+                                       (rxmode, rxval, rxpol))
+
+        mem.mode = 'NFM' if _mem.narrow else 'FM'
+        mem.skip = '' if _mem.scanadd else 'S'
+        mem.power = POWER_LEVELS[int(_mem.highpower)]
+        mem.name = str(_name.name).rstrip('\xFF ')
+
+        return mem
+
+    def set_memory(self, mem):
+        _mem, _name = self._get_memobjs(mem.number)
+        if mem.empty:
+            _mem.set_raw('\xFF' * 16)
+            _name.set_raw('\xFF' * 7)
+            return
+        _mem.set_raw('\x00' * 16)
+
+        _mem.rx_freq = mem.freq / 10
+        if mem.duplex == '-':
+            mult = -1
+        elif not mem.duplex:
+            mult = 0
+        else:
+            mult = 1
+        _mem.tx_freq = (mem.freq + (mem.offset * mult)) / 10
+
+        (txmode, txval, txpol), (rxmode, rxval, rxpol) = \
+                chirp_common.split_tone_encode(mem)
+
+        self._encode_tone(_mem.tx_tone, txmode, txval, txpol)
+        self._encode_tone(_mem.rx_tone, rxmode, rxval, rxpol)
+
+        _mem.narrow = mem.mode == 'NFM'
+        _mem.scanadd = mem.skip != 'S'
+        _mem.highpower = POWER_LEVELS.index(mem.power) if mem.power else 1
+        _name.name = mem.name.rstrip(' ').ljust(7, '\xFF')
+
+
+ at directory.register
+class RH5RV2(TYTTHUVF8_V2):
+    VENDOR = "Rugged"
+    MODEL = "RH5R-V2"
+    _FILEID = 'RUGGED \xFF'
diff --git a/chirp/drivers/tdxone_tdq8a.py b/chirp/drivers/tdxone_tdq8a.py
new file mode 100644
index 0000000..122c056
--- /dev/null
+++ b/chirp/drivers/tdxone_tdq8a.py
@@ -0,0 +1,1154 @@
+# 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
+
+from chirp import chirp_common, directory, memmap
+from chirp import bitwise, errors, util
+from chirp.settings import RadioSettingGroup, RadioSetting, \
+    RadioSettingValueBoolean, RadioSettingValueList, \
+    RadioSettingValueString, RadioSettingValueInteger, \
+    RadioSettingValueFloat, RadioSettings, \
+    InvalidValueError
+from textwrap import dedent
+
+LOG = logging.getLogger(__name__)
+
+MEM_FORMAT = """
+#seekto 0x0010;
+struct {
+  lbcd rxfreq[4];
+  lbcd txfreq[4];
+  ul16 rxtone;
+  ul16 txtone;
+  u8 unknown1:2,
+     dtmf:1,          // DTMF
+     unknown2:1,
+     bcl:1,           // Busy Channel Lockout
+     unknown3:3;
+  u8 unknown4:1,
+     scan:1,          // Scan Add
+     highpower:1,     // TX Power Level
+     wide:1,          // BandWidth
+     unknown5:4;
+  u8 unknown6[2];
+} memory[128];
+
+#seekto 0x0E17;
+struct {
+  u8 displayab:1,  // Selected Display
+     unknown1:6,
+     unknown2:1;
+} settings1;
+
+#seekto 0x0E22;
+struct {
+  u8 squelcha;      // menu 02a Squelch Level              0xe22
+  u8 unknown1;
+  u8 tdrab;         //          TDR A/B                    0xe24
+  u8 roger;         // menu 20  Roger Beep                 0xe25
+  u8 timeout;       // menu 16  TOT                        0xe26
+  u8 vox;           // menu 05  VOX                        0xe27
+  u8 unknown2;
+  u8 mdfb;          // menu 27b Memory Display Format B    0xe37
+  u8 dw;            // menu 37  DW                         0xe2a
+  u8 tdr;           // menu 29  Dual Watch                 0xe2b
+  u8 voice;         // menu 03  Voice Prompts              0xe2c
+  u8 beep;          // menu 01  Key Beep                   0xe2d
+  u8 ani;           // menu 30  ANI                        0xe2e
+  u8 unknown3[4];
+  u8 pttdly;        // menu 31  PTT-ID Delay               0xe33
+  u8 unknown4;
+  u8 dtmfst;        // menu 33  DTMF Side Tone             0xe35
+  u8 toa;           // menu 15  TOT Pre-Alert              0xe36
+  u8 mdfa;          // menu 27a Memory Display Format A    0xe37
+  u8 screv;         // menu 09  Scan Resume Method         0xe38
+  u8 pttid;         // menu 32  PTT-ID Enable              0xe39
+  u8 ponmsg;        // menu 36  Power-on Message           0xe3a
+  u8 pf1;           // menu 28  Programmable Function Key  0xe3b
+  u8 unknown5;
+  u8 wtled;         // menu 17  Standby LED Color          0xe3d
+  u8 rxled;         // menu 18  RX LED Color               0xe3e
+  u8 txled;         // menu 19  TX LED Color               0xe3f
+  u8 unknown6;
+  u8 autolk;        // menu 06  Auto Key Lock              0xe41
+  u8 squelchb;      // menu 02b Squelch Level              0xe42
+  u8 control;       //          Control Code               0xe43
+  u8 unknown7;
+  u8 ach;           //          Selected A channel Number  0xe45
+  u8 unknown8[4];
+  u8 password[6];   //          Control Password           0xe4a-0xe4f
+  u8 unknown9[7];
+  u8 code[3];       //          PTT ID Code                0xe57-0xe59
+  u8 vfomr;         //          Frequency/Channel Modevel  0xe5a
+  u8 keylk;         //          Key Lock                   0xe5b
+  u8 unknown10[2];
+  u8 prioritych;    //          Priority Channel           0xe5e
+  u8 bch;           //          Selected B channel Number  0xe5f
+} settings;
+
+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 0x0F10;
+struct {
+  struct vfo a;
+  struct vfo b;
+} vfo;
+
+#seekto 0x1010;
+struct {
+  u8 name[6];
+  u8 unknown[10];
+} names[128];
+
+"""
+
+##### MAGICS #########################################################
+
+# TDXone TD-Q8A magic string
+MSTRING_TDQ8A = "\x02PYNCRAM"
+
+#STIMEOUT = 2
+
+LIST_DTMF = ["QT", "QT+DTMF"]
+LIST_VOICE = ["Off", "Chinese", "English"]
+LIST_OFF1TO9 = ["Off"] + list("123456789")
+LIST_OFF1TO10 = LIST_OFF1TO9 + ["10"]
+LIST_RESUME = ["Time Operated(TO)", "Carrier Operated(CO)", "Search(SE)"]
+LIST_COLOR = ["Off", "Blue", "Orange", "Purple"]
+LIST_MODE = ["Channel", "Frequency", "Name"]
+LIST_PF1 = ["Off", "Scan", "Lamp", "FM Radio", "Alarm"]
+LIST_OFF1TO30 = ["OFF"] + ["%s" % x for x in range(1, 31)]
+LIST_DTMFST = ["Off", "DTMF Sidetone", "ANI Sidetone", "DTMF+ANI Sidetone"]
+LIST_PONMSG = ["Full", "Welcome", "Battery Voltage"]
+LIST_TIMEOUT = ["Off"] + ["%s sec" % x for x in range(15, 615, 15)]
+LIST_PTTID = ["BOT", "EOT", "Both"]
+LIST_ROGER = ["Off"] + LIST_PTTID
+LIST_PRIORITY = ["Off"] + ["%s" % x for x in range(1, 129)]
+LIST_WORKMODE = ["Frequency", "Channel"]
+LIST_AB = ["A", "B"]
+
+LIST_ALMOD = ["Site", "Tone", "Code"]
+LIST_BANDWIDTH = ["Wide", "Narrow"]
+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_OFFAB = ["Off"] + LIST_AB
+LIST_RESETTIME = ["%s ms" % x for x in range(100, 16100, 100)]
+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_TXPOWER = ["High", "Low"]
+LIST_DTMF_SPECIAL_DIGITS = [ "*", "#", "A", "B", "C", "D"]
+LIST_DTMF_SPECIAL_VALUES = [ 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00]
+
+CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ?+-*"
+STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0]
+POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5),
+                chirp_common.PowerLevel("Low", watts=1)]
+VALID_BANDS = [(136000000, 174000000),
+               (400000000, 520000000)]
+
+
+#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("W"):
+        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 _do_ident(radio, magic):
+    """Put the radio in PROGRAM mode"""
+    #  set the serial discipline
+    radio.pipe.baudrate = 9600
+    ####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("P3107"):
+        # 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)
+
+    # 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("R", addr, radio._recv_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, radio._recv_block_size)
+
+        time.sleep(0.05)
+
+        _rawsend(radio, "\x06")
+
+        ack = _rawrecv(radio, 1)
+        if ack != "\x06":
+            raise errors.RadioError(
+                "Radio refused to send block 0x%04x" % addr)
+
+        ####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 += radio.MODEL.ljust(8)
+
+    return data
+
+
+def _upload(radio):
+    """Upload procedure"""
+    # put radio in program mode
+    _ident_radio(radio)
+
+
+
+    addr = 0x0f80
+    frame = _make_frame("R", addr, radio._recv_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, radio._recv_block_size)
+
+    time.sleep(0.05)
+
+    _rawsend(radio, "\x06")
+
+    ack = _rawrecv(radio, 1)
+    if ack != "\x06":
+        raise errors.RadioError(
+            "Radio refused to send block 0x%04x" % addr)
+
+
+
+    _ranges = radio._ranges
+
+    # 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("W", 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 model_match(cls, data):
+    """Match the opened/downloaded image to the correct version"""
+
+    if len(data) == 0x2008:
+        rid = data[0x2000:0x2008]
+        print rid
+        return rid.startswith(cls.MODEL)
+    else:
+        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 TDXoneTDQ8A(chirp_common.CloneModeRadio,
+                  chirp_common.ExperimentalRadio):
+    """TDXone TD-Q8A Radio"""
+    VENDOR = "TDXone"
+    MODEL = "TD-Q8A"
+
+    ####_fileid = [TDQ8A_fp1, ]
+
+    _magic = [MSTRING_TDQ8A, MSTRING_TDQ8A,]
+    _magic_response_length = 8
+    _fw_ver_start = 0x1EF0
+    _recv_block_size = 0x40
+    _mem_size = 0x2000
+
+    #_ranges = [(0x0000, 0x2000)]
+    # same as radio
+    #_ranges = [(0x0010, 0x0810),
+    #           (0x0F10, 0x0F30),
+    #           (0x1010, 0x1810),
+    #           (0x0E20, 0x0E60),
+    #           (0x1F10, 0x1F30)]
+    # in increasing order
+    _ranges = [(0x0010, 0x0810),
+               (0x0E20, 0x0E60),
+               (0x0F10, 0x0F30),
+               (0x1010, 0x1810),
+               (0x1F10, 0x1F30)]
+    _send_block_size = 0x10
+
+    #DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
+    #DTCS_CODES = sorted(chirp_common.ALL_DTCS_CODES)
+    #POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00),
+    #                chirp_common.PowerLevel("High", watts=5.00)]
+    #VALID_BANDS = [(136000000, 174000000),
+    #               (400000000, 520000000)]
+
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.experimental = \
+            ('The TDXone TD-Q8A 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 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 = False #True
+        rf.has_rx_dtcs = False #True
+        rf.has_dtcs_polarity = False #True
+        rf.has_ctone = True
+        rf.has_cross = True
+        rf.valid_modes = ["FM", "NFM"]
+        #rf.valid_characters = self.VALID_CHARS
+        rf.valid_characters = CHARSET
+        rf.valid_name_length = 6
+        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_tmodes = ['', 'Tone', 'TSQL', 'Cross']
+        rf.valid_cross_modes = [
+            "Tone->Tone",
+            "->Tone"]
+        rf.valid_skips = ["", "S"]
+        #rf.valid_dtcs_codes = self.DTCS_CODES
+        rf.memory_bounds = (1, 128)
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_bands = VALID_BANDS
+
+        return rf
+
+
+    def process_mmap(self):
+        """Process the mem map into the mem object"""
+        self._memobj = bitwise.parse(MEM_FORMAT, 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 _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_mem(self, number):
+        return self._memobj.memory[number - 1]
+
+    def _get_nam(self, number):
+        return self._memobj.names[number - 1]
+
+    def get_memory(self, number):
+        _mem = self._get_mem(number)
+        _nam = self._get_nam(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
+
+        if _nam.name:
+            for char in _nam.name:
+                try:
+                    mem.name += CHARSET[char]
+                except IndexError:
+                    break
+            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
+        else:
+            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
+
+        #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
+        else:
+            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
+
+        #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 rxmode or txmode:
+            mem.tmode = "Cross"
+            mem.cross_mode = "%s->%s" % (txmode, rxmode)
+
+        #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"
+
+        mem.power = POWER_LEVELS[1 - _mem.highpower]
+
+        mem.mode = _mem.wide and "FM" or "NFM"
+
+        mem.extra = RadioSettingGroup("Extra", "extra")
+
+        rs = RadioSetting("dtmf", "DTMF",
+                          RadioSettingValueList(LIST_DTMF,
+                                                LIST_DTMF[_mem.dtmf]))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("bcl", "BCL",
+                          RadioSettingValueBoolean(_mem.bcl))
+        mem.extra.append(rs)
+
+        return mem
+
+
+    def _set_mem(self, number):
+        return self._memobj.memory[number - 1]
+
+    def _set_nam(self, number):
+        return self._memobj.names[number - 1]
+
+    def set_memory(self, mem):
+        _mem = self._get_mem(mem.number)
+        _nam = self._get_nam(mem.number)
+
+        if mem.empty:
+            _mem.set_raw("\xff" * 12 + "\xbf" +"\xff" * 3)
+            _nam.set_raw("\xff" * 16)
+            return
+
+        #_mem.set_raw("\x00" * 16)
+        _mem.set_raw("\xff" * 12 + "\x9f" +"\xff" * 3)
+
+        _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
+
+        if _nam.name:
+            for i in range(0, 6):
+                try:
+                    _nam.name[i] = CHARSET.index(mem.name[i])
+                except IndexError:
+                    _nam.name[i] = 0xFF
+
+        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"
+
+        _mem.highpower = mem.power == POWER_LEVELS[0]
+
+        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")
+        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)
+        top = RadioSettings(basic, advanced, )
+
+        # Basic settings
+        rs = RadioSetting("settings.beep", "Beep",
+                           RadioSettingValueBoolean(_mem.settings.beep))
+        basic.append(rs)
+
+        if _mem.settings.squelcha > 0x09:
+            val = 0x00
+        else:
+            val = _mem.settings.squelcha
+        rs = RadioSetting("squelcha", "Squelch Level A",
+                          RadioSettingValueInteger(0, 9, _mem.settings.squelcha))
+        basic.append(rs)
+
+
+        if _mem.settings.squelchb > 0x09:
+            val = 0x00
+        else:
+            val = _mem.settings.squelchb
+        rs = RadioSetting("squelchb", "Squelch Level B",
+                          RadioSettingValueInteger(0, 9, _mem.settings.squelchb))
+        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)
+
+        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)
+
+        rs = RadioSetting("settings.autolk", "Automatic Key Lock",
+                          RadioSettingValueBoolean(_mem.settings.autolk))
+        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)
+
+        if _mem.settings.toa > 0x0A:
+            val = 0x00
+        else:
+            val = _mem.settings.toa
+        rs = RadioSetting("settings.toa", "Time-out Pre-Alert",
+                          RadioSettingValueList(
+                              LIST_OFF1TO10, LIST_OFF1TO10[val]))
+        basic.append(rs)
+
+        if _mem.settings.timeout > 0x28:
+            val = 0x03
+        else:
+            val = _mem.settings.timeout
+        rs = RadioSetting("settings.timeout", "Timeout Timer",
+                          RadioSettingValueList(
+                              LIST_TIMEOUT, LIST_TIMEOUT[val]))
+        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)
+
+        rs = RadioSetting("settings.roger", "Roger Beep",
+                          RadioSettingValueList(LIST_ROGER, LIST_ROGER[
+                              _mem.settings.roger]))
+        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.pf1", "PF1 Key Assignment",
+                          RadioSettingValueList(LIST_PF1, LIST_PF1[
+                              _mem.settings.pf1]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.tdr", "Dual Watch(TDR)",
+                          RadioSettingValueBoolean(_mem.settings.tdr))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.ani", "ANI",
+                          RadioSettingValueBoolean(_mem.settings.ani))
+        basic.append(rs)
+
+        if _mem.settings.pttdly > 0x0A:
+            val = 0x00
+        else:
+            val = _mem.settings.pttdly
+        rs = RadioSetting("settings.pttdly", "PTT ID Delay",
+                          RadioSettingValueList(
+                              LIST_OFF1TO30, LIST_OFF1TO30[val]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.pttid", "When to send PTT ID",
+                          RadioSettingValueList(LIST_PTTID,
+                                                LIST_PTTID[_mem.settings.pttid]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.dtmfst", "DTMF Sidetone",
+                          RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
+                              _mem.settings.dtmfst]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.ponmsg", "Power-On Message",
+                          RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
+                              _mem.settings.ponmsg]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings.dw", "DW",
+                          RadioSettingValueBoolean(_mem.settings.dw))
+        basic.append(rs)
+
+        # Advanced settings
+        rs = RadioSetting("settings.prioritych", "Priority Channel",
+                          RadioSettingValueList(LIST_PRIORITY, LIST_PRIORITY[
+                              _mem.settings.prioritych]))
+        advanced.append(rs)
+
+        rs = RadioSetting("settings.vfomr", "Work Mode",
+                          RadioSettingValueList(LIST_WORKMODE, LIST_WORKMODE[
+                              _mem.settings.vfomr]))
+        advanced.append(rs)
+
+        dtmfchars = "0123456789"
+        _codeobj = _mem.settings.code
+        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+        val = RadioSettingValueString(0, 3, _code, False)
+        val.set_charset(dtmfchars)
+        rs = RadioSetting("settings.code", "PTT-ID Code", val)
+
+        def apply_code(setting, obj):
+            code = []
+            for j in range(0, 3):
+                try:
+                    code.append(dtmfchars.index(str(setting.value)[j]))
+                except IndexError:
+                    code.append(0xFF)
+            obj.code = code
+        rs.set_apply_callback(apply_code, _mem.settings)
+        advanced.append(rs)
+
+        _codeobj = _mem.settings.password
+        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+        val = RadioSettingValueString(0, 6, _code, False)
+        val.set_charset(dtmfchars)
+        rs = RadioSetting("settings.password", "Control Password", val)
+
+        def apply_code(setting, obj):
+            code = []
+            for j in range(0, 6):
+                try:
+                    code.append(dtmfchars.index(str(setting.value)[j]))
+                except IndexError:
+                    code.append(0xFF)
+            obj.password = code
+        rs.set_apply_callback(apply_code, _mem.settings)
+        advanced.append(rs)
+
+        if _mem.settings.tdrab > 0x01:
+            val = 0x00
+        else:
+            val = _mem.settings.tdrab
+        rs = RadioSetting("settings.tdrab", "Dual Watch TX Priority",
+                          RadioSettingValueList(
+                              LIST_AB, LIST_AB[val]))
+        advanced.append(rs)
+
+        rs = RadioSetting("settings.keylk", "Key Lock",
+                          RadioSettingValueBoolean(_mem.settings.keylk))
+        advanced.append(rs)
+
+        rs = RadioSetting("settings.control", "Control Code",
+                          RadioSettingValueBoolean(_mem.settings.control))
+        advanced.append(rs)
+
+        return top
+
+
+
+        """
+        # 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.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)
+
+        # DTMF encode settings
+
+        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)
+
+    """
+
+
+    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
+
+
+    @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 model fingerprint
+        match_model = model_match(cls, filedata)
+
+        #if match_size and match_model:
+        if match_size and match_model:
+            return True
+        else:
+            return False
diff --git a/chirp/drivers/th9000.py b/chirp/drivers/th9000.py
index 58e138e..ad2d7eb 100644
--- a/chirp/drivers/th9000.py
+++ b/chirp/drivers/th9000.py
@@ -434,7 +434,8 @@ def _finish(radio):
     endframe = "\x45\x4E\x44"
     _echo_write(radio, endframe)
     result = radio.pipe.read(1)
-    if result != "\x06":
+    # TYT radios acknowledge the "endframe" command, Luiton radios do not.
+    if result != "" and result != "\x06":  
         LOG.error( "Got:\n%s" % util.hexprint(result))
         raise errors.RadioError("Radio did not finish cleanly")
 
@@ -813,6 +814,17 @@ class Th9000Radio(chirp_common.CloneModeRadio,
 
         return False
 
+# Declaring Aliases (Clones of the real radios)
+class LT580VHF(chirp_common.Alias):
+    VENDOR = "LUITON"
+    MODEL = "LT-580_VHF"
+
+
+class LT580UHF(chirp_common.Alias):
+    VENDOR = "LUITON"
+    MODEL = "LT-580_UHF"
+
+
 @directory.register
 class Th9000220Radio(Th9000Radio):
     """TYT TH-9000 220"""
@@ -828,6 +840,7 @@ class Th9000144Radio(Th9000220Radio):
     MODEL = "TH9000_144" 
     BAUD_RATE = 9600 
     valid_freq = [(136000000, 174000000)]
+    ALIASES = [LT580VHF, ]
 
 @directory.register
 class Th9000440Radio(Th9000220Radio):
@@ -836,3 +849,4 @@ class Th9000440Radio(Th9000220Radio):
     MODEL = "TH9000_440" 
     BAUD_RATE = 9600 
     valid_freq = [(400000000, 490000000)]
+    ALIASES = [LT580UHF, ]
diff --git a/chirp/drivers/th_uv3r.py b/chirp/drivers/th_uv3r.py
index 10273a2..8e40f3a 100644
--- a/chirp/drivers/th_uv3r.py
+++ b/chirp/drivers/th_uv3r.py
@@ -179,7 +179,7 @@ class TYTUV3RRadio(chirp_common.CloneModeRadio):
             rx_mode = tx_mode = "Tone"
             rx_tone = tx_tone = _tone(mem.ctone)
         elif mem.tmode == "DTCS":
-            rx_tone = tx_tone = "DTCS"
+            rx_mode = tx_mode = "DTCS"
             tx_tone = _dcs(mem.dtcs, mem.dtcs_polarity[0])
             rx_tone = _dcs(mem.dtcs, mem.dtcs_polarity[1])
         elif mem.tmode == "Cross":
diff --git a/chirp/drivers/thd72.py b/chirp/drivers/thd72.py
index 2cf978f..71614a3 100644
--- a/chirp/drivers/thd72.py
+++ b/chirp/drivers/thd72.py
@@ -69,6 +69,12 @@ struct {
   u8   passwd[6];
 } frontmatter;
 
+#seekto 0x02c0;
+struct {
+  ul32 start_freq;
+  ul32 end_freq;
+} prog_vfo[6];
+
 #seekto 0x0300;
 struct {
   char power_on_msg[8];
@@ -89,8 +95,8 @@ struct {
 
 #seekto 0x0c00;
 struct {
-  u8 disabled:7,
-     unknown0:1;
+  u8 disabled:4,
+     prog_vfo:4;
   u8 skip;
 } flag[1032];
 
@@ -183,8 +189,24 @@ DUPLEX_REV = {
 EXCH_R = "R\x00\x00\x00\x00"
 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.
+DEFAULT_PROG_VFO = (
+    (136000000, 174000000),
+    (410000000, 470000000),
+    (118000000, 136000000),
+    (136000000, 174000000),
+    (320000000, 400000000),
+    (400000000, 524000000),
+)
+# index of PROG_VFO used for setting memory.unknown1 and memory.unknown2
+# see http://chirp.danplanet.com/issues/1611#note-9
+UNKNOWN_LOOKUP = (0, 7, 4, 0, 4, 7)
+
+
+def get_prog_vfo(frequency):
+    for i, (start, end) in enumerate(DEFAULT_PROG_VFO):
+        if start <= frequency < end:
+            return i
+    raise ValueError("Frequency is out of range.")
 
 
 @directory.register
@@ -283,7 +305,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
 
     def get_raw_memory(self, number):
         return repr(self._memobj.memory[number]) + \
-            repr(self._memobj.flag[(number)])
+            repr(self._memobj.flag[number])
 
     def get_memory(self, number):
         if isinstance(number, str):
@@ -305,7 +327,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
 
         if number > 999:
             mem.extd_number = THD72_SPECIAL_REV[number]
-        if flag.disabled == 0x7f:
+        if flag.disabled == 0xf:
             mem.empty = True
             return mem
 
@@ -348,9 +370,9 @@ class THD72Radio(chirp_common.CloneModeRadio):
         self.add_dirty_block(self._memobj.flag[mem.number])
 
         # only delete non-WX channels
-        was_empty = flag.disabled == 0x7f
+        was_empty = flag.disabled == 0xf
         if mem.empty:
-            flag.disabled = 0x7f
+            flag.disabled = 0xf
             return
         flag.disabled = 0
 
@@ -373,6 +395,10 @@ class THD72Radio(chirp_common.CloneModeRadio):
         _mem.offset = mem.offset
         _mem.mode = MODES_REV[mem.mode]
 
+        prog_vfo = get_prog_vfo(mem.freq)
+        flag.prog_vfo = prog_vfo
+        _mem.unknown1 = _mem.unknown2 = UNKNOWN_LOOKUP[prog_vfo]
+
         if mem.number < 999:
             flag.skip = chirp_common.SKIP_VALUES.index(mem.skip)
 
@@ -509,9 +535,8 @@ class THD72Radio(chirp_common.CloneModeRadio):
             raise errors.RadioError("No response to ID command")
 
     def initialize(self, mmap):
-        mmap[0] = \
-            "\x80\xc8\xb3\x08\x00\x01\x00\x08" + \
-            "\x08\x00\xc0\x27\x09\x00\x00\xff"
+        mmap.set_raw("\x00\xc8\xb3\x08\x00\x01\x00\x08"
+                     "\x08\x00\xc0\x27\x09\x00\x00\x00")
 
     def _get_settings(self):
         top = RadioSettings(self._get_display_settings(),
diff --git a/chirp/drivers/tk270.py b/chirp/drivers/tk270.py
index 1b76027..766b8d2 100644
--- a/chirp/drivers/tk270.py
+++ b/chirp/drivers/tk270.py
@@ -292,21 +292,16 @@ def do_upload(radio):
         handshake(radio, "Rx error in block %03i" % addr)
 
 
-def get_rid(data):
+def get_radio_id(data):
     """Extract the radio identification from the firmware"""
-    rid = data[0x03d0:0x03d8]
-    # we have to invert rid
-    nrid = ""
-    for i in range(1, len(rid) + 1):
-        nrid += rid[-i]
-    rid = nrid
-
-    return rid
+    # Reverse the radio id string. MemoryMap does not support the step/stride
+    # slice argument, so it is first sliced to a str then reversed.
+    return data[0x03d0:0x03d8][::-1]
 
 
 def model_match(cls, data):
     """Match the opened/downloaded image to the correct version"""
-    rid = get_rid(data)
+    rid = get_radio_id(data)
 
     # DEBUG
     #print("Full ident string is %s" % util.hexprint(rid))
@@ -331,13 +326,16 @@ class Kenwood_P60_Radio(chirp_common.CloneModeRadio, chirp_common.ExperimentalRa
     def get_prompts(cls):
         rp = chirp_common.RadioPrompts()
         rp.experimental = \
-            ('This driver is experimental and for personal use only.'
-             'It has a limited set of features, but the most used.\n'
+            ('This driver is experimental; not all features have been '
+            'implemented, but it has those features most used by hams.\n'
              '\n'
-             'This is a first release, so it will contains bugs, use it'
-             'on your own risk, keep a mem backup with the original '
-             'software at hand\n'
+             'This radios are able to work slightly outside the OEM '
+             'frequency limits. After testing, the limit in Chirp has '
+             'been set 4% outside the OEM limit. This allows you to use '
+             'some models on the ham bands.\n'
              '\n'
+             'Nevertheless, each radio has its own hardware limits and '
+             'your mileage may vary.\n'
              )
         rp.pre_download = _(dedent("""\
             Follow this instructions to read your radio:
@@ -408,19 +406,23 @@ class Kenwood_P60_Radio(chirp_common.CloneModeRadio, chirp_common.ExperimentalRa
     def set_variant(self):
         """Select and set the correct variables for the class acording
         to the correct variant of the radio"""
-        rid = get_rid(self._mmap)
+        rid = get_radio_id(self._mmap)
 
         # indentify the radio variant and set the enviroment to it's values
         try:
             self._upper, low, high, self._kind = self.VARIANTS[rid]
-            self._range = [low * 1000000, high * 1000000]
+
+            # Frequency ranges: some model/variants are able to work the near
+            # ham bands, even if they are outside the OEM ranges.
+            # By experimentation we found that a +/- 4% at the edges is in most
+            # cases safe and will cover the near ham band in full
+            self._range = [low * 1000000 * 0.96, high * 1000000 * 1.04]
 
             # put the VARIANT in the class, clean the model / CHs / Type
             # in the same layout as the KPG program
             self._VARIANT = self.MODEL + " [" + str(self._upper) + "CH]: "
-            self._VARIANT += self._kind + ", "
-            self._VARIANT += str(self._range[0]/1000000) + "-"
-            self._VARIANT += str(self._range[1]/1000000) + " Mhz"
+            # In the OEM string we show the real OEM ranges
+            self._VARIANT += self._kind + ", %d - %d MHz" % (low, high)
 
             # DEBUG
             #print self._VARIANT
@@ -470,9 +472,6 @@ class Kenwood_P60_Radio(chirp_common.CloneModeRadio, chirp_common.ExperimentalRa
         res = self._memobj.active[byte] & (pow(2, bit))
         res = not bool(res)
 
-        # DEBUG
-        #print("GET Chan %s, Byte %s, Bit %s, Active %s" % (chan, byte, bit, int(res)))
-
         return res
 
     def set_active(self, chan, value=True):
@@ -942,4 +941,3 @@ class TK378_Radio(Kenwood_P60_Radio):
         "P0378\x04\x00\x00": (32, 370, 470, "SP1"),
         "P0378\x02\x00\x00": (32, 350, 427, "SP2"),
         }
-
diff --git a/chirp/drivers/tk760.py b/chirp/drivers/tk760.py
index 760a3e3..c9a2ff1 100644
--- a/chirp/drivers/tk760.py
+++ b/chirp/drivers/tk760.py
@@ -333,10 +333,10 @@ def model_match(cls, data):
         return False
 
 
-class Kenwood_M60_Radio(chirp_common.CloneModeRadio):
+class Kenwood_M60_Radio(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
     """Kenwood Mobile Family 60 Radios"""
     VENDOR = "Kenwood"
-    _range = [136000000, 500000000]  # don't mind, it will be overited
+    _range = [136000000, 500000000]  # don't mind, it will be overwritten
     _upper = 32
     VARIANT = ""
     MODEL = ""
@@ -345,18 +345,16 @@ class Kenwood_M60_Radio(chirp_common.CloneModeRadio):
     def get_prompts(cls):
         rp = chirp_common.RadioPrompts()
         rp.experimental = \
-            ('This driver is experimental and for personal use only.'
-             'It has a limited set of features, but the most used.\n'
+            ('This driver is experimental; not all features have been '
+            'implemented, but it has those features most used by hams.\n'
              '\n'
-             'The most notorius missing features are this:\n'
-             '=> PTT ID, in fact it is disabled if detected\n'
-             '=> Priority / Home channel\n'
-             '=> DTMF/2-Tone\n'
-             '=> Others\n'
+             'This radios are able to work slightly outside the OEM '
+             'frequency limits. After testing, the limit in Chirp has '
+             'been set 4% outside the OEM limit. This allows you to use '
+             'some models on the ham bands.\n'
              '\n'
-             'If you need one of this, get your official software to do it'
-             'and raise and issue on the chirp site about it; maybe'
-             'it will be implemented in the future.'
+             'Nevertheless, each radio has its own hardware limits and '
+             'your mileage may vary.\n'
              )
         rp.pre_download = _(dedent("""\
             Follow this instructions to read your radio:
@@ -432,14 +430,18 @@ class Kenwood_M60_Radio(chirp_common.CloneModeRadio):
         # indentify the radio variant and set the enviroment to it's values
         try:
             self._upper, low, high, self._kind = self.VARIANTS[rid]
-            self._range = [low * 1000000, high * 1000000]
+
+            # Frequency ranges: some model/variants are able to work the near
+            # ham bands, even if they are outside the OEM ranges.
+            # By experimentation we found that a +/- 4% at the edges is in most
+            # cases safe and will cover the near ham band in full
+            self._range = [low * 1000000 * 0.96, high * 1000000 * 1.04]
 
             # put the VARIANT in the class, clean the model / CHs / Type
             # in the same layout as the KPG program
             self._VARIANT = self.MODEL + " [" + str(self._upper) + "CH]: "
-            self._VARIANT += self._kind + ", "
-            self._VARIANT += str(self._range[0]/1000000) + "-"
-            self._VARIANT += str(self._range[1]/1000000) + " Mhz"
+            # In the OEM string we show the real OEM ranges
+            self._VARIANT += self._kind + ", %d - %d MHz" % (low, high)
 
         except KeyError:
             LOG.debug("Wrong Kenwood radio, ID or unknown variant")
@@ -814,7 +816,7 @@ class TK760_Radio(Kenwood_M60_Radio):
     TYPE = "M0760"
     VARIANTS = {
         "M0760\x01\x00\x00": (32, 136, 156, "K2"),
-        "M0760\x00\x00\x00": (32, 144, 174, "K")   # 148-147 Original
+        "M0760\x00\x00\x00": (32, 148, 174, "K")
         }
 
 
@@ -825,7 +827,7 @@ class TK762_Radio(Kenwood_M60_Radio):
     TYPE = "M0762"
     VARIANTS = {
         "M0762\x01\x00\x00": (2, 136, 156, "K2"),
-        "M0762\x00\x00\x00": (2, 144, 174, "K")   # 148-147 Original
+        "M0762\x00\x00\x00": (2, 148, 174, "K")
         }
 
 
@@ -836,7 +838,7 @@ class TK768_Radio(Kenwood_M60_Radio):
     TYPE = "M0768"
     VARIANTS = {
         "M0768\x21\x00\x00": (32, 136, 156, "K2"),
-        "M0768\x20\x00\x00": (32, 144, 174, "K")   # 148-147 Original
+        "M0768\x20\x00\x00": (32, 148, 174, "K")
         }
 
 
@@ -846,7 +848,7 @@ class TK860_Radio(Kenwood_M60_Radio):
     MODEL = "TK-860"
     TYPE = "M0860"
     VARIANTS = {
-        "M0860\x05\x00\x00": (32, 406, 440, "F4"),   # 406-430 Original
+        "M0860\x05\x00\x00": (32, 406, 430, "F4"),
         "M0860\x04\x00\x00": (32, 488, 512, "F3"),
         "M0860\x03\x00\x00": (32, 470, 496, "F2"),
         "M0860\x02\x00\x00": (32, 450, 476, "F1")
@@ -859,7 +861,7 @@ class TK862_Radio(Kenwood_M60_Radio):
     MODEL = "TK-862"
     TYPE = "M0862"
     VARIANTS = {
-        "M0862\x05\x00\x00": (2, 406, 440, "F4"),   # 406-430 Original
+        "M0862\x05\x00\x00": (2, 406, 430, "F4"),
         "M0862\x04\x00\x00": (2, 488, 512, "F3"),
         "M0862\x03\x00\x00": (2, 470, 496, "F2"),
         "M0862\x02\x00\x00": (2, 450, 476, "F1")
@@ -872,7 +874,7 @@ class TK868_Radio(Kenwood_M60_Radio):
     MODEL = "TK-868"
     TYPE = "M0868"
     VARIANTS = {
-        "M0868\x25\x00\x00": (32, 406, 440, "F4"),   # 406-430 Original
+        "M0868\x25\x00\x00": (32, 406, 430, "F4"),
         "M0868\x24\x00\x00": (32, 488, 512, "F3"),
         "M0868\x23\x00\x00": (32, 470, 496, "F2"),
         "M0868\x22\x00\x00": (32, 450, 476, "F1")
diff --git a/chirp/drivers/tk760g.py b/chirp/drivers/tk760g.py
index 6563e06..0f90d5e 100644
--- a/chirp/drivers/tk760g.py
+++ b/chirp/drivers/tk760g.py
@@ -696,7 +696,7 @@ class memBank(chirp_common.Bank):
     index = 0
 
 
-class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
+class Kenwood_Serie_60G(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
     """Kenwood Serie 60G Radios base class"""
     VENDOR = "Kenwood"
     BAUD_RATE = 9600
@@ -715,28 +715,16 @@ class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
     def get_prompts(cls):
         rp = chirp_common.RadioPrompts()
         rp.experimental = \
-            ('This driver is experimental and for personal use only.'
-             'It has a limited set of features, but the most used.'
-             ''
-             'The most notorius missing features are this:'
-             '=> PTT ID, in fact it is disabled if detected'
-             '=> Priority / Home channel'
-             '=> Bank names'
-             '=> Others'
-             ''
-             'If you need one of this, get your official software to do it'
-             '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.'
+            ('This driver is experimental; not all features have been '
+            'implemented, but it has those features most used by hams.\n'
+             '\n'
+             'This radios are able to work slightly outside the OEM '
+             'frequency limits. After testing, the limit in Chirp has '
+             'been set 4% outside the OEM limit. This allows you to use '
+             'some models on the ham bands.\n'
+             '\n'
+             'Nevertheless, each radio has its own hardware limits and '
+             'your mileage may vary.\n'
              )
         rp.pre_download = _(dedent("""\
             Follow this instructions to download your info:
@@ -797,7 +785,7 @@ class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
         """Prepare the areas in the memmap to do a consistend write
         it has to make an update on the x300 area with banks and channel
         info; other in the x1000 with banks and channel counts
-        and a last one in x7000 with flog data"""
+        and a last one in x7000 with flag data"""
         rchs = 0
         data = dict()
 
@@ -830,7 +818,7 @@ class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
                 k = 1
                 raise errors.InvalidValueError(
                     "Invalid bank value '%k', bad data in the image? \
-                    Triying to fix this, review your bank data!" % k)
+                    Trying to fix this, review your bank data!" % k)
             c = 1
             for i in v:
                 fdata += chr(k) + chr(c) + chr(k - 1) + chr(i)
@@ -877,7 +865,12 @@ class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
         # indentify the radio variant and set the enviroment to it's values
         try:
             self._upper, low, high, self._kind = self.VARIANTS[rid]
-            self._range = [low * 1000000, high * 1000000]
+
+            # Frequency ranges: some model/variants are able to work the near
+            # ham bands, even if they are outside the OEM ranges.
+            # By experimentation we found that 4% at the edges is in most
+            # cases safe and will cover the near ham bands in full
+            self._range = [low * 1000000 * 0.96, high * 1000000 * 1.04]
 
             # setting the bank data in the features, 8 & 16 CH dont have banks
             if self._upper < 32:
@@ -887,8 +880,8 @@ class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
             # put the VARIANT in the class, clean the model / CHs / Type
             # in the same layout as the KPG program
             self._VARIANT = self.MODEL + " [" + str(self._upper) + "CH]: "
-            self._VARIANT += self._kind + ", " + str(self._range[0]/1000000) + "-"
-            self._VARIANT += str(self._range[1]/1000000) + " Mhz"
+            # In the OEM string we show the real OEM ranges
+            self._VARIANT += self._kind + ", %d - %d MHz" % (low, high)
 
         except KeyError:
             LOG.debug("Wrong Kenwood radio, ID or unknown variant")
@@ -1261,13 +1254,8 @@ class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
         basic.append(pom)
 
         # dealer
-        mstr = ""
-        valid_chars = [32, 44, 45, 47, 58, 91, 93] + range(48, 58) + \
-            range(65, 91) + range(97, 123)
-
-        for i in range(0, len(self._VARIANT)):
-            if ord(self._VARIANT[i]) in valid_chars:
-                mstr += self._VARIANT[i]
+        valid_chars = ",-/:[]" + chirp_common.CHARSET_ALPHANUMERIC
+        mstr = "".join([c for c in self._VARIANT if c in valid_chars])
 
         val = RadioSettingValueString(0, 35, mstr)
         val.set_mutable(False)
@@ -1539,24 +1527,13 @@ class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
 #
 # WARNING !!!! Radios With Password in the data section ###############
 #
-# when a radio has a data password (aka to program it) the last byte (#8)
+# 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
-#
-# 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.
+# Translation: Chirps will read and write password protected radios
+# with no problem.
 
 
 @directory.register
@@ -1567,8 +1544,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, 450, "C2"),   # 400-430Mhz original
-        "M86806\xff":       (128, 430, 490, "C3"),   # 450-490Mhz original
+        "M86808\xff":       (128, 400, 430, "C2"),
+        "M86806\xff":       (128, 450, 490, "C3"),
         }
 
 
@@ -1580,8 +1557,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, 430, 470, "E"),     # 440-470Mhz original
-        "M8620V\xff":       (8, 430, 470, "(N)E"),  # 440-470Mhz original
+        "M8620&\xff":       (8, 440, 470, "E"),
+        "M8620V\xff":       (8, 440, 470, "(N)E"),
         }
 
 
@@ -1591,11 +1568,11 @@ class TK860G_Radios(Kenwood_Serie_60G):
     MODEL = "TK-860G"
     TYPE = "M8600"
     VARIANTS = {
-        "M8600\x08\xff":    (128, 400, 450, "K"),   # 400-430Mhz original
-        "M8600\x06\xff":    (128, 430, 490, "K1"),  # 450-490Mhz original
+        "M8600\x08\xff":    (128, 400, 430, "K"),
+        "M8600\x06\xff":    (128, 450, 490, "K1"),
         "M8600\x07\xff":    (128, 485, 512, "K2"),
-        "M8600\x18\xff":    (128, 400, 450, "M"),   # 400-430Mhz original
-        "M8600\x16\xff":    (128, 430, 490, "M1"),  # 450-490Mhz original
+        "M8600\x18\xff":    (128, 400, 430, "M"),
+        "M8600\x16\xff":    (128, 450, 490, "M1"),
         "M8600\x17\xff":    (128, 485, 520, "M2"),
         }
 
@@ -1608,9 +1585,9 @@ class TK768G_Radios(Kenwood_Serie_60G):
     # Note that 8 CH don't have banks
     VARIANTS = {
         "M7680\x15\xff": (8, 136, 162, "M2"),
-        "M7680\x14\xff": (8, 144, 174, "M"),      # 148-174 original
+        "M7680\x14\xff": (8, 148, 174, "M"),
         "M76805\xff":    (128, 136, 162, "C2"),
-        "M76804\xff":    (128, 144, 174, "C"),    # 148-174 original
+        "M76804\xff":    (128, 148, 174, "C"),
         }
 
 
@@ -1622,9 +1599,9 @@ class TK762G_Radios(Kenwood_Serie_60G):
     # Note that 8 CH don't have banks
     VARIANTS = {
         "M7620\x05\xff": (8, 136, 162, "K2"),
-        "M7620\x04\xff": (8, 144, 172, "K"),    # 148-172 original
-        "M7620$\xff":    (8, 144, 172, "E"),    # 148-172 original
-        "M7620T\xff":    (8, 144, 172, "NE"),   # 148-172 original
+        "M7620\x04\xff": (8, 148, 172, "K"),
+        "M7620$\xff":    (8, 148, 172, "E"),
+        "M7620T\xff":    (8, 148, 172, "NE"),
         }
 
 
@@ -1635,9 +1612,9 @@ class TK760G_Radios(Kenwood_Serie_60G):
     TYPE = "M7600"
     VARIANTS = {
         "M7600\x05\xff": (128, 136, 162, "K2"),
-        "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
+        "M7600\x04\xff": (128, 148, 174, "K"),
+        "M7600\x14\xff": (128, 148, 174, "M"),
+        "M7600T\xff":    (128, 148, 174, "NE")
         }
 
 
@@ -1657,10 +1634,10 @@ class TK378G_Radios(Kenwood_Serie_60G):
     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\x16\xff": (16, 450, 470, "M"),
+        "P3780\x17\xff": (16, 400, 420, "M1"),
         "P3780\x36\xff": (128, 490, 512, "C"),
-        "P3780\x39\xff": (128, 403, 440, "C1")         # 403-430 Mhz original
+        "P3780\x39\xff": (128, 403, 430, "C1")
         }
 
 
@@ -1670,10 +1647,10 @@ class TK372G_Radios(Kenwood_Serie_60G):
     MODEL = "TK-372G"
     TYPE = "P3720"
     VARIANTS = {
-        "P3720\x06\xff": (32, 440, 470, "K"),        # 450-470 Mhz original
+        "P3720\x06\xff": (32, 450, 470, "K"),
         "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
+        "P3720\x09\xff": (32, 403, 430, "K3")
         }
 
 
@@ -1683,16 +1660,16 @@ class TK370G_Radios(Kenwood_Serie_60G):
     MODEL = "TK-370G"
     TYPE = "P3700"
     VARIANTS = {
-        "P3700\x06\xff": (128, 440, 470, "K"),      # 450-470 Mhz original
+        "P3700\x06\xff": (128, 450, 470, "K"),
         "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\x09\xff": (128, 403, 430, "K3"),
+        "P3700\x16\xff": (128, 450, 470, "M"),
         "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
+        "P3700\x19\xff": (128, 403, 430, "M3"),
+        "P3700&\xff": (128, 440, 470, "E"),
+        "P3700V\xff": (128, 440, 470, "NE")
         }
 
 
@@ -1702,17 +1679,17 @@ class TK360G_Radios(Kenwood_Serie_60G):
     MODEL = "TK-360G"
     TYPE = "P3600"
     VARIANTS = {
-        "P3600\x06\xff": (8, 440, 470, "K"),        # 450-470 Mhz original
+        "P3600\x06\xff": (8, 450, 470, "K"),
         "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\x09\xff": (8, 403, 430, "K3"),
+        "P3600&\xff": (8, 440, 470, "E"),
+        "P3600)\xff": (8, 406, 430, "E1"),
+        "P3600\x16\xff": (8, 450, 470, "M"),
         "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
+        "P3600\x19\xff": (8, 403, 430, "M2"),
+        "P3600V\xff": (8, 440, 470, "NE"),
+        "P3600Y\xff": (8, 403, 430, "NE1")
         }
 
 
@@ -1724,9 +1701,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, 144, 174, "C"),      # 150-174 original
+        "P27804\xff":    (128, 150, 174, "C"),
         "P2780\x15\xff": (16,  136, 150, "M1"),
-        "P2780\x14\xff": (16,  144, 174, "M")       # 150-174 original
+        "P2780\x14\xff": (16,  150, 174, "M")
         }
 
 
@@ -1737,7 +1714,7 @@ class TK272G_Radios(Kenwood_Serie_60G):
     TYPE = "P2720"
     VARIANTS = {
         "P2720\x05\xfb": (32, 136, 150, "K1"),
-        "P2720\x04\xfb": (32, 144, 174, "K")        # 150-174 original
+        "P2720\x04\xfb": (32, 150, 174, "K")
         }
 
 
@@ -1747,11 +1724,11 @@ class TK270G_Radios(Kenwood_Serie_60G):
     MODEL = "TK-270G"
     TYPE = "P2700"
     VARIANTS = {
-        "P2700T\xff":    (128, 144, 174, "NE/NT"),   # 146-174 original
-        "P2700$\xff":    (128, 144, 174, "E"),       # 146-174 original
-        "P2700\x14\xff": (128, 144, 174, "M"),       # 150-174 original
+        "P2700T\xff":    (128, 146, 174, "NE/NT"),
+        "P2700$\xff":    (128, 146, 174, "E"),
+        "P2700\x14\xff": (128, 150, 174, "M"),
         "P2700\x05\xff": (128, 136, 150, "K1"),
-        "P2700\x04\xff": (128, 144, 174, "K")        # 150-174 original
+        "P2700\x04\xff": (128, 150, 174, "K")
         }
 
 
@@ -1763,9 +1740,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, 144, 174, "M"),        # 150-174 original
+        "P2600T\xff":    (8, 146, 174, "N"),
+        "P2600$\xff":    (8, 150, 174, "E"),
+        "P2600\x14\xff": (8, 150, 174, "M"),
         "P2600\x05\xff": (8, 136, 150, "K1"),
-        "P2600\x04\xff": (8, 144, 174, "K")         # 150-174 original
+        "P2600\x04\xff": (8, 150, 174, "K")
         }
diff --git a/chirp/drivers/tk8102.py b/chirp/drivers/tk8102.py
index b4491c4..cd4a84c 100644
--- a/chirp/drivers/tk8102.py
+++ b/chirp/drivers/tk8102.py
@@ -302,30 +302,33 @@ class KenwoodTKx102Radio(chirp_common.CloneModeRadio):
                 val += 0xA000
             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
+        rx_mode = tx_mode = None
+        rx_tone = 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
+        if mem.tmode == "Tone":
+            tx_mode = "Tone"
+            rx_mode = None
+            tx_tone = int(mem.rtone * 10)
+        elif mem.tmode == "TSQL":
+            rx_mode = tx_mode = "Tone"
+            rx_tone = tx_tone = int(mem.ctone * 10)
+        elif mem.tmode == "DTCS":
+            tx_mode = rx_mode = "DTCS"
+            tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+            rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
+        elif mem.tmode == "Cross":
+            tx_mode, rx_mode = mem.cross_mode.split("->")
+            if tx_mode == "DTCS":
+                tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+            elif tx_mode == "Tone":
+                tx_tone = int(mem.rtone * 10)
+            if rx_mode == "DTCS":
+                rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
+            elif rx_mode == "Tone":
+                rx_tone = int(mem.ctone * 10)
+
+        _mem.rx_tone = rx_tone
+        _mem.tx_tone = tx_tone
 
         LOG.debug("Set TX %s (%i) RX %s (%i)" %
                   (tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))
diff --git a/chirp/drivers/uv5r.py b/chirp/drivers/uv5r.py
index 83ce148..0cb46bb 100644
--- a/chirp/drivers/uv5r.py
+++ b/chirp/drivers/uv5r.py
@@ -282,7 +282,7 @@ vhf_220_radio = "\x02"
 
 BASETYPE_UV5R = ["BFS", "BFB", "N5R-2", "N5R2", "N5RV", "BTS", "D5R2"]
 BASETYPE_F11 = ["USA"]
-BASETYPE_UV82 = ["US2S", "B82S", "BF82", "N82-2", "N822"]
+BASETYPE_UV82 = ["US2S2", "B82S", "BF82", "N82-2", "N822"]
 BASETYPE_BJ55 = ["BJ55"]  # needed for for the Baojie UV-55 in bjuv55.py
 BASETYPE_UV6 = ["BF1", "UV6"]
 BASETYPE_KT980HP = ["BFP3V3 B"]
@@ -395,7 +395,7 @@ def _firmware_version_from_image(radio):
     return version
 
 
-def _do_ident(radio, magic):
+def _do_ident(radio, magic, secondack=True):
     serial = radio.pipe
     serial.timeout = 1
 
@@ -448,10 +448,11 @@ def _do_ident(radio, magic):
         LOG.debug(msg)
         raise errors.RadioError("Unexpected response from radio.")
 
-    serial.write("\x06")
-    ack = serial.read(1)
-    if ack != "\x06":
-        raise errors.RadioError("Radio refused clone")
+    if secondack:
+        serial.write("\x06")
+        ack = serial.read(1)
+        if ack != "\x06":
+            raise errors.RadioError("Radio refused clone")
 
     return ident
 
@@ -501,6 +502,11 @@ def _get_radio_firmware_version(radio):
     return version
 
 
+IDENT_BLACKLIST = {
+    "\x50\x0D\x0C\x20\x16\x03\x28": "Radio identifies as BTECH UV-5X3",
+}
+
+
 def _ident_radio(radio):
     for magic in radio._idents:
         error = None
@@ -511,6 +517,22 @@ def _ident_radio(radio):
             LOG.error(e)
             error = e
             time.sleep(2)
+
+    for magic, reason in IDENT_BLACKLIST.items():
+        try:
+            _do_ident(radio, magic, secondack=False)
+        except errors.RadioError as e:
+            # No match, try the next one
+            continue
+
+        # If we got here, it means we identified the radio as
+        # something other than one of our valid idents. Warn
+        # the user so they can do the right thing.
+        LOG.warning(('Identified radio as a blacklisted model '
+                     '(details: %s)') % reason)
+        raise errors.RadioError(('%s. Please choose the proper vendor/'
+                                 'model and try again.') % reason)
+
     if error:
         raise error
     raise errors.RadioError("Radio did not respond")
@@ -522,8 +544,29 @@ def _do_download(radio):
     radio_version = _get_radio_firmware_version(radio)
     LOG.info("Radio Version is %s" % repr(radio_version))
 
-    if not any(type in radio_version for type in radio._basetype):
+    if "HN5RV" in radio_version:
+        # A radio with HN5RV firmware has been detected. It could be a
+        # UV-5R style radio with HIGH/LOW power levels or it could be a
+        # BF-F8HP style radio with HIGH/MID/LOW power levels.
+        # We are going to count on the user to make the right choice and
+        # then append that model type to the end of the image so it can
+        # be properly detected when loaded.
+        append_model = True
+    elif "\xFF" * 14 in radio_version:
+        # A radio UV-5R style radio that reports no firmware version has
+        # been detected.
+        # We are going to count on the user to make the right choice and
+        # then append that model type to the end of the image so it can
+        # be properly detected when loaded.
+        append_model = True
+    elif not any(type in radio_version for type in radio._basetype):
+        # This radio can't be properly detected by parsing its firmware
+        # version.
         raise errors.RadioError("Incorrect 'Model' selected.")
+    else:
+        # This radio can be properly detected by parsing its firmware version.
+        # There is no need to append its model type to the end of the image.
+        append_model = False
 
     # Main block
     LOG.debug("downloading main block...")
@@ -536,6 +579,10 @@ def _do_download(radio):
     # Auxiliary block starts at 0x1ECO (?)
     for i in range(0x1EC0, 0x2000, 0x40):
         data += _read_block(radio, i, 0x40, False)
+
+    if append_model:
+        data += radio.MODEL.ljust(8)
+
     LOG.debug("done.")
     return memmap.MemoryMap(data)
 
@@ -564,34 +611,68 @@ def _do_upload(radio):
     LOG.info("Image Version is %s" % repr(image_version))
     LOG.info("Radio Version is %s" % repr(radio_version))
 
-    if image_version != radio_version:
+    # default ranges
+    _ranges_main_default = [
+        (0x0008, 0x0CF8),
+        (0x0D08, 0x0DF8),
+        (0x0E08, 0x1808)
+        ]
+    _ranges_aux_default = [
+        (0x1EC0, 0x1EF0),
+        ]
+
+    # extra aux ranges
+    _ranges_aux_extra = [
+        (0x1F60, 0x1F70),
+        (0x1F80, 0x1F90),
+        (0x1FC0, 0x1FD0)
+        ]
+
+    if image_version == radio_version:
+        image_matched_radio = True
+        if image_version.startswith("HN5RV"):
+            ranges_main = _ranges_main_default
+            ranges_aux = _ranges_aux_default + _ranges_aux_extra
+        elif image_version == 0xFF * 14:
+            ranges_main = _ranges_main_default
+            ranges_aux = _ranges_aux_default + _ranges_aux_extra
+        else:
+            ranges_main = radio._ranges_main
+            ranges_aux = radio._ranges_aux
+    elif any(type in radio_version for type in radio._basetype):
+        image_matched_radio = False
+        ranges_main = _ranges_main_default
+        ranges_aux = _ranges_aux_default
+    else:
         msg = ("The upload was stopped because the firmware "
                "version of the image (%s) does not match that "
                "of the radio (%s).")
         raise errors.RadioError(msg % (image_version, radio_version))
 
     # Main block
-    for i in range(0x08, 0x1808, 0x10):
-        _send_block(radio, i - 0x08, radio.get_mmap()[i:i + 0x10])
-        _do_status(radio, i)
-    _do_status(radio, radio.get_memsize())
+    for start_addr, end_addr in ranges_main:
+        for i in range(start_addr, end_addr, 0x10):
+            _send_block(radio, i - 0x08, radio.get_mmap()[i:i + 0x10])
+            _do_status(radio, i)
+        _do_status(radio, radio.get_memsize())
 
     if len(radio.get_mmap().get_packed()) == 0x1808:
         LOG.info("Old image, not writing aux block")
         return  # Old image, no aux block
 
-    if image_version != radio_version:
+    # Auxiliary block at radio address 0x1EC0, our offset 0x1808
+    for start_addr, end_addr in ranges_aux:
+        for i in range(start_addr, end_addr, 0x10):
+            addr = 0x1808 + (i - 0x1EC0)
+            _send_block(radio, i, radio.get_mmap()[addr:addr + 0x10])
+
+    if not image_matched_radio:
         msg = ("Upload finished, but the 'Other Settings' "
                "could not be sent because the firmware "
                "version of the image (%s) does not match "
                "that of the radio (%s).")
         raise errors.RadioError(msg % (image_version, radio_version))
 
-    # Auxiliary block at radio address 0x1EC0, our offset 0x1808
-    for i in range(0x1EC0, 0x2000, 0x10):
-        addr = 0x1808 + (i - 0x1EC0)
-        _send_block(radio, i, radio.get_mmap()[addr:addr + 0x10])
-
 UV5R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=4.00),
                      chirp_common.PowerLevel("Low",  watts=1.00)]
 
@@ -605,6 +686,20 @@ UV5R_CHARSET = chirp_common.CHARSET_UPPER_NUMERIC + \
     "!@#$%^&*()+-=[]:\";'<>?,./"
 
 
+def model_match(cls, data):
+    """Match the opened/downloaded image to the correct version"""
+
+    if len(data) == 0x1950:
+        rid = data[0x1948:0x1950]
+        return rid.startswith(cls.MODEL)
+    elif len(data) == 0x1948:
+        rid = data[cls._fw_ver_file_start:cls._fw_ver_file_stop]
+        if any(type in rid for type in cls._basetype):
+            return True
+    else:
+        return False
+
+
 class BaofengUV5R(chirp_common.CloneModeRadio,
                   chirp_common.ExperimentalRadio):
 
@@ -627,6 +722,13 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
     _fw_ver_file_start = 0x1838
     _fw_ver_file_stop = 0x1846
 
+    _ranges_main = [
+                    (0x0008, 0x1808),
+                   ]
+    _ranges_aux = [
+                   (0x1EC0, 0x2000),
+                  ]
+
     @classmethod
     def get_prompts(cls):
         rp = chirp_common.RadioPrompts()
@@ -687,13 +789,10 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
     def match_model(cls, filedata, filename):
         match_size = False
         match_model = False
-        if len(filedata) in [0x1808, 0x1948]:
+        if len(filedata) in [0x1808, 0x1948, 0x1950]:
             match_size = True
-        fwdata = _firmware_version_from_data(filedata,
-                                             cls._fw_ver_file_start,
-                                             cls._fw_ver_file_stop)
-        if any(type in fwdata for type in cls._basetype):
-            match_model = True
+        match_model = model_match(cls, filedata)
+
         if match_size and match_model:
             return True
         else:
@@ -1167,8 +1266,9 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
 
         if self.MODEL == "UV-82HP":
             # this is a UV-82HP only feature
-            rs = RadioSetting("vfomrlock", "VFO/MR Switching",
-                              RadioSettingValueBoolean(_settings.vfomrlock))
+            rs = RadioSetting(
+                "vfomrlock", "VFO/MR Switching (BTech UV-82HP only)",
+                RadioSettingValueBoolean(_settings.vfomrlock))
             advanced.append(rs)
 
         if self.MODEL == "UV-82":
@@ -1179,15 +1279,15 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
 
         if self.MODEL == "UV-82HP":
             # this is an UV-82HP only feature
-            rs = RadioSetting("singleptt", "Single PTT",
+            rs = RadioSetting("singleptt", "Single PTT (BTech UV-82HP only)",
                               RadioSettingValueBoolean(_settings.singleptt))
             advanced.append(rs)
 
         if self.MODEL == "UV-82HP":
             # this is an UV-82HP only feature
-            rs = RadioSetting("tdrch", "Tone Burst Frequency",
-                              RadioSettingValueList(
-                                  RTONE_LIST, RTONE_LIST[_settings.tdrch]))
+            rs = RadioSetting(
+                "tdrch", "Tone Burst Frequency (BTech UV-82HP only)",
+                RadioSettingValueList(RTONE_LIST, RTONE_LIST[_settings.tdrch]))
             advanced.append(rs)
 
         if len(self._mmap.get_packed()) == 0x1808:
@@ -1648,9 +1748,14 @@ class RT5_TPAlias(chirp_common.Alias):
     MODEL = "RT5(tri-power)"
 
 
+class RH5RAlias(chirp_common.Alias):
+    VENDOR = "Rugged"
+    MODEL = "RH5R"
+
+
 @directory.register
 class BaofengUV5RGeneric(BaofengUV5R):
-    ALIASES = [UV5XAlias, RT5RAlias, RT5RVAlias, RT5Alias]
+    ALIASES = [UV5XAlias, RT5RAlias, RT5RVAlias, RT5Alias, RH5RAlias]
 
 
 @directory.register
@@ -1734,7 +1839,7 @@ class IntekKT980Radio(BaofengUV5R):
 class BaofengBFF8HPRadio(BaofengUV5R):
     VENDOR = "Baofeng"
     MODEL = "BF-F8HP"
-    ALIASES = [RT5_TPAlias, ]
+    ALIASES = [RT5_TPAlias]
     _basetype = BASETYPE_F8HP
     _idents = [UV5R_MODEL_291,
                UV5R_MODEL_A58
diff --git a/chirp/drivers/uvb5.py b/chirp/drivers/uvb5.py
index f05aa73..3300005 100644
--- a/chirp/drivers/uvb5.py
+++ b/chirp/drivers/uvb5.py
@@ -238,10 +238,19 @@ def do_upload(radio):
         radio.pipe.write(frame)
         ack = radio.pipe.read(1)
         if ack != "\x06":
-            raise errors.RadioError("Radio NAK'd block at address 0x%04x" % i)
+            # UV-B5/UV-B6 radios with 27 menus do not support service settings
+            # and will stop ACKing when the upload reaches 0x0F10
+            if i == 0x0F10:
+                # must be a radio with 27 menus detected - stop upload
+                break
+            else:
+                LOG.debug("Radio NAK'd block at address 0x%04x" % i)
+                raise errors.RadioError(
+                    "Radio NAK'd block at address 0x%04x" % i)
+        LOG.debug("Radio ACK'd block at address 0x%04x" % i)
         do_status(radio, "to", i)
 
-DUPLEX = ["", "-", "+", 'off', "split"]
+DUPLEX = ["", "-", "+"]
 UVB5_STEPS = [5.00, 6.25, 10.0, 12.5, 20.0, 25.0]
 CHARSET = "0123456789- ABCDEFGHIJKLMNOPQRSTUVWXYZ/_+*"
 SPECIALS = {
@@ -296,7 +305,7 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
         rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
         rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
                                 "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
-        rf.valid_duplexes = DUPLEX
+        rf.valid_duplexes = DUPLEX + ["split"]
         rf.can_odd_split = True
         rf.valid_skips = ["", "S"]
         rf.valid_characters = CHARSET
@@ -404,10 +413,6 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
         mem.skip = "" if _mem.scanadd else "S"
         mem.power = POWER_LEVELS[_mem.highpower]
 
-        if mem.freq == mem.offset and mem.duplex == "-":
-            mem.duplex = "off"
-            mem.offset = 0
-
         if _nam:
             for char in _nam:
                 try:
@@ -450,10 +455,7 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
 
         _mem.freq = mem.freq / 10
 
-        if mem.duplex == "off":
-            _mem.duplex = DUPLEX.index("-")
-            _mem.offset = _mem.freq
-        elif mem.duplex == "split":
+        if mem.duplex == "split":
             diff = mem.offset - mem.freq
             _mem.duplex = DUPLEX.index("-") if diff < 0 else DUPLEX.index("+")
             _mem.offset = abs(diff) / 10
diff --git a/chirp/drivers/vx3.py b/chirp/drivers/vx3.py
index b187b79..31cd991 100644
--- a/chirp/drivers/vx3.py
+++ b/chirp/drivers/vx3.py
@@ -141,11 +141,18 @@ struct {
 u8  banks_unk1;
 
 #seekto 0x0356;
-u32 banks_unk2;
+struct {
+    u32 unmask;
+} banks_unmask1;
 
 #seekto 0x0409;
 u8  banks_unk3;
 
+#seekto 0x0416;
+struct {
+    u32 unmask;
+} banks_unmask2;
+
 #seekto 0x04CA;
 struct {
     u8    memory[16];
@@ -300,7 +307,11 @@ class VX3BankModel(chirp_common.BankModel):
         channels_in_bank.add(memory.number)
         self._update_bank_with_channel_numbers(bank, channels_in_bank)
         _bank_used = self._radio._memobj.bank_used[bank.index]
-        _bank_used.in_use = 0x0000
+        _bank_used.in_use = ((len(channels_in_bank) - 1) * 2)
+        _banks_unmask1 = self._radio._memobj.banks_unmask1
+        _banks_unmask2 = self._radio._memobj.banks_unmask2
+        _banks_unmask1.unmask = 0x0017FFFF
+        _banks_unmask2.unmask = 0x0017FFFF
 
     def remove_memory_from_mapping(self, memory, bank):
         channels_in_bank = self._get_channel_numbers_in_bank(bank)
@@ -311,8 +322,10 @@ class VX3BankModel(chirp_common.BankModel):
                             (memory.number, bank))
         self._update_bank_with_channel_numbers(bank, channels_in_bank)
 
-        if not channels_in_bank:
-            _bank_used = self._radio._memobj.bank_used[bank.index]
+        _bank_used = self._radio._memobj.bank_used[bank.index]
+        if channels_in_bank:
+            _bank_used.in_use = ((len(channels_in_bank) - 1) * 2)
+        else:
             _bank_used.in_use = 0xFFFF
 
     def get_mapping_memories(self, bank):
diff --git a/chirp/drivers/vx5.py b/chirp/drivers/vx5.py
index aa6d4bd..fea8659 100644
--- a/chirp/drivers/vx5.py
+++ b/chirp/drivers/vx5.py
@@ -16,6 +16,7 @@
 
 from chirp.drivers import yaesu_clone
 from chirp import chirp_common, directory, errors, bitwise
+from textwrap import dedent
 
 MEM_FORMAT = """
 #seekto 0x002A;
@@ -70,12 +71,7 @@ u8 current_bank;
 TMODES = ["", "Tone", "TSQL", "DTCS"]
 DUPLEX = ["", "-", "+", "split"]
 MODES = ["FM", "AM", "WFM"]
-STEPS = list(chirp_common.TUNING_STEPS)
-STEPS.remove(6.25)
-STEPS.remove(30.0)
-STEPS.append(100.0)
-STEPS.append(9.0)
-
+STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0]
 POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00),
                 chirp_common.PowerLevel("L3", watts=2.50),
                 chirp_common.PowerLevel("L2", watts=1.00),
@@ -167,7 +163,7 @@ class VX5Radio(yaesu_clone.YaesuCloneModeRadio):
     _block_size = 8
 
     def _checksums(self):
-        return [yaesu_clone.YaesuChecksum(0x0000, 0x1FB9)]
+        return [yaesu_clone.YaesuChecksum(0x0000, 0x1FB9, 0x1FBA)]
 
     def get_features(self):
         rf = chirp_common.RadioFeatures()
@@ -177,6 +173,7 @@ class VX5Radio(yaesu_clone.YaesuCloneModeRadio):
         rf.has_dtcs_polarity = False
         rf.valid_modes = MODES + ["NFM"]
         rf.valid_tmodes = TMODES
+        rf.valid_tuning_steps = STEPS
         rf.valid_duplexes = DUPLEX
         rf.memory_bounds = (1, 220)
         rf.valid_bands = [(500000,    16000000),
@@ -277,6 +274,26 @@ class VX5Radio(yaesu_clone.YaesuCloneModeRadio):
         _flg.pskip = mem.skip == "P"
 
     @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.pre_download = _(dedent("""\
+            1. Turn radio off.
+            2. Connect cable to MIC/EAR jack.
+            3. Press and hold in the [F/W] key while turning the radio on
+                ("CLONE" will appear on the display).
+            4. <b>After clicking OK</b>, press the [VFO(DW)SC] key to receive
+                the image from the radio."""))
+        rp.pre_upload = _(dedent("""\
+            1. Turn radio off.
+            2. Connect cable to MIC/EAR jack.
+            3. Press and hold in the [F/W] key while turning the radio on
+                ("CLONE" will appear on the display).
+            4. Press the [MR(SKP)SC] key ("CLONE WAIT" will appear
+                on the LCD).
+            5. Click OK to send image to radio."""))
+        return rp
+
+    @classmethod
     def match_model(cls, filedata, filename):
         return len(filedata) == cls._memsize
 
diff --git a/chirp/drivers/vx7.py b/chirp/drivers/vx7.py
index 1d7b11a..ee5d383 100644
--- a/chirp/drivers/vx7.py
+++ b/chirp/drivers/vx7.py
@@ -81,11 +81,7 @@ DUPLEX = ["", "-", "+", "split"]
 MODES = ["FM", "AM", "WFM", "Auto"]
 TMODES = ["", "Tone", "TSQL", "DTCS", "Cross"]
 CROSS_MODES = ["DTCS->", "Tone->DTCS", "DTCS->Tone"]
-STEPS = list(chirp_common.TUNING_STEPS)
-STEPS.remove(6.25)
-STEPS.remove(30.0)
-STEPS.append(100.0)
-STEPS.append(9.0)
+STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0, 9.0]
 
 CHARSET = ["%i" % int(x) for x in range(0, 10)] + \
     [" "] + \
diff --git a/chirp/drivers/vx8.py b/chirp/drivers/vx8.py
index 6e8dd5f..4fc9836 100644
--- a/chirp/drivers/vx8.py
+++ b/chirp/drivers/vx8.py
@@ -243,9 +243,15 @@ struct {
     } entry[8];
   } digi_path_7;
   u8 unknown22[2];
-  struct {
-    char padded_string[16];
-  } message_macro[7];
+} aprs;
+
+#seekto 0x%04X;
+struct {
+  char padded_string[16];
+} aprs_msg_macro[%d];
+
+#seekto 0x%04X;
+struct {
   u8 unknown23:5,
      selected_msg_group:3;
   u8 unknown24;
@@ -285,7 +291,7 @@ struct {
      vibrate_grp:6;
   u8 unknown34:2,
      vibrate_bln:6;
-} aprs;
+} aprs2;
 
 #seekto 0x%04X;
 struct {
@@ -496,14 +502,16 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
     """Yaesu VX-8"""
     BAUD_RATE = 38400
     VENDOR = "Yaesu"
-    MODEL = "VX-8"
-    VARIANT = "R"
+    MODEL = "VX-8R"
 
     _model = "AH029"
     _memsize = 65227
     _block_lengths = [10, 65217]
     _block_size = 32
-    _mem_params = (0xC24A,  # APRS beacon metadata address.
+    _mem_params = (0xC128,  # APRS message macros
+                   5,       # Number of message macros
+                   0xC178,  # APRS2
+                   0xC24A,  # APRS beacon metadata address.
                    40,      # Number of beacons stored.
                    0xC60A,  # APRS beacon content address.
                    194,     # Length of beacon data stored.
@@ -511,6 +519,62 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
     _has_vibrate = False
     _has_af_dual = True
 
+    _SG_RE = re.compile(r"(?P<sign>[-+NESW]?)(?P<d>[\d]+)[\s\.,]*"
+                        "(?P<m>[\d]*)[\s\']*(?P<s>[\d]*)")
+
+    _RX_BAUD = ("off", "1200 baud", "9600 baud")
+    _TX_DELAY = ("100ms", "200ms", "300ms",
+                 "400ms", "500ms", "750ms", "1000ms")
+    _WIND_UNITS = ("m/s", "mph")
+    _RAIN_UNITS = ("mm", "inch")
+    _TEMP_UNITS = ("C", "F")
+    _ALT_UNITS = ("m", "ft")
+    _DIST_UNITS = ("km", "mile")
+    _POS_UNITS = ("dd.mmmm'", "dd mm'ss\"")
+    _SPEED_UNITS = ("km/h", "knot", "mph")
+    _TIME_SOURCE = ("manual", "GPS")
+    _TZ = ("-13:00", "-13:30", "-12:00", "-12:30", "-11:00", "-11:30",
+           "-10:00", "-10:30", "-09:00", "-09:30", "-08:00", "-08:30",
+           "-07:00", "-07:30", "-06:00", "-06:30", "-05:00", "-05:30",
+           "-04:00", "-04:30", "-03:00", "-03:30", "-02:00", "-02:30",
+           "-01:00", "-01:30", "-00:00", "-00:30", "+01:00", "+01:30",
+           "+02:00", "+02:30", "+03:00", "+03:30", "+04:00", "+04:30",
+           "+05:00", "+05:30", "+06:00", "+06:30", "+07:00", "+07:30",
+           "+08:00", "+08:30", "+09:00", "+09:30", "+10:00", "+10:30",
+           "+11:00", "+11:30")
+    _BEACON_TYPE = ("Off", "Interval")
+    _BEACON_INT = ("15s", "30s", "1m", "2m", "3m", "5m", "10m", "15m",
+                   "30m")
+    _DIGI_PATHS = ("OFF", "WIDE1-1", "WIDE1-1, WIDE2-1", "Digi Path 4",
+                   "Digi Path 5", "Digi Path 6", "Digi Path 7", "Digi Path 8")
+    _MSG_GROUP_NAMES = ("Message Group 1", "Message Group 2",
+                        "Message Group 3", "Message Group 4",
+                        "Message Group 5", "Message Group 6",
+                        "Message Group 7", "Message Group 8")
+    _POSITIONS = ("GPS", "Manual Latitude/Longitude",
+                  "Manual Latitude/Longitude", "P1", "P2", "P3", "P4",
+                  "P5", "P6", "P7", "P8", "P9", "P10")
+    _FLASH = ("OFF", "ON")
+    _BEEP_SELECT = ("Off", "Key+Scan", "Key")
+    _SQUELCH = ["%d" % x for x in range(0, 16)]
+    _VOLUME = ["%d" % x for x in range(0, 33)]
+    _OPENING_MESSAGE = ("Off", "DC", "Message", "Normal")
+    _SCAN_RESUME = ["%.1fs" % (0.5 * x) for x in range(4, 21)] + \
+                   ["Busy", "Hold"]
+    _SCAN_RESTART = ["%.1fs" % (0.1 * x) for x in range(1, 10)] + \
+                    ["%.1fs" % (0.5 * x) for x in range(2, 21)]
+    _LAMP_KEY = ["Key %d sec" % x for x in range(2, 11)] + \
+                ["Continuous", "OFF"]
+    _LCD_CONTRAST = ["Level %d" % x for x in range(1, 33)]
+    _LCD_DIMMER = ["Level %d" % x for x in range(1, 5)]
+    _TOT_TIME = ["Off"] + ["%.1f min" % (0.5 * x) for x in range(1, 21)]
+    _OFF_ON = ("Off", "On")
+    _VOL_MODE = ("Normal", "Auto Back")
+    _DTMF_MODE = ("Manual", "Auto")
+    _DTMF_SPEED = ("50ms", "100ms")
+    _DTMF_DELAY = ("50ms", "250ms", "450ms", "750ms", "1000ms")
+    _MY_SYMBOL = ("/[ Person", "/b Bike", "/> Car", "User selected")
+
     @classmethod
     def get_prompts(cls):
         rp = chirp_common.RadioPrompts()
@@ -547,6 +611,7 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
         rf.can_odd_split = True
         rf.has_ctone = False
         rf.has_bank_names = True
+        rf.has_settings = True
         return rf
 
     def get_raw_memory(self, number):
@@ -654,89 +719,6 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
     def get_bank_model(self):
         return VX8BankModel(self)
 
-
- at directory.register
-class VX8DRadio(VX8Radio):
-    """Yaesu VX-8DR"""
-    _model = "AH29D"
-    _mem_params = (0xC24A,  # APRS beacon metadata address.
-                   50,      # Number of beacons stored.
-                   0xC6FA,  # APRS beacon content address.
-                   146,     # Length of beacon data stored.
-                   50)      # Number of beacons stored.
-    VARIANT = "DR"
-
-    _SG_RE = re.compile(r"(?P<sign>[-+NESW]?)(?P<d>[\d]+)[\s\.,]*"
-                        "(?P<m>[\d]*)[\s\']*(?P<s>[\d]*)")
-
-    _RX_BAUD = ("off", "1200 baud", "9600 baud")
-    _TX_DELAY = ("100ms", "150ms", "200ms", "250ms", "300ms",
-                 "400ms", "500ms", "750ms", "1000ms")
-    _WIND_UNITS = ("m/s", "mph")
-    _RAIN_UNITS = ("mm", "inch")
-    _TEMP_UNITS = ("C", "F")
-    _ALT_UNITS = ("m", "ft")
-    _DIST_UNITS = ("km", "mile")
-    _POS_UNITS = ("dd.mmmm'", "dd mm'ss\"")
-    _SPEED_UNITS = ("km/h", "knot", "mph")
-    _TIME_SOURCE = ("manual", "GPS")
-    _TZ = ("-13:00", "-13:30", "-12:00", "-12:30", "-11:00", "-11:30",
-           "-10:00", "-10:30", "-09:00", "-09:30", "-08:00", "-08:30",
-           "-07:00", "-07:30", "-06:00", "-06:30", "-05:00", "-05:30",
-           "-04:00", "-04:30", "-03:00", "-03:30", "-02:00", "-02:30",
-           "-01:00", "-01:30", "-00:00", "-00:30", "+01:00", "+01:30",
-           "+02:00", "+02:30", "+03:00", "+03:30", "+04:00", "+04:30",
-           "+05:00", "+05:30", "+06:00", "+06:30", "+07:00", "+07:30",
-           "+08:00", "+08:30", "+09:00", "+09:30", "+10:00", "+10:30",
-           "+11:00", "+11:30")
-    _BEACON_TYPE = ("Off", "Interval", "SmartBeaconing")
-    _SMARTBEACON_PROFILE = ("Off", "Type 1", "Type 2", "Type 3")
-    _BEACON_INT = ("30s", "1m", "2m", "3m", "5m", "10m", "15m",
-                   "20m", "30m", "60m")
-    _DIGI_PATHS = ("OFF", "WIDE1-1", "WIDE1-1, WIDE2-1", "Digi Path 4",
-                   "Digi Path 5", "Digi Path 6", "Digi Path 7", "Digi Path 8")
-    _MSG_GROUP_NAMES = ("Message Group 1", "Message Group 2",
-                        "Message Group 3", "Message Group 4",
-                        "Message Group 5", "Message Group 6",
-                        "Message Group 7", "Message Group 8")
-    _POSITIONS = ("GPS", "Manual Latitude/Longitude",
-                  "Manual Latitude/Longitude", "P1", "P2", "P3", "P4",
-                  "P5", "P6", "P7", "P8", "P9")
-    _FLASH = ("OFF", "2 seconds", "4 seconds", "6 seconds", "8 seconds",
-              "10 seconds", "20 seconds", "30 seconds", "60 seconds",
-              "CONTINUOUS", "every 2 seconds", "every 3 seconds",
-              "every 4 seconds", "every 5 seconds", "every 6 seconds",
-              "every 7 seconds", "every 8 seconds", "every 9 seconds",
-              "every 10 seconds", "every 20 seconds", "every 30 seconds",
-              "every 40 seconds", "every 50 seconds", "every minute",
-              "every 2 minutes", "every 3 minutes", "every 4 minutes",
-              "every 5 minutes", "every 6 minutes", "every 7 minutes",
-              "every 8 minutes", "every 9 minutes", "every 10 minutes")
-    _BEEP_SELECT = ("Off", "Key+Scan", "Key")
-    _SQUELCH = ["%d" % x for x in range(0, 16)]
-    _VOLUME = ["%d" % x for x in range(0, 33)]
-    _OPENING_MESSAGE = ("Off", "DC", "Message", "Normal")
-    _SCAN_RESUME = ["%.1fs" % (0.5 * x) for x in range(4, 21)] + \
-                   ["Busy", "Hold"]
-    _SCAN_RESTART = ["%.1fs" % (0.1 * x) for x in range(1, 10)] + \
-                    ["%.1fs" % (0.5 * x) for x in range(2, 21)]
-    _LAMP_KEY = ["Key %d sec" % x for x in range(2, 11)] + \
-                ["Continuous", "OFF"]
-    _LCD_CONTRAST = ["Level %d" % x for x in range(1, 16)]
-    _LCD_DIMMER = ["Level %d" % x for x in range(1, 5)]
-    _TOT_TIME = ["Off"] + ["%.1f min" % (0.5 * x) for x in range(1, 21)]
-    _OFF_ON = ("Off", "On")
-    _VOL_MODE = ("Normal", "Auto Back")
-    _DTMF_MODE = ("Manual", "Auto")
-    _DTMF_SPEED = ("50ms", "100ms")
-    _DTMF_DELAY = ("50ms", "250ms", "450ms", "750ms", "1000ms")
-    _MY_SYMBOL = ("/[ Person", "/b Bike", "/> Car", "User selected")
-
-    def get_features(self):
-        rf = VX8Radio.get_features(self)
-        rf.has_settings = True
-        return rf
-
     @classmethod
     def _digi_path_to_str(cls, path):
         path_cmp = []
@@ -816,6 +798,7 @@ class VX8DRadio(VX8Radio):
     def _get_aprs_general_settings(self):
         menu = RadioSettingGroup("aprs_general", "APRS General")
         aprs = self._memobj.aprs
+        aprs2 = self._memobj.aprs2
 
         val = RadioSettingValueString(
                 0, 6, str(aprs.my_callsign.callsign).rstrip("\xFF"))
@@ -830,8 +813,8 @@ class VX8DRadio(VX8Radio):
         menu.append(rs)
 
         val = RadioSettingValueList(self._MY_SYMBOL,
-                                    self._MY_SYMBOL[aprs.selected_my_symbol])
-        rs = RadioSetting("aprs.selected_my_symbol", "My Symbol", val)
+                                    self._MY_SYMBOL[aprs2.selected_my_symbol])
+        rs = RadioSetting("aprs2.selected_my_symbol", "My Symbol", val)
         menu.append(rs)
 
         symbols = list(chirp_common.APRS_SYMBOLS)
@@ -958,6 +941,7 @@ class VX8DRadio(VX8Radio):
     def _get_aprs_rx_settings(self):
         menu = RadioSettingGroup("aprs_rx", "APRS Receive")
         aprs = self._memobj.aprs
+        aprs2 = self._memobj.aprs2
 
         val = RadioSettingValueList(self._RX_BAUD, self._RX_BAUD[aprs.rx_baud])
         rs = RadioSetting("aprs.rx_baud", "Modem RX", val)
@@ -981,55 +965,55 @@ class VX8DRadio(VX8Radio):
         menu.append(rs)
 
         val = RadioSettingValueList(self._FLASH,
-                                    self._FLASH[aprs.flash_msg])
-        rs = RadioSetting("aprs.flash_msg", "Flash on personal message", val)
+                                    self._FLASH[aprs2.flash_msg])
+        rs = RadioSetting("aprs2.flash_msg", "Flash on personal message", val)
         menu.append(rs)
 
         if self._has_vibrate:
             val = RadioSettingValueList(self._FLASH,
-                                        self._FLASH[aprs.vibrate_msg])
-            rs = RadioSetting("aprs.vibrate_msg",
+                                        self._FLASH[aprs2.vibrate_msg])
+            rs = RadioSetting("aprs2.vibrate_msg",
                               "Vibrate on personal message", val)
             menu.append(rs)
 
         val = RadioSettingValueList(self._FLASH[:10],
-                                    self._FLASH[aprs.flash_bln])
-        rs = RadioSetting("aprs.flash_bln", "Flash on bulletin message", val)
+                                    self._FLASH[aprs2.flash_bln])
+        rs = RadioSetting("aprs2.flash_bln", "Flash on bulletin message", val)
         menu.append(rs)
 
         if self._has_vibrate:
             val = RadioSettingValueList(self._FLASH[:10],
-                                        self._FLASH[aprs.vibrate_bln])
-            rs = RadioSetting("aprs.vibrate_bln",
+                                        self._FLASH[aprs2.vibrate_bln])
+            rs = RadioSetting("aprs2.vibrate_bln",
                               "Vibrate on bulletin message", val)
             menu.append(rs)
 
         val = RadioSettingValueList(self._FLASH[:10],
-                                    self._FLASH[aprs.flash_grp])
-        rs = RadioSetting("aprs.flash_grp", "Flash on group message", val)
+                                    self._FLASH[aprs2.flash_grp])
+        rs = RadioSetting("aprs2.flash_grp", "Flash on group message", val)
         menu.append(rs)
 
         if self._has_vibrate:
             val = RadioSettingValueList(self._FLASH[:10],
-                                        self._FLASH[aprs.vibrate_grp])
-            rs = RadioSetting("aprs.vibrate_grp",
+                                        self._FLASH[aprs2.vibrate_grp])
+            rs = RadioSetting("aprs2.vibrate_grp",
                               "Vibrate on group message", val)
             menu.append(rs)
 
-        filter_val = [m.padded_string for m in aprs.msg_group]
+        filter_val = [m.padded_string for m in aprs2.msg_group]
         filter_val = self._strip_ff_pads(filter_val)
         for index, filter_text in enumerate(filter_val):
             val = RadioSettingValueString(0, 9, filter_text)
-            rs = RadioSetting("aprs.msg_group_%d" % index,
+            rs = RadioSetting("aprs2.msg_group_%d" % index,
                               "Message Group %d" % (index + 1), val)
             menu.append(rs)
             rs.set_apply_callback(self.apply_ff_padded_string,
-                                  aprs.msg_group[index])
+                                  aprs2.msg_group[index])
         # TODO: Use filter_val as the list entries and update it on edit.
         val = RadioSettingValueList(
             self._MSG_GROUP_NAMES,
-            self._MSG_GROUP_NAMES[aprs.selected_msg_group])
-        rs = RadioSetting("aprs.selected_msg_group", "Selected Message Group",
+            self._MSG_GROUP_NAMES[aprs2.selected_msg_group])
+        rs = RadioSetting("aprs2.selected_msg_group", "Selected Message Group",
                           val)
         menu.append(rs)
 
@@ -1068,6 +1052,7 @@ class VX8DRadio(VX8Radio):
     def _get_aprs_tx_settings(self):
         menu = RadioSettingGroup("aprs_tx", "APRS Transmit")
         aprs = self._memobj.aprs
+        aprs2 = self._memobj.aprs2
 
         beacon_type = (aprs.tx_smartbeacon << 1) | aprs.tx_interval_beacon
         val = RadioSettingValueList(
@@ -1103,40 +1088,17 @@ class VX8DRadio(VX8Radio):
                           "Beacon Status Text", val)
         menu.append(rs)
 
-        message_macro = [m.padded_string for m in aprs.message_macro]
+        message_macro = [m.padded_string for m in self._memobj.aprs_msg_macro]
         message_macro = self._strip_ff_pads(message_macro)
         for index, msg_text in enumerate(message_macro):
             val = RadioSettingValueString(0, 16, msg_text)
-            rs = RadioSetting("aprs.message_macro_%d" % index,
+            rs = RadioSetting("aprs_msg_macro_%d" % index,
                               "Message Macro %d" % (index + 1), val)
             rs.set_apply_callback(self.apply_ff_padded_string,
-                                  aprs.message_macro[index])
+                                  self._memobj.aprs_msg_macro[index])
             menu.append(rs)
 
         path_str = list(self._DIGI_PATHS)
-        path_str[3] = self._digi_path_to_str(aprs.digi_path_3_6[0])
-        val = RadioSettingValueString(0, 22, path_str[3])
-        rs = RadioSetting("aprs.digi_path_3", "Digi Path 4 (2 entries)", val)
-        rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[0])
-        menu.append(rs)
-
-        path_str[4] = self._digi_path_to_str(aprs.digi_path_3_6[1])
-        val = RadioSettingValueString(0, 22, path_str[4])
-        rs = RadioSetting("aprs.digi_path_4", "Digi Path 5 (2 entries)", val)
-        rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[1])
-        menu.append(rs)
-
-        path_str[5] = self._digi_path_to_str(aprs.digi_path_3_6[2])
-        val = RadioSettingValueString(0, 22, path_str[5])
-        rs = RadioSetting("aprs.digi_path_5", "Digi Path 6 (2 entries)", val)
-        rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[2])
-        menu.append(rs)
-
-        path_str[6] = self._digi_path_to_str(aprs.digi_path_3_6[3])
-        val = RadioSettingValueString(0, 22, path_str[6])
-        rs = RadioSetting("aprs.digi_path_6", "Digi Path 7 (2 entries)", val)
-        rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[3])
-        menu.append(rs)
 
         path_str[7] = self._digi_path_to_str(aprs.digi_path_7)
         val = RadioSettingValueString(0, 88, path_str[7])
@@ -1151,71 +1113,14 @@ class VX8DRadio(VX8Radio):
         # path_str[5] = path_str[5] or self._DIGI_PATHS[5]
         # path_str[6] = path_str[6] or self._DIGI_PATHS[6]
         # path_str[7] = path_str[7] or self._DIGI_PATHS[7]
-        path_str[3] = self._DIGI_PATHS[3]
-        path_str[4] = self._DIGI_PATHS[4]
-        path_str[5] = self._DIGI_PATHS[5]
-        path_str[6] = self._DIGI_PATHS[6]
+
         path_str[7] = self._DIGI_PATHS[7]
         val = RadioSettingValueList(path_str,
-                                    path_str[aprs.selected_digi_path])
-        rs = RadioSetting("aprs.selected_digi_path", "Selected Digi Path", val)
-        menu.append(rs)
-
-        return menu
-
-    def _get_aprs_smartbeacon(self):
-        menu = RadioSettingGroup("aprs_smartbeacon", "APRS SmartBeacon")
-        aprs = self._memobj.aprs
-
-        val = RadioSettingValueList(
-            self._SMARTBEACON_PROFILE,
-            self._SMARTBEACON_PROFILE[aprs.active_smartbeaconing])
-        rs = RadioSetting("aprs.active_smartbeaconing", "SmartBeacon profile",
-                          val)
+                                    path_str[aprs2.selected_digi_path])
+        rs = RadioSetting("aprs2.selected_digi_path",
+                          "Selected Digi Path", val)
         menu.append(rs)
 
-        for profile in range(3):
-            pfx = "type%d" % (profile + 1)
-            path = "aprs.smartbeaconing_profile[%d]" % profile
-            prof = aprs.smartbeaconing_profile[profile]
-
-            low_val = RadioSettingValueInteger(2, 30, prof.low_speed_mph)
-            high_val = RadioSettingValueInteger(3, 70, prof.high_speed_mph)
-            low_val.get_max = lambda: min(30, int(high_val.get_value()) - 1)
-
-            rs = RadioSetting("%s.low_speed_mph" % path,
-                              "%s Low Speed (mph)" % pfx, low_val)
-            menu.append(rs)
-
-            rs = RadioSetting("%s.high_speed_mph" % path,
-                              "%s High Speed (mph)" % pfx, high_val)
-            menu.append(rs)
-
-            val = RadioSettingValueInteger(1, 100, prof.slow_rate_min)
-            rs = RadioSetting("%s.slow_rate_min" % path,
-                              "%s Slow rate (minutes)" % pfx, val)
-            menu.append(rs)
-
-            val = RadioSettingValueInteger(10, 180, prof.fast_rate_sec)
-            rs = RadioSetting("%s.fast_rate_sec" % path,
-                              "%s Fast rate (seconds)" % pfx, val)
-            menu.append(rs)
-
-            val = RadioSettingValueInteger(5, 90, prof.turn_angle)
-            rs = RadioSetting("%s.turn_angle" % path,
-                              "%s Turn angle (degrees)" % pfx, val)
-            menu.append(rs)
-
-            val = RadioSettingValueInteger(1, 255, prof.turn_slop)
-            rs = RadioSetting("%s.turn_slop" % path,
-                              "%s Turn slop" % pfx, val)
-            menu.append(rs)
-
-            val = RadioSettingValueInteger(5, 180, prof.turn_time_sec)
-            rs = RadioSetting("%s.turn_time_sec" % path,
-                              "%s Turn time (seconds)" % pfx, val)
-            menu.append(rs)
-
         return menu
 
     def _get_dtmf_settings(self):
@@ -1389,7 +1294,6 @@ class VX8DRadio(VX8Radio):
         top = RadioSettings(self._get_aprs_general_settings(),
                             self._get_aprs_rx_settings(),
                             self._get_aprs_tx_settings(),
-                            self._get_aprs_smartbeacon(),
                             self._get_dtmf_settings(),
                             self._get_misc_settings(),
                             self._get_scan_settings())
@@ -1550,9 +1454,207 @@ class VX8DRadio(VX8Radio):
 
 
 @directory.register
+class VX8DRadio(VX8Radio):
+    """Yaesu VX-8DR"""
+    MODEL = "VX-8DR"
+    _model = "AH29D"
+    _mem_params = (0xC128,  # APRS message macros
+                   7,       # Number of message macros
+                   0xC198,  # APRS2
+                   0xC24A,  # APRS beacon metadata address.
+                   50,      # Number of beacons stored.
+                   0xC6FA,  # APRS beacon content address.
+                   146,     # Length of beacon data stored.
+                   50)      # Number of beacons stored.
+
+    _BEACON_TYPE = ("Off", "Interval", "SmartBeaconing")
+    _SMARTBEACON_PROFILE = ("Off", "Type 1", "Type 2", "Type 3")
+    _POSITIONS = ("GPS", "Manual Latitude/Longitude",
+                  "Manual Latitude/Longitude", "P1", "P2", "P3", "P4",
+                  "P5", "P6", "P7", "P8", "P9")
+    _FLASH = ("OFF", "2 seconds", "4 seconds", "6 seconds", "8 seconds",
+              "10 seconds", "20 seconds", "30 seconds", "60 seconds",
+              "CONTINUOUS", "every 2 seconds", "every 3 seconds",
+              "every 4 seconds", "every 5 seconds", "every 6 seconds",
+              "every 7 seconds", "every 8 seconds", "every 9 seconds",
+              "every 10 seconds", "every 20 seconds", "every 30 seconds",
+              "every 40 seconds", "every 50 seconds", "every minute",
+              "every 2 minutes", "every 3 minutes", "every 4 minutes",
+              "every 5 minutes", "every 6 minutes", "every 7 minutes",
+              "every 8 minutes", "every 9 minutes", "every 10 minutes")
+    _LCD_CONTRAST = ["Level %d" % x for x in range(1, 16)]
+    _MY_SYMBOL = ("/[ Person", "/b Bike", "/> Car", "User selected")
+
+    def _get_aprs_tx_settings(self):
+        menu = RadioSettingGroup("aprs_tx", "APRS Transmit")
+        aprs = self._memobj.aprs
+        aprs2 = self._memobj.aprs2
+
+        beacon_type = (aprs.tx_smartbeacon << 1) | aprs.tx_interval_beacon
+        val = RadioSettingValueList(
+                self._BEACON_TYPE, self._BEACON_TYPE[beacon_type])
+        rs = RadioSetting("aprs.transmit", "TX Beacons", val)
+        rs.set_apply_callback(self.apply_beacon_type, aprs)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+                self._TX_DELAY, self._TX_DELAY[aprs.tx_delay])
+        rs = RadioSetting("aprs.tx_delay", "TX Delay", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+                self._BEACON_INT, self._BEACON_INT[aprs.beacon_interval])
+        rs = RadioSetting("aprs.beacon_interval", "Beacon Interval", val)
+        menu.append(rs)
+
+        desc = []
+        status = [m.padded_string for m in self._memobj.aprs_beacon_status_txt]
+        status = self._strip_ff_pads(status)
+        for index, msg_text in enumerate(status):
+            val = RadioSettingValueString(0, 60, msg_text)
+            desc.append("Beacon Status Text %d" % (index + 1))
+            rs = RadioSetting("aprs_beacon_status_txt_%d" % index, desc[-1],
+                              val)
+            rs.set_apply_callback(self.apply_ff_padded_string,
+                                  self._memobj.aprs_beacon_status_txt[index])
+            menu.append(rs)
+        val = RadioSettingValueList(desc,
+                                    desc[aprs.selected_beacon_status_txt])
+        rs = RadioSetting("aprs.selected_beacon_status_txt",
+                          "Beacon Status Text", val)
+        menu.append(rs)
+
+        message_macro = [m.padded_string for m in self._memobj.aprs_msg_macro]
+        message_macro = self._strip_ff_pads(message_macro)
+        for index, msg_text in enumerate(message_macro):
+            val = RadioSettingValueString(0, 16, msg_text)
+            rs = RadioSetting("aprs_msg_macro_%d" % index,
+                              "Message Macro %d" % (index + 1), val)
+            rs.set_apply_callback(self.apply_ff_padded_string,
+                                  self._memobj.aprs_msg_macro[index])
+            menu.append(rs)
+
+        path_str = list(self._DIGI_PATHS)
+        path_str[3] = self._digi_path_to_str(aprs2.digi_path_3_6[0])
+        val = RadioSettingValueString(0, 22, path_str[3])
+        rs = RadioSetting("aprs2.digi_path_3", "Digi Path 4 (2 entries)", val)
+        rs.set_apply_callback(self.apply_digi_path, aprs2.digi_path_3_6[0])
+        menu.append(rs)
+
+        path_str[4] = self._digi_path_to_str(aprs2.digi_path_3_6[1])
+        val = RadioSettingValueString(0, 22, path_str[4])
+        rs = RadioSetting("aprs2.digi_path_4", "Digi Path 5 (2 entries)", val)
+        rs.set_apply_callback(self.apply_digi_path, aprs2.digi_path_3_6[1])
+        menu.append(rs)
+
+        path_str[5] = self._digi_path_to_str(aprs2.digi_path_3_6[2])
+        val = RadioSettingValueString(0, 22, path_str[5])
+        rs = RadioSetting("aprs2.digi_path_5", "Digi Path 6 (2 entries)", val)
+        rs.set_apply_callback(self.apply_digi_path, aprs2.digi_path_3_6[2])
+        menu.append(rs)
+
+        path_str[6] = self._digi_path_to_str(aprs2.digi_path_3_6[3])
+        val = RadioSettingValueString(0, 22, path_str[6])
+        rs = RadioSetting("aprs2.digi_path_6", "Digi Path 7 (2 entries)", val)
+        rs.set_apply_callback(self.apply_digi_path, aprs2.digi_path_3_6[3])
+        menu.append(rs)
+
+        path_str[7] = self._digi_path_to_str(aprs.digi_path_7)
+        val = RadioSettingValueString(0, 88, path_str[7])
+        rs = RadioSetting("aprs.digi_path_7", "Digi Path 8 (8 entries)", val)
+        rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_7)
+        menu.append(rs)
+
+        # Show friendly messages for empty slots rather than blanks.
+        # TODO: Rebuild this when digi_path_[34567] change.
+        # path_str[3] = path_str[3] or self._DIGI_PATHS[3]
+        # path_str[4] = path_str[4] or self._DIGI_PATHS[4]
+        # path_str[5] = path_str[5] or self._DIGI_PATHS[5]
+        # path_str[6] = path_str[6] or self._DIGI_PATHS[6]
+        # path_str[7] = path_str[7] or self._DIGI_PATHS[7]
+        path_str[3] = self._DIGI_PATHS[3]
+        path_str[4] = self._DIGI_PATHS[4]
+        path_str[5] = self._DIGI_PATHS[5]
+        path_str[6] = self._DIGI_PATHS[6]
+        path_str[7] = self._DIGI_PATHS[7]
+        val = RadioSettingValueList(path_str,
+                                    path_str[aprs2.selected_digi_path])
+        rs = RadioSetting("aprs2.selected_digi_path",
+                          "Selected Digi Path", val)
+        menu.append(rs)
+
+        return menu
+
+    def _get_aprs_smartbeacon(self):
+        menu = RadioSettingGroup("aprs_smartbeacon", "APRS SmartBeacon")
+        aprs2 = self._memobj.aprs2
+
+        val = RadioSettingValueList(
+           self._SMARTBEACON_PROFILE,
+           self._SMARTBEACON_PROFILE[aprs2.active_smartbeaconing])
+        rs = RadioSetting("aprs2.active_smartbeaconing",
+                          "SmartBeacon profile", val)
+        menu.append(rs)
+
+        for profile in range(3):
+            pfx = "type%d" % (profile + 1)
+            path = "aprs2.smartbeaconing_profile[%d]" % profile
+            prof = aprs2.smartbeaconing_profile[profile]
+
+            low_val = RadioSettingValueInteger(2, 30, prof.low_speed_mph)
+            high_val = RadioSettingValueInteger(3, 70, prof.high_speed_mph)
+            low_val.get_max = lambda: min(30, int(high_val.get_value()) - 1)
+
+            rs = RadioSetting("%s.low_speed_mph" % path,
+                              "%s Low Speed (mph)" % pfx, low_val)
+            menu.append(rs)
+
+            rs = RadioSetting("%s.high_speed_mph" % path,
+                              "%s High Speed (mph)" % pfx, high_val)
+            menu.append(rs)
+
+            val = RadioSettingValueInteger(1, 100, prof.slow_rate_min)
+            rs = RadioSetting("%s.slow_rate_min" % path,
+                              "%s Slow rate (minutes)" % pfx, val)
+            menu.append(rs)
+
+            val = RadioSettingValueInteger(10, 180, prof.fast_rate_sec)
+            rs = RadioSetting("%s.fast_rate_sec" % path,
+                              "%s Fast rate (seconds)" % pfx, val)
+            menu.append(rs)
+
+            val = RadioSettingValueInteger(5, 90, prof.turn_angle)
+            rs = RadioSetting("%s.turn_angle" % path,
+                              "%s Turn angle (degrees)" % pfx, val)
+            menu.append(rs)
+
+            val = RadioSettingValueInteger(1, 255, prof.turn_slop)
+            rs = RadioSetting("%s.turn_slop" % path,
+                              "%s Turn slop" % pfx, val)
+            menu.append(rs)
+
+            val = RadioSettingValueInteger(5, 180, prof.turn_time_sec)
+            rs = RadioSetting("%s.turn_time_sec" % path,
+                              "%s Turn time (seconds)" % pfx, val)
+            menu.append(rs)
+
+        return menu
+
+    def _get_settings(self):
+        top = RadioSettings(self._get_aprs_general_settings(),
+                            self._get_aprs_rx_settings(),
+                            self._get_aprs_tx_settings(),
+                            self._get_aprs_smartbeacon(),
+                            self._get_dtmf_settings(),
+                            self._get_misc_settings(),
+                            self._get_scan_settings())
+        return top
+
+
+ at directory.register
 class VX8GERadio(VX8DRadio):
     """Yaesu VX-8GE"""
+    MODEL = "VX-8GE"
     _model = "AH041"
-    VARIANT = "GE"
     _has_vibrate = True
     _has_af_dual = False
diff --git a/chirp/drivers/wouxun.py b/chirp/drivers/wouxun.py
index 58536de..fc482fe 100644
--- a/chirp/drivers/wouxun.py
+++ b/chirp/drivers/wouxun.py
@@ -810,30 +810,33 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
                 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
+        rx_mode = tx_mode = None
+        rx_tone = 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
+        if mem.tmode == "Tone":
+            tx_mode = "Tone"
+            rx_mode = None
+            tx_tone = int(mem.rtone * 10)
+        elif mem.tmode == "TSQL":
+            rx_mode = tx_mode = "Tone"
+            rx_tone = tx_tone = int(mem.ctone * 10)
+        elif mem.tmode == "DTCS":
+            tx_mode = rx_mode = "DTCS"
+            tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+            rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
+        elif mem.tmode == "Cross":
+            tx_mode, rx_mode = mem.cross_mode.split("->")
+            if tx_mode == "DTCS":
+                tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+            elif tx_mode == "Tone":
+                tx_tone = int(mem.rtone * 10)
+            if rx_mode == "DTCS":
+                rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
+            elif rx_mode == "Tone":
+                rx_tone = int(mem.ctone * 10)
+
+        _mem.rx_tone = rx_tone
+        _mem.tx_tone = tx_tone
 
         LOG.debug("Set TX %s (%i) RX %s (%i)" %
                   (tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))
diff --git a/chirp/platform.py b/chirp/platform.py
index 8250af1..3c5bc2e 100644
--- a/chirp/platform.py
+++ b/chirp/platform.py
@@ -243,6 +243,23 @@ class Platform:
             return os.path.dirname(os.path.abspath(os.path.join(_find_me(),
                                                                 "..")))
 
+    def find_resource(self, filename):
+        """Searches for files installed to a share/ prefix."""
+        execpath = self.executable_path()
+        share_candidates = [
+            os.path.join(execpath, "share"),
+            os.path.join(sys.prefix, "share"),
+            "/usr/local/share",
+            "/usr/share",
+        ]
+        pkgshare_candidates = [os.path.join(i, "chirp") for i in share_candidates]
+        search_paths = [execpath] + pkgshare_candidates + share_candidates
+        for path in search_paths:
+            candidate = os.path.join(path, filename)
+            if os.path.exists(candidate):
+                return candidate
+        return ""
+
 
 def _unix_editor():
     macos_textedit = "/Applications/TextEdit.app/Contents/MacOS/TextEdit"
diff --git a/chirp/ui/mainapp.py b/chirp/ui/mainapp.py
index a6835c9..1ba9d51 100644
--- a/chirp/ui/mainapp.py
+++ b/chirp/ui/mainapp.py
@@ -545,10 +545,7 @@ of file.
         count = eset.do_import(config)
 
     def copy_shipped_stock_configs(self, stock_dir):
-        execpath = platform.get_platform().executable_path()
-        basepath = os.path.abspath(os.path.join(execpath, "stock_configs"))
-        if not os.path.exists(basepath):
-            basepath = "/usr/share/chirp/stock_configs"
+        basepath = platform.get_platform().find_resource("stock_configs")
 
         files = glob(os.path.join(basepath, "*.csv"))
         for fn in files:
@@ -1907,11 +1904,9 @@ of file.
             a.connect_accelerator()
 
     def _set_icon(self):
-        execpath = platform.get_platform().executable_path()
-        path = os.path.abspath(os.path.join(execpath, "share", "chirp.png"))
-        if not os.path.exists(path):
-            path = "/usr/share/pixmaps/chirp.png"
-
+        this_platform = platform.get_platform()
+        path = (this_platform.find_resource("chirp.png") or
+                this_platform.find_resource(os.path.join("pixmaps", "chirp.png")))
         if os.path.exists(path):
             self.set_icon_from_file(path)
         else:
@@ -1945,13 +1940,33 @@ of file.
         d.destroy()
 
     def _init_macos(self, menu_bar):
+        macapp = None
+
+        # for KK7DS runtime <= R10
         try:
             import gtk_osxapplication
             macapp = gtk_osxapplication.OSXApplication()
-        except ImportError, e:
+        except ImportError:
+            pass
+
+        # for gtk-mac-integration >= 2.0.7
+        try:
+            import gtkosx_application
+            macapp = gtkosx_application.Application()
+        except ImportError:
+            pass
+
+        if macapp is None:
             LOG.error("No MacOS support: %s" % e)
             return
 
+        this_platform = platform.get_platform()
+        icon = (this_platform.find_resource("chirp.png") or
+                this_platform.find_resource(os.path.join("pixmaps", "chirp.png")))
+        if os.path.exists(icon):
+            icon_pixmap = gtk.gdk.pixbuf_new_from_file(icon)
+            macapp.set_dock_icon_pixbuf(icon_pixmap)
+
         menu_bar.hide()
         macapp.set_menu_bar(menu_bar)
 
diff --git a/chirp/ui/memedit.py b/chirp/ui/memedit.py
index 8a2b51e..3c98c4c 100644
--- a/chirp/ui/memedit.py
+++ b/chirp/ui/memedit.py
@@ -496,7 +496,7 @@ class MemoryEditor(common.Editor):
         to_remove = []
         for path in paths:
             iter = self.store.get_iter(path)
-            cur_pos, = self.store.get(iter, self.col("Loc"))
+            cur_pos, = self.store.get(iter, self.col(_("Loc")))
             to_remove.append(cur_pos)
             self.store.set(iter, self.col("_filled"), False)
             job = common.RadioJob(None, "erase_memory", cur_pos)
@@ -935,9 +935,13 @@ class MemoryEditor(common.Editor):
 
     def cell_editing_started(self, rend, event, path):
         self._in_editing = True
+        self._edit_path = self.view.get_cursor()
 
     def cell_editing_stopped(self, *args):
         self._in_editing = False
+        print 'Would activate %s' % str(self._edit_path)
+        self.view.grab_focus()
+        self.view.set_cursor(*self._edit_path)
 
     def make_editor(self):
         types = tuple([x[1] for x in self.cols])
@@ -968,7 +972,7 @@ class MemoryEditor(common.Editor):
             LOG.error(e)
             col_order = default_col_order
 
-        non_editable = ["Loc"]
+        non_editable = [_("Loc")]
 
         unsupported_cols = self.get_unsupported_columns()
         visible_cols = self.get_columns_visible()
diff --git a/chirp/ui/radiobrowser.py b/chirp/ui/radiobrowser.py
index 971a4d6..83cd968 100644
--- a/chirp/ui/radiobrowser.py
+++ b/chirp/ui/radiobrowser.py
@@ -202,7 +202,7 @@ class CharArrayEditor(BitwiseEditor):
 
     def _build_ui(self):
         ent = FixedEntry(len(self._element))
-        ent.set_text(str(self._element))
+        ent.set_text(str(self._element).rstrip("\x00"))
         ent.connect('changed', self._changed)
         ent.show()
         self.pack_start(ent, 1, 1, 1)
diff --git a/chirp/ui/settingsedit.py b/chirp/ui/settingsedit.py
index 289c796..34707e4 100644
--- a/chirp/ui/settingsedit.py
+++ b/chirp/ui/settingsedit.py
@@ -188,7 +188,8 @@ class SettingsEditor(common.Editor):
                     widget = gtk.Entry()
                     widget.set_width_chars(32)
                     widget.set_text(str(value).rstrip())
-                    widget.connect("changed", self._save_setting, value)
+                    widget.connect("focus-out-event", lambda w, e, v:
+                                   self._save_setting(w, v), value)
                 else:
                     LOG.error("Unsupported widget type: %s" % value.__class__)
 
diff --git a/chirpw b/chirpw
index 9199332..6ea9ba0 100755
--- a/chirpw
+++ b/chirpw
@@ -34,10 +34,7 @@ import logging
 LOG = logging.getLogger("chirpw")
 
 
-execpath = platform.get_platform().executable_path()
-localepath = os.path.abspath(os.path.join(execpath, "locale"))
-if not os.path.exists(localepath):
-    localepath = "/usr/share/chirp/locale"
+localepath = platform.get_platform().find_resource("locale")
 
 conf = config.get()
 manual_language = conf.get("language", "state")

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