[chirp_devel] [PATCH] [PATCH][FTL-x011] Add new driver for FTL-1011/2011/7011/8011 radios #7599

Pavel Milanes (CO7WT)
Sat Jul 31 14:40:49 PDT 2021


# HG changeset patch
# User Pavel Milanes (CO7WT) <pavelmc at gmail.com>
# Date 1627767411 14400
#      Sat Jul 31 17:36:51 2021 -0400
# Node ID aac69b79bcebff44a6a3c6d0f32b501bcc89f9a3
# Parent  5f059dbcf609d29cce65266b5be6cc680b26cae0
[PATCH][FTL-x011] Add new driver for FTL-1011/2011/7011/8011 radios #7599

Add suppors for the 4/12/24 variants of the Vertex FTL-1011/2011/7011/8011,
there is a later models with 99 channels that uses a different mem layout
and is not supported by this driver.

Sample image file will be emailed later.

diff --git a/chirp/drivers/ftlx011.py b/chirp/drivers/ftlx011.py
new file mode 100644
--- /dev/null
+++ b/chirp/drivers/ftlx011.py
@@ -0,0 +1,798 @@
+# Copyright 2019 Pavel Milanes, CO7WT <pavelmc at gmail.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 time
+
+from time import sleep
+from chirp import chirp_common, directory, memmap, errors, util, bitwise
+from chirp.settings import RadioSettingGroup, RadioSetting, \
+    RadioSettingValueBoolean, RadioSettingValueList, \
+    RadioSettingValueString, RadioSettingValueInteger, \
+    RadioSettingValueFloat, RadioSettings, InvalidValueError
+from textwrap import dedent
+
+LOG = logging.getLogger(__name__)
+
+### SAMPLE MEM DUMP as sent from the radios
+
+# FTL-1011
+#0x000000  52 f0 16 90 04 08 38 c0  00 00 00 01 00 00 00 ff  |R.....8.........|
+#0x000010  20 f1 00 20 00 00 00 20  04 47 25 04 47 25 00 00  | .. ... .G%.G%..|
+
+# FTL-2011
+#0x000000: 50 90 21 40 04 80 fc 40  00 00 00 01 00 00 00 ff  |P.!@... at ........|
+#0x000010: 20 f1 00 0b 00 00 00 0b  14 51 70 14 45 70 00 00  |.........Qp.Ep..|
+
+
+MEM_FORMAT = """
+#seekto 0x000;
+u8 rid;                 // Radio Identification
+
+struct {
+u8 scan_time:4,         // Scan timer per channel: 0-15 (5-80msec in 5msec steps)
+   unknownA:4;
+bbcd if[2];             // Radio internal IF, depending on model (16.90, 21.40, 45.10, 47.90)
+u8 chcount;             // how many channels are programmed
+u8 scan_resume:1,           // Scan sesume: 0 = 0.5 seconds, 1 = Carrier
+   priority_during_scan:1,  // Priority during scan: 0 = enabled, 1 = disabled
+   priority_speed:1,        // Priority speed: 0 = slow, 1 = fast
+   monitor:1,               // Monitor: 0 = enabled, 1 = disabled
+   off_hook:1,              // Off hook: 0 = enabled, 1 = disabled
+   home_channel:1,          // Home Channel: 0 = Scan Start ch, 1 = Priority 1ch
+   talk_back:1,             // Talk Back: 0 = enabled, 1 = disabled
+   tx_carrier_delay:1;      // TX carrier delay: 1 = enabled, 0 = disabled
+u8 tot:4,                   // Time out timer: 16 values (0.0-7.5 in 0.5s step)
+   tot_resume:2,            // Time out timer resume: 3, 2, 1, 0 => 0s, 6s, 20s, 60s 
+   unknownB:2;
+u8 a_key:2,                 // A key function: resume: 0-3: Talkaround, High/Low, Call, Accessory
+   unknownB:6;
+u8 pch1;                    // Priority channel 1
+u8 pch2;                    // Priority channel 1
+} settings;
+
+#seekto 0x010;
+struct {
+  u8 notx:1,            // 0 = Tx posible,  1 = Tx disabled
+     empty:1,           // 0 = channel enabled, 1 = channed empty
+     tot:1,             // 0 = tot disabled, 1 = tot enabled
+     power:1,           // 0 = high, 1 = low
+     bclo_cw:1,         // 0 = disabled, 1 = Busy Channel Lock out by carrier
+     bclo_tone:1,       // 0 = disabled, 1 = Busy Channel Lock out by tone (set rx tone)
+     skip:1,            // 0 = scan enabled, 1 = skip on scanning
+     unknownA0:1;
+  u8 chname;
+  u8 rx_tone[2];      // empty value is \x00\x0B / disabled is \x00\x00
+  u8 unknown4;
+  u8 unknown5;
+  u8 tx_tone[2];      // empty value is \x00\x0B / disabled is \x00\x00
+  bbcd rx_freq[3];      // RX freq
+  bbcd tx_freq[3];      // TX freq
+  u8 unknownA[2];
+} memory[24];
+
+#seekto 0x0190;
+char filename[11];
+
+#seekto 0x19C;
+u8 checksum;
+"""
+
+MEM_SIZE = 0x019C
+POWER_LEVELS = [chirp_common.PowerLevel("High", watts=50),
+                chirp_common.PowerLevel("Low", watts=5)]
+DTCS_CODES = chirp_common.DTCS_CODES
+SKIP_VALUES = ["", "S"]
+LIST_BCL = ["OFF", "Carrier", "Tone"]
+LIST_SCAN_RESUME = ["0.5 seconds", "Carrier drop"]
+LIST_SCAN_TIME = ["%smsecs" % x for x in range(5, 85, 5)]
+LIST_SCAN_P_SPEED = ["Slow", "Fast"]
+LIST_HOME_CHANNEL = ["Scan Start ch", "Priority 1ch"]
+LIST_TOT = ["Off"] + ["%.1f s" % (x/10.0) for x in range(5, 80, 5)]
+# 3, 2, 1, 0 => 0s, 6s, 20s, 60s
+LIST_TOT_RESUME = ["60s","20s","6s","0s"]
+LIST_A_KEY = ["Talkaround", "High/Low", "Call", "Accessory"]
+LIST_PCH = [] # dynamic, as depends on channel list.
+# make a copy of the tones, is not funny to work with this directly
+TONES = list(chirp_common.TONES)
+# this old radios has not the full tone ranges in CST
+invalid_tones = (
+    69.3,
+    159.8,
+    165.5,
+    171.3,
+    177.3,
+    183.5,
+    189.9,
+    196.6,
+    199.5,
+    206.5,
+    229.1,
+    245.1)
+
+# remove invalid tones
+for tone in invalid_tones:
+    try:
+        TONES.remove(tone)
+    except:
+        pass
+
+
+def _set_serial(radio):
+    """Set the serial protocol settings"""
+    radio.pipe.timeout = 10
+    radio.pipe.parity = "N"
+    radio.pipe.baudrate = 9600
+
+
+def _checksum(data):
+    """the radio block checksum algorithm"""
+    cs = 0
+    for byte in data:
+            cs += ord(byte)
+
+    return cs % 256
+
+
+def _update_cs(radio):
+    """Update the checksum on the mmap"""
+    payload = str(radio.get_mmap())[:-1]
+    cs = _checksum(payload)
+    radio._mmap[MEM_SIZE - 1] = cs
+
+
+def _do_download(radio):
+    """ The download function """
+    # Get the whole 413 bytes (0x019D) bytes one at a time with plenty of time
+    # to get to the user's pace
+
+    # set serial discipline
+    _set_serial(radio)
+
+    # UI progress
+    status = chirp_common.Status()
+    status.cur = 0
+    status.max = MEM_SIZE
+    status.msg = " Press A to clone. "
+    radio.status_fn(status)
+
+    data = ""
+    for i in range(0, MEM_SIZE):
+        a = radio.pipe.read(1)
+        if len(a) == 0:
+            # error, no received data
+            if len(data) != 0:
+                # received some data, not the complete stream
+                msg = "Just %02i bytes of the %02i received, try again." % \
+                    (len(data), MEM_SIZE)
+            else:
+                # timeout, please retry
+                msg = "No data received, try again."
+
+            raise errors.RadioError(msg)
+
+        data += a
+        # UI Update
+        status.cur = len(data)
+        radio.status_fn(status)
+
+    if len(data) != MEM_SIZE:
+        msg = "Incomplete data, we need %02i but got %02i bytes." % \
+            (MEM_SIZE, len(data))
+        raise errors.RadioError(msg)
+
+    if ord(data[-1]) != _checksum(data[:-1]):
+        msg = "Bad checksum, please try again."
+        raise errors.RadioError(msg)
+
+    return data
+
+
+def _do_upload(radio):
+    """The upload function"""
+    # set serial discipline
+    _set_serial(radio)
+
+    # UI progress
+    status = chirp_common.Status()
+
+    # 10 seconds timeout
+    status.cur = 0
+    status.max = 100
+    status.msg = " Quick, press MON on the radio to start. "
+    radio.status_fn(status)
+
+    for byte in range(0,100):
+        status.cur = byte
+        radio.status_fn(status)
+        time.sleep(0.1)
+
+
+    # real upload if user don't cancel the timeout
+    status.cur = 0
+    status.max = MEM_SIZE
+    status.msg = " Cloning to radio... "
+    radio.status_fn(status)
+
+    # send data
+    data = str(radio.get_mmap())
+
+    # this radio has a trick, the EEPROM is an ancient SPI one, so it needs
+    # some time to write, so we send every byte and then allow
+    # a 0.01 seg to complete the write from the MCU to the SPI EEPROM
+    c = 0
+    for byte in data:
+        radio.pipe.write(byte)
+        time.sleep(0.01)
+
+        # UI Update
+        status.cur = c
+        radio.status_fn(status)
+
+        # counter
+        c = c + 1
+
+
+def _model_match(cls, data):
+    """Use a experimental guess to determine if the radio you just
+    downloaded or the img you opened is for this model"""
+
+    # It's hard to tell when this radio is really this radio.
+    # I use the first byte, that appears to be the ID and the IF settings
+
+    LOG.debug("Drivers's ID string:")
+    LOG.debug(cls.finger)
+    LOG.debug("Radio's ID string:")
+    LOG.debug(util.hexprint(data[0:4]))
+
+    radiod = [data[0], data[2:4]]
+    if cls.finger == radiod:
+        return True
+    else:
+        return False
+
+
+def bcd_to_int(data):
+    """Convert an array of bcdDataElement like \x12
+    into an int like 12"""
+    value = 0
+    a = (data & 0xF0) >> 4
+    b = data & 0x0F
+    value = (a * 10) + b
+    return value
+
+
+def int_to_bcd(data):
+    """Convert a int like 94 to 0x94"""
+    data, lsb = divmod(data, 10)
+    data, msb = divmod(data, 10)
+    res = (msb << 4) + lsb
+    return res
+
+
+class ftlx011(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
+    """Vertex FTL1011/2011/7011 4/8/12/24 channels"""
+    VENDOR = "Vertex Standard"
+    _memsize = MEM_SIZE
+    _upper = 0
+    _range = []
+    finger = [] # two elements rid & IF
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.experimental = \
+            ('This is a experimental driver, use it on your own risk.\n'
+             '\n'
+             'This driver is just for the 4/12/24 channels variants of '
+             'these radios, 99 channel variants are not supported yet.\n'
+             '\n'
+             'The 99 channel versions appears to use another mem layout.\n'
+             )
+        rp.pre_download = _(dedent("""\
+            Please follow this steps carefully:
+
+            1 - Turn on your radio
+            2 - Connect the interface cable to your radio.
+            3 - Click the button on this window to start download
+                (Radio will beep and led will flash)
+            4 - Then press the "A" button in your radio to start cloning.
+                (At the end radio will beep)
+            """))
+        rp.pre_upload = _(dedent("""\
+            Please follow this steps carefully:
+
+            1 - Turn on your radio
+            2 - Connect the interface cable to your radio
+            3 - Click the button on this window to start download
+                (you may see another dialog, click ok)
+            4 - Radio will beep and led will flash
+            5 - You will get a 10 seconds timeout to press "MON" before
+                data upload start
+            6 - If all goes right radio will beep at end.
+
+            After cloning remove the cable and power cycle your radio to
+            get into normal mode.
+            """))
+        return rp
+
+    def get_features(self):
+        """Return information about this radio's features"""
+        rf = chirp_common.RadioFeatures()
+        rf.has_settings = True
+        rf.has_bank = False
+        rf.has_tuning_step = False
+        rf.has_name = False
+        rf.has_offset = True
+        rf.has_mode = True
+        rf.has_dtcs = True
+        rf.has_rx_dtcs = True
+        rf.has_dtcs_polarity = False
+        rf.has_ctone = True
+        rf.has_cross = True
+        rf.valid_duplexes = ["", "-", "+", "off"]
+        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
+        rf.valid_cross_modes = [
+            "Tone->Tone",
+            "DTCS->DTCS",
+            "DTCS->",
+            "->DTCS"]
+        rf.valid_dtcs_codes = DTCS_CODES
+        rf.valid_skips = SKIP_VALUES
+        rf.valid_modes = ["FM"]
+        rf.valid_power_levels = POWER_LEVELS
+        #rf.valid_tuning_steps = [5.0]
+        rf.valid_bands = [self._range]
+        rf.memory_bounds = (1, self._upper)
+        return rf
+
+    def sync_in(self):
+        """Do a download of the radio eeprom"""
+        try:
+            data = _do_download(self)
+        except Exception, e:
+            raise errors.RadioError("Failed to communicate with radio:\n %s" % e)
+
+        # match model
+        if _model_match(self, data) is False:
+            raise errors.RadioError("Incorrect radio model")
+
+        self._mmap = memmap.MemoryMap(data)
+        self.process_mmap()
+
+        # set the channel count from the radio eeprom
+        self._upper = int(ord(data[4]))
+
+    def sync_out(self):
+        """Do an upload to the radio eeprom"""
+        # update checksum
+        _update_cs(self)
+
+        # sanity check, match model
+        data = str(self.get_mmap())
+        if len(data) != MEM_SIZE:
+            raise errors.RadioError("Wrong radio image? Size miss match.")
+
+        if _model_match(self, data) is False:
+            raise errors.RadioError("Wrong image? Fingerprint miss match")
+
+        try:
+            _do_upload(self)
+        except Exception, e:
+            msg = "Failed to communicate with radio:\n%s" % e
+            raise errors.RadioError(msg)
+
+    def process_mmap(self):
+        """Process the memory object"""
+        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, mem, rx=True):
+        """Parse the tone data to decode from mem tones are encodded like this
+        CTCS: mapped [0x80...0xa5] = [67.0...250.3]
+        DTCS: mixed  [0x88, 0x23] last is BCD and first is the 100 power - 88
+
+        It return: ((''|DTCS|Tone), Value (None|###), None)"""
+        mode = ""
+        tone = None
+
+        # get the tone depending of rx or tx
+        if rx:
+            t = mem.rx_tone
+        else:
+            t = mem.tx_tone
+        
+        tMSB = t[0]
+        tLSB = t[1]
+
+        # no tone at all
+        if (tMSB == 0 and tLSB < 128):
+            return ('', None, None)
+
+        # extract the tone info
+        if tMSB == 0x00:
+            # CTCS
+            mode = "Tone"
+            try:
+                tone = TONES[tLSB - 128]
+            except IndexError:
+                LOG.debug("Error decoding a CTCS tone")
+                pass
+        else:
+            # DTCS
+            mode = "DTCS"
+            try:
+                tone = ((tMSB - 0x88) * 100) + bcd_to_int(tLSB)
+            except IndexError:
+                LOG.debug("Error decoding a DTCS tone")
+                pass
+
+        return (mode, tone, None)
+
+    def _encode_tone(self, mem, mode, value, pol, rx=True):
+        """Parse the tone data to encode from UI to mem
+        CTCS: mapped [0x80...0xa5] = [67.0...250.3]
+        DTCS: mixed  [0x88, 0x23] last is BCD and first is the 100 power - 88
+        """
+
+        # array to pass
+        tone = [0x00, 0x00]
+
+        # which mod
+        if mode == "DTCS":
+            tone[0] = int(value / 100) + 0x88
+            tone[1] = int_to_bcd(value % 100)
+        
+        if mode == "Tone":
+            #CTCS
+            tone[1] = TONES.index(value) + 128
+
+        # set it
+        if rx:
+            mem.rx_tone = tone
+        else:
+            mem.tx_tone = tone
+
+    def get_memory(self, number):
+        """Extract a memory object from the memory map"""
+        # 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 bool(_mem.empty) is True:
+            mem.empty = True
+            return mem
+
+        # rx freq
+        mem.freq = int(_mem.rx_freq) * 1000
+
+        # power
+        mem.power = POWER_LEVELS[int(_mem.power)]
+
+        # checking if tx freq is disabled
+        if bool(_mem.notx) is True:
+            mem.duplex = "off"
+            mem.offset = 0
+        else:
+            tx = int(_mem.tx_freq) * 1000
+            if tx == mem.freq:
+                mem.offset = 0
+                mem.duplex = ""
+            else:
+                mem.duplex = mem.freq > tx and "-" or "+"
+                mem.offset = abs(tx - mem.freq)
+
+        # skip
+        mem.skip = SKIP_VALUES[_mem.skip]
+
+        # tone data
+        rxtone = txtone = None
+        rxtone = self._decode_tone(_mem)
+        txtone = self._decode_tone(_mem, False)
+        chirp_common.split_tone_decode(mem, txtone, rxtone)
+
+        # this radio has a primitive mode to show the channel number on a 7-segment
+        # two digit LCD, we will use channel number
+        # we will use a trick to show the numbers < 10 wit a space not a zero in front
+        chname = int_to_bcd(mem.number)
+        if mem.number < 10:
+            # convert to F# as BCD
+            chname = mem.number + 240
+
+        _mem.chname = chname
+
+        # Extra
+        mem.extra = RadioSettingGroup("extra", "Extra")
+
+        # bcl preparations: ["OFF", "Carrier", "Tone"]
+        bcls = 0
+        if _mem.bclo_cw:
+            bcls = 1
+        if _mem.bclo_tone:
+            bcls = 2
+
+        bcl = RadioSetting("bclo", "Busy channel lockout",
+                           RadioSettingValueList(LIST_BCL,
+                                                 LIST_BCL[bcls]))
+        mem.extra.append(bcl)
+
+        # 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.empty = True
+            _mem.rx_freq = _mem.tx_freq = 0
+            return
+
+        # freq rx
+        _mem.rx_freq = mem.freq / 1000
+
+        # power, # default power level is high
+        _mem.power = 0 if mem.power is None else POWER_LEVELS.index(mem.power)
+
+        # freq tx
+        if mem.duplex == "+":
+            _mem.tx_freq = (mem.freq + mem.offset) / 1000
+        elif mem.duplex == "-":
+            _mem.tx_freq = (mem.freq - mem.offset) / 1000
+        elif mem.duplex == "off":
+            _mem.notx = 1
+            _mem.tx_freq = _mem.rx_freq
+        else:
+            _mem.tx_freq = mem.freq / 1000
+
+        # scan add property
+        _mem.skip = SKIP_VALUES.index(mem.skip)
+
+        # tone data
+        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
+            chirp_common.split_tone_encode(mem)
+
+        # validate tone data from here
+        if rxmode == "Tone" and rxtone in invalid_tones:
+            msg = "The tone %shz is not valid for this radio" % rxtone
+            raise errors.UnsupportedToneError(msg)
+
+        if txmode == "Tone" and txtone in invalid_tones:
+            msg = "The tone %shz is not valid for this radio" % txtone
+            raise errors.UnsupportedToneError(msg)
+
+        if rxmode == "DTCS" and rxtone not in DTCS_CODES:
+            msg = "The digital tone %s is not valid for this radio" % rxtone
+            raise errors.UnsupportedToneError(msg)
+
+        if txmode == "DTCS" and txtone not in DTCS_CODES:
+            msg = "The digital tone %s is not valid for this radio" % txtone
+            raise errors.UnsupportedToneError(msg)
+
+        self._encode_tone(_mem, rxmode, rxtone, rxpol)
+        self._encode_tone(_mem, txmode, txtone, txpol, False)
+
+        # this radio has a primitive mode to show the channel number on a 7-segment
+        # two digit LCD, we will use channel number
+        # we will use a trick to show the numbers < 10 wit a space not a zero in front
+        chname = int_to_bcd(mem.number)
+        if mem.number < 10:
+            # convert to F# as BCD
+            chname = mem.number + 240
+
+        def _zero_settings():
+            _mem.bclo_cw = 0
+            _mem.bclo_tone = 0
+
+        # extra settings
+        if len(mem.extra) > 0:
+            # there are setting, parse
+            LOG.debug("Extra-Setting supplied. Setting them.")
+            # Zero them all first so any not provided by model don't
+            # stay set
+            _zero_settings()
+            for setting in mem.extra:
+                if setting.get_name() == "bclo":
+                    sw = LIST_BCL.index(str(setting.value))
+                    if sw == 0:
+                        # empty
+                        _zero_settings()
+                    if sw == 1:
+                        # carrier
+                        _mem.bclo_cw = 1
+                    if sw == 2:
+                        # tone
+                        _mem.bclo_tone = 1
+                        # activate the tone
+                        _mem.rx_tone = [0x00, 0x80]
+        else:
+            # reset extra settings 
+            _zero_settings()
+
+        _mem.chname = chname
+
+        return mem
+
+    def get_settings(self):
+        _settings = self._memobj.settings
+        basic = RadioSettingGroup("basic", "Basic Settings")
+        group = RadioSettings(basic)
+
+        # ## Basic Settings 
+        scanr = RadioSetting("scan_resume", "Scan resume by",
+                          RadioSettingValueList(
+                              LIST_SCAN_RESUME, LIST_SCAN_RESUME[_settings.scan_resume]))
+        basic.append(scanr)
+
+        scant = RadioSetting("scan_time", "Scan time per channel",
+                          RadioSettingValueList(
+                              LIST_SCAN_TIME, LIST_SCAN_TIME[_settings.scan_time]))
+        basic.append(scant)
+
+        LIST_PCH = ["%s" % x for x in range(1, _settings.chcount + 1)]
+        pch1 = RadioSetting("pch1", "Priority channel 1",
+                             RadioSettingValueList(
+                                 LIST_PCH, LIST_PCH[_settings.pch1]))
+        basic.append(pch1)
+
+        pch2 = RadioSetting("pch2", "Priority channel 2",
+                            RadioSettingValueList(
+                                LIST_PCH, LIST_PCH[_settings.pch2]))
+        basic.append(pch2)
+
+        scanp = RadioSetting("priority_during_scan", "Disable priority during scan",
+                             RadioSettingValueBoolean(_settings.priority_during_scan))
+        basic.append(scanp)
+
+        scanps = RadioSetting("priority_speed", "Priority scan speed",
+                              RadioSettingValueList(
+                                LIST_SCAN_P_SPEED, LIST_SCAN_P_SPEED[_settings.priority_speed]))
+        basic.append(scanps)
+
+        oh = RadioSetting("off_hook", "Off Hook", #inverted
+                        RadioSettingValueBoolean(not _settings.off_hook))
+        basic.append(oh)
+
+        tb = RadioSetting("talk_back", "Talk Back",  # inverted
+                          RadioSettingValueBoolean(not _settings.talk_back))
+        basic.append(tb)
+
+        tot = RadioSetting("tot", "Time out timer",
+                             RadioSettingValueList(
+                                 LIST_TOT, LIST_TOT[_settings.tot]))
+        basic.append(tot)
+
+        totr = RadioSetting("tot_resume", "Time out timer resume guard",
+                           RadioSettingValueList(
+                               LIST_TOT_RESUME, LIST_TOT_RESUME[_settings.tot_resume]))
+        basic.append(totr)
+
+        ak = RadioSetting("a_key", "A Key function",
+                            RadioSettingValueList(
+                                LIST_A_KEY, LIST_A_KEY[_settings.a_key]))
+        basic.append(ak)
+
+        monitor = RadioSetting("monitor", "Monitor",  # inverted
+                               RadioSettingValueBoolean(not _settings.monitor))
+        basic.append(monitor)
+
+        homec = RadioSetting("home_channel", "Home Channel is",
+                             RadioSettingValueList(
+                                 LIST_HOME_CHANNEL, LIST_HOME_CHANNEL[_settings.home_channel]))
+        basic.append(homec)
+
+        txd = RadioSetting("tx_carrier_delay", "Talk Back",
+                           RadioSettingValueBoolean(_settings.tx_carrier_delay))
+        basic.append(txd)
+
+
+        return group
+
+    def set_settings(self, uisettings):
+        _settings = self._memobj.settings
+
+        for element in uisettings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+            if not element.changed():
+                continue
+
+            try:
+                name = element.get_name()
+                value = element.value
+
+                print("== Setting %s: %s" % (name, value))
+
+                obj = getattr(_settings, name)
+                if name in ["off_hook", "talk_back", "monitor"]:
+                    setattr(_settings, name, not value)
+                else:
+                    setattr(_settings, name, value)
+
+                LOG.debug("Setting %s: %s" % (name, value))
+            except Exception, e:
+                LOG.debug(element.get_name())
+                raise
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        match_size = False
+        match_model = False
+
+        # testing the file data size
+        if len(filedata) == cls._memsize:
+            match_size = True
+            print("Comp: %i file / %i memzise" % (len(filedata), cls._memsize) )
+
+        # testing the firmware fingerprint, this experimental
+        match_model = _model_match(cls, filedata)
+
+        if match_size and match_model:
+            return True
+        else:
+            return False
+
+
+ at directory.register
+class ftl1011(ftlx011):
+    """Vertex FTL-1011"""
+    MODEL = "FTL-1011"
+    _memsize = MEM_SIZE
+    _upper = 4
+    _range = [44000000, 56000000]
+    finger = ["\x52", "\x16\x90"]
+
+
+ at directory.register
+class ftl2011(ftlx011):
+    """Vertex FTL-2011"""
+    MODEL = "FTL-2011"
+    _memsize = MEM_SIZE
+    _upper = 24
+    _range = [134000000, 174000000]
+    finger = ["\x50", "\x21\x40"]
+
+
+ at directory.register
+class ftl7011(ftlx011):
+    """Vertex FTL-7011"""
+    MODEL = "FTL-7011"
+    _memsize = MEM_SIZE
+    _upper = 24
+    _range = [400000000, 512000000]
+    finger = ["\x54", "\x47\x90"]
+
+
+ at directory.register
+class ftl8011(ftlx011):
+    """Vertex FTL-8011"""
+    MODEL = "FTL-8011"
+    _memsize = MEM_SIZE
+    _upper = 24
+    _range = [400000000, 512000000]
+    finger = ["\x5c", "\x45\x10"]



More information about the chirp_devel mailing list