Project

General

Profile

Bug #3993 » btech.py

Dev version with a potential fix for the troubles of reading in Linux. - Pavel Milanes, 09/12/2016 08:59 AM

 
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 chirp import chirp_common, directory, memmap
25
from chirp import bitwise, errors, util
26
from chirp.settings import RadioSettingGroup, RadioSetting, \
27
    RadioSettingValueBoolean, RadioSettingValueList, \
28
    RadioSettingValueString, RadioSettingValueInteger, \
29
    RadioSettings, InvalidValueError
30
from textwrap import dedent
31

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

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

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

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

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

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

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

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

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

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

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

    
186
"""
187

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

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

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

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

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

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

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

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

    
254

    
255
##### ID strings #####################################################
256

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

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

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

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

    
292

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

    
296

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

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

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

    
321

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

    
325

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

    
337

    
338
def _clean_buffer(radio):
339
    """Cleaning the read serial buffer, hard timeout to survive an infinite
340
    data stream"""
341

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

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

    
358
        # restore the default serial timeout
359
        radio.pipe.timeout = STIMEOUT
360

    
361
    except Exception:
362
        raise errors.RadioError("Unknown error cleaning the serial buffer")
363

    
364

    
365
def _rawrecv(radio, amount):
366
    """Raw read from the radio device, less intensive way"""
367

    
368
    data = ""
369

    
370
    try:
371
        data = radio.pipe.read(amount)
372

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

    
378
        # fail if no data is received
379
        if len(data) == 0:
380
            raise errors.RadioError("No data received from radio")
381

    
382
        # notice on the logs if short
383
        if len(data) < amount:
384
            LOG.warn("Short reading %d bytes from the %d requested." %
385
                     (len(data), amount))
386

    
387
    except:
388
        raise errors.RadioError("Error reading data from radio")
389

    
390
    return data
391

    
392

    
393
def _send(radio, data):
394
    """Send data to the radio device"""
395

    
396
    try:
397
        for byte in data:
398
            radio.pipe.write(byte)
399
            # linux is to fast in the serial, causing the radio send a \x05
400
            # byte and stop responding, we need to slow it down a bit.
401
            # Thanks to Michael Wagner for this fix.
402
            # we have not noticed any problem in MAC (YET)
403
            if sys.platform in ["linux"]:
404
                # 10 msec is proved to be safe.
405
                sleep(0.01)
406

    
407
        # DEBUG
408
        if debug is True:
409
            LOG.debug("==> (%d) bytes:\n\n%s" %
410
                      (len(data), util.hexprint(data)))
411
    except:
412
        raise errors.RadioError("Error sending data to radio")
413

    
414

    
415
def _make_frame(cmd, addr, length, data=""):
416
    """Pack the info in the headder format"""
417
    frame = "\x06" + struct.pack(">BHB", ord(cmd), addr, length)
418
    # add the data if set
419
    if len(data) != 0:
420
        frame += data
421

    
422
    return frame
423

    
424

    
425
def _recv(radio, addr):
426
    """Get data from the radio all at once to lower syscalls load"""
427

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

    
431
    # get the whole block
432
    block = _rawrecv(radio, BLOCK_SIZE + 5)
433

    
434
    # basic check
435
    if len(block) < (BLOCK_SIZE + 5):
436
        raise errors.RadioError("Short read of the block 0x%04x" % addr)
437

    
438
    # checking for the ack
439
    if block[0] != ACK_CMD:
440
        raise errors.RadioError("Bad ack from radio in block 0x%04x" % addr)
441

    
442
    # header validation
443
    c, a, l = struct.unpack(">BHB", block[1:5])
444
    if a != addr or l != BLOCK_SIZE or c != ord("X"):
445
        LOG.debug("Invalid header for block 0x%04x" % addr)
446
        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
447
        raise errors.RadioError("Invalid header for block 0x%04x:" % addr)
448

    
449
    # return the data
450
    return block[5:]
451

    
452

    
453
def _start_clone_mode(radio, status):
454
    """Put the radio in clone mode and get the ident string, 3 tries"""
455

    
456
    # cleaning the serial buffer
457
    _clean_buffer(radio)
458

    
459
    # prep the data to show in the UI
460
    status.cur = 0
461
    status.msg = "Identifying the radio..."
462
    status.max = 3
463
    radio.status_fn(status)
464

    
465
    try:
466
        for a in range(0, status.max):
467
            # Update the UI
468
            status.cur = a + 1
469
            radio.status_fn(status)
470

    
471
            # send the magic word
472
            _send(radio, radio._magic)
473

    
474
            # Now you get a x06 of ACK if all goes well
475
            ack = radio.pipe.read(1)
476

    
477
            if ack == "\x06":
478
                # DEBUG
479
                LOG.info("Magic ACK received")
480
                status.cur = status.max
481
                radio.status_fn(status)
482

    
483
                return True
484

    
485
        return False
486

    
487
    except errors.RadioError:
488
        raise
489
    except Exception, e:
490
        raise errors.RadioError("Error sending Magic to radio:\n%s" % e)
491

    
492

    
493
def _do_ident(radio, status, upload=False):
494
    """Put the radio in PROGRAM mode & identify it"""
495
    #  set the serial discipline
496
    radio.pipe.baudrate = 9600
497
    radio.pipe.parity = "N"
498

    
499
    # open the radio into program mode
500
    if _start_clone_mode(radio, status) is False:
501
        msg = "Radio did not enter clone mode"
502
        # warning about old versions of QYT KT8900
503
        if radio.MODEL == "KT8900":
504
            msg += ". You may want to try it as a WACCOM MINI-8900, there is a"
505
            msg += " known variant of this radios that is a clone of it."
506
        raise errors.RadioError(msg)
507

    
508
    # Ok, get the ident string
509
    ident = _rawrecv(radio, 49)
510

    
511
    # basic check for the ident
512
    if len(ident) != 49:
513
        raise errors.RadioError("Radio send a short ident block.")
514

    
515
    # check if ident is OK
516
    itis = False
517
    for fp in radio._fileid:
518
        if fp in ident:
519
            # got it!
520
            itis = True
521
            # checking if we are dealing with a Gen 3 BTECH
522
            if radio.VENDOR == "BTECH" and fp in BTECH3:
523
                radio.btech3 = True
524

    
525
            break
526

    
527
    if itis is False:
528
        LOG.debug("Incorrect model ID, got this:\n\n" + util.hexprint(ident))
529
        raise errors.RadioError("Radio identification failed.")
530

    
531
    # some radios needs a extra read and check for a code on it, this ones
532
    # has the check value in the _id2 var, others simply False
533
    if radio._id2 is not False:
534
        # lower the timeout here as this radios are reseting due to timeout
535
        radio.pipe.timeout = 0.05
536

    
537
        # query & receive the extra ID
538
        _send(radio, _make_frame("S", 0x3DF0, 16))
539
        id2 = _rawrecv(radio, 21)
540

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

    
548
        # ok, the correct string must be in the received data
549
        if radio._id2 not in id2:
550
            LOG.debug("Full *BAD* extra ID on the %s is: \n%s" %
551
                      (radio.MODEL, util.hexprint(id2)))
552
            raise errors.RadioError("The extra ID is wrong, aborting.")
553

    
554
        # this radios need a extra request/answer here on the upload
555
        # the amount of data received depends of the radio type
556
        #
557
        # also the first block of TX must no have the ACK at the beginning
558
        # see _upload for this.
559
        if upload is True:
560
            # send an ACK
561
            _send(radio, ACK_CMD)
562

    
563
            # the amount of data depend on the radio, so far we have two radios
564
            # reading two bytes with an ACK at the end and just ONE with just
565
            # one byte (QYT KT8900)
566
            # the JT-6188 appears a clone of the last, but reads TWO bytes.
567
            #
568
            # we will read two bytes with a custom timeout to not penalize the
569
            # users for this.
570
            #
571
            # we just check for a response and last byte being a ACK, that is
572
            # the common stone for all radios (3 so far)
573
            ack = _rawrecv(radio, 2)
574

    
575
            # checking
576
            if len(ack) == 0 or ack[-1:] != ACK_CMD:
577
                raise errors.RadioError("Radio didn't ACK the upload")
578

    
579
            # restore the default serial timeout
580
            radio.pipe.timeout = STIMEOUT
581

    
582
    # DEBUG
583
    LOG.info("Positive ident, this is a %s %s" % (radio.VENDOR, radio.MODEL))
584

    
585
    return True
586

    
587

    
588
def _download(radio):
589
    """Get the memory map"""
590

    
591
    # UI progress
592
    status = chirp_common.Status()
593

    
594
    # put radio in program mode and identify it
595
    _do_ident(radio, status)
596

    
597
    # the models that doesn't have the extra ID have to make a dummy read here
598
    if radio._id2 is False:
599
        _send(radio, _make_frame("S", 0, BLOCK_SIZE))
600
        discard = _rawrecv(radio, BLOCK_SIZE + 5)
601

    
602
        if debug is True:
603
            LOG.info("Dummy first block read done, got this:\n\n %s",
604
                     util.hexprint(discard))
605

    
606
    # reset the progress bar in the UI
607
    status.max = MEM_SIZE / BLOCK_SIZE
608
    status.msg = "Cloning from radio..."
609
    status.cur = 0
610
    radio.status_fn(status)
611

    
612
    # cleaning the serial buffer
613
    _clean_buffer(radio)
614

    
615
    data = ""
616
    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
617
        # sending the read request
618
        _send(radio, _make_frame("S", addr, BLOCK_SIZE))
619

    
620
        # read
621
        d = _recv(radio, addr)
622

    
623
        # aggregate the data
624
        data += d
625

    
626
        # UI Update
627
        status.cur = addr / BLOCK_SIZE
628
        status.msg = "Cloning from radio..."
629
        radio.status_fn(status)
630

    
631
    return data
632

    
633

    
634
def _upload(radio):
635
    """Upload procedure"""
636

    
637
    # The UPLOAD mem is restricted to lower than 0x3100,
638
    # so we will overide that here localy
639
    MEM_SIZE = 0x3100
640

    
641
    # UI progress
642
    status = chirp_common.Status()
643

    
644
    # put radio in program mode and identify it
645
    _do_ident(radio, status, True)
646

    
647
    # get the data to upload to radio
648
    data = radio.get_mmap()
649

    
650
    # Reset the UI progress
651
    status.max = MEM_SIZE / TX_BLOCK_SIZE
652
    status.cur = 0
653
    status.msg = "Cloning to radio..."
654
    radio.status_fn(status)
655

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

    
660
    # cleaning the serial buffer
661
    _clean_buffer(radio)
662

    
663
    # the fun start here
664
    for addr in range(0, MEM_SIZE, TX_BLOCK_SIZE):
665
        # getting the block of data to send
666
        d = data[addr:addr + TX_BLOCK_SIZE]
667

    
668
        # build the frame to send
669
        frame = _make_frame("X", addr, TX_BLOCK_SIZE, d)
670

    
671
        # first block must not send the ACK at the beginning for the
672
        # ones that has the extra id, since this have to do a extra step
673
        if addr == 0 and radio._id2 is not False:
674
            frame = frame[1:]
675

    
676
        # send the frame
677
        _send(radio, frame)
678

    
679
        # receiving the response
680
        ack = _rawrecv(radio, 1)
681

    
682
        # basic check
683
        if len(ack) != 1:
684
            raise errors.RadioError("No ACK when writing block 0x%04x" % addr)
685

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

    
689
         # UI Update
690
        status.cur = addr / TX_BLOCK_SIZE
691
        status.msg = "Cloning to radio..."
692
        radio.status_fn(status)
693

    
694

    
695
def model_match(cls, data):
696
    """Match the opened/downloaded image to the correct version"""
697
    rid = data[0x3f70:0x3f76]
698

    
699
    if rid in cls._fileid:
700
        return True
701

    
702
    return False
703

    
704

    
705
def _decode_ranges(low, high):
706
    """Unpack the data in the ranges zones in the memmap and return
707
    a tuple with the integer corresponding to the Mhz it means"""
708
    ilow = int(low[0]) * 100 + int(low[1]) * 10 + int(low[2])
709
    ihigh = int(high[0]) * 100 + int(high[1]) * 10 + int(high[2])
710
    ilow *= 1000000
711
    ihigh *= 1000000
712

    
713
    return (ilow, ihigh)
714

    
715

    
716
def _split(rf, f1, f2):
717
    """Returns False if the two freqs are in the same band (no split)
718
    or True otherwise"""
719

    
720
    # determine if the two freqs are in the same band
721
    for low, high in rf.valid_bands:
722
        if f1 >= low and f1 <= high and \
723
                f2 >= low and f2 <= high:
724
            # if the two freqs are on the same Band this is not a split
725
            return False
726

    
727
    # if you get here is because the freq pairs are split
728
    return False
729

    
730

    
731
class BTech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
732
    """BTECH's UV-5001 and alike radios"""
733
    VENDOR = "BTECH"
734
    MODEL = ""
735
    IDENT = ""
736
    _vhf_range = (130000000, 180000000)
737
    _220_range = (210000000, 231000000)
738
    _uhf_range = (400000000, 521000000)
739
    _upper = 199
740
    _magic = MSTRING
741
    _fileid = None
742
    _id2 = False
743
    btech3 = False
744

    
745
    @classmethod
746
    def get_prompts(cls):
747
        rp = chirp_common.RadioPrompts()
748
        rp.experimental = \
749
            ('This driver is experimental.\n'
750
             '\n'
751
             'Please keep a copy of your memories with the original software '
752
             'if you treasure them, this driver is new and may contain'
753
             ' bugs.\n'
754
             '\n'
755
             )
756
        rp.pre_download = _(dedent("""\
757
            Follow these instructions to download your info:
758

    
759
            1 - Turn off your radio
760
            2 - Connect your interface cable
761
            3 - Turn on your radio
762
            4 - Do the download of your radio data
763

    
764
            """))
765
        rp.pre_upload = _(dedent("""\
766
            Follow these instructions to upload your info:
767

    
768
            1 - Turn off your radio
769
            2 - Connect your interface cable
770
            3 - Turn on your radio
771
            4 - Do the upload of your radio data
772

    
773
            """))
774
        return rp
775

    
776
    def get_features(self):
777
        """Get the radio's features"""
778

    
779
        # we will use the following var as global
780
        global POWER_LEVELS
781

    
782
        rf = chirp_common.RadioFeatures()
783
        rf.has_settings = True
784
        rf.has_bank = False
785
        rf.has_tuning_step = False
786
        rf.can_odd_split = True
787
        rf.has_name = True
788
        rf.has_offset = True
789
        rf.has_mode = True
790
        rf.has_dtcs = True
791
        rf.has_rx_dtcs = True
792
        rf.has_dtcs_polarity = True
793
        rf.has_ctone = True
794
        rf.has_cross = True
795
        rf.valid_modes = MODES
796
        rf.valid_characters = VALID_CHARS
797
        rf.valid_name_length = NAME_LENGTH
798
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
799
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
800
        rf.valid_cross_modes = [
801
            "Tone->Tone",
802
            "DTCS->",
803
            "->DTCS",
804
            "Tone->DTCS",
805
            "DTCS->Tone",
806
            "->Tone",
807
            "DTCS->DTCS"]
808
        rf.valid_skips = SKIP_VALUES
809
        rf.valid_dtcs_codes = DTCS
810
        rf.memory_bounds = (0, self._upper)
811

    
812
        # power levels
813
        if self.MODEL == "UV-5001":
814
            POWER_LEVELS = UV5001_POWER_LEVELS  # Higher power (50W)
815
        else:
816
            POWER_LEVELS = NORMAL_POWER_LEVELS  # Lower power (25W)
817

    
818
        rf.valid_power_levels = POWER_LEVELS
819

    
820
        # bands
821
        rf.valid_bands = [self._vhf_range, self._uhf_range]
822

    
823
        # 2501+220 & KT8900R
824
        if self.MODEL in ["UV-2501+220", "KT8900R"]:
825
            rf.valid_bands.append(self._220_range)
826

    
827
        return rf
828

    
829
    def sync_in(self):
830
        """Download from radio"""
831
        data = _download(self)
832
        self._mmap = memmap.MemoryMap(data)
833
        self.process_mmap()
834

    
835
    def sync_out(self):
836
        """Upload to radio"""
837
        try:
838
            _upload(self)
839
        except errors.RadioError:
840
            raise
841
        except Exception, e:
842
            raise errors.RadioError("Error: %s" % e)
843

    
844
    def set_options(self):
845
        """This is to read the options from the image and set it in the
846
        environment, for now just the limits of the freqs in the VHF/UHF
847
        ranges"""
848

    
849
        # setting the correct ranges for each radio type
850
        if self.MODEL in ["UV-2501+220", "KT8900R"]:
851
            # the model 2501+220 has a segment in 220
852
            # and a different position in the memmap
853
            # also the QYT KT8900R
854
            ranges = self._memobj.ranges220
855
        else:
856
            ranges = self._memobj.ranges
857

    
858
        # the normal dual bands
859
        vhf = _decode_ranges(ranges.vhf_low, ranges.vhf_high)
860
        uhf = _decode_ranges(ranges.uhf_low, ranges.uhf_high)
861

    
862
        # DEBUG
863
        LOG.info("Radio ranges: VHF %d to %d" % vhf)
864
        LOG.info("Radio ranges: UHF %d to %d" % uhf)
865

    
866
        # 220Mhz radios case
867
        if self.MODEL in ["UV-2501+220", "KT8900R"]:
868
            vhf2 = _decode_ranges(ranges.vhf2_low, ranges.vhf2_high)
869
            LOG.info("Radio ranges: VHF(220) %d to %d" % vhf2)
870
            self._220_range = vhf2
871

    
872
        # set the class with the real data
873
        self._vhf_range = vhf
874
        self._uhf_range = uhf
875

    
876
    def process_mmap(self):
877
        """Process the mem map into the mem object"""
878

    
879
        # Get it
880
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
881

    
882
        # load specific parameters from the radio image
883
        self.set_options()
884

    
885
    def get_raw_memory(self, number):
886
        return repr(self._memobj.memory[number])
887

    
888
    def _decode_tone(self, val):
889
        """Parse the tone data to decode from mem, it returns:
890
        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
891
        pol = None
892

    
893
        if val in [0, 65535]:
894
            return '', None, None
895
        elif val > 0x0258:
896
            a = val / 10.0
897
            return 'Tone', a, pol
898
        else:
899
            if val > 0x69:
900
                index = val - 0x6A
901
                pol = "R"
902
            else:
903
                index = val - 1
904
                pol = "N"
905

    
906
            tone = DTCS[index]
907
            return 'DTCS', tone, pol
908

    
909
    def _encode_tone(self, memval, mode, val, pol):
910
        """Parse the tone data to encode from UI to mem"""
911
        if mode == '' or mode is None:
912
            memval.set_raw("\x00\x00")
913
        elif mode == 'Tone':
914
            memval.set_value(val * 10)
915
        elif mode == 'DTCS':
916
            # detect the index in the DTCS list
917
            try:
918
                index = DTCS.index(val)
919
                if pol == "N":
920
                    index += 1
921
                else:
922
                    index += 0x6A
923
                memval.set_value(index)
924
            except:
925
                msg = "Digital Tone '%d' is not supported" % value
926
                LOG.error(msg)
927
                raise errors.RadioError(msg)
928
        else:
929
            msg = "Internal error: invalid mode '%s'" % mode
930
            LOG.error(msg)
931
            raise errors.InvalidDataError(msg)
932

    
933
    def get_memory(self, number):
934
        """Get the mem representation from the radio image"""
935
        _mem = self._memobj.memory[number]
936
        _names = self._memobj.names[number]
937

    
938
        # Create a high-level memory object to return to the UI
939
        mem = chirp_common.Memory()
940

    
941
        # Memory number
942
        mem.number = number
943

    
944
        if _mem.get_raw()[0] == "\xFF":
945
            mem.empty = True
946
            return mem
947

    
948
        # Freq and offset
949
        mem.freq = int(_mem.rxfreq) * 10
950
        # tx freq can be blank
951
        if _mem.get_raw()[4] == "\xFF":
952
            # TX freq not set
953
            mem.offset = 0
954
            mem.duplex = "off"
955
        else:
956
            # TX freq set
957
            offset = (int(_mem.txfreq) * 10) - mem.freq
958
            if offset != 0:
959
                if _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10):
960
                    mem.duplex = "split"
961
                    mem.offset = int(_mem.txfreq) * 10
962
                elif offset < 0:
963
                    mem.offset = abs(offset)
964
                    mem.duplex = "-"
965
                elif offset > 0:
966
                    mem.offset = offset
967
                    mem.duplex = "+"
968
            else:
969
                mem.offset = 0
970

    
971
        # name TAG of the channel
972
        mem.name = str(_names.name).rstrip("\xFF").replace("\xFF", " ")
973

    
974
        # power
975
        mem.power = POWER_LEVELS[int(_mem.power)]
976

    
977
        # wide/narrow
978
        mem.mode = MODES[int(_mem.wide)]
979

    
980
        # skip
981
        mem.skip = SKIP_VALUES[_mem.add]
982

    
983
        # tone data
984
        rxtone = txtone = None
985
        txtone = self._decode_tone(_mem.txtone)
986
        rxtone = self._decode_tone(_mem.rxtone)
987
        chirp_common.split_tone_decode(mem, txtone, rxtone)
988

    
989
        # Extra
990
        mem.extra = RadioSettingGroup("extra", "Extra")
991

    
992
        scramble = RadioSetting("scramble", "Scramble",
993
                                RadioSettingValueBoolean(bool(_mem.scramble)))
994
        mem.extra.append(scramble)
995

    
996
        bcl = RadioSetting("bcl", "Busy channel lockout",
997
                           RadioSettingValueBoolean(bool(_mem.bcl)))
998
        mem.extra.append(bcl)
999

    
1000
        pttid = RadioSetting("pttid", "PTT ID",
1001
                             RadioSettingValueList(PTTID_LIST,
1002
                                                   PTTID_LIST[_mem.pttid]))
1003
        mem.extra.append(pttid)
1004

    
1005
        # validating scode
1006
        scode = _mem.scode if _mem.scode != 15 else 0
1007
        pttidcode = RadioSetting("scode", "PTT ID signal code",
1008
                                 RadioSettingValueList(
1009
                                     PTTIDCODE_LIST,
1010
                                     PTTIDCODE_LIST[scode]))
1011
        mem.extra.append(pttidcode)
1012

    
1013
        optsig = RadioSetting("optsig", "Optional signaling",
1014
                              RadioSettingValueList(
1015
                                  OPTSIG_LIST,
1016
                                  OPTSIG_LIST[_mem.optsig]))
1017
        mem.extra.append(optsig)
1018

    
1019
        spmute = RadioSetting("spmute", "Speaker mute",
1020
                              RadioSettingValueList(
1021
                                  SPMUTE_LIST,
1022
                                  SPMUTE_LIST[_mem.spmute]))
1023
        mem.extra.append(spmute)
1024

    
1025
        return mem
1026

    
1027
    def set_memory(self, mem):
1028
        """Set the memory data in the eeprom img from the UI"""
1029
        # get the eprom representation of this channel
1030
        _mem = self._memobj.memory[mem.number]
1031
        _names = self._memobj.names[mem.number]
1032

    
1033
        # if empty memmory
1034
        if mem.empty:
1035
            # the channel itself
1036
            _mem.set_raw("\xFF" * 16)
1037
            # the name tag
1038
            _names.set_raw("\xFF" * 16)
1039
            return
1040

    
1041
        # frequency
1042
        _mem.rxfreq = mem.freq / 10
1043

    
1044
        # duplex
1045
        if mem.duplex == "+":
1046
            _mem.txfreq = (mem.freq + mem.offset) / 10
1047
        elif mem.duplex == "-":
1048
            _mem.txfreq = (mem.freq - mem.offset) / 10
1049
        elif mem.duplex == "off":
1050
            for i in _mem.txfreq:
1051
                i.set_raw("\xFF")
1052
        elif mem.duplex == "split":
1053
            _mem.txfreq = mem.offset / 10
1054
        else:
1055
            _mem.txfreq = mem.freq / 10
1056

    
1057
        # tone data
1058
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
1059
            chirp_common.split_tone_encode(mem)
1060
        self._encode_tone(_mem.txtone, txmode, txtone, txpol)
1061
        self._encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
1062

    
1063
        # name TAG of the channel
1064
        if len(mem.name) < NAME_LENGTH:
1065
            # we must pad to NAME_LENGTH chars, " " = "\xFF"
1066
            mem.name = str(mem.name).ljust(NAME_LENGTH, " ")
1067
        _names.name = str(mem.name).replace(" ", "\xFF")
1068

    
1069
        # power, # default power level is high
1070
        _mem.power = 0 if mem.power is None else POWER_LEVELS.index(mem.power)
1071

    
1072
        # wide/narrow
1073
        _mem.wide = MODES.index(mem.mode)
1074

    
1075
        # scan add property
1076
        _mem.add = SKIP_VALUES.index(mem.skip)
1077

    
1078
        # reseting unknowns, this have to be set by hand
1079
        _mem.unknown0 = 0
1080
        _mem.unknown1 = 0
1081
        _mem.unknown2 = 0
1082
        _mem.unknown3 = 0
1083
        _mem.unknown4 = 0
1084
        _mem.unknown5 = 0
1085
        _mem.unknown6 = 0
1086

    
1087
        # extra settings
1088
        if len(mem.extra) > 0:
1089
            # there are setting, parse
1090
            for setting in mem.extra:
1091
                setattr(_mem, setting.get_name(), setting.value)
1092
        else:
1093
            # there is no extra settings, load defaults
1094
            _mem.spmute = 0
1095
            _mem.optsig = 0
1096
            _mem.scramble = 0
1097
            _mem.bcl = 0
1098
            _mem.pttid = 0
1099
            _mem.scode = 0
1100

    
1101
        return mem
1102

    
1103
    def get_settings(self):
1104
        """Translate the bit in the mem_struct into settings in the UI"""
1105
        _mem = self._memobj
1106
        basic = RadioSettingGroup("basic", "Basic Settings")
1107
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
1108
        other = RadioSettingGroup("other", "Other Settings")
1109
        work = RadioSettingGroup("work", "Work Mode Settings")
1110
        top = RadioSettings(basic, advanced, other, work)
1111

    
1112
        # Basic
1113
        tdr = RadioSetting("settings.tdr", "Transceiver dual receive",
1114
                           RadioSettingValueBoolean(_mem.settings.tdr))
1115
        basic.append(tdr)
1116

    
1117
        sql = RadioSetting("settings.sql", "Squelch level",
1118
                           RadioSettingValueInteger(0, 9, _mem.settings.sql))
1119
        basic.append(sql)
1120

    
1121
        tot = RadioSetting("settings.tot", "Time out timer",
1122
                           RadioSettingValueList(LIST_TOT, LIST_TOT[
1123
                               _mem.settings.tot]))
1124
        basic.append(tot)
1125

    
1126
        if self.MODEL in ("UV-2501", "UV-2501+220", "UV-5001"):
1127
            apo = RadioSetting("settings.apo", "Auto power off timer",
1128
                               RadioSettingValueList(LIST_APO, LIST_APO[
1129
                                   _mem.settings.apo]))
1130
            basic.append(apo)
1131
        else:
1132
            toa = RadioSetting("settings.apo", "Time out alert timer",
1133
                               RadioSettingValueList(LIST_TOA, LIST_TOA[
1134
                                   _mem.settings.apo]))
1135
            basic.append(toa)
1136

    
1137
        abr = RadioSetting("settings.abr", "Backlight timer",
1138
                           RadioSettingValueList(LIST_ABR, LIST_ABR[
1139
                               _mem.settings.abr]))
1140
        basic.append(abr)
1141

    
1142
        beep = RadioSetting("settings.beep", "Key beep",
1143
                            RadioSettingValueBoolean(_mem.settings.beep))
1144
        basic.append(beep)
1145

    
1146
        dtmfst = RadioSetting("settings.dtmfst", "DTMF side tone",
1147
                              RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
1148
                                  _mem.settings.dtmfst]))
1149
        basic.append(dtmfst)
1150

    
1151
        prisc = RadioSetting("settings.prisc", "Priority scan",
1152
                             RadioSettingValueBoolean(_mem.settings.prisc))
1153
        basic.append(prisc)
1154

    
1155
        prich = RadioSetting("settings.prich", "Priority channel",
1156
                             RadioSettingValueInteger(0, 199,
1157
                                 _mem.settings.prich))
1158
        basic.append(prich)
1159

    
1160
        screv = RadioSetting("settings.screv", "Scan resume method",
1161
                             RadioSettingValueList(LIST_SCREV, LIST_SCREV[
1162
                                 _mem.settings.screv]))
1163
        basic.append(screv)
1164

    
1165
        pttlt = RadioSetting("settings.pttlt", "PTT transmit delay",
1166
                             RadioSettingValueInteger(0, 30,
1167
                                 _mem.settings.pttlt))
1168
        basic.append(pttlt)
1169

    
1170
        emctp = RadioSetting("settings.emctp", "Alarm mode",
1171
                             RadioSettingValueList(LIST_EMCTP, LIST_EMCTP[
1172
                                 _mem.settings.emctp]))
1173
        basic.append(emctp)
1174

    
1175
        emcch = RadioSetting("settings.emcch", "Alarm channel",
1176
                             RadioSettingValueInteger(0, 199,
1177
                                 _mem.settings.emcch))
1178
        basic.append(emcch)
1179

    
1180
        ringt = RadioSetting("settings.ringt", "Ring time",
1181
                             RadioSettingValueList(LIST_RINGT, LIST_RINGT[
1182
                                 _mem.settings.ringt]))
1183
        basic.append(ringt)
1184

    
1185
        camdf = RadioSetting("settings.camdf", "Display mode A",
1186
                             RadioSettingValueList(LIST_MDF, LIST_MDF[
1187
                                 _mem.settings.camdf]))
1188
        basic.append(camdf)
1189

    
1190
        cbmdf = RadioSetting("settings.cbmdf", "Display mode B",
1191
                             RadioSettingValueList(LIST_MDF, LIST_MDF[
1192
                                 _mem.settings.cbmdf]))
1193
        basic.append(cbmdf)
1194

    
1195
        if self.MODEL in ("UV-2501", "UV-2501+220", "UV-5001"):
1196
           sync = RadioSetting("settings.sync", "A/B channel sync",
1197
                               RadioSettingValueBoolean(_mem.settings.sync))
1198
           basic.append(sync)
1199
        else:
1200
           autolk = RadioSetting("settings.sync", "Auto keylock",
1201
                                 RadioSettingValueBoolean(_mem.settings.sync))
1202
           basic.append(autolk)
1203

    
1204
        ponmsg = RadioSetting("settings.ponmsg", "Power-on message",
1205
                              RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
1206
                                  _mem.settings.ponmsg]))
1207
        basic.append(ponmsg)
1208

    
1209
        wtled = RadioSetting("settings.wtled", "Standby backlight Color",
1210
                             RadioSettingValueList(LIST_COLOR, LIST_COLOR[
1211
                                 _mem.settings.wtled]))
1212
        basic.append(wtled)
1213

    
1214
        rxled = RadioSetting("settings.rxled", "RX backlight Color",
1215
                             RadioSettingValueList(LIST_COLOR, LIST_COLOR[
1216
                                 _mem.settings.rxled]))
1217
        basic.append(rxled)
1218

    
1219
        txled = RadioSetting("settings.txled", "TX backlight Color",
1220
                             RadioSettingValueList(LIST_COLOR, LIST_COLOR[
1221
                                 _mem.settings.txled]))
1222
        basic.append(txled)
1223

    
1224
        anil = RadioSetting("settings.anil", "ANI length",
1225
                            RadioSettingValueList(LIST_ANIL, LIST_ANIL[
1226
                                _mem.settings.anil]))
1227
        basic.append(anil)
1228

    
1229
        reps = RadioSetting("settings.reps", "Relay signal (tone burst)",
1230
                            RadioSettingValueList(LIST_REPS, LIST_REPS[
1231
                                _mem.settings.reps]))
1232
        basic.append(reps)
1233

    
1234
        repm = RadioSetting("settings.repm", "Relay condition",
1235
                            RadioSettingValueList(LIST_REPM, LIST_REPM[
1236
                                _mem.settings.repm]))
1237
        basic.append(repm)
1238

    
1239
        if self.MODEL in ("UV-2501", "UV-2501+220", "UV-5001"):
1240
            tdrab = RadioSetting("settings.tdrab", "TDR return time",
1241
                                 RadioSettingValueList(LIST_ABR, LIST_ABR[
1242
                                     _mem.settings.tdrab]))
1243
            basic.append(tdrab)
1244

    
1245
            ste = RadioSetting("settings.ste", "Squelch tail eliminate",
1246
                               RadioSettingValueBoolean(_mem.settings.ste))
1247
            basic.append(ste)
1248

    
1249
            rpste = RadioSetting("settings.rpste", "Repeater STE",
1250
                                 RadioSettingValueList(LIST_RINGT, LIST_RINGT[
1251
                                     _mem.settings.rpste]))
1252
            basic.append(rpste)
1253

    
1254
            rptdl = RadioSetting("settings.rptdl", "Repeater STE delay",
1255
                                 RadioSettingValueList(LIST_RPTDL, LIST_RPTDL[
1256
                                     _mem.settings.rptdl]))
1257
            basic.append(rptdl)
1258

    
1259
        if str(_mem.fingerprint.fp) in BTECH3:
1260

    
1261
            mgain = RadioSetting("settings.mgain", "Mic gain",
1262
                                 RadioSettingValueInteger(0, 120,
1263
                                     _mem.settings.mgain))
1264
            basic.append(mgain)
1265

    
1266
            dtmfg = RadioSetting("settings.dtmfg", "DTMF gain",
1267
                                 RadioSettingValueInteger(0, 60,
1268
                                     _mem.settings.dtmfg))
1269
            basic.append(dtmfg)
1270

    
1271
        # Advanced
1272
        def _filter(name):
1273
            filtered = ""
1274
            for char in str(name):
1275
                if char in VALID_CHARS:
1276
                    filtered += char
1277
                else:
1278
                    filtered += " "
1279
            return filtered
1280

    
1281
        _msg = self._memobj.poweron_msg
1282
        line1 = RadioSetting("poweron_msg.line1", "Power-on message line 1",
1283
                             RadioSettingValueString(0, 6, _filter(
1284
                                 _msg.line1)))
1285
        advanced.append(line1)
1286
        line2 = RadioSetting("poweron_msg.line2", "Power-on message line 2",
1287
                             RadioSettingValueString(0, 6, _filter(
1288
                                 _msg.line2)))
1289
        advanced.append(line2)
1290

    
1291
        if self.MODEL in ("UV-2501", "UV-5001"):
1292
            vfomren = RadioSetting("settings2.vfomren", "VFO/MR switching",
1293
                                   RadioSettingValueBoolean(
1294
                                       not _mem.settings2.vfomren))
1295
            advanced.append(vfomren)
1296

    
1297
            reseten = RadioSetting("settings2.reseten", "RESET",
1298
                                   RadioSettingValueBoolean(
1299
                                       _mem.settings2.reseten))
1300
            advanced.append(reseten)
1301

    
1302
            menuen = RadioSetting("settings2.menuen", "Menu",
1303
                                  RadioSettingValueBoolean(
1304
                                      _mem.settings2.menuen))
1305
            advanced.append(menuen)
1306

    
1307
        # Other
1308
        def convert_bytes_to_limit(bytes):
1309
            limit = ""
1310
            for byte in bytes:
1311
                if byte < 10:
1312
                    limit += chr(byte + 0x30)
1313
                else:
1314
                    break
1315
            return limit
1316

    
1317
        if self.MODEL in ["UV-2501+220", "KT8900R"]:
1318
            _ranges = self._memobj.ranges220
1319
            ranges = "ranges220"
1320
        else:
1321
            _ranges = self._memobj.ranges
1322
            ranges = "ranges"
1323

    
1324
        _limit = convert_bytes_to_limit(_ranges.vhf_low)
1325
        val = RadioSettingValueString(0, 3, _limit)
1326
        val.set_mutable(False)
1327
        vhf_low = RadioSetting("%s.vhf_low" % ranges, "VHF low", val)
1328
        other.append(vhf_low)
1329

    
1330
        _limit = convert_bytes_to_limit(_ranges.vhf_high)
1331
        val = RadioSettingValueString(0, 3, _limit)
1332
        val.set_mutable(False)
1333
        vhf_high = RadioSetting("%s.vhf_high" % ranges, "VHF high", val)
1334
        other.append(vhf_high)
1335

    
1336
        if self.MODEL in ["UV-2501+220", "KT8900R"]:
1337
            _limit = convert_bytes_to_limit(_ranges.vhf2_low)
1338
            val = RadioSettingValueString(0, 3, _limit)
1339
            val.set_mutable(False)
1340
            vhf2_low = RadioSetting("%s.vhf2_low" % ranges, "VHF2 low", val)
1341
            other.append(vhf2_low)
1342

    
1343
            _limit = convert_bytes_to_limit(_ranges.vhf2_high)
1344
            val = RadioSettingValueString(0, 3, _limit)
1345
            val.set_mutable(False)
1346
            vhf2_high = RadioSetting("%s.vhf2_high" % ranges, "VHF2 high", val)
1347
            other.append(vhf2_high)
1348

    
1349
        _limit = convert_bytes_to_limit(_ranges.uhf_low)
1350
        val = RadioSettingValueString(0, 3, _limit)
1351
        val.set_mutable(False)
1352
        uhf_low = RadioSetting("%s.uhf_low" % ranges, "UHF low", val)
1353
        other.append(uhf_low)
1354

    
1355
        _limit = convert_bytes_to_limit(_ranges.uhf_high)
1356
        val = RadioSettingValueString(0, 3, _limit)
1357
        val.set_mutable(False)
1358
        uhf_high = RadioSetting("%s.uhf_high" % ranges, "UHF high", val)
1359
        other.append(uhf_high)
1360

    
1361
        val = RadioSettingValueString(0, 6, _filter(_mem.fingerprint.fp))
1362
        val.set_mutable(False)
1363
        fp = RadioSetting("fingerprint.fp", "Fingerprint", val)
1364
        other.append(fp)
1365

    
1366
        # Work
1367
        dispab = RadioSetting("settings2.dispab", "Display",
1368
                              RadioSettingValueList(LIST_AB,LIST_AB[
1369
                                  _mem.settings2.dispab]))
1370
        work.append(dispab)
1371

    
1372
        vfomr = RadioSetting("settings2.vfomr", "VFO/MR mode",
1373
                             RadioSettingValueList(LIST_VFOMR,LIST_VFOMR[
1374
                                 _mem.settings2.vfomr]))
1375
        work.append(vfomr)
1376

    
1377
        keylock = RadioSetting("settings2.keylock", "Keypad lock",
1378
                           RadioSettingValueBoolean(_mem.settings2.keylock))
1379
        work.append(keylock)
1380

    
1381
        mrcha = RadioSetting("settings2.mrcha", "MR A channel",
1382
                             RadioSettingValueInteger(0, 199,
1383
                                 _mem.settings2.mrcha))
1384
        work.append(mrcha)
1385

    
1386
        mrchb = RadioSetting("settings2.mrchb", "MR B channel",
1387
                             RadioSettingValueInteger(0, 199,
1388
                                 _mem.settings2.mrchb))
1389
        work.append(mrchb)
1390

    
1391
        def convert_bytes_to_freq(bytes):
1392
            real_freq = 0
1393
            for byte in bytes:
1394
                real_freq = (real_freq * 10) + byte
1395
            return chirp_common.format_freq(real_freq * 10)
1396

    
1397
        def my_validate(value):
1398
            value = chirp_common.parse_freq(value)
1399
            if "+220" in self.MODEL:
1400
                if 180000000 <= value and value < 210000000:
1401
                    msg = ("Can't be between 180.00000-210.00000")
1402
                    raise InvalidValueError(msg)
1403
                elif 231000000 <= value and value < 400000000:
1404
                    msg = ("Can't be between 231.00000-400.00000")
1405
                    raise InvalidValueError(msg)
1406
            elif "8900R" in self.MODEL:
1407
                if 180000000 <= value and value < 240000000:
1408
                    msg = ("Can't be between 180.00000-240.00000")
1409
                    raise InvalidValueError(msg)
1410
                elif 271000000 <= value and value < 400000000:
1411
                    msg = ("Can't be between 271.00000-400.00000")
1412
                    raise InvalidValueError(msg)
1413
            elif 180000000 <= value and value < 400000000:
1414
                msg = ("Can't be between 180.00000-400.00000")
1415
                raise InvalidValueError(msg)
1416
            return chirp_common.format_freq(value)
1417

    
1418
        def apply_freq(setting, obj):
1419
            value = chirp_common.parse_freq(str(setting.value)) / 10
1420
            for i in range(7, -1, -1):
1421
                obj.freq[i] = value % 10
1422
                value /= 10
1423

    
1424
        val1a = RadioSettingValueString(0, 10, convert_bytes_to_freq(
1425
                                        _mem.vfo.a.freq))
1426
        val1a.set_validate_callback(my_validate)
1427
        vfoafreq = RadioSetting("vfo.a.freq", "VFO A frequency", val1a)
1428
        vfoafreq.set_apply_callback(apply_freq, _mem.vfo.a)
1429
        work.append(vfoafreq)
1430

    
1431
        val1b = RadioSettingValueString(0, 10, convert_bytes_to_freq(
1432
                                        _mem.vfo.b.freq))
1433
        val1b.set_validate_callback(my_validate)
1434
        vfobfreq = RadioSetting("vfo.b.freq", "VFO B frequency", val1b)
1435
        vfobfreq.set_apply_callback(apply_freq, _mem.vfo.b)
1436
        work.append(vfobfreq)
1437

    
1438
        vfoashiftd = RadioSetting("vfo.a.shiftd", "VFO A shift",
1439
                                  RadioSettingValueList(LIST_SHIFT, LIST_SHIFT[
1440
                                      _mem.vfo.a.shiftd]))
1441
        work.append(vfoashiftd)
1442

    
1443
        vfobshiftd = RadioSetting("vfo.b.shiftd", "VFO B shift",
1444
                                  RadioSettingValueList(LIST_SHIFT, LIST_SHIFT[
1445
                                      _mem.vfo.b.shiftd]))
1446
        work.append(vfobshiftd)
1447

    
1448
        def convert_bytes_to_offset(bytes):
1449
            real_offset = 0
1450
            for byte in bytes:
1451
                real_offset = (real_offset * 10) + byte
1452
            return chirp_common.format_freq(real_offset * 10000)
1453

    
1454
        def apply_offset(setting, obj):
1455
            value = chirp_common.parse_freq(str(setting.value)) / 10000
1456
            for i in range(3, -1, -1):
1457
                obj.offset[i] = value % 10
1458
                value /= 10
1459

    
1460
        val1a = RadioSettingValueString(0, 10, convert_bytes_to_offset(
1461
                                        _mem.vfo.a.offset))
1462
        vfoaoffset = RadioSetting("vfo.a.offset",
1463
                                  "VFO A offset (0.00-99.95)", val1a)
1464
        vfoaoffset.set_apply_callback(apply_offset, _mem.vfo.a)
1465
        work.append(vfoaoffset)
1466

    
1467
        val1b = RadioSettingValueString(0, 10, convert_bytes_to_offset(
1468
                                        _mem.vfo.b.offset))
1469
        vfoboffset = RadioSetting("vfo.b.offset",
1470
                                  "VFO B offset (0.00-99.95)", val1b)
1471
        vfoboffset.set_apply_callback(apply_offset, _mem.vfo.b)
1472
        work.append(vfoboffset)
1473

    
1474
        vfoatxp = RadioSetting("vfo.a.power", "VFO A power",
1475
                                RadioSettingValueList(LIST_TXP,LIST_TXP[
1476
                                    _mem.vfo.a.power]))
1477
        work.append(vfoatxp)
1478

    
1479
        vfobtxp = RadioSetting("vfo.b.power", "VFO B power",
1480
                                RadioSettingValueList(LIST_TXP,LIST_TXP[
1481
                                    _mem.vfo.b.power]))
1482
        work.append(vfobtxp)
1483

    
1484
        vfoawide = RadioSetting("vfo.a.wide", "VFO A bandwidth",
1485
                                RadioSettingValueList(LIST_WIDE,LIST_WIDE[
1486
                                    _mem.vfo.a.wide]))
1487
        work.append(vfoawide)
1488

    
1489
        vfobwide = RadioSetting("vfo.b.wide", "VFO B bandwidth",
1490
                                RadioSettingValueList(LIST_WIDE,LIST_WIDE[
1491
                                    _mem.vfo.b.wide]))
1492
        work.append(vfobwide)
1493

    
1494
        vfoastep = RadioSetting("vfo.a.step", "VFO A step",
1495
                                RadioSettingValueList(LIST_STEP,LIST_STEP[
1496
                                    _mem.vfo.a.step]))
1497
        work.append(vfoastep)
1498

    
1499
        vfobstep = RadioSetting("vfo.b.step", "VFO B step",
1500
                                RadioSettingValueList(LIST_STEP,LIST_STEP[
1501
                                    _mem.vfo.b.step]))
1502
        work.append(vfobstep)
1503

    
1504
        vfoaoptsig = RadioSetting("vfo.a.optsig", "VFO A optional signal",
1505
                                  RadioSettingValueList(OPTSIG_LIST,
1506
                                      OPTSIG_LIST[_mem.vfo.a.optsig]))
1507
        work.append(vfoaoptsig)
1508

    
1509
        vfoboptsig = RadioSetting("vfo.b.optsig", "VFO B optional signal",
1510
                                  RadioSettingValueList(OPTSIG_LIST,
1511
                                      OPTSIG_LIST[_mem.vfo.b.optsig]))
1512
        work.append(vfoboptsig)
1513

    
1514
        vfoaspmute = RadioSetting("vfo.a.spmute", "VFO A speaker mute",
1515
                                  RadioSettingValueList(SPMUTE_LIST,
1516
                                      SPMUTE_LIST[_mem.vfo.a.spmute]))
1517
        work.append(vfoaspmute)
1518

    
1519
        vfobspmute = RadioSetting("vfo.b.spmute", "VFO B speaker mute",
1520
                                  RadioSettingValueList(SPMUTE_LIST,
1521
                                      SPMUTE_LIST[_mem.vfo.b.spmute]))
1522
        work.append(vfobspmute)
1523

    
1524
        vfoascr = RadioSetting("vfo.a.scramble", "VFO A scramble",
1525
                               RadioSettingValueBoolean(_mem.vfo.a.scramble))
1526
        work.append(vfoascr)
1527

    
1528
        vfobscr = RadioSetting("vfo.b.scramble", "VFO B scramble",
1529
                               RadioSettingValueBoolean(_mem.vfo.b.scramble))
1530
        work.append(vfobscr)
1531

    
1532
        vfoascode = RadioSetting("vfo.a.scode", "VFO A PTT-ID",
1533
                                 RadioSettingValueList(PTTIDCODE_LIST,
1534
                                     PTTIDCODE_LIST[_mem.vfo.a.scode]))
1535
        work.append(vfoascode)
1536

    
1537
        vfobscode = RadioSetting("vfo.b.scode", "VFO B PTT-ID",
1538
                                 RadioSettingValueList(PTTIDCODE_LIST,
1539
                                     PTTIDCODE_LIST[_mem.vfo.b.scode]))
1540
        work.append(vfobscode)
1541

    
1542
        pttid = RadioSetting("settings.pttid", "PTT ID",
1543
                             RadioSettingValueList(PTTID_LIST,
1544
                                 PTTID_LIST[_mem.settings.pttid]))
1545
        work.append(pttid)
1546

    
1547
        return top
1548

    
1549
    def set_settings(self, settings):
1550
        _settings = self._memobj.settings
1551
        for element in settings:
1552
            if not isinstance(element, RadioSetting):
1553
                if element.get_name() == "fm_preset":
1554
                    self._set_fm_preset(element)
1555
                else:
1556
                    self.set_settings(element)
1557
                    continue
1558
            else:
1559
                try:
1560
                    name = element.get_name()
1561
                    if "." in name:
1562
                        bits = name.split(".")
1563
                        obj = self._memobj
1564
                        for bit in bits[:-1]:
1565
                            if "/" in bit:
1566
                                bit, index = bit.split("/", 1)
1567
                                index = int(index)
1568
                                obj = getattr(obj, bit)[index]
1569
                            else:
1570
                                obj = getattr(obj, bit)
1571
                        setting = bits[-1]
1572
                    else:
1573
                        obj = _settings
1574
                        setting = element.get_name()
1575

    
1576
                    if element.has_apply_callback():
1577
                        LOG.debug("Using apply callback")
1578
                        element.run_apply_callback()
1579
                    elif setting == "vfomren":
1580
                        setattr(obj, setting, not int(element.value))
1581
                    elif element.value.get_mutable():
1582
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1583
                        setattr(obj, setting, element.value)
1584
                except Exception, e:
1585
                    LOG.debug(element.get_name())
1586
                    raise
1587

    
1588
    @classmethod
1589
    def match_model(cls, filedata, filename):
1590
        match_size = False
1591
        match_model = False
1592

    
1593
        # testing the file data size
1594
        if len(filedata) == MEM_SIZE:
1595
            match_size = True
1596

    
1597
        # testing the firmware model fingerprint
1598
        match_model = model_match(cls, filedata)
1599

    
1600
        if match_size and match_model:
1601
            return True
1602
        else:
1603
            return False
1604

    
1605

    
1606
# Declaring Aliases (Clones of the real radios)
1607
class JT2705M(chirp_common.Alias):
1608
    VENDOR = "Jetstream"
1609
    MODEL = "JT2705M"
1610

    
1611

    
1612
class JT6188Mini(chirp_common.Alias):
1613
    VENDOR = "Juentai"
1614
    MODEL = "JT-6188 Mini"
1615

    
1616

    
1617
class JT6188Plus(chirp_common.Alias):
1618
    VENDOR = "Juentai"
1619
    MODEL = "JT-6188 Plus"
1620

    
1621

    
1622
class SSGT890(chirp_common.Alias):
1623
    VENDOR = "Sainsonic"
1624
    MODEL = "GT-890"
1625

    
1626

    
1627
class ZastoneMP300(chirp_common.Alias):
1628
    VENDOR = "Zastone"
1629
    MODEL = "MP-300"
1630

    
1631

    
1632
# real radios
1633
@directory.register
1634
class UV2501(BTech):
1635
    """Baofeng Tech UV2501"""
1636
    MODEL = "UV-2501"
1637
    _fileid = [UV2501G3_fp,
1638
               UV2501G2_fp,
1639
               UV2501pp2_fp,
1640
               UV2501pp_fp]
1641

    
1642

    
1643
@directory.register
1644
class UV2501_220(BTech):
1645
    """Baofeng Tech UV2501+220"""
1646
    MODEL = "UV-2501+220"
1647
    _magic = MSTRING_220
1648
    _id2 = UV2501_220pp_id
1649
    _fileid = [UV2501_220G3_fp,
1650
               UV2501_220G2_fp,
1651
               UV2501_220_fp,
1652
               UV2501_220pp_fp]
1653

    
1654

    
1655
@directory.register
1656
class UV5001(BTech):
1657
    """Baofeng Tech UV5001"""
1658
    MODEL = "UV-5001"
1659
    _fileid = [UV5001G3_fp,
1660
               UV5001G22_fp,
1661
               UV5001G2_fp,
1662
               UV5001alpha_fp,
1663
               UV5001pp_fp]
1664

    
1665

    
1666
@directory.register
1667
class MINI8900(BTech):
1668
    """WACCOM MINI-8900"""
1669
    VENDOR = "WACCOM"
1670
    MODEL = "MINI-8900"
1671
    _magic = MSTRING_MINI8900
1672
    _fileid = [MINI8900_fp, ]
1673
    # Clones
1674
    ALIASES = [JT6188Plus, ]
1675

    
1676

    
1677
@directory.register
1678
class KTUV980(BTech):
1679
    """QYT KT-UV980"""
1680
    VENDOR = "QYT"
1681
    MODEL = "KT-UV980"
1682
    _vhf_range = (136000000, 175000000)
1683
    _uhf_range = (400000000, 481000000)
1684
    _magic = MSTRING_MINI8900
1685
    _fileid = [KTUV980_fp, ]
1686
    # Clones
1687
    ALIASES = [JT2705M, ]
1688

    
1689
# Please note that there is a version of this radios that is a clone of the
1690
# Waccom Mini8900, maybe an early version?
1691
@directory.register
1692
class KT9800(BTech):
1693
    """QYT KT8900"""
1694
    VENDOR = "QYT"
1695
    MODEL = "KT8900"
1696
    _vhf_range = (136000000, 175000000)
1697
    _uhf_range = (400000000, 481000000)
1698
    _magic = MSTRING_KT8900
1699
    _fileid = [KT8900_fp,
1700
               KT8900_fp1,
1701
               KT8900_fp2,
1702
               KT8900_fp3,
1703
               KT8900_fp4]
1704
    _id2 = KT8900_id
1705
    # Clones
1706
    ALIASES = [JT6188Mini, SSGT890, ZastoneMP300]
1707

    
1708

    
1709
@directory.register
1710
class KT9800R(BTech):
1711
    """QYT KT8900R"""
1712
    VENDOR = "QYT"
1713
    MODEL = "KT8900R"
1714
    _vhf_range = (136000000, 175000000)
1715
    _220_range = (240000000, 271000000)
1716
    _uhf_range = (400000000, 481000000)
1717
    _magic = MSTRING_KT8900R
1718
    _fileid = [KT8900R_fp,
1719
               KT8900R_fp1,
1720
               KT8900R_fp2,
1721
               KT8900R_fp3]
1722
    _id2 = KT8900R_id
1723

    
1724

    
1725
@directory.register
1726
class LT588UV(BTech):
1727
    """LUITON LT-588UV"""
1728
    VENDOR = "LUITON"
1729
    MODEL = "LT-588UV"
1730
    _vhf_range = (136000000, 175000000)
1731
    _uhf_range = (400000000, 481000000)
1732
    _magic = MSTRING_KT8900
1733
    _fileid = [LT588UV_fp, ]
(10-10/13)