Project

General

Profile

Feature #10505 » uv5r_10505_v2.py

Jim Unroe, 09/21/2023 07:06 PM

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

    
16
from builtins import bytes
17

    
18
import struct
19
import time
20
import logging
21

    
22
from chirp import chirp_common, errors, util, directory, memmap
23
from chirp import bitwise
24
from chirp.settings import RadioSetting, RadioSettingGroup, \
25
    RadioSettingValueInteger, RadioSettingValueList, \
26
    RadioSettingValueBoolean, RadioSettingValueString, \
27
    RadioSettingValueFloat, InvalidValueError, RadioSettings
28

    
29
LOG = logging.getLogger(__name__)
30

    
31
MEM_FORMAT = """
32
#seekto 0x0008;
33
struct {
34
  lbcd rxfreq[4];
35
  lbcd txfreq[4];
36
  ul16 rxtone;
37
  ul16 txtone;
38
  u8 unused1:3,
39
     isuhf:1,
40
     scode:4;
41
  u8 unknown1:7,
42
     txtoneicon:1;
43
  u8 mailicon:3,
44
     unknown2:3,
45
     lowpower:2;
46
  u8 unknown3:1,
47
     wide:1,
48
     unknown4:2,
49
     bcl:1,
50
     scan:1,
51
     pttid:2;
52
} memory[128];
53

    
54
#seekto 0x0B08;
55
struct {
56
  u8 code[5];
57
  u8 unused[11];
58
} pttid[15];
59

    
60
#seekto 0x0C88;
61
struct {
62
  u8 code222[3];
63
  u8 unused222[2];
64
  u8 code333[3];
65
  u8 unused333[2];
66
  u8 alarmcode[3];
67
  u8 unused119[2];
68
  u8 unknown1;
69
  u8 code555[3];
70
  u8 unused555[2];
71
  u8 code666[3];
72
  u8 unused666[2];
73
  u8 code777[3];
74
  u8 unused777[2];
75
  u8 unknown2;
76
  u8 code60606[5];
77
  u8 code70707[5];
78
  u8 code[5];
79
  u8 unused1:6,
80
     aniid:2;
81
  u8 unknown[2];
82
  u8 dtmfon;
83
  u8 dtmfoff;
84
} ani;
85

    
86
#seekto 0x0E28;
87
struct {
88
  u8 squelch;
89
  u8 step;
90
  u8 unknown1;
91
  u8 save;
92
  u8 vox;
93
  u8 unknown2;
94
  u8 abr;
95
  u8 tdr;
96
  u8 beep;
97
  u8 timeout;
98
  u8 unknown3[4];
99
  u8 voice;
100
  u8 unknown4;
101
  u8 dtmfst;
102
  u8 unknown5;
103
  u8 unknown12:6,
104
     screv:2;
105
  u8 pttid;
106
  u8 pttlt;
107
  u8 mdfa;
108
  u8 mdfb;
109
  u8 bcl;
110
  u8 autolk; // NOTE: The UV-6 calls this byte voxenable, but the UV-5R
111
             // calls it autolk. Since this is a minor difference, it will
112
             // be referred to by the wrong name for the UV-6.
113
  u8 sftd;
114
  u8 unknown6[3];
115
  u8 wtled;
116
  u8 rxled;
117
  u8 txled;
118
  u8 almod;
119
  u8 band;
120
  u8 tdrab;
121
  u8 ste;
122
  u8 rpste;
123
  u8 rptrl;
124
  u8 ponmsg;
125
  u8 roger;
126
  u8 rogerrx;
127
  u8 tdrch; // NOTE: The UV-82HP calls this byte rtone, but the UV-6
128
            // calls it tdrch. Since this is a minor difference, it will
129
            // be referred to by the wrong name for the UV-82HP.
130
  u8 displayab:1,
131
     unknown1:2,
132
     fmradio:1,
133
     alarm:1,
134
     unknown2:1,
135
     reset:1,
136
     menu:1;
137
  u8 unknown1:6,
138
     singleptt:1,
139
     vfomrlock:1;
140
  u8 workmode;
141
  u8 keylock;
142
} settings;
143

    
144
#seekto 0x0E7E;
145
struct {
146
  u8 unused1:1,
147
     mrcha:7;
148
  u8 unused2:1,
149
     mrchb:7;
150
} wmchannel;
151

    
152
#seekto 0x0F10;
153
struct {
154
  u8 freq[8];
155
  u8 offset[6];
156
  ul16 rxtone;
157
  ul16 txtone;
158
  u8 unused1:7,
159
     band:1;
160
  u8 unknown3;
161
  u8 unused2:2,
162
     sftd:2,
163
     scode:4;
164
  u8 unknown4;
165
  u8 unused3:1
166
     step:3,
167
     unused4:4;
168
  u8 txpower:1,
169
     widenarr:1,
170
     unknown5:4,
171
     txpower3:2;
172
} vfoa;
173

    
174
#seekto 0x0F30;
175
struct {
176
  u8 freq[8];
177
  u8 offset[6];
178
  ul16 rxtone;
179
  ul16 txtone;
180
  u8 unused1:7,
181
     band:1;
182
  u8 unknown3;
183
  u8 unused2:2,
184
     sftd:2,
185
     scode:4;
186
  u8 unknown4;
187
  u8 unused3:1
188
     step:3,
189
     unused4:4;
190
  u8 txpower:1,
191
     widenarr:1,
192
     unknown5:4,
193
     txpower3:2;
194
} vfob;
195

    
196
#seekto 0x0F56;
197
u16 fm_presets;
198

    
199
#seekto 0x1008;
200
struct {
201
  char name[7];
202
  u8 unknown2[9];
203
} names[128];
204

    
205
#seekto 0x1818;
206
struct {
207
  char line1[7];
208
  char line2[7];
209
} sixpoweron_msg;
210

    
211
#seekto 0x%04X;
212
struct {
213
  char line1[7];
214
  char line2[7];
215
} poweron_msg;
216

    
217
#seekto 0x1838;
218
struct {
219
  char line1[7];
220
  char line2[7];
221
} firmware_msg;
222

    
223
struct limit {
224
  u8 enable;
225
  bbcd lower[2];
226
  bbcd upper[2];
227
};
228

    
229
#seekto 0x1908;
230
struct {
231
  struct limit vhf;
232
  struct limit uhf;
233
} limits_new;
234

    
235
#seekto 0x1910;
236
struct {
237
  u8 unknown1[2];
238
  struct limit vhf;
239
  u8 unknown2;
240
  u8 unknown3[8];
241
  u8 unknown4[2];
242
  struct limit uhf;
243
} limits_old;
244

    
245
struct squelch {
246
  u8 sql0;
247
  u8 sql1;
248
  u8 sql2;
249
  u8 sql3;
250
  u8 sql4;
251
  u8 sql5;
252
  u8 sql6;
253
  u8 sql7;
254
  u8 sql8;
255
  u8 sql9;
256
};
257

    
258
#seekto 0x18A8;
259
struct {
260
  struct squelch vhf;
261
  u8 unknown1[6];
262
  u8 unknown2[16];
263
  struct squelch uhf;
264
} squelch_new;
265

    
266
#seekto 0x18E8;
267
struct {
268
  struct squelch vhf;
269
  u8 unknown[6];
270
  struct squelch uhf;
271
} squelch_old;
272

    
273
"""
274

    
275
# 0x1EC0 - 0x2000
276

    
277
vhf_220_radio = b"\x02"
278

    
279
BASETYPE_UV5R = [b"BFS", b"BFB", b"N5R-2", b"N5R2", b"N5RV", b"BTS", b"D5R2",
280
                 b"B5R2"]
281
BASETYPE_F11 = [b"USA"]
282
BASETYPE_UV82 = [b"US2S2", b"B82S", b"BF82", b"N82-2", b"N822"]
283
BASETYPE_BJ55 = [b"BJ55"]  # needed for for the Baojie UV-55 in bjuv55.py
284
BASETYPE_UV6 = [b"BF1", b"UV6"]
285
BASETYPE_KT980HP = [b"BFP3V3 B"]
286
BASETYPE_F8HP = [b"BFP3V3 F", b"N5R-3", b"N5R3", b"F5R3", b"BFT", b"N5RV"]
287
BASETYPE_UV82HP = [b"N82-3", b"N823", b"N5R2"]
288
BASETYPE_UV82X3 = [b"HN5RV01"]
289
BASETYPE_LIST = BASETYPE_UV5R + BASETYPE_F11 + BASETYPE_UV82 + \
290
    BASETYPE_BJ55 + BASETYPE_UV6 + BASETYPE_KT980HP + \
291
    BASETYPE_F8HP + BASETYPE_UV82HP + BASETYPE_UV82X3
292

    
293
AB_LIST = ["A", "B"]
294
ALMOD_LIST = ["Site", "Tone", "Code"]
295
BANDWIDTH_LIST = ["Wide", "Narrow"]
296
COLOR_LIST = ["Off", "Blue", "Orange", "Purple"]
297
DTMFSPEED_LIST = ["%s ms" % x for x in range(50, 2010, 10)]
298
DTMFST_LIST = ["OFF", "DT-ST", "ANI-ST", "DT+ANI"]
299
MODE_LIST = ["Channel", "Name", "Frequency"]
300
PONMSG_LIST = ["Full", "Message"]
301
PTTID_LIST = ["Off", "BOT", "EOT", "Both"]
302
PTTIDCODE_LIST = ["%s" % x for x in range(1, 16)]
303
RTONE_LIST = ["1000 Hz", "1450 Hz", "1750 Hz", "2100 Hz"]
304
RESUME_LIST = ["TO", "CO", "SE"]
305
ROGERRX_LIST = ["Off"] + AB_LIST
306
RPSTE_LIST = ["OFF"] + ["%s" % x for x in range(1, 11)]
307
SAVE_LIST = ["Off", "1:1", "1:2", "1:3", "1:4"]
308
SCODE_LIST = ["%s" % x for x in range(1, 16)]
309
SHIFTD_LIST = ["Off", "+", "-"]
310
STEDELAY_LIST = ["OFF"] + ["%s ms" % x for x in range(100, 1100, 100)]
311
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0]
312
STEP_LIST = [str(x) for x in STEPS]
313
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0]
314
STEP291_LIST = [str(x) for x in STEPS]
315
TDRAB_LIST = ["Off"] + AB_LIST
316
TDRCH_LIST = ["CH%s" % x for x in range(1, 129)]
317
TIMEOUT_LIST = ["%s sec" % x for x in range(15, 615, 15)] + \
318
    ["Off (if supported by radio)"]
319
TXPOWER_LIST = ["High", "Low"]
320
TXPOWER3_LIST = ["High", "Mid", "Low"]
321
VOICE_LIST = ["Off", "English", "Chinese"]
322
VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 11)]
323
WORKMODE_LIST = ["Frequency", "Channel"]
324

    
325
GMRS_FREQS1 = [462562500, 462587500, 462612500, 462637500, 462662500,
326
               462687500, 462712500]
327
GMRS_FREQS2 = [467562500, 467587500, 467612500, 467637500, 467662500,
328
               467687500, 467712500]
329
GMRS_FREQS3 = [462550000, 462575000, 462600000, 462625000, 462650000,
330
               462675000, 462700000, 462725000]
331
GMRS_FREQS = GMRS_FREQS1 + GMRS_FREQS2 + GMRS_FREQS3 * 2
332

    
333

    
334
def _do_status(radio, direction, block):
335
    status = chirp_common.Status()
336
    status.msg = "Cloning %s radio" % direction
337
    status.cur = block
338
    status.max = radio.get_memsize()
339
    radio.status_fn(status)
340

    
341

    
342
UV5R_MODEL_ORIG = b"\x50\xBB\xFF\x01\x25\x98\x4D"
343
UV5R_MODEL_291 = b"\x50\xBB\xFF\x20\x12\x07\x25"
344
UV5R_MODEL_F11 = b"\x50\xBB\xFF\x13\xA1\x11\xDD"
345
UV5R_MODEL_UV82 = b"\x50\xBB\xFF\x20\x13\x01\x05"
346
UV5R_MODEL_UV6 = b"\x50\xBB\xFF\x20\x12\x08\x23"
347
UV5R_MODEL_UV6_ORIG = b"\x50\xBB\xFF\x12\x03\x98\x4D"
348
UV5R_MODEL_A58 = b"\x50\xBB\xFF\x20\x14\x04\x13"
349
UV5R_MODEL_UV5G = b"\x50\xBB\xFF\x20\x12\x06\x25"
350

    
351

    
352
def _upper_band_from_data(data):
353
    return data[0x03:0x04]
354

    
355

    
356
def _upper_band_from_image(radio):
357
    return _upper_band_from_data(radio.get_mmap())
358

    
359

    
360
def _read_from_data(data, data_start, data_stop):
361
    data = data[data_start:data_stop]
362
    return data
363

    
364

    
365
def _get_data_from_image(radio, _data_start, _data_stop):
366
    image_data = _read_from_data(
367
        radio.get_mmap().get_byte_compatible(),
368
        _data_start,
369
        _data_stop)
370
    return image_data
371

    
372

    
373
def _firmware_version_from_data(data, version_start, version_stop):
374
    version_tag = data[version_start:version_stop]
375
    return version_tag
376

    
377

    
378
def _firmware_version_from_image(radio):
379
    version = _firmware_version_from_data(
380
        radio.get_mmap().get_byte_compatible(),
381
        radio._fw_ver_file_start,
382
        radio._fw_ver_file_stop)
383
    return version
384

    
385

    
386
def _do_ident(radio, magic, secondack=True):
387
    serial = radio.pipe
388
    serial.timeout = 1
389

    
390
    LOG.info("Sending Magic: %s" % util.hexprint(magic))
391
    for byte in magic:
392
        serial.write(bytes([byte]))
393
        time.sleep(0.01)
394
    ack = serial.read(1)
395

    
396
    if ack != b"\x06":
397
        if ack:
398
            LOG.debug(repr(ack))
399
        raise errors.RadioError("Radio did not respond")
400

    
401
    serial.write(b"\x02")
402

    
403
    # Until recently, the "ident" returned by the radios supported by this
404
    # driver have always been 8 bytes long. The image structure is the 8 byte
405
    # "ident" followed by the downloaded memory data. So all of the settings
406
    # structures are offset by 8 bytes. The ident returned from a UV-6 radio
407
    # can be 8 bytes (original model) or now 12 bytes.
408
    #
409
    # To accommodate this, the "ident" is now read one byte at a time until the
410
    # last byte ("\xdd") is encountered. The bytes containing the value "\x01"
411
    # are discarded to shrink the "ident" length down to 8 bytes to keep the
412
    # image data aligned with the existing settings structures.
413

    
414
    # Ok, get the response
415
    response = b""
416
    for i in range(1, 13):
417
        byte = serial.read(1)
418
        response += byte
419
        # stop reading once the last byte ("\xdd") is encountered
420
        if byte == b"\xDD":
421
            break
422

    
423
    # check if response is OK
424
    if len(response) in [8, 12]:
425
        # DEBUG
426
        LOG.info("Valid response, got this:")
427
        LOG.debug(util.hexprint(response))
428
        if len(response) == 12:
429
            ident = (bytes([response[0], response[3], response[5]]) +
430
                     response[7:])
431
        else:
432
            ident = response
433
    else:
434
        # bad response
435
        msg = "Unexpected response, got this:"
436
        msg += util.hexprint(response)
437
        LOG.debug(msg)
438
        raise errors.RadioError("Unexpected response from radio.")
439

    
440
    if secondack:
441
        serial.write(b"\x06")
442
        ack = serial.read(1)
443
        if ack != b"\x06":
444
            raise errors.RadioError("Radio refused clone")
445

    
446
    return ident
447

    
448

    
449
def _read_block(radio, start, size, first_command=False):
450
    msg = struct.pack(">BHB", ord("S"), start, size)
451
    radio.pipe.write(msg)
452

    
453
    if first_command is False:
454
        ack = radio.pipe.read(1)
455
        if ack != b"\x06":
456
            raise errors.RadioError(
457
                "Radio refused to send second block 0x%04x" % start)
458

    
459
    answer = radio.pipe.read(4)
460
    if len(answer) != 4:
461
        raise errors.RadioError("Radio refused to send block 0x%04x" % start)
462

    
463
    cmd, addr, length = struct.unpack(">BHB", answer)
464
    if cmd != ord("X") or addr != start or length != size:
465
        LOG.error("Invalid answer for block 0x%04x:" % start)
466
        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (cmd, addr, length))
467
        raise errors.RadioError("Unknown response from radio")
468

    
469
    chunk = radio.pipe.read(size)
470
    if not chunk:
471
        raise errors.RadioError("Radio did not send block 0x%04x" % start)
472
    elif len(chunk) != size:
473
        LOG.error("Chunk length was 0x%04i" % len(chunk))
474
        raise errors.RadioError("Radio sent incomplete block 0x%04x" % start)
475

    
476
    radio.pipe.write(b"\x06")
477
    time.sleep(0.05)
478

    
479
    return chunk
480

    
481

    
482
def _get_radio_firmware_version(radio):
483
    if radio.MODEL == "BJ-UV55":
484
        block = _read_block(radio, 0x1FF0, 0x40, True)
485
        version = block[0:6]
486
        return version
487
    else:
488
        # New radios will reply with 'alternative' (aka: bad) data if the Aux
489
        # memory area is read without first reading a block from another area
490
        # of memory. Reading any block that is outside of the Aux memory area
491
        # (which starts at 0x1EC0) prior to reading blocks in the Aux mem area
492
        # turns out to be a workaround for this problem.
493

    
494
        # read and disregard block0
495
        block0 = _read_block(radio, 0x1E80, 0x40, True)
496
        block1 = _read_block(radio, 0x1EC0, 0x40, False)
497
        block2 = _read_block(radio, 0x1FC0, 0x40, False)
498

    
499
        version = block1[48:62]  # firmware version
500
        # New radios will drop a byte at 0x1FCF when read in 0x40 byte blocks.
501
        # This results in the next 0x30 bytes being moved forward one position
502
        # (putting 0xFF in position 0x1FCF and pads the now missing byte at the
503
        # end, 0x1FFF, with 0x80).
504

    
505
        # detect dropped byte
506
        dropped_byte = (block2[15:16] == b"\xFF" and  # dropped byte?
507
                        block2[63:64] == b"\x80")     # padded end?
508
        return version, dropped_byte
509

    
510

    
511
IDENT_BLACKLIST = {
512
    b"\x50\x0D\x0C\x20\x16\x03\x28": "Radio identifies as BTECH UV-5X3",
513
    b"\x50\xBB\xFF\x20\x12\x06\x25": "Radio identifies as Radioddity UV-5G",
514
}
515

    
516

    
517
def _ident_radio(radio):
518
    for magic in radio._idents:
519
        error = None
520
        try:
521
            data = _do_ident(radio, magic)
522
            return data
523
        except errors.RadioError as e:
524
            LOG.error("uv5r._ident_radio: %s", e)
525
            error = e
526
            time.sleep(2)
527

    
528
    for magic, reason in list(IDENT_BLACKLIST.items()):
529
        try:
530
            _do_ident(radio, magic, secondack=False)
531
        except errors.RadioError:
532
            # No match, try the next one
533
            continue
534

    
535
        # If we got here, it means we identified the radio as
536
        # something other than one of our valid idents. Warn
537
        # the user so they can do the right thing.
538
        LOG.warning(('Identified radio as a blacklisted model '
539
                     '(details: %s)') % reason)
540
        raise errors.RadioError(('%s. Please choose the proper vendor/'
541
                                 'model and try again.') % reason)
542

    
543
    if error:
544
        raise error
545
    raise errors.RadioError("Radio did not respond")
546

    
547

    
548
def _do_download(radio):
549
    data = _ident_radio(radio)
550

    
551
    if radio.MODEL == "BJ-UV55":
552
        radio_version = _get_radio_firmware_version(radio)
553
    else:
554
        radio_version, has_dropped_byte = \
555
            _get_radio_firmware_version(radio)
556
    LOG.info("Radio Version is %s" % repr(radio_version))
557
    LOG.info("Radio has dropped byte bug: %s" % repr(has_dropped_byte))
558

    
559
    # Main block
560
    LOG.debug("downloading main block...")
561
    for i in range(0, 0x1800, 0x40):
562
        data += _read_block(radio, i, 0x40, False)
563
        _do_status(radio, "from", i)
564
    _do_status(radio, "from", radio.get_memsize())
565
    LOG.debug("done.")
566
    if radio._aux_block:
567
        if has_dropped_byte:
568
            LOG.debug("downloading aux block...")
569
            # Auxiliary block starts at 0x1ECO (?)
570
            for i in range(0x1EC0, 0x1FC0, 0x40):
571
                data += _read_block(radio, i, 0x40, False)
572
            # Shift to 0x10 block sizes as a workaround for new radios that
573
            # will drop byte 0x1FCF if the last 0x40 bytes are read using a
574
            # 0x40 block size
575
            for i in range(0x1FC0, 0x2000, 0x10):
576
                data += _read_block(radio, i, 0x10, False)
577
        else:
578
            # Retain 0x40 byte block download for legacy radios (the
579
            # 'original' radios with firmware versions prior to BFB291 do not
580
            # support reading the Aux memory are with 0x10 bytes blocks.
581
            LOG.debug("downloading aux block...")
582
            # Auxiliary block starts at 0x1ECO (?)
583
            for i in range(0x1EC0, 0x2000, 0x40):
584
                data += _read_block(radio, i, 0x40, False)
585

    
586
    LOG.debug("done.")
587
    return memmap.MemoryMapBytes(data)
588

    
589

    
590
def _send_block(radio, addr, data):
591
    msg = struct.pack(">BHB", ord("X"), addr, len(data))
592
    radio.pipe.write(msg + data)
593
    time.sleep(0.05)
594

    
595
    ack = radio.pipe.read(1)
596
    if ack != b"\x06":
597
        raise errors.RadioError("Radio refused to accept block 0x%04x" % addr)
598

    
599

    
600
def _do_upload(radio):
601
    ident = _ident_radio(radio)
602
    radio_upper_band = ident[3:4]
603
    image_upper_band = _upper_band_from_image(radio)
604

    
605
    if image_upper_band == vhf_220_radio or radio_upper_band == vhf_220_radio:
606
        if image_upper_band != radio_upper_band:
607
            raise errors.RadioError("Image not supported by radio")
608

    
609
    image_version = _firmware_version_from_image(radio)
610
    if radio.MODEL == "BJ-UV55":
611
        radio_version = _get_radio_firmware_version(radio)
612

    
613
        # default ranges
614
        _ranges_main_default = radio._ranges_main
615
        _ranges_aux_default = radio._ranges_aux
616
    else:
617
        radio_version, has_dropped_byte = _get_radio_firmware_version(radio)
618

    
619
        # determine if radio is 'original' radio
620
        if b'BFB' in radio_version:
621
            idx = radio_version.index(b"BFB") + 3
622
            version = int(radio_version[idx:idx + 3])
623
            _radio_is_orig = version < 291
624
        else:
625
            _radio_is_orig = False
626

    
627
        # determine if image is from 'original' radio
628
        _image_is_orig = radio._is_orig()
629

    
630
        if _image_is_orig != _radio_is_orig:
631
            raise errors.RadioError("Image not supported by radio")
632

    
633
        # default ranges
634
        _ranges_main_default = [
635
            (0x0008, 0x0CF8),  # skip 0x0CF8 - 0x0D08
636
            (0x0D08, 0x0DF8),  # skip 0x0DF8 - 0x0E08
637
            (0x0E08, 0x1808),
638
            ]
639

    
640
        if _image_is_orig:
641
            # default Aux mem ranges for radios before BFB291
642
            _ranges_aux_default = [
643
                (0x1EE0, 0x1EF0),  # welcome message
644
                (0x1FC0, 0x1FE0),  # old band limits
645
                ]
646
        else:
647
            # default Aux mem ranges for radios from BFB291 to present
648
            _ranges_aux_default = [
649
                (0x1EE0, 0x1EF0),  # welcome message
650
                (0x1F60, 0x1F70),  # vhf squelch thresholds
651
                (0x1F80, 0x1F90),  # uhf squelch thresholds
652
                (0x1FC0, 0x1FD0),  # new band limits
653
                ]
654

    
655
    LOG.info("Image Version is %s" % repr(image_version))
656
    LOG.info("Radio Version is %s" % repr(radio_version))
657

    
658
    # set default ranges
659
    ranges_main = _ranges_main_default
660
    ranges_aux = _ranges_aux_default
661

    
662
    if radio._all_range_flag:
663
        # user enabled 'Range Override Parameter', upload everything
664
        ranges_main = radio._ranges_main
665
        ranges_aux = radio._ranges_aux
666
        LOG.warning('Sending all ranges to radio as instructed')
667

    
668
    # Main block
669
    mmap = radio.get_mmap().get_byte_compatible()
670
    for start_addr, end_addr in ranges_main:
671
        for i in range(start_addr, end_addr, 0x10):
672
            _send_block(radio, i - 0x08, mmap[i:i + 0x10])
673
            _do_status(radio, "to", i)
674
        _do_status(radio, "to", radio.get_memsize())
675

    
676
    if len(mmap.get_packed()) == 0x1808:
677
        LOG.info("Old image, not writing aux block")
678
        return  # Old image, no aux block
679

    
680
    # Auxiliary block at radio address 0x1EC0, our offset 0x1808
681
    for start_addr, end_addr in ranges_aux:
682
        for i in range(start_addr, end_addr, 0x10):
683
            addr = 0x1808 + (i - 0x1EC0)
684
            _send_block(radio, i, mmap[addr:addr + 0x10])
685

    
686
    if radio._all_range_flag:
687
        radio._all_range_flag = False
688
        LOG.warning('Sending all ranges to radio has completed')
689
        raise errors.RadioError(
690
            "This is NOT an error.\n"
691
            "The upload has finished successfully.\n"
692
            "Please restart CHIRP.")
693

    
694

    
695
UV5R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=4.00),
696
                     chirp_common.PowerLevel("Low",  watts=1.00)]
697

    
698
UV5R_POWER_LEVELS3 = [chirp_common.PowerLevel("High", watts=8.00),
699
                      chirp_common.PowerLevel("Med",  watts=4.00),
700
                      chirp_common.PowerLevel("Low",  watts=1.00)]
701

    
702
UV5R_DTCS = tuple(sorted(chirp_common.DTCS_CODES + (645,)))
703

    
704
UV5R_CHARSET = chirp_common.CHARSET_UPPER_NUMERIC + \
705
    "!@#$%^&*()+-=[]:\";'<>?,./"
706

    
707

    
708
def model_match(cls, data):
709
    """Match the opened/downloaded image to the correct version"""
710

    
711
    if len(data) == 0x1950:
712
        rid = data[0x1948:0x1950]
713
        return rid.startswith(cls.MODEL)
714
    elif len(data) == 0x1948:
715
        rid = data[cls._fw_ver_file_start:cls._fw_ver_file_stop]
716
        if any(type in rid for type in cls._basetype):
717
            return True
718
    else:
719
        return False
720

    
721

    
722
class BaofengUV5R(chirp_common.CloneModeRadio):
723

    
724
    """Baofeng UV-5R"""
725
    VENDOR = "Baofeng"
726
    MODEL = "UV-5R"
727
    BAUD_RATE = 9600
728
    NEEDS_COMPAT_SERIAL = False
729

    
730
    _memsize = 0x1808
731
    _basetype = BASETYPE_UV5R
732
    _idents = [UV5R_MODEL_291,
733
               UV5R_MODEL_ORIG
734
               ]
735
    _vhf_range = (130000000, 176000000)
736
    _220_range = (220000000, 260000000)
737
    _uhf_range = (400000000, 520000000)
738
    _aux_block = True
739
    _tri_power = False
740
    _bw_shift = False
741
    _mem_params = (0x1828  # poweron_msg offset
742
                   )
743
    # offset of fw version in image file
744
    _fw_ver_file_start = 0x1838
745
    _fw_ver_file_stop = 0x1846
746

    
747
    _ranges_main = [
748
                    (0x0008, 0x1808),
749
                   ]
750
    _ranges_aux = [
751
                   (0x1EC0, 0x2000),
752
                  ]
753
    _valid_chars = UV5R_CHARSET
754

    
755
    @classmethod
756
    def get_prompts(cls):
757
        rp = chirp_common.RadioPrompts()
758
        rp.experimental = \
759
            ('Due to the fact that the manufacturer continues to '
760
             'release new versions of the firmware with obscure and '
761
             'hard-to-track changes, this driver may not work with '
762
             'your device. Thus far and to the best knowledge of the '
763
             'author, no UV-5R radios have been harmed by using CHIRP. '
764
             'However, proceed at your own risk!')
765
        rp.pre_download = _(
766
            "1. Turn radio off.\n"
767
            "2. Connect cable to mic/spkr connector.\n"
768
            "3. Make sure connector is firmly connected.\n"
769
            "4. Turn radio on (volume may need to be set at 100%).\n"
770
            "5. Ensure that the radio is tuned to channel with no"
771
            " activity.\n"
772
            "6. Click OK to download image from device.\n")
773
        rp.pre_upload = _(
774
            "1. Turn radio off.\n"
775
            "2. Connect cable to mic/spkr connector.\n"
776
            "3. Make sure connector is firmly connected.\n"
777
            "4. Turn radio on (volume may need to be set at 100%).\n"
778
            "5. Ensure that the radio is tuned to channel with no"
779
            " activity.\n"
780
            "6. Click OK to upload image to device.\n")
781
        return rp
782

    
783
    def get_features(self):
784
        rf = chirp_common.RadioFeatures()
785
        rf.has_settings = True
786
        rf.has_bank = False
787
        rf.has_cross = True
788
        rf.has_rx_dtcs = True
789
        rf.has_tuning_step = False
790
        rf.can_odd_split = True
791
        rf.valid_name_length = 7
792
        rf.valid_characters = self._valid_chars
793
        rf.valid_skips = ["", "S"]
794
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
795
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
796
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
797
        rf.valid_power_levels = UV5R_POWER_LEVELS
798
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
799
        rf.valid_modes = ["FM", "NFM"]
800
        rf.valid_tuning_steps = STEPS
801
        rf.valid_dtcs_codes = UV5R_DTCS
802

    
803
        normal_bands = [self._vhf_range, self._uhf_range]
804
        rax_bands = [self._vhf_range, self._220_range]
805

    
806
        if self._mmap is None:
807
            rf.valid_bands = [normal_bands[0], rax_bands[1], normal_bands[1]]
808
        elif not self._is_orig() and self._my_upper_band() == vhf_220_radio:
809
            rf.valid_bands = rax_bands
810
        else:
811
            rf.valid_bands = normal_bands
812
        rf.memory_bounds = (0, 127)
813
        return rf
814

    
815
    @classmethod
816
    def match_model(cls, filedata, filename):
817
        match_size = False
818
        match_model = False
819
        if len(filedata) in [0x1808, 0x1948, 0x1950]:
820
            match_size = True
821
        match_model = model_match(cls, filedata)
822

    
823
        if match_size and match_model:
824
            return True
825
        else:
826
            return False
827

    
828
    def process_mmap(self):
829
        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
830
        self._all_range_flag = False
831

    
832
    def sync_in(self):
833
        try:
834
            self._mmap = _do_download(self)
835
        except errors.RadioError:
836
            raise
837
        except Exception as e:
838
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
839
        self.process_mmap()
840

    
841
    def sync_out(self):
842
        try:
843
            _do_upload(self)
844
        except errors.RadioError:
845
            raise
846
        except Exception as e:
847
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
848

    
849
    def get_raw_memory(self, number):
850
        return repr(self._memobj.memory[number])
851

    
852
    def _is_txinh(self, _mem):
853
        raw_tx = ""
854
        for i in range(0, 4):
855
            raw_tx += _mem.txfreq[i].get_raw()
856
        return raw_tx == "\xFF\xFF\xFF\xFF"
857

    
858
    def _get_mem(self, number):
859
        return self._memobj.memory[number]
860

    
861
    def _get_nam(self, number):
862
        return self._memobj.names[number]
863

    
864
    def get_memory(self, number):
865
        _mem = self._get_mem(number)
866
        _nam = self._get_nam(number)
867

    
868
        mem = chirp_common.Memory()
869
        mem.number = number
870

    
871
        if _mem.get_raw()[0] == "\xff":
872
            mem.empty = True
873
            return mem
874

    
875
        mem.freq = int(_mem.rxfreq) * 10
876

    
877
        if self._is_txinh(_mem):
878
            mem.duplex = "off"
879
            mem.offset = 0
880
        elif int(_mem.rxfreq) == int(_mem.txfreq):
881
            mem.duplex = ""
882
            mem.offset = 0
883
        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
884
            mem.duplex = "split"
885
            mem.offset = int(_mem.txfreq) * 10
886
        else:
887
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
888
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
889

    
890
        for char in _nam.name:
891
            if str(char) == "\xFF":
892
                char = " "  # The UV-5R software may have 0xFF mid-name
893
            mem.name += str(char)
894
        mem.name = mem.name.rstrip()
895

    
896
        dtcs_pol = ["N", "N"]
897

    
898
        if _mem.txtone in [0, 0xFFFF]:
899
            txmode = ""
900
        elif _mem.txtone >= 0x0258:
901
            txmode = "Tone"
902
            mem.rtone = int(_mem.txtone) / 10.0
903
        elif _mem.txtone <= 0x0258:
904
            txmode = "DTCS"
905
            if _mem.txtone > 0x69:
906
                index = _mem.txtone - 0x6A
907
                dtcs_pol[0] = "R"
908
            else:
909
                index = _mem.txtone - 1
910
            mem.dtcs = UV5R_DTCS[index]
911
        else:
912
            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
913

    
914
        if _mem.rxtone in [0, 0xFFFF]:
915
            rxmode = ""
916
        elif _mem.rxtone >= 0x0258:
917
            rxmode = "Tone"
918
            mem.ctone = int(_mem.rxtone) / 10.0
919
        elif _mem.rxtone <= 0x0258:
920
            rxmode = "DTCS"
921
            if _mem.rxtone >= 0x6A:
922
                index = _mem.rxtone - 0x6A
923
                dtcs_pol[1] = "R"
924
            else:
925
                index = _mem.rxtone - 1
926
            mem.rx_dtcs = UV5R_DTCS[index]
927
        else:
928
            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
929

    
930
        if txmode == "Tone" and not rxmode:
931
            mem.tmode = "Tone"
932
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
933
            mem.tmode = "TSQL"
934
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
935
            mem.tmode = "DTCS"
936
        elif rxmode or txmode:
937
            mem.tmode = "Cross"
938
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
939

    
940
        mem.dtcs_polarity = "".join(dtcs_pol)
941

    
942
        if not _mem.scan:
943
            mem.skip = "S"
944

    
945
        if self._tri_power:
946
            levels = UV5R_POWER_LEVELS3
947
        else:
948
            levels = UV5R_POWER_LEVELS
949
        try:
950
            mem.power = levels[_mem.lowpower]
951
        except IndexError:
952
            LOG.error("Radio reported invalid power level %s (in %s)" %
953
                      (_mem.lowpower, levels))
954
            mem.power = levels[0]
955

    
956
        mem.mode = _mem.wide and "FM" or "NFM"
957

    
958
        mem.extra = RadioSettingGroup("Extra", "extra")
959

    
960
        rs = RadioSetting("bcl", "BCL",
961
                          RadioSettingValueBoolean(_mem.bcl))
962
        mem.extra.append(rs)
963

    
964
        rs = RadioSetting("pttid", "PTT ID",
965
                          RadioSettingValueList(PTTID_LIST,
966
                                                PTTID_LIST[_mem.pttid]))
967
        mem.extra.append(rs)
968

    
969
        rs = RadioSetting("scode", "PTT ID Code",
970
                          RadioSettingValueList(PTTIDCODE_LIST,
971
                                                PTTIDCODE_LIST[_mem.scode]))
972
        mem.extra.append(rs)
973

    
974
        immutable = []
975

    
976
        if self.MODEL == "GT-5R":
977
            if not ((mem.freq >= self.vhftx[0] and mem.freq < self.vhftx[1]) or
978
                    (mem.freq >= self.uhftx[0] and mem.freq < self.uhftx[1])):
979
                mem.duplex = 'off'
980
                mem.offset = 0
981
                immutable = ["duplex", "offset"]
982

    
983
        mem.immutable = immutable
984

    
985
        return mem
986

    
987
    def _set_mem(self, number):
988
        return self._memobj.memory[number]
989

    
990
    def _set_nam(self, number):
991
        return self._memobj.names[number]
992

    
993
    def set_memory(self, mem):
994
        _mem = self._get_mem(mem.number)
995
        _nam = self._get_nam(mem.number)
996

    
997
        if mem.empty:
998
            _mem.set_raw("\xff" * 16)
999
            _nam.set_raw("\xff" * 16)
1000
            return
1001

    
1002
        was_empty = False
1003
        # same method as used in get_memory to find
1004
        # out whether a raw memory is empty
1005
        if _mem.get_raw()[0] == "\xff":
1006
            was_empty = True
1007
            LOG.debug("UV5R: this mem was empty")
1008
        else:
1009
            # memorize old extra-values before erasing the whole memory
1010
            # used to solve issue 4121
1011
            LOG.debug("mem was not empty, memorize extra-settings")
1012
            prev_bcl = _mem.bcl.get_value()
1013
            prev_scode = _mem.scode.get_value()
1014
            prev_pttid = _mem.pttid.get_value()
1015

    
1016
        _mem.set_raw("\x00" * 16)
1017

    
1018
        _mem.rxfreq = mem.freq / 10
1019

    
1020
        if mem.duplex == "off":
1021
            for i in range(0, 4):
1022
                _mem.txfreq[i].set_raw("\xFF")
1023
        elif mem.duplex == "split":
1024
            _mem.txfreq = mem.offset / 10
1025
        elif mem.duplex == "+":
1026
            _mem.txfreq = (mem.freq + mem.offset) / 10
1027
        elif mem.duplex == "-":
1028
            _mem.txfreq = (mem.freq - mem.offset) / 10
1029
        else:
1030
            _mem.txfreq = mem.freq / 10
1031

    
1032
        _namelength = self.get_features().valid_name_length
1033
        for i in range(_namelength):
1034
            try:
1035
                _nam.name[i] = mem.name[i]
1036
            except IndexError:
1037
                _nam.name[i] = "\xFF"
1038

    
1039
        rxmode = txmode = ""
1040
        if mem.tmode == "Tone":
1041
            _mem.txtone = int(mem.rtone * 10)
1042
            _mem.rxtone = 0
1043
        elif mem.tmode == "TSQL":
1044
            _mem.txtone = int(mem.ctone * 10)
1045
            _mem.rxtone = int(mem.ctone * 10)
1046
        elif mem.tmode == "DTCS":
1047
            rxmode = txmode = "DTCS"
1048
            _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
1049
            _mem.rxtone = UV5R_DTCS.index(mem.dtcs) + 1
1050
        elif mem.tmode == "Cross":
1051
            txmode, rxmode = mem.cross_mode.split("->", 1)
1052
            if txmode == "Tone":
1053
                _mem.txtone = int(mem.rtone * 10)
1054
            elif txmode == "DTCS":
1055
                _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
1056
            else:
1057
                _mem.txtone = 0
1058
            if rxmode == "Tone":
1059
                _mem.rxtone = int(mem.ctone * 10)
1060
            elif rxmode == "DTCS":
1061
                _mem.rxtone = UV5R_DTCS.index(mem.rx_dtcs) + 1
1062
            else:
1063
                _mem.rxtone = 0
1064
        else:
1065
            _mem.rxtone = 0
1066
            _mem.txtone = 0
1067

    
1068
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
1069
            _mem.txtone += 0x69
1070
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
1071
            _mem.rxtone += 0x69
1072

    
1073
        _mem.scan = mem.skip != "S"
1074
        _mem.wide = mem.mode == "FM"
1075

    
1076
        if mem.power:
1077
            if self._tri_power:
1078
                levels = [str(l) for l in UV5R_POWER_LEVELS3]
1079
                _mem.lowpower = levels.index(str(mem.power))
1080
            else:
1081
                _mem.lowpower = UV5R_POWER_LEVELS.index(mem.power)
1082
        else:
1083
            _mem.lowpower = 0
1084

    
1085
        if not was_empty:
1086
            # restoring old extra-settings (issue 4121
1087
            _mem.bcl.set_value(prev_bcl)
1088
            _mem.scode.set_value(prev_scode)
1089
            _mem.pttid.set_value(prev_pttid)
1090

    
1091
        for setting in mem.extra:
1092
            setattr(_mem, setting.get_name(), setting.value)
1093

    
1094
    def _is_orig(self):
1095
        version_tag = _firmware_version_from_image(self)
1096
        try:
1097
            if b'BFB' in version_tag:
1098
                idx = version_tag.index(b"BFB") + 3
1099
                version = int(version_tag[idx:idx + 3])
1100
                return version < 291
1101
            return False
1102
        except:
1103
            pass
1104
        raise errors.RadioError("Unable to parse version string %s" %
1105
                                version_tag)
1106

    
1107
    def _my_version(self):
1108
        version_tag = _firmware_version_from_image(self)
1109
        if b'BFB' in version_tag:
1110
            idx = version_tag.index(b"BFB") + 3
1111
            return int(version_tag[idx:idx + 3])
1112

    
1113
        raise Exception("Unrecognized firmware version string")
1114

    
1115
    def _my_upper_band(self):
1116
        band_tag = _upper_band_from_image(self)
1117
        return band_tag
1118

    
1119
    def _get_settings(self):
1120
        _mem = self._memobj
1121
        _ani = self._memobj.ani
1122
        _settings = self._memobj.settings
1123
        _squelch = self._memobj.squelch_new
1124
        _vfoa = self._memobj.vfoa
1125
        _vfob = self._memobj.vfob
1126
        _wmchannel = self._memobj.wmchannel
1127

    
1128
        basic = RadioSettingGroup("basic", "Basic Settings")
1129
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
1130

    
1131
        group = RadioSettings(basic, advanced)
1132

    
1133
        rs = RadioSetting("squelch", "Carrier Squelch Level",
1134
                          RadioSettingValueInteger(0, 9, _settings.squelch))
1135
        basic.append(rs)
1136

    
1137
        rs = RadioSetting("save", "Battery Saver",
1138
                          RadioSettingValueList(
1139
                              SAVE_LIST, SAVE_LIST[_settings.save]))
1140
        basic.append(rs)
1141

    
1142
        rs = RadioSetting("vox", "VOX Sensitivity",
1143
                          RadioSettingValueList(
1144
                              VOX_LIST, VOX_LIST[_settings.vox]))
1145
        advanced.append(rs)
1146

    
1147
        if self.MODEL == "UV-6":
1148
            # NOTE: The UV-6 calls this byte voxenable, but the UV-5R calls it
1149
            # autolk. Since this is a minor difference, it will be referred to
1150
            # by the wrong name for the UV-6.
1151
            rs = RadioSetting("autolk", "Vox",
1152
                              RadioSettingValueBoolean(_settings.autolk))
1153
            advanced.append(rs)
1154

    
1155
        if self.MODEL != "UV-6":
1156
            rs = RadioSetting("abr", "Backlight Timeout",
1157
                              RadioSettingValueInteger(0, 24, _settings.abr))
1158
            basic.append(rs)
1159

    
1160
        rs = RadioSetting("tdr", "Dual Watch",
1161
                          RadioSettingValueBoolean(_settings.tdr))
1162
        advanced.append(rs)
1163

    
1164
        if self.MODEL == "UV-6":
1165
            rs = RadioSetting("tdrch", "Dual Watch Channel",
1166
                              RadioSettingValueList(
1167
                                  TDRCH_LIST, TDRCH_LIST[_settings.tdrch]))
1168
            advanced.append(rs)
1169

    
1170
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
1171
                              RadioSettingValueBoolean(_settings.tdrab))
1172
            advanced.append(rs)
1173
        else:
1174
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
1175
                              RadioSettingValueList(
1176
                                  TDRAB_LIST, TDRAB_LIST[_settings.tdrab]))
1177
            advanced.append(rs)
1178

    
1179
        if self.MODEL == "UV-6":
1180
            rs = RadioSetting("alarm", "Alarm Sound",
1181
                              RadioSettingValueBoolean(_settings.alarm))
1182
            advanced.append(rs)
1183

    
1184
        if _settings.almod > 0x02:
1185
            val = 0x01
1186
        else:
1187
            val = _settings.almod
1188
        rs = RadioSetting("almod", "Alarm Mode",
1189
                          RadioSettingValueList(
1190
                              ALMOD_LIST, ALMOD_LIST[val]))
1191
        advanced.append(rs)
1192

    
1193
        rs = RadioSetting("beep", "Beep",
1194
                          RadioSettingValueBoolean(_settings.beep))
1195
        basic.append(rs)
1196

    
1197
        rs = RadioSetting("timeout", "Timeout Timer",
1198
                          RadioSettingValueList(
1199
                              TIMEOUT_LIST, TIMEOUT_LIST[_settings.timeout]))
1200
        basic.append(rs)
1201

    
1202
        if ((self._is_orig() and self._my_version() < 251) or
1203
                (self.MODEL in ["TI-F8+", "TS-T9+"])):
1204
            rs = RadioSetting("voice", "Voice",
1205
                              RadioSettingValueBoolean(_settings.voice))
1206
            advanced.append(rs)
1207
        else:
1208
            rs = RadioSetting("voice", "Voice",
1209
                              RadioSettingValueList(
1210
                                  VOICE_LIST, VOICE_LIST[_settings.voice]))
1211
            advanced.append(rs)
1212

    
1213
        rs = RadioSetting("screv", "Scan Resume",
1214
                          RadioSettingValueList(
1215
                              RESUME_LIST, RESUME_LIST[_settings.screv]))
1216
        advanced.append(rs)
1217

    
1218
        if self.MODEL != "UV-6":
1219
            rs = RadioSetting("mdfa", "Display Mode (A)",
1220
                              RadioSettingValueList(
1221
                                  MODE_LIST, MODE_LIST[_settings.mdfa]))
1222
            basic.append(rs)
1223

    
1224
            rs = RadioSetting("mdfb", "Display Mode (B)",
1225
                              RadioSettingValueList(
1226
                                  MODE_LIST, MODE_LIST[_settings.mdfb]))
1227
            basic.append(rs)
1228

    
1229
        rs = RadioSetting("bcl", "Busy Channel Lockout",
1230
                          RadioSettingValueBoolean(_settings.bcl))
1231
        advanced.append(rs)
1232

    
1233
        if self.MODEL != "UV-6":
1234
            rs = RadioSetting("autolk", "Automatic Key Lock",
1235
                              RadioSettingValueBoolean(_settings.autolk))
1236
            advanced.append(rs)
1237

    
1238
        rs = RadioSetting("fmradio", "Broadcast FM Radio",
1239
                          RadioSettingValueBoolean(_settings.fmradio))
1240
        advanced.append(rs)
1241

    
1242
        if self.MODEL != "UV-6":
1243
            rs = RadioSetting("wtled", "Standby LED Color",
1244
                              RadioSettingValueList(
1245
                                  COLOR_LIST, COLOR_LIST[_settings.wtled]))
1246
            basic.append(rs)
1247

    
1248
            rs = RadioSetting("rxled", "RX LED Color",
1249
                              RadioSettingValueList(
1250
                                  COLOR_LIST, COLOR_LIST[_settings.rxled]))
1251
            basic.append(rs)
1252

    
1253
            rs = RadioSetting("txled", "TX LED Color",
1254
                              RadioSettingValueList(
1255
                                  COLOR_LIST, COLOR_LIST[_settings.txled]))
1256
            basic.append(rs)
1257

    
1258
        if isinstance(self, BaofengUV82Radio):
1259
            rs = RadioSetting("roger", "Roger Beep (TX)",
1260
                              RadioSettingValueBoolean(_settings.roger))
1261
            basic.append(rs)
1262
            rs = RadioSetting("rogerrx", "Roger Beep (RX)",
1263
                              RadioSettingValueList(
1264
                                  ROGERRX_LIST,
1265
                                  ROGERRX_LIST[_settings.rogerrx]))
1266
            basic.append(rs)
1267
        else:
1268
            rs = RadioSetting("roger", "Roger Beep",
1269
                              RadioSettingValueBoolean(_settings.roger))
1270
            basic.append(rs)
1271

    
1272
        rs = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)",
1273
                          RadioSettingValueBoolean(_settings.ste))
1274
        advanced.append(rs)
1275

    
1276
        rs = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)",
1277
                          RadioSettingValueList(
1278
                              RPSTE_LIST, RPSTE_LIST[_settings.rpste]))
1279
        advanced.append(rs)
1280

    
1281
        rs = RadioSetting("rptrl", "STE Repeater Delay",
1282
                          RadioSettingValueList(
1283
                              STEDELAY_LIST, STEDELAY_LIST[_settings.rptrl]))
1284
        advanced.append(rs)
1285

    
1286
        if self.MODEL != "UV-6":
1287
            rs = RadioSetting("reset", "RESET Menu",
1288
                              RadioSettingValueBoolean(_settings.reset))
1289
            advanced.append(rs)
1290

    
1291
            rs = RadioSetting("menu", "All Menus",
1292
                              RadioSettingValueBoolean(_settings.menu))
1293
            advanced.append(rs)
1294

    
1295
        if self.MODEL == "F-11":
1296
            # this is an F-11 only feature
1297
            rs = RadioSetting("vfomrlock", "VFO/MR Button",
1298
                              RadioSettingValueBoolean(_settings.vfomrlock))
1299
            advanced.append(rs)
1300

    
1301
        if isinstance(self, BaofengUV82Radio):
1302
            # this is a UV-82C only feature
1303
            rs = RadioSetting("vfomrlock", "VFO/MR Switching (UV-82C only)",
1304
                              RadioSettingValueBoolean(_settings.vfomrlock))
1305
            advanced.append(rs)
1306

    
1307
        if self.MODEL == "UV-82HP":
1308
            # this is a UV-82HP only feature
1309
            rs = RadioSetting(
1310
                "vfomrlock", "VFO/MR Switching (BTech UV-82HP only)",
1311
                RadioSettingValueBoolean(_settings.vfomrlock))
1312
            advanced.append(rs)
1313

    
1314
        if isinstance(self, BaofengUV82Radio):
1315
            # this is an UV-82C only feature
1316
            rs = RadioSetting("singleptt", "Single PTT (UV-82C only)",
1317
                              RadioSettingValueBoolean(_settings.singleptt))
1318
            advanced.append(rs)
1319

    
1320
        if self.MODEL == "UV-82HP":
1321
            # this is an UV-82HP only feature
1322
            rs = RadioSetting("singleptt", "Single PTT (BTech UV-82HP only)",
1323
                              RadioSettingValueBoolean(_settings.singleptt))
1324
            advanced.append(rs)
1325

    
1326
        if self.MODEL == "UV-82HP":
1327
            # this is an UV-82HP only feature
1328
            rs = RadioSetting(
1329
                "tdrch", "Tone Burst Frequency (BTech UV-82HP only)",
1330
                RadioSettingValueList(RTONE_LIST, RTONE_LIST[_settings.tdrch]))
1331
            advanced.append(rs)
1332

    
1333
        def set_range_flag(setting):
1334
            val = [85, 115, 101, 65, 116, 79, 119, 110, 82, 105, 115, 107]
1335
            if [ord(x) for x in str(setting.value).strip()] == val:
1336
                self._all_range_flag = True
1337
            else:
1338
                self._all_range_flag = False
1339
            LOG.debug('Set range flag to %s' % self._all_range_flag)
1340

    
1341
        rs = RadioSetting("allrange", "Range Override Parameter",
1342
                          RadioSettingValueString(0, 12, "Default"))
1343
        rs.set_apply_callback(set_range_flag)
1344
        advanced.append(rs)
1345

    
1346
        if len(self._mmap.get_packed()) == 0x1808:
1347
            # Old image, without aux block
1348
            return group
1349

    
1350
        if self.MODEL != "UV-6":
1351
            other = RadioSettingGroup("other", "Other Settings")
1352
            group.append(other)
1353

    
1354
            def _filter(name):
1355
                filtered = ""
1356
                for char in str(name):
1357
                    if char in chirp_common.CHARSET_ASCII:
1358
                        filtered += char
1359
                    else:
1360
                        filtered += " "
1361
                return filtered
1362

    
1363
            _msg = self._memobj.firmware_msg
1364
            val = RadioSettingValueString(0, 7, _filter(_msg.line1))
1365
            val.set_mutable(False)
1366
            rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
1367
            other.append(rs)
1368

    
1369
            val = RadioSettingValueString(0, 7, _filter(_msg.line2))
1370
            val.set_mutable(False)
1371
            rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
1372
            other.append(rs)
1373

    
1374
            _msg = self._memobj.sixpoweron_msg
1375
            val = RadioSettingValueString(0, 7, _filter(_msg.line1))
1376
            val.set_mutable(False)
1377
            rs = RadioSetting("sixpoweron_msg.line1",
1378
                              "6+Power-On Message 1", val)
1379
            other.append(rs)
1380
            val = RadioSettingValueString(0, 7, _filter(_msg.line2))
1381
            val.set_mutable(False)
1382
            rs = RadioSetting("sixpoweron_msg.line2",
1383
                              "6+Power-On Message 2", val)
1384
            other.append(rs)
1385

    
1386
            _msg = self._memobj.poweron_msg
1387
            rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
1388
                              RadioSettingValueString(
1389
                                  0, 7, _filter(_msg.line1)))
1390
            other.append(rs)
1391
            rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
1392
                              RadioSettingValueString(
1393
                                  0, 7, _filter(_msg.line2)))
1394
            other.append(rs)
1395

    
1396
            rs = RadioSetting("ponmsg", "Power-On Message",
1397
                              RadioSettingValueList(
1398
                                  PONMSG_LIST,
1399
                                  PONMSG_LIST[_settings.ponmsg]))
1400
            other.append(rs)
1401

    
1402
            if self._is_orig():
1403
                limit = "limits_old"
1404
            else:
1405
                limit = "limits_new"
1406

    
1407
            vhf_limit = getattr(self._memobj, limit).vhf
1408
            rs = RadioSetting("%s.vhf.lower" % limit,
1409
                              "VHF Lower Limit (MHz)",
1410
                              RadioSettingValueInteger(1, 1000,
1411
                                                       vhf_limit.lower))
1412
            other.append(rs)
1413

    
1414
            rs = RadioSetting("%s.vhf.upper" % limit,
1415
                              "VHF Upper Limit (MHz)",
1416
                              RadioSettingValueInteger(1, 1000,
1417
                                                       vhf_limit.upper))
1418
            other.append(rs)
1419

    
1420
            rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled",
1421
                              RadioSettingValueBoolean(vhf_limit.enable))
1422
            other.append(rs)
1423

    
1424
            uhf_limit = getattr(self._memobj, limit).uhf
1425
            rs = RadioSetting("%s.uhf.lower" % limit,
1426
                              "UHF Lower Limit (MHz)",
1427
                              RadioSettingValueInteger(1, 1000,
1428
                                                       uhf_limit.lower))
1429
            other.append(rs)
1430
            rs = RadioSetting("%s.uhf.upper" % limit,
1431
                              "UHF Upper Limit (MHz)",
1432
                              RadioSettingValueInteger(1, 1000,
1433
                                                       uhf_limit.upper))
1434
            other.append(rs)
1435
            rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled",
1436
                              RadioSettingValueBoolean(uhf_limit.enable))
1437
            other.append(rs)
1438

    
1439
        if self.MODEL != "UV-6":
1440
            workmode = RadioSettingGroup("workmode", "Work Mode Settings")
1441
            group.append(workmode)
1442

    
1443
            rs = RadioSetting("displayab", "Display",
1444
                              RadioSettingValueList(
1445
                                  AB_LIST, AB_LIST[_settings.displayab]))
1446
            workmode.append(rs)
1447

    
1448
            rs = RadioSetting("workmode", "VFO/MR Mode",
1449
                              RadioSettingValueList(
1450
                                  WORKMODE_LIST,
1451
                                  WORKMODE_LIST[_settings.workmode]))
1452
            workmode.append(rs)
1453

    
1454
            rs = RadioSetting("keylock", "Keypad Lock",
1455
                              RadioSettingValueBoolean(_settings.keylock))
1456
            workmode.append(rs)
1457

    
1458
            rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
1459
                              RadioSettingValueInteger(0, 127,
1460
                                                       _wmchannel.mrcha))
1461
            workmode.append(rs)
1462

    
1463
            rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
1464
                              RadioSettingValueInteger(0, 127,
1465
                                                       _wmchannel.mrchb))
1466
            workmode.append(rs)
1467

    
1468
            def convert_bytes_to_freq(bytes):
1469
                real_freq = 0
1470
                for byte in bytes:
1471
                    real_freq = (real_freq * 10) + byte
1472
                return chirp_common.format_freq(real_freq * 10)
1473

    
1474
            def my_validate(value):
1475
                value = chirp_common.parse_freq(value)
1476
                if 17400000 <= value and value < 40000000:
1477
                    msg = ("Can't be between 174.00000-400.00000")
1478
                    raise InvalidValueError(msg)
1479
                return chirp_common.format_freq(value)
1480

    
1481
            def apply_freq(setting, obj):
1482
                value = chirp_common.parse_freq(str(setting.value)) / 10
1483
                obj.band = value >= 40000000
1484
                for i in range(7, -1, -1):
1485
                    obj.freq[i] = value % 10
1486
                    value /= 10
1487

    
1488
            val1a = RadioSettingValueString(0, 10,
1489
                                            convert_bytes_to_freq(_vfoa.freq))
1490
            val1a.set_validate_callback(my_validate)
1491
            rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a)
1492
            rs.set_apply_callback(apply_freq, _vfoa)
1493
            workmode.append(rs)
1494

    
1495
            val1b = RadioSettingValueString(0, 10,
1496
                                            convert_bytes_to_freq(_vfob.freq))
1497
            val1b.set_validate_callback(my_validate)
1498
            rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b)
1499
            rs.set_apply_callback(apply_freq, _vfob)
1500
            workmode.append(rs)
1501

    
1502
            rs = RadioSetting("vfoa.sftd", "VFO A Shift",
1503
                              RadioSettingValueList(
1504
                                  SHIFTD_LIST, SHIFTD_LIST[_vfoa.sftd]))
1505
            workmode.append(rs)
1506

    
1507
            rs = RadioSetting("vfob.sftd", "VFO B Shift",
1508
                              RadioSettingValueList(
1509
                                  SHIFTD_LIST, SHIFTD_LIST[_vfob.sftd]))
1510
            workmode.append(rs)
1511

    
1512
            def convert_bytes_to_offset(bytes):
1513
                real_offset = 0
1514
                for byte in bytes:
1515
                    real_offset = (real_offset * 10) + byte
1516
                return chirp_common.format_freq(real_offset * 1000)
1517

    
1518
            def apply_offset(setting, obj):
1519
                value = chirp_common.parse_freq(str(setting.value)) / 1000
1520
                for i in range(5, -1, -1):
1521
                    obj.offset[i] = value % 10
1522
                    value /= 10
1523

    
1524
            val1a = RadioSettingValueString(
1525
                0, 10, convert_bytes_to_offset(_vfoa.offset))
1526
            rs = RadioSetting("vfoa.offset",
1527
                              "VFO A Offset (0.0-999.999)", val1a)
1528
            rs.set_apply_callback(apply_offset, _vfoa)
1529
            workmode.append(rs)
1530

    
1531
            val1b = RadioSettingValueString(
1532
                0, 10, convert_bytes_to_offset(_vfob.offset))
1533
            rs = RadioSetting("vfob.offset",
1534
                              "VFO B Offset (0.0-999.999)", val1b)
1535
            rs.set_apply_callback(apply_offset, _vfob)
1536
            workmode.append(rs)
1537

    
1538
            if self._tri_power:
1539
                if _vfoa.txpower3 > 0x02:
1540
                    val = 0x00
1541
                else:
1542
                    val = _vfoa.txpower3
1543
                rs = RadioSetting("vfoa.txpower3", "VFO A Power",
1544
                                  RadioSettingValueList(
1545
                                      TXPOWER3_LIST,
1546
                                      TXPOWER3_LIST[val]))
1547
                workmode.append(rs)
1548

    
1549
                if _vfob.txpower3 > 0x02:
1550
                    val = 0x00
1551
                else:
1552
                    val = _vfob.txpower3
1553
                rs = RadioSetting("vfob.txpower3", "VFO B Power",
1554
                                  RadioSettingValueList(
1555
                                      TXPOWER3_LIST,
1556
                                      TXPOWER3_LIST[val]))
1557
                workmode.append(rs)
1558
            else:
1559
                rs = RadioSetting("vfoa.txpower", "VFO A Power",
1560
                                  RadioSettingValueList(
1561
                                      TXPOWER_LIST,
1562
                                      TXPOWER_LIST[_vfoa.txpower]))
1563
                workmode.append(rs)
1564

    
1565
                rs = RadioSetting("vfob.txpower", "VFO B Power",
1566
                                  RadioSettingValueList(
1567
                                      TXPOWER_LIST,
1568
                                      TXPOWER_LIST[_vfob.txpower]))
1569
                workmode.append(rs)
1570

    
1571
            rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth",
1572
                              RadioSettingValueList(
1573
                                  BANDWIDTH_LIST,
1574
                                  BANDWIDTH_LIST[_vfoa.widenarr]))
1575
            workmode.append(rs)
1576

    
1577
            rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
1578
                              RadioSettingValueList(
1579
                                  BANDWIDTH_LIST,
1580
                                  BANDWIDTH_LIST[_vfob.widenarr]))
1581
            workmode.append(rs)
1582

    
1583
            rs = RadioSetting("vfoa.scode", "VFO A PTT-ID",
1584
                              RadioSettingValueList(
1585
                                  PTTIDCODE_LIST, PTTIDCODE_LIST[_vfoa.scode]))
1586
            workmode.append(rs)
1587

    
1588
            rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
1589
                              RadioSettingValueList(
1590
                                  PTTIDCODE_LIST, PTTIDCODE_LIST[_vfob.scode]))
1591
            workmode.append(rs)
1592

    
1593
            if not self._is_orig():
1594
                rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
1595
                                  RadioSettingValueList(
1596
                                      STEP291_LIST, STEP291_LIST[_vfoa.step]))
1597
                workmode.append(rs)
1598
                rs = RadioSetting("vfob.step", "VFO B Tuning Step",
1599
                                  RadioSettingValueList(
1600
                                      STEP291_LIST, STEP291_LIST[_vfob.step]))
1601
                workmode.append(rs)
1602
            else:
1603
                rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
1604
                                  RadioSettingValueList(
1605
                                      STEP_LIST, STEP_LIST[_vfoa.step]))
1606
                workmode.append(rs)
1607
                rs = RadioSetting("vfob.step", "VFO B Tuning Step",
1608
                                  RadioSettingValueList(
1609
                                      STEP_LIST, STEP_LIST[_vfob.step]))
1610
                workmode.append(rs)
1611

    
1612
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1613
        group.append(dtmf)
1614

    
1615
        if str(self._memobj.firmware_msg.line1) == "HN5RV01":
1616
            dtmfchars = "0123456789ABCD*#"
1617
        else:
1618
            dtmfchars = "0123456789 *#ABCD"
1619

    
1620
        for i in range(0, 15):
1621
            _codeobj = self._memobj.pttid[i].code
1622
            _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1623
            val = RadioSettingValueString(0, 5, _code, False)
1624
            val.set_charset(dtmfchars)
1625
            rs = RadioSetting("pttid/%i.code" % i,
1626
                              "PTT ID Code %i" % (i + 1), val)
1627

    
1628
            def apply_code(setting, obj):
1629
                code = []
1630
                for j in range(0, 5):
1631
                    try:
1632
                        code.append(dtmfchars.index(str(setting.value)[j]))
1633
                    except IndexError:
1634
                        code.append(0xFF)
1635
                obj.code = code
1636
            rs.set_apply_callback(apply_code, self._memobj.pttid[i])
1637
            dtmf.append(rs)
1638

    
1639
        dtmfcharsani = "0123456789"
1640

    
1641
        _codeobj = self._memobj.ani.code
1642
        _code = "".join([dtmfcharsani[x] for x in _codeobj if int(x) < 0x1F])
1643
        val = RadioSettingValueString(0, 5, _code, False)
1644
        val.set_charset(dtmfcharsani)
1645
        rs = RadioSetting("ani.code", "ANI Code", val)
1646

    
1647
        def apply_code(setting, obj):
1648
            code = []
1649
            for j in range(0, 5):
1650
                try:
1651
                    code.append(dtmfchars.index(str(setting.value)[j]))
1652
                except IndexError:
1653
                    code.append(0xFF)
1654
            obj.code = code
1655
        rs.set_apply_callback(apply_code, _ani)
1656
        dtmf.append(rs)
1657

    
1658
        rs = RadioSetting("ani.aniid", "ANI ID",
1659
                          RadioSettingValueList(PTTID_LIST,
1660
                                                PTTID_LIST[_ani.aniid]))
1661
        dtmf.append(rs)
1662

    
1663
        _codeobj = self._memobj.ani.alarmcode
1664
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1665
        val = RadioSettingValueString(0, 3, _code, False)
1666
        val.set_charset(dtmfchars)
1667
        rs = RadioSetting("ani.alarmcode", "Alarm Code", val)
1668

    
1669
        def apply_code(setting, obj):
1670
            alarmcode = []
1671
            for j in range(0, 3):
1672
                try:
1673
                    alarmcode.append(dtmfchars.index(str(setting.value)[j]))
1674
                except IndexError:
1675
                    alarmcode.append(0xFF)
1676
            obj.alarmcode = alarmcode
1677
        rs.set_apply_callback(apply_code, _ani)
1678
        dtmf.append(rs)
1679

    
1680
        rs = RadioSetting("dtmfst", "DTMF Sidetone",
1681
                          RadioSettingValueList(DTMFST_LIST,
1682
                                                DTMFST_LIST[_settings.dtmfst]))
1683
        dtmf.append(rs)
1684

    
1685
        if _ani.dtmfon > 0xC3:
1686
            val = 0x00
1687
        else:
1688
            val = _ani.dtmfon
1689
        rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
1690
                          RadioSettingValueList(DTMFSPEED_LIST,
1691
                                                DTMFSPEED_LIST[val]))
1692
        dtmf.append(rs)
1693

    
1694
        if _ani.dtmfoff > 0xC3:
1695
            val = 0x00
1696
        else:
1697
            val = _ani.dtmfoff
1698
        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
1699
                          RadioSettingValueList(DTMFSPEED_LIST,
1700
                                                DTMFSPEED_LIST[val]))
1701
        dtmf.append(rs)
1702

    
1703
        rs = RadioSetting("pttlt", "PTT ID Delay",
1704
                          RadioSettingValueInteger(0, 50, _settings.pttlt))
1705
        dtmf.append(rs)
1706

    
1707
        if not self._is_orig() and self._aux_block:
1708
            service = RadioSettingGroup("service", "Service Settings")
1709
            group.append(service)
1710

    
1711
            for band in ["vhf", "uhf"]:
1712
                for index in range(0, 10):
1713
                    key = "squelch_new.%s.sql%i" % (band, index)
1714
                    if band == "vhf":
1715
                        _obj = self._memobj.squelch_new.vhf
1716
                    elif band == "uhf":
1717
                        _obj = self._memobj.squelch_new.uhf
1718
                    name = "%s Squelch %i" % (band.upper(), index)
1719
                    rs = RadioSetting(key, name,
1720
                                      RadioSettingValueInteger(
1721
                                          0, 123,
1722
                                          getattr(_obj, "sql%i" % (index))))
1723
                    service.append(rs)
1724

    
1725
        return group
1726

    
1727
    def get_settings(self):
1728
        try:
1729
            return self._get_settings()
1730
        except:
1731
            import traceback
1732
            LOG.error("Failed to parse settings: %s", traceback.format_exc())
1733
            return None
1734

    
1735
    def set_settings(self, settings):
1736
        _settings = self._memobj.settings
1737
        for element in settings:
1738
            if not isinstance(element, RadioSetting):
1739
                self.set_settings(element)
1740
                continue
1741
            else:
1742
                try:
1743
                    name = element.get_name()
1744
                    if "." in name:
1745
                        bits = name.split(".")
1746
                        obj = self._memobj
1747
                        for bit in bits[:-1]:
1748
                            if "/" in bit:
1749
                                bit, index = bit.split("/", 1)
1750
                                index = int(index)
1751
                                obj = getattr(obj, bit)[index]
1752
                            else:
1753
                                obj = getattr(obj, bit)
1754
                        setting = bits[-1]
1755
                    else:
1756
                        obj = _settings
1757
                        setting = element.get_name()
1758

    
1759
                    if element.has_apply_callback():
1760
                        LOG.debug("Using apply callback")
1761
                        element.run_apply_callback()
1762
                    elif element.value.get_mutable():
1763
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1764
                        setattr(obj, setting, element.value)
1765
                except Exception:
1766
                    LOG.debug(element.get_name())
1767
                    raise
1768

    
1769

    
1770
class UV5XAlias(chirp_common.Alias):
1771
    VENDOR = "Baofeng"
1772
    MODEL = "UV-5X"
1773

    
1774

    
1775
class RT5RAlias(chirp_common.Alias):
1776
    VENDOR = "Retevis"
1777
    MODEL = "RT5R"
1778

    
1779

    
1780
class RT5RVAlias(chirp_common.Alias):
1781
    VENDOR = "Retevis"
1782
    MODEL = "RT5RV"
1783

    
1784

    
1785
class RT5Alias(chirp_common.Alias):
1786
    VENDOR = "Retevis"
1787
    MODEL = "RT5"
1788

    
1789

    
1790
class RT5_TPAlias(chirp_common.Alias):
1791
    VENDOR = "Retevis"
1792
    MODEL = "RT5(tri-power)"
1793

    
1794

    
1795
class RH5RAlias(chirp_common.Alias):
1796
    VENDOR = "Rugged"
1797
    MODEL = "RH5R"
1798

    
1799

    
1800
class ROUV5REXAlias(chirp_common.Alias):
1801
    VENDOR = "Radioddity"
1802
    MODEL = "UV-5R EX"
1803

    
1804

    
1805
class A5RAlias(chirp_common.Alias):
1806
    VENDOR = "Ansoko"
1807
    MODEL = "A-5R"
1808

    
1809

    
1810
@directory.register
1811
class BaofengUV5RGeneric(BaofengUV5R):
1812
    ALIASES = [UV5XAlias, RT5RAlias, RT5RVAlias, RT5Alias, RH5RAlias,
1813
               ROUV5REXAlias, A5RAlias]
1814

    
1815

    
1816
@directory.register
1817
class BaofengF11Radio(BaofengUV5R):
1818
    VENDOR = "Baofeng"
1819
    MODEL = "F-11"
1820
    _basetype = BASETYPE_F11
1821
    _idents = [UV5R_MODEL_F11]
1822

    
1823
    def _is_orig(self):
1824
        # Override this for F11 to always return False
1825
        return False
1826

    
1827

    
1828
@directory.register
1829
class BaofengUV82Radio(BaofengUV5R):
1830
    MODEL = "UV-82"
1831
    _basetype = BASETYPE_UV82
1832
    _idents = [UV5R_MODEL_UV82]
1833
    _vhf_range = (130000000, 176000000)
1834
    _uhf_range = (400000000, 521000000)
1835
    _valid_chars = chirp_common.CHARSET_ASCII
1836

    
1837
    def _is_orig(self):
1838
        # Override this for UV82 to always return False
1839
        return False
1840

    
1841

    
1842
@directory.register
1843
class Radioddity82X3Radio(BaofengUV82Radio):
1844
    VENDOR = "Radioddity"
1845
    MODEL = "UV-82X3"
1846
    _basetype = BASETYPE_UV82X3
1847

    
1848
    def get_features(self):
1849
        rf = BaofengUV5R.get_features(self)
1850
        rf.valid_bands = [self._vhf_range,
1851
                          (200000000, 260000000),
1852
                          self._uhf_range]
1853
        return rf
1854

    
1855

    
1856
@directory.register
1857
class BaofengUV6Radio(BaofengUV5R):
1858

    
1859
    """Baofeng UV-6/UV-7"""
1860
    VENDOR = "Baofeng"
1861
    MODEL = "UV-6"
1862
    _basetype = BASETYPE_UV6
1863
    _idents = [UV5R_MODEL_UV6,
1864
               UV5R_MODEL_UV6_ORIG
1865
               ]
1866
    _aux_block = False
1867

    
1868
    def get_features(self):
1869
        rf = BaofengUV5R.get_features(self)
1870
        rf.memory_bounds = (1, 128)
1871
        return rf
1872

    
1873
    def _get_mem(self, number):
1874
        return self._memobj.memory[number - 1]
1875

    
1876
    def _get_nam(self, number):
1877
        return self._memobj.names[number - 1]
1878

    
1879
    def _set_mem(self, number):
1880
        return self._memobj.memory[number - 1]
1881

    
1882
    def _set_nam(self, number):
1883
        return self._memobj.names[number - 1]
1884

    
1885
    def _is_orig(self):
1886
        # Override this for UV6 to always return False
1887
        return False
1888

    
1889

    
1890
@directory.register
1891
class IntekKT980Radio(BaofengUV5R):
1892
    VENDOR = "Intek"
1893
    MODEL = "KT-980HP"
1894
    _basetype = BASETYPE_KT980HP
1895
    _idents = [UV5R_MODEL_291]
1896
    _vhf_range = (130000000, 180000000)
1897
    _uhf_range = (400000000, 521000000)
1898
    _tri_power = True
1899

    
1900
    def get_features(self):
1901
        rf = BaofengUV5R.get_features(self)
1902
        rf.valid_power_levels = UV5R_POWER_LEVELS3
1903
        return rf
1904

    
1905
    def _is_orig(self):
1906
        # Override this for KT980HP to always return False
1907
        return False
1908

    
1909

    
1910
class ROGA5SAlias(chirp_common.Alias):
1911
    VENDOR = "Radioddity"
1912
    MODEL = "GA-5S"
1913

    
1914

    
1915
class UV5XPAlias(chirp_common.Alias):
1916
    VENDOR = "Baofeng"
1917
    MODEL = "UV-5XP"
1918

    
1919

    
1920
class TSTIF8Alias(chirp_common.Alias):
1921
    VENDOR = "TechSide"
1922
    MODEL = "TI-F8+"
1923

    
1924

    
1925
class TenwayUV5RPro(chirp_common.Alias):
1926
    VENDOR = 'Tenway'
1927
    MODEL = 'UV-5R Pro'
1928

    
1929

    
1930
class TSTST9Alias(chirp_common.Alias):
1931
    VENDOR = "TechSide"
1932
    MODEL = "TS-T9+"
1933

    
1934

    
1935
class TDUV5RRadio(chirp_common.Alias):
1936
    VENDOR = "TIDRADIO"
1937
    MODEL = "TD-UV5R TriPower"
1938

    
1939

    
1940
@directory.register
1941
class BaofengBFF8HPRadio(BaofengUV5R):
1942
    VENDOR = "Baofeng"
1943
    MODEL = "BF-F8HP"
1944
    ALIASES = [RT5_TPAlias, ROGA5SAlias, UV5XPAlias, TSTIF8Alias,
1945
               TenwayUV5RPro, TSTST9Alias, TDUV5RRadio]
1946
    _basetype = BASETYPE_F8HP
1947
    _idents = [UV5R_MODEL_291,
1948
               UV5R_MODEL_A58
1949
               ]
1950
    _vhf_range = (130000000, 180000000)
1951
    _uhf_range = (400000000, 521000000)
1952
    _tri_power = True
1953

    
1954
    def get_features(self):
1955
        rf = BaofengUV5R.get_features(self)
1956
        rf.valid_power_levels = UV5R_POWER_LEVELS3
1957
        return rf
1958

    
1959
    def _is_orig(self):
1960
        # Override this for BFF8HP to always return False
1961
        return False
1962

    
1963

    
1964
class TenwayUV82Pro(chirp_common.Alias):
1965
    VENDOR = 'Tenway'
1966
    MODEL = 'UV-82 Pro'
1967

    
1968

    
1969
@directory.register
1970
class BaofengUV82HPRadio(BaofengUV5R):
1971
    VENDOR = "Baofeng"
1972
    MODEL = "UV-82HP"
1973
    ALIASES = [TenwayUV82Pro]
1974
    _basetype = BASETYPE_UV82HP
1975
    _idents = [UV5R_MODEL_UV82]
1976
    _vhf_range = (136000000, 175000000)
1977
    _uhf_range = (400000000, 521000000)
1978
    _valid_chars = chirp_common.CHARSET_ALPHANUMERIC + \
1979
        "!@#$%^&*()+-=[]:\";'<>?,./"
1980
    _tri_power = True
1981

    
1982
    def get_features(self):
1983
        rf = BaofengUV5R.get_features(self)
1984
        rf.valid_power_levels = UV5R_POWER_LEVELS3
1985
        return rf
1986

    
1987
    def _is_orig(self):
1988
        # Override this for UV82HP to always return False
1989
        return False
1990

    
1991

    
1992
@directory.register
1993
class RadioddityUV5RX3Radio(BaofengUV5R):
1994
    VENDOR = "Radioddity"
1995
    MODEL = "UV-5RX3"
1996

    
1997
    def get_features(self):
1998
        rf = BaofengUV5R.get_features(self)
1999
        rf.valid_bands = [self._vhf_range,
2000
                          (200000000, 260000000),
2001
                          self._uhf_range]
2002
        return rf
2003

    
2004
    @classmethod
2005
    def match_model(cls, filename, filedata):
2006
        return False
2007

    
2008

    
2009
@directory.register
2010
class RadioddityGT5RRadio(BaofengUV5R):
2011
    VENDOR = 'Baofeng'
2012
    MODEL = 'GT-5R'
2013

    
2014
    vhftx = [144000000, 148000000]
2015
    uhftx = [420000000, 450000000]
2016

    
2017
    def validate_memory(self, mem):
2018
        msgs = super().validate_memory(mem)
2019

    
2020
        _msg_duplex = 'Duplex must be "off" for this frequency'
2021
        _msg_offset = 'Only simplex or +5 MHz offset allowed on GMRS'
2022

    
2023
        if not ((mem.freq >= self.vhftx[0] and mem.freq < self.vhftx[1]) or
2024
                (mem.freq >= self.uhftx[0] and mem.freq < self.uhftx[1])):
2025
            if mem.duplex != "off":
2026
                msgs.append(chirp_common.ValidationWarning(_msg_duplex))
2027

    
2028
        return msgs
2029

    
2030
    def check_set_memory_immutable_policy(self, existing, new):
2031
        existing.immutable = []
2032
        super().check_set_memory_immutable_policy(existing, new)
2033

    
2034
    @classmethod
2035
    def match_model(cls, filename, filedata):
2036
        return False
2037

    
2038

    
2039
@directory.register
2040
class RadioddityUV5GRadio(BaofengUV5R):
2041
    VENDOR = 'Radioddity'
2042
    MODEL = 'UV-5G'
2043

    
2044
    _basetype = BASETYPE_UV5R
2045
    _idents = [UV5R_MODEL_UV5G]
2046

    
2047
    @classmethod
2048
    def match_model(cls, filename, filedata):
2049
        return False
(39-39/41)