# HG changeset patch
# User Jim Unroe <rock.unroe@gmail.com>
# Date 1357409273 18000
# Node ID 9aa807654b91f7639c390e0e41ab75ad8d91daca
# Parent  8aa4a6ceb520d12b48af469cebee442eaeeae067
[uv5r] workmode menu settings demo
rough draft only

diff -r 8aa4a6ceb520 -r 9aa807654b91 chirp/settings.py
--- a/chirp/settings.py	Fri Jan 04 14:22:46 2013 -0800
+++ b/chirp/settings.py	Sat Jan 05 13:07:53 2013 -0500
@@ -1,290 +1,333 @@
-# Copyright 2012 Dan Smith <dsmith@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/>.
-
-from chirp import chirp_common
-
-class InvalidValueError(Exception):
-    """An invalid value was specified for a given setting"""
-    pass
-
-class InternalError(Exception):
-    """A driver provided an invalid settings object structure"""
-    pass
-
-class RadioSettingValue:
-    """Base class for a single radio setting"""
-    def __init__(self):
-        self._current = None
-        self._has_changed = False
-
-    def changed(self):
-        """Returns True if the setting has been changed since init"""
-        return self._has_changed
-
-    def set_value(self, value):
-        """Sets the current value, triggers changed"""
-        if self._current != None and value != self._current:
-            self._has_changed = True
-        self._current = value
-
-    def get_value(self):
-        """Gets the current value"""
-        return self._current
-
-    def __trunc__(self):
-        return int(self.get_value())
-
-    def __str__(self):
-        return str(self.get_value())
-
-class RadioSettingValueInteger(RadioSettingValue):
-    """An integer setting"""
-    def __init__(self, minval, maxval, current, step=1):
-        RadioSettingValue.__init__(self)
-        self._min = minval
-        self._max = maxval
-        self._step = step
-        self.set_value(current)
-
-    def set_value(self, value):
-        try:
-            value = int(value)
-        except:
-            raise InvalidValueError("An integer is required")
-        if value > self._max or value < self._min:
-            raise InvalidValueError("Value %i not in range %i-%i" % (value,
-                                                                     self._min,
-                                                                     self._max))
-        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"""
-        return self._max
-
-    def get_step(self):
-        """Returns the step increment"""
-        return self._step
-
-class RadioSettingValueBoolean(RadioSettingValue):
-    """A boolean setting"""
-    def __init__(self, current):
-        RadioSettingValue.__init__(self)
-        self.set_value(current)
-
-    def set_value(self, value):
-        RadioSettingValue.set_value(self, bool(value))
-
-    def __str__(self):
-        return str(bool(self.get_value()))
-
-class RadioSettingValueList(RadioSettingValue):
-    """A list-of-strings setting"""
-    def __init__(self, options, current):
-        RadioSettingValue.__init__(self)
-        self._options = options
-        self.set_value(current)
-
-    def set_value(self, value):
-        if not value in self._options:
-            raise InvalidValueError("%s is not valid for this setting" % value)
-        RadioSettingValue.set_value(self, value)
-
-    def get_options(self):
-        """Returns the list of valid option values"""
-        return self._options
-
-    def __trunc__(self):
-        return self._options.index(self._current)
-
-class RadioSettingValueString(RadioSettingValue):
-    """A string setting"""
-    def __init__(self, minlength, maxlength, current,
-                 autopad=True):
-        RadioSettingValue.__init__(self)
-        self._minlength = minlength
-        self._maxlength = maxlength
-        self._charset = chirp_common.CHARSET_ASCII
-        self._autopad = autopad
-        self.set_value(current)
-
-    def set_charset(self, charset):
-        """Sets the set of allowed characters"""
-        self._charset = charset
-
-    def set_value(self, value):
-        if len(value) < self._minlength or len(value) > self._maxlength:
-            raise InvalidValueError("Value must be between %i and %i chars" % (\
-                    self._minlength, self._maxlength))
-        if self._autopad:
-            value = value.ljust(self._maxlength)
-        for char in value:
-            if char not in self._charset:
-                raise InvalidValueError("Value contains invalid " +
-                                        "character `%s'" % char)
-        RadioSettingValue.set_value(self, value)
-
-    def __str__(self):
-        return self._current
-
-class RadioSettingGroup(object):
-    """A group of settings"""
-    def _validate(self, element):
-        # RadioSettingGroup can only contain RadioSettingGroup objects
-        if not isinstance(element, RadioSettingGroup):
-            raise InternalError("Incorrect type")
-
-    def __init__(self, name, shortname, *elements):
-        self._name = name           # Setting identifier
-        self._shortname = shortname # Short human-readable name/description
-        self.__doc__ = name         # Longer explanation/documentation
-        self._elements = {}
-        self._element_order = []
-        
-        for element in elements:
-            self._validate(element)
-            print "Appending element to %s" % self._name
-            self.append(element)
-
-    def get_name(self):
-        """Returns the group name"""
-        return self._name
-
-    def get_shortname(self):
-        """Returns the short group identifier"""
-        return self._shortname
-
-    def set_doc(self, doc):
-        """Sets the docstring for the group"""
-        self.__doc__ = doc
-
-    def __str__(self):
-        string = "{Settings Group %s:\n" % self._name
-        for element in self._elements.values():
-            string += str(element) + "\n"
-        string += "}"
-        return string
-
-    # Kinda list interface
-
-    def append(self, element):
-        """Adds an element to the group"""
-        self[element.get_name()] = element
-
-    def __iter__(self):
-        class RSGIterator:
-            """Iterator for a RadioSettingsGroup"""
-            def __init__(self, rsg):
-                self.__rsg = rsg
-                self.__i = 0
-            def __iter__(self):
-                return self
-            def next(self):
-                """Next Iterator Interface"""
-                if self.__i >= len(self.__rsg.keys()):
-                    raise StopIteration()
-                e =  self.__rsg[self.__rsg.keys()[self.__i]]
-                self.__i += 1
-                return e
-        return RSGIterator(self)
-
-    # Dictionary interface
-
-    def __len__(self):
-        return len(self._elements)
-
-    def __getitem__(self, name):
-        return self._elements[name]
-
-    def __setitem__(self, name, value):
-        if name in self._element_order:
-            raise KeyError("Duplicate item %s" % name)
-        self._elements[name] = value
-        self._element_order.append(name)
-
-    def items(self):
-        """Returns a key=>value set of elements, like a dict"""
-        return [(name, self._elements[name]) for name in self._element_order]
-
-    def keys(self):
-        """Returns a list of string element names"""
-        return self._element_order
-
-    def values(self):
-        """Returns the list of elements"""
-        return [self._elements[name] for name in self._element_order]
-
-class RadioSetting(RadioSettingGroup):
-    """A single setting, which could be an array of items like a group"""
-    def _validate(self, value):
-        # RadioSetting can only contain RadioSettingValue objects
-        if not isinstance(value, RadioSettingValue):
-            raise InternalError("Incorrect type")
-
-    def changed(self):
-        """Returns True if any of the elements in the group have been changed"""
-        for element in self._elements.values():
-            if element.changed():
-                return True
-        return False
-
-    def __str__(self):
-        return "%s:%s" % (self._name, self.value)
-
-    def __repr__(self):
-        return "[RadioSetting %s:%s]" % (self._name, self._value)
-
-    # Magic foo.value attribute
-    def __getattr__(self, name):
-        if name == "value":
-            if len(self) == 1:
-                return self._elements[self._element_order[0]]
-            else:
-                return self._elements.values()
-        else:
-            return self.__dict__[name]
-
-    def __setattr__(self, name, value):
-        if name == "value":
-            if len(self) == 1:
-                self._elements[self._element_order[0]].set_value(value)
-            else:
-                raise InternalError("Setting %s is not a scalar" % self._name)
-        else:
-            self.__dict__[name] = value
-            
-    # List interface
-
-    def append(self, value):
-        index = len(self._element_order)
-        self._elements[index] = value
-        self._element_order.append(index)
-
-    def __getitem__(self, name):
-        if not isinstance(name, int):
-            raise IndexError("Index `%s' is not an integer" % name)
-        return self._elements[name]
-
-    def __setitem__(self, name, value):
-        if not isinstance(name, int):
-            raise IndexError("Index `%s' is not an integer" % name)
-        if self._elements.has_key(name):
-            self._elements[name].set_value(value)
-        else:
-            self._elements[name] = value
-
+# Copyright 2012 Dan Smith <dsmith@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/>.
+
+from chirp import chirp_common
+
+class InvalidValueError(Exception):
+    """An invalid value was specified for a given setting"""
+    pass
+
+class InternalError(Exception):
+    """A driver provided an invalid settings object structure"""
+    pass
+
+class RadioSettingValue:
+    """Base class for a single radio setting"""
+    def __init__(self):
+        self._current = None
+        self._has_changed = False
+        self._callback = lambda x: x
+
+    def changed(self):
+        """Returns True if the setting has been changed since init"""
+        return self._has_changed
+
+    def set_callback(self, callback):
+        self._callback = callback
+
+    def set_value(self, value):
+        """Sets the current value, triggers changed"""
+        if self._current != None and value != self._current:
+            self._has_changed = True
+        self._current = self._callback(value)
+
+    def get_value(self):
+        """Gets the current value"""
+        return self._current
+
+    def __trunc__(self):
+        return int(self.get_value())
+
+    def __str__(self):
+        return str(self.get_value())
+
+class RadioSettingValueInteger(RadioSettingValue):
+    """An integer setting"""
+    def __init__(self, minval, maxval, current, step=1):
+        RadioSettingValue.__init__(self)
+        self._min = minval
+        self._max = maxval
+        self._step = step
+        self.set_value(current)
+
+    def set_value(self, value):
+        try:
+            value = int(value)
+        except:
+            raise InvalidValueError("An integer is required")
+        if value > self._max or value < self._min:
+            raise InvalidValueError("Value %i not in range %i-%i" % (value,
+                                                                     self._min,
+                                                                     self._max))
+        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"""
+        return self._max
+
+    def get_step(self):
+        """Returns the step increment"""
+        return self._step
+
+class RadioSettingValueFloat(RadioSettingValue):
+    """A floating-point setting"""
+    def __init__(self, minval, maxval, current, resolution=0.001, precision=5):
+        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):
+        RadioSettingValue.__init__(self)
+        self.set_value(current)
+
+    def set_value(self, value):
+        RadioSettingValue.set_value(self, bool(value))
+
+    def __str__(self):
+        return str(bool(self.get_value()))
+
+class RadioSettingValueList(RadioSettingValue):
+    """A list-of-strings setting"""
+    def __init__(self, options, current):
+        RadioSettingValue.__init__(self)
+        self._options = options
+        self.set_value(current)
+
+    def set_value(self, value):
+        if not value in self._options:
+            raise InvalidValueError("%s is not valid for this setting" % value)
+        RadioSettingValue.set_value(self, value)
+
+    def get_options(self):
+        """Returns the list of valid option values"""
+        return self._options
+
+    def __trunc__(self):
+        return self._options.index(self._current)
+
+class RadioSettingValueString(RadioSettingValue):
+    """A string setting"""
+    def __init__(self, minlength, maxlength, current,
+                 autopad=True):
+        RadioSettingValue.__init__(self)
+        self._minlength = minlength
+        self._maxlength = maxlength
+        self._charset = chirp_common.CHARSET_ASCII
+        self._autopad = autopad
+        self.set_value(current)
+
+    def set_charset(self, charset):
+        """Sets the set of allowed characters"""
+        self._charset = charset
+
+    def set_value(self, value):
+        if len(value) < self._minlength or len(value) > self._maxlength:
+            raise InvalidValueError("Value must be between %i and %i chars" % (\
+                    self._minlength, self._maxlength))
+        if self._autopad:
+            value = value.ljust(self._maxlength)
+        for char in value:
+            if char not in self._charset:
+                raise InvalidValueError("Value contains invalid " +
+                                        "character `%s'" % char)
+        RadioSettingValue.set_value(self, value)
+
+    def __str__(self):
+        return self._current
+
+class RadioSettingGroup(object):
+    """A group of settings"""
+    def _validate(self, element):
+        # RadioSettingGroup can only contain RadioSettingGroup objects
+        if not isinstance(element, RadioSettingGroup):
+            raise InternalError("Incorrect type")
+
+    def __init__(self, name, shortname, *elements):
+        self._name = name           # Setting identifier
+        self._shortname = shortname # Short human-readable name/description
+        self.__doc__ = name         # Longer explanation/documentation
+        self._elements = {}
+        self._element_order = []
+        
+        for element in elements:
+            self._validate(element)
+            print "Appending element to %s" % self._name
+            self.append(element)
+
+    def get_name(self):
+        """Returns the group name"""
+        return self._name
+
+    def get_shortname(self):
+        """Returns the short group identifier"""
+        return self._shortname
+
+    def set_doc(self, doc):
+        """Sets the docstring for the group"""
+        self.__doc__ = doc
+
+    def __str__(self):
+        string = "{Settings Group %s:\n" % self._name
+        for element in self._elements.values():
+            string += str(element) + "\n"
+        string += "}"
+        return string
+
+    # Kinda list interface
+
+    def append(self, element):
+        """Adds an element to the group"""
+        self[element.get_name()] = element
+
+    def __iter__(self):
+        class RSGIterator:
+            """Iterator for a RadioSettingsGroup"""
+            def __init__(self, rsg):
+                self.__rsg = rsg
+                self.__i = 0
+            def __iter__(self):
+                return self
+            def next(self):
+                """Next Iterator Interface"""
+                if self.__i >= len(self.__rsg.keys()):
+                    raise StopIteration()
+                e =  self.__rsg[self.__rsg.keys()[self.__i]]
+                self.__i += 1
+                return e
+        return RSGIterator(self)
+
+    # Dictionary interface
+
+    def __len__(self):
+        return len(self._elements)
+
+    def __getitem__(self, name):
+        return self._elements[name]
+
+    def __setitem__(self, name, value):
+        if name in self._element_order:
+            raise KeyError("Duplicate item %s" % name)
+        self._elements[name] = value
+        self._element_order.append(name)
+
+    def items(self):
+        """Returns a key=>value set of elements, like a dict"""
+        return [(name, self._elements[name]) for name in self._element_order]
+
+    def keys(self):
+        """Returns a list of string element names"""
+        return self._element_order
+
+    def values(self):
+        """Returns the list of elements"""
+        return [self._elements[name] for name in self._element_order]
+
+class RadioSetting(RadioSettingGroup):
+    """A single setting, which could be an array of items like a group"""
+    def _validate(self, value):
+        # RadioSetting can only contain RadioSettingValue objects
+        if not isinstance(value, RadioSettingValue):
+            raise InternalError("Incorrect type")
+
+    def changed(self):
+        """Returns True if any of the elements in the group have been changed"""
+        for element in self._elements.values():
+            if element.changed():
+                return True
+        return False
+
+    def __str__(self):
+        return "%s:%s" % (self._name, self.value)
+
+    def __repr__(self):
+        return "[RadioSetting %s:%s]" % (self._name, self._value)
+
+    # Magic foo.value attribute
+    def __getattr__(self, name):
+        if name == "value":
+            if len(self) == 1:
+                return self._elements[self._element_order[0]]
+            else:
+                return self._elements.values()
+        else:
+            return self.__dict__[name]
+
+    def __setattr__(self, name, value):
+        if name == "value":
+            if len(self) == 1:
+                self._elements[self._element_order[0]].set_value(value)
+            else:
+                raise InternalError("Setting %s is not a scalar" % self._name)
+        else:
+            self.__dict__[name] = value
+            
+    # List interface
+
+    def append(self, value):
+        index = len(self._element_order)
+        self._elements[index] = value
+        self._element_order.append(index)
+
+    def __getitem__(self, name):
+        if not isinstance(name, int):
+            raise IndexError("Index `%s' is not an integer" % name)
+        return self._elements[name]
+
+    def __setitem__(self, name, value):
+        if not isinstance(name, int):
+            raise IndexError("Index `%s' is not an integer" % name)
+        if self._elements.has_key(name):
+            self._elements[name].set_value(value)
+        else:
+            self._elements[name] = value
+
diff -r 8aa4a6ceb520 -r 9aa807654b91 chirp/uv5r.py
--- a/chirp/uv5r.py	Fri Jan 04 14:22:46 2013 -0800
+++ b/chirp/uv5r.py	Sat Jan 05 13:07:53 2013 -0500
@@ -1,796 +1,924 @@
-# Copyright 2012 Dan Smith <dsmith@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 time
-
-from chirp import chirp_common, errors, util, directory, memmap
-from chirp import bitwise
-from chirp.settings import RadioSetting, RadioSettingGroup, \
-    RadioSettingValueInteger, RadioSettingValueList, \
-    RadioSettingValueList, RadioSettingValueBoolean, \
-    RadioSettingValueString
-
-MEM_FORMAT = """
-#seekto 0x0008;
-struct {
-  lbcd rxfreq[4];
-  lbcd txfreq[4];
-  ul16 rxtone;
-  ul16 txtone;
-  u8 unknown1[2];
-  u8 unknown2:7,
-     lowpower:1;
-  u8 unknown3:1,
-     wide:1,
-     unknown4:3,
-     scan:1,
-     unknown5:2;
-} memory[128];
-
-#seekto 0x0CB2;
-struct {
-  u8 code[5];
-} ani;
-
-#seekto 0x0E28;
-struct {
-  u8 squelch;
-  u8 step;
-  u8 unknown1;
-  u8 save;
-  u8 vox;
-  u8 unknown2;
-  u8 abr;
-  u8 tdr;
-  u8 beep;
-  u8 timeout;
-  u8 unknown3[4];
-  u8 voice;
-  u8 unknown4;
-  u8 dtmfst;
-  u8 unknown5;
-  u8 screv;
-  u8 pttid;
-  u8 pttlt;
-  u8 mdfa;
-  u8 mdfb;
-  u8 bcl;
-  u8 autolk;
-  u8 sftd;
-  u8 unknown6[3];
-  u8 wtled;
-  u8 rxled;
-  u8 txled;
-  u8 almod;
-  u8 band;
-  u8 tdrab;
-  u8 ste;
-  u8 rpste;
-  u8 rptrl;
-  u8 ponmsg;
-  u8 roger;
-} settings[2];
-
-#seekto 0x0E52;
-struct {
-  u8 disp_ab:1,
-     unknown1:2,
-     fmradio:1,
-     alarm:1,
-     unknown2:1,
-     reset:1,
-     menu:1;
-} extra;
-
-#seekto 0x1000;
-struct {
-  u8 unknown1[8];
-  char name[7];
-  u8 unknown2;
-} names[128];
-
-#seekto 0x1818;
-struct {
-  char line1[7];
-  char line2[7];
-} sixpoweron_msg;
-
-#seekto 0x1828;
-struct {
-  char line1[7];
-  char line2[7];
-} poweron_msg;
-
-struct limit {
-  u8 enable;
-  bbcd lower[2];
-  bbcd upper[2];
-};
-
-#seekto 0x1908;
-struct {
-  struct limit vhf;
-  struct limit uhf;
-} limits_new;
-
-#seekto 0x1910;
-struct {
-  u8 unknown1[2];
-  struct limit vhf;
-  u8 unknown2;
-  u8 unknown3[8];
-  u8 unknown4[2];
-  struct limit uhf;
-} limits_old;
-
-"""
-
-# 0x1EC0 - 0x2000
-
-STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0]
-STEP_LIST = [str(x) for x in STEPS]
-TIMEOUT_LIST = ["%s sec" % x for x in range(15, 615, 15)]
-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"]
-
-SETTING_LISTS = {
-    "step" : STEP_LIST,
-    "timeout" : TIMEOUT_LIST,
-    "dtmfst" : DTMFST_LIST,
-    "screv" : RESUME_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,
-}
-
-def _do_status(radio, block):
-    status = chirp_common.Status()
-    status.msg = "Cloning"
-    status.cur = block
-    status.max = radio.get_memsize()
-    radio.status_fn(status)
-
-def validate_orig(ident):
-    try:
-        ver = int(ident[4:7])
-        if ver >= 291:
-            raise errors.RadioError("Radio version %i not supported" % ver)
-    except ValueError:
-        raise errors.RadioError("Radio reported invalid version string")
-
-def validate_291(ident):
-    if ident[4:7] != "\x30\x04\x50":
-        raise errors.RadioError("Radio version not supported")
-
-UV5R_MODEL_ORIG = "\x50\xBB\xFF\x01\x25\x98\x4D"
-UV5R_MODEL_291 = "\x50\xBB\xFF\x20\x12\x07\x25"
-
-IDENTS = [UV5R_MODEL_ORIG,
-          UV5R_MODEL_291,
-          ]
-
-def _firmware_version_from_image(radio):
-    return radio.get_mmap()[0x1838:0x1848]
-
-def _do_ident(radio, magic):
-    serial = radio.pipe
-    serial.setTimeout(1)
-
-    print "Sending Magic: %s" % util.hexprint(magic)
-    serial.write(magic)
-    ack = serial.read(1)
-    
-    if ack != "\x06":
-        if ack:
-            print repr(ack)
-        raise errors.RadioError("Radio did not respond")
-
-    serial.write("\x02")
-    ident = serial.read(8)
-
-    print "Ident:\n%s" % util.hexprint(ident)
-
-    serial.write("\x06")
-    ack = serial.read(1)
-    if ack != "\x06":
-        raise errors.RadioError("Radio refused clone")
-
-    return ident
-
-def _read_block(radio, start, size):
-    msg = struct.pack(">BHB", ord("S"), start, size)
-    radio.pipe.write(msg)
-
-    answer = radio.pipe.read(4)
-    if len(answer) != 4:
-        raise errors.RadioError("Radio refused to send block 0x%04x" % start)
-
-    cmd, addr, length = struct.unpack(">BHB", answer)
-    if cmd != ord("X") or addr != start or length != size:
-        print "Invalid answer for block 0x%04x:" % start
-        print "CMD: %s  ADDR: %04x  SIZE: %02x" % (cmd, addr, length)
-        raise errors.RadioError("Unknown response from radio")
-
-    chunk = radio.pipe.read(0x40)
-    if not chunk:
-        raise errors.RadioError("Radio did not send block 0x%04x" % start)
-    elif len(chunk) != size:
-        print "Chunk length was 0x%04i" % len(chunk)
-        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]
-
-def _ident_radio(radio):
-    for magic in IDENTS:
-        error = None
-        try:
-            data = _do_ident(radio, magic)
-            return data
-        except errors.RadioError, e:
-            print e
-            error = e
-            time.sleep(2)
-    if error:
-        raise error
-    raise errors.RadioError("Radio did not respond")
-
-def _do_download(radio):
-    data = _ident_radio(radio)
-
-    # Main block
-    for i in range(0, 0x1800, 0x40):
-        data += _read_block(radio, i, 0x40)
-        _do_status(radio, i)
-
-    # Auxiliary block starts at 0x1ECO (?)
-    for i in range(0x1EC0, 0x2000, 0x40):
-        data += _read_block(radio, i, 0x40)
-
-    return memmap.MemoryMap(data)
-
-def _send_block(radio, addr, data):
-    msg = struct.pack(">BHB", ord("X"), addr, len(data))
-    radio.pipe.write(msg + 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)
-
-    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 "Ver  BFB" not in radio_version:
-        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])
-        _do_status(radio, i)
-
-    if len(radio.get_mmap().get_packed()) == 0x1808:
-        print "Old image, not writing aux block"
-        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")
-
-    # Auxiliary block at radio address 0x1EC0, our offset 0x1808
-    for i in range(0x1EC0, 0x2000, 0x10):
-        addr = 0x1808 + (i - 0x1EC0)
-        _send_block(radio, i, radio.get_mmap()[addr:addr+0x10])
-
-UV5R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=4.00),
-                     chirp_common.PowerLevel("Low",  watts=1.00)]
-
-UV5R_DTCS = sorted(chirp_common.DTCS_CODES + [645])
-
-UV5R_CHARSET = chirp_common.CHARSET_UPPER_NUMERIC + \
-    "!@#$%^&*()+-=[]:\";'<>?,./"
-
-# Uncomment this to actually register this radio in CHIRP
-@directory.register
-class BaofengUV5R(chirp_common.CloneModeRadio,
-                  chirp_common.ExperimentalRadio):
-    """Baofeng UV-5R"""
-    VENDOR = "Baofeng"
-    MODEL = "UV-5R"
-    BAUD_RATE = 9600
-
-    _memsize = 0x1808
-
-    @classmethod
-    def get_experimental_warning(cls):
-        return ('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!')
-
-    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.can_odd_split = True
-        rf.valid_name_length = 7
-        rf.valid_characters = UV5R_CHARSET
-        rf.valid_skips = ["", "S"]
-        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
-        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
-                                "->Tone", "->DTCS", "DTCS->"]
-        rf.valid_power_levels = UV5R_POWER_LEVELS
-        rf.valid_duplexes = ["", "-", "+", "split", "off"]
-        rf.valid_modes = ["FM", "NFM"]
-        rf.valid_bands = [(136000000, 174000000), (400000000, 512000000)]
-        rf.memory_bounds = (0, 127)
-        return rf
-
-    @classmethod
-    def match_model(cls, filedata, filename):
-        return len(filedata) in [0x1808, 0x1948]
-
-    def process_mmap(self):
-        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
-        print self.get_settings()
-
-    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 get_raw_memory(self, number):
-        return repr(self._memobj.memory[number])
-
-    def _is_txinh(self, _mem):
-        raw_tx = ""
-        for i in range(0, 4):
-            raw_tx += _mem.txfreq[i].get_raw()
-        return raw_tx == "\xFF\xFF\xFF\xFF"
-
-    def get_memory(self, number):
-        _mem = self._memobj.memory[number]
-        _nam = self._memobj.names[number]
-
-        mem = chirp_common.Memory()
-        mem.number = number
-
-        if _mem.get_raw()[0] == "\xff":
-            mem.empty = True
-            return mem
-
-        mem.freq = int(_mem.rxfreq) * 10
-
-        if self._is_txinh(_mem):
-            mem.duplex = "off"
-            mem.offset = 0
-        elif int(_mem.rxfreq) == int(_mem.txfreq):
-            mem.duplex = ""
-            mem.offset = 0
-        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
-            mem.duplex = "split"
-            mem.offset = int(_mem.txfreq) * 10
-        else:
-            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
-            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
-
-        for char in _nam.name:
-            if str(char) == "\xFF":
-                char = " " # The UV-5R software may have 0xFF mid-name
-            mem.name += str(char)
-        mem.name = mem.name.rstrip()
-
-        dtcs_pol = ["N", "N"]
-
-        if _mem.txtone in [0, 0xFFFF]:
-            txmode = ""
-        elif _mem.txtone >= 0x0258:
-            txmode = "Tone"
-            mem.rtone = int(_mem.txtone) / 10.0
-        elif _mem.txtone <= 0x0258:
-            txmode = "DTCS"
-            if _mem.txtone > 0x69:
-                index = _mem.txtone - 0x6A
-                dtcs_pol[0] = "R"
-            else:
-                index = _mem.txtone - 1
-            mem.dtcs = UV5R_DTCS[index]
-        else:
-            print "Bug: txtone is %04x" % _mem.txtone
-
-        if _mem.rxtone in [0, 0xFFFF]:
-            rxmode = ""
-        elif _mem.rxtone >= 0x0258:
-            rxmode = "Tone"
-            mem.ctone = int(_mem.rxtone) / 10.0
-        elif _mem.rxtone <= 0x0258:
-            rxmode = "DTCS"
-            if _mem.rxtone >= 0x6A:
-                index = _mem.rxtone - 0x6A
-                dtcs_pol[1] = "R"
-            else:
-                index = _mem.rxtone - 1
-            mem.dtcs = UV5R_DTCS[index]
-        else:
-            print "Bug: rxtone is %04x" % _mem.rxtone
-
-        if txmode == "Tone" and not rxmode:
-            mem.tmode = "Tone"
-        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
-            mem.tmode = "TSQL"
-        elif txmode == rxmode and txmode == "DTCS":
-            mem.tmode = "DTCS"
-        elif rxmode or txmode:
-            mem.tmode = "Cross"
-            mem.cross_mode = "%s->%s" % (txmode, rxmode)
-
-        mem.dtcs_polarity = "".join(dtcs_pol)
-
-        if not _mem.scan:
-            mem.skip = "S"
-
-        mem.power = UV5R_POWER_LEVELS[_mem.lowpower]
-        mem.mode = _mem.wide and "FM" or "NFM"
-
-        return mem
-
-    def set_memory(self, mem):
-        _mem = self._memobj.memory[mem.number]
-        _nam = self._memobj.names[mem.number]
-
-        if mem.empty:
-            _mem.set_raw("\xff" * 16)
-            return
-
-        _mem.set_raw("\x00" * 16)
-
-        _mem.rxfreq = mem.freq / 10
-
-        if mem.duplex == "off":
-            for i in range(0, 4):
-                _mem.txfreq[i].set_raw("\xFF")
-        elif mem.duplex == "split":
-            _mem.txfreq = mem.offset / 10
-        elif mem.duplex == "+":
-            _mem.txfreq = (mem.freq + mem.offset) / 10
-        elif mem.duplex == "-":
-            _mem.txfreq = (mem.freq - mem.offset) / 10
-        else:
-            _mem.txfreq = mem.freq / 10
-
-        for i in range(0, 7):
-            try:
-                _nam.name[i] = mem.name[i]
-            except IndexError:
-                _nam.name[i] = "\xFF"
-
-        rxmode = txmode = ""
-        if mem.tmode == "Tone":
-            _mem.txtone = int(mem.rtone * 10)
-            _mem.rxtone = 0
-        elif mem.tmode == "TSQL":
-            _mem.txtone = int(mem.ctone * 10)
-            _mem.rxtone = int(mem.ctone * 10)
-        elif mem.tmode == "DTCS":
-            rxmode = txmode = "DTCS"
-            _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
-            _mem.rxtone = UV5R_DTCS.index(mem.dtcs) + 1
-        elif mem.tmode == "Cross":
-            txmode, rxmode = mem.cross_mode.split("->", 1)
-            if txmode == "Tone":
-                _mem.txtone = int(mem.rtone * 10)
-            elif txmode == "DTCS":
-                _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
-            else:
-                _mem.txtone = 0
-            if rxmode == "Tone":
-                _mem.rxtone = int(mem.ctone * 10)
-            elif rxmode == "DTCS":
-                _mem.rxtone = UV5R_DTCS.index(mem.dtcs) + 1
-            else:
-                _mem.rxtone = 0
-        else:
-            _mem.rxtone = 0
-            _mem.txtone = 0
-
-        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
-            _mem.txtone += 0x69
-        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
-            _mem.rxtone += 0x69
-
-        _mem.scan = mem.skip != "S"
-        _mem.wide = mem.mode == "FM"
-        _mem.lowpower = mem.power == UV5R_POWER_LEVELS[1]
-
-    def _is_orig(self):
-        version_tag = _firmware_version_from_image(self)
-        try:
-            if 'BFB' in version_tag:
-                idx = version_tag.index("BFB") + 3
-                version = int(version_tag[idx:idx+3])
-                return version < 291
-        except:
-            pass
-        raise errors.RadioError("Unable to parse version string %s" %
-                                version_tag)
-
-    def _my_version(self):
-        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])
-        raise Exception("Unrecognized firmware version string")
-
-    def get_settings(self):
-        _settings = self._memobj.settings[0]
-        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)
-
-        tuning_steps = list(STEP_LIST) # Make a copy of the main list
-        if self._my_version() >= 291:  # update list for BFB291 and newer
-            tuning_steps.insert(5, "20.0")
-            tuning_steps.append("50.0")
-
-        rs = RadioSetting("step", "Tuning Step",
-                          RadioSettingValueList(tuning_steps,
-                                                tuning_steps[_settings.step]))
-        advanced.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))
-        basic.append(rs)
-
-        rs = RadioSetting("vox", "VOX Sensitivity",
-                          RadioSettingValueInteger(0, 10, _settings.vox))
-        advanced.append(rs)
-
-        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)
-
-        rs = RadioSetting("almod", "Alarm Mode",
-                          RadioSettingValueList(ALMOD_LIST,
-                                                ALMOD_LIST[_settings.almod]))
-        advanced.append(rs)
-
-        rs = RadioSetting("beep", "Beep",
-                          RadioSettingValueBoolean(_settings.beep))
-        basic.append(rs)
-
-        rs = RadioSetting("timeout", "Timeout Timer",
-                          RadioSettingValueList(TIMEOUT_LIST,
-                                                TIMEOUT_LIST[_settings.timeout]))
-        basic.append(rs)
-
-        rs = RadioSetting("voice", "Voice",
-                          RadioSettingValueBoolean(_settings.voice))
-        advanced.append(rs)
-        
-        rs = RadioSetting("screv", "Scan Resume",
-                          RadioSettingValueList(RESUME_LIST,
-                                                RESUME_LIST[_settings.screv]))
-        advanced.append(rs)
-
-        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("bcl", "Busy Channel Lockout",
-                          RadioSettingValueBoolean(_settings.bcl))
-        advanced.append(rs)
-
-        rs = RadioSetting("autolk", "Automatic Key Lock",
-                          RadioSettingValueBoolean(_settings.autolk))
-        advanced.append(rs)
-
-        rs = RadioSetting("extra.fmradio", "Broadcast FM Radio",
-                          RadioSettingValueBoolean(self._memobj.extra.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 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.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)
-
-        return group
-
-    def set_settings(self, settings):
-        _settings = self._memobj.settings[0]
-        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()
-                print "Setting %s = %s" % (setting, element.value)
-                setattr(obj, setting, element.value)
-            except Exception, e:
-                print element.get_name()
-                raise
+# Copyright 2012 Dan Smith <dsmith@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 time
+
+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
+
+MEM_FORMAT = """
+#seekto 0x0008;
+struct {
+  lbcd rxfreq[4];
+  lbcd txfreq[4];
+  ul16 rxtone;
+  ul16 txtone;
+  u8 unknown1[2];
+  u8 unknown2:7,
+     lowpower:1;
+  u8 unknown3:1,
+     wide:1,
+     unknown4:3,
+     scan:1,
+     unknown5:2;
+} memory[128];
+
+#seekto 0x0CB2;
+struct {
+  u8 code[5];
+} ani;
+
+#seekto 0x0E28;
+struct {
+  u8 squelch;
+  u8 step;
+  u8 unknown1;
+  u8 save;
+  u8 vox;
+  u8 unknown2;
+  u8 abr;
+  u8 tdr;
+  u8 beep;
+  u8 timeout;
+  u8 unknown3[4];
+  u8 voice;
+  u8 unknown4;
+  u8 dtmfst;
+  u8 unknown5;
+  u8 screv;
+  u8 pttid;
+  u8 pttlt;
+  u8 mdfa;
+  u8 mdfb;
+  u8 bcl;
+  u8 autolk;
+  u8 sftd;
+  u8 unknown6[3];
+  u8 wtled;
+  u8 rxled;
+  u8 txled;
+  u8 almod;
+  u8 band;
+  u8 tdrab;
+  u8 ste;
+  u8 rpste;
+  u8 rptrl;
+  u8 ponmsg;
+  u8 roger;
+} settings[2];
+
+#seekto 0x0E52;
+struct {
+  u8 dispab:1,
+     unknown1:2,
+     fmradio:1,
+     alarm:1,
+     unknown2:1,
+     wmreset:1,
+     menu:1;
+  u8 unknown3;
+  u8 workmode;
+  u8 klock;
+} extra;
+
+#seekto 0x0E7E;
+struct {
+  u8 mrchnuma;
+  u8 mrchnumb;
+} mrchnum;
+
+#seekto 0x0F08;
+struct {
+  u8 unknown1[8];
+  u8 vfoafreq[8];
+  u8 unknown2[10];
+  u8 vfoaband;
+  u8 unknown3[5];
+} vfoa;
+
+#seekto 0x0F28;
+struct {
+  u8 unknown1[8];
+  u8 vfobfreq[8];
+  u8 unknown2[10];
+  u8 vfobband;
+  u8 unknown3[5];
+} vfob;
+
+#seekto 0x1000;
+struct {
+  u8 unknown1[8];
+  char name[7];
+  u8 unknown2;
+} names[128];
+
+#seekto 0x1818;
+struct {
+  char line1[7];
+  char line2[7];
+} sixpoweron_msg;
+
+#seekto 0x1828;
+struct {
+  char line1[7];
+  char line2[7];
+} poweron_msg;
+
+struct limit {
+  u8 enable;
+  bbcd lower[2];
+  bbcd upper[2];
+};
+
+#seekto 0x1908;
+struct {
+  struct limit vhf;
+  struct limit uhf;
+} limits_new;
+
+#seekto 0x1910;
+struct {
+  u8 unknown1[2];
+  struct limit vhf;
+  u8 unknown2;
+  u8 unknown3[8];
+  u8 unknown4[2];
+  struct limit uhf;
+} limits_old;
+
+"""
+
+# 0x1EC0 - 0x2000
+
+STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0]
+STEP_LIST = [str(x) for x in STEPS]
+TIMEOUT_LIST = ["%s sec" % x for x in range(15, 615, 15)]
+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"]
+WORKMODE_LIST = ["Frequency", "Channel"]
+ABLIST_LIST = ["A", "B"]
+BAND_LIST = ["VHF", "UHF"]
+
+SETTING_LISTS = {
+    "step" : STEP_LIST,
+    "timeout" : TIMEOUT_LIST,
+    "dtmfst" : DTMFST_LIST,
+    "screv" : RESUME_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,
+    "workmode" : WORKMODE_LIST,
+    "ablist" : ABLIST_LIST,
+    "band" : BAND_LIST,
+}
+
+def _do_status(radio, block):
+    status = chirp_common.Status()
+    status.msg = "Cloning"
+    status.cur = block
+    status.max = radio.get_memsize()
+    radio.status_fn(status)
+
+def validate_orig(ident):
+    try:
+        ver = int(ident[4:7])
+        if ver >= 291:
+            raise errors.RadioError("Radio version %i not supported" % ver)
+    except ValueError:
+        raise errors.RadioError("Radio reported invalid version string")
+
+def validate_291(ident):
+    if ident[4:7] != "\x30\x04\x50":
+        raise errors.RadioError("Radio version not supported")
+
+UV5R_MODEL_ORIG = "\x50\xBB\xFF\x01\x25\x98\x4D"
+UV5R_MODEL_291 = "\x50\xBB\xFF\x20\x12\x07\x25"
+
+IDENTS = [UV5R_MODEL_ORIG,
+          UV5R_MODEL_291,
+          ]
+
+def _firmware_version_from_image(radio):
+    return radio.get_mmap()[0x1838:0x1848]
+
+def _do_ident(radio, magic):
+    serial = radio.pipe
+    serial.setTimeout(1)
+
+    print "Sending Magic: %s" % util.hexprint(magic)
+    serial.write(magic)
+    ack = serial.read(1)
+
+    if ack != "\x06":
+        if ack:
+            print repr(ack)
+        raise errors.RadioError("Radio did not respond")
+
+    serial.write("\x02")
+    ident = serial.read(8)
+
+    print "Ident:\n%s" % util.hexprint(ident)
+
+    serial.write("\x06")
+    ack = serial.read(1)
+    if ack != "\x06":
+        raise errors.RadioError("Radio refused clone")
+
+    return ident
+
+def _read_block(radio, start, size):
+    msg = struct.pack(">BHB", ord("S"), start, size)
+    radio.pipe.write(msg)
+
+    answer = radio.pipe.read(4)
+    if len(answer) != 4:
+        raise errors.RadioError("Radio refused to send block 0x%04x" % start)
+
+    cmd, addr, length = struct.unpack(">BHB", answer)
+    if cmd != ord("X") or addr != start or length != size:
+        print "Invalid answer for block 0x%04x:" % start
+        print "CMD: %s  ADDR: %04x  SIZE: %02x" % (cmd, addr, length)
+        raise errors.RadioError("Unknown response from radio")
+
+    chunk = radio.pipe.read(0x40)
+    if not chunk:
+        raise errors.RadioError("Radio did not send block 0x%04x" % start)
+    elif len(chunk) != size:
+        print "Chunk length was 0x%04i" % len(chunk)
+        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]
+
+def _ident_radio(radio):
+    for magic in IDENTS:
+        error = None
+        try:
+            data = _do_ident(radio, magic)
+            return data
+        except errors.RadioError, e:
+            print e
+            error = e
+            time.sleep(2)
+    if error:
+        raise error
+    raise errors.RadioError("Radio did not respond")
+
+def _do_download(radio):
+    data = _ident_radio(radio)
+
+    # Main block
+    for i in range(0, 0x1800, 0x40):
+        data += _read_block(radio, i, 0x40)
+        _do_status(radio, i)
+
+    # Auxiliary block starts at 0x1ECO (?)
+    for i in range(0x1EC0, 0x2000, 0x40):
+        data += _read_block(radio, i, 0x40)
+
+    return memmap.MemoryMap(data)
+
+def _send_block(radio, addr, data):
+    msg = struct.pack(">BHB", ord("X"), addr, len(data))
+    radio.pipe.write(msg + 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)
+
+    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 "Ver  BFB" not in radio_version:
+        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])
+        _do_status(radio, i)
+
+    if len(radio.get_mmap().get_packed()) == 0x1808:
+        print "Old image, not writing aux block"
+        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")
+
+    # Auxiliary block at radio address 0x1EC0, our offset 0x1808
+    for i in range(0x1EC0, 0x2000, 0x10):
+        addr = 0x1808 + (i - 0x1EC0)
+        _send_block(radio, i, radio.get_mmap()[addr:addr+0x10])
+
+UV5R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=4.00),
+                     chirp_common.PowerLevel("Low",  watts=1.00)]
+
+UV5R_DTCS = sorted(chirp_common.DTCS_CODES + [645])
+
+UV5R_CHARSET = chirp_common.CHARSET_UPPER_NUMERIC + \
+    "!@#$%^&*()+-=[]:\";'<>?,./"
+
+# Uncomment this to actually register this radio in CHIRP
+@directory.register
+class BaofengUV5R(chirp_common.CloneModeRadio,
+                  chirp_common.ExperimentalRadio):
+    """Baofeng UV-5R"""
+    VENDOR = "Baofeng"
+    MODEL = "UV-5R"
+    BAUD_RATE = 9600
+
+    _memsize = 0x1808
+
+    @classmethod
+    def get_experimental_warning(cls):
+        return ('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!')
+
+    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.can_odd_split = True
+        rf.valid_name_length = 7
+        rf.valid_characters = UV5R_CHARSET
+        rf.valid_skips = ["", "S"]
+        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
+        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
+                                "->Tone", "->DTCS", "DTCS->"]
+        rf.valid_power_levels = UV5R_POWER_LEVELS
+        rf.valid_duplexes = ["", "-", "+", "split", "off"]
+        rf.valid_modes = ["FM", "NFM"]
+        rf.valid_bands = [(136000000, 174000000), (400000000, 512000000)]
+        rf.memory_bounds = (0, 127)
+        return rf
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return len(filedata) in [0x1808, 0x1948]
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+        print self.get_settings()
+
+    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 get_raw_memory(self, number):
+        return repr(self._memobj.memory[number])
+
+    def _is_txinh(self, _mem):
+        raw_tx = ""
+        for i in range(0, 4):
+            raw_tx += _mem.txfreq[i].get_raw()
+        return raw_tx == "\xFF\xFF\xFF\xFF"
+
+    def get_memory(self, number):
+        _mem = self._memobj.memory[number]
+        _nam = self._memobj.names[number]
+
+        mem = chirp_common.Memory()
+        mem.number = number
+
+        if _mem.get_raw()[0] == "\xff":
+            mem.empty = True
+            return mem
+
+        mem.freq = int(_mem.rxfreq) * 10
+
+        if self._is_txinh(_mem):
+            mem.duplex = "off"
+            mem.offset = 0
+        elif int(_mem.rxfreq) == int(_mem.txfreq):
+            mem.duplex = ""
+            mem.offset = 0
+        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
+            mem.duplex = "split"
+            mem.offset = int(_mem.txfreq) * 10
+        else:
+            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
+            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
+
+        for char in _nam.name:
+            if str(char) == "\xFF":
+                char = " " # The UV-5R software may have 0xFF mid-name
+            mem.name += str(char)
+        mem.name = mem.name.rstrip()
+
+        dtcs_pol = ["N", "N"]
+
+        if _mem.txtone in [0, 0xFFFF]:
+            txmode = ""
+        elif _mem.txtone >= 0x0258:
+            txmode = "Tone"
+            mem.rtone = int(_mem.txtone) / 10.0
+        elif _mem.txtone <= 0x0258:
+            txmode = "DTCS"
+            if _mem.txtone > 0x69:
+                index = _mem.txtone - 0x6A
+                dtcs_pol[0] = "R"
+            else:
+                index = _mem.txtone - 1
+            mem.dtcs = UV5R_DTCS[index]
+        else:
+            print "Bug: txtone is %04x" % _mem.txtone
+
+        if _mem.rxtone in [0, 0xFFFF]:
+            rxmode = ""
+        elif _mem.rxtone >= 0x0258:
+            rxmode = "Tone"
+            mem.ctone = int(_mem.rxtone) / 10.0
+        elif _mem.rxtone <= 0x0258:
+            rxmode = "DTCS"
+            if _mem.rxtone >= 0x6A:
+                index = _mem.rxtone - 0x6A
+                dtcs_pol[1] = "R"
+            else:
+                index = _mem.rxtone - 1
+            mem.dtcs = UV5R_DTCS[index]
+        else:
+            print "Bug: rxtone is %04x" % _mem.rxtone
+
+        if txmode == "Tone" and not rxmode:
+            mem.tmode = "Tone"
+        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
+            mem.tmode = "TSQL"
+        elif txmode == rxmode and txmode == "DTCS":
+            mem.tmode = "DTCS"
+        elif rxmode or txmode:
+            mem.tmode = "Cross"
+            mem.cross_mode = "%s->%s" % (txmode, rxmode)
+
+        mem.dtcs_polarity = "".join(dtcs_pol)
+
+        if not _mem.scan:
+            mem.skip = "S"
+
+        mem.power = UV5R_POWER_LEVELS[_mem.lowpower]
+        mem.mode = _mem.wide and "FM" or "NFM"
+
+        return mem
+
+    def set_memory(self, mem):
+        _mem = self._memobj.memory[mem.number]
+        _nam = self._memobj.names[mem.number]
+
+        if mem.empty:
+            _mem.set_raw("\xff" * 16)
+            return
+
+        _mem.set_raw("\x00" * 16)
+
+        _mem.rxfreq = mem.freq / 10
+
+        if mem.duplex == "off":
+            for i in range(0, 4):
+                _mem.txfreq[i].set_raw("\xFF")
+        elif mem.duplex == "split":
+            _mem.txfreq = mem.offset / 10
+        elif mem.duplex == "+":
+            _mem.txfreq = (mem.freq + mem.offset) / 10
+        elif mem.duplex == "-":
+            _mem.txfreq = (mem.freq - mem.offset) / 10
+        else:
+            _mem.txfreq = mem.freq / 10
+
+        for i in range(0, 7):
+            try:
+                _nam.name[i] = mem.name[i]
+            except IndexError:
+                _nam.name[i] = "\xFF"
+
+        rxmode = txmode = ""
+        if mem.tmode == "Tone":
+            _mem.txtone = int(mem.rtone * 10)
+            _mem.rxtone = 0
+        elif mem.tmode == "TSQL":
+            _mem.txtone = int(mem.ctone * 10)
+            _mem.rxtone = int(mem.ctone * 10)
+        elif mem.tmode == "DTCS":
+            rxmode = txmode = "DTCS"
+            _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
+            _mem.rxtone = UV5R_DTCS.index(mem.dtcs) + 1
+        elif mem.tmode == "Cross":
+            txmode, rxmode = mem.cross_mode.split("->", 1)
+            if txmode == "Tone":
+                _mem.txtone = int(mem.rtone * 10)
+            elif txmode == "DTCS":
+                _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
+            else:
+                _mem.txtone = 0
+            if rxmode == "Tone":
+                _mem.rxtone = int(mem.ctone * 10)
+            elif rxmode == "DTCS":
+                _mem.rxtone = UV5R_DTCS.index(mem.dtcs) + 1
+            else:
+                _mem.rxtone = 0
+        else:
+            _mem.rxtone = 0
+            _mem.txtone = 0
+
+        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
+            _mem.txtone += 0x69
+        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
+            _mem.rxtone += 0x69
+
+        _mem.scan = mem.skip != "S"
+        _mem.wide = mem.mode == "FM"
+        _mem.lowpower = mem.power == UV5R_POWER_LEVELS[1]
+
+    def _is_orig(self):
+        version_tag = _firmware_version_from_image(self)
+        try:
+            if 'BFB' in version_tag:
+                idx = version_tag.index("BFB") + 3
+                version = int(version_tag[idx:idx+3])
+                return version < 291
+        except:
+            pass
+        raise errors.RadioError("Unable to parse version string %s" %
+                                version_tag)
+
+    def _my_version(self):
+        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])
+        raise Exception("Unrecognized firmware version string")
+
+    def get_settings(self):
+        _settings = self._memobj.settings[0]
+        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)
+
+        tuning_steps = list(STEP_LIST) # Make a copy of the main list
+        if self._my_version() >= 291:  # update list for BFB291 and newer
+            tuning_steps.insert(5, "20.0")
+            tuning_steps.append("50.0")
+
+        rs = RadioSetting("step", "Tuning Step",
+                          RadioSettingValueList(tuning_steps,
+                                                tuning_steps[_settings.step]))
+        advanced.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))
+        basic.append(rs)
+
+        rs = RadioSetting("vox", "VOX Sensitivity",
+                          RadioSettingValueInteger(0, 10, _settings.vox))
+        advanced.append(rs)
+
+        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)
+
+        rs = RadioSetting("almod", "Alarm Mode",
+                          RadioSettingValueList(ALMOD_LIST,
+                                                ALMOD_LIST[_settings.almod]))
+        advanced.append(rs)
+
+        rs = RadioSetting("beep", "Beep",
+                          RadioSettingValueBoolean(_settings.beep))
+        basic.append(rs)
+
+        rs = RadioSetting("timeout", "Timeout Timer",
+                          RadioSettingValueList(TIMEOUT_LIST,
+                                                TIMEOUT_LIST[_settings.timeout]))
+        basic.append(rs)
+
+        rs = RadioSetting("voice", "Voice",
+                          RadioSettingValueBoolean(_settings.voice))
+        advanced.append(rs)
+        
+        rs = RadioSetting("screv", "Scan Resume",
+                          RadioSettingValueList(RESUME_LIST,
+                                                RESUME_LIST[_settings.screv]))
+        advanced.append(rs)
+
+        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("bcl", "Busy Channel Lockout",
+                          RadioSettingValueBoolean(_settings.bcl))
+        advanced.append(rs)
+
+        rs = RadioSetting("autolk", "Automatic Key Lock",
+                          RadioSettingValueBoolean(_settings.autolk))
+        advanced.append(rs)
+
+        rs = RadioSetting("extra.fmradio", "Broadcast FM Radio",
+                          RadioSettingValueBoolean(self._memobj.extra.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 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.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)
+
+        workmode = RadioSettingGroup("workmode", "Work Mode Settings")
+        group.append(workmode)
+
+        rs = RadioSetting("extra.workmode", "Work Mode",
+                          RadioSettingValueList(WORKMODE_LIST,
+                                                WORKMODE_LIST[self._memobj.extra.workmode]))
+        workmode.append(rs)
+
+        rs = RadioSetting("extra.dispab", "Display",
+                          RadioSettingValueList(ABLIST_LIST,
+                                                ABLIST_LIST[self._memobj.extra.dispab]))
+        workmode.append(rs)
+
+        _mrcna = self._memobj.mrchnum.mrchnuma
+        rs = RadioSetting("mrchnum.mrchnuma", "MR A Channel",
+                          RadioSettingValueInteger(0, 127, _mrcna))
+        workmode.append(rs)
+
+        _mrcnb = self._memobj.mrchnum.mrchnumb
+        rs = RadioSetting("mrchnum.mrchnumb", "MR B Channel",
+                          RadioSettingValueInteger(0, 127, _mrcnb))
+        workmode.append(rs)
+
+        rs = RadioSetting("vfoa.vfoaband", "VFO A Band",
+                          RadioSettingValueList(BAND_LIST,
+                                                BAND_LIST[self._memobj.vfoa.vfoaband]))
+        workmode.append(rs)
+
+#        _vfoa = self._memobj.vfoa.vfoafreq
+#        rs = RadioSetting("vfoa.vfoafreq", "VFO A Frequency",
+#                          RadioSettingValueInteger(0, 9, _vfoa[0]),
+#                          RadioSettingValueInteger(0, 9, _vfoa[1]),
+#                          RadioSettingValueInteger(0, 9, _vfoa[2]),
+#                          RadioSettingValueInteger(0, 9, _vfoa[3]),
+#                          RadioSettingValueInteger(0, 9, _vfoa[4]),
+#                          RadioSettingValueInteger(0, 9, _vfoa[5]),
+#                          RadioSettingValueInteger(0, 9, _vfoa[6]),
+#                          RadioSettingValueInteger(0, 9, _vfoa[7]))
+#        workmode.append(rs)
+
+        rs = RadioSetting("vfob.vfobband", "VFO B Band",
+                          RadioSettingValueList(BAND_LIST,
+                                                BAND_LIST[self._memobj.vfob.vfobband]))
+        workmode.append(rs)
+
+#        _vfob = self._memobj.vfob.vfobfreq
+#        rs = RadioSetting("vfob.vfobfreq", "VFO B Frequency",
+#                          RadioSettingValueInteger(0, 9, _vfob[0]),
+#                          RadioSettingValueInteger(0, 9, _vfob[1]),
+#                          RadioSettingValueInteger(0, 9, _vfob[2]),
+#                          RadioSettingValueInteger(0, 9, _vfob[3]),
+#                          RadioSettingValueInteger(0, 9, _vfob[4]),
+#                          RadioSettingValueInteger(0, 9, _vfob[5]),
+#                          RadioSettingValueInteger(0, 9, _vfob[6]),
+#                          RadioSettingValueInteger(0, 9, _vfob[7]))
+#        workmode.append(rs)
+
+        def my_validate(value):
+            if value < 400 and value > 174:
+                raise InvalidValueError("Can't be between 174-400")
+            return value
+
+        _vfoa = self._memobj.vfoa.vfoafreq
+        vfoa = _vfoa[0] * 100 + _vfoa[1] * 10 + _vfoa[2] + _vfoa[3] * .1 \
+               + _vfoa[4] * .01 + _vfoa[5] * .001 + _vfoa[6] * .0001 \
+               + _vfoa[7] * .00001
+        val1 = RadioSettingValueFloat(136, 512, vfoa)
+        val1.set_callback(my_validate)
+        rs = RadioSetting("footest1", "VFO A Frequency", val1)
+        workmode.append(rs)
+
+        _vfob = self._memobj.vfob.vfobfreq
+        vfob = _vfob[0] * 100 + _vfob[1] * 10 + _vfob[2] + _vfob[3] * .1 \
+               + _vfob[4] * .01 + _vfob[5] * .001 + _vfob[6] * .0001 \
+               + _vfob[7] * .00001
+        val2 = RadioSettingValueFloat(136, 512, vfob)
+        val2.set_callback(my_validate)
+        rs = RadioSetting("footest2", "VFO B Frequency", val2)
+        workmode.append(rs)
+
+        test = RadioSettingGroup("test", "test")
+        group.append(test)
+        def my_validate(value):
+            if value < 300 and value > 200:
+                raise InvalidValueError("Can't be between 200-300")
+            return value
+
+        val = RadioSettingValueFloat(136, 450, 146.52)
+        val.set_callback(my_validate)
+        rs = RadioSetting("footest", "Foo Test", val)
+        test.append(rs)
+
+        return group
+
+    def set_settings(self, settings):
+        _settings = self._memobj.settings[0]
+        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()
+                print "Setting %s = %s" % (setting, element.value)
+                if setting == "footest":
+                    return # This is a fake setting!
+                setattr(obj, setting, element.value)
+            except Exception, e:
+                print element.get_name()
+                raise
diff -r 8aa4a6ceb520 -r 9aa807654b91 chirpui/settingsedit.py
--- a/chirpui/settingsedit.py	Fri Jan 04 14:22:46 2013 -0800
+++ b/chirpui/settingsedit.py	Sat Jan 05 13:07:53 2013 -0500
@@ -1,204 +1,230 @@
-# Copyright 2012 Dan Smith <dsmith@danplanet.com>
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-import gtk
-import gobject
-
-from chirp import chirp_common, settings
-from chirpui import common, miscwidgets
-
-class RadioSettingProxy(settings.RadioSetting):
-    def __init__(self, setting, editor):
-        self._setting = setting
-        self._editor = editor
-
-class SettingsEditor(common.Editor):
-    def __init__(self, rthread):
-        common.Editor.__init__(self)
-        self._rthread = rthread
-
-        self.root = gtk.HBox(False, 10)
-        self._store = gtk.TreeStore(gobject.TYPE_STRING,
-                                    gobject.TYPE_PYOBJECT)
-        self._view = gtk.TreeView(self._store)
-        self._view.set_size_request(150, -1)
-        self._view.connect("button-press-event", self._group_selected)
-        self._view.show()
-        self.root.pack_start(self._view, 0, 0, 0)
-
-        col = gtk.TreeViewColumn("", gtk.CellRendererText(), text=0)
-        self._view.append_column(col)
-
-        self._table = gtk.Table(20, 3)
-        self._table.set_col_spacings(10)
-        self._table.show()
-        self.root.pack_start(self._table, 1, 1, 1)
-
-        self._index = 0
-
-        self._top_setting_group = None
-
-        job = common.RadioJob(self._build_ui, "get_settings")
-        job.set_desc("Getting radio settings")
-        self._rthread.submit(job)
-
-    def _save_settings(self):
-        if self._top_setting_group is None:
-            return
-
-        job = common.RadioJob(None, "set_settings", self._top_setting_group)
-        job.set_desc("Setting radio settings")
-        self._rthread.submit(job)
-
-    def _load_setting(self, value, widget):
-        if isinstance(value, settings.RadioSettingValueInteger):
-            adj = widget.get_adjustment()
-            adj.configure(value.get_value(),
-                          value.get_min(), value.get_max(),
-                          value.get_step(), 1, 0)
-        elif isinstance(value, settings.RadioSettingValueBoolean):
-            widget.set_active(value.get_value())
-        elif isinstance(value, settings.RadioSettingValueList):
-            model = widget.get_model()
-            model.clear()
-            for option in value.get_options():
-                widget.append_text(option)
-            current = value.get_value()
-            index = value.get_options().index(current)
-            widget.set_active(index)
-        elif isinstance(value, settings.RadioSettingValueString):
-            widget.set_text(str(value).rstrip())
-        else:
-            print "Unsupported widget type %s for %s" % (value.__class__,
-                                                         element.get_name())
-            
-    def _save_setting(self, widget, value):
-        if isinstance(value, settings.RadioSettingValueInteger):
-            value.set_value(widget.get_adjustment().get_value())
-        elif isinstance(value, settings.RadioSettingValueBoolean):
-            value.set_value(widget.get_active())
-        elif isinstance(value, settings.RadioSettingValueList):
-            value.set_value(widget.get_active_text())
-        elif isinstance(value, settings.RadioSettingValueString):
-            value.set_value(widget.get_text())
-        else:
-            print "Unsupported widget type %s for %s" % (\
-                element.value.__class__,
-                element.get_name())
-
-        self._save_settings()
-
-    def _build_ui_group(self, group):
-        def pack(widget, pos):
-            self._table.attach(widget, pos, pos+1, self._index, self._index+1,
-                               xoptions=gtk.FILL, yoptions=0)
-
-        def abandon(child):
-            self._table.remove(child)
-        self._table.foreach(abandon)
-
-        self._index = 0
-        for element in group:
-            if not isinstance(element, settings.RadioSetting):
-                continue
-            label = gtk.Label(element.get_shortname())
-            label.set_alignment(1.0, 0.5)
-            label.show()
-            pack(label, 0)
-
-            if isinstance(element.value, list) and \
-                    isinstance(element.value[0],
-                               settings.RadioSettingValueInteger):
-                arraybox = gtk.HBox(3, True)
-            else:
-                arraybox = gtk.VBox(3, True)
-            pack(arraybox, 1)
-            arraybox.show()
-
-            widgets = []
-            for index in element.keys():
-                value = element[index]
-                if isinstance(value, settings.RadioSettingValueInteger):
-                    widget = gtk.SpinButton()
-                    print "Digits: %i" % widget.get_digits()
-                    signal = "value-changed"
-                elif isinstance(value, settings.RadioSettingValueBoolean):
-                    widget = gtk.CheckButton(_("Enabled"))
-                    signal = "toggled"
-                elif isinstance(value, settings.RadioSettingValueList):
-                    widget = miscwidgets.make_choice([], editable=False)
-                    signal = "changed"
-                elif isinstance(value, settings.RadioSettingValueString):
-                    widget = gtk.Entry()
-                    signal = "changed"
-                else:
-                    print "Unsupported widget type: %s" % value.__class__
-
-                # Make sure the widget gets left-aligned to match up
-                # with its label
-                lalign = gtk.Alignment(0, 0, 0, 0)
-                lalign.add(widget)
-                lalign.show()
-
-                arraybox.pack_start(lalign, 1, 1, 1)
-                widget.show()
-                self._load_setting(value, widget)
-                widget.connect(signal, self._save_setting, value)
-
-            self._index += 1
-
-    def _build_tree(self, group, parent):
-        iter = self._store.append(parent)
-        self._store.set(iter, 0, group.get_shortname(), 1, group)
-        
-        if self._set_default is None:
-            # If we haven't found the first page with actual settings on it
-            # yet, then look for one here
-            for element in group:
-                if isinstance(element, settings.RadioSetting):
-                    self._set_default = self._store.get_path(iter), group
-                    break
-
-        for element in group:
-            if not isinstance(element, settings.RadioSetting):
-                self._build_tree(element, iter)
-        self._view.expand_all()
-
-    def _build_ui_real(self, group):
-        if not isinstance(group, settings.RadioSettingGroup):
-            print "Toplevel is not a group"
-            return
-
-        self._set_default = None
-        self._top_setting_group = group
-        self._build_tree(group, None)
-        self._view.set_cursor(self._set_default[0])
-        self._build_ui_group(self._set_default[1])
-
-    def _build_ui(self, group):
-        gobject.idle_add(self._build_ui_real, group)
-
-    def _group_selected(self, view, event):
-        if event.button != 1:
-            return
-
-        try:
-            path, col, x, y = view.get_path_at_pos(int(event.x), int(event.y))
-        except TypeError:
-            return # Didn't click on an actual item
-
-        group, = self._store.get(self._store.get_iter(path), 1)
-        if group:
-            self._build_ui_group(group)
+# Copyright 2012 Dan Smith <dsmith@danplanet.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import gtk
+import gobject
+
+from chirp import chirp_common, settings
+from chirpui import common, miscwidgets
+
+class RadioSettingProxy(settings.RadioSetting):
+    def __init__(self, setting, editor):
+        self._setting = setting
+        self._editor = editor
+
+class SettingsEditor(common.Editor):
+    def __init__(self, rthread):
+        common.Editor.__init__(self)
+        self._rthread = rthread
+
+        self.root = gtk.HBox(False, 10)
+        self._store = gtk.TreeStore(gobject.TYPE_STRING,
+                                    gobject.TYPE_PYOBJECT)
+        self._view = gtk.TreeView(self._store)
+        self._view.set_size_request(150, -1)
+        self._view.connect("button-press-event", self._group_selected)
+        self._view.show()
+        self.root.pack_start(self._view, 0, 0, 0)
+
+        col = gtk.TreeViewColumn("", gtk.CellRendererText(), text=0)
+        self._view.append_column(col)
+
+        self._table = gtk.Table(20, 3)
+        self._table.set_col_spacings(10)
+        self._table.show()
+        self.root.pack_start(self._table, 1, 1, 1)
+
+        self._index = 0
+
+        self._top_setting_group = None
+
+        job = common.RadioJob(self._build_ui, "get_settings")
+        job.set_desc("Getting radio settings")
+        self._rthread.submit(job)
+
+    def _save_settings(self):
+        if self._top_setting_group is None:
+            return
+
+        def setting_cb(result):
+            if isinstance(result, Exception):
+                common.show_error(_("Error in setting value: %s") % result)
+
+        def setting_cb(result):
+            if isinstance(result, Exception):
+                common.show_error(_("Error in setting value: %s") % result)
+
+        job = common.RadioJob(setting_cb, "set_settings",
+                              self._top_setting_group)
+        job.set_desc("Setting radio settings")
+        self._rthread.submit(job)
+
+    def _load_setting(self, value, widget):
+        if isinstance(value, settings.RadioSettingValueInteger):
+            adj = widget.get_adjustment()
+            adj.configure(value.get_value(),
+                          value.get_min(), value.get_max(),
+                          value.get_step(), 1, 0)
+        elif isinstance(value, settings.RadioSettingValueFloat):
+            widget.set_text(value.format())
+        elif isinstance(value, settings.RadioSettingValueBoolean):
+            widget.set_active(value.get_value())
+        elif isinstance(value, settings.RadioSettingValueList):
+            model = widget.get_model()
+            model.clear()
+            for option in value.get_options():
+                widget.append_text(option)
+            current = value.get_value()
+            index = value.get_options().index(current)
+            widget.set_active(index)
+        elif isinstance(value, settings.RadioSettingValueString):
+            widget.set_text(str(value).rstrip())
+        else:
+            print "Unsupported widget type %s for %s" % (value.__class__,
+                                                         element.get_name())
+            
+    def _do_save_setting(self, widget, value):
+        if isinstance(value, settings.RadioSettingValueInteger):
+            value.set_value(widget.get_adjustment().get_value())
+        elif isinstance(value, settings.RadioSettingValueFloat):
+            value.set_value(widget.get_text())
+        elif isinstance(value, settings.RadioSettingValueBoolean):
+            value.set_value(widget.get_active())
+        elif isinstance(value, settings.RadioSettingValueList):
+            value.set_value(widget.get_active_text())
+        elif isinstance(value, settings.RadioSettingValueString):
+            value.set_value(widget.get_text())
+        else:
+            print "Unsupported widget type %s for %s" % (\
+                element.value.__class__,
+                element.get_name())
+
+        self._save_settings()
+
+    def _save_setting(self, widget, value):
+        try:
+            self._do_save_setting(widget, value)
+        except settings.InvalidValueError, e:
+            common.show_error(_("Invalid setting value: %s") % e)
+
+    def _build_ui_group(self, group):
+        def pack(widget, pos):
+            self._table.attach(widget, pos, pos+1, self._index, self._index+1,
+                               xoptions=gtk.FILL, yoptions=0)
+
+        def abandon(child):
+            self._table.remove(child)
+        self._table.foreach(abandon)
+
+        self._index = 0
+        for element in group:
+            if not isinstance(element, settings.RadioSetting):
+                continue
+            label = gtk.Label(element.get_shortname())
+            label.set_alignment(1.0, 0.5)
+            label.show()
+            pack(label, 0)
+
+            if isinstance(element.value, list) and \
+                    isinstance(element.value[0],
+                               settings.RadioSettingValueInteger):
+                arraybox = gtk.HBox(3, True)
+            else:
+                arraybox = gtk.VBox(3, True)
+            pack(arraybox, 1)
+            arraybox.show()
+
+            widgets = []
+            for index in element.keys():
+                value = element[index]
+                if isinstance(value, settings.RadioSettingValueInteger):
+                    widget = gtk.SpinButton()
+                    print "Digits: %i" % widget.get_digits()
+                    signal = "value-changed"
+                elif isinstance(value, settings.RadioSettingValueFloat):
+                    widget = gtk.Entry()
+                    signal = "focus-out-event"
+                elif isinstance(value, settings.RadioSettingValueBoolean):
+                    widget = gtk.CheckButton(_("Enabled"))
+                    signal = "toggled"
+                elif isinstance(value, settings.RadioSettingValueList):
+                    widget = miscwidgets.make_choice([], editable=False)
+                    signal = "changed"
+                elif isinstance(value, settings.RadioSettingValueString):
+                    widget = gtk.Entry()
+                    signal = "changed"
+                else:
+                    print "Unsupported widget type: %s" % value.__class__
+
+                # Make sure the widget gets left-aligned to match up
+                # with its label
+                lalign = gtk.Alignment(0, 0, 0, 0)
+                lalign.add(widget)
+                lalign.show()
+
+                arraybox.pack_start(lalign, 1, 1, 1)
+                widget.show()
+                self._load_setting(value, widget)
+                if signal == "focus-out-event":
+                    widget.connect(signal, lambda w, e, v:
+                                       self._save_setting(w, v), value)
+                else:
+                    widget.connect(signal, self._save_setting, value)
+
+            self._index += 1
+
+    def _build_tree(self, group, parent):
+        iter = self._store.append(parent)
+        self._store.set(iter, 0, group.get_shortname(), 1, group)
+        
+        if self._set_default is None:
+            # If we haven't found the first page with actual settings on it
+            # yet, then look for one here
+            for element in group:
+                if isinstance(element, settings.RadioSetting):
+                    self._set_default = self._store.get_path(iter), group
+                    break
+
+        for element in group:
+            if not isinstance(element, settings.RadioSetting):
+                self._build_tree(element, iter)
+        self._view.expand_all()
+
+    def _build_ui_real(self, group):
+        if not isinstance(group, settings.RadioSettingGroup):
+            print "Toplevel is not a group"
+            return
+
+        self._set_default = None
+        self._top_setting_group = group
+        self._build_tree(group, None)
+        self._view.set_cursor(self._set_default[0])
+        self._build_ui_group(self._set_default[1])
+
+    def _build_ui(self, group):
+        gobject.idle_add(self._build_ui_real, group)
+
+    def _group_selected(self, view, event):
+        if event.button != 1:
+            return
+
+        try:
+            path, col, x, y = view.get_path_at_pos(int(event.x), int(event.y))
+        except TypeError:
+            return # Didn't click on an actual item
+
+        group, = self._store.get(self._store.get_iter(path), 1)
+        if group:
+            self._build_ui_group(group)
