Project

General

Profile

Feature #4051 ยป btech.py

Michael Wagner, 09/22/2016 02:33 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 0x2400;
159
struct {
160
  u8 period; // one out of LIST_5TONE_STANDARD_PERIODS
161
  u8 group_tone;
162
  u8 repeat_tone;
163
  u8 unused[13];
164
} 5tone_std_settings[15];
165

    
166
#seekto 0x2500;
167
struct {
168
  u8 frame1[5];
169
  u8 frame2[5];
170
  u8 frame3[5];
171
  u8 standard;   // one out of LIST_5TONE_STANDARDS
172
} 5tone_encode[15];
173

    
174
#seekto 0x25F0;
175
struct {
176
  u8 5tone_delay1; // * 10ms
177
  u8 5tone_delay2; // * 10ms
178
  u8 5tone_delay3; // * 10ms
179
  u8 5tone_first_digit_ext_length;
180
  u8 unknown1;
181
  u8 unknown2;
182
  u8 unknown3;
183
  u8 unknown4;
184
  u8 decode_standard;
185
  u8 unknown5:5,
186
     5tone_decode_call_frame1:1,
187
     5tone_decode_call_frame2:1,
188
     5tone_decode_call_frame3:1;
189
  u8 unknown6:5,
190
     5tone_decode_disp_frame1:1,
191
     5tone_decode_disp_frame2:1,
192
     5tone_decode_disp_frame3:1;
193
  u8 decode_reset_time; // * 100 + 100ms
194
} 5tone_settings;
195

    
196
#seekto 0x2900;
197
struct {
198
  u8 code[16]; // 0=x0A, A=0x0D, B=0x0E, C=0x0F, D=0x00, #=0x0C *=0x0B
199
} pttid[15];
200

    
201
#seekto 0x29F0;
202
struct {
203
  u8 dtmfspeed_on;  //list with 50..2000ms in steps of 10
204
  u8 dtmfspeed_off; //list with 50..2000ms in steps of 10
205
  u8 unknown0[14];
206
  u8 inspection[16];
207
  u8 monitor[16];
208
  u8 alarmcode[16];
209
  u8 stun[16];
210
  u8 kill[16];
211
  u8 revive[16];
212
  u8 unknown1[16];
213
  u8 unknown2[16];
214
  u8 unknown3[16];
215
  u8 unknown4[16];
216
  u8 unknown5[16];
217
  u8 unknown6[16];
218
  u8 unknown7[16];
219
  u8 masterid[16];
220
  u8 viceid[16];
221
  u8 unused01:7,
222
     mastervice:1;
223
  u8 unused02:3,
224
     mrevive:1,
225
     mkill:1,
226
     mstun:1,
227
     mmonitor:1,
228
     minspection:1;
229
  u8 unused03:3,
230
     vrevive:1,
231
     vkill:1,
232
     vstun:1,
233
     vmonitor:1,
234
     vinspection:1;
235
  u8 unused04:6,
236
     txdisable:1,
237
     rxdisable:1;
238
  u8 groupcode;
239
  u8 spacecode;
240
  u8 delayproctime; // * 100 + 100ms
241
  u8 resettime;     // * 100 + 100ms
242
} dtmf;
243

    
244
#seekto 0x2D00;
245
struct {
246
  struct {
247
    ul16 freq1;
248
    u8 unused01[6];
249
    ul16 freq2;
250
    u8 unused02[6];
251
  } 2tone_encode[15];
252
  u8 duration_1st_tone; // *10ms
253
  u8 duration_2st_tone; // *10ms
254
  u8 duration_gap;      // *10ms
255
  u8 unused03[13];
256
  struct {
257
    struct {
258
      u8 dec;      // one out of LIST_2TONE_DEC
259
      u8 response; // one out of LIST_2TONE_RESPONSE
260
      u8 alert;
261
    } decs[4];
262
    u8 unused04[4];
263
  } 2tone_decode[15];
264
  u8 unused05[16];
265
  
266
  struct {
267
    ul16 freqA;
268
    ul16 freqB;
269
    ul16 freqC;
270
    ul16 freqD;
271
    ul16 whatisthat_A; // no idea what this means, but it changes with freqA
272
    ul16 whatisthat_B; // no idea what this means, but it changes with freqB
273
    ul16 whatisthat_C; // no idea what this means, but it changes with freqC
274
    ul16 whatisthat_D; // no idea what this means, but it changes with freqD
275
  }freqs[15];
276
  u8 reset_time;  // * 100 + 100ms
277
} 2tone;
278

    
279
#seekto 0x3000;
280
struct {
281
  u8 freq[8];
282
  char broadcast_station_name[6];
283
  u8 unknown[2];
284
} fm_radio_preset[16];
285

    
286
#seekto 0x3C90;
287
struct {
288
  u8 vhf_low[3];
289
  u8 vhf_high[3];
290
  u8 uhf_low[3];
291
  u8 uhf_high[3];
292
} ranges;
293

    
294
// the UV-2501+220 & KT8900R has different zones for storing ranges
295

    
296
#seekto 0x3CD0;
297
struct {
298
  u8 vhf_low[3];
299
  u8 vhf_high[3];
300
  u8 unknown1[4];
301
  u8 unknown2[6];
302
  u8 vhf2_low[3];
303
  u8 vhf2_high[3];
304
  u8 unknown3[4];
305
  u8 unknown4[6];
306
  u8 uhf_low[3];
307
  u8 uhf_high[3];
308
} ranges220;
309

    
310
#seekto 0x3F70;
311
struct {
312
  char fp[6];
313
} fingerprint;
314

    
315
"""
316

    
317
# A note about the memmory in these radios
318
#
319
# The real memory of these radios extends to 0x4000
320
# On read the factory software only uses up to 0x3200
321
# On write it just uploads the contents up to 0x3100
322
#
323
# The mem beyond 0x3200 holds the ID data
324

    
325
MEM_SIZE = 0x4000
326
BLOCK_SIZE = 0x40
327
TX_BLOCK_SIZE = 0x10
328
ACK_CMD = "\x06"
329
MODES = ["FM", "NFM"]
330
SKIP_VALUES = ["S", ""]
331
TONES = chirp_common.TONES
332
DTCS = sorted(chirp_common.DTCS_CODES + [645])
333
NAME_LENGTH = 6
334
PTTID_LIST = ["OFF", "BOT", "EOT", "BOTH"]
335
PTTIDCODE_LIST = ["%s" % x for x in range(1, 16)]
336
OPTSIG_LIST = ["OFF", "DTMF", "2TONE", "5TONE"]
337
SPMUTE_LIST = ["Tone/DTCS", "Tone/DTCS and Optsig", "Tone/DTCS or Optsig"]
338

    
339
LIST_TOT = ["%s sec" % x for x in range(15, 615, 15)]
340
LIST_TOA = ["Off"] + ["%s seconds" % x for x in range(1, 11)]
341
LIST_APO = ["Off"] + ["%s minutes" % x for x in range(30, 330, 30)]
342
LIST_ABR = ["Off"] + ["%s seconds" % x for x in range(1, 51)]
343
LIST_DTMFST = ["OFF", "Keyboard", "ANI", "Keyboad + ANI"]
344
LIST_SCREV = ["TO (timeout)", "CO (carrier operated)", "SE (search)"]
345
LIST_EMCTP = ["TX alarm sound", "TX ANI", "Both"]
346
LIST_RINGT = ["Off"] + ["%s seconds" % x for x in range(1, 10)]
347
LIST_MDF = ["Frequency", "Channel", "Name"]
348
LIST_PONMSG = ["Full", "Message", "Battery voltage"]
349
LIST_COLOR = ["Off", "Blue", "Orange", "Purple"]
350
LIST_REPS = ["1000 Hz", "1450 Hz", "1750 Hz", "2100Hz"]
351
LIST_REPM = ["Off", "Carrier", "CTCSS or DCS", "Tone", "DTMF"]
352
LIST_RPTDL = ["Off"] + ["%s ms" % x for x in range(1, 10)]
353
LIST_ANIL = ["3", "4", "5"]
354
LIST_AB = ["A", "B"]
355
LIST_VFOMR = ["Frequency", "Channel"]
356
LIST_SHIFT = ["Off", "+", "-"]
357
LIST_TXP = ["High", "Low"]
358
LIST_WIDE = ["Wide", "Narrow"]
359
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0]
360
LIST_STEP = [str(x) for x in STEPS]
361

    
362
LIST_5TONE_STANDARDS = ["CCIR1", "CCIR2", "PCCIR", "ZVEI1", "ZVEI2", "ZVEI3", "PZVEI", "DZVEI", "PDZVEI", "EEA", "EIA", "EURO", "CCITT", "NATEL", "MODAT"]
363
LIST_5TONE_STANDARD_PERIODS = ["20", "30", "40", "50", "60", "70", "80", "90", "100", "110", "120", "130", "140", "150", "160", "170", "180", "190", "200"]
364
LIST_2TONE_DEC = ["A-B", "A-C", "A-D", "B-A", "B-C", "B-D", "C-A", "C-B", "C-D", "D-A", "D-B", "D-C"]
365
LIST_2TONE_RESPONSE = ["None", "Alert", "Transpond", "Alert+Transpond"]
366

    
367
# This is a general serial timeout for all serial read functions.
368
# Practice has show that about 0.7 sec will be enough to cover all radios.
369
STIMEOUT = 0.7
370

    
371
# this var controls the verbosity in the debug and by default it's low (False)
372
# make it True and you will to get a very verbose debug.log
373
debug = True
374

    
375
# Power Levels
376
NORMAL_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=25),
377
                       chirp_common.PowerLevel("Low", watts=10)]
378
UV5001_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=50),
379
                       chirp_common.PowerLevel("Low", watts=10)]
380

    
381
# this must be defined globaly
382
POWER_LEVELS = None
383

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

    
388

    
389
##### ID strings #####################################################
390

    
391
# BTECH UV2501 pre-production units
392
UV2501pp_fp = "M2C294"
393
# BTECH UV2501 pre-production units 2 + and 1st Gen radios
394
UV2501pp2_fp = "M29204"
395
# B-TECH UV-2501 second generation (2G) radios
396
UV2501G2_fp = "BTG214"
397
# B-TECH UV-2501 third generation (3G) radios
398
UV2501G3_fp = "BTG324"
399

    
400
# B-TECH UV-2501+220 pre-production units
401
UV2501_220pp_fp = "M3C281"
402
# extra block read for the 2501+220 pre-production units
403
# the same for all of this radios so far
404
UV2501_220pp_id = "      280528"
405
# B-TECH UV-2501+220
406
UV2501_220_fp = "M3G201"
407
# new variant, let's call it Generation 2
408
UV2501_220G2_fp = "BTG211"
409
# B-TECH UV-2501+220 third generation (3G)
410
UV2501_220G3_fp = "BTG311"
411

    
412
# B-TECH UV-5001 pre-production units + 1st Gen radios
413
UV5001pp_fp = "V19204"
414
# B-TECH UV-5001 alpha units
415
UV5001alpha_fp = "V28204"
416
# B-TECH UV-5001 second generation (2G) radios
417
UV5001G2_fp = "BTG214"
418
# B-TECH UV-5001 second generation (2G2)
419
UV5001G22_fp = "V2G204"
420
# B-TECH UV-5001 third generation (3G)
421
UV5001G3_fp = "BTG304"
422

    
423
# special var to know when we found a BTECH Gen 3
424
BTECH3 = [UV2501G3_fp, UV2501_220G3_fp, UV5001G3_fp]
425

    
426

    
427
# WACCOM Mini-8900
428
MINI8900_fp = "M28854"
429

    
430

    
431
# QYT KT-UV980
432
KTUV980_fp = "H28854"
433

    
434
# QYT KT8900
435
KT8900_fp = "M29154"
436
# New generations KT8900
437
KT8900_fp1 = "M2C234"
438
KT8900_fp2 = "M2G1F4"
439
KT8900_fp3 = "M2G2F4"
440
KT8900_fp4 = "M2G304"
441
# this radio has an extra ID
442
KT8900_id = "      303688"
443

    
444
# KT8900R
445
KT8900R_fp = "M3G1F4"
446
# Second Generation
447
KT8900R_fp1 = "M3G214"
448
# another model
449
KT8900R_fp2 = "M3C234"
450
# another model G4?
451
KT8900R_fp3 = "M39164"
452
# this radio has an extra ID
453
KT8900R_id = "280528"
454

    
455

    
456
# LUITON LT-588UV
457
LT588UV_fp = "V2G1F4"
458

    
459

    
460
#### MAGICS
461
# for the Waccom Mini-8900
462
MSTRING_MINI8900 = "\x55\xA5\xB5\x45\x55\x45\x4d\x02"
463
# for the B-TECH UV-2501+220 (including pre production ones)
464
MSTRING_220 = "\x55\x20\x15\x12\x12\x01\x4d\x02"
465
# for the QYT KT8900 & R
466
MSTRING_KT8900 = "\x55\x20\x15\x09\x16\x45\x4D\x02"
467
MSTRING_KT8900R = "\x55\x20\x15\x09\x25\x01\x4D\x02"
468
# magic string for all other models
469
MSTRING = "\x55\x20\x15\x09\x20\x45\x4d\x02"
470

    
471
# this variables controls the forced delay and retry on Linux OS mainly. Added by OE4AMW to workaround Issue 3993
472
NEEDS_DELAY = False
473
RETRY_DELAYED = False
474

    
475
def _clean_buffer(radio):
476
    """Cleaning the read serial buffer, hard timeout to survive an infinite
477
    data stream"""
478

    
479
    # touching the serial timeout to optimize the flushing
480
    # restored at the end to the default value
481
    radio.pipe.timeout = 0.1
482
    dump = "1"
483
    datacount = 0
484

    
485
    try:
486
        while len(dump) > 0:
487
            dump = radio.pipe.read(100)
488
            datacount += len(dump)
489
            # hard limit to survive a infinite serial data stream
490
            # 5 times bigger than a normal rx block (69 bytes)
491
            if datacount > 345:
492
                seriale = "Please check your serial port selection."
493
                raise errors.RadioError(seriale)
494

    
495
        # restore the default serial timeout
496
        radio.pipe.timeout = STIMEOUT
497

    
498
    except Exception:
499
        raise errors.RadioError("Unknown error cleaning the serial buffer")
500

    
501

    
502
def _rawrecv(radio, amount):
503
    """Raw read from the radio device, less intensive way"""
504

    
505
    data = ""
506

    
507
    try:
508
        data = radio.pipe.read(amount)
509

    
510
        # DEBUG
511
        if debug is True:
512
            LOG.debug("<== (%d) bytes:\n\n%s" %
513
                      (len(data), util.hexprint(data)))
514

    
515
        # fail if no data is received
516
        if len(data) == 0:
517
            raise errors.RadioError("No data received from radio")
518

    
519
        # notice on the logs if short
520
        if len(data) < amount:
521
            LOG.warn("Short reading %d bytes from the %d requested." %
522
                     (len(data), amount))
523
            # This problem can be and expression of the MCU getting stuck
524
            # so from now own we must delay the write operations.
525
            global NEEDS_DELAY
526
            NEEDS_DELAY = True
527
            LOG.debug("Delaying future writes.")
528

    
529
    except:
530
        raise errors.RadioError("Error reading data from radio")
531

    
532
    return data
533

    
534

    
535
def _send(radio, data):
536
    """Send data to the radio device"""
537

    
538
    try:
539
        for byte in data:
540
            radio.pipe.write(byte)
541
            # Some OS (mainly Linux ones) are two fast on the serial and
542
            # get the MCU inside the radio stuck in the early stages, this
543
            # hits some models more than others.
544
            #
545
            # To cope with that we introduce a delay on the writes but only if
546
            # we detect this problem, this was found by Michael Wagner who
547
            # proposed a patch for it, well done.
548
            if NEEDS_DELAY:
549
                # 10 msec is proved to be safe, is better to be slow and right
550
                # than fast and some times wrong. (5 msec is tested ok)
551
                sleep(0.010)
552

    
553
        # DEBUG
554
        if debug is True:
555
            if NEEDS_DELAY:
556
                LOG.debug("This write was delayed")
557

    
558
            LOG.debug("==> (%d) bytes:\n\n%s" %
559
                      (len(data), util.hexprint(data)))
560

    
561
    except:
562
        raise errors.RadioError("Error sending data to radio")
563

    
564

    
565
def _make_frame(cmd, addr, length, data=""):
566
    """Pack the info in the headder format"""
567
    frame = "\x06" + struct.pack(">BHB", ord(cmd), addr, length)
568
    # add the data if set
569
    if len(data) != 0:
570
        frame += data
571

    
572
    return frame
573

    
574

    
575
def _recv(radio, addr):
576
    """Get data from the radio all at once to lower syscalls load"""
577

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

    
581
    # get the whole block
582
    block = _rawrecv(radio, BLOCK_SIZE + 5)
583

    
584
    # basic check
585
    if len(block) < (BLOCK_SIZE + 5):
586
        raise errors.RadioError("Short read of the block 0x%04x" % addr)
587

    
588
    # checking for the ack
589
    if block[0] != ACK_CMD:
590
        raise errors.RadioError("Bad ack from radio in block 0x%04x" % addr)
591

    
592
    # header validation
593
    c, a, l = struct.unpack(">BHB", block[1:5])
594
    if a != addr or l != BLOCK_SIZE or c != ord("X"):
595
        LOG.error("Invalid header for block 0x%04x" % addr)
596
        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
597

    
598
        global RETRY_DELAYED
599
        if not RETRY_DELAYED:
600
            # first try with header problems, forcing a write delay
601
            LOG.warn("Failure occured, trying once again with delay")
602
            RETRY_DELAYED = True
603
            global NEEDS_DELAY
604
            NEEDS_DELAY = True
605
            return False
606
        else:
607
            # second try, now we fail.
608
            LOG.debug("This was already a retry")
609
            raise errors.RadioError("Invalid header for block 0x%04x:" % addr)
610

    
611
    # return the data
612
    return block[5:]
613

    
614

    
615
def _start_clone_mode(radio, status):
616
    """Put the radio in clone mode and get the ident string, 3 tries"""
617

    
618
    # cleaning the serial buffer
619
    _clean_buffer(radio)
620

    
621
    # prep the data to show in the UI
622
    status.cur = 0
623
    status.msg = "Identifying the radio..."
624
    status.max = 3
625
    radio.status_fn(status)
626

    
627
    try:
628
        for a in range(0, status.max):
629
            # Update the UI
630
            status.cur = a + 1
631
            radio.status_fn(status)
632

    
633
            # send the magic word
634
            _send(radio, radio._magic)
635

    
636
            # Now you get a x06 of ACK if all goes well
637
            ack = radio.pipe.read(1)
638

    
639
            if ack == "\x06":
640
                # DEBUG
641
                LOG.info("Magic ACK received")
642
                status.cur = status.max
643
                radio.status_fn(status)
644

    
645
                return True
646

    
647
        return False
648

    
649
    except errors.RadioError:
650
        raise
651
    except Exception, e:
652
        raise errors.RadioError("Error sending Magic to radio:\n%s" % e)
653

    
654

    
655
def _do_ident(radio, status, upload=False):
656
    """Put the radio in PROGRAM mode & identify it"""
657
    #  set the serial discipline
658
    radio.pipe.baudrate = 9600
659
    radio.pipe.parity = "N"
660

    
661
    # open the radio into program mode
662
    if _start_clone_mode(radio, status) is False:
663
        msg = "Radio did not enter clone mode"
664
        # warning about old versions of QYT KT8900
665
        if radio.MODEL == "KT8900":
666
            msg += ". You may want to try it as a WACCOM MINI-8900, there is a"
667
            msg += " known variant of this radios that is a clone of it."
668
        raise errors.RadioError(msg)
669

    
670
    # Ok, get the ident string
671
    ident = _rawrecv(radio, 49)
672

    
673
    # basic check for the ident
674
    if len(ident) != 49:
675
        raise errors.RadioError("Radio send a short ident block.")
676

    
677
    # check if ident is OK
678
    itis = False
679
    for fp in radio._fileid:
680
        if fp in ident:
681
            # got it!
682
            itis = True
683
            # checking if we are dealing with a Gen 3 BTECH
684
            if radio.VENDOR == "BTECH" and fp in BTECH3:
685
                radio.btech3 = True
686

    
687
            break
688

    
689
    if itis is False:
690
        LOG.debug("Incorrect model ID, got this:\n\n" + util.hexprint(ident))
691
        raise errors.RadioError("Radio identification failed.")
692

    
693
    # some radios needs a extra read and check for a code on it, this ones
694
    # has the check value in the _id2 var, others simply False
695
    if radio._id2 is not False:
696
        # lower the timeout here as this radios are reseting due to timeout
697
        radio.pipe.timeout = 0.05
698

    
699
        # query & receive the extra ID
700
        _send(radio, _make_frame("S", 0x3DF0, 16))
701
        id2 = _rawrecv(radio, 21)
702

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

    
710
        # ok, the correct string must be in the received data
711
        if radio._id2 not in id2:
712
            LOG.debug("Full *BAD* extra ID on the %s is: \n%s" %
713
                      (radio.MODEL, util.hexprint(id2)))
714
            raise errors.RadioError("The extra ID is wrong, aborting.")
715

    
716
        # this radios need a extra request/answer here on the upload
717
        # the amount of data received depends of the radio type
718
        #
719
        # also the first block of TX must no have the ACK at the beginning
720
        # see _upload for this.
721
        if upload is True:
722
            # send an ACK
723
            _send(radio, ACK_CMD)
724

    
725
            # the amount of data depend on the radio, so far we have two radios
726
            # reading two bytes with an ACK at the end and just ONE with just
727
            # one byte (QYT KT8900)
728
            # the JT-6188 appears a clone of the last, but reads TWO bytes.
729
            #
730
            # we will read two bytes with a custom timeout to not penalize the
731
            # users for this.
732
            #
733
            # we just check for a response and last byte being a ACK, that is
734
            # the common stone for all radios (3 so far)
735
            ack = _rawrecv(radio, 2)
736

    
737
            # checking
738
            if len(ack) == 0 or ack[-1:] != ACK_CMD:
739
                raise errors.RadioError("Radio didn't ACK the upload")
740

    
741
            # restore the default serial timeout
742
            radio.pipe.timeout = STIMEOUT
743

    
744
    # DEBUG
745
    LOG.info("Positive ident, this is a %s %s" % (radio.VENDOR, radio.MODEL))
746

    
747
    return True
748

    
749

    
750
def _download(radio):
751
    """Get the memory map"""
752

    
753
    # UI progress
754
    status = chirp_common.Status()
755

    
756
    # put radio in program mode and identify it
757
    _do_ident(radio, status)
758

    
759
    # the models that doesn't have the extra ID have to make a dummy read here
760
    if radio._id2 is False:
761
        _send(radio, _make_frame("S", 0, BLOCK_SIZE))
762
        discard = _rawrecv(radio, BLOCK_SIZE + 5)
763

    
764
        if debug is True:
765
            LOG.info("Dummy first block read done, got this:\n\n %s",
766
                     util.hexprint(discard))
767

    
768
    # reset the progress bar in the UI
769
    status.max = MEM_SIZE / BLOCK_SIZE
770
    status.msg = "Cloning from radio..."
771
    status.cur = 0
772
    radio.status_fn(status)
773

    
774
    # cleaning the serial buffer
775
    _clean_buffer(radio)
776

    
777
    data = ""
778
    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
779
        # sending the read request
780
        _send(radio, _make_frame("S", addr, BLOCK_SIZE))
781

    
782
        # read
783
        d = _recv(radio, addr)
784

    
785
        if d == False:
786
            # retry to get that block of data.
787
            msg = "Previous block request failed."
788
            msg += " Cleaning buffer and trying again."
789
            LOG.info(msg)
790
            _clean_buffer(radio)
791
            d = _recv(radio, addr)
792
            global RETRY_DELAYED
793
            RETRY_DELAYED = False
794

    
795
        # aggregate the data
796
        data += d
797

    
798
        # UI Update
799
        status.cur = addr / BLOCK_SIZE
800
        status.msg = "Cloning from radio..."
801
        radio.status_fn(status)
802

    
803
    return data
804

    
805

    
806
def _upload(radio):
807
    """Upload procedure"""
808

    
809
    # The UPLOAD mem is restricted to lower than 0x3100,
810
    # so we will overide that here localy
811
    MEM_SIZE = 0x3100
812

    
813
    # UI progress
814
    status = chirp_common.Status()
815

    
816
    # put radio in program mode and identify it
817
    _do_ident(radio, status, True)
818

    
819
    # get the data to upload to radio
820
    data = radio.get_mmap()
821

    
822
    # Reset the UI progress
823
    status.max = MEM_SIZE / TX_BLOCK_SIZE
824
    status.cur = 0
825
    status.msg = "Cloning to radio..."
826
    radio.status_fn(status)
827

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

    
832
    # cleaning the serial buffer
833
    _clean_buffer(radio)
834

    
835
    # the fun start here
836
    for addr in range(0, MEM_SIZE, TX_BLOCK_SIZE):
837
        # getting the block of data to send
838
        d = data[addr:addr + TX_BLOCK_SIZE]
839

    
840
        # build the frame to send
841
        frame = _make_frame("X", addr, TX_BLOCK_SIZE, d)
842

    
843
        # first block must not send the ACK at the beginning for the
844
        # ones that has the extra id, since this have to do a extra step
845
        if addr == 0 and radio._id2 is not False:
846
            frame = frame[1:]
847

    
848
        # send the frame
849
        _send(radio, frame)
850

    
851
        # receiving the response
852
        ack = _rawrecv(radio, 1)
853

    
854
        # basic check
855
        if len(ack) != 1:
856
            raise errors.RadioError("No ACK when writing block 0x%04x" % addr)
857

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

    
861
         # UI Update
862
        status.cur = addr / TX_BLOCK_SIZE
863
        status.msg = "Cloning to radio..."
864
        radio.status_fn(status)
865

    
866

    
867
def model_match(cls, data):
868
    """Match the opened/downloaded image to the correct version"""
869
    rid = data[0x3f70:0x3f76]
870

    
871
    if rid in cls._fileid:
872
        return True
873

    
874
    return False
875

    
876

    
877
def _decode_ranges(low, high):
878
    """Unpack the data in the ranges zones in the memmap and return
879
    a tuple with the integer corresponding to the Mhz it means"""
880
    ilow = int(low[0]) * 100 + int(low[1]) * 10 + int(low[2])
881
    ihigh = int(high[0]) * 100 + int(high[1]) * 10 + int(high[2])
882
    ilow *= 1000000
883
    ihigh *= 1000000
884

    
885
    return (ilow, ihigh)
886

    
887

    
888
def _split(rf, f1, f2):
889
    """Returns False if the two freqs are in the same band (no split)
890
    or True otherwise"""
891

    
892
    # determine if the two freqs are in the same band
893
    for low, high in rf.valid_bands:
894
        if f1 >= low and f1 <= high and \
895
                f2 >= low and f2 <= high:
896
            # if the two freqs are on the same Band this is not a split
897
            return False
898

    
899
    # if you get here is because the freq pairs are split
900
    return False
901

    
902

    
903
class BTech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
904
    """BTECH's UV-5001 and alike radios"""
905
    VENDOR = "BTECH"
906
    MODEL = ""
907
    IDENT = ""
908
    _vhf_range = (130000000, 180000000)
909
    _220_range = (210000000, 231000000)
910
    _uhf_range = (400000000, 521000000)
911
    _upper = 199
912
    _magic = MSTRING
913
    _fileid = None
914
    _id2 = False
915
    btech3 = False
916

    
917
    @classmethod
918
    def get_prompts(cls):
919
        rp = chirp_common.RadioPrompts()
920
        rp.experimental = \
921
            ('This driver is experimental.\n'
922
             '\n'
923
             'Please keep a copy of your memories with the original software '
924
             'if you treasure them, this driver is new and may contain'
925
             ' bugs.\n'
926
             '\n'
927
             )
928
        rp.pre_download = _(dedent("""\
929
            Follow these instructions to download your info:
930

    
931
            1 - Turn off your radio
932
            2 - Connect your interface cable
933
            3 - Turn on your radio
934
            4 - Do the download of your radio data
935

    
936
            """))
937
        rp.pre_upload = _(dedent("""\
938
            Follow these instructions to upload your info:
939

    
940
            1 - Turn off your radio
941
            2 - Connect your interface cable
942
            3 - Turn on your radio
943
            4 - Do the upload of your radio data
944

    
945
            """))
946
        return rp
947

    
948
    def get_features(self):
949
        """Get the radio's features"""
950

    
951
        # we will use the following var as global
952
        global POWER_LEVELS
953

    
954
        rf = chirp_common.RadioFeatures()
955
        rf.has_settings = True
956
        rf.has_bank = False
957
        rf.has_tuning_step = False
958
        rf.can_odd_split = True
959
        rf.has_name = True
960
        rf.has_offset = True
961
        rf.has_mode = True
962
        rf.has_dtcs = True
963
        rf.has_rx_dtcs = True
964
        rf.has_dtcs_polarity = True
965
        rf.has_ctone = True
966
        rf.has_cross = True
967
        rf.valid_modes = MODES
968
        rf.valid_characters = VALID_CHARS
969
        rf.valid_name_length = NAME_LENGTH
970
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
971
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
972
        rf.valid_cross_modes = [
973
            "Tone->Tone",
974
            "DTCS->",
975
            "->DTCS",
976
            "Tone->DTCS",
977
            "DTCS->Tone",
978
            "->Tone",
979
            "DTCS->DTCS"]
980
        rf.valid_skips = SKIP_VALUES
981
        rf.valid_dtcs_codes = DTCS
982
        rf.memory_bounds = (0, self._upper)
983

    
984
        # power levels
985
        if self.MODEL == "UV-5001":
986
            POWER_LEVELS = UV5001_POWER_LEVELS  # Higher power (50W)
987
        else:
988
            POWER_LEVELS = NORMAL_POWER_LEVELS  # Lower power (25W)
989

    
990
        rf.valid_power_levels = POWER_LEVELS
991

    
992
        # bands
993
        rf.valid_bands = [self._vhf_range, self._uhf_range]
994

    
995
        # 2501+220 & KT8900R
996
        if self.MODEL in ["UV-2501+220", "KT8900R"]:
997
            rf.valid_bands.append(self._220_range)
998

    
999
        return rf
1000

    
1001
    def sync_in(self):
1002
        """Download from radio"""
1003
        try:
1004
            data = _download(self)
1005
        except errors.RadioError:
1006
            msg = "First download-attempt failed."
1007
            msg += " Retrying the whole procedure with delayed writes."
1008
            LOG.error(msg)
1009
            global NEEDS_DELAY
1010
            NEEDS_DELAY = True
1011
            data = _download(self)
1012

    
1013
        self._mmap = memmap.MemoryMap(data)
1014
        self.process_mmap()
1015

    
1016
    def sync_out(self):
1017
        """Upload to radio"""
1018
        try:
1019
            _upload(self)
1020
        except errors.RadioError:
1021
            raise
1022
        except Exception, e:
1023
            raise errors.RadioError("Error: %s" % e)
1024

    
1025
    def set_options(self):
1026
        """This is to read the options from the image and set it in the
1027
        environment, for now just the limits of the freqs in the VHF/UHF
1028
        ranges"""
1029

    
1030
        # setting the correct ranges for each radio type
1031
        if self.MODEL in ["UV-2501+220", "KT8900R"]:
1032
            # the model 2501+220 has a segment in 220
1033
            # and a different position in the memmap
1034
            # also the QYT KT8900R
1035
            ranges = self._memobj.ranges220
1036
        else:
1037
            ranges = self._memobj.ranges
1038

    
1039
        # the normal dual bands
1040
        vhf = _decode_ranges(ranges.vhf_low, ranges.vhf_high)
1041
        uhf = _decode_ranges(ranges.uhf_low, ranges.uhf_high)
1042

    
1043
        # DEBUG
1044
        LOG.info("Radio ranges: VHF %d to %d" % vhf)
1045
        LOG.info("Radio ranges: UHF %d to %d" % uhf)
1046

    
1047
        # 220Mhz radios case
1048
        if self.MODEL in ["UV-2501+220", "KT8900R"]:
1049
            vhf2 = _decode_ranges(ranges.vhf2_low, ranges.vhf2_high)
1050
            LOG.info("Radio ranges: VHF(220) %d to %d" % vhf2)
1051
            self._220_range = vhf2
1052

    
1053
        # set the class with the real data
1054
        self._vhf_range = vhf
1055
        self._uhf_range = uhf
1056

    
1057
    def process_mmap(self):
1058
        """Process the mem map into the mem object"""
1059

    
1060
        # Get it
1061
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
1062

    
1063
        # load specific parameters from the radio image
1064
        self.set_options()
1065

    
1066
    def get_raw_memory(self, number):
1067
        return repr(self._memobj.memory[number])
1068

    
1069
    def _decode_tone(self, val):
1070
        """Parse the tone data to decode from mem, it returns:
1071
        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
1072
        pol = None
1073

    
1074
        if val in [0, 65535]:
1075
            return '', None, None
1076
        elif val > 0x0258:
1077
            a = val / 10.0
1078
            return 'Tone', a, pol
1079
        else:
1080
            if val > 0x69:
1081
                index = val - 0x6A
1082
                pol = "R"
1083
            else:
1084
                index = val - 1
1085
                pol = "N"
1086

    
1087
            tone = DTCS[index]
1088
            return 'DTCS', tone, pol
1089

    
1090
    def _encode_tone(self, memval, mode, val, pol):
1091
        """Parse the tone data to encode from UI to mem"""
1092
        if mode == '' or mode is None:
1093
            memval.set_raw("\x00\x00")
1094
        elif mode == 'Tone':
1095
            memval.set_value(val * 10)
1096
        elif mode == 'DTCS':
1097
            # detect the index in the DTCS list
1098
            try:
1099
                index = DTCS.index(val)
1100
                if pol == "N":
1101
                    index += 1
1102
                else:
1103
                    index += 0x6A
1104
                memval.set_value(index)
1105
            except:
1106
                msg = "Digital Tone '%d' is not supported" % value
1107
                LOG.error(msg)
1108
                raise errors.RadioError(msg)
1109
        else:
1110
            msg = "Internal error: invalid mode '%s'" % mode
1111
            LOG.error(msg)
1112
            raise errors.InvalidDataError(msg)
1113

    
1114
    def get_memory(self, number):
1115
        """Get the mem representation from the radio image"""
1116
        _mem = self._memobj.memory[number]
1117
        _names = self._memobj.names[number]
1118

    
1119
        # Create a high-level memory object to return to the UI
1120
        mem = chirp_common.Memory()
1121

    
1122
        # Memory number
1123
        mem.number = number
1124

    
1125
        if _mem.get_raw()[0] == "\xFF":
1126
            mem.empty = True
1127
            return mem
1128

    
1129
        # Freq and offset
1130
        mem.freq = int(_mem.rxfreq) * 10
1131
        # tx freq can be blank
1132
        if _mem.get_raw()[4] == "\xFF":
1133
            # TX freq not set
1134
            mem.offset = 0
1135
            mem.duplex = "off"
1136
        else:
1137
            # TX freq set
1138
            offset = (int(_mem.txfreq) * 10) - mem.freq
1139
            if offset != 0:
1140
                if _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10):
1141
                    mem.duplex = "split"
1142
                    mem.offset = int(_mem.txfreq) * 10
1143
                elif offset < 0:
1144
                    mem.offset = abs(offset)
1145
                    mem.duplex = "-"
1146
                elif offset > 0:
1147
                    mem.offset = offset
1148
                    mem.duplex = "+"
1149
            else:
1150
                mem.offset = 0
1151

    
1152
        # name TAG of the channel
1153
        mem.name = str(_names.name).rstrip("\xFF").replace("\xFF", " ")
1154

    
1155
        # power
1156
        mem.power = POWER_LEVELS[int(_mem.power)]
1157

    
1158
        # wide/narrow
1159
        mem.mode = MODES[int(_mem.wide)]
1160

    
1161
        # skip
1162
        mem.skip = SKIP_VALUES[_mem.add]
1163

    
1164
        # tone data
1165
        rxtone = txtone = None
1166
        txtone = self._decode_tone(_mem.txtone)
1167
        rxtone = self._decode_tone(_mem.rxtone)
1168
        chirp_common.split_tone_decode(mem, txtone, rxtone)
1169

    
1170
        # Extra
1171
        mem.extra = RadioSettingGroup("extra", "Extra")
1172

    
1173
        scramble = RadioSetting("scramble", "Scramble",
1174
                                RadioSettingValueBoolean(bool(_mem.scramble)))
1175
        mem.extra.append(scramble)
1176

    
1177
        bcl = RadioSetting("bcl", "Busy channel lockout",
1178
                           RadioSettingValueBoolean(bool(_mem.bcl)))
1179
        mem.extra.append(bcl)
1180

    
1181
        pttid = RadioSetting("pttid", "PTT ID",
1182
                             RadioSettingValueList(PTTID_LIST,
1183
                                                   PTTID_LIST[_mem.pttid]))
1184
        mem.extra.append(pttid)
1185

    
1186
        # validating scode
1187
        scode = _mem.scode if _mem.scode != 15 else 0
1188
        pttidcode = RadioSetting("scode", "PTT ID signal code",
1189
                                 RadioSettingValueList(
1190
                                     PTTIDCODE_LIST,
1191
                                     PTTIDCODE_LIST[scode]))
1192
        mem.extra.append(pttidcode)
1193

    
1194
        optsig = RadioSetting("optsig", "Optional signaling",
1195
                              RadioSettingValueList(
1196
                                  OPTSIG_LIST,
1197
                                  OPTSIG_LIST[_mem.optsig]))
1198
        mem.extra.append(optsig)
1199

    
1200
        spmute = RadioSetting("spmute", "Speaker mute",
1201
                              RadioSettingValueList(
1202
                                  SPMUTE_LIST,
1203
                                  SPMUTE_LIST[_mem.spmute]))
1204
        mem.extra.append(spmute)
1205

    
1206
        return mem
1207

    
1208
    def set_memory(self, mem):
1209
        """Set the memory data in the eeprom img from the UI"""
1210
        # get the eprom representation of this channel
1211
        _mem = self._memobj.memory[mem.number]
1212
        _names = self._memobj.names[mem.number]
1213

    
1214
        # if empty memmory
1215
        if mem.empty:
1216
            # the channel itself
1217
            _mem.set_raw("\xFF" * 16)
1218
            # the name tag
1219
            _names.set_raw("\xFF" * 16)
1220
            return
1221

    
1222
        # frequency
1223
        _mem.rxfreq = mem.freq / 10
1224

    
1225
        # duplex
1226
        if mem.duplex == "+":
1227
            _mem.txfreq = (mem.freq + mem.offset) / 10
1228
        elif mem.duplex == "-":
1229
            _mem.txfreq = (mem.freq - mem.offset) / 10
1230
        elif mem.duplex == "off":
1231
            for i in _mem.txfreq:
1232
                i.set_raw("\xFF")
1233
        elif mem.duplex == "split":
1234
            _mem.txfreq = mem.offset / 10
1235
        else:
1236
            _mem.txfreq = mem.freq / 10
1237

    
1238
        # tone data
1239
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
1240
            chirp_common.split_tone_encode(mem)
1241
        self._encode_tone(_mem.txtone, txmode, txtone, txpol)
1242
        self._encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
1243

    
1244
        # name TAG of the channel
1245
        if len(mem.name) < NAME_LENGTH:
1246
            # we must pad to NAME_LENGTH chars, " " = "\xFF"
1247
            mem.name = str(mem.name).ljust(NAME_LENGTH, " ")
1248
        _names.name = str(mem.name).replace(" ", "\xFF")
1249

    
1250
        # power, # default power level is high
1251
        _mem.power = 0 if mem.power is None else POWER_LEVELS.index(mem.power)
1252

    
1253
        # wide/narrow
1254
        _mem.wide = MODES.index(mem.mode)
1255

    
1256
        # scan add property
1257
        _mem.add = SKIP_VALUES.index(mem.skip)
1258

    
1259
        # reseting unknowns, this have to be set by hand
1260
        _mem.unknown0 = 0
1261
        _mem.unknown1 = 0
1262
        _mem.unknown2 = 0
1263
        _mem.unknown3 = 0
1264
        _mem.unknown4 = 0
1265
        _mem.unknown5 = 0
1266
        _mem.unknown6 = 0
1267

    
1268
        # extra settings
1269
        if len(mem.extra) > 0:
1270
            # there are setting, parse
1271
            for setting in mem.extra:
1272
                setattr(_mem, setting.get_name(), setting.value)
1273
        else:
1274
            # there is no extra settings, load defaults
1275
            _mem.spmute = 0
1276
            _mem.optsig = 0
1277
            _mem.scramble = 0
1278
            _mem.bcl = 0
1279
            _mem.pttid = 0
1280
            _mem.scode = 0
1281

    
1282
        return mem
1283

    
1284
    def get_settings(self):
1285
        """Translate the bit in the mem_struct into settings in the UI"""
1286
        _mem = self._memobj
1287
        basic = RadioSettingGroup("basic", "Basic Settings")
1288
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
1289
        other = RadioSettingGroup("other", "Other Settings")
1290
        work = RadioSettingGroup("work", "Work Mode Settings")
1291
        fm_presets = RadioSettingGroup("fm_presets", "FM Presets")
1292
        top = RadioSettings(basic, advanced, other, work, fm_presets)
1293

    
1294
        # Basic
1295
        tdr = RadioSetting("settings.tdr", "Transceiver dual receive",
1296
                           RadioSettingValueBoolean(_mem.settings.tdr))
1297
        basic.append(tdr)
1298

    
1299
        sql = RadioSetting("settings.sql", "Squelch level",
1300
                           RadioSettingValueInteger(0, 9, _mem.settings.sql))
1301
        basic.append(sql)
1302

    
1303
        tot = RadioSetting("settings.tot", "Time out timer",
1304
                           RadioSettingValueList(LIST_TOT, LIST_TOT[
1305
                               _mem.settings.tot]))
1306
        basic.append(tot)
1307

    
1308
        if self.MODEL in ("UV-2501", "UV-2501+220", "UV-5001"):
1309
            apo = RadioSetting("settings.apo", "Auto power off timer",
1310
                               RadioSettingValueList(LIST_APO, LIST_APO[
1311
                                   _mem.settings.apo]))
1312
            basic.append(apo)
1313
        else:
1314
            toa = RadioSetting("settings.apo", "Time out alert timer",
1315
                               RadioSettingValueList(LIST_TOA, LIST_TOA[
1316
                                   _mem.settings.apo]))
1317
            basic.append(toa)
1318

    
1319
        abr = RadioSetting("settings.abr", "Backlight timer",
1320
                           RadioSettingValueList(LIST_ABR, LIST_ABR[
1321
                               _mem.settings.abr]))
1322
        basic.append(abr)
1323

    
1324
        beep = RadioSetting("settings.beep", "Key beep",
1325
                            RadioSettingValueBoolean(_mem.settings.beep))
1326
        basic.append(beep)
1327

    
1328
        dtmfst = RadioSetting("settings.dtmfst", "DTMF side tone",
1329
                              RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
1330
                                  _mem.settings.dtmfst]))
1331
        basic.append(dtmfst)
1332

    
1333
        prisc = RadioSetting("settings.prisc", "Priority scan",
1334
                             RadioSettingValueBoolean(_mem.settings.prisc))
1335
        basic.append(prisc)
1336

    
1337
        prich = RadioSetting("settings.prich", "Priority channel",
1338
                             RadioSettingValueInteger(0, 199,
1339
                                 _mem.settings.prich))
1340
        basic.append(prich)
1341

    
1342
        screv = RadioSetting("settings.screv", "Scan resume method",
1343
                             RadioSettingValueList(LIST_SCREV, LIST_SCREV[
1344
                                 _mem.settings.screv]))
1345
        basic.append(screv)
1346

    
1347
        pttlt = RadioSetting("settings.pttlt", "PTT transmit delay",
1348
                             RadioSettingValueInteger(0, 30,
1349
                                 _mem.settings.pttlt))
1350
        basic.append(pttlt)
1351

    
1352
        emctp = RadioSetting("settings.emctp", "Alarm mode",
1353
                             RadioSettingValueList(LIST_EMCTP, LIST_EMCTP[
1354
                                 _mem.settings.emctp]))
1355
        basic.append(emctp)
1356

    
1357
        emcch = RadioSetting("settings.emcch", "Alarm channel",
1358
                             RadioSettingValueInteger(0, 199,
1359
                                 _mem.settings.emcch))
1360
        basic.append(emcch)
1361

    
1362
        ringt = RadioSetting("settings.ringt", "Ring time",
1363
                             RadioSettingValueList(LIST_RINGT, LIST_RINGT[
1364
                                 _mem.settings.ringt]))
1365
        basic.append(ringt)
1366

    
1367
        camdf = RadioSetting("settings.camdf", "Display mode A",
1368
                             RadioSettingValueList(LIST_MDF, LIST_MDF[
1369
                                 _mem.settings.camdf]))
1370
        basic.append(camdf)
1371

    
1372
        cbmdf = RadioSetting("settings.cbmdf", "Display mode B",
1373
                             RadioSettingValueList(LIST_MDF, LIST_MDF[
1374
                                 _mem.settings.cbmdf]))
1375
        basic.append(cbmdf)
1376

    
1377
        if self.MODEL in ("UV-2501", "UV-2501+220", "UV-5001"):
1378
           sync = RadioSetting("settings.sync", "A/B channel sync",
1379
                               RadioSettingValueBoolean(_mem.settings.sync))
1380
           basic.append(sync)
1381
        else:
1382
           autolk = RadioSetting("settings.sync", "Auto keylock",
1383
                                 RadioSettingValueBoolean(_mem.settings.sync))
1384
           basic.append(autolk)
1385

    
1386
        ponmsg = RadioSetting("settings.ponmsg", "Power-on message",
1387
                              RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
1388
                                  _mem.settings.ponmsg]))
1389
        basic.append(ponmsg)
1390

    
1391
        wtled = RadioSetting("settings.wtled", "Standby backlight Color",
1392
                             RadioSettingValueList(LIST_COLOR, LIST_COLOR[
1393
                                 _mem.settings.wtled]))
1394
        basic.append(wtled)
1395

    
1396
        rxled = RadioSetting("settings.rxled", "RX backlight Color",
1397
                             RadioSettingValueList(LIST_COLOR, LIST_COLOR[
1398
                                 _mem.settings.rxled]))
1399
        basic.append(rxled)
1400

    
1401
        txled = RadioSetting("settings.txled", "TX backlight Color",
1402
                             RadioSettingValueList(LIST_COLOR, LIST_COLOR[
1403
                                 _mem.settings.txled]))
1404
        basic.append(txled)
1405

    
1406
        anil = RadioSetting("settings.anil", "ANI length",
1407
                            RadioSettingValueList(LIST_ANIL, LIST_ANIL[
1408
                                _mem.settings.anil]))
1409
        basic.append(anil)
1410

    
1411
        reps = RadioSetting("settings.reps", "Relay signal (tone burst)",
1412
                            RadioSettingValueList(LIST_REPS, LIST_REPS[
1413
                                _mem.settings.reps]))
1414
        basic.append(reps)
1415

    
1416
        repm = RadioSetting("settings.repm", "Relay condition",
1417
                            RadioSettingValueList(LIST_REPM, LIST_REPM[
1418
                                _mem.settings.repm]))
1419
        basic.append(repm)
1420

    
1421
        if self.MODEL in ("UV-2501", "UV-2501+220", "UV-5001"):
1422
            tdrab = RadioSetting("settings.tdrab", "TDR return time",
1423
                                 RadioSettingValueList(LIST_ABR, LIST_ABR[
1424
                                     _mem.settings.tdrab]))
1425
            basic.append(tdrab)
1426

    
1427
            ste = RadioSetting("settings.ste", "Squelch tail eliminate",
1428
                               RadioSettingValueBoolean(_mem.settings.ste))
1429
            basic.append(ste)
1430

    
1431
            rpste = RadioSetting("settings.rpste", "Repeater STE",
1432
                                 RadioSettingValueList(LIST_RINGT, LIST_RINGT[
1433
                                     _mem.settings.rpste]))
1434
            basic.append(rpste)
1435

    
1436
            rptdl = RadioSetting("settings.rptdl", "Repeater STE delay",
1437
                                 RadioSettingValueList(LIST_RPTDL, LIST_RPTDL[
1438
                                     _mem.settings.rptdl]))
1439
            basic.append(rptdl)
1440

    
1441
        if str(_mem.fingerprint.fp) in BTECH3:
1442

    
1443
            mgain = RadioSetting("settings.mgain", "Mic gain",
1444
                                 RadioSettingValueInteger(0, 120,
1445
                                     _mem.settings.mgain))
1446
            basic.append(mgain)
1447

    
1448
            dtmfg = RadioSetting("settings.dtmfg", "DTMF gain",
1449
                                 RadioSettingValueInteger(0, 60,
1450
                                     _mem.settings.dtmfg))
1451
            basic.append(dtmfg)
1452

    
1453
        # Advanced
1454
        def _filter(name):
1455
            filtered = ""
1456
            for char in str(name):
1457
                if char in VALID_CHARS:
1458
                    filtered += char
1459
                else:
1460
                    filtered += " "
1461
            return filtered
1462

    
1463
        _msg = self._memobj.poweron_msg
1464
        line1 = RadioSetting("poweron_msg.line1", "Power-on message line 1",
1465
                             RadioSettingValueString(0, 6, _filter(
1466
                                 _msg.line1)))
1467
        advanced.append(line1)
1468
        line2 = RadioSetting("poweron_msg.line2", "Power-on message line 2",
1469
                             RadioSettingValueString(0, 6, _filter(
1470
                                 _msg.line2)))
1471
        advanced.append(line2)
1472

    
1473
        if self.MODEL in ("UV-2501", "UV-5001"):
1474
            vfomren = RadioSetting("settings2.vfomren", "VFO/MR switching",
1475
                                   RadioSettingValueBoolean(
1476
                                       not _mem.settings2.vfomren))
1477
            advanced.append(vfomren)
1478

    
1479
            reseten = RadioSetting("settings2.reseten", "RESET",
1480
                                   RadioSettingValueBoolean(
1481
                                       _mem.settings2.reseten))
1482
            advanced.append(reseten)
1483

    
1484
            menuen = RadioSetting("settings2.menuen", "Menu",
1485
                                  RadioSettingValueBoolean(
1486
                                      _mem.settings2.menuen))
1487
            advanced.append(menuen)
1488

    
1489
        # Other
1490
        def convert_bytes_to_limit(bytes):
1491
            limit = ""
1492
            for byte in bytes:
1493
                if byte < 10:
1494
                    limit += chr(byte + 0x30)
1495
                else:
1496
                    break
1497
            return limit
1498

    
1499
        if self.MODEL in ["UV-2501+220", "KT8900R"]:
1500
            _ranges = self._memobj.ranges220
1501
            ranges = "ranges220"
1502
        else:
1503
            _ranges = self._memobj.ranges
1504
            ranges = "ranges"
1505

    
1506
        _limit = convert_bytes_to_limit(_ranges.vhf_low)
1507
        val = RadioSettingValueString(0, 3, _limit)
1508
        val.set_mutable(False)
1509
        vhf_low = RadioSetting("%s.vhf_low" % ranges, "VHF low", val)
1510
        other.append(vhf_low)
1511

    
1512
        _limit = convert_bytes_to_limit(_ranges.vhf_high)
1513
        val = RadioSettingValueString(0, 3, _limit)
1514
        val.set_mutable(False)
1515
        vhf_high = RadioSetting("%s.vhf_high" % ranges, "VHF high", val)
1516
        other.append(vhf_high)
1517

    
1518
        if self.MODEL in ["UV-2501+220", "KT8900R"]:
1519
            _limit = convert_bytes_to_limit(_ranges.vhf2_low)
1520
            val = RadioSettingValueString(0, 3, _limit)
1521
            val.set_mutable(False)
1522
            vhf2_low = RadioSetting("%s.vhf2_low" % ranges, "VHF2 low", val)
1523
            other.append(vhf2_low)
1524

    
1525
            _limit = convert_bytes_to_limit(_ranges.vhf2_high)
1526
            val = RadioSettingValueString(0, 3, _limit)
1527
            val.set_mutable(False)
1528
            vhf2_high = RadioSetting("%s.vhf2_high" % ranges, "VHF2 high", val)
1529
            other.append(vhf2_high)
1530

    
1531
        _limit = convert_bytes_to_limit(_ranges.uhf_low)
1532
        val = RadioSettingValueString(0, 3, _limit)
1533
        val.set_mutable(False)
1534
        uhf_low = RadioSetting("%s.uhf_low" % ranges, "UHF low", val)
1535
        other.append(uhf_low)
1536

    
1537
        _limit = convert_bytes_to_limit(_ranges.uhf_high)
1538
        val = RadioSettingValueString(0, 3, _limit)
1539
        val.set_mutable(False)
1540
        uhf_high = RadioSetting("%s.uhf_high" % ranges, "UHF high", val)
1541
        other.append(uhf_high)
1542

    
1543
        val = RadioSettingValueString(0, 6, _filter(_mem.fingerprint.fp))
1544
        val.set_mutable(False)
1545
        fp = RadioSetting("fingerprint.fp", "Fingerprint", val)
1546
        other.append(fp)
1547

    
1548
        # Work
1549
        dispab = RadioSetting("settings2.dispab", "Display",
1550
                              RadioSettingValueList(LIST_AB,LIST_AB[
1551
                                  _mem.settings2.dispab]))
1552
        work.append(dispab)
1553

    
1554
        vfomr = RadioSetting("settings2.vfomr", "VFO/MR mode",
1555
                             RadioSettingValueList(LIST_VFOMR,LIST_VFOMR[
1556
                                 _mem.settings2.vfomr]))
1557
        work.append(vfomr)
1558

    
1559
        keylock = RadioSetting("settings2.keylock", "Keypad lock",
1560
                           RadioSettingValueBoolean(_mem.settings2.keylock))
1561
        work.append(keylock)
1562

    
1563
        mrcha = RadioSetting("settings2.mrcha", "MR A channel",
1564
                             RadioSettingValueInteger(0, 199,
1565
                                 _mem.settings2.mrcha))
1566
        work.append(mrcha)
1567

    
1568
        mrchb = RadioSetting("settings2.mrchb", "MR B channel",
1569
                             RadioSettingValueInteger(0, 199,
1570
                                 _mem.settings2.mrchb))
1571
        work.append(mrchb)
1572

    
1573
        def convert_bytes_to_freq(bytes):
1574
            real_freq = 0
1575
            for byte in bytes:
1576
                real_freq = (real_freq * 10) + byte
1577
            return chirp_common.format_freq(real_freq * 10)
1578

    
1579
        def my_validate(value):
1580
            value = chirp_common.parse_freq(value)
1581
            if "+220" in self.MODEL:
1582
                if 180000000 <= value and value < 210000000:
1583
                    msg = ("Can't be between 180.00000-210.00000")
1584
                    raise InvalidValueError(msg)
1585
                elif 231000000 <= value and value < 400000000:
1586
                    msg = ("Can't be between 231.00000-400.00000")
1587
                    raise InvalidValueError(msg)
1588
            elif "8900R" in self.MODEL:
1589
                if 180000000 <= value and value < 240000000:
1590
                    msg = ("Can't be between 180.00000-240.00000")
1591
                    raise InvalidValueError(msg)
1592
                elif 271000000 <= value and value < 400000000:
1593
                    msg = ("Can't be between 271.00000-400.00000")
1594
                    raise InvalidValueError(msg)
1595
            elif 180000000 <= value and value < 400000000:
1596
                msg = ("Can't be between 180.00000-400.00000")
1597
                raise InvalidValueError(msg)
1598
            return chirp_common.format_freq(value)
1599

    
1600
        def apply_freq(setting, obj):
1601
            value = chirp_common.parse_freq(str(setting.value)) / 10
1602
            for i in range(7, -1, -1):
1603
                obj.freq[i] = value % 10
1604
                value /= 10
1605

    
1606
        val1a = RadioSettingValueString(0, 10, convert_bytes_to_freq(
1607
                                        _mem.vfo.a.freq))
1608
        val1a.set_validate_callback(my_validate)
1609
        vfoafreq = RadioSetting("vfo.a.freq", "VFO A frequency", val1a)
1610
        vfoafreq.set_apply_callback(apply_freq, _mem.vfo.a)
1611
        work.append(vfoafreq)
1612

    
1613
        val1b = RadioSettingValueString(0, 10, convert_bytes_to_freq(
1614
                                        _mem.vfo.b.freq))
1615
        val1b.set_validate_callback(my_validate)
1616
        vfobfreq = RadioSetting("vfo.b.freq", "VFO B frequency", val1b)
1617
        vfobfreq.set_apply_callback(apply_freq, _mem.vfo.b)
1618
        work.append(vfobfreq)
1619

    
1620
        vfoashiftd = RadioSetting("vfo.a.shiftd", "VFO A shift",
1621
                                  RadioSettingValueList(LIST_SHIFT, LIST_SHIFT[
1622
                                      _mem.vfo.a.shiftd]))
1623
        work.append(vfoashiftd)
1624

    
1625
        vfobshiftd = RadioSetting("vfo.b.shiftd", "VFO B shift",
1626
                                  RadioSettingValueList(LIST_SHIFT, LIST_SHIFT[
1627
                                      _mem.vfo.b.shiftd]))
1628
        work.append(vfobshiftd)
1629

    
1630
        def convert_bytes_to_offset(bytes):
1631
            real_offset = 0
1632
            for byte in bytes:
1633
                real_offset = (real_offset * 10) + byte
1634
            return chirp_common.format_freq(real_offset * 10000)
1635

    
1636
        def apply_offset(setting, obj):
1637
            value = chirp_common.parse_freq(str(setting.value)) / 10000
1638
            for i in range(3, -1, -1):
1639
                obj.offset[i] = value % 10
1640
                value /= 10
1641

    
1642
        val1a = RadioSettingValueString(0, 10, convert_bytes_to_offset(
1643
                                        _mem.vfo.a.offset))
1644
        vfoaoffset = RadioSetting("vfo.a.offset",
1645
                                  "VFO A offset (0.00-99.95)", val1a)
1646
        vfoaoffset.set_apply_callback(apply_offset, _mem.vfo.a)
1647
        work.append(vfoaoffset)
1648

    
1649
        val1b = RadioSettingValueString(0, 10, convert_bytes_to_offset(
1650
                                        _mem.vfo.b.offset))
1651
        vfoboffset = RadioSetting("vfo.b.offset",
1652
                                  "VFO B offset (0.00-99.95)", val1b)
1653
        vfoboffset.set_apply_callback(apply_offset, _mem.vfo.b)
1654
        work.append(vfoboffset)
1655

    
1656
        vfoatxp = RadioSetting("vfo.a.power", "VFO A power",
1657
                                RadioSettingValueList(LIST_TXP,LIST_TXP[
1658
                                    _mem.vfo.a.power]))
1659
        work.append(vfoatxp)
1660

    
1661
        vfobtxp = RadioSetting("vfo.b.power", "VFO B power",
1662
                                RadioSettingValueList(LIST_TXP,LIST_TXP[
1663
                                    _mem.vfo.b.power]))
1664
        work.append(vfobtxp)
1665

    
1666
        vfoawide = RadioSetting("vfo.a.wide", "VFO A bandwidth",
1667
                                RadioSettingValueList(LIST_WIDE,LIST_WIDE[
1668
                                    _mem.vfo.a.wide]))
1669
        work.append(vfoawide)
1670

    
1671
        vfobwide = RadioSetting("vfo.b.wide", "VFO B bandwidth",
1672
                                RadioSettingValueList(LIST_WIDE,LIST_WIDE[
1673
                                    _mem.vfo.b.wide]))
1674
        work.append(vfobwide)
1675

    
1676
        vfoastep = RadioSetting("vfo.a.step", "VFO A step",
1677
                                RadioSettingValueList(LIST_STEP,LIST_STEP[
1678
                                    _mem.vfo.a.step]))
1679
        work.append(vfoastep)
1680

    
1681
        vfobstep = RadioSetting("vfo.b.step", "VFO B step",
1682
                                RadioSettingValueList(LIST_STEP,LIST_STEP[
1683
                                    _mem.vfo.b.step]))
1684
        work.append(vfobstep)
1685

    
1686
        vfoaoptsig = RadioSetting("vfo.a.optsig", "VFO A optional signal",
1687
                                  RadioSettingValueList(OPTSIG_LIST,
1688
                                      OPTSIG_LIST[_mem.vfo.a.optsig]))
1689
        work.append(vfoaoptsig)
1690

    
1691
        vfoboptsig = RadioSetting("vfo.b.optsig", "VFO B optional signal",
1692
                                  RadioSettingValueList(OPTSIG_LIST,
1693
                                      OPTSIG_LIST[_mem.vfo.b.optsig]))
1694
        work.append(vfoboptsig)
1695

    
1696
        vfoaspmute = RadioSetting("vfo.a.spmute", "VFO A speaker mute",
1697
                                  RadioSettingValueList(SPMUTE_LIST,
1698
                                      SPMUTE_LIST[_mem.vfo.a.spmute]))
1699
        work.append(vfoaspmute)
1700

    
1701
        vfobspmute = RadioSetting("vfo.b.spmute", "VFO B speaker mute",
1702
                                  RadioSettingValueList(SPMUTE_LIST,
1703
                                      SPMUTE_LIST[_mem.vfo.b.spmute]))
1704
        work.append(vfobspmute)
1705

    
1706
        vfoascr = RadioSetting("vfo.a.scramble", "VFO A scramble",
1707
                               RadioSettingValueBoolean(_mem.vfo.a.scramble))
1708
        work.append(vfoascr)
1709

    
1710
        vfobscr = RadioSetting("vfo.b.scramble", "VFO B scramble",
1711
                               RadioSettingValueBoolean(_mem.vfo.b.scramble))
1712
        work.append(vfobscr)
1713

    
1714
        vfoascode = RadioSetting("vfo.a.scode", "VFO A PTT-ID",
1715
                                 RadioSettingValueList(PTTIDCODE_LIST,
1716
                                     PTTIDCODE_LIST[_mem.vfo.a.scode]))
1717
        work.append(vfoascode)
1718

    
1719
        vfobscode = RadioSetting("vfo.b.scode", "VFO B PTT-ID",
1720
                                 RadioSettingValueList(PTTIDCODE_LIST,
1721
                                     PTTIDCODE_LIST[_mem.vfo.b.scode]))
1722
        work.append(vfobscode)
1723

    
1724
        pttid = RadioSetting("settings.pttid", "PTT ID",
1725
                             RadioSettingValueList(PTTID_LIST,
1726
                                 PTTID_LIST[_mem.settings.pttid]))
1727
        work.append(pttid)
1728
        
1729
        
1730
        #87.5-108MHz
1731
        def my_fm_validate(value):
1732
            value = chirp_common.parse_freq(value)
1733
            if not 87500000 <= value and value < 108000000:
1734
                msg = ("Must be between 87.5 and 108 MHz")
1735
                raise InvalidValueError(msg)
1736
            return chirp_common.format_freq(value)
1737
        
1738
        def apply_fm_preset_name(setting, obj):
1739
            valstring = str (setting.value)
1740
            for i in range(0,6):
1741
                LOG.debug("blop " + valstring[i])
1742
                if valstring[i] in VALID_CHARS:
1743
                    obj[i] = valstring[i]
1744
                else:
1745
                    obj[i] = '0xff'
1746
        
1747
        # FM Presets 87.5-108MHz
1748
        _presets = self._memobj.fm_radio_preset
1749
        counter = 1
1750
        for preset in _presets:
1751
            line = RadioSetting("fm_presets_"+ str(counter), "Station name "+ str(counter),
1752
                                 RadioSettingValueString(0, 6, _filter(
1753
                                     preset.broadcast_station_name)))
1754
            line.set_apply_callback(apply_fm_preset_name, preset.broadcast_station_name)
1755
            val = RadioSettingValueString(0, 10, convert_bytes_to_freq(preset.freq))
1756
            fmfreq = RadioSetting("fm_presets_"+ str(counter) + "_freq", "Frequency "+ str(counter), val)
1757
            val.set_validate_callback(my_fm_validate)
1758
            fmfreq.set_apply_callback(apply_freq, preset)
1759
            fm_presets.append(line)
1760
            fm_presets.append(fmfreq)
1761
            
1762
            counter = counter + 1
1763
        return top
1764

    
1765
    def set_settings(self, settings):
1766
        _settings = self._memobj.settings
1767
        for element in settings:
1768
            if not isinstance(element, RadioSetting):
1769
                if element.get_name() == "fm_preset":
1770
                    self._set_fm_preset(element)
1771
                else:
1772
                    self.set_settings(element)
1773
                    continue
1774
            else:
1775
                try:
1776
                    name = element.get_name()
1777
                    if "." in name:
1778
                        bits = name.split(".")
1779
                        obj = self._memobj
1780
                        for bit in bits[:-1]:
1781
                            if "/" in bit:
1782
                                bit, index = bit.split("/", 1)
1783
                                index = int(index)
1784
                                obj = getattr(obj, bit)[index]
1785
                            else:
1786
                                obj = getattr(obj, bit)
1787
                        setting = bits[-1]
1788
                    else:
1789
                        obj = _settings
1790
                        setting = element.get_name()
1791

    
1792
                    if element.has_apply_callback():
1793
                        LOG.debug("Using apply callback")
1794
                        element.run_apply_callback()
1795
                    elif setting == "vfomren":
1796
                        setattr(obj, setting, not int(element.value))
1797
                    elif element.value.get_mutable():
1798
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1799
                        setattr(obj, setting, element.value)
1800
                except Exception, e:
1801
                    LOG.debug(element.get_name())
1802
                    raise
1803

    
1804
    @classmethod
1805
    def match_model(cls, filedata, filename):
1806
        match_size = False
1807
        match_model = False
1808

    
1809
        # testing the file data size
1810
        if len(filedata) == MEM_SIZE:
1811
            match_size = True
1812

    
1813
        # testing the firmware model fingerprint
1814
        match_model = model_match(cls, filedata)
1815

    
1816
        if match_size and match_model:
1817
            return True
1818
        else:
1819
            return False
1820

    
1821

    
1822
# Declaring Aliases (Clones of the real radios)
1823
class JT2705M(chirp_common.Alias):
1824
    VENDOR = "Jetstream"
1825
    MODEL = "JT2705M"
1826

    
1827

    
1828
class JT6188Mini(chirp_common.Alias):
1829
    VENDOR = "Juentai"
1830
    MODEL = "JT-6188 Mini"
1831

    
1832

    
1833
class JT6188Plus(chirp_common.Alias):
1834
    VENDOR = "Juentai"
1835
    MODEL = "JT-6188 Plus"
1836

    
1837

    
1838
class SSGT890(chirp_common.Alias):
1839
    VENDOR = "Sainsonic"
1840
    MODEL = "GT-890"
1841

    
1842

    
1843
class ZastoneMP300(chirp_common.Alias):
1844
    VENDOR = "Zastone"
1845
    MODEL = "MP-300"
1846

    
1847

    
1848
# real radios
1849
@directory.register
1850
class UV2501(BTech):
1851
    """Baofeng Tech UV2501"""
1852
    MODEL = "UV-2501"
1853
    _fileid = [UV2501G3_fp,
1854
               UV2501G2_fp,
1855
               UV2501pp2_fp,
1856
               UV2501pp_fp]
1857

    
1858

    
1859
@directory.register
1860
class UV2501_220(BTech):
1861
    """Baofeng Tech UV2501+220"""
1862
    MODEL = "UV-2501+220"
1863
    _magic = MSTRING_220
1864
    _id2 = UV2501_220pp_id
1865
    _fileid = [UV2501_220G3_fp,
1866
               UV2501_220G2_fp,
1867
               UV2501_220_fp,
1868
               UV2501_220pp_fp]
1869

    
1870

    
1871
@directory.register
1872
class UV5001(BTech):
1873
    """Baofeng Tech UV5001"""
1874
    MODEL = "UV-5001"
1875
    _fileid = [UV5001G3_fp,
1876
               UV5001G22_fp,
1877
               UV5001G2_fp,
1878
               UV5001alpha_fp,
1879
               UV5001pp_fp]
1880

    
1881

    
1882
@directory.register
1883
class MINI8900(BTech):
1884
    """WACCOM MINI-8900"""
1885
    VENDOR = "WACCOM"
1886
    MODEL = "MINI-8900"
1887
    _magic = MSTRING_MINI8900
1888
    _fileid = [MINI8900_fp, ]
1889
    # Clones
1890
    ALIASES = [JT6188Plus, ]
1891

    
1892

    
1893
@directory.register
1894
class KTUV980(BTech):
1895
    """QYT KT-UV980"""
1896
    VENDOR = "QYT"
1897
    MODEL = "KT-UV980"
1898
    _vhf_range = (136000000, 175000000)
1899
    _uhf_range = (400000000, 481000000)
1900
    _magic = MSTRING_MINI8900
1901
    _fileid = [KTUV980_fp, ]
1902
    # Clones
1903
    ALIASES = [JT2705M, ]
1904

    
1905
# Please note that there is a version of this radios that is a clone of the
1906
# Waccom Mini8900, maybe an early version?
1907
@directory.register
1908
class KT9800(BTech):
1909
    """QYT KT8900"""
1910
    VENDOR = "QYT"
1911
    MODEL = "KT8900"
1912
    _vhf_range = (136000000, 175000000)
1913
    _uhf_range = (400000000, 481000000)
1914
    _magic = MSTRING_KT8900
1915
    _fileid = [KT8900_fp,
1916
               KT8900_fp1,
1917
               KT8900_fp2,
1918
               KT8900_fp3,
1919
               KT8900_fp4]
1920
    _id2 = KT8900_id
1921
    # Clones
1922
    ALIASES = [JT6188Mini, SSGT890, ZastoneMP300]
1923

    
1924

    
1925
@directory.register
1926
class KT9800R(BTech):
1927
    """QYT KT8900R"""
1928
    VENDOR = "QYT"
1929
    MODEL = "KT8900R"
1930
    _vhf_range = (136000000, 175000000)
1931
    _220_range = (240000000, 271000000)
1932
    _uhf_range = (400000000, 481000000)
1933
    _magic = MSTRING_KT8900R
1934
    _fileid = [KT8900R_fp,
1935
               KT8900R_fp1,
1936
               KT8900R_fp2,
1937
               KT8900R_fp3]
1938
    _id2 = KT8900R_id
1939

    
1940

    
1941
@directory.register
1942
class LT588UV(BTech):
1943
    """LUITON LT-588UV"""
1944
    VENDOR = "LUITON"
1945
    MODEL = "LT-588UV"
1946
    _vhf_range = (136000000, 175000000)
1947
    _uhf_range = (400000000, 481000000)
1948
    _magic = MSTRING_KT8900
1949
    _fileid = [LT588UV_fp, ]
    (1-1/1)