Project

General

Profile

New Model #10478 » uvk5.py

uvk5.py chirp driver for UV-K5, version 20230626 - Jacek Lipkowski SQ5BPF, 06/26/2023 04: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
        return msgs
714

    
715
    def _set_tone(self, mem, _mem):
716
        ((txmode, txtone, txpol),
717
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
718

    
719
        if txmode == "Tone":
720
            txtoval = CTCSS_TONES.index(txtone)
721
            txmoval = 0b01
722
        elif txmode == "DTCS":
723
            txmoval = txpol == "R" and 0b11 or 0b10
724
            txtoval = DTCS_CODES.index(txtone)
725
        else:
726
            txmoval = 0
727
            txtoval = 0
728

    
729
        if rxmode == "Tone":
730
            rxtoval = CTCSS_TONES.index(rxtone)
731
            rxmoval = 0b01
732
        elif rxmode == "DTCS":
733
            rxmoval = rxpol == "R" and 0b11 or 0b10
734
            rxtoval = DTCS_CODES.index(rxtone)
735
        else:
736
            rxmoval = 0
737
            rxtoval = 0
738

    
739
        _mem.rxcodeflag = rxmoval
740
        _mem.txcodeflag = txmoval
741
        _mem.unknown1 = 0
742
        _mem.unknown2 = 0
743
        _mem.rxcode = rxtoval
744
        _mem.txcode = txtoval
745

    
746
    def _get_tone(self, mem, _mem):
747
        rxtype = _mem.rxcodeflag
748
        txtype = _mem.txcodeflag
749
        rx_tmode = TMODES[rxtype]
750
        tx_tmode = TMODES[txtype]
751

    
752
        rx_tone = tx_tone = None
753

    
754
        if tx_tmode == "Tone":
755
            if _mem.txcode < len(CTCSS_TONES):
756
                tx_tone = CTCSS_TONES[_mem.txcode]
757
            else:
758
                tx_tone = 0
759
                tx_tmode = ""
760
        elif tx_tmode == "DTCS":
761
            if _mem.txcode < len(DTCS_CODES):
762
                tx_tone = DTCS_CODES[_mem.txcode]
763
            else:
764
                tx_tone = 0
765
                tx_tmode = ""
766

    
767
        if rx_tmode == "Tone":
768
            if _mem.rxcode < len(CTCSS_TONES):
769
                rx_tone = CTCSS_TONES[_mem.rxcode]
770
            else:
771
                rx_tone = 0
772
                rx_tmode = ""
773
        elif rx_tmode == "DTCS":
774
            if _mem.rxcode < len(DTCS_CODES):
775
                rx_tone = DTCS_CODES[_mem.rxcode]
776
            else:
777
                rx_tone = 0
778
                rx_tmode = ""
779

    
780
        tx_pol = txtype == 0x03 and "R" or "N"
781
        rx_pol = rxtype == 0x03 and "R" or "N"
782

    
783
        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
784
                                       (rx_tmode, rx_tone, rx_pol))
785

    
786
    # Extract a high-level memory object from the low-level memory map
787
    # This is called to populate a memory in the UI
788
    def get_memory(self, number2):
789

    
790
        mem = chirp_common.Memory()
791

    
792
        if isinstance(number2, str):
793
            number = SPECIALS[number2]
794
            mem.extd_number = number2
795
        else:
796
            number = number2 - 1
797

    
798
        mem.number = number + 1
799

    
800
        _mem = self._memobj.channel[number]
801

    
802
        tmpcomment = ""
803

    
804
        is_empty = False
805
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
806
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
807
            is_empty = True
808

    
809
        tmpscn = SCANLIST_LIST[0]
810

    
811
        # We'll also look at the channel attributes if a memory has them
812
        if number < 200:
813
            _mem3 = self._memobj.channel_attributes[number]
814
            # free memory bit
815
            if _mem3.is_free > 0:
816
                is_empty = True
817
            # scanlists
818
            if _mem3.is_scanlist1 > 0 and _mem3.is_scanlist2 > 0:
819
                tmpscn = SCANLIST_LIST[3]  # "1+2"
820
            elif _mem3.is_scanlist1 > 0:
821
                tmpscn = SCANLIST_LIST[1]  # "1"
822
            elif _mem3.is_scanlist2 > 0:
823
                tmpscn = SCANLIST_LIST[2]  # "2"
824

    
825
        if is_empty:
826
            mem.empty = True
827
            # set some sane defaults:
828
            mem.power = UVK5_POWER_LEVELS[2]
829
            mem.extra = RadioSettingGroup("Extra", "extra")
830
            rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(False))
831
            mem.extra.append(rs)
832
            rs = RadioSetting("frev", "FreqRev",
833
                              RadioSettingValueBoolean(False))
834
            mem.extra.append(rs)
835
            rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
836
                PTTID_LIST, PTTID_LIST[0]))
837
            mem.extra.append(rs)
838
            rs = RadioSetting("dtmfdecode", "DTMF decode",
839
                              RadioSettingValueBoolean(False))
840
            mem.extra.append(rs)
841
            rs = RadioSetting("scrambler", "Scrambler", RadioSettingValueList(
842
                SCRAMBLER_LIST, SCRAMBLER_LIST[0]))
843
            mem.extra.append(rs)
844

    
845
            rs = RadioSetting("scanlists", "Scanlists", RadioSettingValueList(
846
                SCANLIST_LIST, SCANLIST_LIST[0]))
847
            mem.extra.append(rs)
848

    
849
            # actually the step and duplex are overwritten by chirp based on
850
            # bandplan. they are here to document sane defaults for IARU r1
851
            # mem.tuning_step = 25.0
852
            # mem.duplex = "off"
853

    
854
            return mem
855

    
856
        if number > 199:
857
            mem.name = VFO_CHANNEL_NAMES[number-200]
858
            mem.immutable = ["name", "scanlists"]
859
        else:
860
            _mem2 = self._memobj.channelname[number]
861
            for char in _mem2.name:
862
                if str(char) == "\xFF" or str(char) == "\x00":
863
                    break
864
                mem.name += str(char)
865
            mem.name = mem.name.rstrip()
866

    
867
        # Convert your low-level frequency to Hertz
868
        mem.freq = int(_mem.freq)*10
869
        mem.offset = int(_mem.offset)*10
870

    
871
        if (mem.offset == 0):
872
            mem.duplex = ''
873
        else:
874
            if _mem.shift == FLAGS1_OFFSET_MINUS:
875
                mem.duplex = '-'
876
            elif _mem.shift == FLAGS1_OFFSET_PLUS:
877
                mem.duplex = '+'
878
            else:
879
                mem.duplex = ''
880

    
881
        # tone data
882
        self._get_tone(mem, _mem)
883

    
884
        # mode
885
        if _mem.enable_am > 0:
886
            if _mem.bandwidth > 0:
887
                mem.mode = "NAM"
888
            else:
889
                mem.mode = "AM"
890
        else:
891
            if _mem.bandwidth > 0:
892
                mem.mode = "NFM"
893
            else:
894
                mem.mode = "FM"
895

    
896
        # tuning step
897
        tstep = _mem.step & 0x7
898
        if tstep < len(STEPS):
899
            mem.tuning_step = STEPS[tstep]
900
        else:
901
            mem.tuning_step = 2.5
902

    
903
        # power
904
        if _mem.txpower == POWER_HIGH:
905
            mem.power = UVK5_POWER_LEVELS[2]
906
        elif _mem.txpower == POWER_MEDIUM:
907
            mem.power = UVK5_POWER_LEVELS[1]
908
        else:
909
            mem.power = UVK5_POWER_LEVELS[0]
910

    
911
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
912
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
913
            mem.empty = True
914
        else:
915
            mem.empty = False
916

    
917
        mem.extra = RadioSettingGroup("Extra", "extra")
918

    
919
        # BCLO
920
        is_bclo = bool(_mem.bclo > 0)
921
        rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(is_bclo))
922
        mem.extra.append(rs)
923
        tmpcomment += "BCLO:"+(is_bclo and "ON" or "off")+" "
924

    
925
        # Frequency reverse - whatever that means, don't see it in the manual
926
        is_frev = bool(_mem.freq_reverse > 0)
927
        rs = RadioSetting("frev", "FreqRev", RadioSettingValueBoolean(is_frev))
928
        mem.extra.append(rs)
929
        tmpcomment += "FreqReverse:"+(is_frev and "ON" or "off")+" "
930

    
931
        # PTTID
932
        pttid = _mem.dtmf_pttid
933
        rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
934
            PTTID_LIST, PTTID_LIST[pttid]))
935
        mem.extra.append(rs)
936
        tmpcomment += "PTTid:"+PTTID_LIST[pttid]+" "
937

    
938
        # DTMF DECODE
939
        is_dtmf = bool(_mem.dtmf_decode > 0)
940
        rs = RadioSetting("dtmfdecode", "DTMF decode",
941
                          RadioSettingValueBoolean(is_dtmf))
942
        mem.extra.append(rs)
943
        tmpcomment += "DTMFdecode:"+(is_dtmf and "ON" or "off")+" "
944

    
945
        # Scrambler
946
        if _mem.scrambler & 0x0f < len(SCRAMBLER_LIST):
947
            enc = _mem.scrambler & 0x0f
948
        else:
949
            enc = 0
950

    
951
        rs = RadioSetting("scrambler", "Scrambler", RadioSettingValueList(
952
            SCRAMBLER_LIST, SCRAMBLER_LIST[enc]))
953
        mem.extra.append(rs)
954
        tmpcomment += "Scrambler:"+SCRAMBLER_LIST[enc]+" "
955

    
956
        rs = RadioSetting("scanlists", "Scanlists", RadioSettingValueList(
957
            SCANLIST_LIST, tmpscn))
958
        mem.extra.append(rs)
959

    
960
        return mem
961

    
962
    def set_settings(self, settings):
963
        _mem = self._memobj
964
        for element in settings:
965
            if not isinstance(element, RadioSetting):
966
                self.set_settings(element)
967
                continue
968

    
969
            # basic settings
970

    
971
            # call channel
972
            if element.get_name() == "call_channel":
973
                _mem.call_channel = int(element.value)-1
974

    
975
            # squelch
976
            if element.get_name() == "squelch":
977
                _mem.squelch = int(element.value)
978
            # TOT
979
            if element.get_name() == "tot":
980
                _mem.max_talk_time = int(element.value)
981

    
982
            # NOAA autoscan
983
            if element.get_name() == "noaa_autoscan":
984
                _mem.noaa_autoscan = element.value and 1 or 0
985

    
986
            # VOX switch
987
            if element.get_name() == "vox_switch":
988
                _mem.vox_switch = element.value and 1 or 0
989

    
990
            # vox level
991
            if element.get_name() == "vox_level":
992
                _mem.vox_level = int(element.value)-1
993

    
994
            # mic gain
995
            if element.get_name() == "mic_gain":
996
                _mem.mic_gain = int(element.value)
997

    
998
            # Channel display mode
999
            if element.get_name() == "channel_display_mode":
1000
                _mem.channel_display_mode = CHANNELDISP_LIST.index(
1001
                    str(element.value))
1002

    
1003
            # Crossband receiving/transmitting
1004
            if element.get_name() == "crossband":
1005
                _mem.crossband = CROSSBAND_LIST.index(str(element.value))
1006

    
1007
            # Battery Save
1008
            if element.get_name() == "battery_save":
1009
                _mem.battery_save = BATSAVE_LIST.index(str(element.value))
1010
            # Dual Watch
1011
            if element.get_name() == "dualwatch":
1012
                _mem.dual_watch = DUALWATCH_LIST.index(str(element.value))
1013

    
1014
            # Backlight auto mode
1015
            if element.get_name() == "backlight_auto_mode":
1016
                _mem.backlight_auto_mode = \
1017
                        BACKLIGHT_LIST.index(str(element.value))
1018

    
1019
            # Tail tone elimination
1020
            if element.get_name() == "tail_note_elimination":
1021
                _mem.tail_note_elimination = element.value and 1 or 0
1022

    
1023
            # VFO Open
1024
            if element.get_name() == "vfo_open":
1025
                _mem.vfo_open = element.value and 1 or 0
1026

    
1027
            # Beep control
1028
            if element.get_name() == "beep_control":
1029
                _mem.beep_control = element.value and 1 or 0
1030

    
1031
            # Scan resume mode
1032
            if element.get_name() == "scan_resume_mode":
1033
                _mem.scan_resume_mode = SCANRESUME_LIST.index(
1034
                    str(element.value))
1035

    
1036
            # Keypad lock
1037
            if element.get_name() == "key_lock":
1038
                _mem.key_lock = element.value and 1 or 0
1039

    
1040
            # Auto keypad lock
1041
            if element.get_name() == "auto_keypad_lock":
1042
                _mem.auto_keypad_lock = element.value and 1 or 0
1043

    
1044
            # Power on display mode
1045
            if element.get_name() == "welcome_mode":
1046
                _mem.power_on_dispmode = WELCOME_LIST.index(str(element.value))
1047

    
1048
            # Keypad Tone
1049
            if element.get_name() == "keypad_tone":
1050
                _mem.keypad_tone = KEYPADTONE_LIST.index(str(element.value))
1051

    
1052
            # Language
1053
            if element.get_name() == "language":
1054
                _mem.language = LANGUAGE_LIST.index(str(element.value))
1055

    
1056
            # Alarm mode
1057
            if element.get_name() == "alarm_mode":
1058
                _mem.alarm_mode = ALARMMODE_LIST.index(str(element.value))
1059

    
1060
            # Reminding of end of talk
1061
            if element.get_name() == "reminding_of_end_talk":
1062
                _mem.reminding_of_end_talk = REMENDOFTALK_LIST.index(
1063
                    str(element.value))
1064

    
1065
            # Repeater tail tone elimination
1066
            if element.get_name() == "repeater_tail_elimination":
1067
                _mem.repeater_tail_elimination = RTE_LIST.index(
1068
                    str(element.value))
1069

    
1070
            # Logo string 1
1071
            if element.get_name() == "logo1":
1072
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
1073
                _mem.logo_line1 = b[0:12]+"\xff\xff\xff\xff"
1074

    
1075
            # Logo string 2
1076
            if element.get_name() == "logo2":
1077
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
1078
                _mem.logo_line2 = b[0:12]+"\xff\xff\xff\xff"
1079

    
1080
            # unlock settings
1081

    
1082
            # FLOCK
1083
            if element.get_name() == "flock":
1084
                _mem.int_flock = FLOCK_LIST.index(str(element.value))
1085

    
1086
            # 350TX
1087
            if element.get_name() == "350tx":
1088
                _mem.int_350tx = element.value and 1 or 0
1089

    
1090
            # 200TX
1091
            if element.get_name() == "200tx":
1092
                _mem.int_200tx = element.value and 1 or 0
1093

    
1094
            # 500TX
1095
            if element.get_name() == "500tx":
1096
                _mem.int_500tx = element.value and 1 or 0
1097

    
1098
            # 350EN
1099
            if element.get_name() == "350en":
1100
                _mem.int_350en = element.value and 1 or 0
1101

    
1102
            # SCREN
1103
            if element.get_name() == "scren":
1104
                _mem.int_scren = element.value and 1 or 0
1105

    
1106
            # fm radio
1107
            for i in range(1, 21):
1108
                freqname = "FM_" + str(i)
1109
                if element.get_name() == freqname:
1110
                    val = str(element.value).strip()
1111
                    try:
1112
                        val2 = int(float(val)*10)
1113
                    except Exception:
1114
                        val2 = 0xffff
1115

    
1116
                    if val2 < FMMIN*10 or val2 > FMMAX*10:
1117
                        val2 = 0xffff
1118
#                        raise errors.InvalidValueError(
1119
#                                "FM radio frequency should be a value "
1120
#                                "in the range %.1f - %.1f" % (FMMIN , FMMAX))
1121
                    _mem.fmfreq[i-1] = val2
1122

    
1123
            # dtmf settings
1124
            if element.get_name() == "dtmf_side_tone":
1125
                _mem.dtmf_settings.side_tone = \
1126
                        element.value and 1 or 0
1127

    
1128
            if element.get_name() == "dtmf_separate_code":
1129
                _mem.dtmf_settings.separate_code = str(element.value)
1130

    
1131
            if element.get_name() == "dtmf_group_call_code":
1132
                _mem.dtmf_settings.group_call_code = element.value
1133

    
1134
            if element.get_name() == "dtmf_decode_response":
1135
                _mem.dtmf_settings.decode_response = \
1136
                        DTMF_DECODE_RESPONSE_LIST.index(str(element.value))
1137

    
1138
            if element.get_name() == "dtmf_auto_reset_time":
1139
                _mem.dtmf_settings.auto_reset_time = \
1140
                        int(int(element.value)/10)
1141

    
1142
            if element.get_name() == "dtmf_preload_time":
1143
                _mem.dtmf_settings.preload_time = \
1144
                        int(int(element.value)/10)
1145

    
1146
            if element.get_name() == "dtmf_first_code_persist_time":
1147
                _mem.dtmf_settings.first_code_persist_time = \
1148
                        int(int(element.value)/10)
1149

    
1150
            if element.get_name() == "dtmf_hash_persist_time":
1151
                _mem.dtmf_settings.hash_persist_time = \
1152
                        int(int(element.value)/10)
1153

    
1154
            if element.get_name() == "dtmf_code_persist_time":
1155
                _mem.dtmf_settings.code_persist_time = \
1156
                        int(int(element.value)/10)
1157

    
1158
            if element.get_name() == "dtmf_code_interval_time":
1159
                _mem.dtmf_settings.code_interval_time = \
1160
                        int(int(element.value)/10)
1161

    
1162
            if element.get_name() == "dtmf_permit_remote_kill":
1163
                _mem.dtmf_settings.permit_remote_kill = \
1164
                        element.value and 1 or 0
1165

    
1166
            if element.get_name() == "dtmf_dtmf_local_code":
1167
                k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*3
1168
                _mem.dtmf_settings_numbers.dtmf_local_code = k[0:3]
1169

    
1170
            if element.get_name() == "dtmf_dtmf_up_code":
1171
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*16
1172
                _mem.dtmf_settings_numbers.dtmf_up_code = k[0:16]
1173

    
1174
            if element.get_name() == "dtmf_dtmf_down_code":
1175
                k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*16
1176
                _mem.dtmf_settings_numbers.dtmf_down_code = k[0:16]
1177

    
1178
            if element.get_name() == "dtmf_kill_code":
1179
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*5
1180
                _mem.dtmf_settings_numbers.kill_code = k[0:5]
1181

    
1182
            if element.get_name() == "dtmf_revive_code":
1183
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*5
1184
                _mem.dtmf_settings_numbers.revive_code = k[0:5]
1185

    
1186
            # dtmf contacts
1187
            for i in range(1, 17):
1188
                varname = "DTMF_" + str(i)
1189
                if element.get_name() == varname:
1190
                    k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*8
1191
                    _mem.dtmfcontact[i-1].name = k[0:8]
1192

    
1193
                varnumname = "DTMFNUM_" + str(i)
1194
                if element.get_name() == varnumname:
1195
                    k = str(element.value).rstrip("\x20\xff\x00") + "\xff"*3
1196
                    _mem.dtmfcontact[i-1].number = k[0:3]
1197

    
1198
            # scanlist stuff
1199
            if element.get_name() == "scanlist_default":
1200
                val = (int(element.value) == 2) and 1 or 0
1201
                _mem.scanlist_default = val
1202

    
1203
            if element.get_name() == "scanlist1_priority_scan":
1204
                _mem.scanlist1_priority_scan = \
1205
                        element.value and 1 or 0
1206

    
1207
            if element.get_name() == "scanlist2_priority_scan":
1208
                _mem.scanlist2_priority_scan = \
1209
                        element.value and 1 or 0
1210

    
1211
            if element.get_name() == "scanlist1_priority_ch1" or \
1212
                    element.get_name() == "scanlist1_priority_ch2" or \
1213
                    element.get_name() == "scanlist2_priority_ch1" or \
1214
                    element.get_name() == "scanlist2_priority_ch2":
1215

    
1216
                val = int(element.value)
1217

    
1218
                if val > 200 or val < 1:
1219
                    val = 0xff
1220
                else:
1221
                    val -= 1
1222

    
1223
                if element.get_name() == "scanlist1_priority_ch1":
1224
                    _mem.scanlist1_priority_ch1 = val
1225
                if element.get_name() == "scanlist1_priority_ch2":
1226
                    _mem.scanlist1_priority_ch2 = val
1227
                if element.get_name() == "scanlist2_priority_ch1":
1228
                    _mem.scanlist2_priority_ch1 = val
1229
                if element.get_name() == "scanlist2_priority_ch2":
1230
                    _mem.scanlist2_priority_ch2 = val
1231

    
1232
            if element.get_name() == "key1_shortpress_action":
1233
                _mem.key1_shortpress_action = KEYACTIONS_LIST.index(
1234
                        str(element.value))
1235

    
1236
            if element.get_name() == "key1_longpress_action":
1237
                _mem.key1_longpress_action = KEYACTIONS_LIST.index(
1238
                        str(element.value))
1239

    
1240
            if element.get_name() == "key2_shortpress_action":
1241
                _mem.key2_shortpress_action = KEYACTIONS_LIST.index(
1242
                        str(element.value))
1243

    
1244
            if element.get_name() == "key2_longpress_action":
1245
                _mem.key2_longpress_action = KEYACTIONS_LIST.index(
1246
                        str(element.value))
1247

    
1248
    def get_settings(self):
1249
        _mem = self._memobj
1250
        basic = RadioSettingGroup("basic", "Basic Settings")
1251
        keya = RadioSettingGroup("keya", "Programmable keys")
1252
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1253
        dtmfc = RadioSettingGroup("dtmfc", "DTMF Contacts")
1254
        scanl = RadioSettingGroup("scn", "Scan Lists")
1255
        unlock = RadioSettingGroup("unlock", "Unlock Settings")
1256
        fmradio = RadioSettingGroup("fmradio", "FM Radio")
1257

    
1258
        roinfo = RadioSettingGroup("roinfo", "Driver information")
1259

    
1260
        top = RadioSettings(
1261
                basic, keya, dtmf, dtmfc, scanl, unlock, fmradio, roinfo)
1262

    
1263
        # Programmable keys
1264
        tmpval = int(_mem.key1_shortpress_action)
1265
        if tmpval >= len(KEYACTIONS_LIST):
1266
            tmpval = 0
1267
        rs = RadioSetting("key1_shortpress_action", "Side key 1 short press",
1268
                          RadioSettingValueList(
1269
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1270
        keya.append(rs)
1271

    
1272
        tmpval = int(_mem.key1_longpress_action)
1273
        if tmpval >= len(KEYACTIONS_LIST):
1274
            tmpval = 0
1275
        rs = RadioSetting("key1_longpress_action", "Side key 1 long press",
1276
                          RadioSettingValueList(
1277
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1278
        keya.append(rs)
1279

    
1280
        tmpval = int(_mem.key2_shortpress_action)
1281
        if tmpval >= len(KEYACTIONS_LIST):
1282
            tmpval = 0
1283
        rs = RadioSetting("key2_shortpress_action", "Side key 2 short press",
1284
                          RadioSettingValueList(
1285
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1286
        keya.append(rs)
1287

    
1288
        tmpval = int(_mem.key2_longpress_action)
1289
        if tmpval >= len(KEYACTIONS_LIST):
1290
            tmpval = 0
1291
        rs = RadioSetting("key2_longpress_action", "Side key 2 long press",
1292
                          RadioSettingValueList(
1293
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1294
        keya.append(rs)
1295

    
1296
        # DTMF settings
1297
        tmppr = bool(_mem.dtmf_settings.side_tone > 0)
1298
        rs = RadioSetting(
1299
                "dtmf_side_tone",
1300
                "DTMF Sidetone",
1301
                RadioSettingValueBoolean(tmppr))
1302
        dtmf.append(rs)
1303

    
1304
        tmpval = str(_mem.dtmf_settings.separate_code)
1305
        if tmpval not in DTMF_CODE_CHARS:
1306
            tmpval = '*'
1307
        val = RadioSettingValueString(1, 1, tmpval)
1308
        val.set_charset(DTMF_CODE_CHARS)
1309
        rs = RadioSetting("dtmf_separate_code", "Separate Code", val)
1310
        dtmf.append(rs)
1311

    
1312
        tmpval = str(_mem.dtmf_settings.group_call_code)
1313
        if tmpval not in DTMF_CODE_CHARS:
1314
            tmpval = '#'
1315
        val = RadioSettingValueString(1, 1, tmpval)
1316
        val.set_charset(DTMF_CODE_CHARS)
1317
        rs = RadioSetting("dtmf_group_call_code", "Group Call Code", val)
1318
        dtmf.append(rs)
1319

    
1320
        tmpval = _mem.dtmf_settings.decode_response
1321
        if tmpval >= len(DTMF_DECODE_RESPONSE_LIST):
1322
            tmpval = 0
1323
        rs = RadioSetting("dtmf_decode_response", "Decode Response",
1324
                          RadioSettingValueList(
1325
                              DTMF_DECODE_RESPONSE_LIST,
1326
                              DTMF_DECODE_RESPONSE_LIST[tmpval]))
1327
        dtmf.append(rs)
1328

    
1329
        tmpval = _mem.dtmf_settings.auto_reset_time
1330
        if tmpval > 60 or tmpval < 5:
1331
            tmpval = 5
1332
        rs = RadioSetting("dtmf_auto_reset_time",
1333
                          "Auto reset time (s)",
1334
                          RadioSettingValueInteger(5, 60, tmpval))
1335
        dtmf.append(rs)
1336

    
1337
        tmpval = int(_mem.dtmf_settings.preload_time)
1338
        if tmpval > 100 or tmpval < 3:
1339
            tmpval = 30
1340
        tmpval *= 10
1341
        rs = RadioSetting("dtmf_preload_time",
1342
                          "Pre-load time (ms)",
1343
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1344
        dtmf.append(rs)
1345

    
1346
        tmpval = int(_mem.dtmf_settings.first_code_persist_time)
1347
        if tmpval > 100 or tmpval < 3:
1348
            tmpval = 30
1349
        tmpval *= 10
1350
        rs = RadioSetting("dtmf_first_code_persist_time",
1351
                          "First code persist time (ms)",
1352
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1353
        dtmf.append(rs)
1354

    
1355
        tmpval = int(_mem.dtmf_settings.hash_persist_time)
1356
        if tmpval > 100 or tmpval < 3:
1357
            tmpval = 30
1358
        tmpval *= 10
1359
        rs = RadioSetting("dtmf_hash_persist_time",
1360
                          "#/* persist time (ms)",
1361
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1362
        dtmf.append(rs)
1363

    
1364
        tmpval = int(_mem.dtmf_settings.code_persist_time)
1365
        if tmpval > 100 or tmpval < 3:
1366
            tmpval = 30
1367
        tmpval *= 10
1368
        rs = RadioSetting("dtmf_code_persist_time",
1369
                          "Code persist time (ms)",
1370
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1371
        dtmf.append(rs)
1372

    
1373
        tmpval = int(_mem.dtmf_settings.code_interval_time)
1374
        if tmpval > 100 or tmpval < 3:
1375
            tmpval = 30
1376
        tmpval *= 10
1377
        rs = RadioSetting("dtmf_code_interval_time",
1378
                          "Code interval time (ms)",
1379
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1380
        dtmf.append(rs)
1381

    
1382
        tmpval = bool(_mem.dtmf_settings.permit_remote_kill > 0)
1383
        rs = RadioSetting(
1384
                "dtmf_permit_remote_kill",
1385
                "Permit remote kill",
1386
                RadioSettingValueBoolean(tmpval))
1387
        dtmf.append(rs)
1388

    
1389
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_local_code).upper().strip(
1390
                "\x00\xff\x20")
1391
        for i in tmpval:
1392
            if i in DTMF_CHARS_ID:
1393
                continue
1394
            else:
1395
                tmpval = "103"
1396
                break
1397
        val = RadioSettingValueString(3, 3, tmpval)
1398
        val.set_charset(DTMF_CHARS_ID)
1399
        rs = RadioSetting("dtmf_dtmf_local_code",
1400
                          "Local code (3 chars 0-9 ABCD)", val)
1401
        dtmf.append(rs)
1402

    
1403
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_up_code).upper().strip(
1404
                "\x00\xff\x20")
1405
        for i in tmpval:
1406
            if i in DTMF_CHARS_UPDOWN or i == "":
1407
                continue
1408
            else:
1409
                tmpval = "123"
1410
                break
1411
        val = RadioSettingValueString(1, 16, tmpval)
1412
        val.set_charset(DTMF_CHARS_UPDOWN)
1413
        rs = RadioSetting("dtmf_dtmf_up_code",
1414
                          "Up code (1-16 chars 0-9 ABCD*#)", val)
1415
        dtmf.append(rs)
1416

    
1417
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_down_code).upper().strip(
1418
                "\x00\xff\x20")
1419
        for i in tmpval:
1420
            if i in DTMF_CHARS_UPDOWN:
1421
                continue
1422
            else:
1423
                tmpval = "456"
1424
                break
1425
        val = RadioSettingValueString(1, 16, tmpval)
1426
        val.set_charset(DTMF_CHARS_UPDOWN)
1427
        rs = RadioSetting("dtmf_dtmf_down_code",
1428
                          "Down code (1-16 chars 0-9 ABCD*#)", val)
1429
        dtmf.append(rs)
1430

    
1431
        tmpval = str(_mem.dtmf_settings_numbers.kill_code).upper().strip(
1432
                "\x00\xff\x20")
1433
        for i in tmpval:
1434
            if i in DTMF_CHARS_KILL:
1435
                continue
1436
            else:
1437
                tmpval = "77777"
1438
                break
1439
        if not len(tmpval) == 5:
1440
            tmpval = "77777"
1441
        val = RadioSettingValueString(5, 5, tmpval)
1442
        val.set_charset(DTMF_CHARS_KILL)
1443
        rs = RadioSetting("dtmf_kill_code",
1444
                          "Kill code (5 chars 0-9 ABCD)", val)
1445
        dtmf.append(rs)
1446

    
1447
        tmpval = str(_mem.dtmf_settings_numbers.revive_code).upper().strip(
1448
                "\x00\xff\x20")
1449
        for i in tmpval:
1450
            if i in DTMF_CHARS_KILL:
1451
                continue
1452
            else:
1453
                tmpval = "88888"
1454
                break
1455
        if not len(tmpval) == 5:
1456
            tmpval = "88888"
1457
        val = RadioSettingValueString(5, 5, tmpval)
1458
        val.set_charset(DTMF_CHARS_KILL)
1459
        rs = RadioSetting("dtmf_revive_code",
1460
                          "Revive code (5 chars 0-9 ABCD)", val)
1461
        dtmf.append(rs)
1462

    
1463
        val = RadioSettingValueString(0, 80,
1464
                                      "All DTMF Contacts are 3 codes "
1465
                                      "(valid: 0-9 * # ABCD), "
1466
                                      "or an empty string")
1467
        val.set_mutable(False)
1468
        rs = RadioSetting("dtmf_descr1", "DTMF Contacts", val)
1469
        dtmfc.append(rs)
1470

    
1471
        for i in range(1, 17):
1472
            varname = "DTMF_"+str(i)
1473
            varnumname = "DTMFNUM_"+str(i)
1474
            vardescr = "DTMF Contact "+str(i)+" name"
1475
            varinumdescr = "DTMF Contact "+str(i)+" number"
1476

    
1477
            cntn = str(_mem.dtmfcontact[i-1].name).strip("\x20\x00\xff")
1478
            cntnum = str(_mem.dtmfcontact[i-1].number).strip("\x20\x00\xff")
1479

    
1480
            val = RadioSettingValueString(0, 8, cntn)
1481
            rs = RadioSetting(varname, vardescr, val)
1482
            dtmfc.append(rs)
1483

    
1484
            val = RadioSettingValueString(0, 3, cntnum)
1485
            val.set_charset(DTMF_CHARS)
1486
            rs = RadioSetting(varnumname, varinumdescr, val)
1487
            dtmfc.append(rs)
1488

    
1489
        # scanlists
1490
        if _mem.scanlist_default == 1:
1491
            tmpsc = 2
1492
        else:
1493
            tmpsc = 1
1494
        rs = RadioSetting("scanlist_default",
1495
                          "Default scanlist",
1496
                          RadioSettingValueInteger(1, 2, tmpsc))
1497
        scanl.append(rs)
1498

    
1499
        tmppr = bool((_mem.scanlist1_priority_scan & 1) > 0)
1500
        rs = RadioSetting(
1501
                "scanlist1_priority_scan",
1502
                "Scanlist 1 priority channel scan",
1503
                RadioSettingValueBoolean(tmppr))
1504
        scanl.append(rs)
1505

    
1506
        tmpch = _mem.scanlist1_priority_ch1 + 1
1507
        if tmpch > 200:
1508
            tmpch = 0
1509
        rs = RadioSetting("scanlist1_priority_ch1",
1510
                          "Scanlist 1 priority channel 1 (0 - off)",
1511
                          RadioSettingValueInteger(0, 200, tmpch))
1512
        scanl.append(rs)
1513

    
1514
        tmpch = _mem.scanlist1_priority_ch2 + 1
1515
        if tmpch > 200:
1516
            tmpch = 0
1517
        rs = RadioSetting("scanlist1_priority_ch2",
1518
                          "Scanlist 1 priority channel 2 (0 - off)",
1519
                          RadioSettingValueInteger(0, 200, tmpch))
1520
        scanl.append(rs)
1521

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

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

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

    
1545
        # basic settings
1546

    
1547
        # call channel
1548
        tmpc = _mem.call_channel+1
1549
        if tmpc > 200:
1550
            tmpc = 1
1551
        rs = RadioSetting("call_channel", "One key call channel",
1552
                          RadioSettingValueInteger(1, 200, tmpc))
1553
        basic.append(rs)
1554

    
1555
        # squelch
1556
        tmpsq = _mem.squelch
1557
        if tmpsq > 9:
1558
            tmpsq = 1
1559
        rs = RadioSetting("squelch", "Squelch",
1560
                          RadioSettingValueInteger(0, 9, tmpsq))
1561
        basic.append(rs)
1562

    
1563
        # TOT
1564
        tmptot = _mem.max_talk_time
1565
        if tmptot > 10:
1566
            tmptot = 10
1567
        rs = RadioSetting(
1568
                "tot",
1569
                "Max talk time [min]",
1570
                RadioSettingValueInteger(0, 10, tmptot))
1571
        basic.append(rs)
1572

    
1573
        # NOAA autoscan
1574
        rs = RadioSetting(
1575
                "noaa_autoscan",
1576
                "NOAA Autoscan", RadioSettingValueBoolean(
1577
                    bool(_mem.noaa_autoscan > 0)))
1578
        basic.append(rs)
1579

    
1580
        # VOX switch
1581
        rs = RadioSetting(
1582
                "vox_switch",
1583
                "VOX enabled", RadioSettingValueBoolean(
1584
                    bool(_mem.vox_switch > 0)))
1585
        basic.append(rs)
1586

    
1587
        # VOX Level
1588
        tmpvox = _mem.vox_level+1
1589
        if tmpvox > 10:
1590
            tmpvox = 10
1591
        rs = RadioSetting("vox_level", "VOX Level",
1592
                          RadioSettingValueInteger(1, 10, tmpvox))
1593
        basic.append(rs)
1594

    
1595
        # Mic gain
1596
        tmpmicgain = _mem.mic_gain
1597
        if tmpmicgain > 4:
1598
            tmpmicgain = 4
1599
        rs = RadioSetting("mic_gain", "Mic Gain",
1600
                          RadioSettingValueInteger(0, 4, tmpmicgain))
1601
        basic.append(rs)
1602

    
1603
        # Channel display mode
1604
        tmpchdispmode = _mem.channel_display_mode
1605
        if tmpchdispmode >= len(CHANNELDISP_LIST):
1606
            tmpchdispmode = 0
1607
        rs = RadioSetting(
1608
                "channel_display_mode",
1609
                "Channel display mode",
1610
                RadioSettingValueList(
1611
                    CHANNELDISP_LIST,
1612
                    CHANNELDISP_LIST[tmpchdispmode]))
1613
        basic.append(rs)
1614

    
1615
        # Crossband receiving/transmitting
1616
        tmpcross = _mem.crossband
1617
        if tmpcross >= len(CROSSBAND_LIST):
1618
            tmpcross = 0
1619
        rs = RadioSetting(
1620
                "crossband",
1621
                "Cross-band receiving/transmitting",
1622
                RadioSettingValueList(
1623
                    CROSSBAND_LIST,
1624
                    CROSSBAND_LIST[tmpcross]))
1625
        basic.append(rs)
1626

    
1627
        # Battery save
1628
        tmpbatsave = _mem.battery_save
1629
        if tmpbatsave >= len(BATSAVE_LIST):
1630
            tmpbatsave = BATSAVE_LIST.index("1:4")
1631
        rs = RadioSetting(
1632
                "battery_save",
1633
                "Battery Save",
1634
                RadioSettingValueList(
1635
                    BATSAVE_LIST,
1636
                    BATSAVE_LIST[tmpbatsave]))
1637
        basic.append(rs)
1638

    
1639
        # Dual watch
1640
        tmpdual = _mem.dual_watch
1641
        if tmpdual >= len(DUALWATCH_LIST):
1642
            tmpdual = 0
1643
        rs = RadioSetting("dualwatch", "Dual Watch", RadioSettingValueList(
1644
            DUALWATCH_LIST, DUALWATCH_LIST[tmpdual]))
1645
        basic.append(rs)
1646

    
1647
        # Backlight auto mode
1648
        tmpback = _mem.backlight_auto_mode
1649
        if tmpback >= len(BACKLIGHT_LIST):
1650
            tmpback = 0
1651
        rs = RadioSetting("backlight_auto_mode",
1652
                          "Backlight auto mode",
1653
                          RadioSettingValueList(
1654
                              BACKLIGHT_LIST,
1655
                              BACKLIGHT_LIST[tmpback]))
1656
        basic.append(rs)
1657

    
1658
        # Tail tone elimination
1659
        rs = RadioSetting(
1660
                "tail_note_elimination",
1661
                "Tail tone elimination",
1662
                RadioSettingValueBoolean(
1663
                    bool(_mem.tail_note_elimination > 0)))
1664
        basic.append(rs)
1665

    
1666
        # VFO open
1667
        rs = RadioSetting("vfo_open", "VFO open",
1668
                          RadioSettingValueBoolean(bool(_mem.vfo_open > 0)))
1669
        basic.append(rs)
1670

    
1671
        # Beep control
1672
        rs = RadioSetting(
1673
                "beep_control",
1674
                "Beep control",
1675
                RadioSettingValueBoolean(bool(_mem.beep_control > 0)))
1676
        basic.append(rs)
1677

    
1678
        # Scan resume mode
1679
        tmpscanres = _mem.scan_resume_mode
1680
        if tmpscanres >= len(SCANRESUME_LIST):
1681
            tmpscanres = 0
1682
        rs = RadioSetting(
1683
                "scan_resume_mode",
1684
                "Scan resume mode",
1685
                RadioSettingValueList(
1686
                    SCANRESUME_LIST,
1687
                    SCANRESUME_LIST[tmpscanres]))
1688
        basic.append(rs)
1689

    
1690
        # Keypad locked
1691
        rs = RadioSetting(
1692
                "key_lock",
1693
                "Keypad lock",
1694
                RadioSettingValueBoolean(bool(_mem.key_lock > 0)))
1695
        basic.append(rs)
1696

    
1697
        # Auto keypad lock
1698
        rs = RadioSetting(
1699
                "auto_keypad_lock",
1700
                "Auto keypad lock",
1701
                RadioSettingValueBoolean(bool(_mem.auto_keypad_lock > 0)))
1702
        basic.append(rs)
1703

    
1704
        # Power on display mode
1705
        tmpdispmode = _mem.power_on_dispmode
1706
        if tmpdispmode >= len(WELCOME_LIST):
1707
            tmpdispmode = 0
1708
        rs = RadioSetting(
1709
                "welcome_mode",
1710
                "Power on display mode",
1711
                RadioSettingValueList(
1712
                    WELCOME_LIST,
1713
                    WELCOME_LIST[tmpdispmode]))
1714
        basic.append(rs)
1715

    
1716
        # Keypad Tone
1717
        tmpkeypadtone = _mem.keypad_tone
1718
        if tmpkeypadtone >= len(KEYPADTONE_LIST):
1719
            tmpkeypadtone = 0
1720
        rs = RadioSetting("keypad_tone", "Keypad tone", RadioSettingValueList(
1721
            KEYPADTONE_LIST, KEYPADTONE_LIST[tmpkeypadtone]))
1722
        basic.append(rs)
1723

    
1724
        # Language
1725
        tmplanguage = _mem.language
1726
        if tmplanguage >= len(LANGUAGE_LIST):
1727
            tmplanguage = 0
1728
        rs = RadioSetting("language", "Language", RadioSettingValueList(
1729
            LANGUAGE_LIST, LANGUAGE_LIST[tmplanguage]))
1730
        basic.append(rs)
1731

    
1732
        # Alarm mode
1733
        tmpalarmmode = _mem.alarm_mode
1734
        if tmpalarmmode >= len(ALARMMODE_LIST):
1735
            tmpalarmmode = 0
1736
        rs = RadioSetting("alarm_mode", "Alarm mode", RadioSettingValueList(
1737
            ALARMMODE_LIST, ALARMMODE_LIST[tmpalarmmode]))
1738
        basic.append(rs)
1739

    
1740
        # Reminding of end of talk
1741
        tmpalarmmode = _mem.reminding_of_end_talk
1742
        if tmpalarmmode >= len(REMENDOFTALK_LIST):
1743
            tmpalarmmode = 0
1744
        rs = RadioSetting(
1745
                "reminding_of_end_talk",
1746
                "Reminding of end of talk",
1747
                RadioSettingValueList(
1748
                    REMENDOFTALK_LIST,
1749
                    REMENDOFTALK_LIST[tmpalarmmode]))
1750
        basic.append(rs)
1751

    
1752
        # Repeater tail tone elimination
1753
        tmprte = _mem.repeater_tail_elimination
1754
        if tmprte >= len(RTE_LIST):
1755
            tmprte = 0
1756
        rs = RadioSetting(
1757
                "repeater_tail_elimination",
1758
                "Repeater tail tone elimination",
1759
                RadioSettingValueList(RTE_LIST, RTE_LIST[tmprte]))
1760
        basic.append(rs)
1761

    
1762
        # Logo string 1
1763
        logo1 = str(_mem.logo_line1).strip("\x20\x00\xff") + "\x00"
1764
        logo1 = _getstring(logo1.encode('ascii', errors='ignore'), 0, 12)
1765
        rs = RadioSetting("logo1", "Logo string 1 (12 characters)",
1766
                          RadioSettingValueString(0, 12, logo1))
1767
        basic.append(rs)
1768

    
1769
        # Logo string 2
1770
        logo2 = str(_mem.logo_line2).strip("\x20\x00\xff") + "\x00"
1771
        logo2 = _getstring(logo2.encode('ascii', errors='ignore'), 0, 12)
1772
        rs = RadioSetting("logo2", "Logo string 2 (12 characters)",
1773
                          RadioSettingValueString(0, 12, logo2))
1774
        basic.append(rs)
1775

    
1776
        # FM radio
1777
        for i in range(1, 21):
1778
            freqname = "FM_"+str(i)
1779
            fmfreq = _mem.fmfreq[i-1]/10.0
1780
            if fmfreq < FMMIN or fmfreq > FMMAX:
1781
                rs = RadioSetting(freqname, freqname,
1782
                                  RadioSettingValueString(0, 5, ""))
1783
            else:
1784
                rs = RadioSetting(freqname, freqname,
1785
                                  RadioSettingValueString(0, 5, str(fmfreq)))
1786

    
1787
            fmradio.append(rs)
1788

    
1789
        # unlock settings
1790

    
1791
        # F-LOCK
1792
        tmpflock = _mem.int_flock
1793
        if tmpflock >= len(FLOCK_LIST):
1794
            tmpflock = 0
1795
        rs = RadioSetting(
1796
            "flock", "F-LOCK",
1797
            RadioSettingValueList(FLOCK_LIST, FLOCK_LIST[tmpflock]))
1798
        unlock.append(rs)
1799

    
1800
        # 350TX
1801
        rs = RadioSetting("350tx", "350TX - unlock 350-400MHz TX",
1802
                          RadioSettingValueBoolean(
1803
                              bool(_mem.int_350tx > 0)))
1804
        unlock.append(rs)
1805

    
1806
        # 200TX
1807
        rs = RadioSetting("200tx", "200TX - unlock 174-350MHz TX",
1808
                          RadioSettingValueBoolean(
1809
                              bool(_mem.int_200tx > 0)))
1810
        unlock.append(rs)
1811

    
1812
        # 500TX
1813
        rs = RadioSetting("500tx", "500TX - unlock 500-600MHz TX",
1814
                          RadioSettingValueBoolean(
1815
                              bool(_mem.int_500tx > 0)))
1816
        unlock.append(rs)
1817

    
1818
        # 350EN
1819
        rs = RadioSetting("350en", "350EN - unlock 350-400MHz RX",
1820
                          RadioSettingValueBoolean(
1821
                              bool(_mem.int_350en > 0)))
1822
        unlock.append(rs)
1823

    
1824
        # SCREEN
1825
        rs = RadioSetting("scren", "SCREN - scrambler enable",
1826
                          RadioSettingValueBoolean(
1827
                              bool(_mem.int_scren > 0)))
1828
        unlock.append(rs)
1829

    
1830
        # readonly info
1831
        # Firmware
1832
        if self.FIRMWARE_VERSION == "":
1833
            firmware = "To get the firmware version please download"
1834
            "the image from the radio first"
1835
        else:
1836
            firmware = self.FIRMWARE_VERSION
1837

    
1838
        val = RadioSettingValueString(0, 128, firmware)
1839
        val.set_mutable(False)
1840
        rs = RadioSetting("fw_ver", "Firmware Version", val)
1841
        roinfo.append(rs)
1842

    
1843
        # TODO: remove showing the driver version when it's in mainline chirp
1844
        # Driver version
1845
        val = RadioSettingValueString(0, 128, DRIVER_VERSION)
1846
        val.set_mutable(False)
1847
        rs = RadioSetting("driver_ver", "Driver version", val)
1848
        roinfo.append(rs)
1849

    
1850
        # No limits version for hacked firmware
1851
        val = RadioSettingValueBoolean(self.FIRMWARE_NOLIMITS)
1852
        val.set_mutable(False)
1853
        rs = RadioSetting("nolimits", "Limits disabled for modified firmware",
1854
                          val)
1855
        roinfo.append(rs)
1856

    
1857
        return top
1858

    
1859
    # Store details about a high-level memory to the memory map
1860
    # This is called when a user edits a memory in the UI
1861
    def set_memory(self, mem):
1862
        number = mem.number-1
1863

    
1864
        # Get a low-level memory object mapped to the image
1865
        _mem = self._memobj.channel[number]
1866
        _mem4 = self._memobj
1867
        # empty memory
1868
        if mem.empty:
1869
            _mem.set_raw("\xFF" * 16)
1870
            if number < 200:
1871
                _mem2 = self._memobj.channelname[number]
1872
                _mem2.set_raw("\xFF" * 16)
1873
                _mem4.channel_attributes[number].is_scanlist1 = 0
1874
                _mem4.channel_attributes[number].is_scanlist2 = 0
1875
                _mem4.channel_attributes[number].unknown1 = 0
1876
                _mem4.channel_attributes[number].unknown2 = 0
1877
                _mem4.channel_attributes[number].is_free = 1
1878
                _mem4.channel_attributes[number].band = 0x7
1879
            return mem
1880

    
1881
        # clean the channel memory, restore some bits if it was used before
1882
        if _mem.get_raw()[0] == "\xff":
1883
            # this was an empty memory
1884
            _mem.set_raw("\x00" * 16)
1885
        else:
1886
            # this memory was't empty, save some bits that we don't know the
1887
            # meaning of, or that we don't support yet
1888
            prev_0a = ord(_mem.get_raw()[0x0a]) & SAVE_MASK_0A
1889
            prev_0b = ord(_mem.get_raw()[0x0b]) & SAVE_MASK_0B
1890
            prev_0c = ord(_mem.get_raw()[0x0c]) & SAVE_MASK_0C
1891
            prev_0d = ord(_mem.get_raw()[0x0d]) & SAVE_MASK_0D
1892
            prev_0e = ord(_mem.get_raw()[0x0e]) & SAVE_MASK_0E
1893
            prev_0f = ord(_mem.get_raw()[0x0f]) & SAVE_MASK_0F
1894
            _mem.set_raw("\x00" * 10 +
1895
                         chr(prev_0a) + chr(prev_0b) + chr(prev_0c) +
1896
                         chr(prev_0d) + chr(prev_0e) + chr(prev_0f))
1897

    
1898
        if number < 200:
1899
            _mem4.channel_attributes[number].is_scanlist1 = 0
1900
            _mem4.channel_attributes[number].is_scanlist2 = 0
1901
            _mem4.channel_attributes[number].unknown1 = 0
1902
            _mem4.channel_attributes[number].unknown2 = 0
1903
            _mem4.channel_attributes[number].is_free = 1
1904
            _mem4.channel_attributes[number].band = 0x7
1905

    
1906
        # find tx frequency
1907
        if mem.duplex == '-':
1908
            txfreq = mem.freq - mem.offset
1909
        elif mem.duplex == '+':
1910
            txfreq = mem.freq + mem.offset
1911
        else:
1912
            txfreq = mem.freq
1913

    
1914
        # find band
1915
        band = _find_band(self, txfreq)
1916
        if band is False:
1917
            raise errors.RadioError(
1918
                    "Transmit frequency %.4fMHz is not supported by this radio"
1919
                    % txfreq/1000000.0)
1920

    
1921
        band = _find_band(self, mem.freq)
1922
        if band is False:
1923
            return mem
1924

    
1925
        # mode
1926
        if mem.mode == "NFM":
1927
            _mem.bandwidth = 1
1928
            _mem.enable_am = 0
1929
        elif mem.mode == "FM":
1930
            _mem.bandwidth = 0
1931
            _mem.enable_am = 0
1932
        elif mem.mode == "NAM":
1933
            _mem.bandwidth = 1
1934
            _mem.enable_am = 1
1935
        elif mem.mode == "AM":
1936
            _mem.bandwidth = 0
1937
            _mem.enable_am = 1
1938

    
1939
        # frequency/offset
1940
        _mem.freq = mem.freq/10
1941
        _mem.offset = mem.offset/10
1942

    
1943
        if mem.duplex == "off" or mem.duplex == "":
1944
            _mem.offset = 0
1945
            _mem.shift = 0
1946
        elif mem.duplex == '-':
1947
            _mem.shift = FLAGS1_OFFSET_MINUS
1948
        elif mem.duplex == '+':
1949
            _mem.shift = FLAGS1_OFFSET_PLUS
1950

    
1951
        # set band
1952
        if number < 200:
1953
            _mem4.channel_attributes[number].is_free = 0
1954
            _mem4.channel_attributes[number].band = band
1955

    
1956
        # channels >200 are the 14 VFO chanells and don't have names
1957
        if number < 200:
1958
            _mem2 = self._memobj.channelname[number]
1959
            tag = mem.name.ljust(10) + "\x00"*6
1960
            _mem2.name = tag  # Store the alpha tag
1961

    
1962
        # tone data
1963
        self._set_tone(mem, _mem)
1964

    
1965
        # step
1966
        _mem.step = STEPS.index(mem.tuning_step)
1967

    
1968
        # tx power
1969
        if str(mem.power) == str(UVK5_POWER_LEVELS[2]):
1970
            _mem.txpower = POWER_HIGH
1971
        elif str(mem.power) == str(UVK5_POWER_LEVELS[1]):
1972
            _mem.txpower = POWER_MEDIUM
1973
        else:
1974
            _mem.txpower = POWER_LOW
1975

    
1976
        for setting in mem.extra:
1977
            sname = setting.get_name()
1978
            svalue = setting.value.get_value()
1979

    
1980
            if sname == "bclo":
1981
                _mem.bclo = svalue and 1 or 0
1982

    
1983
            if sname == "pttid":
1984
                _mem.dtmf_pttid = PTTID_LIST.index(svalue)
1985

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

    
1989
            if sname == "dtmfdecode":
1990
                _mem.dtmf_decode = svalue and 1 or 0
1991

    
1992
            if sname == "scrambler":
1993
                _mem.scrambler = (
1994
                    _mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue)
1995

    
1996
            if number < 200 and sname == "scanlists":
1997
                if svalue == "1":
1998
                    _mem4.channel_attributes[number].is_scanlist1 = 1
1999
                    _mem4.channel_attributes[number].is_scanlist2 = 0
2000
                elif svalue == "2":
2001
                    _mem4.channel_attributes[number].is_scanlist1 = 0
2002
                    _mem4.channel_attributes[number].is_scanlist2 = 1
2003
                elif svalue == "1+2":
2004
                    _mem4.channel_attributes[number].is_scanlist1 = 1
2005
                    _mem4.channel_attributes[number].is_scanlist2 = 1
2006
                else:
2007
                    _mem4.channel_attributes[number].is_scanlist1 = 0
2008
                    _mem4.channel_attributes[number].is_scanlist2 = 0
2009

    
2010
        return mem
2011

    
2012

    
2013
@directory.register
2014
class UVK5Radio_nolimit(UVK5Radio):
2015
    VENDOR = "Quansheng"
2016
    MODEL = "UV-K5 (modified firmware)"
2017
    VARIANT = "nolimits"
2018
    FIRMWARE_NOLIMITS = True
2019

    
2020
    def get_features(self):
2021
        rf = UVK5Radio.get_features(self)
2022
        # This is what the BK4819 chip supports
2023
        rf.valid_bands = [(18000000,  620000000),
2024
                          (840000000, 1300000000)
2025
                          ]
2026
        return rf
(45-45/47)