Project

General

Profile

New Model #4933 » bf-t1.py

Latest driver with settings to test/validate - Pavel Milanes, 12/18/2017 07:40 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
#
33
# The '9100' OEM software only manipulates the lower 0x0180 bytes on read/write
34
# operations as we know, the file generated by the OEM software IS NOT an exact
35
# eeprom image, it's a crude text file with a pseudo csv format
36
#
37
# Later investigations by Harold Hankins found that the eeprom extend up to 2k
38
# consistent with a hardware chip K24C16 a 2k x 8 bit serial eeprom
39

    
40
MEM_SIZE = 0x0800 # 2048 bytes
41
WRITE_SIZE = 0x0180 # 384 bytes
42
BLOCK_SIZE = 0x10
43
ACK_CMD = "\x06"
44
MODES = ["NFM", "FM"]
45
SKIP_VALUES = ["S", ""]
46
TONES = chirp_common.TONES
47
DTCS = sorted(chirp_common.DTCS_CODES + [645])
48

    
49
# Settings vars
50
TOT_LIST = ["Off"] + ["%s" % x for x in range(30, 210, 30)]
51
SCAN_TYPE_LIST = ["Time", "Carrier", "Search"]
52
LANGUAGE_LIST = ["Off", "English", "Chinese"]
53
TIMER_LIST = ["Off"] + ["%s h" % (x * 0.5) for x in range(1, 17)]
54
FM_RANGE_LIST = ["76-108", "65-76"]
55
RELAY_MODE_LIST = ["Off", "RX sync", "TX sync"]
56
BACKLIGHT_LIST = ["Off", "Key", "On"]
57
POWER_LIST = ["0.5 Watt", "1.0 Watt"]
58

    
59
# This is a general serial timeout for all serial read functions.
60
# Practice has show that about 0.07 sec will be enough to cover all radios.
61
STIMEOUT = 0.07
62

    
63
# this var controls the verbosity in the debug and by default it's low (False)
64
# make it True and you will to get a very verbose debug.log
65
debug = True
66

    
67
##### ID strings #####################################################
68

    
69
# BF-T1 handheld
70
BFT1_magic = "\x05PROGRAM"
71
BFT1_ident = " BF9100S"
72

    
73

    
74
def _clean_buffer(radio):
75
    """Cleaning the read serial buffer, hard timeout to survive an infinite
76
    data stream"""
77

    
78
    dump = "1"
79
    datacount = 0
80

    
81
    try:
82
        while len(dump) > 0:
83
            dump = radio.pipe.read(100)
84
            datacount += len(dump)
85
            # hard limit to survive a infinite serial data stream
86
            # 5 times bigger than a normal rx block (20 bytes)
87
            if datacount > 101:
88
                seriale = "Please check your serial port selection."
89
                raise errors.RadioError(seriale)
90

    
91
    except Exception:
92
        raise errors.RadioError("Unknown error cleaning the serial buffer")
93

    
94

    
95
def _rawrecv(radio, amount = 0):
96
    """Raw read from the radio device"""
97

    
98
    # var to hold the data to return
99
    data = ""
100

    
101
    try:
102
        if amount == 0:
103
            data = radio.pipe.read()
104
        else:
105
            data = radio.pipe.read(amount)
106

    
107
        # DEBUG
108
        if debug is True:
109
            LOG.debug("<== (%d) bytes:\n\n%s" %
110
                      (len(data), util.hexprint(data)))
111

    
112
        # fail if no data is received
113
        if len(data) == 0:
114
            raise errors.RadioError("No data received from radio")
115

    
116
    except:
117
        raise errors.RadioError("Error reading data from radio")
118

    
119
    return data
120

    
121

    
122
def _send(radio, data):
123
    """Send data to the radio device"""
124

    
125
    try:
126
        radio.pipe.write(data)
127

    
128
        # DEBUG
129
        if debug is True:
130
            LOG.debug("==> (%d) bytes:\n\n%s" %
131
                      (len(data), util.hexprint(data)))
132
    except:
133
        raise errors.RadioError("Error sending data to radio")
134

    
135

    
136
def _make_frame(cmd, addr, data=""):
137
    """Pack the info in the header format"""
138
    frame = struct.pack(">BHB", ord(cmd), addr, BLOCK_SIZE)
139

    
140
    # add the data if set
141
    if len(data) != 0:
142
        frame += data
143

    
144
    return frame
145

    
146

    
147
def _recv(radio, addr):
148
    """Get data from the radio"""
149

    
150
    # Get the full 20 bytes at a time
151
    # 4 bytes header + 16 bytes of data (BLOCK_SIZE)
152

    
153
    # get the whole block
154
    block = _rawrecv(radio, BLOCK_SIZE + 4)
155

    
156
    # short answer
157
    if len(block) < (BLOCK_SIZE + 4):
158
        raise errors.RadioError("Wrong block length (short) at 0x%04x" % addr)
159

    
160
    # long answer
161
    if len(block) > (BLOCK_SIZE + 4):
162
        raise errors.RadioError("Wrong block length (long) at 0x%04x" % addr)
163

    
164

    
165
    # header validation
166
    c, a, l = struct.unpack(">cHB", block[0:4])
167
    if c != "W" or a != addr or l != BLOCK_SIZE:
168
        LOG.debug("Invalid header for block 0x%04x:" % addr)
169
        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
170
        raise errors.RadioError("Invalid header for block 0x%04x:" % addr)
171

    
172
    # return the data, 16 bytes of payload
173
    return block[4:]
174

    
175

    
176
def _start_clone_mode(radio, status):
177
    """Put the radio in clone mode, 3 tries"""
178

    
179
    # cleaning the serial buffer
180
    _clean_buffer(radio)
181

    
182
    # prep the data to show in the UI
183
    status.cur = 0
184
    status.msg = "Identifying the radio..."
185
    status.max = 3
186
    radio.status_fn(status)
187

    
188
    try:
189
        for a in range(0, status.max):
190
            # Update the UI
191
            status.cur = a + 1
192
            radio.status_fn(status)
193

    
194
            # send the magic word
195
            _send(radio, radio._magic)
196

    
197
            # Now you get a x06 of ACK if all goes well
198
            ack = _rawrecv(radio, 1)
199

    
200
            if ack == ACK_CMD:
201
                # DEBUG
202
                LOG.info("Magic ACK received")
203
                status.cur = status.max
204
                radio.status_fn(status)
205

    
206
                return True
207

    
208
        return False
209

    
210
    except errors.RadioError:
211
        raise
212
    except Exception, e:
213
        raise errors.RadioError("Error sending Magic to radio:\n%s" % e)
214

    
215

    
216
def _do_ident(radio, status):
217
    """Put the radio in PROGRAM mode & identify it"""
218
    #  set the serial discipline (default)
219
    radio.pipe.baudrate = 9600
220
    radio.pipe.parity = "N"
221
    radio.pipe.bytesize = 8
222
    radio.pipe.stopbits = 1
223
    radio.pipe.timeout = STIMEOUT
224

    
225
    # open the radio into program mode
226
    if _start_clone_mode(radio, status) is False:
227
        raise errors.RadioError("Radio did not enter clone mode, wrong model?")
228

    
229
    # Ok, poke it to get the ident string
230
    _send(radio, "\x02")
231
    ident = _rawrecv(radio, len(radio._id))
232

    
233
    # basic check for the ident
234
    if len(ident) != len(radio._id):
235
        raise errors.RadioError("Radio send a odd identification block.")
236

    
237
    # check if ident is OK
238
    if ident != radio._id:
239
        LOG.debug("Incorrect model ID, got this:\n\n" + util.hexprint(ident))
240
        raise errors.RadioError("Radio identification failed.")
241

    
242
    # handshake
243
    _send(radio, ACK_CMD)
244
    ack = _rawrecv(radio, 1)
245

    
246
    #checking handshake
247
    if len(ack) == 1 and ack == ACK_CMD:
248
        # DEBUG
249
        LOG.info("ID ACK received")
250
    else:
251
        LOG.debug("Radio handshake failed.")
252
        raise errors.RadioError("Radio handshake failed.")
253

    
254
    # DEBUG
255
    LOG.info("Positive ident, this is a %s %s" % (radio.VENDOR, radio.MODEL))
256

    
257
    return True
258

    
259

    
260
def _download(radio):
261
    """Get the memory map"""
262

    
263
    # UI progress
264
    status = chirp_common.Status()
265

    
266
    # put radio in program mode and identify it
267
    _do_ident(radio, status)
268

    
269
    # reset the progress bar in the UI
270
    status.max = MEM_SIZE / BLOCK_SIZE
271
    status.msg = "Cloning from radio..."
272
    status.cur = 0
273
    radio.status_fn(status)
274

    
275
    # cleaning the serial buffer
276
    _clean_buffer(radio)
277

    
278
    data = ""
279
    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
280
        # sending the read request
281
        _send(radio, _make_frame("R", addr))
282

    
283
        # read
284
        d = _recv(radio, addr)
285

    
286
        # aggregate the data
287
        data += d
288

    
289
        # UI Update
290
        status.cur = addr / BLOCK_SIZE
291
        status.msg = "Cloning from radio..."
292
        radio.status_fn(status)
293

    
294
    # close comms with the radio
295
    _send(radio, "\x62")
296
    # DEBUG
297
    LOG.info("Close comms cmd sent, radio must reboot now.")
298

    
299
    return data
300

    
301

    
302
def _upload(radio):
303
    """Upload procedure, we only upload to the radio the Writable space"""
304

    
305
    # UI progress
306
    status = chirp_common.Status()
307

    
308
    # put radio in program mode and identify it
309
    _do_ident(radio, status)
310

    
311
    # get the data to upload to radio
312
    data = radio.get_mmap()
313

    
314
    # Reset the UI progress
315
    status.max = WRITE_SIZE / BLOCK_SIZE
316
    status.cur = 0
317
    status.msg = "Cloning to radio..."
318
    radio.status_fn(status)
319

    
320
    # cleaning the serial buffer
321
    _clean_buffer(radio)
322

    
323
    # the fun start here, we use WRITE_SIZE instead of the full MEM_SIZE
324
    for addr in range(0, WRITE_SIZE, BLOCK_SIZE):
325
        # getting the block of data to send
326
        d = data[addr:addr + BLOCK_SIZE]
327

    
328
        # build the frame to send
329
        frame = _make_frame("W", addr, d)
330

    
331
        # send the frame
332
        _send(radio, frame)
333

    
334
        # receiving the response
335
        ack = _rawrecv(radio, 1)
336

    
337
        # basic check
338
        if len(ack) != 1:
339
            raise errors.RadioError("No ACK when writing block 0x%04x" % addr)
340

    
341
        if ack != ACK_CMD:
342
            raise errors.RadioError("Bad ACK writing block 0x%04x:" % addr)
343

    
344
         # UI Update
345
        status.cur = addr / BLOCK_SIZE
346
        status.msg = "Cloning to radio..."
347
        radio.status_fn(status)
348

    
349
    # close comms with the radio
350
    _send(radio, "\x62")
351
    # DEBUG
352
    LOG.info("Close comms cmd sent, radio must reboot now.")
353

    
354

    
355
def _model_match(cls, data):
356
    """Match the opened/downloaded image to the correct version"""
357

    
358
    # a reliable fingerprint: the model name at
359
    rid = data[0x06f8:0x0700]
360

    
361
    if rid == BFT1_ident:
362
        return True
363

    
364
    return False
365

    
366

    
367
def _decode_ranges(low, high):
368
    """Unpack the data in the ranges zones in the memmap and return
369
    a tuple with the integer corresponding to the Mhz it means"""
370
    return (int(low) * 100000, int(high) * 100000)
371

    
372

    
373
MEM_FORMAT = """
374
#seekto 0x0000;         // normal 1-20 mem channels
375
                        // channel 0 is Emergent CH
376
struct {
377
  lbcd rxfreq[4];       // rx freq.
378
  u8 rxtone;            // x00 = none
379
                        // x01 - x32 = index of the analog tones
380
                        // x33 - x9b = index of Digital tones
381
                        // Digital tone polarity is handled below
382
  lbcd txoffset[4];     // the difference against RX
383
                        // pending to find the offset polarity in settings
384
  u8 txtone;            // Idem to rxtone
385
  u8 noskip:1,      // if true is included in the scan
386
     wide:1,        // 1 = Wide, 0 = Narrow
387
     ttondinv:1,    // if true TX tone is Digital & Inverted
388
     unA:1,         //
389
     rtondinv:1,    // if true RX tone is Digital & Inverted
390
     unB:1,         //
391
     offplus:1,     // TX = RX + offset
392
     offminus:1;    // TX = RX - offset
393
  u8 empty[5];
394
} memory[21];
395

    
396
#seekto 0x0150;     // Settings; thanks to Harold Hankins for exploration
397
                    // and initial work on this.
398
struct {
399
  lbcd vhfl[2];     // VHF low limit
400
  lbcd vhfh[2];     // VHF high limit
401
  lbcd uhfl[2];     // UHF low limit
402
  lbcd uhfh[2];     // UHF high limit
403
  u8 unk0[8];
404
  u8 unk1[2];       // start of 0x0160 <=======
405
  u8 squelch;       // byte: 0-9
406
  u8 vox;           // byte: 0-9
407
  u8 timeout;       // tot, 0 off, then 30 sec increments up to 180
408
  u8 batsave:1,     // battery save 0 = off, 1 = on
409
     fm_funct:1,    // fm-radio 0=off, 1=on ( off disables fm button on set )
410
     ste:1,         // squelch tail 0 = off, 1 = on
411
     blo:1,         // busy lockout 0 = off, 1 = on
412
     beep:1,        // key beep 0 = off, 1 = on
413
     lock:1,        // keylock 0 = ff,  = on
414
     backlight:2;   // backlight 00 = off, 01 = key, 10 = on
415
  u8 scantype;      // scan type 0 = timed, 1 = carrier, 2 = stop
416
  u8 channel;       // active channel 1-20, setting it works on upload
417
  u8 fmrange;       // fm range 1 = low[65-76](ASIA), 0 = high[76-108](AMERICA)
418
  u8 alarm;         // alarm (count down timer)
419
                    //    d0 - d16 in half hour increments => off, 0.5 - 8.0 h
420
  u8 voice;         // voice prompt 0 = off, 1 = english, 2 = chinese
421
  u8 volume;        // volume 1-7 as per the radio steps
422
                    //    set to #FF by original software on upload
423
                    //    chirp uploads actual value and works.
424
  u16 fm_vfo;       // the frequency of the fm receiver.
425
                    //    resulting frequency is 65 + value * 0.1 MHz
426
                    //    0x145 is then 65 + 325*0.1 = 97.5 MHz
427
  u8 relaym;        // relay mode, d0 = off, d2 = re-tx, d1 = re-rx
428
                    //    still a mystery on how it works
429
  u8 tx_pwr;        // tx pwr 0 = low (0.5W), 1 = high(1.0W)
430
} settings;
431

    
432
#seekto 0x0170;     // Relay CH: same structure of memory ?
433
struct {
434
  lbcd rxfreq[4];       // rx freq.
435
  u8 rxtone;            // x00 = none
436
                        // x01 - x32 = index of the analog tones
437
                        // x33 - x9b = index of Digital tones
438
                        // Digital tone polarity is handled below
439
  lbcd txoffset[4];     // the difference against RX
440
                        // pending to find the offset polarity in settings
441
  u8 txtone;            // Idem to rxtone
442
  u8 noskip:1,      // if true is included in the scan
443
     wide:1,        // 1 = Wide, 0 = Narrow
444
     ttondinv:1,    // if true TX tone is Digital & Inverted
445
     unC:1,         //
446
     rtondinv:1,    // if true RX tone is Digital & Inverted
447
     unD:1,         //
448
     offplus:1,     // TX = RX + offset
449
     offminus:1;    // TX = RX - offset
450
  u8 empty[5];
451
} relaych;
452

    
453
"""
454

    
455

    
456
@directory.register
457
class BFT1(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
458
    """Baofeng BT-F1 radio & possibly alike radios"""
459
    VENDOR = "Baofeng"
460
    MODEL = "BF-T1"
461
    _vhf_range = (136000000, 174000000)
462
    _uhf_range = (400000000, 470000000)
463
    _upper = 20
464
    _magic = BFT1_magic
465
    _id = BFT1_ident
466

    
467
    @classmethod
468
    def get_prompts(cls):
469
        rp = chirp_common.RadioPrompts()
470
        rp.experimental = \
471
            ('This driver is experimental.\n'
472
             '\n'
473
             'Please keep a copy of your memories with the original software '
474
             'if you treasure them, this driver is new and may contain'
475
             ' bugs.\n'
476
             '\n'
477
             'Channel Zero is "Emergent CH", "Relay CH" is not implemented yet,'
478
             'and no settings or configuration by now.'
479
             )
480
        rp.pre_download = _(dedent("""\
481
            Follow these instructions to download your info:
482

    
483
            1 - Turn off your radio
484
            2 - Connect your interface cable
485
            3 - Turn on your radio
486
            4 - Do the download of your radio data
487

    
488
            """))
489
        rp.pre_upload = _(dedent("""\
490
            Follow these instructions to upload your info:
491

    
492
            1 - Turn off your radio
493
            2 - Connect your interface cable
494
            3 - Turn on your radio
495
            4 - Do the upload of your radio data
496

    
497
            """))
498
        return rp
499

    
500
    def get_features(self):
501
        """Get the radio's features"""
502

    
503
        rf = chirp_common.RadioFeatures()
504
        rf.has_settings = True
505
        rf.has_bank = False
506
        rf.has_tuning_step = False
507
        rf.can_odd_split = True
508
        rf.has_name = False
509
        rf.has_offset = True
510
        rf.has_mode = True
511
        rf.valid_modes = MODES
512
        rf.has_dtcs = True
513
        rf.has_rx_dtcs = True
514
        rf.has_dtcs_polarity = True
515
        rf.has_ctone = True
516
        rf.has_cross = True
517
        rf.valid_duplexes = ["", "-", "+", "split"]
518
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
519
        rf.valid_cross_modes = [
520
            "Tone->Tone",
521
            "DTCS->",
522
            "->DTCS",
523
            "Tone->DTCS",
524
            "DTCS->Tone",
525
            "->Tone",
526
            "DTCS->DTCS"]
527
        rf.valid_skips = SKIP_VALUES
528
        rf.valid_dtcs_codes = DTCS
529
        rf.memory_bounds = (0, self._upper)
530

    
531
        # normal dual bands
532
        rf.valid_bands = [self._vhf_range, self._uhf_range]
533

    
534
        return rf
535

    
536
    def process_mmap(self):
537
        """Process the mem map into the mem object"""
538

    
539
        # Get it
540
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
541

    
542
        # set the band limits as the memmap
543
        settings = self._memobj.settings
544
        self._vhf_range = _decode_ranges(settings.vhfl, settings.vhfh)
545
        self._uhf_range = _decode_ranges(settings.uhfl, settings.uhfh)
546

    
547
    def sync_in(self):
548
        """Download from radio"""
549
        data = _download(self)
550
        self._mmap = memmap.MemoryMap(data)
551
        self.process_mmap()
552

    
553
    def sync_out(self):
554
        """Upload to radio"""
555

    
556
        try:
557
            _upload(self)
558
        except errors.RadioError:
559
            raise
560
        except Exception, e:
561
            raise errors.RadioError("Error: %s" % e)
562

    
563
    def _decode_tone(self, val, inv):
564
        """Parse the tone data to decode from mem, it returns:
565
        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
566

    
567
        if val == 0:
568
            return '', None, None
569
        elif val < 51:  # analog tone
570
            return 'Tone', TONES[val - 1], None
571
        elif val > 50:  # digital tone
572
            pol = "N"
573
            # polarity?
574
            if inv == 1:
575
                pol = "R"
576

    
577
            return 'DTCS', DTCS[val - 51], pol
578

    
579
    def _encode_tone(self, memtone, meminv, mode, tone, pol):
580
        """Parse the tone data to encode from UI to mem"""
581

    
582
        if mode == '' or mode is None:
583
            memtone.set_value(0)
584
            meminv.set_value(0)
585
        elif mode == 'Tone':
586
            # caching errors for analog tones.
587
            try:
588
                memtone.set_value(TONES.index(tone) + 1)
589
                meminv.set_value(0)
590
            except:
591
                msg = "TCSS Tone '%d' is not supported" % tone
592
                LOG.error(msg)
593
                raise errors.RadioError(msg)
594

    
595
        elif mode == 'DTCS':
596
            # caching errors for digital tones.
597
            try:
598
                memtone.set_value(DTCS.index(tone) + 51)
599
                if pol == "R":
600
                    meminv.set_value(True)
601
                else:
602
                    meminv.set_value(False)
603
            except:
604
                msg = "Digital Tone '%d' is not supported" % tone
605
                LOG.error(msg)
606
                raise errors.RadioError(msg)
607
        else:
608
            msg = "Internal error: invalid mode '%s'" % mode
609
            LOG.error(msg)
610
            raise errors.InvalidDataError(msg)
611

    
612
    def get_raw_memory(self, number):
613
        return repr(self._memobj.memory[number])
614

    
615
    def get_memory(self, number):
616
        """Get the mem representation from the radio image"""
617
        _mem = self._memobj.memory[number]
618

    
619
        # Create a high-level memory object to return to the UI
620
        mem = chirp_common.Memory()
621

    
622
        # Memory number
623
        mem.number = number
624

    
625
        if _mem.get_raw()[0] == "\xFF":
626
            mem.empty = True
627
            return mem
628

    
629
        # Freq and offset
630
        mem.freq = int(_mem.rxfreq) * 10
631

    
632
        # TX freq (Stored as a difference)
633
        mem.offset = int(_mem.txoffset) * 10
634
        mem.duplex = ""
635

    
636
        # must work out the polarity
637
        if mem.offset != 0:
638
            if _mem.offminus == 1:
639
                mem.duplex = "-"
640
                #  tx below RX
641

    
642
            if _mem.offplus == 1:
643
                #  tx above RX
644
                mem.duplex = "+"
645

    
646
            # split RX/TX in different bands
647
            if mem.offset > 71000000:
648
                mem.duplex = "split"
649

    
650
                # show the actual value in the offset, depending on the shift
651
                if _mem.offminus == 1:
652
                    mem.offset = mem.freq - mem.offset
653
                if _mem.offplus == 1:
654
                    mem.offset = mem.freq + mem.offset
655

    
656
        # wide/narrow
657
        mem.mode = MODES[int(_mem.wide)]
658

    
659
        # skip
660
        mem.skip = SKIP_VALUES[_mem.noskip]
661

    
662
        # tone data
663
        rxtone = txtone = None
664
        txtone = self._decode_tone(_mem.txtone, _mem.ttondinv)
665
        rxtone = self._decode_tone(_mem.rxtone, _mem.rtondinv)
666
        chirp_common.split_tone_decode(mem, txtone, rxtone)
667

    
668

    
669
        return mem
670

    
671
    def set_memory(self, mem):
672
        """Set the memory data in the eeprom img from the UI"""
673
        # get the eprom representation of this channel
674
        _mem = self._memobj.memory[mem.number]
675

    
676
        # if empty memmory
677
        if mem.empty:
678
            # the channel itself
679
            _mem.set_raw("\xFF" * 16)
680
            # return it
681
            return mem
682

    
683
        # frequency
684
        _mem.rxfreq = mem.freq / 10
685

    
686
        # duplex/ offset Offset is an absolute value
687
        _mem.txoffset = mem.offset / 10
688

    
689
        # must work out the polarity
690
        if mem.duplex == "":
691
            _mem.offplus = 0
692
            _mem.offminus = 0
693
        elif mem.duplex == "+":
694
            _mem.offplus = 1
695
            _mem.offminus = 0
696
        elif mem.duplex == "-":
697
            _mem.offplus = 0
698
            _mem.offminus = 1
699
        elif mem.duplex == "split":
700
            if mem.freq > mem.offset:
701
                _mem.offplus = 0
702
                _mem.offminus = 1
703
                _mem.txoffset = (mem.freq - mem.offset) / 10
704
            else:
705
                _mem.offplus = 1
706
                _mem.offminus = 0
707
                _mem.txoffset = (mem.offset - mem.freq) / 10
708

    
709
        # wide/narrow
710
        _mem.wide = MODES.index(mem.mode)
711

    
712
        # skip
713
        _mem.noskip = SKIP_VALUES.index(mem.skip)
714

    
715
        # tone data
716
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
717
            chirp_common.split_tone_encode(mem)
718
        self._encode_tone(_mem.txtone, _mem.ttondinv, txmode, txtone, txpol)
719
        self._encode_tone(_mem.rxtone, _mem.rtondinv, rxmode, rxtone, rxpol)
720

    
721
        return mem
722

    
723
    def get_settings(self):
724
        _settings = self._memobj.settings
725
        basic = RadioSettingGroup("basic", "Basic Settings")
726
        fm = RadioSettingGroup("fm", "FM Radio")
727

    
728
        group = RadioSettings(basic, fm)
729

    
730
        ### Basic Settings
731
        rs = RadioSetting("tx_pwr", "TX Power",
732
                          RadioSettingValueList(
733
                            POWER_LIST, POWER_LIST[_settings.tx_pwr]))
734
        basic.append(rs)
735

    
736
        rs = RadioSetting("channel", "Active Channel",
737
                          RadioSettingValueInteger(1, 20, _settings.channel))
738
        basic.append(rs)
739

    
740
        rs = RadioSetting("relaym", "Relay Mode",
741
                          RadioSettingValueList(
742
                            RELAY_MODE_LIST, RELAY_MODE_LIST[_settings.relaym]))
743
        basic.append(rs)
744

    
745
        def apply_limit(setting, obj):
746
            setattr(obj, setting.get_name(), int(setting.value) * 10)
747

    
748
        rs = RadioSetting("vhfl", "VHF Low Limit",
749
                          RadioSettingValueInteger(136, 174,
750
                            int(_settings.vhfl) / 10))
751
        rs.set_apply_callback(apply_limit, _settings)
752
        basic.append(rs)
753

    
754
        rs = RadioSetting("vhfh", "VHF High Limit",
755
                          RadioSettingValueInteger(136, 174,
756
                            int(_settings.vhfh) / 10))
757
        rs.set_apply_callback(apply_limit, _settings)
758
        basic.append(rs)
759

    
760
        rs = RadioSetting("uhfl", "UHF Low Limit",
761
                          RadioSettingValueInteger(400, 470,
762
                            int(_settings.uhfl) / 10))
763
        rs.set_apply_callback(apply_limit, _settings)
764
        basic.append(rs)
765

    
766
        rs = RadioSetting("uhfh", "UHF High Limit",
767
                          RadioSettingValueInteger(400, 470,
768
                            int(_settings.uhfh) / 10))
769
        rs.set_apply_callback(apply_limit, _settings)
770
        basic.append(rs)
771

    
772
        rs = RadioSetting("squelch", "Squelch Level",
773
                          RadioSettingValueInteger(0, 9, _settings.squelch))
774
        basic.append(rs)
775

    
776
        rs = RadioSetting("vox", "VOX Level",
777
                          RadioSettingValueInteger(0, 9, _settings.vox))
778
        basic.append(rs)
779

    
780
        # volume validation, as the OEM software set 0xFF on write
781
        _volume = _settings.volume
782
        if _volume > 7:
783
            _volume = 7
784
        rs = RadioSetting("volume", "Volume Level",
785
                          RadioSettingValueInteger(0, 7, _volume))
786
        basic.append(rs)
787

    
788
        rs = RadioSetting("scantype", "Scan Type",
789
                          RadioSettingValueList(
790
                            SCAN_TYPE_LIST, SCAN_TYPE_LIST[_settings.scantype]))
791
        basic.append(rs)
792

    
793
        rs = RadioSetting("timeout", "Time Out Timer (seconds)",
794
                          RadioSettingValueList(
795
                            TOT_LIST, TOT_LIST[_settings.timeout]))
796
        basic.append(rs)
797

    
798
        rs = RadioSetting("voice", "Voice Prompt",
799
                          RadioSettingValueList(
800
                            LANGUAGE_LIST, LANGUAGE_LIST[_settings.voice]))
801
        basic.append(rs)
802

    
803
        rs = RadioSetting("alarm", "Alarm Time",
804
                          RadioSettingValueList(
805
                            TIMER_LIST, TIMER_LIST[_settings.alarm]))
806
        basic.append(rs)
807

    
808
        rs = RadioSetting("backlight", "Backlight",
809
                          RadioSettingValueList(
810
                            BACKLIGHT_LIST,
811
                            BACKLIGHT_LIST[_settings.backlight]))
812
        basic.append(rs)
813

    
814
        rs = RadioSetting("blo", "Busy Lockout",
815
                          RadioSettingValueBoolean(_settings.blo))
816
        basic.append(rs)
817

    
818
        rs = RadioSetting("ste", "Squelch Tail Eliminate",
819
                          RadioSettingValueBoolean(_settings.ste))
820
        basic.append(rs)
821

    
822
        rs = RadioSetting("batsave", "Battery Save",
823
                          RadioSettingValueBoolean(_settings.batsave))
824
        basic.append(rs)
825

    
826
        rs = RadioSetting("lock", "Key Lock",
827
                          RadioSettingValueBoolean(_settings.lock))
828
        basic.append(rs)
829

    
830
        rs = RadioSetting("beep", "Key Beep",
831
                          RadioSettingValueBoolean(_settings.beep))
832
        basic.append(rs)
833

    
834
        ### FM Settings
835
        rs = RadioSetting("fm_funct", "FM Function",
836
                          RadioSettingValueBoolean(_settings.fm_funct))
837
        fm.append(rs)
838

    
839
        rs = RadioSetting("fmrange", "FM Range",
840
                          RadioSettingValueList(
841
                            FM_RANGE_LIST, FM_RANGE_LIST[_settings.fmrange]))
842
        fm.append(rs)
843

    
844
        # callbacks for the FM VFO
845
        def apply_fm_freq(setting, obj):
846
            setattr(obj, setting.get_name(),
847
                (float(str(setting.value)) - 65) * 10)
848

    
849
        _fm_vfo = int(_settings.fm_vfo) * 0.1 + 65
850
        rs = RadioSetting("fm_vfo", "FM Station",
851
                          RadioSettingValueFloat(65, 108, _fm_vfo))
852
        rs.set_apply_callback(apply_fm_freq, _settings)
853
        fm.append(rs)
854

    
855
        return group
856

    
857
    def set_settings(self, uisettings):
858
        _settings = self._memobj.settings
859

    
860
        for element in uisettings:
861
            if not isinstance(element, RadioSetting):
862
                self.set_settings(element)
863
                continue
864
            if not element.changed():
865
                continue
866

    
867
            try:
868
                name = element.get_name()
869
                value = element.value
870

    
871
                if element.has_apply_callback():
872
                    LOG.debug("Using apply callback")
873
                    element.run_apply_callback()
874
                else:
875
                    obj = getattr(_settings, name)
876
                    setattr(_settings, name, value)
877

    
878
                LOG.debug("Setting %s: %s" % (name, value))
879
            except Exception, e:
880
                LOG.debug(element.get_name())
881
                raise
882

    
883
    @classmethod
884
    def match_model(cls, filedata, filename):
885
        match_size = False
886
        match_model = False
887

    
888
        # testing the file data size
889
        if len(filedata) == MEM_SIZE:
890
            match_size = True
891

    
892
            # DEBUG
893
            if debug is True:
894
                LOG.debug("BF-T1 matched!")
895

    
896
        # testing the firmware model fingerprint
897
        match_model = _model_match(cls, filedata)
898

    
899
        return match_size and match_model
(66-66/77)