Project

General

Profile

Bug #10406 » uv5r_fm_preset_add_3rd_method_2nd_try.py

Jim Unroe, 03/02/2023 02:26 AM

 
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 os
21
import logging
22

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

    
31
LOG = logging.getLogger(__name__)
32

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

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

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

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

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

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

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

    
198
#seekto 0x0F56;
199
u16 fm_presets;
200

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

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

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

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

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

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

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

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

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

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

    
275
"""
276

    
277
# 0x1EC0 - 0x2000
278

    
279
vhf_220_radio = b"\x02"
280

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

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

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

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

    
369

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

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

    
386

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

    
390

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

    
394

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

    
399

    
400
def _firmware_version_from_image(radio):
401
    version = _firmware_version_from_data(
402
        radio.get_mmap().get_byte_compatible(),
403
        radio._fw_ver_file_start,
404
        radio._fw_ver_file_stop)
405
    LOG.debug("_firmware_version_from_image: " + util.hexprint(version))
406
    return version
407

    
408

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

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

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

    
424
    serial.write(b"\x02")
425

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

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

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

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

    
469
    return ident
470

    
471

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

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

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

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

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

    
499
    radio.pipe.write(b"\x06")
500
    time.sleep(0.05)
501

    
502
    return chunk
503

    
504

    
505
def _get_radio_firmware_version(radio):
506
    if radio.MODEL == "BJ-UV55":
507
        block = _read_block(radio, 0x1FF0, 0x40, True)
508
        version = block[0:6]
509
    else:
510
        block1 = _read_block(radio, 0x1EC0, 0x40, True)
511
        block2 = _read_block(radio, 0x1F00, 0x40, False)
512
        block = block1 + block2
513
        version = block[48:62]
514
    return version
515

    
516

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

    
522

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

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

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

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

    
553

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

    
557
    radio_version = _get_radio_firmware_version(radio)
558
    LOG.info("Radio Version is %s" % repr(radio_version))
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 = _get_radio_firmware_version(radio)
632
    LOG.info("Image Version is %s" % repr(image_version))
633
    LOG.info("Radio Version is %s" % repr(radio_version))
634

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

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

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

    
671
    if not radio._aux_block:
672
        image_matched_radio = True
673

    
674
    # Main block
675
    mmap = radio.get_mmap().get_byte_compatible()
676
    for start_addr, end_addr in ranges_main:
677
        for i in range(start_addr, end_addr, 0x10):
678
            _send_block(radio, i - 0x08, mmap[i:i + 0x10])
679
            _do_status(radio, i)
680
        _do_status(radio, radio.get_memsize())
681

    
682
    if len(mmap.get_packed()) == 0x1808:
683
        LOG.info("Old image, not writing aux block")
684
        return  # Old image, no aux block
685

    
686
    # Auxiliary block at radio address 0x1EC0, our offset 0x1808
687
    for start_addr, end_addr in ranges_aux:
688
        for i in range(start_addr, end_addr, 0x10):
689
            addr = 0x1808 + (i - 0x1EC0)
690
            _send_block(radio, i, mmap[addr:addr + 0x10])
691

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

    
699
    if radio._all_range_flag:
700
        radio._all_range_flag = False
701
        LOG.warning('Sending all ranges to radio has completed')
702
        raise errors.RadioError(
703
            "This is NOT an error.\n"
704
            "The upload has finished successfully.\n"
705
            "Please restart CHIRP.")
706

    
707
UV5R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=4.00),
708
                     chirp_common.PowerLevel("Low",  watts=1.00)]
709

    
710
UV5R_POWER_LEVELS3 = [chirp_common.PowerLevel("High", watts=8.00),
711
                      chirp_common.PowerLevel("Med",  watts=4.00),
712
                      chirp_common.PowerLevel("Low",  watts=1.00)]
713

    
714
UV5R_DTCS = tuple(sorted(chirp_common.DTCS_CODES + (645,)))
715

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

    
719

    
720
def model_match(cls, data):
721
    """Match the opened/downloaded image to the correct version"""
722

    
723
    if len(data) == 0x1950:
724
        rid = data[0x1948:0x1950]
725
        return rid.startswith(cls.MODEL)
726
    elif len(data) == 0x1948:
727
        rid = data[cls._fw_ver_file_start:cls._fw_ver_file_stop]
728
        if any(type in rid for type in cls._basetype):
729
            return True
730
    else:
731
        return False
732

    
733

    
734
class BaofengUV5R(chirp_common.CloneModeRadio):
735

    
736
    """Baofeng UV-5R"""
737
    VENDOR = "Baofeng"
738
    MODEL = "UV-5R"
739
    BAUD_RATE = 9600
740
    NEEDS_COMPAT_SERIAL = False
741

    
742
    _memsize = 0x1808
743
    _basetype = BASETYPE_UV5R
744
    _idents = [UV5R_MODEL_291,
745
               UV5R_MODEL_ORIG
746
               ]
747
    _vhf_range = (130000000, 176000000)
748
    _220_range = (220000000, 260000000)
749
    _uhf_range = (400000000, 520000000)
750
    _aux_block = True
751
    _tri_power = False
752
    _gmrs = False
753
    _mem_params = (0x1828  # poweron_msg offset
754
                   )
755
    # offset of fw version in image file
756
    _fw_ver_file_start = 0x1838
757
    _fw_ver_file_stop = 0x1846
758

    
759
    _ranges_main = [
760
                    (0x0008, 0x1808),
761
                   ]
762
    _ranges_aux = [
763
                   (0x1EC0, 0x2000),
764
                  ]
765
    _valid_chars = UV5R_CHARSET
766

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

    
793
    def get_features(self):
794
        rf = chirp_common.RadioFeatures()
795
        rf.has_settings = True
796
        rf.has_bank = False
797
        rf.has_cross = True
798
        rf.has_rx_dtcs = True
799
        rf.has_tuning_step = False
800
        rf.can_odd_split = True
801
        rf.valid_name_length = 7
802
        rf.valid_characters = self._valid_chars
803
        rf.valid_skips = ["", "S"]
804
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
805
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
806
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
807
        rf.valid_power_levels = UV5R_POWER_LEVELS
808
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
809
        rf.valid_modes = ["FM", "NFM"]
810
        rf.valid_tuning_steps = STEPS
811
        rf.valid_dtcs_codes = UV5R_DTCS
812

    
813
        normal_bands = [self._vhf_range, self._uhf_range]
814
        rax_bands = [self._vhf_range, self._220_range]
815

    
816
        if self._mmap is None:
817
            rf.valid_bands = [normal_bands[0], rax_bands[1], normal_bands[1]]
818
        elif not self._is_orig() and self._my_upper_band() == vhf_220_radio:
819
            rf.valid_bands = rax_bands
820
        else:
821
            rf.valid_bands = normal_bands
822
        rf.memory_bounds = (0, 127)
823
        return rf
824

    
825
    @classmethod
826
    def match_model(cls, filedata, filename):
827
        match_size = False
828
        match_model = False
829
        if len(filedata) in [0x1808, 0x1948, 0x1950]:
830
            match_size = True
831
        match_model = model_match(cls, filedata)
832

    
833
        if match_size and match_model:
834
            return True
835
        else:
836
            return False
837

    
838
    def process_mmap(self):
839
        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
840
        self._all_range_flag = False
841

    
842
    def sync_in(self):
843
        try:
844
            self._mmap = _do_download(self)
845
        except errors.RadioError:
846
            raise
847
        except Exception as e:
848
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
849
        self.process_mmap()
850

    
851
    def sync_out(self):
852
        try:
853
            _do_upload(self)
854
        except errors.RadioError:
855
            raise
856
        except Exception as e:
857
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
858

    
859
    def get_raw_memory(self, number):
860
        return repr(self._memobj.memory[number])
861

    
862
    def _is_txinh(self, _mem):
863
        raw_tx = ""
864
        for i in range(0, 4):
865
            raw_tx += _mem.txfreq[i].get_raw()
866
        return raw_tx == "\xFF\xFF\xFF\xFF"
867

    
868
    def _get_mem(self, number):
869
        return self._memobj.memory[number]
870

    
871
    def _get_nam(self, number):
872
        return self._memobj.names[number]
873

    
874
    def get_memory(self, number):
875
        _mem = self._get_mem(number)
876
        _nam = self._get_nam(number)
877

    
878
        mem = chirp_common.Memory()
879
        mem.number = number
880

    
881
        if _mem.get_raw()[0] == "\xff":
882
            mem.empty = True
883
            return mem
884

    
885
        mem.freq = int(_mem.rxfreq) * 10
886

    
887
        if self._is_txinh(_mem):
888
            mem.duplex = "off"
889
            mem.offset = 0
890
        elif int(_mem.rxfreq) == int(_mem.txfreq):
891
            mem.duplex = ""
892
            mem.offset = 0
893
        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
894
            mem.duplex = "split"
895
            mem.offset = int(_mem.txfreq) * 10
896
        else:
897
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
898
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
899

    
900
        for char in _nam.name:
901
            if str(char) == "\xFF":
902
                char = " "  # The UV-5R software may have 0xFF mid-name
903
            mem.name += str(char)
904
        mem.name = mem.name.rstrip()
905

    
906
        dtcs_pol = ["N", "N"]
907

    
908
        if _mem.txtone in [0, 0xFFFF]:
909
            txmode = ""
910
        elif _mem.txtone >= 0x0258:
911
            txmode = "Tone"
912
            mem.rtone = int(_mem.txtone) / 10.0
913
        elif _mem.txtone <= 0x0258:
914
            txmode = "DTCS"
915
            if _mem.txtone > 0x69:
916
                index = _mem.txtone - 0x6A
917
                dtcs_pol[0] = "R"
918
            else:
919
                index = _mem.txtone - 1
920
            mem.dtcs = UV5R_DTCS[index]
921
        else:
922
            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
923

    
924
        if _mem.rxtone in [0, 0xFFFF]:
925
            rxmode = ""
926
        elif _mem.rxtone >= 0x0258:
927
            rxmode = "Tone"
928
            mem.ctone = int(_mem.rxtone) / 10.0
929
        elif _mem.rxtone <= 0x0258:
930
            rxmode = "DTCS"
931
            if _mem.rxtone >= 0x6A:
932
                index = _mem.rxtone - 0x6A
933
                dtcs_pol[1] = "R"
934
            else:
935
                index = _mem.rxtone - 1
936
            mem.rx_dtcs = UV5R_DTCS[index]
937
        else:
938
            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
939

    
940
        if txmode == "Tone" and not rxmode:
941
            mem.tmode = "Tone"
942
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
943
            mem.tmode = "TSQL"
944
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
945
            mem.tmode = "DTCS"
946
        elif rxmode or txmode:
947
            mem.tmode = "Cross"
948
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
949

    
950
        mem.dtcs_polarity = "".join(dtcs_pol)
951

    
952
        if not _mem.scan:
953
            mem.skip = "S"
954

    
955
        if self._tri_power:
956
            levels = UV5R_POWER_LEVELS3
957
        else:
958
            levels = UV5R_POWER_LEVELS
959
        try:
960
            mem.power = levels[_mem.lowpower]
961
        except IndexError:
962
            LOG.error("Radio reported invalid power level %s (in %s)" %
963
                      (_mem.lowpower, levels))
964
            mem.power = levels[0]
965

    
966
        mem.mode = _mem.wide and "FM" or "NFM"
967

    
968
        mem.extra = RadioSettingGroup("Extra", "extra")
969

    
970
        rs = RadioSetting("bcl", "BCL",
971
                          RadioSettingValueBoolean(_mem.bcl))
972
        mem.extra.append(rs)
973

    
974
        rs = RadioSetting("pttid", "PTT ID",
975
                          RadioSettingValueList(PTTID_LIST,
976
                                                PTTID_LIST[_mem.pttid]))
977
        mem.extra.append(rs)
978

    
979
        rs = RadioSetting("scode", "PTT ID Code",
980
                          RadioSettingValueList(PTTIDCODE_LIST,
981
                                                PTTIDCODE_LIST[_mem.scode]))
982
        mem.extra.append(rs)
983

    
984
        immutable = []
985

    
986
        if self._gmrs:
987
            if mem.number >= 1 and mem.number <= 30:
988
                GMRS_FREQ = GMRS_FREQS[mem.number - 1]
989
                mem.freq = GMRS_FREQ
990
                immutable = ["empty", "freq"]
991
            if mem.number >= 1 and mem.number <= 7:
992
                mem.duplex == ''
993
                mem.offset = 0
994
                immutable += ["duplex", "offset"]
995
            elif mem.number >= 8 and mem.number <= 14:
996
                mem.duplex == ''
997
                mem.offset = 0
998
                mem.mode = "NFM"
999
                mem.power = UV5R_POWER_LEVELS[1]
1000
                immutable += ["duplex", "offset", "mode", "power"]
1001
            elif mem.number >= 15 and mem.number <= 22:
1002
                mem.duplex == ''
1003
                mem.offset = 0
1004
                immutable += ["duplex", "offset"]
1005
            elif mem.number >= 23 and mem.number <= 30:
1006
                mem.duplex == '+'
1007
                mem.offset = 5000000
1008
                immutable += ["duplex", "offset"]
1009
            else:
1010
                mem.duplex = 'off'
1011
                mem.offset = 0
1012
                immutable = ["duplex", "offset"]
1013
        if self.MODEL == "GT-5R":
1014
            if not ((mem.freq >= self.vhftx[0] and mem.freq < self.vhftx[1]) or
1015
                    (mem.freq >= self.uhftx[0] and mem.freq < self.uhftx[1])):
1016
                mem.duplex = 'off'
1017
                mem.offset = 0
1018
                immutable = ["duplex", "offset"]
1019

    
1020
        mem.immutable = immutable
1021

    
1022
        return mem
1023

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

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

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

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

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

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

    
1055
        _mem.rxfreq = mem.freq / 10
1056

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

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

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

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

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

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

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

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

    
1131
    def _is_orig(self):
1132
        version_tag = _firmware_version_from_image(self)
1133
        LOG.debug("@_is_orig, version_tag: %s", util.hexprint(version_tag))
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
    _gmrs = True
2120

    
2121
    def validate_memory(self, mem):
2122
        msgs = super().validate_memory(mem)
2123

    
2124
        _msg_duplex = 'Duplex must be "off" for this frequency'
2125
        _msg_offset = 'Only simplex or +5MHz offset allowed on GMRS'
2126

    
2127
        if not (mem.number >= 1 and mem.number <= 30):
2128
            if mem.duplex != "off":
2129
                msgs.append(chirp_common.ValidationWarning(_msg_duplex))
2130

    
2131
        return msgs
2132

    
2133
    def check_set_memory_immutable_policy(self, existing, new):
2134
        existing.immutable = []
2135
        super().check_set_memory_immutable_policy(existing, new)
2136

    
2137
    @classmethod
2138
    def match_model(cls, filename, filedata):
2139
        return False
(10-10/10)