# HG changeset patch
# User Pavel Milanes (CO7WT) <co7wt@frcuba.co.cu>
# Date 1447269745 18000
#      Wed Nov 11 14:22:25 2015 -0500
# Node ID 2934cd81ba41195a523719022faf57bed401b996
# Parent  02cff0d00d9ac87fdbd598fbf337b8fd7df3f652
New Model Feidaxin FD-268A (VHF) alpha code, fix #2169

It works on reading and setting channel data, in practice it works fine
but it fails the Bruteforce test with this:

 Feidaxin FD-268A       Detect      PASSED: All tests
 Feidaxin FD-268A       Settings   SKIPPED: Settings not supported
 Feidaxin FD-268A       Clone       PASSED: All tests
 Feidaxin FD-268A       Edges       PASSED: All tests
 Feidaxin FD-268A       BruteForce  FAILED: Field `tmode' is `Tone', expected `Cross'
 Feidaxin FD-268A       CopyAll     PASSED: All tests
 Feidaxin FD-268A       Banks      SKIPPED: Banks not supported

Any help on fixing the BruteForce test is welcomed, junior python coder here.

It can work on the FD-268B UHF version, see http://chirp.danplanet.com/issues/2169
for details on how to help me on this.

diff -r 02cff0d00d9a -r 2934cd81ba41 chirp/drivers/fd268.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/chirp/drivers/fd268.py	Wed Nov 11 14:22:25 2015 -0500
@@ -0,0 +1,606 @@
+# Copyright 2012 Dan Smith <dsmith@danplanet.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import struct
+import os
+import logging
+import unittest
+
+from chirp import chirp_common, directory, memmap, errors, util
+from chirp import bitwise
+from textwrap import dedent
+from chirp.settings import RadioSettingGroup, RadioSetting, \
+        RadioSettingValueBoolean, RadioSettingValueList, \
+        RadioSettingValueString, RadioSettings
+
+LOG = logging.getLogger(__name__)
+
+MEM_FORMAT = """
+#seekto 0x0010;
+struct {
+  lbcd rx_freq[4];
+  lbcd tx_freq[4];
+  lbcd rx_tone[2];
+  lbcd tx_tone[2];
+  u8 unknown:4,
+     scramble:1,
+     unknown1:1,
+     unknown2:1,
+     busy_lock:1;
+  u8 unknown3[3];
+} memory[99];
+
+#seekto 0x0640;
+struct {
+  lbcd vrx_freq[4];
+  lbcd vtx_freq[4];
+  lbcd vrx_tone[2];
+  lbcd vtx_tone[2];
+  u8 shift_plus:1,
+     shift_minus:1,
+     unknown11:2,
+     scramble:1,
+     unknown12:1,
+     unknown13:1,
+     busy_lock:1;
+  u8 unknown14[3];
+} vfo;
+
+#seekto 0x07C0;
+struct {
+  u8 unknown22:5,
+     bw1:1,
+     bs1:1,
+     warning1:1;
+  u8 sql[1];
+  u8 monitorval;
+  u8 tot[1];
+  u8 unknown23[4];
+  u8 unknown24[8];
+  char model[8];
+  u8 unknown26[8];
+  u8 step;
+  u8 unknown27:2,
+     power:1,
+     lamp:1,
+     lamp_auto:1,
+     key:1,
+     monitor:1,
+     bw:1;
+  u8 unknown28:3,
+     warning:1,
+     bs:1,
+     unknown29:1,
+     wmem:1,
+     wvfo:1;
+  u8 active_ch;
+  u8 unknown30[4];
+  u8 unknown31[4];
+  bbcd vfo_shift[4];
+} settings;
+"""
+
+######################
+# Radio settigns NOTES
+######################
+#
+# sql: 0..9
+# tot: is val * 10; 0 = OFF (01 = 10; x00..x09 )
+#
+# power, lamp, lamp_auto. wmem, wvfo, moni: 1 on / 0 off;
+# warning, bs : 1 on / 0 off;
+#
+# key => auto = 0, manu = 1
+#
+# model is reversed [...7013P] = P3107...
+# active_ch is CH - 1 (CH15 = x0e)
+# monitorval: moni=0 > monitorval = 0 ; moni=1 > monitorval = x30
+# warning must equal warning1
+# bw: 0 = nar, 1 = Wide & must equal bw1
+# battery save (bs) must equal bs1
+
+# VFO
+# shift_minus 0 = null; 1 = "-" + update the TX freq. in VFO
+# shift_pos 0 = null; 1 = "+" + update the TX freq. in VFO
+#
+# Simplex
+#     vfo_shift: no care
+#     shift_minus = 0
+#     shift_pos = 0
+#
+# - shift
+#     vfo_shift: set + tx vfo updated
+#     shift_minus = 1
+#     shift_pos = 0
+#
+# + shift
+#     vfo_shift: set + tx vfo updated
+#     shift_minus = 0
+#     shift_pos = 1
+
+MEM_SIZE=0x0800
+CMD_ACK = "\x06"
+BLOCK_SIZE = 0x08
+POWER_LEVELS = ["Low", "High"]
+LIST_SQL = ["Off", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
+LIST_TOT = ["Off", "10", "20", "30", "40", "50", "60", "70", "80", "90"]
+BS = ["Off", "On"]
+LAMP = BS + ["Auto"]
+STEPF = ["5", "10", "6.25", "12.5", "25"]
+ACTIVE_CH = ["%s" % x for x in range(1, 100)]
+KEY_LOCK =["Automatic", "Manual"]
+BW = ["Narrow", "Wide"]
+W_MODE = ["VFO", "Memory"]
+VSHIFT = ["Off", "-", "+"]
+
+# Settings are disabled by now, it's a work in progress
+
+def raw_recv(radio, amount):
+    """Raw read from the radio device"""
+    data = ""
+    try:
+        data = radio.pipe.read(amount)
+    except:
+        raise errors.RadioError("Error reading data from radio")
+
+    return data
+
+def raw_send(radio, data):
+    """Raw send to the radio device"""
+    try:
+        data = radio.pipe.write(data)
+    except:
+        raise errors.RadioError("Error sending data to radio")
+
+def make_frame(cmd, addr, length = BLOCK_SIZE):
+    """Pack the info in the format it likes"""
+    return struct.pack(">BHB", ord(cmd), addr, length)
+
+def send(radio, frame, data = ""):
+    """Generic send data to the radio"""
+    raw_send(radio, frame)
+
+    if data != "":
+        raw_send(radio, data)
+        ack = raw_recv(radio, 1)
+        if ack != CMD_ACK:
+            raise errors.RadioError("Radio didn't ack the last block of data")
+
+def recv(radio):
+    """Generic receive data from the radio, return just data"""
+    # you must get it all 12 at once (4 header + 8 data)
+    rxdata = raw_recv(radio, 12)
+    if (len(rxdata) != 12):
+        raise errors.RadioError(
+                "Radio sent %i bytes, we expected 12" % (len(rxdata)))
+    else:
+        data = rxdata[4:]
+
+        # send ACK
+        send(radio, CMD_ACK)
+        ack = raw_recv(radio, 1)
+        if ack != CMD_ACK:
+            raise errors.RadioError(
+                    "Radio did not send ack to the last command")
+
+        return data
+
+def do_program(radio):
+    """Feidaxin program mode and identification dance"""
+    def do_magic():
+        """Try to get the radio in program mode, this is standard
+        procedure of the factory software (FDX-288) from factory,
+        we try 8 times to get the correct response, if not work
+        try again a few times"""
+        # UI information
+        status = chirp_common.Status()
+        status.cur = 0
+        status.max = 8
+        status.msg = "Linking to radio, please wait."
+        radio.status_fn(status)
+        # every byte of this magic chain must be send separatedly
+        magic = "\x02PROGRA"
+
+        # start the fun, finger crossed please...
+        for a in range(0, 8):
+            # UI update
+            status.cur = a
+            radio.status_fn(status)
+            for i in range(0, len(magic)):
+                # this is needed due to timming
+                send(radio, magic[i])
+
+            # Now you get a x06 of ACK
+            ack = raw_recv(radio, 1)
+            if ack == CMD_ACK:
+                return True
+
+        return False
+
+    # try to get the radio in program mode
+    ack = do_magic()
+    if not ack:
+        raise errors.RadioError(
+                "Radio did not accept program mode, try again.")
+
+    # now we request identification
+    send(radio, "M")
+    send(radio, "\x02")
+    ident = raw_recv(radio, 8)
+
+    # WARNING !!!!
+    #
+    # radio identify it self in different modes:
+    # - hardware reset: "\xFF" * 8
+    # - FDX-288 soft from clean template: "P3107" + "\x00" * 3
+    # - Mine, in original image has: "P2107" + "\x00" * 3
+    #
+    # I tested writing "FD-268A " to the ident zone and the radio
+    # and FDX-288 software works ok, seems they don't care about it.
+    #
+    # I need to compare others radio's imgs to determine what to do.
+    #
+    # Seems that ident has a link with the soft used to program it.
+    # It's like FDX-288 soft does not make any try to correctly
+    # ident the radio using the ident mechanism.
+    #
+    # So we ignore ident by now, user must care about it.
+
+    # final ACK
+    send(radio, CMD_ACK)
+    ack = raw_recv(radio, 1)
+    if ack != CMD_ACK:
+        raise errors.RadioError("Radio refused to enter programming mode")
+
+def do_download(radio):
+    """ The download function """
+    do_program(radio)
+    # UI progress
+    status = chirp_common.Status()
+    status.cur = 0
+    status.max = MEM_SIZE
+    status.msg = "Cloning from radio..."
+    radio.status_fn(status)
+    data = ""
+    for addr in range(0x0000, MEM_SIZE, BLOCK_SIZE):
+        send(radio, make_frame("R", addr))
+        d = recv(radio)
+        data += d
+        # UI Update
+        status.cur = addr
+        radio.status_fn(status)
+
+    return memmap.MemoryMap(data)
+
+def do_upload(radio):
+    """The upload function"""
+    do_program(radio)
+    # UI progress
+    status = chirp_common.Status()
+    status.cur = 0
+    status.max = MEM_SIZE
+    status.msg = "Cloning to radio..."
+    radio.status_fn(status)
+
+    for addr in range(0x0000, MEM_SIZE, BLOCK_SIZE):
+        send(radio, make_frame("W",addr), radio.get_mmap()[addr:addr+BLOCK_SIZE])
+        # UI Update
+        status.cur = addr
+        radio.status_fn(status)
+
+class FeidaxinFD268xRadio(chirp_common.CloneModeRadio):
+    """Feidaxin FD-268x Radio"""
+    VENDOR = "Feidaxin"
+    MODEL = "FD-268x"
+    BAUD_RATE = 9600
+    _memsize = MEM_SIZE
+    _upper = 99
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.experimental = \
+            ('The program mode of this radio has his tricks,'
+            'so this driver is completely experimental.')
+        rp.pre_download = _(dedent("""\
+            This radio has a tricky way of enter into program mode,
+            even the original software has a few tries to get inside.
+
+            I will try 8 times (most the time ~3 will doit) and this
+            can take a few seconds, if don't work, try again a few times.
+
+            If you can get into it, please check the radio and cable. """))
+        rp.pre_upload = _(dedent("""\
+            This radio has a tricky way of enter into program mode,
+            even the original software has a few tries to get inside.
+
+            I will try 8 times (most the time ~3 will doit) and this
+            can take a few seconds, if don't work, try again a few times.
+
+            If you can get into it, please check the radio and cable. """))
+        return rp
+
+    def get_features(self):
+        """Return information about this radio's features"""
+        rf = chirp_common.RadioFeatures()
+        # this feature is READ ONLY by now.
+        rf.has_settings = False # not finished yet
+        rf.has_bank = False
+        rf.has_tuning_step = False
+        rf.has_name = False
+        rf.has_offset = True
+        rf.has_mode = False
+        rf.has_dtcs = True
+        rf.has_rx_dtcs = True
+        rf.has_dtcs_polarity = True
+        rf.has_ctone = True
+        rf.has_cross = True
+        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
+        rf.valid_cross_modes = chirp_common.CROSS_MODES
+        #rf.valid_power_levels = POWER_LEVELS
+        rf.valid_bands = [self._range]
+        rf.memory_bounds = (1, self._upper)
+        return rf
+
+    def sync_in(self):
+        """Do a download of the radio eeprom"""
+        self._mmap = do_download(self)
+        self.process_mmap()
+
+    def sync_out(self):
+        """Do an upload to the radio eeprom"""
+        do_upload(self)
+
+    def process_mmap(self):
+        """Process the memory objet, mainly used on file load"""
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+    def get_raw_memory(self, number):
+        """Return a raw representation of the memory object, which
+        is very helpful for development"""
+        return repr(self._memobj.memory[number])
+
+    def _decode_tone(self, val):
+        """Parse the tone data to decode from mem, it returns:
+        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
+        val = int(val)
+        if val == 16665:
+            return '', None, None
+        elif val >= 12000:
+            a = val - 12000
+            return 'DTCS', a, 'R'
+        elif val >= 8000:
+            a = val - 8000
+            return 'DTCS', a, 'N'
+        else:
+            a = val / 10.0
+            return 'Tone', a, None
+
+    def _encode_tone(self, memval, mode, value, pol):
+        """Parse the tone data to encode from UI to mem"""
+        if mode == '':
+            memval[0].set_raw(0xFF)
+            memval[1].set_raw(0xFF)
+        elif mode == 'Tone':
+            memval.set_value(int(value * 10))
+        elif mode == 'DTCS':
+            if pol == 'N':
+                memval.set_value(int(value) + 8000)
+            else:
+                cent = (value / 100) + 192
+                memval.set_value(int(value))
+                memval[1].set_raw(cent)
+        else:
+            raise Exception("Internal error: invalid mode `%s'" % mode)
+
+
+    def get_memory(self, number):
+        """Extract a high-level memory object from the low-level memory map
+        This is called to populate a memory in the UI"""
+        # Get a low-level memory object mapped to the image
+        _mem = self._memobj.memory[number -1 ]
+        # Create a high-level memory object to return to the UI
+        mem = chirp_common.Memory()
+        # number
+        mem.number = number
+
+        # empty
+        if _mem.get_raw()[0] == "\xFF":
+            mem.freq = 0
+            mem.empty = True
+            return mem
+
+        # freq + offset + duplex
+        mem.freq = int(_mem.rx_freq) *10
+        offset = (int(_mem.tx_freq) * 10) - mem.freq
+        if offset < 0:
+            mem.offset = abs(offset)
+            mem.duplex = "-"
+        elif offset > 0:
+            mem.offset = offset
+            mem.duplex = "+"
+        else:
+            mem.offset = 0
+
+        # tone data
+        rxtone = txtone = None
+        txtone = self._decode_tone(_mem.tx_tone)
+        rxtone = self._decode_tone(_mem.rx_tone)
+        chirp_common.split_tone_decode(mem, txtone, rxtone)
+
+        # Extra setting group
+        mem.extra = RadioSettingGroup("all", "All Settings")
+        busy = RadioSetting("Busy", "Busy Channel Lockout",
+                           RadioSettingValueBoolean(bool(_mem.busy_lock)))
+        mem.extra.append(busy)
+        scramble = RadioSetting("Scramble", "Scrambler Option",
+                            RadioSettingValueBoolean(bool(_mem.scramble)))
+        mem.extra.append(scramble)
+
+        # return mem
+        return mem
+
+    def set_memory(self, mem):
+        """Store details about a high-level memory to the memory map
+        This is called when a user edits a memory in the UI"""
+        # Get a low-level memory object mapped to the image
+        _mem = self._memobj.memory[mem.number - 1]
+
+        # Empty memory
+        if mem.empty:
+            _mem.set_raw("\xFF" * 16)
+            return
+
+        # freq rx
+        _mem.rx_freq = mem.freq / 10
+
+        # freq tx
+        if mem.duplex == "+":
+            _mem.tx_freq = (mem.freq + mem.offset) / 10
+        elif mem.duplex == "-":
+            _mem.tx_freq = (mem.freq - mem.offset) / 10
+        else:
+            _mem.tx_freq = mem.freq / 10
+
+        # tone data
+        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
+            chirp_common.split_tone_encode(mem)
+        self._encode_tone(_mem.tx_tone, txmode, txtone, txpol)
+        self._encode_tone(_mem.rx_tone, rxmode, rxtone, rxpol)
+
+
+        # extra settings
+        for setting in mem.extra:
+            setattr(_mem, setting.get_name(), setting.value)
+
+        return mem
+
+    def get_settings(self):
+        """Translate the bit in the mem_struct into settings in the UI"""
+        self._mem = self._memobj
+        basic = RadioSettingGroup("basic", "Basic")
+        work = RadioSettingGroup("work", "Work Mode Settings")
+        top = RadioSettings(basic, work)
+
+        # Basic
+        sql = RadioSetting("sql", "Squelch Level",
+                                RadioSettingValueList(LIST_SQL,
+                                        LIST_SQL[_mem.settings.sql]))
+        basic.append(sql)
+
+        tot = RadioSetting("tot", "Time out timer",
+                                RadioSettingValueList(LIST_TOT,
+                                        LIST_TOT[_mem.settings.tot]))
+        basic.append(tot)
+
+        power = RadioSetting("power", "Power",
+                                RadioSettingValueList(POWER_LEVELS,
+                                        POWER_LEVELS[_mem.settings.power]))
+        basic.append(power)
+
+        key_lock = RadioSetting("key_lock", "Keyboard Lock",
+                                RadioSettingValueList(KEY_LOCK,
+                                        KEY_LOCK[_mem.settings.key]))
+        basic.append(key_lock)
+
+        bw = RadioSetting("bw", "Bandwidth",
+                                RadioSettingValueList(BW,
+                                        BW[_mem.settings.bw]))
+        basic.append(bw)
+
+        lamp = RadioSetting("lamp", "LCD Lamp",
+                                RadioSettingValueBoolean(
+                                        self._mem.settings.lamp))
+        basic.append(lamp)
+
+        lamp_auto = RadioSetting("lamp_auto", "LCD Lamp Automatic",
+                                RadioSettingValueBoolean(
+                                        self._mem.settings.lamp_auto))
+        basic.append(lamp_auto)
+
+        bs = RadioSetting("bs", "Battery Save",
+                                RadioSettingValueBoolean(
+                                        self._mem.settings.bs))
+        basic.append(bs)
+
+        warning = RadioSetting("warning", "Warning Alerts",
+                                RadioSettingValueBoolean(
+                                        self._mem.settings.warning))
+        basic.append(warning)
+
+        monitor = RadioSetting("monitor", "Monitor key",
+                                RadioSettingValueBoolean(
+                                        self._mem.settings.monitor))
+        basic.append(monitor)
+
+        # Work mode settings
+        wmset = RadioSetting("wmset", "VFO/MR Mode",
+                                RadioSettingValueList(W_MODE,
+                                        W_MODE[_mem.settings.wmem]))
+        work.append(wmset)
+
+        active_ch = RadioSetting("active_ch", "Default Channel",
+                                RadioSettingValueList(ACTIVE_CH,
+                                        ACTIVE_CH[_mem.settings.active_ch]))
+        work.append(active_ch)
+
+        vf_freq = RadioSetting("vfo_freq", "VFO frequency",
+                        RadioSettingValueString(0, 7,
+                                str(int(_mem.vfo.vrx_freq) / 100000.0000)))
+        work.append(vf_freq)
+
+        sset = 0
+        if _mem.vfo.shift_minus == 1:
+            sset = 1
+        elif _mem.vfo.shift_plus == 1:
+            sset = 2
+
+        shift = RadioSetting("shift", "VFO Shift",
+                                RadioSettingValueList(VSHIFT,
+                                        VSHIFT[sset]))
+        work.append(shift)
+
+        offset = RadioSetting("offset", "VFO Offset",
+                        RadioSettingValueString(0, 7,
+                            str(int(_mem.settings.vfo_shift) / 100000.0000)))
+        work.append(offset)
+
+        step = RadioSetting("step", "VFO step",
+                                RadioSettingValueList(STEPF,
+                                        STEPF[_mem.settings.step]))
+        work.append(step)
+
+        return top
+
+    def set_settings(self, settings):
+        """Translate the settings in the UI into bit in the mem_struct"""
+        # work in progress, wait for it
+        pass
+
+@directory.register
+class FeidaxinFD268ARadio(FeidaxinFD268xRadio):
+    """Feidaxin FD-268A Radio"""
+    MODEL = "FD-268A"
+    _range = (136000000, 174000000)
+
+# FD-268B UHF: This radio can have the same layout...
+# I need a img to confirm and validate, see
+# http://chirp.danplanet.com/issues/2169
+#
+#@directory.register
+#class FeidaxinFD268BRadio(FeidaxinFD268xRadio):
+#    """Feidaxin FD-268B Radio"""
+#    MODEL = "FD-268B"
+#    _range = (400000000, 470000000)
