Project

General

Profile

Feature #10505 » uv5r_10505.py

Jim Unroe, 09/09/2023 12:47 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", "2100 Hz"]
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 _read_from_data(data, data_start, data_stop):
363
    data = data[data_start:data_stop]
364
    return data
365

    
366

    
367
def _get_data_from_image(radio, _data_start, _data_stop):
368
    image_data = _read_from_data(
369
        radio.get_mmap().get_byte_compatible(),
370
        _data_start,
371
        _data_stop)
372
    return image_data
373

    
374

    
375
def _firmware_version_from_data(data, version_start, version_stop):
376
    version_tag = data[version_start:version_stop]
377
    return version_tag
378

    
379

    
380
def _firmware_version_from_image(radio):
381
    version = _firmware_version_from_data(
382
        radio.get_mmap().get_byte_compatible(),
383
        radio._fw_ver_file_start,
384
        radio._fw_ver_file_stop)
385
    return version
386

    
387

    
388
def _do_ident(radio, magic, secondack=True):
389
    serial = radio.pipe
390
    serial.timeout = 1
391

    
392
    LOG.info("Sending Magic: %s" % util.hexprint(magic))
393
    for byte in magic:
394
        serial.write(bytes([byte]))
395
        time.sleep(0.01)
396
    ack = serial.read(1)
397

    
398
    if ack != b"\x06":
399
        if ack:
400
            LOG.debug(repr(ack))
401
        raise errors.RadioError("Radio did not respond")
402

    
403
    serial.write(b"\x02")
404

    
405
    # Until recently, the "ident" returned by the radios supported by this
406
    # driver have always been 8 bytes long. The image structure is the 8 byte
407
    # "ident" followed by the downloaded memory data. So all of the settings
408
    # structures are offset by 8 bytes. The ident returned from a UV-6 radio
409
    # can be 8 bytes (original model) or now 12 bytes.
410
    #
411
    # To accommodate this, the "ident" is now read one byte at a time until the
412
    # last byte ("\xdd") is encountered. The bytes containing the value "\x01"
413
    # are discarded to shrink the "ident" length down to 8 bytes to keep the
414
    # image data aligned with the existing settings structures.
415

    
416
    # Ok, get the response
417
    response = b""
418
    for i in range(1, 13):
419
        byte = serial.read(1)
420
        response += byte
421
        # stop reading once the last byte ("\xdd") is encountered
422
        if byte == b"\xDD":
423
            break
424

    
425
    # check if response is OK
426
    if len(response) in [8, 12]:
427
        # DEBUG
428
        LOG.info("Valid response, got this:")
429
        LOG.debug(util.hexprint(response))
430
        if len(response) == 12:
431
            ident = (bytes([response[0], response[3], response[5]]) +
432
                     response[7:])
433
        else:
434
            ident = response
435
    else:
436
        # bad response
437
        msg = "Unexpected response, got this:"
438
        msg += util.hexprint(response)
439
        LOG.debug(msg)
440
        raise errors.RadioError("Unexpected response from radio.")
441

    
442
    if secondack:
443
        serial.write(b"\x06")
444
        ack = serial.read(1)
445
        if ack != b"\x06":
446
            raise errors.RadioError("Radio refused clone")
447

    
448
    return ident
449

    
450

    
451
def _read_block(radio, start, size, first_command=False):
452
    msg = struct.pack(">BHB", ord("S"), start, size)
453
    radio.pipe.write(msg)
454

    
455
    if first_command is False:
456
        ack = radio.pipe.read(1)
457
        if ack != b"\x06":
458
            raise errors.RadioError(
459
                "Radio refused to send second block 0x%04x" % start)
460

    
461
    answer = radio.pipe.read(4)
462
    if len(answer) != 4:
463
        raise errors.RadioError("Radio refused to send block 0x%04x" % start)
464

    
465
    cmd, addr, length = struct.unpack(">BHB", answer)
466
    if cmd != ord("X") or addr != start or length != size:
467
        LOG.error("Invalid answer for block 0x%04x:" % start)
468
        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (cmd, addr, length))
469
        raise errors.RadioError("Unknown response from radio")
470

    
471
    chunk = radio.pipe.read(size)
472
    if not chunk:
473
        raise errors.RadioError("Radio did not send block 0x%04x" % start)
474
    elif len(chunk) != size:
475
        LOG.error("Chunk length was 0x%04i" % len(chunk))
476
        raise errors.RadioError("Radio sent incomplete block 0x%04x" % start)
477

    
478
    radio.pipe.write(b"\x06")
479
    time.sleep(0.05)
480

    
481
    return chunk
482

    
483

    
484
def _get_aux_data_from_radio(radio):
485
    block0 = _read_block(radio, 0x1E80, 0x40, True)
486
    block1 = _read_block(radio, 0x1EC0, 0x40, False)
487
    block2 = _read_block(radio, 0x1F00, 0x40, False)
488
    block3 = _read_block(radio, 0x1F40, 0x40, False)
489
    block4 = _read_block(radio, 0x1F80, 0x40, False)
490
    block5 = _read_block(radio, 0x1FC0, 0x40, False)
491
    version = block1[48:62]
492
    area1 = block2 + block3[0:32]
493
    area2 = block3[48:64]
494
    area3 = block4[16:64]
495
    # check for dropped byte bug
496
    dropped_byte = block5[15:16] == b"\xFF"  # True/False
497
    return version, area1, area2, area3, dropped_byte
498

    
499

    
500
def _get_radio_firmware_version(radio):
501
    if radio.MODEL == "BJ-UV55":
502
        block = _read_block(radio, 0x1FF0, 0x40, True)
503
        version = block[0:6]
504
        return version
505
    else:
506
        block0 = _read_block(radio, 0x1E80, 0x40, True)
507
        block1 = _read_block(radio, 0x1EC0, 0x40, False)
508
        block2 = _read_block(radio, 0x1FC0, 0x40, False)
509
        version = block1[48:62]
510
        # check for dropped byte bug
511
        dropped_byte = block2[15:16] == b"\xFF"  # True/False
512
        return version, dropped_byte
513

    
514

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

    
520

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

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

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

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

    
551

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

    
555
    if radio.MODEL == "BJ-UV55":
556
        radio_version = _get_radio_firmware_version(radio)
557
    else:
558
        radio_version, has_dropped_byte_bug = \
559
            _get_radio_firmware_version(radio)
560
    LOG.info("Radio Version is %s" % repr(radio_version))
561
    LOG.info("Radio has dropped byte bug: %s" % repr(has_dropped_byte_bug))
562

    
563
    # Main block
564
    LOG.debug("downloading main block...")
565
    for i in range(0, 0x1800, 0x40):
566
        data += _read_block(radio, i, 0x40, False)
567
        _do_status(radio, "from", i)
568
    _do_status(radio, "from", radio.get_memsize())
569
    LOG.debug("done.")
570
    if radio._aux_block:
571
        if has_dropped_byte_bug:
572
            LOG.debug("downloading aux block...")
573
            # Auxiliary block starts at 0x1ECO (?)
574
            for i in range(0x1EC0, 0x1FC0, 0x40):
575
                data += _read_block(radio, i, 0x40, False)
576
            # Switch to 0x10 block size as a workaround for new radios that
577
            # will drop byte 0x1FCF and pad the end with 0x80 if read as a
578
            # 0x40 block size
579
            for i in range(0x1FC0, 0x2000, 0x10):
580
                data += _read_block(radio, i, 0x10, False)
581
        else:
582
            LOG.debug("downloading aux block...")
583
            # Auxiliary block starts at 0x1ECO (?)
584
            for i in range(0x1EC0, 0x2000, 0x40):
585
                data += _read_block(radio, i, 0x40, False)
586

    
587
    LOG.debug("done.")
588
    return memmap.MemoryMapBytes(data)
589

    
590

    
591
def _send_block(radio, addr, data):
592
    msg = struct.pack(">BHB", ord("X"), addr, len(data))
593
    radio.pipe.write(msg + data)
594
    time.sleep(0.05)
595

    
596
    ack = radio.pipe.read(1)
597
    if ack != b"\x06":
598
        raise errors.RadioError("Radio refused to accept block 0x%04x" % addr)
599

    
600

    
601
def _do_upload(radio):
602
    ident = _ident_radio(radio)
603
    radio_upper_band = ident[3:4]
604
    image_upper_band = _upper_band_from_image(radio)
605

    
606
    if image_upper_band == vhf_220_radio or radio_upper_band == vhf_220_radio:
607
        if image_upper_band != radio_upper_band:
608
            raise errors.RadioError("Image not supported by radio")
609

    
610
    image_version = _firmware_version_from_image(radio)
611
    if radio.MODEL == "BJ-UV55":
612
        radio_version = _get_radio_firmware_version(radio)
613

    
614
        # default ranges
615
        _ranges_main_default = radio._ranges_main
616
        _ranges_aux_default = radio._ranges_aux
617
        _skip_aux_block = False
618
    else:
619
        radio_version, aux_r1, aux_r2, aux_r3, \
620
            has_dropped_byte_bug = \
621
            _get_aux_data_from_radio(radio)
622

    
623
        aux_i1 = _get_data_from_image(radio, 0x1848, 0x18A8)
624
        aux_i2 = _get_data_from_image(radio, 0x18B8, 0x18C8)
625
        aux_i3 = _get_data_from_image(radio, 0x18D8, 0x1908)
626

    
627
        # check if Aux memory of image matches Aux memory of radio
628
        if aux_i1 != aux_r1:
629
            # Area 1 does not match
630
            # The safest thing to do is to skip uploading Aux mem area.
631
            _skip_aux_block = True
632
            LOG.info("Aux memory mis-match")
633
            LOG.info("Aux area 1 from image is %s" % repr(aux_i1))
634
            LOG.info("Aux area 1 from radio is %s" % repr(aux_r1))
635
        elif aux_i2 != aux_r2:
636
            # Area 2 does not match
637
            # The safest thing to do is to skip uploading Aux mem area.
638
            _skip_aux_block = True
639
            LOG.info("Aux memory mis-match")
640
            LOG.info("Aux area 2 from image is %s" % repr(aux_i2))
641
            LOG.info("Aux area 2 from radio is %s" % repr(aux_r2))
642
        elif aux_i3 != aux_r3:
643
            # Area 3 does not match
644
            # The safest thing to do is to skip uploading Aux mem area.
645
            _skip_aux_block = True
646
            LOG.info("Aux memory mis-match")
647
            LOG.info("Aux area 3 from image is %s" % repr(aux_i3))
648
            LOG.info("Aux area 3 from radio is %s" % repr(aux_r3))
649
        else:
650
            # All areas matched
651
            # Uploading Aux mem area is permitted
652
            _skip_aux_block = False
653

    
654
        # default ranges
655
        _ranges_main_default = [
656
            (0x0008, 0x0CF8),  # skip 0x0CF8 - 0x0D08
657
            (0x0D08, 0x0DF8),  # skip 0x0DF8 - 0x0E08
658
            (0x0E08, 0x1808),
659
            ]
660

    
661
        # skip broadcast fm range
662
        _ranges_main_no_fm = [
663
            (0x0008, 0x0CF8),  # skip 0x0CF8 - 0x0D08
664
            (0x0D08, 0x0DF8),  # skip 0x0DF8 - 0x0E08
665
            (0x0E08, 0x0F48),  # skip 0x0F48 - 0x0F68
666
            (0x0F68, 0x1808)
667
            ]
668

    
669
        _ranges_aux_default = [
670
            (0x1EC0, 0x2000),
671
            ]
672

    
673
    LOG.info("Image Version is %s" % repr(image_version))
674
    LOG.info("Radio Version is %s" % repr(radio_version))
675

    
676
    # set default ranges
677
    ranges_main = _ranges_main_default
678
    ranges_aux = _ranges_aux_default
679

    
680
    # broadcast FM support check
681
    if has_dropped_byte_bug:
682
        # radios with the dropped byte bug don't appear to have
683
        # Broadcast FM support
684
        # The safest thing to do is to skip uploading 0x0F48-0x0F68
685
        ranges_main = _ranges_main_no_fm
686
    elif "\xFF" in str(radio._memobj.bcstfmlo) or \
687
            "\xFF" in str(radio._memobj.bcstfmhi):
688
        # 0x0F48-0x0F65 contains 0xFF characters
689
        # This image appears to be from a radio that doesn't have
690
        # Broadcast FM support
691
        # The safest thing to do is to skip uploading 0x0F48-0x0F68
692
        ranges_main = _ranges_main_no_fm
693
    elif _skip_aux_block:
694
        # image / radio Aux memory areas do not match
695
        # The safest thing to do is to skip uploading 0x0F48-0x0F68
696
        ranges_main = _ranges_main_no_fm
697

    
698
    if radio._all_range_flag:
699
        _skip_aux_block = False
700
        ranges_main = radio._ranges_main
701
        ranges_aux = radio._ranges_aux
702
        LOG.warning('Sending all ranges to radio as instructed')
703

    
704
    # Main block
705
    mmap = radio.get_mmap().get_byte_compatible()
706
    for start_addr, end_addr in ranges_main:
707
        for i in range(start_addr, end_addr, 0x10):
708
            _send_block(radio, i - 0x08, mmap[i:i + 0x10])
709
            _do_status(radio, "to", i)
710
        _do_status(radio, "to", radio.get_memsize())
711

    
712
    if len(mmap.get_packed()) == 0x1808:
713
        LOG.info("Old image, not writing aux block")
714
        return  # Old image, no aux block
715

    
716
    if not radio._aux_block:
717
        LOG.info("Skipped writing aux block")
718
        return  # Aux block skipped
719

    
720
    if _skip_aux_block:
721
        msg = ("This is NOT an error. The upload has finished successfully.\n"
722
               "The Main memory area was fully transferred. The Aux memory "
723
               "area (containing 'Other Settings' and 'Service Settings') was "
724
               "skipped because it appears to be from a different radio.")
725
        raise errors.RadioError(msg)
726

    
727
    # Auxiliary block at radio address 0x1EC0, our offset 0x1808
728
    for start_addr, end_addr in ranges_aux:
729
        for i in range(start_addr, end_addr, 0x10):
730
            addr = 0x1808 + (i - 0x1EC0)
731
            _send_block(radio, i, mmap[addr:addr + 0x10])
732

    
733
    if radio._all_range_flag:
734
        radio._all_range_flag = False
735
        LOG.warning('Sending all ranges to radio has completed')
736
        raise errors.RadioError(
737
            "This is NOT an error.\n"
738
            "The upload has finished successfully.\n"
739
            "Please restart CHIRP.")
740

    
741

    
742
UV5R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=4.00),
743
                     chirp_common.PowerLevel("Low",  watts=1.00)]
744

    
745
UV5R_POWER_LEVELS3 = [chirp_common.PowerLevel("High", watts=8.00),
746
                      chirp_common.PowerLevel("Med",  watts=4.00),
747
                      chirp_common.PowerLevel("Low",  watts=1.00)]
748

    
749
UV5R_DTCS = tuple(sorted(chirp_common.DTCS_CODES + (645,)))
750

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

    
754

    
755
def model_match(cls, data):
756
    """Match the opened/downloaded image to the correct version"""
757

    
758
    if len(data) == 0x1950:
759
        rid = data[0x1948:0x1950]
760
        return rid.startswith(cls.MODEL)
761
    elif len(data) == 0x1948:
762
        rid = data[cls._fw_ver_file_start:cls._fw_ver_file_stop]
763
        if any(type in rid for type in cls._basetype):
764
            return True
765
    else:
766
        return False
767

    
768

    
769
class BaofengUV5R(chirp_common.CloneModeRadio):
770

    
771
    """Baofeng UV-5R"""
772
    VENDOR = "Baofeng"
773
    MODEL = "UV-5R"
774
    BAUD_RATE = 9600
775
    NEEDS_COMPAT_SERIAL = False
776

    
777
    _memsize = 0x1808
778
    _basetype = BASETYPE_UV5R
779
    _idents = [UV5R_MODEL_291,
780
               UV5R_MODEL_ORIG
781
               ]
782
    _vhf_range = (130000000, 176000000)
783
    _220_range = (220000000, 260000000)
784
    _uhf_range = (400000000, 520000000)
785
    _aux_block = True
786
    _tri_power = False
787
    _bw_shift = False
788
    _mem_params = (0x1828  # poweron_msg offset
789
                   )
790
    # offset of fw version in image file
791
    _fw_ver_file_start = 0x1838
792
    _fw_ver_file_stop = 0x1846
793

    
794
    _ranges_main = [
795
                    (0x0008, 0x1808),
796
                   ]
797
    _ranges_aux = [
798
                   (0x1EC0, 0x2000),
799
                  ]
800
    _valid_chars = UV5R_CHARSET
801

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

    
830
    def get_features(self):
831
        rf = chirp_common.RadioFeatures()
832
        rf.has_settings = True
833
        rf.has_bank = False
834
        rf.has_cross = True
835
        rf.has_rx_dtcs = True
836
        rf.has_tuning_step = False
837
        rf.can_odd_split = True
838
        rf.valid_name_length = 7
839
        rf.valid_characters = self._valid_chars
840
        rf.valid_skips = ["", "S"]
841
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
842
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
843
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
844
        rf.valid_power_levels = UV5R_POWER_LEVELS
845
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
846
        rf.valid_modes = ["FM", "NFM"]
847
        rf.valid_tuning_steps = STEPS
848
        rf.valid_dtcs_codes = UV5R_DTCS
849

    
850
        normal_bands = [self._vhf_range, self._uhf_range]
851
        rax_bands = [self._vhf_range, self._220_range]
852

    
853
        if self._mmap is None:
854
            rf.valid_bands = [normal_bands[0], rax_bands[1], normal_bands[1]]
855
        elif not self._is_orig() and self._my_upper_band() == vhf_220_radio:
856
            rf.valid_bands = rax_bands
857
        else:
858
            rf.valid_bands = normal_bands
859
        rf.memory_bounds = (0, 127)
860
        return rf
861

    
862
    @classmethod
863
    def match_model(cls, filedata, filename):
864
        match_size = False
865
        match_model = False
866
        if len(filedata) in [0x1808, 0x1948, 0x1950]:
867
            match_size = True
868
        match_model = model_match(cls, filedata)
869

    
870
        if match_size and match_model:
871
            return True
872
        else:
873
            return False
874

    
875
    def process_mmap(self):
876
        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
877
        self._all_range_flag = False
878

    
879
    def sync_in(self):
880
        try:
881
            self._mmap = _do_download(self)
882
        except errors.RadioError:
883
            raise
884
        except Exception as e:
885
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
886
        self.process_mmap()
887

    
888
    def sync_out(self):
889
        try:
890
            _do_upload(self)
891
        except errors.RadioError:
892
            raise
893
        except Exception as e:
894
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
895

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

    
899
    def _is_txinh(self, _mem):
900
        raw_tx = ""
901
        for i in range(0, 4):
902
            raw_tx += _mem.txfreq[i].get_raw()
903
        return raw_tx == "\xFF\xFF\xFF\xFF"
904

    
905
    def _get_mem(self, number):
906
        return self._memobj.memory[number]
907

    
908
    def _get_nam(self, number):
909
        return self._memobj.names[number]
910

    
911
    def get_memory(self, number):
912
        _mem = self._get_mem(number)
913
        _nam = self._get_nam(number)
914

    
915
        mem = chirp_common.Memory()
916
        mem.number = number
917

    
918
        if _mem.get_raw()[0] == "\xff":
919
            mem.empty = True
920
            return mem
921

    
922
        mem.freq = int(_mem.rxfreq) * 10
923

    
924
        if self._is_txinh(_mem):
925
            mem.duplex = "off"
926
            mem.offset = 0
927
        elif int(_mem.rxfreq) == int(_mem.txfreq):
928
            mem.duplex = ""
929
            mem.offset = 0
930
        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
931
            mem.duplex = "split"
932
            mem.offset = int(_mem.txfreq) * 10
933
        else:
934
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
935
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
936

    
937
        for char in _nam.name:
938
            if str(char) == "\xFF":
939
                char = " "  # The UV-5R software may have 0xFF mid-name
940
            mem.name += str(char)
941
        mem.name = mem.name.rstrip()
942

    
943
        dtcs_pol = ["N", "N"]
944

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

    
961
        if _mem.rxtone in [0, 0xFFFF]:
962
            rxmode = ""
963
        elif _mem.rxtone >= 0x0258:
964
            rxmode = "Tone"
965
            mem.ctone = int(_mem.rxtone) / 10.0
966
        elif _mem.rxtone <= 0x0258:
967
            rxmode = "DTCS"
968
            if _mem.rxtone >= 0x6A:
969
                index = _mem.rxtone - 0x6A
970
                dtcs_pol[1] = "R"
971
            else:
972
                index = _mem.rxtone - 1
973
            mem.rx_dtcs = UV5R_DTCS[index]
974
        else:
975
            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
976

    
977
        if txmode == "Tone" and not rxmode:
978
            mem.tmode = "Tone"
979
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
980
            mem.tmode = "TSQL"
981
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
982
            mem.tmode = "DTCS"
983
        elif rxmode or txmode:
984
            mem.tmode = "Cross"
985
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
986

    
987
        mem.dtcs_polarity = "".join(dtcs_pol)
988

    
989
        if not _mem.scan:
990
            mem.skip = "S"
991

    
992
        if self._tri_power:
993
            levels = UV5R_POWER_LEVELS3
994
        else:
995
            levels = UV5R_POWER_LEVELS
996
        try:
997
            mem.power = levels[_mem.lowpower]
998
        except IndexError:
999
            LOG.error("Radio reported invalid power level %s (in %s)" %
1000
                      (_mem.lowpower, levels))
1001
            mem.power = levels[0]
1002

    
1003
        mem.mode = _mem.wide and "FM" or "NFM"
1004

    
1005
        mem.extra = RadioSettingGroup("Extra", "extra")
1006

    
1007
        rs = RadioSetting("bcl", "BCL",
1008
                          RadioSettingValueBoolean(_mem.bcl))
1009
        mem.extra.append(rs)
1010

    
1011
        rs = RadioSetting("pttid", "PTT ID",
1012
                          RadioSettingValueList(PTTID_LIST,
1013
                                                PTTID_LIST[_mem.pttid]))
1014
        mem.extra.append(rs)
1015

    
1016
        rs = RadioSetting("scode", "PTT ID Code",
1017
                          RadioSettingValueList(PTTIDCODE_LIST,
1018
                                                PTTIDCODE_LIST[_mem.scode]))
1019
        mem.extra.append(rs)
1020

    
1021
        immutable = []
1022

    
1023
        if self.MODEL == "GT-5R":
1024
            if not ((mem.freq >= self.vhftx[0] and mem.freq < self.vhftx[1]) or
1025
                    (mem.freq >= self.uhftx[0] and mem.freq < self.uhftx[1])):
1026
                mem.duplex = 'off'
1027
                mem.offset = 0
1028
                immutable = ["duplex", "offset"]
1029

    
1030
        mem.immutable = immutable
1031

    
1032
        return mem
1033

    
1034
    def _set_mem(self, number):
1035
        return self._memobj.memory[number]
1036

    
1037
    def _set_nam(self, number):
1038
        return self._memobj.names[number]
1039

    
1040
    def set_memory(self, mem):
1041
        _mem = self._get_mem(mem.number)
1042
        _nam = self._get_nam(mem.number)
1043

    
1044
        if mem.empty:
1045
            _mem.set_raw("\xff" * 16)
1046
            _nam.set_raw("\xff" * 16)
1047
            return
1048

    
1049
        was_empty = False
1050
        # same method as used in get_memory to find
1051
        # out whether a raw memory is empty
1052
        if _mem.get_raw()[0] == "\xff":
1053
            was_empty = True
1054
            LOG.debug("UV5R: this mem was empty")
1055
        else:
1056
            # memorize old extra-values before erasing the whole memory
1057
            # used to solve issue 4121
1058
            LOG.debug("mem was not empty, memorize extra-settings")
1059
            prev_bcl = _mem.bcl.get_value()
1060
            prev_scode = _mem.scode.get_value()
1061
            prev_pttid = _mem.pttid.get_value()
1062

    
1063
        _mem.set_raw("\x00" * 16)
1064

    
1065
        _mem.rxfreq = mem.freq / 10
1066

    
1067
        if mem.duplex == "off":
1068
            for i in range(0, 4):
1069
                _mem.txfreq[i].set_raw("\xFF")
1070
        elif mem.duplex == "split":
1071
            _mem.txfreq = mem.offset / 10
1072
        elif mem.duplex == "+":
1073
            _mem.txfreq = (mem.freq + mem.offset) / 10
1074
        elif mem.duplex == "-":
1075
            _mem.txfreq = (mem.freq - mem.offset) / 10
1076
        else:
1077
            _mem.txfreq = mem.freq / 10
1078

    
1079
        _namelength = self.get_features().valid_name_length
1080
        for i in range(_namelength):
1081
            try:
1082
                _nam.name[i] = mem.name[i]
1083
            except IndexError:
1084
                _nam.name[i] = "\xFF"
1085

    
1086
        rxmode = txmode = ""
1087
        if mem.tmode == "Tone":
1088
            _mem.txtone = int(mem.rtone * 10)
1089
            _mem.rxtone = 0
1090
        elif mem.tmode == "TSQL":
1091
            _mem.txtone = int(mem.ctone * 10)
1092
            _mem.rxtone = int(mem.ctone * 10)
1093
        elif mem.tmode == "DTCS":
1094
            rxmode = txmode = "DTCS"
1095
            _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
1096
            _mem.rxtone = UV5R_DTCS.index(mem.dtcs) + 1
1097
        elif mem.tmode == "Cross":
1098
            txmode, rxmode = mem.cross_mode.split("->", 1)
1099
            if txmode == "Tone":
1100
                _mem.txtone = int(mem.rtone * 10)
1101
            elif txmode == "DTCS":
1102
                _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
1103
            else:
1104
                _mem.txtone = 0
1105
            if rxmode == "Tone":
1106
                _mem.rxtone = int(mem.ctone * 10)
1107
            elif rxmode == "DTCS":
1108
                _mem.rxtone = UV5R_DTCS.index(mem.rx_dtcs) + 1
1109
            else:
1110
                _mem.rxtone = 0
1111
        else:
1112
            _mem.rxtone = 0
1113
            _mem.txtone = 0
1114

    
1115
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
1116
            _mem.txtone += 0x69
1117
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
1118
            _mem.rxtone += 0x69
1119

    
1120
        _mem.scan = mem.skip != "S"
1121
        _mem.wide = mem.mode == "FM"
1122

    
1123
        if mem.power:
1124
            if self._tri_power:
1125
                levels = [str(l) for l in UV5R_POWER_LEVELS3]
1126
                _mem.lowpower = levels.index(str(mem.power))
1127
            else:
1128
                _mem.lowpower = UV5R_POWER_LEVELS.index(mem.power)
1129
        else:
1130
            _mem.lowpower = 0
1131

    
1132
        if not was_empty:
1133
            # restoring old extra-settings (issue 4121
1134
            _mem.bcl.set_value(prev_bcl)
1135
            _mem.scode.set_value(prev_scode)
1136
            _mem.pttid.set_value(prev_pttid)
1137

    
1138
        for setting in mem.extra:
1139
            setattr(_mem, setting.get_name(), setting.value)
1140

    
1141
    def _is_orig(self):
1142
        version_tag = _firmware_version_from_image(self)
1143
        try:
1144
            if b'BFB' in version_tag:
1145
                idx = version_tag.index(b"BFB") + 3
1146
                version = int(version_tag[idx:idx + 3])
1147
                return version < 291
1148
            return False
1149
        except:
1150
            pass
1151
        raise errors.RadioError("Unable to parse version string %s" %
1152
                                version_tag)
1153

    
1154
    def _my_version(self):
1155
        version_tag = _firmware_version_from_image(self)
1156
        if b'BFB' in version_tag:
1157
            idx = version_tag.index(b"BFB") + 3
1158
            return int(version_tag[idx:idx + 3])
1159

    
1160
        raise Exception("Unrecognized firmware version string")
1161

    
1162
    def _my_upper_band(self):
1163
        band_tag = _upper_band_from_image(self)
1164
        return band_tag
1165

    
1166
    def _get_settings(self):
1167
        _mem = self._memobj
1168
        _ani = self._memobj.ani
1169
        _settings = self._memobj.settings
1170
        _squelch = self._memobj.squelch_new
1171
        _vfoa = self._memobj.vfoa
1172
        _vfob = self._memobj.vfob
1173
        _wmchannel = self._memobj.wmchannel
1174

    
1175
        basic = RadioSettingGroup("basic", "Basic Settings")
1176
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
1177

    
1178
        group = RadioSettings(basic, advanced)
1179

    
1180
        rs = RadioSetting("squelch", "Carrier Squelch Level",
1181
                          RadioSettingValueInteger(0, 9, _settings.squelch))
1182
        basic.append(rs)
1183

    
1184
        rs = RadioSetting("save", "Battery Saver",
1185
                          RadioSettingValueList(
1186
                              SAVE_LIST, SAVE_LIST[_settings.save]))
1187
        basic.append(rs)
1188

    
1189
        rs = RadioSetting("vox", "VOX Sensitivity",
1190
                          RadioSettingValueList(
1191
                              VOX_LIST, VOX_LIST[_settings.vox]))
1192
        advanced.append(rs)
1193

    
1194
        if self.MODEL == "UV-6":
1195
            # NOTE: The UV-6 calls this byte voxenable, but the UV-5R calls it
1196
            # autolk. Since this is a minor difference, it will be referred to
1197
            # by the wrong name for the UV-6.
1198
            rs = RadioSetting("autolk", "Vox",
1199
                              RadioSettingValueBoolean(_settings.autolk))
1200
            advanced.append(rs)
1201

    
1202
        if self.MODEL != "UV-6":
1203
            rs = RadioSetting("abr", "Backlight Timeout",
1204
                              RadioSettingValueInteger(0, 24, _settings.abr))
1205
            basic.append(rs)
1206

    
1207
        rs = RadioSetting("tdr", "Dual Watch",
1208
                          RadioSettingValueBoolean(_settings.tdr))
1209
        advanced.append(rs)
1210

    
1211
        if self.MODEL == "UV-6":
1212
            rs = RadioSetting("tdrch", "Dual Watch Channel",
1213
                              RadioSettingValueList(
1214
                                  TDRCH_LIST, TDRCH_LIST[_settings.tdrch]))
1215
            advanced.append(rs)
1216

    
1217
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
1218
                              RadioSettingValueBoolean(_settings.tdrab))
1219
            advanced.append(rs)
1220
        else:
1221
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
1222
                              RadioSettingValueList(
1223
                                  TDRAB_LIST, TDRAB_LIST[_settings.tdrab]))
1224
            advanced.append(rs)
1225

    
1226
        if self.MODEL == "UV-6":
1227
            rs = RadioSetting("alarm", "Alarm Sound",
1228
                              RadioSettingValueBoolean(_settings.alarm))
1229
            advanced.append(rs)
1230

    
1231
        if _settings.almod > 0x02:
1232
            val = 0x01
1233
        else:
1234
            val = _settings.almod
1235
        rs = RadioSetting("almod", "Alarm Mode",
1236
                          RadioSettingValueList(
1237
                              ALMOD_LIST, ALMOD_LIST[val]))
1238
        advanced.append(rs)
1239

    
1240
        rs = RadioSetting("beep", "Beep",
1241
                          RadioSettingValueBoolean(_settings.beep))
1242
        basic.append(rs)
1243

    
1244
        rs = RadioSetting("timeout", "Timeout Timer",
1245
                          RadioSettingValueList(
1246
                              TIMEOUT_LIST, TIMEOUT_LIST[_settings.timeout]))
1247
        basic.append(rs)
1248

    
1249
        if ((self._is_orig() and self._my_version() < 251) or
1250
                (self.MODEL in ["TI-F8+", "TS-T9+"])):
1251
            rs = RadioSetting("voice", "Voice",
1252
                              RadioSettingValueBoolean(_settings.voice))
1253
            advanced.append(rs)
1254
        else:
1255
            rs = RadioSetting("voice", "Voice",
1256
                              RadioSettingValueList(
1257
                                  VOICE_LIST, VOICE_LIST[_settings.voice]))
1258
            advanced.append(rs)
1259

    
1260
        rs = RadioSetting("screv", "Scan Resume",
1261
                          RadioSettingValueList(
1262
                              RESUME_LIST, RESUME_LIST[_settings.screv]))
1263
        advanced.append(rs)
1264

    
1265
        if self.MODEL != "UV-6":
1266
            rs = RadioSetting("mdfa", "Display Mode (A)",
1267
                              RadioSettingValueList(
1268
                                  MODE_LIST, MODE_LIST[_settings.mdfa]))
1269
            basic.append(rs)
1270

    
1271
            rs = RadioSetting("mdfb", "Display Mode (B)",
1272
                              RadioSettingValueList(
1273
                                  MODE_LIST, MODE_LIST[_settings.mdfb]))
1274
            basic.append(rs)
1275

    
1276
        rs = RadioSetting("bcl", "Busy Channel Lockout",
1277
                          RadioSettingValueBoolean(_settings.bcl))
1278
        advanced.append(rs)
1279

    
1280
        if self.MODEL != "UV-6":
1281
            rs = RadioSetting("autolk", "Automatic Key Lock",
1282
                              RadioSettingValueBoolean(_settings.autolk))
1283
            advanced.append(rs)
1284

    
1285
        rs = RadioSetting("fmradio", "Broadcast FM Radio",
1286
                          RadioSettingValueBoolean(_settings.fmradio))
1287
        advanced.append(rs)
1288

    
1289
        if self.MODEL != "UV-6":
1290
            rs = RadioSetting("wtled", "Standby LED Color",
1291
                              RadioSettingValueList(
1292
                                  COLOR_LIST, COLOR_LIST[_settings.wtled]))
1293
            basic.append(rs)
1294

    
1295
            rs = RadioSetting("rxled", "RX LED Color",
1296
                              RadioSettingValueList(
1297
                                  COLOR_LIST, COLOR_LIST[_settings.rxled]))
1298
            basic.append(rs)
1299

    
1300
            rs = RadioSetting("txled", "TX LED Color",
1301
                              RadioSettingValueList(
1302
                                  COLOR_LIST, COLOR_LIST[_settings.txled]))
1303
            basic.append(rs)
1304

    
1305
        if isinstance(self, BaofengUV82Radio):
1306
            rs = RadioSetting("roger", "Roger Beep (TX)",
1307
                              RadioSettingValueBoolean(_settings.roger))
1308
            basic.append(rs)
1309
            rs = RadioSetting("rogerrx", "Roger Beep (RX)",
1310
                              RadioSettingValueList(
1311
                                  ROGERRX_LIST,
1312
                                  ROGERRX_LIST[_settings.rogerrx]))
1313
            basic.append(rs)
1314
        else:
1315
            rs = RadioSetting("roger", "Roger Beep",
1316
                              RadioSettingValueBoolean(_settings.roger))
1317
            basic.append(rs)
1318

    
1319
        rs = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)",
1320
                          RadioSettingValueBoolean(_settings.ste))
1321
        advanced.append(rs)
1322

    
1323
        rs = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)",
1324
                          RadioSettingValueList(
1325
                              RPSTE_LIST, RPSTE_LIST[_settings.rpste]))
1326
        advanced.append(rs)
1327

    
1328
        rs = RadioSetting("rptrl", "STE Repeater Delay",
1329
                          RadioSettingValueList(
1330
                              STEDELAY_LIST, STEDELAY_LIST[_settings.rptrl]))
1331
        advanced.append(rs)
1332

    
1333
        if self.MODEL != "UV-6":
1334
            rs = RadioSetting("reset", "RESET Menu",
1335
                              RadioSettingValueBoolean(_settings.reset))
1336
            advanced.append(rs)
1337

    
1338
            rs = RadioSetting("menu", "All Menus",
1339
                              RadioSettingValueBoolean(_settings.menu))
1340
            advanced.append(rs)
1341

    
1342
        if self.MODEL == "F-11":
1343
            # this is an F-11 only feature
1344
            rs = RadioSetting("vfomrlock", "VFO/MR Button",
1345
                              RadioSettingValueBoolean(_settings.vfomrlock))
1346
            advanced.append(rs)
1347

    
1348
        if isinstance(self, BaofengUV82Radio):
1349
            # this is a UV-82C only feature
1350
            rs = RadioSetting("vfomrlock", "VFO/MR Switching (UV-82C only)",
1351
                              RadioSettingValueBoolean(_settings.vfomrlock))
1352
            advanced.append(rs)
1353

    
1354
        if self.MODEL == "UV-82HP":
1355
            # this is a UV-82HP only feature
1356
            rs = RadioSetting(
1357
                "vfomrlock", "VFO/MR Switching (BTech UV-82HP only)",
1358
                RadioSettingValueBoolean(_settings.vfomrlock))
1359
            advanced.append(rs)
1360

    
1361
        if isinstance(self, BaofengUV82Radio):
1362
            # this is an UV-82C only feature
1363
            rs = RadioSetting("singleptt", "Single PTT (UV-82C only)",
1364
                              RadioSettingValueBoolean(_settings.singleptt))
1365
            advanced.append(rs)
1366

    
1367
        if self.MODEL == "UV-82HP":
1368
            # this is an UV-82HP only feature
1369
            rs = RadioSetting("singleptt", "Single PTT (BTech UV-82HP only)",
1370
                              RadioSettingValueBoolean(_settings.singleptt))
1371
            advanced.append(rs)
1372

    
1373
        if self.MODEL == "UV-82HP":
1374
            # this is an UV-82HP only feature
1375
            rs = RadioSetting(
1376
                "tdrch", "Tone Burst Frequency (BTech UV-82HP only)",
1377
                RadioSettingValueList(RTONE_LIST, RTONE_LIST[_settings.tdrch]))
1378
            advanced.append(rs)
1379

    
1380
        def set_range_flag(setting):
1381
            val = [85, 115, 101, 65, 116, 79, 119, 110, 82, 105, 115, 107]
1382
            if [ord(x) for x in str(setting.value).strip()] == val:
1383
                self._all_range_flag = True
1384
            else:
1385
                self._all_range_flag = False
1386
            LOG.debug('Set range flag to %s' % self._all_range_flag)
1387

    
1388
        rs = RadioSetting("allrange", "Range Override Parameter",
1389
                          RadioSettingValueString(0, 12, "Default"))
1390
        rs.set_apply_callback(set_range_flag)
1391
        advanced.append(rs)
1392

    
1393
        if len(self._mmap.get_packed()) == 0x1808:
1394
            # Old image, without aux block
1395
            return group
1396

    
1397
        if self.MODEL != "UV-6":
1398
            other = RadioSettingGroup("other", "Other Settings")
1399
            group.append(other)
1400

    
1401
            def _filter(name):
1402
                filtered = ""
1403
                for char in str(name):
1404
                    if char in chirp_common.CHARSET_ASCII:
1405
                        filtered += char
1406
                    else:
1407
                        filtered += " "
1408
                return filtered
1409

    
1410
            _msg = self._memobj.firmware_msg
1411
            val = RadioSettingValueString(0, 7, _filter(_msg.line1))
1412
            val.set_mutable(False)
1413
            rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
1414
            other.append(rs)
1415

    
1416
            val = RadioSettingValueString(0, 7, _filter(_msg.line2))
1417
            val.set_mutable(False)
1418
            rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
1419
            other.append(rs)
1420

    
1421
            _msg = self._memobj.sixpoweron_msg
1422
            val = RadioSettingValueString(0, 7, _filter(_msg.line1))
1423
            val.set_mutable(False)
1424
            rs = RadioSetting("sixpoweron_msg.line1",
1425
                              "6+Power-On Message 1", val)
1426
            other.append(rs)
1427
            val = RadioSettingValueString(0, 7, _filter(_msg.line2))
1428
            val.set_mutable(False)
1429
            rs = RadioSetting("sixpoweron_msg.line2",
1430
                              "6+Power-On Message 2", val)
1431
            other.append(rs)
1432

    
1433
            #aux_mem_warn = ('TBD - Some message provided here')
1434

    
1435
            aux_mem_warn = ("This setting will only be uploaded to your radio "
1436
                            "if CHIRP can verify that your radio's Aux "
1437
                            "memory area sufficiently matches the Aux memory "
1438
                            "area of the 'image' that is being uploaded.")
1439

    
1440
            _msg = self._memobj.poweron_msg
1441
            rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
1442
                              RadioSettingValueString(
1443
                                  0, 7, _filter(_msg.line1)))
1444
            rs.set_warning(aux_mem_warn)
1445
            other.append(rs)
1446
            rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
1447
                              RadioSettingValueString(
1448
                                  0, 7, _filter(_msg.line2)))
1449
            rs.set_warning(aux_mem_warn)
1450
            other.append(rs)
1451

    
1452
            rs = RadioSetting("ponmsg", "Power-On Message",
1453
                              RadioSettingValueList(
1454
                                  PONMSG_LIST,
1455
                                  PONMSG_LIST[_settings.ponmsg]))
1456
            other.append(rs)
1457

    
1458
            if self._is_orig():
1459
                limit = "limits_old"
1460
            else:
1461
                limit = "limits_new"
1462

    
1463
            vhf_limit = getattr(self._memobj, limit).vhf
1464
            rs = RadioSetting("%s.vhf.lower" % limit,
1465
                              "VHF Lower Limit (MHz)",
1466
                              RadioSettingValueInteger(1, 1000,
1467
                                                       vhf_limit.lower))
1468
            rs.set_warning(aux_mem_warn)
1469
            other.append(rs)
1470

    
1471
            rs = RadioSetting("%s.vhf.upper" % limit,
1472
                              "VHF Upper Limit (MHz)",
1473
                              RadioSettingValueInteger(1, 1000,
1474
                                                       vhf_limit.upper))
1475
            rs.set_warning(aux_mem_warn)
1476
            other.append(rs)
1477

    
1478
            rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled",
1479
                              RadioSettingValueBoolean(vhf_limit.enable))
1480
            rs.set_warning(aux_mem_warn)
1481
            other.append(rs)
1482

    
1483
            uhf_limit = getattr(self._memobj, limit).uhf
1484
            rs = RadioSetting("%s.uhf.lower" % limit,
1485
                              "UHF Lower Limit (MHz)",
1486
                              RadioSettingValueInteger(1, 1000,
1487
                                                       uhf_limit.lower))
1488
            rs.set_warning(aux_mem_warn)
1489
            other.append(rs)
1490
            rs = RadioSetting("%s.uhf.upper" % limit,
1491
                              "UHF Upper Limit (MHz)",
1492
                              RadioSettingValueInteger(1, 1000,
1493
                                                       uhf_limit.upper))
1494
            rs.set_warning(aux_mem_warn)
1495
            other.append(rs)
1496
            rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled",
1497
                              RadioSettingValueBoolean(uhf_limit.enable))
1498
            rs.set_warning(aux_mem_warn)
1499
            other.append(rs)
1500

    
1501
        if self.MODEL != "UV-6":
1502
            workmode = RadioSettingGroup("workmode", "Work Mode Settings")
1503
            group.append(workmode)
1504

    
1505
            rs = RadioSetting("displayab", "Display",
1506
                              RadioSettingValueList(
1507
                                  AB_LIST, AB_LIST[_settings.displayab]))
1508
            workmode.append(rs)
1509

    
1510
            rs = RadioSetting("workmode", "VFO/MR Mode",
1511
                              RadioSettingValueList(
1512
                                  WORKMODE_LIST,
1513
                                  WORKMODE_LIST[_settings.workmode]))
1514
            workmode.append(rs)
1515

    
1516
            rs = RadioSetting("keylock", "Keypad Lock",
1517
                              RadioSettingValueBoolean(_settings.keylock))
1518
            workmode.append(rs)
1519

    
1520
            rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
1521
                              RadioSettingValueInteger(0, 127,
1522
                                                       _wmchannel.mrcha))
1523
            workmode.append(rs)
1524

    
1525
            rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
1526
                              RadioSettingValueInteger(0, 127,
1527
                                                       _wmchannel.mrchb))
1528
            workmode.append(rs)
1529

    
1530
            def convert_bytes_to_freq(bytes):
1531
                real_freq = 0
1532
                for byte in bytes:
1533
                    real_freq = (real_freq * 10) + byte
1534
                return chirp_common.format_freq(real_freq * 10)
1535

    
1536
            def my_validate(value):
1537
                value = chirp_common.parse_freq(value)
1538
                if 17400000 <= value and value < 40000000:
1539
                    msg = ("Can't be between 174.00000-400.00000")
1540
                    raise InvalidValueError(msg)
1541
                return chirp_common.format_freq(value)
1542

    
1543
            def apply_freq(setting, obj):
1544
                value = chirp_common.parse_freq(str(setting.value)) / 10
1545
                obj.band = value >= 40000000
1546
                for i in range(7, -1, -1):
1547
                    obj.freq[i] = value % 10
1548
                    value /= 10
1549

    
1550
            val1a = RadioSettingValueString(0, 10,
1551
                                            convert_bytes_to_freq(_vfoa.freq))
1552
            val1a.set_validate_callback(my_validate)
1553
            rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a)
1554
            rs.set_apply_callback(apply_freq, _vfoa)
1555
            workmode.append(rs)
1556

    
1557
            val1b = RadioSettingValueString(0, 10,
1558
                                            convert_bytes_to_freq(_vfob.freq))
1559
            val1b.set_validate_callback(my_validate)
1560
            rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b)
1561
            rs.set_apply_callback(apply_freq, _vfob)
1562
            workmode.append(rs)
1563

    
1564
            rs = RadioSetting("vfoa.sftd", "VFO A Shift",
1565
                              RadioSettingValueList(
1566
                                  SHIFTD_LIST, SHIFTD_LIST[_vfoa.sftd]))
1567
            workmode.append(rs)
1568

    
1569
            rs = RadioSetting("vfob.sftd", "VFO B Shift",
1570
                              RadioSettingValueList(
1571
                                  SHIFTD_LIST, SHIFTD_LIST[_vfob.sftd]))
1572
            workmode.append(rs)
1573

    
1574
            def convert_bytes_to_offset(bytes):
1575
                real_offset = 0
1576
                for byte in bytes:
1577
                    real_offset = (real_offset * 10) + byte
1578
                return chirp_common.format_freq(real_offset * 1000)
1579

    
1580
            def apply_offset(setting, obj):
1581
                value = chirp_common.parse_freq(str(setting.value)) / 1000
1582
                for i in range(5, -1, -1):
1583
                    obj.offset[i] = value % 10
1584
                    value /= 10
1585

    
1586
            val1a = RadioSettingValueString(
1587
                0, 10, convert_bytes_to_offset(_vfoa.offset))
1588
            rs = RadioSetting("vfoa.offset",
1589
                              "VFO A Offset (0.0-999.999)", val1a)
1590
            rs.set_apply_callback(apply_offset, _vfoa)
1591
            workmode.append(rs)
1592

    
1593
            val1b = RadioSettingValueString(
1594
                0, 10, convert_bytes_to_offset(_vfob.offset))
1595
            rs = RadioSetting("vfob.offset",
1596
                              "VFO B Offset (0.0-999.999)", val1b)
1597
            rs.set_apply_callback(apply_offset, _vfob)
1598
            workmode.append(rs)
1599

    
1600
            if self._tri_power:
1601
                if _vfoa.txpower3 > 0x02:
1602
                    val = 0x00
1603
                else:
1604
                    val = _vfoa.txpower3
1605
                rs = RadioSetting("vfoa.txpower3", "VFO A Power",
1606
                                  RadioSettingValueList(
1607
                                      TXPOWER3_LIST,
1608
                                      TXPOWER3_LIST[val]))
1609
                workmode.append(rs)
1610

    
1611
                if _vfob.txpower3 > 0x02:
1612
                    val = 0x00
1613
                else:
1614
                    val = _vfob.txpower3
1615
                rs = RadioSetting("vfob.txpower3", "VFO B Power",
1616
                                  RadioSettingValueList(
1617
                                      TXPOWER3_LIST,
1618
                                      TXPOWER3_LIST[val]))
1619
                workmode.append(rs)
1620
            else:
1621
                rs = RadioSetting("vfoa.txpower", "VFO A Power",
1622
                                  RadioSettingValueList(
1623
                                      TXPOWER_LIST,
1624
                                      TXPOWER_LIST[_vfoa.txpower]))
1625
                workmode.append(rs)
1626

    
1627
                rs = RadioSetting("vfob.txpower", "VFO B Power",
1628
                                  RadioSettingValueList(
1629
                                      TXPOWER_LIST,
1630
                                      TXPOWER_LIST[_vfob.txpower]))
1631
                workmode.append(rs)
1632

    
1633
            rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth",
1634
                              RadioSettingValueList(
1635
                                  BANDWIDTH_LIST,
1636
                                  BANDWIDTH_LIST[_vfoa.widenarr]))
1637
            workmode.append(rs)
1638

    
1639
            rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
1640
                              RadioSettingValueList(
1641
                                  BANDWIDTH_LIST,
1642
                                  BANDWIDTH_LIST[_vfob.widenarr]))
1643
            workmode.append(rs)
1644

    
1645
            rs = RadioSetting("vfoa.scode", "VFO A PTT-ID",
1646
                              RadioSettingValueList(
1647
                                  PTTIDCODE_LIST, PTTIDCODE_LIST[_vfoa.scode]))
1648
            workmode.append(rs)
1649

    
1650
            rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
1651
                              RadioSettingValueList(
1652
                                  PTTIDCODE_LIST, PTTIDCODE_LIST[_vfob.scode]))
1653
            workmode.append(rs)
1654

    
1655
            if not self._is_orig():
1656
                rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
1657
                                  RadioSettingValueList(
1658
                                      STEP291_LIST, STEP291_LIST[_vfoa.step]))
1659
                workmode.append(rs)
1660
                rs = RadioSetting("vfob.step", "VFO B Tuning Step",
1661
                                  RadioSettingValueList(
1662
                                      STEP291_LIST, STEP291_LIST[_vfob.step]))
1663
                workmode.append(rs)
1664
            else:
1665
                rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
1666
                                  RadioSettingValueList(
1667
                                      STEP_LIST, STEP_LIST[_vfoa.step]))
1668
                workmode.append(rs)
1669
                rs = RadioSetting("vfob.step", "VFO B Tuning Step",
1670
                                  RadioSettingValueList(
1671
                                      STEP_LIST, STEP_LIST[_vfob.step]))
1672
                workmode.append(rs)
1673

    
1674
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1675
        group.append(dtmf)
1676

    
1677
        if str(self._memobj.firmware_msg.line1) == "HN5RV01":
1678
            dtmfchars = "0123456789ABCD*#"
1679
        else:
1680
            dtmfchars = "0123456789 *#ABCD"
1681

    
1682
        for i in range(0, 15):
1683
            _codeobj = self._memobj.pttid[i].code
1684
            _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1685
            val = RadioSettingValueString(0, 5, _code, False)
1686
            val.set_charset(dtmfchars)
1687
            rs = RadioSetting("pttid/%i.code" % i,
1688
                              "PTT ID Code %i" % (i + 1), val)
1689

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

    
1701
        dtmfcharsani = "0123456789"
1702

    
1703
        _codeobj = self._memobj.ani.code
1704
        _code = "".join([dtmfcharsani[x] for x in _codeobj if int(x) < 0x1F])
1705
        val = RadioSettingValueString(0, 5, _code, False)
1706
        val.set_charset(dtmfcharsani)
1707
        rs = RadioSetting("ani.code", "ANI Code", val)
1708

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

    
1720
        rs = RadioSetting("ani.aniid", "ANI ID",
1721
                          RadioSettingValueList(PTTID_LIST,
1722
                                                PTTID_LIST[_ani.aniid]))
1723
        dtmf.append(rs)
1724

    
1725
        _codeobj = self._memobj.ani.alarmcode
1726
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1727
        val = RadioSettingValueString(0, 3, _code, False)
1728
        val.set_charset(dtmfchars)
1729
        rs = RadioSetting("ani.alarmcode", "Alarm Code", val)
1730

    
1731
        def apply_code(setting, obj):
1732
            alarmcode = []
1733
            for j in range(0, 3):
1734
                try:
1735
                    alarmcode.append(dtmfchars.index(str(setting.value)[j]))
1736
                except IndexError:
1737
                    alarmcode.append(0xFF)
1738
            obj.alarmcode = alarmcode
1739
        rs.set_apply_callback(apply_code, _ani)
1740
        dtmf.append(rs)
1741

    
1742
        rs = RadioSetting("dtmfst", "DTMF Sidetone",
1743
                          RadioSettingValueList(DTMFST_LIST,
1744
                                                DTMFST_LIST[_settings.dtmfst]))
1745
        dtmf.append(rs)
1746

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

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

    
1765
        rs = RadioSetting("pttlt", "PTT ID Delay",
1766
                          RadioSettingValueInteger(0, 50, _settings.pttlt))
1767
        dtmf.append(rs)
1768

    
1769
        if not self._is_orig() and self._aux_block:
1770
            service = RadioSettingGroup("service", "Service Settings")
1771
            group.append(service)
1772

    
1773
            for band in ["vhf", "uhf"]:
1774
                for index in range(0, 10):
1775
                    key = "squelch_new.%s.sql%i" % (band, index)
1776
                    if band == "vhf":
1777
                        _obj = self._memobj.squelch_new.vhf
1778
                    elif band == "uhf":
1779
                        _obj = self._memobj.squelch_new.uhf
1780
                    name = "%s Squelch %i" % (band.upper(), index)
1781
                    rs = RadioSetting(key, name,
1782
                                      RadioSettingValueInteger(
1783
                                          0, 123,
1784
                                          getattr(_obj, "sql%i" % (index))))
1785
                    rs.set_warning(aux_mem_warn)
1786
                    service.append(rs)
1787

    
1788
        return group
1789

    
1790
    def get_settings(self):
1791
        try:
1792
            return self._get_settings()
1793
        except:
1794
            import traceback
1795
            LOG.error("Failed to parse settings: %s", traceback.format_exc())
1796
            return None
1797

    
1798
    def set_settings(self, settings):
1799
        _settings = self._memobj.settings
1800
        for element in settings:
1801
            if not isinstance(element, RadioSetting):
1802
                self.set_settings(element)
1803
                continue
1804
            else:
1805
                try:
1806
                    name = element.get_name()
1807
                    if "." in name:
1808
                        bits = name.split(".")
1809
                        obj = self._memobj
1810
                        for bit in bits[:-1]:
1811
                            if "/" in bit:
1812
                                bit, index = bit.split("/", 1)
1813
                                index = int(index)
1814
                                obj = getattr(obj, bit)[index]
1815
                            else:
1816
                                obj = getattr(obj, bit)
1817
                        setting = bits[-1]
1818
                    else:
1819
                        obj = _settings
1820
                        setting = element.get_name()
1821

    
1822
                    if element.has_apply_callback():
1823
                        LOG.debug("Using apply callback")
1824
                        element.run_apply_callback()
1825
                    elif element.value.get_mutable():
1826
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1827
                        setattr(obj, setting, element.value)
1828
                except Exception:
1829
                    LOG.debug(element.get_name())
1830
                    raise
1831

    
1832

    
1833
class UV5XAlias(chirp_common.Alias):
1834
    VENDOR = "Baofeng"
1835
    MODEL = "UV-5X"
1836

    
1837

    
1838
class RT5RAlias(chirp_common.Alias):
1839
    VENDOR = "Retevis"
1840
    MODEL = "RT5R"
1841

    
1842

    
1843
class RT5RVAlias(chirp_common.Alias):
1844
    VENDOR = "Retevis"
1845
    MODEL = "RT5RV"
1846

    
1847

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

    
1852

    
1853
class RT5_TPAlias(chirp_common.Alias):
1854
    VENDOR = "Retevis"
1855
    MODEL = "RT5(tri-power)"
1856

    
1857

    
1858
class RH5RAlias(chirp_common.Alias):
1859
    VENDOR = "Rugged"
1860
    MODEL = "RH5R"
1861

    
1862

    
1863
class ROUV5REXAlias(chirp_common.Alias):
1864
    VENDOR = "Radioddity"
1865
    MODEL = "UV-5R EX"
1866

    
1867

    
1868
class A5RAlias(chirp_common.Alias):
1869
    VENDOR = "Ansoko"
1870
    MODEL = "A-5R"
1871

    
1872

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

    
1878

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

    
1886
    def _is_orig(self):
1887
        # Override this for F11 to always return False
1888
        return False
1889

    
1890

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

    
1900
    def _is_orig(self):
1901
        # Override this for UV82 to always return False
1902
        return False
1903

    
1904

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

    
1911
    def get_features(self):
1912
        rf = BaofengUV5R.get_features(self)
1913
        rf.valid_bands = [self._vhf_range,
1914
                          (200000000, 260000000),
1915
                          self._uhf_range]
1916
        return rf
1917

    
1918

    
1919
@directory.register
1920
class BaofengUV6Radio(BaofengUV5R):
1921

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

    
1931
    def get_features(self):
1932
        rf = BaofengUV5R.get_features(self)
1933
        rf.memory_bounds = (1, 128)
1934
        return rf
1935

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

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

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

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

    
1948
    def _is_orig(self):
1949
        # Override this for UV6 to always return False
1950
        return False
1951

    
1952

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

    
1963
    def get_features(self):
1964
        rf = BaofengUV5R.get_features(self)
1965
        rf.valid_power_levels = UV5R_POWER_LEVELS3
1966
        return rf
1967

    
1968
    def _is_orig(self):
1969
        # Override this for KT980HP to always return False
1970
        return False
1971

    
1972

    
1973
class ROGA5SAlias(chirp_common.Alias):
1974
    VENDOR = "Radioddity"
1975
    MODEL = "GA-5S"
1976

    
1977

    
1978
class UV5XPAlias(chirp_common.Alias):
1979
    VENDOR = "Baofeng"
1980
    MODEL = "UV-5XP"
1981

    
1982

    
1983
class TSTIF8Alias(chirp_common.Alias):
1984
    VENDOR = "TechSide"
1985
    MODEL = "TI-F8+"
1986

    
1987

    
1988
class TenwayUV5RPro(chirp_common.Alias):
1989
    VENDOR = 'Tenway'
1990
    MODEL = 'UV-5R Pro'
1991

    
1992

    
1993
class TSTST9Alias(chirp_common.Alias):
1994
    VENDOR = "TechSide"
1995
    MODEL = "TS-T9+"
1996

    
1997

    
1998
class TDUV5RRadio(chirp_common.Alias):
1999
    VENDOR = "TIDRADIO"
2000
    MODEL = "TD-UV5R TriPower"
2001

    
2002

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

    
2017
    def get_features(self):
2018
        rf = BaofengUV5R.get_features(self)
2019
        rf.valid_power_levels = UV5R_POWER_LEVELS3
2020
        return rf
2021

    
2022
    def _is_orig(self):
2023
        # Override this for BFF8HP to always return False
2024
        return False
2025

    
2026

    
2027
class TenwayUV82Pro(chirp_common.Alias):
2028
    VENDOR = 'Tenway'
2029
    MODEL = 'UV-82 Pro'
2030

    
2031

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

    
2045
    def get_features(self):
2046
        rf = BaofengUV5R.get_features(self)
2047
        rf.valid_power_levels = UV5R_POWER_LEVELS3
2048
        return rf
2049

    
2050
    def _is_orig(self):
2051
        # Override this for UV82HP to always return False
2052
        return False
2053

    
2054

    
2055
@directory.register
2056
class RadioddityUV5RX3Radio(BaofengUV5R):
2057
    VENDOR = "Radioddity"
2058
    MODEL = "UV-5RX3"
2059

    
2060
    def get_features(self):
2061
        rf = BaofengUV5R.get_features(self)
2062
        rf.valid_bands = [self._vhf_range,
2063
                          (200000000, 260000000),
2064
                          self._uhf_range]
2065
        return rf
2066

    
2067
    @classmethod
2068
    def match_model(cls, filename, filedata):
2069
        return False
2070

    
2071

    
2072
@directory.register
2073
class RadioddityGT5RRadio(BaofengUV5R):
2074
    VENDOR = 'Baofeng'
2075
    MODEL = 'GT-5R'
2076

    
2077
    vhftx = [144000000, 148000000]
2078
    uhftx = [420000000, 450000000]
2079

    
2080
    def validate_memory(self, mem):
2081
        msgs = super().validate_memory(mem)
2082

    
2083
        _msg_duplex = 'Duplex must be "off" for this frequency'
2084
        _msg_offset = 'Only simplex or +5 MHz offset allowed on GMRS'
2085

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

    
2091
        return msgs
2092

    
2093
    def check_set_memory_immutable_policy(self, existing, new):
2094
        existing.immutable = []
2095
        super().check_set_memory_immutable_policy(existing, new)
2096

    
2097
    @classmethod
2098
    def match_model(cls, filename, filedata):
2099
        return False
2100

    
2101

    
2102
@directory.register
2103
class RadioddityUV5GRadio(BaofengUV5R):
2104
    VENDOR = 'Radioddity'
2105
    MODEL = 'UV-5G'
2106

    
2107
    _basetype = BASETYPE_UV5R
2108
    _idents = [UV5R_MODEL_UV5G]
2109

    
2110
    @classmethod
2111
    def match_model(cls, filename, filedata):
2112
        return False
(38-38/41)