# HG changeset patch
# User Mark Adams <madams74@gmail.com>
# Date 1452896427 18000
#      Fri Jan 15 17:20:27 2016 -0500
# Node ID 9edaa68c89a772282f61b81ab9b78ec2487425cc
# Parent  0f495ecad3d1121eb4b5d60ebb9b79cccf78f57a
[FT-50] Yaesu FT-50 driver overhaul & improved support. Fixes #1233.

diff -r 0f495ecad3d1 -r 9edaa68c89a7 chirp/drivers/ft50.py
--- a/chirp/drivers/ft50.py	Thu Dec 24 19:57:18 2015 -0500
+++ b/chirp/drivers/ft50.py	Fri Jan 15 17:20:27 2016 -0500
@@ -1,4 +1,4 @@
-# Copyright 2010 Dan Smith <dsmith@danplanet.com>
+# Copyright 2011 Dan Smith <dsmith@danplanet.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -12,44 +12,624 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+import time
+import logging
+import re
 
-from chirp.drivers import yaesu_clone, ft50_ll
-from chirp import chirp_common, directory
+from chirp.drivers import yaesu_clone
+from chirp import chirp_common, directory, errors, bitwise, util
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+    RadioSettingValueInteger, RadioSettingValueList, \
+    RadioSettingValueBoolean, RadioSettingValueString, \
+    RadioSettings
+from textwrap import dedent
 
+LOG = logging.getLogger(__name__)
 
-# Not working, don't register
-# @directory.register
+MEM_FORMAT = """
+struct flag_struct {
+  u8 unknown1f:5,
+     skip:1,
+     mask:1,
+     used:1;
+};
+
+struct mem_struct {
+  u8 showname:1,
+     unknown1:3,
+     unknown2:2,
+     unknown3:2;
+  u8 ishighpower:1,
+     power:2,
+     unknown4:1,
+     tuning_step:4;
+  u8 codememno:4,
+     codeorpage:2,
+     duplex:2;
+  u8 tmode:2,
+     tone:6;
+  u8 unknown5:1,
+     dtcs:7;
+  u8 unknown6:6,
+     mode:2;
+  bbcd freq[3];
+  bbcd offset[3];
+  u8 name[4];
+};
+
+#seekto 0x000C;
+struct {
+  u8 extendedrx_flg;    // Seems to be set to 03 when extended rx is enabled
+  u8 extendedrx;        // Seems to be set to 01 when extended rx is enabled
+} extendedrx_struct;    // UNFINISHED!!
+
+#seekto 0x001A;
+struct flag_struct flag[100];
+
+#seekto 0x079C;
+struct flag_struct flag_repeat[100];
+
+#seekto 0x00AA;
+struct mem_struct memory[100];
+struct mem_struct special[11];
+
+#seekto 0x08C7;
+struct {
+  u8 sub_display;
+  u8 unknown1s;
+  u8 apo;
+  u8 timeout;
+  u8 lock;
+  u8 rxsave;
+  u8 lamp;
+  u8 bell;
+  u8 cwid[16];
+  u8 unknown2s;
+  u8 artsmode;
+  u8 artsbeep;
+  u8 unknown3s;
+  u8 unknown4s;
+  struct {
+    u8 header[3];
+    u8 mem_num;
+    u8 digits[16];
+  } autodial[8];
+  struct {
+    u8 header[3];
+    u8 mem_num;
+    u8 digits[32];
+  } autodial9_ro;
+  bbcd pagingcodec_ro[2];
+  bbcd pagingcodep[2];
+  struct {
+    bbcd digits[2];
+  } pagingcode[6];
+  u8 code_dec_c_en:1,
+     code_dec_p_en:1,
+     code_dec_1_en:1,
+     code_dec_2_en:1,
+     code_dec_3_en:1,
+     code_dec_4_en:1,
+     code_dec_5_en:1,
+     code_dec_6_en:1;
+  u8 pagingspeed;
+  u8 pagingdelay;
+  u8 pagingbell;
+  u8 paginganswer;
+
+  #seekto 0x0E32;
+  u8 rptl:1,        // repeater input tracking
+     amod:1,        // auto mode
+     scnl:1,        // scan lamp
+     resm:1,        // scan resume mode 0=5sec, 1=carr
+     ars:1,         // automatic repeater shift
+     keybeep:1,     // keypad beep
+     lck:1,         // lock
+     unknown1c:1;
+  u8 lgt:1,
+     pageamsg:1,
+     unknown2c:1,
+     bclo:1,        // Busy channel lock out
+     unknown3c:2,
+     cwid_en:1,     // CWID off/on
+     tsav:1;        // TX save
+  u8 unknown4c:4,
+     artssped:1,    // ARTS/SPED: 0=15s, 1=25s
+     unknown5c:1,
+     rvhm:1,        // RVHM: 0=home, 1=rev
+     mon:1;         // MON: 0=mon, 1=tcal
+} settings;
+
+#seekto 0x080E;
+struct mem_struct vfo_mem[10];
+
+
+"""
+
+# 10 VFO memories: A145, A220, A380, A430, A800,
+#                  B145, B220, B380, B430, B800
+
+DUPLEX = ["", "-", "+"]
+MODES = ["FM", "AM", "WFM"]
+SKIP_VALUES = ["", "S"]
+TMODES = ["", "Tone", "TSQL", "DTCS"]
+TUNING_STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0]
+TONES = list(chirp_common.OLD_TONES)
+
+# CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ ()+-=*/???|0123456789"
+# the = displays as an underscored dash on radio
+# the first ? is an uppercase delta - \xA7
+# the second ? is an uppercase gamma - \xD1
+# the thrid ? is an uppercase sigma - \xCF
+NUMERIC_CHARSET = list("0123456789")
+CHARSET = [str(x) for x in range(0, 10)] + \
+    [chr(x) for x in range(ord("A"), ord("Z")+1)] + \
+    list(" ()+-=*/" + ("\x00" * 3) + "|") + NUMERIC_CHARSET
+DTMFCHARSET = NUMERIC_CHARSET + list("ABCD*#")
+
+POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.0),
+                chirp_common.PowerLevel("L3", watts=2.5),
+                chirp_common.PowerLevel("L2", watts=1.0),
+                chirp_common.PowerLevel("L1", watts=0.1)]
+SPECIALS = ["L1", "U1", "L2", "U2", "L3", "U3", "L4", "U4", "L5", "U5", "UNK"]
+
+
+@directory.register
 class FT50Radio(yaesu_clone.YaesuCloneModeRadio):
+    """Yaesu FT-50"""
     BAUD_RATE = 9600
     VENDOR = "Yaesu"
     MODEL = "FT-50"
 
+    _model = ""
     _memsize = 3723
     _block_lengths = [10, 16, 112, 16, 16, 1776, 1776, 1]
-    _block_delay = 0.15
+    # _block_delay = 0.15
+    _block_size = 8
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.pre_download = _(dedent("""\
+1. Turn radio off.
+2. Connect cable to MIC/SP jack.
+3. Press and hold [PTT] &amp; Knob while turning the
+     radio on.
+4. <b>After clicking OK</b>, press the [PTT] switch to send image."""))
+        rp.pre_upload = _(dedent("""\
+1. Turn radio off.
+2. Connect cable to MIC/SP jack.
+3. Press and hold [PTT] &amp; Knob while turning the
+     radio on.
+4. Press the [MONI] switch ("WAIT" will appear on the LCD).
+5. Press OK."""))
+        return rp
 
     def get_features(self):
         rf = chirp_common.RadioFeatures()
         rf.memory_bounds = (1, 100)
+        rf.valid_duplexes = DUPLEX
+        rf.valid_tmodes = TMODES
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_tuning_steps = TUNING_STEPS
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_characters = "".join(CHARSET)
+        rf.valid_name_length = 4
+        rf.valid_modes = MODES
+        # Specials not yet implementd
+        # rf.valid_special_chans = SPECIALS
+        rf.valid_bands = [(76000000, 200000000),
+                          (300000000, 540000000),
+                          (590000000, 999000000)]
+        # rf.can_odd_split = True
+        rf.has_ctone = False
+        rf.has_bank = False
+        rf.has_settings = True
         rf.has_dtcs_polarity = False
-        rf.has_bank = False
-        rf.valid_modes = ["FM", "WFM", "AM"]
+
         return rf
 
-    def _update_checksum(self):
-        ft50_ll.update_checksum(self._mmap)
+    def _checksums(self):
+        return [yaesu_clone.YaesuChecksum(0x0000, 0xE89)]
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
 
     def get_raw_memory(self, number):
-        return ft50_ll.get_raw_memory(self._mmap, number)
+        return repr(self._memobj.memory[number - 1])
 
     def get_memory(self, number):
-        return ft50_ll.get_memory(self._mmap, number)
+        mem = chirp_common.Memory()
+        _mem = self._memobj.memory[number-1]
+        _flg = self._memobj.flag[number-1]
+        mem.number = number
 
-    def set_memory(self, number):
-        return ft50_ll.set_memory(self._mmap, number)
+        # if not _flg.visible:
+        #    mem.empty = True
+        if not _flg.used:
+            mem.empty = True
+            return mem
 
-    def erase_memory(self, number):
-        return ft50_ll.erase_memory(self._mmap, number)
+        for i in _mem.name:
+            mem.name += CHARSET[i & 0x7F]
+        mem.name = mem.name.rstrip()
 
-    def filter_name(self, name):
-        return name[:4].upper()
+        mem.freq = chirp_common.fix_rounded_step(int(_mem.freq) * 1000)
+        mem.duplex = DUPLEX[_mem.duplex]
+        mem.offset = chirp_common.fix_rounded_step(int(_mem.offset) * 1000)
+        mem.rtone = mem.ctone = TONES[_mem.tone]
+        mem.tmode = TMODES[_mem.tmode]
+        mem.mode = MODES[_mem.mode]
+        mem.tuning_step = TUNING_STEPS[_mem.tuning_step]
+        mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
+        # Power is stored as 2 bits to describe the 3 low power levels
+        # High power is determined by a different bit.
+        if not _mem.ishighpower:
+            mem.power = POWER_LEVELS[3 - _mem.power]
+        else:
+            mem.power = POWER_LEVELS[0]
+        mem.skip = SKIP_VALUES[_flg.skip]
+
+        return mem
+
+    def set_memory(self, mem):
+        _mem = self._memobj.memory[mem.number-1]
+        _flg = self._memobj.flag[mem.number-1]
+        _flg_repeat = self._memobj.flag_repeat[mem.number-1]
+
+        if mem.empty:
+            _flg.used = False
+            self._wipe_memory_banks(mem)
+            return
+
+        if (len(mem.name) == 0):
+            _mem.name = [0x24] * 4
+            _mem.showname = 0
+        else:
+            _mem.showname = 1
+            for i in range(0, 4):
+                _mem.name[i] = CHARSET.index(mem.name.ljust(4)[i])
+
+        _mem.freq = int(mem.freq / 1000)
+        _mem.duplex = DUPLEX.index(mem.duplex)
+        _mem.offset = int(mem.offset / 1000)
+        _mem.mode = MODES.index(mem.mode)
+        _mem.tuning_step = TUNING_STEPS.index(mem.tuning_step)
+        if mem.power:
+            if (mem.power == POWER_LEVELS[0]):
+                # low power level is not changed when high power is selected
+                _mem.ishighpower = 0x01
+                if (_mem.power == 3):
+                    # Set low power to L3 (0x02) if it is
+                    # set to 3 (new object default)
+                    LOG.debug("SETTING DEFAULT?")
+                    _mem.power = 0x02
+            else:
+                _mem.ishighpower = 0x00
+                _mem.power = 3 - POWER_LEVELS.index(mem.power)
+        else:
+            _mem.ishighpower = 0x01
+            _mem.power = 0x02
+        _mem.tmode = TMODES.index(mem.tmode)
+        try:
+            _mem.tone = TONES.index(mem.rtone)
+        except ValueError:
+            raise errors.UnsupportedToneError(
+                ("This radio does not support tone %s" % mem.rtone))
+        _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
+
+        _flg.skip = SKIP_VALUES.index(mem.skip)
+
+        # initialize new channel to safe defaults
+        if not mem.empty and not _flg.used:
+            _flg.used = True
+            _flg.mask = True        # Mask = True to be visible on radio
+            _mem.unknown1 = 0x00
+            _mem.unknown2 = 0x00
+            _mem.unknown3 = 0x00
+            _mem.unknown4 = 0x00
+            _mem.unknown5 = 0x00
+            _mem.unknown6 = 0x00
+            _mem.codememno = 0x02   # Not implemented in chirp
+            _mem.codeorpage = 0x00  # Not implemented in chirp
+
+        # Duplicate flags to repeated part in memory
+        _flg_repeat.skip = _flg.skip
+        _flg_repeat.mask = _flg.mask
+        _flg_repeat.used = _flg.used
+
+    def _decode_cwid(self, inarr):
+        LOG.debug("@_decode_chars, type: %s" % type(inarr))
+        LOG.debug(inarr)
+        outstr = ""
+        for i in inarr:
+            if i == 0xFF:
+                break
+            outstr += CHARSET[i & 0x7F]
+        LOG.debug(outstr)
+        return outstr.rstrip()
+
+    def _encode_cwid(self, instr, length=16):
+        LOG.debug("@_encode_chars, type: %s" % type(instr))
+        LOG.debug(instr)
+        outarr = []
+        instr = str(instr)
+        for i in range(0, length):
+            if i < len(instr):
+                outarr.append(CHARSET.index(instr[i]))
+            else:
+                outarr.append(0xFF)
+        return outarr
+
+    def get_settings(self):
+        _settings = self._memobj.settings
+        basic = RadioSettingGroup("basic", "Basic")
+        dtmf = RadioSettingGroup("dtmf", "DTMF Code & Paging")
+        arts = RadioSettingGroup("arts", "ARTS")
+        autodial = RadioSettingGroup("autodial", "AutoDial")
+        top = RadioSettings(basic, autodial, arts, dtmf)
+
+        rs = RadioSetting(
+                "keybeep", "Keypad Beep",
+                RadioSettingValueBoolean(_settings.keybeep))
+        basic.append(rs)
+
+        rs = RadioSetting(
+                "scnl", "Scan Lamp",
+                RadioSettingValueBoolean(_settings.scnl))
+        basic.append(rs)
+
+        options = ["off", "30m", "1h", "3h", "5h", "8h"]
+        rs = RadioSetting(
+                "apo", "APO time (hrs)",
+                RadioSettingValueList(options, options[_settings.apo]))
+        basic.append(rs)
+
+        options = ["off", "1m", "2.5m", "5m", "10m"]
+        rs = RadioSetting(
+                "timeout", "Time Out Timer",
+                RadioSettingValueList(options, options[_settings.timeout]))
+        basic.append(rs)
+
+        options = ["key", "dial", "key+dial", "ptt",
+                   "key+ptt", "dial+ptt", "all"]
+        rs = RadioSetting(
+                "lock", "Lock mode",
+                RadioSettingValueList(options, options[_settings.lock]))
+        basic.append(rs)
+
+        options = ["off", "0.2", "0.3", "0.5", "1.0", "2.0"]
+        rs = RadioSetting(
+                "rxsave", "RX Save (sec)",
+                RadioSettingValueList(options, options[_settings.rxsave]))
+        basic.append(rs)
+
+        options = ["5sec", "key", "tgl"]
+        rs = RadioSetting(
+                "lamp", "Lamp mode",
+                RadioSettingValueList(options, options[_settings.lamp]))
+        basic.append(rs)
+
+        options = ["off", "1", "3", "5", "8", "rpt"]
+        rs = RadioSetting(
+                "bell", "Bell Repetitions",
+                RadioSettingValueList(options, options[_settings.bell]))
+        basic.append(rs)
+
+        rs = RadioSetting(
+                "cwid_en", "CWID Enable",
+                RadioSettingValueBoolean(_settings.cwid_en))
+        arts.append(rs)
+
+        cwid = RadioSettingValueString(
+                0, 16, self._decode_cwid(_settings.cwid.get_value()))
+        cwid.set_charset(CHARSET)
+        rs = RadioSetting("cwid", "CWID", cwid)
+        arts.append(rs)
+
+        options = ["off", "rx", "tx", "trx"]
+        rs = RadioSetting(
+                "artsmode", "ARTS Mode",
+                RadioSettingValueList(
+                    options, options[_settings.artsmode]))
+        arts.append(rs)
+
+        options = ["off", "in range", "always"]
+        rs = RadioSetting(
+                "artsbeep", "ARTS Beep",
+                RadioSettingValueList(options, options[_settings.artsbeep]))
+        arts.append(rs)
+
+        for i in range(0, 8):
+            dialsettings = _settings.autodial[i]
+            dialstr = ""
+            for c in dialsettings.digits:
+                if c < len(DTMFCHARSET):
+                    dialstr += DTMFCHARSET[c]
+            dialentry = RadioSettingValueString(0, 16, dialstr)
+            dialentry.set_charset(DTMFCHARSET + list(" "))
+            rs = RadioSetting("autodial" + str(i+1),
+                              "AutoDial " + str(i+1), dialentry)
+            autodial.append(rs)
+
+        dialstr = ""
+        for c in _settings.autodial9_ro.digits:
+            if c < len(DTMFCHARSET):
+                dialstr += DTMFCHARSET[c]
+        dialentry = RadioSettingValueString(0, 32, dialstr)
+        dialentry.set_mutable(False)
+        rs = RadioSetting("autodial9_ro", "AutoDial 9 (read only)", dialentry)
+        autodial.append(rs)
+
+        options = ["50ms", "100ms"]
+        rs = RadioSetting(
+                "pagingspeed", "Paging Speed",
+                RadioSettingValueList(options, options[_settings.pagingspeed]))
+        dtmf.append(rs)
+
+        options = ["250ms", "450ms", "750ms", "1000ms"]
+        rs = RadioSetting(
+                "pagingdelay", "Paging Delay",
+                RadioSettingValueList(options, options[_settings.pagingdelay]))
+        dtmf.append(rs)
+
+        options = ["off", "1", "3", "5", "8", "rpt"]
+        rs = RadioSetting(
+                "pagingbell", "Paging Bell Repetitions",
+                RadioSettingValueList(options, options[_settings.pagingbell]))
+        dtmf.append(rs)
+
+        options = ["off", "ans", "for"]
+        rs = RadioSetting(
+                "paginganswer", "Paging Answerback",
+                RadioSettingValueList(options,
+                                      options[_settings.paginganswer]))
+        dtmf.append(rs)
+
+        rs = RadioSetting(
+                "code_dec_c_en", "Paging Code C Decode Enable",
+                RadioSettingValueBoolean(_settings.code_dec_c_en))
+        dtmf.append(rs)
+
+        _str = str(bitwise.bcd_to_int(_settings.pagingcodec_ro))
+        code = RadioSettingValueString(0, 3, _str)
+        code.set_charset(NUMERIC_CHARSET + list(" "))
+        code.set_mutable(False)
+        rs = RadioSetting("pagingcodec_ro", "Paging Code C (read only)", code)
+        dtmf.append(rs)
+
+        rs = RadioSetting(
+                "code_dec_p_en", "Paging Code P Decode Enable",
+                RadioSettingValueBoolean(_settings.code_dec_p_en))
+        dtmf.append(rs)
+
+        _str = str(bitwise.bcd_to_int(_settings.pagingcodep))
+        code = RadioSettingValueString(0, 3, _str)
+        code.set_charset(NUMERIC_CHARSET + list(" "))
+        rs = RadioSetting("pagingcodep", "Paging Code P", code)
+        dtmf.append(rs)
+
+        for i in range(0, 6):
+            num = str(i+1)
+            name = "code_dec_" + num + "_en"
+            rs = RadioSetting(
+                    name, "Paging Code " + num + " Decode Enable",
+                    RadioSettingValueBoolean(getattr(_settings, name)))
+            dtmf.append(rs)
+
+            _str = str(bitwise.bcd_to_int(_settings.pagingcode[i].digits))
+            code = RadioSettingValueString(0, 3, _str)
+            code.set_charset(NUMERIC_CHARSET + list(" "))
+            rs = RadioSetting("pagingcode" + num, "Paging Code " + num, code)
+            dtmf.append(rs)
+
+        return top
+
+    def set_settings(self, uisettings):
+        for element in uisettings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+            if not element.changed():
+                continue
+            try:
+                setting = element.get_name()
+                _settings = self._memobj.settings
+                if re.match('autodial\d', setting):
+                    # set autodial fields
+                    dtmfstr = str(element.value).strip()
+                    newval = []
+                    for i in range(0, 16):
+                        if i < len(dtmfstr):
+                            newval.append(DTMFCHARSET.index(dtmfstr[i]))
+                        else:
+                            newval.append(0xFF)
+                    LOG.debug(newval)
+                    idx = int(setting[-1:]) - 1
+                    _settings = self._memobj.settings.autodial[idx]
+                    _settings.digits = newval
+                    continue
+                if (setting == "pagingcodep"):
+                    bitwise.int_to_bcd(_settings.pagingcodep,
+                                       int(element.value))
+                    continue
+                if re.match('pagingcode\d', setting):
+                    idx = int(setting[-1:]) - 1
+                    bitwise.int_to_bcd(_settings.pagingcode[idx].digits,
+                                       int(element.value))
+                    continue
+                newval = element.value
+                oldval = getattr(_settings, setting)
+                if setting == "cwid":
+                    newval = self._encode_cwid(newval)
+                LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval))
+                setattr(_settings, setting, newval)
+            except Exception:
+                LOG.debug(element.get_name())
+                raise
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return len(filedata) == cls._memsize
+
+    def sync_out(self):
+        self.update_checksums()
+        return _clone_out(self)
+
+
+def _clone_out(radio):
+    try:
+        return __clone_out(radio)
+    except Exception, e:
+        raise errors.RadioError("Failed to communicate with the radio: %s" % e)
+
+
+def __clone_out(radio):
+    pipe = radio.pipe
+    block_lengths = radio._block_lengths
+    total_written = 0
+
+    def _status():
+        status = chirp_common.Status()
+        status.msg = "Cloning to radio"
+        status.max = sum(block_lengths)
+        status.cur = total_written
+        radio.status_fn(status)
+
+    start = time.time()
+
+    blocks = 0
+    pos = 0
+    for block in radio._block_lengths:
+        blocks += 1
+        data = radio.get_mmap()[pos:pos + block]
+        # LOG.debug(util.hexprint(data))
+
+        recvd = ""
+        # Radio echos every block received
+        for byte in data:
+            time.sleep(0.01)
+            pipe.write(byte)
+            # flush & sleep so don't loose ack
+            pipe.flush()
+            time.sleep(0.015)
+            recvd += pipe.read(1)  # chew the echo
+        # LOG.debug(util.hexprint(recvd))
+        LOG.debug("Bytes sent: %i" % len(data))
+
+        # Radio does not ack last block
+        if (blocks < 8):
+            buf = pipe.read(block)
+            LOG.debug("ACK attempt: " + util.hexprint(buf))
+            if buf and buf[0] != chr(yaesu_clone.CMD_ACK):
+                buf = pipe.read(block)
+            if not buf or buf[-1] != chr(yaesu_clone.CMD_ACK):
+                raise errors.RadioError("Radio did not ack block %i" % blocks)
+
+        total_written += len(data)
+        _status()
+        pos += block
+
+    pipe.read(pos)  # Chew the echo if using a 2-pin cable
+
+    LOG.debug("Clone completed in %i seconds" % (time.time() - start))
diff -r 0f495ecad3d1 -r 9edaa68c89a7 chirp/drivers/ft50_ll.py
--- a/chirp/drivers/ft50_ll.py	Thu Dec 24 19:57:18 2015 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,319 +0,0 @@
-# Copyright 2010 Dan Smith <dsmith@danplanet.com>
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-from chirp import chirp_common, util, errors, memmap
-import time
-import logging
-
-LOG = logging.getLogger(__name__)
-
-ACK = chr(0x06)
-
-MEM_LOC_BASE = 0x00AB
-MEM_LOC_SIZE = 16
-
-POS_DUPLEX = 1
-POS_TMODE = 2
-POS_TONE = 2
-POS_DTCS = 3
-POS_MODE = 4
-POS_FREQ = 5
-POS_OFFSET = 9
-POS_NAME = 11
-
-POS_USED = 0x079C
-
-CHARSET = [str(x) for x in range(0, 10)] + \
-    [chr(x) for x in range(ord("A"), ord("Z")+1)] + \
-    list(" ()+--*/???|0123456789")
-
-
-def send(s, data):
-    s.write(data)
-    r = s.read(len(data))
-    if len(r) != len(data):
-        raise errors.RadioError("Failed to read echo")
-
-
-def read_exact(s, count):
-    data = ""
-    i = 0
-    while len(data) < count:
-        if i == 3:
-            LOG.debug(util.hexprint(data))
-            raise errors.RadioError("Failed to read %i (%i) from radio" %
-                                    (count, len(data)))
-        elif i > 0:
-            LOG.info("Retry %i" % i)
-        data += s.read(count - len(data))
-        i += 1
-
-    return data
-
-
-def download(radio):
-    data = ""
-
-    radio.pipe.setTimeout(1)
-
-    for block in radio._block_lengths:
-        LOG.debug("Doing block %i" % block)
-        if block > 112:
-            step = 16
-        else:
-            step = block
-        for i in range(0, block, step):
-            # data += read_exact(radio.pipe, step)
-            chunk = radio.pipe.read(step*2)
-            LOG.debug("Length of chunk: %i" % len(chunk))
-            data += chunk
-            LOG.debug("Reading %i" % i)
-            time.sleep(0.1)
-            send(radio.pipe, ACK)
-            if radio.status_fn:
-                status = chirp_common.Status()
-                status.max = radio._memsize
-                status.cur = len(data)
-                status.msg = "Cloning from radio"
-                radio.status_fn(status)
-
-    r = radio.pipe.read(100)
-    send(radio.pipe, ACK)
-    LOG.debug("R: %i" % len(r))
-    LOG.debug(util.hexprint(r))
-
-    LOG.debug("Got: %i Expecting %i" % (len(data), radio._memsize))
-
-    return memmap.MemoryMap(data)
-
-
-def get_mem_offset(number):
-    return MEM_LOC_BASE + (number * MEM_LOC_SIZE)
-
-
-def get_raw_memory(map, number):
-    pos = get_mem_offset(number)
-    return memmap.MemoryMap(map[pos:pos+MEM_LOC_SIZE])
-
-
-def get_freq(mmap):
-    khz = (int("%02x" % (ord(mmap[POS_FREQ])), 10) * 100000) + \
-        (int("%02x" % ord(mmap[POS_FREQ+1]), 10) * 1000) + \
-        (int("%02x" % ord(mmap[POS_FREQ+2]), 10) * 10)
-    return khz / 10000.0
-
-
-def set_freq(mmap, freq):
-    val = util.bcd_encode(int(freq * 1000), width=6)[:3]
-    mmap[POS_FREQ] = val
-
-
-def get_tmode(mmap):
-    val = ord(mmap[POS_TMODE]) & 0xC0
-
-    tmodemap = {
-        0x00: "",
-        0x40: "Tone",
-        0x80: "TSQL",
-        0xC0: "DTCS",
-        }
-
-    return tmodemap[val]
-
-
-def set_tmode(mmap, tmode):
-    val = ord(mmap[POS_TMODE]) & 0x3F
-
-    tmodemap = {
-        "":      0x00,
-        "Tone":  0x40,
-        "TSQL":  0x80,
-        "DTCS":  0xC0,
-        }
-
-    val |= tmodemap[tmode]
-
-    mmap[POS_TMODE] = val
-
-
-def get_tone(mmap):
-    val = ord(mmap[POS_TONE]) & 0x3F
-
-    return chirp_common.TONES[val]
-
-
-def set_tone(mmap, tone):
-    val = ord(mmap[POS_TONE]) & 0xC0
-
-    mmap[POS_TONE] = val | chirp_common.TONES.index(tone)
-
-
-def get_dtcs(mmap):
-    val = ord(mmap[POS_DTCS])
-
-    return chirp_common.DTCS_CODES[val]
-
-
-def set_dtcs(mmap, dtcs):
-    mmap[POS_DTCS] = chirp_common.DTCS_CODES.index(dtcs)
-
-
-def get_offset(mmap):
-    khz = (int("%02x" % ord(mmap[POS_OFFSET]), 10) * 10) + \
-        (int("%02x" % (ord(mmap[POS_OFFSET+1]) >> 4), 10) * 1)
-
-    return khz / 1000.0
-
-
-def set_offset(mmap, offset):
-    val = util.bcd_encode(int(offset * 1000), width=4)[:3]
-    LOG.debug("Offset:\n%s" % util.hexprint(val))
-    mmap[POS_OFFSET] = val
-
-
-def get_duplex(mmap):
-    val = ord(mmap[POS_DUPLEX]) & 0x03
-
-    dupmap = {
-        0x00: "",
-        0x01: "-",
-        0x02: "+",
-        0x03: "split",
-        }
-
-    return dupmap[val]
-
-
-def set_duplex(mmap, duplex):
-    val = ord(mmap[POS_DUPLEX]) & 0xFC
-
-    dupmap = {
-        "":       0x00,
-        "-":      0x01,
-        "+":      0x02,
-        "split":  0x03,
-        }
-
-    mmap[POS_DUPLEX] = val | dupmap[duplex]
-
-
-def get_name(mmap):
-    name = ""
-    for x in mmap[POS_NAME:POS_NAME+4]:
-        if ord(x) >= len(CHARSET):
-            break
-        name += CHARSET[ord(x)]
-    return name
-
-
-def set_name(mmap, name):
-    val = ""
-    for i in name[:4].ljust(4):
-        val += chr(CHARSET.index(i))
-    mmap[POS_NAME] = val
-
-
-def get_mode(mmap):
-    val = ord(mmap[POS_MODE]) & 0x03
-
-    modemap = {
-        0x00: "FM",
-        0x01: "AM",
-        0x02: "WFM",
-        0x03: "WFM",
-        }
-
-    return modemap[val]
-
-
-def set_mode(mmap, mode):
-    val = ord(mmap[POS_MODE]) & 0xCF
-
-    modemap = {
-        "FM":   0x00,
-        "AM":   0x01,
-        "WFM":  0x02,
-        }
-
-    mmap[POS_MODE] = val | modemap[mode]
-
-
-def get_used(mmap, number):
-    return ord(mmap[POS_USED + number]) & 0x01
-
-
-def set_used(mmap, number, used):
-    val = ord(mmap[POS_USED + number]) & 0xFC
-    if used:
-        val |= 0x03
-    mmap[POS_USED + number] = val
-
-
-def get_memory(map, number):
-    index = number - 1
-    mmap = get_raw_memory(map, index)
-
-    mem = chirp_common.Memory()
-    mem.number = number
-    if not get_used(map, index):
-        mem.empty = True
-        return mem
-
-    mem.freq = get_freq(mmap)
-    mem.tmode = get_tmode(mmap)
-    mem.rtone = mem.ctone = get_tone(mmap)
-    mem.dtcs = get_dtcs(mmap)
-    mem.offset = get_offset(mmap)
-    mem.duplex = get_duplex(mmap)
-    mem.name = get_name(mmap)
-    mem.mode = get_mode(mmap)
-
-    return mem
-
-
-def set_memory(_map, mem):
-    index = mem.number - 1
-    mmap = get_raw_memory(_map, index)
-
-    if not get_used(_map, index):
-        mmap[0] = ("\x00" * MEM_LOC_SIZE)
-
-    set_freq(mmap, mem.freq)
-    set_tmode(mmap, mem.tmode)
-    set_tone(mmap, mem.rtone)
-    set_dtcs(mmap, mem.dtcs)
-    set_offset(mmap, mem.offset)
-    set_duplex(mmap, mem.duplex)
-    set_name(mmap, mem.name)
-    set_mode(mmap, mem.mode)
-
-    _map[get_mem_offset(index)] = mmap.get_packed()
-    set_used(_map, index, True)
-
-    return _map
-
-
-def erase_memory(map, number):
-    set_used(map, number-1, False)
-    return map
-
-
-def update_checksum(map):
-    cs = 0
-    for i in range(0, 3722):
-        cs += ord(map[i])
-    cs %= 256
-    LOG.debug("Checksum old=%02x new=%02x" % (ord(map[3722]), cs))
-    map[3722] = cs
