Project

General

Profile

Feature #10505 » uv5r_aux_mem_bug_workaround.py

Jim Unroe, 07/09/2023 12:35 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 0x0F48;     // some new radios do not support this feature
197
char bcstfmlo[14];  // broadcast FM 65-75 MHz band
198
u16 fm_presets;     // broadcast FM frequency
199
char bcstfmhi[14];  // broadcast FM 75-108 MHz band
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
GMRS_FREQS1 = [462562500, 462587500, 462612500, 462637500, 462662500,
328
               462687500, 462712500]
329
GMRS_FREQS2 = [467562500, 467587500, 467612500, 467637500, 467662500,
330
               467687500, 467712500]
331
GMRS_FREQS3 = [462550000, 462575000, 462600000, 462625000, 462650000,
332
               462675000, 462700000, 462725000]
333
GMRS_FREQS = GMRS_FREQS1 + GMRS_FREQS2 + GMRS_FREQS3 * 2
334

    
335

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

    
343

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

    
353

    
354
def _upper_band_from_data(data):
355
    return data[0x03:0x04]
356

    
357

    
358
def _upper_band_from_image(radio):
359
    return _upper_band_from_data(radio.get_mmap())
360

    
361

    
362
def _firmware_version_from_data(data, version_start, version_stop):
363
    version_tag = data[version_start:version_stop]
364
    return version_tag
365

    
366

    
367
def _firmware_version_from_image(radio):
368
    version = _firmware_version_from_data(
369
        radio.get_mmap().get_byte_compatible(),
370
        radio._fw_ver_file_start,
371
        radio._fw_ver_file_stop)
372
    return version
373

    
374

    
375
def _do_ident(radio, magic, secondack=True):
376
    serial = radio.pipe
377
    serial.timeout = 1
378

    
379
    LOG.info("Sending Magic: %s" % util.hexprint(magic))
380
    for byte in magic:
381
        serial.write(bytes([byte]))
382
        time.sleep(0.01)
383
    ack = serial.read(1)
384

    
385
    if ack != b"\x06":
386
        if ack:
387
            LOG.debug(repr(ack))
388
        raise errors.RadioError("Radio did not respond")
389

    
390
    serial.write(b"\x02")
391

    
392
    # Until recently, the "ident" returned by the radios supported by this
393
    # driver have always been 8 bytes long. The image structure is the 8 byte
394
    # "ident" followed by the downloaded memory data. So all of the settings
395
    # structures are offset by 8 bytes. The ident returned from a UV-6 radio
396
    # can be 8 bytes (original model) or now 12 bytes.
397
    #
398
    # To accommodate this, the "ident" is now read one byte at a time until the
399
    # last byte ("\xdd") is encountered. The bytes containing the value "\x01"
400
    # are discarded to shrink the "ident" length down to 8 bytes to keep the
401
    # image data aligned with the existing settings structures.
402

    
403
    # Ok, get the response
404
    response = b""
405
    for i in range(1, 13):
406
        byte = serial.read(1)
407
        response += byte
408
        # stop reading once the last byte ("\xdd") is encountered
409
        if byte == b"\xDD":
410
            break
411

    
412
    # check if response is OK
413
    if len(response) in [8, 12]:
414
        # DEBUG
415
        LOG.info("Valid response, got this:")
416
        LOG.debug(util.hexprint(response))
417
        if len(response) == 12:
418
            ident = (bytes([response[0], response[3], response[5]]) +
419
                     response[7:])
420
        else:
421
            ident = response
422
    else:
423
        # bad response
424
        msg = "Unexpected response, got this:"
425
        msg += util.hexprint(response)
426
        LOG.debug(msg)
427
        raise errors.RadioError("Unexpected response from radio.")
428

    
429
    if secondack:
430
        serial.write(b"\x06")
431
        ack = serial.read(1)
432
        if ack != b"\x06":
433
            raise errors.RadioError("Radio refused clone")
434

    
435
    return ident
436

    
437

    
438
def _read_block(radio, start, size, first_command=False):
439
    msg = struct.pack(">BHB", ord("S"), start, size)
440
    radio.pipe.write(msg)
441

    
442
    if first_command is False:
443
        ack = radio.pipe.read(1)
444
        if ack != b"\x06":
445
            raise errors.RadioError(
446
                "Radio refused to send second block 0x%04x" % start)
447

    
448
    answer = radio.pipe.read(4)
449
    if len(answer) != 4:
450
        raise errors.RadioError("Radio refused to send block 0x%04x" % start)
451

    
452
    cmd, addr, length = struct.unpack(">BHB", answer)
453
    if cmd != ord("X") or addr != start or length != size:
454
        LOG.error("Invalid answer for block 0x%04x:" % start)
455
        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (cmd, addr, length))
456
        raise errors.RadioError("Unknown response from radio")
457

    
458
    chunk = radio.pipe.read(size)
459
    if not chunk:
460
        raise errors.RadioError("Radio did not send block 0x%04x" % start)
461
    elif len(chunk) != size:
462
        LOG.error("Chunk length was 0x%04i" % len(chunk))
463
        raise errors.RadioError("Radio sent incomplete block 0x%04x" % start)
464

    
465
    radio.pipe.write(b"\x06")
466
    time.sleep(0.05)
467

    
468
    return chunk
469

    
470

    
471
def _get_radio_firmware_version(radio):
472
    if radio.MODEL == "BJ-UV55":
473
        block = _read_block(radio, 0x1FF0, 0x40, True)
474
        version = block[0:6]
475
    else:
476
        block1 = _read_block(radio, 0x1E80, 0x40, True)
477
        block2 = _read_block(radio, 0x1EC0, 0x40, False)
478
        block3 = _read_block(radio, 0x0F40, 0x40, False)
479
        version = block2[48:62]
480
        brdcstfm = block3[0:30]
481
        return version, brdcstfm
482
    return version
483

    
484

    
485
IDENT_BLACKLIST = {
486
    b"\x50\x0D\x0C\x20\x16\x03\x28": "Radio identifies as BTECH UV-5X3",
487
    b"\x50\xBB\xFF\x20\x12\x06\x25": "Radio identifies as Radioddity UV-5G",
488
}
489

    
490

    
491
def _ident_radio(radio):
492
    for magic in radio._idents:
493
        error = None
494
        try:
495
            data = _do_ident(radio, magic)
496
            return data
497
        except errors.RadioError as e:
498
            LOG.error("uv5r._ident_radio: %s", e)
499
            error = e
500
            time.sleep(2)
501

    
502
    for magic, reason in list(IDENT_BLACKLIST.items()):
503
        try:
504
            _do_ident(radio, magic, secondack=False)
505
        except errors.RadioError:
506
            # No match, try the next one
507
            continue
508

    
509
        # If we got here, it means we identified the radio as
510
        # something other than one of our valid idents. Warn
511
        # the user so they can do the right thing.
512
        LOG.warning(('Identified radio as a blacklisted model '
513
                     '(details: %s)') % reason)
514
        raise errors.RadioError(('%s. Please choose the proper vendor/'
515
                                 'model and try again.') % reason)
516

    
517
    if error:
518
        raise error
519
    raise errors.RadioError("Radio did not respond")
520

    
521

    
522
def _do_download(radio):
523
    data = _ident_radio(radio)
524

    
525
    if radio.MODEL == "BJ-UV55":
526
        radio_version = _get_radio_firmware_version(radio)
527
    else:
528
        radio_version, radio_fm = _get_radio_firmware_version(radio)
529
    LOG.info("Radio Version is %s" % repr(radio_version))
530

    
531
    if b"HN5RV" in radio_version:
532
        # A radio with HN5RV firmware has been detected. It could be a
533
        # UV-5R style radio with HIGH/LOW power levels or it could be a
534
        # BF-F8HP style radio with HIGH/MID/LOW power levels.
535
        # We are going to count on the user to make the right choice and
536
        # then append that model type to the end of the image so it can
537
        # be properly detected when loaded.
538
        append_model = True
539
    elif b"\xFF" * 7 in radio_version:
540
        # A radio UV-5R style radio that reports no firmware version has
541
        # been detected.
542
        # We are going to count on the user to make the right choice and
543
        # then append that model type to the end of the image so it can
544
        # be properly detected when loaded.
545
        append_model = True
546
    elif b"\x20" * 14 in radio_version:
547
        # A radio UV-5R style radio that reports no firmware version has
548
        # been detected.
549
        # We are going to count on the user to make the right choice and
550
        # then append that model type to the end of the image so it can
551
        # be properly detected when loaded.
552
        append_model = True
553
    elif not any(type in radio_version for type in radio._basetype):
554
        # This radio can't be properly detected by parsing its firmware
555
        # version.
556
        raise errors.RadioError("Incorrect 'Model' selected.")
557
    else:
558
        # This radio can be properly detected by parsing its firmware version.
559
        # There is no need to append its model type to the end of the image.
560
        append_model = False
561

    
562
    # Main block
563
    LOG.debug("downloading main block...")
564
    for i in range(0, 0x1800, 0x40):
565
        data += _read_block(radio, i, 0x40, False)
566
        _do_status(radio, "from", i)
567
    _do_status(radio, "from", radio.get_memsize())
568
    LOG.debug("done.")
569
    if radio._aux_block:
570
        LOG.debug("downloading aux block...")
571
        # Auxiliary block starts at 0x1ECO (?)
572
        for i in range(0x1EC0, 0x2000, 0x40):
573
            data += _read_block(radio, i, 0x40, False)
574

    
575
    if append_model:
576
        data += radio.MODEL.ljust(8).encode()
577

    
578
    LOG.debug("done.")
579
    return memmap.MemoryMapBytes(data)
580

    
581

    
582
def _send_block(radio, addr, data):
583
    msg = struct.pack(">BHB", ord("X"), addr, len(data))
584
    radio.pipe.write(msg + data)
585
    time.sleep(0.05)
586

    
587
    ack = radio.pipe.read(1)
588
    if ack != b"\x06":
589
        raise errors.RadioError("Radio refused to accept block 0x%04x" % addr)
590

    
591

    
592
def _do_upload(radio):
593
    ident = _ident_radio(radio)
594
    radio_upper_band = ident[3:4]
595
    image_upper_band = _upper_band_from_image(radio)
596

    
597
    if image_upper_band == vhf_220_radio or radio_upper_band == vhf_220_radio:
598
        if image_upper_band != radio_upper_band:
599
            raise errors.RadioError("Image not supported by radio")
600

    
601
    image_version = _firmware_version_from_image(radio)
602
    if radio.MODEL == "BJ-UV55":
603
        radio_version = _get_radio_firmware_version(radio)
604
    else:
605
        radio_version, radio_fm = _get_radio_firmware_version(radio)
606
    LOG.info("Image Version is %s" % repr(image_version))
607
    LOG.info("Radio Version is %s" % repr(radio_version))
608

    
609
    imagev_matched_radiov = image_version == radio_version
610
    _skip_aux_block = False
611

    
612
    # default ranges
613
    _ranges_main_default = [
614
        (0x0008, 0x0CF8),
615
        (0x0D08, 0x0DF8),
616
        (0x0E08, 0x1808)
617
        ]
618
    _ranges_aux_default = [
619
        (0x1EC0, 0x1EF0)   # 6 key power-on & welcome messages
620
        ]
621

    
622
    # skip broadcast fm range
623
    _ranges_main_no_fm = [
624
        (0x0008, 0x0CF8),
625
        (0x0D08, 0x0DF8),
626
        (0x0E08, 0x0F48),
627
        (0x0F68, 0x1808)
628
        ]
629

    
630
    # extra aux ranges
631
    _ranges_aux_extra = [
632
        (0x1F60, 0x1F70),  # squelch thresholds - vhf
633
        (0x1F80, 0x1F90),  # squelch thresholds - uhf
634
        (0x1FC0, 0x1FE0)   # band limits
635
        ]
636

    
637
    # set default ranges
638
    ranges_main = _ranges_main_default
639
    ranges_aux = _ranges_aux_default
640

    
641
    # check destinaton radio for invalid bcst FM data
642
    if b"\xFF" in radio_fm:
643
        # 0x0F48-0x0F65 contains invalid data
644
        # This radio is likely to 'break' the Aux block if it is changed.
645
        # The safest thing to do is to skip uploading of the Aux block.
646
        ranges_main = _ranges_main_no_fm
647
        _skip_aux_block = True
648
    # check source image for invalid bcst FM data
649
    elif "\xFF" in str(radio._memobj.bcstfmlo) or \
650
            "\xFF" in str(radio._memobj.bcstfmhi):
651
        # 0x0F48-0x0F65 contains invalid data
652
        # This image appears to be from a radio that was likely to 'break'
653
        # the Aux block if it is changed (and could be broken).
654
        # The safest thing to do is to skip uploading of the Aux block.
655
        ranges_main = _ranges_main_no_fm
656
        _skip_aux_block = True
657
    # check if radio and image firmware versions match
658
    elif imagev_matched_radiov:
659
        # matched - allow all safe Aux block ranges
660
        ranges_aux = _ranges_aux_default + _ranges_aux_extra
661

    
662
    if radio._all_range_flag:
663
        _skip_aux_block = False
664
        ranges_main = radio._ranges_main
665
        ranges_aux = radio._ranges_aux
666
        LOG.warning('Sending all ranges to radio as instructed')
667

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

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

    
680
    if _skip_aux_block:
681
        LOG.info("Skipped writing aux block")
682
        return  # Aux block skipped
683

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

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

    
699
    if radio._aux_block and not imagev_matched_radiov:
700
        msg = ("This is NOT an error. The upload finished successfully.\n"
701
               "The 'Other Settings' and 'Service Settings' were skipped "
702
               "because the firmware version of the image (%s) does not "
703
               "match that of the radio (%s).")
704
        raise errors.RadioError(msg % (image_version, radio_version))
705

    
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
    _bw_shift = 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 = _(
778
            "1. Turn radio off.\n"
779
            "2. Connect cable to mic/spkr connector.\n"
780
            "3. Make sure connector is firmly connected.\n"
781
            "4. Turn radio on (volume may need to be set at 100%).\n"
782
            "5. Ensure that the radio is tuned to channel with no"
783
            " activity.\n"
784
            "6. Click OK to download image from device.\n")
785
        rp.pre_upload = _(
786
            "1. Turn radio off.\n"
787
            "2. Connect cable to mic/spkr connector.\n"
788
            "3. Make sure connector is firmly connected.\n"
789
            "4. Turn radio on (volume may need to be set at 100%).\n"
790
            "5. Ensure that the radio is tuned to channel with no"
791
            " activity.\n"
792
            "6. Click OK to upload image to device.\n")
793
        return rp
794

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

    
815
        normal_bands = [self._vhf_range, self._uhf_range]
816
        rax_bands = [self._vhf_range, self._220_range]
817

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

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

    
835
        if match_size and match_model:
836
            return True
837
        else:
838
            return False
839

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

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

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

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

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

    
870
    def _get_mem(self, number):
871
        return self._memobj.memory[number]
872

    
873
    def _get_nam(self, number):
874
        return self._memobj.names[number]
875

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

    
880
        mem = chirp_common.Memory()
881
        mem.number = number
882

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

    
887
        mem.freq = int(_mem.rxfreq) * 10
888

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

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

    
908
        dtcs_pol = ["N", "N"]
909

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

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

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

    
952
        mem.dtcs_polarity = "".join(dtcs_pol)
953

    
954
        if not _mem.scan:
955
            mem.skip = "S"
956

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

    
968
        mem.mode = _mem.wide and "FM" or "NFM"
969

    
970
        mem.extra = RadioSettingGroup("Extra", "extra")
971

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

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

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

    
986
        immutable = []
987

    
988
        if self.MODEL == "GT-5R":
989
            if not ((mem.freq >= self.vhftx[0] and mem.freq < self.vhftx[1]) or
990
                    (mem.freq >= self.uhftx[0] and mem.freq < self.uhftx[1])):
991
                mem.duplex = 'off'
992
                mem.offset = 0
993
                immutable = ["duplex", "offset"]
994

    
995
        mem.immutable = immutable
996

    
997
        return mem
998

    
999
    def _set_mem(self, number):
1000
        return self._memobj.memory[number]
1001

    
1002
    def _set_nam(self, number):
1003
        return self._memobj.names[number]
1004

    
1005
    def set_memory(self, mem):
1006
        _mem = self._get_mem(mem.number)
1007
        _nam = self._get_nam(mem.number)
1008

    
1009
        if mem.empty:
1010
            _mem.set_raw("\xff" * 16)
1011
            _nam.set_raw("\xff" * 16)
1012
            return
1013

    
1014
        was_empty = False
1015
        # same method as used in get_memory to find
1016
        # out whether a raw memory is empty
1017
        if _mem.get_raw()[0] == "\xff":
1018
            was_empty = True
1019
            LOG.debug("UV5R: this mem was empty")
1020
        else:
1021
            # memorize old extra-values before erasing the whole memory
1022
            # used to solve issue 4121
1023
            LOG.debug("mem was not empty, memorize extra-settings")
1024
            prev_bcl = _mem.bcl.get_value()
1025
            prev_scode = _mem.scode.get_value()
1026
            prev_pttid = _mem.pttid.get_value()
1027

    
1028
        _mem.set_raw("\x00" * 16)
1029

    
1030
        _mem.rxfreq = mem.freq / 10
1031

    
1032
        if mem.duplex == "off":
1033
            for i in range(0, 4):
1034
                _mem.txfreq[i].set_raw("\xFF")
1035
        elif mem.duplex == "split":
1036
            _mem.txfreq = mem.offset / 10
1037
        elif mem.duplex == "+":
1038
            _mem.txfreq = (mem.freq + mem.offset) / 10
1039
        elif mem.duplex == "-":
1040
            _mem.txfreq = (mem.freq - mem.offset) / 10
1041
        else:
1042
            _mem.txfreq = mem.freq / 10
1043

    
1044
        _namelength = self.get_features().valid_name_length
1045
        for i in range(_namelength):
1046
            try:
1047
                _nam.name[i] = mem.name[i]
1048
            except IndexError:
1049
                _nam.name[i] = "\xFF"
1050

    
1051
        rxmode = txmode = ""
1052
        if mem.tmode == "Tone":
1053
            _mem.txtone = int(mem.rtone * 10)
1054
            _mem.rxtone = 0
1055
        elif mem.tmode == "TSQL":
1056
            _mem.txtone = int(mem.ctone * 10)
1057
            _mem.rxtone = int(mem.ctone * 10)
1058
        elif mem.tmode == "DTCS":
1059
            rxmode = txmode = "DTCS"
1060
            _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
1061
            _mem.rxtone = UV5R_DTCS.index(mem.dtcs) + 1
1062
        elif mem.tmode == "Cross":
1063
            txmode, rxmode = mem.cross_mode.split("->", 1)
1064
            if txmode == "Tone":
1065
                _mem.txtone = int(mem.rtone * 10)
1066
            elif txmode == "DTCS":
1067
                _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
1068
            else:
1069
                _mem.txtone = 0
1070
            if rxmode == "Tone":
1071
                _mem.rxtone = int(mem.ctone * 10)
1072
            elif rxmode == "DTCS":
1073
                _mem.rxtone = UV5R_DTCS.index(mem.rx_dtcs) + 1
1074
            else:
1075
                _mem.rxtone = 0
1076
        else:
1077
            _mem.rxtone = 0
1078
            _mem.txtone = 0
1079

    
1080
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
1081
            _mem.txtone += 0x69
1082
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
1083
            _mem.rxtone += 0x69
1084

    
1085
        _mem.scan = mem.skip != "S"
1086
        _mem.wide = mem.mode == "FM"
1087

    
1088
        if mem.power:
1089
            if self._tri_power:
1090
                levels = [str(l) for l in UV5R_POWER_LEVELS3]
1091
                _mem.lowpower = levels.index(str(mem.power))
1092
            else:
1093
                _mem.lowpower = UV5R_POWER_LEVELS.index(mem.power)
1094
        else:
1095
            _mem.lowpower = 0
1096

    
1097
        if not was_empty:
1098
            # restoring old extra-settings (issue 4121
1099
            _mem.bcl.set_value(prev_bcl)
1100
            _mem.scode.set_value(prev_scode)
1101
            _mem.pttid.set_value(prev_pttid)
1102

    
1103
        for setting in mem.extra:
1104
            setattr(_mem, setting.get_name(), setting.value)
1105

    
1106
    def _is_orig(self):
1107
        version_tag = _firmware_version_from_image(self)
1108
        try:
1109
            if b'BFB' in version_tag:
1110
                idx = version_tag.index(b"BFB") + 3
1111
                version = int(version_tag[idx:idx + 3])
1112
                return version < 291
1113
            return False
1114
        except:
1115
            pass
1116
        raise errors.RadioError("Unable to parse version string %s" %
1117
                                version_tag)
1118

    
1119
    def _my_version(self):
1120
        version_tag = _firmware_version_from_image(self)
1121
        if b'BFB' in version_tag:
1122
            idx = version_tag.index(b"BFB") + 3
1123
            return int(version_tag[idx:idx + 3])
1124

    
1125
        raise Exception("Unrecognized firmware version string")
1126

    
1127
    def _my_upper_band(self):
1128
        band_tag = _upper_band_from_image(self)
1129
        return band_tag
1130

    
1131
    def _get_settings(self):
1132
        _mem = self._memobj
1133
        _ani = self._memobj.ani
1134
        _fm_presets = self._memobj.fm_presets
1135
        _settings = self._memobj.settings
1136
        _squelch = self._memobj.squelch_new
1137
        _vfoa = self._memobj.vfoa
1138
        _vfob = self._memobj.vfob
1139
        _wmchannel = self._memobj.wmchannel
1140
        _display_tab = (str(_mem.bcstfmlo) == "   FM   65-75M" and
1141
                        str(_mem.bcstfmhi) == "   FM  76-108M")
1142

    
1143
        basic = RadioSettingGroup("basic", "Basic Settings")
1144
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
1145

    
1146
        group = RadioSettings(basic, advanced)
1147

    
1148
        rs = RadioSetting("squelch", "Carrier Squelch Level",
1149
                          RadioSettingValueInteger(0, 9, _settings.squelch))
1150
        basic.append(rs)
1151

    
1152
        rs = RadioSetting("save", "Battery Saver",
1153
                          RadioSettingValueList(
1154
                              SAVE_LIST, SAVE_LIST[_settings.save]))
1155
        basic.append(rs)
1156

    
1157
        rs = RadioSetting("vox", "VOX Sensitivity",
1158
                          RadioSettingValueList(
1159
                              VOX_LIST, VOX_LIST[_settings.vox]))
1160
        advanced.append(rs)
1161

    
1162
        if self.MODEL == "UV-6":
1163
            # NOTE: The UV-6 calls this byte voxenable, but the UV-5R calls it
1164
            # autolk. Since this is a minor difference, it will be referred to
1165
            # by the wrong name for the UV-6.
1166
            rs = RadioSetting("autolk", "Vox",
1167
                              RadioSettingValueBoolean(_settings.autolk))
1168
            advanced.append(rs)
1169

    
1170
        if self.MODEL != "UV-6":
1171
            rs = RadioSetting("abr", "Backlight Timeout",
1172
                              RadioSettingValueInteger(0, 24, _settings.abr))
1173
            basic.append(rs)
1174

    
1175
        rs = RadioSetting("tdr", "Dual Watch",
1176
                          RadioSettingValueBoolean(_settings.tdr))
1177
        advanced.append(rs)
1178

    
1179
        if self.MODEL == "UV-6":
1180
            rs = RadioSetting("tdrch", "Dual Watch Channel",
1181
                              RadioSettingValueList(
1182
                                  TDRCH_LIST, TDRCH_LIST[_settings.tdrch]))
1183
            advanced.append(rs)
1184

    
1185
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
1186
                              RadioSettingValueBoolean(_settings.tdrab))
1187
            advanced.append(rs)
1188
        else:
1189
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
1190
                              RadioSettingValueList(
1191
                                  TDRAB_LIST, TDRAB_LIST[_settings.tdrab]))
1192
            advanced.append(rs)
1193

    
1194
        if self.MODEL == "UV-6":
1195
            rs = RadioSetting("alarm", "Alarm Sound",
1196
                              RadioSettingValueBoolean(_settings.alarm))
1197
            advanced.append(rs)
1198

    
1199
        if _settings.almod > 0x02:
1200
            val = 0x01
1201
        else:
1202
            val = _settings.almod
1203
        rs = RadioSetting("almod", "Alarm Mode",
1204
                          RadioSettingValueList(
1205
                              ALMOD_LIST, ALMOD_LIST[val]))
1206
        advanced.append(rs)
1207

    
1208
        rs = RadioSetting("beep", "Beep",
1209
                          RadioSettingValueBoolean(_settings.beep))
1210
        basic.append(rs)
1211

    
1212
        rs = RadioSetting("timeout", "Timeout Timer",
1213
                          RadioSettingValueList(
1214
                              TIMEOUT_LIST, TIMEOUT_LIST[_settings.timeout]))
1215
        basic.append(rs)
1216

    
1217
        if ((self._is_orig() and self._my_version() < 251) or
1218
                (self.MODEL in ["TI-F8+", "TS-T9+"])):
1219
            rs = RadioSetting("voice", "Voice",
1220
                              RadioSettingValueBoolean(_settings.voice))
1221
            advanced.append(rs)
1222
        else:
1223
            rs = RadioSetting("voice", "Voice",
1224
                              RadioSettingValueList(
1225
                                  VOICE_LIST, VOICE_LIST[_settings.voice]))
1226
            advanced.append(rs)
1227

    
1228
        rs = RadioSetting("screv", "Scan Resume",
1229
                          RadioSettingValueList(
1230
                              RESUME_LIST, RESUME_LIST[_settings.screv]))
1231
        advanced.append(rs)
1232

    
1233
        if self.MODEL != "UV-6":
1234
            rs = RadioSetting("mdfa", "Display Mode (A)",
1235
                              RadioSettingValueList(
1236
                                  MODE_LIST, MODE_LIST[_settings.mdfa]))
1237
            basic.append(rs)
1238

    
1239
            rs = RadioSetting("mdfb", "Display Mode (B)",
1240
                              RadioSettingValueList(
1241
                                  MODE_LIST, MODE_LIST[_settings.mdfb]))
1242
            basic.append(rs)
1243

    
1244
        rs = RadioSetting("bcl", "Busy Channel Lockout",
1245
                          RadioSettingValueBoolean(_settings.bcl))
1246
        advanced.append(rs)
1247

    
1248
        if self.MODEL != "UV-6":
1249
            rs = RadioSetting("autolk", "Automatic Key Lock",
1250
                              RadioSettingValueBoolean(_settings.autolk))
1251
            advanced.append(rs)
1252

    
1253
        rs = RadioSetting("fmradio", "Broadcast FM Radio",
1254
                          RadioSettingValueBoolean(_settings.fmradio))
1255
        advanced.append(rs)
1256

    
1257
        if self.MODEL != "UV-6":
1258
            rs = RadioSetting("wtled", "Standby LED Color",
1259
                              RadioSettingValueList(
1260
                                  COLOR_LIST, COLOR_LIST[_settings.wtled]))
1261
            basic.append(rs)
1262

    
1263
            rs = RadioSetting("rxled", "RX LED Color",
1264
                              RadioSettingValueList(
1265
                                  COLOR_LIST, COLOR_LIST[_settings.rxled]))
1266
            basic.append(rs)
1267

    
1268
            rs = RadioSetting("txled", "TX LED Color",
1269
                              RadioSettingValueList(
1270
                                  COLOR_LIST, COLOR_LIST[_settings.txled]))
1271
            basic.append(rs)
1272

    
1273
        if isinstance(self, BaofengUV82Radio):
1274
            rs = RadioSetting("roger", "Roger Beep (TX)",
1275
                              RadioSettingValueBoolean(_settings.roger))
1276
            basic.append(rs)
1277
            rs = RadioSetting("rogerrx", "Roger Beep (RX)",
1278
                              RadioSettingValueList(
1279
                                  ROGERRX_LIST,
1280
                                  ROGERRX_LIST[_settings.rogerrx]))
1281
            basic.append(rs)
1282
        else:
1283
            rs = RadioSetting("roger", "Roger Beep",
1284
                              RadioSettingValueBoolean(_settings.roger))
1285
            basic.append(rs)
1286

    
1287
        rs = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)",
1288
                          RadioSettingValueBoolean(_settings.ste))
1289
        advanced.append(rs)
1290

    
1291
        rs = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)",
1292
                          RadioSettingValueList(
1293
                              RPSTE_LIST, RPSTE_LIST[_settings.rpste]))
1294
        advanced.append(rs)
1295

    
1296
        rs = RadioSetting("rptrl", "STE Repeater Delay",
1297
                          RadioSettingValueList(
1298
                              STEDELAY_LIST, STEDELAY_LIST[_settings.rptrl]))
1299
        advanced.append(rs)
1300

    
1301
        if self.MODEL != "UV-6":
1302
            rs = RadioSetting("reset", "RESET Menu",
1303
                              RadioSettingValueBoolean(_settings.reset))
1304
            advanced.append(rs)
1305

    
1306
            rs = RadioSetting("menu", "All Menus",
1307
                              RadioSettingValueBoolean(_settings.menu))
1308
            advanced.append(rs)
1309

    
1310
        if self.MODEL == "F-11":
1311
            # this is an F-11 only feature
1312
            rs = RadioSetting("vfomrlock", "VFO/MR Button",
1313
                              RadioSettingValueBoolean(_settings.vfomrlock))
1314
            advanced.append(rs)
1315

    
1316
        if isinstance(self, BaofengUV82Radio):
1317
            # this is a UV-82C only feature
1318
            rs = RadioSetting("vfomrlock", "VFO/MR Switching (UV-82C only)",
1319
                              RadioSettingValueBoolean(_settings.vfomrlock))
1320
            advanced.append(rs)
1321

    
1322
        if self.MODEL == "UV-82HP":
1323
            # this is a UV-82HP only feature
1324
            rs = RadioSetting(
1325
                "vfomrlock", "VFO/MR Switching (BTech UV-82HP only)",
1326
                RadioSettingValueBoolean(_settings.vfomrlock))
1327
            advanced.append(rs)
1328

    
1329
        if isinstance(self, BaofengUV82Radio):
1330
            # this is an UV-82C only feature
1331
            rs = RadioSetting("singleptt", "Single PTT (UV-82C only)",
1332
                              RadioSettingValueBoolean(_settings.singleptt))
1333
            advanced.append(rs)
1334

    
1335
        if self.MODEL == "UV-82HP":
1336
            # this is an UV-82HP only feature
1337
            rs = RadioSetting("singleptt", "Single PTT (BTech UV-82HP only)",
1338
                              RadioSettingValueBoolean(_settings.singleptt))
1339
            advanced.append(rs)
1340

    
1341
        if self.MODEL == "UV-82HP":
1342
            # this is an UV-82HP only feature
1343
            rs = RadioSetting(
1344
                "tdrch", "Tone Burst Frequency (BTech UV-82HP only)",
1345
                RadioSettingValueList(RTONE_LIST, RTONE_LIST[_settings.tdrch]))
1346
            advanced.append(rs)
1347

    
1348
        def set_range_flag(setting):
1349
            val = [85, 115, 101, 65, 116, 79, 119, 110, 82, 105, 115, 107]
1350
            if [ord(x) for x in str(setting.value).strip()] == val:
1351
                self._all_range_flag = True
1352
            else:
1353
                self._all_range_flag = False
1354
            LOG.debug('Set range flag to %s' % self._all_range_flag)
1355

    
1356
        rs = RadioSetting("allrange", "Range Override Parameter",
1357
                          RadioSettingValueString(0, 12, "Default"))
1358
        rs.set_apply_callback(set_range_flag)
1359
        advanced.append(rs)
1360

    
1361
        if len(self._mmap.get_packed()) == 0x1808:
1362
            # Old image, without aux block
1363
            return group
1364

    
1365
        if self.MODEL != "UV-6":
1366
            other = RadioSettingGroup("other", "Other Settings")
1367
            group.append(other)
1368

    
1369
            def _filter(name):
1370
                filtered = ""
1371
                for char in str(name):
1372
                    if char in chirp_common.CHARSET_ASCII:
1373
                        filtered += char
1374
                    else:
1375
                        filtered += " "
1376
                return filtered
1377

    
1378
            _msg = self._memobj.firmware_msg
1379
            val = RadioSettingValueString(0, 7, _filter(_msg.line1))
1380
            val.set_mutable(False)
1381
            rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
1382
            other.append(rs)
1383

    
1384
            val = RadioSettingValueString(0, 7, _filter(_msg.line2))
1385
            val.set_mutable(False)
1386
            rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
1387
            other.append(rs)
1388

    
1389
            if _display_tab:
1390
                _msg = self._memobj.sixpoweron_msg
1391
                rs = RadioSetting("sixpoweron_msg.line1",
1392
                                  "6+Power-On Message 1",
1393
                                  RadioSettingValueString(
1394
                                      0, 7, _filter(_msg.line1)))
1395
                other.append(rs)
1396
                rs = RadioSetting("sixpoweron_msg.line2",
1397
                                  "6+Power-On Message 2",
1398
                                  RadioSettingValueString(
1399
                                      0, 7, _filter(_msg.line2)))
1400
                other.append(rs)
1401

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

    
1412
                rs = RadioSetting("ponmsg", "Power-On Message",
1413
                                  RadioSettingValueList(
1414
                                      PONMSG_LIST,
1415
                                      PONMSG_LIST[_settings.ponmsg]))
1416
                other.append(rs)
1417

    
1418
                if self._is_orig():
1419
                    limit = "limits_old"
1420
                else:
1421
                    limit = "limits_new"
1422

    
1423
                vhf_limit = getattr(self._memobj, limit).vhf
1424
                rs = RadioSetting("%s.vhf.lower" % limit,
1425
                                  "VHF Lower Limit (MHz)",
1426
                                  RadioSettingValueInteger(1, 1000,
1427
                                                           vhf_limit.lower))
1428
                other.append(rs)
1429

    
1430
                rs = RadioSetting("%s.vhf.upper" % limit,
1431
                                  "VHF Upper Limit (MHz)",
1432
                                  RadioSettingValueInteger(1, 1000,
1433
                                                           vhf_limit.upper))
1434
                other.append(rs)
1435

    
1436
                rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled",
1437
                                  RadioSettingValueBoolean(vhf_limit.enable))
1438
                other.append(rs)
1439

    
1440
                uhf_limit = getattr(self._memobj, limit).uhf
1441
                rs = RadioSetting("%s.uhf.lower" % limit,
1442
                                  "UHF Lower Limit (MHz)",
1443
                                  RadioSettingValueInteger(1, 1000,
1444
                                                           uhf_limit.lower))
1445
                other.append(rs)
1446
                rs = RadioSetting("%s.uhf.upper" % limit,
1447
                                  "UHF Upper Limit (MHz)",
1448
                                  RadioSettingValueInteger(1, 1000,
1449
                                                           uhf_limit.upper))
1450
                other.append(rs)
1451
                rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled",
1452
                                  RadioSettingValueBoolean(uhf_limit.enable))
1453
                other.append(rs)
1454

    
1455
        if self.MODEL != "UV-6":
1456
            workmode = RadioSettingGroup("workmode", "Work Mode Settings")
1457
            group.append(workmode)
1458

    
1459
            rs = RadioSetting("displayab", "Display",
1460
                              RadioSettingValueList(
1461
                                  AB_LIST, AB_LIST[_settings.displayab]))
1462
            workmode.append(rs)
1463

    
1464
            rs = RadioSetting("workmode", "VFO/MR Mode",
1465
                              RadioSettingValueList(
1466
                                  WORKMODE_LIST,
1467
                                  WORKMODE_LIST[_settings.workmode]))
1468
            workmode.append(rs)
1469

    
1470
            rs = RadioSetting("keylock", "Keypad Lock",
1471
                              RadioSettingValueBoolean(_settings.keylock))
1472
            workmode.append(rs)
1473

    
1474
            rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
1475
                              RadioSettingValueInteger(0, 127,
1476
                                                       _wmchannel.mrcha))
1477
            workmode.append(rs)
1478

    
1479
            rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
1480
                              RadioSettingValueInteger(0, 127,
1481
                                                       _wmchannel.mrchb))
1482
            workmode.append(rs)
1483

    
1484
            def convert_bytes_to_freq(bytes):
1485
                real_freq = 0
1486
                for byte in bytes:
1487
                    real_freq = (real_freq * 10) + byte
1488
                return chirp_common.format_freq(real_freq * 10)
1489

    
1490
            def my_validate(value):
1491
                value = chirp_common.parse_freq(value)
1492
                if 17400000 <= value and value < 40000000:
1493
                    msg = ("Can't be between 174.00000-400.00000")
1494
                    raise InvalidValueError(msg)
1495
                return chirp_common.format_freq(value)
1496

    
1497
            def apply_freq(setting, obj):
1498
                value = chirp_common.parse_freq(str(setting.value)) / 10
1499
                obj.band = value >= 40000000
1500
                for i in range(7, -1, -1):
1501
                    obj.freq[i] = value % 10
1502
                    value /= 10
1503

    
1504
            val1a = RadioSettingValueString(0, 10,
1505
                                            convert_bytes_to_freq(_vfoa.freq))
1506
            val1a.set_validate_callback(my_validate)
1507
            rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a)
1508
            rs.set_apply_callback(apply_freq, _vfoa)
1509
            workmode.append(rs)
1510

    
1511
            val1b = RadioSettingValueString(0, 10,
1512
                                            convert_bytes_to_freq(_vfob.freq))
1513
            val1b.set_validate_callback(my_validate)
1514
            rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b)
1515
            rs.set_apply_callback(apply_freq, _vfob)
1516
            workmode.append(rs)
1517

    
1518
            rs = RadioSetting("vfoa.sftd", "VFO A Shift",
1519
                              RadioSettingValueList(
1520
                                  SHIFTD_LIST, SHIFTD_LIST[_vfoa.sftd]))
1521
            workmode.append(rs)
1522

    
1523
            rs = RadioSetting("vfob.sftd", "VFO B Shift",
1524
                              RadioSettingValueList(
1525
                                  SHIFTD_LIST, SHIFTD_LIST[_vfob.sftd]))
1526
            workmode.append(rs)
1527

    
1528
            def convert_bytes_to_offset(bytes):
1529
                real_offset = 0
1530
                for byte in bytes:
1531
                    real_offset = (real_offset * 10) + byte
1532
                return chirp_common.format_freq(real_offset * 1000)
1533

    
1534
            def apply_offset(setting, obj):
1535
                value = chirp_common.parse_freq(str(setting.value)) / 1000
1536
                for i in range(5, -1, -1):
1537
                    obj.offset[i] = value % 10
1538
                    value /= 10
1539

    
1540
            val1a = RadioSettingValueString(
1541
                0, 10, convert_bytes_to_offset(_vfoa.offset))
1542
            rs = RadioSetting("vfoa.offset",
1543
                              "VFO A Offset (0.0-999.999)", val1a)
1544
            rs.set_apply_callback(apply_offset, _vfoa)
1545
            workmode.append(rs)
1546

    
1547
            val1b = RadioSettingValueString(
1548
                0, 10, convert_bytes_to_offset(_vfob.offset))
1549
            rs = RadioSetting("vfob.offset",
1550
                              "VFO B Offset (0.0-999.999)", val1b)
1551
            rs.set_apply_callback(apply_offset, _vfob)
1552
            workmode.append(rs)
1553

    
1554
            if self._tri_power:
1555
                if _vfoa.txpower3 > 0x02:
1556
                    val = 0x00
1557
                else:
1558
                    val = _vfoa.txpower3
1559
                rs = RadioSetting("vfoa.txpower3", "VFO A Power",
1560
                                  RadioSettingValueList(
1561
                                      TXPOWER3_LIST,
1562
                                      TXPOWER3_LIST[val]))
1563
                workmode.append(rs)
1564

    
1565
                if _vfob.txpower3 > 0x02:
1566
                    val = 0x00
1567
                else:
1568
                    val = _vfob.txpower3
1569
                rs = RadioSetting("vfob.txpower3", "VFO B Power",
1570
                                  RadioSettingValueList(
1571
                                      TXPOWER3_LIST,
1572
                                      TXPOWER3_LIST[val]))
1573
                workmode.append(rs)
1574
            else:
1575
                rs = RadioSetting("vfoa.txpower", "VFO A Power",
1576
                                  RadioSettingValueList(
1577
                                      TXPOWER_LIST,
1578
                                      TXPOWER_LIST[_vfoa.txpower]))
1579
                workmode.append(rs)
1580

    
1581
                rs = RadioSetting("vfob.txpower", "VFO B Power",
1582
                                  RadioSettingValueList(
1583
                                      TXPOWER_LIST,
1584
                                      TXPOWER_LIST[_vfob.txpower]))
1585
                workmode.append(rs)
1586

    
1587
            rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth",
1588
                              RadioSettingValueList(
1589
                                  BANDWIDTH_LIST,
1590
                                  BANDWIDTH_LIST[_vfoa.widenarr]))
1591
            workmode.append(rs)
1592

    
1593
            rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
1594
                              RadioSettingValueList(
1595
                                  BANDWIDTH_LIST,
1596
                                  BANDWIDTH_LIST[_vfob.widenarr]))
1597
            workmode.append(rs)
1598

    
1599
            rs = RadioSetting("vfoa.scode", "VFO A PTT-ID",
1600
                              RadioSettingValueList(
1601
                                  PTTIDCODE_LIST, PTTIDCODE_LIST[_vfoa.scode]))
1602
            workmode.append(rs)
1603

    
1604
            rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
1605
                              RadioSettingValueList(
1606
                                  PTTIDCODE_LIST, PTTIDCODE_LIST[_vfob.scode]))
1607
            workmode.append(rs)
1608

    
1609
            if not self._is_orig():
1610
                rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
1611
                                  RadioSettingValueList(
1612
                                      STEP291_LIST, STEP291_LIST[_vfoa.step]))
1613
                workmode.append(rs)
1614
                rs = RadioSetting("vfob.step", "VFO B Tuning Step",
1615
                                  RadioSettingValueList(
1616
                                      STEP291_LIST, STEP291_LIST[_vfob.step]))
1617
                workmode.append(rs)
1618
            else:
1619
                rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
1620
                                  RadioSettingValueList(
1621
                                      STEP_LIST, STEP_LIST[_vfoa.step]))
1622
                workmode.append(rs)
1623
                rs = RadioSetting("vfob.step", "VFO B Tuning Step",
1624
                                  RadioSettingValueList(
1625
                                      STEP_LIST, STEP_LIST[_vfob.step]))
1626
                workmode.append(rs)
1627

    
1628
        if _display_tab:
1629
            fm_preset = RadioSettingGroup("fm_preset", "FM Radio Preset")
1630
            group.append(fm_preset)
1631

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

    
1653
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1654
        group.append(dtmf)
1655

    
1656
        if str(self._memobj.firmware_msg.line1) == "HN5RV01":
1657
            dtmfchars = "0123456789ABCD*#"
1658
        else:
1659
            dtmfchars = "0123456789 *#ABCD"
1660

    
1661
        for i in range(0, 15):
1662
            _codeobj = self._memobj.pttid[i].code
1663
            _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1664
            val = RadioSettingValueString(0, 5, _code, False)
1665
            val.set_charset(dtmfchars)
1666
            rs = RadioSetting("pttid/%i.code" % i,
1667
                              "PTT ID Code %i" % (i + 1), val)
1668

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

    
1680
        dtmfcharsani = "0123456789"
1681

    
1682
        _codeobj = self._memobj.ani.code
1683
        _code = "".join([dtmfcharsani[x] for x in _codeobj if int(x) < 0x1F])
1684
        val = RadioSettingValueString(0, 5, _code, False)
1685
        val.set_charset(dtmfcharsani)
1686
        rs = RadioSetting("ani.code", "ANI Code", val)
1687

    
1688
        def apply_code(setting, obj):
1689
            code = []
1690
            for j in range(0, 5):
1691
                try:
1692
                    code.append(dtmfchars.index(str(setting.value)[j]))
1693
                except IndexError:
1694
                    code.append(0xFF)
1695
            obj.code = code
1696
        rs.set_apply_callback(apply_code, _ani)
1697
        dtmf.append(rs)
1698

    
1699
        rs = RadioSetting("ani.aniid", "ANI ID",
1700
                          RadioSettingValueList(PTTID_LIST,
1701
                                                PTTID_LIST[_ani.aniid]))
1702
        dtmf.append(rs)
1703

    
1704
        _codeobj = self._memobj.ani.alarmcode
1705
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1706
        val = RadioSettingValueString(0, 3, _code, False)
1707
        val.set_charset(dtmfchars)
1708
        rs = RadioSetting("ani.alarmcode", "Alarm Code", val)
1709

    
1710
        def apply_code(setting, obj):
1711
            alarmcode = []
1712
            for j in range(0, 3):
1713
                try:
1714
                    alarmcode.append(dtmfchars.index(str(setting.value)[j]))
1715
                except IndexError:
1716
                    alarmcode.append(0xFF)
1717
            obj.alarmcode = alarmcode
1718
        rs.set_apply_callback(apply_code, _ani)
1719
        dtmf.append(rs)
1720

    
1721
        rs = RadioSetting("dtmfst", "DTMF Sidetone",
1722
                          RadioSettingValueList(DTMFST_LIST,
1723
                                                DTMFST_LIST[_settings.dtmfst]))
1724
        dtmf.append(rs)
1725

    
1726
        if _ani.dtmfon > 0xC3:
1727
            val = 0x00
1728
        else:
1729
            val = _ani.dtmfon
1730
        rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
1731
                          RadioSettingValueList(DTMFSPEED_LIST,
1732
                                                DTMFSPEED_LIST[val]))
1733
        dtmf.append(rs)
1734

    
1735
        if _ani.dtmfoff > 0xC3:
1736
            val = 0x00
1737
        else:
1738
            val = _ani.dtmfoff
1739
        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
1740
                          RadioSettingValueList(DTMFSPEED_LIST,
1741
                                                DTMFSPEED_LIST[val]))
1742
        dtmf.append(rs)
1743

    
1744
        rs = RadioSetting("pttlt", "PTT ID Delay",
1745
                          RadioSettingValueInteger(0, 50, _settings.pttlt))
1746
        dtmf.append(rs)
1747

    
1748
        if not self._is_orig() and _display_tab:
1749
            service = RadioSettingGroup("service", "Service Settings")
1750
            group.append(service)
1751

    
1752
            for band in ["vhf", "uhf"]:
1753
                for index in range(0, 10):
1754
                    key = "squelch_new.%s.sql%i" % (band, index)
1755
                    if band == "vhf":
1756
                        _obj = self._memobj.squelch_new.vhf
1757
                    elif band == "uhf":
1758
                        _obj = self._memobj.squelch_new.uhf
1759
                    name = "%s Squelch %i" % (band.upper(), index)
1760
                    rs = RadioSetting(key, name,
1761
                                      RadioSettingValueInteger(
1762
                                          0, 123,
1763
                                          getattr(_obj, "sql%i" % (index))))
1764
                    service.append(rs)
1765

    
1766
        return group
1767

    
1768
    def get_settings(self):
1769
        try:
1770
            return self._get_settings()
1771
        except:
1772
            import traceback
1773
            LOG.error("Failed to parse settings: %s", traceback.format_exc())
1774
            return None
1775

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

    
1803
                    if element.has_apply_callback():
1804
                        LOG.debug("Using apply callback")
1805
                        element.run_apply_callback()
1806
                    elif element.value.get_mutable():
1807
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1808
                        setattr(obj, setting, element.value)
1809
                except Exception:
1810
                    LOG.debug(element.get_name())
1811
                    raise
1812

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

    
1829

    
1830
class UV5XAlias(chirp_common.Alias):
1831
    VENDOR = "Baofeng"
1832
    MODEL = "UV-5X"
1833

    
1834

    
1835
class RT5RAlias(chirp_common.Alias):
1836
    VENDOR = "Retevis"
1837
    MODEL = "RT5R"
1838

    
1839

    
1840
class RT5RVAlias(chirp_common.Alias):
1841
    VENDOR = "Retevis"
1842
    MODEL = "RT5RV"
1843

    
1844

    
1845
class RT5Alias(chirp_common.Alias):
1846
    VENDOR = "Retevis"
1847
    MODEL = "RT5"
1848

    
1849

    
1850
class RT5_TPAlias(chirp_common.Alias):
1851
    VENDOR = "Retevis"
1852
    MODEL = "RT5(tri-power)"
1853

    
1854

    
1855
class RH5RAlias(chirp_common.Alias):
1856
    VENDOR = "Rugged"
1857
    MODEL = "RH5R"
1858

    
1859

    
1860
class ROUV5REXAlias(chirp_common.Alias):
1861
    VENDOR = "Radioddity"
1862
    MODEL = "UV-5R EX"
1863

    
1864

    
1865
class A5RAlias(chirp_common.Alias):
1866
    VENDOR = "Ansoko"
1867
    MODEL = "A-5R"
1868

    
1869

    
1870
@directory.register
1871
class BaofengUV5RGeneric(BaofengUV5R):
1872
    ALIASES = [UV5XAlias, RT5RAlias, RT5RVAlias, RT5Alias, RH5RAlias,
1873
               ROUV5REXAlias, A5RAlias]
1874

    
1875

    
1876
@directory.register
1877
class BaofengF11Radio(BaofengUV5R):
1878
    VENDOR = "Baofeng"
1879
    MODEL = "F-11"
1880
    _basetype = BASETYPE_F11
1881
    _idents = [UV5R_MODEL_F11]
1882

    
1883
    def _is_orig(self):
1884
        # Override this for F11 to always return False
1885
        return False
1886

    
1887

    
1888
@directory.register
1889
class BaofengUV82Radio(BaofengUV5R):
1890
    MODEL = "UV-82"
1891
    _basetype = BASETYPE_UV82
1892
    _idents = [UV5R_MODEL_UV82]
1893
    _vhf_range = (130000000, 176000000)
1894
    _uhf_range = (400000000, 521000000)
1895
    _valid_chars = chirp_common.CHARSET_ASCII
1896

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

    
1901

    
1902
@directory.register
1903
class Radioddity82X3Radio(BaofengUV82Radio):
1904
    VENDOR = "Radioddity"
1905
    MODEL = "UV-82X3"
1906
    _basetype = BASETYPE_UV82X3
1907

    
1908
    def get_features(self):
1909
        rf = BaofengUV5R.get_features(self)
1910
        rf.valid_bands = [self._vhf_range,
1911
                          (200000000, 260000000),
1912
                          self._uhf_range]
1913
        return rf
1914

    
1915

    
1916
@directory.register
1917
class BaofengUV6Radio(BaofengUV5R):
1918

    
1919
    """Baofeng UV-6/UV-7"""
1920
    VENDOR = "Baofeng"
1921
    MODEL = "UV-6"
1922
    _basetype = BASETYPE_UV6
1923
    _idents = [UV5R_MODEL_UV6,
1924
               UV5R_MODEL_UV6_ORIG
1925
               ]
1926
    _aux_block = False
1927

    
1928
    def get_features(self):
1929
        rf = BaofengUV5R.get_features(self)
1930
        rf.memory_bounds = (1, 128)
1931
        return rf
1932

    
1933
    def _get_mem(self, number):
1934
        return self._memobj.memory[number - 1]
1935

    
1936
    def _get_nam(self, number):
1937
        return self._memobj.names[number - 1]
1938

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

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

    
1945
    def _is_orig(self):
1946
        # Override this for UV6 to always return False
1947
        return False
1948

    
1949

    
1950
@directory.register
1951
class IntekKT980Radio(BaofengUV5R):
1952
    VENDOR = "Intek"
1953
    MODEL = "KT-980HP"
1954
    _basetype = BASETYPE_KT980HP
1955
    _idents = [UV5R_MODEL_291]
1956
    _vhf_range = (130000000, 180000000)
1957
    _uhf_range = (400000000, 521000000)
1958
    _tri_power = True
1959

    
1960
    def get_features(self):
1961
        rf = BaofengUV5R.get_features(self)
1962
        rf.valid_power_levels = UV5R_POWER_LEVELS3
1963
        return rf
1964

    
1965
    def _is_orig(self):
1966
        # Override this for KT980HP to always return False
1967
        return False
1968

    
1969

    
1970
class ROGA5SAlias(chirp_common.Alias):
1971
    VENDOR = "Radioddity"
1972
    MODEL = "GA-5S"
1973

    
1974

    
1975
class UV5XPAlias(chirp_common.Alias):
1976
    VENDOR = "Baofeng"
1977
    MODEL = "UV-5XP"
1978

    
1979

    
1980
class TSTIF8Alias(chirp_common.Alias):
1981
    VENDOR = "TechSide"
1982
    MODEL = "TI-F8+"
1983

    
1984

    
1985
class TenwayUV5RPro(chirp_common.Alias):
1986
    VENDOR = 'Tenway'
1987
    MODEL = 'UV-5R Pro'
1988

    
1989

    
1990
class TSTST9Alias(chirp_common.Alias):
1991
    VENDOR = "TechSide"
1992
    MODEL = "TS-T9+"
1993

    
1994

    
1995
class TDUV5RRadio(chirp_common.Alias):
1996
    VENDOR = "TIDRADIO"
1997
    MODEL = "TD-UV5R TriPower"
1998

    
1999

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

    
2014
    def get_features(self):
2015
        rf = BaofengUV5R.get_features(self)
2016
        rf.valid_power_levels = UV5R_POWER_LEVELS3
2017
        return rf
2018

    
2019
    def _is_orig(self):
2020
        # Override this for BFF8HP to always return False
2021
        return False
2022

    
2023

    
2024
class TenwayUV82Pro(chirp_common.Alias):
2025
    VENDOR = 'Tenway'
2026
    MODEL = 'UV-82 Pro'
2027

    
2028

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

    
2042
    def get_features(self):
2043
        rf = BaofengUV5R.get_features(self)
2044
        rf.valid_power_levels = UV5R_POWER_LEVELS3
2045
        return rf
2046

    
2047
    def _is_orig(self):
2048
        # Override this for UV82HP to always return False
2049
        return False
2050

    
2051

    
2052
@directory.register
2053
class RadioddityUV5RX3Radio(BaofengUV5R):
2054
    VENDOR = "Radioddity"
2055
    MODEL = "UV-5RX3"
2056

    
2057
    def get_features(self):
2058
        rf = BaofengUV5R.get_features(self)
2059
        rf.valid_bands = [self._vhf_range,
2060
                          (200000000, 260000000),
2061
                          self._uhf_range]
2062
        return rf
2063

    
2064
    @classmethod
2065
    def match_model(cls, filename, filedata):
2066
        return False
2067

    
2068

    
2069
@directory.register
2070
class RadioddityGT5RRadio(BaofengUV5R):
2071
    VENDOR = 'Baofeng'
2072
    MODEL = 'GT-5R'
2073

    
2074
    vhftx = [144000000, 148000000]
2075
    uhftx = [420000000, 450000000]
2076

    
2077
    def validate_memory(self, mem):
2078
        msgs = super().validate_memory(mem)
2079

    
2080
        _msg_duplex = 'Duplex must be "off" for this frequency'
2081
        _msg_offset = 'Only simplex or +5MHz offset allowed on GMRS'
2082

    
2083
        if not ((mem.freq >= self.vhftx[0] and mem.freq < self.vhftx[1]) or
2084
                (mem.freq >= self.uhftx[0] and mem.freq < self.uhftx[1])):
2085
            if mem.duplex != "off":
2086
                msgs.append(chirp_common.ValidationWarning(_msg_duplex))
2087

    
2088
        return msgs
2089

    
2090
    def check_set_memory_immutable_policy(self, existing, new):
2091
        existing.immutable = []
2092
        super().check_set_memory_immutable_policy(existing, new)
2093

    
2094
    @classmethod
2095
    def match_model(cls, filename, filedata):
2096
        return False
2097

    
2098

    
2099
@directory.register
2100
class RadioddityUV5GRadio(BaofengUV5R):
2101
    VENDOR = 'Radioddity'
2102
    MODEL = 'UV-5G'
2103

    
2104
    _basetype = BASETYPE_UV5R
2105
    _idents = [UV5R_MODEL_UV5G]
2106

    
2107
    @classmethod
2108
    def match_model(cls, filename, filedata):
2109
        return False
(25-25/41)