[hamradio-commits] [chirp] 08/19: Imported Upstream version 0.4.0

Iain R. Learmonth irl at moszumanska.debian.org
Sun Nov 1 16:02:43 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 c86187dcdf158df59977baee6c8d6385f964b69b
Author: Iain R. Learmonth <irl at fsfe.org>
Date:   Sun May 3 18:15:26 2015 +0100

    Imported Upstream version 0.4.0
---
 PKG-INFO                                 |    2 +-
 chirp/__init__.py                        |    2 +-
 chirp/anytone.py                         |  548 +++++++++++++++
 chirp/bandplan.py                        |   84 +++
 chirp/bandplan_au.py                     |  114 ++++
 chirp/bandplan_iaru_r1.py                |  144 ++++
 chirp/bandplan_iaru_r2.py                |  145 ++++
 chirp/bandplan_iaru_r3.py                |  139 ++++
 chirp/bandplan_na.py                     |  269 ++++++++
 chirp/baofeng_uv3r.py                    |  343 +++++++++-
 chirp/bitwise.py                         |  161 ++++-
 chirp/bitwise_grammar.py                 |   36 +-
 chirp/bjuv55.py                          |  647 ++++++++++++++++++
 chirp/chirp_common.py                    |  309 +++++----
 chirp/elib_intl.py                       |  495 ++++++++++++++
 chirp/ft1802.py                          |   16 +
 chirp/ft60.py                            |   39 +-
 chirp/ft7800.py                          |  382 ++++++++++-
 chirp/ft817.py                           |  112 ++-
 chirp/ft857.py                           |  776 ++++++++++++++++++++-
 chirp/ft90.py                            |  652 ++++++++++++++++++
 chirp/ftm350.py                          |  420 ++++++++++++
 chirp/generic_csv.py                     |  195 +++++-
 chirp/generic_tpe.py                     |   50 +-
 chirp/h777.py                            |  542 +++++++++++++++
 chirp/ic208.py                           |   34 +-
 chirp/ic2820.py                          |    2 +-
 chirp/icf.py                             |   21 +-
 chirp/icq7.py                            |  184 +++++
 chirp/ict70.py                           |    2 +-
 chirp/ict7h.py                           |   10 +-
 chirp/id31.py                            |   24 +-
 chirp/id51.py                            |  109 +++
 chirp/import_logic.py                    |   30 +-
 chirp/kenwood_hmk.py                     |    5 +
 chirp/{kenwood_hmk.py => kenwood_itm.py} |   94 +--
 chirp/kenwood_live.py                    |  178 ++++-
 chirp/platform.py                        |   90 +--
 chirp/puxing.py                          |   39 +-
 chirp/pyPEG.py                           |  181 +++--
 chirp/radioreference.py                  |    8 +-
 chirp/settings.py                        |   71 +-
 chirp/th_uv3r.py                         |    2 +-
 chirp/th_uvf8d.py                        |  623 +++++++++++++++++
 chirp/thuv1f.py                          |   22 +-
 chirp/tk8102.py                          |  422 ++++++++++++
 chirp/uv5r.py                            | 1083 ++++++++++++++++++++++--------
 chirp/uvb5.py                            |  775 +++++++++++++++++++++
 chirp/vx2.py                             |  708 +++++++++++++++++++
 chirp/vx3.py                             |  686 ++++++++++++++++++-
 chirp/vx5.py                             |   29 +-
 chirp/vx510.py                           |  201 ++++++
 chirp/vx6.py                             |  159 ++++-
 chirp/vx7.py                             |   36 +-
 chirp/vx8.py                             | 1075 +++++++++++++++++++++++++++--
 chirp/wouxun.py                          |  604 +++++++++++++++--
 chirp/yaesu_clone.py                     |   20 +-
 chirpui/bandplans.py                     |  107 +++
 chirpui/bankedit.py                      |  229 ++++---
 chirpui/clone.py                         |    2 +
 chirpui/common.py                        |   43 +-
 chirpui/config.py                        |    9 +
 chirpui/dstaredit.py                     |    3 +-
 chirpui/editorset.py                     |  216 +++---
 chirpui/mainapp.py                       |  316 ++++++---
 chirpui/memdetail.py                     |   60 +-
 chirpui/memedit.py                       |  212 ++++--
 chirpui/radiobrowser.py                  |  320 +++++++++
 chirpui/settingsedit.py                  |   43 +-
 chirpui/shiftdialog.py                   |   24 +-
 chirpw                                   |   34 +-
 locale/de/LC_MESSAGES/CHIRP.mo           |  Bin 0 -> 17225 bytes
 locale/en_US/LC_MESSAGES/CHIRP.mo        |  Bin 0 -> 4802 bytes
 locale/hu/LC_MESSAGES/CHIRP.mo           |  Bin 0 -> 15824 bytes
 locale/it/LC_MESSAGES/CHIRP.mo           |  Bin 0 -> 15186 bytes
 locale/nl/LC_MESSAGES/CHIRP.mo           |  Bin 0 -> 15633 bytes
 locale/pl/LC_MESSAGES/CHIRP.mo           |  Bin 0 -> 11137 bytes
 locale/pt_BR/LC_MESSAGES/CHIRP.mo        |  Bin 0 -> 15187 bytes
 locale/ru/LC_MESSAGES/CHIRP.mo           |  Bin 0 -> 16690 bytes
 79 files changed, 14377 insertions(+), 1390 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index be04fdb..1d5e8a6 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.0
 Name: chirp
-Version: 0.3.1
+Version: 0.4.0
 Summary: UNKNOWN
 Home-page: UNKNOWN
 Author: UNKNOWN
diff --git a/chirp/__init__.py b/chirp/__init__.py
index 645552e..e676a98 100644
--- a/chirp/__init__.py
+++ b/chirp/__init__.py
@@ -13,7 +13,7 @@
 # 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.3.1"
+CHIRP_VERSION="0.4.0"
 
 import os
 import sys
diff --git a/chirp/anytone.py b/chirp/anytone.py
new file mode 100644
index 0000000..b9058ec
--- /dev/null
+++ b/chirp/anytone.py
@@ -0,0 +1,548 @@
+# Copyright 2013 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 os
+import struct
+import time
+
+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, \
+    RadioSettingValueList, RadioSettingValueString, RadioSettingValueBoolean
+
+_mem_format = """
+#seekto 0x0100;
+struct {
+  u8 even_unknown:2,
+     even_pskip:1,
+     even_skip:1,
+     odd_unknown:2,
+     odd_pskip:1,
+     odd_skip:1;
+} flags[379];
+"""
+
+mem_format = _mem_format + """
+struct memory {
+  bbcd freq[4];
+  bbcd offset[4];
+  u8 unknownA:4,
+     tune_step:4;
+  u8 rxdcsextra:1,
+     txdcsextra:1,
+     rxinv:1,
+     txinv:1,
+     channel_width:2,
+     unknownB:2;
+  u8 unknown8:3,
+     is_am:1,
+     power:2,
+     duplex:2;
+  u8 unknown4:4,
+     rxtmode:2,
+     txtmode:2;
+  u8 unknown5:2,
+     txtone:6;
+  u8 unknown6:2,
+     rxtone:6;
+  u8 txcode;
+  u8 rxcode;
+  u8 unknown7[2];
+  u8 unknown2[5];
+  char name[7];
+  u8 unknownZ[2];
+};
+
+#seekto 0x0030;
+struct {
+	char serial[16];
+} serial_no;
+
+#seekto 0x0050;
+struct {
+	char date[16];
+} version;
+
+#seekto 0x0280;
+struct {
+  u8 unknown1:6,
+     display:2;
+  u8 unknown2[11];
+  u8 unknown3:3,
+     apo:5;
+  u8 unknown4a[2];
+  u8 unknown4b:6,
+     mute:2;
+  u8 unknown4;
+  u8 unknown5:5,
+     beep:1,
+     unknown6:2;
+  u8 unknown[334];
+  char welcome[8];
+} settings;
+
+#seekto 0x0540;
+struct memory memblk1[12];
+
+#seekto 0x2000;
+struct memory memory[758];
+
+#seekto 0x7ec0;
+struct memory memblk2[10];
+"""
+
+class FlagObj(object):
+    def __init__(self, flagobj, which):
+        self._flagobj = flagobj
+        self._which = which
+
+    def _get(self, flag):
+        return getattr(self._flagobj, "%s_%s" % (self._which, flag))
+
+    def _set(self, flag, value):
+        return setattr(self._flagobj, "%s_%s" % (self._which, flag), value)
+
+    def get_skip(self):
+        return self._get("skip")
+
+    def set_skip(self, value):
+        self._set("skip", value)
+
+    skip = property(get_skip, set_skip)
+
+    def get_pskip(self):
+        return self._get("pskip")
+
+    def set_pskip(self, value):
+        self._set("pskip", value)
+
+    pskip = property(get_pskip, set_pskip)
+
+    def set(self):
+        self._set("unknown", 3)
+        self._set("skip", 1)
+        self._set("pskip", 1)
+
+    def clear(self):
+        self._set("unknown", 0)
+        self._set("skip", 0)
+        self._set("pskip", 0)
+
+    def get(self):
+        return (self._get("unknown") << 2 |
+                self._get("skip") << 1 |
+                self._get("pskip"))
+
+    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
+        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
+        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)
+        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)
+        raise errors.RadioError("Unsupported model")
+    _echo_write(radio, "\x02")
+    response = radio.pipe.read(16)
+    _debug(util.hexprint(response))
+    if response[1:8] not in valid_model:
+        print "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)
+        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)
+    _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)
+        return
+    result = _read(radio, length + 6)
+    _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)
+        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)
+        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])
+        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):
+            if memobj is not None and not _should_send_addr(memobj, addr):
+                block = "\xFF" * 0x10
+            else:
+                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)
+
+            if addr == 0x19F0:
+                memobj = bitwise.parse(_mem_format, data)
+
+    _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
+            if not _should_send_addr(radio._memobj, addr):
+                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)
+
+TONES = [62.5] + list(chirp_common.TONES)
+TMODES = ['', 'Tone', 'DTCS', '']
+DUPLEXES = ['', '-', '+', '']
+MODES = ["FM", "FM", "NFM"]
+POWER_LEVELS = [chirp_common.PowerLevel("High", watts=50),
+                chirp_common.PowerLevel("Mid1", watts=25),
+                chirp_common.PowerLevel("Mid2", watts=10),
+                chirp_common.PowerLevel("Low", watts=5)]
+
+
+ at directory.register
+class AnyTone5888UVRadio(chirp_common.CloneModeRadio,
+                         chirp_common.ExperimentalRadio):
+    """AnyTone 5888UV"""
+    VENDOR = "AnyTone"
+    MODEL = "5888UV"
+    BAUD_RATE = 9600
+    _file_ident = "QX588UV"
+
+    # 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", "P"]
+        rf.valid_modes = ["FM", "NFM", "AM"]
+        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
+        rf.valid_cross_modes = ['Tone->DTCS', 'DTCS->Tone',
+                                '->Tone', '->DTCS', 'Tone->Tone']
+        rf.valid_dtcs_codes = chirp_common.ALL_DTCS_CODES
+        rf.valid_bands = [(108000000, 500000000)]
+        rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "-"
+        rf.valid_name_length = 7
+        rf.valid_power_levels = POWER_LEVELS
+        rf.memory_bounds = (1, 758)
+        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_memobjs(self, number):
+        number -= 1
+        _mem = self._memobj.memory[number]
+        _flg = FlagObj(self._memobj.flags[number / 2],
+                       number % 2 and "even" or "odd")
+        return _mem, _flg
+
+    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_raw_memory(self, number):
+        _mem, _flg = self._get_memobjs(number)
+        return repr(_mem) + repr(_flg)
+
+    def get_memory(self, number):
+        _mem, _flg = self._get_memobjs(number)
+        mem = chirp_common.Memory()
+        mem.number = number
+
+        if _flg.get() == 0x0F:
+            mem.empty = True
+            return mem
+
+        mem.freq = int(_mem.freq) * 100
+        mem.offset = int(_mem.offset) * 100
+        mem.name = str(_mem.name).rstrip()
+        mem.duplex = DUPLEXES[_mem.duplex]
+        mem.mode = _mem.is_am and "AM" or MODES[_mem.channel_width]
+
+        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))
+
+        mem.skip = _flg.get_skip() and "S" or _flg.get_pskip() and "P" or ""
+        mem.power = POWER_LEVELS[_mem.power]
+
+        return mem
+
+    def set_memory(self, mem):
+        _mem, _flg = self._get_memobjs(mem.number)
+        if mem.empty:
+            _flg.set()
+            return
+        _flg.clear()
+        _mem.set_raw("\x00" * 32)
+
+        _mem.freq = mem.freq / 100
+        _mem.offset = mem.offset / 100
+        _mem.name = mem.name.ljust(7)
+        _mem.is_am = mem.mode == "AM"
+        _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"
+
+        _flg.set_skip(mem.skip == "S")
+        _flg.set_pskip(mem.skip == "P")
+
+        if mem.power:
+            _mem.power = POWER_LEVELS.index(mem.power)
+        else:
+            _mem.power = 0
+
+    def get_settings(self):
+        _settings = self._memobj.settings
+        settings = RadioSettingGroup('all', 'All Settings')
+
+        display = ["Frequency", "Channel", "Name"]
+        rs = RadioSetting("display", "Display",
+                          RadioSettingValueList(display,
+                                                display[_settings.display]))
+        settings.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)
+
+        def filter(s):
+            s_ = ""
+            for i in range(0, 8):
+                c = str(s[i])
+                s_ += (c if c in chirp_common.CHARSET_ASCII else "")
+            return s_
+
+        rs = RadioSetting("welcome", "Welcome Message",
+                          RadioSettingValueString(0, 8,
+                                                  filter(_settings.welcome)))
+        settings.append(rs)
+
+        rs = RadioSetting("beep", "Beep Enabled",
+                          RadioSettingValueBoolean(_settings.beep))
+        settings.append(rs)
+
+        mute = ["Off", "TX", "RX", "TX/RX"]
+        rs = RadioSetting("mute", "Sub Band Mute",
+                          RadioSettingValueList(mute,
+                                                mute[_settings.mute]))
+        settings.append(rs)
+
+        return settings
+
+    def set_settings(self, settings):
+        _settings = self._memobj.settings
+        for element in settings:
+            name = element.get_name()
+            setattr(_settings, name, element.value)
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return cls._file_ident in filedata[0x20:0x40]
+
+ at directory.register
+class IntekHR2040Radio(AnyTone5888UVRadio):
+    """Intek HR-2040"""
+    VENDOR = "Intek"
+    MODEL = "HR-2040"
+    _file_ident = "HR-2040"
+
+ at directory.register
+class PolmarDB50MRadio(AnyTone5888UVRadio):
+    """Polmar DB-50M"""
+    VENDOR = "Polmar"
+    MODEL = "DB-50M"
+    _file_ident = "DB-50M"
+
+ at directory.register
+class PowerwerxDB750XRadio(AnyTone5888UVRadio):
+    """Powerwerx DB-750X"""
+    VENDOR = "Powerwerx"
+    MODEL = "DB-750X"
+    _file_ident = "DB-750X"
+
+    def get_settings(self):
+        return {}
diff --git a/chirp/bandplan.py b/chirp/bandplan.py
new file mode 100644
index 0000000..f2b8541
--- /dev/null
+++ b/chirp/bandplan.py
@@ -0,0 +1,84 @@
+# Copyright 2013 Sean Burford <sburford at google.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from chirp import chirp_common
+
+class Band(object):
+    def __init__(self, limits, name, mode=None, step_khz=None,
+                 input_offset=None, output_offset=None, tones=None):
+        # Apply semantic and chirp limitations to settings.
+        # memedit applies radio limitations when settings are applied.
+        try:
+            assert limits[0] <= limits[1], "Lower freq > upper freq"
+            if mode is not None:
+                assert mode in chirp_common.MODES, "Mode %s not one of %s" % (
+                    mode, chirp_common.MODES)
+            if step_khz is not None:
+                assert step_khz in chirp_common.TUNING_STEPS, (
+                    "step_khz %s not one of %s" %
+                    (step_khz, chirp_common.TUNING_STEPS))
+            if tones:
+                for tone in tones:
+                    assert tone in chirp_common.TONES, (
+                        "tone %s not one of %s" % (tone, chirp_common.TONES))
+        except AssertionError, e:
+            raise ValueError("%s %s: %s" % (name, limits, e))
+
+        self.name = name
+        self.mode = mode
+        self.step_khz = step_khz
+        self.tones = tones
+        self.limits = limits
+        self.offset = None
+        self.duplex = "simplex"
+        if input_offset is not None:
+            self.offset = input_offset
+            self.duplex = "rpt TX"
+        elif output_offset is not None:
+            self.offset = output_offset
+            self.duplex = "rpt RX"
+
+    def __eq__(self, other):
+        return (other.limits[0] == self.limits[0] and
+                other.limits[1] == self.limits[1])
+
+    def contains(self, other):
+        return (other.limits[0] >= self.limits[0] and
+                other.limits[1] <= self.limits[1])
+
+    def width(self):
+        return self.limits[1] - self.limits[0]
+
+    def inverse(self):
+        """Create an RX/TX shadow of this band using the offset."""
+        if not self.offset:
+            return self
+        limits = (self.limits[0] + self.offset, self.limits[1] + self.offset)
+        offset = -1 * self.offset
+        if self.duplex == "rpt RX":
+            return Band(limits, self.name, self.mode, self.step_khz,
+                        input_offset=offset, tones=self.tones)
+        return Band(limits, self.name, self.mode, self.step_khz,
+                    output_offset=offset, tones=self.tones)
+
+    def __repr__(self):
+        desc = '%s%s%s%s' % (
+            self.mode and 'mode: %s ' % (self.mode,) or '',
+            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
new file mode 100644
index 0000000..136ef93
--- /dev/null
+++ b/chirp/bandplan_au.py
@@ -0,0 +1,114 @@
+# Copyright 2013 Sean Burford <sburford at google.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+from chirp import bandplan, bandplan_iaru_r3
+
+
+SHORTNAME = "australia"
+
+DESC = {
+  "name": "Australian Amateur Band Plan",
+  "updated": "April 2010",
+  "url": "http://www.wia.org.au/members/bandplans/data/documents/Australian%20Band%20Plans%20100404.pdf",
+}
+
+BANDS_10M = (
+  # bandplan.Band((28000000, 29700000), "10 Meter Band"),
+  bandplan.Band((29520000, 29680000), "FM Simplex and Repeaters",
+                mode="FM", step_khz=20),
+  bandplan.Band((29620000, 29680000), "FM Repeaters", input_offset=-100000),
+)
+
+BANDS_6M = (
+  # bandplan.Band((50000000, 54000000), "6 Meter Band"),
+  bandplan.Band((52525000, 53975000), "FM Simplex and Repeaters",
+                mode="FM", step_khz=25),
+  bandplan.Band((53550000, 53975000), "FM Repeaters", input_offset=-1000000),
+)
+
+BANDS_2M = (
+  bandplan.Band((144000000, 148000000), "2 Meter Band",
+                tones=(91.5, 123.0, 141.3, 146.2, 85.4)),
+  bandplan.Band((144400000, 144600000), "Beacons", step_khz=1),
+  bandplan.Band((146025000, 147975000), "FM Simplex and Repeaters",
+                mode="FM", step_khz=25),
+  bandplan.Band((146625000, 147000000), "FM Repeaters Group A",
+                input_offset=-600000),
+  bandplan.Band((147025000, 147375000), "FM Repeaters Group B",
+                input_offset=600000),
+)
+
+BANDS_70CM = (
+  bandplan.Band((420000000, 450000000), "70cm Band",
+                tones=(91.5, 123.0, 141.3, 146.2, 85.4)),
+  bandplan.Band((432400000, 432600000), "Beacons", step_khz=1),
+  bandplan.Band((438025000, 439975000), "FM Simplex and Repeaters",
+                mode="FM", step_khz=25),
+  bandplan.Band((438025000, 438725000), "FM Repeaters Group A",
+                input_offset=-5000000),
+  bandplan.Band((439275000, 439975000), "FM Repeaters Group B",
+                input_offset=-5000000),
+)
+
+BANDS_23CM = (
+  # bandplan.Band((1240000000, 1300000000), "23cm Band"),
+  bandplan.Band((1273025000, 1273975000), "FM Repeaters",
+                mode="FM", step_khz=25, input_offset=20000000),
+  bandplan.Band((1296400000, 1296600000), "Beacons", step_khz=1),
+  bandplan.Band((1297025000, 1300400000), "General FM Simplex Data",
+                mode="FM", step_khz=25),
+)
+
+BANDS_13CM = (
+  bandplan.Band((2300000000, 2450000000), "13cm Band"),
+  bandplan.Band((2403400000, 2403600000), "Beacons", step_khz=1),
+  bandplan.Band((2425000000, 2428000000), "FM Simplex",
+                mode="FM", step_khz=25),
+  bandplan.Band((2428025000, 2429000000), "FM Duplex (Voice)",
+                mode="FM", step_khz=25, input_offset=20000000),
+  bandplan.Band((2429000000, 2429975000), "FM Duplex (Data)",
+                mode="FM", step_khz=100, input_offset=20000000),
+)
+
+BANDS_9CM = (
+  bandplan.Band((3300000000, 3600000000), "9cm Band"),
+  bandplan.Band((3320000000, 3340000000), "WB Channel 2: Voice/Data",
+                step_khz=100),
+  bandplan.Band((3400400000, 3400600000), "Beacons", step_khz=1),
+  bandplan.Band((3402000000, 3403000000), "FM Simplex (Voice)",
+                mode="FM", step_khz=100),
+  bandplan.Band((3403000000, 3405000000), "FM Simplex (Data)",
+                mode="FM", step_khz=100),
+)
+
+BANDS_6CM = (
+  bandplan.Band((5650000000, 5850000000), "6cm Band"),
+  bandplan.Band((5760400000, 5760600000), "Beacons", step_khz=1),
+  bandplan.Band((5700000000, 5720000000), "WB Channel 2: Data",
+                step_khz=100, input_offset=70000000),
+  bandplan.Band((5720000000, 5740000000), "WB Channel 3: Voice",
+                step_khz=100, input_offset=70000000),
+  bandplan.Band((5762000000, 5763000000), "FM Simplex (Voice)",
+                mode="FM", step_khz=100),
+  bandplan.Band((5763000000, 5765000000), "FM Simplex (Data)",
+                mode="FM", step_khz=100),
+)
+
+BANDS = bandplan_iaru_r3.BANDS_20M + bandplan_iaru_r3.BANDS_17M
+BANDS += bandplan_iaru_r3.BANDS_15M + bandplan_iaru_r3.BANDS_12M
+BANDS += bandplan_iaru_r3.BANDS_10M + bandplan_iaru_r3.BANDS_6M
+BANDS += BANDS_10M + BANDS_6M + BANDS_2M + BANDS_70CM + BANDS_23CM
+BANDS += BANDS_13CM + BANDS_9CM + BANDS_6CM
diff --git a/chirp/bandplan_iaru_r1.py b/chirp/bandplan_iaru_r1.py
new file mode 100644
index 0000000..f71be4a
--- /dev/null
+++ b/chirp/bandplan_iaru_r1.py
@@ -0,0 +1,144 @@
+# Copyright 2013 Sean Burford <sburford at google.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from chirp import 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",
+  "updated": "General Conference Sun City 2011",
+}
+
+# Bands are broken up like this so that other plans can import bits.
+
+BANDS_2100M = (
+  bandplan.Band((135700, 137800), "137khz Band", mode="CW"),
+)
+
+BANDS_160M = (
+  bandplan.Band((1810000, 2000000), "160 Meter Band"),
+  bandplan.Band((1810000, 1838000), "CW", mode="CW"),
+  bandplan.Band((1838000, 1840000), "All narrow band modes"),
+  bandplan.Band((1840000, 1843000), "All modes, digimodes", mode="RTTY"),
+)
+
+BANDS_80M = (
+  bandplan.Band((3500000, 3800000), "80 Meter Band"),
+  bandplan.Band((3500000, 3510000), "CW, priority for DX", mode="CW"),
+  bandplan.Band((3510000, 3560000), "CW, contest preferred", mode="CW"),
+  bandplan.Band((3560000, 3580000), "CW", mode="CW"),
+  bandplan.Band((3580000, 3600000), "All narrow band modes, digimodes"),
+  bandplan.Band((3590000, 3600000), "All narrow band, digimodes, unattended"),
+  bandplan.Band((3600000, 3650000), "All modes, SSB contest preferred",
+                mode="LSB"),
+  bandplan.Band((3600000, 3700000), "All modes, SSB QRP", mode="LSB"),
+  bandplan.Band((3700000, 3800000), "All modes, SSB contest preferred",
+                mode="LSB"),
+  bandplan.Band((3775000, 3800000), "All modes, SSB DX preferred", mode="LSB"),
+)
+
+BANDS_40M = (
+  bandplan.Band((7000000, 7200000), "40 Meter Band"),
+  bandplan.Band((7000000, 7040000), "CW", mode="CW"),
+  bandplan.Band((7040000, 7047000), "All narrow band modes, digimodes"),
+  bandplan.Band((7047000, 7050000), "All narrow band, digimodes, unattended"),
+  bandplan.Band((7050000, 7053000), "All modes, digimodes, unattended"),
+  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((7130000, 7200000), "All modes, SSB contest preferred",
+                mode="LSB"),
+  bandplan.Band((7175000, 7200000), "All modes, SSB DX preferred", mode="LSB"),
+)
+
+BANDS_30M = (
+  bandplan.Band((10100000, 10150000), "30 Meter Band"),
+  bandplan.Band((10100000, 10140000), "CW", mode="CW"),
+  bandplan.Band((10140000, 10150000), "All narrow band digimodes"),
+)
+
+BANDS_20M = (
+  bandplan.Band((14000000, 14350000), "20 Meter Band"),
+  bandplan.Band((14000000, 14070000), "CW", mode="CW"),
+  bandplan.Band((14070000, 14099000), "All narrow band modes, digimodes"),
+  bandplan.Band((14099000, 14101000), "IBP, exclusively for beacons",
+                mode="CW"),
+  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"),
+)
+
+BANDS_17M = (
+  bandplan.Band((18068000, 18168000), "17 Meter Band"),
+  bandplan.Band((18068000, 18095000), "CW", mode="CW"),
+  bandplan.Band((18095000, 18109000), "All narrow band modes, digimodes"),
+  bandplan.Band((18109000, 18111000), "IBP, exclusively for beacons",
+                mode="CW"),
+  bandplan.Band((18111000, 18168000), "All modes, digimodes"),
+)
+
+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((21110000, 21120000), "All modes, digimodes, unattended"),
+  bandplan.Band((21120000, 21149000), "All narrow band modes"),
+  bandplan.Band((21149000, 21151000), "IBP, exclusively for beacons",
+                mode="CW"),
+  bandplan.Band((21151000, 21450000), "All modes", mode="USB"),
+)
+
+BANDS_12M = (
+  bandplan.Band((24890000, 24990000), "12 Meter Band"),
+  bandplan.Band((24890000, 24915000), "CW", mode="CW"),
+  bandplan.Band((24915000, 24929000), "All narrow band modes, digimodes"),
+  bandplan.Band((24929000, 24931000), "IBP, exclusively for beacons",
+                mode="CW"),
+  bandplan.Band((24931000, 24990000), "All modes, digimodes", mode="USB"),
+)
+
+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((28150000, 28190000), "All narrow band modes"),
+  bandplan.Band((28190000, 28199000), "Beacons", mode="CW"),
+  bandplan.Band((28199000, 28201000), "IBP, exclusively for beacons",
+                mode="CW"),
+  bandplan.Band((28201000, 28300000), "Beacons", mode="CW"),
+  bandplan.Band((28300000, 28320000), "All modes, digimodes, unattended"),
+  bandplan.Band((28320000, 29100000), "All modes"),
+  bandplan.Band((29100000, 29200000), "FM simplex", mode="NFM", step_khz=10),
+  bandplan.Band((29200000, 29300000), "All modes, digimodes, unattended"),
+  bandplan.Band((29300000, 29510000), "Satellite downlink"),
+  bandplan.Band((29510000, 29520000), "Guard band, no transmission allowed"),
+  bandplan.Band((29520000, 29590000), "All modes, FM repeater inputs",
+                step_khz=10, mode="NFM"),
+  bandplan.Band((29600000, 29610000), "FM simplex", step_khz=10, mode="NFM"),
+  bandplan.Band((29620000, 29700000), "All modes, FM repeater outputs",
+                step_khz=10, mode="NFM", input_offset=-100000),
+  bandplan.Band((29520000, 29700000), "Wide band", step_khz=10, mode="NFM"),
+)
+
+BANDS = BANDS_2100M + BANDS_160M + BANDS_80M + BANDS_40M + BANDS_30M
+BANDS = BANDS + BANDS_20M + BANDS_17M + BANDS_15M + BANDS_12M + BANDS_10M
diff --git a/chirp/bandplan_iaru_r2.py b/chirp/bandplan_iaru_r2.py
new file mode 100644
index 0000000..5886187
--- /dev/null
+++ b/chirp/bandplan_iaru_r2.py
@@ -0,0 +1,145 @@
+# Copyright 2013 Sean Burford <sburford at google.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from chirp import bandplan
+
+SHORTNAME = "iaru_r2"
+
+DESC = {
+  "name": "IARU Region 2 (The Americas)",
+  "updated": "October 8, 2010",
+  "url": "http://www.iaru.org/uploads/1/3/0/7/13073366/r2_band_plan.pdf"
+}
+
+# Bands are broken up like this so that other plans can import bits.
+
+BANDS_160M = (
+  bandplan.Band((1800000, 2000000), "160 Meter Band"),
+  bandplan.Band((1800000, 1810000), "Digimodes"),
+  bandplan.Band((1810000, 1830000), "CW", mode="CW"),
+  bandplan.Band((1830000, 1840000), "CW, priority for DX", mode="CW"),
+  bandplan.Band((1840000, 1850000), "SSB, priority for DX", mode="LSB"),
+  bandplan.Band((1850000, 1999000), "All modes", mode="LSB"),
+  bandplan.Band((1999000, 2000000), "Beacons", mode="CW"),
+)
+
+BANDS_80M = (
+  bandplan.Band((3500000, 4000000), "80 Meter Band"),
+  bandplan.Band((3500000, 3510000), "CW, priority for DX", mode="CW"),
+  bandplan.Band((3510000, 3560000), "CW, contest preferred", mode="CW"),
+  bandplan.Band((3560000, 3580000), "CW", mode="CW"),
+  bandplan.Band((3580000, 3590000), "All narrow band modes, digimodes"),
+  bandplan.Band((3590000, 3600000), "All modes"),
+  bandplan.Band((3600000, 3650000), "All modes, SSB contest preferred",
+                mode="LSB"),
+  bandplan.Band((3650000, 3700000), "All modes", mode="LSB"),
+  bandplan.Band((3700000, 3775000), "All modes, SSB contest preferred",
+                mode="LSB"),
+  bandplan.Band((3775000, 3800000), "All modes, SSB DX preferred", mode="LSB"),
+  bandplan.Band((3800000, 4000000), "All modes"),
+)
+
+BANDS_40M = (
+  bandplan.Band((7000000, 7300000), "40 Meter Band"),
+  bandplan.Band((7000000, 7025000), "CW, priority for DX", mode="CW"),
+  bandplan.Band((7025000, 7035000), "CW", mode="CW"),
+  bandplan.Band((7035000, 7038000), "All narrow band modes, digimodes"),
+  bandplan.Band((7038000, 7040000), "All narrow band modes, digimodes"),
+  bandplan.Band((7040000, 7043000), "All modes, digimodes"),
+  bandplan.Band((7043000, 7300000), "All modes"),
+)
+
+BANDS_30M = (
+  bandplan.Band((10100000, 10150000), "30 Meter Band"),
+  bandplan.Band((10100000, 10130000), "CW", mode="CW"),
+  bandplan.Band((10130000, 10140000), "All narrow band digimodes"),
+  bandplan.Band((10140000, 10150000), "All modes, digimodes, no phone"),
+)
+
+BANDS_20M = (
+  bandplan.Band((14000000, 14350000), "20 Meter Band"),
+  bandplan.Band((14000000, 14025000), "CW, priority for DX", mode="CW"),
+  bandplan.Band((14025000, 14060000), "CW, contest preferred", mode="CW"),
+  bandplan.Band((14060000, 14070000), "CW", mode="CW"),
+  bandplan.Band((14070000, 14089000), "All narrow band modes, digimodes"),
+  bandplan.Band((14089000, 14099000), "All modes, digimodes"),
+  bandplan.Band((14099000, 14101000), "IBP, exclusively for beacons",
+                mode="CW"),
+  bandplan.Band((14101000, 14112000), "All modes, digimodes"),
+  bandplan.Band((14112000, 14285000), "All modes, SSB contest preferred",
+                mode="USB"),
+  bandplan.Band((14285000, 14300000), "All modes", mode="AM"),
+  bandplan.Band((14300000, 14350000), "All modes"),
+)
+
+BANDS_17M = (
+  bandplan.Band((18068000, 18168000), "17 Meter Band"),
+  bandplan.Band((18068000, 18095000), "CW", mode="CW"),
+  bandplan.Band((18095000, 18105000), "All narrow band modes, digimodes"),
+  bandplan.Band((18105000, 18109000), "All narrow band modes, digimodes"),
+  bandplan.Band((18109000, 18111000), "IBP, exclusively for beacons",
+                mode="CW"),
+  bandplan.Band((18111000, 18120000), "All modes, digimodes"),
+  bandplan.Band((18120000, 18168000), "All modes"),
+)
+
+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 modes, digimodes"),
+  bandplan.Band((21110000, 21120000), "All modes (exc SSB), digimodes"),
+  bandplan.Band((21120000, 21149000), "All narrow band modes"),
+  bandplan.Band((21149000, 21151000), "IBP, exclusively for beacons",
+                mode="CW"),
+  bandplan.Band((21151000, 21450000), "All modes", mode="USB"),
+)
+
+BANDS_12M = (
+  bandplan.Band((24890000, 24990000), "12 Meter Band"),
+  bandplan.Band((24890000, 24915000), "CW", mode="CW"),
+  bandplan.Band((24915000, 24925000), "All narrow band modes, digimodes"),
+  bandplan.Band((24925000, 24929000), "All narrow band modes, digimodes"),
+  bandplan.Band((24929000, 24931000), "IBP, exclusively for beacons",
+                mode="CW"),
+  bandplan.Band((24931000, 24940000), "All modes, digimodes"),
+  bandplan.Band((24940000, 24990000), "All modes", mode="USB"),
+)
+
+BANDS_10M = (
+  bandplan.Band((28000000, 29520000), "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 modes, digimodes"),
+  bandplan.Band((28150000, 28190000), "All narrow band modes, digimodes"),
+  bandplan.Band((28190000, 28199000), "Regional time shared beacons",
+                mode="CW"),
+  bandplan.Band((28199000, 28201000), "IBP, exclusively for beacons",
+                mode="CW"),
+  bandplan.Band((28201000, 28225000), "Continuous duty beacons",
+                mode="CW"),
+  bandplan.Band((28225000, 28300000), "All modes, beacons"),
+  bandplan.Band((28300000, 28320000), "All modes, digimodes"),
+  bandplan.Band((28320000, 29000000), "All modes"),
+  bandplan.Band((29000000, 29200000), "All modes, AM preferred", mode="AM"),
+  bandplan.Band((29200000, 29300000), "All modes including FM, digimodes"),
+  bandplan.Band((29300000, 29510000), "Satellite downlink"),
+  bandplan.Band((29510000, 29520000), "Guard band, no transmission allowed"),
+  bandplan.Band((29520000, 29700000), "FM", step_khz=10, mode="NFM"),
+  bandplan.Band((29620000, 29690000), "FM Repeaters", input_offset=-100000),
+)
+
+BANDS = BANDS_160M + BANDS_80M + BANDS_40M + BANDS_30M + BANDS_20M
+BANDS = BANDS + BANDS_17M + BANDS_15M + BANDS_12M + BANDS_10M
diff --git a/chirp/bandplan_iaru_r3.py b/chirp/bandplan_iaru_r3.py
new file mode 100644
index 0000000..08bb56e
--- /dev/null
+++ b/chirp/bandplan_iaru_r3.py
@@ -0,0 +1,139 @@
+# Copyright 2013 Sean Burford <sburford at google.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from chirp import bandplan
+
+SHORTNAME = "iaru_r3"
+
+DESC = {
+  "name": "IARU Region 3 (Asia Pacific)",
+  "updated": "16 October 2009",
+  "url": "http://www.iaru.org/uploads/1/3/0/7/13073366/r3_band_plan.pdf"
+}
+
+# Bands are broken up like this so that other plans can import bits.
+
+BANDS_2100M = (
+  bandplan.Band((135700, 137800), "137khz Band", mode="CW"),
+)
+
+BANDS_160M = (
+  bandplan.Band((1800000, 2000000), "160 Meter Band"),
+  bandplan.Band((1830000, 1840000), "Digimodes", mode="RTTY"),
+  bandplan.Band((1840000, 2000000), "Phone"),
+)
+
+BANDS_80M = (
+  bandplan.Band((3500000, 3900000), "80 Meter Band"),
+  bandplan.Band((3500000, 3510000), "CW, priority for DX", mode="CW"),
+  bandplan.Band((3535000, 3900000), "Phone"),
+  bandplan.Band((3775000, 3800000), "All modes, SSB DX preferred", mode="LSB"),
+)
+
+BANDS_40M = (
+  bandplan.Band((7000000, 7300000), "40 Meter Band"),
+  bandplan.Band((7000000, 7025000), "CW, priority for DX", mode="CW"),
+  bandplan.Band((7025000, 7035000), "All narrow band modes, cw", mode="CW"),
+  bandplan.Band((7035000, 7040000), "All narrow band modes, phone"),
+  bandplan.Band((7040000, 7300000), "All modes, digimodes"),
+)
+
+BANDS_30M = (
+  bandplan.Band((10100000, 10150000), "30 Meter Band"),
+  bandplan.Band((10100000, 10130000), "CW", mode="CW"),
+  bandplan.Band((10130000, 10150000), "All narrow band digimodes"),
+)
+
+BANDS_20M = (
+  bandplan.Band((14000000, 14350000), "20 Meter Band"),
+  bandplan.Band((14000000, 14070000), "CW", mode="CW"),
+  bandplan.Band((14070000, 14099000), "All narrow band modes, digimodes"),
+  bandplan.Band((14099000, 14101000), "IBP, exclusively for beacons",
+                mode="CW"),
+  bandplan.Band((14101000, 14112000), "All narrow band modes, digimodes"),
+  bandplan.Band((14101000, 14350000), "All modes, digimodes"),
+)
+
+BANDS_17M = (
+  bandplan.Band((18068000, 18168000), "17 Meter Band"),
+  bandplan.Band((18068000, 18100000), "CW", mode="CW"),
+  bandplan.Band((18100000, 18109000), "All narrow band modes, digimodes"),
+  bandplan.Band((18109000, 18111000), "IBP, exclusively for beacons",
+                mode="CW"),
+  bandplan.Band((18111000, 18168000), "All modes, digimodes"),
+)
+
+BANDS_15M = (
+  bandplan.Band((21000000, 21450000), "15 Meter Band"),
+  bandplan.Band((21000000, 21070000), "CW", mode="CW"),
+  bandplan.Band((21070000, 21125000), "All narrow band modes, digimodes"),
+  bandplan.Band((21125000, 21149000), "All narrow band modes, digimodes"),
+  bandplan.Band((21149000, 21151000), "IBP, exclusively for beacons",
+                mode="CW"),
+  bandplan.Band((21151000, 21450000), "All modes", mode="USB"),
+)
+
+BANDS_12M = (
+  bandplan.Band((24890000, 24990000), "12 Meter Band"),
+  bandplan.Band((24890000, 24920000), "CW", mode="CW"),
+  bandplan.Band((24920000, 24929000), "All narrow band modes, digimodes"),
+  bandplan.Band((24929000, 24931000), "IBP, exclusively for beacons",
+                mode="CW"),
+  bandplan.Band((24931000, 24990000), "All modes, digimodes", mode="USB"),
+)
+
+BANDS_10M = (
+  bandplan.Band((28000000, 29700000), "10 Meter Band"),
+  bandplan.Band((28000000, 28050000), "CW", mode="CW"),
+  bandplan.Band((28050000, 28150000), "All narrow band modes, digimodes"),
+  bandplan.Band((28150000, 28190000), "All narrow band modes, digimodes"),
+  bandplan.Band((28190000, 28199000), "Beacons", mode="CW"),
+  bandplan.Band((28199000, 28201000), "IBP, exclusively for beacons",
+                mode="CW"),
+  bandplan.Band((28201000, 28300000), "Beacons", mode="CW"),
+  bandplan.Band((28300000, 29300000), "Phone"),
+  bandplan.Band((29300000, 29510000), "Satellite downlink"),
+  bandplan.Band((29510000, 29520000), "Guard band, no transmission allowed"),
+  bandplan.Band((29520000, 29700000), "Wide band", step_khz=10, mode="NFM"),
+)
+
+BANDS_6M = (
+  bandplan.Band((50000000, 54000000), "6 Meter Band"),
+  bandplan.Band((50000000, 50100000), "Beacons", mode="CW"),
+  bandplan.Band((50100000, 50500000), "Phone and narrow band"),
+  bandplan.Band((50500000, 54000000), "Wide band"),
+)
+
+BANDS_2M = (
+  bandplan.Band((144000000, 148000000), "2 Meter Band"),
+  bandplan.Band((144000000, 144035000), "Earth Moon Earth"),
+  bandplan.Band((145800000, 146000000), "Satellite"),
+)
+
+BANDS_70CM = (
+  bandplan.Band((430000000, 450000000), "70cm Band"),
+  bandplan.Band((431900000, 432240000), "Earth Moon Earth"),
+  bandplan.Band((435000000, 438000000), "Satellite"),
+)
+
+BANDS_23CM = (
+  bandplan.Band((1240000000, 1300000000), "23cm Band"),
+  bandplan.Band((1260000000, 1270000000), "Satellite"),
+  bandplan.Band((1296000000, 1297000000), "Earth Moon Earth"),
+)
+
+BANDS = BANDS_2100M + BANDS_160M + BANDS_80M + BANDS_40M + BANDS_30M
+BANDS = BANDS + BANDS_20M + BANDS_17M + BANDS_15M + BANDS_12M + BANDS_10M
+BANDS = BANDS + BANDS_6M + BANDS_2M + BANDS_70CM + BANDS_23CM
diff --git a/chirp/bandplan_na.py b/chirp/bandplan_na.py
new file mode 100644
index 0000000..ba46c8c
--- /dev/null
+++ b/chirp/bandplan_na.py
@@ -0,0 +1,269 @@
+# Copyright 2013 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/>.
+
+from chirp import bandplan, bandplan_iaru_r2
+
+
+SHORTNAME = "north_america"
+
+DESC = {
+  "name": "North American Band Plan",
+  "url": "http://www.arrl.org/band-plan"
+}
+
+BANDS_160M = (
+  bandplan.Band((1800000, 2000000), "160 Meter Band", mode="CW"),
+  bandplan.Band((1800000, 1810000), "Digital Modes"),
+  bandplan.Band((1843000, 2000000), "SSB, SSTV and other wideband modes"),
+  bandplan.Band((1995000, 2000000), "Experimental"),
+  bandplan.Band((1999000, 2000000), "Beacons"),
+)
+
+BANDS_80M = (
+  bandplan.Band((3500000, 4000000), "80 Meter Band"),
+  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"),
+  bandplan.Band((18105000, 18110000), "Packet"),
+)
+
+BANDS_15M = (
+  bandplan.Band((21000000, 21450000), "15 Meter Band"),
+  bandplan.Band((21070000, 21110000), "RTTY/Data", mode="RTTY"),
+)
+
+BANDS_12M = (
+  bandplan.Band((24890000, 24990000), "12 Meter Band"),
+  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"),
+  bandplan.Band((28070000, 28150000), "RTTY", mode="RTTY"),
+  bandplan.Band((28150000, 28190000), "CW", mode="CW"),
+  bandplan.Band((28201000, 28300000), "Beacons", mode="CW"),
+  bandplan.Band((28300000, 29300000), "Phone"),
+  bandplan.Band((29000000, 29200000), "AM", mode="AM"),
+  bandplan.Band((29300000, 29510000), "Satellite Downlinks"),
+  bandplan.Band((29520000, 29590000), "Repeater Inputs",
+                step_khz=10, mode="NFM"),
+  bandplan.Band((29610000, 29700000), "Repeater Outputs", 
+                step_khz=10, mode="NFM", input_offset=-890000),
+)
+
+BANDS_6M = (
+  bandplan.Band((50000000, 54000000), "6 Meter Band"),
+  bandplan.Band((50000000, 50100000), "CW, beacons", mode="CW"),
+  bandplan.Band((50060000, 50080000), "beacon subband"),
+  bandplan.Band((50100000, 50300000), "SSB, CW", mode="USB"),
+  bandplan.Band((50100000, 50125000), "DX window", mode="USB"),
+  bandplan.Band((50300000, 50600000), "All modes"),
+  bandplan.Band((50600000, 50800000), "Nonvoice communications"),
+  bandplan.Band((50800000, 51000000), "Radio remote control", step_khz=20),
+  bandplan.Band((51000000, 51100000), "Pacific DX window"),
+  bandplan.Band((51120000, 51180000), "Digital repeater inputs", step_khz=10),
+  bandplan.Band((51500000, 51600000), "Simplex"),
+  bandplan.Band((51620000, 51980000), "Repeater outputs A",
+                input_offset=-500000),
+  bandplan.Band((51620000, 51680000), "Digital repeater outputs",
+                input_offset=-500000),
+  bandplan.Band((52020000, 52040000), "FM simplex", mode="NFM"),
+  bandplan.Band((52500000, 52980000), "Repeater outputs B",
+                input_offset=-500000, step_khz=20, mode="NFM"),
+  bandplan.Band((53000000, 53100000), "FM simplex", mode="NFM"),
+  bandplan.Band((53100000, 53400000), "Radio remote control", step_khz=100),
+  bandplan.Band((53500000, 53980000), "Repeater outputs C",
+                input_offset=-500000),
+  bandplan.Band((53500000, 53800000), "Radio remote control", step_khz=100),
+  bandplan.Band((53520000, 53900000), "Simplex"),
+)
+
+BANDS_2M = (
+  bandplan.Band((144000000, 148000000), "2 Meter Band"),
+  bandplan.Band((144000000, 144050000), "EME (CW)", mode="CW"),
+  bandplan.Band((144050000, 144100000), "General CW and weak signals",
+                mode="CW"),
+  bandplan.Band((144100000, 144200000), "EME and weak-signal SSB",
+                mode="USB"),
+  bandplan.Band((144200000, 144275000), "General SSB operation",
+                mode="USB"),
+  bandplan.Band((144275000, 144300000), "Propagation beacons", mode="CW"),
+  bandplan.Band((144300000, 144500000), "OSCAR subband"),
+  bandplan.Band((144600000, 144900000), "FM repeater inputs", mode="NFM"),
+  bandplan.Band((144900000, 145100000), "Weak signal and FM simplex",
+                mode="NFM", step_khz=10),
+  bandplan.Band((145100000, 145200000), "Linear translator outputs",
+                input_offset=-600000),
+  bandplan.Band((145200000, 145500000), "FM repeater outputs",
+                input_offset=-600000, mode="NFM",),
+  bandplan.Band((145500000, 145800000), "Misc and experimental modes"),
+  bandplan.Band((145800000, 146000000), "OSCAR subband"),
+  bandplan.Band((146400000, 146580000), "Simplex"),
+  bandplan.Band((146610000, 146970000), "Repeater outputs",
+                input_offset=-600000),
+  bandplan.Band((147000000, 147390000), "Repeater outputs",
+                input_offset=600000),
+  bandplan.Band((147420000, 147570000), "Simplex"),
+)
+
+BANDS_1_25M = (
+  bandplan.Band((222000000, 225000000), "1.25 Meters"),
+  bandplan.Band((222000000, 222150000), "Weak-signal modes"),
+  bandplan.Band((222000000, 222025000), "EME"),
+  bandplan.Band((222050000, 222060000), "Propagation beacons"),
+  bandplan.Band((222100000, 222150000), "Weak-signal CW & SSB"),
+  bandplan.Band((222150000, 222250000), "Local coordinator's option"),
+  bandplan.Band((223400000, 223520000), "FM simplex", mode="NFM"),
+  bandplan.Band((223520000, 223640000), "Digital, packet"),
+  bandplan.Band((223640000, 223700000), "Links, control"),
+  bandplan.Band((223710000, 223850000), "Local coordinator's option"),
+  bandplan.Band((223850000, 224980000), "Repeater outputs only",
+                mode="NFM", input_offset=-1600000),
+)
+
+BANDS_70CM = (
+  bandplan.Band((420000000, 450000000), "70cm Band"),
+  bandplan.Band((420000000, 426000000), "ATV repeater or simplex"),
+  bandplan.Band((426000000, 432000000), "ATV simplex"),
+  bandplan.Band((432000000, 432070000), "EME (Earth-Moon-Earth)"),
+  bandplan.Band((432070000, 432100000), "Weak-signal CW", mode="CW"),
+  bandplan.Band((432100000, 432300000), "Mixed-mode and weak-signal work"),
+  bandplan.Band((432300000, 432400000), "Propagation beacons"),
+  bandplan.Band((432400000, 433000000), "Mixed-mode and weak-signal work"),
+  bandplan.Band((433000000, 435000000), "Auxiliary/repeater links"),
+  bandplan.Band((435000000, 438000000), "Satellite only (internationally)"),
+  bandplan.Band((438000000, 444000000), "ATV repeater input/repeater links",
+                input_offset=5000000),
+  bandplan.Band((442000000, 445000000), "Repeater input/output (local option)",
+                input_offset=5000000),
+  bandplan.Band((445000000, 447000000), "Shared by aux and control links, "
+                "repeaters, simplex (local option)"),
+  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"),
+  bandplan.Band((902100000, 902125000), "CW/SSB, Weak signal"),
+  bandplan.Band((903000000, 903100000), "CW/SSB, Beacons and weak signal"),
+  bandplan.Band((903100000, 903400000), "CW/SSB, Weak signal"),
+  bandplan.Band((903400000, 909000000), "Mixed modes, Mixed operations "
+                "including control links"),
+  bandplan.Band((909000000, 915000000), "Analog/digital Broadband multimedia "
+                "including ATV, DATV and SS"),
+  bandplan.Band((915000000, 921000000), "Analog/digital Broadband multimedia "
+                "including ATV, DATV and SS"),
+  bandplan.Band((921000000, 927000000), "Analog/digital Broadband multimedia "
+                "including ATV, DATV and SS"),
+  bandplan.Band((927000000, 927075000), "FM / other including DV or CW/SSB",
+                input_offset=-25000000, step_khz=12.5),
+  bandplan.Band((927075000, 927125000), "FM / other including DV. Simplex"),
+  bandplan.Band((927125000, 928000000), "FM / other including DV",
+                input_offset=-25000000, step_khz=12.5),
+)
+
+BANDS_23CM = (
+  bandplan.Band((1240000000, 1300000000), "23 Centimeter Band"),
+  bandplan.Band((1240000000, 1246000000), "ATV Channel #1"),
+  bandplan.Band((1246000000, 1248000000), "Point-to-point links paired "
+                "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((1240000000, 1260000000), "Regional option, FM ATV"),
+  bandplan.Band((1260000000, 1270000000), "Satellite uplinks, Experimental, "
+                "Simplex ATV"),
+  bandplan.Band((1270000000, 1276000000), "FM, digital Repeater inputs "
+                "(Regional option)", step_khz=25),
+  bandplan.Band((1276000000, 1282000000), "ATV Channel #3"),
+  bandplan.Band((1282000000, 1288000000), "FM, digital repeater outputs",
+                step_khz=25, input_offset=-12000000),
+  bandplan.Band((1288000000, 1294000000), "Various Broadband Experimental, "
+                "Simplex ATV"),
+  bandplan.Band((1290000000, 1294000000), "FM, digital Repeater outputs "
+                "(Regional option)", step_khz=25, input_offset=-20000000),
+  bandplan.Band((1294000000, 1295000000), "FM simplex", mode="FM"),
+  bandplan.Band((1295000000, 1297000000), "Narrow Band Segment"),
+  bandplan.Band((1295000000, 1295800000), "Narrow Band Image, Experimental"),
+  bandplan.Band((1295800000, 1296080000), "CW, SSB, digital EME"),
+  bandplan.Band((1296080000, 1296200000), "CW, SSB Weak Signal"),
+  bandplan.Band((1296200000, 1296400000), "CW, digital Beacons"),
+  bandplan.Band((1296400000, 1297000000), "General Narrow Band"),
+  bandplan.Band((1297000000, 1300000000), "Digital"),
+)
+
+BANDS_13CM = (
+  bandplan.Band((2300000000, 2450000000), "13 Centimeter Band"),
+  bandplan.Band((2300000000, 2303000000), "Analog & Digital 0.05-1.0 MHz, "
+                "including full duplex; paired with 2390-2393"),
+  bandplan.Band((2303000000, 2303750000), "Analog & Digital <50kHz; "
+                "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((2304300000, 2304400000), "Beacons, <3kHz"),
+  bandplan.Band((2304400000, 2304750000), "SSB, CW, digital weak-signal and "
+                "NBFM, <6kHz"),
+  bandplan.Band((2304750000, 2305000000), "Analog & Digital; paired with "
+                "2394.750-2395, <50kHz"),
+  bandplan.Band((2305000000, 2310000000), "Analog & Digital, paired with "
+                "2395-2400, 0.05 - 1.0 MHz"),
+  bandplan.Band((2310000000, 2390000000), "NON-AMATEUR"),
+  bandplan.Band((2390000000, 2393000000), "Analog & Digital, including full "
+                "duplex; paired with 2300-2303, 0.05 - 1.0 MHz"),
+  bandplan.Band((2393000000, 2393750000), "Analog & Digital; paired with "
+                "2303-2303.750, < 50 kHz"),
+  bandplan.Band((2393750000, 2394750000), "Experimental"),
+  bandplan.Band((2394750000, 2395000000), "Analog & Digital; paired with "
+                "2304.750-2305, < 50 kHz"),
+  bandplan.Band((2395000000, 2400000000), "Analog & Digital, including full "
+                "duplex; paired with 2305-2310, 0.05-1.0 MHz"),
+  bandplan.Band((2400000000, 2410000000), "Amateur Satellite Communications, "
+                "<6kHz"),
+  bandplan.Band((2410000000, 2450000000), "Broadband Modes, 22MHz max."),
+)
+
+BANDS = bandplan_iaru_r2.BANDS
+BANDS += BANDS_160M + BANDS_80M + BANDS_40M + BANDS_30M + BANDS_20M
+BANDS += BANDS_17M + BANDS_15M + BANDS_12M + BANDS_10M + BANDS_6M
+BANDS += BANDS_2M + BANDS_1_25M + BANDS_70CM + BANDS_33CM + BANDS_23CM
+BANDS += BANDS_13CM
diff --git a/chirp/baofeng_uv3r.py b/chirp/baofeng_uv3r.py
index 19d6921..c465edc 100644
--- a/chirp/baofeng_uv3r.py
+++ b/chirp/baofeng_uv3r.py
@@ -19,6 +19,10 @@ import time
 import os
 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
 
 if os.getenv("CHIRP_DEBUG"):
     DEBUG = True
@@ -88,6 +92,60 @@ struct {
   u8 unknown;
   lbcd tx_freq[4];
 } tx_memory[99];
+
+#seekto 0x0780;
+struct {
+  lbcd lower_vhf[2];
+  lbcd upper_vhf[2];
+  lbcd lower_uhf[2];
+  lbcd upper_uhf[2];
+} limits;
+
+struct vfosettings {
+  lbcd freq[4];
+  u8   rxtone;
+  u8   unknown1;
+  lbcd offset[3];
+  u8   txtone;
+  u8   power:1,
+       bandwidth:1,
+       unknown2:4,
+       duplex:2;
+  u8   step;
+  u8   unknown3[4];
+};
+
+#seekto 0x0790;
+struct {
+  struct vfosettings uhf;
+  struct vfosettings vhf;
+} vfo;
+
+#seekto 0x07C2;
+struct {
+  u8 squelch;
+  u8 vox;
+  u8 timeout;
+  u8 save:1,
+     unknown_1:1,
+     dw:1,
+     ste:1,
+     beep:1,
+     unknown_2:1,
+     bclo:1,
+     ch_flag:1;
+  u8 backlight:2,
+     relaym:1,
+     scanm:1,
+     pri:1,
+     unknown_3:3;
+  u8 unknown_4[3];
+  u8 pri_ch;
+} settings;
+
+#seekto 0x07E0;
+u16 fm_presets[16];
+
 #seekto 0x0810;
 struct {
   lbcd rx_freq[4];
@@ -113,6 +171,18 @@ struct {
 } names[128];
 """
 
+STEPS = [5.0, 6.25, 10.0, 12.5, 20.0, 25.0]
+STEP_LIST = [str(x) for x in STEPS]
+BACKLIGHT_LIST = ["Off", "Key", "Continuous"]
+TIMEOUT_LIST = ["Off"] + ["%s sec" % x for x in range(30, 210, 30)]
+SCANM_LIST = ["TO", "CO"]
+PRI_CH_LIST = ["Off"] + ["%s" % x for x in range(1, 100)]
+CH_FLAG_LIST = ["Freq Mode", "Channel Mode"]
+POWER_LIST = ["Low", "High"]
+BANDWIDTH_LIST = ["Narrow", "Wide"]
+DUPLEX_LIST = ["Off", "-", "+"]
+STE_LIST = ["On", "Off"]
+
 UV3R_DUPLEX = ["", "-", "+", ""]
 UV3R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
                      chirp_common.PowerLevel("Low", watts=0.50)]
@@ -126,10 +196,11 @@ class UV3RRadio(chirp_common.CloneModeRadio):
 
     def get_features(self):
         rf = chirp_common.RadioFeatures()
+        rf.has_settings = True
         rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
         rf.valid_modes = ["FM", "NFM"]
         rf.valid_power_levels = UV3R_POWER_LEVELS
-        rf.valid_bands = [(136000000, 174000000), (400000000, 470000000)]
+        rf.valid_bands = [(136000000, 235000000), (400000000, 529000000)]
         rf.valid_skips = []
         rf.valid_duplexes = ["", "-", "+", "split"]
         rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
@@ -282,6 +353,276 @@ class UV3RRadio(chirp_common.CloneModeRadio):
         self._set_memory(mem, _tmem)
         self._set_memory(mem, _rmem)
 
+    def get_settings(self):
+        _settings = self._memobj.settings
+        _vfo = self._memobj.vfo
+        basic = RadioSettingGroup("basic", "Basic Settings")
+        group = RadioSettingGroup("top", "All Settings", 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]))
+        basic.append(rs)
+
+        rs = RadioSetting("beep", "Keypad Beep",
+                          RadioSettingValueBoolean(_settings.beep))
+        basic.append(rs)
+
+        rs = RadioSetting("vox", "VOX Level (0=OFF)",
+                          RadioSettingValueInteger(0, 9, _settings.vox))
+        basic.append(rs)
+
+        rs = RadioSetting("dw", "Dual Watch",
+                          RadioSettingValueBoolean(_settings.dw))
+        basic.append(rs)
+
+        rs = RadioSetting("ste", "Squelch Tail Eliminate",
+                          RadioSettingValueList(STE_LIST,
+                                        STE_LIST[_settings.ste]))
+        basic.append(rs)
+
+        rs = RadioSetting("save", "Battery Saver",
+                          RadioSettingValueBoolean(_settings.save))
+        basic.append(rs)
+
+        rs = RadioSetting("timeout", "Time Out Timer",
+                          RadioSettingValueList(TIMEOUT_LIST,
+                                        TIMEOUT_LIST[_settings.timeout]))
+        basic.append(rs)
+
+        rs = RadioSetting("scanm", "Scan Mode",
+                          RadioSettingValueList(SCANM_LIST,
+                                        SCANM_LIST[_settings.scanm]))
+        basic.append(rs)
+
+        rs = RadioSetting("relaym", "Repeater Sound Response",
+                          RadioSettingValueBoolean(_settings.relaym))
+        basic.append(rs)
+
+        rs = RadioSetting("bclo", "Busy Channel Lock Out",
+                          RadioSettingValueBoolean(_settings.bclo))
+        basic.append(rs)
+
+        rs = RadioSetting("pri", "Priority Channel Scanning",
+                          RadioSettingValueBoolean(_settings.pri))
+        basic.append(rs)
+
+        rs = RadioSetting("pri_ch", "Priority Channel",
+                          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]))
+        basic.append(rs)
+
+        _limit = int(self._memobj.limits.lower_vhf) / 10
+        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
+        rs.set_apply_callback(apply_limit, self._memobj.limits)
+        basic.append(rs)
+
+        _limit = int(self._memobj.limits.upper_vhf) / 10
+        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
+        rs.set_apply_callback(apply_limit, self._memobj.limits)
+        basic.append(rs)
+
+        _limit = int(self._memobj.limits.lower_uhf) / 10
+        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
+        rs.set_apply_callback(apply_limit, self._memobj.limits)
+        basic.append(rs)
+
+        _limit = int(self._memobj.limits.upper_uhf) / 10
+        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
+        rs.set_apply_callback(apply_limit, self._memobj.limits)
+        basic.append(rs)
+
+        vfo_preset = RadioSettingGroup("vfo_preset", "VFO Presets")
+        group.append(vfo_preset)
+
+        def convert_bytes_to_freq(bytes):
+           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)))
+        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]))
+        vfo_preset.append(rs)
+
+        def convert_bytes_to_offset(bytes):
+           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)))
+        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]))
+        vfo_preset.append(rs)
+
+        rs = RadioSetting("vfo.vhf.bandwidth", "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]))
+        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)))
+        rs = RadioSetting("vfo.uhf.freq",
+                               "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]))
+        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)))
+        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]))
+        vfo_preset.append(rs)
+
+        rs = RadioSetting("vfo.uhf.bandwidth", "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]))
+        vfo_preset.append(rs)
+
+        fm_preset = RadioSettingGroup("fm_preset", "FM Radio Presets")
+        group.append(fm_preset)
+
+        for i in range(0, 16):
+            if self._memobj.fm_presets[i] < 0x01AF:
+                used = True
+                preset = self._memobj.fm_presets[i] / 10.0 + 65
+            else:
+                used = False
+                preset = 65
+            rs = RadioSetting("fm_presets_%1i" % i, "FM Preset %i" % (i + 1),
+                          RadioSettingValueBoolean(used),
+                          RadioSettingValueFloat(65, 108, preset, 0.1, 1))
+            fm_preset.append(rs)
+
+        return group
+
+    def set_settings(self, settings):
+        _settings = self._memobj.settings
+        for element in settings:
+            if not isinstance(element, RadioSetting):
+                if element.get_name() == "fm_preset" :
+                    self._set_fm_preset(element)
+                else:
+                    self.set_settings(element)
+                    continue
+            else:
+                try:
+                    name = element.get_name()
+                    if "." in name:
+                        bits = name.split(".")
+                        obj = self._memobj
+                        for bit in bits[:-1]:
+                            if "/" in bit:
+                                bit, index = bit.split("/", 1)
+                                index = int(index)
+                                obj = getattr(obj, bit)[index]
+                            else:
+                                obj = getattr(obj, bit)
+                        setting = bits[-1]
+                    else:
+                        obj = _settings
+                        setting = element.get_name()
+
+                    if element.has_apply_callback():
+                        print "Using apply callback"
+                        element.run_apply_callback()
+                    else:
+                        print "Setting %s = %s" % (setting, element.value)
+                        setattr(obj, setting, element.value)
+                except Exception, e:
+                    print element.get_name()
+                    raise
+
+    def _set_fm_preset(self, settings):
+        for element in settings:
+            try:
+                index = (int(element.get_name().split("_")[-1]))
+                val = element.value
+                if val[0].get_value():
+                    value = int(val[1].get_value() * 10 - 650)
+                else:
+                    value = 0x01AF
+                print "Setting fm_presets[%1i] = %s" % (index, value)
+                setting = self._memobj.fm_presets
+                setting[index] = value
+            except Exception, e:
+                print element.get_name()
+                raise
+
+
     @classmethod
     def match_model(cls, filedata, filename):
         return len(filedata) == 3648
diff --git a/chirp/bitwise.py b/chirp/bitwise.py
index 3f91c8a..149c588 100644
--- a/chirp/bitwise.py
+++ b/chirp/bitwise.py
@@ -17,6 +17,7 @@
 #
 # Example definitions:
 #
+#  bit  foo[8];  /* Eight single bit values                 */
 #  u8   foo;     /* Unsigned 8-bit value                    */
 #  u16  foo;     /* Unsigned 16-bit value                   */
 #  ul16 foo;     /* Unsigned 16-bit value (LE)              */
@@ -24,6 +25,13 @@
 #  ul24 foo;     /* Unsigned 24-bit value (LE)              */
 #  u32  foo;     /* Unsigned 32-bit value                   */
 #  ul32 foo;     /* Unsigned 32-bit value (LE)              */
+#  i8   foo;     /* Signed 8-bit value                      */
+#  i16  foo;     /* Signed 16-bit value                     */
+#  il16 foo;     /* Signed 16-bit value (LE)                */
+#  i24  foo;     /* Signed 24-bit value                     */
+#  il24 foo;     /* Signed 24-bit value (LE)                */
+#  i32  foo;     /* Signed 32-bit value                     */
+#  il32 foo;     /* Signed 32-bit value (LE)                */
 #  char foo;     /* Character (single-byte                  */
 #  lbcd foo;     /* BCD-encoded byte (LE)                   */
 #  bbcd foo;     /* BCD-encoded byte (BE)                   */
@@ -157,7 +165,7 @@ class arrayDataElement(DataElement):
             return "%i:[(%i)]" % (len(self.__items), int(self))
 
         if isinstance(self.__items[0], charDataElement):
-            return "%i:[(%s)]" % (len(self.__items), str(self))
+            return "%i:[(%s)]" % (len(self.__items), repr(str(self))[1:-1])
 
         s = "%i:[" % len(self.__items)
         s += ",".join([repr(item) for item in self.__items])
@@ -193,18 +201,16 @@ class arrayDataElement(DataElement):
             return str(self.__items)
 
     def __int__(self):
-        if isinstance(self.__items[0], bbcdDataElement):
+        if isinstance(self.__items[0], bcdDataElement):
             val = 0
-            for i in self.__items:
+            if isinstance(self.__items[0], bbcdDataElement):
+                items = self.__items
+            else:
+                items = reversed(self.__items)
+            for i in items:
                 tens, ones = i.get_value()
                 val = (val * 100) + (tens * 10) + ones
             return val
-        elif isinstance(self.__items[0], lbcdDataElement):
-            val = 0
-            for i in reversed(self.__items):
-                ones, tens = i.get_value()
-                val = (val * 100) + (tens * 10) + ones
-            return val
         else:
             raise ValueError("Cannot coerce this to int")
 
@@ -221,6 +227,9 @@ class arrayDataElement(DataElement):
             i.set_value(twodigits)
 
     def __set_value_char(self, value):
+        if len(value) != len(self.__items):
+            raise ValueError("String expects exactly %i characters" %
+                             len(self.__items))
         for i in range(0, len(self.__items)):
             self.__items[i].set_value(value[i])
 
@@ -248,6 +257,12 @@ class arrayDataElement(DataElement):
     def __iter__(self):
         return iter(self.__items)
 
+    def items(self):
+        index = 0
+        for item in self.__items:
+            yield (str(index), item)
+            index += 1
+
     def size(self):
         size = 0
         for i in self.__items:
@@ -393,7 +408,7 @@ class u16DataElement(intDataElement):
 
 class ul16DataElement(u16DataElement):
     _endianess = "<"
-
+    
 class u24DataElement(intDataElement):
     _size = 3
     _endianess = ">"
@@ -430,6 +445,65 @@ class u32DataElement(intDataElement):
 class ul32DataElement(u32DataElement):
     _endianess = "<"
 
+class i8DataElement(u8DataElement):
+    _size = 1
+
+    def _get_value(self, data):
+        return struct.unpack("b", data)[0]
+
+    def set_value(self, value):
+        self._data[self._offset] = struct.pack("b", int(value) )
+        
+class i16DataElement(intDataElement):
+    _size = 2
+    _endianess = ">"
+
+    def _get_value(self, data):
+        return struct.unpack(self._endianess + "h", data)[0]
+
+    def set_value(self, value):
+        self._data[self._offset] = struct.pack(self._endianess + "h",
+                                               int(value) )
+
+class il16DataElement(i16DataElement):
+    _endianess = "<"
+
+class i24DataElement(intDataElement):
+    _size = 3
+    _endianess = ">"
+
+    def _get_value(self, data):
+        pre = self._endianess == ">" and "\x00" or ""
+        post = self._endianess == "<" and "\x00" or ""
+        return struct.unpack(self._endianess + "i", pre+data+post)[0]
+
+    def set_value(self, value):
+        if self._endianess == "<":
+            start = 0
+            end = 3
+        else:
+            start = 1
+            end = 4
+        self._data[self._offset] = struct.pack(self._endianess + "i",
+                                               int(value) )[start:end]
+
+class il24DataElement(i24DataElement):
+    _endianess = "<"
+
+class i32DataElement(intDataElement):
+    _size = 4
+    _endianess = ">"
+
+    def _get_value(self, data):
+        return struct.unpack(self._endianess + "i", data)[0]
+
+    def set_value(self, value):
+        self._data[self._offset] = struct.pack(self._endianess + "i",
+                                               int(value) )
+
+class il32DataElement(i32DataElement):
+    _endianess = "<"
+
 class charDataElement(DataElement):
     _size = 1
 
@@ -468,29 +542,20 @@ class bcdDataElement(DataElement):
             raise TypeError("Unable to set bcdDataElement from type %s" %
                             type(data))
 
-class lbcdDataElement(bcdDataElement):
-    _size = 1
+    def set_value(self, value):
+        self._data[self._offset] = int("%02i" % value, 16)
 
     def _get_value(self, data):
         a = (ord(data) & 0xF0) >> 4
         b = ord(data) & 0x0F
-        return (b, a)
+        return (a, b)
 
-    def set_value(self, value):
-        value = int("%02i" % value, 16)
-        self._data[self._offset] = value
+class lbcdDataElement(bcdDataElement):
+    _size = 1
 
 class bbcdDataElement(bcdDataElement):
     _size = 1
 
-    def _get_value(self, data):
-        a = (ord(data) & 0xF0) >> 4
-        b = ord(data) & 0x0F
-        return (a, b)
-
-    def set_value(self, value):
-        self._data[self._offset] = int("%02i" % value, 16)
-
 class bitDataElement(intDataElement):
     _nbits = 0
     _shift = 0
@@ -612,6 +677,14 @@ class structDataElement(DataElement):
             raise ValueError("Struct size mismatch during set_raw()")
         self._data[self._offset] = buffer
 
+    def __iter__(self):
+        for item in self._generators.values():
+            yield item
+
+    def items(self):
+        for key in self._keys:
+            yield key, self._generators[key]
+
 class Processor:
 
     _types = {
@@ -622,6 +695,12 @@ class Processor:
         "ul24" : ul24DataElement,
         "u32"  : u32DataElement,
         "ul32" : ul32DataElement,
+        "i8"   : i8DataElement,
+        "i16"  : i16DataElement,
+        "il16" : il16DataElement,
+        "i24"  : i24DataElement,
+        "il24" : il24DataElement,
+        "i32"  : i32DataElement,
         "char" : charDataElement,
         "lbcd" : lbcdDataElement,
         "bbcd" : bbcdDataElement,
@@ -661,6 +740,16 @@ class Processor:
 
         return bytes
 
+    def do_bitarray(self, i, count):
+        if count % 8 != 0:
+            raise ValueError("bit array must be divisible by 8.")
+
+        class bitDE(bitDataElement):
+            _nbits = 1
+            _shift = 8 - i % 8
+
+        return bitDE(self._data, self._offset)
+
     def parse_defn(self, defn):
         dtype = defn[0]
 
@@ -680,9 +769,13 @@ class Processor:
             res = arrayDataElement(self._offset)
             size = 0
             for i in range(0, count):
-                gen = self._types[dtype](self._data, self._offset)
+                if dtype == "bit":
+                    gen = self.do_bitarray(i, count)
+                    self._offset += int((i+1) % 8 == 0)
+                else:
+                    gen = self._types[dtype](self._data, self._offset)
+                    self._offset += (gen.size() / 8)
                 res.append(gen)
-                self._offset += (gen.size() / 8)
 
             if count == 1:
                 self._generators[name] = res[0]
@@ -732,20 +825,14 @@ class Processor:
 
     def parse_directive(self, directive):
         name = directive[0][0]
+        value = directive[0][1][0][1]
         if name == "seekto":
-            offset = directive[0][1][0][1]
-            if offset.startswith("0x"):
-                offset = int(offset[2:], 16)
-            else:
-                offset = int(offset)
             #print "NOTICE: Setting offset to %i (0x%X)" % (offset, offset)
-            self._offset = offset
+            self._offset = int(value, 0)
         elif name == "seek":
-            offset = int(directive[0][1][0][1])
-            self._offset += offset
+            self._offset += int(value, 0)
         elif name == "printoffset":
-            string = directive[0][1][0][1]
-            print "%s: %i (0x%08X)" % (string[1:-1], self._offset, self._offset)
+            print "%s: %i (0x%08X)" % (value[1:-1], self._offset, self._offset)
 
     def parse_block(self, lang):
         for t, d in lang:
@@ -760,7 +847,7 @@ class Processor:
 
     def parse(self, lang):
         self._generators = structDataElement(self._data, self._offset)
-        self.parse_block(lang[0])
+        self.parse_block(lang)
         return self._generators
 
 
diff --git a/chirp/bitwise_grammar.py b/chirp/bitwise_grammar.py
index f311410..fe81b42 100644
--- a/chirp/bitwise_grammar.py
+++ b/chirp/bitwise_grammar.py
@@ -14,9 +14,10 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import re
-from chirp.pyPEG import keyword, parseLine
+from chirp.pyPEG import keyword, parse as pypeg_parse
 
-TYPES = ["u8", "u16", "ul16", "u24", "ul24", "u32", "ul32", "char",
+TYPES = ["bit", "u8", "u16", "ul16", "u24", "ul24", "u32", "ul32",
+         "i8", "i16", "il16", "i24", "il24", "i32", "il32", "char",
          "lbcd", "bbcd"]
 DIRECTIVES = ["seekto", "seek", "printoffset"]
 
@@ -78,4 +79,33 @@ def _language():
     return _block_inner
 
 def parse(data):
-    return parseLine(data, _language, resultSoFar=[]) 
+    lines = data.split("\n")
+    for index, line in enumerate(lines):
+        if '//' in line:
+            lines[index] = line[:line.index('//')]
+
+    class FakeFileInput:
+        """Simulate line-by-line file reading from @data"""
+        line = -1
+
+        def isfirstline(self):
+            return self.line == 0
+
+        def filename(self):
+            return "input"
+
+        def lineno(self):
+            return self.line
+
+        def __iter__(self):
+            return self
+
+        def next(self):
+            self.line += 1
+            try:
+                # Note, FileInput objects keep the newlines
+                return lines[self.line] + "\n"
+            except IndexError:
+                raise StopIteration
+
+    return pypeg_parse(_language, FakeFileInput())
diff --git a/chirp/bjuv55.py b/chirp/bjuv55.py
new file mode 100644
index 0000000..4f7f5ad
--- /dev/null
+++ b/chirp/bjuv55.py
@@ -0,0 +1,647 @@
+# Copyright 2013 Jens Jensen AF5MI <kd4tjx at yahoo.com>
+# Based on work by Jim Unroe, Dan Smith, et al.
+# Special thanks to Mats SM0BTP for equipment donation.
+#
+# 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 time
+import os
+
+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
+from textwrap import dedent
+from chirp import uv5r
+
+if os.getenv("CHIRP_DEBUG"):
+    CHIRP_DEBUG = True
+else:
+    CHIRP_DEBUG = False
+
+BJUV55_MODEL   = "\x50\xBB\xDD\x55\x63\x98\x4D"
+
+COLOR_LIST = ["Off", "Blue", "Red", "Pink"]
+
+STEPS = uv5r.STEPS
+STEPS.remove(2.5)
+STEP_LIST = [str(x) for x in STEPS]
+
+MEM_FORMAT = """
+#seekto 0x0008;
+struct {
+  lbcd rxfreq[4];
+  lbcd txfreq[4];
+  ul16 rxtone;
+  ul16 txtone;
+  u8 unused1:3,
+     isuhf:1,
+     scode:4;
+  u8 unknown1:7,
+     txtoneicon:1;
+  u8 mailicon:3,
+     unknown2:4,
+     lowpower:1;
+  u8 unknown3:1,
+     wide:1,
+     unknown4:2,
+     bcl:1,
+     scan:1,
+     pttid:2;
+} memory[128];
+
+#seekto 0x0B08;
+struct {
+  u8 code[5];
+  u8 unused[11];
+} pttid[15];
+
+#seekto 0x0C88;
+struct {
+  u8 inspection[5];
+  u8 monitor[5];
+  u8 alarmcode[5];
+  u8 unknown1;
+  u8 stun[5];
+  u8 kill[5];
+  u8 revive[5];
+  u8 unknown2;
+  u8 master_control_id[5];
+  u8 vice_control_id[5];
+  u8 code[5];
+  u8 unused1:6,
+     aniid:2;
+  u8 unknown[2];
+  u8 dtmfon;
+  u8 dtmfoff;
+} ani;
+
+#seekto 0x0E28;
+struct {
+  u8 squelch;
+  u8 step;
+  u8 tdrab;
+  u8 tdr;
+  u8 vox;	
+  u8 timeout;
+  u8 unk2[6];
+  u8 abr;
+  u8 beep; 
+  u8 ani;
+  u8 unknown3[2];
+  u8 voice;
+  u8 ring_time;
+  u8 dtmfst;
+  u8 unknown5;
+  u8 unknown12:6,
+     screv:2;
+  u8 pttid;
+  u8 pttlt;
+  u8 mdfa;
+  u8 mdfb;
+  u8 bcl;
+  u8 autolk;
+  u8 sftd;
+  u8 unknown6[3];
+  u8 wtled;
+  u8 rxled;
+  u8 txled;
+  u8 unknown7[5];
+  u8 save;
+  u8 unknown8;
+  u8 displayab:1,
+     unknown1:2,
+     fmradio:1,
+     alarm:1,
+     unknown2:1,
+     reset:1,
+     menu:1;
+  u8 vfomrlock;
+  u8 workmode;
+  u8 keylock;
+  u8 workmode_channel;
+  u8 password[6];
+  u8 unknown10[11];
+} settings;
+
+#seekto 0x0E7E;
+struct {
+  u8 mrcha;
+  u8 mrchb;
+} wmchannel;
+
+#seekto 0x0F10;
+struct {
+  u8 freq[8];
+  u8 unknown1;
+  u8 offset[4];
+  u8 unknown2;
+  ul16 rxtone;
+  ul16 txtone;
+  u8 unused1:7,
+     band:1;
+  u8 unknown3;
+  u8 unused2:2,
+     sftd:2,
+     scode:4;
+  u8 unknown4;
+  u8 unused3:1
+     step:3,
+     unused4:4;
+  u8 txpower:1,
+     widenarr:1,
+     unknown5:6;
+} vfoa;
+
+#seekto 0x0F30;
+struct {
+  u8 freq[8];
+  u8 unknown1;
+  u8 offset[4];
+  u8 unknown2;
+  ul16 rxtone;
+  ul16 txtone;
+  u8 unused1:7,
+     band:1;
+  u8 unknown3;
+  u8 unused2:2,
+     sftd:2,
+     scode:4;
+  u8 unknown4;
+  u8 unused3:1
+     step:3,
+     unused4:4;
+  u8 txpower:1,
+     widenarr:1,
+     unknown5:6;
+} vfob;
+
+#seekto 0x0F57;
+u8 fm_preset;
+
+#seekto 0x1008;
+struct {
+  char name[6];
+  u8 unknown2[10];
+} names[128];
+
+#seekto 0x%04X;
+struct {
+  char line1[7];
+  char line2[7];
+} poweron_msg;
+
+#seekto 0x1838;
+struct {
+  char line1[7];
+  char line2[7];
+} firmware_msg;
+
+#seekto 0x1849;
+u8 power_vhf_hi[14]; // 136-174 MHz, 3 MHz divisions
+u8 power_uhf_hi[14]; // 400-470 MHz, 5 MHz divisions
+#seekto 0x1889;
+u8 power_vhf_lo[14];
+u8 power_uhf_lo[14];
+
+struct limit {
+  u8 enable;
+  bbcd lower[2];
+  bbcd upper[2];
+};
+
+#seekto 0x1908;
+struct {
+    struct limit vhf;
+    u8 unk11[11];
+    struct limit uhf;
+} limits;
+
+"""
+
+ at directory.register
+class BaojieBJUV55Radio(uv5r.BaofengUV5R):
+    VENDOR = "Baojie"
+    MODEL = "BJ-UV55"
+    #_basetype = "BJ55"
+    _basetype = ["BJ55"]
+    _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)
+
+    def set_memory(self, mem):
+        super(BaojieBJUV55Radio, self).set_memory(mem)
+        _mem = self._memobj.memory[mem.number]
+        if (mem.freq - mem.offset) > (400 * 1000000):
+            _mem.isuhf = True
+        else:
+            _mem.isuhf = False
+        if mem.tmode in ["Tone", "TSQL"]:
+            _mem.txtoneicon = True
+        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)
+
+        rs = RadioSetting("squelch", "Carrier Squelch Level",
+                          RadioSettingValueInteger(0, 9, _settings.squelch))
+        basic.append(rs)
+
+        rs = RadioSetting("save", "Battery Saver",
+                          RadioSettingValueInteger(0, 4, _settings.save))
+        basic.append(rs)
+
+        rs = RadioSetting("abr", "Backlight",
+                          RadioSettingValueBoolean(_settings.abr))
+        basic.append(rs)
+
+        rs = RadioSetting("tdr", "Dual Watch (BDR)",
+                          RadioSettingValueBoolean(_settings.tdr))
+        advanced.append(rs)
+
+        rs = RadioSetting("tdrab", "Dual Watch TX Priority",
+                          RadioSettingValueList(uv5r.TDRAB_LIST,
+                          uv5r.TDRAB_LIST[_settings.tdrab]))
+        advanced.append(rs)
+
+        rs = RadioSetting("alarm", "Alarm",
+                          RadioSettingValueBoolean(_settings.alarm))
+        advanced.append(rs)
+
+        rs = RadioSetting("beep", "Beep",
+                          RadioSettingValueBoolean(_settings.beep))
+        basic.append(rs)
+
+        rs = RadioSetting("timeout", "Timeout Timer",
+                          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]))
+        advanced.append(rs)
+
+        rs = RadioSetting("mdfa", "Display Mode (A)",
+                          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]))
+        basic.append(rs)
+
+        rs = RadioSetting("bcl", "Busy Channel Lockout",
+                          RadioSettingValueBoolean(_settings.bcl))
+        advanced.append(rs)
+
+        rs = RadioSetting("autolk", "Automatic Key Lock",
+                          RadioSettingValueBoolean(_settings.autolk))
+        advanced.append(rs)
+
+        rs = RadioSetting("fmradio", "Broadcast FM Radio",
+                          RadioSettingValueBoolean(_settings.fmradio))
+        advanced.append(rs)
+
+        rs = RadioSetting("wtled", "Standby LED Color",
+                          RadioSettingValueList(COLOR_LIST,
+                          COLOR_LIST[_settings.wtled]))
+        basic.append(rs)
+
+        rs = RadioSetting("rxled", "RX LED Color",
+                          RadioSettingValueList(COLOR_LIST,
+                          COLOR_LIST[_settings.rxled]))
+        basic.append(rs)
+
+        rs = RadioSetting("txled", "TX LED Color",
+                          RadioSettingValueList(COLOR_LIST,
+                          COLOR_LIST[_settings.txled]))
+        basic.append(rs)
+
+        rs = RadioSetting("reset", "RESET Menu",
+                          RadioSettingValueBoolean(_settings.reset))
+        advanced.append(rs)
+
+        rs = RadioSetting("menu", "All Menus",
+                          RadioSettingValueBoolean(_settings.menu))
+        advanced.append(rs)
+
+
+        if len(self._mmap.get_packed()) == 0x1808:
+            # Old image, without aux block
+            return group
+
+        other = RadioSettingGroup("other", "Other Settings")
+        group.append(other)
+
+        def _filter(name):
+            filtered = ""
+            for char in str(name):
+                if char in chirp_common.CHARSET_ASCII:
+                    filtered += char
+                else:
+                    filtered += " "
+            return filtered
+
+        _msg = self._memobj.poweron_msg
+        rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
+                          RadioSettingValueString(0, 7, _filter(_msg.line1)))
+        other.append(rs)
+        rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
+                          RadioSettingValueString(0, 7, _filter(_msg.line2)))
+        other.append(rs)
+
+        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))
+        other.append(rs)
+
+        rs = RadioSetting("%s.vhf.upper" % limit, "VHF Upper Limit (MHz)",
+                          RadioSettingValueInteger(1, 1000,
+                                                   vhf_limit.upper))
+        other.append(rs)
+
+        rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled",
+                          RadioSettingValueBoolean(vhf_limit.enable))
+        other.append(rs)
+
+        uhf_limit = getattr(self._memobj, limit).uhf
+        rs = RadioSetting("%s.uhf.lower" % limit, "UHF Lower Limit (MHz)",
+                          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))
+        other.append(rs)
+        rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled",
+                          RadioSettingValueBoolean(uhf_limit.enable))
+        other.append(rs)
+
+        workmode = RadioSettingGroup("workmode", "Work Mode Settings")
+        group.append(workmode)
+
+        options = ["A", "B"]
+        rs = RadioSetting("displayab", "Display Selected",
+                          RadioSettingValueList(options,
+                                                options[_settings.displayab]))
+        workmode.append(rs)
+
+        options = ["Frequency", "Channel"]
+        rs = RadioSetting("workmode", "VFO/MR Mode",
+                          RadioSettingValueList(options,
+                                                options[_settings.workmode]))
+        workmode.append(rs)
+
+        rs = RadioSetting("keylock", "Keypad Lock",
+                          RadioSettingValueBoolean(_settings.keylock))
+        workmode.append(rs)
+
+        _mrcna = self._memobj.wmchannel.mrcha
+        rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
+                          RadioSettingValueInteger(0, 127, _mrcna))
+        workmode.append(rs)
+
+        _mrcnb = self._memobj.wmchannel.mrchb
+        rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
+                          RadioSettingValueInteger(0, 127, _mrcnb))
+        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)
+
+        def my_validate(value):
+            value = chirp_common.parse_freq(value)
+            if 17400000 <= value and value < 40000000:
+                raise InvalidValueError("Can't be between 174.00000-400.00000")
+            return chirp_common.format_freq(value)
+
+        def apply_freq(setting, obj):
+            value = chirp_common.parse_freq(str(setting.value)) / 10
+            obj.band = value >= 40000000
+            for i in range(7, -1, -1):
+                obj.freq[i] = value % 10
+                value /= 10
+
+        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.set_validate_callback(my_validate)
+        rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b)
+        rs.set_apply_callback(apply_freq, self._memobj.vfob)
+        workmode.append(rs)
+
+        options = ["Off", "+", "-"]
+        rs = RadioSetting("vfoa.sftd", "VFO A Shift",
+                          RadioSettingValueList(options,
+                                                options[self._memobj.vfoa.sftd]))
+        workmode.append(rs)
+
+        rs = RadioSetting("vfob.sftd", "VFO B Shift",
+                          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)
+
+        def apply_offset(setting, obj):
+            value = chirp_common.parse_freq(str(setting.value)) / 10000
+            for i in range(3, -1, -1):
+                obj.offset[i] = value % 10
+                value /= 10
+
+        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))
+        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]))
+        workmode.append(rs)
+
+        rs = RadioSetting("vfob.txpower", "VFO B Power",
+                          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]))
+        workmode.append(rs)
+
+        rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
+                          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]))
+        workmode.append(rs)
+
+        rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
+                          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]))
+        workmode.append(rs)
+        rs = RadioSetting("vfob.step", "VFO B Tuning 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))
+        fm_preset.append(rs)
+
+        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
+        group.append(dtmf)
+        dtmfchars = "0123456789 *#ABCD"
+
+        for i in range(0, 15):
+            _codeobj = self._memobj.pttid[i].code
+            _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)
+            def apply_code(setting, obj):
+                code = []
+                for j in range(0, 5):
+                    try:
+                        code.append(dtmfchars.index(str(setting.value)[j]))
+                    except IndexError:
+                        code.append(0xFF)
+                obj.code = code
+            rs.set_apply_callback(apply_code, self._memobj.pttid[i])
+            dtmf.append(rs)
+
+        _codeobj = self._memobj.ani.code
+        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+        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):
+                try:
+                    code.append(dtmfchars.index(str(setting.value)[j]))
+                except IndexError:
+                    code.append(0xFF)
+            obj.code = code
+        rs.set_apply_callback(apply_code, self._memobj.ani)
+        dtmf.append(rs)
+
+        options = ["Off", "BOT", "EOT", "Both"]
+        rs = RadioSetting("ani.aniid", "ANI ID",
+                          RadioSettingValueList(options,
+                                                options[self._memobj.ani.aniid]))
+        dtmf.append(rs)
+
+        _codeobj = self._memobj.ani.alarmcode
+        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+        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):
+                try:
+                    alarmcode.append(dtmfchars.index(str(setting.value)[j]))
+                except IndexError:
+                    alarmcode.append(0xFF)
+            obj.alarmcode = alarmcode
+        rs.set_apply_callback(apply_code, self._memobj.ani)
+        dtmf.append(rs)
+
+        rs = RadioSetting("dtmfst", "DTMF Sidetone",
+                          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]))
+        dtmf.append(rs)
+
+        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
+                          RadioSettingValueList(uv5r.DTMFSPEED_LIST,
+                          uv5r.DTMFSPEED_LIST[self._memobj.ani.dtmfoff]))
+        dtmf.append(rs)
+
+        return group
+
+    def _set_fm_preset(self, settings):
+        for element in settings:
+            try:
+                val = element.value
+                value = int(val.get_value() * 10 - 870)
+                print "Setting fm_preset = %s" % (value)
+                self._memobj.fm_preset = value
+            except Exception, e:
+                print element.get_name()
+                raise
diff --git a/chirp/chirp_common.py b/chirp/chirp_common.py
index f2472a0..600f4eb 100644
--- a/chirp/chirp_common.py
+++ b/chirp/chirp_common.py
@@ -32,6 +32,12 @@ TONES = [ 67.0, 69.3, 71.9, 74.4, 77.0, 79.7, 82.5,
           225.7, 229.1, 233.6, 241.8, 250.3, 254.1,
           ]          
 
+TONES_EXTRA = [62.5]
+
+OLD_TONES = list(TONES)
+[OLD_TONES.remove(x) for x in [159.8, 165.5, 171.3, 177.3, 183.5, 189.9,
+                               196.6, 199.5, 206.5, 229.1, 254.1]]
+
 # 104 DTCS Codes
 DTCS_CODES = [
      23,  25,  26,  31,  32,  36,  43,  47,  51,  53,  54,
@@ -46,74 +52,26 @@ DTCS_CODES = [
     731, 732, 734, 743, 754,
      ]
 
-# Some radios have some strange codes
-DTCS_EXTRA_CODES = [ 17, 645 ]
+# 512 Possible DTCS Codes
+ALL_DTCS_CODES = []
+for a in range(0, 8):
+    for b in range(0, 8):
+        for c in range(0, 8):
+            ALL_DTCS_CODES.append((a * 100) + (b * 10) + c)
 
 CROSS_MODES = [
     "Tone->Tone",
+    "DTCS->",
+    "->DTCS",
     "Tone->DTCS",
     "DTCS->Tone",
-    "DTCS->",
     "->Tone",
-    "->DTCS",
     "DTCS->DTCS",
 ]
 
 MODES = ["WFM", "FM", "NFM", "AM", "NAM", "DV", "USB", "LSB", "CW", "RTTY",
          "DIG", "PKT", "NCW", "NCWR", "CWR", "P25", "Auto"]
 
-STD_6M_OFFSETS = [
-    (51620000, 51980000, -500000),
-    (52500000, 52980000, -500000),
-    (53500000, 53980000, -500000),
-    ]
-
-STD_2M_OFFSETS = [
-    (145100000, 145500000, -600000),
-    (146000000, 146400000,  600000),
-    (146600000, 147000000, -600000),
-    (147000000, 147400000,  600000),
-    (147600000, 148000000, -600000),
-    ]
-
-STD_220_OFFSETS = [
-    (223850000, 224980000, -1600000),
-    ]
-
-STD_70CM_OFFSETS = [
-    (440000000, 445000000,  5000000),
-    (447000000, 450000000, -5000000),
-    ]
-
-STD_23CM_OFFSETS = [
-    (1282000000, 1288000000, -12000000),
-    ]
-
-# Standard offsets, indexed by band (wavelength in cm)
-STD_OFFSETS = {
-    600 : STD_6M_OFFSETS,
-    200 : STD_2M_OFFSETS,
-    125 : STD_220_OFFSETS,
-     70 : STD_70CM_OFFSETS,
-     23 : STD_23CM_OFFSETS,
-    }
-
-BAND_TO_MHZ = {
-    600 : (   50000000,   54000000 ),
-    200 : (  144000000,  148000000 ),
-    125 : (  219000000,  225000000 ),
-    70 :  (  420000000,  450000000 ),
-    23 :  ( 1240000000, 1300000000 ),
-}
-
-# NB: This only works for some bands, throws an Exception otherwise
-def freq_to_band(freq):
-    """Returns the band (in cm) for a given frequency"""
-    for band, (lo, hi) in BAND_TO_MHZ.items():
-        if int(freq) > lo and int(freq) < hi:
-            return band
-    raise Exception("No conversion for frequency %i" % freq)
-
 TONE_MODES = [
     "",
     "Tone",
@@ -138,6 +96,52 @@ CHARSET_ALPHANUMERIC = \
     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 1234567890"
 CHARSET_ASCII = "".join([chr(x) for x in range(ord(" "), ord("~")+1)])
 
+# http://aprs.org/aprs11/SSIDs.txt
+APRS_SSID = (
+    "0 Your primary station usually fixed and message capable",
+    "1 generic additional station, digi, mobile, wx, etc",
+    "2 generic additional station, digi, mobile, wx, etc",
+    "3 generic additional station, digi, mobile, wx, etc",
+    "4 generic additional station, digi, mobile, wx, etc",
+    "5 Other networks (Dstar, Iphones, Androids, Blackberry's etc)",
+    "6 Special activity, Satellite ops, camping or 6 meters, etc",
+    "7 walkie talkies, HT's or other human portable",
+    "8 boats, sailboats, RV's or second main mobile",
+    "9 Primary Mobile (usually message capable)",
+    "10 internet, Igates, echolink, winlink, AVRS, APRN, etc",
+    "11 balloons, aircraft, spacecraft, etc",
+    "12 APRStt, DTMF, RFID, devices, one-way trackers*, etc",
+    "13 Weather stations",
+    "14 Truckers or generally full time drivers",
+    "15 generic additional station, digi, mobile, wx, etc")
+APRS_POSITION_COMMENT = (
+    "off duty", "en route", "in service", "returning", "committed",
+    "special", "priority", "custom 0", "custom 1", "custom 2", "custom 3",
+    "custom 4", "custom 5", "custom 6", "EMERGENCY")
+# http://aprs.org/symbols/symbolsX.txt
+APRS_SYMBOLS = (
+    "Police/Sheriff", "[reserved]", "Digi", "Phone", "DX Cluster",
+    "HF Gateway", "Small Aircraft", "Mobile Satellite Groundstation",
+    "Wheelchair", "Snowmobile", "Red Cross", "Boy Scouts", "House QTH (VHF)",
+    "X", "Red Dot", "0 in Circle", "1 in Circle", "2 in Circle",
+    "3 in Circle", "4 in Circle", "5 in Circle", "6 in Circle", "7 in Circle",
+    "8 in Circle", "9 in Circle", "Fire", "Campground", "Motorcycle",
+    "Railroad Engine", "Car", "File Server", "Hurricane Future Prediction",
+    "Aid Station", "BBS or PBBS", "Canoe", "[reserved]", "Eyeball",
+    "Tractor/Farm Vehicle", "Grid Square", "Hotel", "TCP/IP", "[reserved]",
+    "School", "PC User", "MacAPRS", "NTS Station", "Balloon", "Police", "TBD",
+    "Recreational Vehicle", "Space Shuttle", "SSTV", "Bus", "ATV",
+    "National WX Service Site", "Helicopter", "Yacht/Sail Boat", "WinAPRS",
+    "Human/Person", "Triangle", "Mail/Postoffice", "Large Aircraft",
+    "WX Station", "Dish Antenna", "Ambulance", "Bicycle",
+    "Incident Command Post", "Dual Garage/Fire Dept", "Horse/Equestrian",
+    "Fire Truck", "Glider", "Hospital", "IOTA", "Jeep", "Truck", "Laptop",
+    "Mic-Repeater", "Node", "Emergency Operations Center", "Rover (dog)",
+    "Grid Square above 128m", "Repeater", "Ship/Power Boat", "Truck Stop",
+    "Truck (18 wheeler)", "Van", "Water Station", "X-APRS", "Yagi at QTH",
+    "TDB", "[reserved]"
+)
+
 def watts_to_dBm(watts):
     """Converts @watts in watts to dBm"""
     return int(10 * math.log10(int(watts * 1000)))
@@ -185,16 +189,28 @@ class PowerLevel:
 
 def parse_freq(freqstr):
     """Parse a frequency string and return the value in integral Hz"""
+    freqstr = freqstr.strip()
+    if freqstr == "":
+        return 0
+    elif freqstr.endswith(" MHz"):
+        return parse_freq(freqstr.split(" ")[0])
+    elif freqstr.endswith(" kHz"):
+        return int(freqstr.split(" ")[0]) * 1000
+
     if "." in freqstr:
         mhz, khz = freqstr.split(".")
+        if mhz == "":
+            mhz = 0
+        khz = khz.ljust(6, "0")
+        if len(khz) > 6:
+            raise ValueError("Invalid kHz value: %s", khz)
+        mhz = int(mhz) * 1000000
+        khz = int(khz)
     else:
-        mhz = freqstr
-        khz = "0"
-    if not mhz.isdigit() and khz.isdigit():
-        raise ValueError("Invalid value")
+        mhz = int(freqstr) * 1000000
+        khz = 0
 
-    # Make kHz exactly six decimal places
-    return int(("%s%-6s" % (mhz, khz)).replace(" ", "0"))
+    return mhz + khz
 
 def format_freq(freq):
     """Format a frequency given in Hz as a string"""
@@ -262,10 +278,10 @@ class Memory:
         self.immutable = []
 
     _valid_map = {
-        "rtone"         : TONES,
-        "ctone"         : TONES,
-        "dtcs"          : DTCS_CODES + DTCS_EXTRA_CODES,
-        "rx_dtcs"       : DTCS_CODES + DTCS_EXTRA_CODES,
+        "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,
@@ -525,8 +541,8 @@ class DVMemory(Memory):
         except Exception:
             self.dv_code = 0
 
-class Bank:
-    """Base class for a radio's Bank"""
+class MemoryMapping(object):
+    """Base class for a memory mapping"""
     def __init__(self, model, index, name):
         self._model = model
         self._index = index
@@ -536,75 +552,87 @@ class Bank:
         return self.get_name()
 
     def __repr__(self):
-        return "Bank-%s" % self._index
+        return "%s-%s" % (self.__class__.__name__, self._index)
 
     def get_name(self):
-        """Returns the static or user-adjustable bank name"""
+        """Returns the mapping name"""
         return self._name
 
     def get_index(self):
-        """Returns the immutable bank index (string or int)"""
+        """Returns the immutable index (string or int)"""
         return self._index
 
     def __eq__(self, other):
         return self.get_index() == other.get_index()
 
-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 MappingModel(object):
+    """Base class for a memory mapping model"""
 
-class BankModel:
-    """A bank model where one memory is in zero or one banks at any point"""
-    def __init__(self, radio):
+    def __init__(self, radio, name):
         self._radio = radio
+        self._name = name
 
-    def get_num_banks(self):
-        """Returns the number of banks (should be callable without
-        consulting the radio"""
-        raise Exception("Not implemented")
+    def get_name(self):
+        return self._name
 
-    def get_banks(self):
-        """Return a list of banks"""
-        raise Exception("Not implemented")
+    def get_num_mappings(self):
+        """Returns the number of mappings in the model (should be
+        callable without consulting the radio"""
+        raise NotImplementedError()
 
-    def add_memory_to_bank(self, memory, bank):
-        """Add @memory to @bank."""
-        raise Exception("Not implemented")
+    def get_mappings(self):
+        """Return a list of mappings"""
+        raise NotImplementedError()
 
-    def remove_memory_from_bank(self, memory, bank):
-        """Remove @memory from @bank.
-        Shall raise exception if @memory is not in @bank."""
-        raise Exception("Not implemented")
+    def add_memory_to_mapping(self, memory, mapping):
+        """Add @memory to @mapping."""
+        raise NotImplementedError()
 
-    def get_bank_memories(self, bank):
-        """Return a list of memories in @bank"""
-        raise Exception("Not implemented")
+    def remove_memory_from_mapping(self, memory, mapping):
+        """Remove @memory from @mapping.
+        Shall raise exception if @memory is not in @bank"""
+        raise NotImplementedError()
 
-    def get_memory_banks(self, memory):
-        """Returns a list of the banks that @memory is in"""
-        raise Exception("Not implemented")
+    def get_mapping_memories(self, mapping):
+        """Return a list of memories in @mapping"""
+        raise NotImplementedError()
 
-class BankIndexInterface:
-    """Interface for banks with index capabilities"""
+    def get_memory_mappings(self, memory):
+        """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):
-        """Returns a tuple (lo,hi) of the minimum and maximum bank indices"""
-        raise Exception("Not implemented")
+        """Returns a tuple (lo,hi) of the min and max mapping indices"""
+        raise NotImplementedError()
 
-    def get_memory_index(self, memory, bank):
-        """Returns the index of @memory in @bank"""
-        raise Exception("Not implemented")
+    def get_memory_index(self, memory, mapping):
+        """Returns the index of @memory in @mapping"""
+        raise NotImplementedError()
 
-    def set_memory_index(self, memory, bank, index):
-        """Sets the index of @memory in @bank to @index"""
-        raise Exception("Not implemented")
+    def set_memory_index(self, memory, mapping, index):
+        """Sets the index of @memory in @mapping to @index"""
+        raise NotImplementedError()
 
-    def get_next_bank_index(self, bank):
-        """Returns the next available bank index in @bank, or raises
+    def get_next_mapping_index(self, mapping):
+        """Returns the next available mapping index in @mapping, or raises
         Exception if full"""
-        raise Exception("Not implemented")
-
+        raise NotImplementedError()
 
 class MTOBankModel(BankModel):
     """A bank model where one memory can be in multiple banks at once """
@@ -615,7 +643,15 @@ def console_status(status):
     import sys
 
     sys.stderr.write("\r%s" % status)
-    
+
+
+class RadioPrompts:
+    """Radio prompt strings"""
+    experimental = None
+    pre_download = None
+    pre_upload = None
+    display_pre_upload_prompt_before_opening_port = True
+
 
 BOOLEAN = [True, False]
 
@@ -652,6 +688,7 @@ class RadioFeatures:
         "valid_name_length"   : 0,
         "valid_cross_modes"   : [],
         "valid_dtcs_pols"     : [],
+        "valid_dtcs_codes"    : [],
         "valid_special_chans" : [],
 
         "has_sub_devices"     : BOOLEAN,
@@ -771,6 +808,8 @@ class RadioFeatures:
                   "Supported tone cross modes")
         self.init("valid_dtcs_pols", ["NN", "RN", "NR", "RR"],
                   "Supported DTCS polarities")
+        self.init("valid_dtcs_codes", list(DTCS_CODES),
+                  "Supported DTCS codes")
         self.init("valid_special_chans", [],
                   "Supported special channel names")
 
@@ -833,6 +872,13 @@ class RadioFeatures:
                                       mem.dtcs_polarity)
             msgs.append(msg)
 
+        if self.valid_dtcs_codes and \
+                mem.dtcs not in self.valid_dtcs_codes:
+            msg = ValidationError("DTCS Code %03i not supported" % mem.dtcs)
+        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)
@@ -893,7 +939,7 @@ class ValidationError(ValidationMessage):
     """A fatal error during memory validation"""
     pass
 
-class Radio:
+class Radio(object):
     """Base class for all Radio drivers"""
     BAUD_RATE = 9600
     HARDWARE_FLOW = False
@@ -918,6 +964,11 @@ class Radio:
         """Return a printable name for this radio"""
         return "%s %s" % (cls.VENDOR, cls.MODEL)
 
+    @classmethod
+    def get_prompts(cls):
+        """Return a set of strings for use in prompts"""
+        return RadioPrompts()
+
     def set_pipe(self, pipe):
         """Set the serial object to be used for communications"""
         self.pipe = pipe
@@ -941,9 +992,14 @@ class Radio:
         """Set the memory object @memory"""
         pass
 
-    def get_bank_model(self):
-        """Returns either a BankModel or None if not supported"""
-        return None
+    def get_mapping_models(self):
+        """Returns a list of MappingModel objects (or an empty list)"""
+        if hasattr(self, "get_bank_model"):
+            # FIXME: Backwards compatibility for old bank models
+            bank_model = self.get_bank_model()
+            if bank_model:
+                return [bank_model]
+        return []
 
     def get_raw_memory(self, number):
         """Return a raw string describing the memory at @number"""
@@ -1300,10 +1356,10 @@ def split_tone_encode(mem):
     "DTCS", 23, "N"
     """
 
-    txmode = txval = None
-    txpol = mem.dtcs_polarity[0]
-    rxmode = rxval = None
-    rxpol = mem.dtcs_polarity[1]
+    txmode = ''
+    rxmode = ''
+    txval = None
+    rxval = None
     
     if mem.tmode == "Tone":
         txmode = "Tone"
@@ -1325,5 +1381,14 @@ def split_tone_encode(mem):
         elif rxmode == "DTCS":
             rxval = mem.rx_dtcs
 
+    if txmode == "DTCS":
+        txpol = mem.dtcs_polarity[0]
+    else:
+        txpol = None
+    if rxmode == "DTCS":
+        rxpol = mem.dtcs_polarity[1]
+    else:
+        rxpol = None
+
     return ((txmode, txval, txpol),
             (rxmode, rxval, rxpol))
diff --git a/chirp/elib_intl.py b/chirp/elib_intl.py
new file mode 100644
index 0000000..6e7cafa
--- /dev/null
+++ b/chirp/elib_intl.py
@@ -0,0 +1,495 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © 2007-2010 Dieter Verfaillie <dieterv at optionexplicit.be>
+#
+# This file is part of elib.intl.
+#
+# elib.intl is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# elib.intl 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with elib.intl. If not, see <http://www.gnu.org/licenses/>.
+
+
+'''
+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:
+
+ - 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.
+
+The elib.intl module defines the following functions:
+'''
+
+
+__all__ = ['install', 'install_module']
+__version__ = '0.0.3'
+__docformat__ = 'restructuredtext'
+
+
+import os
+import sys
+import locale
+import gettext
+
+from logging import getLogger
+
+
+logger = getLogger('elib.intl')
+
+
+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.
+
+    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
+    '''
+    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
+
+    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.
+
+    Works on Microsoft Windows 2000 and up.
+    '''
+    if sys.platform == 'win32' or sys.platform == 'nt':
+        # Start with nothing
+        lang = None
+
+        # Check the LANGUAGE environment variable
+        lang = os.getenv('LANGUAGE')
+
+        if lang is None:
+            # Start with nothing
+            lcid = None
+
+            try:
+                from ctypes import windll
+                lcid = windll.kernel32.GetUserDefaultUILanguage()
+            except:
+                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))
+
+        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.
+    '''
+
+    if sys.platform == 'win32' or sys.platform == 'nt':
+        from ctypes import windll
+        from ctypes import cdll
+        from ctypes.util import find_msvcrt
+
+        # Update Python's copy of the environment variables
+        os.environ[name] = value
+
+        # Update the copy maintained by Windows (so SysInternals Process Explorer sees it)
+        try:
+            result = windll.kernel32.SetEnvironmentVariableW(name, value)
+            if result == 0: raise Warning
+        except Exception:
+            logger.debug('Failed to set environment variable \'%s\' (\'kernel32.SetEnvironmentVariableW\')' % name)
+        else:
+            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
+        except Exception:
+            logger.debug('Failed to set environment variable \'%s\' (\'msvcrt._putenv\')' % name)
+        else:
+            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)
+            result = cdll.LoadLibrary(msvcrt)._putenv('%s=%s' % (name, value))
+            if result != 0: raise Warning
+        except Exception:
+            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))
+
+def _dugettext(domain, message):
+    '''
+    :param domain: translation domain
+    :param message: message to translate
+    :returns: the translated message
+
+    Unicode version of :func:`gettext.dgettext`.
+    '''
+    try:
+        t = gettext.translation(domain, gettext._localedirs.get(domain, None),
+                                codeset=gettext._localecodesets.get(domain))
+    except IOError:
+        return 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
+
+    Private function doing all the work for the :func:`elib.intl.install` and
+    :func:`elib.intl.install_module` functions.
+    '''
+    # prep locale system
+    if asglobal:
+        locale.setlocale(locale.LC_ALL, '')
+
+        # on windows systems, set the LANGUAGE environment variable
+        if sys.platform == 'win32' or sys.platform == 'nt':
+            _putenv('LANGUAGE', _getscreenlanguage())
+
+    # The locale module on Max OS X lacks bindtextdomain so we specifically
+    # test on linux2 here. See commit 4ae8b26fd569382ab66a9e844daa0e01de409ceb
+    if sys.platform == 'linux2':
+        locale.bindtextdomain(domain, localedir)
+        locale.bind_textdomain_codeset(domain, 'UTF-8')
+        locale.textdomain(domain)
+
+    # initialize Python's gettext interface
+    gettext.bindtextdomain(domain, localedir)
+    gettext.bind_textdomain_codeset(domain, 'UTF-8')
+
+    if asglobal:
+        gettext.textdomain(domain)
+
+    # on windows systems, initialize libintl
+    if sys.platform == 'win32' or sys.platform == 'nt':
+        from ctypes import cdll
+        libintl = cdll.intl
+        libintl.bindtextdomain(domain, localedir)
+        libintl.bind_textdomain_codeset(domain, 'UTF-8')
+
+        if asglobal:
+            libintl.textdomain(domain)
+
+        del libintl
+
+def install(domain, localedir):
+    '''
+    :param domain: translation domain
+    :param localedir: locale directory
+
+    Installs the function _() in Python’s builtin namespace, based on
+    domain and localedir. Codeset is always UTF-8.
+
+    As seen below, you usually mark the strings in your application that are
+    candidates for translation, by wrapping them in a call to the _() function,
+    like this:
+
+    .. sourcecode:: python
+
+        import elib.intl
+        elib.intl.install('myapplication', '/path/to/usr/share/locale')
+        print _('This string will be translated.')
+
+    Note that this is only one way, albeit the most convenient way,
+    to make the _() function available to your application. Because it affects
+    the entire application globally, and specifically Python’s built-in
+    namespace, localized modules should never install _(). Instead, you should
+    use :func:`elib.intl.install_module` to make _() available to your module.
+    '''
+    _install(domain, localedir, True)
+    gettext.install(domain, localedir, unicode=True)
+
+def install_module(domain, localedir):
+    '''
+    :param domain: translation domain
+    :param localedir: locale directory
+    :returns: an anonymous function object, based on domain and localedir.
+              Codeset is always UTF-8.
+
+    You may find this function usefull when writing localized modules.
+    Use this code to make _() available to your module:
+
+    .. sourcecode:: python
+
+        import elib.intl
+        _ = 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
+    file and import the _() function from the package namespace as needed.
+    '''
+    _install(domain, localedir, False)
+    return lambda message: _dugettext(domain, message)
diff --git a/chirp/ft1802.py b/chirp/ft1802.py
index 9a806c0..af83a51 100644
--- a/chirp/ft1802.py
+++ b/chirp/ft1802.py
@@ -26,6 +26,7 @@
 from chirp import chirp_common, bitwise, directory, yaesu_clone
 from chirp.settings import RadioSetting, RadioSettingGroup, \
     RadioSettingValueBoolean
+from textwrap import dedent
 
 MEM_FORMAT = """
 #seekto 0x06ea;
@@ -88,6 +89,21 @@ class FT1802Radio(yaesu_clone.YaesuCloneModeRadio):
     _block_lengths = [10, 8001]
     _memsize = 8011
 
+    @classmethod
+    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."""))
+        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)."""))
+        return rp
+
     def get_features(self):
         rf = chirp_common.RadioFeatures()
 
diff --git a/chirp/ft60.py b/chirp/ft60.py
index d51d83b..9a882ab 100644
--- a/chirp/ft60.py
+++ b/chirp/ft60.py
@@ -16,6 +16,7 @@
 import time
 from chirp import chirp_common, yaesu_clone, memmap, bitwise, directory
 from chirp import errors
+from textwrap import dedent
 
 ACK = "\x06"
 
@@ -71,7 +72,7 @@ def _upload(radio):
         _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)
+            raise Exception(_("Radio did not ack block %i") % i)
 
         if radio.status_fn:
             status = chirp_common.Status()
@@ -96,11 +97,12 @@ def _decode_freq(freqraw):
 
 def _encode_freq(freq):
     freqraw = freq / 10000
-    if ((freq / 1000) % 10) == 5:
-        freqraw += 800000
+    flags = 0x00
+    if ((freq / 1000) % 10) >= 5:
+        flags += 0x80
     if chirp_common.is_fractional_step(freq):
-        freqraw += 400000
-    return freqraw
+        flags += 0x40
+    return freqraw, flags
 
 
 MEM_FORMAT = """
@@ -166,6 +168,27 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
 
     _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)
@@ -268,9 +291,11 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
             _mem.used = 1
             print "Wiped"
 
-        _mem.freq = _encode_freq(mem.freq)
+        _mem.freq, flags = _encode_freq(mem.freq)
+        _mem.freq[0].set_bits(flags)
         if mem.duplex == "split":
-            _mem.tx_freq = _encode_freq(mem.offset)
+            _mem.tx_freq, flags = _encode_freq(mem.offset)
+            _mem.tx_freq[0].set_bits(flags)
             _mem.offset = 0
         else:
             _mem.tx_freq = 0
diff --git a/chirp/ft7800.py b/chirp/ft7800.py
index f12b95c..6fd5601 100644
--- a/chirp/ft7800.py
+++ b/chirp/ft7800.py
@@ -16,23 +16,88 @@
 import time
 from chirp import chirp_common, yaesu_clone, memmap, directory
 from chirp import bitwise, errors
+from textwrap import dedent
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettingValueString
+import os, re
 
 from collections import defaultdict
 
+if os.getenv("CHIRP_DEBUG"):
+    CHIRP_DEBUG = True
+else:
+    CHIRP_DEBUG = False
+
 ACK = chr(0x06)
 
 MEM_FORMAT = """
-#seekto 0x04C8;
+#seekto 0x002A;
+u8  banks_unk2;
+u8  current_channel;
+u8  unk3;
+u8  unk4;
+u8  current_menu;
+
+#seekto 0x0035;
+u8  banks_unk1;
+
+#seekto 0x00C8;
 struct {
+    u8  memory[16];
+} dtmf[16];
+
+#seekto 0x003A;
+struct {
+    u8  apo;
+    u8  tot;
+    u8  lock:3,
+        arts_interval:1,
+        unk1a:1,
+        prog_panel_acc:3;
+    u8  prog_p1;
+    u8  prog_p2;
+    u8  prog_p3;
+    u8  prog_p4;
+    u8  rf_sql;
+    u8  inet_dtmf_mem:4,
+        inet_dtmf_digit:4;
+    u8  arts_cwid_enable:1,
+        prog_tone_vm:1,
+        unk2a:1,
+        hyper_write:2,
+        memory_only:1,
+        dimmer:2;
+    u8  beep_scan:1,
+        beep_edge:1,
+        beep_key:1,
+        unk3a:1,
+        inet_mode:1,
+        unk3b:1,
+        dtmf_speed:2;
+    u8  dcs_polarity:2,
+        smart_search:1,
+        priority_revert:1,
+        unk4a:1,
+        dtmf_delay:3;
+    u8  unk5a:3,
+        microphone_type:1,
+        scan_resume:1,
+        unk5b:1,
+        arts_mode:2;
+    u8  unk6;
+} settings;
+
+struct mem_struct {
   u8 used:1,
      unknown1:1,
      mode:2,
      unknown2:1,
      duplex:3;
   bbcd freq[3];
-  u8 unknown3:1,
+  u8 clockshift:1,
      tune_step:3,
-     unknown5:2,
+     unknown5:2, // TODO: tmode has extended settings, at least 4 bits
      tmode:2;
   bbcd split[3];
   u8 power:2,
@@ -42,7 +107,19 @@ struct {
   u8 unknown7[2];
   u8 offset;
   u8 unknown9[3];
-} memory[1000];
+};
+
+#seekto 0x0048;
+struct mem_struct vfos[5];
+
+#seekto 0x01C8;
+struct mem_struct homes[5];
+
+#seekto 0x0218;
+u8  arts_cwid[6];
+
+#seekto 0x04C8;
+struct mem_struct memory[1000];
 
 #seekto 0x4988;
 struct {
@@ -82,6 +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),
@@ -181,7 +260,33 @@ class FTx800Radio(yaesu_clone.YaesuCloneModeRadio):
     """Base class for FT-7800,7900,8800,8900 radios"""
     BAUD_RATE = 9600
     VENDOR = "Yaesu"
-
+    MODES = list(MODES)
+
+    @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."""))
+        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)."""))
+        return rp
+        
     def get_features(self):
         rf = chirp_common.RadioFeatures()
         rf.memory_bounds = (1, 999)
@@ -288,7 +393,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.mode = MODES[_mem.mode]
+        mem.mode = self.MODES[_mem.mode]
         mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
         if self.get_features().has_tuning_step:
             mem.tuning_step = STEPS[_mem.tune_step]
@@ -315,7 +420,7 @@ 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.mode = MODES.index(mem.mode)
+        _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)
@@ -336,7 +441,7 @@ class FTx800Radio(yaesu_clone.YaesuCloneModeRadio):
 class FT7800BankModel(chirp_common.BankModel):
     """Yaesu FT-7800/7900 bank model"""
     def __init__(self, radio):
-        chirp_common.BankModel.__init__(self, radio)
+        super(FT7800BankModel, self).__init__(radio)
         self.__b2m_cache = defaultdict(list)
         self.__m2b_cache = defaultdict(list)
 
@@ -344,24 +449,24 @@ class FT7800BankModel(chirp_common.BankModel):
         if self.__b2m_cache:
             return
 
-        for bank in self.get_banks():
+        for bank in self.get_mappings():
             self.__b2m_cache[bank.index] = self._get_bank_memories(bank)
             for memnum in self.__b2m_cache[bank.index]:
                 self.__m2b_cache[memnum].append(bank.index)
 
-    def get_num_banks(self):
+    def get_num_mappings(self):
         return 20
 
-    def get_banks(self):
+    def get_mappings(self):
         banks = []
-        for i in range(0, self.get_num_banks()):
+        for i in range(0, self.get_num_mappings()):
             bank = chirp_common.Bank(self, "%i" % i, "BANK-%i" % (i + 1))
             bank.index = i
             banks.append(bank)
 
         return banks
 
-    def add_memory_to_bank(self, memory, bank):
+    def add_memory_to_mapping(self, memory, bank):
         self.__precache()
 
         index = memory.number - 1
@@ -371,7 +476,7 @@ class FT7800BankModel(chirp_common.BankModel):
         self.__m2b_cache[memory.number].append(bank.index)
         self.__b2m_cache[bank.index].append(memory.number)
 
-    def remove_memory_from_bank(self, memory, bank):
+    def remove_memory_from_mapping(self, memory, bank):
         self.__precache()
 
         index = memory.number - 1
@@ -395,32 +500,34 @@ class FT7800BankModel(chirp_common.BankModel):
                 memories.append(i + 1)
         return memories
 
-    def get_bank_memories(self, bank):
+    def get_mapping_memories(self, bank):
         self.__precache()
 
         return [self._radio.get_memory(n)
                 for n in self.__b2m_cache[bank.index]]
 
-    def get_memory_banks(self, memory):
+    def get_memory_mappings(self, memory):
         self.__precache()
 
-        _banks = self.get_banks()
+        _banks = self.get_mappings()
         return [_banks[b] for b in self.__m2b_cache[memory.number]]
 
 @directory.register
 class FT7800Radio(FTx800Radio):
     """Yaesu FT-7800"""
-    MODEL = "FT-7800"
+    MODEL = "FT-7800/7900"
 
     _model = "AH016"
     _memsize = 31561
-
+    _block_lengths = [8, 31552, 1]
+    
     def get_bank_model(self):
         return FT7800BankModel(self)
 
     def get_features(self):
         rf = FTx800Radio.get_features(self)
         rf.has_bank = True
+        rf.has_settings = True
         return rf
 
     def set_memory(self, memory):
@@ -428,9 +535,209 @@ class FT7800Radio(FTx800Radio):
             self._wipe_memory_banks(memory)
         FTx800Radio.set_memory(self, memory)
 
-class FT7900Radio(FT7800Radio):
-    """Yaesu FT-7900"""
-    MODEL = "FT-7900"
+    def _decode_chars(self, inarr):
+        if CHIRP_DEBUG:
+            print "@_decode_chars, type: %s" % type(inarr)
+            print 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
+        outarr = []
+        instr = str(instr)
+        for i in range(length):
+            if i < len(instr):
+                outarr.append(CHARSET.index(instr[i]))
+            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")
+        prog = RadioSettingGroup("prog", "Programmable Buttons")
+        top = RadioSettingGroup("top", "All Settings",
+                basic, dtmf, arts, prog)
+
+        basic.append( RadioSetting("priority_revert", "Priority Revert",
+                RadioSettingValueBoolean(_settings.priority_revert)))
+
+        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)",
+                RadioSettingValueList(opts, opts[_settings.apo])))
+
+        basic.append( RadioSetting("beep_scan", "Beep: Scan",
+                RadioSettingValueBoolean(_settings.beep_scan)))
+
+        basic.append( RadioSetting("beep_edge", "Beep: Edge",
+                RadioSettingValueBoolean(_settings.beep_edge)))
+
+        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",
+                RadioSettingValueList(opts, opts[_settings.dcs_polarity])))
+        
+        opts = ["off", "dim 1", "dim 2", "dim 3"]
+        basic.append( RadioSetting("dimmer", "Dimmer",
+                RadioSettingValueList(opts, opts[_settings.dimmer])))
+
+        opts = ["manual", "auto", "1-auto"]
+        basic.append( RadioSetting("hyper_write", "Hyper Write",
+                RadioSettingValueList(opts, opts[_settings.hyper_write])))
+        
+        opts = ["", "key", "dial", "key+dial", "ptt", 
+                "ptt+key", "ptt+dial", "all"]
+        basic.append( RadioSetting("lock", "Lock mode",
+                RadioSettingValueList(opts, opts[_settings.lock]))) 
+
+        opts = ["MH-42", "MH-48"]
+        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",
+                RadioSettingValueList(opts, opts[_settings.rf_sql])))
+
+        opts = ["time", "hold", "busy"]
+        basic.append( RadioSetting("scan_resume", "Scan Resume",
+                RadioSettingValueList(opts, opts[_settings.scan_resume])))
+
+        opts = ["single", "continuous"]
+        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)",
+                RadioSettingValueList(opts, opts[_settings.tot])))
+
+        # dtmf tab
+
+        opts = ["50", "100", "250", "450", "750", "1000"]
+        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)",
+                RadioSettingValueList(opts, opts[_settings.dtmf_speed])))
+
+        for i in range(16):
+            name = "dtmf%02d" % i
+            dtmfsetting = self._memobj.dtmf[i]
+            dtmfstr = ""
+            for c in dtmfsetting.memory:
+                if c == 0xFF:
+                    break
+                if c < len(DTMFCHARSET):
+                    dtmfstr += DTMFCHARSET[c]
+            if CHIRP_DEBUG:
+                print dtmfstr
+            dtmfentry = RadioSettingValueString(0, 16, dtmfstr)
+            dtmfentry.set_charset(DTMFCHARSET + list(" "))
+            rs = RadioSetting(name, name.upper(), dtmfentry)
+            dtmf.append(rs) 
+
+        # arts tab        
+
+        opts = ["off", "in range", "always"]
+        arts.append( RadioSetting("arts_mode", "ARTS beep",
+                RadioSettingValueList(opts, opts[_settings.arts_mode])))
+
+        opts = ["15", "25"]
+        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_cwid = self._memobj.arts_cwid
+        cwid = RadioSettingValueString(0, 16, 
+                self._decode_chars(_arts_cwid.get_value()))
+        cwid.set_charset(CHARSET)
+        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)",
+                RadioSettingValueList(opts, opts[_settings.prog_panel_acc])))
+
+        opts = ["Reverse", "Home"]
+        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" ]
+
+        prog.append( RadioSetting("prog_p1", "P1",
+                RadioSettingValueList(opts, opts[_settings.prog_p1])))
+
+        prog.append( RadioSetting("prog_p2", "P2",
+                RadioSettingValueList(opts, opts[_settings.prog_p2])))
+
+        prog.append( RadioSetting("prog_p3", "P3",
+                RadioSettingValueList(opts, opts[_settings.prog_p3])))
+
+        prog.append( RadioSetting("prog_p4", "P4",
+                RadioSettingValueList(opts, opts[_settings.prog_p4])))
+
+        return top
+
+    def set_settings(self, uisettings):
+        for element in uisettings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+            if not element.changed():
+                continue
+            try:
+                _settings = self._memobj.settings
+                setting = element.get_name()
+                if re.match('dtmf\d', setting):
+                    # set dtmf fields
+                    dtmfstr = str(element.value).strip()
+                    newval = []
+                    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
+                    idx = int(setting[-2:])
+                    _settings = self._memobj.dtmf[idx]
+                    _settings.memory = newval
+                    continue                
+                if setting == "arts_cwid":
+                    oldval = self._memobj.arts_cwid
+                    newval = self._encode_chars(newval.get_value(), 6)
+                    self._memobj.arts_cwid = newval
+                    continue
+                # normal settings
+                newval = element.value
+                oldval = getattr(_settings, setting)
+                if CHIRP_DEBUG:
+                    print "Setting %s(%s) <= %s" % (setting,
+                                    oldval, newval)
+                setattr(_settings, setting, newval)
+            except Exception, e:
+                print element.get_name()
+                raise
 
 MEM_FORMAT_8800 = """
 #seekto 0x%X;
@@ -473,7 +780,7 @@ u8 checksum;
 """
 
 class FT8800BankModel(FT7800BankModel):
-    def get_num_banks(self):
+    def get_num_mappings(self):
         return 10
 
 @directory.register
@@ -489,6 +796,31 @@ class FT8800Radio(FTx800Radio):
 
     _memstart = 0x0000
 
+    @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 "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 
+                 for a moment, then the "CLONE" notation will appear.
+            6. <b>After clicking OK</b>, press the "left" [V/M] key to 
+                 send image."""))
+        rp.pre_upload = _(dedent("""\
+            1. Turn radio off.
+            2. Connect cable to DATA jack.
+            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 
+                 for a moment, then the "CLONE" notation will appear.
+            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 == ""
@@ -607,6 +939,8 @@ class FT8900Radio(FT8800Radio):
     _memsize = 14793
     _block_lengths = [8, 14784, 1]
 
+    MODES = ["FM", "NFM", "AM"]
+
     def process_mmap(self):
         self._memobj = bitwise.parse(MEM_FORMAT_8900, self._mmap)
 
@@ -614,7 +948,7 @@ class FT8900Radio(FT8800Radio):
         rf = FT8800Radio.get_features(self)
         rf.has_sub_devices = False
         rf.has_bank = False
-        rf.valid_modes = MODES
+        rf.valid_modes = self.MODES
         rf.valid_bands = [( 28000000,  29700000),
                           ( 50000000,  54000000),
                           (108000000, 180000000),
diff --git a/chirp/ft817.py b/chirp/ft817.py
index 7f877e9..de73ff5 100644
--- a/chirp/ft817.py
+++ b/chirp/ft817.py
@@ -22,6 +22,7 @@ from chirp.settings import RadioSetting, RadioSettingGroup, \
     RadioSettingValueInteger, RadioSettingValueList, \
     RadioSettingValueBoolean, RadioSettingValueString
 import time, os
+from textwrap import dedent
 
 CMD_ACK = 0x06
 
@@ -31,6 +32,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
     BAUD_RATE = 9600
     MODEL = "FT-817"
     _model = ""
+    _US_model = False
 
     DUPLEX = ["", "-", "+", "split"]
     # narrow modes has to be at end
@@ -46,7 +48,8 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
                    (76000000, 108000000), (108000000, 137000000),
                    (137000000, 154000000), (420000000, 470000000)] 
 
-    CHARSET = [chr(x) for x in range(0, 256)]
+    CHARSET = list(chirp_common.CHARSET_ASCII)
+    CHARSET.remove("\\")
 
     # Hi not used in memory
     POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00),
@@ -159,12 +162,12 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
                 pkt_mic:7;
             u8  unknown14:1,
                 pkt9600_mic:7;
-            ul16 dig_shift;
-            ul16 dig_disp;
-            u8  r_lsb_car;
-            u8  r_usb_car;
-            u8  t_lsb_car;
-            u8  t_usb_car;
+            il16 dig_shift;
+            il16 dig_disp;
+            i8  r_lsb_car;
+            i8  r_usb_car;
+            i8  t_lsb_car;
+            i8  t_usb_car;
             u8  unknown15:2,
                 menu_item:6;
             u8  unknown16:4,
@@ -271,9 +274,33 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
     
     SPECIAL_MEMORIES_REV = dict(zip(SPECIAL_MEMORIES.values(),
                                     SPECIAL_MEMORIES.keys()))
-
-    def _read(self, block, blocknum):
-        for _i in range(0, 60):
+                                    
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.pre_download = _(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. <b>After clicking OK</b>, press the [A] 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 [C] key ("RX" will appear on the LCD)."""))
+        return rp
+
+    def _read(self, block, blocknum, lastblock):
+        # be very patient at first block
+        if blocknum == 0:
+            attempts = 60
+        else:
+            attempts = 5
+        for _i in range(0, attempts):
             data = self.pipe.read(block+2)
             if data:
                 break
@@ -287,7 +314,13 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
                                     checksum.get_calculated(data), blocknum))
             data = data[1:block+1] # Chew away the block number and the checksum
         else:
-            raise Exception("Unable to read block %02X expected %i got %i" %
+            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."))
+            else:
+                raise Exception("Unable to read block %02X expected %i got %i" %
                                 (blocknum, block+2, len(data)))
     
         if os.getenv("CHIRP_DEBUG"):
@@ -303,8 +336,9 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         data = ""
         blocks = 0
         status = chirp_common.Status()
-        status.msg = "Cloning from radio"
-        status.max = len(self._block_lengths) + 39
+        status.msg = _("Cloning from radio")
+        nblocks = len(self._block_lengths) + 39
+        status.max = nblocks
         for block in self._block_lengths:
             if blocks == 8:
                 # repeated read of 40 block same size (memory area)
@@ -312,11 +346,22 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
             else:
                 repeat = 1
             for _i in range(0, repeat):	
-                data += self._read(block, blocks)
+                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, "
+                            "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)
     
@@ -329,7 +374,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         blocks = 0
         pos = 0
         status = chirp_common.Status()
-        status.msg = "Cloning to radio"
+        status.msg = _("Cloning to radio")
         status.max = len(self._block_lengths) + 39
         for block in self._block_lengths:
             if blocks == 8:
@@ -359,7 +404,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
                 if not buf or buf[0] != chr(CMD_ACK):
                     if os.getenv("CHIRP_DEBUG"):
                         print util.hexprint(buf)
-                    raise Exception("Radio did not ack block %i" % blocks)
+                    raise Exception(_("Radio did not ack block %i") % blocks)
                 pos += block
                 blocks += 1
                 status.cur = blocks
@@ -459,21 +504,21 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
                                self.LAST_VFOA_INDEX - 1,
                                -1):
             _mem = self._memobj.vfoa[-self.LAST_VFOA_INDEX + mem.number]
-            immutable = ["number", "skip", "rtone", "ctone", "extd_number",
+            immutable = ["number", "skip", "extd_number",
                          "name", "dtcs_polarity", "power", "comment"]
         elif mem.number in range(self.FIRST_VFOB_INDEX,
                                  self.LAST_VFOB_INDEX - 1,
                                  -1):
             _mem = self._memobj.vfob[-self.LAST_VFOB_INDEX + mem.number]
-            immutable = ["number", "skip", "rtone", "ctone", "extd_number",
+            immutable = ["number", "skip", "extd_number",
                          "name", "dtcs_polarity", "power", "comment"]
         elif mem.number in range(-2, -6, -1):
             _mem = self._memobj.home[5 + mem.number]
-            immutable = ["number", "skip", "rtone", "ctone", "extd_number",
-                         "dtcs_polarity", "power", "comment"]
+            immutable = ["number", "skip", "extd_number",
+                         "name", "dtcs_polarity", "power", "comment"]
         elif mem.number == -1:
             _mem = self._memobj.qmb
-            immutable = ["number", "skip", "rtone", "ctone", "extd_number",
+            immutable = ["number", "skip", "extd_number",
                          "name", "dtcs_polarity", "power", "comment"]
         elif mem.number in self.SPECIAL_PMS.values():
             bitindex = -self.LAST_PMS_INDEX + mem.number
@@ -556,9 +601,8 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         mem.number = number
         if not used:
             mem.empty = True
-        if not valid:
-            mem.empty = True
-            return mem
+            if not valid:
+                return mem
 
         return self._get_memory(mem, _mem)
 
@@ -613,9 +657,15 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
 
         if _mem.tag_on_off == 1:
             for i in _mem.name:
-                if i == "\xFF":
+                if i == 0xFF:
                     break
-                mem.name += self.CHARSET[i]
+                if chr(i) in self.CHARSET:
+                    mem.name += chr(i)
+                else:
+                    # radio have some graphical chars that are not supported
+                    # we replace those with a *
+                    print "Replacing char %x with *" % i
+                    mem.name += "*"
             mem.name = mem.name.rstrip()
         else:
             mem.name = ""
@@ -681,8 +731,11 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
         _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?
         for i in range(0, 8):
-            _mem.name[i] = self.CHARSET.index(mem.name.ljust(8)[i])
+            _mem.name[i] = ord(mem.name.ljust(8)[i])
         
         for setting in mem.extra:
             setattr(_mem, setting.get_name(), setting.value)
@@ -718,10 +771,10 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
                           RadioSettingValueBoolean(_settings.ars_144))
         basic.append(rs)
         rs = RadioSetting("ars_430", "430 ARS",
-                          RadioSettingValueBoolean(_settings.ars_144))
+                          RadioSettingValueBoolean(_settings.ars_430))
         basic.append(rs)
         rs = RadioSetting("pkt9600_mic", "Paket 9600 mic level",
-                          RadioSettingValueInteger(0, 100, _settings.am_mic))
+                          RadioSettingValueInteger(0, 100, _settings.pkt9600_mic))
         packet.append(rs)
         options = ["enable", "disable"]
         rs = RadioSetting("disable_amfm_dial", "AM&FM Dial",
@@ -1041,6 +1094,7 @@ class FT817NDUSRadio(FT817Radio):
     MODEL = "FT-817ND (US)"
 
     _model = ""
+    _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]
diff --git a/chirp/ft857.py b/chirp/ft857.py
index 57db2d0..f86273a 100644
--- a/chirp/ft857.py
+++ b/chirp/ft857.py
@@ -17,6 +17,11 @@
 """FT857 - FT857/US management module"""
 
 from chirp import ft817, chirp_common, errors, directory
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettingValueString
+import os
+from textwrap import dedent
 
 @directory.register
 class FT857Radio(ft817.FT817Radio):
@@ -56,6 +61,10 @@ class FT857Radio(ft817.FT817Radio):
                    (76000000, 108000000), (108000000, 137000000),
                    (137000000, 164000000), (420000000, 470000000)]
 
+    CHARSET = list(chirp_common.CHARSET_ASCII)
+    for i in "\\{|}":
+        CHARSET.remove(i)
+
     MEM_FORMAT = """
         struct mem_struct{
         u8   tag_on_off:1,
@@ -97,6 +106,172 @@ class FT857Radio(ft817.FT817Radio):
         u8   name[8];
         };
         
+        #seekto 0x00;
+        struct {
+            u16 radioconfig;
+            u8  mem_vfo:2,
+                m_tune:1,
+                home:1,
+                pms_tune:1,
+                qmb:1,
+                mt_qmb:1,
+                vfo_ab:1;
+            u8  unknown;
+            u8  fst:1,
+                lock:1,
+                nb:1,
+                unknown1:2,
+                disp:1,
+                agc:2;
+            u8  vox:1,
+                unknown2:1,
+                bk:1,
+                kyr:1,
+                cw_speed_unit:1,
+                cw_key_rev:1,
+                pwr_meter_mode:2;
+            u8  vfo_b_freq_range:4,
+                vfo_a_freq_range:4;
+            u8  unknown3;
+            u8  disp_mode:2,
+                unknown4:2,
+                disp_contrast:4;
+            u8  unknown5:4,
+                clar_dial_sel:2,
+                beep_tone:2;
+            u8  arts_beep:2,
+                dial_step:1,
+                arts_id:1,
+                unknown6:1,
+                pkt_rate:1,
+                unknown7:2;
+            u8  unknown8:2,
+                lock_mode:2,
+                unknown9:1,
+                cw_pitch:3;
+            u8  sql_rf_gain:1,
+                ars_144:1,
+                ars_430:1,
+                cw_weight:5;
+            u8  cw_delay;
+            u8  cw_delay_hi:1
+                cw_sidetone:7;
+            u8  unknown10:2,
+                cw_speed:6;
+            u8  disable_amfm_dial:1,
+                vox_gain:7;
+            u8  cat_rate:2,
+                emergency:1,
+                vox_delay:5;
+            u8  dig_mode:3,
+                mem_group:1,
+                unknown11:1,
+                apo_time:3;
+            u8  dcs_inv:2,
+                unknown12:1,
+                tot_time:5;
+            u8  mic_scan:1,
+                ssb_mic:7;
+            u8  cw_paddle:1,
+                am_mic:7;
+            u8  unknown13:1,
+                fm_mic:7;
+            u8  unknown14:1,
+                dig_mic:7;
+            u8  extended_menu:1,
+                pkt1200:7;
+            u8  unknown15:1,
+                pkt9600:7;
+            il16 dig_shift;
+            il16 dig_disp;
+            i8  r_lsb_car;
+            i8  r_usb_car;
+            i8  t_lsb_car;
+            i8  t_usb_car;
+            u8  unknown16:1,
+                menu_item:7;
+            u8  unknown17[5];
+            u8  unknown18:1,
+                mtr_peak_hold:1,
+                mic_sel:2,
+                cat_lin_tun:2,
+                unknown19:1,
+                split_tone:1;
+            u8  unknown20:1,
+                beep_vol:7;
+            u8  unknown21:1,
+                dig_vox:7;
+            u8  ext_menu:1,
+                home_vfo:1,
+                scan_mode:2,
+                scan_resume:4;
+            u8  cw_auto_mode:1,
+                cw_training:2,
+                cw_qsk:3,
+                cw_bfo:2;
+            u8  dsp_nr:4,
+                dsp_bpf:2,
+                dsp_mic_eq:2;
+            u8  unknown22:3,
+                dsp_lpf:5;
+            u8  mtr_atx_sel:3,
+                unknown23:1,
+                dsp_hpf:4;
+            u8  unknown24:2,
+                disp_intensity:2,
+                unknown25:1,
+                disp_color:3;
+            u8  unknown26:1,
+                disp_color_vfo:1,
+                disp_color_mtr:1,
+                disp_color_mode:1,
+                disp_color_memgrp:1,
+                unknown27:1,
+                disp_color_band:1,
+                disp_color_arts:1;
+            u8  unknown28:3,
+                disp_color_fix:5;
+            u8  unknown29:1,
+                nb_level:7;
+            u8  unknown30:1,
+                proc_level:7;
+            u8  unknown31:1,
+                rf_power_hf:7;
+            u8  unknown32:2,
+                tuner_atas:3,
+                mem_vfo_dial_mode:3;
+            u8  pg_a;
+            u8  pg_b;
+            u8  pg_c;
+            u8  pg_acc;
+            u8  pg_p1;
+            u8  pg_p2;
+            u8  unknown33:3,
+                xvtr_sel:2,
+                unknown33_1:2,
+                op_filter1:1;
+            u8  unknown34:6,
+                tx_if_filter:2;
+            u8  unknown35:3,
+                xvtr_a_negative:1,
+                xvtr_b_negative:1,
+                mtr_arx_sel:3;
+            u8  beacon_time;
+            u8  unknown36[2];
+            u8  dig_vox_enable:1,
+                unknown37:2,
+                scope_peakhold:1,
+                scope_width:2,
+                proc:1,
+                unknown38:1;
+            u8  unknown39:1,
+                rf_power_6m:7;
+            u8  unknown40:1,
+                rf_power_vhf:7;
+            u8  unknown41:1,
+                rf_power_uhf:7;
+        } settings;
+
         #seekto 0x54;
         struct mem_struct vfoa[16];
         struct mem_struct vfob[16];
@@ -117,11 +292,29 @@ class FT857Radio(ft817.FT817Radio):
         struct mem_struct memory[200];
         struct mem_struct pms[10];
         
+        #seekto 0x1bf3;
+        u8 arts_idw[10];
+        u8 beacon_text1[40];
+        u8 beacon_text2[40];
+        u8 beacon_text3[40];
+        u32 xvtr_a_offset;
+        u32 xvtr_b_offset;
+        u8 op_filter1_name[4];
+        u8 op_filter2_name[4];
+
         #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_REV = dict(zip(_CALLSIGN_CHARSET,
+                                    range(0,len(_CALLSIGN_CHARSET))))
+    _BEACON_CHARSET =  _CALLSIGN_CHARSET + ["+", "."]
+    _BEACON_CHARSET_REV = dict(zip(_BEACON_CHARSET,
+                                    range(0,len(_BEACON_CHARSET))))
+
     # WARNING Index are hard wired in memory management code !!!
     SPECIAL_MEMORIES = {
         "VFOa-1.8M" : -37,
@@ -186,6 +379,74 @@ class FT857Radio(ft817.FT817Radio):
     SPECIAL_MEMORIES_REV = dict(zip(SPECIAL_MEMORIES.values(),
                                     SPECIAL_MEMORIES.keys()))
 
+    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",   
+        "MFf:ARTS",             "MFf:SRCH",         "MFf:PMS",
+        "MFg:SCN",              "MFg:PRI",          "MFg:DW",     
+        "MFh:SCOP",             "MFh:WID",          "MFh:STEP",
+        "MFi:MTR",              "MFi:SWR",          "MFi:DISP",   
+        "MFj:SPOT",             "MFj:BK",           "MFj:KYR",
+        "MFk:TUNE",             "MFk:DOWN",         "MFk:UP",     
+        "MFl:NB",               "MFl:AGC",          "MFl:AGC SEL",
+        "MFm:IPO",              "MFm:ATT",          "MFm:NAR",    
+        "MFn:CFIL",             "MFn:FIL1",         "MFn:FIL2",
+        "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",          
+        "10:ARTS IDW",          "11:BEACON TEXT",   "12:BEACON TIME",
+        "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",      
+        "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",      
+        "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",   
+        "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",
+        "61:MTR ATX",           "62:MTR PEAK HOLD", "63:NB LEVEL",
+        "64:OP FILTER",         "71:PKT 1200",      "72:PKT 9600",
+        "73:PKT RATE",          "74:PROC LEVEL",    "75:RF POWER SET",
+        "76:RPT SHIFT",         "77:SCAN MODE",     "78:SCAN RESUME",
+        "79:SPLIT TONE",        "80:SQL/RF GAIN",   "81:SSB MIC GAIN",
+        "82:SSB STEP",          "83:TONE FREQ",     "84:TX TIME",
+        "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"]
+        
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.pre_download = _(dedent("""\
+            1. Turn radio off.
+            2. Connect cable to CAT/LINEAR jack.
+            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."""))
+        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)."""))
+        return rp
+
     def get_features(self):
         rf = ft817.FT817Radio.get_features(self)
         rf.has_cross = True
@@ -193,7 +454,6 @@ class FT857Radio(ft817.FT817Radio):
         rf.has_rx_dtcs = True
         rf.valid_tmodes = self.TMODES_REV.keys()
         rf.valid_cross_modes = self.CROSS_MODES_REV.keys()
-        rf.has_settings = False # not implemented yet
         return rf
 
     def _get_duplex(self, mem, _mem):
@@ -256,6 +516,510 @@ class FT857Radio(ft817.FT817Radio):
         # dunno if there's the same problem here but to be safe ...
         _mem.unknown_rxtoneflag = 0
 
+    def get_settings(self):
+        _settings = self._memobj.settings
+        basic = RadioSettingGroup("basic", "Basic")
+        cw = RadioSettingGroup("cw", "CW")
+        packet = RadioSettingGroup("packet", "Digital & packet")
+        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)
+
+        rs = RadioSetting("extended_menu", "Extended menu",
+                          RadioSettingValueBoolean(_settings.extended_menu))
+        extended.append(rs)
+        rs = RadioSetting("ars_144", "144MHz ARS",
+                          RadioSettingValueBoolean(_settings.ars_144))
+        basic.append(rs)
+        rs = RadioSetting("ars_430", "430MHz ARS",
+                          RadioSettingValueBoolean(_settings.ars_430))
+        basic.append(rs)
+        options = ["enable", "disable"]
+        rs = RadioSetting("disable_amfm_dial", "AM&FM Dial",
+                          RadioSettingValueList(options,
+                                        options[_settings.disable_amfm_dial]))
+        panel.append(rs)
+        rs = RadioSetting("am_mic", "AM mic gain",
+                          RadioSettingValueInteger(0, 100, _settings.am_mic))
+        basic.append(rs)
+        options = ["OFF", "1h", "2h", "3h", "4h", "5h", "6h"]
+        rs = RadioSetting("apo_time", "APO time",
+                          RadioSettingValueList(options,
+                                        options[_settings.apo_time]))
+        basic.append(rs)
+        options = ["OFF", "Range", "All"]
+        rs = RadioSetting("arts_beep", "ARTS beep",
+                          RadioSettingValueList(options,
+                                        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)
+        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)
+        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)
+        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)
+        extended.append(rs)
+        options = ["OFF"]+["%i sec" % i for i in range(1,256)]
+        rs = RadioSetting("beacon_time", "Beacon time",
+                          RadioSettingValueList(options,
+                                        options[_settings.beacon_time]))
+        extended.append(rs)
+        options = ["440Hz", "880Hz", "1760Hz"]
+        rs = RadioSetting("beep_tone", "Beep tone",
+                          RadioSettingValueList(options,
+                                        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))
+        extended.append(rs)
+        rs = RadioSetting("r_usb_car", "USB Rx carrier point (*10 Hz)",
+                          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))
+        extended.append(rs)
+        rs = RadioSetting("t_usb_car", "USB Tx carrier point (*10 Hz)",
+                          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]))
+        basic.append(rs)
+        options = ["CAT", "Linear", "Tuner"]
+        rs = RadioSetting("cat_lin_tun", "CAT/LIN/TUN selection",
+                          RadioSettingValueList(options,
+                                        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
+        rs = RadioSetting("clar_dial_sel", "Clarifier dial selection",
+                          RadioSettingValueList(options,
+                                        options[_settings.clar_dial_sel]))
+        panel.append(rs)
+        rs = RadioSetting("cw_auto_mode", "CW Automatic mode",
+                          RadioSettingValueBoolean(_settings.cw_auto_mode))
+        cw.append(rs)
+        options = ["USB", "LSB", "AUTO"]
+        rs = RadioSetting("cw_bfo", "CW BFO",
+                          RadioSettingValueList(options,
+                                        options[_settings.cw_bfo]))
+        cw.append(rs)
+        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]))
+        cw.append(rs)
+        options = ["Normal", "Reverse"]
+        rs = RadioSetting("cw_key_rev", "CW key reverse",
+                          RadioSettingValueList(options,
+                                        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)]
+        rs = RadioSetting("cw_pitch", "CW pitch",
+                          RadioSettingValueList(options,
+                                        options[_settings.cw_pitch]))
+        cw.append(rs)
+        options = ["%i ms" % i for i in range(5,31,5)]
+        rs = RadioSetting("cw_qsk", "CW QSK",
+                          RadioSettingValueList(options,
+                                        options[_settings.cw_qsk]))
+        cw.append(rs)
+        rs = RadioSetting("cw_sidetone", "CW sidetone volume",
+                          RadioSettingValueInteger(0, 100, _settings.cw_sidetone))
+        cw.append(rs)
+        options = ["%i wpm" % i for i in range(4,61)]
+        rs = RadioSetting("cw_speed", "CW speed",
+                          RadioSettingValueList(options,
+                                        options[_settings.cw_speed]))
+        cw.append(rs)
+        options = ["Numeric", "Alphabet", "AlphaNumeric"]
+        rs = RadioSetting("cw_training", "CW trainig",
+                          RadioSettingValueList(options,
+                                        options[_settings.cw_training]))
+        cw.append(rs)
+        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]))
+        cw.append(rs)
+        options = ["Tn-Rn", "Tn-Riv", "Tiv-Rn", "Tiv-Riv"]
+        rs = RadioSetting("dcs_inv", "DCS inv",
+                          RadioSettingValueList(options,
+                                        options[_settings.dcs_inv]))
+        extended.append(rs)
+        options = ["Fine", "Coarse"]
+        rs = RadioSetting("dial_step", "Dial step",
+                          RadioSettingValueList(options,
+                                        options[_settings.dial_step]))
+        panel.append(rs)
+        rs = RadioSetting("dig_disp", "Dig disp (*10 Hz)",
+                          RadioSettingValueInteger(-300, 300, _settings.dig_disp))
+        packet.append(rs)
+        rs = RadioSetting("dig_mic", "Dig gain",
+                          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]))
+        packet.append(rs)
+        rs = RadioSetting("dig_shift", "Dig shift (*10 Hz)",
+                          RadioSettingValueInteger(-300, 300, _settings.dig_shift))
+        packet.append(rs)
+        rs = RadioSetting("dig_vox", "Dig vox",
+                          RadioSettingValueInteger(0, 100, _settings.dig_vox))
+        packet.append(rs)
+        options = ["ARTS", "BAND", "FIX", "MEMGRP", "MODE", "MTR", "VFO"]
+        rs = RadioSetting("disp_color", "Display color mode",
+                          RadioSettingValueList(options,
+                                        options[_settings.disp_color]))
+        panel.append(rs)
+        rs = RadioSetting("disp_color_arts", "Display color ARTS set",
+                          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))
+        panel.append(rs)
+        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))
+        panel.append(rs)
+        rs = RadioSetting("disp_color_mtr", "Display color meter set",
+                          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))
+        panel.append(rs)
+        rs = RadioSetting("disp_color_fix", "Display color fix set",
+                          RadioSettingValueInteger(1, 32,_settings.disp_color_fix+1)) 
+        panel.append(rs)
+        rs = RadioSetting("disp_contrast", "Contrast",
+                          RadioSettingValueInteger(3, 15,_settings.disp_contrast+2))
+        panel.append(rs)
+        rs = RadioSetting("disp_intensity", "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]))
+        panel.append(rs)
+        options = ["60Hz", "120Hz", "240Hz"]
+        rs = RadioSetting("dsp_bpf", "Dsp band pass filter",
+                          RadioSettingValueList(options,
+                                        options[_settings.dsp_bpf]))
+        cw.append(rs)
+        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]))
+        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"]
+        rs = RadioSetting("dsp_lpf", "Dsp low pass filter cut off",
+                          RadioSettingValueList(options,
+                                        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]))
+        basic.append(rs)
+        rs = RadioSetting("dsp_nr", "DSP noise reduction level",
+                          RadioSettingValueInteger(1, 16, _settings.dsp_nr+1))
+        basic.append(rs)
+        # emergency only for US model
+        rs = RadioSetting("fm_mic", "FM mic gain",
+                          RadioSettingValueInteger(0, 100, _settings.fm_mic))
+        basic.append(rs)
+        rs = RadioSetting("home_vfo", "Enable HOME to VFO moving",
+                          RadioSettingValueBoolean(_settings.home_vfo))
+        panel.append(rs)
+        options = ["Dial", "Freq", "Panel", "All"]
+        rs = RadioSetting("lock_mode", "Lock mode",
+                          RadioSettingValueList(options,
+                                        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"]
+        rs = RadioSetting("mem_vfo_dial_mode", "Mem/VFO dial mode",
+                          RadioSettingValueList(options,
+                                        options[_settings.mem_vfo_dial_mode]))
+        panel.append(rs)
+        rs = RadioSetting("mic_scan", "Mic scan",
+                          RadioSettingValueBoolean(_settings.mic_scan))
+        basic.append(rs)
+        options = ["NOR", "RMT", "CAT"]
+        rs = RadioSetting("mic_sel", "Mic selection",
+                          RadioSettingValueList(options,
+                                        options[_settings.mic_sel]))
+        extended.append(rs)
+        options = ["SIG", "CTR", "VLT", "N/A", "FS",  "OFF"]
+        rs = RadioSetting("mtr_arx_sel", "Meter receive selection",
+                          RadioSettingValueList(options,
+                                        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]))
+        extended.append(rs)
+        rs = RadioSetting("mtr_peak_hold", "Meter peak hold",
+                          RadioSettingValueBoolean(_settings.mtr_peak_hold))
+        extended.append(rs)
+        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)
+        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)
+        extended.append(rs)
+        rs = RadioSetting("pg_a", "Programmable key MFq:A",
+                          RadioSettingValueList(self.PROGRAMMABLEOPTIONS,
+                                        self.PROGRAMMABLEOPTIONS[_settings.pg_a]))
+        extended.append(rs)
+        rs = RadioSetting("pg_b", "Programmable key MFq:B",
+                          RadioSettingValueList(self.PROGRAMMABLEOPTIONS,
+                                        self.PROGRAMMABLEOPTIONS[_settings.pg_b]))
+        extended.append(rs)
+        rs = RadioSetting("pg_c", "Programmable key MFq:C",
+                          RadioSettingValueList(self.PROGRAMMABLEOPTIONS,
+                                        self.PROGRAMMABLEOPTIONS[_settings.pg_c]))
+        extended.append(rs)
+        rs = RadioSetting("pg_acc", "Programmable mic key ACC",
+                          RadioSettingValueList(self.PROGRAMMABLEOPTIONS,
+                                        self.PROGRAMMABLEOPTIONS[_settings.pg_acc]))
+        extended.append(rs)
+        rs = RadioSetting("pg_p1", "Programmable mic key P1",
+                          RadioSettingValueList(self.PROGRAMMABLEOPTIONS,
+                                        self.PROGRAMMABLEOPTIONS[_settings.pg_p1]))
+        extended.append(rs)
+        rs = RadioSetting("pg_p2", "Programmable mic key P2",
+                          RadioSettingValueList(self.PROGRAMMABLEOPTIONS,
+                                        self.PROGRAMMABLEOPTIONS[_settings.pg_p2]))
+        extended.append(rs)
+        rs = RadioSetting("pkt1200", "Packet 1200 gain level",
+                          RadioSettingValueInteger(0, 100, _settings.pkt1200))
+        packet.append(rs)
+        rs = RadioSetting("pkt9600", "Packet 9600 gain level",
+                          RadioSettingValueInteger(0, 100, _settings.pkt9600))
+        packet.append(rs)
+        options = ["1200", "9600"]
+        rs = RadioSetting("pkt_rate", "Packet rate",
+                          RadioSettingValueList(options,
+                                        options[_settings.pkt_rate]))
+        packet.append(rs)
+        rs = RadioSetting("proc_level", "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))
+        basic.append(rs)
+        rs = RadioSetting("rf_power_6m", "Rf power set 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))
+        basic.append(rs)
+        rs = RadioSetting("rf_power_uhf", "Rf power set 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]))
+        basic.append(rs)
+        rs = RadioSetting("scan_resume", "Scan resume",
+                          RadioSettingValueInteger(1, 10, _settings.scan_resume))
+        basic.append(rs)
+        rs = RadioSetting("split_tone", "Split tone enable",
+                          RadioSettingValueBoolean(_settings.split_tone))
+        extended.append(rs)
+        options = ["RF-Gain", "Squelch"]
+        rs = RadioSetting("sql_rf_gain", "Squelch/RF-Gain",
+                          RadioSettingValueList(options,
+                                        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)]
+        rs = RadioSetting("tot_time", "Time-out timer",
+                          RadioSettingValueList(options,
+                                        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]))
+        extended.append(rs)
+        rs = RadioSetting("tx_if_filter", "Transmit IF filter",
+                          RadioSettingValueList(self.FILTERS,
+                                        self.FILTERS[_settings.tx_if_filter]))
+        basic.append(rs)
+        rs = RadioSetting("vox_delay", "VOX delay (*100 ms)",
+                          RadioSettingValueInteger(1, 30, _settings.vox_delay))
+        basic.append(rs)
+        rs = RadioSetting("vox_gain", "VOX Gain",
+                          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)))
+        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)))
+        extended.append(rs)
+        options = ["OFF", "XVTR A", "XVTR B"]
+        rs = RadioSetting("xvtr_sel", "Transverter function selection",
+                          RadioSettingValueList(options,
+                                        options[_settings.xvtr_sel]))
+        extended.append(rs)
+        
+        rs = RadioSetting("disp", "Display large",
+                          RadioSettingValueBoolean(_settings.disp))
+        panel.append(rs)
+        rs = RadioSetting("nb", "Noise blanker",
+                          RadioSettingValueBoolean(_settings.nb))
+        panelcontr.append(rs)
+        options = ["Auto", "Fast", "Slow", "Off"]
+        rs = RadioSetting("agc", "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]))
+        panelcontr.append(rs)
+        rs = RadioSetting("vox", "Vox",
+                          RadioSettingValueBoolean(_settings.vox))
+        panelcontr.append(rs)
+        rs = RadioSetting("bk", "Semi break-in",
+                          RadioSettingValueBoolean(_settings.bk))
+        cw.append(rs)
+        rs = RadioSetting("kyr", "Keyer",
+                          RadioSettingValueBoolean(_settings.kyr))
+        cw.append(rs)
+        options = ["enabled", "disabled"]
+        rs = RadioSetting("fst", "Fast",
+                          RadioSettingValueList(options,
+                                        options[_settings.fst]))
+        panelcontr.append(rs)
+        options = ["enabled", "disabled"]
+        rs = RadioSetting("lock", "Lock",
+                          RadioSettingValueList(options,
+                                        options[_settings.lock]))
+        panelcontr.append(rs)
+        rs = RadioSetting("scope_peakhold", "Scope max hold",
+                          RadioSettingValueBoolean(_settings.scope_peakhold))
+        panelcontr.append(rs)
+        options = ["21", "31", "127"]
+        rs = RadioSetting("scope_width", "Scope width (channels)",
+                          RadioSettingValueList(options,
+                                        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):
+        _settings = self._memobj.settings
+        for element in settings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+            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 = _settings
+                    setting = element.get_name()
+                if os.getenv("CHIRP_DEBUG"):
+	                print "Setting %s(%s) <= %s" % (setting, 
+                                    getattr(obj, setting), element.value)
+                if setting == "arts_idw":
+                    self._memobj.arts_idw = \
+                        [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"]:
+                    setattr(self._memobj, setting,
+                        [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)
+                elif setting == "dig_vox":
+                    val = int(element.value)
+                    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)
+                elif setting == "disp_contrast":
+                    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(self._memobj, setting + "_offset", abs(val))
+                else:
+                    setattr(obj, setting, element.value)
+            except Exception, e:
+                print element.get_name()
+                raise
+
 @directory.register
 class FT857USRadio(FT857Radio):
     """Yaesu FT857/897 (US version)"""
@@ -264,6 +1028,7 @@ class FT857USRadio(FT857Radio):
     MODEL = "FT-857/897 (US)"
 
     _model = ""
+    _US_model = True
     _memsize = 7481
     # block 9 (140 Bytes long) is to be repeted 40 times 
     # should be 42 times but this way I can use original 817 functions
@@ -339,3 +1104,12 @@ class FT857USRadio(FT857Radio):
             return self._set_special_60m(memory)
         else:
             return FT857Radio.set_memory(self, memory)
+
+    def get_settings(self):
+        top = FT857Radio.get_settings(self)
+        basic = top["basic"]
+        rs = RadioSetting("emergency", "Emergency",
+                          RadioSettingValueBoolean(self._memobj.settings.emergency))
+        basic.append(rs)
+        return top
+
diff --git a/chirp/ft90.py b/chirp/ft90.py
new file mode 100644
index 0000000..bd5c1d4
--- /dev/null
+++ b/chirp/ft90.py
@@ -0,0 +1,652 @@
+# Copyright 2011 Dan Smith <dsmith at danplanet.com>
+# Copyright 2013 Jens Jensen AF5MI <kd4tjx 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 import chirp_common, bitwise, memmap, directory, errors, util, yaesu_clone
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettingValueString
+import time, os, traceback, string, re
+from textwrap import dedent
+
+if os.getenv("CHIRP_DEBUG"):
+    CHIRP_DEBUG = True
+else:
+    CHIRP_DEBUG=False
+
+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
+FT90_TONES = list(chirp_common.TONES)
+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)]
+
+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)]
+
+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"]
+
+ at directory.register
+class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
+    VENDOR = "Yaesu"
+    MODEL = "FT-90"
+    ID = "\x8E\xF6"
+
+    _memsize = 4063
+    # block 03 (200 Bytes long) repeats 18 times; channel memories
+    _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;
+	
+    """
+    @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."""))
+        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)."""))
+        rp.display_pre_upload_prompt_before_opening_port = False
+        return rp
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return len(filedata) == cls._memsize
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.has_settings = True
+        rf.has_ctone = False
+        rf.has_bank = False
+        rf.has_dtcs_polarity = False
+        rf.has_dtcs = True
+        rf.valid_modes = FT90_MODES
+        rf.valid_tmodes = FT90_TMODES
+        rf.valid_duplexes = FT90_DUPLEX
+        rf.valid_tuning_steps = FT90_STEPS
+        rf.valid_power_levels = FT90_POWER_LEVELS_VHF
+        rf.valid_name_length = 7
+        rf.valid_characters = chirp_common.CHARSET_ASCII
+        rf.valid_skips = ["", "S"]
+        rf.valid_special_chans = FT90_SPECIAL
+        rf.memory_bounds = (1, 180)
+        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):
+            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
+            
+        else:
+            raise Exception("Unable to read blocknum %02X expected blocksize %i got %i." %
+                                (blocknum, blocksize+2, len(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()
+        status.msg = "Cloning..."
+        self.status_fn(status)
+        status.max = len(self._block_lengths)
+        for blocksize in self._block_lengths:
+            data += self._read(blocksize, blocknum)
+            blocknum += 1
+            status.cur = blocknum
+            self.status_fn(status)
+                
+        print "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()
+        status.msg = "Cloning to radio..."
+        self.status_fn(status)
+        status.max = len(self._block_lengths)
+
+        for blocksize in self._block_lengths:
+            checksum = yaesu_clone.YaesuChecksum(pos, pos+blocksize-1)
+            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)
+            # 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)
+            # radio is slow to write/ack:
+            time.sleep(looppostdelay) 
+            buf = self.pipe.read(1)
+            if CHIRP_DEBUG:
+                print "ack recd:"
+                print 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)
+    
+    def sync_in(self):
+        try:
+            self._mmap = self._clone_in()
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            trace = traceback.format_exc()
+            raise errors.RadioError("Failed to communicate with radio: %s" % trace)
+        self.process_mmap()
+
+    def sync_out(self):
+        try:
+            self._clone_out()
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            trace = traceback.format_exc()
+            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
+        chan_enable = self._memobj.chan_enable[bytepos]
+        if chan_enable & ( 1 << bitpos ):
+            return True
+        else:
+            return False
+
+    def _set_chan_enable(self, number, enable):
+        number = number - 1
+        bytepos = number // 8
+        bitpos = number  % 8
+        chan_enable = self._memobj.chan_enable[bytepos]
+        if enable:
+            chan_enable = chan_enable | ( 1 << bitpos )  # enable
+        else:
+            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):
+            # special channel
+            _mem = getattr(self._memobj, number)
+            mem.number = - len(FT90_SPECIAL) + FT90_SPECIAL.index(number)
+            mem.extd_number = number
+            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")
+        else:
+            # regular memory
+            _mem = self._memobj.memory[number-1]
+            mem.number = number
+            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.offset = _mem.txfreqoffset * 10
+        if not _mem.tmode < len(FT90_TMODES):
+            _mem.tmode = 0
+        mem.tmode = FT90_TMODES[_mem.tmode]
+        mem.rtone = FT90_TONES[_mem.tone]
+        mem.dtcs = chirp_common.DTCS_CODES[_mem.dcstone]
+        mem.mode = FT90_MODES[_mem.mode]
+        mem.duplex = FT90_DUPLEX[_mem.shift]
+        if mem.freq / 1000000 > 300:
+            mem.power = FT90_POWER_LEVELS_UHF[_mem.power]
+        else:
+            mem.power = FT90_POWER_LEVELS_VHF[_mem.power]
+
+        # radio has a known bug with 5khz step and squelch
+        if _mem.step == 0 or _mem.step > len(FT90_STEPS)-1:
+            _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)):
+            # dont display blank/junk name
+            mem.name = ""
+        else:
+            mem.name = str(_mem.name)
+        return mem
+
+    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)
+            if re.match('^pms', mem.extd_number):
+                # enable pms_XY channel flag
+                _special_enables = self._memobj.special_enables
+                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 )
+        _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]:
+            _mem.step = 2
+        else:
+            _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: 
+            # uhf
+            _mem.isUhf1 = 1
+            _mem.isUhf2 = 1
+            if mem.freq > 810000000:
+                # uhf hiband
+                _mem.isUhfHi = 1
+            else:
+                _mem.isUhfHi = 0
+        else:
+            # vhf
+            _mem.isUhf1 = 0
+            _mem.isUhf2 = 0
+            _mem.isUhfHi = 0
+        _mem.txfreqoffset = mem.offset / 10
+        _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.dcstone = chirp_common.DTCS_CODES.index(mem.dtcs)
+        _mem.step = FT90_STEPS.index(mem.tuning_step)
+        _mem.shift = FT90_DUPLEX.index(mem.duplex)
+        if mem.power:
+            _mem.power = FT90_POWER_LEVELS_VHF.index(mem.power)
+        else:
+            _mem.power = 3  # default to low power
+        if (len(mem.name) == 0):
+            _mem.name = bytearray.fromhex("80ffffffffffff")
+            _mem.showname = 0
+        else:
+            _mem.name = str(mem.name).ljust(7)
+            _mem.showname = 1
+            _mem.UseDefaultName = 0
+
+    def _decode_cwid(self, cwidarr):
+        cwid = ""
+        if CHIRP_DEBUG:
+            print "@ +_decode_cwid:"
+        for byte in cwidarr.get_value():
+            char = int(byte)
+            if CHIRP_DEBUG:
+                print char
+            # bitwise wraps in quotes! get rid of those
+            if char < len(FT90_CWID_CHARS):
+                cwid += FT90_CWID_CHARS[char]
+        return cwid
+
+    def _encode_cwid(self, cwidarr):
+        cwid = ""
+        if CHIRP_DEBUG:
+            print "@ _encode_cwid:"
+        for char in cwidarr.get_value():
+            cwid += chr(FT90_CWID_CHARS.index(char))
+        if CHIRP_DEBUG:
+            print cwid
+        return cwid
+    
+    def _bbcd2dtmf(self, bcdarr, strlen = 16):
+        # doing bbcd, but with support for ABCD*#
+        if CHIRP_DEBUG:
+            print 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','#')
+        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" )
+        bcdarr = list(bytearray.fromhex(dtmfstr))
+        if CHIRP_DEBUG:
+            print "@_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))
+        basic.append(rs)
+        rs = RadioSetting("lock", "Lock",
+                          RadioSettingValueBoolean(_settings.lock))
+        basic.append(rs)
+        rs = RadioSetting("ars", "Auto Repeater Shift",
+                        RadioSettingValueBoolean(_settings.ars))
+        basic.append(rs)
+        rs = RadioSetting("txpwrsave", "TX Power Save",
+                          RadioSettingValueBoolean(_settings.txpwrsave))
+        basic.append(rs)
+        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]))
+        basic.append(rs)
+        options = ["Off", "Band A", "Band B", "Both"]
+        rs = RadioSetting("pttlock", "PTT Lock",
+                          RadioSettingValueList(options,
+                                        options[_settings.pttlock]))
+        basic.append(rs)
+        
+        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]))
+        basic.append(rs)
+        
+        options = ["Off"] + map(str, range(1, 60+1))
+        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]))
+        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]))
+        keymaps.append(rs)
+        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]))
+        keymaps.append(rs)
+        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]))
+        keymaps.append(rs)
+        
+        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]))
+        basic.append(rs)
+        
+        options = ["TRX Normal", "RX Reverse", "TX Reverse", "TRX Reverse"]
+        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]))
+        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]))
+        autodial.append(rs)
+        
+        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)):
+            objname = "dtmf" + i
+            dtmfsetting = getattr(_settings, objname)
+            dtmflen = getattr(_settings, objname + "_len")
+            dtmfstr = self._bbcd2dtmf(dtmfsetting, dtmflen)
+            dtmf = RadioSettingValueString(0, 16, dtmfstr)
+            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:
+            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
+                if setting == "cwid":
+                    newval = self._encode_cwid(newval)
+                if re.match('dtmf\d', setting):
+                    # set dtmf length field and then get bcd dtmf
+                    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)
+                setattr(_settings, setting, newval)
+            except Exception, e:
+                print element.get_name()
+                raise
+
diff --git a/chirp/ftm350.py b/chirp/ftm350.py
new file mode 100644
index 0000000..784bdff
--- /dev/null
+++ b/chirp/ftm350.py
@@ -0,0 +1,420 @@
+# Copyright 2013 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 struct
+import os
+
+from chirp import chirp_common, yaesu_clone, directory, errors, util
+from chirp import bitwise, memmap
+from chirp.settings import RadioSettingGroup, RadioSetting
+from chirp.settings import RadioSettingValueInteger, RadioSettingValueString
+
+mem_format = """
+struct mem {
+  u8 used:1,
+     skip:2,
+     unknown1:5;
+  u8 unknown2:1,
+     mode:3,
+     unknown8:1,
+     oddsplit:1,
+     duplex:2;
+  bbcd freq[3];
+  u8 unknownA:1,
+     tmode:3,
+     unknownB:4;
+  bbcd split[3];
+  u8 power:2,
+     tone:6;
+  u8 unknownC:1,
+     dtcs:7;
+  u8 showalpha:1,
+     unknown5:7;
+  u8 unknown6;
+  u8 offset;
+  u8 unknown7[2];
+};
+
+struct lab {
+  u8 string[8];
+};
+
+#seekto 0x0508;
+struct {
+  char call[6];
+  u8 ssid;
+} aprs_my_callsign;
+
+#seekto 0x0480;
+struct mem left_memory_zero;
+#seekto 0x04A0;
+struct lab left_label_zero;
+#seekto 0x04C0;
+struct mem right_memory_zero;
+#seekto 0x04E0;
+struct lab right_label_zero;
+
+#seekto 0x0800;
+struct mem left_memory[500];
+
+#seekto 0x2860;
+struct mem right_memory[500];
+
+#seekto 0x48C0;
+struct lab left_label[518];
+struct lab right_label[518];
+"""
+
+_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)
+
+POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=50),
+                chirp_common.PowerLevel("Mid", watts=20),
+                chirp_common.PowerLevel("Low", watts=5)]
+
+SKIPS = ["", "S", "P"]
+
+def aprs_call_to_str(_call):
+    call = ""
+    for i in str(_call):
+        if i == "\xca":
+            break
+        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 = ""
+
+    radio.pipe.setTimeout(1)
+    attempts = 30
+
+    data = memmap.MemoryMap("\x00" * (radio._memsize + 128))
+    length = 0
+    last_addr = 0
+    while length < radio._memsize:
+        frame = radio.pipe.read(131)
+        if length and not frame:
+            raise errors.RadioError("Radio not responding")
+
+        if not frame:
+            attempts -= 1
+            if attempts <= 0:
+                raise errors.RadioError("Radio not responding")
+
+        if frame:
+            addr, = struct.unpack(">H", frame[0:2])
+            checksum = ord(frame[130])
+            block = frame[2:130]
+
+            cs = 0
+            for i in frame[:-1]:
+                cs = (cs + ord(i)) % 256
+            if cs != checksum:
+                print "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)
+            last_addr = addr
+            data[addr] = block
+            length += len(block)
+
+        status = chirp_common.Status()
+        status.cur = length
+        status.max = radio._memsize
+        status.msg = "Cloning from radio"
+        radio.status_fn(status)
+
+    return data
+
+def _clone_out(radio):
+    radio.pipe.setTimeout(1)
+
+    # Seriously, WTF Yaesu?
+    ranges = [
+        (0x0000, 0x0000),
+        (0x0100, 0x0380),
+        (0x0480, 0xFF80),
+        (0x0080, 0x0080),
+        (0xFFFE, 0xFFFE),
+        ]
+
+    for start, end in ranges:
+        for i in range(start, end+1, 128):
+            block = radio._mmap[i:i + 128]
+            frame = struct.pack(">H", i) + block
+            cs = 0
+            for byte in frame:
+                cs += ord(byte)
+            frame += chr(cs % 256)
+            radio.pipe.write(frame)
+            ack = radio.pipe.read(1)
+            if ack != "\x06":
+                raise errors.RadioError("Radio refused block %i" % (i / 128))
+            time.sleep(0.05)
+
+            status = chirp_common.Status()
+            status.cur = i + 128
+            status.max = radio._memsize
+            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
+    # real frequency.  Gross.
+    if rawfreq > 8000000000:
+        rawfreq = (rawfreq - 8000000000) + 5000
+
+    if rawfreq > 4000000000:
+        rawfreq = (rawfreq - 4000000000) + 2500
+
+    if rawfreq > 2000000000:
+        rawfreq = (rawfreq - 2000000000) + 1250
+
+    return rawfreq
+
+def set_freq(freq, obj, field):
+    """Encode a frequency with any necessary fractional step flags"""
+    obj[field] = freq / 10000
+    frac = freq % 10000
+
+    if frac >= 5000:
+        frac -= 5000
+        obj[field][0].set_bits(0x80)
+
+    if frac >= 2500:
+        frac -= 2500
+        obj[field][0].set_bits(0x40)
+
+    if frac >= 1250:
+        frac -= 1250
+        obj[field][0].set_bits(0x20)
+
+    return freq
+
+ at directory.register
+class FTM350Radio(yaesu_clone.YaesuCloneModeRadio):
+    """Yaesu FTM-350"""
+    BAUD_RATE = 48000
+    VENDOR = "Yaesu"
+    MODEL = "FTM-350"
+
+    _model = ""
+    _memsize = 65536
+    _vfo = ""
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.has_bank = False
+        rf.has_ctone = False
+        rf.has_settings = self._vfo == "left"
+        rf.has_tuning_step = False
+        rf.has_dtcs_polarity = False
+        rf.has_sub_devices = self.VARIANT == ""
+        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
+        rf.valid_skips = SKIPS
+        rf.valid_name_length = 8
+        rf.valid_characters = CHARSET
+        rf.memory_bounds = (0, 500)
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_bands = [(  500000,    1800000),
+                          (76000000,  250000000),
+                          (30000000, 1000000000)]
+        rf.can_odd_split = True
+        return rf
+
+    def get_sub_devices(self):
+        return [FTM350RadioLeft(self._mmap), FTM350RadioRight(self._mmap)]
+
+    def sync_in(self):
+        try:
+            self._mmap = _clone_in(self)
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            raise errors.RadioError("Failed to download from radio (%s)" % e)
+        self.process_mmap()
+
+    def sync_out(self):
+        try:
+            _clone_out(self)
+        except errors.RadioError:
+            raise
+        except Exception, e:
+            raise errors.RadioError("Failed to upload to radio (%s)" % e)
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(mem_format, self._mmap)
+
+    def get_raw_memory(self, number):
+        if number == 0:
+            suffix = "_zero"
+            fn = lambda o: o
+        else:
+            suffix = ""
+            fn = lambda o: o[number - 1]
+        return (repr(fn(self._memory_obj(suffix))) + 
+                repr(fn(self._label_obj(suffix))))
+
+    def _memory_obj(self, suffix=""):
+        return getattr(self._memobj, "%s_memory%s" % (self._vfo, suffix))
+
+    def _label_obj(self, suffix=""):
+        return getattr(self._memobj, "%s_label%s" % (self._vfo, suffix))
+
+    def get_memory(self, number):
+        if number == 0:
+            _mem = self._memory_obj("_zero")
+            _lab = self._label_obj("_zero")
+        else:
+            _mem = self._memory_obj()[number - 1]
+            _lab = self._label_obj()[number - 1]
+        mem = chirp_common.Memory()
+        mem.number = number
+
+        if not _mem.used:
+            mem.empty = True
+            return mem
+
+        mem.freq = get_freq(int(_mem.freq) * 10000)
+        mem.rtone = chirp_common.TONES[_mem.tone]
+        mem.tmode = TMODES[_mem.tmode]
+
+        if _mem.oddsplit:
+            mem.duplex = "split"
+            mem.offset = get_freq(int(_mem.split) * 10000)
+        else: 
+            mem.duplex = DUPLEXES[_mem.duplex]
+            mem.offset = int(_mem.offset) * 50000
+
+        mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
+        mem.mode = MODES[_mem.mode]
+        mem.skip = SKIPS[_mem.skip]
+        mem.power = POWER_LEVELS[_mem.power]
+
+        for char in _lab.string:
+            if char == 0xCA:
+                break
+            try:
+                mem.name += CHARSET[char]
+            except IndexError:
+                mem.name += "?"
+        mem.name = mem.name.rstrip()
+
+        return mem
+
+    def set_memory(self, mem):
+        if mem.number == 0:
+            _mem = self._memory_obj("_zero")
+            _lab = self._label_obj("_zero")
+        else:
+            _mem = self._memory_obj()[mem.number - 1]
+            _lab = self._label_obj()[mem.number - 1]
+        _mem.used = not mem.empty
+        if mem.empty:
+            return
+
+        set_freq(mem.freq, _mem, 'freq')
+        _mem.tone = chirp_common.TONES.index(mem.rtone)
+        _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
+        _mem.tmode = TMODES.index(mem.tmode)
+        _mem.mode = MODES.index(mem.mode)
+        _mem.skip = SKIPS.index(mem.skip)
+
+        _mem.oddsplit = 0
+        _mem.duplex = 0
+        if mem.duplex == "split":
+            set_freq(mem.offset, _mem, 'split')
+            _mem.oddsplit = 1
+        else:
+            _mem.offset = mem.offset / 50000
+            _mem.duplex = DUPLEXES.index(mem.duplex)
+
+        if mem.power:
+            _mem.power = POWER_LEVELS.index(mem.power)
+        else:
+            _mem.power = 0
+
+        for i in range(0, 8):
+            try:
+                char = CHARSET.index(mem.name[i])
+            except IndexError:
+                char = 0xCA
+            _lab.string[i] = char
+        _mem.showalpha = mem.name.strip() != ""
+
+    @classmethod
+    def match_model(self, filedata, filename):
+        return filedata.startswith("AH033$")
+
+    def get_settings(self):
+        top = RadioSettingGroup("all", "All Settings")
+
+        aprs = RadioSettingGroup("aprs", "APRS")
+        top.append(aprs)
+
+        myc = self._memobj.aprs_my_callsign
+        rs = RadioSetting("aprs_my_callsign.call", "APRS My Callsign",
+                          RadioSettingValueString(0, 6,
+                                                  aprs_call_to_str(myc.call)))
+        aprs.append(rs)
+
+        rs = RadioSetting("aprs_my_callsign.ssid", "APRS My SSID",
+                          RadioSettingValueInteger(0, 15, myc.ssid))
+        aprs.append(rs)
+
+        return top
+
+    def set_settings(self, settings):
+        for setting in settings:
+            if not isinstance(setting, RadioSetting):
+                self.set_settings(setting)
+                continue
+
+            # Quick hack to make these work
+            if setting.get_name() == "aprs_my_callsign.call":
+                self._memobj.aprs_my_callsign.call = \
+                    setting.value.get_value().upper().replace(" ", "\xCA")
+            elif setting.get_name() == "aprs_my_callsign.ssid":
+                self._memobj.aprs_my_callsign.ssid = setting.value
+
+
+class FTM350RadioLeft(FTM350Radio):
+    VARIANT = "Left"
+    _vfo = "left"
+
+class FTM350RadioRight(FTM350Radio):
+    VARIANT = "Right"
+    _vfo = "right"
diff --git a/chirp/generic_csv.py b/chirp/generic_csv.py
index 4ca06d9..5777e73 100644
--- a/chirp/generic_csv.py
+++ b/chirp/generic_csv.py
@@ -106,6 +106,19 @@ class CSVRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
 
         return rf
 
+    def _clean(self, headers, line, mem):
+        """Runs post-processing functions on new mem objects.
+
+        This is useful for parsing other CSV dialects when multiple columns
+        convert to a single Chirp column."""
+
+        for attr in dir(mem):
+            fname = "_clean_%s" % attr
+            if hasattr(self, fname):
+                mem = getattr(self, fname)(headers, line, mem)
+        
+        return mem
+
     def _parse_csv_data_line(self, headers, line):
         mem = chirp_common.Memory()
         try:
@@ -114,7 +127,11 @@ class CSVRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
         except OmittedHeaderError:
             pass
 
-        for header, (typ, attr) in self.ATTR_MAP.items():
+        for header in headers:
+            try:
+                typ, attr = self.ATTR_MAP[header]
+            except KeyError:
+                continue
             try:
                 val = get_datum_by_header(headers, line, header)
                 if not val and typ == int:
@@ -128,7 +145,7 @@ class CSVRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
             except Exception, e:
                 raise Exception("[%s] %s" % (attr, e))
 
-        return mem
+        return self._clean(headers, line, mem)
 
     def load(self, filename=None):
         if filename is None and self._filename is None:
@@ -238,6 +255,176 @@ class CSVRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
             ",".join(self.memories[number].to_csv())
 
     @classmethod
-    def match_model(cls, _filedata, filename):
+    def match_model(cls, filedata, filename):
         """Match files ending in .CSV"""
-        return filename.lower().endswith("." + cls.FILE_EXTENSION)
+        return filename.lower().endswith("." + cls.FILE_EXTENSION) and \
+            (filedata.startswith("Location,") or filedata == "")
+
+
+ at directory.register
+class CommanderCSVRadio(CSVRadio):
+    """A driver for reading CSV files generated by KG-UV Commander software"""
+    VENDOR = "Commander"
+    MODEL = "KG-UV"
+    FILE_EXTENSION = "csv"
+
+    MODE_MAP = {
+        "NARR": "NFM",
+        "WIDE": "FM",
+    }
+
+    SCAN_MAP = {
+        "ON":  "",
+        "OFF": "S"
+    }
+
+    ATTR_MAP = {
+        "#":            (int,   "number"),
+        "Name":         (str,   "name"),
+        "RX Freq":      (chirp_common.parse_freq, "freq"),
+        "Scan":         (lambda v: CommanderCSVRadio.SCAN_MAP.get(v), "skip"),
+        "TX Dev":       (lambda v: CommanderCSVRadio.MODE_MAP.get(v), "mode"),
+        "Group/Notes":  (str,   "comment"),
+    }
+
+    def _clean_number(self, headers, line, mem):
+        if mem.number == 0:
+            for memory in self.memories:
+                if memory.empty:
+                    mem.number = memory.number
+                    break
+        return mem
+
+    def _clean_duplex(self, headers, line, mem):
+        try:
+            txfreq = chirp_common.parse_freq(
+                        get_datum_by_header(headers, line, "TX Freq"))
+        except ValueError:
+            mem.duplex = "off"
+            return mem
+
+        if mem.freq == txfreq:
+            mem.duplex = ""
+        elif txfreq:
+            mem.duplex = "split"
+            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")
+        if rtone == "OFF":
+            rtone = None
+        else:
+            rtone = float(rtone)
+
+        if ctone == "OFF":
+            ctone = None
+        else:
+            ctone = float(ctone)
+
+        if rtone:
+            mem.tmode = "Tone"
+        if ctone:
+            mem.tmode = "TSQL"
+
+        mem.rtone = rtone or 88.5
+        mem.ctone = ctone or mem.rtone
+
+        return mem
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        """Match files ending in .csv and using Commander column names."""
+        return filename.lower().endswith("." + cls.FILE_EXTENSION) and \
+            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"')
+
+
+ at directory.register
+class RTCSVRadio(CSVRadio):
+    """A driver for reading CSV files generated by RT Systems software"""
+    VENDOR = "RT Systems"
+    MODEL = "CSV"
+    FILE_EXTENSION = "csv"
+
+    DUPLEX_MAP = {
+        "Minus":    "-",
+        "Plus":     "+",
+        "Simplex":  "",
+        "Split":    "split",
+    }
+    
+    SKIP_MAP = {
+        "Off":    "",
+        "On":     "S",
+        "P Scan": "P",
+        "Skip":   "S",
+        }
+
+    TMODE_MAP = {
+        "None":     "",
+        "T Sql":    "TSQL",
+    }
+
+    BOOL_MAP = {
+        "Off":  False,
+        "On":   True,
+    }
+
+    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"),
+        }
+
+    def _clean_duplex(self, headers, line, mem):
+        if mem.duplex == "split":
+            try:
+                val = get_datum_by_header(headers, line, "Transmit Frequency")
+                val = chirp_common.parse_freq(val)
+                mem.offset = val
+            except OmittedHeaderError:
+                pass
+
+        return mem
+
+    def _clean_mode(self, headers, line, mem):
+        if mem.mode == "FM":
+            try:
+                val = get_datum_by_header(headers, line, "Half Dev")
+                if self.BOOL_MAP[val]:
+                    mem.mode = "FMN"
+            except OmittedHeaderError:
+                pass
+
+        return mem
+
+    def _clean_ctone(self, headers, line, mem):
+        # RT Systems only stores a single tone value
+        mem.ctone = mem.rtone
+        return mem
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        """Match files ending in .csv and using RT Systems column names."""
+        # RT Systems provides a different set of columns for each radio.
+        # We attempt to match only the first few columns, hoping they are
+        # 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")
diff --git a/chirp/generic_tpe.py b/chirp/generic_tpe.py
index 8fa949c..f720c0f 100644
--- a/chirp/generic_tpe.py
+++ b/chirp/generic_tpe.py
@@ -16,25 +16,6 @@
 import UserDict
 from chirp import chirp_common, directory, generic_csv
 
-class TpeMap(UserDict.UserDict):
-    """Pretend we're a dict"""
-    def items(self):
-        return [
-            ("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: "Tone" 
-                                  if float(v) in chirp_common.TONES
-                                  else "", "tmode")),
-            ("CTCSS Tones"     , (lambda v: float(v)
-                                  if float(v) in chirp_common.TONES
-                                  else 88.5, "rtone")),
-            ("CTCSS Tones"     , (lambda v: float(v)
-                                  if float(v) in chirp_common.TONES
-                                  else 88.5, "ctone")),
-        ]
 
 @directory.register
 class TpeRadio(generic_csv.CSVRadio):
@@ -43,4 +24,33 @@ class TpeRadio(generic_csv.CSVRadio):
     MODEL = "Travel Plus"
     FILE_EXTENSION = "tpe"
 
-    ATTR_MAP = TpeMap()
+    ATTR_MAP = {
+        "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"),
+        "Repeater Notes":   (str, "comment"),
+    }
+
+    def _clean_tmode(self, headers, line, mem):
+        try:
+            val = generic_csv.get_datum_by_header(headers, line, "CTCSS Tones")
+            if val and float(val) in chirp_common.TONES:
+                mem.tmode = "Tone"
+        except generic_csv.OmittedHeaderError:
+            pass
+
+        return mem
+
+    def _clean_ctone(self, headers, line, mem):
+        # TPE only stores a single tone value
+        mem.ctone = mem.rtone
+        return mem
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return filename.lower().endswith("." + cls.FILE_EXTENSION)
diff --git a/chirp/h777.py b/chirp/h777.py
new file mode 100644
index 0000000..b9db514
--- /dev/null
+++ b/chirp/h777.py
@@ -0,0 +1,542 @@
+# -*- coding: utf-8 -*-
+# Copyright 2013 Andrew Morgan <ziltro at ziltro.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 unittest
+
+from chirp import chirp_common, directory, memmap
+from chirp import bitwise, errors, util
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean
+
+DEBUG = os.getenv("CHIRP_DEBUG") and True or False
+
+MEM_FORMAT = """
+#seekto 0x0010;
+struct {
+    lbcd rxfreq[4];
+    lbcd txfreq[4];
+    lbcd rxtone[2];
+    lbcd txtone[2];
+    u8 unknown3:1,
+       unknown2:1,
+       unknown1:1,
+       skip:1,
+       highpower:1,
+       narrow:1,
+       beatshift:1,
+       bcl:1;
+    u8 unknown4[3];
+} memory[16];
+#seekto 0x02B0;
+struct {
+    u8 voiceprompt;
+    u8 voicelanguage;
+    u8 scan;
+    u8 vox;
+    u8 voxlevel;
+    u8 voxinhibitonrx;
+    u8 lowvolinhibittx;
+    u8 highvolinhibittx;
+    u8 alarm;
+    u8 fmradio;
+} settings;
+#seekto 0x03C0;
+struct {
+    u8 unused:6,
+       batterysaver:1,
+       beep:1;
+    u8 squelchlevel;
+    u8 sidekeyfunction;
+    u8 timeouttimer;
+    u8 unused2[3];
+    u8 unused3:7,
+       scanmode:1;
+} settings2;
+"""
+
+CMD_ACK = "\x06"
+BLOCK_SIZE = 0x08
+UPLOAD_BLOCKS = [range(0x0000, 0x0110, 8),
+                 range(0x02b0, 0x02c0, 8),
+                 range(0x0380, 0x03e0, 8)]
+
+# TODO: Is it 1 watt?
+H777_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00),
+                     chirp_common.PowerLevel("High", watts=5.00)]
+VOICE_LIST = ["English", "Chinese"]
+SIDEKEYFUNCTION_LIST = ["Off", "Monitor", "Transmit Power", "Alarm"]
+TIMEOUTTIMER_LIST = ["Off", "30 seconds", "60 seconds", "90 seconds",
+                     "120 seconds", "150 seconds", "180 seconds",
+                     "210 seconds", "240 seconds", "270 seconds",
+                     "300 seconds"]
+SCANMODE_LIST = ["Carrier", "Time"]
+
+SETTING_LISTS = {
+    "voice" : VOICE_LIST,
+    }
+
+def _h777_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("P3107"):
+        print 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 _h777_exit_programming_mode(radio):
+    serial = radio.pipe
+    try:
+        serial.write("E")
+    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))
+
+    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 _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)
+
+    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):
+    print "download"
+    _h777_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 = _h777_read_block(radio, addr, BLOCK_SIZE)
+        data += block
+
+        if DEBUG:
+            print "Address: %04x" % addr
+            print 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"
+
+    _h777_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)
+            _h777_write_block(radio, addr, BLOCK_SIZE)
+
+    _h777_exit_programming_mode(radio)
+
+ at directory.register
+class H777Radio(chirp_common.CloneModeRadio):
+    """HST H-777"""
+    # VENDOR = "Heng Shun Tong (恒顺通)"
+    # MODEL = "H-777"
+    VENDOR = "Baofeng"
+    MODEL = "BF-888"
+    BAUD_RATE = 9600
+
+    # 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 = [
+    #       (0x0000, 0x0110),
+    #       (0x02B0, 0x02C0),
+    #       (0x0380, 0x03E0)
+    #       ]
+    # Memory starts looping at 0x1000... But not every 0x1000.
+
+    _ranges = [
+        (0x0000, 0x0110),
+        (0x02B0, 0x02C0),
+        (0x0380, 0x03E0),
+        ]
+    _memsize = 0x03E0
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.has_settings = True
+        rf.valid_modes = ["NFM", "FM"]  # 12.5 KHz, 25 kHz.
+        rf.valid_skips = ["", "S"]
+        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
+        rf.has_rx_dtcs = True
+        rf.has_ctone = True
+        rf.has_cross = True
+        rf.has_tuning_step = False
+        rf.has_bank = False
+        rf.has_name = False
+        rf.memory_bounds = (1, 16)
+        rf.valid_bands = [(400000000, 470000000)]
+        rf.valid_power_levels = H777_POWER_LEVELS
+
+        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 _decode_tone(self, val):
+        val = int(val)
+        if val == 16665:
+            return '', None, None
+        elif val >= 12000:
+            return 'DTCS', val - 12000, 'R'
+        elif val >= 8000:
+            return 'DTCS', val - 8000, 'N'
+        else:
+            return 'Tone', val / 10.0, None
+
+    def _encode_tone(self, memval, mode, value, pol):
+        if mode == '':
+            memval[0].set_raw(0xFF)
+            memval[1].set_raw(0xFF)
+        elif mode == 'Tone':
+            memval.set_value(int(value * 10))
+        elif mode == 'DTCS':
+            flag = 0x80 if pol == 'N' else 0xC0
+            memval.set_value(value)
+            memval[1].set_bits(flag)
+        else:
+            raise Exception("Internal error: invalid mode `%s'" % mode)
+
+    def get_memory(self, number):
+        _mem = self._memobj.memory[number - 1]
+
+        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 = not _mem.narrow and "FM" or "NFM"
+        mem.power = H777_POWER_LEVELS[_mem.highpower]
+
+        mem.skip = _mem.skip and "S" or ""
+
+        txtone = self._decode_tone(_mem.txtone)
+        rxtone = self._decode_tone(_mem.rxtone)
+        chirp_common.split_tone_decode(mem, txtone, rxtone)
+
+        mem.extra = RadioSettingGroup("Extra", "extra")
+        rs = RadioSetting("bcl", "Busy Channel Lockout",
+                          RadioSettingValueBoolean(not _mem.bcl))
+        mem.extra.append(rs)
+        rs = RadioSetting("beatshift", "Beat Shift(scramble)",
+                          RadioSettingValueBoolean(not _mem.beatshift))
+        mem.extra.append(rs)
+
+        return mem
+
+    def set_memory(self, mem):
+        # Get a low-level memory object mapped to the image
+        _mem = self._memobj.memory[mem.number - 1]
+
+        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
+
+        txtone, rxtone = chirp_common.split_tone_encode(mem)
+        self._encode_tone(_mem.txtone, *txtone)
+        self._encode_tone(_mem.rxtone, *rxtone)
+
+        _mem.narrow = 'N' in mem.mode
+        _mem.highpower = mem.power == H777_POWER_LEVELS[1]
+        _mem.skip = mem.skip == "S"
+
+        for setting in mem.extra:
+            # NOTE: Only two settings right now, both are inverted
+            setattr(_mem, setting.get_name(), not setting.value)
+
+    def get_settings(self):
+        _settings = self._memobj.settings
+        basic = RadioSettingGroup("basic", "Basic Settings")
+
+        # TODO: Check that all these settings actually do what they
+        # say they do.
+
+        rs = RadioSetting("voiceprompt", "Voice prompt",
+                          RadioSettingValueBoolean(_settings.voiceprompt))
+        basic.append(rs)
+
+        rs = RadioSetting("voicelanguage", "Voice language",
+                          RadioSettingValueList(VOICE_LIST,
+                              VOICE_LIST[_settings.voicelanguage]))
+        basic.append(rs)
+
+        rs = RadioSetting("scan", "Scan",
+                          RadioSettingValueBoolean(_settings.scan))
+        basic.append(rs)
+
+        rs = RadioSetting("settings2.scanmode", "Scan mode",
+                          RadioSettingValueList(SCANMODE_LIST,
+                          SCANMODE_LIST[self._memobj.settings2.scanmode]))
+        basic.append(rs)
+
+        rs = RadioSetting("vox", "VOX",
+                          RadioSettingValueBoolean(_settings.vox))
+        basic.append(rs)
+
+        rs = RadioSetting("voxlevel", "VOX level",
+                          RadioSettingValueInteger(
+                              1, 5, _settings.voxlevel + 1))
+        basic.append(rs)
+
+        rs = RadioSetting("voxinhibitonrx", "Inhibit VOX on receive",
+                          RadioSettingValueBoolean(_settings.voxinhibitonrx))
+        basic.append(rs)
+
+        rs = RadioSetting("lowvolinhibittx", "Low voltage inhibit transmit",
+                          RadioSettingValueBoolean(_settings.lowvolinhibittx))
+        basic.append(rs)
+
+        rs = RadioSetting("highvolinhibittx", "High voltage inhibit transmit",
+                          RadioSettingValueBoolean(_settings.highvolinhibittx))
+        basic.append(rs)
+
+        rs = RadioSetting("alarm", "Alarm",
+                          RadioSettingValueBoolean(_settings.alarm))
+        basic.append(rs)
+
+        # TODO: This should probably be called “FM Broadcast Band Radio”
+        # or something. I'm not sure if the model actually has one though.
+        rs = RadioSetting("fmradio", "FM function",
+                          RadioSettingValueBoolean(_settings.fmradio))
+        basic.append(rs)
+
+        rs = RadioSetting("settings2.beep", "Beep",
+                          RadioSettingValueBoolean(
+                              self._memobj.settings2.beep))
+        basic.append(rs)
+
+        rs = RadioSetting("settings2.batterysaver", "Battery saver",
+                          RadioSettingValueBoolean(
+                              self._memobj.settings2.batterysaver))
+        basic.append(rs)
+
+        rs = RadioSetting("settings2.squelchlevel", "Squelch level",
+                          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]))
+        basic.append(rs)
+
+        rs = RadioSetting("settings2.timeouttimer", "Timeout timer",
+                          RadioSettingValueList(TIMEOUTTIMER_LIST,
+                          TIMEOUTTIMER_LIST[
+                              self._memobj.settings2.timeouttimer]))
+        basic.append(rs)
+
+        return basic
+
+    def set_settings(self, settings):
+        for element in settings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+            else:
+                try:
+                    if "." in element.get_name():
+                        bits = element.get_name().split(".")
+                        obj = self._memobj
+                        for bit in bits[:-1]:
+                            obj = getattr(obj, bit)
+                        setting = bits[-1]
+                    else:
+                        obj = self._memobj.settings
+                        setting = element.get_name()
+
+                    if element.has_apply_callback():
+                        print "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)
+                        setattr(obj, setting, element.value)
+                except Exception, e:
+                    print element.get_name()
+                    raise
+
+class H777TestCase(unittest.TestCase):
+    def setUp(self):
+        self.driver = H777Radio(None)
+        self.testdata = bitwise.parse("lbcd foo[2];",
+                                      memmap.MemoryMap("\x00\x00"))
+
+    def test_decode_tone_dtcs_normal(self):
+        mode, value, pol = self.driver._decode_tone(8023)
+        self.assertEqual('DTCS', mode)
+        self.assertEqual(23, value)
+        self.assertEqual('N', pol)
+
+    def test_decode_tone_dtcs_rev(self):
+        mode, value, pol = self.driver._decode_tone(12023)
+        self.assertEqual('DTCS', mode)
+        self.assertEqual(23, value)
+        self.assertEqual('R', pol)
+
+    def test_decode_tone_tone(self):
+        mode, value, pol = self.driver._decode_tone(885)
+        self.assertEqual('Tone', mode)
+        self.assertEqual(88.5, value)
+        self.assertEqual(None, pol)
+
+    def test_decode_tone_none(self):
+        mode, value, pol = self.driver._decode_tone(16665)
+        self.assertEqual('', mode)
+        self.assertEqual(None, value)
+        self.assertEqual(None, pol)
+
+    def test_encode_tone_dtcs_normal(self):
+        self.driver._encode_tone(self.testdata.foo, 'DTCS', 23, 'N')
+        self.assertEqual(8023, int(self.testdata.foo))
+
+    def test_encode_tone_dtcs_rev(self):
+        self.driver._encode_tone(self.testdata.foo, 'DTCS', 23, 'R')
+        self.assertEqual(12023, int(self.testdata.foo))
+
+    def test_encode_tone(self):
+        self.driver._encode_tone(self.testdata.foo, 'Tone', 88.5, 'N')
+        self.assertEqual(885, int(self.testdata.foo))
+
+    def test_encode_tone_none(self):
+        self.driver._encode_tone(self.testdata.foo, '', 67.0, 'N')
+        self.assertEqual(16665, int(self.testdata.foo))
diff --git a/chirp/ic208.py b/chirp/ic208.py
index 627607c..f93300c 100644
--- a/chirp/ic208.py
+++ b/chirp/ic208.py
@@ -75,18 +75,19 @@ for i in range(1, 6):
     IC208_SPECIAL.append("%iA" % i)
     IC208_SPECIAL.append("%iB" % i)
 
-ALPHA_CHARSET = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-NUMERIC_CHARSET = "0123456789+-=*/()|"
+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):
-        if val == 0:
-            return " "
-        elif val & 0x20:
-            return ALPHA_CHARSET[val & 0x1F]
-        else:
-            return NUMERIC_CHARSET[val & 0x0F]
+        try:
+            return CHARSET[int(val)]
+        except KeyError:
+            return "*"
 
     name_bytes = [_mem.name1, _mem.name2, _mem.name3,
                   _mem.name4, _mem.name5, _mem.name6]
@@ -99,12 +100,10 @@ def get_name(_mem):
 def set_name(_mem, name):
     """Encode @name in @_mem"""
     def _get_index(char):
-        if char == " ":
-            return 0
-        elif char.isalpha():
-            return ALPHA_CHARSET.index(char) | 0x20
-        else:
-            return NUMERIC_CHARSET.index(char) | 0x10
+        try:
+            return CHARSET_REV[char]
+        except KeyError:
+            return CHARSET_REV["*"]
 
     name = name.ljust(6)[:6]
 
@@ -144,10 +143,11 @@ class IC208Radio(icf.IcomCloneModeRadio):
         rf.valid_duplexes = list(DUPLEX)
         rf.valid_power_levels = list(POWER)
         rf.valid_skips = ["", "S", "P"]
-        rf.valid_bands = [(118000000, 173995000),
-                          (230000000, 549995000),
-                          (810000000, 999990000)]
+        rf.valid_bands = [(118000000, 174000000),
+                          (230000000, 550000000),
+                          (810000000, 999995000)]
         rf.valid_special_chans = ["C1", "C2"] + sorted(IC208_SPECIAL)
+        rf.valid_characters = "".join(CHARSET.values())
         return rf
 
     def get_raw_memory(self, number):
diff --git a/chirp/ic2820.py b/chirp/ic2820.py
index 6475713..5f5827c 100644
--- a/chirp/ic2820.py
+++ b/chirp/ic2820.py
@@ -176,7 +176,7 @@ class IC2820Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
         rf.valid_tuning_steps = list(chirp_common.TUNING_STEPS)
         rf.valid_bands = [(118000000, 999990000)]
         rf.valid_skips = ["", "S", "P"]
-        rf.valid_characters = chirp_common.CHARSET_ALPHANUMERIC
+        rf.valid_characters = chirp_common.CHARSET_ASCII
         rf.valid_name_length = 8
         rf.valid_special_chans = sorted(_get_special().keys())
 
diff --git a/chirp/icf.py b/chirp/icf.py
index 8c00bab..fb5950e 100644
--- a/chirp/icf.py
+++ b/chirp/icf.py
@@ -485,10 +485,10 @@ class IcomBankModel(chirp_common.BankModel):
     central implementation can, with a few icom-specific radio interfaces
     serve most/all of them"""
 
-    def get_num_banks(self):
+    def get_num_mappings(self):
         return self._radio._num_banks
 
-    def get_banks(self):
+    def get_mappings(self):
         banks = []
         
         for i in range(0, self._radio._num_banks):
@@ -498,31 +498,32 @@ class IcomBankModel(chirp_common.BankModel):
             banks.append(bank)
         return banks
 
-    def add_memory_to_bank(self, memory, bank):
+    def add_memory_to_mapping(self, memory, bank):
         self._radio._set_bank(memory.number, bank.index)
 
-    def remove_memory_from_bank(self, memory, bank):
+    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))
 
         self._radio._set_bank(memory.number, None)
 
-    def get_bank_memories(self, bank):
+    def get_mapping_memories(self, bank):
         memories = []
         for i in range(*self._radio.get_features().memory_bounds):
             if self._radio._get_bank(i) == bank.index:
                 memories.append(self._radio.get_memory(i))
         return memories
 
-    def get_memory_banks(self, memory):
+    def get_memory_mappings(self, memory):
         index = self._radio._get_bank(memory.number)
         if index is None:
             return []
         else:
-            return [self.get_banks()[index]]
+            return [self.get_mappings()[index]]
     
-class IcomIndexedBankModel(IcomBankModel, chirp_common.BankIndexInterface):
+class IcomIndexedBankModel(IcomBankModel,
+                           chirp_common.MappingModelIndexInterface):
     """Generic bank model for Icom radios with indexed banks"""
     def get_index_bounds(self):
         return self._radio._bank_index_bounds
@@ -531,7 +532,7 @@ class IcomIndexedBankModel(IcomBankModel, chirp_common.BankIndexInterface):
         return self._radio._get_bank_index(memory.number)
 
     def set_memory_index(self, memory, bank, index):
-        if bank not in self.get_memory_banks(memory):
+        if bank not in self.get_memory_mappings(memory):
             raise Exception("Memory %i is not in bank %s" % (memory.number,
                                                              bank))
 
@@ -539,7 +540,7 @@ class IcomIndexedBankModel(IcomBankModel, chirp_common.BankIndexInterface):
             raise Exception("Invalid index")
         self._radio._set_bank_index(memory.number, index)
 
-    def get_next_bank_index(self, bank):
+    def get_next_mapping_index(self, bank):
         indexes = []
         for i in range(*self._radio.get_features().memory_bounds):
             if self._radio._get_bank(i) == bank.index:
diff --git a/chirp/icq7.py b/chirp/icq7.py
index a6e2e95..6a95ef9 100644
--- a/chirp/icq7.py
+++ b/chirp/icq7.py
@@ -13,9 +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/>.
 
+import struct
 from chirp import chirp_common, icf, directory
 from chirp import bitwise
 from chirp.chirp_common import to_GHz, from_GHz
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+                RadioSettingValueBoolean, RadioSettingValueList, \
+                RadioSettingValueInteger, RadioSettingValueString, \
+                RadioSettingValueFloat
 
 MEM_FORMAT = """
 struct {
@@ -39,12 +44,52 @@ struct {
 
 #seekto 0x0690;
 u8 flags_whole[200];
+
+#seekto 0x0767;
+struct {
+i8 rit;
+u8 squelch;
+u8 lock:1,
+   ritfunct:1,
+   unknown:6;
+u8 unknown1[6];
+u8 d_sel;
+u8 autorp;
+u8 priority;
+u8 resume;
+u8 pause;
+u8 p_scan;
+u8 bnk_scan;
+u8 expand;
+u8 ch;
+u8 beep;
+u8 light;
+u8 ap_off;
+u8 p_save;
+u8 monitor;
+u8 speed;
+u8 edge;
+u8 lockgroup;
+} settings;
+
 """
 
 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]
+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)]
+MONITOR_LIST = ["Push", "Hold"]
+LIGHT_LIST = ["Off", "On", "Auto"]
+PRIORITY_LIST = ["Off", "On", "Bell"]
+BANKSCAN_LIST = ["Off", "Bank 0", "Bank 1"]
+EDGE_LIST = ["%sP" % x for x in range(0, 20)] + ["Band", "All"]
+PAUSE_LIST = ["%s sec" % x for x in range(2, 22, 2)] + ["Hold"]
+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):
@@ -60,6 +105,7 @@ class ICQ7Radio(icf.IcomCloneModeRadio):
 
     def get_features(self):
         rf = chirp_common.RadioFeatures()
+        rf.has_settings = True
         rf.memory_bounds = (0, 199)
         rf.valid_modes = list(MODES)
         rf.valid_tmodes = list(TMODES)
@@ -148,3 +194,141 @@ class ICQ7Radio(icf.IcomCloneModeRadio):
         _flag.mode = MODES.index(mem.mode)
         _flag.skip = mem.skip == "S" and 1 or 0
         _flag.pskip = mem.skip == "P" and 1 or 0
+
+    def get_settings(self):
+        _settings = self._memobj.settings
+        basic = RadioSettingGroup("basic", "Basic Settings")
+        group = RadioSettingGroup("top", "All Settings", basic)
+
+        rs = RadioSetting("ch", "Channel Indication Mode",
+                          RadioSettingValueBoolean(_settings.ch))
+        basic.append(rs)
+
+        rs = RadioSetting("expand", "Expanded Settings Mode",
+                          RadioSettingValueBoolean(_settings.expand))
+        basic.append(rs)
+
+        rs = RadioSetting("beep", "Beep Tones",
+                          RadioSettingValueBoolean(_settings.beep))
+        basic.append(rs)
+
+        rs = RadioSetting("autorp", "Auto Repeater Function",
+                          RadioSettingValueList(AUTORP_LIST,
+                              AUTORP_LIST[_settings.autorp]))
+        basic.append(rs)
+
+        rs = RadioSetting("ritfunct", "RIT Runction",
+                          RadioSettingValueBoolean(_settings.ritfunct))
+        basic.append(rs)
+
+        rs = RadioSetting("rit", "RIT Shift (KHz)",
+                          RadioSettingValueInteger(-7, 7, _settings.rit))
+        basic.append(rs)
+
+        rs = RadioSetting("lock", "Lock",
+                          RadioSettingValueBoolean(_settings.lock))
+        basic.append(rs)
+
+        rs = RadioSetting("lockgroup", "Lock Group",
+                          RadioSettingValueList(LOCKGROUP_LIST,
+                              LOCKGROUP_LIST[_settings.lockgroup]))
+        basic.append(rs)
+
+        rs = RadioSetting("squelch", "Squelch",
+                          RadioSettingValueList(SQUELCH_LIST,
+                              SQUELCH_LIST[_settings.squelch]))
+        basic.append(rs)
+
+        rs = RadioSetting("monitor", "Monitor Switch Function",
+                          RadioSettingValueList(MONITOR_LIST,
+                              MONITOR_LIST[_settings.monitor]))
+        basic.append(rs)
+
+        rs = RadioSetting("light", "Display Backlighting",
+                          RadioSettingValueList(LIGHT_LIST,
+                              LIGHT_LIST[_settings.light]))
+        basic.append(rs)
+
+        rs = RadioSetting("priority", "Priority Watch Operation",
+                          RadioSettingValueList(PRIORITY_LIST,
+                              PRIORITY_LIST[_settings.priority]))
+        basic.append(rs)
+
+        rs = RadioSetting("p_scan", "Frequency Skip Function",
+                          RadioSettingValueBoolean(_settings.p_scan))
+        basic.append(rs)
+
+        rs = RadioSetting("bnk_scan", "Memory Bank Scan Selection",
+                          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]))
+        basic.append(rs)
+
+        rs = RadioSetting("pause", "Scan Pause Time",
+                          RadioSettingValueList(PAUSE_LIST,
+                              PAUSE_LIST[_settings.pause]))
+        basic.append(rs)
+
+        rs = RadioSetting("resume", "Scan Resume Time",
+                          RadioSettingValueList(RESUME_LIST,
+                              RESUME_LIST[_settings.resume]))
+        basic.append(rs)
+
+        rs = RadioSetting("p_save", "Power Saver",
+                          RadioSettingValueBoolean(_settings.p_save))
+        basic.append(rs)
+
+        rs = RadioSetting("ap_off", "Auto Power-off Function",
+                          RadioSettingValueList(APOFF_LIST,
+                              APOFF_LIST[_settings.ap_off]))
+        basic.append(rs)
+
+        rs = RadioSetting("speed", "Dial Speed Acceleration",
+                          RadioSettingValueBoolean(_settings.speed))
+        basic.append(rs)
+
+        rs = RadioSetting("d_sel", "Dial Select Step",
+                          RadioSettingValueList(D_SEL_LIST,
+                              D_SEL_LIST[_settings.d_sel]))
+        basic.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():
+                        print "Using apply callback"
+                        element.run_apply_callback()
+                    else:
+                        print "Setting %s = %s" % (setting, element.value)
+                        setattr(obj, setting, element.value)
+                except Exception, e:
+                    print element.get_name()
+                    raise
+
diff --git a/chirp/ict70.py b/chirp/ict70.py
index 391b129..d035e14 100644
--- a/chirp/ict70.py
+++ b/chirp/ict70.py
@@ -77,7 +77,7 @@ class ICT70Bank(icf.IcomBank):
 
     def set_name(self, name):
         _bank = self._model._radio._memobj.bank_names[self.index]
-        _bank.name = name.ljust(8)[:8]
+        _bank.name = name.ljust(6)[:6]
 
 @directory.register
 class ICT70Radio(icf.IcomCloneModeRadio):
diff --git a/chirp/ict7h.py b/chirp/ict7h.py
index 4781652..768fb34 100644
--- a/chirp/ict7h.py
+++ b/chirp/ict7h.py
@@ -57,7 +57,7 @@ class ICT7HRadio(icf.IcomCloneModeRadio):
 
     def get_features(self):
         rf = chirp_common.RadioFeatures()
-        rf.memory_bounds = (0, 59)
+        rf.memory_bounds = (1, 60)
         rf.valid_modes = list(MODES)
         rf.valid_tmodes = list(TMODES)
         rf.valid_duplexes = list(DUPLEX)
@@ -79,8 +79,8 @@ class ICT7HRadio(icf.IcomCloneModeRadio):
         return repr(self._memobj.memory[number])
 
     def get_memory(self, number):
-        _mem = self._memobj.memory[number]
-        _flag = self._memobj.flags[number]
+        _mem = self._memobj.memory[number - 1]
+        _flag = self._memobj.flags[number - 1]
 
         mem = chirp_common.Memory()
         mem.number = number
@@ -103,8 +103,8 @@ class ICT7HRadio(icf.IcomCloneModeRadio):
         return mem
 
     def set_memory(self, mem):
-        _mem = self._memobj.memory[mem.number]
-        _flag = self._memobj.flags[mem.number]
+        _mem = self._memobj.memory[mem.number - 1]
+        _flag = self._memobj.flags[mem.number - 1]
 
         _mem.freq = int(mem.freq / 100000)
         topfreq = int(mem.freq / 100000) * 100000
diff --git a/chirp/id31.py b/chirp/id31.py
index b381d07..128d79d 100644
--- a/chirp/id31.py
+++ b/chirp/id31.py
@@ -22,9 +22,7 @@ struct {
   u16 rtone:6,
       ctone:6,
       unknown2:1,
-      is_dv:1,
-      unknown2_0:1,
-      is_narrow:1;
+      mode:3;
   u8 dtcs;
   u8 tune_step:4,
      unknown5:4;
@@ -79,6 +77,7 @@ struct {
 #seekto 0x10F20;
 struct {
   char call[8];
+  char tag[4];
 } mycall[6];
 
 #seekto 0x10F68;
@@ -166,6 +165,8 @@ class ID31Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
 
     _ranges = [(0x00000, 0x15500, 32)]
 
+    MODES = {0: "FM", 1: "NFM", 5: "DV"}
+
     def _get_bank(self, loc):
         _bank = self._memobj.banks[loc]
         if _bank.bank == 0xFF:
@@ -198,7 +199,7 @@ class ID31Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
         rf.has_bank_names = True
         rf.valid_tmodes = list(TMODES)
         rf.valid_tuning_steps = sorted(list(TUNING_STEPS))
-        rf.valid_modes = ["FM", "NFM", "DV"]
+        rf.valid_modes = self.MODES.values()
         rf.valid_skips = ["", "S", "P"]
         rf.valid_characters = chirp_common.CHARSET_ASCII
         rf.valid_name_length = 16
@@ -218,7 +219,7 @@ class ID31Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
 
         bit = (1 << (number % 8))
 
-        if _mem.is_dv:
+        if self.MODES[int(_mem.mode)] == "DV":
             mem = chirp_common.DVMemory()
         else:
             mem = chirp_common.Memory()
@@ -237,16 +238,12 @@ class ID31Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
         mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
         mem.dtcs_polarity = DTCS_POLARITY[_mem.dtcs_polarity]
         mem.tuning_step = TUNING_STEPS[_mem.tune_step]
+        mem.mode = self.MODES[int(_mem.mode)]
 
-        if _mem.is_dv:
-            mem.mode = "DV"
+        if mem.mode == "DV":
             mem.dv_urcall = _decode_call(_mem.urcall).rstrip()
             mem.dv_rpt1call = _decode_call(_mem.rpt1call).rstrip()
             mem.dv_rpt2call = _decode_call(_mem.rpt2call).rstrip()
-        elif _mem.is_narrow:
-            mem.mode = "NFM"
-        else:
-            mem.mode = "FM"
 
         if _psk & bit:
             mem.skip = "P"
@@ -279,9 +276,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.is_narrow = memory.mode in ["NFM", "DV"]
-        _mem.is_dv = memory.mode == "DV"
+        _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))
diff --git a/chirp/id51.py b/chirp/id51.py
new file mode 100644
index 0000000..4b72c44
--- /dev/null
+++ b/chirp/id51.py
@@ -0,0 +1,109 @@
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from chirp import directory, bitwise, id31
+
+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 bank;
+  u8 index;
+} banks[500];
+
+#seekto 0x6FD0;
+struct {
+  char name[16];
+} bank_names[26];
+
+#seekto 0xA8C0;
+struct {
+  u24 freq;
+  u16 offset;
+  u8 unknown1[3];
+  u8 call[7];
+  char name[16];
+  char subname[8];
+  u8 unknown3[10];
+} repeaters[750];
+
+#seekto 0x1384E;
+struct {
+  u8 call[7];
+} rptcall[750];
+
+#seekto 0x14E60;
+struct {
+  char call[8];
+  char tag[4];
+} mycall[6];
+
+#seekto 0x14EA8;
+struct {
+  char call[8];
+} urcall[200];
+
+"""
+
+
+ at directory.register
+class ID51Radio(id31.ID31Radio):
+    """Icom ID-51"""
+    MODEL = "ID-51A"
+
+    _memsize = 0x1FB40
+    _model = "\x33\x90\x00\x01"
+    _endframe = "Icom Inc\x2E\x44\x41"
+
+    _ranges = [(0x00000, 0x1FB40, 32)]
+
+    MODES = {0: "FM", 1: "NFM", 3: "AM", 5: "DV"}
+
+    def get_features(self):
+        rf = super(ID51Radio, self).get_features()
+        rf.valid_bands = [(108000000, 174000000), (400000000, 479000000)]
+        return rf
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
diff --git a/chirp/import_logic.py b/chirp/import_logic.py
index e52283a..b787378 100644
--- a/chirp/import_logic.py
+++ b/chirp/import_logic.py
@@ -210,38 +210,42 @@ def import_mem(dst_radio, src_features, src_mem, overrides={}):
                                     ", ".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"""
 
-    dst_bm = dst_radio.get_bank_model()
+    dst_bm = _get_bank_model(dst_radio)
     if not dst_bm:
         return
 
-    dst_banks = dst_bm.get_banks()
+    dst_banks = dst_bm.get_mappings()
 
-    src_bm = src_radio.get_bank_model()
+    src_bm = _get_bank_model(src_radio)
     if not src_bm:
         return
 
-    src_banks = src_bm.get_banks()
-    src_mem_banks = src_bm.get_memory_banks(src_mem)
+    src_banks = src_bm.get_mappings()
+    src_mem_banks = src_bm.get_memory_mappings(src_mem)
     src_indexes = [src_banks.index(b) for b in src_mem_banks]
 
-    for bank in dst_bm.get_memory_banks(dst_mem):
-        dst_bm.remove_memory_from_bank(dst_mem, bank)
+    for bank in dst_bm.get_memory_mappings(dst_mem):
+        dst_bm.remove_memory_from_mapping(dst_mem, bank)
 
     for index in src_indexes:
         try:
             bank = dst_banks[index]
             print "Adding memory to bank %s" % bank
-            dst_bm.add_memory_to_bank(dst_mem, bank)
-            if isinstance(dst_bm, chirp_common.BankIndexInterface):
+            dst_bm.add_memory_to_mapping(dst_mem, bank)
+            if isinstance(dst_bm, chirp_common.MappingModelIndexInterface):
                 dst_bm.set_memory_index(dst_mem, bank,
-                                        dst_bm.get_next_bank_index(bank))
+                                        dst_bm.get_next_mapping_index(bank))
 
         except IndexError:
             pass
-
-    
diff --git a/chirp/kenwood_hmk.py b/chirp/kenwood_hmk.py
index de52f68..59260ac 100644
--- a/chirp/kenwood_hmk.py
+++ b/chirp/kenwood_hmk.py
@@ -120,3 +120,8 @@ class HMKRadio(generic_csv.CSVRadio):
         if not good:
             print self.errors
             raise errors.InvalidDataError("No channels found")
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        """Match files ending in .hmk"""
+        return filename.lower().endswith("." + cls.FILE_EXTENSION)
diff --git a/chirp/kenwood_hmk.py b/chirp/kenwood_itm.py
similarity index 62%
copy from chirp/kenwood_hmk.py
copy to chirp/kenwood_itm.py
index de52f68..86414eb 100644
--- a/chirp/kenwood_hmk.py
+++ b/chirp/kenwood_itm.py
@@ -23,47 +23,53 @@ class OmittedHeaderError(Exception):
     pass
 
 @directory.register
-class HMKRadio(generic_csv.CSVRadio):
-    """Kenwood HMK format"""
+class ITMRadio(generic_csv.CSVRadio):
+    """Kenwood ITM format"""
     VENDOR = "Kenwood"
-    MODEL = "HMK"
-    FILE_EXTENSION = "hmk"
-
-    DUPLEX_MAP = {
-        " ":    "",
-        "S":    "split",
-        "+":    "+",
-        "-":    "-",
-    }
-
-    SKIP_MAP = {
-        "Off":  "",
-        "On":   "S",
-        }
-
-    TMODE_MAP = {
-        "Off":  "",
-        "T":    "Tone",
-        "CT":   "TSQL",
-        "DCS":  "DTCS",
-        "":     "Cross",
-        }
+    MODEL = "ITM"
+    FILE_EXTENSION = "itm"
 
     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"),
+        "RXF"          : (chirp_common.parse_freq, "freq"),
+        "NAME"         : (str,  "name"),
         }
 
+    def _clean_duplex(self, headers, line, mem):
+        try:
+            txfreq = chirp_common.parse_freq(
+                        generic_csv.get_datum_by_header(headers, line, "TXF"))
+        except ValueError:
+            mem.duplex = "off"
+            return mem
+
+        if mem.freq == txfreq:
+            mem.duplex = ""
+        elif txfreq:
+            mem.duplex = "split"
+            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
+        return mem
+
+    def _clean_tmode(self, headers, line, mem):
+        rtone = eval(generic_csv.get_datum_by_header(headers, line, "TXSIG"))
+        ctone = eval(generic_csv.get_datum_by_header(headers, line, "RXSIG"))
+
+        if rtone:
+            mem.tmode = "Tone"
+        if ctone:
+            mem.tmode = "TSQL"
+
+        mem.rtone = rtone or 88.5
+        mem.ctone = ctone or mem.rtone
+
+        return mem
+
     def load(self, filename=None):
         if filename is None and self._filename is None:
             raise errors.RadioError("Need a location to load from")
@@ -75,7 +81,7 @@ class HMKRadio(generic_csv.CSVRadio):
 
         f = file(self._filename, "r")
         for line in f:
-            if line.strip() == "// Memory Channels":
+            if line.strip() == "// Conventional Data":
                 break
 
         reader = csv.reader(f, delimiter=chirp_common.SEPCHAR, quotechar='"')
@@ -88,6 +94,10 @@ class HMKRadio(generic_csv.CSVRadio):
                 header = line
                 continue
 
+            if len(line) == 0:
+                # End of channel data
+                break
+
             if len(header) > len(line):
                 print "Line %i has %i columns, expected %i" % (lineno,
                                                                len(line),
@@ -95,12 +105,6 @@ class HMKRadio(generic_csv.CSVRadio):
                 self.errors.append("Column number mismatch on line %i" % lineno)
                 continue
 
-            # hmk stores Tx Freq. in its own field, but Chirp expects the Tx
-            # Freq. for odd-split channels to be in the Offset field.
-            # If channel is odd-split, copy Tx Freq. field to Offset field.
-            if line[header.index('Shift/Split')] == "S":
-                line[header.index('Offset')] = line[header.index('Tx Freq.')]
-
             # fix EU decimal
             line = [i.replace(',','.') for i in line]
 
@@ -120,3 +124,7 @@ class HMKRadio(generic_csv.CSVRadio):
         if not good:
             print self.errors
             raise errors.InvalidDataError("No channels found")
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return filename.lower().endswith("." + cls.FILE_EXTENSION)
diff --git a/chirp/kenwood_live.py b/chirp/kenwood_live.py
index d66a73f..bfa87dd 100644
--- a/chirp/kenwood_live.py
+++ b/chirp/kenwood_live.py
@@ -158,7 +158,7 @@ class KenwoodLiveRadio(chirp_common.LiveRadio):
             return self.__memcache[number]
 
         result = command(self.pipe, *self._cmd_get_memory(number))
-        if result == "N":
+        if result == "N" or result == "E":
             mem = chirp_common.Memory()
             mem.number = number
             mem.empty = True
@@ -268,8 +268,23 @@ TH_D7_SETTINGS = {
               "9 digit NMEA", "6 digit Magellan", "DGPS"],
 }
 
+class KenwoodOldLiveRadio(KenwoodLiveRadio):
+    _kenwood_valid_tones = list(chirp_common.OLD_TONES)
+
+    def set_memory(self, memory):
+        supported_tones = list(chirp_common.OLD_TONES)
+        supported_tones.remove(69.3)
+        if memory.rtone not in supported_tones:
+            raise errors.UnsupportedToneError("This radio does not support " +
+                                              "tone %.1fHz" % memory.rtone)
+        if memory.ctone not in supported_tones:
+            raise errors.UnsupportedToneError("This radio does not support " +
+                                              "tone %.1fHz" % memory.ctone)
+
+        return KenwoodLiveRadio.set_memory(self, memory)
+
 @directory.register
-class THD7Radio(KenwoodLiveRadio):
+class THD7Radio(KenwoodOldLiveRadio):
     """Kenwood TH-D7"""
     MODEL = "TH-D7"
 
@@ -475,7 +490,7 @@ class THD7GRadio(THD7Radio):
     MODEL = "TH-D7G"
 
 @directory.register
-class TMD700Radio(KenwoodLiveRadio):
+class TMD700Radio(KenwoodOldLiveRadio):
     """Kenwood TH-D700"""
     MODEL = "TM-D700"
 
@@ -542,40 +557,13 @@ class TMD700Radio(KenwoodLiveRadio):
 
         return mem
 
-OLD_TONES = list(chirp_common.TONES)
-OLD_TONES.remove(159.8)
-OLD_TONES.remove(165.5)
-OLD_TONES.remove(171.3)
-OLD_TONES.remove(177.3)
-OLD_TONES.remove(183.5)
-OLD_TONES.remove(189.9)
-OLD_TONES.remove(196.6)
-OLD_TONES.remove(199.5)
-OLD_TONES.remove(206.5)
-OLD_TONES.remove(229.1)
-OLD_TONES.remove(254.1)
-
 @directory.register
-class TMV7Radio(KenwoodLiveRadio):
+class TMV7Radio(KenwoodOldLiveRadio):
     """Kenwood TM-V7"""
     MODEL = "TM-V7"
 
     mem_upper_limit = 200 # Will be updated
 
-    _kenwood_valid_tones = list(OLD_TONES)
-
-    def set_memory(self, memory):
-        supported_tones = list(OLD_TONES)
-        supported_tones.remove(69.3)
-        if memory.rtone not in supported_tones:
-            raise errors.UnsupportedToneError("This radio does not support " +
-                                              "tone %.1fHz" % memory.rtone)
-        if memory.ctone not in supported_tones:
-            raise errors.UnsupportedToneError("This radio does not support " +
-                                              "tone %.1fHz" % memory.ctone)
-
-        return KenwoodLiveRadio.set_memory(self, memory)
-
     def get_features(self):
         rf = chirp_common.RadioFeatures()
         rf.has_dtcs = False
@@ -680,10 +668,63 @@ class TMG707Radio(TMV7Radio):
         rf = TMV7Radio.get_features(self)
         rf.has_sub_devices = False
         rf.memory_bounds = (1, 180)
-        rf.valid_bands = [(144000000, 148000000),
-                          (430000000, 450000000)]
+        rf.valid_bands = [(118000000, 174000000),
+                          (300000000, 520000000),
+                          (800000000, 999000000)]
         return rf
 
+THG71_STEPS = [ 5, 6.25, 10, 12.5, 15, 20, 25, 30, 50, 100 ]
+ at directory.register
+class THG71Radio(TMV7Radio):
+    """Kenwood TH-G71"""
+    MODEL = "TH-G71"
+
+    def get_features(self):
+        rf = TMV7Radio.get_features(self)
+        rf.has_tuning_step = True
+        rf.valid_tuning_steps = list(THG71_STEPS)
+        rf.valid_name_length = 6
+        rf.has_sub_devices = False
+        rf.valid_bands = [(118000000, 174000000),
+                          (320000000, 470000000),
+						  (800000000, 945000000)]		
+        return rf
+
+    def _make_mem_spec(self, mem):
+        spec = ( \
+            "%011i" % mem.freq,
+            "%X" % THG71_STEPS.index(mem.tuning_step),
+            "%i" % util.get_dict_rev(DUPLEX, mem.duplex),
+            "0",
+            "%i" % (mem.tmode == "Tone"),
+            "%i" % (mem.tmode == "TSQL"),
+            "0",
+            "%02i" % (self._kenwood_valid_tones.index(mem.rtone) + 1),
+            "000",
+            "%02i" % (self._kenwood_valid_tones.index(mem.ctone) + 1),
+            "%09i" % mem.offset,
+            "%i" % ((mem.skip == "S") and 1 or 0))
+        return spec
+
+    def _parse_mem_spec(self, spec):
+        mem = chirp_common.Memory()
+        mem.number = int(spec[2])
+        mem.freq = int(spec[3])
+        mem.tuning_step = THG71_STEPS[int(spec[4], 16)]
+        mem.duplex = DUPLEX[int(spec[5])]
+        if int(spec[7]):
+            mem.tmode = "Tone"
+        elif int(spec[8]):
+            mem.tmode = "TSQL"
+        mem.rtone = self._kenwood_valid_tones[int(spec[10]) - 1]
+        mem.ctone = self._kenwood_valid_tones[int(spec[12]) - 1]
+        if spec[13]:
+            mem.offset = int(spec[13])
+        else:
+            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]
 
@@ -1084,6 +1125,12 @@ class TM271Radio(THK2Radio):
     def _cmd_set_memory_name(self, number, name):
         return "MN", "%03i,%s" % (number, name)
 
+ at 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()
@@ -1114,5 +1161,70 @@ def do_test():
     radio = tc(FakeSerial())
     radio.set_memory(mem)
 
+ at directory.register
+class TM471Radio(THK2Radio):
+    """Kenwood TM-471"""
+    MODEL = "TM-471"
+    
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.can_odd_split = False
+        rf.has_dtcs_polarity = False
+        rf.has_bank = False
+        rf.has_tuning_step = False
+        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
+        rf.valid_modes = THK2_MODES
+        rf.valid_duplexes = THK2_DUPLEX
+        rf.valid_characters = THK2_CHARS
+        rf.valid_name_length = 6
+        rf.valid_bands = [(444000000, 479990000)]
+        rf.valid_skips = ["", "S"]
+        rf.valid_tuning_steps = [5.0]
+        rf.memory_bounds = (0, 99)
+        return rf
+
+    def _cmd_get_memory(self, number):
+        return "ME", "%03i" % number
+
+    def _cmd_get_memory_name(self, number):
+        return "MN", "%03i" % number
+
+    def _cmd_set_memory(self, number, spec):
+        return "ME", "%03i,%s" % (number, spec)
+
+    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/platform.py b/chirp/platform.py
index da346a4..313dfbd 100644
--- a/chirp/platform.py
+++ b/chirp/platform.py
@@ -16,41 +16,47 @@
 import os
 import sys
 import glob
+import re
 from subprocess import Popen
 
+def win32_comports_bruteforce():
+    import win32file
+    import win32con
+
+    ports = []
+    for i in range(1, 257):
+        portname = "COM%i" % i
+        try:
+            mode = win32con.GENERIC_READ | win32con.GENERIC_WRITE
+            port = \
+                win32file.CreateFile(portname,
+                                     mode,
+                                     win32con.FILE_SHARE_READ,
+                                     None,
+                                     win32con.OPEN_EXISTING,
+                                     0,
+                                     None)
+            ports.append((portname,"Unknown","Serial"))
+            win32file.CloseHandle(port)
+            port = None
+        except Exception, e:
+            pass
+
+    return ports
+
 try:
     from serial.tools.list_ports import comports
 except:
-    def comports():
-        import win32file
-        import win32con
-
-        ports = []
-        for i in range(1, 257):
-            portname = "COM%i" % i
-            print "Trying %s" % portname
-            try:
-                mode = win32con.GENERIC_READ | win32con.GENERIC_WRITE
-                port = \
-                    win32file.CreateFile(portname,
-                                         mode,
-                                         win32con.FILE_SHARE_READ,
-                                         None,
-                                         win32con.OPEN_EXISTING,
-                                         0,
-                                         None)
-                ports.append((portname,"Unknown","Serial"))
-                win32file.CloseHandle(port)
-                port = None
-            except Exception, e:
-                print "Failed: %s" % e
-                pass
-
-        return ports
-    
+    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)]
+    return sorted(l, key=natural_key)
+
 class Platform:
     """Base class for platform-specific functions"""
 
@@ -277,11 +283,13 @@ class UnixPlatform(Platform):
         os.system("firefox '%s'" % path)
 
     def list_serial_ports(self):
-        return sorted(glob.glob("/dev/ttyS*") +
-                      glob.glob("/dev/ttyUSB*") +
-                      glob.glob("/dev/cu.*") +
-                      glob.glob("/dev/term/*") +
-                      glob.glob("/dev/tty.KeySerial*"))
+        ports = ["/dev/ttyS*",
+                 "/dev/ttyUSB*",
+                 "/dev/ttyAMA*",
+                 "/dev/cu.*",
+                 "/dev/term/*",
+                 "/dev/tty.KeySerial*"]
+        return natural_sorted(sum([glob.glob(x) for x in ports], []))
 
     def os_version_string(self):
         try:
@@ -324,17 +332,15 @@ class Win32Platform(Platform):
 
     def open_html_file(self, path):
         os.system("explorer %s" % path)
-    
+
     def list_serial_ports(self):
-        def cmp(a, b):
-            try:
-                return int(a[3:]) - int(b[3:])
-            except:
-                return 0
-
-        ports = [port for port, name, url in comports()]
-        ports.sort(cmp=cmp)
-        return ports
+        try:
+            ports = list(comports())
+        except Exception, e:
+            if comports != win32_comports_bruteforce:
+                print "Failed to detect win32 serial ports: %s" % e
+                ports = win32_comports_bruteforce()
+        return natural_sorted([port for port, name, url in ports])
 
     def gui_open_file(self, start_dir=None, types=[]):
         import win32gui
diff --git a/chirp/puxing.py b/chirp/puxing.py
index 05602ef..88cbe79 100644
--- a/chirp/puxing.py
+++ b/chirp/puxing.py
@@ -159,7 +159,7 @@ class Puxing777Radio(chirp_common.CloneModeRadio):
         rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
         rf.valid_modes = ["FM", "NFM"]
         rf.valid_power_levels = POWER_LEVELS
-        rf.valid_characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+        rf.valid_characters = ''.join(set(PUXING_CHARSET))
         rf.valid_name_length = 6
         rf.has_ctone = False
         rf.has_tuning_step = False
@@ -167,15 +167,24 @@ class Puxing777Radio(chirp_common.CloneModeRadio):
         rf.memory_bounds = (1, 128)
 
         if not hasattr(self, "_memobj") or self._memobj is None:
-            limit_idx = 1
-        else:
-            limit_idx = self._memobj.model.limits - 0xEE
-        try:
-            rf.valid_bands = [PUXING_777_BANDS[limit_idx]]
-        except IndexError:
-            print "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[777]:
+            limit_idx = self._memobj.model.limits - 0xEE
+            try:
+                rf.valid_bands = [PUXING_777_BANDS[limit_idx]]
+            except IndexError:
+                print "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 ...
+            # for them we only know this freq limits till now
+            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!!")
 
         return rf
 
@@ -188,10 +197,13 @@ class Puxing777Radio(chirp_common.CloneModeRadio):
 
     @classmethod
     def match_model(cls, filedata, filename):
-        if len(filedata) > 0x080B and \
-                ord(filedata[0x080B]) != PUXING_MODELS[777]:
-            return False
-        return len(filedata) == 3168
+        # 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
+                    )
+                )
 
     def get_memory(self, number):
         _mem = self._memobj.memory[number - 1]
@@ -275,6 +287,7 @@ class Puxing777Radio(chirp_common.CloneModeRadio):
             if i == 0xFF:
                 break
             mem.name += PUXING_CHARSET[i]
+        mem.name = mem.name.rstrip()
 
         return mem
 
diff --git a/chirp/pyPEG.py b/chirp/pyPEG.py
index 381a403..5faa637 100644
--- a/chirp/pyPEG.py
+++ b/chirp/pyPEG.py
@@ -1,14 +1,16 @@
-# YPL parser 0.45
+# YPL parser 1.5
 
 # written by VB.
 
 import re
+import sys, codecs
+import exceptions
 
-class keyword(str): pass
-class code(str): pass
+class keyword(unicode): pass
+class code(unicode): pass
 class ignore(object):
-    def __init__(self, regex_text):
-        self.regex = re.compile(regex_text)
+    def __init__(self, regex_text, *args):
+        self.regex = re.compile(regex_text, *args)
 
 class _and(object):
     def __init__(self, something):
@@ -16,18 +18,44 @@ class _and(object):
 
 class _not(_and): pass
 
-class Name(str):
+class Name(unicode):
     def __init__(self, *args):
         self.line = 0
-        self.file = ""
-
-word_regex = re.compile(r"\w+")
-rest_regex = re.compile(r".*")
-ignoring = ignore("")
+        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)
+
+word_regex = re.compile(ur"\w+")
+rest_regex = re.compile(ur".*")
+
+print_trace = False
+
+def u(text):
+    if isinstance(text, exceptions.BaseException):
+        text = text.args[0]
+    if type(text) is unicode:
+        return text
+    if isinstance(text, str):
+        if sys.stdin.encoding:
+            return codecs.decode(text, sys.stdin.encoding)
+        else:
+            return codecs.decode(text, "utf-8")
+    return unicode(text)
 
-def skip(skipper, text, pattern, skipWS, skipComments):
+def skip(skipper, text, skipWS, skipComments):
     if skipWS:
-        t = text.strip()
+        t = text.lstrip()
     else:
         t = text
     if skipComments:
@@ -35,21 +63,22 @@ def skip(skipper, text, pattern, skipWS, skipComments):
             while True:
                 skip, t = skipper.parseLine(t, skipComments, [], skipWS, None)
                 if skipWS:
-                    t = t.strip()
+                    t = t.lstrip()
         except: pass
     return t
 
 class parser(object):
-    def __init__(self, another = False):
+    def __init__(self, another = False, p = False):
         self.restlen = -1 
         if not(another):
-            self.skipper = parser(True)
+            self.skipper = parser(True, p)
+            self.skipper.packrat = p
         else:
             self.skipper = self
         self.lines = None
         self.textlen = 0
         self.memory = {}
-        self.packrat = False
+        self.packrat = p
 
     # parseLine():
     #   textline:       text to parse
@@ -69,100 +98,111 @@ class parser(object):
         name = None
         _textline = textline
         _pattern = pattern
-        _packrat = self.packrat
-        _memory = self.memory
 
         def R(result, text):
+            if __debug__:
+                if print_trace:
+                    try:
+                        if _pattern.__name__ != "comment":
+                            sys.stderr.write(u"match: " + _pattern.__name__ + u"\n")
+                    except: pass
+
             if self.restlen == -1:
                 self.restlen = len(text)
             else:
                 self.restlen = min(self.restlen, len(text))
             res = resultSoFar
             if name and result:
-                res.append((name, result))
+                name.line = self.lineNo()
+                res.append(Symbol(name, result))
             elif name:
-                res.append((name, []))
+                name.line = self.lineNo()
+                res.append(Symbol(name, []))
             elif result:
                 if type(result) is type([]):
                     res.extend(result)
                 else:
                     res.extend([result])
-            if _packrat:
-                if name:
-                    _memory[(len(_textline), id(_pattern))] = (res, text)
+            if self.packrat:
+                self.memory[(len(_textline), id(_pattern))] = (res, text)
             return res, text
 
         def syntaxError():
-            if _packrat:
-                if name:
-                    _memory[(len(_textline), id(_pattern))] = False
+            if self.packrat:
+                self.memory[(len(_textline), id(_pattern))] = False
             raise SyntaxError()
 
-        if type(pattern) is type(lambda x: 0):
-            if _packrat:
-                try:
-                    result = _memory[(len(_textline), id(_pattern))]
-                    if result:
-                        return result
-                    else:
-                        raise SyntaxError()
-                except: pass
+        if self.packrat:
+            try:
+                result = self.memory[(len(textline), id(pattern))]
+                if result:
+                    return result
+                else:
+                    raise SyntaxError()
+            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
 
             if pattern.__name__[0] != "_":
                 name = Name(pattern.__name__)
-                name.line = self.lineNo()
 
             pattern = pattern()
-            if type(pattern) is type(lambda x: 0):
+            if callable(pattern):
                 pattern = (pattern,)
 
-        text = skip(self.skipper, textline, pattern, skipWS, skipComments)
+        text = skip(self.skipper, textline, skipWS, skipComments)
 
         pattern_type = type(pattern)
 
-        if pattern_type is type(""):
+        if pattern_type is str or pattern_type is unicode:
             if text[:len(pattern)] == pattern:
-                text = skip(self.skipper, text[len(pattern):], pattern, skipWS, skipComments)
+                text = skip(self.skipper, text[len(pattern):], skipWS, skipComments)
                 return R(None, text)
             else:
                 syntaxError()
 
-        elif pattern_type is type(keyword("")):
+        elif pattern_type is keyword:
             m = word_regex.match(text)
             if m:
                 if m.group(0) == pattern:
-                    text = skip(self.skipper, text[len(pattern):], pattern, skipWS, skipComments)
+                    text = skip(self.skipper, text[len(pattern):], skipWS, skipComments)
                     return R(None, text)
                 else:
                     syntaxError()
             else:
                 syntaxError()
 
-        elif pattern_type is type(_not("")):
+        elif pattern_type is _not:
             try:
                 r, t = self.parseLine(text, pattern.obj, [], skipWS, skipComments)
             except:
                 return resultSoFar, textline
             syntaxError()
 
-        elif pattern_type is type(_and("")):
+        elif pattern_type is _and:
             r, t = self.parseLine(text, pattern.obj, [], skipWS, skipComments)
             return resultSoFar, textline
 
-        elif pattern_type is type(word_regex) or pattern_type is type(ignoring):
-            if pattern_type is type(ignoring):
+        elif pattern_type is type(word_regex) or pattern_type is ignore:
+            if pattern_type is ignore:
                 pattern = pattern.regex
             m = pattern.match(text)
             if m:
-                text = skip(self.skipper, text[len(m.group(0)):], pattern, skipWS, skipComments)
-                if pattern_type is type(ignoring):
+                text = skip(self.skipper, text[len(m.group(0)):], skipWS, skipComments)
+                if pattern_type is ignore:
                     return R(None, text)
                 else:
                     return R(m.group(0), text)
             else:
                 syntaxError()
 
-        elif pattern_type is type((None,)):
+        elif pattern_type is tuple:
             result = []
             n = 1
             for p in pattern:
@@ -194,7 +234,7 @@ class parser(object):
                     n = 1
             return R(result, text)
 
-        elif pattern_type is type([]):
+        elif pattern_type is list:
             result = []
             found = False
             for p in pattern:
@@ -211,42 +251,41 @@ class parser(object):
                 syntaxError()
 
         else:
-            raise SyntaxError("illegal type in grammar: " + str(pattern_type))
+            raise SyntaxError(u"illegal type in grammar: " + u(pattern_type))
 
     def lineNo(self):
-        if not(self.lines): return ""
-        if self.restlen == -1: return ""
+        if not(self.lines): return u""
+        if self.restlen == -1: return u""
         parsed = self.textlen - self.restlen
 
         left, right = 0, len(self.lines)
 
         while True:
-            mid = (right + left) / 2
+            mid = int((right + left) / 2)
             if self.lines[mid][0] <= parsed:
                 try:
                     if self.lines[mid + 1][0] >= parsed:
                         try:
-                            return self.lines[mid + 1][1] + ":" + str(self.lines[mid + 1][2])
+                            return u(self.lines[mid + 1][1]) + u":" + u(self.lines[mid + 1][2])
                         except:
-                            return ""
+                            return u""
                     else:
                         left = mid + 1
                 except:
                     try:
-                        return self.lines[mid + 1][1] + ":" + str(self.lines[mid + 1][2])
+                        return u(self.lines[mid + 1][1]) + u":" + u(self.lines[mid + 1][2])
                     except:
-                        return ""
+                        return u""
             else:
                 right = mid - 1
             if left > right:
-                return ""
+                return u""
 
 # plain module API
 
 def parseLine(textline, pattern, resultSoFar = [], skipWS = True, skipComments = None, packrat = False):
-    p = parser()
-    p.packrat = packrat
-    text = skip(p.skipper, textline, pattern, skipWS, skipComments)
+    p = parser(p=packrat)
+    text = skip(p.skipper, textline, skipWS, skipComments)
     ast, text = p.parseLine(text, pattern, resultSoFar, skipWS, skipComments)
     return ast, text
 
@@ -266,28 +305,28 @@ def parseLine(textline, pattern, resultSoFar = [], skipWS = True, skipComments =
 def parse(language, lineSource, skipWS = True, skipComments = None, packrat = False, lineCount = True):
     lines, lineNo = [], 0
 
-    while type(language) is type(lambda x: 0):
+    while callable(language):
         language = language()
 
-    orig, ld = "", 0
+    orig, ld = u"", 0
     for line in lineSource:
         if lineSource.isfirstline():
             ld = 1
         else:
             ld += 1
         lines.append((len(orig), lineSource.filename(), lineSource.lineno() - 1))
-        orig += line
+        orig += u(line)
+
     textlen = len(orig)
 
     try:
-        p = parser()
-        p.packrat = packrat
+        p = parser(p=packrat)
         p.textlen = len(orig)
         if lineCount:
             p.lines = lines
         else:
             p.line = None
-        text = skip(p.skipper, orig, language, skipWS, skipComments)
+        text = skip(p.skipper, orig, skipWS, skipComments)
         result, text = p.parseLine(text, language, [], skipWS, skipComments)
         if text:
             raise SyntaxError()
@@ -295,7 +334,7 @@ def parse(language, lineSource, skipWS = True, skipComments = None, packrat = Fa
     except SyntaxError, msg:
         parsed = textlen - p.restlen
         textlen = 0
-        nn, lineNo, file = 0, 0, ""
+        nn, lineNo, file = 0, 0, u""
         for n, ld, l in lines:
             if n >= parsed:
                 break
@@ -307,6 +346,6 @@ def parse(language, lineSource, skipWS = True, skipComments = None, packrat = Fa
         lineNo += 1
         nn -= 1
         lineCont = orig.splitlines()[nn]
-        raise SyntaxError("syntax error in " + file + ":" + str(l) + ": " + 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 6db0ceb..9ca54aa 100644
--- a/chirp/radioreference.py
+++ b/chirp/radioreference.py
@@ -16,6 +16,7 @@
 from chirp import chirp_common, errors
 try:
     from suds.client import Client
+    from suds import WebFault
     HAVE_SUDS = True
 except ImportError:
     HAVE_SUDS = False
@@ -62,8 +63,11 @@ class RadioReferenceRadio(chirp_common.NetworkSourceRadio):
         """Fetches frequencies for all subcategories in a county."""
         self._freqs = []
 
-        zipcode = self._client.service.getZipcodeInfo(self._zip, self._auth)
-        county = self._client.service.getCountyInfo(zipcode.ctid, self._auth)
+        try:
+            zipcode = self._client.service.getZipcodeInfo(self._zip, self._auth)
+            county = self._client.service.getCountyInfo(zipcode.ctid, self._auth)
+        except WebFault, err:
+            raise errors.RadioError(err)
 
         status = chirp_common.Status()
         status.max = 0
diff --git a/chirp/settings.py b/chirp/settings.py
index 111c424..ad1e15c 100644
--- a/chirp/settings.py
+++ b/chirp/settings.py
@@ -28,16 +28,30 @@ class RadioSettingValue:
     def __init__(self):
         self._current = None
         self._has_changed = False
+        self._validate_callback = lambda x: x
+        self._mutable = True
+
+    def set_mutable(self, mutable):
+        self._mutable = mutable
+
+    def get_mutable(self):
+        return self._mutable
 
     def changed(self):
         """Returns True if the setting has been changed since init"""
         return self._has_changed
 
+    def set_validate_callback(self, callback):
+        self._validate_callback = callback
+
     def set_value(self, value):
         """Sets the current value, triggers changed"""
+        if not self.get_mutable():
+            raise InvalidValueError("This value is not mutable")
+
         if self._current != None and value != self._current:
             self._has_changed = True
-        self._current = value
+        self._current = self._validate_callback(value)
 
     def get_value(self):
         """Gets the current value"""
@@ -81,6 +95,45 @@ 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):
+        RadioSettingValue.__init__(self)
+        self._min = minval
+        self._max = maxval
+        self._res = resolution
+        self._pre = precision
+        self.set_value(current)
+
+    def format(self, value=None):
+        """Formats the value into a string"""
+        if value is None:
+            value = self._current
+        fmt_string = "%%.%if" % self._pre
+        print fmt_string
+        return fmt_string % value
+
+    def set_value(self, value):
+        try:
+            value = float(value)
+        except:
+            raise InvalidValueError("A floating point value is required")
+        if value > self._max or value < self._min:
+            raise InvalidValueError("Value %s not in range %s-%s" % (
+                    self.format(value),
+                    self.format(self._min), self.format(self._max)))
+        
+        # FIXME: honor resolution
+
+        RadioSettingValue.set_value(self, value)
+
+    def get_min(self):
+        """Returns the minimum allowed value"""
+        return self._min
+
+    def get_max(self):
+        """Returns the maximum allowed value"""
+
 class RadioSettingValueBoolean(RadioSettingValue):
     """A boolean setting"""
     def __init__(self, current):
@@ -147,7 +200,7 @@ class RadioSettingGroup(object):
     def _validate(self, element):
         # RadioSettingGroup can only contain RadioSettingGroup objects
         if not isinstance(element, RadioSettingGroup):
-            raise InternalError("Incorrect type")
+            raise InternalError("Incorrect type %s" % type(element))
 
     def __init__(self, name, shortname, *elements):
         self._name = name           # Setting identifier
@@ -158,7 +211,6 @@ class RadioSettingGroup(object):
         
         for element in elements:
             self._validate(element)
-            print "Appending element to %s" % self._name
             self.append(element)
 
     def get_name(self):
@@ -231,6 +283,19 @@ class RadioSettingGroup(object):
 
 class RadioSetting(RadioSettingGroup):
     """A single setting, which could be an array of items like a group"""
+    def __init__(self, *args):
+        super(RadioSetting, self).__init__(*args)
+        self._apply_callback = None
+
+    def set_apply_callback(self, callback, *args):
+        self._apply_callback = lambda: callback(self, *args)
+
+    def has_apply_callback(self):
+        return self._apply_callback is not None
+
+    def run_apply_callback(self):
+        return self._apply_callback()
+
     def _validate(self, value):
         # RadioSetting can only contain RadioSettingValue objects
         if not isinstance(value, RadioSettingValue):
diff --git a/chirp/th_uv3r.py b/chirp/th_uv3r.py
index f06ba45..5e3e2c0 100644
--- a/chirp/th_uv3r.py
+++ b/chirp/th_uv3r.py
@@ -90,7 +90,7 @@ class TYTUV3RRadio(chirp_common.CloneModeRadio):
         rf.valid_modes = ["FM", "NFM"]
         rf.valid_name_length = 6
         rf.valid_characters = THUV3R_CHARSET
-        rf.valid_bands = [(136000000, 470000000)]
+        rf.valid_bands = [(136000000, 520000000)]
         rf.valid_tuning_steps = [5.0, 6.25, 10.0, 12.5, 25.0, 37.50,
                                  50.0, 100.0]
         rf.valid_skips = ["", "S"]
diff --git a/chirp/th_uvf8d.py b/chirp/th_uvf8d.py
new file mode 100644
index 0000000..74e73ce
--- /dev/null
+++ b/chirp/th_uvf8d.py
@@ -0,0 +1,623 @@
+# 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
+# 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 TH-UVF8D radio management module"""
+
+# TODO: support FM Radio memories
+# TODO: support bank B (another 128 memories)
+# TODO: [setting] Battery Save
+# TODO: [setting] Tail Eliminate
+# TODO: [setting] Tail Mode
+
+
+import struct
+
+from chirp import chirp_common, bitwise, errors, directory, memmap, util
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettingValueString
+
+
+def uvf8d_identify(radio):
+    """Do identify handshake with TYT TH-UVF8D"""
+    try:
+        radio.pipe.write("\x02PROGRAM")
+        ack = radio.pipe.read(2)
+        if ack != "PG":
+            raise errors.RadioError("Radio did not ACK first command: %x" % ord(ack))
+    except:
+        raise errors.RadioError("Unable to communicate with the radio")
+
+    radio.pipe.write("\x02")
+    ident = radio.pipe.read(32)
+    radio.pipe.write("A")
+    r = radio.pipe.read(1)
+    if r != "A":
+        raise errors.RadioError("Ack failed")
+    return ident
+
+
+def tyt_uvf8d_download(radio):
+    data = uvf8d_identify(radio)
+    for i in range(0, 0x4000, 0x20):
+        msg = struct.pack(">cHb", "R", i, 0x20)
+        radio.pipe.write(msg)
+        block = radio.pipe.read(0x20 + 4)
+        if len(block) != (0x20 + 4):
+            raise errors.RadioError("Radio sent a short block")
+        radio.pipe.write("A")
+        ack = radio.pipe.read(1)
+        if ack != "A":
+            raise errors.RadioError("Radio NAKed block")
+        data += block[4:]
+
+        if radio.status_fn:
+            status = chirp_common.Status()
+            status.cur = i
+            status.max = 0x4000
+            status.msg = "Cloning from radio"
+            radio.status_fn(status)
+
+    radio.pipe.write("ENDR")
+
+    return memmap.MemoryMap(data)
+
+
+def tyt_uvf8d_upload(radio):
+    """Upload to TYT TH-UVF8D"""
+    data = uvf8d_identify(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])))
+
+    for i in range(0, 0x4000, 0x20):
+        addr = i + 0x20
+        msg = struct.pack(">cHb", "W", i, 0x20)
+        msg += radio._mmap[addr:(addr + 0x20)]
+
+        radio.pipe.write(msg)
+        ack = radio.pipe.read(1)
+        if ack != "A":
+            raise errors.RadioError("Radio did not ack block %i" % i)
+
+        if radio.status_fn:
+            status = chirp_common.Status()
+            status.cur = i
+            status.max = 0x4000
+            status.msg = "Cloning to radio"
+            radio.status_fn(status)
+
+    # End of clone?
+    radio.pipe.write("ENDW")
+
+    # Checksum?
+    final_data = radio.pipe.read(3)
+
+# these require working desktop software
+# TODO: DTMF features (ID, delay, speed, kill, etc.)
+
+# TODO: Display Name
+
+
+UVF8D_MEM_FORMAT = """
+struct memory {
+  lbcd rx_freq[4];
+  lbcd tx_freq[4];
+  lbcd rx_tone[2];
+  lbcd tx_tone[2];
+
+  u8 apro:4,
+     rpt_md:2,
+     unknown1:2;
+  u8 bclo:2,
+     wideband:1,
+     ishighpower:1,
+     unknown21:1,
+     vox:1,
+     pttid:2;
+  u8 unknown3:8;
+
+  u8 unknown4:6,
+     duplex:2;
+
+  lbcd offset[4];
+
+  char unknown5[4];
+
+  char name[7];
+
+  char unknown6[1];
+};
+
+struct fm_broadcast_memory {
+  lbcd freq[3];
+  u8 unknown;
+};
+
+struct enable_flags {
+  bit flags[8];
+};
+
+#seekto 0x0020;
+struct memory channels[128];
+
+#seekto 0x2020;
+struct memory vfo1;
+struct memory vfo2;
+
+#seekto 0x2060;
+struct {
+  u8 unknown2060:4,
+     tot:4;
+  u8 unknown2061;
+  u8 squelch;
+  u8 unknown2063:4,
+     vox_level:4;
+  u8 tuning_step;
+  char unknown12;
+  u8 lamp_t;
+  char unknown11;
+  u8 unknown2068;
+  u8 ani:1,
+     scan_mode:2,
+     unknown2069:2,
+     beep:1,
+     tx_sel:1,
+     roger:1;
+  u8 light:2,
+     led:2,
+     unknown206a:1,
+     autolk:1,
+     unknown206ax:2;
+  u8 unknown206b:1,
+     b_display:2,
+     a_display:2,
+     ab_switch:1,
+     dwait:1,
+     mode:1;
+  u8 dw:1,
+     unknown206c:6,
+     voice:1;
+  u8 unknown206d:2,
+     rxsave:2,
+     opnmsg:2,
+     lock_mode:2;
+  u8 a_work_area:1,
+     b_work_area:1,
+     unknown206ex:6;
+  u8 a_channel;
+  u8 b_channel;
+  u8 pad3[15];
+  char ponmsg[7];
+} settings;
+
+#seekto 0x2E60;
+struct enable_flags enable[16];
+struct enable_flags skip[16];
+
+#seekto 0x2FA0;
+struct fm_broadcast_memory fm_current;
+
+#seekto 0x2FA8;
+struct fm_broadcast_memory fm_memories[20];
+"""
+
+THUVF8D_DUPLEX = ["", "-", "+"]
+THUVF8D_CHARSET = "".join([chr(ord("0") + x) for x in range(0, 10)] +
+                          [" -*+"] +
+                          [chr(ord("A") + x) for x in range(0, 26)] +
+                          ["_/"])
+TXSEL_LIST = ["EDIT", "BUSY"]
+LED_LIST = ["Off", "Auto", "On"]
+MODE_LIST = ["Memory", "VFO"]
+AB_LIST = ["A", "B"]
+DISPLAY_LIST = ["Channel", "Frequency", "Name"]
+LIGHT_LIST = ["Purple", "Orange", "Blue"]
+RPTMD_LIST = ["Off", "Reverse", "Talkaround"]
+VOX_LIST = ["1", "2", "3", "4", "5", "6", "7", "8"]
+WIDEBAND_LIST = ["Narrow", "Wide"]
+TOT_LIST = ["Off", "30s", "60s", "90s", "120s", "150s", "180s", "210s",
+            "240s", "270s"]
+SCAN_MODE_LIST = ["Time", "Carry", "Seek"]
+OPNMSG_LIST = ["Off", "DC (Battery)", "Message"]
+
+POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5),
+                chirp_common.PowerLevel("Low", watts=0.5),
+                ]
+
+PTTID_LIST = ["Off", "BOT", "EOT", "Both"]
+BCLO_LIST = ["Off", "Wave", "Call"]
+APRO_LIST = ["Off", "Compander", "Scramble 1", "Scramble 2", "Scramble 3",
+             "Scramble 4", "Scramble 5", "Scramble 6", "Scramble 7",
+             "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"]
+
+SPECIALS = {
+    "VFO1": -2,
+    "VFO2": -1}
+
+
+ at directory.register
+class TYTUVF8DRadio(chirp_common.CloneModeRadio):
+    VENDOR = "TYT"
+    MODEL = "TH-UVF8D"
+    BAUD_RATE = 9600
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.memory_bounds = (1, 128)
+        rf.has_bank = False
+        rf.has_ctone = True
+        rf.has_tuning_step = False
+        rf.has_cross = 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
+        rf.valid_duplexes = THUVF8D_DUPLEX
+        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
+        rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "-"
+        rf.valid_bands = [(136000000, 174000000),
+                          (400000000, 520000000)]
+        rf.valid_skips = ["", "S"]
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_modes = ["FM", "NFM"]
+        rf.valid_special_chans = SPECIALS.keys()
+        rf.valid_name_length = 7
+        return rf
+
+    def sync_in(self):
+        self._mmap = tyt_uvf8d_download(self)
+        self.process_mmap()
+
+    def sync_out(self):
+        tyt_uvf8d_upload(self)
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return filedata.startswith("TYT-F10\x00")
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(UVF8D_MEM_FORMAT, self._mmap)
+
+    def _decode_tone(self, toneval):
+        pol = "N"
+        rawval = (toneval[1].get_bits(0xFF) << 8) | toneval[0].get_bits(0xFF)
+
+        if toneval[0].get_bits(0xFF) == 0xFF:
+            mode = ""
+            val = 0
+        elif toneval[1].get_bits(0xC0) == 0xC0:
+            mode = "DTCS"
+            val = int("%x" % (rawval & 0x3FFF))
+            pol = "R"
+        elif toneval[1].get_bits(0x80):
+            mode = "DTCS"
+            val = int("%x" % (rawval & 0x3FFF))
+        else:
+            mode = "Tone"
+            val = int(toneval) / 10.0
+
+        return mode, val, pol
+
+    def _encode_tone(self, _toneval, mode, val, pol):
+        toneval = 0
+        if mode == "Tone":
+            toneval = int("%i" % (val * 10), 16)
+        elif mode == "DTCS":
+            toneval = int("%i" % val, 16)
+            toneval |= 0x8000
+            if pol == "R":
+                toneval |= 0x4000
+        else:
+            toneval = 0xFFFF
+
+        _toneval[0].set_raw(toneval & 0xFF)
+        _toneval[1].set_raw((toneval >> 8) & 0xFF)
+
+    def get_raw_memory(self, number):
+        return repr(self._memobj.channels[number - 1])
+
+    def _get_memobjs(self, number):
+        if isinstance(number, str):
+            return (getattr(self._memobj, number.lower()), None)
+        elif number < 0:
+            for k, v in SPECIALS.items():
+                if number == v:
+                    return (getattr(self._memobj, k.lower()), None)
+        else:
+            return (self._memobj.channels[number - 1],
+                    None)
+
+    def get_memory(self, number):
+        _mem, _name = self._get_memobjs(number)
+
+        mem = chirp_common.Memory()
+
+        if isinstance(number, str):
+            mem.number = SPECIALS[number]
+            mem.extd_number = number
+        else:
+            mem.number = number
+
+        if _mem.get_raw().startswith("\xFF\xFF\xFF\xFF"):
+            mem.empty = True
+            return mem
+
+        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)]
+        else:
+            enabled = True
+            dont_skip = True
+
+        if not enabled:
+          mem.empty = True
+          return mem
+
+        mem.freq = int(_mem.rx_freq) * 10
+
+        mem.duplex = THUVF8D_DUPLEX[_mem.duplex]
+        mem.offset = int(_mem.offset) * 10
+
+        txmode, txval, txpol = self._decode_tone(_mem.tx_tone)
+        rxmode, rxval, rxpol = self._decode_tone(_mem.rx_tone)
+
+        chirp_common.split_tone_decode(mem,
+                                      (txmode, txval, txpol),
+                                      (rxmode, rxval, rxpol))
+
+        mem.name = str(_mem.name).rstrip('\xFF ')
+
+        mem.skip = dont_skip and "" or "S"
+
+        mem.mode = _mem.wideband and "FM" or "NFM"
+        mem.power = POWER_LEVELS[1 - _mem.ishighpower]
+
+        mem.extra = RadioSettingGroup("extra", "Extra Settings")
+
+        rs = RadioSetting("pttid", "PTT ID",
+                          RadioSettingValueList(PTTID_LIST,
+                                                PTTID_LIST[_mem.pttid]))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("vox", "VOX",
+                          RadioSettingValueBoolean(_mem.vox))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("bclo", "Busy Channel Lockout",
+                          RadioSettingValueList(BCLO_LIST,
+                                                BCLO_LIST[_mem.bclo]))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("apro", "APRO",
+                          RadioSettingValueList(APRO_LIST,
+                                                APRO_LIST[_mem.apro]))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("rpt_md", "Repeater Mode",
+                          RadioSettingValueList(RPTMD_LIST,
+                                                RPTMD_LIST[_mem.rpt_md]))
+        mem.extra.append(rs)
+
+        return mem
+
+    def set_memory(self, mem):
+        _mem, _name = self._get_memobjs(mem.number)
+
+        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
+            return
+        else:
+            self._memobj.enable[(mem.number - 1) / 8].flags[7 - ((mem.number - 1) % 8)] = True
+
+        if _mem.get_raw() == ("\xFF" * 32):
+            print "Initializing empty memory"
+            _mem.set_raw("\x00" * 32)
+
+        _mem.rx_freq = mem.freq / 10
+        if 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.duplex = THUVF8D_DUPLEX.index(mem.duplex)
+        _mem.offset = mem.offset / 10
+
+        (txmode, txval, txpol), (rxmode, rxval, rxpol) = \
+            chirp_common.split_tone_encode(mem)
+
+        self._encode_tone(_mem.tx_tone, txmode, txval, txpol)
+        self._encode_tone(_mem.rx_tone, rxmode, rxval, rxpol)
+
+        _mem.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 == "")
+        _mem.wideband = mem.mode == "FM"
+        _mem.ishighpower = mem.power == POWER_LEVELS[0]
+
+        for element in mem.extra:
+            setattr(_mem, element.get_name(), element.value)
+
+    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)))
+
+        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)))
+
+        return group
+
+    def set_settings(self, settings):
+        _settings = self._memobj.settings
+
+        for element in settings:
+            if element.get_name() == 'rxsave':
+                if bool(element.value.get_value()):
+                    _settings.rxsave = 3
+                else:
+                    _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
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+            setattr(_settings, element.get_name(), element.value)
diff --git a/chirp/thuv1f.py b/chirp/thuv1f.py
index fb3d428..5f99784 100644
--- a/chirp/thuv1f.py
+++ b/chirp/thuv1f.py
@@ -99,7 +99,7 @@ struct mem {
   lbcd rx_tone[2];
   lbcd tx_tone[2];
   u8 unknown1:1,
-     pttid:2, 
+     pttid:2,
      unknown2:2,
      ishighpower:1,
      unknown3:2;
@@ -199,7 +199,7 @@ class TYTTHUVF1Radio(chirp_common.CloneModeRadio):
         rf.valid_cross_modes = ["Tone->Tone", "DTCS->DTCS",
                                 "Tone->DTCS", "DTCS->Tone",
                                 "->Tone", "->DTCS", "DTCS->"]
-                                
+
         return rf
 
     def sync_in(self):
@@ -221,8 +221,16 @@ class TYTTHUVF1Radio(chirp_common.CloneModeRadio):
 
     @classmethod
     def match_model(cls, filedata, filename):
-        return filedata.startswith("\x13\x60\x17\x40\x40\x00\x48\x00" +
-                                   "\x35\x00\x39\x00\x47\x00\x52\x00")
+        # TYT TH-UVF1 original
+        if filedata.startswith("\x13\x60\x17\x40\x40\x00\x48\x00" +
+                               "\x35\x00\x39\x00\x47\x00\x52\x00"):
+            return True
+        # TYT TH-UVF1 V2
+        elif filedata.startswith("\x14\x40\x14\x80\x43\x00\x45\x00" +
+                                 "\x13\x60\x17\x40\x40\x00\x47\x00"):
+            return True
+        else:
+            return False
 
     def process_mmap(self):
         self._memobj = bitwise.parse(THUV1F_MEM_FORMAT, self._mmap)
@@ -230,7 +238,7 @@ class TYTTHUVF1Radio(chirp_common.CloneModeRadio):
     def _decode_tone(self, toneval):
         pol = "N"
         rawval = (toneval[1].get_bits(0xFF) << 8) | toneval[0].get_bits(0xFF)
-                              
+
         if toneval[0].get_bits(0xFF) == 0xFF:
             mode = ""
             val = 0
@@ -452,9 +460,9 @@ class TYTTHUVF1Radio(chirp_common.CloneModeRadio):
 
         group.append(
             RadioSetting("ponmsg", "Power-On Message",
-                         RadioSettingValueString(0, 7,
+                         RadioSettingValueString(0, 6,
                                                  _filter(_settings.ponmsg))))
-        
+
         return group
 
     def set_settings(self, settings):
diff --git a/chirp/tk8102.py b/chirp/tk8102.py
new file mode 100644
index 0000000..85d1dcd
--- /dev/null
+++ b/chirp/tk8102.py
@@ -0,0 +1,422 @@
+# Copyright 2013 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 struct
+import os
+
+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
+
+MEM_FORMAT = """
+#seekto 0x0030;
+struct {
+  lbcd rx_freq[4];
+  lbcd tx_freq[4];
+  ul16 rx_tone;
+  ul16 tx_tone;
+  u8 signaling:2,
+     unknown1:3,
+     bcl:1,
+     wide:1,
+     beatshift:1;
+  u8 pttid:2,
+     highpower:1,
+     scan:1
+     unknown2:4;
+  u8 unknown3[2];
+} memory[8];
+
+#seekto 0x0310;
+struct {
+  char line1[32];
+  char line2[32];
+} messages;
+
+"""
+
+POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5),
+                chirp_common.PowerLevel("High", watts=50)]
+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))
+    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)
+        if len(data) != length:
+            raise errors.RadioError("Radio sent %i bytes (expected %i)" % (
+                    len(data), length))
+    else:
+        data = ""
+    radio.pipe.write("\x06")
+    return addr, data
+
+def do_ident(radio):
+    send(radio, "PROGRAM")
+    ack = radio.pipe.read(1)
+    if ack != "\x06":
+        raise errors.RadioError("Radio refused program mode")
+    radio.pipe.write("\x02")
+    ident = radio.pipe.read(8)
+    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)
+    radio.pipe.write("\x06")
+    ack = radio.pipe.read(1)
+
+def do_download(radio):
+    radio.pipe.setParity("E")
+    radio.pipe.setTimeout(1)
+    do_ident(radio)
+
+    data = ""
+    for addr in range(0, 0x0400, 8):
+        send(radio, make_frame("R", addr, 8))
+        _addr, _data = recv(radio)
+        if _addr != addr:
+            raise errors.RadioError("Radio sent unexpected address")
+        data += _data
+        radio.pipe.write("\x06")
+        ack = radio.pipe.read(1)
+        if ack != "\x06":
+            raise errors.RadioError("Radio refused block at %04x" % addr)
+
+        status = chirp_common.Status()
+        status.cur = addr
+        status.max = 0x0400
+        status.msg = "Cloning to radio"
+        radio.status_fn(status)
+
+    radio.pipe.write("\x45")
+
+    data = ("\x45\x58\x33\x34\x30\x32\xff\xff" + ("\xff" * 8) +
+            data)
+    return memmap.MemoryMap(data)
+
+def do_upload(radio):
+    radio.pipe.setParity("E")
+    radio.pipe.setTimeout(1)
+    do_ident(radio)
+
+    for addr in range(0, 0x0400, 8):
+        eaddr = addr + 16
+        send(radio, make_frame("W", addr, 8, radio._mmap[eaddr:eaddr + 8]))
+        ack = radio.pipe.read(1)
+        if ack != "\x06":
+            raise errors.RadioError("Radio refused block at %04x" % addr)
+        radio.pipe.write("\x06")
+
+        status = chirp_common.Status()
+        status.cur = addr
+        status.max = 0x0400
+        status.msg = "Cloning to radio"
+        radio.status_fn(status)
+
+    radio.pipe.write("\x45")
+
+class KenwoodTKx102Radio(chirp_common.CloneModeRadio):
+    """Kenwood TK-x102"""
+    VENDOR = "Kenwood"
+    MODEL = "TK-x102"
+    BAUD_RATE = 9600
+
+    _memsize = 0x410
+
+    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.has_name = False
+        rf.has_rx_dtcs = True
+        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
+        rf.valid_modes = MODES
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_skips = ["", "S"]
+        rf.valid_bands = [self._range]
+        rf.memory_bounds = (1, self._upper)
+        return rf
+
+    def sync_in(self):
+        try:
+            self._mmap = do_download(self)
+        except errors.RadioError:
+            self.pipe.write("\x45")
+            raise
+        except Exception, e:
+            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:
+            self.pipe.write("\x45")
+            raise
+        except Exception, e:
+            raise errors.RadioError("Failed to upload to radio: %s" % e)
+
+    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)
+
+    def get_memory(self, number):
+        _mem = self._memobj.memory[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
+        offset = (int(_mem.tx_freq) * 10) - mem.freq
+        if offset < 0:
+            mem.offset = abs(offset)
+            mem.duplex = "-"
+        elif offset > 0:
+            mem.offset = offset
+            mem.duplex = "+"
+        else:
+            mem.offset = 0
+
+        self._get_tone(_mem, mem)
+        mem.power = POWER_LEVELS[_mem.highpower]
+        mem.mode = MODES[_mem.wide]
+        mem.skip = not _mem.scan and "S" or ""
+
+        mem.extra = RadioSettingGroup("all", "All Settings")
+
+        bcl = RadioSetting("bcl", "Busy Channel Lockout",
+                           RadioSettingValueBoolean(bool(_mem.bcl)))
+        mem.extra.append(bcl)
+
+        beat = RadioSetting("beatshift", "Beat Shift",
+                            RadioSettingValueBoolean(bool(_mem.beatshift)))
+        mem.extra.append(beat)
+
+        pttid = RadioSetting("pttid", "PTT ID",
+                             RadioSettingValueList(PTTID,
+                                                   PTTID[_mem.pttid]))
+        mem.extra.append(pttid)
+
+        signal = RadioSetting("signaling", "Signaling",
+                              RadioSettingValueList(SIGNAL,
+                                                    SIGNAL[
+                                                      _mem.signaling & 0x01]))
+        mem.extra.append(signal)
+
+        return mem
+
+    def _set_tone(self, mem, _mem):
+        def _set_dcs(code, pol):
+            val = int("%i" % code, 8) + 0x2800
+            if pol == "R":
+                val += 0xA000
+            return val
+
+        if mem.tmode == "Cross":
+            tx_mode, rx_mode = mem.cross_mode.split("->")
+        elif mem.tmode == "Tone":
+            tx_mode = mem.tmode
+            rx_mode = None
+        else:
+            tx_mode = rx_mode = mem.tmode
+
+        if tx_mode == "DTCS":
+            _mem.tx_tone = mem.tmode != "DTCS" and \
+                _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) or \
+                _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[0])
+        elif tx_mode:
+            _mem.tx_tone = tx_mode == "Tone" and \
+                int(mem.rtone * 10) or int(mem.ctone * 10)
+        else:
+            _mem.tx_tone = 0xFFFF
+
+        if rx_mode == "DTCS":
+            _mem.rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
+        elif rx_mode:
+            _mem.rx_tone = int(mem.ctone * 10)
+        else:
+            _mem.rx_tone = 0xFFFF
+
+        if os.getenv("CHIRP_DEBUG"):
+            print "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" * 16)
+            return
+
+        _mem.unknown3[0] = 0x07
+        _mem.unknown3[1] = 0x22
+        _mem.rx_freq = mem.freq / 10
+        if 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.highpower = mem.power == POWER_LEVELS[1]
+        _mem.wide = mem.mode == "FM"
+        _mem.scan = mem.skip != "S"
+
+        for setting in mem.extra:
+            if setting.get_name == "signaling":
+                if setting.value == "DTMF":
+                    _mem.signaling = 0x03
+                else:
+                    _mem.signaling = 0x00
+            else:
+                setattr(_mem, setting.get_name(), setting.value)
+
+    def get_settings(self):
+        _mem = self._memobj
+        top = RadioSettingGroup("all", "All Settings")
+
+        def _f(val):
+            string = ""
+            for char in str(val):
+                if char == "\xFF":
+                    break
+                string += char
+            return string
+
+        line1 = RadioSetting("messages.line1", "Message Line 1",
+                             RadioSettingValueString(0, 32,
+                                                     _f(_mem.messages.line1),
+                                                     autopad=False))
+        top.append(line1)
+
+        line2 = RadioSetting("messages.line2", "Message Line 2",
+                             RadioSettingValueString(0, 32,
+                                                     _f(_mem.messages.line2),
+                                                     autopad=False))
+        top.append(line2)
+
+        return top
+
+    def set_settings(self, settings):
+        for element in settings:
+            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 = _settings
+                setting = element.get_name()
+
+            if "line" in setting:
+                value = str(element.value).ljust(32, "\xFF")
+            else:
+                value = element.value
+            setattr(obj, setting, value)
+            
+    @classmethod
+    def match_model(cls, filedata, filename):
+        model = filedata[0x03D1:0x03D5]
+        print model
+        return model == cls.MODEL.split("-")[1]
+
+
+ at directory.register
+class KenwoodTK7102Radio(KenwoodTKx102Radio):
+    MODEL = "TK-7102"
+    _range = (136000000, 174000000)
+    _upper = 4
+
+ at directory.register
+class KenwoodTK8102Radio(KenwoodTKx102Radio):
+    MODEL = "TK-8102"
+    _range = (400000000, 500000000)
+    _upper = 4
+
+ at directory.register
+class KenwoodTK7108Radio(KenwoodTKx102Radio):
+    MODEL = "TK-7108"
+    _range = (136000000, 174000000)
+    _upper = 8
+
+ at directory.register
+class KenwoodTK8108Radio(KenwoodTKx102Radio):
+    MODEL = "TK-8108"
+    _range = (400000000, 500000000)
+    _upper = 8
+
diff --git a/chirp/uv5r.py b/chirp/uv5r.py
index e7457d8..dcd3afc 100644
--- a/chirp/uv5r.py
+++ b/chirp/uv5r.py
@@ -15,13 +15,20 @@
 
 import struct
 import time
+import os
 
 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, \
+    RadioSettingValueFloat, InvalidValueError
+from textwrap import dedent
+
+if os.getenv("CHIRP_DEBUG"):
+    CHIRP_DEBUG = True
+else:
+    CHIRP_DEBUG = False
 
 MEM_FORMAT = """
 #seekto 0x0008;
@@ -30,23 +37,52 @@ struct {
   lbcd txfreq[4];
   ul16 rxtone;
   ul16 txtone;
-  u8 unused1:4,
+  u8 unused1:3,
+     isuhf:1,
      scode:4;
-  u8 unknown1[1];
-  u8 unknown2:7,
+  u8 unknown1:7,
+     txtoneicon:1;
+  u8 mailicon:3,
+     unknown2:4,
      lowpower:1;
   u8 unknown3:1,
      wide:1,
      unknown4:2,
      bcl:1,
      scan:1,
-     pttideot:1,
-     pttidbot:1;
+     pttid:2;
 } memory[128];
 
-#seekto 0x0CB2;
+#seekto 0x0B08;
+struct {
+  u8 code[5];
+  u8 unused[11];
+} pttid[15];
+
+#seekto 0x0C88;
 struct {
+  u8 code222[3];
+  u8 unused222[2];
+  u8 code333[3];
+  u8 unused333[2];
+  u8 alarmcode[3];
+  u8 unused119[2];
+  u8 unknown1;
+  u8 code555[3];
+  u8 unused555[2];
+  u8 code666[3];
+  u8 unused666[2];
+  u8 code777[3];
+  u8 unused777[2];
+  u8 unknown2;
+  u8 code60606[5];
+  u8 code70707[5];
   u8 code[5];
+  u8 unused1:6,
+     aniid:2;
+  u8 unknown[2];
+  u8 dtmfon;
+  u8 dtmfoff;
 } ani;
 
 #seekto 0x0E28;
@@ -66,13 +102,16 @@ struct {
   u8 unknown4;
   u8 dtmfst;
   u8 unknown5;
-  u8 screv;
+  u8 unknown12:6,
+     screv:2;
   u8 pttid;
   u8 pttlt;
   u8 mdfa;
   u8 mdfb;
   u8 bcl;
-  u8 autolk;
+  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;
@@ -86,10 +125,8 @@ struct {
   u8 rptrl;
   u8 ponmsg;
   u8 roger;
-} settings[2];
-
-#seekto 0x0E52;
-struct {
+  u8 rogerrx;
+  u8 tdrch;
   u8 displayab:1,
      unknown1:2,
      fmradio:1,
@@ -97,10 +134,10 @@ struct {
      unknown2:1,
      reset:1,
      menu:1;
-  u8 unknown3;
+  u8 vfomrlock;
   u8 workmode;
   u8 keylock;
-} extra;
+} settings;
 
 #seekto 0x0E7E;
 struct {
@@ -121,7 +158,8 @@ struct {
   u8 unused1:7,
      band:1;
   u8 unknown3;
-  u8 unused2:4,
+  u8 unused2:2,
+     sftd:2,
      scode:4;
   u8 unknown4;
   u8 unused3:1
@@ -143,7 +181,8 @@ struct {
   u8 unused1:7,
      band:1;
   u8 unknown3;
-  u8 unused2:4,
+  u8 unused2:2,
+     sftd:2,
      scode:4;
   u8 unknown4;
   u8 unused3:1
@@ -154,11 +193,13 @@ struct {
      unknown5:6;
 } vfob;
 
-#seekto 0x1000;
+#seekto 0x0F56;
+u16 fm_presets;
+
+#seekto 0x1008;
 struct {
-  u8 unknown1[8];
   char name[7];
-  u8 unknown2;
+  u8 unknown2[9];
 } names[128];
 
 #seekto 0x1818;
@@ -167,12 +208,18 @@ struct {
   char line2[7];
 } sixpoweron_msg;
 
-#seekto 0x1828;
+#seekto 0x%04X;
 struct {
   char line1[7];
   char line2[7];
 } poweron_msg;
 
+#seekto 0x1838;
+struct {
+  char line1[7];
+  char line2[7];
+} firmware_msg;
+
 struct limit {
   u8 enable;
   bbcd lower[2];
@@ -199,43 +246,75 @@ struct {
 
 # 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_LIST = BASETYPE_UV5R + BASETYPE_F11 + BASETYPE_UV82 + \
+                BASETYPE_BJ55 + BASETYPE_UV6
+
+AB_LIST = ["A", "B"]
+ALMOD_LIST = ["Site", "Tone", "Code", "_unknown_"]
+BANDWIDTH_LIST = ["Wide", "Narrow"]
+COLOR_LIST = ["Off", "Blue", "Orange", "Purple"]
+DTMFSPEED_LIST = ["%s ms" % x for x in range(50, 2010, 10)]
+DTMFST_LIST = ["OFF", "DT-ST", "ANI-ST", "DT+ANI"]
+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)]
+RESUME_LIST = ["TO", "CO", "SE"]
+ROGERRX_LIST = ["Off"] + AB_LIST
+RPSTE_LIST = ["OFF"] + ["%s" % x for x in range(1, 11)]
+SAVE_LIST = ["Off", "1:1", "1:2", "1:3", "1:4"]
+SCODE_LIST = ["%s" % x for x in range(1, 16)]
+SHIFTD_LIST = ["Off", "+", "-"]
+STEDELAY_LIST = ["OFF"] + ["%s ms" % x for x in range(100, 1100, 100)]
 STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0]
 STEP_LIST = [str(x) for x in STEPS]
 STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0]
 STEP291_LIST = [str(x) for x in STEPS]
+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"]
 VOICE_LIST = ["Off", "English", "Chinese"]
-DTMFST_LIST = ["OFF", "DT-ST", "ANI-ST", "DT+ANI"]
-RESUME_LIST = ["TO", "CO", "SE"]
-MODE_LIST = ["Channel", "Name", "Frequency"]
-COLOR_LIST = ["Off", "Blue", "Orange", "Purple"]
-ALMOD_LIST = ["Site", "Tone", "Code"]
-TDRAB_LIST = ["Off", "A", "B"]
-PONMSG_LIST = ["Full", "Message"]
-RPSTE_LIST = ["%s" % x for x in range(1, 11, 1)]
-RPSTE_LIST.insert(0, "OFF")
-STEDELAY_LIST = ["%s ms" % x for x in range(100, 1100, 100)]
-STEDELAY_LIST.insert(0, "OFF")
-SCODE_LIST = ["%s" % x for x in range(1, 16)]
+VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 11)]
+WORKMODE_LIST = ["Frequency", "Channel"]
 
 SETTING_LISTS = {
-    "step" : STEP_LIST,
-    "step291" : STEP291_LIST,
-    "timeout" : TIMEOUT_LIST,
-    "voice" : VOICE_LIST,
+    "almod" : ALMOD_LIST,
+    "aniid" : PTTID_LIST,
+    "displayab" : AB_LIST,
     "dtmfst" : DTMFST_LIST,
-    "screv" : RESUME_LIST,
+    "dtmfspeed" : DTMFSPEED_LIST,
     "mdfa" : MODE_LIST,
     "mdfb" : MODE_LIST,
-    "wtled" : COLOR_LIST,
-    "rxled" : COLOR_LIST,
-    "txled" : COLOR_LIST,
-    "almod" : ALMOD_LIST,
-    "tdrab" : TDRAB_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,
-    "scode" : SCODE_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
 }
 
 def _do_status(radio, block):
@@ -258,23 +337,39 @@ def validate_291(ident):
         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_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"
+
+def _upper_band_from_data(data):
+    return data[0x03:0x04]
 
-IDENTS = [UV5R_MODEL_ORIG,
-          UV5R_MODEL_291,
-          ]
+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):
-    return radio.get_mmap()[0x1838:0x1848]
+    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)
+    return version
 
 def _do_ident(radio, magic):
     serial = radio.pipe
     serial.setTimeout(1)
 
     print "Sending Magic: %s" % util.hexprint(magic)
-    serial.write(magic)
+    for byte in magic:
+        serial.write(byte)
+        time.sleep(0.01)
     ack = serial.read(1)
-    
+
     if ack != "\x06":
         if ack:
             print repr(ack)
@@ -312,23 +407,28 @@ def _read_block(radio, start, size):
     elif len(chunk) != size:
         print "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)
-    
+
     return chunk
 
 def _get_radio_firmware_version(radio):
-    block1 = _read_block(radio, 0x1EC0, 0x40)
-    block2 = _read_block(radio, 0x1F00, 0x40)
-    block = block1 + block2
-    return block[48:64]
+    if radio.MODEL == "BJ-UV55":
+        block = _read_block(radio, 0x1FF0, 0x40)
+        version = block[0:6]
+    else:
+        block1 = _read_block(radio, 0x1EC0, 0x40)
+        block2 = _read_block(radio, 0x1F00, 0x40)
+        block = block1 + block2
+        version = block[48:64]
+    return version
 
 def _ident_radio(radio):
-    for magic in IDENTS:
+    for magic in radio._idents:
         error = None
         try:
             data = _do_ident(radio, magic)
@@ -345,14 +445,19 @@ def _do_download(radio):
     data = _ident_radio(radio)
 
     # Main block
+    if CHIRP_DEBUG:
+        print "downloading main block..."
     for i in range(0, 0x1800, 0x40):
         data += _read_block(radio, i, 0x40)
         _do_status(radio, i)
-
+    if CHIRP_DEBUG:
+        print "done."
+        print "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."
     return memmap.MemoryMap(data)
 
 def _send_block(radio, addr, data):
@@ -362,22 +467,28 @@ def _send_block(radio, addr, data):
     ack = radio.pipe.read(1)
     if ack != "\x06":
         raise errors.RadioError("Radio refused to accept block 0x%04x" % addr)
-    
+
 def _do_upload(radio):
-    _ident_radio(radio)
+    ident = _ident_radio(radio)
+    radio_upper_band = ident[3:4]
+    image_upper_band = _upper_band_from_image(radio)
+
+    if image_upper_band == vhf_220_radio or radio_upper_band == vhf_220_radio:
+        if image_upper_band != radio_upper_band:
+            raise errors.RadioError("Image not supported by radio")
 
     image_version = _firmware_version_from_image(radio)
     radio_version = _get_radio_firmware_version(radio)
     print "Image is %s" % repr(image_version)
     print "Radio is %s" % repr(radio_version)
 
-    if "BFB" not in radio_version:
+    if not any(type in radio_version for type in BASETYPE_LIST):
         raise errors.RadioError("Unsupported firmware version: `%s'" %
                                 radio_version)
 
     # Main block
     for i in range(0x08, 0x1808, 0x10):
-        _send_block(radio, i - 0x08, radio.get_mmap()[i:i+0x10])
+        _send_block(radio, i - 0x08, radio.get_mmap()[i:i + 0x10])
         _do_status(radio, i)
 
     if len(radio.get_mmap().get_packed()) == 0x1808:
@@ -385,15 +496,16 @@ def _do_upload(radio):
         return # Old image, no aux block
 
     if image_version != radio_version:
-        raise errors.RadioError("Upload finished, but the 'Other Settings' "
-                                "could not be sent because the firmware "
-                                "version of the image does not match that "
-                                "of the radio")
+        msg = ("Upload finished, but the 'Other Settings' "
+               "could not be sent because the firmware "
+               "version of the image (%s) does not match "
+               "that of the radio (%s).")
+        raise errors.RadioError(msg % (image_version, radio_version))
 
     # Auxiliary block at radio address 0x1EC0, our offset 0x1808
     for i in range(0x1EC0, 0x2000, 0x10):
         addr = 0x1808 + (i - 0x1EC0)
-        _send_block(radio, i, radio.get_mmap()[addr:addr+0x10])
+        _send_block(radio, i, radio.get_mmap()[addr:addr + 0x10])
 
 UV5R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=4.00),
                      chirp_common.PowerLevel("Low",  watts=1.00)]
@@ -413,15 +525,40 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
     BAUD_RATE = 9600
 
     _memsize = 0x1808
+    _basetype = BASETYPE_UV5R
+    _idents = [UV5R_MODEL_291,
+               UV5R_MODEL_ORIG
+               ]
+    _mem_params = ( 0x1828 # poweron_msg offset
+                    )
+    # offset of fw version in image file
+    _fw_ver_file_start = 0x1838
+    _fw_ver_file_stop = 0x1848
 
     @classmethod
-    def get_experimental_warning(cls):
-        return ('Due to the fact that the manufacturer continues to '
+    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.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.
+            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.
+            5. Ensure that the radio is tuned to channel with no activity.
+            6. Click OK to upload image to device."""))
+        return rp
 
     def get_features(self):
         rf = chirp_common.RadioFeatures()
@@ -440,16 +577,37 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
         rf.valid_power_levels = UV5R_POWER_LEVELS
         rf.valid_duplexes = ["", "-", "+", "split", "off"]
         rf.valid_modes = ["FM", "NFM"]
-        rf.valid_bands = [(136000000, 174000000), (400000000, 520000000)]
+
+        normal_bands = [(136000000, 174000000), (400000000, 520000000)]
+        rax_bands = [(136000000, 174000000), (220000000, 260000000)]
+
+        if self._mmap is None:
+            rf.valid_bands = [normal_bands[0], rax_bands[1], normal_bands[1]]
+        elif not self._is_orig() and self._my_upper_band() == vhf_220_radio:
+            rf.valid_bands = rax_bands
+        else:
+            rf.valid_bands = normal_bands
         rf.memory_bounds = (0, 127)
         return rf
 
     @classmethod
     def match_model(cls, filedata, filename):
-        return len(filedata) in [0x1808, 0x1948]
+        match_size = False
+        match_model = False
+        if len(filedata) in [0x1808, 0x1948]:
+            match_size = True
+        fwdata = _firmware_version_from_data(filedata,
+                                             cls._fw_ver_file_start,
+                                             cls._fw_ver_file_stop)
+        if any(type in fwdata for type in cls._basetype):
+            match_model = True
+        if match_size and match_model:
+            return True
+        else:
+            return False
 
     def process_mmap(self):
-        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
 
     def sync_in(self):
         try:
@@ -477,9 +635,15 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
             raw_tx += _mem.txfreq[i].get_raw()
         return raw_tx == "\xFF\xFF\xFF\xFF"
 
+    def _get_mem(self, number):
+        return self._memobj.memory[number]
+
+    def _get_nam(self, number):
+        return self._memobj.names[number]
+
     def get_memory(self, number):
-        _mem = self._memobj.memory[number]
-        _nam = self._memobj.names[number]
+        _mem = self._get_mem(number)
+        _nam = self._get_nam(number)
 
         mem = chirp_common.Memory()
         mem.number = number
@@ -567,11 +731,27 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
                           RadioSettingValueBoolean(_mem.bcl))
         mem.extra.append(rs)
 
+        rs = RadioSetting("pttid", "PTT ID",
+                          RadioSettingValueList(PTTID_LIST,
+                                                PTTID_LIST[_mem.pttid]))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("scode", "PTT ID Code",
+                          RadioSettingValueList(PTTIDCODE_LIST,
+                                                PTTIDCODE_LIST[_mem.scode]))
+        mem.extra.append(rs)
+
         return mem
 
+    def _set_mem(self, number):
+        return self._memobj.memory[number]
+
+    def _set_nam(self, number):
+        return self._memobj.names[number]
+
     def set_memory(self, mem):
-        _mem = self._memobj.memory[mem.number]
-        _nam = self._memobj.names[mem.number]
+        _mem = self._get_mem(mem.number)
+        _nam = self._get_nam(mem.number)
 
         if mem.empty:
             _mem.set_raw("\xff" * 16)
@@ -593,7 +773,8 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
         else:
             _mem.txfreq = mem.freq / 10
 
-        for i in range(0, 7):
+        _namelength = self.get_features().valid_name_length
+        for i in range(_namelength):
             try:
                 _nam.name[i] = mem.name[i]
             except IndexError:
@@ -642,11 +823,14 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
 
     def _is_orig(self):
         version_tag = _firmware_version_from_image(self)
+        if CHIRP_DEBUG:
+            print "@_is_orig, version_tag:", util.hexprint(version_tag)
         try:
             if 'BFB' in version_tag:
                 idx = version_tag.index("BFB") + 3
-                version = int(version_tag[idx:idx+3])
+                version = int(version_tag[idx:idx + 3])
                 return version < 291
+            return False
         except:
             pass
         raise errors.RadioError("Unable to parse version string %s" %
@@ -656,11 +840,38 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
         version_tag = _firmware_version_from_image(self)
         if 'BFB' in version_tag:
             idx = version_tag.index("BFB") + 3
-            return int(version_tag[idx:idx+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")
 
+    def _my_upper_band(self):
+        band_tag = _upper_band_from_image(self)
+        return band_tag
+
     def _get_settings(self):
-        _settings = self._memobj.settings[0]
+        _ani = self._memobj.ani
+        _settings = self._memobj.settings
+        _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)
@@ -669,31 +880,54 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
                           RadioSettingValueInteger(0, 9, _settings.squelch))
         basic.append(rs)
 
-        rs = RadioSetting("dtmfst", "DTMF Sidetone",
-                          RadioSettingValueList(DTMFST_LIST,
-                                                DTMFST_LIST[_settings.dtmfst]))
-        advanced.append(rs)
-
         rs = RadioSetting("save", "Battery Saver",
-                          RadioSettingValueInteger(0, 4, _settings.save))
+                          RadioSettingValueList(SAVE_LIST,
+                                                SAVE_LIST[_settings.save]))
         basic.append(rs)
 
         rs = RadioSetting("vox", "VOX Sensitivity",
-                          RadioSettingValueInteger(0, 10, _settings.vox))
+                          RadioSettingValueList(VOX_LIST,
+                                                VOX_LIST[_settings.vox]))
         advanced.append(rs)
 
-        rs = RadioSetting("abr", "Backlight Timeout",
-                          RadioSettingValueInteger(0, 24, _settings.abr))
-        basic.append(rs)
+        if self.MODEL == "UV-6":
+            # 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.
+            rs = RadioSetting("autolk", "Vox",
+                              RadioSettingValueBoolean(_settings.autolk))
+            advanced.append(rs)
+
+        if self.MODEL != "UV-6":
+            rs = RadioSetting("abr", "Backlight Timeout",
+                              RadioSettingValueInteger(0, 24, _settings.abr))
+            basic.append(rs)
 
         rs = RadioSetting("tdr", "Dual Watch",
                           RadioSettingValueBoolean(_settings.tdr))
         advanced.append(rs)
 
-        rs = RadioSetting("tdrab", "Dual Watch Priority",
-                          RadioSettingValueList(TDRAB_LIST,
-                                                TDRAB_LIST[_settings.tdrab]))
-        advanced.append(rs)
+        if self.MODEL == "UV-6":
+            rs = RadioSetting("tdrch", "Dual Watch Channel",
+                              RadioSettingValueList(TDRCH_LIST,
+                                                    TDRCH_LIST[
+                                                    _settings.tdrch]))
+            advanced.append(rs)
+
+            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
+                              RadioSettingValueBoolean(_settings.tdrab))
+            advanced.append(rs)
+        else:
+            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
+                              RadioSettingValueList(TDRAB_LIST,
+                                                    TDRAB_LIST[
+                                                    _settings.tdrab]))
+            advanced.append(rs)
+
+        if self.MODEL == "UV-6":
+            rs = RadioSetting("alarm", "Alarm Sound",
+                              RadioSettingValueBoolean(_settings.alarm))
+            advanced.append(rs)
 
         rs = RadioSetting("almod", "Alarm Mode",
                           RadioSettingValueList(ALMOD_LIST,
@@ -706,17 +940,19 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
 
         rs = RadioSetting("timeout", "Timeout Timer",
                           RadioSettingValueList(TIMEOUT_LIST,
-                                                TIMEOUT_LIST[_settings.timeout]))
+                                                TIMEOUT_LIST[
+                                                _settings.timeout]))
         basic.append(rs)
 
-        if self._my_version() >= 251:
+        if self._is_orig() and self._my_version() < 251:
             rs = RadioSetting("voice", "Voice",
-                              RadioSettingValueList(VOICE_LIST,
-                                                    VOICE_LIST[_settings.voice]))
+                              RadioSettingValueBoolean(_settings.voice))
             advanced.append(rs)
         else:
             rs = RadioSetting("voice", "Voice",
-                              RadioSettingValueBoolean(_settings.voice))
+                              RadioSettingValueList(VOICE_LIST,
+                                                    VOICE_LIST[
+                                                    _settings.voice]))
             advanced.append(rs)
 
         rs = RadioSetting("screv", "Scan Resume",
@@ -724,59 +960,62 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
                                                 RESUME_LIST[_settings.screv]))
         advanced.append(rs)
 
-        rs = RadioSetting("mdfa", "Display Mode (A)",
-                          RadioSettingValueList(MODE_LIST,
-                                                MODE_LIST[_settings.mdfa]))
-        basic.append(rs)
+        if self.MODEL != "UV-6":
+            rs = RadioSetting("mdfa", "Display Mode (A)",
+                              RadioSettingValueList(MODE_LIST,
+                                                    MODE_LIST[_settings.mdfa]))
+            basic.append(rs)
 
-        rs = RadioSetting("mdfb", "Display Mode (B)",
-                          RadioSettingValueList(MODE_LIST,
-                                                MODE_LIST[_settings.mdfb]))
-        basic.append(rs)
+            rs = RadioSetting("mdfb", "Display Mode (B)",
+                              RadioSettingValueList(MODE_LIST,
+                                                    MODE_LIST[_settings.mdfb]))
+            basic.append(rs)
 
         rs = RadioSetting("bcl", "Busy Channel Lockout",
                           RadioSettingValueBoolean(_settings.bcl))
         advanced.append(rs)
 
-        rs = RadioSetting("autolk", "Automatic Key Lock",
-                          RadioSettingValueBoolean(_settings.autolk))
-        advanced.append(rs)
+        if self.MODEL != "UV-6":
+            rs = RadioSetting("autolk", "Automatic Key Lock",
+                              RadioSettingValueBoolean(_settings.autolk))
+            advanced.append(rs)
 
-        rs = RadioSetting("extra.fmradio", "Broadcast FM Radio",
-                          RadioSettingValueBoolean(self._memobj.extra.fmradio))
+        rs = RadioSetting("fmradio", "Broadcast FM Radio",
+                          RadioSettingValueBoolean(_settings.fmradio))
         advanced.append(rs)
 
-        rs = RadioSetting("wtled", "Standby LED Color",
-                          RadioSettingValueList(COLOR_LIST,
-                                                COLOR_LIST[_settings.wtled]))
-        basic.append(rs)
-
-        rs = RadioSetting("rxled", "RX LED Color",
-                          RadioSettingValueList(COLOR_LIST,
-                                                COLOR_LIST[_settings.rxled]))
-        basic.append(rs)
-
-        rs = RadioSetting("txled", "TX LED Color",
-                          RadioSettingValueList(COLOR_LIST,
-                                                COLOR_LIST[_settings.txled]))
-        basic.append(rs)
-
-        rs = RadioSetting("roger", "Roger Beep",
-                          RadioSettingValueBoolean(_settings.roger))
-        basic.append(rs)
-
-        try:
-            _ani = self._memobj.ani.code
-            rs = RadioSetting("ani.code", "ANI Code",
-                              RadioSettingValueInteger(0, 9, _ani[0]),
-                              RadioSettingValueInteger(0, 9, _ani[1]),
-                              RadioSettingValueInteger(0, 9, _ani[2]),
-                              RadioSettingValueInteger(0, 9, _ani[3]),
-                              RadioSettingValueInteger(0, 9, _ani[4]))
-            advanced.append(rs)
-        except Exception:
-            print ("Your ANI code is not five digits, which is not currently"
-                   " supported in CHIRP.")
+        if self.MODEL != "UV-6":
+            rs = RadioSetting("wtled", "Standby LED Color",
+                              RadioSettingValueList(COLOR_LIST,
+                                                    COLOR_LIST[
+                                                    _settings.wtled]))
+            basic.append(rs)
+
+            rs = RadioSetting("rxled", "RX LED Color",
+                              RadioSettingValueList(COLOR_LIST,
+                                                    COLOR_LIST[
+                                                    _settings.rxled]))
+            basic.append(rs)
+
+            rs = RadioSetting("txled", "TX LED Color",
+                              RadioSettingValueList(COLOR_LIST,
+                                                    COLOR_LIST[
+                                                    _settings.txled]))
+            basic.append(rs)
+
+        if self.MODEL == "UV-82":
+            rs = RadioSetting("roger", "Roger Beep (TX)",
+                              RadioSettingValueBoolean(_settings.roger))
+            basic.append(rs)
+            rs = RadioSetting("rogerrx", "Roger Beep (RX)",
+                              RadioSettingValueList(ROGERRX_LIST,
+                                                    ROGERRX_LIST[
+                                                    _settings.rogerrx]))
+            basic.append(rs)
+        else:
+            rs = RadioSetting("roger", "Roger Beep",
+                              RadioSettingValueBoolean(_settings.roger))
+            basic.append(rs)
 
         rs = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)",
                           RadioSettingValueBoolean(_settings.ste))
@@ -792,13 +1031,20 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
                                                 STEDELAY_LIST[_settings.rptrl]))
         advanced.append(rs)
 
-        rs = RadioSetting("extra.reset", "RESET Menu",
-                          RadioSettingValueBoolean(self._memobj.extra.reset))
-        advanced.append(rs)
+        if self.MODEL != "UV-6":
+            rs = RadioSetting("reset", "RESET Menu",
+                              RadioSettingValueBoolean(_settings.reset))
+            advanced.append(rs)
 
-        rs = RadioSetting("extra.menu", "All Menus",
-                          RadioSettingValueBoolean(self._memobj.extra.menu))
-        advanced.append(rs)
+            rs = RadioSetting("menu", "All Menus",
+                              RadioSettingValueBoolean(_settings.menu))
+            advanced.append(rs)
+
+        if self.MODEL == "F-11":
+            # this is an F-11 only feature
+            rs = RadioSetting("vfomrlock", "VFO/MR Button",
+                              RadioSettingValueBoolean(_settings.vfomrlock))
+            advanced.append(rs)
 
         if len(self._mmap.get_packed()) == 0x1808:
             # Old image, without aux block
@@ -816,152 +1062,319 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
                     filtered += " "
             return filtered
 
-        _msg = self._memobj.sixpoweron_msg
-        rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1",
-                          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)))
+        _msg = self._memobj.firmware_msg
+        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
+        val.set_mutable(False)
+        rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
         other.append(rs)
 
-        _msg = self._memobj.poweron_msg
-        rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
-                          RadioSettingValueString(0, 7, _filter(_msg.line1)))
-        other.append(rs)
-        rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
-                          RadioSettingValueString(0, 7, _filter(_msg.line2)))
+        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
+        val.set_mutable(False)
+        rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
         other.append(rs)
 
-        rs = RadioSetting("ponmsg", "Power-On Message",
-                          RadioSettingValueList(PONMSG_LIST,
-                                                PONMSG_LIST[_settings.ponmsg]))
-        other.append(rs)
+        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)))
+            other.append(rs)
+            rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2",
+                              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)))
+            other.append(rs)
+            rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
+                              RadioSettingValueString(0, 7, _filter(
+                                                      _msg.line2)))
+            other.append(rs)
+
+            rs = RadioSetting("ponmsg", "Power-On Message",
+                              RadioSettingValueList(PONMSG_LIST,
+                                                    PONMSG_LIST[
+                                                    _settings.ponmsg]))
+            other.append(rs)
+
+            if self._is_orig():
+                limit = "limits_old"
+            else:
+                limit = "limits_new"
+
+            vhf_limit = getattr(self._memobj, limit).vhf
+            rs = RadioSetting("%s.vhf.lower" % limit, "VHF Lower Limit (MHz)",
+                              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))
+            other.append(rs)
+
+            rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled",
+                              RadioSettingValueBoolean(vhf_limit.enable))
+            other.append(rs)
+
+            uhf_limit = getattr(self._memobj, limit).uhf
+            rs = RadioSetting("%s.uhf.lower" % limit, "UHF Lower Limit (MHz)",
+                              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))
+            other.append(rs)
+            rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled",
+                              RadioSettingValueBoolean(uhf_limit.enable))
+            other.append(rs)
+
+        if self.MODEL != "UV-6":
+            workmode = RadioSettingGroup("workmode", "Work Mode Settings")
+            group.append(workmode)
+
+            rs = RadioSetting("displayab", "Display",
+                              RadioSettingValueList(AB_LIST,
+                                                    AB_LIST[
+                                                    _settings.displayab]))
+            workmode.append(rs)
 
-        if self._is_orig():
-            limit = "limits_old"
-        else:
-            limit = "limits_new"
+            rs = RadioSetting("workmode", "VFO/MR Mode",
+                              RadioSettingValueList(WORKMODE_LIST,
+                                                    WORKMODE_LIST[
+                                                    _settings.workmode]))
+            workmode.append(rs)
 
-        vhf_limit = getattr(self._memobj, limit).vhf
-        rs = RadioSetting("%s.vhf.lower" % limit, "VHF Lower Limit (MHz)",
-                          RadioSettingValueInteger(1, 1000,
-                                                   vhf_limit.lower))
-        other.append(rs)
+            rs = RadioSetting("keylock", "Keypad Lock",
+                              RadioSettingValueBoolean(_settings.keylock))
+            workmode.append(rs)
 
-        rs = RadioSetting("%s.vhf.upper" % limit, "VHF Upper Limit (MHz)",
-                          RadioSettingValueInteger(1, 1000,
-                                                   vhf_limit.upper))
-        other.append(rs)
+            rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
+                              RadioSettingValueInteger(0, 127,
+                                                       _wmchannel.mrcha))
+            workmode.append(rs)
 
-        rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled",
-                          RadioSettingValueBoolean(vhf_limit.enable))
-        other.append(rs)
+            rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
+                              RadioSettingValueInteger(0, 127,
+                                                       _wmchannel.mrchb))
+            workmode.append(rs)
 
-        uhf_limit = getattr(self._memobj, limit).uhf
-        rs = RadioSetting("%s.uhf.lower" % limit, "UHF Lower Limit (MHz)",
-                          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))
-        other.append(rs)
-        rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled",
-                          RadioSettingValueBoolean(uhf_limit.enable))
-        other.append(rs)
+            def convert_bytes_to_freq(bytes):
+               real_freq = 0
+               for byte in bytes:
+                   real_freq = (real_freq * 10) + byte
+               return chirp_common.format_freq(real_freq * 10)
+
+            def my_validate(value):
+                value = chirp_common.parse_freq(value)
+                if 17400000 <= value and value < 40000000:
+                    msg = ("Can't be between 174.00000-400.00000")
+                    raise InvalidValueError(msg)
+                return chirp_common.format_freq(value)
+
+            def apply_freq(setting, obj):
+                value = chirp_common.parse_freq(str(setting.value)) / 10
+                obj.band = value >= 40000000
+                for i in range(7, -1, -1):
+                    obj.freq[i] = value % 10
+                    value /= 10
+
+            val1a = RadioSettingValueString(0, 10,
+                                            convert_bytes_to_freq(_vfoa.freq))
+            val1a.set_validate_callback(my_validate)
+            rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a)
+            rs.set_apply_callback(apply_freq, _vfoa)
+            workmode.append(rs)
 
-        workmode = RadioSettingGroup("workmode", "Work Mode Settings")
-        group.append(workmode)
-
-        options = ["A", "B"]
-        rs = RadioSetting("extra.displayab", "Display",
-                          RadioSettingValueList(options,
-                                                options[self._memobj.extra.displayab]))
-        workmode.append(rs)
-
-        options = ["Frequency", "Channel"]
-        rs = RadioSetting("extra.workmode", "VFO/MR Mode",
-                          RadioSettingValueList(options,
-                                                options[self._memobj.extra.workmode]))
-        workmode.append(rs)
-
-        rs = RadioSetting("extra.keylock", "Keypad Lock",
-                          RadioSettingValueBoolean(self._memobj.extra.keylock))
-        workmode.append(rs)
-
-        _mrcna = self._memobj.wmchannel.mrcha
-        rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
-                          RadioSettingValueInteger(0, 127, _mrcna))
-        workmode.append(rs)
-
-        _mrcnb = self._memobj.wmchannel.mrchb
-        rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
-                          RadioSettingValueInteger(0, 127, _mrcnb))
-        workmode.append(rs)
-
-        options = ["VHF", "UHF"]
-        rs = RadioSetting("vfoa.band", "VFO A Band",
-                          RadioSettingValueList(options,
-                                                options[self._memobj.vfoa.band]))
-        workmode.append(rs)
-
-        rs = RadioSetting("vfob.band", "VFO B Band",
-                          RadioSettingValueList(options,
-                                                options[self._memobj.vfob.band]))
-        workmode.append(rs)
-
-        options = ["High", "Low"]
-        rs = RadioSetting("vfoa.txpower", "VFO A Power",
-                          RadioSettingValueList(options,
-                                                options[self._memobj.vfoa.txpower]))
-        workmode.append(rs)
-
-        rs = RadioSetting("vfob.txpower", "VFO B Power",
-                          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]))
-        workmode.append(rs)
-
-        rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
-                          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]))
-        workmode.append(rs)
-
-        rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
-                          RadioSettingValueList(options,
-                                                options[self._memobj.vfob.scode]))
-        workmode.append(rs)
-
-        if self._my_version() >= 291:
-            rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
-                              RadioSettingValueList(STEP291_LIST,
-                                                    STEP291_LIST[self._memobj.vfoa.step]))
+            val1b = RadioSettingValueString(0, 10,
+                                            convert_bytes_to_freq(_vfob.freq))
+            val1b.set_validate_callback(my_validate)
+            rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b)
+            rs.set_apply_callback(apply_freq, _vfob)
             workmode.append(rs)
-            rs = RadioSetting("vfob.step", "VFO B Tuning Step",
-                              RadioSettingValueList(STEP291_LIST,
-                                                    STEP291_LIST[self._memobj.vfob.step]))
+
+            rs = RadioSetting("vfoa.sftd", "VFO A Shift",
+                              RadioSettingValueList(SHIFTD_LIST,
+                                                    SHIFTD_LIST[_vfoa.sftd]))
             workmode.append(rs)
-        else:
-            rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
-                              RadioSettingValueList(STEP_LIST,
-                                                    STEP_LIST[self._memobj.vfoa.step]))
+
+            rs = RadioSetting("vfob.sftd", "VFO B Shift",
+                              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)
+
+            def apply_offset(setting, obj):
+                value = chirp_common.parse_freq(str(setting.value)) / 10000
+                for i in range(3, -1, -1):
+                    obj.offset[i] = value % 10
+                    value /= 10
+
+            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)
+            rs.set_apply_callback(apply_offset, _vfob)
             workmode.append(rs)
-            rs = RadioSetting("vfob.step", "VFO B Tuning Step",
-                              RadioSettingValueList(STEP_LIST,
-                                                    STEP_LIST[self._memobj.vfob.step]))
+
+            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]))
+            workmode.append(rs)
+
+            rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
+                              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]))
             workmode.append(rs)
 
+            rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
+                              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]))
+                workmode.append(rs)
+                rs = RadioSetting("vfob.step", "VFO B Tuning 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]))
+                workmode.append(rs)
+                rs = RadioSetting("vfob.step", "VFO B Tuning Step",
+                                  RadioSettingValueList(STEP_LIST,
+                                                        STEP_LIST[_vfob.step]))
+                workmode.append(rs)
+
+        fm_preset = RadioSettingGroup("fm_preset", "FM Radio Preset")
+        group.append(fm_preset)
+
+        if self._memobj.fm_presets <= 116.1 * 10 - 650:
+            preset = self._memobj.fm_presets / 10.0 + 65
+        else:
+            preset = 76.0
+        rs = RadioSetting("fm_presets", "FM Preset(MHz)",
+                      RadioSettingValueFloat(65, 116.1, preset, 0.1, 1))
+        fm_preset.append(rs)
+
+        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
+        group.append(dtmf)
+        dtmfchars = "0123456789 *#ABCD"
+
+        for i in range(0, 15):
+            _codeobj = self._memobj.pttid[i].code
+            _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)
+            def apply_code(setting, obj):
+                code = []
+                for j in range(0, 5):
+                    try:
+                        code.append(dtmfchars.index(str(setting.value)[j]))
+                    except IndexError:
+                        code.append(0xFF)
+                obj.code = code
+            rs.set_apply_callback(apply_code, self._memobj.pttid[i])
+            dtmf.append(rs)
+
+        _codeobj = self._memobj.ani.code
+        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+        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):
+                try:
+                    code.append(dtmfchars.index(str(setting.value)[j]))
+                except IndexError:
+                    code.append(0xFF)
+            obj.code = code
+        rs.set_apply_callback(apply_code, _ani)
+        dtmf.append(rs)
+
+        rs = RadioSetting("ani.aniid", "ANI ID",
+                          RadioSettingValueList(PTTID_LIST,
+                                                PTTID_LIST[_ani.aniid]))
+        dtmf.append(rs)
+
+        _codeobj = self._memobj.ani.alarmcode
+        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+        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):
+                try:
+                    alarmcode.append(dtmfchars.index(str(setting.value)[j]))
+                except IndexError:
+                    alarmcode.append(0xFF)
+            obj.alarmcode = alarmcode
+        rs.set_apply_callback(apply_code, _ani)
+        dtmf.append(rs)
+
+        rs = RadioSetting("dtmfst", "DTMF Sidetone",
+                          RadioSettingValueList(DTMFST_LIST,
+                                                DTMFST_LIST[_settings.dtmfst]))
+        dtmf.append(rs)
+
+        rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
+                          RadioSettingValueList(DTMFSPEED_LIST,
+                                                DTMFSPEED_LIST[_ani.dtmfon]))
+        dtmf.append(rs)
+
+        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
+                          RadioSettingValueList(DTMFSPEED_LIST,
+                                                DTMFSPEED_LIST[_ani.dtmfoff]))
+        dtmf.append(rs)
+
         return group
 
     def get_settings(self):
@@ -974,23 +1387,99 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
             return None
 
     def set_settings(self, settings):
-        _settings = self._memobj.settings[0]
+        _settings = self._memobj.settings
         for element in settings:
             if not isinstance(element, RadioSetting):
-                self.set_settings(element)
-                continue
-            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]
+                if element.get_name() == "fm_preset" :
+                    self._set_fm_preset(element)
                 else:
-                    obj = _settings
-                    setting = element.get_name()
-                print "Setting %s = %s" % (setting, element.value)
-                setattr(obj, setting, element.value)
+                    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():
+                        print "Using apply callback"
+                        element.run_apply_callback()
+                    else:
+                        print "Setting %s = %s" % (setting, element.value)
+                        setattr(obj, setting, element.value)
+                except Exception, e:
+                    print element.get_name()
+                    raise
+
+    def _set_fm_preset(self, settings):
+        for element in settings:
+            try:
+                val = element.value
+                value = int(val.get_value() * 10 - 650)
+                print "Setting fm_presets = %s" % (value)
+                self._memobj.fm_presets = value
             except Exception, e:
                 print element.get_name()
                 raise
+
+ at directory.register
+class BaofengF11Radio(BaofengUV5R):
+    VENDOR = "Baofeng"
+    MODEL = "F-11"
+    _basetype = BASETYPE_F11
+    _idents = [UV5R_MODEL_F11]
+
+    def _is_orig(self):
+        # Override this for F11 to always return False
+        return False
+
+ at directory.register
+class BaofengUV82Radio(BaofengUV5R):
+    MODEL = "UV-82"
+    _basetype = BASETYPE_UV82
+    _idents = [UV5R_MODEL_UV82]
+
+    def _is_orig(self):
+        # Override this for UV82 to always return False
+        return False
+
+ at directory.register
+class BaofengUV6Radio(BaofengUV5R):
+    """Baofeng UV-6/UV-7"""
+    VENDOR = "Baofeng"
+    MODEL = "UV-6"
+    _basetype = BASETYPE_UV6
+    _idents = [UV5R_MODEL_UV6]
+
+    def get_features(self):
+        rf = BaofengUV5R.get_features(self)
+        rf.memory_bounds = (1, 128)
+        return rf
+
+    def _get_mem(self, number):
+        return self._memobj.memory[number - 1]
+
+    def _get_nam(self, number):
+        return self._memobj.names[number - 1]
+
+    def _set_mem(self, number):
+        return self._memobj.memory[number - 1]
+
+    def _set_nam(self, number):
+        return self._memobj.names[number - 1]
+
+    def _is_orig(self):
+        # Override this for UV6 to always return False
+        return False
diff --git a/chirp/uvb5.py b/chirp/uvb5.py
new file mode 100644
index 0000000..a989372
--- /dev/null
+++ b/chirp/uvb5.py
@@ -0,0 +1,775 @@
+# Copyright 2013 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 struct
+from chirp import chirp_common, directory, bitwise, memmap, errors, util
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+                RadioSettingValueBoolean, RadioSettingValueList, \
+                RadioSettingValueInteger, RadioSettingValueString, \
+                RadioSettingValueFloat
+from textwrap import dedent
+
+mem_format = """
+struct memory {
+  lbcd freq[4];
+  lbcd offset[4];
+  u8 unknown1:2,
+     txpol:1,
+     rxpol:1,
+     compander:1,
+     unknown2:3;
+  u8 rxtone;
+  u8 txtone;
+  u8 pttid:1,
+     scanadd:1,
+     isnarrow:1,
+     bcl:1,
+     highpower:1,
+     revfreq:1,
+     duplex:2;
+  u8 step;
+  u8 unknown[3];
+};
+
+#seekto 0x0000;
+char ident[32];
+u8 blank[16];
+struct memory vfo1;
+struct memory channels[99];
+#seekto 0x0850;
+struct memory vfo2;
+
+#seekto 0x09D0;
+u16 fm_presets[16];
+
+#seekto 0x0A30;
+struct {
+  u8 name[5];
+} names[99];
+
+#seekto 0x0D30;
+struct {
+  u8 squelch;
+  u8 freqmode_ab:1,
+     save_funct:1,
+     backlight:1,
+     beep_tone_disabled:1,
+     roger:1,
+     tdr:1,
+     scantype:2;
+  u8 language:1,
+     workmode_b:1,
+     workmode_a:1,
+     workmode_fm:1,
+     voice_prompt:1,
+     fm:1,
+     pttid:2;
+  u8 unknown_0:5,
+     timeout:3;
+  u8 mdf_b:2,
+     mdf_a:2,
+     unknown_1:2,
+     txtdr:2;
+  u8 unknown_2:4,
+     ste_disabled:1,
+     unknown_3:2,
+     sidetone:1;
+  u8 vox;
+  u8 unk1;
+  u8 mem_chan_a;
+  u16 fm_vfo;
+  u8 unk4;
+  u8 unk5;
+  u8 mem_chan_b;
+  u8 unk6;
+  u8 last_menu; // number of last menu item accessed
+} settings;
+
+#seekto 0x0D50;
+struct {
+  u8 code[6];
+} pttid;
+
+#seekto 0x0F30;
+struct {
+  lbcd lower_vhf[2];
+  lbcd upper_vhf[2];
+  lbcd lower_uhf[2];
+  lbcd upper_uhf[2];
+} limits;
+
+#seekto 0x0FF0;
+struct {
+  u8 vhfsquelch0;
+  u8 vhfsquelch1;
+  u8 vhfsquelch2;
+  u8 vhfsquelch3;
+  u8 vhfsquelch4;
+  u8 vhfsquelch5;
+  u8 vhfsquelch6;
+  u8 vhfsquelch7;
+  u8 vhfsquelch8;
+  u8 vhfsquelch9;
+  u8 unknown1[6];
+  u8 uhfsquelch0;
+  u8 uhfsquelch1;
+  u8 uhfsquelch2;
+  u8 uhfsquelch3;
+  u8 uhfsquelch4;
+  u8 uhfsquelch5;
+  u8 uhfsquelch6;
+  u8 uhfsquelch7;
+  u8 uhfsquelch8;
+  u8 uhfsquelch9;
+  u8 unknown2[6];
+  u8 vhfhipwr0;
+  u8 vhfhipwr1;
+  u8 vhfhipwr2;
+  u8 vhfhipwr3;
+  u8 vhfhipwr4;
+  u8 vhfhipwr5;
+  u8 vhfhipwr6;
+  u8 vhfhipwr7;
+  u8 vhflopwr0;
+  u8 vhflopwr1;
+  u8 vhflopwr2;
+  u8 vhflopwr3;
+  u8 vhflopwr4;
+  u8 vhflopwr5;
+  u8 vhflopwr6;
+  u8 vhflopwr7;
+  u8 uhfhipwr0;
+  u8 uhfhipwr1;
+  u8 uhfhipwr2;
+  u8 uhfhipwr3;
+  u8 uhfhipwr4;
+  u8 uhfhipwr5;
+  u8 uhfhipwr6;
+  u8 uhfhipwr7;
+  u8 uhflopwr0;
+  u8 uhflopwr1;
+  u8 uhflopwr2;
+  u8 uhflopwr3;
+  u8 uhflopwr4;
+  u8 uhflopwr5;
+  u8 uhflopwr6;
+  u8 uhflopwr7;
+} test;
+"""
+
+def do_ident(radio):
+    radio.pipe.setTimeout(3)
+    radio.pipe.write("PROGRAM")
+    ack = radio.pipe.read(1)
+    if ack != '\x06':
+        raise errors.RadioError("Radio did not ack programming mode")
+    radio.pipe.write("\x02")
+    ident = radio.pipe.read(8)
+    print util.hexprint(ident)
+    if not ident.startswith('HKT511'):
+        raise errors.RadioError("Unsupported model")
+    radio.pipe.write("\x06")
+    ack = radio.pipe.read(1)
+    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
+    status.cur = addr
+    status.max = 0x1000
+    radio.status_fn(status)
+
+def do_download(radio):
+    do_ident(radio)
+    data = "KT511 Radio Program data v1.08\x00\x00"
+    data += ("\x00" * 16)
+    firstack = None
+    for i in range(0, 0x1000, 16):
+        frame = struct.pack(">cHB", "R", i, 16)
+        radio.pipe.write(frame)
+        result = radio.pipe.read(20)
+        if frame[1:4] != result[1:4]:
+            print util.hexprint(result)
+            raise errors.RadioError("Invalid response for address 0x%04x" % i)
+        radio.pipe.write("\x06")
+        ack = radio.pipe.read(1)
+        if not firstack:
+            firstack = ack
+        else:
+            if not ack == firstack:
+                print "first ack:", util.hexprint(firstack), \
+                    "ack received:", 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:]
+
+    for i in range(0, 0x1000, 16):
+        frame = struct.pack(">cHB", "W", i, 16)
+        frame += data[i:i + 16]
+        radio.pipe.write(frame)
+        ack = radio.pipe.read(1)
+        if ack != "\x06":
+            raise errors.RadioError("Radio NAK'd block at address 0x%04x" % i)
+        do_status(radio, "to", i)
+
+DUPLEX = ["", "-", "+", 'off', "split"]
+UVB5_STEPS = [5.00, 6.25, 10.0, 12.5, 20.0, 25.0]
+CHARSET = "0123456789- ABCDEFGHIJKLMNOPQRSTUVWXYZ/_+*"
+SPECIALS = {
+    "VFO1": -2,
+    "VFO2": -1,
+    }
+POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1),
+                chirp_common.PowerLevel("High", watts=5)]
+
+ at directory.register
+class BaofengUVB5(chirp_common.CloneModeRadio,
+        chirp_common.ExperimentalRadio):
+    """Baofeng UV-B5"""
+    VENDOR = "Baofeng"
+    MODEL = "UV-B5"
+    BAUD_RATE = 9600
+
+    _memsize = 0x1000
+
+    @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.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.
+            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.
+            5. Ensure that the radio is tuned to channel with no activity.
+            6. Click OK to upload image to device."""))
+        return rp
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.has_settings = True
+        rf.has_cross = True
+        rf.has_rx_dtcs = True
+        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
+        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
+                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
+        rf.valid_duplexes = DUPLEX
+        rf.can_odd_split = True
+        rf.valid_skips = ["", "S"]
+        rf.valid_characters = CHARSET
+        rf.valid_name_length = 5
+        rf.valid_bands = [(136000000, 174000000),
+                          (400000000, 520000000)]
+        rf.valid_modes = ["FM", "NFM"]
+        rf.valid_special_chans = SPECIALS.keys()
+        rf.valid_power_levels = POWER_LEVELS
+        rf.has_ctone = True
+        rf.has_bank = False
+        rf.has_tuning_step = False
+        rf.memory_bounds = (1, 99)
+        return rf
+
+    def sync_in(self):
+        try:
+            self._mmap = do_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):
+        try:
+            do_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.channels[number - 1])
+
+    def _decode_tone(self, value, flag):
+        if value > 155:
+            mode = val = pol = None
+        elif value > 50:
+            mode = 'DTCS'
+            val = chirp_common.DTCS_CODES[value - 51]
+            pol = flag and 'R' or 'N'
+        elif value:
+            mode = 'Tone'
+            val = chirp_common.TONES[value - 1]
+            pol = None
+        else:
+            mode = val = pol = None
+
+        return mode, val, pol
+
+    def _encode_tone(self, _mem, which, mode, val, pol):
+        def _set(field, value):
+            setattr(_mem, "%s%s" % (which, field), value)
+
+        _set("pol", 0)
+        if mode == "Tone":
+            _set("tone", chirp_common.TONES.index(val) + 1)
+        elif mode == "DTCS":
+            _set("tone", chirp_common.DTCS_CODES.index(val) + 51)
+            _set("pol", pol == "R")
+        else:
+            _set("tone", 0)
+
+    def _get_memobjs(self, number):
+        if isinstance(number, str):
+            return (getattr(self._memobj, number.lower()), None)
+        elif number < 0:
+            for k, v in SPECIALS.items():
+                if number == v:
+                    return (getattr(self._memobj, k.lower()), None)
+        else:
+            return (self._memobj.channels[number - 1],
+                    self._memobj.names[number - 1].name)
+
+    def get_memory(self, number):
+        _mem, _nam = self._get_memobjs(number)
+        mem = chirp_common.Memory()
+        if isinstance(number, str):
+            mem.number = SPECIALS[number]
+            mem.extd_number = number
+        else:
+            mem.number = number
+
+        if _mem.freq.get_raw()[0] == "\xFF":
+            mem.empty = True
+            return mem
+
+        mem.freq = int(_mem.freq) * 10
+        mem.offset = int(_mem.offset) * 10
+
+        chirp_common.split_tone_decode(
+            mem,
+            self._decode_tone(_mem.txtone, _mem.txpol),
+            self._decode_tone(_mem.rxtone, _mem.rxpol))
+
+        if _mem.step > 0x05:
+            _mem.step = 0x00
+        mem.duplex = DUPLEX[_mem.duplex]
+        mem.mode = _mem.isnarrow and "NFM" or "FM"
+        mem.skip = "" if _mem.scanadd else "S"
+        mem.power = POWER_LEVELS[_mem.highpower]
+
+        if mem.freq == mem.offset and mem.duplex == "-":
+           mem.duplex = "off"
+           mem.offset = 0
+
+        if _nam:
+            for char in _nam:
+                try:
+                    mem.name += CHARSET[char]
+                except IndexError:
+                    break
+            mem.name = mem.name.rstrip()
+
+        mem.extra = RadioSettingGroup("Extra", "extra")
+
+        rs = RadioSetting("bcl", "BCL",
+                          RadioSettingValueBoolean(_mem.bcl))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("revfreq", "Reverse Duplex",
+                          RadioSettingValueBoolean(_mem.revfreq))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("pttid", "PTT ID",
+                          RadioSettingValueBoolean(_mem.pttid))
+        mem.extra.append(rs)
+
+        rs = RadioSetting("compander", "Compander",
+                          RadioSettingValueBoolean(_mem.compander))
+        mem.extra.append(rs)
+
+        return mem
+
+    def set_memory(self, mem):
+        _mem, _nam = self._get_memobjs(mem.number)
+
+        if mem.empty:
+            if _nam is None:
+                raise errors.InvalidValueError("VFO channels can not be empty")
+            _mem.set_raw("\xFF" * 16)
+            return
+
+        if _mem.get_raw() == ("\xFF" * 16):
+            _mem.set_raw("\x00" * 13 + "\xFF" * 3)
+
+        _mem.freq = mem.freq / 10
+
+        if mem.duplex == "off":
+            _mem.duplex = DUPLEX.index("-")
+            _mem.offset = _mem.freq
+        elif mem.duplex == "split":
+            diff = mem.offset - mem.freq
+            _mem.duplex = DUPLEX.index("-") if diff < 0 else DUPLEX.index("+")
+            _mem.offset = abs(diff) / 10
+        else:
+            _mem.offset = mem.offset / 10
+            _mem.duplex = DUPLEX.index(mem.duplex)
+
+        tx, rx = chirp_common.split_tone_encode(mem)
+        self._encode_tone(_mem, 'tx', *tx)
+        self._encode_tone(_mem, 'rx', *rx)
+
+        _mem.isnarrow = mem.mode == "NFM"
+        _mem.scanadd = mem.skip == ""
+        _mem.highpower = mem.power == POWER_LEVELS[1]
+
+        if _nam:
+            for i in range(0, 5):
+                try:
+                    _nam[i] = CHARSET.index(mem.name[i])
+                except IndexError:
+                    _nam[i] = 0xFF
+
+        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)
+
+        options = ["Time", "Carrier", "Search"]
+        rs = RadioSetting("scantype", "Scan Type",
+                          RadioSettingValueList(options,
+                                                options[_settings.scantype]))
+        basic.append(rs)
+
+        options = ["Off"] + ["%s min" % x for x in range(1, 8)]
+        rs = RadioSetting("timeout", "Time Out Timer",
+                          RadioSettingValueList(options,
+                                                options[_settings.timeout]))
+        basic.append(rs)
+
+        options = ["A", "B"]
+        rs = RadioSetting("freqmode_ab", "Frequency Mode",
+                          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]))
+        basic.append(rs)
+
+        rs = RadioSetting("workmode_b", "Radio Work Mode(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]))
+        basic.append(rs)
+
+        rs = RadioSetting("mdf_b", "Display Format(F2)",
+                          RadioSettingValueList(options,
+                                                options[_settings.mdf_b]))
+        basic.append(rs)
+
+        rs = RadioSetting("mem_chan_a", "Mem Channel (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))
+        basic.append(rs)
+
+        options = ["Off", "BOT", "EOT", "Both"]
+        rs = RadioSetting("pttid", "PTT-ID",
+                          RadioSettingValueList(options,
+                                                options[_settings.pttid]))
+        basic.append(rs)
+
+        dtmfchars = "0123456789ABCD*#"
+        _codeobj = self._memobj.pttid.code
+        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+        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):
+                try:
+                    code.append(dtmfchars.index(str(setting.value)[j]))
+                except IndexError:
+                    code.append(0xFF)
+            obj.code = code
+        rs.set_apply_callback(apply_code, self._memobj.pttid)
+        basic.append(rs)
+
+        rs = RadioSetting("squelch", "Squelch Level",
+                          RadioSettingValueInteger(0, 9, _settings.squelch))
+        basic.append(rs)
+
+        rs = RadioSetting("vox", "VOX Level",
+                          RadioSettingValueInteger(0, 9, _settings.vox))
+        basic.append(rs)
+
+        options = ["Frequency Mode", "Channel Mode"]
+        rs = RadioSetting("workmode_fm", "FM Work Mode",
+                          RadioSettingValueList(options,
+                                                options[_settings.workmode_fm]))
+        basic.append(rs)
+
+        options = ["Current Frequency", "F1 Frequency", "F2 Frequency"]
+        rs = RadioSetting("txtdr", "Dual Standby TX Priority",
+                          RadioSettingValueList(options,
+                                                options[_settings.txtdr]))
+        basic.append(rs)
+
+        options = ["English", "Chinese"]
+        rs = RadioSetting("language", "Language",
+                          RadioSettingValueList(options,
+                                                options[_settings.language]))
+        basic.append(rs)
+
+        rs = RadioSetting("tdr", "Dual Standby",
+                          RadioSettingValueBoolean(_settings.tdr))
+        basic.append(rs)
+
+        rs = RadioSetting("roger", "Roger Beep",
+                          RadioSettingValueBoolean(_settings.roger))
+        basic.append(rs)
+
+        rs = RadioSetting("backlight", "Backlight",
+                          RadioSettingValueBoolean(_settings.backlight))
+        basic.append(rs)
+
+        rs = RadioSetting("save_funct", "Save Mode",
+                          RadioSettingValueBoolean( _settings.save_funct))
+        basic.append(rs)
+
+        rs = RadioSetting("fm", "FM Function",
+                          RadioSettingValueBoolean(_settings.fm))
+        basic.append(rs)
+
+        rs = RadioSetting("beep_tone_disabled", "Beep Prompt",
+                          RadioSettingValueBoolean(
+                              not _settings.beep_tone_disabled))
+        basic.append(rs)
+
+        rs = RadioSetting("voice_prompt", "Voice Prompt",
+                          RadioSettingValueBoolean(_settings.voice_prompt))
+        basic.append(rs)
+
+        rs = RadioSetting("sidetone", "DTMF Side Tone",
+                          RadioSettingValueBoolean(_settings.sidetone))
+        basic.append(rs)
+
+        rs = RadioSetting("ste_disabled", "Squelch Tail Eliminate",
+                          RadioSettingValueBoolean(not _settings.ste_disabled))
+        basic.append(rs)
+
+        _limit = int(self._memobj.limits.lower_vhf) / 10
+        rs = RadioSetting("limits.lower_vhf", "VHF Lower Limit (MHz)",
+                          RadioSettingValueInteger(136, 174, _limit))
+        def apply_limit(setting, obj):
+            value = int(setting.value) * 10
+            obj.lower_vhf = value
+        rs.set_apply_callback(apply_limit, self._memobj.limits)
+        basic.append(rs)
+
+        _limit = int(self._memobj.limits.upper_vhf) / 10
+        rs = RadioSetting("limits.upper_vhf", "VHF Upper Limit (MHz)",
+                          RadioSettingValueInteger(136, 174, _limit))
+        def apply_limit(setting, obj):
+            value = int(setting.value) * 10
+            obj.upper_vhf = value
+        rs.set_apply_callback(apply_limit, self._memobj.limits)
+        basic.append(rs)
+
+        _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
+        rs.set_apply_callback(apply_limit, self._memobj.limits)
+        basic.append(rs)
+
+        _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
+        rs.set_apply_callback(apply_limit, self._memobj.limits)
+        basic.append(rs)
+
+        fm_preset = RadioSettingGroup("fm_preset", "FM Radio Presets")
+        group.append(fm_preset)
+
+        for i in range(0, 16):
+            if self._memobj.fm_presets[i] < 0x01AF:
+                used = True
+                preset = self._memobj.fm_presets[i] / 10.0 + 65
+            else:
+                used = False
+                preset = 65
+            rs = RadioSetting("fm_presets_%1i" % i, "FM Preset %i" % (i + 1),
+                          RadioSettingValueBoolean(used),
+                          RadioSettingValueFloat(65, 108, preset, 0.1, 1))
+            fm_preset.append(rs)
+
+        testmode = RadioSettingGroup("testmode", "Test Mode Settings")
+        group.append(testmode)
+
+        vhfdata = ["136-139", "140-144", "145-149", "150-154",
+                   "155-159", "160-164", "165-169", "170-174"]
+        uhfdata = ["400-409", "410-419", "420-429", "430-439",
+                   "440-449", "450-459", "460-469", "470-479"]
+        powernamedata = ["Hi", "Lo"]
+        powerkeydata = ["hipwr", "lopwr"]
+
+        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))))
+                testmode.append(rs)
+
+        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))))
+                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))))
+                testmode.append(rs)
+
+        return group
+
+    def set_settings(self, settings):
+        _settings = self._memobj.settings
+        for element in settings:
+            if not isinstance(element, RadioSetting):
+                if element.get_name() == "fm_preset" :
+                    self._set_fm_preset(element)
+                else:
+                    self.set_settings(element)
+                    continue
+            else:
+                try:
+                    name = element.get_name()
+                    if "." in name:
+                        bits = name.split(".")
+                        obj = self._memobj
+                        for bit in bits[:-1]:
+                            if "/" in bit:
+                                bit, index = bit.split("/", 1)
+                                index = int(index)
+                                obj = getattr(obj, bit)[index]
+                            else:
+                                obj = getattr(obj, bit)
+                        setting = bits[-1]
+                    else:
+                        obj = _settings
+                        setting = element.get_name()
+
+                    if element.has_apply_callback():
+                        print "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)
+                    elif setting == "ste_disabled":
+                        val = not _settings.ste_disabled
+                        print "Setting %s = %s" % (setting, val)
+                        setattr(obj, setting, val)
+                    else:
+                        print "Setting %s = %s" % (setting, element.value)
+                        setattr(obj, setting, element.value)
+                except Exception, e:
+                    print element.get_name()
+                    raise
+
+    def _set_fm_preset(self, settings):
+        for element in settings:
+            try:
+                index = (int(element.get_name().split("_")[-1]))
+                val = element.value
+                if val[0].get_value():
+                    value = int(val[1].get_value() * 10 - 650)
+                else:
+                    value = 0x01AF
+                print "Setting fm_presets[%1i] = %s" % (index, value)
+                setting = self._memobj.fm_presets
+                setting[index] = value
+            except Exception, e:
+                print element.get_name()
+                raise
+
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return (filedata.startswith("KT511 Radio Program data") and
+                len(filedata) == (cls._memsize + 0x30))
diff --git a/chirp/vx2.py b/chirp/vx2.py
new file mode 100644
index 0000000..d68f098
--- /dev/null
+++ b/chirp/vx2.py
@@ -0,0 +1,708 @@
+# Copyright 2013 Jens Jensen <kd4tjx at yahoo.com>
+#     based on modification of Dan Smith's and Rick Farina's 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/>.
+
+from chirp import chirp_common, yaesu_clone, directory, bitwise
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettingValueString
+import os, traceback, re
+
+if os.getenv("CHIRP_DEBUG"):
+    CHIRP_DEBUG = True
+else:
+    CHIRP_DEBUG = False
+
+MEM_FORMAT = """
+#seekto 0x7F52;
+u8 checksum;
+
+#seekto 0x005A;
+u8  banksoff1;
+#seekto 0x00DA;
+u8  banksoff2;
+
+#seekto 0x0068;
+u16 prioritychan1;
+
+#seekto 0x00E8;
+u16 prioritychan2;
+
+#seekto 0x110;
+struct {
+    u8  unk1;
+    u8  unk2;
+    u8  nfm_sql;
+    u8  wfm_sql;
+    u8  rfsql;
+    u8  vfomode:1,
+        cwid_en:1,
+        scan_lamp:1,
+        unk3:1,
+        ars:1,
+        beep:1,
+        split:1,
+        dtmfmode:1;
+    u8  busyled:1,
+        unk4:2,
+        bclo:1,
+        edgebeep:1,
+        unk5:2,
+        txsave:1;
+    u8  unk6:2,
+        smartsearch:1,
+        unk7:1,
+        artsinterval:1,
+        unk8:1,
+        hmrv:1,
+        moni_tcall:1;
+    u8  unk9:5,
+        dcsrev:1,
+        unk10:1,
+        mwmode:1;
+    u8  internet_mode:1,
+        internet_key:1,
+        wx_alert:1,
+        unk11:2,
+        att:1,
+        unk12:2;
+    u8  lamp;
+    u8  dimmer;
+    u8  rxsave;
+    u8  resume;
+    u8  chcounter;
+    u8  openmsgmode;
+    u8  openmsg[6];
+    u8  cwid[16];
+    u8  unk13[16];
+    u8  artsbeep;
+    u8  bell;
+    u8  apo;
+    u8  tot;
+    u8  lock;
+    u8  mymenu;
+    u8  unk14[4];
+    u8  emergmode;
+        
+} settings;
+
+#seekto 0x0192;
+struct {
+    u8  digits[16];
+}   dtmf[9];
+
+#seekto 0x016A;
+struct {
+    u16 in_use;
+} bank_used[20];
+
+#seekto 0x0396;
+struct {
+    u8 name[6];
+} wxchannels[10];
+
+#seekto 0x05C2;
+struct {
+  u16 channels[100];
+} banks[20];
+
+#seekto 0x1562;
+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[500];
+
+struct mem_struct {
+  u8   unknown1:2,
+       txnarrow:1,
+       clk:1,
+       unknown2:4;
+  u8   mode:2,
+       duplex:2,
+       tune_step:4;
+  bbcd freq[3];
+  u8   power:2,
+       unknown3:4,
+       tmode:2;
+  u8   name[6];
+  bbcd offset[3];
+  u8   unknown4:2,
+       tone:6;
+  u8   unknown5:1,
+       dcs:7;
+  u8   unknown6;
+};
+
+#seekto 0x17C2;
+struct mem_struct memory[1000];
+struct {
+    struct mem_struct lower;
+    struct mem_struct upper;
+} pms[50];
+
+
+#seekto 0x03D2;
+struct mem_struct home[12];
+
+#seekto 0x04E2;
+struct mem_struct vfo[12];
+
+
+"""
+
+VX2_DUPLEX = ["", "-", "+", "split"]
+VX2_MODES  = ["FM", "AM", "WFM", "Auto", "NFM"] # NFM handled specially in radio
+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 ]
+
+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 VX2BankModel(chirp_common.BankModel):
+    """A VX-2 bank model"""
+
+    def get_num_mappings(self):
+        return len(self.get_mappings())
+
+    def get_mappings(self):
+        banks = self._radio._memobj.banks
+        bank_mappings = []
+        for index, _bank in enumerate(banks):
+            bank = chirp_common.Bank(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) + 1 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("Too many entries in bank %d" % bank.index)
+
+        empty = 0
+        for index, channel_number in enumerate(sorted(channels_in_bank)):
+            _members.channels[index] = channel_number - 1
+            empty = index + 1
+        for index in range(empty, len(_members.channels)):
+            _members.channels[index] = 0xFFFF
+
+    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)
+
+        _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
+        # todo: turn back off (0xFF) 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" % \
+                            (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
+
+    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
+
+
+def _wipe_memory(mem):
+    mem.set_raw("\x00" * (mem.size() / 8))
+
+ at directory.register
+class VX2Radio(yaesu_clone.YaesuCloneModeRadio):
+    """Yaesu VX-2"""
+    MODEL = "VX-2"
+    _model = "AH015"
+    BAUD_RATE = 19200
+    _block_lengths = [ 10, 8, 32577 ]
+    _memsize = 32595
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.has_bank = True
+        rf.has_settings = True
+        rf.has_dtcs_polarity = False
+        rf.valid_modes = list(set(VX2_MODES))
+        rf.valid_tmodes = list(VX2_TMODES)
+        rf.valid_duplexes = list(VX2_DUPLEX)
+        rf.valid_tuning_steps = list(VX2_STEPS)
+        rf.valid_bands = [(500000, 999000000)]
+        rf.valid_skips = ["", "S", "P"]
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_characters = "".join(CHARSET)
+        rf.valid_name_length = 6
+        rf.memory_bounds = (1, 1000)
+        rf.can_odd_split = True
+        rf.has_ctone = False
+        return rf
+
+    def _checksums(self):
+        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])
+
+    def get_memory(self, number):
+        _mem = self._memobj.memory[number-1]
+        _flag = self._memobj.flags[(number-1)/2]
+
+        nibble = ((number-1) % 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 not used:
+            mem.empty = True
+        if not valid:
+            mem.empty = True
+            mem.power = POWER_LEVELS[0]
+            return mem
+
+        mem.freq = chirp_common.fix_rounded_step(int(_mem.freq) * 1000)
+        mem.offset = int(_mem.offset) * 1000
+        mem.rtone = mem.ctone = chirp_common.TONES[_mem.tone]
+        mem.tmode = VX2_TMODES[_mem.tmode]
+        mem.duplex = VX2_DUPLEX[_mem.duplex]
+        if mem.duplex == "split":
+            mem.offset = chirp_common.fix_rounded_step(mem.offset)
+        if _mem.txnarrow and _mem.mode == VX2_MODES.index("FM"):
+            # narrow + FM
+            mem.mode = "NFM"
+        else:
+            mem.mode = VX2_MODES[_mem.mode]
+        mem.dtcs = chirp_common.DTCS_CODES[_mem.dcs]
+        mem.tuning_step = VX2_STEPS[_mem.tune_step]
+        mem.skip = pskip and "P" or skip and "S" or ""
+        mem.power = POWER_LEVELS[~_mem.power & 0x01]
+
+        for i in _mem.name:
+            if i == 0xFF:
+                break
+            mem.name += CHARSET[i & 0x7F]
+        mem.name = mem.name.rstrip()
+        return mem
+
+    def set_memory(self, mem):
+        _mem = self._memobj.memory[mem.number-1]
+        _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]
+
+        if not mem.empty and 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.tone = chirp_common.TONES.index(mem.rtone)
+        _mem.tmode = VX2_TMODES.index(mem.tmode)
+        _mem.duplex = VX2_DUPLEX.index(mem.duplex)
+        if mem.mode == "NFM":
+            _mem.mode = VX2_MODES.index("FM")
+            _mem.txnarrow = True
+        else:
+            _mem.mode = VX2_MODES.index(mem.mode)
+            _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
+            _mem.power = 0x00
+        else: # Default to High
+            _mem.power = 0x03
+
+        _flag["%s_pskip" % nibble] = mem.skip == "P"
+        _flag["%s_skip" % nibble] = mem.skip == "S"
+
+        for i in range(0, 6):
+            _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
+            _mem.name[0] |= 0x80
+        
+        # for now, clear unknown fields
+        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
+
+    def get_bank_model(self):
+        return VX2BankModel(self)
+
+    def _decode_chars(self, inarr):
+        if CHIRP_DEBUG:
+            print "@_decode_chars, type: %s" % type(inarr)
+            print 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
+        outarr = []
+        instr = str(instr)
+        for i in range(0, length):
+            if i < len(instr):
+                outarr.append(CHARSET.index(instr[i]))
+            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)
+        
+        options = ["Home", "Reverse"]
+        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)
+        
+        options = ["wires", "link"]
+        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]))
+        basic.append(rs)
+        
+        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]))
+        basic.append(rs)
+        
+        options = ["lower", "next"]
+        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]))
+        basic.append(rs)
+        
+        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]))
+        basic.append(rs)
+        
+        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)
+        
+        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
+        
+        # arts settings (ar beep, ar int, cwid en, cwid field)
+        options = ["15s", "25s"]
+        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]))
+        arts.append(rs)
+        
+        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.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]))
+        dtmf.append(rs)
+        
+        for i in range(0,8+1):
+            name = "dtmf" + str(i+1)
+            dtmfsetting = self._memobj.dtmf[i]
+            #dtmflen = getattr(_settings, objname + "_len")
+            dtmfstr = ""
+            for c in dtmfsetting.digits:
+                if c < len(DTMFCHARSET):
+                    dtmfstr += DTMFCHARSET[c]
+            if CHIRP_DEBUG:
+                print dtmfstr
+            dtmfentry = RadioSettingValueString(0, 16, dtmfstr)
+            dtmfentry.set_charset(DTMFCHARSET + list(" "))
+            rs = RadioSetting(name, name.upper(), dtmfentry)
+            dtmf.append(rs)            
+        
+        return top
+        
+    def set_settings(self, uisettings):
+        for element in uisettings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+            if not element.changed():
+                continue
+            try:
+                setting = element.get_name()
+                _settings = self._memobj.settings
+                if re.match('dtmf\d', setting):
+                    # set dtmf fields
+                    dtmfstr = str(element.value).strip()
+                    newval = []
+                    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
+                    idx = int(setting[-1:]) - 1
+                    _settings = self._memobj.dtmf[idx]
+                    _settings.digits = newval
+                    continue
+                if setting == "prioritychan":
+                    # prioritychan is top-level member, fix 0 index
+                    element.value -= 1
+                    _settings = self._memobj   
+                if setting == "mymenu":
+                    opts = element.value.get_options()                  
+                    optsidx = opts.index(element.value.get_value())
+                    idx =  optsidx + 9
+                    setattr(_settings, "mymenu", idx)
+                    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)
+                setattr(_settings, setting, newval)
+            except Exception, e:
+                print element.get_name()
+                raise
+        
+        
\ No newline at end of file
diff --git a/chirp/vx3.py b/chirp/vx3.py
index a06c910..fddbce0 100644
--- a/chirp/vx3.py
+++ b/chirp/vx3.py
@@ -16,6 +16,11 @@
 
 from chirp import chirp_common, yaesu_clone, directory
 from chirp import bitwise
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettingValueString
+from textwrap import dedent
+import os, re
 
 #interesting offsets which may be checksums needed later
 #0x0393 checksum1?
@@ -23,37 +28,175 @@ from chirp import bitwise
 #0x0409 checksum2?
 #0x04C9 checksum2a?
 
+if os.getenv("CHIRP_DEBUG"):
+    CHIRP_DEBUG = True
+else:
+    CHIRP_DEBUG = False
+
 MEM_FORMAT = """
 #seekto 0x7F4A;
 u8 checksum;
 
+#seekto 0x024A;
+struct {
+    u8  unk01_1:3,
+        att_broadcast:1,
+        att_marine:1,
+        unk01_2:2
+        att_wx:1;
+    u8  unk02;
+    u8  apo;
+    u8  arts_beep;
+    u8  unk04_1;               
+    u8  beep_level;
+    u8  beep_mode;
+    u8  unk04_2;
+    u8  arts_cwid[16];
+    u8  unk05[10];
+    u8  channel_counter;
+    u8  unk06_1[2];
+    u8  dtmf_delay;
+    u8  dtmf_chan_active;
+    u8  unk06_2[5];
+    u8  emergency_eai_time;
+    u8  emergency_signal;
+    u8  unk07[30];
+    u8  fw_key_timer;
+    u8  internet_key;
+    u8  lamp;
+    u8  lock_mode;
+    u8  my_key;
+    u8  mic_gain;
+    u8  mem_ch_step;
+    u8  unk08[3];
+    u8  sql_fm;
+    u8  sql_wfm;
+    u8  radio_am_sql;
+    u8  radio_fm_sql;
+    u8  on_timer;
+    u8  openmsg_mode;
+    u8  openmsg[6];
+    u8  pager_rxtone1;
+    u8  pager_rxtone2;
+    u8  pager_txtone1;
+    u8  pager_txtone2;
+    u8  password[4];
+    u8  unk10;
+    u8  priority_time;
+    u8  ptt_delay;
+    u8  rx_save;
+    u8  scan_resume;
+    u8  scan_restart;
+    u8  sub_rx_timer;
+    u8  unk11[7];
+    u8  tot;
+    u8  wake_up;
+    u8  unk12[2];
+    u8  vfo_mode:1,
+        arts_cwid_enable:1,
+        scan_lamp:1,
+        fast_tone_search:1,
+        ars:1,
+        dtmf_speed:1,
+        split_tone:1,
+        dtmf_autodialer:1;
+    u8  busy_led:1,
+        tone_search_mute:1,
+        unk14_1:1,
+        bclo:1,
+        band_edge_beep:1,
+        unk14_2:2,
+        txsave:1;
+    u8  unk15_1:2,
+        smart_search:1,
+        emergency_eai:1,
+        unk15_2:2,
+        hm_rv:1,
+        moni_tcall:1;
+    u8  lock:1,
+        unk16_1:1,
+        arts:1,
+        arts_interval:1,
+        unk16_2:1,
+        protect_memory:1,
+        unk16_3:1,
+        mem_storage:1;
+    u8  vol_key_mode:1,
+        unk17_1:2,
+        wx_alert:1,
+        temp_unit:1,
+        unk17_2:2,
+        password_active:1;
+    u8  fm_broadcast_mode:1,
+        fm_antenna:1,
+        am_antenna:1,
+        fm_speaker_out:1,
+        home_vfo:1,
+        unk18_1:2,
+        priority_revert:1;
+}   settings;
+
+// banks?
+#seekto 0x034D;
+u8  banks_unk1;
+
+#seekto 0x0356;
+u32 banks_unk2;
+
+#seekto 0x0409;
+u8  banks_unk3;
+
+#seekto 0x04CA;
+struct {
+    u8    memory[16];
+}   dtmf[10];
+
 #seekto 0x0B7A;
 struct {
   u8 name[6];
 } bank_names[24];
 
+#seekto 0x0E0A;
+struct {
+  u16 channels[100];
+} banks[24];
+
+#seekto 0x02EE;
+struct {
+    u16 in_use;
+} bank_used[24];
+
+#seekto 0x03FE;
+struct {
+    u8  speaker;
+    u8  earphone;
+}   volumes;
+
 #seekto 0x20CA;
 struct {
   u8 even_pskip:1,
      even_skip:1,
-     even_valid:1,
+     even_valid:1,  // TODO: should be "showname", i.e., show alpha name
      even_masked:1,
      odd_pskip:1,
      odd_skip:1,
      odd_valid:1,
      odd_masked:1;
-} flags[900];
+} flags[999];
 
 #seekto 0x244A;
 struct {
-  u8   unknown1;
+  u8   unknown1a:2,
+       txnarrow:1,
+       clockshift:1,
+       unknown1b:4;
   u8   mode:2,
        duplex:2,
        tune_step:4;
   bbcd freq[3];
   u8   power:2,
        unknown2:4,
-       tmode:2;
+       tmode:2;  // TODO: tmode should be 6 bits (extended tone modes)
   u8   name[6];
   bbcd offset[3];
   u8   unknown3:2,
@@ -61,18 +204,23 @@ struct {
   u8   unknown4:1,
        dcs:7;
   u8   unknown5;
-  u8   unknown6;
-  u8   unknown7:4,
+  u8   smetersquelch;
+  u8   unknown7a:2,
+       attenuate:1,
+       unknown7b:1,
        automode:1,
-       unknown8:3;
-} memory[900];
+       unknown8:1,
+       bell:2;
+} memory[999];
 """
 
 #fix auto mode setting and auto step setting
 
 DUPLEX = ["", "-", "+", "split"]
-MODES  = ["FM", "AM", "WFM", "FM"] # last is auto
+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 ]
@@ -89,6 +237,7 @@ CHARSET = list("0123456789" + \
                    ("\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)]
 
@@ -101,7 +250,7 @@ class VX3Bank(chirp_common.NamedBank):
             if i == 0xFF:
                 break
             name += CHARSET[i & 0x7F]
-        return name
+        return name.rstrip()
 
     def set_name(self, name):
         name = name.upper()
@@ -110,17 +259,73 @@ class VX3Bank(chirp_common.NamedBank):
 
 class VX3BankModel(chirp_common.BankModel):
     """A VX-3 bank model"""
-    def get_num_banks(self):
-        return 24
-
-    def get_banks(self):
-        _banks = self._radio._memobj.bank_names
 
+    def get_num_mappings(self):
+        return len(self.get_mappings())
+
+    def get_mappings(self):
+        banks = self._radio._memobj.banks
+        bank_mappings = []
+        for index, _bank in enumerate(banks):
+            bank = VX3Bank(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) + 1 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("Too many entries in bank %d" % bank.index)
+
+        empty = 0
+        for index, channel_number in enumerate(sorted(channels_in_bank)):
+            _members.channels[index] = channel_number - 1
+            empty = index + 1
+        for index in range(empty, len(_members.channels)):
+            _members.channels[index] = 0xFFFF
+
+    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)
+        _bank_used = self._radio._memobj.bank_used[bank.index]
+        _bank_used.in_use = 0x0000
+
+    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)
+
+        if not channels_in_bank:
+            _bank_used = self._radio._memobj.bank_used[bank.index]
+            _bank_used.in_use = 0xFFFF
+
+    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 i in range(0, self.get_num_banks()):
-            bank = VX3Bank(self, "%i" % i, "Bank-%i" % i)
-            bank.index = i
-            banks.append(bank)
+        for bank in self.get_mappings():
+            if memory.number in self._get_channel_numbers_in_bank(bank):
+                banks.append(bank)
+
         return banks
 
 def _wipe_memory(mem):
@@ -129,7 +334,8 @@ def _wipe_memory(mem):
     #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.unknown7 = 0x01 #this likely is part of autostep
+    mem.unknown7a = 0b0
+    mem.unknown7b = 0b1
     mem.automode = 0x01 #autoselect mode
 
 @directory.register
@@ -143,10 +349,26 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
     _model = "AH028"
     _memsize = 32587
     _block_lengths = [ 10, 32577 ]
-    #right now this reads in 45 seconds and writes in 123 seconds
-    #attempts to speed it up appear unstable, more testing required
-    _block_size = 8
-
+    #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."""))
+        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)."""))
+        return rp
+        
     def _checksums(self):
         return [ yaesu_clone.YaesuChecksum(0x0000, 0x7F49) ]
 
@@ -155,7 +377,7 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
 
     def get_features(self):
         rf = chirp_common.RadioFeatures()
-        rf.has_bank = False
+        rf.has_bank = True
         rf.has_bank_names = True
         rf.has_dtcs_polarity = False
         rf.valid_modes = list(set(MODES))
@@ -167,9 +389,10 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
         rf.valid_power_levels = POWER_LEVELS
         rf.valid_characters = "".join(CHARSET)
         rf.valid_name_length = 6
-        rf.memory_bounds = (1, 900)
+        rf.memory_bounds = (1, 999)
         rf.can_odd_split = True
         rf.has_ctone = False
+        rf.has_settings = True
         return rf
 
     def get_raw_memory(self, number):
@@ -202,7 +425,11 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
         mem.duplex = DUPLEX[_mem.duplex]
         if mem.duplex == "split":
             mem.offset = chirp_common.fix_rounded_step(mem.offset)
-        mem.mode = MODES[_mem.mode]
+        if _mem.txnarrow and _mem.mode == MODES.index("FM"):
+            # FM narrow
+            mem.mode = "NFM"
+        else:        
+            mem.mode = MODES[_mem.mode]
         mem.dtcs = chirp_common.DTCS_CODES[_mem.dcs]
         mem.tuning_step = STEPS[_mem.tune_step]
         mem.skip = pskip and "P" or skip and "S" or ""
@@ -235,12 +462,19 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
         if mem.empty:
             return
 
+        _flag["%s_valid" % nibble] = True
+
         _mem.freq = mem.freq / 1000
         _mem.offset = mem.offset / 1000
         _mem.tone = chirp_common.TONES.index(mem.rtone)
         _mem.tmode = TMODES.index(mem.tmode)
         _mem.duplex = DUPLEX.index(mem.duplex)
-        _mem.mode = MODES.index(mem.mode)
+        if mem.mode == "NFM":
+            _mem.mode = MODES.index("FM")
+            _mem.txnarrow = True
+        else:
+            _mem.mode = MODES.index(mem.mode)
+            _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
@@ -262,3 +496,399 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
 
     def get_bank_model(self):
         return VX3BankModel(self)
+
+    def _decode_chars(self, inarr):
+        if CHIRP_DEBUG:
+            print "@_decode_chars, type: %s" % type(inarr)
+            print 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
+        outarr = []
+        instr = str(instr)
+        for i in range(length):
+            if i < len(instr):
+                outarr.append(CHARSET.index(instr[i]))
+            else:
+                outarr.append(0xFF)
+        return outarr
+        
+    def get_settings(self):
+        _settings = self._memobj.settings
+        basic = RadioSettingGroup("basic", "Basic")
+        sound = RadioSettingGroup("sound", "Sound")
+        dtmf = RadioSettingGroup("dtmf", "DTMF")
+        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",
+                RadioSettingValueBoolean(_settings.att_wx)))
+
+        basic.append( RadioSetting("att_marine", "Attenuation Marine",
+                RadioSettingValueBoolean(_settings.att_marine)))
+
+        basic.append( RadioSetting("att_broadcast", "Attenuation Broadcast",
+                RadioSettingValueBoolean(_settings.att_broadcast)))
+
+        basic.append( RadioSetting("ars", "Auto Repeater Shift",
+                RadioSettingValueBoolean(_settings.ars)))
+        
+        basic.append( RadioSetting("home_vfo", "Home->VFO",
+                RadioSettingValueBoolean(_settings.home_vfo)))
+ 
+        basic.append( RadioSetting("bclo", "Busy Channel Lockout",
+                RadioSettingValueBoolean(_settings.bclo)))
+
+        basic.append( RadioSetting("busyled", "Busy LED",
+                RadioSettingValueBoolean(_settings.busy_led)))
+
+        basic.append( RadioSetting("fast_tone_search", "Fast Tone search",
+                RadioSettingValueBoolean(_settings.fast_tone_search)))
+
+        basic.append( RadioSetting("priority_revert", "Priority Revert",
+                RadioSettingValueBoolean(_settings.priority_revert)))
+
+        basic.append( RadioSetting("protect_memory", "Protect memory",
+                RadioSettingValueBoolean(_settings.protect_memory)))
+
+        basic.append( RadioSetting("scan_lamp", "Scan Lamp",
+                RadioSettingValueBoolean(_settings.scan_lamp)))
+
+        basic.append( RadioSetting("split_tone", "Split tone",
+                RadioSettingValueBoolean(_settings.split_tone)))
+
+        basic.append( RadioSetting("tone_search_mute", "Tone search mute",
+                RadioSettingValueBoolean(_settings.tone_search_mute)))
+
+        basic.append( RadioSetting("txsave", "TX save",
+                RadioSettingValueBoolean(_settings.txsave)))
+
+        basic.append( RadioSetting("wx_alert", "WX Alert",
+                RadioSettingValueBoolean(_settings.wx_alert)))
+
+        opts = ["Bar Int", "Bar Ext"]
+        basic.append( RadioSetting("am_antenna", "AM antenna",
+                RadioSettingValueList(opts, opts[_settings.am_antenna])))
+
+        opts = ["Ext Ant", "Earphone"]
+        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)",
+                RadioSettingValueList(opts, opts[_settings.apo])))
+        
+        opts = ["+/- 5 MHZ", "+/- 10 MHZ", "+/- 50 MHZ", "+/- 100 MHZ"]
+        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)",
+                RadioSettingValueList(opts, opts[_settings.fw_key_timer])))
+
+        opts = ["Home", "Reverse"]
+        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)",
+                RadioSettingValueList(opts, opts[_settings.lamp])))
+
+        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])))        
+
+        opts = ["10", "20", "50", "100"]
+        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",
+                RadioSettingValueList(opts, opts[_settings.mem_storage])))
+
+        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",
+                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)",
+                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",
+                RadioSettingValueList(opts2, opts2[_settings.priority_time])))
+                          
+        opts = ["off", "20", "50", "100", "200"]
+        basic.append( RadioSetting("ptt_delay", "PTT delay (ms)",
+                RadioSettingValueList(opts, opts[_settings.ptt_delay])))
+
+        basic.append( RadioSetting("rx_save", "RX save (s)",
+                RadioSettingValueList(opts2, opts2[_settings.rx_save])))
+
+        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)",
+                RadioSettingValueList(opts, opts[_settings.scan_resume])))
+
+        opts = ["single", "continuous"]
+        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",
+                RadioSettingValueList(opts, opts[_settings.sub_rx_timer])))
+
+        opts = ["C", "F"]
+        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)",
+                RadioSettingValueList(opts, opts[_settings.tot])))
+
+        opts = ["all", "band"]
+        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)",
+                RadioSettingValueList(opts, opts[_settings.wake_up])))
+
+        opts = ["hold", "3 secs"]
+        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",
+                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",
+                RadioSettingValueList(keys, keys[_settings.my_key - 16]))
+        # TODO: fix keys list isnt exactly right order
+        # leave disabled in settings for now
+        # basic.append(rs)
+
+        # sound tab
+
+        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",
+                RadioSettingValueList(opts, opts[_settings.beep_mode])))
+
+        _volumes = self._memobj.volumes
+
+        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])))    
+
+        opts = ["auto", "speaker"]
+        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])))
+
+        opts = map(str, range(16))
+        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)",
+                RadioSettingValueList(opts, opts[_settings.sql_wfm])))
+
+        opts = map(str, range(16))
+        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])))                        
+
+        # dtmf tab
+
+        opts = ["manual", "auto"]
+        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)",
+                RadioSettingValueList(opts, opts[_settings.dtmf_delay])))
+
+        opts = ["50", "100"]
+        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])))
+
+        for i in range(10):
+            name = "dtmf" + str(i)
+            dtmfsetting = self._memobj.dtmf[i]
+            dtmfstr = ""
+            for c in dtmfsetting.memory:
+                if c < len(DTMFCHARSET):
+                    dtmfstr += DTMFCHARSET[c]
+            if CHIRP_DEBUG:
+                print dtmfstr
+            dtmfentry = RadioSettingValueString(0, 16, dtmfstr)
+            dtmfentry.set_charset(DTMFCHARSET + list(" "))
+            rs = RadioSetting(name, name.upper(), dtmfentry)
+            dtmf.append(rs) 
+
+        # arts tab
+        arts.append( RadioSetting("arts", "ARTS",
+                RadioSettingValueBoolean(_settings.arts)))           
+
+        opts = ["off", "in range", "always"]
+        arts.append( RadioSetting("arts_beep", "ARTS beep",
+                RadioSettingValueList(opts, opts[_settings.arts_beep])))
+
+        opts = ["15", "25"]
+        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)))           
+
+        cwid = RadioSettingValueString(0, 16, 
+                self._decode_chars(_settings.arts_cwid.get_value()))
+        cwid.set_charset(CHARSET)
+        arts.append( RadioSetting("arts_cwid", "CW ID", cwid ))
+
+        # EAI tab
+        
+        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) ]
+
+        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]))) 
+
+        # msg tab
+
+        opts = ["off", "dc voltage", "message"]
+        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.set_charset(CHARSET)
+        msg.append( RadioSetting("openmsg", "Opening Message", openmsg ))              
+        
+        return top
+
+    def set_settings(self, uisettings):
+        for element in uisettings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+            if not element.changed():
+                continue
+            try:
+                setting = element.get_name()
+                _settings = self._memobj.settings
+                if re.match('dtmf\d', setting):
+                    # set dtmf fields
+                    dtmfstr = str(element.value).strip()
+                    newval = []
+                    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
+                    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)
+                    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
+                    setattr(_settings, "my_key", idx)
+                    continue
+                oldval = getattr(_settings, setting)
+                newval = element.value
+                if setting == "arts_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)
+                setattr(_settings, setting, newval)
+            except Exception, e:
+                print element.get_name()
+                raise
+        
\ No newline at end of file
diff --git a/chirp/vx5.py b/chirp/vx5.py
index e532146..65a5ba4 100644
--- a/chirp/vx5.py
+++ b/chirp/vx5.py
@@ -14,7 +14,7 @@
 # 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 chirp_common, yaesu_clone, directory, errors
 from chirp import bitwise
 
 MEM_FORMAT = """
@@ -82,18 +82,18 @@ POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00),
                 chirp_common.PowerLevel("L1", watts=0.05)]
 
 class VX5BankModel(chirp_common.BankModel):
-    def get_num_banks(self):
+    def get_num_mappings(self):
         return 5
 
-    def get_banks(self):
+    def get_mappings(self):
         banks = []
-        for i in range(0, self.get_num_banks()):
+        for i in range(0, self.get_num_mappings()):
             bank = chirp_common.Bank(self, "%i" % (i+1), "MG%i" % (i+1))
             bank.index = i
             banks.append(bank)
         return banks
 
-    def add_memory_to_bank(self, memory, bank):
+    def add_memory_to_mapping(self, memory, bank):
         _members = self._radio._memobj.bank_groups[bank.index].members
         _bank_used = self._radio._memobj.bank_used[bank.index]
         for i in range(0, len(_members)):
@@ -107,7 +107,7 @@ class VX5BankModel(chirp_common.BankModel):
                 return True
         raise Exception(_("{bank} is full").format(bank=bank))
 
-    def remove_memory_from_bank(self, memory, bank):
+    def remove_memory_from_mapping(self, memory, bank):
         _members = self._radio._memobj.bank_groups[bank.index].members
         _bank_used = self._radio._memobj.bank_used[bank.index]
 
@@ -128,7 +128,7 @@ class VX5BankModel(chirp_common.BankModel):
         if not remaining_members:
             _bank_used.current_member = 0xFF
 
-    def get_bank_memories(self, bank):
+    def get_mapping_memories(self, bank):
         memories = []
 
         _members = self._radio._memobj.bank_groups[bank.index].members
@@ -143,10 +143,11 @@ class VX5BankModel(chirp_common.BankModel):
             memories.append(self._radio.get_memory(member.channel+1))
         return memories
 
-    def get_memory_banks(self, memory):
+    def get_memory_mappings(self, memory):
         banks = []
-        for bank in self.get_banks():
-            if memory.number in [x.number for x in self.get_bank_memories(bank)]:
+        for bank in self.get_mappings():
+            if memory.number in [x.number for x in
+                                 self.get_mapping_memories(bank)]:
                     banks.append(bank)
         return banks
 
@@ -215,7 +216,7 @@ class VX5Radio(yaesu_clone.YaesuCloneModeRadio):
         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.TONES[_mem.tone]
+        mem.rtone = mem.ctone = chirp_common.OLD_TONES[_mem.tone]
         mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
 
         mem.skip = _flg.pskip and "P" or _flg.skip and "S" or ""
@@ -262,7 +263,11 @@ class VX5Radio(yaesu_clone.YaesuCloneModeRadio):
         else:
             _mem.power = 0
         _mem.tmode = TMODES.index(mem.tmode)
-        _mem.tone = chirp_common.TONES.index(mem.rtone)
+        try:
+            _mem.tone = chirp_common.OLD_TONES.index(mem.rtone)
+        except ValueError:
+            raise errors.UnsupportedToneError(
+                ("This radio does not support tone %s" % mem.rtone))
         _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
 
         _flg.skip = mem.skip == "S"
diff --git a/chirp/vx510.py b/chirp/vx510.py
new file mode 100644
index 0000000..7f287a9
--- /dev/null
+++ b/chirp/vx510.py
@@ -0,0 +1,201 @@
+# 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/>.
+
+from chirp import chirp_common, yaesu_clone, directory
+from chirp import bitwise
+
+# This driver is unfinished and therefore does not register itself with Chirp.
+#
+# Downloads should work consistently but an upload has not been attempted.
+#
+# There is a stray byte at 0xC in the radio download that is not present in the
+# save file from the CE-21 software. This puts the first few bytes of channel 1
+# in the wrong location. A quick hack to fix the subsequent channels was to
+# insert the #seekto dynamically, but this does nothing to fix reading of
+# channel 1 (memory[0]).
+MEM_FORMAT = """
+u8 unknown1[6];
+u8 prioritych;
+
+#seekto %d;
+struct {
+  u8 empty:1,
+     txinhibit:1,
+     tot:1,
+     low_power:1,
+     bclo:1,
+     btlo:1,
+     skip:1,
+     pwrsave:1;
+  u8 unknown2:5,
+     narrow:1,
+     unknown2b:2;
+  u24 name;
+  u8 ctone;
+  u8 rtone;
+  u8 unknown3;
+  bbcd freq_rx[3];
+  bbcd freq_tx[3];
+} memory[32];
+
+char imgname[10];
+"""
+
+STEPS = [5.0, 6.25]
+CHARSET = "".join([chr(x) for x in range(ord("0"), ord("9")+1)] +
+                  [chr(x) for x in range(ord("A"), ord("Z")+1)]) + "<=>*+-\/_ "
+TONES = list(chirp_common.TONES)
+TONES.remove(165.5)
+TONES.remove(171.3)
+TONES.remove(177.3)
+POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00),
+                chirp_common.PowerLevel("Low", watts=1.0)]
+
+
+# @directory.register
+class VX510Radio(yaesu_clone.YaesuCloneModeRadio):
+    """Vertex VX-510V"""
+    BAUD_RATE = 9600
+    VENDOR = "Vertex Standard"
+    MODEL = "VX-510V"
+
+    _model = ""
+    _memsize = 470
+    _block_lengths = [10, 460]
+    _block_size = 8
+
+    def _checksums(self):
+        return []
+        # These checksums don't pass, so the alg might be different than Yaesu.
+        # return [yaesu_clone.YaesuChecksum(0, self._memsize - 2)]
+        # return [yaesu_clone.YaesuChecksum(0, 10),
+        #         yaesu_clone.YaesuChecksum(12, self._memsize - 1)]
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.can_odd_split = True
+        rf.has_bank = False
+        rf.has_ctone = True
+        rf.has_cross = True
+        rf.has_rx_dtcs = True
+        rf.has_dtcs_polarity = False
+        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
+        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
+                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
+        rf.valid_modes = ["FM", "NFM"]
+        rf.valid_duplexes = ["", "-", "+", "split", "off"]
+        rf.memory_bounds = (1, 32)
+        rf.valid_bands = [(13600000, 174000000)]
+        rf.valid_skips = ["", "S"]
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_name_length = 4
+        rf.valid_characters = CHARSET
+        return rf
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(MEM_FORMAT % 0xA, self._mmap)
+
+    def get_raw_memory(self, number):
+        return repr(self._memobj.memory[number-1])
+
+    def get_memory(self, number):
+        mem = chirp_common.Memory()
+        mem.number = number
+
+        _mem = self._memobj.memory[number-1]
+
+        mem.empty = _mem.empty
+        mem.freq = chirp_common.fix_rounded_step(int(_mem.freq_rx) * 1000)
+
+        for i in range(0, 4):
+            index = (_mem.name >> (i*6)) & 0x3F
+            mem.name += CHARSET[index]
+
+        freq_tx = chirp_common.fix_rounded_step(int(_mem.freq_tx) * 1000)
+        if _mem.txinhibit:
+            mem.duplex = "off"
+        elif mem.freq == freq_tx:
+            mem.duplex = ""
+            mem.offset = 0
+        elif 144000000 <= mem.freq < 148000000:
+            mem.duplex = "+" if freq_tx > mem.freq else "-"
+            mem.offset = abs(mem.freq - freq_tx)
+        else:
+            mem.duplex = "split"
+            mem.offset = freq_tx
+
+        mem.mode = _mem.narrow and "NFM" or "FM"
+        mem.power = POWER_LEVELS[_mem.low_power]
+
+        rtone = int(_mem.rtone)
+        ctone = int(_mem.ctone)
+        tmode_tx = tmode_rx = ""
+
+        if rtone & 0x80:
+            tmode_tx = "DTCS"
+            mem.dtcs = chirp_common.DTCS_CODES[int(rtone) - 0x80]
+        elif rtone:
+            tmode_tx = "Tone"
+            mem.rtone = TONES[rtone - 1]
+            if not ctone:
+                # not used, but this is a better default than 88.5
+                mem.ctone = TONES[rtone - 1]
+
+        if ctone & 0x80:
+            tmode_rx = "DTCS"
+            mem.rx_dtcs = chirp_common.DTCS_CODES[int(ctone) - 0x80]
+        elif ctone:
+            tmode_rx = "Tone"
+            mem.ctone = TONES[ctone - 1]
+
+        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:
+            mem.tmode = "TSQL"
+        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"
+            mem.cross_mode = "%s->%s" % (tmode_tx, tmode_rx)
+
+        mem.skip = _mem.skip and "S" or ""
+
+        return mem
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return len(filedata) == cls._memsize
+
+
+# @directory.register
+class VX510File(VX510Radio, chirp_common.FileBackedRadio):
+    """Vertex CE-21 File"""
+    VENDOR = "Vertex Standard"
+    MODEL = "CE-21 File"
+
+    _model = ""
+    _memsize = 664
+
+    def _checksums(self):
+        return [yaesu_clone.YaesuChecksum(0, self._memsize - 1)]
+
+    def process_mmap(self):
+        # CE-21 file is missing the 0xC byte, probably a checksum.
+        # It's not a YaesuChecksum.
+        self._memobj = bitwise.parse(MEM_FORMAT % 0x9, self._mmap)
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return len(filedata) == cls._memsize
diff --git a/chirp/vx6.py b/chirp/vx6.py
index b4764db..0cdb115 100644
--- a/chirp/vx6.py
+++ b/chirp/vx6.py
@@ -15,6 +15,7 @@
 
 from chirp import chirp_common, yaesu_clone, directory
 from chirp import bitwise
+from textwrap import dedent
 
 # flags.{even|odd}_pskip: These are actually "preferential *scan* channels".
 # Is that what they mean on other radios as well?
@@ -37,7 +38,14 @@ from chirp import bitwise
 # }
 MEM_FORMAT = """
 #seekto 0x018A;
-u16 bank_sizes[24];
+struct {
+    u16 in_use;
+} bank_used[24];
+
+#seekto 0x0214;
+u16  banksoff1;
+#seekto 0x0294;
+u16  banksoff2;
 
 #seekto 0x097A;
 struct {
@@ -46,8 +54,8 @@ struct {
 
 #seekto 0x0C0A;
 struct {
-  u16 channel[100];
-} bank_channels[24];
+  u16 channels[100];
+} banks[24];
 
 #seekto 0x1ECA;
 struct {
@@ -59,7 +67,7 @@ struct {
      odd_skip:1,
      odd_valid:1,
      odd_masked:1;
-} flags[450];
+} flags[500];
 
 #seekto 0x21CA;
 struct {
@@ -81,7 +89,7 @@ struct {
   u8 tone;
   u8 dcs;
   u8 unknown5;
-} memory[900];
+} memory[999];
 """
 
 DUPLEX = ["", "-", "+", "split"]
@@ -104,6 +112,98 @@ 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)]
+                
+class VX6Bank(chirp_common.NamedBank):
+    """A VX6 Bank"""
+    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()
+        _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"""
+
+    def get_num_mappings(self):
+        return len(self.get_mappings())
+
+    def get_mappings(self):
+        banks = self._radio._memobj.banks
+        bank_mappings = []
+        for index, _bank in enumerate(banks):
+            bank = VX6Bank(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) + 1 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("Too many entries in bank %d" % bank.index)
+
+        empty = 0
+        for index, channel_number in enumerate(sorted(channels_in_bank)):
+            _members.channels[index] = channel_number - 1
+            empty = index + 1
+        for index in range(empty, len(_members.channels)):
+            _members.channels[index] = 0xFFFF
+
+    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)
+        _bank_used = self._radio._memobj.bank_used[bank.index]
+        _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" % \
+                            (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
+
+    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
 
 @directory.register
 class VX6Radio(yaesu_clone.YaesuCloneModeRadio):
@@ -117,6 +217,23 @@ class VX6Radio(yaesu_clone.YaesuCloneModeRadio):
     _block_lengths = [10, 32578]
     _block_size = 16
 
+    @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."""))
+        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)."""))
+        return rp
+        
     def _checksums(self):
         return [ yaesu_clone.YaesuChecksum(0x0000, 0x7F49) ]
 
@@ -125,14 +242,15 @@ class VX6Radio(yaesu_clone.YaesuCloneModeRadio):
 
     def get_features(self):
         rf = chirp_common.RadioFeatures()
-        rf.has_bank = False
+        rf.has_bank = True
+        rf.has_bank_names = True
         rf.has_dtcs_polarity = False
         rf.valid_modes = ["FM", "WFM", "AM", "NFM"]
         rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
         rf.valid_duplexes = DUPLEX
         rf.valid_tuning_steps = STEPS
         rf.valid_power_levels = POWER_LEVELS
-        rf.memory_bounds = (1, 900)
+        rf.memory_bounds = (1, 999)
         rf.valid_bands = [(500000, 998990000)]
         rf.valid_characters = "".join(CHARSET)
         rf.valid_name_length = 6
@@ -244,28 +362,7 @@ class VX6Radio(yaesu_clone.YaesuCloneModeRadio):
         if mem.name.strip():
             _mem.name[0] |= 0x80
 
-#    def get_banks(self):
-#        _banks = self._memobj.bank_names
-#
-#        banks = []
-#        for bank in _banks:
-#            name = ""
-#            for i in bank.name:
-#                name += CHARSET[i & 0x7F]
-#            banks.append(name.rstrip())
-#
-#        return banks
-#
-#    # Return channels for a bank. Bank given as number
-#    def get_bank_channels(self, bank):
-#        nchannels = 0
-#        size = self._memobj.bank_sizes[bank]
-#        if size <= 198:
-#            nchannels = 1 + size/2
-#        _channels = self._memobj.bank_channels[bank]
-#        channels = []
-#        for i in range(0, nchannels):
-#            channels.append(int(_channels.channel[i]))
-#
-#        return channels
+    def get_bank_model(self):
+        return VX6BankModel(self)
+
 
diff --git a/chirp/vx7.py b/chirp/vx7.py
index d657ddb..5fff4b1 100644
--- a/chirp/vx7.py
+++ b/chirp/vx7.py
@@ -15,6 +15,7 @@
 
 from chirp import chirp_common, yaesu_clone, directory
 from chirp import bitwise
+from textwrap import dedent
 
 MEM_FORMAT = """
 #seekto 0x0611;
@@ -103,18 +104,18 @@ def _is220(freq):
 
 class VX7BankModel(chirp_common.BankModel):
     """A VX-7 Bank model"""
-    def get_num_banks(self):
+    def get_num_mappings(self):
         return 9
 
-    def get_banks(self):
+    def get_mappings(self):
         banks = []
-        for i in range(0, self.get_num_banks()):
+        for i in range(0, self.get_num_mappings()):
             bank = chirp_common.Bank(self, "%i" % (i+1), "MG%i" % (i+1))
             bank.index = i
             banks.append(bank)
         return banks
 
-    def add_memory_to_bank(self, memory, bank):
+    def add_memory_to_mapping(self, memory, bank):
         _members = self._radio._memobj.bank_members[bank.index]
         _bank_used = self._radio._memobj.bank_used[bank.index]
         for i in range(0, 48):
@@ -123,7 +124,7 @@ class VX7BankModel(chirp_common.BankModel):
                 _bank_used.in_use = 0x0000
                 break
 
-    def remove_memory_from_bank(self, memory, bank):
+    def remove_memory_from_mapping(self, memory, bank):
         _members = self._radio._memobj.bank_members[bank.index].members
         _bank_used = self._radio._memobj.bank_used[bank.index]
 
@@ -143,7 +144,7 @@ class VX7BankModel(chirp_common.BankModel):
         if not remaining_members:
             _bank_used.in_use = 0xFFFF
 
-    def get_bank_memories(self, bank):
+    def get_mapping_memories(self, bank):
         memories = []
 
         _members = self._radio._memobj.bank_members[bank.index].members
@@ -158,11 +159,11 @@ class VX7BankModel(chirp_common.BankModel):
             memories.append(self._radio.get_memory(number+1))
         return memories
 
-    def get_memory_banks(self, memory):
+    def get_memory_mappings(self, memory):
         banks = []
-        for bank in self.get_banks():
+        for bank in self.get_mappings():
             if memory.number in [x.number for x in
-                                 self.get_bank_memories(bank)]:
+                                 self.get_mapping_memories(bank)]:
                 banks.append(bank)
         return banks
 
@@ -183,6 +184,23 @@ class VX7Radio(yaesu_clone.YaesuCloneModeRadio):
     _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."""))
+        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)."""))
+        return rp
+        
     def _checksums(self):
         return [ yaesu_clone.YaesuChecksum(0x0592, 0x0610),
                  yaesu_clone.YaesuChecksum(0x0612, 0x0690),
diff --git a/chirp/vx8.py b/chirp/vx8.py
index 67791f5..ceae189 100644
--- a/chirp/vx8.py
+++ b/chirp/vx8.py
@@ -13,8 +13,15 @@
 # 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
+
 from chirp import chirp_common, yaesu_clone, directory
 from chirp import bitwise
+from chirp.settings import RadioSettingGroup, RadioSetting
+from chirp.settings import RadioSettingValueInteger, RadioSettingValueString
+from chirp.settings import RadioSettingValueList, RadioSettingValueBoolean
+from textwrap import dedent
 
 MEM_FORMAT = """
 #seekto 0x54a;
@@ -22,6 +29,50 @@ struct {
     u16 in_use;
 } bank_used[24];
 
+#seekto 0x64a;
+struct {
+  u8 unknown0[4];
+  u8 frequency_band;
+  u8 unknown1:6,
+     manual_or_mr:2;
+  u8 unknown2:7,
+     mr_banks:1;
+  u8 unknown3;
+  u16 mr_index;
+  u16 bank_index;
+  u16 bank_enable;
+  u8 unknown4[5];
+  u8 unknown5:6,
+     power:2;
+  u8 unknown6:4,
+     tune_step:4;
+  u8 unknown7:6,
+     duplex:2;
+  u8 unknown8:6,
+     tone_mode:2;
+  u8 unknown9:2,
+     tone:6;
+  u8 unknown10;
+  u8 unknown11:6,
+     mode:2;
+  bbcd freq0[4];
+  bbcd offset_freq[4];
+  u8 unknown12[2];
+  char label[16];
+  u8 unknown13[6];
+  bbcd band_lower[4];
+  bbcd band_upper[4];
+  bbcd rx_freq[4];
+  u8 unknown14[22];
+  bbcd freq1[4];
+  u8 unknown15[11];
+  u8 unknown16:3,
+     volume:5;
+  u8 unknown17[18];
+  u8 active_menu_item;
+  u8 checksum;
+} vfo_info[6];
+
 #seekto 0x135A;
 struct {
   u8 unknown[2];
@@ -63,6 +114,156 @@ struct {
   u8 unknown7[3];
 } memory[900];
 
+#seekto 0xC0CA;
+struct {
+  u8 unknown0:6,
+     rx_baud:2;
+  u8 unknown1:4,
+     tx_delay:4;
+  u8 custom_symbol;
+  u8 unknown2;
+  struct {
+    char callsign[6];
+    u8 ssid;
+  } my_callsign;
+  u8 unknown3:4,
+     selected_position_comment:4;
+  u8 unknown4;
+  u8 set_time_manually:1,
+     tx_interval_beacon:1,
+     ring_beacon:1,
+     ring_msg:1,
+     aprs_mute:1,
+     unknown6:1,
+     tx_smartbeacon:1,
+     af_dual:1;
+  u8 unknown7:1,
+     aprs_units_wind_mph:1,
+     aprs_units_rain_inch:1,
+     aprs_units_temperature_f:1
+     aprs_units_altitude_ft:1,
+     unknown8:1,
+     aprs_units_distance_m:1,
+     aprs_units_position_mmss:1;
+  u8 unknown9:6,
+     aprs_units_speed:2;
+  u8 unknown11:1,
+     filter_other:1,
+     filter_status:1,
+     filter_item:1,
+     filter_object:1,
+     filter_weather:1,
+     filter_position:1,
+     filter_mic_e:1;
+  u8 unknown12:2,
+     timezone:6;
+  u8 unknown13:4,
+     beacon_interval:4;
+  u8 unknown14;
+  u8 unknown15:7,
+     latitude_sign:1;
+  u8 latitude_degree;
+  u8 latitude_minute;
+  u8 latitude_second;
+  u8 unknown16:7,
+     longitude_sign:1;
+  u8 longitude_degree;
+  u8 longitude_minute;
+  u8 longitude_second;
+  u8 unknown17:4,
+     selected_position:4;
+  u8 unknown18:5,
+     selected_beacon_status_txt:3;
+  u8 unknown19:6,
+     gps_units_altitude_ft:1,
+     gps_units_position_sss:1;
+  u8 unknown20:6,
+     gps_units_speed:2;
+  u8 unknown21[4];
+  struct {
+    struct {
+      char callsign[6];
+      u8 ssid;
+    } entry[8];
+  } digi_path_7;
+  u8 unknown22[2];
+  struct {
+    char padded_string[16];
+  } message_macro[7];
+  u8 unknown23:5,
+     selected_msg_group:3;
+  u8 unknown24;
+  struct {
+    char padded_string[9];
+  } msg_group[8];
+  u8 unknown25[4];
+  u8 active_smartbeaconing;
+  struct {
+    u8 low_speed_mph;
+    u8 high_speed_mph;
+    u8 slow_rate_min;
+    u8 fast_rate_sec;
+    u8 turn_angle;
+    u8 turn_slop;
+    u8 turn_time_sec;
+  } smartbeaconing_profile[3];
+  u8 unknown26:2,
+     flash_msg:6;
+  u8 unknown27:2,
+     flash_grp:6;
+  u8 unknown28:2,
+     flash_bln:6;
+  u8 selected_digi_path;
+  struct {
+    struct {
+      char callsign[6];
+      u8 ssid;
+    } entry[2];
+  } digi_path_3_6[4];
+  u8 unknown30:6,
+     selected_my_symbol:2;
+  u8 unknown31[3];
+  u8 unknown32:2,
+     vibrate_msg:6;
+  u8 unknown33:2,
+     vibrate_grp:6;
+  u8 unknown34:2,
+     vibrate_bln:6;
+} aprs;
+
+#seekto 0x%04X;
+struct {
+  bbcd date[3];
+  u8 unknown1;
+  bbcd time[2];
+  u8 sequence;
+  u8 unknown2;
+  u8 sender_callsign[7];
+  u8 data_type;
+  u8 yeasu_data_type;
+  u8 unknown3;
+  u8 unknown4:1,
+     callsign_is_ascii:1,
+     unknown5:6;
+  u8 unknown6;
+  u16 pkt_len;
+  u16 in_use;
+} 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];
+} aprs_beacon_pkt[%d];
+
+#seekto 0xf92a;
+struct {
+  char padded_string[60];
+} aprs_beacon_status_txt[5];
+
 #seekto 0xFECA;
 u8 checksum;
 """
@@ -73,7 +274,7 @@ 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 ad index 2 (?)
+STEPS.insert(2, 0.0) # There is a skipped tuning step at index 2 (?)
 SKIPS = ["", "S", "P"]
 
 CHARSET = ["%i" % int(x) for x in range(0, 10)] + \
@@ -107,70 +308,120 @@ class VX8Bank(chirp_common.NamedBank):
 
 class VX8BankModel(chirp_common.BankModel):
     """A VX-8 bank model"""
-    def get_num_banks(self):
-        return 24
+    def __init__(self, radio, name='Banks'):
+        super(VX8BankModel, self).__init__(radio, name)
 
-    def get_banks(self):
-        banks = []
         _banks = self._radio._memobj.bank_info
-
-        index = 0
-        for _bank in _banks:
+        self._bank_mappings = []
+        for index, _bank in enumerate(_banks):
             bank = VX8Bank(self, "%i" % index, "BANK-%i" % index)
             bank.index = index
-            banks.append(bank)
-            index += 1
+            self._bank_mappings.append(bank)
 
-        return banks
+    def get_num_mappings(self):
+        return len(self._bank_mappings)
 
-    def add_memory_to_bank(self, memory, bank):
-        _members = self._radio._memobj.bank_members[bank.index]
+    def get_mappings(self):
+        return self._bank_mappings
+
+    def _channel_numbers_in_bank(self, bank):
         _bank_used = self._radio._memobj.bank_used[bank.index]
-        for i in range(0, 100):
-            if _members.channel[i] == 0xFFFF:
-                _members.channel[i] = memory.number - 1
-                _bank_used.in_use = 0x06
+        if _bank_used.in_use == 0xFFFF:
+            return set()
+
+        _members = self._radio._memobj.bank_members[bank.index]
+        return set([int(ch) + 1 for ch in _members.channel if ch != 0xFFFF])
+
+    def update_vfo(self):
+        chosen_bank = [None, None]
+        chosen_mr = [None, None]
+
+        flags = self._radio._memobj.flag
+
+        # Find a suitable bank and MR for VFO A and B.
+        for bank in self.get_mappings():
+            for channel in self._channel_numbers_in_bank(bank):
+                chosen_bank[0] = bank.index
+                chosen_mr[0] = channel
+                if not flags[channel].nosubvfo:
+                    chosen_bank[1] = bank.index
+                    chosen_mr[1] = channel
+                    break
+            if chosen_bank[1]:
                 break
 
-    def remove_memory_from_bank(self, memory, bank):
+        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
+
+    def _update_bank_with_channel_numbers(self, bank, channels_in_bank):
         _members = self._radio._memobj.bank_members[bank.index]
+        if len(channels_in_bank) > len(_members.channel):
+            raise Exception("Too many entries in bank %d" % bank.index)
+
+        empty = 0
+        for index, channel_number in enumerate(sorted(channels_in_bank)):
+            _members.channel[index] = channel_number - 1
+            empty = index + 1
+        for index in range(empty, len(_members.channel)):
+            _members.channel[index] = 0xFFFF
+
+    def add_memory_to_mapping(self, memory, bank):
+        channels_in_bank = self._channel_numbers_in_bank(bank)
+        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 = 0x06
 
-        remaining_members = 0
-        found = False
-        for i in range(0, len(_members.channel)):
-            if _members.channel[i] == (memory.number - 1):
-                _members.channel[i] = 0xFFFF
-                found = True
-            elif _members.channel[i] != 0xFFFF:
-                remaining_members += 1
+        self.update_vfo()
 
-        if not found:
+    def remove_memory_from_mapping(self, memory, bank):
+        channels_in_bank = self._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))
+                            (memory.number, bank))
+        self._update_bank_with_channel_numbers(bank, channels_in_bank)
 
-        if not remaining_members:
+        if not channels_in_bank:
+            _bank_used = self._radio._memobj.bank_used[bank.index]
             _bank_used.in_use = 0xFFFF
 
-    def get_bank_memories(self, bank):
-        memories = []
-        _members = self._radio._memobj.bank_members[bank.index]
-        _bank_used = self._radio._memobj.bank_used[bank.index]
-
-        if _bank_used.in_use == 0xFFFF:
-            return memories
+        self.update_vfo()
 
-        for channel in _members.channel:
-            if channel != 0xFFFF:
-                memories.append(self._radio.get_memory(int(channel)+1))
+    def get_mapping_memories(self, bank):
+        memories = []
+        for channel in self._channel_numbers_in_bank(bank):
+            memories.append(self._radio.get_memory(channel))
 
         return memories
 
-    def get_memory_banks(self, memory):
+    def get_memory_mappings(self, memory):
         banks = []
-        for bank in self.get_banks():
-            if memory.number in \
-                    [x.number for x in self.get_bank_memories(bank)]:
+        for bank in self.get_mappings():
+            if memory.number in self._channel_numbers_in_bank(bank):
                 banks.append(bank)
 
         return banks
@@ -191,9 +442,33 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
     _memsize = 65227
     _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.
+    _has_vibrate = False
+    _has_af_dual = True
 
+    @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
+                 ("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)."""))
+        return rp
+        
     def process_mmap(self):
-        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
 
     def get_features(self):
         rf = chirp_common.RadioFeatures()
@@ -217,7 +492,22 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
         return repr(self._memobj.memory[number])
 
     def _checksums(self):
-        return [ 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):
+        return val.ljust(length, "\xFF")[:length]
+
+    @classmethod
+    def _strip_ff_pads(cls, messages):
+        result = []
+        for msg_text in messages:
+            result.append(str(msg_text).rstrip("\xFF"))
+        return result
 
     def get_memory(self, number):
         flag = self._memobj.flag[number-1]
@@ -243,22 +533,22 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
         mem.power = POWER_LEVELS[3 - _mem.power]
         mem.skip = flag.pskip and "P" or flag.skip and "S" or ""
 
-        for i in str(_mem.label):
-            if i == "\xFF":
-                break
-            mem.name += CHARSET[ord(i)]
+        charset = ''.join(CHARSET).ljust(256, '.')
+        mem.name = str(_mem.label).rstrip("\xFF").translate(charset)
 
         return mem
 
     def _debank(self, mem):
         bm = self.get_bank_model()
-        for bank in bm.get_memory_banks(mem):
-            bm.remove_memory_from_bank(mem, bank)
+        for bank in bm.get_memory_mappings(mem):
+            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]
 
+        self._debank(mem)
+
         if not mem.empty and not flag.valid:
             _wipe_memory(_mem)
 
@@ -292,7 +582,7 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
             _mem.power = 0
 
         label = "".join([chr(CHARSET.index(x)) for x in mem.name.rstrip()])
-        _mem.label = label.ljust(16, "\xFF")
+        _mem.label = self._add_ff_pad(label, 16)
         # We only speak english here in chirpville
         _mem.charsetbits[0] = 0x00
         _mem.charsetbits[1] = 0x00
@@ -307,4 +597,685 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
 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
+
+    @classmethod
+    def _digi_path_to_str(cls, path):
+        path_cmp = []
+        for entry in path.entry:
+            callsign = str(entry.callsign).rstrip("\xFF")
+            if not callsign:
+                break
+            path_cmp.append("%s-%d" % (callsign, entry.ssid))
+        return ",".join(path_cmp)
+
+    @staticmethod
+    def _latlong_sanity(sign, l_d, l_m, l_s, is_lat):
+        if sign not in (0, 1):
+            sign = 0
+        if is_lat:
+            d_max = 90
+        else:
+            d_max = 180
+        if l_d < 0 or l_d > d_max:
+            l_d = 0
+            l_m = 0
+            l_s = 0
+        if l_m < 0 or l_m > 60:
+            l_m = 0
+            l_s = 0
+        if l_s < 0 or l_s > 60:
+            l_s = 0
+        return sign, l_d, l_m, l_s
+
+    @classmethod
+    def _latlong_to_str(cls, sign, l_d, l_m, l_s, is_lat, to_sexigesimal=True):
+        sign, l_d, l_m, l_s = cls._latlong_sanity(sign, l_d, l_m, l_s, is_lat)
+        mult = sign and -1 or 1
+        if to_sexigesimal:
+            return "%d,%d'%d\"" % (mult * l_d, l_m, l_s)
+        return "%0.5f" % (mult * l_d + (l_m / 60.0) + (l_s / (60.0 * 60.0)))
+
+    @classmethod
+    def _str_to_latlong(cls, lat_long, is_lat):
+        sign = 0
+        result = [0, 0, 0]
+
+        lat_long = lat_long.strip()
+
+        if not lat_long:
+            return 1, 0, 0, 0
+
+        try:
+            # DD.MMMMM is the simple case, try that first.
+            val = float(lat_long)
+            if val < 0:
+                sign = 1
+            val = abs(val)
+            result[0] = int(val)
+            result[1] = int(val * 60) % 60
+            result[2] = int(val * 3600) % 60
+        except ValueError:
+            # Try DD MM'SS" if DD.MMMMM failed.
+            match = cls._SG_RE.match(lat_long.strip())
+            if match:
+                if match.group("sign") and (match.group("sign") in "SE-"):
+                    sign = 1
+                else:
+                    sign = 0
+                if match.group("d"):
+                    result[0] = int(match.group("d"))
+                if match.group("m"):
+                    result[1] = int(match.group("m"))
+                if match.group("s"):
+                    result[2] = int(match.group("s"))
+            elif len(lat_long) > 4:
+                raise Exception("Lat/Long should be DD MM'SS\" or DD.MMMMM")
+
+        return cls._latlong_sanity(sign, result[0], result[1], result[2],
+                                   is_lat)
+
+    def _get_aprs_general_settings(self):
+        menu = RadioSettingGroup("aprs_general", "APRS General")
+        aprs = self._memobj.aprs
+
+        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)
+
+        val = RadioSettingValueList(
+            chirp_common.APRS_SSID,
+            chirp_common.APRS_SSID[aprs.my_callsign.ssid])
+        rs = RadioSetting("aprs.my_callsign.ssid", "My SSID", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(self._MY_SYMBOL,
+                                    self._MY_SYMBOL[aprs.selected_my_symbol])
+        rs = RadioSetting("aprs.selected_my_symbol", "My Symbol", val)
+        menu.append(rs)
+
+        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
+        val = RadioSettingValueList(symbols, symbols[selected])
+        rs = RadioSetting("aprs.custom_symbol_text", "User Selected Symbol",
+                          val)
+        rs.set_apply_callback(self.apply_custom_symbol, aprs)
+        menu.append(rs)
+
+        val = RadioSettingValueList(
+            chirp_common.APRS_POSITION_COMMENT,
+            chirp_common.APRS_POSITION_COMMENT[aprs.selected_position_comment])
+        rs = RadioSetting("aprs.selected_position_comment", "Position Comment",
+                          val)
+        menu.append(rs)
+
+        latitude = self._latlong_to_str(aprs.latitude_sign,
+                                        aprs.latitude_degree,
+                                        aprs.latitude_minute,
+                                        aprs.latitude_second,
+                                        True, aprs.aprs_units_position_mmss)
+        longitude = self._latlong_to_str(aprs.longitude_sign,
+                                         aprs.longitude_degree,
+                                         aprs.longitude_minute,
+                                         aprs.longitude_second,
+                                         False, aprs.aprs_units_position_mmss)
+
+        # TODO: Rebuild this when aprs_units_position_mmss changes.
+        # 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)
+        val = RadioSettingValueList(position_str,
+                                    position_str[aprs.selected_position])
+        rs = RadioSetting("aprs.selected_position", "My Position", val)
+        menu.append(rs)
+
+        val = RadioSettingValueString(0, 10, latitude)
+        rs = RadioSetting("latitude", "Manual Latitude", val)
+        rs.set_apply_callback(self.apply_lat_long, aprs)
+        menu.append(rs)
+
+        val = RadioSettingValueString(0, 11, longitude)
+        rs = RadioSetting("longitude", "Manual Longitude", val)
+        rs.set_apply_callback(self.apply_lat_long, aprs)
+        menu.append(rs)
+
+        val = RadioSettingValueList(self._TIME_SOURCE,
+            self._TIME_SOURCE[aprs.set_time_manually])
+        rs = RadioSetting("aprs.set_time_manually", "Time Source", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(self._TZ, self._TZ[aprs.timezone])
+        rs = RadioSetting("aprs.timezone", "Timezone", val)
+        menu.append(rs)
+
+        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])
+        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])
+        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])
+        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])
+        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])
+        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])
+        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])
+        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])
+        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])
+        rs = RadioSetting("aprs.aprs_units_temperature_f",
+                          "APRS Temperature Units", val)
+        menu.append(rs)
+
+        return menu
+
+    def _get_aprs_rx_settings(self):
+        menu = RadioSettingGroup("aprs_rx", "APRS Receive")
+        aprs = self._memobj.aprs
+
+        val = RadioSettingValueList(self._RX_BAUD, self._RX_BAUD[aprs.rx_baud])
+        rs = RadioSetting("aprs.rx_baud", "Modem RX", val)
+        menu.append(rs)
+
+        val = RadioSettingValueBoolean(aprs.aprs_mute)
+        rs = RadioSetting("aprs.aprs_mute", "APRS Mute", val)
+        menu.append(rs)
+
+        if self._has_af_dual:
+            val = RadioSettingValueBoolean(aprs.af_dual)
+            rs = RadioSetting("aprs.af_dual", "AF Dual", val)
+            menu.append(rs)
+
+        val = RadioSettingValueBoolean(aprs.ring_msg)
+        rs = RadioSetting("aprs.ring_msg", "Ring on Message RX", val)
+        menu.append(rs)
+
+        val = RadioSettingValueBoolean(aprs.ring_beacon)
+        rs = RadioSetting("aprs.ring_beacon", "Ring on Beacon RX", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(self._FLASH,
+                                    self._FLASH[aprs.flash_msg])
+        rs = RadioSetting("aprs.flash_msg", "Flash on personal message", val)
+        menu.append(rs)
+
+        if self._has_vibrate:
+            val = RadioSettingValueList(self._FLASH,
+                                        self._FLASH[aprs.vibrate_msg])
+            rs = RadioSetting("aprs.vibrate_msg",
+                              "Vibrate on personal message", val)
+            menu.append(rs)
+
+        val = RadioSettingValueList(self._FLASH[:10],
+                                    self._FLASH[aprs.flash_bln])
+        rs = RadioSetting("aprs.flash_bln", "Flash on bulletin message", val)
+        menu.append(rs)
+
+        if self._has_vibrate:
+            val = RadioSettingValueList(self._FLASH[:10],
+                                        self._FLASH[aprs.vibrate_bln])
+            rs = RadioSetting("aprs.vibrate_bln",
+                              "Vibrate on bulletin message", val)
+            menu.append(rs)
+
+        val = RadioSettingValueList(self._FLASH[:10],
+                                    self._FLASH[aprs.flash_grp])
+        rs = RadioSetting("aprs.flash_grp", "Flash on group message", val)
+        menu.append(rs)
+
+        if self._has_vibrate:
+            val = RadioSettingValueList(self._FLASH[:10],
+                                        self._FLASH[aprs.vibrate_grp])
+            rs = RadioSetting("aprs.vibrate_grp",
+                              "Vibrate on group message", val)
+            menu.append(rs)
+
+        filter_val = [m.padded_string for m in aprs.msg_group]
+        filter_val = self._strip_ff_pads(filter_val)
+        for index, filter_text in enumerate(filter_val):
+            val = RadioSettingValueString(0, 9, filter_text)
+            rs = RadioSetting("aprs.msg_group_%d" % index,
+                              "Message Group %d" % (index + 1), val)
+            menu.append(rs)
+            rs.set_apply_callback(self.apply_ff_padded_string,
+                                  aprs.msg_group[index])
+        # TODO: Use filter_val as the list entries and update it on edit.
+        val = RadioSettingValueList(
+            self._MSG_GROUP_NAMES,
+            self._MSG_GROUP_NAMES[aprs.selected_msg_group])
+        rs = RadioSetting("aprs.selected_msg_group", "Selected Message Group",
+                          val)
+        menu.append(rs)
+
+        val = RadioSettingValueBoolean(aprs.filter_mic_e)
+        rs = RadioSetting("aprs.filter_mic_e", "Receive Mic-E Beacons", val)
+        menu.append(rs)
+
+        val = RadioSettingValueBoolean(aprs.filter_position)
+        rs = RadioSetting("aprs.filter_position", "Receive Position Beacons",
+                          val)
+        menu.append(rs)
+
+        val = RadioSettingValueBoolean(aprs.filter_weather)
+        rs = RadioSetting("aprs.filter_weather", "Receive Weather Beacons",
+                          val)
+        menu.append(rs)
+
+        val = RadioSettingValueBoolean(aprs.filter_object)
+        rs = RadioSetting("aprs.filter_object", "Receive Object Beacons", val)
+        menu.append(rs)
+
+        val = RadioSettingValueBoolean(aprs.filter_item)
+        rs = RadioSetting("aprs.filter_item", "Receive Item Beacons", val)
+        menu.append(rs)
+
+        val = RadioSettingValueBoolean(aprs.filter_status)
+        rs = RadioSetting("aprs.filter_status", "Receive Status Beacons", val)
+        menu.append(rs)
+
+        val = RadioSettingValueBoolean(aprs.filter_other)
+        rs = RadioSetting("aprs.filter_other", "Receive Other Beacons", val)
+        menu.append(rs)
+
+        return menu
+
+    def _get_aprs_tx_settings(self):
+        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])
+        rs = RadioSetting("aprs.transmit", "TX Beacons", val)
+        rs.set_apply_callback(self.apply_beacon_type, aprs)
+        menu.append(rs)
+
+        val = RadioSettingValueList(self._TX_DELAY,
+                                    self._TX_DELAY[aprs.tx_delay])
+        rs = RadioSetting("aprs.tx_delay", "TX Delay", val)
+        menu.append(rs)
+
+        val = RadioSettingValueList(self._BEACON_INT,
+                                    self._BEACON_INT[aprs.beacon_interval])
+        rs = RadioSetting("aprs.beacon_interval", "Beacon Interval", val)
+        menu.append(rs)
+
+        desc = []
+        status = [m.padded_string for m in self._memobj.aprs_beacon_status_txt]
+        status = self._strip_ff_pads(status)
+        for index, msg_text in enumerate(status):
+            val = RadioSettingValueString(0, 60, msg_text)
+            desc.append("Beacon Status Text %d" % (index + 1))
+            rs = RadioSetting("aprs_beacon_status_txt_%d" % index, desc[-1],
+                              val)
+            rs.set_apply_callback(self.apply_ff_padded_string,
+                                  self._memobj.aprs_beacon_status_txt[index])
+            menu.append(rs)
+        val = RadioSettingValueList(desc,
+                                    desc[aprs.selected_beacon_status_txt])
+        rs = RadioSetting("aprs.selected_beacon_status_txt",
+                          "Beacon Status Text", val)
+        menu.append(rs)
+
+        message_macro = [m.padded_string for m in aprs.message_macro]
+        message_macro = self._strip_ff_pads(message_macro)
+        for index, msg_text in enumerate(message_macro):
+            val = RadioSettingValueString(0, 16, msg_text)
+            rs = RadioSetting("aprs.message_macro_%d" % index,
+                              "Message Macro %d" % (index + 1), val)
+            rs.set_apply_callback(self.apply_ff_padded_string,
+                                  aprs.message_macro[index])
+            menu.append(rs)
+
+        path_str = list(self._DIGI_PATHS)
+        path_str[3] = self._digi_path_to_str(aprs.digi_path_3_6[0])
+        val = RadioSettingValueString(0, 22, path_str[3])
+        rs = RadioSetting("aprs.digi_path_3", "Digi Path 4 (2 entries)", val)
+        rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[0])
+        menu.append(rs)
+
+        path_str[4] = self._digi_path_to_str(aprs.digi_path_3_6[1])
+        val = RadioSettingValueString(0, 22, path_str[4])
+        rs = RadioSetting("aprs.digi_path_4", "Digi Path 5 (2 entries)", val)
+        rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[1])
+        menu.append(rs)
+
+        path_str[5] = self._digi_path_to_str(aprs.digi_path_3_6[2])
+        val = RadioSettingValueString(0, 22, path_str[5])
+        rs = RadioSetting("aprs.digi_path_5", "Digi Path 6 (2 entries)", val)
+        rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[2])
+        menu.append(rs)
+
+        path_str[6] = self._digi_path_to_str(aprs.digi_path_3_6[3])
+        val = RadioSettingValueString(0, 22, path_str[6])
+        rs = RadioSetting("aprs.digi_path_6", "Digi Path 7 (2 entries)", val)
+        rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[3])
+        menu.append(rs)
+
+        path_str[7] = self._digi_path_to_str(aprs.digi_path_7)
+        val = RadioSettingValueString(0, 88, path_str[7])
+        rs = RadioSetting("aprs.digi_path_7", "Digi Path 8 (8 entries)", val)
+        rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_7)
+        menu.append(rs)
+
+        # Show friendly messages for empty slots rather than blanks.
+        # TODO: Rebuild this when digi_path_[34567] change.
+        #path_str[3] = path_str[3] or self._DIGI_PATHS[3]
+        #path_str[4] = path_str[4] or self._DIGI_PATHS[4]
+        #path_str[5] = path_str[5] or self._DIGI_PATHS[5]
+        #path_str[6] = path_str[6] or self._DIGI_PATHS[6]
+        #path_str[7] = path_str[7] or self._DIGI_PATHS[7]
+        path_str[3] = self._DIGI_PATHS[3]
+        path_str[4] = self._DIGI_PATHS[4]
+        path_str[5] = self._DIGI_PATHS[5]
+        path_str[6] = self._DIGI_PATHS[6]
+        path_str[7] = self._DIGI_PATHS[7]
+        val = RadioSettingValueList(path_str,
+                                    path_str[aprs.selected_digi_path])
+        rs = RadioSetting("aprs.selected_digi_path", "Selected Digi Path", val)
+        menu.append(rs)
+
+        return menu
+
+    def _get_aprs_smartbeacon(self):
+        menu = RadioSettingGroup("aprs_smartbeacon", "APRS SmartBeacon")
+        aprs = self._memobj.aprs
+
+        val = RadioSettingValueList(
+            self._SMARTBEACON_PROFILE,
+            self._SMARTBEACON_PROFILE[aprs.active_smartbeaconing])
+        rs = RadioSetting("aprs.active_smartbeaconing", "SmartBeacon profile",
+                          val)
+        menu.append(rs)
+
+        for profile in range(3):
+            pfx = "type%d" % (profile + 1)
+            path = "aprs.smartbeaconing_profile[%d]" % profile
+            prof = aprs.smartbeaconing_profile[profile]
+
+            low_val = RadioSettingValueInteger(2, 30, prof.low_speed_mph)
+            high_val = RadioSettingValueInteger(3, 70, prof.high_speed_mph)
+            low_val.get_max = lambda: min(30, int(high_val.get_value()) - 1)
+
+            rs = RadioSetting("%s.low_speed_mph" % path,
+                              "%s Low Speed (mph)" % pfx, low_val)
+            menu.append(rs)
+
+            rs = RadioSetting("%s.high_speed_mph" % path,
+                              "%s High Speed (mph)" % pfx, high_val)
+            menu.append(rs)
+
+            val = RadioSettingValueInteger(1, 100, prof.slow_rate_min)
+            rs = RadioSetting("%s.slow_rate_min" % path,
+                              "%s Slow rate (minutes)" % pfx, val)
+            menu.append(rs)
+
+            val = RadioSettingValueInteger(10, 180, prof.fast_rate_sec)
+            rs = RadioSetting("%s.fast_rate_sec" % path,
+                              "%s Fast rate (seconds)" % pfx, val)
+            menu.append(rs)
+
+            val = RadioSettingValueInteger(5, 90, prof.turn_angle)
+            rs = RadioSetting("%s.turn_angle" % path,
+                              "%s Turn angle (degrees)" % pfx, val)
+            menu.append(rs)
+
+            val = RadioSettingValueInteger(1, 255, prof.turn_slop)
+            rs = RadioSetting("%s.turn_slop" % path,
+                              "%s Turn slop" % pfx, val)
+            menu.append(rs)
+
+            val = RadioSettingValueInteger(5, 180, prof.turn_time_sec)
+            rs = RadioSetting("%s.turn_time_sec" % path,
+                              "%s Turn time (seconds)" % pfx, val)
+            menu.append(rs)
+
+        return menu
+ 
+    def _get_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())
+        return top
+
+    def get_settings(self):
+        try:
+            return self._get_settings()
+        except:
+            import traceback
+            print "Failed to parse settings:"
+            traceback.print_exc()
+            return None
+
+    @staticmethod
+    def apply_custom_symbol(setting, obj):
+        # Ensure new value falls within known bounds, otherwise leave it as
+        # it's a custom value from the radio that's outside our list.
+        if setting.value.get_value() in chirp_common.APRS_SYMBOLS:
+            setattr(obj, "custom_symbol",
+                    chirp_common.APRS_SYMBOLS.index(setting.value.get_value()))
+
+    @classmethod
+    def _apply_callsign(cls, callsign, obj, default_ssid=None):
+        ssid = default_ssid
+        dash_index = callsign.find("-")
+        if dash_index >= 0:
+            ssid = callsign[dash_index + 1:]
+            callsign = callsign[:dash_index]
+            try:
+                ssid = int(ssid) % 16
+            except ValueError:
+                ssid = default_ssid
+        setattr(obj, "callsign", cls._add_ff_pad(callsign, 6))
+        if ssid is not None:
+            setattr(obj, "ssid", ssid)
+
+    def apply_beacon_type(cls, setting, obj):
+        beacon_type = str(setting.value.get_value())
+        beacon_index = cls._BEACON_TYPE.index(beacon_type)
+        tx_smartbeacon = beacon_index >> 1
+        tx_interval_beacon = beacon_index & 1
+        if tx_interval_beacon:
+            setattr(obj, "tx_interval_beacon", 1)
+            setattr(obj, "tx_smartbeacon", 0)
+        elif tx_smartbeacon:
+            setattr(obj, "tx_interval_beacon", 0)
+            setattr(obj, "tx_smartbeacon", 1)
+        else:
+            setattr(obj, "tx_interval_beacon", 0)
+            setattr(obj, "tx_smartbeacon", 0)
+
+    @classmethod
+    def apply_callsign(cls, setting, obj, default_ssid=None):
+        # Uppercase, strip SSID then FF pad to max string length.
+        callsign = setting.value.get_value().upper()
+        cls._apply_callsign(callsign, obj, default_ssid)
+
+    def apply_digi_path(self, setting, obj):
+        # Parse and map to aprs.digi_path_4_7[0-3] or aprs.digi_path_8
+        # and FF terminate.
+        path = str(setting.value.get_value())
+        callsigns = [c.strip() for c in path.split(",")]
+        for index in range(len(obj.entry)):
+            try:
+                self._apply_callsign(callsigns[index], obj.entry[index], 0)
+            except IndexError:
+                self._apply_callsign("", obj.entry[index], 0)
+        if len(callsigns) > len(obj.entry):
+            raise Exception("This path only supports %d entries" % (index + 1))
+
+    @classmethod
+    def apply_ff_padded_string(cls, setting, obj):
+        # FF pad.
+        val = setting.value.get_value()
+        max_len = getattr(obj, "padded_string").size() / 8
+        val = str(val).rstrip()
+        setattr(obj, "padded_string", cls._add_ff_pad(val, max_len))
+
+    @classmethod
+    def apply_lat_long(cls, setting, obj):
+        name = setting.get_name()
+        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)
+        setattr(obj, "%s_sign" % name, sign)
+        setattr(obj, "%s_degree" % name, l_d)
+        setattr(obj, "%s_minute" % name, l_m)
+        setattr(obj, "%s_second" % name, l_s)
+
+    def set_settings(self, settings):
+        _mem = self._memobj
+        for element in settings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+            if not element.changed():
+                continue
+            try:
+                if element.has_apply_callback():
+                    print "Using apply callback"
+                    try:
+                        element.run_apply_callback()
+                    except NotImplementedError as e:
+                        print e
+                    continue
+
+                # Find the object containing setting.
+                obj = _mem
+                bits = element.get_name().split(".")
+                setting = bits[-1]
+                for name in bits[:-1]:
+                    if name.endswith("]"):
+                        name, index = name.split("[")
+                        index = int(index[:-1])
+                        obj = getattr(obj, name)[index]
+                    else:
+                        obj = getattr(obj, name)
+
+                try:
+                    old_val = getattr(obj, setting)
+                    if os.getenv("CHIRP_DEBUG"):
+                        print "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)
+            except Exception, e:
+                print element.get_name()
+                raise
+
+ at directory.register
+class VX8GERadio(VX8DRadio):
+    """Yaesu VX-8GE"""
+    _model = "AH041"
+    VARIANT = "GE"
+    _has_vibrate = True
+    _has_af_dual = False
diff --git a/chirp/wouxun.py b/chirp/wouxun.py
index bb1f3c9..992b9fa 100644
--- a/chirp/wouxun.py
+++ b/chirp/wouxun.py
@@ -20,8 +20,10 @@ import os
 from chirp import util, chirp_common, bitwise, memmap, errors, directory
 from chirp.settings import RadioSetting, RadioSettingGroup, \
                 RadioSettingValueBoolean, RadioSettingValueList, \
-                RadioSettingValueInteger, RadioSettingValueString
+                RadioSettingValueInteger, RadioSettingValueString, \
+                RadioSettingValueFloat
 from chirp.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 ]
  
@@ -54,7 +56,7 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
     MODEL = "KG-UVD1P"
     _model = "KG669V"
     
-    _querymodel = "HiWOUXUN\x02"
+    _querymodel = ("HiWOUXUN\x02", "PROGUV6X\x02")
     
     CHARSET = list("0123456789") + [chr(x + ord("A")) for x in range(0, 26)] + \
         list("?+-")
@@ -83,6 +85,12 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
           u8 unknown[2];
         } memory[199];
         
+        #seekto 0x0842;
+        u16 fm_presets_0[9];
+
+        #seekto 0x0882;
+        u16 fm_presets_1[9];
+
         #seekto 0x0970;
         struct {
             u16 vhf_rx_start;
@@ -95,10 +103,86 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
             u16 uhf_tx_stop;
         } freq_ranges;
 
-        #seekto 0x0E5C;
+        #seekto 0x0E00;
+        struct {
+          char welcome1[6];
+          char welcome2[6];
+          char single_band[6];
+        } strings;
+
+        #seekto 0x0E20;
         struct {
-          u8 unknown_flag1:7,
+          u8 unknown_flag_01:6,
+             vfo_b_ch_disp:2;
+          u8 unknown_flag_02:5,
+             vfo_a_fr_step:3;
+          u8 unknown_flag_03:4,
+             vfo_a_squelch:4;
+          u8 unknown_flag_04:7,
+             power_save:1;
+          u8 unknown_flag_05:8;
+          u8 unknown_flag_06:6,
+             roger_beep:2;
+          u8 unknown_flag_07:2,
+             transmit_time_out:6;
+          u8 unknown_flag_08:4,
+             vox:4;
+          u8 unknown_1[4];
+          u8 unknown_flag_09:6,
+             voice:2;
+          u8 unknown_flag_10:7,
+             beep:1;
+          u8 unknown_flag_11:7,
+             ani_id_enable:1;
+          u8 unknown_2[2];
+          u8 unknown_flag_12:5,
+             vfo_b_fr_step:3;
+          u8 unknown_3[1];
+          u8 unknown_flag_13:3,
+             ani_id_tx_delay:5;
+          u8 unknown_4[1];
+          u8 unknown_flag_14:6,
+             ani_id_sidetone:2;
+          u8 unknown_flag_15:4,
+             tx_time_out_alert:4;
+          u8 unknown_flag_16:6,
+             vfo_a_ch_disp:2;
+          u8 unknown_flag_15:6,
+             scan_mode:2;
+          u8 unknown_flag_16:7,
+             kbd_lock:1;
+          u8 unknown_flag_17:6,
+             ponmsg:2;
+          u8 unknown_flag_18:5,
+             pf1_function:3;
+          u8 unknown_5[1];
+          u8 unknown_flag_19:7,
+             auto_backlight:1;
+          u8 unknown_flag_20:7,
+             sos_ch:1;
+          u8 unknown_6;
+          u8 sd_available;
+          u8 unknown_flag_21:7,
+             auto_lock_kbd:1;
+          u8 unknown_flag_22:4,
+             vfo_b_squelch:4;
+          u8 unknown_7[1];
+          u8 unknown_flag_23:7,
+             stopwatch:1;
+          u8 vfo_a_cur_chan;
+          u8 unknown_flag_24:7,
+             dual_band_receive:1;
+          u8 current_vfo:1,
+             unknown_flag_24:7;
+          u8 unknown_8[2];
+          u8 mode_password[6];
+          u8 reset_password[6];
+          u8 ani_id_content[6];
+          u8 unknown_flag_25:7,
              menu_available:1;
+          u8 unknown_9[1];
+          u8 priority_chan;
+          u8 vfo_b_cur_chan;
         } settings;
 
         #seekto 0x1008;
@@ -110,18 +194,46 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
     """
 
     @classmethod
-    def get_experimental_warning(cls):
-        return ('This version of the Wouxun driver allows you to modify the '
+    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.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.
+            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.
+            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):
+            while True:
+                yield cls._querymodel
+        else:
+            i = 0
+            while True:
+                yield cls._querymodel[i % len(cls._querymodel)]
+                i += 1
 
     def _identify(self):
         """Do the original wouxun identification dance"""
-        for _i in range(0, 5):
-            self.pipe.write(self._querymodel)
+        query = self._get_querymodel()
+        for _i in range(0, 10):
+            self.pipe.write(query.next())
             resp = self.pipe.read(9)
             if len(resp) != 9:
                 print "Got:\n%s" % util.hexprint(resp)
@@ -218,50 +330,51 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
 
     def get_settings(self):
         freqranges = RadioSettingGroup("freqranges", "Freq ranges")
-        top = RadioSettingGroup("top", "All Settings", freqranges)
+        fm_preset = RadioSettingGroup("fm_preset", "FM Presets")
+        top = RadioSettingGroup("top", "All Settings", freqranges, fm_preset)
 
         rs = RadioSetting("menu_available", "Menu Available",
                           RadioSettingValueBoolean(
                             self._memobj.settings.menu_available))
         top.append(rs)
 
-        rs = RadioSetting("vhf_rx_start", "VHF RX Lower Limit (MHz)",
-                          RadioSettingValueInteger(136, 174, 
+        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)
-        rs = RadioSetting("vhf_rx_stop", "VHF RX Upper Limit (MHz)",
-                          RadioSettingValueInteger(136, 174, 
+        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)
-        rs = RadioSetting("uhf_rx_start", "UHF RX Lower Limit (MHz)",
-                          RadioSettingValueInteger(216, 520, 
+        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)
-        rs = RadioSetting("uhf_rx_stop", "UHF RX Upper Limit (MHz)",
-                          RadioSettingValueInteger(216, 520, 
+        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)
-        rs = RadioSetting("vhf_tx_start", "VHF TX Lower Limit (MHz)",
-                          RadioSettingValueInteger(136, 174, 
+        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)
-        rs = RadioSetting("vhf_tx_stop", "VHF TX Upper Limit (MHz)",
-                          RadioSettingValueInteger(136, 174, 
+        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)
-        rs = RadioSetting("uhf_tx_start", "UHF TX Lower Limit (MHz)",
-                          RadioSettingValueInteger(216, 520, 
+        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)
-        rs = RadioSetting("uhf_tx_stop", "UHF TX Upper Limit (MHz)",
-                          RadioSettingValueInteger(216, 520, 
+        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)
@@ -269,19 +382,236 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
         # 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.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)]
 
+        def _filter(name):
+            filtered = ""
+            for char in str(name):
+                if char in chirp_common.CHARSET_ASCII:
+                    filtered += char
+                else:
+                    filtered += " "
+            return filtered
+
+        # 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)
+        rs = RadioSetting("strings.welcome1", "Power-On Message 1",
+                          RadioSettingValueString(0, 6,
+                              _filter(self._memobj.strings.welcome1)))
+        top.append(rs)
+        rs = RadioSetting("strings.welcome2", "Power-On Message 2",
+                          RadioSettingValueString(0, 6,
+                              _filter(self._memobj.strings.welcome2)))
+        top.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"]
+        rs = RadioSetting("vfo_a_ch_disp", "VFO A Channel disp mode",
+                          RadioSettingValueList(options,
+                              options[self._memobj.settings.vfo_a_ch_disp]))
+        top.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 = ["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)
+        rs = RadioSetting("vfo_b_fr_step", "VFO B Frequency Step",
+                          RadioSettingValueList(options,
+                              options[self._memobj.settings.vfo_b_fr_step]))
+        top.append(rs)
+        rs = RadioSetting("vfo_a_squelch", "VFO A Squelch",
+                          RadioSettingValueInteger(0, 9,
+                              self._memobj.settings.vfo_a_squelch))
+        top.append(rs)
+        rs = RadioSetting("vfo_b_squelch", "VFO B Squelch",
+                          RadioSettingValueInteger(0, 9,
+                              self._memobj.settings.vfo_b_squelch))
+        top.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)
+        rs = RadioSetting("vfo_b_cur_chan", "VFO B current channel",
+                          RadioSettingValueInteger(1, 128,
+                              self._memobj.settings.vfo_b_cur_chan))
+        top.append(rs)
+        rs = RadioSetting("priority_chan", "Priority channel",
+                          RadioSettingValueInteger(0, 199,
+                              self._memobj.settings.priority_chan))
+        top.append(rs)
+        rs = RadioSetting("power_save", "Power save",
+                          RadioSettingValueBoolean(
+                              self._memobj.settings.power_save))
+        top.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)
+        options = ["Off", "Begin", "End", "Both"]
+        rs = RadioSetting("roger_beep", "Roger beep select",
+                          RadioSettingValueList(options,
+                              options[self._memobj.settings.roger_beep]))
+        top.append(rs)
+        options = ["%s" % x for x in range(15, 615, 15)]
+        rs = RadioSetting("transmit_time_out", "TX Time-out Timer",
+                          RadioSettingValueList(options,
+                              options[self._memobj.settings.transmit_time_out]))
+        top.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)
+        rs = RadioSetting("vox", "Vox",
+                          RadioSettingValueInteger(0, 10,
+                              self._memobj.settings.vox))
+        top.append(rs)
+        options = ["Off", "Chinese", "English"]
+        rs = RadioSetting("voice", "Voice",
+                          RadioSettingValueList(options,
+                              options[self._memobj.settings.voice]))
+        top.append(rs)
+        rs = RadioSetting("beep", "Beep",
+                          RadioSettingValueBoolean(self._memobj.settings.beep))
+        top.append(rs)
+        rs = RadioSetting("ani_id_enable", "ANI id enable",
+                          RadioSettingValueBoolean(
+                              self._memobj.settings.ani_id_enable))
+        top.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)
+        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)
+        options = ["Time", "Carrier", "Search"]
+        rs = RadioSetting("scan_mode", "Scan mode",
+                          RadioSettingValueList(options,
+                              options[self._memobj.settings.scan_mode]))
+        top.append(rs)
+        rs = RadioSetting("kbd_lock", "Keyboard lock",
+                          RadioSettingValueBoolean(
+                              self._memobj.settings.kbd_lock))
+        top.append(rs)
+        rs = RadioSetting("auto_lock_kbd", "Auto lock keyboard",
+                          RadioSettingValueBoolean(
+                              self._memobj.settings.auto_lock_kbd))
+        top.append(rs)
+        rs = RadioSetting("auto_backlight", "Auto backlight",
+                          RadioSettingValueBoolean(
+                              self._memobj.settings.auto_backlight))
+        top.append(rs)
+        options = ["CH A", "CH B"]
+        rs = RadioSetting("sos_ch", "SOS CH",
+                          RadioSettingValueList(options,
+                              options[self._memobj.settings.sos_ch]))
+        top.append(rs)
+        rs = RadioSetting("stopwatch", "Stopwatch",
+                          RadioSettingValueBoolean(
+                              self._memobj.settings.stopwatch))
+        top.append(rs)
+        rs = RadioSetting("dual_band_receive", "Dual band receive",
+                          RadioSettingValueBoolean(
+                              self._memobj.settings.dual_band_receive))
+        top.append(rs)
+        options = ["VFO A", "VFO B"]
+        rs = RadioSetting("current_vfo", "Current VFO",
+                          RadioSettingValueList(options,
+                              options[self._memobj.settings.current_vfo]))
+        top.append(rs)
+
+        options = ["Dual", "Single"]
+        rs = RadioSetting("sd_available", "Single/Dual Band",
+                          RadioSettingValueList(options,
+                              options[self._memobj.settings.sd_available]))
+        top.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)
+        _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)
+
+        dtmfchars = "0123456789 *#ABCD"
+        _codeobj = self._memobj.settings.ani_id_content
+        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+        val = RadioSettingValueString(0, 6, _code, False)
+        val.set_charset(dtmfchars)
+        rs = RadioSetting("settings.ani_id_content", "PTT-ID Code", val)
+        def apply_ani_id(setting, obj):
+            value = []
+            for j in range(0, 6):
+                try:
+                    value.append(dtmfchars.index(str(setting.value)[j]))
+                except IndexError:
+                    value.append(0xFF)
+            obj.ani_id_content = value
+        rs.set_apply_callback(apply_ani_id, self._memobj.settings)
+        top.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
+            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))
+            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
+            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))
+            fm_preset.append(rs)
+
         return top
 
     def set_settings(self, settings):
         for element in settings:
             if not isinstance(element, RadioSetting):
-                if element.get_name() != "freqranges" :
-                    self.set_settings(element)
-                else:
+                if element.get_name() == "freqranges" :
                     self._set_freq_settings(element)
+                elif element.get_name() == "fm_preset" :
+                    self._set_fm_preset(element)
+                else:
+                    self.set_settings(element)
+                    continue
             else:
                 try:
                     if "." in element.get_name():
@@ -293,12 +623,37 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
                     else:
                         obj = self._memobj.settings
                         setting = element.get_name()
-                    print "Setting %s = %s" % (setting, element.value)
-                    setattr(obj, setting, element.value)
+
+                    if element.has_apply_callback():
+                        print "Using apply callback"
+                        element.run_apply_callback()
+                    else:
+                        print "Setting %s = %s" % (setting, element.value)
+                        setattr(obj, setting, element.value)
                 except Exception, e:
                     print 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:])
+                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)
+                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()
+                raise
+
     def _set_freq_settings(self, settings):
         for element in settings:
             try:
@@ -317,7 +672,8 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
             code = int("%03o" % (val & 0x07FF))
             pol = (val & 0x8000) and "R" or "N"
             return code, pol
-            
+
+        tpol = False
         if _mem.tx_tone != 0xFFFF and _mem.tx_tone > 0x2800:
             tcode, tpol = _get_dcs(_mem.tx_tone)
             mem.dtcs = tcode
@@ -328,6 +684,7 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
         else:
             txmode = ""
 
+        rpol = False
         if _mem.rx_tone != 0xFFFF and _mem.rx_tone > 0x2800:
             rcode, rpol = _get_dcs(_mem.rx_tone)
             mem.rx_dtcs = rcode
@@ -348,8 +705,8 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
             mem.tmode = "Cross"
             mem.cross_mode = "%s->%s" % (txmode, rxmode)
 
-        if mem.tmode == "DTCS":
-            mem.dtcs_polarity = "%s%s" % (tpol, rpol)
+        # 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,
@@ -415,7 +772,7 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
         def _set_dcs(code, pol):
             val = int("%i" % code, 8) + 0x2800
             if pol == "R":
-                val += 0xA000
+                val += 0x8000
             return val
 
         if mem.tmode == "Cross":
@@ -513,7 +870,7 @@ class KGUV6DRadio(KGUVD1PRadio):
     """Wouxun KG-UV6 (D and X variants)"""
     MODEL = "KG-UV6"
     
-    _querymodel = "HiWXUVD1\x02"
+    _querymodel = ("HiWXUVD1\x02", "HiKGUVD1\x02")
     
     _MEM_FORMAT = """
         #seekto 0x0010;
@@ -591,7 +948,8 @@ class KGUV6DRadio(KGUVD1PRadio):
              auto_backlight:1;
           u8 unknown_flag_20:7,
              sos_ch:1;
-          u8 unknown_6[2];
+          u8 unknown_6;
+          u8 sd_available;
           u8 unknown_flag_21:7,
              auto_lock_kbd:1;
           u8 unknown_flag_22:4,
@@ -632,7 +990,7 @@ class KGUV6DRadio(KGUVD1PRadio):
           u8 pad[2];
         } vfo_settings[2];
 	
-        #seekto 0x0f80;
+        #seekto 0x0f82;
         u16 fm_presets_0[9];
 
         #seekto 0x0ff0;
@@ -661,7 +1019,7 @@ class KGUV6DRadio(KGUVD1PRadio):
             u8 pad[9];
         } vfo_offset[2];
 
-        #seekto 0x1f80;
+        #seekto 0x1f82;
         u16 fm_presets_1[9];
     """
 
@@ -673,7 +1031,8 @@ class KGUV6DRadio(KGUVD1PRadio):
 
     def get_settings(self):
         freqranges = RadioSettingGroup("freqranges", "Freq ranges")
-        top = RadioSettingGroup("top", "All Settings", freqranges)
+        fm_preset = RadioSettingGroup("fm_preset", "FM Presets")
+        top = RadioSettingGroup("top", "All Settings", freqranges, fm_preset)
 
         rs = RadioSetting("menu_available", "Menu Available",
                           RadioSettingValueBoolean(
@@ -761,8 +1120,15 @@ class KGUV6DRadio(KGUVD1PRadio):
                           RadioSettingValueList(options,
                                         options[self._memobj.settings.vfo_b_ch_disp]))
         top.append(rs)
-	# TODO - vfo_a_fr_step
-	# TODO -vfo_b_fr_step:3;
+        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)
+        rs = RadioSetting("vfo_b_fr_step", "VFO B Frequency Step",
+                          RadioSettingValueList(options,
+                                        options[self._memobj.settings.vfo_b_fr_step]))
+        top.append(rs)
         rs = RadioSetting("vfo_a_squelch", "VFO A Squelch",
                           RadioSettingValueInteger(0, 9, self._memobj.settings.vfo_a_squelch))
         top.append(rs)
@@ -773,7 +1139,7 @@ class KGUV6DRadio(KGUVD1PRadio):
                           RadioSettingValueInteger(1, 199, self._memobj.settings.vfo_a_cur_chan))
         top.append(rs)
         rs = RadioSetting("vfo_b_cur_chan", "VFO B current channel",
-                          RadioSettingValueInteger(0, 199, self._memobj.settings.vfo_b_cur_chan))
+                          RadioSettingValueInteger(1, 199, self._memobj.settings.vfo_b_cur_chan))
         top.append(rs)
         rs = RadioSetting("priority_chan", "Priority channel",
                           RadioSettingValueInteger(0, 199, self._memobj.settings.priority_chan))
@@ -796,7 +1162,14 @@ class KGUV6DRadio(KGUVD1PRadio):
                           RadioSettingValueList(options,
                                         options[self._memobj.settings.roger_beep]))
         top.append(rs)
-        # TODO - transmit_time_out:6;
+        options = ["%s" % x for x in range(15, 615, 15)]
+        rs = RadioSetting("transmit_time_out", "TX Time-out Timer",
+                          RadioSettingValueList(options,
+                                        options[self._memobj.settings.transmit_time_out]))
+        top.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)
         rs = RadioSetting("vox", "Vox",
                           RadioSettingValueInteger(0, 10, self._memobj.settings.vox))
         top.append(rs)
@@ -819,7 +1192,6 @@ class KGUV6DRadio(KGUVD1PRadio):
                           RadioSettingValueList(options,
                                         options[self._memobj.settings.ani_id_sidetone]))
         top.append(rs)
-        # TODO tx_time_out_alert:4;
         options = ["Time", "Carrier", "Search"]
         rs = RadioSetting("scan_mode", "Scan mode",
                           RadioSettingValueList(options,
@@ -850,6 +1222,13 @@ class KGUV6DRadio(KGUVD1PRadio):
                           RadioSettingValueList(options,
                                         options[self._memobj.settings.current_vfo]))
         top.append(rs)
+
+        options = ["Dual", "Single"]
+        rs = RadioSetting("sd_available", "Single/Dual Band",
+                          RadioSettingValueList(options,
+                                        options[self._memobj.settings.sd_available]))
+        top.append(rs)
+
         _pwd = self._memobj.settings.mode_password
         rs = RadioSetting("mode_password", "Mode password (000000 disabled)",
                   RadioSettingValueInteger(0, 9, _pwd[0]),
@@ -868,22 +1247,100 @@ class KGUV6DRadio(KGUVD1PRadio):
                   RadioSettingValueInteger(0, 9, _pwd[4]),
                   RadioSettingValueInteger(0, 9, _pwd[5]))
         top.append(rs)
-        try:
-            _ani = self._memobj.settings.ani_id_content
-            rs = RadioSetting("ani_id_content", "ANI Code",
-                              RadioSettingValueInteger(0, 9, _ani[0]),
-                              RadioSettingValueInteger(0, 9, _ani[1]),
-                              RadioSettingValueInteger(0, 9, _ani[2]),
-                              RadioSettingValueInteger(0, 9, _ani[3]),
-                              RadioSettingValueInteger(0, 9, _ani[4]),
-                              RadioSettingValueInteger(0, 9, _ani[5]))
-            top.append(rs)
-        except Exception:
-            print ("Your ANI code is not five digits, which is not currently"
-                   " supported in CHIRP.")            
+
+        dtmfchars = "0123456789 *#ABCD"
+        _codeobj = self._memobj.settings.ani_id_content
+        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+        val = RadioSettingValueString(0, 6, _code, False)
+        val.set_charset(dtmfchars)
+        rs = RadioSetting("settings.ani_id_content", "ANI Code", val)
+        def apply_ani_id(setting, obj):
+            value = []
+            for j in range(0, 6):
+                try:
+                    value.append(dtmfchars.index(str(setting.value)[j]))
+                except IndexError:
+                    value.append(0xFF)
+            obj.ani_id_content = value
+        rs.set_apply_callback(apply_ani_id, self._memobj.settings)
+        top.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
+            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))
+            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
+            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))
+            fm_preset.append(rs)
 
         return top
 
+    def set_settings(self, settings):
+        for element in settings:
+            if not isinstance(element, RadioSetting):
+                if element.get_name() == "freqranges" :
+                    self._set_freq_settings(element)
+                elif element.get_name() == "fm_preset" :
+                    self._set_fm_preset(element)
+                else:
+                    self.set_settings(element)
+            else:
+                try:
+                    if "." in element.get_name():
+                        bits = element.get_name().split(".")
+                        obj = self._memobj
+                        for bit in bits[:-1]:
+                            obj = getattr(obj, bit)
+                        setting = bits[-1]
+                    else:
+                        obj = self._memobj.settings
+                        setting = element.get_name()
+
+                    if element.has_apply_callback():
+                        print "Using apply callback"
+                        element.run_apply_callback()
+                    else:
+                        print "Setting %s = %s" % (setting, element.value)
+                        setattr(obj, setting, element.value)
+                except Exception, e:
+                    print 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:])
+                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)
+                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()
+                raise
+
     @classmethod
     def match_model(cls, filedata, filename):
         if len(filedata) == 8192 and \
@@ -894,9 +1351,11 @@ class KGUV6DRadio(KGUVD1PRadio):
 @directory.register
 class KG816Radio(KGUVD1PRadio,
         chirp_common.ExperimentalRadio):
-    """Wouxun KG816"""
-    MODEL = "KG816"
+    """Wouxun KG-816"""
+    MODEL = "KG-816"
 
+    _querymodel = "HiWOUXUN\x02"
+    
     _MEM_FORMAT = """
         #seekto 0x0010;
         struct {
@@ -953,42 +1412,42 @@ class KG816Radio(KGUVD1PRadio,
         top = RadioSettingGroup("top", "All Settings", freqranges)
 
         rs = RadioSetting("vhf_rx_start", "vhf rx start",
-                          RadioSettingValueInteger(136, 520, 
+                          RadioSettingValueInteger(66, 520,
                                 decode_freq(
                                     self._memobj.freq_ranges.vhf_rx_start)))
         freqranges.append(rs)
         rs = RadioSetting("vhf_rx_stop", "vhf rx stop",
-                          RadioSettingValueInteger(136, 520, 
+                          RadioSettingValueInteger(66, 520,
                                 decode_freq(
                                     self._memobj.freq_ranges.vhf_rx_stop)))
         freqranges.append(rs)
         rs = RadioSetting("uhf_rx_start", "uhf rx start",
-                          RadioSettingValueInteger(136, 520, 
+                          RadioSettingValueInteger(66, 520,
                                 decode_freq(
                                     self._memobj.freq_ranges.uhf_rx_start)))
         freqranges.append(rs)
         rs = RadioSetting("uhf_rx_stop", "uhf rx stop",
-                          RadioSettingValueInteger(136, 520, 
+                          RadioSettingValueInteger(66, 520,
                                 decode_freq(
                                     self._memobj.freq_ranges.uhf_rx_stop)))
         freqranges.append(rs)
         rs = RadioSetting("vhf_tx_start", "vhf tx start",
-                          RadioSettingValueInteger(136, 520, 
+                          RadioSettingValueInteger(66, 520,
                                 decode_freq(
                                     self._memobj.freq_ranges.vhf_tx_start)))
         freqranges.append(rs)
         rs = RadioSetting("vhf_tx_stop", "vhf tx stop",
-                          RadioSettingValueInteger(136, 520, 
+                          RadioSettingValueInteger(66, 520,
                                 decode_freq(
                                     self._memobj.freq_ranges.vhf_tx_stop)))
         freqranges.append(rs)
         rs = RadioSetting("uhf_tx_start", "uhf tx start",
-                          RadioSettingValueInteger(136, 520, 
+                          RadioSettingValueInteger(66, 520,
                                 decode_freq(
                                     self._memobj.freq_ranges.uhf_tx_start)))
         freqranges.append(rs)
         rs = RadioSetting("uhf_tx_stop", "uhf tx stop",
-                          RadioSettingValueInteger(136, 520, 
+                          RadioSettingValueInteger(66, 520,
                                 decode_freq(
                                     self._memobj.freq_ranges.uhf_tx_stop)))
         freqranges.append(rs)
@@ -1010,3 +1469,12 @@ class KG816Radio(KGUVD1PRadio,
             return True
         return False
 
+
+ at directory.register
+class KG818Radio(KG816Radio):
+    """Wouxun KG-818"""
+    MODEL = "KG-818"
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return False
diff --git a/chirp/yaesu_clone.py b/chirp/yaesu_clone.py
index d94b796..fd8419b 100644
--- a/chirp/yaesu_clone.py
+++ b/chirp/yaesu_clone.py
@@ -17,6 +17,7 @@ CMD_ACK = 0x06
 
 from chirp import chirp_common, util, memmap, errors
 import time, os
+from textwrap import dedent
 
 def _safe_read(pipe, count):
     buf = ""
@@ -178,6 +179,21 @@ class YaesuCloneModeRadio(chirp_common.CloneModeRadio):
     VENDOR = "Yaesu"
     _model = "ABCDE"
 
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.pre_download = _(dedent("""\
+            1. Turn radio off.
+            2. Connect data cable.
+            3. Prepare radio for clone.
+            4. <b>After clicking OK</b>, press the key to send image."""))
+        rp.pre_upload = _(dedent("""\
+            1. Turn radio off.
+            2. Connect data cable.
+            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 []
@@ -211,5 +227,5 @@ class YaesuCloneModeRadio(chirp_common.CloneModeRadio):
     def _wipe_memory_banks(self, mem):
         """Remove @mem from all the banks it is currently in"""
         bm = self.get_bank_model()
-        for bank in bm.get_memory_banks(mem):
-            bm.remove_memory_from_bank(mem, bank)
+        for bank in bm.get_memory_mappings(mem):
+            bm.remove_memory_from_mapping(mem, bank)
diff --git a/chirpui/bandplans.py b/chirpui/bandplans.py
new file mode 100644
index 0000000..9d34cd1
--- /dev/null
+++ b/chirpui/bandplans.py
@@ -0,0 +1,107 @@
+# Copyright 2013 Sean Burford <sburford at google.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 gtk
+from chirp import bandplan, bandplan_na, bandplan_au
+from chirp import bandplan_iaru_r1, bandplan_iaru_r2, bandplan_iaru_r3
+from chirpui import inputdialog
+
+
+class BandPlans(object):
+    def __init__(self, config):
+        self._config = config
+        self.plans = {}
+
+        # Migrate old "automatic repeater offset" setting to
+        # "North American Amateur Band Plan"
+        ro = self._config.get("autorpt", "memedit")
+        if ro is not None:
+            self._config.set_bool("north_america", ro, "bandplan")
+            self._config.remove_option("autorpt", "memedit")
+        # And default new users to North America.
+        if not self._config.is_defined("north_america", "bandplan"):
+            self._config.set_bool("north_america", True, "bandplan")
+
+        for plan in (bandplan_na, bandplan_au, bandplan_iaru_r1,
+                     bandplan_iaru_r2, bandplan_iaru_r3):
+            name = plan.DESC.get("name", plan.SHORTNAME)
+            self.plans[plan.SHORTNAME] = (name, plan)
+
+            rpt_inputs = []
+            for band in plan.BANDS:
+                # 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)
+                # Add repeater inputs.
+                rpt_input = band.inverse()
+                if rpt_input not in plan.BANDS:
+                    rpt_inputs.append(band.inverse())
+            plan.bands = list(plan.BANDS)
+            plan.bands.extend(rpt_inputs)
+
+    def get_defaults_for_frequency(self, freq):
+        freq = int(freq)
+        result = bandplan.Band((freq, freq), repr(freq))
+
+        for shortname, details in self.plans.items():
+            if self._config.get_bool(shortname, "bandplan"):
+                matches = [x for x in details[1].bands if x.contains(result)]
+                # Add matches to defaults, favoring more specific matches.
+                matches = sorted(matches, key=lambda x: x.width(),
+                                 reverse=True)
+                for match in matches:
+                    result.mode = match.mode or result.mode
+                    result.step_khz = match.step_khz or result.step_khz
+                    result.offset = match.offset or result.offset
+                    result.duplex = match.duplex or result.duplex
+                    result.tones = match.tones or result.tones
+                    if match.name:
+                        result.name = '/'.join((result.name or '', match.name))
+                # Limit ourselves to one band plan match for simplicity.
+                # Note that if the user selects multiple band plans by editing
+                # the config file it will work as expected (except where plans
+                # conflict).
+                if matches:
+                    break
+
+        return result
+
+    def select_bandplan(self, parent_window):
+        plans = ["None"]
+        for shortname, details in self.plans.iteritems():
+            if self._config.get_bool(shortname, "bandplan"):
+                plans.insert(0, details[0])
+            else:
+                plans.append(details[0])
+
+        d = inputdialog.ChoiceDialog(plans, parent=parent_window,
+                                     title="Choose Defaults")
+        d.label.set_text(_("Band plans define default channel settings for "
+                           "frequencies in a region.  Choose a band plan "
+                           "or None for completely manual channel "
+                           "settings."))
+        d.label.set_line_wrap(True)
+        r = d.run()
+
+        if r == gtk.RESPONSE_OK:
+            selection = d.choice.get_active_text()
+            for shortname, details in self.plans.iteritems():
+                self._config.set_bool(shortname, selection == details[0],
+                                      "bandplan")
+                if selection == details[0]:
+                    print "Selected band plan %s: %s" % (shortname, selection)
+
+        d.destroy()
diff --git a/chirpui/bankedit.py b/chirpui/bankedit.py
index 23c05da..a6cb71e 100644
--- a/chirpui/bankedit.py
+++ b/chirpui/bankedit.py
@@ -22,69 +22,69 @@ from gobject import TYPE_INT, TYPE_STRING, TYPE_BOOLEAN
 from chirp import chirp_common
 from chirpui import common, miscwidgets
 
-class BankNamesJob(common.RadioJob):
-    def __init__(self, bm, editor, cb):
+class MappingNamesJob(common.RadioJob):
+    def __init__(self, model, editor, cb):
         common.RadioJob.__init__(self, cb, None)
-        self.__bm = bm
+        self.__model = model
         self.__editor = editor
 
     def execute(self, radio):
-        self.__editor.banks = []
+        self.__editor.mappings = []
 
-        banks = self.__bm.get_banks()
-        for bank in banks:
-            self.__editor.banks.append((bank, bank.get_name()))
+        mappings = self.__model.get_mappings()
+        for mapping in mappings:
+            self.__editor.mappings.append((mapping, mapping.get_name()))
 
         gobject.idle_add(self.cb, *self.cb_args)
 
-class BankNameEditor(common.Editor):
+class MappingNameEditor(common.Editor):
     def refresh(self):
-        def got_banks():
+        def got_mappings():
             self._keys = []
-            for bank, name in self.banks:
-                self._keys.append(bank.get_index())
-                self.listw.set_item(bank.get_index(),
-                                    bank.get_index(),
+            for mapping, name in self.mappings:
+                self._keys.append(mapping.get_index())
+                self.listw.set_item(mapping.get_index(),
+                                    mapping.get_index(),
                                     name)
 
-            self.listw.connect("item-set", self.bank_changed)
+            self.listw.connect("item-set", self.mapping_changed)
 
-        job = BankNamesJob(self._bm, self, got_banks)
-        job.set_desc(_("Retrieving bank information"))
+        job = MappingNamesJob(self._model, self, got_mappings)
+        job.set_desc(_("Retrieving %s information") % self._type)
         self.rthread.submit(job)
 
-    def get_bank_list(self):
-        banks = []
+    def get_mapping_list(self):
+        mappings = []
         keys = self.listw.get_keys()
         for key in keys:
-            banks.append(self.listw.get_item(key)[2])
+            mappings.append(self.listw.get_item(key)[2])
 
-        return banks
-    
-    def bank_changed(self, listw, key):
+        return mappings
+
+    def mapping_changed(self, listw, key):
         def cb(*args):
             self.emit("changed")
 
         name = self.listw.get_item(key)[2]
-        bank, oldname = self.banks[self._keys.index(key)]
+        mapping, oldname = self.mappings[self._keys.index(key)]
 
         def trigger_changed(*args):
             self.emit("changed")
 
         job = common.RadioJob(trigger_changed, "set_name", name)
-        job.set_target(bank)
-        job.set_desc(_("Setting name on bank"))
+        job.set_target(mapping)
+        job.set_desc(_("Setting name on %s") % self._type.lower())
         self.rthread.submit(job)
 
         return True
 
-    def __init__(self, rthread):
-        common.Editor.__init__(self)
-        self.rthread = rthread
-        self._bm = rthread.radio.get_bank_model()
+    def __init__(self, rthread, model):
+        super(MappingNameEditor, self).__init__(rthread)
+        self._model = model
+        self._type = common.unpluralize(model.get_name())
 
         types = [(gobject.TYPE_STRING, "key"),
-                 (gobject.TYPE_STRING, _("Bank")),
+                 (gobject.TYPE_STRING, self._type),
                  (gobject.TYPE_STRING, _("Name"))]
 
         self.listw = miscwidgets.KeyedListWidget(types)
@@ -93,7 +93,7 @@ class BankNameEditor(common.Editor):
         self.listw.set_sort_column(1, -1)
         self.listw.show()
 
-        self.banks = []
+        self.mappings = []
 
         sw = gtk.ScrolledWindow()
         sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
@@ -109,51 +109,60 @@ class BankNameEditor(common.Editor):
         self.refresh()
         self._loaded = True
 
-class MemoryBanksJob(common.RadioJob):
-    def __init__(self, bm, cb, number):
+    def other_editor_changed(self, target_editor):
+        self._loaded = False
+        if self.is_focused():
+            self.refresh_all_memories()
+
+    def mappings_changed(self):
+        pass
+
+class MemoryMappingsJob(common.RadioJob):
+    def __init__(self, model, cb, number):
         common.RadioJob.__init__(self, cb, None)
-        self.__bm = bm
+        self.__model = model
         self.__number = number
 
     def execute(self, radio):
         mem = radio.get_memory(self.__number)
         if mem.empty:
-            banks = []
+            mappings = []
             indexes = []
         else:
-            banks = self.__bm.get_memory_banks(mem)
+            mappings = self.__model.get_memory_mappings(mem)
             indexes = []
-            if isinstance(self.__bm, chirp_common.BankIndexInterface):
-                for bank in banks:
-                    indexes.append(self.__bm.get_memory_index(mem, bank))
-        self.cb(mem, banks, indexes, *self.cb_args)
-            
-class BankMembershipEditor(common.Editor):
+            if isinstance(self.__model,
+                          chirp_common.MappingModelIndexInterface):
+                for mapping in mappings:
+                    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],)
 
-    def _get_next_bank_index(self, bank):
-        # NB: Only works for one-to-one bank models right now!
+    def _get_next_mapping_index(self, mapping):
+        # NB: Only works for one-to-one models right now!
         iter = self._store.get_iter_first()
         indexes = []
-        ncols = len(self._cols) + len(self.banks)
+        ncols = len(self._cols) + len(self.mappings)
         while iter:
             vals = self._store.get(iter, *tuple([n for n in range(0, ncols)]))
             loc = vals[self.C_LOC]
             index = vals[self.C_INDEX]
-            banks = vals[self.C_BANKS:]
-            if True in banks and banks.index(True) == bank:
+            mappings = vals[self.C_MAPPINGS:]
+            if True in mappings and mappings.index(True) == mapping:
                 indexes.append(index)
             iter = self._store.iter_next(iter)
 
-        index_bounds = self._bm.get_index_bounds()
+        index_bounds = self._model.get_index_bounds()
         num_indexes = index_bounds[1] - index_bounds[0]
         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 0 # If the bank is full, just wrap around!
+        return 0 # If the mapping is full, just wrap around!
 
     def _toggled_cb(self, rend, path, colnum):
         try:
@@ -165,56 +174,60 @@ class BankMembershipEditor(common.Editor):
             if not self._store.get(iter, self.C_FILLED)[0]:
                 return
 
-        # The bank index is the column number, minus the 3 label columns
-        bank, name = self.banks[colnum - len(self._cols)]
+        # The mapping index is the column number, minus the 3 label columns
+        mapping, name = self.mappings[colnum - len(self._cols)]
         loc, = self._store.get(self._store.get_iter(path), self.C_LOC)
 
+        is_indexed = isinstance(self._model,
+                                chirp_common.MappingModelIndexInterface)
+
         if rend.get_active():
             # Changing from True to False
-            fn = "remove_memory_from_bank"
+            fn = "remove_memory_from_mapping"
             index = None
         else:
             # Changing from False to True
-            fn = "add_memory_to_bank"
-            if self._rf.has_bank_index:
-                index = self._get_next_bank_index(colnum - len(self._cols))
+            fn = "add_memory_to_mapping"
+            if is_indexed:
+                index = self._get_next_mapping_index(colnum - len(self._cols))
             else:
                 index = None
 
         def do_refresh_memory(*args):
-            # Step 2: Update our notion of the memory's bank information
+            # Step 2: Update our notion of the memory's mapping information
             self.refresh_memory(loc)
 
-        def do_bank_index(result, memory):
+        def do_mapping_index(result, memory):
             if isinstance(result, Exception):
-                common.show_error("Failed to add {mem} to bank: {err}"
+                common.show_error("Failed to add {mem} to mapping: {err}"
                                   .format(mem=memory.number,
                                           err=str(result)),
                                   parent=self.editorset.parent_window)
                 return
             self.emit("changed")
-            # Step 3: Set the memory's bank index (maybe)
-            if not self._rf.has_bank_index or index is None:
+            # Step 3: Set the memory's mapping index (maybe)
+            if not is_indexed or index is None:
                 return do_refresh_memory()
 
             job = common.RadioJob(do_refresh_memory,
-                                  "set_memory_index", memory, bank, index)
-            job.set_target(self._bm)
-            job.set_desc(_("Updating bank index "
-                           "for memory {num}").format(num=memory.number))
+                                  "set_memory_index", memory, mapping, index)
+            job.set_target(self._model)
+            job.set_desc(_("Updating {type} index "
+                           "for memory {num}").format(type=self._type,
+                                                      num=memory.number))
             self.rthread.submit(job)
 
-        def do_bank_adjustment(memory):
-            # Step 1: Do the bank add/remove
-            job = common.RadioJob(do_bank_index, fn, memory, bank)
-            job.set_target(self._bm)
+        def do_mapping_adjustment(memory):
+            # Step 1: Do the mapping add/remove
+            job = common.RadioJob(do_mapping_index, fn, memory, mapping)
+            job.set_target(self._model)
             job.set_cb_args(memory)
-            job.set_desc(_("Updating bank information "
+            job.set_desc(_("Updating mapping information "
                            "for memory {num}").format(num=memory.number))
             self.rthread.submit(job)
 
         # Step 0: Fetch the memory
-        job = common.RadioJob(do_bank_adjustment, "get_memory", loc)
+        job = common.RadioJob(do_mapping_adjustment, "get_memory", loc)
         job.set_desc(_("Getting memory {num}").format(num=loc))
         self.rthread.submit(job)
 
@@ -224,36 +237,38 @@ class BankMembershipEditor(common.Editor):
         def refresh_memory(*args):
             self.refresh_memory(loc)
 
-        def set_index(banks, memory):
+        def set_index(mappings, memory):
             self.emit("changed")
             # Step 2: Set the index
             job = common.RadioJob(refresh_memory, "set_memory_index",
-                                  memory, banks[0], int(new))
-            job.set_target(self._bm)
+                                  memory, mappings[0], int(new))
+            job.set_target(self._model)
             job.set_desc(_("Setting index "
                            "for memory {num}").format(num=memory.number))
             self.rthread.submit(job)
 
-        def get_bank(memory):
-            # Step 1: Get the first/only bank
-            job = common.RadioJob(set_index, "get_memory_banks", memory)
+        def get_mapping(memory):
+            # Step 1: Get the first/only mapping
+            job = common.RadioJob(set_index, "get_memory_mappings", memory)
             job.set_cb_args(memory)
-            job.set_target(self._bm)
-            job.set_desc(_("Getting bank for "
-                           "memory {num}").format(num=memory.number))
+            job.set_target(self._model)
+            job.set_desc(_("Getting {type} for "
+                           "memory {num}").format(type=self._type,
+                                                  num=memory.number))
             self.rthread.submit(job)
 
         # Step 0: Get the memory
-        job = common.RadioJob(get_bank, "get_memory", loc)
+        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):
-        common.Editor.__init__(self)
-        self.rthread = rthread
+    def __init__(self, rthread, editorset, model):
+        super(MappingMembershipEditor, self).__init__(rthread)
+
         self.editorset = editorset
         self._rf = rthread.radio.get_features()
-        self._bm = rthread.radio.get_bank_model()
+        self._model = model
+        self._type = common.unpluralize(model.get_name())
 
         self._view_cols = [
             (_("Loc"),       TYPE_INT,     gtk.CellRendererText, ),
@@ -271,19 +286,22 @@ class BankMembershipEditor(common.Editor):
         self.C_FREQ   = 2
         self.C_NAME   = 3
         self.C_INDEX  = 4
-        self.C_BANKS  = 5 # and beyond
+        self.C_MAPPINGS  = 5 # and beyond
         
         cols = list(self._cols)
 
         self._index_cache = []
 
-        for i in range(0, self._bm.get_num_banks()):
-            label = "Bank %i" % (i+1)
+        for i in range(0, self._model.get_num_mappings()):
+            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._view = gtk.TreeView(self._store)
 
+        is_indexed = isinstance(self._model,
+                                chirp_common.MappingModelIndexInterface)
+
         colnum = 0
         for label, dtype, rtype in cols:
             if not rtype:
@@ -306,7 +324,7 @@ class BankMembershipEditor(common.Editor):
             elif colnum == self.C_INDEX:
                 rend.set_property("editable", True)
                 rend.connect("edited", self._index_edited_cb)
-                col.set_visible(self._rf.has_bank_index)
+                col.set_visible(is_indexed)
             colnum += 1
 
         # A non-rendered column to absorb extra space in the row
@@ -330,7 +348,7 @@ class BankMembershipEditor(common.Editor):
         self._loaded = False
 
     def refresh_memory(self, number):
-        def got_mem(memory, banks, indexes):
+        def got_mem(memory, mappings, indexes):
             iter = self._store.get_iter(self._number_to_path(memory.number))
             row = [self.C_FILLED, not memory.empty,
                    self.C_LOC, memory.number,
@@ -339,29 +357,30 @@ class BankMembershipEditor(common.Editor):
                    # Hack for only one index right now
                    self.C_INDEX, indexes and indexes[0] or 0,
                    ]
-            for i in range(0, len(self.banks)):
+            for i in range(0, len(self.mappings)):
                 row.append(i + len(self._cols))
-                row.append(self.banks[i][0] in banks)
+                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 bank info in %s" % (time.time() - self._start)
+                print "Got all %s info in %s" % (self._type,
+                                                 (time.time() - self._start))
 
-        job = MemoryBanksJob(self._bm, got_mem, number)
-        job.set_desc(_("Getting bank information "
-                       "for memory {num}").format(num=number))
+        job = MemoryMappingsJob(self._model, got_mem, number)
+        job.set_desc(_("Getting {type} information "
+                       "for memory {num}").format(type=self._type, num=number))
         self.rthread.submit(job)
 
     def refresh_all_memories(self):
         for i in range(*self._rf.memory_bounds):
             self.refresh_memory(i)
 
-    def refresh_banks(self, and_memories=False):
-        def got_banks():
+    def refresh_mappings(self, and_memories=False):
+        def got_mappings():
             for i in range(len(self._cols) - len(self._view_cols) - 1,
-                           len(self.banks)):
+                           len(self.mappings)):
                 col = self._view.get_column(i + len(self._view_cols))
-                bank, name = self.banks[i]
+                mapping, name = self.mappings[i]
                 if name:
                     col.set_title(name)
                 else:
@@ -369,8 +388,8 @@ class BankMembershipEditor(common.Editor):
             if and_memories:
                 self.refresh_all_memories()
 
-        job = BankNamesJob(self._bm, self, got_banks)
-        job.set_desc(_("Getting bank information"))
+        job = MappingNamesJob(self._model, self, got_mappings)
+        job.set_desc(_("Getting %s information") % self._type)
         self.rthread.submit(job)
 
     def focus(self):
@@ -379,14 +398,14 @@ class BankMembershipEditor(common.Editor):
             return
 
         self._start = time.time()
-        self.refresh_banks(True)
+        self.refresh_mappings(True)
 
         self._loaded = True
 
-    def memories_changed(self):
+    def other_editor_changed(self, target_editor):
         self._loaded = False
         if self.is_focused():
             self.refresh_all_memories()
 
-    def banks_changed(self):
-        self.refresh_banks()
+    def mappings_changed(self):
+        self.refresh_mappings()
diff --git a/chirpui/clone.py b/chirpui/clone.py
index 0786e1e..7210d4a 100644
--- a/chirpui/clone.py
+++ b/chirpui/clone.py
@@ -56,6 +56,8 @@ class CloneSettingsDialog(gtk.Dialog):
                 port = conf.get("last_port")
             elif ports:
                 port = ports[0]
+            else:
+                port = ""
             if not port in ports:
                 ports.insert(0, port)
 
diff --git a/chirpui/common.py b/chirpui/common.py
index adacaec..8511b8b 100644
--- a/chirpui/common.py
+++ b/chirpui/common.py
@@ -34,9 +34,11 @@ class Editor(gobject.GObject):
 
     root = None
 
-    def __init__(self):
+    def __init__(self, rthread):
         gobject.GObject.__init__(self)
+        self.read_only = False
         self._focused = False
+        self.rthread = rthread
 
     def is_focused(self):
         return self._focused
@@ -56,6 +58,18 @@ class Editor(gobject.GObject):
     def hotkey(self, action):
         pass
 
+    def set_read_only(self, read_only):
+        self.read_only = read_only
+
+    def get_read_only(self):
+        return self.read_only
+
+    def prepare_close(self):
+        pass
+
+    def other_editor_changed(self, editor):
+        pass
+
 gobject.type_register(Editor)
 
 def DBG(*args):
@@ -128,16 +142,26 @@ class RadioThread(threading.Thread, gobject.GObject):
                     (gobject.TYPE_STRING,)),
         }
 
-    def __init__(self, radio):
+    def __init__(self, radio, parent=None):
         threading.Thread.__init__(self)
         gobject.GObject.__init__(self)
         self.__queue = {}
+        if parent:
+            self.__runlock = parent._get_run_lock()
+            self.status = lambda msg: parent.status(msg)
+        else:
+            self.__runlock = threading.Lock()
+            self.status = self._status
+
         self.__counter = threading.Semaphore(0)
-        self.__enabled = True
         self.__lock = threading.Lock()
-        self.__runlock = threading.Lock()
+
+        self.__enabled = True
         self.radio = radio
 
+    def _get_run_lock(self):
+        return self.__runlock
+
     def _qlock(self):
         self.__lock.acquire()
 
@@ -196,7 +220,7 @@ class RadioThread(threading.Thread, gobject.GObject):
         self.__counter.release()
         self.__enabled = False
     
-    def status(self, msg):
+    def _status(self, msg):
         jobs = 0
         for i in dict(self.__queue):
                 jobs += len(self.__queue[i])
@@ -224,14 +248,14 @@ class RadioThread(threading.Thread, gobject.GObject):
                     DBG("Running job at priority %i" % i)
                     break
             self._qunlock()
-            
+
             if job:
                 self.lock()
                 self.status(job.desc)
                 job.execute(self.radio)
                 last_job_desc = job.desc
                 self.unlock()
-   
+
         print "RadioThread exiting"
 
 def log_exception():
@@ -389,3 +413,8 @@ def show_diff_blob(title, result):
     d.run()
     d.destroy()
 
+def unpluralize(string):
+    if string.endswith("s"):
+        return string[:-1]
+    else:
+        return string
diff --git a/chirpui/config.py b/chirpui/config.py
index d82a1db..c5b55c1 100644
--- a/chirpui/config.py
+++ b/chirpui/config.py
@@ -54,6 +54,12 @@ class ChirpConfig:
     def is_defined(self, key, section):
         return self.__config.has_option(section, key)
 
+    def remove_option(self, section, key):
+        self.__config.remove_option(section, key)
+
+        if not self.__config.items(section):
+            self.__config.remove_section(section)
+
 class ChirpConfigProxy:
     def __init__(self, config, section="global"):
         self._config = config
@@ -98,6 +104,9 @@ class ChirpConfigProxy:
     def is_defined(self, key, section=None):
         return self._config.is_defined(key, section or self._section)
 
+    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/chirpui/dstaredit.py
index 942c6c5..df0baa8 100644
--- a/chirpui/dstaredit.py
+++ b/chirpui/dstaredit.py
@@ -179,8 +179,7 @@ class DStarEditor(common.Editor):
         self.rthread.submit(job)
 
     def __init__(self, rthread):
-        common.Editor.__init__(self)
-        self.rthread = rthread
+        super(DStarEditor, self).__init__(rthread)
 
         self.loaded = False
 
diff --git a/chirpui/editorset.py b/chirpui/editorset.py
index 1f578a0..c38b7d8 100644
--- a/chirpui/editorset.py
+++ b/chirpui/editorset.py
@@ -19,7 +19,7 @@ import gobject
 
 from chirp import chirp_common, directory, generic_csv, generic_xml
 from chirpui import memedit, dstaredit, bankedit, common, importdialog
-from chirpui import inputdialog, reporting, settingsedit
+from chirpui import inputdialog, reporting, settingsedit, radiobrowser, config
 
 class EditorSet(gtk.VBox):
     __gsignals__ = {
@@ -35,6 +35,68 @@ class EditorSet(gtk.VBox):
                              (gobject.TYPE_STRING,)),
         }
 
+    def _make_device_mapping_editors(self, device, devrthread, index):
+        sub_index = 0
+        memory_editor = self.editors["memedit%i" % index]
+        mappings = device.get_mapping_models()
+        for mapping_model in mappings:
+            members = bankedit.MappingMembershipEditor(devrthread, self,
+                                                       mapping_model)
+            label = mapping_model.get_name()
+            if self.rf.has_sub_devices:
+                label += "(%s)" % device.VARIANT
+            lab = gtk.Label(label)
+            self.tabs.append_page(members.root, lab)
+            self.editors["mapping_members%i%i" % (index, sub_index)] = members
+
+            basename = common.unpluralize(mapping_model.get_name())
+            names = bankedit.MappingNameEditor(devrthread, mapping_model)
+            label = "%s Names" % basename
+            if self.rf.has_sub_devices:
+                label += " (%s)" % device.VARIANT
+            lab = gtk.Label(label)
+            self.tabs.append_page(names.root, lab)
+            self.editors["mapping_names%i%i" % (index, sub_index)] = names
+
+            members.root.show()
+            members.connect("changed", self.editor_changed)
+            if hasattr(mapping_model.get_mappings()[0], "set_name"):
+                names.root.show()
+                members.connect("changed", lambda x: names.mappings_changed())
+                names.connect("changed", lambda x: members.mappings_changed())
+                names.connect("changed", self.editor_changed)
+
+    def _make_device_editors(self, device, devrthread, index):
+        if isinstance(device, chirp_common.IcomDstarSupport):
+            memories = memedit.DstarMemoryEditor(devrthread)
+        else:
+            memories = memedit.MemoryEditor(devrthread)
+
+        memories.connect("usermsg", lambda e, m: self.emit("usermsg", m))
+        memories.connect("changed", self.editor_changed)
+
+        if self.rf.has_sub_devices:
+            label = (_("Memories (%(variant)s)") % 
+                     dict(variant=device.VARIANT))
+            rf = device.get_features()
+        else:
+            label = _("Memories")
+            rf = self.rf
+        lab = gtk.Label(label)
+        self.tabs.append_page(memories.root, lab)
+        memories.root.show()
+        self.editors["memedit%i" % index] = memories
+
+        self._make_device_mapping_editors(device, devrthread, index)
+
+        if isinstance(device, chirp_common.IcomDstarSupport):
+            editor = dstaredit.DStarEditor(devrthread)
+            self.tabs.append_page(editor.root, gtk.Label(_("D-STAR")))
+            editor.root.show()
+            editor.connect("changed", self.dstar_changed, memories)
+            editor.connect("changed", self.editor_changed)
+            self.editors["dstar"] = editor
+
     def __init__(self, source, parent_window=None, filename=None, tempname=None):
         gtk.VBox.__init__(self, True, 0)
 
@@ -49,78 +111,51 @@ class EditorSet(gtk.VBox):
         else:
             raise Exception("Unknown source type")
 
-        self.rthread = common.RadioThread(self.radio)
-        self.rthread.setDaemon(True)
-        self.rthread.start()
+        rthread = common.RadioThread(self.radio)
+        rthread.setDaemon(True)
+        rthread.start()
 
-        self.rthread.connect("status", lambda e, m: self.emit("status", m))
+        rthread.connect("status", lambda e, m: self.emit("status", m))
 
         self.tabs = gtk.Notebook()
         self.tabs.connect("switch-page", self.tab_selected)
         self.tabs.set_tab_pos(gtk.POS_LEFT)
 
-        self.editors = {
-            "memedit"      : None,
-            "dstar"        : None,
-            "bank_names"   : None,
-            "bank_members" : None,
-            "settings"     : None,
-            }
-
-        if isinstance(self.radio, chirp_common.IcomDstarSupport):
-            self.editors["memedit"] = memedit.DstarMemoryEditor(self.rthread)
-            self.editors["dstar"] = dstaredit.DStarEditor(self.rthread)
-        else:
-            self.editors["memedit"] = memedit.MemoryEditor(self.rthread)
-
-        self.editors["memedit"].connect("usermsg",
-                                        lambda e, m: self.emit("usermsg", m))
-
-        rf = self.radio.get_features()
+        self.editors = {}
 
-        if rf.has_bank:
-            self.editors["bank_members"] = \
-                bankedit.BankMembershipEditor(self.rthread, self)
-        
-        if rf.has_bank_names:
-            self.editors["bank_names"] = bankedit.BankNameEditor(self.rthread)
-
-        if rf.has_settings:
-            self.editors["settings"] = settingsedit.SettingsEditor(self.rthread)
-
-        lab = gtk.Label(_("Memories"))
-        self.tabs.append_page(self.editors["memedit"].root, lab)
-        self.editors["memedit"].root.show()
-
-        if self.editors["dstar"]:
-            lab = gtk.Label(_("D-STAR"))
-            self.tabs.append_page(self.editors["dstar"].root, lab)
-            self.editors["dstar"].root.show()
-            self.editors["dstar"].connect("changed", self.dstar_changed)
-
-        if self.editors["bank_names"]:
-            lab = gtk.Label(_("Bank Names"))
-            self.tabs.append_page(self.editors["bank_names"].root, lab)
-            self.editors["bank_names"].root.show()
-            self.editors["bank_names"].connect("changed", self.banks_changed)
-
-        if self.editors["bank_members"]:
-            lab = gtk.Label(_("Banks"))
-            self.tabs.append_page(self.editors["bank_members"].root, lab)
-            self.editors["bank_members"].root.show()
-            self.editors["bank_members"].connect("changed", self.banks_changed)
-
-        if self.editors["settings"]:
-            lab = gtk.Label(_("Settings"))
-            self.tabs.append_page(self.editors["settings"].root, lab)
-            self.editors["settings"].root.show()
+        self.rf = self.radio.get_features()
+        if self.rf.has_sub_devices:
+            devices = self.radio.get_sub_devices()
+        else:
+            devices = [self.radio]
+
+        index = 0
+        for device in devices:
+            devrthread = common.RadioThread(device, rthread)
+            devrthread.setDaemon(True)
+            devrthread.start()
+            self._make_device_editors(device, devrthread, index)
+            index += 1
+
+        if self.rf.has_settings:
+            editor = settingsedit.SettingsEditor(rthread)
+            self.tabs.append_page(editor.root, gtk.Label(_("Settings")))
+            editor.root.show()
+            editor.connect("changed", self.editor_changed)
+            self.editors["settings"] = editor
+
+        conf = config.get()
+        if (hasattr(self.rthread.radio, '_memobj') and
+            conf.get_bool("developer", "state")):
+            editor = radiobrowser.RadioBrowser(self.rthread)
+            lab = gtk.Label(_("Browser"))
+            self.tabs.append_page(editor.root, lab)
+            editor.connect("changed", self.editor_changed)
+            self.editors["browser"] = editor
 
         self.pack_start(self.tabs)
         self.tabs.show()
 
-        # pylint: disable-msg=E1101
-        self.editors["memedit"].connect("changed", self.editor_changed)
-
         self.label = self.text_label = None
         self.make_label()
         self.modified = (tempname is not None)
@@ -171,31 +206,19 @@ class EditorSet(gtk.VBox):
         self.modified = False
         self.update_tab()
 
-    def dstar_changed(self, *args):
-        print "D-STAR editor changed"
-        memedit = self.editors["memedit"]
-        dstared = self.editors["dstar"]
+    def dstar_changed(self, dstared, memedit):
         memedit.set_urcall_list(dstared.editor_ucall.get_callsigns())
         memedit.set_repeater_list(dstared.editor_rcall.get_callsigns())
         memedit.prefill()
-        if not isinstance(self.radio, chirp_common.LiveRadio):
-            self.modified = True
-            self.update_tab()
-
-    def banks_changed(self, *args):
-        print "Banks changed"
-        if self.editors["bank_members"]:
-            self.editors["bank_members"].banks_changed()
-        if not isinstance(self.radio, chirp_common.LiveRadio):
-            self.modified = True
-            self.update_tab()
 
-    def editor_changed(self, *args):
+    def editor_changed(self, target_editor=None):
+        print "%s changed" % target_editor
         if not isinstance(self.radio, chirp_common.LiveRadio):
             self.modified = True
             self.update_tab()
-        if self.editors["bank_members"]:
-            self.editors["bank_members"].memories_changed()
+        for editor in self.editors.values():
+            if editor != target_editor:
+                editor.other_editor_changed(target_editor)
 
     def get_tab_label(self):
         return self.label
@@ -226,7 +249,8 @@ class EditorSet(gtk.VBox):
 
         if count > 0:
             self.editor_changed()
-            gobject.idle_add(self.editors["memedit"].prefill)
+            current_editor = self.get_current_editor()
+            gobject.idle_add(current_editor.prefill)
 
         return count
 
@@ -251,6 +275,13 @@ class EditorSet(gtk.VBox):
         raise Exception(_("Internal Error"))
 
     def do_import(self, filen):
+        current_editor = self.get_current_editor()
+        if not isinstance(current_editor, memedit.MemoryEditor):
+            # FIXME: We need a nice message to let the user know that they
+            # need to select the appropriate memory editor tab before doing
+            # and import so that we know which thread and editor to import
+            # into and refresh. This will do for the moment.
+            common.show_error("Memory editor must be selected before import")
         try:
             src_radio = directory.get_radio_by_image(filen)
         except Exception, e:
@@ -338,11 +369,13 @@ class EditorSet(gtk.VBox):
                               self)
             
     def prime(self):
+        # NOTE: this is only called to prime new CSV files, so assume
+        # only one memory editor for now
         mem = chirp_common.Memory()
         mem.freq = 146010000
 
         def cb(*args):
-            gobject.idle_add(self.editors["memedit"].prefill)
+            gobject.idle_add(self.editors["memedit0"].prefill)
 
         job = common.RadioJob(cb, "set_memory", mem)
         job.set_desc(_("Priming memory"))
@@ -358,16 +391,25 @@ class EditorSet(gtk.VBox):
                 v.unfocus()
 
     def set_read_only(self, read_only=True):
-        self.editors["memedit"].set_read_only(read_only)
-    
+        for editor in self.editors.values():
+            editor and editor.set_read_only(read_only)
+
     def get_read_only(self):
-        return self.editors["memedit"].get_read_only()
+        return self.editors["memedit0"].get_read_only()
 
     def prepare_close(self):
-        self.editors["memedit"].prepare_close()
+        for editor in self.editors.values():
+            editor and editor.prepare_close()
 
     def get_current_editor(self):
-        for e in self.editors.values():
+        for lab, e in self.editors.items():
             if e and self.tabs.page_num(e.root) == self.tabs.get_current_page():
                 return e
         raise Exception("No editor selected?")
+
+    @property
+    def rthread(self):
+        """Magic rthread property to return the rthread of the currently-
+        selected editor"""
+        e = self.get_current_editor()
+        return e.rthread
diff --git a/chirpui/mainapp.py b/chirpui/mainapp.py
index e3b2eb1..719d096 100644
--- a/chirpui/mainapp.py
+++ b/chirpui/mainapp.py
@@ -40,6 +40,7 @@ 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()
 
@@ -129,7 +130,7 @@ class ChirpMain(gtk.Window):
         for _editortype, actions in mappings.items():
             for _action in actions:
                 action = self.menu_ag.get_action(_action)
-                action.set_sensitive(_editortype == editortype)
+                action.set_sensitive(editortype.startswith(_editortype))
 
     def _connect_editorset(self, eset):
         eset.connect("want-close", self.do_close)
@@ -300,8 +301,6 @@ If you think that it is valid, you can select a radio model below to force an op
             # We have to actually instantiate the IC9xICFRadio to get its
             # sub-devices
             radio = ic9x_icf.IC9xICFRadio(fname)
-            devices = radio.get_sub_devices()
-            del radio
         else:
             try:
                 radio = directory.get_radio_by_image(fname)
@@ -315,39 +314,30 @@ If you think that it is valid, you can select a radio model below to force an op
                 common.show_error(os.path.basename(fname) + ": " + str(e))
                 return
 
-            if radio.get_features().has_sub_devices:
-                devices = radio.get_sub_devices()
-            else:
-                devices = [radio]
-
-        prio = len(devices)
         first_tab = False
-        for device in devices:
-            try:
-                eset = editorset.EditorSet(device, self,
-                                           filename=fname,
-                                           tempname=tempname)
-            except Exception, e:
-                common.log_exception()
-                common.show_error(
-                    _("There was an error opening {fname}: {error}").format(
-                        fname=fname,
-                        error=error))
-                return
-    
-            eset.set_read_only(read_only)
-            self._connect_editorset(eset)
-            eset.show()
-            tab = self.tabs.append_page(eset, eset.get_tab_label())
-            if first_tab:
-                self.tabs.set_current_page(tab)
-                first_tab = False
-
-            if hasattr(eset.rthread.radio, "errors") and \
-                    eset.rthread.radio.errors:
-                msg = _("{num} errors during open:").format(num=len(eset.rthread.radio.errors))
-                common.show_error_text(msg,
-                                       "\r\n".join(eset.rthread.radio.errors))
+        try:
+            eset = editorset.EditorSet(radio, self,
+                                       filename=fname,
+                                       tempname=tempname)
+        except Exception, e:
+            common.log_exception()
+            common.show_error(
+                _("There was an error opening {fname}: {error}").format(
+                    fname=fname,
+                    error=e))
+            return
+
+        eset.set_read_only(read_only)
+        self._connect_editorset(eset)
+        eset.show()
+        self.tabs.append_page(eset, eset.get_tab_label())
+
+        if hasattr(eset.rthread.radio, "errors") and \
+                eset.rthread.radio.errors:
+            msg = _("{num} errors during open:").format(
+                num=len(eset.rthread.radio.errors))
+            common.show_error_text(msg,
+                                   "\r\n".join(eset.rthread.radio.errors))
 
     def do_live_warning(self, radio):
         d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK)
@@ -369,23 +359,12 @@ If you think that it is valid, you can select a radio model below to force an op
         d.destroy()
 
     def do_open_live(self, radio, tempname=None, read_only=False):
-        if radio.get_features().has_sub_devices:
-            devices = radio.get_sub_devices()
-        else:
-            devices = [radio]
-        
-        first_tab = True
-        for device in devices:
-            eset = editorset.EditorSet(device, self, tempname=tempname)
-            eset.connect("want-close", self.do_close)
-            eset.connect("status", self.ev_status)
-            eset.set_read_only(read_only)
-            eset.show()
-
-            tab = self.tabs.append_page(eset, eset.get_tab_label())
-            if first_tab:
-                self.tabs.set_current_page(tab)
-                first_tab = False
+        eset = editorset.EditorSet(radio, self, tempname=tempname)
+        eset.connect("want-close", self.do_close)
+        eset.connect("status", self.ev_status)
+        eset.set_read_only(read_only)
+        eset.show()
+        self.tabs.append_page(eset, eset.get_tab_label())
 
         if isinstance(radio, chirp_common.LiveRadio):
             reporting.report_model_usage(radio, "live", True)
@@ -585,7 +564,7 @@ If you think that it is valid, you can select a radio model below to force an op
             return True
 
         title = _("Proceed with experimental driver?")
-        text = rclass.get_experimental_warning()
+        text = rclass.get_prompts().experimental
         msg = _("This radio's driver is experimental. "
                 "Do you want to proceed?")
         resp, squelch = common.show_warning(msg, text,
@@ -596,6 +575,34 @@ If you think that it is valid, you can select a radio model below to force an op
             CONF.set_bool(sql_key, not squelch, "state")
         return resp == gtk.RESPONSE_YES
 
+    def _show_instructions(self, radio, message):
+        if message is None:
+            return
+
+        if CONF.get_bool("clone_instructions", "noconfirm"):
+            return
+
+        d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK)
+        d.set_markup("<big><b>" + _("{name} Instructions").format(
+                     name=radio.get_name()) + "</b></big>")
+        msg = _("{instructions}").format(instructions=message)
+        d.format_secondary_markup(msg)
+
+        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]
+        try:
+            ok_button = h_button_box.get_children()[0]
+            ok_button.grab_default()
+            ok_button.grab_focus()
+        except AttributeError:
+            # don't grab focus on GTK+ 2.0
+            pass
+        d.run()
+        d.destroy()
+        CONF.set_bool("clone_instructions", again.get_active(), "noconfirm")
+
     def do_download(self, port=None, rtype=None):
         d = clone.CloneSettingsDialog(parent=self)
         settings = d.run()
@@ -609,6 +616,8 @@ If you think that it is valid, you can select a radio model below to force an op
             # User does not want to proceed with experimental driver
             return
 
+        self._show_instructions(rclass, rclass.get_prompts().pre_download)
+
         print "User selected %s %s on port %s" % (rclass.VENDOR,
                                                   rclass.MODEL,
                                                   settings.port)
@@ -646,6 +655,11 @@ If you think that it is valid, you can select a radio model below to force an op
         d.destroy()
         if not settings:
             return
+        prompts = radio.get_prompts()
+
+        if prompts.display_pre_upload_prompt_before_opening_port == True:
+            print "Opening port after pre_upload prompt."
+            self._show_instructions(radio, prompts.pre_upload)
 
         if isinstance(radio, chirp_common.ExperimentalRadio) and \
                 not self._confirm_experimental(radio.__class__):
@@ -664,6 +678,10 @@ 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."
+            self._show_instructions(radio, prompts.pre_upload)
+
         radio.set_pipe(ser)
 
         ct = clone.CloneThread(radio, "out", cb=self.cb_cloneout, parent=self)
@@ -718,6 +736,7 @@ If you think that it is valid, you can select a radio model below to force an op
                  (_("EVE Files (VX5)") + " (*.eve)", "*.eve"),
                  (_("ICF Files") + " (*.icf)", "*.icf"),
                  (_("Kenwood HMK Files") + " (*.hmk)", "*.hmk"),
+                 (_("Kenwood ITM Files") + " (*.itm)", "*.itm"),
                  (_("Travel Plus Files") + " (*.tpe)", "*.tpe"),
                  (_("VX5 Commander Files") + " (*.vx5)", "*.vx5"),
                  (_("VX6 Commander Files") + " (*.vx6)", "*.vx6"),
@@ -783,7 +802,7 @@ If you think that it is valid, you can select a radio model below to force an op
             county.set_active(0)
         state.connect("changed", _changed, county)
         
-        d = inputdialog.FieldDialog(title="RepeaterBook Query", parent=self)
+        d = inputdialog.FieldDialog(title=_("RepeaterBook Query"), parent=self)
         d.add_field("State", state)
         d.add_field("County", county)
         d.add_field("Band", band)
@@ -842,7 +861,7 @@ If you think that it is valid, you can select a radio model below to force an op
         if not os.path.exists(filename):
             print "Failed, headers were:"
             print str(headers)
-            common.show_error("RepeaterBook query failed")
+            common.show_error(_("RepeaterBook query failed"))
             self.window.set_cursor(None)
             return
 
@@ -875,6 +894,87 @@ If you think that it is valid, you can select a radio model below to force an op
         else:
             self.do_open_live(radio, read_only=True)
 
+    def do_przemienniki_prompt(self):
+        d = inputdialog.FieldDialog(title='przemienniki.net query',
+                                    parent=self)
+        fields = {
+            "Country":
+                (miscwidgets.make_choice(['by', 'cz', 'de', 'lt', 'pl',
+                                          'sk', 'uk'], False),
+                 lambda x: str(x.get_active_text())),
+            "Band":
+                (miscwidgets.make_choice(['10m', '4m', '6m', '2m', '70cm',
+                                          '23cm', '13cm', '3cm'], False, '2m'),
+                 lambda x: str(x.get_active_text())),
+            "Mode":
+                (miscwidgets.make_choice(['fm', 'dv'], False),
+                 lambda x: str(x.get_active_text())),
+            "Only Working":
+                (miscwidgets.make_choice(['', 'yes'], False),
+                 lambda x: str(x.get_active_text())),
+            "Latitude": (gtk.Entry(), lambda x: float(x.get_text())),
+            "Longitude": (gtk.Entry(), lambda x: float(x.get_text())),
+            "Range": (gtk.Entry(), lambda x: int(x.get_text())),
+            }
+        for name in sorted(fields.keys()):
+            value, fn = fields[name]
+            d.add_field(name, value)
+        while d.run() == gtk.RESPONSE_OK:
+            query = "http://przemienniki.net/export/chirp.csv?"
+            args = []
+            for name, (value, fn) in fields.items():
+                if isinstance(value, gtk.Entry):
+                    contents = value.get_text()
+                else:
+                    contents = value.get_active_text()
+                if contents:
+                    try:
+                        _value = fn(value)
+                    except ValueError:
+                        common.show_error(_("Invalid value for %s") % name)
+                        query = None
+                        continue
+
+                    args.append("=".join((name.replace(" ", "").lower(),
+                                          contents)))
+            query += "&".join(args)
+            print query
+            d.destroy()
+            return query
+
+        d.destroy()
+        return query
+
+    def do_przemienniki(self, do_import):
+        url = self.do_przemienniki_prompt()
+        if not url:
+            return
+
+        fn = tempfile.mktemp(".csv")
+        filename, headers = urllib.urlretrieve(url, fn)
+        if not os.path.exists(filename):
+            print "Failed, headers were:"
+            print str(headers)
+            common.show_error(_("Query failed"))
+            return
+
+        class PRRadio(generic_csv.CSVRadio,
+                      chirp_common.NetworkSourceRadio):
+            VENDOR = "przemienniki.net"
+            MODEL = ""
+
+        try:
+            radio = PRRadio(filename)
+        except Exception, e:
+            common.show_error(str(e))
+            return
+
+        if do_import:
+            eset = self.get_current_editorset()
+            count = eset.do_import(filename)
+        else:
+            self.do_open_live(radio, read_only=True)
+
     def do_rfinder_prompt(self):
         fields = {"1Email"    :      (gtk.Entry(),
                                       lambda x: "@" in x),
@@ -952,7 +1052,8 @@ If you think that it is valid, you can select a radio model below to force an op
                   "3Zipcode"     : (gtk.Entry(), lambda x: x),
                   }
 
-        d = inputdialog.FieldDialog(title="RadioReference.com Query", parent=self)
+        d = inputdialog.FieldDialog(title=_("RadioReference.com Query"),
+                                    parent=self)
         for k in sorted(fields.keys()):
             d.add_field(k[1:], fields[k][0])
             fields[k][0].set_text(CONF.get(k[1:], "radioreference") or "")
@@ -1051,14 +1152,13 @@ If you think that it is valid, you can select a radio model below to force an op
 
         d.set_name("CHIRP")
         d.set_version(CHIRP_VERSION)
-        d.set_copyright("Copyright 2012 Dan Smith (KK7DS)")
+        d.set_copyright("Copyright 2013 Dan Smith (KK7DS)")
         d.set_website("http://chirp.danplanet.com")
-        d.set_authors(("Dan Smith <dsmith at danplanet.com>",
-                       _("With significant contributions by:"),
-                       "Marco IZ3GME",
-                       "Rick WZ3RO",
+        d.set_authors(("Dan Smith KK7DS <dsmith at danplanet.com>",
+                       _("With significant contributions from:"),
                        "Tom KD7LXL",
-                       "Vernon N7OH"
+                       "Marco IZ3GME",
+                       "Jim KC9HI"
                        ))
         d.set_translator_credits("Polish: Grzegorz SQ2RBY" +
                                  os.linesep +
@@ -1066,12 +1166,33 @@ If you think that it is valid, you can select a radio model below to force an op
                                  os.linesep +
                                  "Dutch: Michael PD4MT" +
                                  os.linesep +
-                                 "German: Benjamin HB9EUK")
+                                 "German: Benjamin HB9EUK" +
+                                 os.linesep +
+                                 "Hungarian: Attila HA7JA" +
+                                 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_columns(self):
         eset = self.get_current_editorset()
         driver = directory.get_driver(eset.rthread.radio.__class__)
@@ -1098,7 +1219,7 @@ If you think that it is valid, you can select a radio model below to force an op
         vbox.pack_start(label)
 
         fields = []
-        memedit = eset.editors["memedit"]
+        memedit = eset.get_current_editor() #.editors["memedit"]
         unsupported = memedit.get_unsupported_columns()
         for colspec in memedit.cols:
             if colspec[0].startswith("_"):
@@ -1132,7 +1253,9 @@ If you think that it is valid, you can select a radio model below to force an op
             conf = config.get("memedit")
             conf.set_bool("hide_unused", action.get_active())
         else:
-            eset.editors["memedit"].set_hide_unused(action.get_active())
+            for editortype, editor in eset.editors.iteritems():
+                if "memedit" in editortype:
+                    editor.set_hide_unused(action.get_active())
 
     def do_clearq(self):
         eset = self.get_current_editorset()
@@ -1173,8 +1296,8 @@ If you think that it is valid, you can select a radio model below to force an op
         conf = config.get()
         conf.set_bool("no_report", not action.get_active())
 
-    def do_toggle_autorpt(self, action):
-        CONF.set_bool("autorpt", action.get_active(), "memedit")
+    def do_toggle_no_smart_tmode(self, action):
+        CONF.set_bool("no_smart_tmode", not action.get_active(), "memedit")
 
     def do_toggle_developer(self, action):
         conf = config.get()
@@ -1186,7 +1309,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"]
+                 "Hungarian", "Russian", "Portuguese (BR)"]
         d = inputdialog.ChoiceDialog(langs, parent=self,
                                      title="Choose Language")
         d.label.set_text(_("Choose a language or Auto to use the "
@@ -1253,8 +1376,12 @@ If you think that it is valid, you can select a radio model below to force an op
             self.do_export()
         elif action in ["qrbook", "irbook"]:
             self.do_repeaterbook(action[0] == "i")
+        elif action in ["qpr", "ipr"]:
+            self.do_przemienniki(action[0] == "i")
         elif action == "about":
             self.do_about()
+        elif action == "documentation":
+            self.do_documentation()
         elif action == "columns":
             self.do_columns()
         elif action == "hide_unused":
@@ -1263,8 +1390,12 @@ If you think that it is valid, you can select a radio model below to force an op
             self.do_clearq()
         elif action == "report":
             self.do_toggle_report(_action)
-        elif action == "autorpt":
-            self.do_toggle_autorpt(_action)
+        elif action == "channel_defaults":
+            # The memedit thread also has an instance of bandplans.
+            bp = bandplans.BandPlans(CONF)
+            bp.select_bandplan(self)
+        elif action == "no_smart_tmode":
+            self.do_toggle_no_smart_tmode(_action)
         elif action == "developer":
             self.do_toggle_developer(_action)
         elif action in ["cut", "copy", "paste", "delete",
@@ -1314,6 +1445,7 @@ If you think that it is valid, you can select a radio model below to force an op
     <menu action="view">
       <menuitem action="columns"/>
       <menuitem action="hide_unused"/>
+      <menuitem action="no_smart_tmode"/>
       <menu action="viewdeveloper">
         <menuitem action="devshowraw"/>
         <menuitem action="devdiffraw"/>
@@ -1327,21 +1459,24 @@ If you think that it is valid, you can select a radio model below to force an op
       <menu action="importsrc" name="importsrc">
         <menuitem action="iradioreference"/>
         <menuitem action="irbook"/>
+        <menuitem action="ipr"/>
         <menuitem action="irfinder"/>
       </menu>
       <menu action="querysrc" name="querysrc">
         <menuitem action="qradioreference"/>
         <menuitem action="qrbook"/>
+        <menuitem action="qpr"/>
         <menuitem action="qrfinder"/>
       </menu>
       <menu action="stock" name="stock"/>
       <separator/>
-      <menuitem action="autorpt"/>
+      <menuitem action="channel_defaults"/>
       <separator/>
       <menuitem action="cancelq"/>
     </menu>
     <menu action="help">
       <menuitem action="about"/>
+      <menuitem action="documentation"/>
       <menuitem action="report"/>
       <menuitem action="developer"/>
     </menu>
@@ -1383,28 +1518,32 @@ If you think that it is valid, you can select a radio model below to force an op
             ('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),
             ('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_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),
             ('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),
             ]
 
         conf = config.get()
         re = not conf.get_bool("no_report");
         hu = conf.get_bool("hide_unused", "memedit")
-        ro = conf.get_bool("autorpt", "memedit")
         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),
-            ('autorpt', None, _("Automatic Repeater Offset"), None, None, self.mh, ro),
+            ('no_smart_tmode', None, _("Smart Tone Modes"), None, None, self.mh, st),
             ('developer', None, _("Enable Developer Functions"), None, None, self.mh, dv),
             ]
 
@@ -1539,6 +1678,9 @@ 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")
+        macapp.insert_app_menu_item(documentationitem, 0)
+        
         macapp.set_use_quartz_accelerators(False)
         macapp.ready()
 
@@ -1547,6 +1689,19 @@ If you think that it is valid, you can select a radio model below to force an op
     def __init__(self, *args, **kwargs):
         gtk.Window.__init__(self, *args, **kwargs)
 
+        def expose(window, event):
+            allocation = window.get_allocation()
+            CONF.set_int("window_w", allocation.width, "state")
+            CONF.set_int("window_h", allocation.height, "state")
+        self.connect("expose_event", expose)
+
+        def state_change(window, event):
+            CONF.set_bool(
+                "window_maximized",
+                event.new_window_state == gtk.gdk.WINDOW_STATE_MAXIMIZED,
+                "state")
+        self.connect("window-state-event", state_change)
+
         d = CONF.get("last_dir", "state")
         if d and os.path.isdir(d):
             platform.get_platform().set_last_dir(d)
@@ -1579,7 +1734,16 @@ If you think that it is valid, you can select a radio model below to force an op
 
         self.add(vbox)
 
-        self.set_default_size(800, 600)
+        try:
+            width = CONF.get_int("window_w", "state")
+            height = CONF.get_int("window_h", "state")
+        except Exception:
+            width = 800
+            height = 600
+
+        self.set_default_size(width, height)
+        if CONF.get_bool("window_maximized", "state"):
+            self.maximize()
         self.set_title("CHIRP")
 
         self.connect("delete_event", self.ev_delete)
@@ -1598,10 +1762,6 @@ If you think that it is valid, you can select a radio model below to force an op
             d.destroy()
         CONF.set_bool("warned_about_reporting", True)
 
-        if not CONF.is_defined("autorpt", "memedit"):
-            print "autorpt not set et"
-            CONF.set_bool("autorpt", True, "memedit")
-
         self.update_recent_files()
         self.update_stock_configs()
         self.setup_extra_hotkeys()
diff --git a/chirpui/memdetail.py b/chirpui/memdetail.py
index cc605fc..d202964 100644
--- a/chirpui/memdetail.py
+++ b/chirpui/memdetail.py
@@ -34,6 +34,9 @@ class ValueEditor:
     def _init(self, data):
         """Type-specific initialization"""
 
+    def set_sensitive(self, sensitive):
+        self._widget.set_sensitive(sensitive)
+
     def get_widget(self):
         """Returns the widget associated with this editor"""
         return self._widget
@@ -157,21 +160,24 @@ class OffsetEditor(FreqEditor):
 class MemoryDetailEditor(gtk.Dialog):
     """Detail editor for a memory"""
 
-    def _add(self, tab, row, name, editor, labeltxt):
+    def _add(self, tab, row, name, editor, labeltxt, colindex=0):
         label = gtk.Label(labeltxt)
         img = gtk.Image()
 
         label.show()
-        tab.attach(label, 0, 1, row, row+1)
+        tab.attach(label, colindex, colindex + 1, row, row + 1)
+        colindex += 1
 
         editor.get_widget().show()
-        tab.attach(editor.get_widget(), 1, 2, row, row+1)
+        tab.attach(editor.get_widget(), colindex, colindex + 1, row, row + 1)
+        colindex += 1
 
         img.set_size_request(15, -1)
         img.show()
-        tab.attach(img, 2, 3, row, row+1)
+        tab.attach(img, colindex, colindex + 1, row, row + 1)
 
         self._editors[name] = label, editor, img
+        return label, editor, img
 
     def _set_doc(self, name, doc):
         label, editor, _img = self._editors[name]
@@ -179,8 +185,12 @@ class MemoryDetailEditor(gtk.Dialog):
         self._tips.set_tip(editor.get_widget(), doc)
 
     def _make_ui(self):
+        sw = gtk.ScrolledWindow()
+        sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+        sw.show()
         tab = gtk.Table(len(self._order), 3, False)
-        self.vbox.pack_start(tab, 1, 1, 1)
+        sw.add_with_viewport(tab)
+        self.vbox.pack_start(sw, 1, 1, 1)
         tab.show()
 
         row = 0
@@ -225,18 +235,21 @@ class MemoryDetailEditor(gtk.Dialog):
             row += 1
             self._order.append(name)
 
+    def _title(self):
+        return _("Edit Memory #{num}").format(num=self._memory.number)
+
     def __init__(self, features, memory, parent=None):
+        self._memory = memory
         gtk.Dialog.__init__(self,
-                            title=_("Edit Memory"
-                                    "#{num}").format(num=memory.number),
+                            title=self._title(),
                             flags=gtk.DIALOG_MODAL,
                             parent=parent,
                             buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
                                      gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
+        self.set_size_request(-1, 500)
         self._tips = gtk.Tooltips()
 
         self._features = features
-        self._memory = memory
 
         self._editors = {}
         self._elements = {
@@ -322,3 +335,34 @@ class MemoryDetailEditor(gtk.Dialog):
     def get_memory(self):
         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()
+        super(MultiMemoryDetailEditor, self).__init__(features, memory, parent)
+
+    def _toggle_selector(self, selector, *widgets):
+        for widget in widgets:
+            widget.set_sensitive(selector.get_active())
+
+    def _add(self, tab, row, name, editor, labeltxt):
+        label, editor, img = super(MultiMemoryDetailEditor, self)._add(
+            tab, row, name, editor, labeltxt, 1)
+
+        selector = gtk.CheckButton()
+        tab.attach(selector, 0, 1, row, row + 1)
+        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/chirpui/memedit.py
index 5764ee0..b0c3ab9 100644
--- a/chirpui/memedit.py
+++ b/chirpui/memedit.py
@@ -33,6 +33,7 @@ import pickle
 import os
 
 from chirpui import common, shiftdialog, miscwidgets, config, memdetail
+from chirpui import bandplans
 from chirp import chirp_common, errors, directory, import_logic
 
 def handle_toggle(_, path, store, col):
@@ -104,10 +105,8 @@ class MemoryEditor(common.Editor):
     choices = {
         _("Tone")          : chirp_common.TONES,
         _("ToneSql")       : chirp_common.TONES,
-        _("DTCS Code")     : sorted(chirp_common.DTCS_CODES +
-                                    chirp_common.DTCS_EXTRA_CODES),
-        _("DTCS Rx Code")  : sorted(chirp_common.DTCS_CODES +
-                                    chirp_common.DTCS_EXTRA_CODES),
+        _("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")         : [],
@@ -121,13 +120,14 @@ class MemoryEditor(common.Editor):
         return self.rthread.radio.filter_name(new)
 
     def ed_offset(self, _, path, new, __):
-        return chirp_common.parse_freq(new)
+        new = chirp_common.parse_freq(new)
+        return abs(new)
 
     def ed_freq(self, _foo, path, new, colnum):
         iter = self.store.get_iter(path)
-        prev, = self.store.get(iter, colnum)
+        was_filled, prev = self.store.get(iter, self.col("_filled"), colnum)
 
-        def set_offset(path, offset):
+        def set_offset(offset):
             if offset > 0:
                 dup = "+"
             elif offset == 0:
@@ -136,17 +136,37 @@ class MemoryEditor(common.Editor):
                 dup = "-"
                 offset *= -1
 
+            if not dup in self.choices[_("Duplex")]:
+                print "Duplex %s not supported by this radio" % dup
+                return
+
             if offset:
                 self.store.set(iter, self.col(_("Offset")), offset)
 
             self.store.set(iter, self.col(_("Duplex")), dup)
 
         def set_ts(ts):
-            self.store.set(iter, self.col(_("Tune Step")), ts)
+            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
 
         def get_ts(path):
             return self.store.get(iter, self.col(_("Tune Step")))[0]
 
+        def set_mode(mode):
+            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")])
+
+        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
+
         try:
             new = chirp_common.parse_freq(new)
         except ValueError, e:
@@ -156,16 +176,16 @@ class MemoryEditor(common.Editor):
         if not self._features.has_nostep_tuning:
             set_ts(chirp_common.required_step(new))
 
-        if new and self._config.get_bool("autorpt") and new != prev:
-            try:
-                band = chirp_common.freq_to_band(new)
-                set_offset(path, 0)
-                for lo, hi, offset in chirp_common.STD_OFFSETS[band]:
-                    if new > lo and new < hi:
-                        set_offset(path, offset)
-                        break
-            except Exception, e:
-                pass
+        is_changed = new != prev if was_filled else True
+        if new is not None and is_changed:
+            defaults = self.bandplans.get_defaults_for_frequency(new)
+            set_offset(defaults.offset or 0)
+            if defaults.step_khz:
+                set_ts(defaults.step_khz)
+            if defaults.mode:
+                set_mode(defaults.mode)
+            if defaults.tones:
+                set_tone(defaults.tones[0])
 
         return new
 
@@ -192,15 +212,58 @@ class MemoryEditor(common.Editor):
             # RX frequency as the default TX frequency
             self.store.set(iter, self.col("Offset"), freq)
         else:
-            band = int(freq / 100000000)
-            if chirp_common.STD_OFFSETS.has_key(band):
-                offset = chirp_common.STD_OFFSETS[band][0][2]
-            else:
-                offset = 0
+            defaults = self.bandplans.get_defaults_for_frequency(freq)
+            offset = defaults.offset or 0
             self.store.set(iter, self.col(_("Offset")), abs(offset))
 
         return new
 
+    def ed_tone_field(self, _foo, path, new, col):
+        if self._config.get_bool("no_smart_tmode"):
+            return new
+
+        iter = self.store.get_iter(path)
+
+        # Python scoping hurts us here, so store this as a list
+        # that we can modify, instead of helpful variables :(
+        modes = list(self.store.get(iter,
+                                    self.col(_("Tone Mode")),
+                                    self.col(_("Cross Mode"))))
+
+        def _tm(*tmodes):
+            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]
+                self.store.set(iter, self.col(_("Cross Mode")), modes[1])
+
+        if col == self.col(_("DTCS Code")):
+            _tm("DTCS", "Cross")
+            _cm(*tuple([x for x in chirp_common.CROSS_MODES
+                        if x.startswith("DTCS->")]))
+        elif col == self.col(_("DTCS Rx Code")):
+            _tm("Cross")
+            _cm(*tuple([x for x in chirp_common.CROSS_MODES
+                        if x.endswith("->DTCS")]))
+        elif col == self.col(_("DTCS Pol")):
+            _tm("DTCS", "Cross")
+            _cm(*tuple([x for x in chirp_common.CROSS_MODES
+                        if "DTCS" in x]))
+        elif col == self.col(_("Tone")):
+            _tm("Tone", "Cross")
+            _cm(*tuple([x for x in chirp_common.CROSS_MODES
+                        if x.startswith("Tone->")]))
+        elif col == self.col(_("ToneSql")):
+            _tm("TSQL", "Cross")
+            _cm(*tuple([x for x in chirp_common.CROSS_MODES
+                        if x.endswith("->Tone")]))
+        elif col == self.col(_("Cross Mode")):
+            _tm("Cross")
+
+        return new
+
     def _get_cols_to_hide(self, iter):
         tmode, duplex, cmode = self.store.get(iter,
                                               self.col(_("Tone Mode")),
@@ -248,7 +311,7 @@ class MemoryEditor(common.Editor):
             if "DTCS" not in cmode:
                 hide += [self.col(_("DTCS Pol"))]
 
-        if duplex == "" or duplex == "(None)":
+        if duplex == "" or duplex == "(None)" or duplex == "off":
             hide += [self.col(_("Offset"))]
 
 
@@ -276,6 +339,12 @@ class MemoryEditor(common.Editor):
             _("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,
+            _("DTCS Pol"): self.ed_tone_field,
+            _("Cross Mode"): self.ed_tone_field,
             }
 
         if funcs.has_key(cap):
@@ -306,7 +375,7 @@ class MemoryEditor(common.Editor):
 
         msgs = self.rthread.radio.validate_memory(mem)
         if msgs:
-            common.show_error(_("Error setting memory") + \
+            common.show_error(_("Error setting memory") + ": " + \
                                   "\r\n\r\n".join(msgs))
             self.prefill()
             return
@@ -397,7 +466,7 @@ class MemoryEditor(common.Editor):
         else:
             iter = store.iter_next(_iter)
 
-        pos, = store.get(iter, self.col("Loc"))
+        pos, = store.get(iter, self.col(_("Loc")))
 
         sd = shiftdialog.ShiftDialog(self.rthread)
 
@@ -455,12 +524,12 @@ class MemoryEditor(common.Editor):
 
         return True # We changed memories
 
-    def _delete_rows_and_shift(self, paths):
+    def _delete_rows_and_shift(self, paths, all=False):
         iter = self.store.get_iter(paths[0])
         starting_loc, = self.store.get(iter, self.col(_("Loc")))
         for i in range(0, len(paths)):
             sd = shiftdialog.ShiftDialog(self.rthread)
-            sd.delete(starting_loc, quiet=True)
+            sd.delete(starting_loc, quiet=True, all=all)
             sd.destroy()
 
         self.prefill()
@@ -640,23 +709,57 @@ class MemoryEditor(common.Editor):
         job.set_desc(_("Getting raw memory {number}").format(number=loc_b))
         self.rthread.submit(job)
 
-    def edit_memory(self, memory):
-        dlg = memdetail.MemoryDetailEditor(self._features, memory)
+    def _copy_field(self, src_memory, dst_memory, field):
+        if field.startswith("extra_"):
+            field = field.split("_", 1)[1]
+            value = src_memory.extra[field].value.get_value()
+            dst_memory.extra[field].value = value
+        else:
+            setattr(dst_memory, field, getattr(src_memory, field))
+
+    def _apply_multiple(self, src_memory, fields, locations):
+        for location in locations:
+            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)
+                    job = common.RadioJob(cb, "set_memory", memory)
+                    job.set_desc(_("Writing memory {number}").format(
+                            number=memory.number))
+                    self.rthread.submit(job)
+            job = common.RadioJob(apply_and_set, "get_memory", location)
+            job.set_desc(_("Getting original memory {number}").format(
+                    number=location))
+            self.rthread.submit(job)
+
+    def edit_memory(self, memory, locations):
+        if len(locations) > 1:
+            dlg = memdetail.MultiMemoryDetailEditor(self._features, memory)
+        else:
+            dlg = memdetail.MemoryDetailEditor(self._features, memory)
         r = dlg.run()
         if r == gtk.RESPONSE_OK:
             self.need_refresh = True
             mem = dlg.get_memory()
-            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))
-            self.rthread.submit(job)
-            self.emit("changed")
+            if len(locations) > 1:
+                self._apply_multiple(memory, dlg.get_fields(), locations)
+            else:
+                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))
+                self.rthread.submit(job)
         dlg.destroy()
 
     def mh(self, _action, store, paths):
         action = _action.get_name()
-        iter = store.get_iter(paths[0])
-        cur_pos, = store.get(iter, self.col(_("Loc")))
+        selected = []
+        for path in paths:
+            iter = store.get_iter(path)
+            loc, = store.get(iter, self.col(_("Loc")))
+            selected.append(loc)
+        cur_pos = selected[0]
 
         require_contiguous = ["delete_s", "move_up", "move_dn"]
         if action in require_contiguous:
@@ -677,6 +780,8 @@ class MemoryEditor(common.Editor):
             changed = self._delete_rows(paths)
         elif action == "delete_s":
             changed = self._delete_rows_and_shift(paths)
+        elif action == "delete_sall":
+            changed = self._delete_rows_and_shift(paths, all=True)
         elif action in ["move_up", "move_dn"]:
             changed = self._move_up_down(paths, action)
         elif action == "exchange":
@@ -691,6 +796,7 @@ class MemoryEditor(common.Editor):
             self._diff_raw(paths)
         elif action == "edit":
             job = common.RadioJob(self.edit_memory, "get_memory", cur_pos)
+            job.set_cb_args(selected)
             self.rthread.submit(job)
 
         if changed:
@@ -724,8 +830,11 @@ class MemoryEditor(common.Editor):
     <menuitem action="edit"/>
     <menuitem action="insert_prev"/>
     <menuitem action="insert_next"/>
-    <menuitem action="delete"/>
-    <menuitem action="delete_s"/>
+    <menu action="deletes">
+      <menuitem action="delete"/>
+      <menuitem action="delete_s"/>
+      <menuitem action="delete_sall"/>
+    </menu>
     <menuitem action="move_up"/>
     <menuitem action="move_dn"/>
     <menuitem action="exchange"/>
@@ -747,8 +856,10 @@ class MemoryEditor(common.Editor):
             ("edit", _("Edit")),
             ("insert_prev", _("Insert row above")),
             ("insert_next", _("Insert row below")),
-            ("delete", issingle and _("Delete") or _("Delete all")),
-            ("delete_s", _("Delete (and shift up)")),
+            ("deletes", _("Delete")),
+            ("delete", issingle and _("this memory") or _("these memories")),
+            ("delete_s", _("...and shift block up")),
+            ("delete_sall", _("...and shift all memories up")),
             ("move_up", _("Move up")),
             ("move_dn", _("Move down")),
             ("exchange", _("Exchange memories")),
@@ -1022,7 +1133,7 @@ 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))
+            raise Exception(_("Internal Error: Invalid limit {number}").format(number=which))
         return "%s_%s" % (directory.radio_class_id(self.rthread.radio.__class__),
                           which)
 
@@ -1110,12 +1221,6 @@ class MemoryEditor(common.Editor):
         self.prefill()
         self._config.set_bool("hide_empty", not show)
 
-    def set_read_only(self, read_only):
-        self.read_only = read_only
-
-    def get_read_only(self):
-        return self.read_only
-
     def set_hide_unused(self, hide_unused):
         self.hide_unused = hide_unused
         self.prefill()
@@ -1175,13 +1280,14 @@ class MemoryEditor(common.Editor):
         return user_visible
 
     def __init__(self, rthread):
-        common.Editor.__init__(self)
-        self.rthread = rthread
+        super(MemoryEditor, self).__init__(rthread)
 
         self.defaults = dict(self.defaults)
 
         self._config = config.get("memedit")
 
+        self.bandplans = bandplans.BandPlans(config.get())
+
         self.allowed_bands = [144, 440]
         self.count = 100
         self.show_special = self._config.get_bool("show_special")
@@ -1208,6 +1314,8 @@ class MemoryEditor(common.Editor):
         self.choices[_("Power")] = [str(x) for x in
                                     self._features["valid_power_levels"]]
         self.choices[_("DTCS Pol")] = self._features["valid_dtcs_pols"]
+        self.choices[_("DTCS Code")] = self._features["valid_dtcs_codes"]
+        self.choices[_("DTCS Rx Code")] = self._features["valid_dtcs_codes"]
 
         if self._features["valid_power_levels"]:
             self.defaults[_("Power")] = self._features["valid_power_levels"][0]
@@ -1371,7 +1479,9 @@ class MemoryEditor(common.Editor):
         cols = self.view.get_columns()
         self._config.set("column_order_%s" % self.__class__.__name__,
                          ",".join([x.get_title() for x in cols]))
-            
+
+    def other_editor_changed(self, target_editor):
+        self.need_refresh = True
 
 class DstarMemoryEditor(MemoryEditor):
     def _get_cols_to_hide(self, iter):
diff --git a/chirpui/radiobrowser.py b/chirpui/radiobrowser.py
new file mode 100644
index 0000000..db96b04
--- /dev/null
+++ b/chirpui/radiobrowser.py
@@ -0,0 +1,320 @@
+import gtk
+import gobject
+import pango
+import re
+import os
+
+from chirp import bitwise
+from chirpui import common
+
+def do_insert_line_with_tags(b, line):
+    def i(text, *tags):
+        b.insert_with_tags_by_name(b.get_end_iter(), text, *tags)
+
+    def ident(name):
+        if "unknown" in name:
+            i(name, 'grey', 'bold')
+        else:
+            i(name, 'bold')
+
+    def nonzero(value):
+        i(value, 'red', 'bold')
+
+    def foo(value):
+        i(value, 'blue', 'bold')
+
+    m = re.match("^( *)([A-z0-9_]+: )(0x[A-F0-9]+) \((.*)\)$", line)
+    if m:
+        i(m.group(1))
+        ident(m.group(2))
+        if m.group(3) == '0x00':
+            i(m.group(3))
+        else:
+            nonzero(m.group(3))
+        i(' (')
+        for char in m.group(4):
+            if char == '1':
+                nonzero(char)
+            else:
+                i(char)
+        i(')')
+        return
+
+    m = re.match("^( *)([A-z0-9_]+: )(.*)$", line)
+    if m:
+        i(m.group(1))
+        ident(m.group(2))
+        i(m.group(3))
+        return
+
+    m = re.match("^(.*} )([A-z0-9_]+)( \()([0-9]+)( bytes at )(0x[A-F0-9]+)",
+                 line)
+    if m:
+        i(m.group(1))
+        ident(m.group(2))
+        i(m.group(3))
+        foo(m.group(4))
+        i(m.group(5))
+        foo(m.group(6))
+        i(")")
+        return
+
+    i(line)
+
+def do_insert_with_tags(buf, text):
+    buf.set_text('')
+    lines = text.split(os.linesep)
+    for line in lines:
+        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")
+        self.modify_font(fontdesc)
+
+class IntegerEntry(FixedEntry):
+    def _colorize(self, _self):
+        value = self.get_text()
+        if value.startswith("0x"):
+            value = value[2:]
+        value = value.replace("0", "")
+        if not value:
+            self.modify_text(gtk.STATE_NORMAL, None)
+        else:
+            self.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse('red'))
+
+    def __init__(self, *args, **kwargs):
+        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:
+            return
+        value = entry.get_text()
+        if value.startswith("0x"):
+            value = value[2:]
+        self._element.set_value(int(value, base))
+        self._update_entries(skip=entry)
+
+    def _update_entries(self, skip=None):
+        self._update = False
+        for ent, format_spec in self._entries:
+            if ent != skip:
+                ent.set_text(format_spec.format(int(self._element)))
+        self._update = True
+
+    def _build_ui(self):
+        self._entries = []
+        self._update = True
+
+        hexdigits = ((self._element.size() / 4) +
+                     (self._element.size() % 4 and 1 or 0))
+        formats = [('Hex', 16, '0x{:0%iX}' % hexdigits),
+                   ('Dec', 10, '{:d}'),
+                   ('Bin', 2, '{:0%ib}' % self._element.size())]
+        for name, base, format_spec in formats:
+            lab = gtk.Label(name)
+            self.pack_start(lab, 0, 0, 0)
+            lab.show()
+            int(self._element)
+            ent = IntegerEntry()
+            self._entries.append((ent, format_spec))
+            ent.connect('changed', self._changed, base)
+            self.pack_start(ent, 0, 0, 0)
+            ent.show()
+        self._update_entries()
+
+class BCDArrayEditor(BitwiseEditor):
+    def _changed(self, entry, hexent):
+        self._element.set_value(int(entry.get_text()))
+        self._format_hexent(hexent)
+
+    def _format_hexent(self, hexent):
+        value = ""
+        for i in self._element:
+            a, b = i.get_value()
+            value += "%i%i" % (a, b)
+        hexent.set_text(value)
+
+    def _build_ui(self):
+        lab = gtk.Label("Dec")
+        lab.show()
+        self.pack_start(lab, 0, 0, 0)
+        ent = FixedEntry()
+        ent.set_text(str(int(self._element)))
+        ent.show()
+        self.pack_start(ent, 1, 1, 1)
+
+        lab = gtk.Label("Hex")
+        lab.show()
+        self.pack_start(lab, 0, 0, 0)
+
+        hexent = FixedEntry()
+        hexent.show()
+        self.pack_start(hexent, 1, 1, 1)
+        hexent.set_editable(False)
+
+        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)))
+
+    def _build_ui(self):
+        ent = FixedEntry(len(self._element))
+        ent.set_text(str(self._element))
+        ent.connect('changed', self._changed)
+        ent.show()
+        self.pack_start(ent, 1, 1, 1)
+
+class OtherEditor(BitwiseEditor):
+    def _build_ui(self):
+        name = classname(self._element)
+        name = bitwise_type(name)
+        if isinstance(self._element, bitwise.arrayDataElement):
+            name += " %s[%i]" % (
+                bitwise_type(classname(self._element[0])),
+                len(self._element))
+
+        l = gtk.Label(name)
+        l.show()
+        self.pack_start(l, 1, 1, 1)
+
+class RadioBrowser(common.Editor):
+    def _build_ui(self):
+        self._display = gtk.Table(20, 2)
+
+        self._store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
+        self._tree = gtk.TreeView(self._store)
+
+        rend = gtk.CellRendererText()
+        tvc = gtk.TreeViewColumn('Element', rend, text=0)
+        self._tree.append_column(tvc)
+        self._tree.connect('button_press_event', self._tree_click)
+        self._tree.set_size_request(200, -1)
+
+        self.root = gtk.HBox(False, 3)
+        sw = gtk.ScrolledWindow()
+        sw.add(self._tree)
+        sw.show()
+        self.root.pack_start(sw, 0, 0, 0)
+        sw = gtk.ScrolledWindow()
+        sw.add_with_viewport(self._display)
+        sw.show()
+        self.root.pack_start(sw, 1, 1, 1)
+        self._tree.show()
+        self._display.show()
+        self.root.show()
+
+    def _fill(self, name, obj, parent=None):
+        iter = self._store.append(parent, (name, obj))
+
+        if isinstance(obj, bitwise.structDataElement):
+            for name, item in obj.items():
+                if isinstance(item, bitwise.structDataElement):
+                    self._fill(name, item, iter)
+                elif isinstance(item, bitwise.arrayDataElement):
+                    self._fill("%s[%i]" % (name, len(item)), item, iter)
+        elif isinstance(obj, bitwise.arrayDataElement):
+            i = 0
+            for item in obj:
+                if isinstance(obj[0], bitwise.structDataElement):
+                    self._fill("%s[%i]" % (name, i), item, iter)
+                i += 1
+
+    def _tree_click(self, view, event):
+        if event.button != 1:
+            return
+
+        index = [0]
+
+        def pack(widget, pos):
+            self._display.attach(widget, pos, pos + 1, index[0], index[0] + 1,
+                                 xoptions=gtk.FILL, yoptions=0)
+
+        def next_row():
+            index[0] += 1
+
+        def abandon(child):
+            self._display.remove(child)
+
+        pathinfo = view.get_path_at_pos(int(event.x), int(event.y))
+        path = pathinfo[0]
+        iter = self._store.get_iter(path)
+        name, obj = self._store.get(iter, 0, 1)
+
+        self._display.foreach(abandon)
+
+        for name, item in obj.items():
+            if item.size() % 8 == 0:
+                name = '<b>%s</b> <small>(%s %i bytes)</small>' % (
+                    name, bitwise_type(classname(item)), item.size() / 8)
+            else:
+                name = '<b>%s</b> <small>(%s %i bits)</small>' % (
+                    name, bitwise_type(classname(item)), item.size())
+            l = gtk.Label(name + "   ")
+            l.set_use_markup(True)
+            l.show()
+            pack(l, 0)
+
+            if (isinstance(item, bitwise.intDataElement) or
+                isinstance(item, bitwise.bcdDataElement)):
+                e = IntegerEditor(item)
+            elif (isinstance(item, bitwise.arrayDataElement) and
+                  isinstance(item[0], bitwise.bcdDataElement)):
+                e = BCDArrayEditor(item)
+            elif (isinstance(item, bitwise.arrayDataElement) and
+                  isinstance(item[0], bitwise.charDataElement)):
+                e = CharArrayEditor(item)
+            else:
+                e = OtherEditor(item)
+            e.show()
+            pack(e, 1)
+            next_row()
+
+
+    def __init__(self, rthread):
+        super(RadioBrowser, self).__init__(rthread)
+        self._radio = rthread.radio
+        self._focused = False
+        self._build_ui()
+        self._fill('root', self._radio._memobj)
+
+    def focus(self):
+        self._focused = True
+
+    def unfocus(self):
+        if self._focused:
+            self.emit("changed")
+            self._focused = False
+
+if __name__ == "__main__":
+    from chirp 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)
+    w.add(b.root)
+    w.show()
+    gtk.main()
diff --git a/chirpui/settingsedit.py b/chirpui/settingsedit.py
index b67982f..535296b 100644
--- a/chirpui/settingsedit.py
+++ b/chirpui/settingsedit.py
@@ -26,9 +26,8 @@ class RadioSettingProxy(settings.RadioSetting):
 
 class SettingsEditor(common.Editor):
     def __init__(self, rthread):
-        common.Editor.__init__(self)
-        self._rthread = 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)
@@ -57,15 +56,23 @@ class SettingsEditor(common.Editor):
 
         job = common.RadioJob(self._build_ui, "get_settings")
         job.set_desc("Getting radio settings")
-        self._rthread.submit(job)
+        self.rthread.submit(job)
 
     def _save_settings(self):
         if self._top_setting_group is None:
             return
 
-        job = common.RadioJob(None, "set_settings", self._top_setting_group)
+        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)
+        self.rthread.submit(job)
 
     def _load_setting(self, value, widget):
         if isinstance(value, settings.RadioSettingValueInteger):
@@ -73,6 +80,8 @@ class SettingsEditor(common.Editor):
             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):
@@ -89,9 +98,11 @@ class SettingsEditor(common.Editor):
             print "Unsupported widget type %s for %s" % (value.__class__,
                                                          element.get_name())
             
-    def _save_setting(self, widget, value):
+    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):
@@ -103,8 +114,15 @@ class SettingsEditor(common.Editor):
                 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,
@@ -139,6 +157,9 @@ class SettingsEditor(common.Editor):
                     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"
@@ -157,10 +178,16 @@ class SettingsEditor(common.Editor):
                 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)
-                widget.connect(signal, self._save_setting, value)
+                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
 
diff --git a/chirpui/shiftdialog.py b/chirpui/shiftdialog.py
index f35802e..0ca50a4 100644
--- a/chirpui/shiftdialog.py
+++ b/chirpui/shiftdialog.py
@@ -65,12 +65,15 @@ class ShiftDialog(gtk.Dialog):
                         count / len(memories))
 
             i.number = dst
-            self.rthread.radio.set_memory(i)
+            if i.empty:
+                self.rthread.radio.erase_memory(i.number)
+            else:
+                self.rthread.radio.set_memory(i)
             count += 1.0
 
         return int(count)
 
-    def _get_mems_until_hole(self, start, endokay=False):
+    def _get_mems_until_hole(self, start, endokay=False, all=False):
         mems = []
 
         llimit, ulimit = self.rthread.radio.get_features().memory_bounds
@@ -81,7 +84,7 @@ class ShiftDialog(gtk.Dialog):
                         number=pos), 0)
             try:
                 mem = self.rthread.radio.get_memory(pos)
-                if mem.empty:
+                if mem.empty and not all:
                     break
             except errors.InvalidMemoryLocation:
                 break
@@ -109,9 +112,8 @@ class ShiftDialog(gtk.Dialog):
             print "No memory list?"
             return 0
 
-    def _delete_hole(self, start):
-        mems = self._get_mems_until_hole(start+1, endokay=True)
-            
+    def _delete_hole(self, start, all=False):
+        mems = self._get_mems_until_hole(start+1, endokay=True, all=all)
         if mems:
             count = self._shift_memories(-1, mems)
             self.rthread.radio.erase_memory(count+start)
@@ -126,12 +128,12 @@ class ShiftDialog(gtk.Dialog):
         else:
             gobject.idle_add(self.set_response_sensitive, gtk.RESPONSE_OK, True)
 
-    def threadfn(self, newhole, func):
+    def threadfn(self, newhole, func, *args):
         self.status("Waiting for radio to become available", 0)
         self.rthread.lock()
 
         try:
-            count = func(newhole)
+            count = func(newhole, *args)
         except errors.InvalidMemoryLocation, e:
             self.status(str(e), 0)
             self.finished()
@@ -145,13 +147,13 @@ class ShiftDialog(gtk.Dialog):
     def insert(self, newhole, quiet=False):
         self.quiet = quiet
         self.thread = threading.Thread(target=self.threadfn,
-                                       args=(newhole,self._insert_hole))
+                                       args=(newhole, self._insert_hole))
         self.thread.start()
         gtk.Dialog.run(self)
 
-    def delete(self, newhole, quiet=False):
+    def delete(self, newhole, quiet=False, all=False):
         self.quiet = quiet
         self.thread = threading.Thread(target=self.threadfn,
-                                       args=(newhole,self._delete_hole))
+                                       args=(newhole, self._delete_hole, all))
         self.thread.start()
         gtk.Dialog.run(self)
diff --git a/chirpw b/chirpw
index cd97918..570d13c 100755
--- a/chirpw
+++ b/chirpw
@@ -15,14 +15,15 @@
 # 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
 
+from chirp import elib_intl
 from chirp import platform, CHIRP_VERSION
 from chirpui import config
 
 # Hack to setup environment
 platform.get_platform()
 
-import gtk
 import sys
 import os
 import locale
@@ -51,12 +52,14 @@ 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",
+    lang_codes = { "English"         : "en_US",
+                   "Polish"          : "pl",
+                   "Italian"         : "it",
+                   "Dutch"           : "nl",
+                   "German"          : "de",
+                   "Hungarian"       : "hu",
+                   "Russian"         : "ru",
+                   "Portuguese (BR)" : "pt_BR",
                    }
     try:
         print lang_codes[manual_language]
@@ -72,12 +75,21 @@ else:
     except:
         pass
 
+try:
+    if os.name == "nt":
+        elib_intl._putenv("LANG", langs[0])
+    else:
+        os.putenv("LANG", langs[0])
+except IndexError:
+    pass
 path = "locale"
 gettext.bindtextdomain("CHIRP", localepath)
 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,
 # we can install our own, which returns a string of the following class,
@@ -91,7 +103,13 @@ class CompatStr(str):
         return base
 
 pyver = sys.version.split()[0]
-vmaj, vmin, vrel = pyver.split(".", 3)
+
+try :
+    vmaj, vmin, vrel = pyver.split(".", 3)
+except :
+    vmaj, vmin = pyver.split(".", 2)
+    vrel = 0
+
 if int(vmaj) < 2 or int(vmin) < 6:
     # Python <2.6, emulate str.format()
     import __builtin__
diff --git a/locale/de/LC_MESSAGES/CHIRP.mo b/locale/de/LC_MESSAGES/CHIRP.mo
new file mode 100644
index 0000000..127e167
Binary files /dev/null and b/locale/de/LC_MESSAGES/CHIRP.mo differ
diff --git a/locale/en_US/LC_MESSAGES/CHIRP.mo b/locale/en_US/LC_MESSAGES/CHIRP.mo
new file mode 100644
index 0000000..46476c1
Binary files /dev/null and b/locale/en_US/LC_MESSAGES/CHIRP.mo differ
diff --git a/locale/hu/LC_MESSAGES/CHIRP.mo b/locale/hu/LC_MESSAGES/CHIRP.mo
new file mode 100644
index 0000000..0709dbf
Binary files /dev/null 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
new file mode 100644
index 0000000..0b94898
Binary files /dev/null and b/locale/it/LC_MESSAGES/CHIRP.mo differ
diff --git a/locale/nl/LC_MESSAGES/CHIRP.mo b/locale/nl/LC_MESSAGES/CHIRP.mo
new file mode 100644
index 0000000..39d703e
Binary files /dev/null and b/locale/nl/LC_MESSAGES/CHIRP.mo differ
diff --git a/locale/pl/LC_MESSAGES/CHIRP.mo b/locale/pl/LC_MESSAGES/CHIRP.mo
new file mode 100644
index 0000000..022eee2
Binary files /dev/null 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
new file mode 100644
index 0000000..07fe797
Binary files /dev/null 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
new file mode 100644
index 0000000..8e9b2e1
Binary files /dev/null and b/locale/ru/LC_MESSAGES/CHIRP.mo differ

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