Project

General

Profile

Feature #10505 » uv5r_bfb298_bug_workaround.py

Jim Unroe, 06/17/2023 07:48 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", "2100Hz"]
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
SETTING_LISTS = {
326
    "almod": ALMOD_LIST,
327
    "aniid": PTTID_LIST,
328
    "displayab": AB_LIST,
329
    "dtmfst": DTMFST_LIST,
330
    "dtmfspeed": DTMFSPEED_LIST,
331
    "mdfa": MODE_LIST,
332
    "mdfb": MODE_LIST,
333
    "ponmsg": PONMSG_LIST,
334
    "pttid": PTTID_LIST,
335
    "rtone": RTONE_LIST,
336
    "rogerrx": ROGERRX_LIST,
337
    "rpste": RPSTE_LIST,
338
    "rxled": COLOR_LIST,
339
    "save": SAVE_LIST,
340
    "scode": PTTIDCODE_LIST,
341
    "screv": RESUME_LIST,
342
    "sftd": SHIFTD_LIST,
343
    "stedelay": STEDELAY_LIST,
344
    "step": STEP_LIST,
345
    "step291": STEP291_LIST,
346
    "tdrab": TDRAB_LIST,
347
    "tdrch": TDRCH_LIST,
348
    "timeout": TIMEOUT_LIST,
349
    "txled": COLOR_LIST,
350
    "txpower": TXPOWER_LIST,
351
    "txpower3": TXPOWER3_LIST,
352
    "voice": VOICE_LIST,
353
    "vox": VOX_LIST,
354
    "widenarr": BANDWIDTH_LIST,
355
    "workmode": WORKMODE_LIST,
356
    "wtled": COLOR_LIST
357
}
358

    
359
GMRS_FREQS1 = [462562500, 462587500, 462612500, 462637500, 462662500,
360
               462687500, 462712500]
361
GMRS_FREQS2 = [467562500, 467587500, 467612500, 467637500, 467662500,
362
               467687500, 467712500]
363
GMRS_FREQS3 = [462550000, 462575000, 462600000, 462625000, 462650000,
364
               462675000, 462700000, 462725000]
365
GMRS_FREQS = GMRS_FREQS1 + GMRS_FREQS2 + GMRS_FREQS3 * 2
366

    
367

    
368
def _do_status(radio, block):
369
    status = chirp_common.Status()
370
    status.msg = "Cloning"
371
    status.cur = block
372
    status.max = radio.get_memsize()
373
    radio.status_fn(status)
374

    
375

    
376
UV5R_MODEL_ORIG = b"\x50\xBB\xFF\x01\x25\x98\x4D"
377
UV5R_MODEL_291 = b"\x50\xBB\xFF\x20\x12\x07\x25"
378
UV5R_MODEL_F11 = b"\x50\xBB\xFF\x13\xA1\x11\xDD"
379
UV5R_MODEL_UV82 = b"\x50\xBB\xFF\x20\x13\x01\x05"
380
UV5R_MODEL_UV6 = b"\x50\xBB\xFF\x20\x12\x08\x23"
381
UV5R_MODEL_UV6_ORIG = b"\x50\xBB\xFF\x12\x03\x98\x4D"
382
UV5R_MODEL_A58 = b"\x50\xBB\xFF\x20\x14\x04\x13"
383
UV5R_MODEL_UV5G = b"\x50\xBB\xFF\x20\x12\x06\x25"
384

    
385

    
386
def _upper_band_from_data(data):
387
    return data[0x03:0x04]
388

    
389

    
390
def _upper_band_from_image(radio):
391
    return _upper_band_from_data(radio.get_mmap())
392

    
393

    
394
def _firmware_version_from_data(data, version_start, version_stop):
395
    version_tag = data[version_start:version_stop]
396
    return version_tag
397

    
398

    
399
def _firmware_version_from_image(radio):
400
    version = _firmware_version_from_data(
401
        radio.get_mmap().get_byte_compatible(),
402
        radio._fw_ver_file_start,
403
        radio._fw_ver_file_stop)
404
    return version
405

    
406

    
407
def _do_ident(radio, magic, secondack=True):
408
    serial = radio.pipe
409
    serial.timeout = 1
410

    
411
    LOG.info("Sending Magic: %s" % util.hexprint(magic))
412
    for byte in magic:
413
        serial.write(bytes([byte]))
414
        time.sleep(0.01)
415
    ack = serial.read(1)
416

    
417
    if ack != b"\x06":
418
        if ack:
419
            LOG.debug(repr(ack))
420
        raise errors.RadioError("Radio did not respond")
421

    
422
    serial.write(b"\x02")
423

    
424
    # Until recently, the "ident" returned by the radios supported by this
425
    # driver have always been 8 bytes long. The image structure is the 8 byte
426
    # "ident" followed by the downloaded memory data. So all of the settings
427
    # structures are offset by 8 bytes. The ident returned from a UV-6 radio
428
    # can be 8 bytes (original model) or now 12 bytes.
429
    #
430
    # To accommodate this, the "ident" is now read one byte at a time until the
431
    # last byte ("\xdd") is encountered. The bytes containing the value "\x01"
432
    # are discarded to shrink the "ident" length down to 8 bytes to keep the
433
    # image data aligned with the existing settings structures.
434

    
435
    # Ok, get the response
436
    response = b""
437
    for i in range(1, 13):
438
        byte = serial.read(1)
439
        response += byte
440
        # stop reading once the last byte ("\xdd") is encountered
441
        if byte == b"\xDD":
442
            break
443

    
444
    # check if response is OK
445
    if len(response) in [8, 12]:
446
        # DEBUG
447
        LOG.info("Valid response, got this:")
448
        LOG.debug(util.hexprint(response))
449
        if len(response) == 12:
450
            ident = (bytes([response[0], response[3], response[5]]) +
451
                     response[7:])
452
        else:
453
            ident = response
454
    else:
455
        # bad response
456
        msg = "Unexpected response, got this:"
457
        msg += util.hexprint(response)
458
        LOG.debug(msg)
459
        raise errors.RadioError("Unexpected response from radio.")
460

    
461
    if secondack:
462
        serial.write(b"\x06")
463
        ack = serial.read(1)
464
        if ack != b"\x06":
465
            raise errors.RadioError("Radio refused clone")
466

    
467
    return ident
468

    
469

    
470
def _read_block(radio, start, size, first_command=False):
471
    msg = struct.pack(">BHB", ord("S"), start, size)
472
    radio.pipe.write(msg)
473

    
474
    if first_command is False:
475
        ack = radio.pipe.read(1)
476
        if ack != b"\x06":
477
            raise errors.RadioError(
478
                "Radio refused to send second block 0x%04x" % start)
479

    
480
    answer = radio.pipe.read(4)
481
    if len(answer) != 4:
482
        raise errors.RadioError("Radio refused to send block 0x%04x" % start)
483

    
484
    cmd, addr, length = struct.unpack(">BHB", answer)
485
    if cmd != ord("X") or addr != start or length != size:
486
        LOG.error("Invalid answer for block 0x%04x:" % start)
487
        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (cmd, addr, length))
488
        raise errors.RadioError("Unknown response from radio")
489

    
490
    chunk = radio.pipe.read(size)
491
    if not chunk:
492
        raise errors.RadioError("Radio did not send block 0x%04x" % start)
493
    elif len(chunk) != size:
494
        LOG.error("Chunk length was 0x%04i" % len(chunk))
495
        raise errors.RadioError("Radio sent incomplete block 0x%04x" % start)
496

    
497
    radio.pipe.write(b"\x06")
498
    time.sleep(0.05)
499

    
500
    return chunk
501

    
502

    
503
def _get_radio_firmware_version(radio):
504
    if radio.MODEL == "BJ-UV55":
505
        block = _read_block(radio, 0x1FF0, 0x40, True)
506
        version = block[0:6]
507
    else:
508
        block0 = _read_block(radio, 0x1EC0, 0x40, True)   # dir read
509
        block1 = _read_block(radio, 0x1E80, 0x40, False)  # seq read 1 of 2
510
        block2 = _read_block(radio, 0x1EC0, 0x40, False)  # seq read 2 of 2
511
        version_d = block0[48:62]  # directly read block
512
        version = block2[48:62]    # sequentially read block
513
    return version, version_d
514

    
515

    
516
IDENT_BLACKLIST = {
517
    b"\x50\x0D\x0C\x20\x16\x03\x28": "Radio identifies as BTECH UV-5X3",
518
    b"\x50\xBB\xFF\x20\x12\x06\x25": "Radio identifies as Radioddity UV-5G",
519
}
520

    
521

    
522
def _ident_radio(radio):
523
    for magic in radio._idents:
524
        error = None
525
        try:
526
            data = _do_ident(radio, magic)
527
            return data
528
        except errors.RadioError as e:
529
            LOG.error("uv5r._ident_radio: %s", e)
530
            error = e
531
            time.sleep(2)
532

    
533
    for magic, reason in list(IDENT_BLACKLIST.items()):
534
        try:
535
            _do_ident(radio, magic, secondack=False)
536
        except errors.RadioError as e:
537
            # No match, try the next one
538
            continue
539

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

    
548
    if error:
549
        raise error
550
    raise errors.RadioError("Radio did not respond")
551

    
552

    
553
def _do_download(radio):
554
    data = _ident_radio(radio)
555

    
556
    radio_version, radio_version_d = _get_radio_firmware_version(radio)
557
    LOG.info("Radio Version S is %s" % repr(radio_version))
558
    LOG.info("Radio Version D is %s" % repr(radio_version_d))
559

    
560
    if b"HN5RV" in radio_version:
561
        # A radio with HN5RV firmware has been detected. It could be a
562
        # UV-5R style radio with HIGH/LOW power levels or it could be a
563
        # BF-F8HP style radio with HIGH/MID/LOW power levels.
564
        # We are going to count on the user to make the right choice and
565
        # then append that model type to the end of the image so it can
566
        # be properly detected when loaded.
567
        append_model = True
568
    elif b"\xFF" * 7 in radio_version:
569
        # A radio UV-5R style radio that reports no firmware version has
570
        # been detected.
571
        # We are going to count on the user to make the right choice and
572
        # then append that model type to the end of the image so it can
573
        # be properly detected when loaded.
574
        append_model = True
575
    elif b"\x20" * 14 in radio_version:
576
        # A radio UV-5R style radio that reports no firmware version has
577
        # been detected.
578
        # We are going to count on the user to make the right choice and
579
        # then append that model type to the end of the image so it can
580
        # be properly detected when loaded.
581
        append_model = True
582
    elif not any(type in radio_version for type in radio._basetype):
583
        # This radio can't be properly detected by parsing its firmware
584
        # version.
585
        raise errors.RadioError("Incorrect 'Model' selected.")
586
    else:
587
        # This radio can be properly detected by parsing its firmware version.
588
        # There is no need to append its model type to the end of the image.
589
        append_model = False
590

    
591
    # Main block
592
    LOG.debug("downloading main block...")
593
    for i in range(0, 0x1800, 0x40):
594
        data += _read_block(radio, i, 0x40, False)
595
        _do_status(radio, i)
596
    _do_status(radio, radio.get_memsize())
597
    LOG.debug("done.")
598
    if radio._aux_block:
599
        LOG.debug("downloading aux block...")
600
        # Auxiliary block starts at 0x1ECO (?)
601
        for i in range(0x1EC0, 0x2000, 0x40):
602
            data += _read_block(radio, i, 0x40, False)
603

    
604
    if append_model:
605
        data += radio.MODEL.ljust(8).encode()
606

    
607
    LOG.debug("done.")
608
    return memmap.MemoryMapBytes(data)
609

    
610

    
611
def _send_block(radio, addr, data):
612
    msg = struct.pack(">BHB", ord("X"), addr, len(data))
613
    radio.pipe.write(msg + data)
614
    time.sleep(0.05)
615

    
616
    ack = radio.pipe.read(1)
617
    if ack != b"\x06":
618
        raise errors.RadioError("Radio refused to accept block 0x%04x" % addr)
619

    
620

    
621
def _do_upload(radio):
622
    ident = _ident_radio(radio)
623
    radio_upper_band = ident[3:4]
624
    image_upper_band = _upper_band_from_image(radio)
625

    
626
    if image_upper_band == vhf_220_radio or radio_upper_band == vhf_220_radio:
627
        if image_upper_band != radio_upper_band:
628
            raise errors.RadioError("Image not supported by radio")
629

    
630
    image_version = _firmware_version_from_image(radio)
631
    radio_version, radio_version_d = _get_radio_firmware_version(radio)
632
    LOG.info("Image Version is %s" % repr(image_version))
633
    LOG.info("Radio Version S is %s" % repr(radio_version))
634
    LOG.info("Radio Version D is %s" % repr(radio_version_d))
635

    
636
    # default ranges
637
    _ranges_main_default = [
638
        (0x0008, 0x0CF8),
639
        (0x0D08, 0x0DF8),
640
        (0x0E08, 0x1808)
641
        ]
642
    _ranges_aux_default = [
643
        (0x1EC0, 0x1EF0),
644
        ]
645

    
646
    # extra aux ranges
647
    _ranges_aux_extra = [
648
        (0x1F60, 0x1F70),
649
        (0x1F80, 0x1F90),
650
        (0x1FC0, 0x1FE0)
651
        ]
652

    
653
    if radio_version != radio_version_d:
654
        # not safe to upload 'aux block', set flag to skip it
655
        _skip_aux_block_flag = True
656
    else:
657
        # safe to upload 'aux block'
658
        _skip_aux_block_flag = False
659

    
660
    if radio._all_range_flag:
661
        image_matched_radio = True
662
        ranges_main = radio._ranges_main
663
        ranges_aux = radio._ranges_aux
664
        LOG.warning('Sending all ranges to radio as instructed')
665
    elif image_version == radio_version:
666
        image_matched_radio = True
667
        ranges_main = _ranges_main_default
668
        ranges_aux = _ranges_aux_default + _ranges_aux_extra
669
    elif any(type in radio_version for type in radio._basetype):
670
        image_matched_radio = False
671
        ranges_main = _ranges_main_default
672
        ranges_aux = _ranges_aux_default
673
    else:
674
        msg = ("The upload was stopped because the firmware "
675
               "version of the image (%s) does not match that "
676
               "of the radio (%s).")
677
        raise errors.RadioError(msg % (image_version, radio_version))
678

    
679
    if not radio._aux_block:
680
        image_matched_radio = True
681

    
682
    # Main block
683
    mmap = radio.get_mmap().get_byte_compatible()
684
    for start_addr, end_addr in ranges_main:
685
        for i in range(start_addr, end_addr, 0x10):
686
            _send_block(radio, i - 0x08, mmap[i:i + 0x10])
687
            _do_status(radio, i)
688
        _do_status(radio, radio.get_memsize())
689

    
690
    if len(mmap.get_packed()) == 0x1808:
691
        LOG.info("Old image, not writing aux block")
692
        return  # Old image, no aux block
693

    
694
    if _skip_aux_block_flag:
695
        LOG.info("Skiped writing aux block")
696
        msg = ("This is NOT an error.\n"
697
               "Upload finished. The 'Aux block' (which contains "
698
               "the 'Other Settings' and 'Service Settings') was "
699
               "skipped because testing has determined that uploading "
700
               "it to this radio is not safe.")
701
        raise errors.RadioError(msg)
702
        return  # Aux block skipped
703

    
704
    # Auxiliary block at radio address 0x1EC0, our offset 0x1808
705
    for start_addr, end_addr in ranges_aux:
706
        for i in range(start_addr, end_addr, 0x10):
707
            addr = 0x1808 + (i - 0x1EC0)
708
            _send_block(radio, i, mmap[addr:addr + 0x10])
709

    
710
    if not image_matched_radio:
711
        msg = ("Upload finished, but the 'Other Settings' "
712
               "could not be sent because the firmware "
713
               "version of the image (%s) does not match "
714
               "that of the radio (%s).")
715
        raise errors.RadioError(msg % (image_version, radio_version))
716

    
717
    if radio._all_range_flag:
718
        radio._all_range_flag = False
719
        LOG.warning('Sending all ranges to radio has completed')
720
        raise errors.RadioError(
721
            "This is NOT an error.\n"
722
            "The upload has finished successfully.\n"
723
            "Please restart CHIRP.")
724

    
725

    
726
UV5R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=4.00),
727
                     chirp_common.PowerLevel("Low",  watts=1.00)]
728

    
729
UV5R_POWER_LEVELS3 = [chirp_common.PowerLevel("High", watts=8.00),
730
                      chirp_common.PowerLevel("Med",  watts=4.00),
731
                      chirp_common.PowerLevel("Low",  watts=1.00)]
732

    
733
UV5R_DTCS = tuple(sorted(chirp_common.DTCS_CODES + (645,)))
734

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

    
738

    
739
def model_match(cls, data):
740
    """Match the opened/downloaded image to the correct version"""
741

    
742
    if len(data) == 0x1950:
743
        rid = data[0x1948:0x1950]
744
        return rid.startswith(cls.MODEL)
745
    elif len(data) == 0x1948:
746
        rid = data[cls._fw_ver_file_start:cls._fw_ver_file_stop]
747
        if any(type in rid for type in cls._basetype):
748
            return True
749
    else:
750
        return False
751

    
752

    
753
class BaofengUV5R(chirp_common.CloneModeRadio):
754

    
755
    """Baofeng UV-5R"""
756
    VENDOR = "Baofeng"
757
    MODEL = "UV-5R"
758
    BAUD_RATE = 9600
759
    NEEDS_COMPAT_SERIAL = False
760

    
761
    _memsize = 0x1808
762
    _basetype = BASETYPE_UV5R
763
    _idents = [UV5R_MODEL_291,
764
               UV5R_MODEL_ORIG
765
               ]
766
    _vhf_range = (130000000, 176000000)
767
    _220_range = (220000000, 260000000)
768
    _uhf_range = (400000000, 520000000)
769
    _aux_block = True
770
    _tri_power = False
771
    _bw_shift = False
772
    _mem_params = (0x1828  # poweron_msg offset
773
                   )
774
    # offset of fw version in image file
775
    _fw_ver_file_start = 0x1838
776
    _fw_ver_file_stop = 0x1846
777

    
778
    _ranges_main = [
779
                    (0x0008, 0x1808),
780
                   ]
781
    _ranges_aux = [
782
                   (0x1EC0, 0x2000),
783
                  ]
784
    _valid_chars = UV5R_CHARSET
785

    
786
    @classmethod
787
    def get_prompts(cls):
788
        rp = chirp_common.RadioPrompts()
789
        rp.experimental = \
790
            ('Due to the fact that the manufacturer continues to '
791
             'release new versions of the firmware with obscure and '
792
             'hard-to-track changes, this driver may not work with '
793
             'your device. Thus far and to the best knowledge of the '
794
             'author, no UV-5R radios have been harmed by using CHIRP. '
795
             'However, proceed at your own risk!')
796
        rp.pre_download = _(
797
            "1. Turn radio off.\n"
798
            "2. Connect cable to mic/spkr connector.\n"
799
            "3. Make sure connector is firmly connected.\n"
800
            "4. Turn radio on (volume may need to be set at 100%).\n"
801
            "5. Ensure that the radio is tuned to channel with no"
802
            " activity.\n"
803
            "6. Click OK to download image from device.\n")
804
        rp.pre_upload = _(
805
            "1. Turn radio off.\n"
806
            "2. Connect cable to mic/spkr connector.\n"
807
            "3. Make sure connector is firmly connected.\n"
808
            "4. Turn radio on (volume may need to be set at 100%).\n"
809
            "5. Ensure that the radio is tuned to channel with no"
810
            " activity.\n"
811
            "6. Click OK to upload image to device.\n")
812
        return rp
813

    
814
    def get_features(self):
815
        rf = chirp_common.RadioFeatures()
816
        rf.has_settings = True
817
        rf.has_bank = False
818
        rf.has_cross = True
819
        rf.has_rx_dtcs = True
820
        rf.has_tuning_step = False
821
        rf.can_odd_split = True
822
        rf.valid_name_length = 7
823
        rf.valid_characters = self._valid_chars
824
        rf.valid_skips = ["", "S"]
825
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
826
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
827
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
828
        rf.valid_power_levels = UV5R_POWER_LEVELS
829
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
830
        rf.valid_modes = ["FM", "NFM"]
831
        rf.valid_tuning_steps = STEPS
832
        rf.valid_dtcs_codes = UV5R_DTCS
833

    
834
        normal_bands = [self._vhf_range, self._uhf_range]
835
        rax_bands = [self._vhf_range, self._220_range]
836

    
837
        if self._mmap is None:
838
            rf.valid_bands = [normal_bands[0], rax_bands[1], normal_bands[1]]
839
        elif not self._is_orig() and self._my_upper_band() == vhf_220_radio:
840
            rf.valid_bands = rax_bands
841
        else:
842
            rf.valid_bands = normal_bands
843
        rf.memory_bounds = (0, 127)
844
        return rf
845

    
846
    @classmethod
847
    def match_model(cls, filedata, filename):
848
        match_size = False
849
        match_model = False
850
        if len(filedata) in [0x1808, 0x1948, 0x1950]:
851
            match_size = True
852
        match_model = model_match(cls, filedata)
853

    
854
        if match_size and match_model:
855
            return True
856
        else:
857
            return False
858

    
859
    def process_mmap(self):
860
        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
861
        self._all_range_flag = False
862

    
863
    def sync_in(self):
864
        try:
865
            self._mmap = _do_download(self)
866
        except errors.RadioError:
867
            raise
868
        except Exception as e:
869
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
870
        self.process_mmap()
871

    
872
    def sync_out(self):
873
        try:
874
            _do_upload(self)
875
        except errors.RadioError:
876
            raise
877
        except Exception as e:
878
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
879

    
880
    def get_raw_memory(self, number):
881
        return repr(self._memobj.memory[number])
882

    
883
    def _is_txinh(self, _mem):
884
        raw_tx = ""
885
        for i in range(0, 4):
886
            raw_tx += _mem.txfreq[i].get_raw()
887
        return raw_tx == "\xFF\xFF\xFF\xFF"
888

    
889
    def _get_mem(self, number):
890
        return self._memobj.memory[number]
891

    
892
    def _get_nam(self, number):
893
        return self._memobj.names[number]
894

    
895
    def get_memory(self, number):
896
        _mem = self._get_mem(number)
897
        _nam = self._get_nam(number)
898

    
899
        mem = chirp_common.Memory()
900
        mem.number = number
901

    
902
        if _mem.get_raw()[0] == "\xff":
903
            mem.empty = True
904
            return mem
905

    
906
        mem.freq = int(_mem.rxfreq) * 10
907

    
908
        if self._is_txinh(_mem):
909
            mem.duplex = "off"
910
            mem.offset = 0
911
        elif int(_mem.rxfreq) == int(_mem.txfreq):
912
            mem.duplex = ""
913
            mem.offset = 0
914
        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
915
            mem.duplex = "split"
916
            mem.offset = int(_mem.txfreq) * 10
917
        else:
918
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
919
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
920

    
921
        for char in _nam.name:
922
            if str(char) == "\xFF":
923
                char = " "  # The UV-5R software may have 0xFF mid-name
924
            mem.name += str(char)
925
        mem.name = mem.name.rstrip()
926

    
927
        dtcs_pol = ["N", "N"]
928

    
929
        if _mem.txtone in [0, 0xFFFF]:
930
            txmode = ""
931
        elif _mem.txtone >= 0x0258:
932
            txmode = "Tone"
933
            mem.rtone = int(_mem.txtone) / 10.0
934
        elif _mem.txtone <= 0x0258:
935
            txmode = "DTCS"
936
            if _mem.txtone > 0x69:
937
                index = _mem.txtone - 0x6A
938
                dtcs_pol[0] = "R"
939
            else:
940
                index = _mem.txtone - 1
941
            mem.dtcs = UV5R_DTCS[index]
942
        else:
943
            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
944

    
945
        if _mem.rxtone in [0, 0xFFFF]:
946
            rxmode = ""
947
        elif _mem.rxtone >= 0x0258:
948
            rxmode = "Tone"
949
            mem.ctone = int(_mem.rxtone) / 10.0
950
        elif _mem.rxtone <= 0x0258:
951
            rxmode = "DTCS"
952
            if _mem.rxtone >= 0x6A:
953
                index = _mem.rxtone - 0x6A
954
                dtcs_pol[1] = "R"
955
            else:
956
                index = _mem.rxtone - 1
957
            mem.rx_dtcs = UV5R_DTCS[index]
958
        else:
959
            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
960

    
961
        if txmode == "Tone" and not rxmode:
962
            mem.tmode = "Tone"
963
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
964
            mem.tmode = "TSQL"
965
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
966
            mem.tmode = "DTCS"
967
        elif rxmode or txmode:
968
            mem.tmode = "Cross"
969
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
970

    
971
        mem.dtcs_polarity = "".join(dtcs_pol)
972

    
973
        if not _mem.scan:
974
            mem.skip = "S"
975

    
976
        if self._tri_power:
977
            levels = UV5R_POWER_LEVELS3
978
        else:
979
            levels = UV5R_POWER_LEVELS
980
        try:
981
            mem.power = levels[_mem.lowpower]
982
        except IndexError:
983
            LOG.error("Radio reported invalid power level %s (in %s)" %
984
                      (_mem.lowpower, levels))
985
            mem.power = levels[0]
986

    
987
        mem.mode = _mem.wide and "FM" or "NFM"
988

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

    
991
        rs = RadioSetting("bcl", "BCL",
992
                          RadioSettingValueBoolean(_mem.bcl))
993
        mem.extra.append(rs)
994

    
995
        rs = RadioSetting("pttid", "PTT ID",
996
                          RadioSettingValueList(PTTID_LIST,
997
                                                PTTID_LIST[_mem.pttid]))
998
        mem.extra.append(rs)
999

    
1000
        rs = RadioSetting("scode", "PTT ID Code",
1001
                          RadioSettingValueList(PTTIDCODE_LIST,
1002
                                                PTTIDCODE_LIST[_mem.scode]))
1003
        mem.extra.append(rs)
1004

    
1005
        immutable = []
1006

    
1007
        if self.MODEL == "GT-5R":
1008
            if not ((mem.freq >= self.vhftx[0] and mem.freq < self.vhftx[1]) or
1009
                    (mem.freq >= self.uhftx[0] and mem.freq < self.uhftx[1])):
1010
                mem.duplex = 'off'
1011
                mem.offset = 0
1012
                immutable = ["duplex", "offset"]
1013

    
1014
        mem.immutable = immutable
1015

    
1016
        return mem
1017

    
1018
    def _set_mem(self, number):
1019
        return self._memobj.memory[number]
1020

    
1021
    def _set_nam(self, number):
1022
        return self._memobj.names[number]
1023

    
1024
    def set_memory(self, mem):
1025
        _mem = self._get_mem(mem.number)
1026
        _nam = self._get_nam(mem.number)
1027

    
1028
        if mem.empty:
1029
            _mem.set_raw("\xff" * 16)
1030
            _nam.set_raw("\xff" * 16)
1031
            return
1032

    
1033
        was_empty = False
1034
        # same method as used in get_memory to find
1035
        # out whether a raw memory is empty
1036
        if _mem.get_raw()[0] == "\xff":
1037
            was_empty = True
1038
            LOG.debug("UV5R: this mem was empty")
1039
        else:
1040
            # memorize old extra-values before erasing the whole memory
1041
            # used to solve issue 4121
1042
            LOG.debug("mem was not empty, memorize extra-settings")
1043
            prev_bcl = _mem.bcl.get_value()
1044
            prev_scode = _mem.scode.get_value()
1045
            prev_pttid = _mem.pttid.get_value()
1046

    
1047
        _mem.set_raw("\x00" * 16)
1048

    
1049
        _mem.rxfreq = mem.freq / 10
1050

    
1051
        if mem.duplex == "off":
1052
            for i in range(0, 4):
1053
                _mem.txfreq[i].set_raw("\xFF")
1054
        elif mem.duplex == "split":
1055
            _mem.txfreq = mem.offset / 10
1056
        elif mem.duplex == "+":
1057
            _mem.txfreq = (mem.freq + mem.offset) / 10
1058
        elif mem.duplex == "-":
1059
            _mem.txfreq = (mem.freq - mem.offset) / 10
1060
        else:
1061
            _mem.txfreq = mem.freq / 10
1062

    
1063
        _namelength = self.get_features().valid_name_length
1064
        for i in range(_namelength):
1065
            try:
1066
                _nam.name[i] = mem.name[i]
1067
            except IndexError:
1068
                _nam.name[i] = "\xFF"
1069

    
1070
        rxmode = txmode = ""
1071
        if mem.tmode == "Tone":
1072
            _mem.txtone = int(mem.rtone * 10)
1073
            _mem.rxtone = 0
1074
        elif mem.tmode == "TSQL":
1075
            _mem.txtone = int(mem.ctone * 10)
1076
            _mem.rxtone = int(mem.ctone * 10)
1077
        elif mem.tmode == "DTCS":
1078
            rxmode = txmode = "DTCS"
1079
            _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
1080
            _mem.rxtone = UV5R_DTCS.index(mem.dtcs) + 1
1081
        elif mem.tmode == "Cross":
1082
            txmode, rxmode = mem.cross_mode.split("->", 1)
1083
            if txmode == "Tone":
1084
                _mem.txtone = int(mem.rtone * 10)
1085
            elif txmode == "DTCS":
1086
                _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
1087
            else:
1088
                _mem.txtone = 0
1089
            if rxmode == "Tone":
1090
                _mem.rxtone = int(mem.ctone * 10)
1091
            elif rxmode == "DTCS":
1092
                _mem.rxtone = UV5R_DTCS.index(mem.rx_dtcs) + 1
1093
            else:
1094
                _mem.rxtone = 0
1095
        else:
1096
            _mem.rxtone = 0
1097
            _mem.txtone = 0
1098

    
1099
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
1100
            _mem.txtone += 0x69
1101
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
1102
            _mem.rxtone += 0x69
1103

    
1104
        _mem.scan = mem.skip != "S"
1105
        _mem.wide = mem.mode == "FM"
1106

    
1107
        if mem.power:
1108
            if self._tri_power:
1109
                levels = [str(l) for l in UV5R_POWER_LEVELS3]
1110
                _mem.lowpower = levels.index(str(mem.power))
1111
            else:
1112
                _mem.lowpower = UV5R_POWER_LEVELS.index(mem.power)
1113
        else:
1114
            _mem.lowpower = 0
1115

    
1116
        if not was_empty:
1117
            # restoring old extra-settings (issue 4121
1118
            _mem.bcl.set_value(prev_bcl)
1119
            _mem.scode.set_value(prev_scode)
1120
            _mem.pttid.set_value(prev_pttid)
1121

    
1122
        for setting in mem.extra:
1123
            setattr(_mem, setting.get_name(), setting.value)
1124

    
1125
    def _is_orig(self):
1126
        version_tag = _firmware_version_from_image(self)
1127
        try:
1128
            if b'BFB' in version_tag:
1129
                idx = version_tag.index(b"BFB") + 3
1130
                version = int(version_tag[idx:idx + 3])
1131
                return version < 291
1132
            return False
1133
        except:
1134
            pass
1135
        raise errors.RadioError("Unable to parse version string %s" %
1136
                                version_tag)
1137

    
1138
    def _my_version(self):
1139
        version_tag = _firmware_version_from_image(self)
1140
        if b'BFB' in version_tag:
1141
            idx = version_tag.index(b"BFB") + 3
1142
            return int(version_tag[idx:idx + 3])
1143

    
1144
        raise Exception("Unrecognized firmware version string")
1145

    
1146
    def _my_upper_band(self):
1147
        band_tag = _upper_band_from_image(self)
1148
        return band_tag
1149

    
1150
    def _get_settings(self):
1151
        _ani = self._memobj.ani
1152
        _fm_presets = self._memobj.fm_presets
1153
        _settings = self._memobj.settings
1154
        _squelch = self._memobj.squelch_new
1155
        _vfoa = self._memobj.vfoa
1156
        _vfob = self._memobj.vfob
1157
        _wmchannel = self._memobj.wmchannel
1158
        basic = RadioSettingGroup("basic", "Basic Settings")
1159
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
1160

    
1161
        group = RadioSettings(basic, advanced)
1162

    
1163
        rs = RadioSetting("squelch", "Carrier Squelch Level",
1164
                          RadioSettingValueInteger(0, 9, _settings.squelch))
1165
        basic.append(rs)
1166

    
1167
        rs = RadioSetting("save", "Battery Saver",
1168
                          RadioSettingValueList(
1169
                              SAVE_LIST, SAVE_LIST[_settings.save]))
1170
        basic.append(rs)
1171

    
1172
        rs = RadioSetting("vox", "VOX Sensitivity",
1173
                          RadioSettingValueList(
1174
                              VOX_LIST, VOX_LIST[_settings.vox]))
1175
        advanced.append(rs)
1176

    
1177
        if self.MODEL == "UV-6":
1178
            # NOTE: The UV-6 calls this byte voxenable, but the UV-5R calls it
1179
            # autolk. Since this is a minor difference, it will be referred to
1180
            # by the wrong name for the UV-6.
1181
            rs = RadioSetting("autolk", "Vox",
1182
                              RadioSettingValueBoolean(_settings.autolk))
1183
            advanced.append(rs)
1184

    
1185
        if self.MODEL != "UV-6":
1186
            rs = RadioSetting("abr", "Backlight Timeout",
1187
                              RadioSettingValueInteger(0, 24, _settings.abr))
1188
            basic.append(rs)
1189

    
1190
        rs = RadioSetting("tdr", "Dual Watch",
1191
                          RadioSettingValueBoolean(_settings.tdr))
1192
        advanced.append(rs)
1193

    
1194
        if self.MODEL == "UV-6":
1195
            rs = RadioSetting("tdrch", "Dual Watch Channel",
1196
                              RadioSettingValueList(
1197
                                  TDRCH_LIST, TDRCH_LIST[_settings.tdrch]))
1198
            advanced.append(rs)
1199

    
1200
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
1201
                              RadioSettingValueBoolean(_settings.tdrab))
1202
            advanced.append(rs)
1203
        else:
1204
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
1205
                              RadioSettingValueList(
1206
                                  TDRAB_LIST, TDRAB_LIST[_settings.tdrab]))
1207
            advanced.append(rs)
1208

    
1209
        if self.MODEL == "UV-6":
1210
            rs = RadioSetting("alarm", "Alarm Sound",
1211
                              RadioSettingValueBoolean(_settings.alarm))
1212
            advanced.append(rs)
1213

    
1214
        if _settings.almod > 0x02:
1215
            val = 0x01
1216
        else:
1217
            val = _settings.almod
1218
        rs = RadioSetting("almod", "Alarm Mode",
1219
                          RadioSettingValueList(
1220
                              ALMOD_LIST, ALMOD_LIST[val]))
1221
        advanced.append(rs)
1222

    
1223
        rs = RadioSetting("beep", "Beep",
1224
                          RadioSettingValueBoolean(_settings.beep))
1225
        basic.append(rs)
1226

    
1227
        rs = RadioSetting("timeout", "Timeout Timer",
1228
                          RadioSettingValueList(
1229
                              TIMEOUT_LIST, TIMEOUT_LIST[_settings.timeout]))
1230
        basic.append(rs)
1231

    
1232
        if ((self._is_orig() and self._my_version() < 251) or
1233
                (self.MODEL in ["TI-F8+", "TS-T9+"])):
1234
            rs = RadioSetting("voice", "Voice",
1235
                              RadioSettingValueBoolean(_settings.voice))
1236
            advanced.append(rs)
1237
        else:
1238
            rs = RadioSetting("voice", "Voice",
1239
                              RadioSettingValueList(
1240
                                  VOICE_LIST, VOICE_LIST[_settings.voice]))
1241
            advanced.append(rs)
1242

    
1243
        rs = RadioSetting("screv", "Scan Resume",
1244
                          RadioSettingValueList(
1245
                              RESUME_LIST, RESUME_LIST[_settings.screv]))
1246
        advanced.append(rs)
1247

    
1248
        if self.MODEL != "UV-6":
1249
            rs = RadioSetting("mdfa", "Display Mode (A)",
1250
                              RadioSettingValueList(
1251
                                  MODE_LIST, MODE_LIST[_settings.mdfa]))
1252
            basic.append(rs)
1253

    
1254
            rs = RadioSetting("mdfb", "Display Mode (B)",
1255
                              RadioSettingValueList(
1256
                                  MODE_LIST, MODE_LIST[_settings.mdfb]))
1257
            basic.append(rs)
1258

    
1259
        rs = RadioSetting("bcl", "Busy Channel Lockout",
1260
                          RadioSettingValueBoolean(_settings.bcl))
1261
        advanced.append(rs)
1262

    
1263
        if self.MODEL != "UV-6":
1264
            rs = RadioSetting("autolk", "Automatic Key Lock",
1265
                              RadioSettingValueBoolean(_settings.autolk))
1266
            advanced.append(rs)
1267

    
1268
        rs = RadioSetting("fmradio", "Broadcast FM Radio",
1269
                          RadioSettingValueBoolean(_settings.fmradio))
1270
        advanced.append(rs)
1271

    
1272
        if self.MODEL != "UV-6":
1273
            rs = RadioSetting("wtled", "Standby LED Color",
1274
                              RadioSettingValueList(
1275
                                  COLOR_LIST, COLOR_LIST[_settings.wtled]))
1276
            basic.append(rs)
1277

    
1278
            rs = RadioSetting("rxled", "RX LED Color",
1279
                              RadioSettingValueList(
1280
                                  COLOR_LIST, COLOR_LIST[_settings.rxled]))
1281
            basic.append(rs)
1282

    
1283
            rs = RadioSetting("txled", "TX LED Color",
1284
                              RadioSettingValueList(
1285
                                  COLOR_LIST, COLOR_LIST[_settings.txled]))
1286
            basic.append(rs)
1287

    
1288
        if isinstance(self, BaofengUV82Radio):
1289
            rs = RadioSetting("roger", "Roger Beep (TX)",
1290
                              RadioSettingValueBoolean(_settings.roger))
1291
            basic.append(rs)
1292
            rs = RadioSetting("rogerrx", "Roger Beep (RX)",
1293
                              RadioSettingValueList(
1294
                                  ROGERRX_LIST,
1295
                                  ROGERRX_LIST[_settings.rogerrx]))
1296
            basic.append(rs)
1297
        else:
1298
            rs = RadioSetting("roger", "Roger Beep",
1299
                              RadioSettingValueBoolean(_settings.roger))
1300
            basic.append(rs)
1301

    
1302
        rs = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)",
1303
                          RadioSettingValueBoolean(_settings.ste))
1304
        advanced.append(rs)
1305

    
1306
        rs = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)",
1307
                          RadioSettingValueList(
1308
                              RPSTE_LIST, RPSTE_LIST[_settings.rpste]))
1309
        advanced.append(rs)
1310

    
1311
        rs = RadioSetting("rptrl", "STE Repeater Delay",
1312
                          RadioSettingValueList(
1313
                              STEDELAY_LIST, STEDELAY_LIST[_settings.rptrl]))
1314
        advanced.append(rs)
1315

    
1316
        if self.MODEL != "UV-6":
1317
            rs = RadioSetting("reset", "RESET Menu",
1318
                              RadioSettingValueBoolean(_settings.reset))
1319
            advanced.append(rs)
1320

    
1321
            rs = RadioSetting("menu", "All Menus",
1322
                              RadioSettingValueBoolean(_settings.menu))
1323
            advanced.append(rs)
1324

    
1325
        if self.MODEL == "F-11":
1326
            # this is an F-11 only feature
1327
            rs = RadioSetting("vfomrlock", "VFO/MR Button",
1328
                              RadioSettingValueBoolean(_settings.vfomrlock))
1329
            advanced.append(rs)
1330

    
1331
        if isinstance(self, BaofengUV82Radio):
1332
            # this is a UV-82C only feature
1333
            rs = RadioSetting("vfomrlock", "VFO/MR Switching (UV-82C only)",
1334
                              RadioSettingValueBoolean(_settings.vfomrlock))
1335
            advanced.append(rs)
1336

    
1337
        if self.MODEL == "UV-82HP":
1338
            # this is a UV-82HP only feature
1339
            rs = RadioSetting(
1340
                "vfomrlock", "VFO/MR Switching (BTech UV-82HP only)",
1341
                RadioSettingValueBoolean(_settings.vfomrlock))
1342
            advanced.append(rs)
1343

    
1344
        if isinstance(self, BaofengUV82Radio):
1345
            # this is an UV-82C only feature
1346
            rs = RadioSetting("singleptt", "Single PTT (UV-82C only)",
1347
                              RadioSettingValueBoolean(_settings.singleptt))
1348
            advanced.append(rs)
1349

    
1350
        if self.MODEL == "UV-82HP":
1351
            # this is an UV-82HP only feature
1352
            rs = RadioSetting("singleptt", "Single PTT (BTech UV-82HP only)",
1353
                              RadioSettingValueBoolean(_settings.singleptt))
1354
            advanced.append(rs)
1355

    
1356
        if self.MODEL == "UV-82HP":
1357
            # this is an UV-82HP only feature
1358
            rs = RadioSetting(
1359
                "tdrch", "Tone Burst Frequency (BTech UV-82HP only)",
1360
                RadioSettingValueList(RTONE_LIST, RTONE_LIST[_settings.tdrch]))
1361
            advanced.append(rs)
1362

    
1363
        def set_range_flag(setting):
1364
            val = [85, 115, 101, 65, 116, 79, 119, 110, 82, 105, 115, 107]
1365
            if [ord(x) for x in str(setting.value).strip()] == val:
1366
                self._all_range_flag = True
1367
            else:
1368
                self._all_range_flag = False
1369
            LOG.debug('Set range flag to %s' % self._all_range_flag)
1370

    
1371
        rs = RadioSetting("allrange", "Range Override Parameter",
1372
                          RadioSettingValueString(0, 12, "Default"))
1373
        rs.set_apply_callback(set_range_flag)
1374
        advanced.append(rs)
1375

    
1376
        if len(self._mmap.get_packed()) == 0x1808:
1377
            # Old image, without aux block
1378
            return group
1379

    
1380
        if self.MODEL != "UV-6":
1381
            other = RadioSettingGroup("other", "Other Settings")
1382
            group.append(other)
1383

    
1384
            def _filter(name):
1385
                filtered = ""
1386
                for char in str(name):
1387
                    if char in chirp_common.CHARSET_ASCII:
1388
                        filtered += char
1389
                    else:
1390
                        filtered += " "
1391
                return filtered
1392

    
1393
            _msg = self._memobj.firmware_msg
1394
            val = RadioSettingValueString(0, 7, _filter(_msg.line1))
1395
            val.set_mutable(False)
1396
            rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
1397
            other.append(rs)
1398

    
1399
            val = RadioSettingValueString(0, 7, _filter(_msg.line2))
1400
            val.set_mutable(False)
1401
            rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
1402
            other.append(rs)
1403

    
1404
            _msg = self._memobj.sixpoweron_msg
1405
            rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1",
1406
                              RadioSettingValueString(
1407
                                  0, 7, _filter(_msg.line1)))
1408
            other.append(rs)
1409
            rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2",
1410
                              RadioSettingValueString(
1411
                                  0, 7, _filter(_msg.line2)))
1412
            other.append(rs)
1413

    
1414
            _msg = self._memobj.poweron_msg
1415
            rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
1416
                              RadioSettingValueString(
1417
                                  0, 7, _filter(_msg.line1)))
1418
            other.append(rs)
1419
            rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
1420
                              RadioSettingValueString(
1421
                                  0, 7, _filter(_msg.line2)))
1422
            other.append(rs)
1423

    
1424
            rs = RadioSetting("ponmsg", "Power-On Message",
1425
                              RadioSettingValueList(
1426
                                  PONMSG_LIST, PONMSG_LIST[_settings.ponmsg]))
1427
            other.append(rs)
1428

    
1429
            if self._is_orig():
1430
                limit = "limits_old"
1431
            else:
1432
                limit = "limits_new"
1433

    
1434
            vhf_limit = getattr(self._memobj, limit).vhf
1435
            rs = RadioSetting("%s.vhf.lower" % limit, "VHF Lower Limit (MHz)",
1436
                              RadioSettingValueInteger(1, 1000,
1437
                                                       vhf_limit.lower))
1438
            other.append(rs)
1439

    
1440
            rs = RadioSetting("%s.vhf.upper" % limit, "VHF Upper Limit (MHz)",
1441
                              RadioSettingValueInteger(1, 1000,
1442
                                                       vhf_limit.upper))
1443
            other.append(rs)
1444

    
1445
            rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled",
1446
                              RadioSettingValueBoolean(vhf_limit.enable))
1447
            other.append(rs)
1448

    
1449
            uhf_limit = getattr(self._memobj, limit).uhf
1450
            rs = RadioSetting("%s.uhf.lower" % limit, "UHF Lower Limit (MHz)",
1451
                              RadioSettingValueInteger(1, 1000,
1452
                                                       uhf_limit.lower))
1453
            other.append(rs)
1454
            rs = RadioSetting("%s.uhf.upper" % limit, "UHF Upper Limit (MHz)",
1455
                              RadioSettingValueInteger(1, 1000,
1456
                                                       uhf_limit.upper))
1457
            other.append(rs)
1458
            rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled",
1459
                              RadioSettingValueBoolean(uhf_limit.enable))
1460
            other.append(rs)
1461

    
1462
        if self.MODEL != "UV-6":
1463
            workmode = RadioSettingGroup("workmode", "Work Mode Settings")
1464
            group.append(workmode)
1465

    
1466
            rs = RadioSetting("displayab", "Display",
1467
                              RadioSettingValueList(
1468
                                  AB_LIST, AB_LIST[_settings.displayab]))
1469
            workmode.append(rs)
1470

    
1471
            rs = RadioSetting("workmode", "VFO/MR Mode",
1472
                              RadioSettingValueList(
1473
                                  WORKMODE_LIST,
1474
                                  WORKMODE_LIST[_settings.workmode]))
1475
            workmode.append(rs)
1476

    
1477
            rs = RadioSetting("keylock", "Keypad Lock",
1478
                              RadioSettingValueBoolean(_settings.keylock))
1479
            workmode.append(rs)
1480

    
1481
            rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
1482
                              RadioSettingValueInteger(0, 127,
1483
                                                       _wmchannel.mrcha))
1484
            workmode.append(rs)
1485

    
1486
            rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
1487
                              RadioSettingValueInteger(0, 127,
1488
                                                       _wmchannel.mrchb))
1489
            workmode.append(rs)
1490

    
1491
            def convert_bytes_to_freq(bytes):
1492
                real_freq = 0
1493
                for byte in bytes:
1494
                    real_freq = (real_freq * 10) + byte
1495
                return chirp_common.format_freq(real_freq * 10)
1496

    
1497
            def my_validate(value):
1498
                value = chirp_common.parse_freq(value)
1499
                if 17400000 <= value and value < 40000000:
1500
                    msg = ("Can't be between 174.00000-400.00000")
1501
                    raise InvalidValueError(msg)
1502
                return chirp_common.format_freq(value)
1503

    
1504
            def apply_freq(setting, obj):
1505
                value = chirp_common.parse_freq(str(setting.value)) / 10
1506
                obj.band = value >= 40000000
1507
                for i in range(7, -1, -1):
1508
                    obj.freq[i] = value % 10
1509
                    value /= 10
1510

    
1511
            val1a = RadioSettingValueString(0, 10,
1512
                                            convert_bytes_to_freq(_vfoa.freq))
1513
            val1a.set_validate_callback(my_validate)
1514
            rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a)
1515
            rs.set_apply_callback(apply_freq, _vfoa)
1516
            workmode.append(rs)
1517

    
1518
            val1b = RadioSettingValueString(0, 10,
1519
                                            convert_bytes_to_freq(_vfob.freq))
1520
            val1b.set_validate_callback(my_validate)
1521
            rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b)
1522
            rs.set_apply_callback(apply_freq, _vfob)
1523
            workmode.append(rs)
1524

    
1525
            rs = RadioSetting("vfoa.sftd", "VFO A Shift",
1526
                              RadioSettingValueList(
1527
                                  SHIFTD_LIST, SHIFTD_LIST[_vfoa.sftd]))
1528
            workmode.append(rs)
1529

    
1530
            rs = RadioSetting("vfob.sftd", "VFO B Shift",
1531
                              RadioSettingValueList(
1532
                                  SHIFTD_LIST, SHIFTD_LIST[_vfob.sftd]))
1533
            workmode.append(rs)
1534

    
1535
            def convert_bytes_to_offset(bytes):
1536
                real_offset = 0
1537
                for byte in bytes:
1538
                    real_offset = (real_offset * 10) + byte
1539
                return chirp_common.format_freq(real_offset * 1000)
1540

    
1541
            def apply_offset(setting, obj):
1542
                value = chirp_common.parse_freq(str(setting.value)) / 1000
1543
                for i in range(5, -1, -1):
1544
                    obj.offset[i] = value % 10
1545
                    value /= 10
1546

    
1547
            val1a = RadioSettingValueString(
1548
                0, 10, convert_bytes_to_offset(_vfoa.offset))
1549
            rs = RadioSetting("vfoa.offset",
1550
                              "VFO A Offset (0.0-999.999)", val1a)
1551
            rs.set_apply_callback(apply_offset, _vfoa)
1552
            workmode.append(rs)
1553

    
1554
            val1b = RadioSettingValueString(
1555
                0, 10, convert_bytes_to_offset(_vfob.offset))
1556
            rs = RadioSetting("vfob.offset",
1557
                              "VFO B Offset (0.0-999.999)", val1b)
1558
            rs.set_apply_callback(apply_offset, _vfob)
1559
            workmode.append(rs)
1560

    
1561
            if self._tri_power:
1562
                if _vfoa.txpower3 > 0x02:
1563
                    val = 0x00
1564
                else:
1565
                    val = _vfoa.txpower3
1566
                rs = RadioSetting("vfoa.txpower3", "VFO A Power",
1567
                                  RadioSettingValueList(
1568
                                      TXPOWER3_LIST,
1569
                                      TXPOWER3_LIST[val]))
1570
                workmode.append(rs)
1571

    
1572
                if _vfob.txpower3 > 0x02:
1573
                    val = 0x00
1574
                else:
1575
                    val = _vfob.txpower3
1576
                rs = RadioSetting("vfob.txpower3", "VFO B Power",
1577
                                  RadioSettingValueList(
1578
                                      TXPOWER3_LIST,
1579
                                      TXPOWER3_LIST[val]))
1580
                workmode.append(rs)
1581
            else:
1582
                rs = RadioSetting("vfoa.txpower", "VFO A Power",
1583
                                  RadioSettingValueList(
1584
                                      TXPOWER_LIST,
1585
                                      TXPOWER_LIST[_vfoa.txpower]))
1586
                workmode.append(rs)
1587

    
1588
                rs = RadioSetting("vfob.txpower", "VFO B Power",
1589
                                  RadioSettingValueList(
1590
                                      TXPOWER_LIST,
1591
                                      TXPOWER_LIST[_vfob.txpower]))
1592
                workmode.append(rs)
1593

    
1594
            rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth",
1595
                              RadioSettingValueList(
1596
                                  BANDWIDTH_LIST,
1597
                                  BANDWIDTH_LIST[_vfoa.widenarr]))
1598
            workmode.append(rs)
1599

    
1600
            rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
1601
                              RadioSettingValueList(
1602
                                  BANDWIDTH_LIST,
1603
                                  BANDWIDTH_LIST[_vfob.widenarr]))
1604
            workmode.append(rs)
1605

    
1606
            rs = RadioSetting("vfoa.scode", "VFO A PTT-ID",
1607
                              RadioSettingValueList(
1608
                                  PTTIDCODE_LIST, PTTIDCODE_LIST[_vfoa.scode]))
1609
            workmode.append(rs)
1610

    
1611
            rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
1612
                              RadioSettingValueList(
1613
                                  PTTIDCODE_LIST, PTTIDCODE_LIST[_vfob.scode]))
1614
            workmode.append(rs)
1615

    
1616
            if not self._is_orig():
1617
                rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
1618
                                  RadioSettingValueList(
1619
                                      STEP291_LIST, STEP291_LIST[_vfoa.step]))
1620
                workmode.append(rs)
1621
                rs = RadioSetting("vfob.step", "VFO B Tuning Step",
1622
                                  RadioSettingValueList(
1623
                                      STEP291_LIST, STEP291_LIST[_vfob.step]))
1624
                workmode.append(rs)
1625
            else:
1626
                rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
1627
                                  RadioSettingValueList(
1628
                                      STEP_LIST, STEP_LIST[_vfoa.step]))
1629
                workmode.append(rs)
1630
                rs = RadioSetting("vfob.step", "VFO B Tuning Step",
1631
                                  RadioSettingValueList(
1632
                                      STEP_LIST, STEP_LIST[_vfob.step]))
1633
                workmode.append(rs)
1634

    
1635
        fm_preset = RadioSettingGroup("fm_preset", "FM Radio Preset")
1636
        group.append(fm_preset)
1637

    
1638
        # broadcast FM settings
1639
        value = self._memobj.fm_presets
1640
        value_shifted = ((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8)
1641
        if value_shifted >= 65.0 * 10 and value_shifted <= 108.0 * 10:
1642
            # storage method 3 (discovered 2022)
1643
            self._bw_shift = True
1644
            preset = value_shifted / 10.0
1645
        elif value >= 65.0 * 10 and value <= 108.0 * 10:
1646
            # storage method 2
1647
            preset = value / 10.0
1648
        elif value <= 108.0 * 10 - 650:
1649
            # original storage method (2012)
1650
            preset = value / 10.0 + 65
1651
        else:
1652
            # unknown (undiscovered method or no FM chip?)
1653
            preset = False
1654
        if preset:
1655
            rs = RadioSettingValueFloat(65, 108.0, preset, 0.1, 1)
1656
            rset = RadioSetting("fm_presets", "FM Preset(MHz)", rs)
1657
            fm_preset.append(rset)
1658

    
1659
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1660
        group.append(dtmf)
1661

    
1662
        if str(self._memobj.firmware_msg.line1) == "HN5RV01":
1663
            dtmfchars = "0123456789ABCD*#"
1664
        else:
1665
            dtmfchars = "0123456789 *#ABCD"
1666

    
1667
        for i in range(0, 15):
1668
            _codeobj = self._memobj.pttid[i].code
1669
            _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1670
            val = RadioSettingValueString(0, 5, _code, False)
1671
            val.set_charset(dtmfchars)
1672
            rs = RadioSetting("pttid/%i.code" % i,
1673
                              "PTT ID Code %i" % (i + 1), val)
1674

    
1675
            def apply_code(setting, obj):
1676
                code = []
1677
                for j in range(0, 5):
1678
                    try:
1679
                        code.append(dtmfchars.index(str(setting.value)[j]))
1680
                    except IndexError:
1681
                        code.append(0xFF)
1682
                obj.code = code
1683
            rs.set_apply_callback(apply_code, self._memobj.pttid[i])
1684
            dtmf.append(rs)
1685

    
1686
        dtmfcharsani = "0123456789"
1687

    
1688
        _codeobj = self._memobj.ani.code
1689
        _code = "".join([dtmfcharsani[x] for x in _codeobj if int(x) < 0x1F])
1690
        val = RadioSettingValueString(0, 5, _code, False)
1691
        val.set_charset(dtmfcharsani)
1692
        rs = RadioSetting("ani.code", "ANI Code", val)
1693

    
1694
        def apply_code(setting, obj):
1695
            code = []
1696
            for j in range(0, 5):
1697
                try:
1698
                    code.append(dtmfchars.index(str(setting.value)[j]))
1699
                except IndexError:
1700
                    code.append(0xFF)
1701
            obj.code = code
1702
        rs.set_apply_callback(apply_code, _ani)
1703
        dtmf.append(rs)
1704

    
1705
        rs = RadioSetting("ani.aniid", "ANI ID",
1706
                          RadioSettingValueList(PTTID_LIST,
1707
                                                PTTID_LIST[_ani.aniid]))
1708
        dtmf.append(rs)
1709

    
1710
        _codeobj = self._memobj.ani.alarmcode
1711
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1712
        val = RadioSettingValueString(0, 3, _code, False)
1713
        val.set_charset(dtmfchars)
1714
        rs = RadioSetting("ani.alarmcode", "Alarm Code", val)
1715

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

    
1727
        rs = RadioSetting("dtmfst", "DTMF Sidetone",
1728
                          RadioSettingValueList(DTMFST_LIST,
1729
                                                DTMFST_LIST[_settings.dtmfst]))
1730
        dtmf.append(rs)
1731

    
1732
        if _ani.dtmfon > 0xC3:
1733
            val = 0x00
1734
        else:
1735
            val = _ani.dtmfon
1736
        rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
1737
                          RadioSettingValueList(DTMFSPEED_LIST,
1738
                                                DTMFSPEED_LIST[val]))
1739
        dtmf.append(rs)
1740

    
1741
        if _ani.dtmfoff > 0xC3:
1742
            val = 0x00
1743
        else:
1744
            val = _ani.dtmfoff
1745
        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
1746
                          RadioSettingValueList(DTMFSPEED_LIST,
1747
                                                DTMFSPEED_LIST[val]))
1748
        dtmf.append(rs)
1749

    
1750
        rs = RadioSetting("pttlt", "PTT ID Delay",
1751
                          RadioSettingValueInteger(0, 50, _settings.pttlt))
1752
        dtmf.append(rs)
1753

    
1754
        if not self._is_orig() and self._aux_block:
1755
            service = RadioSettingGroup("service", "Service Settings")
1756
            group.append(service)
1757

    
1758
            for band in ["vhf", "uhf"]:
1759
                for index in range(0, 10):
1760
                    key = "squelch_new.%s.sql%i" % (band, index)
1761
                    if band == "vhf":
1762
                        _obj = self._memobj.squelch_new.vhf
1763
                    elif band == "uhf":
1764
                        _obj = self._memobj.squelch_new.uhf
1765
                    name = "%s Squelch %i" % (band.upper(), index)
1766
                    rs = RadioSetting(key, name,
1767
                                      RadioSettingValueInteger(
1768
                                          0, 123,
1769
                                          getattr(_obj, "sql%i" % (index))))
1770
                    service.append(rs)
1771

    
1772
        return group
1773

    
1774
    def get_settings(self):
1775
        try:
1776
            return self._get_settings()
1777
        except:
1778
            import traceback
1779
            LOG.error("Failed to parse settings: %s", traceback.format_exc())
1780
            return None
1781

    
1782
    def set_settings(self, settings):
1783
        _settings = self._memobj.settings
1784
        for element in settings:
1785
            if not isinstance(element, RadioSetting):
1786
                if element.get_name() == "fm_preset":
1787
                    self._set_fm_preset(element)
1788
                else:
1789
                    self.set_settings(element)
1790
                    continue
1791
            else:
1792
                try:
1793
                    name = element.get_name()
1794
                    if "." in name:
1795
                        bits = name.split(".")
1796
                        obj = self._memobj
1797
                        for bit in bits[:-1]:
1798
                            if "/" in bit:
1799
                                bit, index = bit.split("/", 1)
1800
                                index = int(index)
1801
                                obj = getattr(obj, bit)[index]
1802
                            else:
1803
                                obj = getattr(obj, bit)
1804
                        setting = bits[-1]
1805
                    else:
1806
                        obj = _settings
1807
                        setting = element.get_name()
1808

    
1809
                    if element.has_apply_callback():
1810
                        LOG.debug("Using apply callback")
1811
                        element.run_apply_callback()
1812
                    elif element.value.get_mutable():
1813
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1814
                        setattr(obj, setting, element.value)
1815
                except Exception as e:
1816
                    LOG.debug(element.get_name())
1817
                    raise
1818

    
1819
    def _set_fm_preset(self, settings):
1820
        for element in settings:
1821
            try:
1822
                val = element.value
1823
                if self._memobj.fm_presets <= 108.0 * 10 - 650:
1824
                    value = int(val.get_value() * 10 - 650)
1825
                else:
1826
                    value = int(val.get_value() * 10)
1827
                LOG.debug("Setting fm_presets = %s" % (value))
1828
                if self._bw_shift:
1829
                    value = ((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8)
1830
                self._memobj.fm_presets = value
1831
            except Exception as e:
1832
                LOG.debug(element.get_name())
1833
                raise
1834

    
1835

    
1836
class UV5XAlias(chirp_common.Alias):
1837
    VENDOR = "Baofeng"
1838
    MODEL = "UV-5X"
1839

    
1840

    
1841
class RT5RAlias(chirp_common.Alias):
1842
    VENDOR = "Retevis"
1843
    MODEL = "RT5R"
1844

    
1845

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

    
1850

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

    
1855

    
1856
class RT5_TPAlias(chirp_common.Alias):
1857
    VENDOR = "Retevis"
1858
    MODEL = "RT5(tri-power)"
1859

    
1860

    
1861
class RH5RAlias(chirp_common.Alias):
1862
    VENDOR = "Rugged"
1863
    MODEL = "RH5R"
1864

    
1865

    
1866
class ROUV5REXAlias(chirp_common.Alias):
1867
    VENDOR = "Radioddity"
1868
    MODEL = "UV-5R EX"
1869

    
1870

    
1871
class A5RAlias(chirp_common.Alias):
1872
    VENDOR = "Ansoko"
1873
    MODEL = "A-5R"
1874

    
1875

    
1876
@directory.register
1877
class BaofengUV5RGeneric(BaofengUV5R):
1878
    ALIASES = [UV5XAlias, RT5RAlias, RT5RVAlias, RT5Alias, RH5RAlias,
1879
               ROUV5REXAlias, A5RAlias]
1880

    
1881

    
1882
@directory.register
1883
class BaofengF11Radio(BaofengUV5R):
1884
    VENDOR = "Baofeng"
1885
    MODEL = "F-11"
1886
    _basetype = BASETYPE_F11
1887
    _idents = [UV5R_MODEL_F11]
1888

    
1889
    def _is_orig(self):
1890
        # Override this for F11 to always return False
1891
        return False
1892

    
1893

    
1894
@directory.register
1895
class BaofengUV82Radio(BaofengUV5R):
1896
    MODEL = "UV-82"
1897
    _basetype = BASETYPE_UV82
1898
    _idents = [UV5R_MODEL_UV82]
1899
    _vhf_range = (130000000, 176000000)
1900
    _uhf_range = (400000000, 521000000)
1901
    _valid_chars = chirp_common.CHARSET_ASCII
1902

    
1903
    def _is_orig(self):
1904
        # Override this for UV82 to always return False
1905
        return False
1906

    
1907

    
1908
@directory.register
1909
class Radioddity82X3Radio(BaofengUV82Radio):
1910
    VENDOR = "Radioddity"
1911
    MODEL = "UV-82X3"
1912
    _basetype = BASETYPE_UV82X3
1913

    
1914
    def get_features(self):
1915
        rf = BaofengUV5R.get_features(self)
1916
        rf.valid_bands = [self._vhf_range,
1917
                          (200000000, 260000000),
1918
                          self._uhf_range]
1919
        return rf
1920

    
1921

    
1922
@directory.register
1923
class BaofengUV6Radio(BaofengUV5R):
1924

    
1925
    """Baofeng UV-6/UV-7"""
1926
    VENDOR = "Baofeng"
1927
    MODEL = "UV-6"
1928
    _basetype = BASETYPE_UV6
1929
    _idents = [UV5R_MODEL_UV6,
1930
               UV5R_MODEL_UV6_ORIG
1931
               ]
1932
    _aux_block = False
1933

    
1934
    def get_features(self):
1935
        rf = BaofengUV5R.get_features(self)
1936
        rf.memory_bounds = (1, 128)
1937
        return rf
1938

    
1939
    def _get_mem(self, number):
1940
        return self._memobj.memory[number - 1]
1941

    
1942
    def _get_nam(self, number):
1943
        return self._memobj.names[number - 1]
1944

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

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

    
1951
    def _is_orig(self):
1952
        # Override this for UV6 to always return False
1953
        return False
1954

    
1955

    
1956
@directory.register
1957
class IntekKT980Radio(BaofengUV5R):
1958
    VENDOR = "Intek"
1959
    MODEL = "KT-980HP"
1960
    _basetype = BASETYPE_KT980HP
1961
    _idents = [UV5R_MODEL_291]
1962
    _vhf_range = (130000000, 180000000)
1963
    _uhf_range = (400000000, 521000000)
1964
    _tri_power = True
1965

    
1966
    def get_features(self):
1967
        rf = BaofengUV5R.get_features(self)
1968
        rf.valid_power_levels = UV5R_POWER_LEVELS3
1969
        return rf
1970

    
1971
    def _is_orig(self):
1972
        # Override this for KT980HP to always return False
1973
        return False
1974

    
1975

    
1976
class ROGA5SAlias(chirp_common.Alias):
1977
    VENDOR = "Radioddity"
1978
    MODEL = "GA-5S"
1979

    
1980

    
1981
class UV5XPAlias(chirp_common.Alias):
1982
    VENDOR = "Baofeng"
1983
    MODEL = "UV-5XP"
1984

    
1985

    
1986
class TSTIF8Alias(chirp_common.Alias):
1987
    VENDOR = "TechSide"
1988
    MODEL = "TI-F8+"
1989

    
1990

    
1991
class TenwayUV5RPro(chirp_common.Alias):
1992
    VENDOR = 'Tenway'
1993
    MODEL = 'UV-5R Pro'
1994

    
1995

    
1996
class TSTST9Alias(chirp_common.Alias):
1997
    VENDOR = "TechSide"
1998
    MODEL = "TS-T9+"
1999

    
2000

    
2001
class TDUV5RRadio(chirp_common.Alias):
2002
    VENDOR = "TIDRADIO"
2003
    MODEL = "TD-UV5R TriPower"
2004

    
2005

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

    
2020
    def get_features(self):
2021
        rf = BaofengUV5R.get_features(self)
2022
        rf.valid_power_levels = UV5R_POWER_LEVELS3
2023
        return rf
2024

    
2025
    def _is_orig(self):
2026
        # Override this for BFF8HP to always return False
2027
        return False
2028

    
2029

    
2030
class TenwayUV82Pro(chirp_common.Alias):
2031
    VENDOR = 'Tenway'
2032
    MODEL = 'UV-82 Pro'
2033

    
2034

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

    
2048
    def get_features(self):
2049
        rf = BaofengUV5R.get_features(self)
2050
        rf.valid_power_levels = UV5R_POWER_LEVELS3
2051
        return rf
2052

    
2053
    def _is_orig(self):
2054
        # Override this for UV82HP to always return False
2055
        return False
2056

    
2057

    
2058
@directory.register
2059
class RadioddityUV5RX3Radio(BaofengUV5R):
2060
    VENDOR = "Radioddity"
2061
    MODEL = "UV-5RX3"
2062

    
2063
    def get_features(self):
2064
        rf = BaofengUV5R.get_features(self)
2065
        rf.valid_bands = [self._vhf_range,
2066
                          (200000000, 260000000),
2067
                          self._uhf_range]
2068
        return rf
2069

    
2070
    @classmethod
2071
    def match_model(cls, filename, filedata):
2072
        return False
2073

    
2074

    
2075
@directory.register
2076
class RadioddityGT5RRadio(BaofengUV5R):
2077
    VENDOR = 'Baofeng'
2078
    MODEL = 'GT-5R'
2079

    
2080
    vhftx = [144000000, 148000000]
2081
    uhftx = [420000000, 450000000]
2082

    
2083
    def validate_memory(self, mem):
2084
        msgs = super().validate_memory(mem)
2085

    
2086
        _msg_duplex = 'Duplex must be "off" for this frequency'
2087
        _msg_offset = 'Only simplex or +5MHz offset allowed on GMRS'
2088

    
2089
        if not ((mem.freq >= self.vhftx[0] and mem.freq < self.vhftx[1]) or
2090
                (mem.freq >= self.uhftx[0] and mem.freq < self.uhftx[1])):
2091
            if mem.duplex != "off":
2092
                msgs.append(chirp_common.ValidationWarning(_msg_duplex))
2093

    
2094
        return msgs
2095

    
2096
    def check_set_memory_immutable_policy(self, existing, new):
2097
        existing.immutable = []
2098
        super().check_set_memory_immutable_policy(existing, new)
2099

    
2100
    @classmethod
2101
    def match_model(cls, filename, filedata):
2102
        return False
2103

    
2104

    
2105
@directory.register
2106
class RadioddityUV5GRadio(BaofengUV5R):
2107
    VENDOR = 'Radioddity'
2108
    MODEL = 'UV-5G'
2109

    
2110
    _basetype = BASETYPE_UV5R
2111
    _idents = [UV5R_MODEL_UV5G]
2112

    
2113
    @classmethod
2114
    def match_model(cls, filename, filedata):
2115
        return False
(13-13/41)