Project

General

Profile

Feature #10505 » uv5r_10505_v2(JohnM).py

Jim Unroe, 10/16/2023 03:27 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_aux_data_from_radio(radio):
483
    block0 = _read_block(radio, 0x1E80, 0x40, True)
484
    block1 = _read_block(radio, 0x1EC0, 0x40, False)
485
    block2 = _read_block(radio, 0x1F00, 0x40, False)
486
    block3 = _read_block(radio, 0x1F40, 0x40, False)
487
    block4 = _read_block(radio, 0x1F80, 0x40, False)
488
    block5 = _read_block(radio, 0x1FC0, 0x40, False)
489
    version = block1[48:62]
490
    area1 = block2 + block3[0:32]
491
    area2 = block3[48:64]
492
    area3 = block4[16:64]
493
    # check for dropped byte
494
    dropped_byte = block5[15:16] == b"\xFF"  # True/False
495
    return version, area1, area2, area3, dropped_byte
496

    
497

    
498
def _get_radio_firmware_version(radio):
499
    if radio.MODEL == "BJ-UV55":
500
        block = _read_block(radio, 0x1FF0, 0x40, True)
501
        version = block[0:6]
502
        return version
503
    else:
504
        # New radios will reply with 'alternative' (aka: bad) data if the Aux
505
        # memory area is read without first reading a block from another area
506
        # of memory. Reading any block that is outside of the Aux memory area
507
        # (which starts at 0x1EC0) prior to reading blocks in the Aux mem area
508
        # turns out to be a workaround for this problem.
509

    
510
        # read and disregard block0
511
        block0 = _read_block(radio, 0x1E80, 0x40, True)
512
        block1 = _read_block(radio, 0x1EC0, 0x40, False)
513
        block2 = _read_block(radio, 0x1FC0, 0x40, False)
514

    
515
        version = block1[48:62]  # firmware version
516

    
517
        # Some new radios will drop the byte at 0x1FCF when read in 0x40 byte
518
        # blocks. This results in the next 0x30 bytes being moved forward one
519
        # position (putting 0xFF in position 0x1FCF and pads the now missing
520
        # byte at the end, 0x1FFF, with 0x80).
521

    
522
        # detect dropped byte
523
        dropped_byte = (block2[15:16] == b"\xFF")  # dropped byte?
524

    
525
        return version, dropped_byte
526

    
527

    
528
IDENT_BLACKLIST = {
529
    b"\x50\x0D\x0C\x20\x16\x03\x28": "Radio identifies as BTECH UV-5X3",
530
    b"\x50\xBB\xFF\x20\x12\x06\x25": "Radio identifies as Radioddity UV-5G",
531
}
532

    
533

    
534
def _ident_radio(radio):
535
    for magic in radio._idents:
536
        error = None
537
        try:
538
            data = _do_ident(radio, magic)
539
            return data
540
        except errors.RadioError as e:
541
            LOG.error("uv5r._ident_radio: %s", e)
542
            error = e
543
            time.sleep(2)
544

    
545
    for magic, reason in list(IDENT_BLACKLIST.items()):
546
        try:
547
            _do_ident(radio, magic, secondack=False)
548
        except errors.RadioError:
549
            # No match, try the next one
550
            continue
551

    
552
        # If we got here, it means we identified the radio as
553
        # something other than one of our valid idents. Warn
554
        # the user so they can do the right thing.
555
        LOG.warning(('Identified radio as a blacklisted model '
556
                     '(details: %s)') % reason)
557
        raise errors.RadioError(('%s. Please choose the proper vendor/'
558
                                 'model and try again.') % reason)
559

    
560
    if error:
561
        raise error
562
    raise errors.RadioError("Radio did not respond")
563

    
564

    
565
def _do_download(radio):
566
    data = _ident_radio(radio)
567

    
568
    if radio.MODEL == "BJ-UV55":
569
        radio_version = _get_radio_firmware_version(radio)
570
    else:
571
        radio_version, has_dropped_byte = \
572
            _get_radio_firmware_version(radio)
573
    LOG.info("Radio Version is %s" % repr(radio_version))
574
    LOG.info("Radio has dropped byte issue: %s" % repr(has_dropped_byte))
575

    
576
    # Main block
577
    LOG.debug("downloading main block...")
578
    for i in range(0, 0x1800, 0x40):
579
        data += _read_block(radio, i, 0x40, False)
580
        _do_status(radio, "from", i)
581
    _do_status(radio, "from", radio.get_memsize())
582
    LOG.debug("done.")
583
    if radio._aux_block:
584
        if has_dropped_byte:
585
            LOG.debug("downloading aux block...")
586
            # Auxiliary block starts at 0x1ECO (?)
587
            for i in range(0x1EC0, 0x1FC0, 0x40):
588
                data += _read_block(radio, i, 0x40, False)
589
            # Shift to 0x10 block sizes as a workaround for new radios that
590
            # will drop byte 0x1FCF if the last 0x40 bytes are read using a
591
            # 0x40 block size
592
            for i in range(0x1FC0, 0x2000, 0x10):
593
                data += _read_block(radio, i, 0x10, False)
594
        else:
595
            # Retain 0x40 byte block download for legacy radios (the
596
            # 'original' radios with firmware versions prior to BFB291 do not
597
            # support reading the Aux memory are with 0x10 bytes blocks.
598
            LOG.debug("downloading aux block...")
599
            # Auxiliary block starts at 0x1ECO (?)
600
            for i in range(0x1EC0, 0x2000, 0x40):
601
                data += _read_block(radio, i, 0x40, False)
602

    
603
    LOG.debug("done.")
604
    return memmap.MemoryMapBytes(data)
605

    
606

    
607
def _send_block(radio, addr, data):
608
    msg = struct.pack(">BHB", ord("X"), addr, len(data))
609
    radio.pipe.write(msg + data)
610
    time.sleep(0.05)
611

    
612
    ack = radio.pipe.read(1)
613
    if ack != b"\x06":
614
        raise errors.RadioError("Radio refused to accept block 0x%04x" % addr)
615

    
616

    
617
def _do_upload(radio):
618
    ident = _ident_radio(radio)
619
    radio_upper_band = ident[3:4]
620
    image_upper_band = _upper_band_from_image(radio)
621

    
622
    if image_upper_band == vhf_220_radio or radio_upper_band == vhf_220_radio:
623
        if image_upper_band != radio_upper_band:
624
            raise errors.RadioError("Image not supported by radio")
625

    
626
    image_version = _firmware_version_from_image(radio)
627
    if radio.MODEL == "BJ-UV55":
628
        radio_version = _get_radio_firmware_version(radio)
629

    
630
        # default ranges
631
        _ranges_main_default = radio._ranges_main
632
        _ranges_aux_default = radio._ranges_aux
633
    else:
634
        radio_version, aux_r1, aux_r2, aux_r3, \
635
            has_dropped_byte = _get_aux_data_from_radio(radio)
636
        LOG.info("Radio has dropped byte issue: %s" % repr(has_dropped_byte))
637

    
638
        # determine if radio is 'original' radio
639
        if b'BFB' in radio_version:
640
            idx = radio_version.index(b"BFB") + 3
641
            version = int(radio_version[idx:idx + 3])
642
            _radio_is_orig = version < 291
643
        else:
644
            _radio_is_orig = False
645

    
646
        # determine if image is from 'original' radio
647
        _image_is_orig = radio._is_orig()
648

    
649
        if _image_is_orig != _radio_is_orig:
650
            raise errors.RadioError("Image not supported by radio")
651

    
652
        ##if has_dropped_byte:
653
        aux_i1 = _get_data_from_image(radio, 0x1848, 0x18A8)
654
        aux_i2 = _get_data_from_image(radio, 0x18B8, 0x18C8)
655
        aux_i3 = _get_data_from_image(radio, 0x18D8, 0x1908)
656

    
657
        ##msg = ("Image not supported by radio. You must...\n"
658
        ##       "1. Download from radio.\n"
659
        ##       "2. Make changes.\n"
660
        ##       "3. Upload back to same radio.")
661

    
662
        # check if Aux memory of image matches Aux memory of radio
663
        aux_matched = False
664
        if aux_i1 != aux_r1:
665
            # Area 1 does not match
666
            # The safest thing to do is to skip uploading Aux mem area.
667
            LOG.info("Aux memory mis-match")
668
            LOG.info("Aux area 1 from image is %s" % repr(aux_i1))
669
            LOG.info("Aux area 1 from radio is %s" % repr(aux_r1))
670
            ##raise errors.RadioError(msg)
671
        elif aux_i2 != aux_r2:
672
            # Area 2 does not match
673
            # The safest thing to do is to skip uploading Aux mem area.
674
            LOG.info("Aux memory mis-match")
675
            LOG.info("Aux area 2 from image is %s" % repr(aux_i2))
676
            LOG.info("Aux area 2 from radio is %s" % repr(aux_r2))
677
            ##raise errors.RadioError(msg)
678
        elif aux_i3 != aux_r3:
679
            # Area 3 does not match
680
            # The safest thing to do is to skip uploading Aux mem area.
681
            LOG.info("Aux memory mis-match")
682
            LOG.info("Aux area 3 from image is %s" % repr(aux_i3))
683
            LOG.info("Aux area 3 from radio is %s" % repr(aux_r3))
684
            ##raise errors.RadioError(msg)
685
        else:
686
            # All areas matched
687
            # Uploading full Aux mem area is permitted
688
            aux_matched = True
689
            ##_skip_aux_block = False
690

    
691
        if has_dropped_byte and not aux_matched:
692
            msg = ("Image not supported by radio. You must...\n"
693
                   "1. Download from radio.\n"
694
                   "2. Make changes.\n"
695
                   "3. Upload back to same radio.")
696
            raise errors.RadioError(msg)
697

    
698
        # default ranges
699
        _ranges_main_default = [
700
            (0x0008, 0x0CF8),  # skip 0x0CF8 - 0x0D08
701
            (0x0D08, 0x0DF8),  # skip 0x0DF8 - 0x0E08
702
            (0x0E08, 0x1808),
703
            ]
704

    
705
        if _image_is_orig:
706
            # default Aux mem ranges for radios before BFB291
707
            _ranges_aux_default = [
708
                (0x1EE0, 0x1EF0),  # welcome message
709
                (0x1FC0, 0x1FE0),  # old band limits
710
                ]
711
        elif has_dropped_byte or aux_matched:
712
            # default Aux mem ranges for radios with dropped byte issue
713
            _ranges_aux_default = [
714
                (0x1EC0, 0x2000),  # the full Aux mem range
715
                ]
716
        else:
717
            # default Aux mem ranges for radios from BFB291 to present
718
            # (that don't have dropped byte issue)
719
            _ranges_aux_default = [
720
                (0x1EE0, 0x1EF0),  # welcome message
721
                (0x1F60, 0x1F70),  # vhf squelch thresholds
722
                (0x1F80, 0x1F90),  # uhf squelch thresholds
723
                (0x1FC0, 0x1FD0),  # new band limits
724
                ]
725

    
726
    LOG.info("Image Version is %s" % repr(image_version))
727
    LOG.info("Radio Version is %s" % repr(radio_version))
728

    
729
    if radio._all_range_flag:
730
        # user enabled 'Range Override Parameter', upload everything
731
        ranges_main = radio._ranges_main
732
        ranges_aux = radio._ranges_aux
733
        LOG.warning('Sending all ranges to radio as instructed')
734
    else:
735
        # set default ranges
736
        ranges_main = _ranges_main_default
737
        ranges_aux = _ranges_aux_default
738

    
739
    # Main block
740
    mmap = radio.get_mmap().get_byte_compatible()
741
    for start_addr, end_addr in ranges_main:
742
        for i in range(start_addr, end_addr, 0x10):
743
            _send_block(radio, i - 0x08, mmap[i:i + 0x10])
744
            _do_status(radio, "to", i)
745
        _do_status(radio, "to", radio.get_memsize())
746

    
747
    if len(mmap.get_packed()) == 0x1808:
748
        LOG.info("Old image, not writing aux block")
749
        return  # Old image, no aux block
750

    
751
    # Auxiliary block at radio address 0x1EC0, our offset 0x1808
752
    for start_addr, end_addr in ranges_aux:
753
        for i in range(start_addr, end_addr, 0x10):
754
            addr = 0x1808 + (i - 0x1EC0)
755
            _send_block(radio, i, mmap[addr:addr + 0x10])
756

    
757
    if radio._all_range_flag:
758
        radio._all_range_flag = False
759
        LOG.warning('Sending all ranges to radio has completed')
760
        raise errors.RadioError(
761
            "This is NOT an error.\n"
762
            "The upload has finished successfully.\n"
763
            "Please restart CHIRP.")
764

    
765

    
766
UV5R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=4.00),
767
                     chirp_common.PowerLevel("Low",  watts=1.00)]
768

    
769
UV5R_POWER_LEVELS3 = [chirp_common.PowerLevel("High", watts=8.00),
770
                      chirp_common.PowerLevel("Med",  watts=4.00),
771
                      chirp_common.PowerLevel("Low",  watts=1.00)]
772

    
773
UV5R_DTCS = tuple(sorted(chirp_common.DTCS_CODES + (645,)))
774

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

    
778

    
779
def model_match(cls, data):
780
    """Match the opened/downloaded image to the correct version"""
781

    
782
    if len(data) == 0x1950:
783
        rid = data[0x1948:0x1950]
784
        return rid.startswith(cls.MODEL)
785
    elif len(data) == 0x1948:
786
        rid = data[cls._fw_ver_file_start:cls._fw_ver_file_stop]
787
        if any(type in rid for type in cls._basetype):
788
            return True
789
    else:
790
        return False
791

    
792

    
793
class BaofengUV5R(chirp_common.CloneModeRadio):
794

    
795
    """Baofeng UV-5R"""
796
    VENDOR = "Baofeng"
797
    MODEL = "UV-5R"
798
    BAUD_RATE = 9600
799
    NEEDS_COMPAT_SERIAL = False
800

    
801
    _memsize = 0x1808
802
    _basetype = BASETYPE_UV5R
803
    _idents = [UV5R_MODEL_291,
804
               UV5R_MODEL_ORIG
805
               ]
806
    _vhf_range = (130000000, 176000000)
807
    _220_range = (220000000, 260000000)
808
    _uhf_range = (400000000, 520000000)
809
    _aux_block = True
810
    _tri_power = False
811
    _bw_shift = False
812
    _mem_params = (0x1828  # poweron_msg offset
813
                   )
814
    # offset of fw version in image file
815
    _fw_ver_file_start = 0x1838
816
    _fw_ver_file_stop = 0x1846
817

    
818
    _ranges_main = [
819
                    (0x0008, 0x1808),
820
                   ]
821
    _ranges_aux = [
822
                   (0x1EC0, 0x2000),
823
                  ]
824
    _valid_chars = UV5R_CHARSET
825

    
826
    @classmethod
827
    def get_prompts(cls):
828
        rp = chirp_common.RadioPrompts()
829
        rp.experimental = \
830
            ('Due to the fact that the manufacturer continues to '
831
             'release new versions of the firmware with obscure and '
832
             'hard-to-track changes, this driver may not work with '
833
             'your device. Thus far and to the best knowledge of the '
834
             'author, no UV-5R radios have been harmed by using CHIRP. '
835
             'However, proceed at your own risk!')
836
        rp.pre_download = _(
837
            "1. Turn radio off.\n"
838
            "2. Connect cable to mic/spkr connector.\n"
839
            "3. Make sure connector is firmly connected.\n"
840
            "4. Turn radio on (volume may need to be set at 100%).\n"
841
            "5. Ensure that the radio is tuned to channel with no"
842
            " activity.\n"
843
            "6. Click OK to download image from device.\n")
844
        rp.pre_upload = _(
845
            "1. Turn radio off.\n"
846
            "2. Connect cable to mic/spkr connector.\n"
847
            "3. Make sure connector is firmly connected.\n"
848
            "4. Turn radio on (volume may need to be set at 100%).\n"
849
            "5. Ensure that the radio is tuned to channel with no"
850
            " activity.\n"
851
            "6. Click OK to upload image to device.\n")
852
        return rp
853

    
854
    def get_features(self):
855
        rf = chirp_common.RadioFeatures()
856
        rf.has_settings = True
857
        rf.has_bank = False
858
        rf.has_cross = True
859
        rf.has_rx_dtcs = True
860
        rf.has_tuning_step = False
861
        rf.can_odd_split = True
862
        rf.valid_name_length = 7
863
        rf.valid_characters = self._valid_chars
864
        rf.valid_skips = ["", "S"]
865
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
866
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
867
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
868
        rf.valid_power_levels = UV5R_POWER_LEVELS
869
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
870
        rf.valid_modes = ["FM", "NFM"]
871
        rf.valid_tuning_steps = STEPS
872
        rf.valid_dtcs_codes = UV5R_DTCS
873

    
874
        normal_bands = [self._vhf_range, self._uhf_range]
875
        rax_bands = [self._vhf_range, self._220_range]
876

    
877
        if self._mmap is None:
878
            rf.valid_bands = [normal_bands[0], rax_bands[1], normal_bands[1]]
879
        elif not self._is_orig() and self._my_upper_band() == vhf_220_radio:
880
            rf.valid_bands = rax_bands
881
        else:
882
            rf.valid_bands = normal_bands
883
        rf.memory_bounds = (0, 127)
884
        return rf
885

    
886
    @classmethod
887
    def match_model(cls, filedata, filename):
888
        match_size = False
889
        match_model = False
890
        if len(filedata) in [0x1808, 0x1948, 0x1950]:
891
            match_size = True
892
        match_model = model_match(cls, filedata)
893

    
894
        if match_size and match_model:
895
            return True
896
        else:
897
            return False
898

    
899
    def process_mmap(self):
900
        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
901
        self._all_range_flag = False
902

    
903
    def sync_in(self):
904
        try:
905
            self._mmap = _do_download(self)
906
        except errors.RadioError:
907
            raise
908
        except Exception as e:
909
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
910
        self.process_mmap()
911

    
912
    def sync_out(self):
913
        try:
914
            _do_upload(self)
915
        except errors.RadioError:
916
            raise
917
        except Exception as e:
918
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
919

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

    
923
    def _is_txinh(self, _mem):
924
        raw_tx = ""
925
        for i in range(0, 4):
926
            raw_tx += _mem.txfreq[i].get_raw()
927
        return raw_tx == "\xFF\xFF\xFF\xFF"
928

    
929
    def _get_mem(self, number):
930
        return self._memobj.memory[number]
931

    
932
    def _get_nam(self, number):
933
        return self._memobj.names[number]
934

    
935
    def get_memory(self, number):
936
        _mem = self._get_mem(number)
937
        _nam = self._get_nam(number)
938

    
939
        mem = chirp_common.Memory()
940
        mem.number = number
941

    
942
        if _mem.get_raw()[0] == "\xff":
943
            mem.empty = True
944
            return mem
945

    
946
        mem.freq = int(_mem.rxfreq) * 10
947

    
948
        if self._is_txinh(_mem):
949
            mem.duplex = "off"
950
            mem.offset = 0
951
        elif int(_mem.rxfreq) == int(_mem.txfreq):
952
            mem.duplex = ""
953
            mem.offset = 0
954
        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
955
            mem.duplex = "split"
956
            mem.offset = int(_mem.txfreq) * 10
957
        else:
958
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
959
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
960

    
961
        for char in _nam.name:
962
            if str(char) == "\xFF":
963
                char = " "  # The UV-5R software may have 0xFF mid-name
964
            mem.name += str(char)
965
        mem.name = mem.name.rstrip()
966

    
967
        dtcs_pol = ["N", "N"]
968

    
969
        if _mem.txtone in [0, 0xFFFF]:
970
            txmode = ""
971
        elif _mem.txtone >= 0x0258:
972
            txmode = "Tone"
973
            mem.rtone = int(_mem.txtone) / 10.0
974
        elif _mem.txtone <= 0x0258:
975
            txmode = "DTCS"
976
            if _mem.txtone > 0x69:
977
                index = _mem.txtone - 0x6A
978
                dtcs_pol[0] = "R"
979
            else:
980
                index = _mem.txtone - 1
981
            mem.dtcs = UV5R_DTCS[index]
982
        else:
983
            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
984

    
985
        if _mem.rxtone in [0, 0xFFFF]:
986
            rxmode = ""
987
        elif _mem.rxtone >= 0x0258:
988
            rxmode = "Tone"
989
            mem.ctone = int(_mem.rxtone) / 10.0
990
        elif _mem.rxtone <= 0x0258:
991
            rxmode = "DTCS"
992
            if _mem.rxtone >= 0x6A:
993
                index = _mem.rxtone - 0x6A
994
                dtcs_pol[1] = "R"
995
            else:
996
                index = _mem.rxtone - 1
997
            mem.rx_dtcs = UV5R_DTCS[index]
998
        else:
999
            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
1000

    
1001
        if txmode == "Tone" and not rxmode:
1002
            mem.tmode = "Tone"
1003
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
1004
            mem.tmode = "TSQL"
1005
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
1006
            mem.tmode = "DTCS"
1007
        elif rxmode or txmode:
1008
            mem.tmode = "Cross"
1009
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
1010

    
1011
        mem.dtcs_polarity = "".join(dtcs_pol)
1012

    
1013
        if not _mem.scan:
1014
            mem.skip = "S"
1015

    
1016
        if self._tri_power:
1017
            levels = UV5R_POWER_LEVELS3
1018
        else:
1019
            levels = UV5R_POWER_LEVELS
1020
        try:
1021
            mem.power = levels[_mem.lowpower]
1022
        except IndexError:
1023
            LOG.error("Radio reported invalid power level %s (in %s)" %
1024
                      (_mem.lowpower, levels))
1025
            mem.power = levels[0]
1026

    
1027
        mem.mode = _mem.wide and "FM" or "NFM"
1028

    
1029
        mem.extra = RadioSettingGroup("Extra", "extra")
1030

    
1031
        rs = RadioSetting("bcl", "BCL",
1032
                          RadioSettingValueBoolean(_mem.bcl))
1033
        mem.extra.append(rs)
1034

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

    
1040
        rs = RadioSetting("scode", "PTT ID Code",
1041
                          RadioSettingValueList(PTTIDCODE_LIST,
1042
                                                PTTIDCODE_LIST[_mem.scode]))
1043
        mem.extra.append(rs)
1044

    
1045
        immutable = []
1046

    
1047
        if self.MODEL == "GT-5R":
1048
            if not ((mem.freq >= self.vhftx[0] and mem.freq < self.vhftx[1]) or
1049
                    (mem.freq >= self.uhftx[0] and mem.freq < self.uhftx[1])):
1050
                mem.duplex = 'off'
1051
                mem.offset = 0
1052
                immutable = ["duplex", "offset"]
1053

    
1054
        mem.immutable = immutable
1055

    
1056
        return mem
1057

    
1058
    def _set_mem(self, number):
1059
        return self._memobj.memory[number]
1060

    
1061
    def _set_nam(self, number):
1062
        return self._memobj.names[number]
1063

    
1064
    def set_memory(self, mem):
1065
        _mem = self._get_mem(mem.number)
1066
        _nam = self._get_nam(mem.number)
1067

    
1068
        if mem.empty:
1069
            _mem.set_raw("\xff" * 16)
1070
            _nam.set_raw("\xff" * 16)
1071
            return
1072

    
1073
        was_empty = False
1074
        # same method as used in get_memory to find
1075
        # out whether a raw memory is empty
1076
        if _mem.get_raw()[0] == "\xff":
1077
            was_empty = True
1078
            LOG.debug("UV5R: this mem was empty")
1079
        else:
1080
            # memorize old extra-values before erasing the whole memory
1081
            # used to solve issue 4121
1082
            LOG.debug("mem was not empty, memorize extra-settings")
1083
            prev_bcl = _mem.bcl.get_value()
1084
            prev_scode = _mem.scode.get_value()
1085
            prev_pttid = _mem.pttid.get_value()
1086

    
1087
        _mem.set_raw("\x00" * 16)
1088

    
1089
        _mem.rxfreq = mem.freq / 10
1090

    
1091
        if mem.duplex == "off":
1092
            for i in range(0, 4):
1093
                _mem.txfreq[i].set_raw("\xFF")
1094
        elif mem.duplex == "split":
1095
            _mem.txfreq = mem.offset / 10
1096
        elif mem.duplex == "+":
1097
            _mem.txfreq = (mem.freq + mem.offset) / 10
1098
        elif mem.duplex == "-":
1099
            _mem.txfreq = (mem.freq - mem.offset) / 10
1100
        else:
1101
            _mem.txfreq = mem.freq / 10
1102

    
1103
        _namelength = self.get_features().valid_name_length
1104
        for i in range(_namelength):
1105
            try:
1106
                _nam.name[i] = mem.name[i]
1107
            except IndexError:
1108
                _nam.name[i] = "\xFF"
1109

    
1110
        rxmode = txmode = ""
1111
        if mem.tmode == "Tone":
1112
            _mem.txtone = int(mem.rtone * 10)
1113
            _mem.rxtone = 0
1114
        elif mem.tmode == "TSQL":
1115
            _mem.txtone = int(mem.ctone * 10)
1116
            _mem.rxtone = int(mem.ctone * 10)
1117
        elif mem.tmode == "DTCS":
1118
            rxmode = txmode = "DTCS"
1119
            _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
1120
            _mem.rxtone = UV5R_DTCS.index(mem.dtcs) + 1
1121
        elif mem.tmode == "Cross":
1122
            txmode, rxmode = mem.cross_mode.split("->", 1)
1123
            if txmode == "Tone":
1124
                _mem.txtone = int(mem.rtone * 10)
1125
            elif txmode == "DTCS":
1126
                _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
1127
            else:
1128
                _mem.txtone = 0
1129
            if rxmode == "Tone":
1130
                _mem.rxtone = int(mem.ctone * 10)
1131
            elif rxmode == "DTCS":
1132
                _mem.rxtone = UV5R_DTCS.index(mem.rx_dtcs) + 1
1133
            else:
1134
                _mem.rxtone = 0
1135
        else:
1136
            _mem.rxtone = 0
1137
            _mem.txtone = 0
1138

    
1139
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
1140
            _mem.txtone += 0x69
1141
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
1142
            _mem.rxtone += 0x69
1143

    
1144
        _mem.scan = mem.skip != "S"
1145
        _mem.wide = mem.mode == "FM"
1146

    
1147
        if mem.power:
1148
            if self._tri_power:
1149
                levels = [str(l) for l in UV5R_POWER_LEVELS3]
1150
                _mem.lowpower = levels.index(str(mem.power))
1151
            else:
1152
                _mem.lowpower = UV5R_POWER_LEVELS.index(mem.power)
1153
        else:
1154
            _mem.lowpower = 0
1155

    
1156
        if not was_empty:
1157
            # restoring old extra-settings (issue 4121
1158
            _mem.bcl.set_value(prev_bcl)
1159
            _mem.scode.set_value(prev_scode)
1160
            _mem.pttid.set_value(prev_pttid)
1161

    
1162
        for setting in mem.extra:
1163
            setattr(_mem, setting.get_name(), setting.value)
1164

    
1165
    def _is_orig(self):
1166
        version_tag = _firmware_version_from_image(self)
1167
        try:
1168
            if b'BFB' in version_tag:
1169
                idx = version_tag.index(b"BFB") + 3
1170
                version = int(version_tag[idx:idx + 3])
1171
                return version < 291
1172
            return False
1173
        except:
1174
            pass
1175
        raise errors.RadioError("Unable to parse version string %s" %
1176
                                version_tag)
1177

    
1178
    def _my_version(self):
1179
        version_tag = _firmware_version_from_image(self)
1180
        if b'BFB' in version_tag:
1181
            idx = version_tag.index(b"BFB") + 3
1182
            return int(version_tag[idx:idx + 3])
1183

    
1184
        raise Exception("Unrecognized firmware version string")
1185

    
1186
    def _my_upper_band(self):
1187
        band_tag = _upper_band_from_image(self)
1188
        return band_tag
1189

    
1190
    def _get_settings(self):
1191
        _mem = self._memobj
1192
        _ani = self._memobj.ani
1193
        _settings = self._memobj.settings
1194
        _squelch = self._memobj.squelch_new
1195
        _vfoa = self._memobj.vfoa
1196
        _vfob = self._memobj.vfob
1197
        _wmchannel = self._memobj.wmchannel
1198

    
1199
        basic = RadioSettingGroup("basic", "Basic Settings")
1200
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
1201

    
1202
        group = RadioSettings(basic, advanced)
1203

    
1204
        rs = RadioSetting("squelch", "Carrier Squelch Level",
1205
                          RadioSettingValueInteger(0, 9, _settings.squelch))
1206
        basic.append(rs)
1207

    
1208
        rs = RadioSetting("save", "Battery Saver",
1209
                          RadioSettingValueList(
1210
                              SAVE_LIST, SAVE_LIST[_settings.save]))
1211
        basic.append(rs)
1212

    
1213
        rs = RadioSetting("vox", "VOX Sensitivity",
1214
                          RadioSettingValueList(
1215
                              VOX_LIST, VOX_LIST[_settings.vox]))
1216
        advanced.append(rs)
1217

    
1218
        if self.MODEL == "UV-6":
1219
            # NOTE: The UV-6 calls this byte voxenable, but the UV-5R calls it
1220
            # autolk. Since this is a minor difference, it will be referred to
1221
            # by the wrong name for the UV-6.
1222
            rs = RadioSetting("autolk", "Vox",
1223
                              RadioSettingValueBoolean(_settings.autolk))
1224
            advanced.append(rs)
1225

    
1226
        if self.MODEL != "UV-6":
1227
            rs = RadioSetting("abr", "Backlight Timeout",
1228
                              RadioSettingValueInteger(0, 24, _settings.abr))
1229
            basic.append(rs)
1230

    
1231
        rs = RadioSetting("tdr", "Dual Watch",
1232
                          RadioSettingValueBoolean(_settings.tdr))
1233
        advanced.append(rs)
1234

    
1235
        if self.MODEL == "UV-6":
1236
            rs = RadioSetting("tdrch", "Dual Watch Channel",
1237
                              RadioSettingValueList(
1238
                                  TDRCH_LIST, TDRCH_LIST[_settings.tdrch]))
1239
            advanced.append(rs)
1240

    
1241
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
1242
                              RadioSettingValueBoolean(_settings.tdrab))
1243
            advanced.append(rs)
1244
        else:
1245
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
1246
                              RadioSettingValueList(
1247
                                  TDRAB_LIST, TDRAB_LIST[_settings.tdrab]))
1248
            advanced.append(rs)
1249

    
1250
        if self.MODEL == "UV-6":
1251
            rs = RadioSetting("alarm", "Alarm Sound",
1252
                              RadioSettingValueBoolean(_settings.alarm))
1253
            advanced.append(rs)
1254

    
1255
        if _settings.almod > 0x02:
1256
            val = 0x01
1257
        else:
1258
            val = _settings.almod
1259
        rs = RadioSetting("almod", "Alarm Mode",
1260
                          RadioSettingValueList(
1261
                              ALMOD_LIST, ALMOD_LIST[val]))
1262
        advanced.append(rs)
1263

    
1264
        rs = RadioSetting("beep", "Beep",
1265
                          RadioSettingValueBoolean(_settings.beep))
1266
        basic.append(rs)
1267

    
1268
        rs = RadioSetting("timeout", "Timeout Timer",
1269
                          RadioSettingValueList(
1270
                              TIMEOUT_LIST, TIMEOUT_LIST[_settings.timeout]))
1271
        basic.append(rs)
1272

    
1273
        if ((self._is_orig() and self._my_version() < 251) or
1274
                (self.MODEL in ["TI-F8+", "TS-T9+"])):
1275
            rs = RadioSetting("voice", "Voice",
1276
                              RadioSettingValueBoolean(_settings.voice))
1277
            advanced.append(rs)
1278
        else:
1279
            rs = RadioSetting("voice", "Voice",
1280
                              RadioSettingValueList(
1281
                                  VOICE_LIST, VOICE_LIST[_settings.voice]))
1282
            advanced.append(rs)
1283

    
1284
        rs = RadioSetting("screv", "Scan Resume",
1285
                          RadioSettingValueList(
1286
                              RESUME_LIST, RESUME_LIST[_settings.screv]))
1287
        advanced.append(rs)
1288

    
1289
        if self.MODEL != "UV-6":
1290
            rs = RadioSetting("mdfa", "Display Mode (A)",
1291
                              RadioSettingValueList(
1292
                                  MODE_LIST, MODE_LIST[_settings.mdfa]))
1293
            basic.append(rs)
1294

    
1295
            rs = RadioSetting("mdfb", "Display Mode (B)",
1296
                              RadioSettingValueList(
1297
                                  MODE_LIST, MODE_LIST[_settings.mdfb]))
1298
            basic.append(rs)
1299

    
1300
        rs = RadioSetting("bcl", "Busy Channel Lockout",
1301
                          RadioSettingValueBoolean(_settings.bcl))
1302
        advanced.append(rs)
1303

    
1304
        if self.MODEL != "UV-6":
1305
            rs = RadioSetting("autolk", "Automatic Key Lock",
1306
                              RadioSettingValueBoolean(_settings.autolk))
1307
            advanced.append(rs)
1308

    
1309
        rs = RadioSetting("fmradio", "Broadcast FM Radio",
1310
                          RadioSettingValueBoolean(_settings.fmradio))
1311
        advanced.append(rs)
1312

    
1313
        if self.MODEL != "UV-6":
1314
            rs = RadioSetting("wtled", "Standby LED Color",
1315
                              RadioSettingValueList(
1316
                                  COLOR_LIST, COLOR_LIST[_settings.wtled]))
1317
            basic.append(rs)
1318

    
1319
            rs = RadioSetting("rxled", "RX LED Color",
1320
                              RadioSettingValueList(
1321
                                  COLOR_LIST, COLOR_LIST[_settings.rxled]))
1322
            basic.append(rs)
1323

    
1324
            rs = RadioSetting("txled", "TX LED Color",
1325
                              RadioSettingValueList(
1326
                                  COLOR_LIST, COLOR_LIST[_settings.txled]))
1327
            basic.append(rs)
1328

    
1329
        if isinstance(self, BaofengUV82Radio):
1330
            rs = RadioSetting("roger", "Roger Beep (TX)",
1331
                              RadioSettingValueBoolean(_settings.roger))
1332
            basic.append(rs)
1333
            rs = RadioSetting("rogerrx", "Roger Beep (RX)",
1334
                              RadioSettingValueList(
1335
                                  ROGERRX_LIST,
1336
                                  ROGERRX_LIST[_settings.rogerrx]))
1337
            basic.append(rs)
1338
        else:
1339
            rs = RadioSetting("roger", "Roger Beep",
1340
                              RadioSettingValueBoolean(_settings.roger))
1341
            basic.append(rs)
1342

    
1343
        rs = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)",
1344
                          RadioSettingValueBoolean(_settings.ste))
1345
        advanced.append(rs)
1346

    
1347
        rs = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)",
1348
                          RadioSettingValueList(
1349
                              RPSTE_LIST, RPSTE_LIST[_settings.rpste]))
1350
        advanced.append(rs)
1351

    
1352
        rs = RadioSetting("rptrl", "STE Repeater Delay",
1353
                          RadioSettingValueList(
1354
                              STEDELAY_LIST, STEDELAY_LIST[_settings.rptrl]))
1355
        advanced.append(rs)
1356

    
1357
        if self.MODEL != "UV-6":
1358
            rs = RadioSetting("reset", "RESET Menu",
1359
                              RadioSettingValueBoolean(_settings.reset))
1360
            advanced.append(rs)
1361

    
1362
            rs = RadioSetting("menu", "All Menus",
1363
                              RadioSettingValueBoolean(_settings.menu))
1364
            advanced.append(rs)
1365

    
1366
        if self.MODEL == "F-11":
1367
            # this is an F-11 only feature
1368
            rs = RadioSetting("vfomrlock", "VFO/MR Button",
1369
                              RadioSettingValueBoolean(_settings.vfomrlock))
1370
            advanced.append(rs)
1371

    
1372
        if isinstance(self, BaofengUV82Radio):
1373
            # this is a UV-82C only feature
1374
            rs = RadioSetting("vfomrlock", "VFO/MR Switching (UV-82C only)",
1375
                              RadioSettingValueBoolean(_settings.vfomrlock))
1376
            advanced.append(rs)
1377

    
1378
        if self.MODEL == "UV-82HP":
1379
            # this is a UV-82HP only feature
1380
            rs = RadioSetting(
1381
                "vfomrlock", "VFO/MR Switching (BTech UV-82HP only)",
1382
                RadioSettingValueBoolean(_settings.vfomrlock))
1383
            advanced.append(rs)
1384

    
1385
        if isinstance(self, BaofengUV82Radio):
1386
            # this is an UV-82C only feature
1387
            rs = RadioSetting("singleptt", "Single PTT (UV-82C only)",
1388
                              RadioSettingValueBoolean(_settings.singleptt))
1389
            advanced.append(rs)
1390

    
1391
        if self.MODEL == "UV-82HP":
1392
            # this is an UV-82HP only feature
1393
            rs = RadioSetting("singleptt", "Single PTT (BTech UV-82HP only)",
1394
                              RadioSettingValueBoolean(_settings.singleptt))
1395
            advanced.append(rs)
1396

    
1397
        if self.MODEL == "UV-82HP":
1398
            # this is an UV-82HP only feature
1399
            rs = RadioSetting(
1400
                "tdrch", "Tone Burst Frequency (BTech UV-82HP only)",
1401
                RadioSettingValueList(RTONE_LIST, RTONE_LIST[_settings.tdrch]))
1402
            advanced.append(rs)
1403

    
1404
        def set_range_flag(setting):
1405
            val = [85, 115, 101, 65, 116, 79, 119, 110, 82, 105, 115, 107]
1406
            if [ord(x) for x in str(setting.value).strip()] == val:
1407
                self._all_range_flag = True
1408
            else:
1409
                self._all_range_flag = False
1410
            LOG.debug('Set range flag to %s' % self._all_range_flag)
1411

    
1412
        rs = RadioSetting("allrange", "Range Override Parameter",
1413
                          RadioSettingValueString(0, 12, "Default"))
1414
        rs.set_apply_callback(set_range_flag)
1415
        advanced.append(rs)
1416

    
1417
        if len(self._mmap.get_packed()) == 0x1808:
1418
            # Old image, without aux block
1419
            return group
1420

    
1421
        if self.MODEL != "UV-6":
1422
            other = RadioSettingGroup("other", "Other Settings")
1423
            group.append(other)
1424

    
1425
            def _filter(name):
1426
                filtered = ""
1427
                for char in str(name):
1428
                    if char in chirp_common.CHARSET_ASCII:
1429
                        filtered += char
1430
                    else:
1431
                        filtered += " "
1432
                return filtered
1433

    
1434
            _msg = self._memobj.firmware_msg
1435
            val = RadioSettingValueString(0, 7, _filter(_msg.line1))
1436
            val.set_mutable(False)
1437
            rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
1438
            other.append(rs)
1439

    
1440
            val = RadioSettingValueString(0, 7, _filter(_msg.line2))
1441
            val.set_mutable(False)
1442
            rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
1443
            other.append(rs)
1444

    
1445
            _msg = self._memobj.sixpoweron_msg
1446
            val = RadioSettingValueString(0, 7, _filter(_msg.line1))
1447
            val.set_mutable(False)
1448
            rs = RadioSetting("sixpoweron_msg.line1",
1449
                              "6+Power-On Message 1", val)
1450
            other.append(rs)
1451
            val = RadioSettingValueString(0, 7, _filter(_msg.line2))
1452
            val.set_mutable(False)
1453
            rs = RadioSetting("sixpoweron_msg.line2",
1454
                              "6+Power-On Message 2", val)
1455
            other.append(rs)
1456

    
1457
            _msg = self._memobj.poweron_msg
1458
            rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
1459
                              RadioSettingValueString(
1460
                                  0, 7, _filter(_msg.line1)))
1461
            other.append(rs)
1462
            rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
1463
                              RadioSettingValueString(
1464
                                  0, 7, _filter(_msg.line2)))
1465
            other.append(rs)
1466

    
1467
            rs = RadioSetting("ponmsg", "Power-On Message",
1468
                              RadioSettingValueList(
1469
                                  PONMSG_LIST,
1470
                                  PONMSG_LIST[_settings.ponmsg]))
1471
            other.append(rs)
1472

    
1473
            if self._is_orig():
1474
                limit = "limits_old"
1475
            else:
1476
                limit = "limits_new"
1477

    
1478
            vhf_limit = getattr(self._memobj, limit).vhf
1479
            rs = RadioSetting("%s.vhf.lower" % limit,
1480
                              "VHF Lower Limit (MHz)",
1481
                              RadioSettingValueInteger(1, 1000,
1482
                                                       vhf_limit.lower))
1483
            other.append(rs)
1484

    
1485
            rs = RadioSetting("%s.vhf.upper" % limit,
1486
                              "VHF Upper Limit (MHz)",
1487
                              RadioSettingValueInteger(1, 1000,
1488
                                                       vhf_limit.upper))
1489
            other.append(rs)
1490

    
1491
            rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled",
1492
                              RadioSettingValueBoolean(vhf_limit.enable))
1493
            other.append(rs)
1494

    
1495
            uhf_limit = getattr(self._memobj, limit).uhf
1496
            rs = RadioSetting("%s.uhf.lower" % limit,
1497
                              "UHF Lower Limit (MHz)",
1498
                              RadioSettingValueInteger(1, 1000,
1499
                                                       uhf_limit.lower))
1500
            other.append(rs)
1501
            rs = RadioSetting("%s.uhf.upper" % limit,
1502
                              "UHF Upper Limit (MHz)",
1503
                              RadioSettingValueInteger(1, 1000,
1504
                                                       uhf_limit.upper))
1505
            other.append(rs)
1506
            rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled",
1507
                              RadioSettingValueBoolean(uhf_limit.enable))
1508
            other.append(rs)
1509

    
1510
        if self.MODEL != "UV-6":
1511
            workmode = RadioSettingGroup("workmode", "Work Mode Settings")
1512
            group.append(workmode)
1513

    
1514
            rs = RadioSetting("displayab", "Display",
1515
                              RadioSettingValueList(
1516
                                  AB_LIST, AB_LIST[_settings.displayab]))
1517
            workmode.append(rs)
1518

    
1519
            rs = RadioSetting("workmode", "VFO/MR Mode",
1520
                              RadioSettingValueList(
1521
                                  WORKMODE_LIST,
1522
                                  WORKMODE_LIST[_settings.workmode]))
1523
            workmode.append(rs)
1524

    
1525
            rs = RadioSetting("keylock", "Keypad Lock",
1526
                              RadioSettingValueBoolean(_settings.keylock))
1527
            workmode.append(rs)
1528

    
1529
            rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
1530
                              RadioSettingValueInteger(0, 127,
1531
                                                       _wmchannel.mrcha))
1532
            workmode.append(rs)
1533

    
1534
            rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
1535
                              RadioSettingValueInteger(0, 127,
1536
                                                       _wmchannel.mrchb))
1537
            workmode.append(rs)
1538

    
1539
            def convert_bytes_to_freq(bytes):
1540
                real_freq = 0
1541
                for byte in bytes:
1542
                    real_freq = (real_freq * 10) + byte
1543
                return chirp_common.format_freq(real_freq * 10)
1544

    
1545
            def my_validate(value):
1546
                value = chirp_common.parse_freq(value)
1547
                if 17400000 <= value and value < 40000000:
1548
                    msg = ("Can't be between 174.00000-400.00000")
1549
                    raise InvalidValueError(msg)
1550
                return chirp_common.format_freq(value)
1551

    
1552
            def apply_freq(setting, obj):
1553
                value = chirp_common.parse_freq(str(setting.value)) / 10
1554
                obj.band = value >= 40000000
1555
                for i in range(7, -1, -1):
1556
                    obj.freq[i] = value % 10
1557
                    value /= 10
1558

    
1559
            val1a = RadioSettingValueString(0, 10,
1560
                                            convert_bytes_to_freq(_vfoa.freq))
1561
            val1a.set_validate_callback(my_validate)
1562
            rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a)
1563
            rs.set_apply_callback(apply_freq, _vfoa)
1564
            workmode.append(rs)
1565

    
1566
            val1b = RadioSettingValueString(0, 10,
1567
                                            convert_bytes_to_freq(_vfob.freq))
1568
            val1b.set_validate_callback(my_validate)
1569
            rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b)
1570
            rs.set_apply_callback(apply_freq, _vfob)
1571
            workmode.append(rs)
1572

    
1573
            rs = RadioSetting("vfoa.sftd", "VFO A Shift",
1574
                              RadioSettingValueList(
1575
                                  SHIFTD_LIST, SHIFTD_LIST[_vfoa.sftd]))
1576
            workmode.append(rs)
1577

    
1578
            rs = RadioSetting("vfob.sftd", "VFO B Shift",
1579
                              RadioSettingValueList(
1580
                                  SHIFTD_LIST, SHIFTD_LIST[_vfob.sftd]))
1581
            workmode.append(rs)
1582

    
1583
            def convert_bytes_to_offset(bytes):
1584
                real_offset = 0
1585
                for byte in bytes:
1586
                    real_offset = (real_offset * 10) + byte
1587
                return chirp_common.format_freq(real_offset * 1000)
1588

    
1589
            def apply_offset(setting, obj):
1590
                value = chirp_common.parse_freq(str(setting.value)) / 1000
1591
                for i in range(5, -1, -1):
1592
                    obj.offset[i] = value % 10
1593
                    value /= 10
1594

    
1595
            val1a = RadioSettingValueString(
1596
                0, 10, convert_bytes_to_offset(_vfoa.offset))
1597
            rs = RadioSetting("vfoa.offset",
1598
                              "VFO A Offset (0.0-999.999)", val1a)
1599
            rs.set_apply_callback(apply_offset, _vfoa)
1600
            workmode.append(rs)
1601

    
1602
            val1b = RadioSettingValueString(
1603
                0, 10, convert_bytes_to_offset(_vfob.offset))
1604
            rs = RadioSetting("vfob.offset",
1605
                              "VFO B Offset (0.0-999.999)", val1b)
1606
            rs.set_apply_callback(apply_offset, _vfob)
1607
            workmode.append(rs)
1608

    
1609
            if self._tri_power:
1610
                if _vfoa.txpower3 > 0x02:
1611
                    val = 0x00
1612
                else:
1613
                    val = _vfoa.txpower3
1614
                rs = RadioSetting("vfoa.txpower3", "VFO A Power",
1615
                                  RadioSettingValueList(
1616
                                      TXPOWER3_LIST,
1617
                                      TXPOWER3_LIST[val]))
1618
                workmode.append(rs)
1619

    
1620
                if _vfob.txpower3 > 0x02:
1621
                    val = 0x00
1622
                else:
1623
                    val = _vfob.txpower3
1624
                rs = RadioSetting("vfob.txpower3", "VFO B Power",
1625
                                  RadioSettingValueList(
1626
                                      TXPOWER3_LIST,
1627
                                      TXPOWER3_LIST[val]))
1628
                workmode.append(rs)
1629
            else:
1630
                rs = RadioSetting("vfoa.txpower", "VFO A Power",
1631
                                  RadioSettingValueList(
1632
                                      TXPOWER_LIST,
1633
                                      TXPOWER_LIST[_vfoa.txpower]))
1634
                workmode.append(rs)
1635

    
1636
                rs = RadioSetting("vfob.txpower", "VFO B Power",
1637
                                  RadioSettingValueList(
1638
                                      TXPOWER_LIST,
1639
                                      TXPOWER_LIST[_vfob.txpower]))
1640
                workmode.append(rs)
1641

    
1642
            rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth",
1643
                              RadioSettingValueList(
1644
                                  BANDWIDTH_LIST,
1645
                                  BANDWIDTH_LIST[_vfoa.widenarr]))
1646
            workmode.append(rs)
1647

    
1648
            rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
1649
                              RadioSettingValueList(
1650
                                  BANDWIDTH_LIST,
1651
                                  BANDWIDTH_LIST[_vfob.widenarr]))
1652
            workmode.append(rs)
1653

    
1654
            rs = RadioSetting("vfoa.scode", "VFO A PTT-ID",
1655
                              RadioSettingValueList(
1656
                                  PTTIDCODE_LIST, PTTIDCODE_LIST[_vfoa.scode]))
1657
            workmode.append(rs)
1658

    
1659
            rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
1660
                              RadioSettingValueList(
1661
                                  PTTIDCODE_LIST, PTTIDCODE_LIST[_vfob.scode]))
1662
            workmode.append(rs)
1663

    
1664
            if not self._is_orig():
1665
                rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
1666
                                  RadioSettingValueList(
1667
                                      STEP291_LIST, STEP291_LIST[_vfoa.step]))
1668
                workmode.append(rs)
1669
                rs = RadioSetting("vfob.step", "VFO B Tuning Step",
1670
                                  RadioSettingValueList(
1671
                                      STEP291_LIST, STEP291_LIST[_vfob.step]))
1672
                workmode.append(rs)
1673
            else:
1674
                rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
1675
                                  RadioSettingValueList(
1676
                                      STEP_LIST, STEP_LIST[_vfoa.step]))
1677
                workmode.append(rs)
1678
                rs = RadioSetting("vfob.step", "VFO B Tuning Step",
1679
                                  RadioSettingValueList(
1680
                                      STEP_LIST, STEP_LIST[_vfob.step]))
1681
                workmode.append(rs)
1682

    
1683
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1684
        group.append(dtmf)
1685

    
1686
        if str(self._memobj.firmware_msg.line1) == "HN5RV01":
1687
            dtmfchars = "0123456789ABCD*#"
1688
        else:
1689
            dtmfchars = "0123456789 *#ABCD"
1690

    
1691
        for i in range(0, 15):
1692
            _codeobj = self._memobj.pttid[i].code
1693
            _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1694
            val = RadioSettingValueString(0, 5, _code, False)
1695
            val.set_charset(dtmfchars)
1696
            rs = RadioSetting("pttid/%i.code" % i,
1697
                              "PTT ID Code %i" % (i + 1), val)
1698

    
1699
            def apply_code(setting, obj):
1700
                code = []
1701
                for j in range(0, 5):
1702
                    try:
1703
                        code.append(dtmfchars.index(str(setting.value)[j]))
1704
                    except IndexError:
1705
                        code.append(0xFF)
1706
                obj.code = code
1707
            rs.set_apply_callback(apply_code, self._memobj.pttid[i])
1708
            dtmf.append(rs)
1709

    
1710
        dtmfcharsani = "0123456789"
1711

    
1712
        _codeobj = self._memobj.ani.code
1713
        _code = "".join([dtmfcharsani[x] for x in _codeobj if int(x) < 0x1F])
1714
        val = RadioSettingValueString(0, 5, _code, False)
1715
        val.set_charset(dtmfcharsani)
1716
        rs = RadioSetting("ani.code", "ANI Code", val)
1717

    
1718
        def apply_code(setting, obj):
1719
            code = []
1720
            for j in range(0, 5):
1721
                try:
1722
                    code.append(dtmfchars.index(str(setting.value)[j]))
1723
                except IndexError:
1724
                    code.append(0xFF)
1725
            obj.code = code
1726
        rs.set_apply_callback(apply_code, _ani)
1727
        dtmf.append(rs)
1728

    
1729
        rs = RadioSetting("ani.aniid", "ANI ID",
1730
                          RadioSettingValueList(PTTID_LIST,
1731
                                                PTTID_LIST[_ani.aniid]))
1732
        dtmf.append(rs)
1733

    
1734
        _codeobj = self._memobj.ani.alarmcode
1735
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1736
        val = RadioSettingValueString(0, 3, _code, False)
1737
        val.set_charset(dtmfchars)
1738
        rs = RadioSetting("ani.alarmcode", "Alarm Code", val)
1739

    
1740
        def apply_code(setting, obj):
1741
            alarmcode = []
1742
            for j in range(0, 3):
1743
                try:
1744
                    alarmcode.append(dtmfchars.index(str(setting.value)[j]))
1745
                except IndexError:
1746
                    alarmcode.append(0xFF)
1747
            obj.alarmcode = alarmcode
1748
        rs.set_apply_callback(apply_code, _ani)
1749
        dtmf.append(rs)
1750

    
1751
        rs = RadioSetting("dtmfst", "DTMF Sidetone",
1752
                          RadioSettingValueList(DTMFST_LIST,
1753
                                                DTMFST_LIST[_settings.dtmfst]))
1754
        dtmf.append(rs)
1755

    
1756
        if _ani.dtmfon > 0xC3:
1757
            val = 0x00
1758
        else:
1759
            val = _ani.dtmfon
1760
        rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
1761
                          RadioSettingValueList(DTMFSPEED_LIST,
1762
                                                DTMFSPEED_LIST[val]))
1763
        dtmf.append(rs)
1764

    
1765
        if _ani.dtmfoff > 0xC3:
1766
            val = 0x00
1767
        else:
1768
            val = _ani.dtmfoff
1769
        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
1770
                          RadioSettingValueList(DTMFSPEED_LIST,
1771
                                                DTMFSPEED_LIST[val]))
1772
        dtmf.append(rs)
1773

    
1774
        rs = RadioSetting("pttlt", "PTT ID Delay",
1775
                          RadioSettingValueInteger(0, 50, _settings.pttlt))
1776
        dtmf.append(rs)
1777

    
1778
        if not self._is_orig() and self._aux_block:
1779
            service = RadioSettingGroup("service", "Service Settings")
1780
            group.append(service)
1781

    
1782
            for band in ["vhf", "uhf"]:
1783
                for index in range(0, 10):
1784
                    key = "squelch_new.%s.sql%i" % (band, index)
1785
                    if band == "vhf":
1786
                        _obj = self._memobj.squelch_new.vhf
1787
                    elif band == "uhf":
1788
                        _obj = self._memobj.squelch_new.uhf
1789
                    name = "%s Squelch %i" % (band.upper(), index)
1790
                    rs = RadioSetting(key, name,
1791
                                      RadioSettingValueInteger(
1792
                                          0, 123,
1793
                                          getattr(_obj, "sql%i" % (index))))
1794
                    service.append(rs)
1795

    
1796
        return group
1797

    
1798
    def get_settings(self):
1799
        try:
1800
            return self._get_settings()
1801
        except:
1802
            import traceback
1803
            LOG.error("Failed to parse settings: %s", traceback.format_exc())
1804
            return None
1805

    
1806
    def set_settings(self, settings):
1807
        _settings = self._memobj.settings
1808
        for element in settings:
1809
            if not isinstance(element, RadioSetting):
1810
                self.set_settings(element)
1811
                continue
1812
            else:
1813
                try:
1814
                    name = element.get_name()
1815
                    if "." in name:
1816
                        bits = name.split(".")
1817
                        obj = self._memobj
1818
                        for bit in bits[:-1]:
1819
                            if "/" in bit:
1820
                                bit, index = bit.split("/", 1)
1821
                                index = int(index)
1822
                                obj = getattr(obj, bit)[index]
1823
                            else:
1824
                                obj = getattr(obj, bit)
1825
                        setting = bits[-1]
1826
                    else:
1827
                        obj = _settings
1828
                        setting = element.get_name()
1829

    
1830
                    if element.has_apply_callback():
1831
                        LOG.debug("Using apply callback")
1832
                        element.run_apply_callback()
1833
                    elif element.value.get_mutable():
1834
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1835
                        setattr(obj, setting, element.value)
1836
                except Exception:
1837
                    LOG.debug(element.get_name())
1838
                    raise
1839

    
1840

    
1841
class UV5XAlias(chirp_common.Alias):
1842
    VENDOR = "Baofeng"
1843
    MODEL = "UV-5X"
1844

    
1845

    
1846
class RT5RAlias(chirp_common.Alias):
1847
    VENDOR = "Retevis"
1848
    MODEL = "RT5R"
1849

    
1850

    
1851
class RT5RVAlias(chirp_common.Alias):
1852
    VENDOR = "Retevis"
1853
    MODEL = "RT5RV"
1854

    
1855

    
1856
class RT5Alias(chirp_common.Alias):
1857
    VENDOR = "Retevis"
1858
    MODEL = "RT5"
1859

    
1860

    
1861
class RT5_TPAlias(chirp_common.Alias):
1862
    VENDOR = "Retevis"
1863
    MODEL = "RT5(tri-power)"
1864

    
1865

    
1866
class RH5RAlias(chirp_common.Alias):
1867
    VENDOR = "Rugged"
1868
    MODEL = "RH5R"
1869

    
1870

    
1871
class ROUV5REXAlias(chirp_common.Alias):
1872
    VENDOR = "Radioddity"
1873
    MODEL = "UV-5R EX"
1874

    
1875

    
1876
class A5RAlias(chirp_common.Alias):
1877
    VENDOR = "Ansoko"
1878
    MODEL = "A-5R"
1879

    
1880

    
1881
@directory.register
1882
class BaofengUV5RGeneric(BaofengUV5R):
1883
    ALIASES = [UV5XAlias, RT5RAlias, RT5RVAlias, RT5Alias, RH5RAlias,
1884
               ROUV5REXAlias, A5RAlias]
1885

    
1886

    
1887
@directory.register
1888
class BaofengF11Radio(BaofengUV5R):
1889
    VENDOR = "Baofeng"
1890
    MODEL = "F-11"
1891
    _basetype = BASETYPE_F11
1892
    _idents = [UV5R_MODEL_F11]
1893

    
1894
    def _is_orig(self):
1895
        # Override this for F11 to always return False
1896
        return False
1897

    
1898

    
1899
@directory.register
1900
class BaofengUV82Radio(BaofengUV5R):
1901
    MODEL = "UV-82"
1902
    _basetype = BASETYPE_UV82
1903
    _idents = [UV5R_MODEL_UV82]
1904
    _vhf_range = (130000000, 176000000)
1905
    _uhf_range = (400000000, 521000000)
1906
    _valid_chars = chirp_common.CHARSET_ASCII
1907

    
1908
    def _is_orig(self):
1909
        # Override this for UV82 to always return False
1910
        return False
1911

    
1912

    
1913
@directory.register
1914
class Radioddity82X3Radio(BaofengUV82Radio):
1915
    VENDOR = "Radioddity"
1916
    MODEL = "UV-82X3"
1917
    _basetype = BASETYPE_UV82X3
1918

    
1919
    def get_features(self):
1920
        rf = BaofengUV5R.get_features(self)
1921
        rf.valid_bands = [self._vhf_range,
1922
                          (200000000, 260000000),
1923
                          self._uhf_range]
1924
        return rf
1925

    
1926

    
1927
@directory.register
1928
class BaofengUV6Radio(BaofengUV5R):
1929

    
1930
    """Baofeng UV-6/UV-7"""
1931
    VENDOR = "Baofeng"
1932
    MODEL = "UV-6"
1933
    _basetype = BASETYPE_UV6
1934
    _idents = [UV5R_MODEL_UV6,
1935
               UV5R_MODEL_UV6_ORIG
1936
               ]
1937
    _aux_block = False
1938

    
1939
    def get_features(self):
1940
        rf = BaofengUV5R.get_features(self)
1941
        rf.memory_bounds = (1, 128)
1942
        return rf
1943

    
1944
    def _get_mem(self, number):
1945
        return self._memobj.memory[number - 1]
1946

    
1947
    def _get_nam(self, number):
1948
        return self._memobj.names[number - 1]
1949

    
1950
    def _set_mem(self, number):
1951
        return self._memobj.memory[number - 1]
1952

    
1953
    def _set_nam(self, number):
1954
        return self._memobj.names[number - 1]
1955

    
1956
    def _is_orig(self):
1957
        # Override this for UV6 to always return False
1958
        return False
1959

    
1960

    
1961
@directory.register
1962
class IntekKT980Radio(BaofengUV5R):
1963
    VENDOR = "Intek"
1964
    MODEL = "KT-980HP"
1965
    _basetype = BASETYPE_KT980HP
1966
    _idents = [UV5R_MODEL_291]
1967
    _vhf_range = (130000000, 180000000)
1968
    _uhf_range = (400000000, 521000000)
1969
    _tri_power = True
1970

    
1971
    def get_features(self):
1972
        rf = BaofengUV5R.get_features(self)
1973
        rf.valid_power_levels = UV5R_POWER_LEVELS3
1974
        return rf
1975

    
1976
    def _is_orig(self):
1977
        # Override this for KT980HP to always return False
1978
        return False
1979

    
1980

    
1981
class ROGA5SAlias(chirp_common.Alias):
1982
    VENDOR = "Radioddity"
1983
    MODEL = "GA-5S"
1984

    
1985

    
1986
class UV5XPAlias(chirp_common.Alias):
1987
    VENDOR = "Baofeng"
1988
    MODEL = "UV-5XP"
1989

    
1990

    
1991
class TSTIF8Alias(chirp_common.Alias):
1992
    VENDOR = "TechSide"
1993
    MODEL = "TI-F8+"
1994

    
1995

    
1996
class TenwayUV5RPro(chirp_common.Alias):
1997
    VENDOR = 'Tenway'
1998
    MODEL = 'UV-5R Pro'
1999

    
2000

    
2001
class TSTST9Alias(chirp_common.Alias):
2002
    VENDOR = "TechSide"
2003
    MODEL = "TS-T9+"
2004

    
2005

    
2006
class TDUV5RRadio(chirp_common.Alias):
2007
    VENDOR = "TIDRADIO"
2008
    MODEL = "TD-UV5R TriPower"
2009

    
2010

    
2011
@directory.register
2012
class BaofengBFF8HPRadio(BaofengUV5R):
2013
    VENDOR = "Baofeng"
2014
    MODEL = "BF-F8HP"
2015
    ALIASES = [RT5_TPAlias, ROGA5SAlias, UV5XPAlias, TSTIF8Alias,
2016
               TenwayUV5RPro, TSTST9Alias, TDUV5RRadio]
2017
    _basetype = BASETYPE_F8HP
2018
    _idents = [UV5R_MODEL_291,
2019
               UV5R_MODEL_A58
2020
               ]
2021
    _vhf_range = (130000000, 180000000)
2022
    _uhf_range = (400000000, 521000000)
2023
    _tri_power = True
2024

    
2025
    def get_features(self):
2026
        rf = BaofengUV5R.get_features(self)
2027
        rf.valid_power_levels = UV5R_POWER_LEVELS3
2028
        return rf
2029

    
2030
    def _is_orig(self):
2031
        # Override this for BFF8HP to always return False
2032
        return False
2033

    
2034

    
2035
class TenwayUV82Pro(chirp_common.Alias):
2036
    VENDOR = 'Tenway'
2037
    MODEL = 'UV-82 Pro'
2038

    
2039

    
2040
@directory.register
2041
class BaofengUV82HPRadio(BaofengUV5R):
2042
    VENDOR = "Baofeng"
2043
    MODEL = "UV-82HP"
2044
    ALIASES = [TenwayUV82Pro]
2045
    _basetype = BASETYPE_UV82HP
2046
    _idents = [UV5R_MODEL_UV82]
2047
    _vhf_range = (136000000, 175000000)
2048
    _uhf_range = (400000000, 521000000)
2049
    _valid_chars = chirp_common.CHARSET_ALPHANUMERIC + \
2050
        "!@#$%^&*()+-=[]:\";'<>?,./"
2051
    _tri_power = True
2052

    
2053
    def get_features(self):
2054
        rf = BaofengUV5R.get_features(self)
2055
        rf.valid_power_levels = UV5R_POWER_LEVELS3
2056
        return rf
2057

    
2058
    def _is_orig(self):
2059
        # Override this for UV82HP to always return False
2060
        return False
2061

    
2062

    
2063
@directory.register
2064
class RadioddityUV5RX3Radio(BaofengUV5R):
2065
    VENDOR = "Radioddity"
2066
    MODEL = "UV-5RX3"
2067

    
2068
    def get_features(self):
2069
        rf = BaofengUV5R.get_features(self)
2070
        rf.valid_bands = [self._vhf_range,
2071
                          (200000000, 260000000),
2072
                          self._uhf_range]
2073
        return rf
2074

    
2075
    @classmethod
2076
    def match_model(cls, filename, filedata):
2077
        return False
2078

    
2079

    
2080
@directory.register
2081
class RadioddityGT5RRadio(BaofengUV5R):
2082
    VENDOR = 'Baofeng'
2083
    MODEL = 'GT-5R'
2084

    
2085
    vhftx = [144000000, 148000000]
2086
    uhftx = [420000000, 450000000]
2087

    
2088
    def validate_memory(self, mem):
2089
        msgs = super().validate_memory(mem)
2090

    
2091
        _msg_duplex = 'Duplex must be "off" for this frequency'
2092
        _msg_offset = 'Only simplex or +5 MHz offset allowed on GMRS'
2093

    
2094
        if not ((mem.freq >= self.vhftx[0] and mem.freq < self.vhftx[1]) or
2095
                (mem.freq >= self.uhftx[0] and mem.freq < self.uhftx[1])):
2096
            if mem.duplex != "off":
2097
                msgs.append(chirp_common.ValidationWarning(_msg_duplex))
2098

    
2099
        return msgs
2100

    
2101
    def check_set_memory_immutable_policy(self, existing, new):
2102
        existing.immutable = []
2103
        super().check_set_memory_immutable_policy(existing, new)
2104

    
2105
    @classmethod
2106
    def match_model(cls, filename, filedata):
2107
        return False
2108

    
2109

    
2110
@directory.register
2111
class RadioddityUV5GRadio(BaofengUV5R):
2112
    VENDOR = 'Radioddity'
2113
    MODEL = 'UV-5G'
2114

    
2115
    _basetype = BASETYPE_UV5R
2116
    _idents = [UV5R_MODEL_UV5G]
2117

    
2118
    @classmethod
2119
    def match_model(cls, filename, filedata):
2120
        return False
(41-41/41)