# HG changeset patch
# User Pavel Milanes (CO7WT) <co7wt@frcuba.co.cu>
# Date 1448546965 18000
#      Thu Nov 26 09:09:25 2015 -0500
# Node ID fd91e24c70d6d81bbdf190174f7c0448c45a1318
# Parent  02cff0d00d9ac87fdbd598fbf337b8fd7df3f652
[FD-268x/288x] New Driver, fix issue #2169

This are full support for Feidaxin models:

FD-268A (VHF)
FD-268B (UHF)
FD-288A-ANI (VHF)
FD-288B-ANI (UHF)

In the file are commented the definition to other
models that looks promising, I need radio to test
it's support

diff -r 02cff0d00d9a -r fd91e24c70d6 chirp/drivers/fd268.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/chirp/drivers/fd268.py	Thu Nov 26 09:09:25 2015 -0500
@@ -0,0 +1,923 @@
+# 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/>.
+
+# Driver author: Pavel, CO7WT, co7wt@frcuba.co.cu
+
+import struct
+import os
+import logging
+
+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,
+     scrambler: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 0x07B0;
+struct {
+  u8 ani_mode;          // fd-288's family store the any settings here
+  char ani[3];          // str of the any code (200-999) inclusive
+  u8 unknown21[12];     // fill to match the fd-268's start
+  u8 unknown22:5,       // x07c0, fd-268's start (LCD icons)
+     bw1:1,             // twin setting of bw (LCD "romb")
+     bs1:1,             // twin setting of bs (LCD "S")
+     warning1:1;        // twin setting of warning (LCD "Tune")
+  u8 sql[1];
+  u8 monitorval;
+  u8 tot[1];
+  u8 powerrank;
+  u8 unknown23[3];
+  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;
+"""
+
+MEM_SIZE=0x0800
+CMD_ACK = "\x06"
+BLOCK_SIZE = 0x08
+POWER_LEVELS = ["Low", "High"]
+LIST_SQL = ["Off"] + ["%s" % x for x in range(1, 10)]
+LIST_TOT = ["Off"] + ["%s" % x for x in range(10, 100, 10)]
+ONOFF = ["Off", "On"]
+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 = ["None", "-", "+"]
+POWER_RANK = ["%s" % x for x in range(0, 28)]
+ANI = ["Off", "BOT", "EOT", "Both"]
+
+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 check_ack(r, text):
+    """Check for a correct ACK from radio, rising error 'Text'
+    if something was wrong """
+    ack = raw_recv(r, 1)
+    if ack != CMD_ACK:
+        raise errors.RadioError(text)
+    else:
+        return True
+
+def send(radio, frame, data = ""):
+    """Generic send data to the radio"""
+    raw_send(radio, frame)
+    if data != "":
+        raw_send(radio, data)
+        check_ack(radio, "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(radio, CMD_ACK)
+        check_ack(radio, "Radio didn't ack the sended data")
+        return data
+
+def do_program(radio):
+    """Feidaxin program mode and identification dance"""
+    def do_magic():
+        """Try to get the radio in program mode, the factory software
+        (FDX-288) tries up to ~16 times to get the correct response,
+        we will do the same, but with a lower count."""
+        tries = 8
+        # UI information
+        status = chirp_common.Status()
+        status.cur = 0
+        status.max = tries
+        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, tries):
+            # 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:
+        erc  = "Radio did not accept program mode, "
+        erc += "check your cable and radio; then try again."
+        raise errors.RadioError(erc)
+
+    # now we request identification
+    send(radio, "M")
+    send(radio, "\x02")
+    ident = raw_recv(radio, 8)
+
+    ################# WARNING ##########################################
+    # Feidaxin radios has a "send id" procedure in the initial handshake
+    # but it's worthless, once you do a hardware reset the ident area
+    # get all 0xFF.
+    #
+    # Even FDX-288 software appears to match the model by any other
+    # mean, so I detected on a few images that the 3 first bytes are
+    # unique to each radio model, so for now we use that method untill
+    # proven otherwise
+    ####################################################################
+
+    LOG.debug("Radio's ID string:")
+    LOG.debug(util.hexprint(ident))
+
+    # final ACK
+    send(radio, CMD_ACK)
+    check_ack(radio, "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)
+
+def model_match(cls, data):
+    """Use a experimental guess to determine if the radio you just
+    downloaded or the img opened you is for this model"""
+
+    # Using a few imgs of some FD radio I found that the four first
+    # bytes it's like the model fingerprint, so we have to testing the
+    # model type with this experimental method so far.
+    fp = data[0:4]
+    if fp == cls._IDENT:
+        return True
+    else:
+        LOG.debug("Unknowd Feidaxing radio, ID:")
+        LOG.debug(util.hexprint(fp))
+        print("Unknowd Feidaxing radio, ID:")
+        print util.hexprint(fp)
+        return False
+
+class FeidaxinFD2x8yRadio(chirp_common.CloneModeRadio):
+    """Feidaxin FD-268x & 288x Radios"""
+    VENDOR = "Feidaxin"
+    MODEL = "FD-268A/B & FD-288A/B"
+    BAUD_RATE = 9600
+    _memsize = MEM_SIZE
+    _upper = 99
+    _VFO_DEFAULT = 0
+    _IDENT = ""
+    _active_ch = ACTIVE_CH
+
+    @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 = True
+        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_duplexes = ["", "-", "+", "off"]
+        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
+        # we have to remove "Tone->" because this is the same to "TQSL"
+        # I get a few days hitting the wall with my head about this...
+        rf.valid_cross_modes = [
+            "Tone->Tone",
+            "DTCS->",
+            "->DTCS",
+            "Tone->DTCS",
+            "DTCS->Tone",
+            "->Tone",
+            "DTCS->DTCS"]
+        # Power levels are golbal and no per channel, so disabled
+        #rf.valid_power_levels = POWER_LEVELS
+        # this radio has no skips
+        rf.valid_skips = []
+        # this radio modes are global and not per channel, so just FM
+        rf.valid_modes = ["FM"]
+        rf.valid_bands = [self._range]
+        rf.memory_bounds = (1, self._upper)
+        return rf
+
+    def sync_in(self):
+        """Do a download of the radio eeprom"""
+        data = do_download(self)
+
+        # as the radio comm protocol's identification is useless
+        # we test the model after having the img
+        if not model_match(self, data):
+            # ok, wrong model, fire an error
+            erc  = "EEPROM fingerprint don't match, check if you "
+            erc += "selected the right radio model."
+            raise errors.RadioError(erc)
+
+        # all ok
+        self._mmap = data
+        self.process_mmap()
+
+    def sync_out(self):
+        """Do an upload to the radio eeprom"""
+        do_upload(self)
+
+    def process_mmap(self):
+        """Process the memory object.
+        Used in the download & interpretation of the eeporm and files"""
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+    def get_raw_memory(self, number):
+        """Return a raw representation of the memory object"""
+        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)"""
+        if val.get_raw() == "\xFF\xFF":
+            return '', None, None
+
+        val = int(val)
+        if 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':
+            flag = 0x80 if pol == 'N' else 0xC0
+            memval.set_value(value)
+            memval[1].set_bits(flag)
+        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.empty = True
+            return mem
+
+        # rx freq
+        mem.freq = int(_mem.rx_freq) *10
+
+        # checking if tx freq is empty, this is "possible" on the
+        # original soft after a warning, and radio is happy with it
+        if _mem.tx_freq.get_raw() == "\xFF\xFF\xFF\xFF":
+            mem.duplex = "off"
+            mem.offset = 0
+        else:
+            rx = int(_mem.rx_freq) * 10
+            tx = int(_mem.tx_freq) * 10
+            if tx == rx:
+                mem.offset = 0
+                mem.duplex = ""
+            else:
+                mem.duplex = rx > tx and "-" or "+"
+                mem.offset = abs(tx - rx)
+
+        # tone data
+        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, FD-268 don't uset it at all
+        # FD-288's do?
+        mem.extra = RadioSettingGroup("extra", "Extra")
+        busy = RadioSetting("Busy", "Busy Channel Lockout",
+                                RadioSettingValueBoolean(
+                                    bool(_mem.busy_lock)))
+        mem.extra.append(busy)
+        scramble = RadioSetting("Scrambler", "Scrambler Option",
+                                    RadioSettingValueBoolean(
+                                        bool(_mem.scrambler)))
+        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
+        elif mem.duplex == "off":
+            for i in range(0, 4):
+                _mem.tx_freq[i].set_raw("\xFF")
+        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"""
+        _mem = self._memobj
+        basic = RadioSettingGroup("basic", "Basic")
+        work = RadioSettingGroup("work", "Work Mode Settings")
+        top = RadioSettings(basic, work)
+
+        # Basic
+        sql = RadioSetting("settings.sql", "Squelch Level",
+                                RadioSettingValueList(LIST_SQL,
+                                        LIST_SQL[_mem.settings.sql]))
+        basic.append(sql)
+
+        tot = RadioSetting("settings.tot", "Time out timer",
+                                RadioSettingValueList(LIST_TOT,
+                                        LIST_TOT[_mem.settings.tot]))
+        basic.append(tot)
+
+        power = RadioSetting("settings.power", "Actual Power",
+                                RadioSettingValueList(POWER_LEVELS,
+                                        POWER_LEVELS[
+                                            _mem.settings.power]))
+        basic.append(power)
+
+        key_lock = RadioSetting("settings.key", "Keyboard Lock",
+                                RadioSettingValueList(KEY_LOCK,
+                                        KEY_LOCK[_mem.settings.key]))
+        basic.append(key_lock)
+
+        bw = RadioSetting("settings.bw", "Bandwidth",
+                                RadioSettingValueList(BW,
+                                        BW[_mem.settings.bw]))
+        basic.append(bw)
+
+        powerrank = RadioSetting("settings.powerrank", "Power output adjust",
+                                RadioSettingValueList(POWER_RANK,
+                                        POWER_RANK[_mem.settings.powerrank]))
+        basic.append(powerrank)
+
+        lamp = RadioSetting("settings.lamp", "LCD Lamp",
+                                RadioSettingValueBoolean(
+                                        _mem.settings.lamp))
+        basic.append(lamp)
+
+        lamp_auto = RadioSetting("settings.lamp_auto", "LCD Lamp auto on/off",
+                                RadioSettingValueBoolean(
+                                        _mem.settings.lamp_auto))
+        basic.append(lamp_auto)
+
+        bs = RadioSetting("settings.bs", "Battery Save",
+                                RadioSettingValueBoolean(
+                                        _mem.settings.bs))
+        basic.append(bs)
+
+        warning = RadioSetting("settings.warning", "Warning Alerts",
+                                RadioSettingValueBoolean(
+                                        _mem.settings.warning))
+        basic.append(warning)
+
+        monitor = RadioSetting("settings.monitor", "Monitor key",
+                                RadioSettingValueBoolean(
+                                        _mem.settings.monitor))
+        basic.append(monitor)
+
+        # Work mode settings
+        wmset = RadioSetting("settings.wmem", "VFO/MR Mode",
+                                RadioSettingValueList(W_MODE,
+                                        W_MODE[_mem.settings.wmem]))
+        work.append(wmset)
+
+        active_ch = RadioSetting("settings.active_ch", "Work Channel",
+                                RadioSettingValueList(ACTIVE_CH,
+                                        ACTIVE_CH[_mem.settings.active_ch]))
+        work.append(active_ch)
+
+        # vfo rx validation
+        if _mem.vfo.vrx_freq.get_raw()[0] == "\xFF":
+            # if the vfo is not set, the UI cares about the
+            # length of the field, so set a default
+            LOG.debug("VFO freq not set, setting it to default %s" \
+                % self._VFO_DEFAULT)
+            vfo = self._VFO_DEFAULT
+        else:
+            vfo = int(_mem.vfo.vrx_freq) * 10
+
+        vf_freq = RadioSetting("vfo.vrx_freq", "VFO frequency",
+                        RadioSettingValueString(0, 10,
+                                chirp_common.format_freq(vfo)))
+        work.append(vf_freq)
+
+        # shift works
+        # VSHIFT = ["None", "-", "+"]
+        sset = 0
+        if bool(_mem.vfo.shift_minus) == True:
+            sset = 1
+        elif bool(_mem.vfo.shift_plus) == True:
+            sset = 2
+
+        shift = RadioSetting("shift", "VFO Shift",
+                                RadioSettingValueList(VSHIFT,
+                                        VSHIFT[sset]))
+        work.append(shift)
+
+        # vfo shift validation if none set it to ZERO
+        if _mem.settings.vfo_shift.get_raw()[0] == "\xFF":
+            # if the shift is not set, the UI cares about the
+            # length of the field, so set to zero
+            LOG.debug("VFO shift not set, setting it to zero")
+            vfo_shift = 0
+        else:
+            vfo_shift = int(_mem.settings.vfo_shift) * 10
+
+        offset = RadioSetting("settings.vfo_shift", "VFO Offset",
+                        RadioSettingValueString(0, 9,
+                             chirp_common.format_freq(vfo_shift)))
+        work.append(offset)
+
+        step = RadioSetting("settings.step", "VFO step",
+                                RadioSettingValueList(STEPF,
+                                        STEPF[_mem.settings.step]))
+        work.append(step)
+
+        # at least for FD-268A/B it doesn't work as stated, so disabled
+        # by now
+        #scamble = RadioSetting("vfo.scramble", "Scramble",
+                                #RadioSettingValueList(ONOFF,
+                                        #ONOFF[int(_mem.vfo.scramble)]))
+        #work.append(scamble)
+
+        #busy_lock = RadioSetting("vfo.busy_lock", "Busy Lock out",
+                                #RadioSettingValueList(ONOFF,
+                                        #ONOFF[int(_mem.vfo.busy_lock)]))
+        #work.append(busy_lock)
+
+        # FD-288 Family ANI settings
+        if "FD-288" in self.MODEL:
+            ani_mode = RadioSetting("settings.ani_mode", "ANI ID",
+                                RadioSettingValueList(ANI,
+                                        ANI[_mem.settings.ani_mode]))
+            work.append(ani_mode)
+
+            # it can't be \xFF
+            ani_value = str(_mem.settings.ani)
+            if ani_value == "\xFF\xFF\xFF":
+                ani_value = "200"
+
+            ani_value = "".join(x for x in ani_value if (int(x) >= 2 and int(x) <= 9))
+
+            ani = RadioSetting("settings.ani", "ANI (200-999)",
+                                RadioSettingValueString(0, 3, ani_value))
+            work.append(ani)
+
+        return top
+
+    def set_settings(self, settings):
+        """Translate the settings in the UI into bit in the mem_struct
+        I don't understand well the method used in many drivers
+        so, I used mine, ugly but works ok"""
+        def _get_shift(obj):
+            """Get the amount of offset in the memmap"""
+            shift = 0
+            sf = str(obj.settings.vfo_shift)
+            if sf[0] != "\xFF":
+                shift = int(mobj.settings.vfo_shift) * 10
+            return shift
+
+        def _get_vrx(obj):
+            """Get the vfo rx freq"""
+            return int(obj.vfo.vrx_freq) * 10
+
+        def _update_vtx(o, rx, offset):
+            """Update the Vfo TX mem from the value of rx & the offset"""
+            # check the shift sign
+            plus = bool(getattr(o, "shift_plus"))
+            minus = bool(getattr(o, "shift_minus"))
+
+            if plus:
+                o.vtx_freq = (rx + offset) / 10
+            elif minus:
+                o.vtx_freq = (rx - offset) / 10
+            else:
+                o.vtx_freq = rx / 10
+
+        mobj = self._memobj
+        flag = False
+        for element in settings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+
+            # Let's roll the ball
+            if "." in element.get_name():
+                # real properties, more or less mapeable
+                inter, setting  = element.get_name().split(".")
+                obj = getattr(mobj, inter)
+                value = element.value
+
+                # test on this cases .......
+                if setting in ["sql", "tot", "powerrank", \
+                    "active_ch", "ani_mode"]:
+                    value = int(value)
+
+                # test on this cases .......
+                if setting in ["lamp", "lamp_auto", "bs", \
+                    "warning","monitor"]:
+                    value = bool(value)
+                    # warning and bs have a sister setting in LCD
+                    if setting == "warning" or setting == "bs":
+                        # aditional setting
+                        setattr(obj, setting + "1", value)
+
+                    # monitorval: monitor = 0 > monitorval = 0
+                    # monitorval: monitor = 1 > monitorval = x30
+                    if setting == "monitor":
+                        # sister setting in LCD
+                        if value:
+                            setattr(obj, "monitorval", 0x30)
+                        else:
+                            setattr(obj, "monitorval", 0)
+
+                # case power
+                if setting == "power":
+                    value = str(value) == "High"  and True or False
+
+                # case key
+                # key => auto = 0, manu = 1
+                if setting == "key":
+                    value = str(value) == "Manual" and True or False
+
+                # case bandwidth
+                # bw: 0 = nar, 1 = Wide & must equal bw1
+                if setting == "bw":
+                    value = str(value) == "Wide"  and True or False
+                    # sister attr
+                    setattr(obj, "bw1", value)
+
+                # work mem wmem/wvfo
+                if setting == "wmem":
+                    if str(value) == "Memory":
+                        value = True
+                        # sister attr
+                        setattr(obj, "wvfo", not value)
+                    else:
+                        value = False
+                        # sister attr
+                        setattr(obj, "wvfo", not value)
+
+                # case step
+                # STEPF = ["5", "10", "6.25", "12.5", "25"]
+                if setting == "step":
+                    value = STEPF.index(str(value))
+
+                # case vrx_freq
+                if setting == "vrx_freq":
+                    value = chirp_common.parse_freq(str(value)) / 10
+                    # you must calculate the apropiate txfreq from shift and offset
+                    # verify shift
+                    shift = _get_shift(mobj)
+                    # update the tx vfo freq
+                    _update_vtx(obj, value * 10, shift)
+
+                # vfo_shift = offset
+                if setting == "vfo_shift":
+                    value = chirp_common.parse_freq(str(value)) / 10
+                    # you must calculate the apropiate txfreq from shift and offset
+                    # get vfo rx
+                    vrx = _get_vrx(mobj)
+                    # update the tx vfo freq
+                    _update_vtx(mobj.vfo, vrx, value * 10)
+
+                # at least for FD-268A/B it doesn't work as stated, so disabled
+                # by now, does this work on the fd-288s?
+
+                ## case scramble & busy_lock
+                #if setting == "scramble" or setting == "busy_lock":
+                    #value = bool(ONOFF.index(str(value)))
+
+                # ani value, only for FD-288
+                if setting == "ani":
+                    if "FD-268" in self.MODEL:
+                        # 268 doesn't have ani setting
+                        continue
+                    else:
+                        # 288 models, validate the input [200-999] | ""
+                        # we will left adjust and zero pad to avoid errors
+                        # between inputs in the UI
+                        value = str(value).strip().ljust(3, "0")
+                        value = int(value)
+                        if value == 0:
+                            value = 200
+                        else:
+                            if value > 999 or value < 200:
+                                raise errors.InvalidValueError(
+                                    "The ANI value must be between 200 and 999, not %03i" % value)
+
+                        value = str(value)
+
+            else:
+                # Others that are artifact for real values, not than mapeables
+                # selecting the values to work
+                setting = str(element.get_name())
+                value = str(element.value)
+
+                # vfo_shift
+                if setting == "shift":
+                    # get shift
+                    offset = _get_shift(mobj)
+                    # get vfo rx
+                    vrx = _get_vrx(mobj)
+
+                    # VSHIFT = ["None", "-", "+"]
+                    if value == "+":
+                        mobj.vfo.shift_plus = True
+                        mobj.vfo.shift_minus = False
+                    elif value == "-":
+                        mobj.vfo.shift_plus = False
+                        mobj.vfo.shift_minus = True
+                    else:
+                        mobj.vfo.shift_plus = False
+                        mobj.vfo.shift_minus = False
+
+                    # update the tx vfo freq
+                    _update_vtx(mobj.vfo, vrx, offset)
+
+            if setting != "shift":
+                setattr(obj, setting, value)
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        match_size = False
+        match_model = False
+
+        # testing the file data size
+        if len(filedata) == MEM_SIZE:
+            match_size = True
+
+        # testing the firmware fingerprint, this experimental
+        match_model = model_match(cls, filedata)
+
+        if match_size and match_model:
+            return True
+        else:
+            return False
+
+##
+## FD-268 family: this are the original tested models, FD-268B UHF
+## was tested "remotely" just have the 268A at hand to test.
+##
+
+@directory.register
+class FD268ARadio(FeidaxinFD2x8yRadio):
+    """Feidaxin FD-268A Radio"""
+    MODEL = "FD-268A"
+    _range = (136000000, 174000000)
+    _VFO_DEFAULT = 145000000
+    _IDENT = "\xFF\xEE\x46\xFF"
+
+@directory.register
+class FD268BRadio(FeidaxinFD2x8yRadio):
+    """Feidaxin FD-268B Radio"""
+    MODEL = "FD-268B"
+    _range = (400000000, 470000000)
+    _VFO_DEFAULT = 439000000
+    _IDENT = "\xFF\xEE\x44\xFF"
+
+##
+## FD-288 Family: the only difference from this family to the FD-268's
+## are the ANI settings
+##
+
+@directory.register
+class FD288ARadio(FeidaxinFD2x8yRadio):
+    """Feidaxin FD-288A Radio"""
+    MODEL = "FD-288A"
+    _range = (136000000, 174000000)
+    _VFO_DEFAULT = 145000000
+    _IDENT = "\xFF\xEE\x4B\xFF"
+
+@directory.register
+class FD288BRadio(FeidaxinFD2x8yRadio):
+    """Feidaxin FD-288 Radio"""
+    MODEL = "FD-288B"
+    _range = (400000000, 470000000)
+    _VFO_DEFAULT = 439000000
+    _IDENT = "\xFF\xEE\x4C\xFF"
+
+#######################################################################$
+# This is are just rough guesses and must be tested with radios
+# on hand to test it, but, playing with FDX-288 soft & chirp
+# it looks promising
+########################################################################
+
+#@directory.register
+#class FD160ARadio(FeidaxinFD2x8yRadio):
+    #"""Feidaxin FD-160A Radio"""
+    #MODEL = "FD-160A"
+    #_range = (136000000, 174000000)
+    #_VFO_DEFAULT = 145000000
+    #_IDENT = "\xFF\xEE\x48\xFF"
+
+#@directory.register
+#class FD460ARadio(FeidaxinFD2x8yRadio):
+    #"""Feidaxin FD-460A Radio"""
+    #MODEL = "FD-460A"
+    #_range = (400000000, 470000000)
+    #_VFO_DEFAULT = 439000000
+    #_IDENT = "\xFF\xEE\x4A\xFF"
+
+#@directory.register
+#class FD450ARadio(FeidaxinFD2x8yRadio):
+    #"""Feidaxin FD-450A Radio"""
+    #MODEL = "FD-450A"
+    #_range = (400000000, 470000000)
+    #_VFO_DEFAULT = 439000000
+    #_IDENT = "\xFF\xEE\x44\xFF"
