Project

General

Profile

Bug #9499 » ftlx011_notx-empty_fix_#2.py

Jim Unroe, 12/31/2021 05:37 PM

 
1
# Copyright 2019 Pavel Milanes, CO7WT <pavelmc@gmail.com>
2
#
3
# This program is free software: you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation, either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
15

    
16
import struct
17
import os
18
import logging
19
import time
20

    
21
from time import sleep
22
from chirp import chirp_common, directory, memmap, errors, util, bitwise
23
from chirp.settings import RadioSettingGroup, RadioSetting, \
24
    RadioSettingValueBoolean, RadioSettingValueList, \
25
    RadioSettingValueString, RadioSettingValueInteger, \
26
    RadioSettingValueFloat, RadioSettings, InvalidValueError
27
from textwrap import dedent
28

    
29
LOG = logging.getLogger(__name__)
30

    
31
### SAMPLE MEM DUMP as sent from the radios
32

    
33
# FTL-1011
34
#0x000000  52 f0 16 90 04 08 38 c0  00 00 00 01 00 00 00 ff  |R.....8.........|
35
#0x000010  20 f1 00 20 00 00 00 20  04 47 25 04 47 25 00 00  | .. ... .G%.G%..|
36

    
37
# FTL-2011
38
#0x000000: 50 90 21 40 04 80 fc 40  00 00 00 01 00 00 00 ff  |P.!@...@........|
39
#0x000010: 20 f1 00 0b 00 00 00 0b  14 51 70 14 45 70 00 00  |.........Qp.Ep..|
40

    
41

    
42
MEM_FORMAT = """
43
#seekto 0x000;
44
u8 rid;                 // Radio Identification
45

    
46
struct {
47
u8 scan_time:4,         // Scan timer per channel: 0-15 (5-80msec in 5msec steps)
48
   unknownA:4;
49
bbcd if[2];             // Radio internal IF, depending on model (16.90, 21.40, 45.10, 47.90)
50
u8 chcount;             // how many channels are programmed
51
u8 scan_resume:1,           // Scan sesume: 0 = 0.5 seconds, 1 = Carrier
52
   priority_during_scan:1,  // Priority during scan: 0 = enabled, 1 = disabled
53
   priority_speed:1,        // Priority speed: 0 = slow, 1 = fast
54
   monitor:1,               // Monitor: 0 = enabled, 1 = disabled
55
   off_hook:1,              // Off hook: 0 = enabled, 1 = disabled
56
   home_channel:1,          // Home Channel: 0 = Scan Start ch, 1 = Priority 1ch
57
   talk_back:1,             // Talk Back: 0 = enabled, 1 = disabled
58
   tx_carrier_delay:1;      // TX carrier delay: 1 = enabled, 0 = disabled
59
u8 tot:4,                   // Time out timer: 16 values (0.0-7.5 in 0.5s step)
60
   tot_resume:2,            // Time out timer resume: 3, 2, 1, 0 => 0s, 6s, 20s, 60s 
61
   unknownB:2;
62
u8 a_key:2,                 // A key function: resume: 0-3: Talkaround, High/Low, Call, Accessory
63
   unknownB:6;
64
u8 pch1;                    // Priority channel 1
65
u8 pch2;                    // Priority channel 1
66
} settings;
67

    
68
#seekto 0x010;
69
struct {
70
  u8 empty:1,           // 0 = channel enabled, 1 = channed empty
71
     notx:1,            // 0 = Tx posible,  1 = Tx disabled
72
     tot:1,             // 0 = tot disabled, 1 = tot enabled
73
     power:1,           // 0 = high, 1 = low
74
     bclo_cw:1,         // 0 = disabled, 1 = Busy Channel Lock out by carrier
75
     bclo_tone:1,       // 0 = disabled, 1 = Busy Channel Lock out by tone (set rx tone)
76
     skip:1,            // 0 = scan enabled, 1 = skip on scanning
77
     unknownA0:1;
78
  u8 chname;
79
  u8 rx_tone[2];      // empty value is \x00\x0B / disabled is \x00\x00
80
  u8 unknown4;
81
  u8 unknown5;
82
  u8 tx_tone[2];      // empty value is \x00\x0B / disabled is \x00\x00
83
  bbcd rx_freq[3];      // RX freq
84
  bbcd tx_freq[3];      // TX freq
85
  u8 unknownA[2];
86
} memory[24];
87

    
88
#seekto 0x0190;
89
char filename[11];
90

    
91
#seekto 0x19C;
92
u8 checksum;
93
"""
94

    
95
MEM_SIZE = 0x019C
96
POWER_LEVELS = [chirp_common.PowerLevel("High", watts=50),
97
                chirp_common.PowerLevel("Low", watts=5)]
98
DTCS_CODES = chirp_common.DTCS_CODES
99
SKIP_VALUES = ["", "S"]
100
LIST_BCL = ["OFF", "Carrier", "Tone"]
101
LIST_SCAN_RESUME = ["0.5 seconds", "Carrier drop"]
102
LIST_SCAN_TIME = ["%smsecs" % x for x in range(5, 85, 5)]
103
LIST_SCAN_P_SPEED = ["Slow", "Fast"]
104
LIST_HOME_CHANNEL = ["Scan Start ch", "Priority 1ch"]
105
LIST_TOT = ["Off"] + ["%.1f s" % (x/10.0) for x in range(5, 80, 5)]
106
# 3, 2, 1, 0 => 0s, 6s, 20s, 60s
107
LIST_TOT_RESUME = ["60s","20s","6s","0s"]
108
LIST_A_KEY = ["Talkaround", "High/Low", "Call", "Accessory"]
109
LIST_PCH = [] # dynamic, as depends on channel list.
110
# make a copy of the tones, is not funny to work with this directly
111
TONES = list(chirp_common.TONES)
112
# this old radios has not the full tone ranges in CST
113
invalid_tones = (
114
    69.3,
115
    159.8,
116
    165.5,
117
    171.3,
118
    177.3,
119
    183.5,
120
    189.9,
121
    196.6,
122
    199.5,
123
    206.5,
124
    229.1,
125
    245.1)
126

    
127
# remove invalid tones
128
for tone in invalid_tones:
129
    try:
130
        TONES.remove(tone)
131
    except:
132
        pass
133

    
134

    
135
def _set_serial(radio):
136
    """Set the serial protocol settings"""
137
    radio.pipe.timeout = 10
138
    radio.pipe.parity = "N"
139
    radio.pipe.baudrate = 9600
140

    
141

    
142
def _checksum(data):
143
    """the radio block checksum algorithm"""
144
    cs = 0
145
    for byte in data:
146
            cs += ord(byte)
147

    
148
    return cs % 256
149

    
150

    
151
def _update_cs(radio):
152
    """Update the checksum on the mmap"""
153
    payload = str(radio.get_mmap())[:-1]
154
    cs = _checksum(payload)
155
    radio._mmap[MEM_SIZE - 1] = cs
156

    
157

    
158
def _do_download(radio):
159
    """ The download function """
160
    # Get the whole 413 bytes (0x019D) bytes one at a time with plenty of time
161
    # to get to the user's pace
162

    
163
    # set serial discipline
164
    _set_serial(radio)
165

    
166
    # UI progress
167
    status = chirp_common.Status()
168
    status.cur = 0
169
    status.max = MEM_SIZE
170
    status.msg = " Press A to clone. "
171
    radio.status_fn(status)
172

    
173
    data = ""
174
    for i in range(0, MEM_SIZE):
175
        a = radio.pipe.read(1)
176
        if len(a) == 0:
177
            # error, no received data
178
            if len(data) != 0:
179
                # received some data, not the complete stream
180
                msg = "Just %02i bytes of the %02i received, try again." % \
181
                    (len(data), MEM_SIZE)
182
            else:
183
                # timeout, please retry
184
                msg = "No data received, try again."
185

    
186
            raise errors.RadioError(msg)
187

    
188
        data += a
189
        # UI Update
190
        status.cur = len(data)
191
        radio.status_fn(status)
192

    
193
    if len(data) != MEM_SIZE:
194
        msg = "Incomplete data, we need %02i but got %02i bytes." % \
195
            (MEM_SIZE, len(data))
196
        raise errors.RadioError(msg)
197

    
198
    if ord(data[-1]) != _checksum(data[:-1]):
199
        msg = "Bad checksum, please try again."
200
        raise errors.RadioError(msg)
201

    
202
    return data
203

    
204

    
205
def _do_upload(radio):
206
    """The upload function"""
207
    # set serial discipline
208
    _set_serial(radio)
209

    
210
    # UI progress
211
    status = chirp_common.Status()
212

    
213
    # 10 seconds timeout
214
    status.cur = 0
215
    status.max = 100
216
    status.msg = " Quick, press MON on the radio to start. "
217
    radio.status_fn(status)
218

    
219
    for byte in range(0,100):
220
        status.cur = byte
221
        radio.status_fn(status)
222
        time.sleep(0.1)
223

    
224

    
225
    # real upload if user don't cancel the timeout
226
    status.cur = 0
227
    status.max = MEM_SIZE
228
    status.msg = " Cloning to radio... "
229
    radio.status_fn(status)
230

    
231
    # send data
232
    data = str(radio.get_mmap())
233

    
234
    # this radio has a trick, the EEPROM is an ancient SPI one, so it needs
235
    # some time to write, so we send every byte and then allow
236
    # a 0.01 seg to complete the write from the MCU to the SPI EEPROM
237
    c = 0
238
    for byte in data:
239
        radio.pipe.write(byte)
240
        time.sleep(0.01)
241

    
242
        # UI Update
243
        status.cur = c
244
        radio.status_fn(status)
245

    
246
        # counter
247
        c = c + 1
248

    
249

    
250
def _model_match(cls, data):
251
    """Use a experimental guess to determine if the radio you just
252
    downloaded or the img you opened is for this model"""
253

    
254
    # It's hard to tell when this radio is really this radio.
255
    # I use the first byte, that appears to be the ID and the IF settings
256

    
257
    LOG.debug("Drivers's ID string:")
258
    LOG.debug(cls.finger)
259
    LOG.debug("Radio's ID string:")
260
    LOG.debug(util.hexprint(data[0:4]))
261

    
262
    radiod = [data[0], data[2:4]]
263
    if cls.finger == radiod:
264
        return True
265
    else:
266
        return False
267

    
268

    
269
def bcd_to_int(data):
270
    """Convert an array of bcdDataElement like \x12
271
    into an int like 12"""
272
    value = 0
273
    a = (data & 0xF0) >> 4
274
    b = data & 0x0F
275
    value = (a * 10) + b
276
    return value
277

    
278

    
279
def int_to_bcd(data):
280
    """Convert a int like 94 to 0x94"""
281
    data, lsb = divmod(data, 10)
282
    data, msb = divmod(data, 10)
283
    res = (msb << 4) + lsb
284
    return res
285

    
286

    
287
class ftlx011(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
288
    """Vertex FTL1011/2011/7011 4/8/12/24 channels"""
289
    VENDOR = "Vertex Standard"
290
    _memsize = MEM_SIZE
291
    _upper = 0
292
    _range = []
293
    finger = [] # two elements rid & IF
294

    
295
    @classmethod
296
    def get_prompts(cls):
297
        rp = chirp_common.RadioPrompts()
298
        rp.experimental = \
299
            ('This is a experimental driver, use it on your own risk.\n'
300
             '\n'
301
             'This driver is just for the 4/12/24 channels variants of '
302
             'these radios, 99 channel variants are not supported yet.\n'
303
             '\n'
304
             'The 99 channel versions appears to use another mem layout.\n'
305
             )
306
        rp.pre_download = _(dedent("""\
307
            Please follow this steps carefully:
308

    
309
            1 - Turn on your radio
310
            2 - Connect the interface cable to your radio.
311
            3 - Click the button on this window to start download
312
                (Radio will beep and led will flash)
313
            4 - Then press the "A" button in your radio to start cloning.
314
                (At the end radio will beep)
315
            """))
316
        rp.pre_upload = _(dedent("""\
317
            Please follow this steps carefully:
318

    
319
            1 - Turn on your radio
320
            2 - Connect the interface cable to your radio
321
            3 - Click the button on this window to start download
322
                (you may see another dialog, click ok)
323
            4 - Radio will beep and led will flash
324
            5 - You will get a 10 seconds timeout to press "MON" before
325
                data upload start
326
            6 - If all goes right radio will beep at end.
327

    
328
            After cloning remove the cable and power cycle your radio to
329
            get into normal mode.
330
            """))
331
        return rp
332

    
333
    def get_features(self):
334
        """Return information about this radio's features"""
335
        rf = chirp_common.RadioFeatures()
336
        rf.has_settings = True
337
        rf.has_bank = False
338
        rf.has_tuning_step = False
339
        rf.has_name = False
340
        rf.has_offset = True
341
        rf.has_mode = True
342
        rf.has_dtcs = True
343
        rf.has_rx_dtcs = True
344
        rf.has_dtcs_polarity = False
345
        rf.has_ctone = True
346
        rf.has_cross = True
347
        rf.valid_duplexes = ["", "-", "+", "off"]
348
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
349
        rf.valid_cross_modes = [
350
            "Tone->Tone",
351
            "DTCS->DTCS",
352
            "DTCS->",
353
            "->DTCS"]
354
        rf.valid_dtcs_codes = DTCS_CODES
355
        rf.valid_skips = SKIP_VALUES
356
        rf.valid_modes = ["FM"]
357
        rf.valid_power_levels = POWER_LEVELS
358
        #rf.valid_tuning_steps = [5.0]
359
        rf.valid_bands = [self._range]
360
        rf.memory_bounds = (1, self._upper)
361
        return rf
362

    
363
    def sync_in(self):
364
        """Do a download of the radio eeprom"""
365
        try:
366
            data = _do_download(self)
367
        except Exception, e:
368
            raise errors.RadioError("Failed to communicate with radio:\n %s" % e)
369

    
370
        # match model
371
        if _model_match(self, data) is False:
372
            raise errors.RadioError("Incorrect radio model")
373

    
374
        self._mmap = memmap.MemoryMap(data)
375
        self.process_mmap()
376

    
377
        # set the channel count from the radio eeprom
378
        self._upper = int(ord(data[4]))
379

    
380
    def sync_out(self):
381
        """Do an upload to the radio eeprom"""
382
        # update checksum
383
        _update_cs(self)
384

    
385
        # sanity check, match model
386
        data = str(self.get_mmap())
387
        if len(data) != MEM_SIZE:
388
            raise errors.RadioError("Wrong radio image? Size miss match.")
389

    
390
        if _model_match(self, data) is False:
391
            raise errors.RadioError("Wrong image? Fingerprint miss match")
392

    
393
        try:
394
            _do_upload(self)
395
        except Exception, e:
396
            msg = "Failed to communicate with radio:\n%s" % e
397
            raise errors.RadioError(msg)
398

    
399
    def process_mmap(self):
400
        """Process the memory object"""
401
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
402

    
403
    def get_raw_memory(self, number):
404
        """Return a raw representation of the memory object"""
405
        return repr(self._memobj.memory[number])
406

    
407
    def _decode_tone(self, mem, rx=True):
408
        """Parse the tone data to decode from mem tones are encodded like this
409
        CTCS: mapped [0x80...0xa5] = [67.0...250.3]
410
        DTCS: mixed  [0x88, 0x23] last is BCD and first is the 100 power - 88
411

    
412
        It return: ((''|DTCS|Tone), Value (None|###), None)"""
413
        mode = ""
414
        tone = None
415

    
416
        # get the tone depending of rx or tx
417
        if rx:
418
            t = mem.rx_tone
419
        else:
420
            t = mem.tx_tone
421
        
422
        tMSB = t[0]
423
        tLSB = t[1]
424

    
425
        # no tone at all
426
        if (tMSB == 0 and tLSB < 128):
427
            return ('', None, None)
428

    
429
        # extract the tone info
430
        if tMSB == 0x00:
431
            # CTCS
432
            mode = "Tone"
433
            try:
434
                tone = TONES[tLSB - 128]
435
            except IndexError:
436
                LOG.debug("Error decoding a CTCS tone")
437
                pass
438
        else:
439
            # DTCS
440
            mode = "DTCS"
441
            try:
442
                tone = ((tMSB - 0x88) * 100) + bcd_to_int(tLSB)
443
            except IndexError:
444
                LOG.debug("Error decoding a DTCS tone")
445
                pass
446

    
447
        return (mode, tone, None)
448

    
449
    def _encode_tone(self, mem, mode, value, pol, rx=True):
450
        """Parse the tone data to encode from UI to mem
451
        CTCS: mapped [0x80...0xa5] = [67.0...250.3]
452
        DTCS: mixed  [0x88, 0x23] last is BCD and first is the 100 power - 88
453
        """
454

    
455
        # array to pass
456
        tone = [0x00, 0x00]
457

    
458
        # which mod
459
        if mode == "DTCS":
460
            tone[0] = int(value / 100) + 0x88
461
            tone[1] = int_to_bcd(value % 100)
462
        
463
        if mode == "Tone":
464
            #CTCS
465
            tone[1] = TONES.index(value) + 128
466

    
467
        # set it
468
        if rx:
469
            mem.rx_tone = tone
470
        else:
471
            mem.tx_tone = tone
472

    
473
    def get_memory(self, number):
474
        """Extract a memory object from the memory map"""
475
        # Get a low-level memory object mapped to the image
476
        _mem = self._memobj.memory[number - 1]
477
        # Create a high-level memory object to return to the UI
478
        mem = chirp_common.Memory()
479
        # number
480
        mem.number = number
481

    
482
        # empty
483
        if bool(_mem.empty) is True:
484
            mem.empty = True
485
            return mem
486

    
487
        # rx freq
488
        mem.freq = int(_mem.rx_freq) * 1000
489

    
490
        # power
491
        mem.power = POWER_LEVELS[int(_mem.power)]
492

    
493
        # checking if tx freq is disabled
494
        if bool(_mem.notx) is True:
495
            mem.duplex = "off"
496
            mem.offset = 0
497
        else:
498
            tx = int(_mem.tx_freq) * 1000
499
            if tx == mem.freq:
500
                mem.offset = 0
501
                mem.duplex = ""
502
            else:
503
                mem.duplex = mem.freq > tx and "-" or "+"
504
                mem.offset = abs(tx - mem.freq)
505

    
506
        # skip
507
        mem.skip = SKIP_VALUES[_mem.skip]
508

    
509
        # tone data
510
        rxtone = txtone = None
511
        rxtone = self._decode_tone(_mem)
512
        txtone = self._decode_tone(_mem, False)
513
        chirp_common.split_tone_decode(mem, txtone, rxtone)
514

    
515
        # this radio has a primitive mode to show the channel number on a 7-segment
516
        # two digit LCD, we will use channel number
517
        # we will use a trick to show the numbers < 10 wit a space not a zero in front
518
        chname = int_to_bcd(mem.number)
519
        if mem.number < 10:
520
            # convert to F# as BCD
521
            chname = mem.number + 240
522

    
523
        _mem.chname = chname
524

    
525
        # Extra
526
        mem.extra = RadioSettingGroup("extra", "Extra")
527

    
528
        # bcl preparations: ["OFF", "Carrier", "Tone"]
529
        bcls = 0
530
        if _mem.bclo_cw:
531
            bcls = 1
532
        if _mem.bclo_tone:
533
            bcls = 2
534

    
535
        bcl = RadioSetting("bclo", "Busy channel lockout",
536
                           RadioSettingValueList(LIST_BCL,
537
                                                 LIST_BCL[bcls]))
538
        mem.extra.append(bcl)
539

    
540
        # return mem
541
        return mem
542

    
543
    def set_memory(self, mem):
544
        """Store details about a high-level memory to the memory map
545
        This is called when a user edits a memory in the UI"""
546
        # Get a low-level memory object mapped to the image
547
        _mem = self._memobj.memory[mem.number - 1]
548

    
549
        # Empty memory
550
        if mem.empty:
551
            _mem.empty = True
552
            _mem.rx_freq = _mem.tx_freq = 0
553
            return
554
        else:
555
            _mem.empty = False
556

    
557
        # freq rx
558
        _mem.rx_freq = mem.freq / 1000
559

    
560
        # power, # default power level is high
561
        _mem.power = 0 if mem.power is None else POWER_LEVELS.index(mem.power)
562

    
563
        # freq tx
564
        if mem.duplex == "+":
565
            _mem.tx_freq = (mem.freq + mem.offset) / 1000
566
            _mem.notx = 0
567
        elif mem.duplex == "-":
568
            _mem.tx_freq = (mem.freq - mem.offset) / 1000
569
            _mem.notx = 0
570
        elif mem.duplex == "off":
571
            _mem.notx = 1
572
            _mem.tx_freq = _mem.rx_freq
573
        else:
574
            _mem.tx_freq = mem.freq / 1000
575
            _mem.notx = 0
576

    
577
        # scan add property
578
        _mem.skip = SKIP_VALUES.index(mem.skip)
579

    
580
        # tone data
581
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
582
            chirp_common.split_tone_encode(mem)
583

    
584
        # validate tone data from here
585
        if rxmode == "Tone" and rxtone in invalid_tones:
586
            msg = "The tone %shz is not valid for this radio" % rxtone
587
            raise errors.UnsupportedToneError(msg)
588

    
589
        if txmode == "Tone" and txtone in invalid_tones:
590
            msg = "The tone %shz is not valid for this radio" % txtone
591
            raise errors.UnsupportedToneError(msg)
592

    
593
        if rxmode == "DTCS" and rxtone not in DTCS_CODES:
594
            msg = "The digital tone %s is not valid for this radio" % rxtone
595
            raise errors.UnsupportedToneError(msg)
596

    
597
        if txmode == "DTCS" and txtone not in DTCS_CODES:
598
            msg = "The digital tone %s is not valid for this radio" % txtone
599
            raise errors.UnsupportedToneError(msg)
600

    
601
        self._encode_tone(_mem, rxmode, rxtone, rxpol)
602
        self._encode_tone(_mem, txmode, txtone, txpol, False)
603

    
604
        # this radio has a primitive mode to show the channel number on a 7-segment
605
        # two digit LCD, we will use channel number
606
        # we will use a trick to show the numbers < 10 wit a space not a zero in front
607
        chname = int_to_bcd(mem.number)
608
        if mem.number < 10:
609
            # convert to F# as BCD
610
            chname = mem.number + 240
611

    
612
        def _zero_settings():
613
            _mem.bclo_cw = 0
614
            _mem.bclo_tone = 0
615

    
616
        # extra settings
617
        if len(mem.extra) > 0:
618
            # there are setting, parse
619
            LOG.debug("Extra-Setting supplied. Setting them.")
620
            # Zero them all first so any not provided by model don't
621
            # stay set
622
            _zero_settings()
623
            for setting in mem.extra:
624
                if setting.get_name() == "bclo":
625
                    sw = LIST_BCL.index(str(setting.value))
626
                    if sw == 0:
627
                        # empty
628
                        _zero_settings()
629
                    if sw == 1:
630
                        # carrier
631
                        _mem.bclo_cw = 1
632
                    if sw == 2:
633
                        # tone
634
                        _mem.bclo_tone = 1
635
                        # activate the tone
636
                        _mem.rx_tone = [0x00, 0x80]
637
        else:
638
            # reset extra settings 
639
            _zero_settings()
640

    
641
        _mem.chname = chname
642

    
643
        return mem
644

    
645
    def get_settings(self):
646
        _settings = self._memobj.settings
647
        basic = RadioSettingGroup("basic", "Basic Settings")
648
        group = RadioSettings(basic)
649

    
650
        # ## Basic Settings 
651
        scanr = RadioSetting("scan_resume", "Scan resume by",
652
                          RadioSettingValueList(
653
                              LIST_SCAN_RESUME, LIST_SCAN_RESUME[_settings.scan_resume]))
654
        basic.append(scanr)
655

    
656
        scant = RadioSetting("scan_time", "Scan time per channel",
657
                          RadioSettingValueList(
658
                              LIST_SCAN_TIME, LIST_SCAN_TIME[_settings.scan_time]))
659
        basic.append(scant)
660

    
661
        LIST_PCH = ["%s" % x for x in range(1, _settings.chcount + 1)]
662
        pch1 = RadioSetting("pch1", "Priority channel 1",
663
                             RadioSettingValueList(
664
                                 LIST_PCH, LIST_PCH[_settings.pch1]))
665
        basic.append(pch1)
666

    
667
        pch2 = RadioSetting("pch2", "Priority channel 2",
668
                            RadioSettingValueList(
669
                                LIST_PCH, LIST_PCH[_settings.pch2]))
670
        basic.append(pch2)
671

    
672
        scanp = RadioSetting("priority_during_scan", "Disable priority during scan",
673
                             RadioSettingValueBoolean(_settings.priority_during_scan))
674
        basic.append(scanp)
675

    
676
        scanps = RadioSetting("priority_speed", "Priority scan speed",
677
                              RadioSettingValueList(
678
                                LIST_SCAN_P_SPEED, LIST_SCAN_P_SPEED[_settings.priority_speed]))
679
        basic.append(scanps)
680

    
681
        oh = RadioSetting("off_hook", "Off Hook", #inverted
682
                        RadioSettingValueBoolean(not _settings.off_hook))
683
        basic.append(oh)
684

    
685
        tb = RadioSetting("talk_back", "Talk Back",  # inverted
686
                          RadioSettingValueBoolean(not _settings.talk_back))
687
        basic.append(tb)
688

    
689
        tot = RadioSetting("tot", "Time out timer",
690
                             RadioSettingValueList(
691
                                 LIST_TOT, LIST_TOT[_settings.tot]))
692
        basic.append(tot)
693

    
694
        totr = RadioSetting("tot_resume", "Time out timer resume guard",
695
                           RadioSettingValueList(
696
                               LIST_TOT_RESUME, LIST_TOT_RESUME[_settings.tot_resume]))
697
        basic.append(totr)
698

    
699
        ak = RadioSetting("a_key", "A Key function",
700
                            RadioSettingValueList(
701
                                LIST_A_KEY, LIST_A_KEY[_settings.a_key]))
702
        basic.append(ak)
703

    
704
        monitor = RadioSetting("monitor", "Monitor",  # inverted
705
                               RadioSettingValueBoolean(not _settings.monitor))
706
        basic.append(monitor)
707

    
708
        homec = RadioSetting("home_channel", "Home Channel is",
709
                             RadioSettingValueList(
710
                                 LIST_HOME_CHANNEL, LIST_HOME_CHANNEL[_settings.home_channel]))
711
        basic.append(homec)
712

    
713
        txd = RadioSetting("tx_carrier_delay", "Talk Back",
714
                           RadioSettingValueBoolean(_settings.tx_carrier_delay))
715
        basic.append(txd)
716

    
717

    
718
        return group
719

    
720
    def set_settings(self, uisettings):
721
        _settings = self._memobj.settings
722

    
723
        for element in uisettings:
724
            if not isinstance(element, RadioSetting):
725
                self.set_settings(element)
726
                continue
727
            if not element.changed():
728
                continue
729

    
730
            try:
731
                name = element.get_name()
732
                value = element.value
733

    
734
                print("== Setting %s: %s" % (name, value))
735

    
736
                obj = getattr(_settings, name)
737
                if name in ["off_hook", "talk_back", "monitor"]:
738
                    setattr(_settings, name, not value)
739
                else:
740
                    setattr(_settings, name, value)
741

    
742
                LOG.debug("Setting %s: %s" % (name, value))
743
            except Exception, e:
744
                LOG.debug(element.get_name())
745
                raise
746

    
747
    @classmethod
748
    def match_model(cls, filedata, filename):
749
        match_size = False
750
        match_model = False
751

    
752
        # testing the file data size
753
        if len(filedata) == cls._memsize:
754
            match_size = True
755
            print("Comp: %i file / %i memzise" % (len(filedata), cls._memsize) )
756

    
757
        # testing the firmware fingerprint, this experimental
758
        match_model = _model_match(cls, filedata)
759

    
760
        if match_size and match_model:
761
            return True
762
        else:
763
            return False
764

    
765

    
766
@directory.register
767
class ftl1011(ftlx011):
768
    """Vertex FTL-1011"""
769
    MODEL = "FTL-1011"
770
    _memsize = MEM_SIZE
771
    _upper = 4
772
    _range = [44000000, 56000000]
773
    finger = ["\x52", "\x16\x90"]
774

    
775

    
776
@directory.register
777
class ftl2011(ftlx011):
778
    """Vertex FTL-2011"""
779
    MODEL = "FTL-2011"
780
    _memsize = MEM_SIZE
781
    _upper = 24
782
    _range = [134000000, 174000000]
783
    finger = ["\x50", "\x21\x40"]
784

    
785

    
786
@directory.register
787
class ftl7011(ftlx011):
788
    """Vertex FTL-7011"""
789
    MODEL = "FTL-7011"
790
    _memsize = MEM_SIZE
791
    _upper = 24
792
    _range = [400000000, 512000000]
793
    finger = ["\x54", "\x47\x90"]
794

    
795

    
796
@directory.register
797
class ftl8011(ftlx011):
798
    """Vertex FTL-8011"""
799
    MODEL = "FTL-8011"
800
    _memsize = MEM_SIZE
801
    _upper = 24
802
    _range = [400000000, 512000000]
803
    finger = ["\x5c", "\x45\x10"]
(9-9/9)