Project

General

Profile

Bug #3993 » btech_2.py

different approach: try without delay, and retry with delay if download fails - Michael Wagner, 09/12/2016 03:11 PM

 
1
# Copyright 2016:
2
# * Pavel Milanes CO7WT, <pavelmc@gmail.com>
3
# * Jim Unroe KC9HI, <rock.unroe@gmail.com>
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 RadioSettingGroup, RadioSetting, \
28
    RadioSettingValueBoolean, RadioSettingValueList, \
29
    RadioSettingValueString, RadioSettingValueInteger, \
30
    RadioSettings, InvalidValueError
31
from textwrap import dedent
32

    
33
MEM_FORMAT = """
34
#seekto 0x0000;
35
struct {
36
  lbcd rxfreq[4];
37
  lbcd txfreq[4];
38
  ul16 rxtone;
39
  ul16 txtone;
40
  u8 unknown0:4,
41
     scode:4;
42
  u8 unknown1:2,
43
     spmute:1,
44
     unknown2:3,
45
     optsig:2;
46
  u8 unknown3:3,
47
     scramble:1,
48
     unknown4:3,
49
     power:1;
50
  u8 unknown5:1,
51
     wide:1,
52
     unknown6:2,
53
     bcl:1,
54
     add:1,
55
     pttid:2;
56
} memory[200];
57

    
58
#seekto 0x0E00;
59
struct {
60
  u8 tdr;
61
  u8 unknown1;
62
  u8 sql;
63
  u8 unknown2[2];
64
  u8 tot;
65
  u8 apo;           // BTech radios use this as the Auto Power Off time
66
                    // other radios use this as pre-Time Out Alert
67
  u8 unknown3;
68
  u8 abr;
69
  u8 beep;
70
  u8 unknown4[4];
71
  u8 dtmfst;
72
  u8 unknown5[2];
73
  u8 prisc;
74
  u8 prich;
75
  u8 screv;
76
  u8 unknown6[2];
77
  u8 pttid;
78
  u8 pttlt;
79
  u8 unknown7;
80
  u8 emctp;
81
  u8 emcch;
82
  u8 ringt;
83
  u8 unknown8;
84
  u8 camdf;
85
  u8 cbmdf;
86
  u8 sync;          // BTech radios use this as the display sync setting
87
                    // other radios use this as the auto keypad lock setting
88
  u8 ponmsg;
89
  u8 wtled;
90
  u8 rxled;
91
  u8 txled;
92
  u8 unknown9[5];
93
  u8 anil;
94
  u8 reps;
95
  u8 repm;
96
  u8 tdrab;
97
  u8 ste;
98
  u8 rpste;
99
  u8 rptdl;
100
  u8 mgain;
101
  u8 dtmfg;
102
} settings;
103

    
104
#seekto 0x0E80;
105
struct {
106
  u8 unknown1;
107
  u8 vfomr;
108
  u8 keylock;
109
  u8 unknown2;
110
  u8 unknown3:4,
111
     vfomren:1,
112
     unknown4:1,
113
     reseten:1,
114
     menuen:1;
115
  u8 unknown5[11];
116
  u8 dispab;
117
  u8 mrcha;
118
  u8 mrchb;
119
  u8 menu;
120
} settings2;
121

    
122
#seekto 0x0EC0;
123
struct {
124
  char line1[6];
125
  char line2[6];
126
} poweron_msg;
127

    
128
struct settings_vfo {
129
  u8 freq[8];
130
  u8 unknown1;
131
  u8 offset[4];
132
  u8 unknown2[3];
133
  ul16 rxtone;
134
  ul16 txtone;
135
  u8 scode;
136
  u8 spmute;
137
  u8 optsig;
138
  u8 scramble;
139
  u8 wide;
140
  u8 power;
141
  u8 shiftd;
142
  u8 step;
143
  u8 unknown3[4];
144
};
145

    
146
#seekto 0x0F00;
147
struct {
148
  struct settings_vfo a;
149
  struct settings_vfo b;
150
} vfo;
151

    
152
#seekto 0x1000;
153
struct {
154
  char name[6];
155
  u8 unknown1[10];
156
} names[200];
157

    
158
#seekto 0x3C90;
159
struct {
160
  u8 vhf_low[3];
161
  u8 vhf_high[3];
162
  u8 uhf_low[3];
163
  u8 uhf_high[3];
164
} ranges;
165

    
166
// the UV-2501+220 & KT8900R has different zones for storing ranges
167

    
168
#seekto 0x3CD0;
169
struct {
170
  u8 vhf_low[3];
171
  u8 vhf_high[3];
172
  u8 unknown1[4];
173
  u8 unknown2[6];
174
  u8 vhf2_low[3];
175
  u8 vhf2_high[3];
176
  u8 unknown3[4];
177
  u8 unknown4[6];
178
  u8 uhf_low[3];
179
  u8 uhf_high[3];
180
} ranges220;
181

    
182
#seekto 0x3F70;
183
struct {
184
  char fp[6];
185
} fingerprint;
186

    
187
"""
188

    
189
# A note about the memmory in these radios
190
#
191
# The real memory of these radios extends to 0x4000
192
# On read the factory software only uses up to 0x3200
193
# On write it just uploads the contents up to 0x3100
194
#
195
# The mem beyond 0x3200 holds the ID data
196

    
197
MEM_SIZE = 0x4000
198
BLOCK_SIZE = 0x40
199
TX_BLOCK_SIZE = 0x10
200
ACK_CMD = "\x06"
201
MODES = ["FM", "NFM"]
202
SKIP_VALUES = ["S", ""]
203
TONES = chirp_common.TONES
204
DTCS = sorted(chirp_common.DTCS_CODES + [645])
205
NAME_LENGTH = 6
206
PTTID_LIST = ["OFF", "BOT", "EOT", "BOTH"]
207
PTTIDCODE_LIST = ["%s" % x for x in range(1, 16)]
208
OPTSIG_LIST = ["OFF", "DTMF", "2TONE", "5TONE"]
209
SPMUTE_LIST = ["Tone/DTCS", "Tone/DTCS and Optsig", "Tone/DTCS or Optsig"]
210

    
211
LIST_TOT = ["%s sec" % x for x in range(15, 615, 15)]
212
LIST_TOA = ["Off"] + ["%s seconds" % x for x in range(1, 11)]
213
LIST_APO = ["Off"] + ["%s minutes" % x for x in range(30, 330, 30)]
214
LIST_ABR = ["Off"] + ["%s seconds" % x for x in range(1, 51)]
215
LIST_DTMFST = ["OFF", "Keyboard", "ANI", "Keyboad + ANI"]
216
LIST_SCREV = ["TO (timeout)", "CO (carrier operated)", "SE (search)"]
217
LIST_EMCTP = ["TX alarm sound", "TX ANI", "Both"]
218
LIST_RINGT = ["Off"] + ["%s seconds" % x for x in range(1, 10)]
219
LIST_MDF = ["Frequency", "Channel", "Name"]
220
LIST_PONMSG = ["Full", "Message", "Battery voltage"]
221
LIST_COLOR = ["Off", "Blue", "Orange", "Purple"]
222
LIST_REPS = ["1000 Hz", "1450 Hz", "1750 Hz", "2100Hz"]
223
LIST_REPM = ["Off", "Carrier", "CTCSS or DCS", "Tone", "DTMF"]
224
LIST_RPTDL = ["Off"] + ["%s ms" % x for x in range(1, 10)]
225
LIST_ANIL = ["3", "4", "5"]
226
LIST_AB = ["A", "B"]
227
LIST_VFOMR = ["Frequency", "Channel"]
228
LIST_SHIFT = ["Off", "+", "-"]
229
LIST_TXP = ["High", "Low"]
230
LIST_WIDE = ["Wide", "Narrow"]
231
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0]
232
LIST_STEP = [str(x) for x in STEPS]
233

    
234
# This is a general serial timeout for all serial read functions.
235
# Practice has show that about 0.7 sec will be enough to cover all radios.
236
STIMEOUT = 0.7
237

    
238
# this var controls the verbosity in the debug and by default it's low (False)
239
# make it True and you will to get a very verbose debug.log
240
debug = True
241

    
242
# Power Levels
243
NORMAL_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=25),
244
                       chirp_common.PowerLevel("Low", watts=10)]
245
UV5001_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=50),
246
                       chirp_common.PowerLevel("Low", watts=10)]
247

    
248
# this must be defined globaly
249
POWER_LEVELS = None
250

    
251
# valid chars on the LCD, Note that " " (space) is stored as "\xFF"
252
VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
253
    "`{|}!\"#$%&'()*+,-./:;<=>?@[]^_"
254

    
255

    
256
##### ID strings #####################################################
257

    
258
# BTECH UV2501 pre-production units
259
UV2501pp_fp = "M2C294"
260
# BTECH UV2501 pre-production units 2 + and 1st Gen radios
261
UV2501pp2_fp = "M29204"
262
# B-TECH UV-2501 second generation (2G) radios
263
UV2501G2_fp = "BTG214"
264
# B-TECH UV-2501 third generation (3G) radios
265
UV2501G3_fp = "BTG324"
266

    
267
# B-TECH UV-2501+220 pre-production units
268
UV2501_220pp_fp = "M3C281"
269
# extra block read for the 2501+220 pre-production units
270
# the same for all of this radios so far
271
UV2501_220pp_id = "      280528"
272
# B-TECH UV-2501+220
273
UV2501_220_fp = "M3G201"
274
# new variant, let's call it Generation 2
275
UV2501_220G2_fp = "BTG211"
276
# B-TECH UV-2501+220 third generation (3G)
277
UV2501_220G3_fp = "BTG311"
278

    
279
# B-TECH UV-5001 pre-production units + 1st Gen radios
280
UV5001pp_fp = "V19204"
281
# B-TECH UV-5001 alpha units
282
UV5001alpha_fp = "V28204"
283
# B-TECH UV-5001 second generation (2G) radios
284
UV5001G2_fp = "BTG214"
285
# B-TECH UV-5001 second generation (2G2)
286
UV5001G22_fp = "V2G204"
287
# B-TECH UV-5001 third generation (3G)
288
UV5001G3_fp = "BTG304"
289

    
290
# special var to know when we found a BTECH Gen 3
291
BTECH3 = [UV2501G3_fp, UV2501_220G3_fp, UV5001G3_fp]
292

    
293

    
294
# WACCOM Mini-8900
295
MINI8900_fp = "M28854"
296

    
297

    
298
# QYT KT-UV980
299
KTUV980_fp = "H28854"
300

    
301
# QYT KT8900
302
KT8900_fp = "M29154"
303
# New generations KT8900
304
KT8900_fp1 = "M2C234"
305
KT8900_fp2 = "M2G1F4"
306
KT8900_fp3 = "M2G2F4"
307
KT8900_fp4 = "M2G304"
308
# this radio has an extra ID
309
KT8900_id = "      303688"
310

    
311
# KT8900R
312
KT8900R_fp = "M3G1F4"
313
# Second Generation
314
KT8900R_fp1 = "M3G214"
315
# another model
316
KT8900R_fp2 = "M3C234"
317
# another model G4?
318
KT8900R_fp3 = "M39164"
319
# this radio has an extra ID
320
KT8900R_id = "280528"
321

    
322

    
323
# LUITON LT-588UV
324
LT588UV_fp = "V2G1F4"
325

    
326

    
327
#### MAGICS
328
# for the Waccom Mini-8900
329
MSTRING_MINI8900 = "\x55\xA5\xB5\x45\x55\x45\x4d\x02"
330
# for the B-TECH UV-2501+220 (including pre production ones)
331
MSTRING_220 = "\x55\x20\x15\x12\x12\x01\x4d\x02"
332
# for the QYT KT8900 & R
333
MSTRING_KT8900 = "\x55\x20\x15\x09\x16\x45\x4D\x02"
334
MSTRING_KT8900R = "\x55\x20\x15\x09\x25\x01\x4D\x02"
335
# magic string for all other models
336
MSTRING = "\x55\x20\x15\x09\x20\x45\x4d\x02"
337

    
338
NEEDS_DELAY = False
339
RETRY_DELAYED = False
340

    
341
def _clean_buffer(radio):
342
    """Cleaning the read serial buffer, hard timeout to survive an infinite
343
    data stream"""
344

    
345
    # touching the serial timeout to optimize the flushing
346
    # restored at the end to the default value
347
    radio.pipe.timeout = 0.1
348
    dump = "1"
349
    datacount = 0
350

    
351
    try:
352
        while len(dump) > 0:
353
            dump = radio.pipe.read(100)
354
            datacount += len(dump)
355
            # hard limit to survive a infinite serial data stream
356
            # 5 times bigger than a normal rx block (69 bytes)
357
            if datacount > 345:
358
                seriale = "Please check your serial port selection."
359
                raise errors.RadioError(seriale)
360

    
361
        # restore the default serial timeout
362
        radio.pipe.timeout = STIMEOUT
363

    
364
    except Exception:
365
        raise errors.RadioError("Unknown error cleaning the serial buffer")
366

    
367

    
368
def _rawrecv(radio, amount):
369
    """Raw read from the radio device, less intensive way"""
370

    
371
    data = ""
372

    
373
    try:
374
        data = radio.pipe.read(amount)
375

    
376
        # DEBUG
377
        if debug is True:
378
            LOG.debug("<== (%d) bytes:\n\n%s" %
379
                      (len(data), util.hexprint(data)))
380

    
381
        # fail if no data is received
382
        if len(data) == 0:
383
            raise errors.RadioError("No data received from radio")
384

    
385
        # notice on the logs if short
386
        if len(data) < amount:
387
            LOG.warn("Short reading %d bytes from the %d requested." %
388
                     (len(data), amount))
389
            global NEEDS_DELAY
390
            NEEDS_DELAY = True
391
            LOG.debug("Delaying future writes")
392

    
393
    except:
394
        raise errors.RadioError("Error reading data from radio")
395

    
396
    return data
397

    
398

    
399
def _send(radio, data):
400
    """Send data to the radio device"""
401

    
402
    try:
403
        for byte in data:
404
            radio.pipe.write(byte)
405
            # linux is to fast in the serial, causing the radio send a \x05
406
            # byte and stop responding, we need to slow it down a bit.
407
            # Thanks to Michael Wagner for this fix.
408
            # we have not noticed any problem in MAC (YET)
409
            #if sys.platform.startswith ('linux'):
410
            #LOG.debug(NEEDS_DELAY)
411
            if NEEDS_DELAY:
412
                # 10 msec is proved to be safe.
413
                sleep(0.005)
414
                LOG.debug("delayed")
415

    
416
        # DEBUG
417
        if debug is True:
418
            LOG.debug("==> (%d) bytes:\n\n%s" %
419
                      (len(data), util.hexprint(data)))
420
    except:
421
        raise errors.RadioError("Error sending data to radio")
422

    
423

    
424
def _make_frame(cmd, addr, length, data=""):
425
    """Pack the info in the headder format"""
426
    frame = "\x06" + struct.pack(">BHB", ord(cmd), addr, length)
427
    # add the data if set
428
    if len(data) != 0:
429
        frame += data
430

    
431
    return frame
432

    
433

    
434
def _recv(radio, addr):
435
    """Get data from the radio all at once to lower syscalls load"""
436

    
437
    # Get the full 69 bytes at a time to reduce load
438
    # 1 byte ACK + 4 bytes header + 64 bytes of data (BLOCK_SIZE)
439

    
440
    # get the whole block
441
    block = _rawrecv(radio, BLOCK_SIZE + 5)
442

    
443
    # basic check
444
    if len(block) < (BLOCK_SIZE + 5):
445
        raise errors.RadioError("Short read of the block 0x%04x" % addr)
446

    
447
    # checking for the ack
448
    if block[0] != ACK_CMD:
449
        raise errors.RadioError("Bad ack from radio in block 0x%04x" % addr)
450

    
451
    # header validation
452
    c, a, l = struct.unpack(">BHB", block[1:5])
453
    if a != addr or l != BLOCK_SIZE or c != ord("X"):
454
        LOG.error("Invalid header for block 0x%04x" % addr)
455
        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
456
        #retry once
457
        global RETRY_DELAYED
458
        if RETRY_DELAYED:
459
            LOG.debug("This was already a retry")
460
            raise errors.RadioError("Invalid header for block 0x%04x:" % addr)
461
        else:
462
            LOG.warn("Failure occured. Trying once again with delay")
463
            RETRY_DELAYED = True
464
            global NEEDS_DELAY
465
            NEEDS_DELAY = True
466
            return False
467
        #raise errors.RadioError("Invalid header for block 0x%04x:" % addr)
468

    
469
    # return the data
470
    return block[5:]
471

    
472

    
473
def _start_clone_mode(radio, status):
474
    """Put the radio in clone mode and get the ident string, 3 tries"""
475

    
476
    # cleaning the serial buffer
477
    _clean_buffer(radio)
478

    
479
    # prep the data to show in the UI
480
    status.cur = 0
481
    status.msg = "Identifying the radio..."
482
    status.max = 3
483
    radio.status_fn(status)
484

    
485
    try:
486
        for a in range(0, status.max):
487
            # Update the UI
488
            status.cur = a + 1
489
            radio.status_fn(status)
490

    
491
            # send the magic word
492
            _send(radio, radio._magic)
493

    
494
            # Now you get a x06 of ACK if all goes well
495
            ack = radio.pipe.read(1)
496

    
497
            if ack == "\x06":
498
                # DEBUG
499
                LOG.info("Magic ACK received")
500
                status.cur = status.max
501
                radio.status_fn(status)
502

    
503
                return True
504

    
505
        return False
506

    
507
    except errors.RadioError:
508
        raise
509
    except Exception, e:
510
        raise errors.RadioError("Error sending Magic to radio:\n%s" % e)
511

    
512

    
513
def _do_ident(radio, status, upload=False):
514
    """Put the radio in PROGRAM mode & identify it"""
515
    #  set the serial discipline
516
    radio.pipe.baudrate = 9600
517
    radio.pipe.parity = "N"
518

    
519
    # open the radio into program mode
520
    if _start_clone_mode(radio, status) is False:
521
        msg = "Radio did not enter clone mode"
522
        # warning about old versions of QYT KT8900
523
        if radio.MODEL == "KT8900":
524
            msg += ". You may want to try it as a WACCOM MINI-8900, there is a"
525
            msg += " known variant of this radios that is a clone of it."
526
        raise errors.RadioError(msg)
527

    
528
    # Ok, get the ident string
529
    ident = _rawrecv(radio, 49)
530

    
531
    # basic check for the ident
532
    if len(ident) != 49:
533
        raise errors.RadioError("Radio send a short ident block.")
534

    
535
    # check if ident is OK
536
    itis = False
537
    for fp in radio._fileid:
538
        if fp in ident:
539
            # got it!
540
            itis = True
541
            # checking if we are dealing with a Gen 3 BTECH
542
            if radio.VENDOR == "BTECH" and fp in BTECH3:
543
                radio.btech3 = True
544

    
545
            break
546

    
547
    if itis is False:
548
        LOG.debug("Incorrect model ID, got this:\n\n" + util.hexprint(ident))
549
        raise errors.RadioError("Radio identification failed.")
550

    
551
    # some radios needs a extra read and check for a code on it, this ones
552
    # has the check value in the _id2 var, others simply False
553
    if radio._id2 is not False:
554
        # lower the timeout here as this radios are reseting due to timeout
555
        radio.pipe.timeout = 0.05
556

    
557
        # query & receive the extra ID
558
        _send(radio, _make_frame("S", 0x3DF0, 16))
559
        id2 = _rawrecv(radio, 21)
560

    
561
        # WARNING !!!!!!
562
        # different radios send a response with a different amount of data
563
        # it seems that it's padded with \xff, \x20 and some times with \x00
564
        # we just care about the first 16, our magic string is in there
565
        if len(id2) < 16:
566
            raise errors.RadioError("The extra ID is short, aborting.")
567

    
568
        # ok, the correct string must be in the received data
569
        if radio._id2 not in id2:
570
            LOG.debug("Full *BAD* extra ID on the %s is: \n%s" %
571
                      (radio.MODEL, util.hexprint(id2)))
572
            raise errors.RadioError("The extra ID is wrong, aborting.")
573

    
574
        # this radios need a extra request/answer here on the upload
575
        # the amount of data received depends of the radio type
576
        #
577
        # also the first block of TX must no have the ACK at the beginning
578
        # see _upload for this.
579
        if upload is True:
580
            # send an ACK
581
            _send(radio, ACK_CMD)
582

    
583
            # the amount of data depend on the radio, so far we have two radios
584
            # reading two bytes with an ACK at the end and just ONE with just
585
            # one byte (QYT KT8900)
586
            # the JT-6188 appears a clone of the last, but reads TWO bytes.
587
            #
588
            # we will read two bytes with a custom timeout to not penalize the
589
            # users for this.
590
            #
591
            # we just check for a response and last byte being a ACK, that is
592
            # the common stone for all radios (3 so far)
593
            ack = _rawrecv(radio, 2)
594

    
595
            # checking
596
            if len(ack) == 0 or ack[-1:] != ACK_CMD:
597
                raise errors.RadioError("Radio didn't ACK the upload")
598

    
599
            # restore the default serial timeout
600
            radio.pipe.timeout = STIMEOUT
601

    
602
    # DEBUG
603
    LOG.info("Positive ident, this is a %s %s" % (radio.VENDOR, radio.MODEL))
604

    
605
    return True
606

    
607

    
608
def _download(radio):
609
    """Get the memory map"""
610

    
611
    # UI progress
612
    status = chirp_common.Status()
613

    
614
    # put radio in program mode and identify it
615
    _do_ident(radio, status)
616

    
617
    # the models that doesn't have the extra ID have to make a dummy read here
618
    if radio._id2 is False:
619
        _send(radio, _make_frame("S", 0, BLOCK_SIZE))
620
        discard = _rawrecv(radio, BLOCK_SIZE + 5)
621

    
622
        if debug is True:
623
            LOG.info("Dummy first block read done, got this:\n\n %s",
624
                     util.hexprint(discard))
625

    
626
    # reset the progress bar in the UI
627
    status.max = MEM_SIZE / BLOCK_SIZE
628
    status.msg = "Cloning from radio..."
629
    status.cur = 0
630
    radio.status_fn(status)
631

    
632
    # cleaning the serial buffer
633
    _clean_buffer(radio)
634

    
635
    data = ""
636
    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
637
        # sending the read request
638
        _send(radio, _make_frame("S", addr, BLOCK_SIZE))
639

    
640
        # read
641
        d = _recv(radio, addr)
642

    
643
        if d == False:
644
            LOG.info("Previous _recv failed. Cleaning buffer and trying again.")
645
            _clean_buffer(radio)
646
            d = _recv(radio, addr)
647
            global RETRY_DELAYED
648
            RETRY_DELAYED = False
649
        # aggregate the data
650
        data += d
651

    
652
        # UI Update
653
        status.cur = addr / BLOCK_SIZE
654
        status.msg = "Cloning from radio..."
655
        radio.status_fn(status)
656

    
657
    return data
658

    
659

    
660
def _upload(radio):
661
    """Upload procedure"""
662

    
663
    # The UPLOAD mem is restricted to lower than 0x3100,
664
    # so we will overide that here localy
665
    MEM_SIZE = 0x3100
666

    
667
    # UI progress
668
    status = chirp_common.Status()
669

    
670
    # put radio in program mode and identify it
671
    _do_ident(radio, status, True)
672

    
673
    # get the data to upload to radio
674
    data = radio.get_mmap()
675

    
676
    # Reset the UI progress
677
    status.max = MEM_SIZE / TX_BLOCK_SIZE
678
    status.cur = 0
679
    status.msg = "Cloning to radio..."
680
    radio.status_fn(status)
681

    
682
    # the radios that doesn't have the extra ID 'may' do a dummy write, I found
683
    # that leveraging the bad ACK and NOT doing the dummy write is ok, as the
684
    # dummy write is accepted (it actually writes to the mem!) by the radio.
685

    
686
    # cleaning the serial buffer
687
    _clean_buffer(radio)
688

    
689
    # the fun start here
690
    for addr in range(0, MEM_SIZE, TX_BLOCK_SIZE):
691
        # getting the block of data to send
692
        d = data[addr:addr + TX_BLOCK_SIZE]
693

    
694
        # build the frame to send
695
        frame = _make_frame("X", addr, TX_BLOCK_SIZE, d)
696

    
697
        # first block must not send the ACK at the beginning for the
698
        # ones that has the extra id, since this have to do a extra step
699
        if addr == 0 and radio._id2 is not False:
700
            frame = frame[1:]
701

    
702
        # send the frame
703
        _send(radio, frame)
704

    
705
        # receiving the response
706
        ack = _rawrecv(radio, 1)
707

    
708
        # basic check
709
        if len(ack) != 1:
710
            raise errors.RadioError("No ACK when writing block 0x%04x" % addr)
711

    
712
        if not ack in "\x06\x05":
713
            raise errors.RadioError("Bad ACK writing block 0x%04x:" % addr)
714

    
715
         # UI Update
716
        status.cur = addr / TX_BLOCK_SIZE
717
        status.msg = "Cloning to radio..."
718
        radio.status_fn(status)
719

    
720

    
721
def model_match(cls, data):
722
    """Match the opened/downloaded image to the correct version"""
723
    rid = data[0x3f70:0x3f76]
724

    
725
    if rid in cls._fileid:
726
        return True
727

    
728
    return False
729

    
730

    
731
def _decode_ranges(low, high):
732
    """Unpack the data in the ranges zones in the memmap and return
733
    a tuple with the integer corresponding to the Mhz it means"""
734
    ilow = int(low[0]) * 100 + int(low[1]) * 10 + int(low[2])
735
    ihigh = int(high[0]) * 100 + int(high[1]) * 10 + int(high[2])
736
    ilow *= 1000000
737
    ihigh *= 1000000
738

    
739
    return (ilow, ihigh)
740

    
741

    
742
def _split(rf, f1, f2):
743
    """Returns False if the two freqs are in the same band (no split)
744
    or True otherwise"""
745

    
746
    # determine if the two freqs are in the same band
747
    for low, high in rf.valid_bands:
748
        if f1 >= low and f1 <= high and \
749
                f2 >= low and f2 <= high:
750
            # if the two freqs are on the same Band this is not a split
751
            return False
752

    
753
    # if you get here is because the freq pairs are split
754
    return False
755

    
756

    
757
class BTech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
758
    """BTECH's UV-5001 and alike radios"""
759
    VENDOR = "BTECH"
760
    MODEL = ""
761
    IDENT = ""
762
    _vhf_range = (130000000, 180000000)
763
    _220_range = (210000000, 231000000)
764
    _uhf_range = (400000000, 521000000)
765
    _upper = 199
766
    _magic = MSTRING
767
    _fileid = None
768
    _id2 = False
769
    btech3 = False
770

    
771
    @classmethod
772
    def get_prompts(cls):
773
        rp = chirp_common.RadioPrompts()
774
        rp.experimental = \
775
            ('This driver is experimental.\n'
776
             '\n'
777
             'Please keep a copy of your memories with the original software '
778
             'if you treasure them, this driver is new and may contain'
779
             ' bugs.\n'
780
             '\n'
781
             )
782
        rp.pre_download = _(dedent("""\
783
            Follow these instructions to download your info:
784

    
785
            1 - Turn off your radio
786
            2 - Connect your interface cable
787
            3 - Turn on your radio
788
            4 - Do the download of your radio data
789

    
790
            """))
791
        rp.pre_upload = _(dedent("""\
792
            Follow these instructions to upload your info:
793

    
794
            1 - Turn off your radio
795
            2 - Connect your interface cable
796
            3 - Turn on your radio
797
            4 - Do the upload of your radio data
798

    
799
            """))
800
        return rp
801

    
802
    def get_features(self):
803
        """Get the radio's features"""
804

    
805
        # we will use the following var as global
806
        global POWER_LEVELS
807

    
808
        rf = chirp_common.RadioFeatures()
809
        rf.has_settings = True
810
        rf.has_bank = False
811
        rf.has_tuning_step = False
812
        rf.can_odd_split = True
813
        rf.has_name = True
814
        rf.has_offset = True
815
        rf.has_mode = True
816
        rf.has_dtcs = True
817
        rf.has_rx_dtcs = True
818
        rf.has_dtcs_polarity = True
819
        rf.has_ctone = True
820
        rf.has_cross = True
821
        rf.valid_modes = MODES
822
        rf.valid_characters = VALID_CHARS
823
        rf.valid_name_length = NAME_LENGTH
824
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
825
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
826
        rf.valid_cross_modes = [
827
            "Tone->Tone",
828
            "DTCS->",
829
            "->DTCS",
830
            "Tone->DTCS",
831
            "DTCS->Tone",
832
            "->Tone",
833
            "DTCS->DTCS"]
834
        rf.valid_skips = SKIP_VALUES
835
        rf.valid_dtcs_codes = DTCS
836
        rf.memory_bounds = (0, self._upper)
837

    
838
        # power levels
839
        if self.MODEL == "UV-5001":
840
            POWER_LEVELS = UV5001_POWER_LEVELS  # Higher power (50W)
841
        else:
842
            POWER_LEVELS = NORMAL_POWER_LEVELS  # Lower power (25W)
843

    
844
        rf.valid_power_levels = POWER_LEVELS
845

    
846
        # bands
847
        rf.valid_bands = [self._vhf_range, self._uhf_range]
848

    
849
        # 2501+220 & KT8900R
850
        if self.MODEL in ["UV-2501+220", "KT8900R"]:
851
            rf.valid_bands.append(self._220_range)
852

    
853
        return rf
854

    
855
    def sync_in(self):
856
        """Download from radio"""
857
        try:
858
            data = _download(self)
859
            LOG.info("Download succeeded for the first attempt!")
860
        except errors.RadioError:
861
            LOG.error("First download-attempt failed. Retrying whole procedure delayed")
862
            global NEEDS_DELAY
863
            NEEDS_DELAY = True
864
            data = _download(self)
865
        self._mmap = memmap.MemoryMap(data)
866
        self.process_mmap()
867

    
868
    def sync_out(self):
869
        """Upload to radio"""
870
        try:
871
            _upload(self)
872
        except errors.RadioError:
873
            raise
874
        except Exception, e:
875
            raise errors.RadioError("Error: %s" % e)
876

    
877
    def set_options(self):
878
        """This is to read the options from the image and set it in the
879
        environment, for now just the limits of the freqs in the VHF/UHF
880
        ranges"""
881

    
882
        # setting the correct ranges for each radio type
883
        if self.MODEL in ["UV-2501+220", "KT8900R"]:
884
            # the model 2501+220 has a segment in 220
885
            # and a different position in the memmap
886
            # also the QYT KT8900R
887
            ranges = self._memobj.ranges220
888
        else:
889
            ranges = self._memobj.ranges
890

    
891
        # the normal dual bands
892
        vhf = _decode_ranges(ranges.vhf_low, ranges.vhf_high)
893
        uhf = _decode_ranges(ranges.uhf_low, ranges.uhf_high)
894

    
895
        # DEBUG
896
        LOG.info("Radio ranges: VHF %d to %d" % vhf)
897
        LOG.info("Radio ranges: UHF %d to %d" % uhf)
898

    
899
        # 220Mhz radios case
900
        if self.MODEL in ["UV-2501+220", "KT8900R"]:
901
            vhf2 = _decode_ranges(ranges.vhf2_low, ranges.vhf2_high)
902
            LOG.info("Radio ranges: VHF(220) %d to %d" % vhf2)
903
            self._220_range = vhf2
904

    
905
        # set the class with the real data
906
        self._vhf_range = vhf
907
        self._uhf_range = uhf
908

    
909
    def process_mmap(self):
910
        """Process the mem map into the mem object"""
911

    
912
        # Get it
913
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
914

    
915
        # load specific parameters from the radio image
916
        self.set_options()
917

    
918
    def get_raw_memory(self, number):
919
        return repr(self._memobj.memory[number])
920

    
921
    def _decode_tone(self, val):
922
        """Parse the tone data to decode from mem, it returns:
923
        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
924
        pol = None
925

    
926
        if val in [0, 65535]:
927
            return '', None, None
928
        elif val > 0x0258:
929
            a = val / 10.0
930
            return 'Tone', a, pol
931
        else:
932
            if val > 0x69:
933
                index = val - 0x6A
934
                pol = "R"
935
            else:
936
                index = val - 1
937
                pol = "N"
938

    
939
            tone = DTCS[index]
940
            return 'DTCS', tone, pol
941

    
942
    def _encode_tone(self, memval, mode, val, pol):
943
        """Parse the tone data to encode from UI to mem"""
944
        if mode == '' or mode is None:
945
            memval.set_raw("\x00\x00")
946
        elif mode == 'Tone':
947
            memval.set_value(val * 10)
948
        elif mode == 'DTCS':
949
            # detect the index in the DTCS list
950
            try:
951
                index = DTCS.index(val)
952
                if pol == "N":
953
                    index += 1
954
                else:
955
                    index += 0x6A
956
                memval.set_value(index)
957
            except:
958
                msg = "Digital Tone '%d' is not supported" % value
959
                LOG.error(msg)
960
                raise errors.RadioError(msg)
961
        else:
962
            msg = "Internal error: invalid mode '%s'" % mode
963
            LOG.error(msg)
964
            raise errors.InvalidDataError(msg)
965

    
966
    def get_memory(self, number):
967
        """Get the mem representation from the radio image"""
968
        _mem = self._memobj.memory[number]
969
        _names = self._memobj.names[number]
970

    
971
        # Create a high-level memory object to return to the UI
972
        mem = chirp_common.Memory()
973

    
974
        # Memory number
975
        mem.number = number
976

    
977
        if _mem.get_raw()[0] == "\xFF":
978
            mem.empty = True
979
            return mem
980

    
981
        # Freq and offset
982
        mem.freq = int(_mem.rxfreq) * 10
983
        # tx freq can be blank
984
        if _mem.get_raw()[4] == "\xFF":
985
            # TX freq not set
986
            mem.offset = 0
987
            mem.duplex = "off"
988
        else:
989
            # TX freq set
990
            offset = (int(_mem.txfreq) * 10) - mem.freq
991
            if offset != 0:
992
                if _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10):
993
                    mem.duplex = "split"
994
                    mem.offset = int(_mem.txfreq) * 10
995
                elif offset < 0:
996
                    mem.offset = abs(offset)
997
                    mem.duplex = "-"
998
                elif offset > 0:
999
                    mem.offset = offset
1000
                    mem.duplex = "+"
1001
            else:
1002
                mem.offset = 0
1003

    
1004
        # name TAG of the channel
1005
        mem.name = str(_names.name).rstrip("\xFF").replace("\xFF", " ")
1006

    
1007
        # power
1008
        mem.power = POWER_LEVELS[int(_mem.power)]
1009

    
1010
        # wide/narrow
1011
        mem.mode = MODES[int(_mem.wide)]
1012

    
1013
        # skip
1014
        mem.skip = SKIP_VALUES[_mem.add]
1015

    
1016
        # tone data
1017
        rxtone = txtone = None
1018
        txtone = self._decode_tone(_mem.txtone)
1019
        rxtone = self._decode_tone(_mem.rxtone)
1020
        chirp_common.split_tone_decode(mem, txtone, rxtone)
1021

    
1022
        # Extra
1023
        mem.extra = RadioSettingGroup("extra", "Extra")
1024

    
1025
        scramble = RadioSetting("scramble", "Scramble",
1026
                                RadioSettingValueBoolean(bool(_mem.scramble)))
1027
        mem.extra.append(scramble)
1028

    
1029
        bcl = RadioSetting("bcl", "Busy channel lockout",
1030
                           RadioSettingValueBoolean(bool(_mem.bcl)))
1031
        mem.extra.append(bcl)
1032

    
1033
        pttid = RadioSetting("pttid", "PTT ID",
1034
                             RadioSettingValueList(PTTID_LIST,
1035
                                                   PTTID_LIST[_mem.pttid]))
1036
        mem.extra.append(pttid)
1037

    
1038
        # validating scode
1039
        scode = _mem.scode if _mem.scode != 15 else 0
1040
        pttidcode = RadioSetting("scode", "PTT ID signal code",
1041
                                 RadioSettingValueList(
1042
                                     PTTIDCODE_LIST,
1043
                                     PTTIDCODE_LIST[scode]))
1044
        mem.extra.append(pttidcode)
1045

    
1046
        optsig = RadioSetting("optsig", "Optional signaling",
1047
                              RadioSettingValueList(
1048
                                  OPTSIG_LIST,
1049
                                  OPTSIG_LIST[_mem.optsig]))
1050
        mem.extra.append(optsig)
1051

    
1052
        spmute = RadioSetting("spmute", "Speaker mute",
1053
                              RadioSettingValueList(
1054
                                  SPMUTE_LIST,
1055
                                  SPMUTE_LIST[_mem.spmute]))
1056
        mem.extra.append(spmute)
1057

    
1058
        return mem
1059

    
1060
    def set_memory(self, mem):
1061
        """Set the memory data in the eeprom img from the UI"""
1062
        # get the eprom representation of this channel
1063
        _mem = self._memobj.memory[mem.number]
1064
        _names = self._memobj.names[mem.number]
1065

    
1066
        # if empty memmory
1067
        if mem.empty:
1068
            # the channel itself
1069
            _mem.set_raw("\xFF" * 16)
1070
            # the name tag
1071
            _names.set_raw("\xFF" * 16)
1072
            return
1073

    
1074
        # frequency
1075
        _mem.rxfreq = mem.freq / 10
1076

    
1077
        # duplex
1078
        if mem.duplex == "+":
1079
            _mem.txfreq = (mem.freq + mem.offset) / 10
1080
        elif mem.duplex == "-":
1081
            _mem.txfreq = (mem.freq - mem.offset) / 10
1082
        elif mem.duplex == "off":
1083
            for i in _mem.txfreq:
1084
                i.set_raw("\xFF")
1085
        elif mem.duplex == "split":
1086
            _mem.txfreq = mem.offset / 10
1087
        else:
1088
            _mem.txfreq = mem.freq / 10
1089

    
1090
        # tone data
1091
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
1092
            chirp_common.split_tone_encode(mem)
1093
        self._encode_tone(_mem.txtone, txmode, txtone, txpol)
1094
        self._encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
1095

    
1096
        # name TAG of the channel
1097
        if len(mem.name) < NAME_LENGTH:
1098
            # we must pad to NAME_LENGTH chars, " " = "\xFF"
1099
            mem.name = str(mem.name).ljust(NAME_LENGTH, " ")
1100
        _names.name = str(mem.name).replace(" ", "\xFF")
1101

    
1102
        # power, # default power level is high
1103
        _mem.power = 0 if mem.power is None else POWER_LEVELS.index(mem.power)
1104

    
1105
        # wide/narrow
1106
        _mem.wide = MODES.index(mem.mode)
1107

    
1108
        # scan add property
1109
        _mem.add = SKIP_VALUES.index(mem.skip)
1110

    
1111
        # reseting unknowns, this have to be set by hand
1112
        _mem.unknown0 = 0
1113
        _mem.unknown1 = 0
1114
        _mem.unknown2 = 0
1115
        _mem.unknown3 = 0
1116
        _mem.unknown4 = 0
1117
        _mem.unknown5 = 0
1118
        _mem.unknown6 = 0
1119

    
1120
        # extra settings
1121
        if len(mem.extra) > 0:
1122
            # there are setting, parse
1123
            for setting in mem.extra:
1124
                setattr(_mem, setting.get_name(), setting.value)
1125
        else:
1126
            # there is no extra settings, load defaults
1127
            _mem.spmute = 0
1128
            _mem.optsig = 0
1129
            _mem.scramble = 0
1130
            _mem.bcl = 0
1131
            _mem.pttid = 0
1132
            _mem.scode = 0
1133

    
1134
        return mem
1135

    
1136
    def get_settings(self):
1137
        """Translate the bit in the mem_struct into settings in the UI"""
1138
        _mem = self._memobj
1139
        basic = RadioSettingGroup("basic", "Basic Settings")
1140
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
1141
        other = RadioSettingGroup("other", "Other Settings")
1142
        work = RadioSettingGroup("work", "Work Mode Settings")
1143
        top = RadioSettings(basic, advanced, other, work)
1144

    
1145
        # Basic
1146
        tdr = RadioSetting("settings.tdr", "Transceiver dual receive",
1147
                           RadioSettingValueBoolean(_mem.settings.tdr))
1148
        basic.append(tdr)
1149

    
1150
        sql = RadioSetting("settings.sql", "Squelch level",
1151
                           RadioSettingValueInteger(0, 9, _mem.settings.sql))
1152
        basic.append(sql)
1153

    
1154
        tot = RadioSetting("settings.tot", "Time out timer",
1155
                           RadioSettingValueList(LIST_TOT, LIST_TOT[
1156
                               _mem.settings.tot]))
1157
        basic.append(tot)
1158

    
1159
        if self.MODEL in ("UV-2501", "UV-2501+220", "UV-5001"):
1160
            apo = RadioSetting("settings.apo", "Auto power off timer",
1161
                               RadioSettingValueList(LIST_APO, LIST_APO[
1162
                                   _mem.settings.apo]))
1163
            basic.append(apo)
1164
        else:
1165
            toa = RadioSetting("settings.apo", "Time out alert timer",
1166
                               RadioSettingValueList(LIST_TOA, LIST_TOA[
1167
                                   _mem.settings.apo]))
1168
            basic.append(toa)
1169

    
1170
        abr = RadioSetting("settings.abr", "Backlight timer",
1171
                           RadioSettingValueList(LIST_ABR, LIST_ABR[
1172
                               _mem.settings.abr]))
1173
        basic.append(abr)
1174

    
1175
        beep = RadioSetting("settings.beep", "Key beep",
1176
                            RadioSettingValueBoolean(_mem.settings.beep))
1177
        basic.append(beep)
1178

    
1179
        dtmfst = RadioSetting("settings.dtmfst", "DTMF side tone",
1180
                              RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
1181
                                  _mem.settings.dtmfst]))
1182
        basic.append(dtmfst)
1183

    
1184
        prisc = RadioSetting("settings.prisc", "Priority scan",
1185
                             RadioSettingValueBoolean(_mem.settings.prisc))
1186
        basic.append(prisc)
1187

    
1188
        prich = RadioSetting("settings.prich", "Priority channel",
1189
                             RadioSettingValueInteger(0, 199,
1190
                                 _mem.settings.prich))
1191
        basic.append(prich)
1192

    
1193
        screv = RadioSetting("settings.screv", "Scan resume method",
1194
                             RadioSettingValueList(LIST_SCREV, LIST_SCREV[
1195
                                 _mem.settings.screv]))
1196
        basic.append(screv)
1197

    
1198
        pttlt = RadioSetting("settings.pttlt", "PTT transmit delay",
1199
                             RadioSettingValueInteger(0, 30,
1200
                                 _mem.settings.pttlt))
1201
        basic.append(pttlt)
1202

    
1203
        emctp = RadioSetting("settings.emctp", "Alarm mode",
1204
                             RadioSettingValueList(LIST_EMCTP, LIST_EMCTP[
1205
                                 _mem.settings.emctp]))
1206
        basic.append(emctp)
1207

    
1208
        emcch = RadioSetting("settings.emcch", "Alarm channel",
1209
                             RadioSettingValueInteger(0, 199,
1210
                                 _mem.settings.emcch))
1211
        basic.append(emcch)
1212

    
1213
        ringt = RadioSetting("settings.ringt", "Ring time",
1214
                             RadioSettingValueList(LIST_RINGT, LIST_RINGT[
1215
                                 _mem.settings.ringt]))
1216
        basic.append(ringt)
1217

    
1218
        camdf = RadioSetting("settings.camdf", "Display mode A",
1219
                             RadioSettingValueList(LIST_MDF, LIST_MDF[
1220
                                 _mem.settings.camdf]))
1221
        basic.append(camdf)
1222

    
1223
        cbmdf = RadioSetting("settings.cbmdf", "Display mode B",
1224
                             RadioSettingValueList(LIST_MDF, LIST_MDF[
1225
                                 _mem.settings.cbmdf]))
1226
        basic.append(cbmdf)
1227

    
1228
        if self.MODEL in ("UV-2501", "UV-2501+220", "UV-5001"):
1229
           sync = RadioSetting("settings.sync", "A/B channel sync",
1230
                               RadioSettingValueBoolean(_mem.settings.sync))
1231
           basic.append(sync)
1232
        else:
1233
           autolk = RadioSetting("settings.sync", "Auto keylock",
1234
                                 RadioSettingValueBoolean(_mem.settings.sync))
1235
           basic.append(autolk)
1236

    
1237
        ponmsg = RadioSetting("settings.ponmsg", "Power-on message",
1238
                              RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
1239
                                  _mem.settings.ponmsg]))
1240
        basic.append(ponmsg)
1241

    
1242
        wtled = RadioSetting("settings.wtled", "Standby backlight Color",
1243
                             RadioSettingValueList(LIST_COLOR, LIST_COLOR[
1244
                                 _mem.settings.wtled]))
1245
        basic.append(wtled)
1246

    
1247
        rxled = RadioSetting("settings.rxled", "RX backlight Color",
1248
                             RadioSettingValueList(LIST_COLOR, LIST_COLOR[
1249
                                 _mem.settings.rxled]))
1250
        basic.append(rxled)
1251

    
1252
        txled = RadioSetting("settings.txled", "TX backlight Color",
1253
                             RadioSettingValueList(LIST_COLOR, LIST_COLOR[
1254
                                 _mem.settings.txled]))
1255
        basic.append(txled)
1256

    
1257
        anil = RadioSetting("settings.anil", "ANI length",
1258
                            RadioSettingValueList(LIST_ANIL, LIST_ANIL[
1259
                                _mem.settings.anil]))
1260
        basic.append(anil)
1261

    
1262
        reps = RadioSetting("settings.reps", "Relay signal (tone burst)",
1263
                            RadioSettingValueList(LIST_REPS, LIST_REPS[
1264
                                _mem.settings.reps]))
1265
        basic.append(reps)
1266

    
1267
        repm = RadioSetting("settings.repm", "Relay condition",
1268
                            RadioSettingValueList(LIST_REPM, LIST_REPM[
1269
                                _mem.settings.repm]))
1270
        basic.append(repm)
1271

    
1272
        if self.MODEL in ("UV-2501", "UV-2501+220", "UV-5001"):
1273
            tdrab = RadioSetting("settings.tdrab", "TDR return time",
1274
                                 RadioSettingValueList(LIST_ABR, LIST_ABR[
1275
                                     _mem.settings.tdrab]))
1276
            basic.append(tdrab)
1277

    
1278
            ste = RadioSetting("settings.ste", "Squelch tail eliminate",
1279
                               RadioSettingValueBoolean(_mem.settings.ste))
1280
            basic.append(ste)
1281

    
1282
            rpste = RadioSetting("settings.rpste", "Repeater STE",
1283
                                 RadioSettingValueList(LIST_RINGT, LIST_RINGT[
1284
                                     _mem.settings.rpste]))
1285
            basic.append(rpste)
1286

    
1287
            rptdl = RadioSetting("settings.rptdl", "Repeater STE delay",
1288
                                 RadioSettingValueList(LIST_RPTDL, LIST_RPTDL[
1289
                                     _mem.settings.rptdl]))
1290
            basic.append(rptdl)
1291

    
1292
        if str(_mem.fingerprint.fp) in BTECH3:
1293

    
1294
            mgain = RadioSetting("settings.mgain", "Mic gain",
1295
                                 RadioSettingValueInteger(0, 120,
1296
                                     _mem.settings.mgain))
1297
            basic.append(mgain)
1298

    
1299
            dtmfg = RadioSetting("settings.dtmfg", "DTMF gain",
1300
                                 RadioSettingValueInteger(0, 60,
1301
                                     _mem.settings.dtmfg))
1302
            basic.append(dtmfg)
1303

    
1304
        # Advanced
1305
        def _filter(name):
1306
            filtered = ""
1307
            for char in str(name):
1308
                if char in VALID_CHARS:
1309
                    filtered += char
1310
                else:
1311
                    filtered += " "
1312
            return filtered
1313

    
1314
        _msg = self._memobj.poweron_msg
1315
        line1 = RadioSetting("poweron_msg.line1", "Power-on message line 1",
1316
                             RadioSettingValueString(0, 6, _filter(
1317
                                 _msg.line1)))
1318
        advanced.append(line1)
1319
        line2 = RadioSetting("poweron_msg.line2", "Power-on message line 2",
1320
                             RadioSettingValueString(0, 6, _filter(
1321
                                 _msg.line2)))
1322
        advanced.append(line2)
1323

    
1324
        if self.MODEL in ("UV-2501", "UV-5001"):
1325
            vfomren = RadioSetting("settings2.vfomren", "VFO/MR switching",
1326
                                   RadioSettingValueBoolean(
1327
                                       not _mem.settings2.vfomren))
1328
            advanced.append(vfomren)
1329

    
1330
            reseten = RadioSetting("settings2.reseten", "RESET",
1331
                                   RadioSettingValueBoolean(
1332
                                       _mem.settings2.reseten))
1333
            advanced.append(reseten)
1334

    
1335
            menuen = RadioSetting("settings2.menuen", "Menu",
1336
                                  RadioSettingValueBoolean(
1337
                                      _mem.settings2.menuen))
1338
            advanced.append(menuen)
1339

    
1340
        # Other
1341
        def convert_bytes_to_limit(bytes):
1342
            limit = ""
1343
            for byte in bytes:
1344
                if byte < 10:
1345
                    limit += chr(byte + 0x30)
1346
                else:
1347
                    break
1348
            return limit
1349

    
1350
        if self.MODEL in ["UV-2501+220", "KT8900R"]:
1351
            _ranges = self._memobj.ranges220
1352
            ranges = "ranges220"
1353
        else:
1354
            _ranges = self._memobj.ranges
1355
            ranges = "ranges"
1356

    
1357
        _limit = convert_bytes_to_limit(_ranges.vhf_low)
1358
        val = RadioSettingValueString(0, 3, _limit)
1359
        val.set_mutable(False)
1360
        vhf_low = RadioSetting("%s.vhf_low" % ranges, "VHF low", val)
1361
        other.append(vhf_low)
1362

    
1363
        _limit = convert_bytes_to_limit(_ranges.vhf_high)
1364
        val = RadioSettingValueString(0, 3, _limit)
1365
        val.set_mutable(False)
1366
        vhf_high = RadioSetting("%s.vhf_high" % ranges, "VHF high", val)
1367
        other.append(vhf_high)
1368

    
1369
        if self.MODEL in ["UV-2501+220", "KT8900R"]:
1370
            _limit = convert_bytes_to_limit(_ranges.vhf2_low)
1371
            val = RadioSettingValueString(0, 3, _limit)
1372
            val.set_mutable(False)
1373
            vhf2_low = RadioSetting("%s.vhf2_low" % ranges, "VHF2 low", val)
1374
            other.append(vhf2_low)
1375

    
1376
            _limit = convert_bytes_to_limit(_ranges.vhf2_high)
1377
            val = RadioSettingValueString(0, 3, _limit)
1378
            val.set_mutable(False)
1379
            vhf2_high = RadioSetting("%s.vhf2_high" % ranges, "VHF2 high", val)
1380
            other.append(vhf2_high)
1381

    
1382
        _limit = convert_bytes_to_limit(_ranges.uhf_low)
1383
        val = RadioSettingValueString(0, 3, _limit)
1384
        val.set_mutable(False)
1385
        uhf_low = RadioSetting("%s.uhf_low" % ranges, "UHF low", val)
1386
        other.append(uhf_low)
1387

    
1388
        _limit = convert_bytes_to_limit(_ranges.uhf_high)
1389
        val = RadioSettingValueString(0, 3, _limit)
1390
        val.set_mutable(False)
1391
        uhf_high = RadioSetting("%s.uhf_high" % ranges, "UHF high", val)
1392
        other.append(uhf_high)
1393

    
1394
        val = RadioSettingValueString(0, 6, _filter(_mem.fingerprint.fp))
1395
        val.set_mutable(False)
1396
        fp = RadioSetting("fingerprint.fp", "Fingerprint", val)
1397
        other.append(fp)
1398

    
1399
        # Work
1400
        dispab = RadioSetting("settings2.dispab", "Display",
1401
                              RadioSettingValueList(LIST_AB,LIST_AB[
1402
                                  _mem.settings2.dispab]))
1403
        work.append(dispab)
1404

    
1405
        vfomr = RadioSetting("settings2.vfomr", "VFO/MR mode",
1406
                             RadioSettingValueList(LIST_VFOMR,LIST_VFOMR[
1407
                                 _mem.settings2.vfomr]))
1408
        work.append(vfomr)
1409

    
1410
        keylock = RadioSetting("settings2.keylock", "Keypad lock",
1411
                           RadioSettingValueBoolean(_mem.settings2.keylock))
1412
        work.append(keylock)
1413

    
1414
        mrcha = RadioSetting("settings2.mrcha", "MR A channel",
1415
                             RadioSettingValueInteger(0, 199,
1416
                                 _mem.settings2.mrcha))
1417
        work.append(mrcha)
1418

    
1419
        mrchb = RadioSetting("settings2.mrchb", "MR B channel",
1420
                             RadioSettingValueInteger(0, 199,
1421
                                 _mem.settings2.mrchb))
1422
        work.append(mrchb)
1423

    
1424
        def convert_bytes_to_freq(bytes):
1425
            real_freq = 0
1426
            for byte in bytes:
1427
                real_freq = (real_freq * 10) + byte
1428
            return chirp_common.format_freq(real_freq * 10)
1429

    
1430
        def my_validate(value):
1431
            value = chirp_common.parse_freq(value)
1432
            if "+220" in self.MODEL:
1433
                if 180000000 <= value and value < 210000000:
1434
                    msg = ("Can't be between 180.00000-210.00000")
1435
                    raise InvalidValueError(msg)
1436
                elif 231000000 <= value and value < 400000000:
1437
                    msg = ("Can't be between 231.00000-400.00000")
1438
                    raise InvalidValueError(msg)
1439
            elif "8900R" in self.MODEL:
1440
                if 180000000 <= value and value < 240000000:
1441
                    msg = ("Can't be between 180.00000-240.00000")
1442
                    raise InvalidValueError(msg)
1443
                elif 271000000 <= value and value < 400000000:
1444
                    msg = ("Can't be between 271.00000-400.00000")
1445
                    raise InvalidValueError(msg)
1446
            elif 180000000 <= value and value < 400000000:
1447
                msg = ("Can't be between 180.00000-400.00000")
1448
                raise InvalidValueError(msg)
1449
            return chirp_common.format_freq(value)
1450

    
1451
        def apply_freq(setting, obj):
1452
            value = chirp_common.parse_freq(str(setting.value)) / 10
1453
            for i in range(7, -1, -1):
1454
                obj.freq[i] = value % 10
1455
                value /= 10
1456

    
1457
        val1a = RadioSettingValueString(0, 10, convert_bytes_to_freq(
1458
                                        _mem.vfo.a.freq))
1459
        val1a.set_validate_callback(my_validate)
1460
        vfoafreq = RadioSetting("vfo.a.freq", "VFO A frequency", val1a)
1461
        vfoafreq.set_apply_callback(apply_freq, _mem.vfo.a)
1462
        work.append(vfoafreq)
1463

    
1464
        val1b = RadioSettingValueString(0, 10, convert_bytes_to_freq(
1465
                                        _mem.vfo.b.freq))
1466
        val1b.set_validate_callback(my_validate)
1467
        vfobfreq = RadioSetting("vfo.b.freq", "VFO B frequency", val1b)
1468
        vfobfreq.set_apply_callback(apply_freq, _mem.vfo.b)
1469
        work.append(vfobfreq)
1470

    
1471
        vfoashiftd = RadioSetting("vfo.a.shiftd", "VFO A shift",
1472
                                  RadioSettingValueList(LIST_SHIFT, LIST_SHIFT[
1473
                                      _mem.vfo.a.shiftd]))
1474
        work.append(vfoashiftd)
1475

    
1476
        vfobshiftd = RadioSetting("vfo.b.shiftd", "VFO B shift",
1477
                                  RadioSettingValueList(LIST_SHIFT, LIST_SHIFT[
1478
                                      _mem.vfo.b.shiftd]))
1479
        work.append(vfobshiftd)
1480

    
1481
        def convert_bytes_to_offset(bytes):
1482
            real_offset = 0
1483
            for byte in bytes:
1484
                real_offset = (real_offset * 10) + byte
1485
            return chirp_common.format_freq(real_offset * 10000)
1486

    
1487
        def apply_offset(setting, obj):
1488
            value = chirp_common.parse_freq(str(setting.value)) / 10000
1489
            for i in range(3, -1, -1):
1490
                obj.offset[i] = value % 10
1491
                value /= 10
1492

    
1493
        val1a = RadioSettingValueString(0, 10, convert_bytes_to_offset(
1494
                                        _mem.vfo.a.offset))
1495
        vfoaoffset = RadioSetting("vfo.a.offset",
1496
                                  "VFO A offset (0.00-99.95)", val1a)
1497
        vfoaoffset.set_apply_callback(apply_offset, _mem.vfo.a)
1498
        work.append(vfoaoffset)
1499

    
1500
        val1b = RadioSettingValueString(0, 10, convert_bytes_to_offset(
1501
                                        _mem.vfo.b.offset))
1502
        vfoboffset = RadioSetting("vfo.b.offset",
1503
                                  "VFO B offset (0.00-99.95)", val1b)
1504
        vfoboffset.set_apply_callback(apply_offset, _mem.vfo.b)
1505
        work.append(vfoboffset)
1506

    
1507
        vfoatxp = RadioSetting("vfo.a.power", "VFO A power",
1508
                                RadioSettingValueList(LIST_TXP,LIST_TXP[
1509
                                    _mem.vfo.a.power]))
1510
        work.append(vfoatxp)
1511

    
1512
        vfobtxp = RadioSetting("vfo.b.power", "VFO B power",
1513
                                RadioSettingValueList(LIST_TXP,LIST_TXP[
1514
                                    _mem.vfo.b.power]))
1515
        work.append(vfobtxp)
1516

    
1517
        vfoawide = RadioSetting("vfo.a.wide", "VFO A bandwidth",
1518
                                RadioSettingValueList(LIST_WIDE,LIST_WIDE[
1519
                                    _mem.vfo.a.wide]))
1520
        work.append(vfoawide)
1521

    
1522
        vfobwide = RadioSetting("vfo.b.wide", "VFO B bandwidth",
1523
                                RadioSettingValueList(LIST_WIDE,LIST_WIDE[
1524
                                    _mem.vfo.b.wide]))
1525
        work.append(vfobwide)
1526

    
1527
        vfoastep = RadioSetting("vfo.a.step", "VFO A step",
1528
                                RadioSettingValueList(LIST_STEP,LIST_STEP[
1529
                                    _mem.vfo.a.step]))
1530
        work.append(vfoastep)
1531

    
1532
        vfobstep = RadioSetting("vfo.b.step", "VFO B step",
1533
                                RadioSettingValueList(LIST_STEP,LIST_STEP[
1534
                                    _mem.vfo.b.step]))
1535
        work.append(vfobstep)
1536

    
1537
        vfoaoptsig = RadioSetting("vfo.a.optsig", "VFO A optional signal",
1538
                                  RadioSettingValueList(OPTSIG_LIST,
1539
                                      OPTSIG_LIST[_mem.vfo.a.optsig]))
1540
        work.append(vfoaoptsig)
1541

    
1542
        vfoboptsig = RadioSetting("vfo.b.optsig", "VFO B optional signal",
1543
                                  RadioSettingValueList(OPTSIG_LIST,
1544
                                      OPTSIG_LIST[_mem.vfo.b.optsig]))
1545
        work.append(vfoboptsig)
1546

    
1547
        vfoaspmute = RadioSetting("vfo.a.spmute", "VFO A speaker mute",
1548
                                  RadioSettingValueList(SPMUTE_LIST,
1549
                                      SPMUTE_LIST[_mem.vfo.a.spmute]))
1550
        work.append(vfoaspmute)
1551

    
1552
        vfobspmute = RadioSetting("vfo.b.spmute", "VFO B speaker mute",
1553
                                  RadioSettingValueList(SPMUTE_LIST,
1554
                                      SPMUTE_LIST[_mem.vfo.b.spmute]))
1555
        work.append(vfobspmute)
1556

    
1557
        vfoascr = RadioSetting("vfo.a.scramble", "VFO A scramble",
1558
                               RadioSettingValueBoolean(_mem.vfo.a.scramble))
1559
        work.append(vfoascr)
1560

    
1561
        vfobscr = RadioSetting("vfo.b.scramble", "VFO B scramble",
1562
                               RadioSettingValueBoolean(_mem.vfo.b.scramble))
1563
        work.append(vfobscr)
1564

    
1565
        vfoascode = RadioSetting("vfo.a.scode", "VFO A PTT-ID",
1566
                                 RadioSettingValueList(PTTIDCODE_LIST,
1567
                                     PTTIDCODE_LIST[_mem.vfo.a.scode]))
1568
        work.append(vfoascode)
1569

    
1570
        vfobscode = RadioSetting("vfo.b.scode", "VFO B PTT-ID",
1571
                                 RadioSettingValueList(PTTIDCODE_LIST,
1572
                                     PTTIDCODE_LIST[_mem.vfo.b.scode]))
1573
        work.append(vfobscode)
1574

    
1575
        pttid = RadioSetting("settings.pttid", "PTT ID",
1576
                             RadioSettingValueList(PTTID_LIST,
1577
                                 PTTID_LIST[_mem.settings.pttid]))
1578
        work.append(pttid)
1579

    
1580
        return top
1581

    
1582
    def set_settings(self, settings):
1583
        _settings = self._memobj.settings
1584
        for element in settings:
1585
            if not isinstance(element, RadioSetting):
1586
                if element.get_name() == "fm_preset":
1587
                    self._set_fm_preset(element)
1588
                else:
1589
                    self.set_settings(element)
1590
                    continue
1591
            else:
1592
                try:
1593
                    name = element.get_name()
1594
                    if "." in name:
1595
                        bits = name.split(".")
1596
                        obj = self._memobj
1597
                        for bit in bits[:-1]:
1598
                            if "/" in bit:
1599
                                bit, index = bit.split("/", 1)
1600
                                index = int(index)
1601
                                obj = getattr(obj, bit)[index]
1602
                            else:
1603
                                obj = getattr(obj, bit)
1604
                        setting = bits[-1]
1605
                    else:
1606
                        obj = _settings
1607
                        setting = element.get_name()
1608

    
1609
                    if element.has_apply_callback():
1610
                        LOG.debug("Using apply callback")
1611
                        element.run_apply_callback()
1612
                    elif setting == "vfomren":
1613
                        setattr(obj, setting, not int(element.value))
1614
                    elif element.value.get_mutable():
1615
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1616
                        setattr(obj, setting, element.value)
1617
                except Exception, e:
1618
                    LOG.debug(element.get_name())
1619
                    raise
1620

    
1621
    @classmethod
1622
    def match_model(cls, filedata, filename):
1623
        match_size = False
1624
        match_model = False
1625

    
1626
        # testing the file data size
1627
        if len(filedata) == MEM_SIZE:
1628
            match_size = True
1629

    
1630
        # testing the firmware model fingerprint
1631
        match_model = model_match(cls, filedata)
1632

    
1633
        if match_size and match_model:
1634
            return True
1635
        else:
1636
            return False
1637

    
1638

    
1639
# Declaring Aliases (Clones of the real radios)
1640
class JT2705M(chirp_common.Alias):
1641
    VENDOR = "Jetstream"
1642
    MODEL = "JT2705M"
1643

    
1644

    
1645
class JT6188Mini(chirp_common.Alias):
1646
    VENDOR = "Juentai"
1647
    MODEL = "JT-6188 Mini"
1648

    
1649

    
1650
class JT6188Plus(chirp_common.Alias):
1651
    VENDOR = "Juentai"
1652
    MODEL = "JT-6188 Plus"
1653

    
1654

    
1655
class SSGT890(chirp_common.Alias):
1656
    VENDOR = "Sainsonic"
1657
    MODEL = "GT-890"
1658

    
1659

    
1660
class ZastoneMP300(chirp_common.Alias):
1661
    VENDOR = "Zastone"
1662
    MODEL = "MP-300"
1663

    
1664

    
1665
# real radios
1666
@directory.register
1667
class UV2501(BTech):
1668
    """Baofeng Tech UV2501"""
1669
    MODEL = "UV-2501"
1670
    _fileid = [UV2501G3_fp,
1671
               UV2501G2_fp,
1672
               UV2501pp2_fp,
1673
               UV2501pp_fp]
1674

    
1675

    
1676
@directory.register
1677
class UV2501_220(BTech):
1678
    """Baofeng Tech UV2501+220"""
1679
    MODEL = "UV-2501+220"
1680
    _magic = MSTRING_220
1681
    _id2 = UV2501_220pp_id
1682
    _fileid = [UV2501_220G3_fp,
1683
               UV2501_220G2_fp,
1684
               UV2501_220_fp,
1685
               UV2501_220pp_fp]
1686

    
1687

    
1688
@directory.register
1689
class UV5001(BTech):
1690
    """Baofeng Tech UV5001"""
1691
    MODEL = "UV-5001"
1692
    _fileid = [UV5001G3_fp,
1693
               UV5001G22_fp,
1694
               UV5001G2_fp,
1695
               UV5001alpha_fp,
1696
               UV5001pp_fp]
1697

    
1698

    
1699
@directory.register
1700
class MINI8900(BTech):
1701
    """WACCOM MINI-8900"""
1702
    VENDOR = "WACCOM"
1703
    MODEL = "MINI-8900"
1704
    _magic = MSTRING_MINI8900
1705
    _fileid = [MINI8900_fp, ]
1706
    # Clones
1707
    ALIASES = [JT6188Plus, ]
1708

    
1709

    
1710
@directory.register
1711
class KTUV980(BTech):
1712
    """QYT KT-UV980"""
1713
    VENDOR = "QYT"
1714
    MODEL = "KT-UV980"
1715
    _vhf_range = (136000000, 175000000)
1716
    _uhf_range = (400000000, 481000000)
1717
    _magic = MSTRING_MINI8900
1718
    _fileid = [KTUV980_fp, ]
1719
    # Clones
1720
    ALIASES = [JT2705M, ]
1721

    
1722
# Please note that there is a version of this radios that is a clone of the
1723
# Waccom Mini8900, maybe an early version?
1724
@directory.register
1725
class KT9800(BTech):
1726
    """QYT KT8900"""
1727
    VENDOR = "QYT"
1728
    MODEL = "KT8900"
1729
    _vhf_range = (136000000, 175000000)
1730
    _uhf_range = (400000000, 481000000)
1731
    _magic = MSTRING_KT8900
1732
    _fileid = [KT8900_fp,
1733
               KT8900_fp1,
1734
               KT8900_fp2,
1735
               KT8900_fp3,
1736
               KT8900_fp4]
1737
    _id2 = KT8900_id
1738
    # Clones
1739
    ALIASES = [JT6188Mini, SSGT890, ZastoneMP300]
1740

    
1741

    
1742
@directory.register
1743
class KT9800R(BTech):
1744
    """QYT KT8900R"""
1745
    VENDOR = "QYT"
1746
    MODEL = "KT8900R"
1747
    _vhf_range = (136000000, 175000000)
1748
    _220_range = (240000000, 271000000)
1749
    _uhf_range = (400000000, 481000000)
1750
    _magic = MSTRING_KT8900R
1751
    _fileid = [KT8900R_fp,
1752
               KT8900R_fp1,
1753
               KT8900R_fp2,
1754
               KT8900R_fp3]
1755
    _id2 = KT8900R_id
1756

    
1757

    
1758
@directory.register
1759
class LT588UV(BTech):
1760
    """LUITON LT-588UV"""
1761
    VENDOR = "LUITON"
1762
    MODEL = "LT-588UV"
1763
    _vhf_range = (136000000, 175000000)
1764
    _uhf_range = (400000000, 481000000)
1765
    _magic = MSTRING_KT8900
1766
    _fileid = [LT588UV_fp, ]
(13-13/13)