Project

General

Profile

Bug #10993 » uv5r-off_fix.py

Jim Unroe, 12/08/2023 09:28 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 squelch {
224
  u8 sql0;
225
  u8 sql1;
226
  u8 sql2;
227
  u8 sql3;
228
  u8 sql4;
229
  u8 sql5;
230
  u8 sql6;
231
  u8 sql7;
232
  u8 sql8;
233
  u8 sql9;
234
};
235

    
236
#seekto 0x18A8;
237
struct {
238
  struct squelch vhf;
239
  u8 unknown1[6];
240
  u8 unknown2[16];
241
  struct squelch uhf;
242
} squelch_new;
243

    
244
#seekto 0x18E8;
245
struct {
246
  struct squelch vhf;
247
  u8 unknown[6];
248
  struct squelch uhf;
249
} squelch_old;
250

    
251
struct limit {
252
  u8 enable;
253
  bbcd lower[2];
254
  bbcd upper[2];
255
};
256

    
257
#seekto 0x1908;
258
struct {
259
  struct limit vhf;
260
  struct limit uhf;
261
} limits_new;
262

    
263
#seekto 0x1910;
264
struct {
265
  u8 unknown1[2];
266
  struct limit vhf;
267
  u8 unknown2;
268
  u8 unknown3[8];
269
  u8 unknown4[2];
270
  struct limit uhf;
271
} limits_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(radio.get_mmap(), _data_start, _data_stop)
367
    return image_data
368

    
369

    
370
def _firmware_version_from_data(data, version_start, version_stop):
371
    version_tag = data[version_start:version_stop]
372
    return version_tag
373

    
374

    
375
def _firmware_version_from_image(radio):
376
    version = _firmware_version_from_data(
377
        radio.get_mmap().get_byte_compatible(),
378
        radio._fw_ver_file_start,
379
        radio._fw_ver_file_stop)
380
    return version
381

    
382

    
383
def _do_ident(radio, magic, secondack=True):
384
    serial = radio.pipe
385
    serial.timeout = 1
386

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

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

    
398
    serial.write(b"\x02")
399

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

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

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

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

    
443
    return ident
444

    
445

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

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

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

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

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

    
473
    radio.pipe.write(b"\x06")
474
    time.sleep(0.05)
475

    
476
    return chunk
477

    
478

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

    
494

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

    
507
        # read and disregard block0
508
        block0 = _read_block(radio, 0x1E80, 0x40, True)
509
        block1 = _read_block(radio, 0x1EC0, 0x40, False)
510
        block2 = _read_block(radio, 0x1FC0, 0x40, False)
511

    
512
        version = block1[48:62]  # firmware version
513

    
514
        # Some new radios will drop the byte at 0x1FCF when read in 0x40 byte
515
        # blocks. This results in the next 0x30 bytes being moved forward one
516
        # position (putting 0xFF in position 0x1FCF and pads the now missing
517
        # byte at the end, 0x1FFF, with 0x80).
518

    
519
        # detect dropped byte
520
        dropped_byte = (block2[15:16] == b"\xFF")  # dropped byte?
521

    
522
        return version, dropped_byte
523

    
524

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

    
530

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

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

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

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

    
561

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

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

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

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

    
603

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

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

    
613

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

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

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

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

    
635
        # determine if radio is 'original' radio
636
        if b'BFB' in radio_version:
637
            idx = radio_version.index(b"BFB") + 3
638
            version = int(radio_version[idx:idx + 3])
639
            _radio_is_orig = version < 291
640
        else:
641
            _radio_is_orig = False
642

    
643
        # determine if image is from 'original' radio
644
        _image_is_orig = radio._is_orig()
645

    
646
        if _image_is_orig != _radio_is_orig:
647
            raise errors.RadioError("Image not supported by radio")
648

    
649
        aux_i1 = _get_data_from_image(radio, 0x1848, 0x18A8)
650
        aux_i2 = _get_data_from_image(radio, 0x18B8, 0x18C8)
651
        aux_i3 = _get_data_from_image(radio, 0x18D8, 0x1908)
652

    
653
        # check if Aux memory of image matches Aux memory of radio
654
        aux_matched = False
655
        if aux_i1 != aux_r1:
656
            # Area 1 does not match
657
            # The safest thing to do is to skip uploading Aux mem area.
658
            LOG.info("Aux memory mis-match")
659
            LOG.info("Aux area 1 from image is %s" % repr(aux_i1))
660
            LOG.info("Aux area 1 from radio is %s" % repr(aux_r1))
661
        elif aux_i2 != aux_r2:
662
            # Area 2 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 2 from image is %s" % repr(aux_i2))
666
            LOG.info("Aux area 2 from radio is %s" % repr(aux_r2))
667
        elif aux_i3 != aux_r3:
668
            # Area 3 does not match
669
            # The safest thing to do is to skip uploading Aux mem area.
670
            LOG.info("Aux memory mis-match")
671
            LOG.info("Aux area 3 from image is %s" % repr(aux_i3))
672
            LOG.info("Aux area 3 from radio is %s" % repr(aux_r3))
673
        else:
674
            # All areas matched
675
            # Uploading full Aux mem area is permitted
676
            aux_matched = True
677

    
678
        if not radio._all_range_flag:
679
            if has_dropped_byte and not aux_matched:
680
                msg = ("Image not supported by radio. You must...\n"
681
                       "1. Download from radio.\n"
682
                       "2. Make changes.\n"
683
                       "3. Upload back to same radio.")
684
                raise errors.RadioError(msg)
685

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

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

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

    
717
    if radio._all_range_flag:
718
        # user enabled 'Range Override Parameter', upload everything
719
        ranges_main = radio._ranges_main
720
        ranges_aux = radio._ranges_aux
721
        LOG.warning('Sending all ranges to radio as instructed')
722
    else:
723
        # set default ranges
724
        ranges_main = _ranges_main_default
725
        ranges_aux = _ranges_aux_default
726

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

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

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

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

    
753

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

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

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

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

    
766

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

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

    
780

    
781
class BaofengUV5R(chirp_common.CloneModeRadio):
782

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1033
        immutable = []
1034

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

    
1042
        mem.immutable = immutable
1043

    
1044
        return mem
1045

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

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

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

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

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

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

    
1077
        _mem.rxfreq = mem.freq / 10
1078

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1190
        group = RadioSettings(basic, advanced)
1191

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1479
            rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled",
1480
                              RadioSettingValueBoolean(vhf_limit.enable))
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
            other.append(rs)
1489
            rs = RadioSetting("%s.uhf.upper" % limit,
1490
                              "UHF Upper Limit (MHz)",
1491
                              RadioSettingValueInteger(1, 1000,
1492
                                                       uhf_limit.upper))
1493
            other.append(rs)
1494
            rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled",
1495
                              RadioSettingValueBoolean(uhf_limit.enable))
1496
            other.append(rs)
1497

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1674
        # broadcast FM settings
1675

    
1676
        # radios with the dropped byte issue also does not expose the broadcast
1677
        # FM frequency to CHIRP and ignores any frequency provided by CHIRP.
1678
        # Older radios that do expose the broadcast FM frequency to CHIRP have
1679
        # a minimum of 3 different know definitions for storing the
1680
        # frequencies. Since some frequencies have collisions for some of the
1681
        # storage methods, it is not always obvious to know which definition
1682
        # is being used. It is for these reasons that the FM Radio Preset tab
1683
        # and its associated FM Preset(MHz) setting have been removed.
1684

    
1685
        if str(self._memobj.firmware_msg.line1) == "HN5RV01":
1686
            dtmfchars = "0123456789ABCD*#"
1687
        else:
1688
            dtmfchars = "0123456789 *#ABCD"
1689

    
1690
        for i in range(0, 15):
1691
            _codeobj = self._memobj.pttid[i].code
1692
            _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1693
            val = RadioSettingValueString(0, 5, _code, False)
1694
            val.set_charset(dtmfchars)
1695
            rs = RadioSetting("pttid/%i.code" % i,
1696
                              "PTT ID Code %i" % (i + 1), val)
1697

    
1698
            def apply_code(setting, obj):
1699
                code = []
1700
                for j in range(0, 5):
1701
                    try:
1702
                        code.append(dtmfchars.index(str(setting.value)[j]))
1703
                    except IndexError:
1704
                        code.append(0xFF)
1705
                obj.code = code
1706
            rs.set_apply_callback(apply_code, self._memobj.pttid[i])
1707
            dtmf.append(rs)
1708

    
1709
        dtmfcharsani = "0123456789"
1710

    
1711
        _codeobj = self._memobj.ani.code
1712
        _code = "".join([dtmfcharsani[x] for x in _codeobj if int(x) < 0x1F])
1713
        val = RadioSettingValueString(0, 5, _code, False)
1714
        val.set_charset(dtmfcharsani)
1715
        rs = RadioSetting("ani.code", "ANI Code", val)
1716

    
1717
        def apply_code(setting, obj):
1718
            code = []
1719
            for j in range(0, 5):
1720
                try:
1721
                    code.append(dtmfchars.index(str(setting.value)[j]))
1722
                except IndexError:
1723
                    code.append(0xFF)
1724
            obj.code = code
1725
        rs.set_apply_callback(apply_code, _ani)
1726
        dtmf.append(rs)
1727

    
1728
        rs = RadioSetting("ani.aniid", "ANI ID",
1729
                          RadioSettingValueList(PTTID_LIST,
1730
                                                PTTID_LIST[_ani.aniid]))
1731
        dtmf.append(rs)
1732

    
1733
        _codeobj = self._memobj.ani.alarmcode
1734
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1735
        val = RadioSettingValueString(0, 3, _code, False)
1736
        val.set_charset(dtmfchars)
1737
        rs = RadioSetting("ani.alarmcode", "Alarm Code", val)
1738

    
1739
        def apply_code(setting, obj):
1740
            alarmcode = []
1741
            for j in range(0, 3):
1742
                try:
1743
                    alarmcode.append(dtmfchars.index(str(setting.value)[j]))
1744
                except IndexError:
1745
                    alarmcode.append(0xFF)
1746
            obj.alarmcode = alarmcode
1747
        rs.set_apply_callback(apply_code, _ani)
1748
        dtmf.append(rs)
1749

    
1750
        rs = RadioSetting("dtmfst", "DTMF Sidetone",
1751
                          RadioSettingValueList(DTMFST_LIST,
1752
                                                DTMFST_LIST[_settings.dtmfst]))
1753
        dtmf.append(rs)
1754

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

    
1764
        if _ani.dtmfoff > 0xC3:
1765
            val = 0x00
1766
        else:
1767
            val = _ani.dtmfoff
1768
        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
1769
                          RadioSettingValueList(DTMFSPEED_LIST,
1770
                                                DTMFSPEED_LIST[val]))
1771
        dtmf.append(rs)
1772

    
1773
        rs = RadioSetting("pttlt", "PTT ID Delay",
1774
                          RadioSettingValueInteger(0, 50, _settings.pttlt))
1775
        dtmf.append(rs)
1776

    
1777
        if not self._is_orig() and self._aux_block:
1778
            service = RadioSettingGroup("service", "Service Settings")
1779
            group.append(service)
1780

    
1781
            for band in ["vhf", "uhf"]:
1782
                for index in range(0, 10):
1783
                    key = "squelch_new.%s.sql%i" % (band, index)
1784
                    if band == "vhf":
1785
                        _obj = self._memobj.squelch_new.vhf
1786
                    elif band == "uhf":
1787
                        _obj = self._memobj.squelch_new.uhf
1788
                    name = "%s Squelch %i" % (band.upper(), index)
1789
                    rs = RadioSetting(key, name,
1790
                                      RadioSettingValueInteger(
1791
                                          0, 123,
1792
                                          getattr(_obj, "sql%i" % (index))))
1793
                    service.append(rs)
1794

    
1795
        return group
1796

    
1797
    def get_settings(self):
1798
        try:
1799
            return self._get_settings()
1800
        except:
1801
            import traceback
1802
            LOG.error("Failed to parse settings: %s", traceback.format_exc())
1803
            return None
1804

    
1805
    def set_settings(self, settings):
1806
        _settings = self._memobj.settings
1807
        for element in settings:
1808
            if not isinstance(element, RadioSetting):
1809
                self.set_settings(element)
1810
                continue
1811
            else:
1812
                try:
1813
                    name = element.get_name()
1814
                    if "." in name:
1815
                        bits = name.split(".")
1816
                        obj = self._memobj
1817
                        for bit in bits[:-1]:
1818
                            if "/" in bit:
1819
                                bit, index = bit.split("/", 1)
1820
                                index = int(index)
1821
                                obj = getattr(obj, bit)[index]
1822
                            else:
1823
                                obj = getattr(obj, bit)
1824
                        setting = bits[-1]
1825
                    else:
1826
                        obj = _settings
1827
                        setting = element.get_name()
1828

    
1829
                    if element.has_apply_callback():
1830
                        LOG.debug("Using apply callback")
1831
                        element.run_apply_callback()
1832
                    elif element.value.get_mutable():
1833
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1834
                        setattr(obj, setting, element.value)
1835
                except Exception:
1836
                    LOG.debug(element.get_name())
1837
                    raise
1838

    
1839

    
1840
class UV5XAlias(chirp_common.Alias):
1841
    VENDOR = "Baofeng"
1842
    MODEL = "UV-5X"
1843

    
1844

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

    
1849

    
1850
class RT5RVAlias(chirp_common.Alias):
1851
    VENDOR = "Retevis"
1852
    MODEL = "RT5RV"
1853

    
1854

    
1855
class RT5Alias(chirp_common.Alias):
1856
    VENDOR = "Retevis"
1857
    MODEL = "RT5"
1858

    
1859

    
1860
class RT5_TPAlias(chirp_common.Alias):
1861
    VENDOR = "Retevis"
1862
    MODEL = "RT5(tri-power)"
1863

    
1864

    
1865
class RH5RAlias(chirp_common.Alias):
1866
    VENDOR = "Rugged"
1867
    MODEL = "RH5R"
1868

    
1869

    
1870
class ROUV5REXAlias(chirp_common.Alias):
1871
    VENDOR = "Radioddity"
1872
    MODEL = "UV-5R EX"
1873

    
1874

    
1875
class A5RAlias(chirp_common.Alias):
1876
    VENDOR = "Ansoko"
1877
    MODEL = "A-5R"
1878

    
1879

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

    
1885

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

    
1893
    def _is_orig(self):
1894
        # Override this for F11 to always return False
1895
        return False
1896

    
1897

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

    
1907
    def _is_orig(self):
1908
        # Override this for UV82 to always return False
1909
        return False
1910

    
1911

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

    
1918
    def get_features(self):
1919
        rf = BaofengUV5R.get_features(self)
1920
        rf.valid_bands = [self._vhf_range,
1921
                          (200000000, 260000000),
1922
                          self._uhf_range]
1923
        return rf
1924

    
1925

    
1926
@directory.register
1927
class BaofengUV6Radio(BaofengUV5R):
1928

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

    
1938
    def get_features(self):
1939
        rf = BaofengUV5R.get_features(self)
1940
        rf.memory_bounds = (1, 128)
1941
        return rf
1942

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

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

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

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

    
1955
    def _is_orig(self):
1956
        # Override this for UV6 to always return False
1957
        return False
1958

    
1959

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

    
1970
    def get_features(self):
1971
        rf = BaofengUV5R.get_features(self)
1972
        rf.valid_power_levels = UV5R_POWER_LEVELS3
1973
        return rf
1974

    
1975
    def _is_orig(self):
1976
        # Override this for KT980HP to always return False
1977
        return False
1978

    
1979

    
1980
class ROGA5SAlias(chirp_common.Alias):
1981
    VENDOR = "Radioddity"
1982
    MODEL = "GA-5S"
1983

    
1984

    
1985
class UV5XPAlias(chirp_common.Alias):
1986
    VENDOR = "Baofeng"
1987
    MODEL = "UV-5XP"
1988

    
1989

    
1990
class TSTIF8Alias(chirp_common.Alias):
1991
    VENDOR = "TechSide"
1992
    MODEL = "TI-F8+"
1993

    
1994

    
1995
class TenwayUV5RPro(chirp_common.Alias):
1996
    VENDOR = 'Tenway'
1997
    MODEL = 'UV-5R Pro'
1998

    
1999

    
2000
class TSTST9Alias(chirp_common.Alias):
2001
    VENDOR = "TechSide"
2002
    MODEL = "TS-T9+"
2003

    
2004

    
2005
class TDUV5RRadio(chirp_common.Alias):
2006
    VENDOR = "TIDRADIO"
2007
    MODEL = "TD-UV5R TriPower"
2008

    
2009

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

    
2024
    def get_features(self):
2025
        rf = BaofengUV5R.get_features(self)
2026
        rf.valid_power_levels = UV5R_POWER_LEVELS3
2027
        return rf
2028

    
2029
    def _is_orig(self):
2030
        # Override this for BFF8HP to always return False
2031
        return False
2032

    
2033

    
2034
class TenwayUV82Pro(chirp_common.Alias):
2035
    VENDOR = 'Tenway'
2036
    MODEL = 'UV-82 Pro'
2037

    
2038

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

    
2052
    def get_features(self):
2053
        rf = BaofengUV5R.get_features(self)
2054
        rf.valid_power_levels = UV5R_POWER_LEVELS3
2055
        return rf
2056

    
2057
    def _is_orig(self):
2058
        # Override this for UV82HP to always return False
2059
        return False
2060

    
2061

    
2062
@directory.register
2063
class RadioddityUV5RX3Radio(BaofengUV5R):
2064
    VENDOR = "Radioddity"
2065
    MODEL = "UV-5RX3"
2066

    
2067
    def get_features(self):
2068
        rf = BaofengUV5R.get_features(self)
2069
        rf.valid_bands = [self._vhf_range,
2070
                          (200000000, 260000000),
2071
                          self._uhf_range]
2072
        return rf
2073

    
2074
    @classmethod
2075
    def match_model(cls, filename, filedata):
2076
        return False
2077

    
2078

    
2079
@directory.register
2080
class RadioddityGT5RRadio(BaofengUV5R):
2081
    VENDOR = 'Baofeng'
2082
    MODEL = 'GT-5R'
2083

    
2084
    vhftx = [144000000, 148000000]
2085
    uhftx = [420000000, 450000000]
2086

    
2087
    def validate_memory(self, mem):
2088
        msgs = super().validate_memory(mem)
2089

    
2090
        _msg_duplex = 'Duplex must be "off" for this frequency'
2091
        _msg_offset = 'Only simplex or +5 MHz offset allowed on GMRS'
2092

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

    
2098
        return msgs
2099

    
2100
    def check_set_memory_immutable_policy(self, existing, new):
2101
        existing.immutable = []
2102
        super().check_set_memory_immutable_policy(existing, new)
2103

    
2104
    @classmethod
2105
    def match_model(cls, filename, filedata):
2106
        return False
2107

    
2108

    
2109
@directory.register
2110
class RadioddityUV5GRadio(BaofengUV5R):
2111
    VENDOR = 'Radioddity'
2112
    MODEL = 'UV-5G'
2113

    
2114
    _basetype = BASETYPE_UV5R
2115
    _idents = [UV5R_MODEL_UV5G]
2116

    
2117
    @classmethod
2118
    def match_model(cls, filename, filedata):
2119
        return False
(5-5/5)