Project

General

Profile

Feature #10505 » uv5r_10505_v2.py

Jim Unroe, 10/06/2023 04:50 AM

 
1
# Copyright 2012 Dan Smith <dsmith@danplanet.com>
2
#
3
# This program is free software: you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation, either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
15

    
16
from builtins import bytes
17

    
18
import struct
19
import time
20
import logging
21

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

    
29
LOG = logging.getLogger(__name__)
30

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

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

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

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

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

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

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

    
196
#seekto 0x0F56;
197
u16 fm_presets;
198

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

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

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

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

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

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

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

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

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

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

    
273
"""
274

    
275
# 0x1EC0 - 0x2000
276

    
277
vhf_220_radio = b"\x02"
278

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

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

    
325
GMRS_FREQS1 = [462562500, 462587500, 462612500, 462637500, 462662500,
326
               462687500, 462712500]
327
GMRS_FREQS2 = [467562500, 467587500, 467612500, 467637500, 467662500,
328
               467687500, 467712500]
329
GMRS_FREQS3 = [462550000, 462575000, 462600000, 462625000, 462650000,
330
               462675000, 462700000, 462725000]
331
GMRS_FREQS = GMRS_FREQS1 + GMRS_FREQS2 + GMRS_FREQS3 * 2
332

    
333

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

    
341

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

    
351

    
352
def _upper_band_from_data(data):
353
    return data[0x03:0x04]
354

    
355

    
356
def _upper_band_from_image(radio):
357
    return _upper_band_from_data(radio.get_mmap())
358

    
359

    
360
def _read_from_data(data, data_start, data_stop):
361
    data = data[data_start:data_stop]
362
    return data
363

    
364

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

    
372

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

    
377

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

    
385

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

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

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

    
401
    serial.write(b"\x02")
402

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

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

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

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

    
446
    return ident
447

    
448

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

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

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

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

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

    
476
    radio.pipe.write(b"\x06")
477
    time.sleep(0.05)
478

    
479
    return chunk
480

    
481

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

    
497

    
498
def _get_radio_firmware_version(radio):
499
    if radio.MODEL == "BJ-UV55":
500
        block = _read_block(radio, 0x1FF0, 0x40, True)
501
        version = block[0:6]
502
        return version
503
    else:
504
        # New radios will reply with 'alternative' (aka: bad) data if the Aux
505
        # memory area is read without first reading a block from another area
506
        # of memory. Reading any block that is outside of the Aux memory area
507
        # (which starts at 0x1EC0) prior to reading blocks in the Aux mem area
508
        # turns out to be a workaround for this problem.
509

    
510
        # read and disregard block0
511
        block0 = _read_block(radio, 0x1E80, 0x40, True)
512
        block1 = _read_block(radio, 0x1EC0, 0x40, False)
513
        block2 = _read_block(radio, 0x1FC0, 0x40, False)
514

    
515
        version = block1[48:62]  # firmware version
516
        # New radios will drop a byte at 0x1FCF when read in 0x40 byte blocks.
517
        # This results in the next 0x30 bytes being moved forward one position
518
        # (putting 0xFF in position 0x1FCF and pads the now missing byte at the
519
        # end, 0x1FFF, with 0x80).
520

    
521
        # detect dropped byte
522
        dropped_byte = (block2[15:16] == b"\xFF")  # dropped byte?
523
        return version, dropped_byte
524

    
525

    
526
IDENT_BLACKLIST = {
527
    b"\x50\x0D\x0C\x20\x16\x03\x28": "Radio identifies as BTECH UV-5X3",
528
    b"\x50\xBB\xFF\x20\x12\x06\x25": "Radio identifies as Radioddity UV-5G",
529
}
530

    
531

    
532
def _ident_radio(radio):
533
    for magic in radio._idents:
534
        error = None
535
        try:
536
            data = _do_ident(radio, magic)
537
            return data
538
        except errors.RadioError as e:
539
            LOG.error("uv5r._ident_radio: %s", e)
540
            error = e
541
            time.sleep(2)
542

    
543
    for magic, reason in list(IDENT_BLACKLIST.items()):
544
        try:
545
            _do_ident(radio, magic, secondack=False)
546
        except errors.RadioError:
547
            # No match, try the next one
548
            continue
549

    
550
        # If we got here, it means we identified the radio as
551
        # something other than one of our valid idents. Warn
552
        # the user so they can do the right thing.
553
        LOG.warning(('Identified radio as a blacklisted model '
554
                     '(details: %s)') % reason)
555
        raise errors.RadioError(('%s. Please choose the proper vendor/'
556
                                 'model and try again.') % reason)
557

    
558
    if error:
559
        raise error
560
    raise errors.RadioError("Radio did not respond")
561

    
562

    
563
def _do_download(radio):
564
    data = _ident_radio(radio)
565

    
566
    if radio.MODEL == "BJ-UV55":
567
        radio_version = _get_radio_firmware_version(radio)
568
    else:
569
        radio_version, has_dropped_byte = \
570
            _get_radio_firmware_version(radio)
571
    LOG.info("Radio Version is %s" % repr(radio_version))
572
    LOG.info("Radio has dropped byte issue: %s" % repr(has_dropped_byte))
573

    
574
    # Main block
575
    LOG.debug("downloading main block...")
576
    for i in range(0, 0x1800, 0x40):
577
        data += _read_block(radio, i, 0x40, False)
578
        _do_status(radio, "from", i)
579
    _do_status(radio, "from", radio.get_memsize())
580
    LOG.debug("done.")
581
    if radio._aux_block:
582
        if has_dropped_byte:
583
            LOG.debug("downloading aux block...")
584
            # Auxiliary block starts at 0x1ECO (?)
585
            for i in range(0x1EC0, 0x1FC0, 0x40):
586
                data += _read_block(radio, i, 0x40, False)
587
            # Shift to 0x10 block sizes as a workaround for new radios that
588
            # will drop byte 0x1FCF if the last 0x40 bytes are read using a
589
            # 0x40 block size
590
            for i in range(0x1FC0, 0x2000, 0x10):
591
                data += _read_block(radio, i, 0x10, False)
592
        else:
593
            # Retain 0x40 byte block download for legacy radios (the
594
            # 'original' radios with firmware versions prior to BFB291 do not
595
            # support reading the Aux memory are with 0x10 bytes blocks.
596
            LOG.debug("downloading aux block...")
597
            # Auxiliary block starts at 0x1ECO (?)
598
            for i in range(0x1EC0, 0x2000, 0x40):
599
                data += _read_block(radio, i, 0x40, False)
600

    
601
    LOG.debug("done.")
602
    return memmap.MemoryMapBytes(data)
603

    
604

    
605
def _send_block(radio, addr, data):
606
    msg = struct.pack(">BHB", ord("X"), addr, len(data))
607
    radio.pipe.write(msg + data)
608
    time.sleep(0.05)
609

    
610
    ack = radio.pipe.read(1)
611
    if ack != b"\x06":
612
        raise errors.RadioError("Radio refused to accept block 0x%04x" % addr)
613

    
614

    
615
def _do_upload(radio):
616
    ident = _ident_radio(radio)
617
    radio_upper_band = ident[3:4]
618
    image_upper_band = _upper_band_from_image(radio)
619

    
620
    if image_upper_band == vhf_220_radio or radio_upper_band == vhf_220_radio:
621
        if image_upper_band != radio_upper_band:
622
            raise errors.RadioError("Image not supported by radio")
623

    
624
    image_version = _firmware_version_from_image(radio)
625
    if radio.MODEL == "BJ-UV55":
626
        radio_version = _get_radio_firmware_version(radio)
627

    
628
        # default ranges
629
        _ranges_main_default = radio._ranges_main
630
        _ranges_aux_default = radio._ranges_aux
631
    else:
632
        radio_version, aux_r1, aux_r2, aux_r3, \
633
            has_dropped_byte = _get_aux_data_from_radio(radio)
634
        LOG.info("Radio has dropped byte issue: %s" % repr(has_dropped_byte))
635

    
636
        if has_dropped_byte:
637
            aux_i1 = _get_data_from_image(radio, 0x1848, 0x18A8)
638
            aux_i2 = _get_data_from_image(radio, 0x18B8, 0x18C8)
639
            aux_i3 = _get_data_from_image(radio, 0x18D8, 0x1908)
640

    
641
            msg = ("Image not supported by radio. You must...\n"
642
                   "1. Download from radio.\n"
643
                   "2. Make changes.\n"
644
                   "3. Upload back to same radio.")
645

    
646
            # check if Aux memory of image matches Aux memory of radio
647
            if aux_i1 != aux_r1:
648
                # Area 1 does not match
649
                # The safest thing to do is to skip uploading Aux mem area.
650
                LOG.info("Aux memory mis-match")
651
                LOG.info("Aux area 1 from image is %s" % repr(aux_i1))
652
                LOG.info("Aux area 1 from radio is %s" % repr(aux_r1))
653
                raise errors.RadioError(msg)
654
            elif aux_i2 != aux_r2:
655
                # Area 2 does not match
656
                # The safest thing to do is to skip uploading Aux mem area.
657
                LOG.info("Aux memory mis-match")
658
                LOG.info("Aux area 2 from image is %s" % repr(aux_i2))
659
                LOG.info("Aux area 2 from radio is %s" % repr(aux_r2))
660
                raise errors.RadioError(msg)
661
            elif aux_i3 != aux_r3:
662
                # Area 3 does not match
663
                # The safest thing to do is to skip uploading Aux mem area.
664
                LOG.info("Aux memory mis-match")
665
                LOG.info("Aux area 3 from image is %s" % repr(aux_i3))
666
                LOG.info("Aux area 3 from radio is %s" % repr(aux_r3))
667
                raise errors.RadioError(msg)
668
            else:
669
                # All areas matched
670
                # Uploading Aux mem area is permitted
671
                _skip_aux_block = False
672

    
673
        # determine if radio is 'original' radio
674
        if b'BFB' in radio_version:
675
            idx = radio_version.index(b"BFB") + 3
676
            version = int(radio_version[idx:idx + 3])
677
            _radio_is_orig = version < 291
678
        else:
679
            _radio_is_orig = False
680

    
681
        # determine if image is from 'original' radio
682
        _image_is_orig = radio._is_orig()
683

    
684
        if _image_is_orig != _radio_is_orig:
685
            raise errors.RadioError("Image not supported by radio")
686

    
687
        # default ranges
688
        _ranges_main_default = [
689
            (0x0008, 0x0CF8),  # skip 0x0CF8 - 0x0D08
690
            (0x0D08, 0x0DF8),  # skip 0x0DF8 - 0x0E08
691
            (0x0E08, 0x1808),
692
            ]
693

    
694
        if _image_is_orig:
695
            # default Aux mem ranges for radios before BFB291
696
            _ranges_aux_default = [
697
                (0x1EE0, 0x1EF0),  # welcome message
698
                (0x1FC0, 0x1FE0),  # old band limits
699
                ]
700
        elif has_dropped_byte:
701
            # default Aux mem ranges for radios with dropped byte issue
702
            _ranges_aux_default = [
703
                (0x1EC0, 0x2000),  # the full Aux mem range
704
                ]
705
        else:
706
            # default Aux mem ranges for radios from BFB291 to present
707
            # (that don't have dropped byte issue)
708
            _ranges_aux_default = [
709
                (0x1EE0, 0x1EF0),  # welcome message
710
                (0x1F60, 0x1F70),  # vhf squelch thresholds
711
                (0x1F80, 0x1F90),  # uhf squelch thresholds
712
                (0x1FC0, 0x1FD0),  # new band limits
713
                ]
714

    
715
    LOG.info("Image Version is %s" % repr(image_version))
716
    LOG.info("Radio Version is %s" % repr(radio_version))
717

    
718
    # set default ranges
719
    ranges_main = _ranges_main_default
720
    ranges_aux = _ranges_aux_default
721

    
722
    if radio._all_range_flag:
723
        # user enabled 'Range Override Parameter', upload everything
724
        ranges_main = radio._ranges_main
725
        ranges_aux = radio._ranges_aux
726
        LOG.warning('Sending all ranges to radio as instructed')
727

    
728
    # Main block
729
    mmap = radio.get_mmap().get_byte_compatible()
730
    for start_addr, end_addr in ranges_main:
731
        for i in range(start_addr, end_addr, 0x10):
732
            _send_block(radio, i - 0x08, mmap[i:i + 0x10])
733
            _do_status(radio, "to", i)
734
        _do_status(radio, "to", radio.get_memsize())
735

    
736
    if len(mmap.get_packed()) == 0x1808:
737
        LOG.info("Old image, not writing aux block")
738
        return  # Old image, no aux block
739

    
740
    # Auxiliary block at radio address 0x1EC0, our offset 0x1808
741
    for start_addr, end_addr in ranges_aux:
742
        for i in range(start_addr, end_addr, 0x10):
743
            addr = 0x1808 + (i - 0x1EC0)
744
            _send_block(radio, i, mmap[addr:addr + 0x10])
745

    
746
    if radio._all_range_flag:
747
        radio._all_range_flag = False
748
        LOG.warning('Sending all ranges to radio has completed')
749
        raise errors.RadioError(
750
            "This is NOT an error.\n"
751
            "The upload has finished successfully.\n"
752
            "Please restart CHIRP.")
753

    
754

    
755
UV5R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=4.00),
756
                     chirp_common.PowerLevel("Low",  watts=1.00)]
757

    
758
UV5R_POWER_LEVELS3 = [chirp_common.PowerLevel("High", watts=8.00),
759
                      chirp_common.PowerLevel("Med",  watts=4.00),
760
                      chirp_common.PowerLevel("Low",  watts=1.00)]
761

    
762
UV5R_DTCS = tuple(sorted(chirp_common.DTCS_CODES + (645,)))
763

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

    
767

    
768
def model_match(cls, data):
769
    """Match the opened/downloaded image to the correct version"""
770

    
771
    if len(data) == 0x1950:
772
        rid = data[0x1948:0x1950]
773
        return rid.startswith(cls.MODEL)
774
    elif len(data) == 0x1948:
775
        rid = data[cls._fw_ver_file_start:cls._fw_ver_file_stop]
776
        if any(type in rid for type in cls._basetype):
777
            return True
778
    else:
779
        return False
780

    
781

    
782
class BaofengUV5R(chirp_common.CloneModeRadio):
783

    
784
    """Baofeng UV-5R"""
785
    VENDOR = "Baofeng"
786
    MODEL = "UV-5R"
787
    BAUD_RATE = 9600
788
    NEEDS_COMPAT_SERIAL = False
789

    
790
    _memsize = 0x1808
791
    _basetype = BASETYPE_UV5R
792
    _idents = [UV5R_MODEL_291,
793
               UV5R_MODEL_ORIG
794
               ]
795
    _vhf_range = (130000000, 176000000)
796
    _220_range = (220000000, 260000000)
797
    _uhf_range = (400000000, 520000000)
798
    _aux_block = True
799
    _tri_power = False
800
    _bw_shift = False
801
    _mem_params = (0x1828  # poweron_msg offset
802
                   )
803
    # offset of fw version in image file
804
    _fw_ver_file_start = 0x1838
805
    _fw_ver_file_stop = 0x1846
806

    
807
    _ranges_main = [
808
                    (0x0008, 0x1808),
809
                   ]
810
    _ranges_aux = [
811
                   (0x1EC0, 0x2000),
812
                  ]
813
    _valid_chars = UV5R_CHARSET
814

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

    
843
    def get_features(self):
844
        rf = chirp_common.RadioFeatures()
845
        rf.has_settings = True
846
        rf.has_bank = False
847
        rf.has_cross = True
848
        rf.has_rx_dtcs = True
849
        rf.has_tuning_step = False
850
        rf.can_odd_split = True
851
        rf.valid_name_length = 7
852
        rf.valid_characters = self._valid_chars
853
        rf.valid_skips = ["", "S"]
854
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
855
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
856
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
857
        rf.valid_power_levels = UV5R_POWER_LEVELS
858
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
859
        rf.valid_modes = ["FM", "NFM"]
860
        rf.valid_tuning_steps = STEPS
861
        rf.valid_dtcs_codes = UV5R_DTCS
862

    
863
        normal_bands = [self._vhf_range, self._uhf_range]
864
        rax_bands = [self._vhf_range, self._220_range]
865

    
866
        if self._mmap is None:
867
            rf.valid_bands = [normal_bands[0], rax_bands[1], normal_bands[1]]
868
        elif not self._is_orig() and self._my_upper_band() == vhf_220_radio:
869
            rf.valid_bands = rax_bands
870
        else:
871
            rf.valid_bands = normal_bands
872
        rf.memory_bounds = (0, 127)
873
        return rf
874

    
875
    @classmethod
876
    def match_model(cls, filedata, filename):
877
        match_size = False
878
        match_model = False
879
        if len(filedata) in [0x1808, 0x1948, 0x1950]:
880
            match_size = True
881
        match_model = model_match(cls, filedata)
882

    
883
        if match_size and match_model:
884
            return True
885
        else:
886
            return False
887

    
888
    def process_mmap(self):
889
        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
890
        self._all_range_flag = False
891

    
892
    def sync_in(self):
893
        try:
894
            self._mmap = _do_download(self)
895
        except errors.RadioError:
896
            raise
897
        except Exception as e:
898
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
899
        self.process_mmap()
900

    
901
    def sync_out(self):
902
        try:
903
            _do_upload(self)
904
        except errors.RadioError:
905
            raise
906
        except Exception as e:
907
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
908

    
909
    def get_raw_memory(self, number):
910
        return repr(self._memobj.memory[number])
911

    
912
    def _is_txinh(self, _mem):
913
        raw_tx = ""
914
        for i in range(0, 4):
915
            raw_tx += _mem.txfreq[i].get_raw()
916
        return raw_tx == "\xFF\xFF\xFF\xFF"
917

    
918
    def _get_mem(self, number):
919
        return self._memobj.memory[number]
920

    
921
    def _get_nam(self, number):
922
        return self._memobj.names[number]
923

    
924
    def get_memory(self, number):
925
        _mem = self._get_mem(number)
926
        _nam = self._get_nam(number)
927

    
928
        mem = chirp_common.Memory()
929
        mem.number = number
930

    
931
        if _mem.get_raw()[0] == "\xff":
932
            mem.empty = True
933
            return mem
934

    
935
        mem.freq = int(_mem.rxfreq) * 10
936

    
937
        if self._is_txinh(_mem):
938
            mem.duplex = "off"
939
            mem.offset = 0
940
        elif int(_mem.rxfreq) == int(_mem.txfreq):
941
            mem.duplex = ""
942
            mem.offset = 0
943
        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
944
            mem.duplex = "split"
945
            mem.offset = int(_mem.txfreq) * 10
946
        else:
947
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
948
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
949

    
950
        for char in _nam.name:
951
            if str(char) == "\xFF":
952
                char = " "  # The UV-5R software may have 0xFF mid-name
953
            mem.name += str(char)
954
        mem.name = mem.name.rstrip()
955

    
956
        dtcs_pol = ["N", "N"]
957

    
958
        if _mem.txtone in [0, 0xFFFF]:
959
            txmode = ""
960
        elif _mem.txtone >= 0x0258:
961
            txmode = "Tone"
962
            mem.rtone = int(_mem.txtone) / 10.0
963
        elif _mem.txtone <= 0x0258:
964
            txmode = "DTCS"
965
            if _mem.txtone > 0x69:
966
                index = _mem.txtone - 0x6A
967
                dtcs_pol[0] = "R"
968
            else:
969
                index = _mem.txtone - 1
970
            mem.dtcs = UV5R_DTCS[index]
971
        else:
972
            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
973

    
974
        if _mem.rxtone in [0, 0xFFFF]:
975
            rxmode = ""
976
        elif _mem.rxtone >= 0x0258:
977
            rxmode = "Tone"
978
            mem.ctone = int(_mem.rxtone) / 10.0
979
        elif _mem.rxtone <= 0x0258:
980
            rxmode = "DTCS"
981
            if _mem.rxtone >= 0x6A:
982
                index = _mem.rxtone - 0x6A
983
                dtcs_pol[1] = "R"
984
            else:
985
                index = _mem.rxtone - 1
986
            mem.rx_dtcs = UV5R_DTCS[index]
987
        else:
988
            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
989

    
990
        if txmode == "Tone" and not rxmode:
991
            mem.tmode = "Tone"
992
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
993
            mem.tmode = "TSQL"
994
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
995
            mem.tmode = "DTCS"
996
        elif rxmode or txmode:
997
            mem.tmode = "Cross"
998
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
999

    
1000
        mem.dtcs_polarity = "".join(dtcs_pol)
1001

    
1002
        if not _mem.scan:
1003
            mem.skip = "S"
1004

    
1005
        if self._tri_power:
1006
            levels = UV5R_POWER_LEVELS3
1007
        else:
1008
            levels = UV5R_POWER_LEVELS
1009
        try:
1010
            mem.power = levels[_mem.lowpower]
1011
        except IndexError:
1012
            LOG.error("Radio reported invalid power level %s (in %s)" %
1013
                      (_mem.lowpower, levels))
1014
            mem.power = levels[0]
1015

    
1016
        mem.mode = _mem.wide and "FM" or "NFM"
1017

    
1018
        mem.extra = RadioSettingGroup("Extra", "extra")
1019

    
1020
        rs = RadioSetting("bcl", "BCL",
1021
                          RadioSettingValueBoolean(_mem.bcl))
1022
        mem.extra.append(rs)
1023

    
1024
        rs = RadioSetting("pttid", "PTT ID",
1025
                          RadioSettingValueList(PTTID_LIST,
1026
                                                PTTID_LIST[_mem.pttid]))
1027
        mem.extra.append(rs)
1028

    
1029
        rs = RadioSetting("scode", "PTT ID Code",
1030
                          RadioSettingValueList(PTTIDCODE_LIST,
1031
                                                PTTIDCODE_LIST[_mem.scode]))
1032
        mem.extra.append(rs)
1033

    
1034
        immutable = []
1035

    
1036
        if self.MODEL == "GT-5R":
1037
            if not ((mem.freq >= self.vhftx[0] and mem.freq < self.vhftx[1]) or
1038
                    (mem.freq >= self.uhftx[0] and mem.freq < self.uhftx[1])):
1039
                mem.duplex = 'off'
1040
                mem.offset = 0
1041
                immutable = ["duplex", "offset"]
1042

    
1043
        mem.immutable = immutable
1044

    
1045
        return mem
1046

    
1047
    def _set_mem(self, number):
1048
        return self._memobj.memory[number]
1049

    
1050
    def _set_nam(self, number):
1051
        return self._memobj.names[number]
1052

    
1053
    def set_memory(self, mem):
1054
        _mem = self._get_mem(mem.number)
1055
        _nam = self._get_nam(mem.number)
1056

    
1057
        if mem.empty:
1058
            _mem.set_raw("\xff" * 16)
1059
            _nam.set_raw("\xff" * 16)
1060
            return
1061

    
1062
        was_empty = False
1063
        # same method as used in get_memory to find
1064
        # out whether a raw memory is empty
1065
        if _mem.get_raw()[0] == "\xff":
1066
            was_empty = True
1067
            LOG.debug("UV5R: this mem was empty")
1068
        else:
1069
            # memorize old extra-values before erasing the whole memory
1070
            # used to solve issue 4121
1071
            LOG.debug("mem was not empty, memorize extra-settings")
1072
            prev_bcl = _mem.bcl.get_value()
1073
            prev_scode = _mem.scode.get_value()
1074
            prev_pttid = _mem.pttid.get_value()
1075

    
1076
        _mem.set_raw("\x00" * 16)
1077

    
1078
        _mem.rxfreq = mem.freq / 10
1079

    
1080
        if mem.duplex == "off":
1081
            for i in range(0, 4):
1082
                _mem.txfreq[i].set_raw("\xFF")
1083
        elif mem.duplex == "split":
1084
            _mem.txfreq = mem.offset / 10
1085
        elif mem.duplex == "+":
1086
            _mem.txfreq = (mem.freq + mem.offset) / 10
1087
        elif mem.duplex == "-":
1088
            _mem.txfreq = (mem.freq - mem.offset) / 10
1089
        else:
1090
            _mem.txfreq = mem.freq / 10
1091

    
1092
        _namelength = self.get_features().valid_name_length
1093
        for i in range(_namelength):
1094
            try:
1095
                _nam.name[i] = mem.name[i]
1096
            except IndexError:
1097
                _nam.name[i] = "\xFF"
1098

    
1099
        rxmode = txmode = ""
1100
        if mem.tmode == "Tone":
1101
            _mem.txtone = int(mem.rtone * 10)
1102
            _mem.rxtone = 0
1103
        elif mem.tmode == "TSQL":
1104
            _mem.txtone = int(mem.ctone * 10)
1105
            _mem.rxtone = int(mem.ctone * 10)
1106
        elif mem.tmode == "DTCS":
1107
            rxmode = txmode = "DTCS"
1108
            _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
1109
            _mem.rxtone = UV5R_DTCS.index(mem.dtcs) + 1
1110
        elif mem.tmode == "Cross":
1111
            txmode, rxmode = mem.cross_mode.split("->", 1)
1112
            if txmode == "Tone":
1113
                _mem.txtone = int(mem.rtone * 10)
1114
            elif txmode == "DTCS":
1115
                _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
1116
            else:
1117
                _mem.txtone = 0
1118
            if rxmode == "Tone":
1119
                _mem.rxtone = int(mem.ctone * 10)
1120
            elif rxmode == "DTCS":
1121
                _mem.rxtone = UV5R_DTCS.index(mem.rx_dtcs) + 1
1122
            else:
1123
                _mem.rxtone = 0
1124
        else:
1125
            _mem.rxtone = 0
1126
            _mem.txtone = 0
1127

    
1128
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
1129
            _mem.txtone += 0x69
1130
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
1131
            _mem.rxtone += 0x69
1132

    
1133
        _mem.scan = mem.skip != "S"
1134
        _mem.wide = mem.mode == "FM"
1135

    
1136
        if mem.power:
1137
            if self._tri_power:
1138
                levels = [str(l) for l in UV5R_POWER_LEVELS3]
1139
                _mem.lowpower = levels.index(str(mem.power))
1140
            else:
1141
                _mem.lowpower = UV5R_POWER_LEVELS.index(mem.power)
1142
        else:
1143
            _mem.lowpower = 0
1144

    
1145
        if not was_empty:
1146
            # restoring old extra-settings (issue 4121
1147
            _mem.bcl.set_value(prev_bcl)
1148
            _mem.scode.set_value(prev_scode)
1149
            _mem.pttid.set_value(prev_pttid)
1150

    
1151
        for setting in mem.extra:
1152
            setattr(_mem, setting.get_name(), setting.value)
1153

    
1154
    def _is_orig(self):
1155
        version_tag = _firmware_version_from_image(self)
1156
        try:
1157
            if b'BFB' in version_tag:
1158
                idx = version_tag.index(b"BFB") + 3
1159
                version = int(version_tag[idx:idx + 3])
1160
                return version < 291
1161
            return False
1162
        except:
1163
            pass
1164
        raise errors.RadioError("Unable to parse version string %s" %
1165
                                version_tag)
1166

    
1167
    def _my_version(self):
1168
        version_tag = _firmware_version_from_image(self)
1169
        if b'BFB' in version_tag:
1170
            idx = version_tag.index(b"BFB") + 3
1171
            return int(version_tag[idx:idx + 3])
1172

    
1173
        raise Exception("Unrecognized firmware version string")
1174

    
1175
    def _my_upper_band(self):
1176
        band_tag = _upper_band_from_image(self)
1177
        return band_tag
1178

    
1179
    def _get_settings(self):
1180
        _mem = self._memobj
1181
        _ani = self._memobj.ani
1182
        _settings = self._memobj.settings
1183
        _squelch = self._memobj.squelch_new
1184
        _vfoa = self._memobj.vfoa
1185
        _vfob = self._memobj.vfob
1186
        _wmchannel = self._memobj.wmchannel
1187

    
1188
        basic = RadioSettingGroup("basic", "Basic Settings")
1189
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
1190

    
1191
        group = RadioSettings(basic, advanced)
1192

    
1193
        rs = RadioSetting("squelch", "Carrier Squelch Level",
1194
                          RadioSettingValueInteger(0, 9, _settings.squelch))
1195
        basic.append(rs)
1196

    
1197
        rs = RadioSetting("save", "Battery Saver",
1198
                          RadioSettingValueList(
1199
                              SAVE_LIST, SAVE_LIST[_settings.save]))
1200
        basic.append(rs)
1201

    
1202
        rs = RadioSetting("vox", "VOX Sensitivity",
1203
                          RadioSettingValueList(
1204
                              VOX_LIST, VOX_LIST[_settings.vox]))
1205
        advanced.append(rs)
1206

    
1207
        if self.MODEL == "UV-6":
1208
            # NOTE: The UV-6 calls this byte voxenable, but the UV-5R calls it
1209
            # autolk. Since this is a minor difference, it will be referred to
1210
            # by the wrong name for the UV-6.
1211
            rs = RadioSetting("autolk", "Vox",
1212
                              RadioSettingValueBoolean(_settings.autolk))
1213
            advanced.append(rs)
1214

    
1215
        if self.MODEL != "UV-6":
1216
            rs = RadioSetting("abr", "Backlight Timeout",
1217
                              RadioSettingValueInteger(0, 24, _settings.abr))
1218
            basic.append(rs)
1219

    
1220
        rs = RadioSetting("tdr", "Dual Watch",
1221
                          RadioSettingValueBoolean(_settings.tdr))
1222
        advanced.append(rs)
1223

    
1224
        if self.MODEL == "UV-6":
1225
            rs = RadioSetting("tdrch", "Dual Watch Channel",
1226
                              RadioSettingValueList(
1227
                                  TDRCH_LIST, TDRCH_LIST[_settings.tdrch]))
1228
            advanced.append(rs)
1229

    
1230
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
1231
                              RadioSettingValueBoolean(_settings.tdrab))
1232
            advanced.append(rs)
1233
        else:
1234
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
1235
                              RadioSettingValueList(
1236
                                  TDRAB_LIST, TDRAB_LIST[_settings.tdrab]))
1237
            advanced.append(rs)
1238

    
1239
        if self.MODEL == "UV-6":
1240
            rs = RadioSetting("alarm", "Alarm Sound",
1241
                              RadioSettingValueBoolean(_settings.alarm))
1242
            advanced.append(rs)
1243

    
1244
        if _settings.almod > 0x02:
1245
            val = 0x01
1246
        else:
1247
            val = _settings.almod
1248
        rs = RadioSetting("almod", "Alarm Mode",
1249
                          RadioSettingValueList(
1250
                              ALMOD_LIST, ALMOD_LIST[val]))
1251
        advanced.append(rs)
1252

    
1253
        rs = RadioSetting("beep", "Beep",
1254
                          RadioSettingValueBoolean(_settings.beep))
1255
        basic.append(rs)
1256

    
1257
        rs = RadioSetting("timeout", "Timeout Timer",
1258
                          RadioSettingValueList(
1259
                              TIMEOUT_LIST, TIMEOUT_LIST[_settings.timeout]))
1260
        basic.append(rs)
1261

    
1262
        if ((self._is_orig() and self._my_version() < 251) or
1263
                (self.MODEL in ["TI-F8+", "TS-T9+"])):
1264
            rs = RadioSetting("voice", "Voice",
1265
                              RadioSettingValueBoolean(_settings.voice))
1266
            advanced.append(rs)
1267
        else:
1268
            rs = RadioSetting("voice", "Voice",
1269
                              RadioSettingValueList(
1270
                                  VOICE_LIST, VOICE_LIST[_settings.voice]))
1271
            advanced.append(rs)
1272

    
1273
        rs = RadioSetting("screv", "Scan Resume",
1274
                          RadioSettingValueList(
1275
                              RESUME_LIST, RESUME_LIST[_settings.screv]))
1276
        advanced.append(rs)
1277

    
1278
        if self.MODEL != "UV-6":
1279
            rs = RadioSetting("mdfa", "Display Mode (A)",
1280
                              RadioSettingValueList(
1281
                                  MODE_LIST, MODE_LIST[_settings.mdfa]))
1282
            basic.append(rs)
1283

    
1284
            rs = RadioSetting("mdfb", "Display Mode (B)",
1285
                              RadioSettingValueList(
1286
                                  MODE_LIST, MODE_LIST[_settings.mdfb]))
1287
            basic.append(rs)
1288

    
1289
        rs = RadioSetting("bcl", "Busy Channel Lockout",
1290
                          RadioSettingValueBoolean(_settings.bcl))
1291
        advanced.append(rs)
1292

    
1293
        if self.MODEL != "UV-6":
1294
            rs = RadioSetting("autolk", "Automatic Key Lock",
1295
                              RadioSettingValueBoolean(_settings.autolk))
1296
            advanced.append(rs)
1297

    
1298
        rs = RadioSetting("fmradio", "Broadcast FM Radio",
1299
                          RadioSettingValueBoolean(_settings.fmradio))
1300
        advanced.append(rs)
1301

    
1302
        if self.MODEL != "UV-6":
1303
            rs = RadioSetting("wtled", "Standby LED Color",
1304
                              RadioSettingValueList(
1305
                                  COLOR_LIST, COLOR_LIST[_settings.wtled]))
1306
            basic.append(rs)
1307

    
1308
            rs = RadioSetting("rxled", "RX LED Color",
1309
                              RadioSettingValueList(
1310
                                  COLOR_LIST, COLOR_LIST[_settings.rxled]))
1311
            basic.append(rs)
1312

    
1313
            rs = RadioSetting("txled", "TX LED Color",
1314
                              RadioSettingValueList(
1315
                                  COLOR_LIST, COLOR_LIST[_settings.txled]))
1316
            basic.append(rs)
1317

    
1318
        if isinstance(self, BaofengUV82Radio):
1319
            rs = RadioSetting("roger", "Roger Beep (TX)",
1320
                              RadioSettingValueBoolean(_settings.roger))
1321
            basic.append(rs)
1322
            rs = RadioSetting("rogerrx", "Roger Beep (RX)",
1323
                              RadioSettingValueList(
1324
                                  ROGERRX_LIST,
1325
                                  ROGERRX_LIST[_settings.rogerrx]))
1326
            basic.append(rs)
1327
        else:
1328
            rs = RadioSetting("roger", "Roger Beep",
1329
                              RadioSettingValueBoolean(_settings.roger))
1330
            basic.append(rs)
1331

    
1332
        rs = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)",
1333
                          RadioSettingValueBoolean(_settings.ste))
1334
        advanced.append(rs)
1335

    
1336
        rs = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)",
1337
                          RadioSettingValueList(
1338
                              RPSTE_LIST, RPSTE_LIST[_settings.rpste]))
1339
        advanced.append(rs)
1340

    
1341
        rs = RadioSetting("rptrl", "STE Repeater Delay",
1342
                          RadioSettingValueList(
1343
                              STEDELAY_LIST, STEDELAY_LIST[_settings.rptrl]))
1344
        advanced.append(rs)
1345

    
1346
        if self.MODEL != "UV-6":
1347
            rs = RadioSetting("reset", "RESET Menu",
1348
                              RadioSettingValueBoolean(_settings.reset))
1349
            advanced.append(rs)
1350

    
1351
            rs = RadioSetting("menu", "All Menus",
1352
                              RadioSettingValueBoolean(_settings.menu))
1353
            advanced.append(rs)
1354

    
1355
        if self.MODEL == "F-11":
1356
            # this is an F-11 only feature
1357
            rs = RadioSetting("vfomrlock", "VFO/MR Button",
1358
                              RadioSettingValueBoolean(_settings.vfomrlock))
1359
            advanced.append(rs)
1360

    
1361
        if isinstance(self, BaofengUV82Radio):
1362
            # this is a UV-82C only feature
1363
            rs = RadioSetting("vfomrlock", "VFO/MR Switching (UV-82C only)",
1364
                              RadioSettingValueBoolean(_settings.vfomrlock))
1365
            advanced.append(rs)
1366

    
1367
        if self.MODEL == "UV-82HP":
1368
            # this is a UV-82HP only feature
1369
            rs = RadioSetting(
1370
                "vfomrlock", "VFO/MR Switching (BTech UV-82HP only)",
1371
                RadioSettingValueBoolean(_settings.vfomrlock))
1372
            advanced.append(rs)
1373

    
1374
        if isinstance(self, BaofengUV82Radio):
1375
            # this is an UV-82C only feature
1376
            rs = RadioSetting("singleptt", "Single PTT (UV-82C only)",
1377
                              RadioSettingValueBoolean(_settings.singleptt))
1378
            advanced.append(rs)
1379

    
1380
        if self.MODEL == "UV-82HP":
1381
            # this is an UV-82HP only feature
1382
            rs = RadioSetting("singleptt", "Single PTT (BTech UV-82HP only)",
1383
                              RadioSettingValueBoolean(_settings.singleptt))
1384
            advanced.append(rs)
1385

    
1386
        if self.MODEL == "UV-82HP":
1387
            # this is an UV-82HP only feature
1388
            rs = RadioSetting(
1389
                "tdrch", "Tone Burst Frequency (BTech UV-82HP only)",
1390
                RadioSettingValueList(RTONE_LIST, RTONE_LIST[_settings.tdrch]))
1391
            advanced.append(rs)
1392

    
1393
        def set_range_flag(setting):
1394
            val = [85, 115, 101, 65, 116, 79, 119, 110, 82, 105, 115, 107]
1395
            if [ord(x) for x in str(setting.value).strip()] == val:
1396
                self._all_range_flag = True
1397
            else:
1398
                self._all_range_flag = False
1399
            LOG.debug('Set range flag to %s' % self._all_range_flag)
1400

    
1401
        rs = RadioSetting("allrange", "Range Override Parameter",
1402
                          RadioSettingValueString(0, 12, "Default"))
1403
        rs.set_apply_callback(set_range_flag)
1404
        advanced.append(rs)
1405

    
1406
        if len(self._mmap.get_packed()) == 0x1808:
1407
            # Old image, without aux block
1408
            return group
1409

    
1410
        if self.MODEL != "UV-6":
1411
            other = RadioSettingGroup("other", "Other Settings")
1412
            group.append(other)
1413

    
1414
            def _filter(name):
1415
                filtered = ""
1416
                for char in str(name):
1417
                    if char in chirp_common.CHARSET_ASCII:
1418
                        filtered += char
1419
                    else:
1420
                        filtered += " "
1421
                return filtered
1422

    
1423
            _msg = self._memobj.firmware_msg
1424
            val = RadioSettingValueString(0, 7, _filter(_msg.line1))
1425
            val.set_mutable(False)
1426
            rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
1427
            other.append(rs)
1428

    
1429
            val = RadioSettingValueString(0, 7, _filter(_msg.line2))
1430
            val.set_mutable(False)
1431
            rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
1432
            other.append(rs)
1433

    
1434
            _msg = self._memobj.sixpoweron_msg
1435
            val = RadioSettingValueString(0, 7, _filter(_msg.line1))
1436
            val.set_mutable(False)
1437
            rs = RadioSetting("sixpoweron_msg.line1",
1438
                              "6+Power-On Message 1", val)
1439
            other.append(rs)
1440
            val = RadioSettingValueString(0, 7, _filter(_msg.line2))
1441
            val.set_mutable(False)
1442
            rs = RadioSetting("sixpoweron_msg.line2",
1443
                              "6+Power-On Message 2", val)
1444
            other.append(rs)
1445

    
1446
            _msg = self._memobj.poweron_msg
1447
            rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
1448
                              RadioSettingValueString(
1449
                                  0, 7, _filter(_msg.line1)))
1450
            other.append(rs)
1451
            rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
1452
                              RadioSettingValueString(
1453
                                  0, 7, _filter(_msg.line2)))
1454
            other.append(rs)
1455

    
1456
            rs = RadioSetting("ponmsg", "Power-On Message",
1457
                              RadioSettingValueList(
1458
                                  PONMSG_LIST,
1459
                                  PONMSG_LIST[_settings.ponmsg]))
1460
            other.append(rs)
1461

    
1462
            if self._is_orig():
1463
                limit = "limits_old"
1464
            else:
1465
                limit = "limits_new"
1466

    
1467
            vhf_limit = getattr(self._memobj, limit).vhf
1468
            rs = RadioSetting("%s.vhf.lower" % limit,
1469
                              "VHF Lower Limit (MHz)",
1470
                              RadioSettingValueInteger(1, 1000,
1471
                                                       vhf_limit.lower))
1472
            other.append(rs)
1473

    
1474
            rs = RadioSetting("%s.vhf.upper" % limit,
1475
                              "VHF Upper Limit (MHz)",
1476
                              RadioSettingValueInteger(1, 1000,
1477
                                                       vhf_limit.upper))
1478
            other.append(rs)
1479

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

    
1484
            uhf_limit = getattr(self._memobj, limit).uhf
1485
            rs = RadioSetting("%s.uhf.lower" % limit,
1486
                              "UHF Lower Limit (MHz)",
1487
                              RadioSettingValueInteger(1, 1000,
1488
                                                       uhf_limit.lower))
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
            other.append(rs)
1495
            rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled",
1496
                              RadioSettingValueBoolean(uhf_limit.enable))
1497
            other.append(rs)
1498

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1672
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1673
        group.append(dtmf)
1674

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

    
1680
        for i in range(0, 15):
1681
            _codeobj = self._memobj.pttid[i].code
1682
            _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1683
            val = RadioSettingValueString(0, 5, _code, False)
1684
            val.set_charset(dtmfchars)
1685
            rs = RadioSetting("pttid/%i.code" % i,
1686
                              "PTT ID Code %i" % (i + 1), 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, self._memobj.pttid[i])
1697
            dtmf.append(rs)
1698

    
1699
        dtmfcharsani = "0123456789"
1700

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

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

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

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

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

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

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

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

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

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

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

    
1785
        return group
1786

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

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

    
1819
                    if element.has_apply_callback():
1820
                        LOG.debug("Using apply callback")
1821
                        element.run_apply_callback()
1822
                    elif element.value.get_mutable():
1823
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1824
                        setattr(obj, setting, element.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 +5 MHz 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
(40-40/41)