Project

General

Profile

New Model #4933 » my-bf-t1.py

experimental settings tab added , basic settings - Henk Groningen, 12/16/2017 02:32 AM

 
1
# Copyright 2017 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 time
17
import struct
18
import logging
19

    
20
LOG = logging.getLogger(__name__)
21

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

    
31
# A note about the memmory in these radios
32
# mainly speculation until proven otherwise:
33
#
34
# The '9100' OEM software only manipulates the lower 0x180 bytes on read/write
35
# operations as we know, the file generated by the OEM software IN NOT an exact
36
# eeprom image, it's a crude text file with a pseudo csv format
37

    
38
MEM_SIZE = 0x180 # 384 bytes
39
BLOCK_SIZE = 0x10
40
ACK_CMD = "\x06"
41
MODES = ["NFM", "FM"]
42
SKIP_VALUES = ["S", ""]
43
TONES = chirp_common.TONES
44
DTCS = sorted(chirp_common.DTCS_CODES + [645])
45

    
46
# This is a general serial timeout for all serial read functions.
47
# Practice has show that about 0.07 sec will be enough to cover all radios.
48
STIMEOUT = 0.07
49

    
50
# this var controls the verbosity in the debug and by default it's low (False)
51
# make it True and you will to get a very verbose debug.log
52
debug = True
53

    
54
##### ID strings #####################################################
55

    
56
# BF-T1 handheld
57
BFT1_magic = "\x05PROGRAM"
58
BFT1_ident = " BF9100S"
59

    
60

    
61
def _clean_buffer(radio):
62
    """Cleaning the read serial buffer, hard timeout to survive an infinite
63
    data stream"""
64

    
65
    dump = "1"
66
    datacount = 0
67

    
68
    try:
69
        while len(dump) > 0:
70
            dump = radio.pipe.read(100)
71
            datacount += len(dump)
72
            # hard limit to survive a infinite serial data stream
73
            # 5 times bigger than a normal rx block (20 bytes)
74
            if datacount > 101:
75
                seriale = "Please check your serial port selection."
76
                raise errors.RadioError(seriale)
77

    
78
    except Exception:
79
        raise errors.RadioError("Unknown error cleaning the serial buffer")
80

    
81

    
82
def _rawrecv(radio, amount = 0):
83
    """Raw read from the radio device"""
84

    
85
    # var to hold the data to return
86
    data = ""
87

    
88
    try:
89
        if amount == 0:
90
            data = radio.pipe.read()
91
        else:
92
            data = radio.pipe.read(amount)
93

    
94
        # DEBUG
95
        if debug is True:
96
            LOG.debug("<== (%d) bytes:\n\n%s" %
97
                      (len(data), util.hexprint(data)))
98

    
99
        # fail if no data is received
100
        if len(data) == 0:
101
            raise errors.RadioError("No data received from radio")
102

    
103
    except:
104
        raise errors.RadioError("Error reading data from radio")
105

    
106
    return data
107

    
108

    
109
def _send(radio, data):
110
    """Send data to the radio device"""
111

    
112
    try:
113
        radio.pipe.write(data)
114

    
115
        # DEBUG
116
        if debug is True:
117
            LOG.debug("==> (%d) bytes:\n\n%s" %
118
                      (len(data), util.hexprint(data)))
119
    except:
120
        raise errors.RadioError("Error sending data to radio")
121

    
122

    
123
def _make_frame(cmd, addr, data=""):
124
    """Pack the info in the header format"""
125
    frame = struct.pack(">BHB", ord(cmd), addr, BLOCK_SIZE)
126

    
127
    # add the data if set
128
    if len(data) != 0:
129
        frame += data
130

    
131
    return frame
132

    
133

    
134
def _recv(radio, addr):
135
    """Get data from the radio"""
136

    
137
    # Get the full 20 bytes at a time
138
    # 4 bytes header + 16 bytes of data (BLOCK_SIZE)
139

    
140
    # get the whole block
141
    block = _rawrecv(radio, BLOCK_SIZE + 4)
142

    
143
    # short answer
144
    if len(block) < (BLOCK_SIZE + 4):
145
        raise errors.RadioError("Wrong block length (short) at 0x%04x" % addr)
146

    
147
    # long answer
148
    if len(block) > (BLOCK_SIZE + 4):
149
        raise errors.RadioError("Wrong block length (long) at 0x%04x" % addr)
150

    
151

    
152
    # header validation
153
    c, a, l = struct.unpack(">cHB", block[0:4])
154
    if c != "W" or a != addr or l != BLOCK_SIZE:
155
        LOG.debug("Invalid header for block 0x%04x:" % addr)
156
        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
157
        raise errors.RadioError("Invalid header for block 0x%04x:" % addr)
158

    
159
    # return the data, 16 bytes of payload
160
    return block[4:]
161

    
162

    
163
def _start_clone_mode(radio, status):
164
    """Put the radio in clone mode, 3 tries"""
165

    
166
    # cleaning the serial buffer
167
    _clean_buffer(radio)
168

    
169
    # prep the data to show in the UI
170
    status.cur = 0
171
    status.msg = "Identifying the radio..."
172
    status.max = 3
173
    radio.status_fn(status)
174

    
175
    try:
176
        for a in range(0, status.max):
177
            # Update the UI
178
            status.cur = a + 1
179
            radio.status_fn(status)
180

    
181
            # send the magic word
182
            _send(radio, radio._magic)
183

    
184
            # Now you get a x06 of ACK if all goes well
185
            ack = _rawrecv(radio, 1)
186

    
187
            if ack == ACK_CMD:
188
                # DEBUG
189
                LOG.info("Magic ACK received")
190
                status.cur = status.max
191
                radio.status_fn(status)
192

    
193
                return True
194

    
195
        return False
196

    
197
    except errors.RadioError:
198
        raise
199
    except Exception, e:
200
        raise errors.RadioError("Error sending Magic to radio:\n%s" % e)
201

    
202

    
203
def _do_ident(radio, status):
204
    """Put the radio in PROGRAM mode & identify it"""
205
    #  set the serial discipline (default)
206
    radio.pipe.baudrate = 9600
207
    radio.pipe.parity = "N"
208
    radio.pipe.bytesize = 8
209
    radio.pipe.stopbits = 1
210
    radio.pipe.timeout = STIMEOUT
211

    
212
    # open the radio into program mode
213
    if _start_clone_mode(radio, status) is False:
214
        raise errors.RadioError("Radio did not enter clone mode, wrong model?")
215

    
216
    # Ok, poke it to get the ident string
217
    _send(radio, "\x02")
218
    ident = _rawrecv(radio, len(radio._id))
219

    
220
    # basic check for the ident
221
    if len(ident) != len(radio._id):
222
        raise errors.RadioError("Radio send a odd identification block.")
223

    
224
    # check if ident is OK
225
    if ident != radio._id:
226
        LOG.debug("Incorrect model ID, got this:\n\n" + util.hexprint(ident))
227
        raise errors.RadioError("Radio identification failed.")
228

    
229
    # handshake
230
    _send(radio, ACK_CMD)
231
    ack = _rawrecv(radio, 1)
232

    
233
    #checking handshake
234
    if len(ack) == 1 and ack == ACK_CMD:
235
        # DEBUG
236
        LOG.info("ID ACK received")
237
    else:
238
        LOG.debug("Radio handshake failed.")
239
        raise errors.RadioError("Radio handshake failed.")
240

    
241
    # DEBUG
242
    LOG.info("Positive ident, this is a %s %s" % (radio.VENDOR, radio.MODEL))
243

    
244
    return True
245

    
246

    
247
def _download(radio):
248
    """Get the memory map"""
249

    
250
    # UI progress
251
    status = chirp_common.Status()
252

    
253
    # put radio in program mode and identify it
254
    _do_ident(radio, status)
255

    
256
    # reset the progress bar in the UI
257
    status.max = MEM_SIZE / BLOCK_SIZE
258
    status.msg = "Cloning from radio..."
259
    status.cur = 0
260
    radio.status_fn(status)
261

    
262
    # cleaning the serial buffer
263
    _clean_buffer(radio)
264

    
265
    data = ""
266
    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
267
        # sending the read request
268
        _send(radio, _make_frame("R", addr))
269

    
270
        # read
271
        d = _recv(radio, addr)
272

    
273
        # aggregate the data
274
        data += d
275

    
276
        # UI Update
277
        status.cur = addr / BLOCK_SIZE
278
        status.msg = "Cloning from radio..."
279
        radio.status_fn(status)
280

    
281
    # close comms with the radio
282
    _send(radio, "\x62")
283
    # DEBUG
284
    LOG.info("Close comms cmd sent, radio must reboot now.")
285

    
286
    return data
287

    
288

    
289
def _upload(radio):
290
    """Upload procedure"""
291

    
292
    # UI progress
293
    status = chirp_common.Status()
294

    
295
    # put radio in program mode and identify it
296
    _do_ident(radio, status)
297

    
298
    # get the data to upload to radio
299
    data = radio.get_mmap()
300

    
301
    # Reset the UI progress
302
    status.max = MEM_SIZE / BLOCK_SIZE
303
    status.cur = 0
304
    status.msg = "Cloning to radio..."
305
    radio.status_fn(status)
306

    
307
    # cleaning the serial buffer
308
    _clean_buffer(radio)
309

    
310
    # the fun start here
311
    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
312
        # getting the block of data to send
313
        d = data[addr:addr + BLOCK_SIZE]
314

    
315
        # build the frame to send
316
        frame = _make_frame("W", addr, d)
317

    
318
        # send the frame
319
        _send(radio, frame)
320

    
321
        # receiving the response
322
        ack = _rawrecv(radio, 1)
323

    
324
        # basic check
325
        if len(ack) != 1:
326
            raise errors.RadioError("No ACK when writing block 0x%04x" % addr)
327

    
328
        if ack != ACK_CMD:
329
            raise errors.RadioError("Bad ACK writing block 0x%04x:" % addr)
330

    
331
         # UI Update
332
        status.cur = addr / BLOCK_SIZE
333
        status.msg = "Cloning to radio..."
334
        radio.status_fn(status)
335

    
336
    # close comms with the radio
337
    _send(radio, "\x62")
338
    # DEBUG
339
    LOG.info("Close comms cmd sent, radio must reboot now.")
340

    
341

    
342
def _model_match(cls, data):
343
    """Match the opened/downloaded image to the correct version"""
344

    
345
    # we don't have a reliable fingerprint in the mem space by now.
346
    # then we just aim for a specific zone filled with \xff
347
    rid = data[0x0158:0x0160]
348

    
349
    if rid == ("\xff" * 8):
350
        return True
351

    
352
    return False
353

    
354

    
355
def _decode_ranges(low, high):
356
    """Unpack the data in the ranges zones in the memmap and return
357
    a tuple with the integer corresponding to the Mhz it means"""
358
    return (int(low) * 100000, int(high) * 100000)
359

    
360

    
361
MEM_FORMAT = """
362
#seekto 0x0000;         // normal 1-20 mem channels
363
                        // channel 0 is Emergent CH
364
struct {
365
  lbcd rxfreq[4];       // rx freq.
366
  u8 rxtone;            // x00 = none
367
                        // x01 - x32 = index of the analog tones
368
                        // x33 - x9b = index of Digital tones
369
                        // Digital tone polarity is handled below
370
  lbcd txoffset[4];     // the difference against RX
371
                        // pending to find the offset polarity in settings
372
  u8 txtone;            // Idem to rxtone
373
  u8 noskip:1,      // if true is included in the scan
374
     wide:1,        // 1 = Wide, 0 = Narrow
375
     ttondinv:1,    // if true TX tone is Digital & Inverted
376
     unA:1,         //
377
     rtondinv:1,    // if true RX tone is Digital & Inverted
378
     unB:1,         //
379
     offplus:1,     // TX = RX + offset
380
     offminus:1;    // TX = RX - offset
381
  u8 empty[5];
382
} memory[21];
383

    
384
#seekto 0x0150;     // Unknown data... settings?
385
struct {
386
  lbcd vhfl[2];     // VHF low limit
387
  lbcd vhfh[2];     // VHF high limit
388
  lbcd uhfl[2];     // UHF low limit
389
  lbcd uhfh[2];     // UHF high limit
390
  u8 finger[8];     // can we use this as fingerprint "\xFF" * 16
391
  u8 unk0;
392
  u8 unk1;
393
  u8 squelch;
394
  u8 vox;
395
  u8 timeout;
396
  u8 batsave:1,
397
	 fm_funct:1,
398
	 ste:1,
399
	 blo:1,
400
	 beep:1,
401
	 lock:1,
402
	 backlight:2;
403
  u8 scantype;
404
  u8 channel;
405
  u8 fmrange;
406
  u8 alarm;
407
  u8 voice;
408
  u8 volume;
409
  u16 fm_vfo;
410
  u8 relaym;
411
  u8 tx_pwr;
412
} settings;
413

    
414
#seekto 0x0170;     // Relay CH: same structure of memory ?
415
struct {
416
  lbcd rxfreq[4];       // rx freq.
417
  u8 rxtone;            // x00 = none
418
                        // x01 - x32 = index of the analog tones
419
                        // x33 - x9b = index of Digital tones
420
                        // Digital tone polarity is handled below
421
  lbcd txoffset[4];     // the difference against RX
422
                        // pending to find the offset polarity in settings
423
  u8 txtone;            // Idem to rxtone
424
  u8 noskip:1,      // if true is included in the scan
425
     wide:1,        // 1 = Wide, 0 = Narrow
426
     ttondinv:1,    // if true TX tone is Digital & Inverted
427
     unC:1,         //
428
     rtondinv:1,    // if true RX tone is Digital & Inverted
429
     unD:1,         //
430
     offplus:1,     // TX = RX + offset
431
     offminus:1;    // TX = RX - offset
432
  u8 empty[5];
433
} relaych;
434

    
435
"""
436

    
437

    
438
@directory.register
439
class BFT1(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
440
    """Baofeng BT-F1 radio & possibly alike radios"""
441
    VENDOR = "Baofeng"
442
    MODEL = "BF-T1"
443
    _vhf_range = (136000000, 174000000)
444
    _uhf_range = (400000000, 470000000)
445
    _upper = 20
446
    _magic = BFT1_magic
447
    _id = BFT1_ident
448

    
449
    @classmethod
450
    def get_prompts(cls):
451
        rp = chirp_common.RadioPrompts()
452
        rp.experimental = \
453
            ('This driver is experimental.\n'
454
             '\n'
455
             'Please keep a copy of your memories with the original software '
456
             'if you treasure them, this driver is new and may contain'
457
             ' bugs.\n'
458
             '\n'
459
             'Channel Zero is "Emergent CH", "Relay CH" is not implemented yet,'
460
             'and no settings or configuration by now.'
461
             )
462
        rp.pre_download = _(dedent("""\
463
            Follow these instructions to download your info:
464

    
465
            1 - Turn off your radio
466
            2 - Connect your interface cable
467
            3 - Turn on your radio
468
            4 - Do the download of your radio data
469

    
470
            """))
471
        rp.pre_upload = _(dedent("""\
472
            Follow these instructions to upload your info:
473

    
474
            1 - Turn off your radio
475
            2 - Connect your interface cable
476
            3 - Turn on your radio
477
            4 - Do the upload of your radio data
478

    
479
            """))
480
        return rp
481

    
482
    def get_features(self):
483
        """Get the radio's features"""
484

    
485
        rf = chirp_common.RadioFeatures()
486
        rf.has_settings = True
487
        rf.has_bank = False
488
        rf.has_tuning_step = False
489
        rf.can_odd_split = True
490
        rf.has_name = False
491
        rf.has_offset = True
492
        rf.has_mode = True
493
        rf.valid_modes = MODES
494
        rf.has_dtcs = True
495
        rf.has_rx_dtcs = True
496
        rf.has_dtcs_polarity = True
497
        rf.has_ctone = True
498
        rf.has_cross = True
499
        rf.valid_duplexes = ["", "-", "+", "split"]
500
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
501
        rf.valid_cross_modes = [
502
            "Tone->Tone",
503
            "DTCS->",
504
            "->DTCS",
505
            "Tone->DTCS",
506
            "DTCS->Tone",
507
            "->Tone",
508
            "DTCS->DTCS"]
509
        rf.valid_skips = SKIP_VALUES
510
        rf.valid_dtcs_codes = DTCS
511
        rf.memory_bounds = (0, self._upper)
512

    
513
        # normal dual bands
514
        rf.valid_bands = [self._vhf_range, self._uhf_range]
515

    
516
        return rf
517

    
518
    def process_mmap(self):
519
        """Process the mem map into the mem object"""
520

    
521
        # Get it
522
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
523

    
524
        # set the band limits as the memmap
525
        settings = self._memobj.settings
526
        self._vhf_range = _decode_ranges(settings.vhfl, settings.vhfh)
527
        self._uhf_range = _decode_ranges(settings.uhfl, settings.uhfh)
528

    
529
    def sync_in(self):
530
        """Download from radio"""
531
        data = _download(self)
532
        self._mmap = memmap.MemoryMap(data)
533
        self.process_mmap()
534

    
535
    def sync_out(self):
536
        """Upload to radio"""
537

    
538
        try:
539
            _upload(self)
540
        except errors.RadioError:
541
            raise
542
        except Exception, e:
543
            raise errors.RadioError("Error: %s" % e)
544

    
545
    def _decode_tone(self, val, inv):
546
        """Parse the tone data to decode from mem, it returns:
547
        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
548

    
549
        if val == 0:
550
            return '', None, None
551
        elif val < 51:  # analog tone
552
            return 'Tone', TONES[val - 1], None
553
        elif val > 50:  # digital tone
554
            pol = "N"
555
            # polarity?
556
            if inv == 1:
557
                pol = "R"
558

    
559
            return 'DTCS', DTCS[val - 51], pol
560

    
561
    def _encode_tone(self, memtone, meminv, mode, tone, pol):
562
        """Parse the tone data to encode from UI to mem"""
563

    
564
        if mode == '' or mode is None:
565
            memtone.set_value(0)
566
            meminv.set_value(0)
567
        elif mode == 'Tone':
568
            # caching errors for analog tones.
569
            try:
570
                memtone.set_value(TONES.index(tone) + 1)
571
                meminv.set_value(0)
572
            except:
573
                msg = "TCSS Tone '%d' is not supported" % tone
574
                LOG.error(msg)
575
                raise errors.RadioError(msg)
576

    
577
        elif mode == 'DTCS':
578
            # caching errors for digital tones.
579
            try:
580
                memtone.set_value(DTCS.index(tone) + 51)
581
                if pol == "R":
582
                    meminv.set_value(True)
583
                else:
584
                    meminv.set_value(False)
585
            except:
586
                msg = "Digital Tone '%d' is not supported" % tone
587
                LOG.error(msg)
588
                raise errors.RadioError(msg)
589
        else:
590
            msg = "Internal error: invalid mode '%s'" % mode
591
            LOG.error(msg)
592
            raise errors.InvalidDataError(msg)
593

    
594
    def get_raw_memory(self, number):
595
        return repr(self._memobj.memory[number])
596

    
597
    def get_memory(self, number):
598
        """Get the mem representation from the radio image"""
599
        _mem = self._memobj.memory[number]
600

    
601
        # Create a high-level memory object to return to the UI
602
        mem = chirp_common.Memory()
603

    
604
        # Memory number
605
        mem.number = number
606

    
607
        if _mem.get_raw()[0] == "\xFF":
608
            mem.empty = True
609
            return mem
610

    
611
        # Freq and offset
612
        mem.freq = int(_mem.rxfreq) * 10
613

    
614
        # TX freq (Stored as a difference)
615
        mem.offset = int(_mem.txoffset) * 10
616
        mem.duplex = ""
617

    
618
        # must work out the polarity
619
        if mem.offset != 0:
620
            if _mem.offminus == 1:
621
                mem.duplex = "-"
622
                #  tx below RX
623

    
624
            if _mem.offplus == 1:
625
                #  tx above RX
626
                mem.duplex = "+"
627

    
628
            # split RX/TX in different bands
629
            if mem.offset > 71000000:
630
                mem.duplex = "split"
631

    
632
                # show the actual value in the offset, depending on the shift
633
                if _mem.offminus == 1:
634
                    mem.offset = mem.freq - mem.offset
635
                if _mem.offplus == 1:
636
                    mem.offset = mem.freq + mem.offset
637

    
638
        # wide/narrow
639
        mem.mode = MODES[int(_mem.wide)]
640

    
641
        # skip
642
        mem.skip = SKIP_VALUES[_mem.noskip]
643

    
644
        # tone data
645
        rxtone = txtone = None
646
        txtone = self._decode_tone(_mem.txtone, _mem.ttondinv)
647
        rxtone = self._decode_tone(_mem.rxtone, _mem.rtondinv)
648
        chirp_common.split_tone_decode(mem, txtone, rxtone)
649

    
650

    
651
        return mem
652

    
653
    def set_memory(self, mem):
654
        """Set the memory data in the eeprom img from the UI"""
655
        # get the eprom representation of this channel
656
        _mem = self._memobj.memory[mem.number]
657

    
658
        # if empty memmory
659
        if mem.empty:
660
            # the channel itself
661
            _mem.set_raw("\xFF" * 16)
662
            # return it
663
            return mem
664

    
665
        # frequency
666
        _mem.rxfreq = mem.freq / 10
667

    
668
        # duplex/ offset Offset is an absolute value
669
        _mem.txoffset = mem.offset / 10
670

    
671
        # must work out the polarity
672
        if mem.duplex == "":
673
            _mem.offplus = 0
674
            _mem.offminus = 0
675
        elif mem.duplex == "+":
676
            _mem.offplus = 1
677
            _mem.offminus = 0
678
        elif mem.duplex == "-":
679
            _mem.offplus = 0
680
            _mem.offminus = 1
681
        elif mem.duplex == "split":
682
            if mem.freq > mem.offset:
683
                _mem.offplus = 0
684
                _mem.offminus = 1
685
                _mem.txoffset = (mem.freq - mem.offset) / 10
686
            else:
687
                _mem.offplus = 1
688
                _mem.offminus = 0
689
                _mem.txoffset = (mem.offset - mem.freq) / 10
690

    
691
        # wide/narrow
692
        _mem.wide = MODES.index(mem.mode)
693

    
694
        # skip
695
        _mem.noskip = SKIP_VALUES.index(mem.skip)
696

    
697
        # tone data
698
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
699
            chirp_common.split_tone_encode(mem)
700
        self._encode_tone(_mem.txtone, _mem.ttondinv, txmode, txtone, txpol)
701
        self._encode_tone(_mem.rxtone, _mem.rtondinv, rxmode, rxtone, rxpol)
702

    
703
        return mem
704
# added this
705
    def get_settings(self):
706
        _settings = self._memobj.settings
707
        basic = RadioSettingGroup("basic", "Basic Settings")
708

    
709
        group = RadioSettings(basic)
710

    
711
        options = ["Time", "Carrier", "Search"]
712
        rs = RadioSetting("scantype", "Scan Type",
713
                          RadioSettingValueList(options,
714
                                                options[_settings.scantype]))
715
        basic.append(rs)
716
		
717
        options = ["Off"] + ["%s sec" % (x*30) for x in range(1, 7)]
718
        rs = RadioSetting("timeout", "Time Out Timer",
719
                          RadioSettingValueList(
720
                              options, options[_settings.timeout]))
721
        basic.append(rs)
722

    
723
        rs = RadioSetting("squelch", "Squelch Level",
724
                          RadioSettingValueInteger(0, 9, _settings.squelch))
725
        basic.append(rs)
726

    
727
        rs = RadioSetting("vox", "VOX Level",
728
                          RadioSettingValueInteger(0, 9, _settings.vox))
729
        basic.append(rs)
730

    
731
        rs = RadioSetting("volume", "Volume Level",
732
                          RadioSettingValueInteger(0, 9, _settings.volume))
733
        basic.append(rs)
734
	
735
	rs = RadioSetting("channel", "Current Channel",
736
                          RadioSettingValueInteger(1, 20, _settings.channel))
737
        basic.append(rs)
738

    
739
        options = ["Off","English", "Chinese"]
740
        rs = RadioSetting("voice", "Voice Prompt",
741
                          RadioSettingValueList(options,
742
                                                options[_settings.voice]))
743
        basic.append(rs)
744

    
745
        options = ["Off"] + ["%s h" % (x*0.5) for x in range(1, 17)]
746
        rs = RadioSetting("alarm", "Alarm Time",
747
                          RadioSettingValueList(
748
                              options, options[_settings.alarm]))
749
        basic.append(rs)
750

    
751
        options = ["76-108", "65-76"]
752
        rs = RadioSetting("fmrange", "FM Range",
753
                          RadioSettingValueList(options,
754
                                                options[_settings.fmrange]))
755
        basic.append(rs)
756

    
757
        options = ["Off", "RX sync", "TX sync"]
758
        rs = RadioSetting("relaym", "Relay Mode",
759
                          RadioSettingValueList(options,
760
                                                options[_settings.relaym]))
761
        basic.append(rs)
762

    
763
        options = ["Off", "Key", "On"]
764
        rs = RadioSetting("backlight", "Backlight",
765
                          RadioSettingValueList(options,
766
                                                options[_settings.backlight]))
767
        basic.append(rs)
768

    
769
        options = ["0.5 Watt", "1.0 Watt"]
770
        rs = RadioSetting("tx_pwr", "TX Power",
771
                          RadioSettingValueList(options,
772
                                                options[_settings.tx_pwr]))
773
        basic.append(rs)
774

    
775
        rs = RadioSetting("lock", "Key Lock",
776
                          RadioSettingValueBoolean(_settings.lock))
777
        basic.append(rs)
778

    
779
        rs = RadioSetting("beep", "Key Beep",
780
                          RadioSettingValueBoolean(_settings.beep))
781
        basic.append(rs)
782

    
783
        rs = RadioSetting("blo", "Busy Lockout",
784
                          RadioSettingValueBoolean(_settings.blo))
785
        basic.append(rs)
786

    
787
        rs = RadioSetting("ste", "Squelch Tail Eliminate",
788
                          RadioSettingValueBoolean(_settings.ste))
789
        basic.append(rs)
790

    
791
        rs = RadioSetting("batsave", "Battery Save",
792
                          RadioSettingValueBoolean(_settings.batsave))
793
        basic.append(rs)
794

    
795
        rs = RadioSetting("fm_funct", "FM Function",
796
                          RadioSettingValueBoolean(_settings.fm_funct))
797
        basic.append(rs)
798

    
799
        return group
800

    
801
    def set_settings(self, settings):
802
        _settings = self._memobj.settings
803
        for element in settings:
804
            if not isinstance(element, RadioSetting):
805
                self.set_settings(element)
806
                continue
807
            else:
808
                try:
809
                    name = element.get_name()
810
                    if "." in name:
811
                        bits = name.split(".")
812
                        obj = self._memobj
813
                        for bit in bits[:-1]:
814
                            if "/" in bit:
815
                                bit, index = bit.split("/", 1)
816
                                index = int(index)
817
                                obj = getattr(obj, bit)[index]
818
                            else:
819
                                obj = getattr(obj, bit)
820
                        setting = bits[-1]
821
                    else:
822
                        obj = _settings
823
                        setting = element.get_name()
824

    
825
                    if element.has_apply_callback():
826
                        LOG.debug("Using apply callback")
827
                        element.run_apply_callback()
828
                    elif setting == "beep_tone_disabled":
829
                        setattr(obj, setting, not int(element.value))
830
                    elif setting == "ste_disabled":
831
                        setattr(obj, setting, not int(element.value))
832
                    else:
833
                        LOG.debug("Setting %s = %s" % (setting, element.value))
834
                        setattr(obj, setting, element.value)
835
                except Exception, e:
836
                    LOG.debug(element.get_name())
837
                    raise
838

    
839
# experimental end
840
					
841
    @classmethod
842
    def match_model(cls, filedata, filename):
843
        match_size = False
844
        match_model = False
845

    
846
        # testing the file data size
847
        if len(filedata) == MEM_SIZE:
848
            match_size = True
849

    
850
            # DEBUG
851
            if debug is True:
852
                LOG.debug("BF-T1 matched!")
853

    
854

    
855
        # testing the firmware model fingerprint
856
        match_model = _model_match(cls, filedata)
857

    
858
        if match_size and match_model:
859
            return True
860
        else:
861
            return False
(63-63/77)