# HG changeset patch # User Jim Unroe # 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 -# -# 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 . - -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 +# +# 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 . + +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 -# -# 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 . - -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 +# +# 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 . + +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 -# -# 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 . - -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 +# +# 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 . + +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)