Project

General

Profile

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

Henk Groningen, 12/20/2017 12:05 AM

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

    
18
import time
19
import struct
20
import logging
21

    
22
LOG = logging.getLogger(__name__)
23

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

    
33
# A note about the memmory in these radios
34
#
35
# The '9100' OEM software only manipulates the lower 0x0180 bytes on read/write
36
# operations as we know, the file generated by the OEM software IS NOT an exact
37
# eeprom image, it's a crude text file with a pseudo csv format
38
#
39
# Later investigations by Harold Hankins found that the eeprom extend up to 2k
40
# consistent with a hardware chip K24C16 a 2k x 8 bit serial eeprom
41

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

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

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

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

    
69
##### ID strings #####################################################
70

    
71
# BF-T1 handheld
72
BFT1_magic = "\x05PROGRAM"
73
BFT1_ident = " BF9100S"
74

    
75

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

    
80
    dump = "1"
81
    datacount = 0
82

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

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

    
96

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

    
100
    # var to hold the data to return
101
    data = ""
102

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

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

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

    
118
    except:
119
        raise errors.RadioError("Error reading data from radio")
120

    
121
    return data
122

    
123

    
124
def _send(radio, data):
125
    """Send data to the radio device"""
126

    
127
    try:
128
        radio.pipe.write(data)
129

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

    
137

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

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

    
146
    return frame
147

    
148

    
149
def _recv(radio, addr):
150
    """Get data from the radio"""
151

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

    
155
    # get the whole block
156
    block = _rawrecv(radio, BLOCK_SIZE + 4)
157

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

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

    
166

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

    
174
    # return the data, 16 bytes of payload
175
    return block[4:]
176

    
177

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

    
181
    # cleaning the serial buffer
182
    _clean_buffer(radio)
183

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

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

    
196
            # send the magic word
197
            _send(radio, radio._magic)
198

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

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

    
208
                return True
209

    
210
        return False
211

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

    
217

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

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

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

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

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

    
244
    # handshake
245
    _send(radio, ACK_CMD)
246
    ack = _rawrecv(radio, 1)
247

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

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

    
259
    return True
260

    
261

    
262
def _download(radio):
263
    """Get the memory map"""
264

    
265
    # UI progress
266
    status = chirp_common.Status()
267

    
268
    # put radio in program mode and identify it
269
    _do_ident(radio, status)
270

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

    
277
    # cleaning the serial buffer
278
    _clean_buffer(radio)
279

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

    
285
        # read
286
        d = _recv(radio, addr)
287

    
288
        # aggregate the data
289
        data += d
290

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

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

    
301
    return data
302

    
303

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

    
307
    # UI progress
308
    status = chirp_common.Status()
309

    
310
    # put radio in program mode and identify it
311
    _do_ident(radio, status)
312

    
313
    # get the data to upload to radio
314
    data = radio.get_mmap()
315

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

    
322
    # cleaning the serial buffer
323
    _clean_buffer(radio)
324

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

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

    
333
        # send the frame
334
        _send(radio, frame)
335

    
336
        # receiving the response
337
        ack = _rawrecv(radio, 1)
338

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

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

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

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

    
356

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

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

    
363
    if rid == BFT1_ident:
364
        return True
365

    
366
    return False
367

    
368

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

    
374

    
375
MEM_FORMAT = """
376

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

    
397
#seekto 0x0000;         // normal 1-20 mem channels
398
                        // channel 0 is Emergent CH
399
struct channel emg;
400
#seekto 0x0010;
401
struct channel channels[20];
402

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

    
439
#seekto 0x0170;     // Relay CH: same structure of memory ?
440
struct channel rly;
441

    
442
"""
443

    
444
SPECIALS = {
445
    "EMG": -2,
446
    "RLY": -1
447
    }
448

    
449
@directory.register
450
class BFT1(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
451
    """Baofeng BT-F1 radio & possibly alike radios"""
452
    VENDOR = "Baofeng"
453
    MODEL = "BF-T1"
454
    _vhf_range = (136000000, 174000000)
455
    _uhf_range = (400000000, 470000000)
456
    _upper = 20
457
    _lower = 1
458
    _magic = BFT1_magic
459
    _id = BFT1_ident
460

    
461
    @classmethod
462
    def get_prompts(cls):
463
        rp = chirp_common.RadioPrompts()
464
        rp.experimental = \
465
            ('This driver is experimental.\n'
466
             '\n'
467
             'Please keep a copy of your memories with the original software '
468
             'if you treasure them, this driver is new and may contain'
469
             ' bugs.\n'
470
             '\n'
471
             'Delete any old images you made and download a new one'
472
             'from your radio.'
473
             )
474
        rp.pre_download = _(dedent("""\
475
            Follow these instructions to download or upload your info:
476

    
477
            1 - Turn off your radio
478
            2 - Connect your interface cable
479
            3 - Turn on your radio
480
            4 - Do the download or upload of your radio data
481

    
482
            """))
483
        rp.pre_upload = _(dedent("""\
484
            Follow these instructions to uploador upload your info:
485

    
486
            1 - Turn off your radio
487
            2 - Connect your interface cable
488
            3 - Turn on your radio
489
            4 - Do the upload or upload of your radio data
490

    
491
            """))
492
        return rp
493

    
494
    def get_features(self):
495
        """Get the radio's features"""
496

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

    
526
        # normal dual bands
527
        rf.valid_bands = [self._vhf_range, self._uhf_range]
528

    
529
        return rf
530

    
531
    def process_mmap(self):
532
        """Process the mem map into the mem object"""
533

    
534
        # Get it
535
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
536

    
537
        # set the band limits as the memmap
538
        settings = self._memobj.settings
539
        self._vhf_range = _decode_ranges(settings.vhfl, settings.vhfh)
540
        self._uhf_range = _decode_ranges(settings.uhfl, settings.uhfh)
541

    
542
    def sync_in(self):
543
        """Download from radio"""
544
        data = _download(self)
545
        self._mmap = memmap.MemoryMap(data)
546
        self.process_mmap()
547

    
548
    def sync_out(self):
549
        """Upload to radio"""
550

    
551
        try:
552
            _upload(self)
553
        except errors.RadioError:
554
            raise
555
        except Exception, e:
556
            raise errors.RadioError("Error: %s" % e)
557

    
558
    def _decode_tone(self, val, inv):
559
        """Parse the tone data to decode from mem, it returns:
560
        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
561

    
562
        if val == 0:
563
            return '', None, None
564
        elif val < 51:  # analog tone
565
            return 'Tone', TONES[val - 1], None
566
        elif val > 50:  # digital tone
567
            pol = "N"
568
            # polarity?
569
            if inv == 1:
570
                pol = "R"
571

    
572
            return 'DTCS', DTCS[val - 51], pol
573

    
574
    def _encode_tone(self, memtone, meminv, mode, tone, pol):
575
        """Parse the tone data to encode from UI to mem"""
576

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

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

    
607
    def get_raw_memory(self, number):
608
        return repr(self._memobj.channels[number-1])
609

    
610
    def _get_special(self,number):
611
        if isinstance(number, str): 
612
            return (getattr(self._memobj, number.lower()))
613
        elif number < 0:
614
            for k, v in SPECIALS.items():
615
                if number == v:
616
                    return (getattr(self._memobj, k.lower()))
617
        else:                   
618
            return self._memobj.channels[number-1]
619

    
620
    def get_memory(self, number):
621
        """Get the mem representation from the radio image"""
622
        _mem = self._get_special(number)
623
        # Create a high-level memory object to return to the UI
624
        mem = chirp_common.Memory()
625
        #check if special or normal
626
        if isinstance(number, str):
627
            mem.number = SPECIALS[number]
628
            mem.extd_number = number
629
        else:
630
            mem.number = number
631

    
632
        if _mem.get_raw()[0] == "\xFF":
633
            mem.empty = True
634
            return mem
635

    
636
        # Freq and offset
637
        mem.freq = int(_mem.rxfreq) * 10
638

    
639
        # TX freq (Stored as a difference)
640
        mem.offset = int(_mem.txoffset) * 10
641
        mem.duplex = ""
642

    
643
        # must work out the polarity
644
        if mem.offset != 0:
645
            if _mem.offminus == 1:
646
                mem.duplex = "-"
647
                #  tx below RX
648

    
649
            if _mem.offplus == 1:
650
                #  tx above RX
651
                mem.duplex = "+"
652

    
653
            # split RX/TX in different bands
654
            if mem.offset > 71000000:
655
                mem.duplex = "split"
656

    
657
                # show the actual value in the offset, depending on the shift
658
                if _mem.offminus == 1:
659
                    mem.offset = mem.freq - mem.offset
660
                if _mem.offplus == 1:
661
                    mem.offset = mem.freq + mem.offset
662

    
663
        # wide/narrow
664
        mem.mode = MODES[int(_mem.wide)]
665

    
666
        # skip
667
        mem.skip = SKIP_VALUES[_mem.noskip]
668

    
669
        # tone data
670
        rxtone = txtone = None
671
        txtone = self._decode_tone(_mem.txtone, _mem.ttondinv)
672
        rxtone = self._decode_tone(_mem.rxtone, _mem.rtondinv)
673
        chirp_common.split_tone_decode(mem, txtone, rxtone)
674

    
675

    
676
        return mem
677

    
678
    def set_memory(self, mem):
679
        """Set the memory data in the eeprom img from the UI"""
680
        # get the eprom representation of this channel
681
        _mem = self._get_special(mem.number)
682

    
683
        # if empty memmory
684
        if mem.empty:
685
            # the channel itself
686
            _mem.set_raw("\xFF" * 16)
687
            # return it
688
            return mem
689

    
690
        # frequency
691
        _mem.rxfreq = mem.freq / 10
692

    
693
        # duplex/ offset Offset is an absolute value
694
        _mem.txoffset = mem.offset / 10
695

    
696
        # must work out the polarity
697
        if mem.duplex == "":
698
            _mem.offplus = 0
699
            _mem.offminus = 0
700
        elif mem.duplex == "+":
701
            _mem.offplus = 1
702
            _mem.offminus = 0
703
        elif mem.duplex == "-":
704
            _mem.offplus = 0
705
            _mem.offminus = 1
706
        elif mem.duplex == "split":
707
            if mem.freq > mem.offset:
708
                _mem.offplus = 0
709
                _mem.offminus = 1
710
                _mem.txoffset = (mem.freq - mem.offset) / 10
711
            else:
712
                _mem.offplus = 1
713
                _mem.offminus = 0
714
                _mem.txoffset = (mem.offset - mem.freq) / 10
715

    
716
        # wide/narrow
717
        _mem.wide = MODES.index(mem.mode)
718

    
719
        # skip
720
        _mem.noskip = SKIP_VALUES.index(mem.skip)
721

    
722
        # tone data
723
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
724
            chirp_common.split_tone_encode(mem)
725
        self._encode_tone(_mem.txtone, _mem.ttondinv, txmode, txtone, txpol)
726
        self._encode_tone(_mem.rxtone, _mem.rtondinv, rxmode, rxtone, rxpol)
727

    
728
        return mem
729

    
730
    def get_settings(self):
731
        _settings = self._memobj.settings
732
        basic = RadioSettingGroup("basic", "Basic Settings")
733
        fm = RadioSettingGroup("fm", "FM Radio")
734
        lm = RadioSettingGroup("lm", "Band Limits")
735
        group = RadioSettings(basic, fm, lm)
736

    
737
        ### Basic Settings
738
        rs = RadioSetting("tx_pwr", "TX Power",
739
                          RadioSettingValueList(
740
                            POWER_LIST, POWER_LIST[_settings.tx_pwr]))
741
        basic.append(rs)
742

    
743
        rs = RadioSetting("channel", "Active Channel",
744
                          RadioSettingValueInteger(1, 20, _settings.channel))
745
        basic.append(rs)
746

    
747
        rs = RadioSetting("relaym", "Relay Mode",
748
                          RadioSettingValueList(
749
                            RELAY_MODE_LIST, RELAY_MODE_LIST[_settings.relaym]))
750
        basic.append(rs)
751

    
752
        def apply_limit(setting, obj):
753
            setattr(obj, setting.get_name(), int(setting.value) * 10)
754

    
755
        rs = RadioSetting("vhfl", "VHF Low Limit",
756
                          RadioSettingValueInteger(136, 174,
757
                            int(_settings.vhfl) / 10))
758
        rs.set_apply_callback(apply_limit, _settings)
759
        lm.append(rs)
760

    
761
        rs = RadioSetting("vhfh", "VHF High Limit",
762
                          RadioSettingValueInteger(136, 174,
763
                            int(_settings.vhfh) / 10))
764
        rs.set_apply_callback(apply_limit, _settings)
765
        lm.append(rs)
766

    
767
        rs = RadioSetting("uhfl", "UHF Low Limit",
768
                          RadioSettingValueInteger(400, 470,
769
                            int(_settings.uhfl) / 10))
770
        rs.set_apply_callback(apply_limit, _settings)
771
        lm.append(rs)
772

    
773
        rs = RadioSetting("uhfh", "UHF High Limit",
774
                          RadioSettingValueInteger(400, 470,
775
                            int(_settings.uhfh) / 10))
776
        rs.set_apply_callback(apply_limit, _settings)
777
        lm.append(rs)
778

    
779
        rs = RadioSetting("squelch", "Squelch Level",
780
                          RadioSettingValueInteger(0, 9, _settings.squelch))
781
        basic.append(rs)
782

    
783
        rs = RadioSetting("vox", "VOX Level",
784
                          RadioSettingValueInteger(0, 9, _settings.vox))
785
        basic.append(rs)
786

    
787
        # volume validation, as the OEM software set 0xFF on write
788
        _volume = _settings.volume
789
        if _volume > 7:
790
            _volume = 7
791
        rs = RadioSetting("volume", "Volume Level",
792
                          RadioSettingValueInteger(0, 7, _volume))
793
        basic.append(rs)
794

    
795
        rs = RadioSetting("scantype", "Scan Type",
796
                          RadioSettingValueList(
797
                            SCAN_TYPE_LIST, SCAN_TYPE_LIST[_settings.scantype]))
798
        basic.append(rs)
799

    
800
        rs = RadioSetting("timeout", "Time Out Timer (seconds)",
801
                          RadioSettingValueList(
802
                            TOT_LIST, TOT_LIST[_settings.timeout]))
803
        basic.append(rs)
804

    
805
        rs = RadioSetting("voice", "Voice Prompt",
806
                          RadioSettingValueList(
807
                            LANGUAGE_LIST, LANGUAGE_LIST[_settings.voice]))
808
        basic.append(rs)
809

    
810
        rs = RadioSetting("alarm", "Alarm Time",
811
                          RadioSettingValueList(
812
                            TIMER_LIST, TIMER_LIST[_settings.alarm]))
813
        basic.append(rs)
814

    
815
        rs = RadioSetting("backlight", "Backlight",
816
                          RadioSettingValueList(
817
                            BACKLIGHT_LIST,
818
                            BACKLIGHT_LIST[_settings.backlight]))
819
        basic.append(rs)
820

    
821
        rs = RadioSetting("blo", "Busy Lockout",
822
                          RadioSettingValueBoolean(_settings.blo))
823
        basic.append(rs)
824

    
825
        rs = RadioSetting("ste", "Squelch Tail Eliminate",
826
                          RadioSettingValueBoolean(_settings.ste))
827
        basic.append(rs)
828

    
829
        rs = RadioSetting("batsave", "Battery Save",
830
                          RadioSettingValueBoolean(_settings.batsave))
831
        basic.append(rs)
832

    
833
        rs = RadioSetting("lock", "Key Lock",
834
                          RadioSettingValueBoolean(_settings.lock))
835
        basic.append(rs)
836

    
837
        rs = RadioSetting("beep", "Key Beep",
838
                          RadioSettingValueBoolean(_settings.beep))
839
        basic.append(rs)
840

    
841
        ### FM Settings
842
        rs = RadioSetting("fm_funct", "FM Function",
843
                          RadioSettingValueBoolean(_settings.fm_funct))
844
        fm.append(rs)
845

    
846
        rs = RadioSetting("fmrange", "FM Range",
847
                          RadioSettingValueList(
848
                            FM_RANGE_LIST, FM_RANGE_LIST[_settings.fmrange]))
849
        fm.append(rs)
850

    
851
        # callbacks for the FM VFO
852
        def apply_fm_freq(setting, obj):
853
            setattr(obj, setting.get_name(),
854
                (float(str(setting.value)) - 65) * 10)
855

    
856
        _fm_vfo = int(_settings.fm_vfo) * 0.1 + 65
857
        rs = RadioSetting("fm_vfo", "FM Station",
858
                          RadioSettingValueFloat(65, 108, _fm_vfo))
859
        rs.set_apply_callback(apply_fm_freq, _settings)
860
        fm.append(rs)
861

    
862
        return group
863

    
864
    def set_settings(self, uisettings):
865
        _settings = self._memobj.settings
866

    
867
        for element in uisettings:
868
            if not isinstance(element, RadioSetting):
869
                self.set_settings(element)
870
                continue
871
            if not element.changed():
872
                continue
873

    
874
            try:
875
                name = element.get_name()
876
                value = element.value
877

    
878
                if element.has_apply_callback():
879
                    LOG.debug("Using apply callback")
880
                    element.run_apply_callback()
881
                else:
882
                    obj = getattr(_settings, name)
883
                    setattr(_settings, name, value)
884

    
885
                LOG.debug("Setting %s: %s" % (name, value))
886
            except Exception, e:
887
                LOG.debug(element.get_name())
888
                raise
889

    
890
    @classmethod
891
    def match_model(cls, filedata, filename):
892
        match_size = False
893
        match_model = False
894

    
895
        # testing the file data size
896
        if len(filedata) == MEM_SIZE:
897
            match_size = True
898

    
899
            # DEBUG
900
            if debug is True:
901
                LOG.debug("BF-T1 matched!")
902

    
903
        # testing the firmware model fingerprint
904
        match_model = _model_match(cls, filedata)
905

    
906
        return match_size and match_model
(75-75/77)