[hamradio-commits] [chirp] 13/19: Imported Upstream version 1:20151023

Iain R. Learmonth irl at moszumanska.debian.org
Sun Nov 1 16:02:44 UTC 2015


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

irl pushed a commit to branch master
in repository chirp.

commit 28a98a1075d5de60e58c0ab318cadb9b86025636
Author: Iain R. Learmonth <irl at debian.org>
Date:   Sun Nov 1 15:16:09 2015 +0000

    Imported Upstream version 1:20151023
---
 PKG-INFO                                      |   2 +-
 chirp/__init__.py                             |   4 +-
 chirp/bandplan.py                             |   3 +-
 chirp/bandplan_au.py                          |   5 +-
 chirp/bandplan_iaru_r1.py                     |  19 +-
 chirp/bandplan_na.py                          |  25 +-
 chirp/bitwise.py                              | 161 +++--
 chirp/bitwise_grammar.py                      |  20 +
 chirp/chirp_common.py                         | 373 ++++++----
 chirp/detect.py                               |  29 +-
 chirp/directory.py                            |  40 +-
 chirp/drivers/__init__.py                     |  10 +
 chirp/{ => drivers}/alinco.py                 |  47 +-
 chirp/{ => drivers}/anytone.py                |  83 ++-
 chirp/drivers/anytone_ht.py                   | 946 ++++++++++++++++++++++++++
 chirp/drivers/ap510.py                        | 808 ++++++++++++++++++++++
 chirp/{ => drivers}/baofeng_uv3r.py           | 143 ++--
 chirp/drivers/bj9900.py                       | 407 +++++++++++
 chirp/{ => drivers}/bjuv55.py                 | 197 +++---
 chirp/{ => drivers}/ft1802.py                 |  35 +-
 chirp/{vx8.py => drivers/ft1d.py}             | 830 +++++++++++++++-------
 chirp/{ => drivers}/ft2800.py                 |  40 +-
 chirp/drivers/ft2900.py                       | 652 ++++++++++++++++++
 chirp/{ => drivers}/ft50.py                   |   8 +-
 chirp/{ => drivers}/ft50_ll.py                | 122 ++--
 chirp/drivers/ft60.py                         | 775 +++++++++++++++++++++
 chirp/{ => drivers}/ft7800.py                 | 313 +++++----
 chirp/drivers/ft8100.py                       | 314 +++++++++
 chirp/{ => drivers}/ft817.py                  | 456 +++++++------
 chirp/{ => drivers}/ft857.py                  | 605 +++++++++-------
 chirp/{ => drivers}/ft90.py                   | 619 +++++++++--------
 chirp/{ => drivers}/ftm350.py                 |  56 +-
 chirp/{ => drivers}/generic_csv.py            | 122 ++--
 chirp/{ => drivers}/generic_tpe.py            |  17 +-
 chirp/{ => drivers}/generic_xml.py            |  29 +-
 chirp/{ => drivers}/h777.py                   |  79 ++-
 chirp/{ => drivers}/ic208.py                  |  20 +-
 chirp/{ => drivers}/ic2100.py                 |  43 +-
 chirp/{ => drivers}/ic2200.py                 |  21 +-
 chirp/{ => drivers}/ic2720.py                 |  11 +-
 chirp/{ => drivers}/ic2820.py                 |  30 +-
 chirp/{ => drivers}/ic9x.py                   |  65 +-
 chirp/{ => drivers}/ic9x_icf.py               |   9 +-
 chirp/{ => drivers}/ic9x_icf_ll.py            |  33 +-
 chirp/{ => drivers}/ic9x_ll.py                |  77 ++-
 chirp/{ => drivers}/icf.py                    | 134 ++--
 chirp/{ => drivers}/icomciv.py                |  80 ++-
 chirp/{ => drivers}/icq7.py                   |  83 ++-
 chirp/{ => drivers}/ict70.py                  |  13 +-
 chirp/{ => drivers}/ict7h.py                  |   4 +-
 chirp/{ => drivers}/ict8.py                   |   4 +-
 chirp/{ => drivers}/icw32.py                  |  20 +-
 chirp/{ => drivers}/icx8x.py                  |  17 +-
 chirp/{ => drivers}/icx8x_ll.py               | 125 ++--
 chirp/{ => drivers}/id31.py                   |  27 +-
 chirp/{ => drivers}/id51.py                   |  29 +-
 chirp/drivers/id51plus.py                     | 171 +++++
 chirp/{ => drivers}/id800.py                  |  41 +-
 chirp/{ => drivers}/id880.py                  |  50 +-
 chirp/{ => drivers}/idrp.py                   |  33 +-
 chirp/{ => drivers}/kenwood_hmk.py            |  47 +-
 chirp/{ => drivers}/kenwood_itm.py            |  31 +-
 chirp/{ => drivers}/kenwood_live.py           | 643 +++++++++--------
 chirp/drivers/kguv8d.py                       | 930 +++++++++++++++++++++++++
 chirp/drivers/kyd.py                          | 506 ++++++++++++++
 chirp/drivers/kyd_IP620.py                    | 624 +++++++++++++++++
 chirp/drivers/leixen.py                       | 943 +++++++++++++++++++++++++
 chirp/{ => drivers}/puxing.py                 |  65 +-
 chirp/{ => drivers}/rfinder.py                |  77 ++-
 chirp/{ => drivers}/template.py               |  30 +-
 chirp/drivers/th9000.py                       | 838 +++++++++++++++++++++++
 chirp/drivers/th9800.py                       | 782 +++++++++++++++++++++
 chirp/{ => drivers}/th_uv3r.py                |  21 +-
 chirp/drivers/th_uv3r25.py                    | 209 ++++++
 chirp/{ => drivers}/th_uvf8d.py               | 328 ++++-----
 chirp/{ => drivers}/thd72.py                  | 104 +--
 chirp/{ => drivers}/thuv1f.py                 |  37 +-
 chirp/{ => drivers}/tk8102.py                 |  53 +-
 chirp/{ => drivers}/tmv71.py                  |  14 +-
 chirp/{ => drivers}/tmv71_ll.py               | 109 +--
 chirp/drivers/ts2000.py                       | 296 ++++++++
 chirp/{ => drivers}/uv5r.py                   | 725 +++++++++++++-------
 chirp/{ => drivers}/uvb5.py                   | 164 ++---
 chirp/drivers/vx170.py                        | 132 ++++
 chirp/{ => drivers}/vx2.py                    | 497 ++++++++------
 chirp/{ => drivers}/vx3.py                    | 497 ++++++++------
 chirp/{ => drivers}/vx5.py                    |  23 +-
 chirp/{ => drivers}/vx510.py                  |  10 +-
 chirp/{ => drivers}/vx6.py                    |  57 +-
 chirp/{ => drivers}/vx7.py                    |  59 +-
 chirp/{ => drivers}/vx8.py                    | 515 ++++++++++----
 chirp/{ => drivers}/vxa700.py                 |  48 +-
 chirp/{ => drivers}/wouxun.py                 | 882 +++++++++++++-----------
 chirp/{ => drivers}/wouxun_common.py          |  31 +-
 chirp/{ => drivers}/yaesu_clone.py            |  60 +-
 chirp/elib_intl.py                            | 575 ++++++++--------
 chirp/errors.py                               |   6 +
 chirp/ft60.py                                 | 321 ---------
 chirp/import_logic.py                         |  39 +-
 chirp/logger.py                               | 185 +++++
 chirp/memmap.py                               |  10 +-
 chirp/platform.py                             |  72 +-
 chirp/pyPEG.py                                | 127 ++--
 chirp/radioreference.py                       |  37 +-
 chirp/settings.py                             |  72 +-
 {chirpui => chirp/ui}/__init__.py             |   0
 {chirpui => chirp/ui}/bandplans.py            |  11 +-
 {chirpui => chirp/ui}/bankedit.py             |  48 +-
 {chirpui => chirp/ui}/clone.py                |  34 +-
 {chirpui => chirp/ui}/cloneprog.py            |   7 +-
 {chirpui => chirp/ui}/common.py               |  97 ++-
 {chirpui => chirp/ui}/config.py               |  24 +-
 {chirpui => chirp/ui}/dstaredit.py            |  33 +-
 {chirpui => chirp/ui}/editorset.py            |  68 +-
 {chirpui => chirp/ui}/fips.py                 | 373 +++++-----
 {chirpui => chirp/ui}/importdialog.py         | 123 ++--
 {chirpui => chirp/ui}/inputdialog.py          |  23 +-
 {chirpui => chirp/ui}/mainapp.py              | 444 +++++++-----
 {chirpui => chirp/ui}/memdetail.py            | 240 ++++---
 {chirpui => chirp/ui}/memedit.py              | 398 ++++++-----
 {chirpui => chirp/ui}/miscwidgets.py          | 115 ++--
 {chirpui => chirp/ui}/radiobrowser.py         |  40 +-
 {chirpui => chirp/ui}/reporting.py            |  54 +-
 chirp/ui/settingsedit.py                      | 224 ++++++
 {chirpui => chirp/ui}/shiftdialog.py          |  18 +-
 chirp/util.py                                 |  47 +-
 chirp/xml_ll.py                               |  46 +-
 chirpui/settingsedit.py                       | 236 -------
 chirpw                                        |  99 ++-
 locale/de/LC_MESSAGES/CHIRP.mo                | Bin 17225 -> 17226 bytes
 locale/fr/LC_MESSAGES/CHIRP.mo                | Bin 0 -> 15724 bytes
 locale/hu/LC_MESSAGES/CHIRP.mo                | Bin 15824 -> 20248 bytes
 locale/it/LC_MESSAGES/CHIRP.mo                | Bin 15186 -> 15243 bytes
 locale/pl/LC_MESSAGES/CHIRP.mo                | Bin 11137 -> 11138 bytes
 locale/pt_BR/LC_MESSAGES/CHIRP.mo             | Bin 15187 -> 15187 bytes
 locale/ru/LC_MESSAGES/CHIRP.mo                | Bin 16690 -> 16689 bytes
 setup.py                                      |  78 ++-
 stock_configs/FR Marine VHF Channels.csv      |  58 ++
 stock_configs/Marine VHF Channels.csv         |  61 --
 stock_configs/US 60 meter channels (Dial).csv |   2 +-
 stock_configs/US Calling Frequencies.csv      |   2 +-
 stock_configs/US Marine VHF Channels.csv      |  61 ++
 142 files changed, 18118 insertions(+), 6311 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index 42fa59a..c294215 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.0
 Name: chirp
-Version: 0.4.1
+Version: daily-20151023
 Summary: UNKNOWN
 Home-page: UNKNOWN
 Author: UNKNOWN
diff --git a/chirp/__init__.py b/chirp/__init__.py
index bf501cc..8763f2a 100644
--- a/chirp/__init__.py
+++ b/chirp/__init__.py
@@ -13,12 +13,12 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-CHIRP_VERSION="0.4.1"
-
 import os
 import sys
 from glob import glob
 
+CHIRP_VERSION="daily-20151023"
+
 module_dir = os.path.dirname(sys.modules["chirp"].__file__)
 __all__ = []
 for i in glob(os.path.join(module_dir, "*.py")):
diff --git a/chirp/bandplan.py b/chirp/bandplan.py
index f2b8541..97f2425 100644
--- a/chirp/bandplan.py
+++ b/chirp/bandplan.py
@@ -15,6 +15,7 @@
 
 from chirp import chirp_common
 
+
 class Band(object):
     def __init__(self, limits, name, mode=None, step_khz=None,
                  input_offset=None, output_offset=None, tones=None):
@@ -79,6 +80,6 @@ class Band(object):
             self.step_khz and 'step_khz: %s ' % (self.step_khz,) or '',
             self.offset and 'offset: %s ' % (self.offset,) or '',
             self.tones and 'tones: %s ' % (self.tones,) or '')
-        
+
         return "%s-%s %s %s %s" % (
             self.limits[0], self.limits[1], self.name, self.duplex, desc)
diff --git a/chirp/bandplan_au.py b/chirp/bandplan_au.py
index 136ef93..9f4ba50 100644
--- a/chirp/bandplan_au.py
+++ b/chirp/bandplan_au.py
@@ -20,9 +20,10 @@ from chirp import bandplan, bandplan_iaru_r3
 SHORTNAME = "australia"
 
 DESC = {
-  "name": "Australian Amateur Band Plan",
+  "name":    "Australian Amateur Band Plan",
   "updated": "April 2010",
-  "url": "http://www.wia.org.au/members/bandplans/data/documents/Australian%20Band%20Plans%20100404.pdf",
+  "url":     "http://www.wia.org.au/members/bandplans/data"
+             "/documents/Australian%20Band%20Plans%20100404.pdf",
 }
 
 BANDS_10M = (
diff --git a/chirp/bandplan_iaru_r1.py b/chirp/bandplan_iaru_r1.py
index f71be4a..e41b0ee 100644
--- a/chirp/bandplan_iaru_r1.py
+++ b/chirp/bandplan_iaru_r1.py
@@ -18,8 +18,9 @@ from chirp import bandplan
 SHORTNAME = "iaru_r1"
 
 DESC = {
-  "name": "IARU Region 1 (Europe, Africa, Middle East and Northern Asia)",
-  "url": "http://iaru-r1.org/index.php?option=com_content&view=article&id=175&Itemid=127",
+  "name":    "IARU Region 1 (Europe, Africa, Middle East and Northern Asia)",
+  "url":     "http://iaru-r1.org/index.php?option=com_content"
+             "&view=article&id=175&Itemid=127",
   "updated": "General Conference Sun City 2011",
 }
 
@@ -60,8 +61,8 @@ BANDS_40M = (
   bandplan.Band((7053000, 7060000), "All modes, digimodes"),
   bandplan.Band((7060000, 7100000), "All modes, SSB contest preferred",
                 mode="LSB"),
-  bandplan.Band((7100000, 7130000), "All modes, R1 Emergency Center Of Activity",
-                mode="LSB"),
+  bandplan.Band((7100000, 7130000),
+                "All modes, R1 Emergency Center Of Activity", mode="LSB"),
   bandplan.Band((7130000, 7200000), "All modes, SSB contest preferred",
                 mode="LSB"),
   bandplan.Band((7175000, 7200000), "All modes, SSB DX preferred", mode="LSB"),
@@ -82,8 +83,8 @@ BANDS_20M = (
   bandplan.Band((14101000, 14112000), "All narrow band modes, digimodes"),
   bandplan.Band((14125000, 14350000), "All modes, SSB contest preferred",
                 mode="USB"),
-  bandplan.Band((14300000, 14350000), "All modes, Global Emergency center of activity",
-                mode="USB"),
+  bandplan.Band((14300000, 14350000),
+                "All modes, Global Emergency center of activity", mode="USB"),
 )
 
 BANDS_17M = (
@@ -99,7 +100,8 @@ BANDS_15M = (
   bandplan.Band((21000000, 21450000), "15 Meter Band"),
   bandplan.Band((21000000, 21070000), "CW", mode="CW"),
   bandplan.Band((21070000, 21090000), "All narrow band modes, digimodes"),
-  bandplan.Band((21090000, 21110000), "All narrow band, digimodes, unattended"),
+  bandplan.Band((21090000, 21110000),
+                "All narrow band, digimodes, unattended"),
   bandplan.Band((21110000, 21120000), "All modes, digimodes, unattended"),
   bandplan.Band((21120000, 21149000), "All narrow band modes"),
   bandplan.Band((21149000, 21151000), "IBP, exclusively for beacons",
@@ -120,7 +122,8 @@ BANDS_10M = (
   bandplan.Band((28000000, 29700000), "10 Meter Band"),
   bandplan.Band((28000000, 28070000), "CW", mode="CW"),
   bandplan.Band((28070000, 28120000), "All narrow band modes, digimodes"),
-  bandplan.Band((28120000, 28150000), "All narrow band, digimodes, unattended"),
+  bandplan.Band((28120000, 28150000),
+                "All narrow band, digimodes, unattended"),
   bandplan.Band((28150000, 28190000), "All narrow band modes"),
   bandplan.Band((28190000, 28199000), "Beacons", mode="CW"),
   bandplan.Band((28199000, 28201000), "IBP, exclusively for beacons",
diff --git a/chirp/bandplan_na.py b/chirp/bandplan_na.py
index ba46c8c..50bbb27 100644
--- a/chirp/bandplan_na.py
+++ b/chirp/bandplan_na.py
@@ -20,7 +20,7 @@ SHORTNAME = "north_america"
 
 DESC = {
   "name": "North American Band Plan",
-  "url": "http://www.arrl.org/band-plan"
+  "url":  "http://www.arrl.org/band-plan"
 }
 
 BANDS_160M = (
@@ -36,25 +36,25 @@ BANDS_80M = (
   bandplan.Band((3570000, 3600000), "RTTY/Data", mode="RTTY"),
   bandplan.Band((3790000, 3800000), "DX window"),
 )
- 
+
 BANDS_40M = (
   bandplan.Band((7000000, 7300000), "40 Meter Band"),
   bandplan.Band((7080000, 7125000), "RTTY/Data", mode="RTTY"),
 )
- 
+
 BANDS_30M = (
   bandplan.Band((10100000, 10150000), "30 Meter Band"),
   bandplan.Band((10130000, 10140000), "RTTY", mode="RTTY"),
   bandplan.Band((10140000, 10150000), "Packet"),
 )
- 
+
 BANDS_20M = (
   bandplan.Band((14000000, 14350000), "20 Meter Band"),
   bandplan.Band((14070000, 14095000), "RTTY", mode="RTTY"),
   bandplan.Band((14095000, 14099500), "Packet"),
   bandplan.Band((14100500, 14112000), "Packet"),
 )
- 
+
 BANDS_17M = (
   bandplan.Band((18068000, 18168000), "17 Meter Band"),
   bandplan.Band((18100000, 18105000), "RTTY", mode="RTTY"),
@@ -71,7 +71,7 @@ BANDS_12M = (
   bandplan.Band((24920000, 24925000), "RTTY", mode="RTTY"),
   bandplan.Band((24925000, 24930000), "Packet"),
 )
- 
+
 BANDS_10M = (
   bandplan.Band((28000000, 29700000), "10 Meter Band"),
   bandplan.Band((28000000, 28070000), "CW", mode="CW"),
@@ -83,7 +83,7 @@ BANDS_10M = (
   bandplan.Band((29300000, 29510000), "Satellite Downlinks"),
   bandplan.Band((29520000, 29590000), "Repeater Inputs",
                 step_khz=10, mode="NFM"),
-  bandplan.Band((29610000, 29700000), "Repeater Outputs", 
+  bandplan.Band((29610000, 29700000), "Repeater Outputs",
                 step_khz=10, mode="NFM", input_offset=-890000),
 )
 
@@ -177,7 +177,7 @@ BANDS_70CM = (
   bandplan.Band((447000000, 450000000), "Repeater inputs and outputs "
                 "(local option)", input_offset=-5000000),
 )
- 
+
 BANDS_33CM = (
   bandplan.Band((902000000, 928000000), "33 Centimeter Band"),
   bandplan.Band((902075000, 902100000), "CW/SSB, Weak signal"),
@@ -206,8 +206,9 @@ BANDS_23CM = (
                 "with 1258.000-1260.000", mode="FM"),
   bandplan.Band((1248000000, 1252000000), "Digital"),
   bandplan.Band((1252000000, 1258000000), "ATV Channel #2"),
-  bandplan.Band((1258000000, 1260000000), "Point-to-point links paired "
-                 "with 1246.000-1248.000", mode="FM"),
+  bandplan.Band((1258000000, 1260000000),
+                "Point-to-point links paired with 1246.000-1248.000",
+                mode="FM"),
   bandplan.Band((1240000000, 1260000000), "Regional option, FM ATV"),
   bandplan.Band((1260000000, 1270000000), "Satellite uplinks, Experimental, "
                 "Simplex ATV"),
@@ -238,8 +239,8 @@ BANDS_13CM = (
                 "paired with 2393 - 2393.750"),
   bandplan.Band((2303750000, 2304000000), "SSB, CW, digital weak-signal"),
   bandplan.Band((2304000000, 2304100000), "Weak Signal EME Band, <3kHz"),
-  bandplan.Band((2304100000, 2304300000), "SSB, CW, digital weak-signal, <3kHz"
-               ),
+  bandplan.Band((2304100000, 2304300000),
+                "SSB, CW, digital weak-signal, <3kHz"),
   bandplan.Band((2304300000, 2304400000), "Beacons, <3kHz"),
   bandplan.Band((2304400000, 2304750000), "SSB, CW, digital weak-signal and "
                 "NBFM, <6kHz"),
diff --git a/chirp/bitwise.py b/chirp/bitwise.py
index 149c588..39efe8f 100644
--- a/chirp/bitwise.py
+++ b/chirp/bitwise.py
@@ -36,13 +36,13 @@
 #  lbcd foo;     /* BCD-encoded byte (LE)                   */
 #  bbcd foo;     /* BCD-encoded byte (BE)                   */
 #  char foo[8];  /* 8-char array                            */
-#  struct {                                                 
-#   u8 foo;                                                 
-#   u16 bar;                                                
+#  struct {
+#   u8 foo;
+#   u16 bar;
 #  } baz;        /* Structure with u8 and u16               */
 #
 # Example directives:
-# 
+#
 # #seekto 0x1AB; /* Set the data offset to 0x1AB            */
 # #seek 4;       /* Set the data offset += 4                */
 # #printoffset "foobar" /* Echo the live data offset,
@@ -51,7 +51,7 @@
 # Usage:
 #
 # Create a data definition in a string, and pass it and the data
-# to parse to the parse() function.  The result is a structure with 
+# to parse to the parse() function.  The result is a structure with
 # dict-like objects for structures, indexed by name, and lists of
 # objects for arrays.  The actual data elements can be interpreted
 # as integers directly (for int types).  Strings and BCD arrays
@@ -59,14 +59,19 @@
 
 import struct
 import os
+import logging
 
 from chirp import bitwise_grammar
 from chirp.memmap import MemoryMap
 
+LOG = logging.getLogger(__name__)
+
+
 class ParseError(Exception):
     """Indicates an error parsing a definition"""
     pass
 
+
 def format_binary(nbits, value, pad=8):
     s = ""
     for i in range(0, nbits):
@@ -74,10 +79,12 @@ def format_binary(nbits, value, pad=8):
         value >>= 1
     return "%s%s" % ((pad - len(s)) * ".", s)
 
+
 def bits_between(start, end):
-    bits = (1 << (end - start )) - 1
+    bits = (1 << (end - start)) - 1
     return bits << start
 
+
 def pp(structure, level=0):
     for i in structure:
         if isinstance(i, list):
@@ -91,6 +98,7 @@ def pp(structure, level=0):
         elif isinstance(i, str):
             print "%s%s" % (" " * level, i)
 
+
 def array_copy(dst, src):
     """Copy an array src into DataElement array dst"""
     if len(dst) != len(src):
@@ -99,28 +107,34 @@ def array_copy(dst, src):
     for i in range(0, len(dst)):
         dst[i].set_value(src[i])
 
+
 def bcd_to_int(bcd_array):
-    """Convert an array of bcdDataElement like \x12\x34 into an int like 1234"""
+    """Convert an array of bcdDataElement like \x12\x34
+    into an int like 1234"""
     value = 0
     for bcd in bcd_array:
         a, b = bcd.get_value()
         value = (value * 100) + (a * 10) + b
     return value
-        
+
+
 def int_to_bcd(bcd_array, value):
     """Convert an int like 1234 into bcdDataElements like "\x12\x34" """
     for i in reversed(range(0, len(bcd_array))):
         bcd_array[i].set_value(value % 100)
         value /= 100
 
+
 def get_string(char_array):
     """Convert an array of charDataElements into a string"""
     return "".join([x.get_value() for x in char_array])
 
+
 def set_string(char_array, string):
     """Set an array of charDataElements from a string"""
     array_copy(char_array, list(string))
 
+
 class DataElement:
     _size = 1
 
@@ -139,7 +153,8 @@ class DataElement:
         raise Exception("Not implemented")
 
     def get_value(self):
-        return self._get_value(self._data[self._offset:self._offset+self._size])
+        value = self._data[self._offset:self._offset + self._size]
+        return self._get_value(value)
 
     def set_value(self, value):
         raise Exception("Not implemented for %s" % self.__class__)
@@ -159,6 +174,7 @@ class DataElement:
                                          self._size,
                                          self._offset)
 
+
 class arrayDataElement(DataElement):
     def __repr__(self):
         if isinstance(self.__items[0], bcdDataElement):
@@ -252,7 +268,7 @@ class arrayDataElement(DataElement):
             if i.get_value() == value:
                 return index
             index += 1
-        raise IndexError()            
+        raise IndexError()
 
     def __iter__(self):
         return iter(self.__items)
@@ -269,11 +285,12 @@ class arrayDataElement(DataElement):
             size += i.size()
         return size
 
+
 class intDataElement(DataElement):
     def __repr__(self):
         fmt = "0x%%0%iX" % (self._size * 2)
         return fmt % int(self)
-    
+
     def __int__(self):
         return self.get_value()
 
@@ -304,6 +321,9 @@ class intDataElement(DataElement):
     def __or__(self, val):
         return self.get_value() | val
 
+    def __xor__(self, val):
+        return self.get_value() ^ val
+
     def __and__(self, val):
         return self.get_value() & val
 
@@ -325,6 +345,9 @@ class intDataElement(DataElement):
     def __ror__(self, val):
         return val | self.get_value()
 
+    def __rxor__(self, val):
+        return val ^ self.get_value()
+
     def __rmod__(self, val):
         return val % self.get_value()
 
@@ -362,6 +385,10 @@ class intDataElement(DataElement):
         self.set_value(self.get_value() | val)
         return self
 
+    def __ixor__(self, val):
+        self.set_value(self.get_value() ^ val)
+        return self
+
     def __index__(self):
         return abs(self)
 
@@ -386,6 +413,7 @@ class intDataElement(DataElement):
     def __nonzero__(self):
         return self.get_value() != 0
 
+
 class u8DataElement(intDataElement):
     _size = 1
 
@@ -395,6 +423,7 @@ class u8DataElement(intDataElement):
     def set_value(self, value):
         self._data[self._offset] = (int(value) & 0xFF)
 
+
 class u16DataElement(intDataElement):
     _size = 2
     _endianess = ">"
@@ -406,9 +435,11 @@ class u16DataElement(intDataElement):
         self._data[self._offset] = struct.pack(self._endianess + "H",
                                                int(value) & 0xFFFF)
 
+
 class ul16DataElement(u16DataElement):
     _endianess = "<"
-    
+
+
 class u24DataElement(intDataElement):
     _size = 3
     _endianess = ">"
@@ -425,12 +456,14 @@ class u24DataElement(intDataElement):
         else:
             start = 1
             end = 4
-        self._data[self._offset] = struct.pack(self._endianess + "I",
-                                               int(value) & 0xFFFFFFFF)[start:end]
+        packed = struct.pack(self._endianess + "I", int(value) & 0xFFFFFFFF)
+        self._data[self._offset] = packed[start:end]
+
 
 class ul24DataElement(u24DataElement):
     _endianess = "<"
 
+
 class u32DataElement(intDataElement):
     _size = 4
     _endianess = ">"
@@ -442,9 +475,11 @@ class u32DataElement(intDataElement):
         self._data[self._offset] = struct.pack(self._endianess + "I",
                                                int(value) & 0xFFFFFFFF)
 
+
 class ul32DataElement(u32DataElement):
     _endianess = "<"
 
+
 class i8DataElement(u8DataElement):
     _size = 1
 
@@ -452,8 +487,9 @@ class i8DataElement(u8DataElement):
         return struct.unpack("b", data)[0]
 
     def set_value(self, value):
-        self._data[self._offset] = struct.pack("b", int(value) )
-        
+        self._data[self._offset] = struct.pack("b", int(value))
+
+
 class i16DataElement(intDataElement):
     _size = 2
     _endianess = ">"
@@ -463,11 +499,13 @@ class i16DataElement(intDataElement):
 
     def set_value(self, value):
         self._data[self._offset] = struct.pack(self._endianess + "h",
-                                               int(value) )
+                                               int(value))
+
 
 class il16DataElement(i16DataElement):
     _endianess = "<"
 
+
 class i24DataElement(intDataElement):
     _size = 3
     _endianess = ">"
@@ -485,11 +523,13 @@ class i24DataElement(intDataElement):
             start = 1
             end = 4
         self._data[self._offset] = struct.pack(self._endianess + "i",
-                                               int(value) )[start:end]
+                                               int(value))[start:end]
+
 
 class il24DataElement(i24DataElement):
     _endianess = "<"
 
+
 class i32DataElement(intDataElement):
     _size = 4
     _endianess = ">"
@@ -499,11 +539,13 @@ class i32DataElement(intDataElement):
 
     def set_value(self, value):
         self._data[self._offset] = struct.pack(self._endianess + "i",
-                                               int(value) )
+                                               int(value))
+
 
 class il32DataElement(i32DataElement):
     _endianess = "<"
 
+
 class charDataElement(DataElement):
     _size = 1
 
@@ -519,6 +561,7 @@ class charDataElement(DataElement):
     def set_value(self, value):
         self._data[self._offset] = str(value)
 
+
 class bcdDataElement(DataElement):
     def __int__(self):
         tens, ones = self.get_value()
@@ -550,16 +593,19 @@ class bcdDataElement(DataElement):
         b = ord(data) & 0x0F
         return (a, b)
 
+
 class lbcdDataElement(bcdDataElement):
     _size = 1
 
+
 class bbcdDataElement(bcdDataElement):
     _size = 1
 
+
 class bitDataElement(intDataElement):
     _nbits = 0
     _shift = 0
-    _subgen = u8DataElement # Default to a byte
+    _subgen = u8DataElement  # Default to a byte
 
     def __repr__(self):
         fmt = "0x%%0%iX (%%sb)" % (self._size * 2)
@@ -568,31 +614,23 @@ class bitDataElement(intDataElement):
     def get_value(self):
         data = self._subgen(self._data, self._offset).get_value()
         mask = bits_between(self._shift-self._nbits, self._shift)
-        val = data & mask
-
-        #print "start: %i bits: %i" % (self._shift, self._nbits)
-        #print "data:  %04x" % data
-        #print "mask:  %04x" % mask
-        #print " val:  %04x" % val
-
-        val >>= (self._shift - self._nbits)
+        val = (data & mask) >> (self._shift - self._nbits)
         return val
 
     def set_value(self, value):
         mask = bits_between(self._shift-self._nbits, self._shift)
+
         data = self._subgen(self._data, self._offset).get_value()
         data &= ~mask
 
-        #print "data: %04x" % data
-        #print "mask: %04x" % mask
-        #print "valu: %04x" % value
-
         value = ((int(value) << (self._shift-self._nbits)) & mask) | data
+
         self._subgen(self._data, self._offset).set_value(value)
-        
+
     def size(self):
         return self._nbits
 
+
 class structDataElement(DataElement):
     def __repr__(self):
         s = "struct {" + os.linesep
@@ -650,7 +688,7 @@ class structDataElement(DataElement):
             raise AttributeError("No attribute %s in struct" % name)
 
     def __setattr__(self, name, value):
-        if not self.__dict__.has_key("_structDataElement__init"):
+        if "_structDataElement__init" not in self.__dict__:
             self.__dict__[name] = value
         else:
             self.__dict__["_generators"][name].set_value(value)
@@ -665,7 +703,6 @@ class structDataElement(DataElement):
             for el in gen:
                 i += 1
                 size += el.size()
-                #print "Size of %s[%i] = %i" % (name, i, el.size())
         return size
 
     def get_raw(self):
@@ -685,25 +722,26 @@ class structDataElement(DataElement):
         for key in self._keys:
             yield key, self._generators[key]
 
+
 class Processor:
 
     _types = {
-        "u8"   : u8DataElement,
-        "u16"  : u16DataElement,
-        "ul16" : ul16DataElement,
-        "u24"  : u24DataElement,
-        "ul24" : ul24DataElement,
-        "u32"  : u32DataElement,
-        "ul32" : ul32DataElement,
-        "i8"   : i8DataElement,
-        "i16"  : i16DataElement,
-        "il16" : il16DataElement,
-        "i24"  : i24DataElement,
-        "il24" : il24DataElement,
-        "i32"  : i32DataElement,
-        "char" : charDataElement,
-        "lbcd" : lbcdDataElement,
-        "bbcd" : bbcdDataElement,
+        "u8":    u8DataElement,
+        "u16":   u16DataElement,
+        "ul16":  ul16DataElement,
+        "u24":   u24DataElement,
+        "ul24":  ul24DataElement,
+        "u32":   u32DataElement,
+        "ul32":  ul32DataElement,
+        "i8":    i8DataElement,
+        "i16":   i16DataElement,
+        "il16":  il16DataElement,
+        "i24":   i24DataElement,
+        "il24":  il24DataElement,
+        "i32":   i32DataElement,
+        "char":  charDataElement,
+        "lbcd":  lbcdDataElement,
+        "bbcd":  bbcdDataElement,
         }
 
     def __init__(self, data, offset):
@@ -730,13 +768,13 @@ class Processor:
                 _nbits = bits
                 _shift = bitsleft
                 _subgen = self._types[dtype]
-            
+
             self._generators[name] = bitDE(self._data, self._offset)
             bitsleft -= bits
 
         if bitsleft:
-            print "WARNING: %i trailing bits unaccounted for in %s" % (bitsleft,
-                                                                       bitfield)
+            LOG.warn("WARNING: %i trailing bits unaccounted for in %s" %
+                     (bitsleft, bitfield))
 
         return bytes
 
@@ -818,7 +856,7 @@ class Processor:
     def parse_struct(self, struct):
         if struct[0][0] == "struct_defn":
             return self.parse_struct_defn(struct[0][1])
-        elif struct [0][0] == "struct_decl":
+        elif struct[0][0] == "struct_decl":
             return self.parse_struct_decl(struct[0][1])
         else:
             raise Exception("Internal error: What is `%s'?" % struct[0][0])
@@ -827,23 +865,21 @@ class Processor:
         name = directive[0][0]
         value = directive[0][1][0][1]
         if name == "seekto":
-            #print "NOTICE: Setting offset to %i (0x%X)" % (offset, offset)
             self._offset = int(value, 0)
         elif name == "seek":
             self._offset += int(value, 0)
         elif name == "printoffset":
-            print "%s: %i (0x%08X)" % (value[1:-1], self._offset, self._offset)
+            LOG.debug("%s: %i (0x%08X)" %
+                      (value[1:-1], self._offset, self._offset))
 
     def parse_block(self, lang):
         for t, d in lang:
-            #print t
             if t == "struct":
                 self.parse_struct(d)
             elif t == "definition":
                 self.parse_defn(d)
             elif t == "directive":
                 self.parse_directive(d)
-        
 
     def parse(self, lang):
         self._generators = structDataElement(self._data, self._offset)
@@ -884,7 +920,6 @@ struct {
     import sys
     sys.exit(0)
 
-
     test = """
     struct {
       u16 bar;
@@ -903,12 +938,12 @@ struct {
     data = "\xfe\x10\x00\x08\xFF\x23\x01\x02\x03abc\x34\x89"
     data = (data * 2) + "\x12"
     data = MemoryMap(data)
-    
+
     ast = bitwise_grammar.parse(test)
 
     # Just for testing, pretty-print the tree
     pp(ast)
-    
+
     # Mess with it a little
     p = Processor(data, 0)
     obj = p.parse(ast)
@@ -921,7 +956,7 @@ struct {
     obj["foo"][0]["onebit"].set_value(1)
     print "%i" % int(obj["foo"][0]["bar"])
 
-    for i in  obj["foo"][0]["array"]:
+    for i in obj["foo"][0]["array"]:
         print int(i)
     obj["foo"][0]["array"][1].set_value(255)
 
diff --git a/chirp/bitwise_grammar.py b/chirp/bitwise_grammar.py
index fe81b42..b6eb20c 100644
--- a/chirp/bitwise_grammar.py
+++ b/chirp/bitwise_grammar.py
@@ -21,63 +21,83 @@ TYPES = ["bit", "u8", "u16", "ul16", "u24", "ul24", "u32", "ul32",
          "lbcd", "bbcd"]
 DIRECTIVES = ["seekto", "seek", "printoffset"]
 
+
 def string():
     return re.compile(r"\"[^\"]*\"")
 
+
 def symbol():
     return re.compile(r"\w+")
 
+
 def count():
     return re.compile(r"([1-9][0-9]*|0x[0-9a-fA-F]+)")
 
+
 def bitdef():
     return symbol, ":", count, -1
 
+
 def _bitdeflist():
     return bitdef, -1, (",", bitdef)
 
+
 def bitfield():
     return -2, _bitdeflist
 
+
 def array():
     return symbol, '[', count, ']'
 
+
 def _typedef():
     return re.compile(r"(%s)" % "|".join(TYPES))
 
+
 def definition():
     return _typedef, [array, bitfield, symbol], ";"
 
+
 def seekto():
     return keyword("seekto"), count
 
+
 def seek():
     return keyword("seek"), count
 
+
 def printoffset():
     return keyword("printoffset"), string
 
+
 def directive():
     return "#", [seekto, seek, printoffset], ";"
 
+
 def _block_inner():
     return -2, [definition, struct, directive]
 
+
 def _block():
     return "{", _block_inner, "}"
 
+
 def struct_defn():
     return symbol, _block
 
+
 def struct_decl():
     return [symbol, _block], [array, symbol]
 
+
 def struct():
     return keyword("struct"), [struct_defn, struct_decl], ";"
 
+
 def _language():
     return _block_inner
 
+
 def parse(data):
     lines = data.split("\n")
     for index, line in enumerate(lines):
diff --git a/chirp/chirp_common.py b/chirp/chirp_common.py
index 600f4eb..038ec01 100644
--- a/chirp/chirp_common.py
+++ b/chirp/chirp_common.py
@@ -13,24 +13,21 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-SEPCHAR = ","
-    
-#print "Using separation character of '%s'" % SEPCHAR
-
 import math
-
 from chirp import errors, memmap
 
+SEPCHAR = ","
+
 # 50 Tones
-TONES = [ 67.0, 69.3, 71.9, 74.4, 77.0, 79.7, 82.5,
-          85.4, 88.5, 91.5, 94.8, 97.4, 100.0, 103.5,
-          107.2, 110.9, 114.8, 118.8, 123.0, 127.3,
-          131.8, 136.5, 141.3, 146.2, 151.4, 156.7,
-          159.8, 162.2, 165.5, 167.9, 171.3, 173.8,
-          177.3, 179.9, 183.5, 186.2, 189.9, 192.8,
-          196.6, 199.5, 203.5, 206.5, 210.7, 218.1,
-          225.7, 229.1, 233.6, 241.8, 250.3, 254.1,
-          ]          
+TONES = [67.0, 69.3, 71.9, 74.4, 77.0, 79.7, 82.5,
+         85.4, 88.5, 91.5, 94.8, 97.4, 100.0, 103.5,
+         107.2, 110.9, 114.8, 118.8, 123.0, 127.3,
+         131.8, 136.5, 141.3, 146.2, 151.4, 156.7,
+         159.8, 162.2, 165.5, 167.9, 171.3, 173.8,
+         177.3, 179.9, 183.5, 186.2, 189.9, 192.8,
+         196.6, 199.5, 203.5, 206.5, 210.7, 218.1,
+         225.7, 229.1, 233.6, 241.8, 250.3, 254.1,
+         ]
 
 TONES_EXTRA = [62.5]
 
@@ -40,8 +37,8 @@ OLD_TONES = list(TONES)
 
 # 104 DTCS Codes
 DTCS_CODES = [
-     23,  25,  26,  31,  32,  36,  43,  47,  51,  53,  54,
-     65,  71,  72,  73,  74, 114, 115, 116, 122, 125, 131,
+    23,  25,  26,  31,  32,  36,  43,  47,  51,  53,  54,
+    65,  71,  72,  73,  74,  114, 115, 116, 122, 125, 131,
     132, 134, 143, 145, 152, 155, 156, 162, 165, 172, 174,
     205, 212, 223, 225, 226, 243, 244, 245, 246, 251, 252,
     255, 261, 263, 265, 266, 271, 274, 306, 311, 315, 325,
@@ -50,7 +47,7 @@ DTCS_CODES = [
     465, 466, 503, 506, 516, 523, 526, 532, 546, 565, 606,
     612, 624, 627, 631, 632, 654, 662, 664, 703, 712, 723,
     731, 732, 734, 743, 754,
-     ]
+    ]
 
 # 512 Possible DTCS Codes
 ALL_DTCS_CODES = []
@@ -67,6 +64,7 @@ CROSS_MODES = [
     "DTCS->Tone",
     "->Tone",
     "DTCS->DTCS",
+    "Tone->"
 ]
 
 MODES = ["WFM", "FM", "NFM", "AM", "NAM", "DV", "USB", "LSB", "CW", "RTTY",
@@ -89,7 +87,7 @@ TUNING_STEPS = [
     9.0, 1.0, 2.5,
 ]
 
-SKIP_VALUES = [ "", "S", "P" ]
+SKIP_VALUES = ["", "S", "P"]
 
 CHARSET_UPPER_NUMERIC = "ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890"
 CHARSET_ALPHANUMERIC = \
@@ -142,14 +140,17 @@ APRS_SYMBOLS = (
     "TDB", "[reserved]"
 )
 
+
 def watts_to_dBm(watts):
     """Converts @watts in watts to dBm"""
     return int(10 * math.log10(int(watts * 1000)))
 
+
 def dBm_to_watts(dBm):
     """Converts @dBm from dBm to watts"""
     return int(math.pow(10, (dBm - 30) / 10))
 
+
 class PowerLevel:
     """Represents a power level supported by a radio"""
     def __init__(self, label, watts=0, dBm=0):
@@ -187,6 +188,7 @@ class PowerLevel:
     def __repr__(self):
         return "%s (%i dBm)" % (self._label, self._power)
 
+
 def parse_freq(freqstr):
     """Parse a frequency string and return the value in integral Hz"""
     freqstr = freqstr.strip()
@@ -212,14 +214,17 @@ def parse_freq(freqstr):
 
     return mhz + khz
 
+
 def format_freq(freq):
     """Format a frequency given in Hz as a string"""
 
     return "%i.%06i" % (freq / 1000000, freq % 1000000)
 
+
 class ImmutableValueError(ValueError):
     pass
 
+
 class Memory:
     """Base class for a single radio memory"""
     freq = 0
@@ -247,49 +252,49 @@ class Memory:
 
     immutable = []
 
-    # A RadioSettingsGroup of additional settings supported by the radio,
+    # A RadioSettingGroup of additional settings supported by the radio,
     # or an empty list if none
     extra = []
 
     def __init__(self):
         self.freq = 0
-        self.number = 0                   
-        self.extd_number = ""             
-        self.name = ""                    
-        self.vfo = 0                      
-        self.rtone = 88.5                 
-        self.ctone = 88.5                 
-        self.dtcs = 23                    
-        self.rx_dtcs = 23                    
-        self.tmode = ""                   
-        self.cross_mode = "Tone->Tone"      
-        self.dtcs_polarity = "NN"         
-        self.skip = ""                    
-        self.power = None                 
-        self.duplex = ""                  
+        self.number = 0
+        self.extd_number = ""
+        self.name = ""
+        self.vfo = 0
+        self.rtone = 88.5
+        self.ctone = 88.5
+        self.dtcs = 23
+        self.rx_dtcs = 23
+        self.tmode = ""
+        self.cross_mode = "Tone->Tone"
+        self.dtcs_polarity = "NN"
+        self.skip = ""
+        self.power = None
+        self.duplex = ""
         self.offset = 600000
-        self.mode = "FM"                  
-        self.tuning_step = 5.0            
-                                          
+        self.mode = "FM"
+        self.tuning_step = 5.0
+
         self.comment = ""
 
-        self.empty = False                
+        self.empty = False
 
         self.immutable = []
 
     _valid_map = {
-        "rtone"         : TONES + TONES_EXTRA,
-        "ctone"         : TONES + TONES_EXTRA,
-        "dtcs"          : ALL_DTCS_CODES,
-        "rx_dtcs"       : ALL_DTCS_CODES,
-        "tmode"         : TONE_MODES,
-        "dtcs_polarity" : ["NN", "NR", "RN", "RR"],
-        "cross_mode"    : CROSS_MODES,
-        "mode"          : MODES,
-        "duplex"        : ["", "+", "-", "split", "off"],
-        "skip"          : SKIP_VALUES,
-        "empty"         : [True, False],
-        "dv_code"       : [x for x in range(0, 100)],
+        "rtone":          TONES + TONES_EXTRA,
+        "ctone":          TONES + TONES_EXTRA,
+        "dtcs":           ALL_DTCS_CODES,
+        "rx_dtcs":        ALL_DTCS_CODES,
+        "tmode":          TONE_MODES,
+        "dtcs_polarity":  ["NN", "NR", "RN", "RR"],
+        "cross_mode":     CROSS_MODES,
+        "mode":           MODES,
+        "duplex":         ["", "+", "-", "split", "off"],
+        "skip":           SKIP_VALUES,
+        "empty":          [True, False],
+        "dv_code":        [x for x in range(0, 100)],
         }
 
     def __repr__(self):
@@ -323,10 +328,9 @@ class Memory:
             raise ImmutableValueError("Field %s is not " % name +
                                       "mutable on this memory")
 
-        if self._valid_map.has_key(name) and val not in self._valid_map[name]:
-            raise ValueError("`%s' is not in valid list: %s" % (\
-                    val,
-                    self._valid_map[name]))
+        if name in self._valid_map and val not in self._valid_map[name]:
+            raise ValueError("`%s' is not in valid list: %s" %
+                             (val, self._valid_map[name]))
 
         self.__dict__[name] = val
 
@@ -360,8 +364,9 @@ class Memory:
         else:
             dup = self.duplex
 
-        return "Memory %i: %s%s%s %s (%s) r%.1f%s c%.1f%s d%03i%s%s [%.2f]"% \
-            (self.number,
+        return \
+            "Memory %s: %s%s%s %s (%s) r%.1f%s c%.1f%s d%03i%s%s [%.2f]" % \
+            (self.number if self.extd_number == "" else self.extd_number,
              format_freq(self.freq),
              dup,
              format_freq(self.offset),
@@ -379,20 +384,20 @@ class Memory:
     def to_csv(self):
         """Return a CSV representation of this memory"""
         return [
-            "%i"   % self.number,
-            "%s"   % self.name,
+            "%i" % self.number,
+            "%s" % self.name,
             format_freq(self.freq),
-            "%s"   % self.duplex,
+            "%s" % self.duplex,
             format_freq(self.offset),
-            "%s"   % self.tmode,
+            "%s" % self.tmode,
             "%.1f" % self.rtone,
             "%.1f" % self.ctone,
             "%03i" % self.dtcs,
-            "%s"   % self.dtcs_polarity,
-            "%s"   % self.mode,
+            "%s" % self.dtcs_polarity,
+            "%s" % self.mode,
             "%.2f" % self.tuning_step,
-            "%s"   % self.skip,
-            "%s"   % self.comment,
+            "%s" % self.skip,
+            "%s" % self.comment,
             "", "", "", ""]
 
     @classmethod
@@ -419,8 +424,8 @@ class Memory:
         try:
             self.number = int(vals[0])
         except:
-            print "Loc: %s" % vals[0]
-            raise errors.InvalidDataError("Location is not a valid integer")
+            raise errors.InvalidDataError(
+                "Location '%s' is not a valid integer" % vals[0])
 
         self.name = vals[1]
 
@@ -438,10 +443,11 @@ class Memory:
             self.offset = float(vals[4])
         except:
             raise errors.InvalidDataError("Offset is not a valid number")
-        
+
         self.tmode = vals[5]
         if self.tmode not in TONE_MODES:
-            raise errors.InvalidDataError("Invalid tone mode `%s'" % self.tmode)
+            raise errors.InvalidDataError("Invalid tone mode `%s'" %
+                                          self.tmode)
 
         try:
             self.rtone = float(vals[6])
@@ -479,7 +485,7 @@ class Memory:
         if vals[10] in MODES:
             self.mode = vals[10]
         else:
-            raise errors.InvalidDataError("Mode is not valid")           
+            raise errors.InvalidDataError("Mode is not valid")
 
         try:
             self.tuning_step = float(vals[11])
@@ -493,6 +499,7 @@ class Memory:
 
         return True
 
+
 class DVMemory(Memory):
     """A Memory with D-STAR attributes"""
     dv_urcall = "CQCQCQ"
@@ -511,24 +518,24 @@ class DVMemory(Memory):
 
     def to_csv(self):
         return [
-            "%i"   % self.number,
-            "%s"   % self.name,
+            "%i" % self.number,
+            "%s" % self.name,
             format_freq(self.freq),
-            "%s"   % self.duplex,
+            "%s" % self.duplex,
             format_freq(self.offset),
-            "%s"   % self.tmode,
+            "%s" % self.tmode,
             "%.1f" % self.rtone,
             "%.1f" % self.ctone,
             "%03i" % self.dtcs,
-            "%s"   % self.dtcs_polarity,
-            "%s"   % self.mode,
+            "%s" % self.dtcs_polarity,
+            "%s" % self.mode,
             "%.2f" % self.tuning_step,
-            "%s"   % self.skip,
+            "%s" % self.skip,
             "%s" % self.comment,
-            "%s"   % self.dv_urcall,
-            "%s"   % self.dv_rpt1call,
-            "%s"   % self.dv_rpt2call,
-            "%i"   % self.dv_code]
+            "%s" % self.dv_urcall,
+            "%s" % self.dv_rpt1call,
+            "%s" % self.dv_rpt2call,
+            "%i" % self.dv_code]
 
     def really_from_csv(self, vals):
         Memory.really_from_csv(self, vals)
@@ -541,6 +548,7 @@ class DVMemory(Memory):
         except Exception:
             self.dv_code = 0
 
+
 class MemoryMapping(object):
     """Base class for a memory mapping"""
     def __init__(self, model, index, name):
@@ -565,6 +573,7 @@ class MemoryMapping(object):
     def __eq__(self, other):
         return self.get_index() == other.get_index()
 
+
 class MappingModel(object):
     """Base class for a memory mapping model"""
 
@@ -601,20 +610,24 @@ class MappingModel(object):
         """Return a list of mappings that @memory is in"""
         raise NotImplementedError()
 
+
 class Bank(MemoryMapping):
     """Base class for a radio's Bank"""
 
+
 class NamedBank(Bank):
     """A bank that can have a name"""
     def set_name(self, name):
         """Changes the user-adjustable bank name"""
         self._name = name
 
+
 class BankModel(MappingModel):
     """A bank model where one memory is in zero or one banks at any point"""
     def __init__(self, radio, name='Banks'):
         super(BankModel, self).__init__(radio, name)
 
+
 class MappingModelIndexInterface:
     """Interface for mappings with index capabilities"""
     def get_index_bounds(self):
@@ -634,15 +647,23 @@ class MappingModelIndexInterface:
         Exception if full"""
         raise NotImplementedError()
 
+
 class MTOBankModel(BankModel):
     """A bank model where one memory can be in multiple banks at once """
     pass
 
+
 def console_status(status):
     """Write a status object to the console"""
+    import logging
+    from chirp import logger
+    if not logger.is_visible(logging.WARN):
+        return
     import sys
-
-    sys.stderr.write("\r%s" % status)
+    import os
+    sys.stdout.write("\r%s" % status)
+    if status.cur == status.max:
+        sys.stdout.write(os.linesep)
 
 
 class RadioPrompts:
@@ -655,76 +676,77 @@ class RadioPrompts:
 
 BOOLEAN = [True, False]
 
+
 class RadioFeatures:
     """Radio Feature Flags"""
     _valid_map = {
         # General
-        "has_bank_index"      : BOOLEAN,
-        "has_dtcs"            : BOOLEAN,
-        "has_rx_dtcs"         : BOOLEAN,
-        "has_dtcs_polarity"   : BOOLEAN,
-        "has_mode"            : BOOLEAN,
-        "has_offset"          : BOOLEAN,
-        "has_name"            : BOOLEAN,
-        "has_bank"            : BOOLEAN,
-        "has_bank_names"      : BOOLEAN,
-        "has_tuning_step"     : BOOLEAN,
-        "has_ctone"           : BOOLEAN,
-        "has_cross"           : BOOLEAN,
-        "has_infinite_number" : BOOLEAN,
-        "has_nostep_tuning"   : BOOLEAN,
-        "has_comment"         : BOOLEAN,
-        "has_settings"        : BOOLEAN,
+        "has_bank_index":       BOOLEAN,
+        "has_dtcs":             BOOLEAN,
+        "has_rx_dtcs":          BOOLEAN,
+        "has_dtcs_polarity":    BOOLEAN,
+        "has_mode":             BOOLEAN,
+        "has_offset":           BOOLEAN,
+        "has_name":             BOOLEAN,
+        "has_bank":             BOOLEAN,
+        "has_bank_names":       BOOLEAN,
+        "has_tuning_step":      BOOLEAN,
+        "has_ctone":            BOOLEAN,
+        "has_cross":            BOOLEAN,
+        "has_infinite_number":  BOOLEAN,
+        "has_nostep_tuning":    BOOLEAN,
+        "has_comment":          BOOLEAN,
+        "has_settings":         BOOLEAN,
 
         # Attributes
-        "valid_modes"         : [],
-        "valid_tmodes"        : [],
-        "valid_duplexes"      : [],
-        "valid_tuning_steps"  : [],
-        "valid_bands"         : [],
-        "valid_skips"         : [],
-        "valid_power_levels"  : [],
-        "valid_characters"    : "",
-        "valid_name_length"   : 0,
-        "valid_cross_modes"   : [],
-        "valid_dtcs_pols"     : [],
-        "valid_dtcs_codes"    : [],
-        "valid_special_chans" : [],
-
-        "has_sub_devices"     : BOOLEAN,
-        "memory_bounds"       : (0, 0),
-        "can_odd_split"       : BOOLEAN,
+        "valid_modes":          [],
+        "valid_tmodes":         [],
+        "valid_duplexes":       [],
+        "valid_tuning_steps":   [],
+        "valid_bands":          [],
+        "valid_skips":          [],
+        "valid_power_levels":   [],
+        "valid_characters":     "",
+        "valid_name_length":    0,
+        "valid_cross_modes":    [],
+        "valid_dtcs_pols":      [],
+        "valid_dtcs_codes":     [],
+        "valid_special_chans":  [],
+
+        "has_sub_devices":      BOOLEAN,
+        "memory_bounds":        (0, 0),
+        "can_odd_split":        BOOLEAN,
 
         # D-STAR
-        "requires_call_lists" : BOOLEAN,
-        "has_implicit_calls"  : BOOLEAN,
+        "requires_call_lists":  BOOLEAN,
+        "has_implicit_calls":   BOOLEAN,
         }
 
     def __setattr__(self, name, val):
         if name.startswith("_"):
             self.__dict__[name] = val
             return
-        elif not name in self._valid_map.keys():
+        elif name not in self._valid_map.keys():
             raise ValueError("No such attribute `%s'" % name)
 
         if type(self._valid_map[name]) == tuple:
             # Tuple, cardinality must match
             if type(val) != tuple or len(val) != len(self._valid_map[name]):
-                raise ValueError("Invalid value `%s' for attribute `%s'" % \
-                                     (val, name))
+                raise ValueError("Invalid value `%s' for attribute `%s'" %
+                                 (val, name))
         elif type(self._valid_map[name]) == list and not self._valid_map[name]:
             # Empty list, must be another list
             if type(val) != list:
-                raise ValueError("Invalid value `%s' for attribute `%s'" % \
-                                     (val, name))
+                raise ValueError("Invalid value `%s' for attribute `%s'" %
+                                 (val, name))
         elif type(self._valid_map[name]) == str:
             if type(val) != str:
-                raise ValueError("Invalid value `%s' for attribute `%s'" % \
-                                     (val, name))
+                raise ValueError("Invalid value `%s' for attribute `%s'" %
+                                 (val, name))
         elif type(self._valid_map[name]) == int:
             if type(val) != int:
-                raise ValueError("Invalid value `%s' for attribute `%s'" % \
-                                     (val, name))
+                raise ValueError("Invalid value `%s' for attribute `%s'" %
+                                 (val, name))
         elif val not in self._valid_map[name]:
             # Value not in the list of valid values
             raise ValueError("Invalid value `%s' for attribute `%s'" % (val,
@@ -752,7 +774,8 @@ class RadioFeatures:
         self.init("has_dtcs", True,
                   "Indicates that DTCS tone mode is available")
         self.init("has_rx_dtcs", False,
-                  "Indicates that radio can use two different DTCS codes for rx and tx")
+                  "Indicates that radio can use two different " +
+                  "DTCS codes for rx and tx")
         self.init("has_dtcs_polarity", True,
                   "Indicates that the DTCS polarity can be changed")
         self.init("has_mode", True,
@@ -850,8 +873,8 @@ class RadioFeatures:
             msgs.append(msg)
 
         if (self.valid_modes and
-            mem.mode not in self.valid_modes and
-            mem.mode != "Auto"):
+                mem.mode not in self.valid_modes and
+                mem.mode != "Auto"):
             msg = ValidationError("Mode %s not supported" % mem.mode)
             msgs.append(msg)
 
@@ -862,14 +885,14 @@ class RadioFeatures:
             if mem.tmode == "Cross":
                 if self.valid_cross_modes and \
                         mem.cross_mode not in self.valid_cross_modes:
-                    msg = ValidationError("Cross tone mode %s not supported" % \
-                                              mem.cross_mode)
+                    msg = ValidationError("Cross tone mode %s not supported" %
+                                          mem.cross_mode)
                     msgs.append(msg)
 
         if self.has_dtcs_polarity and \
                 mem.dtcs_polarity not in self.valid_dtcs_pols:
-            msg = ValidationError("DTCS Polarity %s not supported" % \
-                                      mem.dtcs_polarity)
+            msg = ValidationError("DTCS Polarity %s not supported" %
+                                  mem.dtcs_polarity)
             msgs.append(msg)
 
         if self.valid_dtcs_codes and \
@@ -878,7 +901,7 @@ class RadioFeatures:
         if self.valid_dtcs_codes and \
                 mem.rx_dtcs not in self.valid_dtcs_codes:
             msg = ValidationError("DTCS Code %03i not supported" % mem.rx_dtcs)
-            
+
         if self.valid_duplexes and mem.duplex not in self.valid_duplexes:
             msg = ValidationError("Duplex %s not supported" % mem.duplex)
             msgs.append(msg)
@@ -901,6 +924,26 @@ class RadioFeatures:
                      "of supported range").format(freq=format_freq(mem.freq)))
                 msgs.append(msg)
 
+        if self.valid_bands and \
+                self.valid_duplexes and \
+                mem.duplex in ["split", "-", "+"]:
+            if mem.duplex == "split":
+                freq = mem.offset
+            elif mem.duplex == "-":
+                freq = mem.freq - mem.offset
+            elif mem.duplex == "+":
+                freq = mem.freq + mem.offset
+            valid = False
+            for lo, hi in self.valid_bands:
+                if lo <= freq < hi:
+                    valid = True
+                    break
+            if not valid:
+                msg = ValidationError(
+                        ("Tx freq {freq} is out "
+                         "of supported range").format(freq=format_freq(freq)))
+                msgs.append(msg)
+
         if mem.power and \
                 self.valid_power_levels and \
                 mem.power not in self.valid_power_levels:
@@ -911,8 +954,8 @@ class RadioFeatures:
             try:
                 step = required_step(mem.freq)
                 if step not in self.valid_tuning_steps:
-                    msg = ValidationError("Frequency requires %.2fkHz step" %\
-                                              required_step(mem.freq))
+                    msg = ValidationError("Frequency requires %.2fkHz step" %
+                                          required_step(mem.freq))
                     msgs.append(msg)
             except errors.InvalidDataError, e:
                 msgs.append(str(e))
@@ -927,18 +970,22 @@ class RadioFeatures:
 
         return msgs
 
+
 class ValidationMessage(str):
     """Base class for Validation Errors and Warnings"""
     pass
 
+
 class ValidationWarning(ValidationMessage):
     """A non-fatal warning during memory validation"""
     pass
 
+
 class ValidationError(ValidationMessage):
     """A fatal error during memory validation"""
     pass
 
+
 class Radio(object):
     """Base class for all Radio drivers"""
     BAUD_RATE = 9600
@@ -1011,7 +1058,7 @@ class Radio(object):
         if rf.valid_characters == rf.valid_characters.upper():
             # Radio only supports uppercase, so help out here
             name = name.upper()
-        return "".join([x for x in name[:rf.valid_name_length] 
+        return "".join([x for x in name[:rf.valid_name_length]
                         if x in rf.valid_characters])
 
     def get_sub_devices(self):
@@ -1026,7 +1073,7 @@ class Radio(object):
         return rf.validate_memory(mem)
 
     def get_settings(self):
-        """Returns a RadioSettingGroup containing one or more
+        """Returns a RadioSettings list containing one or more
         RadioSettingGroup or RadioSetting objects. These represent general
         setting knobs and dials that can be adjusted on the radio. If this
         function is implemented, the has_settings RadioFeatures flag should
@@ -1041,6 +1088,7 @@ class Radio(object):
         should be True and get_settings() must be implemented as well."""
         pass
 
+
 class FileBackedRadio(Radio):
     """A file-backed radio stores its data in a file"""
     FILE_EXTENSION = "img"
@@ -1048,7 +1096,7 @@ class FileBackedRadio(Radio):
     def __init__(self, *args, **kwargs):
         Radio.__init__(self, *args, **kwargs)
         self._memobj = None
-        
+
     def save(self, filename):
         """Save the radio's memory map to @filename"""
         self.save_mmap(filename)
@@ -1085,7 +1133,6 @@ class FileBackedRadio(Radio):
         return self._mmap
 
 
-
 class CloneModeRadio(FileBackedRadio):
     """A clone-mode radio does a full memory dump in and out and we store
     an image of the radio into an image file"""
@@ -1112,7 +1159,7 @@ class CloneModeRadio(FileBackedRadio):
 
     @classmethod
     def match_model(cls, filedata, filename):
-        """Given contents of a stored file (@filedata), return True if 
+        """Given contents of a stored file (@filedata), return True if
         this radio driver handles the represented model"""
 
         # Unless the radio driver does something smarter, claim
@@ -1130,22 +1177,25 @@ class CloneModeRadio(FileBackedRadio):
         "Initiate a PC-to-radio clone operation"
         pass
 
+
 class LiveRadio(Radio):
     """Base class for all Live-Mode radios"""
     pass
 
+
 class NetworkSourceRadio(Radio):
     """Base class for all radios based on a network source"""
     def do_fetch(self):
         """Fetch the source data from the network"""
         pass
 
+
 class IcomDstarSupport:
     """Base interface for radios supporting Icom's D-STAR technology"""
     MYCALL_LIMIT = (1, 1)
     URCALL_LIMIT = (1, 1)
     RPTCALL_LIMIT = (1, 1)
-    
+
     def get_urcall_list(self):
         """Return a list of URCALL callsigns"""
         return []
@@ -1170,6 +1220,7 @@ class IcomDstarSupport:
         """Set the MYCALL callsign list"""
         pass
 
+
 class ExperimentalRadio:
     """Interface for experimental radios"""
     @classmethod
@@ -1177,6 +1228,7 @@ class ExperimentalRadio:
         return ("This radio's driver is marked as experimental and may " +
                 "be unstable or unsafe to use.")
 
+
 class Status:
     """Clone status object for conveying clone progress to the UI"""
     name = "Job"
@@ -1195,26 +1247,32 @@ class Status:
 
         return "|%-10s| %2.1f%% %s" % (ticks, pct, self.msg)
 
+
 def is_fractional_step(freq):
     """Returns True if @freq requires a 12.5kHz or 6.25kHz step"""
     return not is_5_0(freq) and (is_12_5(freq) or is_6_25(freq))
 
+
 def is_5_0(freq):
     """Returns True if @freq is reachable by a 5kHz step"""
     return (freq % 5000) == 0
 
+
 def is_12_5(freq):
     """Returns True if @freq is reachable by a 12.5kHz step"""
     return (freq % 12500) == 0
 
+
 def is_6_25(freq):
     """Returns True if @freq is reachable by a 6.25kHz step"""
     return (freq % 6250) == 0
 
+
 def is_2_5(freq):
     """Returns True if @freq is reachable by a 2.5kHz step"""
     return (freq % 2500) == 0
 
+
 def required_step(freq):
     """Returns the simplest tuning step that is required to reach @freq"""
     if is_5_0(freq):
@@ -1227,9 +1285,9 @@ def required_step(freq):
         return 2.5
     else:
         raise errors.InvalidDataError("Unable to calculate the required " +
-                                      "tuning step for %i.%5i" % \
-                                          (freq / 1000000,
-                                           freq % 1000000))
+                                      "tuning step for %i.%5i" %
+                                      (freq / 1000000, freq % 1000000))
+
 
 def fix_rounded_step(freq):
     """Some radios imply the last bit of 12.5kHz and 6.25kHz step
@@ -1258,8 +1316,9 @@ def fix_rounded_step(freq):
     except errors.InvalidDataError:
         pass
 
-    raise errors.InvalidDataError("Unable to correct rounded frequency " + \
-                                      format_freq(freq))
+    raise errors.InvalidDataError("Unable to correct rounded frequency " +
+                                  format_freq(freq))
+
 
 def _name(name, len, just_upper):
     """Justify @name to @len, optionally converting to all uppercase"""
@@ -1267,42 +1326,52 @@ def _name(name, len, just_upper):
         name = name.upper()
     return name.ljust(len)[:len]
 
+
 def name6(name, just_upper=True):
     """6-char name"""
     return _name(name, 6, just_upper)
 
+
 def name8(name, just_upper=False):
     """8-char name"""
     return _name(name, 8, just_upper)
 
+
 def name16(name, just_upper=False):
     """16-char name"""
     return _name(name, 16, just_upper)
 
+
 def to_GHz(val):
     """Convert @val in GHz to Hz"""
     return val * 1000000000
 
+
 def to_MHz(val):
     """Convert @val in MHz to Hz"""
     return val * 1000000
 
+
 def to_kHz(val):
     """Convert @val in kHz to Hz"""
     return val * 1000
 
+
 def from_GHz(val):
     """Convert @val in Hz to GHz"""
     return val / 100000000
 
+
 def from_MHz(val):
     """Convert @val in Hz to MHz"""
     return val / 100000
 
+
 def from_kHz(val):
     """Convert @val in Hz to kHz"""
     return val / 100
 
+
 def split_tone_decode(mem, txtone, rxtone):
     """
     Set tone mode and values on @mem based on txtone and rxtone specs like:
@@ -1348,6 +1417,7 @@ def split_tone_decode(mem, txtone, rxtone):
     elif rxmode == "DTCS":
         mem.rx_dtcs = rxval
 
+
 def split_tone_encode(mem):
     """
     Returns TX, RX tone specs based on @mem like:
@@ -1360,7 +1430,7 @@ def split_tone_encode(mem):
     rxmode = ''
     txval = None
     rxval = None
-    
+
     if mem.tmode == "Tone":
         txmode = "Tone"
         txval = mem.rtone
@@ -1392,3 +1462,12 @@ def split_tone_encode(mem):
 
     return ((txmode, txval, txpol),
             (rxmode, rxval, rxpol))
+
+
+def sanitize_string(astring, validcharset=CHARSET_ASCII, replacechar='*'):
+    myfilter = ''.join(
+        [
+            [replacechar, chr(x)][chr(x) in validcharset]
+            for x in xrange(256)
+        ])
+    return astring.translate(myfilter)
diff --git a/chirp/detect.py b/chirp/detect.py
index eeefb32..7f3ee87 100644
--- a/chirp/detect.py
+++ b/chirp/detect.py
@@ -14,9 +14,13 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import serial
+import logging
+
+from chirp import errors, directory
+from chirp.drivers import ic9x_ll, icf, kenwood_live, icomciv
+
+LOG = logging.getLogger(__name__)
 
-from chirp import errors, icf, directory, ic9x_ll
-from chirp import kenwood_live, icomciv
 
 def _icom_model_data_to_rclass(md):
     for _rtype, rclass in directory.DRV_TO_RADIO.items():
@@ -27,11 +31,9 @@ def _icom_model_data_to_rclass(md):
         if rclass.get_model()[:4] == md[:4]:
             return rclass
 
-    raise errors.RadioError("Unknown radio type %02x%02x%02x%02x" %\
-                                (ord(md[0]),
-                                 ord(md[1]),
-                                 ord(md[2]),
-                                 ord(md[3])))
+    raise errors.RadioError("Unknown radio type %02x%02x%02x%02x" %
+                            (ord(md[0]), ord(md[1]), ord(md[2]), ord(md[3])))
+
 
 def _detect_icom_radio(ser):
     # ICOM VHF/UHF Clone-type radios @ 9600 baud
@@ -41,7 +43,7 @@ def _detect_icom_radio(ser):
         md = icf.get_model_data(ser)
         return _icom_model_data_to_rclass(md)
     except errors.RadioError, e:
-        print e
+        LOG.error(e)
 
     # ICOM IC-91/92 Live-mode radios @ 4800/38400 baud
 
@@ -65,6 +67,7 @@ def _detect_icom_radio(ser):
 
     raise errors.RadioError("Unable to get radio model")
 
+
 def detect_icom_radio(port):
     """Detect which Icom model is connected to @port"""
     ser = serial.Serial(port=port, timeout=0.5)
@@ -77,12 +80,12 @@ def detect_icom_radio(port):
 
     ser.close()
 
-    print "Auto-detected %s %s on %s" % (result.VENDOR,
-                                         result.MODEL,
-                                         port)
+    LOG.info("Auto-detected %s %s on %s" %
+             (result.VENDOR, result.MODEL, port))
 
     return result
 
+
 def detect_kenwoodlive_radio(port):
     """Detect which Kenwood model is connected to @port"""
     ser = serial.Serial(port=port, baudrate=9600, timeout=0.5)
@@ -100,6 +103,6 @@ def detect_kenwoodlive_radio(port):
         raise errors.RadioError("Unsupported model `%s'" % r_id)
 
 DETECT_FUNCTIONS = {
-    "Icom" : detect_icom_radio,
-    "Kenwood" : detect_kenwoodlive_radio,
+    "Icom":    detect_icom_radio,
+    "Kenwood": detect_kenwoodlive_radio,
 }
diff --git a/chirp/directory.py b/chirp/directory.py
index 5fc47e3..b1e39c7 100644
--- a/chirp/directory.py
+++ b/chirp/directory.py
@@ -16,9 +16,13 @@
 
 import os
 import tempfile
+import logging
+
+from chirp.drivers import icf, rfinder
+from chirp import chirp_common, util, radioreference, errors
+
+LOG = logging.getLogger(__name__)
 
-from chirp import icf
-from chirp import chirp_common, util, rfinder, radioreference, errors
 
 def radio_class_id(cls):
     """Return a unique identification string for @cls"""
@@ -31,50 +35,58 @@ def radio_class_id(cls):
     ident = ident.replace(")", "")
     return ident
 
+
 ALLOW_DUPS = False
+
+
 def enable_reregistrations():
     """Set the global flag ALLOW_DUPS=True, which will enable a driver
     to re-register for a slot in the directory without triggering an
     exception"""
     global ALLOW_DUPS
     if not ALLOW_DUPS:
-        print "NOTE: driver re-registration enabled"
+        LOG.info("driver re-registration enabled")
     ALLOW_DUPS = True
 
+
 def register(cls):
     """Register radio @cls with the directory"""
     global DRV_TO_RADIO
     ident = radio_class_id(cls)
     if ident in DRV_TO_RADIO.keys():
         if ALLOW_DUPS:
-            print "Replacing existing driver id `%s'" % ident
+            LOG.warn("Replacing existing driver id `%s'" % ident)
         else:
             raise Exception("Duplicate radio driver id `%s'" % ident)
     DRV_TO_RADIO[ident] = cls
     RADIO_TO_DRV[cls] = ident
-    print "Registered %s = %s" % (ident, cls.__name__)
+    LOG.info("Registered %s = %s" % (ident, cls.__name__))
 
     return cls
 
+
 DRV_TO_RADIO = {}
 RADIO_TO_DRV = {}
 
+
 def get_radio(driver):
     """Get radio driver class by identification string"""
-    if DRV_TO_RADIO.has_key(driver):
+    if driver in DRV_TO_RADIO:
         return DRV_TO_RADIO[driver]
     else:
         raise Exception("Unknown radio type `%s'" % driver)
 
+
 def get_driver(rclass):
     """Get the identification string for a given class"""
-    if RADIO_TO_DRV.has_key(rclass):
+    if rclass in RADIO_TO_DRV:
         return RADIO_TO_DRV[rclass]
-    elif RADIO_TO_DRV.has_key(rclass.__bases__[0]):
+    elif rclass.__bases__[0] in RADIO_TO_DRV:
         return RADIO_TO_DRV[rclass.__bases__[0]]
     else:
         raise Exception("Unknown radio type `%s'" % rclass)
 
+
 def icf_to_image(icf_file, img_file):
     # FIXME: Why is this here?
     """Convert an ICF file to a .img file"""
@@ -87,17 +99,17 @@ def icf_to_image(icf_file, img_file):
                 img_data = mmap.get_packed()[:model._memsize]
                 break
         except Exception:
-            pass # Skip non-Icoms
+            pass  # Skip non-Icoms
 
     if img_data:
         f = file(img_file, "wb")
         f.write(img_data)
         f.close()
     else:
-        print "Unsupported model data:"
-        print util.hexprint(mdata)
+        LOG.error("Unsupported model data: %s" % util.hexprint(mdata))
         raise Exception("Unsupported model")
 
+
 def get_radio_by_image(image_file):
     """Attempt to get the radio class that owns @image_file"""
     if image_file.startswith("radioreference://"):
@@ -105,17 +117,17 @@ def get_radio_by_image(image_file):
         rr = radioreference.RadioReferenceRadio(None)
         rr.set_params(zipcode, username, password)
         return rr
-    
+
     if image_file.startswith("rfinder://"):
         _, _, email, passwd, lat, lon, miles = image_file.split("/")
         rf = rfinder.RFinderRadio(None)
         rf.set_params((float(lat), float(lon)), int(miles), email, passwd)
         return rf
-    
+
     if os.path.exists(image_file) and icf.is_icf_file(image_file):
         tempf = tempfile.mktemp()
         icf_to_image(image_file, tempf)
-        print "Auto-converted %s -> %s" % (image_file, tempf)
+        LOG.info("Auto-converted %s -> %s" % (image_file, tempf))
         image_file = tempf
 
     if os.path.exists(image_file):
diff --git a/chirp/drivers/__init__.py b/chirp/drivers/__init__.py
new file mode 100644
index 0000000..ea9dd2c
--- /dev/null
+++ b/chirp/drivers/__init__.py
@@ -0,0 +1,10 @@
+import os
+import sys
+from glob import glob
+
+module_dir = os.path.dirname(sys.modules["chirp.drivers"].__file__)
+__all__ = []
+for i in sorted(glob(os.path.join(module_dir, "*.py"))):
+    name = os.path.basename(i)[:-3]
+    if not name.startswith("__"):
+        __all__.append(name)
diff --git a/chirp/alinco.py b/chirp/drivers/alinco.py
similarity index 95%
rename from chirp/alinco.py
rename to chirp/drivers/alinco.py
index a92e6ab..5662aaa 100644
--- a/chirp/alinco.py
+++ b/chirp/drivers/alinco.py
@@ -15,9 +15,13 @@
 
 from chirp import chirp_common, bitwise, memmap, errors, directory, util
 from chirp.settings import RadioSettingGroup, RadioSetting
-from chirp.settings import RadioSettingValueBoolean
+from chirp.settings import RadioSettingValueBoolean, RadioSettings
 
 import time
+import logging
+
+LOG = logging.getLogger(__name__)
+
 
 DRX35_MEM_FORMAT = """
 #seekto 0x0120;
@@ -67,6 +71,7 @@ RLENGTH = 2 + 5 + 32 + 2
 
 STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0]
 
+
 def isascii(data):
     for byte in data:
         if (ord(byte) < ord(" ") or ord(byte) > ord("~")) and \
@@ -74,6 +79,7 @@ def isascii(data):
             return False
     return True
 
+
 def tohex(data):
     if isascii(data):
         return repr(data)
@@ -82,19 +88,20 @@ def tohex(data):
         string += "%02X" % ord(byte)
     return string
 
+
 class AlincoStyleRadio(chirp_common.CloneModeRadio):
     """Base class for all known Alinco radios"""
     _memsize = 0
     _model = "NONE"
 
     def _send(self, data):
-        print "PC->R: (%2i) %s" % (len(data), tohex(data))
+        LOG.debug("PC->R: (%2i) %s" % (len(data), tohex(data)))
         self.pipe.write(data)
         self.pipe.read(len(data))
 
     def _read(self, length):
         data = self.pipe.read(length)
-        print "R->PC: (%2i) %s" % (len(data), tohex(data))
+        LOG.debug("R->PC: (%2i) %s" % (len(data), tohex(data)))
         return data
 
     def _download_chunk(self, addr):
@@ -115,10 +122,10 @@ class AlincoStyleRadio(chirp_common.CloneModeRadio):
             data += chr(int(_data[i:i+2], 16))
 
         if len(data) != 16:
-            print "Response was:"
-            print "|%s|"
-            print "Which I converted to:"
-            print util.hexprint(data)
+            LOG.debug("Response was:")
+            LOG.debug("|%s|")
+            LOG.debug("Which I converted to:")
+            LOG.debug(util.hexprint(data))
             raise Exception("Radio returned less than 16 bytes")
 
         return data
@@ -204,12 +211,13 @@ class AlincoStyleRadio(chirp_common.CloneModeRadio):
     def get_raw_memory(self, number):
         return repr(self._memobj.memory[number])
 
+
 DUPLEX = ["", "-", "+"]
 TMODES = ["", "Tone", "", "TSQL"] + [""] * 12
 TMODES[12] = "DTCS"
 DCS_CODES = {
-    "Alinco" : chirp_common.DTCS_CODES,
-    "Jetstream" : [17] + chirp_common.DTCS_CODES,
+    "Alinco": chirp_common.DTCS_CODES,
+    "Jetstream": [17] + chirp_common.DTCS_CODES,
 }
 
 CHARSET = (["\x00"] * 0x30) + \
@@ -217,6 +225,7 @@ CHARSET = (["\x00"] * 0x30) + \
     [chr(x + ord("A")) for x in range(0, 26)] + [" "] + \
     list("\x00" * 128)
 
+
 def _get_name(_mem):
     name = ""
     for i in _mem.name:
@@ -225,6 +234,7 @@ def _get_name(_mem):
         name += CHARSET[i]
     return name
 
+
 def _set_name(mem, _mem):
     name = [0x00] * 7
     j = 0
@@ -251,6 +261,7 @@ ALINCO_TONES.remove(206.5)
 ALINCO_TONES.remove(229.1)
 ALINCO_TONES.remove(254.1)
 
+
 class DRx35Radio(AlincoStyleRadio):
     """Base class for the DR-x35 radios"""
     _range = [(118000000, 155000000)]
@@ -385,6 +396,7 @@ class DRx35Radio(AlincoStyleRadio):
 
         self._set_extra(_mem, mem)
 
+
 @directory.register
 class DR03Radio(DRx35Radio):
     """Alinco DR03"""
@@ -400,6 +412,7 @@ class DR03Radio(DRx35Radio):
         return len(filedata) == cls._memsize and \
             filedata[0x64] == chr(0x00) and filedata[0x65] == chr(0x28)
 
+
 @directory.register
 class DR06Radio(DRx35Radio):
     """Alinco DR06"""
@@ -414,7 +427,8 @@ class DR06Radio(DRx35Radio):
     def match_model(cls, filedata, filename):
         return len(filedata) == cls._memsize and \
             filedata[0x64] == chr(0x00) and filedata[0x65] == chr(0x50)
-            
+
+
 @directory.register
 class DR135Radio(DRx35Radio):
     """Alinco DR135"""
@@ -430,6 +444,7 @@ class DR135Radio(DRx35Radio):
         return len(filedata) == cls._memsize and \
             filedata[0x64] == chr(0x01) and filedata[0x65] == chr(0x44)
 
+
 @directory.register
 class DR235Radio(DRx35Radio):
     """Alinco DR235"""
@@ -445,6 +460,7 @@ class DR235Radio(DRx35Radio):
         return len(filedata) == cls._memsize and \
             filedata[0x64] == chr(0x02) and filedata[0x65] == chr(0x22)
 
+
 @directory.register
 class DR435Radio(DRx35Radio):
     """Alinco DR435"""
@@ -460,6 +476,7 @@ class DR435Radio(DRx35Radio):
         return len(filedata) == cls._memsize and \
             filedata[0x64] == chr(0x04) and filedata[0x65] == chr(0x00)
 
+
 @directory.register
 class DJ596Radio(DRx35Radio):
     """Alinco DJ596"""
@@ -477,6 +494,7 @@ class DJ596Radio(DRx35Radio):
         return len(filedata) == cls._memsize and \
             filedata[0x64] == chr(0x45) and filedata[0x65] == chr(0x01)
 
+
 @directory.register
 class JT220MRadio(DRx35Radio):
     """Jetstream JT220"""
@@ -492,6 +510,7 @@ class JT220MRadio(DRx35Radio):
         return len(filedata) == cls._memsize and \
             filedata[0x60:0x64] == "2009"
 
+
 @directory.register
 class DJ175Radio(DRx35Radio):
     """Alinco DJ175"""
@@ -540,10 +559,10 @@ class DJ175Radio(DRx35Radio):
             data += chr(int(_data[i:i+2], 16))
 
         if len(data) != 16:
-            print "Response was:"
-            print "|%s|"
-            print "Which I converted to:"
-            print util.hexprint(data)
+            LOG.debug("Response was:")
+            LOG.debug("|%s|")
+            LOG.debug("Which I converted to:")
+            LOG.debug(util.hexprint(data))
             raise Exception("Radio returned less than 16 bytes")
 
         return data
diff --git a/chirp/anytone.py b/chirp/drivers/anytone.py
similarity index 90%
rename from chirp/anytone.py
rename to chirp/drivers/anytone.py
index b9058ec..b80099c 100644
--- a/chirp/anytone.py
+++ b/chirp/drivers/anytone.py
@@ -16,6 +16,7 @@
 import os
 import struct
 import time
+import logging
 
 from chirp import bitwise
 from chirp import chirp_common
@@ -23,9 +24,12 @@ from chirp import directory
 from chirp import errors
 from chirp import memmap
 from chirp import util
-from chirp.settings import RadioSettingGroup, RadioSetting, \
+from chirp.settings import RadioSettingGroup, RadioSetting, RadioSettings, \
     RadioSettingValueList, RadioSettingValueString, RadioSettingValueBoolean
 
+
+LOG = logging.getLogger(__name__)
+
 _mem_format = """
 #seekto 0x0100;
 struct {
@@ -71,12 +75,12 @@ struct memory {
 
 #seekto 0x0030;
 struct {
-	char serial[16];
+  char serial[16];
 } serial_no;
 
 #seekto 0x0050;
 struct {
-	char date[16];
+  char date[16];
 } version;
 
 #seekto 0x0280;
@@ -107,6 +111,7 @@ struct memory memory[758];
 struct memory memblk2[10];
 """
 
+
 class FlagObj(object):
     def __init__(self, flagobj, which):
         self._flagobj = flagobj
@@ -152,74 +157,79 @@ class FlagObj(object):
     def __repr__(self):
         return repr(self._flagobj)
 
+
 def _is_loc_used(memobj, loc):
     return memobj.flags[loc / 2].get_raw() != "\xFF"
 
+
 def _addr_to_loc(addr):
     return (addr - 0x2000) / 32
 
+
 def _should_send_addr(memobj, addr):
     if addr < 0x2000 or addr >= 0x7EC0:
         return True
     else:
         return _is_loc_used(memobj, _addr_to_loc(addr))
 
-def _debug(string):
-    if "CHIRP_DEBUG" in os.environ or True:
-        print string
 
 def _echo_write(radio, data):
     try:
         radio.pipe.write(data)
         radio.pipe.read(len(data))
     except Exception, e:
-        print "Error writing to radio: %s" % e
+        LOG.error("Error writing to radio: %s" % e)
         raise errors.RadioError("Unable to write to radio")
 
+
 def _read(radio, length):
     try:
         data = radio.pipe.read(length)
     except Exception, e:
-        print "Error reading from radio: %s" % e
+        LOG.error("Error reading from radio: %s" % e)
         raise errors.RadioError("Unable to read from radio")
 
     if len(data) != length:
-        print "Short read from radio (%i, expected %i)" % (len(data),
-                                                           length)
-        print util.hexprint(data)
+        LOG.error("Short read from radio (%i, expected %i)" %
+                  (len(data), length))
+        LOG.debug(util.hexprint(data))
         raise errors.RadioError("Short read from radio")
     return data
 
 valid_model = ['QX588UV', 'HR-2040', 'DB-50M\x00', 'DB-750X']
 
+
 def _ident(radio):
     radio.pipe.setTimeout(1)
     _echo_write(radio, "PROGRAM")
     response = radio.pipe.read(3)
     if response != "QX\x06":
-        print "Response was:\n%s" % util.hexprint(response)
+        LOG.debug("Response was:\n%s" % util.hexprint(response))
         raise errors.RadioError("Unsupported model")
     _echo_write(radio, "\x02")
     response = radio.pipe.read(16)
-    _debug(util.hexprint(response))
+    LOG.debug(util.hexprint(response))
     if response[1:8] not in valid_model:
-        print "Response was:\n%s" % util.hexprint(response)
+        LOG.debug("Response was:\n%s" % util.hexprint(response))
         raise errors.RadioError("Unsupported model")
 
+
 def _finish(radio):
     endframe = "\x45\x4E\x44"
     _echo_write(radio, endframe)
     result = radio.pipe.read(1)
     if result != "\x06":
-        print "Got:\n%s" % util.hexprint(result)
+        LOG.debug("Got:\n%s" % util.hexprint(result))
         raise errors.RadioError("Radio did not finish cleanly")
 
+
 def _checksum(data):
     cs = 0
     for byte in data:
         cs += ord(byte)
     return cs % 256
 
+
 def _send(radio, cmd, addr, length, data=None):
     frame = struct.pack(">cHb", cmd, addr, length)
     if data:
@@ -227,34 +237,36 @@ def _send(radio, cmd, addr, length, data=None):
         frame += chr(_checksum(frame[1:]))
         frame += "\x06"
     _echo_write(radio, frame)
-    _debug("Sent:\n%s" % util.hexprint(frame))
+    LOG.debug("Sent:\n%s" % util.hexprint(frame))
     if data:
         result = radio.pipe.read(1)
         if result != "\x06":
-            print "Ack was: %s" % repr(result)
-            raise errors.RadioError("Radio did not accept block at %04x" % addr)
+            LOG.debug("Ack was: %s" % repr(result))
+            raise errors.RadioError(
+                "Radio did not accept block at %04x" % addr)
         return
     result = _read(radio, length + 6)
-    _debug("Got:\n%s" % util.hexprint(result))
+    LOG.debug("Got:\n%s" % util.hexprint(result))
     header = result[0:4]
     data = result[4:-2]
     ack = result[-1]
     if ack != "\x06":
-        print "Ack was: %s" % repr(ack)
+        LOG.debug("Ack was: %s" % repr(ack))
         raise errors.RadioError("Radio NAK'd block at %04x" % addr)
     _cmd, _addr, _length = struct.unpack(">cHb", header)
     if _addr != addr or _length != _length:
-        print "Expected/Received:"
-        print " Length: %02x/%02x" % (length, _length)
-        print " Addr: %04x/%04x" % (addr, _addr)
+        LOG.debug("Expected/Received:")
+        LOG.debug(" Length: %02x/%02x" % (length, _length))
+        LOG.debug(" Addr: %04x/%04x" % (addr, _addr))
         raise errors.RadioError("Radio send unexpected block")
     cs = _checksum(result[1:-2])
     if cs != ord(result[-2]):
-        print "Calculated: %02x" % cs
-        print "Actual:     %02x" % ord(result[-2])
+        LOG.debug("Calculated: %02x" % cs)
+        LOG.debug("Actual:     %02x" % ord(result[-2]))
         raise errors.RadioError("Block at 0x%04x failed checksum" % addr)
     return data
 
+
 def _download(radio):
     _ident(radio)
 
@@ -282,6 +294,7 @@ def _download(radio):
 
     return memmap.MemoryMap(data)
 
+
 def _upload(radio):
     _ident(radio)
 
@@ -302,6 +315,7 @@ def _upload(radio):
 
     _finish(radio)
 
+
 TONES = [62.5] + list(chirp_common.TONES)
 TMODES = ['', 'Tone', 'DTCS', '']
 DUPLEXES = ['', '-', '+', '']
@@ -475,19 +489,20 @@ class AnyTone5888UVRadio(chirp_common.CloneModeRadio,
 
     def get_settings(self):
         _settings = self._memobj.settings
-        settings = RadioSettingGroup('all', 'All Settings')
+        basic = RadioSettingGroup("basic", "Basic")
+        settings = RadioSettings(basic)
 
         display = ["Frequency", "Channel", "Name"]
         rs = RadioSetting("display", "Display",
                           RadioSettingValueList(display,
                                                 display[_settings.display]))
-        settings.append(rs)
+        basic.append(rs)
 
         apo = ["Off"] + ['%.1f hour(s)' % (0.5 * x) for x in range(1, 25)]
         rs = RadioSetting("apo", "Automatic Power Off",
                           RadioSettingValueList(apo,
                                                 apo[_settings.apo]))
-        settings.append(rs)
+        basic.append(rs)
 
         def filter(s):
             s_ = ""
@@ -499,23 +514,26 @@ class AnyTone5888UVRadio(chirp_common.CloneModeRadio,
         rs = RadioSetting("welcome", "Welcome Message",
                           RadioSettingValueString(0, 8,
                                                   filter(_settings.welcome)))
-        settings.append(rs)
+        basic.append(rs)
 
         rs = RadioSetting("beep", "Beep Enabled",
                           RadioSettingValueBoolean(_settings.beep))
-        settings.append(rs)
+        basic.append(rs)
 
         mute = ["Off", "TX", "RX", "TX/RX"]
         rs = RadioSetting("mute", "Sub Band Mute",
                           RadioSettingValueList(mute,
                                                 mute[_settings.mute]))
-        settings.append(rs)
+        basic.append(rs)
 
         return settings
 
     def set_settings(self, settings):
         _settings = self._memobj.settings
         for element in settings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
             name = element.get_name()
             setattr(_settings, name, element.value)
 
@@ -523,6 +541,7 @@ class AnyTone5888UVRadio(chirp_common.CloneModeRadio,
     def match_model(cls, filedata, filename):
         return cls._file_ident in filedata[0x20:0x40]
 
+
 @directory.register
 class IntekHR2040Radio(AnyTone5888UVRadio):
     """Intek HR-2040"""
@@ -530,6 +549,7 @@ class IntekHR2040Radio(AnyTone5888UVRadio):
     MODEL = "HR-2040"
     _file_ident = "HR-2040"
 
+
 @directory.register
 class PolmarDB50MRadio(AnyTone5888UVRadio):
     """Polmar DB-50M"""
@@ -537,6 +557,7 @@ class PolmarDB50MRadio(AnyTone5888UVRadio):
     MODEL = "DB-50M"
     _file_ident = "DB-50M"
 
+
 @directory.register
 class PowerwerxDB750XRadio(AnyTone5888UVRadio):
     """Powerwerx DB-750X"""
diff --git a/chirp/drivers/anytone_ht.py b/chirp/drivers/anytone_ht.py
new file mode 100644
index 0000000..5029620
--- /dev/null
+++ b/chirp/drivers/anytone_ht.py
@@ -0,0 +1,946 @@
+# Copyright 2015 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 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 os
+import struct
+import time
+import logging
+
+from chirp import bitwise
+from chirp import chirp_common
+from chirp import directory
+from chirp import errors
+from chirp import memmap
+from chirp import util
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettingValueString, \
+    RadioSettingValueFloat, InvalidValueError, RadioSettings
+
+LOG = logging.getLogger(__name__)
+
+mem_format = """
+struct memory {
+  bbcd freq[4];
+  bbcd offset[4];     
+  u8 unknown1:4,      
+     tune_step:4;
+  u8 unknown2:2,      
+     txdcsextra:1,
+     txinv:1,
+     channel_width:2,
+     unknown3:1,
+     tx_off:1;
+  u8 unknown4:2,      
+     rxdcsextra:1,
+     rxinv:1,
+     power:2,
+     duplex:2;
+  u8 unknown5:4,      
+     rxtmode:2,
+     txtmode:2;
+  u8 unknown6:2,      
+     txtone:6;
+  u8 unknown7:2,      
+     rxtone:6;
+  u8 txcode;          
+  u8 rxcode;          
+  u8 unknown8[3];     
+  char name[6];       
+  u8 squelch:4,       
+     unknown9:2,
+     bcl:2;
+  u8 unknownA;        
+  u8 unknownB:7,      
+     sqlmode:1;
+  u8 unknownC[4];
+};
+
+#seekto 0x0010;
+struct {
+    u8 unknown1;
+    u8 unknown2:5,     
+       bands1:3;
+    char model[7];     
+    u8 unknown3:5,     
+       bands2:3;
+    u8 unknown4[6];    
+    u8 unknown5[16];   
+    char date[9];      
+    u8 unknown6[7];    
+    u8 unknown7[16];   
+    u8 unknown8[16];   
+    char dealer[16];   
+    char stockdate[9]; 
+    u8 unknown9[7];    
+    char selldate[9];  
+    u8 unknownA[7];    
+    char seller[16];
+} oem_info;
+
+#seekto 0x0100;
+u8 used_flags[50];
+
+#seekto 0x0120;
+u8 skip_flags[50];
+
+#seekto 0x0220;
+struct {
+  u8 unknown220:6,
+     display:2;
+  u8 unknown221:7,
+     upvfomr:1;
+  u8 unknown222:7,
+     dnvfomr:1;
+  u8 unknown223:7,
+     fmvfomr:1;
+  u8 upmrch;
+  u8 dnmrch;
+  u8 unknown226:1,
+     fmmrch:7;
+  u8 unknown227;
+  u8 unknown228:7,
+     fastscano:1;       // obltr-8r only
+  u8 unknown229:6,
+     pause:2;
+  u8 unknown22A:5,
+     stop:3;
+  u8 unknown22B:6,
+     backlight:2;
+  u8 unknown22C:6,
+     color:2;
+  u8 unknown22D:6,
+     vdisplay:2;
+  u8 unknown22E;
+  u8 unknown22F:5,
+     pf1key:3;
+  u8 beep:1,
+     alarmoff:1,
+     main:1,
+     radio:1,
+     unknown230:1,
+     allband:1,
+     elimtail:1,
+     monikey:1;
+  u8 fastscan:1,        // termn-8r only
+     keylock:1,
+     unknown231:2,
+     lwenable:1,
+     swenable:1,
+     fmenable:1,
+     amenable:1;
+  u8 unknown232:3,
+     tot:5;
+  u8 unknown233:7,
+     amvfomr:1;
+  u8 unknown234:3,
+     apo:5;
+  u8 unknown235:5,
+     pf2key:3;          // keylock for obltr-8r
+  u8 unknown236;
+  u8 unknown237:4,
+     save:4;
+  u8 unknown238:5,
+     tbst:3;
+  u8 unknown239:4,
+     voxlevel:4;
+  u8 unknown23A:3,
+     voxdelay:5;
+  u8 unknown23B:5,
+     tail:3;
+  u8 unknown23C;
+  u8 unknown23D:1,
+     ammrch:7;
+  u8 unknown23E:5,
+     vvolume:3;
+  u8 unknown23F:5,
+     fmam:3;
+  u8 unknown240:4,
+     upmrbank:4;
+  u8 unknown241:7,
+     upwork:1;
+  u8 unknown242:7,
+     uplink:1;
+  u8 unknown243:4,
+     dnmrbank:4;
+  u8 unknown244:7,
+     dnwork:1;
+  u8 unknown245:7,
+     downlink:1;
+  u8 unknown246:7,
+     banklink1:1;
+  u8 unknown247:7,
+     banklink2:1;
+  u8 unknown248:7,
+     banklink3:1;
+  u8 unknown249:7,
+     banklink4:1;
+  u8 unknown24A:7,
+     banklink5:1;
+  u8 unknown24B:7,
+     banklink6:1;
+  u8 unknown24C:7,
+     banklink7:1;
+  u8 unknown24D:7,
+     banklink8:1;
+  u8 unknown24E:7,
+     banklink9:1;
+  u8 unknown24F:7,
+     banklink0:1;
+  u8 unknown250:6,
+     noaa:2;
+  u8 unknown251:5,
+     noaach:3;
+  u8 unknown252:6,
+     part95:2;
+  u8 unknown253:3,
+     gmrs:5;
+  u8 unknown254:5,
+     murs:3;
+  u8 unknown255:5,
+     amsql:3;
+} settings;
+
+#seekto 0x0246;
+struct {
+  u8 unused:7,
+     bank:1;
+} banklink[10];
+
+#seekto 0x03E0;
+struct {
+  char line1[6];
+  char line2[6];
+} welcome_msg;
+
+#seekto 0x2000;
+struct memory memory[200];
+"""
+
+
+def _echo_write(radio, data):
+    try:
+        radio.pipe.write(data)
+    except Exception, e:
+        LOG.error("Error writing to radio: %s" % e)
+        raise errors.RadioError("Unable to write to radio")
+
+
+def _read(radio, length):
+    try:
+        data = radio.pipe.read(length)
+    except Exception, e:
+        LOG.error("Error reading from radio: %s" % e)
+        raise errors.RadioError("Unable to read from radio")
+
+    if len(data) != length:
+        LOG.error("Short read from radio (%i, expected %i)" %
+                  (len(data), length))
+        LOG.debug(util.hexprint(data))
+        raise errors.RadioError("Short read from radio")
+    return data
+
+valid_model = ['TERMN8R', 'OBLTR8R']
+
+
+def _ident(radio):
+    radio.pipe.setTimeout(1)
+    _echo_write(radio, "PROGRAM")
+    response = radio.pipe.read(3)
+    if response != "QX\x06":
+        LOG.debug("Response was:\n%s" % util.hexprint(response))
+        raise errors.RadioError("Radio did not respond. Check connection.")
+    _echo_write(radio, "\x02")
+    response = radio.pipe.read(16)
+    LOG.debug(util.hexprint(response))
+    if radio._file_ident not in response:
+        LOG.debug("Response was:\n%s" % util.hexprint(response))
+        raise errors.RadioError("Unsupported model")
+
+
+def _finish(radio):
+    endframe = "\x45\x4E\x44"
+    _echo_write(radio, endframe)
+    result = radio.pipe.read(1)
+    if result != "\x06":
+        LOG.debug("Got:\n%s" % util.hexprint(result))
+        raise errors.RadioError("Radio did not finish cleanly")
+
+
+def _checksum(data):
+    cs = 0
+    for byte in data:
+        cs += ord(byte)
+    return cs % 256
+
+
+def _send(radio, cmd, addr, length, data=None):
+    frame = struct.pack(">cHb", cmd, addr, length)
+    if data:
+        frame += data
+        frame += chr(_checksum(frame[1:]))
+        frame += "\x06"
+    _echo_write(radio, frame)
+    LOG.debug("Sent:\n%s" % util.hexprint(frame))
+    if data:
+        result = radio.pipe.read(1)
+        if result != "\x06":
+            LOG.debug("Ack was: %s" % repr(result))
+            raise errors.RadioError(
+                "Radio did not accept block at %04x" % addr)
+        return
+    result = _read(radio, length + 6)
+    LOG.debug("Got:\n%s" % util.hexprint(result))
+    header = result[0:4]
+    data = result[4:-2]
+    ack = result[-1]
+    if ack != "\x06":
+        LOG.debug("Ack was: %s" % repr(ack))
+        raise errors.RadioError("Radio NAK'd block at %04x" % addr)
+    _cmd, _addr, _length = struct.unpack(">cHb", header)
+    if _addr != addr or _length != _length:
+        LOG.debug("Expected/Received:")
+        LOG.debug(" Length: %02x/%02x" % (length, _length))
+        LOG.debug(" Addr: %04x/%04x" % (addr, _addr))
+        raise errors.RadioError("Radio send unexpected block")
+    cs = _checksum(result[1:-2])
+    if cs != ord(result[-2]):
+        LOG.debug("Calculated: %02x" % cs)
+        LOG.debug("Actual:     %02x" % ord(result[-2]))
+        raise errors.RadioError("Block at 0x%04x failed checksum" % addr)
+    return data
+
+
+def _download(radio):
+    _ident(radio)
+
+    memobj = None
+
+    data = ""
+    for start, end in radio._ranges:
+        for addr in range(start, end, 0x10):
+            block = _send(radio, 'R', addr, 0x10)
+            data += block
+
+            status = chirp_common.Status()
+            status.cur = len(data)
+            status.max = end
+            status.msg = "Cloning from radio"
+            radio.status_fn(status)
+
+    _finish(radio)
+
+    return memmap.MemoryMap(data)
+
+
+def _upload(radio):
+    _ident(radio)
+
+    for start, end in radio._ranges:
+        for addr in range(start, end, 0x10):
+            if addr < 0x0100:
+                continue
+            block = radio._mmap[addr:addr + 0x10]
+            _send(radio, 'W', addr, len(block), block)
+
+            status = chirp_common.Status()
+            status.cur = addr
+            status.max = end
+            status.msg = "Cloning to radio"
+            radio.status_fn(status)
+
+    _finish(radio)
+
+
+APO = ['Off', '30 Minutes', '1 Hour', '2 Hours']
+BACKLIGHT = ['Off', 'On', 'Auto']
+BCLO = ['Off', 'Repeater', 'Busy']
+CHARSET = chirp_common.CHARSET_ASCII
+COLOR = ['Blue', 'Orange', 'Purple']
+DISPLAY = ['Frequency', 'N/A', 'Name']
+DUPLEXES = ['', 'N/A', '-', '+', 'split', 'off']
+GMRS = ['GMRS %s' % x for x in range(1, 8)] + \
+       ['GMRS %s' % x for x in range(15, 23)] + \
+       ['GMRS Repeater %s' % x for x in range(15, 23)]
+MAIN = ['Up', 'Down']
+MODES = ['FM', 'NFM']
+MONI = ['Squelch Off Momentarily', 'Squelch Off']
+MRBANK = ['Bank %s' % x for x in range(1, 10)] + ['Bank 0']
+MURS = ['MURS %s' % x for x in range(1, 6)]
+NOAA = ['Weather Off', 'Weather On', 'Weather Alerts']
+NOAACH = ['WX %s' % x for x in range(1, 8)]
+PART95 = ['Normal(Part 90)', 'GMRS(Part 95A)', 'MURS(Part 95J)']
+PAUSE = ['%s Seconds (TO)' % x for x in range(5, 20, 5)] + ['2 Seconds (CO)']
+PFKEYT = ['Off', 'VOLT', 'CALL', 'FHSS', 'SUB PTT', 'ALARM', 'MONI']
+PFKEYO = ['Off', 'VOLT', 'CALL', 'SUB PTT', 'ALARM', 'MONI']
+POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5),
+                chirp_common.PowerLevel("Mid", watts=2),
+                chirp_common.PowerLevel("Low", watts=1)]
+SAVE = ['Off', '1:2', '1:3', '1:5', '1:8', 'Auto']
+SQUELCH = ['%s' % x for x in range(0, 10)]
+STOP = ['%s Seconds' % x for x in range(0, 4)] + ['Manual']
+TAIL = ['Off', '120 Degree', '180 Degree', '240 Degree']
+TBST = ['Off', '1750 Hz', '2100 Hz', '1000 Hz', '1450 Hz']
+TMODES = ['', 'Tone', 'DTCS', '']
+TONES = [62.5] + list(chirp_common.TONES)
+TOT = ['Off'] + ['%s Seconds' % x for x in range(10, 280, 10)]
+VDISPLAY = ['Frequency/Channel', 'Battery Voltage', 'Off']
+VFOMR = ["VFO", "MR"]
+VOXLEVEL = ['Off'] + ['%s' % x for x in range(1, 11)]
+VOXDELAY = ['%.1f Seconds' % (0.1 * x) for x in range(5, 31)]
+WORKMODE = ["Channel", "Bank"]
+
+
+ at directory.register
+class AnyToneTERMN8RRadio(chirp_common.CloneModeRadio,
+                         chirp_common.ExperimentalRadio):
+    """AnyTone TERMN-8R"""
+    VENDOR = "AnyTone"
+    MODEL = "TERMN-8R"
+    BAUD_RATE = 9600
+    _file_ident = "TERMN8R"
+
+    # May try to mirror the OEM behavior later
+    _ranges = [
+        (0x0000, 0x8000),
+        ]
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.experimental = ("The Anytone driver is currently experimental. "
+                           "There are no known issues with it, but you should "
+                           "proceed with caution.")
+        return rp
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.has_settings = True
+        rf.has_bank = False
+        rf.has_cross = True
+        rf.has_tuning_step = False
+        rf.has_rx_dtcs = True
+        rf.valid_skips = ["", "S"]
+        rf.valid_modes = MODES
+        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
+        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
+                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
+        rf.valid_dtcs_codes = chirp_common.ALL_DTCS_CODES
+        rf.valid_bands = [(136000000, 174000000),
+                          (400000000, 520000000)]
+        rf.valid_characters = CHARSET
+        rf.valid_name_length = 6
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_duplexes = DUPLEXES
+        rf.can_odd_split = True
+        rf.memory_bounds = (0, 199)
+        return rf
+                        
+    def sync_in(self):
+        self._mmap = _download(self)
+        self.process_mmap()
+
+    def sync_out(self):
+        _upload(self)
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(mem_format, self._mmap)
+
+    def _get_dcs_index(self, _mem, which):
+        base = getattr(_mem, '%scode' % which)
+        extra = getattr(_mem, '%sdcsextra' % which)
+        return (int(extra) << 8) | int(base)
+
+    def _set_dcs_index(self, _mem, which, index):
+        base = getattr(_mem, '%scode' % which)
+        extra = getattr(_mem, '%sdcsextra' % which)
+        base.set_value(index & 0xFF)
+        extra.set_value(index >> 8)
+
+    def get_memory(self, number):
+        bitpos = (1 << (number % 8))
+        bytepos = (number / 8)
+
+        _mem = self._memobj.memory[number]
+        _skp = self._memobj.skip_flags[bytepos]
+        _usd = self._memobj.used_flags[bytepos]
+
+        mem = chirp_common.Memory()
+        mem.number = number
+
+        if _usd & bitpos:
+            mem.empty = True
+            return mem
+
+        mem.freq = int(_mem.freq) * 100
+        mem.offset = int(_mem.offset) * 100
+        mem.name = self.filter_name(str(_mem.name).rstrip())
+        mem.duplex = DUPLEXES[_mem.duplex]
+        mem.mode = MODES[_mem.channel_width]
+
+        if _mem.tx_off == True:
+            mem.duplex = "off"
+            mem.offset = 0
+
+        rxtone = txtone = None
+        rxmode = TMODES[_mem.rxtmode]
+        txmode = TMODES[_mem.txtmode]
+        if txmode == "Tone":
+            txtone = TONES[_mem.txtone]
+        elif txmode == "DTCS":
+            txtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(_mem,
+                                                                     'tx')]
+        if rxmode == "Tone":
+            rxtone = TONES[_mem.rxtone]
+        elif rxmode == "DTCS":
+            rxtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(_mem,
+                                                                     'rx')]
+
+        rxpol = _mem.rxinv and "R" or "N"
+        txpol = _mem.txinv and "R" or "N"
+
+        chirp_common.split_tone_decode(mem,
+                                       (txmode, txtone, txpol),
+                                       (rxmode, rxtone, rxpol))
+
+        if _skp & bitpos:
+            mem.skip = "S"
+
+        mem.power = POWER_LEVELS[_mem.power]
+
+        mem.extra = RadioSettingGroup("Extra", "extra")
+
+        rs = RadioSetting("bcl", "Busy Channel Lockout",
+                          RadioSettingValueList(BCLO,
+                                                BCLO[_mem.bcl]))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("squelch", "Squelch",
+                          RadioSettingValueList(SQUELCH,
+                                                SQUELCH[_mem.squelch]))
+        mem.extra.append(rs)
+
+        return mem
+
+    def set_memory(self, mem):
+        bitpos = (1 << (mem.number % 8))
+        bytepos = (mem.number / 8)
+
+        _mem = self._memobj.memory[mem.number]
+        _skp = self._memobj.skip_flags[bytepos]
+        _usd = self._memobj.used_flags[bytepos]
+
+        if mem.empty:
+            _usd |= bitpos
+            _skp |= bitpos
+            _mem.set_raw("\xFF" * 32)
+            return
+        _usd &= ~bitpos
+
+        if _mem.get_raw() == ("\xFF" * 32):
+            LOG.debug("Initializing empty memory")
+            _mem.set_raw("\x00" * 32)
+            _mem.squelch = 3
+
+        _mem.freq = mem.freq / 100
+
+        if mem.duplex == "off":
+            _mem.duplex = DUPLEXES.index("")
+            _mem.offset = 0
+            _mem.tx_off = True
+        elif mem.duplex == "split":
+            diff = mem.offset - mem.freq
+            _mem.duplex = DUPLEXES.index("-") if diff < 0 \
+                else DUPLEXES.index("+")
+            _mem.offset = abs(diff) / 100
+        else:
+            _mem.offset = mem.offset / 100
+            _mem.duplex = DUPLEXES.index(mem.duplex)
+
+        _mem.name = mem.name.ljust(6)
+
+        try:
+            _mem.channel_width = MODES.index(mem.mode)
+        except ValueError:
+            _mem.channel_width = 0
+
+        ((txmode, txtone, txpol),
+         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
+
+        _mem.txtmode = TMODES.index(txmode)
+        _mem.rxtmode = TMODES.index(rxmode)
+        if txmode == "Tone":
+            _mem.txtone = TONES.index(txtone)
+        elif txmode == "DTCS":
+            self._set_dcs_index(_mem, 'tx',
+                                chirp_common.ALL_DTCS_CODES.index(txtone))
+        if rxmode == "Tone":
+            _mem.sqlmode = 1
+            _mem.rxtone = TONES.index(rxtone)
+        elif rxmode == "DTCS":
+            _mem.sqlmode = 1
+            self._set_dcs_index(_mem, 'rx',
+                                chirp_common.ALL_DTCS_CODES.index(rxtone))
+        else:
+            _mem.sqlmode = 0
+
+        _mem.txinv = txpol == "R"
+        _mem.rxinv = rxpol == "R"
+
+        if mem.skip:
+            _skp |= bitpos
+        else:
+            _skp &= ~bitpos
+
+        if mem.power:
+            _mem.power = POWER_LEVELS.index(mem.power)
+        else:
+            _mem.power = 0
+
+        for setting in mem.extra:
+            setattr(_mem, setting.get_name(), setting.value)
+
+    def get_settings(self):
+        _msg = self._memobj.welcome_msg
+        _oem = self._memobj.oem_info
+        _settings = self._memobj.settings
+        cfg_grp = RadioSettingGroup("cfg_grp", "Function Setup")
+        oem_grp = RadioSettingGroup("oem_grp", "OEM Info")
+
+        group = RadioSettings(cfg_grp,
+                              oem_grp)
+
+        def _filter(name):
+            filtered = ""
+            for char in str(name):
+                if char in chirp_common.CHARSET_ASCII:
+                    filtered += char
+                else:
+                    filtered += " "
+            return filtered
+
+        #
+        # Function Setup
+        #
+
+        rs = RadioSetting("welcome_msg.line1", "Welcome Message 1",
+                          RadioSettingValueString(
+                              0, 6, _filter(_msg.line1)))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("welcome_msg.line2", "Welcome Message 2",
+                          RadioSettingValueString(
+                              0, 6, _filter(_msg.line2)))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("display", "Display Mode",
+                          RadioSettingValueList(DISPLAY,
+                                                DISPLAY[_settings.display]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("upvfomr", "Up VFO/MR",
+                          RadioSettingValueList(VFOMR,
+                                                VFOMR[_settings.upvfomr]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("dnvfomr", "Down VFO/MR",
+                          RadioSettingValueList(VFOMR,
+                                                VFOMR[_settings.dnvfomr]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("upwork", "Up Work Mode",
+                          RadioSettingValueList(WORKMODE,
+                                                WORKMODE[_settings.upwork]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("upmrbank", "Up MR Bank",
+                          RadioSettingValueList(MRBANK,
+                                                MRBANK[_settings.upmrbank]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("upmrch", "Up MR Channel",
+                          RadioSettingValueInteger(0, 200, _settings.upmrch))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("dnwork", "Down Work Mode",
+                          RadioSettingValueList(WORKMODE,
+                                                WORKMODE[_settings.dnwork]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("dnmrbank", "Down MR Bank",
+                          RadioSettingValueList(MRBANK,
+                                                MRBANK[_settings.dnmrbank]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("dnmrch", "Down MR Channel",
+                          RadioSettingValueInteger(0, 200, _settings.dnmrch))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("main", "Main",
+                          RadioSettingValueList(MAIN,
+                                                MAIN[_settings.main]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("pause", "Scan Pause Time",
+                          RadioSettingValueList(PAUSE,
+                                                PAUSE[_settings.pause]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("stop", "Function Keys Stop Time",
+                          RadioSettingValueList(STOP,
+                                                STOP[_settings.stop]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("backlight", "Backlight",
+                          RadioSettingValueList(BACKLIGHT,
+                                                BACKLIGHT[_settings.backlight]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("color", "Backlight Color",
+                          RadioSettingValueList(COLOR,
+                                                COLOR[_settings.color]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("vdisplay", "Vice-Machine Display",
+                          RadioSettingValueList(VDISPLAY,
+                                                VDISPLAY[_settings.vdisplay]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("voxlevel", "Vox Level",
+                          RadioSettingValueList(VOXLEVEL,
+                                                VOXLEVEL[_settings.voxlevel]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("voxdelay", "Vox Delay",
+                          RadioSettingValueList(VOXDELAY,
+                                                VOXDELAY[_settings.voxdelay]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("tot", "Time Out Timer",
+                          RadioSettingValueList(TOT,
+                                                TOT[_settings.tot]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("tbst", "Tone Burst",
+                          RadioSettingValueList(TBST,
+                                                TBST[_settings.tbst]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("monikey", "MONI Key Function",
+                          RadioSettingValueList(MONI,
+                                                MONI[_settings.monikey]))
+        cfg_grp.append(rs)
+
+        if self.MODEL == "TERMN-8R":
+            rs = RadioSetting("pf1key", "PF1 Key Function",
+                              RadioSettingValueList(PFKEYT,
+                                                    PFKEYT[_settings.pf1key]))
+            cfg_grp.append(rs)
+
+            rs = RadioSetting("pf2key", "PF2 Key Function",
+                              RadioSettingValueList(PFKEYT,
+                                                    PFKEYT[_settings.pf2key]))
+            cfg_grp.append(rs)
+
+        if self.MODEL == "OBLTR-8R":
+            rs = RadioSetting("pf1key", "PF1 Key Function",
+                              RadioSettingValueList(PFKEYO,
+                                                    PFKEYO[_settings.pf1key]))
+            cfg_grp.append(rs)
+
+            rs = RadioSetting("fmam", "PF2 Key Function",
+                              RadioSettingValueList(PFKEYO,
+                                                    PFKEYO[_settings.fmam]))
+            cfg_grp.append(rs)
+
+        rs = RadioSetting("apo", "Automatic Power Off",
+                          RadioSettingValueList(APO,
+                                                APO[_settings.apo]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("save", "Power Save",
+                          RadioSettingValueList(SAVE,
+                                                SAVE[_settings.save]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("tail", "Tail Eliminator Type",
+                          RadioSettingValueList(TAIL,
+                                                TAIL[_settings.tail]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("fmvfomr", "FM VFO/MR",
+                          RadioSettingValueList(VFOMR,
+                                                VFOMR[_settings.fmvfomr]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("fmmrch", "FM MR Channel",
+                          RadioSettingValueInteger(0, 100, _settings.fmmrch))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("noaa", "NOAA",
+                          RadioSettingValueList(NOAA,
+                                                NOAA[_settings.noaa]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("noaach", "NOAA Channel",
+                          RadioSettingValueList(NOAACH,
+                                                NOAACH[_settings.noaach]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("part95", "PART95",
+                          RadioSettingValueList(PART95,
+                                                PART95[_settings.part95]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("gmrs", "GMRS",
+                          RadioSettingValueList(GMRS,
+                                                GMRS[_settings.gmrs]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("murs", "MURS",
+                          RadioSettingValueList(MURS,
+                                                MURS[_settings.murs]))
+        cfg_grp.append(rs)
+
+        for i in range(0, 9):
+            val = self._memobj.banklink[i].bank
+            rs = RadioSetting("banklink/%i.bank" % i,
+                              "Bank Link %i" % (i + 1),
+                              RadioSettingValueBoolean(val))
+            cfg_grp.append(rs)
+
+        val = self._memobj.banklink[9].bank
+        rs = RadioSetting("banklink/9.bank", "Bank Link 0",
+                          RadioSettingValueBoolean(val))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("allband", "All Band",
+                          RadioSettingValueBoolean(_settings.allband))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("alarmoff", "Alarm Function Off",
+                          RadioSettingValueBoolean(_settings.alarmoff))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("beep", "Beep",
+                          RadioSettingValueBoolean(_settings.beep))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("radio", "Radio",
+                          RadioSettingValueBoolean(_settings.radio))
+        cfg_grp.append(rs)
+
+        if self.MODEL == "TERMN-8R":
+            rs = RadioSetting("keylock", "Keylock",
+                              RadioSettingValueBoolean(_settings.keylock))
+            cfg_grp.append(rs)
+
+            rs = RadioSetting("fastscan", "Fast Scan",
+                              RadioSettingValueBoolean(_settings.fastscan))
+            cfg_grp.append(rs)
+
+        if self.MODEL == "OBLTR-8R":
+            # "pf2key" is used for OBLTR-8R "keylock"
+            rs = RadioSetting("pf2key", "Keylock",
+                              RadioSettingValueBoolean(_settings.pf2key))
+            cfg_grp.append(rs)
+
+            rs = RadioSetting("fastscano", "Fast Scan",
+                              RadioSettingValueBoolean(_settings.fastscano))
+            cfg_grp.append(rs)
+
+        rs = RadioSetting("uplink", "Up Bank Link Select",
+                          RadioSettingValueBoolean(_settings.uplink))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("downlink", "Down Bank Link Select",
+                          RadioSettingValueBoolean(_settings.downlink))
+        cfg_grp.append(rs)
+
+        #
+        # OEM info
+        #
+
+        val = RadioSettingValueString(0, 7, _filter(_oem.model))
+        val.set_mutable(False)
+        rs = RadioSetting("oem_info.model", "Model", val)
+        oem_grp.append(rs)
+
+        val = RadioSettingValueString(0, 9, _filter(_oem.date))
+        val.set_mutable(False)
+        rs = RadioSetting("oem_info.date", "Date", val)
+        oem_grp.append(rs)
+
+        val = RadioSettingValueString(0, 16, _filter(_oem.dealer))
+        val.set_mutable(False)
+        rs = RadioSetting("oem_info.dealer", "Dealer Code", val)
+        oem_grp.append(rs)
+
+        val = RadioSettingValueString(0, 9, _filter(_oem.stockdate))
+        val.set_mutable(False)
+        rs = RadioSetting("oem_info.stockdate", "Stock Date", val)
+        oem_grp.append(rs)
+
+        val = RadioSettingValueString(0, 9, _filter(_oem.selldate))
+        val.set_mutable(False)
+        rs = RadioSetting("oem_info.selldate", "Sell Date", val)
+        oem_grp.append(rs)
+
+        return group
+
+    def set_settings(self, settings):
+        _settings = self._memobj.settings
+        for element in settings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+            else:
+                try:
+                    name = element.get_name()
+                    if "." in name:
+                        bits = name.split(".")
+                        obj = self._memobj
+                        for bit in bits[:-1]:
+                            if "/" in bit:
+                                bit, index = bit.split("/", 1)
+                                index = int(index)
+                                obj = getattr(obj, bit)[index]
+                            else:
+                                obj = getattr(obj, bit)
+                        setting = bits[-1]
+                    else:
+                        obj = _settings
+                        setting = element.get_name()
+
+                    if element.has_apply_callback():
+                        LOG.debug("Using apply callback")
+                        element.run_apply_callback()
+                    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):
+        return cls._file_ident in filedata[0x10:0x20]
+
+ at directory.register
+class AnyToneOBLTR8RRadio(AnyToneTERMN8RRadio):
+    """AnyTone OBLTR-8R"""
+    VENDOR = "AnyTone"
+    MODEL = "OBLTR-8R"
+    _file_ident = "OBLTR8R"
diff --git a/chirp/drivers/ap510.py b/chirp/drivers/ap510.py
new file mode 100644
index 0000000..d7b3ff2
--- /dev/null
+++ b/chirp/drivers/ap510.py
@@ -0,0 +1,808 @@
+# Copyright 2014 Tom Hayward <tom at tomh.us>
+#
+# 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 struct
+from time import sleep
+import logging
+
+from chirp import chirp_common, directory, errors, util
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettingValueString, \
+    InvalidValueError, RadioSettings
+
+LOG = logging.getLogger(__name__)
+
+
+def chunks(s, t):
+    """ Yield chunks of s in sizes defined in t."""
+    i = 0
+    for n in t:
+        yield s[i:i+n]
+        i += n
+
+
+def encode_base100(v):
+    return (v / 100 << 8) + (v % 100)
+
+
+def decode_base100(u16):
+    return 100 * (u16 >> 8 & 0xff) + (u16 & 0xff)
+
+
+def drain(pipe):
+    """Chew up any data waiting on @pipe"""
+    for x in xrange(3):
+        buf = pipe.read(4096)
+        if not buf:
+            return
+    raise errors.RadioError('Your pipes are clogged.')
+
+
+def enter_setup(pipe):
+    """Put AP510 in configuration mode."""
+    for x in xrange(30):
+        if x % 2:
+            pipe.write("@SETUP")
+        else:
+            pipe.write("\r\nSETUP\r\n")
+        s = pipe.read(64)
+        if s and "\r\nSETUP" in s:
+            return True
+        elif s and "SETUP" in s:
+            return False
+    raise errors.RadioError('Radio did not respond.')
+
+
+def download(radio):
+    status = chirp_common.Status()
+    drain(radio.pipe)
+
+    status.msg = " Power on AP510 now, waiting "
+    radio.status_fn(status)
+    new = enter_setup(radio.pipe)
+
+    status.cur = 1
+    status.max = 5
+    status.msg = "Downloading"
+    radio.status_fn(status)
+    if new:
+        radio.pipe.write("\r\nDISP\r\n")
+    else:
+        radio.pipe.write("@DISP")
+    buf = ""
+
+    for status.cur in xrange(status.cur, status.max):
+        buf += radio.pipe.read(1024)
+        if buf.endswith("\r\n"):
+            status.cur = status.max
+            radio.status_fn(status)
+            break
+        radio.status_fn(status)
+    else:
+        raise errors.RadioError("Incomplete data received.")
+
+    LOG.debug("%04i P<R: %s" %
+              (len(buf), util.hexprint(buf).replace("\n", "\n          ")))
+    return buf
+
+
+def upload(radio):
+    status = chirp_common.Status()
+    drain(radio.pipe)
+
+    status.msg = " Power on AP510 now, waiting "
+    radio.status_fn(status)
+    new = enter_setup(radio.pipe)
+
+    status.msg = "Uploading"
+    status.cur = 1
+    status.max = len(radio._mmap._memobj.items())
+    for k, v in radio._mmap._memobj.items():
+        if k == '00':
+            continue
+        if new:
+            radio.pipe.write("%s=%s\r\n" % (k, v))
+            sleep(0.05)
+        elif k in ('09', '10', '15'):
+            radio.pipe.write("@" + k + v + "\x00\r\n")
+        else:
+            radio.pipe.write("@" + k + v)
+        # Older firmware acks every command except 15 with OK.
+        if not new and radio.pipe.read(2) != "OK" and k != '15':
+            raise errors.RadioError("Radio did not acknowledge upload: %s" % k)
+        status.cur += 1
+        radio.status_fn(status)
+    if new and radio.pipe.read(6) != "\r\n\r\nOK":
+        raise errors.RadioError("Radio did not acknowledge upload.")
+
+
+def strbool(s):
+    return s == '1'
+
+
+def boolstr(b):
+    return b and '1' or '0'
+
+
+class AP510Memory(object):
+    """Parses and generates AP510 key/value format
+
+    The AP510 sends it's configuration as a set of keys and values. There
+    is one key/value pair per line. Line separators are \r\n. Keys are
+    deliminated from values with the = symbol.
+
+    Sample:
+    00=AVRT5 20140829
+    01=KD7LXL7
+    02=3
+    03=1
+
+    This base class is compatible with firmware 20141008 (rx free).
+    """
+
+    ATTR_MAP = {
+        'version':  '00',
+        'callsign': '01',
+        'pttdelay': '02',
+        'output':   '03',
+        'mice':     '04',
+        'path':     '05',
+        'symbol':   '06',
+        'beacon':   '07',
+        'rate':     '08',
+        'status':   '09',
+        'comment':  '10',
+        'digipeat': '12',
+        'autooff':  '13',
+        'chinamapfix': '14',
+        'virtualgps': '15',
+        'freq':     '16',
+        'beep':     '17',
+        'smartbeacon': '18',
+        'highaltitude': '19',
+        'busywait': '20',
+    }
+
+    def __init__(self, data):
+        self._data = data
+        self.process_data()
+
+    def get_packed(self):
+        self._data = "\r\n"
+        for v in sorted(self.ATTR_MAP.values()):
+            self._data += "%s=%s\r\n" % (v, self._memobj[v])
+        return self._data
+
+    def process_data(self):
+        data = []
+        for line in self._data.split('\r\n'):
+            if '=' in line:
+                data.append(line.split('=', 1))
+        self._memobj = dict(data)
+        LOG.debug(self.version)
+
+    def __getattr__(self, name):
+        if hasattr(self, 'get_%s' % name):
+            return getattr(self, 'get_%s' % name)()
+        try:
+            return self._memobj[self.ATTR_MAP[name]]
+        except KeyError as e:
+            raise NotImplementedError(e)
+
+    def __setattr__(self, name, value):
+        if name.startswith('_'):
+            super(AP510Memory, self).__setattr__(name, value)
+            return
+        if hasattr(self, 'set_%s' % name):
+            return getattr(self, 'set_%s' % name)(value)
+        try:
+            self._memobj[self.ATTR_MAP[name]] = str(value)
+        except KeyError as e:
+            raise NotImplementedError(e)
+
+    def get_smartbeacon(self):
+        return dict(zip((
+            'lowspeed',
+            'slowrate',
+            'highspeed',
+            'fastrate',
+            'turnslope',
+            'turnangle',
+            'turntime',
+        ), map(
+            decode_base100,
+            struct.unpack(">7H", self._memobj[self.ATTR_MAP['smartbeacon']]))
+        ))
+
+    def set_smartbeacon(self, d):
+        self._memobj[self.ATTR_MAP['smartbeacon']] = \
+            struct.pack(">7H",
+                        encode_base100(d['lowspeed']),
+                        encode_base100(d['slowrate']),
+                        encode_base100(d['highspeed']),
+                        encode_base100(d['fastrate']),
+                        encode_base100(d['turnslope']),
+                        encode_base100(d['turnangle']),
+                        encode_base100(d['turntime']),
+                        )
+
+
+class AP510Memory20141215(AP510Memory):
+    """Compatible with firmware version 20141215"""
+    ATTR_MAP = dict(AP510Memory.ATTR_MAP.items() + {
+        'tx_volume': '21',  # 1-6
+        'rx_volume': '22',  # 1-9
+        'tx_power': '23',  # 1: 1 watt,  0: 0.5 watt
+        'tx_serial_ui_out': '24',
+        'path1': '25',
+        'path2': '26',
+        'path3': '27',  # like "WIDE1 1" else "0"
+        'multiple': '28',
+        'auto_on': '29',
+    }.items())
+
+    def get_multiple(self):
+        return dict(zip(
+           (
+            'mice_message',     # conveniently matches APRS spec Mic-E messages
+            'voltage',          # voltage in comment
+            'temperature',      # temperature in comment
+            'tfx',              # not sure what the TF/X toggle does
+            'squelch',          # squelch level 0-8 (0 = disabled)
+            'blueled',          # 0: squelch LED on GPS lock
+                                # 1: light LED on GPS lock
+            'telemetry',        # 1: enable
+            'telemetry_every',  # two-digit int
+            'timeslot_enable',  # 1: enable   Is this implemented in firmware?
+            'timeslot',         # int 00-59
+            'dcd',              # 0: Blue LED displays squelch,
+                                # 1: Blue LED displays software DCD
+            'tf_card'           # 0: KML,  1: WPL
+            ), map(int, chunks(self._memobj[self.ATTR_MAP['multiple']],
+                               (1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1)))
+        ))
+
+    def set_multiple(self, d):
+        self._memobj[self.ATTR_MAP['multiple']] = "%(mice_message)1d" \
+                                                  "%(voltage)1d" \
+                                                  "%(temperature)1d" \
+                                                  "%(tfx)1d" \
+                                                  "%(squelch)1d" \
+                                                  "%(blueled)1d" \
+                                                  "%(telemetry)1d" \
+                                                  "%(telemetry_every)02d" \
+                                                  "%(timeslot_enable)1d" \
+                                                  "%(timeslot)02d" \
+                                                  "%(dcd)1d" \
+                                                  "%(tf_card)1d" % d
+
+    def get_smartbeacon(self):
+        # raw:    18=0100300060010240028005
+        # chunks: 18=010 0300 060 010 240 028 005
+        return dict(zip((
+            'lowspeed',
+            'slowrate',
+            'highspeed',
+            'fastrate',
+            'turnslope',
+            'turnangle',
+            'turntime',
+        ), map(int, chunks(
+            self._memobj[self.ATTR_MAP['smartbeacon']],
+            (3, 4, 3, 3, 3, 3, 3)))
+        ))
+
+    def set_smartbeacon(self, d):
+        self._memobj[self.ATTR_MAP['smartbeacon']] = "%(lowspeed)03d" \
+                                                     "%(slowrate)04d" \
+                                                     "%(highspeed)03d" \
+                                                     "%(fastrate)03d" \
+                                                     "%(turnslope)03d" \
+                                                     "%(turnangle)03d" \
+                                                     "%(turntime)03d" % d
+
+
+PTT_DELAY = ['60 ms', '120 ms', '180 ms', '300 ms', '480 ms',
+             '600 ms', '1000 ms']
+OUTPUT = ['KISS', 'Waypoint out', 'UI out']
+PATH = [
+    '(None)',
+    'WIDE1-1',
+    'WIDE1-1,WIDE2-1',
+    'WIDE1-1,WIDE2-2',
+    'TEMP1-1',
+    'TEMP1-1,WIDE 2-1',
+    'WIDE2-1',
+]
+TABLE = "/\#&0>AW^_acnsuvz"
+SYMBOL = "".join(map(chr, range(ord("!"), ord("~")+1)))
+BEACON = ['manual', 'auto', 'auto + manual', 'smart', 'smart + manual']
+ALIAS = ['WIDE1-N', 'WIDE2-N', 'WIDE1-N + WIDE2-N']
+CHARSET = "".join(map(chr, range(0, 256)))
+MICE_MESSAGE = ['Emergency', 'Priority', 'Special', 'Committed', 'Returning',
+                'In Service', 'En Route', 'Off Duty']
+TF_CARD = ['WPL', 'KML']
+POWER_LEVELS = [chirp_common.PowerLevel("0.5 watt", watts=0.50),
+                chirp_common.PowerLevel("1 watt", watts=1.00)]
+
+RP_IMMUTABLE = ["number", "skip", "bank", "extd_number", "name", "rtone",
+                "ctone", "dtcs", "tmode", "dtcs_polarity", "skip", "duplex",
+                "offset", "mode", "tuning_step", "bank_index"]
+
+
+ at directory.register
+class AP510Radio(chirp_common.CloneModeRadio):
+    """Sainsonic AP510"""
+    BAUD_RATE = 9600
+    VENDOR = "Sainsonic"
+    MODEL = "AP510"
+
+    _model = "AVRT5"
+    mem_upper_limit = 0
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.has_settings = True
+        rf.valid_modes = ["FM"]
+        rf.valid_tmodes = [""]
+        rf.valid_characters = ""
+        rf.valid_duplexes = [""]
+        rf.valid_name_length = 0
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_skips = []
+        rf.valid_tuning_steps = []
+        rf.has_bank = False
+        rf.has_ctone = False
+        rf.has_dtcs = False
+        rf.has_dtcs_polarity = False
+        rf.has_mode = False
+        rf.has_name = False
+        rf.has_offset = False
+        rf.has_tuning_step = False
+        rf.valid_bands = [(136000000, 174000000)]
+        rf.memory_bounds = (0, 0)
+        return rf
+
+    def sync_in(self):
+        try:
+            data = download(self)
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            raise errors.RadioError("Failed to communicate with radio: %s" % e)
+
+        # _mmap isn't a Chirp MemoryMap, but since AP510Memory implements
+        # get_packed(), the standard Chirp save feature works.
+        if data.startswith('\r\n00=%s 20141215' % self._model):
+            self._mmap = AP510Memory20141215(data)
+        else:
+            self._mmap = AP510Memory(data)
+
+    def process_mmap(self):
+        self._mmap.process_data()
+
+    def sync_out(self):
+        try:
+            upload(self)
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            raise errors.RadioError("Failed to communicate with radio: %s" % e)
+
+    def load_mmap(self, filename):
+        """Load the radio's memory map from @filename"""
+        mapfile = file(filename, "rb")
+        data = mapfile.read()
+        if data.startswith('\r\n00=%s 20141215' % self._model):
+            self._mmap = AP510Memory20141215(data)
+        else:
+            self._mmap = AP510Memory(data)
+        mapfile.close()
+
+    def get_raw_memory(self, number):
+        return self._mmap.get_packed()
+
+    def get_memory(self, number):
+        if number != 0:
+            raise errors.InvalidMemoryLocation("AP510 has only one slot")
+
+        mem = chirp_common.Memory()
+        mem.number = 0
+        mem.freq = float(self._mmap.freq) * 1000000
+        mem.name = "TX/RX"
+        mem.mode = "FM"
+        mem.offset = 0.0
+        try:
+            mem.power = POWER_LEVELS[int(self._mmap.tx_power)]
+        except NotImplementedError:
+            mem.power = POWER_LEVELS[1]
+        mem.immutable = RP_IMMUTABLE
+
+        return mem
+
+    def set_memory(self, mem):
+        if mem.number != 0:
+            raise errors.InvalidMemoryLocation("AP510 has only one slot")
+
+        self._mmap.freq = "%8.4f" % (mem.freq / 1000000.0)
+        if mem.power:
+            try:
+                self._mmap.tx_power = str(POWER_LEVELS.index(mem.power))
+            except NotImplementedError:
+                pass
+
+    def get_settings(self):
+        china = RadioSettingGroup("china", "China Map Fix")
+        smartbeacon = RadioSettingGroup("smartbeacon", "Smartbeacon")
+
+        aprs = RadioSettingGroup("aprs", "APRS", china, smartbeacon)
+        digipeat = RadioSettingGroup("digipeat", "Digipeat")
+        system = RadioSettingGroup("system", "System")
+        settings = RadioSettings(aprs, digipeat, system)
+
+        aprs.append(RadioSetting("callsign", "Callsign",
+                    RadioSettingValueString(0, 6, self._mmap.callsign[:6])))
+        aprs.append(RadioSetting("ssid", "SSID", RadioSettingValueInteger(
+                    0, 15, ord(self._mmap.callsign[6]) - 0x30)))
+        pttdelay = PTT_DELAY[int(self._mmap.pttdelay) - 1]
+        aprs.append(RadioSetting("pttdelay", "PTT Delay",
+                    RadioSettingValueList(PTT_DELAY, pttdelay)))
+        output = OUTPUT[int(self._mmap.output) - 1]
+        aprs.append(RadioSetting("output", "Output",
+                    RadioSettingValueList(OUTPUT, output)))
+        aprs.append(RadioSetting("mice", "Mic-E",
+                    RadioSettingValueBoolean(strbool(self._mmap.mice))))
+        try:
+            mice_msg = MICE_MESSAGE[int(self._mmap.multiple['mice_message'])]
+            aprs.append(RadioSetting("mice_message", "Mic-E Message",
+                        RadioSettingValueList(MICE_MESSAGE, mice_msg)))
+        except NotImplementedError:
+            pass
+        try:
+            aprs.append(RadioSetting("path1", "Path 1",
+                        RadioSettingValueString(0, 6, self._mmap.path1[:6],
+                                                autopad=True,
+                                                charset=CHARSET)))
+            ssid1 = ord(self._mmap.path1[6]) - 0x30
+            aprs.append(RadioSetting("ssid1", "SSID 1",
+                        RadioSettingValueInteger(0, 7, ssid1)))
+            aprs.append(RadioSetting("path2", "Path 2",
+                        RadioSettingValueString(0, 6, self._mmap.path2[:6],
+                                                autopad=True,
+                                                charset=CHARSET)))
+            ssid2 = ord(self._mmap.path2[6]) - 0x30
+            aprs.append(RadioSetting("ssid2", "SSID 2",
+                        RadioSettingValueInteger(0, 7, ssid2)))
+            aprs.append(RadioSetting("path3", "Path 3",
+                        RadioSettingValueString(0, 6, self._mmap.path3[:6],
+                                                autopad=True,
+                                                charset=CHARSET)))
+            ssid3 = ord(self._mmap.path3[6]) - 0x30
+            aprs.append(RadioSetting("ssid3", "SSID 3",
+                        RadioSettingValueInteger(0, 7, ssid3)))
+        except NotImplementedError:
+            aprs.append(RadioSetting("path", "Path",
+                        RadioSettingValueList(PATH,
+                                              PATH[int(self._mmap.path)])))
+        aprs.append(RadioSetting("table", "Table or Overlay",
+                    RadioSettingValueList(TABLE, self._mmap.symbol[1])))
+        aprs.append(RadioSetting("symbol", "Symbol",
+                    RadioSettingValueList(SYMBOL, self._mmap.symbol[0])))
+        aprs.append(RadioSetting("beacon", "Beacon Mode",
+                    RadioSettingValueList(BEACON,
+                                          BEACON[int(self._mmap.beacon) - 1])))
+        aprs.append(RadioSetting("rate", "Beacon Rate (seconds)",
+                    RadioSettingValueInteger(10, 9999, self._mmap.rate)))
+        aprs.append(RadioSetting("comment", "Comment",
+                    RadioSettingValueString(0, 34, self._mmap.comment,
+                                            autopad=False, charset=CHARSET)))
+        try:
+            voltage = self._mmap.multiple['voltage']
+            aprs.append(RadioSetting("voltage", "Voltage in comment",
+                        RadioSettingValueBoolean(voltage)))
+            temperature = self._mmap.multiple['temperature']
+            aprs.append(RadioSetting("temperature", "Temperature in comment",
+                        RadioSettingValueBoolean(temperature)))
+        except NotImplementedError:
+            pass
+        aprs.append(RadioSetting("status", "Status", RadioSettingValueString(
+            0, 34, self._mmap.status, autopad=False, charset=CHARSET)))
+        try:
+            telemetry = self._mmap.multiple['telemetry']
+            aprs.append(RadioSetting("telemetry", "Telemetry",
+                        RadioSettingValueBoolean(telemetry)))
+            telemetry_every = self._mmap.multiple['telemetry_every']
+            aprs.append(RadioSetting("telemetry_every", "Telemetry every",
+                        RadioSettingValueInteger(1, 99, telemetry_every)))
+            timeslot_enable = self._mmap.multiple['telemetry']
+            aprs.append(RadioSetting("timeslot_enable", "Timeslot",
+                        RadioSettingValueBoolean(timeslot_enable)))
+            timeslot = self._mmap.multiple['timeslot']
+            aprs.append(RadioSetting("timeslot", "Timeslot (second of minute)",
+                        RadioSettingValueInteger(0, 59, timeslot)))
+        except NotImplementedError:
+            pass
+
+        fields = [
+            ("chinamapfix", "China map fix",
+                RadioSettingValueBoolean(strbool(self._mmap.chinamapfix[0]))),
+            ("chinalat", "Lat",
+                RadioSettingValueInteger(
+                    -45, 45, ord(self._mmap.chinamapfix[2]) - 80)),
+            ("chinalon", "Lon",
+                RadioSettingValueInteger(
+                    -45, 45, ord(self._mmap.chinamapfix[1]) - 80)),
+        ]
+        for field in fields:
+            china.append(RadioSetting(*field))
+
+        try:
+            # Sometimes when digipeat is disabled, alias is 0xFF
+            alias = ALIAS[int(self._mmap.digipeat[1]) - 1]
+        except ValueError:
+            alias = ALIAS[0]
+        fields = [
+            ("digipeat", "Digipeat",
+                RadioSettingValueBoolean(strbool(self._mmap.digipeat[0]))),
+            ("alias", "Digipeat Alias",
+                RadioSettingValueList(
+                    ALIAS, alias)),
+            ("virtualgps", "Static Position",
+                RadioSettingValueBoolean(strbool(self._mmap.virtualgps[0]))),
+            ("btext", "Static Position BTEXT", RadioSettingValueString(
+                0, 27, self._mmap.virtualgps[1:], autopad=False,
+                charset=CHARSET)),
+        ]
+        for field in fields:
+            digipeat.append(RadioSetting(*field))
+
+        sb = self._mmap.smartbeacon
+        fields = [
+            ("lowspeed", "Low Speed"),
+            ("highspeed", "High Speed"),
+            ("slowrate", "Slow Rate (seconds)"),
+            ("fastrate", "Fast Rate (seconds)"),
+            ("turnslope", "Turn Slope"),
+            ("turnangle", "Turn Angle"),
+            ("turntime", "Turn Time (seconds)"),
+        ]
+        for field in fields:
+            smartbeacon.append(RadioSetting(
+                field[0], field[1],
+                RadioSettingValueInteger(0, 9999, sb[field[0]])
+            ))
+
+        system.append(RadioSetting("version", "Version (read-only)",
+                      RadioSettingValueString(0, 14, self._mmap.version)))
+        system.append(RadioSetting("autooff", "Auto off (after 90 minutes)",
+                      RadioSettingValueBoolean(strbool(self._mmap.autooff))))
+        system.append(RadioSetting("beep", "Beep on transmit",
+                      RadioSettingValueBoolean(strbool(self._mmap.beep))))
+        system.append(RadioSetting("highaltitude", "High Altitude",
+                      RadioSettingValueBoolean(
+                          strbool(self._mmap.highaltitude))))
+        system.append(RadioSetting("busywait",
+                                   "Wait for clear channel before transmit",
+                                   RadioSettingValueBoolean(
+                                       strbool(self._mmap.busywait))))
+        try:
+            system.append(RadioSetting("tx_volume", "Transmit volume",
+                          RadioSettingValueList(
+                              map(str, range(1, 7)), self._mmap.tx_volume)))
+            system.append(RadioSetting("rx_volume", "Receive volume",
+                          RadioSettingValueList(
+                              map(str, range(1, 10)), self._mmap.rx_volume)))
+            system.append(RadioSetting("squelch", "Squelch",
+                          RadioSettingValueList(
+                              map(str, range(0, 9)),
+                              str(self._mmap.multiple['squelch']))))
+            system.append(RadioSetting("tx_serial_ui_out", "Tx serial UI out",
+                          RadioSettingValueBoolean(
+                              strbool(self._mmap.tx_serial_ui_out))))
+            system.append(RadioSetting("auto_on", "Auto-on with 5V input",
+                          RadioSettingValueBoolean(
+                              strbool(self._mmap.auto_on[0]))))
+            system.append(RadioSetting(
+                              "auto_on_delay",
+                              "Auto-off delay after 5V lost (seconds)",
+                              RadioSettingValueInteger(
+                                  0, 9999, int(self._mmap.auto_on[1:]))
+            ))
+            system.append(RadioSetting("tfx", "TF/X",
+                          RadioSettingValueBoolean(
+                              self._mmap.multiple['tfx'])))
+            system.append(RadioSetting("blueled", "Light blue LED on GPS lock",
+                          RadioSettingValueBoolean(
+                              self._mmap.multiple['blueled'])))
+            system.append(RadioSetting("dcd", "Blue LED shows software DCD",
+                          RadioSettingValueBoolean(
+                              self._mmap.multiple['dcd'])))
+            system.append(RadioSetting("tf_card", "TF card format",
+                          RadioSettingValueList(
+                              TF_CARD,
+                              TF_CARD[int(self._mmap.multiple['tf_card'])])))
+        except NotImplementedError:
+            pass
+
+        return settings
+
+    def set_settings(self, settings):
+        for setting in settings:
+            if not isinstance(setting, RadioSetting):
+                self.set_settings(setting)
+                continue
+            if not setting.changed():
+                continue
+            try:
+                name = setting.get_name()
+                if name == "callsign":
+                    self.set_callsign(callsign=setting.value)
+                elif name == "ssid":
+                    self.set_callsign(ssid=int(setting.value))
+                elif name == "pttdelay":
+                    self._mmap.pttdelay = PTT_DELAY.index(
+                        str(setting.value)) + 1
+                elif name == "output":
+                    self._mmap.output = OUTPUT.index(str(setting.value)) + 1
+                elif name in ('mice', 'autooff', 'beep', 'highaltitude',
+                              'busywait', 'tx_serial_ui_out'):
+                    setattr(self._mmap, name, boolstr(setting.value))
+                elif name == "mice_message":
+                    multiple = self._mmap.multiple
+                    multiple['mice_message'] = MICE_MESSAGE.index(
+                        str(setting.value))
+                    self._mmap.multiple = multiple
+                elif name == "path":
+                    self._mmap.path = PATH.index(str(setting.value))
+                elif name == "path1":
+                    self._mmap.path1 = "%s%s" % (
+                        setting.value, self._mmap.path1[6])
+                elif name == "ssid1":
+                    self._mmap.path1 = "%s%s" % (
+                        self._mmap.path1[:6], setting.value)
+                elif name == "path2":
+                    self._mmap.path2 = "%s%s" % (
+                        setting.value, self._mmap.path2[6])
+                elif name == "ssid2":
+                    self._mmap.path2 = "%s%s" % (
+                        self._mmap.path2[:6], setting.value)
+                elif name == "path3":
+                    self._mmap.path3 = "%s%s" % (
+                        setting.value, self._mmap.path3[6])
+                elif name == "ssid3":
+                    self._mmap.path3 = "%s%s" % (
+                        self._mmap.path3[:6], setting.value)
+                elif name == "table":
+                    self.set_symbol(table=setting.value)
+                elif name == "symbol":
+                    self.set_symbol(symbol=setting.value)
+                elif name == "beacon":
+                    self._mmap.beacon = BEACON.index(str(setting.value)) + 1
+                elif name == "rate":
+                    self._mmap.rate = "%04d" % setting.value
+                elif name == "comment":
+                    self._mmap.comment = str(setting.value)
+                elif name == "voltage":
+                    multiple = self._mmap.multiple
+                    multiple['voltage'] = int(setting.value)
+                    self._mmap.multiple = multiple
+                elif name == "temperature":
+                    multiple = self._mmap.multiple
+                    multiple['temperature'] = int(setting.value)
+                    self._mmap.multiple = multiple
+                elif name == "status":
+                    self._mmap.status = str(setting.value)
+                elif name in ("telemetry", "telemetry_every",
+                              "timeslot_enable", "timeslot",
+                              "tfx", "blueled", "dcd"):
+                    multiple = self._mmap.multiple
+                    multiple[name] = int(setting.value)
+                    self._mmap.multiple = multiple
+                elif name == "chinamapfix":
+                    self.set_chinamapfix(enable=setting.value)
+                elif name == "chinalat":
+                    self.set_chinamapfix(lat=int(setting.value))
+                elif name == "chinalon":
+                    self.set_chinamapfix(lon=int(setting.value))
+                elif name == "digipeat":
+                    self.set_digipeat(enable=setting.value)
+                elif name == "alias":
+                    self.set_digipeat(
+                        alias=str(ALIAS.index(str(setting.value)) + 1))
+                elif name == "virtualgps":
+                    self.set_virtualgps(enable=setting.value)
+                elif name == "btext":
+                    self.set_virtualgps(btext=str(setting.value))
+                elif name == "lowspeed":
+                    self.set_smartbeacon(lowspeed=int(setting.value))
+                elif name == "highspeed":
+                    self.set_smartbeacon(highspeed=int(setting.value))
+                elif name == "slowrate":
+                    self.set_smartbeacon(slowrate=int(setting.value))
+                elif name == "fastrate":
+                    self.set_smartbeacon(fastrate=int(setting.value))
+                elif name == "turnslope":
+                    self.set_smartbeacon(turnslope=int(setting.value))
+                elif name == "turnangle":
+                    self.set_smartbeacon(turnangle=int(setting.value))
+                elif name == "turntime":
+                    self.set_smartbeacon(turntime=int(setting.value))
+                elif name in ("tx_volume", "rx_volume", "squelch"):
+                    setattr(self._mmap, name, "%1d" % setting.value)
+                elif name == "auto_on":
+                    self._mmap.auto_on = "%s%05d" % (
+                        bool(setting.value) and '1' or 'i',
+                        int(self._mmap.auto_on[1:]))
+                elif name == "auto_on_delay":
+                    self._mmap.auto_on = "%s%05d" % (
+                        self._mmap.auto_on[0], setting.value)
+                elif name == "tf_card":
+                    multiple = self._mmap.multiple
+                    multiple['tf_card'] = TF_CARD.index(str(setting.value))
+                    self._mmap.multiple = multiple
+            except:
+                LOG.debug(setting.get_name())
+                raise
+
+    def set_callsign(self, callsign=None, ssid=None):
+        if callsign is None:
+            callsign = self._mmap.callsign[:6]
+        if ssid is None:
+            ssid = ord(self._mmap.callsign[6]) - 0x30
+        self._mmap.callsign = str(callsign) + chr(ssid + 0x30)
+
+    def set_symbol(self, table=None, symbol=None):
+        if table is None:
+            table = self._mmap.symbol[1]
+        if symbol is None:
+            symbol = self._mmap.symbol[0]
+        self._mmap.symbol = str(symbol) + str(table)
+
+    def set_chinamapfix(self, enable=None, lat=None, lon=None):
+        if enable is None:
+            enable = strbool(self._mmap.chinamapfix[0])
+        if lat is None:
+            lat = ord(self._mmap.chinamapfix[2]) - 80
+        if lon is None:
+            lon = ord(self._mmap.chinamapfix[1]) - 80
+        self._mmapchinamapfix = boolstr(enable) + chr(lon + 80) + chr(lat + 80)
+
+    def set_digipeat(self, enable=None, alias=None):
+        if enable is None:
+            enable = strbool(self._mmap.digipeat[0])
+        if alias is None:
+            alias = self._mmap.digipeat[1]
+        self._mmap.digipeat = boolstr(enable) + alias
+
+    def set_virtualgps(self, enable=None, btext=None):
+        if enable is None:
+            enable = strbool(self._mmap.virtualgps[0])
+        if btext is None:
+            btext = self._mmap.virtualgps[1:]
+        self._mmap.virtualgps = boolstr(enable) + btext
+
+    def set_smartbeacon(self, **kwargs):
+        sb = self._mmap.smartbeacon
+        sb.update(kwargs)
+        if sb['lowspeed'] > sb['highspeed']:
+            raise InvalidValueError("Low speed must be less than high speed")
+        if sb['slowrate'] < sb['fastrate']:
+            raise InvalidValueError("Slow rate must be greater than fast rate")
+        self._mmap.smartbeacon = sb
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return filedata.startswith('\r\n00=' + cls._model)
diff --git a/chirp/baofeng_uv3r.py b/chirp/drivers/baofeng_uv3r.py
similarity index 82%
rename from chirp/baofeng_uv3r.py
rename to chirp/drivers/baofeng_uv3r.py
index c465edc..7c72a96 100644
--- a/chirp/baofeng_uv3r.py
+++ b/chirp/drivers/baofeng_uv3r.py
@@ -17,17 +17,17 @@
 
 import time
 import os
+import logging
+
+from wouxun_common import do_download, do_upload
 from chirp import util, chirp_common, bitwise, errors, directory
-from chirp.wouxun_common import do_download, do_upload
 from chirp.settings import RadioSetting, RadioSettingGroup, \
                 RadioSettingValueBoolean, RadioSettingValueList, \
                 RadioSettingValueInteger, RadioSettingValueString, \
-                RadioSettingValueFloat
+                RadioSettingValueFloat, RadioSettings
+
+LOG = logging.getLogger(__name__)
 
-if os.getenv("CHIRP_DEBUG"):
-    DEBUG = True
-else:
-    DEBUG = False
 
 def _uv3r_prep(radio):
     radio.pipe.write("\x05PROGRAM")
@@ -38,13 +38,14 @@ def _uv3r_prep(radio):
     radio.pipe.write("\x02")
     ident = radio.pipe.read(8)
     if len(ident) != 8:
-        print util.hexprint(ident)
+        LOG.debug(util.hexprint(ident))
         raise errors.RadioError("Radio did not send identification")
 
     radio.pipe.write("\x06")
     if radio.pipe.read(1) != "\x06":
         raise errors.RadioError("Radio did not ACK ident")
 
+
 def uv3r_prep(radio):
     """Do the UV3R identification dance"""
     for _i in range(0, 10):
@@ -55,6 +56,7 @@ def uv3r_prep(radio):
 
     raise e
 
+
 def uv3r_download(radio):
     """Talk to a UV3R and do a download"""
     try:
@@ -65,6 +67,7 @@ def uv3r_download(radio):
     except Exception, e:
         raise errors.RadioError("Failed to communicate with radio: %s" % e)
 
+
 def uv3r_upload(radio):
     """Talk to a UV3R and do an upload"""
     try:
@@ -75,6 +78,7 @@ def uv3r_upload(radio):
     except Exception, e:
         raise errors.RadioError("Failed to communicate with radio: %s" % e)
 
+
 UV3R_MEM_FORMAT = """
 #seekto 0x0010;
 struct {
@@ -188,6 +192,7 @@ UV3R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
                      chirp_common.PowerLevel("Low", watts=0.50)]
 UV3R_DTCS_POL = ["NN", "NR", "RN", "RR"]
 
+
 @directory.register
 class UV3RRadio(chirp_common.CloneModeRadio):
     """Baofeng UV-3R"""
@@ -259,7 +264,7 @@ class UV3RRadio(chirp_common.CloneModeRadio):
             mem.dtcs = tcode
             txmode = "DTCS"
         else:
-            print "Bug: tx_mode is %02x" % _mem.txtone
+            LOG.warn("Bug: tx_mode is %02x" % _mem.txtone)
 
         if _mem.rxtone in [0, 0xFF]:
             rxmode = ""
@@ -271,7 +276,7 @@ class UV3RRadio(chirp_common.CloneModeRadio):
             mem.dtcs = rcode
             rxmode = "DTCS"
         else:
-            print "Bug: rx_mode is %02x" % _mem.rxtone
+            LOG.warn("Bug: rx_mode is %02x" % _mem.rxtone)
 
         if txmode == "Tone" and not rxmode:
             mem.tmode = "Tone"
@@ -357,15 +362,16 @@ class UV3RRadio(chirp_common.CloneModeRadio):
         _settings = self._memobj.settings
         _vfo = self._memobj.vfo
         basic = RadioSettingGroup("basic", "Basic Settings")
-        group = RadioSettingGroup("top", "All Settings", basic)
+        group = RadioSettings(basic)
 
         rs = RadioSetting("squelch", "Squelch Level",
                           RadioSettingValueInteger(0, 9, _settings.squelch))
         basic.append(rs)
 
         rs = RadioSetting("backlight", "LCD Back Light",
-                          RadioSettingValueList(BACKLIGHT_LIST,
-                                        BACKLIGHT_LIST[_settings.backlight]))
+                          RadioSettingValueList(
+                              BACKLIGHT_LIST,
+                              BACKLIGHT_LIST[_settings.backlight]))
         basic.append(rs)
 
         rs = RadioSetting("beep", "Keypad Beep",
@@ -381,8 +387,8 @@ class UV3RRadio(chirp_common.CloneModeRadio):
         basic.append(rs)
 
         rs = RadioSetting("ste", "Squelch Tail Eliminate",
-                          RadioSettingValueList(STE_LIST,
-                                        STE_LIST[_settings.ste]))
+                          RadioSettingValueList(
+                              STE_LIST, STE_LIST[_settings.ste]))
         basic.append(rs)
 
         rs = RadioSetting("save", "Battery Saver",
@@ -390,13 +396,13 @@ class UV3RRadio(chirp_common.CloneModeRadio):
         basic.append(rs)
 
         rs = RadioSetting("timeout", "Time Out Timer",
-                          RadioSettingValueList(TIMEOUT_LIST,
-                                        TIMEOUT_LIST[_settings.timeout]))
+                          RadioSettingValueList(
+                              TIMEOUT_LIST, TIMEOUT_LIST[_settings.timeout]))
         basic.append(rs)
 
         rs = RadioSetting("scanm", "Scan Mode",
-                          RadioSettingValueList(SCANM_LIST,
-                                        SCANM_LIST[_settings.scanm]))
+                          RadioSettingValueList(
+                              SCANM_LIST, SCANM_LIST[_settings.scanm]))
         basic.append(rs)
 
         rs = RadioSetting("relaym", "Repeater Sound Response",
@@ -412,18 +418,21 @@ class UV3RRadio(chirp_common.CloneModeRadio):
         basic.append(rs)
 
         rs = RadioSetting("pri_ch", "Priority Channel",
-                          RadioSettingValueList(PRI_CH_LIST,
-                                        PRI_CH_LIST[_settings.pri_ch]))
+                          RadioSettingValueList(
+                              PRI_CH_LIST, PRI_CH_LIST[_settings.pri_ch]))
         basic.append(rs)
 
         rs = RadioSetting("ch_flag", "Display Mode",
-                          RadioSettingValueList(CH_FLAG_LIST,
-                                        CH_FLAG_LIST[_settings.ch_flag]))
+                          RadioSettingValueList(
+                              CH_FLAG_LIST, CH_FLAG_LIST[_settings.ch_flag]))
         basic.append(rs)
 
         _limit = int(self._memobj.limits.lower_vhf) / 10
+        if _limit < 115 or _limit > 239:
+            _limit = 144
         rs = RadioSetting("limits.lower_vhf", "VHF Lower Limit (115-239 MHz)",
                           RadioSettingValueInteger(115, 235, _limit))
+
         def apply_limit(setting, obj):
             value = int(setting.value) * 10
             obj.lower_vhf = value
@@ -431,8 +440,11 @@ class UV3RRadio(chirp_common.CloneModeRadio):
         basic.append(rs)
 
         _limit = int(self._memobj.limits.upper_vhf) / 10
+        if _limit < 115 or _limit > 239:
+            _limit = 146
         rs = RadioSetting("limits.upper_vhf", "VHF Upper Limit (115-239 MHz)",
                           RadioSettingValueInteger(115, 235, _limit))
+
         def apply_limit(setting, obj):
             value = int(setting.value) * 10
             obj.upper_vhf = value
@@ -440,8 +452,11 @@ class UV3RRadio(chirp_common.CloneModeRadio):
         basic.append(rs)
 
         _limit = int(self._memobj.limits.lower_uhf) / 10
+        if _limit < 200 or _limit > 529:
+            _limit = 420
         rs = RadioSetting("limits.lower_uhf", "UHF Lower Limit (200-529 MHz)",
                           RadioSettingValueInteger(200, 529, _limit))
+
         def apply_limit(setting, obj):
             value = int(setting.value) * 10
             obj.lower_uhf = value
@@ -449,8 +464,11 @@ class UV3RRadio(chirp_common.CloneModeRadio):
         basic.append(rs)
 
         _limit = int(self._memobj.limits.upper_uhf) / 10
+        if _limit < 200 or _limit > 529:
+            _limit = 450
         rs = RadioSetting("limits.upper_uhf", "UHF Upper Limit (200-529 MHz)",
                           RadioSettingValueInteger(200, 529, _limit))
+
         def apply_limit(setting, obj):
             value = int(setting.value) * 10
             obj.upper_uhf = value
@@ -461,95 +479,97 @@ class UV3RRadio(chirp_common.CloneModeRadio):
         group.append(vfo_preset)
 
         def convert_bytes_to_freq(bytes):
-           real_freq = 0
-           real_freq = bytes
-           return chirp_common.format_freq(real_freq * 10)
+            real_freq = 0
+            real_freq = bytes
+            return chirp_common.format_freq(real_freq * 10)
 
         def apply_vhf_freq(setting, obj):
             value = chirp_common.parse_freq(str(setting.value)) / 10
             obj.vhf.freq = value
 
-        val = RadioSettingValueString(0, 10,
-            convert_bytes_to_freq(int(_vfo.vhf.freq)))
+        val = RadioSettingValueString(
+                0, 10, convert_bytes_to_freq(int(_vfo.vhf.freq)))
         rs = RadioSetting("vfo.vhf.freq",
                           "VHF RX Frequency (115.00000-236.00000)", val)
         rs.set_apply_callback(apply_vhf_freq, _vfo)
         vfo_preset.append(rs)
 
         rs = RadioSetting("vfo.vhf.duplex", "Shift Direction",
-                          RadioSettingValueList(DUPLEX_LIST,
-                                        DUPLEX_LIST[_vfo.vhf.duplex]))
+                          RadioSettingValueList(
+                              DUPLEX_LIST, DUPLEX_LIST[_vfo.vhf.duplex]))
         vfo_preset.append(rs)
 
         def convert_bytes_to_offset(bytes):
-           real_offset = 0
-           real_offset = bytes
-           return chirp_common.format_freq(real_offset * 10000)
+            real_offset = 0
+            real_offset = bytes
+            return chirp_common.format_freq(real_offset * 10000)
 
         def apply_vhf_offset(setting, obj):
             value = chirp_common.parse_freq(str(setting.value)) / 10000
             obj.vhf.offset = value
 
-        val = RadioSettingValueString(0, 10,
-            convert_bytes_to_offset(int(_vfo.vhf.offset)))
+        val = RadioSettingValueString(
+                0, 10, convert_bytes_to_offset(int(_vfo.vhf.offset)))
         rs = RadioSetting("vfo.vhf.offset", "Offset (0.00-37.995)", val)
         rs.set_apply_callback(apply_vhf_offset, _vfo)
         vfo_preset.append(rs)
 
         rs = RadioSetting("vfo.vhf.power", "Power Level",
-                          RadioSettingValueList(POWER_LIST,
-                                        POWER_LIST[_vfo.vhf.power]))
+                          RadioSettingValueList(
+                              POWER_LIST, POWER_LIST[_vfo.vhf.power]))
         vfo_preset.append(rs)
 
         rs = RadioSetting("vfo.vhf.bandwidth", "Bandwidth",
-                          RadioSettingValueList(BANDWIDTH_LIST,
-                                        BANDWIDTH_LIST[_vfo.vhf.bandwidth]))
+                          RadioSettingValueList(
+                              BANDWIDTH_LIST,
+                              BANDWIDTH_LIST[_vfo.vhf.bandwidth]))
         vfo_preset.append(rs)
 
         rs = RadioSetting("vfo.vhf.step", "Step",
-                          RadioSettingValueList(STEP_LIST,
-                                        STEP_LIST[_vfo.vhf.step]))
+                          RadioSettingValueList(
+                              STEP_LIST, STEP_LIST[_vfo.vhf.step]))
         vfo_preset.append(rs)
 
         def apply_uhf_freq(setting, obj):
             value = chirp_common.parse_freq(str(setting.value)) / 10
             obj.uhf.freq = value
 
-        val = RadioSettingValueString(0, 10,
-            convert_bytes_to_freq(int(_vfo.uhf.freq)))
+        val = RadioSettingValueString(
+                0, 10, convert_bytes_to_freq(int(_vfo.uhf.freq)))
         rs = RadioSetting("vfo.uhf.freq",
-                               "UHF RX Frequency (200.00000-529.00000)", val)
+                          "UHF RX Frequency (200.00000-529.00000)", val)
         rs.set_apply_callback(apply_uhf_freq, _vfo)
         vfo_preset.append(rs)
 
         rs = RadioSetting("vfo.uhf.duplex", "Shift Direction",
-                          RadioSettingValueList(DUPLEX_LIST,
-                                        DUPLEX_LIST[_vfo.uhf.duplex]))
+                          RadioSettingValueList(
+                              DUPLEX_LIST, DUPLEX_LIST[_vfo.uhf.duplex]))
         vfo_preset.append(rs)
 
         def apply_uhf_offset(setting, obj):
             value = chirp_common.parse_freq(str(setting.value)) / 10000
             obj.uhf.offset = value
 
-        val = RadioSettingValueString(0, 10,
-            convert_bytes_to_offset(int(_vfo.uhf.offset)))
+        val = RadioSettingValueString(
+                0, 10, convert_bytes_to_offset(int(_vfo.uhf.offset)))
         rs = RadioSetting("vfo.uhf.offset", "Offset (0.00-69.995)", val)
         rs.set_apply_callback(apply_uhf_offset, _vfo)
         vfo_preset.append(rs)
 
         rs = RadioSetting("vfo.uhf.power", "Power Level",
-                          RadioSettingValueList(POWER_LIST,
-                                        POWER_LIST[_vfo.uhf.power]))
+                          RadioSettingValueList(
+                              POWER_LIST, POWER_LIST[_vfo.uhf.power]))
         vfo_preset.append(rs)
 
         rs = RadioSetting("vfo.uhf.bandwidth", "Bandwidth",
-                          RadioSettingValueList(BANDWIDTH_LIST,
-                                        BANDWIDTH_LIST[_vfo.uhf.bandwidth]))
+                          RadioSettingValueList(
+                              BANDWIDTH_LIST,
+                              BANDWIDTH_LIST[_vfo.uhf.bandwidth]))
         vfo_preset.append(rs)
 
         rs = RadioSetting("vfo.uhf.step", "Step",
-                          RadioSettingValueList(STEP_LIST,
-                                        STEP_LIST[_vfo.uhf.step]))
+                          RadioSettingValueList(
+                              STEP_LIST, STEP_LIST[_vfo.uhf.step]))
         vfo_preset.append(rs)
 
         fm_preset = RadioSettingGroup("fm_preset", "FM Radio Presets")
@@ -563,8 +583,8 @@ class UV3RRadio(chirp_common.CloneModeRadio):
                 used = False
                 preset = 65
             rs = RadioSetting("fm_presets_%1i" % i, "FM Preset %i" % (i + 1),
-                          RadioSettingValueBoolean(used),
-                          RadioSettingValueFloat(65, 108, preset, 0.1, 1))
+                              RadioSettingValueBoolean(used),
+                              RadioSettingValueFloat(65, 108, preset, 0.1, 1))
             fm_preset.append(rs)
 
         return group
@@ -573,7 +593,7 @@ class UV3RRadio(chirp_common.CloneModeRadio):
         _settings = self._memobj.settings
         for element in settings:
             if not isinstance(element, RadioSetting):
-                if element.get_name() == "fm_preset" :
+                if element.get_name() == "fm_preset":
                     self._set_fm_preset(element)
                 else:
                     self.set_settings(element)
@@ -597,13 +617,13 @@ class UV3RRadio(chirp_common.CloneModeRadio):
                         setting = element.get_name()
 
                     if element.has_apply_callback():
-                        print "Using apply callback"
+                        LOG.debug("Using apply callback")
                         element.run_apply_callback()
                     else:
-                        print "Setting %s = %s" % (setting, element.value)
+                        LOG.debug("Setting %s = %s" % (setting, element.value))
                         setattr(obj, setting, element.value)
                 except Exception, e:
-                    print element.get_name()
+                    LOG.debug(element.get_name())
                     raise
 
     def _set_fm_preset(self, settings):
@@ -615,14 +635,13 @@ class UV3RRadio(chirp_common.CloneModeRadio):
                     value = int(val[1].get_value() * 10 - 650)
                 else:
                     value = 0x01AF
-                print "Setting fm_presets[%1i] = %s" % (index, value)
+                LOG.debug("Setting fm_presets[%1i] = %s" % (index, value))
                 setting = self._memobj.fm_presets
                 setting[index] = value
             except Exception, e:
-                print element.get_name()
+                LOG.debug(element.get_name())
                 raise
 
-
     @classmethod
     def match_model(cls, filedata, filename):
         return len(filedata) == 3648
diff --git a/chirp/drivers/bj9900.py b/chirp/drivers/bj9900.py
new file mode 100644
index 0000000..ef92d64
--- /dev/null
+++ b/chirp/drivers/bj9900.py
@@ -0,0 +1,407 @@
+#
+# Copyright 2015 Marco Filippi IZ3GME <iz3gme.marco 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/>.
+
+"""Baojie BJ-9900 management module"""
+
+from chirp import chirp_common, util, memmap, errors, directory, bitwise
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettingValueString, \
+    RadioSettings
+import struct
+import time
+import logging
+from textwrap import dedent
+
+LOG = logging.getLogger(__name__)
+
+CMD_ACK = 0x06
+
+ at directory.register
+class BJ9900Radio(chirp_common.CloneModeRadio,
+        chirp_common.ExperimentalRadio):
+    """Baojie BJ-9900"""
+    VENDOR = "Baojie"
+    MODEL = "BJ-9900"
+    VARIANT = ""
+    BAUD_RATE = 115200
+
+    DUPLEX = ["", "-", "+", "split"]
+    MODES = ["NFM", "FM"]
+    TMODES = ["", "Tone", "TSQL", "DTCS", "Cross"]
+    CROSS_MODES = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
+        "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
+    STEPS = [5.0, 6.25, 10.0, 12.5, 25.0]
+    VALID_BANDS = [(109000000, 136000000), (136000000, 174000000),
+        (400000000, 470000000)]
+
+    CHARSET = list(chirp_common.CHARSET_ALPHANUMERIC)
+    CHARSET.remove(" ")
+
+    POWER_LEVELS = [
+            chirp_common.PowerLevel("Low", watts=20.00),
+            chirp_common.PowerLevel("High", watts=40.00)]
+
+    _memsize = 0x18F1
+
+    # dat file format is
+    # 2 char per byte hex string
+    # on CR LF terminated lines of 96 char
+    # plus an empty line at the end
+    _datsize = (_memsize * 2) / 96 * 98 + 2
+
+    # block are read in same order as original sw eventhough they are not
+    # in physical order
+    _blocks = [
+        (0x400, 0x1BFF, 0x30),
+        (0x300, 0x32F, 0x30),
+        (0x380, 0x3AF, 0x30),
+        (0x200, 0x22F, 0x30),
+        (0x240, 0x26F, 0x30),
+        (0x270, 0x2A0, 0x31),
+        ]
+
+    MEM_FORMAT = """
+        #seekto 0x%X;
+        struct {
+            u32 rxfreq;
+            u16 is_rxdigtone:1,
+                rxdtcs_pol:1,
+                rxtone:14;
+            u8  rxdtmf:4,
+                spmute:4;
+            u8  unknown1;
+            u32 txfreq;
+            u16 is_txdigtone:1,
+                txdtcs_pol:1,
+                txtone:14;
+            u8  txdtmf:4
+                pttid:4;
+            u8  power:1,
+                wide:1,
+                compandor:1
+                unknown3:5;
+            u8  namelen;
+            u8  name[7];
+        } memory[128];
+    """
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.pre_upload = rp.pre_download = _(dedent("""\
+            1. Turn radio off.
+            2. Remove front head.
+            3. Connect data cable to radio, use the same connector where
+                 head was connected to, <b>not the mic connector</b>.
+            4. Click OK."""))
+        rp.experimental = _(
+         'This is experimental support for BJ-9900 '
+         'which is still under development.\n'
+         'Please ensure you have a good backup with OEM software.\n'
+         'Also please send in bug and enhancement requests!\n'
+         'You have been warned. Proceed at your own risk!')
+        return rp
+
+    def _read(self, addr, blocksize):
+        # read a single block
+        msg = struct.pack(">4sHH", "READ", addr, blocksize)
+        LOG.debug("sending " + util.hexprint(msg))
+        self.pipe.write(msg)
+        block = self.pipe.read(blocksize)
+        LOG.debug("received " + util.hexprint(block))
+        if len(block) != blocksize:
+            raise Exception("Unable to read block at addr %04X expected"
+                            " %i got %i bytes" %
+                            (addr, blocksize, len(block)))
+        return block
+
+    def _clone_in(self):
+        start = time.time()
+
+        data = ""
+        status = chirp_common.Status()
+        status.msg = _("Cloning from radio")
+        status.max = self._memsize
+        for addr_from, addr_to, blocksize in self._blocks:
+            for addr in range(addr_from, addr_to, blocksize):
+                data += self._read(addr, blocksize)
+                status.cur = len(data)
+                self.status_fn(status)
+
+        LOG.info("Clone completed in %i seconds" % (time.time() - start))
+
+        return memmap.MemoryMap(data)
+
+    def _write(self, addr, block):
+        # write a single block
+        msg = struct.pack(">4sHH", "WRIE", addr, len(block)) + block
+        LOG.debug("sending " + util.hexprint(msg))
+        self.pipe.write(msg)
+        data = self.pipe.read(1)
+        LOG.debug("received " + util.hexprint(data))
+        if ord(data) != CMD_ACK:
+            raise errors.RadioError(
+                "Radio refused to accept block 0x%04x" % addr)
+
+    def _clone_out(self):
+        start = time.time()
+
+        status = chirp_common.Status()
+        status.msg = _("Cloning to radio")
+        status.max = self._memsize
+        pos = 0
+        for addr_from, addr_to, blocksize in self._blocks:
+            for addr in range(addr_from, addr_to, blocksize):
+                self._write(addr, self._mmap[pos:(pos + blocksize)])
+                pos += blocksize
+                status.cur = pos
+                self.status_fn(status)
+
+        LOG.info("Clone completed in %i seconds" % (time.time() - start))
+
+    def sync_in(self):
+        try:
+            self._mmap = self._clone_in()
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            raise errors.RadioError("Failed to communicate with radio: %s" % e)
+        self.process_mmap()
+
+    def sync_out(self):
+        try:
+            self._clone_out()
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            raise errors.RadioError("Failed to communicate with radio: %s" % e)
+
+    def process_mmap(self):
+        if len(self._mmap) == self._datsize:
+            self._mmap = memmap.MemoryMap([
+                    chr(int(self._mmap.get(i, 2), 16))
+                    for i in range(0, self._datsize, 2)
+                    if self._mmap.get(i, 2) != "\r\n"
+                    ])
+        try:
+            self._memobj = bitwise.parse(
+                self.MEM_FORMAT % self._memstart, self._mmap)
+        except AttributeError:
+            # main variant have no _memstart attribute
+            return
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.has_bank = False
+        rf.has_dtcs_polarity = True
+        rf.has_nostep_tuning = False
+        rf.valid_modes = list(self.MODES)
+        rf.valid_tmodes = list(self.TMODES)
+        rf.valid_cross_modes = list(self.CROSS_MODES)
+        rf.valid_duplexes = list(self.DUPLEX)
+        rf.has_tuning_step = False
+        # rf.valid_tuning_steps = list(self.STEPS)
+        rf.valid_bands = self.VALID_BANDS
+        rf.valid_skips = [""]
+        rf.valid_power_levels = self.POWER_LEVELS
+        rf.valid_characters = "".join(self.CHARSET)
+        rf.valid_name_length = 7
+        rf.memory_bounds = (1, 128)
+        rf.can_odd_split = True
+        rf.has_settings = False
+        rf.has_cross = True
+        rf.has_ctone = True
+        rf.has_rx_dtcs = True
+        rf.has_sub_devices = self.VARIANT == ""
+
+        return rf
+
+    def get_sub_devices(self):
+        return [BJ9900RadioLeft(self._mmap), BJ9900RadioRight(self._mmap)]
+
+    def get_raw_memory(self, number):
+        return repr(self._memobj.memory[number - 1])
+
+    def set_memory(self, mem):
+        _mem = self._memobj.memory[mem.number - 1]
+
+        if mem.empty:
+            _mem.set_raw("\xff" * (_mem.size() / 8))    # clean up
+            _mem.namelen = 0
+            return
+
+        _mem.rxfreq = mem.freq / 10
+        if mem.duplex == "split":
+            _mem.txfreq = mem.offset / 10
+        elif mem.duplex == "+":
+            _mem.txfreq = (mem.freq + mem.offset) / 10
+        elif mem.duplex == "-":
+            _mem.txfreq = (mem.freq - mem.offset) / 10
+        else:
+            _mem.txfreq = mem.freq / 10
+
+        _mem.namelen = len(mem.name)
+        for i in range(_mem.namelen):
+                _mem.name[i] = ord(mem.name[i])
+
+        rxmode = ""
+        txmode = ""
+
+        if mem.tmode == "Tone":
+            txmode = "Tone"
+        elif mem.tmode == "TSQL":
+            rxmode = "Tone"
+            txmode = "TSQL"
+        elif mem.tmode == "DTCS":
+            rxmode = "DTCSSQL"
+            txmode = "DTCS"
+        elif mem.tmode == "Cross":
+            txmode, rxmode = mem.cross_mode.split("->", 1)
+
+        if rxmode == "":
+            _mem.rxdtcs_pol = 1
+            _mem.is_rxdigtone = 1
+            _mem.rxtone = 0x3FFF
+        elif rxmode == "Tone":
+            _mem.rxdtcs_pol = 0
+            _mem.is_rxdigtone = 0
+            _mem.rxtone = int(mem.ctone * 10)
+        elif rxmode == "DTCSSQL":
+            _mem.rxdtcs_pol = 1 if mem.dtcs_polarity[1] == "R" else 0
+            _mem.is_rxdigtone = 1
+            _mem.rxtone = mem.dtcs
+        elif rxmode == "DTCS":
+            _mem.rxdtcs_pol = 1 if mem.dtcs_polarity[1] == "R" else 0
+            _mem.is_rxdigtone = 1
+            _mem.rxtone = mem.rx_dtcs
+
+        if txmode == "":
+            _mem.txdtcs_pol = 1
+            _mem.is_txdigtone = 1
+            _mem.txtone = 0x3FFF
+        elif txmode == "Tone":
+            _mem.txdtcs_pol = 0
+            _mem.is_txdigtone = 0
+            _mem.txtone = int(mem.rtone * 10)
+        elif txmode == "TSQL":
+            _mem.txdtcs_pol = 0
+            _mem.is_txdigtone = 0
+            _mem.txtone = int(mem.ctone * 10)
+        elif txmode == "DTCS":
+            _mem.txdtcs_pol = 1 if mem.dtcs_polarity[0] == "R" else 0
+            _mem.is_txdigtone = 1
+            _mem.txtone = mem.dtcs
+
+        if (mem.power):
+            _mem.power = self.POWER_LEVELS.index(mem.power)
+        _mem.wide = self.MODES.index(mem.mode)
+
+        # not supported yet
+        _mem.compandor = 0
+        _mem.pttid = 0
+        _mem.txdtmf = 0
+        _mem.rxdtmf = 0
+        _mem.spmute = 0
+
+        # set to mimic radio behaviour
+        _mem.unknown3 = 0
+
+    def get_memory(self, number):
+        _mem = self._memobj.memory[number - 1]
+
+        mem = chirp_common.Memory()
+        mem.number = number
+
+        if _mem.get_raw()[0] == "\xff":
+            mem.empty = True
+            return mem
+
+        mem.freq = int(_mem.rxfreq) * 10
+
+        if int(_mem.rxfreq) == int(_mem.txfreq) or _mem.txfreq == 0xFFFFFFFF:
+            mem.duplex = ""
+            mem.offset = 0
+        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
+            mem.duplex = "split"
+            mem.offset = int(_mem.txfreq) * 10
+        else:
+            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
+            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
+
+        for char in _mem.name[:_mem.namelen]:
+            mem.name += chr(char)
+
+        dtcs_pol = ["N", "N"]
+
+        if _mem.rxtone == 0x3FFF:
+            rxmode = ""
+        elif _mem.is_rxdigtone == 0:
+            # ctcss
+            rxmode = "Tone"
+            mem.ctone = int(_mem.rxtone) / 10.0
+        else:
+            # digital
+            rxmode = "DTCS"
+            mem.rx_dtcs = int(_mem.rxtone & 0x3FFF)
+            if _mem.rxdtcs_pol == 1:
+                dtcs_pol[1] = "R"
+
+        if _mem.txtone == 0x3FFF:
+            txmode = ""
+        elif _mem.is_txdigtone == 0:
+            # ctcss
+            txmode = "Tone"
+            mem.rtone = int(_mem.txtone) / 10.0
+        else:
+            # digital
+            txmode = "DTCS"
+            mem.dtcs = int(_mem.txtone & 0x3FFF)
+            if _mem.txdtcs_pol == 1:
+                dtcs_pol[0] = "R"
+
+        if txmode == "Tone" and not rxmode:
+            mem.tmode = "Tone"
+        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
+            mem.tmode = "TSQL"
+        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
+            mem.tmode = "DTCS"
+        elif rxmode or txmode:
+            mem.tmode = "Cross"
+            mem.cross_mode = "%s->%s" % (txmode, rxmode)
+
+        mem.dtcs_polarity = "".join(dtcs_pol)
+
+        mem.power = self.POWER_LEVELS[_mem.power]
+        mem.mode = self.MODES[_mem.wide]
+
+        return mem
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return len(filedata) == cls._memsize or \
+            (len(filedata) == cls._datsize and filedata[-4:] == "\r\n\r\n")
+
+class BJ9900RadioLeft(BJ9900Radio):
+    """Baojie BJ-9900 Left VFO subdevice"""
+    VARIANT = "Left"
+    _memstart = 0x0
+
+
+class BJ9900RadioRight(BJ9900Radio):
+    """Baojie BJ-9900 Right VFO subdevice"""
+    VARIANT = "Right"
+    _memstart = 0xC00
diff --git a/chirp/bjuv55.py b/chirp/drivers/bjuv55.py
similarity index 75%
rename from chirp/bjuv55.py
rename to chirp/drivers/bjuv55.py
index 4f7f5ad..fc9f43c 100644
--- a/chirp/bjuv55.py
+++ b/chirp/drivers/bjuv55.py
@@ -18,22 +18,21 @@
 import struct
 import time
 import os
+import logging
 
+from chirp.drivers import uv5r
 from chirp import chirp_common, errors, util, directory, memmap
 from chirp import bitwise
 from chirp.settings import RadioSetting, RadioSettingGroup, \
     RadioSettingValueInteger, RadioSettingValueList, \
     RadioSettingValueBoolean, RadioSettingValueString, \
-    RadioSettingValueFloat, InvalidValueError
+    RadioSettingValueFloat, InvalidValueError, RadioSettings
 from textwrap import dedent
-from chirp import uv5r
 
-if os.getenv("CHIRP_DEBUG"):
-    CHIRP_DEBUG = True
-else:
-    CHIRP_DEBUG = False
+LOG = logging.getLogger(__name__)
 
-BJUV55_MODEL   = "\x50\xBB\xDD\x55\x63\x98\x4D"
+
+BJUV55_MODEL = "\x50\xBB\xDD\x55\x63\x98\x4D"
 
 COLOR_LIST = ["Off", "Blue", "Red", "Pink"]
 
@@ -96,11 +95,11 @@ struct {
   u8 step;
   u8 tdrab;
   u8 tdr;
-  u8 vox;	
+  u8 vox;
   u8 timeout;
   u8 unk2[6];
   u8 abr;
-  u8 beep; 
+  u8 beep;
   u8 ani;
   u8 unknown3[2];
   u8 voice;
@@ -233,23 +232,23 @@ struct {
 
 """
 
+
 @directory.register
 class BaojieBJUV55Radio(uv5r.BaofengUV5R):
     VENDOR = "Baojie"
     MODEL = "BJ-UV55"
-    #_basetype = "BJ55"
     _basetype = ["BJ55"]
-    _idents = [ BJUV55_MODEL ]
-    _mem_params = ( 0x1928  # poweron_msg offset
-                    )
+    _idents = [BJUV55_MODEL]
+    _mem_params = (0x1928  # poweron_msg offset
+                   )
     _fw_ver_file_start = 0x1938
     _fw_ver_file_stop = 0x193E
-    
+
     def get_features(self):
         rf = super(BaojieBJUV55Radio, self).get_features()
         rf.valid_name_length = 6
         return rf
-    
+
     def process_mmap(self):
         self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
 
@@ -265,12 +264,11 @@ class BaojieBJUV55Radio(uv5r.BaofengUV5R):
         else:
             _mem.txtoneicon = False
 
-
     def _get_settings(self):
         _settings = self._memobj.settings
         basic = RadioSettingGroup("basic", "Basic Settings")
         advanced = RadioSettingGroup("advanced", "Advanced Settings")
-        group = RadioSettingGroup("top", "All Settings", basic, advanced)
+        group = RadioSettings(basic, advanced)
 
         rs = RadioSetting("squelch", "Carrier Squelch Level",
                           RadioSettingValueInteger(0, 9, _settings.squelch))
@@ -289,8 +287,9 @@ class BaojieBJUV55Radio(uv5r.BaofengUV5R):
         advanced.append(rs)
 
         rs = RadioSetting("tdrab", "Dual Watch TX Priority",
-                          RadioSettingValueList(uv5r.TDRAB_LIST,
-                          uv5r.TDRAB_LIST[_settings.tdrab]))
+                          RadioSettingValueList(
+                              uv5r.TDRAB_LIST,
+                              uv5r.TDRAB_LIST[_settings.tdrab]))
         advanced.append(rs)
 
         rs = RadioSetting("alarm", "Alarm",
@@ -302,23 +301,25 @@ class BaojieBJUV55Radio(uv5r.BaofengUV5R):
         basic.append(rs)
 
         rs = RadioSetting("timeout", "Timeout Timer",
-                          RadioSettingValueList(uv5r.TIMEOUT_LIST,
-                          uv5r.TIMEOUT_LIST[_settings.timeout]))
+                          RadioSettingValueList(
+                              uv5r.TIMEOUT_LIST,
+                              uv5r.TIMEOUT_LIST[_settings.timeout]))
         basic.append(rs)
 
         rs = RadioSetting("screv", "Scan Resume",
-                          RadioSettingValueList(uv5r.RESUME_LIST,
-                          uv5r.RESUME_LIST[_settings.screv]))
+                          RadioSettingValueList(
+                              uv5r.RESUME_LIST,
+                              uv5r.RESUME_LIST[_settings.screv]))
         advanced.append(rs)
 
         rs = RadioSetting("mdfa", "Display Mode (A)",
-                          RadioSettingValueList(uv5r.MODE_LIST,
-                          uv5r.MODE_LIST[_settings.mdfa]))
+                          RadioSettingValueList(
+                              uv5r.MODE_LIST, uv5r.MODE_LIST[_settings.mdfa]))
         basic.append(rs)
 
         rs = RadioSetting("mdfb", "Display Mode (B)",
-                          RadioSettingValueList(uv5r.MODE_LIST,
-                          uv5r.MODE_LIST[_settings.mdfb]))
+                          RadioSettingValueList(
+                              uv5r.MODE_LIST, uv5r.MODE_LIST[_settings.mdfb]))
         basic.append(rs)
 
         rs = RadioSetting("bcl", "Busy Channel Lockout",
@@ -334,18 +335,18 @@ class BaojieBJUV55Radio(uv5r.BaofengUV5R):
         advanced.append(rs)
 
         rs = RadioSetting("wtled", "Standby LED Color",
-                          RadioSettingValueList(COLOR_LIST,
-                          COLOR_LIST[_settings.wtled]))
+                          RadioSettingValueList(
+                              COLOR_LIST, COLOR_LIST[_settings.wtled]))
         basic.append(rs)
 
         rs = RadioSetting("rxled", "RX LED Color",
-                          RadioSettingValueList(COLOR_LIST,
-                          COLOR_LIST[_settings.rxled]))
+                          RadioSettingValueList(
+                              COLOR_LIST, COLOR_LIST[_settings.rxled]))
         basic.append(rs)
 
         rs = RadioSetting("txled", "TX LED Color",
-                          RadioSettingValueList(COLOR_LIST,
-                          COLOR_LIST[_settings.txled]))
+                          RadioSettingValueList(
+                              COLOR_LIST, COLOR_LIST[_settings.txled]))
         basic.append(rs)
 
         rs = RadioSetting("reset", "RESET Menu",
@@ -356,7 +357,6 @@ class BaojieBJUV55Radio(uv5r.BaofengUV5R):
                           RadioSettingValueBoolean(_settings.menu))
         advanced.append(rs)
 
-
         if len(self._mmap.get_packed()) == 0x1808:
             # Old image, without aux block
             return group
@@ -384,13 +384,13 @@ class BaojieBJUV55Radio(uv5r.BaofengUV5R):
         limit = "limits"
         vhf_limit = getattr(self._memobj, limit).vhf
         rs = RadioSetting("%s.vhf.lower" % limit, "VHF Lower Limit (MHz)",
-                          RadioSettingValueInteger(1, 1000,
-                                                   vhf_limit.lower))
+                          RadioSettingValueInteger(
+                              1, 1000, vhf_limit.lower))
         other.append(rs)
 
         rs = RadioSetting("%s.vhf.upper" % limit, "VHF Upper Limit (MHz)",
-                          RadioSettingValueInteger(1, 1000,
-                                                   vhf_limit.upper))
+                          RadioSettingValueInteger(
+                              1, 1000, vhf_limit.upper))
         other.append(rs)
 
         rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled",
@@ -399,12 +399,12 @@ class BaojieBJUV55Radio(uv5r.BaofengUV5R):
 
         uhf_limit = getattr(self._memobj, limit).uhf
         rs = RadioSetting("%s.uhf.lower" % limit, "UHF Lower Limit (MHz)",
-                          RadioSettingValueInteger(1, 1000,
-                                                   uhf_limit.lower))
+                          RadioSettingValueInteger(
+                              1, 1000, uhf_limit.lower))
         other.append(rs)
         rs = RadioSetting("%s.uhf.upper" % limit, "UHF Upper Limit (MHz)",
-                          RadioSettingValueInteger(1, 1000,
-                                                   uhf_limit.upper))
+                          RadioSettingValueInteger(
+                              1, 1000, uhf_limit.upper))
         other.append(rs)
         rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled",
                           RadioSettingValueBoolean(uhf_limit.enable))
@@ -415,14 +415,14 @@ class BaojieBJUV55Radio(uv5r.BaofengUV5R):
 
         options = ["A", "B"]
         rs = RadioSetting("displayab", "Display Selected",
-                          RadioSettingValueList(options,
-                                                options[_settings.displayab]))
+                          RadioSettingValueList(
+                              options, options[_settings.displayab]))
         workmode.append(rs)
 
         options = ["Frequency", "Channel"]
         rs = RadioSetting("workmode", "VFO/MR Mode",
-                          RadioSettingValueList(options,
-                                                options[_settings.workmode]))
+                          RadioSettingValueList(
+                              options, options[_settings.workmode]))
         workmode.append(rs)
 
         rs = RadioSetting("keylock", "Keypad Lock",
@@ -440,10 +440,10 @@ class BaojieBJUV55Radio(uv5r.BaofengUV5R):
         workmode.append(rs)
 
         def convert_bytes_to_freq(bytes):
-           real_freq = 0
-           for byte in bytes:
-               real_freq = (real_freq * 10) + byte
-           return chirp_common.format_freq(real_freq * 10)
+            real_freq = 0
+            for byte in bytes:
+                real_freq = (real_freq * 10) + byte
+            return chirp_common.format_freq(real_freq * 10)
 
         def my_validate(value):
             value = chirp_common.parse_freq(value)
@@ -458,15 +458,15 @@ class BaojieBJUV55Radio(uv5r.BaofengUV5R):
                 obj.freq[i] = value % 10
                 value /= 10
 
-        val1a = RadioSettingValueString(0, 10,
-            convert_bytes_to_freq(self._memobj.vfoa.freq))
+        val1a = RadioSettingValueString(
+                0, 10, convert_bytes_to_freq(self._memobj.vfoa.freq))
         val1a.set_validate_callback(my_validate)
         rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a)
         rs.set_apply_callback(apply_freq, self._memobj.vfoa)
         workmode.append(rs)
 
-        val1b = RadioSettingValueString(0, 10,
-            convert_bytes_to_freq(self._memobj.vfob.freq))
+        val1b = RadioSettingValueString(
+                0, 10, convert_bytes_to_freq(self._memobj.vfob.freq))
         val1b.set_validate_callback(my_validate)
         rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b)
         rs.set_apply_callback(apply_freq, self._memobj.vfob)
@@ -474,20 +474,20 @@ class BaojieBJUV55Radio(uv5r.BaofengUV5R):
 
         options = ["Off", "+", "-"]
         rs = RadioSetting("vfoa.sftd", "VFO A Shift",
-                          RadioSettingValueList(options,
-                                                options[self._memobj.vfoa.sftd]))
+                          RadioSettingValueList(
+                              options, options[self._memobj.vfoa.sftd]))
         workmode.append(rs)
 
         rs = RadioSetting("vfob.sftd", "VFO B Shift",
-                          RadioSettingValueList(options,
-                                                options[self._memobj.vfob.sftd]))
+                          RadioSettingValueList(
+                              options, options[self._memobj.vfob.sftd]))
         workmode.append(rs)
 
         def convert_bytes_to_offset(bytes):
-           real_offset = 0
-           for byte in bytes:
-               real_offset = (real_offset * 10) + byte
-           return chirp_common.format_freq(real_offset * 10000)
+            real_offset = 0
+            for byte in bytes:
+                real_offset = (real_offset * 10) + byte
+            return chirp_common.format_freq(real_offset * 10000)
 
         def apply_offset(setting, obj):
             value = chirp_common.parse_freq(str(setting.value)) / 10000
@@ -495,68 +495,66 @@ class BaojieBJUV55Radio(uv5r.BaofengUV5R):
                 obj.offset[i] = value % 10
                 value /= 10
 
-        val1a = RadioSettingValueString(0, 10,
-            convert_bytes_to_offset(self._memobj.vfoa.offset))
+        val1a = RadioSettingValueString(
+                0, 10, convert_bytes_to_offset(self._memobj.vfoa.offset))
         rs = RadioSetting("vfoa.offset", "VFO A Offset (0.00-69.95)", val1a)
         rs.set_apply_callback(apply_offset, self._memobj.vfoa)
         workmode.append(rs)
 
-        val1b = RadioSettingValueString(0, 10,
-            convert_bytes_to_offset(self._memobj.vfob.offset))
+        val1b = RadioSettingValueString(
+                0, 10, convert_bytes_to_offset(self._memobj.vfob.offset))
         rs = RadioSetting("vfob.offset", "VFO B Offset (0.00-69.95)", val1b)
         rs.set_apply_callback(apply_offset, self._memobj.vfob)
         workmode.append(rs)
 
         options = ["High", "Low"]
         rs = RadioSetting("vfoa.txpower", "VFO A Power",
-                          RadioSettingValueList(options,
-                                                options[self._memobj.vfoa.txpower]))
+                          RadioSettingValueList(
+                              options, options[self._memobj.vfoa.txpower]))
         workmode.append(rs)
 
         rs = RadioSetting("vfob.txpower", "VFO B Power",
-                          RadioSettingValueList(options,
-                                                options[self._memobj.vfob.txpower]))
+                          RadioSettingValueList(
+                              options, options[self._memobj.vfob.txpower]))
         workmode.append(rs)
 
         options = ["Wide", "Narrow"]
         rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth",
-                          RadioSettingValueList(options,
-                                                options[self._memobj.vfoa.widenarr]))
+                          RadioSettingValueList(
+                              options, options[self._memobj.vfoa.widenarr]))
         workmode.append(rs)
 
         rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
-                          RadioSettingValueList(options,
-                                                options[self._memobj.vfob.widenarr]))
+                          RadioSettingValueList(
+                              options, options[self._memobj.vfob.widenarr]))
         workmode.append(rs)
 
         options = ["%s" % x for x in range(1, 16)]
         rs = RadioSetting("vfoa.scode", "VFO A PTT-ID",
-                          RadioSettingValueList(options,
-                                                options[self._memobj.vfoa.scode]))
+                          RadioSettingValueList(
+                              options, options[self._memobj.vfoa.scode]))
         workmode.append(rs)
 
         rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
-                          RadioSettingValueList(options,
-                                                options[self._memobj.vfob.scode]))
+                          RadioSettingValueList(
+                              options, options[self._memobj.vfob.scode]))
         workmode.append(rs)
 
-
         rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
-                          RadioSettingValueList(STEP_LIST,
-                          STEP_LIST[self._memobj.vfoa.step]))
+                          RadioSettingValueList(
+                              STEP_LIST, STEP_LIST[self._memobj.vfoa.step]))
         workmode.append(rs)
         rs = RadioSetting("vfob.step", "VFO B Tuning Step",
-                          RadioSettingValueList(STEP_LIST,
-                          STEP_LIST[self._memobj.vfob.step]))
+                          RadioSettingValueList(
+                              STEP_LIST, STEP_LIST[self._memobj.vfob.step]))
         workmode.append(rs)
 
-
         fm_preset = RadioSettingGroup("fm_preset", "FM Radio Preset")
         group.append(fm_preset)
 
         preset = self._memobj.fm_preset / 10.0 + 87
         rs = RadioSetting("fm_preset", "FM Preset(MHz)",
-                      RadioSettingValueFloat(87, 107.5, preset, 0.1, 1))
+                          RadioSettingValueFloat(87, 107.5, preset, 0.1, 1))
         fm_preset.append(rs)
 
         dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
@@ -568,7 +566,9 @@ class BaojieBJUV55Radio(uv5r.BaofengUV5R):
             _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
             val = RadioSettingValueString(0, 5, _code, False)
             val.set_charset(dtmfchars)
-            rs = RadioSetting("pttid/%i.code" % i, "PTT ID Code %i" % (i + 1), val)
+            rs = RadioSetting("pttid/%i.code" % i,
+                              "PTT ID Code %i" % (i + 1), val)
+
             def apply_code(setting, obj):
                 code = []
                 for j in range(0, 5):
@@ -585,6 +585,7 @@ class BaojieBJUV55Radio(uv5r.BaofengUV5R):
         val = RadioSettingValueString(0, 5, _code, False)
         val.set_charset(dtmfchars)
         rs = RadioSetting("ani.code", "ANI Code", val)
+
         def apply_code(setting, obj):
             code = []
             for j in range(0, 5):
@@ -598,8 +599,8 @@ class BaojieBJUV55Radio(uv5r.BaofengUV5R):
 
         options = ["Off", "BOT", "EOT", "Both"]
         rs = RadioSetting("ani.aniid", "ANI ID",
-                          RadioSettingValueList(options,
-                                                options[self._memobj.ani.aniid]))
+                          RadioSettingValueList(
+                              options, options[self._memobj.ani.aniid]))
         dtmf.append(rs)
 
         _codeobj = self._memobj.ani.alarmcode
@@ -607,6 +608,7 @@ class BaojieBJUV55Radio(uv5r.BaofengUV5R):
         val = RadioSettingValueString(0, 5, _code, False)
         val.set_charset(dtmfchars)
         rs = RadioSetting("ani.alarmcode", "Alarm Code", val)
+
         def apply_code(setting, obj):
             alarmcode = []
             for j in range(5):
@@ -619,18 +621,21 @@ class BaojieBJUV55Radio(uv5r.BaofengUV5R):
         dtmf.append(rs)
 
         rs = RadioSetting("dtmfst", "DTMF Sidetone",
-                          RadioSettingValueList(uv5r.DTMFST_LIST,
-                          uv5r.DTMFST_LIST[_settings.dtmfst]))
+                          RadioSettingValueList(
+                              uv5r.DTMFST_LIST,
+                              uv5r.DTMFST_LIST[_settings.dtmfst]))
         dtmf.append(rs)
 
         rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
-                          RadioSettingValueList(uv5r.DTMFSPEED_LIST,
-                          uv5r.DTMFSPEED_LIST[self._memobj.ani.dtmfon]))
+                          RadioSettingValueList(
+                              uv5r.DTMFSPEED_LIST,
+                              uv5r.DTMFSPEED_LIST[self._memobj.ani.dtmfon]))
         dtmf.append(rs)
 
         rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
-                          RadioSettingValueList(uv5r.DTMFSPEED_LIST,
-                          uv5r.DTMFSPEED_LIST[self._memobj.ani.dtmfoff]))
+                          RadioSettingValueList(
+                              uv5r.DTMFSPEED_LIST,
+                              uv5r.DTMFSPEED_LIST[self._memobj.ani.dtmfoff]))
         dtmf.append(rs)
 
         return group
@@ -640,8 +645,8 @@ class BaojieBJUV55Radio(uv5r.BaofengUV5R):
             try:
                 val = element.value
                 value = int(val.get_value() * 10 - 870)
-                print "Setting fm_preset = %s" % (value)
+                LOG.debug("Setting fm_preset = %s" % (value))
                 self._memobj.fm_preset = value
             except Exception, e:
-                print element.get_name()
+                LOG.debug(element.get_name())
                 raise
diff --git a/chirp/ft1802.py b/chirp/drivers/ft1802.py
similarity index 88%
rename from chirp/ft1802.py
rename to chirp/drivers/ft1802.py
index af83a51..952a23c 100644
--- a/chirp/ft1802.py
+++ b/chirp/drivers/ft1802.py
@@ -23,9 +23,10 @@
 # 4. Press the [D/MR(MW)] key ("--WAIT--" will appear on the LCD).
 # 5. In Chirp, choose Upload to Radio.
 
-from chirp import chirp_common, bitwise, directory, yaesu_clone
+from chirp.drivers import yaesu_clone
+from chirp import chirp_common, bitwise, directory
 from chirp.settings import RadioSetting, RadioSettingGroup, \
-    RadioSettingValueBoolean
+    RadioSettingValueBoolean, RadioSettings
 from textwrap import dedent
 
 MEM_FORMAT = """
@@ -93,15 +94,15 @@ class FT1802Radio(yaesu_clone.YaesuCloneModeRadio):
     def get_prompts(cls):
         rp = chirp_common.RadioPrompts()
         rp.pre_download = _(dedent("""\
-            1. Turn radio off.
-            2. Connect cable to mic jack.
-            3. Press and hold in the [LOW(A/N)] key while turning the radio on.
-            4. <b>After clicking OK</b>, press the [MHz(SET)] key to send image."""))
+1. Turn radio off.
+2. Connect cable to mic jack.
+3. Press and hold in the [LOW(A/N)] key while turning the radio on.
+4. <b>After clicking OK</b>, press the [MHz(SET)] key to send image."""))
         rp.pre_upload = _(dedent("""\
-            1. Turn radio off.
-            2. Connect cable to mic jack.
-            3. Press and hold in the [LOW(A/N)] key while turning the radio on.
-            4. Press the [D/MR(MW)] key ("--WAIT--" will appear on the LCD)."""))
+1. Turn radio off.
+2. Connect cable to mic jack.
+3. Press and hold in the [LOW(A/N)] key while turning the radio on.
+4. Press the [D/MR(MW)] key ("--WAIT--" will appear on the LCD)."""))
         return rp
 
     def get_features(self):
@@ -112,8 +113,8 @@ class FT1802Radio(yaesu_clone.YaesuCloneModeRadio):
         rf.can_odd_split = True
         rf.has_ctone = False
         rf.has_tuning_step = True
-        rf.has_dtcs_polarity = False # in radio settings, not per memory
-        rf.has_bank = False # has banks, but not implemented
+        rf.has_dtcs_polarity = False  # in radio settings, not per memory
+        rf.has_bank = False  # has banks, but not implemented
 
         rf.valid_tuning_steps = STEPS
         rf.valid_modes = MODES
@@ -161,7 +162,8 @@ class FT1802Radio(yaesu_clone.YaesuCloneModeRadio):
         mem.freq = chirp_common.fix_rounded_step(int(_mem.freq) * 1000)
         mem.offset = chirp_common.fix_rounded_step(int(_mem.offset) * 1000)
         mem.duplex = DUPLEX[_mem.duplex]
-        mem.tuning_step = _mem.step_changed and STEPS[_mem.tune_step] or STEPS[0]
+        mem.tuning_step = _mem.step_changed and \
+            STEPS[_mem.tune_step] or STEPS[0]
         if _mem.tmode < TMODES.index("Cross"):
             mem.tmode = TMODES[_mem.tmode]
             mem.cross_mode = CROSS_MODES[0]
@@ -195,7 +197,7 @@ class FT1802Radio(yaesu_clone.YaesuCloneModeRadio):
         _flag = self._memobj.flags[mem.number/2]
 
         nibble = (mem.number % 2) and "odd" or "even"
-        
+
         valid = _flag["%s_valid" % nibble]
         visible = _flag["%s_visible" % nibble]
 
@@ -227,7 +229,8 @@ class FT1802Radio(yaesu_clone.YaesuCloneModeRadio):
         if mem.tmode != "Cross":
             _mem.tmode = TMODES.index(mem.tmode)
         else:
-            _mem.tmode = TMODES.index("Cross") + CROSS_MODES.index(mem.cross_mode)
+            _mem.tmode = TMODES.index("Cross") + \
+                         CROSS_MODES.index(mem.cross_mode)
         _mem.tone = chirp_common.TONES.index(mem.rtone)
         _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
 
@@ -238,7 +241,7 @@ class FT1802Radio(yaesu_clone.YaesuCloneModeRadio):
             except IndexError:
                 raise Exception("Character `%s' not supported")
         if _mem.name[0] != 0xFF:
-            _mem.name[0] += 0x80 # show name instead of frequency
+            _mem.name[0] += 0x80  # show name instead of frequency
 
         _mem.narrow = MODES.index(mem.mode)
         _mem.power = 3 if mem.power is None else POWER_LEVELS.index(mem.power)
diff --git a/chirp/vx8.py b/chirp/drivers/ft1d.py
similarity index 66%
copy from chirp/vx8.py
copy to chirp/drivers/ft1d.py
index ceae189..8616d3c 100644
--- a/chirp/vx8.py
+++ b/chirp/drivers/ft1d.py
@@ -1,4 +1,5 @@
 # Copyright 2010 Dan Smith <dsmith at danplanet.com>
+# Copyright 2014 Angus Ainslie <angus at akkea.ca>
 #
 # 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
@@ -13,23 +14,60 @@
 # 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 os
 import re
+import string
+import logging
 
-from chirp import chirp_common, yaesu_clone, directory
-from chirp import bitwise
-from chirp.settings import RadioSettingGroup, RadioSetting
+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 textwrap import dedent
 
+LOG = logging.getLogger(__name__)
+
 MEM_FORMAT = """
-#seekto 0x54a;
+#seekto 0x049a;
 struct {
-    u16 in_use;
-} bank_used[24];
+  u8 vfo_a;
+  u8 vfo_b;
+} squelch;
 
-#seekto 0x64a;
+#seekto 0x04c1;
+struct {
+  u8 beep;
+} beep_select;
+
+#seekto 0x04ce;
+struct {
+  u8 lcd_dimmer;
+  u8 dtmf_delay;
+  u8 unknown0[3];
+  u8 unknown1:4
+     lcd_contrast:4;
+  u8 lamp;
+  u8 unknown2[7];
+  u8 scan_restart;
+  u8 unknown3;
+  u8 scan_resume;
+  u8 unknown4[5];
+  u8 tot;
+  u8 unknown5[3];
+  u8 unknown6:2,
+     scan_lamp:1,
+     unknown7:2,
+     dtmf_speed:1,
+     unknown8:1,
+     dtmf_mode:1;
+  u8 busy_led:1,
+     unknown9:7;
+  u8 unknown10[2];
+  u8 vol_mode:1,
+     unknown11:7;
+} scan_settings;
+
+#seekto 0x064a;
 struct {
   u8 unknown0[4];
   u8 frequency_band;
@@ -73,28 +111,38 @@ struct {
   u8 checksum;
 } vfo_info[6];
 
-#seekto 0x135A;
+#seekto 0x047e;
 struct {
-  u8 unknown[2];
-  u8 name[16];
-} bank_info[24];
+  u8 unknown1;
+  u8 flag;
+  u16 unknown2;
+  struct {
+    u8 padded_yaesu[16];
+  } message;
+} opening_message;
+
+#seekto 0x0e4a;
+struct {
+  u8 memory[16];
+} dtmf[10];
 
-#seekto 0x198a;
+#seekto 0x154a;
 struct {
     u16 channel[100];
 } bank_members[24];
 
-#seekto 0x2C4A;
+#seekto 0x54a;
 struct {
-  u8 nosubvfo:1,
-     unknown:3,
-     pskip:1,
-     skip:1,
-     used:1,
-     valid:1;
-} flag[900];
+    u16 in_use;
+} bank_used[24];
+
+#seekto 0x0EFE;
+struct {
+  u8 unknown[2];
+  u8 name[16];
+} bank_info[24];
 
-#seekto 0x328A;
+#seekto 0x2D4A;
 struct {
   u8 unknown1;
   u8 mode:2,
@@ -114,14 +162,20 @@ struct {
   u8 unknown7[3];
 } memory[900];
 
-#seekto 0xC0CA;
+#seekto 0x280A;
 struct {
-  u8 unknown0:6,
-     rx_baud:2;
-  u8 unknown1:4,
-     tx_delay:4;
+  u8 nosubvfo:1,
+     unknown:3,
+     pskip:1,
+     skip:1,
+     used:1,
+     valid:1;
+} flag[900];
+
+#seekto 0xbeca;
+struct {
+  u8 rx_baud;
   u8 custom_symbol;
-  u8 unknown2;
   struct {
     char callsign[6];
     u8 ssid;
@@ -155,10 +209,8 @@ struct {
      filter_weather:1,
      filter_position:1,
      filter_mic_e:1;
-  u8 unknown12:2,
-     timezone:6;
-  u8 unknown13:4,
-     beacon_interval:4;
+  u8 unknown12;
+  u8 unknown13;
   u8 unknown14;
   u8 unknown15:7,
      latitude_sign:1;
@@ -174,19 +226,23 @@ struct {
      selected_position:4;
   u8 unknown18:5,
      selected_beacon_status_txt:3;
-  u8 unknown19:6,
+  u8 unknown19:4,
+     beacon_interval:4;
+  u8 unknowni21:4,
+       tx_delay:4;
+  u8 unknown21b:6,
      gps_units_altitude_ft:1,
      gps_units_position_sss:1;
   u8 unknown20:6,
      gps_units_speed:2;
-  u8 unknown21[4];
+  u8 unknown21c[4];
   struct {
     struct {
       char callsign[6];
       u8 ssid;
     } entry[8];
   } digi_path_7;
-  u8 unknown22[2];
+  u8 unknown22[18];
   struct {
     char padded_string[16];
   } message_macro[7];
@@ -196,7 +252,10 @@ struct {
   struct {
     char padded_string[9];
   } msg_group[8];
-  u8 unknown25[4];
+  u8 unknown25;
+  u8 unknown25a:2,
+     timezone:6;
+  u8 unknown25b[2];
   u8 active_smartbeaconing;
   struct {
     u8 low_speed_mph;
@@ -231,56 +290,69 @@ struct {
      vibrate_bln:6;
 } aprs;
 
+#seekto 0xc26a;
+struct {
+  char padded_string[60];
+} aprs_beacon_status_txt[5];
+
 #seekto 0x%04X;
 struct {
   bbcd date[3];
-  u8 unknown1;
   bbcd time[2];
   u8 sequence;
+  u8 unknown1;
   u8 unknown2;
-  u8 sender_callsign[7];
+  char sender_callsign[9];
   u8 data_type;
   u8 yeasu_data_type;
-  u8 unknown3;
   u8 unknown4:1,
      callsign_is_ascii:1,
      unknown5:6;
   u8 unknown6;
   u16 pkt_len;
+  u8 unknown7;
   u16 in_use;
+  u16 unknown8;
+  u16 unknown9;
+  u16 unknown10;
 } aprs_beacon_meta[%d];
 
 #seekto 0x%04X;
 struct {
-  u8 dst_callsign[6];
-  u8 dst_callsign_ssid;
-  u8 src_callsign[6];
-  u8 src_callsign_ssid;
-  u8 path_and_body[%d];
+  char dst_callsign[9];
+  char path[30];
+  u16 flags;
+  u8 seperator;
+  char body[%d];
 } aprs_beacon_pkt[%d];
 
-#seekto 0xf92a;
+#seekto 0x137c4;
 struct {
-  char padded_string[60];
-} aprs_beacon_status_txt[5];
+  u8 flag;
+  char dst_callsign[6];
+  u8 dst_callsign_ssid;
+  char path_and_body[66];
+  u8 unknown[70];
+} aprs_message_pkt[60];
 
-#seekto 0xFECA;
+#seekto 0x1FDC9;
 u8 checksum;
 """
 
 TMODES = ["", "Tone", "TSQL", "DTCS"]
 DUPLEX = ["", "-", "+", "split"]
-MODES  = ["FM", "AM", "WFM"]
+MODES = ["FM", "AM", "WFM"]
 STEPS = list(chirp_common.TUNING_STEPS)
 STEPS.remove(30.0)
 STEPS.append(100.0)
-STEPS.insert(2, 0.0) # There is a skipped tuning step at index 2 (?)
+STEPS.insert(2, 0.0)  # There is a skipped tuning step at index 2 (?)
 SKIPS = ["", "S", "P"]
+FT1_DTMF_CHARS = list("0123456789ABCD*#-")
 
 CHARSET = ["%i" % int(x) for x in range(0, 10)] + \
-    [chr(x) for x in range(ord("A"), ord("Z")+1)] + \
-    [" ",] + \
-    [chr(x) for x in range(ord("a"), ord("z")+1)] + \
+    [chr(x) for x in range(ord("A"), ord("Z") + 1)] + \
+    [" ", ] + \
+    [chr(x) for x in range(ord("a"), ord("z") + 1)] + \
     list(".,:;*#_-/&()@!?^ ") + list("\x00" * 100)
 
 POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00),
@@ -288,13 +360,13 @@ POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00),
                 chirp_common.PowerLevel("L2", watts=1.00),
                 chirp_common.PowerLevel("L1", watts=0.05)]
 
-class VX8Bank(chirp_common.NamedBank):
-    """A VX-8 bank"""
+
+class FT1Bank(chirp_common.NamedBank):
+    """A FT1D bank"""
 
     def get_name(self):
         _bank = self._model._radio._memobj.bank_info[self.index]
-        _bank_used = self._model._radio._memobj.bank_used[self.index]
-                      
+
         name = ""
         for i in _bank.name:
             if i == 0xFF:
@@ -306,15 +378,16 @@ class VX8Bank(chirp_common.NamedBank):
         _bank = self._model._radio._memobj.bank_info[self.index]
         _bank.name = [CHARSET.index(x) for x in name.ljust(16)[:16]]
 
-class VX8BankModel(chirp_common.BankModel):
-    """A VX-8 bank model"""
+
+class FT1BankModel(chirp_common.BankModel):
+    """A FT1D bank model"""
     def __init__(self, radio, name='Banks'):
-        super(VX8BankModel, self).__init__(radio, name)
+        super(FT1BankModel, self).__init__(radio, name)
 
         _banks = self._radio._memobj.bank_info
         self._bank_mappings = []
         for index, _bank in enumerate(_banks):
-            bank = VX8Bank(self, "%i" % index, "BANK-%i" % index)
+            bank = FT1Bank(self, "%i" % index, "BANK-%i" % index)
             bank.index = index
             self._bank_mappings.append(bank)
 
@@ -351,28 +424,28 @@ class VX8BankModel(chirp_common.BankModel):
                 break
 
         for vfo_index in (0, 1):
-          # 3 VFO info structs are stored as 3 pairs of (master, backup)
-          vfo = self._radio._memobj.vfo_info[vfo_index * 2]
-          vfo_bak = self._radio._memobj.vfo_info[(vfo_index * 2) + 1]
-
-          if vfo.checksum != vfo_bak.checksum:
-              print "Warning: VFO settings are inconsistent with backup"
-          else:
-              if ((chosen_bank[vfo_index] is None) and
-                  (vfo.bank_index != 0xFFFF)):
-                  print "Disabling banks for VFO %d" % vfo_index
-                  vfo.bank_index = 0xFFFF
-                  vfo.mr_index = 0xFFFF
-                  vfo.bank_enable = 0xFFFF
-              elif ((chosen_bank[vfo_index] is not None) and
-                  (vfo.bank_index == 0xFFFF)):
-                  print "Enabling banks for VFO %d" % vfo_index
-                  vfo.bank_index = chosen_bank[vfo_index]
-                  vfo.mr_index = chosen_mr[vfo_index]
-                  vfo.bank_enable = 0x0000
-              vfo_bak.bank_index = vfo.bank_index
-              vfo_bak.mr_index = vfo.mr_index
-              vfo_bak.bank_enable = vfo.bank_enable
+            # 3 VFO info structs are stored as 3 pairs of (master, backup)
+            vfo = self._radio._memobj.vfo_info[vfo_index * 2]
+            vfo_bak = self._radio._memobj.vfo_info[(vfo_index * 2) + 1]
+
+            if vfo.checksum != vfo_bak.checksum:
+                LOG.warn("VFO settings are inconsistent with backup")
+            else:
+                if ((chosen_bank[vfo_index] is None) and (vfo.bank_index !=
+                                                          0xFFFF)):
+                    LOG.info("Disabling banks for VFO %d" % vfo_index)
+                    vfo.bank_index = 0xFFFF
+                    vfo.mr_index = 0xFFFF
+                    vfo.bank_enable = 0xFFFF
+                elif ((chosen_bank[vfo_index] is not None) and
+                      (vfo.bank_index == 0xFFFF)):
+                    LOG.info("Enabling banks for VFO %d" % vfo_index)
+                    vfo.bank_index = chosen_bank[vfo_index]
+                    vfo.mr_index = chosen_mr[vfo_index]
+                    vfo.bank_enable = 0x0000
+                vfo_bak.bank_index = vfo.bank_index
+                vfo_bak.mr_index = vfo.mr_index
+                vfo_bak.bank_enable = vfo.bank_enable
 
     def _update_bank_with_channel_numbers(self, bank, channels_in_bank):
         _members = self._radio._memobj.bank_members[bank.index]
@@ -401,7 +474,7 @@ class VX8BankModel(chirp_common.BankModel):
         try:
             channels_in_bank.remove(memory.number)
         except KeyError:
-            raise Exception("Memory %i is not in bank %s. Cannot remove" % \
+            raise Exception("Memory %i is not in bank %s. Cannot remove" %
                             (memory.number, bank))
         self._update_bank_with_channel_numbers(bank, channels_in_bank)
 
@@ -426,47 +499,116 @@ class VX8BankModel(chirp_common.BankModel):
 
         return banks
 
+
 def _wipe_memory(mem):
     mem.set_raw("\x00" * (mem.size() / 8))
     mem.unknown1 = 0x05
 
+
 @directory.register
-class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
-    """Yaesu VX-8"""
+class FT1Radio(yaesu_clone.YaesuCloneModeRadio):
+    """Yaesu FT1DR"""
     BAUD_RATE = 38400
     VENDOR = "Yaesu"
-    MODEL = "VX-8"
+    MODEL = "FT-1D"
     VARIANT = "R"
 
-    _model = "AH029"
-    _memsize = 65227
-    _block_lengths = [ 10, 65217 ]
+    _model = "AH44M"
+    _memsize = 130507
+    _block_lengths = [10, 130497]
     _block_size = 32
-    _mem_params = (0xC24A, # APRS beacon metadata address.
-                   40,     # Number of beacons stored.
-                   0xC60A, # APRS beacon content address.
-                   194,    # Length of beacon data stored.
-                   40)     # Number of beacons stored.
+    _mem_params = (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.
     _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", "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, 7)]
+    _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()
         rp.pre_download = _(dedent("""\
             1. Turn radio off.
-            2. Connect cable to DATA jack.
-            3. Press and hold in the [FW] key while turning the radio on
+            2. Connect cable to DATA terminal.
+            3. Press and hold in the [F] key while turning the radio on
                  ("CLONE" will appear on the display).
-            4. <b>After clicking OK</b>, press the [BAND] key to send image."""))
+            4. <b>After clicking OK</b>, press the [BAND] key to send image."""
+                                   ))
         rp.pre_upload = _(dedent("""\
             1. Turn radio off.
-            2. Connect cable to DATA jack.
-            3. Press and hold in the [FW] key while turning the radio on
+            2. Connect cable to DATA terminal.
+            3. Press and hold in the [F] key while turning the radio on
                  ("CLONE" will appear on the display).
-            4. Press the [MODE] key ("-WAIT-" will appear on the LCD)."""))
+            4. Press the [Dx] key ("-WAIT-" will appear on the LCD)."""))
         return rp
-        
+
     def process_mmap(self):
         self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
 
@@ -486,17 +628,18 @@ 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):
         return repr(self._memobj.memory[number])
 
     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) ]
+        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, 0x1FDC9)]
 
     @staticmethod
     def _add_ff_pad(val, length):
@@ -510,8 +653,8 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
         return result
 
     def get_memory(self, number):
-        flag = self._memobj.flag[number-1]
-        _mem = self._memobj.memory[number-1]
+        flag = self._memobj.flag[number - 1]
+        _mem = self._memobj.memory[number - 1]
 
         mem = chirp_common.Memory()
         mem.number = number
@@ -544,8 +687,8 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
             bm.remove_memory_from_mapping(mem, bank)
 
     def set_memory(self, mem):
-        _mem = self._memobj.memory[mem.number-1]
-        flag = self._memobj.flag[mem.number-1]
+        _mem = self._memobj.memory[mem.number - 1]
+        flag = self._memobj.flag[mem.number - 1]
 
         self._debank(mem)
 
@@ -564,9 +707,9 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
         if mem.freq < 30000000 or \
                 (mem.freq > 88000000 and mem.freq < 108000000) or \
                 mem.freq > 580000000:
-            flag.nosubvfo = True  # Masked from VFO B
+            flag.nosubvfo = True     # Masked from VFO B
         else:
-            flag.nosubvfo = False # Available in both VFOs
+            flag.nosubvfo = False    # Available in both VFOs
 
         _mem.freq = int(mem.freq / 1000)
         _mem.offset = int(mem.offset / 1000)
@@ -591,71 +734,7 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
         flag.pskip = mem.skip == "P"
 
     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")
-    _MY_SYMBOL = ("/[ Person", "/b Bike", "/> Car", "User selected")
-
-    def get_features(self):
-        rf = VX8Radio.get_features(self)
-        rf.has_settings = True
-        return rf
+        return FT1BankModel(self)
 
     @classmethod
     def _digi_path_to_str(cls, path):
@@ -737,8 +816,8 @@ class VX8DRadio(VX8Radio):
         menu = RadioSettingGroup("aprs_general", "APRS General")
         aprs = self._memobj.aprs
 
-        val = RadioSettingValueString(0, 6,
-            str(aprs.my_callsign.callsign).rstrip("\xFF"))
+        val = RadioSettingValueString(
+            0, 6, str(aprs.my_callsign.callsign).rstrip("\xFF"))
         rs = RadioSetting("aprs.my_callsign.callsign", "My Callsign", val)
         rs.set_apply_callback(self.apply_callsign, aprs.my_callsign)
         menu.append(rs)
@@ -757,8 +836,8 @@ class VX8DRadio(VX8Radio):
         symbols = list(chirp_common.APRS_SYMBOLS)
         selected = aprs.custom_symbol
         if aprs.custom_symbol >= len(chirp_common.APRS_SYMBOLS):
-          symbols.append("%d" % aprs.custom_symbol)
-          selected = len(symbols) - 1
+            symbols.append("%d" % aprs.custom_symbol)
+            selected = len(symbols) - 1
         val = RadioSettingValueList(symbols, symbols[selected])
         rs = RadioSetting("aprs.custom_symbol_text", "User Selected Symbol",
                           val)
@@ -787,8 +866,8 @@ class VX8DRadio(VX8Radio):
         # TODO: Rebuild this when latitude/longitude change.
         # TODO: Add saved positions p1 - p10 to memory map.
         position_str = list(self._POSITIONS)
-        #position_str[1] = "%s %s" % (latitude, longitude)
-        #position_str[2] = "%s %s" % (latitude, longitude)
+        # position_str[1] = "%s %s" % (latitude, longitude)
+        # position_str[2] = "%s %s" % (latitude, longitude)
         val = RadioSettingValueList(position_str,
                                     position_str[aprs.selected_position])
         rs = RadioSetting("aprs.selected_position", "My Position", val)
@@ -804,8 +883,8 @@ class VX8DRadio(VX8Radio):
         rs.set_apply_callback(self.apply_lat_long, aprs)
         menu.append(rs)
 
-        val = RadioSettingValueList(self._TIME_SOURCE,
-            self._TIME_SOURCE[aprs.set_time_manually])
+        val = RadioSettingValueList(
+            self._TIME_SOURCE, self._TIME_SOURCE[aprs.set_time_manually])
         rs = RadioSetting("aprs.set_time_manually", "Time Source", val)
         menu.append(rs)
 
@@ -823,32 +902,32 @@ class VX8DRadio(VX8Radio):
         rs = RadioSetting("aprs.gps_units_speed", "GPS Speed Units", val)
         menu.append(rs)
 
-        val = RadioSettingValueList(self._ALT_UNITS,
-            self._ALT_UNITS[aprs.aprs_units_altitude_ft])
+        val = RadioSettingValueList(
+            self._ALT_UNITS, self._ALT_UNITS[aprs.aprs_units_altitude_ft])
         rs = RadioSetting("aprs.aprs_units_altitude_ft", "APRS Altitude Units",
                           val)
         menu.append(rs)
 
-        val = RadioSettingValueList(self._ALT_UNITS,
-            self._ALT_UNITS[aprs.gps_units_altitude_ft])
+        val = RadioSettingValueList(
+            self._ALT_UNITS, self._ALT_UNITS[aprs.gps_units_altitude_ft])
         rs = RadioSetting("aprs.gps_units_altitude_ft", "GPS Altitude Units",
                           val)
         menu.append(rs)
 
-        val = RadioSettingValueList(self._POS_UNITS,
-            self._POS_UNITS[aprs.aprs_units_position_mmss])
+        val = RadioSettingValueList(
+            self._POS_UNITS, self._POS_UNITS[aprs.aprs_units_position_mmss])
         rs = RadioSetting("aprs.aprs_units_position_mmss",
                           "APRS Position Format", val)
         menu.append(rs)
 
-        val = RadioSettingValueList(self._POS_UNITS,
-            self._POS_UNITS[aprs.gps_units_position_sss])
+        val = RadioSettingValueList(
+            self._POS_UNITS, self._POS_UNITS[aprs.gps_units_position_sss])
         rs = RadioSetting("aprs.gps_units_position_sss",
                           "GPS Position Format", val)
         menu.append(rs)
 
-        val = RadioSettingValueList(self._DIST_UNITS,
-            self._DIST_UNITS[aprs.aprs_units_distance_m])
+        val = RadioSettingValueList(
+            self._DIST_UNITS, self._DIST_UNITS[aprs.aprs_units_distance_m])
         rs = RadioSetting("aprs.aprs_units_distance_m", "APRS Distance Units",
                           val)
         menu.append(rs)
@@ -859,19 +938,121 @@ class VX8DRadio(VX8Radio):
                           val)
         menu.append(rs)
 
-        val = RadioSettingValueList(self._RAIN_UNITS,
-            self._RAIN_UNITS[aprs.aprs_units_rain_inch])
+        val = RadioSettingValueList(
+            self._RAIN_UNITS, self._RAIN_UNITS[aprs.aprs_units_rain_inch])
         rs = RadioSetting("aprs.aprs_units_rain_inch", "APRS Rain Units", val)
         menu.append(rs)
 
-        val = RadioSettingValueList(self._TEMP_UNITS,
-            self._TEMP_UNITS[aprs.aprs_units_temperature_f])
+        val = RadioSettingValueList(
+            self._TEMP_UNITS, self._TEMP_UNITS[aprs.aprs_units_temperature_f])
         rs = RadioSetting("aprs.aprs_units_temperature_f",
                           "APRS Temperature Units", val)
         menu.append(rs)
 
         return menu
 
+    def _get_aprs_msgs(self):
+        menu = RadioSettingGroup("aprs_msg", "APRS Messages")
+        aprs_msg = self._memobj.aprs_message_pkt
+
+        for index in range(0, 60):
+            if aprs_msg[index].flag != 255:
+                astring = \
+                    str(aprs_msg[index].dst_callsign).partition("\xFF")[0]
+
+                val = RadioSettingValueString(
+                    0, 9, chirp_common.sanitize_string(astring) +
+                    "-%d" % aprs_msg[index].dst_callsign_ssid)
+                val.set_mutable(False)
+                rs = RadioSetting(
+                    "aprs_msg.dst_callsign%d" % index,
+                    "Dst Callsign %d" % index, val)
+                menu.append(rs)
+
+                astring = \
+                    str(aprs_msg[index].path_and_body).partition("\xFF")[0]
+                val = RadioSettingValueString(
+                    0, 66, chirp_common.sanitize_string(astring))
+                val.set_mutable(False)
+                rs = RadioSetting(
+                    "aprs_msg.path_and_body%d" % index, "Body", val)
+                menu.append(rs)
+
+        return menu
+
+    def _get_aprs_beacons(self):
+        menu = RadioSettingGroup("aprs_beacons", "APRS Beacons")
+        aprs_beacon = self._memobj.aprs_beacon_pkt
+        aprs_meta = self._memobj.aprs_beacon_meta
+
+        for index in range(0, 60):
+            # There is probably a more pythonesque way to do this
+            if int(aprs_meta[index].sender_callsign[0]) != 255:
+                callsign = str(aprs_meta[index].sender_callsign).rstrip("\xFF")
+                # LOG.debug("Callsign %s %s" % (callsign, list(callsign)))
+                val = RadioSettingValueString(0, 9, callsign)
+                val.set_mutable(False)
+                rs = RadioSetting(
+                    "aprs_beacon.src_callsign%d" % index,
+                    "SRC Callsign %d" % index, val)
+                menu.append(rs)
+
+            if int(aprs_beacon[index].dst_callsign[0]) != 255:
+                val = RadioSettingValueString(
+                        0, 9,
+                        str(aprs_beacon[index].dst_callsign).rstrip("\xFF"))
+                val.set_mutable(False)
+                rs = RadioSetting(
+                        "aprs_beacon.dst_callsign%d" % index,
+                        "DST Callsign %d" % index, val)
+                menu.append(rs)
+
+            if int(aprs_meta[index].sender_callsign[0]) != 255:
+                date = "%02d/%02d/%02d" % (
+                    aprs_meta[index].date[0],
+                    aprs_meta[index].date[1],
+                    aprs_meta[index].date[2])
+                val = RadioSettingValueString(0, 8, date)
+                val.set_mutable(False)
+                rs = RadioSetting("aprs_beacon.date%d" % index, "Date", val)
+                menu.append(rs)
+
+                time = "%02d:%02d" % (
+                    aprs_meta[index].time[0],
+                    aprs_meta[index].time[1])
+                val = RadioSettingValueString(0, 5, time)
+                val.set_mutable(False)
+                rs = RadioSetting("aprs_beacon.time%d" % index, "Time", val)
+                menu.append(rs)
+
+            if int(aprs_beacon[index].dst_callsign[0]) != 255:
+                path = str(aprs_beacon[index].path).replace("\x00", " ")
+                path = ''.join(c for c in path
+                               if c in string.printable).strip()
+                path = str(path).replace("\xE0", "*")
+                # LOG.debug("path %s %s" % (path, list(path)))
+                val = RadioSettingValueString(0, 32, path)
+                val.set_mutable(False)
+                rs = RadioSetting(
+                    "aprs_beacon.path%d" % index, "Digipath", val)
+                menu.append(rs)
+
+                body = str(aprs_beacon[index].body).rstrip("\xFF")
+                checksum = body[-2:]
+                body = ''.join(s for s in body[:-2]
+                               if s in string.printable).translate(
+                                   None, "\x09\x0a\x0b\x0c\x0d")
+                try:
+                    val = RadioSettingValueString(0, 134, body.strip())
+                except Exception as e:
+                    LOG.error("Error in APRS beacon at index %s", index)
+                    raise e
+                val.set_mutable(False)
+                rs = RadioSetting("aprs_beacon.body%d" % index, "Body", val)
+                menu.append(rs)
+
+        return menu
+
     def _get_aprs_rx_settings(self):
         menu = RadioSettingGroup("aprs_rx", "APRS Receive")
         aprs = self._memobj.aprs
@@ -986,7 +1167,7 @@ class VX8DRadio(VX8Radio):
         menu = RadioSettingGroup("aprs_tx", "APRS Transmit")
         aprs = self._memobj.aprs
 
-        beacon_type = (aprs.tx_smartbeacon << 1) | aprs.tx_interval_beacon;
+        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)
@@ -1063,11 +1244,11 @@ class VX8DRadio(VX8Radio):
 
         # 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] = 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]
@@ -1134,13 +1315,184 @@ class VX8DRadio(VX8Radio):
             menu.append(rs)
 
         return menu
- 
+
+    def _get_dtmf_settings(self):
+        menu = RadioSettingGroup("dtmf_settings", "DTMF")
+        dtmf = self._memobj.scan_settings
+
+        val = RadioSettingValueList(
+            self._DTMF_MODE,
+            self._DTMF_MODE[dtmf.dtmf_mode])
+        rs = RadioSetting("scan_settings.dtmf_mode", "DTMF Mode", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._DTMF_SPEED,
+            self._DTMF_SPEED[dtmf.dtmf_speed])
+        rs = RadioSetting(
+            "scan_settings.dtmf_speed", "DTMF AutoDial Speed", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._DTMF_DELAY,
+            self._DTMF_DELAY[dtmf.dtmf_delay])
+        rs = RadioSetting(
+            "scan_settings.dtmf_delay", "DTMF AutoDial Delay", val)
+        menu.append(rs)
+
+        for i in range(10):
+            name = "dtmf_%02d" % i
+            dtmfsetting = self._memobj.dtmf[i]
+            dtmfstr = ""
+            for c in dtmfsetting.memory:
+                if c == 0xFF:
+                    break
+                if c < len(FT1_DTMF_CHARS):
+                    dtmfstr += FT1_DTMF_CHARS[c]
+            dtmfentry = RadioSettingValueString(0, 16, dtmfstr)
+            dtmfentry.set_charset(FT1_DTMF_CHARS + list("abcd "))
+            rs = RadioSetting(name, name.upper(), dtmfentry)
+            rs.set_apply_callback(self.apply_dtmf, i)
+            menu.append(rs)
+
+        return menu
+
+    def _get_misc_settings(self):
+        menu = RadioSettingGroup("misc_settings", "Misc")
+        scan_settings = self._memobj.scan_settings
+
+        val = RadioSettingValueList(
+            self._LCD_DIMMER,
+            self._LCD_DIMMER[scan_settings.lcd_dimmer])
+        rs = RadioSetting("scan_settings.lcd_dimmer", "LCD Dimmer", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._LCD_CONTRAST,
+            self._LCD_CONTRAST[scan_settings.lcd_contrast - 1])
+        rs = RadioSetting("scan_settings.lcd_contrast", "LCD Contrast",
+                          val)
+        rs.set_apply_callback(self.apply_lcd_contrast, scan_settings)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._LAMP_KEY,
+            self._LAMP_KEY[scan_settings.lamp])
+        rs = RadioSetting("scan_settings.lamp", "Lamp", val)
+        menu.append(rs)
+
+        beep_select = self._memobj.beep_select
+
+        val = RadioSettingValueList(
+            self._BEEP_SELECT,
+            self._BEEP_SELECT[beep_select.beep])
+        rs = RadioSetting("beep_select.beep", "Beep Select", val)
+        menu.append(rs)
+
+        opening_message = self._memobj.opening_message
+
+        val = RadioSettingValueList(
+            self._OPENING_MESSAGE,
+            self._OPENING_MESSAGE[opening_message.flag])
+        rs = RadioSetting("opening_message.flag", "Opening Msg Mode",
+                          val)
+        menu.append(rs)
+
+        msg = ""
+        for i in opening_message.message.padded_yaesu:
+            if i == 0xFF:
+                break
+            msg += CHARSET[i & 0x7F]
+        val = RadioSettingValueString(0, 16, msg)
+        rs = RadioSetting("opening_message.message.padded_yaesu",
+                          "Opening Message", val)
+        rs.set_apply_callback(self.apply_ff_padded_yaesu,
+                              opening_message.message)
+        menu.append(rs)
+
+        return menu
+
+    def _get_scan_settings(self):
+        menu = RadioSettingGroup("scan_settings", "Scan")
+        scan_settings = self._memobj.scan_settings
+
+        val = RadioSettingValueList(
+            self._VOL_MODE,
+            self._VOL_MODE[scan_settings.vol_mode])
+        rs = RadioSetting("scan_settings.vol_mode", "Volume Mode", val)
+        menu.append(rs)
+
+        vfoa = self._memobj.vfo_info[0]
+        val = RadioSettingValueList(
+            self._VOLUME,
+            self._VOLUME[vfoa.volume])
+        rs = RadioSetting("vfo_info[0].volume", "VFO A Volume", val)
+        rs.set_apply_callback(self.apply_volume, 0)
+        menu.append(rs)
+
+        vfob = self._memobj.vfo_info[1]
+        val = RadioSettingValueList(
+            self._VOLUME,
+            self._VOLUME[vfob.volume])
+        rs = RadioSetting("vfo_info[1].volume", "VFO B Volume", val)
+        rs.set_apply_callback(self.apply_volume, 1)
+        menu.append(rs)
+
+        squelch = self._memobj.squelch
+        val = RadioSettingValueList(
+            self._SQUELCH,
+            self._SQUELCH[squelch.vfo_a])
+        rs = RadioSetting("squelch.vfo_a", "VFO A Squelch", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._SQUELCH,
+            self._SQUELCH[squelch.vfo_b])
+        rs = RadioSetting("squelch.vfo_b", "VFO B Squelch", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._SCAN_RESTART,
+            self._SCAN_RESTART[scan_settings.scan_restart])
+        rs = RadioSetting("scan_settings.scan_restart", "Scan Restart", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._SCAN_RESUME,
+            self._SCAN_RESUME[scan_settings.scan_resume])
+        rs = RadioSetting("scan_settings.scan_resume", "Scan Resume", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._OFF_ON,
+            self._OFF_ON[scan_settings.busy_led])
+        rs = RadioSetting("scan_settings.busy_led", "Busy LED", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._OFF_ON,
+            self._OFF_ON[scan_settings.scan_lamp])
+        rs = RadioSetting("scan_settings.scan_lamp", "Scan Lamp", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._TOT_TIME,
+            self._TOT_TIME[scan_settings.tot])
+        rs = RadioSetting("scan_settings.tot", "Transmit Timeout (TOT)", val)
+        menu.append(rs)
+
+        return menu
+
     def _get_settings(self):
-        top = RadioSettingGroup("all", "All Settings",
-                                self._get_aprs_general_settings(),
-                                self._get_aprs_rx_settings(),
-                                self._get_aprs_tx_settings(),
-                                self._get_aprs_smartbeacon())
+        top = RadioSettings(self._get_aprs_general_settings(),
+                            self._get_aprs_rx_settings(),
+                            self._get_aprs_tx_settings(),
+                            self._get_aprs_smartbeacon(),
+                            self._get_aprs_msgs(),
+                            self._get_aprs_beacons(),
+                            self._get_dtmf_settings(),
+                            self._get_misc_settings(),
+                            self._get_scan_settings())
         return top
 
     def get_settings(self):
@@ -1148,8 +1500,7 @@ class VX8DRadio(VX8Radio):
             return self._get_settings()
         except:
             import traceback
-            print "Failed to parse settings:"
-            traceback.print_exc()
+            LOG.error("Failed to parse settings: %s", traceback.format_exc())
             return None
 
     @staticmethod
@@ -1223,8 +1574,7 @@ class VX8DRadio(VX8Radio):
         is_latitude = name.endswith("latitude")
         lat_long = setting.value.get_value().strip()
         sign, l_d, l_m, l_s = cls._str_to_latlong(lat_long, is_latitude)
-        if os.getenv("CHIRP_DEBUG"):
-            print "%s: %d %d %d %d" % (name, sign, l_d, l_m, l_s)
+        LOG.debug("%s: %d %d %d %d" % (name, sign, l_d, l_m, l_s))
         setattr(obj, "%s_sign" % name, sign)
         setattr(obj, "%s_degree" % name, l_d)
         setattr(obj, "%s_minute" % name, l_m)
@@ -1240,11 +1590,11 @@ class VX8DRadio(VX8Radio):
                 continue
             try:
                 if element.has_apply_callback():
-                    print "Using apply callback"
+                    LOG.debug("Using apply callback")
                     try:
                         element.run_apply_callback()
                     except NotImplementedError as e:
-                        print e
+                        LOG.error(e)
                     continue
 
                 # Find the object containing setting.
@@ -1261,21 +1611,39 @@ class VX8DRadio(VX8Radio):
 
                 try:
                     old_val = getattr(obj, setting)
-                    if os.getenv("CHIRP_DEBUG"):
-                        print "Setting %s(%r) <= %s" % (
-                            element.get_name(), old_val, element.value)
+                    LOG.debug("Setting %s(%r) <= %s" % (
+                            element.get_name(), old_val, element.value))
                     setattr(obj, setting, element.value)
                 except AttributeError as e:
-                    print "Setting %s is not in the memory map: %s" % (
-                        element.get_name(), e)
+                    LOG.error("Setting %s is not in the memory map: %s" %
+                              (element.get_name(), e))
             except Exception, e:
-                print element.get_name()
+                LOG.debug(element.get_name())
                 raise
 
- at directory.register
-class VX8GERadio(VX8DRadio):
-    """Yaesu VX-8GE"""
-    _model = "AH041"
-    VARIANT = "GE"
-    _has_vibrate = True
-    _has_af_dual = False
+    def apply_ff_padded_yaesu(cls, setting, obj):
+        # FF pad yaesus custom string format.
+        rawval = setting.value.get_value()
+        max_len = getattr(obj, "padded_yaesu").size() / 8
+        rawval = str(rawval).rstrip()
+        val = [CHARSET.index(x) for x in rawval]
+        for x in range(len(val), max_len):
+            val.append(0xFF)
+        obj.padded_yaesu = val
+
+    def apply_volume(cls, setting, vfo):
+        val = setting.value.get_value()
+        cls._memobj.vfo_info[(vfo * 2)].volume = val
+        cls._memobj.vfo_info[(vfo * 2) + 1].volume = val
+
+    def apply_lcd_contrast(cls, setting, obj):
+        rawval = setting.value.get_value()
+        val = cls._LCD_CONTRAST.index(rawval) + 1
+        obj.lcd_contrast = val
+
+    def apply_dtmf(cls, setting, i):
+        rawval = setting.value.get_value().upper().rstrip()
+        val = [FT1_DTMF_CHARS.index(x) for x in rawval]
+        for x in range(len(val), 16):
+            val.append(0xFF)
+        cls._memobj.dtmf[i].memory = val
diff --git a/chirp/ft2800.py b/chirp/drivers/ft2800.py
similarity index 89%
rename from chirp/ft2800.py
rename to chirp/drivers/ft2800.py
index fb9d10c..9c39ad1 100644
--- a/chirp/ft2800.py
+++ b/chirp/drivers/ft2800.py
@@ -15,13 +15,16 @@
 
 import time
 import os
+import logging
 
 from chirp import util, memmap, chirp_common, bitwise, directory, errors
-from chirp.yaesu_clone import YaesuCloneModeRadio
+from yaesu_clone import YaesuCloneModeRadio
 
-DEBUG = os.getenv("CHIRP_DEBUG") and True or False
+LOG = logging.getLogger(__name__)
 
 CHUNK_SIZE = 16
+
+
 def _send(s, data):
     for i in range(0, len(data), CHUNK_SIZE):
         chunk = data[i:i+CHUNK_SIZE]
@@ -34,6 +37,7 @@ IDBLOCK = "\x0c\x01\x41\x33\x35\x02\x00\xb8"
 TRAILER = "\x0c\x02\x41\x33\x35\x00\x00\xb7"
 ACK = "\x0C\x06\x00"
 
+
 def _download(radio):
     data = ""
     for _i in range(0, 10):
@@ -41,8 +45,7 @@ def _download(radio):
         if data == IDBLOCK:
             break
 
-    if DEBUG:
-        print "Header:\n%s" % util.hexprint(data)
+    LOG.debug("Header:\n%s" % util.hexprint(data))
 
     if len(data) != 8:
         raise Exception("Failed to read header")
@@ -54,14 +57,13 @@ def _download(radio):
     while len(data) < radio._block_sizes[1]:
         time.sleep(0.1)
         chunk = radio.pipe.read(38)
-        if DEBUG:
-            print "Got: %i:\n%s" % (len(chunk), util.hexprint(chunk))
+        LOG.debug("Got: %i:\n%s" % (len(chunk), util.hexprint(chunk)))
         if len(chunk) == 8:
-            print "END?"
+            LOG.debug("END?")
         elif len(chunk) != 38:
-            print "Should fail?"
+            LOG.debug("Should fail?")
             break
-            #raise Exception("Failed to get full data block")
+            # raise Exception("Failed to get full data block")
         else:
             cs = 0
             for byte in chunk[:-1]:
@@ -79,23 +81,22 @@ def _download(radio):
             status.msg = "Cloning from radio"
             radio.status_fn(status)
 
-    if DEBUG:
-        print "Total: %i" % len(data)
+    LOG.debug("Total: %i" % len(data))
 
     return memmap.MemoryMap(data)
 
+
 def _upload(radio):
     for _i in range(0, 10):
         data = radio.pipe.read(256)
         if not data:
             break
-        print "What is this garbage?\n%s" % util.hexprint(data)
+        LOG.debug("What is this garbage?\n%s" % util.hexprint(data))
 
     _send(radio.pipe, IDBLOCK)
     time.sleep(1)
     ack = radio.pipe.read(300)
-    if DEBUG:
-        print "Ack was (%i):\n%s" % (len(ack), util.hexprint(ack))
+    LOG.debug("Ack was (%i):\n%s" % (len(ack), util.hexprint(ack)))
     if ack != ACK:
         raise Exception("Radio did not ack ID")
 
@@ -108,8 +109,7 @@ def _upload(radio):
             cs += ord(byte)
         data += chr(cs & 0xFF)
 
-        if DEBUG:
-            print "Writing block %i:\n%s" % (block, util.hexprint(data))
+        LOG.debug("Writing block %i:\n%s" % (block, util.hexprint(data)))
 
         _send(radio.pipe, data)
         time.sleep(0.1)
@@ -161,6 +161,7 @@ POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=65),
                 ]
 CHARSET = chirp_common.CHARSET_UPPER_NUMERIC + "()+-=*/???|_"
 
+
 @directory.register
 class FT2800Radio(YaesuCloneModeRadio):
     """Yaesu FT-2800"""
@@ -180,7 +181,8 @@ class FT2800Radio(YaesuCloneModeRadio):
         rf.has_dtcs_polarity = False
         rf.has_bank = False
 
-        rf.valid_tuning_steps = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0]
+        rf.valid_tuning_steps = [5.0, 10.0, 12.5, 15.0,
+                                 20.0, 25.0, 50.0, 100.0]
         rf.valid_modes = MODES
         rf.valid_tmodes = TMODES
         rf.valid_bands = [(137000000, 174000000)]
@@ -201,7 +203,7 @@ class FT2800Radio(YaesuCloneModeRadio):
             raise
         except Exception, e:
             raise errors.RadioError("Failed to communicate with radio: %s" % e)
-        print "Downloaded in %.2f sec" % (time.time() - start)
+        LOG.info("Downloaded in %.2f sec" % (time.time() - start))
         self.process_mmap()
 
     def sync_out(self):
@@ -214,7 +216,7 @@ class FT2800Radio(YaesuCloneModeRadio):
             raise
         except Exception, e:
             raise errors.RadioError("Failed to communicate with radio: %s" % e)
-        print "Uploaded in %.2f sec" % (time.time() - start)
+        LOG.info("Uploaded in %.2f sec" % (time.time() - start))
 
     def process_mmap(self):
         self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
diff --git a/chirp/drivers/ft2900.py b/chirp/drivers/ft2900.py
new file mode 100644
index 0000000..f56f915
--- /dev/null
+++ b/chirp/drivers/ft2900.py
@@ -0,0 +1,652 @@
+# Copyright 2011 Dan Smith <dsmith at danplanet.com>
+#
+# FT-2900-specific modifications by Richard Cochran, <ag6qr at sonic.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 time
+import os
+import logging
+
+from chirp import util, memmap, chirp_common, bitwise, directory, errors
+from chirp.drivers.yaesu_clone import YaesuCloneModeRadio
+
+from textwrap import dedent
+
+LOG = logging.getLogger(__name__)
+
+
+def _send(s, data):
+    s.write(data)
+    echo = s.read(len(data))
+    if data != echo:
+        raise Exception("Failed to read echo")
+    LOG.debug("got echo\n%s\n" % util.hexprint(echo))
+
+ACK = "\x06"
+INITIAL_CHECKSUM = 0
+
+
+def _download(radio):
+
+    blankChunk = ""
+    for _i in range(0, 32):
+        blankChunk += "\xff"
+
+    LOG.debug("in _download\n")
+
+    data = ""
+    for _i in range(0, 20):
+        data = radio.pipe.read(20)
+        LOG.debug("Header:\n%s" % util.hexprint(data))
+        LOG.debug("len(header) = %s\n" % len(data))
+
+        if data == radio.IDBLOCK:
+            break
+
+    if data != radio.IDBLOCK:
+        raise Exception("Failed to read header")
+
+    _send(radio.pipe, ACK)
+
+    # initialize data, the big var that holds all memory
+    data = ""
+
+    _blockNum = 0
+
+    while len(data) < radio._block_sizes[1]:
+        _blockNum += 1
+        time.sleep(0.03)
+        chunk = radio.pipe.read(32)
+        LOG.debug("Block %i " % (_blockNum))
+        if chunk == blankChunk:
+            LOG.debug("blank chunk\n")
+        else:
+            LOG.debug("Got: %i:\n%s" % (len(chunk), util.hexprint(chunk)))
+        if len(chunk) != 32:
+            LOG.debug("len chunk is %i\n" % (len(chunk)))
+            raise Exception("Failed to get full data block")
+            break
+        else:
+            data += chunk
+
+        if radio.status_fn:
+            status = chirp_common.Status()
+            status.max = radio._block_sizes[1]
+            status.cur = len(data)
+            status.msg = "Cloning from radio"
+            radio.status_fn(status)
+
+    LOG.debug("Total: %i" % len(data))
+
+    # radio should send us one final termination byte, containing
+    # checksum
+    chunk = radio.pipe.read(32)
+    if len(chunk) != 1:
+        LOG.debug("len(chunk) is %i\n" % len(chunk))
+        raise Exception("radio sent extra unknown data")
+    LOG.debug("Got: %i:\n%s" % (len(chunk), util.hexprint(chunk)))
+
+    # compute checksum
+    cs = INITIAL_CHECKSUM
+    for byte in radio.IDBLOCK:
+        cs += ord(byte)
+    for byte in data:
+        cs += ord(byte)
+    LOG.debug("calculated checksum is %x\n" % (cs & 0xff))
+    LOG.debug("Radio sent checksum is %x\n" % ord(chunk[0]))
+
+    if (cs & 0xff) != ord(chunk[0]):
+        raise Exception("Failed checksum on read.")
+
+    # for debugging purposes, dump the channels, in hex.
+    for _i in range(0, 200):
+        _startData = 1892 + 20 * _i
+        chunk = data[_startData:_startData + 20]
+        LOG.debug("channel %i:\n%s" % (_i, util.hexprint(chunk)))
+
+    return memmap.MemoryMap(data)
+
+
+def _upload(radio):
+    for _i in range(0, 10):
+        data = radio.pipe.read(256)
+        if not data:
+            break
+        LOG.debug("What is this garbage?\n%s" % util.hexprint(data))
+        raise Exception("Radio sent unrecognized data")
+
+    _send(radio.pipe, radio.IDBLOCK)
+    time.sleep(.2)
+    ack = radio.pipe.read(300)
+    LOG.debug("Ack was (%i):\n%s" % (len(ack), util.hexprint(ack)))
+    if ack != ACK:
+        raise Exception("Radio did not ack ID")
+
+    block = 0
+    cs = INITIAL_CHECKSUM
+    for byte in radio.IDBLOCK:
+        cs += ord(byte)
+
+    while block < (radio.get_memsize() / 32):
+        data = radio.get_mmap()[block*32:(block+1)*32]
+
+        LOG.debug("Writing block %i:\n%s" % (block, util.hexprint(data)))
+
+        _send(radio.pipe, data)
+        time.sleep(0.03)
+
+        for byte in data:
+            cs += ord(byte)
+
+        if radio.status_fn:
+            status = chirp_common.Status()
+            status.max = radio._block_sizes[1]
+            status.cur = block * 32
+            status.msg = "Cloning to radio"
+            radio.status_fn(status)
+        block += 1
+
+    _send(radio.pipe, chr(cs & 0xFF))
+
+MEM_FORMAT = """
+#seekto 0x00c0;
+struct {
+  u16 in_use;
+} bank_used[8];
+
+#seekto 0x00ef;
+  u8 currentTone;
+
+#seekto 0x00f0;
+  u8 curChannelMem[20];
+
+#seekto 0x0127;
+  u8 curChannelNum;
+
+#seekto 0x012a;
+  u8 banksoff1;
+
+#seekto 0x15f;
+  u8 checksum1;
+
+#seekto 0x16f;
+  u8 curentTone2;
+
+#seekto 0x1aa;
+  u16 banksoff2;
+
+#seekto 0x1df;
+  u8 checksum2;
+
+#seekto 0x0360;
+struct{
+  u8 name[6];
+} bank_names[8];
+
+
+#seekto 0x03c4;
+struct{
+  u16 channels[50];
+} banks[8];
+
+#seekto 0x06e4;
+struct {
+  u8 even_pskip:1,
+     even_skip:1,
+     even_valid:1,
+     even_masked:1,
+     odd_pskip:1,
+     odd_skip:1,
+     odd_valid:1,
+     odd_masked:1;
+} flags[225];
+
+#seekto 0x0764;
+struct {
+  u8 unknown0:2,
+     isnarrow:1,
+     unknown1:5;
+  u8 unknown2:2,
+     duplex:2,
+     unknown3:1,
+     step:3;
+  bbcd freq[3];
+  u8 power:2,
+     unknown4:3,
+     tmode:3;
+  u8 name[6];
+  bbcd offset[3];
+  u8 ctonesplitflag:1,
+     ctone:7;
+  u8 rx_dtcssplitflag:1,
+     rx_dtcs:7;
+  u8 unknown5;
+  u8 rtonesplitflag:1,
+     rtone:7;
+  u8 dtcssplitflag:1,
+     dtcs:7;
+} memory[200];
+
+"""
+
+MODES = ["FM", "NFM"]
+TMODES = ["", "Tone", "TSQL", "DTCS", "TSQL-R", "Cross"]
+CROSS_MODES = ["DTCS->", "Tone->DTCS", "DTCS->Tone",
+               "Tone->Tone", "DTCS->DTCS"]
+DUPLEX = ["", "-", "+", "split"]
+POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=75),
+                chirp_common.PowerLevel("Low3", watts=30),
+                chirp_common.PowerLevel("Low2", watts=10),
+                chirp_common.PowerLevel("Low1", watts=5),
+                ]
+
+CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ +-/?C[] _"
+STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0]
+
+
+def _decode_tone(radiotone):
+    try:
+        chirptone = chirp_common.TONES[radiotone]
+    except IndexError:
+        chirptone = 100
+        LOG.debug("found invalid radio tone: %i\n" % radiotone)
+    return chirptone
+
+
+def _decode_dtcs(radiodtcs):
+    try:
+        chirpdtcs = chirp_common.DTCS_CODES[radiodtcs]
+    except IndexError:
+        chirpdtcs = 23
+        LOG.debug("found invalid radio dtcs code: %i\n" % radiodtcs)
+    return chirpdtcs
+
+
+def _decode_name(mem):
+    name = ""
+    for i in mem:
+        if (i & 0x7F) == 0x7F:
+            break
+        try:
+            name += CHARSET[i & 0x7F]
+        except IndexError:
+            LOG.debug("Unknown char index: %x " % (i))
+    name = name.strip()
+    return name
+
+
+def _encode_name(mem):
+    if(mem.strip() == ""):
+        return [0xff]*6
+
+    name = [None]*6
+    for i in range(0, 6):
+        try:
+            name[i] = CHARSET.index(mem[i])
+        except IndexError:
+            name[i] = CHARSET.index(" ")
+
+    name[0] = name[0] | 0x80
+    return name
+
+
+def _wipe_memory(mem):
+    mem.set_raw("\xff" * (mem.size() / 8))
+
+
+class FT2900Bank(chirp_common.NamedBank):
+    def get_name(self):
+        _bank = self._model._radio._memobj.bank_names[self.index]
+        name = ""
+        for i in _bank.name:
+            if i == 0xff:
+                break
+            name += CHARSET[i & 0x7f]
+
+        return name.rstrip()
+
+    def set_name(self, name):
+        name = name.upper().ljust(6)[:6]
+        _bank = self._model._radio._memobj.bank_names[self.index]
+        _bank.name = [CHARSET.index(x) for x in name.ljust(6)[:6]]
+
+
+class FT2900BankModel(chirp_common.BankModel):
+    def get_num_mappings(self):
+        return 8
+
+    def get_mappings(self):
+        banks = self._radio._memobj.banks
+        bank_mappings = []
+        for index, _bank in enumerate(banks):
+            bank = FT2900Bank(self, "%i" % index, "b%i" % (index + 1))
+            bank.index = index
+            bank_mappings.append(bank)
+
+        return bank_mappings
+
+    def _get_channel_numbers_in_bank(self, bank):
+        _bank_used = self._radio._memobj.bank_used[bank.index]
+        if _bank_used.in_use == 0xffff:
+            return set()
+
+        _members = self._radio._memobj.banks[bank.index]
+        return set([int(ch) for ch in _members.channels if ch != 0xffff])
+
+    def _update_bank_with_channel_numbers(self, bank, channels_in_bank):
+        _members = self._radio._memobj.banks[bank.index]
+        if len(channels_in_bank) > len(_members.channels):
+            raise Exception("More than %i entries in bank %d" %
+                            (len(_members.channels), bank.index))
+
+        empty = 0
+        for index, channel_number in enumerate(sorted(channels_in_bank)):
+            _members.channels[index] = channel_number
+            empty = index + 1
+        for index in range(empty, len(_members.channels)):
+            _members.channels[index] = 0xffff
+
+        _bank_used = self._radio._memobj.bank_used[bank.index]
+        if empty == 0:
+            _bank_used.in_use = 0xffff
+        else:
+            _bank_used.in_use = empty - 1
+
+    def add_memory_to_mapping(self, memory, bank):
+        channels_in_bank = self._get_channel_numbers_in_bank(bank)
+        channels_in_bank.add(memory.number)
+        self._update_bank_with_channel_numbers(bank, channels_in_bank)
+
+        # tells radio that banks are active
+        self._radio._memobj.banksoff1 = bank.index
+        self._radio._memobj.banksoff2 = bank.index
+
+    def remove_memory_from_mapping(self, memory, bank):
+        channels_in_bank = self._get_channel_numbers_in_bank(bank)
+        try:
+            channels_in_bank.remove(memory.number)
+        except KeyError:
+            raise Exception("Memory %i is not in bank %s. Cannot remove" %
+                            (memory.number, bank))
+        self._update_bank_with_channel_numbers(bank, channels_in_bank)
+
+    def get_mapping_memories(self, bank):
+        memories = []
+        for channel in self._get_channel_numbers_in_bank(bank):
+            memories.append(self._radio.get_memory(channel))
+
+        return memories
+
+    def get_memory_mappings(self, memory):
+        banks = []
+        for bank in self.get_mappings():
+            if memory.number in self._get_channel_numbers_in_bank(bank):
+                banks.append(bank)
+
+        return banks
+
+
+ at directory.register
+class FT2900Radio(YaesuCloneModeRadio):
+    """Yaesu FT-2900"""
+    VENDOR = "Yaesu"
+    MODEL = "FT-2900R/1900R"
+    IDBLOCK = "\x56\x43\x32\x33\x00\x02\x46\x01\x01\x01"
+    BAUD_RATE = 19200
+
+    _memsize = 8000
+    _block_sizes = [8, 8000]
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+
+        rf.memory_bounds = (0, 199)
+
+        rf.can_odd_split = True
+        rf.has_ctone = True
+        rf.has_rx_dtcs = True
+        rf.has_cross = True
+        rf.has_dtcs_polarity = False
+        rf.has_bank = True
+        rf.has_bank_names = True
+
+        rf.valid_tuning_steps = STEPS
+        rf.valid_modes = MODES
+        rf.valid_tmodes = TMODES
+        rf.valid_cross_modes = CROSS_MODES
+        rf.valid_bands = [(136000000, 174000000)]
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_duplexes = DUPLEX
+        rf.valid_skips = ["", "S", "P"]
+        rf.valid_name_length = 6
+        rf.valid_characters = CHARSET
+
+        return rf
+
+    def sync_in(self):
+        start = time.time()
+        try:
+            self._mmap = _download(self)
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            raise errors.RadioError("Failed to communicate with radio: %s" % e)
+        LOG.info("Downloaded in %.2f sec" % (time.time() - start))
+        self.process_mmap()
+
+    def sync_out(self):
+        self.pipe.setTimeout(1)
+        start = time.time()
+        try:
+            _upload(self)
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            raise errors.RadioError("Failed to communicate with radio: %s" % e)
+        LOG.info("Uploaded in %.2f sec" % (time.time() - start))
+
+    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):
+        _mem = self._memobj.memory[number]
+        _flag = self._memobj.flags[(number)/2]
+
+        nibble = ((number) % 2) and "even" or "odd"
+        used = _flag["%s_masked" % nibble]
+        valid = _flag["%s_valid" % nibble]
+        pskip = _flag["%s_pskip" % nibble]
+        skip = _flag["%s_skip" % nibble]
+
+        mem = chirp_common.Memory()
+
+        mem.number = number
+
+        if _mem.get_raw()[0] == "\xFF" or not valid or not used:
+            mem.empty = True
+            return mem
+
+        mem.tuning_step = STEPS[_mem.step]
+        mem.freq = int(_mem.freq) * 1000
+
+        # compensate for 12.5 kHz tuning steps, add 500 Hz if needed
+        if(mem.tuning_step == 12.5):
+            lastdigit = int(_mem.freq) % 10
+            if (lastdigit == 2 or lastdigit == 7):
+                mem.freq += 500
+
+        mem.offset = chirp_common.fix_rounded_step(int(_mem.offset) * 1000)
+        mem.duplex = DUPLEX[_mem.duplex]
+        if _mem.tmode < TMODES.index("Cross"):
+            mem.tmode = TMODES[_mem.tmode]
+            mem.cross_mode = CROSS_MODES[0]
+        else:
+            mem.tmode = "Cross"
+            mem.cross_mode = CROSS_MODES[_mem.tmode - TMODES.index("Cross")]
+
+        mem.rtone = _decode_tone(_mem.rtone)
+        mem.ctone = _decode_tone(_mem.ctone)
+
+        # check for unequal ctone/rtone in TSQL mode.  map it as a
+        # cross tone mode
+        if mem.rtone != mem.ctone and (mem.tmode == "TSQL" or
+                                       mem.tmode == "Tone"):
+            mem.tmode = "Cross"
+            mem.cross_mode = "Tone->Tone"
+
+        mem.dtcs = _decode_dtcs(_mem.dtcs)
+        mem.rx_dtcs = _decode_dtcs(_mem.rx_dtcs)
+
+        # check for unequal dtcs/rx_dtcs in DTCS mode.  map it as a
+        # cross tone mode
+        if mem.dtcs != mem.rx_dtcs and mem.tmode == "DTCS":
+            mem.tmode = "Cross"
+            mem.cross_mode = "DTCS->DTCS"
+
+        if (int(_mem.name[0]) & 0x80) != 0:
+            mem.name = _decode_name(_mem.name)
+
+        mem.mode = _mem.isnarrow and "NFM" or "FM"
+        mem.skip = pskip and "P" or skip and "S" or ""
+        mem.power = POWER_LEVELS[3 - _mem.power]
+
+        return mem
+
+    def set_memory(self, mem):
+        _mem = self._memobj.memory[mem.number]
+        _flag = self._memobj.flags[(mem.number)/2]
+
+        nibble = ((mem.number) % 2) and "even" or "odd"
+
+        valid = _flag["%s_valid" % nibble]
+        used = _flag["%s_masked" % nibble]
+
+        if not valid:
+            _wipe_memory(_mem)
+
+        if mem.empty and valid and not used:
+            _flag["%s_valid" % nibble] = False
+            return
+
+        _flag["%s_masked" % nibble] = not mem.empty
+
+        if mem.empty:
+            return
+
+        _flag["%s_valid" % nibble] = True
+
+        _mem.freq = mem.freq / 1000
+        _mem.offset = mem.offset / 1000
+        _mem.duplex = DUPLEX.index(mem.duplex)
+
+        # clear all the split tone flags -- we'll set them as needed below
+        _mem.ctonesplitflag = 0
+        _mem.rx_dtcssplitflag = 0
+        _mem.rtonesplitflag = 0
+        _mem.dtcssplitflag = 0
+
+        if mem.tmode != "Cross":
+            _mem.tmode = TMODES.index(mem.tmode)
+            # for the non-cross modes, use ONE tone for both send
+            # and receive but figure out where to get it from.
+            if mem.tmode == "TSQL" or mem.tmode == "TSQL-R":
+                _mem.rtone = chirp_common.TONES.index(mem.ctone)
+                _mem.ctone = chirp_common.TONES.index(mem.ctone)
+            else:
+                _mem.rtone = chirp_common.TONES.index(mem.rtone)
+                _mem.ctone = chirp_common.TONES.index(mem.rtone)
+
+            # and one tone for dtcs, but this is always the sending one
+            _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
+            _mem.rx_dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
+
+        else:
+            _mem.rtone = chirp_common.TONES.index(mem.rtone)
+            _mem.ctone = chirp_common.TONES.index(mem.ctone)
+            _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
+            _mem.rx_dtcs = chirp_common.DTCS_CODES.index(mem.rx_dtcs)
+            if mem.cross_mode == "Tone->Tone":
+                # tone->tone cross mode is treated as
+                # TSQL, but with separate tones for
+                # send and receive
+                _mem.tmode = TMODES.index("TSQL")
+                _mem.rtonesplitflag = 1
+            elif mem.cross_mode == "DTCS->DTCS":
+                # DTCS->DTCS cross mode is treated as
+                # DTCS, but with separate codes for
+                # send and receive
+                _mem.tmode = TMODES.index("DTCS")
+                _mem.dtcssplitflag = 1
+            else:
+                _mem.tmode = TMODES.index("Cross") + \
+                    CROSS_MODES.index(mem.cross_mode)
+
+        _mem.isnarrow = MODES.index(mem.mode)
+        _mem.step = STEPS.index(mem.tuning_step)
+        _flag["%s_pskip" % nibble] = mem.skip == "P"
+        _flag["%s_skip" % nibble] = mem.skip == "S"
+        if mem.power:
+            _mem.power = 3 - POWER_LEVELS.index(mem.power)
+        else:
+            _mem.power = 3
+
+        _mem.name = _encode_name(mem.name)
+
+        # set all unknown areas of the memory map to 0
+        _mem.unknown0 = 0
+        _mem.unknown1 = 0
+        _mem.unknown2 = 0
+        _mem.unknown3 = 0
+        _mem.unknown4 = 0
+        _mem.unknown5 = 0
+
+        LOG.debug("encoded mem\n%s\n" % (util.hexprint(_mem.get_raw()[0:20])))
+
+    def get_bank_model(self):
+        return FT2900BankModel(self)
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return len(filedata) == cls._memsize
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.pre_download = _(dedent("""\
+            1. Turn Radio off.
+            2. Connect data cable.
+            3. While holding "A/N LOW" button, turn radio on.
+            4. <b>After clicking OK</b>, press "SET MHz" to send image."""))
+        rp.pre_upload = _(dedent("""\
+            1. Turn Radio off.
+            2. Connect data cable.
+            3. While holding "A/N LOW" button, turn radio on.
+            4. Press "MW D/MR" to receive image.
+            5. Click OK to dismiss this dialog and start transfer."""))
+        return rp
+
+
+# the FT2900E is the European version of the radio, almost identical
+# to the R (USA) version, except for the model number and ID Block.  We
+# create and register a class for it, with only the needed overrides
+ at directory.register
+class FT2900ERadio(FT2900Radio):
+    """Yaesu FT-2900E"""
+    MODEL = "FT-2900E/1900E"
+    VARIANT = "E"
+    IDBLOCK = "\x56\x43\x32\x33\x00\x02\x41\x02\x01\x01"
diff --git a/chirp/ft50.py b/chirp/drivers/ft50.py
similarity index 91%
rename from chirp/ft50.py
rename to chirp/drivers/ft50.py
index d101360..6a9cf21 100644
--- a/chirp/ft50.py
+++ b/chirp/drivers/ft50.py
@@ -13,10 +13,12 @@
 # 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, yaesu_clone, ft50_ll, directory
+from chirp.drivers import yaesu_clone, ft50_ll
+from chirp import chirp_common, directory
+
 
 # Not working, don't register
-#@directory.register
+# @directory.register
 class FT50Radio(yaesu_clone.YaesuCloneModeRadio):
     BAUD_RATE = 9600
     VENDOR = "Yaesu"
@@ -31,7 +33,7 @@ class FT50Radio(yaesu_clone.YaesuCloneModeRadio):
         rf.memory_bounds = (1, 100)
         rf.has_dtcs_polarity = False
         rf.has_bank = False
-        rf.valid_modes = [ "FM", "WFM", "AM" ]
+        rf.valid_modes = ["FM", "WFM", "AM"]
         return rf
 
     def _update_checksum(self):
diff --git a/chirp/ft50_ll.py b/chirp/drivers/ft50_ll.py
similarity index 82%
rename from chirp/ft50_ll.py
rename to chirp/drivers/ft50_ll.py
index c38b4f9..abf4b48 100644
--- a/chirp/ft50_ll.py
+++ b/chirp/drivers/ft50_ll.py
@@ -15,6 +15,9 @@
 
 from chirp import chirp_common, util, errors, memmap
 import time
+import logging
+
+LOG = logging.getLogger(__name__)
 
 ACK = chr(0x06)
 
@@ -22,58 +25,61 @@ MEM_LOC_BASE = 0x00AB
 MEM_LOC_SIZE = 16
 
 POS_DUPLEX = 1
-POS_TMODE  = 2
-POS_TONE   = 2
-POS_DTCS   = 3
-POS_MODE   = 4
-POS_FREQ   = 5
+POS_TMODE = 2
+POS_TONE = 2
+POS_DTCS = 3
+POS_MODE = 4
+POS_FREQ = 5
 POS_OFFSET = 9
-POS_NAME   = 11
+POS_NAME = 11
 
-POS_USED   = 0x079C
+POS_USED = 0x079C
 
 CHARSET = [str(x) for x in range(0, 10)] + \
     [chr(x) for x in range(ord("A"), ord("Z")+1)] + \
     list(" ()+--*/???|0123456789")
 
+
 def send(s, data):
     s.write(data)
     r = s.read(len(data))
     if len(r) != len(data):
         raise errors.RadioError("Failed to read echo")
 
+
 def read_exact(s, count):
     data = ""
     i = 0
     while len(data) < count:
         if i == 3:
-            print util.hexprint(data)
-            raise errors.RadioError("Failed to read %i (%i) from radio" % (count,
-                                                                           len(data)))
+            LOG.debug(util.hexprint(data))
+            raise errors.RadioError("Failed to read %i (%i) from radio" %
+                                    (count, len(data)))
         elif i > 0:
-            print "Retry %i" % i
+            LOG.info("Retry %i" % i)
         data += s.read(count - len(data))
         i += 1
 
     return data
 
+
 def download(radio):
     data = ""
 
     radio.pipe.setTimeout(1)
 
     for block in radio._block_lengths:
-        print "Doing block %i" % block
+        LOG.debug("Doing block %i" % block)
         if block > 112:
             step = 16
         else:
             step = block
         for i in range(0, block, step):
-            #data += read_exact(radio.pipe, step)
+            # data += read_exact(radio.pipe, step)
             chunk = radio.pipe.read(step*2)
-            print "Length of chunk: %i" % len(chunk)
+            LOG.debug("Length of chunk: %i" % len(chunk))
             data += chunk
-            print "Reading %i" % i
+            LOG.debug("Reading %i" % i)
             time.sleep(0.1)
             send(radio.pipe, ACK)
             if radio.status_fn:
@@ -85,109 +91,124 @@ def download(radio):
 
     r = radio.pipe.read(100)
     send(radio.pipe, ACK)
-    print "R: %i" % len(r)
-    print util.hexprint(r)
+    LOG.debug("R: %i" % len(r))
+    LOG.debug(util.hexprint(r))
 
-    print "Got: %i Expecting %i" % (len(data), radio._memsize)
+    LOG.debug("Got: %i Expecting %i" % (len(data), radio._memsize))
 
     return memmap.MemoryMap(data)
 
+
 def get_mem_offset(number):
     return MEM_LOC_BASE + (number * MEM_LOC_SIZE)
 
+
 def get_raw_memory(map, number):
     pos = get_mem_offset(number)
     return memmap.MemoryMap(map[pos:pos+MEM_LOC_SIZE])
 
+
 def get_freq(mmap):
     khz = (int("%02x" % (ord(mmap[POS_FREQ])), 10) * 100000) + \
         (int("%02x" % ord(mmap[POS_FREQ+1]), 10) * 1000) + \
         (int("%02x" % ord(mmap[POS_FREQ+2]), 10) * 10)
     return khz / 10000.0
 
+
 def set_freq(mmap, freq):
     val = util.bcd_encode(int(freq * 1000), width=6)[:3]
     mmap[POS_FREQ] = val
 
+
 def get_tmode(mmap):
     val = ord(mmap[POS_TMODE]) & 0xC0
 
     tmodemap = {
-        0x00 : "",
-        0x40 : "Tone",
-        0x80 : "TSQL",
-        0xC0 : "DTCS",
+        0x00: "",
+        0x40: "Tone",
+        0x80: "TSQL",
+        0xC0: "DTCS",
         }
-    
+
     return tmodemap[val]
 
+
 def set_tmode(mmap, tmode):
     val = ord(mmap[POS_TMODE]) & 0x3F
 
     tmodemap = {
-        ""     : 0x00,
-        "Tone" : 0x40,
-        "TSQL" : 0x80,
-        "DTCS" : 0xC0,
+        "":      0x00,
+        "Tone":  0x40,
+        "TSQL":  0x80,
+        "DTCS":  0xC0,
         }
 
     val |= tmodemap[tmode]
 
     mmap[POS_TMODE] = val
 
+
 def get_tone(mmap):
     val = ord(mmap[POS_TONE]) & 0x3F
 
     return chirp_common.TONES[val]
 
+
 def set_tone(mmap, tone):
     val = ord(mmap[POS_TONE]) & 0xC0
 
     mmap[POS_TONE] = val | chirp_common.TONES.index(tone)
 
+
 def get_dtcs(mmap):
     val = ord(mmap[POS_DTCS])
 
     return chirp_common.DTCS_CODES[val]
 
+
 def set_dtcs(mmap, dtcs):
     mmap[POS_DTCS] = chirp_common.DTCS_CODES.index(dtcs)
 
+
 def get_offset(mmap):
     khz = (int("%02x" % ord(mmap[POS_OFFSET]), 10) * 10) + \
         (int("%02x" % (ord(mmap[POS_OFFSET+1]) >> 4), 10) * 1)
 
     return khz / 1000.0
 
+
 def set_offset(mmap, offset):
     val = util.bcd_encode(int(offset * 1000), width=4)[:3]
-    print "Offfset:\n%s"% util.hexprint(val)
+    LOG.debug("Offset:\n%s" % util.hexprint(val))
     mmap[POS_OFFSET] = val
 
+
 def get_duplex(mmap):
     val = ord(mmap[POS_DUPLEX]) & 0x03
 
     dupmap = {
-        0x00 : "",
-        0x01 : "-",
-        0x02 : "+",
-        0x03 : "split",
+        0x00: "",
+        0x01: "-",
+        0x02: "+",
+        0x03: "split",
         }
 
     return dupmap[val]
 
+
 def set_duplex(mmap, duplex):
     val = ord(mmap[POS_DUPLEX]) & 0xFC
 
     dupmap = {
-        ""      : 0x00,
-        "-"     : 0x01,
-        "+"     : 0x02,
-        "split" : 0x03,
+        "":       0x00,
+        "-":      0x01,
+        "+":      0x02,
+        "split":  0x03,
         }
-    
+
     mmap[POS_DUPLEX] = val | dupmap[duplex]
 
+
 def get_name(mmap):
     name = ""
     for x in mmap[POS_NAME:POS_NAME+4]:
@@ -196,44 +217,50 @@ def get_name(mmap):
         name += CHARSET[ord(x)]
     return name
 
+
 def set_name(mmap, name):
     val = ""
     for i in name[:4].ljust(4):
         val += chr(CHARSET.index(i))
     mmap[POS_NAME] = val
 
+
 def get_mode(mmap):
     val = ord(mmap[POS_MODE]) & 0x03
 
     modemap = {
-        0x00 : "FM",
-        0x01 : "AM",
-        0x02 : "WFM",
-        0x03 : "WFM",
+        0x00: "FM",
+        0x01: "AM",
+        0x02: "WFM",
+        0x03: "WFM",
         }
 
     return modemap[val]
 
+
 def set_mode(mmap, mode):
     val = ord(mmap[POS_MODE]) & 0xCF
 
     modemap = {
-        "FM"  : 0x00,
-        "AM"  : 0x01,
-        "WFM" : 0x02,
+        "FM":   0x00,
+        "AM":   0x01,
+        "WFM":  0x02,
         }
 
     mmap[POS_MODE] = val | modemap[mode]
 
+
 def get_used(mmap, number):
     return ord(mmap[POS_USED + number]) & 0x01
 
+
 def set_used(mmap, number, used):
     val = ord(mmap[POS_USED + number]) & 0xFC
     if used:
         val |= 0x03
     mmap[POS_USED + number] = val
-    
+
+
 def get_memory(map, number):
     index = number - 1
     mmap = get_raw_memory(map, index)
@@ -255,6 +282,7 @@ def get_memory(map, number):
 
     return mem
 
+
 def set_memory(_map, mem):
     index = mem.number - 1
     mmap = get_raw_memory(_map, index)
@@ -276,14 +304,16 @@ def set_memory(_map, mem):
 
     return _map
 
+
 def erase_memory(map, number):
     set_used(map, number-1, False)
     return map
 
+
 def update_checksum(map):
     cs = 0
     for i in range(0, 3722):
         cs += ord(map[i])
     cs %= 256
-    print "Checksum old=%02x new=%02x" % (ord(map[3722]), cs)
+    LOG.debug("Checksum old=%02x new=%02x" % (ord(map[3722]), cs))
     map[3722] = cs
diff --git a/chirp/drivers/ft60.py b/chirp/drivers/ft60.py
new file mode 100644
index 0000000..2bd29f7
--- /dev/null
+++ b/chirp/drivers/ft60.py
@@ -0,0 +1,775 @@
+# Copyright 2011 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/>.
+
+import time
+import os
+import logging
+
+from chirp.drivers import yaesu_clone
+from chirp import chirp_common, memmap, bitwise, directory, errors
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettingValueString, \
+    RadioSettingValueFloat, RadioSettings
+from textwrap import dedent
+
+LOG = logging.getLogger(__name__)
+
+ACK = "\x06"
+
+
+def _send(pipe, data):
+    pipe.write(data)
+    echo = pipe.read(len(data))
+    if echo != data:
+        raise errors.RadioError("Error reading echo (Bad cable?)")
+
+
+def _download(radio):
+    data = ""
+    for i in range(0, 10):
+        chunk = radio.pipe.read(8)
+        if len(chunk) == 8:
+            data += chunk
+            break
+        elif chunk:
+            raise Exception("Received invalid response from radio")
+        time.sleep(1)
+        LOG.info("Trying again...")
+
+    if not data:
+        raise Exception("Radio is not responding")
+
+    _send(radio.pipe, ACK)
+
+    for i in range(0, 448):
+        chunk = radio.pipe.read(64)
+        data += chunk
+        _send(radio.pipe, ACK)
+        if len(chunk) == 1 and i == 447:
+            break
+        elif len(chunk) != 64:
+            raise Exception("Reading block %i was short (%i)" %
+                            (i, len(chunk)))
+        if radio.status_fn:
+            status = chirp_common.Status()
+            status.cur = i * 64
+            status.max = radio.get_memsize()
+            status.msg = "Cloning from radio"
+            radio.status_fn(status)
+
+    return memmap.MemoryMap(data)
+
+
+def _upload(radio):
+    _send(radio.pipe, radio.get_mmap()[0:8])
+
+    ack = radio.pipe.read(1)
+    if ack != ACK:
+        raise Exception("Radio did not respond")
+
+    for i in range(0, 448):
+        offset = 8 + (i * 64)
+        _send(radio.pipe, radio.get_mmap()[offset:offset+64])
+        ack = radio.pipe.read(1)
+        if ack != ACK:
+            raise Exception(_("Radio did not ack block %i") % i)
+
+        if radio.status_fn:
+            status = chirp_common.Status()
+            status.cur = offset+64
+            status.max = radio.get_memsize()
+            status.msg = "Cloning to radio"
+            radio.status_fn(status)
+
+
+def _decode_freq(freqraw):
+    freq = int(freqraw) * 10000
+    if freq > 8000000000:
+        freq = (freq - 8000000000) + 5000
+
+    if freq > 4000000000:
+        freq -= 4000000000
+        for i in range(0, 3):
+            freq += 2500
+            if chirp_common.required_step(freq) == 12.5:
+                break
+
+    return freq
+
+
+def _encode_freq(freq):
+    freqraw = freq / 10000
+    flags = 0x00
+    if ((freq / 1000) % 10) >= 5:
+        flags += 0x80
+    if chirp_common.is_fractional_step(freq):
+        flags += 0x40
+    return freqraw, flags
+
+
+def _decode_name(mem):
+    name = ""
+    for i in mem:
+        if i == 0xFF:
+            break
+        try:
+            name += CHARSET[i]
+        except IndexError:
+            LOG.error("Unknown char index: %i " % (i))
+    return name
+
+
+def _encode_name(mem):
+    name = [None]*6
+    for i in range(0, 6):
+        try:
+            name[i] = CHARSET.index(mem[i])
+        except IndexError:
+            name[i] = CHARSET.index(" ")
+
+    return name
+
+
+MEM_FORMAT = """
+#seekto 0x0024;
+struct {
+  u8 apo;
+  u8 x25:3,
+     tot:5;
+  u8 x26;
+  u8 x27;
+  u8 x28:4,
+     rf_sql:4;
+  u8 x29:4,
+     int_cd:4;
+  u8 x2A:4,
+     int_mr:4;
+  u8 x2B:5,
+     lock:3;
+  u8 x2C:5,
+     dt_dly:3;
+  u8 x2D:7,
+     dt_spd:1;
+  u8 ar_bep;
+  u8 x2F:6,
+     lamp:2;
+  u8 x30:5,
+     bell:3;
+  u8 x31:5,
+     rxsave:3;
+  u8 x32;
+  u8 x33;
+  u8 x34;
+  u8 x35;
+  u8 x36;
+  u8 x37;
+  u8 wx_alt:1,
+     x38_1:3,
+     ar_int:1,
+     x38_5:3;
+  u8 x39:3,
+     ars:1,
+     vfo_bnd:1,
+     dcs_nr:2,
+     ssrch:1;
+  u8 pri_rvt:1,
+     x3A_1:1,
+     beep_sc:1,
+     edg_bep:1,
+     beep_key:1,
+     inet:2,
+     x3A_7:1;
+  u8 x3B_0:5,
+     scn_md:1,
+     x3B_6:2;
+  u8 x3C_0:2,
+     rev_hm:1,
+     mt_cl:1
+     resume:2,
+     txsave:1,
+     pag_abk:1;
+  u8 x3D_0:1,
+     scn_lmp:1,
+     x3D_2:1,
+     bsy_led:1,
+     x3D_4:1,
+     tx_led:1,
+     x3D_6:2;
+  u8 x3E_0:2,
+     bclo:1,
+     x3E_3:5;
+} settings;
+
+#seekto 0x09E;
+ul16 mbs;
+
+struct mem {
+  u8 used:1,
+     unknown1:1,
+     isnarrow:1,
+     isam:1,
+     duplex:4;
+  bbcd freq[3];
+  u8 unknown2:1,
+     step:3,
+     unknown2_1:1,
+     tmode:3;
+  bbcd tx_freq[3];
+  u8 power:2,
+     tone:6;
+  u8 unknown4:1,
+     dtcs:7;
+  u8 unknown5;
+  u16 unknown5_1:1
+      offset:15;
+  u8 unknown6[3];
+};
+
+#seekto 0x0248;
+struct mem memory[1000];
+
+#seekto 0x40c8;
+struct mem pms[100];
+
+#seekto 0x6EC8;
+// skips:2 for Memory M in [1, 1000] is in flags[(M-1)/4].skip((M-1)%4).
+// Interpret with SKIPS[].
+// PMS memories L0 - U50 aka memory 1001 - 1100 don't have skip flags.
+struct {
+  u8 skip3:2,
+     skip2:2,
+     skip1:2,
+     skip0:2;
+} flags[250];
+
+#seekto 0x4708;
+struct {
+  u8 name[6];
+  u8 use_name:1,
+     unknown1:7;
+  u8 valid:1,
+     unknown2:7;
+} names[1000];
+
+#seekto 0x69C8;
+struct {
+  bbcd memory[128];
+} banks[10];
+
+#seekto 0x6FC8;
+u8 checksum;
+"""
+
+DUPLEX = ["", "", "-", "+", "split", "off"]
+TMODES = ["", "Tone", "TSQL", "TSQL-R", "DTCS"]
+POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.0),
+                chirp_common.PowerLevel("Mid", watts=2.0),
+                chirp_common.PowerLevel("Low", watts=0.5)]
+STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0]
+SKIPS = ["", "S", "P"]
+CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ [?]^__|`?$%&-()*+,-,/|;/=>?@"
+SPECIALS = ["%s%d" % (c, i + 1) for i in range(0, 50) for c in ('L', 'U')]
+
+
+class FT60BankModel(chirp_common.BankModel):
+    def get_num_mappings(self):
+        return 10
+
+    def get_mappings(self):
+        banks = []
+        for i in range(0, self.get_num_mappings()):
+            bank = chirp_common.Bank(self, "%i" % (i + 1), "Bank %i" % (i + 1))
+            bank.index = i
+            banks.append(bank)
+        return banks
+
+    def add_memory_to_mapping(self, memory, bank):
+        number = (memory.number - 1) / 8
+        mask = 1 << ((memory.number - 1) & 7)
+        self._radio._memobj.banks[bank.index].memory[number].set_bits(mask)
+
+    def remove_memory_from_mapping(self, memory, bank):
+        number = (memory.number - 1) / 8
+        mask = 1 << ((memory.number - 1) & 7)
+        m = self._radio._memobj.banks[bank.index].memory[number]
+        if m.get_bits(mask) != mask:
+            raise Exception("Memory %i is not in bank %s." %
+                            (memory.number, bank))
+        self._radio._memobj.banks[bank.index].memory[number].clr_bits(mask)
+
+    def get_mapping_memories(self, bank):
+        memories = []
+        for i in range(*self._radio.get_features().memory_bounds):
+            number = (i - 1) / 8
+            mask = 1 << ((i - 1) & 7)
+            m = self._radio._memobj.banks[bank.index].memory[number]
+            if m.get_bits(mask) == mask:
+                memories.append(self._radio.get_memory(i))
+        return memories
+
+    def get_memory_mappings(self, memory):
+        banks = []
+        for bank in self.get_mappings():
+            number = (memory.number - 1) / 8
+            mask = 1 << ((memory.number - 1) & 7)
+            m = self._radio._memobj.banks[bank.index].memory[number]
+            if m.get_bits(mask) == mask:
+                banks.append(bank)
+        return banks
+
+
+ at directory.register
+class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
+    """Yaesu FT-60"""
+    BAUD_RATE = 9600
+    VENDOR = "Yaesu"
+    MODEL = "FT-60"
+    _model = "AH017"
+
+    _memsize = 28617
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.pre_download = _(dedent("""\
+1. Turn radio off.
+2. Connect cable to MIC/SP jack.
+3. Press and hold in the [MONI] switch while turning the
+     radio on.
+4. Rotate the DIAL job to select "F8 CLONE".
+5. Press the [F/W] key momentarily.
+6. <b>After clicking OK</b>, press the [PTT] switch to send image."""))
+        rp.pre_upload = _(dedent("""\
+1. Turn radio off.
+2. Connect cable to MIC/SP jack.
+3. Press and hold in the [MONI] switch while turning the
+     radio on.
+4. Rotate the DIAL job to select "F8 CLONE".
+5. Press the [F/W] key momentarily.
+6. Press the [MONI] switch ("--RX--" will appear on the LCD)."""))
+        return rp
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.memory_bounds = (1, 1000)
+        rf.valid_duplexes = DUPLEX[1:]
+        rf.valid_tmodes = TMODES
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_tuning_steps = STEPS
+        rf.valid_skips = SKIPS
+        rf.valid_special_chans = SPECIALS
+        rf.valid_characters = CHARSET
+        rf.valid_name_length = 6
+        rf.valid_modes = ["FM", "NFM", "AM"]
+        rf.valid_bands = [(108000000, 520000000), (700000000, 999990000)]
+        rf.can_odd_split = True
+        rf.has_ctone = False
+        rf.has_bank = True
+        rf.has_settings = True
+        rf.has_dtcs_polarity = False
+
+        return rf
+
+    def get_bank_model(self):
+        return FT60BankModel(self)
+
+    def _checksums(self):
+        return [yaesu_clone.YaesuChecksum(0x0000, 0x6FC7)]
+
+    def sync_in(self):
+        try:
+            self._mmap = _download(self)
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            raise errors.RadioError("Failed to communicate with radio: %s" % e)
+        self.process_mmap()
+        self.check_checksums()
+
+    def sync_out(self):
+        self.update_checksums()
+        try:
+            _upload(self)
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            raise errors.RadioError("Failed to communicate with radio: %s" % e)
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+    def get_settings(self):
+        _settings = self._memobj.settings
+
+        repeater = RadioSettingGroup("repeater", "Repeater Settings")
+        ctcss = RadioSettingGroup("ctcss", "CTCSS/DCS/DTMF Settings")
+        arts = RadioSettingGroup("arts", "ARTS Settings")
+        scan = RadioSettingGroup("scan", "Scan Settings")
+        power = RadioSettingGroup("power", "Power Saver Settings")
+        wires = RadioSettingGroup("wires", "WiRES(tm) Settings")
+        eai = RadioSettingGroup("eai", "EAI/EPCS Settings")
+        switch = RadioSettingGroup("switch", "Switch/Knob Settings")
+        misc = RadioSettingGroup("misc", "Miscellaneous Settings")
+        mbls = RadioSettingGroup("banks", "Memory Bank Link Scan")
+
+        setmode = RadioSettings(repeater, ctcss, arts, scan, power,
+                                wires, eai, switch, misc, mbls)
+
+        # APO
+        opts = ["OFF"] + ["%0.1f" % (x * 0.5) for x in range(1, 24+1)]
+        misc.append(
+            RadioSetting(
+                "apo", "Automatic Power Off",
+                RadioSettingValueList(opts, opts[_settings.apo])))
+
+        # AR.BEP
+        opts = ["OFF", "INRANG", "ALWAYS"]
+        arts.append(
+            RadioSetting(
+                "ar_bep", "ARTS Beep",
+                RadioSettingValueList(opts, opts[_settings.ar_bep])))
+
+        # AR.INT
+        opts = ["25 SEC", "15 SEC"]
+        arts.append(
+            RadioSetting(
+                "ar_int", "ARTS Polling Interval",
+                RadioSettingValueList(opts, opts[_settings.ar_int])))
+
+        # ARS
+        opts = ["OFF", "ON"]
+        repeater.append(
+            RadioSetting(
+                "ars", "Automatic Repeater Shift",
+                RadioSettingValueList(opts, opts[_settings.ars])))
+
+        # BCLO
+        opts = ["OFF", "ON"]
+        misc.append(RadioSetting(
+                "bclo", "Busy Channel Lock-Out",
+                RadioSettingValueList(opts, opts[_settings.bclo])))
+
+        # BEEP
+        opts = ["OFF", "KEY", "KEY+SC"]
+        rs = RadioSetting(
+                "beep_key", "Enable the Beeper",
+                RadioSettingValueList(
+                    opts, opts[_settings.beep_key + _settings.beep_sc]))
+
+        def apply_beep(s, obj):
+            setattr(obj, "beep_key",
+                    (int(s.value) & 1) or ((int(s.value) >> 1) & 1))
+            setattr(obj, "beep_sc", (int(s.value) >> 1) & 1)
+        rs.set_apply_callback(apply_beep, self._memobj.settings)
+        switch.append(rs)
+
+        # BELL
+        opts = ["OFF", "1T", "3T", "5T", "8T", "CONT"]
+        ctcss.append(RadioSetting("bell", "Bell Repetitions",
+                     RadioSettingValueList(opts, opts[_settings.bell])))
+
+        # BSY.LED
+        opts = ["ON", "OFF"]
+        misc.append(RadioSetting("bsy_led", "Busy LED",
+                    RadioSettingValueList(opts, opts[_settings.bsy_led])))
+
+        # DCS.NR
+        opts = ["TR/X N", "RX R", "TX R", "T/RX R"]
+        ctcss.append(RadioSetting("dcs_nr", "\"Inverted\" DCS Code Decoding",
+                     RadioSettingValueList(opts, opts[_settings.dcs_nr])))
+
+        # DT.DLY
+        opts = ["50 MS", "100 MS", "250 MS", "450 MS", "750 MS", "1000 MS"]
+        ctcss.append(RadioSetting("dt_dly", "DTMF Autodialer Delay Time",
+                     RadioSettingValueList(opts, opts[_settings.dt_dly])))
+
+        # DT.SPD
+        opts = ["50 MS", "100 MS"]
+        ctcss.append(RadioSetting("dt_spd", "DTMF Autodialer Sending Speed",
+                     RadioSettingValueList(opts, opts[_settings.dt_spd])))
+
+        # EDG.BEP
+        opts = ["OFF", "ON"]
+        misc.append(RadioSetting("edg_bep", "Band Edge Beeper",
+                    RadioSettingValueList(opts, opts[_settings.edg_bep])))
+
+        # I.NET
+        opts = ["OFF", "COD", "MEM"]
+        rs = RadioSetting("inet", "Internet Link Connection",
+                          RadioSettingValueList(
+                              opts, opts[_settings.inet - 1]))
+
+        def apply_inet(s, obj):
+            setattr(obj, s.get_name(), int(s.value) + 1)
+        rs.set_apply_callback(apply_inet, self._memobj.settings)
+        wires.append(rs)
+
+        # INT.CD
+        opts = ["CODE 0", "CODE 1", "CODE 2", "CODE 3", "CODE 4",
+                "CODE 5", "CODE 6", "CODE 7", "CODE 8", "CODE 9",
+                "CODE A", "CODE B", "CODE C", "CODE D", "CODE E", "CODE F"]
+        wires.append(RadioSetting("int_cd", "Access Number for WiRES(TM)",
+                     RadioSettingValueList(opts, opts[_settings.int_cd])))
+
+        # INT.MR
+        opts = ["d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"]
+        wires.append(RadioSetting(
+                    "int_mr", "Access Number (DTMF) for Non-WiRES(TM)",
+                     RadioSettingValueList(opts, opts[_settings.int_mr])))
+
+        # LAMP
+        opts = ["KEY", "5SEC", "TOGGLE"]
+        switch.append(RadioSetting("lamp", "Lamp Mode",
+                      RadioSettingValueList(opts, opts[_settings.lamp])))
+
+        # LOCK
+        opts = ["LK KEY", "LKDIAL", "LK K+D", "LK PTT",
+                "LP P+K", "LK P+D", "LK ALL"]
+        rs = RadioSetting("lock", "Control Locking",
+                          RadioSettingValueList(
+                              opts, opts[_settings.lock - 1]))
+
+        def apply_lock(s, obj):
+            setattr(obj, s.get_name(), int(s.value) + 1)
+        rs.set_apply_callback(apply_lock, self._memobj.settings)
+        switch.append(rs)
+
+        # M/T-CL
+        opts = ["MONI", "T-CALL"]
+        switch.append(RadioSetting("mt_cl", "MONI Switch Function",
+                      RadioSettingValueList(opts, opts[_settings.mt_cl])))
+
+        # PAG.ABK
+        opts = ["OFF", "ON"]
+        eai.append(RadioSetting("pag_abk", "Paging Answer Back",
+                   RadioSettingValueList(opts, opts[_settings.pag_abk])))
+
+        # RESUME
+        opts = ["TIME", "HOLD", "BUSY"]
+        scan.append(RadioSetting("resume", "Scan Resume Mode",
+                    RadioSettingValueList(opts, opts[_settings.resume])))
+
+        # REV/HM
+        opts = ["REV", "HOME"]
+        switch.append(RadioSetting("rev_hm", "HM/RV Key Function",
+                      RadioSettingValueList(opts, opts[_settings.rev_hm])))
+
+        # RF.SQL
+        opts = ["OFF", "S-1", "S-2", "S-3", "S-4", "S-5", "S-6",
+                "S-7", "S-8", "S-FULL"]
+        misc.append(RadioSetting("rf_sql", "RF Squelch Threshold",
+                    RadioSettingValueList(opts, opts[_settings.rf_sql])))
+
+        # PRI.RVT
+        opts = ["OFF", "ON"]
+        scan.append(RadioSetting("pri_rvt", "Priority Revert",
+                    RadioSettingValueList(opts, opts[_settings.pri_rvt])))
+
+        # RXSAVE
+        opts = ["OFF", "200 MS", "300 MS", "500 MS", "1 S", "2 S"]
+        power.append(RadioSetting(
+                    "rxsave", "Receive Mode Batery Savery Interval",
+                     RadioSettingValueList(opts, opts[_settings.rxsave])))
+
+        # S.SRCH
+        opts = ["SINGLE", "CONT"]
+        misc.append(RadioSetting("ssrch", "Smart Search Sweep Mode",
+                    RadioSettingValueList(opts, opts[_settings.ssrch])))
+
+        # SCN.MD
+        opts = ["MEM", "ONLY"]
+        scan.append(RadioSetting(
+                    "scn_md", "Memory Scan Channel Selection Mode",
+                    RadioSettingValueList(opts, opts[_settings.scn_md])))
+
+        # SCN.LMP
+        opts = ["OFF", "ON"]
+        scan.append(RadioSetting("scn_lmp", "Scan Lamp",
+                    RadioSettingValueList(opts, opts[_settings.scn_lmp])))
+
+        # TOT
+        opts = ["OFF"] + ["%dMIN" % (x) for x in range(1, 30+1)]
+        misc.append(RadioSetting("tot", "Timeout Timer",
+                    RadioSettingValueList(opts, opts[_settings.tot])))
+
+        # TX.LED
+        opts = ["ON", "OFF"]
+        misc.append(RadioSetting("tx_led", "TX LED",
+                    RadioSettingValueList(opts, opts[_settings.tx_led])))
+
+        # TXSAVE
+        opts = ["OFF", "ON"]
+        power.append(RadioSetting("txsave", "Transmitter Battery Saver",
+                     RadioSettingValueList(opts, opts[_settings.txsave])))
+
+        # VFO.BND
+        opts = ["BAND", "ALL"]
+        misc.append(RadioSetting("vfo_bnd", "VFO Band Edge Limiting",
+                    RadioSettingValueList(opts, opts[_settings.vfo_bnd])))
+
+        # WX.ALT
+        opts = ["OFF", "ON"]
+        scan.append(RadioSetting("wx_alt", "Weather Alert Scan",
+                    RadioSettingValueList(opts, opts[_settings.wx_alt])))
+
+        # MBS
+        for i in range(0, 10):
+            opts = ["OFF", "ON"]
+            mbs = (self._memobj.mbs >> i) & 1
+            rs = RadioSetting("mbs%i" % i, "Bank %s Scan" % (i + 1),
+                              RadioSettingValueList(opts, opts[mbs]))
+
+            def apply_mbs(s, index):
+                if int(s.value):
+                    self._memobj.mbs |= (1 << index)
+                else:
+                    self._memobj.mbs &= ~(1 << index)
+            rs.set_apply_callback(apply_mbs, i)
+            mbls.append(rs)
+
+        return setmode
+
+    def set_settings(self, uisettings):
+        _settings = self._memobj.settings
+        for element in uisettings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+            if not element.changed():
+                continue
+
+            try:
+                name = element.get_name()
+                value = element.value
+
+                if element.has_apply_callback():
+                    LOG.debug("Using apply callback")
+                    element.run_apply_callback()
+                else:
+                    obj = getattr(_settings, name)
+                    setattr(_settings, name, value)
+
+                LOG.debug("Setting %s: %s" % (name, value))
+            except Exception, e:
+                LOG.debug(element.get_name())
+                raise
+
+    def get_raw_memory(self, number):
+        return repr(self._memobj.memory[number - 1]) + \
+            repr(self._memobj.flags[(number - 1) / 4]) + \
+            repr(self._memobj.names[number - 1])
+
+    def get_memory(self, number):
+
+        mem = chirp_common.Memory()
+
+        if isinstance(number, str):
+            # pms channel
+            mem.number = 1001 + SPECIALS.index(number)
+            mem.extd_number = number
+            mem.immutable = ["number", "extd_number", "name", "skip"]
+            _mem = self._memobj.pms[mem.number - 1001]
+            _nam = _skp = None
+        elif number > 1000:
+            # pms channel
+            mem.number = number
+            mem.extd_number = SPECIALS[number - 1001]
+            mem.immutable = ["number", "extd_number", "name", "skip"]
+            _mem = self._memobj.pms[mem.number - 1001]
+            _nam = _skp = None
+        else:
+            mem.number = number
+            _mem = self._memobj.memory[mem.number - 1]
+            _nam = self._memobj.names[mem.number - 1]
+            _skp = self._memobj.flags[(mem.number - 1) / 4]
+
+        if not _mem.used:
+            mem.empty = True
+            return mem
+
+        mem.freq = _decode_freq(_mem.freq)
+        mem.offset = int(_mem.offset) * 50000
+        mem.duplex = DUPLEX[_mem.duplex]
+        if mem.duplex == "split":
+            if int(_mem.tx_freq) == 0:
+                mem.duplex = "off"
+            else:
+                mem.offset = _decode_freq(_mem.tx_freq)
+        mem.tmode = TMODES[_mem.tmode]
+        mem.rtone = chirp_common.TONES[_mem.tone]
+        mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
+        mem.power = POWER_LEVELS[_mem.power]
+        mem.mode = _mem.isam and "AM" or _mem.isnarrow and "NFM" or "FM"
+        mem.tuning_step = STEPS[_mem.step]
+
+        if _skp is not None:
+            skip = _skp["skip%i" % ((mem.number - 1) % 4)]
+            mem.skip = SKIPS[skip]
+
+        if _nam is not None:
+            if _nam.use_name and _nam.valid:
+                mem.name = _decode_name(_nam.name).rstrip()
+
+        return mem
+
+    def set_memory(self, mem):
+
+        if mem.number > 1000:
+            # pms channel
+            _mem = self._memobj.pms[mem.number - 1001]
+            _nam = _skp = None
+        else:
+            _mem = self._memobj.memory[mem.number - 1]
+            _nam = self._memobj.names[mem.number - 1]
+            _skp = self._memobj.flags[(mem.number - 1) / 4]
+
+        assert(_mem)
+        if mem.empty:
+            _mem.used = False
+            return
+
+        if not _mem.used:
+            _mem.set_raw("\x00" * 16)
+            _mem.used = 1
+
+        _mem.freq, flags = _encode_freq(mem.freq)
+        _mem.freq[0].set_bits(flags)
+        if mem.duplex == "split":
+            _mem.tx_freq, flags = _encode_freq(mem.offset)
+            _mem.tx_freq[0].set_bits(flags)
+            _mem.offset = 0
+        elif mem.duplex == "off":
+            _mem.tx_freq = 0
+            _mem.offset = 0
+        else:
+            _mem.tx_freq = 0
+            _mem.offset = mem.offset / 50000
+        _mem.duplex = DUPLEX.index(mem.duplex)
+        _mem.tmode = TMODES.index(mem.tmode)
+        _mem.tone = chirp_common.TONES.index(mem.rtone)
+        _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
+        _mem.power = mem.power and POWER_LEVELS.index(mem.power) or 0
+        _mem.isnarrow = mem.mode == "NFM"
+        _mem.isam = mem.mode == "AM"
+        _mem.step = STEPS.index(mem.tuning_step)
+
+        if _skp is not None:
+            _skp["skip%i" % ((mem.number - 1) % 4)] = SKIPS.index(mem.skip)
+
+        if _nam is not None:
+            _nam.name = _encode_name(mem.name)
+            _nam.use_name = mem.name.strip() and True or False
+            _nam.valid = _nam.use_name
diff --git a/chirp/ft7800.py b/chirp/drivers/ft7800.py
similarity index 78%
rename from chirp/ft7800.py
rename to chirp/drivers/ft7800.py
index 6fd5601..a391a8b 100644
--- a/chirp/ft7800.py
+++ b/chirp/drivers/ft7800.py
@@ -14,20 +14,21 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import time
-from chirp import chirp_common, yaesu_clone, memmap, directory
-from chirp import bitwise, errors
+import logging
+import os
+import re
+
+from chirp.drivers import yaesu_clone
+from chirp import chirp_common, memmap, directory, bitwise, errors
 from textwrap import dedent
 from chirp.settings import RadioSetting, RadioSettingGroup, \
     RadioSettingValueInteger, RadioSettingValueList, \
-    RadioSettingValueBoolean, RadioSettingValueString
-import os, re
+    RadioSettingValueBoolean, RadioSettingValueString, \
+    RadioSettings
 
 from collections import defaultdict
 
-if os.getenv("CHIRP_DEBUG"):
-    CHIRP_DEBUG = True
-else:
-    CHIRP_DEBUG = False
+LOG = logging.getLogger(__name__)
 
 ACK = chr(0x06)
 
@@ -97,8 +98,8 @@ struct mem_struct {
   bbcd freq[3];
   u8 clockshift:1,
      tune_step:3,
-     unknown5:2, // TODO: tmode has extended settings, at least 4 bits
-     tmode:2;
+     unknown5:1, // TODO: tmode has extended settings, at least 4 bits
+     tmode:3;
   bbcd split[3];
   u8 power:2,
      tone:6;
@@ -148,9 +149,8 @@ u8 checksum;
 """
 
 MODES = ["FM", "AM", "NFM"]
-TMODES = ["", "Tone", "TSQL", "DTCS"]
 DUPLEX = ["", "", "-", "+", "split"]
-STEPS =  [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0]
+STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0]
 SKIPS = ["", "S", "P", ""]
 
 CHARSET = ["%i" % int(x) for x in range(0, 10)] + \
@@ -159,17 +159,8 @@ CHARSET = ["%i" % int(x) for x in range(0, 10)] + \
     list("*+,- /|      [ ] _") + \
     list("\x00" * 100)
 
-DTMFCHARSET = list("0123456789ABCD*#")           
-
-POWER_LEVELS_VHF = [chirp_common.PowerLevel("Hi", watts=50),
-                    chirp_common.PowerLevel("Mid1", watts=20),
-                    chirp_common.PowerLevel("Mid2", watts=10),
-                    chirp_common.PowerLevel("Low", watts=5)]
+DTMFCHARSET = list("0123456789ABCD*#")
 
-POWER_LEVELS_UHF = [chirp_common.PowerLevel("Hi", watts=35),
-                    chirp_common.PowerLevel("Mid1", watts=20),
-                    chirp_common.PowerLevel("Mid2", watts=10),
-                    chirp_common.PowerLevel("Low", watts=5)]
 
 def _send(ser, data):
     for i in data:
@@ -179,6 +170,7 @@ def _send(ser, data):
     if echo != data:
         raise errors.RadioError("Error reading echo (Bad cable?)")
 
+
 def _download(radio):
     data = ""
 
@@ -194,10 +186,10 @@ def _download(radio):
 
     _send(radio.pipe, ACK)
 
-    for i in range(0, radio._block_lengths[1], 64):
-        chunk = radio.pipe.read(64)
+    for i in range(0, radio._block_lengths[1], radio._block_size):
+        chunk = radio.pipe.read(radio._block_size)
         data += chunk
-        if len(chunk) != 64:
+        if len(chunk) != radio._block_size:
             break
         time.sleep(0.01)
         _send(radio.pipe, ACK)
@@ -213,13 +205,14 @@ def _download(radio):
 
     return memmap.MemoryMap(data)
 
+
 def _upload(radio):
     cur = 0
     for block in radio._block_lengths:
-        for _i in range(0, block, 64):
-            length = min(64, block)
-            #print "i=%i length=%i range: %i-%i" % (i, length,
-            #                                       cur, cur+length)
+        for _i in range(0, block, radio._block_size):
+            length = min(radio._block_size, block)
+            # LOG.debug("i=%i length=%i range: %i-%i" %
+            #           (i, length, cur, cur+length))
             _send(radio.pipe, radio.get_mmap()[cur:cur+length])
             if radio.pipe.read(1) != ACK:
                 raise errors.RadioError("Radio did not ack block at %i" % cur)
@@ -233,6 +226,7 @@ def _upload(radio):
                 status.msg = "Cloning to radio"
                 radio.status_fn(status)
 
+
 def get_freq(rawfreq):
     """Decode a frequency that may include a fractional step flag"""
     # Ugh.  The 0x80 and 0x40 indicate values to add to get the
@@ -245,6 +239,7 @@ def get_freq(rawfreq):
 
     return rawfreq
 
+
 def set_freq(freq, obj, field):
     """Encode a frequency with any necessary fractional step flags"""
     obj[field] = freq / 10000
@@ -253,40 +248,52 @@ def set_freq(freq, obj, field):
 
     if (freq % 10000) >= 5000:
         obj[field][0].set_bits(0x80)
-        
+
     return freq
 
+
 class FTx800Radio(yaesu_clone.YaesuCloneModeRadio):
     """Base class for FT-7800,7900,8800,8900 radios"""
     BAUD_RATE = 9600
     VENDOR = "Yaesu"
     MODES = list(MODES)
+    _block_size = 64
+
+    POWER_LEVELS_VHF = [chirp_common.PowerLevel("Hi", watts=50),
+                        chirp_common.PowerLevel("Mid1", watts=20),
+                        chirp_common.PowerLevel("Mid2", watts=10),
+                        chirp_common.PowerLevel("Low", watts=5)]
+
+    POWER_LEVELS_UHF = [chirp_common.PowerLevel("Hi", watts=35),
+                        chirp_common.PowerLevel("Mid1", watts=20),
+                        chirp_common.PowerLevel("Mid2", watts=10),
+                        chirp_common.PowerLevel("Low", watts=5)]
 
     @classmethod
     def get_prompts(cls):
         rp = chirp_common.RadioPrompts()
         rp.pre_download = _(dedent("""\
-            1. Turn radio off.
-            2. Connect cable to DATA jack.
-            3. Press and hold in the [MHz(PRI)] key while turning the
-                 radio on.
-            4. Rotate the DIAL job to select "F-7 CLONE".
-            5. Press and hold in the [BAND(SET)] key. The display
-                 will disappear for a moment, then the "CLONE" notation
-                 will appear.
-            6. <b>After clicking OK</b>, press the [V/M(MW)] key to send image."""))
+1. Turn radio off.
+2. Connect cable to DATA jack.
+3. Press and hold in the [MHz(PRI)] key while turning the
+ radio on.
+4. Rotate the DIAL job to select "F-7 CLONE".
+5. Press and hold in the [BAND(SET)] key. The display
+ will disappear for a moment, then the "CLONE" notation
+ will appear.
+6. <b>After clicking OK</b>, press the [V/M(MW)] key to send image."""))
         rp.pre_upload = _(dedent("""\
-            1. Turn radio off.
-            2. Connect cable to DATA jack.
-            3. Press and hold in the [MHz(PRI)] key while turning the
-                 radio on.
-            4. Rotate the DIAL job to select "F-7 CLONE".
-            5. Press and hold in the [BAND(SET)] key. The display
-                 will disappear for a moment, then the "CLONE" notation
-                 will appear.
-            6. Press the [LOW(ACC)] key ("--RX--" will appear on the display)."""))
+1. Turn radio off.
+2. Connect cable to DATA jack.
+3. Press and hold in the [MHz(PRI)] key while turning the
+     radio on.
+4. Rotate the DIAL job to select "F-7 CLONE".
+5. Press and hold in the [BAND(SET)] key. The display
+     will disappear for a moment, then the "CLONE" notation
+     will appear.
+6. Press the [LOW(ACC)] key ("--RX--" will appear on the display)."""))
         return rp
-        
+
     def get_features(self):
         rf = chirp_common.RadioFeatures()
         rf.memory_bounds = (1, 999)
@@ -294,19 +301,19 @@ class FTx800Radio(yaesu_clone.YaesuCloneModeRadio):
         rf.has_ctone = False
         rf.has_dtcs_polarity = False
         rf.valid_modes = MODES
-        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
+        rf.valid_tmodes = self.TMODES
         rf.valid_duplexes = ["", "-", "+", "split"]
         rf.valid_tuning_steps = STEPS
         rf.valid_bands = [(108000000, 520000000), (700000000, 990000000)]
         rf.valid_skips = ["", "S", "P"]
-        rf.valid_power_levels = POWER_LEVELS_VHF
+        rf.valid_power_levels = self.POWER_LEVELS_VHF
         rf.valid_characters = "".join(CHARSET)
         rf.valid_name_length = 6
         rf.can_odd_split = True
         return rf
 
     def _checksums(self):
-        return [ yaesu_clone.YaesuChecksum(0x0000, 0x7B47) ]
+        return [yaesu_clone.YaesuChecksum(0x0000, 0x7B47)]
 
     def sync_in(self):
         start = time.time()
@@ -316,7 +323,7 @@ class FTx800Radio(yaesu_clone.YaesuCloneModeRadio):
             raise
         except Exception, e:
             raise errors.RadioError("Failed to communicate with radio: %s" % e)
-        print "Download finished in %i seconds" % (time.time() - start)
+        LOG.info("Download finished in %i seconds" % (time.time() - start))
         self.check_checksums()
         self.process_mmap()
 
@@ -332,7 +339,7 @@ class FTx800Radio(yaesu_clone.YaesuCloneModeRadio):
             raise
         except Exception, e:
             raise errors.RadioError("Failed to communicate with radio: %s" % e)
-        print "Upload finished in %i seconds" % (time.time() - start)
+        LOG.info("Upload finished in %i seconds" % (time.time() - start))
 
     def get_raw_memory(self, number):
         return repr(self._memobj.memory[number-1])
@@ -392,7 +399,7 @@ class FTx800Radio(yaesu_clone.YaesuCloneModeRadio):
 
         mem.freq = get_freq(int(_mem.freq) * 10000)
         mem.rtone = chirp_common.TONES[_mem.tone]
-        mem.tmode = TMODES[_mem.tmode]
+        mem.tmode = self.TMODES[_mem.tmode]
         mem.mode = self.MODES[_mem.mode]
         mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
         if self.get_features().has_tuning_step:
@@ -402,9 +409,9 @@ class FTx800Radio(yaesu_clone.YaesuCloneModeRadio):
         mem.name = self._get_mem_name(mem, _mem)
 
         if int(mem.freq / 100) == 4:
-            mem.power = POWER_LEVELS_UHF[_mem.power]
+            mem.power = self.POWER_LEVELS_UHF[_mem.power]
         else:
-            mem.power = POWER_LEVELS_VHF[_mem.power]
+            mem.power = self.POWER_LEVELS_VHF[_mem.power]
 
         mem.skip = self._get_mem_skip(mem, _mem)
 
@@ -419,18 +426,18 @@ class FTx800Radio(yaesu_clone.YaesuCloneModeRadio):
 
         set_freq(mem.freq, _mem, "freq")
         _mem.tone = chirp_common.TONES.index(mem.rtone)
-        _mem.tmode = TMODES.index(mem.tmode)
+        _mem.tmode = self.TMODES.index(mem.tmode)
         _mem.mode = self.MODES.index(mem.mode)
         _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
         if self.get_features().has_tuning_step:
             _mem.tune_step = STEPS.index(mem.tuning_step)
         _mem.duplex = DUPLEX.index(mem.duplex)
-        _mem.split = mem.duplex == "split" and int (mem.offset / 10000) or 0
+        _mem.split = mem.duplex == "split" and int(mem.offset / 10000) or 0
         if mem.power:
-            _mem.power = POWER_LEVELS_VHF.index(mem.power)
+            _mem.power = self.POWER_LEVELS_VHF.index(mem.power)
         else:
             _mem.power = 0
-        _mem.unknown5 = 0 # Make sure we don't leave garbage here
+        _mem.unknown5 = 0  # Make sure we don't leave garbage here
 
         # NB: Leave offset after mem name for the 8800!
         self._set_mem_name(mem, _mem)
@@ -438,6 +445,7 @@ class FTx800Radio(yaesu_clone.YaesuCloneModeRadio):
 
         self._set_mem_skip(mem, _mem)
 
+
 class FT7800BankModel(chirp_common.BankModel):
     """Yaesu FT-7800/7900 bank model"""
     def __init__(self, radio):
@@ -485,7 +493,7 @@ class FT7800BankModel(chirp_common.BankModel):
         if not (_bitmap.bitmap[index / 32] & (1 << ishft)):
             raise Exception("Memory {num} is " +
                             "not in bank {bank}".format(num=memory.number,
-                                                           bank=bank))
+                                                        bank=bank))
         _bitmap.bitmap[index / 32] &= ~(1 << ishft)
         self.__b2m_cache[bank.index].remove(memory.number)
         self.__m2b_cache[memory.number].remove(bank.index)
@@ -493,8 +501,9 @@ class FT7800BankModel(chirp_common.BankModel):
     def _get_bank_memories(self, bank):
         memories = []
         upper = self._radio.get_features().memory_bounds[1]
+        c = self._radio._memobj.bank_channels[bank.index]
         for i in range(0, upper):
-            _bitmap = self._radio._memobj.bank_channels[bank.index].bitmap[i/32]
+            _bitmap = c.bitmap[i / 32]
             ishft = 31 - (i % 32)
             if _bitmap & (1 << ishft):
                 memories.append(i + 1)
@@ -512,6 +521,7 @@ class FT7800BankModel(chirp_common.BankModel):
         _banks = self.get_mappings()
         return [_banks[b] for b in self.__m2b_cache[memory.number]]
 
+
 @directory.register
 class FT7800Radio(FTx800Radio):
     """Yaesu FT-7800"""
@@ -520,7 +530,8 @@ class FT7800Radio(FTx800Radio):
     _model = "AH016"
     _memsize = 31561
     _block_lengths = [8, 31552, 1]
-    
+    TMODES = ["", "Tone", "TSQL", "TSQL-R", "DTCS"]
+
     def get_bank_model(self):
         return FT7800BankModel(self)
 
@@ -536,20 +547,18 @@ class FT7800Radio(FTx800Radio):
         FTx800Radio.set_memory(self, memory)
 
     def _decode_chars(self, inarr):
-        if CHIRP_DEBUG:
-            print "@_decode_chars, type: %s" % type(inarr)
-            print inarr
+        LOG.debug("@_decode_chars, type: %s" % type(inarr))
+        LOG.debug(inarr)
         outstr = ""
         for i in inarr:
             if i == 0xFF:
                 break
             outstr += CHARSET[i & 0x7F]
         return outstr.rstrip()
-            
-    def _encode_chars(self, instr, length = 16):
-        if CHIRP_DEBUG:
-            print "@_encode_chars, type: %s" % type(instr)
-            print instr
+
+    def _encode_chars(self, instr, length=16):
+        LOG.debug("@_encode_chars, type: %s" % type(instr))
+        LOG.debug(instr)
         outarr = []
         instr = str(instr)
         for i in range(length):
@@ -565,73 +574,90 @@ class FT7800Radio(FTx800Radio):
         dtmf = RadioSettingGroup("dtmf", "DTMF")
         arts = RadioSettingGroup("arts", "ARTS")
         prog = RadioSettingGroup("prog", "Programmable Buttons")
-        top = RadioSettingGroup("top", "All Settings",
-                basic, dtmf, arts, prog)
 
-        basic.append( RadioSetting("priority_revert", "Priority Revert",
+        top = RadioSettings(basic, dtmf, arts, prog)
+
+        basic.append(RadioSetting(
+                "priority_revert", "Priority Revert",
                 RadioSettingValueBoolean(_settings.priority_revert)))
 
-        basic.append( RadioSetting("memory_only", "Memory Only mode",
+        basic.append(RadioSetting(
+                "memory_only", "Memory Only mode",
                 RadioSettingValueBoolean(_settings.memory_only)))
-        
-        opts = ["off"] + [ "%0.1f" % (t / 60.0) for t in range(30, 750, 30) ]
-        basic.append( RadioSetting("apo", "APO time (hrs)",
+
+        opts = ["off"] + ["%0.1f" % (t / 60.0) for t in range(30, 750, 30)]
+        basic.append(RadioSetting(
+                "apo", "APO time (hrs)",
                 RadioSettingValueList(opts, opts[_settings.apo])))
 
-        basic.append( RadioSetting("beep_scan", "Beep: Scan",
+        basic.append(RadioSetting(
+                "beep_scan", "Beep: Scan",
                 RadioSettingValueBoolean(_settings.beep_scan)))
 
-        basic.append( RadioSetting("beep_edge", "Beep: Edge",
+        basic.append(RadioSetting(
+                "beep_edge", "Beep: Edge",
                 RadioSettingValueBoolean(_settings.beep_edge)))
 
-        basic.append( RadioSetting("beep_key", "Beep: Key",
+        basic.append(RadioSetting(
+                "beep_key", "Beep: Key",
                 RadioSettingValueBoolean(_settings.beep_key)))
-        
+
         opts = ["T/RX Normal", "RX Reverse", "TX Reverse", "T/RX Reverse"]
-        basic.append( RadioSetting("dcs_polarity", "DCS polarity",
+        basic.append(RadioSetting(
+                "dcs_polarity", "DCS polarity",
                 RadioSettingValueList(opts, opts[_settings.dcs_polarity])))
-        
+
         opts = ["off", "dim 1", "dim 2", "dim 3"]
-        basic.append( RadioSetting("dimmer", "Dimmer",
+        basic.append(RadioSetting(
+                "dimmer", "Dimmer",
                 RadioSettingValueList(opts, opts[_settings.dimmer])))
 
         opts = ["manual", "auto", "1-auto"]
-        basic.append( RadioSetting("hyper_write", "Hyper Write",
+        basic.append(RadioSetting(
+                "hyper_write", "Hyper Write",
                 RadioSettingValueList(opts, opts[_settings.hyper_write])))
-        
-        opts = ["", "key", "dial", "key+dial", "ptt", 
+
+        opts = ["", "key", "dial", "key+dial", "ptt",
                 "ptt+key", "ptt+dial", "all"]
-        basic.append( RadioSetting("lock", "Lock mode",
-                RadioSettingValueList(opts, opts[_settings.lock]))) 
+        basic.append(RadioSetting(
+                "lock", "Lock mode",
+                RadioSettingValueList(opts, opts[_settings.lock])))
 
         opts = ["MH-42", "MH-48"]
-        basic.append( RadioSetting("microphone_type", "Microphone Type",
+        basic.append(RadioSetting(
+                "microphone_type", "Microphone Type",
                 RadioSettingValueList(opts, opts[_settings.microphone_type])))
 
-        opts = ["off"] + ["S-%d" % n for n in range(2, 10) ] + ["S-Full"]
-        basic.append( RadioSetting("rf_sql", "RF Squelch",
+        opts = ["off"] + ["S-%d" % n for n in range(2, 10)] + ["S-Full"]
+        basic.append(RadioSetting(
+                "rf_sql", "RF Squelch",
                 RadioSettingValueList(opts, opts[_settings.rf_sql])))
 
         opts = ["time", "hold", "busy"]
-        basic.append( RadioSetting("scan_resume", "Scan Resume",
+        basic.append(RadioSetting(
+                "scan_resume", "Scan Resume",
                 RadioSettingValueList(opts, opts[_settings.scan_resume])))
 
         opts = ["single", "continuous"]
-        basic.append( RadioSetting("smart_search", "Smart Search",
+        basic.append(RadioSetting(
+                "smart_search", "Smart Search",
                 RadioSettingValueList(opts, opts[_settings.smart_search])))
 
-        opts = ["off"] + [ "%d" % t for t in range(1, 31) ]
-        basic.append( RadioSetting("tot", "Time-out timer (mins)",
+        opts = ["off"] + ["%d" % t for t in range(1, 31)]
+        basic.append(RadioSetting(
+                "tot", "Time-out timer (mins)",
                 RadioSettingValueList(opts, opts[_settings.tot])))
 
         # dtmf tab
 
         opts = ["50", "100", "250", "450", "750", "1000"]
-        dtmf.append( RadioSetting("dtmf_delay", "DTMF delay (ms)",
+        dtmf.append(RadioSetting(
+                "dtmf_delay", "DTMF delay (ms)",
                 RadioSettingValueList(opts, opts[_settings.dtmf_delay])))
 
         opts = ["50", "75", "100"]
-        dtmf.append( RadioSetting("dtmf_speed", "DTMF speed (ms)",
+        dtmf.append(RadioSetting(
+                "dtmf_speed", "DTMF speed (ms)",
                 RadioSettingValueList(opts, opts[_settings.dtmf_speed])))
 
         for i in range(16):
@@ -643,57 +669,65 @@ class FT7800Radio(FTx800Radio):
                     break
                 if c < len(DTMFCHARSET):
                     dtmfstr += DTMFCHARSET[c]
-            if CHIRP_DEBUG:
-                print dtmfstr
+            LOG.debug(dtmfstr)
             dtmfentry = RadioSettingValueString(0, 16, dtmfstr)
             dtmfentry.set_charset(DTMFCHARSET + list(" "))
             rs = RadioSetting(name, name.upper(), dtmfentry)
-            dtmf.append(rs) 
+            dtmf.append(rs)
 
-        # arts tab        
+        # arts tab
 
         opts = ["off", "in range", "always"]
-        arts.append( RadioSetting("arts_mode", "ARTS beep",
+        arts.append(RadioSetting(
+                "arts_mode", "ARTS beep",
                 RadioSettingValueList(opts, opts[_settings.arts_mode])))
 
         opts = ["15", "25"]
-        arts.append( RadioSetting("arts_interval", "ARTS interval",
+        arts.append(RadioSetting(
+                "arts_interval", "ARTS interval",
                 RadioSettingValueList(opts, opts[_settings.arts_interval])))
 
-        arts.append( RadioSetting("arts_cwid_enable", "CW ID",
-                RadioSettingValueBoolean(_settings.arts_cwid_enable)))           
+        arts.append(RadioSetting(
+                "arts_cwid_enable", "CW ID",
+                RadioSettingValueBoolean(_settings.arts_cwid_enable)))
 
         _arts_cwid = self._memobj.arts_cwid
-        cwid = RadioSettingValueString(0, 16, 
-                self._decode_chars(_arts_cwid.get_value()))
+        cwid = RadioSettingValueString(
+                0, 16, self._decode_chars(_arts_cwid.get_value()))
         cwid.set_charset(CHARSET)
-        arts.append( RadioSetting("arts_cwid", "CW ID", cwid ))
+        arts.append(RadioSetting("arts_cwid", "CW ID", cwid))
 
         # prog buttons
 
         opts = ["WX", "Reverse", "Repeater", "SQL Off", "Lock", "Dimmer"]
-        prog.append( RadioSetting("prog_panel_acc", "Prog Panel - Low(ACC)",
+        prog.append(RadioSetting(
+                "prog_panel_acc", "Prog Panel - Low(ACC)",
                 RadioSettingValueList(opts, opts[_settings.prog_panel_acc])))
 
         opts = ["Reverse", "Home"]
-        prog.append( RadioSetting("prog_tone_vm", "TONE | V/M",
+        prog.append(RadioSetting(
+                "prog_tone_vm", "TONE | V/M",
                 RadioSettingValueList(opts, opts[_settings.prog_tone_vm])))
 
         opts = ["" for n in range(26)] + \
-            ["Priority", "Low", "Tone", "MHz", "Reverse", "Home", "Band", 
-            "VFO/MR", "Scan", "Sql Off", "TCall", "SSCH", "ARTS", "Tone Freq",
-            "DCSC", "WX", "Repeater" ]
+            ["Priority", "Low", "Tone", "MHz", "Reverse", "Home", "Band",
+             "VFO/MR", "Scan", "Sql Off", "TCall", "SSCH", "ARTS", "Tone Freq",
+             "DCSC", "WX", "Repeater"]
 
-        prog.append( RadioSetting("prog_p1", "P1",
+        prog.append(RadioSetting(
+                "prog_p1", "P1",
                 RadioSettingValueList(opts, opts[_settings.prog_p1])))
 
-        prog.append( RadioSetting("prog_p2", "P2",
+        prog.append(RadioSetting(
+                "prog_p2", "P2",
                 RadioSettingValueList(opts, opts[_settings.prog_p2])))
 
-        prog.append( RadioSetting("prog_p3", "P3",
+        prog.append(RadioSetting(
+                "prog_p3", "P3",
                 RadioSettingValueList(opts, opts[_settings.prog_p3])))
 
-        prog.append( RadioSetting("prog_p4", "P4",
+        prog.append(RadioSetting(
+                "prog_p4", "P4",
                 RadioSettingValueList(opts, opts[_settings.prog_p4])))
 
         return top
@@ -712,17 +746,16 @@ class FT7800Radio(FTx800Radio):
                     # set dtmf fields
                     dtmfstr = str(element.value).strip()
                     newval = []
-                    for i in range(0,16):
+                    for i in range(0, 16):
                         if i < len(dtmfstr):
                             newval.append(DTMFCHARSET.index(dtmfstr[i]))
                         else:
                             newval.append(0xFF)
-                    if CHIRP_DEBUG:
-                        print newval
+                    LOG.debug(newval)
                     idx = int(setting[-2:])
                     _settings = self._memobj.dtmf[idx]
                     _settings.memory = newval
-                    continue                
+                    continue
                 if setting == "arts_cwid":
                     oldval = self._memobj.arts_cwid
                     newval = self._encode_chars(newval.get_value(), 6)
@@ -731,12 +764,10 @@ class FT7800Radio(FTx800Radio):
                 # normal settings
                 newval = element.value
                 oldval = getattr(_settings, setting)
-                if CHIRP_DEBUG:
-                    print "Setting %s(%s) <= %s" % (setting,
-                                    oldval, newval)
+                LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval))
                 setattr(_settings, setting, newval)
             except Exception, e:
-                print element.get_name()
+                LOG.debug(element.get_name())
                 raise
 
 MEM_FORMAT_8800 = """
@@ -779,10 +810,12 @@ struct {
 u8 checksum;
 """
 
+
 class FT8800BankModel(FT7800BankModel):
     def get_num_mappings(self):
         return 10
 
+
 @directory.register
 class FT8800Radio(FTx800Radio):
     """Base class for Yaesu FT-8800"""
@@ -792,10 +825,11 @@ class FT8800Radio(FTx800Radio):
     _memsize = 22217
 
     _block_lengths = [8, 22208, 1]
-    _block_size = 64
 
     _memstart = 0x0000
 
+    TMODES = ["", "Tone", "TSQL", "DTCS"]
+
     @classmethod
     def get_prompts(cls):
         rp = chirp_common.RadioPrompts()
@@ -805,9 +839,9 @@ class FT8800Radio(FTx800Radio):
             3. Press and hold in the "left" [V/M] key while turning the
                  radio on.
             4. Rotate the "right" DIAL knob to select "CLONE START".
-            5. Press the [SET] key. The display will disappear 
+            5. Press the [SET] key. The display will disappear
                  for a moment, then the "CLONE" notation will appear.
-            6. <b>After clicking OK</b>, press the "left" [V/M] key to 
+            6. <b>After clicking OK</b>, press the "left" [V/M] key to
                  send image."""))
         rp.pre_upload = _(dedent("""\
             1. Turn radio off.
@@ -815,12 +849,12 @@ class FT8800Radio(FTx800Radio):
             3. Press and hold in the "left" [V/M] key while turning the
                  radio on.
             4. Rotate the "right" DIAL knob to select "CLONE START".
-            5. Press the [SET] key. The display will disappear 
+            5. Press the [SET] key. The display will disappear
                  for a moment, then the "CLONE" notation will appear.
-            6. Press the "left" [LOW] key ("CLONE -RX-" will appear on 
+            6. Press the "left" [LOW] key ("CLONE -RX-" will appear on
                  the display)."""))
         return rp
-        
+
     def get_features(self):
         rf = FTx800Radio.get_features(self)
         rf.has_sub_devices = self.VARIANT == ""
@@ -835,7 +869,7 @@ class FT8800Radio(FTx800Radio):
         return FT8800BankModel(self)
 
     def _checksums(self):
-        return [ yaesu_clone.YaesuChecksum(0x0000, 0x56C7) ]
+        return [yaesu_clone.YaesuChecksum(0x0000, 0x56C7)]
 
     def process_mmap(self):
         if not self._memstart:
@@ -883,6 +917,7 @@ class FT8800Radio(FTx800Radio):
         _mem.namevalid = 1
         _mem.nameused = bool(mem.name.rstrip())
 
+
 class FT8800RadioLeft(FT8800Radio):
     """Yaesu FT-8800 Left VFO subdevice"""
     VARIANT = "Left"
@@ -930,6 +965,7 @@ struct {
 u8 checksum;
 """
 
+
 @directory.register
 class FT8900Radio(FT8800Radio):
     """Yaesu FT-8900"""
@@ -949,8 +985,8 @@ class FT8900Radio(FT8800Radio):
         rf.has_sub_devices = False
         rf.has_bank = False
         rf.valid_modes = self.MODES
-        rf.valid_bands = [( 28000000,  29700000),
-                          ( 50000000,  54000000),
+        rf.valid_bands = [(28000000,  29700000),
+                          (50000000,  54000000),
                           (108000000, 180000000),
                           (320000000, 480000000),
                           (700000000, 985000000)]
@@ -960,7 +996,7 @@ class FT8900Radio(FT8800Radio):
         return rf
 
     def _checksums(self):
-        return [ yaesu_clone.YaesuChecksum(0x0000, 0x39C7) ]
+        return [yaesu_clone.YaesuChecksum(0x0000, 0x39C7)]
 
     def _get_mem_skip(self, mem, _mem):
         return SKIPS[_mem.skip]
@@ -985,4 +1021,3 @@ class FT8900Radio(FT8800Radio):
             _mem.sub_used = 0
         else:
             _mem.sub_used = 1
-
diff --git a/chirp/drivers/ft8100.py b/chirp/drivers/ft8100.py
new file mode 100644
index 0000000..1d238c3
--- /dev/null
+++ b/chirp/drivers/ft8100.py
@@ -0,0 +1,314 @@
+# Copyright 2010 Eric Allen <eric at hackerengineer.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 time
+import os
+
+from chirp import chirp_common, directory, bitwise, errors
+from chirp.drivers import yaesu_clone
+
+TONES = chirp_common.OLD_TONES
+
+TMODES = ["", "Tone"]
+
+MODES = ['FM', 'AM']
+
+STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0]
+
+DUPLEX = ["", "-", "+", "split"]
+
+# "M" for masked memories, which are invisible until un-masked
+SKIPS = ["", "S", "M"]
+
+POWER_LEVELS_VHF = [chirp_common.PowerLevel("Low", watts=5),
+                    chirp_common.PowerLevel("Mid", watts=20),
+                    chirp_common.PowerLevel("High", watts=50)]
+
+POWER_LEVELS_UHF = [chirp_common.PowerLevel("Low", watts=5),
+                    chirp_common.PowerLevel("Mid", watts=20),
+                    chirp_common.PowerLevel("High", watts=35)]
+
+SPECIALS = {'1L': -1,
+            '1U': -2,
+            '2L': -3,
+            '2U': -4,
+            'Home': -5}
+
+MEM_FORMAT = """
+#seekto 0x{skips:X};
+u8 skips[13];
+
+#seekto 0x{enables:X};
+u8 enables[13];
+
+struct mem_struct {{
+    u8 unknown4:2,
+       baud9600:1,
+       am:1,
+       unknown4b:4;
+    u8 power:2,
+       duplex:2,
+       unknown1b:4;
+    u8 unknown2:1,
+       tone_enable:1,
+       tone:6;
+    bbcd freq[3];
+    bbcd offset[3];
+}};
+
+#seekto 0x{memories:X};
+struct mem_struct memory[99];
+"""
+
+
+ at directory.register
+class FT8100Radio(yaesu_clone.YaesuCloneModeRadio):
+    """Implementation for Yaesu FT-8100"""
+    MODEL = "FT-8100"
+
+    _memstart = 0
+    _memsize = 2968
+    _block_lengths = [10, 32, 114, 101, 101, 97, 128, 128, 128, 128, 128, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1]
+
+    @classmethod
+    def match_model(cls, data, path):
+        if (len(data) == cls._memsize and
+                data[1:10] == '\x01\x01\x07\x08\x02\x01\x01\x00\x01'):
+            return True
+
+        return False
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.memory_bounds = (1, 99)
+        rf.has_ctone = False
+        rf.has_dtcs = False
+        rf.has_dtcs_polarity = False
+        rf.has_bank = False
+        rf.has_name = False
+
+        rf.valid_modes = list(MODES)
+        rf.valid_tmodes = list(TMODES)
+        rf.valid_duplexes = list(DUPLEX)
+        rf.valid_power_levels = POWER_LEVELS_VHF
+        rf.has_sub_devices = self.VARIANT == ''
+
+        rf.valid_tuning_steps = list(STEPS)
+
+        rf.valid_bands = [(110000000, 550000000),
+                          (750000000, 1300000000)]
+
+        rf.valid_skips = SKIPS
+
+        rf.can_odd_split = True
+
+        # TODO
+        #rf.valid_special_chans = SPECIALS.keys()
+
+        # TODO
+        #rf.has_tuning_step = False
+        return rf
+
+    def sync_in(self):
+        super(FT8100Radio, self).sync_in()
+        self.pipe.write(chr(yaesu_clone.CMD_ACK))
+        self.pipe.read(1)
+
+    def sync_out(self):
+        self.update_checksums()
+        return _clone_out(self)
+
+    def process_mmap(self):
+        if not self._memstart:
+            return
+
+        mem_format = MEM_FORMAT.format(memories=self._memstart,
+                                       skips=self._skipstart,
+                                       enables=self._enablestart
+                                       )
+
+        self._memobj = bitwise.parse(mem_format, self._mmap)
+
+    def get_sub_devices(self):
+        return [FT8100RadioVHF(self._mmap), FT8100RadioUHF(self._mmap)]
+
+    def get_memory(self, number):
+        bit, byte = self._bit_byte(number)
+
+        _mem = self._memobj.memory[number - 1]
+
+        mem = chirp_common.Memory()
+        mem.number = number
+
+        mem.freq = int(_mem.freq) * 1000
+        if _mem.tone >= len(TONES) or _mem.duplex >= len(DUPLEX):
+            mem.empty = True
+            return mem
+        else:
+            mem.rtone = TONES[_mem.tone]
+        mem.tmode = TMODES[_mem.tone_enable]
+        mem.mode = MODES[_mem.am]
+        mem.duplex = DUPLEX[_mem.duplex]
+
+        if _mem.duplex == DUPLEX.index("split"):
+            tx_freq = int(_mem.offset) * 1000
+            print self.VARIANT, number, tx_freq, mem.freq
+            mem.offset = tx_freq - mem.freq
+        else:
+            mem.offset = int(_mem.offset) * 1000
+
+        if int(mem.freq / 100) == 4:
+            mem.power = POWER_LEVELS_UHF[_mem.power]
+        else:
+            mem.power = POWER_LEVELS_VHF[_mem.power]
+
+        # M01 can't be disabled
+        if not self._memobj.enables[byte] & bit and number != 1:
+            mem.empty = True
+
+        print 'R', self.VARIANT, number, _mem.baud9600
+
+        return mem
+
+    def get_raw_memory(self, number):
+        return repr(self._memobj.memory[number - 1])
+
+    def set_memory(self, mem):
+        bit, byte = self._bit_byte(mem.number)
+
+        _mem = self._memobj.memory[mem.number - 1]
+
+        _mem.freq = int(mem.freq / 1000)
+        _mem.tone = TONES.index(mem.rtone)
+        _mem.tone_enable = TMODES.index(mem.tmode)
+        _mem.am = MODES.index(mem.mode)
+        _mem.duplex = DUPLEX.index(mem.duplex)
+
+        if mem.duplex == "split":
+            tx_freq = mem.freq + mem.offset
+            _mem.split_high = tx_freq / 10000000
+            _mem.offset = (tx_freq % 10000000) / 1000
+        else:
+            _mem.offset = int(mem.offset / 1000)
+
+        if mem.power:
+            _mem.power = POWER_LEVELS_VHF.index(mem.power)
+        else:
+            _mem.power = 0
+
+        if mem.empty:
+            self._memobj.enables[byte] &= ~bit
+        else:
+            self._memobj.enables[byte] |= bit
+
+        # TODO expose these options
+        _mem.baud9600 = 0
+        _mem.am = 0
+
+        # These need to be cleared, otherwise strange things happen
+        _mem.unknown4 = 0
+        _mem.unknown4b = 0
+        _mem.unknown1b = 0
+        _mem.unknown2 = 0
+
+    def _checksums(self):
+        return [yaesu_clone.YaesuChecksum(0x0000, 0x0B96)]
+
+    # I didn't believe this myself, but it seems that there's a bit for
+    # enabling VHF M01, but no bit for UHF01, and the enables are shifted down,
+    # so that the first bit is for M02
+    def _bit_byte(self, number):
+        if self.VARIANT == 'VHF':
+            bit = 1 << ((number - 1) % 8)
+            byte = (number - 1) / 8
+        else:
+            bit = 1 << ((number - 2) % 8)
+            byte = (number - 2) / 8
+
+        return bit, byte
+
+
+class FT8100RadioVHF(FT8100Radio):
+    """Yaesu FT-8100 VHF subdevice"""
+    VARIANT = "VHF"
+    _memstart = 0x447
+    _skipstart = 0x02D
+    _enablestart = 0x04D
+
+
+class FT8100RadioUHF(FT8100Radio):
+    """Yaesu FT-8100 UHF subdevice"""
+    VARIANT = "UHF"
+    _memstart = 0x7E6
+    _skipstart = 0x03A
+    _enablestart = 0x05A
+
+
+def _clone_out(radio):
+    try:
+        return __clone_out(radio)
+    except Exception, e:
+        raise errors.RadioError("Failed to communicate with the radio: %s" % e)
+
+
+def __clone_out(radio):
+    pipe = radio.pipe
+    block_lengths = radio._block_lengths
+    total_written = 0
+
+    def _status():
+        status = chirp_common.Status()
+        status.msg = "Cloning to radio"
+        status.max = sum(block_lengths)
+        status.cur = total_written
+        radio.status_fn(status)
+
+    start = time.time()
+
+    pos = 0
+    for block in radio._block_lengths:
+        if os.getenv("CHIRP_DEBUG"):
+            print "\nSending %i-%i" % (pos, pos + block)
+        out = radio.get_mmap()[pos:pos + block]
+
+        # need to chew byte-by-byte here or else we lose the ACK...not sure why
+        for b in out:
+            pipe.write(b)
+            pipe.read(1)  # chew the echo
+
+        ack = pipe.read(1)
+
+        if ack != chr(yaesu_clone.CMD_ACK):
+            raise Exception("block not ack'ed: %s" % repr(ack))
+
+        total_written += len(out)
+        _status()
+
+        pos += block
+
+    print "Clone completed in %i seconds" % (time.time() - start)
+
+    return True
diff --git a/chirp/ft817.py b/chirp/drivers/ft817.py
similarity index 77%
rename from chirp/ft817.py
rename to chirp/drivers/ft817.py
index de73ff5..1dd94bf 100644
--- a/chirp/ft817.py
+++ b/chirp/drivers/ft817.py
@@ -16,16 +16,21 @@
 
 """FT817 - FT817ND - FT817ND/US management module"""
 
-from chirp import chirp_common, yaesu_clone, util, memmap, errors, directory
-from chirp import bitwise
+from chirp.drivers import yaesu_clone
+from chirp import chirp_common, util, memmap, errors, directory, bitwise
 from chirp.settings import RadioSetting, RadioSettingGroup, \
     RadioSettingValueInteger, RadioSettingValueList, \
-    RadioSettingValueBoolean, RadioSettingValueString
-import time, os
+    RadioSettingValueBoolean, RadioSettingValueString, \
+    RadioSettings
+import time
+import logging
 from textwrap import dedent
 
+LOG = logging.getLogger(__name__)
+
 CMD_ACK = 0x06
 
+
 @directory.register
 class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
     """Yaesu FT-817"""
@@ -36,8 +41,8 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
 
     DUPLEX = ["", "-", "+", "split"]
     # narrow modes has to be at end
-    MODES  = ["LSB", "USB", "CW", "CWR", "AM", "FM", "DIG", "PKT", "NCW",
-              "NCWR", "NFM"]
+    MODES = ["LSB", "USB", "CW", "CWR", "AM", "FM", "DIG", "PKT", "NCW",
+             "NCWR", "NFM"]
     TMODES = ["", "Tone", "TSQL", "DTCS"]
     STEPSFM = [5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0]
     STEPSAM = [2.5, 5.0, 9.0, 10.0, 12.5, 25.0]
@@ -46,20 +51,14 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
     # warning ranges has to be in this exact order
     VALID_BANDS = [(100000, 33000000), (33000000, 56000000),
                    (76000000, 108000000), (108000000, 137000000),
-                   (137000000, 154000000), (420000000, 470000000)] 
+                   (137000000, 154000000), (420000000, 470000000)]
 
     CHARSET = list(chirp_common.CHARSET_ASCII)
     CHARSET.remove("\\")
 
-    # Hi not used in memory
-    POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00),
-                    chirp_common.PowerLevel("L3", watts=2.50),
-                    chirp_common.PowerLevel("L2", watts=1.00),
-                    chirp_common.PowerLevel("L1", watts=0.5)]
-
     _memsize = 6509
     # block 9 (130 Bytes long) is to be repeted 40 times
-    _block_lengths = [ 2, 40, 208, 182, 208, 182, 198, 53, 130, 118, 118]
+    _block_lengths = [2, 40, 208, 182, 208, 182, 198, 53, 130, 118, 118]
 
     MEM_FORMAT = """
         struct mem_struct {
@@ -95,7 +94,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
             u32 offset;
             u8  name[8];
         };
-        
+
         #seekto 0x4;
         struct {
             u8  fst:1,
@@ -196,15 +195,15 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         struct mem_struct qmb;
         struct mem_struct mtqmb;
         struct mem_struct mtune;
-        
+
         #seekto 0x3FD;
         u8 visible[25];
         u8 pmsvisible;
-        
+
         #seekto 0x417;
         u8 filled[25];
         u8 pmsfilled;
-        
+
         #seekto 0x431;
         struct mem_struct memory[200];
         struct mem_struct pms[2];
@@ -215,49 +214,48 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         #seekto 0x1979;
         struct mem_struct sixtymeterchannels[5];
     """
-    _CALLSIGN_CHARSET = [chr(x) for x in range(ord("0"), ord("9")+1) +
-                                        range(ord("A"), ord("Z")+1) + 
-                                        [ord(" ")]]
+    _CALLSIGN_CHARSET = [chr(x) for x in range(ord("0"), ord("9") + 1) +
+                         range(ord("A"), ord("Z") + 1) + [ord(" ")]]
     _CALLSIGN_CHARSET_REV = dict(zip(_CALLSIGN_CHARSET,
-                                    range(0,len(_CALLSIGN_CHARSET))))
+                                     range(0, len(_CALLSIGN_CHARSET))))
 
     # WARNING Index are hard wired in memory management code !!!
     SPECIAL_MEMORIES = {
-        "VFOa-1.8M" : -35,
-        "VFOa-3.5M" : -34,
-        "VFOa-7M" : -33,
-        "VFOa-10M" : -32,
-        "VFOa-14M" : -31,
-        "VFOa-18M" : -30,
-        "VFOa-21M" : -29,
-        "VFOa-24M" : -28,
-        "VFOa-28M" : -27,
-        "VFOa-50M" : -26,
-        "VFOa-FM" : -25,
-        "VFOa-AIR" : -24,
-        "VFOa-144" : -23,
-        "VFOa-430" : -22,
-        "VFOa-HF" : -21,
-        "VFOb-1.8M" : -20,
-        "VFOb-3.5M" : -19,
-        "VFOb-7M" : -18,
-        "VFOb-10M" : -17,
-        "VFOb-14M" : -16,
-        "VFOb-18M" : -15,
-        "VFOb-21M" : -14,
-        "VFOb-24M" : -13,
-        "VFOb-28M" : -12,
-        "VFOb-50M" : -11,
-        "VFOb-FM" : -10,
-        "VFOb-AIR" : -9,
-        "VFOb-144M" : -8,
-        "VFOb-430M" : -7,
-        "VFOb-HF" : -6,
-        "HOME HF" : -5,
-        "HOME 50M" : -4,
-        "HOME 144M" : -3,
-        "HOME 430M" : -2,
-        "QMB" : -1,
+        "VFOa-1.8M": -35,
+        "VFOa-3.5M": -34,
+        "VFOa-7M": -33,
+        "VFOa-10M": -32,
+        "VFOa-14M": -31,
+        "VFOa-18M": -30,
+        "VFOa-21M": -29,
+        "VFOa-24M": -28,
+        "VFOa-28M": -27,
+        "VFOa-50M": -26,
+        "VFOa-FM": -25,
+        "VFOa-AIR": -24,
+        "VFOa-144": -23,
+        "VFOa-430": -22,
+        "VFOa-HF": -21,
+        "VFOb-1.8M": -20,
+        "VFOb-3.5M": -19,
+        "VFOb-7M": -18,
+        "VFOb-10M": -17,
+        "VFOb-14M": -16,
+        "VFOb-18M": -15,
+        "VFOb-21M": -14,
+        "VFOb-24M": -13,
+        "VFOb-28M": -12,
+        "VFOb-50M": -11,
+        "VFOb-FM": -10,
+        "VFOb-AIR": -9,
+        "VFOb-144M": -8,
+        "VFOb-430M": -7,
+        "VFOb-HF": -6,
+        "HOME HF": -5,
+        "HOME 50M": -4,
+        "HOME 144M": -3,
+        "HOME 430M": -2,
+        "QMB": -1,
     }
     FIRST_VFOB_INDEX = -6
     LAST_VFOB_INDEX = -20
@@ -265,16 +263,16 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
     LAST_VFOA_INDEX = -35
 
     SPECIAL_PMS = {
-        "PMS-L" : -37,
-        "PMS-U" : -36,
+        "PMS-L": -37,
+        "PMS-U": -36,
     }
     LAST_PMS_INDEX = -37
 
     SPECIAL_MEMORIES.update(SPECIAL_PMS)
-    
+
     SPECIAL_MEMORIES_REV = dict(zip(SPECIAL_MEMORIES.values(),
                                     SPECIAL_MEMORIES.keys()))
-                                    
+
     @classmethod
     def get_prompts(cls):
         rp = chirp_common.RadioPrompts()
@@ -301,38 +299,39 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         else:
             attempts = 5
         for _i in range(0, attempts):
-            data = self.pipe.read(block+2)
+            data = self.pipe.read(block + 2)
             if data:
                 break
             time.sleep(0.5)
-        if len(data) == block+2 and data[0] == chr(blocknum):
+        if len(data) == block + 2 and data[0] == chr(blocknum):
             checksum = yaesu_clone.YaesuChecksum(1, block)
             if checksum.get_existing(data) != \
                     checksum.get_calculated(data):
                 raise Exception("Checksum Failed [%02X<>%02X] block %02X" %
-                                    (checksum.get_existing(data),
-                                    checksum.get_calculated(data), blocknum))
-            data = data[1:block+1] # Chew away the block number and the checksum
+                                (checksum.get_existing(data),
+                                 checksum.get_calculated(data), blocknum))
+            # Chew away the block number and the checksum
+            data = data[1:block + 1]
         else:
             if lastblock and self._US_model:
                 raise Exception(_("Unable to read last block. "
-                            "This often happens when the selected model is US "
-                            "but the radio is a non-US one (or widebanded). "
-                            "Please choose the correct model and try again."))
+                                  "This often happens when the selected model "
+                                  "is US but the radio is a non-US one (or "
+                                  "widebanded). Please choose the correct "
+                                  "model and try again."))
             else:
-                raise Exception("Unable to read block %02X expected %i got %i" %
-                                (blocknum, block+2, len(data)))
-    
-        if os.getenv("CHIRP_DEBUG"):
-            print "Read %i" % len(data)
-        return data        
-    
+                raise Exception("Unable to read block %02X expected %i got %i"
+                                % (blocknum, block + 2, len(data)))
+
+        LOG.debug("Read %i" % len(data))
+        return data
+
     def _clone_in(self):
         # Be very patient with the radio
         self.pipe.setTimeout(2)
-    
+
         start = time.time()
-    
+
         data = ""
         blocks = 0
         status = chirp_common.Status()
@@ -345,32 +344,32 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
                 repeat = 40
             else:
                 repeat = 1
-            for _i in range(0, repeat):	
-                data += self._read(block, blocks, blocks == nblocks-1)
+            for _i in range(0, repeat):
+                data += self._read(block, blocks, blocks == nblocks - 1)
                 self.pipe.write(chr(CMD_ACK))
                 blocks += 1
                 status.cur = blocks
                 self.status_fn(status)
-                
+
         if not self._US_model:
             status.msg = _("Clone completed, checking for spurious bytes")
             self.status_fn(status)
             moredata = self.pipe.read(2)
             if moredata:
-                raise Exception(_("Radio sent data after the last awaited block, "
+                raise Exception(
+                        _("Radio sent data after the last awaited block, "
                             "this happens when the selected model is a non-US "
                             "but the radio is a US one. "
                             "Please choose the correct model and try again."))
-            
-    
-        print "Clone completed in %i seconds" % (time.time() - start)
-    
+
+        LOG.info("Clone completed in %i seconds" % (time.time() - start))
+
         return memmap.MemoryMap(data)
-    
+
     def _clone_out(self):
         delay = 0.5
         start = time.time()
-    
+
         blocks = 0
         pos = 0
         status = chirp_common.Status()
@@ -384,34 +383,30 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
                 repeat = 1
             for _i in range(0, repeat):
                 time.sleep(0.01)
-                checksum = yaesu_clone.YaesuChecksum(pos, pos+block-1)
-                if os.getenv("CHIRP_DEBUG"):
-                    print "Block %i - will send from %i to %i byte " % \
-                        (blocks,
-                         pos,
-                         pos + block)
-                    print util.hexprint(chr(blocks))
-                    print util.hexprint(self.get_mmap()[pos:pos+block])
-                    print util.hexprint(chr(checksum.get_calculated(
-                                self.get_mmap())))
+                checksum = yaesu_clone.YaesuChecksum(pos, pos + block - 1)
+                LOG.debug("Block %i - will send from %i to %i byte " %
+                          (blocks, pos, pos + block))
+                LOG.debug(util.hexprint(chr(blocks)))
+                LOG.debug(util.hexprint(self.get_mmap()[pos:pos + block]))
+                LOG.debug(util.hexprint(chr(checksum.get_calculated(
+                            self.get_mmap()))))
                 self.pipe.write(chr(blocks))
-                self.pipe.write(self.get_mmap()[pos:pos+block])
+                self.pipe.write(self.get_mmap()[pos:pos + block])
                 self.pipe.write(chr(checksum.get_calculated(self.get_mmap())))
                 buf = self.pipe.read(1)
                 if not buf or buf[0] != chr(CMD_ACK):
                     time.sleep(delay)
                     buf = self.pipe.read(1)
                 if not buf or buf[0] != chr(CMD_ACK):
-                    if os.getenv("CHIRP_DEBUG"):
-                        print util.hexprint(buf)
+                    LOG.debug(util.hexprint(buf))
                     raise Exception(_("Radio did not ack block %i") % blocks)
                 pos += block
                 blocks += 1
                 status.cur = blocks
                 self.status_fn(status)
-    
-        print "Clone completed in %i seconds" % (time.time() - start)
-    
+
+        LOG.info("Clone completed in %i seconds" % (time.time() - start))
+
     def sync_in(self):
         try:
             self._mmap = self._clone_in()
@@ -443,7 +438,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         rf.valid_tuning_steps = list(self.STEPSFM)
         rf.valid_bands = self.VALID_BANDS
         rf.valid_skips = ["", "S"]
-        rf.valid_power_levels = self.POWER_LEVELS
+        rf.valid_power_levels = []
         rf.valid_characters = "".join(self.CHARSET)
         rf.valid_name_length = 8
         rf.valid_special_chans = sorted(self.SPECIAL_MEMORIES.keys())
@@ -454,7 +449,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         return rf
 
     def get_raw_memory(self, number):
-        return repr(self._memobj.memory[number-1])
+        return repr(self._memobj.memory[number - 1])
 
     def _get_duplex(self, mem, _mem):
         if _mem.is_duplex == 1:
@@ -530,8 +525,8 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
                 mem.empty = True
                 return mem
             _mem = self._memobj.pms[-self.LAST_PMS_INDEX + mem.number]
-            immutable = ["number", "skip", "rtone", "ctone", "extd_number", 
-                         "dtcs", "tmode", "cross_mode", "dtcs_polarity", 
+            immutable = ["number", "skip", "rtone", "ctone", "extd_number",
+                         "dtcs", "tmode", "cross_mode", "dtcs_polarity",
                          "power", "duplex", "offset", "comment"]
         else:
             raise Exception("Sorry, special memory index %i " % mem.number +
@@ -543,7 +538,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         return mem
 
     def _set_special(self, mem):
-        if mem.empty and not mem.number in self.SPECIAL_PMS.values():
+        if mem.empty and mem.number not in self.SPECIAL_PMS.values():
             # can't delete special memories!
             raise Exception("Sorry, special memory can't be deleted")
 
@@ -551,11 +546,11 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
 
         # TODO add frequency range check for vfo and home memories
         if mem.number in range(self.FIRST_VFOA_INDEX,
-                               self.LAST_VFOA_INDEX -1,
+                               self.LAST_VFOA_INDEX - 1,
                                -1):
             _mem = self._memobj.vfoa[-self.LAST_VFOA_INDEX + mem.number]
         elif mem.number in range(self.FIRST_VFOB_INDEX,
-                                 self.LAST_VFOB_INDEX -1,
+                                 self.LAST_VFOB_INDEX - 1,
                                  -1):
             _mem = self._memobj.vfob[-self.LAST_VFOB_INDEX + mem.number]
         elif mem.number in range(-2, -6, -1):
@@ -593,41 +588,43 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         self._set_memory(mem, _mem)
 
     def _get_normal(self, number):
-        _mem = self._memobj.memory[number-1]
-        used = (self._memobj.visible[(number-1)/8] >> (number-1)%8) & 0x01
-        valid = (self._memobj.filled[(number-1)/8] >> (number-1)%8) & 0x01
+        _mem = self._memobj.memory[number - 1]
+        used = (self._memobj.visible[(number - 1) / 8] >> (number - 1) % 8) \
+            & 0x01
+        valid = (self._memobj.filled[(number - 1) / 8] >> (number - 1) % 8) \
+            & 0x01
 
         mem = chirp_common.Memory()
         mem.number = number
         if not used:
             mem.empty = True
-            if not valid:
+            if not valid or _mem.freq == 0xffffffff:
                 return mem
 
         return self._get_memory(mem, _mem)
 
     def _set_normal(self, mem):
-        _mem = self._memobj.memory[mem.number-1]
+        _mem = self._memobj.memory[mem.number - 1]
         wasused = (self._memobj.visible[(mem.number - 1) / 8] >>
-                       (mem.number - 1) % 8) & 0x01
+                   (mem.number - 1) % 8) & 0x01
         wasvalid = (self._memobj.filled[(mem.number - 1) / 8] >>
-                        (mem.number - 1) % 8) & 0x01
+                    (mem.number - 1) % 8) & 0x01
 
         if mem.empty:
             if mem.number == 1:
                 # as Dan says "yaesus are not good about that :("
                 # if you ulpoad an empty image you can brick your radio
-                raise Exception("Sorry, can't delete first memory") 
+                raise Exception("Sorry, can't delete first memory")
             if wasvalid and not wasused:
-                self._memobj.filled[(mem.number-1) / 8] &= \
+                self._memobj.filled[(mem.number - 1) / 8] &= \
                     ~(1 << (mem.number - 1) % 8)
-                _mem.set_raw("\xFF" * (_mem.size() / 8)) # clean up
-            self._memobj.visible[(mem.number-1) / 8] &= \
+                _mem.set_raw("\xFF" * (_mem.size() / 8))    # clean up
+            self._memobj.visible[(mem.number - 1) / 8] &= \
                 ~(1 << (mem.number - 1) % 8)
             return
         if not wasvalid:
-            _mem.set_raw("\x00" * (_mem.size() / 8)) # clean up
-        
+            _mem.set_raw("\x00" * (_mem.size() / 8))    # clean up
+
         self._memobj.visible[(mem.number - 1) / 8] |= 1 << (mem.number - 1) % 8
         self._memobj.filled[(mem.number - 1) / 8] |= 1 << (mem.number - 1) % 8
         self._set_memory(mem, _mem)
@@ -664,7 +661,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
                 else:
                     # radio have some graphical chars that are not supported
                     # we replace those with a *
-                    print "Replacing char %x with *" % i
+                    LOG.info("Replacing char %x with *" % i)
                     mem.name += "*"
             mem.name = mem.name.rstrip()
         else:
@@ -675,7 +672,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
                            RadioSettingValueBoolean(bool(_mem.ipo)))
         ipo.set_doc("Bypass preamp")
         mem.extra.append(ipo)
-        
+
         att = RadioSetting("att", "ATT",
                            RadioSettingValueBoolean(bool(_mem.att)))
         att.set_doc("10dB front end attenuator")
@@ -684,34 +681,34 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         return mem
 
     def _set_memory(self, mem, _mem):
-        if len(mem.name) > 0:     # not supported in chirp
-                                  # so I make label visible if have one
+        if len(mem.name) > 0:   # not supported in chirp
+                                # so I make label visible if have one
             _mem.tag_on_off = 1
         else:
             _mem.tag_on_off = 0
         _mem.tag_default = 0       # never use default label "CH-nnn"
         self._set_duplex(mem, _mem)
-        if mem.mode[0] == "N": # is it narrow?
+        if mem.mode[0] == "N":    # is it narrow?
             _mem.mode = self.MODES.index(mem.mode[1:])
             # here I suppose it's safe to set both
-            _mem.is_fm_narrow = _mem.is_cwdig_narrow = 1       
+            _mem.is_fm_narrow = _mem.is_cwdig_narrow = 1
         else:
             _mem.mode = self.MODES.index(mem.mode)
             # here I suppose it's safe to set both
-            _mem.is_fm_narrow = _mem.is_cwdig_narrow = 0       
+            _mem.is_fm_narrow = _mem.is_cwdig_narrow = 0
         i = 0
         for lo, hi in self.VALID_BANDS:
             if mem.freq > lo and mem.freq < hi:
-                break 
+                break
             i += 1
         _mem.freq_range = i
-        # all this should be safe also when not in split but ... 
+        # all this should be safe also when not in split but ...
         if mem.duplex == "split":
             _mem.tx_mode = _mem.mode
             i = 0
             for lo, hi in self.VALID_BANDS:
                 if mem.offset >= lo and mem.offset < hi:
-                    break 
+                    break
                 i += 1
             _mem.tx_freq_range = i
         _mem.skip = mem.skip == "S"
@@ -728,15 +725,16 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
             _mem.fm_step = self.STEPSFM.index(mem.tuning_step)
         except ValueError:
             pass
-        _mem.rit = 0	# not supported in chirp
+        _mem.rit = 0    # not supported in chirp
         _mem.freq = mem.freq / 10
         _mem.offset = mem.offset / 10
         # there are ft857D that have problems with short labels, see bug #937
         # some of the radio fill with 0xff and some with blanks
-        # the latter is safe for all ft8x7 radio so why should i do it only for some?
+        # the latter is safe for all ft8x7 radio
+        # so why should i do it only for some?
         for i in range(0, 8):
             _mem.name[i] = ord(mem.name.ljust(8)[i])
-        
+
         for setting in mem.extra:
             setattr(_mem, setting.get_name(), setting.value)
 
@@ -764,8 +762,9 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         extended = RadioSettingGroup("extended", "Extended")
         antenna = RadioSettingGroup("antenna", "Antenna selection")
         panelcontr = RadioSettingGroup("panelcontr", "Panel controls")
-        top = RadioSettingGroup("top", "All Settings", basic, cw, packet,
-                                panelcontr, panel, extended, antenna)
+
+        top = RadioSettings(basic, cw, packet,
+                            panelcontr, panel, extended, antenna)
 
         rs = RadioSetting("ars_144", "144 ARS",
                           RadioSettingValueBoolean(_settings.ars_144))
@@ -774,12 +773,15 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
                           RadioSettingValueBoolean(_settings.ars_430))
         basic.append(rs)
         rs = RadioSetting("pkt9600_mic", "Paket 9600 mic level",
-                          RadioSettingValueInteger(0, 100, _settings.pkt9600_mic))
+                          RadioSettingValueInteger(0, 100,
+                                                   _settings.pkt9600_mic))
         packet.append(rs)
         options = ["enable", "disable"]
         rs = RadioSetting("disable_amfm_dial", "AM&FM Dial",
                           RadioSettingValueList(options,
-                                        options[_settings.disable_amfm_dial]))
+                                                options[
+                                                    _settings.disable_amfm_dial
+                                                    ]))
         panel.append(rs)
         rs = RadioSetting("am_mic", "AM mic level",
                           RadioSettingValueInteger(0, 100, _settings.am_mic))
@@ -787,46 +789,49 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         options = ["OFF", "1h", "2h", "3h", "4h", "5h", "6h"]
         rs = RadioSetting("apo_time", "APO time",
                           RadioSettingValueList(options,
-                                        options[_settings.apo_time]))
+                                                options[_settings.apo_time]))
         basic.append(rs)
         options = ["OFF", "Range", "All"]
         rs = RadioSetting("arts_beep", "ARTS beep",
                           RadioSettingValueList(options,
-                                        options[_settings.arts_beep]))
+                                                options[_settings.arts_beep]))
         basic.append(rs)
         options = ["OFF", "ON", "Auto"]
         rs = RadioSetting("backlight", "Backlight",
                           RadioSettingValueList(options,
-                                        options[_settings.backlight]))
+                                                options[_settings.backlight]))
         panel.append(rs)
         options = ["6h", "8h", "10h"]
         rs = RadioSetting("batt_chg", "Battery charge",
                           RadioSettingValueList(options,
-                                        options[_settings.batt_chg]))
+                                                options[_settings.batt_chg]))
         basic.append(rs)
         options = ["440Hz", "880Hz"]
         rs = RadioSetting("beep_freq", "Beep frequency",
                           RadioSettingValueList(options,
-                                        options[_settings.beep_freq]))
+                                                options[_settings.beep_freq]))
         panel.append(rs)
         rs = RadioSetting("beep_volume", "Beep volume",
-                          RadioSettingValueInteger(0, 100, _settings.beep_volume))
+                          RadioSettingValueInteger(0, 100,
+                                                   _settings.beep_volume))
         panel.append(rs)
         options = ["4800", "9600", "38400"]
         rs = RadioSetting("cat_rate", "CAT rate",
                           RadioSettingValueList(options,
-                                        options[_settings.cat_rate]))
+                                                options[_settings.cat_rate]))
         basic.append(rs)
         options = ["Blue", "Amber", "Violet"]
         rs = RadioSetting("color", "Color",
                           RadioSettingValueList(options,
-                                        options[_settings.color]))
+                                                options[_settings.color]))
         panel.append(rs)
         rs = RadioSetting("contrast", "Contrast",
-                          RadioSettingValueInteger(1, 12,_settings.contrast-1))
+                          RadioSettingValueInteger(1, 12,
+                                                   _settings.contrast - 1))
         panel.append(rs)
         rs = RadioSetting("cw_delay", "CW delay (*10 ms)",
-                          RadioSettingValueInteger(1, 250, _settings.cw_delay))
+                          RadioSettingValueInteger(1, 250,
+                                                   _settings.cw_delay))
         cw.append(rs)
         rs = RadioSetting("cw_id", "CW id",
                           RadioSettingValueBoolean(_settings.cw_id))
@@ -834,49 +839,53 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         options = ["Normal", "Reverse"]
         rs = RadioSetting("cw_paddle", "CW paddle",
                           RadioSettingValueList(options,
-                                        options[_settings.cw_paddle]))
+                                                options[_settings.cw_paddle]))
         cw.append(rs)
-        options = ["%i Hz" % i for i in range(300,1001,50)]
+        options = ["%i Hz" % i for i in range(300, 1001, 50)]
         rs = RadioSetting("cw_pitch", "CW pitch",
                           RadioSettingValueList(options,
-                                        options[_settings.cw_pitch]))
+                                                options[_settings.cw_pitch]))
         cw.append(rs)
-        options = ["%i wpm" % i for i in range(4,61)]
+        options = ["%i wpm" % i for i in range(4, 61)]
         rs = RadioSetting("cw_speed", "CW speed",
                           RadioSettingValueList(options,
-                                        options[_settings.cw_speed]))
+                                                options[_settings.cw_speed]))
         cw.append(rs)
-        options = ["1:%1.1f" % (i/10) for i in range(25,46,1)]
+        options = ["1:%1.1f" % (i / 10) for i in range(25, 46, 1)]
         rs = RadioSetting("cw_weight", "CW weight",
                           RadioSettingValueList(options,
-                                        options[_settings.cw_weight]))
+                                                options[_settings.cw_weight]))
         cw.append(rs)
         rs = RadioSetting("dig_disp", "Dig disp (*10 Hz)",
-                          RadioSettingValueInteger(-300, 300, _settings.dig_disp))
+                          RadioSettingValueInteger(-300, 300,
+                                                   _settings.dig_disp))
         packet.append(rs)
         rs = RadioSetting("dig_mic", "Dig mic",
-                          RadioSettingValueInteger(0, 100, _settings.dig_mic))
+                          RadioSettingValueInteger(0, 100,
+                                                   _settings.dig_mic))
         packet.append(rs)
         options = ["RTTY", "PSK31-L", "PSK31-U", "USER-L", "USER-U"]
         rs = RadioSetting("dig_mode", "Dig mode",
                           RadioSettingValueList(options,
-                                        options[_settings.dig_mode]))
+                                                options[_settings.dig_mode]))
         packet.append(rs)
         rs = RadioSetting("dig_shift", "Dig shift (*10 Hz)",
-                          RadioSettingValueInteger(-300, 300, _settings.dig_shift))
+                          RadioSettingValueInteger(-300, 300,
+                                                   _settings.dig_shift))
         packet.append(rs)
         rs = RadioSetting("fm_mic", "FM mic",
-                          RadioSettingValueInteger(0, 100, _settings.fm_mic))
+                          RadioSettingValueInteger(0, 100,
+                                                   _settings.fm_mic))
         basic.append(rs)
         options = ["Dial", "Freq", "Panel"]
         rs = RadioSetting("lock_mode", "Lock mode",
                           RadioSettingValueList(options,
-                                        options[_settings.lock_mode]))
+                                                options[_settings.lock_mode]))
         panel.append(rs)
         options = ["Fine", "Coarse"]
         rs = RadioSetting("main_step", "Main step",
                           RadioSettingValueList(options,
-                                        options[_settings.main_step]))
+                                                options[_settings.main_step]))
         panel.append(rs)
         rs = RadioSetting("mem_group", "Mem group",
                           RadioSettingValueBoolean(_settings.mem_group))
@@ -890,7 +899,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         options = ["Off", "SSB", "CW"]
         rs = RadioSetting("op_filter", "Optional filter",
                           RadioSettingValueList(options,
-                                        options[_settings.op_filter]))
+                                                options[_settings.op_filter]))
         basic.append(rs)
         rs = RadioSetting("pkt_mic", "Packet mic",
                           RadioSettingValueInteger(0, 100, _settings.pkt_mic))
@@ -898,17 +907,18 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         options = ["1200", "9600"]
         rs = RadioSetting("pkt_rate", "Packet rate",
                           RadioSettingValueList(options,
-                                        options[_settings.pkt_rate]))
+                                                options[_settings.pkt_rate]))
         packet.append(rs)
         options = ["Off", "3 sec", "5 sec", "10 sec"]
         rs = RadioSetting("resume_scan", "Resume scan",
                           RadioSettingValueList(options,
-                                        options[_settings.resume_scan]))
+                                                options[_settings.resume_scan])
+                          )
         basic.append(rs)
         options = ["Cont", "Chk"]
         rs = RadioSetting("scope", "Scope",
                           RadioSettingValueList(options,
-                                        options[_settings.scope]))
+                                                options[_settings.scope]))
         basic.append(rs)
         rs = RadioSetting("sidetone", "Sidetone",
                           RadioSettingValueInteger(0, 100, _settings.sidetone))
@@ -916,7 +926,8 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         options = ["RF-Gain", "Squelch"]
         rs = RadioSetting("sql_rf_gain", "Squelch/RF-Gain",
                           RadioSettingValueList(options,
-                                        options[_settings.sql_rf_gain]))
+                                                options[_settings.sql_rf_gain])
+                          )
         panel.append(rs)
         rs = RadioSetting("ssb_mic", "SSB Mic",
                           RadioSettingValueInteger(0, 100, _settings.ssb_mic))
@@ -925,7 +936,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         options[0] = "Off"
         rs = RadioSetting("tot_time", "Time-out timer",
                           RadioSettingValueList(options,
-                                        options[_settings.tot_time]))
+                                                options[_settings.tot_time]))
         basic.append(rs)
         rs = RadioSetting("vox_delay", "VOX delay (*100 ms)",
                           RadioSettingValueInteger(1, 25, _settings.vox_delay))
@@ -939,58 +950,65 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         options = ["Tn-Rn", "Tn-Riv", "Tiv-Rn", "Tiv-Riv"]
         rs = RadioSetting("dcs_inv", "DCS coding",
                           RadioSettingValueList(options,
-                                        options[_settings.dcs_inv]))
+                                                options[_settings.dcs_inv]))
         extended.append(rs)
         rs = RadioSetting("r_lsb_car", "LSB Rx carrier point (*10 Hz)",
-                          RadioSettingValueInteger(-30, 30, _settings.r_lsb_car))
+                          RadioSettingValueInteger(-30, 30,
+                                                   _settings.r_lsb_car))
         extended.append(rs)
         rs = RadioSetting("r_usb_car", "USB Rx carrier point (*10 Hz)",
-                          RadioSettingValueInteger(-30, 30, _settings.r_usb_car))
+                          RadioSettingValueInteger(-30, 30,
+                                                   _settings.r_usb_car))
         extended.append(rs)
         rs = RadioSetting("t_lsb_car", "LSB Tx carrier point (*10 Hz)",
-                          RadioSettingValueInteger(-30, 30, _settings.t_lsb_car))
+                          RadioSettingValueInteger(-30, 30,
+                                                   _settings.t_lsb_car))
         extended.append(rs)
         rs = RadioSetting("t_usb_car", "USB Tx carrier point (*10 Hz)",
-                          RadioSettingValueInteger(-30, 30, _settings.t_usb_car))
+                          RadioSettingValueInteger(-30, 30,
+                                                   _settings.t_usb_car))
         extended.append(rs)
 
         options = ["Hi", "L3", "L2", "L1"]
         rs = RadioSetting("tx_power", "TX power",
                           RadioSettingValueList(options,
-                                        options[_settings.tx_power]))
+                                                options[_settings.tx_power]))
         basic.append(rs)
 
         options = ["Front", "Rear"]
         rs = RadioSetting("hf_antenna", "HF",
                           RadioSettingValueList(options,
-                                        options[_settings.hf_antenna]))
+                                                options[_settings.hf_antenna]))
         antenna.append(rs)
         rs = RadioSetting("sixm_antenna", "6M",
                           RadioSettingValueList(options,
-                                        options[_settings.sixm_antenna]))
+                                                options[_settings.sixm_antenna]
+                                                ))
         antenna.append(rs)
         rs = RadioSetting("bc_antenna", "Broadcasting",
                           RadioSettingValueList(options,
-                                        options[_settings.bc_antenna]))
+                                                options[_settings.bc_antenna]))
         antenna.append(rs)
         rs = RadioSetting("air_antenna", "Air band",
                           RadioSettingValueList(options,
-                                        options[_settings.air_antenna]))
+                                                options[_settings.air_antenna])
+                          )
         antenna.append(rs)
         rs = RadioSetting("vhf_antenna", "VHF",
                           RadioSettingValueList(options,
-                                        options[_settings.vhf_antenna]))
+                                                options[_settings.vhf_antenna])
+                          )
         antenna.append(rs)
         rs = RadioSetting("uhf_antenna", "UHF",
                           RadioSettingValueList(options,
-                                        options[_settings.uhf_antenna]))
+                                                options[_settings.uhf_antenna])
+                          )
         antenna.append(rs)
 
-        s = RadioSettingValueString(0, 7, 
-                            ''.join([self._CALLSIGN_CHARSET[x] for x in 
-                                        self._memobj.callsign]))
-        s.set_charset(self._CALLSIGN_CHARSET)
-        rs = RadioSetting("callsign", "Callsign", s)
+        st = RadioSettingValueString(0, 7, ''.join([self._CALLSIGN_CHARSET[x]
+                                     for x in self._memobj.callsign]))
+        st.set_charset(self._CALLSIGN_CHARSET)
+        rs = RadioSetting("callsign", "Callsign", st)
         cw.append(rs)
 
         rs = RadioSetting("spl", "Split",
@@ -999,7 +1017,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         options = ["None", "Up", "Down"]
         rs = RadioSetting("scn_mode", "Scan mode",
                           RadioSettingValueList(options,
-                                        options[_settings.scn_mode]))
+                                                options[_settings.scn_mode]))
         panelcontr.append(rs)
         rs = RadioSetting("pri", "Priority",
                           RadioSettingValueBoolean(_settings.pri))
@@ -1015,13 +1033,15 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         panelcontr.append(rs)
         options = ["Auto", "Fast", "Slow", "Off"]
         rs = RadioSetting("agc", "AGC",
-                          RadioSettingValueList(options,
-                                        options[_settings.agc]))
+                          RadioSettingValueList(options, options[_settings.agc]
+                                                ))
         panelcontr.append(rs)
         options = ["PWR", "ALC", "SWR", "MOD"]
         rs = RadioSetting("pwr_meter_mode", "Power meter mode",
                           RadioSettingValueList(options,
-                                        options[_settings.pwr_meter_mode]))
+                                                options[
+                                                    _settings.pwr_meter_mode
+                                                        ]))
         panelcontr.append(rs)
         rs = RadioSetting("vox", "Vox",
                           RadioSettingValueBoolean(_settings.vox))
@@ -1034,13 +1054,13 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         cw.append(rs)
         options = ["enabled", "disabled"]
         rs = RadioSetting("fst", "Fast",
-                          RadioSettingValueList(options,
-                                        options[_settings.fst]))
+                          RadioSettingValueList(options, options[_settings.fst]
+                                                ))
         panelcontr.append(rs)
         options = ["enabled", "disabled"]
         rs = RadioSetting("lock", "Lock",
                           RadioSettingValueList(options,
-                                        options[_settings.lock]))
+                                                options[_settings.lock]))
         panelcontr.append(rs)
 
         return top
@@ -1061,21 +1081,24 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
                 else:
                     obj = _settings
                     setting = element.get_name()
-                if os.getenv("CHIRP_DEBUG"):
-	                print "Setting %s(%s) <= %s" % (setting, 
-                                    getattr(obj, setting), element.value)
+                try:
+                    LOG.debug("Setting %s(%s) <= %s" % (setting,
+                              getattr(obj, setting), element.value))
+                except AttributeError:
+                    LOG.debug("Setting %s <= %s" % (setting, element.value))
                 if setting == "contrast":
-                    setattr(obj, setting, int(element.value)+1)
+                    setattr(obj, setting, int(element.value) + 1)
                 elif setting == "callsign":
                     self._memobj.callsign = \
-                        [self._CALLSIGN_CHARSET_REV[x] for x in 
-                                                        str(element.value)]
+                        [self._CALLSIGN_CHARSET_REV[x] for x in
+                         str(element.value)]
                 else:
                     setattr(obj, setting, element.value)
-            except Exception, e:
-                print element.get_name()
+            except:
+                LOG.debug(element.get_name())
                 raise
 
+
 @directory.register
 class FT817NDRadio(FT817Radio):
     """Yaesu FT-817ND"""
@@ -1084,7 +1107,8 @@ class FT817NDRadio(FT817Radio):
     _model = ""
     _memsize = 6521
     # block 9 (130 Bytes long) is to be repeted 40 times
-    _block_lengths = [ 2, 40, 208, 182, 208, 182, 198, 53, 130, 118, 130]
+    _block_lengths = [2, 40, 208, 182, 208, 182, 198, 53, 130, 118, 130]
+
 
 @directory.register
 class FT817NDUSRadio(FT817Radio):
@@ -1097,14 +1121,14 @@ class FT817NDUSRadio(FT817Radio):
     _US_model = True
     _memsize = 6651
     # block 9 (130 Bytes long) is to be repeted 40 times
-    _block_lengths = [ 2, 40, 208, 182, 208, 182, 198, 53, 130, 118, 130, 130]
+    _block_lengths = [2, 40, 208, 182, 208, 182, 198, 53, 130, 118, 130, 130]
 
     SPECIAL_60M = {
-        "M-601" : -42,
-        "M-602" : -41,
-        "M-603" : -40,
-        "M-604" : -39,
-        "M-605" : -38,
+        "M-601": -42,
+        "M-602": -41,
+        "M-603": -40,
+        "M-604": -39,
+        "M-605": -38,
         }
     LAST_SPECIAL60M_INDEX = -42
 
@@ -1120,11 +1144,11 @@ class FT817NDUSRadio(FT817Radio):
         mem.extd_number = number
 
         _mem = self._memobj.sixtymeterchannels[-self.LAST_SPECIAL60M_INDEX +
-                                                mem.number]
+                                               mem.number]
 
         mem = self._get_memory(mem, _mem)
 
-        mem.immutable = ["number", "skip", "rtone", "ctone",
+        mem.immutable = ["number", "rtone", "ctone",
                          "extd_number", "name", "dtcs", "tmode", "cross_mode",
                          "dtcs_polarity", "power", "duplex", "offset",
                          "comment", "empty"]
@@ -1147,7 +1171,7 @@ class FT817NDUSRadio(FT817Radio):
             raise errors.RadioError("Mode {mode} is not valid "
                                     "in 60m channels".format(mode=mem.mode))
         _mem = self._memobj.sixtymeterchannels[-self.LAST_SPECIAL60M_INDEX +
-                                                mem.number]
+                                               mem.number]
         self._set_memory(mem, _mem)
 
     def get_memory(self, number):
@@ -1169,9 +1193,9 @@ class FT817NDUSRadio(FT817Radio):
 
     def get_settings(self):
         top = FT817Radio.get_settings(self)
-        basic = top["basic"]
+        basic = top[0]
         rs = RadioSetting("emergency", "Emergency",
-                          RadioSettingValueBoolean(self._memobj.settings.emergency))
+                          RadioSettingValueBoolean(
+                              self._memobj.settings.emergency))
         basic.append(rs)
         return top
-
diff --git a/chirp/ft857.py b/chirp/drivers/ft857.py
similarity index 66%
rename from chirp/ft857.py
rename to chirp/drivers/ft857.py
index f86273a..ca00e30 100644
--- a/chirp/ft857.py
+++ b/chirp/drivers/ft857.py
@@ -16,13 +16,19 @@
 
 """FT857 - FT857/US management module"""
 
-from chirp import ft817, chirp_common, errors, directory
+from chirp.drivers import ft817
+from chirp import chirp_common, errors, directory
 from chirp.settings import RadioSetting, RadioSettingGroup, \
     RadioSettingValueInteger, RadioSettingValueList, \
-    RadioSettingValueBoolean, RadioSettingValueString
+    RadioSettingValueBoolean, RadioSettingValueString, \
+    RadioSettings
 import os
+import logging
 from textwrap import dedent
 
+LOG = logging.getLogger(__name__)
+
+
 @directory.register
 class FT857Radio(ft817.FT817Radio):
     """Yaesu FT-857/897"""
@@ -30,32 +36,32 @@ class FT857Radio(ft817.FT817Radio):
     _model = ""
 
     TMODES = {
-        0x04 : "Tone",
-        0x05 : "TSQL",
+        0x04: "Tone",
+        0x05: "TSQL",
         # 0x08 : "DTCS Enc", not supported in UI yet
-        0x0a : "DTCS",
-        0xff : "Cross",
-        0x00 : "",
+        0x0a: "DTCS",
+        0xff: "Cross",
+        0x00: "",
     }
     TMODES_REV = dict(zip(TMODES.values(), TMODES.keys()))
 
     CROSS_MODES = {
-        0x01 : "->Tone",
-        0x02 : "->DTCS",
-        # 0x04 : "Tone->", not supported in UI yet
-        0x05 : "Tone->Tone",
-        0x06 : "Tone->DTCS",
-        0x08 : "DTCS->",
-        0x09 : "DTCS->Tone",
-        0x0a : "DTCS->DTCS",
+        0x01: "->Tone",
+        0x02: "->DTCS",
+        0x04: "Tone->",
+        0x05: "Tone->Tone",
+        0x06: "Tone->DTCS",
+        0x08: "DTCS->",
+        0x09: "DTCS->Tone",
+        0x0a: "DTCS->DTCS",
     }
     CROSS_MODES_REV = dict(zip(CROSS_MODES.values(), CROSS_MODES.keys()))
 
     _memsize = 7341
-    # block 9 (140 Bytes long) is to be repeted 40 times 
+    # block 9 (140 Bytes long) is to be repeted 40 times
     # should be 42 times but this way I can use original 817 functions
-    _block_lengths = [ 2, 82, 252, 196, 252, 196, 212, 55, 140, 140, 140,
-                       38, 176]
+    _block_lengths = [2, 82, 252, 196, 252, 196, 212, 55, 140, 140, 140,
+                      38, 176]
     # warning ranges has to be in this exact order
     VALID_BANDS = [(100000, 33000000), (33000000, 56000000),
                    (76000000, 108000000), (108000000, 137000000),
@@ -105,7 +111,7 @@ class FT857Radio(ft817.FT817Radio):
         u32 offset;
         u8   name[8];
         };
-        
+
         #seekto 0x00;
         struct {
             u16 radioconfig;
@@ -279,19 +285,19 @@ class FT857Radio(ft817.FT817Radio):
         struct mem_struct qmb;
         struct mem_struct mtqmb;
         struct mem_struct mtune;
-        
+
         #seekto 0x4a9;
         u8 visible[25];
         ul16 pmsvisible;
-        
+
         #seekto 0x4c4;
         u8 filled[25];
         ul16 pmsfilled;
-        
+
         #seekto 0x4df;
         struct mem_struct memory[200];
         struct mem_struct pms[10];
-        
+
         #seekto 0x1bf3;
         u8 arts_idw[10];
         u8 beacon_text1[40];
@@ -304,56 +310,56 @@ class FT857Radio(ft817.FT817Radio):
 
         #seekto 0x1CAD;
         struct mem_struct sixtymeterchannels[5];
-    
+
     """
 
-    _CALLSIGN_CHARSET = [chr(x) for x in range(ord("0"), ord("9")+1) +
-                           range(ord("A"), ord("Z")+1)] + [" ", "/"]
+    _CALLSIGN_CHARSET = [chr(x) for x in range(ord("0"), ord("9") + 1) +
+                         range(ord("A"), ord("Z") + 1)] + [" ", "/"]
     _CALLSIGN_CHARSET_REV = dict(zip(_CALLSIGN_CHARSET,
-                                    range(0,len(_CALLSIGN_CHARSET))))
-    _BEACON_CHARSET =  _CALLSIGN_CHARSET + ["+", "."]
+                                 range(0, len(_CALLSIGN_CHARSET))))
+    _BEACON_CHARSET = _CALLSIGN_CHARSET + ["+", "."]
     _BEACON_CHARSET_REV = dict(zip(_BEACON_CHARSET,
-                                    range(0,len(_BEACON_CHARSET))))
+                               range(0, len(_BEACON_CHARSET))))
 
     # WARNING Index are hard wired in memory management code !!!
     SPECIAL_MEMORIES = {
-        "VFOa-1.8M" : -37,
-        "VFOa-3.5M" : -36,
-        "VFOa-5M" : -35,
-        "VFOa-7M" : -34,
-        "VFOa-10M" : -33,
-        "VFOa-14M" : -32,
-        "VFOa-18M" : -31,
-        "VFOa-21M" : -30,
-        "VFOa-24M" : -29,
-        "VFOa-28M" : -28,
-        "VFOa-50M" : -27,
-        "VFOa-FM" : -26,
-        "VFOa-AIR" : -25,
-        "VFOa-144" : -24,
-        "VFOa-430" : -23,
-        "VFOa-HF" : -22,
-        "VFOb-1.8M" : -21,
-        "VFOb-3.5M" : -20,
-        "VFOb-5M" : -19,
-        "VFOb-7M" : -18,
-        "VFOb-10M" : -17,
-        "VFOb-14M" : -16,
-        "VFOb-18M" : -15,
-        "VFOb-21M" : -14,
-        "VFOb-24M" : -13,
-        "VFOb-28M" : -12,
-        "VFOb-50M" : -11,
-        "VFOb-FM" : -10,
-        "VFOb-AIR" : -9,
-        "VFOb-144M" : -8,
-        "VFOb-430M" : -7,
-        "VFOb-HF" : -6,
-        "HOME HF" : -5,
-        "HOME 50M" : -4,
-        "HOME 144M" : -3,
-        "HOME 430M" : -2,
-        "QMB" : -1,
+        "VFOa-1.8M": -37,
+        "VFOa-3.5M": -36,
+        "VFOa-5M": -35,
+        "VFOa-7M": -34,
+        "VFOa-10M": -33,
+        "VFOa-14M": -32,
+        "VFOa-18M": -31,
+        "VFOa-21M": -30,
+        "VFOa-24M": -29,
+        "VFOa-28M": -28,
+        "VFOa-50M": -27,
+        "VFOa-FM": -26,
+        "VFOa-AIR": -25,
+        "VFOa-144": -24,
+        "VFOa-430": -23,
+        "VFOa-HF": -22,
+        "VFOb-1.8M": -21,
+        "VFOb-3.5M": -20,
+        "VFOb-5M": -19,
+        "VFOb-7M": -18,
+        "VFOb-10M": -17,
+        "VFOb-14M": -16,
+        "VFOb-18M": -15,
+        "VFOb-21M": -14,
+        "VFOb-24M": -13,
+        "VFOb-28M": -12,
+        "VFOb-50M": -11,
+        "VFOb-FM": -10,
+        "VFOb-AIR": -9,
+        "VFOb-144M": -8,
+        "VFOb-430M": -7,
+        "VFOb-HF": -6,
+        "HOME HF": -5,
+        "HOME 50M": -4,
+        "HOME 144M": -3,
+        "HOME 430M": -2,
+        "QMB": -1,
     }
     FIRST_VFOB_INDEX = -6
     LAST_VFOB_INDEX = -21
@@ -361,16 +367,16 @@ class FT857Radio(ft817.FT817Radio):
     LAST_VFOA_INDEX = -37
 
     SPECIAL_PMS = {
-        "PMS-1L" : -47,
-        "PMS-1U" : -46,
-        "PMS-2L" : -45,
-        "PMS-2U" : -44,
-        "PMS-3L" : -43,
-        "PMS-3U" : -42,
-        "PMS-4L" : -41,
-        "PMS-4U" : -40,
-        "PMS-5L" : -39,
-        "PMS-5U" : -38,
+        "PMS-1L": -47,
+        "PMS-1U": -46,
+        "PMS-2L": -45,
+        "PMS-2U": -44,
+        "PMS-3L": -43,
+        "PMS-3U": -42,
+        "PMS-4L": -41,
+        "PMS-4U": -40,
+        "PMS-5L": -39,
+        "PMS-5U": -38,
     }
     LAST_PMS_INDEX = -47
 
@@ -379,41 +385,41 @@ class FT857Radio(ft817.FT817Radio):
     SPECIAL_MEMORIES_REV = dict(zip(SPECIAL_MEMORIES.values(),
                                     SPECIAL_MEMORIES.keys()))
 
-    FILTERS  = [ "CFIL", "FIL1", "FIL2" ]
+    FILTERS = ["CFIL", "FIL1", "FIL2"]
     PROGRAMMABLEOPTIONS = [
         "MFa:A/B",              "MFa:A=B",          "MFa:SPL",
         "MFb:MW",               "MFb:SKIP/MCLR",    "MFb:TAG",
         "MFc:STO",              "MFc:RCL",          "MFc:PROC",
         "MFd:RPT",              "MFd:REV",          "MFd:VOX",
-        "MFe:TON/ENC",          "MFe:TON/DEC",      "MFe:TDCH",   
+        "MFe:TON/ENC",          "MFe:TON/DEC",      "MFe:TDCH",
         "MFf:ARTS",             "MFf:SRCH",         "MFf:PMS",
-        "MFg:SCN",              "MFg:PRI",          "MFg:DW",     
+        "MFg:SCN",              "MFg:PRI",          "MFg:DW",
         "MFh:SCOP",             "MFh:WID",          "MFh:STEP",
-        "MFi:MTR",              "MFi:SWR",          "MFi:DISP",   
+        "MFi:MTR",              "MFi:SWR",          "MFi:DISP",
         "MFj:SPOT",             "MFj:BK",           "MFj:KYR",
-        "MFk:TUNE",             "MFk:DOWN",         "MFk:UP",     
+        "MFk:TUNE",             "MFk:DOWN",         "MFk:UP",
         "MFl:NB",               "MFl:AGC",          "MFl:AGC SEL",
-        "MFm:IPO",              "MFm:ATT",          "MFm:NAR",    
+        "MFm:IPO",              "MFm:ATT",          "MFm:NAR",
         "MFn:CFIL",             "MFn:FIL1",         "MFn:FIL2",
-        "MFo:PLY1",             "MFo:PLY2",         "MFo:PLY3",   
+        "MFo:PLY1",             "MFo:PLY2",         "MFo:PLY3",
         "MFp:DNR",              "MFp:DNF",          "MFp:DBF",
-        "01:EXT MENU",          "02:144MHz ARS",    "03:430MHz ARS",     
-        "04:AM&FM DIAL",        "05:AM MIC GAIN",   "06:AM STEP",        
-        "07:APO TIME",          "08:ARTS BEEP",     "09:ARTS ID",          
+        "01:EXT MENU",          "02:144MHz ARS",    "03:430MHz ARS",
+        "04:AM&FM DIAL",        "05:AM MIC GAIN",   "06:AM STEP",
+        "07:APO TIME",          "08:ARTS BEEP",     "09:ARTS ID",
         "10:ARTS IDW",          "11:BEACON TEXT",   "12:BEACON TIME",
-        "13:BEEP TONE",         "14:BEEP VOL",      "15:CAR LSB R",     
+        "13:BEEP TONE",         "14:BEEP VOL",      "15:CAR LSB R",
         "16:CAR LSB T",         "17:CAR USB R",     "18:CAR USB T",
         "19:CAT RATE",          "20:CAT/LIN/TUN",   "21:CLAR DIAL SEL",
         "22:CW AUTO MODE",      "23:CW BFO",        "24:CW DELAY",
-        "25:CW KEY REV",        "26:CW PADDLE",     "27:CW PITCH",      
+        "25:CW KEY REV",        "26:CW PADDLE",     "27:CW PITCH",
         "28:CW QSK",            "29:CW SIDE TONE",  "30:CW SPEED",
         "31:CW TRAINING",       "32:CW WEIGHT",     "33:DCS CODE",
         "34:DCS INV",           "35:DIAL STEP",     "36:DIG DISP",
-        "37:DIG GAIN",          "38:DIG MODE",      "39:DIG SHIFT",      
+        "37:DIG GAIN",          "38:DIG MODE",      "39:DIG SHIFT",
         "40:DIG VOX",           "41:DISP COLOR",    "42:DISP CONTRAST",
         "43:DISP INTENSITY",    "44:DISP MODE",     "45:DSP BPF WIDTH",
-        "46:DSP HPF CUTOFF",    "47:DSP LPF CUTOFF","48:DSP MIC EQ",
-        "49:DSP NR LEVEL",      "50:EMERGENCY",     "51:FM MIC GAIN",   
+        "46:DSP HPF CUTOFF",    "47:DSP LPF CUTOFF", "48:DSP MIC EQ",
+        "49:DSP NR LEVEL",      "50:EMERGENCY",     "51:FM MIC GAIN",
         "52:FM STEP",           "53:HOME->VFO",     "54:LOCK MODE",
         "55:MEM GROUP",         "56:MEM TAG",       "57:MEM/VFO DIAL MODE",
         "58:MIC SCAN",          "59:MIC SEL",       "60:MTR ARX",
@@ -426,8 +432,8 @@ class FT857Radio(ft817.FT817Radio):
         "85:TUNER/ATAS",        "86:TX IF FILTER",  "87:VOX DELAY",
         "88:VOX GAIN",          "89:XVTR A FREQ",   "90:XVTR B FREQ",
         "91:XVTR SEL",
-        "MONI", "Q.SPL",  "TCALL",  "ATC",  "USER"]
-        
+        "MONI", "Q.SPL", "TCALL", "ATC", "USER"]
+
     @classmethod
     def get_prompts(cls):
         rp = chirp_common.RadioPrompts()
@@ -437,14 +443,16 @@ class FT857Radio(ft817.FT817Radio):
             3. Press and hold in the [MODE <] and [MODE >] keys while
                  turning the radio on ("CLONE MODE" will appear on the
                  display).
-            4. <b>After clicking OK</b>, press the [C](SEND) key to send image."""))
+            4. <b>After clicking OK</b>,
+                 press the [C](SEND) key to send image."""))
         rp.pre_upload = _(dedent("""\
             1. Turn radio off.
             2. Connect cable to ACC jack.
             3. Press and hold in the [MODE <] and [MODE >] keys while
                  turning the radio on ("CLONE MODE" will appear on the
                  display).
-            4. Press the [A](RCV) key ("receiving" will appear on the LCD)."""))
+            4. Press the [A](RCV) key ("receiving" will appear on the LCD)."""
+                                 ))
         return rp
 
     def get_features(self):
@@ -470,14 +478,14 @@ class FT857Radio(ft817.FT817Radio):
             mem.cross_mode = self.CROSS_MODES[int(_mem.tmode)]
 
         if mem.tmode == "Tone":
-             mem.rtone = mem.ctone = chirp_common.TONES[_mem.tone]
+            mem.rtone = mem.ctone = chirp_common.TONES[_mem.tone]
         elif mem.tmode == "TSQL":
-             mem.rtone = mem.ctone = chirp_common.TONES[_mem.tone]
-        elif mem.tmode == "DTCS Enc": # UI does not support it yet but
-                                      # this code has alreay been tested
-             mem.dtcs = mem.rx_dtcs = chirp_common.DTCS_CODES[_mem.dcs]
+            mem.rtone = mem.ctone = chirp_common.TONES[_mem.tone]
+        elif mem.tmode == "DTCS Enc":   # UI does not support it yet but
+                                        # this code has alreay been tested
+            mem.dtcs = mem.rx_dtcs = chirp_common.DTCS_CODES[_mem.dcs]
         elif mem.tmode == "DTCS":
-             mem.dtcs = mem.rx_dtcs = chirp_common.DTCS_CODES[_mem.dcs]
+            mem.dtcs = mem.rx_dtcs = chirp_common.DTCS_CODES[_mem.dcs]
         elif mem.tmode == "Cross":
             mem.ctone = chirp_common.TONES[_mem.rxtone]
             # don't want to fail for this
@@ -500,8 +508,8 @@ class FT857Radio(ft817.FT817Radio):
             _mem.tone = _mem.rxtone = chirp_common.TONES.index(mem.rtone)
         elif mem.tmode == "TSQL":
             _mem.tone = _mem.rxtone = chirp_common.TONES.index(mem.ctone)
-        elif mem.tmode == "DTCS Enc": # UI does not support it yet but
-                                      # this code has alreay been tested
+        elif mem.tmode == "DTCS Enc":   # UI does not support it yet but
+                                        # this code has alreay been tested
             _mem.dcs = _mem.rxdcs = chirp_common.DTCS_CODES.index(mem.dtcs)
         elif mem.tmode == "DTCS":
             _mem.dcs = _mem.rxdcs = chirp_common.DTCS_CODES.index(mem.rx_dtcs)
@@ -524,8 +532,9 @@ class FT857Radio(ft817.FT817Radio):
         panel = RadioSettingGroup("panel", "Panel settings")
         extended = RadioSettingGroup("extended", "Extended")
         panelcontr = RadioSettingGroup("panelcontr", "Panel controls")
-        top = RadioSettingGroup("top", "All Settings", basic, cw, packet,
-                                panelcontr, panel, extended)
+
+        top = RadioSettings(basic, cw, packet,
+                            panelcontr, panel, extended)
 
         rs = RadioSetting("extended_menu", "Extended menu",
                           RadioSettingValueBoolean(_settings.extended_menu))
@@ -539,7 +548,9 @@ class FT857Radio(ft817.FT817Radio):
         options = ["enable", "disable"]
         rs = RadioSetting("disable_amfm_dial", "AM&FM Dial",
                           RadioSettingValueList(options,
-                                        options[_settings.disable_amfm_dial]))
+                                                options[
+                                                    _settings.disable_amfm_dial
+                                                        ]))
         panel.append(rs)
         rs = RadioSetting("am_mic", "AM mic gain",
                           RadioSettingValueInteger(0, 100, _settings.am_mic))
@@ -547,80 +558,91 @@ class FT857Radio(ft817.FT817Radio):
         options = ["OFF", "1h", "2h", "3h", "4h", "5h", "6h"]
         rs = RadioSetting("apo_time", "APO time",
                           RadioSettingValueList(options,
-                                        options[_settings.apo_time]))
+                                                options[_settings.apo_time]))
         basic.append(rs)
         options = ["OFF", "Range", "All"]
         rs = RadioSetting("arts_beep", "ARTS beep",
                           RadioSettingValueList(options,
-                                        options[_settings.arts_beep]))
+                                                options[_settings.arts_beep]))
         basic.append(rs)
         rs = RadioSetting("arts_id", "ARTS ID",
                           RadioSettingValueBoolean(_settings.arts_id))
         extended.append(rs)
-        s = RadioSettingValueString(0, 10, 
-                            ''.join([self._CALLSIGN_CHARSET[x] for x in 
-                                        self._memobj.arts_idw]))
-        s.set_charset(self._CALLSIGN_CHARSET)
-        rs = RadioSetting("arts_idw", "ARTS IDW", s)
+        st = RadioSettingValueString(0, 10,
+                                     ''.join([self._CALLSIGN_CHARSET[x]
+                                             for x in self._memobj.arts_idw]))
+        st.set_charset(self._CALLSIGN_CHARSET)
+        rs = RadioSetting("arts_idw", "ARTS IDW", st)
         extended.append(rs)
-        s = RadioSettingValueString(0, 40, 
-                            ''.join([self._BEACON_CHARSET[x] for x in 
-                                        self._memobj.beacon_text1]))
-        s.set_charset(self._BEACON_CHARSET)
-        rs = RadioSetting("beacon_text1", "Beacon text1", s)
+        st = RadioSettingValueString(0, 40,
+                                     ''.join([self._BEACON_CHARSET[x]
+                                             for x in self._memobj.beacon_text1
+                                              ]))
+        st.set_charset(self._BEACON_CHARSET)
+        rs = RadioSetting("beacon_text1", "Beacon text1", st)
         extended.append(rs)
-        s = RadioSettingValueString(0, 40, 
-                            ''.join([self._BEACON_CHARSET[x] for x in 
-                                        self._memobj.beacon_text2]))
-        s.set_charset(self._BEACON_CHARSET)
-        rs = RadioSetting("beacon_text2", "Beacon text2", s)
+        st = RadioSettingValueString(0, 40,
+                                     ''.join([self._BEACON_CHARSET[x]
+                                              for x in
+                                              self._memobj.beacon_text2]))
+        st.set_charset(self._BEACON_CHARSET)
+        rs = RadioSetting("beacon_text2", "Beacon text2", st)
         extended.append(rs)
-        s = RadioSettingValueString(0, 40, 
-                            ''.join([self._BEACON_CHARSET[x] for x in 
-                                        self._memobj.beacon_text3]))
-        s.set_charset(self._BEACON_CHARSET)
-        rs = RadioSetting("beacon_text3", "Beacon text3", s)
+        st = RadioSettingValueString(0, 40,
+                                     ''.join([self._BEACON_CHARSET[x]
+                                              for x in
+                                              self._memobj.beacon_text3]))
+        st.set_charset(self._BEACON_CHARSET)
+        rs = RadioSetting("beacon_text3", "Beacon text3", st)
         extended.append(rs)
-        options = ["OFF"]+["%i sec" % i for i in range(1,256)]
+        options = ["OFF"] + ["%i sec" % i for i in range(1, 256)]
         rs = RadioSetting("beacon_time", "Beacon time",
                           RadioSettingValueList(options,
-                                        options[_settings.beacon_time]))
+                                                options[_settings.beacon_time])
+                          )
         extended.append(rs)
         options = ["440Hz", "880Hz", "1760Hz"]
         rs = RadioSetting("beep_tone", "Beep tone",
                           RadioSettingValueList(options,
-                                        options[_settings.beep_tone]))
+                                                options[_settings.beep_tone]))
         panel.append(rs)
         rs = RadioSetting("beep_vol", "Beep volume",
                           RadioSettingValueInteger(0, 100, _settings.beep_vol))
         panel.append(rs)
         rs = RadioSetting("r_lsb_car", "LSB Rx carrier point (*10 Hz)",
-                          RadioSettingValueInteger(-30, 30, _settings.r_lsb_car))
+                          RadioSettingValueInteger(-30, 30,
+                                                   _settings.r_lsb_car))
         extended.append(rs)
         rs = RadioSetting("r_usb_car", "USB Rx carrier point (*10 Hz)",
-                          RadioSettingValueInteger(-30, 30, _settings.r_usb_car))
+                          RadioSettingValueInteger(-30, 30,
+                                                   _settings.r_usb_car))
         extended.append(rs)
         rs = RadioSetting("t_lsb_car", "LSB Tx carrier point (*10 Hz)",
-                          RadioSettingValueInteger(-30, 30, _settings.t_lsb_car))
+                          RadioSettingValueInteger(-30, 30,
+                                                   _settings.t_lsb_car))
         extended.append(rs)
         rs = RadioSetting("t_usb_car", "USB Tx carrier point (*10 Hz)",
-                          RadioSettingValueInteger(-30, 30, _settings.t_usb_car))
+                          RadioSettingValueInteger(-30, 30,
+                                                   _settings.t_usb_car))
         extended.append(rs)
         options = ["4800", "9600", "38400"]
         rs = RadioSetting("cat_rate", "CAT rate",
                           RadioSettingValueList(options,
-                                        options[_settings.cat_rate]))
+                                                options[_settings.cat_rate]))
         basic.append(rs)
         options = ["CAT", "Linear", "Tuner"]
         rs = RadioSetting("cat_lin_tun", "CAT/LIN/TUN selection",
                           RadioSettingValueList(options,
-                                        options[_settings.cat_lin_tun]))
+                                                options[_settings.cat_lin_tun])
+                          )
         extended.append(rs)
-        options = ["MAIN", "VFO/MEM", "CLAR"]   # TODO test the 3 options on non D radio
-                                                # which have only SEL and MAIN
+        options = ["MAIN", "VFO/MEM", "CLAR"]
+        # TODO test the 3 options on non D radio
+        # which have only SEL and MAIN
         rs = RadioSetting("clar_dial_sel", "Clarifier dial selection",
                           RadioSettingValueList(options,
-                                        options[_settings.clar_dial_sel]))
+                                                options[
+                                                    _settings.clar_dial_sel]))
         panel.append(rs)
         rs = RadioSetting("cw_auto_mode", "CW Automatic mode",
                           RadioSettingValueBoolean(_settings.cw_auto_mode))
@@ -628,73 +650,77 @@ class FT857Radio(ft817.FT817Radio):
         options = ["USB", "LSB", "AUTO"]
         rs = RadioSetting("cw_bfo", "CW BFO",
                           RadioSettingValueList(options,
-                                        options[_settings.cw_bfo]))
+                                                options[_settings.cw_bfo]))
         cw.append(rs)
-        options = ["FULL"]+["%i ms" % (i*10) for i in range(3,301)]
+        options = ["FULL"] + ["%i ms" % (i * 10) for i in range(3, 301)]
         val = (_settings.cw_delay + _settings.cw_delay_hi * 256) - 2
         rs = RadioSetting("cw_delay", "CW delay",
-                          RadioSettingValueList(options,
-                                        options[val]))
+                          RadioSettingValueList(options, options[val]))
         cw.append(rs)
         options = ["Normal", "Reverse"]
         rs = RadioSetting("cw_key_rev", "CW key reverse",
                           RadioSettingValueList(options,
-                                        options[_settings.cw_key_rev]))
+                                                options[_settings.cw_key_rev]))
         cw.append(rs)
         rs = RadioSetting("cw_paddle", "CW paddle",
                           RadioSettingValueBoolean(_settings.cw_paddle))
         cw.append(rs)
-        options = ["%i Hz" % i for i in range(400,801,100)]
+        options = ["%i Hz" % i for i in range(400, 801, 100)]
         rs = RadioSetting("cw_pitch", "CW pitch",
                           RadioSettingValueList(options,
-                                        options[_settings.cw_pitch]))
+                                                options[_settings.cw_pitch]))
         cw.append(rs)
-        options = ["%i ms" % i for i in range(5,31,5)]
+        options = ["%i ms" % i for i in range(5, 31, 5)]
         rs = RadioSetting("cw_qsk", "CW QSK",
                           RadioSettingValueList(options,
-                                        options[_settings.cw_qsk]))
+                                                options[_settings.cw_qsk]))
         cw.append(rs)
         rs = RadioSetting("cw_sidetone", "CW sidetone volume",
-                          RadioSettingValueInteger(0, 100, _settings.cw_sidetone))
+                          RadioSettingValueInteger(0, 100,
+                                                   _settings.cw_sidetone))
         cw.append(rs)
-        options = ["%i wpm" % i for i in range(4,61)]
+        options = ["%i wpm" % i for i in range(4, 61)]
         rs = RadioSetting("cw_speed", "CW speed",
                           RadioSettingValueList(options,
-                                        options[_settings.cw_speed]))
+                                                options[_settings.cw_speed]))
         cw.append(rs)
         options = ["Numeric", "Alphabet", "AlphaNumeric"]
         rs = RadioSetting("cw_training", "CW trainig",
                           RadioSettingValueList(options,
-                                        options[_settings.cw_training]))
+                                                options[_settings.cw_training])
+                          )
         cw.append(rs)
-        options = ["1:%1.1f" % (i/10) for i in range(25,46,1)]
+        options = ["1:%1.1f" % (i / 10) for i in range(25, 46, 1)]
         rs = RadioSetting("cw_weight", "CW weight",
                           RadioSettingValueList(options,
-                                        options[_settings.cw_weight]))
+                                                options[_settings.cw_weight]))
         cw.append(rs)
         options = ["Tn-Rn", "Tn-Riv", "Tiv-Rn", "Tiv-Riv"]
         rs = RadioSetting("dcs_inv", "DCS inv",
                           RadioSettingValueList(options,
-                                        options[_settings.dcs_inv]))
+                                                options[_settings.dcs_inv]))
         extended.append(rs)
         options = ["Fine", "Coarse"]
         rs = RadioSetting("dial_step", "Dial step",
                           RadioSettingValueList(options,
-                                        options[_settings.dial_step]))
+                                                options[_settings.dial_step]))
         panel.append(rs)
         rs = RadioSetting("dig_disp", "Dig disp (*10 Hz)",
-                          RadioSettingValueInteger(-300, 300, _settings.dig_disp))
+                          RadioSettingValueInteger(-300, 300,
+                                                   _settings.dig_disp))
         packet.append(rs)
         rs = RadioSetting("dig_mic", "Dig gain",
-                          RadioSettingValueInteger(0, 100, _settings.dig_mic))
+                          RadioSettingValueInteger(0, 100,
+                                                   _settings.dig_mic))
         packet.append(rs)
         options = ["RTTYL", "RTTYU", "PSK31-L", "PSK31-U", "USER-L", "USER-U"]
         rs = RadioSetting("dig_mode", "Dig mode",
                           RadioSettingValueList(options,
-                                        options[_settings.dig_mode]))
+                                                options[_settings.dig_mode]))
         packet.append(rs)
         rs = RadioSetting("dig_shift", "Dig shift (*10 Hz)",
-                          RadioSettingValueInteger(-300, 300, _settings.dig_shift))
+                          RadioSettingValueInteger(-300, 300,
+                                                   _settings.dig_shift))
         packet.append(rs)
         rs = RadioSetting("dig_vox", "Dig vox",
                           RadioSettingValueInteger(0, 100, _settings.dig_vox))
@@ -702,66 +728,83 @@ class FT857Radio(ft817.FT817Radio):
         options = ["ARTS", "BAND", "FIX", "MEMGRP", "MODE", "MTR", "VFO"]
         rs = RadioSetting("disp_color", "Display color mode",
                           RadioSettingValueList(options,
-                                        options[_settings.disp_color]))
+                                                options[_settings.disp_color]))
         panel.append(rs)
         rs = RadioSetting("disp_color_arts", "Display color ARTS set",
-                          RadioSettingValueInteger(0, 1,_settings.disp_color_arts))
+                          RadioSettingValueInteger(0, 1,
+                                                   _settings.disp_color_arts))
         panel.append(rs)
         rs = RadioSetting("disp_color_band", "Display color band set",
-                          RadioSettingValueInteger(0, 1,_settings.disp_color_band))
+                          RadioSettingValueInteger(0, 1,
+                                                   _settings.disp_color_band))
         panel.append(rs)
-        rs = RadioSetting("disp_color_memgrp", "Display color memory group set",
-                          RadioSettingValueInteger(0, 1,_settings.disp_color_memgrp))
+        rs = RadioSetting("disp_color_memgrp",
+                          "Display color memory group set",
+                          RadioSettingValueInteger(0, 1,
+                                                   _settings.disp_color_memgrp)
+                          )
         panel.append(rs)
         rs = RadioSetting("disp_color_mode", "Display color mode set",
-                          RadioSettingValueInteger(0, 1,_settings.disp_color_mode))
+                          RadioSettingValueInteger(0, 1,
+                                                   _settings.disp_color_mode))
         panel.append(rs)
         rs = RadioSetting("disp_color_mtr", "Display color meter set",
-                          RadioSettingValueInteger(0, 1,_settings.disp_color_mtr))
+                          RadioSettingValueInteger(0, 1,
+                                                   _settings.disp_color_mtr))
         panel.append(rs)
         rs = RadioSetting("disp_color_vfo", "Display color VFO set",
-                          RadioSettingValueInteger(0, 1,_settings.disp_color_vfo))
+                          RadioSettingValueInteger(0, 1,
+                                                   _settings.disp_color_vfo))
         panel.append(rs)
         rs = RadioSetting("disp_color_fix", "Display color fix set",
-                          RadioSettingValueInteger(1, 32,_settings.disp_color_fix+1)) 
+                          RadioSettingValueInteger(1, 32,
+                                                   _settings.disp_color_fix + 1
+                                                   ))
         panel.append(rs)
         rs = RadioSetting("disp_contrast", "Contrast",
-                          RadioSettingValueInteger(3, 15,_settings.disp_contrast+2))
+                          RadioSettingValueInteger(3, 15,
+                                                   _settings.disp_contrast + 2)
+                          )
         panel.append(rs)
         rs = RadioSetting("disp_intensity", "Intensity",
-                          RadioSettingValueInteger(1, 3,_settings.disp_intensity))
+                          RadioSettingValueInteger(1, 3,
+                                                   _settings.disp_intensity))
         panel.append(rs)
         options = ["OFF", "Auto1", "Auto2", "ON"]
         rs = RadioSetting("disp_mode", "Display backlight mode",
                           RadioSettingValueList(options,
-                                        options[_settings.disp_mode]))
+                                                options[_settings.disp_mode]))
         panel.append(rs)
         options = ["60Hz", "120Hz", "240Hz"]
         rs = RadioSetting("dsp_bpf", "Dsp band pass filter",
                           RadioSettingValueList(options,
-                                        options[_settings.dsp_bpf]))
+                                                options[_settings.dsp_bpf]))
         cw.append(rs)
-        options = ["100Hz", "160Hz", "220Hz", "280Hz", "340Hz", "400Hz", "460Hz", "520Hz",
-            "580Hz", "640Hz", "720Hz", "760Hz", "820Hz", "880Hz", "940Hz", "1000Hz"]
+        options = ["100Hz", "160Hz", "220Hz", "280Hz", "340Hz", "400Hz",
+                   "460Hz", "520Hz", "580Hz", "640Hz", "720Hz", "760Hz",
+                   "820Hz", "880Hz", "940Hz", "1000Hz"]
         rs = RadioSetting("dsp_hpf", "Dsp hi pass filter cut off",
                           RadioSettingValueList(options,
-                                        options[_settings.dsp_hpf]))
+                                                options[_settings.dsp_hpf]))
         basic.append(rs)
-        options = ["1000Hz", "1160Hz", "1320Hz", "1480Hz", "1650Hz", "1800Hz", "1970Hz", "2130Hz",
-            "2290Hz", "2450Hz", "2610Hz", "2770Hz", "2940Hz", "3100Hz", "3260Hz", "3420Hz",
-            "3580Hz", "3740Hz", "3900Hz", "4060Hz", "4230Hz", "4390Hz", "4550Hz", "4710Hz",
-            "4870Hz", "5030Hz", "5190Hz", "5390Hz", "5520Hz", "5680Hz", "5840Hz", "6000Hz"]
+        options = ["1000Hz", "1160Hz", "1320Hz", "1480Hz", "1650Hz", "1800Hz",
+                   "1970Hz", "2130Hz", "2290Hz", "2450Hz", "2610Hz", "2770Hz",
+                   "2940Hz", "3100Hz", "3260Hz", "3420Hz", "3580Hz", "3740Hz",
+                   "3900Hz", "4060Hz", "4230Hz", "4390Hz", "4550Hz", "4710Hz",
+                   "4870Hz", "5030Hz", "5190Hz", "5390Hz", "5520Hz", "5680Hz",
+                   "5840Hz", "6000Hz"]
         rs = RadioSetting("dsp_lpf", "Dsp low pass filter cut off",
                           RadioSettingValueList(options,
-                                        options[_settings.dsp_lpf]))
+                                                options[_settings.dsp_lpf]))
         basic.append(rs)
         options = ["OFF", "LPF", "HPF", "BOTH"]
         rs = RadioSetting("dsp_mic_eq", "Dsp mic equalization",
                           RadioSettingValueList(options,
-                                        options[_settings.dsp_mic_eq]))
+                                                options[_settings.dsp_mic_eq]))
         basic.append(rs)
         rs = RadioSetting("dsp_nr", "DSP noise reduction level",
-                          RadioSettingValueInteger(1, 16, _settings.dsp_nr+1))
+                          RadioSettingValueInteger(1, 16,
+                                                   _settings.dsp_nr + 1))
         basic.append(rs)
         # emergency only for US model
         rs = RadioSetting("fm_mic", "FM mic gain",
@@ -773,16 +816,18 @@ class FT857Radio(ft817.FT817Radio):
         options = ["Dial", "Freq", "Panel", "All"]
         rs = RadioSetting("lock_mode", "Lock mode",
                           RadioSettingValueList(options,
-                                        options[_settings.lock_mode]))
+                                                options[_settings.lock_mode]))
         panel.append(rs)
         rs = RadioSetting("mem_group", "Mem group",
                           RadioSettingValueBoolean(_settings.mem_group))
         basic.append(rs)
         options = ["CW SIDETONE", "CW SPEED", "MHz/MEM GRP", "MIC GAIN",
-            "NB LEVEL", "RF POWER", "STEP"]
+                   "NB LEVEL", "RF POWER", "STEP"]
         rs = RadioSetting("mem_vfo_dial_mode", "Mem/VFO dial mode",
                           RadioSettingValueList(options,
-                                        options[_settings.mem_vfo_dial_mode]))
+                                                options[
+                                                    _settings.mem_vfo_dial_mode
+                                                       ]))
         panel.append(rs)
         rs = RadioSetting("mic_scan", "Mic scan",
                           RadioSettingValueBoolean(_settings.mic_scan))
@@ -790,17 +835,19 @@ class FT857Radio(ft817.FT817Radio):
         options = ["NOR", "RMT", "CAT"]
         rs = RadioSetting("mic_sel", "Mic selection",
                           RadioSettingValueList(options,
-                                        options[_settings.mic_sel]))
+                                                options[_settings.mic_sel]))
         extended.append(rs)
-        options = ["SIG", "CTR", "VLT", "N/A", "FS",  "OFF"]
+        options = ["SIG", "CTR", "VLT", "N/A", "FS", "OFF"]
         rs = RadioSetting("mtr_arx_sel", "Meter receive selection",
                           RadioSettingValueList(options,
-                                        options[_settings.mtr_arx_sel]))
+                                                options[_settings.mtr_arx_sel])
+                          )
         extended.append(rs)
         options = ["PWR", "ALC", "MOD", "SWR", "VLT", "N/A", "OFF"]
         rs = RadioSetting("mtr_atx_sel", "Meter transmit selection",
                           RadioSettingValueList(options,
-                                        options[_settings.mtr_atx_sel]))
+                                                options[_settings.mtr_atx_sel])
+                          )
         extended.append(rs)
         rs = RadioSetting("mtr_peak_hold", "Meter peak hold",
                           RadioSettingValueBoolean(_settings.mtr_peak_hold))
@@ -808,44 +855,53 @@ class FT857Radio(ft817.FT817Radio):
         rs = RadioSetting("nb_level", "Noise blanking level",
                           RadioSettingValueInteger(0, 100, _settings.nb_level))
         basic.append(rs)
-        s = RadioSettingValueString(0, 4, 
-                            ''.join([self._CALLSIGN_CHARSET[x] for x in 
-                                        self._memobj.op_filter1_name]))
-        s.set_charset(self._CALLSIGN_CHARSET)
-        rs = RadioSetting("op_filter1_name", "Optional filter1 name", s)
+        st = RadioSettingValueString(0, 4,
+                                     ''.join([self._CALLSIGN_CHARSET[x]
+                                              for x in
+                                              self._memobj.op_filter1_name]))
+        st.set_charset(self._CALLSIGN_CHARSET)
+        rs = RadioSetting("op_filter1_name", "Optional filter1 name", st)
         extended.append(rs)
-        s = RadioSettingValueString(0, 4, 
-                            ''.join([self._CALLSIGN_CHARSET[x] for x in 
-                                        self._memobj.op_filter2_name]))
-        s.set_charset(self._CALLSIGN_CHARSET)
-        rs = RadioSetting("op_filter2_name", "Optional filter2 name", s)
+        st = RadioSettingValueString(0, 4,
+                                     ''.join([self._CALLSIGN_CHARSET[x]
+                                              for x in
+                                              self._memobj.op_filter2_name]))
+        st.set_charset(self._CALLSIGN_CHARSET)
+        rs = RadioSetting("op_filter2_name", "Optional filter2 name", st)
         extended.append(rs)
         rs = RadioSetting("pg_a", "Programmable key MFq:A",
                           RadioSettingValueList(self.PROGRAMMABLEOPTIONS,
-                                        self.PROGRAMMABLEOPTIONS[_settings.pg_a]))
+                                                self.PROGRAMMABLEOPTIONS[
+                                                    _settings.pg_a]))
         extended.append(rs)
         rs = RadioSetting("pg_b", "Programmable key MFq:B",
                           RadioSettingValueList(self.PROGRAMMABLEOPTIONS,
-                                        self.PROGRAMMABLEOPTIONS[_settings.pg_b]))
+                                                self.PROGRAMMABLEOPTIONS[
+                                                    _settings.pg_b]))
         extended.append(rs)
         rs = RadioSetting("pg_c", "Programmable key MFq:C",
                           RadioSettingValueList(self.PROGRAMMABLEOPTIONS,
-                                        self.PROGRAMMABLEOPTIONS[_settings.pg_c]))
+                                                self.PROGRAMMABLEOPTIONS[
+                                                    _settings.pg_c]))
         extended.append(rs)
         rs = RadioSetting("pg_acc", "Programmable mic key ACC",
                           RadioSettingValueList(self.PROGRAMMABLEOPTIONS,
-                                        self.PROGRAMMABLEOPTIONS[_settings.pg_acc]))
+                                                self.PROGRAMMABLEOPTIONS[
+                                                    _settings.pg_acc]))
         extended.append(rs)
         rs = RadioSetting("pg_p1", "Programmable mic key P1",
                           RadioSettingValueList(self.PROGRAMMABLEOPTIONS,
-                                        self.PROGRAMMABLEOPTIONS[_settings.pg_p1]))
+                                                self.PROGRAMMABLEOPTIONS[
+                                                    _settings.pg_p1]))
         extended.append(rs)
         rs = RadioSetting("pg_p2", "Programmable mic key P2",
                           RadioSettingValueList(self.PROGRAMMABLEOPTIONS,
-                                        self.PROGRAMMABLEOPTIONS[_settings.pg_p2]))
+                                                self.PROGRAMMABLEOPTIONS[
+                                                    _settings.pg_p2]))
         extended.append(rs)
         rs = RadioSetting("pkt1200", "Packet 1200 gain level",
-                          RadioSettingValueInteger(0, 100, _settings.pkt1200))
+                          RadioSettingValueInteger(0, 100,
+                                                   _settings.pkt1200))
         packet.append(rs)
         rs = RadioSetting("pkt9600", "Packet 9600 gain level",
                           RadioSettingValueInteger(0, 100, _settings.pkt9600))
@@ -853,30 +909,36 @@ class FT857Radio(ft817.FT817Radio):
         options = ["1200", "9600"]
         rs = RadioSetting("pkt_rate", "Packet rate",
                           RadioSettingValueList(options,
-                                        options[_settings.pkt_rate]))
+                                                options[_settings.pkt_rate]))
         packet.append(rs)
         rs = RadioSetting("proc_level", "Proc level",
-                          RadioSettingValueInteger(0, 100, _settings.proc_level))
+                          RadioSettingValueInteger(0, 100,
+                                                   _settings.proc_level))
         basic.append(rs)
         rs = RadioSetting("rf_power_hf", "Rf power set HF",
-                          RadioSettingValueInteger(5, 100, _settings.rf_power_hf))
+                          RadioSettingValueInteger(5, 100,
+                                                   _settings.rf_power_hf))
         basic.append(rs)
         rs = RadioSetting("rf_power_6m", "Rf power set 6m",
-                          RadioSettingValueInteger(5, 100, _settings.rf_power_6m))
+                          RadioSettingValueInteger(5, 100,
+                                                   _settings.rf_power_6m))
         basic.append(rs)
         rs = RadioSetting("rf_power_vhf", "Rf power set VHF",
-                          RadioSettingValueInteger(5, 50, _settings.rf_power_vhf))
+                          RadioSettingValueInteger(5, 50,
+                                                   _settings.rf_power_vhf))
         basic.append(rs)
         rs = RadioSetting("rf_power_uhf", "Rf power set UHF",
-                          RadioSettingValueInteger(2, 20, _settings.rf_power_uhf))
+                          RadioSettingValueInteger(2, 20,
+                                                   _settings.rf_power_uhf))
         basic.append(rs)
         options = ["TIME", "BUSY", "STOP"]
         rs = RadioSetting("scan_mode", "Scan mode",
                           RadioSettingValueList(options,
-                                        options[_settings.scan_mode]))
+                                                options[_settings.scan_mode]))
         basic.append(rs)
         rs = RadioSetting("scan_resume", "Scan resume",
-                          RadioSettingValueInteger(1, 10, _settings.scan_resume))
+                          RadioSettingValueInteger(1, 10,
+                                                   _settings.scan_resume))
         basic.append(rs)
         rs = RadioSetting("split_tone", "Split tone enable",
                           RadioSettingValueBoolean(_settings.split_tone))
@@ -884,24 +946,26 @@ class FT857Radio(ft817.FT817Radio):
         options = ["RF-Gain", "Squelch"]
         rs = RadioSetting("sql_rf_gain", "Squelch/RF-Gain",
                           RadioSettingValueList(options,
-                                        options[_settings.sql_rf_gain]))
+                                                options[_settings.sql_rf_gain])
+                          )
         panel.append(rs)
         rs = RadioSetting("ssb_mic", "SSB Mic gain",
                           RadioSettingValueInteger(0, 100, _settings.ssb_mic))
         basic.append(rs)
-        options = ["Off"]+["%i" % i for i in range(1, 21)]
+        options = ["Off"] + ["%i" % i for i in range(1, 21)]
         rs = RadioSetting("tot_time", "Time-out timer",
                           RadioSettingValueList(options,
-                                        options[_settings.tot_time]))
+                                                options[_settings.tot_time]))
         basic.append(rs)
         options = ["OFF", "ATAS(HF)", "ATAS(HF&50)", "ATAS(ALL)", "TUNER"]
         rs = RadioSetting("tuner_atas", "Tuner/ATAS device",
                           RadioSettingValueList(options,
-                                        options[_settings.tuner_atas]))
+                                                options[_settings.tuner_atas]))
         extended.append(rs)
         rs = RadioSetting("tx_if_filter", "Transmit IF filter",
                           RadioSettingValueList(self.FILTERS,
-                                        self.FILTERS[_settings.tx_if_filter]))
+                                                self.FILTERS[
+                                                    _settings.tx_if_filter]))
         basic.append(rs)
         rs = RadioSetting("vox_delay", "VOX delay (*100 ms)",
                           RadioSettingValueInteger(1, 30, _settings.vox_delay))
@@ -910,17 +974,24 @@ class FT857Radio(ft817.FT817Radio):
                           RadioSettingValueInteger(1, 100, _settings.vox_gain))
         basic.append(rs)
         rs = RadioSetting("xvtr_a", "Xvtr A displacement",
-                          RadioSettingValueInteger(-4294967295, 4294967295, self._memobj.xvtr_a_offset * (-1 if _settings.xvtr_a_negative else 1)))
+                          RadioSettingValueInteger(
+                              -4294967295, 4294967295,
+                              self._memobj.xvtr_a_offset *
+                              (-1 if _settings.xvtr_a_negative else 1)))
+
         extended.append(rs)
         rs = RadioSetting("xvtr_b", "Xvtr B displacement",
-                          RadioSettingValueInteger(-4294967295, 4294967295, self._memobj.xvtr_b_offset * (-1 if _settings.xvtr_b_negative else 1)))
+                          RadioSettingValueInteger(
+                              -4294967295, 4294967295,
+                              self._memobj.xvtr_b_offset *
+                              (-1 if _settings.xvtr_b_negative else 1)))
         extended.append(rs)
         options = ["OFF", "XVTR A", "XVTR B"]
         rs = RadioSetting("xvtr_sel", "Transverter function selection",
                           RadioSettingValueList(options,
-                                        options[_settings.xvtr_sel]))
+                                                options[_settings.xvtr_sel]))
         extended.append(rs)
-        
+
         rs = RadioSetting("disp", "Display large",
                           RadioSettingValueBoolean(_settings.disp))
         panel.append(rs)
@@ -930,12 +1001,13 @@ class FT857Radio(ft817.FT817Radio):
         options = ["Auto", "Fast", "Slow", "Off"]
         rs = RadioSetting("agc", "AGC",
                           RadioSettingValueList(options,
-                                        options[_settings.agc]))
+                                                options[_settings.agc]))
         panelcontr.append(rs)
         options = ["PWR", "ALC", "SWR", "MOD"]
         rs = RadioSetting("pwr_meter_mode", "Power meter mode",
                           RadioSettingValueList(options,
-                                        options[_settings.pwr_meter_mode]))
+                                                options[
+                                                    _settings.pwr_meter_mode]))
         panelcontr.append(rs)
         rs = RadioSetting("vox", "Vox",
                           RadioSettingValueBoolean(_settings.vox))
@@ -948,13 +1020,13 @@ class FT857Radio(ft817.FT817Radio):
         cw.append(rs)
         options = ["enabled", "disabled"]
         rs = RadioSetting("fst", "Fast",
-                          RadioSettingValueList(options,
-                                        options[_settings.fst]))
+                          RadioSettingValueList(options, options[_settings.fst]
+                                                ))
         panelcontr.append(rs)
         options = ["enabled", "disabled"]
         rs = RadioSetting("lock", "Lock",
                           RadioSettingValueList(options,
-                                        options[_settings.lock]))
+                                                options[_settings.lock]))
         panelcontr.append(rs)
         rs = RadioSetting("scope_peakhold", "Scope max hold",
                           RadioSettingValueBoolean(_settings.scope_peakhold))
@@ -962,12 +1034,13 @@ class FT857Radio(ft817.FT817Radio):
         options = ["21", "31", "127"]
         rs = RadioSetting("scope_width", "Scope width (channels)",
                           RadioSettingValueList(options,
-                                        options[_settings.scope_width]))
+                                                options[_settings.scope_width])
+                          )
         panelcontr.append(rs)
         rs = RadioSetting("proc", "Speech processor",
                           RadioSettingValueBoolean(_settings.proc))
         panelcontr.append(rs)
-        
+
         return top
 
     def set_settings(self, settings):
@@ -986,40 +1059,44 @@ class FT857Radio(ft817.FT817Radio):
                 else:
                     obj = _settings
                     setting = element.get_name()
-                if os.getenv("CHIRP_DEBUG"):
-	                print "Setting %s(%s) <= %s" % (setting, 
-                                    getattr(obj, setting), element.value)
+                try:
+                    LOG.debug("Setting %s(%s) <= %s" % (setting,
+                              getattr(obj, setting), element.value))
+                except AttributeError:
+                    LOG.debug("Setting %s <= %s" % (setting, element.value))
                 if setting == "arts_idw":
                     self._memobj.arts_idw = \
-                        [self._CALLSIGN_CHARSET_REV[x] for x in 
-                                                        str(element.value)]
+                        [self._CALLSIGN_CHARSET_REV[x]
+                         for x in str(element.value)]
                 elif setting in ["beacon_text1", "beacon_text2",
-                        "beacon_text3", "op_filter1_name", "op_filter2_name"]:
+                                 "beacon_text3", "op_filter1_name",
+                                 "op_filter2_name"]:
                     setattr(self._memobj, setting,
-                        [self._BEACON_CHARSET_REV[x] for x in 
-                                                        str(element.value)])
+                            [self._BEACON_CHARSET_REV[x]
+                             for x in str(element.value)])
                 elif setting == "cw_delay":
-                    val = int(element.value)+2
-                    setattr(obj, "cw_delay_hi", val/256)
-                    setattr(obj, setting, val&0xff)
+                    val = int(element.value) + 2
+                    setattr(obj, "cw_delay_hi", val / 256)
+                    setattr(obj, setting, val & 0xff)
                 elif setting == "dig_vox":
                     val = int(element.value)
-                    setattr(obj, "dig_vox_enable", int(val>0))
+                    setattr(obj, "dig_vox_enable", int(val > 0))
                     setattr(obj, setting, val)
                 elif setting in ["disp_color_fix", "dsp_nr"]:
-                    setattr(obj, setting, int(element.value)-1)
+                    setattr(obj, setting, int(element.value) - 1)
                 elif setting == "disp_contrast":
-                    setattr(obj, setting, int(element.value)-2)
+                    setattr(obj, setting, int(element.value) - 2)
                 elif setting in ["xvtr_a", "xvtr_b"]:
                     val = int(element.value)
-                    setattr(obj, setting + "_negative", int(val<0))
+                    setattr(obj, setting + "_negative", int(val < 0))
                     setattr(self._memobj, setting + "_offset", abs(val))
                 else:
                     setattr(obj, setting, element.value)
-            except Exception, e:
-                print element.get_name()
+            except:
+                LOG.debug(element.get_name())
                 raise
 
+
 @directory.register
 class FT857USRadio(FT857Radio):
     """Yaesu FT857/897 (US version)"""
@@ -1030,20 +1107,20 @@ class FT857USRadio(FT857Radio):
     _model = ""
     _US_model = True
     _memsize = 7481
-    # block 9 (140 Bytes long) is to be repeted 40 times 
+    # block 9 (140 Bytes long) is to be repeted 40 times
     # should be 42 times but this way I can use original 817 functions
-    _block_lengths = [ 2, 82, 252, 196, 252, 196, 212, 55, 140, 140, 140, 38,
-                       176, 140]
+    _block_lengths = [2, 82, 252, 196, 252, 196, 212, 55, 140, 140, 140, 38,
+                      176, 140]
 
     SPECIAL_60M = {
-        "M-601" : -52,
-        "M-602" : -51,
-        "M-603" : -50,
-        "M-604" : -49,
-        "M-605" : -48,
+        "M-601": -52,
+        "M-602": -51,
+        "M-603": -50,
+        "M-604": -49,
+        "M-605": -48,
         }
     LAST_SPECIAL60M_INDEX = -52
-    
+
     SPECIAL_MEMORIES = dict(FT857Radio.SPECIAL_MEMORIES)
     SPECIAL_MEMORIES.update(SPECIAL_60M)
 
@@ -1057,11 +1134,11 @@ class FT857USRadio(FT857Radio):
         mem.extd_number = number
 
         _mem = self._memobj.sixtymeterchannels[-self.LAST_SPECIAL60M_INDEX +
-                                                mem.number]
+                                               mem.number]
 
         mem = self._get_memory(mem, _mem)
 
-        mem.immutable = ["number", "skip", "rtone", "ctone",
+        mem.immutable = ["number", "rtone", "ctone",
                          "extd_number", "name", "dtcs", "tmode", "cross_mode",
                          "dtcs_polarity", "power", "duplex", "offset",
                          "comment", "empty"]
@@ -1085,7 +1162,7 @@ class FT857USRadio(FT857Radio):
             raise errors.RadioError("Mode {mode} is not valid "
                                     "in 60m channels".format(mode=mem.mode))
         _mem = self._memobj.sixtymeterchannels[-self.LAST_SPECIAL60M_INDEX +
-                                                mem.number]
+                                               mem.number]
         self._set_memory(mem, _mem)
 
     def get_memory(self, number):
@@ -1107,9 +1184,9 @@ class FT857USRadio(FT857Radio):
 
     def get_settings(self):
         top = FT857Radio.get_settings(self)
-        basic = top["basic"]
+        basic = top[0]
         rs = RadioSetting("emergency", "Emergency",
-                          RadioSettingValueBoolean(self._memobj.settings.emergency))
+                          RadioSettingValueBoolean(
+                              self._memobj.settings.emergency))
         basic.append(rs)
         return top
-
diff --git a/chirp/ft90.py b/chirp/drivers/ft90.py
similarity index 56%
rename from chirp/ft90.py
rename to chirp/drivers/ft90.py
index bd5c1d4..f777d94 100644
--- a/chirp/ft90.py
+++ b/chirp/drivers/ft90.py
@@ -14,41 +14,49 @@
 # 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, bitwise, memmap, directory, errors, util, yaesu_clone
+from chirp.drivers import yaesu_clone
+from chirp import chirp_common, bitwise, memmap, directory, errors, util
 from chirp.settings import RadioSetting, RadioSettingGroup, \
     RadioSettingValueInteger, RadioSettingValueList, \
-    RadioSettingValueBoolean, RadioSettingValueString
-import time, os, traceback, string, re
+    RadioSettingValueBoolean, RadioSettingValueString, \
+    RadioSettings
+
+import time
+import os
+import traceback
+import string
+import re
+import logging
+
 from textwrap import dedent
 
-if os.getenv("CHIRP_DEBUG"):
-    CHIRP_DEBUG = True
-else:
-    CHIRP_DEBUG=False
+LOG = logging.getLogger(__name__)
 
 CMD_ACK = chr(0x06)
 
 FT90_STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0]
 FT90_MODES = ["AM", "FM", "Auto"]
-FT90_TMODES = ["", "Tone", "TSQL", "", "DTCS"] # idx 3 (Bell) not supported yet
+# idx 3 (Bell) not supported yet
+FT90_TMODES = ["", "Tone", "TSQL", "", "DTCS"]
 FT90_TONES = list(chirp_common.TONES)
-for tone in [ 165.5, 171.3, 177.3 ]:
+for tone in [165.5, 171.3, 177.3]:
     FT90_TONES.remove(tone)
 FT90_POWER_LEVELS_VHF = [chirp_common.PowerLevel("Hi", watts=50),
-                    chirp_common.PowerLevel("Mid1", watts=20),
-                    chirp_common.PowerLevel("Mid2", watts=10),
-                    chirp_common.PowerLevel("Low", watts=5)]
+                         chirp_common.PowerLevel("Mid1", watts=20),
+                         chirp_common.PowerLevel("Mid2", watts=10),
+                         chirp_common.PowerLevel("Low", watts=5)]
 
 FT90_POWER_LEVELS_UHF = [chirp_common.PowerLevel("Hi", watts=35),
-                    chirp_common.PowerLevel("Mid1", watts=20),
-                    chirp_common.PowerLevel("Mid2", watts=10),
-                    chirp_common.PowerLevel("Low", watts=5)]
+                         chirp_common.PowerLevel("Mid1", watts=20),
+                         chirp_common.PowerLevel("Mid2", watts=10),
+                         chirp_common.PowerLevel("Low", watts=5)]
 
 FT90_DUPLEX = ["", "-", "+", "split"]
 FT90_CWID_CHARS = list(string.digits) + list(string.uppercase) + list(" ")
 FT90_DTMF_CHARS = list("0123456789ABCD*#")
-FT90_SPECIAL = [ "vfo_vhf", "home_vhf", "vfo_uhf", "home_uhf", \
-        "pms_1L", "pms_1U", "pms_2L", "pms_2U"]
+FT90_SPECIAL = ["vfo_vhf", "home_vhf", "vfo_uhf", "home_uhf",
+                "pms_1L", "pms_1U", "pms_2L", "pms_2U"]
+
 
 @directory.register
 class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
@@ -58,147 +66,146 @@ class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
 
     _memsize = 4063
     # block 03 (200 Bytes long) repeats 18 times; channel memories
-    _block_lengths = [ 2, 232, 24 ] + ([200] * 18 ) +  [205]
+    _block_lengths = [2, 232, 24] + ([200] * 18) + [205]
 
     mem_format = """
-    u16 id;
-	#seekto 0x22;	
-	struct {
-		u8	dtmf_active;
-		u8	dtmf1_len;
-		u8	dtmf2_len;
-		u8	dtmf3_len;
-		u8	dtmf4_len;
-		u8	dtmf5_len;
-		u8	dtmf6_len;
-		u8	dtmf7_len;
-		u8	dtmf8_len;
-        u8  dtmf1[8];
-        u8  dtmf2[8];
-        u8  dtmf3[8];
-        u8  dtmf4[8];
-        u8  dtmf5[8];
-        u8  dtmf6[8];
-        u8  dtmf7[8];
-        u8  dtmf8[8];
-		char cwid[7];
-		u8 	unk1;
-        u8  scan1:2,
-			beep:1,
-            unk3:3,
-            rfsqlvl:2;
-        u8  unk4:2,
-            scan2:1,
-            cwid_en:1,
-			txnarrow:1,
-			dtmfspeed:1,
-			pttlock:2;
-		u8	dtmftxdelay:3,
-			fancontrol:2,
-			unk5:3;
-		u8	dimmer:3,
-			unk6:1,
-			lcdcontrast:4;
-        u8  dcsmode:2,
-            unk16:2,
-            tot:4;
-        u8  unk14;
-		u8	unk8:1,
-			ars:1,
-			lock:1,
-			txpwrsave:1,
-			apo:4;
-        u8  unk15;
-        u8  unk9:4,
-            key_lt:4;
-        u8  unk10:4,
-            key_rt:4;
-        u8  unk11:4,
-            key_p1:4;
-        u8  unk12:4,
-            key_p2:4;
-        u8  unk13:4,
-            key_acc:4;
-		
-	} settings;
-	
-    struct mem_struct {
-      u8 mode:2,
-         isUhf1:1,
-         unknown1:2,
-         step:3;
-      u8 artsmode:2,
-         unknown2:1,
-         isUhf2:1
-         power:2,
-         shift:2;
-      u8 skip:1,
-         showname:1,
-         unknown3:1,
-         isUhfHi:1,
-         unknown4:1,
-         tmode:3;
-      u32 rxfreq;
-      u32 txfreqoffset;
-      u8 UseDefaultName:1,
-         ars:1,
-         tone:6;
-      u8 packetmode:1,
-         unknown5:1,
-         dcstone:6;
-      char name[7];
-    };
-
-	#seekto 0x86;
-    struct mem_struct vfo_vhf;
-    struct mem_struct home_vhf;
-    struct mem_struct vfo_uhf;
-    struct mem_struct home_uhf;
-
-    #seekto 0xEB;
-    u8 chan_enable[23];    
-    
-    #seekto 0x101;
-    struct {
-        u8  pms_2U_enable:1,
-            pms_2L_enable:1,
-            pms_1U_enable:1,
-            pms_1L_enable:1,
-            unknown6:4;
-    } special_enables;
-	
-	#seekto 0x102;
-    struct mem_struct memory[180];
-
-	#seekto 0xf12;
-	struct mem_struct pms_1L;
-	struct mem_struct pms_1U;
-	struct mem_struct pms_2L;	
-	struct mem_struct pms_2U;
-	
-	#seekto 0x0F7B;
-	struct  {
-	    char	demomsg1[50];
-	    char	demomsg2[50];
-	} demomsg;
-	
-    """
+u16 id;
+#seekto 0x22;
+struct {
+    u8 dtmf_active;
+    u8 dtmf1_len;
+    u8 dtmf2_len;
+    u8 dtmf3_len;
+    u8 dtmf4_len;
+    u8 dtmf5_len;
+    u8 dtmf6_len;
+    u8 dtmf7_len;
+    u8 dtmf8_len;
+    u8 dtmf1[8];
+    u8 dtmf2[8];
+    u8 dtmf3[8];
+    u8 dtmf4[8];
+    u8 dtmf5[8];
+    u8 dtmf6[8];
+    u8 dtmf7[8];
+    u8 dtmf8[8];
+    char cwid[7];
+    u8 unk1;
+    u8 scan1:2,
+       beep:1,
+       unk3:3,
+       rfsqlvl:2;
+    u8 unk4:2,
+       scan2:1,
+       cwid_en:1,
+       txnarrow:1,
+       dtmfspeed:1,
+       pttlock:2;
+    u8 dtmftxdelay:3,
+       fancontrol:2,
+       unk5:3;
+    u8 dimmer:3,
+       unk6:1,
+       lcdcontrast:4;
+    u8 dcsmode:2,
+       unk16:2,
+       tot:4;
+    u8 unk14;
+    u8 unk8:1,
+       ars:1,
+       lock:1,
+       txpwrsave:1,
+       apo:4;
+    u8 unk15;
+    u8 unk9:4,
+       key_lt:4;
+    u8 unk10:4,
+       key_rt:4;
+    u8 unk11:4,
+       key_p1:4;
+    u8 unk12:4,
+       key_p2:4;
+    u8 unk13:4,
+       key_acc:4;
+} settings;
+
+struct mem_struct {
+    u8 mode:2,
+       isUhf1:1,
+       unknown1:2,
+       step:3;
+    u8 artsmode:2,
+       unknown2:1,
+       isUhf2:1
+       power:2,
+       shift:2;
+    u8 skip:1,
+       showname:1,
+       unknown3:1,
+       isUhfHi:1,
+       unknown4:1,
+       tmode:3;
+    u32 rxfreq;
+    u32 txfreqoffset;
+    u8 UseDefaultName:1,
+       ars:1,
+       tone:6;
+    u8 packetmode:1,
+       unknown5:1,
+       dcstone:6;
+    char name[7];
+};
+
+#seekto 0x86;
+struct mem_struct vfo_vhf;
+struct mem_struct home_vhf;
+struct mem_struct vfo_uhf;
+struct mem_struct home_uhf;
+
+#seekto 0xEB;
+u8 chan_enable[23];
+
+#seekto 0x101;
+struct {
+    u8 pms_2U_enable:1,
+       pms_2L_enable:1,
+       pms_1U_enable:1,
+       pms_1L_enable:1,
+       unknown6:4;
+} special_enables;
+
+#seekto 0x102;
+struct mem_struct memory[180];
+
+#seekto 0xf12;
+struct mem_struct pms_1L;
+struct mem_struct pms_1U;
+struct mem_struct pms_2L;
+struct mem_struct pms_2U;
+
+#seekto 0x0F7B;
+struct  {
+    char demomsg1[50];
+    char demomsg2[50];
+} demomsg;
+"""
+
     @classmethod
     def get_prompts(cls):
         rp = chirp_common.RadioPrompts()
         rp.pre_download = _(dedent("""\
-            1. Turn radio off.
-            2. Connect mic and hold [ACC] on mic while powering on.
-                ("CLONE" will appear on the display)
-            3. Replace mic with PC programming cable.
-            4. <b>After clicking OK</b>, press the [SET] key to send image."""))
+1. Turn radio off.
+2. Connect mic and hold [ACC] on mic while powering on.
+    ("CLONE" will appear on the display)
+3. Replace mic with PC programming cable.
+4. <b>After clicking OK</b>, press the [SET] key to send image."""))
         rp.pre_upload = _(dedent("""\
-            1. Turn radio off.
-            2. Connect mic and hold [ACC] on mic while powering on.
-                ("CLONE" will appear on the display)
-            3. Replace mic with PC programming cable.
-            4. Press the [DISP/SS] key
-                ("R" will appear on the lower left of LCD)."""))
+1. Turn radio off.
+2. Connect mic and hold [ACC] on mic while powering on.
+    ("CLONE" will appear on the display)
+3. Replace mic with PC programming cable.
+4. Press the [DISP/SS] key
+    ("R" will appear on the lower left of LCD)."""))
         rp.display_pre_upload_prompt_before_opening_port = False
         return rp
 
@@ -223,38 +230,42 @@ class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
         rf.valid_skips = ["", "S"]
         rf.valid_special_chans = FT90_SPECIAL
         rf.memory_bounds = (1, 180)
-        rf.valid_bands = [(100000000, 230000000), 
-            (300000000, 530000000), (810000000, 999975000)]
+        rf.valid_bands = [(100000000, 230000000),
+                          (300000000, 530000000),
+                          (810000000, 999975000)]
 
         return rf
 
     def _read(self, blocksize, blocknum):
         data = self.pipe.read(blocksize+2)
-        
+
         # chew echo'd ack
         self.pipe.write(CMD_ACK)
         time.sleep(0.02)
-        self.pipe.read(1) # chew echoed ACK from 1-wire serial
-        
-        if len(data) == blocksize+2 and data[0] == chr(blocknum):
+        self.pipe.read(1)  # chew echoed ACK from 1-wire serial
+
+        if len(data) == blocksize + 2 and data[0] == chr(blocknum):
             checksum = yaesu_clone.YaesuChecksum(1, blocksize)
             if checksum.get_existing(data) != checksum.get_calculated(data):
-                raise Exception("Checksum Failed [%02X<>%02X] block %02X, data len: %i" %
-                                    (checksum.get_existing(data),
-                                    checksum.get_calculated(data), blocknum, len(data) ))
-            data = data[1:blocksize+1] # Chew blocknum and checksum
-            
+                raise Exception("Checksum Failed [%02X<>%02X] block %02X, "
+                                "data len: %i" %
+                                (checksum.get_existing(data),
+                                 checksum.get_calculated(data),
+                                 blocknum, len(data)))
+            data = data[1:blocksize + 1]  # Chew blocknum and checksum
+
         else:
-            raise Exception("Unable to read blocknum %02X expected blocksize %i got %i." %
-                                (blocknum, blocksize+2, len(data)))
+            raise Exception("Unable to read blocknum %02X "
+                            "expected blocksize %i got %i." %
+                            (blocknum, blocksize+2, len(data)))
+
+        return data
 
-        return data        
-    
     def _clone_in(self):
         # Be very patient with the radio
         self.pipe.setTimeout(4)
         start = time.time()
-    
+
         data = ""
         blocknum = 0
         status = chirp_common.Status()
@@ -266,16 +277,17 @@ class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
             blocknum += 1
             status.cur = blocknum
             self.status_fn(status)
-                
-        print "Clone completed in %i seconds, blocks read: %i" % (time.time() - start, blocknum)
-    
+
+        LOG.info("Clone completed in %i seconds, blocks read: %i" %
+                 (time.time() - start, blocknum))
+
         return memmap.MemoryMap(data)
-    
+
     def _clone_out(self):
         looppredelay = 0.4
         looppostdelay = 1.9
         start = time.time()
-    
+
         blocknum = 0
         pos = 0
         status = chirp_common.Status()
@@ -288,36 +300,33 @@ class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
             blocknumbyte = chr(blocknum)
             payloadbytes = self.get_mmap()[pos:pos+blocksize]
             checksumbyte = chr(checksum.get_calculated(self.get_mmap()))
-            if CHIRP_DEBUG:
-                print "Block %i - will send from %i to %i byte " % \
-                    (blocknum, pos, pos + blocksize)
-                print util.hexprint(blocknumbyte)
-                print util.hexprint(payloadbytes)
-                print util.hexprint(checksumbyte)
+            LOG.debug("Block %i - will send from %i to %i byte " %
+                      (blocknum, pos, pos + blocksize))
+            LOG.debug(util.hexprint(blocknumbyte))
+            LOG.debug(util.hexprint(payloadbytes))
+            LOG.debug(util.hexprint(checksumbyte))
             # send wrapped bytes
             time.sleep(looppredelay)
             self.pipe.write(blocknumbyte)
             self.pipe.write(payloadbytes)
             self.pipe.write(checksumbyte)
-            tmp = self.pipe.read(blocksize+2)  #chew echo
-            if CHIRP_DEBUG:
-                print "bytes echoed: "
-                print util.hexprint(tmp)
+            tmp = self.pipe.read(blocksize + 2)  # chew echo
+            LOG.debug("bytes echoed: ")
+            LOG.debug(util.hexprint(tmp))
             # radio is slow to write/ack:
-            time.sleep(looppostdelay) 
+            time.sleep(looppostdelay)
             buf = self.pipe.read(1)
-            if CHIRP_DEBUG:
-                print "ack recd:"
-                print util.hexprint(buf)
+            LOG.debug("ack recd:")
+            LOG.debug(util.hexprint(buf))
             if buf != CMD_ACK:
                 raise Exception("Radio did not ack block %i" % blocknum)
             pos += blocksize
             blocknum += 1
             status.cur = blocknum
             self.status_fn(status)
-    
-        print "Clone completed in %i seconds" % (time.time() - start)
-    
+
+        LOG.info("Clone completed in %i seconds" % (time.time() - start))
+
     def sync_in(self):
         try:
             self._mmap = self._clone_in()
@@ -325,7 +334,8 @@ class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
             raise
         except Exception, e:
             trace = traceback.format_exc()
-            raise errors.RadioError("Failed to communicate with radio: %s" % trace)
+            raise errors.RadioError(
+                    "Failed to communicate with radio: %s" % trace)
         self.process_mmap()
 
     def sync_out(self):
@@ -335,17 +345,18 @@ class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
             raise
         except Exception, e:
             trace = traceback.format_exc()
-            raise errors.RadioError("Failed to communicate with radio: %s" % trace)
+            raise errors.RadioError(
+                    "Failed to communicate with radio: %s" % trace)
 
     def process_mmap(self):
         self._memobj = bitwise.parse(self.mem_format, self._mmap)
-    
+
     def _get_chan_enable(self, number):
         number = number - 1
         bytepos = number // 8
-        bitpos = number  % 8
+        bitpos = number % 8
         chan_enable = self._memobj.chan_enable[bytepos]
-        if chan_enable & ( 1 << bitpos ):
+        if chan_enable & (1 << bitpos):
             return True
         else:
             return False
@@ -353,14 +364,14 @@ class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
     def _set_chan_enable(self, number, enable):
         number = number - 1
         bytepos = number // 8
-        bitpos = number  % 8
+        bitpos = number % 8
         chan_enable = self._memobj.chan_enable[bytepos]
         if enable:
-            chan_enable = chan_enable | ( 1 << bitpos )  # enable
+            chan_enable = chan_enable | (1 << bitpos)  # enable
         else:
-            chan_enable = chan_enable & ~ ( 1 << bitpos )  # disable            
+            chan_enable = chan_enable & ~(1 << bitpos)  # disable
         self._memobj.chan_enable[bytepos] = chan_enable
-        
+
     def get_memory(self, number):
         mem = chirp_common.Memory()
         if isinstance(number, str):
@@ -371,7 +382,8 @@ class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
             if re.match('^pms', mem.extd_number):
                 # enable pms_XY channel flag
                 _special_enables = self._memobj.special_enables
-                mem.empty = not getattr(_special_enables, mem.extd_number + "_enable")
+                mem.empty = not getattr(_special_enables,
+                                        mem.extd_number + "_enable")
         else:
             # regular memory
             _mem = self._memobj.memory[number-1]
@@ -379,7 +391,7 @@ class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
             mem.empty = not self._get_chan_enable(number)
         if mem.empty:
             return mem  # bail out, do not parse junk
-        mem.freq = _mem.rxfreq * 10      
+        mem.freq = _mem.rxfreq * 10
         mem.offset = _mem.txfreqoffset * 10
         if not _mem.tmode < len(FT90_TMODES):
             _mem.tmode = 0
@@ -398,7 +410,8 @@ class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
             _mem.step = 2
         mem.tuning_step = FT90_STEPS[_mem.step]
         mem.skip = _mem.skip and "S" or ""
-        if not all(char in chirp_common.CHARSET_ASCII for char in str(_mem.name)):
+        if not all(char in chirp_common.CHARSET_ASCII
+                   for char in str(_mem.name)):
             # dont display blank/junk name
             mem.name = ""
         else:
@@ -407,7 +420,7 @@ class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
 
     def get_raw_memory(self, number):
         return repr(self._memobj.memory[number-1])
-    
+
     def set_memory(self, mem):
         if mem.number < 0:      # special channels
             _mem = getattr(self._memobj, mem.extd_number)
@@ -417,7 +430,7 @@ class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
                 setattr(_special_enables, mem.extd_number + "_enable", True)
         else:
             _mem = self._memobj.memory[mem.number - 1]
-            self._set_chan_enable( mem.number, not mem.empty )
+            self._set_chan_enable(mem.number, not mem.empty)
         _mem.skip = mem.skip == "S"
         # radio has a known bug with 5khz step and dead squelch
         if not mem.tuning_step or mem.tuning_step == FT90_STEPS[0]:
@@ -426,7 +439,7 @@ class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
             _mem.step = FT90_STEPS.index(mem.tuning_step)
         _mem.rxfreq = mem.freq / 10
         # vfo will unlock if not in right band?
-        if mem.freq > 300000000: 
+        if mem.freq > 300000000:
             # uhf
             _mem.isUhf1 = 1
             _mem.isUhf2 = 1
@@ -444,7 +457,7 @@ class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
         _mem.tone = FT90_TONES.index(mem.rtone)
         _mem.tmode = FT90_TMODES.index(mem.tmode)
         _mem.mode = FT90_MODES.index(mem.mode)
-        _mem.shift = FT90_DUPLEX.index(mem.duplex)    
+        _mem.shift = FT90_DUPLEX.index(mem.duplex)
         _mem.dcstone = chirp_common.DTCS_CODES.index(mem.dtcs)
         _mem.step = FT90_STEPS.index(mem.tuning_step)
         _mem.shift = FT90_DUPLEX.index(mem.duplex)
@@ -462,12 +475,10 @@ class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
 
     def _decode_cwid(self, cwidarr):
         cwid = ""
-        if CHIRP_DEBUG:
-            print "@ +_decode_cwid:"
+        LOG.debug("@ +_decode_cwid:")
         for byte in cwidarr.get_value():
             char = int(byte)
-            if CHIRP_DEBUG:
-                print char
+            LOG.debug(char)
             # bitwise wraps in quotes! get rid of those
             if char < len(FT90_CWID_CHARS):
                 cwid += FT90_CWID_CHARS[char]
@@ -475,143 +486,158 @@ class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
 
     def _encode_cwid(self, cwidarr):
         cwid = ""
-        if CHIRP_DEBUG:
-            print "@ _encode_cwid:"
+        LOG.debug("@ _encode_cwid:")
         for char in cwidarr.get_value():
             cwid += chr(FT90_CWID_CHARS.index(char))
-        if CHIRP_DEBUG:
-            print cwid
+        LOG.debug(cwid)
         return cwid
-    
-    def _bbcd2dtmf(self, bcdarr, strlen = 16):
+
+    def _bbcd2dtmf(self, bcdarr, strlen=16):
         # doing bbcd, but with support for ABCD*#
-        if CHIRP_DEBUG:
-            print bcdarr.get_value()
+        LOG.debug(bcdarr.get_value())
         string = ''.join("%02X" % b for b in bcdarr)
-        if CHIRP_DEBUG:
-            print "@_bbcd2dtmf, received: %s" % string
-        string = string.replace('E','*').replace('F','#')
+        LOG.debug("@_bbcd2dtmf, received: %s" % string)
+        string = string.replace('E', '*').replace('F', '#')
         if strlen <= 16:
             string = string[:strlen]
         return string
-    
+
     def _dtmf2bbcd(self, dtmf):
         dtmfstr = dtmf.get_value()
         dtmfstr = dtmfstr.replace('*', 'E').replace('#', 'F')
-        dtmfstr = str.ljust(dtmfstr.strip(), 16, "0" )
+        dtmfstr = str.ljust(dtmfstr.strip(), 16, "0")
         bcdarr = list(bytearray.fromhex(dtmfstr))
-        if CHIRP_DEBUG:
-            print "@_dtmf2bbcd, sending: %s" % bcdarr
+        LOG.debug("@_dtmf2bbcd, sending: %s" % bcdarr)
         return bcdarr
-    
+
     def get_settings(self):
         _settings = self._memobj.settings
         basic = RadioSettingGroup("basic", "Basic")
         autodial = RadioSettingGroup("autodial", "AutoDial")
         keymaps = RadioSettingGroup("keymaps", "KeyMaps")
-        top = RadioSettingGroup("top", "All Settings", basic, keymaps, autodial)
-        
-        rs = RadioSetting("beep", "Beep",
-                          RadioSettingValueBoolean(_settings.beep))
+
+        top = RadioSettings(basic, keymaps, autodial)
+
+        rs = RadioSetting(
+                "beep", "Beep",
+                RadioSettingValueBoolean(_settings.beep))
         basic.append(rs)
-        rs = RadioSetting("lock", "Lock",
-                          RadioSettingValueBoolean(_settings.lock))
+        rs = RadioSetting(
+                "lock", "Lock",
+                RadioSettingValueBoolean(_settings.lock))
         basic.append(rs)
-        rs = RadioSetting("ars", "Auto Repeater Shift",
-                        RadioSettingValueBoolean(_settings.ars))
+        rs = RadioSetting(
+                "ars", "Auto Repeater Shift",
+                RadioSettingValueBoolean(_settings.ars))
         basic.append(rs)
-        rs = RadioSetting("txpwrsave", "TX Power Save",
-                          RadioSettingValueBoolean(_settings.txpwrsave))
+        rs = RadioSetting(
+                "txpwrsave", "TX Power Save",
+                RadioSettingValueBoolean(_settings.txpwrsave))
         basic.append(rs)
-        rs = RadioSetting("txnarrow", "TX Narrow",
-                          RadioSettingValueBoolean(_settings.txnarrow))
+        rs = RadioSetting(
+                "txnarrow", "TX Narrow",
+                RadioSettingValueBoolean(_settings.txnarrow))
         basic.append(rs)
         options = ["Off", "S-3", "S-5", "S-Full"]
-        rs = RadioSetting("rfsqlvl", "RF Squelch Level",
-                          RadioSettingValueList(options,
-                                        options[_settings.rfsqlvl]))
+        rs = RadioSetting(
+                "rfsqlvl", "RF Squelch Level",
+                RadioSettingValueList(options, options[_settings.rfsqlvl]))
         basic.append(rs)
         options = ["Off", "Band A", "Band B", "Both"]
-        rs = RadioSetting("pttlock", "PTT Lock",
-                          RadioSettingValueList(options,
-                                        options[_settings.pttlock]))
+        rs = RadioSetting(
+                "pttlock", "PTT Lock",
+                RadioSettingValueList(options, options[_settings.pttlock]))
         basic.append(rs)
-        
-        rs = RadioSetting("cwid_en", "CWID Enable",
-                          RadioSettingValueBoolean(_settings.cwid_en))
+
+        rs = RadioSetting(
+                "cwid_en", "CWID Enable",
+                RadioSettingValueBoolean(_settings.cwid_en))
         basic.append(rs)
-        
+
         cwid = RadioSettingValueString(0, 7, self._decode_cwid(_settings.cwid))
         cwid.set_charset(FT90_CWID_CHARS)
         rs = RadioSetting("cwid", "CWID", cwid)
         basic.append(rs)
-        
+
         options = ["OFF"] + map(str, range(1, 12+1))
-        rs = RadioSetting("apo", "APO time (hrs)",
-                          RadioSettingValueList(options,
-                                        options[_settings.apo]))
+        rs = RadioSetting(
+                "apo", "APO time (hrs)",
+                RadioSettingValueList(options, options[_settings.apo]))
         basic.append(rs)
-        
+
         options = ["Off"] + map(str, range(1, 60+1))
-        rs = RadioSetting("tot", "Time Out Timer (mins)",
-                        RadioSettingValueList(options,options[_settings.tot]))
+        rs = RadioSetting(
+                "tot", "Time Out Timer (mins)",
+                RadioSettingValueList(options, options[_settings.tot]))
         basic.append(rs)
-        
+
         options = ["off", "Auto/TX", "Auto", "TX"]
-        rs = RadioSetting("fancontrol", "Fan Control",
-                        RadioSettingValueList(options,options[_settings.fancontrol]))
+        rs = RadioSetting(
+                "fancontrol", "Fan Control",
+                RadioSettingValueList(options, options[_settings.fancontrol]))
         basic.append(rs)
-        
+
         keyopts = ["Scan Up", "Scan Down", "Repeater", "Reverse", "Tone Burst",
-                        "Tx Power", "Home Ch", "VFO/MR", "Tone", "Priority"]
-        rs = RadioSetting("key_lt", "Left Key",
-                    RadioSettingValueList(keyopts,keyopts[_settings.key_lt]))
+                   "Tx Power", "Home Ch", "VFO/MR", "Tone", "Priority"]
+        rs = RadioSetting(
+                "key_lt", "Left Key",
+                RadioSettingValueList(keyopts, keyopts[_settings.key_lt]))
         keymaps.append(rs)
-        rs = RadioSetting("key_rt", "Right Key",
-                    RadioSettingValueList(keyopts,keyopts[_settings.key_rt]))
+        rs = RadioSetting(
+                "key_rt", "Right Key",
+                RadioSettingValueList(keyopts, keyopts[_settings.key_rt]))
         keymaps.append(rs)
-        rs = RadioSetting("key_p1", "P1 Key",
-                    RadioSettingValueList(keyopts,keyopts[_settings.key_p1]))
+        rs = RadioSetting(
+                "key_p1", "P1 Key",
+                RadioSettingValueList(keyopts, keyopts[_settings.key_p1]))
         keymaps.append(rs)
-        rs = RadioSetting("key_p2", "P2 Key",
-                    RadioSettingValueList(keyopts,keyopts[_settings.key_p2]))
+        rs = RadioSetting(
+                "key_p2", "P2 Key",
+                RadioSettingValueList(keyopts, keyopts[_settings.key_p2]))
         keymaps.append(rs)
-        rs = RadioSetting("key_acc", "ACC Key",
-                    RadioSettingValueList(keyopts,keyopts[_settings.key_acc]))
+        rs = RadioSetting(
+                "key_acc", "ACC Key",
+                RadioSettingValueList(keyopts, keyopts[_settings.key_acc]))
         keymaps.append(rs)
-        
-        options = map(str, range(0,12+1))
-        rs = RadioSetting("lcdcontrast", "LCD Contrast",
-                        RadioSettingValueList(options,options[_settings.lcdcontrast]))
+
+        options = map(str, range(0, 12+1))
+        rs = RadioSetting(
+                "lcdcontrast", "LCD Contrast",
+                RadioSettingValueList(options, options[_settings.lcdcontrast]))
         basic.append(rs)
-        
+
         options = ["off", "d4", "d3", "d2", "d1"]
-        rs = RadioSetting("dimmer", "Dimmer",
-                        RadioSettingValueList(options,options[_settings.dimmer]))
+        rs = RadioSetting(
+                "dimmer", "Dimmer",
+                RadioSettingValueList(options, options[_settings.dimmer]))
         basic.append(rs)
-        
+
         options = ["TRX Normal", "RX Reverse", "TX Reverse", "TRX Reverse"]
-        rs = RadioSetting("dcsmode", "DCS Mode",
-                        RadioSettingValueList(options,options[_settings.dcsmode]))
+        rs = RadioSetting(
+                "dcsmode", "DCS Mode",
+                RadioSettingValueList(options, options[_settings.dcsmode]))
         basic.append(rs)
-        
+
         options = ["50 ms", "100 ms"]
-        rs = RadioSetting("dtmfspeed", "DTMF Speed",
-                        RadioSettingValueList(options,options[_settings.dtmfspeed]))
+        rs = RadioSetting(
+                "dtmfspeed", "DTMF Speed",
+                RadioSettingValueList(options, options[_settings.dtmfspeed]))
         autodial.append(rs)
-        
+
         options = ["50 ms", "250 ms", "450 ms", "750 ms", "1 sec"]
-        rs = RadioSetting("dtmftxdelay", "DTMF TX Delay",
-                        RadioSettingValueList(options,options[_settings.dtmftxdelay]))
+        rs = RadioSetting(
+                "dtmftxdelay", "DTMF TX Delay",
+                RadioSettingValueList(options, options[_settings.dtmftxdelay]))
         autodial.append(rs)
-        
-        options = map(str,range(1,8+1))
-        rs = RadioSetting("dtmf_active", "DTMF Active",
-                RadioSettingValueList(options,options[_settings.dtmf_active]))
+
+        options = map(str, range(1, 8 + 1))
+        rs = RadioSetting(
+                "dtmf_active", "DTMF Active",
+                RadioSettingValueList(options, options[_settings.dtmf_active]))
         autodial.append(rs)
-        
+
         # setup 8 dtmf autodial entries
-        for i in map(str, range(1,9)):
+        for i in map(str, range(1, 9)):
             objname = "dtmf" + i
             dtmfsetting = getattr(_settings, objname)
             dtmflen = getattr(_settings, objname + "_len")
@@ -620,9 +646,9 @@ class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
             dtmf.set_charset(FT90_DTMF_CHARS + list(" "))
             rs = RadioSetting(objname, objname.upper(), dtmf)
             autodial.append(rs)
-        
+
         return top
-    
+
     def set_settings(self, uisettings):
         _settings = self._memobj.settings
         for element in uisettings:
@@ -642,11 +668,8 @@ class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
                     dtmfstrlen = len(str(newval).strip())
                     setattr(_settings, setting + "_len", dtmfstrlen)
                     newval = self._dtmf2bbcd(newval)
-                if CHIRP_DEBUG:
-                    print "Setting %s(%s) <= %s" % (setting,
-                                    oldval, newval)
+                LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval))
                 setattr(_settings, setting, newval)
             except Exception, e:
-                print element.get_name()
+                LOG.debug(element.get_name())
                 raise
-
diff --git a/chirp/ftm350.py b/chirp/drivers/ftm350.py
similarity index 90%
rename from chirp/ftm350.py
rename to chirp/drivers/ftm350.py
index 784bdff..fde98f7 100644
--- a/chirp/ftm350.py
+++ b/chirp/drivers/ftm350.py
@@ -16,12 +16,15 @@
 import time
 import struct
 import os
+import logging
 
-from chirp import chirp_common, yaesu_clone, directory, errors, util
-from chirp import bitwise, memmap
-from chirp.settings import RadioSettingGroup, RadioSetting
+from chirp.drivers import yaesu_clone
+from chirp import chirp_common, directory, errors, util, bitwise, memmap
+from chirp.settings import RadioSettingGroup, RadioSetting, RadioSettings
 from chirp.settings import RadioSettingValueInteger, RadioSettingValueString
 
+LOG = logging.getLogger(__name__)
+
 mem_format = """
 struct mem {
   u8 used:1,
@@ -82,9 +85,10 @@ _TMODES = ["", "Tone", "TSQL", "-RVT", "DTCS", "-PR", "-PAG"]
 TMODES = ["", "Tone", "TSQL", "", "DTCS", "", ""]
 MODES = ["FM", "AM", "NFM", "", "WFM"]
 DUPLEXES = ["", "", "-", "+", "split"]
-#TODO: add japaneese characters (viewable in special menu, scroll backwards)
-CHARSET = ('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!"' +
-           '#$%&`()*+,-./:;<=>?@[\\]^_`{|}~?????? ' + '?' * 91)
+# TODO: add japaneese characters (viewable in special menu, scroll backwards)
+CHARSET = \
+    ('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!"' +
+     '#$%&`()*+,-./:;<=>?@[\\]^_`{|}~?????? ' + '?' * 91)
 
 POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=50),
                 chirp_common.PowerLevel("Mid", watts=20),
@@ -92,6 +96,7 @@ POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=50),
 
 SKIPS = ["", "S", "P"]
 
+
 def aprs_call_to_str(_call):
     call = ""
     for i in str(_call):
@@ -100,12 +105,14 @@ def aprs_call_to_str(_call):
         call += i
     return call
 
+
 def _safe_read(radio, length):
     data = ""
     while len(data) < length:
         data += radio.pipe.read(length - len(data))
     return data
 
+
 def _clone_in(radio):
     data = ""
 
@@ -134,15 +141,16 @@ def _clone_in(radio):
             for i in frame[:-1]:
                 cs = (cs + ord(i)) % 256
             if cs != checksum:
-                print "Calc: %02x Real: %02x Len: %i" % (cs, checksum,
-                                                         len(block))
+                LOG.debug("Calc: %02x Real: %02x Len: %i" %
+                          (cs, checksum, len(block)))
                 raise errors.RadioError("Block failed checksum")
 
             radio.pipe.write("\x06")
             time.sleep(0.05)
 
-            if os.getenv("CHIRP_DEBUG") and (last_addr + 128) != addr:
-                print "Gap, expecting %04x, got %04x" % (last_addr+128, addr)
+            if (last_addr + 128) != addr:
+                LOG.debug("Gap, expecting %04x, got %04x" %
+                          (last_addr+128, addr))
             last_addr = addr
             data[addr] = block
             length += len(block)
@@ -155,6 +163,7 @@ def _clone_in(radio):
 
     return data
 
+
 def _clone_out(radio):
     radio.pipe.setTimeout(1)
 
@@ -187,6 +196,7 @@ def _clone_out(radio):
             status.msg = "Cloning to radio"
             radio.status_fn(status)
 
+
 def get_freq(rawfreq):
     """Decode a frequency that may include a fractional step flag"""
     # Ugh.  The 0x80 and 0x40 indicate values to add to get the
@@ -202,6 +212,7 @@ def get_freq(rawfreq):
 
     return rawfreq
 
+
 def set_freq(freq, obj, field):
     """Encode a frequency with any necessary fractional step flags"""
     obj[field] = freq / 10000
@@ -221,6 +232,7 @@ def set_freq(freq, obj, field):
 
     return freq
 
+
 @directory.register
 class FTM350Radio(yaesu_clone.YaesuCloneModeRadio):
     """Yaesu FTM-350"""
@@ -240,7 +252,7 @@ class FTM350Radio(yaesu_clone.YaesuCloneModeRadio):
         rf.has_tuning_step = False
         rf.has_dtcs_polarity = False
         rf.has_sub_devices = self.VARIANT == ""
-        rf.valid_skips = [] # FIXME: Finish this
+        rf.valid_skips = []  # FIXME: Finish this
         rf.valid_tmodes = [""] + [x for x in TMODES if x]
         rf.valid_modes = [x for x in MODES if x]
         rf.valid_duplexes = DUPLEXES
@@ -249,8 +261,8 @@ class FTM350Radio(yaesu_clone.YaesuCloneModeRadio):
         rf.valid_characters = CHARSET
         rf.memory_bounds = (0, 500)
         rf.valid_power_levels = POWER_LEVELS
-        rf.valid_bands = [(  500000,    1800000),
-                          (76000000,  250000000),
+        rf.valid_bands = [(500000,   1800000),
+                          (76000000, 250000000),
                           (30000000, 1000000000)]
         rf.can_odd_split = True
         return rf
@@ -279,13 +291,20 @@ class FTM350Radio(yaesu_clone.YaesuCloneModeRadio):
         self._memobj = bitwise.parse(mem_format, self._mmap)
 
     def get_raw_memory(self, number):
+
+        def identity(o):
+            return o
+
+        def indexed(o):
+            return o[number - 1]
+
         if number == 0:
             suffix = "_zero"
-            fn = lambda o: o
+            fn = identity
         else:
             suffix = ""
-            fn = lambda o: o[number - 1]
-        return (repr(fn(self._memory_obj(suffix))) + 
+            fn = indexed
+        return (repr(fn(self._memory_obj(suffix))) +
                 repr(fn(self._label_obj(suffix))))
 
     def _memory_obj(self, suffix=""):
@@ -315,7 +334,7 @@ class FTM350Radio(yaesu_clone.YaesuCloneModeRadio):
         if _mem.oddsplit:
             mem.duplex = "split"
             mem.offset = get_freq(int(_mem.split) * 10000)
-        else: 
+        else:
             mem.duplex = DUPLEXES[_mem.duplex]
             mem.offset = int(_mem.offset) * 50000
 
@@ -380,7 +399,7 @@ class FTM350Radio(yaesu_clone.YaesuCloneModeRadio):
         return filedata.startswith("AH033$")
 
     def get_settings(self):
-        top = RadioSettingGroup("all", "All Settings")
+        top = RadioSettings()
 
         aprs = RadioSettingGroup("aprs", "APRS")
         top.append(aprs)
@@ -415,6 +434,7 @@ class FTM350RadioLeft(FTM350Radio):
     VARIANT = "Left"
     _vfo = "left"
 
+
 class FTM350RadioRight(FTM350Radio):
     VARIANT = "Right"
     _vfo = "right"
diff --git a/chirp/generic_csv.py b/chirp/drivers/generic_csv.py
similarity index 78%
rename from chirp/generic_csv.py
rename to chirp/drivers/generic_csv.py
index 5777e73..e7c3c93 100644
--- a/chirp/generic_csv.py
+++ b/chirp/drivers/generic_csv.py
@@ -15,13 +15,18 @@
 
 import os
 import csv
+import logging
 
 from chirp import chirp_common, errors, directory
 
+LOG = logging.getLogger(__name__)
+
+
 class OmittedHeaderError(Exception):
     """Internal exception to signal that a column has been omitted"""
     pass
 
+
 def get_datum_by_header(headers, data, header):
     """Return the column corresponding to @headers[@header] from @data"""
     if header not in headers:
@@ -30,8 +35,9 @@ def get_datum_by_header(headers, data, header):
     try:
         return data[headers.index(header)]
     except IndexError:
-        raise OmittedHeaderError("Header %s not provided on this line" % \
-                                     header)
+        raise OmittedHeaderError("Header %s not provided on this line" %
+                                 header)
+
 
 def write_memory(writer, mem):
     """Write @mem using @writer if not empty"""
@@ -39,6 +45,7 @@ def write_memory(writer, mem):
         return
     writer.writerow(mem.to_csv())
 
+
 @directory.register
 class CSVRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
     """A driver for Generic CSV files"""
@@ -47,23 +54,23 @@ class CSVRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
     FILE_EXTENSION = "csv"
 
     ATTR_MAP = {
-        "Location"     : (int,   "number"),
-        "Name"         : (str,   "name"),
-        "Frequency"    : (chirp_common.parse_freq, "freq"),
-        "Duplex"       : (str,   "duplex"),
-        "Offset"       : (chirp_common.parse_freq, "offset"),
-        "Tone"         : (str,   "tmode"),
-        "rToneFreq"    : (float, "rtone"),
-        "cToneFreq"    : (float, "ctone"),
-        "DtcsCode"     : (int,   "dtcs"),
-        "DtcsPolarity" : (str,   "dtcs_polarity"),
-        "Mode"         : (str,   "mode"),
-        "TStep"        : (float, "tuning_step"),
-        "Skip"         : (str,   "skip"),
-        "URCALL"       : (str,   "dv_urcall"),
-        "RPT1CALL"     : (str,   "dv_rpt1call"),
-        "RPT2CALL"     : (str,   "dv_rpt2call"),
-        "Comment"      : (str,   "comment"),
+        "Location":      (int,   "number"),
+        "Name":          (str,   "name"),
+        "Frequency":     (chirp_common.parse_freq, "freq"),
+        "Duplex":        (str,   "duplex"),
+        "Offset":        (chirp_common.parse_freq, "offset"),
+        "Tone":          (str,   "tmode"),
+        "rToneFreq":     (float, "rtone"),
+        "cToneFreq":     (float, "ctone"),
+        "DtcsCode":      (int,   "dtcs"),
+        "DtcsPolarity":  (str,   "dtcs_polarity"),
+        "Mode":          (str,   "mode"),
+        "TStep":         (float, "tuning_step"),
+        "Skip":          (str,   "skip"),
+        "URCALL":        (str,   "dv_urcall"),
+        "RPT1CALL":      (str,   "dv_rpt1call"),
+        "RPT2CALL":      (str,   "dv_rpt2call"),
+        "Comment":       (str,   "comment"),
         }
 
     def _blank(self):
@@ -78,6 +85,8 @@ class CSVRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
     def __init__(self, pipe):
         chirp_common.FileBackedRadio.__init__(self, None)
         self.memories = []
+        self.file_has_rTone = None  # Set in load(), used in _clean_tmode()
+        self.file_has_cTone = None
 
         self._filename = pipe
         if self._filename and os.path.exists(self._filename):
@@ -116,7 +125,18 @@ class CSVRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
             fname = "_clean_%s" % attr
             if hasattr(self, fname):
                 mem = getattr(self, fname)(headers, line, mem)
-        
+
+        return mem
+
+    def _clean_tmode(self, headers, line, mem):
+        """ If there is exactly one of [rToneFreq, cToneFreq] columns in the
+        csv file, use it for both rtone & ctone. Makes TSQL use friendlier."""
+
+        if self.file_has_rTone and not self.file_has_cTone:
+            mem.ctone = mem.rtone
+        elif self.file_has_cTone and not self.file_has_rTone:
+            mem.rtone = mem.ctone
+
         return mem
 
     def _parse_csv_data_line(self, headers, line):
@@ -168,13 +188,15 @@ class CSVRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
             lineno += 1
             if lineno == 1:
                 header = line
+                self.file_has_rTone = "rToneFreq" in header
+                self.file_has_cTone = "cToneFreq" in header
                 continue
 
             if len(header) > len(line):
-                print "Line %i has %i columns, expected %i" % (lineno,
-                                                               len(line),
-                                                               len(header))
-                self.errors.append("Column number mismatch on line %i" % lineno)
+                LOG.error("Line %i has %i columns, expected %i",
+                          lineno, len(line), len(header))
+                self.errors.append("Column number mismatch on line %i" %
+                                   lineno)
                 continue
 
             try:
@@ -182,7 +204,7 @@ class CSVRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
                 if mem.number is None:
                     raise Exception("Invalid Location field" % lineno)
             except Exception, e:
-                print "Line %i: %s" % (lineno, e)
+                LOG.error("Line %i: %s", lineno, e)
                 self.errors.append("Line %i: %s" % (lineno, e))
                 continue
 
@@ -191,7 +213,7 @@ class CSVRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
             good += 1
 
         if not good:
-            print self.errors
+            LOG.error(self.errors)
             raise errors.InvalidDataError("No channels found")
 
     def save(self, filename=None):
@@ -232,7 +254,7 @@ class CSVRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
             return
 
         delta += 1
-        
+
         for i in range(len(self.memories), len(self.memories) + delta + 1):
             mem = chirp_common.Memory()
             mem.empty = True
@@ -298,7 +320,7 @@ class CommanderCSVRadio(CSVRadio):
     def _clean_duplex(self, headers, line, mem):
         try:
             txfreq = chirp_common.parse_freq(
-                        get_datum_by_header(headers, line, "TX Freq"))
+                get_datum_by_header(headers, line, "TX Freq"))
         except ValueError:
             mem.duplex = "off"
             return mem
@@ -310,7 +332,7 @@ class CommanderCSVRadio(CSVRadio):
             mem.offset = txfreq
 
         return mem
-    
+
     def _clean_tmode(self, headers, line, mem):
         rtone = get_datum_by_header(headers, line, "Encode")
         ctone = get_datum_by_header(headers, line, "Decode")
@@ -341,7 +363,8 @@ class CommanderCSVRadio(CSVRadio):
             filedata.startswith("Name,RX Freq,TX Freq,Decode,Encode,TX Pwr,"
                                 "Scan,TX Dev,Busy Lck,Group/Notes") or \
             filedata.startswith('"#","Name","RX Freq","TX Freq","Decode",'
-                '"Encode","TX Pwr","Scan","TX Dev","Busy Lck","Group/Notes"')
+                                '"Encode","TX Pwr","Scan","TX Dev",'
+                                '"Busy Lck","Group/Notes"')
 
 
 @directory.register
@@ -357,7 +380,7 @@ class RTCSVRadio(CSVRadio):
         "Simplex":  "",
         "Split":    "split",
     }
-    
+
     SKIP_MAP = {
         "Off":    "",
         "On":     "S",
@@ -376,19 +399,25 @@ class RTCSVRadio(CSVRadio):
     }
 
     ATTR_MAP = {
-        "Channel Number":   (int,   "number"),
-        "Receive Frequency":(chirp_common.parse_freq, "freq"),
-        "Offset Frequency": (chirp_common.parse_freq, "offset"),
-        "Offset Direction": (lambda v: RTCSVRadio.DUPLEX_MAP.get(v, v), "duplex"),
-        "Operating Mode":   (str,   "mode"),
-        "Name":             (str,   "name"),
-        "Tone Mode":        (lambda v: RTCSVRadio.TMODE_MAP.get(v, v), "tmode"),
-        "CTCSS":            (lambda v: float(v.split(" ")[0]), "rtone"),
-        "DCS":              (int,   "dtcs"),
-        "Skip":             (lambda v: RTCSVRadio.SKIP_MAP.get(v, v), "skip"),
-        "Step":             (lambda v: float(v.split(" ")[0]), "tuning_step"),
-        "Mask":             (lambda v: RTCSVRadio.BOOL_MAP.get(v, v), "empty",),
-        "Comment":          (str,   "comment"),
+        "Channel Number":    (int,   "number"),
+        "Receive Frequency": (chirp_common.parse_freq, "freq"),
+        "Offset Frequency":  (chirp_common.parse_freq, "offset"),
+        "Offset Direction":  (lambda v:
+                              RTCSVRadio.DUPLEX_MAP.get(v, v), "duplex"),
+        "Operating Mode":    (str,   "mode"),
+        "Name":              (str,   "name"),
+        "Tone Mode":         (lambda v:
+                              RTCSVRadio.TMODE_MAP.get(v, v), "tmode"),
+        "CTCSS":             (lambda v:
+                              float(v.split(" ")[0]), "rtone"),
+        "DCS":               (int,   "dtcs"),
+        "Skip":              (lambda v:
+                              RTCSVRadio.SKIP_MAP.get(v, v), "skip"),
+        "Step":              (lambda v:
+                              float(v.split(" ")[0]), "tuning_step"),
+        "Mask":              (lambda v:
+                              RTCSVRadio.BOOL_MAP.get(v, v), "empty",),
+        "Comment":           (str,   "comment"),
         }
 
     def _clean_duplex(self, headers, line, mem):
@@ -426,5 +455,6 @@ class RTCSVRadio(CSVRadio):
         # consistent across radio models.
         return filename.lower().endswith("." + cls.FILE_EXTENSION) and \
             filedata.startswith("Channel Number,Receive Frequency,"
-                "Transmit Frequency,Offset Frequency,Offset Direction,"
-                "Operating Mode,Name,Tone Mode,CTCSS,DCS")
+                                "Transmit Frequency,Offset Frequency,"
+                                "Offset Direction,Operating Mode,"
+                                "Name,Tone Mode,CTCSS,DCS")
diff --git a/chirp/generic_tpe.py b/chirp/drivers/generic_tpe.py
similarity index 78%
rename from chirp/generic_tpe.py
rename to chirp/drivers/generic_tpe.py
index f720c0f..1d24492 100644
--- a/chirp/generic_tpe.py
+++ b/chirp/drivers/generic_tpe.py
@@ -14,7 +14,8 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import UserDict
-from chirp import chirp_common, directory, generic_csv
+from chirp import chirp_common, directory
+from chirp.drivers import generic_csv
 
 
 @directory.register
@@ -25,14 +26,14 @@ class TpeRadio(generic_csv.CSVRadio):
     FILE_EXTENSION = "tpe"
 
     ATTR_MAP = {
-        "Sequence Number" : (int, "number"),
-        "Location"        : (str, "comment"),
-        "Call Sign"       : (str, "name"),
+        "Sequence Number":  (int, "number"),
+        "Location":         (str, "comment"),
+        "Call Sign":        (str, "name"),
         "Output Frequency": (chirp_common.parse_freq, "freq"),
-        "Input Frequency" : (str, "duplex"),
-        "CTCSS Tones"     : (lambda v: float(v)
-                              if v and float(v) in chirp_common.TONES
-                              else 88.5, "rtone"),
+        "Input Frequency":  (str, "duplex"),
+        "CTCSS Tones":      (lambda v: float(v)
+                             if v and float(v) in chirp_common.TONES
+                             else 88.5, "rtone"),
         "Repeater Notes":   (str, "comment"),
     }
 
diff --git a/chirp/generic_xml.py b/chirp/drivers/generic_xml.py
similarity index 90%
rename from chirp/generic_xml.py
rename to chirp/drivers/generic_xml.py
index 1153c63..06c5da9 100644
--- a/chirp/generic_xml.py
+++ b/chirp/drivers/generic_xml.py
@@ -15,22 +15,26 @@
 
 import os
 import libxml2
+import logging
 
 from chirp import chirp_common, errors, xml_ll, platform, directory
 
+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 = "/usr/share/chirp/chirp.xsd"
 
     try:
         ctx = libxml2.schemaNewParserCtxt(path)
         schema = ctx.schemaParse()
     except libxml2.parserError, e:
-        print "Unable to load schema: %s" % e
-        print "Path: %s" % path
+        LOG.error("Unable to load schema: %s" % e)
+        LOG.error("Path: %s" % path)
         raise errors.RadioError("Unable to load schema")
 
     del ctx
@@ -42,18 +46,22 @@ def validate_doc(doc):
         errs.append("ERROR: %s" % msg)
 
     def _wrn(msg, *_args):
-        print "WARNING: %s" % msg
         warnings.append("WARNING: %s" % msg)
 
     validctx = schema.schemaNewValidCtxt()
     validctx.setValidityErrorHandler(_err, _wrn)
     err = validctx.schemaValidateDoc(doc)
-    print os.linesep.join(warnings)
+    for w in warnings:
+        LOG.warn(w)
     if err:
-        print "---DOC---\n%s\n------" % doc.serialize(format=1)
-        print os.linesep.join(errs)
+        for l in ["--- DOC ---",
+                  doc.serialize(format=1).split("\n"),
+                  "-----------",
+                  errs]:
+            LOG.error(l)
         raise errors.RadioError("Schema error")
 
+
 def default_banks():
     """Return an empty set of banks"""
     banks = []
@@ -63,6 +71,7 @@ def default_banks():
 
     return banks
 
+
 @directory.register
 class XMLRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
     """Generic XML driver"""
@@ -86,7 +95,7 @@ class XMLRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
     def get_features(self):
         rf = chirp_common.RadioFeatures()
         rf.has_bank = False
-        #rf.has_bank_index = True
+        # rf.has_bank_index = True
         rf.requires_call_lists = False
         rf.has_implicit_calls = False
         rf.memory_bounds = (0, 1000)
@@ -94,7 +103,7 @@ class XMLRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
         rf.valid_name_length = 999
         rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
         return rf
-        
+
     def load(self, filename=None):
         if not self._filename and not filename:
             raise errors.RadioError("Need a location to load from")
@@ -125,7 +134,7 @@ class XMLRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
                 pass
 
         return mems
-    
+
     def get_memory(self, number):
         mem = xml_ll.get_memory(self.doc, number)
 
diff --git a/chirp/h777.py b/chirp/drivers/h777.py
similarity index 90%
rename from chirp/h777.py
rename to chirp/drivers/h777.py
index b9db514..d48d34c 100644
--- a/chirp/h777.py
+++ b/chirp/drivers/h777.py
@@ -18,14 +18,15 @@ import time
 import os
 import struct
 import unittest
+import logging
 
 from chirp import chirp_common, directory, memmap
 from chirp import bitwise, errors, util
 from chirp.settings import RadioSetting, RadioSettingGroup, \
     RadioSettingValueInteger, RadioSettingValueList, \
-    RadioSettingValueBoolean
+    RadioSettingValueBoolean, RadioSettings
 
-DEBUG = os.getenv("CHIRP_DEBUG") and True or False
+LOG = logging.getLogger(__name__)
 
 MEM_FORMAT = """
 #seekto 0x0010;
@@ -89,9 +90,10 @@ TIMEOUTTIMER_LIST = ["Off", "30 seconds", "60 seconds", "90 seconds",
 SCANMODE_LIST = ["Carrier", "Time"]
 
 SETTING_LISTS = {
-    "voice" : VOICE_LIST,
+    "voice": VOICE_LIST,
     }
 
+
 def _h777_enter_programming_mode(radio):
     serial = radio.pipe
 
@@ -115,7 +117,7 @@ def _h777_enter_programming_mode(radio):
         raise errors.RadioError("Error communicating with radio")
 
     if not ident.startswith("P3107"):
-        print util.hexprint(ident)
+        LOG.debug(util.hexprint(ident))
         raise errors.RadioError("Radio returned unknown identification string")
 
     try:
@@ -127,6 +129,7 @@ def _h777_enter_programming_mode(radio):
     if ack != CMD_ACK:
         raise errors.RadioError("Radio refused to enter programming mode")
 
+
 def _h777_exit_programming_mode(radio):
     serial = radio.pipe
     try:
@@ -134,13 +137,13 @@ def _h777_exit_programming_mode(radio):
     except:
         raise errors.RadioError("Radio refused to exit programming mode")
 
+
 def _h777_read_block(radio, block_addr, block_size):
     serial = radio.pipe
 
     cmd = struct.pack(">cHb", 'R', block_addr, BLOCK_SIZE)
     expectedresponse = "W" + cmd[1:]
-    if DEBUG:
-        print("Reading block %04x..." % (block_addr))
+    LOG.debug("Reading block %04x..." % (block_addr))
 
     try:
         serial.write(cmd)
@@ -160,15 +163,15 @@ def _h777_read_block(radio, block_addr, block_size):
 
     return block_data
 
+
 def _h777_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 + 8]
 
-    if DEBUG:
-        print("Writing Data:")
-        print util.hexprint(cmd + data)
+    LOG.debug("Writing Data:")
+    LOG.debug(util.hexprint(cmd + data))
 
     try:
         serial.write(cmd + data)
@@ -178,8 +181,9 @@ def _h777_write_block(radio, block_addr, block_size):
         raise errors.RadioError("Failed to send block "
                                 "to radio at %04x" % block_addr)
 
+
 def do_download(radio):
-    print "download"
+    LOG.debug("download")
     _h777_enter_programming_mode(radio)
 
     data = ""
@@ -197,14 +201,14 @@ def do_download(radio):
         block = _h777_read_block(radio, addr, BLOCK_SIZE)
         data += block
 
-        if DEBUG:
-            print "Address: %04x" % addr
-            print util.hexprint(block)
+        LOG.debug("Address: %04x" % addr)
+        LOG.debug(util.hexprint(block))
 
     _h777_exit_programming_mode(radio)
 
     return memmap.MemoryMap(data)
 
+
 def do_upload(radio):
     status = chirp_common.Status()
     status.msg = "Uploading to radio"
@@ -222,6 +226,7 @@ def do_upload(radio):
 
     _h777_exit_programming_mode(radio)
 
+
 @directory.register
 class H777Radio(chirp_common.CloneModeRadio):
     """HST H-777"""
@@ -234,7 +239,7 @@ class H777Radio(chirp_common.CloneModeRadio):
     # This code currently requires that ranges start at 0x0000
     # and are continious. In the original program 0x0388 and 0x03C8
     # are only written (all bytes 0xFF), not read.
-    #_ranges = [
+    # _ranges = [
     #       (0x0000, 0x0110),
     #       (0x02B0, 0x02C0),
     #       (0x0380, 0x03E0)
@@ -257,6 +262,14 @@ class H777Radio(chirp_common.CloneModeRadio):
         rf.has_rx_dtcs = True
         rf.has_ctone = True
         rf.has_cross = True
+        rf.valid_cross_modes = [
+            "Tone->Tone",
+            "DTCS->",
+            "->DTCS",
+            "Tone->DTCS",
+            "DTCS->Tone",
+            "->Tone",
+            "DTCS->DTCS"]
         rf.has_tuning_step = False
         rf.has_bank = False
         rf.has_name = False
@@ -379,11 +392,12 @@ class H777Radio(chirp_common.CloneModeRadio):
 
         for setting in mem.extra:
             # NOTE: Only two settings right now, both are inverted
-            setattr(_mem, setting.get_name(), not setting.value)
+            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)
 
         # TODO: Check that all these settings actually do what they
         # say they do.
@@ -393,7 +407,8 @@ class H777Radio(chirp_common.CloneModeRadio):
         basic.append(rs)
 
         rs = RadioSetting("voicelanguage", "Voice language",
-                          RadioSettingValueList(VOICE_LIST,
+                          RadioSettingValueList(
+                              VOICE_LIST,
                               VOICE_LIST[_settings.voicelanguage]))
         basic.append(rs)
 
@@ -402,8 +417,9 @@ class H777Radio(chirp_common.CloneModeRadio):
         basic.append(rs)
 
         rs = RadioSetting("settings2.scanmode", "Scan mode",
-                          RadioSettingValueList(SCANMODE_LIST,
-                          SCANMODE_LIST[self._memobj.settings2.scanmode]))
+                          RadioSettingValueList(
+                              SCANMODE_LIST,
+                              SCANMODE_LIST[self._memobj.settings2.scanmode]))
         basic.append(rs)
 
         rs = RadioSetting("vox", "VOX",
@@ -448,23 +464,25 @@ class H777Radio(chirp_common.CloneModeRadio):
         basic.append(rs)
 
         rs = RadioSetting("settings2.squelchlevel", "Squelch level",
-                          RadioSettingValueInteger(0, 9,
-                              self._memobj.settings2.squelchlevel))
+                          RadioSettingValueInteger(
+                              0, 9, self._memobj.settings2.squelchlevel))
         basic.append(rs)
 
         rs = RadioSetting("settings2.sidekeyfunction", "Side key function",
-                          RadioSettingValueList(SIDEKEYFUNCTION_LIST,
-                          SIDEKEYFUNCTION_LIST[
-                              self._memobj.settings2.sidekeyfunction]))
+                          RadioSettingValueList(
+                              SIDEKEYFUNCTION_LIST,
+                              SIDEKEYFUNCTION_LIST[
+                                  self._memobj.settings2.sidekeyfunction]))
         basic.append(rs)
 
         rs = RadioSetting("settings2.timeouttimer", "Timeout timer",
-                          RadioSettingValueList(TIMEOUTTIMER_LIST,
-                          TIMEOUTTIMER_LIST[
-                              self._memobj.settings2.timeouttimer]))
+                          RadioSettingValueList(
+                              TIMEOUTTIMER_LIST,
+                              TIMEOUTTIMER_LIST[
+                                  self._memobj.settings2.timeouttimer]))
         basic.append(rs)
 
-        return basic
+        return top
 
     def set_settings(self, settings):
         for element in settings:
@@ -484,17 +502,18 @@ class H777Radio(chirp_common.CloneModeRadio):
                         setting = element.get_name()
 
                     if element.has_apply_callback():
-                        print "Using apply callback"
+                        LOG.debug("Using apply callback")
                         element.run_apply_callback()
                     elif setting == "voxlevel":
                         setattr(obj, setting, int(element.value) - 1)
                     else:
-                        print "Setting %s = %s" % (setting, element.value)
+                        LOG.debug("Setting %s = %s" % (setting, element.value))
                         setattr(obj, setting, element.value)
                 except Exception, e:
-                    print element.get_name()
+                    LOG.debug(element.get_name())
                     raise
 
+
 class H777TestCase(unittest.TestCase):
     def setUp(self):
         self.driver = H777Radio(None)
diff --git a/chirp/ic208.py b/chirp/drivers/ic208.py
similarity index 96%
rename from chirp/ic208.py
rename to chirp/drivers/ic208.py
index f93300c..b327371 100644
--- a/chirp/ic208.py
+++ b/chirp/drivers/ic208.py
@@ -13,13 +13,13 @@
 # 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, icf, errors, directory
-from chirp import bitwise
+from chirp.drivers import icf
+from chirp import chirp_common, errors, directory, bitwise
 
 MEM_FORMAT = """
 struct memory {
   u24 freq;
-  u16 offset;  
+  u16 offset;
   u8  power:2,
       rtone:6;
   u8  duplex:2,
@@ -75,12 +75,13 @@ for i in range(1, 6):
     IC208_SPECIAL.append("%iA" % i)
     IC208_SPECIAL.append("%iB" % i)
 
-CHARSET = dict(zip([0x00, 0x08, 0x09, 0x0a, 0x0b, 0x0d, 0x0f], " ()*+-/") + 
-    zip(range(0x10, 0x1a), "0123456789") + 
-    [(0x1c,'|'), (0x1d,'=')] + 
-    zip(range(0x21, 0x3b), "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))
+CHARSET = dict(zip([0x00, 0x08, 0x09, 0x0a, 0x0b, 0x0d, 0x0f], " ()*+-/") +
+               zip(range(0x10, 0x1a), "0123456789") +
+               [(0x1c, '|'), (0x1d, '=')] +
+               zip(range(0x21, 0x3b), "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))
 CHARSET_REV = dict(zip(CHARSET.values(), CHARSET.keys()))
 
+
 def get_name(_mem):
     """Decode the name from @_mem"""
     def _get_char(val):
@@ -97,6 +98,7 @@ def get_name(_mem):
 
     return name.rstrip()
 
+
 def set_name(_mem, name):
     """Encode @name in @_mem"""
     def _get_index(char):
@@ -118,6 +120,7 @@ def set_name(_mem, name):
     _mem.name5 = _get_index(name[4])
     _mem.name6 = _get_index(name[5])
 
+
 @directory.register
 class IC208Radio(icf.IcomCloneModeRadio):
     """Icom IC800"""
@@ -157,7 +160,6 @@ class IC208Radio(icf.IcomCloneModeRadio):
     def process_mmap(self):
         self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
 
-
     def _get_bank(self, loc):
         _flg = self._memobj.flags[loc-1]
         if _flg.bank >= 0x0A:
@@ -194,7 +196,6 @@ class IC208Radio(icf.IcomCloneModeRadio):
 
         return _mem, _flg, index
 
-
     def get_memory(self, number):
         _mem, _flg, index = self._get_memory(number)
 
@@ -258,4 +259,3 @@ class IC208Radio(icf.IcomCloneModeRadio):
         if not isinstance(mem.number, str):
             _flg.skip = mem.skip == "S"
             _flg.pskip = mem.skip == "P"
-
diff --git a/chirp/ic2100.py b/chirp/drivers/ic2100.py
similarity index 87%
rename from chirp/ic2100.py
rename to chirp/drivers/ic2100.py
index b10b6d3..7525ebf 100644
--- a/chirp/ic2100.py
+++ b/chirp/drivers/ic2100.py
@@ -13,8 +13,12 @@
 # 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, icf, util, directory
-from chirp import bitwise, memmap
+from chirp.drivers import icf
+from chirp import chirp_common, util, directory, bitwise, memmap
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettingValueString, \
+    RadioSettingValueFloat, InvalidValueError, RadioSettings
 
 MEM_FORMAT = """
 struct {
@@ -25,7 +29,9 @@ struct {
   u8    unknown1;
   bbcd  offset[2];
   u8    is_12_5:1,
-        unknownbits:3,
+        unknownbit1:1,
+        anm:1,
+        unknownbit2:1,
         duplex:2,
         tmode:2;
   u8    ctone;
@@ -43,7 +49,9 @@ struct {
   u8    unknown1;
   bbcd  offset[2];
   u8    is_12_5:1,
-        unknownbits:3,
+        unknownbit1:1,
+        anm:1,
+        unknownbit2:1,
         duplex:2,
         tmode:2;
   u8    ctone;
@@ -59,7 +67,9 @@ struct {
   u8    unknown1;
   bbcd  offset[2];
   u8    is_12_5:1,
-        unknownbits:3,
+        unknownbit1:1,
+        anm:1,
+        unknownbit2:1,
         duplex:2,
         tmode:2;
   u8    ctone;
@@ -80,10 +90,11 @@ struct {
 
 TMODES = ["", "Tone", "", "TSQL"]
 DUPLEX = ["", "", "+", "-"]
-STEPS =  [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0]
+STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0]
+
 
 def _get_special():
-    special = { "C": 506 }
+    special = {"C": 506}
     for i in range(0, 3):
         ida = "%iA" % (i + 1)
         idb = "%iB" % (i + 1)
@@ -93,6 +104,7 @@ def _get_special():
 
     return special
 
+
 def _get_freq(mem):
     freq = (int(mem.freq) * 100000) + \
         (mem.freq_10khz * 10000) + \
@@ -112,6 +124,7 @@ def _get_freq(mem):
 
     return freq
 
+
 def _set_freq(mem, freq):
     mem.freq = freq / 100000
     mem.freq_10khz = (freq / 10000) % 10
@@ -119,6 +132,7 @@ def _set_freq(mem, freq):
     mem.freq_1khz = khz
     mem.is_12_5 = chirp_common.is_12_5(freq)
 
+
 def _get_offset(mem):
     raw = memmap.MemoryMap(mem.get_raw())
     if ord(raw[5]) & 0x0A:
@@ -131,6 +145,7 @@ def _get_offset(mem):
     else:
         return int(mem.offset) * 1000
 
+
 def _set_offset(mem, offset):
     if (offset % 10) == 5000:
         extra = 0x0A
@@ -143,9 +158,11 @@ def _set_offset(mem, offset):
     raw[5] = ord(raw[5]) | extra
     mem.set_raw(raw.get_packed())
 
+
 def _wipe_memory(mem, char):
     mem.set_raw(char * (mem.size() / 8))
 
+
 @directory.register
 class IC2100Radio(icf.IcomCloneModeRadio):
     """Icom IC-2100"""
@@ -217,7 +234,13 @@ class IC2100Radio(icf.IcomCloneModeRadio):
         mem.ctone = chirp_common.TONES[_mem.ctone]
         mem.tmode = TMODES[_mem.tmode]
         mem.duplex = DUPLEX[_mem.duplex]
-        
+
+        mem.extra = RadioSettingGroup("Extra", "extra")
+
+        rs = RadioSetting("anm", "Alphanumeric Name",
+                          RadioSettingValueBoolean(_mem.anm))
+        mem.extra.append(rs)
+
         return mem
 
     def set_memory(self, mem):
@@ -240,6 +263,7 @@ class IC2100Radio(icf.IcomCloneModeRadio):
             else:
                 _skp &= ~mask
             _mem.name = mem.name.ljust(6)
+            _mem.anm = mem.name.strip() != ""
 
         _set_freq(_mem, mem.freq)
         _set_offset(_mem, mem.offset)
@@ -248,5 +272,8 @@ class IC2100Radio(icf.IcomCloneModeRadio):
         _mem.tmode = TMODES.index(mem.tmode)
         _mem.duplex = DUPLEX.index(mem.duplex)
 
+        for setting in mem.extra:
+            setattr(_mem, setting.get_name(), setting.value)
+
     def get_raw_memory(self, number):
         return repr(self._memobj.memory[number])
diff --git a/chirp/ic2200.py b/chirp/drivers/ic2200.py
similarity index 96%
rename from chirp/ic2200.py
rename to chirp/drivers/ic2200.py
index d6b1fe2..cb5d66e 100644
--- a/chirp/ic2200.py
+++ b/chirp/drivers/ic2200.py
@@ -13,8 +13,8 @@
 # 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, icf, util, directory
-from chirp import bitwise
+from chirp.drivers import icf
+from chirp import chirp_common, util, directory, bitwise
 
 MEM_FORMAT = """
 struct {
@@ -75,16 +75,17 @@ struct {
 
 TMODES = ["", "Tone", "TSQL", "DTCS"]
 DUPLEX = ["", "-", "+"]
-DTCSP  = ["NN", "NR", "RN", "RR"]
-STEPS =  [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0]
+DTCSP = ["NN", "NR", "RN", "RR"]
+STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0]
 
 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 _get_special():
-    special = { "C" : 206 }
+    special = {"C": 206}
     for i in range(0, 3):
         ida = "%iA" % (i+1)
         idb = "%iB" % (i+1)
@@ -94,9 +95,11 @@ def _get_special():
 
     return special
 
+
 def _wipe_memory(mem, char):
     mem.set_raw(char * (mem.size() / 8))
 
+
 @directory.register
 class IC2200Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
     """Icom IC-2200"""
@@ -128,8 +131,8 @@ class IC2200Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
                (0x1AB8, 0x1AC0,  8),
                ]
 
-    MYCALL_LIMIT  = (0, 6)
-    URCALL_LIMIT  = (0, 6)
+    MYCALL_LIMIT = (0, 6)
+    URCALL_LIMIT = (0, 6)
     RPTCALL_LIMIT = (0, 6)
 
     def _get_bank(self, loc):
@@ -145,7 +148,7 @@ class IC2200Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
             _flag.bank = 0x0A
         else:
             _flag.bank = bank
-        
+
     def get_features(self):
         rf = chirp_common.RadioFeatures()
         rf.memory_bounds = (0, 199)
@@ -173,7 +176,7 @@ class IC2200Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
 
         if _mem.mode_dv and not _flag.empty:
             mem = chirp_common.DVMemory()
-            mem.dv_urcall   = \
+            mem.dv_urcall = \
                 str(self._memobj.urcalls[_mem.urcall].call).rstrip()
             mem.dv_rpt1call = \
                 str(self._memobj.rptcalls[_mem.r1call].call).rstrip()
diff --git a/chirp/ic2720.py b/chirp/drivers/ic2720.py
similarity index 98%
rename from chirp/ic2720.py
rename to chirp/drivers/ic2720.py
index 24d499a..30f4a8e 100644
--- a/chirp/ic2720.py
+++ b/chirp/drivers/ic2720.py
@@ -13,8 +13,8 @@
 # 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, icf, directory
-from chirp import bitwise
+from chirp.drivers import icf
+from chirp import chirp_common, util, directory, bitwise
 
 MEM_FORMAT = """
 struct {
@@ -67,12 +67,13 @@ POWER_LEVELS_UHF = [chirp_common.PowerLevel("High", watts=35),
                     chirp_common.PowerLevel("Low", watts=5),
                     chirp_common.PowerLevel("Mid", watts=15)]
 
+
 @directory.register
 class IC2720Radio(icf.IcomCloneModeRadio):
     """Icom IC-2720"""
     VENDOR = "Icom"
     MODEL = "IC-2720H"
-    
+
     _model = "\x24\x92\x00\x01"
     _memsize = 5152
     _endframe = "Icom Inc\x2eA0"
@@ -161,7 +162,7 @@ class IC2720Radio(icf.IcomCloneModeRadio):
         _mem = self._memobj.memory[mem.number]
         _skp = self._memobj.skips[bytepos]
         _usd = self._memobj.used[bytepos]
-        
+
         if mem.empty:
             _usd |= bitpos
             self._set_bank(mem.number, None)
@@ -178,7 +179,7 @@ class IC2720Radio(icf.IcomCloneModeRadio):
         _mem.tuning_step = STEPS.index(mem.tuning_step)
         _mem.is_fm = mem.mode == "FM"
         _mem.duplex = DUPLEX.index(mem.duplex)
-        
+
         if mem.skip == "S":
             _skp |= bitpos
         else:
diff --git a/chirp/ic2820.py b/chirp/drivers/ic2820.py
similarity index 95%
rename from chirp/ic2820.py
rename to chirp/drivers/ic2820.py
index 5f5827c..37b996d 100644
--- a/chirp/ic2820.py
+++ b/chirp/drivers/ic2820.py
@@ -13,8 +13,8 @@
 # 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, icf, util, directory
-from chirp import bitwise
+from chirp.drivers import icf
+from chirp import chirp_common, util, directory, bitwise
 
 MEM_FORMAT = """
 struct {
@@ -78,12 +78,13 @@ struct {
 """
 
 TMODES = ["", "Tone", "??0", "TSQL", "??1", "??2", "DTCS"]
-DUPLEX = ["", "-", "+", "+"] # Not sure about index 3
-MODES  = ["FM", "NFM", "AM", "??", "DV"]
-DTCSP  = ["NN", "NR", "RN", "RR"]
+DUPLEX = ["", "-", "+", "+"]  # Not sure about index 3
+MODES = ["FM", "NFM", "AM", "??", "DV"]
+DTCSP = ["NN", "NR", "RN", "RR"]
 
 MEM_LOC_SIZE = 48
 
+
 class IC2820Bank(icf.IcomNamedBank):
     """An IC2820 bank"""
     def get_name(self):
@@ -94,9 +95,10 @@ class IC2820Bank(icf.IcomNamedBank):
         _banks = self._model._radio._memobj.bank_names
         _banks[self.index].name = str(name).ljust(8)[:8]
 
+
 def _get_special():
-    special = {"C0" : 500 + 20,
-               "C1" : 500 + 21}
+    special = {"C0": 500 + 20,
+               "C1": 500 + 21}
 
     for i in range(0, 10):
         ida = "%iA" % i
@@ -106,15 +108,18 @@ def _get_special():
 
     return special
 
+
 def _resolve_memory_number(number):
     if isinstance(number, str):
         return _get_special()[number]
     else:
         return number
 
+
 def _wipe_memory(mem, char):
     mem.set_raw(char * (mem.size() / 8))
 
+
 @directory.register
 class IC2820Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
     """Icom IC-2820"""
@@ -129,7 +134,8 @@ class IC2820Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
                (0x6960, 0x6980, 16),
                (0x6980, 0x7160, 32),
                (0x7160, 0x7180, 16),
-               (0x7180, 0xACC0, 32),]
+               (0x7180, 0xACC0, 32),
+               ]
 
     _num_banks = 26
     _bank_class = IC2820Bank
@@ -231,7 +237,7 @@ class IC2820Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
         mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
         mem.dtcs_polarity = DTCSP[_mem.dtcs_polarity]
         if _mem.tune_step > 8:
-            mem.tuning_step = 5.0 # Sometimes TS is garbage?
+            mem.tuning_step = 5.0  # Sometimes TS is garbage?
         else:
             mem.tuning_step = chirp_common.TUNING_STEPS[_mem.tune_step]
         mem.name = str(_mem.name).rstrip()
@@ -285,10 +291,10 @@ class IC2820Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
             _mem.urcall = mem.dv_urcall.ljust(8)
             _mem.r1call = mem.dv_rpt1call.ljust(8)
             _mem.r2call = mem.dv_rpt2call.ljust(8)
-            
+
     def get_raw_memory(self, number):
         return repr(self._memobj.memory[number])
-    
+
     def get_urcall_list(self):
         _calls = self._memobj.urcall
         calls = []
@@ -310,7 +316,7 @@ class IC2820Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
     def get_mycall_list(self):
         _calls = self._memobj.mycall
         calls = []
-        
+
         for i in range(*self.MYCALL_LIMIT):
             calls.append(str(_calls[i-1].call))
 
diff --git a/chirp/ic9x.py b/chirp/drivers/ic9x.py
similarity index 90%
rename from chirp/ic9x.py
rename to chirp/drivers/ic9x.py
index cc388a6..bdccf22 100644
--- a/chirp/ic9x.py
+++ b/chirp/drivers/ic9x.py
@@ -15,10 +15,14 @@
 
 import time
 import threading
+import logging
 
-from chirp import chirp_common, errors, ic9x_ll, icf, util, directory
+from chirp.drivers import ic9x_ll, icf
+from chirp import chirp_common, errors, util, directory
 from chirp import bitwise
 
+LOG = logging.getLogger(__name__)
+
 IC9XA_SPECIAL = {}
 IC9XB_SPECIAL = {}
 
@@ -38,9 +42,9 @@ IC9XA_SPECIAL["C0"] = IC9XB_SPECIAL["C0"] = -1
 IC9XA_SPECIAL["C1"] = IC9XB_SPECIAL["C1"] = -2
 
 IC9X_SPECIAL = {
-    0 : {},
-    1 : IC9XA_SPECIAL,
-    2 : IC9XB_SPECIAL,
+    0: {},
+    1: IC9XA_SPECIAL,
+    2: IC9XB_SPECIAL,
 }
 
 CHARSET = chirp_common.CHARSET_ALPHANUMERIC + \
@@ -48,6 +52,7 @@ CHARSET = chirp_common.CHARSET_ALPHANUMERIC + \
 
 LOCK = threading.Lock()
 
+
 class IC9xBank(icf.IcomNamedBank):
     """Icom 9x Bank"""
     def get_name(self):
@@ -59,12 +64,13 @@ class IC9xBank(icf.IcomNamedBank):
         banks[self.index] = name
         self._model._radio._ic9x_set_banks(banks)
 
+
 @directory.register
 class IC9xRadio(icf.IcomLiveRadio):
     """Base class for Icom IC-9x radios"""
     MODEL = "IC-91/92AD"
 
-    _model = "ic9x" # Fake model info for detect.py
+    _model = "ic9x"    # Fake model info for detect.py
     vfo = 0
     __last = 0
     _upper = 300
@@ -104,7 +110,7 @@ class IC9xRadio(icf.IcomLiveRadio):
 
     def _maybe_send_magic(self):
         if (time.time() - self.__last) > 1:
-            print "Sending magic"
+            LOG.debug("Sending magic")
             ic9x_ll.send_magic(self.pipe)
         self.__last = time.time()
 
@@ -113,13 +119,13 @@ class IC9xRadio(icf.IcomLiveRadio):
             try:
                 number = IC9X_SPECIAL[self.vfo][number]
             except KeyError:
-                raise errors.InvalidMemoryLocation("Unknown channel %s" % \
-                                                       number)
+                raise errors.InvalidMemoryLocation(
+                        "Unknown channel %s" % number)
 
         if number < -2 or number > 999:
             raise errors.InvalidValueError("Number must be between 0 and 999")
 
-        if self.__memcache.has_key(number):
+        if number in self.__memcache:
             return self.__memcache[number]
 
         self._lock.acquire()
@@ -162,25 +168,25 @@ class IC9xRadio(icf.IcomLiveRadio):
 
     def get_memories(self, lo=0, hi=None):
         if hi is None:
-            hi = self._upper            
+            hi = self._upper
 
         memories = []
 
         for i in range(lo, hi + 1):
             try:
-                print "Getting %i" % i
+                LOG.debug("Getting %i" % i)
                 mem = self.get_memory(i)
                 if mem:
                     memories.append(mem)
-                print "Done: %s" % mem
+                LOG.debug("Done: %s" % mem)
             except errors.InvalidMemoryLocation:
                 pass
             except errors.InvalidDataError, e:
-                print "Error talking to radio: %s" % e
+                LOG.error("Error talking to radio: %s" % e)
                 break
 
         return memories
-        
+
     def set_memory(self, _memory):
         # Make sure we mirror the DV-ness of the new memory we're
         # setting, and that we capture the Bank value of any currently
@@ -237,25 +243,24 @@ class IC9xRadio(icf.IcomLiveRadio):
             i += 1
 
         return banks
-        
+
     def _ic9x_set_banks(self, banks):
 
         if len(banks) != len(self.__bankcache.keys()):
-            raise errors.InvalidDataError("Invalid bank list length (%i:%i)" %\
-                                              (len(banks),
-                                               len(self.__bankcache.keys())))
+            raise errors.InvalidDataError("Invalid bank list length (%i:%i)" %
+                                          (len(banks),
+                                           len(self.__bankcache.keys())))
 
-        cached_names = [str(self.__bankcache[x]) \
-                            for x in sorted(self.__bankcache.keys())]
+        cached_names = [str(self.__bankcache[x])
+                        for x in sorted(self.__bankcache.keys())]
 
         need_update = False
         for i in range(0, 26):
             if banks[i] != cached_names[i]:
                 need_update = True
                 self.__bankcache[i] = banks[i]
-                print "Updating %s: %s -> %s" % (chr(i + ord("A")),
-                                                 cached_names[i],
-                                                 banks[i])
+                LOG.dbeug("Updating %s: %s -> %s" %
+                          (chr(i + ord("A")), cached_names[i], banks[i]))
 
         if need_update:
             self._lock.acquire()
@@ -275,9 +280,10 @@ class IC9xRadio(icf.IcomLiveRadio):
         rf = chirp_common.RadioFeatures()
         rf.has_sub_devices = True
         rf.valid_special_chans = IC9X_SPECIAL[self.vfo].keys()
-    
+
         return rf
 
+
 class IC9xRadioA(IC9xRadio):
     """IC9x Band A subdevice"""
     VARIANT = "Band A"
@@ -300,6 +306,7 @@ class IC9xRadioA(IC9xRadio):
         rf.valid_name_length = 8
         return rf
 
+
 class IC9xRadioB(IC9xRadio, chirp_common.IcomDstarSupport):
     """IC9x Band B subdevice"""
     VARIANT = "Band B"
@@ -360,12 +367,12 @@ class IC9xRadioB(IC9xRadio, chirp_common.IcomDstarSupport):
                 bcall = calls[i]
             except IndexError:
                 bcall = blank
-            
+
             if acall == bcall:
-                continue # No change to this one
+                continue    # No change to this one
 
             self._maybe_send_magic()
-            ic9x_ll.set_call(self.pipe, cstype, i+1, calls[i])
+            ic9x_ll.set_call(self.pipe, cstype, i + 1, calls[i])
 
         return calls
 
@@ -374,7 +381,7 @@ class IC9xRadioB(IC9xRadio, chirp_common.IcomDstarSupport):
                                              ic9x_ll.IC92MyCallsignFrame,
                                              self.MYCALL_LIMIT[1])
         return self.__mcalls
-        
+
     def get_urcall_list(self):
         self.__ucalls = self.__get_call_list(self.__ucalls,
                                              ic9x_ll.IC92YourCallsignFrame,
@@ -405,6 +412,7 @@ class IC9xRadioB(IC9xRadio, chirp_common.IcomDstarSupport):
                                              self.RPTCALL_LIMIT[1],
                                              calls)
 
+
 def _test():
     import serial
     ser = IC9xRadioB(serial.Serial(port="/dev/ttyUSB1",
@@ -413,5 +421,6 @@ def _test():
     print "-- FOO --"
     ser.set_urcall_list(["K7TAY", "FOOBAR", "BAZ"])
 
+
 if __name__ == "__main__":
     _test()
diff --git a/chirp/ic9x_icf.py b/chirp/drivers/ic9x_icf.py
similarity index 91%
rename from chirp/ic9x_icf.py
rename to chirp/drivers/ic9x_icf.py
index 89b3beb..3e3e3d5 100644
--- a/chirp/ic9x_icf.py
+++ b/chirp/drivers/ic9x_icf.py
@@ -13,9 +13,12 @@
 # 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, icf, ic9x_icf_ll, util, directory, errors
+from chirp.drivers import icf, ic9x_icf_ll
+from chirp import chirp_common, util, directory, errors
 
- at directory.register
+
+# Don't register as this module is used to load icf file from File-Open menu
+# see do_open in mainapp.py
 class IC9xICFRadio(chirp_common.CloneModeRadio):
     VENDOR = "Icom"
     MODEL = "IC-91/92AD"
@@ -51,6 +54,7 @@ class IC9xICFRadio(chirp_common.CloneModeRadio):
         return [IC9xICFRadioA(self._mmap),
                 IC9xICFRadioB(self._mmap)]
 
+
 class IC9xICFRadioA(IC9xICFRadio):
     VARIANT = "ICF File Band A"
 
@@ -62,6 +66,7 @@ class IC9xICFRadioA(IC9xICFRadio):
 
         return ic9x_icf_ll.get_memory(self._mmap, number)
 
+
 class IC9xICFRadioB(IC9xICFRadio):
     VARIANT = "ICF File Band B"
 
diff --git a/chirp/ic9x_icf_ll.py b/chirp/drivers/ic9x_icf_ll.py
similarity index 94%
rename from chirp/ic9x_icf_ll.py
rename to chirp/drivers/ic9x_icf_ll.py
index 30376d7..e1619d5 100644
--- a/chirp/ic9x_icf_ll.py
+++ b/chirp/drivers/ic9x_icf_ll.py
@@ -20,15 +20,16 @@ from chirp.memmap import MemoryMap
 MEM_LOC_SIZE_A = 20
 MEM_LOC_SIZE_B = MEM_LOC_SIZE_A + 1 + (3 * 8)
 
-POS_FREQ     = 0
-POS_OFFSET   = 3
-POS_TONE     = 5
-POS_MODE     = 6
-POS_DTCS     = 7
-POS_TS       = 8
-POS_DTCSPOL  = 11
-POS_DUPLEX   = 11
-POS_NAME     = 12
+POS_FREQ = 0
+POS_OFFSET = 3
+POS_TONE = 5
+POS_MODE = 6
+POS_DTCS = 7
+POS_TS = 8
+POS_DTCSPOL = 11
+POS_DUPLEX = 11
+POS_NAME = 12
+
 
 def get_mem_offset(number):
     """Get the offset into the memory map for memory @number"""
@@ -37,6 +38,7 @@ def get_mem_offset(number):
     else:
         return (MEM_LOC_SIZE_A * 850) + (MEM_LOC_SIZE_B * (number - 850))
 
+
 def get_raw_memory(mmap, number):
     """Return a raw representation of memory @number"""
     offset = get_mem_offset(number)
@@ -46,6 +48,7 @@ def get_raw_memory(mmap, number):
         size = MEM_LOC_SIZE_A
     return MemoryMap(mmap[offset:offset+size])
 
+
 def get_freq(mmap):
     """Return the memory frequency"""
     if ord(mmap[10]) & 0x10:
@@ -55,26 +58,31 @@ def get_freq(mmap):
     val, = struct.unpack(">I", "\x00" + mmap[POS_FREQ:POS_FREQ+3])
     return val * mult
 
+
 def get_offset(mmap):
     """Return the memory offset"""
     val, = struct.unpack(">H", mmap[POS_OFFSET:POS_OFFSET+2])
     return val * 5000
 
+
 def get_rtone(mmap):
     """Return the memory rtone"""
     val = (ord(mmap[POS_TONE]) & 0xFC) >> 2
     return chirp_common.TONES[val]
 
+
 def get_ctone(mmap):
     """Return the memory ctone"""
     val = (ord(mmap[POS_TONE]) & 0x03) | ((ord(mmap[POS_TONE+1]) & 0xF0) >> 4)
     return chirp_common.TONES[val]
 
+
 def get_dtcs(mmap):
     """Return the memory dtcs value"""
     val = ord(mmap[POS_DTCS]) >> 1
     return chirp_common.DTCS_CODES[val]
 
+
 def get_mode(mmap):
     """Return the memory mode"""
     val = ord(mmap[POS_MODE]) & 0x07
@@ -83,11 +91,12 @@ def get_mode(mmap):
 
     return modemap[val]
 
+
 def get_ts(mmap):
     """Return the memory tuning step"""
     val = (ord(mmap[POS_TS]) & 0xF0) >> 4
     if val == 14:
-        return 5.0 # Coerce "Auto" to 5.0
+        return 5.0  # Coerce "Auto" to 5.0
 
     icf_ts = list(chirp_common.TUNING_STEPS)
     icf_ts.insert(2, 8.33)
@@ -98,6 +107,7 @@ def get_ts(mmap):
 
     return icf_ts[val]
 
+
 def get_dtcs_polarity(mmap):
     """Return the memory dtcs polarity"""
     val = (ord(mmap[POS_DTCSPOL]) & 0x03)
@@ -106,6 +116,7 @@ def get_dtcs_polarity(mmap):
 
     return pols[val]
 
+
 def get_duplex(mmap):
     """Return the memory duplex"""
     val = (ord(mmap[POS_DUPLEX]) & 0x0C) >> 2
@@ -114,10 +125,12 @@ def get_duplex(mmap):
 
     return dup[val]
 
+
 def get_name(mmap):
     """Return the memory name"""
     return mmap[POS_NAME:POS_NAME+8]
 
+
 def get_memory(_mmap, number):
     """Get memory @number from global memory map @_mmap"""
     mmap = get_raw_memory(_mmap, number)
diff --git a/chirp/ic9x_ll.py b/chirp/drivers/ic9x_ll.py
similarity index 93%
rename from chirp/ic9x_ll.py
rename to chirp/drivers/ic9x_ll.py
index 42a40ed..c114ad7 100644
--- a/chirp/ic9x_ll.py
+++ b/chirp/drivers/ic9x_ll.py
@@ -14,10 +14,13 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import struct
+import logging
 
 from chirp import chirp_common, util, errors, bitwise
 from chirp.memmap import MemoryMap
 
+LOG = logging.getLogger(__name__)
+
 TUNING_STEPS = [
     5.0, 6.25, 8.33,  9.0, 10.0, 12.5, 15, 20, 25, 30, 50, 100, 125, 200
     ]
@@ -30,20 +33,26 @@ DTCS_POL = ["NN", "NR", "RN", "RR"]
 MEM_LEN = 34
 DV_MEM_LEN = 60
 
+
 # Dirty hack until I clean up this IC9x mess
 class IC9xMemory(chirp_common.Memory):
     """A dirty hack to stash bank information in a memory"""
     _bank = None
     _bank_index = 0
+
     def __init__(self):
         chirp_common.Memory.__init__(self)
+
+
 class IC9xDVMemory(chirp_common.DVMemory):
     """See above dirty hack"""
     _bank = None
     _bank_index = 0
+
     def __init__(self):
         chirp_common.DVMemory.__init__(self)
 
+
 def _ic9x_parse_frames(buf):
     frames = []
 
@@ -52,7 +61,7 @@ def _ic9x_parse_frames(buf):
             start = buf.index("\xfe\xfe")
             end = buf[start:].index("\xfd") + start + 1
         except Exception, e:
-            print "No trailing bit"
+            LOG.error("No trailing bit")
             break
 
         framedata = buf[start:end]
@@ -63,12 +72,13 @@ def _ic9x_parse_frames(buf):
             frame.from_raw(framedata[2:-1])
             frames.append(frame)
         except errors.InvalidDataError, e:
-            print "Broken frame: %s" % e
+            LOG.error("Broken frame: %s" % e)
 
-        #print "Parsed %i frames" % len(frames)
+        # LOG.debug("Parsed %i frames" % len(frames))
 
     return frames
 
+
 def ic9x_send(pipe, buf):
     """Send @buf to @pipe, wrapped in a header and trailer.  Attempt to read
     any response frames, which are returned as a list"""
@@ -76,7 +86,7 @@ def ic9x_send(pipe, buf):
     # Add header and trailer
     realbuf = "\xfe\xfe" + buf + "\xfd"
 
-    #print "Sending:\n%s" % util.hexprint(realbuf)
+    # LOG.debug("Sending:\n%s" % util.hexprint(realbuf))
 
     pipe.write(realbuf)
     pipe.flush()
@@ -91,6 +101,7 @@ def ic9x_send(pipe, buf):
 
     return _ic9x_parse_frames(data)
 
+
 class IC92Frame:
     """IC9x frame base class"""
     def get_vfo(self):
@@ -132,7 +143,7 @@ class IC92Frame:
     def send(self, pipe, verbose=False):
         """Send the frame to the radio via @pipe"""
         if verbose:
-            print "Sending:\n%s" % util.hexprint(self.get_raw())
+            LOG.debug("Sending:\n%s" % util.hexprint(self.get_raw()))
 
         response = ic9x_send(pipe, self.get_raw())
 
@@ -149,7 +160,8 @@ class IC92Frame:
 
     def __getslice__(self, start, end):
         return self._map[start+4:end+4]
-    
+
+
 class IC92GetBankFrame(IC92Frame):
     """A frame for requesting bank information"""
     def __init__(self):
@@ -163,6 +175,7 @@ class IC92GetBankFrame(IC92Frame):
 
         return rframes
 
+
 class IC92BankFrame(IC92Frame):
     """A frame for bank information"""
     def __init__(self):
@@ -186,6 +199,7 @@ class IC92BankFrame(IC92Frame):
         """Set the letter for the bank (A-Z)"""
         self[0] = ident[0]
 
+
 class IC92MemClearFrame(IC92Frame):
     """A frame for clearing (erasing) a memory"""
     def __init__(self, loc):
@@ -195,6 +209,7 @@ class IC92MemClearFrame(IC92Frame):
 
         self[0] = struct.pack(">BHB", 1, int("%i" % loc, 16), 0xFF)
 
+
 class IC92MemGetFrame(IC92Frame):
     """A frame for requesting a memory"""
     def __init__(self, loc, iscall=False):
@@ -208,6 +223,7 @@ class IC92MemGetFrame(IC92Frame):
 
         self[0] = struct.pack(">BH", call, int("%i" % loc, 16))
 
+
 class IC92GetCallsignFrame(IC92Frame):
     """A frame for getting callsign information"""
     def __init__(self, calltype, number):
@@ -215,9 +231,10 @@ class IC92GetCallsignFrame(IC92Frame):
 
         self[0] = chr(number)
 
+
 class IC92CallsignFrame(IC92Frame):
     """A frame to communicate callsign information"""
-    command = 0 # Invalid
+    command = 0  # Invalid
     width = 8
 
     def __init__(self, number=0, callsign=""):
@@ -231,18 +248,21 @@ class IC92CallsignFrame(IC92Frame):
         """Return the actual callsign"""
         return self[1:self.width+1].rstrip()
 
+
 class IC92YourCallsignFrame(IC92CallsignFrame):
     """URCALL frame"""
-    command = 6 # Your
+    command = 6  # Your
+
 
 class IC92RepeaterCallsignFrame(IC92CallsignFrame):
     """RPTCALL frame"""
-    command = 7 # Repeater
+    command = 7  # Repeater
+
 
 class IC92MyCallsignFrame(IC92CallsignFrame):
     """MYCALL frame"""
-    command = 8 # My
-    width = 12 # 4 bytes for /STID
+    command = 8  # My
+    width = 12  # 4 bytes for /STID
 
 MEMORY_FRAME_FORMAT = """
 struct {
@@ -276,6 +296,7 @@ struct {
 } mem[1];
 """
 
+
 class IC92MemoryFrame(IC92Frame):
     """A frame for communicating memory information"""
     def __init__(self):
@@ -316,7 +337,8 @@ class IC92MemoryFrame(IC92Frame):
         if mem.number < 0:
             self.set_iscall(True)
             mem.number = abs(mem.number) - 1
-            print "Memory is %i (call %s)" % (mem.number, self.get_iscall())
+            LOG.debug("Memory is %i (call %s)" %
+                      (mem.number, self.get_iscall()))
 
         _mem = bitwise.parse(MEMORY_FRAME_FORMAT, self).mem
 
@@ -393,6 +415,7 @@ class IC92MemoryFrame(IC92Frame):
 
         return mem
 
+
 def _send_magic_4800(pipe):
     cmd = "\x01\x80\x19"
     magic = ("\xFE" * 25) + cmd
@@ -402,9 +425,10 @@ def _send_magic_4800(pipe):
             return resp[0].get_raw()[0] == "\x80"
     return True
 
+
 def _send_magic_38400(pipe):
     cmd = "\x01\x80\x19"
-    #rsp = "\x80\x01\x19"
+    # rsp = "\x80\x01\x19"
     magic = ("\xFE" * 400) + cmd
     for _i in [0, 1]:
         resp = ic9x_send(pipe, magic)
@@ -412,13 +436,14 @@ def _send_magic_38400(pipe):
             return resp[0].get_raw()[0] == "\x80"
     return False
 
+
 def send_magic(pipe):
     """Send the magic incantation to wake up an ic9x radio"""
     if pipe.getBaudrate() == 38400:
         resp = _send_magic_38400(pipe)
         if resp:
             return
-        print "Switching from 38400 to 4800"
+        LOG.info("Switching from 38400 to 4800")
         pipe.setBaudrate(4800)
         resp = _send_magic_4800(pipe)
         pipe.setBaudrate(38400)
@@ -429,7 +454,7 @@ def send_magic(pipe):
         resp = _send_magic_4800(pipe)
         if resp:
             return
-        print "Switching from 4800 to 38400"
+        LOG.info("Switching from 4800 to 38400")
         pipe.setBaudrate(38400)
         resp = _send_magic_38400(pipe)
         if resp:
@@ -437,8 +462,9 @@ def send_magic(pipe):
         pipe.setBaudrate(4800)
         raise errors.RadioError("Radio not responding")
     else:
-        raise errors.InvalidDataError("Radio in unknown state (%i)" % \
-                                          pipe.getBaudrate())    
+        raise errors.InvalidDataError("Radio in unknown state (%i)" %
+                                      pipe.getBaudrate())
+
 
 def get_memory_frame(pipe, vfo, number):
     """Get the memory frame for @vfo and @number via @pipe"""
@@ -453,6 +479,7 @@ def get_memory_frame(pipe, vfo, number):
 
     return frame.send(pipe)
 
+
 def get_memory(pipe, vfo, number):
     """Get a memory object for @vfo and @number via @pipe"""
     rframe = get_memory_frame(pipe, vfo, number)
@@ -468,20 +495,22 @@ def get_memory(pipe, vfo, number):
 
     return mf.get_memory()
 
+
 def set_memory(pipe, vfo, memory):
     """Set memory @memory on @vfo via @pipe"""
     frame = IC92MemoryFrame()
     frame.set_memory(memory)
     frame.set_vfo(vfo)
 
-    #print "Sending (%i):" % (len(frame.get_raw()))
-    #print util.hexprint(frame.get_raw())
+    # LOG.debug("Sending (%i):" % (len(frame.get_raw())))
+    # LOG.debug(util.hexprint(frame.get_raw()))
 
     rframe = frame.send(pipe)
 
     if rframe.get_raw()[2] != "\xfb":
-        raise errors.InvalidDataError("Radio reported error:\n%s" %\
-                                          util.hexprint(rframe.get_payload()))
+        raise errors.InvalidDataError("Radio reported error:\n%s" %
+                                      util.hexprint(rframe.get_payload()))
+
 
 def erase_memory(pipe, vfo, number):
     """Erase memory @number on @vfo via @pipe"""
@@ -492,6 +521,7 @@ def erase_memory(pipe, vfo, number):
     if rframe.get_raw()[2] != "\xfb":
         raise errors.InvalidDataError("Radio reported error")
 
+
 def get_banks(pipe, vfo):
     """Get banks for @vfo via @pipe"""
     frame = IC92GetBankFrame()
@@ -511,9 +541,10 @@ def get_banks(pipe, vfo):
         bframe.from_frame(rframes[i])
 
         banks.append(bframe.get_name().rstrip())
-    
+
     return banks
 
+
 def set_banks(pipe, vfo, banks):
     """Set banks for @vfo via @pipe"""
     for i in range(0, 26):
@@ -526,6 +557,7 @@ def set_banks(pipe, vfo, banks):
         if rframe.get_payload() != "\xfb":
             raise errors.InvalidDataError("Radio reported error")
 
+
 def get_call(pipe, cstype, number):
     """Get @cstype callsign @number via @pipe"""
     cframe = IC92GetCallsignFrame(cstype.command, number)
@@ -537,6 +569,7 @@ def get_call(pipe, cstype, number):
 
     return cframe.get_callsign()
 
+
 def set_call(pipe, cstype, number, call):
     """Set @cstype @call at position @number via @pipe"""
     cframe = cstype(number, call)
diff --git a/chirp/icf.py b/chirp/drivers/icf.py
similarity index 88%
rename from chirp/icf.py
rename to chirp/drivers/icf.py
index fb5950e..b51d8cb 100644
--- a/chirp/icf.py
+++ b/chirp/drivers/icf.py
@@ -16,18 +16,22 @@
 import struct
 import re
 import time
+import logging
 
 from chirp import chirp_common, errors, util, memmap
 from chirp.settings import RadioSetting, RadioSettingGroup, \
-    RadioSettingValueBoolean
+    RadioSettingValueBoolean, RadioSettings
+
+LOG = logging.getLogger(__name__)
 
 CMD_CLONE_OUT = 0xE2
-CMD_CLONE_IN  = 0xE3
+CMD_CLONE_IN = 0xE3
 CMD_CLONE_DAT = 0xE4
-CMD_CLONE_END = 0xE5 
+CMD_CLONE_END = 0xE5
 
 SAVE_PIPE = None
 
+
 class IcfFrame:
     """A single ICF communication frame"""
     src = 0
@@ -37,15 +41,15 @@ class IcfFrame:
     payload = ""
 
     def __str__(self):
-        addrs = { 0xEE : "PC",
-                  0xEF : "Radio"}
-        cmds = {0xE0 : "ID",
-                0xE1 : "Model",
-                0xE2 : "Clone out",
-                0xE3 : "Clone in",
-                0xE4 : "Clone data",
-                0xE5 : "Clone end",
-                0xE6 : "Clone result"}
+        addrs = {0xEE: "PC",
+                 0xEF: "Radio"}
+        cmds = {0xE0: "ID",
+                0xE1: "Model",
+                0xE2: "Clone out",
+                0xE3: "Clone in",
+                0xE4: "Clone data",
+                0xE5: "Clone end",
+                0xE6: "Clone result"}
 
         return "%s -> %s [%s]:\n%s" % (addrs[self.src], addrs[self.dst],
                                        cmds[self.cmd],
@@ -54,6 +58,7 @@ class IcfFrame:
     def __init__(self):
         pass
 
+
 def parse_frame_generic(data):
     """Parse an ICF frame of unknown type from the beginning of @data"""
     frame = IcfFrame()
@@ -71,6 +76,7 @@ def parse_frame_generic(data):
 
     return frame, data[end+1:]
 
+
 class RadioStream:
     """A class to make reading a stream of IcfFrames easier"""
     def __init__(self, pipe):
@@ -81,7 +87,7 @@ class RadioStream:
         if not self.data.startswith("\xFE\xFE"):
             raise errors.InvalidDataError("Out of sync with radio")
         elif len(self.data) < 5:
-            return [] # Not enough data for a full frame
+            return []  # Not enough data for a full frame
 
         frames = []
 
@@ -89,7 +95,7 @@ class RadioStream:
             try:
                 cmd = ord(self.data[4])
             except IndexError:
-                break # Out of data
+                break  # Out of data
 
             try:
                 frame, rest = parse_frame_generic(self.data)
@@ -103,7 +109,7 @@ class RadioStream:
 
                 self.data = rest
             except errors.InvalidDataError, e:
-                print "Failed to parse frame (cmd=%i): %s" % (cmd, e)
+                LOG.error("Failed to parse frame (cmd=%i): %s" % (cmd, e))
                 return []
 
         return frames
@@ -118,19 +124,20 @@ class RadioStream:
                 self.data += _data
 
             if not nolimit and len(self.data) > 128 and "\xFD" in self.data:
-                break # Give us a chance to do some status
+                break  # Give us a chance to do some status
             if len(self.data) > 1024:
-                break # Avoid an endless loop of chewing garbage
+                break  # Avoid an endless loop of chewing garbage
 
         if not self.data:
             return []
 
         return self._process_frames()
 
+
 def get_model_data(pipe, mdata="\x00\x00\x00\x00"):
     """Query the radio connected to @pipe for its model data"""
     send_clone_frame(pipe, 0xe0, mdata, raw=True)
-    
+
     stream = RadioStream(pipe)
     frames = stream.get_frames()
 
@@ -139,6 +146,7 @@ def get_model_data(pipe, mdata="\x00\x00\x00\x00"):
 
     return frames[0].payload
 
+
 def get_clone_resp(pipe, length=None):
     """Read the response to a clone frame"""
     def exit_criteria(buf, length):
@@ -155,6 +163,7 @@ def get_clone_resp(pipe, length=None):
 
     return resp
 
+
 def send_clone_frame(pipe, cmd, data, raw=False, checksum=False):
     """Send a clone frame with @cmd and @data to the radio attached
     to @pipe"""
@@ -178,20 +187,21 @@ def send_clone_frame(pipe, cmd, data, raw=False, checksum=False):
     frame = "\xfe\xfe\xee\xef%s%s%s\xfd" % (chr(cmd), hed, cs)
 
     if SAVE_PIPE:
-        print "Saving data..."
+        LOG.debug("Saving data...")
         SAVE_PIPE.write(frame)
 
-    #print "Sending:\n%s" % util.hexprint(frame)
-    #print "Sending:\n%s" % util.hexprint(hed[6:])
+    # LOG.debug("Sending:\n%s" % util.hexprint(frame))
+    # LOG.debug("Sending:\n%s" % util.hexprint(hed[6:]))
     if cmd == 0xe4:
         # Uncomment to avoid cloning to the radio
         # return frame
         pass
-    
+
     pipe.write(frame)
 
     return frame
 
+
 def process_bcd(bcddata):
     """Convert BCD-encoded data to raw"""
     data = ""
@@ -202,11 +212,12 @@ def process_bcd(bcddata):
             i += 2
             data += struct.pack("B", val)
         except ValueError, e:
-            print "Failed to parse byte: %s" % e
+            LOG.error("Failed to parse byte: %s" % e)
             break
 
     return data
 
+
 def process_data_frame(frame, _mmap):
     """Process a data frame, adding the payload to @_mmap"""
     _data = process_bcd(frame.payload)
@@ -222,23 +233,24 @@ def process_data_frame(frame, _mmap):
     try:
         _mmap[saddr] = data
     except IndexError:
-        print "Error trying to set %i bytes at %05x (max %05x)" % \
-            (bytes, saddr, len(_mmap))
+        LOG.error("Error trying to set %i bytes at %05x (max %05x)" %
+                  (bytes, saddr, len(_mmap)))
     return saddr, saddr + length
 
+
 def start_hispeed_clone(radio, cmd):
     """Send the magic incantation to the radio to go fast"""
     buf = ("\xFE" * 20) + \
         "\xEE\xEF\xE8" + \
         radio.get_model() + \
         "\x00\x00\x02\x01\xFD"
-    print "Starting HiSpeed:\n%s" % util.hexprint(buf)
+    LOG.debug("Starting HiSpeed:\n%s" % util.hexprint(buf))
     radio.pipe.write(buf)
     radio.pipe.flush()
     resp = radio.pipe.read(128)
-    print "Response:\n%s" % util.hexprint(resp)
+    LOG.debug("Response:\n%s" % util.hexprint(resp))
 
-    print "Switching to 38400 baud"
+    LOG.info("Switching to 38400 baud")
     radio.pipe.setBaudrate(38400)
 
     buf = ("\xFE" * 14) + \
@@ -246,24 +258,26 @@ def start_hispeed_clone(radio, cmd):
         chr(cmd) + \
         radio.get_model()[:3] + \
         "\x00\xFD"
-    print "Starting HiSpeed Clone:\n%s" % util.hexprint(buf)
+    LOG.debug("Starting HiSpeed Clone:\n%s" % util.hexprint(buf))
     radio.pipe.write(buf)
     radio.pipe.flush()
 
+
 def _clone_from_radio(radio):
     md = get_model_data(radio.pipe)
 
     if md[0:4] != radio.get_model():
-        print "This model: %s" % util.hexprint(md[0:4])
-        print "Supp model: %s" % util.hexprint(radio.get_model())
+        LOG.info("This model: %s" % util.hexprint(md[0:4]))
+        LOG.info("Supp model: %s" % util.hexprint(radio.get_model()))
         raise errors.RadioError("I can't talk to this model")
 
     if radio.is_hispeed():
         start_hispeed_clone(radio, CMD_CLONE_OUT)
     else:
-        send_clone_frame(radio.pipe, CMD_CLONE_OUT, radio.get_model(), raw=True)
+        send_clone_frame(radio.pipe, CMD_CLONE_OUT,
+                         radio.get_model(), raw=True)
 
-    print "Sent clone frame"
+    LOG.debug("Sent clone frame")
 
     stream = RadioStream(radio.pipe)
 
@@ -279,17 +293,16 @@ def _clone_from_radio(radio):
             if frame.cmd == CMD_CLONE_DAT:
                 src, dst = process_data_frame(frame, _mmap)
                 if last_size != (dst - src):
-                    print "ICF Size change from %i to %i at %04x" % (last_size,
-                                                                     dst - src,
-                                                                     src)
+                    LOG.debug("ICF Size change from %i to %i at %04x" %
+                              (last_size, dst - src, src))
                     last_size = dst - src
                 if addr != src:
-                    print "ICF GAP %04x - %04x" % (addr, src)
+                    LOG.debug("ICF GAP %04x - %04x" % (addr, src))
                 addr = dst
             elif frame.cmd == CMD_CLONE_END:
-                print "End frame (%i):\n%s" % (len(frame.payload),
-                                               util.hexprint(frame.payload))
-                print "Last addr: %04x" % addr
+                LOG.debug("End frame (%i):\n%s" %
+                          (len(frame.payload), util.hexprint(frame.payload)))
+                LOG.debug("Last addr: %04x" % addr)
 
         if radio.status_fn:
             status = chirp_common.Status()
@@ -300,6 +313,7 @@ def _clone_from_radio(radio):
 
     return _mmap
 
+
 def clone_from_radio(radio):
     """Do a full clone out of the radio's memory"""
     try:
@@ -307,6 +321,7 @@ def clone_from_radio(radio):
     except Exception, e:
         raise errors.RadioError("Failed to communicate with the radio: %s" % e)
 
+
 def send_mem_chunk(radio, start, stop, bs=32):
     """Send a single chunk of the radio's memory from @start- at stop"""
     _mmap = radio.get_mmap()
@@ -338,6 +353,7 @@ def send_mem_chunk(radio, start, stop, bs=32):
 
     return True
 
+
 def _clone_to_radio(radio):
     global SAVE_PIPE
 
@@ -379,7 +395,7 @@ def _clone_to_radio(radio):
             frames += stream.get_frames(True)
             result = frames[-1]
         except IndexError:
-            print "Waiting for clone result..."
+            LOG.debug("Waiting for clone result...")
             time.sleep(0.5)
 
     if len(frames) == 0:
@@ -387,6 +403,7 @@ def _clone_to_radio(radio):
 
     return result.payload[0] == '\x00'
 
+
 def clone_to_radio(radio):
     """Initiate a full memory clone out to @radio"""
     try:
@@ -394,6 +411,7 @@ def clone_to_radio(radio):
     except Exception, e:
         raise errors.RadioError("Failed to communicate with the radio: %s" % e)
 
+
 def convert_model(mod_str):
     """Convert an ICF-style model string into what we get from the radio"""
     data = ""
@@ -404,6 +422,7 @@ def convert_model(mod_str):
 
     return data
 
+
 def convert_data_line(line):
     """Convert an ICF data line to raw memory format"""
     if line.startswith("#"):
@@ -428,18 +447,19 @@ def convert_data_line(line):
             i += 2
             _mmap += struct.pack("B", val)
         except ValueError, e:
-            print "Failed to parse byte: %s" % e
+            LOG.debug("Failed to parse byte: %s" % e)
             break
 
     return _mmap
 
+
 def read_file(filename):
     """Read an ICF file and return the model string and memory data"""
     f = file(filename)
 
     mod_str = f.readline()
     dat = f.readlines()
-    
+
     model = convert_model(mod_str.strip())
 
     _mmap = ""
@@ -449,6 +469,7 @@ def read_file(filename):
 
     return model, memmap.MemoryMap(_mmap)
 
+
 def is_9x_icf(filename):
     """Returns True if @filename is an IC9x ICF file"""
     f = file(filename)
@@ -457,6 +478,7 @@ def is_9x_icf(filename):
 
     return mdata in ["30660000", "28880000"]
 
+
 def is_icf_file(filename):
     """Returns True if @filename is an ICF file"""
     f = file(filename)
@@ -468,18 +490,21 @@ def is_icf_file(filename):
 
     return bool(re.match("^[0-9]{8}#", data))
 
+
 class IcomBank(chirp_common.Bank):
     """A bank that works for all Icom radios"""
     # Integral index of the bank (not to be confused with per-memory
     # bank indexes
     index = 0
 
+
 class IcomNamedBank(IcomBank):
     """A bank with an adjustable name"""
     def set_name(self, name):
         """Set the name of the bank"""
         pass
 
+
 class IcomBankModel(chirp_common.BankModel):
     """Icom radios all have pretty much the same simple bank model. This
     central implementation can, with a few icom-specific radio interfaces
@@ -490,7 +515,7 @@ class IcomBankModel(chirp_common.BankModel):
 
     def get_mappings(self):
         banks = []
-        
+
         for i in range(0, self._radio._num_banks):
             index = chr(ord("A") + i)
             bank = self._radio._bank_class(self, index, "BANK-%s" % index)
@@ -503,8 +528,8 @@ class IcomBankModel(chirp_common.BankModel):
 
     def remove_memory_from_mapping(self, memory, bank):
         if self._radio._get_bank(memory.number) != bank.index:
-            raise Exception("Memory %i not in bank %s. Cannot remove." % \
-                                (memory.number, bank))
+            raise Exception("Memory %i not in bank %s. Cannot remove." %
+                            (memory.number, bank))
 
         self._radio._set_bank(memory.number, None)
 
@@ -521,7 +546,8 @@ class IcomBankModel(chirp_common.BankModel):
             return []
         else:
             return [self.get_mappings()[index]]
-    
+
+
 class IcomIndexedBankModel(IcomBankModel,
                            chirp_common.MappingModelIndexInterface):
     """Generic bank model for Icom radios with indexed banks"""
@@ -545,13 +571,13 @@ class IcomIndexedBankModel(IcomBankModel,
         for i in range(*self._radio.get_features().memory_bounds):
             if self._radio._get_bank(i) == bank.index:
                 indexes.append(self._radio._get_bank_index(i))
-                
+
         for i in range(0, 256):
             if i not in indexes:
                 return i
 
         raise errors.RadioError("Out of slots in this bank")
-        
+
 
 class IcomCloneModeRadio(chirp_common.CloneModeRadio):
     """Base class for Icom clone-mode radios"""
@@ -619,6 +645,7 @@ class IcomCloneModeRadio(chirp_common.CloneModeRadio):
     def set_settings(self, settings):
         return honor_speed_switch_setting(self, settings)
 
+
 class IcomLiveRadio(chirp_common.LiveRadio):
     """Base class for an Icom Live-mode radio"""
     VENDOR = "Icom"
@@ -638,19 +665,22 @@ class IcomLiveRadio(chirp_common.LiveRadio):
         else:
             return None
 
+
 def make_speed_switch_setting(radio):
     if not radio.__class__._can_hispeed:
-        return []
+        return {}
     drvopts = RadioSettingGroup("drvopts", "Driver Options")
+    top = RadioSettings(drvopts)
     rs = RadioSetting("drv_clone_speed", "Use Hi-Speed Clone",
                       RadioSettingValueBoolean(radio._can_hispeed))
     drvopts.append(rs)
-    return drvopts
+    return top
+
 
 def honor_speed_switch_setting(radio, settings):
     for element in settings:
         if element.get_name() == "drvopts":
-            return honor_speed_switch_setting(radio, settings)
+            return honor_speed_switch_setting(radio, element)
         if element.get_name() == "drv_clone_speed":
             radio.__class__._can_hispeed = element.value.get_value()
             return
diff --git a/chirp/icomciv.py b/chirp/drivers/icomciv.py
similarity index 85%
rename from chirp/icomciv.py
rename to chirp/drivers/icomciv.py
index 71af300..dd32ae7 100644
--- a/chirp/icomciv.py
+++ b/chirp/drivers/icomciv.py
@@ -1,9 +1,11 @@
 
 import struct
-from chirp import chirp_common, icf, util, errors, bitwise, directory
+import logging
+from chirp.drivers import icf
+from chirp import chirp_common, util, errors, bitwise, directory
 from chirp.memmap import MemoryMap
 
-DEBUG = True
+LOG = logging.getLogger(__name__)
 
 MEM_FORMAT = """
 bbcd number[2];
@@ -54,6 +56,7 @@ u8   unknown[11];
 char name[9];
 """
 
+
 class Frame:
     """Base class for an ICF frame"""
     _cmd = 0x00
@@ -80,17 +83,16 @@ class Frame:
         raw = struct.pack("BBBBBB", 0xFE, 0xFE, src, dst, self._cmd, self._sub)
         raw += str(self._data) + chr(0xFD)
 
-        if DEBUG:
-            print "%02x -> %02x (%i):\n%s" % (src, dst,
-                                              len(raw), util.hexprint(raw))
+        LOG.debug("%02x -> %02x (%i):\n%s" %
+                  (src, dst, len(raw), util.hexprint(raw)))
 
         serial.write(raw)
         if willecho:
             echo = serial.read(len(raw))
             if echo != raw and echo:
-                print "Echo differed (%i/%i)" % (len(raw), len(echo))
-                print util.hexprint(raw)
-                print util.hexprint(echo)
+                LOG.debug("Echo differed (%i/%i)" % (len(raw), len(echo)))
+                LOG.debug(util.hexprint(raw))
+                LOG.debug(util.hexprint(echo))
 
     def read(self, serial):
         """Read the frame from @serial"""
@@ -98,7 +100,7 @@ class Frame:
         while not data.endswith(chr(0xFD)):
             char = serial.read(1)
             if not char:
-                print "Read %i bytes total" % len(data)
+                LOG.debug("Read %i bytes total" % len(data))
                 raise errors.RadioError("Timeout")
             data += char
 
@@ -106,8 +108,7 @@ class Frame:
             raise errors.RadioError("Radio reported error")
 
         src, dst = struct.unpack("BB", data[2:4])
-        if DEBUG:
-            print "%02x <- %02x:\n%s" % (src, dst, util.hexprint(data))
+        LOG.debug("%02x <- %02x:\n%s" % (src, dst, util.hexprint(data)))
 
         self._cmd = ord(data[4])
         self._sub = ord(data[5])
@@ -118,6 +119,7 @@ class Frame:
     def get_obj(self):
         raise errors.RadioError("Generic frame has no structure")
 
+
 class MemFrame(Frame):
     """A memory frame"""
     _cmd = 0x1A
@@ -139,13 +141,14 @@ class MemFrame(Frame):
 
     def get_obj(self):
         """Return a bitwise parsed object"""
-        self._data = MemoryMap(str(self._data)) # Make sure we're assignable
+        self._data = MemoryMap(str(self._data))  # Make sure we're assignable
         return bitwise.parse(MEM_FORMAT, self._data)
 
     def initialize(self):
         """Initialize to sane values"""
         self._data = MemoryMap("".join(["\x00"] * (self.get_obj().size() / 8)))
 
+
 class MultiVFOMemFrame(MemFrame):
     """A memory frame for radios with multiple VFOs"""
     def set_location(self, loc, vfo=1):
@@ -153,14 +156,16 @@ class MultiVFOMemFrame(MemFrame):
         self._data = struct.pack(">BH", vfo, int("%04i" % loc, 16))
 
     def get_obj(self):
-        self._data = MemoryMap(str(self._data)) # Make sure we're assignable
+        self._data = MemoryMap(str(self._data))  # Make sure we're assignable
         return bitwise.parse(MEM_VFO_FORMAT, self._data)
 
+
 class DupToneMemFrame(MemFrame):
     def get_obj(self):
         self._data = MemoryMap(str(self._data))
         return bitwise.parse(mem_duptone_format, self._data)
 
+
 class IcomCIVRadio(icf.IcomLiveRadio):
     """Base class for ICOM CIV-based radios"""
     BAUD_RATE = 19200
@@ -185,31 +190,32 @@ class IcomCIVRadio(icf.IcomLiveRadio):
         echo_test = "\xfe\xfe\xe0\xe0\xfa\xfd"
         self.pipe.write(echo_test)
         resp = self.pipe.read(6)
-        print "Echo:\n%s" % util.hexprint(resp)
+        LOG.debug("Echo:\n%s" % util.hexprint(resp))
         return resp == echo_test
 
     def __init__(self, *args, **kwargs):
         icf.IcomLiveRadio.__init__(self, *args, **kwargs)
 
         self._classes = {
-            "mem" : MemFrame,
+            "mem": MemFrame,
             }
 
         if self.pipe:
             self._willecho = self._detect_echo()
-            print "Interface echo: %s" % self._willecho
+            LOG.debug("Interface echo: %s" % self._willecho)
             self.pipe.setTimeout(1)
 
-        #f = Frame()
-        #f.set_command(0x19, 0x00)
-        #self._send_frame(f)
+        # f = Frame()
+        # f.set_command(0x19, 0x00)
+        # self._send_frame(f)
         #
-        #res = f.read(self.pipe)
-        #if res:
-        #    print "Result: %x->%x (%i)" % (res[0], res[1], len(f.get_data()))
-        #    print util.hexprint(f.get_data())
+        # res = f.read(self.pipe)
+        # if res:
+        #    LOG.debug("Result: %x->%x (%i)" %
+        #              (res[0], res[1], len(f.get_data())))
+        #    LOG.debug(util.hexprint(f.get_data()))
         #
-        #self._id = f.get_data()[0]
+        # self._id = f.get_data()[0]
         self._rf = chirp_common.RadioFeatures()
 
         self._initialize()
@@ -232,7 +238,7 @@ class IcomCIVRadio(icf.IcomLiveRadio):
         return repr(f.get_obj())
 
     def get_memory(self, number):
-        print "Getting %i" % number
+        LOG.debug("Getting %i" % number)
         f = self._classes["mem"]()
         f.set_location(number)
         self._send_frame(f)
@@ -248,7 +254,7 @@ class IcomCIVRadio(icf.IcomLiveRadio):
             return mem
 
         memobj = f.get_obj()
-        print repr(memobj)
+        LOG.debug(repr(memobj))
 
         mem.freq = int(memobj.freq)
         mem.mode = self._rf.valid_modes[memobj.mode]
@@ -282,8 +288,8 @@ class IcomCIVRadio(icf.IcomLiveRadio):
             self._send_frame(f)
             return
 
-        #f.set_data(MemoryMap(self.get_raw_memory(mem.number)))
-        #f.initialize()
+        # f.set_data(MemoryMap(self.get_raw_memory(mem.number)))
+        # f.initialize()
 
         memobj = f.get_obj()
         memobj.number = mem.number
@@ -302,11 +308,12 @@ class IcomCIVRadio(icf.IcomLiveRadio):
             memobj.ctone = int(mem.ctone * 10)
             memobj.rtone = int(mem.rtone * 10)
 
-        print repr(memobj)
+        LOG.debug(repr(memobj))
         self._send_frame(f)
 
         f = self._recv_frame()
-        print "Result:\n%s" % util.hexprint(f.get_data())
+        LOG.debug("Result:\n%s" % util.hexprint(f.get_data()))
+
 
 @directory.register
 class Icom7200Radio(IcomCIVRadio):
@@ -330,6 +337,7 @@ class Icom7200Radio(IcomCIVRadio):
         self._rf.valid_skips = []
         self._rf.memory_bounds = (1, 200)
 
+
 @directory.register
 class Icom7000Radio(IcomCIVRadio):
     """Icom IC-7000"""
@@ -356,6 +364,7 @@ class Icom7000Radio(IcomCIVRadio):
         self._rf.valid_characters = chirp_common.CHARSET_ASCII
         self._rf.memory_bounds = (1, 99)
 
+
 @directory.register
 class Icom746Radio(IcomCIVRadio):
     """Icom IC-746"""
@@ -384,11 +393,12 @@ class Icom746Radio(IcomCIVRadio):
         self._rf.memory_bounds = (1, 99)
 
 CIV_MODELS = {
-    (0x76, 0xE0) : Icom7200Radio,
-    (0x70, 0xE0) : Icom7000Radio,
-    (0x46, 0xE0) : Icom746Radio,
+    (0x76, 0xE0): Icom7200Radio,
+    (0x70, 0xE0): Icom7000Radio,
+    (0x46, 0xE0): Icom746Radio,
 }
 
+
 def probe_model(ser):
     """Probe the radio attatched to @ser for its model"""
     f = Frame()
@@ -406,8 +416,8 @@ def probe_model(ser):
             return CIV_MODELS[(model, controller)]
 
         if f.get_data():
-            print "Got data, but not 1 byte:"
-            print util.hexprint(f.get_data())
+            LOG.debug("Got data, but not 1 byte:")
+            LOG.debug(util.hexprint(f.get_data()))
             raise errors.RadioError("Unknown response")
 
     raise errors.RadioError("Unsupported model")
diff --git a/chirp/icq7.py b/chirp/drivers/icq7.py
similarity index 81%
rename from chirp/icq7.py
rename to chirp/drivers/icq7.py
index 6a95ef9..04cf63e 100644
--- a/chirp/icq7.py
+++ b/chirp/drivers/icq7.py
@@ -14,13 +14,18 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import struct
-from chirp import chirp_common, icf, directory
-from chirp import bitwise
+import logging
+
+from chirp.drivers import icf
+from chirp import chirp_common, directory, bitwise
 from chirp.chirp_common import to_GHz, from_GHz
 from chirp.settings import RadioSetting, RadioSettingGroup, \
                 RadioSettingValueBoolean, RadioSettingValueList, \
                 RadioSettingValueInteger, RadioSettingValueString, \
-                RadioSettingValueFloat
+                RadioSettingValueFloat, RadioSettings
+
+
+LOG = logging.getLogger(__name__)
 
 MEM_FORMAT = """
 struct {
@@ -74,10 +79,10 @@ u8 lockgroup;
 
 """
 
-TMODES = ["", "", "Tone", "TSQL", "TSQL"] # last one is pocket beep
+TMODES = ["", "", "Tone", "TSQL", "TSQL"]  # last one is pocket beep
 DUPLEX = ["", "", "-", "+"]
-MODES  = ["FM", "WFM", "AM", "Auto"]
-STEPS =  [5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0]
+MODES = ["FM", "WFM", "AM", "Auto"]
+STEPS = [5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0]
 AUTORP_LIST = ["Off", "Duplex Only", "Duplex and Tone"]
 LOCKGROUP_LIST = ["Normal", "No Squelch", "No Volume", "All"]
 SQUELCH_LIST = ["Open", "Auto"] + ["L%s" % x for x in range(1, 10)]
@@ -91,6 +96,7 @@ RESUME_LIST = ["%s sec" % x for x in range(0, 6)]
 APOFF_LIST = ["Off"] + ["%s min" % x for x in range(30, 150, 30)]
 D_SEL_LIST = ["100 KHz", "1 MHz", "10 MHz"]
 
+
 @directory.register
 class ICQ7Radio(icf.IcomCloneModeRadio):
     """Icom IC-Q7A"""
@@ -111,8 +117,8 @@ class ICQ7Radio(icf.IcomCloneModeRadio):
         rf.valid_tmodes = list(TMODES)
         rf.valid_duplexes = list(DUPLEX)
         rf.valid_tuning_steps = list(STEPS)
-        rf.valid_bands = [(  1000000,  823995000),
-                          (849000000,  868995000),
+        rf.valid_bands = [(1000000,   823995000),
+                          (849000000, 868995000),
                           (894000000, 1309995000)]
         rf.valid_skips = ["", "S", "P"]
         rf.has_dtcs = False
@@ -153,7 +159,7 @@ class ICQ7Radio(icf.IcomCloneModeRadio):
         try:
             mem.tuning_step = STEPS[_mem.tune_step]
         except IndexError:
-            print "Invalid tune step index %i" % _mem.tune_step
+            LOG.error("Invalid tune step index %i" % _mem.tune_step)
         mem.tmode = TMODES[_flag.tmode]
         mem.duplex = DUPLEX[_flag.duplex]
         if mem.freq < 30000000:
@@ -170,12 +176,12 @@ class ICQ7Radio(icf.IcomCloneModeRadio):
     def set_memory(self, mem):
         _mem = self._memobj.memory[mem.number]
         _flag = self._memobj.flags[mem.number]
-        
+
         if mem.empty:
             self._memobj.flags_whole[mem.number] = 0xFF
             return
-            
-        _mem.set_raw("\x00" * 8)    
+
+        _mem.set_raw("\x00" * 8)
 
         if mem.freq > to_GHz(1):
             _mem.freq = (mem.freq / 1000) - to_GHz(1)
@@ -198,7 +204,7 @@ class ICQ7Radio(icf.IcomCloneModeRadio):
     def get_settings(self):
         _settings = self._memobj.settings
         basic = RadioSettingGroup("basic", "Basic Settings")
-        group = RadioSettingGroup("top", "All Settings", basic)
+        group = RadioSettings(basic)
 
         rs = RadioSetting("ch", "Channel Indication Mode",
                           RadioSettingValueBoolean(_settings.ch))
@@ -213,8 +219,8 @@ class ICQ7Radio(icf.IcomCloneModeRadio):
         basic.append(rs)
 
         rs = RadioSetting("autorp", "Auto Repeater Function",
-                          RadioSettingValueList(AUTORP_LIST,
-                              AUTORP_LIST[_settings.autorp]))
+                          RadioSettingValueList(
+                              AUTORP_LIST, AUTORP_LIST[_settings.autorp]))
         basic.append(rs)
 
         rs = RadioSetting("ritfunct", "RIT Runction",
@@ -230,27 +236,30 @@ class ICQ7Radio(icf.IcomCloneModeRadio):
         basic.append(rs)
 
         rs = RadioSetting("lockgroup", "Lock Group",
-                          RadioSettingValueList(LOCKGROUP_LIST,
+                          RadioSettingValueList(
+                              LOCKGROUP_LIST,
                               LOCKGROUP_LIST[_settings.lockgroup]))
         basic.append(rs)
 
         rs = RadioSetting("squelch", "Squelch",
-                          RadioSettingValueList(SQUELCH_LIST,
-                              SQUELCH_LIST[_settings.squelch]))
+                          RadioSettingValueList(
+                              SQUELCH_LIST, SQUELCH_LIST[_settings.squelch]))
         basic.append(rs)
 
         rs = RadioSetting("monitor", "Monitor Switch Function",
-                          RadioSettingValueList(MONITOR_LIST,
+                          RadioSettingValueList(
+                              MONITOR_LIST,
                               MONITOR_LIST[_settings.monitor]))
         basic.append(rs)
 
         rs = RadioSetting("light", "Display Backlighting",
-                          RadioSettingValueList(LIGHT_LIST,
-                              LIGHT_LIST[_settings.light]))
+                          RadioSettingValueList(
+                              LIGHT_LIST, LIGHT_LIST[_settings.light]))
         basic.append(rs)
 
         rs = RadioSetting("priority", "Priority Watch Operation",
-                          RadioSettingValueList(PRIORITY_LIST,
+                          RadioSettingValueList(
+                              PRIORITY_LIST,
                               PRIORITY_LIST[_settings.priority]))
         basic.append(rs)
 
@@ -259,23 +268,24 @@ class ICQ7Radio(icf.IcomCloneModeRadio):
         basic.append(rs)
 
         rs = RadioSetting("bnk_scan", "Memory Bank Scan Selection",
-                          RadioSettingValueList(BANKSCAN_LIST,
+                          RadioSettingValueList(
+                              BANKSCAN_LIST,
                               BANKSCAN_LIST[_settings.bnk_scan]))
         basic.append(rs)
 
         rs = RadioSetting("edge", "Band Edge Scan Selection",
-                          RadioSettingValueList(EDGE_LIST,
-                              EDGE_LIST[_settings.edge]))
+                          RadioSettingValueList(
+                              EDGE_LIST, EDGE_LIST[_settings.edge]))
         basic.append(rs)
 
         rs = RadioSetting("pause", "Scan Pause Time",
-                          RadioSettingValueList(PAUSE_LIST,
-                              PAUSE_LIST[_settings.pause]))
+                          RadioSettingValueList(
+                              PAUSE_LIST, PAUSE_LIST[_settings.pause]))
         basic.append(rs)
 
         rs = RadioSetting("resume", "Scan Resume Time",
-                          RadioSettingValueList(RESUME_LIST,
-                              RESUME_LIST[_settings.resume]))
+                          RadioSettingValueList(
+                              RESUME_LIST, RESUME_LIST[_settings.resume]))
         basic.append(rs)
 
         rs = RadioSetting("p_save", "Power Saver",
@@ -283,8 +293,8 @@ class ICQ7Radio(icf.IcomCloneModeRadio):
         basic.append(rs)
 
         rs = RadioSetting("ap_off", "Auto Power-off Function",
-                          RadioSettingValueList(APOFF_LIST,
-                              APOFF_LIST[_settings.ap_off]))
+                          RadioSettingValueList(
+                              APOFF_LIST, APOFF_LIST[_settings.ap_off]))
         basic.append(rs)
 
         rs = RadioSetting("speed", "Dial Speed Acceleration",
@@ -292,8 +302,8 @@ class ICQ7Radio(icf.IcomCloneModeRadio):
         basic.append(rs)
 
         rs = RadioSetting("d_sel", "Dial Select Step",
-                          RadioSettingValueList(D_SEL_LIST,
-                              D_SEL_LIST[_settings.d_sel]))
+                          RadioSettingValueList(
+                              D_SEL_LIST, D_SEL_LIST[_settings.d_sel]))
         basic.append(rs)
 
         return group
@@ -323,12 +333,11 @@ class ICQ7Radio(icf.IcomCloneModeRadio):
                         setting = element.get_name()
 
                     if element.has_apply_callback():
-                        print "Using apply callback"
+                        LOG.debug("Using apply callback")
                         element.run_apply_callback()
                     else:
-                        print "Setting %s = %s" % (setting, element.value)
+                        LOG.debug("Setting %s = %s" % (setting, element.value))
                         setattr(obj, setting, element.value)
                 except Exception, e:
-                    print element.get_name()
+                    LOG.debug(element.get_name())
                     raise
-
diff --git a/chirp/ict70.py b/chirp/drivers/ict70.py
similarity index 98%
rename from chirp/ict70.py
rename to chirp/drivers/ict70.py
index d035e14..4066c69 100644
--- a/chirp/ict70.py
+++ b/chirp/drivers/ict70.py
@@ -13,8 +13,8 @@
 # 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, icf, directory
-from chirp import bitwise
+from chirp.drivers import icf
+from chirp import chirp_common, directory, bitwise
 
 MEM_FORMAT = """
 struct {
@@ -69,6 +69,7 @@ POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5),
                 chirp_common.PowerLevel("Mid", watts=1.0),
                 ]
 
+
 class ICT70Bank(icf.IcomBank):
     """ICT70 bank"""
     def get_name(self):
@@ -79,6 +80,7 @@ class ICT70Bank(icf.IcomBank):
         _bank = self._model._radio._memobj.bank_names[self.index]
         _bank.name = name.ljust(6)[:6]
 
+
 @directory.register
 class ICT70Radio(icf.IcomCloneModeRadio):
     """Icom IC-T70"""
@@ -93,7 +95,7 @@ class ICT70Radio(icf.IcomCloneModeRadio):
 
     _num_banks = 26
     _bank_class = ICT70Bank
-    
+
     def _get_bank(self, loc):
         _bank = self._memobj.banks[loc]
         if _bank.bank != 0xFF:
@@ -115,7 +117,7 @@ class ICT70Radio(icf.IcomCloneModeRadio):
     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, 299)
@@ -171,7 +173,7 @@ class ICT70Radio(icf.IcomCloneModeRadio):
         mem.dtcs_polarity = DTCS_POLARITY[_mem.dtcs_polarity]
         mem.tmode = TMODES[_mem.tmode]
         mem.skip = (_psk & bit and "P") or (_skp & bit and "S") or ""
-        
+
         return mem
 
     def set_memory(self, mem):
@@ -219,4 +221,3 @@ class ICT70Radio(icf.IcomCloneModeRadio):
         else:
             _skp &= ~bit
             _psk &= ~bit
-        
diff --git a/chirp/ict7h.py b/chirp/drivers/ict7h.py
similarity index 97%
rename from chirp/ict7h.py
rename to chirp/drivers/ict7h.py
index 768fb34..5648261 100644
--- a/chirp/ict7h.py
+++ b/chirp/drivers/ict7h.py
@@ -13,8 +13,8 @@
 # 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, icf, directory
-from chirp import bitwise
+from chirp.drivers import icf
+from chirp import chirp_common, directory, bitwise
 
 mem_format = """
 struct {
diff --git a/chirp/ict8.py b/chirp/drivers/ict8.py
similarity index 97%
rename from chirp/ict8.py
rename to chirp/drivers/ict8.py
index c77d8f3..742bc73 100644
--- a/chirp/ict8.py
+++ b/chirp/drivers/ict8.py
@@ -13,7 +13,8 @@
 # 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, icf, util, directory
+from chirp.drivers import icf
+from chirp import chirp_common, util, directory
 from chirp import bitwise
 
 mem_format = """
@@ -41,6 +42,7 @@ struct flags flags[100];
 DUPLEX = ["", "", "-", "+"]
 TMODES = ["", "", "Tone", "TSQL"]
 
+
 @directory.register
 class ICT8ARadio(icf.IcomCloneModeRadio):
     """Icom IC-T8A"""
diff --git a/chirp/icw32.py b/chirp/drivers/icw32.py
similarity index 95%
rename from chirp/icw32.py
rename to chirp/drivers/icw32.py
index 2b028ad..dc08ea0 100644
--- a/chirp/icw32.py
+++ b/chirp/drivers/icw32.py
@@ -13,8 +13,12 @@
 # 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, icf, util, directory
-from chirp import bitwise
+import logging
+
+from chirp.drivers import icf
+from chirp import chirp_common, util, directory, bitwise
+
+LOG = logging.getLogger(__name__)
 
 MEM_FORMAT = """
 #seekto 0x%x;
@@ -61,12 +65,14 @@ struct {
 DUPLEX = ["", "", "-", "+"]
 TONE = ["", "", "Tone", "TSQL"]
 
+
 def _get_special():
     special = {}
     for i in range(0, 5):
         special["M%iA" % (i+1)] = 100 + i*2
         special["M%iB" % (i+1)] = 100 + i*2 + 1
-    return special            
+    return special
+
 
 @directory.register
 class ICW32ARadio(icf.IcomCloneModeRadio):
@@ -94,7 +100,7 @@ class ICW32ARadio(icf.IcomCloneModeRadio):
         rf.valid_tmodes = ["", "Tone", "TSQL"]
         rf.valid_name_length = 8
         rf.valid_special_chans = sorted(_get_special().keys())
-    
+
         rf.has_sub_devices = self.VARIANT == ""
         rf.has_ctone = True
         rf.has_dtcs = False
@@ -172,10 +178,10 @@ class ICW32ARadio(icf.IcomCloneModeRadio):
         _flg.am = mem.mode == "AM"
 
         if self._memobj.state.left_scanning:
-            print "Canceling scan on left VFO"
+            LOG.debug("Canceling scan on left VFO")
             self._memobj.state.left_scanning = 0
         if self._memobj.state.right_scanning:
-            print "Canceling scan on right VFO"
+            LOG.debug("Canceling scan on right VFO")
             self._memobj.state.right_scanning = 0
 
     def get_sub_devices(self):
@@ -187,12 +193,14 @@ class ICW32ARadio(icf.IcomCloneModeRadio):
             return False
         return filedata[-16:] == "IcomCloneFormat3"
 
+
 class ICW32ARadioVHF(ICW32ARadio):
     """ICW32 VHF subdevice"""
     VARIANT = "VHF"
     _limits = (118000000, 174000000)
     _mem_positions = (0x0000, 0x0DC0)
 
+
 class ICW32ARadioUHF(ICW32ARadio):
     """ICW32 UHF subdevice"""
     VARIANT = "UHF"
diff --git a/chirp/icx8x.py b/chirp/drivers/icx8x.py
similarity index 94%
rename from chirp/icx8x.py
rename to chirp/drivers/icx8x.py
index b9ea35c..6a5c8b0 100644
--- a/chirp/icx8x.py
+++ b/chirp/drivers/icx8x.py
@@ -13,7 +13,13 @@
 # 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, icf, icx8x_ll, errors, directory
+import logging
+
+from chirp.drivers import icf, icx8x_ll
+from chirp import chirp_common, errors, directory
+
+LOG = logging.getLogger(__name__)
+
 
 def _isuhf(pipe):
     try:
@@ -23,10 +29,11 @@ def _isuhf(pipe):
     except:
         raise errors.RadioError("Unable to probe radio band")
 
-    print "Radio is a %s82" % (uhf and "U" or "V")
+    LOG.debug("Radio is a %s82" % (uhf and "U" or "V"))
 
     return uhf
 
+
 @directory.register
 class ICx8xRadio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
     """Icom IC-V/U82"""
@@ -99,7 +106,7 @@ class ICx8xRadio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
         # that flag.
         if isinstance(pipe, str):
             self._isuhf = (ord(self._mmap[0x1930]) != 0)
-            #print "Found %s image" % (self.isUHF and "UHF" or "VHF")
+            # LOG.debug("Found %s image" % (self.isUHF and "UHF" or "VHF"))
         else:
             self._isuhf = None
 
@@ -125,8 +132,8 @@ class ICx8xRadio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
             try:
                 number = icx8x_ll.ICx8x_SPECIAL[number]
             except KeyError:
-                raise errors.InvalidMemoryLocation("Unknown channel %s" % \
-                                                       number)
+                raise errors.InvalidMemoryLocation("Unknown channel %s" %
+                                                   number)
 
         return icx8x_ll.get_memory(self._mmap, number, base)
 
diff --git a/chirp/icx8x_ll.py b/chirp/drivers/icx8x_ll.py
similarity index 92%
rename from chirp/icx8x_ll.py
rename to chirp/drivers/icx8x_ll.py
index 92f2d9c..ffbfa12 100644
--- a/chirp/icx8x_ll.py
+++ b/chirp/drivers/icx8x_ll.py
@@ -20,32 +20,32 @@ from chirp.memmap import MemoryMap
 from chirp.chirp_common import to_MHz
 
 POS_FREQ_START = 0
-POS_FREQ_END   = 2
-POS_OFFSET     = 2
+POS_FREQ_END = 2
+POS_OFFSET = 2
 POS_NAME_START = 4
-POS_NAME_END   = 9
-POS_RTONE      = 9
-POS_CTONE      = 10
-POS_DTCS       = 11
-POS_TUNE_STEP  = 17
-POS_TMODE      = 21
-POS_MODE       = 21
-POS_MULT_FLAG  = 21
-POS_DTCS_POL   = 22
-POS_DUPLEX     = 22
-POS_DIG        = 23
-POS_TXI        = 23
-
-POS_FLAGS_START= 0x1370
-POS_MYCALL     = 0x15E0
-POS_URCALL     = 0x1640
-POS_RPCALL     = 0x16A0
-POS_RP2CALL    = 0x1700
-
-MEM_LOC_SIZE   = 24
-
-ICx8x_SPECIAL = { "C" : 206 }
-ICx8x_SPECIAL_REV = { 206 : "C" }
+POS_NAME_END = 9
+POS_RTONE = 9
+POS_CTONE = 10
+POS_DTCS = 11
+POS_TUNE_STEP = 17
+POS_TMODE = 21
+POS_MODE = 21
+POS_MULT_FLAG = 21
+POS_DTCS_POL = 22
+POS_DUPLEX = 22
+POS_DIG = 23
+POS_TXI = 23
+
+POS_FLAGS_START = 0x1370
+POS_MYCALL = 0x15E0
+POS_URCALL = 0x1640
+POS_RPCALL = 0x16A0
+POS_RP2CALL = 0x1700
+
+MEM_LOC_SIZE = 24
+
+ICx8x_SPECIAL = {"C": 206}
+ICx8x_SPECIAL_REV = {206: "C"}
 
 for i in range(0, 3):
     idA = "%iA" % i
@@ -56,10 +56,12 @@ for i in range(0, 3):
     ICx8x_SPECIAL_REV[num] = idA
     ICx8x_SPECIAL_REV[num+1] = idB
 
+
 def bank_name(index):
     char = chr(ord("A") + index)
     return "BANK-%s" % char
 
+
 def get_freq(mmap, base):
     if (ord(mmap[POS_MULT_FLAG]) & 0x80) == 0x80:
         mult = 6250
@@ -70,6 +72,7 @@ def get_freq(mmap, base):
 
     return (val * mult) + to_MHz(base)
 
+
 def set_freq(mmap, freq, base):
     tflag = ord(mmap[POS_MULT_FLAG]) & 0x7F
 
@@ -84,65 +87,78 @@ def set_freq(mmap, freq, base):
     mmap[POS_MULT_FLAG] = tflag
     mmap[POS_FREQ_START] = struct.pack("<H", value)
 
+
 def get_name(mmap):
     return mmap[POS_NAME_START:POS_NAME_END].strip()
 
+
 def set_name(mmap, name):
     mmap[POS_NAME_START] = name.ljust(5)[:5]
 
+
 def get_rtone(mmap):
     idx, = struct.unpack("B", mmap[POS_RTONE])
 
     return chirp_common.TONES[idx]
 
+
 def set_rtone(mmap, tone):
     mmap[POS_RTONE] = chirp_common.TONES.index(tone)
 
+
 def get_ctone(mmap):
     idx, = struct.unpack("B", mmap[POS_CTONE])
 
     return chirp_common.TONES[idx]
 
+
 def set_ctone(mmap, tone):
     mmap[POS_CTONE] = chirp_common.TONES.index(tone)
 
+
 def get_dtcs(mmap):
     idx, = struct.unpack("B", mmap[POS_DTCS])
 
     return chirp_common.DTCS_CODES[idx]
 
+
 def set_dtcs(mmap, code):
     mmap[POS_DTCS] = chirp_common.DTCS_CODES.index(code)
 
+
 def get_dtcs_polarity(mmap):
     val = struct.unpack("B", mmap[POS_DTCS_POL])[0] & 0xC0
 
     pol_values = {
-        0x00 : "NN",
-        0x40 : "NR",
-        0x80 : "RN",
-        0xC0 : "RR" }
+        0x00: "NN",
+        0x40: "NR",
+        0x80: "RN",
+        0xC0: "RR"}
 
     return pol_values[val]
 
+
 def set_dtcs_polarity(mmap, polarity):
     val = struct.unpack("B", mmap[POS_DTCS_POL])[0] & 0x3F
-    pol_values = { "NN" : 0x00,
-                   "NR" : 0x40,
-                   "RN" : 0x80,
-                   "RR" : 0xC0 }
+    pol_values = {"NN": 0x00,
+                  "NR": 0x40,
+                  "RN": 0x80,
+                  "RR": 0xC0}
     val |= pol_values[polarity]
 
     mmap[POS_DTCS_POL] = val
 
+
 def get_dup_offset(mmap):
     val = struct.unpack("<H", mmap[POS_OFFSET:POS_OFFSET+2])[0]
     return val * 5000
 
+
 def set_dup_offset(mmap, offset):
     val = struct.pack("<H", offset / 5000)
     mmap[POS_OFFSET] = val
 
+
 def get_duplex(mmap):
     val = struct.unpack("B", mmap[POS_DUPLEX])[0] & 0x30
 
@@ -153,6 +169,7 @@ def get_duplex(mmap):
     else:
         return ""
 
+
 def set_duplex(mmap, duplex):
     val = struct.unpack("B", mmap[POS_DUPLEX])[0] & 0xCF
 
@@ -163,6 +180,7 @@ def set_duplex(mmap, duplex):
 
     mmap[POS_DUPLEX] = val
 
+
 def get_tone_enabled(mmap):
     val = struct.unpack("B", mmap[POS_TMODE])[0] & 0x03
 
@@ -175,6 +193,7 @@ def get_tone_enabled(mmap):
     else:
         return ""
 
+
 def set_tone_enabled(mmap, tmode):
     val = struct.unpack("B", mmap[POS_TMODE])[0] & 0xFC
 
@@ -187,6 +206,7 @@ def set_tone_enabled(mmap, tmode):
 
     mmap[POS_TMODE] = val
 
+
 def get_tune_step(mmap):
     tsidx = struct.unpack("B", mmap[POS_TUNE_STEP])[0] & 0xF0
     tsidx >>= 4
@@ -196,8 +216,9 @@ def get_tune_step(mmap):
     try:
         return icx8x_ts[tsidx]
     except IndexError:
-        raise errors.InvalidDataError("TS index %i out of range (%i)" % (tsidx,
-                                                                         len(icx8x_ts)))
+        raise errors.InvalidDataError("TS index %i out of range (%i)" %
+                                      (tsidx, len(icx8x_ts)))
+
 
 def set_tune_step(mmap, tstep):
     val = struct.unpack("B", mmap[POS_TUNE_STEP])[0] & 0x0F
@@ -207,7 +228,8 @@ def set_tune_step(mmap, tstep):
     tsidx = icx8x_ts.index(tstep)
     val |= (tsidx << 4)
 
-    mmap[POS_TUNE_STEP] = val    
+    mmap[POS_TUNE_STEP] = val
+
 
 def get_mode(mmap):
     val = struct.unpack("B", mmap[POS_DIG])[0] & 0x08
@@ -222,6 +244,7 @@ def get_mode(mmap):
     else:
         return "FM"
 
+
 def set_mode(mmap, mode):
     dig = struct.unpack("B", mmap[POS_DIG])[0] & 0xF7
 
@@ -239,12 +262,14 @@ def set_mode(mmap, mode):
     mmap[POS_DIG] = dig
     mmap[POS_MODE] = val
 
+
 def is_used(mmap, number):
     if number == ICx8x_SPECIAL["C"]:
         return True
 
     return (ord(mmap[POS_FLAGS_START + number]) & 0x20) == 0
 
+
 def set_used(mmap, number, used=True):
     if number == ICx8x_SPECIAL["C"]:
         return
@@ -256,6 +281,7 @@ def set_used(mmap, number, used=True):
 
     mmap[POS_FLAGS_START + number] = val
 
+
 def get_skip(mmap, number):
     val = struct.unpack("B", mmap[POS_FLAGS_START + number])[0] & 0x10
 
@@ -264,6 +290,7 @@ def get_skip(mmap, number):
     else:
         return ""
 
+
 def set_skip(mmap, number, skip):
     if skip == "P":
         raise errors.InvalidDataError("PSKIP not supported by this model")
@@ -275,11 +302,13 @@ def set_skip(mmap, number, skip):
 
     mmap[POS_FLAGS_START + number] = val
 
+
 def get_call_indices(mmap):
     return ord(mmap[18]) & 0x0F, \
         (ord(mmap[19]) & 0xF0) >> 4, \
         ord(mmap[19]) & 0x0F
 
+
 def set_call_indices(_map, mmap, urcall, r1call, r2call):
     ulist = []
     for i in range(0, 6):
@@ -318,13 +347,16 @@ def set_call_indices(_map, mmap, urcall, r1call, r2call):
 
 # --
 
+
 def get_mem_offset(number):
     return number * MEM_LOC_SIZE
 
+
 def get_raw_memory(mmap, number):
     offset = get_mem_offset(number)
     return MemoryMap(mmap[offset:offset + MEM_LOC_SIZE])
 
+
 def get_bank(mmap, number):
     val = ord(mmap[POS_FLAGS_START + number]) & 0x0F
 
@@ -333,6 +365,7 @@ def get_bank(mmap, number):
     else:
         return val
 
+
 def set_bank(mmap, number, bank):
     if bank > 9:
         raise errors.InvalidDataError("Invalid bank number %i" % bank)
@@ -344,7 +377,8 @@ def set_bank(mmap, number, bank):
 
     val = ord(mmap[POS_FLAGS_START + number]) & 0xF0
     val |= index
-    mmap[POS_FLAGS_START + number] = val    
+    mmap[POS_FLAGS_START + number] = val
+
 
 def _get_memory(_map, mmap, base):
     if get_mode(mmap) == "DV":
@@ -370,6 +404,7 @@ def _get_memory(_map, mmap, base):
 
     return mem
 
+
 def get_memory(_map, number, base):
     if not is_used(_map, number):
         mem = chirp_common.Memory()
@@ -391,11 +426,13 @@ def get_memory(_map, number, base):
 
     return mem
 
+
 def clear_tx_inhibit(mmap):
     txi = struct.unpack("B", mmap[POS_TXI])[0]
     txi |= 0x40
     mmap[POS_TXI] = txi
 
+
 def set_memory(_map, memory, base):
     mmap = get_raw_memory(_map, memory.number)
 
@@ -428,14 +465,17 @@ def set_memory(_map, memory, base):
 
     return _map
 
+
 def erase_memory(_map, number):
     set_used(_map, number, False)
 
     return _map
 
+
 def call_location(base, index):
     return base + (16 * index)
 
+
 def get_urcall(mmap, index):
     if index > 5:
         raise errors.InvalidDataError("URCALL index %i must be <= 5" % index)
@@ -444,6 +484,7 @@ def get_urcall(mmap, index):
 
     return mmap[start:start+8].rstrip()
 
+
 def get_rptcall(mmap, index):
     if index > 5:
         raise errors.InvalidDataError("RPTCALL index %i must be <= 5" % index)
@@ -452,6 +493,7 @@ def get_rptcall(mmap, index):
 
     return mmap[start:start+8].rstrip()
 
+
 def get_mycall(mmap, index):
     if index > 5:
         raise errors.InvalidDataError("MYCALL index %i must be <= 5" % index)
@@ -460,6 +502,7 @@ def get_mycall(mmap, index):
 
     return mmap[start:start+8].rstrip()
 
+
 def set_urcall(mmap, index, call):
     if index > 5:
         raise errors.InvalidDataError("URCALL index %i must be <= 5" % index)
@@ -467,9 +510,10 @@ def set_urcall(mmap, index, call):
     start = call_location(POS_URCALL, index)
 
     mmap[start] = call.ljust(12)
-    
+
     return mmap
 
+
 def set_rptcall(mmap, index, call):
     if index > 5:
         raise errors.InvalidDataError("RPTCALL index %i must be <= 5" % index)
@@ -479,9 +523,10 @@ def set_rptcall(mmap, index, call):
 
     start = call_location(POS_RP2CALL, index)
     mmap[start] = call.ljust(12)
-    
+
     return mmap
 
+
 def set_mycall(mmap, index, call):
     if index > 5:
         raise errors.InvalidDataError("MYCALL index %i must be <= 5" % index)
@@ -489,5 +534,5 @@ def set_mycall(mmap, index, call):
     start = call_location(POS_MYCALL, index)
 
     mmap[start] = call.ljust(12)
-    
+
     return mmap
diff --git a/chirp/id31.py b/chirp/drivers/id31.py
similarity index 96%
rename from chirp/id31.py
rename to chirp/drivers/id31.py
index 128d79d..9883682 100644
--- a/chirp/id31.py
+++ b/chirp/drivers/id31.py
@@ -13,7 +13,8 @@
 # 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 directory, icf, bitwise, chirp_common
+from chirp.drivers import icf
+from chirp import directory, bitwise, chirp_common
 
 MEM_FORMAT = """
 struct {
@@ -93,6 +94,7 @@ DTCS_POLARITY = ["NN", "NR", "RN", "RR"]
 TUNING_STEPS = [5.0, 6.25, 0, 0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0,
                 100.0, 125.0, 200.0]
 
+
 def _decode_call(_call):
     # Why Icom, why?
     call = ""
@@ -106,6 +108,7 @@ def _decode_call(_call):
     call += chr(acc)
     return call
 
+
 def _encode_call(call):
     _call = [0x00] * 7
     for i in range(0, 7):
@@ -114,9 +117,10 @@ def _encode_call(call):
             _call[i-1] |= (val & 0xFF00) >> 8
         _call[i] = val
     _call[6] |= (ord(call[7]) & 0x7F)
-        
+
     return _call
 
+
 def _get_freq(_mem):
     freq = int(_mem.freq)
     offs = int(_mem.offset)
@@ -130,6 +134,7 @@ def _get_freq(_mem):
 
     return (freq * mult), (offs * mult)
 
+
 def _set_freq(_mem, freq, offset):
     if chirp_common.is_fractional_step(freq):
         mult = 6250
@@ -141,6 +146,7 @@ def _set_freq(_mem, freq, offset):
     _mem.freq = (freq / mult) | flag
     _mem.offset = (offset / mult)
 
+
 class ID31Bank(icf.IcomBank):
     """A ID-31 Bank"""
     def get_name(self):
@@ -151,6 +157,7 @@ class ID31Bank(icf.IcomBank):
         _banks = self._model._radio._memobj.bank_names
         _banks[self.index].name = str(name).ljust(16)[:16]
 
+
 @directory.register
 class ID31Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
     """Icom ID-31"""
@@ -247,9 +254,9 @@ class ID31Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
 
         if _psk & bit:
             mem.skip = "P"
-        if _skp & bit:
+        elif _skp & bit:
             mem.skip = "S"
-            
+
         return mem
 
     def set_memory(self, memory):
@@ -276,8 +283,8 @@ class ID31Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
         _mem.dtcs = chirp_common.DTCS_CODES.index(memory.dtcs)
         _mem.dtcs_polarity = DTCS_POLARITY.index(memory.dtcs_polarity)
         _mem.tune_step = TUNING_STEPS.index(memory.tuning_step)
-        _mem.mode = next(i for i, mode in self.MODES.items() \
-                            if mode == memory.mode)
+        _mem.mode = next(i for i, mode in self.MODES.items()
+                         if mode == memory.mode)
 
         if isinstance(memory, chirp_common.DVMemory):
             _mem.urcall = _encode_call(memory.dv_urcall.ljust(8))
@@ -287,15 +294,15 @@ class ID31Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
             raise Exception("BUG")
 
         if memory.skip == "S":
-            _skp |=  bit
+            _skp |= bit
             _psk &= ~bit
         elif memory.skip == "P":
-            _skp &= ~bit
-            _psk |=  bit
+            _skp |= bit
+            _psk |= bit
         else:
             _skp &= ~bit
             _psk &= ~bit
-            
+
     def get_urcall_list(self):
         calls = []
         for i in range(0, 200):
diff --git a/chirp/id51.py b/chirp/drivers/id51.py
similarity index 64%
rename from chirp/id51.py
rename to chirp/drivers/id51.py
index 4b72c44..058f120 100644
--- a/chirp/id51.py
+++ b/chirp/drivers/id51.py
@@ -12,8 +12,10 @@
 #
 # 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 chirp import directory, bitwise, id31
+from chirp.drivers import id31
+from chirp import directory, bitwise
 
 MEM_FORMAT = """
 struct {
@@ -85,12 +87,13 @@ struct {
 } urcall[200];
 
 """
+LOG = logging.getLogger(__name__)
 
 
 @directory.register
 class ID51Radio(id31.ID31Radio):
     """Icom ID-51"""
-    MODEL = "ID-51A"
+    MODEL = "ID-51"
 
     _memsize = 0x1FB40
     _model = "\x33\x90\x00\x01"
@@ -100,6 +103,28 @@ class ID51Radio(id31.ID31Radio):
 
     MODES = {0: "FM", 1: "NFM", 3: "AM", 5: "DV"}
 
+    @classmethod
+    def match_model(cls, filedata, filename):
+        """Given contents of a stored file (@filedata), return True if
+        this radio driver handles the represented model"""
+
+        # The default check for ICOM is just to check memory size
+        # Since the ID-51 and ID-51 Plus/Anniversary have exactly
+        # the same memory size, we need to do a more detailed check.
+        if len(filedata) == cls._memsize:
+            LOG.debug('File has correct memory size, '
+                      'checking 20 bytes at offset 0x1AF40')
+            snip = filedata[0x1AF40:0x1AF60]
+            if snip == ('\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
+                        '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
+                        '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
+                        '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'):
+                LOG.debug('bytes matched ID-51 Signature')
+                return True
+            else:
+                LOG.debug('bytes did not match ID-51 Signature')
+        return False
+
     def get_features(self):
         rf = super(ID51Radio, self).get_features()
         rf.valid_bands = [(108000000, 174000000), (400000000, 479000000)]
diff --git a/chirp/drivers/id51plus.py b/chirp/drivers/id51plus.py
new file mode 100644
index 0000000..7157be9
--- /dev/null
+++ b/chirp/drivers/id51plus.py
@@ -0,0 +1,171 @@
+# Copyright 2015 Eric Dropps <kc1ckh at kc1ckh.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/>.
+import logging
+
+from chirp.drivers import id31
+from chirp import directory, bitwise
+
+LOG = logging.getLogger(__name__)
+
+MEM_FORMAT = """
+struct {
+  u24 freq;
+  u16 offset;
+  u16 rtone:6,
+      ctone:6,
+      unknown2:1,
+      mode:3;
+  u8 dtcs;
+  u8 tune_step:4,
+     unknown5:4;
+  u8 unknown4;
+  u8 tmode:4,
+     duplex:2,
+     dtcs_polarity:2;
+  char name[16];
+  u8 unknown13;
+  u8 urcall[7];
+  u8 rpt1call[7];
+  u8 rpt2call[7];
+} memory[500];
+
+#seekto 0x6A40;
+u8 used_flags[70];
+
+#seekto 0x6A86;
+u8 skip_flags[69];
+
+#seekto 0x6ACB;
+u8 pskp_flags[69];
+
+#seekto 0x6B40;
+struct {
+  u8 unknown:3,
+     bank:5;
+  u8 index;
+} banks[500];
+
+#seekto 0x6FD0;
+struct {
+  char name[16];
+} bank_names[26];
+
+
+#seekto 0xA8C0;
+struct {
+  u24 freq;
+  u16 offset; 
+  u8 unknown1[4];
+  u8 call[7];
+  char name[16];
+  char subname[8];
+  u8 unknown3[10];
+} repeaters[750];
+
+#seekto 0x1384E;
+struct {
+  u8 call[7];
+} rptcall[750];
+
+#seekto 0x14FBE;
+struct {
+ char name[16];
+} rptgroup_names[30];
+
+#seekto 0x1519E;
+struct {
+  char call[8];
+  char tag[4];
+} mycall[6];
+
+#seekto 0x151E6;
+struct {
+  char call[8];
+} urcall[200];
+
+#seekto 0x15826;
+struct {
+  char name[16];
+} urcallname[200];
+"""
+
+ at directory.register
+class ID51PLUSRadio(id31.ID31Radio):
+    """Icom ID-51 Plus/50th Anniversary"""
+    MODEL = "ID-51 Plus"
+
+    _memsize = 0x1FB40
+    _model = "\x33\x90\x00\x02"
+    _endframe = "Icom Inc\x2E\x44\x41"
+    _bank_class = id31.ID31Bank
+    _ranges = [(0x00000, 0x1FB40, 32)]
+
+    MODES = {0: "FM", 1: "NFM", 3: "AM", 5: "DV"}
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        """Given contents of a stored file (@filedata), return True if
+        this radio driver handles the represented model"""
+
+        # The default check for ICOM is just to check memory size
+        # Since the ID-51 and ID-51 Plus/Anniversary have exactly
+        # the same memory size, we need to do a more detailed check.
+        if len(filedata) == cls._memsize:
+            LOG.debug('File has correct memory size, '
+                      'checking 20 bytes at offset 0x1AF40')
+            snip = filedata[0x1AF40:0x1AF60]
+            if snip != ('\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
+                        '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
+                        '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
+                        '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'):
+                LOG.debug('bytes matched ID-51 Plus Signature')
+                return True
+            else:
+                LOG.debug('bytes did not match ID-51 Plus Signature')
+        return False
+
+    def _get_bank(self, loc):
+        _bank = self._memobj.banks[loc]
+        LOG.debug("Bank Value for location %s is %s" % (loc, _bank.bank))
+        if _bank.bank == 0x1F:
+            return None         
+        else:
+            return _bank.bank
+
+    def _set_bank(self, loc, bank):
+        _bank = self._memobj.banks[loc]
+        if bank is None:
+            _bank.bank = 0x1F
+        else:
+            _bank.bank = bank
+
+    def get_features(self):
+        rf = super(ID51PLUSRadio, self).get_features()
+        rf.valid_bands = [(108000000, 174000000), (380000000, 479000000)]
+        return rf
+
+    def get_repeater_call_list(self):
+        calls = []
+        # Unlike previos DStar radios, there is not a seperate repeater
+        # callsign list. It's only the DV Memory banks.
+        for repeater in self._memobj.repeaters:
+            call = id31._decode_call(repeater.call)
+            if call == "CALLSIGN":
+                call = ""
+            calls.append(call.rstrip())
+        return calls
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
diff --git a/chirp/id800.py b/chirp/drivers/id800.py
similarity index 94%
rename from chirp/id800.py
rename to chirp/drivers/id800.py
index 56350de..f71ec3b 100644
--- a/chirp/id800.py
+++ b/chirp/drivers/id800.py
@@ -13,14 +13,14 @@
 # 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, icf, errors, directory
-from chirp import bitwise
+from chirp.drivers import icf
+from chirp import chirp_common, errors, directory, bitwise
 
 MEM_FORMAT = """
 #seekto 0x0020;
 struct {
   u24 freq;
-  u16 offset;  
+  u16 offset;
   u8  unknown0:2,
       rtone:6;
   u8  duplex:2,
@@ -46,7 +46,7 @@ struct {
      digital_code:7;
   u8 urcall;
   u8 rpt1call;
-  u8 rpt2call;  
+  u8 rpt2call;
   u8 unknown7:1,
      mode:3,
      unknown8:4;
@@ -84,12 +84,12 @@ DTCS_POL = ["NN", "NR", "RN", "RR"]
 STEPS = [5.0, 10.0, 12.5, 15, 20.0, 25.0, 30.0, 50.0, 100.0, 200.0, 6.25]
 
 ID800_SPECIAL = {
-    "C2" : 510,
-    "C1" : 511,
+    "C2": 510,
+    "C1": 511,
     }
 ID800_SPECIAL_REV = {
-    510 : "C2",
-    511 : "C1",
+    510: "C2",
+    511: "C1",
     }
 
 for i in range(0, 5):
@@ -104,6 +104,7 @@ for i in range(0, 5):
 ALPHA_CHARSET = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 NUMERIC_CHARSET = "0123456789+-=*/()|"
 
+
 def get_name(_mem):
     """Decode the name from @_mem"""
     def _get_char(val):
@@ -122,6 +123,7 @@ def get_name(_mem):
 
     return name.rstrip()
 
+
 def set_name(_mem, name):
     """Encode @name in @_mem"""
     def _get_index(char):
@@ -145,6 +147,7 @@ def set_name(_mem, name):
     _mem.name5 = _get_index(name[4])
     _mem.name6 = _get_index(name[5])
 
+
 @directory.register
 class ID800v2Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
     """Icom ID800"""
@@ -192,10 +195,10 @@ class ID800v2Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
                (0x37E0, 0x3898, 32),
                (0x3898, 0x389A,  2),
 
-               (0x38A8, 0x38C0, 16),]
+               (0x38A8, 0x38C0, 16), ]
 
-    MYCALL_LIMIT  = (1, 7)
-    URCALL_LIMIT  = (1, 99)
+    MYCALL_LIMIT = (1, 7)
+    URCALL_LIMIT = (1, 99)
     RPTCALL_LIMIT = (1, 59)
 
     def _get_bank(self, loc):
@@ -235,10 +238,10 @@ class ID800v2Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
     def get_memory(self, number):
         if isinstance(number, str):
             try:
-                number = ID800_SPECIAL[number] + 1 # Because we subtract below
+                number = ID800_SPECIAL[number] + 1  # Because we subtract below
             except KeyError:
-                raise errors.InvalidMemoryLocation("Unknown channel %s" % \
-                                                       number)
+                raise errors.InvalidMemoryLocation("Unknown channel %s" %
+                                                   number)
 
         _mem = self._memobj.memory[number-1]
         _flg = self._memobj.flags[number-1]
@@ -351,25 +354,25 @@ class ID800v2Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
             calls.append(str(self._memobj.mycalls[i-1].call).rstrip())
 
         return calls
-    
+
     def set_urcall_list(self, calls):
         for i in range(*self.URCALL_LIMIT):
             try:
-                call = calls[i].upper() # Skip the implicit CQCQCQ
+                call = calls[i].upper()  # Skip the implicit CQCQCQ
             except IndexError:
                 call = " " * 8
-            
+
             self._memobj.urcalls[i-1].call = call.ljust(8)[:8]
 
     def set_repeater_call_list(self, calls):
         for i in range(*self.RPTCALL_LIMIT):
             try:
-                call = calls[i].upper() # Skip the implicit blank
+                call = calls[i].upper()  # Skip the implicit blank
             except IndexError:
                 call = " " * 8
 
             self._memobj.rptcalls[i-1].call = call.ljust(8)[:8]
-        
+
     def set_mycall_list(self, calls):
         for i in range(*self.MYCALL_LIMIT):
             try:
diff --git a/chirp/id880.py b/chirp/drivers/id880.py
similarity index 92%
rename from chirp/id880.py
rename to chirp/drivers/id880.py
index b6c8b39..713258d 100644
--- a/chirp/id880.py
+++ b/chirp/drivers/id880.py
@@ -13,8 +13,8 @@
 # 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, icf, directory
-from chirp import bitwise
+from chirp.drivers import icf
+from chirp import chirp_common, directory, bitwise
 
 MEM_FORMAT = """
 struct {
@@ -80,10 +80,11 @@ u8 name_flags[132];
 
 TMODES = ["", "Tone", "?2", "TSQL", "DTCS", "TSQL-R", "DTCS-R", ""]
 DUPLEX = ["", "-", "+", "?3"]
-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]
+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]
+
 
 def decode_call(sevenbytes):
     """Decode a callsign from a packed region @sevenbytes"""
@@ -96,16 +97,17 @@ def decode_call(sevenbytes):
     for byte in [ord(x) for x in sevenbytes]:
         i += 1
 
-        mask = (1 << i) - 1           # Mask is 0x01, 0x03, 0x07, etc
+        # Mask is 0x01, 0x03, 0x07, etc
+        mask = (1 << i) - 1
 
-        code = (byte >> i) | rem      # Code gets the upper bits of remainder
-                                      # plus all but the i lower bits of this
-                                      # byte
+        # Code gets the upper bits of remainder plus all but the i lower
+        # bits of this byte
+        code = (byte >> i) | rem
         call += chr(code)
 
-        rem = (byte & mask) << 7 - i  # Remainder for next time are the masked
-                                      # bits, moved to the high places for the
-                                      # next round
+        # Remainder for next time are the masked bits, moved to the high
+        # places for the next round
+        rem = (byte & mask) << 7 - i
 
     # After seven trips gathering overflow bits, we chould have seven
     # left, which is the final character
@@ -113,11 +115,12 @@ def decode_call(sevenbytes):
 
     return call.rstrip()
 
+
 def encode_call(call):
     """Encode @call into a 7-byte region"""
     call = call.ljust(8)
     buf = []
-    
+
     for i in range(0, 8):
         byte = ord(call[i])
         if i > 0:
@@ -132,6 +135,7 @@ def encode_call(call):
 
     return "".join([chr(x) for x in buf[:7]])
 
+
 def _get_freq(_mem):
     val = int(_mem.freq)
 
@@ -144,6 +148,7 @@ def _get_freq(_mem):
 
     return (val * mult)
 
+
 def _set_freq(_mem, freq):
     if chirp_common.is_fractional_step(freq):
         mult = 6250
@@ -154,9 +159,11 @@ def _set_freq(_mem, freq):
 
     _mem.freq = (freq / mult) | flag
 
+
 def _wipe_memory(mem, char):
     mem.set_raw(char * (mem.size() / 8))
 
+
 class ID880Bank(icf.IcomNamedBank):
     """ID880 Bank"""
     def get_name(self):
@@ -167,6 +174,7 @@ class ID880Bank(icf.IcomNamedBank):
         _bank = self._model._radio._memobj.bank_names[self.index]
         _bank.name = name.ljust(6)[:6]
 
+
 @directory.register
 class ID880Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
     """Icom ID880"""
@@ -206,7 +214,7 @@ class ID880Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
     def _get_bank_index(self, loc):
         _bank = self._memobj.bank_info[loc]
         return _bank.index
-        
+
     def _set_bank_index(self, loc, index):
         _bank = self._memobj.bank_info[loc]
         _bank.index = index
@@ -231,7 +239,7 @@ class ID880Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
         rf.valid_skips = ["", "S", "P"]
         rf.valid_name_length = 8
         rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + \
-                              "!\"#$%&'()*+,-./:;<=>?@[\]^"
+            "!\"#$%&'()*+,-./:;<=>?@[\]^"
         rf.memory_bounds = (0, 999)
         return rf
 
@@ -265,7 +273,7 @@ class ID880Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
             elif _pskip & bitpos:
                 mem.skip = "P"
         else:
-            pass # FIXME: Special memories
+            pass  # FIXME: Special memories
 
         if not is_used:
             mem.empty = True
@@ -329,7 +337,7 @@ class ID880Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
             _mem.urcall = encode_call(mem.dv_urcall)
             _mem.r1call = encode_call(mem.dv_rpt1call)
             _mem.r2call = encode_call(mem.dv_rpt2call)
-            
+
         if mem.number < 1000:
             skip = self._memobj.skip_flags[bytepos]
             pskip = self._memobj.pskip_flags[bytepos]
@@ -377,7 +385,8 @@ class ID880Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
         # destination, but it should suffice in most cases until we get
         # a rich container file format
         return len(filedata) == cls._memsize and "API880," in filedata
-        
+
+
 # This radio isn't really supported yet and detects as a conflict with
 # the ID-880. So, don't register right now
 @directory.register
@@ -386,11 +395,10 @@ class ID80Radio(ID880Radio):
     MODEL = "ID-80H"
 
     _model = "\x31\x55\x00\x01"
-    
+
     @classmethod
     def match_model(cls, filedata, filename):
         # This is a horrid hack, given that people can change the GPS-A
         # destination, but it should suffice in most cases until we get
         # a rich container file format
         return len(filedata) == cls._memsize and "API80," in filedata
-        
diff --git a/chirp/idrp.py b/chirp/drivers/idrp.py
similarity index 91%
rename from chirp/idrp.py
rename to chirp/drivers/idrp.py
index e20ba07..f7c115d 100644
--- a/chirp/idrp.py
+++ b/chirp/drivers/idrp.py
@@ -14,11 +14,12 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import serial
+import logging
 
-from chirp import chirp_common, errors
-from chirp import util
+from chirp import chirp_common, errors, util
+
+LOG = logging.getLogger(__name__)
 
-DEBUG_IDRP = False
 
 def parse_frames(buf):
     """Parse frames from the radio"""
@@ -29,7 +30,7 @@ def parse_frames(buf):
             start = buf.index("\xfe\xfe")
             end = buf[start:].index("\xfd") + start + 1
         except Exception:
-            print "Unable to parse frames"
+            LOG.error("Unable to parse frames")
             break
 
         frames.append(buf[start:end])
@@ -37,6 +38,7 @@ def parse_frames(buf):
 
     return frames
 
+
 def send(pipe, buf):
     """Send data in @buf to @pipe"""
     pipe.write("\xfe\xfe%s\xfd" % buf)
@@ -49,15 +51,16 @@ def send(pipe, buf):
             break
 
         data += buf
-        if DEBUG_IDRP:
-            print "Got: \n%s" % util.hexprint(buf)
+        LOG.debug("Got: \n%s" % util.hexprint(buf))
 
     return parse_frames(data)
 
+
 def send_magic(pipe):
     """Send the magic wakeup call to @pipe"""
     send(pipe, ("\xfe" * 15) + "\x01\x7f\x19")
 
+
 def drain(pipe):
     """Chew up any data waiting on @pipe"""
     while True:
@@ -65,6 +68,7 @@ def drain(pipe):
         if not buf:
             break
 
+
 def set_freq(pipe, freq):
     """Set the frequency of the radio on @pipe to @freq"""
     freqbcd = util.bcd_encode(freq, bigendian=False, width=9)
@@ -79,7 +83,8 @@ def set_freq(pipe, freq):
                 return True
 
     raise errors.InvalidDataError("Repeater reported error")
-    
+
+
 def get_freq(pipe):
     """Get the frequency of the radio attached to @pipe"""
     buf = "\x01\x7f\x1a\x09"
@@ -97,8 +102,7 @@ def get_freq(pipe):
                                                  ord(els[2]),
                                                  ord(els[1]),
                                                  ord(els[0])))
-            if DEBUG_IDRP:
-                print "Freq: %f" % freq
+            LOG.debug("Freq: %f" % freq)
             return freq
 
     raise errors.InvalidDataError("No frequency frame received")
@@ -107,15 +111,16 @@ RP_IMMUTABLE = ["number", "skip", "bank", "extd_number", "name", "rtone",
                 "ctone", "dtcs", "tmode", "dtcs_polarity", "skip", "duplex",
                 "offset", "mode", "tuning_step", "bank_index"]
 
+
 class IDRPx000V(chirp_common.LiveRadio):
     """Icom IDRP-*"""
     BAUD_RATE = 19200
     VENDOR = "Icom"
     MODEL = "ID-2000V/4000V/2D/2V"
 
-    _model = "0000" # Unknown
+    _model = "0000"  # Unknown
     mem_upper_limit = 0
-        
+
     def get_features(self):
         rf = chirp_common.RadioFeatures()
         rf.valid_modes = ["DV"]
@@ -132,7 +137,7 @@ class IDRPx000V(chirp_common.LiveRadio):
         rf.has_mode = False
         rf.has_name = False
         rf.has_offset = False
-        rf.has_tuning_step = False        
+        rf.has_tuning_step = False
         rf.memory_bounds = (0, 0)
         return rf
 
@@ -156,11 +161,13 @@ class IDRPx000V(chirp_common.LiveRadio):
 
         set_freq(self.pipe, mem.freq)
 
+
 def do_test():
     """Get the frequency of /dev/icom"""
     ser = serial.Serial(port="/dev/icom", baudrate=19200, timeout=0.5)
-    #set_freq(pipe, 439.920)
+    # set_freq(pipe, 439.920)
     get_freq(ser)
 
+
 if __name__ == "__main__":
     do_test()
diff --git a/chirp/kenwood_hmk.py b/chirp/drivers/kenwood_hmk.py
similarity index 74%
rename from chirp/kenwood_hmk.py
rename to chirp/drivers/kenwood_hmk.py
index 59260ac..ba3fe1a 100644
--- a/chirp/kenwood_hmk.py
+++ b/chirp/drivers/kenwood_hmk.py
@@ -15,13 +15,19 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import csv
+import logging
+
+from chirp import chirp_common, errors, directory
+from chirp.drivers import generic_csv
+
+LOG = logging.getLogger(__name__)
 
-from chirp import chirp_common, errors, directory, generic_csv
 
 class OmittedHeaderError(Exception):
     """An internal exception to indicate that a header was omitted"""
     pass
 
+
 @directory.register
 class HMKRadio(generic_csv.CSVRadio):
     """Kenwood HMK format"""
@@ -50,18 +56,18 @@ class HMKRadio(generic_csv.CSVRadio):
         }
 
     ATTR_MAP = {
-        "!!Ch"         : (int,   "number"),
-        "M.Name"       : (str,   "name"),
-        "Rx Freq."     : (chirp_common.parse_freq, "freq"),
-        "Shift/Split"  : (lambda v: HMKRadio.DUPLEX_MAP[v], "duplex"),
-        "Offset"       : (chirp_common.parse_freq, "offset"),
-        "T/CT/DCS"     : (lambda v: HMKRadio.TMODE_MAP[v], "tmode"),
-        "TO Freq."     : (float, "rtone"),
-        "CT Freq."     : (float, "ctone"),
-        "DCS Code"     : (int,   "dtcs"),
-        "Mode"         : (str,   "mode"),
-        "Rx Step"      : (float, "tuning_step"),
-        "L.Out"        : (lambda v: HMKRadio.SKIP_MAP[v], "skip"),
+        "!!Ch":          (int,   "number"),
+        "M.Name":        (str,   "name"),
+        "Rx Freq.":      (chirp_common.parse_freq, "freq"),
+        "Shift/Split":   (lambda v: HMKRadio.DUPLEX_MAP[v], "duplex"),
+        "Offset":        (chirp_common.parse_freq, "offset"),
+        "T/CT/DCS":      (lambda v: HMKRadio.TMODE_MAP[v], "tmode"),
+        "TO Freq.":      (float, "rtone"),
+        "CT Freq.":      (float, "ctone"),
+        "DCS Code":      (int,   "dtcs"),
+        "Mode":          (str,   "mode"),
+        "Rx Step":       (float, "tuning_step"),
+        "L.Out":         (lambda v: HMKRadio.SKIP_MAP[v], "skip"),
         }
 
     def load(self, filename=None):
@@ -89,10 +95,10 @@ class HMKRadio(generic_csv.CSVRadio):
                 continue
 
             if len(header) > len(line):
-                print "Line %i has %i columns, expected %i" % (lineno,
-                                                               len(line),
-                                                               len(header))
-                self.errors.append("Column number mismatch on line %i" % lineno)
+                LOG.debug("Line %i has %i columns, expected %i" %
+                          (lineno, len(line), len(header)))
+                self.errors.append("Column number mismatch on line %i" %
+                                   lineno)
                 continue
 
             # hmk stores Tx Freq. in its own field, but Chirp expects the Tx
@@ -102,14 +108,14 @@ class HMKRadio(generic_csv.CSVRadio):
                 line[header.index('Offset')] = line[header.index('Tx Freq.')]
 
             # fix EU decimal
-            line = [i.replace(',','.') for i in line]
+            line = [i.replace(',', '.') for i in line]
 
             try:
                 mem = self._parse_csv_data_line(header, line)
                 if mem.number is None:
                     raise Exception("Invalid Location field" % lineno)
             except Exception, e:
-                print "Line %i: %s" % (lineno, e)
+                LOG.error("Line %i: %s" % (lineno, e))
                 self.errors.append("Line %i: %s" % (lineno, e))
                 continue
 
@@ -118,7 +124,8 @@ class HMKRadio(generic_csv.CSVRadio):
             good += 1
 
         if not good:
-            print self.errors
+            for e in errors:
+                LOG.error(e)
             raise errors.InvalidDataError("No channels found")
 
     @classmethod
diff --git a/chirp/kenwood_itm.py b/chirp/drivers/kenwood_itm.py
similarity index 85%
rename from chirp/kenwood_itm.py
rename to chirp/drivers/kenwood_itm.py
index 86414eb..57c3076 100644
--- a/chirp/kenwood_itm.py
+++ b/chirp/drivers/kenwood_itm.py
@@ -15,13 +15,19 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import csv
+import logging
+
+from chirp import chirp_common, errors, directory
+from chirp.drivers import generic_csv
+
+LOG = logging.getLogger(__name__)
 
-from chirp import chirp_common, errors, directory, generic_csv
 
 class OmittedHeaderError(Exception):
     """An internal exception to indicate that a header was omitted"""
     pass
 
+
 @directory.register
 class ITMRadio(generic_csv.CSVRadio):
     """Kenwood ITM format"""
@@ -30,9 +36,9 @@ class ITMRadio(generic_csv.CSVRadio):
     FILE_EXTENSION = "itm"
 
     ATTR_MAP = {
-        "CH"           : (int,  "number"),
-        "RXF"          : (chirp_common.parse_freq, "freq"),
-        "NAME"         : (str,  "name"),
+        "CH":            (int,  "number"),
+        "RXF":           (chirp_common.parse_freq, "freq"),
+        "NAME":          (str,  "name"),
         }
 
     def _clean_duplex(self, headers, line, mem):
@@ -50,7 +56,7 @@ class ITMRadio(generic_csv.CSVRadio):
             mem.offset = txfreq
 
         return mem
-    
+
     def _clean_number(self, headers, line, mem):
         zone = int(generic_csv.get_datum_by_header(headers, line, "ZN"))
         mem.number = zone * 100 + mem.number
@@ -99,21 +105,21 @@ class ITMRadio(generic_csv.CSVRadio):
                 break
 
             if len(header) > len(line):
-                print "Line %i has %i columns, expected %i" % (lineno,
-                                                               len(line),
-                                                               len(header))
-                self.errors.append("Column number mismatch on line %i" % lineno)
+                LOG.error("Line %i has %i columns, expected %i" %
+                          (lineno, len(line), len(header)))
+                self.errors.append("Column number mismatch on line %i" %
+                                   lineno)
                 continue
 
             # fix EU decimal
-            line = [i.replace(',','.') for i in line]
+            line = [i.replace(',', '.') for i in line]
 
             try:
                 mem = self._parse_csv_data_line(header, line)
                 if mem.number is None:
                     raise Exception("Invalid Location field" % lineno)
             except Exception, e:
-                print "Line %i: %s" % (lineno, e)
+                LOG.error("Line %i: %s" % (lineno, e))
                 self.errors.append("Line %i: %s" % (lineno, e))
                 continue
 
@@ -122,7 +128,8 @@ class ITMRadio(generic_csv.CSVRadio):
             good += 1
 
         if not good:
-            print self.errors
+            for e in errors:
+                LOG.error(e)
             raise errors.InvalidDataError("No channels found")
 
     @classmethod
diff --git a/chirp/kenwood_live.py b/chirp/drivers/kenwood_live.py
similarity index 73%
rename from chirp/kenwood_live.py
rename to chirp/drivers/kenwood_live.py
index bfa87dd..f4a9d5a 100644
--- a/chirp/kenwood_live.py
+++ b/chirp/drivers/kenwood_live.py
@@ -17,57 +17,77 @@ import threading
 import os
 import sys
 import time
-
-NOCACHE = os.environ.has_key("CHIRP_NOCACHE")
-
-if __name__ == "__main__":
-    import sys
-    sys.path.insert(0, "..")
+import logging
 
 from chirp import chirp_common, errors, directory, util
 from chirp.settings import RadioSetting, RadioSettingGroup, \
     RadioSettingValueInteger, RadioSettingValueBoolean, \
-    RadioSettingValueString, RadioSettingValueList
+    RadioSettingValueString, RadioSettingValueList, RadioSettings
 
-DEBUG = True
+LOG = logging.getLogger(__name__)
 
-DUPLEX = { 0 : "", 1 : "+", 2 : "-" }
-MODES = { 0 : "FM", 1 : "AM" }
+NOCACHE = "CHIRP_NOCACHE" in os.environ
+
+DUPLEX = {0: "", 1: "+", 2: "-"}
+MODES = {0: "FM", 1: "AM"}
 STEPS = list(chirp_common.TUNING_STEPS)
 STEPS.append(100.0)
 
+KENWOOD_TONES = list(chirp_common.TONES)
+KENWOOD_TONES.remove(159.8)
+KENWOOD_TONES.remove(165.5)
+KENWOOD_TONES.remove(171.3)
+KENWOOD_TONES.remove(177.3)
+KENWOOD_TONES.remove(183.5)
+KENWOOD_TONES.remove(189.9)
+KENWOOD_TONES.remove(196.6)
+KENWOOD_TONES.remove(199.5)
+
 THF6_MODES = ["FM", "WFM", "AM", "LSB", "USB", "CW"]
 
 LOCK = threading.Lock()
+COMMAND_RESP_BUFSIZE = 8
+LAST_BAUD = 9600
+LAST_DELIMITER = ("\r", " ")
+
+# The Kenwood TS-2000 uses ";" as a CAT command message delimiter, and all
+# others use "\n". Also, TS-2000 doesn't space delimite the command fields,
+# but others do.
+
 
 def command(ser, cmd, *args):
     """Send @cmd to radio via @ser"""
-    global LOCK
+    global LOCK, LAST_DELIMITER, COMMAND_RESP_BUFSIZE
 
     start = time.time()
 
     LOCK.acquire()
+
     if args:
-        cmd += " " + " ".join(args)
-    if DEBUG:
-        print "PC->RADIO: %s" % cmd
-    ser.write(cmd + "\r")
+        cmd += LAST_DELIMITER[1] + LAST_DELIMITER[1].join(args)
+    cmd += LAST_DELIMITER[0]
+
+    LOG.debug("PC->RADIO: %s" % cmd.strip())
+    ser.write(cmd)
 
     result = ""
-    while not result.endswith("\r"):
-        result += ser.read(8)
+    while not result.endswith(LAST_DELIMITER[0]):
+        result += ser.read(COMMAND_RESP_BUFSIZE)
         if (time.time() - start) > 0.5:
-            print "Timeout waiting for data"
+            LOG.error("Timeout waiting for data")
             break
 
-    if DEBUG:
-        print "D7->PC: %s" % result.strip()
+    if result.endswith(LAST_DELIMITER[0]):
+        LOG.debug("RADIO->PC: %s" % result.strip())
+        result = result[:-1]
+    else:
+        LOG.error("Giving up")
 
     LOCK.release()
 
     return result.strip()
 
-LAST_BAUD = 9600
+
 def get_id(ser):
     """Get the ID of the radio attached to @ser"""
     global LAST_BAUD
@@ -75,18 +95,32 @@ def get_id(ser):
     bauds.remove(LAST_BAUD)
     bauds.insert(0, LAST_BAUD)
 
+    global LAST_DELIMITER
+    command_delimiters = [("\r", " "), (";", "")]
+
     for i in bauds:
-        print "Trying ID at baud %i" % i
-        ser.setBaudrate(i)
-        ser.write("\r")
-        ser.read(25)
-        resp = command(ser, "ID")
-        if " " in resp:
-            LAST_BAUD = i
-            return resp.split(" ")[1]
+        for delimiter in command_delimiters:
+            LAST_DELIMITER = delimiter
+            LOG.info("Trying ID at baud %i with delimiter \"%s\"" %
+                     (i, repr(delimiter)))
+            ser.setBaudrate(i)
+            ser.write(LAST_DELIMITER[0])
+            ser.read(25)
+            resp = command(ser, "ID")
+
+            # most kenwood radios
+            if " " in resp:
+                LAST_BAUD = i
+                return resp.split(" ")[1]
+
+            # TS-2000
+            if "ID019" == resp:
+                LAST_BAUD = i
+                return "TS-2000"
 
     raise errors.RadioError("No response from radio")
 
+
 def get_tmode(tone, ctcss, dcs):
     """Get the tone mode based on the values of the tone, ctcss, dcs"""
     if dcs and int(dcs) == 1:
@@ -98,10 +132,12 @@ def get_tmode(tone, ctcss, dcs):
     else:
         return ""
 
+
 def iserr(result):
     """Returns True if the @result from a radio is an error"""
     return result in ["N", "?"]
 
+
 class KenwoodLiveRadio(chirp_common.LiveRadio):
     """Base class for all live-mode kenwood radios"""
     BAUD_RATE = 9600
@@ -116,7 +152,7 @@ class KenwoodLiveRadio(chirp_common.LiveRadio):
     def __init__(self, *args, **kwargs):
         chirp_common.LiveRadio.__init__(self, *args, **kwargs)
 
-        self.__memcache = {}
+        self._memcache = {}
 
         if self.pipe:
             self.pipe.setTimeout(0.1)
@@ -152,27 +188,27 @@ class KenwoodLiveRadio(chirp_common.LiveRadio):
 
     def get_memory(self, number):
         if number < 0 or number > self._upper:
-            raise errors.InvalidMemoryLocation( \
+            raise errors.InvalidMemoryLocation(
                 "Number must be between 0 and %i" % self._upper)
-        if self.__memcache.has_key(number) and not NOCACHE:
-            return self.__memcache[number]
+        if number in self._memcache and not NOCACHE:
+            return self._memcache[number]
 
         result = command(self.pipe, *self._cmd_get_memory(number))
         if result == "N" or result == "E":
             mem = chirp_common.Memory()
             mem.number = number
             mem.empty = True
-            self.__memcache[mem.number] = mem
+            self._memcache[mem.number] = mem
             return mem
         elif " " not in result:
-            print "Not sure what to do with this: `%s'" % result
+            LOG.error("Not sure what to do with this: `%s'" % result)
             raise errors.RadioError("Unexpected result returned from radio")
 
         value = result.split(" ")[1]
         spec = value.split(",")
 
         mem = self._parse_mem_spec(spec)
-        self.__memcache[mem.number] = mem
+        self._memcache[mem.number] = mem
 
         result = command(self.pipe, *self._cmd_get_memory_name(number))
         if " " in result:
@@ -181,7 +217,7 @@ class KenwoodLiveRadio(chirp_common.LiveRadio):
                 _zero, _loc, mem.name = value.split(",")
             else:
                 _loc, mem.name = value.split(",")
- 
+
         if mem.duplex == "" and self._kenwood_split:
             result = command(self.pipe, *self._cmd_get_split(number))
             if " " in result:
@@ -205,7 +241,7 @@ class KenwoodLiveRadio(chirp_common.LiveRadio):
 
     def set_memory(self, memory):
         if memory.number < 0 or memory.number > self._upper:
-            raise errors.InvalidMemoryLocation( \
+            raise errors.InvalidMemoryLocation(
                 "Number must be between 0 and %i" % self._upper)
 
         spec = self._make_mem_spec(memory)
@@ -217,56 +253,86 @@ class KenwoodLiveRadio(chirp_common.LiveRadio):
                                                                memory.name))
             if not iserr(r2):
                 memory.name = memory.name.rstrip()
-                self.__memcache[memory.number] = memory
+                self._memcache[memory.number] = memory
             else:
-                raise errors.InvalidDataError("Radio refused name %i: %s" %\
-                                                  (memory.number,
-                                                   repr(memory.name)))
+                raise errors.InvalidDataError("Radio refused name %i: %s" %
+                                              (memory.number,
+                                               repr(memory.name)))
         else:
             raise errors.InvalidDataError("Radio refused %i" % memory.number)
 
-        if memory.duplex == "split" and self._kenwood_split: 
+        if memory.duplex == "split" and self._kenwood_split:
             spec = ",".join(self._make_split_spec(memory))
             result = command(self.pipe, *self._cmd_set_split(memory.number,
                                                              spec))
             if iserr(result):
-                raise errors.InvalidDataError("Radio refused %i" % \
-                                                  memory.number)
+                raise errors.InvalidDataError("Radio refused %i" %
+                                              memory.number)
 
     def erase_memory(self, number):
-        if not self.__memcache.has_key(number):
+        if number not in self._memcache:
             return
 
         resp = command(self.pipe, *self._cmd_set_memory(number, ""))
         if iserr(resp):
             raise errors.RadioError("Radio refused delete of %i" % number)
-        del self.__memcache[number]
-
-TH_D7_SETTINGS = {
-    "BAL"  : ["4:0", "3:1", "2:2", "1:3", "0:4"],
-    "BEP"  : ["Off", "Key", "Key+Data", "All"],
-    "BEPT" : ["Off", "Mine", "All New"], # D700 has fourth "All"
-    "DS"   : ["Data Band", "Both Bands"],
-    "DTB"  : ["A", "B"],
-    "DTBA" : ["A", "B", "A:TX/B:RX"], # D700 has fourth A:RX/B:TX
-    "DTX"  : ["Manual", "PTT", "Auto"],
-    "ICO"  : ["Kenwood", "Runner", "House", "Tent", "Boat", "SSTV",
-              "Plane", "Speedboat", "Car", "Bicycle"],
-    "MNF"  : ["Name", "Frequency"],
-    "PKSA" : ["1200", "9600"],
-    "POSC" : ["Off Duty", "Enroute", "In Service", "Returning",
-              "Committed", "Special", "Priority", "Emergency"],
-    "PT"   : ["100ms", "200ms", "500ms", "750ms", "1000ms", "1500ms", "2000ms"],
-    "SCR"  : ["Time", "Carrier", "Seek"],
-    "SV"   : ["Off", "0.2s", "0.4s", "0.6s", "0.8s", "1.0s",
-              "2s", "3s", "4s", "5s"],
-    "TEMP" : ["F", "C"],
-    "TXI"  : ["30sec", "1min", "2min", "3min", "4min", "5min",
-              "10min", "20min", "30min"],
-    "UNIT" : ["English", "Metric"],
-    "WAY"  : ["Off", "6 digit NMEA", "7 digit NMEA", "8 digit NMEA",
-              "9 digit NMEA", "6 digit Magellan", "DGPS"],
-}
+        del self._memcache[number]
+
+    def _kenwood_get(self, cmd):
+        resp = command(self.pipe, cmd)
+        if " " in resp:
+            return resp.split(" ", 1)
+        else:
+            if resp == cmd:
+                return [resp, ""]
+            else:
+                raise errors.RadioError("Radio refused to return %s" % cmd)
+
+    def _kenwood_set(self, cmd, value):
+        resp = command(self.pipe, cmd, value)
+        if resp[:len(cmd)] == cmd:
+            return
+        raise errors.RadioError("Radio refused to set %s" % cmd)
+
+    def _kenwood_get_bool(self, cmd):
+        _cmd, result = self._kenwood_get(cmd)
+        return result == "1"
+
+    def _kenwood_set_bool(self, cmd, value):
+        return self._kenwood_set(cmd, str(int(value)))
+
+    def _kenwood_get_int(self, cmd):
+        _cmd, result = self._kenwood_get(cmd)
+        return int(result)
+
+    def _kenwood_set_int(self, cmd, value, digits=1):
+        return self._kenwood_set(cmd, ("%%0%ii" % digits) % value)
+
+    def set_settings(self, settings):
+        for element in settings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+            if not element.changed():
+                continue
+            if isinstance(element.value, RadioSettingValueBoolean):
+                self._kenwood_set_bool(element.get_name(), element.value)
+            elif isinstance(element.value, RadioSettingValueList):
+                options = self._SETTINGS_OPTIONS[element.get_name()]
+                self._kenwood_set_int(element.get_name(),
+                                      options.index(str(element.value)))
+            elif isinstance(element.value, RadioSettingValueInteger):
+                if element.value.get_max() > 9:
+                    digits = 2
+                else:
+                    digits = 1
+                self._kenwood_set_int(element.get_name(),
+                                      element.value, digits)
+            elif isinstance(element.value, RadioSettingValueString):
+                self._kenwood_set(element.get_name(), str(element.value))
+            else:
+                LOG.error("Unknown type %s" % element.value)
+
 
 class KenwoodOldLiveRadio(KenwoodLiveRadio):
     _kenwood_valid_tones = list(chirp_common.OLD_TONES)
@@ -283,6 +349,7 @@ class KenwoodOldLiveRadio(KenwoodLiveRadio):
 
         return KenwoodLiveRadio.set_memory(self, memory)
 
+
 @directory.register
 class THD7Radio(KenwoodOldLiveRadio):
     """Kenwood TH-D7"""
@@ -290,6 +357,33 @@ class THD7Radio(KenwoodOldLiveRadio):
 
     _kenwood_split = True
 
+    _SETTINGS_OPTIONS = {
+        "BAL": ["4:0", "3:1", "2:2", "1:3", "0:4"],
+        "BEP": ["Off", "Key", "Key+Data", "All"],
+        "BEPT": ["Off", "Mine", "All New"],  # D700 has fourth "All"
+        "DS": ["Data Band", "Both Bands"],
+        "DTB": ["A", "B"],
+        "DTBA": ["A", "B", "A:TX/B:RX"],  # D700 has fourth A:RX/B:TX
+        "DTX": ["Manual", "PTT", "Auto"],
+        "ICO": ["Kenwood", "Runner", "House", "Tent", "Boat", "SSTV",
+                "Plane", "Speedboat", "Car", "Bicycle"],
+        "MNF": ["Name", "Frequency"],
+        "PKSA": ["1200", "9600"],
+        "POSC": ["Off Duty", "Enroute", "In Service", "Returning",
+                 "Committed", "Special", "Priority", "Emergency"],
+        "PT": ["100ms", "200ms", "500ms", "750ms",
+               "1000ms", "1500ms", "2000ms"],
+        "SCR": ["Time", "Carrier", "Seek"],
+        "SV": ["Off", "0.2s", "0.4s", "0.6s", "0.8s", "1.0s",
+               "2s", "3s", "4s", "5s"],
+        "TEMP": ["F", "C"],
+        "TXI": ["30sec", "1min", "2min", "3min", "4min", "5min",
+                "10min", "20min", "30min"],
+        "UNIT": ["English", "Metric"],
+        "WAY": ["Off", "6 digit NMEA", "7 digit NMEA", "8 digit NMEA",
+                "9 digit NMEA", "6 digit Magellan", "DGPS"],
+    }
+
     def get_features(self):
         rf = chirp_common.RadioFeatures()
         rf.has_settings = True
@@ -302,7 +396,8 @@ class THD7Radio(KenwoodOldLiveRadio):
         rf.valid_duplexes = ["", "-", "+", "split"]
         rf.valid_modes = MODES.values()
         rf.valid_tmodes = ["", "Tone", "TSQL"]
-        rf.valid_characters = chirp_common.CHARSET_ALPHANUMERIC
+        rf.valid_characters = \
+            chirp_common.CHARSET_ALPHANUMERIC + "/.-+*)('&%$#! ~}|{"
         rf.valid_name_length = 7
         rf.memory_bounds = (1, self._upper)
         return rf
@@ -314,17 +409,17 @@ class THD7Radio(KenwoodOldLiveRadio):
         else:
             duplex = 0
             offset = 0
-        
-        spec = ( \
+
+        spec = (
             "%011i" % mem.freq,
             "%X" % STEPS.index(mem.tuning_step),
             "%i" % duplex,
             "0",
             "%i" % (mem.tmode == "Tone"),
             "%i" % (mem.tmode == "TSQL"),
-            "", # DCS Flag
+            "",  # DCS Flag
             "%02i" % (self._kenwood_valid_tones.index(mem.rtone) + 1),
-            "", # DCS Code
+            "",  # DCS Code
             "%02i" % (self._kenwood_valid_tones.index(mem.ctone) + 1),
             "%09i" % offset,
             "%i" % util.get_dict_rev(MODES, mem.mode),
@@ -345,7 +440,7 @@ class THD7Radio(KenwoodOldLiveRadio):
         if spec[11] and spec[11].isdigit():
             mem.dtcs = chirp_common.DTCS_CODES[int(spec[11][:-1]) - 1]
         else:
-            print "Unknown or invalid DCS: %s" % spec[11]
+            LOG.warn("Unknown or invalid DCS: %s" % spec[11])
         if spec[13]:
             mem.offset = int(spec[13])
         else:
@@ -355,34 +450,8 @@ class THD7Radio(KenwoodOldLiveRadio):
 
         return mem
 
-    def _kenwood_get(self, cmd):
-        resp = command(self.pipe, cmd)
-        if " " in resp:
-            return resp.split(" ", 1)
-        else:
-            raise errors.RadioError("Radio refused to return %s" % cmd)
-
-    def _kenwood_set(self, cmd, value):
-        resp = command(self.pipe, cmd, value)
-        if " " in resp:
-            return
-        raise errors.RadioError("Radio refused to set %s" % cmd)
-
-    def _kenwood_get_bool(self, cmd):
-        _cmd, result = self._kenwood_get(cmd)
-        return result == "1"
-
-    def _kenwood_set_bool(self, cmd, value):
-        return self._kenwood_set(cmd, str(int(value)))
-
-    def _kenwood_get_int(self, cmd):
-        _cmd, result = self._kenwood_get(cmd)
-        return int(result)
-
-    def _kenwood_set_int(self, cmd, value, digits=1):
-        return self._kenwood_set(cmd, ("%%0%ii" % digits) % value)
-    
     def get_settings(self):
+        main = RadioSettingGroup("main", "Main")
         aux = RadioSettingGroup("aux", "Aux")
         tnc = RadioSettingGroup("tnc", "TNC")
         save = RadioSettingGroup("save", "Save")
@@ -392,17 +461,18 @@ class THD7Radio(KenwoodOldLiveRadio):
                                   aux, tnc, save, display, dtmf)
         sky = RadioSettingGroup("sky", "SkyCommand")
         aprs = RadioSettingGroup("aprs", "APRS")
-        top = RadioSettingGroup("top", "All Settings", radio, aprs, sky)
+
+        top = RadioSettings(main, radio, aprs, sky)
 
         bools = [("AMR", aprs, "APRS Message Auto-Reply"),
                  ("AIP", aux, "Advanced Intercept Point"),
                  ("ARO", aux, "Automatic Repeater Offset"),
                  ("BCN", aprs, "Beacon"),
                  ("CH", radio, "Channel Mode Display"),
-                 #("DIG", aprs, "APRS Digipeater"),
-                 ("DL", all, "Dual"),
-                 ("LK", all, "Lock"),
-                 ("LMP", all, "Lamp"),
+                 # ("DIG", aprs, "APRS Digipeater"),
+                 ("DL", main, "Dual"),
+                 ("LK", main, "Lock"),
+                 ("LMP", main, "Lamp"),
                  ("TSP", dtmf, "DTMF Fast Transmission"),
                  ("TXH", dtmf, "TX Hold"),
                  ]
@@ -413,28 +483,28 @@ class THD7Radio(KenwoodOldLiveRadio):
                               RadioSettingValueBoolean(value))
             group.append(rs)
 
-        lists = [("BAL", all, "Balance"),
+        lists = [("BAL", main, "Balance"),
                  ("BEP", aux, "Beep"),
                  ("BEPT", aprs, "APRS Beep"),
                  ("DS", tnc, "Data Sense"),
                  ("DTB", tnc, "Data Band"),
                  ("DTBA", aprs, "APRS Data Band"),
                  ("DTX", aprs, "APRS Data TX"),
-                 #("ICO", aprs, "APRS Icon"),
-                 ("MNF", all, "Memory Display Mode"),
+                 # ("ICO", aprs, "APRS Icon"),
+                 ("MNF", main, "Memory Display Mode"),
                  ("PKSA", aprs, "APRS Packet Speed"),
                  ("POSC", aprs, "APRS Position Comment"),
                  ("PT", dtmf, "DTMF Speed"),
                  ("SV", save, "Battery Save"),
                  ("TEMP", aprs, "APRS Temperature Units"),
                  ("TXI", aprs, "APRS Transmit Interval"),
-                 #("UNIT", aprs, "APRS Display Units"),
+                 # ("UNIT", aprs, "APRS Display Units"),
                  ("WAY", aprs, "Waypoint Mode"),
                  ]
 
         for setting, group, name in lists:
             value = self._kenwood_get_int(setting)
-            options = TH_D7_SETTINGS[setting]
+            options = self._SETTINGS_OPTIONS[setting]
             rs = RadioSetting(setting, name,
                               RadioSettingValueList(options,
                                                     options[value]))
@@ -453,7 +523,7 @@ class THD7Radio(KenwoodOldLiveRadio):
                    ("PP", aprs, "APRS Path", 32),
                    ("SCC", sky, "SkyCommand Callsign", 8),
                    ("SCT", sky, "SkyCommand To Callsign", 8),
-                   #("STAT", aprs, "APRS Status Text", 32),
+                   # ("STAT", aprs, "APRS Status Text", 32),
                    ]
         for setting, group, name, length in strings:
             _cmd, value = self._kenwood_get(setting)
@@ -463,32 +533,18 @@ class THD7Radio(KenwoodOldLiveRadio):
 
         return top
 
-    def set_settings(self, settings):
-        for element in settings:
-            if not element.changed():
-                continue
-            if isinstance(element.value, RadioSettingValueBoolean):
-                self._kenwood_set_bool(element.get_name(), element.value)
-            elif isinstance(element.value, RadioSettingValueList):
-                options = TH_D7_SETTINGS[element.get_name()]
-                self._kenwood_set_int(element.get_name(),
-                                      options.index(str(element.value)))
-            elif isinstance(element.value, RadioSettingValueInteger):
-                if element.value.get_max() > 9:
-                    digits = 2
-                else:
-                    digits = 1
-                self._kenwood_set_int(element.get_name(), element.value, digits)
-            elif isinstance(element.value, RadioSettingValueString):
-                self._kenwood_set(element.get_name(), str(element.value))
-            else:
-                print "Unknown type %s" % element.value
 
 @directory.register
 class THD7GRadio(THD7Radio):
     """Kenwood TH-D7G"""
     MODEL = "TH-D7G"
 
+    def get_features(self):
+        rf = super(THD7GRadio, self).get_features()
+        rf.valid_name_length = 8
+        return rf
+
+
 @directory.register
 class TMD700Radio(KenwoodOldLiveRadio):
     """Kenwood TH-D700"""
@@ -517,7 +573,7 @@ class TMD700Radio(KenwoodOldLiveRadio):
             duplex = util.get_dict_rev(DUPLEX, mem.duplex)
         else:
             duplex = 0
-        spec = ( \
+        spec = (
             "%011i" % mem.freq,
             "%X" % STEPS.index(mem.tuning_step),
             "%i" % duplex,
@@ -547,7 +603,7 @@ class TMD700Radio(KenwoodOldLiveRadio):
         if spec[11] and spec[11].isdigit():
             mem.dtcs = chirp_common.DTCS_CODES[int(spec[11][:-1]) - 1]
         else:
-            print "Unknown or invalid DCS: %s" % spec[11]
+            LOG.warn("Unknown or invalid DCS: %s" % spec[11])
         if spec[13]:
             mem.offset = int(spec[13])
         else:
@@ -557,12 +613,13 @@ class TMD700Radio(KenwoodOldLiveRadio):
 
         return mem
 
+
 @directory.register
 class TMV7Radio(KenwoodOldLiveRadio):
     """Kenwood TM-V7"""
     MODEL = "TM-V7"
 
-    mem_upper_limit = 200 # Will be updated
+    mem_upper_limit = 200  # Will be updated
 
     def get_features(self):
         rf = chirp_common.RadioFeatures()
@@ -580,7 +637,7 @@ class TMV7Radio(KenwoodOldLiveRadio):
         return rf
 
     def _make_mem_spec(self, mem):
-        spec = ( \
+        spec = (
             "%011i" % mem.freq,
             "%X" % STEPS.index(mem.tuning_step),
             "%i" % util.get_dict_rev(DUPLEX, mem.duplex),
@@ -636,34 +693,38 @@ class TMV7Radio(KenwoodOldLiveRadio):
         try:
             self.erase_memory(loc)
         except Exception:
-            pass # V7A Can't delete just yet
+            pass  # V7A Can't delete just yet
 
         return True
 
     def _detect_split(self):
         return 50
 
+
 class TMV7RadioSub(TMV7Radio):
     """Base class for the TM-V7 sub devices"""
     def __init__(self, pipe):
         TMV7Radio.__init__(self, pipe)
         self._detect_split()
 
+
 class TMV7RadioVHF(TMV7RadioSub):
     """TM-V7 VHF subdevice"""
     VARIANT = "VHF"
     _vfo = 0
 
+
 class TMV7RadioUHF(TMV7RadioSub):
     """TM-V7 UHF subdevice"""
     VARIANT = "UHF"
     _vfo = 1
 
+
 @directory.register
 class TMG707Radio(TMV7Radio):
     """Kenwood TM-G707"""
     MODEL = "TM-G707"
-    
+
     def get_features(self):
         rf = TMV7Radio.get_features(self)
         rf.has_sub_devices = False
@@ -673,7 +734,10 @@ class TMG707Radio(TMV7Radio):
                           (800000000, 999000000)]
         return rf
 
-THG71_STEPS = [ 5, 6.25, 10, 12.5, 15, 20, 25, 30, 50, 100 ]
+
+THG71_STEPS = [5, 6.25, 10, 12.5, 15, 20, 25, 30, 50, 100]
+
+
 @directory.register
 class THG71Radio(TMV7Radio):
     """Kenwood TH-G71"""
@@ -687,11 +751,11 @@ class THG71Radio(TMV7Radio):
         rf.has_sub_devices = False
         rf.valid_bands = [(118000000, 174000000),
                           (320000000, 470000000),
-						  (800000000, 945000000)]		
+                          (800000000, 945000000)]
         return rf
 
     def _make_mem_spec(self, mem):
-        spec = ( \
+        spec = (
             "%011i" % mem.freq,
             "%X" % THG71_STEPS.index(mem.tuning_step),
             "%i" % util.get_dict_rev(DUPLEX, mem.duplex),
@@ -724,13 +788,14 @@ class THG71Radio(TMV7Radio):
             mem.offset = 0
         return mem
 
-		
+
 THF6A_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]
 
 THF6A_DUPLEX = dict(DUPLEX)
 THF6A_DUPLEX[3] = "split"
 
+
 @directory.register
 class THF6ARadio(KenwoodLiveRadio):
     """Kenwood TH-F6"""
@@ -738,6 +803,7 @@ class THF6ARadio(KenwoodLiveRadio):
 
     _upper = 399
     _kenwood_split = True
+    _kenwood_valid_tones = list(KENWOOD_TONES)
 
     def get_features(self):
         rf = chirp_common.RadioFeatures()
@@ -753,6 +819,7 @@ class THF6ARadio(KenwoodLiveRadio):
         rf.valid_characters = chirp_common.CHARSET_ASCII
         rf.valid_name_length = 8
         rf.memory_bounds = (0, self._upper)
+        rf.has_settings = True
         return rf
 
     def _cmd_set_memory(self, number, spec):
@@ -788,7 +855,7 @@ class THF6ARadio(KenwoodLiveRadio):
         if spec[11] and spec[11].isdigit():
             mem.dtcs = chirp_common.DTCS_CODES[int(spec[11])]
         else:
-            print "Unknown or invalid DCS: %s" % spec[11]
+            LOG.warn("Unknown or invalid DCS: %s" % spec[11])
         if spec[12]:
             mem.offset = int(spec[12])
         else:
@@ -807,8 +874,8 @@ class THF6ARadio(KenwoodLiveRadio):
             duplex = 0
             offset = 0
         else:
-            print "Bug: unsupported duplex `%s'" % mem.duplex
-        spec = ( \
+            LOG.warn("Bug: unsupported duplex `%s'" % mem.duplex)
+        spec = (
             "%011i" % mem.freq,
             "%X" % THF6A_STEPS.index(mem.tuning_step),
             "%i" % duplex,
@@ -825,6 +892,99 @@ class THF6ARadio(KenwoodLiveRadio):
 
         return spec
 
+    _SETTINGS_OPTIONS = {
+        "APO": ["Off", "30min", "60min"],
+        "BAL": ["100%:0%", "75%:25%", "50%:50%", "25%:75%", "%0:100%"],
+        "BAT": ["Lithium", "Alkaline"],
+        "CKEY": ["Call", "1750Hz"],
+        "DATP": ["1200bps", "9600bps"],
+        "LAN": ["English", "Japanese"],
+        "MNF": ["Name", "Frequency"],
+        "MRM": ["All Band", "Current Band"],
+        "PT": ["100ms", "250ms", "500ms", "750ms",
+               "1000ms", "1500ms", "2000ms"],
+        "SCR": ["Time", "Carrier", "Seek"],
+        "SV": ["Off", "0.2s", "0.4s", "0.6s", "0.8s", "1.0s",
+               "2s", "3s", "4s", "5s"],
+        "VXD": ["250ms", "500ms", "750ms", "1s", "1.5s", "2s", "3s"],
+    }
+
+    def get_settings(self):
+        main = RadioSettingGroup("main", "Main")
+        aux = RadioSettingGroup("aux", "Aux")
+        save = RadioSettingGroup("save", "Save")
+        display = RadioSettingGroup("display", "Display")
+        dtmf = RadioSettingGroup("dtmf", "DTMF")
+        top = RadioSettings(main, aux, save, display, dtmf)
+
+        lists = [("APO", save, "Automatic Power Off"),
+                 ("BAL", main, "Balance"),
+                 ("BAT", save, "Battery Type"),
+                 ("CKEY", aux, "CALL Key Set Up"),
+                 ("DATP", aux, "Data Packet Speed"),
+                 ("LAN", display, "Language"),
+                 ("MNF", main, "Memory Display Mode"),
+                 ("MRM", main, "Memory Recall Method"),
+                 ("PT", dtmf, "DTMF Speed"),
+                 ("SCR", main, "Scan Resume"),
+                 ("SV", save, "Battery Save"),
+                 ("VXD", aux, "VOX Drop Delay"),
+                 ]
+
+        bools = [("ANT", aux, "Bar Antenna"),
+                 ("ATT", main, "Attenuator Enabled"),
+                 ("ARO", main, "Automatic Repeater Offset"),
+                 ("BEP", aux, "Beep for keypad"),
+                 ("DL", main, "Dual"),
+                 ("DLK", dtmf, "DTMF Lockout On Transmit"),
+                 ("ELK", aux, "Enable Locked Tuning"),
+                 ("LK", main, "Lock"),
+                 ("LMP", display, "Lamp"),
+                 ("NSFT", aux, "Noise Shift"),
+                 ("TH", aux, "Tx Hold for 1750"),
+                 ("TSP", dtmf, "DTMF Fast Transmission"),
+                 ("TXH", dtmf, "TX Hold DTMF"),
+                 ("TXS", main, "Transmit Inhibit"),
+                 ("VOX", aux, "VOX Enable"),
+                 ("VXB", aux, "VOX On Busy"),
+                 ]
+
+        ints = [("CNT", display, "Contrast", 1, 16),
+                ("VXG", aux, "VOX Gain", 0, 9),
+                ]
+
+        strings = [("MES", display, "Power-on Message", 8),
+                   ]
+
+        for setting, group, name in bools:
+            value = self._kenwood_get_bool(setting)
+            rs = RadioSetting(setting, name,
+                              RadioSettingValueBoolean(value))
+            group.append(rs)
+
+        for setting, group, name in lists:
+            value = self._kenwood_get_int(setting)
+            options = self._SETTINGS_OPTIONS[setting]
+            rs = RadioSetting(setting, name,
+                              RadioSettingValueList(options,
+                                                    options[value]))
+            group.append(rs)
+
+        for setting, group, name, minv, maxv in ints:
+            value = self._kenwood_get_int(setting)
+            rs = RadioSetting(setting, name,
+                              RadioSettingValueInteger(minv, maxv, value))
+            group.append(rs)
+
+        for setting, group, name, length in strings:
+            _cmd, value = self._kenwood_get(setting)
+            rs = RadioSetting(setting, name,
+                              RadioSettingValueString(0, length, value))
+            group.append(rs)
+
+        return top
+
+
 @directory.register
 class THF7ERadio(THF6ARadio):
     """Kenwood TH-F7"""
@@ -834,23 +994,15 @@ D710_DUPLEX = ["", "+", "-", "split"]
 D710_MODES = ["FM", "NFM", "AM"]
 D710_SKIP = ["", "S"]
 D710_STEPS = [5.0, 6.25, 8.33, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0]
-D710_TONES = list(chirp_common.TONES)
-D710_TONES.remove(159.8)
-D710_TONES.remove(165.5)
-D710_TONES.remove(171.3)
-D710_TONES.remove(177.3)
-D710_TONES.remove(183.5)
-D710_TONES.remove(189.9)
-D710_TONES.remove(196.6)
-D710_TONES.remove(199.5)
+
 
 @directory.register
 class TMD710Radio(KenwoodLiveRadio):
     """Kenwood TM-D710"""
     MODEL = "TM-D710"
-    
+
     _upper = 999
-    _kenwood_valid_tones = list(D710_TONES)
+    _kenwood_valid_tones = list(KENWOOD_TONES)
 
     def get_features(self):
         rf = chirp_common.RadioFeatures()
@@ -861,7 +1013,7 @@ class TMD710Radio(KenwoodLiveRadio):
         rf.valid_modes = D710_MODES
         rf.valid_duplexes = D710_DUPLEX
         rf.valid_tuning_steps = D710_STEPS
-        rf.valid_characters = chirp_common.CHARSET_ASCII.replace(',','')
+        rf.valid_characters = chirp_common.CHARSET_ASCII.replace(',', '')
         rf.valid_name_length = 8
         rf.valid_skips = D710_SKIP
         rf.memory_bounds = (0, 999)
@@ -903,37 +1055,38 @@ class TMD710Radio(KenwoodLiveRadio):
             mem.duplex = "split"
             mem.offset = int(spec[13])
         # Unknown
-        mem.skip = D710_SKIP[int(spec[15])] # Memory Lockout
+        mem.skip = D710_SKIP[int(spec[15])]  # Memory Lockout
 
         return mem
 
     def _make_mem_spec(self, mem):
-        spec = ( \
+        spec = (
             "%010i" % mem.freq,
             "%X" % D710_STEPS.index(mem.tuning_step),
-            "%i" % (0 if mem.duplex == "split" else \
-                        D710_DUPLEX.index(mem.duplex)),
-            "0", # Reverse
+            "%i" % (0 if mem.duplex == "split"
+                    else D710_DUPLEX.index(mem.duplex)),
+            "0",  # Reverse
             "%i" % (mem.tmode == "Tone" and 1 or 0),
             "%i" % (mem.tmode == "TSQL" and 1 or 0),
             "%i" % (mem.tmode == "DTCS" and 1 or 0),
             "%02i" % (self._kenwood_valid_tones.index(mem.rtone)),
             "%02i" % (self._kenwood_valid_tones.index(mem.ctone)),
             "%03i" % (chirp_common.DTCS_CODES.index(mem.dtcs)),
-            "%08i" % (0 if mem.duplex == "split" else mem.offset), # Offset
+            "%08i" % (0 if mem.duplex == "split" else mem.offset),  # Offset
             "%i" % D710_MODES.index(mem.mode),
-            "%010i" % (mem.offset if mem.duplex == "split" else 0), # TX Freq
-            "0", # Unknown
-            "%i" % D710_SKIP.index(mem.skip), # Memory Lockout
+            "%010i" % (mem.offset if mem.duplex == "split" else 0),  # TX Freq
+            "0",  # Unknown
+            "%i" % D710_SKIP.index(mem.skip),  # Memory Lockout
             )
 
         return spec
 
+
 @directory.register
 class THD72Radio(TMD710Radio):
     """Kenwood TH-D72"""
     MODEL = "TH-D72 (live mode)"
-    HARDWARE_FLOW = sys.platform == "darwin" # only OS X driver needs hw flow
+    HARDWARE_FLOW = sys.platform == "darwin"  # only OS X driver needs hw flow
 
     def _parse_mem_spec(self, spec):
         mem = chirp_common.Memory()
@@ -959,17 +1112,17 @@ class THD72Radio(TMD710Radio):
             mem.duplex = "split"
             mem.offset = int(spec[15])
         # Lockout
-        mem.skip = D710_SKIP[int(spec[17])] # Memory Lockout
+        mem.skip = D710_SKIP[int(spec[17])]  # Memory Lockout
 
         return mem
 
     def _make_mem_spec(self, mem):
-        spec = ( \
+        spec = (
             "%010i" % mem.freq,
             "%X" % D710_STEPS.index(mem.tuning_step),
-            "%i" % (0 if mem.duplex == "split" else \
-                        D710_DUPLEX.index(mem.duplex)),
-            "0", # Reverse
+            "%i" % (0 if mem.duplex == "split"
+                    else D710_DUPLEX.index(mem.duplex)),
+            "0",  # Reverse
             "%i" % (mem.tmode == "Tone" and 1 or 0),
             "%i" % (mem.tmode == "TSQL" and 1 or 0),
             "%i" % (mem.tmode == "DTCS" and 1 or 0),
@@ -978,40 +1131,49 @@ class THD72Radio(TMD710Radio):
             "%02i" % (self._kenwood_valid_tones.index(mem.ctone)),
             "%03i" % (chirp_common.DTCS_CODES.index(mem.dtcs)),
             "0",
-            "%08i" % (0 if mem.duplex == "split" else mem.offset), # Offset
+            "%08i" % (0 if mem.duplex == "split" else mem.offset),  # Offset
             "%i" % D710_MODES.index(mem.mode),
-            "%010i" % (mem.offset if mem.duplex == "split" else 0), # TX Freq
-            "0", # Unknown
-            "%i" % D710_SKIP.index(mem.skip), # Memory Lockout
+            "%010i" % (mem.offset if mem.duplex == "split" else 0),  # TX Freq
+            "0",  # Unknown
+            "%i" % D710_SKIP.index(mem.skip),  # Memory Lockout
             )
 
         return spec
 
+
 @directory.register
 class TMV71Radio(TMD710Radio):
     """Kenwood TM-V71"""
     MODEL = "TM-V71"
 
+
+ at directory.register
+class TMD710GRadio(TMD710Radio):
+    """Kenwood TM-D710G"""
+    MODEL = "TM-D710G"
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.experimental = ("This radio driver is currently under development, "
+                           "and supports the same features as the TM-D710A/E. "
+                           "There are no known issues with it, but you should "
+                           "proceed with caution.")
+        return rp
+
+
 THK2_DUPLEX = ["", "+", "-"]
 THK2_MODES = ["FM", "NFM"]
-THK2_TONES = list(chirp_common.TONES)
-THK2_TONES.remove(159.8) # ??
-THK2_TONES.remove(165.5) # ??
-THK2_TONES.remove(171.3) # ??
-THK2_TONES.remove(177.3) # ??
-THK2_TONES.remove(183.5) # ??
-THK2_TONES.remove(189.9) # ??
-THK2_TONES.remove(196.6) # ??
-THK2_TONES.remove(199.5) # ??
 
 THK2_CHARS = chirp_common.CHARSET_UPPER_NUMERIC + "-/"
 
+
 @directory.register
 class THK2Radio(KenwoodLiveRadio):
     """Kenwood TH-K2"""
     MODEL = "TH-K2"
 
-    _kenwood_valid_tones = list(THK2_TONES)
+    _kenwood_valid_tones = list(KENWOOD_TONES)
 
     def get_features(self):
         rf = chirp_common.RadioFeatures()
@@ -1047,7 +1209,7 @@ class THK2Radio(KenwoodLiveRadio):
 
         mem.number = int(spec[0])
         mem.freq = int(spec[1])
-        #mem.tuning_step = 
+        # mem.tuning_step =
         mem.duplex = THK2_DUPLEX[int(spec[3])]
         if int(spec[5]):
             mem.tmode = "Tone"
@@ -1070,32 +1232,35 @@ class THK2Radio(KenwoodLiveRadio):
         except ValueError:
             raise errors.UnsupportedToneError()
 
-        spec = ( \
+        spec = (
             "%010i" % mem.freq,
             "0",
-            "%i"    % THK2_DUPLEX.index(mem.duplex),
+            "%i" % THK2_DUPLEX.index(mem.duplex),
             "0",
-            "%i"    % int(mem.tmode == "Tone"),
-            "%i"    % int(mem.tmode == "TSQL"),
-            "%i"    % int(mem.tmode == "DTCS"),
-            "%02i"  % rti,
-            "%02i"  % cti,
-            "%03i"  % chirp_common.DTCS_CODES.index(mem.dtcs),
-            "%08i"  % mem.offset,
-            "%i"    % THK2_MODES.index(mem.mode),
+            "%i" % int(mem.tmode == "Tone"),
+            "%i" % int(mem.tmode == "TSQL"),
+            "%i" % int(mem.tmode == "DTCS"),
+            "%02i" % rti,
+            "%02i" % cti,
+            "%03i" % chirp_common.DTCS_CODES.index(mem.dtcs),
+            "%08i" % mem.offset,
+            "%i" % THK2_MODES.index(mem.mode),
             "0",
             "%010i" % 0,
             "0",
-            "%i"    % int(mem.skip == "S")
+            "%i" % int(mem.skip == "S")
             )
         return spec
-            
+
+
+TM271_STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0]
+
 
 @directory.register
 class TM271Radio(THK2Radio):
     """Kenwood TM-271"""
     MODEL = "TM-271"
-    
+
     def get_features(self):
         rf = chirp_common.RadioFeatures()
         rf.can_odd_split = False
@@ -1109,7 +1274,7 @@ class TM271Radio(THK2Radio):
         rf.valid_name_length = 6
         rf.valid_bands = [(137000000, 173990000)]
         rf.valid_skips = ["", "S"]
-        rf.valid_tuning_steps = [5.0]
+        rf.valid_tuning_steps = list(TM271_STEPS)
         rf.memory_bounds = (0, 99)
         return rf
 
@@ -1125,47 +1290,19 @@ class TM271Radio(THK2Radio):
     def _cmd_set_memory_name(self, number, name):
         return "MN", "%03i,%s" % (number, name)
 
+
 @directory.register
 class TM281Radio(TM271Radio):
     """Kenwood TM-281"""
     MODEL = "TM-281"
     # seems that this is a perfect clone of TM271 with just a different model
 
-def do_test():
-    """Dev test"""
-    mem = chirp_common.Memory()
-    mem.number = 1
-    mem.freq = 144000000
-    mem.duplex = "split"
-    mem.offset = 146000000
-
-    tc = THF6ARadio
-    class FakeSerial:
-        """Faked serial line"""
-        buf = ""
-        def write(self, buf):
-            """Write"""
-            self.buf = buf
-        def read(self, count):
-            """Read"""
-            if self.buf[:2] == "ID":
-                return "ID %s\r" % tc.MODEL
-            return self.buf
-        def setTimeout(self, foo):
-            """Set Timeout"""
-            pass
-        def setBaudrate(self, foo):
-            """Set Baudrate"""
-            pass
-
-    radio = tc(FakeSerial())
-    radio.set_memory(mem)
 
 @directory.register
 class TM471Radio(THK2Radio):
     """Kenwood TM-471"""
     MODEL = "TM-471"
-    
+
     def get_features(self):
         rf = chirp_common.RadioFeatures()
         rf.can_odd_split = False
@@ -1194,37 +1331,3 @@ class TM471Radio(THK2Radio):
 
     def _cmd_set_memory_name(self, number, name):
         return "MN", "%03i,%s" % (number, name)
-
-def do_test():
-    """Dev test"""
-    mem = chirp_common.Memory()
-    mem.number = 1
-    mem.freq = 440000000
-    mem.duplex = "split"
-    mem.offset = 442000000
-
-    tc = THF6ARadio
-    class FakeSerial:
-        """Faked serial line"""
-        buf = ""
-        def write(self, buf):
-            """Write"""
-            self.buf = buf
-        def read(self, count):
-            """Read"""
-            if self.buf[:2] == "ID":
-                return "ID %s\r" % tc.MODEL
-            return self.buf
-        def setTimeout(self, foo):
-            """Set Timeout"""
-            pass
-        def setBaudrate(self, foo):
-            """Set Baudrate"""
-            pass
-
-    radio = tc(FakeSerial())
-    radio.set_memory(mem)
-
-if __name__ == "__main__":
-    do_test()
-
diff --git a/chirp/drivers/kguv8d.py b/chirp/drivers/kguv8d.py
new file mode 100644
index 0000000..cf130ce
--- /dev/null
+++ b/chirp/drivers/kguv8d.py
@@ -0,0 +1,930 @@
+# Copyright 2014 Ron Wellsted <ron at wellsted.org.uk> M0RNW
+#
+# 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/>.
+
+"""Wouxun KG-UV8D radio management module"""
+
+import time
+import os
+import logging
+from chirp import util, chirp_common, bitwise, memmap, errors, directory
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+                RadioSettingValueBoolean, RadioSettingValueList, \
+                RadioSettingValueInteger, RadioSettingValueString, \
+                RadioSettings
+
+LOG = logging.getLogger(__name__)
+
+CMD_ID = 128
+CMD_END = 129
+CMD_RD = 130
+CMD_WR = 131
+
+MEM_VALID = 158
+
+AB_LIST = ["A", "B"]
+STEPS = [5.0, 6.25, 10.0, 12.5, 25.0, 50.0, 100.0]
+STEP_LIST = [str(x) for x in STEPS]
+ROGER_LIST = ["Off", "BOT", "EOT", "Both"]
+TIMEOUT_LIST = ["Off"] + [str(x) + "s" for x in range(15, 901, 15)]
+VOX_LIST = ["Off"] + ["%s" % x for x in range(1, 10)]
+BANDWIDTH_LIST = ["Narrow", "Wide"]
+VOICE_LIST = ["Off", "On"]
+LANGUAGE_LIST = ["Chinese", "English"]
+SCANMODE_LIST = ["TO", "CO", "SE"]
+PF1KEY_LIST = ["Call", "VFTX"]
+PF3KEY_LIST = ["Scan", "Lamp", "Tele Alarm", "SOS-CH", "Radio", "Disable"]
+WORKMODE_LIST = ["VFO", "Channel No.", "Frequency + No", "Name"]
+BACKLIGHT_LIST = ["Always On"] + [str(x) + "s" for x in range(1, 21)] + \
+                ["Always Off"]
+OFFSET_LIST = ["+", "-"]
+PONMSG_LIST = ["Bitmap", "Battery Volts"]
+SPMUTE_LIST = ["QT", "QT+DTMF", "QT*DTMF"]
+DTMFST_LIST = ["DT-ST", "ANI-ST", "DT-ANI", "Off"]
+RPTSET_LIST = ["X-DIRPT", "X-TWRPT"]
+ALERTS = [1750, 2100, 1000, 1450]
+ALERTS_LIST = [str(x) for x in ALERTS]
+PTTID_LIST = ["BOT", "EOT", "Both"]
+LIST_10 = ["Off"] + ["%s" % x for x in range(1, 11)]
+SCANGRP_LIST = ["All"] + ["%s" % x for x in range(1, 11)]
+SCQT_LIST = ["All", "Decoder", "Encoder"]
+SMUTESET_LIST = ["Off", "Tx", "Rx", "Tx/Rx"]
+POWER_LIST = ["Lo", "Hi"]
+
+# memory slot 0 is not used, start at 1 (so need 1000 slots, not 999)
+# structure elements whose name starts with x are currently unidentified
+_MEM_FORMAT = """
+    #seekto 0x0044;
+    struct {
+        u32    rx_start;
+        u32    rx_stop;
+        u32    tx_start;
+        u32    tx_stop;
+    } uhf_limits;
+
+    #seekto 0x0054;
+    struct {
+        u32    rx_start;
+        u32    rx_stop;
+        u32    tx_start;
+        u32    tx_stop;
+    } vhf_limits;
+
+    #seekto 0x0400;
+    struct {
+        u8     model[8];
+        u8     unknown[2];
+        u8     oem1[10];
+        u8     oem2[10];
+        u8     unknown2[8];
+        u8     version[10];
+        u8     unknown3[6];
+        u8     date[8];
+    } oem_info;
+
+    #seekto 0x0480;
+    struct {
+        u16    lower;
+        u16    upper;
+    } scan_groups[10];
+
+    #seekto 0x0500;
+    struct {
+        u8    call_code[6];
+    } call_groups[20];
+
+    #seekto 0x0580;
+    struct {
+        char    call_name[6];
+    } call_group_name[20];
+
+    #seekto 0x0800;
+    struct {
+        u8      ponmsg;
+        char    dispstr[15];
+        u8 x0810;
+        u8 x0811;
+        u8 x0812;
+        u8 x0813;
+        u8 x0814;
+        u8      voice;
+        u8      timeout;
+        u8      toalarm;
+        u8      channel_menu;
+        u8      power_save;
+        u8      autolock;
+        u8      keylock;
+        u8      beep;
+        u8      stopwatch;
+        u8      vox;
+        u8      scan_rev;
+        u8      backlight;
+        u8      roger_beep;
+        u8      mode_sw_pwd[6];
+        u8      reset_pwd[6];
+        u16     pri_ch;
+        u8      ani_sw;
+        u8      ptt_delay;
+        u8      ani[6];
+        u8      dtmf_st;
+        u8      bcl_a;
+        u8      bcl_b;
+        u8      ptt_id;
+        u8      prich_sw;
+        u8      rpt_set;
+        u8      rpt_spk;
+        u8      rpt_ptt;
+        u8      alert;
+        u8      pf1_func;
+        u8      pf3_func;
+        u8      workmode_a;
+        u8      workmode_b;
+        u8 x0845;
+        u8      dtmf_tx_time;
+        u8      dtmf_interval;
+        u8      main_ab;
+        u16     work_cha;
+        u16     work_chb;
+        u8 x084d;
+        u8 x084e;
+        u8 x084f;
+        u8 x0850;
+        u8 x0851;
+        u8 x0852;
+        u8 x0853;
+        u8 x0854;
+        u8      rpt_mode;
+        u8      language;
+        u8 x0857;
+        u8 x0858;
+        u8 x0859;
+        u8 x085a;
+        u8 x085b;
+        u8 x085c;
+        u8 x085d;
+        u8 x085e;
+        u8      single_display;
+        u8      ring;
+        u8      scg_a;
+        u8      scg_b;
+        u8 x0863;
+        u8      rpt_tone;
+        u8      rpt_hold;
+        u8      scan_det;
+        u8      sc_qt;
+        u8 x0868;
+        u8      smuteset;
+        u8      callcode;
+    } settings;
+
+    #seekto 0x0880;
+    struct {
+        u32     rxfreq;
+        u32     txoffset;
+        u16     rxtone;
+        u16     txtone;
+        u8      unknown1:6,
+                power:1,
+                unknown2:1;
+        u8      unknown3:1,
+                shift_dir:2
+                unknown4:2,
+                mute_mode:2,
+                iswide:1;
+        u8      step;
+        u8      squelch;
+      } vfoa;
+
+    #seekto 0x08c0;
+    struct {
+        u32     rxfreq;
+        u32     txoffset;
+        u16     rxtone;
+        u16     txtone;
+        u8      unknown1:6,
+                power:1,
+                unknown2:1;
+        u8      unknown3:1,
+                shift_dir:2
+                unknown4:2,
+                mute_mode:2,
+                iswide:1;
+        u8      step;
+        u8      squelch;
+    } vfob;
+
+    #seekto 0x0900;
+    struct {
+        u32     rxfreq;
+        u32     txfreq;
+        u16     rxtone;
+        u16     txtone;
+        u8      unknown1:6,
+                power:1,
+                unknown2:1;
+        u8      unknown3:2,
+                scan_add:1,
+                unknown4:2,
+                mute_mode:2,
+                iswide:1;
+        u16     padding;
+    } memory[1000];
+
+    #seekto 0x4780;
+    struct {
+        u8    name[8];
+    } names[1000];
+
+    #seekto 0x6700;
+    u8          valid[1000];
+    """
+
+# Support for the Wouxun KG-UV8D radio
+# Serial coms are at 19200 baud
+# The data is passed in variable length records
+# Record structure:
+#  Offset   Usage
+#    0      start of record (\x7d)
+#    1      Command (\x80 Identify \x81 End/Reboot \x82 Read \x83 Write)
+#    2      direction (\xff PC-> Radio, \x00 Radio -> PC)
+#    3      length of payload (excluding header/checksum) (n)
+#    4      payload (n bytes)
+#    4+n+1  checksum - byte sum (% 256) of bytes 1 -> 4+n
+#
+# Memory Read Records:
+# the payload is 3 bytes, first 2 are offset (big endian),
+# 3rd is number of bytes to read
+# Memory Write Records:
+# the maximum payload size (from the Wouxun software) seems to be 66 bytes
+#  (2 bytes location + 64 bytes data).
+
+
+ at directory.register
+class KGUV8DRadio(chirp_common.CloneModeRadio,
+                  chirp_common.ExperimentalRadio):
+    """Wouxun KG-UV8D"""
+    VENDOR = "Wouxun"
+    MODEL = "KG-UV8D"
+    _model = "KG-UV8D"
+    _file_ident = "KGUV8D"
+    BAUD_RATE = 19200
+    POWER_LEVELS = [chirp_common.PowerLevel("L", watts=1),
+                    chirp_common.PowerLevel("H", watts=5)]
+    _mmap = ""
+
+    def _checksum(self, data):
+        cs = 0
+        for byte in data:
+            cs += ord(byte)
+        return cs % 256
+
+    def _write_record(self, cmd, payload=None):
+        # build the packet
+        _packet = '\x7d' + chr(cmd) + '\xff'
+        _length = 0
+        if payload:
+            _length = len(payload)
+        # update the length field
+        _packet += chr(_length)
+        if payload:
+            # add the chars to the packet
+            _packet += payload
+        # calculate and add the checksum to the packet
+        _packet += chr(self._checksum(_packet[1:]))
+        LOG.debug("Sent:\n%s" % util.hexprint(_packet))
+        self.pipe.write(_packet)
+
+    def _read_record(self):
+        # read 4 chars for the header
+        _header = self.pipe.read(4)
+        _length = ord(_header[3])
+        _packet = self.pipe.read(_length)
+        _cs = self._checksum(_header[1:])
+        _cs += self._checksum(_packet)
+        _cs %= 256
+        _rcs = ord(self.pipe.read(1))
+        LOG.debug("_cs =%x", _cs)
+        LOG.debug("_rcs=%x", _rcs)
+        return (_rcs != _cs, _packet)
+
+# Identify the radio
+#
+# A Gotcha: the first identify packet returns a bad checksum, subsequent
+# attempts return the correct checksum... (well it does on my radio!)
+#
+# The ID record returned by the radio also includes the current frequency range
+# as 4 bytes big-endian in 10Hz increments
+#
+# Offset
+#  0:10     Model, zero padded (Use first 7 chars for 'KG-UV8D')
+#  11:14    UHF rx lower limit (in units of 10Hz)
+#  15:18    UHF rx upper limit
+#  19:22    UHF tx lower limit
+#  23:26    UHF tx upper limit
+#  27:30    VHF rx lower limit
+#  31:34    VHF rx upper limit
+#  35:38    VHF tx lower limit
+#  39:42    VHF tx upper limit
+##
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return cls._file_ident in filedata[0x400:0x408]
+
+    def _identify(self):
+        """Do the identification dance"""
+        for _i in range(0, 10):
+            self._write_record(CMD_ID)
+            _chksum_err, _resp = self._read_record()
+            LOG.debug("Got:\n%s" % util.hexprint(_resp))
+            if _chksum_err:
+                LOG.error("Checksum error: retrying ident...")
+                time.sleep(0.100)
+                continue
+            LOG.debug("Model %s" % util.hexprint(_resp[0:7]))
+            if _resp[0:7] == self._model:
+                return
+            if len(_resp) == 0:
+                raise Exception("Radio not responding")
+            else:
+                raise Exception("Unable to identify radio")
+
+    def _finish(self):
+        self._write_record(CMD_END)
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(_MEM_FORMAT, self._mmap)
+
+    def sync_in(self):
+        try:
+            self._mmap = self._download()
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            raise errors.RadioError("Failed to communicate with radio: %s" % e)
+        self.process_mmap()
+
+    def sync_out(self):
+        self._upload()
+
+    # TODO: This is a dumb, brute force method of downlolading the memory.
+    # it would be smarter to only load the active areas and none of
+    # the padding/unused areas.
+    def _download(self):
+        """Talk to a wouxun KG-UV8D and do a download"""
+        try:
+            self._identify()
+            return self._do_download(0, 32768, 64)
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            raise errors.RadioError("Failed to communicate with radio: %s" % e)
+
+    def _do_download(self, start, end, blocksize):
+        # allocate & fill memory
+        image = ""
+        for i in range(start, end, blocksize):
+            req = chr(i / 256) + chr(i % 256) + chr(blocksize)
+            self._write_record(CMD_RD, req)
+            cs_error, resp = self._read_record()
+            if cs_error:
+                # TODO: probably should retry a few times here
+                LOG.debug(util.hexprint(resp))
+                raise Exception("Checksum error on read")
+            LOG.debug("Got:\n%s" % util.hexprint(resp))
+            image += resp[2:]
+            if self.status_fn:
+                status = chirp_common.Status()
+                status.cur = i
+                status.max = end
+                status.msg = "Cloning from radio"
+                self.status_fn(status)
+        self._finish()
+        return memmap.MemoryMap(''.join(image))
+
+    def _upload(self):
+        """Talk to a wouxun KG-UV8D and do a upload"""
+        try:
+            self._identify()
+            self._do_upload(0, 32768, 64)
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            raise errors.RadioError("Failed to communicate with radio: %s" % e)
+        return
+
+    def _do_upload(self, start, end, blocksize):
+        ptr = start
+        for i in range(start, end, blocksize):
+            req = chr(i / 256) + chr(i % 256)
+            chunk = self.get_mmap()[ptr:ptr + blocksize]
+            self._write_record(CMD_WR, req + chunk)
+            LOG.debug(util.hexprint(req + chunk))
+            cserr, ack = self._read_record()
+            LOG.debug(util.hexprint(ack))
+            j = ord(ack[0]) * 256 + ord(ack[1])
+            if cserr or j != ptr:
+                raise Exception("Radio did not ack block %i" % ptr)
+            ptr += blocksize
+            if self.status_fn:
+                status = chirp_common.Status()
+                status.cur = i
+                status.max = end
+                status.msg = "Cloning to radio"
+                self.status_fn(status)
+        self._finish()
+
+    def get_features(self):
+        # TODO: This probably needs to be setup correctly to match the true
+        # features of the radio
+        rf = chirp_common.RadioFeatures()
+        rf.has_settings = True
+        rf.has_ctone = True
+        rf.has_rx_dtcs = True
+        rf.has_cross = True
+        rf.has_tuning_step = False
+        rf.has_bank = False
+        rf.can_odd_split = True
+        rf.valid_skips = ["", "S"]
+        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
+        rf.valid_cross_modes = [
+                        "Tone->Tone",
+                        "Tone->DTCS",
+                        "DTCS->Tone",
+                        "DTCS->",
+                        "->Tone",
+                        "->DTCS",
+                        "DTCS->DTCS",
+                    ]
+        rf.valid_modes = ["FM", "NFM"]
+        rf.valid_power_levels = self.POWER_LEVELS
+        rf.valid_name_length = 8
+        rf.valid_duplexes = ["", "+", "-", "split"]
+        rf.valid_bands = [(134000000, 175000000),  # supports 2m
+                          (400000000, 520000000)]  # supports 70cm
+        rf.valid_characters = chirp_common.CHARSET_ASCII
+        rf.memory_bounds = (1, 999)  # 999 memories
+        return rf
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.experimental = ("This radio driver is currently under development. "
+                           "There are no known issues with it, but you should "
+                           "proceed with caution.")
+        return rp
+
+    def get_raw_memory(self, number):
+        return repr(self._memobj.memory[number])
+
+    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
+
+        tpol = False
+        if _mem.txtone != 0xFFFF and (_mem.txtone & 0x2800) == 0x2800:
+            tcode, tpol = _get_dcs(_mem.txtone)
+            mem.dtcs = tcode
+            txmode = "DTCS"
+        elif _mem.txtone != 0xFFFF and _mem.txtone != 0x0:
+            mem.rtone = (_mem.txtone & 0x7fff) / 10.0
+            txmode = "Tone"
+        else:
+            txmode = ""
+
+        rpol = False
+        if _mem.rxtone != 0xFFFF and (_mem.rxtone & 0x2800) == 0x2800:
+            rcode, rpol = _get_dcs(_mem.rxtone)
+            mem.rx_dtcs = rcode
+            rxmode = "DTCS"
+        elif _mem.rxtone != 0xFFFF and _mem.rxtone != 0x0:
+            mem.ctone = (_mem.rxtone & 0x7fff) / 10.0
+            rxmode = "Tone"
+        else:
+            rxmode = ""
+
+        if txmode == "Tone" and not rxmode:
+            mem.tmode = "Tone"
+        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
+            mem.tmode = "TSQL"
+        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
+            mem.tmode = "DTCS"
+        elif rxmode or txmode:
+            mem.tmode = "Cross"
+            mem.cross_mode = "%s->%s" % (txmode, rxmode)
+
+        # always set it even if no dtcs is used
+        mem.dtcs_polarity = "%s%s" % (tpol or "N", rpol or "N")
+
+        LOG.debug("Got TX %s (%i) RX %s (%i)" %
+                  (txmode, _mem.txtone, rxmode, _mem.rxtone))
+
+    def get_memory(self, number):
+        _mem = self._memobj.memory[number]
+        _nam = self._memobj.names[number]
+
+        mem = chirp_common.Memory()
+        mem.number = number
+        _valid = self._memobj.valid[mem.number]
+
+        LOG.debug("%d %s", number, _valid == MEM_VALID)
+        if _valid != MEM_VALID:
+            mem.empty = True
+            return mem
+        else:
+            mem.empty = False
+
+        mem.freq = int(_mem.rxfreq) * 10
+
+        if int(_mem.rxfreq) == int(_mem.txfreq):
+            mem.duplex = ""
+            mem.offset = 0
+        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
+            mem.duplex = "split"
+            mem.offset = int(_mem.txfreq) * 10
+        else:
+            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
+            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
+
+        for char in _nam.name:
+            if char != 0:
+                mem.name += chr(char)
+        mem.name = mem.name.rstrip()
+
+        self._get_tone(_mem, mem)
+
+        mem.skip = "" if bool(_mem.scan_add) else "S"
+
+        mem.power = self.POWER_LEVELS[_mem.power]
+        mem.mode = _mem.iswide and "FM" or "NFM"
+        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.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
+
+        LOG.debug("Set TX %s (%i) RX %s (%i)" %
+                  (tx_mode, _mem.txtone, rx_mode, _mem.rxtone))
+
+    def set_memory(self, mem):
+        number = mem.number
+
+        _mem = self._memobj.memory[number]
+        _nam = self._memobj.names[number]
+
+        if mem.empty:
+            _mem.set_raw("\x00" * (_mem.size() / 8))
+            self._memobj.valid[number] = 0
+            self._memobj.names[number].set_raw("\x00" * (_nam.size() / 8))
+            return
+
+        _mem.rxfreq = int(mem.freq / 10)
+        if mem.duplex == "split":
+            _mem.txfreq = int(mem.offset / 10)
+        elif mem.duplex == "off":
+            for i in range(0, 4):
+                _mem.txfreq[i].set_raw("\xFF")
+        elif mem.duplex == "+":
+            _mem.txfreq = int(mem.freq / 10) + int(mem.offset / 10)
+        elif mem.duplex == "-":
+            _mem.txfreq = int(mem.freq / 10) - int(mem.offset / 10)
+        else:
+            _mem.txfreq = int(mem.freq / 10)
+        _mem.scan_add = int(mem.skip != "S")
+        _mem.iswide = int(mem.mode == "FM")
+        # set the tone
+        self._set_tone(mem, _mem)
+        # set the power
+        if mem.power:
+            _mem.power = self.POWER_LEVELS.index(mem.power)
+        else:
+            _mem.power = True
+        # TODO: sett the correct mute mode, for now just
+        # set to mute mode to QT (not QT+DTMF or QT*DTMF)
+        _mem.mute_mode = 0
+
+        for i in range(0, len(_nam.name)):
+            if i < len(mem.name) and mem.name[i]:
+                _nam.name[i] = ord(mem.name[i])
+            else:
+                _nam.name[i] = 0x0
+        self._memobj.valid[mem.number] = MEM_VALID
+
+    def _get_settings(self):
+        _settings = self._memobj.settings
+        _vfoa = self._memobj.vfoa
+        _vfob = self._memobj.vfob
+        cfg_grp = RadioSettingGroup("cfg_grp", "Configuration Settings")
+        vfoa_grp = RadioSettingGroup("vfoa_grp", "VFO A Settings")
+        vfob_grp = RadioSettingGroup("vfob_grp", "VFO B Settings")
+        key_grp = RadioSettingGroup("key_grp", "Key Settings")
+        lmt_grp = RadioSettingGroup("lmt_grp", "Frequency Limits")
+        oem_grp = RadioSettingGroup("oem_grp", "OEM Info")
+
+        group = RadioSettings(cfg_grp, vfoa_grp, vfob_grp,
+                              key_grp, lmt_grp, oem_grp)
+
+        #
+        # Configuration Settings
+        #
+        rs = RadioSetting("ponmsg", "Poweron message",
+                          RadioSettingValueList(
+                              PONMSG_LIST, PONMSG_LIST[_settings.ponmsg]))
+        cfg_grp.append(rs)
+        rs = RadioSetting("voice", "Voice Guide",
+                          RadioSettingValueBoolean(_settings.voice))
+        cfg_grp.append(rs)
+        rs = RadioSetting("language", "Language",
+                          RadioSettingValueList(
+                              LANGUAGE_LIST,
+                              LANGUAGE_LIST[_settings.language]))
+        cfg_grp.append(rs)
+        rs = RadioSetting("timeout", "Timeout Timer",
+                          RadioSettingValueInteger(
+                              15, 900, _settings.timeout * 15, 15))
+        cfg_grp.append(rs)
+        rs = RadioSetting("toalarm", "Timeout Alarm",
+                          RadioSettingValueInteger(0, 10, _settings.toalarm))
+        cfg_grp.append(rs)
+        rs = RadioSetting("channel_menu", "Menu available in channel mode",
+                          RadioSettingValueBoolean(_settings.channel_menu))
+        cfg_grp.append(rs)
+        rs = RadioSetting("power_save", "Power save",
+                          RadioSettingValueBoolean(_settings.power_save))
+        cfg_grp.append(rs)
+        rs = RadioSetting("autolock", "Autolock",
+                          RadioSettingValueBoolean(_settings.autolock))
+        cfg_grp.append(rs)
+        rs = RadioSetting("keylock", "Keypad Lock",
+                          RadioSettingValueBoolean(_settings.keylock))
+        cfg_grp.append(rs)
+        rs = RadioSetting("beep", "Keypad Beep",
+                          RadioSettingValueBoolean(_settings.keylock))
+        cfg_grp.append(rs)
+        rs = RadioSetting("stopwatch", "Stopwatch",
+                          RadioSettingValueBoolean(_settings.keylock))
+        cfg_grp.append(rs)
+
+        #
+        # VFO A Settings
+        #
+        rs = RadioSetting("vfoa_mode", "VFO A Workmode",
+                          RadioSettingValueList(
+                              WORKMODE_LIST,
+                              WORKMODE_LIST[_settings.workmode_a]))
+        vfoa_grp.append(rs)
+        rs = RadioSetting("vfoa_chan", "VFO A Channel",
+                          RadioSettingValueInteger(1, 999, _settings.work_cha))
+        vfoa_grp.append(rs)
+        rs = RadioSetting("rxfreqa", "VFO A Rx Frequency",
+                          RadioSettingValueInteger(
+                              134000000, 520000000, _vfoa.rxfreq * 10, 5000))
+        vfoa_grp.append(rs)
+        #   u32   txoffset;
+        #   u16   rxtone;
+        #   u16   txtone;
+        #   u8    unknown1:6,
+        rs = RadioSetting("vfoa_power", "VFO A Power",
+                          RadioSettingValueList(
+                              POWER_LIST, POWER_LIST[_vfoa.power]))
+        vfoa_grp.append(rs)
+        #         unknown2:1;
+        #   u8    unknown3:1,
+        #         shift_dir:2
+        #         unknown4:2,
+        rs = RadioSetting("vfoa_mute_mode", "VFO A Mute",
+                          RadioSettingValueList(
+                              SPMUTE_LIST, SPMUTE_LIST[_vfoa.mute_mode]))
+        vfoa_grp.append(rs)
+        rs = RadioSetting("vfoa_iswide", "VFO A NBFM",
+                          RadioSettingValueList(
+                              BANDWIDTH_LIST, BANDWIDTH_LIST[_vfoa.iswide]))
+        vfoa_grp.append(rs)
+        rs = RadioSetting("vfoa_step", "VFO A Step (kHz)",
+                          RadioSettingValueList(
+                              STEP_LIST, STEP_LIST[_vfoa.step]))
+        vfoa_grp.append(rs)
+        rs = RadioSetting("vfoa_squelch", "VFO A Squelch",
+                          RadioSettingValueList(
+                              LIST_10, LIST_10[_vfoa.squelch]))
+        vfoa_grp.append(rs)
+        #
+        # VFO B Settings
+        #
+        rs = RadioSetting("vfob_mode", "VFO B Workmode",
+                          RadioSettingValueList(
+                              WORKMODE_LIST,
+                              WORKMODE_LIST[_settings.workmode_b]))
+        vfob_grp.append(rs)
+        rs = RadioSetting("vfob_chan", "VFO B Channel",
+                          RadioSettingValueInteger(1, 999, _settings.work_chb))
+        vfob_grp.append(rs)
+        rs = RadioSetting("rxfreqb", "VFO B Rx Frequency",
+                          RadioSettingValueInteger(
+                              134000000, 520000000, _vfob.rxfreq * 10, 5000))
+        vfob_grp.append(rs)
+        #   u32   txoffset;
+        #   u16   rxtone;
+        #   u16   txtone;
+        #   u8    unknown1:6,
+        rs = RadioSetting("vfob_power", "VFO B Power",
+                          RadioSettingValueList(
+                              POWER_LIST, POWER_LIST[_vfob.power]))
+        vfob_grp.append(rs)
+        #         unknown2:1;
+        #   u8    unknown3:1,
+        #         shift_dir:2
+        #         unknown4:2,
+        rs = RadioSetting("vfob_mute_mode", "VFO B Mute",
+                          RadioSettingValueList(
+                              SPMUTE_LIST, SPMUTE_LIST[_vfob.mute_mode]))
+        vfob_grp.append(rs)
+        rs = RadioSetting("vfob_iswide", "VFO B NBFM",
+                          RadioSettingValueList(
+                              BANDWIDTH_LIST, BANDWIDTH_LIST[_vfob.iswide]))
+        vfob_grp.append(rs)
+        rs = RadioSetting("vfob_step", "VFO B Step (kHz)",
+                          RadioSettingValueList(
+                              STEP_LIST, STEP_LIST[_vfob.step]))
+        vfob_grp.append(rs)
+        rs = RadioSetting("vfob_squelch", "VFO B Squelch",
+                          RadioSettingValueList(
+                              LIST_10, LIST_10[_vfob.squelch]))
+        vfob_grp.append(rs)
+
+        #
+        # Key Settings
+        #
+        _msg = str(_settings.dispstr).split("\0")[0]
+        val = RadioSettingValueString(0, 15, _msg)
+        val.set_mutable(True)
+        rs = RadioSetting("dispstr", "Display Message", val)
+        key_grp.append(rs)
+        _ani = ""
+        for i in _settings.ani:
+            if i < 10:
+                _ani += chr(i + 0x30)
+            else:
+                break
+        val = RadioSettingValueString(0, 6, _ani)
+        val.set_mutable(True)
+        rs = RadioSetting("ani", "ANI code", val)
+        key_grp.append(rs)
+        rs = RadioSetting("pf1_func", "PF1 Key function",
+                          RadioSettingValueList(
+                              PF1KEY_LIST,
+                              PF1KEY_LIST[self._memobj.settings.pf1_func]))
+        key_grp.append(rs)
+        rs = RadioSetting("pf3_func", "PF3 Key function",
+                          RadioSettingValueList(
+                              PF3KEY_LIST,
+                              PF3KEY_LIST[self._memobj.settings.pf3_func]))
+        key_grp.append(rs)
+
+        #
+        # Scan Group Settings
+        #
+        # settings:
+        #   u8    scg_a;
+        #   u8    scg_b;
+        #
+        #   struct {
+        #       u16    lower;
+        #       u16    upper;
+        #   } scan_groups[10];
+
+        #
+        # Call group settings
+        #
+
+        #
+        # Limits settings
+        #
+        rs = RadioSetting("urx_start", "UHF RX Lower Limit",
+                          RadioSettingValueInteger(
+                              400000000, 520000000,
+                              self._memobj.uhf_limits.rx_start * 10, 5000))
+        lmt_grp.append(rs)
+        rs = RadioSetting("urx_stop", "UHF RX Upper Limit",
+                          RadioSettingValueInteger(
+                              400000000, 520000000,
+                              self._memobj.uhf_limits.rx_stop * 10, 5000))
+        lmt_grp.append(rs)
+        rs = RadioSetting("utx_start", "UHF TX Lower Limit",
+                          RadioSettingValueInteger(
+                              400000000, 520000000,
+                              self._memobj.uhf_limits.tx_start * 10, 5000))
+        lmt_grp.append(rs)
+        rs = RadioSetting("utx_stop", "UHF TX Upper Limit",
+                          RadioSettingValueInteger(
+                              400000000, 520000000,
+                              self._memobj.uhf_limits.tx_stop * 10, 5000))
+        lmt_grp.append(rs)
+        rs = RadioSetting("vrx_start", "VHF RX Lower Limit",
+                          RadioSettingValueInteger(
+                              134000000, 174997500,
+                              self._memobj.vhf_limits.rx_start * 10, 5000))
+        lmt_grp.append(rs)
+        rs = RadioSetting("vrx_stop", "VHF RX Upper Limit",
+                          RadioSettingValueInteger(
+                              134000000, 174997500,
+                              self._memobj.vhf_limits.rx_stop * 10, 5000))
+        lmt_grp.append(rs)
+        rs = RadioSetting("vtx_start", "VHF TX Lower Limit",
+                          RadioSettingValueInteger(
+                              134000000, 174997500,
+                              self._memobj.vhf_limits.tx_start * 10, 5000))
+        lmt_grp.append(rs)
+        rs = RadioSetting("vtx_stop", "VHF TX Upper Limit",
+                          RadioSettingValueInteger(
+                              134000000, 174997500,
+                              self._memobj.vhf_limits.tx_stop * 10, 5000))
+        lmt_grp.append(rs)
+
+        #
+        # OEM info
+        #
+        def _decode(lst):
+            _str = ''.join([chr(c) for c in lst
+                            if chr(c) in chirp_common.CHARSET_ASCII])
+            return _str
+
+        _str = _decode(self._memobj.oem_info.model)
+        val = RadioSettingValueString(0, 15, _str)
+        val.set_mutable(False)
+        rs = RadioSetting("model", "Model", val)
+        oem_grp.append(rs)
+        _str = _decode(self._memobj.oem_info.oem1)
+        val = RadioSettingValueString(0, 15, _str)
+        val.set_mutable(False)
+        rs = RadioSetting("oem1", "OEM String 1", val)
+        oem_grp.append(rs)
+        _str = _decode(self._memobj.oem_info.oem2)
+        val = RadioSettingValueString(0, 15, _str)
+        val.set_mutable(False)
+        rs = RadioSetting("oem2", "OEM String 2", val)
+        oem_grp.append(rs)
+        _str = _decode(self._memobj.oem_info.version)
+        val = RadioSettingValueString(0, 15, _str)
+        val.set_mutable(False)
+        rs = RadioSetting("version", "Software Version", val)
+        oem_grp.append(rs)
+        _str = _decode(self._memobj.oem_info.date)
+        val = RadioSettingValueString(0, 15, _str)
+        val.set_mutable(False)
+        rs = RadioSetting("date", "OEM Date", val)
+        oem_grp.append(rs)
+
+        return group
+
+    def get_settings(self):
+        try:
+            return self._get_settings()
+        except:
+            import traceback
+            LOG.error("Failed to parse settings: %s", traceback.format_exc())
+            return None
diff --git a/chirp/drivers/kyd.py b/chirp/drivers/kyd.py
new file mode 100644
index 0000000..87c46cc
--- /dev/null
+++ b/chirp/drivers/kyd.py
@@ -0,0 +1,506 @@
+# Copyright 2014 Jim Unroe <rock.unroe at gmail.com>
+# Copyright 2014 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import time
+import os
+import struct
+import logging
+
+from chirp import chirp_common, directory, memmap
+from chirp import bitwise, errors, util
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettings
+
+LOG = logging.getLogger(__name__)
+
+MEM_FORMAT = """
+#seekto 0x0010;
+struct {
+  lbcd rxfreq[4];
+  lbcd txfreq[4];
+  ul16 rx_tone;
+  ul16 tx_tone;
+  u8 unknown1:3,
+     bcl:2,       // Busy Lock
+     unknown2:3;
+  u8 unknown3:2,
+     highpower:1, // Power Level
+     wide:1,      // Bandwidth
+     unknown4:4;
+  u8 unknown5[2];
+} memory[16];
+
+#seekto 0x012F;
+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
+} 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"]
+
+SETTING_LISTS = {
+    "bcl": BCL_LIST,
+    "tot": TIMEOUTTIMER_LIST,
+    "totalert": TOTALERT_LIST,
+    "voice": VOICE_LIST,
+    "vox": VOX_LIST,
+    "voxdelay": VOXDELAY_LIST,
+    }
+
+
+def _nc630a_enter_programming_mode(radio):
+    serial = radio.pipe
+
+    try:
+        serial.write("\x02")
+        time.sleep(0.1)
+        serial.write("PROGRAM")
+        ack = serial.read(1)
+    except:
+        raise errors.RadioError("Error communicating with radio")
+
+    if not ack:
+        raise errors.RadioError("No response from radio")
+    elif ack != CMD_ACK:
+        raise errors.RadioError("Radio refused to enter programming mode")
+
+    try:
+        serial.write("\x02")
+        ident = serial.read(8)
+    except:
+        raise errors.RadioError("Error communicating with radio")
+
+    if not ident.startswith("P32073"):
+        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 _nc630a_exit_programming_mode(radio):
+    serial = radio.pipe
+    try:
+        serial.write("E")
+    except:
+        raise errors.RadioError("Radio refused to exit programming mode")
+
+
+def _nc630a_read_block(radio, block_addr, block_size):
+    serial = radio.pipe
+
+    cmd = struct.pack(">cHb", 'R', block_addr, BLOCK_SIZE)
+    expectedresponse = "W" + cmd[1:]
+    LOG.debug("Reading block %04x..." % (block_addr))
+
+    try:
+        serial.write(cmd)
+        response = serial.read(4 + BLOCK_SIZE)
+        if response[:4] != expectedresponse:
+            raise Exception("Error reading block %04x." % (block_addr))
+
+        block_data = response[4:]
+
+        serial.write(CMD_ACK)
+        ack = serial.read(1)
+    except:
+        raise errors.RadioError("Failed to read block at %04x" % block_addr)
+
+    if ack != CMD_ACK:
+        raise Exception("No ACK reading block %04x." % (block_addr))
+
+    return block_data
+
+
+def _nc630a_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 + 8]
+
+    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")
+    _nc630a_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 = _nc630a_read_block(radio, addr, BLOCK_SIZE)
+        data += block
+
+        LOG.debug("Address: %04x" % addr)
+        LOG.debug(util.hexprint(block))
+
+    _nc630a_exit_programming_mode(radio)
+
+    return memmap.MemoryMap(data)
+
+
+def do_upload(radio):
+    status = chirp_common.Status()
+    status.msg = "Uploading to radio"
+
+    _nc630a_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)
+            _nc630a_write_block(radio, addr, BLOCK_SIZE)
+
+    _nc630a_exit_programming_mode(radio)
+
+
+ at directory.register
+class NC630aRadio(chirp_common.CloneModeRadio):
+    """KYD NC-630A"""
+    VENDOR = "KYD"
+    MODEL = "NC-630A"
+    BAUD_RATE = 9600
+
+    _ranges = [
+               (0x0000, 0x0338),
+              ]
+    _memsize = 0x0338
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.has_settings = True
+        rf.has_bank = False
+        rf.has_ctone = True
+        rf.has_cross = True
+        rf.has_rx_dtcs = True
+        rf.has_tuning_step = False
+        rf.can_odd_split = True
+        rf.has_name = False
+        rf.valid_skips = ["", "S"]
+        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
+        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
+                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
+        rf.valid_power_levels = 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)]
+
+        return rf
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+    def sync_in(self):
+        self._mmap = do_download(self)
+        self.process_mmap()
+
+    def sync_out(self):
+        do_upload(self)
+
+    def get_raw_memory(self, number):
+        return repr(self._memobj.memory[number - 1])
+
+    def _get_tone(self, _mem, mem):
+        def _get_dcs(val):
+            code = int("%03o" % (val & 0x07FF))
+            pol = (val & 0x8000) and "R" or "N"
+            return code, pol
+
+        if _mem.tx_tone != 0xFFFF and _mem.tx_tone > 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"
+        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"
+        else:
+            rxmode = ""
+
+        if txmode == "Tone" and not rxmode:
+            mem.tmode = "Tone"
+        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
+            mem.tmode = "TSQL"
+        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
+            mem.tmode = "DTCS"
+        elif rxmode or txmode:
+            mem.tmode = "Cross"
+            mem.cross_mode = "%s->%s" % (txmode, rxmode)
+
+        if mem.tmode == "DTCS":
+            mem.dtcs_polarity = "%s%s" % (tpol, rpol)
+
+        LOG.debug("Got TX %s (%i) RX %s (%i)" %
+                  (txmode, _mem.tx_tone, rxmode, _mem.rx_tone))
+
+    def get_memory(self, number):
+        bitpos = (1 << ((number - 1) % 8))
+        bytepos = ((number - 1) / 8)
+        LOG.debug("bitpos %s" % bitpos)
+        LOG.debug("bytepos %s" % bytepos)
+
+        _mem = self._memobj.memory[number - 1]
+        _skp = self._memobj.skipflags[bytepos]
+
+        mem = chirp_common.Memory()
+
+        mem.number = number
+        mem.freq = int(_mem.rxfreq) * 10
+
+        # We'll consider any blank (i.e. 0MHz frequency) to be empty
+        if mem.freq == 0:
+            mem.empty = True
+            return mem
+
+        if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF":
+            mem.freq = 0
+            mem.empty = True
+            return mem
+
+        if int(_mem.rxfreq) == int(_mem.txfreq):
+            mem.duplex = ""
+            mem.offset = 0
+        else:
+            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
+            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
+
+        mem.mode = _mem.wide and "FM" or "NFM"
+
+        self._get_tone(_mem, mem)
+
+        mem.power = NC630A_POWER_LEVELS[_mem.highpower]
+
+        mem.skip = "" if (_skp & bitpos) else "S"
+        LOG.debug("mem.skip %s" % mem.skip)
+
+        mem.extra = RadioSettingGroup("Extra", "extra")
+
+        rs = RadioSetting("bcl", "Busy Channel Lockout",
+                          RadioSettingValueList(
+                              BCL_LIST, BCL_LIST[_mem.bcl]))
+        mem.extra.append(rs)
+
+        return mem
+
+    def _set_tone(self, mem, _mem):
+        def _set_dcs(code, pol):
+            val = int("%i" % code, 8) + 0x2800
+            if pol == "R":
+                val += 0x8000
+            return val
+
+        if mem.tmode == "Cross":
+            tx_mode, rx_mode = mem.cross_mode.split("->")
+        elif mem.tmode == "Tone":
+            tx_mode = mem.tmode
+            rx_mode = None
+        else:
+            tx_mode = rx_mode = mem.tmode
+
+        if tx_mode == "DTCS":
+            _mem.tx_tone = mem.tmode != "DTCS" and \
+                           _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) or \
+                           _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[0])
+        elif tx_mode:
+            _mem.tx_tone = tx_mode == "Tone" and \
+                int(mem.rtone * 10) or int(mem.ctone * 10)
+        else:
+            _mem.tx_tone = 0xFFFF
+
+        if rx_mode == "DTCS":
+            _mem.rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
+        elif rx_mode:
+            _mem.rx_tone = int(mem.ctone * 10)
+        else:
+            _mem.rx_tone = 0xFFFF
+
+        LOG.debug("Set TX %s (%i) RX %s (%i)" %
+                  (tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))
+
+    def set_memory(self, mem):
+        bitpos = (1 << ((mem.number - 1) % 8))
+        bytepos = ((mem.number - 1) / 8)
+        LOG.debug("bitpos %s" % bitpos)
+        LOG.debug("bytepos %s" % bytepos)
+
+        _mem = self._memobj.memory[mem.number - 1]
+        _skp = self._memobj.skipflags[bytepos]
+
+        if mem.empty:
+            _mem.set_raw("\xFF" * (_mem.size() / 8))
+            return
+
+        _mem.rxfreq = mem.freq / 10
+
+        if mem.duplex == "off":
+            for i in range(0, 4):
+                _mem.txfreq[i].set_raw("\xFF")
+        elif mem.duplex == "split":
+            _mem.txfreq = mem.offset / 10
+        elif mem.duplex == "+":
+            _mem.txfreq = (mem.freq + mem.offset) / 10
+        elif mem.duplex == "-":
+            _mem.txfreq = (mem.freq - mem.offset) / 10
+        else:
+            _mem.txfreq = mem.freq / 10
+
+        _mem.wide = mem.mode == "FM"
+
+        self._set_tone(mem, _mem)
+
+        _mem.highpower = mem.power == NC630A_POWER_LEVELS[1]
+
+        if mem.skip != "S":
+            _skp |= bitpos
+        else:
+            _skp &= ~bitpos
+        LOG.debug("_skp %s" % _skp)
+
+        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")
+        top = RadioSettings(basic)
+
+        rs = RadioSetting("tot", "Time-out timer",
+                          RadioSettingValueList(
+                              TIMEOUTTIMER_LIST,
+                              TIMEOUTTIMER_LIST[_settings.tot]))
+        basic.append(rs)
+
+        rs = RadioSetting("totalert", "TOT Pre-alert",
+                          RadioSettingValueList(
+                              TOTALERT_LIST,
+                              TOTALERT_LIST[_settings.totalert]))
+        basic.append(rs)
+
+        rs = RadioSetting("vox", "VOX Gain",
+                          RadioSettingValueList(
+                              VOX_LIST, VOX_LIST[_settings.vox]))
+        basic.append(rs)
+
+        rs = RadioSetting("voice", "Voice Annumciation",
+                          RadioSettingValueList(
+                              VOICE_LIST, VOICE_LIST[_settings.voice]))
+        basic.append(rs)
+
+        rs = RadioSetting("squelch", "Squelch Level",
+                          RadioSettingValueInteger(0, 9, _settings.squelch))
+        basic.append(rs)
+
+        rs = RadioSetting("voxdelay", "VOX Delay",
+                          RadioSettingValueList(
+                              VOXDELAY_LIST,
+                              VOXDELAY_LIST[_settings.voxdelay]))
+        basic.append(rs)
+
+        rs = RadioSetting("beep", "Beep",
+                          RadioSettingValueBoolean(_settings.beep))
+        basic.append(rs)
+
+        rs = RadioSetting("save", "Battery Saver",
+                          RadioSettingValueBoolean(_settings.save))
+        basic.append(rs)
+
+        return top
+
+    def set_settings(self, settings):
+        for element in settings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+            else:
+                try:
+                    if "." in element.get_name():
+                        bits = element.get_name().split(".")
+                        obj = self._memobj
+                        for bit in bits[:-1]:
+                            obj = getattr(obj, bit)
+                        setting = bits[-1]
+                    else:
+                        obj = self._memobj.settings
+                        setting = element.get_name()
+
+                    LOG.debug("Setting %s = %s" % (setting, element.value))
+                    setattr(obj, setting, element.value)
+                except Exception, e:
+                    LOG.debug(element.get_name())
+                    raise
diff --git a/chirp/drivers/kyd_IP620.py b/chirp/drivers/kyd_IP620.py
new file mode 100644
index 0000000..4bff0d6
--- /dev/null
+++ b/chirp/drivers/kyd_IP620.py
@@ -0,0 +1,624 @@
+# Copyright 2015 Lepik.stv <lepik.stv at gmail.com>
+# based on modification of Dan Smith's and Jim Unroe original work
+#
+# 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/>.
+
+"""KYD IP-620 radios management module"""
+
+# TODO: Power on message
+# TODO: Channel name
+# TODO: Tuning step
+
+import struct
+import time
+import os
+import logging
+from chirp import util, chirp_common, bitwise, memmap, errors, directory
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+                RadioSettingValueBoolean, RadioSettingValueList, \
+                RadioSettingValueInteger, RadioSettingValueString, \
+                RadioSettings
+
+LOG = logging.getLogger(__name__)
+
+IP620_MEM_FORMAT = """
+#seekto 0x0000;
+struct {           // Channel memory structure
+  lbcd rx_freq[4]; // RX frequency
+  lbcd tx_freq[4]; // TX frequency
+  ul16 rx_tone;    // RX tone
+  ul16 tx_tone;    // TX tone
+  u8 unknown_1:4   // n-a
+     busy_loc:2,   // NO-00, Crrier wave-01, SM-10
+     n_a:2;        // n-a
+  u8 unknown_2:1   // n-a
+     scan_add:1,   // Scan add
+     n_a:1,        // n-a
+     w_n:1,        // Narrow-0 Wide-1
+     lout:1,       // LOCKOUT OFF-0 ON-1
+     n_a_:1,       // n-a
+     power:2;      // Power  low-00 middle-01 high-10
+  u8 unknown_3;    // n-a
+  u8 unknown_4;    // n-a
+} memory[200];
+
+#seekto 0x1000;
+struct {
+  u8 chan_name[6];  //Channel name
+  u8 unknown_1[10];
+} chan_names[200];
+
+#seekto 0x0C80;
+struct {           // Settings memory structure ( A-Frequency mode )
+  lbcd freq_a_rx[4];
+  lbcd freq_a_tx[4];
+  ul16 freq_a_rx_tone;    // RX tone
+  ul16 freq_a_tx_tone;    // TX tone
+  u8 unknown_1_5:4
+  freq_a_busy_loc:2,
+  n_a:2;
+  u8 unknown_1_6:3
+  freq_a_w_n:1,
+  n_a:1,
+  na:1,
+  freq_a_power:2;
+  u8 unknown_1_7;
+  u8 unknown_1_8;
+} settings_freq_a;
+
+#seekto 0x0E20;
+struct {
+  u8 chan_disp_way;  // Channel display way
+  u8 step_freq;      // Step frequency KHz
+  u8 rf_sql;         // Squelch level
+  u8 bat_save;       // Battery Saver
+  u8 chan_pri;       // Channel PRI
+  u8 end_beep;       // End beep
+  u8 tot;            // Time-out timer
+  u8 vox;            // VOX Gain
+  u8 chan_pri_num;   // Channel PRI time Sec
+  u8 n_a_2;
+  u8 ch_mode;        // CH mode
+  u8 n_a_3;
+  u8 call_tone;      // Call tone
+  u8 beep;           // Beep
+  u8 unknown_1_1[2];
+  u8 unknown_1_2[8];
+  u8 scan_rev;       // Scan rev
+  u8 unknown_1_3[2];
+  u8 enc;            // Frequency lock
+  u8 vox_dly;        // VOX Delay
+  u8 wait_back_light;// Wait back light
+  u8 unknown_1_4[2];
+} settings;
+
+#seekto 0x0E40;
+struct {
+  u8 fm_radio;        // FM radio
+  u8 auto_lock;       // Auto lock
+  u8 unknown_1[8];
+  u8 pon_msg[6];      //Power on msg
+} settings_misc;
+
+#seekto 0x1C80;
+struct {
+  u8 unknown_1[16];
+  u8 unknown_2[16];
+} settings_radio_3;
+"""
+
+CMD_ACK = "\x06"
+WRITE_BLOCK_SIZE = 0x10
+READ_BLOCK_SIZE = 0x40
+
+CHAR_LENGTH_MAX = 6
+
+OFF_ON_LIST = ["OFF", "ON"]
+ON_OFF_LIST = ["ON", "OFF"]
+NO_YES_LIST = ["NO", "YES"]
+STEP_LIST = ["5.0", "6.25", "10.0", "12.5", "25.0"]
+BAT_SAVE_LIST = ["OFF", "0.2 Sec", "0.4 Sec", "0.6 Sec", "0.8 Sec","1.0 Sec"]
+SHIFT_LIST = ["", "-", "+"]
+SCANM_LIST = ["Time", "Carrier wave", "Search"]
+ENDBEEP_LIST = ["OFF", "Begin", "End", "Begin/End"]
+POWER_LEVELS = [chirp_common.PowerLevel("Low",  watts=1.00), chirp_common.PowerLevel("Medium", watts=2.50), chirp_common.PowerLevel("High", watts=5.00)]
+TIMEOUT_LIST = ["OFF", "1 Min", "3 Min", "10 Min"]
+TOTALERT_LIST = ["", "OFF"] + ["%s seconds" % x for x in range(1, 11)]
+VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 17)]
+VOXDELAY_LIST = ["0.3 Sec", "0.5 Sec", "1.0 Sec", "1.5 Sec", "2.0 Sec", "3.0 Sec", "4.0 Sec", "5.0 Sec"]
+PRI_NUM = [3, 5, 8, 10]
+PRI_NUM_LIST = [str(x) for x in PRI_NUM]
+CH_FLAG_LIST = ["Channel+Freq", "Channel+Name"]
+BACKLIGHT_LIST = ["Always Off", "Auto", "Always On"]
+BUSYLOCK_LIST = ["NO", "Carrier", "SM"]
+KEYBLOCK_LIST = ["Manual", "Auto"]
+CALLTONE_LIST = ["OFF", "1", "2", "3", "4", "5", "6", "7", "8", "1750"]
+RFSQL_LIST = ["OFF", "S-1", "S-2", "S-3", "S-4", "S-5", "S-6","S-7", "S-8", "S-FULL"]
+
+IP620_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ?+-* "
+
+IP620_BANDS = [
+    (136000000, 174000000),
+    (200000000, 260000000),
+    (300000000, 340000000),  # <--- this band supports only Russian model (ARGUT A-36)
+    (350000000, 390000000),
+    (400000000, 480000000),
+    (420000000, 510000000),
+    (450000000, 520000000),
+]
+
+ at directory.register
+class IP620Radio(chirp_common.CloneModeRadio,
+                chirp_common.ExperimentalRadio):
+    """KYD IP-620"""
+    VENDOR = "KYD"
+    MODEL = "IP-620"
+    BAUD_RATE = 9600
+
+    _ranges = [
+               (0x0000, 0x2000),
+              ]
+    _memsize = 0x2000
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return len(filedata) == cls._memsize and \
+            filedata[0xF7E:0xF80] == "\x01\xE2"
+
+    def _ip620_exit_programming_mode(self):
+        try:
+            self.pipe.write("\x06")
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            raise errors.RadioError("Radio refused to exit programming mode: %s" % e)
+
+    def _ip620_enter_programming_mode(self):
+        try:
+            self.pipe.write("iUHOUXUN")
+            self.pipe.write("\x02")
+            time.sleep(0.2)
+            _ack = self.pipe.read(1)
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            raise errors.RadioError("Error communicating with radio: %s" % e)
+        if not _ack:
+            raise errors.RadioError("No response from radio")
+        elif _ack != CMD_ACK:
+            raise errors.RadioError("Radio refused to enter programming mode")
+        try:
+            self.pipe.write("\x02")
+            _ident = self.pipe.read(8)
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            raise errors.RadioError("Error communicating with radio: %s" % e)
+        if not _ident.startswith("\x06\x4B\x47\x36\x37\x01\x56\xF8"):
+            print util.hexprint(_ident)
+            raise errors.RadioError("Radio returned unknown identification string")
+        try:
+            self.pipe.write(CMD_ACK)
+            _ack = self.pipe.read(1)
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            raise errors.RadioError("Error communicating with radio: %s" % e)
+        if _ack != CMD_ACK:
+            raise errors.RadioError("Radio refused to enter programming mode")
+
+    def _ip620_write_block(self, block_addr):
+        _cmd = struct.pack(">cHb", 'W', block_addr, WRITE_BLOCK_SIZE)
+        _data = self.get_mmap()[block_addr:block_addr + WRITE_BLOCK_SIZE]
+        LOG.debug("Writing Data:")
+        LOG.debug(util.hexprint(_cmd + _data))
+        try:
+            self.pipe.write(_cmd + _data)
+            if self.pipe.read(1) != CMD_ACK:
+                raise Exception("No ACK")
+        except:
+            raise errors.RadioError("Failed to send block "
+                                    "to radio at %04x" % block_addr)
+
+    def _ip620_read_block(self, block_addr):
+        _cmd = struct.pack(">cHb", 'R', block_addr, READ_BLOCK_SIZE)
+        _expectedresponse = "W" + _cmd[1:]
+        LOG.debug("Reading block %04x..." % (block_addr))
+        try:
+            self.pipe.write(_cmd)
+            _response = self.pipe.read(4 + READ_BLOCK_SIZE)
+            if _response[:4] != _expectedresponse:
+                raise Exception("Error reading block %04x." % (block_addr))
+            _block_data = _response[4:]
+            self.pipe.write(CMD_ACK)
+            _ack = self.pipe.read(1)
+        except:
+            raise errors.RadioError("Failed to read block at %04x" % block_addr)
+        if _ack != CMD_ACK:
+            raise Exception("No ACK reading block %04x." % (block_addr))
+        return _block_data
+
+    def _do_download(self):
+        self._ip620_enter_programming_mode()
+        _data = ""
+        _status = chirp_common.Status()
+        _status.msg = "Cloning from radio"
+        _status.cur = 0
+        _status.max = self._memsize
+        for _addr in range(0, self._memsize, READ_BLOCK_SIZE):
+            _status.cur = _addr + READ_BLOCK_SIZE
+            self.status_fn(_status)
+            _block = self._ip620_read_block(_addr)
+            _data += _block
+            LOG.debug("Address: %04x" % _addr)
+            LOG.debug(util.hexprint(_block))
+        self._ip620_exit_programming_mode()
+        return memmap.MemoryMap(_data)
+
+    def _do_upload(self):
+        _status = chirp_common.Status()
+        _status.msg = "Uploading to radio"
+        self._ip620_enter_programming_mode()
+        _status.cur = 0
+        _status.max = self._memsize
+        for _start_addr, _end_addr in self._ranges:
+            for _addr in range(_start_addr, _end_addr, WRITE_BLOCK_SIZE):
+                _status.cur = _addr + WRITE_BLOCK_SIZE
+                self.status_fn(_status)
+                self._ip620_write_block(_addr)
+        self._ip620_exit_programming_mode()
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.experimental = ("This radio driver is currently under development. "
+                           "There are no known issues with it, but you should "
+                           "proceed with caution. However, proceed at your own risk!")
+        return rp
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.has_settings = True
+        rf.has_bank = False
+        rf.has_ctone = True
+        rf.has_cross = False
+        rf.has_rx_dtcs = True
+        rf.has_tuning_step = False
+        rf.can_odd_split = False
+        rf.has_name = False
+        rf.valid_skips = []
+        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_duplexes = SHIFT_LIST
+        rf.valid_modes = ["FM", "NFM"]
+        rf.memory_bounds = (1, 200)
+        rf.valid_bands = IP620_BANDS
+        rf.valid_characters = ''.join(set(IP620_CHARSET))
+        rf.valid_name_length = CHAR_LENGTH_MAX
+        return rf
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(IP620_MEM_FORMAT, self._mmap)
+
+    def sync_in(self):
+        try:
+            self._mmap = self._do_download()
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            raise errors.RadioError("Failed to communicate with radio: %s" % e)
+        self.process_mmap()
+
+    def sync_out(self):
+        self._do_upload()
+
+    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"
+        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"
+        else:
+            rxmode = ""
+
+        if txmode == "Tone" and not rxmode:
+            mem.tmode = "Tone"
+        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
+            mem.tmode = "TSQL"
+        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
+            mem.tmode = "DTCS"
+        elif rxmode or txmode:
+            mem.tmode = "Cross"
+            mem.cross_mode = "%s->%s" % (txmode, rxmode)
+
+        if mem.tmode == "DTCS":
+            mem.dtcs_polarity = "%s%s" % (tpol, rpol)
+
+        LOG.debug("Got TX %s (%i) RX %s (%i)" % (txmode, _mem.tx_tone,
+                                              rxmode, _mem.rx_tone))
+
+    def get_memory(self, number):
+        _mem = self._memobj.memory[number - 1]
+        _nam = self._memobj.chan_names[number - 1]
+
+        def _is_empty():
+            for i in range(0, 4):
+                if _mem.rx_freq[i].get_raw() != "\xFF":
+                    return False
+            return True
+
+        mem = chirp_common.Memory()
+        mem.number = number
+
+        if _is_empty():
+            mem.empty = True
+            return mem
+
+        mem.freq = int(_mem.rx_freq) * 10
+
+        if int(_mem.rx_freq) == int(_mem.tx_freq):
+            mem.duplex = ""
+            mem.offset = 0
+        else:
+            mem.duplex = int(_mem.rx_freq) > int(_mem.tx_freq) and "-" or "+"
+            mem.offset = abs(int(_mem.rx_freq) - int(_mem.tx_freq)) * 10
+
+        mem.mode = _mem.w_n and "FM" or "NFM"
+        self._get_tone(_mem, mem)
+        mem.power = POWER_LEVELS[_mem.power]
+
+        mem.extra = RadioSettingGroup("Extra", "extra")
+        rs = RadioSetting("lout", "Lock out",
+                          RadioSettingValueList(OFF_ON_LIST,
+                          OFF_ON_LIST[_mem.lout]))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("busy_loc", "Busy lock",
+                          RadioSettingValueList(BUSYLOCK_LIST,
+                          BUSYLOCK_LIST[_mem.busy_loc]))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("scan_add", "Scan add",
+                          RadioSettingValueList(NO_YES_LIST,
+                          NO_YES_LIST[_mem.scan_add]))
+        mem.extra.append(rs)
+        #TODO: Show name channel
+##        count = 0
+##        for i in _nam.chan_name:
+##            if i == 0xFF:
+##                break
+##            try:
+##                mem.name += IP620_CHARSET[i]
+##            except Exception:
+##                LOG.error("Unknown name char %i: 0x%02x (mem %i)" %
+##                          (count, i, number - 1))
+##                mem.name += " "
+##            count += 1
+##        mem.name = mem.name.rstrip()
+
+        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):
+        _mem = self._memobj.memory[mem.number - 1]
+        if mem.empty:
+            _mem.set_raw("\xFF" * (_mem.size() / 8))
+            return
+
+        _mem.rx_freq = mem.freq / 10
+        if mem.duplex == "OFF":
+            for i in range(0, 4):
+                _mem.tx_freq[i].set_raw("\xFF")
+        elif mem.duplex == "+":
+            _mem.tx_freq = (mem.freq + mem.offset) / 10
+        elif mem.duplex == "-":
+            _mem.tx_freq = (mem.freq - mem.offset) / 10
+        else:
+            _mem.tx_freq = mem.freq / 10
+
+        _mem.w_n = mem.mode == "FM"
+        self._set_tone(mem, _mem)
+        _mem.power = mem.power == POWER_LEVELS[1]
+
+        for setting in mem.extra:
+            setattr(_mem, setting.get_name(), setting.value)
+
+    def get_settings(self):
+        _settings = self._memobj.settings
+        _settings_misc = self._memobj.settings_misc
+        basic = RadioSettingGroup("basic", "Basic Settings")
+        top = RadioSettings(basic)
+
+        rs = RadioSetting("rf_sql", "Squelch level (SQL)",
+                          RadioSettingValueList(RFSQL_LIST,
+                          RFSQL_LIST[_settings.rf_sql]))
+        basic.append(rs)
+
+        rs = RadioSetting("step_freq", "Step frequency KHz (STP)",
+                          RadioSettingValueList(STEP_LIST,
+                          STEP_LIST[_settings.step_freq]))
+        basic.append(rs)
+
+        rs = RadioSetting("fm_radio", "FM radio (DW)",
+                          RadioSettingValueList(OFF_ON_LIST,
+                          OFF_ON_LIST[_settings_misc.fm_radio]))
+        basic.append(rs)
+
+        rs = RadioSetting("call_tone", "Call tone (CK)",
+                          RadioSettingValueList(CALLTONE_LIST,
+                          CALLTONE_LIST[_settings.call_tone]))
+        basic.append(rs)
+
+        rs = RadioSetting("tot", "Time-out timer (TOT)",
+                          RadioSettingValueList(TIMEOUT_LIST,
+                          TIMEOUT_LIST[_settings.tot]))
+        basic.append(rs)
+
+        rs = RadioSetting("chan_disp_way", "Channel display way",
+                          RadioSettingValueList(CH_FLAG_LIST,
+                          CH_FLAG_LIST[_settings.chan_disp_way]))
+        basic.append(rs)
+
+        rs = RadioSetting("vox", "VOX Gain (VOX)",
+                          RadioSettingValueList(VOX_LIST,
+                          VOX_LIST[_settings.vox]))
+        basic.append(rs)
+
+        rs = RadioSetting("vox_dly", "VOX Delay",
+                          RadioSettingValueList(VOXDELAY_LIST,
+                          VOXDELAY_LIST[_settings.vox_dly]))
+        basic.append(rs)
+
+        rs = RadioSetting("beep", "Beep (BP)",
+                          RadioSettingValueList(OFF_ON_LIST,
+                          OFF_ON_LIST[_settings.beep]))
+        basic.append(rs)
+
+        rs = RadioSetting("auto_lock", "Auto lock (KY)",
+                          RadioSettingValueList(NO_YES_LIST,
+                          NO_YES_LIST[_settings_misc.auto_lock]))
+        basic.append(rs)
+
+        rs = RadioSetting("bat_save", "Battery Saver (SAV)",
+                          RadioSettingValueList(BAT_SAVE_LIST,
+                          BAT_SAVE_LIST[_settings.bat_save]))
+        basic.append(rs)
+
+        rs = RadioSetting("chan_pri", "Channel PRI (PRI)",
+                          RadioSettingValueList(OFF_ON_LIST,
+                          OFF_ON_LIST[_settings.chan_pri]))
+        basic.append(rs)
+
+        rs = RadioSetting("chan_pri_num", "Channel PRI time Sec (PRI)",
+                          RadioSettingValueList(PRI_NUM_LIST,
+                          PRI_NUM_LIST[_settings.chan_pri_num]))
+        basic.append(rs)
+
+        rs = RadioSetting("end_beep", "End beep (ET)",
+                          RadioSettingValueList(ENDBEEP_LIST,
+                          ENDBEEP_LIST[_settings.end_beep]))
+        basic.append(rs)
+
+        rs = RadioSetting("ch_mode", "CH mode",
+                          RadioSettingValueList(ON_OFF_LIST,
+                          ON_OFF_LIST[_settings.ch_mode]))
+        basic.append(rs)
+
+        rs = RadioSetting("scan_rev", "Scan rev (SCAN)",
+                          RadioSettingValueList(SCANM_LIST,
+                          SCANM_LIST[_settings.scan_rev]))
+        basic.append(rs)
+
+        rs = RadioSetting("enc", "Frequency lock (ENC)",
+                          RadioSettingValueList(OFF_ON_LIST,
+                          OFF_ON_LIST[_settings.enc]))
+        basic.append(rs)
+
+        rs = RadioSetting("wait_back_light", "Wait back light (LED)",
+                          RadioSettingValueList(BACKLIGHT_LIST,
+                          BACKLIGHT_LIST[_settings.wait_back_light]))
+        basic.append(rs)
+
+        return top
+
+    def _set_misc_settings(self, settings):
+        for element in settings:
+            try:
+                setattr(self._memobj.settings_misc,
+                        element.get_name(),
+                        element.value)
+            except Exception, e:
+                LOG.debug(element.get_name())
+                raise
+
+    def set_settings(self, settings):
+        _settings = self._memobj.settings
+        _settings_misc = self._memobj.settings_misc
+        for element in settings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+            if not element.changed():
+                continue
+            try:
+                setting = element.get_name()
+                if setting in ["auto_lock","fm_radio"]:
+                    oldval = getattr(_settings_misc, setting)
+                else:
+                    oldval = getattr(_settings, setting)
+
+                newval = element.value
+
+                LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval))
+                if setting in ["auto_lock","fm_radio"]:
+                    setattr(_settings_misc, setting, newval)
+                else:
+                    setattr(_settings, setting, newval)
+            except Exception, e:
+                LOG.debug(element.get_name())
+                raise
diff --git a/chirp/drivers/leixen.py b/chirp/drivers/leixen.py
new file mode 100644
index 0000000..caa34dd
--- /dev/null
+++ b/chirp/drivers/leixen.py
@@ -0,0 +1,943 @@
+# Copyright 2014 Tom Hayward <tom at tomh.us>
+#
+# 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 struct
+import os
+import logging
+
+from chirp import chirp_common, directory, memmap, errors, util
+from chirp import bitwise
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettingValueString, \
+    RadioSettingValueFloat, InvalidValueError, RadioSettings
+from textwrap import dedent
+
+LOG = logging.getLogger(__name__)
+
+MEM_FORMAT = """
+#seekto 0x0184;
+struct {
+  u8 unknown:4,
+     sql:4;              // squelch level
+  u8 unknown0x0185;
+  u8 obeep:1,            // open beep
+     dw_off:1,           // dual watch (inverted)
+     kbeep:1,            // key beep
+     rbeep:1,            // roger beep
+     unknown:2,
+     ctdcsb:1,           // ct/dcs busy lock
+     unknown:1;
+  u8 alarm:1,            // alarm key
+     unknown1:1,
+     aliasen_off:1,      // alias enable (inverted)
+     save:1,             // battery save
+     unknown2:2,
+     mrcha:1,            // mr/cha
+     vfomr:1;            // vfo/mr
+  u8 keylock_off:1,      // key lock (inverted)
+     txstop_off:1,       // tx stop (inverted)
+     scanm:1,            // scan key/mode
+     vir:1,              // vox inhibit on receive
+     keylockm:2,         // key lock mode
+     lamp:2;             // backlight
+  u8 opendis:2,          // open display
+     fmen_off:1,         // fm enable (inverted)
+     unknown1:1,
+     fmscan_off:1,       // fm scan (inverted)
+     fmdw:1,             // fm dual watch
+     unknown2:2;
+  u8 step:4,             // step
+     vol:4;              // volume
+  u8 apo:4,              // auto power off
+     tot:4;              // time out timer
+  u8 unknown0x018C;
+  u8 voxdt:4,            // vox delay time
+     voxgain:4;          // vox gain
+  u8 unknown0x018E;
+  u8 unknown0x018F;
+  u8 unknown:3,
+     lptime:5;           // long press time
+  u8 keyp2long:4,        // p2 key long press
+     keyp2short:4;       // p2 key short press
+  u8 keyp1long:4,        // p1 key long press
+     keyp1short:4;       // p1 key short press
+  u8 keyp3long:4,        // p3 key long press
+     keyp3short:4;       // p3 key short press
+  u8 unknown0x0194;
+  u8 menuen:1,           // menu enable
+     absel:1,            // a/b select
+     unknown:2
+     keymshort:4;        // m key short press
+  u8 unknown:4,
+     dtmfst:1,           // dtmf sidetone
+     ackdecode:1,        // ack decode
+     monitor:2;          // monitor
+  u8 unknown1:3,
+     reset:1,            // reset enable
+     unknown2:1,
+     keypadmic_off:1,    // keypad mic (inverted)
+     unknown3:2;
+  u8 unknown0x0198;
+  u8 unknown1:3,
+     dtmftime:5;         // dtmf digit time
+  u8 unknown1:3,
+     dtmfspace:5;        // dtmf digit space time
+  u8 unknown1:2,
+     dtmfdelay:6;        // dtmf first digit delay
+  u8 unknown1:1,
+     dtmfpretime:7;      // dtmf pretime
+  u8 unknown1:2,
+     dtmfdelay2:6;       // dtmf * and # digit delay
+  u8 unknown1:3,
+     smfont_off:1,       // small font (inverted)
+     unknown:4;
+} settings;
+
+#seekto 0x01cd;
+struct {
+  u8 rssi136;            // squelch base level (vhf)
+  u8 unknown0x01ce;
+  u8 rssi400;            // squelch base level (uhf)
+} service;
+
+#seekto 0x0900;
+struct {
+  char user1[7];         // user message 1
+  char unknown0x0907;
+  char unknown0x0908[8];
+  char unknown0x0910[8];
+  char system[7];        // system message
+  char unknown0x091F;
+  char user2[7];         // user message 2
+  char unknown0x0927;
+} messages;
+
+struct channel {
+  bbcd rx_freq[4];
+  bbcd tx_freq[4];
+  u8 rx_tone;
+  u8 rx_tmode;
+  u8 tx_tone;
+  u8 tx_tmode;
+  u8 unknown5;
+  u8 pttidoff:1,
+     dtmfoff:1,
+     unknown6:1,
+     tailcut:1,
+     aliasop:1,
+     talkaroundoff:1,
+     voxoff:1,
+     skip:1;
+  u8 power:1,
+     mode:1
+     reverseoff:1,
+     blckoff:1,
+     unknown7:1,
+     apro:3;
+  u8 unknown8;
+};
+
+struct name {
+    char name[7];
+    u8 pad;
+};
+
+#seekto 0x0d00;
+struct channel default[3];
+struct channel memory[199];
+
+#seekto 0x19b0;
+struct name defaultname[3];
+struct name name[199];
+"""
+
+
+APO_LIST = ["OFF", "10M", "20M", "30M", "40M", "50M", "60M", "90M",
+            "2H", "4H", "6H", "8H", "10H", "12H", "14H", "16H"]
+SQL_LIST = ["%s" % x for x in range(0, 10)]
+SCANM_LIST = ["CO", "TO"]
+TOT_LIST = ["OFF"] + ["%s seconds" % x for x in range(10, 130, 10)]
+STEP_LIST = ["2.5 KHz", "5 KHz", "6.25 KHz", "10 KHz", "12.5 KHz", "25 KHz"]
+MONITOR_LIST = ["CTC/DCS", "DTMF", "CTC/DCS and DTMF", "CTC/DCS or DTMF"]
+VFOMR_LIST = ["MR", "VFO"]
+MRCHA_LIST = ["MR CHA", "Freq. MR"]
+VOL_LIST = ["OFF"] + ["%s" % x for x in range(1, 16)]
+OPENDIS_LIST = ["All", "Lease Time", "User-defined", "Leixen"]
+LAMP_LIST = ["OFF", "KEY", "CONT"]
+KEYLOCKM_LIST = ["K+S", "PTT", "KEY", "ALL"]
+ABSEL_LIST = ["B Channel",  "A Channel"]
+VOXGAIN_LIST = ["%s" % x for x in range(1, 9)]
+VOXDT_LIST = ["%s seconds" % x for x in range(1, 5)]
+DTMFTIME_LIST = ["%i milliseconds" % x for x in range(50, 210, 10)]
+DTMFDELAY_LIST = ["%i milliseconds" % x for x in range(0, 550, 50)]
+DTMFPRETIME_LIST = ["%i milliseconds" % x for x in range(100, 1100, 100)]
+DTMFDELAY2_LIST = ["%i milliseconds" % x for x in range(0, 450, 50)]
+
+LPTIME_LIST = ["%i milliseconds" % x for x in range(500, 2600, 100)]
+PFKEYLONG_LIST = ["OFF",
+                  "FM",
+                  "Monitor Momentary",
+                  "Monitor Lock",
+                  "SQ Off Momentary",
+                  "Mute",
+                  "SCAN",
+                  "TX Power",
+                  "EMG",
+                  "VFO/MR",
+                  "DTMF",
+                  "CALL",
+                  "Transmit 1750Hz",
+                  "A/B",
+                  "Talk Around",
+                  "Reverse"
+                  ]
+
+PFKEYSHORT_LIST = ["OFF",
+                   "FM",
+                   "BandChange",
+                   "Time",
+                   "Monitor Lock",
+                   "Mute",
+                   "SCAN",
+                   "TX Power",
+                   "EMG",
+                   "VFO/MR",
+                   "DTMF",
+                   "CALL",
+                   "Transmit 1750Hz",
+                   "A/B",
+                   "Talk Around",
+                   "Reverse"
+                   ]
+
+POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=4),
+                chirp_common.PowerLevel("High", watts=10)]
+MODES = ["NFM", "FM"]
+WTFTONES = map(float, xrange(56, 64))
+TONES = WTFTONES + chirp_common.TONES
+DTCS_CODES = [17, 50, 645] + chirp_common.DTCS_CODES
+DTCS_CODES.sort()
+TMODES = ["", "Tone", "DTCS", "DTCS"]
+
+
+def _image_ident_from_data(data):
+    return data[0x168:0x178]
+
+
+def _image_ident_from_image(radio):
+    return _image_ident_from_data(radio.get_mmap())
+
+
+def checksum(frame):
+    x = 0
+    for b in frame:
+        x ^= ord(b)
+    return chr(x)
+
+
+def make_frame(cmd, addr, data=""):
+    payload = struct.pack(">H", addr) + data
+    header = struct.pack(">BB", ord(cmd), len(payload))
+    frame = header + payload
+    return frame + checksum(frame)
+
+
+def send(radio, frame):
+    # LOG.debug("%04i P>R: %s" %
+    #           (len(frame),
+    #            util.hexprint(frame).replace("\n", "\n          ")))
+    try:
+        radio.pipe.write(frame)
+    except Exception, e:
+        raise errors.RadioError("Failed to communicate with radio: %s" % e)
+
+
+def recv(radio, readdata=True):
+    hdr = radio.pipe.read(4)
+    # LOG.debug("%04i P<R: %s" %
+    #           (len(hdr), util.hexprint(hdr).replace("\n", "\n          ")))
+    if hdr == "\x09\x00\x09":
+        raise errors.RadioError("Radio rejected command.")
+    cmd, length, addr = struct.unpack(">BBH", hdr)
+    length -= 2
+    if readdata:
+        data = radio.pipe.read(length)
+        # LOG.debug("     P<R: %s" %
+        #           util.hexprint(hdr + data).replace("\n", "\n          "))
+        if len(data) != length:
+            raise errors.RadioError("Radio sent %i bytes (expected %i)" % (
+                    len(data), length))
+        chk = radio.pipe.read(1)
+    else:
+        data = ""
+    return addr, data
+
+
+def do_ident(radio):
+    send(radio, "\x02\x06LEIXEN\x17")
+    ident = radio.pipe.read(9)
+    LOG.debug("     P<R: %s" %
+              util.hexprint(ident).replace("\n", "\n          "))
+    if ident != "\x06\x06leixen\x13":
+        raise errors.RadioError("Radio refused program mode")
+    radio.pipe.write("\x06\x00\x06")
+    ack = radio.pipe.read(3)
+    if ack != "\x06\x00\x06":
+        raise errors.RadioError("Radio did not ack.")
+
+
+def do_download(radio):
+    do_ident(radio)
+
+    data = ""
+    data += "\xFF" * (0 - len(data))
+    for addr in range(0, radio._memsize, 0x10):
+        send(radio, make_frame("R", addr, chr(0x10)))
+        _addr, _data = recv(radio)
+        if _addr != addr:
+            raise errors.RadioError("Radio sent unexpected address")
+        data += _data
+
+        status = chirp_common.Status()
+        status.cur = addr
+        status.max = radio._memsize
+        status.msg = "Cloning from radio"
+        radio.status_fn(status)
+
+    finish(radio)
+
+    return memmap.MemoryMap(data)
+
+
+def do_upload(radio):
+    _ranges = [(0x0d00, 0x2000)]
+
+    image_ident = _image_ident_from_image(radio)
+    if image_ident.startswith(radio._file_ident) and "LX-" in image_ident:
+        _ranges = radio._ranges
+
+    do_ident(radio)
+
+    for start, end in _ranges:
+        for addr in range(start, end, 0x10):
+            frame = make_frame("W", addr, radio._mmap[addr:addr + 0x10])
+            send(radio, frame)
+            # LOG.debug("     P<R: %s" %
+            #           util.hexprint(frame).replace("\n", "\n          "))
+            radio.pipe.write("\x06\x00\x06")
+            ack = radio.pipe.read(3)
+            if ack != "\x06\x00\x06":
+                raise errors.RadioError("Radio refused block at %04x" % addr)
+
+            status = chirp_common.Status()
+            status.cur = addr
+            status.max = radio._memsize
+            status.msg = "Cloning to radio"
+            radio.status_fn(status)
+
+    finish(radio)
+
+
+def finish(radio):
+    send(radio, "\x64\x01\x6F\x0A")
+    ack = radio.pipe.read(8)
+
+
+ at directory.register
+class LeixenVV898Radio(chirp_common.CloneModeRadio):
+    """Leixen VV-898"""
+    VENDOR = "Leixen"
+    MODEL = "VV-898"
+    BAUD_RATE = 9600
+
+    _file_ident = "Leixen"
+    _memsize = 0x2000
+    _ranges = [
+        (0x0000, 0x013f),
+        (0x0148, 0x0167),
+        (0x0184, 0x018f),
+        (0x0190, 0x01cf),
+        (0x0900, 0x090f),
+        (0x0920, 0x0927),
+        (0x0d00, 0x2000),
+    ]
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.has_settings = True
+        rf.has_cross = True
+        rf.has_bank = False
+        rf.has_tuning_step = False
+        rf.can_odd_split = True
+        rf.has_rx_dtcs = True
+        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
+        rf.valid_modes = MODES
+        rf.valid_cross_modes = [
+            "Tone->Tone",
+            "DTCS->",
+            "->DTCS",
+            "Tone->DTCS",
+            "DTCS->Tone",
+            "->Tone",
+            "DTCS->DTCS"]
+        rf.valid_characters = chirp_common.CHARSET_ASCII
+        rf.valid_name_length = 7
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_duplexes = ["", "-", "+", "split", "off"]
+        rf.valid_skips = ["", "S"]
+        rf.valid_bands = [(136000000, 174000000),
+                          (400000000, 470000000)]
+        rf.memory_bounds = (1, 199)
+        return rf
+
+    def sync_in(self):
+        try:
+            self._mmap = do_download(self)
+        except Exception, e:
+            finish(self)
+            raise errors.RadioError("Failed to download from radio: %s" % e)
+        self.process_mmap()
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+    def sync_out(self):
+        try:
+            do_upload(self)
+        except errors.RadioError:
+            finish(self)
+            raise
+        except Exception, e:
+            raise errors.RadioError("Failed to upload to radio: %s" % e)
+
+    def get_raw_memory(self, number):
+        return repr(self._memobj.name[number - 1]) + \
+               repr(self._memobj.memory[number - 1])
+
+    def _get_tone(self, mem, _mem):
+        rx_tone = tx_tone = None
+
+        tx_tmode = TMODES[_mem.tx_tmode]
+        rx_tmode = TMODES[_mem.rx_tmode]
+
+        if tx_tmode == "Tone":
+            tx_tone = TONES[_mem.tx_tone - 1]
+        elif tx_tmode == "DTCS":
+            tx_tone = DTCS_CODES[_mem.tx_tone - 1]
+
+        if rx_tmode == "Tone":
+            rx_tone = TONES[_mem.rx_tone - 1]
+        elif rx_tmode == "DTCS":
+            rx_tone = DTCS_CODES[_mem.rx_tone - 1]
+
+        tx_pol = _mem.tx_tmode == 0x03 and "R" or "N"
+        rx_pol = _mem.rx_tmode == 0x03 and "R" or "N"
+
+        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
+                                            (rx_tmode, rx_tone, rx_pol))
+
+    def _is_txinh(self, _mem):
+        raw_tx = ""
+        for i in range(0, 4):
+            raw_tx += _mem.tx_freq[i].get_raw()
+        return raw_tx == "\xFF\xFF\xFF\xFF"
+
+    def get_memory(self, number):
+        _mem = self._memobj.memory[number - 1]
+        _name = self._memobj.name[number - 1]
+
+        mem = chirp_common.Memory()
+        mem.number = number
+
+        if _mem.get_raw()[:4] == "\xFF\xFF\xFF\xFF":
+            mem.empty = True
+            return mem
+
+        mem.freq = int(_mem.rx_freq) * 10
+
+        if self._is_txinh(_mem):
+            mem.duplex = "off"
+            mem.offset = 0
+        elif int(_mem.rx_freq) == int(_mem.tx_freq):
+            mem.duplex = ""
+            mem.offset = 0
+        elif abs(int(_mem.rx_freq) * 10 - int(_mem.tx_freq) * 10) > 70000000:
+            mem.duplex = "split"
+            mem.offset = int(_mem.tx_freq) * 10
+        else:
+            mem.duplex = int(_mem.rx_freq) > int(_mem.tx_freq) and "-" or "+"
+            mem.offset = abs(int(_mem.rx_freq) - int(_mem.tx_freq)) * 10
+
+        mem.name = str(_name.name).rstrip()
+
+        self._get_tone(mem, _mem)
+        mem.mode = MODES[_mem.mode]
+        mem.power = POWER_LEVELS[_mem.power]
+        mem.skip = _mem.skip and "S" or ""
+
+        self._get_tone(mem, _mem)
+        mem.mode = MODES[_mem.mode]
+        mem.power = POWER_LEVELS[_mem.power]
+        mem.skip = _mem.skip and "S" or ""
+
+        mem.extra = RadioSettingGroup("Extra", "extra")
+
+        opts = ["On", "Off"]
+        rs = RadioSetting("blckoff", "Busy Channel Lockout",
+                          RadioSettingValueList(
+                              opts, opts[_mem.blckoff]))
+        mem.extra.append(rs)
+        opts = ["Off", "On"]
+        rs = RadioSetting("tailcut", "Squelch Tail Elimination",
+                          RadioSettingValueList(
+                              opts, opts[_mem.tailcut]))
+        mem.extra.append(rs)
+        opts = ["Off", "Compander", "Scrambler", "TX Scrambler",
+                "RX Scrambler"]
+        rs = RadioSetting("apro", "Audio Processing",
+                          RadioSettingValueList(
+                              opts, opts[_mem.apro]))
+        mem.extra.append(rs)
+        opts = ["On", "Off"]
+        rs = RadioSetting("voxoff", "VOX",
+                          RadioSettingValueList(
+                              opts, opts[_mem.voxoff]))
+        mem.extra.append(rs)
+        opts = ["On", "Off"]
+        rs = RadioSetting("pttidoff", "PTT ID",
+                          RadioSettingValueList(
+                              opts, opts[_mem.pttidoff]))
+        mem.extra.append(rs)
+        opts = ["On", "Off"]
+        rs = RadioSetting("dtmfoff", "DTMF",
+                          RadioSettingValueList(
+                              opts, opts[_mem.dtmfoff]))
+        mem.extra.append(rs)
+        opts = ["Name", "Frequency"]
+        aliasop = RadioSetting("aliasop", "Display",
+                               RadioSettingValueList(
+                                   opts, opts[_mem.aliasop]))
+        mem.extra.append(aliasop)
+        opts = ["On", "Off"]
+        rs = RadioSetting("reverseoff", "Reverse Frequency",
+                          RadioSettingValueList(
+                              opts, opts[_mem.reverseoff]))
+        mem.extra.append(rs)
+        opts = ["On", "Off"]
+        rs = RadioSetting("talkaroundoff", "Talk Around",
+                          RadioSettingValueList(
+                              opts, opts[_mem.talkaroundoff]))
+        mem.extra.append(rs)
+
+        return mem
+
+    def _set_tone(self, mem, _mem):
+        ((txmode, txtone, txpol),
+         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
+
+        _mem.tx_tmode = TMODES.index(txmode)
+        _mem.rx_tmode = TMODES.index(rxmode)
+        if txmode == "Tone":
+            _mem.tx_tone = TONES.index(txtone) + 1
+        elif txmode == "DTCS":
+            _mem.tx_tmode = txpol == "R" and 0x03 or 0x02
+            _mem.tx_tone = DTCS_CODES.index(txtone) + 1
+        if rxmode == "Tone":
+            _mem.rx_tone = TONES.index(rxtone) + 1
+        elif rxmode == "DTCS":
+            _mem.rx_tmode = rxpol == "R" and 0x03 or 0x02
+            _mem.rx_tone = DTCS_CODES.index(rxtone) + 1
+
+    def set_memory(self, mem):
+        _mem = self._memobj.memory[mem.number - 1]
+        _name = self._memobj.name[mem.number - 1]
+
+        if mem.empty:
+            _mem.set_raw("\xFF" * 16)
+            return
+        elif _mem.get_raw() == ("\xFF" * 16):
+            _mem.set_raw("\xFF" * 8 + "\xFF\x00\xFF\x00\xFF\xFE\xF0\xFC")
+
+        _mem.rx_freq = mem.freq / 10
+
+        if mem.duplex == "off":
+            for i in range(0, 4):
+                _mem.tx_freq[i].set_raw("\xFF")
+        elif mem.duplex == "split":
+            _mem.tx_freq = mem.offset / 10
+        elif mem.duplex == "+":
+            _mem.tx_freq = (mem.freq + mem.offset) / 10
+        elif mem.duplex == "-":
+            _mem.tx_freq = (mem.freq - mem.offset) / 10
+        else:
+            _mem.tx_freq = mem.freq / 10
+
+        self._set_tone(mem, _mem)
+
+        _mem.power = mem.power and POWER_LEVELS.index(mem.power) or 0
+        _mem.mode = MODES.index(mem.mode)
+        _mem.skip = mem.skip == "S"
+        _name.name = mem.name.ljust(7)
+
+        # autoset display to name if filled, else show frequency
+        if mem.extra:
+            # mem.extra only seems to be populated when called from edit panel
+            aliasop = mem.extra["aliasop"]
+        else:
+            aliasop = None
+        if mem.name:
+            _mem.aliasop = False
+            if aliasop and not aliasop.changed():
+                aliasop.value = "Name"
+        else:
+            _mem.aliasop = True
+            if aliasop and not aliasop.changed():
+                aliasop.value = "Frequency"
+
+        for setting in mem.extra:
+            setattr(_mem, setting.get_name(), setting.value)
+
+    def _get_settings(self):
+        _settings = self._memobj.settings
+        _service = self._memobj.service
+        _msg = self._memobj.messages
+        cfg_grp = RadioSettingGroup("cfg_grp", "Basic Settings")
+        adv_grp = RadioSettingGroup("adv_grp", "Advanced Settings")
+        key_grp = RadioSettingGroup("key_grp", "Key Assignment")
+        group = RadioSettings(cfg_grp, adv_grp, key_grp)
+
+        #
+        # Basic Settings
+        #
+        rs = RadioSetting("apo", "Auto Power Off",
+                          RadioSettingValueList(
+                              APO_LIST, APO_LIST[_settings.apo]))
+        cfg_grp.append(rs)
+        rs = RadioSetting("sql", "Squelch Level",
+                          RadioSettingValueList(
+                              SQL_LIST, SQL_LIST[_settings.sql]))
+        cfg_grp.append(rs)
+        rs = RadioSetting("scanm", "Scan Mode",
+                          RadioSettingValueList(
+                              SCANM_LIST, SCANM_LIST[_settings.scanm]))
+        cfg_grp.append(rs)
+        rs = RadioSetting("tot", "Time Out Timer",
+                          RadioSettingValueList(
+                              TOT_LIST, TOT_LIST[_settings.tot]))
+        cfg_grp.append(rs)
+        rs = RadioSetting("step", "Step",
+                          RadioSettingValueList(
+                              STEP_LIST, STEP_LIST[_settings.step]))
+        cfg_grp.append(rs)
+        rs = RadioSetting("monitor", "Monitor",
+                          RadioSettingValueList(
+                              MONITOR_LIST, MONITOR_LIST[_settings.monitor]))
+        cfg_grp.append(rs)
+        rs = RadioSetting("vfomr", "VFO/MR",
+                          RadioSettingValueList(
+                              VFOMR_LIST, VFOMR_LIST[_settings.vfomr]))
+        cfg_grp.append(rs)
+        rs = RadioSetting("mrcha", "MR/CHA",
+                          RadioSettingValueList(
+                              MRCHA_LIST, MRCHA_LIST[_settings.mrcha]))
+        cfg_grp.append(rs)
+        rs = RadioSetting("vol", "Volume",
+                          RadioSettingValueList(
+                              VOL_LIST, VOL_LIST[_settings.vol]))
+        cfg_grp.append(rs)
+        rs = RadioSetting("opendis", "Open Display",
+                          RadioSettingValueList(
+                              OPENDIS_LIST, OPENDIS_LIST[_settings.opendis]))
+        cfg_grp.append(rs)
+
+        def _filter(name):
+            filtered = ""
+            for char in str(name):
+                if char in chirp_common.CHARSET_ASCII:
+                    filtered += char
+                else:
+                    filtered += " "
+            LOG.debug("Filtered: %s" % filtered)
+            return filtered
+
+        rs = RadioSetting("messages.user1", "User-defined Message 1",
+                          RadioSettingValueString(0, 7, _filter(_msg.user1)))
+        cfg_grp.append(rs)
+        rs = RadioSetting("messages.user2", "User-defined Message 2",
+                          RadioSettingValueString(0, 7, _filter(_msg.user2)))
+        cfg_grp.append(rs)
+
+        val = RadioSettingValueString(0, 7, _filter(_msg.system))
+        val.set_mutable(False)
+        rs = RadioSetting("messages.system", "System Message", val)
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("lamp", "Backlight",
+                          RadioSettingValueList(
+                              LAMP_LIST, LAMP_LIST[_settings.lamp]))
+        cfg_grp.append(rs)
+        rs = RadioSetting("keylockm", "Key Lock Mode",
+                          RadioSettingValueList(
+                              KEYLOCKM_LIST,
+                              KEYLOCKM_LIST[_settings.keylockm]))
+        cfg_grp.append(rs)
+        rs = RadioSetting("absel", "A/B Select",
+                          RadioSettingValueList(ABSEL_LIST,
+                                                ABSEL_LIST[_settings.absel]))
+        cfg_grp.append(rs)
+
+        rs = RadioSetting("obeep", "Open Beep",
+                          RadioSettingValueBoolean(_settings.obeep))
+        cfg_grp.append(rs)
+        rs = RadioSetting("rbeep", "Roger Beep",
+                          RadioSettingValueBoolean(_settings.rbeep))
+        cfg_grp.append(rs)
+        rs = RadioSetting("keylock_off", "Key Lock",
+                          RadioSettingValueBoolean(not _settings.keylock_off))
+        cfg_grp.append(rs)
+        rs = RadioSetting("ctdcsb", "CT/DCS Busy Lock",
+                          RadioSettingValueBoolean(_settings.ctdcsb))
+        cfg_grp.append(rs)
+        rs = RadioSetting("alarm", "Alarm Key",
+                          RadioSettingValueBoolean(_settings.alarm))
+        cfg_grp.append(rs)
+        rs = RadioSetting("save", "Battery Save",
+                          RadioSettingValueBoolean(_settings.save))
+        cfg_grp.append(rs)
+        rs = RadioSetting("kbeep", "Key Beep",
+                          RadioSettingValueBoolean(_settings.kbeep))
+        cfg_grp.append(rs)
+        rs = RadioSetting("reset", "Reset Enable",
+                          RadioSettingValueBoolean(_settings.reset))
+        cfg_grp.append(rs)
+        rs = RadioSetting("smfont_off", "Small Font",
+                          RadioSettingValueBoolean(not _settings.smfont_off))
+        cfg_grp.append(rs)
+        rs = RadioSetting("aliasen_off", "Alias Enable",
+                          RadioSettingValueBoolean(not _settings.aliasen_off))
+        cfg_grp.append(rs)
+        rs = RadioSetting("txstop_off", "TX Stop",
+                          RadioSettingValueBoolean(not _settings.txstop_off))
+        cfg_grp.append(rs)
+        rs = RadioSetting("dw_off", "Dual Watch",
+                          RadioSettingValueBoolean(not _settings.dw_off))
+        cfg_grp.append(rs)
+        rs = RadioSetting("fmen_off", "FM Enable",
+                          RadioSettingValueBoolean(not _settings.fmen_off))
+        cfg_grp.append(rs)
+        rs = RadioSetting("fmdw", "FM Dual Watch",
+                          RadioSettingValueBoolean(_settings.fmdw))
+        cfg_grp.append(rs)
+        rs = RadioSetting("fmscan_off", "FM Scan",
+                          RadioSettingValueBoolean(
+                              not _settings.fmscan_off))
+        cfg_grp.append(rs)
+        rs = RadioSetting("keypadmic_off", "Keypad MIC",
+                          RadioSettingValueBoolean(
+                              not _settings.keypadmic_off))
+        cfg_grp.append(rs)
+        rs = RadioSetting("voxgain", "VOX Gain",
+                          RadioSettingValueList(
+                              VOXGAIN_LIST, VOXGAIN_LIST[_settings.voxgain]))
+        cfg_grp.append(rs)
+        rs = RadioSetting("voxdt", "VOX Delay Time",
+                          RadioSettingValueList(
+                              VOXDT_LIST, VOXDT_LIST[_settings.voxdt]))
+        cfg_grp.append(rs)
+        rs = RadioSetting("vir", "VOX Inhibit on Receive",
+                          RadioSettingValueBoolean(_settings.vir))
+        cfg_grp.append(rs)
+
+        #
+        # Advanced Settings
+        #
+        val = (_settings.dtmftime) - 5
+        rs = RadioSetting("dtmftime", "DTMF Digit Time",
+                          RadioSettingValueList(
+                              DTMFTIME_LIST, DTMFTIME_LIST[val]))
+        adv_grp.append(rs)
+        val = (_settings.dtmfspace) - 5
+        rs = RadioSetting("dtmfspace", "DTMF Digit Space Time",
+                          RadioSettingValueList(
+                              DTMFTIME_LIST, DTMFTIME_LIST[val]))
+        adv_grp.append(rs)
+        val = (_settings.dtmfdelay) / 5
+        rs = RadioSetting("dtmfdelay", "DTMF 1st Digit Delay",
+                          RadioSettingValueList(
+                              DTMFDELAY_LIST, DTMFDELAY_LIST[val]))
+        adv_grp.append(rs)
+        val = (_settings.dtmfpretime) / 10 - 1
+        rs = RadioSetting("dtmfpretime", "DTMF Pretime",
+                          RadioSettingValueList(
+                              DTMFPRETIME_LIST, DTMFPRETIME_LIST[val]))
+        adv_grp.append(rs)
+        val = (_settings.dtmfdelay2) / 5
+        rs = RadioSetting("dtmfdelay2", "DTMF * and # Digit Delay",
+                          RadioSettingValueList(
+                              DTMFDELAY2_LIST, DTMFDELAY2_LIST[val]))
+        adv_grp.append(rs)
+        rs = RadioSetting("ackdecode", "ACK Decode",
+                          RadioSettingValueBoolean(_settings.ackdecode))
+        adv_grp.append(rs)
+        rs = RadioSetting("dtmfst", "DTMF Sidetone",
+                          RadioSettingValueBoolean(_settings.dtmfst))
+        adv_grp.append(rs)
+
+        rs = RadioSetting("service.rssi400", "Squelch Base Level (UHF)",
+                          RadioSettingValueInteger(0, 255, _service.rssi400))
+        adv_grp.append(rs)
+        rs = RadioSetting("service.rssi136", "Squelch Base Level (VHF)",
+                          RadioSettingValueInteger(0, 255, _service.rssi136))
+        adv_grp.append(rs)
+
+        #
+        # Key Settings
+        #
+        val = (_settings.lptime) - 5
+        rs = RadioSetting("lptime", "Long Press Time",
+                          RadioSettingValueList(
+                              LPTIME_LIST, LPTIME_LIST[val]))
+        key_grp.append(rs)
+        rs = RadioSetting("keyp1long", "P1 Long Key",
+                          RadioSettingValueList(
+                              PFKEYLONG_LIST,
+                              PFKEYLONG_LIST[_settings.keyp1long]))
+        key_grp.append(rs)
+        rs = RadioSetting("keyp1short", "P1 Short Key",
+                          RadioSettingValueList(
+                              PFKEYSHORT_LIST,
+                              PFKEYSHORT_LIST[_settings.keyp1short]))
+        key_grp.append(rs)
+        rs = RadioSetting("keyp2long", "P2 Long Key",
+                          RadioSettingValueList(
+                              PFKEYLONG_LIST,
+                              PFKEYLONG_LIST[_settings.keyp2long]))
+        key_grp.append(rs)
+        rs = RadioSetting("keyp2short", "P2 Short Key",
+                          RadioSettingValueList(
+                              PFKEYSHORT_LIST,
+                              PFKEYSHORT_LIST[_settings.keyp2short]))
+        key_grp.append(rs)
+        rs = RadioSetting("keyp3long", "P3 Long Key",
+                          RadioSettingValueList(
+                              PFKEYLONG_LIST,
+                              PFKEYLONG_LIST[_settings.keyp3long]))
+        key_grp.append(rs)
+        rs = RadioSetting("keyp3short", "P3 Short Key",
+                          RadioSettingValueList(
+                              PFKEYSHORT_LIST,
+                              PFKEYSHORT_LIST[_settings.keyp3short]))
+        key_grp.append(rs)
+
+        val = RadioSettingValueList(PFKEYSHORT_LIST,
+                                    PFKEYSHORT_LIST[_settings.keymshort])
+        val.set_mutable(_settings.menuen == 0)
+        rs = RadioSetting("keymshort", "M Short Key", val)
+        key_grp.append(rs)
+        val = RadioSettingValueBoolean(_settings.menuen)
+        rs = RadioSetting("menuen", "Menu Enable", val)
+        key_grp.append(rs)
+
+        return group
+
+    def get_settings(self):
+        try:
+            return self._get_settings()
+        except:
+            import traceback
+            LOG.error("Failed to parse settings: %s", traceback.format_exc())
+            return None
+
+    def set_settings(self, settings):
+        _settings = self._memobj.settings
+        for element in settings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+            else:
+                try:
+                    name = element.get_name()
+                    if "." in name:
+                        bits = name.split(".")
+                        obj = self._memobj
+                        for bit in bits[:-1]:
+                            if "/" in bit:
+                                bit, index = bit.split("/", 1)
+                                index = int(index)
+                                obj = getattr(obj, bit)[index]
+                            else:
+                                obj = getattr(obj, bit)
+                        setting = bits[-1]
+                    else:
+                        obj = _settings
+                        setting = element.get_name()
+
+                    if element.has_apply_callback():
+                        LOG.debug("Using apply callback")
+                        element.run_apply_callback()
+                    elif setting == "keylock_off":
+                        setattr(obj, setting, not int(element.value))
+                    elif setting == "smfont_off":
+                        setattr(obj, setting, not int(element.value))
+                    elif setting == "aliasen_off":
+                        setattr(obj, setting, not int(element.value))
+                    elif setting == "txstop_off":
+                        setattr(obj, setting, not int(element.value))
+                    elif setting == "dw_off":
+                        setattr(obj, setting, not int(element.value))
+                    elif setting == "fmen_off":
+                        setattr(obj, setting, not int(element.value))
+                    elif setting == "fmscan_off":
+                        setattr(obj, setting, not int(element.value))
+                    elif setting == "keypadmic_off":
+                        setattr(obj, setting, not int(element.value))
+                    elif setting == "dtmftime":
+                        setattr(obj, setting, int(element.value) + 5)
+                    elif setting == "dtmfspace":
+                        setattr(obj, setting, int(element.value) + 5)
+                    elif setting == "dtmfdelay":
+                        setattr(obj, setting, int(element.value) * 5)
+                    elif setting == "dtmfpretime":
+                        setattr(obj, setting, (int(element.value) + 1) * 10)
+                    elif setting == "dtmfdelay2":
+                        setattr(obj, setting, int(element.value) * 5)
+                    elif setting == "lptime":
+                        setattr(obj, setting, int(element.value) + 5)
+                    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):
+        if filedata[0x168:0x170].startswith(cls._file_ident) and \
+                               filedata[0x170:0x178].startswith("LX-\x89\x85"):
+            return True
+        elif filedata[0x900:0x906] == cls.MODEL:
+            return True
+        else:
+            return False
+
+
+ at directory.register
+class JetstreamJT270MRadio(LeixenVV898Radio):
+    """Jetstream JT270M"""
+    VENDOR = "Jetstream"
+    MODEL = "JT270M"
+
+    _file_ident = "JET"
diff --git a/chirp/puxing.py b/chirp/drivers/puxing.py
similarity index 92%
rename from chirp/puxing.py
rename to chirp/drivers/puxing.py
index 88cbe79..7a1ad55 100644
--- a/chirp/puxing.py
+++ b/chirp/drivers/puxing.py
@@ -17,13 +17,13 @@
 
 import time
 import os
+import logging
+
 from chirp import util, chirp_common, bitwise, errors, directory
-from chirp.wouxun_common import wipe_memory, do_download, do_upload
+from chirp.drivers.wouxun import wipe_memory, do_download, do_upload
+
+LOG = logging.getLogger(__name__)
 
-if os.getenv("CHIRP_DEBUG"):
-    DEBUG = True
-else:
-    DEBUG = False
 
 def _puxing_prep(radio):
     radio.pipe.write("\x02PROGRA")
@@ -34,13 +34,14 @@ def _puxing_prep(radio):
     radio.pipe.write("M\x02")
     ident = radio.pipe.read(8)
     if len(ident) != 8:
-        print util.hexprint(ident)
+        LOG.debug(util.hexprint(ident))
         raise Exception("Radio did not send identification")
 
     radio.pipe.write("\x06")
     if radio.pipe.read(1) != "\x06":
         raise Exception("Radio did not ACK ident")
 
+
 def puxing_prep(radio):
     """Do the Puxing PX-777 identification dance"""
     for _i in range(0, 10):
@@ -51,6 +52,7 @@ def puxing_prep(radio):
 
     raise e
 
+
 def puxing_download(radio):
     """Talk to a Puxing PX-777 and do a download"""
     try:
@@ -61,6 +63,7 @@ def puxing_download(radio):
     except Exception, e:
         raise errors.RadioError("Failed to communicate with radio: %s" % e)
 
+
 def puxing_upload(radio):
     """Talk to a Puxing PX-777 and do an upload"""
     try:
@@ -73,7 +76,7 @@ def puxing_upload(radio):
 
 POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
                 chirp_common.PowerLevel("Low", watts=1.00)]
-                
+
 PUXING_CHARSET = list("0123456789") + \
     [chr(x + ord("A")) for x in range(0, 26)] + \
     list("-                       ")
@@ -123,13 +126,13 @@ struct {
 #  460-520: 0xF7
 
 PUXING_MODELS = {
-    328 : 0x38,
-    338 : 0x39,
-    777 : 0x3A,
+    328: 0x38,
+    338: 0x39,
+    777: 0x3A,
 }
 
 PUXING_777_BANDS = [
-    ( 67000000,  72000000),
+    (67000000,  72000000),
     (136000000, 174000000),
     (240000000, 260000000),
     (350000000, 390000000),
@@ -141,6 +144,7 @@ PUXING_777_BANDS = [
     (460000000, 520000000),
 ]
 
+
 @directory.register
 class Puxing777Radio(chirp_common.CloneModeRadio):
     """Puxing PX-777"""
@@ -173,8 +177,8 @@ class Puxing777Radio(chirp_common.CloneModeRadio):
             try:
                 rf.valid_bands = [PUXING_777_BANDS[limit_idx]]
             except IndexError:
-                print "Invalid band index %i (0x%02x)" % \
-                    (limit_idx, self._memobj.model.limits)
+                LOG.error("Invalid band index %i (0x%02x)" %
+                          (limit_idx, self._memobj.model.limits))
                 rf.valid_bands = [PUXING_777_BANDS[1]]
         elif self._memobj.model.model == PUXING_MODELS[328]:
             # There are PX-777 that says to be model 328 ...
@@ -182,9 +186,9 @@ class Puxing777Radio(chirp_common.CloneModeRadio):
             if self._memobj.model.limits == 0xEE:
                 rf.valid_bands = [PUXING_777_BANDS[1]]
             else:
-                raise Exception("Unsupported band limits 0x%02x for PX-777" % \
-                    (self._memobj.model.limits) +
-                    " submodel 328 - PLEASE REPORT THIS ERROR TO DEVELOPERS!!")
+                raise Exception("Unsupported band limits 0x%02x for PX-777" %
+                                (self._memobj.model.limits) + " submodel 328"
+                                " - PLEASE REPORT THIS ERROR TO DEVELOPERS!!")
 
         return rf
 
@@ -198,12 +202,10 @@ class Puxing777Radio(chirp_common.CloneModeRadio):
     @classmethod
     def match_model(cls, filedata, filename):
         # There are PX-777 that says to be model 328 ...
-        return len(filedata) == 3168 and (
-                ord(filedata[0x080B]) == PUXING_MODELS[777] or (
-                    ord(filedata[0x080B]) == PUXING_MODELS[328] and
-                        ord(filedata[0x080A]) == 0xEE
-                    )
-                )
+        return (len(filedata) == 3168 and
+                (ord(filedata[0x080B]) == PUXING_MODELS[777] or
+                (ord(filedata[0x080B]) == PUXING_MODELS[328] and
+                 ord(filedata[0x080A]) == 0xEE)))
 
     def get_memory(self, number):
         _mem = self._memobj.memory[number - 1]
@@ -235,7 +237,7 @@ class Puxing777Radio(chirp_common.CloneModeRadio):
                 tp, tx = "N", None
             else:
                 tp, tx = _get_dtcs(int(txfield))
-            
+
             if rxfield[0].get_raw() == "\xFF":
                 rp, rx = "N", None
             else:
@@ -272,7 +274,7 @@ class Puxing777Radio(chirp_common.CloneModeRadio):
             mem.mode = "NFM"
 
         if _is_no_tone(_mem.tx_tone):
-            pass # No tone
+            pass  # No tone
         elif int(_mem.tx_tone) > 8000 or \
                 (not _is_no_tone(_mem.rx_tone) and int(_mem.rx_tone) > 8000):
             mem.tmode = "DTCS"
@@ -309,13 +311,11 @@ class Puxing777Radio(chirp_common.CloneModeRadio):
         _mem.skip = mem.skip != "S"
         _mem.iswide = mem.mode != "NFM"
 
-
         _mem.rx_tone[0].set_raw("\xFF")
         _mem.rx_tone[1].set_raw("\xFF")
         _mem.tx_tone[0].set_raw("\xFF")
         _mem.tx_tone[1].set_raw("\xFF")
 
-
         if mem.tmode == "DTCS":
             _mem.tx_tone = int("%x" % int("%i" % (mem.dtcs), 16))
             _mem.rx_tone = int("%x" % int("%i" % (mem.dtcs), 16))
@@ -349,6 +349,7 @@ class Puxing777Radio(chirp_common.CloneModeRadio):
             except IndexError:
                 raise Exception("Character `%s' not supported")
 
+
 def puxing_2r_prep(radio):
     """Do the Puxing 2R identification dance"""
     radio.pipe.setTimeout(0.2)
@@ -359,7 +360,8 @@ def puxing_2r_prep(radio):
 
     radio.pipe.write(ack)
     ident = radio.pipe.read(16)
-    print "Radio ident: %s (%i)" % (repr(ident), len(ident))
+    LOG.info("Radio ident: %s (%i)" % (repr(ident), len(ident)))
+
 
 def puxing_2r_download(radio):
     """Talk to a Puxing 2R and do a download"""
@@ -371,6 +373,7 @@ def puxing_2r_download(radio):
     except Exception, e:
         raise errors.RadioError("Failed to communicate with radio: %s" % e)
 
+
 def puxing_2r_upload(radio):
     """Talk to a Puxing 2R and do an upload"""
     try:
@@ -404,6 +407,7 @@ PX2R_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.0),
                      chirp_common.PowerLevel("High", watts=2.0)]
 PX2R_CHARSET = "0123456789- ABCDEFGHIJKLMNOPQRSTUVWXYZ +"
 
+
 @directory.register
 class Puxing2RRadio(chirp_common.CloneModeRadio):
     """Puxing PX-2R"""
@@ -475,8 +479,8 @@ class Puxing2RRadio(chirp_common.CloneModeRadio):
             try:
                 mem.name += PX2R_CHARSET[i]
             except Exception:
-                print "Unknown name char %i: 0x%02x (mem %i)" % (count,
-                                                                 i, number)
+                LOG.error("Unknown name char %i: 0x%02x (mem %i)" %
+                          (count, i, number))
                 mem.name += " "
             count += 1
         mem.name = mem.name.rstrip()
@@ -499,7 +503,7 @@ class Puxing2RRadio(chirp_common.CloneModeRadio):
         if mem.tmode == "DTCS":
             _mem.tx_tone = chirp_common.DTCS_CODES.index(mem.dtcs) + 0x33
             _mem.rx_tone = chirp_common.DTCS_CODES.index(mem.dtcs) + 0x33
-            _mem.txdtcsinv = mem.dtcs_polarity[0] == "R" 
+            _mem.txdtcsinv = mem.dtcs_polarity[0] == "R"
             _mem.rxdtcsinv = mem.dtcs_polarity[1] == "R"
         elif mem.tmode in ["Tone", "TSQL"]:
             _mem.tx_tone = chirp_common.TONES.index(mem.rtone) + 1
@@ -516,4 +520,3 @@ class Puxing2RRadio(chirp_common.CloneModeRadio):
 
     def get_raw_memory(self, number):
         return repr(self._memobj.memory[number-1])
-
diff --git a/chirp/rfinder.py b/chirp/drivers/rfinder.py
similarity index 87%
rename from chirp/rfinder.py
rename to chirp/drivers/rfinder.py
index 0879ce5..35dbe70 100644
--- a/chirp/rfinder.py
+++ b/chirp/drivers/rfinder.py
@@ -16,11 +16,13 @@
 import urllib
 import hashlib
 import re
+import logging
 
 from math import pi, cos, acos, sin, atan2
-
 from chirp import chirp_common, CHIRP_VERSION
 
+LOG = logging.getLogger(__name__)
+
 EARTH_RADIUS = 3963.1
 
 SCHEMA = [
@@ -47,18 +49,22 @@ SCHEMA = [
     "DOC_ID",
     ]
 
+
 def deg2rad(deg):
     """Convert degrees to radians"""
     return deg * (pi / 180)
 
+
 def rad2deg(rad):
     """Convert radians to degrees"""
     return rad / (pi / 180)
 
+
 def dm2deg(degrees, minutes):
     """Convert degrees and minutes to decimal degrees"""
     return degrees + (minutes / 60.0)
 
+
 def deg2dm(decdeg):
     """Convert decimal degrees to degrees and minutes"""
     degrees = int(decdeg)
@@ -66,6 +72,7 @@ def deg2dm(decdeg):
 
     return degrees, minutes
 
+
 def nmea2deg(nmea, direction="N"):
     """Convert NMEA-encoded value to float"""
     deg = int(nmea) / 100
@@ -81,35 +88,37 @@ def nmea2deg(nmea, direction="N"):
 
     return dm2deg(deg, minutes) * sign
 
+
 def deg2nmea(deg):
     """Convert degrees to a NMEA-encoded value"""
     degrees, minutes = deg2dm(deg)
 
     return (degrees * 100) + minutes
 
+
 def meters2feet(meters):
     """Convert meters to feet"""
     return meters * 3.2808399
 
+
 def feet2meters(feet):
     """Convert feet to meters"""
     return feet * 0.3048
 
+
 def distance(lat_a, lon_a, lat_b, lon_b):
     """Calculate the distance between two points"""
     lat_a = deg2rad(lat_a)
     lon_a = deg2rad(lon_a)
-    
+
     lat_b = deg2rad(lat_b)
     lon_b = deg2rad(lon_b)
-    
+
     earth_radius = EARTH_RADIUS
-    
-    tmp = (cos(lat_a) * cos(lon_a) * \
-               cos(lat_b) * cos(lon_b)) + \
-               (cos(lat_a) * sin(lon_a) * \
-                    cos(lat_b) * sin(lon_b)) + \
-                    (sin(lat_a) * sin(lat_b))
+
+    tmp = (cos(lat_a) * cos(lon_a) * cos(lat_b) * cos(lon_b)) + \
+          (cos(lat_a) * sin(lon_a) * cos(lat_b) * sin(lon_b)) + \
+          (sin(lat_a) * sin(lat_b))
 
     # Correct round-off error (which is just *silly*)
     if tmp > 1:
@@ -121,6 +130,7 @@ def distance(lat_a, lon_a, lat_b, lon_b):
 
     return dist * earth_radius
 
+
 def bearing(lat_a, lon_a, lat_b, lon_b):
     """Calculate the bearing between two points"""
     lat_me = deg2rad(lat_a)
@@ -135,6 +145,7 @@ def bearing(lat_a, lon_a, lat_b, lon_b):
 
     return (bear + 360) % 360
 
+
 def fuzzy_to(lat_a, lon_a, lat_b, lon_b):
     """Calculate a fuzzy distance to a point"""
     bear = bearing(lat_a, lon_a, lat_b, lon_b)
@@ -155,6 +166,7 @@ def fuzzy_to(lat_a, lon_a, lat_b, lon_b):
 
     return direction
 
+
 class RFinderParser:
     """Parser for RFinder's data format"""
     def __init__(self, lat, lon):
@@ -165,21 +177,21 @@ class RFinderParser:
 
     def fetch_data(self, user, pw, coords, radius):
         """Fetches the data for a set of parameters"""
-        print user
-        print pw
+        LOG.debug(user)
+        LOG.debug(pw)
         args = {
-            "email"  : urllib.quote_plus(user),
-            "pass"  : hashlib.new("md5", pw).hexdigest(),
-            "lat"   : "%7.5f" % coords[0],
-            "lon"   : "%8.5f" % coords[1],
+            "email": urllib.quote_plus(user),
+            "pass": hashlib.new("md5", pw).hexdigest(),
+            "lat": "%7.5f" % coords[0],
+            "lon": "%7.5f" % coords[1],
             "radius": "%i" % radius,
-            "vers"  : "CH%s" % CHIRP_VERSION,
+            "vers": "CH%s" % CHIRP_VERSION,
             }
 
-        _url = "https://www.rfinder.net/query.php?%s" % (\
-            "&".join(["%s=%s" % (k,v) for k,v in args.items()]))
+        _url = "https://www.rfinder.net/query.php?%s" % \
+               ("&".join(["%s=%s" % (k, v) for k, v in args.items()]))
 
-        print "Query URL: %s" % _url    
+        LOG.debug("Query URL: %s" % _url)
 
         f = urllib.urlopen(_url)
         data = f.read()
@@ -201,7 +213,7 @@ class RFinderParser:
             try:
                 vals[SCHEMA[i]] = _vals[i]
             except IndexError:
-                print "No such vals %s" % SCHEMA[i]
+                LOG.error("No such vals %s" % SCHEMA[i])
         self.__cheat = vals
 
         mem.name = vals["TRUSTEE"]
@@ -229,7 +241,7 @@ class RFinderParser:
                 bear = fuzzy_to(self.__lat, self.__lon, lat, lon)
                 mem.comment = "(%imi %s) %s" % (dist, bear, mem.comment)
             except Exception, e:
-                print "Failed to calculate distance: %s" % e
+                LOG.error("Failed to calculate distance: %s" % e)
 
         return mem
 
@@ -247,18 +259,18 @@ class RFinderParser:
                 number += 1
                 self.__memories.append(mem)
             except Exception, e:
-                import traceback, sys
-                traceback.print_exc(file=sys.stdout)
-                print "Error in received data, cannot continue"
-                print e
-                print self.__cheat
-                print line
-                print "\n\n"
+                import traceback
+                LOG.error(traceback.format_exc())
+                LOG.error("Error in received data, cannot continue")
+                LOG.error(e)
+                LOG.error(self.__cheat)
+                LOG.error(line)
 
     def get_memories(self):
         """Return the Memory objects associated with the fetched data"""
         return self.__memories
 
+
 class RFinderRadio(chirp_common.NetworkSourceRadio):
     """A network source radio that supports the RFinder repeater directory"""
     VENDOR = "ITWeRKS"
@@ -266,13 +278,13 @@ class RFinderRadio(chirp_common.NetworkSourceRadio):
 
     def __init__(self, *args, **kwargs):
         chirp_common.NetworkSourceRadio.__init__(self, *args, **kwargs)
-       
+
         self._lat = 0
         self._lon = 0
         self._user = ""
         self._pass = ""
         self._miles = 25
- 
+
         self._rfp = None
 
     def set_params(self, (lat, lon), miles, email, password):
@@ -290,7 +302,7 @@ class RFinderRadio(chirp_common.NetworkSourceRadio):
                                                   self._pass,
                                                   (self._lat, self._lon),
                                                   self._miles))
-        
+
     def get_features(self):
         if not self._rfp:
             self.do_fetch()
@@ -309,6 +321,7 @@ class RFinderRadio(chirp_common.NetworkSourceRadio):
 
         return self._rfp.get_memories()[number-1]
 
+
 def _test():
     rfp = RFinderParser()
     data = rfp.fetch_data("KK7DS", "dsmith at danplanet.com",
@@ -316,7 +329,7 @@ def _test():
     rfp.parse_data(data)
 
     for mem in rfp.get_memories():
-        print mem
+        LOG.debug(mem)
 
 if __name__ == "__main__":
     _test()
diff --git a/chirp/template.py b/chirp/drivers/template.py
similarity index 81%
rename from chirp/template.py
rename to chirp/drivers/template.py
index d01dbea..381b103 100644
--- a/chirp/template.py
+++ b/chirp/drivers/template.py
@@ -32,6 +32,7 @@ struct {
 } memory[10];
 """
 
+
 def do_download(radio):
     """This is your download function"""
     # NOTE: Remove this in your real implementation!
@@ -49,6 +50,7 @@ def do_download(radio):
 
     return memmap.MemoryMap(data)
 
+
 def do_upload(radio):
     """This is your upload function"""
     # NOTE: Remove this in your real implementation!
@@ -63,22 +65,23 @@ def do_upload(radio):
     for i in range(0, 1000):
         serial.write(radio.get_mmap()[i])
 
+
 # Uncomment this to actually register this radio in CHIRP
 # @directory.register
 class TemplateRadio(chirp_common.CloneModeRadio):
     """Acme Template"""
-    VENDOR = "Acme"    # Replace this with your vendor
-    MODEL = "Template" # Replace this with your model
-    BAUD_RATE = 9600   # Replace this with your baud rate
+    VENDOR = "Acme"     # Replace this with your vendor
+    MODEL = "Template"  # Replace this with your model
+    BAUD_RATE = 9600    # Replace this with your baud rate
 
     # Return information about this radio's features, including
     # how many memories it has, what bands it supports, etc
     def get_features(self):
         rf = chirp_common.RadioFeatures()
         rf.has_bank = False
-        rf.memory_bounds = (0, 9) # This radio supports memories 0-9
-        rf.valid_bands = [(144000000, 148000000), # Supports 2-meters
-                          (440000000, 450000000), # Supports 70-centimeters
+        rf.memory_bounds = (0, 9)  # This radio supports memories 0-9
+        rf.valid_bands = [(144000000, 148000000),  # Supports 2-meters
+                          (440000000, 450000000),  # Supports 70-centimeters
                           ]
         return rf
 
@@ -91,7 +94,7 @@ class TemplateRadio(chirp_common.CloneModeRadio):
     def sync_out(self):
         do_upload(self)
 
-    # Return a raw representation of the memory object, which 
+    # Return a raw representation of the memory object, which
     # is very helpful for development
     def get_raw_memory(self, number):
         return repr(self._memobj.memory[number])
@@ -105,10 +108,10 @@ class TemplateRadio(chirp_common.CloneModeRadio):
         # Create a high-level memory object to return to the UI
         mem = chirp_common.Memory()
 
-        mem.number = number                # Set the memory number
-        mem.freq = int(_mem.freq)          # Convert your low-level frequency
-                                           # to Hertz
-        mem.name = str(_mem.name).rstrip() # Set the alpha tag
+        mem.number = number                 # Set the memory number
+        # Convert your low-level frequency to Hertz
+        mem.freq = int(_mem.freq)
+        mem.name = str(_mem.name).rstrip()  # Set the alpha tag
 
         # We'll consider any blank (i.e. 0MHz frequency) to be empty
         if mem.freq == 0:
@@ -122,7 +125,6 @@ class TemplateRadio(chirp_common.CloneModeRadio):
         # Get a low-level memory object mapped to the image
         _mem = self._memobj.memory[mem.number]
 
-        _mem.freq = mem.freq               # Convert to low-level frequency
-                                           # representation
+        # Convert to low-level frequency representation
+        _mem.freq = mem.freq
         _mem.name = mem.name.ljust(8)[:8]  # Store the alpha tag
-
diff --git a/chirp/drivers/th9000.py b/chirp/drivers/th9000.py
new file mode 100644
index 0000000..ef52da7
--- /dev/null
+++ b/chirp/drivers/th9000.py
@@ -0,0 +1,838 @@
+# Copyright 2015 David Fannin KK6DF  <kk6df at arrl.org>
+#
+# 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 os
+import struct
+import time
+import logging
+
+from chirp import bitwise
+from chirp import chirp_common
+from chirp import directory
+from chirp import errors
+from chirp import memmap
+from chirp import util
+from chirp.settings import RadioSettingGroup, RadioSetting, RadioSettings, \
+    RadioSettingValueList, RadioSettingValueString, RadioSettingValueBoolean, \
+    RadioSettingValueInteger, RadioSettingValueString, \
+    RadioSettingValueFloat, InvalidValueError
+
+LOG = logging.getLogger(__name__)
+
+#
+#  Chirp Driver for TYT TH-9000D (models: 2M (144 Mhz), 1.25M (220 Mhz)  and 70cm (440 Mhz)  radios)
+#
+#  Version 1.0 
+#
+#         - Skip channels
+#
+# Global Parameters 
+#
+MMAPSIZE = 16384
+TONES = [62.5] + list(chirp_common.TONES)
+TMODES =  ['','Tone','DTCS',''] 
+DUPLEXES = ['','err','-','+'] # index 2 not used
+MODES = ['WFM','FM','NFM']  #  25k, 20k,15k bw 
+TUNING_STEPS=[ 5.0, 6.25, 8.33, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0 ] # index 0-9
+POWER_LEVELS=[chirp_common.PowerLevel("High", watts=65),
+              chirp_common.PowerLevel("Mid", watts=25),
+              chirp_common.PowerLevel("Low", watts=10)]
+
+CROSS_MODES = chirp_common.CROSS_MODES
+
+APO_LIST = [ "Off","30 min","1 hr","2 hrs" ] 
+BGCOLOR_LIST = ["Blue","Orange","Purple"]
+BGBRIGHT_LIST = ["%s" % x for x in range(1,32)]
+SQUELCH_LIST = ["Off"] + ["Level %s" % x for x in range(1,20)] 
+TIMEOUT_LIST = ["Off"] + ["%s min" % x for x in range(1,30)]
+TXPWR_LIST = ["60W","25W"]  # maximum power for Hi setting
+TBSTFREQ_LIST = ["1750Hz","2100Hz","1000Hz","1450Hz"]
+BEEP_LIST = ["Off","On"]
+
+SETTING_LISTS = {
+        "auto_power_off": APO_LIST,
+        "bg_color"      : BGCOLOR_LIST,
+        "bg_brightness" : BGBRIGHT_LIST,
+        "squelch"       : SQUELCH_LIST,
+        "timeout_timer" : TIMEOUT_LIST,
+        "choose_tx_power": TXPWR_LIST,
+        "tbst_freq"     : TBSTFREQ_LIST,
+        "voice_prompt"  : BEEP_LIST
+}
+
+MEM_FORMAT = """
+#seekto 0x0000;
+struct {
+   u8 unknown0000[16];
+   char idhdr[16];
+   u8 unknown0001[16];
+} fidhdr;
+"""
+#Overall Memory Map:
+#
+#    Memory Map (Range 0x0100-3FF0, step 0x10):
+#
+#        Field                   Start  End  Size   
+#                                (hex)  (hex) (hex)  
+#        
+#        1 Channel Set Flag        0100  011F   20 
+#        2 Channel Skip Flag       0120  013F   20 
+#        3 Blank/Unknown           0140  01EF   B0 
+#        4 Unknown                 01F0  01FF   10
+#        5 TX/RX Range             0200  020F   10    
+#        6 Bootup Passwd           0210  021F   10 
+#        7 Options, Radio          0220  023F   20
+#        8 Unknown                 0240  019F   
+#            8B Startup Label      03E0  03E7   07
+#        9 Channel Bank            2000  38FF 1900  
+#             Channel 000          2000  201F   20  
+#             Channel 001          2020  202F   20  
+#             ... 
+#             Channel 199          38E0  38FF   20 
+#        10 Blank/Unknown          3900  3FFF  6FF  14592  16383    1792   
+#            Total Map Size           16128 (2^8 = 16384)
+#
+#  TH9000/220  memory map 
+#  section: 1 and 2:  Channel Set/Skip Flags
+# 
+#    Channel Set (starts 0x100) : Channel Set  bit is value 0 if a memory location in the channel bank is active.
+#    Channel Skip (starts 0x120): Channel Skip bit is value 0 if a memory location in the channel bank is active.
+#
+#    Both flag maps are a total 24 bytes in length, aligned on 32 byte records.
+#    bit = 0 channel set/no skip,  1 is channel not set/skip
+#
+#    to index a channel:
+#        cbyte = channel / 8 ;
+#        cbit  = channel % 8 ;
+#        setflag  = csetflag[cbyte].c[cbit] ;
+#        skipflag = cskipflag[cbyte].c[cbit] ;
+#
+#    channel range is 0-199, range is 32 bytes (last 7 unknown)
+#
+MEM_FORMAT = MEM_FORMAT + """
+#seekto 0x0100;
+struct {
+   bit c[8];
+} csetflag[32];
+
+struct {
+   u8 unknown0100[7];
+} ropt0100;
+
+#seekto 0x0120;
+struct {
+   bit c[8];
+} cskipflag[32];
+
+struct {
+   u8 unknown0120[7];
+} ropt0120;
+"""
+#  TH9000  memory map 
+#  section: 5  TX/RX Range
+#     used to set the TX/RX range of the radio (e.g.  222-228Mhz for 220 meter)
+#     possible to set range for tx/rx 
+#
+MEM_FORMAT = MEM_FORMAT + """
+#seekto 0x0200;
+struct {
+    bbcd txrangelow[4];
+    bbcd txrangehi[4];
+    bbcd rxrangelow[4];
+    bbcd rxrangehi[4];
+} freqrange;
+"""
+# TH9000  memory map 
+# section: 6  bootup_passwd
+#    used to set bootup passwd (see boot_passwd checkbox option)
+#
+#  options - bootup password
+#
+#  bytes:bit   type                 description
+#  ---------------------------------------------------------------------------
+#  6         u8 bootup_passwd[6]     bootup passwd, 6 chars, numberic chars 30-39 , see boot_passwd checkbox to set
+#  10        u8 unknown;  
+#
+
+MEM_FORMAT = MEM_FORMAT + """
+#seekto 0x0210;
+struct {
+   u8 bootup_passwd[6];
+   u8 unknown2010[10];
+} ropt0210;
+"""
+#  TH9000/220  memory map 
+#  section: 7  Radio Options  
+#        used to set a number of radio options 
+#
+#  bytes:bit   type                 description
+#  ---------------------------------------------------------------------------
+#  1         u8 display_mode     display mode, range 0-2, 0=freq,1=channel,2=name (selecting name affects vfo_mr)
+#  1         u8 vfo_mr;          vfo_mr , 0=vfo, mr=1 
+#  1         u8 unknown;  
+#  1         u8 squelch;         squelch level, range 0-19, hex for menu
+#  1         u8 unknown[2]; 
+#  1         u8 channel_lock;    if display_mode[channel] selected, then lock=1,no lock =0
+#  1         u8 unknown; 
+#  1         u8 bg_brightness ;  background brightness, range 0-21, hex, menu index 
+#  1         u8 unknown;     
+#  1         u8 bg_color ;       bg color, menu index,  blue 0 , orange 1, purple 2
+#  1         u8 tbst_freq ;      tbst freq , menu 0 = 1750Hz, 1=2100 , 2=1000 , 3=1450hz 
+#  1         u8 timeout_timer;   timeout timer, hex, value = minutes, 0= no timeout
+#  1         u8 unknown; 
+#  1         u8 auto_power_off;   auto power off, range 0-3, off,30min, 1hr, 2hr, hex menu index
+#  1         u8 voice_prompt;     voice prompt, value 0,1 , Beep ON = 1, Beep Off = 2
+#
+# description of function setup options, starting at 0x0230
+#
+#  bytes:bit   type                 description
+#  ---------------------------------------------------------------------------
+#  1         u8  // 0
+#   :4       unknown:6
+#   :1       elim_sql_tail:1   eliminate squelsh tail when no ctcss checkbox (1=checked)
+#   :1       sql_key_function  "squelch off" 1 , "squelch momentary off" 0 , menu index
+#  2         u8 unknown[2] /1-2  
+#  1         u8 // 3
+#   :4       unknown:4
+#   :1       inhibit_init_ops:1 //bit 5
+#   :1       unknownD:1
+#   :1       inhibit_setup_bg_chk:1 //bit 7
+#   :1       unknown:1
+#  1         u8 tail_elim_type    menu , (off=0,120=1,180=2),  // 4
+#  1         u8 choose_tx_power    menu , (60w=0,25w=1) // 5
+#  2         u8 unknown[2]; // 6-7 
+#  1         u8 bootup_passwd_flag  checkbox 1=on, 0=off // 8
+#  7         u8 unknown[7]; // 9-F 
+#
+MEM_FORMAT = MEM_FORMAT + """
+#seekto 0x0220;
+struct {
+   u8 display_mode; 
+   u8 vfo_mr; 
+   u8 unknown0220A; 
+   u8 squelch; 
+   u8 unknown0220B[2]; 
+   u8 channel_lock; 
+   u8 unknown0220C; 
+   u8 bg_brightness; 
+   u8 unknown0220D; 
+   u8 bg_color;
+   u8 tbst_freq;
+   u8 timeout_timer;
+   u8 unknown0220E;
+   u8 auto_power_off;
+   u8 voice_prompt; 
+   u8 unknown0230A:6,
+      elim_sql_tail:1,
+      sql_key_function:1;
+   u8 unknown0230B[2];
+   u8 unknown0230C:4, 
+      inhibit_init_ops:1,
+      unknown0230D:1,
+      inhibit_setup_bg_chk:1,
+      unknown0230E:1;
+   u8 tail_elim_type;
+   u8 choose_tx_power;
+   u8 unknown0230F[2];
+   u8 bootup_passwd_flag;
+   u8 unknown0230G[7];
+} settings;
+"""
+#  TH9000  memory map 
+#  section: 8B  Startup Label  
+#
+#  bytes:bit   type                 description
+#  ---------------------------------------------------------------------------
+#  7     char start_label[7]    label displayed at startup (usually your call sign)
+#
+MEM_FORMAT = MEM_FORMAT + """
+#seekto 0x03E0;
+struct {
+    char startname[7];
+} slabel;
+"""
+#  TH9000/220  memory map 
+#  section: 9  Channel Bank
+#         description of channel bank (200 channels , range 0-199)
+#         Each 32 Byte (0x20 hex)  record:
+#  bytes:bit   type                 description
+#  ---------------------------------------------------------------------------
+#  4         bbcd freq[4]        receive frequency in packed binary coded decimal  
+#  4         bbcd offset[4]      transmit offset in packed binary coded decimal (note: plus/minus direction set by 'duplex' field)
+#  1         u8
+#   :4       unknown:4
+#   :4       tuning_step:4         tuning step, menu index value from 0-9
+#            5,6.25,8.33,10,12.5,15,20,25,30,50
+#  1         u8
+#   :4       unknown:4          not yet decoded, used for DCS coding?
+#   :2       channel_width:2     channel spacing, menu index value from 0-3
+#            25,20,12.5
+#   :1       reverse:1           reverse flag, 0=off, 1=on (reverses tx and rx freqs)
+#   :1       txoff:1             transmitt off flag, 0=transmit , 1=do not transmit 
+#  1         u8
+#   :1       talkaround:1        talkaround flag, 0=off, 1=on (bypasses repeater) 
+#   :1       compander:1         compander flag, 0=off, 1=on (turns on/off voice compander option)  
+#   :2       unknown:2          
+#   :2       power:2             tx power setting, value range 0-2, 0=hi,1=med,2=lo 
+#   :2       duplex:2            duplex settings, 0=simplex,2= minus(-) offset, 3= plus (+) offset (see offset field) 
+#            
+#  1         u8 
+#   :4       unknown:4
+#   :2       rxtmode:2           rx tone mode, value range 0-2, 0=none, 1=CTCSS, 2=DCS  (ctcss tone in field rxtone)
+#   :2       txtmode:2           tx tone mode, value range 0-2, 0=none, 1=CTCSS, 3=DCS  (ctcss tone in field txtone)
+#  1         u8 
+#   :2       unknown:2
+#   :6       txtone:6            tx ctcss tone, menu index
+#  1         u8 
+#   :2       unknown:2 
+#   :6       rxtone:6            rx ctcss tone, menu index
+#  1         u8 txcode           ?, not used for ctcss
+#  1         u8 rxcode           ?, not used for ctcss
+#  3         u8 unknown[3]
+#  7         char name[7]        7 byte char string for channel name
+#  1         u8 
+#   :6       unknown:6,
+#   :2       busychannellockout:2 busy channel lockout option , 0=off, 1=repeater, 2=busy  (lock out tx if channel busy)
+#  4         u8 unknownI[4];
+#  1         u8 
+#   :7       unknown:7 
+#   :1       scrambler:1         scrambler flag, 0=off, 1=on (turns on tyt scrambler option)
+#
+MEM_FORMAT = MEM_FORMAT + """
+#seekto 0x2000;
+struct {
+  bbcd freq[4];
+  bbcd offset[4];
+  u8 unknown2000A:4,
+     tuning_step:4;
+  u8 rxdcsextra:1,
+     txdcsextra:1,
+     rxinv:1,
+     txinv:1,
+     channel_width:2,
+     reverse:1,
+     txoff:1;
+  u8 talkaround:1,
+     compander:1,
+     unknown2000C:2,
+     power:2,
+     duplex:2;
+  u8 unknown2000D:4,
+     rxtmode:2,
+     txtmode:2;
+  u8 unknown2000E:2,
+     txtone:6;
+  u8 unknown2000F:2,
+     rxtone:6;
+  u8 txcode;
+  u8 rxcode;
+  u8 unknown2000G[3];
+  char name[7];
+  u8 unknown2000H:6,
+     busychannellockout:2;
+  u8 unknown2000I[4];
+  u8 unknown2000J:7,
+     scrambler:1; 
+} memory[200] ;
+"""
+
+def _echo_write(radio, data):
+    try:
+        radio.pipe.write(data)
+        radio.pipe.read(len(data))
+    except Exception, e:
+        LOG.error("Error writing to radio: %s" % e)
+        raise errors.RadioError("Unable to write to radio")
+
+
+def _checksum(data):
+    cs = 0
+    for byte in data:
+        cs += ord(byte)
+    return cs % 256
+
+def _read(radio, length):
+    try:
+        data = radio.pipe.read(length)
+    except Exception, e:
+        LOG.error( "Error reading from radio: %s" % e)
+        raise errors.RadioError("Unable to read from radio")
+
+    if len(data) != length:
+        LOG.error( "Short read from radio (%i, expected %i)" % (len(data),
+                                                           length))
+        LOG.debug(util.hexprint(data))
+        raise errors.RadioError("Short read from radio")
+    return data
+
+
+
+def _ident(radio):
+    radio.pipe.setTimeout(1)
+    _echo_write(radio,"PROGRAM")
+    response = radio.pipe.read(3)
+    if response != "QX\06":
+        LOG.debug( "Response was :\n%s" % util.hexprint(response))
+        raise errors.RadioError("Unsupported model")
+    _echo_write(radio, "\x02")
+    response = radio.pipe.read(16)
+    LOG.debug(util.hexprint(response))
+    if response[1:8] != "TH-9000":
+        LOG.error( "Looking  for:\n%s" % util.hexprint("TH-9000"))
+        LOG.error( "Response was:\n%s" % util.hexprint(response))
+        raise errors.RadioError("Unsupported model")
+
+def _send(radio, cmd, addr, length, data=None):
+    frame = struct.pack(">cHb", cmd, addr, length)
+    if data:
+        frame += data
+        frame += chr(_checksum(frame[1:]))
+        frame += "\x06"
+    _echo_write(radio, frame)
+    LOG.debug("Sent:\n%s" % util.hexprint(frame))
+    if data:
+        result = radio.pipe.read(1)
+        if result != "\x06":
+            LOG.debug( "Ack was: %s" % repr(result))
+            raise errors.RadioError("Radio did not accept block at %04x" % addr)
+        return
+    result = _read(radio, length + 6)
+    LOG.debug("Got:\n%s" % util.hexprint(result))
+    header = result[0:4]
+    data = result[4:-2]
+    ack = result[-1]
+    if ack != "\x06":
+        LOG.debug("Ack was: %s" % repr(ack))
+        raise errors.RadioError("Radio NAK'd block at %04x" % addr)
+    _cmd, _addr, _length = struct.unpack(">cHb", header)
+    if _addr != addr or _length != _length:
+        LOG.debug( "Expected/Received:")
+        LOG.debug(" Length: %02x/%02x" % (length, _length))
+        LOG.debug( " Addr: %04x/%04x" % (addr, _addr))
+        raise errors.RadioError("Radio send unexpected block")
+    cs = _checksum(result[1:-2])
+    if cs != ord(result[-2]):
+        LOG.debug( "Calculated: %02x" % cs)
+        LOG.debug( "Actual:     %02x" % ord(result[-2]))
+        raise errors.RadioError("Block at 0x%04x failed checksum" % addr)
+    return data
+
+
+def _finish(radio):
+    endframe = "\x45\x4E\x44"
+    _echo_write(radio, endframe)
+    result = radio.pipe.read(1)
+    if result != "\x06":
+        LOG.error( "Got:\n%s" % util.hexprint(result))
+        raise errors.RadioError("Radio did not finish cleanly")
+
+def do_download(radio):
+
+    _ident(radio)
+
+    _memobj = None
+    data = ""
+
+    for start,end in radio._ranges: 
+        for addr in range(start,end,0x10):
+            block = _send(radio,'R',addr,0x10) 
+            data += block
+            status = chirp_common.Status()
+            status.cur = len(data)
+            status.max = end
+            status.msg = "Downloading from radio"
+            radio.status_fn(status)
+
+    _finish(radio)
+
+    return memmap.MemoryMap(data)
+
+def do_upload(radio):
+
+    _ident(radio)
+
+    for start,end in radio._ranges:
+        for addr in range(start,end,0x10):
+            if addr < 0x0100:
+                continue
+            block = radio._mmap[addr:addr+0x10]
+            _send(radio,'W',addr,len(block),block)
+            status = chirp_common.Status()
+            status.cur = addr
+            status.max = end
+            status.msg = "Uploading to Radio"
+            radio.status_fn(status)
+
+    _finish(radio)
+            
+
+
+#
+# The base class, extended for use with other models
+#
+class Th9000Radio(chirp_common.CloneModeRadio,
+                  chirp_common.ExperimentalRadio):
+    """TYT TH-9000"""
+    VENDOR = "TYT"    
+    MODEL = "TH9000 Base" 
+    BAUD_RATE = 9600 
+    valid_freq = [(900000000, 999000000)]
+    
+
+    _memsize = MMAPSIZE
+    _ranges = [(0x0000,0x4000)]
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.experimental = ("The TYT TH-9000 driver is an beta version."
+                           "Proceed with Caution and backup your data")
+        return rp
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.has_settings = True
+        rf.has_bank = False
+        rf.has_cross = True
+        rf.has_tuning_step = False
+        rf.has_rx_dtcs = True
+        rf.valid_skips = ["","S"]
+        rf.memory_bounds = (0, 199) 
+        rf.valid_name_length = 7
+        rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "-"
+        rf.valid_modes = MODES
+        rf.valid_tmodes = ['','Tone','TSQL','DTCS','Cross']
+        rf.valid_cross_modes = ['Tone->DTCS','DTCS->Tone',
+                               '->Tone','->DTCS','Tone->Tone']
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_dtcs_codes = chirp_common.ALL_DTCS_CODES
+        rf.valid_bands = self.valid_freq
+        return rf
+
+    # Do a download of the radio from the serial port
+    def sync_in(self):
+        self._mmap = do_download(self)
+        self.process_mmap()
+
+    # Do an upload of the radio to the serial port
+    def sync_out(self):
+        do_upload(self)
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+
+    # Return a raw representation of the memory object, which 
+    # is very helpful for development
+    def get_raw_memory(self, number):
+        return repr(self._memobj.memory[number])
+
+    # not working yet
+    def _get_dcs_index(self, _mem,which):
+        base = getattr(_mem, '%scode' % which)
+        extra = getattr(_mem, '%sdcsextra' % which)
+        return (int(extra) << 8) | int(base)
+
+    def _set_dcs_index(self, _mem, which, index):
+        base = getattr(_mem, '%scode' % which)
+        extra = getattr(_mem, '%sdcsextra' % which)
+        base.set_value(index & 0xFF)
+        extra.set_value(index >> 8)
+
+
+    # Extract a high-level memory object from the low-level memory map
+    # This is called to populate a memory in the UI
+    def get_memory(self, number):
+        # Get a low-level memory object mapped to the image
+        _mem = self._memobj.memory[number]
+
+        # get flag info
+        cbyte = number / 8 ;
+        cbit =  7 - (number % 8) ;
+        setflag = self._memobj.csetflag[cbyte].c[cbit]; 
+        skipflag = self._memobj.cskipflag[cbyte].c[cbit]; 
+
+        mem = chirp_common.Memory()
+
+        mem.number = number  # Set the memory number
+
+        if setflag == 1:
+            mem.empty = True
+            return mem
+
+        mem.freq = int(_mem.freq) * 100    
+        mem.offset = int(_mem.offset) * 100
+        mem.name = str(_mem.name).rstrip() # Set the alpha tag
+        mem.duplex = DUPLEXES[_mem.duplex]
+        mem.mode = MODES[_mem.channel_width]
+        mem.power = POWER_LEVELS[_mem.power]
+
+        rxtone = txtone = None
+
+
+        rxmode = TMODES[_mem.rxtmode]
+        txmode = TMODES[_mem.txtmode]
+
+
+
+        # doesn't work
+        if rxmode == "Tone":
+            rxtone = TONES[_mem.rxtone]
+        elif rxmode == "DTCS":
+            rxtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(_mem,'rx')]
+
+        if txmode == "Tone":
+            txtone = TONES[_mem.txtone]
+        elif txmode == "DTCS":
+            txtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(_mem,'tx')]
+
+        rxpol = _mem.rxinv and "R" or "N"
+        txpol = _mem.txinv and "R" or "N"
+
+        chirp_common.split_tone_decode(mem,
+                                       (txmode, txtone, txpol),
+                                       (rxmode, rxtone, rxpol))
+
+        mem.skip = "S" if skipflag == 1 else ""
+
+
+        # We'll consider any blank (i.e. 0MHz frequency) to be empty
+        if mem.freq == 0:
+            mem.empty = True
+
+        return mem
+
+    # Store details about a high-level memory to the memory map
+    # This is called when a user edits a memory in the UI
+    def set_memory(self, mem):
+        # Get a low-level memory object mapped to the image
+
+        _mem = self._memobj.memory[mem.number]
+
+        cbyte = mem.number / 8 
+        cbit =  7 - (mem.number % 8) 
+
+        if mem.empty:
+            self._memobj.csetflag[cbyte].c[cbit] = 1
+            self._memobj.cskipflag[cbyte].c[cbit] = 1
+            return
+
+        self._memobj.csetflag[cbyte].c[cbit] =  0 
+        self._memobj.cskipflag[cbyte].c[cbit]  =  1 if (mem.skip == "S") else 0
+
+        _mem.set_raw("\x00" * 32)
+
+        _mem.freq = mem.freq / 100         # Convert to low-level frequency
+        _mem.offset = mem.offset / 100         # Convert to low-level frequency
+
+        _mem.name = mem.name.ljust(7)[:7]  # Store the alpha tag
+        _mem.duplex = DUPLEXES.index(mem.duplex)
+
+
+        try:
+            _mem.channel_width = MODES.index(mem.mode)
+        except ValueError:
+            _mem.channel_width = 0
+
+        ((txmode, txtone, txpol),
+         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
+
+        _mem.txtmode = TMODES.index(txmode)
+
+        _mem.rxtmode = TMODES.index(rxmode)
+
+        if txmode == "Tone":
+            _mem.txtone = TONES.index(txtone)
+        elif txmode == "DTCS":
+            self._set_dcs_index(_mem,'tx',chirp_common.ALL_DTCS_CODES.index(txtone))
+
+        if rxmode == "Tone":
+            _mem.rxtone = TONES.index(rxtone)
+        elif rxmode == "DTCS":
+            self._set_dcs_index(_mem, 'rx', chirp_common.ALL_DTCS_CODES.index(rxtone))
+
+        _mem.txinv = txpol == "R"
+        _mem.rxinv = rxpol == "R"
+
+       
+        if mem.power:
+            _mem.power = POWER_LEVELS.index(mem.power)
+        else:
+            _mem.power = 0
+
+    def _get_settings(self):
+        _settings = self._memobj.settings
+        _freqrange = self._memobj.freqrange
+        _slabel = self._memobj.slabel
+
+        basic = RadioSettingGroup("basic","Global Settings")
+        freqrange = RadioSettingGroup("freqrange","Frequency Ranges")
+        top = RadioSettingGroup("top","All Settings",basic,freqrange)
+        settings = RadioSettings(top)
+
+        def _filter(name):
+            filtered = ""
+            for char in str(name):
+                if char in chirp_common.CHARSET_ASCII:
+                    filtered += char
+                else:
+                    filtered += ""
+            return filtered
+                   
+        val = RadioSettingValueString(0,7,_filter(_slabel.startname))
+        rs = RadioSetting("startname","Startup Label",val)
+        basic.append(rs)
+
+        rs = RadioSetting("bg_color","LCD Color",
+                           RadioSettingValueList(BGCOLOR_LIST, BGCOLOR_LIST[_settings.bg_color]))
+        basic.append(rs)
+
+        rs = RadioSetting("bg_brightness","LCD Brightness",
+                           RadioSettingValueList(BGBRIGHT_LIST, BGBRIGHT_LIST[_settings.bg_brightness]))
+        basic.append(rs)
+
+        rs = RadioSetting("squelch","Squelch Level",
+                           RadioSettingValueList(SQUELCH_LIST, SQUELCH_LIST[_settings.squelch]))
+        basic.append(rs)
+
+        rs = RadioSetting("timeout_timer","Timeout Timer (TOT)",
+                           RadioSettingValueList(TIMEOUT_LIST, TIMEOUT_LIST[_settings.timeout_timer]))
+        basic.append(rs)
+
+        rs = RadioSetting("auto_power_off","Auto Power Off (APO)",
+                           RadioSettingValueList(APO_LIST, APO_LIST[_settings.auto_power_off]))
+        basic.append(rs)
+
+        rs = RadioSetting("voice_prompt","Beep Prompt",
+                           RadioSettingValueList(BEEP_LIST, BEEP_LIST[_settings.voice_prompt]))
+        basic.append(rs)
+
+        rs = RadioSetting("tbst_freq","Tone Burst Frequency",
+                           RadioSettingValueList(TBSTFREQ_LIST, TBSTFREQ_LIST[_settings.tbst_freq]))
+        basic.append(rs)
+
+        rs = RadioSetting("choose_tx_power","Max Level of TX Power",
+                           RadioSettingValueList(TXPWR_LIST, TXPWR_LIST[_settings.choose_tx_power]))
+        basic.append(rs)
+
+        (flow,fhigh)  = self.valid_freq[0]
+        flow  /= 1000
+        fhigh /= 1000
+        fmidrange = (fhigh- flow)/2
+
+        rs = RadioSetting("txrangelow","TX Freq, Lower Limit (khz)", RadioSettingValueInteger(flow,
+            flow + fmidrange,
+            int(_freqrange.txrangelow)/10))
+        freqrange.append(rs)
+
+        rs = RadioSetting("txrangehi","TX Freq, Upper Limit (khz)", RadioSettingValueInteger(fhigh-fmidrange,
+            fhigh,
+            int(_freqrange.txrangehi)/10))
+        freqrange.append(rs)
+
+        rs = RadioSetting("rxrangelow","RX Freq, Lower Limit (khz)", RadioSettingValueInteger(flow,
+            flow+fmidrange,
+            int(_freqrange.rxrangelow)/10))
+        freqrange.append(rs)
+
+        rs = RadioSetting("rxrangehi","RX Freq, Upper Limit (khz)", RadioSettingValueInteger(fhigh-fmidrange,
+            fhigh,
+            int(_freqrange.rxrangehi)/10))
+        freqrange.append(rs)
+
+        return settings
+
+    def get_settings(self):
+        try:
+            return self._get_settings()
+        except:
+            import traceback
+            LOG.error( "failed to parse settings")
+            traceback.print_exc()
+            return None
+
+    def set_settings(self,settings):
+        _settings = self._memobj.settings
+        for element in settings:
+            if not isinstance(element,RadioSetting):
+                self.set_settings(element)
+                continue
+            else:
+                try:
+                    name = element.get_name()
+
+                    if  name in ["txrangelow","txrangehi","rxrangelow","rxrangehi"]:
+                        LOG.debug( "setting %s = %s" % (name,int(element.value)*10))
+                        setattr(self._memobj.freqrange,name,int(element.value)*10)
+                        continue
+
+                    if name in ["startname"]:
+                        LOG.debug( "setting %s = %s" % (name, element.value))
+                        setattr(self._memobj.slabel,name,element.value)
+                        continue
+
+                    obj = _settings
+                    setting = element.get_name()
+
+                    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):
+        if  MMAPSIZE == len(filedata):
+           (flow,fhigh)  = cls.valid_freq[0]
+           flow  /= 1000000
+           fhigh /= 1000000
+
+           txmin=ord(filedata[0x200])*100 + (ord(filedata[0x201])>>4)*10 + ord(filedata[0x201])%16
+           txmax=ord(filedata[0x204])*100 + (ord(filedata[0x205])>>4)*10 + ord(filedata[0x205])%16
+           rxmin=ord(filedata[0x208])*100 + (ord(filedata[0x209])>>4)*10 + ord(filedata[0x209])%16
+           rxmax=ord(filedata[0x20C])*100 + (ord(filedata[0x20D])>>4)*10 + ord(filedata[0x20D])%16
+
+           if ( rxmin >= flow and rxmax <= fhigh and txmin >= flow and txmax <= fhigh ):
+                return True
+
+        return False
+
+ at directory.register
+class Th9000220Radio(Th9000Radio):
+    """TYT TH-9000 220"""
+    VENDOR = "TYT"    
+    MODEL = "TH9000_220" 
+    BAUD_RATE = 9600 
+    valid_freq = [(220000000, 260000000)]
+
+ at directory.register
+class Th9000144Radio(Th9000220Radio):
+    """TYT TH-9000 144"""
+    VENDOR = "TYT"    
+    MODEL = "TH9000_144" 
+    BAUD_RATE = 9600 
+    valid_freq = [(136000000, 174000000)]
+
+ at directory.register
+class Th9000440Radio(Th9000220Radio):
+    """TYT TH-9000 440"""
+    VENDOR = "TYT"    
+    MODEL = "TH9000_440" 
+    BAUD_RATE = 9600 
+    valid_freq = [(400000000, 490000000)]
diff --git a/chirp/drivers/th9800.py b/chirp/drivers/th9800.py
new file mode 100644
index 0000000..b3ac955
--- /dev/null
+++ b/chirp/drivers/th9800.py
@@ -0,0 +1,782 @@
+# Copyright 2014 Tom Hayward <tom at tomh.us>
+# Copyright 2014 Jens Jensen <af5mi at yahoo.com>
+# Copyright 2014 James Lee N1DDK <jml at jmlzone.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from chirp import bitwise, chirp_common, directory, errors, util, memmap
+import struct
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettingValueString, \
+    RadioSettingValueFloat, InvalidValueError, RadioSettings
+from chirp.chirp_common import format_freq
+import os
+import time
+import logging
+from datetime import date
+
+LOG = logging.getLogger(__name__)
+
+TH9800_MEM_FORMAT = """
+struct mem {
+  lbcd rx_freq[4];
+  lbcd tx_freq[4];
+  lbcd ctcss[2];
+  lbcd dtcs[2];
+  u8 power:2,
+     BeatShift:1,
+     unknown0a:2,
+     display:1,     // freq=0, name=1
+     scan:2;
+  u8 fmdev:2,       // wide=00, mid=01, narrow=10
+     scramb:1,
+     compand:1,
+     emphasis:1
+     unknown1a:1,
+     unknown1b:2;
+  u8 rptmod:2,      // off, -, +
+     reverse:1,
+     talkaround:1,
+     step:4;
+  u8 dtcs_pol:2,
+     bclo:2,
+     unknown3:2,
+     tmode:2;
+  lbcd offset[4];
+  u8 hsdtype:2,     // off, 2-tone, 5-tone, dtmf
+     unknown5a:1,
+     am:1,
+     unknown5b:4;
+  u8 unknown6[3];
+  char name[6];
+  u8 empty[2];
+};
+
+#seekto 0x%04X;
+struct mem memory[800];
+
+#seekto 0x%04X;
+struct {
+  struct mem lower;
+  struct mem upper;
+} scanlimits[5];
+
+#seekto 0x%04X;
+struct {
+    u8  unk0xdc20:5,
+        left_sql:3;
+    u8  apo;
+    u8  unk0xdc22:5,
+        backlight:3;
+    u8  unk0xdc23;
+    u8  beep:1,
+        keylock:1,
+        pttlock:2,
+        unk0xdc24_32:2,
+        hyper_chan:1,
+        right_func_key:1;
+    u8  tbst_freq:2,
+        ani_display:1,
+        unk0xdc25_4:1
+        mute_mode:2,
+        unk0xdc25_10:2;
+    u8  auto_xfer:1,
+        auto_contact:1,
+        unk0xdc26_54:2,
+        auto_am:1,
+        unk0xdc26_210:3;
+    u8  unk0xdc27_76543:5,
+        scan_mode:1,
+        unk0xdc27_1:1,
+        scan_resume:1;
+    u16 scramb_freq;
+    u16 scramb_freq1;
+    u8  exit_delay;
+    u8  unk0xdc2d;
+    u8  unk0xdc2e:5,
+        right_sql:3;
+    u8  unk0xdc2f:4,
+        beep_vol:4;
+    u8  tot;
+    u8  tot_alert;
+    u8  tot_rekey;
+    u8  tot_reset;
+    u8  unk0xdc34;
+    u8  unk0xdc35;
+    u8  unk0xdc36;
+    u8  unk0xdc37;
+    u8  p1;
+    u8  p2;
+    u8  p3;
+    u8  p4;
+} settings;
+
+#seekto 0x%04X;
+u8  chan_active[128];
+u8  scan_enable[128];
+u8  priority[128];
+
+#seekto 0x%04X;
+struct {
+    char sn[8];
+    char model[8];
+    char code[16];
+    u8 empty[8];
+    lbcd prog_yr[2];
+    lbcd prog_mon;
+    lbcd prog_day;
+    u8 empty_10f2c[4];
+} info;
+
+struct {
+  lbcd lorx[4];
+  lbcd hirx[4];
+  lbcd lotx[4];
+  lbcd hitx[4];
+} bandlimits[9];
+
+"""
+
+
+BLANK_MEMORY = "\xFF" * 8 + "\x00\x10\x23\x00\xC0\x08\x06\x00" \
+               "\x00\x00\x76\x00\x00\x00" + "\xFF" * 10
+DTCS_POLARITY = ["NN", "RN", "NR", "RR"]
+SCAN_MODES = ["", "S", "P"]
+MODES = ["WFM", "FM", "NFM"]
+TMODES = ["", "Tone", "TSQL", "DTCS"]
+POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5.00),
+                chirp_common.PowerLevel("Mid2", watts=10.00),
+                chirp_common.PowerLevel("Mid1", watts=20.00),
+                chirp_common.PowerLevel("High", watts=50.00)]
+BUSY_LOCK = ["off", "Carrier", "2 tone"]
+MICKEYFUNC = ["None", "SCAN", "SQL.OFF", "TCALL", "PPTR", "PRI", "LOW", "TONE",
+              "MHz", "REV", "HOME", "BAND", "VFO/MR"]
+SQLPRESET = ["Off", "2", "5", "9", "Full"]
+BANDS = ["30MHz", "50MHz", "60MHz", "108MHz", "150MHz", "250MHz", "350MHz",
+         "450MHz", "850MHz"]
+STEPS = [2.5, 5.0, 6.25, 7.5, 8.33, 10.0, 12.5,
+         15.0, 20.0, 25.0, 30.0, 50.0, 100.0]
+
+
+class TYTTH9800Base(chirp_common.Radio):
+    """Base class for TYT TH-9800"""
+    VENDOR = "TYT"
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.memory_bounds = (1, 800)
+        rf.has_bank = False
+        rf.has_tuning_step = True
+        rf.valid_tuning_steps = STEPS
+        rf.can_odd_split = True
+        rf.valid_duplexes = ["", "-", "+", "split", "off"]
+        rf.valid_tmodes = TMODES
+        rf.has_ctone = False
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "#*-+"
+        rf.valid_bands = [(26000000,  33000000),
+                          (47000000,  54000000),
+                          (108000000, 180000000),
+                          (350000000, 399995000),
+                          (400000000, 512000000),
+                          (750000000, 950000000)]
+        rf.valid_skips = SCAN_MODES
+        rf.valid_modes = MODES + ["AM"]
+        rf.valid_name_length = 6
+        rf.has_settings = True
+        return rf
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(
+            TH9800_MEM_FORMAT %
+            (self._mmap_offset, self._scanlimits_offset, self._settings_offset,
+             self._chan_active_offset, self._info_offset), self._mmap)
+
+    def get_active(self, banktype, num):
+        """get active flag for channel active,
+        scan enable, or priority banks"""
+        bank = getattr(self._memobj, banktype)
+        index = (num - 1) / 8
+        bitpos = (num - 1) % 8
+        mask = 2**bitpos
+        enabled = bank[index] & mask
+        if enabled:
+            return True
+        else:
+            return False
+
+    def set_active(self, banktype, num, enable=True):
+        """set active flag for channel active,
+        scan enable, or priority banks"""
+        bank = getattr(self._memobj, banktype)
+        index = (num - 1) / 8
+        bitpos = (num - 1) % 8
+        mask = 2**bitpos
+        if enable:
+            bank[index] |= mask
+        else:
+            bank[index] &= ~mask
+
+    def get_raw_memory(self, number):
+        return repr(self._memobj.memory[number - 1])
+
+    def get_memory(self, number):
+        _mem = self._memobj.memory[number - 1]
+        mem = chirp_common.Memory()
+        mem.number = number
+
+        mem.empty = not self.get_active("chan_active", number)
+        if mem.empty:
+            return mem
+
+        mem.freq = int(_mem.rx_freq) * 10
+
+        txfreq = int(_mem.tx_freq) * 10
+        if txfreq == mem.freq:
+            mem.duplex = ""
+        elif txfreq == 0:
+            mem.duplex = "off"
+            mem.offset = 0
+        elif abs(txfreq - mem.freq) > 70000000:
+            mem.duplex = "split"
+            mem.offset = txfreq
+        elif txfreq < mem.freq:
+            mem.duplex = "-"
+            mem.offset = mem.freq - txfreq
+        elif txfreq > mem.freq:
+            mem.duplex = "+"
+            mem.offset = txfreq - mem.freq
+
+        mem.dtcs_polarity = DTCS_POLARITY[_mem.dtcs_pol]
+
+        mem.tmode = TMODES[int(_mem.tmode)]
+        mem.ctone = mem.rtone = int(_mem.ctcss) / 10.0
+        mem.dtcs = int(_mem.dtcs)
+
+        mem.name = str(_mem.name)
+        mem.name = mem.name.replace("\xFF", " ").rstrip()
+
+        if not self.get_active("scan_enable", number):
+            mem.skip = "S"
+        elif self.get_active("priority", number):
+            mem.skip = "P"
+        else:
+            mem.skip = ""
+
+        mem.mode = _mem.am and "AM" or MODES[int(_mem.fmdev)]
+
+        mem.power = POWER_LEVELS[_mem.power]
+        mem.tuning_step = STEPS[_mem.step]
+
+        mem.extra = RadioSettingGroup("extra", "Extra")
+
+        opts = ["Frequency", "Name"]
+        display = RadioSetting(
+                "display", "Display",
+                RadioSettingValueList(opts, opts[_mem.display]))
+        mem.extra.append(display)
+
+        bclo = RadioSetting(
+                "bclo", "Busy Lockout",
+                RadioSettingValueList(BUSY_LOCK, BUSY_LOCK[_mem.bclo]))
+        bclo.set_doc("Busy Lockout")
+        mem.extra.append(bclo)
+
+        emphasis = RadioSetting(
+                "emphasis", "Emphasis",
+                RadioSettingValueBoolean(bool(_mem.emphasis)))
+        emphasis.set_doc("Boosts 300Hz to 2500Hz mic response")
+        mem.extra.append(emphasis)
+
+        compand = RadioSetting(
+                "compand", "Compand",
+                RadioSettingValueBoolean(bool(_mem.compand)))
+        compand.set_doc("Compress Audio")
+        mem.extra.append(compand)
+
+        BeatShift = RadioSetting(
+                "BeatShift", "BeatShift",
+                RadioSettingValueBoolean(bool(_mem.BeatShift)))
+        BeatShift.set_doc("Beat Shift")
+        mem.extra.append(BeatShift)
+
+        TalkAround = RadioSetting(
+                "talkaround", "Talk Around",
+                RadioSettingValueBoolean(bool(_mem.talkaround)))
+        TalkAround.set_doc("Simplex mode when out of range of repeater")
+        mem.extra.append(TalkAround)
+
+        scramb = RadioSetting(
+                "scramb", "Scramble",
+                RadioSettingValueBoolean(bool(_mem.scramb)))
+        scramb.set_doc("Frequency inversion Scramble")
+        mem.extra.append(scramb)
+
+        return mem
+
+    def set_memory(self, mem):
+        _mem = self._memobj.memory[mem.number - 1]
+
+        _prev_active = self.get_active("chan_active", mem.number)
+        self.set_active("chan_active", mem.number, not mem.empty)
+        if mem.empty or not _prev_active:
+            LOG.debug("initializing memory channel %d" % mem.number)
+            _mem.set_raw(BLANK_MEMORY)
+
+        if mem.empty:
+            return
+
+        _mem.rx_freq = mem.freq / 10
+        if mem.duplex == "split":
+            _mem.tx_freq = mem.offset / 10
+        elif mem.duplex == "-":
+            _mem.tx_freq = (mem.freq - mem.offset) / 10
+        elif mem.duplex == "+":
+            _mem.tx_freq = (mem.freq + mem.offset) / 10
+        elif mem.duplex == "off":
+            _mem.tx_freq = 0
+            _mem.offset = 0
+        else:
+            _mem.tx_freq = mem.freq / 10
+
+        _mem.tmode = TMODES.index(mem.tmode)
+        _mem.ctcss = mem.rtone * 10
+        _mem.dtcs = mem.dtcs
+        _mem.dtcs_pol = DTCS_POLARITY.index(mem.dtcs_polarity)
+
+        _mem.name = mem.name.ljust(6, "\xFF")
+
+        # autoset display to name if filled, else show frequency
+        if mem.extra:
+            # mem.extra only seems to be populated when called from edit panel
+            display = mem.extra["display"]
+        else:
+            display = None
+        if mem.name:
+            _mem.display = True
+            if display and not display.changed():
+                display.value = "Name"
+        else:
+            _mem.display = False
+            if display and not display.changed():
+                display.value = "Frequency"
+
+        _mem.scan = SCAN_MODES.index(mem.skip)
+        if mem.skip == "P":
+            self.set_active("priority", mem.number, True)
+            self.set_active("scan_enable", mem.number, True)
+        elif mem.skip == "S":
+            self.set_active("priority", mem.number, False)
+            self.set_active("scan_enable", mem.number, False)
+        elif mem.skip == "":
+            self.set_active("priority", mem.number, False)
+            self.set_active("scan_enable", mem.number, True)
+
+        if mem.mode == "AM":
+            _mem.am = True
+            _mem.fmdev = 0
+        else:
+            _mem.am = False
+            _mem.fmdev = MODES.index(mem.mode)
+
+        if mem.power:
+            _mem.power = POWER_LEVELS.index(mem.power)
+        else:
+            _mem.power = 0    # low
+        _mem.step = STEPS.index(mem.tuning_step)
+
+        for setting in mem.extra:
+            LOG.debug("@set_mem:", setting.get_name(), setting.value)
+            setattr(_mem, setting.get_name(), setting.value)
+
+    def get_settings(self):
+        _settings = self._memobj.settings
+        _info = self._memobj.info
+        _bandlimits = self._memobj.bandlimits
+        basic = RadioSettingGroup("basic", "Basic")
+        info = RadioSettingGroup("info", "Model Info")
+        top = RadioSettings(basic, info)
+        basic.append(RadioSetting(
+                "beep", "Beep",
+                RadioSettingValueBoolean(_settings.beep)))
+        basic.append(RadioSetting(
+                "beep_vol", "Beep Volume",
+                RadioSettingValueInteger(0, 15, _settings.beep_vol)))
+        basic.append(RadioSetting(
+                "keylock", "Key Lock",
+                RadioSettingValueBoolean(_settings.keylock)))
+        basic.append(RadioSetting(
+                "ani_display", "ANI Display",
+                RadioSettingValueBoolean(_settings.ani_display)))
+        basic.append(RadioSetting(
+                "auto_xfer", "Auto Transfer",
+                RadioSettingValueBoolean(_settings.auto_xfer)))
+        basic.append(RadioSetting(
+                "auto_contact", "Auto Contact Always Remind",
+                RadioSettingValueBoolean(_settings.auto_contact)))
+        basic.append(RadioSetting(
+                "auto_am", "Auto AM",
+                RadioSettingValueBoolean(_settings.auto_am)))
+        basic.append(RadioSetting(
+                "left_sql", "Left Squelch",
+                RadioSettingValueList(
+                    SQLPRESET, SQLPRESET[_settings.left_sql])))
+        basic.append(RadioSetting(
+                "right_sql", "Right Squelch",
+                RadioSettingValueList(
+                    SQLPRESET, SQLPRESET[_settings.right_sql])))
+#      basic.append(RadioSetting("apo", "Auto Power off (0.1h)",
+#              RadioSettingValueInteger(0, 20, _settings.apo)))
+        opts = ["Off"] + ["%0.1f" % (t / 10.0) for t in range(1, 21, 1)]
+        basic.append(RadioSetting(
+                "apo", "Auto Power off (Hours)",
+                RadioSettingValueList(opts, opts[_settings.apo])))
+        opts = ["Off", "1", "2", "3", "Full"]
+        basic.append(RadioSetting(
+                "backlight", "Display Backlight",
+                RadioSettingValueList(opts, opts[_settings.backlight])))
+        opts = ["Off", "Right", "Left", "Both"]
+        basic.append(RadioSetting(
+                "pttlock", "PTT Lock",
+                RadioSettingValueList(opts, opts[_settings.pttlock])))
+        opts = ["Manual", "Auto"]
+        basic.append(RadioSetting(
+                "hyper_chan", "Hyper Channel",
+                RadioSettingValueList(opts, opts[_settings.hyper_chan])))
+        opts = ["Key 1", "Key 2"]
+        basic.append(RadioSetting(
+                "right_func_key", "Right Function Key",
+                RadioSettingValueList(opts, opts[_settings.right_func_key])))
+        opts = ["1000Hz", "1450Hz", "1750Hz", "2100Hz"]
+        basic.append(RadioSetting(
+                "tbst_freq", "Tone Burst Frequency",
+                RadioSettingValueList(opts, opts[_settings.tbst_freq])))
+        opts = ["Off", "TX", "RX", "TX RX"]
+        basic.append(RadioSetting(
+                "mute_mode", "Mute Mode",
+                RadioSettingValueList(opts, opts[_settings.mute_mode])))
+        opts = ["MEM", "MSM"]
+        scanmode = RadioSetting(
+                "scan_mode", "Scan Mode",
+                RadioSettingValueList(opts, opts[_settings.scan_mode]))
+        scanmode.set_doc("MEM = Normal scan, bypass channels marked skip. "
+                         " MSM = Scan only channels marked priority.")
+        basic.append(scanmode)
+        opts = ["TO", "CO"]
+        basic.append(RadioSetting(
+                "scan_resume", "Scan Resume",
+                RadioSettingValueList(opts, opts[_settings.scan_resume])))
+        opts = ["%0.1f" % (t / 10.0) for t in range(0, 51, 1)]
+        basic.append(RadioSetting(
+                "exit_delay", "Span Transit Exit Delay",
+                RadioSettingValueList(opts, opts[_settings.exit_delay])))
+        basic.append(RadioSetting(
+                "tot", "Time Out Timer (minutes)",
+                RadioSettingValueInteger(0, 30, _settings.tot)))
+        basic.append(RadioSetting(
+                "tot_alert", "Time Out Timer Pre Alert(seconds)",
+                RadioSettingValueInteger(0, 15, _settings.tot_alert)))
+        basic.append(RadioSetting(
+                "tot_rekey", "Time Out Rekey (seconds)",
+                RadioSettingValueInteger(0, 15, _settings.tot_rekey)))
+        basic.append(RadioSetting(
+                "tot_reset", "Time Out Reset(seconds)",
+                RadioSettingValueInteger(0, 15, _settings.tot_reset)))
+        basic.append(RadioSetting(
+                "p1", "P1 Function",
+                RadioSettingValueList(MICKEYFUNC, MICKEYFUNC[_settings.p1])))
+        basic.append(RadioSetting(
+                "p2", "P2 Function",
+                RadioSettingValueList(MICKEYFUNC, MICKEYFUNC[_settings.p2])))
+        basic.append(RadioSetting(
+                "p3", "P3 Function",
+                RadioSettingValueList(MICKEYFUNC, MICKEYFUNC[_settings.p3])))
+        basic.append(RadioSetting(
+                "p4", "P4 Function",
+                RadioSettingValueList(MICKEYFUNC, MICKEYFUNC[_settings.p4])))
+#      opts = ["0", "1"]
+#      basic.append(RadioSetting("x", "Desc",
+#            RadioSettingValueList(opts, opts[_settings.x])))
+
+        def _filter(name):
+            filtered = ""
+            for char in str(name):
+                if char in chirp_common.CHARSET_ASCII:
+                    filtered += char
+                else:
+                    filtered += " "
+            return filtered
+
+        rsvs = RadioSettingValueString(0, 8, _filter(_info.sn))
+        rsvs.set_mutable(False)
+        rs = RadioSetting("sn", "Serial Number", rsvs)
+        info.append(rs)
+
+        rsvs = RadioSettingValueString(0, 8, _filter(_info.model))
+        rsvs.set_mutable(False)
+        rs = RadioSetting("model", "Model Name", rsvs)
+        info.append(rs)
+
+        rsvs = RadioSettingValueString(0, 16, _filter(_info.code))
+        rsvs.set_mutable(False)
+        rs = RadioSetting("code", "Model Code", rsvs)
+        info.append(rs)
+
+        progdate = "%d/%d/%d" % (_info.prog_mon, _info.prog_day,
+                                 _info.prog_yr)
+        rsvs = RadioSettingValueString(0, 10, progdate)
+        rsvs.set_mutable(False)
+        rs = RadioSetting("progdate", "Last Program Date", rsvs)
+        info.append(rs)
+
+        # 9 band limits
+        for i in range(0, 9):
+            objname = BANDS[i] + "lorx"
+            objnamepp = BANDS[i] + " Rx Start"
+            # rsv = RadioSettingValueInteger(0, 100000000,
+            #              int(_bandlimits[i].lorx))
+            rsv = RadioSettingValueString(
+                    0, 10, format_freq(int(_bandlimits[i].lorx)*10))
+            rsv.set_mutable(False)
+            rs = RadioSetting(objname, objnamepp, rsv)
+            info.append(rs)
+            objname = BANDS[i] + "hirx"
+            objnamepp = BANDS[i] + " Rx end"
+            rsv = RadioSettingValueString(
+                    0, 10, format_freq(int(_bandlimits[i].hirx)*10))
+            rsv.set_mutable(False)
+            rs = RadioSetting(objname, objnamepp, rsv)
+            info.append(rs)
+            objname = BANDS[i] + "lotx"
+            objnamepp = BANDS[i] + " Tx Start"
+            rsv = RadioSettingValueString(
+                    0, 10, format_freq(int(_bandlimits[i].lotx)*10))
+            rsv.set_mutable(False)
+            rs = RadioSetting(objname, objnamepp, rsv)
+            info.append(rs)
+            objname = BANDS[i] + "hitx"
+            objnamepp = BANDS[i] + " Tx end"
+            rsv = RadioSettingValueString(
+                    0, 10, format_freq(int(_bandlimits[i].hitx)*10))
+            rsv.set_mutable(False)
+            rs = RadioSetting(objname, objnamepp, rsv)
+            info.append(rs)
+
+        return top
+
+    def set_settings(self, settings):
+        _settings = self._memobj.settings
+        _info = self._memobj.info
+        _bandlimits = self._memobj.bandlimits
+        for element in settings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+            if not element.changed():
+                continue
+            try:
+                setting = element.get_name()
+                oldval = getattr(_settings, setting)
+                newval = element.value
+
+                LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval))
+                setattr(_settings, setting, newval)
+            except Exception, e:
+                LOG.debug(element.get_name())
+                raise
+
+
+ at directory.register
+class TYTTH9800File(TYTTH9800Base, chirp_common.FileBackedRadio):
+    """TYT TH-9800 .dat file"""
+    MODEL = "TH-9800 File"
+
+    FILE_EXTENSION = "dat"
+
+    _memsize = 69632
+    _mmap_offset = 0x1100
+    _scanlimits_offset = 0xC800 + _mmap_offset
+    _settings_offset = 0xCB20 + _mmap_offset
+    _chan_active_offset = 0xCB80 + _mmap_offset
+    _info_offset = 0xfe00 + _mmap_offset
+
+    def __init__(self, pipe):
+        self.errors = []
+        self._mmap = None
+
+        if isinstance(pipe, str):
+            self.pipe = None
+            self.load_mmap(pipe)
+        else:
+            chirp_common.FileBackedRadio.__init__(self, pipe)
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return len(filedata) == cls._memsize and filename.endswith('.dat')
+
+
+def _identify(radio):
+    """Do identify handshake with TYT"""
+    try:
+        radio.pipe.write("\x02PROGRA")
+        ack = radio.pipe.read(1)
+        if ack != "A":
+            util.hexprint(ack)
+            raise errors.RadioError("Radio did not ACK first command: %x"
+                                    % ord(ack))
+    except:
+        LOG.debug(util.hexprint(ack))
+        raise errors.RadioError("Unable to communicate with the radio")
+
+    radio.pipe.write("M\x02")
+    ident = radio.pipe.read(16)
+    radio.pipe.write("A")
+    r = radio.pipe.read(1)
+    if r != "A":
+        raise errors.RadioError("Ack failed")
+    return ident
+
+
+def _download(radio, memsize=0x10000, blocksize=0x80):
+    """Download from TYT TH-9800"""
+    data = _identify(radio)
+    LOG.info("ident:", util.hexprint(data))
+    offset = 0x100
+    for addr in range(offset, memsize, blocksize):
+        msg = struct.pack(">cHB", "R", addr, blocksize)
+        radio.pipe.write(msg)
+        block = radio.pipe.read(blocksize + 4)
+        if len(block) != (blocksize + 4):
+            LOG.debug(util.hexprint(block))
+            raise errors.RadioError("Radio sent a short block")
+        radio.pipe.write("A")
+        ack = radio.pipe.read(1)
+        if ack != "A":
+            LOG.debug(util.hexprint(ack))
+            raise errors.RadioError("Radio NAKed block")
+        data += block[4:]
+
+        if radio.status_fn:
+            status = chirp_common.Status()
+            status.cur = addr
+            status.max = memsize
+            status.msg = "Cloning from radio"
+            radio.status_fn(status)
+
+    radio.pipe.write("ENDR")
+
+    return memmap.MemoryMap(data)
+
+
+def _upload(radio, memsize=0xF400, blocksize=0x80):
+    """Upload to TYT TH-9800"""
+    data = _identify(radio)
+
+    radio.pipe.setTimeout(1)
+
+    if data != radio._mmap[:radio._mmap_offset]:
+        raise errors.RadioError(
+            "Model mis-match: \n%s\n%s" %
+            (util.hexprint(data),
+             util.hexprint(radio._mmap[:radio._mmap_offset])))
+    # in the factory software they update the last program date when
+    # they upload, So let's do the same
+    today = date.today()
+    y = today.year
+    m = today.month
+    d = today.day
+    _info = radio._memobj.info
+
+    ly = _info.prog_yr
+    lm = _info.prog_mon
+    ld = _info.prog_day
+    LOG.debug("Updating last program date:%d/%d/%d" % (lm, ld, ly))
+    LOG.debug("                  to today:%d/%d/%d" % (m, d, y))
+
+    _info.prog_yr = y
+    _info.prog_mon = m
+    _info.prog_day = d
+
+    offset = 0x0100
+    for addr in range(offset, memsize, blocksize):
+        mapaddr = addr + radio._mmap_offset - offset
+        LOG.debug("addr: 0x%04X, mmapaddr: 0x%04X" % (addr, mapaddr))
+        msg = struct.pack(">cHB", "W", addr, blocksize)
+        msg += radio._mmap[mapaddr:(mapaddr + blocksize)]
+        LOG.debug(util.hexprint(msg))
+        radio.pipe.write(msg)
+        ack = radio.pipe.read(1)
+        if ack != "A":
+            LOG.debug(util.hexprint(ack))
+            raise errors.RadioError("Radio did not ack block 0x%04X" % addr)
+
+        if radio.status_fn:
+            status = chirp_common.Status()
+            status.cur = addr
+            status.max = memsize
+            status.msg = "Cloning to radio"
+            radio.status_fn(status)
+
+    # End of clone
+    radio.pipe.write("ENDW")
+
+    # Checksum?
+    final_data = radio.pipe.read(3)
+    LOG.debug("final:", util.hexprint(final_data))
+
+
+ at directory.register
+class TYTTH9800Radio(TYTTH9800Base, chirp_common.CloneModeRadio,
+                     chirp_common.ExperimentalRadio):
+    VENDOR = "TYT"
+    MODEL = "TH-9800"
+    BAUD_RATE = 38400
+
+    _memsize = 65296
+    _mmap_offset = 0x0010
+    _scanlimits_offset = 0xC800 + _mmap_offset
+    _settings_offset = 0xCB20 + _mmap_offset
+    _chan_active_offset = 0xCB80 + _mmap_offset
+    _info_offset = 0xfe00 + _mmap_offset
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return len(filedata) == cls._memsize
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.experimental = (
+         'This is experimental support for TH-9800 '
+         'which is still under development.\n'
+         'Please ensure you have a good backup with OEM software.\n'
+         'Also please send in bug and enhancement requests!\n'
+         'You have been warned. Proceed at your own risk!')
+        return rp
+
+    def sync_in(self):
+        try:
+            self._mmap = _download(self)
+        except Exception, e:
+            raise errors.RadioError(
+                    "Failed to communicate with the radio: %s" % e)
+        self.process_mmap()
+
+    def sync_out(self):
+        try:
+            _upload(self)
+        except Exception, e:
+            raise errors.RadioError(
+                    "Failed to communicate with the radio: %s" % e)
diff --git a/chirp/th_uv3r.py b/chirp/drivers/th_uv3r.py
similarity index 96%
rename from chirp/th_uv3r.py
rename to chirp/drivers/th_uv3r.py
index 5e3e2c0..77eca93 100644
--- a/chirp/th_uv3r.py
+++ b/chirp/drivers/th_uv3r.py
@@ -16,13 +16,12 @@
 """TYT uv3r radio management module"""
 
 import os
+import logging
 from chirp import chirp_common, bitwise, errors, directory
-from chirp.wouxun_common import do_download, do_upload
+from chirp.drivers.wouxun import do_download, do_upload
+
+LOG = logging.getLogger(__name__)
 
-if os.getenv("CHIRP_DEBUG"):
-    DEBUG = True
-else:
-    DEBUG = False
 
 def tyt_uv3r_prep(radio):
     try:
@@ -33,10 +32,12 @@ def tyt_uv3r_prep(radio):
     except:
         raise errors.RadioError("Unable to communicate with the radio")
 
+
 def tyt_uv3r_download(radio):
     tyt_uv3r_prep(radio)
     return do_download(radio, 0x0000, 0x0910, 0x0010)
 
+
 def tyt_uv3r_upload(radio):
     tyt_uv3r_prep(radio)
     return do_upload(radio, 0x0000, 0x0910, 0x0010)
@@ -69,6 +70,8 @@ THUV3R_CHARSET = "".join([chr(ord("0") + x) for x in range(0, 10)] +
                          [" -*+"] +
                          [chr(ord("A") + x) for x in range(0, 26)] +
                          ["_/"])
+
+
 @directory.register
 class TYTUV3RRadio(chirp_common.CloneModeRadio):
     VENDOR = "TYT"
@@ -152,7 +155,7 @@ class TYTUV3RRadio(chirp_common.CloneModeRadio):
         if rx_mode == "Tone":
             mem.ctone = rx_tone
         elif rx_mode == "DTCS":
-            mem.dtcs = rx_tone # No support for different codes yet
+            mem.dtcs = rx_tone  # No support for different codes yet
 
     def _encode_tone(self, mem, _mem):
         if mem.tmode == "":
@@ -161,6 +164,7 @@ class TYTUV3RRadio(chirp_common.CloneModeRadio):
 
         def _tone(val):
             return int("%i" % (val * 10), 16)
+
         def _dcs(val, pol):
             polmask = pol == "R" and 0xC000 or 0x8000
             return int("%i" % (val), 16) | polmask
@@ -190,7 +194,7 @@ class TYTUV3RRadio(chirp_common.CloneModeRadio):
                 rx_tone = _tone(mem.ctone)
 
         _mem.rx_tone = rx_tone
-        _mem.tx_tone = tx_tone            
+        _mem.tx_tone = tx_tone
 
     def get_memory(self, number):
         _mem = self._memobj.memory[number - 1]
@@ -259,9 +263,8 @@ class TYTUV3RRadio(chirp_common.CloneModeRadio):
                 c = THUV3R_CHARSET.index(" ")
             name.append(c)
         _mem.name = name
-        print repr(_mem)
+        LOG.debug(repr(_mem))
 
     @classmethod
     def match_model(cls, filedata, filename):
         return len(filedata) == 2320
-
diff --git a/chirp/drivers/th_uv3r25.py b/chirp/drivers/th_uv3r25.py
new file mode 100644
index 0000000..bba73d4
--- /dev/null
+++ b/chirp/drivers/th_uv3r25.py
@@ -0,0 +1,209 @@
+# Copyright 2015 Eric Allen <eric at hackerengineer.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/>.
+
+"""TYT uv3r (2.5kHz) radio management module"""
+
+from chirp import chirp_common, bitwise, directory
+from chirp.drivers.wouxun import do_download, do_upload
+
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettingValueString
+
+from th_uv3r import TYTUV3RRadio, tyt_uv3r_prep, THUV3R_CHARSET
+
+
+def tyt_uv3r_download(radio):
+    tyt_uv3r_prep(radio)
+    return do_download(radio, 0x0000, 0x0B30, 0x0010)
+
+
+def tyt_uv3r_upload(radio):
+    tyt_uv3r_prep(radio)
+    return do_upload(radio, 0x0000, 0x0B30, 0x0010)
+
+mem_format = """
+// 20 bytes per memory
+struct memory {
+  ul32 rx_freq; // 4 bytes
+  ul32 tx_freq; // 8 bytes
+
+  ul16 rx_tone; // 10 bytes
+  ul16 tx_tone; // 12 bytes
+
+  u8 unknown1a:1,
+     iswide:1,
+     bclo_n:1,
+     vox_n:1,
+     tail:1,
+     power_high:1,
+     voice_mode:2;
+  u8 name[6]; // 19 bytes
+  u8 unknown2; // 20 bytes
+};
+
+#seekto 0x0010;
+struct memory memory[128];
+
+#seekto 0x0A80;
+u8 emptyflags[16];
+u8 skipflags[16];
+"""
+
+VOICE_MODE_LIST = ["Compander", "Scrambler", "None"]
+
+
+ at directory.register
+class TYTUV3R25Radio(TYTUV3RRadio):
+    MODEL = "TH-UV3R-25"
+    _memsize = 2864
+
+    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
+                    chirp_common.PowerLevel("Low", watts=0.80)]
+
+    def get_features(self):
+        rf = super(TYTUV3R25Radio, self).get_features()
+
+        rf.valid_tuning_steps = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 37.50,
+                                 50.0, 100.0]
+        rf.valid_power_levels = self.POWER_LEVELS
+        return rf
+
+    def sync_in(self):
+        self.pipe.setTimeout(2)
+        self._mmap = tyt_uv3r_download(self)
+        self.process_mmap()
+
+    def sync_out(self):
+        tyt_uv3r_upload(self)
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(mem_format, self._mmap)
+
+    def get_raw_memory(self, number):
+        return repr(self._memobj.memory[number - 1])
+
+    def get_memory(self, number):
+        _mem = self._memobj.memory[number - 1]
+        mem = chirp_common.Memory()
+        mem.number = number
+
+        bit = 1 << ((number - 1) % 8)
+        byte = (number - 1) / 8
+
+        if self._memobj.emptyflags[byte] & bit:
+            mem.empty = True
+            return mem
+
+        mem.freq = _mem.rx_freq * 10
+        mem.offset = abs(_mem.rx_freq - _mem.tx_freq) * 10
+        if _mem.tx_freq == _mem.rx_freq:
+            mem.duplex = ""
+        elif _mem.tx_freq < _mem.rx_freq:
+            mem.duplex = "-"
+        elif _mem.tx_freq > _mem.rx_freq:
+            mem.duplex = "+"
+
+        mem.mode = _mem.iswide and "FM" or "NFM"
+        self._decode_tone(mem, _mem)
+        mem.skip = (self._memobj.skipflags[byte] & bit) and "S" or ""
+
+        for char in _mem.name:
+            try:
+                c = THUV3R_CHARSET[char]
+            except:
+                c = ""
+            mem.name += c
+        mem.name = mem.name.rstrip()
+
+        mem.power = self.POWER_LEVELS[not _mem.power_high]
+
+        mem.extra = RadioSettingGroup("extra", "Extra Settings")
+
+        rs = RadioSetting("bclo_n", "Busy Channel Lockout",
+                          RadioSettingValueBoolean(not _mem.bclo_n))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("vox_n", "VOX",
+                          RadioSettingValueBoolean(not _mem.vox_n))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("tail", "Squelch Tail Elimination",
+                          RadioSettingValueBoolean(_mem.tail))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("voice_mode", "Voice Mode",
+                          RadioSettingValueList(
+                              VOICE_MODE_LIST,
+                              VOICE_MODE_LIST[_mem.voice_mode-1]))
+        mem.extra.append(rs)
+
+        return mem
+
+    def set_memory(self, mem):
+        _mem = self._memobj.memory[mem.number - 1]
+        bit = 1 << ((mem.number - 1) % 8)
+        byte = (mem.number - 1) / 8
+
+        if mem.empty:
+            self._memobj.emptyflags[byte] |= bit
+            _mem.set_raw("\xFF" * 16)
+            return
+
+        self._memobj.emptyflags[byte] &= ~bit
+
+        _mem.rx_freq = mem.freq / 10
+
+        if mem.duplex == "":
+            _mem.tx_freq = _mem.rx_freq
+        elif mem.duplex == "-":
+            _mem.tx_freq = _mem.rx_freq - mem.offset / 10.0
+        elif mem.duplex == "+":
+            _mem.tx_freq = _mem.rx_freq + mem.offset / 10.0
+
+        _mem.iswide = mem.mode == "FM"
+
+        self._encode_tone(mem, _mem)
+
+        if mem.skip:
+            self._memobj.skipflags[byte] |= bit
+        else:
+            self._memobj.skipflags[byte] &= ~bit
+
+        name = []
+        for char in mem.name.ljust(6):
+            try:
+                c = THUV3R_CHARSET.index(char)
+            except:
+                c = THUV3R_CHARSET.index(" ")
+            name.append(c)
+        _mem.name = name
+
+        if mem.power == self.POWER_LEVELS[0]:
+            _mem.power_high = 1
+        else:
+            _mem.power_high = 0
+
+        for element in mem.extra:
+            if element.get_name() == 'voice_mode':
+                setattr(_mem, element.get_name(), int(element.value) + 1)
+            elif element.get_name().endswith('_n'):
+                setattr(_mem, element.get_name(), 1 - int(element.value))
+            else:
+                setattr(_mem, element.get_name(), element.value)
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return len(filedata) == cls._memsize
diff --git a/chirp/th_uvf8d.py b/chirp/drivers/th_uvf8d.py
similarity index 64%
rename from chirp/th_uvf8d.py
rename to chirp/drivers/th_uvf8d.py
index 74e73ce..f20042e 100644
--- a/chirp/th_uvf8d.py
+++ b/chirp/drivers/th_uvf8d.py
@@ -1,4 +1,5 @@
-# Copyright 2013 Dan Smith <dsmith at danplanet.com>, Eric Allen <eric at hackerengineer.net>
+# Copyright 2013 Dan Smith <dsmith at danplanet.com>
+#                Eric Allen <eric at hackerengineer.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
@@ -21,13 +22,16 @@
 # TODO: [setting] Tail Eliminate
 # TODO: [setting] Tail Mode
 
-
 import struct
+import logging
 
 from chirp import chirp_common, bitwise, errors, directory, memmap, util
 from chirp.settings import RadioSetting, RadioSettingGroup, \
     RadioSettingValueInteger, RadioSettingValueList, \
-    RadioSettingValueBoolean, RadioSettingValueString
+    RadioSettingValueBoolean, RadioSettingValueString, \
+    RadioSettings
+
+LOG = logging.getLogger(__name__)
 
 
 def uvf8d_identify(radio):
@@ -36,7 +40,8 @@ def uvf8d_identify(radio):
         radio.pipe.write("\x02PROGRAM")
         ack = radio.pipe.read(2)
         if ack != "PG":
-            raise errors.RadioError("Radio did not ACK first command: %x" % ord(ack))
+            raise errors.RadioError("Radio did not ACK first command: %x" %
+                                    ord(ack))
     except:
         raise errors.RadioError("Unable to communicate with the radio")
 
@@ -82,8 +87,9 @@ def tyt_uvf8d_upload(radio):
     radio.pipe.setTimeout(1)
 
     if data != radio._mmap[:32]:
-        raise errors.RadioError("Model mis-match: \n%s\n%s" % (util.hexprint(data),
-                                                               util.hexprint(radio._mmap[:32])))
+        raise errors.RadioError("Model mis-match: \n%s\n%s" %
+                                (util.hexprint(data),
+                                 util.hexprint(radio._mmap[:32])))
 
     for i in range(0, 0x4000, 0x20):
         addr = i + 0x20
@@ -247,8 +253,10 @@ APRO_LIST = ["Off", "Compander", "Scramble 1", "Scramble 2", "Scramble 3",
              "Scramble 8"]
 LOCK_MODE_LIST = ["PTT", "Key", "Key+S", "All"]
 
-TUNING_STEPS_LIST = ["2.5", "5.0", "6.25", "10.0", "12.5", "25.0", "50.0", "100.0"]
-BACKLIGHT_TIMEOUT_LIST = ["1s", "2s", "3s", "4s", "5s", "6s", "7s", "8s", "9s", "10s"]
+TUNING_STEPS_LIST = ["2.5", "5.0", "6.25", "10.0", "12.5",
+                     "25.0", "50.0", "100.0"]
+BACKLIGHT_TIMEOUT_LIST = ["1s", "2s", "3s", "4s", "5s",
+                          "6s", "7s", "8s", "9s", "10s"]
 
 SPECIALS = {
     "VFO1": -2,
@@ -270,7 +278,8 @@ class TYTUVF8DRadio(chirp_common.CloneModeRadio):
         rf.has_cross = False
         rf.has_rx_dtcs = True
         rf.has_settings = True
-        rf.can_odd_split = False  # it may actually be supported, but I haven't tested
+        # it may actually be supported, but I haven't tested
+        rf.can_odd_split = False
         rf.valid_duplexes = THUVF8D_DUPLEX
         rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
         rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "-"
@@ -362,15 +371,17 @@ class TYTUVF8DRadio(chirp_common.CloneModeRadio):
             return mem
 
         if isinstance(number, int):
-            enabled = self._memobj.enable[(number - 1) / 8].flags[7 - ((number - 1) % 8)]
-            dont_skip = self._memobj.skip[(number - 1) / 8].flags[7 - ((number - 1) % 8)]
+            e = self._memobj.enable[(number - 1) / 8]
+            enabled = e.flags[7 - ((number - 1) % 8)]
+            s = self._memobj.skip[(number - 1) / 8]
+            dont_skip = s.flags[7 - ((number - 1) % 8)]
         else:
             enabled = True
             dont_skip = True
 
         if not enabled:
-          mem.empty = True
-          return mem
+            mem.empty = True
+            return mem
 
         mem.freq = int(_mem.rx_freq) * 10
 
@@ -381,12 +392,15 @@ class TYTUVF8DRadio(chirp_common.CloneModeRadio):
         rxmode, rxval, rxpol = self._decode_tone(_mem.rx_tone)
 
         chirp_common.split_tone_decode(mem,
-                                      (txmode, txval, txpol),
-                                      (rxmode, rxval, rxpol))
+                                       (txmode, txval, txpol),
+                                       (rxmode, rxval, rxpol))
 
         mem.name = str(_mem.name).rstrip('\xFF ')
 
-        mem.skip = dont_skip and "" or "S"
+        if dont_skip:
+            mem.skip = ''
+        else:
+            mem.skip = 'S'
 
         mem.mode = _mem.wideband and "FM" or "NFM"
         mem.power = POWER_LEVELS[1 - _mem.ishighpower]
@@ -422,16 +436,18 @@ class TYTUVF8DRadio(chirp_common.CloneModeRadio):
     def set_memory(self, mem):
         _mem, _name = self._get_memobjs(mem.number)
 
+        e = self._memobj.enable[(mem.number - 1) / 8]
+        s = self._memobj.skip[(mem.number - 1) / 8]
         if mem.empty:
             _mem.set_raw("\xFF" * 32)
-            self._memobj.enable[(mem.number - 1) / 8].flags[7 - ((mem.number - 1) % 8)] = False
-            self._memobj.skip[(mem.number - 1) / 8].flags[7 - ((mem.number - 1) % 8)] = False
+            e.flags[7 - ((mem.number - 1) % 8)] = False
+            s.flags[7 - ((mem.number - 1) % 8)] = False
             return
         else:
-            self._memobj.enable[(mem.number - 1) / 8].flags[7 - ((mem.number - 1) % 8)] = True
+            e.flags[7 - ((mem.number - 1) % 8)] = True
 
         if _mem.get_raw() == ("\xFF" * 32):
-            print "Initializing empty memory"
+            LOG.debug("Initializing empty memory")
             _mem.set_raw("\x00" * 32)
 
         _mem.rx_freq = mem.freq / 10
@@ -454,7 +470,7 @@ class TYTUVF8DRadio(chirp_common.CloneModeRadio):
         _mem.name = mem.name.rstrip(' ').ljust(7, "\xFF")
 
         flag_index = 7 - ((mem.number - 1) % 8)
-        self._memobj.skip[(mem.number - 1) / 8].flags[flag_index] = (mem.skip == "")
+        s.flags[flag_index] = (mem.skip == "")
         _mem.wideband = mem.mode == "FM"
         _mem.ishighpower = mem.power == POWER_LEVELS[0]
 
@@ -464,142 +480,142 @@ class TYTUVF8DRadio(chirp_common.CloneModeRadio):
     def get_settings(self):
         _settings = self._memobj.settings
 
-        group = RadioSettingGroup("top", "All Settings")
-
-        group.append(
-            RadioSetting("mode", "Mode",
-                         RadioSettingValueList(MODE_LIST,
-                                               MODE_LIST[_settings.mode])))
-
-        group.append(
-            RadioSetting("ab_switch", "A/B",
-                         RadioSettingValueList(AB_LIST,
-                                               AB_LIST[_settings.ab_switch])))
-
-        group.append(
-            RadioSetting("a_channel", "A Selected Memory",
-                         RadioSettingValueInteger(1, 128, _settings.a_channel + 1)))
-
-        group.append(
-            RadioSetting("b_channel", "B Selected Memory",
-                         RadioSettingValueInteger(1, 128, _settings.b_channel + 1)))
-
-        group.append(
-            RadioSetting("a_display", "A Channel Display",
-                         RadioSettingValueList(DISPLAY_LIST,
-                                               DISPLAY_LIST[_settings.a_display])))
-        group.append(
-            RadioSetting("b_display", "B Channel Display",
-                         RadioSettingValueList(DISPLAY_LIST,
-                                               DISPLAY_LIST[_settings.b_display])))
-        group.append(
-            RadioSetting("tx_sel", "Priority Transmit",
-                         RadioSettingValueList(TXSEL_LIST,
-                                               TXSEL_LIST[_settings.tx_sel])))
-        group.append(
-            RadioSetting("vox_level", "VOX Level",
-                         RadioSettingValueList(VOX_LIST,
-                                               VOX_LIST[_settings.vox_level])))
-
-        group.append(
-            RadioSetting("squelch", "Squelch Level",
-                         RadioSettingValueInteger(0, 9, _settings.squelch)))
-
-        group.append(
-            RadioSetting("dwait", "Dual Wait",
-                         RadioSettingValueBoolean(_settings.dwait)))
-
-        group.append(
-            RadioSetting("led", "LED Mode",
-                         RadioSettingValueList(LED_LIST,
-                                               LED_LIST[_settings.led])))
-
-        group.append(
-            RadioSetting("light", "Light Color",
-                         RadioSettingValueList(LIGHT_LIST,
-                                               LIGHT_LIST[_settings.light])))
-
-        group.append(
-            RadioSetting("beep", "Beep",
-                         RadioSettingValueBoolean(_settings.beep)))
-
-        group.append(
-            RadioSetting("ani", "ANI",
-                         RadioSettingValueBoolean(_settings.ani)))
-
-        group.append(
-            RadioSetting("tot", "Timeout Timer",
-                         RadioSettingValueList(TOT_LIST,
-                                               TOT_LIST[_settings.tot])))
-
-        group.append(
-            RadioSetting("roger", "Roger Beep",
-                         RadioSettingValueBoolean(_settings.roger)))
-
-        group.append(
-            RadioSetting("dw", "Dual Watch",
-                         RadioSettingValueBoolean(_settings.dw)))
-
-        group.append(
-            RadioSetting("rxsave", "RX Save",
-                         RadioSettingValueBoolean(_settings.rxsave)))
+        group = RadioSettingGroup("basic", "Basic")
+        top = RadioSettings(basic)
+
+        group.append(RadioSetting(
+                "mode", "Mode",
+                RadioSettingValueList(
+                    MODE_LIST, MODE_LIST[_settings.mode])))
+
+        group.append(RadioSetting(
+                "ab_switch", "A/B",
+                RadioSettingValueList(
+                    AB_LIST, AB_LIST[_settings.ab_switch])))
+
+        group.append(RadioSetting(
+                "a_channel", "A Selected Memory",
+                RadioSettingValueInteger(1, 128, _settings.a_channel + 1)))
+
+        group.append(RadioSetting(
+                "b_channel", "B Selected Memory",
+                RadioSettingValueInteger(1, 128, _settings.b_channel + 1)))
+
+        group.append(RadioSetting(
+                "a_display", "A Channel Display",
+                RadioSettingValueList(
+                    DISPLAY_LIST, DISPLAY_LIST[_settings.a_display])))
+        group.append(RadioSetting(
+                "b_display", "B Channel Display",
+                RadioSettingValueList(
+                    DISPLAY_LIST, DISPLAY_LIST[_settings.b_display])))
+        group.append(RadioSetting(
+                "tx_sel", "Priority Transmit",
+                RadioSettingValueList(
+                    TXSEL_LIST, TXSEL_LIST[_settings.tx_sel])))
+        group.append(RadioSetting(
+                "vox_level", "VOX Level",
+                RadioSettingValueList(
+                    VOX_LIST, VOX_LIST[_settings.vox_level])))
+
+        group.append(RadioSetting(
+                "squelch", "Squelch Level",
+                RadioSettingValueInteger(0, 9, _settings.squelch)))
+
+        group.append(RadioSetting(
+                "dwait", "Dual Wait",
+                RadioSettingValueBoolean(_settings.dwait)))
+
+        group.append(RadioSetting(
+                "led", "LED Mode",
+                RadioSettingValueList(LED_LIST, LED_LIST[_settings.led])))
+
+        group.append(RadioSetting(
+                "light", "Light Color",
+                RadioSettingValueList(
+                    LIGHT_LIST, LIGHT_LIST[_settings.light])))
+
+        group.append(RadioSetting(
+                "beep", "Beep",
+                RadioSettingValueBoolean(_settings.beep)))
+
+        group.append(RadioSetting(
+                "ani", "ANI",
+                RadioSettingValueBoolean(_settings.ani)))
+
+        group.append(RadioSetting(
+                "tot", "Timeout Timer",
+                RadioSettingValueList(TOT_LIST, TOT_LIST[_settings.tot])))
+
+        group.append(RadioSetting(
+                "roger", "Roger Beep",
+                RadioSettingValueBoolean(_settings.roger)))
+
+        group.append(RadioSetting(
+                "dw", "Dual Watch",
+                RadioSettingValueBoolean(_settings.dw)))
+
+        group.append(RadioSetting(
+                "rxsave", "RX Save",
+                RadioSettingValueBoolean(_settings.rxsave)))
 
         def _filter(name):
             return str(name).rstrip("\xFF").rstrip()
 
-        group.append(
-            RadioSetting("ponmsg", "Power-On Message",
-                         RadioSettingValueString(0, 7,
-                                                 _filter(_settings.ponmsg))))
-
-        group.append(
-            RadioSetting("scan_mode", "Scan Mode",
-                         RadioSettingValueList(SCAN_MODE_LIST,
-                                               SCAN_MODE_LIST[_settings.scan_mode])))
-
-        group.append(
-            RadioSetting("autolk", "Auto Lock",
-                         RadioSettingValueBoolean(_settings.autolk)))
-
-        group.append(
-            RadioSetting("lock_mode", "Keypad Lock Mode",
-                         RadioSettingValueList(LOCK_MODE_LIST,
-                                               LOCK_MODE_LIST[_settings.lock_mode])))
-
-        group.append(
-            RadioSetting("voice", "Voice Prompt",
-                         RadioSettingValueBoolean(_settings.voice)))
-
-        group.append(
-            RadioSetting("opnmsg", "Opening Message",
-                         RadioSettingValueList(OPNMSG_LIST,
-                                               OPNMSG_LIST[_settings.opnmsg])))
-
-        group.append(
-            RadioSetting("tuning_step", "Tuning Step",
-                         RadioSettingValueList(TUNING_STEPS_LIST,
-                                               TUNING_STEPS_LIST[_settings.tuning_step])))
-
-        group.append(
-            RadioSetting("lamp_t", "Backlight Timeout",
-                         RadioSettingValueList(BACKLIGHT_TIMEOUT_LIST,
-                                               BACKLIGHT_TIMEOUT_LIST[_settings.lamp_t])))
-
-        group.append(
-            RadioSetting("a_work_area", "A Work Area",
-                         RadioSettingValueList(AB_LIST,
-                                               AB_LIST[_settings.a_work_area])))
-
-        group.append(
-            RadioSetting("b_work_area", "B Work Area",
-                         RadioSettingValueList(AB_LIST,
-                                               AB_LIST[_settings.b_work_area])))
-
-        return group
-
-        group.append(
-            RadioSetting("disnm", "Display Name",
-                         RadioSettingValueBoolean(_settings.disnm)))
+        group.append(RadioSetting(
+                "ponmsg", "Power-On Message",
+                RadioSettingValueString(0, 7, _filter(_settings.ponmsg))))
+
+        group.append(RadioSetting(
+                "scan_mode", "Scan Mode",
+                RadioSettingValueList(
+                    SCAN_MODE_LIST, SCAN_MODE_LIST[_settings.scan_mode])))
+
+        group.append(RadioSetting(
+                "autolk", "Auto Lock",
+                RadioSettingValueBoolean(_settings.autolk)))
+
+        group.append(RadioSetting(
+                "lock_mode", "Keypad Lock Mode",
+                RadioSettingValueList(
+                    LOCK_MODE_LIST, LOCK_MODE_LIST[_settings.lock_mode])))
+
+        group.append(RadioSetting(
+                "voice", "Voice Prompt",
+                RadioSettingValueBoolean(_settings.voice)))
+
+        group.append(RadioSetting(
+                "opnmsg", "Opening Message",
+                RadioSettingValueList(
+                    OPNMSG_LIST, OPNMSG_LIST[_settings.opnmsg])))
+
+        group.append(RadioSetting(
+                "tuning_step", "Tuning Step",
+                RadioSettingValueList(
+                    TUNING_STEPS_LIST,
+                    TUNING_STEPS_LIST[_settings.tuning_step])))
+
+        group.append(RadioSetting(
+                "lamp_t", "Backlight Timeout",
+                RadioSettingValueList(
+                    BACKLIGHT_TIMEOUT_LIST,
+                    BACKLIGHT_TIMEOUT_LIST[_settings.lamp_t])))
+
+        group.append(RadioSetting(
+                "a_work_area", "A Work Area",
+                RadioSettingValueList(
+                    AB_LIST, AB_LIST[_settings.a_work_area])))
+
+        group.append(RadioSetting(
+                "b_work_area", "B Work Area",
+                RadioSettingValueList(
+                    AB_LIST, AB_LIST[_settings.b_work_area])))
+
+        return top
+
+        group.append(RadioSetting(
+                "disnm", "Display Name",
+                RadioSettingValueBoolean(_settings.disnm)))
 
         return group
 
@@ -614,9 +630,9 @@ class TYTUVF8DRadio(chirp_common.CloneModeRadio):
                     _settings.rxsave = 0
                 continue
             if element.get_name().endswith('_channel'):
-              print element.value, type(element.value)
-              setattr(_settings, element.get_name(), int(element.value) - 1)
-              continue
+                LOG.debug(element.value, type(element.value))
+                setattr(_settings, element.get_name(), int(element.value) - 1)
+                continue
             if not isinstance(element, RadioSetting):
                 self.set_settings(element)
                 continue
diff --git a/chirp/thd72.py b/chirp/drivers/thd72.py
similarity index 90%
rename from chirp/thd72.py
rename to chirp/drivers/thd72.py
index 0d80adc..a1167c6 100644
--- a/chirp/thd72.py
+++ b/chirp/drivers/thd72.py
@@ -15,9 +15,12 @@
 
 from chirp import chirp_common, errors, util, directory
 from chirp import bitwise, memmap
-import time, struct, sys
+import time
+import struct
+import sys
+import logging
 
-DEBUG = True
+LOG = logging.getLogger(__name__)
 
 # TH-D72 memory map
 # 0x0000..0x0200: startup password and other stuff
@@ -41,7 +44,7 @@ DEBUG = True
 # 0xe500..0xe7d0: startup bitmap
 # 0xe7d0..0xe800: startup bitmap filename
 # 0xe800..0xead0: gps-logger bitmap
-# 0xe8d0..0xeb00: gps-logger bipmap filename 
+# 0xe8d0..0xeb00: gps-logger bipmap filename
 # 0xeb00..0xff00: ?
 # 0xff00..0xffff: stuff?
 
@@ -111,47 +114,47 @@ THD72_SPECIAL["C VHF"] = 1030
 THD72_SPECIAL["C UHF"] = 1031
 
 THD72_SPECIAL_REV = {}
-for k,v in THD72_SPECIAL.items():
+for k, v in THD72_SPECIAL.items():
     THD72_SPECIAL_REV[v] = k
 
 TMODES = {
-    0x08 : "Tone",
-    0x04 : "TSQL",
-    0x02 : "DTCS",
-    0x01 : "Cross",
-    0x00 : "",
+    0x08: "Tone",
+    0x04: "TSQL",
+    0x02: "DTCS",
+    0x01: "Cross",
+    0x00: "",
 }
 TMODES_REV = {
-    ""      : 0x00,
-    "Cross" : 0x01,
-    "DTCS"  : 0x02,
-    "TSQL"  : 0x04,
-    "Tone"  : 0x08,
+    "": 0x00,
+    "Cross": 0x01,
+    "DTCS": 0x02,
+    "TSQL": 0x04,
+    "Tone": 0x08,
 }
 
 MODES = {
-    0x00 : "FM",
-    0x01 : "NFM",
-    0x02 : "AM",
+    0x00: "FM",
+    0x01: "NFM",
+    0x02: "AM",
 }
 
 MODES_REV = {
-    "FM" : 0x00,
+    "FM": 0x00,
     "NFM": 0x01,
-    "AM" : 0x2,
+    "AM": 0x2,
 }
 
 DUPLEX = {
-    0x00 : "",
-    0x01 : "+",
-    0x02 : "-",
-    0x04 : "split",
+    0x00: "",
+    0x01: "+",
+    0x02: "-",
+    0x04: "split",
 }
 DUPLEX_REV = {
-    ""  : 0x00,
-    "+" : 0x01,
-    "-" : 0x02,
-    "split" : 0x04,
+    "": 0x00,
+    "+": 0x01,
+    "-": 0x02,
+    "split": 0x04,
 }
 
 
@@ -170,7 +173,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
 
     mem_upper_limit = 1022
     _memsize = 65536
-    _model = "" # FIXME: REMOVE
+    _model = ""  # FIXME: REMOVE
     _dirty_blocks = []
 
     def get_features(self):
@@ -206,7 +209,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
             self.pipe.read(32)
             try:
                 id = self.get_id()
-                print "Radio %s at %i baud" % (id, baud)
+                LOG.info("Radio %s at %i baud" % (id, baud))
                 return True
             except errors.RadioError:
                 pass
@@ -221,7 +224,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
         if block not in self._dirty_blocks:
             self._dirty_blocks.append(block)
         self._dirty_blocks.sort()
-        print "dirty blocks:", self._dirty_blocks
+        print("dirty blocks: ", self._dirty_blocks)
 
     def get_channel_name(self, number):
         if number < 999:
@@ -252,11 +255,12 @@ class THD72Radio(chirp_common.CloneModeRadio):
             try:
                 number = THD72_SPECIAL[number]
             except KeyError:
-                raise errors.InvalidMemoryLocation("Unknown channel %s" % \
-                                                       number)
+                raise errors.InvalidMemoryLocation("Unknown channel %s" %
+                                                   number)
 
         if number < 0 or number > (max(THD72_SPECIAL.values()) + 1):
-            raise errors.InvalidMemoryLocation("Number must be between 0 and 999")
+            raise errors.InvalidMemoryLocation(
+                    "Number must be between 0 and 999")
 
         _mem = self._memobj.memory[number]
         flag = self._memobj.flag[number]
@@ -287,17 +291,18 @@ class THD72Radio(chirp_common.CloneModeRadio):
             mem.cross_mode = chirp_common.CROSS_MODES[0]
             mem.immutable = ["number", "bank", "extd_number", "cross_mode"]
             if number >= 1020 and number < 1030:
-                mem.immutable += ["freq", "offset", "tone", "mode", "tmode", "ctone", "skip"] # FIXME: ALL
+                mem.immutable += ["freq", "offset", "tone", "mode",
+                                  "tmode", "ctone", "skip"]  # FIXME: ALL
             else:
                 mem.immutable += ["name"]
 
         return mem
 
-
     def set_memory(self, mem):
-        print "set_memory(%d)"%mem.number
+        LOG.debug("set_memory(%d)" % mem.number)
         if mem.number < 0 or mem.number > (max(THD72_SPECIAL.values()) + 1):
-            raise errors.InvalidMemoryLocation("Number must be between 0 and 999")
+            raise errors.InvalidMemoryLocation(
+                "Number must be between 0 and 999")
 
         # weather channels can only change name, nothing else
         if mem.number >= 1020 and mem.number < 1030:
@@ -336,7 +341,6 @@ class THD72Radio(chirp_common.CloneModeRadio):
         if mem.number < 999:
             flag.skip = chirp_common.SKIP_VALUES.index(mem.skip)
 
-
     def sync_in(self):
         self._detect_baud()
         self._mmap = self.download()
@@ -393,7 +397,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
         self.pipe.setRTS()
         self.pipe.read(1)
         data = ""
-        print "reading blocks %d..%d" % (blocks[0], blocks[-1])
+        LOG.debug("reading blocks %d..%d" % (blocks[0], blocks[-1]))
         total = len(blocks)
         count = 0
         for i in allblocks:
@@ -428,7 +432,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
         self.pipe.getCTS()
         self.pipe.setRTS()
         self.pipe.read(1)
-        print "writing blocks %d..%d" % (blocks[0], blocks[-1])
+        LOG.debug("writing blocks %d..%d" % (blocks[0], blocks[-1]))
         total = len(blocks)
         count = 0
         for i in blocks:
@@ -447,18 +451,15 @@ class THD72Radio(chirp_common.CloneModeRadio):
         # clear out blocks we uploaded from the dirty blocks list
         self._dirty_blocks = [b for b in self._dirty_blocks if b not in blocks]
 
-
     def command(self, cmd, timeout=0.5):
         start = time.time()
 
         data = ""
-        if DEBUG:
-            print "PC->D72: %s" % cmd
+        LOG.debug("PC->D72: %s" % cmd)
         self.pipe.write(cmd + "\r")
         while not data.endswith("\r") and (time.time() - start) < timeout:
             data += self.pipe.read(1)
-        if DEBUG:
-            print "D72->PC: %s" % data.strip()
+        LOG.debug("D72->PC: %s" % data.strip())
         return data.strip()
 
     def get_id(self):
@@ -468,7 +469,6 @@ class THD72Radio(chirp_common.CloneModeRadio):
         else:
             raise errors.RadioError("No response to ID command")
 
-
     def initialize(self, mmap):
         mmap[0] = \
             "\x80\xc8\xb3\x08\x00\x01\x00\x08" + \
@@ -480,15 +480,18 @@ if __name__ == "__main__":
     import serial
     import detect
     import getopt
+
     def fixopts(opts):
         r = {}
         for opt in opts:
-            k,v = opt
+            k, v = opt
             r[k] = v
         return r
 
     def usage():
-        print "Usage: %s <-i input.img>|<-o output.img> -p port [[-f first-addr] [-l last-addr] | [-b list,of,blocks]]" % sys.argv[0]
+        print "Usage: %s <-i input.img>|<-o output.img> -p port " \
+            "[[-f first-addr] [-l last-addr] | [-b list,of,blocks]]" % \
+            sys.argv[0]
         sys.exit(1)
 
     opts, args = getopt.getopt(sys.argv[1:], "i:o:p:f:l:b:")
@@ -509,9 +512,9 @@ if __name__ == "__main__":
         usage()
 
     if '-f' in opts:
-        first = int(opts['-f'],0)
+        first = int(opts['-f'], 0)
     if '-l' in opts:
-        last = int(opts['-l'],0)
+        last = int(opts['-l'], 0)
     if '-b' in opts:
         blocks = [int(b, 0) for b in opts['-b'].split(',')]
         blocks.sort()
@@ -542,4 +545,3 @@ if __name__ == "__main__":
         r._mmap = file(fname, "rb").read(r._memsize)
         r.upload(blocks)
     print "\nDone"
-
diff --git a/chirp/thuv1f.py b/chirp/drivers/thuv1f.py
similarity index 95%
rename from chirp/thuv1f.py
rename to chirp/drivers/thuv1f.py
index 5f99784..3771633 100644
--- a/chirp/thuv1f.py
+++ b/chirp/drivers/thuv1f.py
@@ -14,14 +14,17 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import struct
+import logging
 
 from chirp import chirp_common, errors, util, directory, memmap
 from chirp import bitwise
-
 from chirp.settings import RadioSetting, RadioSettingGroup, \
     RadioSettingValueInteger, RadioSettingValueList, \
-    RadioSettingValueList, RadioSettingValueBoolean, \
-    RadioSettingValueString
+    RadioSettingValueBoolean, RadioSettingValueString, \
+    RadioSettings
+
+LOG = logging.getLogger(__name__)
+
 
 def uvf1_identify(radio):
     """Do identify handshake with TYT TH-UVF1"""
@@ -31,13 +34,14 @@ def uvf1_identify(radio):
         raise errors.RadioError("Radio did not respond")
     radio.pipe.write("\x02")
     ident = radio.pipe.read(16)
-    print "Ident:\n%s" % util.hexprint(ident)
+    LOG.info("Ident:\n%s" % util.hexprint(ident))
     radio.pipe.write("\x06")
     ack = radio.pipe.read(1)
     if ack != "\x06":
         raise errors.RadioError("Radio did not ack identification")
     return ident
 
+
 def uvf1_download(radio):
     """Download from TYT TH-UVF1"""
     data = uvf1_identify(radio)
@@ -64,6 +68,7 @@ def uvf1_download(radio):
 
     return memmap.MemoryMap(data)
 
+
 def uvf1_upload(radio):
     """Upload to TYT TH-UVF1"""
     data = uvf1_identify(radio)
@@ -81,7 +86,7 @@ def uvf1_upload(radio):
         radio.pipe.write(msg)
         ack = radio.pipe.read(1)
         if ack != "\x06":
-            print repr(ack)
+            LOG.debug(repr(ack))
             raise errors.RadioError("Radio did not ack block %i" % i)
         status = chirp_common.Status()
         status.cur = i
@@ -92,6 +97,7 @@ def uvf1_upload(radio):
     # End of clone?
     radio.pipe.write("\x45")
 
+
 THUV1F_MEM_FORMAT = """
 struct mem {
   bbcd rx_freq[4];
@@ -171,6 +177,7 @@ PTTID_LIST = ["Off", "BOT", "EOT", "Both"]
 BCL_LIST = ["Off", "CSQ", "QT/DQT"]
 CODES_LIST = [x for x in range(1, 9)]
 
+
 @directory.register
 class TYTTHUVF1Radio(chirp_common.CloneModeRadio):
     """TYT TH-UVF1"""
@@ -308,9 +315,8 @@ class TYTTHUVF1Radio(chirp_common.CloneModeRadio):
         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))
+        chirp_common.split_tone_decode(
+            mem, (txmode, txval, txpol), (rxmode, rxval, rxpol))
 
         mem.name = str(self._memobj.names[number - 1].name)
         mem.name = mem.name.replace("\xFF", " ").rstrip()
@@ -336,8 +342,8 @@ class TYTTHUVF1Radio(chirp_common.CloneModeRadio):
         mem.extra.append(rs)
 
         rs = RadioSetting("scramble_code", "Scramble Code",
-                          RadioSettingValueList(CODES_LIST,
-                                                CODES_LIST[_mem.scramble_code]))
+                          RadioSettingValueList(
+                              CODES_LIST, CODES_LIST[_mem.scramble_code]))
         mem.extra.append(rs)
 
         return mem
@@ -349,7 +355,7 @@ class TYTTHUVF1Radio(chirp_common.CloneModeRadio):
             return
 
         if _mem.get_raw() == ("\xFF" * 16):
-            print "Initializing empty memory"
+            LOG.debug("Initializing empty memory")
             _mem.set_raw("\x00" * 16)
 
         _mem.rx_freq = mem.freq / 10
@@ -383,7 +389,8 @@ class TYTTHUVF1Radio(chirp_common.CloneModeRadio):
     def get_settings(self):
         _settings = self._memobj.settings
 
-        group = RadioSettingGroup("top", "All Settings")
+        group = RadioSettingGroup("basic", "Basic")
+        top = RadioSettings(group)
 
         group.append(
             RadioSetting("led", "LED Mode",
@@ -396,7 +403,7 @@ class TYTTHUVF1Radio(chirp_common.CloneModeRadio):
 
         group.append(
             RadioSetting("squelch", "Squelch Level",
-                          RadioSettingValueInteger(0, 9, _settings.squelch)))
+                         RadioSettingValueInteger(0, 9, _settings.squelch)))
 
         group.append(
             RadioSetting("vox_level", "VOX Level",
@@ -455,7 +462,7 @@ class TYTTHUVF1Radio(chirp_common.CloneModeRadio):
                          RadioSettingValueBoolean(_settings.disnm)))
 
         def _filter(name):
-            print repr(str(name))
+            LOG.debug(repr(str(name)))
             return str(name).rstrip("\xFF").rstrip()
 
         group.append(
@@ -463,7 +470,7 @@ class TYTTHUVF1Radio(chirp_common.CloneModeRadio):
                          RadioSettingValueString(0, 6,
                                                  _filter(_settings.ponmsg))))
 
-        return group
+        return top
 
     def set_settings(self, settings):
         _settings = self._memobj.settings
diff --git a/chirp/tk8102.py b/chirp/drivers/tk8102.py
similarity index 93%
rename from chirp/tk8102.py
rename to chirp/drivers/tk8102.py
index 85d1dcd..8fe8211 100644
--- a/chirp/tk8102.py
+++ b/chirp/drivers/tk8102.py
@@ -15,12 +15,15 @@
 
 import struct
 import os
+import logging
 
 from chirp import chirp_common, directory, memmap, errors, util
 from chirp import bitwise
 from chirp.settings import RadioSettingGroup, RadioSetting
 from chirp.settings import RadioSettingValueBoolean, RadioSettingValueList
-from chirp.settings import RadioSettingValueString
+from chirp.settings import RadioSettingValueString, RadioSettings
+
+LOG = logging.getLogger(__name__)
 
 MEM_FORMAT = """
 #seekto 0x0030;
@@ -55,19 +58,22 @@ MODES = ["NFM", "FM"]
 PTTID = ["", "BOT", "EOT", "Both"]
 SIGNAL = ["", "DTMF"]
 
+
 def make_frame(cmd, addr, length, data=""):
     return struct.pack(">BHB", ord(cmd), addr, length) + data
 
+
 def send(radio, frame):
-    #print "%04i P>R: %s" % (len(frame), util.hexprint(frame))
+    # LOG.debug("%04i P>R: %s" % (len(frame), util.hexprint(frame)))
     radio.pipe.write(frame)
 
+
 def recv(radio, readdata=True):
     hdr = radio.pipe.read(4)
     cmd, addr, length = struct.unpack(">BHB", hdr)
     if readdata:
         data = radio.pipe.read(length)
-        #print "     P<R: %s" % util.hexprint(hdr + data)
+        # LOG.debug("     P<R: %s" % util.hexprint(hdr + data))
         if len(data) != length:
             raise errors.RadioError("Radio sent %i bytes (expected %i)" % (
                     len(data), length))
@@ -76,6 +82,7 @@ def recv(radio, readdata=True):
     radio.pipe.write("\x06")
     return addr, data
 
+
 def do_ident(radio):
     send(radio, "PROGRAM")
     ack = radio.pipe.read(1)
@@ -86,10 +93,11 @@ def do_ident(radio):
     if ident[1:5] != radio.MODEL.split("-")[1]:
         raise errors.RadioError("Incorrect model: TK-%s, expected %s" % (
                 ident[1:5], radio.MODEL))
-    print "Model: %s" % util.hexprint(ident)
+    LOG.info("Model: %s" % util.hexprint(ident))
     radio.pipe.write("\x06")
     ack = radio.pipe.read(1)
 
+
 def do_download(radio):
     radio.pipe.setParity("E")
     radio.pipe.setTimeout(1)
@@ -119,6 +127,7 @@ def do_download(radio):
             data)
     return memmap.MemoryMap(data)
 
+
 def do_upload(radio):
     radio.pipe.setParity("E")
     radio.pipe.setTimeout(1)
@@ -140,6 +149,7 @@ def do_upload(radio):
 
     radio.pipe.write("\x45")
 
+
 class KenwoodTKx102Radio(chirp_common.CloneModeRadio):
     """Kenwood TK-x102"""
     VENDOR = "Kenwood"
@@ -158,6 +168,14 @@ class KenwoodTKx102Radio(chirp_common.CloneModeRadio):
         rf.has_rx_dtcs = True
         rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
         rf.valid_modes = MODES
+        rf.valid_cross_modes = [
+            "Tone->Tone",
+            "DTCS->",
+            "->DTCS",
+            "Tone->DTCS",
+            "DTCS->Tone",
+            "->Tone",
+            "DTCS->DTCS"]
         rf.valid_power_levels = POWER_LEVELS
         rf.valid_skips = ["", "S"]
         rf.valid_bands = [self._range]
@@ -194,7 +212,7 @@ class KenwoodTKx102Radio(chirp_common.CloneModeRadio):
             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
@@ -309,9 +327,9 @@ class KenwoodTKx102Radio(chirp_common.CloneModeRadio):
         else:
             _mem.rx_tone = 0xFFFF
 
-        if os.getenv("CHIRP_DEBUG"):
-            print "Set TX %s (%i) RX %s (%i)" % (tx_mode, _mem.tx_tone,
-                                                 rx_mode, _mem.rx_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]
 
@@ -330,7 +348,6 @@ class KenwoodTKx102Radio(chirp_common.CloneModeRadio):
             _mem.tx_freq = mem.freq / 10
 
         self._set_tone(mem, _mem)
-        
 
         _mem.highpower = mem.power == POWER_LEVELS[1]
         _mem.wide = mem.mode == "FM"
@@ -347,7 +364,8 @@ class KenwoodTKx102Radio(chirp_common.CloneModeRadio):
 
     def get_settings(self):
         _mem = self._memobj
-        top = RadioSettingGroup("all", "All Settings")
+        basic = RadioSettingGroup("basic", "Basic")
+        top = RadioSettings(basic)
 
         def _f(val):
             string = ""
@@ -361,18 +379,21 @@ class KenwoodTKx102Radio(chirp_common.CloneModeRadio):
                              RadioSettingValueString(0, 32,
                                                      _f(_mem.messages.line1),
                                                      autopad=False))
-        top.append(line1)
+        basic.append(line1)
 
         line2 = RadioSetting("messages.line2", "Message Line 2",
                              RadioSettingValueString(0, 32,
                                                      _f(_mem.messages.line2),
                                                      autopad=False))
-        top.append(line2)
+        basic.append(line2)
 
         return top
 
     def set_settings(self, settings):
         for element in settings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
             if "." in element.get_name():
                 bits = element.get_name().split(".")
                 obj = self._memobj
@@ -388,11 +409,11 @@ class KenwoodTKx102Radio(chirp_common.CloneModeRadio):
             else:
                 value = element.value
             setattr(obj, setting, value)
-            
+
     @classmethod
     def match_model(cls, filedata, filename):
         model = filedata[0x03D1:0x03D5]
-        print model
+        LOG.debug(model)
         return model == cls.MODEL.split("-")[1]
 
 
@@ -402,21 +423,23 @@ class KenwoodTK7102Radio(KenwoodTKx102Radio):
     _range = (136000000, 174000000)
     _upper = 4
 
+
 @directory.register
 class KenwoodTK8102Radio(KenwoodTKx102Radio):
     MODEL = "TK-8102"
     _range = (400000000, 500000000)
     _upper = 4
 
+
 @directory.register
 class KenwoodTK7108Radio(KenwoodTKx102Radio):
     MODEL = "TK-7108"
     _range = (136000000, 174000000)
     _upper = 8
 
+
 @directory.register
 class KenwoodTK8108Radio(KenwoodTKx102Radio):
     MODEL = "TK-8108"
     _range = (400000000, 500000000)
     _upper = 8
-
diff --git a/chirp/tmv71.py b/chirp/drivers/tmv71.py
similarity index 90%
rename from chirp/tmv71.py
rename to chirp/drivers/tmv71.py
index 59ef226..7c85c38 100644
--- a/chirp/tmv71.py
+++ b/chirp/drivers/tmv71.py
@@ -14,7 +14,11 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from chirp import chirp_common, errors, util
-from chirp import tmv71_ll
+from chirp.drivers import tmv71_ll
+import logging
+
+LOG = logging.getLogger(__name__)
+
 
 class TMV71ARadio(chirp_common.CloneModeRadio):
     BAUD_RATE = 9600
@@ -23,7 +27,7 @@ class TMV71ARadio(chirp_common.CloneModeRadio):
 
     mem_upper_limit = 1022
     _memsize = 32512
-    _model = "" # FIXME: REMOVE
+    _model = ""  # FIXME: REMOVE
 
     def get_features(self):
         rf = chirp_common.RadioFeatures()
@@ -37,7 +41,7 @@ class TMV71ARadio(chirp_common.CloneModeRadio):
             self.pipe.read(32)
             try:
                 id = tmv71_ll.get_id(self.pipe)
-                print "Radio %s at %i baud" % (id, baud)
+                LOG.info("Radio %s at %i baud" % (id, baud))
                 return True
             except errors.RadioError:
                 pass
@@ -55,8 +59,8 @@ class TMV71ARadio(chirp_common.CloneModeRadio):
             try:
                 number = tmv71_ll.V71_SPECIAL[number]
             except KeyError:
-                raise errors.InvalidMemoryLocation("Unknown channel %s" % \
-                                                       number)
+                raise errors.InvalidMemoryLocation("Unknown channel %s" %
+                                                   number)
 
         return tmv71_ll.get_memory(self._mmap, number)
 
diff --git a/chirp/tmv71_ll.py b/chirp/drivers/tmv71_ll.py
similarity index 89%
rename from chirp/tmv71_ll.py
rename to chirp/drivers/tmv71_ll.py
index dac7f36..50a100b 100644
--- a/chirp/tmv71_ll.py
+++ b/chirp/drivers/tmv71_ll.py
@@ -13,18 +13,20 @@
 # 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 struct, time
+import struct
+import time
+import logging
 
 from chirp import memmap, chirp_common, errors
 
-DEBUG = True
+LOG = logging.getLogger(__name__)
 
-POS_MODE   = 5
-POS_DUP    = 6
-POS_TMODE  = 6
-POS_RTONE  = 7
-POS_CTONE  = 8
-POS_DTCS   = 9
+POS_MODE = 5
+POS_DUP = 6
+POS_TMODE = 6
+POS_RTONE = 7
+POS_CTONE = 8
+POS_DTCS = 9
 POS_OFFSET = 10
 
 MEM_LOC_BASE = 0x1700
@@ -43,22 +45,22 @@ V71_SPECIAL["C VHF"] = 1030
 V71_SPECIAL["C UHF"] = 1031
 
 V71_SPECIAL_REV = {}
-for k,v in V71_SPECIAL.items():
+for k, v in V71_SPECIAL.items():
     V71_SPECIAL_REV[v] = k
 
+
 def command(s, cmd, timeout=0.5):
     start = time.time()
 
     data = ""
-    if DEBUG:
-        print "PC->V71: %s" % cmd
+    LOG.debug("PC->V71: %s" % cmd)
     s.write(cmd + "\r")
     while not data.endswith("\r") and (time.time() - start) < timeout:
         data += s.read(1)
-    if DEBUG:
-        print "V71->PC: %s" % data.strip()
+    LOG.debug("V71->PC: %s" % data.strip())
     return data.strip()
 
+
 def get_id(s):
     r = command(s, "ID")
     if r.startswith("ID "):
@@ -69,6 +71,7 @@ def get_id(s):
 EXCH_R = "R\x00\x00\x00"
 EXCH_W = "W\x00\x00\x00"
 
+
 def read_block(s, block, count=256):
     s.write(struct.pack("<cHB", "R", block, 0))
     r = s.read(4)
@@ -89,6 +92,7 @@ def read_block(s, block, count=256):
 
     return data
 
+
 def write_block(s, block, map):
     s.write(struct.pack("<cHB", "W", block, 0))
     base = block * 256
@@ -98,6 +102,7 @@ def write_block(s, block, map):
 
     return ack == chr(0x06)
 
+
 def download(radio):
     if command(radio.pipe, "0M PROGRAM") != "0M":
         raise errors.RadioError("No response from radio")
@@ -116,6 +121,7 @@ def download(radio):
 
     return memmap.MemoryMap(data)
 
+
 def upload(radio):
     if command(radio.pipe, "0M PROGRAM") != "0M":
         raise errors.RadioError("No response from radio")
@@ -133,20 +139,24 @@ def upload(radio):
 
     radio.pipe.write("E")
 
+
 def get_mem_offset(number):
     return MEM_LOC_BASE + (MEM_LOC_SIZE * number)
 
+
 def get_raw_mem(map, number):
     base = get_mem_offset(number)
-    #print "Offset for %i is %04x" % (number, base)
+    # LOG.debug("Offset for %i is %04x" % (number, base))
     return map[base:base+MEM_LOC_SIZE]
 
+
 def get_used(map, number):
     pos = MEM_FLG_BASE + (number * 2)
     flag = ord(map[pos])
-    print "Flag byte is %02x" % flag
+    LOG.debug("Flag byte is %02x" % flag)
     return not (flag & 0x80)
 
+
 def set_used(map, number, freq):
     pos = MEM_FLG_BASE + (number * 2)
     if freq == 0:
@@ -157,6 +167,7 @@ def set_used(map, number, freq):
     elif int(freq / 100) == 4:
         map[pos] = "\x08\x00"
 
+
 def get_skip(map, number):
     pos = MEM_FLG_BASE + (number * 2)
     flag = ord(map[pos+1])
@@ -165,6 +176,7 @@ def get_skip(map, number):
     else:
         return ""
 
+
 def set_skip(map, number, skip):
     pos = MEM_FLG_BASE + (number * 2)
     flag = ord(map[pos+1])
@@ -174,111 +186,128 @@ def set_skip(map, number, skip):
         flag &= ~0x01
     map[pos+1] = flag
 
+
 def get_freq(mmap):
     freq, = struct.unpack("<I", mmap[0:4])
     return freq / 1000000.0
 
+
 def set_freq(mmap, freq):
     mmap[0] = struct.pack("<I", int(freq * 1000000))
 
+
 def get_name(map, number):
     base = MEM_TAG_BASE + (8 * number)
     return map[base:base+6].replace("\xff", "")
 
+
 def set_name(mmap, number, name):
     base = MEM_TAG_BASE + (8 * number)
     mmap[base] = name.ljust(6)[:6].upper()
 
+
 def get_tmode(mmap):
     val = ord(mmap[POS_TMODE]) & 0x70
 
     tmodemap = {
-        0x00 : "",
-        0x40 : "Tone",
-        0x20 : "TSQL",
-        0x10 : "DTCS",
+        0x00: "",
+        0x40: "Tone",
+        0x20: "TSQL",
+        0x10: "DTCS",
         }
 
     return tmodemap[val]
 
+
 def set_tmode(mmap, tmode):
     val = ord(mmap[POS_TMODE]) & 0x8F
 
     tmodemap = {
-        ""     : 0x00,
-        "Tone" : 0x40,
-        "TSQL" : 0x20,
-        "DTCS" : 0x10,
+        "":     0x00,
+        "Tone": 0x40,
+        "TSQL": 0x20,
+        "DTCS": 0x10,
         }
 
     mmap[POS_TMODE] = val | tmodemap[tmode]
 
+
 def get_tone(mmap, offset):
     val = ord(mmap[offset])
 
     return chirp_common.TONES[val]
 
+
 def set_tone(mmap, tone, offset):
-    print tone
+    LOG.debug(tone)
     mmap[offset] = chirp_common.TONES.index(tone)
 
+
 def get_dtcs(mmap):
     val = ord(mmap[POS_DTCS])
 
     return chirp_common.DTCS_CODES[val]
 
+
 def set_dtcs(mmap, dtcs):
     mmap[POS_DTCS] = chirp_common.DTCS_CODES.index(dtcs)
 
+
 def get_duplex(mmap):
     val = ord(mmap[POS_DUP]) & 0x03
 
     dupmap = {
-        0x00 : "",
-        0x01 : "+",
-        0x02 : "-",
+        0x00: "",
+        0x01: "+",
+        0x02: "-",
         }
 
     return dupmap[val]
 
+
 def set_duplex(mmap, duplex):
     val = ord(mmap[POS_DUP]) & 0xFC
 
     dupmap = {
-        ""  : 0x00,
-        "+" : 0x01,
-        "-" : 0x02,
+        "":  0x00,
+        "+": 0x01,
+        "-": 0x02,
         }
 
     mmap[POS_DUP] = val | dupmap[duplex]
 
+
 def get_offset(mmap):
     val, = struct.unpack("<I", mmap[POS_OFFSET:POS_OFFSET+4])
     return val / 1000000.0
 
+
 def set_offset(mmap, offset):
     mmap[POS_OFFSET] = struct.pack("<I", int(offset * 1000000))
 
+
 def get_mode(mmap):
     val = ord(mmap[POS_MODE]) & 0x03
     modemap = {
-        0x00 : "FM",
-        0x01 : "NFM",
-        0x02 : "AM",
+        0x00: "FM",
+        0x01: "NFM",
+        0x02: "AM",
         }
 
     return modemap[val]
 
+
 def set_mode(mmap, mode):
     val = ord(mmap[POS_MODE]) & 0xFC
     modemap = {
-        "FM" : 0x00,
+        "FM":  0x00,
         "NFM": 0x01,
-        "AM" : 0x02,
+        "AM":  0x02,
         }
 
     mmap[POS_MODE] = val | modemap[mode]
 
+
 def get_memory(map, number):
     if number < 0 or number > (max(V71_SPECIAL.values()) + 1):
         raise errors.InvalidMemoryLocation("Number must be between 0 and 999")
@@ -310,15 +339,17 @@ def get_memory(map, number):
     if number > 999:
         mem.immutable = ["number", "bank", "extd_number", "name"]
     if number > 1020 and number < 1030:
-        mem.immutable += ["freq"] # FIXME: ALL
+        mem.immutable += ["freq"]  # FIXME: ALL
 
     return mem
 
+
 def initialize(mmap):
     mmap[0] = \
         "\x80\xc8\xb3\x08\x00\x01\x00\x08" + \
         "\x08\x00\xc0\x27\x09\x00\x00\xff"
 
+
 def set_memory(map, mem):
     if mem.number < 0 or mem.number > (max(V71_SPECIAL.values()) + 1):
         raise errors.InvalidMemoryLocation("Number must be between 0 and 999")
@@ -348,13 +379,13 @@ def set_memory(map, mem):
 
     return map
 
+
 if __name__ == "__main__":
     import sys
     import serial
     s = serial.Serial(port=sys.argv[1], baudrate=9600, dsrdtr=True,
                       timeout=0.25)
-    #s.write("\r\r")
-    #print get_id(s)
+    # s.write("\r\r")
+    # print get_id(s)
     data = download(s)
     file(sys.argv[2], "wb").write(data)
-
diff --git a/chirp/drivers/ts2000.py b/chirp/drivers/ts2000.py
new file mode 100644
index 0000000..a47ef88
--- /dev/null
+++ b/chirp/drivers/ts2000.py
@@ -0,0 +1,296 @@
+# Copyright 2012 Tom Hayward <tom at tomh.us>
+#
+# 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 re
+from chirp import chirp_common, directory, util, errors
+from chirp.drivers import kenwood_live
+from chirp.drivers.kenwood_live import KenwoodLiveRadio, \
+    command, iserr, NOCACHE
+
+TS2000_SSB_STEPS = [1.0, 2.5, 5.0, 10.0]
+TS2000_FM_STEPS = [5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0]
+TS2000_DUPLEX = dict(kenwood_live.DUPLEX)
+TS2000_DUPLEX[3] = "="
+TS2000_DUPLEX[4] = "split"
+TS2000_MODES = ["?", "LSB", "USB", "CW", "FM", "AM",
+                "FSK", "CR-R", "?", "FSK-R"]
+TS2000_TMODES = ["", "Tone", "TSQL", "DTCS"]
+TS2000_TONES = list(chirp_common.OLD_TONES)
+TS2000_TONES.remove(69.3)
+
+
+ at directory.register
+class TS2000Radio(KenwoodLiveRadio):
+    """Kenwood TS-2000"""
+    MODEL = "TS-2000"
+
+    _upper = 289
+    _kenwood_split = True
+    _kenwood_valid_tones = list(TS2000_TONES)
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.has_dtcs_polarity = False
+        rf.has_bank = False
+        rf.can_odd_split = True
+        rf.valid_modes = ["LSB", "USB", "CW", "FM", "AM"]
+        rf.valid_tmodes = list(TS2000_TMODES)
+        rf.valid_tuning_steps = list(TS2000_SSB_STEPS + TS2000_FM_STEPS)
+        rf.valid_bands = [(1000, 1300000000)]
+        rf.valid_skips = ["", "S"]
+        rf.valid_duplexes = TS2000_DUPLEX.values()
+
+        # TS-2000 uses ";" as a message separator even though it seems to
+        # allow you to to use all printable ASCII characters at the manual
+        # controls.  The radio doesn't send the name after the ";" if you
+        # input one from the manual controls.
+        rf.valid_characters = chirp_common.CHARSET_ASCII.replace(';', '')
+        rf.valid_name_length = 7    # 7 character channel names
+        rf.memory_bounds = (0, self._upper)
+        return rf
+
+    def _cmd_set_memory(self, number, spec):
+        return "MW0%03i%s" % (number, spec)
+
+    def _cmd_set_split(self, number, spec):
+        return "MW1%03i%s" % (number, spec)
+
+    def _cmd_get_memory(self, number):
+        return "MR0%03i" % number
+
+    def _cmd_get_split(self, number):
+        return "MR1%03i" % number
+
+    def _cmd_recall_memory(self, number):
+        return "MC%03i" % (number)
+
+    def _cmd_cur_memory(self, number):
+        return "MC"
+
+    def _cmd_erase_memory(self, number):
+        # write a memory channel that's effectively zeroed except
+        # for the channel number
+        return "MW%04i%035i" % (number, 0)
+
+    def erase_memory(self, number):
+        if number not in self._memcache:
+            return
+
+        resp = command(self.pipe, *self._cmd_erase_memory(number))
+        if iserr(resp):
+            raise errors.RadioError("Radio refused delete of %i" % number)
+        del self._memcache[number]
+
+    def get_memory(self, number):
+        if number < 0 or number > self._upper:
+            raise errors.InvalidMemoryLocation(
+                "Number must be between 0 and %i" % self._upper)
+        if number in self._memcache and not NOCACHE:
+            return self._memcache[number]
+
+        result = command(self.pipe, *self._cmd_get_memory(number))
+        if result == "N":
+            mem = chirp_common.Memory()
+            mem.number = number
+            mem.empty = True
+            self._memcache[mem.number] = mem
+            return mem
+
+        mem = self._parse_mem_spec(result)
+        self._memcache[mem.number] = mem
+
+        # check for split frequency operation
+        if mem.duplex == "" and self._kenwood_split:
+            result = command(self.pipe, *self._cmd_get_split(number))
+            self._parse_split_spec(mem, result)
+
+        return mem
+
+    def _parse_mem_spec(self, spec):
+        mem = chirp_common.Memory()
+
+        # pad string so indexes match Kenwood docs
+        spec = " " + spec
+
+        # use the same variable names as the Kenwood docs
+        #_p1 = spec[3]
+        _p2 = spec[4]
+        _p3 = spec[5:7]
+        _p4 = spec[7:18]
+        _p5 = spec[18]
+        _p6 = spec[19]
+        _p7 = spec[20]
+        _p8 = spec[21:23]
+        _p9 = spec[23:25]
+        _p10 = spec[25:28]
+        #_p11 = spec[28]
+        _p12 = spec[29]
+        _p13 = spec[30:39]
+        _p14 = spec[39:41]
+        #_p15 = spec[41]
+        _p16 = spec[42:49]
+
+        mem.number = int(_p2 + _p3)     # concat bank num and chan num
+        mem.freq = int(_p4)
+        mem.mode = TS2000_MODES[int(_p5)]
+        mem.skip = ["", "S"][int(_p6)]
+        mem.tmode = TS2000_TMODES[int(_p7)]
+        # PL and T-SQL are 1 indexed, DTCS is 0 indexed
+        mem.rtone = self._kenwood_valid_tones[int(_p8) - 1]
+        mem.ctone = self._kenwood_valid_tones[int(_p9) - 1]
+        mem.dtcs = chirp_common.DTCS_CODES[int(_p10)]
+        mem.duplex = TS2000_DUPLEX[int(_p12)]
+        mem.offset = int(_p13)      # 9-digit
+        if mem.mode in ["AM", "FM"]:
+            mem.tuning_step = TS2000_FM_STEPS[int(_p14)]
+        else:
+            mem.tuning_step = TS2000_SSB_STEPS[int(_p14)]
+        mem.name = _p16
+
+        return mem
+
+    def _parse_split_spec(self, mem, spec):
+
+        # pad string so indexes match Kenwood docs
+        spec = " " + spec
+
+        # use the same variable names as the Kenwood docs
+        split_freq = int(spec[7:18])
+        if mem.freq != split_freq:
+            mem.duplex = "split"
+            mem.offset = split_freq
+
+        return mem
+
+    def set_memory(self, memory):
+        if memory.number < 0 or memory.number > self._upper:
+            raise errors.InvalidMemoryLocation(
+                "Number must be between 0 and %i" % self._upper)
+
+        spec = self._make_mem_spec(memory)
+        spec = "".join(spec)
+        r1 = command(self.pipe, *self._cmd_set_memory(memory.number, spec))
+        if not iserr(r1):
+            memory.name = memory.name.rstrip()
+            self._memcache[memory.number] = memory
+
+            # if we're tuned to the channel, reload it
+            r1 = command(self.pipe, *self._cmd_cur_memory(memory.number))
+            if not iserr(r1):
+                pattern = re.compile("MC([0-9]{3})")
+                match = pattern.search(r1)
+                if match is not None:
+                    cur_mem = int(match.group(1))
+                    if cur_mem == memory.number:
+                        cur_mem = \
+                            command(self.pipe,
+                                    *self._cmd_recall_memory(memory.number))
+        else:
+            raise errors.InvalidDataError("Radio refused %i" % memory.number)
+
+        # FIXME
+        if memory.duplex == "split" and self._kenwood_split:
+            spec = "".join(self._make_split_spec(memory))
+            result = command(self.pipe, *self._cmd_set_split(memory.number,
+                                                             spec))
+            if iserr(result):
+                raise errors.InvalidDataError("Radio refused %i" %
+                                              memory.number)
+
+    def _make_mem_spec(self, mem):
+        if mem.duplex in " +-":
+            duplex = util.get_dict_rev(TS2000_DUPLEX, mem.duplex)
+            offset = mem.offset
+        elif mem.duplex == "split":
+            duplex = 0
+            offset = 0
+        else:
+            print "Bug: unsupported duplex `%s'" % mem.duplex
+        if mem.mode in ["AM", "FM"]:
+            step = TS2000_FM_STEPS.index(mem.tuning_step)
+        else:
+            step = TS2000_SSB_STEPS.index(mem.tuning_step)
+
+        # TS-2000 won't accept channels with tone mode off if they have
+        # tone values
+        if mem.tmode == "":
+            rtone = 0
+            ctone = 0
+            dtcs = 0
+        else:
+            # PL and T-SQL are 1 indexed, DTCS is 0 indexed
+            rtone = (self._kenwood_valid_tones.index(mem.rtone) + 1)
+            ctone = (self._kenwood_valid_tones.index(mem.ctone) + 1)
+            dtcs = (chirp_common.DTCS_CODES.index(mem.dtcs))
+
+        spec = (
+            "%011i" % mem.freq,
+            "%i" % (TS2000_MODES.index(mem.mode)),
+            "%i" % (mem.skip == "S"),
+            "%i" % TS2000_TMODES.index(mem.tmode),
+            "%02i" % (rtone),
+            "%02i" % (ctone),
+            "%03i" % (dtcs),
+            "0",    # REVERSE status
+            "%i" % duplex,
+            "%09i" % offset,
+            "%02i" % step,
+            "0",    # Memory Group number (0-9)
+            "%s" % mem.name,
+        )
+
+        return spec
+
+    def _make_split_spec(self, mem):
+        if mem.duplex in " +-":
+            duplex = util.get_dict_rev(TS2000_DUPLEX, mem.duplex)
+        elif mem.duplex == "split":
+            duplex = 0
+        else:
+            print "Bug: unsupported duplex `%s'" % mem.duplex
+        if mem.mode in ["AM", "FM"]:
+            step = TS2000_FM_STEPS.index(mem.tuning_step)
+        else:
+            step = TS2000_SSB_STEPS.index(mem.tuning_step)
+
+        # TS-2000 won't accept channels with tone mode off if they have
+        # tone values
+        if mem.tmode == "":
+            rtone = 0
+            ctone = 0
+            dtcs = 0
+        else:
+            # PL and T-SQL are 1 indexed, DTCS is 0 indexed
+            rtone = (self._kenwood_valid_tones.index(mem.rtone) + 1)
+            ctone = (self._kenwood_valid_tones.index(mem.ctone) + 1)
+            dtcs = (chirp_common.DTCS_CODES.index(mem.dtcs))
+
+        spec = (
+            "%011i" % mem.offset,
+            "%i" % (TS2000_MODES.index(mem.mode)),
+            "%i" % (mem.skip == "S"),
+            "%i" % TS2000_TMODES.index(mem.tmode),
+            "%02i" % (rtone),
+            "%02i" % (ctone),
+            "%03i" % (dtcs),
+            "0",    # REVERSE status
+            "%i" % duplex,
+            "%09i" % 0,
+            "%02i" % step,
+            "0",    # Memory Group number (0-9)
+            "%s" % mem.name,
+        )
+
+        return spec
diff --git a/chirp/uv5r.py b/chirp/drivers/uv5r.py
similarity index 67%
rename from chirp/uv5r.py
rename to chirp/drivers/uv5r.py
index 54b743b..c34fcbc 100644
--- a/chirp/uv5r.py
+++ b/chirp/drivers/uv5r.py
@@ -16,19 +16,17 @@
 import struct
 import time
 import os
+import logging
 
 from chirp import chirp_common, errors, util, directory, memmap
 from chirp import bitwise
 from chirp.settings import RadioSetting, RadioSettingGroup, \
     RadioSettingValueInteger, RadioSettingValueList, \
     RadioSettingValueBoolean, RadioSettingValueString, \
-    RadioSettingValueFloat, InvalidValueError
+    RadioSettingValueFloat, InvalidValueError, RadioSettings
 from textwrap import dedent
 
-if os.getenv("CHIRP_DEBUG"):
-    CHIRP_DEBUG = True
-else:
-    CHIRP_DEBUG = False
+LOG = logging.getLogger(__name__)
 
 MEM_FORMAT = """
 #seekto 0x0008;
@@ -43,8 +41,8 @@ struct {
   u8 unknown1:7,
      txtoneicon:1;
   u8 mailicon:3,
-     unknown2:4,
-     lowpower:1;
+     unknown2:3,
+     lowpower:2;
   u8 unknown3:1,
      wide:1,
      unknown4:2,
@@ -109,9 +107,9 @@ struct {
   u8 mdfa;
   u8 mdfb;
   u8 bcl;
-  u8 autolk; // NOTE: The UV-6 calls this byte voxenable, but the UV-5R calls
-             // it autolk. Since this is a minor difference, it will be referred
-             // to by the wrong name for the UV-6.
+  u8 autolk; // NOTE: The UV-6 calls this byte voxenable, but the UV-5R
+             // calls it autolk. Since this is a minor difference, it will
+             // be referred to by the wrong name for the UV-6.
   u8 sftd;
   u8 unknown6[3];
   u8 wtled;
@@ -126,7 +124,9 @@ struct {
   u8 ponmsg;
   u8 roger;
   u8 rogerrx;
-  u8 tdrch;
+  u8 tdrch; // NOTE: The UV-82HP calls this byte rtone, but the UV-6
+            // calls it tdrch. Since this is a minor difference, it will
+            // be referred to by the wrong name for the UV-82HP.
   u8 displayab:1,
      unknown1:2,
      fmradio:1,
@@ -134,7 +134,9 @@ struct {
      unknown2:1,
      reset:1,
      menu:1;
-  u8 vfomrlock;
+  u8 unknown1:6,
+     singleptt:1,
+     vfomrlock:1;
   u8 workmode;
   u8 keylock;
 } settings;
@@ -167,7 +169,8 @@ struct {
      unused4:4;
   u8 txpower:1,
      widenarr:1,
-     unknown5:6;
+     unknown5:4,
+     txpower3:2;
 } vfoa;
 
 #seekto 0x0F30;
@@ -190,7 +193,8 @@ struct {
      unused4:4;
   u8 txpower:1,
      widenarr:1,
-     unknown5:6;
+     unknown5:4,
+     txpower3:2;
 } vfob;
 
 #seekto 0x0F56;
@@ -242,22 +246,54 @@ struct {
   struct limit uhf;
 } limits_old;
 
+struct squelch {
+  u8 sql0;
+  u8 sql1;
+  u8 sql2;
+  u8 sql3;
+  u8 sql4;
+  u8 sql5;
+  u8 sql6;
+  u8 sql7;
+  u8 sql8;
+  u8 sql9;
+};
+
+#seekto 0x18A8;
+struct {
+  struct squelch vhf;
+  u8 unknown1[6];
+  u8 unknown2[16];
+  struct squelch uhf;
+} squelch_new;
+
+#seekto 0x18E8;
+struct {
+  struct squelch vhf;
+  u8 unknown[6];
+  struct squelch uhf;
+} squelch_old;
+
 """
 
 # 0x1EC0 - 0x2000
 
 vhf_220_radio = "\x02"
 
-BASETYPE_UV5R = ["BFS", "BFB"]
-BASETYPE_F11  = ["USA"]
-BASETYPE_UV82 = ["B82S", "BF82"]
-BASETYPE_BJ55 = ["BJ55"]          # needed for for the Baojie UV-55 in bjuv55.py
-BASETYPE_UV6  = ["BF1"]
+BASETYPE_UV5R = ["BFS", "BFB", "N5R-2", "N5R2", "N5RV", "BTS", "D5R2"]
+BASETYPE_F11 = ["USA"]
+BASETYPE_UV82 = ["US2S", "B82S", "BF82", "N82-2", "N822"]
+BASETYPE_BJ55 = ["BJ55"]  # needed for for the Baojie UV-55 in bjuv55.py
+BASETYPE_UV6 = ["BF1"]
+BASETYPE_KT980HP = ["BFP3V3 B"]
+BASETYPE_F8HP = ["BFP3V3 F", "N5R-3", "N5R3", "F5R3", "BFT"]
+BASETYPE_UV82HP = ["N82-3", "N823"]
 BASETYPE_LIST = BASETYPE_UV5R + BASETYPE_F11 + BASETYPE_UV82 + \
-                BASETYPE_BJ55 + BASETYPE_UV6
+                BASETYPE_BJ55 + BASETYPE_UV6 + BASETYPE_KT980HP + \
+                BASETYPE_F8HP + BASETYPE_UV82HP
 
 AB_LIST = ["A", "B"]
-ALMOD_LIST = ["Site", "Tone", "Code", "_unknown_"]
+ALMOD_LIST = ["Site", "Tone", "Code"]
 BANDWIDTH_LIST = ["Wide", "Narrow"]
 COLOR_LIST = ["Off", "Blue", "Orange", "Purple"]
 DTMFSPEED_LIST = ["%s ms" % x for x in range(50, 2010, 10)]
@@ -266,6 +302,7 @@ MODE_LIST = ["Channel", "Name", "Frequency"]
 PONMSG_LIST = ["Full", "Message"]
 PTTID_LIST = ["Off", "BOT", "EOT", "Both"]
 PTTIDCODE_LIST = ["%s" % x for x in range(1, 16)]
+RTONE_LIST = ["1000 Hz", "1450 Hz", "1750 Hz", "2100Hz"]
 RESUME_LIST = ["TO", "CO", "SE"]
 ROGERRX_LIST = ["Off"] + AB_LIST
 RPSTE_LIST = ["OFF"] + ["%s" % x for x in range(1, 11)]
@@ -281,42 +318,46 @@ TDRAB_LIST = ["Off"] + AB_LIST
 TDRCH_LIST = ["CH%s" % x for x in range(1, 129)]
 TIMEOUT_LIST = ["%s sec" % x for x in range(15, 615, 15)]
 TXPOWER_LIST = ["High", "Low"]
+TXPOWER3_LIST = ["High", "Mid", "Low"]
 VOICE_LIST = ["Off", "English", "Chinese"]
 VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 11)]
 WORKMODE_LIST = ["Frequency", "Channel"]
 
 SETTING_LISTS = {
-    "almod" : ALMOD_LIST,
-    "aniid" : PTTID_LIST,
-    "displayab" : AB_LIST,
-    "dtmfst" : DTMFST_LIST,
-    "dtmfspeed" : DTMFSPEED_LIST,
-    "mdfa" : MODE_LIST,
-    "mdfb" : MODE_LIST,
-    "ponmsg" : PONMSG_LIST,
-    "pttid" : PTTID_LIST,
-    "rogerrx" : ROGERRX_LIST,
-    "rpste" : RPSTE_LIST,
-    "rxled" : COLOR_LIST,
-    "save" : SAVE_LIST,
-    "scode" : PTTIDCODE_LIST,
-    "screv" : RESUME_LIST,
-    "sftd" : SHIFTD_LIST,
-    "stedelay" : STEDELAY_LIST,
-    "step" : STEP_LIST,
-    "step291" : STEP291_LIST,
-    "tdrab" : TDRAB_LIST,
-    "tdrch" : TDRCH_LIST,
-    "timeout" : TIMEOUT_LIST,
-    "txled" : COLOR_LIST,
-    "txpower" : TXPOWER_LIST,
-    "voice" : VOICE_LIST,
-    "vox" : VOX_LIST,
-    "widenarr" : BANDWIDTH_LIST,
-    "workmode" : WORKMODE_LIST,
-    "wtled" : COLOR_LIST
+    "almod": ALMOD_LIST,
+    "aniid": PTTID_LIST,
+    "displayab": AB_LIST,
+    "dtmfst": DTMFST_LIST,
+    "dtmfspeed": DTMFSPEED_LIST,
+    "mdfa": MODE_LIST,
+    "mdfb": MODE_LIST,
+    "ponmsg": PONMSG_LIST,
+    "pttid": PTTID_LIST,
+    "rtone": RTONE_LIST,
+    "rogerrx": ROGERRX_LIST,
+    "rpste": RPSTE_LIST,
+    "rxled": COLOR_LIST,
+    "save": SAVE_LIST,
+    "scode": PTTIDCODE_LIST,
+    "screv": RESUME_LIST,
+    "sftd": SHIFTD_LIST,
+    "stedelay": STEDELAY_LIST,
+    "step": STEP_LIST,
+    "step291": STEP291_LIST,
+    "tdrab": TDRAB_LIST,
+    "tdrch": TDRCH_LIST,
+    "timeout": TIMEOUT_LIST,
+    "txled": COLOR_LIST,
+    "txpower": TXPOWER_LIST,
+    "txpower3": TXPOWER3_LIST,
+    "voice": VOICE_LIST,
+    "vox": VOX_LIST,
+    "widenarr": BANDWIDTH_LIST,
+    "workmode": WORKMODE_LIST,
+    "wtled": COLOR_LIST
 }
 
+
 def _do_status(radio, block):
     status = chirp_common.Status()
     status.msg = "Cloning"
@@ -324,57 +365,52 @@ def _do_status(radio, block):
     status.max = radio.get_memsize()
     radio.status_fn(status)
 
-def validate_orig(ident):
-    try:
-        ver = int(ident[4:7])
-        if ver >= 291:
-            raise errors.RadioError("Radio version %i not supported" % ver)
-    except ValueError:
-        raise errors.RadioError("Radio reported invalid version string")
-
-def validate_291(ident):
-    if ident[4:7] != "\x30\x04\x50":
-        raise errors.RadioError("Radio version not supported")
-
 UV5R_MODEL_ORIG = "\x50\xBB\xFF\x01\x25\x98\x4D"
-UV5R_MODEL_291  = "\x50\xBB\xFF\x20\x12\x07\x25"
-UV5R_MODEL_F11  = "\x50\xBB\xFF\x13\xA1\x11\xDD"
+UV5R_MODEL_291 = "\x50\xBB\xFF\x20\x12\x07\x25"
+UV5R_MODEL_F11 = "\x50\xBB\xFF\x13\xA1\x11\xDD"
 UV5R_MODEL_UV82 = "\x50\xBB\xFF\x20\x13\x01\x05"
-UV5R_MODEL_UV6  = "\x50\xBB\xFF\x20\x12\x08\x23"
+UV5R_MODEL_UV6 = "\x50\xBB\xFF\x20\x12\x08\x23"
+UV5R_MODEL_UV6_ORIG = "\x50\xBB\xFF\x12\x03\x98\x4D"
+UV5R_MODEL_A58 = "\x50\xBB\xFF\x20\x14\x04\x13"
+
 
 def _upper_band_from_data(data):
     return data[0x03:0x04]
 
+
 def _upper_band_from_image(radio):
     return _upper_band_from_data(radio.get_mmap())
 
+
 def _firmware_version_from_data(data, version_start, version_stop):
     version_tag = data[version_start:version_stop]
     return version_tag
 
+
 def _firmware_version_from_image(radio):
     version = _firmware_version_from_data(radio.get_mmap(),
                                           radio._fw_ver_file_start,
                                           radio._fw_ver_file_stop)
-    if CHIRP_DEBUG:
-        print "_firmware_version_from_image: " + util.hexprint(version)
+    LOG.debug("_firmware_version_from_image: " + util.hexprint(version))
     return version
 
+
 def _special_block_from_data(data, special_block_start, special_block_stop):
     special_block_tag = data[special_block_start:special_block_stop]
     return special_block_tag
 
+
 def _special_block_from_image(radio):
     special_block = _special_block_from_data(radio.get_mmap(), 0x0CFA, 0x0D01)
-    if CHIRP_DEBUG:
-        print "_special_block_from_image: " + util.hexprint(special_block)
+    LOG.debug("_special_block_from_image: " + util.hexprint(special_block))
     return special_block
 
+
 def _do_ident(radio, magic):
     serial = radio.pipe
     serial.setTimeout(1)
 
-    print "Sending Magic: %s" % util.hexprint(magic)
+    LOG.info("Sending Magic: %s" % util.hexprint(magic))
     for byte in magic:
         serial.write(byte)
         time.sleep(0.01)
@@ -382,13 +418,13 @@ def _do_ident(radio, magic):
 
     if ack != "\x06":
         if ack:
-            print repr(ack)
+            LOG.debug(repr(ack))
         raise errors.RadioError("Radio did not respond")
 
     serial.write("\x02")
     ident = serial.read(8)
 
-    print "Ident:\n%s" % util.hexprint(ident)
+    LOG.info("Ident: %s" % util.hexprint(ident))
 
     serial.write("\x06")
     ack = serial.read(1)
@@ -397,51 +433,58 @@ def _do_ident(radio, magic):
 
     return ident
 
-def _read_block(radio, start, size):
+
+def _read_block(radio, start, size, first_command=False):
     msg = struct.pack(">BHB", ord("S"), start, size)
     radio.pipe.write(msg)
 
+    if first_command is False:
+        ack = radio.pipe.read(1)
+        if ack != "\x06":
+            raise errors.RadioError(
+                "Radio refused to send second block 0x%04x" % start)
+
     answer = radio.pipe.read(4)
     if len(answer) != 4:
         raise errors.RadioError("Radio refused to send block 0x%04x" % start)
 
     cmd, addr, length = struct.unpack(">BHB", answer)
     if cmd != ord("X") or addr != start or length != size:
-        print "Invalid answer for block 0x%04x:" % start
-        print "CMD: %s  ADDR: %04x  SIZE: %02x" % (cmd, addr, length)
+        LOG.error("Invalid answer for block 0x%04x:" % start)
+        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (cmd, addr, length))
         raise errors.RadioError("Unknown response from radio")
 
     chunk = radio.pipe.read(0x40)
     if not chunk:
         raise errors.RadioError("Radio did not send block 0x%04x" % start)
     elif len(chunk) != size:
-        print "Chunk length was 0x%04i" % len(chunk)
+        LOG.error("Chunk length was 0x%04i" % len(chunk))
         raise errors.RadioError("Radio sent incomplete block 0x%04x" % start)
 
     radio.pipe.write("\x06")
-
-    ack = radio.pipe.read(1)
-    if ack != "\x06":
-        raise errors.RadioError("Radio refused to send block 0x%04x" % start)
+    time.sleep(0.05)
 
     return chunk
 
+
 def _get_radio_firmware_version(radio):
     if radio.MODEL == "BJ-UV55":
-        block = _read_block(radio, 0x1FF0, 0x40)
+        block = _read_block(radio, 0x1FF0, 0x40, True)
         version = block[0:6]
     else:
-        block1 = _read_block(radio, 0x1EC0, 0x40)
-        block2 = _read_block(radio, 0x1F00, 0x40)
+        block1 = _read_block(radio, 0x1EC0, 0x40, True)
+        block2 = _read_block(radio, 0x1F00, 0x40, False)
         block = block1 + block2
-        version = block[48:64]
+        version = block[48:62]
     return version
 
+
 def _get_radio_special_block(radio):
-    block = _read_block(radio, 0xCF0, 0x40)
+    block = _read_block(radio, 0xCF0, 0x40, False)
     special_block = block[2:9]
     return special_block
 
+
 def _ident_radio(radio):
     for magic in radio._idents:
         error = None
@@ -449,40 +492,48 @@ def _ident_radio(radio):
             data = _do_ident(radio, magic)
             return data
         except errors.RadioError, e:
-            print e
+            LOG.error(e)
             error = e
             time.sleep(2)
     if error:
         raise error
     raise errors.RadioError("Radio did not respond")
 
+
 def _do_download(radio):
     data = _ident_radio(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):
+        raise errors.RadioError("Incorrect 'Model' selected.")
+
     # Main block
-    if CHIRP_DEBUG:
-        print "downloading main block..."
+    LOG.debug("downloading main block...")
     for i in range(0, 0x1800, 0x40):
-        data += _read_block(radio, i, 0x40)
+        data += _read_block(radio, i, 0x40, False)
         _do_status(radio, i)
-    if CHIRP_DEBUG:
-        print "done."
-        print "downloading aux block..."
+    _do_status(radio, radio.get_memsize())
+    LOG.debug("done.")
+    LOG.debug("downloading aux block...")
     # Auxiliary block starts at 0x1ECO (?)
     for i in range(0x1EC0, 0x2000, 0x40):
-        data += _read_block(radio, i, 0x40)
-    if CHIRP_DEBUG:
-        print "done."
+        data += _read_block(radio, i, 0x40, False)
+    LOG.debug("done.")
     return memmap.MemoryMap(data)
 
+
 def _send_block(radio, addr, data):
     msg = struct.pack(">BHB", ord("X"), addr, len(data))
     radio.pipe.write(msg + data)
+    time.sleep(0.05)
 
     ack = radio.pipe.read(1)
     if ack != "\x06":
         raise errors.RadioError("Radio refused to accept block 0x%04x" % addr)
 
+
 def _do_upload(radio):
     ident = _ident_radio(radio)
     radio_upper_band = ident[3:4]
@@ -494,17 +545,19 @@ def _do_upload(radio):
 
     image_version = _firmware_version_from_image(radio)
     radio_version = _get_radio_firmware_version(radio)
-    print "Image Version is %s" % repr(image_version)
-    print "Radio Version is %s" % repr(radio_version)
+    LOG.info("Image Version is %s" % repr(image_version))
+    LOG.info("Radio Version is %s" % repr(radio_version))
 
-    if not any(type in radio_version for type in BASETYPE_LIST):
-        raise errors.RadioError("Unsupported firmware version: `%s'" %
-                                radio_version)
+    if image_version != radio_version:
+        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))
 
     image_special_block = _special_block_from_image(radio)
     radio_special_block = _get_radio_special_block(radio)
-    print "Image Special Block is " + util.hexprint(image_special_block)
-    print "Radio Special Block is " + util.hexprint(radio_special_block)
+    LOG.debug("Image Special Block is " + util.hexprint(image_special_block))
+    LOG.debug("Radio Special Block is " + util.hexprint(radio_special_block))
 
     if image_special_block != radio_special_block:
         raise errors.RadioError("Image not supported by radio: `%s'" %
@@ -514,10 +567,11 @@ def _do_upload(radio):
     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())
 
     if len(radio.get_mmap().get_packed()) == 0x1808:
-        print "Old image, not writing aux block"
-        return # Old image, no aux block
+        LOG.info("Old image, not writing aux block")
+        return  # Old image, no aux block
 
     if image_version != radio_version:
         msg = ("Upload finished, but the 'Other Settings' "
@@ -534,11 +588,16 @@ def _do_upload(radio):
 UV5R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=4.00),
                      chirp_common.PowerLevel("Low",  watts=1.00)]
 
+UV5R_POWER_LEVELS3 = [chirp_common.PowerLevel("High", watts=8.00),
+                      chirp_common.PowerLevel("Med",  watts=4.00),
+                      chirp_common.PowerLevel("Low",  watts=1.00)]
+
 UV5R_DTCS = sorted(chirp_common.DTCS_CODES + [645])
 
 UV5R_CHARSET = chirp_common.CHARSET_UPPER_NUMERIC + \
     "!@#$%^&*()+-=[]:\";'<>?,./"
 
+
 # Uncomment this to actually register this radio in CHIRP
 @directory.register
 class BaofengUV5R(chirp_common.CloneModeRadio,
@@ -553,33 +612,37 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
     _idents = [UV5R_MODEL_291,
                UV5R_MODEL_ORIG
                ]
-    _mem_params = ( 0x1828 # poweron_msg offset
-                    )
+    _vhf_range = (136000000, 174000000)
+    _220_range = (220000000, 260000000)
+    _uhf_range = (400000000, 520000000)
+    _mem_params = (0x1828  # poweron_msg offset
+                   )
     # offset of fw version in image file
     _fw_ver_file_start = 0x1838
-    _fw_ver_file_stop = 0x1848
+    _fw_ver_file_stop = 0x1846
 
     @classmethod
     def get_prompts(cls):
         rp = chirp_common.RadioPrompts()
-        rp.experimental = ('Due to the fact that the manufacturer continues to '
-                'release new versions of the firmware with obscure and '
-                'hard-to-track changes, this driver may not work with '
-                'your device. Thus far and to the best knowledge of the '
-                'author, no UV-5R radios have been harmed by using CHIRP. '
-                'However, proceed at your own risk!')
+        rp.experimental = \
+            ('Due to the fact that the manufacturer continues to '
+             'release new versions of the firmware with obscure and '
+             'hard-to-track changes, this driver may not work with '
+             'your device. Thus far and to the best knowledge of the '
+             'author, no UV-5R radios have been harmed by using CHIRP. '
+             'However, proceed at your own risk!')
         rp.pre_download = _(dedent("""\
             1. Turn radio off.
             2. Connect cable to mic/spkr connector.
             3. Make sure connector is firmly connected.
-            4. Turn radio on.
+            4. Turn radio on (volume may need to be set at 100%).
             5. Ensure that the radio is tuned to channel with no activity.
             6. Click OK to download image from device."""))
         rp.pre_upload = _(dedent("""\
             1. Turn radio off.
             2. Connect cable to mic/spkr connector.
             3. Make sure connector is firmly connected.
-            4. Turn radio on.
+            4. Turn radio on (volume may need to be set at 100%).
             5. Ensure that the radio is tuned to channel with no activity.
             6. Click OK to upload image to device."""))
         return rp
@@ -602,8 +665,8 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
         rf.valid_duplexes = ["", "-", "+", "split", "off"]
         rf.valid_modes = ["FM", "NFM"]
 
-        normal_bands = [(136000000, 174000000), (400000000, 520000000)]
-        rax_bands = [(136000000, 174000000), (220000000, 260000000)]
+        normal_bands = [self._vhf_range, self._uhf_range]
+        rax_bands = [self._vhf_range, self._220_range]
 
         if self._mmap is None:
             rf.valid_bands = [normal_bands[0], rax_bands[1], normal_bands[1]]
@@ -693,7 +756,7 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
 
         for char in _nam.name:
             if str(char) == "\xFF":
-                char = " " # The UV-5R software may have 0xFF mid-name
+                char = " "  # The UV-5R software may have 0xFF mid-name
             mem.name += str(char)
         mem.name = mem.name.rstrip()
 
@@ -713,7 +776,7 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
                 index = _mem.txtone - 1
             mem.dtcs = UV5R_DTCS[index]
         else:
-            print "Bug: txtone is %04x" % _mem.txtone
+            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
 
         if _mem.rxtone in [0, 0xFFFF]:
             rxmode = ""
@@ -729,7 +792,7 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
                 index = _mem.rxtone - 1
             mem.rx_dtcs = UV5R_DTCS[index]
         else:
-            print "Bug: rxtone is %04x" % _mem.rxtone
+            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
 
         if txmode == "Tone" and not rxmode:
             mem.tmode = "Tone"
@@ -746,7 +809,17 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
         if not _mem.scan:
             mem.skip = "S"
 
-        mem.power = UV5R_POWER_LEVELS[_mem.lowpower]
+        if self.MODEL in ("KT-980HP", "BF-F8HP", "UV-82HP"):
+            levels = UV5R_POWER_LEVELS3
+        else:
+            levels = UV5R_POWER_LEVELS
+        try:
+            mem.power = levels[_mem.lowpower]
+        except IndexError:
+            LOG.error("Radio reported invalid power level %s (in %s)" %
+                      (_mem.power, levels))
+            mem.power = levels[0]
+
         mem.mode = _mem.wide and "FM" or "NFM"
 
         mem.extra = RadioSettingGroup("Extra", "extra")
@@ -840,15 +913,22 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
 
         _mem.scan = mem.skip != "S"
         _mem.wide = mem.mode == "FM"
-        _mem.lowpower = mem.power == UV5R_POWER_LEVELS[1]
+
+        if mem.power:
+            if self.MODEL in ("KT-980HP", "BF-F8HP", "UV-82HP"):
+                levels = [str(l) for l in UV5R_POWER_LEVELS3]
+                _mem.lowpower = levels.index(str(mem.power))
+            else:
+                _mem.lowpower = UV5R_POWER_LEVELS.index(mem.power)
+        else:
+            _mem.lowpower = 0
 
         for setting in mem.extra:
             setattr(_mem, setting.get_name(), setting.value)
 
     def _is_orig(self):
         version_tag = _firmware_version_from_image(self)
-        if CHIRP_DEBUG:
-            print "@_is_orig, version_tag:", util.hexprint(version_tag)
+        LOG.debug("@_is_orig, version_tag: %s", util.hexprint(version_tag))
         try:
             if 'BFB' in version_tag:
                 idx = version_tag.index("BFB") + 3
@@ -865,24 +945,6 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
         if 'BFB' in version_tag:
             idx = version_tag.index("BFB") + 3
             return int(version_tag[idx:idx + 3])
-        elif 'BFS' in version_tag:
-            idx = version_tag.index("BFS") + 3
-            return int(version_tag[idx:idx + 3])
-        elif 'BF82' in version_tag:
-            idx = version_tag.index("BF82") + 2
-            return int(version_tag[idx:idx + 4])
-        elif 'B82S' in version_tag:
-            idx = version_tag.index("B82S") + 4
-            return int(version_tag[idx:idx + 2]) + 8200
-        elif 'USA' in version_tag:
-            idx = version_tag.index("USA") + 3
-            return int(version_tag[idx:idx + 3]) + 11000
-        elif 'BJ55' in version_tag:
-            idx = version_tag.index("BJ55") + 2
-            return int(version_tag[idx:idx + 4])
-        elif 'BF1' in version_tag:
-            idx = version_tag.index("BF1") + 2
-            return int(version_tag[idx:idx + 4])
 
         raise Exception("Unrecognized firmware version string")
 
@@ -893,25 +955,27 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
     def _get_settings(self):
         _ani = self._memobj.ani
         _settings = self._memobj.settings
+        _squelch = self._memobj.squelch_new
         _vfoa = self._memobj.vfoa
         _vfob = self._memobj.vfob
         _wmchannel = self._memobj.wmchannel
         basic = RadioSettingGroup("basic", "Basic Settings")
         advanced = RadioSettingGroup("advanced", "Advanced Settings")
-        group = RadioSettingGroup("top", "All Settings", basic, advanced)
+
+        group = RadioSettings(basic, advanced)
 
         rs = RadioSetting("squelch", "Carrier Squelch Level",
                           RadioSettingValueInteger(0, 9, _settings.squelch))
         basic.append(rs)
 
         rs = RadioSetting("save", "Battery Saver",
-                          RadioSettingValueList(SAVE_LIST,
-                                                SAVE_LIST[_settings.save]))
+                          RadioSettingValueList(
+                              SAVE_LIST, SAVE_LIST[_settings.save]))
         basic.append(rs)
 
         rs = RadioSetting("vox", "VOX Sensitivity",
-                          RadioSettingValueList(VOX_LIST,
-                                                VOX_LIST[_settings.vox]))
+                          RadioSettingValueList(
+                              VOX_LIST, VOX_LIST[_settings.vox]))
         advanced.append(rs)
 
         if self.MODEL == "UV-6":
@@ -933,9 +997,8 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
 
         if self.MODEL == "UV-6":
             rs = RadioSetting("tdrch", "Dual Watch Channel",
-                              RadioSettingValueList(TDRCH_LIST,
-                                                    TDRCH_LIST[
-                                                    _settings.tdrch]))
+                              RadioSettingValueList(
+                                  TDRCH_LIST, TDRCH_LIST[_settings.tdrch]))
             advanced.append(rs)
 
             rs = RadioSetting("tdrab", "Dual Watch TX Priority",
@@ -943,9 +1006,8 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
             advanced.append(rs)
         else:
             rs = RadioSetting("tdrab", "Dual Watch TX Priority",
-                              RadioSettingValueList(TDRAB_LIST,
-                                                    TDRAB_LIST[
-                                                    _settings.tdrab]))
+                              RadioSettingValueList(
+                                  TDRAB_LIST, TDRAB_LIST[_settings.tdrab]))
             advanced.append(rs)
 
         if self.MODEL == "UV-6":
@@ -953,9 +1015,13 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
                               RadioSettingValueBoolean(_settings.alarm))
             advanced.append(rs)
 
+        if _settings.almod > 0x02:
+            val = 0x01
+        else:
+            val = _settings.almod
         rs = RadioSetting("almod", "Alarm Mode",
-                          RadioSettingValueList(ALMOD_LIST,
-                                                ALMOD_LIST[_settings.almod]))
+                          RadioSettingValueList(
+                              ALMOD_LIST, ALMOD_LIST[val]))
         advanced.append(rs)
 
         rs = RadioSetting("beep", "Beep",
@@ -963,9 +1029,8 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
         basic.append(rs)
 
         rs = RadioSetting("timeout", "Timeout Timer",
-                          RadioSettingValueList(TIMEOUT_LIST,
-                                                TIMEOUT_LIST[
-                                                _settings.timeout]))
+                          RadioSettingValueList(
+                              TIMEOUT_LIST, TIMEOUT_LIST[_settings.timeout]))
         basic.append(rs)
 
         if self._is_orig() and self._my_version() < 251:
@@ -974,25 +1039,24 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
             advanced.append(rs)
         else:
             rs = RadioSetting("voice", "Voice",
-                              RadioSettingValueList(VOICE_LIST,
-                                                    VOICE_LIST[
-                                                    _settings.voice]))
+                              RadioSettingValueList(
+                                  VOICE_LIST, VOICE_LIST[_settings.voice]))
             advanced.append(rs)
 
         rs = RadioSetting("screv", "Scan Resume",
-                          RadioSettingValueList(RESUME_LIST,
-                                                RESUME_LIST[_settings.screv]))
+                          RadioSettingValueList(
+                              RESUME_LIST, RESUME_LIST[_settings.screv]))
         advanced.append(rs)
 
         if self.MODEL != "UV-6":
             rs = RadioSetting("mdfa", "Display Mode (A)",
-                              RadioSettingValueList(MODE_LIST,
-                                                    MODE_LIST[_settings.mdfa]))
+                              RadioSettingValueList(
+                                  MODE_LIST, MODE_LIST[_settings.mdfa]))
             basic.append(rs)
 
             rs = RadioSetting("mdfb", "Display Mode (B)",
-                              RadioSettingValueList(MODE_LIST,
-                                                    MODE_LIST[_settings.mdfb]))
+                              RadioSettingValueList(
+                                  MODE_LIST, MODE_LIST[_settings.mdfb]))
             basic.append(rs)
 
         rs = RadioSetting("bcl", "Busy Channel Lockout",
@@ -1010,21 +1074,18 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
 
         if self.MODEL != "UV-6":
             rs = RadioSetting("wtled", "Standby LED Color",
-                              RadioSettingValueList(COLOR_LIST,
-                                                    COLOR_LIST[
-                                                    _settings.wtled]))
+                              RadioSettingValueList(
+                                  COLOR_LIST, COLOR_LIST[_settings.wtled]))
             basic.append(rs)
 
             rs = RadioSetting("rxled", "RX LED Color",
-                              RadioSettingValueList(COLOR_LIST,
-                                                    COLOR_LIST[
-                                                    _settings.rxled]))
+                              RadioSettingValueList(
+                                  COLOR_LIST, COLOR_LIST[_settings.rxled]))
             basic.append(rs)
 
             rs = RadioSetting("txled", "TX LED Color",
-                              RadioSettingValueList(COLOR_LIST,
-                                                    COLOR_LIST[
-                                                    _settings.txled]))
+                              RadioSettingValueList(
+                                  COLOR_LIST, COLOR_LIST[_settings.txled]))
             basic.append(rs)
 
         if self.MODEL == "UV-82":
@@ -1032,9 +1093,9 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
                               RadioSettingValueBoolean(_settings.roger))
             basic.append(rs)
             rs = RadioSetting("rogerrx", "Roger Beep (RX)",
-                              RadioSettingValueList(ROGERRX_LIST,
-                                                    ROGERRX_LIST[
-                                                    _settings.rogerrx]))
+                              RadioSettingValueList(
+                                  ROGERRX_LIST,
+                                  ROGERRX_LIST[_settings.rogerrx]))
             basic.append(rs)
         else:
             rs = RadioSetting("roger", "Roger Beep",
@@ -1046,13 +1107,13 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
         advanced.append(rs)
 
         rs = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)",
-                          RadioSettingValueList(RPSTE_LIST,
-                                                RPSTE_LIST[_settings.rpste]))
+                          RadioSettingValueList(
+                              RPSTE_LIST, RPSTE_LIST[_settings.rpste]))
         advanced.append(rs)
 
         rs = RadioSetting("rptrl", "STE Repeater Delay",
-                          RadioSettingValueList(STEDELAY_LIST,
-                                                STEDELAY_LIST[_settings.rptrl]))
+                          RadioSettingValueList(
+                              STEDELAY_LIST, STEDELAY_LIST[_settings.rptrl]))
         advanced.append(rs)
 
         if self.MODEL != "UV-6":
@@ -1070,6 +1131,37 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
                               RadioSettingValueBoolean(_settings.vfomrlock))
             advanced.append(rs)
 
+        if self.MODEL == "UV-82":
+            # this is a UV-82C only feature
+            rs = RadioSetting("vfomrlock", "VFO/MR Switching (UV-82C only)",
+                              RadioSettingValueBoolean(_settings.vfomrlock))
+            advanced.append(rs)
+
+        if self.MODEL == "UV-82HP":
+            # this is a UV-82HP only feature
+            rs = RadioSetting("vfomrlock", "VFO/MR Switching",
+                              RadioSettingValueBoolean(_settings.vfomrlock))
+            advanced.append(rs)
+
+        if self.MODEL == "UV-82":
+            # this is an UV-82C only feature
+            rs = RadioSetting("singleptt", "Single PTT (UV-82C only)",
+                              RadioSettingValueBoolean(_settings.singleptt))
+            advanced.append(rs)
+
+        if self.MODEL == "UV-82HP":
+            # this is an UV-82HP only feature
+            rs = RadioSetting("singleptt", "Single PTT",
+                              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]))
+            advanced.append(rs)
+
         if len(self._mmap.get_packed()) == 0x1808:
             # Old image, without aux block
             return group
@@ -1100,28 +1192,27 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
         if self.MODEL != "UV-6":
             _msg = self._memobj.sixpoweron_msg
             rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1",
-                              RadioSettingValueString(0, 7, _filter(
-                                                      _msg.line1)))
+                              RadioSettingValueString(
+                                  0, 7, _filter(_msg.line1)))
             other.append(rs)
             rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2",
-                              RadioSettingValueString(0, 7, _filter(
-                                                      _msg.line2)))
+                              RadioSettingValueString(
+                                  0, 7, _filter(_msg.line2)))
             other.append(rs)
 
             _msg = self._memobj.poweron_msg
             rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
-                              RadioSettingValueString(0, 7, _filter(
-                                                      _msg.line1)))
+                              RadioSettingValueString(
+                                  0, 7, _filter(_msg.line1)))
             other.append(rs)
             rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
-                              RadioSettingValueString(0, 7, _filter(
-                                                      _msg.line2)))
+                              RadioSettingValueString(
+                                  0, 7, _filter(_msg.line2)))
             other.append(rs)
 
             rs = RadioSetting("ponmsg", "Power-On Message",
-                              RadioSettingValueList(PONMSG_LIST,
-                                                    PONMSG_LIST[
-                                                    _settings.ponmsg]))
+                              RadioSettingValueList(
+                                  PONMSG_LIST, PONMSG_LIST[_settings.ponmsg]))
             other.append(rs)
 
             if self._is_orig():
@@ -1162,15 +1253,14 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
             group.append(workmode)
 
             rs = RadioSetting("displayab", "Display",
-                              RadioSettingValueList(AB_LIST,
-                                                    AB_LIST[
-                                                    _settings.displayab]))
+                              RadioSettingValueList(
+                                  AB_LIST, AB_LIST[_settings.displayab]))
             workmode.append(rs)
 
             rs = RadioSetting("workmode", "VFO/MR Mode",
-                              RadioSettingValueList(WORKMODE_LIST,
-                                                    WORKMODE_LIST[
-                                                    _settings.workmode]))
+                              RadioSettingValueList(
+                                  WORKMODE_LIST,
+                                  WORKMODE_LIST[_settings.workmode]))
             workmode.append(rs)
 
             rs = RadioSetting("keylock", "Keypad Lock",
@@ -1188,10 +1278,10 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
             workmode.append(rs)
 
             def convert_bytes_to_freq(bytes):
-               real_freq = 0
-               for byte in bytes:
-                   real_freq = (real_freq * 10) + byte
-               return chirp_common.format_freq(real_freq * 10)
+                real_freq = 0
+                for byte in bytes:
+                    real_freq = (real_freq * 10) + byte
+                return chirp_common.format_freq(real_freq * 10)
 
             def my_validate(value):
                 value = chirp_common.parse_freq(value)
@@ -1222,20 +1312,20 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
             workmode.append(rs)
 
             rs = RadioSetting("vfoa.sftd", "VFO A Shift",
-                              RadioSettingValueList(SHIFTD_LIST,
-                                                    SHIFTD_LIST[_vfoa.sftd]))
+                              RadioSettingValueList(
+                                  SHIFTD_LIST, SHIFTD_LIST[_vfoa.sftd]))
             workmode.append(rs)
 
             rs = RadioSetting("vfob.sftd", "VFO B Shift",
-                              RadioSettingValueList(SHIFTD_LIST,
-                                                    SHIFTD_LIST[_vfob.sftd]))
+                              RadioSettingValueList(
+                                  SHIFTD_LIST, SHIFTD_LIST[_vfob.sftd]))
             workmode.append(rs)
 
             def convert_bytes_to_offset(bytes):
-               real_offset = 0
-               for byte in bytes:
-                   real_offset = (real_offset * 10) + byte
-               return chirp_common.format_freq(real_offset * 10000)
+                real_offset = 0
+                for byte in bytes:
+                    real_offset = (real_offset * 10) + byte
+                return chirp_common.format_freq(real_offset * 10000)
 
             def apply_offset(setting, obj):
                 value = chirp_common.parse_freq(str(setting.value)) / 10000
@@ -1243,75 +1333,84 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
                     obj.offset[i] = value % 10
                     value /= 10
 
-            val1a = RadioSettingValueString(0, 10,
-                                            convert_bytes_to_offset(
-                                            _vfoa.offset))
-            rs = RadioSetting("vfoa.offset", "VFO A Offset (0.00-69.95)", val1a)
+            val1a = RadioSettingValueString(
+                        0, 10, convert_bytes_to_offset(_vfoa.offset))
+            rs = RadioSetting("vfoa.offset",
+                              "VFO A Offset (0.00-69.95)", val1a)
             rs.set_apply_callback(apply_offset, _vfoa)
             workmode.append(rs)
 
-            val1b = RadioSettingValueString(0, 10,
-                                            convert_bytes_to_offset(
-                                            _vfob.offset))
-            rs = RadioSetting("vfob.offset", "VFO B Offset (0.00-69.95)", val1b)
+            val1b = RadioSettingValueString(
+                        0, 10, convert_bytes_to_offset(_vfob.offset))
+            rs = RadioSetting("vfob.offset",
+                              "VFO B Offset (0.00-69.95)", val1b)
             rs.set_apply_callback(apply_offset, _vfob)
             workmode.append(rs)
 
-            rs = RadioSetting("vfoa.txpower", "VFO A Power",
-                              RadioSettingValueList(TXPOWER_LIST,
-                                                    TXPOWER_LIST[
-                                                    _vfoa.txpower]))
-            workmode.append(rs)
+            if self.MODEL in ("KT-980HP", "BF-F8HP", "UV-82HP"):
+                rs = RadioSetting("vfoa.txpower3", "VFO A Power",
+                                  RadioSettingValueList(
+                                      TXPOWER3_LIST,
+                                      TXPOWER3_LIST[_vfoa.txpower3]))
+                workmode.append(rs)
 
-            rs = RadioSetting("vfob.txpower", "VFO B Power",
-                              RadioSettingValueList(TXPOWER_LIST,
-                                                    TXPOWER_LIST[
-                                                    _vfob.txpower]))
-            workmode.append(rs)
+                rs = RadioSetting("vfob.txpower3", "VFO B Power",
+                                  RadioSettingValueList(
+                                      TXPOWER3_LIST,
+                                      TXPOWER3_LIST[_vfob.txpower3]))
+                workmode.append(rs)
+            else:
+                rs = RadioSetting("vfoa.txpower", "VFO A Power",
+                                  RadioSettingValueList(
+                                      TXPOWER_LIST,
+                                      TXPOWER_LIST[_vfoa.txpower]))
+                workmode.append(rs)
+
+                rs = RadioSetting("vfob.txpower", "VFO B Power",
+                                  RadioSettingValueList(
+                                      TXPOWER_LIST,
+                                      TXPOWER_LIST[_vfob.txpower]))
+                workmode.append(rs)
 
             rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth",
-                              RadioSettingValueList(BANDWIDTH_LIST,
-                                                    BANDWIDTH_LIST[
-                                                    _vfoa.widenarr]))
+                              RadioSettingValueList(
+                                  BANDWIDTH_LIST,
+                                  BANDWIDTH_LIST[_vfoa.widenarr]))
             workmode.append(rs)
 
             rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
-                              RadioSettingValueList(BANDWIDTH_LIST,
-                                                    BANDWIDTH_LIST[
-                                                    _vfob.widenarr]))
+                              RadioSettingValueList(
+                                  BANDWIDTH_LIST,
+                                  BANDWIDTH_LIST[_vfob.widenarr]))
             workmode.append(rs)
 
             rs = RadioSetting("vfoa.scode", "VFO A PTT-ID",
-                              RadioSettingValueList(PTTIDCODE_LIST,
-                                                    PTTIDCODE_LIST[
-                                                    _vfoa.scode]))
+                              RadioSettingValueList(
+                                  PTTIDCODE_LIST, PTTIDCODE_LIST[_vfoa.scode]))
             workmode.append(rs)
 
             rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
-                              RadioSettingValueList(PTTIDCODE_LIST,
-                                                    PTTIDCODE_LIST[
-                                                    _vfob.scode]))
+                              RadioSettingValueList(
+                                  PTTIDCODE_LIST, PTTIDCODE_LIST[_vfob.scode]))
             workmode.append(rs)
 
             if not self._is_orig():
                 rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
-                                  RadioSettingValueList(STEP291_LIST,
-                                                        STEP291_LIST[
-                                                        _vfoa.step]))
+                                  RadioSettingValueList(
+                                      STEP291_LIST, STEP291_LIST[_vfoa.step]))
                 workmode.append(rs)
                 rs = RadioSetting("vfob.step", "VFO B Tuning Step",
-                                  RadioSettingValueList(STEP291_LIST,
-                                                        STEP291_LIST[
-                                                        _vfob.step]))
+                                  RadioSettingValueList(
+                                      STEP291_LIST, STEP291_LIST[_vfob.step]))
                 workmode.append(rs)
             else:
                 rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
-                                  RadioSettingValueList(STEP_LIST,
-                                                        STEP_LIST[_vfoa.step]))
+                                  RadioSettingValueList(
+                                      STEP_LIST, STEP_LIST[_vfoa.step]))
                 workmode.append(rs)
                 rs = RadioSetting("vfob.step", "VFO B Tuning Step",
-                                  RadioSettingValueList(STEP_LIST,
-                                                        STEP_LIST[_vfob.step]))
+                                  RadioSettingValueList(
+                                      STEP_LIST, STEP_LIST[_vfob.step]))
                 workmode.append(rs)
 
         fm_preset = RadioSettingGroup("fm_preset", "FM Radio Preset")
@@ -1322,7 +1421,7 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
         else:
             preset = 76.0
         rs = RadioSetting("fm_presets", "FM Preset(MHz)",
-                      RadioSettingValueFloat(65, 116.1, preset, 0.1, 1))
+                          RadioSettingValueFloat(65, 116.1, preset, 0.1, 1))
         fm_preset.append(rs)
 
         dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
@@ -1336,6 +1435,7 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
             val.set_charset(dtmfchars)
             rs = RadioSetting("pttid/%i.code" % i,
                               "PTT ID Code %i" % (i + 1), val)
+
             def apply_code(setting, obj):
                 code = []
                 for j in range(0, 5):
@@ -1352,6 +1452,7 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
         val = RadioSettingValueString(0, 5, _code, False)
         val.set_charset(dtmfchars)
         rs = RadioSetting("ani.code", "ANI Code", val)
+
         def apply_code(setting, obj):
             code = []
             for j in range(0, 5):
@@ -1373,6 +1474,7 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
         val = RadioSettingValueString(0, 3, _code, False)
         val.set_charset(dtmfchars)
         rs = RadioSetting("ani.alarmcode", "Alarm Code", val)
+
         def apply_code(setting, obj):
             alarmcode = []
             for j in range(0, 3):
@@ -1389,16 +1491,46 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
                                                 DTMFST_LIST[_settings.dtmfst]))
         dtmf.append(rs)
 
+        if _ani.dtmfon > 0xC3:
+            val = 0x00
+        else:
+            val = _ani.dtmfon
         rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
                           RadioSettingValueList(DTMFSPEED_LIST,
-                                                DTMFSPEED_LIST[_ani.dtmfon]))
+                                                DTMFSPEED_LIST[val]))
         dtmf.append(rs)
 
+        if _ani.dtmfoff > 0xC3:
+            val = 0x00
+        else:
+            val = _ani.dtmfoff
         rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
                           RadioSettingValueList(DTMFSPEED_LIST,
-                                                DTMFSPEED_LIST[_ani.dtmfoff]))
+                                                DTMFSPEED_LIST[val]))
         dtmf.append(rs)
 
+        rs = RadioSetting("pttlt", "PTT ID Delay",
+                          RadioSettingValueInteger(0, 30, _settings.pttlt))
+        dtmf.append(rs)
+
+        if not self._is_orig():
+            service = RadioSettingGroup("service", "Service Settings")
+            group.append(service)
+
+            for band in ["vhf", "uhf"]:
+                for index in range(0, 10):
+                    key = "squelch_new.%s.sql%i" % (band, index)
+                    if band == "vhf":
+                        _obj = self._memobj.squelch_new.vhf
+                    elif band == "uhf":
+                        _obj = self._memobj.squelch_new.uhf
+                    name = "%s Squelch %i" % (band.upper(), index)
+                    rs = RadioSetting(key, name,
+                                      RadioSettingValueInteger(
+                                          0, 123,
+                                          getattr(_obj, "sql%i" % (index))))
+                    service.append(rs)
+
         return group
 
     def get_settings(self):
@@ -1406,15 +1538,14 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
             return self._get_settings()
         except:
             import traceback
-            print "Failed to parse settings:"
-            traceback.print_exc()
+            LOG.error("Failed to parse settings: %s", traceback.format_exc())
             return None
 
     def set_settings(self, settings):
         _settings = self._memobj.settings
         for element in settings:
             if not isinstance(element, RadioSetting):
-                if element.get_name() == "fm_preset" :
+                if element.get_name() == "fm_preset":
                     self._set_fm_preset(element)
                 else:
                     self.set_settings(element)
@@ -1438,13 +1569,13 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
                         setting = element.get_name()
 
                     if element.has_apply_callback():
-                        print "Using apply callback"
+                        LOG.debug("Using apply callback")
                         element.run_apply_callback()
                     else:
-                        print "Setting %s = %s" % (setting, element.value)
+                        LOG.debug("Setting %s = %s" % (setting, element.value))
                         setattr(obj, setting, element.value)
                 except Exception, e:
-                    print element.get_name()
+                    LOG.debug(element.get_name())
                     raise
 
     def _set_fm_preset(self, settings):
@@ -1452,12 +1583,13 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
             try:
                 val = element.value
                 value = int(val.get_value() * 10 - 650)
-                print "Setting fm_presets = %s" % (value)
+                LOG.debug("Setting fm_presets = %s" % (value))
                 self._memobj.fm_presets = value
             except Exception, e:
-                print element.get_name()
+                LOG.debug(element.get_name())
                 raise
 
+
 @directory.register
 class BaofengF11Radio(BaofengUV5R):
     VENDOR = "Baofeng"
@@ -1469,23 +1601,29 @@ class BaofengF11Radio(BaofengUV5R):
         # Override this for F11 to always return False
         return False
 
+
 @directory.register
 class BaofengUV82Radio(BaofengUV5R):
     MODEL = "UV-82"
     _basetype = BASETYPE_UV82
     _idents = [UV5R_MODEL_UV82]
+    _vhf_range = (130000000, 176000000)
+    _uhf_range = (400000000, 521000000)
 
     def _is_orig(self):
         # Override this for UV82 to always return False
         return False
 
+
 @directory.register
 class BaofengUV6Radio(BaofengUV5R):
     """Baofeng UV-6/UV-7"""
     VENDOR = "Baofeng"
     MODEL = "UV-6"
     _basetype = BASETYPE_UV6
-    _idents = [UV5R_MODEL_UV6]
+    _idents = [UV5R_MODEL_UV6,
+               UV5R_MODEL_UV6_ORIG
+               ]
 
     def get_features(self):
         rf = BaofengUV5R.get_features(self)
@@ -1507,3 +1645,62 @@ class BaofengUV6Radio(BaofengUV5R):
     def _is_orig(self):
         # Override this for UV6 to always return False
         return False
+
+
+ at directory.register
+class IntekKT980Radio(BaofengUV5R):
+    VENDOR = "Intek"
+    MODEL = "KT-980HP"
+    _basetype = BASETYPE_KT980HP
+    _idents = [UV5R_MODEL_291]
+    _vhf_range = (130000000, 180000000)
+    _uhf_range = (400000000, 521000000)
+
+    def get_features(self):
+        rf = BaofengUV5R.get_features(self)
+        rf.valid_power_levels = UV5R_POWER_LEVELS3
+        return rf
+
+    def _is_orig(self):
+        # Override this for KT980HP to always return False
+        return False
+
+
+ at directory.register
+class BaofengBFF8HPRadio(BaofengUV5R):
+    VENDOR = "Baofeng"
+    MODEL = "BF-F8HP"
+    _basetype = BASETYPE_F8HP
+    _idents = [UV5R_MODEL_291,
+               UV5R_MODEL_A58
+               ]
+    _vhf_range = (130000000, 180000000)
+    _uhf_range = (400000000, 521000000)
+
+    def get_features(self):
+        rf = BaofengUV5R.get_features(self)
+        rf.valid_power_levels = UV5R_POWER_LEVELS3
+        return rf
+
+    def _is_orig(self):
+        # Override this for BFF8HP to always return False
+        return False
+
+
+ at directory.register
+class BaofengUV82HPRadio(BaofengUV5R):
+    VENDOR = "Baofeng"
+    MODEL = "UV-82HP"
+    _basetype = BASETYPE_UV82HP
+    _idents = [UV5R_MODEL_UV82]
+    _vhf_range = (136000000, 175000000)
+    _uhf_range = (400000000, 521000000)
+
+    def get_features(self):
+        rf = BaofengUV5R.get_features(self)
+        rf.valid_power_levels = UV5R_POWER_LEVELS3
+        return rf
+
+    def _is_orig(self):
+        # Override this for UV82HP to always return False
+        return False
diff --git a/chirp/uvb5.py b/chirp/drivers/uvb5.py
similarity index 83%
rename from chirp/uvb5.py
rename to chirp/drivers/uvb5.py
index a989372..32976bc 100644
--- a/chirp/uvb5.py
+++ b/chirp/drivers/uvb5.py
@@ -14,13 +14,16 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import struct
+import logging
 from chirp import chirp_common, directory, bitwise, memmap, errors, util
 from chirp.settings import RadioSetting, RadioSettingGroup, \
                 RadioSettingValueBoolean, RadioSettingValueList, \
                 RadioSettingValueInteger, RadioSettingValueString, \
-                RadioSettingValueFloat
+                RadioSettingValueFloat, RadioSettings
 from textwrap import dedent
 
+LOG = logging.getLogger(__name__)
+
 mem_format = """
 struct memory {
   lbcd freq[4];
@@ -169,15 +172,19 @@ struct {
 } test;
 """
 
+
 def do_ident(radio):
     radio.pipe.setTimeout(3)
-    radio.pipe.write("PROGRAM")
-    ack = radio.pipe.read(1)
-    if ack != '\x06':
+    radio.pipe.write("\x05PROGRAM")
+    for x in xrange(10):
+        ack = radio.pipe.read(1)
+        if ack == '\x06':
+            break
+    else:
         raise errors.RadioError("Radio did not ack programming mode")
     radio.pipe.write("\x02")
     ident = radio.pipe.read(8)
-    print util.hexprint(ident)
+    LOG.debug(util.hexprint(ident))
     if not ident.startswith('HKT511'):
         raise errors.RadioError("Unsupported model")
     radio.pipe.write("\x06")
@@ -185,6 +192,7 @@ def do_ident(radio):
     if ack != "\x06":
         raise errors.RadioError("Radio did not ack ident")
 
+
 def do_status(radio, direction, addr):
     status = chirp_common.Status()
     status.msg = "Cloning %s radio" % direction
@@ -192,6 +200,7 @@ def do_status(radio, direction, addr):
     status.max = 0x1000
     radio.status_fn(status)
 
+
 def do_download(radio):
     do_ident(radio)
     data = "KT511 Radio Program data v1.08\x00\x00"
@@ -202,7 +211,7 @@ def do_download(radio):
         radio.pipe.write(frame)
         result = radio.pipe.read(20)
         if frame[1:4] != result[1:4]:
-            print util.hexprint(result)
+            LOG.debug(util.hexprint(result))
             raise errors.RadioError("Invalid response for address 0x%04x" % i)
         radio.pipe.write("\x06")
         ack = radio.pipe.read(1)
@@ -210,14 +219,15 @@ def do_download(radio):
             firstack = ack
         else:
             if not ack == firstack:
-                print "first ack:", util.hexprint(firstack), \
-                    "ack received:", util.hexprint(ack)
+                LOG.debug("first ack: %s ack received: %s",
+                          util.hexprint(firstack), util.hexprint(ack))
                 raise errors.RadioError("Unexpected response")
         data += result[4:]
         do_status(radio, "from", i)
 
     return memmap.MemoryMap(data)
 
+
 def do_upload(radio):
     do_ident(radio)
     data = radio._mmap[0x0030:]
@@ -241,9 +251,10 @@ SPECIALS = {
 POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1),
                 chirp_common.PowerLevel("High", watts=5)]
 
+
 @directory.register
 class BaofengUVB5(chirp_common.CloneModeRadio,
-        chirp_common.ExperimentalRadio):
+                  chirp_common.ExperimentalRadio):
     """Baofeng UV-B5"""
     VENDOR = "Baofeng"
     MODEL = "UV-B5"
@@ -254,12 +265,13 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
     @classmethod
     def get_prompts(cls):
         rp = chirp_common.RadioPrompts()
-        rp.experimental = ('This version of the UV-B5 driver allows you to '
-                'modify the Test Mode settings of your radio. This has been '
-                'tested and reports from other users indicate that it is a '
-                'safe thing to do. However, modifications to these values may '
-                'have unintended consequences, including damage to your '
-                'device. You have been warned. Proceed at your own risk!')
+        rp.experimental = \
+            ('This version of the UV-B5 driver allows you to '
+             'modify the Test Mode settings of your radio. This has been '
+             'tested and reports from other users indicate that it is a '
+             'safe thing to do. However, modifications to these values may '
+             'have unintended consequences, including damage to your '
+             'device. You have been warned. Proceed at your own risk!')
         rp.pre_download = _(dedent("""\
             1. Turn radio off.
             2. Connect cable to mic/spkr connector.
@@ -289,7 +301,8 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
         rf.valid_skips = ["", "S"]
         rf.valid_characters = CHARSET
         rf.valid_name_length = 5
-        rf.valid_bands = [(136000000, 174000000),
+        rf.valid_bands = [(130000000, 175000000),
+                          (220000000, 269000000),
                           (400000000, 520000000)]
         rf.valid_modes = ["FM", "NFM"]
         rf.valid_special_chans = SPECIALS.keys()
@@ -392,8 +405,8 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
         mem.power = POWER_LEVELS[_mem.highpower]
 
         if mem.freq == mem.offset and mem.duplex == "-":
-           mem.duplex = "off"
-           mem.offset = 0
+            mem.duplex = "off"
+            mem.offset = 0
 
         if _nam:
             for char in _nam:
@@ -466,20 +479,11 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
         for setting in mem.extra:
             setattr(_mem, setting.get_name(), setting.value)
 
-    def validate_memory(self, mem):
-        msgs = chirp_common.CloneModeRadio.validate_memory(self, mem)
-
-        if (mem.duplex == "split" and abs(mem.freq - mem.offset) > 69995000) \
-                or (mem.duplex in ["+", "-"] and mem.offset > 69995000) :
-            msgs.append(chirp_common.ValidationError(
-                    "Max split is 69.995MHz"))
-        return msgs
-
-
     def get_settings(self):
         _settings = self._memobj.settings
         basic = RadioSettingGroup("basic", "Basic Settings")
-        group = RadioSettingGroup("top", "All Settings", basic)
+
+        group = RadioSettings(basic)
 
         options = ["Time", "Carrier", "Search"]
         rs = RadioSetting("scantype", "Scan Type",
@@ -489,50 +493,52 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
 
         options = ["Off"] + ["%s min" % x for x in range(1, 8)]
         rs = RadioSetting("timeout", "Time Out Timer",
-                          RadioSettingValueList(options,
-                                                options[_settings.timeout]))
+                          RadioSettingValueList(
+                              options, options[_settings.timeout]))
         basic.append(rs)
 
         options = ["A", "B"]
         rs = RadioSetting("freqmode_ab", "Frequency Mode",
-                          RadioSettingValueList(options,
-                                               options[_settings.freqmode_ab]))
+                          RadioSettingValueList(
+                              options, options[_settings.freqmode_ab]))
         basic.append(rs)
 
         options = ["Frequency Mode", "Channel Mode"]
         rs = RadioSetting("workmode_a", "Radio Work Mode(A)",
-                          RadioSettingValueList(options,
-                                                options[_settings.workmode_a]))
+                          RadioSettingValueList(
+                              options, options[_settings.workmode_a]))
         basic.append(rs)
 
         rs = RadioSetting("workmode_b", "Radio Work Mode(B)",
-                          RadioSettingValueList(options,
-                                                options[_settings.workmode_b]))
+                          RadioSettingValueList(
+                              options, options[_settings.workmode_b]))
         basic.append(rs)
 
         options = ["Frequency", "Name", "Channel"]
         rs = RadioSetting("mdf_a", "Display Format(F1)",
-                          RadioSettingValueList(options,
-                                                options[_settings.mdf_a]))
+                          RadioSettingValueList(
+                              options, options[_settings.mdf_a]))
         basic.append(rs)
 
         rs = RadioSetting("mdf_b", "Display Format(F2)",
-                          RadioSettingValueList(options,
-                                                options[_settings.mdf_b]))
+                          RadioSettingValueList(
+                              options, options[_settings.mdf_b]))
         basic.append(rs)
 
         rs = RadioSetting("mem_chan_a", "Mem Channel (A)",
-              RadioSettingValueInteger(1, 99, _settings.mem_chan_a))
+                          RadioSettingValueInteger(
+                              1, 99, _settings.mem_chan_a))
         basic.append(rs)
 
         rs = RadioSetting("mem_chan_b", "Mem Channel (B)",
-              RadioSettingValueInteger(1, 99, _settings.mem_chan_b))
+                          RadioSettingValueInteger(
+                              1, 99, _settings.mem_chan_b))
         basic.append(rs)
 
         options = ["Off", "BOT", "EOT", "Both"]
         rs = RadioSetting("pttid", "PTT-ID",
-                          RadioSettingValueList(options,
-                                                options[_settings.pttid]))
+                          RadioSettingValueList(
+                              options, options[_settings.pttid]))
         basic.append(rs)
 
         dtmfchars = "0123456789ABCD*#"
@@ -541,6 +547,7 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
         val = RadioSettingValueString(0, 6, _code, False)
         val.set_charset(dtmfchars)
         rs = RadioSetting("pttid.code", "PTT-ID Code", val)
+
         def apply_code(setting, obj):
             code = []
             for j in range(0, 6):
@@ -562,8 +569,8 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
 
         options = ["Frequency Mode", "Channel Mode"]
         rs = RadioSetting("workmode_fm", "FM Work Mode",
-                          RadioSettingValueList(options,
-                                                options[_settings.workmode_fm]))
+                          RadioSettingValueList(
+                              options, options[_settings.workmode_fm]))
         basic.append(rs)
 
         options = ["Current Frequency", "F1 Frequency", "F2 Frequency"]
@@ -591,7 +598,7 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
         basic.append(rs)
 
         rs = RadioSetting("save_funct", "Save Mode",
-                          RadioSettingValueBoolean( _settings.save_funct))
+                          RadioSettingValueBoolean(_settings.save_funct))
         basic.append(rs)
 
         rs = RadioSetting("fm", "FM Function",
@@ -617,7 +624,8 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
 
         _limit = int(self._memobj.limits.lower_vhf) / 10
         rs = RadioSetting("limits.lower_vhf", "VHF Lower Limit (MHz)",
-                          RadioSettingValueInteger(136, 174, _limit))
+                          RadioSettingValueInteger(128, 270, _limit))
+
         def apply_limit(setting, obj):
             value = int(setting.value) * 10
             obj.lower_vhf = value
@@ -626,7 +634,8 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
 
         _limit = int(self._memobj.limits.upper_vhf) / 10
         rs = RadioSetting("limits.upper_vhf", "VHF Upper Limit (MHz)",
-                          RadioSettingValueInteger(136, 174, _limit))
+                          RadioSettingValueInteger(128, 270, _limit))
+
         def apply_limit(setting, obj):
             value = int(setting.value) * 10
             obj.upper_vhf = value
@@ -636,6 +645,7 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
         _limit = int(self._memobj.limits.lower_uhf) / 10
         rs = RadioSetting("limits.lower_uhf", "UHF Lower Limit (MHz)",
                           RadioSettingValueInteger(400, 520, _limit))
+
         def apply_limit(setting, obj):
             value = int(setting.value) * 10
             obj.lower_uhf = value
@@ -645,6 +655,7 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
         _limit = int(self._memobj.limits.upper_uhf) / 10
         rs = RadioSetting("limits.upper_uhf", "UHF Upper Limit (MHz)",
                           RadioSettingValueInteger(400, 520, _limit))
+
         def apply_limit(setting, obj):
             value = int(setting.value) * 10
             obj.upper_uhf = value
@@ -662,8 +673,8 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
                 used = False
                 preset = 65
             rs = RadioSetting("fm_presets_%1i" % i, "FM Preset %i" % (i + 1),
-                          RadioSettingValueBoolean(used),
-                          RadioSettingValueFloat(65, 108, preset, 0.1, 1))
+                              RadioSettingValueBoolean(used),
+                              RadioSettingValueFloat(65, 108, preset, 0.1, 1))
             fm_preset.append(rs)
 
         testmode = RadioSettingGroup("testmode", "Test Mode Settings")
@@ -676,33 +687,39 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
         powernamedata = ["Hi", "Lo"]
         powerkeydata = ["hipwr", "lopwr"]
 
-        for power in range (0, 2):
+        for power in range(0, 2):
             for index in range(0, 8):
                 key = "test.vhf%s%i" % (powerkeydata[power], index)
                 name = "%s Mhz %s Power" % (vhfdata[index],
                                             powernamedata[power])
-                rs = RadioSetting(key, name, RadioSettingValueInteger(0, 255,
-                        getattr(self._memobj.test, "vhf%s%i"
-                                % (powerkeydata[power], index))))
+                rs = RadioSetting(
+                        key, name, RadioSettingValueInteger(
+                            0, 255, getattr(
+                                self._memobj.test,
+                                "vhf%s%i" % (powerkeydata[power], index))))
                 testmode.append(rs)
 
-        for power in range (0, 2):
+        for power in range(0, 2):
             for index in range(0, 8):
                 key = "test.uhf%s%i" % (powerkeydata[power], index)
                 name = "%s Mhz %s Power" % (uhfdata[index],
                                             powernamedata[power])
-                rs = RadioSetting(key, name, RadioSettingValueInteger(0, 255,
-                        getattr(self._memobj.test, "uhf%s%i"
-                                % (powerkeydata[power], index))))
+                rs = RadioSetting(
+                        key, name, RadioSettingValueInteger(
+                            0, 255, getattr(
+                                self._memobj.test,
+                                "uhf%s%i" % (powerkeydata[power], index))))
                 testmode.append(rs)
 
         for band in ["vhf", "uhf"]:
             for index in range(0, 10):
                 key = "test.%ssquelch%i" % (band, index)
                 name = "%s Squelch %i" % (band.upper(), index)
-                rs = RadioSetting(key, name, RadioSettingValueInteger(0, 255,
-                        getattr(self._memobj.test, "%ssquelch%i"
-                                % (band, index))))
+                rs = RadioSetting(
+                        key, name, RadioSettingValueInteger(
+                            0, 255, getattr(
+                                self._memobj.test,
+                                "%ssquelch%i" % (band, index))))
                 testmode.append(rs)
 
         return group
@@ -711,7 +728,7 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
         _settings = self._memobj.settings
         for element in settings:
             if not isinstance(element, RadioSetting):
-                if element.get_name() == "fm_preset" :
+                if element.get_name() == "fm_preset":
                     self._set_fm_preset(element)
                 else:
                     self.set_settings(element)
@@ -735,21 +752,17 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
                         setting = element.get_name()
 
                     if element.has_apply_callback():
-                        print "Using apply callback"
+                        LOG.debug("Using apply callback")
                         element.run_apply_callback()
                     elif setting == "beep_tone_disabled":
-                        val = not _settings.beep_tone_disabled
-                        print "Setting %s = %s" % (setting, val)
-                        setattr(obj, setting, val)
+                        setattr(obj, setting, not int(element.value))
                     elif setting == "ste_disabled":
-                        val = not _settings.ste_disabled
-                        print "Setting %s = %s" % (setting, val)
-                        setattr(obj, setting, val)
+                        setattr(obj, setting, not int(element.value))
                     else:
-                        print "Setting %s = %s" % (setting, element.value)
+                        LOG.debug("Setting %s = %s" % (setting, element.value))
                         setattr(obj, setting, element.value)
                 except Exception, e:
-                    print element.get_name()
+                    LOG.debug(element.get_name())
                     raise
 
     def _set_fm_preset(self, settings):
@@ -761,14 +774,13 @@ class BaofengUVB5(chirp_common.CloneModeRadio,
                     value = int(val[1].get_value() * 10 - 650)
                 else:
                     value = 0x01AF
-                print "Setting fm_presets[%1i] = %s" % (index, value)
+                LOG.debug("Setting fm_presets[%1i] = %s" % (index, value))
                 setting = self._memobj.fm_presets
                 setting[index] = value
             except Exception, e:
-                print element.get_name()
+                LOG.debug(element.get_name())
                 raise
 
-
     @classmethod
     def match_model(cls, filedata, filename):
         return (filedata.startswith("KT511 Radio Program data") and
diff --git a/chirp/drivers/vx170.py b/chirp/drivers/vx170.py
new file mode 100644
index 0000000..b5aaa26
--- /dev/null
+++ b/chirp/drivers/vx170.py
@@ -0,0 +1,132 @@
+# Copyright 2014 Jens Jensen <af5mi at yahoo.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.drivers import yaesu_clone, ft7800
+from chirp import chirp_common, directory, memmap, bitwise, errors
+from textwrap import dedent
+import time
+import os
+
+MEM_FORMAT = """
+#seekto 0x018A;
+struct {
+    u16 in_use;
+} bank_used[24];
+
+#seekto 0x0214;
+u16  banksoff1;
+#seekto 0x0294;
+u16  banksoff2;
+
+#seekto 0x097A;
+struct {
+  u8 name[6];
+} bank_names[24];
+
+#seekto 0x0C0A;
+struct {
+  u16 channels[100];
+} banks[24];
+
+#seekto 0x0168;
+struct {
+  u8 used:1,
+     unknown1:1,
+     mode:1,
+     unknown2:2,
+     duplex:3;
+  bbcd freq[3];
+  u8 clockshift:1,
+     tune_step:3,
+     unknown5:1,
+     tmode:3;
+  bbcd split[3];
+  u8 power:2,
+     tone:6;
+  u8 unknown6:1,
+     dtcs:7;
+  u8 unknown7[2];
+  u8 offset;
+  u8 unknown9[3];
+} memory [200];
+
+#seekto 0x0F28;
+struct {
+  char name[6];
+  u8 enabled:1,
+     unknown1:7;
+  u8 used:1,
+     unknown2:7;
+} names[200];
+
+#seekto 0x1768;
+struct {
+  u8 skip3:2,
+     skip2:2,
+     skip1:2,
+     skip0:2;
+} flags[50];
+"""
+
+
+ at directory.register
+class VX170Radio(ft7800.FTx800Radio):
+    """Yaesu VX-170"""
+    MODEL = "VX-170"
+    _model = "AH022"
+    _memsize = 6057
+    _block_lengths = [8, 6048, 1]
+    _block_size = 32
+
+    POWER_LEVELS_VHF = [chirp_common.PowerLevel("Hi", watts=5.00),
+                        chirp_common.PowerLevel("Med", watts=2.00),
+                        chirp_common.PowerLevel("Lo", watts=0.50)]
+
+    MODES = ["FM", "NFM"]
+    TMODES = ["", "Tone", "TSQL", "DTCS"]
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.pre_download = _(dedent("""\
+1. Turn radio off.
+2. Connect cable to MIC/SP jack.
+3. Press and hold in the [moni] key while turning the radio on.
+4. Select CLONE in menu, then press F. Radio restarts in clone mode.
+     ("CLONE" will appear on the display).
+5. <b>After clicking OK</b>, breifly hold [PTT] key to send image.
+    ("-TX-" will appear on the LCD). """))
+        rp.pre_upload = _(dedent("""\
+1. Turn radio off.
+3. Press and hold in the [moni] key while turning the radio on.
+4. Select CLONE in menu, then press F. Radio restarts in clone mode.
+     ("CLONE" will appear on the display).
+5. Press the [moni] key ("-RX-" will appear on the LCD)."""))
+        return rp
+
+    def _checksums(self):
+        return [yaesu_clone.YaesuChecksum(0x0000, self._memsize - 2)]
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+    def get_features(self):
+        rf = super(VX170Radio, self).get_features()
+        rf.has_bank = False
+        rf.has_bank_names = False
+        rf.valid_modes = self.MODES
+        rf.memory_bounds = (1, 200)
+        rf.valid_bands = [(137000000, 174000000)]
+        return rf
diff --git a/chirp/vx2.py b/chirp/drivers/vx2.py
similarity index 61%
rename from chirp/vx2.py
rename to chirp/drivers/vx2.py
index d68f098..dfec883 100644
--- a/chirp/vx2.py
+++ b/chirp/drivers/vx2.py
@@ -14,16 +14,18 @@
 # 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, yaesu_clone, directory, bitwise
+from chirp.drivers import yaesu_clone
+from chirp import chirp_common, directory, bitwise
 from chirp.settings import RadioSetting, RadioSettingGroup, \
     RadioSettingValueInteger, RadioSettingValueList, \
-    RadioSettingValueBoolean, RadioSettingValueString
-import os, traceback, re
+    RadioSettingValueBoolean, RadioSettingValueString, \
+    RadioSettings
+import os
+import traceback
+import re
+import logging
 
-if os.getenv("CHIRP_DEBUG"):
-    CHIRP_DEBUG = True
-else:
-    CHIRP_DEBUG = False
+LOG = logging.getLogger(__name__)
 
 MEM_FORMAT = """
 #seekto 0x7F52;
@@ -95,7 +97,7 @@ struct {
     u8  mymenu;
     u8  unk14[4];
     u8  emergmode;
-        
+
 } settings;
 
 #seekto 0x0192;
@@ -169,22 +171,24 @@ struct mem_struct vfo[12];
 """
 
 VX2_DUPLEX = ["", "-", "+", "split"]
-VX2_MODES  = ["FM", "AM", "WFM", "Auto", "NFM"] # NFM handled specially in radio
+# NFM handled specially in radio
+VX2_MODES = ["FM", "AM", "WFM", "Auto", "NFM"]
 VX2_TMODES = ["", "Tone", "TSQL", "DTCS"]
 
-VX2_STEPS = [ 5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0, 9.0 ]
+VX2_STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0, 9.0]
 
-CHARSET = list("0123456789" + \
-                   "ABCDEFGHIJKLMNOPQRSTUVWXYZ " + \
-                   "+-/\x00[](){}\x00\x00_" + \
-                   ("\x00" * 13) + "*" + "\x00\x00,'|\x00\x00\x00\x00" + \
-                   ("\x00" * 64))
+CHARSET = list("0123456789" +
+               "ABCDEFGHIJKLMNOPQRSTUVWXYZ " +
+               "+-/\x00[](){}\x00\x00_" +
+               ("\x00" * 13) + "*" + "\x00\x00,'|\x00\x00\x00\x00" +
+               ("\x00" * 64))
 
-DTMFCHARSET = list("0123456789ABCD*#")           
+DTMFCHARSET = list("0123456789ABCD*#")
 
 POWER_LEVELS = [chirp_common.PowerLevel("High", watts=1.50),
                 chirp_common.PowerLevel("Low", watts=0.10)]
 
+
 class VX2BankModel(chirp_common.BankModel):
     """A VX-2 bank model"""
 
@@ -228,7 +232,7 @@ class VX2BankModel(chirp_common.BankModel):
 
         _bank_used = self._radio._memobj.bank_used[bank.index]
         _bank_used.in_use = 0x0000
-        
+
         # also needed for unit to recognize banks?
         self._radio._memobj.banksoff1 = 0x00
         self._radio._memobj.banksoff2 = 0x00
@@ -239,7 +243,7 @@ class VX2BankModel(chirp_common.BankModel):
         try:
             channels_in_bank.remove(memory.number)
         except KeyError:
-            raise Exception("Memory %i is not in bank %s. Cannot remove" % \
+            raise Exception("Memory %i is not in bank %s. Cannot remove" %
                             (memory.number, bank))
         self._update_bank_with_channel_numbers(bank, channels_in_bank)
 
@@ -266,13 +270,14 @@ class VX2BankModel(chirp_common.BankModel):
 def _wipe_memory(mem):
     mem.set_raw("\x00" * (mem.size() / 8))
 
+
 @directory.register
 class VX2Radio(yaesu_clone.YaesuCloneModeRadio):
     """Yaesu VX-2"""
     MODEL = "VX-2"
     _model = "AH015"
     BAUD_RATE = 19200
-    _block_lengths = [ 10, 8, 32577 ]
+    _block_lengths = [10, 8, 32577]
     _memsize = 32595
 
     def get_features(self):
@@ -295,11 +300,11 @@ class VX2Radio(yaesu_clone.YaesuCloneModeRadio):
         return rf
 
     def _checksums(self):
-        return [ yaesu_clone.YaesuChecksum(0x0000, 0x7F51) ]
+        return [yaesu_clone.YaesuChecksum(0x0000, 0x7F51)]
 
     def process_mmap(self):
         self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
-    
+
     def get_raw_memory(self, number):
         return repr(self._memobj.memory[number])
 
@@ -382,9 +387,9 @@ class VX2Radio(yaesu_clone.YaesuCloneModeRadio):
             _mem.txnarrow = False
         _mem.dcs = chirp_common.DTCS_CODES.index(mem.dtcs)
         _mem.tune_step = VX2_STEPS.index(mem.tuning_step)
-        if mem.power == POWER_LEVELS[1]: # Low
+        if mem.power == POWER_LEVELS[1]:  # Low
             _mem.power = 0x00
-        else: # Default to High
+        else:  # Default to High
             _mem.power = 0x03
 
         _flag["%s_pskip" % nibble] = mem.skip == "P"
@@ -394,13 +399,14 @@ class VX2Radio(yaesu_clone.YaesuCloneModeRadio):
             _mem.name[i] = CHARSET.index(mem.name.ljust(6)[i])
         if mem.name.strip():
             # empty name field, disable name display
-            # leftmost bit of name chararr is: 1 = display freq, 0 = display name
+            # leftmost bit of name chararr is:
+            #   1 = display freq, 0 = display name
             _mem.name[0] |= 0x80
-        
+
         # for now, clear unknown fields
-        for i in range(1,7):
+        for i in range(1, 7):
             setattr(_mem, "unknown%i" % i, 0)
-        
+
     def validate_memory(self, mem):
         msgs = yaesu_clone.YaesuCloneModeRadio.validate_memory(self, mem)
         return msgs
@@ -409,20 +415,18 @@ class VX2Radio(yaesu_clone.YaesuCloneModeRadio):
         return VX2BankModel(self)
 
     def _decode_chars(self, inarr):
-        if CHIRP_DEBUG:
-            print "@_decode_chars, type: %s" % type(inarr)
-            print inarr
+        LOG.debug("@_decode_chars, type: %s" % type(inarr))
+        LOG.debug(inarr)
         outstr = ""
         for i in inarr:
             if i == 0xFF:
                 break
             outstr += CHARSET[i & 0x7F]
         return outstr.rstrip()
-            
-    def _encode_chars(self, instr, length = 16):
-        if CHIRP_DEBUG:
-            print "@_encode_chars, type: %s" % type(instr)
-            print instr
+
+    def _encode_chars(self, instr, length=16):
+        LOG.debug("@_encode_chars, type: %s" % type(instr))
+        LOG.debug(instr)
         outarr = []
         instr = str(instr)
         for i in range(0, length):
@@ -431,231 +435,275 @@ class VX2Radio(yaesu_clone.YaesuCloneModeRadio):
             else:
                 outarr.append(0xFF)
         return outarr
-        
+
     def get_settings(self):
         _settings = self._memobj.settings
         basic = RadioSettingGroup("basic", "Basic")
         dtmf = RadioSettingGroup("dtmf", "DTMF")
         arts = RadioSettingGroup("arts", "ARTS")
-        top = RadioSettingGroup("top", "All Settings", basic, arts, dtmf)
-
-        options = [ "off", "30m", "1h", "3h", "5h", "8h" ]
-        rs = RadioSetting("apo", "APO time (hrs)",
-                          RadioSettingValueList(options,
-                                        options[_settings.apo]))
-        basic.append(rs)
-        
-        rs = RadioSetting("ars", "Auto Repeater Shift",
-                        RadioSettingValueBoolean(_settings.ars))
-        basic.append(rs)
-        
-        rs = RadioSetting("att", "Attenuation",
-                        RadioSettingValueBoolean(_settings.att))
-        basic.append(rs)
-        
-        rs = RadioSetting("bclo", "Busy Channel Lockout",
-                        RadioSettingValueBoolean(_settings.bclo))
-        basic.append(rs)
-                          
-        rs = RadioSetting("beep", "Beep",
-                        RadioSettingValueBoolean(_settings.beep))
-        basic.append(rs)
-        
-        options = [ "off", "1", "3", "5", "8", "cont"]
-        rs = RadioSetting("bell", "Bell",
-                          RadioSettingValueList(options,
-                                        options[_settings.bell]))
-        basic.append(rs)                          
-        
-        rs = RadioSetting("busyled", "Busy LED",
-                        RadioSettingValueBoolean(_settings.busyled))
-        basic.append(rs)
-        
-        options = [ "5", "10", "50", "100" ]
-        rs = RadioSetting("chcounter", "Channel Counter (MHz)",
-                          RadioSettingValueList(options,
-                                        options[_settings.chcounter]))
-        basic.append(rs)
-        
-        rs = RadioSetting("dcsrev", "DCS Reverse",
-                        RadioSettingValueBoolean(_settings.dcsrev))
-        basic.append(rs)
-        
-        options = map(str, range(0,12+1))
-        rs = RadioSetting("dimmer", "Dimmer",
-                        RadioSettingValueList(options, options[_settings.dimmer]))
-        basic.append(rs)
-        
-        rs = RadioSetting("edgebeep", "Edge Beep",
-                        RadioSettingValueBoolean(_settings.edgebeep))
-        basic.append(rs)
-        
-        options = [ "beep", "strobe", "bp+str", "beam", "bp+beam", "cw", "bp+cw" ]
-        rs = RadioSetting("emergmode", "Emergency Mode",
-                          RadioSettingValueList(options,
-                                        options[_settings.emergmode]))
-        basic.append(rs)
-        
+        top = RadioSettings(basic, arts, dtmf)
+
+        options = ["off", "30m", "1h", "3h", "5h", "8h"]
+        rs = RadioSetting(
+                "apo", "APO time (hrs)",
+                RadioSettingValueList(options, options[_settings.apo]))
+        basic.append(rs)
+
+        rs = RadioSetting(
+                "ars", "Auto Repeater Shift",
+                RadioSettingValueBoolean(_settings.ars))
+        basic.append(rs)
+
+        rs = RadioSetting(
+                "att", "Attenuation",
+                RadioSettingValueBoolean(_settings.att))
+        basic.append(rs)
+
+        rs = RadioSetting(
+                "bclo", "Busy Channel Lockout",
+                RadioSettingValueBoolean(_settings.bclo))
+        basic.append(rs)
+
+        rs = RadioSetting(
+                "beep", "Beep",
+                RadioSettingValueBoolean(_settings.beep))
+        basic.append(rs)
+
+        options = ["off", "1", "3", "5", "8", "cont"]
+        rs = RadioSetting(
+                "bell", "Bell",
+                RadioSettingValueList(options, options[_settings.bell]))
+        basic.append(rs)
+
+        rs = RadioSetting(
+                "busyled", "Busy LED",
+                RadioSettingValueBoolean(_settings.busyled))
+        basic.append(rs)
+
+        options = ["5", "10", "50", "100"]
+        rs = RadioSetting(
+                "chcounter", "Channel Counter (MHz)",
+                RadioSettingValueList(options, options[_settings.chcounter]))
+        basic.append(rs)
+
+        rs = RadioSetting(
+                "dcsrev", "DCS Reverse",
+                RadioSettingValueBoolean(_settings.dcsrev))
+        basic.append(rs)
+
+        options = map(str, range(0, 12+1))
+        rs = RadioSetting(
+                "dimmer", "Dimmer",
+                RadioSettingValueList(options, options[_settings.dimmer]))
+        basic.append(rs)
+
+        rs = RadioSetting(
+                "edgebeep", "Edge Beep",
+                RadioSettingValueBoolean(_settings.edgebeep))
+        basic.append(rs)
+
+        options = ["beep", "strobe", "bp+str", "beam",
+                   "bp+beam", "cw", "bp+cw"]
+        rs = RadioSetting(
+                "emergmode", "Emergency Mode",
+                RadioSettingValueList(options, options[_settings.emergmode]))
+        basic.append(rs)
+
         options = ["Home", "Reverse"]
-        rs = RadioSetting("hmrv", "HM/RV key",
-                        RadioSettingValueList(options, options[_settings.hmrv]))
+        rs = RadioSetting(
+                "hmrv", "HM/RV key",
+                RadioSettingValueList(options, options[_settings.hmrv]))
         basic.append(rs)
-        
+
         options = ["My Menu", "Internet"]
-        rs = RadioSetting("internet_key", "Internet key",
-                        RadioSettingValueList(options, options[_settings.internet_key]))
-        basic.append(rs)
-        
-        options = ["1 APO","2 AR BEP","3 AR INT","4 ARS","5 ATT","6 BCLO","7 BEEP","8 BELL",
-            "9 BSYLED","10 CH CNT","11 CK SFT","12 CW ID","13 DC VLT","14 DCS CD","15 DCS RV",
-            "16 DIMMER","17 DTMF","18 DTMF S","19 EDG BP","20 EMG S","21 HLFDEV","22 HM/RV",
-            "23 INT MD","24 LAMP","25 LOCK","26 M/T-CL","27 MW MD","28 NAME","29 NM SET",
-            "30 OPNMSG","31 RESUME","32 RF SQL","33 RPT","34 RX MD","35 RXSAVE","36 S SCH",
-            "37 SCNLMP","38 SHIFT","39 SKIP","40 SPLIT","41 SQL","42 SQL TYP","43 STEP",
-            "44 TN FRQ","45 TOT","46 TXSAVE","47 VFO MD", "48 TR SQL (JAPAN)", "48 WX ALT"]
-        
-        rs = RadioSetting("mymenu", "My Menu function",
-                            RadioSettingValueList(options, options[_settings.mymenu - 9]))
-        basic.append(rs)
-        
+        rs = RadioSetting(
+                "internet_key", "Internet key",
+                RadioSettingValueList(
+                    options, options[_settings.internet_key]))
+        basic.append(rs)
+
+        options = ["1 APO", "2 AR BEP", "3 AR INT", "4 ARS", "5 ATT",
+                   "6 BCLO", "7 BEEP", "8 BELL", "9 BSYLED", "10 CH CNT",
+                   "11 CK SFT", "12 CW ID", "13 DC VLT", "14 DCS CD",
+                   "15 DCS RV", "16 DIMMER", "17 DTMF", "18 DTMF S",
+                   "19 EDG BP", "20 EMG S", "21 HLFDEV", "22 HM/RV",
+                   "23 INT MD", "24 LAMP", "25 LOCK", "26 M/T-CL",
+                   "27 MW MD", "28 NAME", "29 NM SET", "30 OPNMSG",
+                   "31 RESUME", "32 RF SQL", "33 RPT", "34 RX MD",
+                   "35 RXSAVE", "36 S SCH", "37 SCNLMP", "38 SHIFT",
+                   "39 SKIP", "40 SPLIT", "41 SQL", "42 SQL TYP",
+                   "43 STEP", "44 TN FRQ", "45 TOT", "46 TXSAVE",
+                   "47 VFO MD", "48 TR SQL (JAPAN)", "48 WX ALT"]
+
+        rs = RadioSetting(
+                "mymenu", "My Menu function",
+                RadioSettingValueList(options, options[_settings.mymenu - 9]))
+        basic.append(rs)
+
         options = ["wires", "link"]
-        rs = RadioSetting("internet_mode", "Internet mode",
-                        RadioSettingValueList(options, options[_settings.internet_mode]))
+        rs = RadioSetting(
+                "internet_mode", "Internet mode",
+                RadioSettingValueList(
+                    options, options[_settings.internet_mode]))
         basic.append(rs)
-        
+
         options = ["key", "cont", "off"]
-        rs = RadioSetting("lamp", "Lamp mode",
-                        RadioSettingValueList(options, options[_settings.lamp]))
+        rs = RadioSetting(
+                "lamp", "Lamp mode",
+                RadioSettingValueList(options, options[_settings.lamp]))
         basic.append(rs)
-        
-        options = ["key", "dial", "key+dial", "ptt", "key+ptt", "dial+ptt", "all"]
-        rs = RadioSetting("lock", "Lock mode",
-                        RadioSettingValueList(options, options[_settings.lock]))
+
+        options = ["key", "dial", "key+dial", "ptt",
+                   "key+ptt", "dial+ptt", "all"]
+        rs = RadioSetting(
+                "lock", "Lock mode",
+                RadioSettingValueList(options, options[_settings.lock]))
         basic.append(rs)
-        
+
         options = ["monitor", "tone call"]
-        rs = RadioSetting("moni_tcall", "MONI key",
-                        RadioSettingValueList(options, options[_settings.moni_tcall]))
+        rs = RadioSetting(
+                "moni_tcall", "MONI key",
+                RadioSettingValueList(options, options[_settings.moni_tcall]))
         basic.append(rs)
-        
+
         options = ["lower", "next"]
-        rs = RadioSetting("mwmode", "Memory write mode",
-                        RadioSettingValueList(options, options[_settings.mwmode]))
+        rs = RadioSetting(
+                "mwmode", "Memory write mode",
+                RadioSettingValueList(options, options[_settings.mwmode]))
         basic.append(rs)
-        
-        options = map(str, range(0,15+1))
-        rs = RadioSetting("nfm_sql", "NFM Sql",
-                        RadioSettingValueList(options, options[_settings.nfm_sql]))
+
+        options = map(str, range(0, 15+1))
+        rs = RadioSetting(
+                "nfm_sql", "NFM Sql",
+                RadioSettingValueList(options, options[_settings.nfm_sql]))
         basic.append(rs)
-        
-        options = map(str, range(0,8+1))
-        rs = RadioSetting("wfm_sql", "WFM Sql",
-                        RadioSettingValueList(options, options[_settings.wfm_sql]))
+
+        options = map(str, range(0, 8+1))
+        rs = RadioSetting(
+                "wfm_sql", "WFM Sql",
+                RadioSettingValueList(options, options[_settings.wfm_sql]))
         basic.append(rs)
-        
+
         options = ["off", "dc", "msg"]
-        rs = RadioSetting("openmsgmode", "Opening message",
-                        RadioSettingValueList(options, options[_settings.openmsgmode]))
+        rs = RadioSetting(
+                "openmsgmode", "Opening message",
+                RadioSettingValueList(options, options[_settings.openmsgmode]))
         basic.append(rs)
-        
-        openmsg = RadioSettingValueString(0, 6, self._decode_chars(_settings.openmsg.get_value()))
+
+        openmsg = RadioSettingValueString(
+                0, 6, self._decode_chars(_settings.openmsg.get_value()))
         openmsg.set_charset(CHARSET)
         rs = RadioSetting("openmsg", "Opening Message", openmsg)
-        basic.append(rs)        
-                
-        options = ["3s","5s","10s","busy","hold"]
-        rs = RadioSetting("resume", "Resume",
-                        RadioSettingValueList(options, options[_settings.resume]))
-        basic.append(rs)
-        
-        options = ["off"] + map(str,range(1,9+1))
-        rs = RadioSetting("rfsql", "RF Sql",
-                 RadioSettingValueList(options,options[_settings.rfsql]))
-        basic.append(rs)
-        
-        options = ["off","200ms","300ms","500ms","1s","2s"]
-        rs = RadioSetting("rxsave", "RX pwr save",
-                        RadioSettingValueList(options, options[_settings.rxsave]))
-        basic.append(rs) 
-         
-        options = ["single","cont"]
-        rs = RadioSetting("smartsearch", "Smart search",
-                        RadioSettingValueList(options, options[_settings.smartsearch]))
-        basic.append(rs)
-        
-        rs = RadioSetting("scan_lamp", "Scan lamp",
-                        RadioSettingValueBoolean(_settings.scan_lamp))
-        basic.append(rs)
-        
-        rs = RadioSetting("split", "Split",
-                        RadioSettingValueBoolean(_settings.split))
-        basic.append(rs)
-        
-        options = ["off","1","3","5","10"]
-        rs = RadioSetting("tot", "TOT (mins)",
-                        RadioSettingValueList(options, options[_settings.tot]))
-        basic.append(rs)
-        
-        rs = RadioSetting("txsave", "TX pwr save",
-                        RadioSettingValueBoolean(_settings.txsave))
-        basic.append(rs)
-        
+        basic.append(rs)
+
+        options = ["3s", "5s", "10s", "busy", "hold"]
+        rs = RadioSetting(
+                "resume", "Resume",
+                RadioSettingValueList(options, options[_settings.resume]))
+        basic.append(rs)
+
+        options = ["off"] + map(str, range(1, 9+1))
+        rs = RadioSetting(
+                "rfsql", "RF Sql",
+                RadioSettingValueList(options, options[_settings.rfsql]))
+        basic.append(rs)
+
+        options = ["off", "200ms", "300ms", "500ms", "1s", "2s"]
+        rs = RadioSetting(
+                "rxsave", "RX pwr save",
+                RadioSettingValueList(options, options[_settings.rxsave]))
+        basic.append(rs)
+
+        options = ["single", "cont"]
+        rs = RadioSetting(
+                "smartsearch", "Smart search",
+                RadioSettingValueList(options, options[_settings.smartsearch]))
+        basic.append(rs)
+
+        rs = RadioSetting(
+                "scan_lamp", "Scan lamp",
+                RadioSettingValueBoolean(_settings.scan_lamp))
+        basic.append(rs)
+
+        rs = RadioSetting(
+                "split", "Split",
+                RadioSettingValueBoolean(_settings.split))
+        basic.append(rs)
+
+        options = ["off", "1", "3", "5", "10"]
+        rs = RadioSetting(
+                "tot", "TOT (mins)",
+                RadioSettingValueList(options, options[_settings.tot]))
+        basic.append(rs)
+
+        rs = RadioSetting(
+                "txsave", "TX pwr save",
+                RadioSettingValueBoolean(_settings.txsave))
+        basic.append(rs)
+
         options = ["all", "band"]
-        rs = RadioSetting("vfomode", "VFO mode",
-                        RadioSettingValueList(options, options[_settings.vfomode]))
-        basic.append(rs)
-        
-        rs = RadioSetting("wx_alert", "WX Alert",
-                        RadioSettingValueBoolean(_settings.wx_alert))
-        basic.append(rs)
-        
-        #todo: priority channel
-        
-        #todo: handle WX ch labels
-        
+        rs = RadioSetting(
+                "vfomode", "VFO mode",
+                RadioSettingValueList(options, options[_settings.vfomode]))
+        basic.append(rs)
+
+        rs = RadioSetting(
+                "wx_alert", "WX Alert",
+                RadioSettingValueBoolean(_settings.wx_alert))
+        basic.append(rs)
+
+        # todo: priority channel
+
+        # todo: handle WX ch labels
+
         # arts settings (ar beep, ar int, cwid en, cwid field)
         options = ["15s", "25s"]
-        rs = RadioSetting("artsinterval", "ARTS Interval",
-                        RadioSettingValueList(options, options[_settings.artsinterval]))
+        rs = RadioSetting(
+                "artsinterval", "ARTS Interval",
+                RadioSettingValueList(
+                    options, options[_settings.artsinterval]))
         arts.append(rs)
-        
+
         options = ["off", "in range", "always"]
-        rs = RadioSetting("artsbeep", "ARTS Beep",
-                        RadioSettingValueList(options, options[_settings.artsbeep]))
+        rs = RadioSetting(
+                "artsbeep", "ARTS Beep",
+                RadioSettingValueList(options, options[_settings.artsbeep]))
         arts.append(rs)
-        
-        rs = RadioSetting("cwid_en", "CWID Enable",
-                        RadioSettingValueBoolean(_settings.cwid_en))
+
+        rs = RadioSetting(
+                "cwid_en", "CWID Enable",
+                RadioSettingValueBoolean(_settings.cwid_en))
         arts.append(rs)
-        
-        cwid = RadioSettingValueString(0, 16, self._decode_chars(_settings.cwid.get_value()))
+
+        cwid = RadioSettingValueString(
+                0, 16, self._decode_chars(_settings.cwid.get_value()))
         cwid.set_charset(CHARSET)
         rs = RadioSetting("cwid", "CWID", cwid)
         arts.append(rs)
-        
+
         # setup dtmf
         options = ["manual", "auto"]
-        rs = RadioSetting("dtmfmode", "DTMF mode",
-                        RadioSettingValueList(options, options[_settings.dtmfmode]))
+        rs = RadioSetting(
+                "dtmfmode", "DTMF mode",
+                RadioSettingValueList(options, options[_settings.dtmfmode]))
         dtmf.append(rs)
-        
-        for i in range(0,8+1):
+
+        for i in range(0, 8+1):
             name = "dtmf" + str(i+1)
             dtmfsetting = self._memobj.dtmf[i]
-            #dtmflen = getattr(_settings, objname + "_len")
+            # dtmflen = getattr(_settings, objname + "_len")
             dtmfstr = ""
             for c in dtmfsetting.digits:
                 if c < len(DTMFCHARSET):
                     dtmfstr += DTMFCHARSET[c]
-            if CHIRP_DEBUG:
-                print dtmfstr
+            LOG.debug(dtmfstr)
             dtmfentry = RadioSettingValueString(0, 16, dtmfstr)
             dtmfentry.set_charset(DTMFCHARSET + list(" "))
             rs = RadioSetting(name, name.upper(), dtmfentry)
-            dtmf.append(rs)            
-        
+            dtmf.append(rs)
+
         return top
-        
+
     def set_settings(self, uisettings):
         for element in uisettings:
             if not isinstance(element, RadioSetting):
@@ -670,13 +718,12 @@ class VX2Radio(yaesu_clone.YaesuCloneModeRadio):
                     # set dtmf fields
                     dtmfstr = str(element.value).strip()
                     newval = []
-                    for i in range(0,16):
+                    for i in range(0, 16):
                         if i < len(dtmfstr):
                             newval.append(DTMFCHARSET.index(dtmfstr[i]))
                         else:
                             newval.append(0xFF)
-                    if CHIRP_DEBUG:
-                        print newval
+                    LOG.debug(newval)
                     idx = int(setting[-1:]) - 1
                     _settings = self._memobj.dtmf[idx]
                     _settings.digits = newval
@@ -684,25 +731,21 @@ class VX2Radio(yaesu_clone.YaesuCloneModeRadio):
                 if setting == "prioritychan":
                     # prioritychan is top-level member, fix 0 index
                     element.value -= 1
-                    _settings = self._memobj   
+                    _settings = self._memobj
                 if setting == "mymenu":
-                    opts = element.value.get_options()                  
+                    opts = element.value.get_options()
                     optsidx = opts.index(element.value.get_value())
-                    idx =  optsidx + 9
+                    idx = optsidx + 9
                     setattr(_settings, "mymenu", idx)
-                    continue            
+                    continue
                 oldval = getattr(_settings, setting)
                 newval = element.value
                 if setting == "cwid":
                     newval = self._encode_chars(newval)
                 if setting == "openmsg":
                     newval = self._encode_chars(newval, 6)
-                if CHIRP_DEBUG:
-                    print "Setting %s(%s) <= %s" % (setting,
-                                    oldval, newval)
+                LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval))
                 setattr(_settings, setting, newval)
             except Exception, e:
-                print element.get_name()
+                LOG.debug(element.get_name())
                 raise
-        
-        
\ No newline at end of file
diff --git a/chirp/vx3.py b/chirp/drivers/vx3.py
similarity index 63%
rename from chirp/vx3.py
rename to chirp/drivers/vx3.py
index fddbce0..b187b79 100644
--- a/chirp/vx3.py
+++ b/chirp/drivers/vx3.py
@@ -14,24 +14,24 @@
 # 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, yaesu_clone, directory
-from chirp import bitwise
+from chirp.drivers import yaesu_clone
+from chirp import chirp_common, directory, bitwise
 from chirp.settings import RadioSetting, RadioSettingGroup, \
     RadioSettingValueInteger, RadioSettingValueList, \
-    RadioSettingValueBoolean, RadioSettingValueString
+    RadioSettingValueBoolean, RadioSettingValueString, \
+    RadioSettings
 from textwrap import dedent
-import os, re
+import os
+import re
+import logging
 
-#interesting offsets which may be checksums needed later
-#0x0393 checksum1?
-#0x0453 checksum1a?
-#0x0409 checksum2?
-#0x04C9 checksum2a?
+LOG = logging.getLogger(__name__)
 
-if os.getenv("CHIRP_DEBUG"):
-    CHIRP_DEBUG = True
-else:
-    CHIRP_DEBUG = False
+# interesting offsets which may be checksums needed later
+# 0x0393 checksum1?
+# 0x0453 checksum1a?
+# 0x0409 checksum2?
+# 0x04C9 checksum2a?
 
 MEM_FORMAT = """
 #seekto 0x7F4A;
@@ -47,7 +47,7 @@ struct {
     u8  unk02;
     u8  apo;
     u8  arts_beep;
-    u8  unk04_1;               
+    u8  unk04_1;
     u8  beep_level;
     u8  beep_mode;
     u8  unk04_2;
@@ -214,33 +214,34 @@ struct {
 } memory[999];
 """
 
-#fix auto mode setting and auto step setting
+# fix auto mode setting and auto step setting
 
 DUPLEX = ["", "-", "+", "split"]
-MODES  = ["FM", "AM", "WFM", "Auto", "NFM"] # NFM handled specially in radio
+MODES = ["FM", "AM", "WFM", "Auto", "NFM"]  # NFM handled specially in radio
 TMODES = ["", "Tone", "TSQL", "DTCS"]
-## TODO: TMODES = ["", "Tone, "TSQL", "DTCS", "Rev Tone", "User Tone", "Pager",
-##          "Message", "D Code", "Tone/DTCS", "DTCS/Tone"]
-
-#still need to verify 9 is correct, and add auto: look at byte 1 and 20
-STEPS = [ 5.0, 9, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0 ]
-#STEPS = list(chirp_common.TUNING_STEPS)
-#STEPS.remove(6.25)
-#STEPS.remove(30.0)
-#STEPS.append(100.0)
-#STEPS.append(9.0) #this fails because 9 is out of order in the list
-
-#Empty char should be 0xFF but right now we are coding in a space
-CHARSET = list("0123456789" + \
-                   "ABCDEFGHIJKLMNOPQRSTUVWXYZ " + \
-                   "+-/\x00[](){}\x00\x00_" + \
-                   ("\x00" * 13) + "*" + "\x00\x00,'|\x00\x00\x00\x00" + \
-                   ("\x00" * 64))
-
-DTMFCHARSET = list("0123456789ABCD*#")           
+# TODO: TMODES = ["", "Tone, "TSQL", "DTCS", "Rev Tone", "User Tone", "Pager",
+#          "Message", "D Code", "Tone/DTCS", "DTCS/Tone"]
+
+# still need to verify 9 is correct, and add auto: look at byte 1 and 20
+STEPS = [5.0, 9, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0]
+# STEPS = list(chirp_common.TUNING_STEPS)
+# STEPS.remove(6.25)
+# STEPS.remove(30.0)
+# STEPS.append(100.0)
+# STEPS.append(9.0) #this fails because 9 is out of order in the list
+
+# Empty char should be 0xFF but right now we are coding in a space
+CHARSET = list("0123456789" +
+               "ABCDEFGHIJKLMNOPQRSTUVWXYZ " +
+               "+-/\x00[](){}\x00\x00_" +
+               ("\x00" * 13) + "*" + "\x00\x00,'|\x00\x00\x00\x00" +
+               ("\x00" * 64))
+
+DTMFCHARSET = list("0123456789ABCD*#")
 POWER_LEVELS = [chirp_common.PowerLevel("High", watts=1.50),
                 chirp_common.PowerLevel("Low", watts=0.10)]
 
+
 class VX3Bank(chirp_common.NamedBank):
     """A VX3 Bank"""
     def get_name(self):
@@ -257,6 +258,7 @@ class VX3Bank(chirp_common.NamedBank):
         _bank = self._model._radio._memobj.bank_names[self.index]
         _bank.name = [CHARSET.index(x) for x in name.ljust(6)[:6]]
 
+
 class VX3BankModel(chirp_common.BankModel):
     """A VX-3 bank model"""
 
@@ -305,7 +307,7 @@ class VX3BankModel(chirp_common.BankModel):
         try:
             channels_in_bank.remove(memory.number)
         except KeyError:
-            raise Exception("Memory %i is not in bank %s. Cannot remove" % \
+            raise Exception("Memory %i is not in bank %s. Cannot remove" %
                             (memory.number, bank))
         self._update_bank_with_channel_numbers(bank, channels_in_bank)
 
@@ -328,15 +330,17 @@ class VX3BankModel(chirp_common.BankModel):
 
         return banks
 
+
 def _wipe_memory(mem):
     mem.set_raw("\x00" * (mem.size() / 8))
-    #the following settings are set to match the defaults
-    #on the radio, some of these fields are unknown
+    # the following settings are set to match the defaults
+    # on the radio, some of these fields are unknown
     mem.name = [0xFF for _i in range(0, 6)]
-    mem.unknown5 = 0x0D #not sure what this is
+    mem.unknown5 = 0x0D  # not sure what this is
     mem.unknown7a = 0b0
     mem.unknown7b = 0b1
-    mem.automode = 0x01 #autoselect mode
+    mem.automode = 0x01  # autoselect mode
+
 
 @directory.register
 class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
@@ -348,29 +352,29 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
     # 41 48 30 32 38
     _model = "AH028"
     _memsize = 32587
-    _block_lengths = [ 10, 32577 ]
-    #right now this reads in 45 seconds and writes in 41 seconds
+    _block_lengths = [10, 32577]
+    # right now this reads in 45 seconds and writes in 41 seconds
     _block_size = 32
 
     @classmethod
     def get_prompts(cls):
         rp = chirp_common.RadioPrompts()
         rp.pre_download = _(dedent("""\
-            1. Turn radio off.
-            2. Connect cable to MIC/SP 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 [BAND] key to send image."""))
+1. Turn radio off.
+2. Connect cable to MIC/SP 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 [BAND] key to send image."""))
         rp.pre_upload = _(dedent("""\
-            1. Turn radio off.
-            2. Connect cable to MIC/SP jack.
-            3. Press and hold in the [F/W] key while turning the radio on
-                 ("CLONE" will appear on the display).
-            4. Press the [V/M] key ("-WAIT-" will appear on the LCD)."""))
+1. Turn radio off.
+2. Connect cable to MIC/SP jack.
+3. Press and hold in the [F/W] key while turning the radio on
+     ("CLONE" will appear on the display).
+4. Press the [V/M] key ("-WAIT-" will appear on the LCD)."""))
         return rp
-        
+
     def _checksums(self):
-        return [ yaesu_clone.YaesuChecksum(0x0000, 0x7F49) ]
+        return [yaesu_clone.YaesuChecksum(0x0000, 0x7F49)]
 
     def process_mmap(self):
         self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
@@ -428,7 +432,7 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
         if _mem.txnarrow and _mem.mode == MODES.index("FM"):
             # FM narrow
             mem.mode = "NFM"
-        else:        
+        else:
             mem.mode = MODES[_mem.mode]
         mem.dtcs = chirp_common.DTCS_CODES[_mem.dcs]
         mem.tuning_step = STEPS[_mem.tune_step]
@@ -447,7 +451,7 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
         _flag = self._memobj.flags[(mem.number-1)/2]
 
         nibble = ((mem.number-1) % 2) and "even" or "odd"
-      
+
         used = _flag["%s_masked" % nibble]
         valid = _flag["%s_valid" % nibble]
 
@@ -477,9 +481,9 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
             _mem.txnarrow = False
         _mem.dcs = chirp_common.DTCS_CODES.index(mem.dtcs)
         _mem.tune_step = STEPS.index(mem.tuning_step)
-        if mem.power == POWER_LEVELS[1]: # Low
+        if mem.power == POWER_LEVELS[1]:  # Low
             _mem.power = 0x00
-        else: # Default to High
+        else:  # Default to High
             _mem.power = 0x03
 
         _flag["%s_pskip" % nibble] = mem.skip == "P"
@@ -489,7 +493,7 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
             _mem.name[i] = CHARSET.index(mem.name.ljust(6)[i])
         if mem.name.strip():
             _mem.name[0] |= 0x80
-      
+
     def validate_memory(self, mem):
         msgs = yaesu_clone.YaesuCloneModeRadio.validate_memory(self, mem)
         return msgs
@@ -498,20 +502,18 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
         return VX3BankModel(self)
 
     def _decode_chars(self, inarr):
-        if CHIRP_DEBUG:
-            print "@_decode_chars, type: %s" % type(inarr)
-            print inarr
+        LOG.debug("@_decode_chars, type: %s" % type(inarr))
+        LOG.debug(inarr)
         outstr = ""
         for i in inarr:
             if i == 0xFF:
                 break
             outstr += CHARSET[i & 0x7F]
         return outstr.rstrip()
-            
-    def _encode_chars(self, instr, length = 16):
-        if CHIRP_DEBUG:
-            print "@_encode_chars, type: %s" % type(instr)
-            print instr
+
+    def _encode_chars(self, instr, length=16):
+        LOG.debug("@_encode_chars, type: %s" % type(instr))
+        LOG.debug(instr)
         outarr = []
         instr = str(instr)
         for i in range(length):
@@ -520,7 +522,7 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
             else:
                 outarr.append(0xFF)
         return outarr
-        
+
     def get_settings(self):
         _settings = self._memobj.settings
         basic = RadioSettingGroup("basic", "Basic")
@@ -529,189 +531,236 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
         arts = RadioSettingGroup("arts", "ARTS")
         eai = RadioSettingGroup("eai", "Emergency")
         msg = RadioSettingGroup("msg", "Messages")
-        top = RadioSettingGroup("top", "All Settings",
-                basic, sound, arts, dtmf, eai, msg)
 
-        basic.append( RadioSetting("att_wx", "Attenuation WX",
+        top = RadioSettings(basic, sound, arts, dtmf, eai, msg)
+
+        basic.append(RadioSetting(
+                "att_wx", "Attenuation WX",
                 RadioSettingValueBoolean(_settings.att_wx)))
 
-        basic.append( RadioSetting("att_marine", "Attenuation Marine",
+        basic.append(RadioSetting(
+                "att_marine", "Attenuation Marine",
                 RadioSettingValueBoolean(_settings.att_marine)))
 
-        basic.append( RadioSetting("att_broadcast", "Attenuation Broadcast",
+        basic.append(RadioSetting(
+                "att_broadcast", "Attenuation Broadcast",
                 RadioSettingValueBoolean(_settings.att_broadcast)))
 
-        basic.append( RadioSetting("ars", "Auto Repeater Shift",
+        basic.append(RadioSetting(
+                "ars", "Auto Repeater Shift",
                 RadioSettingValueBoolean(_settings.ars)))
-        
-        basic.append( RadioSetting("home_vfo", "Home->VFO",
+
+        basic.append(RadioSetting(
+                "home_vfo", "Home->VFO",
                 RadioSettingValueBoolean(_settings.home_vfo)))
- 
-        basic.append( RadioSetting("bclo", "Busy Channel Lockout",
+
+        basic.append(RadioSetting(
+                "bclo", "Busy Channel Lockout",
                 RadioSettingValueBoolean(_settings.bclo)))
 
-        basic.append( RadioSetting("busyled", "Busy LED",
+        basic.append(RadioSetting(
+                "busyled", "Busy LED",
                 RadioSettingValueBoolean(_settings.busy_led)))
 
-        basic.append( RadioSetting("fast_tone_search", "Fast Tone search",
+        basic.append(RadioSetting(
+                "fast_tone_search", "Fast Tone search",
                 RadioSettingValueBoolean(_settings.fast_tone_search)))
 
-        basic.append( RadioSetting("priority_revert", "Priority Revert",
+        basic.append(RadioSetting(
+                "priority_revert", "Priority Revert",
                 RadioSettingValueBoolean(_settings.priority_revert)))
 
-        basic.append( RadioSetting("protect_memory", "Protect memory",
+        basic.append(RadioSetting(
+                "protect_memory", "Protect memory",
                 RadioSettingValueBoolean(_settings.protect_memory)))
 
-        basic.append( RadioSetting("scan_lamp", "Scan Lamp",
+        basic.append(RadioSetting(
+                "scan_lamp", "Scan Lamp",
                 RadioSettingValueBoolean(_settings.scan_lamp)))
 
-        basic.append( RadioSetting("split_tone", "Split tone",
+        basic.append(RadioSetting(
+                "split_tone", "Split tone",
                 RadioSettingValueBoolean(_settings.split_tone)))
 
-        basic.append( RadioSetting("tone_search_mute", "Tone search mute",
+        basic.append(RadioSetting(
+                "tone_search_mute", "Tone search mute",
                 RadioSettingValueBoolean(_settings.tone_search_mute)))
 
-        basic.append( RadioSetting("txsave", "TX save",
+        basic.append(RadioSetting(
+                "txsave", "TX save",
                 RadioSettingValueBoolean(_settings.txsave)))
 
-        basic.append( RadioSetting("wx_alert", "WX Alert",
+        basic.append(RadioSetting(
+                "wx_alert", "WX Alert",
                 RadioSettingValueBoolean(_settings.wx_alert)))
 
         opts = ["Bar Int", "Bar Ext"]
-        basic.append( RadioSetting("am_antenna", "AM antenna",
+        basic.append(RadioSetting(
+                "am_antenna", "AM antenna",
                 RadioSettingValueList(opts, opts[_settings.am_antenna])))
 
         opts = ["Ext Ant", "Earphone"]
-        basic.append( RadioSetting("fm_antenna", "FM antenna",
+        basic.append(RadioSetting(
+                "fm_antenna", "FM antenna",
                 RadioSettingValueList(opts, opts[_settings.fm_antenna])))
 
-        opts = ["off"] + [ "%0.1f" % (t / 60.0) for t in range(30, 750, 30) ]
-        basic.append( RadioSetting("apo", "APO time (hrs)",
+        opts = ["off"] + ["%0.1f" % (t / 60.0) for t in range(30, 750, 30)]
+        basic.append(RadioSetting(
+                "apo", "APO time (hrs)",
                 RadioSettingValueList(opts, opts[_settings.apo])))
-        
+
         opts = ["+/- 5 MHZ", "+/- 10 MHZ", "+/- 50 MHZ", "+/- 100 MHZ"]
-        basic.append( RadioSetting("channel_counter", "Channel counter",
+        basic.append(RadioSetting(
+                "channel_counter", "Channel counter",
                 RadioSettingValueList(opts, opts[_settings.channel_counter])))
 
         opts = ["0.3", "0.5", "0.7", "1.0", "1.5"]
-        basic.append( RadioSetting("fw_key_timer", "FW key timer (s)",
+        basic.append(RadioSetting(
+                "fw_key_timer", "FW key timer (s)",
                 RadioSettingValueList(opts, opts[_settings.fw_key_timer])))
 
         opts = ["Home", "Reverse"]
-        basic.append( RadioSetting("hm_rv", "HM/RV key",
+        basic.append(RadioSetting(
+                "hm_rv", "HM/RV key",
                 RadioSettingValueList(opts, opts[_settings.hm_rv])))
 
-        opts = [ "%d" % t for t in range(2, 11) ] + ["continuous", "off"]
-        basic.append( RadioSetting("lamp", "Lamp Timer (s)",
+        opts = ["%d" % t for t in range(2, 11)] + ["continuous", "off"]
+        basic.append(RadioSetting(
+                "lamp", "Lamp Timer (s)",
                 RadioSettingValueList(opts, opts[_settings.lamp])))
 
-        basic.append( RadioSetting("lock", "Lock",
-                RadioSettingValueBoolean(_settings.lock)))        
+        basic.append(RadioSetting(
+                "lock", "Lock",
+                RadioSettingValueBoolean(_settings.lock)))
 
         opts = ["key", "ptt", "key+ptt"]
-        basic.append( RadioSetting("lock_mode", "Lock mode",
-                RadioSettingValueList(opts, opts[_settings.lock_mode])))        
+        basic.append(RadioSetting(
+                "lock_mode", "Lock mode",
+                RadioSettingValueList(opts, opts[_settings.lock_mode])))
 
         opts = ["10", "20", "50", "100"]
-        basic.append( RadioSetting("mem_ch_step", "Memory Chan step",
+        basic.append(RadioSetting(
+                "mem_ch_step", "Memory Chan step",
                 RadioSettingValueList(opts, opts[_settings.mem_ch_step])))
 
         opts = ["lower", "next"]
-        basic.append( RadioSetting("mem_storage", "Memory storage mode",
+        basic.append(RadioSetting(
+                "mem_storage", "Memory storage mode",
                 RadioSettingValueList(opts, opts[_settings.mem_storage])))
 
-        opts = [ "%d" % t for t in range(1, 10) ]
-        basic.append( RadioSetting("mic_gain", "Mic gain",
+        opts = ["%d" % t for t in range(1, 10)]
+        basic.append(RadioSetting(
+                "mic_gain", "Mic gain",
                 RadioSettingValueList(opts, opts[_settings.mic_gain])))
 
         opts = ["monitor", "tone call"]
-        basic.append( RadioSetting("moni_tcall", "Moni/TCall button",
+        basic.append(RadioSetting(
+                "moni_tcall", "Moni/TCall button",
                 RadioSettingValueList(opts, opts[_settings.moni_tcall])))
 
         opts = ["off"] + \
-                [ "%02d:%02d" % (t / 60, t % 60) for t in range(10, 1450, 10) ]
-        basic.append( RadioSetting("on_timer", "On Timer (hrs)",
+               ["%02d:%02d" % (t / 60, t % 60) for t in range(10, 1450, 10)]
+        basic.append(RadioSetting(
+                "on_timer", "On Timer (hrs)",
                 RadioSettingValueList(opts, opts[_settings.on_timer])))
 
         opts2 = ["off"] + \
-                [ "0.%d" % t for t in range(1, 10) ] + \
-                [ "%1.1f" % (t / 10.0) for t in range(10, 105, 5) ] 
-        basic.append( RadioSetting("priority_time", "Priority time",
+                ["0.%d" % t for t in range(1, 10)] + \
+                ["%1.1f" % (t / 10.0) for t in range(10, 105, 5)]
+        basic.append(RadioSetting(
+                "priority_time", "Priority time",
                 RadioSettingValueList(opts2, opts2[_settings.priority_time])))
-                          
+
         opts = ["off", "20", "50", "100", "200"]
-        basic.append( RadioSetting("ptt_delay", "PTT delay (ms)",
+        basic.append(RadioSetting(
+                "ptt_delay", "PTT delay (ms)",
                 RadioSettingValueList(opts, opts[_settings.ptt_delay])))
 
-        basic.append( RadioSetting("rx_save", "RX save (s)",
+        basic.append(RadioSetting(
+                "rx_save", "RX save (s)",
                 RadioSettingValueList(opts2, opts2[_settings.rx_save])))
 
-        basic.append( RadioSetting("scan_restart", "Scan restart (s)",
+        basic.append(RadioSetting(
+                "scan_restart", "Scan restart (s)",
                 RadioSettingValueList(opts2, opts2[_settings.scan_restart])))
 
-        opts = [ "%1.1f" % (t / 10.0) for t in range(20, 105, 5) ] + \
-                ["busy", "hold"]
-        basic.append( RadioSetting("scan_resume", "Scan resume (s)",
+        opts = ["%1.1f" % (t / 10.0) for t in range(20, 105, 5)] + \
+               ["busy", "hold"]
+        basic.append(RadioSetting(
+                "scan_resume", "Scan resume (s)",
                 RadioSettingValueList(opts, opts[_settings.scan_resume])))
 
         opts = ["single", "continuous"]
-        basic.append( RadioSetting("smart_search", "Smart search",
+        basic.append(RadioSetting(
+                "smart_search", "Smart search",
                 RadioSettingValueList(opts, opts[_settings.smart_search])))
-        
-        opts = ["off"] + [ "TRX %d" % t for t in range(1, 11)] + ["hold"] + \
-                [ "TX %d" % t for t in range(1, 11)]
-        basic.append( RadioSetting("sub_rx_timer", "Sub RX timer",
+
+        opts = ["off"] + ["TRX %d" % t for t in range(1, 11)] + ["hold"] + \
+               ["TX %d" % t for t in range(1, 11)]
+        basic.append(RadioSetting(
+                "sub_rx_timer", "Sub RX timer",
                 RadioSettingValueList(opts, opts[_settings.sub_rx_timer])))
 
         opts = ["C", "F"]
-        basic.append( RadioSetting("temp_unit", "Temperature unit",
+        basic.append(RadioSetting(
+                "temp_unit", "Temperature unit",
                 RadioSettingValueList(opts, opts[_settings.temp_unit])))
 
-        opts = ["off"] + [ "%1.1f" % (t / 10.0) for t in range(5, 105, 5) ]
-        basic.append( RadioSetting("tot", "Time-out timer (mins)",
+        opts = ["off"] + ["%1.1f" % (t / 10.0) for t in range(5, 105, 5)]
+        basic.append(RadioSetting(
+                "tot", "Time-out timer (mins)",
                 RadioSettingValueList(opts, opts[_settings.tot])))
 
         opts = ["all", "band"]
-        basic.append( RadioSetting("vfo_mode", "VFO mode",
+        basic.append(RadioSetting(
+                "vfo_mode", "VFO mode",
                 RadioSettingValueList(opts, opts[_settings.vfo_mode])))
 
-        opts = ["off"] + [ "%d" % t for t in range(5, 65, 5) ] + ["EAI"]
-        basic.append( RadioSetting("wake_up", "Wake up (s)",
+        opts = ["off"] + ["%d" % t for t in range(5, 65, 5)] + ["EAI"]
+        basic.append(RadioSetting(
+                "wake_up", "Wake up (s)",
                 RadioSettingValueList(opts, opts[_settings.wake_up])))
 
         opts = ["hold", "3 secs"]
-        basic.append( RadioSetting("vol_key_mode", "Volume key mode",
+        basic.append(RadioSetting(
+                "vol_key_mode", "Volume key mode",
                 RadioSettingValueList(opts, opts[_settings.vol_key_mode])))
-        
+
         # subgroup programmable keys
 
         opts = ["INTNET", "INT MR", "Set Mode (my key)"]
-        basic.append( RadioSetting("internet_key", "Internet key",
+        basic.append(RadioSetting(
+                "internet_key", "Internet key",
                 RadioSettingValueList(opts, opts[_settings.internet_key])))
 
-        keys = [ "Antenna AM", "Antenna FM", "Antenna Attenuator", "Auto Power Off",
-            "Auto Repeater Shift", "ARTS Beep", "ARTS Interval", "Busy Channel Lockout",
-            "Bell Ringer", "Bell Select", "Bank Name", "Band Edge Beep", "Beep Level",
-            "Beep Select", "Beep User", "Busy LED", "Channel Counter", "Clock Shift",
-            "CW ID", "CW Learning", "CW Pitch", "CW Training", "DC Voltage",
-            "DCS Code", "DCS Reverse", "DTMF A/M", "DTMF Delay", "DTMF Set",
-            "DTMF Speed", "EAI Timer", "Emergency Alarm", "Ext Menu", "FW Key",
-            "Half Deviation", "Home/Reverse", "Home > VFO", "INT Code",
-            "INT Conn Mode", "INT A/M", "INT Set", "INT Key", "INTNET", "Lamp",
-            "LED Light", "Lock", "Moni/T-Call", "Mic Gain", "Memory Display",
-            "Memory Write Mode", "Memory Channel Step", "Memory Name Write", 
-            "Memory Protect", "Memory Skip", "Message List", "Message Reg", 
-            "Message Set", "On Timer", "Open Message", "Pager Answer Back", 
-            "Pager Receive Code", "Pager Transmit Code", "Pager Frequency", 
-            "Priority Revert", "Priority Timer", "Password", "PTT Delay", 
-            "Repeater Shift Direction", "Repeater Shift", "Receive Mode", 
-            "Smart Search", "Save Rx", "Save Tx", "Scan Lamp", "Scan Resume", 
-            "Scan Restart", "Speaker Out", "Squelch Level", "Squelch Type", 
-            "Squelch S Meter", "Squelch Split Tone", "Step", "Stereo", "Sub Rx", 
-            "Temp", "Tone Frequency", "Time Out Timer", "Tone Search Mute", 
-            "Tone Search Speed", "VFO Band", "VFO Skip", "Volume Mode", "Wake Up", 
-            "Weather Alert" ]
-        rs = RadioSetting("my_key", "My key",
+        keys = ["Antenna AM", "Antenna FM", "Antenna Attenuator",
+                "Auto Power Off", "Auto Repeater Shift", "ARTS Beep",
+                "ARTS Interval", "Busy Channel Lockout", "Bell Ringer",
+                "Bell Select", "Bank Name", "Band Edge Beep", "Beep Level",
+                "Beep Select", "Beep User", "Busy LED", "Channel Counter",
+                "Clock Shift", "CW ID", "CW Learning", "CW Pitch",
+                "CW Training", "DC Voltage", "DCS Code", "DCS Reverse",
+                "DTMF A/M", "DTMF Delay", "DTMF Set", "DTMF Speed",
+                "EAI Timer", "Emergency Alarm", "Ext Menu", "FW Key",
+                "Half Deviation", "Home/Reverse", "Home > VFO", "INT Code",
+                "INT Conn Mode", "INT A/M", "INT Set", "INT Key", "INTNET",
+                "Lamp", "LED Light", "Lock", "Moni/T-Call", "Mic Gain",
+                "Memory Display", "Memory Write Mode", "Memory Channel Step",
+                "Memory Name Write", "Memory Protect", "Memory Skip",
+                "Message List", "Message Reg", "Message Set", "On Timer",
+                "Open Message", "Pager Answer Back", "Pager Receive Code",
+                "Pager Transmit Code", "Pager Frequency", "Priority Revert",
+                "Priority Timer", "Password", "PTT Delay",
+                "Repeater Shift Direction", "Repeater Shift", "Receive Mode",
+                "Smart Search", "Save Rx", "Save Tx", "Scan Lamp",
+                "Scan Resume", "Scan Restart", "Speaker Out",
+                "Squelch Level", "Squelch Type", "Squelch S Meter",
+                "Squelch Split Tone", "Step", "Stereo", "Sub Rx", "Temp",
+                "Tone Frequency", "Time Out Timer", "Tone Search Mute",
+                "Tone Search Speed", "VFO Band", "VFO Skip", "Volume Mode",
+                "Wake Up", "Weather Alert"]
+        rs = RadioSetting(
+                "my_key", "My key",
                 RadioSettingValueList(keys, keys[_settings.my_key - 16]))
         # TODO: fix keys list isnt exactly right order
         # leave disabled in settings for now
@@ -719,63 +768,79 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
 
         # sound tab
 
-        sound.append( RadioSetting("band_edge_beep", "Band edge beep",
-                RadioSettingValueBoolean(_settings.band_edge_beep)))   
+        sound.append(RadioSetting(
+                "band_edge_beep", "Band edge beep",
+                RadioSettingValueBoolean(_settings.band_edge_beep)))
 
         opts = ["off", "key+scan", "key"]
-        sound.append( RadioSetting("beep_mode", "Beep mode",
+        sound.append(RadioSetting(
+                "beep_mode", "Beep mode",
                 RadioSettingValueList(opts, opts[_settings.beep_mode])))
 
         _volumes = self._memobj.volumes
 
-        opts = map(str, range(0,33))
-        sound.append( RadioSetting("speaker_vol", "Speaker volume",
+        opts = map(str, range(0, 33))
+        sound.append(RadioSetting(
+                "speaker_vol", "Speaker volume",
                 RadioSettingValueList(opts, opts[_volumes.speaker])))
 
-        sound.append( RadioSetting("earphone_vol", "Earphone volume",
-                RadioSettingValueList(opts, opts[_volumes.earphone])))    
+        sound.append(RadioSetting(
+                "earphone_vol", "Earphone volume",
+                RadioSettingValueList(opts, opts[_volumes.earphone])))
 
         opts = ["auto", "speaker"]
-        sound.append( RadioSetting("fm_speaker_out", "FM Speaker out",
+        sound.append(RadioSetting(
+                "fm_speaker_out", "FM Speaker out",
                 RadioSettingValueList(opts, opts[_settings.fm_speaker_out])))
 
         opts = ["mono", "stereo"]
-        sound.append( RadioSetting("fm_broadcast_mode", "FM broadcast mode",
-                RadioSettingValueList(opts, opts[_settings.fm_broadcast_mode])))
+        sound.append(RadioSetting(
+                "fm_broadcast_mode", "FM broadcast mode",
+                RadioSettingValueList(
+                    opts, opts[_settings.fm_broadcast_mode])))
 
         opts = map(str, range(16))
-        sound.append( RadioSetting("sql_fm", "Squelch level (FM)",
+        sound.append(RadioSetting(
+                "sql_fm", "Squelch level (FM)",
                 RadioSettingValueList(opts, opts[_settings.sql_fm])))
 
         opts = map(str, range(9))
-        sound.append( RadioSetting("sql_wfm", "Squelch level (WFM)",
+        sound.append(RadioSetting(
+                "sql_wfm", "Squelch level (WFM)",
                 RadioSettingValueList(opts, opts[_settings.sql_wfm])))
 
         opts = map(str, range(16))
-        sound.append( RadioSetting("radio_am_sql", "Squelch level (Broadcast Radio AM)",
+        sound.append(RadioSetting(
+                "radio_am_sql", "Squelch level (Broadcast Radio AM)",
                 RadioSettingValueList(opts, opts[_settings.radio_am_sql])))
 
         opts = map(str, range(9))
-        sound.append( RadioSetting("radio_fm_sql", "Squelch level (Broadcast Radio FM)",
-                RadioSettingValueList(opts, opts[_settings.radio_fm_sql])))                        
+        sound.append(RadioSetting(
+                "radio_fm_sql", "Squelch level (Broadcast Radio FM)",
+                RadioSettingValueList(opts, opts[_settings.radio_fm_sql])))
 
         # dtmf tab
 
         opts = ["manual", "auto"]
-        dtmf.append( RadioSetting("dtmf_autodialer", "DTMF autodialer mode",
+        dtmf.append(RadioSetting(
+                "dtmf_autodialer", "DTMF autodialer mode",
                 RadioSettingValueList(opts, opts[_settings.dtmf_autodialer])))
 
         opts = ["50", "250", "450", "750", "1000"]
-        dtmf.append( RadioSetting("dtmf_delay", "DTMF delay (ms)",
+        dtmf.append(RadioSetting(
+                "dtmf_delay", "DTMF delay (ms)",
                 RadioSettingValueList(opts, opts[_settings.dtmf_delay])))
 
         opts = ["50", "100"]
-        dtmf.append( RadioSetting("dtmf_speed", "DTMF speed (ms)",
+        dtmf.append(RadioSetting(
+                "dtmf_speed", "DTMF speed (ms)",
                 RadioSettingValueList(opts, opts[_settings.dtmf_speed])))
 
         opts = map(str, range(10))
-        dtmf.append( RadioSetting("dtmf_chan_active", "DTMF active",
-                RadioSettingValueList(opts, opts[_settings.dtmf_chan_active])))
+        dtmf.append(RadioSetting(
+                "dtmf_chan_active", "DTMF active",
+                RadioSettingValueList(
+                    opts, opts[_settings.dtmf_chan_active])))
 
         for i in range(10):
             name = "dtmf" + str(i)
@@ -784,61 +849,71 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
             for c in dtmfsetting.memory:
                 if c < len(DTMFCHARSET):
                     dtmfstr += DTMFCHARSET[c]
-            if CHIRP_DEBUG:
-                print dtmfstr
+            LOG.debug(dtmfstr)
             dtmfentry = RadioSettingValueString(0, 16, dtmfstr)
             dtmfentry.set_charset(DTMFCHARSET + list(" "))
             rs = RadioSetting(name, name.upper(), dtmfentry)
-            dtmf.append(rs) 
+            dtmf.append(rs)
 
         # arts tab
-        arts.append( RadioSetting("arts", "ARTS",
-                RadioSettingValueBoolean(_settings.arts)))           
+        arts.append(RadioSetting(
+                "arts", "ARTS",
+                RadioSettingValueBoolean(_settings.arts)))
 
         opts = ["off", "in range", "always"]
-        arts.append( RadioSetting("arts_beep", "ARTS beep",
+        arts.append(RadioSetting(
+                "arts_beep", "ARTS beep",
                 RadioSettingValueList(opts, opts[_settings.arts_beep])))
 
         opts = ["15", "25"]
-        arts.append( RadioSetting("arts_interval", "ARTS interval",
+        arts.append(RadioSetting(
+                "arts_interval", "ARTS interval",
                 RadioSettingValueList(opts, opts[_settings.arts_interval])))
 
-        arts.append( RadioSetting("arts_cwid_enable", "CW ID",
-                RadioSettingValueBoolean(_settings.arts_cwid_enable)))           
+        arts.append(RadioSetting(
+                "arts_cwid_enable", "CW ID",
+                RadioSettingValueBoolean(_settings.arts_cwid_enable)))
 
-        cwid = RadioSettingValueString(0, 16, 
-                self._decode_chars(_settings.arts_cwid.get_value()))
+        cwid = RadioSettingValueString(
+                0, 16, self._decode_chars(_settings.arts_cwid.get_value()))
         cwid.set_charset(CHARSET)
-        arts.append( RadioSetting("arts_cwid", "CW ID", cwid ))
+        arts.append(RadioSetting("arts_cwid", "CW ID", cwid))
 
         # EAI tab
-        
-        eai.append( RadioSetting("emergency_eai", "EAI",
+
+        eai.append(RadioSetting(
+                "emergency_eai", "EAI",
                 RadioSettingValueBoolean(_settings.emergency_eai)))
 
-        opts = [ "interval %dm" % t for t in range(1, 10) ] + \
-                [ "interval %dm" % t for t in range(10, 55, 5) ] + \
-                [ "continuous %dm" % t for t in range(1, 10) ] + \
-                [ "continuous %dm" % t for t in range(10, 55, 5) ]
+        opts = ["interval %dm" % t for t in range(1, 10)] + \
+               ["interval %dm" % t for t in range(10, 55, 5)] + \
+               ["continuous %dm" % t for t in range(1, 10)] + \
+               ["continuous %dm" % t for t in range(10, 55, 5)]
 
-        eai.append( RadioSetting("emergency_eai_time", "EAI time",
-                RadioSettingValueList(opts, opts[_settings.emergency_eai_time]))) 
+        eai.append(RadioSetting(
+                "emergency_eai_time", "EAI time",
+                RadioSettingValueList(
+                    opts, opts[_settings.emergency_eai_time])))
 
-        opts = ["beep", "strobe", "beep+strobe", "beam", "beep+beam", "cw", "beep+cw", "cwt"]
-        eai.append( RadioSetting("emergency_signal", "emergency signal",
-                RadioSettingValueList(opts, opts[_settings.emergency_signal]))) 
+        opts = ["beep", "strobe", "beep+strobe", "beam",
+                "beep+beam", "cw", "beep+cw", "cwt"]
+        eai.append(RadioSetting(
+                "emergency_signal", "emergency signal",
+                RadioSettingValueList(
+                    opts, opts[_settings.emergency_signal])))
 
         # msg tab
 
         opts = ["off", "dc voltage", "message"]
-        msg.append( RadioSetting("openmsg_mode", "Opening message mode",
+        msg.append(RadioSetting(
+                "openmsg_mode", "Opening message mode",
                 RadioSettingValueList(opts, opts[_settings.openmsg_mode])))
 
-        openmsg = RadioSettingValueString(0, 6, 
-                self._decode_chars(_settings.openmsg.get_value()))
+        openmsg = RadioSettingValueString(
+                0, 6, self._decode_chars(_settings.openmsg.get_value()))
         openmsg.set_charset(CHARSET)
-        msg.append( RadioSetting("openmsg", "Opening Message", openmsg ))              
-        
+        msg.append(RadioSetting("openmsg", "Opening Message", openmsg))
+
         return top
 
     def set_settings(self, uisettings):
@@ -855,27 +930,26 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
                     # set dtmf fields
                     dtmfstr = str(element.value).strip()
                     newval = []
-                    for i in range(0,16):
+                    for i in range(0, 16):
                         if i < len(dtmfstr):
                             newval.append(DTMFCHARSET.index(dtmfstr[i]))
                         else:
                             newval.append(0xFF)
-                    if CHIRP_DEBUG:
-                        print newval
+                    LOG.debug(newval)
                     idx = int(setting[-1:])
                     _settings = self._memobj.dtmf[idx]
                     _settings.memory = newval
                     continue
                 if re.match('.*_vol$', setting):
                     # volume fields
-                    voltype = re.sub('_vol$','', setting)
+                    voltype = re.sub('_vol$', '', setting)
                     setattr(self._memobj.volumes, voltype, element.value)
                     continue
                 if setting == "my_key":
                     # my_key is memory is off by 9 from list, beware hacks!
                     opts = element.value.get_options()
                     optsidx = opts.index(element.value.get_value())
-                    idx =  optsidx + 16
+                    idx = optsidx + 16
                     setattr(_settings, "my_key", idx)
                     continue
                 oldval = getattr(_settings, setting)
@@ -884,11 +958,8 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
                     newval = self._encode_chars(newval)
                 if setting == "openmsg":
                     newval = self._encode_chars(newval, 6)
-                if CHIRP_DEBUG:
-                    print "Setting %s(%s) <= %s" % (setting,
-                                    oldval, newval)
+                LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval))
                 setattr(_settings, setting, newval)
             except Exception, e:
-                print element.get_name()
+                LOG.debug(element.get_name())
                 raise
-        
\ No newline at end of file
diff --git a/chirp/vx5.py b/chirp/drivers/vx5.py
similarity index 94%
rename from chirp/vx5.py
rename to chirp/drivers/vx5.py
index 65a5ba4..aa6d4bd 100644
--- a/chirp/vx5.py
+++ b/chirp/drivers/vx5.py
@@ -14,8 +14,8 @@
 # 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, yaesu_clone, directory, errors
-from chirp import bitwise
+from chirp.drivers import yaesu_clone
+from chirp import chirp_common, directory, errors, bitwise
 
 MEM_FORMAT = """
 #seekto 0x002A;
@@ -81,6 +81,7 @@ POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00),
                 chirp_common.PowerLevel("L2", watts=1.00),
                 chirp_common.PowerLevel("L1", watts=0.05)]
 
+
 class VX5BankModel(chirp_common.BankModel):
     def get_num_mappings(self):
         return 5
@@ -98,7 +99,8 @@ class VX5BankModel(chirp_common.BankModel):
         _bank_used = self._radio._memobj.bank_used[bank.index]
         for i in range(0, len(_members)):
             if _members[i].status == 0xFF:
-                #print "empty found, inserting %d at %d" % (memory.number, i)
+                # LOG.debug("empty found, inserting %d at %d" %
+                #           (memory.number, i))
                 if self._radio._memobj.current_bank == 0xFF:
                     self._radio._memobj.current_bank = bank.index
                 _members[i].status = 0x00
@@ -151,6 +153,7 @@ class VX5BankModel(chirp_common.BankModel):
                     banks.append(bank)
         return banks
 
+
 @directory.register
 class VX5Radio(yaesu_clone.YaesuCloneModeRadio):
     """Yaesu VX-5"""
@@ -164,7 +167,7 @@ class VX5Radio(yaesu_clone.YaesuCloneModeRadio):
     _block_size = 8
 
     def _checksums(self):
-        return [ yaesu_clone.YaesuChecksum(0x0000, 0x1FB9) ]
+        return [yaesu_clone.YaesuChecksum(0x0000, 0x1FB9)]
 
     def get_features(self):
         rf = chirp_common.RadioFeatures()
@@ -176,8 +179,8 @@ class VX5Radio(yaesu_clone.YaesuCloneModeRadio):
         rf.valid_tmodes = TMODES
         rf.valid_duplexes = DUPLEX
         rf.memory_bounds = (1, 220)
-        rf.valid_bands = [(   500000,  16000000),
-                          ( 48000000, 729000000),
+        rf.valid_bands = [(500000,    16000000),
+                          (48000000,  729000000),
                           (800000000, 999000000)]
         rf.valid_skips = ["", "S", "P"]
         rf.valid_power_levels = POWER_LEVELS
@@ -213,7 +216,7 @@ class VX5Radio(yaesu_clone.YaesuCloneModeRadio):
         mem.tuning_step = STEPS[_mem.tuning_step]
         mem.offset = int(_mem.offset) * 1000
         mem.power = POWER_LEVELS[3 - _mem.power]
-        mem.tmode = TMODES[_mem.tmode & 0x3] # masked so bad mems can be read
+        mem.tmode = TMODES[_mem.tmode & 0x3]  # masked so bad mems can be read
         if mem.duplex == "split":
             mem.offset = chirp_common.fix_rounded_step(mem.offset)
         mem.rtone = mem.ctone = chirp_common.OLD_TONES[_mem.tone]
@@ -226,7 +229,7 @@ class VX5Radio(yaesu_clone.YaesuCloneModeRadio):
     def set_memory(self, mem):
         _mem = self._memobj.memory[mem.number-1]
         _flg = self._memobj.flag[mem.number-1]
-        
+
         # initialize new channel to safe defaults
         if not mem.empty and not _flg.used:
             _flg.used = True
@@ -234,11 +237,11 @@ class VX5Radio(yaesu_clone.YaesuCloneModeRadio):
             _mem.unknown2 = 0x00
             _mem.unknown3 = 0x00
             _mem.unknown4 = 0x00
-            _mem.icon = 12 # file cabinet icon
+            _mem.icon = 12  # file cabinet icon
             _mem.unknown7 = 0x00
             _mem.unknown8 = 0x00
             _mem.unknown9 = 0x00
-            
+
         if mem.empty and _flg.used and not _flg.visible:
             _flg.used = False
             return
diff --git a/chirp/vx510.py b/chirp/drivers/vx510.py
similarity index 95%
rename from chirp/vx510.py
rename to chirp/drivers/vx510.py
index 7f287a9..49570e2 100644
--- a/chirp/vx510.py
+++ b/chirp/drivers/vx510.py
@@ -13,8 +13,8 @@
 # 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, yaesu_clone, directory
-from chirp import bitwise
+from chirp.drivers import yaesu_clone
+from chirp import chirp_common, directory, bitwise
 
 # This driver is unfinished and therefore does not register itself with Chirp.
 #
@@ -162,9 +162,11 @@ class VX510Radio(yaesu_clone.YaesuCloneModeRadio):
 
         if tmode_tx == "Tone" and not tmode_rx:
             mem.tmode = "Tone"
-        elif tmode_tx == tmode_rx and tmode_tx == "Tone" and mem.rtone == mem.ctone:
+        elif tmode_tx == tmode_rx and tmode_tx == "Tone" and \
+                mem.rtone == mem.ctone:
             mem.tmode = "TSQL"
-        elif tmode_tx == tmode_rx and tmode_tx == "DTCS" and mem.dtcs == mem.rx_dtcs:
+        elif tmode_tx == tmode_rx and tmode_tx == "DTCS" and \
+                mem.dtcs == mem.rx_dtcs:
             mem.tmode = "DTCS"
         elif tmode_rx or tmode_tx:
             mem.tmode = "Cross"
diff --git a/chirp/vx6.py b/chirp/drivers/vx6.py
similarity index 89%
rename from chirp/vx6.py
rename to chirp/drivers/vx6.py
index 0cdb115..d37a94a 100644
--- a/chirp/vx6.py
+++ b/chirp/drivers/vx6.py
@@ -13,8 +13,8 @@
 # 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, yaesu_clone, directory
-from chirp import bitwise
+from chirp.drivers import yaesu_clone
+from chirp import chirp_common, directory, bitwise
 from textwrap import dedent
 
 # flags.{even|odd}_pskip: These are actually "preferential *scan* channels".
@@ -93,10 +93,10 @@ struct {
 """
 
 DUPLEX = ["", "-", "+", "split"]
-MODES  = ["FM", "AM", "WFM", "FM"] # last is auto
+MODES = ["FM", "AM", "WFM", "FM"]  # last is auto
 TMODES = ["", "Tone", "TSQL", "DTCS"]
-STEPS =  [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0,
-          9.0, 200.0, 5.0]  # last is auto, 9.0k and 200.0k are unadvertised
+STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0,
+         9.0, 200.0, 5.0]  # last is auto, 9.0k and 200.0k are unadvertised
 
 
 CHARSET = ["%i" % int(x) for x in range(0, 10)] + \
@@ -109,10 +109,11 @@ POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00),
                 chirp_common.PowerLevel("L2", watts=1.00),
                 chirp_common.PowerLevel("L1", watts=0.30)]
 POWER_LEVELS_220 = [chirp_common.PowerLevel("Hi", watts=1.50),
-                chirp_common.PowerLevel("L3", watts=1.00),
-                chirp_common.PowerLevel("L2", watts=0.50),
-                chirp_common.PowerLevel("L1", watts=0.20)]
-                
+                    chirp_common.PowerLevel("L3", watts=1.00),
+                    chirp_common.PowerLevel("L2", watts=0.50),
+                    chirp_common.PowerLevel("L1", watts=0.20)]
+
+
 class VX6Bank(chirp_common.NamedBank):
     """A VX6 Bank"""
     def get_name(self):
@@ -129,6 +130,7 @@ class VX6Bank(chirp_common.NamedBank):
         _bank = self._model._radio._memobj.bank_names[self.index]
         _bank.name = [CHARSET.index(x) for x in name.ljust(6)[:6]]
 
+
 class VX6BankModel(chirp_common.BankModel):
     """A VX-6 bank model"""
 
@@ -170,25 +172,25 @@ class VX6BankModel(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 # enable
+        _bank_used.in_use = 0x0000  # enable
 
         # also needed for unit to recognize any banks?
         self._radio._memobj.banksoff1 = 0x0000
         self._radio._memobj.banksoff2 = 0x0000
         # TODO: turn back off (0xFFFF) when all banks are empty?
-        
+
     def remove_memory_from_mapping(self, memory, bank):
         channels_in_bank = self._get_channel_numbers_in_bank(bank)
         try:
             channels_in_bank.remove(memory.number)
         except KeyError:
-            raise Exception("Memory %i is not in bank %s. Cannot remove" % \
+            raise Exception("Memory %i is not in bank %s. Cannot remove" %
                             (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.in_use = 0xFFFF # disable bank
+            _bank_used.in_use = 0xFFFF  # disable bank
 
     def get_mapping_memories(self, bank):
         memories = []
@@ -205,6 +207,7 @@ class VX6BankModel(chirp_common.BankModel):
 
         return banks
 
+
 @directory.register
 class VX6Radio(yaesu_clone.YaesuCloneModeRadio):
     """Yaesu VX-6"""
@@ -221,21 +224,21 @@ class VX6Radio(yaesu_clone.YaesuCloneModeRadio):
     def get_prompts(cls):
         rp = chirp_common.RadioPrompts()
         rp.pre_download = _(dedent("""\
-            1. Turn radio off.
-            2. Connect cable to MIC/SP 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 [BAND] key to send image."""))
+1. Turn radio off.
+2. Connect cable to MIC/SP 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 [BAND] key to send image."""))
         rp.pre_upload = _(dedent("""\
-            1. Turn radio off.
-            2. Connect cable to MIC/SP jack.
-            3. Press and hold in the [F/W] key while turning the radio on
-                 ("CLONE" will appear on the display).
-            4. Press the [V/M] key ("-WAIT-" will appear on the LCD)."""))
+1. Turn radio off.
+2. Connect cable to MIC/SP jack.
+3. Press and hold in the [F/W] key while turning the radio on
+     ("CLONE" will appear on the display).
+4. Press the [V/M] key ("-WAIT-" will appear on the LCD)."""))
         return rp
-        
+
     def _checksums(self):
-        return [ yaesu_clone.YaesuChecksum(0x0000, 0x7F49) ]
+        return [yaesu_clone.YaesuChecksum(0x0000, 0x7F49)]
 
     def process_mmap(self):
         self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
@@ -293,7 +296,7 @@ class VX6Radio(yaesu_clone.YaesuCloneModeRadio):
         mem.dtcs = chirp_common.DTCS_CODES[_mem.dcs & 0x7f]
         mem.tuning_step = STEPS[_mem.tune_step]
         mem.skip = pskip and "P" or skip and "S" or ""
-        
+
         if mem.freq > 220000000 and mem.freq < 225000000:
             mem.power = POWER_LEVELS_220[3 - _mem.power]
         else:
@@ -364,5 +367,3 @@ class VX6Radio(yaesu_clone.YaesuCloneModeRadio):
 
     def get_bank_model(self):
         return VX6BankModel(self)
-
-
diff --git a/chirp/vx7.py b/chirp/drivers/vx7.py
similarity index 89%
rename from chirp/vx7.py
rename to chirp/drivers/vx7.py
index 5fff4b1..1d7b11a 100644
--- a/chirp/vx7.py
+++ b/chirp/drivers/vx7.py
@@ -13,9 +13,12 @@
 # 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, yaesu_clone, directory
-from chirp import bitwise
+from chirp.drivers import yaesu_clone
+from chirp import chirp_common, directory, bitwise
 from textwrap import dedent
+import logging
+
+LOG = logging.getLogger(__name__)
 
 MEM_FORMAT = """
 #seekto 0x0611;
@@ -75,7 +78,7 @@ struct {
 """
 
 DUPLEX = ["", "-", "+", "split"]
-MODES  = ["FM", "AM", "WFM", "Auto"]
+MODES = ["FM", "AM", "WFM", "Auto"]
 TMODES = ["", "Tone", "TSQL", "DTCS", "Cross"]
 CROSS_MODES = ["DTCS->", "Tone->DTCS", "DTCS->Tone"]
 STEPS = list(chirp_common.TUNING_STEPS)
@@ -99,9 +102,11 @@ POWER_LEVELS = [chirp_common.PowerLevel("L1", watts=0.05),
 POWER_LEVELS_220 = [chirp_common.PowerLevel("L1", watts=0.05),
                     chirp_common.PowerLevel("L2", watts=0.30)]
 
+
 def _is220(freq):
     return freq >= 222000000 and freq <= 225000000
 
+
 class VX7BankModel(chirp_common.BankModel):
     """A VX-7 Bank model"""
     def get_num_mappings(self):
@@ -140,7 +145,7 @@ class VX7BankModel(chirp_common.BankModel):
         if not found:
             raise Exception("Memory {num} not in " +
                             "bank {bank}".format(num=memory.number,
-                                                    bank=bank))
+                                                 bank=bank))
         if not remaining_members:
             _bank_used.in_use = 0xFFFF
 
@@ -167,11 +172,13 @@ class VX7BankModel(chirp_common.BankModel):
                 banks.append(bank)
         return banks
 
+
 def _wipe_memory(mem):
     mem.set_raw("\x00" * (mem.size() / 8))
     mem.unknown1 = 0x05
     mem.ones = 0x03
 
+
 @directory.register
 class VX7Radio(yaesu_clone.YaesuCloneModeRadio):
     """Yaesu VX-7"""
@@ -181,31 +188,31 @@ class VX7Radio(yaesu_clone.YaesuCloneModeRadio):
 
     _model = ""
     _memsize = 16211
-    _block_lengths = [ 10, 8, 16193 ]
+    _block_lengths = [10, 8, 16193]
     _block_size = 8
 
     @classmethod
     def get_prompts(cls):
         rp = chirp_common.RadioPrompts()
         rp.pre_download = _(dedent("""\
-            1. Turn radio off.
-            2. Connect cable to MIC/SP jack.
-            3. Press and hold in the [MON-F] key while turning the radio on
-                 ("CLONE" will appear on the display).
-            4. <b>After clicking OK</b>, press the [BAND] key to send image."""))
+1. Turn radio off.
+2. Connect cable to MIC/SP jack.
+3. Press and hold in the [MON-F] key while turning the radio on
+     ("CLONE" will appear on the display).
+4. <b>After clicking OK</b>, press the [BAND] key to send image."""))
         rp.pre_upload = _(dedent("""\
-            1. Turn radio off.
-            2. Connect cable to MIC/SP jack.
-            3. Press and hold in the [MON-F] key while turning the radio on
-                 ("CLONE" will appear on the display).
-            4. Press the [V/M] key ("CLONE WAIT" will appear on the LCD)."""))
+1. Turn radio off.
+2. Connect cable to MIC/SP jack.
+3. Press and hold in the [MON-F] key while turning the radio on
+     ("CLONE" will appear on the display).
+4. Press the [V/M] key ("CLONE WAIT" will appear on the LCD)."""))
         return rp
-        
+
     def _checksums(self):
-        return [ yaesu_clone.YaesuChecksum(0x0592, 0x0610),
-                 yaesu_clone.YaesuChecksum(0x0612, 0x0690),
-                 yaesu_clone.YaesuChecksum(0x0000, 0x3F51),
-                 ]
+        return [yaesu_clone.YaesuChecksum(0x0592, 0x0610),
+                yaesu_clone.YaesuChecksum(0x0612, 0x0690),
+                yaesu_clone.YaesuChecksum(0x0000, 0x3F51),
+                ]
 
     def process_mmap(self):
         self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
@@ -276,8 +283,8 @@ class VX7Radio(yaesu_clone.YaesuCloneModeRadio):
         try:
             mem.power = levels[_mem.power]
         except IndexError:
-            print "Radio reported invalid power level %s (in %s)" % (
-                _mem.power, levels)
+            LOG.error("Radio reported invalid power level %s (in %s)" %
+                      (_mem.power, levels))
             mem.power = levels[0]
 
         for i in _mem.name:
@@ -293,7 +300,7 @@ class VX7Radio(yaesu_clone.YaesuCloneModeRadio):
         _flag = self._memobj.flags[(mem.number-1)/2]
 
         nibble = ((mem.number-1) % 2) and "even" or "odd"
-        
+
         valid = _flag["%s_valid" % nibble]
         used = _flag["%s_masked" % nibble]
 
@@ -339,14 +346,14 @@ class VX7Radio(yaesu_clone.YaesuCloneModeRadio):
 
         for i in range(0, 8):
             _mem.name[i] = CHARSET.index(mem.name.ljust(8)[i])
-        
+
     def validate_memory(self, mem):
         msgs = yaesu_clone.YaesuCloneModeRadio.validate_memory(self, mem)
 
         if _is220(mem.freq):
             if str(mem.power) not in [str(l) for l in POWER_LEVELS_220]:
-                msgs.append(chirp_common.ValidationError(\
-                        "Power level %s not supported on 220MHz band" % \
+                msgs.append(chirp_common.ValidationError(
+                        "Power level %s not supported on 220MHz band" %
                             mem.power))
 
         return msgs
diff --git a/chirp/vx8.py b/chirp/drivers/vx8.py
similarity index 72%
rename from chirp/vx8.py
rename to chirp/drivers/vx8.py
index ceae189..d65e77c 100644
--- a/chirp/vx8.py
+++ b/chirp/drivers/vx8.py
@@ -15,21 +15,72 @@
 
 import os
 import re
+import logging
 
-from chirp import chirp_common, yaesu_clone, directory
-from chirp import bitwise
-from chirp.settings import RadioSettingGroup, RadioSetting
+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 textwrap import dedent
 
+LOG = logging.getLogger(__name__)
+
 MEM_FORMAT = """
+#seekto 0x047f;
+struct {
+  u8 flag;
+  u16 unknown;
+  struct {
+    u8 padded_yaesu[16];
+  } message;
+} opening_message;
+
+#seekto 0x049a;
+struct {
+  u8 vfo_a;
+  u8 vfo_b;
+} squelch;
+
+#seekto 0x04bf;
+struct {
+  u8 beep;
+} beep_select;
+
+#seekto 0x04cc;
+struct {
+  u8 lcd_dimmer;
+  u8 dtmf_delay;
+  u8 unknown0[3];
+  u8 unknown1:4
+     lcd_contrast:4;
+  u8 lamp;
+  u8 unknown2[7];
+  u8 scan_restart;
+  u8 unknown3;
+  u8 scan_resume;
+  u8 unknown4[6];
+  u8 tot;
+  u8 unknown5[3];
+  u8 unknown6:2,
+     scan_lamp:1,
+     unknown7:2,
+     dtmf_speed:1,
+     unknown8:1,
+     dtmf_mode:1;
+  u8 busy_led:1,
+     unknown9:7;
+  u8 unknown10[2];
+  u8 vol_mode:1,
+     unknown11:7;
+} scan_settings;
+
 #seekto 0x54a;
 struct {
     u16 in_use;
 } bank_used[24];
 
-#seekto 0x64a;
+#seekto 0x064a;
 struct {
   u8 unknown0[4];
   u8 frequency_band;
@@ -73,6 +124,11 @@ struct {
   u8 checksum;
 } vfo_info[6];
 
+#seekto 0x094a;
+struct {
+  u8 memory[16];
+} dtmf[10];
+
 #seekto 0x135A;
 struct {
   u8 unknown[2];
@@ -270,16 +326,17 @@ u8 checksum;
 
 TMODES = ["", "Tone", "TSQL", "DTCS"]
 DUPLEX = ["", "-", "+", "split"]
-MODES  = ["FM", "AM", "WFM"]
+MODES = ["FM", "AM", "WFM"]
 STEPS = list(chirp_common.TUNING_STEPS)
 STEPS.remove(30.0)
 STEPS.append(100.0)
-STEPS.insert(2, 0.0) # There is a skipped tuning step at index 2 (?)
+STEPS.insert(2, 0.0)  # There is a skipped tuning step at index 2 (?)
 SKIPS = ["", "S", "P"]
+VX8_DTMF_CHARS = list("0123456789ABCD*#-")
 
 CHARSET = ["%i" % int(x) for x in range(0, 10)] + \
     [chr(x) for x in range(ord("A"), ord("Z")+1)] + \
-    [" ",] + \
+    [" "] + \
     [chr(x) for x in range(ord("a"), ord("z")+1)] + \
     list(".,:;*#_-/&()@!?^ ") + list("\x00" * 100)
 
@@ -288,13 +345,14 @@ POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00),
                 chirp_common.PowerLevel("L2", watts=1.00),
                 chirp_common.PowerLevel("L1", watts=0.05)]
 
+
 class VX8Bank(chirp_common.NamedBank):
     """A VX-8 bank"""
 
     def get_name(self):
         _bank = self._model._radio._memobj.bank_info[self.index]
         _bank_used = self._model._radio._memobj.bank_used[self.index]
-                      
+
         name = ""
         for i in _bank.name:
             if i == 0xFF:
@@ -306,6 +364,7 @@ class VX8Bank(chirp_common.NamedBank):
         _bank = self._model._radio._memobj.bank_info[self.index]
         _bank.name = [CHARSET.index(x) for x in name.ljust(16)[:16]]
 
+
 class VX8BankModel(chirp_common.BankModel):
     """A VX-8 bank model"""
     def __init__(self, radio, name='Banks'):
@@ -351,28 +410,28 @@ class VX8BankModel(chirp_common.BankModel):
                 break
 
         for vfo_index in (0, 1):
-          # 3 VFO info structs are stored as 3 pairs of (master, backup)
-          vfo = self._radio._memobj.vfo_info[vfo_index * 2]
-          vfo_bak = self._radio._memobj.vfo_info[(vfo_index * 2) + 1]
-
-          if vfo.checksum != vfo_bak.checksum:
-              print "Warning: VFO settings are inconsistent with backup"
-          else:
-              if ((chosen_bank[vfo_index] is None) and
-                  (vfo.bank_index != 0xFFFF)):
-                  print "Disabling banks for VFO %d" % vfo_index
-                  vfo.bank_index = 0xFFFF
-                  vfo.mr_index = 0xFFFF
-                  vfo.bank_enable = 0xFFFF
-              elif ((chosen_bank[vfo_index] is not None) and
-                  (vfo.bank_index == 0xFFFF)):
-                  print "Enabling banks for VFO %d" % vfo_index
-                  vfo.bank_index = chosen_bank[vfo_index]
-                  vfo.mr_index = chosen_mr[vfo_index]
-                  vfo.bank_enable = 0x0000
-              vfo_bak.bank_index = vfo.bank_index
-              vfo_bak.mr_index = vfo.mr_index
-              vfo_bak.bank_enable = vfo.bank_enable
+            # 3 VFO info structs are stored as 3 pairs of (master, backup)
+            vfo = self._radio._memobj.vfo_info[vfo_index * 2]
+            vfo_bak = self._radio._memobj.vfo_info[(vfo_index * 2) + 1]
+
+            if vfo.checksum != vfo_bak.checksum:
+                LOG.warn("VFO settings are inconsistent with backup")
+            else:
+                if ((chosen_bank[vfo_index] is None) and
+                        (vfo.bank_index != 0xFFFF)):
+                    LOG.info("Disabling banks for VFO %d" % vfo_index)
+                    vfo.bank_index = 0xFFFF
+                    vfo.mr_index = 0xFFFF
+                    vfo.bank_enable = 0xFFFF
+                elif ((chosen_bank[vfo_index] is not None) and
+                        (vfo.bank_index == 0xFFFF)):
+                    LOG.debug("Enabling banks for VFO %d" % vfo_index)
+                    vfo.bank_index = chosen_bank[vfo_index]
+                    vfo.mr_index = chosen_mr[vfo_index]
+                    vfo.bank_enable = 0x0000
+                vfo_bak.bank_index = vfo.bank_index
+                vfo_bak.mr_index = vfo.mr_index
+                vfo_bak.bank_enable = vfo.bank_enable
 
     def _update_bank_with_channel_numbers(self, bank, channels_in_bank):
         _members = self._radio._memobj.bank_members[bank.index]
@@ -401,7 +460,7 @@ class VX8BankModel(chirp_common.BankModel):
         try:
             channels_in_bank.remove(memory.number)
         except KeyError:
-            raise Exception("Memory %i is not in bank %s. Cannot remove" % \
+            raise Exception("Memory %i is not in bank %s. Cannot remove" %
                             (memory.number, bank))
         self._update_bank_with_channel_numbers(bank, channels_in_bank)
 
@@ -426,10 +485,12 @@ class VX8BankModel(chirp_common.BankModel):
 
         return banks
 
+
 def _wipe_memory(mem):
     mem.set_raw("\x00" * (mem.size() / 8))
     mem.unknown1 = 0x05
 
+
 @directory.register
 class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
     """Yaesu VX-8"""
@@ -440,13 +501,13 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
 
     _model = "AH029"
     _memsize = 65227
-    _block_lengths = [ 10, 65217 ]
+    _block_lengths = [10, 65217]
     _block_size = 32
-    _mem_params = (0xC24A, # APRS beacon metadata address.
-                   40,     # Number of beacons stored.
-                   0xC60A, # APRS beacon content address.
-                   194,    # Length of beacon data stored.
-                   40)     # Number of beacons stored.
+    _mem_params = (0xC24A,  # APRS beacon metadata address.
+                   40,      # Number of beacons stored.
+                   0xC60A,  # APRS beacon content address.
+                   194,     # Length of beacon data stored.
+                   40)      # Number of beacons stored.
     _has_vibrate = False
     _has_af_dual = True
 
@@ -454,19 +515,19 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
     def get_prompts(cls):
         rp = chirp_common.RadioPrompts()
         rp.pre_download = _(dedent("""\
-            1. Turn radio off.
-            2. Connect cable to DATA jack.
-            3. Press and hold in the [FW] key while turning the radio on
-                 ("CLONE" will appear on the display).
-            4. <b>After clicking OK</b>, press the [BAND] key to send image."""))
+1. Turn radio off.
+2. Connect cable to DATA jack.
+3. Press and hold in the [FW] key while turning the radio on
+     ("CLONE" will appear on the display).
+4. <b>After clicking OK</b>, press the [BAND] key to send image."""))
         rp.pre_upload = _(dedent("""\
-            1. Turn radio off.
-            2. Connect cable to DATA jack.
-            3. Press and hold in the [FW] key while turning the radio on
-                 ("CLONE" will appear on the display).
-            4. Press the [MODE] key ("-WAIT-" will appear on the LCD)."""))
+1. Turn radio off.
+2. Connect cable to DATA jack.
+3. Press and hold in the [FW] key while turning the radio on
+     ("CLONE" will appear on the display).
+4. Press the [MODE] key ("-WAIT-" will appear on the LCD)."""))
         return rp
-        
+
     def process_mmap(self):
         self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
 
@@ -492,11 +553,11 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
         return repr(self._memobj.memory[number])
 
     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) ]
+        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)]
 
     @staticmethod
     def _add_ff_pad(val, length):
@@ -564,9 +625,9 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
         if mem.freq < 30000000 or \
                 (mem.freq > 88000000 and mem.freq < 108000000) or \
                 mem.freq > 580000000:
-            flag.nosubvfo = True  # Masked from VFO B
+            flag.nosubvfo = True   # Masked from VFO B
         else:
-            flag.nosubvfo = False # Available in both VFOs
+            flag.nosubvfo = False  # Available in both VFOs
 
         _mem.freq = int(mem.freq / 1000)
         _mem.offset = int(mem.offset / 1000)
@@ -593,19 +654,20 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
     def get_bank_model(self):
         return VX8BankModel(self)
 
+
 @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.
+    _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]*)")
+                        "(?P<m>[\d]*)[\s\']*(?P<s>[\d]*)")
 
     _RX_BAUD = ("off", "1200 baud", "9600 baud")
     _TX_DELAY = ("100ms", "150ms", "200ms", "250ms", "300ms",
@@ -650,6 +712,24 @@ class VX8DRadio(VX8Radio):
               "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):
@@ -737,8 +817,8 @@ class VX8DRadio(VX8Radio):
         menu = RadioSettingGroup("aprs_general", "APRS General")
         aprs = self._memobj.aprs
 
-        val = RadioSettingValueString(0, 6,
-            str(aprs.my_callsign.callsign).rstrip("\xFF"))
+        val = RadioSettingValueString(
+                0, 6, str(aprs.my_callsign.callsign).rstrip("\xFF"))
         rs = RadioSetting("aprs.my_callsign.callsign", "My Callsign", val)
         rs.set_apply_callback(self.apply_callsign, aprs.my_callsign)
         menu.append(rs)
@@ -757,8 +837,8 @@ class VX8DRadio(VX8Radio):
         symbols = list(chirp_common.APRS_SYMBOLS)
         selected = aprs.custom_symbol
         if aprs.custom_symbol >= len(chirp_common.APRS_SYMBOLS):
-          symbols.append("%d" % aprs.custom_symbol)
-          selected = len(symbols) - 1
+            symbols.append("%d" % aprs.custom_symbol)
+            selected = len(symbols) - 1
         val = RadioSettingValueList(symbols, symbols[selected])
         rs = RadioSetting("aprs.custom_symbol_text", "User Selected Symbol",
                           val)
@@ -787,8 +867,8 @@ class VX8DRadio(VX8Radio):
         # TODO: Rebuild this when latitude/longitude change.
         # TODO: Add saved positions p1 - p10 to memory map.
         position_str = list(self._POSITIONS)
-        #position_str[1] = "%s %s" % (latitude, longitude)
-        #position_str[2] = "%s %s" % (latitude, longitude)
+        # position_str[1] = "%s %s" % (latitude, longitude)
+        # position_str[2] = "%s %s" % (latitude, longitude)
         val = RadioSettingValueList(position_str,
                                     position_str[aprs.selected_position])
         rs = RadioSetting("aprs.selected_position", "My Position", val)
@@ -804,8 +884,8 @@ class VX8DRadio(VX8Radio):
         rs.set_apply_callback(self.apply_lat_long, aprs)
         menu.append(rs)
 
-        val = RadioSettingValueList(self._TIME_SOURCE,
-            self._TIME_SOURCE[aprs.set_time_manually])
+        val = RadioSettingValueList(
+                self._TIME_SOURCE, self._TIME_SOURCE[aprs.set_time_manually])
         rs = RadioSetting("aprs.set_time_manually", "Time Source", val)
         menu.append(rs)
 
@@ -813,59 +893,62 @@ class VX8DRadio(VX8Radio):
         rs = RadioSetting("aprs.timezone", "Timezone", val)
         menu.append(rs)
 
-        val = RadioSettingValueList(self._SPEED_UNITS,
-                                    self._SPEED_UNITS[aprs.aprs_units_speed])
+        val = RadioSettingValueList(
+                self._SPEED_UNITS, self._SPEED_UNITS[aprs.aprs_units_speed])
         rs = RadioSetting("aprs.aprs_units_speed", "APRS Speed Units", val)
         menu.append(rs)
 
-        val = RadioSettingValueList(self._SPEED_UNITS,
-                                    self._SPEED_UNITS[aprs.gps_units_speed])
+        val = RadioSettingValueList(
+                self._SPEED_UNITS, self._SPEED_UNITS[aprs.gps_units_speed])
         rs = RadioSetting("aprs.gps_units_speed", "GPS Speed Units", val)
         menu.append(rs)
 
-        val = RadioSettingValueList(self._ALT_UNITS,
-            self._ALT_UNITS[aprs.aprs_units_altitude_ft])
+        val = RadioSettingValueList(
+                self._ALT_UNITS, self._ALT_UNITS[aprs.aprs_units_altitude_ft])
         rs = RadioSetting("aprs.aprs_units_altitude_ft", "APRS Altitude Units",
                           val)
         menu.append(rs)
 
-        val = RadioSettingValueList(self._ALT_UNITS,
-            self._ALT_UNITS[aprs.gps_units_altitude_ft])
+        val = RadioSettingValueList(
+                self._ALT_UNITS, self._ALT_UNITS[aprs.gps_units_altitude_ft])
         rs = RadioSetting("aprs.gps_units_altitude_ft", "GPS Altitude Units",
                           val)
         menu.append(rs)
 
-        val = RadioSettingValueList(self._POS_UNITS,
-            self._POS_UNITS[aprs.aprs_units_position_mmss])
+        val = RadioSettingValueList(
+                self._POS_UNITS,
+                self._POS_UNITS[aprs.aprs_units_position_mmss])
         rs = RadioSetting("aprs.aprs_units_position_mmss",
                           "APRS Position Format", val)
         menu.append(rs)
 
-        val = RadioSettingValueList(self._POS_UNITS,
-            self._POS_UNITS[aprs.gps_units_position_sss])
+        val = RadioSettingValueList(
+                self._POS_UNITS, self._POS_UNITS[aprs.gps_units_position_sss])
         rs = RadioSetting("aprs.gps_units_position_sss",
                           "GPS Position Format", val)
         menu.append(rs)
 
-        val = RadioSettingValueList(self._DIST_UNITS,
-            self._DIST_UNITS[aprs.aprs_units_distance_m])
+        val = RadioSettingValueList(
+                self._DIST_UNITS,
+                self._DIST_UNITS[aprs.aprs_units_distance_m])
         rs = RadioSetting("aprs.aprs_units_distance_m", "APRS Distance Units",
                           val)
         menu.append(rs)
 
-        val = RadioSettingValueList(self._WIND_UNITS,
-                                    self._WIND_UNITS[aprs.aprs_units_wind_mph])
+        val = RadioSettingValueList(
+                self._WIND_UNITS, self._WIND_UNITS[aprs.aprs_units_wind_mph])
         rs = RadioSetting("aprs.aprs_units_wind_mph", "APRS Wind Speed Units",
                           val)
         menu.append(rs)
 
-        val = RadioSettingValueList(self._RAIN_UNITS,
-            self._RAIN_UNITS[aprs.aprs_units_rain_inch])
+        val = RadioSettingValueList(
+                self._RAIN_UNITS, self._RAIN_UNITS[aprs.aprs_units_rain_inch])
         rs = RadioSetting("aprs.aprs_units_rain_inch", "APRS Rain Units", val)
         menu.append(rs)
 
-        val = RadioSettingValueList(self._TEMP_UNITS,
-            self._TEMP_UNITS[aprs.aprs_units_temperature_f])
+        val = RadioSettingValueList(
+                self._TEMP_UNITS,
+                self._TEMP_UNITS[aprs.aprs_units_temperature_f])
         rs = RadioSetting("aprs.aprs_units_temperature_f",
                           "APRS Temperature Units", val)
         menu.append(rs)
@@ -986,20 +1069,20 @@ class VX8DRadio(VX8Radio):
         menu = RadioSettingGroup("aprs_tx", "APRS Transmit")
         aprs = self._memobj.aprs
 
-        beacon_type = (aprs.tx_smartbeacon << 1) | aprs.tx_interval_beacon;
-        val = RadioSettingValueList(self._BEACON_TYPE,
-                                    self._BEACON_TYPE[beacon_type])
+        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])
+        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])
+        val = RadioSettingValueList(
+                self._BEACON_INT, self._BEACON_INT[aprs.beacon_interval])
         rs = RadioSetting("aprs.beacon_interval", "Beacon Interval", val)
         menu.append(rs)
 
@@ -1063,11 +1146,11 @@ class VX8DRadio(VX8Radio):
 
         # 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] = 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]
@@ -1134,13 +1217,182 @@ class VX8DRadio(VX8Radio):
             menu.append(rs)
 
         return menu
- 
+
+    def _get_dtmf_settings(self):
+        menu = RadioSettingGroup("dtmf_settings", "DTMF")
+        dtmf = self._memobj.scan_settings
+
+        val = RadioSettingValueList(
+            self._DTMF_MODE,
+            self._DTMF_MODE[dtmf.dtmf_mode])
+        rs = RadioSetting("scan_settings.dtmf_mode", "DTMF Mode", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._DTMF_SPEED,
+            self._DTMF_SPEED[dtmf.dtmf_speed])
+        rs = RadioSetting("scan_settings.dtmf_speed",
+                          "DTMF AutoDial Speed", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._DTMF_DELAY,
+            self._DTMF_DELAY[dtmf.dtmf_delay])
+        rs = RadioSetting("scan_settings.dtmf_delay",
+                          "DTMF AutoDial Delay", val)
+        menu.append(rs)
+
+        for i in range(10):
+            name = "dtmf_%02d" % i
+            dtmfsetting = self._memobj.dtmf[i]
+            dtmfstr = ""
+            for c in dtmfsetting.memory:
+                if c == 0xFF:
+                    break
+                if c < len(VX8_DTMF_CHARS):
+                    dtmfstr += VX8_DTMF_CHARS[c]
+            dtmfentry = RadioSettingValueString(0, 16, dtmfstr)
+            dtmfentry.set_charset(VX8_DTMF_CHARS + list("abcd "))
+            rs = RadioSetting(name, name.upper(), dtmfentry)
+            rs.set_apply_callback(self.apply_dtmf, i)
+            menu.append(rs)
+
+        return menu
+
+    def _get_misc_settings(self):
+        menu = RadioSettingGroup("misc_settings", "Misc")
+        scan_settings = self._memobj.scan_settings
+
+        val = RadioSettingValueList(
+            self._LCD_DIMMER,
+            self._LCD_DIMMER[scan_settings.lcd_dimmer])
+        rs = RadioSetting("scan_settings.lcd_dimmer", "LCD Dimmer", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._LCD_CONTRAST,
+            self._LCD_CONTRAST[scan_settings.lcd_contrast - 1])
+        rs = RadioSetting("scan_settings.lcd_contrast", "LCD Contrast",
+                          val)
+        rs.set_apply_callback(self.apply_lcd_contrast, scan_settings)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._LAMP_KEY,
+            self._LAMP_KEY[scan_settings.lamp])
+        rs = RadioSetting("scan_settings.lamp", "Lamp", val)
+        menu.append(rs)
+
+        beep_select = self._memobj.beep_select
+
+        val = RadioSettingValueList(
+            self._BEEP_SELECT,
+            self._BEEP_SELECT[beep_select.beep])
+        rs = RadioSetting("beep_select.beep", "Beep Select", val)
+        menu.append(rs)
+
+        opening_message = self._memobj.opening_message
+
+        val = RadioSettingValueList(
+            self._OPENING_MESSAGE,
+            self._OPENING_MESSAGE[opening_message.flag])
+        rs = RadioSetting("opening_message.flag", "Opening Msg Mode",
+                          val)
+        menu.append(rs)
+
+        msg = ""
+        for i in opening_message.message.padded_yaesu:
+            if i == 0xFF:
+                break
+            msg += CHARSET[i & 0x7F]
+        val = RadioSettingValueString(0, 16, msg)
+        rs = RadioSetting("opening_message.message.padded_yaesu",
+                          "Opening Message", val)
+        rs.set_apply_callback(self.apply_ff_padded_yaesu,
+                              opening_message.message)
+        menu.append(rs)
+
+        return menu
+
+    def _get_scan_settings(self):
+        menu = RadioSettingGroup("scan_settings", "Scan")
+        scan_settings = self._memobj.scan_settings
+
+        val = RadioSettingValueList(
+            self._VOL_MODE,
+            self._VOL_MODE[scan_settings.vol_mode])
+        rs = RadioSetting("scan_settings.vol_mode", "Volume Mode", val)
+        menu.append(rs)
+
+        vfoa = self._memobj.vfo_info[0]
+        val = RadioSettingValueList(
+            self._VOLUME,
+            self._VOLUME[vfoa.volume])
+        rs = RadioSetting("vfo_info[0].volume", "VFO A Volume", val)
+        rs.set_apply_callback(self.apply_volume, 0)
+        menu.append(rs)
+
+        vfob = self._memobj.vfo_info[1]
+        val = RadioSettingValueList(
+            self._VOLUME,
+            self._VOLUME[vfob.volume])
+        rs = RadioSetting("vfo_info[1].volume", "VFO B Volume", val)
+        rs.set_apply_callback(self.apply_volume, 1)
+        menu.append(rs)
+
+        squelch = self._memobj.squelch
+        val = RadioSettingValueList(
+            self._SQUELCH,
+            self._SQUELCH[squelch.vfo_a])
+        rs = RadioSetting("squelch.vfo_a", "VFO A Squelch", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._SQUELCH,
+            self._SQUELCH[squelch.vfo_b])
+        rs = RadioSetting("squelch.vfo_b", "VFO B Squelch", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._SCAN_RESTART,
+            self._SCAN_RESTART[scan_settings.scan_restart])
+        rs = RadioSetting("scan_settings.scan_restart", "Scan Restart", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._SCAN_RESUME,
+            self._SCAN_RESUME[scan_settings.scan_resume])
+        rs = RadioSetting("scan_settings.scan_resume", "Scan Resume", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._OFF_ON,
+            self._OFF_ON[scan_settings.busy_led])
+        rs = RadioSetting("scan_settings.busy_led", "Busy LED", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._OFF_ON,
+            self._OFF_ON[scan_settings.scan_lamp])
+        rs = RadioSetting("scan_settings.scan_lamp", "Scan Lamp", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            self._TOT_TIME,
+            self._TOT_TIME[scan_settings.tot])
+        rs = RadioSetting("scan_settings.tot", "Transmit Timeout (TOT)", val)
+        menu.append(rs)
+
+        return menu
+
     def _get_settings(self):
-        top = RadioSettingGroup("all", "All Settings",
-                                self._get_aprs_general_settings(),
-                                self._get_aprs_rx_settings(),
-                                self._get_aprs_tx_settings(),
-                                self._get_aprs_smartbeacon())
+        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
 
     def get_settings(self):
@@ -1148,8 +1400,7 @@ class VX8DRadio(VX8Radio):
             return self._get_settings()
         except:
             import traceback
-            print "Failed to parse settings:"
-            traceback.print_exc()
+            LOG.error("Failed to parse settings: %s", traceback.format_exc())
             return None
 
     @staticmethod
@@ -1223,8 +1474,7 @@ class VX8DRadio(VX8Radio):
         is_latitude = name.endswith("latitude")
         lat_long = setting.value.get_value().strip()
         sign, l_d, l_m, l_s = cls._str_to_latlong(lat_long, is_latitude)
-        if os.getenv("CHIRP_DEBUG"):
-            print "%s: %d %d %d %d" % (name, sign, l_d, l_m, l_s)
+        LOG.debug("%s: %d %d %d %d" % (name, sign, l_d, l_m, l_s))
         setattr(obj, "%s_sign" % name, sign)
         setattr(obj, "%s_degree" % name, l_d)
         setattr(obj, "%s_minute" % name, l_m)
@@ -1240,11 +1490,11 @@ class VX8DRadio(VX8Radio):
                 continue
             try:
                 if element.has_apply_callback():
-                    print "Using apply callback"
+                    LOG.debug("Using apply callback")
                     try:
                         element.run_apply_callback()
                     except NotImplementedError as e:
-                        print e
+                        LOG.error(e)
                     continue
 
                 # Find the object containing setting.
@@ -1261,17 +1511,44 @@ class VX8DRadio(VX8Radio):
 
                 try:
                     old_val = getattr(obj, setting)
-                    if os.getenv("CHIRP_DEBUG"):
-                        print "Setting %s(%r) <= %s" % (
-                            element.get_name(), old_val, element.value)
+                    LOG.debug("Setting %s(%r) <= %s" % (
+                            element.get_name(), old_val, element.value))
                     setattr(obj, setting, element.value)
                 except AttributeError as e:
-                    print "Setting %s is not in the memory map: %s" % (
-                        element.get_name(), e)
+                    LOG.error("Setting %s is not in the memory map: %s" %
+                              (element.get_name(), e))
             except Exception, e:
-                print element.get_name()
+                LOG.debug(element.get_name())
                 raise
 
+    def apply_ff_padded_yaesu(cls, setting, obj):
+        # FF pad yaesus custom string format.
+        rawval = setting.value.get_value()
+        max_len = getattr(obj, "padded_yaesu").size() / 8
+        rawval = str(rawval).rstrip()
+        val = [CHARSET.index(x) for x in rawval]
+        for x in range(len(val), max_len):
+            val.append(0xFF)
+        obj.padded_yaesu = val
+
+    def apply_volume(cls, setting, vfo):
+        val = setting.value.get_value()
+        cls._memobj.vfo_info[(vfo*2)].volume = val
+        cls._memobj.vfo_info[(vfo*2)+1].volume = val
+
+    def apply_lcd_contrast(cls, setting, obj):
+        rawval = setting.value.get_value()
+        val = cls._LCD_CONTRAST.index(rawval) + 1
+        obj.lcd_contrast = val
+
+    def apply_dtmf(cls, setting, i):
+        rawval = setting.value.get_value().upper().rstrip()
+        val = [VX8_DTMF_CHARS.index(x) for x in rawval]
+        for x in range(len(val), 16):
+            val.append(0xFF)
+        cls._memobj.dtmf[i].memory = val
+
+
 @directory.register
 class VX8GERadio(VX8DRadio):
     """Yaesu VX-8GE"""
diff --git a/chirp/vxa700.py b/chirp/drivers/vxa700.py
similarity index 90%
rename from chirp/vxa700.py
rename to chirp/drivers/vxa700.py
index bfccfbd..3aa39d5 100644
--- a/chirp/vxa700.py
+++ b/chirp/drivers/vxa700.py
@@ -18,21 +18,22 @@ from chirp import bitwise
 
 import time
 import struct
+import logging
+
+LOG = logging.getLogger(__name__)
 
-def _debug(string):
-    pass
-    print string
 
 def _send(radio, data):
-    _debug("Sending %s" % repr(data))
+    LOG.debug("Sending %s" % repr(data))
     radio.pipe.write(data)
     radio.pipe.flush()
     echo = radio.pipe.read(len(data))
     if len(echo) != len(data):
         raise errors.RadioError("Invalid echo")
 
+
 def _spoonfeed(radio, data):
-    #count = 0
+    # count = 0
     _debug("Writing %i:\n%s" % (len(data), util.hexprint(data)))
     for byte in data:
         radio.pipe.write(byte)
@@ -43,9 +44,10 @@ def _spoonfeed(radio, data):
         # so just blindly send the data
         echo = radio.pipe.read(1)
         if echo != byte:
-            print "%02x != %02x" % (ord(echo), ord(byte))
+            LOG.debug("%02x != %02x" % (ord(echo), ord(byte)))
             raise errors.RadioError("No echo?")
-        #count += 1
+        # count += 1
+
 
 def _download(radio):
     count = 0
@@ -80,6 +82,7 @@ def _download(radio):
 
     return memmap.MemoryMap(data)
 
+
 def _upload(radio):
     for i in range(0, radio.get_memsize(), 128):
         chunk = radio.get_mmap()[i:i+128]
@@ -100,9 +103,9 @@ def _upload(radio):
         ack = "\x06"
         time.sleep(0.5)
         if ack != "\x06":
-            print repr(ack)
+            LOG.debug(repr(ack))
             raise errors.RadioError("Radio did not ack block %i" % (i / 132))
-        #radio.pipe.read(1)
+        # radio.pipe.read(1)
         if radio.status_fn:
             status = chirp_common.Status()
             status.msg = "Cloning to radio"
@@ -146,9 +149,9 @@ struct memory_struct memory[100];
 
 CHARSET = "".join(["%i" % i for i in range(0, 10)]) + \
     "".join([chr(ord("A") + i) for i in range(0, 26)]) + \
-    "".join([chr(ord("a") + i) for i in range(0,26)]) + \
+    "".join([chr(ord("a") + i) for i in range(0, 26)]) + \
     "., :;!\"#$%&'()*+-/=<>?@[?]^_`{|}????~??????????????????????????"
-            
+
 TMODES = ["", "Tone", "TSQL", "DTCS"]
 DUPLEX = ["", "-", "+", ""]
 POWER = [chirp_common.PowerLevel("Low1", watts=0.050),
@@ -156,9 +159,11 @@ POWER = [chirp_common.PowerLevel("Low1", watts=0.050),
          chirp_common.PowerLevel("Low3", watts=2.500),
          chirp_common.PowerLevel("High", watts=5.000)]
 
+
 def _wipe_memory(_mem):
     _mem.set_raw("\x00" * (_mem.size() / 8))
 
+
 @directory.register
 class VXA700Radio(chirp_common.CloneModeRadio):
     """Vertex Standard VXA-700"""
@@ -178,10 +183,10 @@ class VXA700Radio(chirp_common.CloneModeRadio):
         self.process_mmap()
 
     def sync_out(self):
-        #header[4] = 0x00 <- default
-        #            0xFF <- air band only
-        #            0x01 <- air band only
-        #            0x02 <- air band only
+        # header[4] = 0x00 <- default
+        #             0xFF <- air band only
+        #             0x01 <- air band only
+        #             0x02 <- air band only
         try:
             self.pipe.setTimeout(2)
             _upload(self)
@@ -205,7 +210,8 @@ class VXA700Radio(chirp_common.CloneModeRadio):
         rf.valid_characters = CHARSET
         rf.valid_skips = ["", "S"]
         rf.valid_bands = [(88000000, 165000000)]
-        rf.valid_tuning_steps = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0]
+        rf.valid_tuning_steps = \
+            [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0]
         rf.valid_modes = ["AM", "FM"]
         rf.valid_power_levels = POWER
         rf.memory_bounds = (1, 100)
@@ -232,7 +238,7 @@ class VXA700Radio(chirp_common.CloneModeRadio):
             mem.empty = True
             return mem
 
-        if _mem.step & 0x05: # Not sure this is right, but it seems to be
+        if _mem.step & 0x05:  # Not sure this is right, but it seems to be
             mult = 6250
         else:
             mult = 5000
@@ -274,9 +280,9 @@ class VXA700Radio(chirp_common.CloneModeRadio):
         self._memobj.invisible_bits[byte] &= ~bit
         self._memobj.invalid_bits[byte] &= ~bit
 
-        _mem.unknown2 = 0x02 # Channels don't display without this
-        _mem.unknown7 = 0x01 # some bit in this field is related to
-        _mem.unknown8 = 0xFF # being able to transmit
+        _mem.unknown2 = 0x02  # Channels don't display without this
+        _mem.unknown7 = 0x01  # some bit in this field is related to
+        _mem.unknown8 = 0xFF  # being able to transmit
 
         if chirp_common.required_step(mem.freq) == 12.5:
             mult = 6250
@@ -296,7 +302,7 @@ class VXA700Radio(chirp_common.CloneModeRadio):
         try:
             _mem.power = POWER.index(mem.power)
         except ValueError:
-            _mem.power = 3 # High
+            _mem.power = 3  # High
 
         for i in range(0, 8):
             try:
diff --git a/chirp/wouxun.py b/chirp/drivers/wouxun.py
similarity index 62%
rename from chirp/wouxun.py
rename to chirp/drivers/wouxun.py
index 992b9fa..23eb3a7 100644
--- a/chirp/wouxun.py
+++ b/chirp/drivers/wouxun.py
@@ -17,55 +17,60 @@
 
 import time
 import os
+import logging
 from chirp import util, chirp_common, bitwise, memmap, errors, directory
 from chirp.settings import RadioSetting, RadioSettingGroup, \
                 RadioSettingValueBoolean, RadioSettingValueList, \
                 RadioSettingValueInteger, RadioSettingValueString, \
-                RadioSettingValueFloat
-from chirp.wouxun_common import wipe_memory, do_download, do_upload
+                RadioSettingValueFloat, RadioSettings
+from wouxun_common import wipe_memory, do_download, do_upload
 from textwrap import dedent
 
-FREQ_ENCODE_TABLE = [ 0x7, 0xa, 0x0, 0x9, 0xb, 0x2, 0xe, 0x1, 0x3, 0xf ]
- 
+LOG = logging.getLogger(__name__)
+
+FREQ_ENCODE_TABLE = [0x7, 0xa, 0x0, 0x9, 0xb, 0x2, 0xe, 0x1, 0x3, 0xf]
+
+
 def encode_freq(freq):
     """Convert frequency (4 decimal digits) to wouxun format (2 bytes)"""
     enc = 0
     div = 1000
     for i in range(0, 4):
         enc <<= 4
-        enc |= FREQ_ENCODE_TABLE[ (freq/div) % 10 ]
+        enc |= FREQ_ENCODE_TABLE[(freq/div) % 10]
         div /= 10
     return enc
 
+
 def decode_freq(data):
     """Convert from wouxun format (2 bytes) to frequency (4 decimal digits)"""
     freq = 0
     shift = 12
     for i in range(0, 4):
         freq *= 10
-        freq += FREQ_ENCODE_TABLE.index( (data>>shift) & 0xf )
+        freq += FREQ_ENCODE_TABLE.index((data >> shift) & 0xf)
         shift -= 4
-        # print "data %04x freq %d shift %d" % (data, freq, shift)
+        # LOG.debug("data %04x freq %d shift %d" % (data, freq, shift))
     return freq
 
+
 @directory.register
 class KGUVD1PRadio(chirp_common.CloneModeRadio,
-        chirp_common.ExperimentalRadio):
+                   chirp_common.ExperimentalRadio):
     """Wouxun KG-UVD1P,UV2,UV3"""
     VENDOR = "Wouxun"
     MODEL = "KG-UVD1P"
     _model = "KG669V"
-    
+
     _querymodel = ("HiWOUXUN\x02", "PROGUV6X\x02")
-    
-    CHARSET = list("0123456789") + [chr(x + ord("A")) for x in range(0, 26)] + \
-        list("?+-")
+
+    CHARSET = list("0123456789") + \
+        [chr(x + ord("A")) for x in range(0, 26)] + list("?+-")
 
     POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
                     chirp_common.PowerLevel("Low", watts=1.00)]
-                    
-    valid_freq = [(136000000, 175000000), (216000000, 520000000)]
 
+    valid_freq = [(136000000, 175000000), (216000000, 520000000)]
 
     _MEM_FORMAT = """
         #seekto 0x0010;
@@ -82,9 +87,12 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
              power_high:1,
              iswide:1,
              _2_unknown_2:4;
-          u8 unknown[2];
+          u8 unknown;
+          u8 _0_unknown_1:3,
+             iswidex:1,
+             _0_unknown_2:4;
         } memory[199];
-        
+
         #seekto 0x0842;
         u16 fm_presets_0[9];
 
@@ -196,12 +204,13 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
     @classmethod
     def get_prompts(cls):
         rp = chirp_common.RadioPrompts()
-        rp.experimental = ('This version of the Wouxun driver allows you to modify the '
-                'frequency range settings of your radio. This has been tested '
-                'and reports from other users indicate that it is a safe '
-                'thing to do. However, modifications to this value may have '
-                'unintended consequences, including damage to your device. '
-                'You have been warned. Proceed at your own risk!')
+        rp.experimental = \
+            ('This version of the Wouxun driver allows you to modify the '
+             'frequency range settings of your radio. This has been tested '
+             'and reports from other users indicate that it is a safe '
+             'thing to do. However, modifications to this value may have '
+             'unintended consequences, including damage to your device. '
+             'You have been warned. Proceed at your own risk!')
         rp.pre_download = _(dedent("""\
             1. Turn radio off.
             2. Connect cable to mic/spkr connector.
@@ -217,7 +226,7 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
             5. Ensure that the radio is tuned to channel with no activity.
             6. Click OK to upload image to device."""))
         return rp
-                
+
     @classmethod
     def _get_querymodel(cls):
         if isinstance(cls._querymodel, str):
@@ -236,13 +245,13 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
             self.pipe.write(query.next())
             resp = self.pipe.read(9)
             if len(resp) != 9:
-                print "Got:\n%s" % util.hexprint(resp)
-                print "Retrying identification..."
+                LOG.debug("Got:\n%s" % util.hexprint(resp))
+                LOG.info("Retrying identification...")
                 time.sleep(1)
                 continue
             if resp[2:8] != self._model:
-                raise Exception("I can't talk to this model (%s)" % 
-                    util.hexprint(resp))
+                raise Exception("I can't talk to this model (%s)" %
+                                util.hexprint(resp))
             return
         if len(resp) == 0:
             raise Exception("Radio not responding")
@@ -255,7 +264,8 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
         time.sleep(0.05)
         ack = self.pipe.read(1)
         if ack != "\x06":
-            raise Exception("Radio refused transfer mode")    
+            raise Exception("Radio refused transfer mode")
+
     def _download(self):
         """Talk to an original wouxun and do a download"""
         try:
@@ -278,7 +288,6 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
         except Exception, e:
             raise errors.RadioError("Failed to communicate with radio: %s" % e)
 
-
     def sync_in(self):
         self._mmap = self._download()
         self.process_mmap()
@@ -288,7 +297,7 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
 
     def process_mmap(self):
         if len(self._mmap.get_packed()) != 8192:
-            print "NOTE: Fixing old-style Wouxun image"
+            LOG.info("Fixing old-style Wouxun image")
             # Originally, CHIRP's wouxun image had eight bytes of
             # static data, followed by the first memory at offset
             # 0x0008.  Between 0.1.11 and 0.1.12, this was fixed to 16
@@ -296,8 +305,8 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
             # offset 0x0010, like the radio actually stores it.  So,
             # if we find one of those old ones, convert it to the new
             # format, padding 16 bytes of 0xFF in front.
-            self._mmap = memmap.MemoryMap(("\xFF" * 16) + \
-                                              self._mmap.get_packed()[8:8184])
+            self._mmap = memmap.MemoryMap(
+                    ("\xFF" * 16) + self._mmap.get_packed()[8:8184])
         self._memobj = bitwise.parse(self._MEM_FORMAT, self._mmap)
 
     def get_features(self):
@@ -329,62 +338,64 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
         return rf
 
     def get_settings(self):
-        freqranges = RadioSettingGroup("freqranges", "Freq ranges")
+        freq_ranges = RadioSettingGroup("freq_ranges", "Freq Ranges")
         fm_preset = RadioSettingGroup("fm_preset", "FM Presets")
-        top = RadioSettingGroup("top", "All Settings", freqranges, fm_preset)
+        cfg_s = RadioSettingGroup("cfg_settings", "Configuration Settings")
+        group = RadioSettings(cfg_s, freq_ranges, fm_preset)
 
         rs = RadioSetting("menu_available", "Menu Available",
                           RadioSettingValueBoolean(
-                            self._memobj.settings.menu_available))
-        top.append(rs)
+                              self._memobj.settings.menu_available))
+        cfg_s.append(rs)
 
         rs = RadioSetting("vhf_rx_start", "1st band RX Lower Limit (MHz)",
-                          RadioSettingValueInteger(50, 174, 
-                                decode_freq(
-                                    self._memobj.freq_ranges.vhf_rx_start)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              50, 174, decode_freq(
+                                  self._memobj.freq_ranges.vhf_rx_start)))
+        freq_ranges.append(rs)
         rs = RadioSetting("vhf_rx_stop", "1st band RX Upper Limit (MHz)",
-                          RadioSettingValueInteger(50, 174, 
-                                decode_freq(
-                                    self._memobj.freq_ranges.vhf_rx_stop)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              50, 174, decode_freq(
+                                  self._memobj.freq_ranges.vhf_rx_stop)))
+        freq_ranges.append(rs)
         rs = RadioSetting("uhf_rx_start", "2nd band RX Lower Limit (MHz)",
-                          RadioSettingValueInteger(136, 520, 
-                                decode_freq(
-                                    self._memobj.freq_ranges.uhf_rx_start)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              136, 520, decode_freq(
+                                  self._memobj.freq_ranges.uhf_rx_start)))
+        freq_ranges.append(rs)
         rs = RadioSetting("uhf_rx_stop", "2nd band RX Upper Limit (MHz)",
-                          RadioSettingValueInteger(136, 520, 
-                                decode_freq(
-                                    self._memobj.freq_ranges.uhf_rx_stop)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              136, 520, decode_freq(
+                                  self._memobj.freq_ranges.uhf_rx_stop)))
+        freq_ranges.append(rs)
         rs = RadioSetting("vhf_tx_start", "1st band TX Lower Limit (MHz)",
-                          RadioSettingValueInteger(50, 174, 
-                                decode_freq(
-                                    self._memobj.freq_ranges.vhf_tx_start)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              50, 174, decode_freq(
+                                  self._memobj.freq_ranges.vhf_tx_start)))
+        freq_ranges.append(rs)
         rs = RadioSetting("vhf_tx_stop", "1st TX Upper Limit (MHz)",
-                          RadioSettingValueInteger(50, 174, 
-                                decode_freq(
-                                    self._memobj.freq_ranges.vhf_tx_stop)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              50, 174, decode_freq(
+                                  self._memobj.freq_ranges.vhf_tx_stop)))
+        freq_ranges.append(rs)
         rs = RadioSetting("uhf_tx_start", "2st band TX Lower Limit (MHz)",
-                          RadioSettingValueInteger(136, 520, 
-                                decode_freq(
-                                    self._memobj.freq_ranges.uhf_tx_start)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              136, 520, decode_freq(
+                                  self._memobj.freq_ranges.uhf_tx_start)))
+        freq_ranges.append(rs)
         rs = RadioSetting("uhf_tx_stop", "2st band TX Upper Limit (MHz)",
-                          RadioSettingValueInteger(136, 520, 
-                                decode_freq(
-                                    self._memobj.freq_ranges.uhf_tx_stop)))
-        freqranges.append(rs)
-        
+                          RadioSettingValueInteger(
+                              136, 520, decode_freq(
+                                  self._memobj.freq_ranges.uhf_tx_stop)))
+        freq_ranges.append(rs)
+
         # tell the decoded ranges to UI
-        self.valid_freq = [
-            ( decode_freq(self._memobj.freq_ranges.vhf_rx_start) * 1000000, 
-             (decode_freq(self._memobj.freq_ranges.vhf_rx_stop)+1) * 1000000),
-            ( decode_freq(self._memobj.freq_ranges.uhf_rx_start)  * 1000000,
-             (decode_freq(self._memobj.freq_ranges.uhf_rx_stop)+1) * 1000000)]
+        freq_ranges = self._memobj.freq_ranges
+        self.valid_freq = \
+            [(decode_freq(freq_ranges.vhf_rx_start) * 1000000,
+             (decode_freq(freq_ranges.vhf_rx_stop) + 1) * 1000000),
+             (decode_freq(freq_ranges.uhf_rx_start) * 1000000,
+             (decode_freq(freq_ranges.uhf_rx_stop) + 1) * 1000000)]
 
         def _filter(name):
             filtered = ""
@@ -398,167 +409,180 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
         # add some radio specific settings
         options = ["Off", "Welcome", "V bat"]
         rs = RadioSetting("ponmsg", "Poweron message",
-                          RadioSettingValueList(options,
-                              options[self._memobj.settings.ponmsg]))
-        top.append(rs)
+                          RadioSettingValueList(
+                              options, options[self._memobj.settings.ponmsg]))
+        cfg_s.append(rs)
         rs = RadioSetting("strings.welcome1", "Power-On Message 1",
-                          RadioSettingValueString(0, 6,
-                              _filter(self._memobj.strings.welcome1)))
-        top.append(rs)
+                          RadioSettingValueString(
+                              0, 6, _filter(self._memobj.strings.welcome1)))
+        cfg_s.append(rs)
         rs = RadioSetting("strings.welcome2", "Power-On Message 2",
-                          RadioSettingValueString(0, 6,
-                              _filter(self._memobj.strings.welcome2)))
-        top.append(rs)
+                          RadioSettingValueString(
+                              0, 6, _filter(self._memobj.strings.welcome2)))
+        cfg_s.append(rs)
         rs = RadioSetting("strings.single_band", "Single Band Message",
-                          RadioSettingValueString(0, 6,
-                              _filter(self._memobj.strings.single_band)))
-        top.append(rs)
-        options = ["Channel", "ch/freq","Name", "VFO"]
+                          RadioSettingValueString(
+                              0, 6, _filter(self._memobj.strings.single_band)))
+        cfg_s.append(rs)
+        options = ["Channel", "ch/freq", "Name", "VFO"]
         rs = RadioSetting("vfo_a_ch_disp", "VFO A Channel disp mode",
-                          RadioSettingValueList(options,
+                          RadioSettingValueList(
+                              options,
                               options[self._memobj.settings.vfo_a_ch_disp]))
-        top.append(rs)
+        cfg_s.append(rs)
         rs = RadioSetting("vfo_b_ch_disp", "VFO B Channel disp mode",
-                          RadioSettingValueList(options,
+                          RadioSettingValueList(
+                              options,
                               options[self._memobj.settings.vfo_b_ch_disp]))
-        top.append(rs)
+        cfg_s.append(rs)
         options = ["5.0", "6.25", "10.0", "12.5", "25.0", "50.0", "100.0"]
         rs = RadioSetting("vfo_a_fr_step", "VFO A Frequency Step",
-                          RadioSettingValueList(options,
+                          RadioSettingValueList(
+                              options,
                               options[self._memobj.settings.vfo_a_fr_step]))
-        top.append(rs)
+        cfg_s.append(rs)
         rs = RadioSetting("vfo_b_fr_step", "VFO B Frequency Step",
-                          RadioSettingValueList(options,
+                          RadioSettingValueList(
+                              options,
                               options[self._memobj.settings.vfo_b_fr_step]))
-        top.append(rs)
+        cfg_s.append(rs)
         rs = RadioSetting("vfo_a_squelch", "VFO A Squelch",
-                          RadioSettingValueInteger(0, 9,
-                              self._memobj.settings.vfo_a_squelch))
-        top.append(rs)
+                          RadioSettingValueInteger(
+                              0, 9, self._memobj.settings.vfo_a_squelch))
+        cfg_s.append(rs)
         rs = RadioSetting("vfo_b_squelch", "VFO B Squelch",
-                          RadioSettingValueInteger(0, 9,
-                              self._memobj.settings.vfo_b_squelch))
-        top.append(rs)
+                          RadioSettingValueInteger(
+                              0, 9, self._memobj.settings.vfo_b_squelch))
+        cfg_s.append(rs)
         rs = RadioSetting("vfo_a_cur_chan", "VFO A current channel",
-                          RadioSettingValueInteger(1, 128,
-                              self._memobj.settings.vfo_a_cur_chan))
-        top.append(rs)
+                          RadioSettingValueInteger(
+                              1, 128, self._memobj.settings.vfo_a_cur_chan))
+        cfg_s.append(rs)
         rs = RadioSetting("vfo_b_cur_chan", "VFO B current channel",
-                          RadioSettingValueInteger(1, 128,
-                              self._memobj.settings.vfo_b_cur_chan))
-        top.append(rs)
+                          RadioSettingValueInteger(
+                              1, 128, self._memobj.settings.vfo_b_cur_chan))
+        cfg_s.append(rs)
         rs = RadioSetting("priority_chan", "Priority channel",
-                          RadioSettingValueInteger(0, 199,
-                              self._memobj.settings.priority_chan))
-        top.append(rs)
+                          RadioSettingValueInteger(
+                              0, 199, self._memobj.settings.priority_chan))
+        cfg_s.append(rs)
         rs = RadioSetting("power_save", "Power save",
                           RadioSettingValueBoolean(
                               self._memobj.settings.power_save))
-        top.append(rs)
+        cfg_s.append(rs)
         options = ["Off", "Scan", "Lamp", "SOS", "Radio"]
         rs = RadioSetting("pf1_function", "PF1 Function select",
-                          RadioSettingValueList(options,
+                          RadioSettingValueList(
+                              options,
                               options[self._memobj.settings.pf1_function]))
-        top.append(rs)
+        cfg_s.append(rs)
         options = ["Off", "Begin", "End", "Both"]
         rs = RadioSetting("roger_beep", "Roger beep select",
-                          RadioSettingValueList(options,
+                          RadioSettingValueList(
+                              options,
                               options[self._memobj.settings.roger_beep]))
-        top.append(rs)
+        cfg_s.append(rs)
         options = ["%s" % x for x in range(15, 615, 15)]
+        transmit_time_out = options[self._memobj.settings.transmit_time_out]
         rs = RadioSetting("transmit_time_out", "TX Time-out Timer",
-                          RadioSettingValueList(options,
-                              options[self._memobj.settings.transmit_time_out]))
-        top.append(rs)
+                          RadioSettingValueList(
+                              options, transmit_time_out))
+        cfg_s.append(rs)
         rs = RadioSetting("tx_time_out_alert", "TX Time-out Alert",
-                          RadioSettingValueInteger(0, 10,
-                              self._memobj.settings.tx_time_out_alert))
-        top.append(rs)
+                          RadioSettingValueInteger(
+                              0, 10, self._memobj.settings.tx_time_out_alert))
+        cfg_s.append(rs)
         rs = RadioSetting("vox", "Vox",
-                          RadioSettingValueInteger(0, 10,
-                              self._memobj.settings.vox))
-        top.append(rs)
+                          RadioSettingValueInteger(
+                              0, 10, self._memobj.settings.vox))
+        cfg_s.append(rs)
         options = ["Off", "Chinese", "English"]
         rs = RadioSetting("voice", "Voice",
-                          RadioSettingValueList(options,
-                              options[self._memobj.settings.voice]))
-        top.append(rs)
+                          RadioSettingValueList(
+                              options, options[self._memobj.settings.voice]))
+        cfg_s.append(rs)
         rs = RadioSetting("beep", "Beep",
-                          RadioSettingValueBoolean(self._memobj.settings.beep))
-        top.append(rs)
+                          RadioSettingValueBoolean(
+                              self._memobj.settings.beep))
+        cfg_s.append(rs)
         rs = RadioSetting("ani_id_enable", "ANI id enable",
                           RadioSettingValueBoolean(
                               self._memobj.settings.ani_id_enable))
-        top.append(rs)
+        cfg_s.append(rs)
         rs = RadioSetting("ani_id_tx_delay", "ANI id tx delay",
-                          RadioSettingValueInteger(0, 30,
-                              self._memobj.settings.ani_id_tx_delay))
-        top.append(rs)
+                          RadioSettingValueInteger(
+                              0, 30, self._memobj.settings.ani_id_tx_delay))
+        cfg_s.append(rs)
         options = ["Off", "Key", "ANI", "Key+ANI"]
         rs = RadioSetting("ani_id_sidetone", "ANI id sidetone",
-                          RadioSettingValueList(options,
+                          RadioSettingValueList(
+                              options,
                               options[self._memobj.settings.ani_id_sidetone]))
-        top.append(rs)
+        cfg_s.append(rs)
         options = ["Time", "Carrier", "Search"]
         rs = RadioSetting("scan_mode", "Scan mode",
-                          RadioSettingValueList(options,
+                          RadioSettingValueList(
+                              options,
                               options[self._memobj.settings.scan_mode]))
-        top.append(rs)
+        cfg_s.append(rs)
         rs = RadioSetting("kbd_lock", "Keyboard lock",
                           RadioSettingValueBoolean(
                               self._memobj.settings.kbd_lock))
-        top.append(rs)
+        cfg_s.append(rs)
         rs = RadioSetting("auto_lock_kbd", "Auto lock keyboard",
                           RadioSettingValueBoolean(
                               self._memobj.settings.auto_lock_kbd))
-        top.append(rs)
+        cfg_s.append(rs)
         rs = RadioSetting("auto_backlight", "Auto backlight",
                           RadioSettingValueBoolean(
                               self._memobj.settings.auto_backlight))
-        top.append(rs)
+        cfg_s.append(rs)
         options = ["CH A", "CH B"]
         rs = RadioSetting("sos_ch", "SOS CH",
-                          RadioSettingValueList(options,
+                          RadioSettingValueList(
+                              options,
                               options[self._memobj.settings.sos_ch]))
-        top.append(rs)
+        cfg_s.append(rs)
         rs = RadioSetting("stopwatch", "Stopwatch",
                           RadioSettingValueBoolean(
                               self._memobj.settings.stopwatch))
-        top.append(rs)
+        cfg_s.append(rs)
         rs = RadioSetting("dual_band_receive", "Dual band receive",
                           RadioSettingValueBoolean(
                               self._memobj.settings.dual_band_receive))
-        top.append(rs)
+        cfg_s.append(rs)
         options = ["VFO A", "VFO B"]
         rs = RadioSetting("current_vfo", "Current VFO",
-                          RadioSettingValueList(options,
+                          RadioSettingValueList(
+                              options,
                               options[self._memobj.settings.current_vfo]))
-        top.append(rs)
+        cfg_s.append(rs)
 
         options = ["Dual", "Single"]
         rs = RadioSetting("sd_available", "Single/Dual Band",
-                          RadioSettingValueList(options,
+                          RadioSettingValueList(
+                              options,
                               options[self._memobj.settings.sd_available]))
-        top.append(rs)
+        cfg_s.append(rs)
 
         _pwd = self._memobj.settings.mode_password
         rs = RadioSetting("mode_password", "Mode password (000000 disabled)",
-                  RadioSettingValueInteger(0, 9, _pwd[0]),
-                  RadioSettingValueInteger(0, 9, _pwd[1]),
-                  RadioSettingValueInteger(0, 9, _pwd[2]),
-                  RadioSettingValueInteger(0, 9, _pwd[3]),
-                  RadioSettingValueInteger(0, 9, _pwd[4]),
-                  RadioSettingValueInteger(0, 9, _pwd[5]))
-        top.append(rs)
+                          RadioSettingValueInteger(0, 9, _pwd[0]),
+                          RadioSettingValueInteger(0, 9, _pwd[1]),
+                          RadioSettingValueInteger(0, 9, _pwd[2]),
+                          RadioSettingValueInteger(0, 9, _pwd[3]),
+                          RadioSettingValueInteger(0, 9, _pwd[4]),
+                          RadioSettingValueInteger(0, 9, _pwd[5]))
+        cfg_s.append(rs)
         _pwd = self._memobj.settings.reset_password
         rs = RadioSetting("reset_password", "Reset password (000000 disabled)",
-                  RadioSettingValueInteger(0, 9, _pwd[0]),
-                  RadioSettingValueInteger(0, 9, _pwd[1]),
-                  RadioSettingValueInteger(0, 9, _pwd[2]),
-                  RadioSettingValueInteger(0, 9, _pwd[3]),
-                  RadioSettingValueInteger(0, 9, _pwd[4]),
-                  RadioSettingValueInteger(0, 9, _pwd[5]))
-        top.append(rs)
+                          RadioSettingValueInteger(0, 9, _pwd[0]),
+                          RadioSettingValueInteger(0, 9, _pwd[1]),
+                          RadioSettingValueInteger(0, 9, _pwd[2]),
+                          RadioSettingValueInteger(0, 9, _pwd[3]),
+                          RadioSettingValueInteger(0, 9, _pwd[4]),
+                          RadioSettingValueInteger(0, 9, _pwd[5]))
+        cfg_s.append(rs)
 
         dtmfchars = "0123456789 *#ABCD"
         _codeobj = self._memobj.settings.ani_id_content
@@ -566,6 +590,7 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
         val = RadioSettingValueString(0, 6, _code, False)
         val.set_charset(dtmfchars)
         rs = RadioSetting("settings.ani_id_content", "PTT-ID Code", val)
+
         def apply_ani_id(setting, obj):
             value = []
             for j in range(0, 6):
@@ -575,39 +600,41 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
                     value.append(0xFF)
             obj.ani_id_content = value
         rs.set_apply_callback(apply_ani_id, self._memobj.settings)
-        top.append(rs)
+        cfg_s.append(rs)
 
         for i in range(0, 9):
             if self._memobj.fm_presets_0[i] != 0xFFFF:
                 used = True
-                preset = self._memobj.fm_presets_0[i]/10.0+76
+                preset = self._memobj.fm_presets_0[i] / 10.0 + 76
             else:
                 used = False
                 preset = 76
-            rs = RadioSetting("fm_presets_0_%1i" % i, "Team 1 Location %i" % (i+1),
-                          RadioSettingValueBoolean(used),
-                          RadioSettingValueFloat(76, 108, preset, 0.1, 1))
+            rs = RadioSetting("fm_presets_0_%1i" % i,
+                              "Team 1 Location %i" % (i + 1),
+                              RadioSettingValueBoolean(used),
+                              RadioSettingValueFloat(76, 108, preset, 0.1, 1))
             fm_preset.append(rs)
         for i in range(0, 9):
             if self._memobj.fm_presets_1[i] != 0xFFFF:
                 used = True
-                preset = self._memobj.fm_presets_1[i]/10.0+76
+                preset = self._memobj.fm_presets_1[i] / 10.0 + 76
             else:
                 used = False
                 preset = 76
-            rs = RadioSetting("fm_presets_1_%1i" % i, "Team 2 Location %i" % (i+1),
-                          RadioSettingValueBoolean(used),
-                          RadioSettingValueFloat(76, 108, preset, 0.1, 1))
+            rs = RadioSetting("fm_presets_1_%1i" % i,
+                              "Team 2 Location %i" % (i + 1),
+                              RadioSettingValueBoolean(used),
+                              RadioSettingValueFloat(76, 108, preset, 0.1, 1))
             fm_preset.append(rs)
 
-        return top
+        return group
 
     def set_settings(self, settings):
         for element in settings:
             if not isinstance(element, RadioSetting):
-                if element.get_name() == "freqranges" :
+                if element.get_name() == "freq_ranges":
                     self._set_freq_settings(element)
-                elif element.get_name() == "fm_preset" :
+                elif element.get_name() == "fm_preset":
                     self._set_fm_preset(element)
                 else:
                     self.set_settings(element)
@@ -625,33 +652,35 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
                         setting = element.get_name()
 
                     if element.has_apply_callback():
-                        print "Using apply callback"
+                        LOG.debug("Using apply callback")
                         element.run_apply_callback()
                     else:
-                        print "Setting %s = %s" % (setting, element.value)
+                        LOG.debug("Setting %s = %s" % (setting, element.value))
                         setattr(obj, setting, element.value)
                 except Exception, e:
-                    print element.get_name()
+                    LOG.debug(element.get_name())
                     raise
 
     def _set_fm_preset(self, settings):
         obj = self._memobj
         for element in settings:
             try:
-                (bank, index) = (int(a) for a in element.get_name().split("_")[-2:])
+                (bank, index) = \
+                    (int(a) for a in element.get_name().split("_")[-2:])
                 val = element.value
                 if val[0].get_value():
                     value = int(val[1].get_value()*10-760)
                 else:
                     value = 0xffff
-                print "Setting fm_presets_%1i[%1i] = %s" % (bank, index, value)
+                LOG.debug("Setting fm_presets_%1i[%1i] = %s" %
+                          (bank, index, value))
                 if bank == 0:
                     setting = self._memobj.fm_presets_0
                 else:
                     setting = self._memobj.fm_presets_1
                 setting[index] = value
             except Exception, e:
-                print element.get_name()
+                LOG.debug(element.get_name())
                 raise
 
     def _set_freq_settings(self, settings):
@@ -661,7 +690,7 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
                         element.get_name(),
                         encode_freq(int(element.value)))
             except Exception, e:
-                print element.get_name()
+                LOG.debug(element.get_name())
                 raise
 
     def get_raw_memory(self, number):
@@ -708,9 +737,8 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
         # always set it even if no dtcs is used
         mem.dtcs_polarity = "%s%s" % (tpol or "N", rpol or "N")
 
-        if os.getenv("CHIRP_DEBUG"):
-            print "Got TX %s (%i) RX %s (%i)" % (txmode, _mem.tx_tone,
-                                                 rxmode, _mem.rx_tone)
+        LOG.debug("Got TX %s (%i) RX %s (%i)" %
+                  (txmode, _mem.tx_tone, rxmode, _mem.rx_tone))
 
     def _is_txinh(self, _mem):
         raw_tx = ""
@@ -766,6 +794,13 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
         bcl.set_doc("Busy Channel Lockout")
         mem.extra.append(bcl)
 
+        options = ["NFM", "FM"]
+        iswidex = RadioSetting("iswidex", "Mode TX(KG-UV6X)",
+                               RadioSettingValueList(
+                                   options, options[_mem.iswidex]))
+        iswidex.set_doc("Mode TX")
+        mem.extra.append(iswidex)
+
         return mem
 
     def _set_tone(self, mem, _mem):
@@ -783,7 +818,6 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
         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 \
@@ -801,9 +835,8 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
         else:
             _mem.rx_tone = 0xFFFF
 
-        if os.getenv("CHIRP_DEBUG"):
-            print "Set TX %s (%i) RX %s (%i)" % (tx_mode, _mem.tx_tone,
-                                                 rx_mode, _mem.rx_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]
@@ -856,7 +889,7 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
                 filedata[0x60:0x64] != "2009" and \
                 filedata[0x1f77:0x1f7d] == "\xff\xff\xff\xff\xff\xff" and \
                 filedata[0x0d70:0x0d80] == "\xff\xff\xff\xff\xff\xff\xff\xff" \
-                                           "\xff\xff\xff\xff\xff\xff\xff\xff": 
+                                           "\xff\xff\xff\xff\xff\xff\xff\xff":
                 # those areas are (seems to be) unused
             return True
         # Old-style image (CHIRP 0.1.11)
@@ -865,13 +898,14 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
             return True
         return False
 
+
 @directory.register
 class KGUV6DRadio(KGUVD1PRadio):
     """Wouxun KG-UV6 (D and X variants)"""
     MODEL = "KG-UV6"
-    
+
     _querymodel = ("HiWXUVD1\x02", "HiKGUVD1\x02")
-    
+
     _MEM_FORMAT = """
         #seekto 0x0010;
         struct {
@@ -887,7 +921,10 @@ class KGUV6DRadio(KGUVD1PRadio):
              power_high:1,
              iswide:1,
              _2_unknown_2:4;
-          u8 pad[2];
+          u8 pad;
+          u8 _0_unknown_1:3,
+             iswidex:1,
+             _0_unknown_2:4;
         } memory[199];
 
         #seekto 0x0F00;
@@ -989,7 +1026,7 @@ class KGUV6DRadio(KGUVD1PRadio):
              _2_unknown_4:4;
           u8 pad[2];
         } vfo_settings[2];
-	
+
         #seekto 0x0f82;
         u16 fm_presets_0[9];
 
@@ -1023,69 +1060,70 @@ class KGUV6DRadio(KGUVD1PRadio):
         u16 fm_presets_1[9];
     """
 
-
     def get_features(self):
         rf = KGUVD1PRadio.get_features(self)
         rf.memory_bounds = (1, 199)
         return rf
 
     def get_settings(self):
-        freqranges = RadioSettingGroup("freqranges", "Freq ranges")
+        freq_ranges = RadioSettingGroup("freq_ranges", "Freq Ranges")
         fm_preset = RadioSettingGroup("fm_preset", "FM Presets")
-        top = RadioSettingGroup("top", "All Settings", freqranges, fm_preset)
+        cfg_s = RadioSettingGroup("cfg_settings", "Configuration Settings")
+        group = RadioSettings(cfg_s, freq_ranges, fm_preset)
 
         rs = RadioSetting("menu_available", "Menu Available",
                           RadioSettingValueBoolean(
-                            self._memobj.settings.menu_available))
-        top.append(rs)
+                              self._memobj.settings.menu_available))
+        cfg_s.append(rs)
 
         rs = RadioSetting("vhf_rx_start", "VHF RX Lower Limit (MHz)",
-                          RadioSettingValueInteger(1, 1000, 
-                                decode_freq(
-                                    self._memobj.freq_ranges.vhf_rx_start)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              1, 1000, decode_freq(
+                                  self._memobj.freq_ranges.vhf_rx_start)))
+        freq_ranges.append(rs)
         rs = RadioSetting("vhf_rx_stop", "VHF RX Upper Limit (MHz)",
-                          RadioSettingValueInteger(1, 1000, 
-                                decode_freq(
-                                    self._memobj.freq_ranges.vhf_rx_stop)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              1, 1000, decode_freq(
+                                  self._memobj.freq_ranges.vhf_rx_stop)))
+        freq_ranges.append(rs)
         rs = RadioSetting("uhf_rx_start", "UHF RX Lower Limit (MHz)",
-                          RadioSettingValueInteger(1, 1000, 
-                                decode_freq(
-                                    self._memobj.freq_ranges.uhf_rx_start)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              1, 1000, decode_freq(
+                                  self._memobj.freq_ranges.uhf_rx_start)))
+        freq_ranges.append(rs)
         rs = RadioSetting("uhf_rx_stop", "UHF RX Upper Limit (MHz)",
-                          RadioSettingValueInteger(1, 1000, 
-                                decode_freq(
-                                    self._memobj.freq_ranges.uhf_rx_stop)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              1, 1000, decode_freq(
+                                  self._memobj.freq_ranges.uhf_rx_stop)))
+        freq_ranges.append(rs)
         rs = RadioSetting("vhf_tx_start", "VHF TX Lower Limit (MHz)",
-                          RadioSettingValueInteger(1, 1000, 
-                                decode_freq(
-                                    self._memobj.freq_ranges.vhf_tx_start)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              1, 1000, decode_freq(
+                                  self._memobj.freq_ranges.vhf_tx_start)))
+        freq_ranges.append(rs)
         rs = RadioSetting("vhf_tx_stop", "VHF TX Upper Limit (MHz)",
-                          RadioSettingValueInteger(1, 1000, 
-                                decode_freq(
-                                    self._memobj.freq_ranges.vhf_tx_stop)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              1, 1000, decode_freq(
+                                  self._memobj.freq_ranges.vhf_tx_stop)))
+        freq_ranges.append(rs)
         rs = RadioSetting("uhf_tx_start", "UHF TX Lower Limit (MHz)",
-                          RadioSettingValueInteger(1, 1000, 
-                                decode_freq(
-                                    self._memobj.freq_ranges.uhf_tx_start)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              1, 1000, decode_freq(
+                                  self._memobj.freq_ranges.uhf_tx_start)))
+        freq_ranges.append(rs)
         rs = RadioSetting("uhf_tx_stop", "UHF TX Upper Limit (MHz)",
-                          RadioSettingValueInteger(1, 1000, 
-                                decode_freq(
-                                    self._memobj.freq_ranges.uhf_tx_stop)))
-        freqranges.append(rs)
-        
+                          RadioSettingValueInteger(
+                              1, 1000, decode_freq(
+                                  self._memobj.freq_ranges.uhf_tx_stop)))
+        freq_ranges.append(rs)
+
         # tell the decoded ranges to UI
-        self.valid_freq = [
-            ( decode_freq(self._memobj.freq_ranges.vhf_rx_start) * 1000000, 
-             (decode_freq(self._memobj.freq_ranges.vhf_rx_stop)+1) * 1000000), 
-            ( decode_freq(self._memobj.freq_ranges.uhf_rx_start)  * 1000000,
-             (decode_freq(self._memobj.freq_ranges.uhf_rx_stop)+1) * 1000000)]
+        freq_ranges = self._memobj.freq_ranges
+        self.valid_freq = \
+            [(decode_freq(freq_ranges.vhf_rx_start) * 1000000,
+             (decode_freq(freq_ranges.vhf_rx_stop) + 1) * 1000000),
+             (decode_freq(freq_ranges.uhf_rx_start) * 1000000,
+             (decode_freq(freq_ranges.uhf_rx_stop) + 1) * 1000000)]
 
         def _filter(name):
             filtered = ""
@@ -1097,156 +1135,188 @@ class KGUV6DRadio(KGUVD1PRadio):
             return filtered
 
         # add some radio specific settings
-        options = ["Off", "Welcome", "V bat"]
+        options = ["Off", "Welcome", "V bat", "N/A(KG-UV6X)"]
         rs = RadioSetting("ponmsg", "Poweron message",
-                          RadioSettingValueList(options,
-                                        options[self._memobj.settings.ponmsg]))
-        top.append(rs)
+                          RadioSettingValueList(
+                              options, options[self._memobj.settings.ponmsg]))
+        cfg_s.append(rs)
         rs = RadioSetting("strings.welcome1", "Power-On Message 1",
-                          RadioSettingValueString(0, 6, _filter(self._memobj.strings.welcome1)))
-        top.append(rs)
+                          RadioSettingValueString(
+                              0, 6, _filter(self._memobj.strings.welcome1)))
+        cfg_s.append(rs)
         rs = RadioSetting("strings.welcome2", "Power-On Message 2",
-                          RadioSettingValueString(0, 6, _filter(self._memobj.strings.welcome2)))
-        top.append(rs)
+                          RadioSettingValueString(
+                              0, 6, _filter(self._memobj.strings.welcome2)))
+        cfg_s.append(rs)
         rs = RadioSetting("strings.single_band", "Single Band Message",
-                          RadioSettingValueString(0, 6, _filter(self._memobj.strings.single_band)))
-        top.append(rs)
-        options = ["Channel", "ch/freq","Name", "VFO"]
+                          RadioSettingValueString(
+                              0, 6, _filter(self._memobj.strings.single_band)))
+        cfg_s.append(rs)
+        options = ["Channel", "ch/freq", "Name", "VFO"]
         rs = RadioSetting("vfo_a_ch_disp", "VFO A Channel disp mode",
-                          RadioSettingValueList(options,
-                                        options[self._memobj.settings.vfo_a_ch_disp]))
-        top.append(rs)
+                          RadioSettingValueList(
+                              options,
+                              options[self._memobj.settings.vfo_a_ch_disp]))
+        cfg_s.append(rs)
         rs = RadioSetting("vfo_b_ch_disp", "VFO B Channel disp mode",
-                          RadioSettingValueList(options,
-                                        options[self._memobj.settings.vfo_b_ch_disp]))
-        top.append(rs)
-        options = ["2.5", "5.0", "6.25", "10.0", "12.5", "25.0", "50.0", "100.0"]
+                          RadioSettingValueList(
+                              options,
+                              options[self._memobj.settings.vfo_b_ch_disp]))
+        cfg_s.append(rs)
+        options = \
+            ["2.5", "5.0", "6.25", "10.0", "12.5", "25.0", "50.0", "100.0"]
         rs = RadioSetting("vfo_a_fr_step", "VFO A Frequency Step",
-                          RadioSettingValueList(options,
-                                        options[self._memobj.settings.vfo_a_fr_step]))
-        top.append(rs)
+                          RadioSettingValueList(
+                              options,
+                              options[self._memobj.settings.vfo_a_fr_step]))
+        cfg_s.append(rs)
         rs = RadioSetting("vfo_b_fr_step", "VFO B Frequency Step",
-                          RadioSettingValueList(options,
-                                        options[self._memobj.settings.vfo_b_fr_step]))
-        top.append(rs)
+                          RadioSettingValueList(
+                              options,
+                              options[self._memobj.settings.vfo_b_fr_step]))
+        cfg_s.append(rs)
         rs = RadioSetting("vfo_a_squelch", "VFO A Squelch",
-                          RadioSettingValueInteger(0, 9, self._memobj.settings.vfo_a_squelch))
-        top.append(rs)
+                          RadioSettingValueInteger(
+                              0, 9, self._memobj.settings.vfo_a_squelch))
+        cfg_s.append(rs)
         rs = RadioSetting("vfo_b_squelch", "VFO B Squelch",
-                          RadioSettingValueInteger(0, 9, self._memobj.settings.vfo_b_squelch))
-        top.append(rs)
+                          RadioSettingValueInteger(
+                              0, 9, self._memobj.settings.vfo_b_squelch))
+        cfg_s.append(rs)
         rs = RadioSetting("vfo_a_cur_chan", "VFO A current channel",
-                          RadioSettingValueInteger(1, 199, self._memobj.settings.vfo_a_cur_chan))
-        top.append(rs)
+                          RadioSettingValueInteger(
+                              1, 199, self._memobj.settings.vfo_a_cur_chan))
+        cfg_s.append(rs)
         rs = RadioSetting("vfo_b_cur_chan", "VFO B current channel",
-                          RadioSettingValueInteger(1, 199, self._memobj.settings.vfo_b_cur_chan))
-        top.append(rs)
+                          RadioSettingValueInteger(
+                              1, 199, self._memobj.settings.vfo_b_cur_chan))
+        cfg_s.append(rs)
         rs = RadioSetting("priority_chan", "Priority channel",
-                          RadioSettingValueInteger(0, 199, self._memobj.settings.priority_chan))
-        top.append(rs)
+                          RadioSettingValueInteger(
+                              0, 199, self._memobj.settings.priority_chan))
+        cfg_s.append(rs)
         rs = RadioSetting("power_save", "Power save",
-                          RadioSettingValueBoolean(self._memobj.settings.power_save))
-        top.append(rs)
+                          RadioSettingValueBoolean(
+                              self._memobj.settings.power_save))
+        cfg_s.append(rs)
         options = ["Off", "Scan", "Lamp", "SOS", "Radio"]
         rs = RadioSetting("pf1_function", "PF1 Function select",
-                          RadioSettingValueList(options,
-                                        options[self._memobj.settings.pf1_function]))
-        top.append(rs)
+                          RadioSettingValueList(
+                              options,
+                              options[self._memobj.settings.pf1_function]))
+        cfg_s.append(rs)
         options = ["Off", "Radio", "fr/ch", "Rpt", "Stopwatch", "Lamp", "SOS"]
         rs = RadioSetting("pf2_function", "PF2 Function select",
-                          RadioSettingValueList(options,
-                                        options[self._memobj.settings.pf2_function]))
-        top.append(rs)
+                          RadioSettingValueList(
+                              options,
+                              options[self._memobj.settings.pf2_function]))
+        cfg_s.append(rs)
         options = ["Off", "Begin", "End", "Both"]
         rs = RadioSetting("roger_beep", "Roger beep select",
-                          RadioSettingValueList(options,
-                                        options[self._memobj.settings.roger_beep]))
-        top.append(rs)
+                          RadioSettingValueList(
+                              options,
+                              options[self._memobj.settings.roger_beep]))
+        cfg_s.append(rs)
         options = ["%s" % x for x in range(15, 615, 15)]
+        transmit_time_out = options[self._memobj.settings.transmit_time_out]
         rs = RadioSetting("transmit_time_out", "TX Time-out Timer",
-                          RadioSettingValueList(options,
-                                        options[self._memobj.settings.transmit_time_out]))
-        top.append(rs)
+                          RadioSettingValueList(
+                              options, transmit_time_out))
+        cfg_s.append(rs)
         rs = RadioSetting("tx_time_out_alert", "TX Time-out Alert",
-                          RadioSettingValueInteger(0, 10, self._memobj.settings.tx_time_out_alert))
-        top.append(rs)
+                          RadioSettingValueInteger(
+                              0, 10, self._memobj.settings.tx_time_out_alert))
+        cfg_s.append(rs)
         rs = RadioSetting("vox", "Vox",
-                          RadioSettingValueInteger(0, 10, self._memobj.settings.vox))
-        top.append(rs)
+                          RadioSettingValueInteger(
+                              0, 10, self._memobj.settings.vox))
+        cfg_s.append(rs)
         options = ["Off", "Chinese", "English"]
         rs = RadioSetting("voice", "Voice",
-                          RadioSettingValueList(options,
-                                        options[self._memobj.settings.voice]))
-        top.append(rs)
+                          RadioSettingValueList(
+                              options, options[self._memobj.settings.voice]))
+        cfg_s.append(rs)
         rs = RadioSetting("beep", "Beep",
-                          RadioSettingValueBoolean(self._memobj.settings.beep))
-        top.append(rs)
+                          RadioSettingValueBoolean(
+                              self._memobj.settings.beep))
+        cfg_s.append(rs)
         rs = RadioSetting("ani_id_enable", "ANI id enable",
-                          RadioSettingValueBoolean(self._memobj.settings.ani_id_enable))
-        top.append(rs)
+                          RadioSettingValueBoolean(
+                              self._memobj.settings.ani_id_enable))
+        cfg_s.append(rs)
         rs = RadioSetting("ani_id_tx_delay", "ANI id tx delay",
-                          RadioSettingValueInteger(0, 30, self._memobj.settings.ani_id_tx_delay))
-        top.append(rs)
+                          RadioSettingValueInteger(
+                              0, 30, self._memobj.settings.ani_id_tx_delay))
+        cfg_s.append(rs)
         options = ["Off", "Key", "ANI", "Key+ANI"]
         rs = RadioSetting("ani_id_sidetone", "ANI id sidetone",
-                          RadioSettingValueList(options,
-                                        options[self._memobj.settings.ani_id_sidetone]))
-        top.append(rs)
+                          RadioSettingValueList(
+                              options,
+                              options[self._memobj.settings.ani_id_sidetone]))
+        cfg_s.append(rs)
         options = ["Time", "Carrier", "Search"]
         rs = RadioSetting("scan_mode", "Scan mode",
-                          RadioSettingValueList(options,
-                                        options[self._memobj.settings.scan_mode]))
-        top.append(rs)
+                          RadioSettingValueList(
+                              options,
+                              options[self._memobj.settings.scan_mode]))
+        cfg_s.append(rs)
         rs = RadioSetting("kbd_lock", "Keyboard lock",
-                          RadioSettingValueBoolean(self._memobj.settings.kbd_lock))
-        top.append(rs)
+                          RadioSettingValueBoolean(
+                              self._memobj.settings.kbd_lock))
+        cfg_s.append(rs)
         rs = RadioSetting("auto_lock_kbd", "Auto lock keyboard",
-                          RadioSettingValueBoolean(self._memobj.settings.auto_lock_kbd))
-        top.append(rs)
+                          RadioSettingValueBoolean(
+                              self._memobj.settings.auto_lock_kbd))
+        cfg_s.append(rs)
         rs = RadioSetting("auto_backlight", "Auto backlight",
-                          RadioSettingValueBoolean(self._memobj.settings.auto_backlight))
-        top.append(rs)
+                          RadioSettingValueBoolean(
+                              self._memobj.settings.auto_backlight))
+        cfg_s.append(rs)
         options = ["CH A", "CH B"]
         rs = RadioSetting("sos_ch", "SOS CH",
-                          RadioSettingValueList(options,
-                                        options[self._memobj.settings.sos_ch]))
-        top.append(rs)
+                          RadioSettingValueList(
+                              options, options[self._memobj.settings.sos_ch]))
+        cfg_s.append(rs)
         rs = RadioSetting("stopwatch", "Stopwatch",
-                          RadioSettingValueBoolean(self._memobj.settings.stopwatch))
-        top.append(rs)
+                          RadioSettingValueBoolean(
+                              self._memobj.settings.stopwatch))
+        cfg_s.append(rs)
         rs = RadioSetting("dual_band_receive", "Dual band receive",
-                          RadioSettingValueBoolean(self._memobj.settings.dual_band_receive))
-        top.append(rs)
+                          RadioSettingValueBoolean(
+                              self._memobj.settings.dual_band_receive))
+        cfg_s.append(rs)
         options = ["VFO A", "VFO B"]
         rs = RadioSetting("current_vfo", "Current VFO",
-                          RadioSettingValueList(options,
-                                        options[self._memobj.settings.current_vfo]))
-        top.append(rs)
+                          RadioSettingValueList(
+                              options,
+                              options[self._memobj.settings.current_vfo]))
+        cfg_s.append(rs)
 
         options = ["Dual", "Single"]
         rs = RadioSetting("sd_available", "Single/Dual Band",
-                          RadioSettingValueList(options,
-                                        options[self._memobj.settings.sd_available]))
-        top.append(rs)
+                          RadioSettingValueList(
+                              options,
+                              options[self._memobj.settings.sd_available]))
+        cfg_s.append(rs)
 
         _pwd = self._memobj.settings.mode_password
         rs = RadioSetting("mode_password", "Mode password (000000 disabled)",
-                  RadioSettingValueInteger(0, 9, _pwd[0]),
-                  RadioSettingValueInteger(0, 9, _pwd[1]),
-                  RadioSettingValueInteger(0, 9, _pwd[2]),
-                  RadioSettingValueInteger(0, 9, _pwd[3]),
-                  RadioSettingValueInteger(0, 9, _pwd[4]),
-                  RadioSettingValueInteger(0, 9, _pwd[5]))
-        top.append(rs)
+                          RadioSettingValueInteger(0, 9, _pwd[0]),
+                          RadioSettingValueInteger(0, 9, _pwd[1]),
+                          RadioSettingValueInteger(0, 9, _pwd[2]),
+                          RadioSettingValueInteger(0, 9, _pwd[3]),
+                          RadioSettingValueInteger(0, 9, _pwd[4]),
+                          RadioSettingValueInteger(0, 9, _pwd[5]))
+        cfg_s.append(rs)
         _pwd = self._memobj.settings.reset_password
         rs = RadioSetting("reset_password", "Reset password (000000 disabled)",
-                  RadioSettingValueInteger(0, 9, _pwd[0]),
-                  RadioSettingValueInteger(0, 9, _pwd[1]),
-                  RadioSettingValueInteger(0, 9, _pwd[2]),
-                  RadioSettingValueInteger(0, 9, _pwd[3]),
-                  RadioSettingValueInteger(0, 9, _pwd[4]),
-                  RadioSettingValueInteger(0, 9, _pwd[5]))
-        top.append(rs)
+                          RadioSettingValueInteger(0, 9, _pwd[0]),
+                          RadioSettingValueInteger(0, 9, _pwd[1]),
+                          RadioSettingValueInteger(0, 9, _pwd[2]),
+                          RadioSettingValueInteger(0, 9, _pwd[3]),
+                          RadioSettingValueInteger(0, 9, _pwd[4]),
+                          RadioSettingValueInteger(0, 9, _pwd[5]))
+        cfg_s.append(rs)
 
         dtmfchars = "0123456789 *#ABCD"
         _codeobj = self._memobj.settings.ani_id_content
@@ -1254,6 +1324,7 @@ class KGUV6DRadio(KGUVD1PRadio):
         val = RadioSettingValueString(0, 6, _code, False)
         val.set_charset(dtmfchars)
         rs = RadioSetting("settings.ani_id_content", "ANI Code", val)
+
         def apply_ani_id(setting, obj):
             value = []
             for j in range(0, 6):
@@ -1263,7 +1334,7 @@ class KGUV6DRadio(KGUVD1PRadio):
                     value.append(0xFF)
             obj.ani_id_content = value
         rs.set_apply_callback(apply_ani_id, self._memobj.settings)
-        top.append(rs)
+        cfg_s.append(rs)
 
         for i in range(0, 9):
             if self._memobj.fm_presets_0[i] != 0xFFFF:
@@ -1272,9 +1343,10 @@ class KGUV6DRadio(KGUVD1PRadio):
             else:
                 used = False
                 preset = 76
-            rs = RadioSetting("fm_presets_0_%1i" % i, "Team 1 Location %i" % (i+1),
-                          RadioSettingValueBoolean(used),
-                          RadioSettingValueFloat(76, 108, preset, 0.1, 1))
+            rs = RadioSetting("fm_presets_0_%1i" % i,
+                              "Team 1 Location %i" % (i+1),
+                              RadioSettingValueBoolean(used),
+                              RadioSettingValueFloat(76, 108, preset, 0.1, 1))
             fm_preset.append(rs)
         for i in range(0, 9):
             if self._memobj.fm_presets_1[i] != 0xFFFF:
@@ -1283,19 +1355,20 @@ class KGUV6DRadio(KGUVD1PRadio):
             else:
                 used = False
                 preset = 76
-            rs = RadioSetting("fm_presets_1_%1i" % i, "Team 2 Location %i" % (i+1),
-                          RadioSettingValueBoolean(used),
-                          RadioSettingValueFloat(76, 108, preset, 0.1, 1))
+            rs = RadioSetting("fm_presets_1_%1i" % i,
+                              "Team 2 Location %i" % (i+1),
+                              RadioSettingValueBoolean(used),
+                              RadioSettingValueFloat(76, 108, preset, 0.1, 1))
             fm_preset.append(rs)
 
-        return top
+        return group
 
     def set_settings(self, settings):
         for element in settings:
             if not isinstance(element, RadioSetting):
-                if element.get_name() == "freqranges" :
+                if element.get_name() == "freq_ranges":
                     self._set_freq_settings(element)
-                elif element.get_name() == "fm_preset" :
+                elif element.get_name() == "fm_preset":
                     self._set_fm_preset(element)
                 else:
                     self.set_settings(element)
@@ -1312,33 +1385,35 @@ class KGUV6DRadio(KGUVD1PRadio):
                         setting = element.get_name()
 
                     if element.has_apply_callback():
-                        print "Using apply callback"
+                        LOG.debug("Using apply callback")
                         element.run_apply_callback()
                     else:
-                        print "Setting %s = %s" % (setting, element.value)
+                        LOG.debug("Setting %s = %s" % (setting, element.value))
                         setattr(obj, setting, element.value)
                 except Exception, e:
-                    print element.get_name()
+                    LOG.debug(element.get_name())
                     raise
 
     def _set_fm_preset(self, settings):
         obj = self._memobj
         for element in settings:
             try:
-                (bank, index) = (int(a) for a in element.get_name().split("_")[-2:])
+                (bank, index) = \
+                    (int(a) for a in element.get_name().split("_")[-2:])
                 val = element.value
                 if val[0].get_value():
                     value = int(val[1].get_value()*10-760)
                 else:
                     value = 0xffff
-                print "Setting fm_presets_%1i[%1i] = %s" % (bank, index, value)
+                LOG.debug("Setting fm_presets_%1i[%1i] = %s" %
+                          (bank, index, value))
                 if bank == 0:
                     setting = self._memobj.fm_presets_0
                 else:
                     setting = self._memobj.fm_presets_1
                 setting[index] = value
             except Exception, e:
-                print element.get_name()
+                LOG.debug(element.get_name())
                 raise
 
     @classmethod
@@ -1348,14 +1423,14 @@ class KGUV6DRadio(KGUVD1PRadio):
             return True
         return False
 
+
 @directory.register
-class KG816Radio(KGUVD1PRadio,
-        chirp_common.ExperimentalRadio):
+class KG816Radio(KGUVD1PRadio, chirp_common.ExperimentalRadio):
     """Wouxun KG-816"""
     MODEL = "KG-816"
 
     _querymodel = "HiWOUXUN\x02"
-    
+
     _MEM_FORMAT = """
         #seekto 0x0010;
         struct {
@@ -1371,9 +1446,12 @@ class KG816Radio(KGUVD1PRadio,
              power_high:1,
              iswide:1,
              _2_unknown_2:4;
-          u8 unknown[2];
+          u8 unknown;
+          u8 _0_unknown_1:3,
+             iswidex:1,
+             _0_unknown_2:4;
         } memory[199];
-        
+
         #seekto 0x0d70;
         struct {
             u16 vhf_rx_start;
@@ -1388,10 +1466,9 @@ class KG816Radio(KGUVD1PRadio,
 
         #seekto 0x1010;
         struct {
-		u8 name[6];
-		u8 pad[10];
+            u8 name[6];
+            u8 pad[10];
         } names[199];
-	
     """
 
     @classmethod
@@ -1401,71 +1478,74 @@ class KG816Radio(KGUVD1PRadio,
                 'organization of KGUVD1 but uses 199 memories. '
                 'it has been reported to work but '
                 'proceed at your own risk!')
-    
+
     def get_features(self):
         rf = KGUVD1PRadio.get_features(self)
-        rf.memory_bounds = (1, 199) # this is the only known difference
+        rf.memory_bounds = (1, 199)  # this is the only known difference
         return rf
 
     def get_settings(self):
-        freqranges = RadioSettingGroup("freqranges", "Freq ranges (read only)")
-        top = RadioSettingGroup("top", "All Settings", freqranges)
+        freq_ranges = RadioSettingGroup("freq_ranges",
+                                        "Freq Ranges (read only)")
+        group = RadioSettings(freq_ranges)
 
         rs = RadioSetting("vhf_rx_start", "vhf rx start",
-                          RadioSettingValueInteger(66, 520,
-                                decode_freq(
-                                    self._memobj.freq_ranges.vhf_rx_start)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              66, 520, decode_freq(
+                                  self._memobj.freq_ranges.vhf_rx_start)))
+        freq_ranges.append(rs)
         rs = RadioSetting("vhf_rx_stop", "vhf rx stop",
-                          RadioSettingValueInteger(66, 520,
-                                decode_freq(
-                                    self._memobj.freq_ranges.vhf_rx_stop)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              66, 520, decode_freq(
+                                  self._memobj.freq_ranges.vhf_rx_stop)))
+        freq_ranges.append(rs)
         rs = RadioSetting("uhf_rx_start", "uhf rx start",
-                          RadioSettingValueInteger(66, 520,
-                                decode_freq(
-                                    self._memobj.freq_ranges.uhf_rx_start)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              66, 520, decode_freq(
+                                  self._memobj.freq_ranges.uhf_rx_start)))
+        freq_ranges.append(rs)
         rs = RadioSetting("uhf_rx_stop", "uhf rx stop",
-                          RadioSettingValueInteger(66, 520,
-                                decode_freq(
-                                    self._memobj.freq_ranges.uhf_rx_stop)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              66, 520, decode_freq(
+                                  self._memobj.freq_ranges.uhf_rx_stop)))
+        freq_ranges.append(rs)
         rs = RadioSetting("vhf_tx_start", "vhf tx start",
-                          RadioSettingValueInteger(66, 520,
-                                decode_freq(
-                                    self._memobj.freq_ranges.vhf_tx_start)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              66, 520, decode_freq(
+                                  self._memobj.freq_ranges.vhf_tx_start)))
+        freq_ranges.append(rs)
         rs = RadioSetting("vhf_tx_stop", "vhf tx stop",
-                          RadioSettingValueInteger(66, 520,
-                                decode_freq(
-                                    self._memobj.freq_ranges.vhf_tx_stop)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              66, 520, decode_freq(
+                                  self._memobj.freq_ranges.vhf_tx_stop)))
+        freq_ranges.append(rs)
         rs = RadioSetting("uhf_tx_start", "uhf tx start",
-                          RadioSettingValueInteger(66, 520,
-                                decode_freq(
-                                    self._memobj.freq_ranges.uhf_tx_start)))
-        freqranges.append(rs)
+                          RadioSettingValueInteger(
+                              66, 520, decode_freq(
+                                  self._memobj.freq_ranges.uhf_tx_start)))
+        freq_ranges.append(rs)
         rs = RadioSetting("uhf_tx_stop", "uhf tx stop",
-                          RadioSettingValueInteger(66, 520,
-                                decode_freq(
-                                    self._memobj.freq_ranges.uhf_tx_stop)))
-        freqranges.append(rs)
-        
+                          RadioSettingValueInteger(
+                              66, 520, decode_freq(
+                                  self._memobj.freq_ranges.uhf_tx_stop)))
+        freq_ranges.append(rs)
+
         # tell the decoded ranges to UI
-        self.valid_freq = [
-            ( decode_freq(self._memobj.freq_ranges.vhf_rx_start) * 1000000, 
+        self.valid_freq = \
+            [(decode_freq(self._memobj.freq_ranges.vhf_rx_start) * 1000000,
              (decode_freq(self._memobj.freq_ranges.vhf_rx_stop)+1) * 1000000)]
 
-        return top
+        return group
 
     @classmethod
     def match_model(cls, filedata, filename):
         if len(filedata) == 8192 and \
                 filedata[0x60:0x64] != "2009" and \
+                filedata[0x170:0x173] != "LX-" and \
+                filedata[0xF7E:0xF80] != "\x01\xE2" and \
                 filedata[0x1f77:0x1f7d] == "\xff\xff\xff\xff\xff\xff" and \
                 filedata[0x0d70:0x0d80] != "\xff\xff\xff\xff\xff\xff\xff\xff" \
-                                           "\xff\xff\xff\xff\xff\xff\xff\xff": 
+                                           "\xff\xff\xff\xff\xff\xff\xff\xff":
             return True
         return False
 
diff --git a/chirp/wouxun_common.py b/chirp/drivers/wouxun_common.py
similarity index 85%
rename from chirp/wouxun_common.py
rename to chirp/drivers/wouxun_common.py
index 3c78b3a..ec65d38 100644
--- a/chirp/wouxun_common.py
+++ b/chirp/drivers/wouxun_common.py
@@ -18,41 +18,45 @@
 
 import struct
 import os
+import logging
 from chirp import util, chirp_common, memmap
 
+LOG = logging.getLogger(__name__)
+
+
 def wipe_memory(_mem, byte):
     """Cleanup a memory"""
     _mem.set_raw(byte * (_mem.size() / 8))
-    
+
+
 def do_download(radio, start, end, blocksize):
     """Initiate a download of @radio between @start and @end"""
     image = ""
     for i in range(start, end, blocksize):
         cmd = struct.pack(">cHb", "R", i, blocksize)
-        if os.getenv("CHIRP_DEBUG"):
-            print util.hexprint(cmd)
+        LOG.debug(util.hexprint(cmd))
         radio.pipe.write(cmd)
         length = len(cmd) + blocksize
         resp = radio.pipe.read(length)
         if len(resp) != (len(cmd) + blocksize):
-            print util.hexprint(resp)
-            raise Exception("Failed to read full block (%i!=%i)" % \
-                                (len(resp),
-                                 len(cmd) + blocksize))
-        
+            LOG.debug(util.hexprint(resp))
+            raise Exception("Failed to read full block (%i!=%i)" %
+                            (len(resp), len(cmd) + blocksize))
+
         radio.pipe.write("\x06")
         radio.pipe.read(1)
         image += resp[4:]
 
         if radio.status_fn:
-            status = chirp_common.Status()           
+            status = chirp_common.Status()
             status.cur = i
             status.max = end
             status.msg = "Cloning from radio"
             radio.status_fn(status)
-    
+
     return memmap.MemoryMap(image)
 
+
 def do_upload(radio, start, end, blocksize):
     """Initiate an upload of @radio between @start and @end"""
     ptr = start
@@ -61,13 +65,12 @@ def do_upload(radio, start, end, blocksize):
         chunk = radio.get_mmap()[ptr:ptr+blocksize]
         ptr += blocksize
         radio.pipe.write(cmd + chunk)
-        if os.getenv("CHIRP_DEBUG"):
-            print util.hexprint(cmd + chunk)
+        LOG.debug(util.hexprint(cmd + chunk))
 
         ack = radio.pipe.read(1)
         if not ack == "\x06":
             raise Exception("Radio did not ack block %i" % ptr)
-        #radio.pipe.write(ack)
+        # radio.pipe.write(ack)
 
         if radio.status_fn:
             status = chirp_common.Status()
@@ -75,5 +78,3 @@ def do_upload(radio, start, end, blocksize):
             status.max = end
             status.msg = "Cloning to radio"
             radio.status_fn(status)
-
-
diff --git a/chirp/yaesu_clone.py b/chirp/drivers/yaesu_clone.py
similarity index 85%
rename from chirp/yaesu_clone.py
rename to chirp/drivers/yaesu_clone.py
index fd8419b..fd18507 100644
--- a/chirp/yaesu_clone.py
+++ b/chirp/drivers/yaesu_clone.py
@@ -13,28 +13,35 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-CMD_ACK = 0x06
+import time
+import os
+import logging
+from textwrap import dedent
 
 from chirp import chirp_common, util, memmap, errors
-import time, os
-from textwrap import dedent
+
+LOG = logging.getLogger(__name__)
+
+CMD_ACK = 0x06
+
 
 def _safe_read(pipe, count):
     buf = ""
     first = True
     for _i in range(0, 60):
         buf += pipe.read(count - len(buf))
-        #print "safe_read: %i/%i\n" % (len(buf), count)
+        # LOG.debug("safe_read: %i/%i\n" % (len(buf), count))
         if buf:
             if first and buf[0] == chr(CMD_ACK):
-                #print "Chewed an ack"
-                buf = buf[1:] # Chew an echo'd ack if using a 2-pin cable
+                # LOG.debug("Chewed an ack")
+                buf = buf[1:]  # Chew an echo'd ack if using a 2-pin cable
             first = False
         if len(buf) == count:
             break
-    print util.hexprint(buf)
+    LOG.debug(util.hexprint(buf))
     return buf
 
+
 def _chunk_read(pipe, count, status_fn):
     block = 32
     data = ""
@@ -42,20 +49,24 @@ def _chunk_read(pipe, count, status_fn):
         data += pipe.read(block)
         if data:
             if data[0] == chr(CMD_ACK):
-                data = data[1:] # Chew an echo'd ack if using a 2-pin cable
-                #print "Chewed an ack"
+                data = data[1:]  # Chew an echo'd ack if using a 2-pin cable
+                # LOG.debug("Chewed an ack")
         status = chirp_common.Status()
         status.msg = "Cloning from radio"
         status.max = count
         status.cur = len(data)
         status_fn(status)
-        if os.getenv("CHIRP_DEBUG"):
-            print "Read %i/%i" % (len(data), count)
-    return data        
+        LOG.debug("Read %i/%i" % (len(data), count))
+    return data
+
 
 def __clone_in(radio):
     pipe = radio.pipe
 
+    status = chirp_common.Status()
+    status.msg = "Cloning from radio"
+    status.max = radio.get_memsize()
+
     start = time.time()
 
     data = ""
@@ -69,21 +80,26 @@ def __clone_in(radio):
             pipe.write(chr(CMD_ACK))
         if not chunk:
             raise errors.RadioError("No response from radio")
+        if radio.status_fn:
+            status.cur = len(data)
+            radio.status_fn(status)
         data += chunk
 
     if len(data) != radio.get_memsize():
         raise errors.RadioError("Received incomplete image from radio")
 
-    print "Clone completed in %i seconds" % (time.time() - start)
+    LOG.debug("Clone completed in %i seconds" % (time.time() - start))
 
     return memmap.MemoryMap(data)
 
+
 def _clone_in(radio):
     try:
         return __clone_in(radio)
     except Exception, e:
         raise errors.RadioError("Failed to communicate with the radio: %s" % e)
 
+
 def _chunk_write(pipe, data, status_fn, block):
     delay = 0.03
     count = 0
@@ -91,7 +107,7 @@ def _chunk_write(pipe, data, status_fn, block):
         chunk = data[i:i+block]
         pipe.write(chunk)
         count += len(chunk)
-        #print "Count is %i" % count
+        LOG.debug("@_chunk_write, count: %i, blocksize: %i" % (count, block))
         time.sleep(delay)
 
         status = chirp_common.Status()
@@ -99,7 +115,8 @@ def _chunk_write(pipe, data, status_fn, block):
         status.max = len(data)
         status.cur = count
         status_fn(status)
-        
+
+
 def __clone_out(radio):
     pipe = radio.pipe
     block_lengths = radio._block_lengths
@@ -119,7 +136,7 @@ def __clone_out(radio):
     for block in radio._block_lengths:
         blocks += 1
         if blocks != len(radio._block_lengths):
-            #print "Sending %i-%i" % (pos, pos+block)
+            LOG.debug("Sending %i-%i" % (pos, pos+block))
             pipe.write(radio.get_mmap()[pos:pos+block])
             buf = pipe.read(1)
             if buf and buf[0] != chr(CMD_ACK):
@@ -131,9 +148,10 @@ def __clone_out(radio):
                          radio.status_fn, radio._block_size)
         pos += block
 
-    pipe.read(pos) # Chew the echo if using a 2-pin cable
+    pipe.read(pos)  # Chew the echo if using a 2-pin cable
+
+    LOG.debug("Clone completed in %i seconds" % (time.time() - start))
 
-    print "Clone completed in %i seconds" % (time.time() - start)
 
 def _clone_out(radio):
     try:
@@ -141,6 +159,7 @@ def _clone_out(radio):
     except Exception, e:
         raise errors.RadioError("Failed to communicate with the radio: %s" % e)
 
+
 class YaesuChecksum:
     """A Yaesu Checksum Object"""
     def __init__(self, start, stop, address=None):
@@ -171,6 +190,7 @@ class YaesuChecksum:
                                       self._stop,
                                       self._address)
 
+
 class YaesuCloneModeRadio(chirp_common.CloneModeRadio):
     """Base class for all Yaesu clone-mode radios"""
     _block_lengths = [8, 65536]
@@ -193,7 +213,7 @@ class YaesuCloneModeRadio(chirp_common.CloneModeRadio):
             3. Prepare radio for clone.
             4. Press the key to receive the image."""))
         return rp
-        
+
     def _checksums(self):
         """Return a list of checksum objects that need to be calculated"""
         return []
@@ -209,7 +229,7 @@ class YaesuCloneModeRadio(chirp_common.CloneModeRadio):
             if checksum.get_existing(self._mmap) != \
                     checksum.get_calculated(self._mmap):
                 raise errors.RadioError("Checksum Failed [%s]" % checksum)
-            print "Checksum %s: OK" % checksum
+            LOG.debug("Checksum %s: OK" % checksum)
 
     def sync_in(self):
         self._mmap = _clone_in(self)
diff --git a/chirp/elib_intl.py b/chirp/elib_intl.py
index 6e7cafa..76e3b9b 100644
--- a/chirp/elib_intl.py
+++ b/chirp/elib_intl.py
@@ -19,17 +19,18 @@
 
 
 '''
-The elib.intl module provides enhanced internationalization (I18N) services for
-your Python modules and applications.
+The elib.intl module provides enhanced internationalization (I18N)
+services for your Python modules and applications.
 
-elib.intl wraps Python's :func:`gettext` functionality and adds the following on
-Microsoft Windows systems:
+elib.intl wraps Python's :func:`gettext` functionality and adds the
+following on Microsoft Windows systems:
 
- - automatic detection of the current screen language (not necessarily the same
-   as the installation language) provided by MUI packs,
- - makes sure internationalized C libraries which internally invoke gettext() or
-   dcgettext() can properly locate their message catalogs. This fixes a known
-   limitation in gettext's Windows support when using eg. gtk.builder or gtk.glade.
+ - automatic detection of the current screen language (not necessarily
+   the same as the installation language) provided by MUI packs,
+ - makes sure internationalized C libraries which internally invoke
+   gettext() or dcgettext() can properly locate their message catalogs.
+   This fixes a known limitation in gettext's Windows support when using
+   eg. gtk.builder or gtk.glade.
 
 See http://www.gnu.org/software/gettext/FAQ.html#windows_setenv for more
 information.
@@ -37,12 +38,6 @@ information.
 The elib.intl module defines the following functions:
 '''
 
-
-__all__ = ['install', 'install_module']
-__version__ = '0.0.3'
-__docformat__ = 'restructuredtext'
-
-
 import os
 import sys
 import locale
@@ -50,6 +45,9 @@ import gettext
 
 from logging import getLogger
 
+__all__ = ['install', 'install_module']
+__version__ = '0.0.3'
+__docformat__ = 'restructuredtext'
 
 logger = getLogger('elib.intl')
 
@@ -58,249 +56,254 @@ def _isofromlcid(lcid):
     '''
     :param lcid: Microsoft Windows LCID
     :returns: the ISO 639-1 language code for a given lcid. If there is no
-              ISO 639-1 language code assigned to the language specified by lcid,
-              the ISO 639-2 language code is returned. If the language specified
-              by lcid is unknown in the ISO 639-x database, None is returned.
+              ISO 639-1 language code assigned to the language specified
+              by lcid, the ISO 639-2 language code is returned. If the
+              language specified by lcid is unknown in the ISO 639-x
+              database, None is returned.
 
     More information can be found on the following websites:
-        - List of ISO 639-1 and ISO 639-2 language codes: http://www.loc.gov/standards/iso639-2/
-        - List of known lcid's: http://www.microsoft.com/globaldev/reference/lcid-all.mspx
-        - List of known MUI packs: http://www.microsoft.com/globaldev/reference/win2k/setup/Langid.mspx
+        - List of ISO 639-1 and ISO 639-2 language codes:
+          http://www.loc.gov/standards/iso639-2/
+        - List of known lcid's:
+          http://www.microsoft.com/globaldev/reference/lcid-all.mspx
+        - List of known MUI packs:
+          http://www.microsoft.com/globaldev/reference/win2k/setup/Langid.mspx
     '''
-    mapping = {1078:    'af',  #Afrikaans - South Africa
-               1052:    'sq',  #Albanian - Albania
-               1118:    'am',  #Amharic - Ethiopia
-               1025:    'ar',  #Arabic - Saudi Arabia
-               5121:    'ar',  #Arabic - Algeria
-               15361:   'ar',  #Arabic - Bahrain
-               3073:    'ar',  #Arabic - Egypt
-               2049:    'ar',  #Arabic - Iraq
-               11265:   'ar',  #Arabic - Jordan
-               13313:   'ar',  #Arabic - Kuwait
-               12289:   'ar',  #Arabic - Lebanon
-               4097:    'ar',  #Arabic - Libya
-               6145:    'ar',  #Arabic - Morocco
-               8193:    'ar',  #Arabic - Oman
-               16385:   'ar',  #Arabic - Qatar
-               10241:   'ar',  #Arabic - Syria
-               7169:    'ar',  #Arabic - Tunisia
-               14337:   'ar',  #Arabic - U.A.E.
-               9217:    'ar',  #Arabic - Yemen
-               1067:    'hy',  #Armenian - Armenia
-               1101:    'as',  #Assamese
-               2092:    'az',  #Azeri (Cyrillic)
-               1068:    'az',  #Azeri (Latin)
-               1069:    'eu',  #Basque
-               1059:    'be',  #Belarusian
-               1093:    'bn',  #Bengali (India)
-               2117:    'bn',  #Bengali (Bangladesh)
-               5146:    'bs',  #Bosnian (Bosnia/Herzegovina)
-               1026:    'bg',  #Bulgarian
-               1109:    'my',  #Burmese
-               1027:    'ca',  #Catalan
-               1116:    'chr', #Cherokee - United States
-               2052:    'zh',  #Chinese - People's Republic of China
-               4100:    'zh',  #Chinese - Singapore
-               1028:    'zh',  #Chinese - Taiwan
-               3076:    'zh',  #Chinese - Hong Kong SAR
-               5124:    'zh',  #Chinese - Macao SAR
-               1050:    'hr',  #Croatian
-               4122:    'hr',  #Croatian (Bosnia/Herzegovina)
-               1029:    'cs',  #Czech
-               1030:    'da',  #Danish
-               1125:    'dv',  #Divehi
-               1043:    'nl',  #Dutch - Netherlands
-               2067:    'nl',  #Dutch - Belgium
-               1126:    'bin', #Edo
-               1033:    'en',  #English - United States
-               2057:    'en',  #English - United Kingdom
-               3081:    'en',  #English - Australia
-               10249:   'en',  #English - Belize
-               4105:    'en',  #English - Canada
-               9225:    'en',  #English - Caribbean
-               15369:   'en',  #English - Hong Kong SAR
-               16393:   'en',  #English - India
-               14345:   'en',  #English - Indonesia
-               6153:    'en',  #English - Ireland
-               8201:    'en',  #English - Jamaica
-               17417:   'en',  #English - Malaysia
-               5129:    'en',  #English - New Zealand
-               13321:   'en',  #English - Philippines
-               18441:   'en',  #English - Singapore
-               7177:    'en',  #English - South Africa
-               11273:   'en',  #English - Trinidad
-               12297:   'en',  #English - Zimbabwe
-               1061:    'et',  #Estonian
-               1080:    'fo',  #Faroese
-               1065:    None,  #TODO: Farsi
-               1124:    'fil', #Filipino
-               1035:    'fi',  #Finnish
-               1036:    'fr',  #French - France
-               2060:    'fr',  #French - Belgium
-               11276:   'fr',  #French - Cameroon
-               3084:    'fr',  #French - Canada
-               9228:    'fr',  #French - Democratic Rep. of Congo
-               12300:   'fr',  #French - Cote d'Ivoire
-               15372:   'fr',  #French - Haiti
-               5132:    'fr',  #French - Luxembourg
-               13324:   'fr',  #French - Mali
-               6156:    'fr',  #French - Monaco
-               14348:   'fr',  #French - Morocco
-               58380:   'fr',  #French - North Africa
-               8204:    'fr',  #French - Reunion
-               10252:   'fr',  #French - Senegal
-               4108:    'fr',  #French - Switzerland
-               7180:    'fr',  #French - West Indies
-               1122:    'fy',  #Frisian - Netherlands
-               1127:    None,  #TODO: Fulfulde - Nigeria
-               1071:    'mk',  #FYRO Macedonian
-               2108:    'ga',  #Gaelic (Ireland)
-               1084:    'gd',  #Gaelic (Scotland)
-               1110:    'gl',  #Galician
-               1079:    'ka',  #Georgian
-               1031:    'de',  #German - Germany
-               3079:    'de',  #German - Austria
-               5127:    'de',  #German - Liechtenstein
-               4103:    'de',  #German - Luxembourg
-               2055:    'de',  #German - Switzerland
-               1032:    'el',  #Greek
-               1140:    'gn',  #Guarani - Paraguay
-               1095:    'gu',  #Gujarati
-               1128:    'ha',  #Hausa - Nigeria
-               1141:    'haw', #Hawaiian - United States
-               1037:    'he',  #Hebrew
-               1081:    'hi',  #Hindi
-               1038:    'hu',  #Hungarian
-               1129:    None,  #TODO: Ibibio - Nigeria
-               1039:    'is',  #Icelandic
-               1136:    'ig',  #Igbo - Nigeria
-               1057:    'id',  #Indonesian
-               1117:    'iu',  #Inuktitut
-               1040:    'it',  #Italian - Italy
-               2064:    'it',  #Italian - Switzerland
-               1041:    'ja',  #Japanese
-               1099:    'kn',  #Kannada
-               1137:    'kr',  #Kanuri - Nigeria
-               2144:    'ks',  #Kashmiri
-               1120:    'ks',  #Kashmiri (Arabic)
-               1087:    'kk',  #Kazakh
-               1107:    'km',  #Khmer
-               1111:    'kok', #Konkani
-               1042:    'ko',  #Korean
-               1088:    'ky',  #Kyrgyz (Cyrillic)
-               1108:    'lo',  #Lao
-               1142:    'la',  #Latin
-               1062:    'lv',  #Latvian
-               1063:    'lt',  #Lithuanian
-               1086:    'ms',  #Malay - Malaysia
-               2110:    'ms',  #Malay - Brunei Darussalam
-               1100:    'ml',  #Malayalam
-               1082:    'mt',  #Maltese
-               1112:    'mni', #Manipuri
-               1153:    'mi',  #Maori - New Zealand
-               1102:    'mr',  #Marathi
-               1104:    'mn',  #Mongolian (Cyrillic)
-               2128:    'mn',  #Mongolian (Mongolian)
-               1121:    'ne',  #Nepali
-               2145:    'ne',  #Nepali - India
-               1044:    'no',  #Norwegian (Bokmᅢᆬl)
-               2068:    'no',  #Norwegian (Nynorsk)
-               1096:    'or',  #Oriya
-               1138:    'om',  #Oromo
-               1145:    'pap', #Papiamentu
-               1123:    'ps',  #Pashto
-               1045:    'pl',  #Polish
-               1046:    'pt',  #Portuguese - Brazil
-               2070:    'pt',  #Portuguese - Portugal
-               1094:    'pa',  #Punjabi
-               2118:    'pa',  #Punjabi (Pakistan)
-               1131:    'qu',  #Quecha - Bolivia
-               2155:    'qu',  #Quecha - Ecuador
-               3179:    'qu',  #Quecha - Peru
-               1047:    'rm',  #Rhaeto-Romanic
-               1048:    'ro',  #Romanian
-               2072:    'ro',  #Romanian - Moldava
-               1049:    'ru',  #Russian
-               2073:    'ru',  #Russian - Moldava
-               1083:    'se',  #Sami (Lappish)
-               1103:    'sa',  #Sanskrit
-               1132:    'nso', #Sepedi
-               3098:    'sr',  #Serbian (Cyrillic)
-               2074:    'sr',  #Serbian (Latin)
-               1113:    'sd',  #Sindhi - India
-               2137:    'sd',  #Sindhi - Pakistan
-               1115:    'si',  #Sinhalese - Sri Lanka
-               1051:    'sk',  #Slovak
-               1060:    'sl',  #Slovenian
-               1143:    'so',  #Somali
-               1070:    'wen', #Sorbian
-               3082:    'es',  #Spanish - Spain (Modern Sort)
-               1034:    'es',  #Spanish - Spain (Traditional Sort)
-               11274:   'es',  #Spanish - Argentina
-               16394:   'es',  #Spanish - Bolivia
-               13322:   'es',  #Spanish - Chile
-               9226:    'es',  #Spanish - Colombia
-               5130:    'es',  #Spanish - Costa Rica
-               7178:    'es',  #Spanish - Dominican Republic
-               12298:   'es',  #Spanish - Ecuador
-               17418:   'es',  #Spanish - El Salvador
-               4106:    'es',  #Spanish - Guatemala
-               18442:   'es',  #Spanish - Honduras
-               58378:   'es',  #Spanish - Latin America
-               2058:    'es',  #Spanish - Mexico
-               19466:   'es',  #Spanish - Nicaragua
-               6154:    'es',  #Spanish - Panama
-               15370:   'es',  #Spanish - Paraguay
-               10250:   'es',  #Spanish - Peru
-               20490:   'es',  #Spanish - Puerto Rico
-               21514:   'es',  #Spanish - United States
-               14346:   'es',  #Spanish - Uruguay
-               8202:    'es',  #Spanish - Venezuela
-               1072:    None,  #TODO: Sutu
-               1089:    'sw',  #Swahili
-               1053:    'sv',  #Swedish
-               2077:    'sv',  #Swedish - Finland
-               1114:    'syr', #Syriac
-               1064:    'tg',  #Tajik
-               1119:    None,  #TODO: Tamazight (Arabic)
-               2143:    None,  #TODO: Tamazight (Latin)
-               1097:    'ta',  #Tamil
-               1092:    'tt',  #Tatar
-               1098:    'te',  #Telugu
-               1054:    'th',  #Thai
-               2129:    'bo',  #Tibetan - Bhutan
-               1105:    'bo',  #Tibetan - People's Republic of China
-               2163:    'ti',  #Tigrigna - Eritrea
-               1139:    'ti',  #Tigrigna - Ethiopia
-               1073:    'ts',  #Tsonga
-               1074:    'tn',  #Tswana
-               1055:    'tr',  #Turkish
-               1090:    'tk',  #Turkmen
-               1152:    'ug',  #Uighur - China
-               1058:    'uk',  #Ukrainian
-               1056:    'ur',  #Urdu
-               2080:    'ur',  #Urdu - India
-               2115:    'uz',  #Uzbek (Cyrillic)
-               1091:    'uz',  #Uzbek (Latin)
-               1075:    've',  #Venda
-               1066:    'vi',  #Vietnamese
-               1106:    'cy',  #Welsh
-               1076:    'xh',  #Xhosa
-               1144:    'ii',  #Yi
-               1085:    'yi',  #Yiddish
-               1130:    'yo',  #Yoruba
-               1077:    'zu'}  #Zulu
+    mapping = {1078:    'af',   # frikaans - South Africa
+               1052:    'sq',   # lbanian - Albania
+               1118:    'am',   # mharic - Ethiopia
+               1025:    'ar',   # rabic - Saudi Arabia
+               5121:    'ar',   # rabic - Algeria
+               15361:   'ar',   # rabic - Bahrain
+               3073:    'ar',   # rabic - Egypt
+               2049:    'ar',   # rabic - Iraq
+               11265:   'ar',   # rabic - Jordan
+               13313:   'ar',   # rabic - Kuwait
+               12289:   'ar',   # rabic - Lebanon
+               4097:    'ar',   # rabic - Libya
+               6145:    'ar',   # rabic - Morocco
+               8193:    'ar',   # rabic - Oman
+               16385:   'ar',   # rabic - Qatar
+               10241:   'ar',   # rabic - Syria
+               7169:    'ar',   # rabic - Tunisia
+               14337:   'ar',   # rabic - U.A.E.
+               9217:    'ar',   # rabic - Yemen
+               1067:    'hy',   # rmenian - Armenia
+               1101:    'as',   # ssamese
+               2092:    'az',   # zeri (Cyrillic)
+               1068:    'az',   # zeri (Latin)
+               1069:    'eu',   # asque
+               1059:    'be',   # elarusian
+               1093:    'bn',   # engali (India)
+               2117:    'bn',   # engali (Bangladesh)
+               5146:    'bs',   # osnian (Bosnia/Herzegovina)
+               1026:    'bg',   # ulgarian
+               1109:    'my',   # urmese
+               1027:    'ca',   # atalan
+               1116:    'chr',  # herokee - United States
+               2052:    'zh',   # hinese - People's Republic of China
+               4100:    'zh',   # hinese - Singapore
+               1028:    'zh',   # hinese - Taiwan
+               3076:    'zh',   # hinese - Hong Kong SAR
+               5124:    'zh',   # hinese - Macao SAR
+               1050:    'hr',   # roatian
+               4122:    'hr',   # roatian (Bosnia/Herzegovina)
+               1029:    'cs',   # zech
+               1030:    'da',   # anish
+               1125:    'dv',   # ivehi
+               1043:    'nl',   # utch - Netherlands
+               2067:    'nl',   # utch - Belgium
+               1126:    'bin',  # do
+               1033:    'en',   # nglish - United States
+               2057:    'en',   # nglish - United Kingdom
+               3081:    'en',   # nglish - Australia
+               10249:   'en',   # nglish - Belize
+               4105:    'en',   # nglish - Canada
+               9225:    'en',   # nglish - Caribbean
+               15369:   'en',   # nglish - Hong Kong SAR
+               16393:   'en',   # nglish - India
+               14345:   'en',   # nglish - Indonesia
+               6153:    'en',   # nglish - Ireland
+               8201:    'en',   # nglish - Jamaica
+               17417:   'en',   # nglish - Malaysia
+               5129:    'en',   # nglish - New Zealand
+               13321:   'en',   # nglish - Philippines
+               18441:   'en',   # nglish - Singapore
+               7177:    'en',   # nglish - South Africa
+               11273:   'en',   # nglish - Trinidad
+               12297:   'en',   # nglish - Zimbabwe
+               1061:    'et',   # stonian
+               1080:    'fo',   # aroese
+               1065:    None,   # ODO: Farsi
+               1124:    'fil',  # ilipino
+               1035:    'fi',   # innish
+               1036:    'fr',   # rench - France
+               2060:    'fr',   # rench - Belgium
+               11276:   'fr',   # rench - Cameroon
+               3084:    'fr',   # rench - Canada
+               9228:    'fr',   # rench - Democratic Rep. of Congo
+               12300:   'fr',   # rench - Cote d'Ivoire
+               15372:   'fr',   # rench - Haiti
+               5132:    'fr',   # rench - Luxembourg
+               13324:   'fr',   # rench - Mali
+               6156:    'fr',   # rench - Monaco
+               14348:   'fr',   # rench - Morocco
+               58380:   'fr',   # rench - North Africa
+               8204:    'fr',   # rench - Reunion
+               10252:   'fr',   # rench - Senegal
+               4108:    'fr',   # rench - Switzerland
+               7180:    'fr',   # rench - West Indies
+               1122:    'fy',   # risian - Netherlands
+               1127:    None,   # ODO: Fulfulde - Nigeria
+               1071:    'mk',   # YRO Macedonian
+               2108:    'ga',   # aelic (Ireland)
+               1084:    'gd',   # aelic (Scotland)
+               1110:    'gl',   # alician
+               1079:    'ka',   # eorgian
+               1031:    'de',   # erman - Germany
+               3079:    'de',   # erman - Austria
+               5127:    'de',   # erman - Liechtenstein
+               4103:    'de',   # erman - Luxembourg
+               2055:    'de',   # erman - Switzerland
+               1032:    'el',   # reek
+               1140:    'gn',   # uarani - Paraguay
+               1095:    'gu',   # ujarati
+               1128:    'ha',   # ausa - Nigeria
+               1141:    'haw',  # awaiian - United States
+               1037:    'he',   # ebrew
+               1081:    'hi',   # indi
+               1038:    'hu',   # ungarian
+               1129:    None,   # ODO: Ibibio - Nigeria
+               1039:    'is',   # celandic
+               1136:    'ig',   # gbo - Nigeria
+               1057:    'id',   # ndonesian
+               1117:    'iu',   # nuktitut
+               1040:    'it',   # talian - Italy
+               2064:    'it',   # talian - Switzerland
+               1041:    'ja',   # apanese
+               1099:    'kn',   # annada
+               1137:    'kr',   # anuri - Nigeria
+               2144:    'ks',   # ashmiri
+               1120:    'ks',   # ashmiri (Arabic)
+               1087:    'kk',   # azakh
+               1107:    'km',   # hmer
+               1111:    'kok',  # onkani
+               1042:    'ko',   # orean
+               1088:    'ky',   # yrgyz (Cyrillic)
+               1108:    'lo',   # ao
+               1142:    'la',   # atin
+               1062:    'lv',   # atvian
+               1063:    'lt',   # ithuanian
+               1086:    'ms',   # alay - Malaysia
+               2110:    'ms',   # alay - Brunei Darussalam
+               1100:    'ml',   # alayalam
+               1082:    'mt',   # altese
+               1112:    'mni',  # anipuri
+               1153:    'mi',   # aori - New Zealand
+               1102:    'mr',   # arathi
+               1104:    'mn',   # ongolian (Cyrillic)
+               2128:    'mn',   # ongolian (Mongolian)
+               1121:    'ne',   # epali
+               2145:    'ne',   # epali - India
+               1044:    'no',   # orwegian (Bokmᅢᆬl)
+               2068:    'no',   # orwegian (Nynorsk)
+               1096:    'or',   # riya
+               1138:    'om',   # romo
+               1145:    'pap',  # apiamentu
+               1123:    'ps',   # ashto
+               1045:    'pl',   # olish
+               1046:    'pt',   # ortuguese - Brazil
+               2070:    'pt',   # ortuguese - Portugal
+               1094:    'pa',   # unjabi
+               2118:    'pa',   # unjabi (Pakistan)
+               1131:    'qu',   # uecha - Bolivia
+               2155:    'qu',   # uecha - Ecuador
+               3179:    'qu',   # uecha - Peru
+               1047:    'rm',   # haeto-Romanic
+               1048:    'ro',   # omanian
+               2072:    'ro',   # omanian - Moldava
+               1049:    'ru',   # ussian
+               2073:    'ru',   # ussian - Moldava
+               1083:    'se',   # ami (Lappish)
+               1103:    'sa',   # anskrit
+               1132:    'nso',  # epedi
+               3098:    'sr',   # erbian (Cyrillic)
+               2074:    'sr',   # erbian (Latin)
+               1113:    'sd',   # indhi - India
+               2137:    'sd',   # indhi - Pakistan
+               1115:    'si',   # inhalese - Sri Lanka
+               1051:    'sk',   # lovak
+               1060:    'sl',   # lovenian
+               1143:    'so',   # omali
+               1070:    'wen',  # orbian
+               3082:    'es',   # panish - Spain (Modern Sort)
+               1034:    'es',   # panish - Spain (Traditional Sort)
+               11274:   'es',   # panish - Argentina
+               16394:   'es',   # panish - Bolivia
+               13322:   'es',   # panish - Chile
+               9226:    'es',   # panish - Colombia
+               5130:    'es',   # panish - Costa Rica
+               7178:    'es',   # panish - Dominican Republic
+               12298:   'es',   # panish - Ecuador
+               17418:   'es',   # panish - El Salvador
+               4106:    'es',   # panish - Guatemala
+               18442:   'es',   # panish - Honduras
+               58378:   'es',   # panish - Latin America
+               2058:    'es',   # panish - Mexico
+               19466:   'es',   # panish - Nicaragua
+               6154:    'es',   # panish - Panama
+               15370:   'es',   # panish - Paraguay
+               10250:   'es',   # panish - Peru
+               20490:   'es',   # panish - Puerto Rico
+               21514:   'es',   # panish - United States
+               14346:   'es',   # panish - Uruguay
+               8202:    'es',   # panish - Venezuela
+               1072:    None,   # ODO: Sutu
+               1089:    'sw',   # wahili
+               1053:    'sv',   # wedish
+               2077:    'sv',   # wedish - Finland
+               1114:    'syr',  # yriac
+               1064:    'tg',   # ajik
+               1119:    None,   # ODO: Tamazight (Arabic)
+               2143:    None,   # ODO: Tamazight (Latin)
+               1097:    'ta',   # amil
+               1092:    'tt',   # atar
+               1098:    'te',   # elugu
+               1054:    'th',   # hai
+               2129:    'bo',   # ibetan - Bhutan
+               1105:    'bo',   # ibetan - People's Republic of China
+               2163:    'ti',   # igrigna - Eritrea
+               1139:    'ti',   # igrigna - Ethiopia
+               1073:    'ts',   # songa
+               1074:    'tn',   # swana
+               1055:    'tr',   # urkish
+               1090:    'tk',   # urkmen
+               1152:    'ug',   # ighur - China
+               1058:    'uk',   # krainian
+               1056:    'ur',   # rdu
+               2080:    'ur',   # rdu - India
+               2115:    'uz',   # zbek (Cyrillic)
+               1091:    'uz',   # zbek (Latin)
+               1075:    've',   # enda
+               1066:    'vi',   # ietnamese
+               1106:    'cy',   # elsh
+               1076:    'xh',   # hosa
+               1144:    'ii',   # i
+               1085:    'yi',   # iddish
+               1130:    'yo',   # oruba
+               1077:    'zu'}   # ulu
 
     return mapping[lcid]
 
+
 def _getscreenlanguage():
     '''
     :returns: the ISO 639-x language code for this session.
 
-    If the LANGUAGE environment variable is set, it's value overrides the
-    screen language detection. Otherwise the screen language is determined by
-    the currently selected Microsoft Windows MUI language pack or the Microsoft
-    Windows installation language.
+    If the LANGUAGE environment variable is set, it's value overrides
+    the screen language detection. Otherwise the screen language is
+    determined by the currently selected Microsoft Windows MUI language
+    pack or the Microsoft Windows installation language.
 
     Works on Microsoft Windows 2000 and up.
     '''
@@ -319,34 +322,39 @@ def _getscreenlanguage():
                 from ctypes import windll
                 lcid = windll.kernel32.GetUserDefaultUILanguage()
             except:
-                logger.debug('Failed to get current screen language with \'GetUserDefaultUILanguage\'')
+                logger.debug('Failed to get current screen language '
+                             'with \'GetUserDefaultUILanguage\'')
             finally:
                 if lcid is None:
                     lang = 'C'
                 else:
                     lang = _isofromlcid(lcid)
 
-                logger.debug('Windows screen language is \'%s\' (lcid %s)' % (lang, lcid))
+                logger.debug('Windows screen language is \'%s\' '
+                             '(lcid %s)' % (lang, lcid))
 
         return lang
 
+
 def _putenv(name, value):
     '''
     :param name: environment variable name
     :param value: environment variable value
 
-    This function ensures that changes to an environment variable are applied
-    to each copy of the environment variables used by a process. Starting from
-    Python 2.4, os.environ changes only apply to the copy Python keeps (os.environ)
-    and are no longer automatically applied to the other copies for the process.
-
-    On Microsoft Windows, each process has multiple copies of the environment
-    variables, one managed by the OS and one managed by the C library. We also
-    need to take care of the fact that the C library used by Python is not
-    necessarily the same as the C library used by pygtk and friends. This because
-    the latest releases of pygtk and friends are built with mingw32 and are thus
-    linked against msvcrt.dll. The official gtk+ binaries have always been built
-    in this way.
+    This function ensures that changes to an environment variable are
+    applied to each copy of the environment variables used by a process.
+    Starting from Python 2.4, os.environ changes only apply to the copy
+    Python keeps (os.environ) and are no longer automatically applied to
+    the other copies for the process.
+
+    On Microsoft Windows, each process has multiple copies of the
+    environment variables, one managed by the OS and one managed by the
+    C library. We also need to take care of the fact that the C library
+    used by Python is not necessarily the same as the C library used by
+    pygtk and friends. This because the latest releases of pygtk and
+    friends are built with mingw32 and are thus linked against
+    msvcrt.dll. The official gtk+ binaries have always been built in
+    this way.
     '''
 
     if sys.platform == 'win32' or sys.platform == 'nt':
@@ -357,34 +365,47 @@ def _putenv(name, value):
         # Update Python's copy of the environment variables
         os.environ[name] = value
 
-        # Update the copy maintained by Windows (so SysInternals Process Explorer sees it)
+        # Update the copy maintained by Windows (so SysInternals
+        # Process Explorer sees it)
         try:
             result = windll.kernel32.SetEnvironmentVariableW(name, value)
-            if result == 0: raise Warning
+            if result == 0:
+                raise Warning
         except Exception:
-            logger.debug('Failed to set environment variable \'%s\' (\'kernel32.SetEnvironmentVariableW\')' % name)
+            logger.debug('Failed to set environment variable \'%s\' '
+                         '(\'kernel32.SetEnvironmentVariableW\')' % name)
         else:
-            logger.debug('Set environment variable \'%s\' to \'%s\' (\'kernel32.SetEnvironmentVariableW\')' % (name, value))
+            logger.debug('Set environment variable \'%s\' to \'%s\' '
+                         '(\'kernel32.SetEnvironmentVariableW\')' %
+                         (name, value))
 
         # Update the copy maintained by msvcrt (used by gtk+ runtime)
         try:
             result = cdll.msvcrt._putenv('%s=%s' % (name, value))
-            if result !=0: raise Warning
+            if result != 0:
+                raise Warning
         except Exception:
-            logger.debug('Failed to set environment variable \'%s\' (\'msvcrt._putenv\')' % name)
+            logger.debug('Failed to set environment variable \'%s\' '
+                         '(\'msvcrt._putenv\')' % name)
         else:
-            logger.debug('Set environment variable \'%s\' to \'%s\' (\'msvcrt._putenv\')' % (name, value))
+            logger.debug('Set environment variable \'%s\' to \'%s\' '
+                         '(\'msvcrt._putenv\')' % (name, value))
 
         # Update the copy maintained by whatever c runtime is used by Python
         try:
             msvcrt = find_msvcrt()
-            msvcrtname = str(msvcrt).split('.')[0] if '.' in msvcrt else str(msvcrt)
+            msvcrtname = str(msvcrt).split('.')[0] \
+                if '.' in msvcrt else str(msvcrt)
             result = cdll.LoadLibrary(msvcrt)._putenv('%s=%s' % (name, value))
-            if result != 0: raise Warning
+            if result != 0:
+                raise Warning
         except Exception:
-            logger.debug('Failed to set environment variable \'%s\' (\'%s._putenv\')' % (name, msvcrtname))
+            logger.debug('Failed to set environment variable \'%s\' '
+                         '(\'%s._putenv\')' % (name, msvcrtname))
         else:
-            logger.debug('Set environment variable \'%s\' to \'%s\' (\'%s._putenv\')' % (name, value, msvcrtname))
+            logger.debug('Set environment variable \'%s\' to \'%s\' '
+                         '(\'%s._putenv\')' % (name, value, msvcrtname))
+
 
 def _dugettext(domain, message):
     '''
@@ -402,11 +423,13 @@ def _dugettext(domain, message):
     else:
         return t.ugettext(message)
 
+
 def _install(domain, localedir, asglobal=False):
     '''
     :param domain: translation domain
     :param localedir: locale directory
-    :param asglobal: if True, installs the function _() in Python’s builtin namespace. Default is False
+    :param asglobal: if True, installs the function _() in Python’s
+            builtin namespace. Default is False
 
     Private function doing all the work for the :func:`elib.intl.install` and
     :func:`elib.intl.install_module` functions.
@@ -445,6 +468,7 @@ def _install(domain, localedir, asglobal=False):
 
         del libintl
 
+
 def install(domain, localedir):
     '''
     :param domain: translation domain
@@ -472,6 +496,7 @@ def install(domain, localedir):
     _install(domain, localedir, True)
     gettext.install(domain, localedir, unicode=True)
 
+
 def install_module(domain, localedir):
     '''
     :param domain: translation domain
@@ -488,7 +513,7 @@ def install_module(domain, localedir):
         _ = elib.intl.install_module('mymodule', '/path/to/usr/share/locale')
         print _('This string will be translated.')
 
-    When writing a package, you can usually do this in the package's __init__.py
+    When writing packages, you can usually do this in the package's __init__.py
     file and import the _() function from the package namespace as needed.
     '''
     _install(domain, localedir, False)
diff --git a/chirp/errors.py b/chirp/errors.py
index 3fe1027..f4d9324 100644
--- a/chirp/errors.py
+++ b/chirp/errors.py
@@ -13,26 +13,32 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+
 class InvalidDataError(Exception):
     """The radio driver encountered some invalid data"""
     pass
 
+
 class InvalidValueError(Exception):
     """An invalid value for a given parameter was used"""
     pass
 
+
 class InvalidMemoryLocation(Exception):
     """The requested memory location does not exist"""
     pass
 
+
 class RadioError(Exception):
     """An error occurred while talking to the radio"""
     pass
 
+
 class UnsupportedToneError(Exception):
     """The radio does not support the specified tone value"""
     pass
 
+
 class ImageDetectFailed(Exception):
     """The driver for the supplied image could not be determined"""
     pass
diff --git a/chirp/ft60.py b/chirp/ft60.py
deleted file mode 100644
index 9a882ab..0000000
--- a/chirp/ft60.py
+++ /dev/null
@@ -1,321 +0,0 @@
-# Copyright 2011 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/>.
-
-import time
-from chirp import chirp_common, yaesu_clone, memmap, bitwise, directory
-from chirp import errors
-from textwrap import dedent
-
-ACK = "\x06"
-
-def _send(pipe, data):
-    pipe.write(data)
-    echo = pipe.read(len(data))
-    if echo != data:
-        raise errors.RadioError("Error reading echo (Bad cable?)")
-
-def _download(radio):
-    data = ""
-    for i in range(0, 10):
-        chunk = radio.pipe.read(8)
-        if len(chunk) == 8:
-            data += chunk
-            break
-        elif chunk:
-            raise Exception("Received invalid response from radio")
-        time.sleep(1)
-        print "Trying again..."
-
-    if not data:
-        raise Exception("Radio is not responding")
-
-    _send(radio.pipe, ACK)
-
-    for i in range(0, 448):
-        chunk = radio.pipe.read(64)
-        data += chunk
-        _send(radio.pipe, ACK)
-        if len(chunk) == 1 and i == 447:
-            break
-        elif len(chunk) != 64:
-            raise Exception("Reading block %i was short (%i)" % (i, len(chunk)))
-        if radio.status_fn:
-            status = chirp_common.Status()
-            status.cur = i * 64
-            status.max = radio.get_memsize()
-            status.msg = "Cloning from radio"
-            radio.status_fn(status)
-
-    return memmap.MemoryMap(data)
-
-def _upload(radio):
-    _send(radio.pipe, radio.get_mmap()[0:8])
-
-    ack = radio.pipe.read(1)
-    if ack != ACK:
-        raise Exception("Radio did not respond")
-
-    for i in range(0, 448):
-        offset = 8 + (i * 64)
-        _send(radio.pipe, radio.get_mmap()[offset:offset+64])
-        ack = radio.pipe.read(1)
-        if ack != ACK:
-            raise Exception(_("Radio did not ack block %i") % i)
-
-        if radio.status_fn:
-            status = chirp_common.Status()
-            status.cur = offset+64
-            status.max = radio.get_memsize()
-            status.msg = "Cloning to radio"
-            radio.status_fn(status)            
-
-def _decode_freq(freqraw):
-    freq = int(freqraw) * 10000
-    if freq > 8000000000:
-        freq = (freq - 8000000000) + 5000
-
-    if freq > 4000000000:
-        freq -= 4000000000
-        for i in range(0, 3):
-            freq += 2500
-            if chirp_common.required_step(freq) == 12.5:
-                break
-
-    return freq
-
-def _encode_freq(freq):
-    freqraw = freq / 10000
-    flags = 0x00
-    if ((freq / 1000) % 10) >= 5:
-        flags += 0x80
-    if chirp_common.is_fractional_step(freq):
-        flags += 0x40
-    return freqraw, flags
-
-
-MEM_FORMAT = """
-#seekto 0x0238;
-struct {
-  u8 used:1,
-     unknown1:1,
-     isnarrow:1,
-     isam:1,
-     duplex:4;
-  bbcd freq[3];
-  u8 unknown2:1,
-     step:3, 
-     unknown2_1:1,
-     tmode:3;
-  bbcd tx_freq[3];
-  u8 power:2,
-     tone:6;
-  u8 unknown4:1,
-     dtcs:7;
-  u8 unknown5[2];
-  u8 offset;
-  u8 unknown6[3];
-} memory[1000];
-
-#seekto 0x6EC8;
-struct {
-  u8 skip0:2,
-     skip1:2,
-     skip2:2,
-     skip3:2;
-} flags[500];
-
-#seekto 0x4700;
-struct {
-  u8 name[6];
-  u8 use_name:1,
-     unknown1:7;
-  u8 valid:1,
-     unknown2:7;
-} names[1000];
-
-#seekto 0x6FC8;
-u8 checksum;
-"""
-
-DUPLEX = ["", "", "-", "+", "split"]
-TMODES = ["", "Tone", "TSQL", "TSQL-R", "DTCS"]
-POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.0),
-                chirp_common.PowerLevel("Mid", watts=2.5),
-                chirp_common.PowerLevel("Low", watts=1.0)]
-STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0]
-SKIPS = ["", "P", "S"]
-CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ [?]^__|`?$%&-()*+,-,/|;/=>?@"
-
- at directory.register
-class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
-    """Yaesu FT-60"""
-    BAUD_RATE = 9600
-    VENDOR = "Yaesu"
-    MODEL = "FT-60"
-    _model = "AH017"
-
-    _memsize = 28617
-
-    @classmethod
-    def get_prompts(cls):
-        rp = chirp_common.RadioPrompts()
-        rp.pre_download = _(dedent("""\
-            1. Turn radio off.
-            2. Connect cable to MIC/SP jack.
-            3. Press and hold in the [MONI] switch while turning the
-                 radio on.
-            4. Rotate the DIAL job to select "F8 CLONE".
-            5. Press the [F/W] key momentarily.
-            6. <b>After clicking OK</b>, press the [PTT] switch to send image."""))
-        rp.pre_upload = _(dedent("""\
-            1. Turn radio off.
-            2. Connect cable to MIC/SP jack.
-            3. Press and hold in the [MONI] switch while turning the
-                 radio on.
-            4. Rotate the DIAL job to select "F8 CLONE".
-            5. Press the [F/W] key momentarily.
-            6. Press the [MONI] switch ("--RX--" will appear on the LCD)."""))
-        return rp
-        
-    def get_features(self):
-        rf = chirp_common.RadioFeatures()
-        rf.memory_bounds = (1, 999)
-        rf.valid_duplexes = DUPLEX
-        rf.valid_tmodes = TMODES
-        rf.valid_power_levels = POWER_LEVELS
-        rf.valid_tuning_steps = STEPS
-        rf.valid_skips = SKIPS
-        rf.valid_characters = CHARSET
-        rf.valid_name_length = 6
-        rf.valid_modes = ["FM", "NFM", "AM"]
-        rf.valid_bands = [(108000000, 520000000), (700000000, 999990000)]
-        rf.can_odd_split = True
-        rf.has_ctone = False
-        rf.has_bank = False
-        rf.has_dtcs_polarity = False
-
-        return rf
-
-    def _checksums(self):
-        return [ yaesu_clone.YaesuChecksum(0x0000, 0x6FC7) ]
-
-    def sync_in(self):
-        try:
-            self._mmap = _download(self)
-        except errors.RadioError:
-            raise
-        except Exception, e:
-            raise errors.RadioError("Failed to communicate with radio: %s" % e)
-        self.process_mmap()
-
-    def sync_out(self):
-        self.update_checksums()
-        try:
-            _upload(self)
-        except errors.RadioError:
-            raise
-        except Exception, e:
-            raise errors.RadioError("Failed to communicate with radio: %s" % e)
-
-    def process_mmap(self):
-        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
-
-    def get_raw_memory(self, number):
-        return repr(self._memobj.memory[number]) + \
-            repr(self._memobj.flags[number/4]) + \
-            repr(self._memobj.names[number])
-
-    def get_memory(self, number):
-        _mem = self._memobj.memory[number]
-        _skp = self._memobj.flags[number/4]
-        _nam = self._memobj.names[number]
-
-        skip = _skp["skip%i" % (number%4)]
-
-        mem = chirp_common.Memory()
-        mem.number = number
-
-        if not _mem.used:
-            mem.empty = True
-            return mem
-
-        mem.freq = _decode_freq(_mem.freq)
-        mem.offset = int(_mem.offset) * 50000
-
-        mem.duplex = DUPLEX[_mem.duplex]
-        if mem.duplex == "split":
-            mem.offset = _decode_freq(_mem.tx_freq)
-        mem.tmode = TMODES[_mem.tmode]
-        mem.rtone = chirp_common.TONES[_mem.tone]
-        mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
-        mem.power = POWER_LEVELS[_mem.power]
-        mem.mode = _mem.isam and "AM" or _mem.isnarrow and "NFM" or "FM"
-        mem.tuning_step = STEPS[_mem.step]
-        mem.skip = SKIPS[skip]
-
-        if _nam.use_name and _nam.valid:
-            for i in _nam.name:
-                if i == 0xFF:
-                    break
-                try:
-                    mem.name += CHARSET[i]
-                except IndexError:
-                    print "Memory %i: Unknown char index: %i " % (number, i)
-            mem.name = mem.name.rstrip()
-
-        return mem
-
-    def set_memory(self, mem):
-        _mem = self._memobj.memory[mem.number]
-        _skp = self._memobj.flags[mem.number/4]
-        _nam = self._memobj.names[mem.number]
-
-        if mem.empty:
-            _mem.used = False
-            return
-
-        if not _mem.used:
-            _mem.set_raw("\x00" * 16)
-            _mem.used = 1
-            print "Wiped"
-
-        _mem.freq, flags = _encode_freq(mem.freq)
-        _mem.freq[0].set_bits(flags)
-        if mem.duplex == "split":
-            _mem.tx_freq, flags = _encode_freq(mem.offset)
-            _mem.tx_freq[0].set_bits(flags)
-            _mem.offset = 0
-        else:
-            _mem.tx_freq = 0
-            _mem.offset = mem.offset / 50000
-        _mem.duplex = DUPLEX.index(mem.duplex)
-        _mem.tmode = TMODES.index(mem.tmode)
-        _mem.tone = chirp_common.TONES.index(mem.rtone)
-        _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
-        _mem.power = mem.power and POWER_LEVELS.index(mem.power) or 0
-        _mem.isnarrow = mem.mode == "NFM"
-        _mem.isam = mem.mode == "AM"
-        _mem.step = STEPS.index(mem.tuning_step)
-
-        _skp["skip%i" % (mem.number%4)] = SKIPS.index(mem.skip)
-
-        for i in range(0, 6):
-            try:
-                _nam.name[i] = CHARSET.index(mem.name[i])
-            except IndexError:
-                _nam.name[i] = CHARSET.index(" ")
-            
-        _nam.use_name = mem.name.strip() and True or False
-        _nam.valid = _nam.use_name
diff --git a/chirp/import_logic.py b/chirp/import_logic.py
index b787378..c2ed867 100644
--- a/chirp/import_logic.py
+++ b/chirp/import_logic.py
@@ -13,16 +13,22 @@
 # 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 chirp import chirp_common, errors
 
+LOG = logging.getLogger(__name__)
+
+
 class ImportError(Exception):
     """An import error"""
     pass
 
+
 class DestNotCompatible(ImportError):
     """Memory is not compatible with the destination radio"""
     pass
 
+
 def ensure_has_calls(radio, memory):
     """Make sure @radio has the necessary D-STAR callsigns for @memory"""
     ulist_changed = rlist_changed = False
@@ -62,10 +68,12 @@ def ensure_has_calls(radio, memory):
     if rlist_changed:
         radio.set_repeater_call_list(rlist)
 
+
 # Filter the name according to the destination's rules
 def _import_name(dst_radio, _srcrf, mem):
     mem.name = dst_radio.filter_name(mem.name)
 
+
 def _import_power(dst_radio, _srcrf, mem):
     levels = dst_radio.get_features().valid_power_levels
     if not levels:
@@ -75,7 +83,7 @@ def _import_power(dst_radio, _srcrf, mem):
         # Source radio did not support power levels, so choose the
         # first (highest) level from the destination radio.
         mem.power = levels[0]
-        return 
+        return
 
     # If both radios support power levels, we need to decide how to
     # convert the source power level to a valid one for the destination
@@ -86,6 +94,7 @@ def _import_power(dst_radio, _srcrf, mem):
     deltas = [abs(mem.power - power) for power in levels]
     mem.power = levels[deltas.index(min(deltas))]
 
+
 def _import_tone(dst_radio, srcrf, mem):
     dstrf = dst_radio.get_features()
 
@@ -104,6 +113,7 @@ def _import_tone(dst_radio, srcrf, mem):
         if mem.tmode == "TSQL":
             mem.ctone = mem.rtone
 
+
 def _import_dtcs(dst_radio, srcrf, mem):
     dstrf = dst_radio.get_features()
 
@@ -122,6 +132,7 @@ def _import_dtcs(dst_radio, srcrf, mem):
         if mem.tmode == "DTCS":
             mem.rx_dtcs = mem.dtcs
 
+
 def _guess_mode_by_frequency(freq):
     ranges = [
         (0, 136000000, "AM"),
@@ -135,6 +146,7 @@ def _guess_mode_by_frequency(freq):
     # If we don't know, assume FM
     return "FM"
 
+
 def _import_mode(dst_radio, srcrf, mem):
     dstrf = dst_radio.get_features()
 
@@ -148,9 +160,10 @@ def _import_mode(dst_radio, srcrf, mem):
             raise DestNotCompatible("Destination does not support %s" % mode)
         mem.mode = mode
 
+
 def _make_offset_with_split(rxfreq, txfreq):
     offset = txfreq - rxfreq
-    
+
     if offset == 0:
         return "", offset
     elif offset > 0:
@@ -158,24 +171,25 @@ def _make_offset_with_split(rxfreq, txfreq):
     elif offset < 0:
         return "-", offset * -1
 
+
 def _import_duplex(dst_radio, srcrf, mem):
     dstrf = dst_radio.get_features()
 
     # If a radio does not support odd split, we can use an equivalent offset
     if mem.duplex == "split" and mem.duplex not in dstrf.valid_duplexes:
         mem.duplex, mem.offset = _make_offset_with_split(mem.freq, mem.offset)
-        
+
         # Enforce maximum offset
-        ranges = [
-            (        0,  500000000, 15000000),
-            (500000000, 3000000000, 50000000),
-        ]
+        ranges = [(0,          500000000, 15000000),
+                  (500000000, 3000000000, 50000000),
+                  ]
         for lo, hi, limit in ranges:
             if lo < mem.freq <= hi:
                 if abs(mem.offset) > limit:
                     raise DestNotCompatible("Unable to create import memory: "
                                             "offset is abnormally large.")
 
+
 def import_mem(dst_radio, src_features, src_mem, overrides={}):
     """Perform import logic to create a destination memory from
     src_mem that will be compatible with @dst_radio"""
@@ -183,7 +197,8 @@ def import_mem(dst_radio, src_features, src_mem, overrides={}):
 
     if isinstance(src_mem, chirp_common.DVMemory):
         if not isinstance(dst_radio, chirp_common.IcomDstarSupport):
-            raise DestNotCompatible("Destination radio does not support D-STAR")
+            raise DestNotCompatible(
+                "Destination radio does not support D-STAR")
         if dst_rf.requires_call_lists:
             ensure_has_calls(dst_radio, src_mem)
 
@@ -206,17 +221,19 @@ def import_mem(dst_radio, src_features, src_mem, overrides={}):
     msgs = dst_radio.validate_memory(dst_mem)
     errs = [x for x in msgs if isinstance(x, chirp_common.ValidationError)]
     if errs:
-        raise DestNotCompatible("Unable to create import memory: %s" %\
-                                    ", ".join(errs))
+        raise DestNotCompatible("Unable to create import memory: %s" %
+                                ", ".join(errs))
 
     return dst_mem
 
+
 def _get_bank_model(radio):
     for model in radio.get_mapping_models():
         if isinstance(model, chirp_common.BankModel):
             return model
     return None
 
+
 def import_bank(dst_radio, src_radio, dst_mem, src_mem):
     """Attempt to set the same banks for @mem(by index) in @dst_radio that
     it has in @src_radio"""
@@ -241,7 +258,7 @@ def import_bank(dst_radio, src_radio, dst_mem, src_mem):
     for index in src_indexes:
         try:
             bank = dst_banks[index]
-            print "Adding memory to bank %s" % bank
+            LOG.debug("Adding memory to bank %s" % bank)
             dst_bm.add_memory_to_mapping(dst_mem, bank)
             if isinstance(dst_bm, chirp_common.MappingModelIndexInterface):
                 dst_bm.set_memory_index(dst_mem, bank,
diff --git a/chirp/logger.py b/chirp/logger.py
new file mode 100644
index 0000000..6e841bc
--- /dev/null
+++ b/chirp/logger.py
@@ -0,0 +1,185 @@
+# Copyright 2015  Zachary T Welch  <zach at mandolincreekfarm.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/>.
+
+
+r"""
+The chirp.logger module provides the core logging facilties for CHIRP.
+It sets up the console and (optionally) a log file.  For early debugging,
+it checks the CHIRP_DEBUG, CHIRP_LOG, and CHIRP_LOG_LEVEL environment
+variables.
+"""
+
+import os
+import sys
+import logging
+import argparse
+import platform
+from chirp import CHIRP_VERSION
+
+
+def version_string():
+    args = (CHIRP_VERSION,
+            platform.get_platform().os_version_string(),
+            sys.version.split()[0])
+    return "CHIRP %s on %s (Python %s)" % args
+
+
+class VersionAction(argparse.Action):
+    def __call__(self, parser, namespace, value, option_string=None):
+        print version_string()
+        sys.exit(1)
+
+
+def add_version_argument(parser):
+    parser.add_argument("--version", action=VersionAction, nargs=0,
+                        help="Print version and exit")
+
+#: Map human-readable logging levels to their internal values.
+log_level_names = {"critical": logging.CRITICAL,
+                   "error":    logging.ERROR,
+                   "warn":     logging.WARNING,
+                   "info":     logging.INFO,
+                   "debug":    logging.DEBUG,
+                   }
+
+
+class Logger(object):
+
+    log_format = '[%(asctime)s] %(name)s - %(levelname)s: %(message)s'
+
+    def __init__(self):
+        # create root logger
+        self.logger = logging.getLogger()
+        self.logger.setLevel(logging.DEBUG)
+
+        self.LOG = logging.getLogger(__name__)
+
+        # Set CHIRP_DEBUG in environment for early console debugging.
+        # It can be a number or a name; otherwise, level is set to 'debug'
+        # in order to maintain backward compatibility.
+        CHIRP_DEBUG = os.getenv("CHIRP_DEBUG")
+        self.early_level = logging.WARNING
+        if CHIRP_DEBUG:
+            try:
+                self.early_level = int(CHIRP_DEBUG)
+            except ValueError:
+                try:
+                    self.early_level = log_level_names[CHIRP_DEBUG]
+                except KeyError:
+                    self.early_level = logging.DEBUG
+
+        # If we're on Win32 or MacOS, we don't use the console; instead,
+        # we create 'debug.log', redirect all output there, and set the
+        # console logging handler level to DEBUG.  To test this on Linux,
+        # set CHIRP_DEBUG_LOG in the environment.
+        console_stream = None
+        console_format = '%(levelname)s: %(message)s'
+        if hasattr(sys, "frozen") or not os.isatty(0) \
+                or os.getenv("CHIRP_DEBUG_LOG"):
+            p = platform.get_platform()
+            log = file(p.config_file("debug.log"), "w", 0)
+            sys.stdout = log
+            sys.stderr = log
+            console_stream = log
+            console_format = self.log_format
+            self.early_level = logging.DEBUG
+
+        self.console = logging.StreamHandler(console_stream)
+        self.console_level = self.early_level
+        self.console.setLevel(self.early_level)
+        self.console.setFormatter(logging.Formatter(console_format))
+        self.logger.addHandler(self.console)
+
+        # Set CHIRP_LOG in environment to the name of log file.
+        logname = os.getenv("CHIRP_LOG")
+        self.logfile = None
+        if logname is not None:
+            self.create_log_file(logname)
+            level = os.getenv("CHIRP_LOG_LEVEL")
+            if level is not None:
+                self.set_log_verbosity(level)
+            else:
+                self.set_log_level(logging.DEBUG)
+
+        if self.early_level <= logging.DEBUG:
+            self.LOG.debug(version_string())
+
+    def create_log_file(self, name):
+        if self.logfile is None:
+            self.logname = name
+            # always truncate the log file
+            with file(name, "w") as fh:
+                pass
+            self.logfile = logging.FileHandler(name)
+            format_str = self.log_format
+            self.logfile.setFormatter(logging.Formatter(format_str))
+            self.logger.addHandler(self.logfile)
+        else:
+            self.logger.error("already logging to " + self.logname)
+
+    def set_verbosity(self, level):
+        self.LOG.debug("verbosity=%d", level)
+        if level > logging.CRITICAL:
+            level = logging.CRITICAL
+        self.console_level = level
+        self.console.setLevel(level)
+
+    def set_log_level(self, level):
+        self.LOG.debug("log level=%d", level)
+        if level > logging.CRITICAL:
+            level = logging.CRITICAL
+        self.logfile.setLevel(level)
+
+    def set_log_level_by_name(self, level):
+        self.set_log_level(log_level_names[level])
+
+    instance = None
+
+Logger.instance = Logger()
+
+
+def is_visible(level):
+    """Returns True if a message at level will be shown on the console"""
+    return level >= Logger.instance.console_level
+
+
+def add_arguments(parser):
+    parser.add_argument("-q", "--quiet", action="count", default=0,
+                        help="Decrease verbosity")
+    parser.add_argument("-v", "--verbose", action="count", default=0,
+                        help="Increase verbosity")
+    parser.add_argument("--log", dest="log_file", action="store", default=0,
+                        help="Log messages to a file")
+    parser.add_argument("--log-level", action="store", default="debug",
+                        help="Log file verbosity (critical, error, warn, " +
+                        "info, debug).  Defaults to 'debug'.")
+
+
+def handle_options(options):
+    logger = Logger.instance
+
+    if options.verbose or options.quiet:
+        logger.set_verbosity(30 + 10 * (options.quiet - options.verbose))
+
+    if options.log_file:
+        logger.create_log_file(options.log_file)
+        try:
+            level = int(options.log_level)
+            logger.set_log_level(level)
+        except ValueError:
+            logger.set_log_level_by_name(options.log_level)
+
+    if logger.early_level > logging.DEBUG:
+        logger.LOG.debug(version_string())
diff --git a/chirp/memmap.py b/chirp/memmap.py
index 2230265..af0706c 100644
--- a/chirp/memmap.py
+++ b/chirp/memmap.py
@@ -15,6 +15,7 @@
 
 from chirp import util
 
+
 class MemoryMap:
     """
     A pythonic memory map interface
@@ -23,7 +24,7 @@ class MemoryMap:
     def __init__(self, data):
         self._data = list(data)
 
-    def printable(self, start=None, end=None, printit=True):
+    def printable(self, start=None, end=None):
         """Return a printable representation of the memory map"""
         if not start:
             start = 0
@@ -33,9 +34,6 @@ class MemoryMap:
 
         string = util.hexprint(self._data[start:end])
 
-        if printit:
-            print string
-
         return string
 
     def get(self, start, length=1):
@@ -54,8 +52,8 @@ class MemoryMap:
                 self._data[pos] = byte
                 pos += 1
         else:
-            raise ValueError("Unsupported type %s for value" % \
-                                 type(value).__name__)
+            raise ValueError("Unsupported type %s for value" %
+                             type(value).__name__)
 
     def get_packed(self):
         """Return the entire memory map as raw data"""
diff --git a/chirp/platform.py b/chirp/platform.py
index 313dfbd..3b18d41 100644
--- a/chirp/platform.py
+++ b/chirp/platform.py
@@ -17,15 +17,19 @@ import os
 import sys
 import glob
 import re
+import logging
 from subprocess import Popen
 
+LOG = logging.getLogger(__name__)
+
+
 def win32_comports_bruteforce():
     import win32file
     import win32con
 
     ports = []
     for i in range(1, 257):
-        portname = "COM%i" % i
+        portname = "\\\\.\\COM%i" % i
         try:
             mode = win32con.GENERIC_READ | win32con.GENERIC_WRITE
             port = \
@@ -36,7 +40,9 @@ def win32_comports_bruteforce():
                                      win32con.OPEN_EXISTING,
                                      0,
                                      None)
-            ports.append((portname,"Unknown","Serial"))
+            if portname.startswith("\\"):
+                portname = portname[4:]
+            ports.append((portname, "Unknown", "Serial"))
             win32file.CloseHandle(port)
             port = None
         except Exception, e:
@@ -44,19 +50,27 @@ def win32_comports_bruteforce():
 
     return ports
 
+
 try:
     from serial.tools.list_ports import comports
 except:
     comports = win32_comports_bruteforce
 
+
 def _find_me():
     return sys.modules["chirp.platform"].__file__
 
+
 def natural_sorted(l):
-    convert = lambda text: int(text) if text.isdigit() else text.lower()
-    natural_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
+    def convert(text):
+        return int(text) if text.isdigit() else text.lower()
+
+    def natural_key(key):
+        return [convert(c) for c in re.split('([0-9]+)', key)]
+
     return sorted(l, key=natural_key)
 
+
 class Platform:
     """Base class for platform-specific functions"""
 
@@ -229,6 +243,7 @@ class Platform:
             return os.path.dirname(os.path.abspath(os.path.join(_find_me(),
                                                                 "..")))
 
+
 def _unix_editor():
     macos_textedit = "/Applications/TextEdit.app/Contents/MacOS/TextEdit"
 
@@ -237,24 +252,25 @@ def _unix_editor():
     else:
         return "gedit"
 
+
 class UnixPlatform(Platform):
     """A platform module suitable for UNIX systems"""
     def __init__(self, basepath):
         if not basepath:
             basepath = os.path.abspath(os.path.join(self.default_dir(),
                                                     ".chirp"))
-        
+
         if not os.path.isdir(basepath):
             os.mkdir(basepath)
 
         Platform.__init__(self, basepath)
 
-	# This is a hack that needs to be properly fixed by importing the
-	# latest changes to this module from d-rats.  In the interest of
-	# time, however, I'll throw it here
+        # This is a hack that needs to be properly fixed by importing the
+        # latest changes to this module from d-rats.  In the interest of
+        # time, however, I'll throw it here
         if sys.platform == "darwin":
-            if not os.environ.has_key("DISPLAY"):
-                print "Forcing DISPLAY for MacOS"
+            if "DISPLAY" not in os.environ:
+                LOG.info("Forcing DISPLAY for MacOS")
                 os.environ["DISPLAY"] = ":0"
 
             os.environ["PANGO_RC_FILE"] = "../Resources/etc/pango/pangorc"
@@ -271,13 +287,13 @@ class UnixPlatform(Platform):
             pid2 = os.fork()
             if pid2 == 0:
                 editor = _unix_editor()
-                print "calling `%s %s'" % (editor, path)
+                LOG.debug("calling `%s %s'" % (editor, path))
                 os.execlp(editor, editor, path)
             else:
                 sys.exit(0)
         else:
             os.waitpid(pid1, 0)
-            print "Exec child exited"
+            LOG.debug("Exec child exited")
 
     def open_html_file(self, path):
         os.system("firefox '%s'" % path)
@@ -302,6 +318,7 @@ class UnixPlatform(Platform):
 
         return ver
 
+
 class Win32Platform(Platform):
     """A platform module suitable for Windows systems"""
     def __init__(self, basepath=None):
@@ -338,7 +355,7 @@ class Win32Platform(Platform):
             ports = list(comports())
         except Exception, e:
             if comports != win32_comports_bruteforce:
-                print "Failed to detect win32 serial ports: %s" % e
+                LOG.error("Failed to detect win32 serial ports: %s" % e)
                 ports = win32_comports_bruteforce()
         return natural_sorted([port for port, name, url in ports])
 
@@ -354,7 +371,7 @@ class Win32Platform(Platform):
         try:
             fname, _, _ = win32gui.GetOpenFileNameW(Filter=typestrs)
         except Exception, e:
-            print "Failed to get filename: %s" % e
+            LOG.error("Failed to get filename: %s" % e)
             return None
 
         return str(fname)
@@ -362,7 +379,7 @@ class Win32Platform(Platform):
     def gui_save_file(self, start_dir=None, default_name=None, types=[]):
         import win32gui
         import win32api
-        
+
         (pform, _, _, _, _) = win32api.GetVersionEx()
 
         typestrs = ""
@@ -385,7 +402,7 @@ class Win32Platform(Platform):
                                                     DefExt=def_ext,
                                                     Filter=typestrs)
         except Exception, e:
-            print "Failed to get filename: %s" % e
+            LOG.error("Failed to get filename: %s" % e)
             return None
 
         return str(fname)
@@ -397,7 +414,7 @@ class Win32Platform(Platform):
             pidl, _, _ = shell.SHBrowseForFolder()
             fname = shell.SHGetPathFromIDList(pidl)
         except Exception, e:
-            print "Failed to get directory: %s" % e
+            LOG.error("Failed to get directory: %s" % e)
             return None
 
         return str(fname)
@@ -405,14 +422,16 @@ class Win32Platform(Platform):
     def os_version_string(self):
         import win32api
 
-        vers = { 4: "Win2k",
-                 5: "WinXP",
-                 6: "WinVista/7",
-                 }
+        vers = {4: "Win2k",
+                5: "WinXP",
+                6: "WinVista/7",
+                }
 
         (pform, sub, build, _, _) = win32api.GetVersionEx()
 
-        return vers.get(pform, "Win32 (Unknown %i.%i:%i)" % (pform, sub, build))
+        return vers.get(pform,
+                        "Win32 (Unknown %i.%i:%i)" % (pform, sub, build))
+
 
 def _get_platform(basepath):
     if os.name == "nt":
@@ -421,6 +440,8 @@ def _get_platform(basepath):
         return UnixPlatform(basepath)
 
 PLATFORM = None
+
+
 def get_platform(basepath=None):
     """Return the platform singleton"""
     global PLATFORM
@@ -430,6 +451,7 @@ def get_platform(basepath=None):
 
     return PLATFORM
 
+
 def _do_test():
     __pform = get_platform()
 
@@ -438,10 +460,10 @@ def _do_test():
     print "Log file (foo): %s" % __pform.log_file("foo")
     print "Serial ports: %s" % __pform.list_serial_ports()
     print "OS Version: %s" % __pform.os_version_string()
-    #__pform.open_text_file("d-rats.py")
+    # __pform.open_text_file("d-rats.py")
 
-    #print "Open file: %s" % __pform.gui_open_file()
-    #print "Save file: %s" % __pform.gui_save_file(default_name="Foo.txt")
+    # print "Open file: %s" % __pform.gui_open_file()
+    # print "Save file: %s" % __pform.gui_save_file(default_name="Foo.txt")
     print "Open folder: %s" % __pform.gui_select_dir("/tmp")
 
 if __name__ == "__main__":
diff --git a/chirp/pyPEG.py b/chirp/pyPEG.py
index 5faa637..68d8b74 100644
--- a/chirp/pyPEG.py
+++ b/chirp/pyPEG.py
@@ -3,36 +3,52 @@
 # written by VB.
 
 import re
-import sys, codecs
+import sys
+import codecs
 import exceptions
 
-class keyword(unicode): pass
-class code(unicode): pass
+
+class keyword(unicode):
+    pass
+
+
+class code(unicode):
+    pass
+
+
 class ignore(object):
     def __init__(self, regex_text, *args):
         self.regex = re.compile(regex_text, *args)
 
+
 class _and(object):
     def __init__(self, something):
         self.obj = something
 
-class _not(_and): pass
+
+class _not(_and):
+    pass
+
 
 class Name(unicode):
     def __init__(self, *args):
         self.line = 0
         self.file = u""
 
+
 class Symbol(list):
     def __init__(self, name, what):
         self.__name__ = name
         self.append(name)
         self.what = what
         self.append(what)
+
     def __call__(self):
         return self.what
+
     def __unicode__(self):
         return u'Symbol(' + repr(self.__name__) + ', ' + repr(self.what) + u')'
+
     def __repr__(self):
         return unicode(self)
 
@@ -41,6 +57,7 @@ rest_regex = re.compile(ur".*")
 
 print_trace = False
 
+
 def u(text):
     if isinstance(text, exceptions.BaseException):
         text = text.args[0]
@@ -53,6 +70,7 @@ def u(text):
             return codecs.decode(text, "utf-8")
     return unicode(text)
 
+
 def skip(skipper, text, skipWS, skipComments):
     if skipWS:
         t = text.lstrip()
@@ -64,12 +82,14 @@ def skip(skipper, text, skipWS, skipComments):
                 skip, t = skipper.parseLine(t, skipComments, [], skipWS, None)
                 if skipWS:
                     t = t.lstrip()
-        except: pass
+        except:
+            pass
     return t
 
+
 class parser(object):
-    def __init__(self, another = False, p = False):
-        self.restlen = -1 
+    def __init__(self, another=False, p=False):
+        self.restlen = -1
         if not(another):
             self.skipper = parser(True, p)
             self.skipper.packrat = p
@@ -86,15 +106,17 @@ class parser(object):
     #   resultSoFar:    parsing result so far (default: blank list [])
     #   skipWS:         Flag if whitespace should be skipped (default: True)
     #   skipComments:   Python functions returning pyPEG for matching comments
-    #   
+    #
     #   returns:        pyAST, textrest
     #
-    #   raises:         SyntaxError(reason) if textline is detected not being in language
-    #                   described by pattern
+    #   raises:         SyntaxError(reason) if textline is detected not
+    #                   being in language described by pattern
     #
-    #                   SyntaxError(reason) if pattern is an illegal language description
+    #                   SyntaxError(reason) if pattern is an illegal
+    #                   language description
 
-    def parseLine(self, textline, pattern, resultSoFar = [], skipWS = True, skipComments = None):
+    def parseLine(self, textline, pattern, resultSoFar=[],
+                  skipWS=True, skipComments=None):
         name = None
         _textline = textline
         _pattern = pattern
@@ -104,8 +126,10 @@ class parser(object):
                 if print_trace:
                     try:
                         if _pattern.__name__ != "comment":
-                            sys.stderr.write(u"match: " + _pattern.__name__ + u"\n")
-                    except: pass
+                            sys.stderr.write(u"match: " +
+                                             _pattern.__name__ + u"\n")
+                    except:
+                        pass
 
             if self.restlen == -1:
                 self.restlen = len(text)
@@ -119,7 +143,7 @@ class parser(object):
                 name.line = self.lineNo()
                 res.append(Symbol(name, []))
             elif result:
-                if type(result) is type([]):
+                if isinstance(result, list):
                     res.extend(result)
                 else:
                     res.extend([result])
@@ -139,15 +163,19 @@ class parser(object):
                     return result
                 else:
                     raise SyntaxError()
-            except: pass
+            except:
+                pass
 
         if callable(pattern):
             if __debug__:
                 if print_trace:
                     try:
                         if pattern.__name__ != "comment":
-                            sys.stderr.write(u"testing with " + pattern.__name__ + u": " + textline[:40] + u"\n")
-                    except: pass
+                            sys.stderr.write(u"testing with " +
+                                             pattern.__name__ + u": " +
+                                             textline[:40] + u"\n")
+                    except:
+                        pass
 
             if pattern.__name__[0] != "_":
                 name = Name(pattern.__name__)
@@ -162,7 +190,8 @@ class parser(object):
 
         if pattern_type is str or pattern_type is unicode:
             if text[:len(pattern)] == pattern:
-                text = skip(self.skipper, text[len(pattern):], skipWS, skipComments)
+                text = skip(self.skipper, text[len(pattern):],
+                            skipWS, skipComments)
                 return R(None, text)
             else:
                 syntaxError()
@@ -171,7 +200,8 @@ class parser(object):
             m = word_regex.match(text)
             if m:
                 if m.group(0) == pattern:
-                    text = skip(self.skipper, text[len(pattern):], skipWS, skipComments)
+                    text = skip(self.skipper, text[len(pattern):],
+                                skipWS, skipComments)
                     return R(None, text)
                 else:
                     syntaxError()
@@ -180,7 +210,8 @@ class parser(object):
 
         elif pattern_type is _not:
             try:
-                r, t = self.parseLine(text, pattern.obj, [], skipWS, skipComments)
+                r, t = self.parseLine(text, pattern.obj, [],
+                                      skipWS, skipComments)
             except:
                 return resultSoFar, textline
             syntaxError()
@@ -194,7 +225,8 @@ class parser(object):
                 pattern = pattern.regex
             m = pattern.match(text)
             if m:
-                text = skip(self.skipper, text[len(m.group(0)):], skipWS, skipComments)
+                text = skip(self.skipper, text[len(m.group(0)):],
+                            skipWS, skipComments)
                 if pattern_type is ignore:
                     return R(None, text)
                 else:
@@ -206,26 +238,29 @@ class parser(object):
             result = []
             n = 1
             for p in pattern:
-                if type(p) is type(0):
+                if isinstance(p, int):
                     n = p
                 else:
-                    if n>0:
+                    if n > 0:
                         for i in range(n):
-                            result, text = self.parseLine(text, p, result, skipWS, skipComments)
-                    elif n==0:
+                            result, text = self.parseLine(
+                                text, p, result, skipWS, skipComments)
+                    elif n == 0:
                         if text == "":
                             pass
                         else:
                             try:
-                                newResult, newText = self.parseLine(text, p, result, skipWS, skipComments)
+                                newResult, newText = self.parseLine(
+                                    text, p, result, skipWS, skipComments)
                                 result, text = newResult, newText
                             except SyntaxError:
                                 pass
-                    elif n<0:
+                    elif n < 0:
                         found = False
                         while True:
                             try:
-                                newResult, newText = self.parseLine(text, p, result, skipWS, skipComments)
+                                newResult, newText = self.parseLine(
+                                    text, p, result, skipWS, skipComments)
                                 result, text, found = newResult, newText, True
                             except SyntaxError:
                                 break
@@ -239,7 +274,8 @@ class parser(object):
             found = False
             for p in pattern:
                 try:
-                    result, text = self.parseLine(text, p, result, skipWS, skipComments)
+                    result, text = self.parseLine(text, p, result,
+                                                  skipWS, skipComments)
                     found = True
                 except SyntaxError:
                     pass
@@ -254,8 +290,10 @@ class parser(object):
             raise SyntaxError(u"illegal type in grammar: " + u(pattern_type))
 
     def lineNo(self):
-        if not(self.lines): return u""
-        if self.restlen == -1: return u""
+        if not(self.lines):
+            return u""
+        if self.restlen == -1:
+            return u""
         parsed = self.textlen - self.restlen
 
         left, right = 0, len(self.lines)
@@ -266,14 +304,16 @@ class parser(object):
                 try:
                     if self.lines[mid + 1][0] >= parsed:
                         try:
-                            return u(self.lines[mid + 1][1]) + u":" + u(self.lines[mid + 1][2])
+                            return u(self.lines[mid + 1][1]) + \
+                                   u":" + u(self.lines[mid + 1][2])
                         except:
                             return u""
                     else:
                         left = mid + 1
                 except:
                     try:
-                        return u(self.lines[mid + 1][1]) + u":" + u(self.lines[mid + 1][2])
+                        return u(self.lines[mid + 1][1]) + \
+                               u":" + u(self.lines[mid + 1][2])
                     except:
                         return u""
             else:
@@ -281,9 +321,12 @@ class parser(object):
             if left > right:
                 return u""
 
-# plain module API
 
-def parseLine(textline, pattern, resultSoFar = [], skipWS = True, skipComments = None, packrat = False):
+# plain module APIs
+
+
+def parseLine(textline, pattern, resultSoFar=[], skipWS=True,
+              skipComments=None, packrat=False):
     p = parser(p=packrat)
     text = skip(p.skipper, textline, skipWS, skipComments)
     ast, text = p.parseLine(text, pattern, resultSoFar, skipWS, skipComments)
@@ -296,13 +339,15 @@ def parseLine(textline, pattern, resultSoFar = [], skipWS = True, skipComments =
 #   skipComments:   Python function which returns pyPEG for matching comments
 #   packrat:        use memoization
 #   lineCount:      add line number information to AST
-#   
+#
 #   returns:        pyAST
 #
 #   raises:         SyntaxError(reason), if a parsed line is not in language
 #                   SyntaxError(reason), if the language description is illegal
 
-def parse(language, lineSource, skipWS = True, skipComments = None, packrat = False, lineCount = True):
+
+def parse(language, lineSource, skipWS=True, skipComments=None,
+          packrat=False, lineCount=True):
     lines, lineNo = [], 0
 
     while callable(language):
@@ -314,7 +359,8 @@ def parse(language, lineSource, skipWS = True, skipComments = None, packrat = Fa
             ld = 1
         else:
             ld += 1
-        lines.append((len(orig), lineSource.filename(), lineSource.lineno() - 1))
+        lines.append((len(orig), lineSource.filename(),
+                     lineSource.lineno() - 1))
         orig += u(line)
 
     textlen = len(orig)
@@ -346,6 +392,7 @@ def parse(language, lineSource, skipWS = True, skipComments = None, packrat = Fa
         lineNo += 1
         nn -= 1
         lineCont = orig.splitlines()[nn]
-        raise SyntaxError(u"syntax error in " + u(file) + u":" + u(lineNo) + u": " + lineCont)
+        raise SyntaxError(u"syntax error in " + u(file) + u":" +
+                          u(lineNo) + u": " + lineCont)
 
     return result
diff --git a/chirp/radioreference.py b/chirp/radioreference.py
index 9ca54aa..d52284e 100644
--- a/chirp/radioreference.py
+++ b/chirp/radioreference.py
@@ -13,7 +13,11 @@
 # 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 chirp import chirp_common, errors
+
+LOG = logging.getLogger(__name__)
+
 try:
     from suds.client import Client
     from suds import WebFault
@@ -22,15 +26,16 @@ except ImportError:
     HAVE_SUDS = False
 
 MODES = {
-    "FM"    : "FM",
-    "AM"    : "AM",
-    "FMN"   : "NFM",
+    "FM":     "FM",
+    "AM":     "AM",
+    "FMN":    "NFM",
     "D-STAR": "DV",
-    "USB"   : "USB",
-    "LSB"   : "LSB",
-    "P25"   : "P25",
+    "USB":    "USB",
+    "LSB":    "LSB",
+    "P25":    "P25",
 }
 
+
 class RadioReferenceRadio(chirp_common.NetworkSourceRadio):
     """RadioReference.com data source"""
     VENDOR = "Radio Reference LLC"
@@ -44,7 +49,7 @@ class RadioReferenceRadio(chirp_common.NetworkSourceRadio):
 
         if not HAVE_SUDS:
             raise errors.RadioError(
-                "Suds library required for RadioReference.com import.\n" + \
+                "Suds library required for RadioReference.com import.\n" +
                 "Try installing your distribution's python-suds package.")
 
         self._auth = {"appKey": self.APPKEY, "username": "", "password": ""}
@@ -64,8 +69,9 @@ class RadioReferenceRadio(chirp_common.NetworkSourceRadio):
         self._freqs = []
 
         try:
-            zipcode = self._client.service.getZipcodeInfo(self._zip, self._auth)
-            county = self._client.service.getCountyInfo(zipcode.ctid, self._auth)
+            service = self._client.service
+            zipcode = service.getZipcodeInfo(self._zip, self._auth)
+            county = service.getCountyInfo(zipcode.ctid, self._auth)
         except WebFault, err:
             raise errors.RadioError(err)
 
@@ -76,9 +82,9 @@ class RadioReferenceRadio(chirp_common.NetworkSourceRadio):
         status.max += len(county.agencyList)
 
         for cat in county.cats:
-            print "Fetching category:", cat.cName
+            LOG.debug("Fetching category:", cat.cName)
             for subcat in cat.subcats:
-                print "\t", subcat.scName
+                LOG.debug("\t", subcat.scName)
                 result = self._client.service.getSubcatFreqs(subcat.scid,
                                                              self._auth)
                 self._freqs += result
@@ -90,9 +96,9 @@ class RadioReferenceRadio(chirp_common.NetworkSourceRadio):
             for cat in agency.cats:
                 status.max += len(cat.subcats)
             for cat in agency.cats:
-                print "Fetching category:", cat.cName
+                LOG.debug("Fetching category:", cat.cName)
                 for subcat in cat.subcats:
-                    print "\t", subcat.scName
+                    LOG.debug("\t", subcat.scName)
                     result = self._client.service.getSubcatFreqs(subcat.scid,
                                                                  self._auth)
                     self._freqs += result
@@ -130,7 +136,7 @@ class RadioReferenceRadio(chirp_common.NetworkSourceRadio):
             mem.duplex = "split"
             mem.offset = chirp_common.parse_freq(str(freq["in"]))
         if freq.tone is not None:
-            if str(freq.tone) == "CSQ": # Carrier Squelch
+            if str(freq.tone) == "CSQ":  # Carrier Squelch
                 mem.tmode = ""
             else:
                 try:
@@ -144,8 +150,7 @@ class RadioReferenceRadio(chirp_common.NetworkSourceRadio):
                     mem.tmode = "DTCS"
                     mem.dtcs = int(tone)
                 else:
-                    print "Error: unsupported tone"
-                    print freq
+                    LOG.error("Error: unsupported tone: %s" % freq)
         try:
             mem.mode = self._get_mode(freq.mode)
         except KeyError:
diff --git a/chirp/settings.py b/chirp/settings.py
index ad1e15c..55571a3 100644
--- a/chirp/settings.py
+++ b/chirp/settings.py
@@ -15,14 +15,17 @@
 
 from chirp import chirp_common
 
+
 class InvalidValueError(Exception):
     """An invalid value was specified for a given setting"""
     pass
 
+
 class InternalError(Exception):
     """A driver provided an invalid settings object structure"""
     pass
 
+
 class RadioSettingValue:
     """Base class for a single radio setting"""
     def __init__(self):
@@ -49,7 +52,7 @@ class RadioSettingValue:
         if not self.get_mutable():
             raise InvalidValueError("This value is not mutable")
 
-        if self._current != None and value != self._current:
+        if self._current is not None and value != self._current:
             self._has_changed = True
         self._current = self._validate_callback(value)
 
@@ -63,6 +66,7 @@ class RadioSettingValue:
     def __str__(self):
         return str(self.get_value())
 
+
 class RadioSettingValueInteger(RadioSettingValue):
     """An integer setting"""
     def __init__(self, minval, maxval, current, step=1):
@@ -78,9 +82,8 @@ class RadioSettingValueInteger(RadioSettingValue):
         except:
             raise InvalidValueError("An integer is required")
         if value > self._max or value < self._min:
-            raise InvalidValueError("Value %i not in range %i-%i" % (value,
-                                                                     self._min,
-                                                                     self._max))
+            raise InvalidValueError("Value %i not in range %i-%i" %
+                                    (value, self._min, self._max))
         RadioSettingValue.set_value(self, value)
 
     def get_min(self):
@@ -95,6 +98,7 @@ class RadioSettingValueInteger(RadioSettingValue):
         """Returns the step increment"""
         return self._step
 
+
 class RadioSettingValueFloat(RadioSettingValue):
     """A floating-point setting"""
     def __init__(self, minval, maxval, current, resolution=0.001, precision=4):
@@ -110,7 +114,6 @@ class RadioSettingValueFloat(RadioSettingValue):
         if value is None:
             value = self._current
         fmt_string = "%%.%if" % self._pre
-        print fmt_string
         return fmt_string % value
 
     def set_value(self, value):
@@ -122,7 +125,7 @@ class RadioSettingValueFloat(RadioSettingValue):
             raise InvalidValueError("Value %s not in range %s-%s" % (
                     self.format(value),
                     self.format(self._min), self.format(self._max)))
-        
+
         # FIXME: honor resolution
 
         RadioSettingValue.set_value(self, value)
@@ -134,6 +137,7 @@ class RadioSettingValueFloat(RadioSettingValue):
     def get_max(self):
         """Returns the maximum allowed value"""
 
+
 class RadioSettingValueBoolean(RadioSettingValue):
     """A boolean setting"""
     def __init__(self, current):
@@ -143,9 +147,14 @@ class RadioSettingValueBoolean(RadioSettingValue):
     def set_value(self, value):
         RadioSettingValue.set_value(self, bool(value))
 
+    def __bool__(self):
+        return bool(self.get_value())
+    __nonzero__ = __bool__
+
     def __str__(self):
         return str(bool(self.get_value()))
 
+
 class RadioSettingValueList(RadioSettingValue):
     """A list-of-strings setting"""
     def __init__(self, options, current):
@@ -154,7 +163,7 @@ class RadioSettingValueList(RadioSettingValue):
         self.set_value(current)
 
     def set_value(self, value):
-        if not value in self._options:
+        if value not in self._options:
             raise InvalidValueError("%s is not valid for this setting" % value)
         RadioSettingValue.set_value(self, value)
 
@@ -165,14 +174,15 @@ class RadioSettingValueList(RadioSettingValue):
     def __trunc__(self):
         return self._options.index(self._current)
 
+
 class RadioSettingValueString(RadioSettingValue):
     """A string setting"""
     def __init__(self, minlength, maxlength, current,
-                 autopad=True):
+                 autopad=True, charset=chirp_common.CHARSET_ASCII):
         RadioSettingValue.__init__(self)
         self._minlength = minlength
         self._maxlength = maxlength
-        self._charset = chirp_common.CHARSET_ASCII
+        self._charset = charset
         self._autopad = autopad
         self.set_value(current)
 
@@ -182,8 +192,8 @@ class RadioSettingValueString(RadioSettingValue):
 
     def set_value(self, value):
         if len(value) < self._minlength or len(value) > self._maxlength:
-            raise InvalidValueError("Value must be between %i and %i chars" % (\
-                    self._minlength, self._maxlength))
+            raise InvalidValueError("Value must be between %i and %i chars" %
+                                    (self._minlength, self._maxlength))
         if self._autopad:
             value = value.ljust(self._maxlength)
         for char in value:
@@ -195,6 +205,16 @@ class RadioSettingValueString(RadioSettingValue):
     def __str__(self):
         return self._current
 
+
+class RadioSettings(list):
+    def __init__(self, *groups):
+        list.__init__(self, groups)
+
+    def __str__(self):
+        items = [str(self[i]) for i in range(0, len(self))]
+        return "\n".join(items)
+
+
 class RadioSettingGroup(object):
     """A group of settings"""
     def _validate(self, element):
@@ -203,12 +223,12 @@ class RadioSettingGroup(object):
             raise InternalError("Incorrect type %s" % type(element))
 
     def __init__(self, name, shortname, *elements):
-        self._name = name           # Setting identifier
-        self._shortname = shortname # Short human-readable name/description
-        self.__doc__ = name         # Longer explanation/documentation
+        self._name = name            # Setting identifier
+        self._shortname = shortname  # Short human-readable name/description
+        self.__doc__ = name          # Longer explanation/documentation
         self._elements = {}
         self._element_order = []
-        
+
         for element in elements:
             self._validate(element)
             self.append(element)
@@ -226,9 +246,9 @@ class RadioSettingGroup(object):
         self.__doc__ = doc
 
     def __str__(self):
-        string = "{Settings Group %s:\n" % self._name
-        for element in self._elements.values():
-            string += str(element) + "\n"
+        string = "group '%s': {\n" % self._name
+        for element in sorted(self._elements.values()):
+            string += "\t" + str(element) + "\n"
         string += "}"
         return string
 
@@ -240,17 +260,20 @@ class RadioSettingGroup(object):
 
     def __iter__(self):
         class RSGIterator:
-            """Iterator for a RadioSettingsGroup"""
+            """Iterator for a RadioSettingGroup"""
+
             def __init__(self, rsg):
                 self.__rsg = rsg
                 self.__i = 0
+
             def __iter__(self):
                 return self
+
             def next(self):
                 """Next Iterator Interface"""
                 if self.__i >= len(self.__rsg.keys()):
                     raise StopIteration()
-                e =  self.__rsg[self.__rsg.keys()[self.__i]]
+                e = self.__rsg[self.__rsg.keys()[self.__i]]
                 self.__i += 1
                 return e
         return RSGIterator(self)
@@ -281,6 +304,7 @@ class RadioSettingGroup(object):
         """Returns the list of elements"""
         return [self._elements[name] for name in self._element_order]
 
+
 class RadioSetting(RadioSettingGroup):
     """A single setting, which could be an array of items like a group"""
     def __init__(self, *args):
@@ -302,7 +326,8 @@ class RadioSetting(RadioSettingGroup):
             raise InternalError("Incorrect type")
 
     def changed(self):
-        """Returns True if any of the elements in the group have been changed"""
+        """Returns True if any of the elements
+        in the group have been changed"""
         for element in self._elements.values():
             if element.changed():
                 return True
@@ -332,7 +357,7 @@ class RadioSetting(RadioSettingGroup):
                 raise InternalError("Setting %s is not a scalar" % self._name)
         else:
             self.__dict__[name] = value
-            
+
     # List interface
 
     def append(self, value):
@@ -348,8 +373,7 @@ class RadioSetting(RadioSettingGroup):
     def __setitem__(self, name, value):
         if not isinstance(name, int):
             raise IndexError("Index `%s' is not an integer" % name)
-        if self._elements.has_key(name):
+        if name in self._elements:
             self._elements[name].set_value(value)
         else:
             self._elements[name] = value
-
diff --git a/chirpui/__init__.py b/chirp/ui/__init__.py
similarity index 100%
rename from chirpui/__init__.py
rename to chirp/ui/__init__.py
diff --git a/chirpui/bandplans.py b/chirp/ui/bandplans.py
similarity index 93%
rename from chirpui/bandplans.py
rename to chirp/ui/bandplans.py
index 9d34cd1..483261c 100644
--- a/chirpui/bandplans.py
+++ b/chirp/ui/bandplans.py
@@ -14,9 +14,12 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import gtk
+import logging
 from chirp import bandplan, bandplan_na, bandplan_au
 from chirp import bandplan_iaru_r1, bandplan_iaru_r2, bandplan_iaru_r3
-from chirpui import inputdialog
+from chirp.ui import inputdialog
+
+LOG = logging.getLogger(__name__)
 
 
 class BandPlans(object):
@@ -44,7 +47,8 @@ class BandPlans(object):
                 # Check for duplicates.
                 duplicates = [x for x in plan.BANDS if x == band]
                 if len(duplicates) > 1:
-                    print "Bandplan %s has duplicates %s" % (name, duplicates)
+                    LOG.warn("Bandplan %s has duplicates %s" %
+                             (name, duplicates))
                 # Add repeater inputs.
                 rpt_input = band.inverse()
                 if rpt_input not in plan.BANDS:
@@ -102,6 +106,7 @@ class BandPlans(object):
                 self._config.set_bool(shortname, selection == details[0],
                                       "bandplan")
                 if selection == details[0]:
-                    print "Selected band plan %s: %s" % (shortname, selection)
+                    LOG.info("Selected band plan %s: %s" %
+                             (shortname, selection))
 
         d.destroy()
diff --git a/chirpui/bankedit.py b/chirp/ui/bankedit.py
similarity index 94%
rename from chirpui/bankedit.py
rename to chirp/ui/bankedit.py
index a6cb71e..48d4b4f 100644
--- a/chirpui/bankedit.py
+++ b/chirp/ui/bankedit.py
@@ -16,11 +16,15 @@
 import gtk
 import gobject
 import time
+import logging
 
 from gobject import TYPE_INT, TYPE_STRING, TYPE_BOOLEAN
 
 from chirp import chirp_common
-from chirpui import common, miscwidgets
+from chirp.ui import common, miscwidgets
+
+LOG = logging.getLogger(__name__)
+
 
 class MappingNamesJob(common.RadioJob):
     def __init__(self, model, editor, cb):
@@ -37,6 +41,7 @@ class MappingNamesJob(common.RadioJob):
 
         gobject.idle_add(self.cb, *self.cb_args)
 
+
 class MappingNameEditor(common.Editor):
     def refresh(self):
         def got_mappings():
@@ -117,6 +122,7 @@ class MappingNameEditor(common.Editor):
     def mappings_changed(self):
         pass
 
+
 class MemoryMappingsJob(common.RadioJob):
     def __init__(self, model, cb, number):
         common.RadioJob.__init__(self, cb, None)
@@ -137,6 +143,7 @@ class MemoryMappingsJob(common.RadioJob):
                     indexes.append(self.__model.get_memory_index(mem, mapping))
         self.cb(mem, mappings, indexes, *self.cb_args)
 
+
 class MappingMembershipEditor(common.Editor):
     def _number_to_path(self, number):
         return (number - self._rf.memory_bounds[0],)
@@ -160,9 +167,9 @@ class MappingMembershipEditor(common.Editor):
         indexes.sort()
         for i in range(0, num_indexes):
             if i not in indexes:
-                return i + index_bounds[0] # In case not zero-origin index
+                return i + index_bounds[0]  # In case not zero-origin index
 
-        return 0 # If the mapping is full, just wrap around!
+        return 0  # If the mapping is full, just wrap around!
 
     def _toggled_cb(self, rend, path, colnum):
         try:
@@ -233,7 +240,7 @@ class MappingMembershipEditor(common.Editor):
 
     def _index_edited_cb(self, rend, path, new):
         loc, = self._store.get(self._store.get_iter(path), self.C_LOC)
-        
+
         def refresh_memory(*args):
             self.refresh_memory(loc)
 
@@ -261,7 +268,7 @@ class MappingMembershipEditor(common.Editor):
         job = common.RadioJob(get_mapping, "get_memory", loc)
         job.set_desc(_("Getting memory {num}").format(num=loc))
         self.rthread.submit(job)
-            
+
     def __init__(self, rthread, editorset, model):
         super(MappingMembershipEditor, self).__init__(rthread)
 
@@ -278,16 +285,16 @@ class MappingMembershipEditor(common.Editor):
             ]
 
         self._cols = [
-            ("_filled",      TYPE_BOOLEAN, None,                 ),
+            ("_filled",      TYPE_BOOLEAN, None, ),
             ] + self._view_cols
 
         self.C_FILLED = 0
-        self.C_LOC    = 1
-        self.C_FREQ   = 2
-        self.C_NAME   = 3
-        self.C_INDEX  = 4
-        self.C_MAPPINGS  = 5 # and beyond
-        
+        self.C_LOC = 1
+        self.C_FREQ = 2
+        self.C_NAME = 3
+        self.C_INDEX = 4
+        self.C_MAPPINGS = 5  # and beyond
+
         cols = list(self._cols)
 
         self._index_cache = []
@@ -296,7 +303,7 @@ class MappingMembershipEditor(common.Editor):
             label = "%s %i" % (self._type, (i+1))
             cols.append((label, TYPE_BOOLEAN, gtk.CellRendererToggle))
 
-        self._store = gtk.ListStore(*tuple([y for x,y,z in cols]))
+        self._store = gtk.ListStore(*tuple([y for x, y, z in cols]))
         self._view = gtk.TreeView(self._store)
 
         is_indexed = isinstance(self._model,
@@ -335,7 +342,8 @@ class MappingMembershipEditor(common.Editor):
         sw.add(self._view)
         self._view.show()
 
-        for i in range(*self._rf.memory_bounds):
+        (min, max) = self._rf.memory_bounds
+        for i in range(min, max+1):
             iter = self._store.append()
             self._store.set(iter,
                             self.C_FILLED, False,
@@ -360,11 +368,8 @@ class MappingMembershipEditor(common.Editor):
             for i in range(0, len(self.mappings)):
                 row.append(i + len(self._cols))
                 row.append(self.mappings[i][0] in mappings)
-                
+
             self._store.set(iter, *tuple(row))
-            if memory.number == self._rf.memory_bounds[1] - 1:
-                print "Got all %s info in %s" % (self._type,
-                                                 (time.time() - self._start))
 
         job = MemoryMappingsJob(self._model, got_mem, number)
         job.set_desc(_("Getting {type} information "
@@ -372,8 +377,12 @@ class MappingMembershipEditor(common.Editor):
         self.rthread.submit(job)
 
     def refresh_all_memories(self):
-        for i in range(*self._rf.memory_bounds):
+        start = time.time()
+        (min, max) = self._rf.memory_bounds
+        for i in range(min, max+1):
             self.refresh_memory(i)
+        LOG.debug("Got all %s info in %s" %
+                  (self._type, (time.time() - start)))
 
     def refresh_mappings(self, and_memories=False):
         def got_mappings():
@@ -397,7 +406,6 @@ class MappingMembershipEditor(common.Editor):
         if self._loaded:
             return
 
-        self._start = time.time()
         self.refresh_mappings(True)
 
         self._loaded = True
diff --git a/chirpui/clone.py b/chirp/ui/clone.py
similarity index 91%
rename from chirpui/clone.py
rename to chirp/ui/clone.py
index 7210d4a..f20c1d2 100644
--- a/chirpui/clone.py
+++ b/chirp/ui/clone.py
@@ -14,16 +14,20 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import threading
+import logging
 import os
 
 import gtk
 import gobject
 
 from chirp import platform, directory, detect, chirp_common
-from chirpui import miscwidgets, cloneprog, inputdialog, common, config
+from chirp.ui import miscwidgets, cloneprog, inputdialog, common, config
+
+LOG = logging.getLogger(__name__)
 
 AUTO_DETECT_STRING = "Auto Detect (Icom Only)"
 
+
 class CloneSettings:
     def __init__(self):
         self.port = None
@@ -32,11 +36,12 @@ class CloneSettings:
     def __str__(self):
         s = ""
         if self.radio_class:
-            return _("{vendor} {model} on {port}").format(\
+            return _("{vendor} {model} on {port}").format(
                 vendor=self.radio_class.VENDOR,
                 model=self.radio_class.MODEL,
                 port=self.port)
 
+
 class CloneSettingsDialog(gtk.Dialog):
     def __make_field(self, label, widget):
         l = gtk.Label(label)
@@ -58,7 +63,7 @@ class CloneSettingsDialog(gtk.Dialog):
                 port = ports[0]
             else:
                 port = ""
-            if not port in ports:
+            if port not in ports:
                 ports.insert(0, port)
 
         return miscwidgets.make_choice(ports, True, port)
@@ -73,7 +78,7 @@ class CloneSettingsDialog(gtk.Dialog):
                     not issubclass(rclass, chirp_common.LiveRadio):
                 continue
 
-            if not vendors.has_key(rclass.VENDOR):
+            if rclass.VENDOR not in vendors:
                 vendors[rclass.VENDOR] = []
 
             vendors[rclass.VENDOR].append(rclass)
@@ -169,7 +174,9 @@ class CloneSettingsDialog(gtk.Dialog):
             try:
                 cs.radio_class = detect.DETECT_FUNCTIONS[vendor](cs.port)
                 if not cs.radio_class:
-                    raise Exception(_("Unable to detect radio on {port}").format(port=cs.port))
+                    raise Exception(
+                        _("Unable to detect radio on {port}").format(
+                            port=cs.port))
             except Exception, e:
                 d = inputdialog.ExceptionDialog(e)
                 d.run()
@@ -181,8 +188,10 @@ class CloneSettingsDialog(gtk.Dialog):
                     cs.radio_class = rclass
                     break
             if not cs.radio_class:
-                common.show_error(_("Internal error: Unable to upload to {model}").format(model=model))
-                print self.__vendors
+                common.show_error(
+                    _("Internal error: Unable to upload to {model}").format(
+                        model=model))
+                LOG.info(self.__vendors)
                 return None
 
         conf = config.get("state")
@@ -192,9 +201,11 @@ class CloneSettingsDialog(gtk.Dialog):
 
         return cs
 
+
 class CloneCancelledException(Exception):
     pass
 
+
 class CloneThread(threading.Thread):
     def __status(self, status):
         gobject.idle_add(self.__progw.status, status)
@@ -214,12 +225,12 @@ class CloneThread(threading.Thread):
         self.__cancelled = True
 
     def run(self):
-        print "Clone thread started"
+        LOG.debug("Clone thread started")
 
         gobject.idle_add(self.__progw.show)
 
         self.__radio.status_fn = self.__status
-        
+
         try:
             if self.__out:
                 self.__radio.sync_out()
@@ -229,7 +240,7 @@ class CloneThread(threading.Thread):
             emsg = None
         except Exception, e:
             common.log_exception()
-            print _("Clone failed: {error}").format(error=e)
+            LOG.error(_("Clone failed: {error}").format(error=e))
             emsg = e
 
         gobject.idle_add(self.__progw.hide)
@@ -237,11 +248,12 @@ class CloneThread(threading.Thread):
         # NB: Compulsory close of the radio's serial connection
         self.__radio.pipe.close()
 
-        print "Clone thread ended"
+        LOG.debug("Clone thread ended")
 
         if self.__cback and not self.__cancelled:
             gobject.idle_add(self.__cback, self.__radio, emsg)
 
+
 if __name__ == "__main__":
     d = CloneSettingsDialog("/dev/ttyUSB0")
     r = d.run()
diff --git a/chirpui/cloneprog.py b/chirp/ui/cloneprog.py
similarity index 93%
rename from chirpui/cloneprog.py
rename to chirp/ui/cloneprog.py
index 7427bd2..99beea9 100644
--- a/chirpui/cloneprog.py
+++ b/chirp/ui/cloneprog.py
@@ -15,15 +15,16 @@
 
 import gtk
 
+
 class CloneProg(gtk.Window):
     def __init__(self, **args):
-        if args.has_key("parent"):
+        if "parent" in args:
             parent = args["parent"]
             del args["parent"]
         else:
             parent = None
 
-        if args.has_key("cancel"):
+        if "cancel" in args:
             cancel = args["cancel"]
             del args["cancel"]
         else:
@@ -34,7 +35,7 @@ class CloneProg(gtk.Window):
         self.set_transient_for(parent)
         self.set_modal(True)
         self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
-        self.set_position(gtk.WIN_POS_CENTER_ON_PARENT)	
+        self.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
 
         vbox = gtk.VBox(False, 2)
         vbox.show()
diff --git a/chirpui/common.py b/chirp/ui/common.py
similarity index 84%
rename from chirpui/common.py
rename to chirp/ui/common.py
index 8511b8b..dc611b3 100644
--- a/chirpui/common.py
+++ b/chirp/ui/common.py
@@ -21,15 +21,21 @@ import threading
 import time
 import os
 import traceback
+import logging
 
 from chirp import errors
-from chirpui import reporting
+from chirp.ui import reporting, config
+
+LOG = logging.getLogger(__name__)
+
+CONF = config.get()
+
 
 class Editor(gobject.GObject):
     __gsignals__ = {
-        'changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
-        'usermsg' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
-                     (gobject.TYPE_STRING,)),
+        'changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+        'usermsg': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+                    (gobject.TYPE_STRING,)),
         }
 
     root = None
@@ -72,11 +78,11 @@ class Editor(gobject.GObject):
 
 gobject.type_register(Editor)
 
+
 def DBG(*args):
     if False:
-        print " ".join(args)
+        LOG.debug(" ".join(args))
 
-VERBOSE = False
 
 class RadioJob:
     def __init__(self, cb, func, *args, **kwargs):
@@ -106,17 +112,17 @@ class RadioJob:
             DBG("Running %s (%s %s)" % (self.func,
                                         str(self.args),
                                         str(self.kwargs)))
-            if VERBOSE:
-                print self.desc
+            DBG(self.desc)
             result = func(*self.args, **self.kwargs)
         except errors.InvalidMemoryLocation, e:
             result = e
         except Exception, e:
-            print "Exception running RadioJob: %s" % e
+            LOG.error("Exception running RadioJob: %s" % e)
             log_exception()
-            print "Job Args:   %s" % str(self.args)
-            print "Job KWArgs: %s" % str(self.kwargs)
-            print "Job Called from:%s%s" % (os.linesep, "".join(self.tb[:-1]))
+            LOG.error("Job Args:   %s" % str(self.args))
+            LOG.error("Job KWArgs: %s" % str(self.kwargs))
+            LOG.error("Job Called from:%s%s" %
+                      (os.linesep, "".join(self.tb[:-1])))
             result = e
 
         if self.cb:
@@ -129,17 +135,17 @@ class RadioJob:
         try:
             func = getattr(self.target, self.func)
         except AttributeError, e:
-            print "No such radio function `%s' in %s" % (self.func,
-                                                         self.target)
+            LOG.error("No such radio function `%s' in %s" %
+                      (self.func, self.target))
             return
 
         self._execute(self.target, func)
 
+
 class RadioThread(threading.Thread, gobject.GObject):
     __gsignals__ = {
-        "status" : (gobject.SIGNAL_RUN_LAST,
-                    gobject.TYPE_NONE,
-                    (gobject.TYPE_STRING,)),
+        "status": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+                   (gobject.TYPE_STRING,)),
         }
 
     def __init__(self, radio, parent=None):
@@ -169,7 +175,7 @@ class RadioThread(threading.Thread, gobject.GObject):
         self.__lock.release()
 
     def _qsubmit(self, job, priority):
-        if not self.__queue.has_key(priority):
+        if priority not in self.__queue:
             self.__queue[priority] = []
 
         self.__queue[priority].append(job)
@@ -177,7 +183,7 @@ class RadioThread(threading.Thread, gobject.GObject):
 
     def _queue_clear_below(self, priority):
         for i in range(0, priority):
-            if self.__queue.has_key(i) and len(self.__queue[i]) != 0:
+            if i in self.__queue and len(self.__queue[i]) != 0:
                 return False
 
         return True
@@ -219,13 +225,13 @@ class RadioThread(threading.Thread, gobject.GObject):
         self.flush()
         self.__counter.release()
         self.__enabled = False
-    
+
     def _status(self, msg):
         jobs = 0
         for i in dict(self.__queue):
                 jobs += len(self.__queue[i])
         gobject.idle_add(self.emit, "status", "[%i] %s" % (jobs, msg))
-            
+
     def _queue_pop(self, priority):
         try:
             return self.__queue[priority].pop(0)
@@ -237,8 +243,8 @@ class RadioThread(threading.Thread, gobject.GObject):
         while self.__enabled:
             DBG("Waiting for a job")
             if last_job_desc:
-                self.status(_("Completed") + " " + last_job_desc + \
-                                " (" + _("idle") + ")")
+                self.status(_("Completed") + " " + last_job_desc +
+                            " (" + _("idle") + ")")
             self.__counter.acquire()
 
             self._qlock()
@@ -256,17 +262,19 @@ class RadioThread(threading.Thread, gobject.GObject):
                 last_job_desc = job.desc
                 self.unlock()
 
-        print "RadioThread exiting"
+        LOG.debug("RadioThread exiting")
+
 
 def log_exception():
-	import traceback
-	import sys
+    import traceback
+    import sys
 
-        reporting.report_exception(traceback.format_exc(limit=30))
+    reporting.report_exception(traceback.format_exc(limit=30))
+
+    LOG.error("-- Exception: --")
+    LOG.error(traceback.format_exc(limit=30))
+    LOG.error("----------------")
 
-	print "-- Exception: --"
-	traceback.print_exc(limit=30, file=sys.stdout)
-	print "------"
 
 def show_error(msg, parent=None):
     d = gtk.MessageDialog(buttons=gtk.BUTTONS_OK, parent=parent,
@@ -279,6 +287,7 @@ def show_error(msg, parent=None):
     d.run()
     d.destroy()
 
+
 def ask_yesno_question(msg, parent=None):
     d = gtk.MessageDialog(buttons=gtk.BUTTONS_YES_NO, parent=parent,
                           type=gtk.MESSAGE_QUESTION)
@@ -292,6 +301,7 @@ def ask_yesno_question(msg, parent=None):
 
     return r == gtk.RESPONSE_YES
 
+
 def combo_select(box, value):
     store = box.get_model()
     iter = store.get_iter_first()
@@ -303,6 +313,7 @@ def combo_select(box, value):
 
     return False
 
+
 def _add_text(d, text):
     v = gtk.TextView()
     v.get_buffer().set_text(text)
@@ -312,9 +323,10 @@ def _add_text(d, text):
     sw = gtk.ScrolledWindow()
     sw.add(v)
     sw.show()
-    d.vbox.pack_start(sw, 1,1,1)
+    d.vbox.pack_start(sw, 1, 1, 1)
     return v
 
+
 def show_error_text(msg, text, parent=None):
     d = gtk.MessageDialog(buttons=gtk.BUTTONS_OK, parent=parent,
                           type=gtk.MESSAGE_ERROR)
@@ -328,6 +340,7 @@ def show_error_text(msg, text, parent=None):
     d.run()
     d.destroy()
 
+
 def show_warning(msg, text,
                  parent=None, buttons=None, title="Warning",
                  can_squelch=False):
@@ -361,19 +374,28 @@ def show_warning(msg, text,
         return r, cb.get_active()
     return r
 
-def simple_diff(a, b):
+
+def simple_diff(a, b, diffsonly=False):
     lines_a = a.split(os.linesep)
     lines_b = b.split(os.linesep)
+    blankprinted = True
 
     diff = ""
     for i in range(0, len(lines_a)):
         if lines_a[i] != lines_b[i]:
             diff += "-%s%s" % (lines_a[i], os.linesep)
             diff += "+%s%s" % (lines_b[i], os.linesep)
+            blankprinted = False
+        elif diffsonly is True:
+            if blankprinted:
+                continue
+            diff += os.linesep
+            blankprinted = True
         else:
             diff += " %s%s" % (lines_a[i], os.linesep)
     return diff
 
+
 # A quick hacked up tool to show a blob of text in a dialog window
 # using fixed-width fonts. It also highlights lines that start with
 # a '-' in red bold font and '+' with blue bold font.
@@ -391,6 +413,14 @@ def show_diff_blob(title, result):
     tag.set_property("weight", pango.WEIGHT_BOLD)
     tags.add(tag)
 
+    try:
+        fontsize = CONF.get_int("diff_fontsize", "developer")
+    except Exception:
+        fontsize = 11
+    if fontsize < 4 or fontsize > 144:
+        LOG.info("Unsupported diff_fontsize %i. Using 11." % fontsize)
+        fontsize = 11
+
     lines = result.split(os.linesep)
     for line in lines:
         if line.startswith("-"):
@@ -401,7 +431,7 @@ def show_diff_blob(title, result):
             tags = ()
         b.insert_with_tags_by_name(b.get_end_iter(), line + os.linesep, *tags)
     v = gtk.TextView(b)
-    fontdesc = pango.FontDescription("Courier 11")
+    fontdesc = pango.FontDescription("Courier %i" % fontsize)
     v.modify_font(fontdesc)
     v.set_editable(False)
     v.show()
@@ -413,6 +443,7 @@ def show_diff_blob(title, result):
     d.run()
     d.destroy()
 
+
 def unpluralize(string):
     if string.endswith("s"):
         return string[:-1]
diff --git a/chirpui/config.py b/chirp/ui/config.py
similarity index 88%
rename from chirpui/config.py
rename to chirp/ui/config.py
index c5b55c1..29a4bf2 100644
--- a/chirpui/config.py
+++ b/chirp/ui/config.py
@@ -17,6 +17,7 @@ from chirp import platform
 from ConfigParser import ConfigParser
 import os
 
+
 class ChirpConfig:
     def __init__(self, basepath, name="chirp.config"):
         self.__basepath = basepath
@@ -36,14 +37,14 @@ class ChirpConfig:
         self.__config.write(cfg_file)
         cfg_file.close()
 
-    def get(self, key, section):
+    def get(self, key, section, raw=False):
         if not self.__config.has_section(section):
             return None
 
         if not self.__config.has_option(section, key):
             return None
 
-        return self.__config.get(section, key)
+        return self.__config.get(section, key, raw=raw)
 
     def set(self, key, value, section):
         if not self.__config.has_section(section):
@@ -60,13 +61,15 @@ class ChirpConfig:
         if not self.__config.items(section):
             self.__config.remove_section(section)
 
+
 class ChirpConfigProxy:
     def __init__(self, config, section="global"):
         self._config = config
         self._section = section
 
-    def get(self, key, section=None):
-        return self._config.get(key, section or self._section)
+    def get(self, key, section=None, raw=False):
+        return self._config.get(key, section or self._section,
+                                raw=raw)
 
     def set(self, key, value, section=None):
         return self._config.set(key, value, section or self._section)
@@ -94,9 +97,13 @@ class ChirpConfigProxy:
             raise ValueError("Value is not an integer")
 
         self.set(key, "%i" % value, section)
-       
-    def get_bool(self, key, section=None):
-        return self.get(key, section) == "True"
+
+    def get_bool(self, key, section=None, default=False):
+        val = self.get(key, section)
+        if val is None:
+            return default
+        else:
+            return val == "True"
 
     def set_bool(self, key, value, section=None):
         self.set(key, str(bool(value)), section)
@@ -107,7 +114,10 @@ class ChirpConfigProxy:
     def remove_option(self, key, section):
         self._config.remove_option(section, key)
 
+
 _CONFIG = None
+
+
 def get(section="global"):
     global _CONFIG
 
diff --git a/chirpui/dstaredit.py b/chirp/ui/dstaredit.py
similarity index 89%
rename from chirpui/dstaredit.py
rename to chirp/ui/dstaredit.py
index df0baa8..3409ef4 100644
--- a/chirpui/dstaredit.py
+++ b/chirp/ui/dstaredit.py
@@ -15,15 +15,19 @@
 
 import gtk
 import gobject
+import logging
 
-from chirpui import common, miscwidgets
+from chirp.ui import common, miscwidgets
+
+LOG = logging.getLogger(__name__)
 
 WIDGETW = 80
 WIDGETH = 30
 
+
 class CallsignEditor(gtk.HBox):
     __gsignals__ = {
-        "changed" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+        "changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
         }
 
     def _cs_changed(self, listw, callid):
@@ -35,10 +39,10 @@ class CallsignEditor(gtk.HBox):
         return True
 
     def make_list(self, width):
-        cols = [ (gobject.TYPE_INT, ""),
-                 (gobject.TYPE_INT, ""),
-                 (gobject.TYPE_STRING, _("Callsign")),
-                 ]
+        cols = [(gobject.TYPE_INT, ""),
+                (gobject.TYPE_INT, ""),
+                (gobject.TYPE_STRING, _("Callsign")),
+                ]
 
         self.listw = miscwidgets.KeyedListWidget(cols)
         self.listw.show()
@@ -54,7 +58,7 @@ class CallsignEditor(gtk.HBox):
         sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
         sw.add_with_viewport(self.listw)
         sw.show()
-        
+
         return sw
 
     def __init__(self, first_fixed=False, width=8):
@@ -90,28 +94,29 @@ class CallsignEditor(gtk.HBox):
 
         return calls
 
+
 class DStarEditor(common.Editor):
     def __cs_changed(self, cse):
         job = None
 
-        print "Callsigns: %s" % cse.get_callsigns()
+        LOG.debug("Callsigns: %s" % cse.get_callsigns())
         if cse == self.editor_ucall:
             job = common.RadioJob(None,
                                   "set_urcall_list",
                                   cse.get_callsigns())
-            print "Set urcall"
+            LOG.debug("Set urcall")
         elif cse == self.editor_rcall:
             job = common.RadioJob(None,
                                   "set_repeater_call_list",
                                   cse.get_callsigns())
-            print "Set rcall"
+            LOG.debug("Set rcall")
         elif cse == self.editor_mcall:
             job = common.RadioJob(None,
                                   "set_mycall_list",
                                   cse.get_callsigns())
 
         if job:
-            print "Submitting job to update call lists"
+            LOG.debug("Submitting job to update call lists")
             self.rthread.submit(job)
 
         self.emit("changed")
@@ -149,10 +154,10 @@ class DStarEditor(common.Editor):
         return box
 
     def focus(self):
-        if  self.loaded:
+        if self.loaded:
             return
         self.loaded = True
-        print "Loading callsigns..."
+        LOG.debug("Loading callsigns...")
 
         def set_ucall(calls):
             self.editor_ucall.set_callsigns(calls)
@@ -186,7 +191,7 @@ class DStarEditor(common.Editor):
         self.editor_ucall = self.editor_rcall = None
 
         vbox = gtk.VBox(False, 2)
-        vbox.pack_start(self.make_callsigns(), 0, 0, 0)        
+        vbox.pack_start(self.make_callsigns(), 0, 0, 0)
 
         tmp = gtk.Label("")
         tmp.show()
diff --git a/chirpui/editorset.py b/chirp/ui/editorset.py
similarity index 89%
rename from chirpui/editorset.py
rename to chirp/ui/editorset.py
index c38b7d8..5c5d62e 100644
--- a/chirpui/editorset.py
+++ b/chirp/ui/editorset.py
@@ -16,23 +16,28 @@
 import os
 import gtk
 import gobject
+import logging
+
+from chirp import chirp_common, directory
+from chirp.drivers import generic_csv, generic_xml
+from chirp.ui import memedit, dstaredit, bankedit, common, importdialog
+from chirp.ui import inputdialog, reporting, settingsedit, radiobrowser, config
+
+LOG = logging.getLogger(__name__)
 
-from chirp import chirp_common, directory, generic_csv, generic_xml
-from chirpui import memedit, dstaredit, bankedit, common, importdialog
-from chirpui import inputdialog, reporting, settingsedit, radiobrowser, config
 
 class EditorSet(gtk.VBox):
     __gsignals__ = {
-        "want-close" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
-        "status" : (gobject.SIGNAL_RUN_LAST,
-                    gobject.TYPE_NONE,
-                    (gobject.TYPE_STRING,)),
-        "usermsg": (gobject.SIGNAL_RUN_LAST,
+        "want-close": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+        "status": (gobject.SIGNAL_RUN_LAST,
                    gobject.TYPE_NONE,
                    (gobject.TYPE_STRING,)),
-        "editor-selected" : (gobject.SIGNAL_RUN_LAST,
-                             gobject.TYPE_NONE,
-                             (gobject.TYPE_STRING,)),
+        "usermsg": (gobject.SIGNAL_RUN_LAST,
+                    gobject.TYPE_NONE,
+                    (gobject.TYPE_STRING,)),
+        "editor-selected": (gobject.SIGNAL_RUN_LAST,
+                            gobject.TYPE_NONE,
+                            (gobject.TYPE_STRING,)),
         }
 
     def _make_device_mapping_editors(self, device, devrthread, index):
@@ -76,7 +81,7 @@ class EditorSet(gtk.VBox):
         memories.connect("changed", self.editor_changed)
 
         if self.rf.has_sub_devices:
-            label = (_("Memories (%(variant)s)") % 
+            label = (_("Memories (%(variant)s)") %
                      dict(variant=device.VARIANT))
             rf = device.get_features()
         else:
@@ -97,7 +102,8 @@ class EditorSet(gtk.VBox):
             editor.connect("changed", self.editor_changed)
             self.editors["dstar"] = editor
 
-    def __init__(self, source, parent_window=None, filename=None, tempname=None):
+    def __init__(self, source, parent_window=None,
+                 filename=None, tempname=None):
         gtk.VBox.__init__(self, True, 0)
 
         self.parent_window = parent_window
@@ -146,7 +152,7 @@ class EditorSet(gtk.VBox):
 
         conf = config.get()
         if (hasattr(self.rthread.radio, '_memobj') and
-            conf.get_bool("developer", "state")):
+                conf.get_bool("developer", "state")):
             editor = radiobrowser.RadioBrowser(self.rthread)
             lab = gtk.Label(_("Browser"))
             self.tabs.append_page(editor.root, lab)
@@ -170,8 +176,14 @@ class EditorSet(gtk.VBox):
         self.text_label.show()
         self.label.pack_start(self.text_label, 1, 1, 1)
 
-        button = gtk.Button("X")
+        button = gtk.Button()
         button.set_relief(gtk.RELIEF_NONE)
+        button.set_focus_on_click(False)
+
+        icon = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
+        icon.show()
+        button.add(icon)
+
         button.connect("clicked", lambda x: self.emit("want-close"))
         button.show()
         self.label.pack_start(button, 0, 0, 0)
@@ -191,7 +203,7 @@ class EditorSet(gtk.VBox):
         if not fname:
             fname = self.filename
             if not os.path.exists(self.filename):
-                return # Probably before the first "Save as"
+                return  # Probably before the first "Save as"
         else:
             self.filename = fname
 
@@ -212,7 +224,7 @@ class EditorSet(gtk.VBox):
         memedit.prefill()
 
     def editor_changed(self, target_editor=None):
-        print "%s changed" % target_editor
+        LOG.debug("%s changed" % target_editor)
         if not isinstance(self.radio, chirp_common.LiveRadio):
             self.modified = True
             self.update_tab()
@@ -234,7 +246,7 @@ class EditorSet(gtk.VBox):
         # interface to queue our own changes before opening it up to the
         # rest of the world.
 
-        dst_rthread._qlock_when_idle(5) # Suspend job submission when idle
+        dst_rthread._qlock_when_idle(5)  # Suspend job submission when idle
 
         dialog = dlgclass(src_radio, dst_rthread.radio, self.parent_window)
         r = dialog.run()
@@ -244,7 +256,7 @@ class EditorSet(gtk.VBox):
             return
 
         count = dialog.do_import(dst_rthread)
-        print "Imported %i" % count
+        LOG.debug("Imported %i" % count)
         dst_rthread._qunlock()
 
         if count > 0:
@@ -259,10 +271,9 @@ class EditorSet(gtk.VBox):
         choices = [x.VARIANT for x in devices]
 
         d = inputdialog.ChoiceDialog(choices)
-        d.label.set_text(_("The {vendor} {model} has multiple "
-                           "independent sub-devices").format( \
-                vendor=radio.VENDOR, model=radio.MODEL) + os.linesep + \
-                             _("Choose one to import from:"))
+        text = _("The {vendor} {model} has multiple independent sub-devices")
+        d.label.set_text(text.format(vendor=radio.VENDOR, model=radio.MODEL) +
+                         os.linesep + _("Choose one to import from:"))
         r = d.run()
         chosen = d.choice.get_active_text()
         d.destroy()
@@ -291,8 +302,10 @@ class EditorSet(gtk.VBox):
         if isinstance(src_radio, chirp_common.NetworkSourceRadio):
             ww = importdialog.WaitWindow("Querying...", self.parent_window)
             ww.show()
+
             def status(status):
                 ww.set(float(status.cur) / float(status.max))
+
             try:
                 src_radio.status_fn = status
                 src_radio.do_fetch()
@@ -325,7 +338,7 @@ class EditorSet(gtk.VBox):
             common.log_exception()
             common.show_error(_("There was an error during "
                                 "import: {error}").format(error=e))
-        
+
     def do_export(self, filen):
         try:
             if filen.lower().endswith(".csv"):
@@ -367,7 +380,7 @@ class EditorSet(gtk.VBox):
             common.show_error(_("There was an error during "
                                 "export: {error}").format(error=e),
                               self)
-            
+
     def prime(self):
         # NOTE: this is only called to prime new CSV files, so assume
         # only one memory editor for now
@@ -383,7 +396,7 @@ class EditorSet(gtk.VBox):
 
     def tab_selected(self, notebook, foo, pagenum):
         widget = notebook.get_nth_page(pagenum)
-        for k,v in self.editors.items():
+        for k, v in self.editors.items():
             if v and v.root == widget:
                 v.focus()
                 self.emit("editor-selected", k)
@@ -402,8 +415,9 @@ class EditorSet(gtk.VBox):
             editor and editor.prepare_close()
 
     def get_current_editor(self):
+        tabs = self.tabs
         for lab, e in self.editors.items():
-            if e and self.tabs.page_num(e.root) == self.tabs.get_current_page():
+            if e and tabs.page_num(e.root) == tabs.get_current_page():
                 return e
         raise Exception("No editor selected?")
 
diff --git a/chirpui/fips.py b/chirp/ui/fips.py
similarity index 97%
rename from chirpui/fips.py
rename to chirp/ui/fips.py
index 0094ec1..a7089e7 100644
--- a/chirpui/fips.py
+++ b/chirp/ui/fips.py
@@ -15,77 +15,77 @@
 
 
 FIPS_STATES = {
-    "Alaska"               : 2,
-    "Alabama"              : 1,
-    "Arkansas"             : 5,
-    "Arizona"              : 4,
-    "California"           : 6,
-    "Colorado"             : 8,
-    "Connecticut"          : 9,
-    "District of Columbia" : 11,
-    "Delaware"             : 10,
-    "Florida"              : 12,
-    "Georgia"              : 13,
-    "Guam"                 : 66,
-    "Hawaii"               : 15,
-    "Iowa"                 : 19,
-    "Idaho"                : 16,
-    "Illinois"             : 17,
-    "Indiana"              : 18,
-    "Kansas"               : 20,
-    "Kentucky"             : 21,
-    "Louisiana"            : 22,
-    "Massachusetts"        : 25,
-    "Maryland"             : 24,
-    "Maine"                : 23,
-    "Michigan"             : 26,
-    "Minnesota"            : 27,
-    "Missouri"             : 29,
-    "Mississippi"          : 28,
-    "Montana"              : 30,
-    "North Carolina"       : 37,
-    "North Dakota"         : 38,
-    "Nebraska"             : 31,
-    "New Hampshire"        : 33,
-    "New Jersey"           : 34,
-    "New Mexico"           : 35,
-    "Nevada"               : 32,
-    "New York"             : 36,
-    "Ohio"                 : 39,
-    "Oklahoma"             : 40,
-    "Oregon"               : 41,
-    "Pennsylvania"         : 42,
-    "Puerto Rico"          : 72,
-    "Rhode Island"         : 44,
-    "South Carolina"       : 45,
-    "South Dakota"         : 46,
-    "Tennessee"            : 47,
-    "Texas"                : 48,
-    "Utah"                 : 49,
-    "Virginia"             : 51,
-    "Virgin Islands"       : 78,
-    "Vermont"              : 50,
-    "Washington"           : 53,
-    "Wisconsin"            : 55,
-    "West Virginia"        : 54,
-    "Wyoming"              : 56,
-    "Alberta"              : "CA01",
-    "British Columbia"     : "CA02",
-    "Manitoba"             : "CA03",
-    "New Brunswick"        : "CA04",
+    "Alaska":                    2,
+    "Alabama":                   1,
+    "Arkansas":                  5,
+    "Arizona":                   4,
+    "California":                6,
+    "Colorado":                  8,
+    "Connecticut":               9,
+    "District of Columbia":      11,
+    "Delaware":                  10,
+    "Florida":                   12,
+    "Georgia":                   13,
+    "Guam":                      66,
+    "Hawaii":                    15,
+    "Iowa":                      19,
+    "Idaho":                     16,
+    "Illinois":                  17,
+    "Indiana":                   18,
+    "Kansas":                    20,
+    "Kentucky":                  21,
+    "Louisiana":                 22,
+    "Massachusetts":             25,
+    "Maryland":                  24,
+    "Maine":                     23,
+    "Michigan":                  26,
+    "Minnesota":                 27,
+    "Missouri":                  29,
+    "Mississippi":               28,
+    "Montana":                   30,
+    "North Carolina":            37,
+    "North Dakota":              38,
+    "Nebraska":                  31,
+    "New Hampshire":             33,
+    "New Jersey":                34,
+    "New Mexico":                35,
+    "Nevada":                    32,
+    "New York":                  36,
+    "Ohio":                      39,
+    "Oklahoma":                  40,
+    "Oregon":                    41,
+    "Pennsylvania":              42,
+    "Puerto Rico":               72,
+    "Rhode Island":              44,
+    "South Carolina":            45,
+    "South Dakota":              46,
+    "Tennessee":                 47,
+    "Texas":                     48,
+    "Utah":                      49,
+    "Virginia":                  51,
+    "Virgin Islands":            78,
+    "Vermont":                   50,
+    "Washington":                53,
+    "Wisconsin":                 55,
+    "West Virginia":             54,
+    "Wyoming":                   56,
+    "Alberta":                   "CA01",
+    "British Columbia":          "CA02",
+    "Manitoba":                  "CA03",
+    "New Brunswick":             "CA04",
     "Newfoundland and Labrador": "CA05",
-    "Northwest Territories": "CA13",
-    "Nova Scotia"          : "CA07",
-    "Nunavut"              : "CA14",
-    "Ontario"              : "CA08",
-    "Prince Edward Island" : "CA09",
-    "Quebec"               : "CA10",
-    "Saskatchewan"         : "CA11",
-    "Yukon"                : "CA12",
+    "Northwest Territories":     "CA13",
+    "Nova Scotia":               "CA07",
+    "Nunavut":                   "CA14",
+    "Ontario":                   "CA08",
+    "Prince Edward Island":      "CA09",
+    "Quebec":                    "CA10",
+    "Saskatchewan":              "CA11",
+    "Yukon":                     "CA12",
 }
 
 FIPS_COUNTIES = {
-  1: { '--All--': '%',
+  1:  {'--All--': '%',
        'Autauga County, AL': '001',
        'Baldwin County, AL': '003',
        'Barbour County, AL': '005',
@@ -153,7 +153,7 @@ FIPS_COUNTIES = {
        'Washington County, AL': '129',
        'Wilcox County, AL': '131',
        'Winston County, AL': '133'},
-  2: { '--All--': '%',
+  2:  {'--All--': '%',
        'Aleutians East Borough, AK': '013',
        'Aleutians West Census Area, AK': '016',
        'Anchorage Borough/municipality, AK': '020',
@@ -181,7 +181,7 @@ FIPS_COUNTIES = {
        'Wrangell-Petersburg Census Area, AK': '280',
        'Yakutat Borough, AK': '282',
        'Yukon-Koyukuk Census Area, AK': '290'},
-  4: { '--All--': '%',
+  4:  {'--All--': '%',
        'Apache County, AZ': '001',
        'Cochise County, AZ': '003',
        'Coconino County, AZ': '005',
@@ -197,7 +197,7 @@ FIPS_COUNTIES = {
        'Santa Cruz County, AZ': '023',
        'Yavapai County, AZ': '025',
        'Yuma County, AZ': '027'},
-  5: { '--All--': '%',
+  5:  {'--All--': '%',
        'Arkansas County, AR': '001',
        'Ashley County, AR': '003',
        'Baxter County, AR': '005',
@@ -273,7 +273,7 @@ FIPS_COUNTIES = {
        'White County, AR': '145',
        'Woodruff County, AR': '147',
        'Yell County, AR': '149'},
-  6: { '--All--': '%',
+  6:  {'--All--': '%',
        'Alameda County, CA': '001',
        'Alpine County, CA': '003',
        'Amador County, CA': '005',
@@ -332,7 +332,7 @@ FIPS_COUNTIES = {
        'Ventura County, CA': '111',
        'Yolo County, CA': '113',
        'Yuba County, CA': '115'},
-  8: { '--All--': '%',
+  8:  {'--All--': '%',
        'Adams County, CO': '001',
        'Alamosa County, CO': '003',
        'Arapahoe County, CO': '005',
@@ -397,7 +397,7 @@ FIPS_COUNTIES = {
        'Washington County, CO': '121',
        'Weld County, CO': '123',
        'Yuma County, CO': '125'},
-  9: { '--All--': '%',
+  9:  {'--All--': '%',
        'Fairfield County, CT': '001',
        'Hartford County, CT': '003',
        'Litchfield County, CT': '005',
@@ -406,12 +406,12 @@ FIPS_COUNTIES = {
        'New London County, CT': '011',
        'Tolland County, CT': '013',
        'Windham County, CT': '015'},
-  10: { '--All--': '%',
+  10:  {'--All--': '%',
         'Kent County, DE': '001',
         'New Castle County, DE': '003',
         'Sussex County, DE': '005'},
-  11: { '--All--': '%', 'District of Columbia': '001'},
-  12: { '--All--': '%',
+  11:  {'--All--': '%', 'District of Columbia': '001'},
+  12:  {'--All--': '%',
         'Alachua County, FL': '001',
         'Baker County, FL': '003',
         'Bay County, FL': '005',
@@ -479,7 +479,7 @@ FIPS_COUNTIES = {
         'Wakulla County, FL': '129',
         'Walton County, FL': '131',
         'Washington County, FL': '133'},
-  13: { '--All--': '%',
+  13:  {'--All--': '%',
         'Appling County, GA': '001',
         'Atkinson County, GA': '003',
         'Bacon County, GA': '005',
@@ -639,12 +639,12 @@ FIPS_COUNTIES = {
         'Wilkes County, GA': '317',
         'Wilkinson County, GA': '319',
         'Worth County, GA': '321'},
-  15: { '--All--': '%',
+  15:  {'--All--': '%',
         'Hawaii County, HI': '001',
         'Honolulu County/city, HI': '003',
         'Kauai County, HI': '007',
         'Maui County, HI': '009'},
-  16: { '--All--': '%',
+  16:  {'--All--': '%',
         'Ada County, ID': '001',
         'Adams County, ID': '003',
         'Bannock County, ID': '005',
@@ -689,7 +689,7 @@ FIPS_COUNTIES = {
         'Twin Falls County, ID': '083',
         'Valley County, ID': '085',
         'Washington County, ID': '087'},
-  17: { '--All--': '%',
+  17:  {'--All--': '%',
         'Adams County, IL': '001',
         'Alexander County, IL': '003',
         'Bond County, IL': '005',
@@ -792,7 +792,7 @@ FIPS_COUNTIES = {
         'Williamson County, IL': '199',
         'Winnebago County, IL': '201',
         'Woodford County, IL': '203'},
-  18: { '--All--': '%',
+  18:  {'--All--': '%',
         'Adams County, IN': '001',
         'Allen County, IN': '003',
         'Bartholomew County, IN': '005',
@@ -885,7 +885,7 @@ FIPS_COUNTIES = {
         'Wells County, IN': '179',
         'White County, IN': '181',
         'Whitley County, IN': '183'},
-  19: { '--All--': '%',
+  19:  {'--All--': '%',
         'Adair County, IA': '001',
         'Adams County, IA': '003',
         'Allamakee County, IA': '005',
@@ -985,7 +985,7 @@ FIPS_COUNTIES = {
         'Woodbury County, IA': '193',
         'Worth County, IA': '195',
         'Wright County, IA': '197'},
-  20: { '--All--': '%',
+  20:  {'--All--': '%',
         'Allen County, KS': '001',
         'Anderson County, KS': '003',
         'Atchison County, KS': '005',
@@ -1091,7 +1091,7 @@ FIPS_COUNTIES = {
         'Wilson County, KS': '205',
         'Woodson County, KS': '207',
         'Wyandotte County, KS': '209'},
-  21: { '--All--': '%',
+  21:  {'--All--': '%',
         'Adair County, KY': '001',
         'Allen County, KY': '003',
         'Anderson County, KY': '005',
@@ -1212,7 +1212,7 @@ FIPS_COUNTIES = {
         'Whitley County, KY': '235',
         'Wolfe County, KY': '237',
         'Woodford County, KY': '239'},
-  22: { '--All--': '%',
+  22:  {'--All--': '%',
         'Acadia Parish, LA': '001',
         'Allen Parish, LA': '003',
         'Ascension Parish, LA': '005',
@@ -1277,7 +1277,7 @@ FIPS_COUNTIES = {
         'West Carroll Parish, LA': '123',
         'West Feliciana Parish, LA': '125',
         'Winn Parish, LA': '127'},
-  23: { '--All--': '%',
+  23:  {'--All--': '%',
         'Androscoggin County, ME': '001',
         'Aroostook County, ME': '003',
         'Cumberland County, ME': '005',
@@ -1294,7 +1294,7 @@ FIPS_COUNTIES = {
         'Waldo County, ME': '027',
         'Washington County, ME': '029',
         'York County, ME': '031'},
-  24: { '--All--': '%',
+  24:  {'--All--': '%',
         'Allegany County, MD': '001',
         'Anne Arundel County, MD': '003',
         'Baltimore County, MD': '005',
@@ -1319,7 +1319,7 @@ FIPS_COUNTIES = {
         'Washington County, MD': '043',
         'Wicomico County, MD': '045',
         'Worcester County, MD': '047'},
-  25: { '--All--': '%',
+  25:  {'--All--': '%',
         'Barnstable County, MA': '001',
         'Berkshire County, MA': '003',
         'Bristol County, MA': '005',
@@ -1334,7 +1334,7 @@ FIPS_COUNTIES = {
         'Plymouth County, MA': '023',
         'Suffolk County, MA': '025',
         'Worcester County, MA': '027'},
-  26: { '--All--': '%',
+  26:  {'--All--': '%',
         'Alcona County, MI': '001',
         'Alger County, MI': '003',
         'Allegan County, MI': '005',
@@ -1418,7 +1418,7 @@ FIPS_COUNTIES = {
         'Washtenaw County, MI': '161',
         'Wayne County, MI': '163',
         'Wexford County, MI': '165'},
-  27: { '--All--': '%',
+  27:  {'--All--': '%',
         'Aitkin County, MN': '001',
         'Anoka County, MN': '003',
         'Becker County, MN': '005',
@@ -1506,7 +1506,7 @@ FIPS_COUNTIES = {
         'Winona County, MN': '169',
         'Wright County, MN': '171',
         'Yellow Medicine County, MN': '173'},
-  28: { '--All--': '%',
+  28:  {'--All--': '%',
         'Adams County, MS': '001',
         'Alcorn County, MS': '003',
         'Amite County, MS': '005',
@@ -1589,7 +1589,7 @@ FIPS_COUNTIES = {
         'Winston County, MS': '159',
         'Yalobusha County, MS': '161',
         'Yazoo County, MS': '163'},
-  29: { '--All--': '%',
+  29:  {'--All--': '%',
         'Adair County, MO': '001',
         'Andrew County, MO': '003',
         'Atchison County, MO': '005',
@@ -1705,7 +1705,7 @@ FIPS_COUNTIES = {
         'Webster County, MO': '225',
         'Worth County, MO': '227',
         'Wright County, MO': '229'},
-  30: { '--All--': '%',
+  30:  {'--All--': '%',
         'Beaverhead County, MT': '001',
         'Big Horn County, MT': '003',
         'Blaine County, MT': '005',
@@ -1762,7 +1762,7 @@ FIPS_COUNTIES = {
         'Wheatland County, MT': '107',
         'Wibaux County, MT': '109',
         'Yellowstone County, MT': '111'},
-  31: { '--All--': '%',
+  31:  {'--All--': '%',
         'Adams County, NE': '001',
         'Antelope County, NE': '003',
         'Arthur County, NE': '005',
@@ -1856,7 +1856,7 @@ FIPS_COUNTIES = {
         'Webster County, NE': '181',
         'Wheeler County, NE': '183',
         'York County, NE': '185'},
-  32: { '--All--': '%',
+  32:  {'--All--': '%',
         'Carson City, NV': '510',
         'Churchill County, NV': '001',
         'Clark County, NV': '003',
@@ -1874,7 +1874,7 @@ FIPS_COUNTIES = {
         'Storey County, NV': '029',
         'Washoe County, NV': '031',
         'White Pine County, NV': '033'},
-  33: { '--All--': '%',
+  33:  {'--All--': '%',
         'Belknap County, NH': '001',
         'Carroll County, NH': '003',
         'Cheshire County, NH': '005',
@@ -1885,7 +1885,7 @@ FIPS_COUNTIES = {
         'Rockingham County, NH': '015',
         'Strafford County, NH': '017',
         'Sullivan County, NH': '019'},
-  34: { '--All--': '%',
+  34:  {'--All--': '%',
         'Atlantic County, NJ': '001',
         'Bergen County, NJ': '003',
         'Burlington County, NJ': '005',
@@ -1907,7 +1907,7 @@ FIPS_COUNTIES = {
         'Sussex County, NJ': '037',
         'Union County, NJ': '039',
         'Warren County, NJ': '041'},
-  35: { '--All--': '%',
+  35:  {'--All--': '%',
         'Bernalillo County, NM': '001',
         'Catron County, NM': '003',
         'Chaves County, NM': '005',
@@ -1941,7 +1941,7 @@ FIPS_COUNTIES = {
         'Torrance County, NM': '057',
         'Union County, NM': '059',
         'Valencia County, NM': '061'},
-  36: { '--All--': '%',
+  36:  {'--All--': '%',
         'Albany County, NY': '001',
         'Allegany County, NY': '003',
         'Bronx County, NY': '005',
@@ -2004,7 +2004,7 @@ FIPS_COUNTIES = {
         'Westchester County, NY': '119',
         'Wyoming County, NY': '121',
         'Yates County, NY': '123'},
-  37: { '--All--': '%',
+  37:  {'--All--': '%',
         'Alamance County, NC': '001',
         'Alexander County, NC': '003',
         'Alleghany County, NC': '005',
@@ -2105,7 +2105,7 @@ FIPS_COUNTIES = {
         'Wilson County, NC': '195',
         'Yadkin County, NC': '197',
         'Yancey County, NC': '199'},
-  38: { '--All--': '%',
+  38:  {'--All--': '%',
         'Adams County, ND': '001',
         'Barnes County, ND': '003',
         'Benson County, ND': '005',
@@ -2159,7 +2159,7 @@ FIPS_COUNTIES = {
         'Ward County, ND': '101',
         'Wells County, ND': '103',
         'Williams County, ND': '105'},
-  39: { '--All--': '%',
+  39:  {'--All--': '%',
         'Adams County, OH': '001',
         'Allen County, OH': '003',
         'Ashland County, OH': '005',
@@ -2248,7 +2248,7 @@ FIPS_COUNTIES = {
         'Williams County, OH': '171',
         'Wood County, OH': '173',
         'Wyandot County, OH': '175'},
-  40: { '--All--': '%',
+  40:  {'--All--': '%',
         'Adair County, OK': '001',
         'Alfalfa County, OK': '003',
         'Atoka County, OK': '005',
@@ -2326,7 +2326,7 @@ FIPS_COUNTIES = {
         'Washita County, OK': '149',
         'Woods County, OK': '151',
         'Woodward County, OK': '153'},
-  41: { '--All--': '%',
+  41:  {'--All--': '%',
         'Baker County, OR': '001',
         'Benton County, OR': '003',
         'Clackamas County, OR': '005',
@@ -2363,7 +2363,7 @@ FIPS_COUNTIES = {
         'Washington County, OR': '067',
         'Wheeler County, OR': '069',
         'Yamhill County, OR': '071'},
-  42: { '--All--': '%',
+  42:  {'--All--': '%',
         'Adams County, PA': '001',
         'Allegheny County, PA': '003',
         'Armstrong County, PA': '005',
@@ -2431,13 +2431,13 @@ FIPS_COUNTIES = {
         'Westmoreland County, PA': '129',
         'Wyoming County, PA': '131',
         'York County, PA': '133'},
-  44: { '--All--': '%',
+  44:  {'--All--': '%',
         'Bristol County, RI': '001',
         'Kent County, RI': '003',
         'Newport County, RI': '005',
         'Providence County, RI': '007',
         'Washington County, RI': '009'},
-  45: { '--All--': '%',
+  45:  {'--All--': '%',
         'Abbeville County, SC': '001',
         'Aiken County, SC': '003',
         'Allendale County, SC': '005',
@@ -2484,7 +2484,7 @@ FIPS_COUNTIES = {
         'Union County, SC': '087',
         'Williamsburg County, SC': '089',
         'York County, SC': '091'},
-  46: { '--All--': '%',
+  46:  {'--All--': '%',
         'Aurora County, SD': '003',
         'Beadle County, SD': '005',
         'Bennett County, SD': '007',
@@ -2551,7 +2551,7 @@ FIPS_COUNTIES = {
         'Walworth County, SD': '129',
         'Yankton County, SD': '135',
         'Ziebach County, SD': '137'},
-  47: { '--All--': '%',
+  47:  {'--All--': '%',
         'Anderson County, TN': '001',
         'Bedford County, TN': '003',
         'Benton County, TN': '005',
@@ -2647,7 +2647,7 @@ FIPS_COUNTIES = {
         'White County, TN': '185',
         'Williamson County, TN': '187',
         'Wilson County, TN': '189'},
-  48: { '--All--': '%',
+  48:  {'--All--': '%',
         'Anderson County, TX': '001',
         'Andrews County, TX': '003',
         'Angelina County, TX': '005',
@@ -2902,7 +2902,7 @@ FIPS_COUNTIES = {
         'Young County, TX': '503',
         'Zapata County, TX': '505',
         'Zavala County, TX': '507'},
-  49: { '--All--': '%',
+  49:  {'--All--': '%',
         'Beaver County, UT': '001',
         'Box Elder County, UT': '003',
         'Cache County, UT': '005',
@@ -2932,7 +2932,7 @@ FIPS_COUNTIES = {
         'Washington County, UT': '053',
         'Wayne County, UT': '055',
         'Weber County, UT': '057'},
-  50: { '--All--': '%',
+  50:  {'--All--': '%',
         'Addison County, VT': '001',
         'Bennington County, VT': '003',
         'Caledonia County, VT': '005',
@@ -2947,7 +2947,7 @@ FIPS_COUNTIES = {
         'Washington County, VT': '023',
         'Windham County, VT': '025',
         'Windsor County, VT': '027'},
-  51: { '--All--': '%',
+  51:  {'--All--': '%',
         'Accomack County, VA': '001',
         'Albemarle County, VA': '003',
         'Alexandria city, VA': '510',
@@ -3082,7 +3082,7 @@ FIPS_COUNTIES = {
         'Wise County, VA': '195',
         'Wythe County, VA': '197',
         'York County, VA': '199'},
-  53: { '--All--': '%',
+  53:  {'--All--': '%',
         'Adams County, WA': '001',
         'Asotin County, WA': '003',
         'Benton County, WA': '005',
@@ -3122,7 +3122,7 @@ FIPS_COUNTIES = {
         'Whatcom County, WA': '073',
         'Whitman County, WA': '075',
         'Yakima County, WA': '077'},
-  54: { '--All--': '%',
+  54:  {'--All--': '%',
         'Barbour County, WV': '001',
         'Berkeley County, WV': '003',
         'Boone County, WV': '005',
@@ -3178,7 +3178,7 @@ FIPS_COUNTIES = {
         'Wirt County, WV': '105',
         'Wood County, WV': '107',
         'Wyoming County, WV': '109'},
-  55: { '--All--': '%',
+  55:  {'--All--': '%',
         'Adams County, WI': '001',
         'Ashland County, WI': '003',
         'Barron County, WI': '005',
@@ -3251,7 +3251,7 @@ FIPS_COUNTIES = {
         'Waushara County, WI': '137',
         'Winnebago County, WI': '139',
         'Wood County, WI': '141'},
-  56: { '--All--': '%',
+  56:  {'--All--': '%',
         'Albany County, WY': '001',
         'Big Horn County, WY': '003',
         'Campbell County, WY': '005',
@@ -3275,7 +3275,7 @@ FIPS_COUNTIES = {
         'Uinta County, WY': '041',
         'Washakie County, WY': '043',
         'Weston County, WY': '045'},
-  72: { '--All--': '%',
+  72:  {'--All--': '%',
         'Adjuntas Municipio, PR': '001',
         'Aguada Municipio, PR': '003',
         'Aguadilla Municipio, PR': '005',
@@ -3354,7 +3354,7 @@ FIPS_COUNTIES = {
         'Villalba Municipio, PR': '149',
         'Yabucoa Municipio, PR': '151',
         'Yauco Municipio, PR': '153'},
-  '01': { 'Autauga County, AL': '001',
+  '01':  {'Autauga County, AL': '001',
           'Baldwin County, AL': '003',
           'Barbour County, AL': '005',
           'Bibb County, AL': '007',
@@ -3421,7 +3421,7 @@ FIPS_COUNTIES = {
           'Washington County, AL': '129',
           'Wilcox County, AL': '131',
           'Winston County, AL': '133'},
-  '02': { 'Aleutians East Borough, AK': '013',
+  '02':  {'Aleutians East Borough, AK': '013',
           'Aleutians West Census Area, AK': '016',
           'Anchorage Borough/municipality, AK': '020',
           'Bethel Census Area, AK': '050',
@@ -3448,7 +3448,7 @@ FIPS_COUNTIES = {
           'Wrangell-Petersburg Census Area, AK': '280',
           'Yakutat Borough, AK': '282',
           'Yukon-Koyukuk Census Area, AK': '290'},
-  '04': { 'Apache County, AZ': '001',
+  '04':  {'Apache County, AZ': '001',
           'Cochise County, AZ': '003',
           'Coconino County, AZ': '005',
           'Gila County, AZ': '007',
@@ -3463,7 +3463,7 @@ FIPS_COUNTIES = {
           'Santa Cruz County, AZ': '023',
           'Yavapai County, AZ': '025',
           'Yuma County, AZ': '027'},
-  '05': { 'Arkansas County, AR': '001',
+  '05':  {'Arkansas County, AR': '001',
           'Ashley County, AR': '003',
           'Baxter County, AR': '005',
           'Benton County, AR': '007',
@@ -3538,7 +3538,7 @@ FIPS_COUNTIES = {
           'White County, AR': '145',
           'Woodruff County, AR': '147',
           'Yell County, AR': '149'},
-  '06': { 'Alameda County, CA': '001',
+  '06':  {'Alameda County, CA': '001',
           'Alpine County, CA': '003',
           'Amador County, CA': '005',
           'Butte County, CA': '007',
@@ -3596,7 +3596,7 @@ FIPS_COUNTIES = {
           'Ventura County, CA': '111',
           'Yolo County, CA': '113',
           'Yuba County, CA': '115'},
-  '08': { 'Adams County, CO': '001',
+  '08':  {'Adams County, CO': '001',
           'Alamosa County, CO': '003',
           'Arapahoe County, CO': '005',
           'Archuleta County, CO': '007',
@@ -3660,7 +3660,7 @@ FIPS_COUNTIES = {
           'Washington County, CO': '121',
           'Weld County, CO': '123',
           'Yuma County, CO': '125'},
-  '09': { 'Fairfield County, CT': '001',
+  '09':  {'Fairfield County, CT': '001',
           'Hartford County, CT': '003',
           'Litchfield County, CT': '005',
           'Middlesex County, CT': '007',
@@ -3668,11 +3668,11 @@ FIPS_COUNTIES = {
           'New London County, CT': '011',
           'Tolland County, CT': '013',
           'Windham County, CT': '015'},
-  '10': { 'Kent County, DE': '001',
+  '10':  {'Kent County, DE': '001',
           'New Castle County, DE': '003',
           'Sussex County, DE': '005'},
-  '11': { 'District of Columbia': '001'},
-  '12': { 'Alachua County, FL': '001',
+  '11':  {'District of Columbia': '001'},
+  '12':  {'Alachua County, FL': '001',
           'Baker County, FL': '003',
           'Bay County, FL': '005',
           'Bradford County, FL': '007',
@@ -3739,7 +3739,7 @@ FIPS_COUNTIES = {
           'Wakulla County, FL': '129',
           'Walton County, FL': '131',
           'Washington County, FL': '133'},
-  '13': { 'Appling County, GA': '001',
+  '13':  {'Appling County, GA': '001',
           'Atkinson County, GA': '003',
           'Bacon County, GA': '005',
           'Baker County, GA': '007',
@@ -3898,11 +3898,11 @@ FIPS_COUNTIES = {
           'Wilkes County, GA': '317',
           'Wilkinson County, GA': '319',
           'Worth County, GA': '321'},
-  '15': { 'Hawaii County, HI': '001',
+  '15':  {'Hawaii County, HI': '001',
           'Honolulu County/city, HI': '003',
           'Kauai County, HI': '007',
           'Maui County, HI': '009'},
-  '16': { 'Ada County, ID': '001',
+  '16':  {'Ada County, ID': '001',
           'Adams County, ID': '003',
           'Bannock County, ID': '005',
           'Bear Lake County, ID': '007',
@@ -3946,7 +3946,7 @@ FIPS_COUNTIES = {
           'Twin Falls County, ID': '083',
           'Valley County, ID': '085',
           'Washington County, ID': '087'},
-  '17': { 'Adams County, IL': '001',
+  '17':  {'Adams County, IL': '001',
           'Alexander County, IL': '003',
           'Bond County, IL': '005',
           'Boone County, IL': '007',
@@ -4048,7 +4048,7 @@ FIPS_COUNTIES = {
           'Williamson County, IL': '199',
           'Winnebago County, IL': '201',
           'Woodford County, IL': '203'},
-  '18': { 'Adams County, IN': '001',
+  '18':  {'Adams County, IN': '001',
           'Allen County, IN': '003',
           'Bartholomew County, IN': '005',
           'Benton County, IN': '007',
@@ -4140,7 +4140,7 @@ FIPS_COUNTIES = {
           'Wells County, IN': '179',
           'White County, IN': '181',
           'Whitley County, IN': '183'},
-  '19': { 'Adair County, IA': '001',
+  '19':  {'Adair County, IA': '001',
           'Adams County, IA': '003',
           'Allamakee County, IA': '005',
           'Appanoose County, IA': '007',
@@ -4239,7 +4239,7 @@ FIPS_COUNTIES = {
           'Woodbury County, IA': '193',
           'Worth County, IA': '195',
           'Wright County, IA': '197'},
-  '20': { 'Allen County, KS': '001',
+  '20':  {'Allen County, KS': '001',
           'Anderson County, KS': '003',
           'Atchison County, KS': '005',
           'Barber County, KS': '007',
@@ -4344,7 +4344,7 @@ FIPS_COUNTIES = {
           'Wilson County, KS': '205',
           'Woodson County, KS': '207',
           'Wyandotte County, KS': '209'},
-  '21': { 'Adair County, KY': '001',
+  '21':  {'Adair County, KY': '001',
           'Allen County, KY': '003',
           'Anderson County, KY': '005',
           'Ballard County, KY': '007',
@@ -4464,7 +4464,7 @@ FIPS_COUNTIES = {
           'Whitley County, KY': '235',
           'Wolfe County, KY': '237',
           'Woodford County, KY': '239'},
-  '22': { 'Acadia Parish, LA': '001',
+  '22':  {'Acadia Parish, LA': '001',
           'Allen Parish, LA': '003',
           'Ascension Parish, LA': '005',
           'Assumption Parish, LA': '007',
@@ -4528,7 +4528,7 @@ FIPS_COUNTIES = {
           'West Carroll Parish, LA': '123',
           'West Feliciana Parish, LA': '125',
           'Winn Parish, LA': '127'},
-  '23': { 'Androscoggin County, ME': '001',
+  '23':  {'Androscoggin County, ME': '001',
           'Aroostook County, ME': '003',
           'Cumberland County, ME': '005',
           'Franklin County, ME': '007',
@@ -4544,7 +4544,7 @@ FIPS_COUNTIES = {
           'Waldo County, ME': '027',
           'Washington County, ME': '029',
           'York County, ME': '031'},
-  '24': { 'Allegany County, MD': '001',
+  '24':  {'Allegany County, MD': '001',
           'Anne Arundel County, MD': '003',
           'Baltimore County, MD': '005',
           'Baltimore city, MD': '510',
@@ -4568,7 +4568,7 @@ FIPS_COUNTIES = {
           'Washington County, MD': '043',
           'Wicomico County, MD': '045',
           'Worcester County, MD': '047'},
-  '25': { 'Barnstable County, MA': '001',
+  '25':  {'Barnstable County, MA': '001',
           'Berkshire County, MA': '003',
           'Bristol County, MA': '005',
           'Dukes County, MA': '007',
@@ -4582,7 +4582,7 @@ FIPS_COUNTIES = {
           'Plymouth County, MA': '023',
           'Suffolk County, MA': '025',
           'Worcester County, MA': '027'},
-  '26': { 'Alcona County, MI': '001',
+  '26':  {'Alcona County, MI': '001',
           'Alger County, MI': '003',
           'Allegan County, MI': '005',
           'Alpena County, MI': '007',
@@ -4665,7 +4665,7 @@ FIPS_COUNTIES = {
           'Washtenaw County, MI': '161',
           'Wayne County, MI': '163',
           'Wexford County, MI': '165'},
-  '27': { 'Aitkin County, MN': '001',
+  '27':  {'Aitkin County, MN': '001',
           'Anoka County, MN': '003',
           'Becker County, MN': '005',
           'Beltrami County, MN': '007',
@@ -4752,7 +4752,7 @@ FIPS_COUNTIES = {
           'Winona County, MN': '169',
           'Wright County, MN': '171',
           'Yellow Medicine County, MN': '173'},
-  '28': { 'Adams County, MS': '001',
+  '28':  {'Adams County, MS': '001',
           'Alcorn County, MS': '003',
           'Amite County, MS': '005',
           'Attala County, MS': '007',
@@ -4834,7 +4834,7 @@ FIPS_COUNTIES = {
           'Winston County, MS': '159',
           'Yalobusha County, MS': '161',
           'Yazoo County, MS': '163'},
-  '29': { 'Adair County, MO': '001',
+  '29':  {'Adair County, MO': '001',
           'Andrew County, MO': '003',
           'Atchison County, MO': '005',
           'Audrain County, MO': '007',
@@ -4949,7 +4949,7 @@ FIPS_COUNTIES = {
           'Webster County, MO': '225',
           'Worth County, MO': '227',
           'Wright County, MO': '229'},
-  '30': { 'Beaverhead County, MT': '001',
+  '30':  {'Beaverhead County, MT': '001',
           'Big Horn County, MT': '003',
           'Blaine County, MT': '005',
           'Broadwater County, MT': '007',
@@ -5005,7 +5005,7 @@ FIPS_COUNTIES = {
           'Wheatland County, MT': '107',
           'Wibaux County, MT': '109',
           'Yellowstone County, MT': '111'},
-  '31': { 'Adams County, NE': '001',
+  '31':  {'Adams County, NE': '001',
           'Antelope County, NE': '003',
           'Arthur County, NE': '005',
           'Banner County, NE': '007',
@@ -5098,7 +5098,7 @@ FIPS_COUNTIES = {
           'Webster County, NE': '181',
           'Wheeler County, NE': '183',
           'York County, NE': '185'},
-  '32': { 'Carson City, NV': '510',
+  '32':  {'Carson City, NV': '510',
           'Churchill County, NV': '001',
           'Clark County, NV': '003',
           'Douglas County, NV': '005',
@@ -5115,7 +5115,7 @@ FIPS_COUNTIES = {
           'Storey County, NV': '029',
           'Washoe County, NV': '031',
           'White Pine County, NV': '033'},
-  '33': { 'Belknap County, NH': '001',
+  '33':  {'Belknap County, NH': '001',
           'Carroll County, NH': '003',
           'Cheshire County, NH': '005',
           'Coos County, NH': '007',
@@ -5125,7 +5125,7 @@ FIPS_COUNTIES = {
           'Rockingham County, NH': '015',
           'Strafford County, NH': '017',
           'Sullivan County, NH': '019'},
-  '34': { 'Atlantic County, NJ': '001',
+  '34':  {'Atlantic County, NJ': '001',
           'Bergen County, NJ': '003',
           'Burlington County, NJ': '005',
           'Camden County, NJ': '007',
@@ -5146,7 +5146,7 @@ FIPS_COUNTIES = {
           'Sussex County, NJ': '037',
           'Union County, NJ': '039',
           'Warren County, NJ': '041'},
-  '35': { 'Bernalillo County, NM': '001',
+  '35':  {'Bernalillo County, NM': '001',
           'Catron County, NM': '003',
           'Chaves County, NM': '005',
           'Cibola County, NM': '006',
@@ -5179,7 +5179,7 @@ FIPS_COUNTIES = {
           'Torrance County, NM': '057',
           'Union County, NM': '059',
           'Valencia County, NM': '061'},
-  '36': { 'Albany County, NY': '001',
+  '36':  {'Albany County, NY': '001',
           'Allegany County, NY': '003',
           'Bronx County, NY': '005',
           'Broome County, NY': '007',
@@ -5241,7 +5241,7 @@ FIPS_COUNTIES = {
           'Westchester County, NY': '119',
           'Wyoming County, NY': '121',
           'Yates County, NY': '123'},
-  '37': { 'Alamance County, NC': '001',
+  '37':  {'Alamance County, NC': '001',
           'Alexander County, NC': '003',
           'Alleghany County, NC': '005',
           'Anson County, NC': '007',
@@ -5341,7 +5341,7 @@ FIPS_COUNTIES = {
           'Wilson County, NC': '195',
           'Yadkin County, NC': '197',
           'Yancey County, NC': '199'},
-  '38': { 'Adams County, ND': '001',
+  '38':  {'Adams County, ND': '001',
           'Barnes County, ND': '003',
           'Benson County, ND': '005',
           'Billings County, ND': '007',
@@ -5394,7 +5394,7 @@ FIPS_COUNTIES = {
           'Ward County, ND': '101',
           'Wells County, ND': '103',
           'Williams County, ND': '105'},
-  '39': { 'Adams County, OH': '001',
+  '39':  {'Adams County, OH': '001',
           'Allen County, OH': '003',
           'Ashland County, OH': '005',
           'Ashtabula County, OH': '007',
@@ -5482,7 +5482,7 @@ FIPS_COUNTIES = {
           'Williams County, OH': '171',
           'Wood County, OH': '173',
           'Wyandot County, OH': '175'},
-  '40': { 'Adair County, OK': '001',
+  '40':  {'Adair County, OK': '001',
           'Alfalfa County, OK': '003',
           'Atoka County, OK': '005',
           'Beaver County, OK': '007',
@@ -5559,7 +5559,7 @@ FIPS_COUNTIES = {
           'Washita County, OK': '149',
           'Woods County, OK': '151',
           'Woodward County, OK': '153'},
-  '41': { 'Baker County, OR': '001',
+  '41':  {'Baker County, OR': '001',
           'Benton County, OR': '003',
           'Clackamas County, OR': '005',
           'Clatsop County, OR': '007',
@@ -5595,7 +5595,7 @@ FIPS_COUNTIES = {
           'Washington County, OR': '067',
           'Wheeler County, OR': '069',
           'Yamhill County, OR': '071'},
-  '42': { 'Adams County, PA': '001',
+  '42':  {'Adams County, PA': '001',
           'Allegheny County, PA': '003',
           'Armstrong County, PA': '005',
           'Beaver County, PA': '007',
@@ -5662,12 +5662,12 @@ FIPS_COUNTIES = {
           'Westmoreland County, PA': '129',
           'Wyoming County, PA': '131',
           'York County, PA': '133'},
-  '44': { 'Bristol County, RI': '001',
+  '44':  {'Bristol County, RI': '001',
           'Kent County, RI': '003',
           'Newport County, RI': '005',
           'Providence County, RI': '007',
           'Washington County, RI': '009'},
-  '45': { 'Abbeville County, SC': '001',
+  '45':  {'Abbeville County, SC': '001',
           'Aiken County, SC': '003',
           'Allendale County, SC': '005',
           'Anderson County, SC': '007',
@@ -5713,7 +5713,7 @@ FIPS_COUNTIES = {
           'Union County, SC': '087',
           'Williamsburg County, SC': '089',
           'York County, SC': '091'},
-  '46': { 'Aurora County, SD': '003',
+  '46':  {'Aurora County, SD': '003',
           'Beadle County, SD': '005',
           'Bennett County, SD': '007',
           'Bon Homme County, SD': '009',
@@ -5779,7 +5779,7 @@ FIPS_COUNTIES = {
           'Walworth County, SD': '129',
           'Yankton County, SD': '135',
           'Ziebach County, SD': '137'},
-  '47': { 'Anderson County, TN': '001',
+  '47':  {'Anderson County, TN': '001',
           'Bedford County, TN': '003',
           'Benton County, TN': '005',
           'Bledsoe County, TN': '007',
@@ -5874,7 +5874,7 @@ FIPS_COUNTIES = {
           'White County, TN': '185',
           'Williamson County, TN': '187',
           'Wilson County, TN': '189'},
-  '48': { 'Anderson County, TX': '001',
+  '48':  {'Anderson County, TX': '001',
           'Andrews County, TX': '003',
           'Angelina County, TX': '005',
           'Aransas County, TX': '007',
@@ -6128,7 +6128,7 @@ FIPS_COUNTIES = {
           'Young County, TX': '503',
           'Zapata County, TX': '505',
           'Zavala County, TX': '507'},
-  '49': { 'Beaver County, UT': '001',
+  '49':  {'Beaver County, UT': '001',
           'Box Elder County, UT': '003',
           'Cache County, UT': '005',
           'Carbon County, UT': '007',
@@ -6157,7 +6157,7 @@ FIPS_COUNTIES = {
           'Washington County, UT': '053',
           'Wayne County, UT': '055',
           'Weber County, UT': '057'},
-  '50': { 'Addison County, VT': '001',
+  '50':  {'Addison County, VT': '001',
           'Bennington County, VT': '003',
           'Caledonia County, VT': '005',
           'Chittenden County, VT': '007',
@@ -6171,7 +6171,7 @@ FIPS_COUNTIES = {
           'Washington County, VT': '023',
           'Windham County, VT': '025',
           'Windsor County, VT': '027'},
-  '51': { 'Accomack County, VA': '001',
+  '51':  {'Accomack County, VA': '001',
           'Albemarle County, VA': '003',
           'Alexandria city, VA': '510',
           'Alleghany County, VA': '005',
@@ -6305,7 +6305,7 @@ FIPS_COUNTIES = {
           'Wise County, VA': '195',
           'Wythe County, VA': '197',
           'York County, VA': '199'},
-  '53': { 'Adams County, WA': '001',
+  '53':  {'Adams County, WA': '001',
           'Asotin County, WA': '003',
           'Benton County, WA': '005',
           'Chelan County, WA': '007',
@@ -6344,7 +6344,7 @@ FIPS_COUNTIES = {
           'Whatcom County, WA': '073',
           'Whitman County, WA': '075',
           'Yakima County, WA': '077'},
-  '54': { 'Barbour County, WV': '001',
+  '54':  {'Barbour County, WV': '001',
           'Berkeley County, WV': '003',
           'Boone County, WV': '005',
           'Braxton County, WV': '007',
@@ -6399,7 +6399,7 @@ FIPS_COUNTIES = {
           'Wirt County, WV': '105',
           'Wood County, WV': '107',
           'Wyoming County, WV': '109'},
-  '55': { 'Adams County, WI': '001',
+  '55':  {'Adams County, WI': '001',
           'Ashland County, WI': '003',
           'Barron County, WI': '005',
           'Bayfield County, WI': '007',
@@ -6471,7 +6471,7 @@ FIPS_COUNTIES = {
           'Waushara County, WI': '137',
           'Winnebago County, WI': '139',
           'Wood County, WI': '141'},
-  '56': { 'Albany County, WY': '001',
+  '56':  {'Albany County, WY': '001',
           'Big Horn County, WY': '003',
           'Campbell County, WY': '005',
           'Carbon County, WY': '007',
@@ -6494,7 +6494,7 @@ FIPS_COUNTIES = {
           'Uinta County, WY': '041',
           'Washakie County, WY': '043',
           'Weston County, WY': '045'},
-  '72': { 'Adjuntas Municipio, PR': '001',
+  '72':  {'Adjuntas Municipio, PR': '001',
           'Aguada Municipio, PR': '003',
           'Aguadilla Municipio, PR': '005',
           'Aguas Buenas Municipio, PR': '007',
@@ -6572,19 +6572,19 @@ FIPS_COUNTIES = {
           'Villalba Municipio, PR': '149',
           'Yabucoa Municipio, PR': '151',
           'Yauco Municipio, PR': '153'},
-  "CA01": { '--All--': '%', },
-  "CA02": { '--All--': '%', },
-  "CA03": { '--All--': '%', },
-  "CA04": { '--All--': '%', },
-  "CA05": { '--All--': '%', },
-  "CA13": { '--All--': '%', },
-  "CA07": { '--All--': '%', },
-  "CA14": { '--All--': '%', },
-  "CA08": { '--All--': '%', },
-  "CA09": { '--All--': '%', },
-  "CA10": { '--All--': '%', },
-  "CA11": { '--All--': '%', },
-  "CA12": { '--All--': '%', },
+  "CA01": {'--All--': '%', },
+  "CA02": {'--All--': '%', },
+  "CA03": {'--All--': '%', },
+  "CA04": {'--All--': '%', },
+  "CA05": {'--All--': '%', },
+  "CA13": {'--All--': '%', },
+  "CA07": {'--All--': '%', },
+  "CA14": {'--All--': '%', },
+  "CA08": {'--All--': '%', },
+  "CA09": {'--All--': '%', },
+  "CA10": {'--All--': '%', },
+  "CA11": {'--All--': '%', },
+  "CA12": {'--All--': '%', },
 }
 
 
@@ -6592,15 +6592,14 @@ if __name__ == "__main__":
     from sys import argv
     from pprint import PrettyPrinter
     pp = PrettyPrinter(indent=2)
-    
+
     import csv
-    fipsreader = csv.reader(open(argv[1],'rb'), delimiter=',', quotechar='"')
+    fipsreader = csv.reader(open(argv[1], 'rb'), delimiter=',', quotechar='"')
     for row in fipsreader:
         try:
             FIPS_COUNTIES[int(row[1])][row[3]] = row[2]
         except KeyError:
             FIPS_COUNTIES[int(row[1])] = {'--All--': '%'}
             FIPS_COUNTIES[int(row[1])][row[3]] = row[2]
-    
+
     pp.pprint(FIPS_COUNTIES)
-    
\ No newline at end of file
diff --git a/chirpui/importdialog.py b/chirp/ui/importdialog.py
similarity index 86%
rename from chirpui/importdialog.py
rename to chirp/ui/importdialog.py
index b7417ce..2bc9899 100644
--- a/chirpui/importdialog.py
+++ b/chirp/ui/importdialog.py
@@ -16,9 +16,14 @@
 import gtk
 import gobject
 import pango
+import logging
+
+from chirp import errors, chirp_common, import_logic
+from chirp.drivers import generic_xml
+from chirp.ui import common
+
+LOG = logging.getLogger(__name__)
 
-from chirp import errors, chirp_common, generic_xml, import_logic
-from chirpui import common
 
 class WaitWindow(gtk.Window):
     def __init__(self, msg, parent=None):
@@ -56,6 +61,7 @@ class WaitWindow(gtk.Window):
 
         self.prog.set_fraction(fraction)
 
+
 class ImportMemoryBankJob(common.RadioJob):
     def __init__(self, cb, dst_mem, src_radio, src_mem):
         common.RadioJob.__init__(self, cb, None)
@@ -69,6 +75,7 @@ class ImportMemoryBankJob(common.RadioJob):
         if self.cb:
             gobject.idle_add(self.cb, *self.cb_args)
 
+
 class ImportDialog(gtk.Dialog):
 
     def _check_for_dupe(self, location):
@@ -89,8 +96,7 @@ class ImportDialog(gtk.Dialog):
             d.set_property("text",
                            _("Location {number} is already being imported. "
                              "Choose another value for 'New Location' "
-                             "before selection 'Import'").format(\
-                    number=nloc))
+                             "before selection 'Import'").format(number=nloc))
             d.run()
             d.destroy()
         else:
@@ -98,7 +104,7 @@ class ImportDialog(gtk.Dialog):
 
     def _render(self, _, rend, model, iter, colnum):
         newloc, imp = model.get(iter, self.col_nloc, self.col_import)
-        lo,hi = self.dst_radio.get_features().memory_bounds
+        lo, hi = self.dst_radio.get_features().memory_bounds
 
         rend.set_property("text", "%i" % newloc)
         if newloc in self.used_list and imp:
@@ -113,7 +119,7 @@ class ImportDialog(gtk.Dialog):
 
     def _edited(self, rend, path, new, col):
         iter = self.__store.get_iter(path)
-        
+
         if col == self.col_nloc:
             nloc, = self.__store.get(iter, self.col_nloc)
 
@@ -149,12 +155,9 @@ class ImportDialog(gtk.Dialog):
         import_list = []
         iter = self.__store.get_iter_first()
         while iter:
-            old, new, name, comm, enb = self.__store.get(iter,
-                                             self.col_oloc,
-                                             self.col_nloc,
-                                             self.col_name,
-                                             self.col_comm,
-                                             self.col_import)
+            old, new, name, comm, enb = \
+                self.__store.get(iter, self.col_oloc, self.col_nloc,
+                                 self.col_name, self.col_comm, self.col_import)
             if enb:
                 import_list.append((old, new, name, comm))
             iter = self.__store.iter_next(iter)
@@ -175,18 +178,18 @@ class ImportDialog(gtk.Dialog):
             mem = self.src_radio.get_memory(old)
             if isinstance(mem, chirp_common.DVMemory):
                 if mem.dv_urcall not in ulist:
-                    print "Adding %s to ucall list" % mem.dv_urcall
+                    LOG.debug("Adding %s to ucall list" % mem.dv_urcall)
                     ulist.append(mem.dv_urcall)
                     ulist_changed = True
                 if mem.dv_rpt1call not in rlist:
-                    print "Adding %s to rcall list" % mem.dv_rpt1call
+                    LOG.debug("Adding %s to rcall list" % mem.dv_rpt1call)
                     rlist.append(mem.dv_rpt1call)
                     rlist_changed = True
                 if mem.dv_rpt2call not in rlist:
-                    print "Adding %s to rcall list" % mem.dv_rpt2call
+                    LOG.debug("Adding %s to rcall list" % mem.dv_rpt2call)
                     rlist.append(mem.dv_rpt2call)
                     rlist_changed = True
-                
+
         if ulist_changed:
             job = common.RadioJob(None, "set_urcall_list", ulist)
             job.set_desc(_("Updating URCALL list"))
@@ -196,7 +199,7 @@ class ImportDialog(gtk.Dialog):
             job = common.RadioJob(None, "set_repeater_call_list", ulist)
             job.set_desc(_("Updating RPTCALL list"))
             dst_rthread._qsubmit(job, 0)
-            
+
         return
 
     def _convert_power(self, dst_levels, src_levels, mem):
@@ -232,12 +235,13 @@ class ImportDialog(gtk.Dialog):
             if not dst_banks or not src_banks:
                 raise Exception()
         except Exception:
-            print "One or more of the radios doesn't support banks"
+            LOG.error("One or more of the radios doesn't support banks")
             return
 
         if not isinstance(self.dst_radio, generic_xml.XMLRadio) and \
                 len(dst_banks) != len(src_banks):
-            print "Source and destination radios have a different number of banks"
+            LOG.warn("Source and destination radios have "
+                     "a different number of banks")
         else:
             self.dst_radio.set_banks(src_banks)
 
@@ -250,7 +254,7 @@ class ImportDialog(gtk.Dialog):
 
         for old, new, name, comm in import_list:
             i += 1
-            print "%sing %i -> %i" % (self.ACTION, old, new)
+            LOG.debug("%sing %i -> %i" % (self.ACTION, old, new))
 
             src = self.src_radio.get_memory(old)
 
@@ -258,16 +262,17 @@ class ImportDialog(gtk.Dialog):
                 mem = import_logic.import_mem(self.dst_radio,
                                               src_features,
                                               src,
-                                              {"number" : new,
-                                               "name"   : name,
+                                              {"number":  new,
+                                               "name":    name,
                                                "comment": comm})
             except import_logic.ImportError, e:
-                print e
+                LOG.error(e)
                 error_messages[new] = str(e)
                 continue
 
             job = common.RadioJob(None, "set_memory", mem)
-            job.set_desc(_("Setting memory {number}").format(number=mem.number))
+            desc = _("Setting memory {number}").format(number=mem.number)
+            job.set_desc(desc)
             dst_rthread._qsubmit(job, 0)
 
             job = ImportMemoryBankJob(None, mem, self.src_radio, src)
@@ -285,12 +290,12 @@ class ImportDialog(gtk.Dialog):
     def make_view(self):
         editable = [self.col_nloc, self.col_name, self.col_comm]
 
-        self.__store = gtk.ListStore(gobject.TYPE_BOOLEAN, # Import
-                                     gobject.TYPE_INT,     # Source loc
-                                     gobject.TYPE_INT,     # Destination loc
-                                     gobject.TYPE_STRING,  # Name
-                                     gobject.TYPE_STRING,  # Frequency
-                                     gobject.TYPE_STRING,  # Comment
+        self.__store = gtk.ListStore(gobject.TYPE_BOOLEAN,  # Import
+                                     gobject.TYPE_INT,      # Source loc
+                                     gobject.TYPE_INT,      # Destination loc
+                                     gobject.TYPE_STRING,   # Name
+                                     gobject.TYPE_STRING,   # Frequency
+                                     gobject.TYPE_STRING,   # Comment
                                      gobject.TYPE_BOOLEAN,
                                      gobject.TYPE_STRING)
         self.__view = gtk.TreeView(self.__store)
@@ -321,7 +326,7 @@ class ImportDialog(gtk.Dialog):
                 column.set_cell_data_func(rend, self._render, k)
 
             if k in self.tips.keys():
-                print "Doing %s" % k
+                LOG.debug("Doing %s" % k)
                 lab = gtk.Label(self.caps[k])
                 column.set_widget(lab)
                 tips.set_tip(lab, self.tips[k])
@@ -330,7 +335,7 @@ class ImportDialog(gtk.Dialog):
             self.__view.append_column(column)
 
         self.__view.set_tooltip_column(self.col_tmsg)
-        
+
         sw = gtk.ScrolledWindow()
         sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
         sw.add(self.__view)
@@ -387,13 +392,13 @@ class ImportDialog(gtk.Dialog):
     def make_select(self):
         hbox = gtk.HBox(True, 2)
 
-        all = gtk.Button(_("All"));
+        all = gtk.Button(_("All"))
         all.connect("clicked", self.__select_all, True)
         all.set_size_request(50, 25)
         all.show()
         hbox.pack_start(all, 0, 0, 0)
 
-        none = gtk.Button(_("None"));
+        none = gtk.Button(_("None"))
         none.connect("clicked", self.__select_all, False)
         none.set_size_request(50, 25)
         none.show()
@@ -488,10 +493,10 @@ class ImportDialog(gtk.Dialog):
 
     def make_controls(self):
         hbox = gtk.HBox(False, 2)
-        
+
         hbox.pack_start(self.make_select(), 0, 0, 0)
         hbox.pack_start(self.make_adjust(), 0, 0, 0)
-        #hbox.pack_start(self.make_options(), 0, 0, 0)
+        # hbox.pack_start(self.make_options(), 0, 0, 0)
         hbox.show()
 
         return hbox
@@ -511,10 +516,11 @@ class ImportDialog(gtk.Dialog):
             if mem and not mem.empty and number not in self.used_list:
                 self.used_list.append(number)
         except errors.InvalidMemoryLocation:
-            print "Location %i empty or at limit of destination radio" % number
+            LOG.error("Location %i empty or at limit of destination radio" %
+                      number)
         except errors.InvalidDataError, e:
-            print "Got error from radio, assuming %i beyond limits: %s" % \
-                (number, e)
+            LOG.error("Got error from radio, assuming %i beyond limits: %s" %
+                      (number, e))
 
     def populate_list(self):
         start, end = self.src_radio.get_features().memory_bounds
@@ -548,7 +554,8 @@ class ImportDialog(gtk.Dialog):
                                                 mem))
             except import_logic.DestNotCompatible:
                 msgs = self.dst_radio.validate_memory(mem)
-            errs = [x for x in msgs if isinstance(x, chirp_common.ValidationError)]
+            errs = [x for x in msgs
+                    if isinstance(x, chirp_common.ValidationError)]
             if errs:
                 msg = _("Cannot be imported because") + ":\r\n"
                 msg += ",".join(errs)
@@ -567,7 +574,6 @@ class ImportDialog(gtk.Dialog):
                                      ))
             self.record_use_of(mem.number)
 
-
     TITLE = _("Import From File")
     ACTION = _("Import")
 
@@ -588,28 +594,28 @@ class ImportDialog(gtk.Dialog):
         self.col_tmsg = 7
 
         self.caps = {
-            self.col_import : self.ACTION,
-            self.col_nloc   : _("To"),
-            self.col_oloc   : _("From"),
-            self.col_name   : _("Name"),
-            self.col_freq   : _("Frequency"),
-            self.col_comm   : _("Comment"),
+            self.col_import:  self.ACTION,
+            self.col_nloc:    _("To"),
+            self.col_oloc:    _("From"),
+            self.col_name:    _("Name"),
+            self.col_freq:    _("Frequency"),
+            self.col_comm:    _("Comment"),
             }
 
         self.tips = {
-            self.col_nloc : _("Location memory will be imported into"),
-            self.col_oloc : _("Location of memory in the file being imported"),
+            self.col_nloc:  _("Location memory will be imported into"),
+            self.col_oloc:  _("Location of memory in the file being imported"),
             }
 
         self.types = {
-            self.col_import : gobject.TYPE_BOOLEAN,
-            self.col_oloc   : gobject.TYPE_INT,
-            self.col_nloc   : gobject.TYPE_INT,
-            self.col_name   : gobject.TYPE_STRING,
-            self.col_freq   : gobject.TYPE_STRING,
-            self.col_comm   : gobject.TYPE_STRING,
-            self.col_okay   : gobject.TYPE_BOOLEAN,
-            self.col_tmsg   : gobject.TYPE_STRING,
+            self.col_import:  gobject.TYPE_BOOLEAN,
+            self.col_oloc:    gobject.TYPE_INT,
+            self.col_nloc:    gobject.TYPE_INT,
+            self.col_name:    gobject.TYPE_STRING,
+            self.col_freq:    gobject.TYPE_STRING,
+            self.col_comm:    gobject.TYPE_STRING,
+            self.col_okay:    gobject.TYPE_BOOLEAN,
+            self.col_tmsg:    gobject.TYPE_STRING,
             }
 
         self.src_radio = src_radio
@@ -629,12 +635,13 @@ class ImportDialog(gtk.Dialog):
 
         self.ww.hide()
 
+
 class ExportDialog(ImportDialog):
     TITLE = _("Export To File")
     ACTION = _("Export")
 
 if __name__ == "__main__":
-    from chirpui import editorset
+    from chirp.ui import editorset
     import sys
 
     f = sys.argv[1]
diff --git a/chirpui/inputdialog.py b/chirp/ui/inputdialog.py
similarity index 93%
rename from chirpui/inputdialog.py
rename to chirp/ui/inputdialog.py
index a5c2def..96531d0 100644
--- a/chirpui/inputdialog.py
+++ b/chirp/ui/inputdialog.py
@@ -14,9 +14,13 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import gtk
+import logging
 
 from miscwidgets import make_choice
-from chirpui import reporting
+from chirp.ui import reporting
+
+LOG = logging.getLogger(__name__)
+
 
 class TextInputDialog(gtk.Dialog):
     def respond_ok(self, _):
@@ -31,7 +35,7 @@ class TextInputDialog(gtk.Dialog):
         self.label.set_size_request(300, 100)
         # pylint: disable-msg=E1101
         self.vbox.pack_start(self.label, 1, 1, 0)
-       
+
         self.text = gtk.Entry()
         self.text.connect("activate", self.respond_ok, None)
         # pylint: disable-msg=E1101
@@ -40,6 +44,7 @@ class TextInputDialog(gtk.Dialog):
         self.label.show()
         self.text.show()
 
+
 class ChoiceDialog(gtk.Dialog):
     editable = False
 
@@ -66,6 +71,7 @@ class ChoiceDialog(gtk.Dialog):
 
         self.set_default_response(gtk.RESPONSE_OK)
 
+
 class EditableChoiceDialog(ChoiceDialog):
     editable = True
 
@@ -74,6 +80,7 @@ class EditableChoiceDialog(ChoiceDialog):
 
         self.choice.child.set_activates_default(True)
 
+
 class ExceptionDialog(gtk.MessageDialog):
     def __init__(self, exception, **args):
         gtk.MessageDialog.__init__(self, buttons=gtk.BUTTONS_OK,
@@ -84,9 +91,10 @@ class ExceptionDialog(gtk.MessageDialog):
         import traceback
         import sys
         reporting.report_exception(traceback.format_exc(limit=30))
-        print "--- Exception Dialog: %s ---" % exception
-        traceback.print_exc(limit=100, file=sys.stdout)
-        print "----------------------------"
+        LOG.error("--- Exception Dialog: %s ---" % exception)
+        LOG.error(traceback.format_exc(limit=100))
+        LOG.error("----------------------------")
+
 
 class FieldDialog(gtk.Dialog):
     def __init__(self, **kwargs):
@@ -100,7 +108,7 @@ class FieldDialog(gtk.Dialog):
         gtk.Dialog.__init__(self, **kwargs)
 
     def response(self, _):
-        print "Blocking response"
+        LOG.debug("Blocking response")
         return
 
     def add_field(self, label, widget, validator=None):
@@ -118,12 +126,13 @@ class FieldDialog(gtk.Dialog):
 
         # pylint: disable-msg=E1101
         self.vbox.pack_start(box, 0, 0, 0)
-    
+
         self.__fields[label] = widget
 
     def get_field(self, label):
         return self.__fields.get(label, None)
 
+
 class OverwriteDialog(gtk.MessageDialog):
     def __init__(self, filename):
         gtk.Dialog.__init__(self,
diff --git a/chirpui/mainapp.py b/chirp/ui/mainapp.py
similarity index 83%
rename from chirpui/mainapp.py
rename to chirp/ui/mainapp.py
index 719d096..faff97c 100644
--- a/chirpui/mainapp.py
+++ b/chirp/ui/mainapp.py
@@ -17,46 +17,54 @@
 import os
 import tempfile
 import urllib
+import webbrowser
 from glob import glob
 import shutil
 import time
-
+import logging
 import gtk
 import gobject
+
+from chirp.ui import inputdialog, common
+from chirp import platform, directory, util
+from chirp.drivers import generic_xml, generic_csv
+from chirp.drivers import ic9x, kenwood_live, idrp, vx7, vx5, vx6
+from chirp.drivers import icf, ic9x_icf
+from chirp import CHIRP_VERSION, chirp_common, detect, errors
+from chirp.ui import editorset, clone, miscwidgets, config, reporting, fips
+from chirp.ui import bandplans
+
 gobject.threads_init()
 
+LOG = logging.getLogger(__name__)
+
 if __name__ == "__main__":
     import sys
     sys.path.insert(0, "..")
 
-from chirpui import inputdialog, common
 try:
     import serial
-except ImportError,e:
+except ImportError, e:
     common.log_exception()
     common.show_error("\nThe Pyserial module is not installed!")
-from chirp import platform, generic_xml, generic_csv, directory, util
-from chirp import ic9x, kenwood_live, idrp, vx7, vx5, vx6
-from chirp import CHIRP_VERSION, chirp_common, detect, errors
-from chirp import icf, ic9x_icf
-from chirpui import editorset, clone, miscwidgets, config, reporting, fips
-from chirpui import bandplans
+
 
 CONF = config.get()
 
 KEEP_RECENT = 8
 
 RB_BANDS = {
-    "--All--"                 : 0,
-    "10 meters (29MHz)"       : 29,
-    "6 meters (54MHz)"        : 5,
-    "2 meters (144MHz)"       : 14,
-    "1.25 meters (220MHz)"    : 22,
-    "70 centimeters (440MHz)" : 4,
-    "33 centimeters (900MHz)" : 9,
-    "23 centimeters (1.2GHz)" : 12,
+    "--All--":                  0,
+    "10 meters (29MHz)":        29,
+    "6 meters (54MHz)":         5,
+    "2 meters (144MHz)":        14,
+    "1.25 meters (220MHz)":     22,
+    "70 centimeters (440MHz)":  4,
+    "33 centimeters (900MHz)":  9,
+    "23 centimeters (1.2GHz)":  12,
 }
 
+
 def key_bands(band):
     if band.startswith("-"):
         return -1
@@ -66,9 +74,11 @@ def key_bands(band):
 
     return 100000 - (float(amount) * scale)
 
+
 class ModifiedError(Exception):
     pass
 
+
 class ChirpMain(gtk.Window):
     def get_current_editorset(self):
         page = self.tabs.get_current_page()
@@ -108,10 +118,11 @@ class ChirpMain(gtk.Window):
 
         for i in ["cancelq"]:
             set_action_sensitive(i, eset is not None and not save_sens)
-        
+
         for i in ["export", "close", "columns", "irbook", "irfinder",
                   "move_up", "move_dn", "exchange", "iradioreference",
-                  "cut", "copy", "paste", "delete", "viewdeveloper"]:
+                  "cut", "copy", "paste", "delete", "viewdeveloper",
+                  "all", "properties"]:
             set_action_sensitive(i, eset is not None)
 
     def ev_status(self, editorset, msg):
@@ -124,7 +135,7 @@ class ChirpMain(gtk.Window):
 
     def ev_editor_selected(self, editorset, editortype):
         mappings = {
-            "memedit" : ["view", "edit"],
+            "memedit": ["view", "edit"],
             }
 
         for _editortype, actions in mappings.items():
@@ -152,6 +163,14 @@ class ChirpMain(gtk.Window):
                                 gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
                        parent=self)
 
+        label = gtk.Label("")
+        label.set_markup("<b>-1</b> for either Mem # does a full-file hex " +
+                         "dump with diffs highlighted.\n" +
+                         "<b>-2</b> for first Mem # shows " +
+                         "<b>only</b> the diffs.")
+        d.vbox.pack_start(label, True, True, 0)
+        label.show()
+
         choices = []
         for eset in esets:
             choices.append("%s %s (%s)" % (eset.rthread.radio.VENDOR,
@@ -160,7 +179,7 @@ class ChirpMain(gtk.Window):
         choice_a = miscwidgets.make_choice(choices, False, choices[0])
         choice_a.show()
         chan_a = gtk.SpinButton()
-        chan_a.get_adjustment().set_all(1, -1, 999, 1, 10, 0)
+        chan_a.get_adjustment().set_all(1, -2, 999, 1, 10, 0)
         chan_a.show()
         hbox = gtk.HBox(False, 3)
         hbox.pack_start(choice_a, 1, 1, 1)
@@ -192,8 +211,14 @@ class ChirpMain(gtk.Window):
             common.show_error("Can't diff the same tab!")
             return
 
-        print "Selected %s@%i and %s@%i" % (sel_a, sel_chan_a,
-                                            sel_b, sel_chan_b)
+        LOG.debug("Selected %s@%i and %s@%i" %
+                  (sel_a, sel_chan_a, sel_b, sel_chan_b))
+        name_a = os.path.basename(sel_a)
+        name_a = name_a[:name_a.rindex(")")]
+        name_b = os.path.basename(sel_b)
+        name_b = name_b[:name_b.rindex(")")]
+        diffwintitle = "%s@%i  diff  %s@%i" % (
+            name_a, sel_chan_a, name_b, sel_chan_b)
 
         eset_a = esets[choices.index(sel_a)]
         eset_b = esets[choices.index(sel_b)]
@@ -201,25 +226,39 @@ class ChirpMain(gtk.Window):
         def _show_diff(mem_b, mem_a):
             # Step 3: Show the diff
             diff = common.simple_diff(mem_a, mem_b)
-            common.show_diff_blob("Differences", diff)
+            common.show_diff_blob(diffwintitle, diff)
 
         def _get_mem_b(mem_a):
             # Step 2: Get memory b
-            job = common.RadioJob(_show_diff, "get_raw_memory", int(sel_chan_b))
+            job = common.RadioJob(_show_diff, "get_raw_memory",
+                                  int(sel_chan_b))
             job.set_cb_args(mem_a)
             eset_b.rthread.submit(job)
-            
+
         if sel_chan_a >= 0 and sel_chan_b >= 0:
             # Diff numbered memory
             # Step 1: Get memory a
-            job = common.RadioJob(_get_mem_b, "get_raw_memory", int(sel_chan_a))
+            job = common.RadioJob(_get_mem_b, "get_raw_memory",
+                                  int(sel_chan_a))
             eset_a.rthread.submit(job)
         elif isinstance(eset_a.rthread.radio, chirp_common.CloneModeRadio) and\
                 isinstance(eset_b.rthread.radio, chirp_common.CloneModeRadio):
             # Diff whole (can do this without a job, since both are clone-mode)
-            a = util.hexprint(eset_a.rthread.radio._mmap.get_packed())
-            b = util.hexprint(eset_b.rthread.radio._mmap.get_packed())
-            common.show_diff_blob("Differences", common.simple_diff(a, b))
+            try:
+                addrfmt = CONF.get('hexdump_addrfmt', section='developer',
+                                   raw=True)
+            except:
+                pass
+            a = util.hexprint(eset_a.rthread.radio._mmap.get_packed(),
+                              addrfmt=addrfmt)
+            b = util.hexprint(eset_b.rthread.radio._mmap.get_packed(),
+                              addrfmt=addrfmt)
+            if sel_chan_a == -2:
+                diffsonly = True
+            else:
+                diffsonly = False
+            common.show_diff_blob(diffwintitle,
+                                  common.simple_diff(a, b, diffsonly))
         else:
             common.show_error("Cannot diff whole live-mode radios!")
 
@@ -241,7 +280,11 @@ class ChirpMain(gtk.Window):
 
         lab = gtk.Label("""<b><big>Unable to detect model!</big></b>
 
-If you think that it is valid, you can select a radio model below to force an open attempt. If selecting the model manually works, please file a bug on the website and attach your image. If selecting the model does not work, it is likely that you are trying to open some other type of file.
+If you think that it is valid, you can select a radio model below to
+force an open attempt. If selecting the model manually works, please
+file a bug on the website and attach your image. If selecting the model
+does not work, it is likely that you are trying to open some other type
+of file.
 """)
 
         lab.set_justify(gtk.JUSTIFY_FILL)
@@ -258,7 +301,7 @@ If you think that it is valid, you can select a radio model below to force an op
         d.vbox.set_spacing(5)
         choice.show()
         d.set_default_size(400, 200)
-        #d.set_resizable(False)
+        # d.set_resizable(False)
         r = d.run()
         d.destroy()
         if r != gtk.RESPONSE_OK:
@@ -274,6 +317,7 @@ If you think that it is valid, you can select a radio model below to force an op
             types = [(_("CHIRP Radio Images") + " (*.img)", "*.img"),
                      (_("CHIRP Files") + " (*.chirp)", "*.chirp"),
                      (_("CSV Files") + " (*.csv)", "*.csv"),
+                     (_("DAT Files") + " (*.dat)", "*.dat"),
                      (_("EVE Files (VX5)") + " (*.eve)", "*.eve"),
                      (_("ICF Files") + " (*.icf)", "*.icf"),
                      (_("VX5 Commander Files") + " (*.vx5)", "*.vx5"),
@@ -287,7 +331,7 @@ If you think that it is valid, you can select a radio model below to force an op
         self.record_recent_file(fname)
 
         if icf.is_icf_file(fname):
-            a = common.ask_yesno_question(\
+            a = common.ask_yesno_question(
                 _("ICF files cannot be edited, only displayed or imported "
                   "into another file. Open in read-only mode?"),
                 self)
@@ -308,7 +352,7 @@ If you think that it is valid, you can select a radio model below to force an op
                 radio = self._do_manual_select(fname)
                 if not radio:
                     return
-                print "Manually selected %s" % radio
+                LOG.debug("Manually selected %s" % radio)
             except Exception, e:
                 common.log_exception()
                 common.show_error(os.path.basename(fname) + ": " + str(e))
@@ -347,8 +391,8 @@ If you think that it is valid, you can select a radio model below to force an op
                 "to the radio. Because of this, you cannot perform the "
                 "<u>Save</u> or <u>Upload</u> operations. If you wish to "
                 "edit the contents offline, please <u>Export</u> to a CSV "
-                "file, using the <b>File menu</b>.").format(vendor=radio.VENDOR,
-                                                            model=radio.MODEL)
+                "file, using the <b>File menu</b>.")
+        msg = msg.format(vendor=radio.VENDOR, model=radio.MODEL)
         d.format_secondary_markup(msg)
 
         again = gtk.CheckButton(_("Don't show this again"))
@@ -385,10 +429,10 @@ If you think that it is valid, you can select a radio model below to force an op
     def do_saveas(self):
         eset = self.get_current_editorset()
 
-        label = _("{vendor} {model} image file").format(\
+        label = _("{vendor} {model} image file").format(
             vendor=eset.radio.VENDOR,
             model=eset.radio.MODEL)
-                                                     
+
         types = [(label + " (*.%s)" % eset.radio.FILE_EXTENSION,
                  eset.radio.FILE_EXTENSION)]
 
@@ -416,7 +460,7 @@ If you think that it is valid, you can select a radio model below to force an op
 
         try:
             eset.save(fname)
-        except Exception,e:
+        except Exception, e:
             d = inputdialog.ExceptionDialog(e)
             d.run()
             d.destroy()
@@ -431,7 +475,7 @@ If you think that it is valid, you can select a radio model below to force an op
             d.run()
             d.destroy()
 
-    def cb_cloneout(self, radio, emsg= None):
+    def cb_cloneout(self, radio, emsg=None):
         radio.pipe.close()
         reporting.report_model_usage(radio, "upload", True)
         if emsg:
@@ -446,7 +490,7 @@ If you think that it is valid, you can select a radio model below to force an op
             if fn:
                 recent.append(fn)
         return recent
-                    
+
     def _set_recent_list(self, recent):
         for fn in recent:
             CONF.set("recent%i" % recent.index(fn), fn, "state")
@@ -462,13 +506,12 @@ If you think that it is valid, you can select a radio model below to force an op
                 self.menu_ag.remove_action(old_action)
 
             file_basename = os.path.basename(fname).replace("_", "__")
-            action = gtk.Action(action_name,
-                                "_%i. %s" % (i+1, file_basename),
-                                _("Open recent file {name}").format(name=fname),
-                                "")
-            action.connect("activate", lambda a,f: self.do_open(f), fname)
+            action = gtk.Action(
+                action_name, "_%i. %s" % (i+1, file_basename),
+                _("Open recent file {name}").format(name=fname), "")
+            action.connect("activate", lambda a, f: self.do_open(f), fname)
             mid = self.menu_uim.new_merge_id()
-            self.menu_uim.add_ui(mid, path, 
+            self.menu_uim.add_ui(mid, path,
                                  action_name, action_name,
                                  gtk.UI_MANAGER_MENUITEM, False)
             self.menu_ag.add_action(action)
@@ -498,13 +541,13 @@ If you think that it is valid, you can select a radio model below to force an op
         files = glob(os.path.join(basepath, "*.csv"))
         for fn in files:
             if os.path.exists(os.path.join(stock_dir, os.path.basename(fn))):
-                print "Skipping existing stock config"
+                LOG.info("Skipping existing stock config")
                 continue
             try:
                 shutil.copy(fn, stock_dir)
-                print "Copying %s -> %s" % (fn, stock_dir)
+                LOG.debug("Copying %s -> %s" % (fn, stock_dir))
             except Exception, e:
-                print "ERROR: Unable to copy %s to %s: %s" % (fn, stock_dir, e)
+                LOG.error("Unable to copy %s to %s: %s" % (fn, stock_dir, e))
                 return False
         return True
 
@@ -514,7 +557,7 @@ If you think that it is valid, you can select a radio model below to force an op
             try:
                 os.mkdir(stock_dir)
             except Exception, e:
-                print "ERROR: Unable to create directory: %s" % stock_dir
+                LOG.error("Unable to create directory: %s" % stock_dir)
                 return
         if not self.copy_shipped_stock_configs(stock_dir):
             return
@@ -544,13 +587,12 @@ If you think that it is valid, you can select a radio model below to force an op
                                 _("Open stock "
                                   "configuration {name}").format(name=name),
                                 "")
-            action.connect("activate", lambda a,c: self.do_open(c), config)
+            action.connect("activate", lambda a, c: self.do_open(c), config)
             mid = self.menu_uim.new_merge_id()
             mid = self.menu_uim.add_ui(mid, path,
                                        action_name, action_name,
                                        gtk.UI_MANAGER_MENUITEM, False)
             self.menu_ag.add_action(action)
-            
 
         configs = glob(os.path.join(stock_dir, "*.csv"))
         for config in configs:
@@ -588,7 +630,8 @@ If you think that it is valid, you can select a radio model below to force an op
         msg = _("{instructions}").format(instructions=message)
         d.format_secondary_markup(msg)
 
-        again = gtk.CheckButton(_("Don't show instructions for any radio again"))
+        again = gtk.CheckButton(
+            _("Don't show instructions for any radio again"))
         again.show()
         d.vbox.pack_start(again, 0, 0, 0)
         h_button_box = d.vbox.get_children()[2]
@@ -618,9 +661,8 @@ If you think that it is valid, you can select a radio model below to force an op
 
         self._show_instructions(rclass, rclass.get_prompts().pre_download)
 
-        print "User selected %s %s on port %s" % (rclass.VENDOR,
-                                                  rclass.MODEL,
-                                                  settings.port)
+        LOG.debug("User selected %s %s on port %s" %
+                  (rclass.VENDOR, rclass.MODEL, settings.port))
 
         try:
             ser = serial.Serial(port=settings.port,
@@ -638,7 +680,8 @@ If you think that it is valid, you can select a radio model below to force an op
 
         fn = tempfile.mktemp()
         if isinstance(radio, chirp_common.CloneModeRadio):
-            ct = clone.CloneThread(radio, "in", cb=self.cb_clonein, parent=self)
+            ct = clone.CloneThread(radio, "in", cb=self.cb_clonein,
+                                   parent=self)
             ct.start()
         else:
             self.do_open_live(radio)
@@ -657,8 +700,8 @@ If you think that it is valid, you can select a radio model below to force an op
             return
         prompts = radio.get_prompts()
 
-        if prompts.display_pre_upload_prompt_before_opening_port == True:
-            print "Opening port after pre_upload prompt."
+        if prompts.display_pre_upload_prompt_before_opening_port is True:
+            LOG.debug("Opening port after pre_upload prompt.")
             self._show_instructions(radio, prompts.pre_upload)
 
         if isinstance(radio, chirp_common.ExperimentalRadio) and \
@@ -678,8 +721,8 @@ If you think that it is valid, you can select a radio model below to force an op
             d.destroy()
             return
 
-        if prompts.display_pre_upload_prompt_before_opening_port == False:
-            print "Opening port before pre_upload prompt."
+        if prompts.display_pre_upload_prompt_before_opening_port is False:
+            LOG.debug("Opening port before pre_upload prompt.")
             self._show_instructions(radio, prompts.pre_upload)
 
         radio.set_pipe(ser)
@@ -697,22 +740,23 @@ If you think that it is valid, you can select a radio model below to force an op
             return False
 
         if eset.is_modified():
-            dlg = miscwidgets.YesNoDialog(title=_("Save Changes?"),
-                                          parent=self,
-                                          buttons=(gtk.STOCK_YES, gtk.RESPONSE_YES,
-                                                   gtk.STOCK_NO, gtk.RESPONSE_NO,
-                                                   gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
+            dlg = miscwidgets.YesNoDialog(
+                title=_("Save Changes?"), parent=self,
+                buttons=(gtk.STOCK_YES, gtk.RESPONSE_YES,
+                         gtk.STOCK_NO, gtk.RESPONSE_NO,
+                         gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
             dlg.set_text(_("File is modified, save changes before closing?"))
             res = dlg.run()
             dlg.destroy()
+
             if res == gtk.RESPONSE_YES:
                 self.do_save(eset)
-            elif res == gtk.RESPONSE_CANCEL:
+            elif res != gtk.RESPONSE_NO:
                 raise ModifiedError()
 
         eset.rthread.stop()
         eset.rthread.join()
-    
+
         eset.prepare_close()
 
         if eset.radio.pipe:
@@ -733,6 +777,7 @@ If you think that it is valid, you can select a radio model below to force an op
         types = [(_("CHIRP Files") + " (*.chirp)", "*.chirp"),
                  (_("CHIRP Radio Images") + " (*.img)", "*.img"),
                  (_("CSV Files") + " (*.csv)", "*.csv"),
+                 (_("DAT Files") + " (*.dat)", "*.dat"),
                  (_("EVE Files (VX5)") + " (*.eve)", "*.eve"),
                  (_("ICF Files") + " (*.icf)", "*.icf"),
                  (_("Kenwood HMK Files") + " (*.hmk)", "*.hmk"),
@@ -752,11 +797,11 @@ If you think that it is valid, you can select a radio model below to force an op
     def do_repeaterbook_prompt(self):
         if not CONF.get_bool("has_seen_credit", "repeaterbook"):
             d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK)
-            d.set_markup("<big><big><b>RepeaterBook</b></big>\r\n" + \
-                             "<i>North American Repeater Directory</i></big>")
-            d.format_secondary_markup("For more information about this " +\
-                                          "free service, please go to\r\n" +\
-                                          "http://www.repeaterbook.com")
+            d.set_markup("<big><big><b>RepeaterBook</b></big>\r\n" +
+                         "<i>North American Repeater Directory</i></big>")
+            d.format_secondary_markup("For more information about this " +
+                                      "free service, please go to\r\n" +
+                                      "http://www.repeaterbook.com")
             d.run()
             d.destroy()
             CONF.set_bool("has_seen_credit", True, "repeaterbook")
@@ -769,19 +814,20 @@ If you think that it is valid, you can select a radio model below to force an op
                 code = int(CONF.get("state", "repeaterbook"))
             except:
                 code = CONF.get("state", "repeaterbook")
-            for k,v in fips.FIPS_STATES.items():
+            for k, v in fips.FIPS_STATES.items():
                 if code == v:
                     default_state = k
                     break
 
             code = CONF.get("county", "repeaterbook")
-            for k,v in fips.FIPS_COUNTIES[fips.FIPS_STATES[default_state]].items():
+            items = fips.FIPS_COUNTIES[fips.FIPS_STATES[default_state]].items()
+            for k, v in items:
                 if code == v:
                     default_county = k
                     break
 
             code = int(CONF.get("band", "repeaterbook"))
-            for k,v in RB_BANDS.items():
+            for k, v in RB_BANDS.items():
                 if code == v:
                     default_band = k
                     break
@@ -790,18 +836,21 @@ If you think that it is valid, you can select a radio model below to force an op
 
         state = miscwidgets.make_choice(sorted(fips.FIPS_STATES.keys()),
                                         False, default_state)
-        county = miscwidgets.make_choice(sorted(fips.FIPS_COUNTIES[fips.FIPS_STATES[default_state]].keys()),
-                                        False, default_county)
+        county = miscwidgets.make_choice(
+            sorted(fips.FIPS_COUNTIES[fips.FIPS_STATES[default_state]].keys()),
+            False, default_county)
         band = miscwidgets.make_choice(sorted(RB_BANDS.keys(), key=key_bands),
                                        False, default_band)
+
         def _changed(box, county):
             state = fips.FIPS_STATES[box.get_active_text()]
             county.get_model().clear()
             for fips_county in sorted(fips.FIPS_COUNTIES[state].keys()):
                 county.append_text(fips_county)
             county.set_active(0)
+
         state.connect("changed", _changed, county)
-        
+
         d = inputdialog.FieldDialog(title=_("RepeaterBook Query"), parent=self)
         d.add_field("State", state)
         d.add_field("County", county)
@@ -833,22 +882,24 @@ If you think that it is valid, you can select a radio model below to force an op
             try:
                 code = CONF.get("state", "repeaterbook")
             except:
-                code = '41' # Oregon default
+                code = '41'  # Oregon default
 
         try:
             county = CONF.get("county", "repeaterbook")
         except:
-            county = '%' # --All-- default
+            county = '%'  # --All-- default
 
         try:
             band = int(CONF.get("band", "repeaterbook"))
         except:
-            band = 14 # 2m default
+            band = 14  # 2m default
 
-        query = "http://www.repeaterbook.com/repeaters/downloads/chirp.php?" + \
-            "func=default&state_id=%s&band=%s&freq=%%&band6=%%&loc=%%" + \
+        query = "http://www.repeaterbook.com/repeaters/downloads/chirp.php" + \
+            "?func=default&state_id=%s&band=%s&freq=%%&band6=%%&loc=%%" + \
             "&county_id=%s&status_id=%%&features=%%&coverage=%%&use=%%"
-        query = query % (code, band and band or "%%", county and county or "%%")
+        query = query % (code,
+                         band and band or "%%",
+                         county and county or "%%")
 
         # Do this in case the import process is going to take a while
         # to make sure we process events leading up to this
@@ -859,8 +910,7 @@ If you think that it is valid, you can select a radio model below to force an op
         fn = tempfile.mktemp(".csv")
         filename, headers = urllib.urlretrieve(query, fn)
         if not os.path.exists(filename):
-            print "Failed, headers were:"
-            print str(headers)
+            LOG.error("Failed, headers were: %s", headers)
             common.show_error(_("RepeaterBook query failed"))
             self.window.set_cursor(None)
             return
@@ -899,8 +949,10 @@ If you think that it is valid, you can select a radio model below to force an op
                                     parent=self)
         fields = {
             "Country":
-                (miscwidgets.make_choice(['by', 'cz', 'de', 'lt', 'pl',
-                                          'sk', 'uk'], False),
+                (miscwidgets.make_choice(
+                    ['at', 'bg', 'by', 'ch', 'cz', 'de', 'dk', 'es', 'fi',
+                        'fr', 'hu', 'it', 'lt', 'lv', 'no', 'pl', 'ro', 'se',
+                        'sk', 'ua', 'uk'], False),
                  lambda x: str(x.get_active_text())),
             "Band":
                 (miscwidgets.make_choice(['10m', '4m', '6m', '2m', '70cm',
@@ -938,7 +990,7 @@ If you think that it is valid, you can select a radio model below to force an op
                     args.append("=".join((name.replace(" ", "").lower(),
                                           contents)))
             query += "&".join(args)
-            print query
+            LOG.debug(query)
             d.destroy()
             return query
 
@@ -953,8 +1005,7 @@ If you think that it is valid, you can select a radio model below to force an op
         fn = tempfile.mktemp(".csv")
         filename, headers = urllib.urlretrieve(url, fn)
         if not os.path.exists(filename):
-            print "Failed, headers were:"
-            print str(headers)
+            LOG.error("Failed, headers were: %s", str(headers))
             common.show_error(_("Query failed"))
             return
 
@@ -976,16 +1027,12 @@ If you think that it is valid, you can select a radio model below to force an op
             self.do_open_live(radio, read_only=True)
 
     def do_rfinder_prompt(self):
-        fields = {"1Email"    :      (gtk.Entry(),
-                                      lambda x: "@" in x),
-                  "2Password" :      (gtk.Entry(),
-                                      lambda x: x),
-                  "3Latitude" :      (gtk.Entry(),
-                                      lambda x: float(x) < 90 and \
-                                          float(x) > -90),
-                  "4Longitude":      (gtk.Entry(),
-                                      lambda x: float(x) < 180 and \
-                                          float(x) > -180),
+        fields = {"1Email": (gtk.Entry(), lambda x: "@" in x),
+                  "2Password": (gtk.Entry(), lambda x: x),
+                  "3Latitude": (gtk.Entry(),
+                                lambda x: float(x) < 90 and float(x) > -90),
+                  "4Longitude": (gtk.Entry(),
+                                 lambda x: float(x) < 180 and float(x) > -180),
                   "5Range_in_Miles": (gtk.Entry(),
                                       lambda x: int(x) > 0 and int(x) < 5000),
                   }
@@ -1037,9 +1084,11 @@ If you think that it is valid, you can select a radio model below to force an op
 
         if do_import:
             eset = self.get_current_editorset()
-            count = eset.do_import("rfinder://%s/%s/%f/%f/%i" % (email, passwd, lat, lon, miles))
+            rfstr = "rfinder://%s/%s/%f/%f/%i" % \
+                    (email, passwd, lat, lon, miles)
+            count = eset.do_import(rfstr)
         else:
-            from chirp import rfinder
+            from chirp.drivers import rfinder
             radio = rfinder.RFinderRadio(None)
             radio.set_params((lat, lon), miles, email, passwd)
             self.do_open_live(radio, read_only=True)
@@ -1047,9 +1096,9 @@ If you think that it is valid, you can select a radio model below to force an op
         self.window.set_cursor(None)
 
     def do_radioreference_prompt(self):
-        fields = {"1Username"    : (gtk.Entry(), lambda x: x),
-                  "2Password"    : (gtk.Entry(), lambda x: x),
-                  "3Zipcode"     : (gtk.Entry(), lambda x: x),
+        fields = {"1Username":  (gtk.Entry(), lambda x: x),
+                  "2Password":  (gtk.Entry(), lambda x: x),
+                  "3Zipcode":   (gtk.Entry(), lambda x: x),
                   }
 
         d = inputdialog.FieldDialog(title=_("RadioReference.com Query"),
@@ -1098,7 +1147,8 @@ If you think that it is valid, you can select a radio model below to force an op
 
         if do_import:
             eset = self.get_current_editorset()
-            count = eset.do_import("radioreference://%s/%s/%s" % (zipcode, username, passwd))
+            rrstr = "radioreference://%s/%s/%s" % (zipcode, username, passwd)
+            count = eset.do_import(rrstr)
         else:
             try:
                 from chirp import radioreference
@@ -1145,14 +1195,17 @@ If you think that it is valid, you can select a radio model below to force an op
         d = gtk.AboutDialog()
         d.set_transient_for(self)
         import sys
-        verinfo = "GTK %s\nPyGTK %s\nPython %s\n" % ( \
+        verinfo = "GTK %s\nPyGTK %s\nPython %s\n" % (
             ".".join([str(x) for x in gtk.gtk_version]),
             ".".join([str(x) for x in gtk.pygtk_version]),
             sys.version.split()[0])
 
+        # Set url hook to handle user activating a URL link in the about dialog
+        gtk.about_dialog_set_url_hook(lambda dlg, url: webbrowser.open(url))
+
         d.set_name("CHIRP")
         d.set_version(CHIRP_VERSION)
-        d.set_copyright("Copyright 2013 Dan Smith (KK7DS)")
+        d.set_copyright("Copyright 2015 Dan Smith (KK7DS)")
         d.set_website("http://chirp.danplanet.com")
         d.set_authors(("Dan Smith KK7DS <dsmith at danplanet.com>",
                        _("With significant contributions from:"),
@@ -1168,31 +1221,19 @@ If you think that it is valid, you can select a radio model below to force an op
                                  os.linesep +
                                  "German: Benjamin HB9EUK" +
                                  os.linesep +
-                                 "Hungarian: Attila HA7JA" +
+                                 "Hungarian: Attila HA5JA" +
                                  os.linesep +
                                  "Russian: Dmitry Slukin" +
                                  os.linesep +
                                  "Portuguese (BR): Crezivando PP7CJ")
         d.set_comments(verinfo)
-        
-        d.run()
-        d.destroy()
 
-    def do_documentation(self):
-        d = gtk.MessageDialog(buttons=gtk.BUTTONS_OK, parent=self,
-                              type=gtk.MESSAGE_INFO)
-
-        d.set_markup("<b><big>" + _("CHIRP Documentation") + "</big></b>\r\n")
-        msg = _("Documentation for CHIRP, including FAQs, and help for common "
-                "problems is available on the CHIRP web site, please go to\n\n"
-                "<a href=\"http://chirp.danplanet.com/projects/chirp/wiki/"
-                "Documentation\">"
-                "http://chirp.danplanet.com/projects/chirp/wiki/"
-                "Documentation</a>\n")
-        d.format_secondary_markup(msg.replace("\n","\r\n"))
         d.run()
         d.destroy()
 
+    def do_gethelp(self):
+        webbrowser.open("http://chirp.danplanet.com")
+
     def do_columns(self):
         eset = self.get_current_editorset()
         driver = directory.get_driver(eset.rthread.radio.__class__)
@@ -1214,12 +1255,13 @@ If you think that it is valid, you can select a radio model below to force an op
         d.set_size_request(-1, 300)
         d.set_resizable(False)
 
-        label = gtk.Label(_("Visible columns for {radio}").format(radio=radio_name))
+        labelstr = _("Visible columns for {radio}").format(radio=radio_name)
+        label = gtk.Label(labelstr)
         label.show()
         vbox.pack_start(label)
 
         fields = []
-        memedit = eset.get_current_editor() #.editors["memedit"]
+        memedit = eset.get_current_editor()  # .editors["memedit"]
         unsupported = memedit.get_unsupported_columns()
         for colspec in memedit.cols:
             if colspec[0].startswith("_"):
@@ -1242,7 +1284,7 @@ If you think that it is valid, you can select a radio model below to force an op
                 memedit.set_column_visible(colnum, widget.get_active())
                 if widget.get_active():
                     selected_columns.append(widget.get_label())
-                                                
+
         d.destroy()
 
         CONF.set(driver, ",".join(selected_columns), "memedit_columns")
@@ -1275,18 +1317,18 @@ If you think that it is valid, you can select a radio model below to force an op
 
     def do_toggle_report(self, action):
         if not action.get_active():
-            d = gtk.MessageDialog(buttons=gtk.BUTTONS_YES_NO,
-                                  parent=self)
-            d.set_markup("<b><big>" + _("Reporting is disabled") + "</big></b>")
+            d = gtk.MessageDialog(buttons=gtk.BUTTONS_YES_NO, parent=self)
+            markup = "<b><big>" + _("Reporting is disabled") + "</big></b>"
+            d.set_markup(markup)
             msg = _("The reporting feature of CHIRP is designed to help "
                     "<u>improve quality</u> by allowing the authors to focus "
                     "on the radio drivers used most often and errors "
                     "experienced by the users. The reports contain no "
-                    "identifying information and are used only for statistical "
-                    "purposes by the authors. Your privacy is extremely "
-                    "important, but <u>please consider leaving this feature "
-                    "enabled to help make CHIRP better!</u>\n\n<b>Are you "
-                    "sure you want to disable this feature?</b>")
+                    "identifying information and are used only for "
+                    "statistical purposes by the authors. Your privacy is "
+                    "extremely important, but <u>please consider leaving "
+                    "this feature enabled to help make CHIRP better!</u>\n\n"
+                    "<b>Are you sure you want to disable this feature?</b>")
             d.format_secondary_markup(msg.replace("\n", "\r\n"))
             r = d.run()
             d.destroy()
@@ -1309,7 +1351,7 @@ If you think that it is valid, you can select a radio model below to force an op
 
     def do_change_language(self):
         langs = ["Auto", "English", "Polish", "Italian", "Dutch", "German",
-                 "Hungarian", "Russian", "Portuguese (BR)"]
+                 "Hungarian", "Russian", "Portuguese (BR)", "French"]
         d = inputdialog.ChoiceDialog(langs, parent=self,
                                      title="Choose Language")
         d.label.set_text(_("Choose a language or Auto to use the "
@@ -1319,7 +1361,7 @@ If you think that it is valid, you can select a radio model below to force an op
         d.label.set_line_wrap(True)
         r = d.run()
         if r == gtk.RESPONSE_OK:
-            print "Chose language %s" % d.choice.get_active_text()
+            LOG.debug("Chose language %s" % d.choice.get_active_text())
             conf = config.get()
             conf.set("language", d.choice.get_active_text(), "state")
         d.destroy()
@@ -1380,8 +1422,8 @@ If you think that it is valid, you can select a radio model below to force an op
             self.do_przemienniki(action[0] == "i")
         elif action == "about":
             self.do_about()
-        elif action == "documentation":
-            self.do_documentation()
+        elif action == "gethelp":
+            self.do_gethelp()
         elif action == "columns":
             self.do_columns()
         elif action == "hide_unused":
@@ -1399,8 +1441,8 @@ If you think that it is valid, you can select a radio model below to force an op
         elif action == "developer":
             self.do_toggle_developer(_action)
         elif action in ["cut", "copy", "paste", "delete",
-                        "move_up", "move_dn", "exchange",
-                        "devshowraw", "devdiffraw"]:
+                        "move_up", "move_dn", "exchange", "all",
+                        "devshowraw", "devdiffraw", "properties"]:
             self.get_current_editorset().get_current_editor().hotkey(_action)
         elif action == "devdifftab":
             self.do_diff_radio()
@@ -1438,9 +1480,13 @@ If you think that it is valid, you can select a radio model below to force an op
       <menuitem action="paste"/>
       <menuitem action="delete"/>
       <separator/>
+      <menuitem action="all"/>
+      <separator/>
       <menuitem action="move_up"/>
       <menuitem action="move_dn"/>
       <menuitem action="exchange"/>
+      <separator/>
+      <menuitem action="properties"/>
     </menu>
     <menu action="view">
       <menuitem action="columns"/>
@@ -1475,15 +1521,17 @@ If you think that it is valid, you can select a radio model below to force an op
       <menuitem action="cancelq"/>
     </menu>
     <menu action="help">
-      <menuitem action="about"/>
-      <menuitem action="documentation"/>
+      <menuitem action="gethelp"/>
+      <separator/>
       <menuitem action="report"/>
       <menuitem action="developer"/>
+      <separator/>
+      <menuitem action="about"/>
     </menu>
   </menubar>
 </ui>
 """
-        actions = [\
+        actions = [
             ('file', None, _("_File"), None, None, self.mh),
             ('new', gtk.STOCK_NEW, None, None, None, self.mh),
             ('open', gtk.STOCK_OPEN, None, None, None, self.mh),
@@ -1499,53 +1547,71 @@ If you think that it is valid, you can select a radio model below to force an op
             ('copy', None, _("_Copy"), "<Ctrl>c", None, self.mh),
             ('paste', None, _("_Paste"), "<Ctrl>v", None, self.mh),
             ('delete', None, _("_Delete"), "Delete", None, self.mh),
-            ('move_up', None, _("Move _Up"), "<Control>Up", None, self.mh),
-            ('move_dn', None, _("Move Dow_n"), "<Control>Down", None, self.mh),
-            ('exchange', None, _("E_xchange"), "<Control><Shift>x", None, self.mh),
+            ('all', None, _("Select _All"), None, None, self.mh),
+            ('move_up', None, _("Move _Up"),
+             "<Control>Up", None, self.mh),
+            ('move_dn', None, _("Move Dow_n"),
+             "<Control>Down", None, self.mh),
+            ('exchange', None, _("E_xchange"),
+             "<Control><Shift>x", None, self.mh),
+            ('properties', None, _("P_roperties"), None, None, self.mh),
             ('view', None, _("_View"), None, None, self.mh),
             ('columns', None, _("Columns"), None, None, self.mh),
             ('viewdeveloper', None, _("Developer"), None, None, self.mh),
-            ('devshowraw', None, _('Show raw memory'), "<Control><Shift>r", None, self.mh),
-            ('devdiffraw', None, _("Diff raw memories"), "<Control><Shift>d", None, self.mh),
-            ('devdifftab', None, _("Diff tabs"), "<Control><Shift>t", None, self.mh),
+            ('devshowraw', None, _('Show raw memory'),
+             "<Control><Shift>r", None, self.mh),
+            ('devdiffraw', None, _("Diff raw memories"),
+             "<Control><Shift>d", None, self.mh),
+            ('devdifftab', None, _("Diff tabs"),
+             "<Control><Shift>t", None, self.mh),
             ('language', None, _("Change language"), None, None, self.mh),
             ('radio', None, _("_Radio"), None, None, self.mh),
-            ('download', None, _("Download From Radio"), "<Alt>d", None, self.mh),
+            ('download', None, _("Download From Radio"),
+             "<Alt>d", None, self.mh),
             ('upload', None, _("Upload To Radio"), "<Alt>u", None, self.mh),
             ('import', None, _("Import"), "<Alt>i", None, self.mh),
             ('export', None, _("Export"), "<Alt>x", None, self.mh),
-            ('importsrc', None, _("Import from data source"), None, None, self.mh),
-            ('iradioreference', None, _("RadioReference.com"), None, None, self.mh),
+            ('importsrc', None, _("Import from data source"),
+             None, None, self.mh),
+            ('iradioreference', None, _("RadioReference.com"),
+             None, None, self.mh),
             ('irfinder', None, _("RFinder"), None, None, self.mh),
             ('irbook', None, _("RepeaterBook"), None, None, self.mh),
             ('ipr', None, _("przemienniki.net"), None, None, self.mh),
             ('querysrc', None, _("Query data source"), None, None, self.mh),
-            ('qradioreference', None, _("RadioReference.com"), None, None, self.mh),
+            ('qradioreference', None, _("RadioReference.com"),
+             None, None, self.mh),
             ('qrfinder', None, _("RFinder"), None, None, self.mh),
             ('qpr', None, _("przemienniki.net"), None, None, self.mh),
             ('qrbook', None, _("RepeaterBook"), None, None, self.mh),
-            ('export_chirp', None, _("CHIRP Native File"), None, None, self.mh),
+            ('export_chirp', None, _("CHIRP Native File"),
+             None, None, self.mh),
             ('export_csv', None, _("CSV File"), None, None, self.mh),
-            ('stock', None, _("Import from stock config"), None, None, self.mh),
-            ('channel_defaults', None, _("Channel defaults"), None, None, self.mh),
+            ('stock', None, _("Import from stock config"),
+             None, None, self.mh),
+            ('channel_defaults', None, _("Channel defaults"),
+             None, None, self.mh),
             ('cancelq', gtk.STOCK_STOP, None, "Escape", None, self.mh),
             ('help', None, _('Help'), None, None, self.mh),
             ('about', gtk.STOCK_ABOUT, None, None, None, self.mh),
-            ('documentation', None, _("Documentation"), None, None, self.mh),
+            ('gethelp', None, _("Get Help Online..."), None, None, self.mh),
             ]
 
         conf = config.get()
-        re = not conf.get_bool("no_report");
-        hu = conf.get_bool("hide_unused", "memedit")
+        re = not conf.get_bool("no_report")
+        hu = conf.get_bool("hide_unused", "memedit", default=True)
         dv = conf.get_bool("developer", "state")
         st = not conf.get_bool("no_smart_tmode", "memedit")
 
-        toggles = [\
-            ('report', None, _("Report statistics"), None, None, self.mh, re),
-            ('hide_unused', None, _("Hide Unused Fields"), None, None, self.mh, hu),
-            ('no_smart_tmode', None, _("Smart Tone Modes"), None, None, self.mh, st),
-            ('developer', None, _("Enable Developer Functions"), None, None, self.mh, dv),
-            ]
+        toggles = [('report', None, _("Report statistics"),
+                    None, None, self.mh, re),
+                   ('hide_unused', None, _("Hide Unused Fields"),
+                    None, None, self.mh, hu),
+                   ('no_smart_tmode', None, _("Smart Tone Modes"),
+                    None, None, self.mh, st),
+                   ('developer', None, _("Enable Developer Functions"),
+                    None, None, self.mh, dv),
+                   ]
 
         self.menu_uim = gtk.UIManager()
         self.menu_ag = gtk.ActionGroup("MenuBar")
@@ -1566,14 +1632,15 @@ If you think that it is valid, you can select a radio model below to force an op
 
     def make_tabs(self):
         self.tabs = gtk.Notebook()
+        self.tabs.set_scrollable(True)
 
-        return self.tabs        
+        return self.tabs
 
     def close_out(self):
         num = self.tabs.get_n_pages()
         while num > 0:
             num -= 1
-            print "Closing %i" % num
+            LOG.debug("Closing %i" % num)
             try:
                 self.do_close(self.tabs.get_nth_page(num))
             except ModifiedError:
@@ -1589,28 +1656,29 @@ If you think that it is valid, you can select a radio model below to force an op
         self.sb_general = gtk.Statusbar()
         self.sb_general.set_has_resize_grip(False)
         self.sb_general.show()
-        box.pack_start(self.sb_general, 1,1,1)
-        
+        box.pack_start(self.sb_general, 1, 1, 1)
+
         self.sb_radio = gtk.Statusbar()
         self.sb_radio.set_has_resize_grip(True)
         self.sb_radio.show()
-        box.pack_start(self.sb_radio, 1,1,1)
+        box.pack_start(self.sb_radio, 1, 1, 1)
 
         box.show()
         return box
 
     def ev_delete(self, window, event):
         if not self.close_out():
-            return True # Don't exit
+            return True  # Don't exit
 
     def ev_destroy(self, window):
         if not self.close_out():
-            return True # Don't exit
+            return True  # Don't exit
 
     def setup_extra_hotkeys(self):
         accelg = self.menu_uim.get_accel_group()
 
-        memedit = lambda a: self.get_current_editorset().editors["memedit"].hotkey(a)
+        def memedit(a):
+            self.get_current_editorset().editors["memedit"].hotkey(a)
 
         actions = [
             # ("action_name", "key", function)
@@ -1622,7 +1690,7 @@ If you think that it is valid, you can select a radio model below to force an op
             self.menu_ag.add_action_with_accel(a, key)
             a.set_accel_group(accelg)
             a.connect_accelerator()
-        
+
     def _set_icon(self):
         execpath = platform.get_platform().executable_path()
         path = os.path.abspath(os.path.join(execpath, "share", "chirp.png"))
@@ -1632,7 +1700,7 @@ If you think that it is valid, you can select a radio model below to force an op
         if os.path.exists(path):
             self.set_icon_from_file(path)
         else:
-            print "Icon %s not found" % path
+            LOG.warn("Icon %s not found" % path)
 
     def _updates(self, version):
         if not version:
@@ -1641,13 +1709,13 @@ If you think that it is valid, you can select a radio model below to force an op
         if version == CHIRP_VERSION:
             return
 
-        print "Server reports version %s is available" % version
+        LOG.info("Server reports version %s is available" % version)
 
         # Report new updates every seven days
         intv = 3600 * 24 * 7
 
         if CONF.is_defined("last_update_check", "state") and \
-             (time.time() - CONF.get_int("last_update_check", "state")) < intv:
+           (time.time() - CONF.get_int("last_update_check", "state")) < intv:
             return
 
         CONF.set_int("last_update_check", int(time.time()), "state")
@@ -1666,9 +1734,9 @@ If you think that it is valid, you can select a radio model below to force an op
             import gtk_osxapplication
             macapp = gtk_osxapplication.OSXApplication()
         except ImportError, e:
-            print "No MacOS support: %s" % e
+            LOG.error("No MacOS support: %s" % e)
             return
-        
+
         menu_bar.hide()
         macapp.set_menu_bar(menu_bar)
 
@@ -1678,13 +1746,13 @@ If you think that it is valid, you can select a radio model below to force an op
         aboutitem = self.menu_uim.get_widget("/MenuBar/help/about")
         macapp.insert_app_menu_item(aboutitem, 0)
 
-        documentationitem = self.menu_uim.get_widget("/MenuBar/help/documentation")
+        documentationitem = self.menu_uim.get_widget("/MenuBar/help/gethelp")
         macapp.insert_app_menu_item(documentationitem, 0)
-        
+
         macapp.set_use_quartz_accelerators(False)
         macapp.ready()
 
-        print "Initialized MacOS support"
+        LOG.debug("Initialized MacOS support")
 
     def __init__(self, *args, **kwargs):
         gtk.Window.__init__(self, *args, **kwargs)
@@ -1714,7 +1782,7 @@ If you think that it is valid, you can select a radio model below to force an op
         mbar = self.make_menubar()
 
         if os.name != "nt":
-            self._set_icon() # Windows gets the icon from the exe
+            self._set_icon()  # Windows gets the icon from the exe
             if os.uname()[0] == "Darwin":
                 self._init_macos(mbar)
 
@@ -1755,7 +1823,7 @@ If you think that it is valid, you can select a radio model below to force an op
             d.set_markup("<b><big>" +
                          _("Error reporting is enabled") +
                          "</big></b>")
-            d.format_secondary_markup(\
+            d.format_secondary_markup(
                 _("If you wish to disable this feature you may do so in "
                   "the <u>Help</u> menu"))
             d.run()
diff --git a/chirpui/memdetail.py b/chirp/ui/memdetail.py
similarity index 61%
rename from chirpui/memdetail.py
rename to chirp/ui/memdetail.py
index d202964..d58ecde 100644
--- a/chirpui/memdetail.py
+++ b/chirp/ui/memdetail.py
@@ -15,12 +15,16 @@
 
 import gtk
 import os
+import logging
 
 from chirp import chirp_common, settings
-from chirpui import miscwidgets, common
+from chirp.ui import miscwidgets, common
+
+LOG = logging.getLogger(__name__)
 
 POL = ["NN", "NR", "RN", "RR"]
 
+
 class ValueEditor:
     """Base class"""
     def __init__(self, features, memory, errfn, name, data=None):
@@ -49,7 +53,8 @@ class ValueEditor:
             return getattr(self._memory, self._name)
 
     def _get_value(self):
-        """Returns the value from the widget that should be set in the memory"""
+        """Returns the value from the widget that
+        should be set in the memory"""
 
     def update(self):
         """Updates the memory object with self._getvalue()"""
@@ -87,6 +92,7 @@ class ValueEditor:
         else:
             self._errfn(self._name, None)
 
+
 class StringEditor(ValueEditor):
     def _init(self, data):
         self._widget = gtk.Entry(int(data))
@@ -99,6 +105,7 @@ class StringEditor(ValueEditor):
     def changed(self, _widget):
         self.update()
 
+
 class ChoiceEditor(ValueEditor):
     def _init(self, data):
         self._widget = miscwidgets.make_choice([str(x) for x in data],
@@ -112,6 +119,7 @@ class ChoiceEditor(ValueEditor):
     def changed(self, _widget):
         self.update()
 
+
 class PowerChoiceEditor(ChoiceEditor):
     def _init(self, data):
         self._choices = data
@@ -124,14 +132,17 @@ class PowerChoiceEditor(ChoiceEditor):
                 return level
         raise Exception("Internal error: power level went missing")
 
+
 class IntChoiceEditor(ChoiceEditor):
     def _get_value(self):
         return int(self._widget.get_active_text())
 
+
 class FloatChoiceEditor(ChoiceEditor):
     def _get_value(self):
         return float(self._widget.get_active_text())
 
+
 class FreqEditor(StringEditor):
     def _init(self, data):
         StringEditor._init(self, 0)
@@ -142,6 +153,7 @@ class FreqEditor(StringEditor):
     def _get_value(self):
         return chirp_common.parse_freq(self._widget.get_text())
 
+
 class BooleanEditor(ValueEditor):
     def _init(self, data):
         self._widget = gtk.CheckButton("Enabled")
@@ -154,27 +166,31 @@ class BooleanEditor(ValueEditor):
     def toggled(self, _widget):
         self.update()
 
+
 class OffsetEditor(FreqEditor):
     pass
 
+
 class MemoryDetailEditor(gtk.Dialog):
     """Detail editor for a memory"""
 
-    def _add(self, tab, row, name, editor, labeltxt, colindex=0):
-        label = gtk.Label(labeltxt)
-        img = gtk.Image()
-
+    def _add(self, tab, row, name, editor, text, colindex=0):
+        label = gtk.Label(text + ":")
+        label.set_alignment(0.0, 0.5)
         label.show()
-        tab.attach(label, colindex, colindex + 1, row, row + 1)
-        colindex += 1
+        tab.attach(label, colindex, colindex + 1, row, row + 1,
+                   xoptions=gtk.FILL, yoptions=0, xpadding=6, ypadding=3)
 
-        editor.get_widget().show()
-        tab.attach(editor.get_widget(), colindex, colindex + 1, row, row + 1)
-        colindex += 1
+        widget = editor.get_widget()
+        widget.show()
+        tab.attach(widget, colindex + 1, colindex + 2, row, row + 1,
+                   xoptions=gtk.FILL, yoptions=0, xpadding=3, ypadding=3)
 
-        img.set_size_request(15, -1)
+        img = gtk.Image()
+        img.set_size_request(16, -1)
         img.show()
-        tab.attach(img, colindex, colindex + 1, row, row + 1)
+        tab.attach(img, colindex + 2, colindex + 3, row, row + 1,
+                   xoptions=gtk.FILL, yoptions=0, xpadding=3, ypadding=3)
 
         self._editors[name] = label, editor, img
         return label, editor, img
@@ -182,66 +198,98 @@ class MemoryDetailEditor(gtk.Dialog):
     def _set_doc(self, name, doc):
         label, editor, _img = self._editors[name]
         self._tips.set_tip(label, doc)
-        self._tips.set_tip(editor.get_widget(), doc)
 
     def _make_ui(self):
+
+        box = gtk.VBox()
+        box.show()
+
+        notebook = gtk.Notebook()
+        notebook.set_show_border(False)
+        notebook.show()
+
         sw = gtk.ScrolledWindow()
         sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
         sw.show()
-        tab = gtk.Table(len(self._order), 3, False)
-        sw.add_with_viewport(tab)
-        self.vbox.pack_start(sw, 1, 1, 1)
-        tab.show()
 
-        row = 0
+        hbox = gtk.HBox()
+        hbox.pack_start(sw, 1, 1, 1)
+        hbox.show()
+
+        tab = notebook.append_page(hbox, gtk.Label(_("General")))
+
+        table = gtk.Table(len(self._order), 4, False)
+        table.set_resize_mode(gtk.RESIZE_IMMEDIATE)
+        table.show()
+        sw.add_with_viewport(table)
 
         def _err(name, msg):
             try:
                 _img = self._editors[name][2]
             except KeyError:
-                print self._editors.keys()
+                LOG.error(self._editors.keys())
             if msg is None:
                 _img.clear()
                 self._tips.set_tip(_img, "")
             else:
-                _img.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_MENU)
+                _img.set_from_stock(gtk.STOCK_DIALOG_WARNING,
+                                    gtk.ICON_SIZE_MENU)
                 self._tips.set_tip(_img, str(msg))
             self._errors[self._order.index(name)] = msg is not None
             self.set_response_sensitive(gtk.RESPONSE_OK,
                                         True not in self._errors)
 
+        row = 0
         for name in self._order:
-            labeltxt, editorcls, data = self._elements[name]
-
+            text, editorcls, data = self._elements[name]
             editor = editorcls(self._features, self._memory,
                                _err, name, data)
-            self._add(tab, row, name, editor, labeltxt)
-            row += 1
 
-        for setting in self._memory.extra:
-            name = "extra_%s" % setting.get_name()
-            if isinstance(setting.value,
-                          settings.RadioSettingValueBoolean):
-                editor = BooleanEditor(self._features, self._memory,
-                                       _err, name)
-                self._add(tab, row, name, editor, setting.get_shortname())
-                self._set_doc(name, setting.__doc__)
-            elif isinstance(setting.value,
-                            settings.RadioSettingValueList):
-                editor = ChoiceEditor(self._features, self._memory,
-                                      _err, name, setting.value.get_options())
-                self._add(tab, row, name, editor, setting.get_shortname())
-                self._set_doc(name, setting.__doc__)
+            self._add(table, row, name, editor, text)
+            self._set_doc(name, text)
             row += 1
-            self._order.append(name)
 
-    def _title(self):
-        return _("Edit Memory #{num}").format(num=self._memory.number)
+        if len(self._memory.extra):
+            sw = gtk.ScrolledWindow()
+            sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+            sw.show()
+
+            hbox = gtk.HBox()
+            hbox.pack_start(sw, 1, 1, 1)
+            hbox.show()
+
+            tab = notebook.append_page(hbox, gtk.Label(_("Other")))
+
+            table = gtk.Table(len(self._memory.extra), 4, False)
+            table.set_resize_mode(gtk.RESIZE_IMMEDIATE)
+            table.show()
+            sw.add_with_viewport(table)
+
+            for setting in self._memory.extra:
+                name = "extra_%s" % setting.get_name()
+                if isinstance(setting.value,
+                              settings.RadioSettingValueBoolean):
+                    editor = BooleanEditor(self._features, self._memory,
+                                           _err, name)
+                    self._add(table, row, name, editor,
+                              setting.get_shortname())
+                    self._set_doc(name, setting.__doc__)
+                elif isinstance(setting.value,
+                                settings.RadioSettingValueList):
+                    editor = ChoiceEditor(self._features, self._memory, _err,
+                                          name, setting.value.get_options())
+                    self._add(table, row, name, editor,
+                              setting.get_shortname())
+                    self._set_doc(name, setting.__doc__)
+                row += 1
+                self._order.append(name)
+
+        self.vbox.pack_start(notebook, 1, 1, 1)
 
     def __init__(self, features, memory, parent=None):
         self._memory = memory
         gtk.Dialog.__init__(self,
-                            title=self._title(),
+                            title="Memory Properties",
                             flags=gtk.DIALOG_MODAL,
                             parent=parent,
                             buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
@@ -253,50 +301,52 @@ class MemoryDetailEditor(gtk.Dialog):
 
         self._editors = {}
         self._elements = {
-            "freq" : (_("Frequency"), FreqEditor, None),
-            "name" : (_("Name"), StringEditor, features.valid_name_length),
-            "tmode" : (_("Tone Mode"), ChoiceEditor, features.valid_tmodes),
-            "rtone" : (_("Tone"), FloatChoiceEditor, chirp_common.TONES),
-            "ctone" : (_("ToneSql"), FloatChoiceEditor, chirp_common.TONES),
-            "dtcs"  : (_("DTCS Code"), IntChoiceEditor,
-                                       chirp_common.DTCS_CODES),
-            "dtcs_polarity" : (_("DTCS Pol"), ChoiceEditor, POL),
-            "cross_mode" : (_("Cross mode"),
-                            ChoiceEditor,
-                            features.valid_cross_modes),
-            "duplex" : (_("Duplex"), ChoiceEditor, features.valid_duplexes),
-            "offset" : (_("Offset"), OffsetEditor, None),
-            "mode" : (_("Mode"), ChoiceEditor, features.valid_modes),
-            "tuning_step" : (_("Tune Step"),
-                             FloatChoiceEditor,
-                             features.valid_tuning_steps),
-            "skip" : (_("Skip"), ChoiceEditor, features.valid_skips),
-            "comment" : (_("Comment"), StringEditor, 256),
-            }
-        self._order = ["freq", "name", "tmode", "rtone", "ctone", "cross_mode",
-                       "dtcs", "dtcs_polarity", "duplex", "offset",
-                       "mode", "tuning_step", "skip", "comment"]
-
-        if self._features.has_rx_dtcs:
-            self._elements['rx_dtcs'] = (_("RX DTCS Code"),
-                                         IntChoiceEditor,
-                                         chirp_common.DTCS_CODES)
-            self._order.insert(self._order.index("dtcs") + 1, "rx_dtcs")
-
-        if self._features.valid_power_levels:
-            self._elements["power"] = (_("Power"),
-                                       PowerChoiceEditor,
-                                       features.valid_power_levels)
-            self._order.insert(self._order.index("skip"), "power")
-
-        self._make_ui()
-        self.set_default_size(400, -1)
+            "freq":          (_("Frequency"),
+                              FreqEditor, None),
+            "name":          (_("Name"),
+                              StringEditor, features.valid_name_length),
+            "tmode":         (_("Tone Mode"),
+                              ChoiceEditor, features.valid_tmodes),
+            "rtone":         (_("Tone"),
+                              FloatChoiceEditor, chirp_common.TONES),
+            "ctone":         (_("ToneSql"),
+                              FloatChoiceEditor, chirp_common.TONES),
+            "dtcs":          (_("DTCS Code"),
+                              IntChoiceEditor, chirp_common.DTCS_CODES),
+            "rx_dtcs":       (_("RX DTCS Code"),
+                              IntChoiceEditor, chirp_common.DTCS_CODES),
+            "dtcs_polarity": (_("DTCS Pol"),
+                              ChoiceEditor, POL),
+            "cross_mode":    (_("Cross mode"),
+                              ChoiceEditor, features.valid_cross_modes),
+            "duplex":        (_("Duplex"),
+                              ChoiceEditor, features.valid_duplexes),
+            "offset":        (_("Offset"),
+                              OffsetEditor, None),
+            "mode":          (_("Mode"),
+                              ChoiceEditor, features.valid_modes),
+            "tuning_step":   (_("Tune Step"),
+                              FloatChoiceEditor, features.valid_tuning_steps),
+            "skip":          (_("Skip"),
+                              ChoiceEditor, features.valid_skips),
+            "power":         (_("Power"),
+                              PowerChoiceEditor, features.valid_power_levels),
+            "comment":       (_("Comment"),
+                              StringEditor, 256),
+        }
+
+        self._order = [
+            "freq", "name", "tmode", "rtone", "ctone",  "cross_mode",
+            "dtcs", "rx_dtcs", "dtcs_polarity", "duplex", "offset",
+            "mode", "tuning_step", "skip", "power", "comment"
+        ]
 
         hide_rules = [
             ("name", features.has_name),
             ("tmode", len(features.valid_tmodes) > 0),
             ("ctone", features.has_ctone),
             ("dtcs", features.has_dtcs),
+            ("rx_dtcs", features.has_rx_dtcs),
             ("dtcs_polarity", features.has_dtcs_polarity),
             ("cross_mode", "Cross" in features.valid_tmodes),
             ("duplex", len(features.valid_duplexes) > 0),
@@ -304,16 +354,16 @@ class MemoryDetailEditor(gtk.Dialog):
             ("mode", len(features.valid_modes) > 0),
             ("tuning_step", features.has_tuning_step),
             ("skip", len(features.valid_skips) > 0),
+            ("power", features.valid_power_levels),
             ("comment", features.has_comment),
-            ]
+        ]
 
         for name, visible in hide_rules:
             if not visible:
-                for widget in self._editors[name]:
-                    if isinstance(widget, ValueEditor):
-                        widget.get_widget().hide()
-                    else:
-                        widget.hide()
+                del self._elements[name]
+                self._order.remove(name)
+
+        self._make_ui()
 
         self._errors = [False] * len(self._order)
 
@@ -336,9 +386,8 @@ class MemoryDetailEditor(gtk.Dialog):
         self._memory.empty = False
         return self._memory
 
+
 class MultiMemoryDetailEditor(MemoryDetailEditor):
-    def _title(self):
-        return _("Edit Multiple Memories")
 
     def __init__(self, features, memory, parent=None):
         self._selections = dict()
@@ -348,21 +397,18 @@ class MultiMemoryDetailEditor(MemoryDetailEditor):
         for widget in widgets:
             widget.set_sensitive(selector.get_active())
 
-    def _add(self, tab, row, name, editor, labeltxt):
+    def _add(self, tab, row, name, editor, text):
+
         label, editor, img = super(MultiMemoryDetailEditor, self)._add(
-            tab, row, name, editor, labeltxt, 1)
+            tab, row, name, editor, text, 1)
 
         selector = gtk.CheckButton()
-        tab.attach(selector, 0, 1, row, row + 1)
+        tab.attach(selector, 0, 1, row, row + 1,
+                   xoptions=gtk.FILL, yoptions=0, xpadding=0, ypadding=3)
         selector.show()
         self._toggle_selector(selector, label, editor, img)
         selector.connect("toggled", self._toggle_selector, label, editor, img)
         self._selections[name] = selector
-        self._tips.set_tip(
-            selector,
-            _("Check this to change the {name} value").format(
-                name=labeltxt))
-
 
     def get_fields(self):
         return [k for k, v in self._selections.items() if v.get_active()]
diff --git a/chirpui/memedit.py b/chirp/ui/memedit.py
similarity index 85%
rename from chirpui/memedit.py
rename to chirp/ui/memedit.py
index b0c3ab9..c3d33af 100644
--- a/chirpui/memedit.py
+++ b/chirp/ui/memedit.py
@@ -14,10 +14,6 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-if __name__ == "__main__":
-    import sys
-    sys.path.insert(0, "..")
-
 import threading
 
 import gtk
@@ -31,13 +27,23 @@ from gobject import TYPE_INT, \
 import gobject
 import pickle
 import os
+import logging
 
-from chirpui import common, shiftdialog, miscwidgets, config, memdetail
-from chirpui import bandplans
+from chirp.ui import common, shiftdialog, miscwidgets, config, memdetail
+from chirp.ui import bandplans
 from chirp import chirp_common, errors, directory, import_logic
 
+LOG = logging.getLogger(__name__)
+
+
+if __name__ == "__main__":
+    import sys
+    sys.path.insert(0, "..")
+
+
 def handle_toggle(_, path, store, col):
-    store[path][col] = not store[path][col]    
+    store[path][col] = not store[path][col]
+
 
 def handle_ed(_, iter, new, store, col):
     old, = store.get(iter, col)
@@ -47,75 +53,78 @@ def handle_ed(_, iter, new, store, col):
     else:
         return False
 
+
 class ValueErrorDialog(gtk.MessageDialog):
     def __init__(self, exception, **args):
         gtk.MessageDialog.__init__(self, buttons=gtk.BUTTONS_OK, **args)
         self.set_property("text", _("Invalid value for this field"))
         self.format_secondary_text(str(exception))
 
+
 def iter_prev(store, iter):
     row = store.get_path(iter)[0]
     if row == 0:
         return None
     return store.get_iter((row - 1,))
 
+
 class MemoryEditor(common.Editor):
     cols = [
-        (_("Loc")           , TYPE_INT,     gtk.CellRendererText,  ),
-        (_("Frequency")     , TYPE_INT64,   gtk.CellRendererText,  ),
-        (_("Name")          , TYPE_STRING,  gtk.CellRendererText,  ), 
-        (_("Tone Mode")     , TYPE_STRING,  gtk.CellRendererCombo, ),
-        (_("Tone")          , TYPE_FLOAT,   gtk.CellRendererCombo, ),
-        (_("ToneSql")       , TYPE_FLOAT,   gtk.CellRendererCombo, ),
-        (_("DTCS Code")     , TYPE_INT,     gtk.CellRendererCombo, ),
-        (_("DTCS Rx Code")  , TYPE_INT,     gtk.CellRendererCombo, ),
-        (_("DTCS Pol")      , TYPE_STRING,  gtk.CellRendererCombo, ),
-        (_("Cross Mode")    , TYPE_STRING,  gtk.CellRendererCombo, ),
-        (_("Duplex")        , TYPE_STRING,  gtk.CellRendererCombo, ),
-        (_("Offset")        , TYPE_INT64,   gtk.CellRendererText,  ),
-        (_("Mode")          , TYPE_STRING,  gtk.CellRendererCombo, ),
-        (_("Power")         , TYPE_STRING,  gtk.CellRendererCombo, ),
-        (_("Tune Step")     , TYPE_FLOAT,   gtk.CellRendererCombo, ),
-        (_("Skip")          , TYPE_STRING,  gtk.CellRendererCombo, ),
-        (_("Comment")       , TYPE_STRING,  gtk.CellRendererText,  ),
-        ("_filled"          , TYPE_BOOLEAN, None,                  ),
-        ("_hide_cols"       , TYPE_PYOBJECT,None,                  ),
-        ("_extd"            , TYPE_STRING,  None,                  ),
+        (_("Loc"),            TYPE_INT,      gtk.CellRendererText,),
+        (_("Frequency"),      TYPE_INT64,    gtk.CellRendererText,),
+        (_("Name"),           TYPE_STRING,   gtk.CellRendererText,),
+        (_("Tone Mode"),      TYPE_STRING,   gtk.CellRendererCombo,),
+        (_("Tone"),           TYPE_FLOAT,    gtk.CellRendererCombo,),
+        (_("ToneSql"),        TYPE_FLOAT,    gtk.CellRendererCombo,),
+        (_("DTCS Code"),      TYPE_INT,      gtk.CellRendererCombo,),
+        (_("DTCS Rx Code"),   TYPE_INT,      gtk.CellRendererCombo,),
+        (_("DTCS Pol"),       TYPE_STRING,   gtk.CellRendererCombo,),
+        (_("Cross Mode"),     TYPE_STRING,   gtk.CellRendererCombo,),
+        (_("Duplex"),         TYPE_STRING,   gtk.CellRendererCombo,),
+        (_("Offset"),         TYPE_INT64,    gtk.CellRendererText,),
+        (_("Mode"),           TYPE_STRING,   gtk.CellRendererCombo,),
+        (_("Power"),          TYPE_STRING,   gtk.CellRendererCombo,),
+        (_("Tune Step"),      TYPE_FLOAT,    gtk.CellRendererCombo,),
+        (_("Skip"),           TYPE_STRING,   gtk.CellRendererCombo,),
+        (_("Comment"),        TYPE_STRING,   gtk.CellRendererText,),
+        ("_filled",           TYPE_BOOLEAN,  None,),
+        ("_hide_cols",        TYPE_PYOBJECT, None,),
+        ("_extd",             TYPE_STRING,   None,),
         ]
 
     defaults = {
-        _("Name")          : "",
-        _("Frequency")     : 146010000,
-        _("Tone")          : 88.5,
-        _("ToneSql")       : 88.5,
-        _("DTCS Code")     : 23,
-        _("DTCS Rx Code")  : 23,
-        _("DTCS Pol")      : "NN",
-        _("Cross Mode")    : "Tone->Tone",
-        _("Duplex")        : "",
-        _("Offset")        : 0,
-        _("Mode")          : "FM",
-        _("Power")         : "",
-        _("Tune Step")     : 5.0,
-        _("Tone Mode")     : "",
-        _("Skip")          : "",
-        _("Comment")       : "",
+        _("Name"):           "",
+        _("Frequency"):      146010000,
+        _("Tone"):           88.5,
+        _("ToneSql"):        88.5,
+        _("DTCS Code"):      23,
+        _("DTCS Rx Code"):   23,
+        _("DTCS Pol"):       "NN",
+        _("Cross Mode"):     "Tone->Tone",
+        _("Duplex"):         "",
+        _("Offset"):         0,
+        _("Mode"):           "FM",
+        _("Power"):          "",
+        _("Tune Step"):      5.0,
+        _("Tone Mode"):      "",
+        _("Skip"):           "",
+        _("Comment"):        "",
         }
 
     choices = {
-        _("Tone")          : chirp_common.TONES,
-        _("ToneSql")       : chirp_common.TONES,
-        _("DTCS Code")     : chirp_common.ALL_DTCS_CODES,
-        _("DTCS Rx Code")  : chirp_common.ALL_DTCS_CODES,
-        _("DTCS Pol")      : ["NN", "NR", "RN", "RR"],
-        _("Mode")          : chirp_common.MODES,
-        _("Power")         : [],
-        _("Duplex")        : ["", "-", "+", "split", "off"],
-        _("Tune Step")     : chirp_common.TUNING_STEPS,
-        _("Tone Mode")     : ["", "Tone", "TSQL", "DTCS"],
-        _("Cross Mode")    : chirp_common.CROSS_MODES,
+        _("Tone"):           chirp_common.TONES,
+        _("ToneSql"):        chirp_common.TONES,
+        _("DTCS Code"):      chirp_common.ALL_DTCS_CODES,
+        _("DTCS Rx Code"):   chirp_common.ALL_DTCS_CODES,
+        _("DTCS Pol"):       ["NN", "NR", "RN", "RR"],
+        _("Mode"):           chirp_common.MODES,
+        _("Power"):          [],
+        _("Duplex"):         ["", "-", "+", "split", "off"],
+        _("Tune Step"):      chirp_common.TUNING_STEPS,
+        _("Tone Mode"):      ["", "Tone", "TSQL", "DTCS"],
+        _("Cross Mode"):     chirp_common.CROSS_MODES,
         }
-    
+
     def ed_name(self, _, __, new, ___):
         return self.rthread.radio.filter_name(new)
 
@@ -136,8 +145,8 @@ class MemoryEditor(common.Editor):
                 dup = "-"
                 offset *= -1
 
-            if not dup in self.choices[_("Duplex")]:
-                print "Duplex %s not supported by this radio" % dup
+            if dup not in self.choices[_("Duplex")]:
+                LOG.warn("Duplex %s not supported by this radio" % dup)
                 return
 
             if offset:
@@ -149,7 +158,7 @@ class MemoryEditor(common.Editor):
             if ts in self.choices[_("Tune Step")]:
                 self.store.set(iter, self.col(_("Tune Step")), ts)
             else:
-                print "Tune step %s not supported by this radio" % ts
+                LOG.warn("Tune step %s not supported by this radio" % ts)
 
         def get_ts(path):
             return self.store.get(iter, self.col(_("Tune Step")))[0]
@@ -158,19 +167,19 @@ class MemoryEditor(common.Editor):
             if mode in self.choices[_("Mode")]:
                 self.store.set(iter, self.col(_("Mode")), mode)
             else:
-                print "Mode %s not supported by this radio (%s)" % (
-                    mode, self.choices[_("Mode")])
+                LOG.warn("Mode %s not supported by this radio (%s)" %
+                         (mode, self.choices[_("Mode")]))
 
         def set_tone(tone):
             if tone in self.choices[_("Tone")]:
                 self.store.set(iter, self.col(_("Tone")), tone)
             else:
-                print "Tone %s not supported by this radio" % tone
+                LOG.warn("Tone %s not supported by this radio" % tone)
 
         try:
             new = chirp_common.parse_freq(new)
         except ValueError, e:
-            print e
+            LOG.error(e)
             new = None
 
         if not self._features.has_nostep_tuning:
@@ -203,7 +212,7 @@ class MemoryEditor(common.Editor):
 
     def ed_duplex(self, _foo1, path, new, _foo2):
         if new == "":
-            return # Fast path outta here
+            return  # Fast path outta here
 
         iter = self.store.get_iter(path)
         freq, = self.store.get(iter, self.col(_("Frequency")))
@@ -234,6 +243,7 @@ class MemoryEditor(common.Editor):
             if modes[0] not in tmodes:
                 modes[0] = tmodes[0]
                 self.store.set(iter, self.col(_("Tone Mode")), modes[0])
+
         def _cm(*cmodes):
             if modes[0] == "Cross" and modes[1] not in cmodes:
                 modes[1] = cmodes[0]
@@ -279,7 +289,7 @@ class MemoryEditor(common.Editor):
                      self.col(_("DTCS Rx Code")),
                      self.col(_("DTCS Pol")),
                      self.col(_("Cross Mode"))]
-        elif tmode == "TSQL":
+        elif tmode == "TSQL" or tmode == "TSQL-R":
             if self._features.has_ctone:
                 hide += [self.col(_("Tone"))]
 
@@ -287,7 +297,7 @@ class MemoryEditor(common.Editor):
                      self.col(_("DTCS Rx Code")),
                      self.col(_("DTCS Pol")),
                      self.col(_("Cross Mode"))]
-        elif tmode == "DTCS":
+        elif tmode == "DTCS" or tmode == "DTCS-R":
             hide += [self.col(_("Tone")),
                      self.col(_("ToneSql")),
                      self.col(_("Cross Mode")),
@@ -314,7 +324,6 @@ class MemoryEditor(common.Editor):
         if duplex == "" or duplex == "(None)" or duplex == "off":
             hide += [self.col(_("Offset"))]
 
-
         return hide
 
     def maybe_hide_cols(self, iter):
@@ -327,19 +336,19 @@ class MemoryEditor(common.Editor):
             return
 
         iter = self.store.get_iter(path)
-        if not self.store.get(iter, self.col("_filled"))[0] \
-        and self.store.get(iter, self.col(_("Frequency")))[0] == 0:
-            print _("Editing new item, taking defaults")
+        if not self.store.get(iter, self.col("_filled"))[0] and \
+                self.store.get(iter, self.col(_("Frequency")))[0] == 0:
+            LOG.error(_("Editing new item, taking defaults"))
             self.insert_new(iter)
 
         colnum = self.col(cap)
         funcs = {
-            _("Loc") : self.ed_loc,
-            _("Name") : self.ed_name,
-            _("Frequency") : self.ed_freq,
-            _("Duplex") : self.ed_duplex,
-            _("Offset") : self.ed_offset,
-            _("Tone") : self.ed_tone_field,
+            _("Loc"): self.ed_loc,
+            _("Name"): self.ed_name,
+            _("Frequency"): self.ed_freq,
+            _("Duplex"): self.ed_duplex,
+            _("Offset"): self.ed_offset,
+            _("Tone"): self.ed_tone_field,
             _("ToneSql"): self.ed_tone_field,
             _("DTCS Code"): self.ed_tone_field,
             _("DTCS Rx Code"): self.ed_tone_field,
@@ -347,11 +356,11 @@ class MemoryEditor(common.Editor):
             _("Cross Mode"): self.ed_tone_field,
             }
 
-        if funcs.has_key(cap):
+        if cap in funcs:
             new = funcs[cap](rend, path, new, colnum)
 
         if new is None:
-            print _("Bad value for {col}: {val}").format(col=cap, val=new)
+            LOG.error(_("Bad value for {col}: {val}").format(col=cap, val=new))
             return
 
         if self.store.get_column_type(colnum) == TYPE_INT:
@@ -375,8 +384,8 @@ class MemoryEditor(common.Editor):
 
         msgs = self.rthread.radio.validate_memory(mem)
         if msgs:
-            common.show_error(_("Error setting memory") + ": " + \
-                                  "\r\n\r\n".join(msgs))
+            common.show_error(_("Error setting memory") + ": " +
+                              "\r\n\r\n".join(msgs))
             self.prefill()
             return
 
@@ -414,7 +423,6 @@ class MemoryEditor(common.Editor):
             if extd:
                 val = extd
 
-
         return val
 
     def render(self, _, rend, model, iter, colnum):
@@ -427,14 +435,14 @@ class MemoryEditor(common.Editor):
         for key, val in self.defaults.items():
             line.append(self.col(key))
             line.append(val)
-        
+
         if not loc:
             loc, = self.store.get(iter, self.col(_("Loc")))
 
         self.store.set(iter,
                        0, loc,
                        *tuple(line))
-        
+
         return self._get_memory(iter)
 
     def insert_easy(self, store, _iter, delta):
@@ -446,7 +454,7 @@ class MemoryEditor(common.Editor):
         newpos, = store.get(_iter, self.col(_("Loc")))
         newpos += delta
 
-        print "Insert easy: %i" % delta
+        LOG.debug("Insert easy: %i" % delta)
 
         mem = self.insert_new(iter, newpos)
         job = common.RadioJob(None, "set_memory", mem)
@@ -454,12 +462,12 @@ class MemoryEditor(common.Editor):
         self.rthread.submit(job)
 
     def insert_hard(self, store, _iter, delta, warn=True):
-	if isinstance(self.rthread.radio, chirp_common.LiveRadio) and warn:
+        if isinstance(self.rthread.radio, chirp_common.LiveRadio) and warn:
             txt = _("This operation requires moving all subsequent channels "
                     "by one spot until an empty location is reached.  This "
                     "can take a LONG time.  Are you sure you want to do this?")
             if not common.ask_yesno_question(txt):
-                return False # No change
+                return False  # No change
 
         if delta <= 0:
             iter = _iter
@@ -477,11 +485,12 @@ class MemoryEditor(common.Editor):
         else:
             sd.insert(pos)
             sd.destroy()
-            job = common.RadioJob(lambda x: self.prefill(), "erase_memory", pos)
+            job = common.RadioJob(
+                lambda x: self.prefill(), "erase_memory", pos)
             job.set_desc(_("Adding memory {number}").format(number=pos))
             self.rthread.submit(job)
 
-        return True # We changed memories
+        return True  # We changed memories
 
     def _delete_rows(self, paths):
         to_remove = []
@@ -493,16 +502,15 @@ class MemoryEditor(common.Editor):
             job = common.RadioJob(None, "erase_memory", cur_pos)
             job.set_desc(_("Erasing memory {number}").format(number=cur_pos))
             self.rthread.submit(job)
-            
+
             def handler(mem):
                 if not isinstance(mem, Exception):
                     if not mem.empty or self.show_empty:
                         gobject.idle_add(self.set_memory, mem)
-            
+
             job = common.RadioJob(handler, "get_memory", cur_pos)
             job.set_desc(_("Getting memory {number}").format(number=cur_pos))
             self.rthread.submit(job)
-            
 
         if not self.show_empty:
             # We need to actually remove the rows from the store
@@ -518,11 +526,11 @@ class MemoryEditor(common.Editor):
                 if pos in to_remove:
                     to_remove.remove(pos)
                     if not self.store.remove(iter):
-                        break # This was the last row
+                        break  # This was the last row
                 else:
                     iter = self.store.iter_next(iter)
 
-        return True # We changed memories
+        return True  # We changed memories
 
     def _delete_rows_and_shift(self, paths, all=False):
         iter = self.store.get_iter(paths[0])
@@ -533,7 +541,7 @@ class MemoryEditor(common.Editor):
             sd.destroy()
 
         self.prefill()
-        return True # We changed memories
+        return True  # We changed memories
 
     def _move_up_down(self, paths, action):
         if action.endswith("up"):
@@ -550,12 +558,12 @@ class MemoryEditor(common.Editor):
             if victim_path[0] < 0:
                 raise ValueError()
             donor_loc = self.store.get(self.store.get_iter(donor_path),
-                                  self.col(_("Loc")))[0]
+                                       self.col(_("Loc")))[0]
             victim_loc = self.store.get(self.store.get_iter(victim_path),
-                                   self.col(_("Loc")))[0]
+                                        self.col(_("Loc")))[0]
         except ValueError:
             self.emit("usermsg", "No room to %s" % (action.replace("_", " ")))
-            return False # No change
+            return False  # No change
 
         class Context:
             pass
@@ -587,7 +595,7 @@ class MemoryEditor(common.Editor):
             old = mem.number
             mem.number = dest
             job = common.RadioJob(None, "set_memory", mem)
-            job.set_desc(\
+            job.set_desc(
                 _("Moving memory from {old} to {new}").format(old=old,
                                                               new=dest))
             self.rthread.submit(job)
@@ -598,7 +606,7 @@ class MemoryEditor(common.Editor):
             old = mem.number
             mem.number += delta
             job = common.RadioJob(None, "set_memory", mem)
-            job.set_desc(\
+            job.set_desc(
                 _("Moving memory from {old} to {new}").format(old=old,
                                                               new=old+delta))
             self.rthread.submit(job)
@@ -631,7 +639,7 @@ class MemoryEditor(common.Editor):
             job.set_desc("Getting memory %i" % loc)
             self.rthread.submit(job)
 
-        return True # We (scheduled some) change to the memories
+        return True  # We (scheduled some) change to the memories
 
     def _exchange_memories(self, paths):
         if len(paths) != 2:
@@ -647,8 +655,9 @@ class MemoryEditor(common.Editor):
             src = mem.number
             mem.number = dst
             job = common.RadioJob(None, "set_memory", mem)
-            job.set_desc(_("Moving memory from {old} to {new}").format(old=src,
-                                                                       new=dst))
+            job.set_desc(
+                _("Moving memory from {old} to {new}").format(
+                    old=src, new=dst))
             self.rthread.submit(job)
             if dst == loc_a:
                 self.prefill()
@@ -669,8 +678,8 @@ class MemoryEditor(common.Editor):
     def _show_raw(self, cur_pos):
         def idle_show_raw(result):
             gobject.idle_add(common.show_diff_blob,
-                             _("Raw memory {number}").format(number=cur_pos),
-                                                             result)
+                             _("Raw memory {number}").format(
+                                 number=cur_pos), result)
 
         job = common.RadioJob(idle_show_raw, "get_raw_memory", cur_pos)
         job.set_desc(_("Getting raw memory {number}").format(number=cur_pos))
@@ -722,8 +731,8 @@ class MemoryEditor(common.Editor):
             def apply_and_set(memory):
                 for field in fields:
                     self._copy_field(src_memory, memory, field)
-                    cb = (memory.number == locations[-1]
-                          and self._set_memory_cb or None)
+                    cb = (memory.number == locations[-1] and
+                          self._set_memory_cb or None)
                     job = common.RadioJob(cb, "set_memory", memory)
                     job.set_desc(_("Writing memory {number}").format(
                             number=memory.number))
@@ -745,7 +754,8 @@ class MemoryEditor(common.Editor):
             if len(locations) > 1:
                 self._apply_multiple(memory, dlg.get_fields(), locations)
             else:
-                mem.name = self.rthread.radio.filter_name(mem.name)
+                if "name" not in mem.immutable:
+                    mem.name = self.rthread.radio.filter_name(mem.name)
                 job = common.RadioJob(self._set_memory_cb, "set_memory", mem)
                 job.set_desc(_("Writing memory {number}").format(
                         number=mem.number))
@@ -787,14 +797,16 @@ class MemoryEditor(common.Editor):
         elif action == "exchange":
             changed = self._exchange_memories(paths)
         elif action in ["cut", "copy"]:
-            changed = self.copy_selection(action=="cut")
+            changed = self.copy_selection(action == "cut")
         elif action == "paste":
             changed = self.paste_selection()
+        elif action == "all":
+            changed = self.select_all()
         elif action == "devshowraw":
             self._show_raw(cur_pos)
         elif action == "devdiffraw":
             self._diff_raw(paths)
-        elif action == "edit":
+        elif action == "properties":
             job = common.RadioJob(self.edit_memory, "get_memory", cur_pos)
             job.set_cb_args(selected)
             self.rthread.submit(job)
@@ -826,8 +838,13 @@ class MemoryEditor(common.Editor):
 
         menu_xml = """
 <ui>
-  <popup name="Menu"> 
-    <menuitem action="edit"/>
+  <popup name="Menu">
+    <menuitem action="cut"/>
+    <menuitem action="copy"/>
+    <menuitem action="paste"/>
+    <separator/>
+    <menuitem action="all"/>
+    <separator/>
     <menuitem action="insert_prev"/>
     <menuitem action="insert_next"/>
     <menu action="deletes">
@@ -839,21 +856,21 @@ class MemoryEditor(common.Editor):
     <menuitem action="move_dn"/>
     <menuitem action="exchange"/>
     <separator/>
-    <menuitem action="cut"/>
-    <menuitem action="copy"/>
-    <menuitem action="paste"/>
+    <menuitem action="properties"/>
     %s
   </popup>
 </ui>
 """ % devmenu
 
-
         (store, paths) = self.view.get_selection().get_selected_rows()
         issingle = len(paths) == 1
         istwo = len(paths) == 2
 
         actions = [
-            ("edit", _("Edit")),
+            ("cut", _("Cut")),
+            ("copy", _("Copy")),
+            ("paste", _("Paste")),
+            ("all", _("Select All")),
             ("insert_prev", _("Insert row above")),
             ("insert_next", _("Insert row below")),
             ("deletes", _("Delete")),
@@ -863,9 +880,7 @@ class MemoryEditor(common.Editor):
             ("move_up", _("Move up")),
             ("move_dn", _("Move down")),
             ("exchange", _("Exchange memories")),
-            ("cut", _("Cut")),
-            ("copy", _("Copy")),
-            ("paste", _("Paste")),
+            ("properties", _("P_roperties")),
             ("devshowraw", _("Show Raw Memory")),
             ("devdiffraw", _("Diff Raw Memories")),
             ]
@@ -898,14 +913,18 @@ class MemoryEditor(common.Editor):
 
     def click_cb(self, view, event):
         self.emit("usermsg", "")
-        if event.button != 3:
-            return False
-
-        menu = self.make_context_menu()
-        menu.popup(None, None, None, event.button, event.time)
+        if event.button == 3:
+            pathinfo = view.get_path_at_pos(int(event.x), int(event.y))
+            if pathinfo is not None:
+                path, col, x, y = pathinfo
+                view.grab_focus()
+                sel = view.get_selection()
+                if (not sel.path_is_selected(path)):
+                    view.set_cursor(path, col)
+                menu = self.make_context_menu()
+                menu.popup(None, None, None, event.button, event.time)
+            return True
 
-        return True
-        
     def get_column_visible(self, col):
         column = self.view.get_column(col)
         return column.get_visible()
@@ -913,7 +932,7 @@ class MemoryEditor(common.Editor):
     def set_column_visible(self, col, visible):
         column = self.view.get_column(col)
         column.set_visible(visible)
-    
+
     def cell_editing_started(self, rend, event, path):
         self._in_editing = True
 
@@ -928,23 +947,25 @@ class MemoryEditor(common.Editor):
         self.view.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
         self.view.set_rules_hint(True)
 
+        hbox = gtk.HBox()
+
         sw = gtk.ScrolledWindow()
         sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
         sw.add(self.view)
 
         filled = self.col("_filled")
 
-        default_col_order = [x for x,y,z in self.cols if z]
+        default_col_order = [x for x, y, z in self.cols if z]
         try:
-            col_order = self._config.get("column_order_%s" % \
-                                             self.__class__.__name__).split(",")
+            col_order = self._config.get("column_order_%s" %
+                                         self.__class__.__name__).split(",")
             if len(col_order) != len(default_col_order):
                 raise Exception()
             for i in col_order:
                 if i not in default_col_order:
                     raise Exception()
         except Exception, e:
-            print e
+            LOG.error(e)
             col_order = default_col_order
 
         non_editable = ["Loc"]
@@ -963,9 +984,10 @@ class MemoryEditor(common.Editor):
             rend.connect('edited', self.cell_editing_stopped)
 
             if _type == TYPE_BOOLEAN:
-                #rend.set_property("activatable", True)
-                #rend.connect("toggled", handle_toggle, self.store, i)
-                col = gtk.TreeViewColumn(_cap, rend, active=i, sensitive=filled)
+                # rend.set_property("activatable", True)
+                # rend.connect("toggled", handle_toggle, self.store, i)
+                col = gtk.TreeViewColumn(_cap, rend, active=i,
+                                         sensitive=filled)
             elif _rend == gtk.CellRendererCombo:
                 if isinstance(self.choices[_cap], gtk.ListStore):
                     choices = self.choices[_cap]
@@ -985,14 +1007,14 @@ class MemoryEditor(common.Editor):
                 rend.connect("edited", self.edited, _cap)
                 col = gtk.TreeViewColumn(_cap, rend, text=i, sensitive=filled)
                 col.set_cell_data_func(rend, self.render, i)
-                
+
             col.set_reorderable(True)
             col.set_sort_column_id(i)
             col.set_resizable(True)
             col.set_min_width(1)
             col.set_visible(not _cap.startswith("_") and
                             _cap in visible_cols and
-                            not _cap in unsupported_cols)
+                            _cap not in unsupported_cols)
             cols[_cap] = col
             i += 1
 
@@ -1003,16 +1025,21 @@ class MemoryEditor(common.Editor):
 
         self.view.show()
         sw.show()
+        hbox.pack_start(sw, 1, 1, 1)
 
         self.view.connect("button_press_event", self.click_cb)
 
-        return sw
+        hbox.show()
+
+        return hbox
 
     def col(self, caption):
         try:
             return self._cached_cols[caption]
         except KeyError:
-            raise Exception(_("Internal Error: Column {name} not found").format(name=caption))
+            raise Exception(
+                _("Internal Error: Column {name} not found").format(
+                    name=caption))
 
     def prefill(self):
         self.store.clear()
@@ -1089,17 +1116,18 @@ class MemoryEditor(common.Editor):
         while iter:
             loc, = self.store.get(iter, self.col(_("Loc")))
             if loc == number:
-                print "Deleting %i" % number
+                LOG.debug("Deleting %i" % number)
                 # FIXME: Make the actual remove happen on callback
                 self.store.remove(iter)
                 job = common.RadioJob(None, "erase_memory", number)
-                job.set_desc(_("Erasing memory {number}").format(number=number))
+                job.set_desc(
+                    _("Erasing memory {number}").format(number=number))
                 self.rthread.submit()
                 break
             iter = self.store.iter_next(iter)
 
     def _set_mem_vals(self, mem, vals, iter):
-        power_levels = {"" : None}
+        power_levels = {"": None}
         for i in self._features.valid_power_levels:
             power_levels[str(i)] = i
 
@@ -1133,9 +1161,10 @@ class MemoryEditor(common.Editor):
 
     def _limit_key(self, which):
         if which not in ["lo", "hi"]:
-            raise Exception(_("Internal Error: Invalid limit {number}").format(number=which))
-        return "%s_%s" % (directory.radio_class_id(self.rthread.radio.__class__),
-                          which)
+            raise Exception(_("Internal Error: Invalid limit {number}").format(
+                             number=which))
+        return "%s_%s" % \
+            (directory.radio_class_id(self.rthread.radio.__class__), which)
 
     def _store_limit(self, sb, which):
         self._config.set_int(self._limit_key(which), int(sb.get_value()))
@@ -1143,7 +1172,7 @@ class MemoryEditor(common.Editor):
     def make_controls(self, min, max):
         hbox = gtk.HBox(False, 2)
 
-        lab = gtk.Label(_("Memory range:"))
+        lab = gtk.Label(_("Memory Range:"))
         lab.show()
         hbox.pack_start(lab, 0, 0, 0)
 
@@ -1170,9 +1199,10 @@ class MemoryEditor(common.Editor):
         hi.show()
         hbox.pack_start(hi, 0, 0, 0)
 
-        refresh = gtk.Button(_("Go"))
-        refresh.show()
+        refresh = gtk.Button(_("Refresh"))
+        refresh.set_relief(gtk.RELIEF_NONE)
         refresh.connect("clicked", lambda x: self.prefill())
+        refresh.show()
         hbox.pack_start(refresh, 0, 0, 0)
 
         def activate_go(widget):
@@ -1183,30 +1213,43 @@ class MemoryEditor(common.Editor):
             hival = self.hi_limit_adj.get_value()
             if loval >= hival:
                 self.hi_limit_adj.set_value(loval + 25)
-        
+
         lo.connect_after("focus-out-event", set_hi)
         lo.connect_after("activate", activate_go)
         hi.connect_after("activate", activate_go)
 
         sep = gtk.VSeparator()
         sep.show()
-        sep.set_size_request(20, -1)
-        hbox.pack_start(sep, 0, 0, 0)
+        hbox.pack_start(sep, 0, 0, 2)
 
-        showspecial = gtk.CheckButton(_("Special Channels"))
+        showspecial = gtk.ToggleButton(_("Special Channels"))
+        showspecial.set_relief(gtk.RELIEF_NONE)
         showspecial.set_active(self.show_special)
         showspecial.connect("toggled",
                             lambda x: self.set_show_special(x.get_active()))
         showspecial.show()
         hbox.pack_start(showspecial, 0, 0, 0)
 
-        showempty = gtk.CheckButton(_("Show Empty"))
-        showempty.set_active(self.show_empty);
+        showempty = gtk.ToggleButton(_("Show Empty"))
+        showempty.set_relief(gtk.RELIEF_NONE)
+        showempty.set_active(self.show_empty)
         showempty.connect("toggled",
                           lambda x: self.set_show_empty(x.get_active()))
         showempty.show()
         hbox.pack_start(showempty, 0, 0, 0)
 
+        sep = gtk.VSeparator()
+        sep.show()
+        hbox.pack_start(sep, 0, 0, 2)
+
+        props = gtk.Button(_("Properties"))
+        props.set_relief(gtk.RELIEF_NONE)
+        props.connect("clicked",
+                      lambda x: self.hotkey(
+                            gtk.Action("properties", "", "", 0)))
+        props.show()
+        hbox.pack_start(props, 0, 0, 0)
+
         hbox.show()
 
         return hbox
@@ -1259,7 +1302,7 @@ class MemoryEditor(common.Editor):
         for feature, colname in maybe_hide:
             if feature.startswith("has_"):
                 supported = self._features[feature]
-                print "%s supported: %s" % (colname, supported)
+                LOG.info("%s supported: %s" % (colname, supported))
             elif feature.startswith("valid_"):
                 supported = len(self._features[feature]) != 0
 
@@ -1331,13 +1374,9 @@ class MemoryEditor(common.Editor):
         vbox.show()
 
         self.prefill()
-        
-        self.choices["Mode"] = self._features.valid_modes
 
         self.root = vbox
 
-        self.prefill()
-
         # Run low priority jobs to get the rest of the memories
         hi = int(self.hi_limit_adj.get_value())
         for i in range(hi, max+1):
@@ -1369,13 +1408,14 @@ class MemoryEditor(common.Editor):
             mem = self._get_memory(iter)
             selection.append(mem.dupe())
             maybe_cut.append((iter, mem))
-        
+
         if cut:
             for iter, mem in maybe_cut:
                 mem.empty = True
                 job = common.RadioJob(self._set_memory_cb,
                                       "erase_memory", mem.number)
-                job.set_desc(_("Cutting memory {number}").format(number=mem.number))
+                job.set_desc(
+                    _("Cutting memory {number}").format(number=mem.number))
                 self.rthread.submit(job)
 
                 self._set_memory(iter, mem)
@@ -1384,7 +1424,7 @@ class MemoryEditor(common.Editor):
         clipboard = gtk.Clipboard(selection="PRIMARY")
         clipboard.set_text(result)
 
-        return cut # Only changed if we did a cut
+        return cut  # Only changed if we did a cut
 
     def _paste_selection(self, clipboard, text, data):
         if not text:
@@ -1402,15 +1442,15 @@ class MemoryEditor(common.Editor):
         try:
             src_features, mem_list = pickle.loads(text)
         except Exception:
-            print "Paste failed to unpickle"
+            LOG.error("Paste failed to unpickle")
             return
 
         if (paths[0][0] + len(mem_list)) > self._rows_in_store:
             common.show_error(_("Unable to paste {src} memories into "
                                 "{dst} rows. Increase the memory bounds "
-                                "or show empty memories.").format(\
-                    src=len(mem_list),
-                    dst=(self._rows_in_store - paths[0][0])))
+                                "or show empty memories.").format(
+                src=len(mem_list),
+                dst=(self._rows_in_store - paths[0][0])))
             return
 
         for mem in mem_list:
@@ -1425,7 +1465,8 @@ class MemoryEditor(common.Editor):
                                                      gtk.STOCK_NO, 2,
                                                      gtk.STOCK_CANCEL, 3,
                                                      "All", 4))
-                d.set_text(_("Overwrite location {number}?").format(number=loc))
+                d.set_text(
+                    _("Overwrite location {number}?").format(number=loc))
                 r = d.run()
                 d.destroy()
                 if r == 4:
@@ -1440,22 +1481,23 @@ class MemoryEditor(common.Editor):
 
             src_number = mem.number
             mem.number = loc
-            
+
             try:
                 mem = import_logic.import_mem(self.rthread.radio,
                                               src_features,
                                               mem)
             except import_logic.DestNotCompatible:
                 msgs = self.rthread.radio.validate_memory(mem)
-                errs = [x for x in msgs if isinstance(x,
-                                                      chirp_common.ValidationError)]
+                errs = [x for x in msgs
+                        if isinstance(x, chirp_common.ValidationError)]
                 if errs:
                     d = miscwidgets.YesNoDialog(title=_("Incompatible Memory"),
                                                 buttons=(gtk.STOCK_OK, 1,
                                                          gtk.STOCK_CANCEL, 2))
-                    d.set_text(_("Pasted memory {number} is not compatible with "
-                                 "this radio because:").format(number=src_number) +\
-                                   os.linesep + os.linesep.join(msgs))
+                    d.set_text(
+                        _("Pasted memory {number} is not compatible with "
+                          "this radio because:").format(number=src_number) +
+                        os.linesep + os.linesep.join(msgs))
                     r = d.run()
                     d.destroy()
                     if r == 2:
@@ -1468,13 +1510,17 @@ class MemoryEditor(common.Editor):
             iter = store.iter_next(iter)
 
             job = common.RadioJob(self._set_memory_cb, "set_memory", mem)
-            job.set_desc(_("Writing memory {number}").format(number=mem.number))
+            job.set_desc(
+                _("Writing memory {number}").format(number=mem.number))
             self.rthread.submit(job)
 
     def paste_selection(self):
         clipboard = gtk.Clipboard(selection="PRIMARY")
         clipboard.request_text(self._paste_selection)
 
+    def select_all(self):
+        self.view.get_selection().select_all()
+
     def prepare_close(self):
         cols = self.view.get_columns()
         self._config.set("column_order_%s" % self.__class__.__name__,
@@ -1483,6 +1529,7 @@ class MemoryEditor(common.Editor):
     def other_editor_changed(self, target_editor):
         self.need_refresh = True
 
+
 class DstarMemoryEditor(MemoryEditor):
     def _get_cols_to_hide(self, iter):
         hide = MemoryEditor._get_cols_to_hide(self, iter)
@@ -1555,12 +1602,12 @@ class DstarMemoryEditor(MemoryEditor):
         self.defaults["Digital Code"] = 0
 
         MemoryEditor.__init__(self, rthread)
-    
+
         def ucall_cb(calls):
             self.defaults["URCALL"] = calls[0]
             for call in calls:
                 self.choices["URCALL"].append((call, call))
-        
+
         if self._features.requires_call_lists:
             ujob = common.RadioJob(ucall_cb, "get_urcall_list")
             ujob.set_desc(_("Downloading URCALL list"))
@@ -1582,7 +1629,7 @@ class DstarMemoryEditor(MemoryEditor):
 
         if not self._features.requires_call_lists:
             for i in _dv_columns:
-                if not self.choices.has_key(i):
+                if i not in self.choices:
                     continue
                 column = self.view.get_column(self.col(i))
                 rend = column.get_cell_renderers()[0]
@@ -1626,5 +1673,6 @@ class DstarMemoryEditor(MemoryEditor):
                            self.col("Digital Code"), 0,
                            )
 
+
 class ID800MemoryEditor(DstarMemoryEditor):
     pass
diff --git a/chirpui/miscwidgets.py b/chirp/ui/miscwidgets.py
similarity index 92%
rename from chirpui/miscwidgets.py
rename to chirp/ui/miscwidgets.py
index 6562363..768cfb6 100644
--- a/chirpui/miscwidgets.py
+++ b/chirp/ui/miscwidgets.py
@@ -18,20 +18,24 @@ import gobject
 import pango
 
 import os
+import logging
 
 from chirp import platform
 
+LOG = logging.getLogger(__name__)
+
+
 class KeyedListWidget(gtk.HBox):
     __gsignals__ = {
-        "item-selected" : (gobject.SIGNAL_RUN_LAST,
-                           gobject.TYPE_NONE,
-                           (gobject.TYPE_STRING,)),
-        "item-toggled" : (gobject.SIGNAL_ACTION,
-                          gobject.TYPE_BOOLEAN,
-                          (gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)),
-        "item-set" : (gobject.SIGNAL_ACTION,
-                      gobject.TYPE_BOOLEAN,
-                      (gobject.TYPE_STRING,)),
+        "item-selected": (gobject.SIGNAL_RUN_LAST,
+                          gobject.TYPE_NONE,
+                          (gobject.TYPE_STRING,)),
+        "item-toggled": (gobject.SIGNAL_ACTION,
+                         gobject.TYPE_BOOLEAN,
+                         (gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)),
+        "item-set": (gobject.SIGNAL_ACTION,
+                     gobject.TYPE_BOOLEAN,
+                     (gobject.TYPE_STRING,)),
         }
 
     def _toggle(self, rend, path, colnum):
@@ -60,13 +64,14 @@ class KeyedListWidget(gtk.HBox):
 
     def _make_view(self):
         colnum = -1
-    
+
         for typ, cap in self.columns:
             colnum += 1
             if colnum == 0:
-                continue # Key column
-    
-            if typ in [gobject.TYPE_STRING, gobject.TYPE_INT, gobject.TYPE_FLOAT]:
+                continue  # Key column
+
+            if typ in [gobject.TYPE_STRING, gobject.TYPE_INT,
+                       gobject.TYPE_FLOAT]:
                 rend = gtk.CellRendererText()
                 rend.set_property("ellipsize", pango.ELLIPSIZE_END)
                 column = gtk.TreeViewColumn(cap, rend, text=colnum)
@@ -76,10 +81,10 @@ class KeyedListWidget(gtk.HBox):
                 column = gtk.TreeViewColumn(cap, rend, active=colnum)
             else:
                 raise Exception("Unsupported type %s" % typ)
-            
+
             column.set_sort_column_id(colnum)
             self.__view.append_column(column)
-    
+
         self.__view.connect("button_press_event", self._mouse)
 
     def set_item(self, key, *values):
@@ -91,11 +96,11 @@ class KeyedListWidget(gtk.HBox):
                 self.__store.remove(iter)
                 return
             iter = self.__store.iter_next(iter)
-    
+
         self.__store.append(row=(key,) + values)
 
         self.emit("item-set", key)
-    
+
     def get_item(self, key):
         iter = self.__store.get_iter_first()
         while iter:
@@ -103,9 +108,9 @@ class KeyedListWidget(gtk.HBox):
             if vals[0] == key:
                 return vals
             iter = self.__store.iter_next(iter)
-    
+
         return None
-    
+
     def del_item(self, key):
         iter = self.__store.get_iter_first()
         while iter:
@@ -115,18 +120,18 @@ class KeyedListWidget(gtk.HBox):
                 return True
 
             iter = self.__store.iter_next(iter)
-    
+
         return False
-    
+
     def has_item(self, key):
         return self.get_item(key) is not None
-    
+
     def get_selected(self):
         try:
             (store, iter) = self.__view.get_selection().get_selected()
             return store.get(iter, 0)[0]
         except Exception, e:
-            print "Unable to find selected: %s" % e
+            LOG.error("Unable to find selected: %s" % e)
             return None
 
     def select_item(self, key):
@@ -145,7 +150,7 @@ class KeyedListWidget(gtk.HBox):
             iter = self.__store.iter_next(iter)
 
         return False
-        
+
     def get_keys(self):
         keys = []
         iter = self.__store.get_iter_first()
@@ -158,16 +163,16 @@ class KeyedListWidget(gtk.HBox):
 
     def __init__(self, columns):
         gtk.HBox.__init__(self, True, 0)
-    
+
         self.columns = columns
-    
-        types = tuple([x for x,y in columns])
-    
+
+        types = tuple([x for x, y in columns])
+
         self.__store = gtk.ListStore(*types)
         self.__view = gtk.TreeView(self.__store)
-    
+
         self.pack_start(self.__view, 1, 1, 1)
-    
+
         self.__toggle_connected = False
 
         self._make_view()
@@ -176,7 +181,7 @@ class KeyedListWidget(gtk.HBox):
     def connect(self, signame, *args):
         if signame == "item-toggled":
             self.__toggle_connected = True
-        
+
         gtk.HBox.connect(self, signame, *args)
 
     def set_editable(self, column, is_editable):
@@ -194,14 +199,15 @@ class KeyedListWidget(gtk.HBox):
     def get_renderer(self, colnum):
         return self.__view.get_column(colnum).get_cell_renderers()[0]
 
+
 class ListWidget(gtk.HBox):
     __gsignals__ = {
-        "click-on-list" : (gobject.SIGNAL_RUN_LAST,
-                           gobject.TYPE_NONE,
-                           (gtk.TreeView, gtk.gdk.Event)),
-        "item-toggled" : (gobject.SIGNAL_RUN_LAST,
+        "click-on-list": (gobject.SIGNAL_RUN_LAST,
                           gobject.TYPE_NONE,
-                          (gobject.TYPE_PYOBJECT,)),
+                          (gtk.TreeView, gtk.gdk.Event)),
+        "item-toggled": (gobject.SIGNAL_RUN_LAST,
+                         gobject.TYPE_NONE,
+                         (gobject.TYPE_PYOBJECT,)),
         }
 
     store_type = gtk.ListStore
@@ -251,7 +257,7 @@ class ListWidget(gtk.HBox):
         # pylint: disable-msg=W0612
         col_types = tuple([x for x, y in columns])
         self._ncols = len(col_types)
-        
+
         self._store = self.store_type(*col_types)
         self._view = None
         self.make_view(columns)
@@ -295,7 +301,7 @@ class ListWidget(gtk.HBox):
             (lst, iter) = self._view.get_selection().get_selected()
             lst.remove(iter)
         except Exception, e:
-            print "Unable to remove selected: %s" % e
+            LOG.error("Unable to remove selected: %s" % e)
 
     def get_selected(self, take_default=False):
         (lst, iter) = self._view.get_selection().get_selected()
@@ -338,6 +344,7 @@ class ListWidget(gtk.HBox):
         for i in lst:
             self.add_item(*i)
 
+
 class TreeWidget(ListWidget):
     store_type = gtk.TreeStore
 
@@ -412,7 +419,7 @@ class TreeWidget(ListWidget):
         elif isinstance(vals, tuple):
             self._add_item(parent, *vals)
         else:
-            print "Unknown type: %s" % vals
+            LOG.error("Unknown type: %s" % vals)
 
     def set_values(self, vals):
         self._store.clear()
@@ -452,6 +459,7 @@ class TreeWidget(ListWidget):
         else:
             raise Exception("Item not found")
 
+
 class ProgressDialog(gtk.Window):
     def __init__(self, title, parent=None):
         gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
@@ -471,7 +479,7 @@ class ProgressDialog(gtk.Window):
 
         self.pbar = gtk.ProgressBar()
         self.pbar.show()
-        
+
         vbox.pack_start(self.label, 0, 0, 0)
         vbox.pack_start(self.pbar, 0, 0, 0)
 
@@ -493,6 +501,7 @@ class ProgressDialog(gtk.Window):
         while gtk.events_pending():
             gtk.main_iteration_do(False)
 
+
 class LatLonEntry(gtk.Entry):
     def __init__(self, *args):
         gtk.Entry.__init__(self, *args)
@@ -526,7 +535,7 @@ class LatLonEntry(gtk.Entry):
     def parse_dm(self, string):
         string = string.strip()
         string = string.replace('  ', ' ')
-        
+
         (_degrees, _minutes) = string.split(' ', 2)
 
         degrees = int(_degrees)
@@ -565,7 +574,7 @@ class LatLonEntry(gtk.Entry):
         degrees = int(deg)
         minutes = int(mns)
         seconds = float(sec)
-        
+
         return degrees + (minutes / 60.0) + (seconds / 3600.0)
 
     def value(self):
@@ -580,7 +589,7 @@ class LatLonEntry(gtk.Entry):
                 try:
                     return self.parse_dms(string)
                 except Exception, e:
-                    print "DMS: %s" % e
+                    LOG.error("DMS: %s" % e)
 
         raise Exception("Invalid format")
 
@@ -591,6 +600,7 @@ class LatLonEntry(gtk.Entry):
         except:
             return False
 
+
 class YesNoDialog(gtk.Dialog):
     def __init__(self, title="", parent=None, buttons=None):
         gtk.Dialog.__init__(self, title=title, parent=parent, buttons=buttons)
@@ -604,6 +614,7 @@ class YesNoDialog(gtk.Dialog):
     def set_text(self, text):
         self._label.set_text(text)
 
+
 def make_choice(options, editable=True, default=None):
     if editable:
         sel = gtk.combo_box_entry_new_text()
@@ -622,9 +633,10 @@ def make_choice(options, editable=True, default=None):
 
     return sel
 
+
 class FilenameBox(gtk.HBox):
     __gsignals__ = {
-        "filename-changed" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+        "filename-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
         }
 
     def do_browse(self, _, dir):
@@ -663,7 +675,8 @@ class FilenameBox(gtk.HBox):
         self.filename.set_text(fn)
 
     def get_filename(self):
-        return self.filename.get_text()    
+        return self.filename.get_text()
+
 
 def make_pixbuf_choice(options, default=None):
     store = gtk.ListStore(gtk.gdk.Pixbuf, gobject.TYPE_STRING)
@@ -689,14 +702,15 @@ def make_pixbuf_choice(options, default=None):
 
     return box
 
+
 def test():
     win = gtk.Window(gtk.WINDOW_TOPLEVEL)
     lst = ListWidget([(gobject.TYPE_STRING, "Foo"),
-                    (gobject.TYPE_BOOLEAN, "Bar")])
+                      (gobject.TYPE_BOOLEAN, "Bar")])
 
     lst.add_item("Test1", True)
     lst.set_values([("Test2", True), ("Test3", False)])
-    
+
     lst.show()
     win.add(lst)
     win.show()
@@ -714,10 +728,8 @@ def test():
     lst = TreeWidget([(gobject.TYPE_STRING, "Id"),
                       (gobject.TYPE_STRING, "Value")],
                      1)
-    #l.add_item(None, "Foo", "Bar")
-    #l.add_item("Foo", "Bar", "Baz")
-    lst.set_values({"Fruit" : [("Apple", "Red"), ("Orange", "Orange")],
-                    "Pizza" : [("Cheese", "Simple"), ("Pepperoni", "Yummy")]})
+    lst.set_values({"Fruit": [("Apple", "Red"), ("Orange", "Orange")],
+                    "Pizza": [("Cheese", "Simple"), ("Pepperoni", "Yummy")]})
     lst.add_item("Fruit", "Bananna", "Yellow")
     lst.show()
     win3.add(lst)
@@ -739,5 +751,6 @@ def test():
 
     print lst.get_values()
 
+
 if __name__ == "__main__":
     test()
diff --git a/chirpui/radiobrowser.py b/chirp/ui/radiobrowser.py
similarity index 93%
rename from chirpui/radiobrowser.py
rename to chirp/ui/radiobrowser.py
index db96b04..971a4d6 100644
--- a/chirpui/radiobrowser.py
+++ b/chirp/ui/radiobrowser.py
@@ -3,9 +3,15 @@ import gobject
 import pango
 import re
 import os
+import logging
 
 from chirp import bitwise
-from chirpui import common
+from chirp.ui import common, config
+
+LOG = logging.getLogger(__name__)
+
+CONF = config.get()
+
 
 def do_insert_line_with_tags(b, line):
     def i(text, *tags):
@@ -61,6 +67,7 @@ def do_insert_line_with_tags(b, line):
 
     i(line)
 
+
 def do_insert_with_tags(buf, text):
     buf.set_text('')
     lines = text.split(os.linesep)
@@ -68,18 +75,31 @@ def do_insert_with_tags(buf, text):
         do_insert_line_with_tags(buf, line)
         buf.insert_with_tags_by_name(buf.get_end_iter(), os.linesep)
 
+
 def classname(obj):
     return str(obj.__class__).split('.')[-1]
 
+
 def bitwise_type(classname):
     return classname.split("DataElement")[0]
 
+
 class FixedEntry(gtk.Entry):
     def __init__(self, *args, **kwargs):
         super(FixedEntry, self).__init__(*args, **kwargs)
-        fontdesc = pango.FontDescription("Courier bold 10")
+
+        try:
+            fontsize = CONF.get_int("browser_fontsize", "developer")
+        except Exception:
+            fontsize = 10
+        if fontsize < 4 or fontsize > 144:
+            LOG.warn("Unsupported browser_fontsize %i. Using 10." % fontsize)
+            fontsize = 11
+
+        fontdesc = pango.FontDescription("Courier bold %i" % fontsize)
         self.modify_font(fontdesc)
 
+
 class IntegerEntry(FixedEntry):
     def _colorize(self, _self):
         value = self.get_text()
@@ -95,12 +115,14 @@ class IntegerEntry(FixedEntry):
         super(IntegerEntry, self).__init__(*args, **kwargs)
         self.connect("changed", self._colorize)
 
+
 class BitwiseEditor(gtk.HBox):
     def __init__(self, element):
         super(BitwiseEditor, self).__init__(False, 3)
         self._element = element
         self._build_ui()
 
+
 class IntegerEditor(BitwiseEditor):
     def _changed(self, entry, base):
         if not self._update:
@@ -139,6 +161,7 @@ class IntegerEditor(BitwiseEditor):
             ent.show()
         self._update_entries()
 
+
 class BCDArrayEditor(BitwiseEditor):
     def _changed(self, entry, hexent):
         self._element.set_value(int(entry.get_text()))
@@ -172,6 +195,7 @@ class BCDArrayEditor(BitwiseEditor):
         ent.connect('changed', self._changed, hexent)
         self._format_hexent(hexent)
 
+
 class CharArrayEditor(BitwiseEditor):
     def _changed(self, entry):
         self._element.set_value(entry.get_text().ljust(len(self._element)))
@@ -183,6 +207,7 @@ class CharArrayEditor(BitwiseEditor):
         ent.show()
         self.pack_start(ent, 1, 1, 1)
 
+
 class OtherEditor(BitwiseEditor):
     def _build_ui(self):
         name = classname(self._element)
@@ -196,6 +221,7 @@ class OtherEditor(BitwiseEditor):
         l.show()
         self.pack_start(l, 1, 1, 1)
 
+
 class RadioBrowser(common.Editor):
     def _build_ui(self):
         self._display = gtk.Table(20, 2)
@@ -211,10 +237,12 @@ class RadioBrowser(common.Editor):
 
         self.root = gtk.HBox(False, 3)
         sw = gtk.ScrolledWindow()
+        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
         sw.add(self._tree)
         sw.show()
         self.root.pack_start(sw, 0, 0, 0)
         sw = gtk.ScrolledWindow()
+        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
         sw.add_with_viewport(self._display)
         sw.show()
         self.root.pack_start(sw, 1, 1, 1)
@@ -274,7 +302,7 @@ class RadioBrowser(common.Editor):
             pack(l, 0)
 
             if (isinstance(item, bitwise.intDataElement) or
-                isinstance(item, bitwise.bcdDataElement)):
+                    isinstance(item, bitwise.bcdDataElement)):
                 e = IntegerEditor(item)
             elif (isinstance(item, bitwise.arrayDataElement) and
                   isinstance(item[0], bitwise.bcdDataElement)):
@@ -288,7 +316,6 @@ class RadioBrowser(common.Editor):
             pack(e, 1)
             next_row()
 
-
     def __init__(self, rthread):
         super(RadioBrowser, self).__init__(rthread)
         self._radio = rthread.radio
@@ -304,14 +331,17 @@ class RadioBrowser(common.Editor):
             self.emit("changed")
             self._focused = False
 
+
 if __name__ == "__main__":
-    from chirp import *
+    from chirp.drivers import *
     from chirp import directory
     import sys
 
     r = directory.get_radio_by_image(sys.argv[1])
+
     class Foo:
         radio = r
+
     w = gtk.Window()
     b = RadioBrowser(Foo)
     w.set_default_size(1024, 768)
diff --git a/chirpui/reporting.py b/chirp/ui/reporting.py
similarity index 83%
rename from chirpui/reporting.py
rename to chirp/ui/reporting.py
index c48c6bb..6908e10 100644
--- a/chirpui/reporting.py
+++ b/chirp/ui/reporting.py
@@ -28,49 +28,50 @@
 import threading
 import os
 import time
+import logging
 
 from chirp import CHIRP_VERSION, platform
 
 REPORT_URL = "http://chirp.danplanet.com/report/report.php?do_report"
 ENABLED = True
-DEBUG = os.getenv("CHIRP_DEBUG") == "y"
-THREAD_SEM = threading.Semaphore(10) # Maximum number of outstanding threads
+THREAD_SEM = threading.Semaphore(10)  # Maximum number of outstanding threads
 LAST = 0
 LAST_TYPE = None
 
+LOG = logging.getLogger(__name__)
+
 try:
     # Don't let failure to import any of these modules cause trouble
-    from chirpui import config
+    from chirp.ui import config
     import xmlrpclib
 except:
     ENABLED = False
 
-def debug(string):
-    if DEBUG:
-        print string
 
 def should_report():
     if not ENABLED:
-        debug("Not reporting due to recent failure")
+        LOG.info("Not reporting due to recent failure")
         return False
 
     conf = config.get()
     if conf.get_bool("no_report"):
-        debug("Reporting disabled")
+        LOG.info("Reporting disabled")
         return False
 
     return True
 
+
 def _report_model_usage(model, direction, success):
     global ENABLED
-    if direction not in ["live", "download", "upload", "import", "export", "importsrc"]:
-        print "Invalid direction `%s'" % direction
-        return True # This is a bug, but not fatal
+    if direction not in ["live", "download", "upload",
+                         "import", "export", "importsrc"]:
+        LOG.warn("Invalid direction `%s'" % direction)
+        return True  # This is a bug, but not fatal
 
     model = "%s_%s" % (model.VENDOR, model.MODEL)
     data = "%s,%s,%s" % (model, direction, success)
 
-    debug("Reporting model usage: %s" % data)
+    LOG.debug("Reporting model usage: %s" % data)
 
     proxy = xmlrpclib.ServerProxy(REPORT_URL)
     id = proxy.report_stats(CHIRP_VERSION,
@@ -81,10 +82,11 @@ def _report_model_usage(model, direction, success):
     # If the server returns zero, it wants us to shut up
     return id != 0
 
+
 def _report_exception(stack):
     global ENABLED
 
-    debug("Reporting exception")
+    LOG.debug("Reporting exception")
 
     proxy = xmlrpclib.ServerProxy(REPORT_URL)
     id = proxy.report_exception(CHIRP_VERSION,
@@ -95,10 +97,11 @@ def _report_exception(stack):
     # If the server returns zero, it wants us to shut up
     return id != 0
 
+
 def _report_misc_error(module, data):
     global ENABLED
 
-    debug("Reporting misc error with %s" % module)
+    LOG.debug("Reporting misc error with %s" % module)
 
     proxy = xmlrpclib.ServerProxy(REPORT_URL)
     id = proxy.report_misc_error(CHIRP_VERSION,
@@ -108,16 +111,18 @@ def _report_misc_error(module, data):
     # If the server returns zero, it wants us to shut up
     return id != 0
 
+
 def _check_for_updates(callback):
-    debug("Checking for updates")
+    LOG.debug("Checking for updates")
     proxy = xmlrpclib.ServerProxy(REPORT_URL)
     ver = proxy.check_for_updates(CHIRP_VERSION,
                                   platform.get_platform().os_version_string())
 
-    debug("Server reports version %s is latest" % ver)
+    LOG.debug("Server reports version %s is latest" % ver)
     callback(ver)
     return True
 
+
 class ReportThread(threading.Thread):
     def __init__(self, func, *args):
         threading.Thread.__init__(self)
@@ -128,9 +133,9 @@ class ReportThread(threading.Thread):
         try:
             return self.__func(*self.__args)
         except Exception, e:
-            debug("Failed to report: %s" % e)
+            LOG.debug("Failed to report: %s" % e)
             return False
-        
+
     def run(self):
         start = time.time()
         result = self._run()
@@ -139,25 +144,26 @@ class ReportThread(threading.Thread):
             ENABLED = False
         elif (time.time() - start) > 15:
             # Reporting took too long
-            debug("Time to report was %.2f sec -- Disabling" % \
+            LOG.debug("Time to report was %.2f sec -- Disabling" %
                       (time.time()-start))
             ENABLED = False
 
         THREAD_SEM.release()
 
+
 def dispatch_thread(func, *args):
     global LAST
     global LAST_TYPE
 
     # If reporting is disabled or failing, bail
     if not should_report():
-        debug("Reporting is disabled")
+        LOG.debug("Reporting is disabled")
         return
 
     # If the time between now and the last report is less than 5 seconds, bail
     delta = time.time() - LAST
     if delta < 5 and func == LAST_TYPE:
-        debug("Throttling...")
+        LOG.debug("Throttling...")
         return
 
     LAST = time.time()
@@ -165,21 +171,25 @@ def dispatch_thread(func, *args):
 
     # If there are already too many threads running, bail
     if not THREAD_SEM.acquire(False):
-        debug("Too many threads already running")
+        LOG.debug("Too many threads already running")
         return
 
     t = ReportThread(func, *args)
     t.start()
 
+
 def report_model_usage(model, direction, success):
     dispatch_thread(_report_model_usage, model, direction, success)
 
+
 def report_exception(stack):
     dispatch_thread(_report_exception, stack)
 
+
 def report_misc_error(module, data):
     dispatch_thread(_report_misc_error, module, data)
 
+
 # Calls callback with the latest version
 def check_for_updates(callback):
     dispatch_thread(_check_for_updates, callback)
diff --git a/chirp/ui/settingsedit.py b/chirp/ui/settingsedit.py
new file mode 100644
index 0000000..4d50048
--- /dev/null
+++ b/chirp/ui/settingsedit.py
@@ -0,0 +1,224 @@
+# Copyright 2012 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 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 gtk
+import gobject
+import logging
+
+from chirp import chirp_common
+from chirp import settings
+from chirp.ui import common, miscwidgets
+
+LOG = logging.getLogger(__name__)
+
+
+class RadioSettingProxy(settings.RadioSetting):
+    def __init__(self, setting, editor):
+        self._setting = setting
+        self._editor = editor
+
+
+class SettingsEditor(common.Editor):
+    def __init__(self, rthread):
+        super(SettingsEditor, self).__init__(rthread)
+
+        # The main box
+        self.root = gtk.HBox(False, 0)
+
+        # The pane
+        paned = gtk.HPaned()
+        paned.show()
+        self.root.pack_start(paned, 1, 1, 0)
+
+        # The selection tree
+        self._store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_INT)
+        self._view = gtk.TreeView(self._store)
+        self._view.set_size_request(150, -1)
+        self._view.get_selection().connect("changed", self._view_changed_cb)
+        self._view.append_column(
+            gtk.TreeViewColumn("", gtk.CellRendererText(), text=0))
+        self._view.show()
+        paned.pack1(self._view)
+
+        # The settings notebook
+        self._notebook = gtk.Notebook()
+        self._notebook.set_show_tabs(False)
+        self._notebook.set_show_border(False)
+        self._notebook.show()
+        paned.pack2(self._notebook)
+
+        self._changed = False
+        self._settings = None
+
+        job = common.RadioJob(self._get_settings_cb, "get_settings")
+        job.set_desc("Getting radio settings")
+        self.rthread.submit(job)
+
+    def _save_settings(self):
+        if self._settings is None:
+            return
+
+        def setting_cb(result):
+            if isinstance(result, Exception):
+                common.show_error(_("Error in setting value: %s") % result)
+            elif self._changed:
+                self.emit("changed")
+                self._changed = False
+
+        job = common.RadioJob(setting_cb, "set_settings",
+                              self._settings)
+        job.set_desc("Setting radio settings")
+        self.rthread.submit(job)
+
+    def _do_save_setting(self, widget, value):
+        if isinstance(value, settings.RadioSettingValueInteger):
+            value.set_value(widget.get_adjustment().get_value())
+        elif isinstance(value, settings.RadioSettingValueFloat):
+            value.set_value(widget.get_text())
+        elif isinstance(value, settings.RadioSettingValueBoolean):
+            value.set_value(widget.get_active())
+        elif isinstance(value, settings.RadioSettingValueList):
+            value.set_value(widget.get_active_text())
+        elif isinstance(value, settings.RadioSettingValueString):
+            value.set_value(widget.get_text())
+        else:
+            LOG.error("Unsupported widget type %s for %s" %
+                      (element.value.__class__, element.get_name()))
+
+        self._changed = True
+        self._save_settings()
+
+    def _save_setting(self, widget, value):
+        try:
+            self._do_save_setting(widget, value)
+        except settings.InvalidValueError, e:
+            common.show_error(_("Invalid setting value: %s") % e)
+
+    def _build_ui_tab(self, group):
+
+        # The scrolled window
+        sw = gtk.ScrolledWindow()
+        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+        sw.show()
+
+        # Notebook tab
+        tab = self._notebook.append_page(sw, gtk.Label(_(group.get_name())))
+
+        # Settings table
+        table = gtk.Table(len(group), 2, False)
+        table.set_resize_mode(gtk.RESIZE_IMMEDIATE)
+        table.show()
+        sw.add_with_viewport(table)
+
+        row = 0
+        for element in group:
+            if not isinstance(element, settings.RadioSetting):
+                continue
+
+            # Label
+            label = gtk.Label(element.get_shortname() + ":")
+            label.set_alignment(0.0, 0.5)
+            label.show()
+
+            table.attach(label, 0, 1, row, row + 1,
+                         xoptions=gtk.FILL, yoptions=0,
+                         xpadding=6, ypadding=3)
+
+            if isinstance(element.value, list) and \
+                    isinstance(element.value[0],
+                               settings.RadioSettingValueInteger):
+                box = gtk.HBox(True)
+            else:
+                box = gtk.VBox(True)
+
+            # Widget container
+            box.show()
+            table.attach(box, 1, 2, row, row + 1,
+                         xoptions=gtk.FILL, yoptions=0,
+                         xpadding=12, ypadding=3)
+
+            for i in element.keys():
+                value = element[i]
+                if isinstance(value, settings.RadioSettingValueInteger):
+                    widget = gtk.SpinButton()
+                    adj = widget.get_adjustment()
+                    adj.configure(value.get_value(),
+                                  value.get_min(), value.get_max(),
+                                  value.get_step(), 1, 0)
+                    widget.connect("value-changed", self._save_setting, value)
+                elif isinstance(value, settings.RadioSettingValueFloat):
+                    widget = gtk.Entry()
+                    widget.set_width_chars(16)
+                    widget.set_text(value.format())
+                    widget.connect("focus-out-event", lambda w, e, v:
+                                   self._save_setting(w, v), value)
+                elif isinstance(value, settings.RadioSettingValueBoolean):
+                    widget = gtk.CheckButton(_("Enabled"))
+                    widget.set_active(value.get_value())
+                    widget.connect("toggled", self._save_setting, value)
+                elif isinstance(value, settings.RadioSettingValueList):
+                    widget = miscwidgets.make_choice([], editable=False)
+                    model = widget.get_model()
+                    model.clear()
+                    for option in value.get_options():
+                        widget.append_text(option)
+                    current = value.get_value()
+                    index = value.get_options().index(current)
+                    widget.set_active(index)
+                    widget.connect("changed", self._save_setting, value)
+                elif isinstance(value, settings.RadioSettingValueString):
+                    widget = gtk.Entry()
+                    widget.set_width_chars(32)
+                    widget.set_text(str(value).rstrip())
+                    widget.connect("changed", self._save_setting, value)
+                else:
+                    LOG.error("Unsupported widget type: %s" % value.__class__)
+
+                widget.set_sensitive(value.get_mutable())
+                widget.show()
+
+                box.pack_start(widget, 1, 1, 1)
+
+            row += 1
+
+        return tab
+
+    def _build_ui_group(self, group, parent):
+        tab = self._build_ui_tab(group)
+
+        iter = self._store.append(parent)
+        self._store.set(iter, 0, group.get_shortname(), 1, tab)
+
+        for element in group:
+            if not isinstance(element, settings.RadioSetting):
+                self._build_ui_group(element, iter)
+
+    def _build_ui(self, settings):
+        if not isinstance(settings, list):
+            raise Exception("Invalid Radio Settings")
+            return
+
+        self._settings = settings
+        for group in settings:
+            self._build_ui_group(group, None)
+        self._view.expand_all()
+
+    def _get_settings_cb(self, settings):
+        gobject.idle_add(self._build_ui, settings)
+
+    def _view_changed_cb(self, selection):
+        (lst, iter) = selection.get_selected()
+        tab, = self._store.get(iter, 1)
+        self._notebook.set_current_page(tab)
diff --git a/chirpui/shiftdialog.py b/chirp/ui/shiftdialog.py
similarity index 93%
rename from chirpui/shiftdialog.py
rename to chirp/ui/shiftdialog.py
index 0ca50a4..e971611 100644
--- a/chirpui/shiftdialog.py
+++ b/chirp/ui/shiftdialog.py
@@ -16,11 +16,14 @@
 
 import gtk
 import gobject
-
 import threading
+import logging
 
 from chirp import errors, chirp_common
 
+LOG = logging.getLogger(__name__)
+
+
 class ShiftDialog(gtk.Dialog):
     def __init__(self, rthread, parent=None):
         gtk.Dialog.__init__(self,
@@ -59,7 +62,7 @@ class ShiftDialog(gtk.Dialog):
             src = i.number
             dst = src + delta
 
-            print "Moving %i to %i" % (src, dst)
+            LOG.info("Moving %i to %i" % (src, dst))
             self.status(_("Moving {src} to {dst}").format(src=src,
                                                           dst=dst),
                         count / len(memories))
@@ -80,7 +83,7 @@ class ShiftDialog(gtk.Dialog):
 
         pos = start
         while pos <= ulimit:
-            self.status(_("Looking for a free spot ({number})").format(\
+            self.status(_("Looking for a free spot ({number})").format(
                         number=pos), 0)
             try:
                 mem = self.rthread.radio.get_memory(pos)
@@ -95,7 +98,7 @@ class ShiftDialog(gtk.Dialog):
         if pos > ulimit and not endokay:
             raise errors.InvalidMemoryLocation(_("No space to insert a row"))
 
-        print "Found a hole: %i" % pos
+        LOG.debug("Found a hole: %i" % pos)
 
         return mems
 
@@ -109,7 +112,7 @@ class ShiftDialog(gtk.Dialog):
                 self.rthread.radio.erase_memory(start)
             return ret
         else:
-            print "No memory list?"
+            LOG.warn("No memory list?")
             return 0
 
     def _delete_hole(self, start, all=False):
@@ -119,14 +122,15 @@ class ShiftDialog(gtk.Dialog):
             self.rthread.radio.erase_memory(count+start)
             return count
         else:
-            print "No memory list?"
+            LOG.warn("No memory list?")
             return 0
 
     def finished(self):
         if self.quiet:
             gobject.idle_add(self.response, gtk.RESPONSE_OK)
         else:
-            gobject.idle_add(self.set_response_sensitive, gtk.RESPONSE_OK, True)
+            gobject.idle_add(self.set_response_sensitive,
+                             gtk.RESPONSE_OK, True)
 
     def threadfn(self, newhole, func, *args):
         self.status("Waiting for radio to become available", 0)
diff --git a/chirp/util.py b/chirp/util.py
index de17e11..1fc5529 100644
--- a/chirp/util.py
+++ b/chirp/util.py
@@ -15,34 +15,43 @@
 
 import struct
 
-def hexprint(data):
+
+def hexprint(data, addrfmt=None):
     """Return a hexdump-like encoding of @data"""
-    line_sz = 8
+    if addrfmt is None:
+        addrfmt = '%(addr)03i'
+
+    block_size = 8
 
-    lines = len(data) / line_sz
-    
-    if (len(data) % line_sz) != 0:
+    lines = len(data) / block_size
+
+    if (len(data) % block_size) != 0:
         lines += 1
-        data += "\x00" * ((lines * line_sz) - len(data))
+        data += "\x00" * ((lines * block_size) - len(data))
 
     out = ""
-        
-    for i in range(0, (len(data)/line_sz)):
-        out += "%03i: " % (i * line_sz)
 
-        left = len(data) - (i * line_sz)
-        if left < line_sz:
+    for block in range(0, (len(data)/block_size)):
+        addr = block * block_size
+        try:
+            out += addrfmt % locals()
+        except (OverflowError, ValueError, TypeError, KeyError):
+            out += "%03i" % addr
+        out += ': '
+
+        left = len(data) - (block * block_size)
+        if left < block_size:
             limit = left
         else:
-            limit = line_sz
-            
+            limit = block_size
+
         for j in range(0, limit):
-            out += "%02x " % ord(data[(i * line_sz) + j])
+            out += "%02x " % ord(data[(block * block_size) + j])
 
         out += "  "
 
         for j in range(0, limit):
-            char = data[(i * line_sz) + j]
+            char = data[(block * block_size) + j]
 
             if ord(char) > 0x20 and ord(char) < 0x7E:
                 out += "%s" % char
@@ -53,6 +62,7 @@ def hexprint(data):
 
     return out
 
+
 def bcd_encode(val, bigendian=True, width=None):
     """This is really old and shouldn't be used anymore"""
     digits = []
@@ -69,14 +79,15 @@ def bcd_encode(val, bigendian=True, width=None):
         digits.append(0)
 
     for i in range(0, len(digits), 2):
-        newval = struct.pack("B", (digits[i+1] << 4) | digits[i])
+        newval = struct.pack("B", (digits[i + 1] << 4) | digits[i])
         if bigendian:
-            result =  newval + result
+            result = newval + result
         else:
             result = result + newval
-    
+
     return result
 
+
 def get_dict_rev(thedict, value):
     """Return the first matching key for a given @value in @dict"""
     _dict = {}
diff --git a/chirp/xml_ll.py b/chirp/xml_ll.py
index 28ec333..e3bac20 100644
--- a/chirp/xml_ll.py
+++ b/chirp/xml_ll.py
@@ -17,6 +17,7 @@ import re
 
 from chirp import chirp_common, errors
 
+
 def get_memory(doc, number):
     """Extract a Memory object from @doc"""
     ctx = doc.xpathNewContext()
@@ -59,7 +60,7 @@ def get_memory(doc, number):
     mem.ctone = float(_get("/squelch[@id='ctone']/tone/text()"))
     mem.dtcs = int(_get("/squelch[@id='dtcs']/code/text()"), 10)
     mem.dtcs_polarity = _get("/squelch[@id='dtcs']/polarity/text()")
-    
+
     try:
         sql = _get("/squelchSetting/text()")
         if sql == "rtone":
@@ -73,7 +74,7 @@ def get_memory(doc, number):
     except IndexError:
         mem.tmode = ""
 
-    dmap = {"positive" : "+", "negative" : "-", "none" : ""}
+    dmap = {"positive": "+", "negative": "-", "none": ""}
     dupx = _get("/duplex/text()")
     mem.duplex = dmap.get(dupx, "")
 
@@ -87,16 +88,17 @@ def get_memory(doc, number):
     else:
         mem.skip = skip
 
-    #FIXME: bank support in .chirp files needs to be re-written
-    #bank_id = _get("/bank/@bankId")
-    #if bank_id:
-    #    mem.bank = int(bank_id)
-    #    bank_index = _get("/bank/@bankIndex")
-    #    if bank_index:
-    #        mem.bank_index = int(bank_index)
+    # FIXME: bank support in .chirp files needs to be re-written
+    # bank_id = _get("/bank/@bankId")
+    # if bank_id:
+    #     mem.bank = int(bank_id)
+    #     bank_index = _get("/bank/@bankIndex")
+    #     if bank_index:
+    #         mem.bank_index = int(bank_index)
 
     return mem
 
+
 def set_memory(doc, mem):
     """Set @mem in @doc"""
     ctx = doc.xpathNewContext()
@@ -121,11 +123,11 @@ def set_memory(doc, mem):
     lname_filter = "[^.A-Za-z0-9/ >-]"
     lname = memnode.newChild(None, "longName", None)
     lname.addContent(re.sub(lname_filter, "", mem.name[:16]))
-    
+
     freq = memnode.newChild(None, "frequency", None)
     freq.newProp("units", "MHz")
     freq.addContent(chirp_common.format_freq(mem.freq))
-    
+
     rtone = memnode.newChild(None, "squelch", None)
     rtone.newProp("id", "rtone")
     rtone.newProp("type", "repeater")
@@ -154,7 +156,7 @@ def set_memory(doc, mem):
     elif mem.tmode == "DTCS":
         sset.addContent("dtcs")
 
-    dmap = {"+" : "positive", "-" : "negative", "" : "none"}
+    dmap = {"+": "positive", "-": "negative", "": "none"}
     dupx = memnode.newChild(None, "duplex", None)
     dupx.addContent(dmap[mem.duplex])
 
@@ -168,17 +170,17 @@ def set_memory(doc, mem):
     step = memnode.newChild(None, "tuningStep", None)
     step.newProp("units", "kHz")
     step.addContent("%.5f" % mem.tuning_step)
-    
+
     if mem.skip:
         skip = memnode.newChild(None, "skip", None)
         skip.addContent(mem.skip)
 
-    #FIXME: .chirp bank support needs to be redone
-    #if mem.bank is not None:
-    #    bank = memnode.newChild(None, "bank", None)
-    #    bank.newProp("bankId", str(int(mem.bank)))
-    #    if mem.bank_index >= 0:
-    #        bank.newProp("bankIndex", str(int(mem.bank_index)))
+    # FIXME: .chirp bank support needs to be redone
+    # if mem.bank is not None:
+    #     bank = memnode.newChild(None, "bank", None)
+    #     bank.newProp("bankId", str(int(mem.bank)))
+    #     if mem.bank_index >= 0:
+    #         bank.newProp("bankIndex", str(int(mem.bank_index)))
 
     if isinstance(mem, chirp_common.DVMemory):
         dv = memnode.newChild(None, "dv", None)
@@ -197,6 +199,7 @@ def set_memory(doc, mem):
         dc = dv.newChild(None, "digitalCode", None)
         dc.addContent(str(mem.dv_code))
 
+
 def del_memory(doc, number):
     """Remove memory @number from @doc"""
     path = "//radio/memories/memory[@location=%i]" % number
@@ -205,13 +208,15 @@ def del_memory(doc, number):
 
     for field in fields:
         field.unlinkNode()
-    
+
+
 def _get_bank(node):
     bank = chirp_common.Bank(node.prop("label"))
     ident = int(node.prop("id"))
 
     return ident, bank
 
+
 def get_banks(doc):
     """Return a list of banks from @doc"""
     path = "//radio/banks/bank"
@@ -229,6 +234,7 @@ def get_banks(doc):
 
     return [x[1] for x in banks]
 
+
 def set_banks(doc, banklist):
     """Set the list of banks in @doc"""
     path = "//radio/banks/bank"
diff --git a/chirpui/settingsedit.py b/chirpui/settingsedit.py
deleted file mode 100644
index 535296b..0000000
--- a/chirpui/settingsedit.py
+++ /dev/null
@@ -1,236 +0,0 @@
-# Copyright 2012 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 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 gtk
-import gobject
-
-from chirp import chirp_common, settings
-from chirpui import common, miscwidgets
-
-class RadioSettingProxy(settings.RadioSetting):
-    def __init__(self, setting, editor):
-        self._setting = setting
-        self._editor = editor
-
-class SettingsEditor(common.Editor):
-    def __init__(self, rthread):
-        super(SettingsEditor, self).__init__(rthread)
-        self._changed = False
-        self.root = gtk.HBox(False, 10)
-        self._store = gtk.TreeStore(gobject.TYPE_STRING,
-                                    gobject.TYPE_PYOBJECT)
-        self._view = gtk.TreeView(self._store)
-        self._view.set_size_request(150, -1)
-        self._view.connect("button-press-event", self._group_selected)
-        self._view.show()
-        self.root.pack_start(self._view, 0, 0, 0)
-
-        col = gtk.TreeViewColumn("", gtk.CellRendererText(), text=0)
-        self._view.append_column(col)
-
-        self._table = gtk.Table(20, 3)
-        self._table.set_col_spacings(10)
-        self._table.show()
-
-        sw = gtk.ScrolledWindow()
-        sw.add_with_viewport(self._table)
-        sw.show()
-
-        self.root.pack_start(sw, 1, 1, 1)
-
-        self._index = 0
-
-        self._top_setting_group = None
-
-        job = common.RadioJob(self._build_ui, "get_settings")
-        job.set_desc("Getting radio settings")
-        self.rthread.submit(job)
-
-    def _save_settings(self):
-        if self._top_setting_group is None:
-            return
-
-        def setting_cb(result):
-            if isinstance(result, Exception):
-                common.show_error(_("Error in setting value: %s") % result)
-            elif self._changed:
-                self.emit("changed")
-                self._changed = False
-
-        job = common.RadioJob(setting_cb, "set_settings",
-                              self._top_setting_group)
-        job.set_desc("Setting radio settings")
-        self.rthread.submit(job)
-
-    def _load_setting(self, value, widget):
-        if isinstance(value, settings.RadioSettingValueInteger):
-            adj = widget.get_adjustment()
-            adj.configure(value.get_value(),
-                          value.get_min(), value.get_max(),
-                          value.get_step(), 1, 0)
-        elif isinstance(value, settings.RadioSettingValueFloat):
-            widget.set_text(value.format())
-        elif isinstance(value, settings.RadioSettingValueBoolean):
-            widget.set_active(value.get_value())
-        elif isinstance(value, settings.RadioSettingValueList):
-            model = widget.get_model()
-            model.clear()
-            for option in value.get_options():
-                widget.append_text(option)
-            current = value.get_value()
-            index = value.get_options().index(current)
-            widget.set_active(index)
-        elif isinstance(value, settings.RadioSettingValueString):
-            widget.set_text(str(value).rstrip())
-        else:
-            print "Unsupported widget type %s for %s" % (value.__class__,
-                                                         element.get_name())
-            
-    def _do_save_setting(self, widget, value):
-        if isinstance(value, settings.RadioSettingValueInteger):
-            value.set_value(widget.get_adjustment().get_value())
-        elif isinstance(value, settings.RadioSettingValueFloat):
-            value.set_value(widget.get_text())
-        elif isinstance(value, settings.RadioSettingValueBoolean):
-            value.set_value(widget.get_active())
-        elif isinstance(value, settings.RadioSettingValueList):
-            value.set_value(widget.get_active_text())
-        elif isinstance(value, settings.RadioSettingValueString):
-            value.set_value(widget.get_text())
-        else:
-            print "Unsupported widget type %s for %s" % (\
-                element.value.__class__,
-                element.get_name())
-
-        self._changed = True
-        self._save_settings()
-
-    def _save_setting(self, widget, value):
-        try:
-            self._do_save_setting(widget, value)
-        except settings.InvalidValueError, e:
-            common.show_error(_("Invalid setting value: %s") % e)
-
-    def _build_ui_group(self, group):
-        def pack(widget, pos):
-            self._table.attach(widget, pos, pos+1, self._index, self._index+1,
-                               xoptions=gtk.FILL, yoptions=0)
-
-        def abandon(child):
-            self._table.remove(child)
-        self._table.foreach(abandon)
-
-        self._index = 0
-        for element in group:
-            if not isinstance(element, settings.RadioSetting):
-                continue
-            label = gtk.Label(element.get_shortname())
-            label.set_alignment(1.0, 0.5)
-            label.show()
-            pack(label, 0)
-
-            if isinstance(element.value, list) and \
-                    isinstance(element.value[0],
-                               settings.RadioSettingValueInteger):
-                arraybox = gtk.HBox(3, True)
-            else:
-                arraybox = gtk.VBox(3, True)
-            pack(arraybox, 1)
-            arraybox.show()
-
-            widgets = []
-            for index in element.keys():
-                value = element[index]
-                if isinstance(value, settings.RadioSettingValueInteger):
-                    widget = gtk.SpinButton()
-                    print "Digits: %i" % widget.get_digits()
-                    signal = "value-changed"
-                elif isinstance(value, settings.RadioSettingValueFloat):
-                    widget = gtk.Entry()
-                    signal = "focus-out-event"
-                elif isinstance(value, settings.RadioSettingValueBoolean):
-                    widget = gtk.CheckButton(_("Enabled"))
-                    signal = "toggled"
-                elif isinstance(value, settings.RadioSettingValueList):
-                    widget = miscwidgets.make_choice([], editable=False)
-                    signal = "changed"
-                elif isinstance(value, settings.RadioSettingValueString):
-                    widget = gtk.Entry()
-                    signal = "changed"
-                else:
-                    print "Unsupported widget type: %s" % value.__class__
-
-                # Make sure the widget gets left-aligned to match up
-                # with its label
-                lalign = gtk.Alignment(0, 0, 0, 0)
-                lalign.add(widget)
-                lalign.show()
-
-                widget.set_sensitive(value.get_mutable())
-
-                arraybox.pack_start(lalign, 1, 1, 1)
-                widget.show()
-                self._load_setting(value, widget)
-                if signal == "focus-out-event":
-                    widget.connect(signal, lambda w, e, v:
-                                       self._save_setting(w, v), value)
-                else:
-                    widget.connect(signal, self._save_setting, value)
-
-            self._index += 1
-
-    def _build_tree(self, group, parent):
-        iter = self._store.append(parent)
-        self._store.set(iter, 0, group.get_shortname(), 1, group)
-        
-        if self._set_default is None:
-            # If we haven't found the first page with actual settings on it
-            # yet, then look for one here
-            for element in group:
-                if isinstance(element, settings.RadioSetting):
-                    self._set_default = self._store.get_path(iter), group
-                    break
-
-        for element in group:
-            if not isinstance(element, settings.RadioSetting):
-                self._build_tree(element, iter)
-        self._view.expand_all()
-
-    def _build_ui_real(self, group):
-        if not isinstance(group, settings.RadioSettingGroup):
-            print "Toplevel is not a group"
-            return
-
-        self._set_default = None
-        self._top_setting_group = group
-        self._build_tree(group, None)
-        self._view.set_cursor(self._set_default[0])
-        self._build_ui_group(self._set_default[1])
-
-    def _build_ui(self, group):
-        gobject.idle_add(self._build_ui_real, group)
-
-    def _group_selected(self, view, event):
-        if event.button != 1:
-            return
-
-        try:
-            path, col, x, y = view.get_path_at_pos(int(event.x), int(event.y))
-        except TypeError:
-            return # Didn't click on an actual item
-
-        group, = self._store.get(self._store.get_iter(path), 1)
-        if group:
-            self._build_ui_group(group)
diff --git a/chirpw b/chirpw
index 570d13c..cb67047 100755
--- a/chirpw
+++ b/chirpw
@@ -1,6 +1,6 @@
-#!/usr/bin/python
+#!/usr/bin/env python
 #
-# Copyright 2008 Dan Smith <dsmith at danplanet.com>
+# Copyright 2014 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
@@ -17,31 +17,22 @@
 
 import os
 
+from chirp import logger
 from chirp import elib_intl
-from chirp import platform, CHIRP_VERSION
-from chirpui import config
+from chirp import platform
+from chirp.drivers import *
+from chirp.ui import config
 
-# Hack to setup environment
-platform.get_platform()
 
 import sys
 import os
 import locale
 import gettext
+import argparse
+import logging
 
-p = platform.get_platform()
-if hasattr(sys, "frozen"):
-    log = file(p.config_file("debug.log"), "w", 0)
-    sys.stderr = log
-    sys.stdout = log
-elif not os.isatty(0):
-    log = file(p.config_file("debug.log"), "w", 0)
-    sys.stdout = log
-    sys.stderr = log
+LOG = logging.getLogger("chirpw")
 
-print "CHIRP %s on %s (Python %s)" % (CHIRP_VERSION,
-                                      platform.get_platform().os_version_string(),
-                                      sys.version.split()[0])
 
 execpath = platform.get_platform().executable_path()
 localepath = os.path.abspath(os.path.join(execpath, "locale"))
@@ -52,24 +43,25 @@ conf = config.get()
 manual_language = conf.get("language", "state")
 langs = []
 if manual_language and manual_language != "Auto":
-    lang_codes = { "English"         : "en_US",
-                   "Polish"          : "pl",
-                   "Italian"         : "it",
-                   "Dutch"           : "nl",
-                   "German"          : "de",
-                   "Hungarian"       : "hu",
-                   "Russian"         : "ru",
-                   "Portuguese (BR)" : "pt_BR",
-                   }
+    lang_codes = {"English":          "en_US",
+                  "Polish":           "pl",
+                  "Italian":          "it",
+                  "Dutch":            "nl",
+                  "German":           "de",
+                  "Hungarian":        "hu",
+                  "Russian":          "ru",
+                  "Portuguese (BR)":  "pt_BR",
+                  "French":           "fr",
+                  }
     try:
-        print lang_codes[manual_language]
+        LOG.info("Language: %s", lang_codes[manual_language])
         langs = [lang_codes[manual_language]]
     except KeyError:
-        print "Unsupported language `%s'" % manual_language
+        LOG.error("Unsupported language `%s'" % manual_language)
 else:
     lc, encoding = locale.getdefaultlocale()
     if (lc):
-	langs = [lc]
+        langs = [lc]
     try:
         langs += os.getenv("LANG").split(":")
     except:
@@ -88,7 +80,6 @@ gettext.textdomain("CHIRP")
 lang = gettext.translation("CHIRP", localepath, languages=langs,
                            fallback=True)
 
-import gtk
 
 # Python <2.6 does not have str.format(), which chirp uses to make translation
 # strings nicer. So, instead of installing the gettext standard "_()" function,
@@ -98,21 +89,22 @@ import gtk
 class CompatStr(str):
     def format(self, **kwargs):
         base = lang.gettext(self)
-        for k,v in kwargs.items():
+        for k, v in kwargs.items():
             base = base.replace("{%s}" % k, str(v))
         return base
 
 pyver = sys.version.split()[0]
 
-try :
+try:
     vmaj, vmin, vrel = pyver.split(".", 3)
-except :
+except:
     vmaj, vmin = pyver.split(".", 2)
     vrel = 0
 
 if int(vmaj) < 2 or int(vmin) < 6:
     # Python <2.6, emulate str.format()
     import __builtin__
+
     def lang_with_format(string):
         return CompatStr(string)
     __builtin__._ = lang_with_format
@@ -120,37 +112,36 @@ else:
     # Python >=2.6, use normal gettext behavior
     lang.install()
 
-from chirp import *
-from chirpui import mainapp, config
+parser = argparse.ArgumentParser()
+parser.add_argument("files", metavar="file", nargs='*', help="File to open")
+logger.add_version_argument(parser)
+parser.add_argument("--profile", action="store_true",
+                    help="Enable profiling")
+logger.add_arguments(parser)
+args = parser.parse_args()
 
-a = mainapp.ChirpMain()
+logger.handle_options(args)
 
-profile = False
-if len(sys.argv) > 1:
-    arg = sys.argv[1]
-    index = 2
-    if arg == "--profile":
-        profile = True
-    elif arg == "--help":
-        print "Usage: %s [file...]" % sys.argv[0]
-        sys.exit(0)
-    else:
-        index = 1
-else:
-    index = 1
+a = None
+if True:
+    from chirp.ui import mainapp
+    a = mainapp.ChirpMain()
 
-for i in sys.argv[index:]:
-    print "Opening %s" % i
+for i in args.files:
+    LOG.info("Opening %s", i)
     a.do_open(i)
 
 a.show()
 
-if profile:
-    import cProfile, pstats
+if args.profile:
+    import cProfile
+    import pstats
+    import gtk
     cProfile.run("gtk.main()", "chirpw.stats")
     p = pstats.Stats("chirpw.stats")
     p.sort_stats("cumulative").print_stats(10)
 else:
+    import gtk
     gtk.main()
 
 if config._CONFIG:
diff --git a/locale/de/LC_MESSAGES/CHIRP.mo b/locale/de/LC_MESSAGES/CHIRP.mo
index 127e167..3b76d2b 100644
Binary files a/locale/de/LC_MESSAGES/CHIRP.mo and b/locale/de/LC_MESSAGES/CHIRP.mo differ
diff --git a/locale/fr/LC_MESSAGES/CHIRP.mo b/locale/fr/LC_MESSAGES/CHIRP.mo
new file mode 100644
index 0000000..981fc11
Binary files /dev/null and b/locale/fr/LC_MESSAGES/CHIRP.mo differ
diff --git a/locale/hu/LC_MESSAGES/CHIRP.mo b/locale/hu/LC_MESSAGES/CHIRP.mo
index 0709dbf..263621d 100644
Binary files a/locale/hu/LC_MESSAGES/CHIRP.mo and b/locale/hu/LC_MESSAGES/CHIRP.mo differ
diff --git a/locale/it/LC_MESSAGES/CHIRP.mo b/locale/it/LC_MESSAGES/CHIRP.mo
index 0b94898..c2067fa 100644
Binary files a/locale/it/LC_MESSAGES/CHIRP.mo and b/locale/it/LC_MESSAGES/CHIRP.mo differ
diff --git a/locale/pl/LC_MESSAGES/CHIRP.mo b/locale/pl/LC_MESSAGES/CHIRP.mo
index 022eee2..8bfbf9c 100644
Binary files a/locale/pl/LC_MESSAGES/CHIRP.mo and b/locale/pl/LC_MESSAGES/CHIRP.mo differ
diff --git a/locale/pt_BR/LC_MESSAGES/CHIRP.mo b/locale/pt_BR/LC_MESSAGES/CHIRP.mo
index 07fe797..87bf37b 100644
Binary files a/locale/pt_BR/LC_MESSAGES/CHIRP.mo and b/locale/pt_BR/LC_MESSAGES/CHIRP.mo differ
diff --git a/locale/ru/LC_MESSAGES/CHIRP.mo b/locale/ru/LC_MESSAGES/CHIRP.mo
index 8e9b2e1..2ddb97a 100644
Binary files a/locale/ru/LC_MESSAGES/CHIRP.mo and b/locale/ru/LC_MESSAGES/CHIRP.mo differ
diff --git a/setup.py b/setup.py
index 28c4eb3..101d081 100644
--- a/setup.py
+++ b/setup.py
@@ -3,18 +3,29 @@ import sys
 import os
 
 from chirp import CHIRP_VERSION
-from chirp import *
+from chirp.drivers import *
 import chirp
 
+
 def staticify_chirp_module():
     import chirp
 
-    init = file("chirp/__init__.py", "w")
-    print >>init, "CHIRP_VERSION = \"%s\"" % CHIRP_VERSION
-    print >>init, "__all__ = %s\n" % str(chirp.__all__)
-    init.close()
+    with file("chirp/__init__.py", "w") as init:
+        print >>init, "CHIRP_VERSION = \"%s\"" % CHIRP_VERSION
+        print >>init, "__all__ = %s\n" % str(chirp.__all__)
+
+    print "Set chirp/__init__.py::__all__ = %s" % str(chirp.__all__)
+
+
+def staticify_drivers_module():
+    import chirp.drivers
+
+    with file("chirp/drivers/__init__.py", "w") as init:
+        print >>init, "__all__ = %s\n" % str(chirp.drivers.__all__)
+
+    print "Set chirp/drivers/__init__.py::__all__ = %s" % str(
+        chirp.drivers.__all__)
 
-    print "Set chirp.py::__all__ = %s" % str(chirp.__all__)
 
 def win32_build():
     from distutils.core import setup
@@ -26,7 +37,7 @@ def win32_build():
         import win32com
         for p in win32com.__path__[1:]:
             modulefinder.AddPackagePath("win32com", p)
-        for extra in ["win32com.shell"]: #,"win32com.mapi"
+        for extra in ["win32com.shell"]:  # ,"win32com.mapi"
             __import__(extra)
             m = sys.modules[extra]
             for p in m.__path__[1:]:
@@ -36,41 +47,46 @@ def win32_build():
         pass
 
     staticify_chirp_module()
+    staticify_drivers_module()
 
     opts = {
-        "py2exe" : {
-            "includes" : "pango,atk,gobject,cairo,pangocairo,win32gui,win32com,win32com.shell,email.iterators,email.generator,gio",
-
-            "compressed" : 1,
-            "optimize" : 2,
-            "bundle_files" : 3,
-            #        "packages" : ""
+        "py2exe": {
+            "includes": "pango,atk,gobject,cairo,pangocairo," +
+                        "win32gui,win32com,win32com.shell," +
+                        "email.iterators,email.generator,gio",
+
+            "compressed": 1,
+            "optimize": 2,
+            "bundle_files": 3,
+            # "packages": ""
             }
         }
 
     mods = []
     for mod in chirp.__all__:
         mods.append("chirp.%s" % mod)
+    for mod in chirp.drivers.__all__:
+        mods.append("chirp.drivers.%s" % mod)
     opts["py2exe"]["includes"] += ("," + ",".join(mods))
 
     setup(
         zipfile=None,
-        windows=[{'script'        : "chirpw",
+        windows=[{'script':         "chirpw",
                   'icon_resources': [(0x0004, 'share/chirp.ico')],
-		 }],
+                  }],
         options=opts)
 
+
 def macos_build():
     from setuptools import setup
     import shutil
 
     APP = ['chirp-%s.py' % CHIRP_VERSION]
     shutil.copy("chirpw", APP[0])
-    DATA_FILES = [('../Frameworks',
-                   ['/opt/local/lib/libpangox-1.0.dylib']),
-		  ('../Resources/', ['/opt/local/lib/pango']),
+    DATA_FILES = [('../Frameworks', ['/opt/local/lib/libpangox-1.0.dylib']),
+                  ('../Resources/', ['/opt/local/lib/pango']),
                   ]
-    OPTIONS = {'argv_emulation': True, "includes" : "gtk,atk,pangocairo,cairo"}
+    OPTIONS = {'argv_emulation': True, "includes": "gtk,atk,pangocairo,cairo"}
 
     setup(
         app=APP,
@@ -79,9 +95,11 @@ def macos_build():
         setup_requires=['py2app'],
         )
 
-    EXEC = 'bash ./build/macos/make_pango.sh /opt/local dist/chirp-%s.app' % CHIRP_VERSION
-    #print "exec string: %s" % EXEC
-    os.system(EXEC) 
+    EXEC = 'bash ./build/macos/make_pango.sh ' + \
+           '/opt/local dist/chirp-%s.app' % CHIRP_VERSION
+    # print "exec string: %s" % EXEC
+    os.system(EXEC)
+
 
 def default_build():
     from distutils.core import setup
@@ -90,7 +108,7 @@ def default_build():
     os.system("make -C locale clean all")
 
     desktop_files = glob("share/*.desktop")
-    #form_files = glob("forms/*.x?l")
+    # form_files = glob("forms/*.x?l")
     image_files = glob("images/*")
     _locale_files = glob("locale/*/LC_MESSAGES/CHIRP.mo")
     stock_configs = glob("stock_configs/*")
@@ -105,7 +123,7 @@ def default_build():
 
     setup(
         name="chirp",
-        packages=["chirp", "chirpui"],
+        packages=["chirp", "chirp.drivers", "chirp.ui"],
         version=CHIRP_VERSION,
         scripts=["chirpw"],
         data_files=[('share/applications', desktop_files),
@@ -117,9 +135,10 @@ def default_build():
                     ('share/chirp/stock_configs', stock_configs),
                     ] + locale_files)
 
+
 def rpttool_build():
     from distutils.core import setup
-    
+
     setup(name="rpttool",
           packages=["chirp"],
           version="0.3",
@@ -128,6 +147,7 @@ def rpttool_build():
           data_files=[('/usr/sbin', ["tools/icomsio.sh"])],
           )
 
+
 def nuke_manifest(*files):
     for i in ["MANIFEST", "MANIFEST.in"]:
         if os.path.exists(i):
@@ -140,7 +160,8 @@ def nuke_manifest(*files):
     for fn in files:
         print >>f, fn
     f.close()
-                    
+
+
 if sys.platform == "darwin":
     macos_build()
 elif sys.platform == "win32":
@@ -149,7 +170,7 @@ else:
     if os.path.exists("rpttool"):
         nuke_manifest("include tools/icomsio.sh", "include README.rpttool")
         rpttool_build()
-    if os.path.exists("chirpui"):
+    if os.path.exists("chirp/ui"):
         nuke_manifest("include *.xsd",
                       "include share/*.desktop",
                       "include share/chirp.png",
@@ -157,4 +178,3 @@ else:
                       "include stock_configs/*",
                       "include COPYING")
         default_build()
-
diff --git a/stock_configs/FR Marine VHF Channels.csv b/stock_configs/FR Marine VHF Channels.csv
new file mode 100644
index 0000000..02bc3ee
--- /dev/null
+++ b/stock_configs/FR Marine VHF Channels.csv	
@@ -0,0 +1,58 @@
+Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
+1,SEA 01,160.650000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+2,SEA 02,160.700000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+3,SEA 03,160.750000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+4,SEA 04,160.800000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+5,SEA 05,160.850000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+6,SEA 06,156.300000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+7,SEA 07,160.950000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+8,SEA 08,156.400000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+9,SEA 09,156.450000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+10,SEA 10,156.500000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+11,SEA 11,156.550000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+12,SEA 12,156.600000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+13,SEA 13,156.650000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+14,SEA 14,156.700000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+15,SEA 15,156.750000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+16,SEA 16,156.800000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+17,SEA 17,156.850000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+18,SEA 18,161.500000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+19,SEA 19,161.550000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+20,SEA 20,161.600000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+21,SEA 21,161.650000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+22,SEA 22,161.700000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+23,SEA 23,161.750000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+24,SEA 24,161.800000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+25,SEA 25,161.850000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+26,SEA 26,161.900000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+27,SEA 27,161.950000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+28,SEA 28,162.000000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+29,SEA 60,160.625000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+30,SEA 61,160.675000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+31,SEA 62,160.725000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+32,SEA 63,160.775000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+33,SEA 64,160.825000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+34,SEA 65,160.875000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+35,SEA 66,160.925000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+36,SEA 67,156.375000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+37,SEA 68,156.425000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+38,SEA 69,156.475000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+39,SEA 70,156.525000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+40,SEA 71,156.575000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+41,SEA 72,156.625000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+42,SEA 73,156.675000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+43,SEA 74,156.725000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+44,SEA 75,156.775000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+45,SEA 76,156.825000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+46,SEA 77,156.875000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+47,SEA 78,161.525000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+48,SEA 79,161.575000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+49,SEA 80,161.625000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+50,SEA 81,161.675000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+51,SEA 82,161.725000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+52,SEA 83,161.775000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+53,SEA 84,161.825000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+54,SEA 85,161.875000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+55,SEA 86,161.925000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+56,SEA 87,157.375000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+57,SEA 88,157.425000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
diff --git a/stock_configs/Marine VHF Channels.csv b/stock_configs/Marine VHF Channels.csv
deleted file mode 100644
index 82c6cc2..0000000
--- a/stock_configs/Marine VHF Channels.csv	
+++ /dev/null
@@ -1,61 +0,0 @@
-Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
-0,MVHF  L1,155.500000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-1,MVHF  L2,155.525000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-2,MVHF  F1,155.625000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-3,MVHF  F2,155.775000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-4,MVHF  F3,155.825000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-5,MVHF K06,156.300000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-6,MVHF K67,156.375000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-7,MVHF K08,156.400000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-8,MVHF K68,156.425000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-9,MVHF K09,156.450000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-10,MVHF K69,156.475000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-11,MVHF K10,156.500000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-12,MDSC K70,156.525000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-13,MVHF K11,156.550000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-14,MVHF K71,156.575000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-15,MVHF K12,156.600000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-16,MVHF K72,156.625000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-17,MVHF K13,156.650000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-18,MVHF K73,156.675000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-19,MVHF K14,156.700000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-20,MVHF K74,156.725000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-21,MVHF K15,156.750000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-22,MVHF K16,156.800000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-23,MVHF K17,156.850000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-24,MVHF K77,156.875000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-25,MVHF K60,160.625000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-26,MVHF K01,160.650000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-27,MVHF K61,160.675000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-28,MVHF K02,160.700000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-29,MVHF K62,160.725000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-30,MVHF K03,160.750000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-31,MVHF K63,160.775000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-32,MVHF K04,160.800000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-33,MVHF K64,160.825000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-34,MVHF K05,160.850000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-35,MVHF K65,160.875000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-36,MVHF K66,160.925000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-37,MVHF K07,160.950000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-38,MVHF K18,161.500000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-39,MVHF K78,161.525000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-40,MVHF K19,161.550000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-41,MVHF K79,161.575000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-42,MVHF K20,161.600000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-43,MVHF K80,161.625000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-44,MVHF K21,161.650000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-45,MVHF K81,161.675000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-46,MVHF K22,161.700000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-47,MVHF K82,161.725000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-48,MVHF K23,161.750000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-49,MVHF K83,161.775000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-50,MVHF K24,161.800000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-51,MVHF K84,161.825000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-52,MVHF K25,161.850000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-53,MVHF K85,161.875000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-54,MVHF K26,161.900000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-55,MVHF K86,161.925000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-56,MVHF K27,161.950000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-57,MAIS K87,161.975000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-58,MVHF K28,162.000000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-59,MAIS K88,162.025000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
diff --git a/stock_configs/US 60 meter channels (Dial).csv b/stock_configs/US 60 meter channels (Dial).csv
index 37c5eda..db3c13a 100644
--- a/stock_configs/US 60 meter channels (Dial).csv	
+++ b/stock_configs/US 60 meter channels (Dial).csv	
@@ -1,6 +1,6 @@
 Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,URCALL,RPT1CALL,RPT2CALL
 1,60m CH1,5.330500,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,,
-2,60m CH2,3.346500,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,,
+2,60m CH2,5.346500,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,,
 3,60m CH3,5.357000,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,,
 4,60m CH4,5.371500,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,,
 5,60m CH5,5.403500,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,,
diff --git a/stock_configs/US Calling Frequencies.csv b/stock_configs/US Calling Frequencies.csv
index 2138a05..4b8b804 100644
--- a/stock_configs/US Calling Frequencies.csv	
+++ b/stock_configs/US Calling Frequencies.csv	
@@ -1,5 +1,5 @@
 Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,URCALL,RPT1CALL,RPT2CALL
-1,6m Call,52.525000,-,0.500000,,88.5,88.5,023,NN,FM,5.00,,,,,
+1,6m Call,52.525000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
 2,2m Call,146.520000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
 3,220 Call,223.500000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
 4,70cm Call,446.000000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
diff --git a/stock_configs/US Marine VHF Channels.csv b/stock_configs/US Marine VHF Channels.csv
new file mode 100644
index 0000000..5dc7369
--- /dev/null
+++ b/stock_configs/US Marine VHF Channels.csv	
@@ -0,0 +1,61 @@
+Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
+1,SEA 01,160.650000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+2,SEA 02,160.700000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+3,SEA 03,160.750000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+4,SEA 04,160.800000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+5,SEA 05,160.850000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+6,SEA 06,156.300000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+7,SEA 07,160.950000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+8,SEA 08,156.400000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+9,SEA 09,156.450000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+10,SEA 10,156.500000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+11,SEA 11,156.550000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+12,SEA 12,156.600000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+13,SEA 13,156.650000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+14,SEA 14,156.700000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+15,SEA 15,156.750000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+16,SEA 16,156.800000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+17,SEA 17,156.850000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+18,SEA 18,161.500000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+19,SEA 19,161.550000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+20,SEA 20,161.600000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+21,SEA 21,161.650000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+22,SEA 22,161.700000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+23,SEA 23,161.750000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+24,SEA 24,161.800000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+25,SEA 25,161.850000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+26,SEA 26,161.900000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+27,SEA 27,161.950000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+28,SEA 28,162.000000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+29,SEA 60,160.625000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+30,SEA 61,160.675000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+31,SEA 62,160.725000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+32,SEA 63,160.775000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+33,SEA 64,160.825000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+34,SEA 65,160.875000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+35,SEA 66,160.925000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+36,SEA 67,156.375000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+37,SEA 68,156.425000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+38,SEA 69,156.475000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+39,DSC 70,156.525000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+40,SEA 71,156.575000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+41,SEA 72,156.625000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+42,SEA 73,156.675000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+43,SEA 74,156.725000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+44,SEA 77,156.875000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+45,SEA 78,161.525000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+46,SEA 79,161.575000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+47,SEA 80,161.625000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+48,SEA 81,161.675000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+49,SEA 82,161.725000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+50,SEA 83,161.775000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+51,SEA 84,161.825000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+52,SEA 85,161.875000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+53,SEA 86,161.925000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+54,AIS 87,161.975000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+55,AIS 88,162.025000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
+56,SEA F1,155.625000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+57,SEA F2,155.775000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+58,SEA F3,155.825000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+59,SEA L1,155.500000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+60,SEA L2,155.525000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,

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



More information about the pkg-hamradio-commits mailing list