# HG changeset patch
# User Jens Jensen <kd4tjx@yahoo.com>
# Date 1388386233 21600
#      Mon Dec 30 00:50:33 2013 -0600
# Node ID b2abb304c6a5e5eea5f69555d4ea176e5bfa565d
# Parent  855cab699f14f2bc02e91c86eed7251c19f5ff03
[bj-uv55] refactor bj-uv55 into bjuv55.py, part 3 #1335

diff -r 855cab699f14 -r b2abb304c6a5 chirp/bjuv55.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/chirp/bjuv55.py	Mon Dec 30 00:50:33 2013 -0600
@@ -0,0 +1,630 @@
+# Copyright 2013 Jens Jensen AF5MI <kd4tjx@yahoo.com>
+# Based on work by Jim Unroe, Dan Smith, et al.
+# Special thanks to Mats SM0BTP for equipment donation.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import struct
+import time
+import os
+
+from chirp import chirp_common, errors, util, directory, memmap
+from chirp import bitwise
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettingValueString, \
+    RadioSettingValueFloat, InvalidValueError
+from textwrap import dedent
+from chirp import uv5r
+
+if os.getenv("CHIRP_DEBUG"):
+    CHIRP_DEBUG = True
+else:
+    CHIRP_DEBUG = False
+
+BJUV55_MODEL   = "\x50\xBB\xDD\x55\x63\x98\x4D"
+
+COLOR_LIST = ["Off", "Blue", "Red", "Pink"]
+
+MEM_FORMAT = """
+#seekto 0x0008;
+struct {
+  lbcd rxfreq[4];
+  lbcd txfreq[4];
+  ul16 rxtone;
+  ul16 txtone;
+  u8 unused1:3,
+     isuhf:1,
+     scode:4;
+  u8 unknown1:7,
+     txtoneicon:1;
+  u8 mailicon:3,
+     unknown2:4,
+     lowpower:1;
+  u8 unknown3:1,
+     wide:1,
+     unknown4:2,
+     bcl:1,
+     scan:1,
+     pttid:2;
+} memory[128];
+
+#seekto 0x0B08;
+struct {
+  u8 code[5];
+  u8 unused[11];
+} pttid[15];
+
+#seekto 0x0C88;
+struct {
+  u8 inspection[5];
+  u8 monitor[5];
+  u8 alarmcode[5];
+  u8 unknown1;
+  u8 stun[5];
+  u8 kill[5];
+  u8 revive[5];
+  u8 unknown2;
+  u8 master_control_id[5];
+  u8 vice_control_id[5];
+  u8 code[5];
+  u8 unused1:6,
+     aniid:2;
+  u8 unknown[2];
+  u8 dtmfon;
+  u8 dtmfoff;
+} ani;
+
+#seekto 0x0E28;
+struct {
+  u8 squelch;
+  u8 step;
+  u8 tdrab;
+  u8 tdr;
+  u8 vox;	
+  u8 timeout;
+  u8 unk2[6];
+  u8 abr;
+  u8 beep; 
+  u8 ani;
+  u8 unknown3[2];
+  u8 voice;
+  u8 ring_time;
+  u8 dtmfst;
+  u8 unknown5;
+  u8 unknown12:6,
+     screv:2;
+  u8 pttid;
+  u8 pttlt;
+  u8 mdfa;
+  u8 mdfb;
+  u8 bcl;
+  u8 autolk;
+  u8 sftd;
+  u8 unknown6[3];
+  u8 wtled;
+  u8 rxled;
+  u8 txled;
+  u8 unknown7[5];
+  u8 save;
+  u8 unknown8;
+  u8 displayab:1,
+     unknown1:2,
+     fmradio:1,
+     alarm:1,
+     unknown2:1,
+     reset:1,
+     menu:1;
+  u8 vfomrlock;
+  u8 workmode;
+  u8 keylock;
+  u8 workmode_channel;
+  u8 password[6];
+  u8 unknown10[11];
+} settings;
+
+#seekto 0x0E7E;
+struct {
+  u8 mrcha;
+  u8 mrchb;
+} wmchannel;
+
+#seekto 0x0F10;
+struct {
+  u8 freq[8];
+  u8 unknown1;
+  u8 offset[4];
+  u8 unknown2;
+  ul16 rxtone;
+  ul16 txtone;
+  u8 unused1:7,
+     band:1;
+  u8 unknown3;
+  u8 unused2:2,
+     sftd:2,
+     scode:4;
+  u8 unknown4;
+  u8 unused3:1
+     step:3,
+     unused4:4;
+  u8 txpower:1,
+     widenarr:1,
+     unknown5:6;
+} vfoa;
+
+#seekto 0x0F30;
+struct {
+  u8 freq[8];
+  u8 unknown1;
+  u8 offset[4];
+  u8 unknown2;
+  ul16 rxtone;
+  ul16 txtone;
+  u8 unused1:7,
+     band:1;
+  u8 unknown3;
+  u8 unused2:2,
+     sftd:2,
+     scode:4;
+  u8 unknown4;
+  u8 unused3:1
+     step:3,
+     unused4:4;
+  u8 txpower:1,
+     widenarr:1,
+     unknown5:6;
+} vfob;
+
+#seekto 0x0F56;
+u16 fm_presets;
+
+#seekto 0x1008;
+struct {
+  char name[6];
+  u8 unknown2[10];
+} names[128];
+
+#seekto 0x%04X;
+struct {
+  char line1[7];
+  char line2[7];
+} poweron_msg;
+
+#seekto 0x1838;
+struct {
+  char line1[7];
+  char line2[7];
+} firmware_msg;
+
+#seekto 0x1849;
+u8 power_vhf_hi[14]; // 136-174 MHz, 3 MHz divisions
+u8 power_uhf_hi[14]; // 400-470 MHz, 5 MHz divisions
+#seekto 0x1889;
+u8 power_vhf_lo[14];
+u8 power_uhf_lo[14];
+
+struct limit {
+  u8 enable;
+  bbcd lower[2];
+  bbcd upper[2];
+};
+
+#seekto 0x1908;
+struct {
+    struct limit vhf;
+    u8 unk11[11];
+    struct limit uhf;
+} limits;
+
+"""
+
+@directory.register
+class BaojieBJUV55Radio(uv5r.BaofengUV5R):
+    VENDOR = "Baojie"
+    MODEL = "BJ-UV55"
+    _basetype = "BJ55"
+    _idents = [ BJUV55_MODEL ]
+    _mem_params = ( 0x1928  # poweron_msg offset
+                    )
+    _fw_ver_file_start = 0x1938
+    _fw_ver_file_stop = 0x193E
+    
+    def get_features(self):
+        rf = super(BaojieBJUV55Radio, self).get_features()
+        #rf.has_settings = False
+        rf.valid_name_length = 6
+        return rf
+    
+    def process_mmap(self):
+        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
+
+    def _get_settings(self):
+        _settings = self._memobj.settings
+        basic = RadioSettingGroup("basic", "Basic Settings")
+        advanced = RadioSettingGroup("advanced", "Advanced Settings")
+        group = RadioSettingGroup("top", "All Settings", basic, advanced)
+
+        rs = RadioSetting("squelch", "Carrier Squelch Level",
+                          RadioSettingValueInteger(0, 9, _settings.squelch))
+        basic.append(rs)
+
+        rs = RadioSetting("save", "Battery Saver",
+                          RadioSettingValueInteger(0, 4, _settings.save))
+        basic.append(rs)
+
+        rs = RadioSetting("abr", "Backlight",
+                          RadioSettingValueBoolean(_settings.abr))
+        basic.append(rs)
+
+        rs = RadioSetting("tdr", "Dual Watch (BDR)",
+                          RadioSettingValueBoolean(_settings.tdr))
+        advanced.append(rs)
+
+        rs = RadioSetting("tdrab", "Dual Watch TX Priority",
+                          RadioSettingValueList(uv5r.TDRAB_LIST,
+                          uv5r.TDRAB_LIST[_settings.tdrab]))
+        advanced.append(rs)
+
+        rs = RadioSetting("alarm", "Alarm",
+                          RadioSettingValueBoolean(_settings.alarm))
+        advanced.append(rs)
+
+        rs = RadioSetting("beep", "Beep",
+                          RadioSettingValueBoolean(_settings.beep))
+        basic.append(rs)
+
+        rs = RadioSetting("timeout", "Timeout Timer",
+                          RadioSettingValueList(uv5r.TIMEOUT_LIST,
+                          uv5r.TIMEOUT_LIST[_settings.timeout]))
+        basic.append(rs)
+
+        rs = RadioSetting("screv", "Scan Resume",
+                          RadioSettingValueList(uv5r.RESUME_LIST,
+                          uv5r.RESUME_LIST[_settings.screv]))
+        advanced.append(rs)
+
+        rs = RadioSetting("mdfa", "Display Mode (A)",
+                          RadioSettingValueList(uv5r.MODE_LIST,
+                          uv5r.MODE_LIST[_settings.mdfa]))
+        basic.append(rs)
+
+        rs = RadioSetting("mdfb", "Display Mode (B)",
+                          RadioSettingValueList(uv5r.MODE_LIST,
+                          uv5r.MODE_LIST[_settings.mdfb]))
+        basic.append(rs)
+
+        rs = RadioSetting("bcl", "Busy Channel Lockout",
+                          RadioSettingValueBoolean(_settings.bcl))
+        advanced.append(rs)
+
+        rs = RadioSetting("autolk", "Automatic Key Lock",
+                          RadioSettingValueBoolean(_settings.autolk))
+        advanced.append(rs)
+
+        rs = RadioSetting("fmradio", "Broadcast FM Radio",
+                          RadioSettingValueBoolean(_settings.fmradio))
+        advanced.append(rs)
+
+        rs = RadioSetting("wtled", "Standby LED Color",
+                          RadioSettingValueList(COLOR_LIST,
+                          COLOR_LIST[_settings.wtled]))
+        basic.append(rs)
+
+        rs = RadioSetting("rxled", "RX LED Color",
+                          RadioSettingValueList(COLOR_LIST,
+                          COLOR_LIST[_settings.rxled]))
+        basic.append(rs)
+
+        rs = RadioSetting("txled", "TX LED Color",
+                          RadioSettingValueList(COLOR_LIST,
+                          COLOR_LIST[_settings.txled]))
+        basic.append(rs)
+
+        rs = RadioSetting("reset", "RESET Menu",
+                          RadioSettingValueBoolean(_settings.reset))
+        advanced.append(rs)
+
+        rs = RadioSetting("menu", "All Menus",
+                          RadioSettingValueBoolean(_settings.menu))
+        advanced.append(rs)
+
+
+        if len(self._mmap.get_packed()) == 0x1808:
+            # Old image, without aux block
+            return group
+
+        other = RadioSettingGroup("other", "Other Settings")
+        group.append(other)
+
+        def _filter(name):
+            filtered = ""
+            for char in str(name):
+                if char in chirp_common.CHARSET_ASCII:
+                    filtered += char
+                else:
+                    filtered += " "
+            return filtered
+
+        _msg = self._memobj.poweron_msg
+        rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
+                          RadioSettingValueString(0, 7, _filter(_msg.line1)))
+        other.append(rs)
+        rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
+                          RadioSettingValueString(0, 7, _filter(_msg.line2)))
+        other.append(rs)
+
+        limit = "limits"
+        vhf_limit = getattr(self._memobj, limit).vhf
+        rs = RadioSetting("%s.vhf.lower" % limit, "VHF Lower Limit (MHz)",
+                          RadioSettingValueInteger(1, 1000,
+                                                   vhf_limit.lower))
+        other.append(rs)
+
+        rs = RadioSetting("%s.vhf.upper" % limit, "VHF Upper Limit (MHz)",
+                          RadioSettingValueInteger(1, 1000,
+                                                   vhf_limit.upper))
+        other.append(rs)
+
+        rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled",
+                          RadioSettingValueBoolean(vhf_limit.enable))
+        other.append(rs)
+
+        uhf_limit = getattr(self._memobj, limit).uhf
+        rs = RadioSetting("%s.uhf.lower" % limit, "UHF Lower Limit (MHz)",
+                          RadioSettingValueInteger(1, 1000,
+                                                   uhf_limit.lower))
+        other.append(rs)
+        rs = RadioSetting("%s.uhf.upper" % limit, "UHF Upper Limit (MHz)",
+                          RadioSettingValueInteger(1, 1000,
+                                                   uhf_limit.upper))
+        other.append(rs)
+        rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled",
+                          RadioSettingValueBoolean(uhf_limit.enable))
+        other.append(rs)
+
+        workmode = RadioSettingGroup("workmode", "Work Mode Settings")
+        group.append(workmode)
+
+        options = ["A", "B"]
+        rs = RadioSetting("displayab", "Display Selected",
+                          RadioSettingValueList(options,
+                                                options[_settings.displayab]))
+        workmode.append(rs)
+
+        options = ["Frequency", "Channel"]
+        rs = RadioSetting("workmode", "VFO/MR Mode",
+                          RadioSettingValueList(options,
+                                                options[_settings.workmode]))
+        workmode.append(rs)
+
+        rs = RadioSetting("keylock", "Keypad Lock",
+                          RadioSettingValueBoolean(_settings.keylock))
+        workmode.append(rs)
+
+        _mrcna = self._memobj.wmchannel.mrcha
+        rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
+                          RadioSettingValueInteger(0, 127, _mrcna))
+        workmode.append(rs)
+
+        _mrcnb = self._memobj.wmchannel.mrchb
+        rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
+                          RadioSettingValueInteger(0, 127, _mrcnb))
+        workmode.append(rs)
+
+        def convert_bytes_to_freq(bytes):
+           real_freq = 0
+           for byte in bytes:
+               real_freq = (real_freq * 10) + byte
+           return chirp_common.format_freq(real_freq * 10)
+
+        def my_validate(value):
+            value = chirp_common.parse_freq(value)
+            if 17400000 <= value and value < 40000000:
+                raise InvalidValueError("Can't be between 174.00000-400.00000")
+            return chirp_common.format_freq(value)
+
+        def apply_freq(setting, obj):
+            value = chirp_common.parse_freq(str(setting.value)) / 10
+            obj.band = value >= 40000000
+            for i in range(7, -1, -1):
+                obj.freq[i] = value % 10
+                value /= 10
+
+        val1a = RadioSettingValueString(0, 10,
+            convert_bytes_to_freq(self._memobj.vfoa.freq))
+        val1a.set_validate_callback(my_validate)
+        rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a)
+        rs.set_apply_callback(apply_freq, self._memobj.vfoa)
+        workmode.append(rs)
+
+        val1b = RadioSettingValueString(0, 10,
+            convert_bytes_to_freq(self._memobj.vfob.freq))
+        val1b.set_validate_callback(my_validate)
+        rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b)
+        rs.set_apply_callback(apply_freq, self._memobj.vfob)
+        workmode.append(rs)
+
+        options = ["Off", "+", "-"]
+        rs = RadioSetting("vfoa.sftd", "VFO A Shift",
+                          RadioSettingValueList(options,
+                                                options[self._memobj.vfoa.sftd]))
+        workmode.append(rs)
+
+        rs = RadioSetting("vfob.sftd", "VFO B Shift",
+                          RadioSettingValueList(options,
+                                                options[self._memobj.vfob.sftd]))
+        workmode.append(rs)
+
+        def convert_bytes_to_offset(bytes):
+           real_offset = 0
+           for byte in bytes:
+               real_offset = (real_offset * 10) + byte
+           return chirp_common.format_freq(real_offset * 10000)
+
+        def apply_offset(setting, obj):
+            value = chirp_common.parse_freq(str(setting.value)) / 10000
+            for i in range(3, -1, -1):
+                obj.offset[i] = value % 10
+                value /= 10
+
+        val1a = RadioSettingValueString(0, 10,
+            convert_bytes_to_offset(self._memobj.vfoa.offset))
+        rs = RadioSetting("vfoa.offset", "VFO A Offset (0.00-69.95)", val1a)
+        rs.set_apply_callback(apply_offset, self._memobj.vfoa)
+        workmode.append(rs)
+
+        val1b = RadioSettingValueString(0, 10,
+            convert_bytes_to_offset(self._memobj.vfob.offset))
+        rs = RadioSetting("vfob.offset", "VFO B Offset (0.00-69.95)", val1b)
+        rs.set_apply_callback(apply_offset, self._memobj.vfob)
+        workmode.append(rs)
+
+        options = ["High", "Low"]
+        rs = RadioSetting("vfoa.txpower", "VFO A Power",
+                          RadioSettingValueList(options,
+                                                options[self._memobj.vfoa.txpower]))
+        workmode.append(rs)
+
+        rs = RadioSetting("vfob.txpower", "VFO B Power",
+                          RadioSettingValueList(options,
+                                                options[self._memobj.vfob.txpower]))
+        workmode.append(rs)
+
+        options = ["Wide", "Narrow"]
+        rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth",
+                          RadioSettingValueList(options,
+                                                options[self._memobj.vfoa.widenarr]))
+        workmode.append(rs)
+
+        rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
+                          RadioSettingValueList(options,
+                                                options[self._memobj.vfob.widenarr]))
+        workmode.append(rs)
+
+        options = ["%s" % x for x in range(1, 16)]
+        rs = RadioSetting("vfoa.scode", "VFO A PTT-ID",
+                          RadioSettingValueList(options,
+                                                options[self._memobj.vfoa.scode]))
+        workmode.append(rs)
+
+        rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
+                          RadioSettingValueList(options,
+                                                options[self._memobj.vfob.scode]))
+        workmode.append(rs)
+
+
+        rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
+                          RadioSettingValueList(uv5r.STEP291_LIST,
+                          uv5r.STEP291_LIST[self._memobj.vfoa.step]))
+        workmode.append(rs)
+        rs = RadioSetting("vfob.step", "VFO B Tuning Step",
+                          RadioSettingValueList(uv5r.STEP291_LIST,
+                          uv5r.STEP291_LIST[self._memobj.vfob.step]))
+        workmode.append(rs)
+
+
+        fm_preset = RadioSettingGroup("fm_preset", "FM Radio Preset")
+        group.append(fm_preset)
+
+        preset = self._memobj.fm_presets / 10.0 + 87
+        rs = RadioSetting("fm_presets", "FM Preset(MHz)",
+                      RadioSettingValueFloat(87, 107.5, preset, 0.1, 1))
+        fm_preset.append(rs)
+
+        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
+        group.append(dtmf)
+        dtmfchars = "0123456789 *#ABCD"
+
+        for i in range(0, 15):
+            _codeobj = self._memobj.pttid[i].code
+            _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+            val = RadioSettingValueString(0, 5, _code, False)
+            val.set_charset(dtmfchars)
+            rs = RadioSetting("pttid/%i.code" % i, "PTT ID Code %i" % (i + 1), val)
+            def apply_code(setting, obj):
+                code = []
+                for j in range(0, 5):
+                    try:
+                        code.append(dtmfchars.index(str(setting.value)[j]))
+                    except IndexError:
+                        code.append(0xFF)
+                obj.code = code
+            rs.set_apply_callback(apply_code, self._memobj.pttid[i])
+            dtmf.append(rs)
+
+        _codeobj = self._memobj.ani.code
+        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+        val = RadioSettingValueString(0, 5, _code, False)
+        val.set_charset(dtmfchars)
+        rs = RadioSetting("ani.code", "ANI Code", val)
+        def apply_code(setting, obj):
+            code = []
+            for j in range(0, 5):
+                try:
+                    code.append(dtmfchars.index(str(setting.value)[j]))
+                except IndexError:
+                    code.append(0xFF)
+            obj.code = code
+        rs.set_apply_callback(apply_code, self._memobj.ani)
+        dtmf.append(rs)
+
+        options = ["Off", "BOT", "EOT", "Both"]
+        rs = RadioSetting("ani.aniid", "ANI ID",
+                          RadioSettingValueList(options,
+                                                options[self._memobj.ani.aniid]))
+        dtmf.append(rs)
+
+        _codeobj = self._memobj.ani.alarmcode
+        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+        val = RadioSettingValueString(0, 5, _code, False)
+        val.set_charset(dtmfchars)
+        rs = RadioSetting("ani.alarmcode", "Alarm Code", val)
+        def apply_code(setting, obj):
+            alarmcode = []
+            for j in range(5):
+                try:
+                    alarmcode.append(dtmfchars.index(str(setting.value)[j]))
+                except IndexError:
+                    alarmcode.append(0xFF)
+            obj.alarmcode = alarmcode
+        rs.set_apply_callback(apply_code, self._memobj.ani)
+        dtmf.append(rs)
+
+        rs = RadioSetting("dtmfst", "DTMF Sidetone",
+                          RadioSettingValueList(uv5r.DTMFST_LIST,
+                          uv5r.DTMFST_LIST[_settings.dtmfst]))
+        dtmf.append(rs)
+
+        rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
+                          RadioSettingValueList(uv5r.DTMFSPEED_LIST,
+                          uv5r.DTMFSPEED_LIST[self._memobj.ani.dtmfon]))
+        dtmf.append(rs)
+
+        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
+                          RadioSettingValueList(uv5r.DTMFSPEED_LIST,
+                          uv5r.DTMFSPEED_LIST[self._memobj.ani.dtmfoff]))
+        dtmf.append(rs)
+
+        return group
+
+    def _set_fm_preset(self, settings):
+        for element in settings:
+            try:
+                val = element.value
+                value = int(val.get_value() * 10 - 870)
+                print "Setting fm_presets = %s" % (value)
+                self._memobj.fm_presets = value
+            except Exception, e:
+                print element.get_name()
+                raise
\ No newline at end of file
