Project

General

Profile

New Model #4933 » bf-t1.py

Finas driver, this is the one sent to the devel queue for inclusion in next Chirp. - Pavel Milanes, 12/21/2017 02:47 PM

 
1
# Copyright 2017 Pavel Milanes, CO7WT, <pavelmc@gmail.com>
2
#
3
# This driver is a community effort as I don have the radio on my hands, so
4
# I was only the director of the orchestra, without the players this may never
5
# came true, so special thanks to the following hams for their contribution:
6
# - Henk van der Laan, PA3CQN
7
#       - Setting Discovery.
8
#       - Special channels for RELAY and EMERGENCY.
9
# - Harold Hankins
10
#       - Memory limits, testing & bug hunting.
11
# - Dmitry Milkov
12
#       - Testing & bug hunting.
13
# - Many others participants in the issue page on Chirp's site.
14
#
15
# This program is free software: you can redistribute it and/or modify
16
# it under the terms of the GNU General Public License as published by
17
# the Free Software Foundation, either version 2 of the License, or
18
# (at your option) any later version.
19
#
20
# This program is distributed in the hope that it will be useful,
21
# but WITHOUT ANY WARRANTY; without even the implied warranty of
22
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
# GNU General Public License for more details.
24
#
25
# You should have received a copy of the GNU General Public License
26
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
27

    
28
import time
29
import struct
30
import logging
31

    
32
LOG = logging.getLogger(__name__)
33

    
34
from time import sleep
35
from chirp import chirp_common, directory, memmap
36
from chirp import bitwise, errors, util
37
from chirp.settings import RadioSetting, RadioSettingGroup, \
38
                RadioSettingValueBoolean, RadioSettingValueList, \
39
                RadioSettingValueInteger, RadioSettingValueString, \
40
                RadioSettingValueFloat, RadioSettings
41
from textwrap import dedent
42

    
43
# A note about the memmory in these radios
44
#
45
# The '9100' OEM software only manipulates the lower 0x0180 bytes on read/write
46
# operations as we know, the file generated by the OEM software IS NOT an exact
47
# eeprom image, it's a crude text file with a pseudo csv format
48
#
49
# Later investigations by Harold Hankins found that the eeprom extend up to 2k
50
# consistent with a hardware chip K24C16 a 2k x 8 bit serial eeprom
51

    
52
MEM_SIZE = 0x0800 # 2048 bytes
53
WRITE_SIZE = 0x0180 # 384 bytes
54
BLOCK_SIZE = 0x10
55
ACK_CMD = "\x06"
56
MODES = ["NFM", "FM"]
57
SKIP_VALUES = ["S", ""]
58
TONES = chirp_common.TONES
59
DTCS = sorted(chirp_common.DTCS_CODES + [645])
60

    
61
# Special channels
62
SPECIALS = {
63
    "EMG": -2,
64
    "RLY": -1
65
    }
66

    
67
# Settings vars
68
TOT_LIST = ["Off"] + ["%s" % x for x in range(30, 210, 30)]
69
SCAN_TYPE_LIST = ["Time", "Carrier", "Search"]
70
LANGUAGE_LIST = ["Off", "English", "Chinese"]
71
TIMER_LIST = ["Off"] + ["%s h" % (x * 0.5) for x in range(1, 17)]
72
FM_RANGE_LIST = ["76-108", "65-76"]
73
RELAY_MODE_LIST = ["Off", "RX sync", "TX sync"]
74
BACKLIGHT_LIST = ["Off", "Key", "On"]
75
POWER_LIST = ["0.5 Watt", "1.0 Watt"]
76

    
77
# This is a general serial timeout for all serial read functions.
78
# Practice has show that about 0.07 sec will be enough to cover all radios.
79
STIMEOUT = 0.07
80

    
81
# this var controls the verbosity in the debug and by default it's low (False)
82
# make it True and you will to get a very verbose debug.log
83
debug = False
84

    
85
##### ID strings #####################################################
86

    
87
# BF-T1 handheld
88
BFT1_magic = "\x05PROGRAM"
89
BFT1_ident = " BF9100S"
90

    
91

    
92
def _clean_buffer(radio):
93
    """Cleaning the read serial buffer, hard timeout to survive an infinite
94
    data stream"""
95

    
96
    dump = "1"
97
    datacount = 0
98

    
99
    try:
100
        while len(dump) > 0:
101
            dump = radio.pipe.read(100)
102
            datacount += len(dump)
103
            # hard limit to survive a infinite serial data stream
104
            # 5 times bigger than a normal rx block (20 bytes)
105
            if datacount > 101:
106
                seriale = "Please check your serial port selection."
107
                raise errors.RadioError(seriale)
108

    
109
    except Exception:
110
        raise errors.RadioError("Unknown error cleaning the serial buffer")
111

    
112

    
113
def _rawrecv(radio, amount = 0):
114
    """Raw read from the radio device"""
115

    
116
    # var to hold the data to return
117
    data = ""
118

    
119
    try:
120
        if amount == 0:
121
            data = radio.pipe.read()
122
        else:
123
            data = radio.pipe.read(amount)
124

    
125
        # DEBUG
126
        if debug is True:
127
            LOG.debug("<== (%d) bytes:\n\n%s" %
128
                      (len(data), util.hexprint(data)))
129

    
130
        # fail if no data is received
131
        if len(data) == 0:
132
            raise errors.RadioError("No data received from radio")
133

    
134
    except:
135
        raise errors.RadioError("Error reading data from radio")
136

    
137
    return data
138

    
139

    
140
def _send(radio, data):
141
    """Send data to the radio device"""
142

    
143
    try:
144
        radio.pipe.write(data)
145

    
146
        # DEBUG
147
        if debug is True:
148
            LOG.debug("==> (%d) bytes:\n\n%s" %
149
                      (len(data), util.hexprint(data)))
150
    except:
151
        raise errors.RadioError("Error sending data to radio")
152

    
153

    
154
def _make_frame(cmd, addr, data=""):
155
    """Pack the info in the header format"""
156
    frame = struct.pack(">BHB", ord(cmd), addr, BLOCK_SIZE)
157

    
158
    # add the data if set
159
    if len(data) != 0:
160
        frame += data
161

    
162
    return frame
163

    
164

    
165
def _recv(radio, addr):
166
    """Get data from the radio"""
167

    
168
    # Get the full 20 bytes at a time
169
    # 4 bytes header + 16 bytes of data (BLOCK_SIZE)
170

    
171
    # get the whole block
172
    block = _rawrecv(radio, BLOCK_SIZE + 4)
173

    
174
    # short answer
175
    if len(block) < (BLOCK_SIZE + 4):
176
        raise errors.RadioError("Wrong block length (short) at 0x%04x" % addr)
177

    
178
    # long answer
179
    if len(block) > (BLOCK_SIZE + 4):
180
        raise errors.RadioError("Wrong block length (long) at 0x%04x" % addr)
181

    
182

    
183
    # header validation
184
    c, a, l = struct.unpack(">cHB", block[0:4])
185
    if c != "W" or a != addr or l != BLOCK_SIZE:
186
        LOG.debug("Invalid header for block 0x%04x:" % addr)
187
        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
188
        raise errors.RadioError("Invalid header for block 0x%04x:" % addr)
189

    
190
    # return the data, 16 bytes of payload
191
    return block[4:]
192

    
193

    
194
def _start_clone_mode(radio, status):
195
    """Put the radio in clone mode, 3 tries"""
196

    
197
    # cleaning the serial buffer
198
    _clean_buffer(radio)
199

    
200
    # prep the data to show in the UI
201
    status.cur = 0
202
    status.msg = "Identifying the radio..."
203
    status.max = 3
204
    radio.status_fn(status)
205

    
206
    try:
207
        for a in range(0, status.max):
208
            # Update the UI
209
            status.cur = a + 1
210
            radio.status_fn(status)
211

    
212
            # send the magic word
213
            _send(radio, radio._magic)
214

    
215
            # Now you get a x06 of ACK if all goes well
216
            ack = _rawrecv(radio, 1)
217

    
218
            if ack == ACK_CMD:
219
                # DEBUG
220
                LOG.info("Magic ACK received")
221
                status.cur = status.max
222
                radio.status_fn(status)
223

    
224
                return True
225

    
226
        return False
227

    
228
    except errors.RadioError:
229
        raise
230
    except Exception, e:
231
        raise errors.RadioError("Error sending Magic to radio:\n%s" % e)
232

    
233

    
234
def _do_ident(radio, status):
235
    """Put the radio in PROGRAM mode & identify it"""
236
    #  set the serial discipline (default)
237
    radio.pipe.baudrate = 9600
238
    radio.pipe.parity = "N"
239
    radio.pipe.bytesize = 8
240
    radio.pipe.stopbits = 1
241
    radio.pipe.timeout = STIMEOUT
242

    
243
    # open the radio into program mode
244
    if _start_clone_mode(radio, status) is False:
245
        raise errors.RadioError("Radio did not enter clone mode, wrong model?")
246

    
247
    # Ok, poke it to get the ident string
248
    _send(radio, "\x02")
249
    ident = _rawrecv(radio, len(radio._id))
250

    
251
    # basic check for the ident
252
    if len(ident) != len(radio._id):
253
        raise errors.RadioError("Radio send a odd identification block.")
254

    
255
    # check if ident is OK
256
    if ident != radio._id:
257
        LOG.debug("Incorrect model ID, got this:\n\n" + util.hexprint(ident))
258
        raise errors.RadioError("Radio identification failed.")
259

    
260
    # handshake
261
    _send(radio, ACK_CMD)
262
    ack = _rawrecv(radio, 1)
263

    
264
    #checking handshake
265
    if len(ack) == 1 and ack == ACK_CMD:
266
        # DEBUG
267
        LOG.info("ID ACK received")
268
    else:
269
        LOG.debug("Radio handshake failed.")
270
        raise errors.RadioError("Radio handshake failed.")
271

    
272
    # DEBUG
273
    LOG.info("Positive ident, this is a %s %s" % (radio.VENDOR, radio.MODEL))
274

    
275
    return True
276

    
277

    
278
def _download(radio):
279
    """Get the memory map"""
280

    
281
    # UI progress
282
    status = chirp_common.Status()
283

    
284
    # put radio in program mode and identify it
285
    _do_ident(radio, status)
286

    
287
    # reset the progress bar in the UI
288
    status.max = MEM_SIZE / BLOCK_SIZE
289
    status.msg = "Cloning from radio..."
290
    status.cur = 0
291
    radio.status_fn(status)
292

    
293
    # cleaning the serial buffer
294
    _clean_buffer(radio)
295

    
296
    data = ""
297
    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
298
        # sending the read request
299
        _send(radio, _make_frame("R", addr))
300

    
301
        # read
302
        d = _recv(radio, addr)
303

    
304
        # aggregate the data
305
        data += d
306

    
307
        # UI Update
308
        status.cur = addr / BLOCK_SIZE
309
        status.msg = "Cloning from radio..."
310
        radio.status_fn(status)
311

    
312
    # close comms with the radio
313
    _send(radio, "\x62")
314
    # DEBUG
315
    LOG.info("Close comms cmd sent, radio must reboot now.")
316

    
317
    return data
318

    
319

    
320
def _upload(radio):
321
    """Upload procedure, we only upload to the radio the Writable space"""
322

    
323
    # UI progress
324
    status = chirp_common.Status()
325

    
326
    # put radio in program mode and identify it
327
    _do_ident(radio, status)
328

    
329
    # get the data to upload to radio
330
    data = radio.get_mmap()
331

    
332
    # Reset the UI progress
333
    status.max = WRITE_SIZE / BLOCK_SIZE
334
    status.cur = 0
335
    status.msg = "Cloning to radio..."
336
    radio.status_fn(status)
337

    
338
    # cleaning the serial buffer
339
    _clean_buffer(radio)
340

    
341
    # the fun start here, we use WRITE_SIZE instead of the full MEM_SIZE
342
    for addr in range(0, WRITE_SIZE, BLOCK_SIZE):
343
        # getting the block of data to send
344
        d = data[addr:addr + BLOCK_SIZE]
345

    
346
        # build the frame to send
347
        frame = _make_frame("W", addr, d)
348

    
349
        # send the frame
350
        _send(radio, frame)
351

    
352
        # receiving the response
353
        ack = _rawrecv(radio, 1)
354

    
355
        # basic check
356
        if len(ack) != 1:
357
            raise errors.RadioError("No ACK when writing block 0x%04x" % addr)
358

    
359
        if ack != ACK_CMD:
360
            raise errors.RadioError("Bad ACK writing block 0x%04x:" % addr)
361

    
362
         # UI Update
363
        status.cur = addr / BLOCK_SIZE
364
        status.msg = "Cloning to radio..."
365
        radio.status_fn(status)
366

    
367
    # close comms with the radio
368
    _send(radio, "\x62")
369
    # DEBUG
370
    LOG.info("Close comms cmd sent, radio must reboot now.")
371

    
372

    
373
def _model_match(cls, data):
374
    """Match the opened/downloaded image to the correct version"""
375

    
376
    # a reliable fingerprint: the model name at
377
    rid = data[0x06f8:0x0700]
378

    
379
    if rid == BFT1_ident:
380
        return True
381

    
382
    return False
383

    
384

    
385
def _decode_ranges(low, high):
386
    """Unpack the data in the ranges zones in the memmap and return
387
    a tuple with the integer corresponding to the Mhz it means"""
388
    return (int(low) * 100000, int(high) * 100000)
389

    
390

    
391
MEM_FORMAT = """
392

    
393
struct channel {
394
  lbcd rxfreq[4];       // rx freq.
395
  u8 rxtone;            // x00 = none
396
                        // x01 - x32 = index of the analog tones
397
                        // x33 - x9b = index of Digital tones
398
                        // Digital tone polarity is handled below by
399
                        // ttondinv & ttondinv settings
400
  lbcd txoffset[4];     // the difference against RX, direction handled by
401
                        // offplus & offminus
402
  u8 txtone;            // Idem to rxtone
403
  u8 noskip:1,      // if true is included in the scan
404
     wide:1,        // 1 = Wide, 0 = Narrow
405
     ttondinv:1,    // if true TX tone is Digital & Inverted
406
     unA:1,         //
407
     rtondinv:1,    // if true RX tone is Digital & Inverted
408
     unB:1,         //
409
     offplus:1,     // TX = RX + offset
410
     offminus:1;    // TX = RX - offset
411
  u8 empty[5];
412
};
413

    
414
#seekto 0x0000;
415
struct channel emg;             // channel 0 is Emergent CH
416
#seekto 0x0010;
417
struct channel channels[20];    // normal 1-20 mem channels
418

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

    
454
#seekto 0x0170;     // Relay CH
455
struct channel rly;
456

    
457
"""
458

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

    
470
    @classmethod
471
    def get_prompts(cls):
472
        rp = chirp_common.RadioPrompts()
473
        rp.experimental = \
474
            ('This driver is experimental.\n'
475
             '\n'
476
             'Please keep a copy of your memories with the original software '
477
             'if you treasure them, this driver is new and may contain'
478
             ' bugs.\n'
479
             '\n'
480
             '"Emergent CH" & "Relay CH" are implemented via special channels,'
481
             'be sure to click on the button on the interface to access them.'
482
             )
483
        rp.pre_download = _(dedent("""\
484
            Follow these instructions to download 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 download of your radio data
490

    
491
            """))
492
        rp.pre_upload = _(dedent("""\
493
            Follow these instructions to upload your info:
494

    
495
            1 - Turn off your radio
496
            2 - Connect your interface cable
497
            3 - Turn on your radio
498
            4 - Do the upload of your radio data
499

    
500
            """))
501
        return rp
502

    
503
    def get_features(self):
504
        """Get the radio's features"""
505

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

    
535
        # normal dual bands
536
        rf.valid_bands = [self._vhf_range, self._uhf_range]
537

    
538
        return rf
539

    
540
    def process_mmap(self):
541
        """Process the mem map into the mem object"""
542

    
543
        # Get it
544
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
545

    
546
        # set the band limits as the memmap
547
        settings = self._memobj.settings
548
        self._vhf_range = _decode_ranges(settings.vhfl, settings.vhfh)
549
        self._uhf_range = _decode_ranges(settings.uhfl, settings.uhfh)
550

    
551
    def sync_in(self):
552
        """Download from radio"""
553
        data = _download(self)
554
        self._mmap = memmap.MemoryMap(data)
555
        self.process_mmap()
556

    
557
    def sync_out(self):
558
        """Upload to radio"""
559

    
560
        try:
561
            _upload(self)
562
        except errors.RadioError:
563
            raise
564
        except Exception, e:
565
            raise errors.RadioError("Error: %s" % e)
566

    
567
    def _decode_tone(self, val, inv):
568
        """Parse the tone data to decode from mem, it returns:
569
        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
570

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

    
581
            return 'DTCS', DTCS[val - 51], pol
582

    
583
    def _encode_tone(self, memtone, meminv, mode, tone, pol):
584
        """Parse the tone data to encode from UI to mem"""
585

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

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

    
616
    def get_raw_memory(self, number):
617
        return repr(self._memobj.memory[number])
618

    
619
    def _get_special(self,number):
620
        if isinstance(number, str):
621
            return (getattr(self._memobj, number.lower()))
622
        elif number < 0:
623
            for k, v in SPECIALS.items():
624
                if number == v:
625
                    return (getattr(self._memobj, k.lower()))
626
        else:
627
            return self._memobj.channels[number-1]
628

    
629
    def get_memory(self, number):
630
        """Get the mem representation from the radio image"""
631
        _mem = self._get_special(number)
632

    
633
        # Create a high-level memory object to return to the UI
634
        mem = chirp_common.Memory()
635

    
636
        # Check if special or normal
637
        if isinstance(number, str):
638
            mem.number = SPECIALS[number]
639
            mem.extd_number = number
640
        else:
641
            mem.number = number
642

    
643
        if _mem.get_raw()[0] == "\xFF":
644
            mem.empty = True
645
            return mem
646

    
647
        # Freq and offset
648
        mem.freq = int(_mem.rxfreq) * 10
649

    
650
        # TX freq (Stored as a difference)
651
        mem.offset = int(_mem.txoffset) * 10
652
        mem.duplex = ""
653

    
654
        # must work out the polarity
655
        if mem.offset != 0:
656
            if _mem.offminus == 1:
657
                mem.duplex = "-"
658
                #  tx below RX
659

    
660
            if _mem.offplus == 1:
661
                #  tx above RX
662
                mem.duplex = "+"
663

    
664
            # split RX/TX in different bands
665
            if mem.offset > 71000000:
666
                mem.duplex = "split"
667

    
668
                # show the actual value in the offset, depending on the shift
669
                if _mem.offminus == 1:
670
                    mem.offset = mem.freq - mem.offset
671
                if _mem.offplus == 1:
672
                    mem.offset = mem.freq + mem.offset
673

    
674
        # wide/narrow
675
        mem.mode = MODES[int(_mem.wide)]
676

    
677
        # skip
678
        mem.skip = SKIP_VALUES[_mem.noskip]
679

    
680
        # tone data
681
        rxtone = txtone = None
682
        txtone = self._decode_tone(_mem.txtone, _mem.ttondinv)
683
        rxtone = self._decode_tone(_mem.rxtone, _mem.rtondinv)
684
        chirp_common.split_tone_decode(mem, txtone, rxtone)
685

    
686

    
687
        return mem
688

    
689
    def set_memory(self, mem):
690
        """Set the memory data in the eeprom img from the UI"""
691
        # get the eprom representation of this channel
692
        _mem = self._get_special(mem.number)
693

    
694
        # if empty memmory
695
        if mem.empty:
696
            # the channel itself
697
            _mem.set_raw("\xFF" * 16)
698
            # return it
699
            return mem
700

    
701
        # frequency
702
        _mem.rxfreq = mem.freq / 10
703

    
704
        # duplex/ offset Offset is an absolute value
705
        _mem.txoffset = mem.offset / 10
706

    
707
        # must work out the polarity
708
        if mem.duplex == "":
709
            _mem.offplus = 0
710
            _mem.offminus = 0
711
        elif mem.duplex == "+":
712
            _mem.offplus = 1
713
            _mem.offminus = 0
714
        elif mem.duplex == "-":
715
            _mem.offplus = 0
716
            _mem.offminus = 1
717
        elif mem.duplex == "split":
718
            if mem.freq > mem.offset:
719
                _mem.offplus = 0
720
                _mem.offminus = 1
721
                _mem.txoffset = (mem.freq - mem.offset) / 10
722
            else:
723
                _mem.offplus = 1
724
                _mem.offminus = 0
725
                _mem.txoffset = (mem.offset - mem.freq) / 10
726

    
727
        # wide/narrow
728
        _mem.wide = MODES.index(mem.mode)
729

    
730
        # skip
731
        _mem.noskip = SKIP_VALUES.index(mem.skip)
732

    
733
        # tone data
734
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
735
            chirp_common.split_tone_encode(mem)
736
        self._encode_tone(_mem.txtone, _mem.ttondinv, txmode, txtone, txpol)
737
        self._encode_tone(_mem.rxtone, _mem.rtondinv, rxmode, rxtone, rxpol)
738

    
739
        return mem
740

    
741
    def get_settings(self):
742
        _settings = self._memobj.settings
743
        basic = RadioSettingGroup("basic", "Basic Settings")
744
        fm = RadioSettingGroup("fm", "FM Radio")
745
        adv = RadioSettingGroup("adv", "Advanced Settings")
746
        group = RadioSettings(basic, fm, adv)
747

    
748
        ### Basic Settings
749
        rs = RadioSetting("tx_pwr", "TX Power",
750
                          RadioSettingValueList(
751
                            POWER_LIST, POWER_LIST[_settings.tx_pwr]))
752
        basic.append(rs)
753

    
754
        rs = RadioSetting("channel", "Active Channel",
755
                          RadioSettingValueInteger(1, 20, _settings.channel))
756
        basic.append(rs)
757

    
758
        rs = RadioSetting("squelch", "Squelch Level",
759
                          RadioSettingValueInteger(0, 9, _settings.squelch))
760
        basic.append(rs)
761

    
762
        rs = RadioSetting("vox", "VOX Level",
763
                          RadioSettingValueInteger(0, 9, _settings.vox))
764
        basic.append(rs)
765

    
766
        # volume validation, as the OEM software set 0xFF on write
767
        _volume = _settings.volume
768
        if _volume > 7:
769
            _volume = 7
770
        rs = RadioSetting("volume", "Volume Level",
771
                          RadioSettingValueInteger(0, 7, _volume))
772
        basic.append(rs)
773

    
774
        rs = RadioSetting("scantype", "Scan Type",
775
                          RadioSettingValueList(
776
                            SCAN_TYPE_LIST, SCAN_TYPE_LIST[_settings.scantype]))
777
        basic.append(rs)
778

    
779
        rs = RadioSetting("timeout", "Time Out Timer (seconds)",
780
                          RadioSettingValueList(
781
                            TOT_LIST, TOT_LIST[_settings.timeout]))
782
        basic.append(rs)
783

    
784
        rs = RadioSetting("voice", "Voice Prompt",
785
                          RadioSettingValueList(
786
                            LANGUAGE_LIST, LANGUAGE_LIST[_settings.voice]))
787
        basic.append(rs)
788

    
789
        rs = RadioSetting("alarm", "Alarm Time",
790
                          RadioSettingValueList(
791
                            TIMER_LIST, TIMER_LIST[_settings.alarm]))
792
        basic.append(rs)
793

    
794
        rs = RadioSetting("backlight", "Backlight",
795
                          RadioSettingValueList(
796
                            BACKLIGHT_LIST,
797
                            BACKLIGHT_LIST[_settings.backlight]))
798
        basic.append(rs)
799

    
800
        rs = RadioSetting("blo", "Busy Lockout",
801
                          RadioSettingValueBoolean(_settings.blo))
802
        basic.append(rs)
803

    
804
        rs = RadioSetting("ste", "Squelch Tail Eliminate",
805
                          RadioSettingValueBoolean(_settings.ste))
806
        basic.append(rs)
807

    
808
        rs = RadioSetting("batsave", "Battery Save",
809
                          RadioSettingValueBoolean(_settings.batsave))
810
        basic.append(rs)
811

    
812
        rs = RadioSetting("lock", "Key Lock",
813
                          RadioSettingValueBoolean(_settings.lock))
814
        basic.append(rs)
815

    
816
        rs = RadioSetting("beep", "Key Beep",
817
                          RadioSettingValueBoolean(_settings.beep))
818
        basic.append(rs)
819

    
820
        ### FM Settings
821
        rs = RadioSetting("fm_funct", "FM Function",
822
                          RadioSettingValueBoolean(_settings.fm_funct))
823
        fm.append(rs)
824

    
825
        rs = RadioSetting("fmrange", "FM Range",
826
                          RadioSettingValueList(
827
                            FM_RANGE_LIST, FM_RANGE_LIST[_settings.fmrange]))
828
        fm.append(rs)
829

    
830
        # callbacks for the FM VFO
831
        def apply_fm_freq(setting, obj):
832
            setattr(obj, setting.get_name(),
833
                (float(str(setting.value)) - 65) * 10)
834

    
835
        _fm_vfo = int(_settings.fm_vfo) * 0.1 + 65
836
        rs = RadioSetting("fm_vfo", "FM Station",
837
                          RadioSettingValueFloat(65, 108, _fm_vfo))
838
        rs.set_apply_callback(apply_fm_freq, _settings)
839
        fm.append(rs)
840

    
841
        ### Advanced
842
        def apply_limit(setting, obj):
843
            setattr(obj, setting.get_name(), int(setting.value) * 10)
844

    
845
        rs = RadioSetting("vhfl", "VHF Low Limit",
846
                          RadioSettingValueInteger(136, 174,
847
                            int(_settings.vhfl) / 10))
848
        rs.set_apply_callback(apply_limit, _settings)
849
        adv.append(rs)
850

    
851
        rs = RadioSetting("vhfh", "VHF High Limit",
852
                          RadioSettingValueInteger(136, 174,
853
                            int(_settings.vhfh) / 10))
854
        rs.set_apply_callback(apply_limit, _settings)
855
        adv.append(rs)
856

    
857
        rs = RadioSetting("uhfl", "UHF Low Limit",
858
                          RadioSettingValueInteger(400, 470,
859
                            int(_settings.uhfl) / 10))
860
        rs.set_apply_callback(apply_limit, _settings)
861
        adv.append(rs)
862

    
863
        rs = RadioSetting("uhfh", "UHF High Limit",
864
                          RadioSettingValueInteger(400, 470,
865
                            int(_settings.uhfh) / 10))
866
        rs.set_apply_callback(apply_limit, _settings)
867
        adv.append(rs)
868

    
869
        rs = RadioSetting("relaym", "Relay Mode",
870
                          RadioSettingValueList(
871
                            RELAY_MODE_LIST, RELAY_MODE_LIST[_settings.relaym]))
872
        adv.append(rs)
873

    
874
        return group
875

    
876
    def set_settings(self, uisettings):
877
        _settings = self._memobj.settings
878

    
879
        for element in uisettings:
880
            if not isinstance(element, RadioSetting):
881
                self.set_settings(element)
882
                continue
883
            if not element.changed():
884
                continue
885

    
886
            try:
887
                name = element.get_name()
888
                value = element.value
889

    
890
                if element.has_apply_callback():
891
                    LOG.debug("Using apply callback")
892
                    element.run_apply_callback()
893
                else:
894
                    obj = getattr(_settings, name)
895
                    setattr(_settings, name, value)
896

    
897
                LOG.debug("Setting %s: %s" % (name, value))
898
            except Exception, e:
899
                LOG.debug(element.get_name())
900
                raise
901

    
902
    @classmethod
903
    def match_model(cls, filedata, filename):
904
        match_size = False
905
        match_model = False
906

    
907
        # testing the file data size
908
        if len(filedata) == MEM_SIZE:
909
            match_size = True
910

    
911
            # DEBUG
912
            if debug is True:
913
                LOG.debug("BF-T1 matched!")
914

    
915
        # testing the firmware model fingerprint
916
        match_model = _model_match(cls, filedata)
917

    
918
        return match_size and match_model
(77-77/77)