[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