Project

General

Profile

New Model #10478 » uvk5.py

uvk5.py chirp driver for UV-K5, version 20230626_3 - Jacek Lipkowski SQ5BPF, 06/26/2023 06:16 AM

 
1
# Quansheng UV-K5 driver (c) 2023 Jacek Lipkowski <sq5bpf@lipkowski.org>
2
#
3
# based on template.py Copyright 2012 Dan Smith <dsmith@danplanet.com>
4
#
5
#
6
# This is a preliminary version of a driver for the UV-K5
7
# It is based on my reverse engineering effort described here:
8
# https://github.com/sq5bpf/uvk5-reverse-engineering
9
#
10
# Warning: this driver is experimental, it may brick your radio,
11
# eat your lunch and mess up your configuration.
12
#
13
#
14
# This program is free software: you can redistribute it and/or modify
15
# it under the terms of the GNU General Public License as published by
16
# the Free Software Foundation, either version 2 of the License, or
17
# (at your option) any later version.
18
#
19
# This program is distributed in the hope that it will be useful,
20
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
# GNU General Public License for more details.
23
#
24
# You should have received a copy of the GNU General Public License
25
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
26

    
27

    
28
import struct
29
import logging
30

    
31
from chirp import chirp_common, directory, bitwise, memmap, errors, util
32
from chirp.settings import RadioSetting, RadioSettingGroup, \
33
    RadioSettingValueBoolean, RadioSettingValueList, \
34
    RadioSettingValueInteger, RadioSettingValueString, \
35
    RadioSettings
36

    
37
LOG = logging.getLogger(__name__)
38

    
39
# Show the obfuscated version of commands. Not needed normally, but
40
# might be useful for someone who is debugging a similar radio
41
DEBUG_SHOW_OBFUSCATED_COMMANDS = False
42

    
43
# Show the memory being written/received. Not needed normally, because
44
# this is the same information as in the packet hexdumps, but
45
# might be useful for someone debugging some obscure memory issue
46
DEBUG_SHOW_MEMORY_ACTIONS = False
47

    
48
# TODO: remove the driver version when it's in mainline chirp
49
DRIVER_VERSION = "Quansheng UV-K5 driver v20230626 (c) Jacek Lipkowski SQ5BPF"
50

    
51
MEM_FORMAT = """
52
#seekto 0x0000;
53
struct {
54
  ul32 freq;
55
  ul32 offset;
56
  u8 rxcode;
57
  u8 txcode;
58

    
59
  u8 unknown1:2,
60
  txcodeflag:2,
61
  unknown2:2,
62
  rxcodeflag:2;
63

    
64
  //u8 flags1;
65
  u8 flags1_unknown7:1,
66
  flags1_unknown6:1,
67
  flags1_unknown5:1,
68
  enable_am:1,
69
  flags1_unknown3:1,
70
  is_in_scanlist:1,
71
  shift:2;
72

    
73
  //u8 flags2;
74
  u8 flags2_unknown7:1,
75
  flags2_unknown6:1,
76
  flags2_unknown5:1,
77
  bclo:1,
78
  txpower:2,
79
  bandwidth:1,
80
  freq_reverse:1;
81

    
82
  //u8 dtmf_flags;
83
  u8 dtmf_flags_unknown7:1,
84
  dtmf_flags_unknown6:1,
85
  dtmf_flags_unknown5:1,
86
  dtmf_flags_unknown4:1,
87
  dtmf_flags_unknown3:1,
88
  dtmf_pttid:2,
89
  dtmf_decode:1;
90

    
91

    
92
  u8 step;
93
  u8 scrambler;
94
} channel[214];
95

    
96
#seekto 0xd60;
97
struct {
98
u8 is_scanlist1:1,
99
is_scanlist2:1,
100
unknown1:1,
101
unknown2:1,
102
is_free:1,
103
band:3;
104
} channel_attributes[200];
105

    
106
#seekto 0xe40;
107
ul16 fmfreq[20];
108

    
109
#seekto 0xe70;
110
u8 call_channel;
111
u8 squelch;
112
u8 max_talk_time;
113
u8 noaa_autoscan;
114
u8 key_lock;
115
u8 vox_switch;
116
u8 vox_level;
117
u8 mic_gain;
118
u8 unknown3;
119
u8 channel_display_mode;
120
u8 crossband;
121
u8 battery_save;
122
u8 dual_watch;
123
u8 backlight_auto_mode;
124
u8 tail_note_elimination;
125
u8 vfo_open;
126

    
127
#seekto 0xe90;
128
u8 beep_control;
129
u8 key1_shortpress_action;
130
u8 key1_longpress_action;
131
u8 key2_shortpress_action;
132
u8 key2_longpress_action;
133
u8 scan_resume_mode;
134
u8 auto_keypad_lock;
135
u8 power_on_dispmode;
136
u8 password[4];
137

    
138
#seekto 0xea0;
139
u8 keypad_tone;
140
u8 language;
141

    
142
#seekto 0xea8;
143
u8 alarm_mode;
144
u8 reminding_of_end_talk;
145
u8 repeater_tail_elimination;
146

    
147
#seekto 0xeb0;
148
char logo_line1[16];
149
char logo_line2[16];
150

    
151
#seekto 0xed0;
152
struct {
153
u8 side_tone;
154
char separate_code;
155
char group_call_code;
156
u8 decode_response;
157
u8 auto_reset_time;
158
u8 preload_time;
159
u8 first_code_persist_time;
160
u8 hash_persist_time;
161
u8 code_persist_time;
162
u8 code_interval_time;
163
u8 permit_remote_kill;
164
} dtmf_settings;
165

    
166
#seekto 0xee0;
167
struct {
168
char dtmf_local_code[3];
169
char unused1[5];
170
char kill_code[5];
171
char unused2[3];
172
char revive_code[5];
173
char unused3[3];
174
char dtmf_up_code[16];
175
char dtmf_down_code[16];
176
} dtmf_settings_numbers;
177

    
178
#seekto 0xf18;
179
u8 scanlist_default;
180
u8 scanlist1_priority_scan;
181
u8 scanlist1_priority_ch1;
182
u8 scanlist1_priority_ch2;
183
u8 scanlist2_priority_scan;
184
u8 scanlist2_priority_ch1;
185
u8 scanlist2_priority_ch2;
186
u8 scanlist_unknown_0xff;
187

    
188

    
189
#seekto 0xf40;
190
u8 int_flock;
191
u8 int_350tx;
192
u8 int_unknown1;
193
u8 int_200tx;
194
u8 int_500tx;
195
u8 int_350en;
196
u8 int_scren;
197

    
198
#seekto 0xf50;
199
struct {
200
char name[16];
201
} channelname[200];
202

    
203
#seekto 0x1c00;
204
struct {
205
char name[8];
206
char number[3];
207
char unused_00[5];
208
} dtmfcontact[16];
209
"""
210
# bits that we will save from the channel structure (mostly unknown)
211
SAVE_MASK_0A = 0b11001100
212
SAVE_MASK_0B = 0b11101100
213
SAVE_MASK_0C = 0b11100000
214
SAVE_MASK_0D = 0b11111000
215
SAVE_MASK_0E = 0b11110001
216
SAVE_MASK_0F = 0b11110000
217

    
218
# flags1
219
FLAGS1_OFFSET_NONE = 0b00
220
FLAGS1_OFFSET_MINUS = 0b10
221
FLAGS1_OFFSET_PLUS = 0b01
222

    
223
POWER_HIGH = 0b10
224
POWER_MEDIUM = 0b01
225
POWER_LOW = 0b00
226

    
227
# dtmf_flags
228
PTTID_LIST = ["off", "BOT", "EOT", "BOTH"]
229

    
230
# power
231
UVK5_POWER_LEVELS = [chirp_common.PowerLevel("Low",  watts=1.50),
232
                     chirp_common.PowerLevel("Med",  watts=3.00),
233
                     chirp_common.PowerLevel("High", watts=5.00),
234
                     ]
235

    
236
# scrambler
237
SCRAMBLER_LIST = ["off", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
238

    
239
# channel display mode
240
CHANNELDISP_LIST = ["Frequency", "Channel No", "Channel Name"]
241
# battery save
242
BATSAVE_LIST = ["OFF", "1:1", "1:2", "1:3", "1:4"]
243

    
244
# Backlight auto mode
245
BACKLIGHT_LIST = ["Off", "1s", "2s", "3s", "4s", "5s"]
246

    
247
# Crossband receiving/transmitting
248
CROSSBAND_LIST = ["Off", "Band A", "Band B"]
249
DUALWATCH_LIST = CROSSBAND_LIST
250

    
251
# steps
252
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 8.33]
253

    
254
# ctcss/dcs codes
255
TMODES = ["", "Tone", "DTCS", "DTCS"]
256
TONE_NONE = 0
257
TONE_CTCSS = 1
258
TONE_DCS = 2
259
TONE_RDCS = 3
260

    
261

    
262
CTCSS_TONES = [
263
    67.0, 69.3, 71.9, 74.4, 77.0, 79.7, 82.5, 85.4,
264
    88.5, 91.5, 94.8, 97.4, 100.0, 103.5, 107.2, 110.9,
265
    114.8, 118.8, 123.0, 127.3, 131.8, 136.5, 141.3, 146.2,
266
    151.4, 156.7, 159.8, 162.2, 165.5, 167.9, 171.3, 173.8,
267
    177.3, 179.9, 183.5, 186.2, 189.9, 192.8, 196.6, 199.5,
268
    203.5, 206.5, 210.7, 218.1, 225.7, 229.1, 233.6, 241.8,
269
    250.3, 254.1
270
]
271

    
272
# lifted from ft4.py
273
DTCS_CODES = [
274
    23,  25,  26,  31,  32,  36,  43,  47,  51,  53,  54,
275
    65,  71,  72,  73,  74,  114, 115, 116, 122, 125, 131,
276
    132, 134, 143, 145, 152, 155, 156, 162, 165, 172, 174,
277
    205, 212, 223, 225, 226, 243, 244, 245, 246, 251, 252,
278
    255, 261, 263, 265, 266, 271, 274, 306, 311, 315, 325,
279
    331, 332, 343, 346, 351, 356, 364, 365, 371, 411, 412,
280
    413, 423, 431, 432, 445, 446, 452, 454, 455, 462, 464,
281
    465, 466, 503, 506, 516, 523, 526, 532, 546, 565, 606,
282
    612, 624, 627, 631, 632, 654, 662, 664, 703, 712, 723,
283
    731, 732, 734, 743, 754
284
]
285

    
286
FLOCK_LIST = ["Off", "FCC", "CE", "GB", "430", "438"]
287

    
288
SCANRESUME_LIST = ["TO: Resume after 5 seconds",
289
                   "CO: Resume after signal dissapears",
290
                   "SE: Stop scanning after receiving a signal"]
291

    
292
WELCOME_LIST = ["Full Screen", "Welcome Info", "Voltage"]
293
KEYPADTONE_LIST = ["Off", "Chinese", "English"]
294
LANGUAGE_LIST = ["Chinese", "English"]
295
ALARMMODE_LIST = ["SITE", "TONE"]
296
REMENDOFTALK_LIST = ["Off", "ROGER", "MDC"]
297
RTE_LIST = ["Off", "100ms", "200ms", "300ms", "400ms",
298
            "500ms", "600ms", "700ms", "800ms", "900ms"]
299

    
300
MEM_SIZE = 0x2000  # size of all memory
301
PROG_SIZE = 0x1d00  # size of the memory that we will write
302
MEM_BLOCK = 0x80  # largest block of memory that we can reliably write
303

    
304
# fm radio supported frequencies
305
FMMIN = 76.0
306
FMMAX = 108.0
307

    
308
# bands supported by the UV-K5
309
BANDS = {
310
        0: [50.0, 76.0],
311
        1: [108.0, 135.9999],
312
        2: [136.0, 199.9990],
313
        3: [200.0, 299.9999],
314
        4: [350.0, 399.9999],
315
        5: [400.0, 469.9999],
316
        6: [470.0, 600.0]
317
        }
318

    
319
# for radios with modified firmware:
320
BANDS_NOLIMITS = {
321
        0: [18.0, 76.0],
322
        1: [108.0, 135.9999],
323
        2: [136.0, 199.9990],
324
        3: [200.0, 299.9999],
325
        4: [350.0, 399.9999],
326
        5: [400.0, 469.9999],
327
        6: [470.0, 1300.0]
328
        }
329

    
330
SPECIALS = {
331
        "F1(50M-76M)A": 200,
332
        "F1(50M-76M)B": 201,
333
        "F2(108M-136M)A": 202,
334
        "F2(108M-136M)B": 203,
335
        "F3(136M-174M)A": 204,
336
        "F3(136M-174M)B": 205,
337
        "F4(174M-350M)A": 206,
338
        "F4(174M-350M)B": 207,
339
        "F5(350M-400M)A": 208,
340
        "F5(350M-400M)B": 209,
341
        "F6(400M-470M)A": 210,
342
        "F6(400M-470M)B": 211,
343
        "F7(470M-600M)A": 212,
344
        "F7(470M-600M)B": 213
345
        }
346

    
347
VFO_CHANNEL_NAMES = ["F1(50M-76M)A", "F1(50M-76M)B",
348
                     "F2(108M-136M)A", "F2(108M-136M)B",
349
                     "F3(136M-174M)A", "F3(136M-174M)B",
350
                     "F4(174M-350M)A", "F4(174M-350M)B",
351
                     "F5(350M-400M)A", "F5(350M-400M)B",
352
                     "F6(400M-470M)A", "F6(400M-470M)B",
353
                     "F7(470M-600M)A", "F7(470M-600M)B"]
354

    
355
SCANLIST_LIST = ["None", "1", "2", "1+2"]
356

    
357
DTMF_CHARS = "0123456789ABCD*# "
358
DTMF_CHARS_ID = "0123456789ABCDabcd"
359
DTMF_CHARS_KILL = "0123456789ABCDabcd"
360
DTMF_CHARS_UPDOWN = "0123456789ABCDabcd#* "
361
DTMF_CODE_CHARS = "ABCD*# "
362
DTMF_DECODE_RESPONSE_LIST = ["None", "Ring", "Reply", "Both"]
363

    
364
KEYACTIONS_LIST = ["None", "Flashlight on/off", "Power select",
365
                   "Monitor", "Scan on/off", "VOX on/off",
366
                   "Alarm on/off", "FM radio on/off", "Transmit 1750Hz"]
367

    
368

    
369
# the communication is obfuscated using this fine mechanism
370
def xorarr(data: bytes):
371
    tbl = [22, 108, 20, 230, 46, 145, 13, 64, 33, 53, 213, 64, 19, 3, 233, 128]
372
    x = b""
373
    r = 0
374
    for byte in data:
375
        x += bytes([byte ^ tbl[r]])
376
        r = (r+1) % len(tbl)
377
    return x
378

    
379

    
380
# if this crc was used for communication to AND from the radio, then it
381
# would be a measure to increase reliability.
382
# but it's only used towards the radio, so it's for further obfuscation
383
def calculate_crc16_xmodem(data: bytes):
384
    poly = 0x1021
385
    crc = 0x0
386
    for byte in data:
387
        crc = crc ^ (byte << 8)
388
        for i in range(8):
389
            crc = crc << 1
390
            if (crc & 0x10000):
391
                crc = (crc ^ poly) & 0xFFFF
392
    return crc & 0xFFFF
393

    
394

    
395
def _send_command(serport, data: bytes):
396
    """Send a command to UV-K5 radio"""
397
    LOG.debug("Sending command (unobfuscated) len=0x%4.4x:\n%s" %
398
              (len(data), util.hexprint(data)))
399

    
400
    crc = calculate_crc16_xmodem(data)
401
    data2 = data + struct.pack("<H", crc)
402

    
403
    command = struct.pack(">HBB", 0xabcd, len(data), 0) + \
404
        xorarr(data2) + \
405
        struct.pack(">H", 0xdcba)
406
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
407
        LOG.debug("Sending command (obfuscated):\n%s" % util.hexprint(command))
408
    try:
409
        result = serport.write(command)
410
    except Exception:
411
        raise errors.RadioError("Error writing data to radio")
412
    return result
413

    
414

    
415
def _receive_reply(serport):
416
    header = serport.read(4)
417
    if len(header) != 4:
418
        LOG.warning("Header short read: [%s] len=%i" %
419
                    (util.hexprint(header), len(header)))
420
        raise errors.RadioError("Header short read")
421
    if header[0] != 0xAB or header[1] != 0xCD or header[3] != 0x00:
422
        LOG.warning("Bad response header: %s len=%i" %
423
                    (util.hexprint(header), len(header)))
424
        raise errors.RadioError("Bad response header")
425

    
426
    cmd = serport.read(int(header[2]))
427
    if len(cmd) != int(header[2]):
428
        LOG.warning("Body short read: [%s] len=%i" %
429
                    (util.hexprint(cmd), len(cmd)))
430
        raise errors.RadioError("Command body short read")
431

    
432
    footer = serport.read(4)
433

    
434
    if len(footer) != 4:
435
        LOG.warning("Footer short read: [%s] len=%i" %
436
                    (util.hexprint(footer), len(footer)))
437
        raise errors.RadioError("Footer short read")
438

    
439
    if footer[2] != 0xDC or footer[3] != 0xBA:
440
        LOG.debug(
441
                "Reply before bad response footer (obfuscated)"
442
                "len=0x%4.4x:\n%s" % (len(cmd), util.hexprint(cmd)))
443
        LOG.warning("Bad response footer: %s len=%i" %
444
                    (util.hexprint(footer), len(footer)))
445
        raise errors.RadioError("Bad response footer")
446

    
447
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
448
        LOG.debug("Received reply (obfuscated) len=0x%4.4x:\n%s" %
449
                  (len(cmd), util.hexprint(cmd)))
450

    
451
    cmd2 = xorarr(cmd)
452

    
453
    LOG.debug("Received reply (unobfuscated) len=0x%4.4x:\n%s" %
454
              (len(cmd2), util.hexprint(cmd2)))
455

    
456
    return cmd2
457

    
458

    
459
def _getstring(data: bytes, begin, maxlen):
460
    tmplen = min(maxlen+1, len(data))
461
    s = [data[i] for i in range(begin, tmplen)]
462
    for key, val in enumerate(s):
463
        if val < ord(' ') or val > ord('~'):
464
            break
465
    return ''.join(chr(x) for x in s[0:key])
466

    
467

    
468
def _sayhello(serport):
469
    hellopacket = b"\x14\x05\x04\x00\x6a\x39\x57\x64"
470

    
471
    tries = 5
472
    while True:
473
        LOG.debug("Sending hello packet")
474
        _send_command(serport, hellopacket)
475
        o = _receive_reply(serport)
476
        if (o):
477
            break
478
        tries -= 1
479
        if tries == 0:
480
            LOG.warning("Failed to initialise radio")
481
            raise errors.RadioError("Failed to initialize radio")
482
    firmware = _getstring(o, 4, 16)
483
    LOG.info("Found firmware: %s" % firmware)
484
    return firmware
485

    
486

    
487
def _readmem(serport, offset, length):
488
    LOG.debug("Sending readmem offset=0x%4.4x len=0x%4.4x" % (offset, length))
489

    
490
    readmem = b"\x1b\x05\x08\x00" + \
491
        struct.pack("<HBB", offset, length, 0) + \
492
        b"\x6a\x39\x57\x64"
493
    _send_command(serport, readmem)
494
    o = _receive_reply(serport)
495
    if DEBUG_SHOW_MEMORY_ACTIONS:
496
        LOG.debug("readmem Received data len=0x%4.4x:\n%s" %
497
                  (len(o), util.hexprint(o)))
498
    return o[8:]
499

    
500

    
501
def _writemem(serport, data, offset):
502
    LOG.debug("Sending writemem offset=0x%4.4x len=0x%4.4x" %
503
              (offset, len(data)))
504

    
505
    if DEBUG_SHOW_MEMORY_ACTIONS:
506
        LOG.debug("writemem sent data offset=0x%4.4x len=0x%4.4x:\n%s" %
507
                  (offset, len(data), util.hexprint(data)))
508

    
509
    dlen = len(data)
510
    writemem = b"\x1d\x05" + \
511
        struct.pack("<BBHBB", dlen+8, 0, offset, dlen, 1) + \
512
        b"\x6a\x39\x57\x64"+data
513

    
514
    _send_command(serport, writemem)
515
    o = _receive_reply(serport)
516

    
517
    LOG.debug("writemem Received data: %s len=%i" % (util.hexprint(o), len(o)))
518

    
519
    if (o[0] == 0x1e
520
            and
521
            o[4] == (offset & 0xff)
522
            and
523
            o[5] == (offset >> 8) & 0xff):
524
        return True
525
    else:
526
        LOG.warning("Bad data from writemem")
527
        raise errors.RadioError("Bad response to writemem")
528

    
529

    
530
def _resetradio(serport):
531
    resetpacket = b"\xdd\x05\x00\x00"
532
    _send_command(serport, resetpacket)
533

    
534

    
535
def do_download(radio):
536
    serport = radio.pipe
537
    serport.timeout = 0.5
538
    status = chirp_common.Status()
539
    status.cur = 0
540
    status.max = MEM_SIZE
541
    status.msg = "Downloading from radio"
542
    radio.status_fn(status)
543

    
544
    eeprom = b""
545
    f = _sayhello(serport)
546
    if f:
547
        radio.FIRMWARE_VERSION = f
548
    else:
549
        return False
550

    
551
    addr = 0
552
    while addr < MEM_SIZE:
553
        o = _readmem(serport, addr, MEM_BLOCK)
554
        status.cur = addr
555
        radio.status_fn(status)
556

    
557
        if o and len(o) == MEM_BLOCK:
558
            eeprom += o
559
            addr += MEM_BLOCK
560
        else:
561
            raise errors.RadioError("Memory download incomplete")
562

    
563
    return memmap.MemoryMapBytes(eeprom)
564

    
565

    
566
def do_upload(radio):
567
    serport = radio.pipe
568
    serport.timeout = 0.5
569
    status = chirp_common.Status()
570
    status.cur = 0
571
    status.max = PROG_SIZE
572
    status.msg = "Uploading to radio"
573
    radio.status_fn(status)
574

    
575
    f = _sayhello(serport)
576
    if f:
577
        radio.FIRMWARE_VERSION = f
578
    else:
579
        return False
580

    
581
    addr = 0
582
    while addr < PROG_SIZE:
583
        o = radio.get_mmap()[addr:addr+MEM_BLOCK]
584
        _writemem(serport, o, addr)
585
        status.cur = addr
586
        radio.status_fn(status)
587
        if o:
588
            addr += MEM_BLOCK
589
        else:
590
            raise errors.RadioError("Memory upload incomplete")
591
    status.msg = "Uploaded OK"
592

    
593
    _resetradio(serport)
594

    
595
    return True
596

    
597

    
598
def _find_band(self, hz):
599
    mhz = hz/1000000.0
600
    if self.FIRMWARE_NOLIMITS:
601
        B = BANDS_NOLIMITS
602
    else:
603
        B = BANDS
604

    
605
    # currently the hacked firmware sets band=1 below 50MHz
606
    if self.FIRMWARE_NOLIMITS and mhz < 50.0:
607
        return 1
608

    
609
    for a in B:
610
        if mhz >= B[a][0] and mhz <= B[a][1]:
611
            return a
612
    return False
613

    
614

    
615
@directory.register
616
class UVK5Radio(chirp_common.CloneModeRadio):
617
    """Quansheng UV-K5"""
618
    VENDOR = "Quansheng"
619
    MODEL = "UV-K5"
620
    BAUD_RATE = 38400
621
    NEEDS_COMPAT_SERIAL = False
622
    FIRMWARE_VERSION = ""
623
    FIRMWARE_NOLIMITS = False
624

    
625
    def get_prompts(x=None):
626
        rp = chirp_common.RadioPrompts()
627
        rp.experimental = \
628
            ('This is an experimental driver for the Quansheng UV-K5. '
629
             'It may harm your radio, or worse. Use at your own risk.\n\n'
630
             'Before attempting to do any changes please download'
631
             'the memory image from the radio with chirp '
632
             'and keep it. This can be later used to recover the '
633
             'original settings. \n\n'
634
             'some details are not yet implemented')
635
        rp.pre_download = _(
636
            "1. Turn radio on.\n"
637
            "2. Connect cable to mic/spkr connector.\n"
638
            "3. Make sure connector is firmly connected.\n"
639
            "4. Click OK to download image from device.\n\n"
640
            "It will may not work if you turn on the radio "
641
            "with the cable already attached\n")
642
        rp.pre_upload = _(
643
            "1. Turn radio on.\n"
644
            "2. Connect cable to mic/spkr connector.\n"
645
            "3. Make sure connector is firmly connected.\n"
646
            "4. Click OK to upload the image to device.\n\n"
647
            "It will may not work if you turn on the radio "
648
            "with the cable already attached")
649
        return rp
650

    
651
    # Return information about this radio's features, including
652
    # how many memories it has, what bands it supports, etc
653
    def get_features(self):
654
        rf = chirp_common.RadioFeatures()
655
        rf.has_bank = False
656
        rf.valid_dtcs_codes = DTCS_CODES
657
        rf.has_rx_dtcs = True
658
        rf.has_ctone = True
659
        rf.has_settings = True
660
        rf.has_comment = False
661
        rf.valid_name_length = 10
662
        rf.valid_power_levels = UVK5_POWER_LEVELS
663
        rf.valid_special_chans = list(SPECIALS.keys())
664

    
665
        # hack so we can input any frequency,
666
        # the 0.1 and 0.01 steps don't work unfortunately
667
        rf.valid_tuning_steps = [0.01, 0.1, 1.0] + STEPS
668

    
669
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
670
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
671
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
672

    
673
        rf.valid_characters = chirp_common.CHARSET_ASCII
674
        rf.valid_modes = ["FM", "NFM", "AM", "NAM"]
675
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
676

    
677
        rf.valid_skips = [""]
678

    
679
        # This radio supports memories 1-200, 201-214 are the VFO memories
680
        rf.memory_bounds = (1, 200)
681

    
682
        # This is what the BK4819 chip supports
683
        # Will leave it in a comment, might be useful someday
684
        # rf.valid_bands = [(18000000,  620000000),
685
        #                  (840000000, 1300000000)
686
        #                  ]
687
        rf.valid_bands = []
688
        for a in BANDS:
689
            rf.valid_bands.append(
690
                    (int(BANDS[a][0]*1000000), int(BANDS[a][1]*1000000)))
691
        return rf
692

    
693
    # Do a download of the radio from the serial port
694
    def sync_in(self):
695
        self._mmap = do_download(self)
696
        self.process_mmap()
697

    
698
    # Do an upload of the radio to the serial port
699
    def sync_out(self):
700
        do_upload(self)
701

    
702
    # Convert the raw byte array into a memory object structure
703
    def process_mmap(self):
704
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
705

    
706
    # Return a raw representation of the memory object, which
707
    # is very helpful for development
708
    def get_raw_memory(self, number):
709
        return repr(self._memobj.channel[number-1])
710

    
711
    def validate_memory(self, mem):
712
        msgs = super().validate_memory(mem)
713

    
714
        # find tx frequency
715
        if mem.duplex == '-':
716
            txfreq = mem.freq - mem.offset
717
        elif mem.duplex == '+':
718
            txfreq = mem.freq + mem.offset
719
        else:
720
            txfreq = mem.freq
721

    
722
        # find band
723
        band = _find_band(self, txfreq)
724
        if band is False:
725
            msg = "Transmit frequency %.4fMHz is not supported by this radio" \
726
                   % (txfreq/1000000.0)
727
            msgs.append(chirp_common.ValidationWarning(msg))
728

    
729
        band = _find_band(self, mem.freq)
730
        if band is False:
731
            msg = "The frequency %.4fMHz is not supported by this radio" \
732
                   % (mem.freq/1000000.0)
733
            msgs.append(chirp_common.ValidationWarning(msg))
734

    
735
        return msgs
736

    
737
    def _set_tone(self, mem, _mem):
738
        ((txmode, txtone, txpol),
739
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
740

    
741
        if txmode == "Tone":
742
            txtoval = CTCSS_TONES.index(txtone)
743
            txmoval = 0b01
744
        elif txmode == "DTCS":
745
            txmoval = txpol == "R" and 0b11 or 0b10
746
            txtoval = DTCS_CODES.index(txtone)
747
        else:
748
            txmoval = 0
749
            txtoval = 0
750

    
751
        if rxmode == "Tone":
752
            rxtoval = CTCSS_TONES.index(rxtone)
753
            rxmoval = 0b01
754
        elif rxmode == "DTCS":
755
            rxmoval = rxpol == "R" and 0b11 or 0b10
756
            rxtoval = DTCS_CODES.index(rxtone)
757
        else:
758
            rxmoval = 0
759
            rxtoval = 0
760

    
761
        _mem.rxcodeflag = rxmoval
762
        _mem.txcodeflag = txmoval
763
        _mem.unknown1 = 0
764
        _mem.unknown2 = 0
765
        _mem.rxcode = rxtoval
766
        _mem.txcode = txtoval
767

    
768
    def _get_tone(self, mem, _mem):
769
        rxtype = _mem.rxcodeflag
770
        txtype = _mem.txcodeflag
771
        rx_tmode = TMODES[rxtype]
772
        tx_tmode = TMODES[txtype]
773

    
774
        rx_tone = tx_tone = None
775

    
776
        if tx_tmode == "Tone":
777
            if _mem.txcode < len(CTCSS_TONES):
778
                tx_tone = CTCSS_TONES[_mem.txcode]
779
            else:
780
                tx_tone = 0
781
                tx_tmode = ""
782
        elif tx_tmode == "DTCS":
783
            if _mem.txcode < len(DTCS_CODES):
784
                tx_tone = DTCS_CODES[_mem.txcode]
785
            else:
786
                tx_tone = 0
787
                tx_tmode = ""
788

    
789
        if rx_tmode == "Tone":
790
            if _mem.rxcode < len(CTCSS_TONES):
791
                rx_tone = CTCSS_TONES[_mem.rxcode]
792
            else:
793
                rx_tone = 0
794
                rx_tmode = ""
795
        elif rx_tmode == "DTCS":
796
            if _mem.rxcode < len(DTCS_CODES):
797
                rx_tone = DTCS_CODES[_mem.rxcode]
798
            else:
799
                rx_tone = 0
800
                rx_tmode = ""
801

    
802
        tx_pol = txtype == 0x03 and "R" or "N"
803
        rx_pol = rxtype == 0x03 and "R" or "N"
804

    
805
        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
806
                                       (rx_tmode, rx_tone, rx_pol))
807

    
808
    # Extract a high-level memory object from the low-level memory map
809
    # This is called to populate a memory in the UI
810
    def get_memory(self, number2):
811

    
812
        mem = chirp_common.Memory()
813

    
814
        if isinstance(number2, str):
815
            number = SPECIALS[number2]
816
            mem.extd_number = number2
817
        else:
818
            number = number2 - 1
819

    
820
        mem.number = number + 1
821

    
822
        _mem = self._memobj.channel[number]
823

    
824
        tmpcomment = ""
825

    
826
        is_empty = False
827
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
828
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
829
            is_empty = True
830

    
831
        tmpscn = SCANLIST_LIST[0]
832

    
833
        # We'll also look at the channel attributes if a memory has them
834
        if number < 200:
835
            _mem3 = self._memobj.channel_attributes[number]
836
            # free memory bit
837
            if _mem3.is_free > 0:
838
                is_empty = True
839
            # scanlists
840
            if _mem3.is_scanlist1 > 0 and _mem3.is_scanlist2 > 0:
841
                tmpscn = SCANLIST_LIST[3]  # "1+2"
842
            elif _mem3.is_scanlist1 > 0:
843
                tmpscn = SCANLIST_LIST[1]  # "1"
844
            elif _mem3.is_scanlist2 > 0:
845
                tmpscn = SCANLIST_LIST[2]  # "2"
846

    
847
        if is_empty:
848
            mem.empty = True
849
            # set some sane defaults:
850
            mem.power = UVK5_POWER_LEVELS[2]
851
            mem.extra = RadioSettingGroup("Extra", "extra")
852
            rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(False))
853
            mem.extra.append(rs)
854
            rs = RadioSetting("frev", "FreqRev",
855
                              RadioSettingValueBoolean(False))
856
            mem.extra.append(rs)
857
            rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
858
                PTTID_LIST, PTTID_LIST[0]))
859
            mem.extra.append(rs)
860
            rs = RadioSetting("dtmfdecode", "DTMF decode",
861
                              RadioSettingValueBoolean(False))
862
            mem.extra.append(rs)
863
            rs = RadioSetting("scrambler", "Scrambler", RadioSettingValueList(
864
                SCRAMBLER_LIST, SCRAMBLER_LIST[0]))
865
            mem.extra.append(rs)
866

    
867
            rs = RadioSetting("scanlists", "Scanlists", RadioSettingValueList(
868
                SCANLIST_LIST, SCANLIST_LIST[0]))
869
            mem.extra.append(rs)
870

    
871
            # actually the step and duplex are overwritten by chirp based on
872
            # bandplan. they are here to document sane defaults for IARU r1
873
            # mem.tuning_step = 25.0
874
            # mem.duplex = "off"
875

    
876
            return mem
877

    
878
        if number > 199:
879
            mem.name = VFO_CHANNEL_NAMES[number-200]
880
            mem.immutable = ["name", "scanlists"]
881
        else:
882
            _mem2 = self._memobj.channelname[number]
883
            for char in _mem2.name:
884
                if str(char) == "\xFF" or str(char) == "\x00":
885
                    break
886
                mem.name += str(char)
887
            mem.name = mem.name.rstrip()
888

    
889
        # Convert your low-level frequency to Hertz
890
        mem.freq = int(_mem.freq)*10
891
        mem.offset = int(_mem.offset)*10
892

    
893
        if (mem.offset == 0):
894
            mem.duplex = ''
895
        else:
896
            if _mem.shift == FLAGS1_OFFSET_MINUS:
897
                mem.duplex = '-'
898
            elif _mem.shift == FLAGS1_OFFSET_PLUS:
899
                mem.duplex = '+'
900
            else:
901
                mem.duplex = ''
902

    
903
        # tone data
904
        self._get_tone(mem, _mem)
905

    
906
        # mode
907
        if _mem.enable_am > 0:
908
            if _mem.bandwidth > 0:
909
                mem.mode = "NAM"
910
            else:
911
                mem.mode = "AM"
912
        else:
913
            if _mem.bandwidth > 0:
914
                mem.mode = "NFM"
915
            else:
916
                mem.mode = "FM"
917

    
918
        # tuning step
919
        tstep = _mem.step & 0x7
920
        if tstep < len(STEPS):
921
            mem.tuning_step = STEPS[tstep]
922
        else:
923
            mem.tuning_step = 2.5
924

    
925
        # power
926
        if _mem.txpower == POWER_HIGH:
927
            mem.power = UVK5_POWER_LEVELS[2]
928
        elif _mem.txpower == POWER_MEDIUM:
929
            mem.power = UVK5_POWER_LEVELS[1]
930
        else:
931
            mem.power = UVK5_POWER_LEVELS[0]
932

    
933
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
934
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
935
            mem.empty = True
936
        else:
937
            mem.empty = False
938

    
939
        mem.extra = RadioSettingGroup("Extra", "extra")
940

    
941
        # BCLO
942
        is_bclo = bool(_mem.bclo > 0)
943
        rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(is_bclo))
944
        mem.extra.append(rs)
945
        tmpcomment += "BCLO:"+(is_bclo and "ON" or "off")+" "
946

    
947
        # Frequency reverse - whatever that means, don't see it in the manual
948
        is_frev = bool(_mem.freq_reverse > 0)
949
        rs = RadioSetting("frev", "FreqRev", RadioSettingValueBoolean(is_frev))
950
        mem.extra.append(rs)
951
        tmpcomment += "FreqReverse:"+(is_frev and "ON" or "off")+" "
952

    
953
        # PTTID
954
        pttid = _mem.dtmf_pttid
955
        rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
956
            PTTID_LIST, PTTID_LIST[pttid]))
957
        mem.extra.append(rs)
958
        tmpcomment += "PTTid:"+PTTID_LIST[pttid]+" "
959

    
960
        # DTMF DECODE
961
        is_dtmf = bool(_mem.dtmf_decode > 0)
962
        rs = RadioSetting("dtmfdecode", "DTMF decode",
963
                          RadioSettingValueBoolean(is_dtmf))
964
        mem.extra.append(rs)
965
        tmpcomment += "DTMFdecode:"+(is_dtmf and "ON" or "off")+" "
966

    
967
        # Scrambler
968
        if _mem.scrambler & 0x0f < len(SCRAMBLER_LIST):
969
            enc = _mem.scrambler & 0x0f
970
        else:
971
            enc = 0
972

    
973
        rs = RadioSetting("scrambler", "Scrambler", RadioSettingValueList(
974
            SCRAMBLER_LIST, SCRAMBLER_LIST[enc]))
975
        mem.extra.append(rs)
976
        tmpcomment += "Scrambler:"+SCRAMBLER_LIST[enc]+" "
977

    
978
        rs = RadioSetting("scanlists", "Scanlists", RadioSettingValueList(
979
            SCANLIST_LIST, tmpscn))
980
        mem.extra.append(rs)
981

    
982
        return mem
983

    
984
    def set_settings(self, settings):
985
        _mem = self._memobj
986
        for element in settings:
987
            if not isinstance(element, RadioSetting):
988
                self.set_settings(element)
989
                continue
990

    
991
            # basic settings
992

    
993
            # call channel
994
            if element.get_name() == "call_channel":
995
                _mem.call_channel = int(element.value)-1
996

    
997
            # squelch
998
            if element.get_name() == "squelch":
999
                _mem.squelch = int(element.value)
1000
            # TOT
1001
            if element.get_name() == "tot":
1002
                _mem.max_talk_time = int(element.value)
1003

    
1004
            # NOAA autoscan
1005
            if element.get_name() == "noaa_autoscan":
1006
                _mem.noaa_autoscan = element.value and 1 or 0
1007

    
1008
            # VOX switch
1009
            if element.get_name() == "vox_switch":
1010
                _mem.vox_switch = element.value and 1 or 0
1011

    
1012
            # vox level
1013
            if element.get_name() == "vox_level":
1014
                _mem.vox_level = int(element.value)-1
1015

    
1016
            # mic gain
1017
            if element.get_name() == "mic_gain":
1018
                _mem.mic_gain = int(element.value)
1019

    
1020
            # Channel display mode
1021
            if element.get_name() == "channel_display_mode":
1022
                _mem.channel_display_mode = CHANNELDISP_LIST.index(
1023
                    str(element.value))
1024

    
1025
            # Crossband receiving/transmitting
1026
            if element.get_name() == "crossband":
1027
                _mem.crossband = CROSSBAND_LIST.index(str(element.value))
1028

    
1029
            # Battery Save
1030
            if element.get_name() == "battery_save":
1031
                _mem.battery_save = BATSAVE_LIST.index(str(element.value))
1032
            # Dual Watch
1033
            if element.get_name() == "dualwatch":
1034
                _mem.dual_watch = DUALWATCH_LIST.index(str(element.value))
1035

    
1036
            # Backlight auto mode
1037
            if element.get_name() == "backlight_auto_mode":
1038
                _mem.backlight_auto_mode = \
1039
                        BACKLIGHT_LIST.index(str(element.value))
1040

    
1041
            # Tail tone elimination
1042
            if element.get_name() == "tail_note_elimination":
1043
                _mem.tail_note_elimination = element.value and 1 or 0
1044

    
1045
            # VFO Open
1046
            if element.get_name() == "vfo_open":
1047
                _mem.vfo_open = element.value and 1 or 0
1048

    
1049
            # Beep control
1050
            if element.get_name() == "beep_control":
1051
                _mem.beep_control = element.value and 1 or 0
1052

    
1053
            # Scan resume mode
1054
            if element.get_name() == "scan_resume_mode":
1055
                _mem.scan_resume_mode = SCANRESUME_LIST.index(
1056
                    str(element.value))
1057

    
1058
            # Keypad lock
1059
            if element.get_name() == "key_lock":
1060
                _mem.key_lock = element.value and 1 or 0
1061

    
1062
            # Auto keypad lock
1063
            if element.get_name() == "auto_keypad_lock":
1064
                _mem.auto_keypad_lock = element.value and 1 or 0
1065

    
1066
            # Power on display mode
1067
            if element.get_name() == "welcome_mode":
1068
                _mem.power_on_dispmode = WELCOME_LIST.index(str(element.value))
1069

    
1070
            # Keypad Tone
1071
            if element.get_name() == "keypad_tone":
1072
                _mem.keypad_tone = KEYPADTONE_LIST.index(str(element.value))
1073

    
1074
            # Language
1075
            if element.get_name() == "language":
1076
                _mem.language = LANGUAGE_LIST.index(str(element.value))
1077

    
1078
            # Alarm mode
1079
            if element.get_name() == "alarm_mode":
1080
                _mem.alarm_mode = ALARMMODE_LIST.index(str(element.value))
1081

    
1082
            # Reminding of end of talk
1083
            if element.get_name() == "reminding_of_end_talk":
1084
                _mem.reminding_of_end_talk = REMENDOFTALK_LIST.index(
1085
                    str(element.value))
1086

    
1087
            # Repeater tail tone elimination
1088
            if element.get_name() == "repeater_tail_elimination":
1089
                _mem.repeater_tail_elimination = RTE_LIST.index(
1090
                    str(element.value))
1091

    
1092
            # Logo string 1
1093
            if element.get_name() == "logo1":
1094
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
1095
                _mem.logo_line1 = b[0:12]+"\x00\xff\xff\xff"
1096

    
1097
            # Logo string 2
1098
            if element.get_name() == "logo2":
1099
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
1100
                _mem.logo_line2 = b[0:12]+"\x00\xff\xff\xff"
1101

    
1102
            # unlock settings
1103

    
1104
            # FLOCK
1105
            if element.get_name() == "flock":
1106
                _mem.int_flock = FLOCK_LIST.index(str(element.value))
1107

    
1108
            # 350TX
1109
            if element.get_name() == "350tx":
1110
                _mem.int_350tx = element.value and 1 or 0
1111

    
1112
            # 200TX
1113
            if element.get_name() == "200tx":
1114
                _mem.int_200tx = element.value and 1 or 0
1115

    
1116
            # 500TX
1117
            if element.get_name() == "500tx":
1118
                _mem.int_500tx = element.value and 1 or 0
1119

    
1120
            # 350EN
1121
            if element.get_name() == "350en":
1122
                _mem.int_350en = element.value and 1 or 0
1123

    
1124
            # SCREN
1125
            if element.get_name() == "scren":
1126
                _mem.int_scren = element.value and 1 or 0
1127

    
1128
            # fm radio
1129
            for i in range(1, 21):
1130
                freqname = "FM_" + str(i)
1131
                if element.get_name() == freqname:
1132
                    val = str(element.value).strip()
1133
                    try:
1134
                        val2 = int(float(val)*10)
1135
                    except Exception:
1136
                        val2 = 0xffff
1137

    
1138
                    if val2 < FMMIN*10 or val2 > FMMAX*10:
1139
                        val2 = 0xffff
1140
#                        raise errors.InvalidValueError(
1141
#                                "FM radio frequency should be a value "
1142
#                                "in the range %.1f - %.1f" % (FMMIN , FMMAX))
1143
                    _mem.fmfreq[i-1] = val2
1144

    
1145
            # dtmf settings
1146
            if element.get_name() == "dtmf_side_tone":
1147
                _mem.dtmf_settings.side_tone = \
1148
                        element.value and 1 or 0
1149

    
1150
            if element.get_name() == "dtmf_separate_code":
1151
                _mem.dtmf_settings.separate_code = str(element.value)
1152

    
1153
            if element.get_name() == "dtmf_group_call_code":
1154
                _mem.dtmf_settings.group_call_code = element.value
1155

    
1156
            if element.get_name() == "dtmf_decode_response":
1157
                _mem.dtmf_settings.decode_response = \
1158
                        DTMF_DECODE_RESPONSE_LIST.index(str(element.value))
1159

    
1160
            if element.get_name() == "dtmf_auto_reset_time":
1161
                _mem.dtmf_settings.auto_reset_time = \
1162
                        int(int(element.value)/10)
1163

    
1164
            if element.get_name() == "dtmf_preload_time":
1165
                _mem.dtmf_settings.preload_time = \
1166
                        int(int(element.value)/10)
1167

    
1168
            if element.get_name() == "dtmf_first_code_persist_time":
1169
                _mem.dtmf_settings.first_code_persist_time = \
1170
                        int(int(element.value)/10)
1171

    
1172
            if element.get_name() == "dtmf_hash_persist_time":
1173
                _mem.dtmf_settings.hash_persist_time = \
1174
                        int(int(element.value)/10)
1175

    
1176
            if element.get_name() == "dtmf_code_persist_time":
1177
                _mem.dtmf_settings.code_persist_time = \
1178
                        int(int(element.value)/10)
1179

    
1180
            if element.get_name() == "dtmf_code_interval_time":
1181
                _mem.dtmf_settings.code_interval_time = \
1182
                        int(int(element.value)/10)
1183

    
1184
            if element.get_name() == "dtmf_permit_remote_kill":
1185
                _mem.dtmf_settings.permit_remote_kill = \
1186
                        element.value and 1 or 0
1187

    
1188
            if element.get_name() == "dtmf_dtmf_local_code":
1189
                k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*3
1190
                _mem.dtmf_settings_numbers.dtmf_local_code = k[0:3]
1191

    
1192
            if element.get_name() == "dtmf_dtmf_up_code":
1193
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*16
1194
                _mem.dtmf_settings_numbers.dtmf_up_code = k[0:16]
1195

    
1196
            if element.get_name() == "dtmf_dtmf_down_code":
1197
                k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*16
1198
                _mem.dtmf_settings_numbers.dtmf_down_code = k[0:16]
1199

    
1200
            if element.get_name() == "dtmf_kill_code":
1201
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*5
1202
                _mem.dtmf_settings_numbers.kill_code = k[0:5]
1203

    
1204
            if element.get_name() == "dtmf_revive_code":
1205
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*5
1206
                _mem.dtmf_settings_numbers.revive_code = k[0:5]
1207

    
1208
            # dtmf contacts
1209
            for i in range(1, 17):
1210
                varname = "DTMF_" + str(i)
1211
                if element.get_name() == varname:
1212
                    k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*8
1213
                    _mem.dtmfcontact[i-1].name = k[0:8]
1214

    
1215
                varnumname = "DTMFNUM_" + str(i)
1216
                if element.get_name() == varnumname:
1217
                    k = str(element.value).rstrip("\x20\xff\x00") + "\xff"*3
1218
                    _mem.dtmfcontact[i-1].number = k[0:3]
1219

    
1220
            # scanlist stuff
1221
            if element.get_name() == "scanlist_default":
1222
                val = (int(element.value) == 2) and 1 or 0
1223
                _mem.scanlist_default = val
1224

    
1225
            if element.get_name() == "scanlist1_priority_scan":
1226
                _mem.scanlist1_priority_scan = \
1227
                        element.value and 1 or 0
1228

    
1229
            if element.get_name() == "scanlist2_priority_scan":
1230
                _mem.scanlist2_priority_scan = \
1231
                        element.value and 1 or 0
1232

    
1233
            if element.get_name() == "scanlist1_priority_ch1" or \
1234
                    element.get_name() == "scanlist1_priority_ch2" or \
1235
                    element.get_name() == "scanlist2_priority_ch1" or \
1236
                    element.get_name() == "scanlist2_priority_ch2":
1237

    
1238
                val = int(element.value)
1239

    
1240
                if val > 200 or val < 1:
1241
                    val = 0xff
1242
                else:
1243
                    val -= 1
1244

    
1245
                if element.get_name() == "scanlist1_priority_ch1":
1246
                    _mem.scanlist1_priority_ch1 = val
1247
                if element.get_name() == "scanlist1_priority_ch2":
1248
                    _mem.scanlist1_priority_ch2 = val
1249
                if element.get_name() == "scanlist2_priority_ch1":
1250
                    _mem.scanlist2_priority_ch1 = val
1251
                if element.get_name() == "scanlist2_priority_ch2":
1252
                    _mem.scanlist2_priority_ch2 = val
1253

    
1254
            if element.get_name() == "key1_shortpress_action":
1255
                _mem.key1_shortpress_action = KEYACTIONS_LIST.index(
1256
                        str(element.value))
1257

    
1258
            if element.get_name() == "key1_longpress_action":
1259
                _mem.key1_longpress_action = KEYACTIONS_LIST.index(
1260
                        str(element.value))
1261

    
1262
            if element.get_name() == "key2_shortpress_action":
1263
                _mem.key2_shortpress_action = KEYACTIONS_LIST.index(
1264
                        str(element.value))
1265

    
1266
            if element.get_name() == "key2_longpress_action":
1267
                _mem.key2_longpress_action = KEYACTIONS_LIST.index(
1268
                        str(element.value))
1269

    
1270
    def get_settings(self):
1271
        _mem = self._memobj
1272
        basic = RadioSettingGroup("basic", "Basic Settings")
1273
        keya = RadioSettingGroup("keya", "Programmable keys")
1274
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1275
        dtmfc = RadioSettingGroup("dtmfc", "DTMF Contacts")
1276
        scanl = RadioSettingGroup("scn", "Scan Lists")
1277
        unlock = RadioSettingGroup("unlock", "Unlock Settings")
1278
        fmradio = RadioSettingGroup("fmradio", "FM Radio")
1279

    
1280
        roinfo = RadioSettingGroup("roinfo", "Driver information")
1281

    
1282
        top = RadioSettings(
1283
                basic, keya, dtmf, dtmfc, scanl, unlock, fmradio, roinfo)
1284

    
1285
        # Programmable keys
1286
        tmpval = int(_mem.key1_shortpress_action)
1287
        if tmpval >= len(KEYACTIONS_LIST):
1288
            tmpval = 0
1289
        rs = RadioSetting("key1_shortpress_action", "Side key 1 short press",
1290
                          RadioSettingValueList(
1291
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1292
        keya.append(rs)
1293

    
1294
        tmpval = int(_mem.key1_longpress_action)
1295
        if tmpval >= len(KEYACTIONS_LIST):
1296
            tmpval = 0
1297
        rs = RadioSetting("key1_longpress_action", "Side key 1 long press",
1298
                          RadioSettingValueList(
1299
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1300
        keya.append(rs)
1301

    
1302
        tmpval = int(_mem.key2_shortpress_action)
1303
        if tmpval >= len(KEYACTIONS_LIST):
1304
            tmpval = 0
1305
        rs = RadioSetting("key2_shortpress_action", "Side key 2 short press",
1306
                          RadioSettingValueList(
1307
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1308
        keya.append(rs)
1309

    
1310
        tmpval = int(_mem.key2_longpress_action)
1311
        if tmpval >= len(KEYACTIONS_LIST):
1312
            tmpval = 0
1313
        rs = RadioSetting("key2_longpress_action", "Side key 2 long press",
1314
                          RadioSettingValueList(
1315
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1316
        keya.append(rs)
1317

    
1318
        # DTMF settings
1319
        tmppr = bool(_mem.dtmf_settings.side_tone > 0)
1320
        rs = RadioSetting(
1321
                "dtmf_side_tone",
1322
                "DTMF Sidetone",
1323
                RadioSettingValueBoolean(tmppr))
1324
        dtmf.append(rs)
1325

    
1326
        tmpval = str(_mem.dtmf_settings.separate_code)
1327
        if tmpval not in DTMF_CODE_CHARS:
1328
            tmpval = '*'
1329
        val = RadioSettingValueString(1, 1, tmpval)
1330
        val.set_charset(DTMF_CODE_CHARS)
1331
        rs = RadioSetting("dtmf_separate_code", "Separate Code", val)
1332
        dtmf.append(rs)
1333

    
1334
        tmpval = str(_mem.dtmf_settings.group_call_code)
1335
        if tmpval not in DTMF_CODE_CHARS:
1336
            tmpval = '#'
1337
        val = RadioSettingValueString(1, 1, tmpval)
1338
        val.set_charset(DTMF_CODE_CHARS)
1339
        rs = RadioSetting("dtmf_group_call_code", "Group Call Code", val)
1340
        dtmf.append(rs)
1341

    
1342
        tmpval = _mem.dtmf_settings.decode_response
1343
        if tmpval >= len(DTMF_DECODE_RESPONSE_LIST):
1344
            tmpval = 0
1345
        rs = RadioSetting("dtmf_decode_response", "Decode Response",
1346
                          RadioSettingValueList(
1347
                              DTMF_DECODE_RESPONSE_LIST,
1348
                              DTMF_DECODE_RESPONSE_LIST[tmpval]))
1349
        dtmf.append(rs)
1350

    
1351
        tmpval = _mem.dtmf_settings.auto_reset_time
1352
        if tmpval > 60 or tmpval < 5:
1353
            tmpval = 5
1354
        rs = RadioSetting("dtmf_auto_reset_time",
1355
                          "Auto reset time (s)",
1356
                          RadioSettingValueInteger(5, 60, tmpval))
1357
        dtmf.append(rs)
1358

    
1359
        tmpval = int(_mem.dtmf_settings.preload_time)
1360
        if tmpval > 100 or tmpval < 3:
1361
            tmpval = 30
1362
        tmpval *= 10
1363
        rs = RadioSetting("dtmf_preload_time",
1364
                          "Pre-load time (ms)",
1365
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1366
        dtmf.append(rs)
1367

    
1368
        tmpval = int(_mem.dtmf_settings.first_code_persist_time)
1369
        if tmpval > 100 or tmpval < 3:
1370
            tmpval = 30
1371
        tmpval *= 10
1372
        rs = RadioSetting("dtmf_first_code_persist_time",
1373
                          "First code persist time (ms)",
1374
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1375
        dtmf.append(rs)
1376

    
1377
        tmpval = int(_mem.dtmf_settings.hash_persist_time)
1378
        if tmpval > 100 or tmpval < 3:
1379
            tmpval = 30
1380
        tmpval *= 10
1381
        rs = RadioSetting("dtmf_hash_persist_time",
1382
                          "#/* persist time (ms)",
1383
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1384
        dtmf.append(rs)
1385

    
1386
        tmpval = int(_mem.dtmf_settings.code_persist_time)
1387
        if tmpval > 100 or tmpval < 3:
1388
            tmpval = 30
1389
        tmpval *= 10
1390
        rs = RadioSetting("dtmf_code_persist_time",
1391
                          "Code persist time (ms)",
1392
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1393
        dtmf.append(rs)
1394

    
1395
        tmpval = int(_mem.dtmf_settings.code_interval_time)
1396
        if tmpval > 100 or tmpval < 3:
1397
            tmpval = 30
1398
        tmpval *= 10
1399
        rs = RadioSetting("dtmf_code_interval_time",
1400
                          "Code interval time (ms)",
1401
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1402
        dtmf.append(rs)
1403

    
1404
        tmpval = bool(_mem.dtmf_settings.permit_remote_kill > 0)
1405
        rs = RadioSetting(
1406
                "dtmf_permit_remote_kill",
1407
                "Permit remote kill",
1408
                RadioSettingValueBoolean(tmpval))
1409
        dtmf.append(rs)
1410

    
1411
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_local_code).upper().strip(
1412
                "\x00\xff\x20")
1413
        for i in tmpval:
1414
            if i in DTMF_CHARS_ID:
1415
                continue
1416
            else:
1417
                tmpval = "103"
1418
                break
1419
        val = RadioSettingValueString(3, 3, tmpval)
1420
        val.set_charset(DTMF_CHARS_ID)
1421
        rs = RadioSetting("dtmf_dtmf_local_code",
1422
                          "Local code (3 chars 0-9 ABCD)", val)
1423
        dtmf.append(rs)
1424

    
1425
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_up_code).upper().strip(
1426
                "\x00\xff\x20")
1427
        for i in tmpval:
1428
            if i in DTMF_CHARS_UPDOWN or i == "":
1429
                continue
1430
            else:
1431
                tmpval = "123"
1432
                break
1433
        val = RadioSettingValueString(1, 16, tmpval)
1434
        val.set_charset(DTMF_CHARS_UPDOWN)
1435
        rs = RadioSetting("dtmf_dtmf_up_code",
1436
                          "Up code (1-16 chars 0-9 ABCD*#)", val)
1437
        dtmf.append(rs)
1438

    
1439
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_down_code).upper().strip(
1440
                "\x00\xff\x20")
1441
        for i in tmpval:
1442
            if i in DTMF_CHARS_UPDOWN:
1443
                continue
1444
            else:
1445
                tmpval = "456"
1446
                break
1447
        val = RadioSettingValueString(1, 16, tmpval)
1448
        val.set_charset(DTMF_CHARS_UPDOWN)
1449
        rs = RadioSetting("dtmf_dtmf_down_code",
1450
                          "Down code (1-16 chars 0-9 ABCD*#)", val)
1451
        dtmf.append(rs)
1452

    
1453
        tmpval = str(_mem.dtmf_settings_numbers.kill_code).upper().strip(
1454
                "\x00\xff\x20")
1455
        for i in tmpval:
1456
            if i in DTMF_CHARS_KILL:
1457
                continue
1458
            else:
1459
                tmpval = "77777"
1460
                break
1461
        if not len(tmpval) == 5:
1462
            tmpval = "77777"
1463
        val = RadioSettingValueString(5, 5, tmpval)
1464
        val.set_charset(DTMF_CHARS_KILL)
1465
        rs = RadioSetting("dtmf_kill_code",
1466
                          "Kill code (5 chars 0-9 ABCD)", val)
1467
        dtmf.append(rs)
1468

    
1469
        tmpval = str(_mem.dtmf_settings_numbers.revive_code).upper().strip(
1470
                "\x00\xff\x20")
1471
        for i in tmpval:
1472
            if i in DTMF_CHARS_KILL:
1473
                continue
1474
            else:
1475
                tmpval = "88888"
1476
                break
1477
        if not len(tmpval) == 5:
1478
            tmpval = "88888"
1479
        val = RadioSettingValueString(5, 5, tmpval)
1480
        val.set_charset(DTMF_CHARS_KILL)
1481
        rs = RadioSetting("dtmf_revive_code",
1482
                          "Revive code (5 chars 0-9 ABCD)", val)
1483
        dtmf.append(rs)
1484

    
1485
        val = RadioSettingValueString(0, 80,
1486
                                      "All DTMF Contacts are 3 codes "
1487
                                      "(valid: 0-9 * # ABCD), "
1488
                                      "or an empty string")
1489
        val.set_mutable(False)
1490
        rs = RadioSetting("dtmf_descr1", "DTMF Contacts", val)
1491
        dtmfc.append(rs)
1492

    
1493
        for i in range(1, 17):
1494
            varname = "DTMF_"+str(i)
1495
            varnumname = "DTMFNUM_"+str(i)
1496
            vardescr = "DTMF Contact "+str(i)+" name"
1497
            varinumdescr = "DTMF Contact "+str(i)+" number"
1498

    
1499
            cntn = str(_mem.dtmfcontact[i-1].name).strip("\x20\x00\xff")
1500
            cntnum = str(_mem.dtmfcontact[i-1].number).strip("\x20\x00\xff")
1501

    
1502
            val = RadioSettingValueString(0, 8, cntn)
1503
            rs = RadioSetting(varname, vardescr, val)
1504
            dtmfc.append(rs)
1505

    
1506
            val = RadioSettingValueString(0, 3, cntnum)
1507
            val.set_charset(DTMF_CHARS)
1508
            rs = RadioSetting(varnumname, varinumdescr, val)
1509
            dtmfc.append(rs)
1510

    
1511
        # scanlists
1512
        if _mem.scanlist_default == 1:
1513
            tmpsc = 2
1514
        else:
1515
            tmpsc = 1
1516
        rs = RadioSetting("scanlist_default",
1517
                          "Default scanlist",
1518
                          RadioSettingValueInteger(1, 2, tmpsc))
1519
        scanl.append(rs)
1520

    
1521
        tmppr = bool((_mem.scanlist1_priority_scan & 1) > 0)
1522
        rs = RadioSetting(
1523
                "scanlist1_priority_scan",
1524
                "Scanlist 1 priority channel scan",
1525
                RadioSettingValueBoolean(tmppr))
1526
        scanl.append(rs)
1527

    
1528
        tmpch = _mem.scanlist1_priority_ch1 + 1
1529
        if tmpch > 200:
1530
            tmpch = 0
1531
        rs = RadioSetting("scanlist1_priority_ch1",
1532
                          "Scanlist 1 priority channel 1 (0 - off)",
1533
                          RadioSettingValueInteger(0, 200, tmpch))
1534
        scanl.append(rs)
1535

    
1536
        tmpch = _mem.scanlist1_priority_ch2 + 1
1537
        if tmpch > 200:
1538
            tmpch = 0
1539
        rs = RadioSetting("scanlist1_priority_ch2",
1540
                          "Scanlist 1 priority channel 2 (0 - off)",
1541
                          RadioSettingValueInteger(0, 200, tmpch))
1542
        scanl.append(rs)
1543

    
1544
        tmppr = bool((_mem.scanlist2_priority_scan & 1) > 0)
1545
        rs = RadioSetting(
1546
                "scanlist2_priority_scan",
1547
                "Scanlist 2 priority channel scan",
1548
                RadioSettingValueBoolean(tmppr))
1549
        scanl.append(rs)
1550

    
1551
        tmpch = _mem.scanlist2_priority_ch1 + 1
1552
        if tmpch > 200:
1553
            tmpch = 0
1554
        rs = RadioSetting("scanlist2_priority_ch1",
1555
                          "Scanlist 2 priority channel 1 (0 - off)",
1556
                          RadioSettingValueInteger(0, 200, tmpch))
1557
        scanl.append(rs)
1558

    
1559
        tmpch = _mem.scanlist2_priority_ch2 + 1
1560
        if tmpch > 200:
1561
            tmpch = 0
1562
        rs = RadioSetting("scanlist2_priority_ch2",
1563
                          "Scanlist 2 priority channel 2 (0 - off)",
1564
                          RadioSettingValueInteger(0, 200, tmpch))
1565
        scanl.append(rs)
1566

    
1567
        # basic settings
1568

    
1569
        # call channel
1570
        tmpc = _mem.call_channel+1
1571
        if tmpc > 200:
1572
            tmpc = 1
1573
        rs = RadioSetting("call_channel", "One key call channel",
1574
                          RadioSettingValueInteger(1, 200, tmpc))
1575
        basic.append(rs)
1576

    
1577
        # squelch
1578
        tmpsq = _mem.squelch
1579
        if tmpsq > 9:
1580
            tmpsq = 1
1581
        rs = RadioSetting("squelch", "Squelch",
1582
                          RadioSettingValueInteger(0, 9, tmpsq))
1583
        basic.append(rs)
1584

    
1585
        # TOT
1586
        tmptot = _mem.max_talk_time
1587
        if tmptot > 10:
1588
            tmptot = 10
1589
        rs = RadioSetting(
1590
                "tot",
1591
                "Max talk time [min]",
1592
                RadioSettingValueInteger(0, 10, tmptot))
1593
        basic.append(rs)
1594

    
1595
        # NOAA autoscan
1596
        rs = RadioSetting(
1597
                "noaa_autoscan",
1598
                "NOAA Autoscan", RadioSettingValueBoolean(
1599
                    bool(_mem.noaa_autoscan > 0)))
1600
        basic.append(rs)
1601

    
1602
        # VOX switch
1603
        rs = RadioSetting(
1604
                "vox_switch",
1605
                "VOX enabled", RadioSettingValueBoolean(
1606
                    bool(_mem.vox_switch > 0)))
1607
        basic.append(rs)
1608

    
1609
        # VOX Level
1610
        tmpvox = _mem.vox_level+1
1611
        if tmpvox > 10:
1612
            tmpvox = 10
1613
        rs = RadioSetting("vox_level", "VOX Level",
1614
                          RadioSettingValueInteger(1, 10, tmpvox))
1615
        basic.append(rs)
1616

    
1617
        # Mic gain
1618
        tmpmicgain = _mem.mic_gain
1619
        if tmpmicgain > 4:
1620
            tmpmicgain = 4
1621
        rs = RadioSetting("mic_gain", "Mic Gain",
1622
                          RadioSettingValueInteger(0, 4, tmpmicgain))
1623
        basic.append(rs)
1624

    
1625
        # Channel display mode
1626
        tmpchdispmode = _mem.channel_display_mode
1627
        if tmpchdispmode >= len(CHANNELDISP_LIST):
1628
            tmpchdispmode = 0
1629
        rs = RadioSetting(
1630
                "channel_display_mode",
1631
                "Channel display mode",
1632
                RadioSettingValueList(
1633
                    CHANNELDISP_LIST,
1634
                    CHANNELDISP_LIST[tmpchdispmode]))
1635
        basic.append(rs)
1636

    
1637
        # Crossband receiving/transmitting
1638
        tmpcross = _mem.crossband
1639
        if tmpcross >= len(CROSSBAND_LIST):
1640
            tmpcross = 0
1641
        rs = RadioSetting(
1642
                "crossband",
1643
                "Cross-band receiving/transmitting",
1644
                RadioSettingValueList(
1645
                    CROSSBAND_LIST,
1646
                    CROSSBAND_LIST[tmpcross]))
1647
        basic.append(rs)
1648

    
1649
        # Battery save
1650
        tmpbatsave = _mem.battery_save
1651
        if tmpbatsave >= len(BATSAVE_LIST):
1652
            tmpbatsave = BATSAVE_LIST.index("1:4")
1653
        rs = RadioSetting(
1654
                "battery_save",
1655
                "Battery Save",
1656
                RadioSettingValueList(
1657
                    BATSAVE_LIST,
1658
                    BATSAVE_LIST[tmpbatsave]))
1659
        basic.append(rs)
1660

    
1661
        # Dual watch
1662
        tmpdual = _mem.dual_watch
1663
        if tmpdual >= len(DUALWATCH_LIST):
1664
            tmpdual = 0
1665
        rs = RadioSetting("dualwatch", "Dual Watch", RadioSettingValueList(
1666
            DUALWATCH_LIST, DUALWATCH_LIST[tmpdual]))
1667
        basic.append(rs)
1668

    
1669
        # Backlight auto mode
1670
        tmpback = _mem.backlight_auto_mode
1671
        if tmpback >= len(BACKLIGHT_LIST):
1672
            tmpback = 0
1673
        rs = RadioSetting("backlight_auto_mode",
1674
                          "Backlight auto mode",
1675
                          RadioSettingValueList(
1676
                              BACKLIGHT_LIST,
1677
                              BACKLIGHT_LIST[tmpback]))
1678
        basic.append(rs)
1679

    
1680
        # Tail tone elimination
1681
        rs = RadioSetting(
1682
                "tail_note_elimination",
1683
                "Tail tone elimination",
1684
                RadioSettingValueBoolean(
1685
                    bool(_mem.tail_note_elimination > 0)))
1686
        basic.append(rs)
1687

    
1688
        # VFO open
1689
        rs = RadioSetting("vfo_open", "VFO open",
1690
                          RadioSettingValueBoolean(bool(_mem.vfo_open > 0)))
1691
        basic.append(rs)
1692

    
1693
        # Beep control
1694
        rs = RadioSetting(
1695
                "beep_control",
1696
                "Beep control",
1697
                RadioSettingValueBoolean(bool(_mem.beep_control > 0)))
1698
        basic.append(rs)
1699

    
1700
        # Scan resume mode
1701
        tmpscanres = _mem.scan_resume_mode
1702
        if tmpscanres >= len(SCANRESUME_LIST):
1703
            tmpscanres = 0
1704
        rs = RadioSetting(
1705
                "scan_resume_mode",
1706
                "Scan resume mode",
1707
                RadioSettingValueList(
1708
                    SCANRESUME_LIST,
1709
                    SCANRESUME_LIST[tmpscanres]))
1710
        basic.append(rs)
1711

    
1712
        # Keypad locked
1713
        rs = RadioSetting(
1714
                "key_lock",
1715
                "Keypad lock",
1716
                RadioSettingValueBoolean(bool(_mem.key_lock > 0)))
1717
        basic.append(rs)
1718

    
1719
        # Auto keypad lock
1720
        rs = RadioSetting(
1721
                "auto_keypad_lock",
1722
                "Auto keypad lock",
1723
                RadioSettingValueBoolean(bool(_mem.auto_keypad_lock > 0)))
1724
        basic.append(rs)
1725

    
1726
        # Power on display mode
1727
        tmpdispmode = _mem.power_on_dispmode
1728
        if tmpdispmode >= len(WELCOME_LIST):
1729
            tmpdispmode = 0
1730
        rs = RadioSetting(
1731
                "welcome_mode",
1732
                "Power on display mode",
1733
                RadioSettingValueList(
1734
                    WELCOME_LIST,
1735
                    WELCOME_LIST[tmpdispmode]))
1736
        basic.append(rs)
1737

    
1738
        # Keypad Tone
1739
        tmpkeypadtone = _mem.keypad_tone
1740
        if tmpkeypadtone >= len(KEYPADTONE_LIST):
1741
            tmpkeypadtone = 0
1742
        rs = RadioSetting("keypad_tone", "Keypad tone", RadioSettingValueList(
1743
            KEYPADTONE_LIST, KEYPADTONE_LIST[tmpkeypadtone]))
1744
        basic.append(rs)
1745

    
1746
        # Language
1747
        tmplanguage = _mem.language
1748
        if tmplanguage >= len(LANGUAGE_LIST):
1749
            tmplanguage = 0
1750
        rs = RadioSetting("language", "Language", RadioSettingValueList(
1751
            LANGUAGE_LIST, LANGUAGE_LIST[tmplanguage]))
1752
        basic.append(rs)
1753

    
1754
        # Alarm mode
1755
        tmpalarmmode = _mem.alarm_mode
1756
        if tmpalarmmode >= len(ALARMMODE_LIST):
1757
            tmpalarmmode = 0
1758
        rs = RadioSetting("alarm_mode", "Alarm mode", RadioSettingValueList(
1759
            ALARMMODE_LIST, ALARMMODE_LIST[tmpalarmmode]))
1760
        basic.append(rs)
1761

    
1762
        # Reminding of end of talk
1763
        tmpalarmmode = _mem.reminding_of_end_talk
1764
        if tmpalarmmode >= len(REMENDOFTALK_LIST):
1765
            tmpalarmmode = 0
1766
        rs = RadioSetting(
1767
                "reminding_of_end_talk",
1768
                "Reminding of end of talk",
1769
                RadioSettingValueList(
1770
                    REMENDOFTALK_LIST,
1771
                    REMENDOFTALK_LIST[tmpalarmmode]))
1772
        basic.append(rs)
1773

    
1774
        # Repeater tail tone elimination
1775
        tmprte = _mem.repeater_tail_elimination
1776
        if tmprte >= len(RTE_LIST):
1777
            tmprte = 0
1778
        rs = RadioSetting(
1779
                "repeater_tail_elimination",
1780
                "Repeater tail tone elimination",
1781
                RadioSettingValueList(RTE_LIST, RTE_LIST[tmprte]))
1782
        basic.append(rs)
1783

    
1784
        # Logo string 1
1785
        logo1 = str(_mem.logo_line1).strip("\x20\x00\xff") + "\x00"
1786
        logo1 = _getstring(logo1.encode('ascii', errors='ignore'), 0, 12)
1787
        rs = RadioSetting("logo1", "Logo string 1 (12 characters)",
1788
                          RadioSettingValueString(0, 12, logo1))
1789
        basic.append(rs)
1790

    
1791
        # Logo string 2
1792
        logo2 = str(_mem.logo_line2).strip("\x20\x00\xff") + "\x00"
1793
        logo2 = _getstring(logo2.encode('ascii', errors='ignore'), 0, 12)
1794
        rs = RadioSetting("logo2", "Logo string 2 (12 characters)",
1795
                          RadioSettingValueString(0, 12, logo2))
1796
        basic.append(rs)
1797

    
1798
        # FM radio
1799
        for i in range(1, 21):
1800
            freqname = "FM_"+str(i)
1801
            fmfreq = _mem.fmfreq[i-1]/10.0
1802
            if fmfreq < FMMIN or fmfreq > FMMAX:
1803
                rs = RadioSetting(freqname, freqname,
1804
                                  RadioSettingValueString(0, 5, ""))
1805
            else:
1806
                rs = RadioSetting(freqname, freqname,
1807
                                  RadioSettingValueString(0, 5, str(fmfreq)))
1808

    
1809
            fmradio.append(rs)
1810

    
1811
        # unlock settings
1812

    
1813
        # F-LOCK
1814
        tmpflock = _mem.int_flock
1815
        if tmpflock >= len(FLOCK_LIST):
1816
            tmpflock = 0
1817
        rs = RadioSetting(
1818
            "flock", "F-LOCK",
1819
            RadioSettingValueList(FLOCK_LIST, FLOCK_LIST[tmpflock]))
1820
        unlock.append(rs)
1821

    
1822
        # 350TX
1823
        rs = RadioSetting("350tx", "350TX - unlock 350-400MHz TX",
1824
                          RadioSettingValueBoolean(
1825
                              bool(_mem.int_350tx > 0)))
1826
        unlock.append(rs)
1827

    
1828
        # 200TX
1829
        rs = RadioSetting("200tx", "200TX - unlock 174-350MHz TX",
1830
                          RadioSettingValueBoolean(
1831
                              bool(_mem.int_200tx > 0)))
1832
        unlock.append(rs)
1833

    
1834
        # 500TX
1835
        rs = RadioSetting("500tx", "500TX - unlock 500-600MHz TX",
1836
                          RadioSettingValueBoolean(
1837
                              bool(_mem.int_500tx > 0)))
1838
        unlock.append(rs)
1839

    
1840
        # 350EN
1841
        rs = RadioSetting("350en", "350EN - unlock 350-400MHz RX",
1842
                          RadioSettingValueBoolean(
1843
                              bool(_mem.int_350en > 0)))
1844
        unlock.append(rs)
1845

    
1846
        # SCREEN
1847
        rs = RadioSetting("scren", "SCREN - scrambler enable",
1848
                          RadioSettingValueBoolean(
1849
                              bool(_mem.int_scren > 0)))
1850
        unlock.append(rs)
1851

    
1852
        # readonly info
1853
        # Firmware
1854
        if self.FIRMWARE_VERSION == "":
1855
            firmware = "To get the firmware version please download"
1856
            "the image from the radio first"
1857
        else:
1858
            firmware = self.FIRMWARE_VERSION
1859

    
1860
        val = RadioSettingValueString(0, 128, firmware)
1861
        val.set_mutable(False)
1862
        rs = RadioSetting("fw_ver", "Firmware Version", val)
1863
        roinfo.append(rs)
1864

    
1865
        # TODO: remove showing the driver version when it's in mainline chirp
1866
        # Driver version
1867
        val = RadioSettingValueString(0, 128, DRIVER_VERSION)
1868
        val.set_mutable(False)
1869
        rs = RadioSetting("driver_ver", "Driver version", val)
1870
        roinfo.append(rs)
1871

    
1872
        # No limits version for hacked firmware
1873
        val = RadioSettingValueBoolean(self.FIRMWARE_NOLIMITS)
1874
        val.set_mutable(False)
1875
        rs = RadioSetting("nolimits", "Limits disabled for modified firmware",
1876
                          val)
1877
        roinfo.append(rs)
1878

    
1879
        return top
1880

    
1881
    # Store details about a high-level memory to the memory map
1882
    # This is called when a user edits a memory in the UI
1883
    def set_memory(self, mem):
1884
        number = mem.number-1
1885

    
1886
        # Get a low-level memory object mapped to the image
1887
        _mem = self._memobj.channel[number]
1888
        _mem4 = self._memobj
1889
        # empty memory
1890
        if mem.empty:
1891
            _mem.set_raw("\xFF" * 16)
1892
            if number < 200:
1893
                _mem2 = self._memobj.channelname[number]
1894
                _mem2.set_raw("\xFF" * 16)
1895
                _mem4.channel_attributes[number].is_scanlist1 = 0
1896
                _mem4.channel_attributes[number].is_scanlist2 = 0
1897
                _mem4.channel_attributes[number].unknown1 = 0
1898
                _mem4.channel_attributes[number].unknown2 = 0
1899
                _mem4.channel_attributes[number].is_free = 1
1900
                _mem4.channel_attributes[number].band = 0x7
1901
            return mem
1902

    
1903
        # clean the channel memory, restore some bits if it was used before
1904
        if _mem.get_raw()[0] == "\xff":
1905
            # this was an empty memory
1906
            _mem.set_raw("\x00" * 16)
1907
        else:
1908
            # this memory was't empty, save some bits that we don't know the
1909
            # meaning of, or that we don't support yet
1910
            prev_0a = _mem.get_raw(asbytes=True)[0x0a] & SAVE_MASK_0A
1911
            prev_0b = _mem.get_raw(asbytes=True)[0x0b] & SAVE_MASK_0B
1912
            prev_0c = _mem.get_raw(asbytes=True)[0x0c] & SAVE_MASK_0C
1913
            prev_0d = _mem.get_raw(asbytes=True)[0x0d] & SAVE_MASK_0D
1914
            prev_0e = _mem.get_raw(asbytes=True)[0x0e] & SAVE_MASK_0E
1915
            prev_0f = _mem.get_raw(asbytes=True)[0x0f] & SAVE_MASK_0F
1916
            _mem.set_raw("\x00" * 10 +
1917
                         chr(prev_0a) + chr(prev_0b) + chr(prev_0c) +
1918
                         chr(prev_0d) + chr(prev_0e) + chr(prev_0f))
1919

    
1920
        if number < 200:
1921
            _mem4.channel_attributes[number].is_scanlist1 = 0
1922
            _mem4.channel_attributes[number].is_scanlist2 = 0
1923
            _mem4.channel_attributes[number].unknown1 = 0
1924
            _mem4.channel_attributes[number].unknown2 = 0
1925
            _mem4.channel_attributes[number].is_free = 1
1926
            _mem4.channel_attributes[number].band = 0x7
1927

    
1928
        # find band
1929
        band = _find_band(self, mem.freq)
1930

    
1931
        # mode
1932
        if mem.mode == "NFM":
1933
            _mem.bandwidth = 1
1934
            _mem.enable_am = 0
1935
        elif mem.mode == "FM":
1936
            _mem.bandwidth = 0
1937
            _mem.enable_am = 0
1938
        elif mem.mode == "NAM":
1939
            _mem.bandwidth = 1
1940
            _mem.enable_am = 1
1941
        elif mem.mode == "AM":
1942
            _mem.bandwidth = 0
1943
            _mem.enable_am = 1
1944

    
1945
        # frequency/offset
1946
        _mem.freq = mem.freq/10
1947
        _mem.offset = mem.offset/10
1948

    
1949
        if mem.duplex == "off" or mem.duplex == "":
1950
            _mem.offset = 0
1951
            _mem.shift = 0
1952
        elif mem.duplex == '-':
1953
            _mem.shift = FLAGS1_OFFSET_MINUS
1954
        elif mem.duplex == '+':
1955
            _mem.shift = FLAGS1_OFFSET_PLUS
1956

    
1957
        # set band
1958
        if number < 200:
1959
            _mem4.channel_attributes[number].is_free = 0
1960
            _mem4.channel_attributes[number].band = band
1961

    
1962
        # channels >200 are the 14 VFO chanells and don't have names
1963
        if number < 200:
1964
            _mem2 = self._memobj.channelname[number]
1965
            tag = mem.name.ljust(10) + "\x00"*6
1966
            _mem2.name = tag  # Store the alpha tag
1967

    
1968
        # tone data
1969
        self._set_tone(mem, _mem)
1970

    
1971
        # step
1972
        _mem.step = STEPS.index(mem.tuning_step)
1973

    
1974
        # tx power
1975
        if str(mem.power) == str(UVK5_POWER_LEVELS[2]):
1976
            _mem.txpower = POWER_HIGH
1977
        elif str(mem.power) == str(UVK5_POWER_LEVELS[1]):
1978
            _mem.txpower = POWER_MEDIUM
1979
        else:
1980
            _mem.txpower = POWER_LOW
1981

    
1982
        for setting in mem.extra:
1983
            sname = setting.get_name()
1984
            svalue = setting.value.get_value()
1985

    
1986
            if sname == "bclo":
1987
                _mem.bclo = svalue and 1 or 0
1988

    
1989
            if sname == "pttid":
1990
                _mem.dtmf_pttid = PTTID_LIST.index(svalue)
1991

    
1992
            if sname == "frev":
1993
                _mem.freq_reverse = svalue and 1 or 0
1994

    
1995
            if sname == "dtmfdecode":
1996
                _mem.dtmf_decode = svalue and 1 or 0
1997

    
1998
            if sname == "scrambler":
1999
                _mem.scrambler = (
2000
                    _mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue)
2001

    
2002
            if number < 200 and sname == "scanlists":
2003
                if svalue == "1":
2004
                    _mem4.channel_attributes[number].is_scanlist1 = 1
2005
                    _mem4.channel_attributes[number].is_scanlist2 = 0
2006
                elif svalue == "2":
2007
                    _mem4.channel_attributes[number].is_scanlist1 = 0
2008
                    _mem4.channel_attributes[number].is_scanlist2 = 1
2009
                elif svalue == "1+2":
2010
                    _mem4.channel_attributes[number].is_scanlist1 = 1
2011
                    _mem4.channel_attributes[number].is_scanlist2 = 1
2012
                else:
2013
                    _mem4.channel_attributes[number].is_scanlist1 = 0
2014
                    _mem4.channel_attributes[number].is_scanlist2 = 0
2015

    
2016
        return mem
2017

    
2018

    
2019
@directory.register
2020
class UVK5Radio_nolimit(UVK5Radio):
2021
    VENDOR = "Quansheng"
2022
    MODEL = "UV-K5 (modified firmware)"
2023
    VARIANT = "nolimits"
2024
    FIRMWARE_NOLIMITS = True
2025

    
2026
    def get_features(self):
2027
        rf = UVK5Radio.get_features(self)
2028
        # This is what the BK4819 chip supports
2029
        rf.valid_bands = [(18000000,  620000000),
2030
                          (840000000, 1300000000)
2031
                          ]
2032
        return rf
(47-47/47)