Project

General

Profile

Feature #10505 » uv5r_aux_mem_bug_workaround.py

Jim Unroe, 06/25/2023 07:19 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
        _special_handle_aux_block = True
655
    else:
656
        _special_handle_aux_block = False
657

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

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

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

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

    
695
    if image_matched_radio == False:
696
        #msg = ("Upload finished, but the 'Other Settings' "
697
        #       "could not be sent because the firmware "
698
        #       "version of the image (%s) does not match "
699
        #       "that of the radio (%s).")
700
        #raise errors.RadioError(msg % (image_version, radio_version))
701

    
702
        LOG.info("Skipped writing aux block")
703
        msg = ("This is NOT an error.\n"
704
               "Upload finished. The 'Aux block' (which contains "
705
               "the 'Other Settings' and 'Service Settings') was "
706
               "skipped because the firmware version of the image "
707
               "(%s) does not match that of the radio (%s).")
708
        raise errors.RadioError(msg % (image_version, radio_version))
709
        return  # Aux block skipped
710

    
711
    # Auxiliary block at radio address 0x1EC0, our offset 0x1808
712
    for start_addr, end_addr in ranges_aux:
713
        for i in range(start_addr, end_addr, 0x10):
714
            addr = 0x1808 + (i - 0x1EC0)
715
            _send_block(radio, i, mmap[addr:addr + 0x10])
716

    
717
    #if not image_matched_radio:
718
    #    msg = ("Upload finished, but the 'Other Settings' "
719
    #           "could not be sent because the firmware "
720
    #           "version of the image (%s) does not match "
721
    #           "that of the radio (%s).")
722
    #    raise errors.RadioError(msg % (image_version, radio_version))
723

    
724
    if radio._all_range_flag:
725
        radio._all_range_flag = False
726
        LOG.warning('Sending all ranges to radio has completed')
727
        raise errors.RadioError(
728
            "This is NOT an error.\n"
729
            "The upload has finished successfully.\n"
730
            "Please restart CHIRP.")
731

    
732

    
733
UV5R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=4.00),
734
                     chirp_common.PowerLevel("Low",  watts=1.00)]
735

    
736
UV5R_POWER_LEVELS3 = [chirp_common.PowerLevel("High", watts=8.00),
737
                      chirp_common.PowerLevel("Med",  watts=4.00),
738
                      chirp_common.PowerLevel("Low",  watts=1.00)]
739

    
740
UV5R_DTCS = tuple(sorted(chirp_common.DTCS_CODES + (645,)))
741

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

    
745

    
746
def model_match(cls, data):
747
    """Match the opened/downloaded image to the correct version"""
748

    
749
    if len(data) == 0x1950:
750
        rid = data[0x1948:0x1950]
751
        return rid.startswith(cls.MODEL)
752
    elif len(data) == 0x1948:
753
        rid = data[cls._fw_ver_file_start:cls._fw_ver_file_stop]
754
        if any(type in rid for type in cls._basetype):
755
            return True
756
    else:
757
        return False
758

    
759

    
760
class BaofengUV5R(chirp_common.CloneModeRadio):
761

    
762
    """Baofeng UV-5R"""
763
    VENDOR = "Baofeng"
764
    MODEL = "UV-5R"
765
    BAUD_RATE = 9600
766
    NEEDS_COMPAT_SERIAL = False
767

    
768
    _memsize = 0x1808
769
    _basetype = BASETYPE_UV5R
770
    _idents = [UV5R_MODEL_291,
771
               UV5R_MODEL_ORIG
772
               ]
773
    _vhf_range = (130000000, 176000000)
774
    _220_range = (220000000, 260000000)
775
    _uhf_range = (400000000, 520000000)
776
    _aux_block = True
777
    _tri_power = False
778
    _bw_shift = False
779
    _mem_params = (0x1828  # poweron_msg offset
780
                   )
781
    # offset of fw version in image file
782
    _fw_ver_file_start = 0x1838
783
    _fw_ver_file_stop = 0x1846
784

    
785
    _ranges_main = [
786
                    (0x0008, 0x1808),
787
                   ]
788
    _ranges_aux = [
789
                   (0x1EC0, 0x2000),
790
                  ]
791
    _valid_chars = UV5R_CHARSET
792

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

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

    
841
        normal_bands = [self._vhf_range, self._uhf_range]
842
        rax_bands = [self._vhf_range, self._220_range]
843

    
844
        if self._mmap is None:
845
            rf.valid_bands = [normal_bands[0], rax_bands[1], normal_bands[1]]
846
        elif not self._is_orig() and self._my_upper_band() == vhf_220_radio:
847
            rf.valid_bands = rax_bands
848
        else:
849
            rf.valid_bands = normal_bands
850
        rf.memory_bounds = (0, 127)
851
        return rf
852

    
853
    @classmethod
854
    def match_model(cls, filedata, filename):
855
        match_size = False
856
        match_model = False
857
        if len(filedata) in [0x1808, 0x1948, 0x1950]:
858
            match_size = True
859
        match_model = model_match(cls, filedata)
860

    
861
        if match_size and match_model:
862
            return True
863
        else:
864
            return False
865

    
866
    def process_mmap(self):
867
        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
868
        self._all_range_flag = False
869

    
870
    def sync_in(self):
871
        try:
872
            self._mmap = _do_download(self)
873
        except errors.RadioError:
874
            raise
875
        except Exception as e:
876
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
877
        self.process_mmap()
878

    
879
    def sync_out(self):
880
        try:
881
            _do_upload(self)
882
        except errors.RadioError:
883
            raise
884
        except Exception as e:
885
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
886

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

    
890
    def _is_txinh(self, _mem):
891
        raw_tx = ""
892
        for i in range(0, 4):
893
            raw_tx += _mem.txfreq[i].get_raw()
894
        return raw_tx == "\xFF\xFF\xFF\xFF"
895

    
896
    def _get_mem(self, number):
897
        return self._memobj.memory[number]
898

    
899
    def _get_nam(self, number):
900
        return self._memobj.names[number]
901

    
902
    def get_memory(self, number):
903
        _mem = self._get_mem(number)
904
        _nam = self._get_nam(number)
905

    
906
        mem = chirp_common.Memory()
907
        mem.number = number
908

    
909
        if _mem.get_raw()[0] == "\xff":
910
            mem.empty = True
911
            return mem
912

    
913
        mem.freq = int(_mem.rxfreq) * 10
914

    
915
        if self._is_txinh(_mem):
916
            mem.duplex = "off"
917
            mem.offset = 0
918
        elif int(_mem.rxfreq) == int(_mem.txfreq):
919
            mem.duplex = ""
920
            mem.offset = 0
921
        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
922
            mem.duplex = "split"
923
            mem.offset = int(_mem.txfreq) * 10
924
        else:
925
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
926
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
927

    
928
        for char in _nam.name:
929
            if str(char) == "\xFF":
930
                char = " "  # The UV-5R software may have 0xFF mid-name
931
            mem.name += str(char)
932
        mem.name = mem.name.rstrip()
933

    
934
        dtcs_pol = ["N", "N"]
935

    
936
        if _mem.txtone in [0, 0xFFFF]:
937
            txmode = ""
938
        elif _mem.txtone >= 0x0258:
939
            txmode = "Tone"
940
            mem.rtone = int(_mem.txtone) / 10.0
941
        elif _mem.txtone <= 0x0258:
942
            txmode = "DTCS"
943
            if _mem.txtone > 0x69:
944
                index = _mem.txtone - 0x6A
945
                dtcs_pol[0] = "R"
946
            else:
947
                index = _mem.txtone - 1
948
            mem.dtcs = UV5R_DTCS[index]
949
        else:
950
            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
951

    
952
        if _mem.rxtone in [0, 0xFFFF]:
953
            rxmode = ""
954
        elif _mem.rxtone >= 0x0258:
955
            rxmode = "Tone"
956
            mem.ctone = int(_mem.rxtone) / 10.0
957
        elif _mem.rxtone <= 0x0258:
958
            rxmode = "DTCS"
959
            if _mem.rxtone >= 0x6A:
960
                index = _mem.rxtone - 0x6A
961
                dtcs_pol[1] = "R"
962
            else:
963
                index = _mem.rxtone - 1
964
            mem.rx_dtcs = UV5R_DTCS[index]
965
        else:
966
            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
967

    
968
        if txmode == "Tone" and not rxmode:
969
            mem.tmode = "Tone"
970
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
971
            mem.tmode = "TSQL"
972
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
973
            mem.tmode = "DTCS"
974
        elif rxmode or txmode:
975
            mem.tmode = "Cross"
976
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
977

    
978
        mem.dtcs_polarity = "".join(dtcs_pol)
979

    
980
        if not _mem.scan:
981
            mem.skip = "S"
982

    
983
        if self._tri_power:
984
            levels = UV5R_POWER_LEVELS3
985
        else:
986
            levels = UV5R_POWER_LEVELS
987
        try:
988
            mem.power = levels[_mem.lowpower]
989
        except IndexError:
990
            LOG.error("Radio reported invalid power level %s (in %s)" %
991
                      (_mem.lowpower, levels))
992
            mem.power = levels[0]
993

    
994
        mem.mode = _mem.wide and "FM" or "NFM"
995

    
996
        mem.extra = RadioSettingGroup("Extra", "extra")
997

    
998
        rs = RadioSetting("bcl", "BCL",
999
                          RadioSettingValueBoolean(_mem.bcl))
1000
        mem.extra.append(rs)
1001

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

    
1007
        rs = RadioSetting("scode", "PTT ID Code",
1008
                          RadioSettingValueList(PTTIDCODE_LIST,
1009
                                                PTTIDCODE_LIST[_mem.scode]))
1010
        mem.extra.append(rs)
1011

    
1012
        immutable = []
1013

    
1014
        if self.MODEL == "GT-5R":
1015
            if not ((mem.freq >= self.vhftx[0] and mem.freq < self.vhftx[1]) or
1016
                    (mem.freq >= self.uhftx[0] and mem.freq < self.uhftx[1])):
1017
                mem.duplex = 'off'
1018
                mem.offset = 0
1019
                immutable = ["duplex", "offset"]
1020

    
1021
        mem.immutable = immutable
1022

    
1023
        return mem
1024

    
1025
    def _set_mem(self, number):
1026
        return self._memobj.memory[number]
1027

    
1028
    def _set_nam(self, number):
1029
        return self._memobj.names[number]
1030

    
1031
    def set_memory(self, mem):
1032
        _mem = self._get_mem(mem.number)
1033
        _nam = self._get_nam(mem.number)
1034

    
1035
        if mem.empty:
1036
            _mem.set_raw("\xff" * 16)
1037
            _nam.set_raw("\xff" * 16)
1038
            return
1039

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

    
1054
        _mem.set_raw("\x00" * 16)
1055

    
1056
        _mem.rxfreq = mem.freq / 10
1057

    
1058
        if mem.duplex == "off":
1059
            for i in range(0, 4):
1060
                _mem.txfreq[i].set_raw("\xFF")
1061
        elif mem.duplex == "split":
1062
            _mem.txfreq = mem.offset / 10
1063
        elif mem.duplex == "+":
1064
            _mem.txfreq = (mem.freq + mem.offset) / 10
1065
        elif mem.duplex == "-":
1066
            _mem.txfreq = (mem.freq - mem.offset) / 10
1067
        else:
1068
            _mem.txfreq = mem.freq / 10
1069

    
1070
        _namelength = self.get_features().valid_name_length
1071
        for i in range(_namelength):
1072
            try:
1073
                _nam.name[i] = mem.name[i]
1074
            except IndexError:
1075
                _nam.name[i] = "\xFF"
1076

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

    
1106
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
1107
            _mem.txtone += 0x69
1108
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
1109
            _mem.rxtone += 0x69
1110

    
1111
        _mem.scan = mem.skip != "S"
1112
        _mem.wide = mem.mode == "FM"
1113

    
1114
        if mem.power:
1115
            if self._tri_power:
1116
                levels = [str(l) for l in UV5R_POWER_LEVELS3]
1117
                _mem.lowpower = levels.index(str(mem.power))
1118
            else:
1119
                _mem.lowpower = UV5R_POWER_LEVELS.index(mem.power)
1120
        else:
1121
            _mem.lowpower = 0
1122

    
1123
        if not was_empty:
1124
            # restoring old extra-settings (issue 4121
1125
            _mem.bcl.set_value(prev_bcl)
1126
            _mem.scode.set_value(prev_scode)
1127
            _mem.pttid.set_value(prev_pttid)
1128

    
1129
        for setting in mem.extra:
1130
            setattr(_mem, setting.get_name(), setting.value)
1131

    
1132
    def _is_orig(self):
1133
        version_tag = _firmware_version_from_image(self)
1134
        try:
1135
            if b'BFB' in version_tag:
1136
                idx = version_tag.index(b"BFB") + 3
1137
                version = int(version_tag[idx:idx + 3])
1138
                return version < 291
1139
            return False
1140
        except:
1141
            pass
1142
        raise errors.RadioError("Unable to parse version string %s" %
1143
                                version_tag)
1144

    
1145
    def _my_version(self):
1146
        version_tag = _firmware_version_from_image(self)
1147
        if b'BFB' in version_tag:
1148
            idx = version_tag.index(b"BFB") + 3
1149
            return int(version_tag[idx:idx + 3])
1150

    
1151
        raise Exception("Unrecognized firmware version string")
1152

    
1153
    def _my_upper_band(self):
1154
        band_tag = _upper_band_from_image(self)
1155
        return band_tag
1156

    
1157
    def _get_settings(self):
1158
        _ani = self._memobj.ani
1159
        _fm_presets = self._memobj.fm_presets
1160
        _settings = self._memobj.settings
1161
        _squelch = self._memobj.squelch_new
1162
        _vfoa = self._memobj.vfoa
1163
        _vfob = self._memobj.vfob
1164
        _wmchannel = self._memobj.wmchannel
1165
        basic = RadioSettingGroup("basic", "Basic Settings")
1166
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
1167

    
1168
        group = RadioSettings(basic, advanced)
1169

    
1170
        rs = RadioSetting("squelch", "Carrier Squelch Level",
1171
                          RadioSettingValueInteger(0, 9, _settings.squelch))
1172
        basic.append(rs)
1173

    
1174
        rs = RadioSetting("save", "Battery Saver",
1175
                          RadioSettingValueList(
1176
                              SAVE_LIST, SAVE_LIST[_settings.save]))
1177
        basic.append(rs)
1178

    
1179
        rs = RadioSetting("vox", "VOX Sensitivity",
1180
                          RadioSettingValueList(
1181
                              VOX_LIST, VOX_LIST[_settings.vox]))
1182
        advanced.append(rs)
1183

    
1184
        if self.MODEL == "UV-6":
1185
            # NOTE: The UV-6 calls this byte voxenable, but the UV-5R calls it
1186
            # autolk. Since this is a minor difference, it will be referred to
1187
            # by the wrong name for the UV-6.
1188
            rs = RadioSetting("autolk", "Vox",
1189
                              RadioSettingValueBoolean(_settings.autolk))
1190
            advanced.append(rs)
1191

    
1192
        if self.MODEL != "UV-6":
1193
            rs = RadioSetting("abr", "Backlight Timeout",
1194
                              RadioSettingValueInteger(0, 24, _settings.abr))
1195
            basic.append(rs)
1196

    
1197
        rs = RadioSetting("tdr", "Dual Watch",
1198
                          RadioSettingValueBoolean(_settings.tdr))
1199
        advanced.append(rs)
1200

    
1201
        if self.MODEL == "UV-6":
1202
            rs = RadioSetting("tdrch", "Dual Watch Channel",
1203
                              RadioSettingValueList(
1204
                                  TDRCH_LIST, TDRCH_LIST[_settings.tdrch]))
1205
            advanced.append(rs)
1206

    
1207
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
1208
                              RadioSettingValueBoolean(_settings.tdrab))
1209
            advanced.append(rs)
1210
        else:
1211
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
1212
                              RadioSettingValueList(
1213
                                  TDRAB_LIST, TDRAB_LIST[_settings.tdrab]))
1214
            advanced.append(rs)
1215

    
1216
        if self.MODEL == "UV-6":
1217
            rs = RadioSetting("alarm", "Alarm Sound",
1218
                              RadioSettingValueBoolean(_settings.alarm))
1219
            advanced.append(rs)
1220

    
1221
        if _settings.almod > 0x02:
1222
            val = 0x01
1223
        else:
1224
            val = _settings.almod
1225
        rs = RadioSetting("almod", "Alarm Mode",
1226
                          RadioSettingValueList(
1227
                              ALMOD_LIST, ALMOD_LIST[val]))
1228
        advanced.append(rs)
1229

    
1230
        rs = RadioSetting("beep", "Beep",
1231
                          RadioSettingValueBoolean(_settings.beep))
1232
        basic.append(rs)
1233

    
1234
        rs = RadioSetting("timeout", "Timeout Timer",
1235
                          RadioSettingValueList(
1236
                              TIMEOUT_LIST, TIMEOUT_LIST[_settings.timeout]))
1237
        basic.append(rs)
1238

    
1239
        if ((self._is_orig() and self._my_version() < 251) or
1240
                (self.MODEL in ["TI-F8+", "TS-T9+"])):
1241
            rs = RadioSetting("voice", "Voice",
1242
                              RadioSettingValueBoolean(_settings.voice))
1243
            advanced.append(rs)
1244
        else:
1245
            rs = RadioSetting("voice", "Voice",
1246
                              RadioSettingValueList(
1247
                                  VOICE_LIST, VOICE_LIST[_settings.voice]))
1248
            advanced.append(rs)
1249

    
1250
        rs = RadioSetting("screv", "Scan Resume",
1251
                          RadioSettingValueList(
1252
                              RESUME_LIST, RESUME_LIST[_settings.screv]))
1253
        advanced.append(rs)
1254

    
1255
        if self.MODEL != "UV-6":
1256
            rs = RadioSetting("mdfa", "Display Mode (A)",
1257
                              RadioSettingValueList(
1258
                                  MODE_LIST, MODE_LIST[_settings.mdfa]))
1259
            basic.append(rs)
1260

    
1261
            rs = RadioSetting("mdfb", "Display Mode (B)",
1262
                              RadioSettingValueList(
1263
                                  MODE_LIST, MODE_LIST[_settings.mdfb]))
1264
            basic.append(rs)
1265

    
1266
        rs = RadioSetting("bcl", "Busy Channel Lockout",
1267
                          RadioSettingValueBoolean(_settings.bcl))
1268
        advanced.append(rs)
1269

    
1270
        if self.MODEL != "UV-6":
1271
            rs = RadioSetting("autolk", "Automatic Key Lock",
1272
                              RadioSettingValueBoolean(_settings.autolk))
1273
            advanced.append(rs)
1274

    
1275
        rs = RadioSetting("fmradio", "Broadcast FM Radio",
1276
                          RadioSettingValueBoolean(_settings.fmradio))
1277
        advanced.append(rs)
1278

    
1279
        if self.MODEL != "UV-6":
1280
            rs = RadioSetting("wtled", "Standby LED Color",
1281
                              RadioSettingValueList(
1282
                                  COLOR_LIST, COLOR_LIST[_settings.wtled]))
1283
            basic.append(rs)
1284

    
1285
            rs = RadioSetting("rxled", "RX LED Color",
1286
                              RadioSettingValueList(
1287
                                  COLOR_LIST, COLOR_LIST[_settings.rxled]))
1288
            basic.append(rs)
1289

    
1290
            rs = RadioSetting("txled", "TX LED Color",
1291
                              RadioSettingValueList(
1292
                                  COLOR_LIST, COLOR_LIST[_settings.txled]))
1293
            basic.append(rs)
1294

    
1295
        if isinstance(self, BaofengUV82Radio):
1296
            rs = RadioSetting("roger", "Roger Beep (TX)",
1297
                              RadioSettingValueBoolean(_settings.roger))
1298
            basic.append(rs)
1299
            rs = RadioSetting("rogerrx", "Roger Beep (RX)",
1300
                              RadioSettingValueList(
1301
                                  ROGERRX_LIST,
1302
                                  ROGERRX_LIST[_settings.rogerrx]))
1303
            basic.append(rs)
1304
        else:
1305
            rs = RadioSetting("roger", "Roger Beep",
1306
                              RadioSettingValueBoolean(_settings.roger))
1307
            basic.append(rs)
1308

    
1309
        rs = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)",
1310
                          RadioSettingValueBoolean(_settings.ste))
1311
        advanced.append(rs)
1312

    
1313
        rs = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)",
1314
                          RadioSettingValueList(
1315
                              RPSTE_LIST, RPSTE_LIST[_settings.rpste]))
1316
        advanced.append(rs)
1317

    
1318
        rs = RadioSetting("rptrl", "STE Repeater Delay",
1319
                          RadioSettingValueList(
1320
                              STEDELAY_LIST, STEDELAY_LIST[_settings.rptrl]))
1321
        advanced.append(rs)
1322

    
1323
        if self.MODEL != "UV-6":
1324
            rs = RadioSetting("reset", "RESET Menu",
1325
                              RadioSettingValueBoolean(_settings.reset))
1326
            advanced.append(rs)
1327

    
1328
            rs = RadioSetting("menu", "All Menus",
1329
                              RadioSettingValueBoolean(_settings.menu))
1330
            advanced.append(rs)
1331

    
1332
        if self.MODEL == "F-11":
1333
            # this is an F-11 only feature
1334
            rs = RadioSetting("vfomrlock", "VFO/MR Button",
1335
                              RadioSettingValueBoolean(_settings.vfomrlock))
1336
            advanced.append(rs)
1337

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

    
1344
        if self.MODEL == "UV-82HP":
1345
            # this is a UV-82HP only feature
1346
            rs = RadioSetting(
1347
                "vfomrlock", "VFO/MR Switching (BTech UV-82HP only)",
1348
                RadioSettingValueBoolean(_settings.vfomrlock))
1349
            advanced.append(rs)
1350

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

    
1357
        if self.MODEL == "UV-82HP":
1358
            # this is an UV-82HP only feature
1359
            rs = RadioSetting("singleptt", "Single PTT (BTech UV-82HP only)",
1360
                              RadioSettingValueBoolean(_settings.singleptt))
1361
            advanced.append(rs)
1362

    
1363
        if self.MODEL == "UV-82HP":
1364
            # this is an UV-82HP only feature
1365
            rs = RadioSetting(
1366
                "tdrch", "Tone Burst Frequency (BTech UV-82HP only)",
1367
                RadioSettingValueList(RTONE_LIST, RTONE_LIST[_settings.tdrch]))
1368
            advanced.append(rs)
1369

    
1370
        def set_range_flag(setting):
1371
            val = [85, 115, 101, 65, 116, 79, 119, 110, 82, 105, 115, 107]
1372
            if [ord(x) for x in str(setting.value).strip()] == val:
1373
                self._all_range_flag = True
1374
            else:
1375
                self._all_range_flag = False
1376
            LOG.debug('Set range flag to %s' % self._all_range_flag)
1377

    
1378
        rs = RadioSetting("allrange", "Range Override Parameter",
1379
                          RadioSettingValueString(0, 12, "Default"))
1380
        rs.set_apply_callback(set_range_flag)
1381
        advanced.append(rs)
1382

    
1383
        if len(self._mmap.get_packed()) == 0x1808:
1384
            # Old image, without aux block
1385
            return group
1386

    
1387
        if self.MODEL != "UV-6":
1388
            other = RadioSettingGroup("other", "Other Settings")
1389
            group.append(other)
1390

    
1391
            def _filter(name):
1392
                filtered = ""
1393
                for char in str(name):
1394
                    if char in chirp_common.CHARSET_ASCII:
1395
                        filtered += char
1396
                    else:
1397
                        filtered += " "
1398
                return filtered
1399

    
1400
            _msg = self._memobj.firmware_msg
1401
            val = RadioSettingValueString(0, 7, _filter(_msg.line1))
1402
            val.set_mutable(False)
1403
            rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
1404
            other.append(rs)
1405

    
1406
            val = RadioSettingValueString(0, 7, _filter(_msg.line2))
1407
            val.set_mutable(False)
1408
            rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
1409
            other.append(rs)
1410

    
1411
            _msg = self._memobj.sixpoweron_msg
1412
            rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1",
1413
                              RadioSettingValueString(
1414
                                  0, 7, _filter(_msg.line1)))
1415
            other.append(rs)
1416
            rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2",
1417
                              RadioSettingValueString(
1418
                                  0, 7, _filter(_msg.line2)))
1419
            other.append(rs)
1420

    
1421
            _msg = self._memobj.poweron_msg
1422
            rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
1423
                              RadioSettingValueString(
1424
                                  0, 7, _filter(_msg.line1)))
1425
            other.append(rs)
1426
            rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
1427
                              RadioSettingValueString(
1428
                                  0, 7, _filter(_msg.line2)))
1429
            other.append(rs)
1430

    
1431
            rs = RadioSetting("ponmsg", "Power-On Message",
1432
                              RadioSettingValueList(
1433
                                  PONMSG_LIST, PONMSG_LIST[_settings.ponmsg]))
1434
            other.append(rs)
1435

    
1436
            if self._is_orig():
1437
                limit = "limits_old"
1438
            else:
1439
                limit = "limits_new"
1440

    
1441
            vhf_limit = getattr(self._memobj, limit).vhf
1442
            rs = RadioSetting("%s.vhf.lower" % limit, "VHF Lower Limit (MHz)",
1443
                              RadioSettingValueInteger(1, 1000,
1444
                                                       vhf_limit.lower))
1445
            other.append(rs)
1446

    
1447
            rs = RadioSetting("%s.vhf.upper" % limit, "VHF Upper Limit (MHz)",
1448
                              RadioSettingValueInteger(1, 1000,
1449
                                                       vhf_limit.upper))
1450
            other.append(rs)
1451

    
1452
            rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled",
1453
                              RadioSettingValueBoolean(vhf_limit.enable))
1454
            other.append(rs)
1455

    
1456
            uhf_limit = getattr(self._memobj, limit).uhf
1457
            rs = RadioSetting("%s.uhf.lower" % limit, "UHF Lower Limit (MHz)",
1458
                              RadioSettingValueInteger(1, 1000,
1459
                                                       uhf_limit.lower))
1460
            other.append(rs)
1461
            rs = RadioSetting("%s.uhf.upper" % limit, "UHF Upper Limit (MHz)",
1462
                              RadioSettingValueInteger(1, 1000,
1463
                                                       uhf_limit.upper))
1464
            other.append(rs)
1465
            rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled",
1466
                              RadioSettingValueBoolean(uhf_limit.enable))
1467
            other.append(rs)
1468

    
1469
        if self.MODEL != "UV-6":
1470
            workmode = RadioSettingGroup("workmode", "Work Mode Settings")
1471
            group.append(workmode)
1472

    
1473
            rs = RadioSetting("displayab", "Display",
1474
                              RadioSettingValueList(
1475
                                  AB_LIST, AB_LIST[_settings.displayab]))
1476
            workmode.append(rs)
1477

    
1478
            rs = RadioSetting("workmode", "VFO/MR Mode",
1479
                              RadioSettingValueList(
1480
                                  WORKMODE_LIST,
1481
                                  WORKMODE_LIST[_settings.workmode]))
1482
            workmode.append(rs)
1483

    
1484
            rs = RadioSetting("keylock", "Keypad Lock",
1485
                              RadioSettingValueBoolean(_settings.keylock))
1486
            workmode.append(rs)
1487

    
1488
            rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
1489
                              RadioSettingValueInteger(0, 127,
1490
                                                       _wmchannel.mrcha))
1491
            workmode.append(rs)
1492

    
1493
            rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
1494
                              RadioSettingValueInteger(0, 127,
1495
                                                       _wmchannel.mrchb))
1496
            workmode.append(rs)
1497

    
1498
            def convert_bytes_to_freq(bytes):
1499
                real_freq = 0
1500
                for byte in bytes:
1501
                    real_freq = (real_freq * 10) + byte
1502
                return chirp_common.format_freq(real_freq * 10)
1503

    
1504
            def my_validate(value):
1505
                value = chirp_common.parse_freq(value)
1506
                if 17400000 <= value and value < 40000000:
1507
                    msg = ("Can't be between 174.00000-400.00000")
1508
                    raise InvalidValueError(msg)
1509
                return chirp_common.format_freq(value)
1510

    
1511
            def apply_freq(setting, obj):
1512
                value = chirp_common.parse_freq(str(setting.value)) / 10
1513
                obj.band = value >= 40000000
1514
                for i in range(7, -1, -1):
1515
                    obj.freq[i] = value % 10
1516
                    value /= 10
1517

    
1518
            val1a = RadioSettingValueString(0, 10,
1519
                                            convert_bytes_to_freq(_vfoa.freq))
1520
            val1a.set_validate_callback(my_validate)
1521
            rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a)
1522
            rs.set_apply_callback(apply_freq, _vfoa)
1523
            workmode.append(rs)
1524

    
1525
            val1b = RadioSettingValueString(0, 10,
1526
                                            convert_bytes_to_freq(_vfob.freq))
1527
            val1b.set_validate_callback(my_validate)
1528
            rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b)
1529
            rs.set_apply_callback(apply_freq, _vfob)
1530
            workmode.append(rs)
1531

    
1532
            rs = RadioSetting("vfoa.sftd", "VFO A Shift",
1533
                              RadioSettingValueList(
1534
                                  SHIFTD_LIST, SHIFTD_LIST[_vfoa.sftd]))
1535
            workmode.append(rs)
1536

    
1537
            rs = RadioSetting("vfob.sftd", "VFO B Shift",
1538
                              RadioSettingValueList(
1539
                                  SHIFTD_LIST, SHIFTD_LIST[_vfob.sftd]))
1540
            workmode.append(rs)
1541

    
1542
            def convert_bytes_to_offset(bytes):
1543
                real_offset = 0
1544
                for byte in bytes:
1545
                    real_offset = (real_offset * 10) + byte
1546
                return chirp_common.format_freq(real_offset * 1000)
1547

    
1548
            def apply_offset(setting, obj):
1549
                value = chirp_common.parse_freq(str(setting.value)) / 1000
1550
                for i in range(5, -1, -1):
1551
                    obj.offset[i] = value % 10
1552
                    value /= 10
1553

    
1554
            val1a = RadioSettingValueString(
1555
                0, 10, convert_bytes_to_offset(_vfoa.offset))
1556
            rs = RadioSetting("vfoa.offset",
1557
                              "VFO A Offset (0.0-999.999)", val1a)
1558
            rs.set_apply_callback(apply_offset, _vfoa)
1559
            workmode.append(rs)
1560

    
1561
            val1b = RadioSettingValueString(
1562
                0, 10, convert_bytes_to_offset(_vfob.offset))
1563
            rs = RadioSetting("vfob.offset",
1564
                              "VFO B Offset (0.0-999.999)", val1b)
1565
            rs.set_apply_callback(apply_offset, _vfob)
1566
            workmode.append(rs)
1567

    
1568
            if self._tri_power:
1569
                if _vfoa.txpower3 > 0x02:
1570
                    val = 0x00
1571
                else:
1572
                    val = _vfoa.txpower3
1573
                rs = RadioSetting("vfoa.txpower3", "VFO A Power",
1574
                                  RadioSettingValueList(
1575
                                      TXPOWER3_LIST,
1576
                                      TXPOWER3_LIST[val]))
1577
                workmode.append(rs)
1578

    
1579
                if _vfob.txpower3 > 0x02:
1580
                    val = 0x00
1581
                else:
1582
                    val = _vfob.txpower3
1583
                rs = RadioSetting("vfob.txpower3", "VFO B Power",
1584
                                  RadioSettingValueList(
1585
                                      TXPOWER3_LIST,
1586
                                      TXPOWER3_LIST[val]))
1587
                workmode.append(rs)
1588
            else:
1589
                rs = RadioSetting("vfoa.txpower", "VFO A Power",
1590
                                  RadioSettingValueList(
1591
                                      TXPOWER_LIST,
1592
                                      TXPOWER_LIST[_vfoa.txpower]))
1593
                workmode.append(rs)
1594

    
1595
                rs = RadioSetting("vfob.txpower", "VFO B Power",
1596
                                  RadioSettingValueList(
1597
                                      TXPOWER_LIST,
1598
                                      TXPOWER_LIST[_vfob.txpower]))
1599
                workmode.append(rs)
1600

    
1601
            rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth",
1602
                              RadioSettingValueList(
1603
                                  BANDWIDTH_LIST,
1604
                                  BANDWIDTH_LIST[_vfoa.widenarr]))
1605
            workmode.append(rs)
1606

    
1607
            rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
1608
                              RadioSettingValueList(
1609
                                  BANDWIDTH_LIST,
1610
                                  BANDWIDTH_LIST[_vfob.widenarr]))
1611
            workmode.append(rs)
1612

    
1613
            rs = RadioSetting("vfoa.scode", "VFO A PTT-ID",
1614
                              RadioSettingValueList(
1615
                                  PTTIDCODE_LIST, PTTIDCODE_LIST[_vfoa.scode]))
1616
            workmode.append(rs)
1617

    
1618
            rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
1619
                              RadioSettingValueList(
1620
                                  PTTIDCODE_LIST, PTTIDCODE_LIST[_vfob.scode]))
1621
            workmode.append(rs)
1622

    
1623
            if not self._is_orig():
1624
                rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
1625
                                  RadioSettingValueList(
1626
                                      STEP291_LIST, STEP291_LIST[_vfoa.step]))
1627
                workmode.append(rs)
1628
                rs = RadioSetting("vfob.step", "VFO B Tuning Step",
1629
                                  RadioSettingValueList(
1630
                                      STEP291_LIST, STEP291_LIST[_vfob.step]))
1631
                workmode.append(rs)
1632
            else:
1633
                rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
1634
                                  RadioSettingValueList(
1635
                                      STEP_LIST, STEP_LIST[_vfoa.step]))
1636
                workmode.append(rs)
1637
                rs = RadioSetting("vfob.step", "VFO B Tuning Step",
1638
                                  RadioSettingValueList(
1639
                                      STEP_LIST, STEP_LIST[_vfob.step]))
1640
                workmode.append(rs)
1641

    
1642
        fm_preset = RadioSettingGroup("fm_preset", "FM Radio Preset")
1643
        group.append(fm_preset)
1644

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

    
1666
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1667
        group.append(dtmf)
1668

    
1669
        if str(self._memobj.firmware_msg.line1) == "HN5RV01":
1670
            dtmfchars = "0123456789ABCD*#"
1671
        else:
1672
            dtmfchars = "0123456789 *#ABCD"
1673

    
1674
        for i in range(0, 15):
1675
            _codeobj = self._memobj.pttid[i].code
1676
            _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1677
            val = RadioSettingValueString(0, 5, _code, False)
1678
            val.set_charset(dtmfchars)
1679
            rs = RadioSetting("pttid/%i.code" % i,
1680
                              "PTT ID Code %i" % (i + 1), val)
1681

    
1682
            def apply_code(setting, obj):
1683
                code = []
1684
                for j in range(0, 5):
1685
                    try:
1686
                        code.append(dtmfchars.index(str(setting.value)[j]))
1687
                    except IndexError:
1688
                        code.append(0xFF)
1689
                obj.code = code
1690
            rs.set_apply_callback(apply_code, self._memobj.pttid[i])
1691
            dtmf.append(rs)
1692

    
1693
        dtmfcharsani = "0123456789"
1694

    
1695
        _codeobj = self._memobj.ani.code
1696
        _code = "".join([dtmfcharsani[x] for x in _codeobj if int(x) < 0x1F])
1697
        val = RadioSettingValueString(0, 5, _code, False)
1698
        val.set_charset(dtmfcharsani)
1699
        rs = RadioSetting("ani.code", "ANI Code", val)
1700

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

    
1712
        rs = RadioSetting("ani.aniid", "ANI ID",
1713
                          RadioSettingValueList(PTTID_LIST,
1714
                                                PTTID_LIST[_ani.aniid]))
1715
        dtmf.append(rs)
1716

    
1717
        _codeobj = self._memobj.ani.alarmcode
1718
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1719
        val = RadioSettingValueString(0, 3, _code, False)
1720
        val.set_charset(dtmfchars)
1721
        rs = RadioSetting("ani.alarmcode", "Alarm Code", val)
1722

    
1723
        def apply_code(setting, obj):
1724
            alarmcode = []
1725
            for j in range(0, 3):
1726
                try:
1727
                    alarmcode.append(dtmfchars.index(str(setting.value)[j]))
1728
                except IndexError:
1729
                    alarmcode.append(0xFF)
1730
            obj.alarmcode = alarmcode
1731
        rs.set_apply_callback(apply_code, _ani)
1732
        dtmf.append(rs)
1733

    
1734
        rs = RadioSetting("dtmfst", "DTMF Sidetone",
1735
                          RadioSettingValueList(DTMFST_LIST,
1736
                                                DTMFST_LIST[_settings.dtmfst]))
1737
        dtmf.append(rs)
1738

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

    
1748
        if _ani.dtmfoff > 0xC3:
1749
            val = 0x00
1750
        else:
1751
            val = _ani.dtmfoff
1752
        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
1753
                          RadioSettingValueList(DTMFSPEED_LIST,
1754
                                                DTMFSPEED_LIST[val]))
1755
        dtmf.append(rs)
1756

    
1757
        rs = RadioSetting("pttlt", "PTT ID Delay",
1758
                          RadioSettingValueInteger(0, 50, _settings.pttlt))
1759
        dtmf.append(rs)
1760

    
1761
        if not self._is_orig() and self._aux_block:
1762
            service = RadioSettingGroup("service", "Service Settings")
1763
            group.append(service)
1764

    
1765
            for band in ["vhf", "uhf"]:
1766
                for index in range(0, 10):
1767
                    key = "squelch_new.%s.sql%i" % (band, index)
1768
                    if band == "vhf":
1769
                        _obj = self._memobj.squelch_new.vhf
1770
                    elif band == "uhf":
1771
                        _obj = self._memobj.squelch_new.uhf
1772
                    name = "%s Squelch %i" % (band.upper(), index)
1773
                    rs = RadioSetting(key, name,
1774
                                      RadioSettingValueInteger(
1775
                                          0, 123,
1776
                                          getattr(_obj, "sql%i" % (index))))
1777
                    service.append(rs)
1778

    
1779
        return group
1780

    
1781
    def get_settings(self):
1782
        try:
1783
            return self._get_settings()
1784
        except:
1785
            import traceback
1786
            LOG.error("Failed to parse settings: %s", traceback.format_exc())
1787
            return None
1788

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

    
1816
                    if element.has_apply_callback():
1817
                        LOG.debug("Using apply callback")
1818
                        element.run_apply_callback()
1819
                    elif element.value.get_mutable():
1820
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1821
                        setattr(obj, setting, element.value)
1822
                except Exception as e:
1823
                    LOG.debug(element.get_name())
1824
                    raise
1825

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

    
1842

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

    
1847

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

    
1852

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

    
1857

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

    
1862

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

    
1867

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

    
1872

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

    
1877

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

    
1882

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

    
1888

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

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

    
1900

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

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

    
1914

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

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

    
1928

    
1929
@directory.register
1930
class BaofengUV6Radio(BaofengUV5R):
1931

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

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

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

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

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

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

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

    
1962

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

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

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

    
1982

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

    
1987

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

    
1992

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

    
1997

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

    
2002

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

    
2007

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

    
2012

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

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

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

    
2036

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

    
2041

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

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

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

    
2064

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

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

    
2077
    @classmethod
2078
    def match_model(cls, filename, filedata):
2079
        return False
2080

    
2081

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

    
2087
    vhftx = [144000000, 148000000]
2088
    uhftx = [420000000, 450000000]
2089

    
2090
    def validate_memory(self, mem):
2091
        msgs = super().validate_memory(mem)
2092

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

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

    
2101
        return msgs
2102

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

    
2107
    @classmethod
2108
    def match_model(cls, filename, filedata):
2109
        return False
2110

    
2111

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

    
2117
    _basetype = BASETYPE_UV5R
2118
    _idents = [UV5R_MODEL_UV5G]
2119

    
2120
    @classmethod
2121
    def match_model(cls, filename, filedata):
2122
        return False
(16-16/41)