Project

General

Profile

New Model #10478 » uvk5.py

uvk5.py chirp driver for UV-K5, version 20230621 - Jacek Lipkowski SQ5BPF, 06/21/2023 05:54 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. Before even attempting
12
# to use it save a memory image from the radio using k5prog:
13
# https://github.com/sq5bpf/k5prog
14
#
15
#
16
# This program is free software: you can redistribute it and/or modify
17
# it under the terms of the GNU General Public License as published by
18
# the Free Software Foundation, either version 2 of the License, or
19
# (at your option) any later version.
20
#
21
# This program is distributed in the hope that it will be useful,
22
# but WITHOUT ANY WARRANTY; without even the implied warranty of
23
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
# GNU General Public License for more details.
25
#
26
# You should have received a copy of the GNU General Public License
27
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
28

    
29

    
30
import struct
31
import logging
32

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

    
39
LOG = logging.getLogger(__name__)
40

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

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

    
50
DRIVER_VERSION = "Quansheng UV-K5 driver v20230621 (c) Jacek Lipkowski SQ5BPF"
51

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

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

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

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

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

    
92

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

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

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

    
110
#seekto 0xe70;
111
u8 call_channel;
112
u8 squelch;
113
u8 max_talk_time;
114
u8 noaa_autoscan;
115
u8 unknown1;
116
u8 unknown2;
117
u8 vox_level;
118
u8 mic_gain;
119
u8 unknown3;
120
u8 channel_display_mode;
121
u8 crossband;
122
u8 battery_save;
123
u8 dual_watch;
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_screen;
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
# Crossband receiving/transmitting
245
CROSSBAND_LIST = ["Off", "Band A", "Band B"]
246
DUALWATCH_LIST = CROSSBAND_LIST
247

    
248
# steps
249
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 8.33]
250

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

    
258

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

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

    
283
FLOCK_LIST = ["Off", "FCC", "CE", "GB", "430", "438"]
284

    
285
SCANRESUME_LIST = ["TO: Resume after 5 seconds",
286
                   "CO: Resume after signal dissapears",
287
                   "SE: Stop scanning after receiving a signal"]
288

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

    
297
MEM_SIZE = 0x2000  # size of all memory
298
PROG_SIZE = 0x1d00  # size of the memory that we will write
299
MEM_BLOCK = 0x80  # largest block of memory that we can reliably write
300

    
301
# fm radio supported frequencies
302
FMMIN = 76.0
303
FMMAX = 108.0
304

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

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

    
327
VFO_CHANNEL_NAMES = ["F1(50M-76M)A", "F1(50M-76M)B",
328
                     "F2(108M-136M)A", "F2(108M-136M)B",
329
                     "F3(136M-174M)A", "F3(136M-174M)B",
330
                     "F4(174M-350M)A", "F4(174M-350M)B",
331
                     "F5(350M-400M)A", "F5(350M-400M)B",
332
                     "F6(400M-470M)A", "F6(400M-470M)B",
333
                     "F7(470M-600M)A", "F7(470M-600M)B"]
334

    
335
SCANLIST_LIST = ["None", "1", "2", "1+2"]
336

    
337
DTMF_CHARS = "0123456789ABCD*# "
338
DTMF_CHARS_ID = "0123456789ABCDabcd"
339
DTMF_CHARS_KILL = "0123456789ABCDabcd"
340
DTMF_CHARS_UPDOWN = "0123456789ABCDabcd#* "
341
DTMF_CODE_CHARS = "ABCD*# "
342
DTMF_DECODE_RESPONSE_LIST = ["None", "Ring", "Reply", "Both"]
343

    
344
KEYACTIONS_LIST = ["None", "Flashlight on/off", "Power select",
345
                   "Monitor", "Scan on/off", "VOX on/off",
346
                   "Alarm on/off", "FM radio on/off", "Transmit 1750Hz"]
347

    
348

    
349
# the communication is obfuscated using this fine mechanism
350
def xorarr(data: bytes):
351
    tbl = [22, 108, 20, 230, 46, 145, 13, 64, 33, 53, 213, 64, 19, 3, 233, 128]
352
    x = b""
353
    r = 0
354
    for byte in data:
355
        x += bytes([byte ^ tbl[r]])
356
        r = (r+1) % len(tbl)
357
    return x
358

    
359

    
360
# if this crc was used for communication to AND from the radio, then it
361
# would be a measure to increase reliability.
362
# but it's only used towards the radio, so it's for further obfuscation
363
def calculate_crc16_xmodem(data: bytes):
364
    poly = 0x1021
365
    crc = 0x0
366
    for byte in data:
367
        crc = crc ^ (byte << 8)
368
        for i in range(8):
369
            crc = crc << 1
370
            if (crc & 0x10000):
371
                crc = (crc ^ poly) & 0xFFFF
372
    return crc & 0xFFFF
373

    
374

    
375
def _send_command(serport, data: bytes):
376
    """Send a command to UV-K5 radio"""
377
    LOG.debug("Sending command (unobfuscated) len=0x%4.4x:\n%s" %
378
              (len(data), util.hexprint(data)))
379

    
380
    crc = calculate_crc16_xmodem(data)
381
    data2 = data + struct.pack("<H", crc)
382

    
383
    command = struct.pack(">HBB", 0xabcd, len(data), 0) + \
384
        xorarr(data2) + \
385
        struct.pack(">H", 0xdcba)
386
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
387
        LOG.debug("Sending command (obfuscated):\n%s" % util.hexprint(command))
388
    try:
389
        result = serport.write(command)
390
    except Exception:
391
        raise errors.RadioError("Error writing data to radio")
392
    return result
393

    
394

    
395
def _receive_reply(serport):
396
    header = serport.read(4)
397
    if len(header) != 4:
398
        LOG.warning("Header short read: [%s] len=%i" %
399
                    (util.hexprint(header), len(header)))
400
        raise errors.RadioError("Header short read")
401
    if header[0] != 0xAB or header[1] != 0xCD or header[3] != 0x00:
402
        LOG.warning("Bad response header: %s len=%i" %
403
                    (util.hexprint(header), len(header)))
404
        raise errors.RadioError("Bad response header")
405

    
406
        return False
407

    
408
    cmd = serport.read(int(header[2]))
409
    if len(cmd) != int(header[2]):
410
        LOG.warning("Body short read: [%s] len=%i" %
411
                    (util.hexprint(cmd), len(cmd)))
412
        raise errors.RadioError("Command body short read")
413

    
414
    footer = serport.read(4)
415

    
416
    if len(footer) != 4:
417
        LOG.warning("Footer short read: [%s] len=%i" %
418
                    (util.hexprint(footer), len(footer)))
419
        raise errors.RadioError("Footer short read")
420

    
421
    if footer[2] != 0xDC or footer[3] != 0xBA:
422
        LOG.debug(
423
                "Reply before bad response footer (obfuscated)"
424
                "len=0x%4.4x:\n%s" % (len(cmd), util.hexprint(cmd)))
425
        LOG.warning("Bad response footer: %s len=%i" %
426
                    (util.hexprint(footer), len(footer)))
427
        raise errors.RadioError("Bad response footer")
428
        return False
429

    
430
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
431
        LOG.debug("Received reply (obfuscated) len=0x%4.4x:\n%s" %
432
                  (len(cmd), util.hexprint(cmd)))
433

    
434
    cmd2 = xorarr(cmd)
435

    
436
    LOG.debug("Received reply (unobfuscated) len=0x%4.4x:\n%s" %
437
              (len(cmd2), util.hexprint(cmd2)))
438

    
439
    return cmd2
440

    
441

    
442
def _getstring(data: bytes, begin, maxlen):
443
    tmplen = min(maxlen+1, len(data))
444
    s = [data[i] for i in range(begin, tmplen)]
445
    for key, val in enumerate(s):
446
        if val < ord(' ') or val > ord('~'):
447
            break
448
    return ''.join(chr(x) for x in s[0:key])
449

    
450

    
451
def _sayhello(serport):
452
    hellopacket = b"\x14\x05\x04\x00\x6a\x39\x57\x64"
453

    
454
    tries = 5
455
    while (True):
456
        LOG.debug("Sending hello packet")
457
        _send_command(serport, hellopacket)
458
        o = _receive_reply(serport)
459
        if (o):
460
            break
461
        tries -= 1
462
        if tries == 0:
463
            LOG.warning("Failed to initialise radio")
464
            raise errors.RadioError("Failed to initialize radio")
465
            return False
466
    firmware = _getstring(o, 4, 16)
467
    LOG.info("Found firmware: %s" % firmware)
468
    return firmware
469

    
470

    
471
def _readmem(serport, offset, length):
472
    LOG.debug("Sending readmem offset=0x%4.4x len=0x%4.4x" % (offset, length))
473

    
474
    readmem = b"\x1b\x05\x08\x00" + \
475
        struct.pack("<HBB", offset, length, 0) + \
476
        b"\x6a\x39\x57\x64"
477
    _send_command(serport, readmem)
478
    o = _receive_reply(serport)
479
    if DEBUG_SHOW_MEMORY_ACTIONS:
480
        LOG.debug("readmem Received data len=0x%4.4x:\n%s" %
481
                  (len(o), util.hexprint(o)))
482
    return o[8:]
483

    
484

    
485
def _writemem(serport, data, offset):
486
    LOG.debug("Sending writemem offset=0x%4.4x len=0x%4.4x" %
487
              (offset, len(data)))
488

    
489
    if DEBUG_SHOW_MEMORY_ACTIONS:
490
        LOG.debug("writemem sent data offset=0x%4.4x len=0x%4.4x:\n%s" %
491
                  (offset, len(data), util.hexprint(data)))
492

    
493
    dlen = len(data)
494
    writemem = b"\x1d\x05" + \
495
        struct.pack("<BBHBB", dlen+8, 0, offset, dlen, 1) + \
496
        b"\x6a\x39\x57\x64"+data
497

    
498
    _send_command(serport, writemem)
499
    o = _receive_reply(serport)
500

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

    
503
    if (o[0] == 0x1e
504
            and
505
            o[4] == (offset & 0xff)
506
            and
507
            o[5] == (offset >> 8) & 0xff):
508
        return True
509
    else:
510
        LOG.warning("Bad data from writemem")
511
        raise errors.RadioError("Bad response to writemem")
512
    return False
513

    
514

    
515
def _resetradio(serport):
516
    resetpacket = b"\xdd\x05\x00\x00"
517
    _send_command(serport, resetpacket)
518

    
519

    
520
def do_download(radio):
521
    serport = radio.pipe
522
    serport.timeout = 0.5
523
    status = chirp_common.Status()
524
    status.cur = 0
525
    status.max = MEM_SIZE
526
    status.msg = "Downloading from radio"
527
    radio.status_fn(status)
528

    
529
    eeprom = b""
530
    f = _sayhello(serport)
531
    if f:
532
        radio.FIRMWARE_VERSION = f
533
    else:
534
        return False
535

    
536
    addr = 0
537
    while addr < MEM_SIZE:
538
        o = _readmem(serport, addr, MEM_BLOCK)
539
        status.cur = addr
540
        radio.status_fn(status)
541

    
542
        if o and len(o) == MEM_BLOCK:
543
            eeprom += o
544
            addr += MEM_BLOCK
545
        else:
546
            raise errors.RadioError("Memory download incomplete")
547

    
548
    return memmap.MemoryMapBytes(eeprom)
549

    
550

    
551
def do_upload(radio):
552
    serport = radio.pipe
553
    serport.timeout = 0.5
554
    status = chirp_common.Status()
555
    status.cur = 0
556
    status.max = PROG_SIZE
557
    status.msg = "Uploading to radio"
558
    radio.status_fn(status)
559

    
560
    f = _sayhello(serport)
561
    if f:
562
        radio.FIRMWARE_VERSION = f
563
    else:
564
        return False
565

    
566
    addr = 0
567
    while addr < PROG_SIZE:
568
        o = radio.get_mmap()[addr:addr+MEM_BLOCK]
569
        _writemem(serport, o, addr)
570
        status.cur = addr
571
        radio.status_fn(status)
572
        if o:
573
            addr += MEM_BLOCK
574
        else:
575
            raise errors.RadioError("Memory upload incomplete")
576
    status.msg = "Uploaded OK"
577

    
578
    _resetradio(serport)
579

    
580
    return True
581

    
582

    
583
def _find_band(self, hz):
584
    mhz = hz/1000000.0
585
    if self.FIRMWARE_NOLIMITS:
586
        B = BANDS_NOLIMITS
587
    else:
588
        B = BANDS
589

    
590
    # currently the hacked firmware sets band=1 below 50MHz
591
    if self.FIRMWARE_NOLIMITS and mhz < 50.0:
592
        return 1
593

    
594
    for a in B:
595
        if mhz >= B[a][0] and mhz <= B[a][1]:
596
            return a
597
    return False
598

    
599

    
600
@directory.register
601
class UVK5Radio(chirp_common.CloneModeRadio):
602
    """Quansheng UV-K5"""
603
    VENDOR = "Quansheng"
604
    MODEL = "UV-K5"
605
    BAUD_RATE = 38400
606
    NEEDS_COMPAT_SERIAL = False
607
    FIRMWARE_VERSION = ""
608
    FIRMWARE_NOLIMITS = False
609

    
610
    def get_prompts(x=None):
611
        rp = chirp_common.RadioPrompts()
612
        rp.experimental = \
613
            ('This is an experimental driver for the Quanscheng UV-K5. '
614
             'It may harm your radio, or worse. Use at your own risk.\n\n'
615
             'Before attempting to do any changes please download'
616
             'the memory image from the radio with chirp or k5prog '
617
             'and keep it. This can be later used to recover the '
618
             'original settings. \n\n'
619
             'some details are not yet implemented')
620
        rp.pre_download = _(
621
            "1. Turn radio on.\n"
622
            "2. Connect cable to mic/spkr connector.\n"
623
            "3. Make sure connector is firmly connected.\n"
624
            "4. Click OK to download image from device.\n\n"
625
            "It will may not work if you turn o the radio "
626
            "with the cable already attached\n")
627
        rp.pre_upload = _(
628
            "1. Turn radio on.\n"
629
            "2. Connect cable to mic/spkr connector.\n"
630
            "3. Make sure connector is firmly connected.\n"
631
            "4. Click OK to upload the image to device.\n\n"
632
            "It will may not work if you turn o the radio "
633
            "with the cable already attached")
634
        return rp
635

    
636
    # Return information about this radio's features, including
637
    # how many memories it has, what bands it supports, etc
638
    def get_features(self):
639
        rf = chirp_common.RadioFeatures()
640
        rf.has_bank = False
641
        rf.valid_dtcs_codes = DTCS_CODES
642
        rf.has_rx_dtcs = True
643
        rf.has_ctone = True
644
        rf.has_settings = True
645
        rf.has_comment = False
646
        rf.valid_name_length = 16
647
        rf.valid_power_levels = UVK5_POWER_LEVELS
648

    
649
        # hack so we can input any frequency,
650
        # the 0.1 and 0.01 steps don't work unfortunately
651
        rf.valid_tuning_steps = [0.01, 0.1, 1.0] + STEPS
652

    
653
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
654
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
655
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
656

    
657
        rf.valid_characters = chirp_common.CHARSET_ASCII
658
        rf.valid_modes = ["FM", "NFM", "AM", "NAM"]
659
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
660

    
661
        rf.valid_skips = [""]
662

    
663
        # This radio supports memories 1-200, 201-214 are the VFO memories
664
        rf.memory_bounds = (1, 214)
665

    
666
        # This is what the BK4819 chip supports
667
        # Will leave it in a comment, might be useful someday
668
        # rf.valid_bands = [(18000000,  620000000),
669
        #                  (840000000, 1300000000)
670
        #                  ]
671
        rf.valid_bands = []
672
        for a in BANDS:
673
            rf.valid_bands.append(
674
                    (int(BANDS[a][0]*1000000), int(BANDS[a][1]*1000000)))
675
        return rf
676

    
677
    # Do a download of the radio from the serial port
678
    def sync_in(self):
679
        self._mmap = do_download(self)
680
        self.process_mmap()
681

    
682
    # Do an upload of the radio to the serial port
683
    def sync_out(self):
684
        do_upload(self)
685

    
686
    # Convert the raw byte array into a memory object structure
687
    def process_mmap(self):
688
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
689

    
690
    # Return a raw representation of the memory object, which
691
    # is very helpful for development
692
    def get_raw_memory(self, number):
693
        return repr(self._memobj.channel[number-1])
694

    
695
    def validate_memory(self, mem):
696
        msgs = super().validate_memory(mem)
697
        return msgs
698

    
699
    def _set_tone(self, mem, _mem):
700
        ((txmode, txtone, txpol),
701
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
702

    
703
        if txmode == "Tone":
704
            txtoval = CTCSS_TONES.index(txtone)
705
            txmoval = 0b01
706
        elif txmode == "DTCS":
707
            txmoval = txpol == "R" and 0b11 or 0b10
708
            txtoval = DTCS_CODES.index(txtone)
709
        else:
710
            txmoval = 0
711
            txtoval = 0
712

    
713
        if rxmode == "Tone":
714
            rxtoval = CTCSS_TONES.index(rxtone)
715
            rxmoval = 0b01
716
        elif rxmode == "DTCS":
717
            rxmoval = rxpol == "R" and 0b11 or 0b10
718
            rxtoval = DTCS_CODES.index(rxtone)
719
        else:
720
            rxmoval = 0
721
            rxtoval = 0
722

    
723
        _mem.rxcodeflag = rxmoval
724
        _mem.txcodeflag = txmoval
725
        _mem.unknown1 = 0
726
        _mem.unknown2 = 0
727
        _mem.rxcode = rxtoval
728
        _mem.txcode = txtoval
729

    
730
    def _get_tone(self, mem, _mem):
731
        rxtype = _mem.rxcodeflag
732
        txtype = _mem.txcodeflag
733
        rx_tmode = TMODES[rxtype]
734
        tx_tmode = TMODES[txtype]
735

    
736
        rx_tone = tx_tone = None
737

    
738
        if tx_tmode == "Tone":
739
            if _mem.txcode < len(CTCSS_TONES):
740
                tx_tone = CTCSS_TONES[_mem.txcode]
741
            else:
742
                tx_tone = 0
743
                tx_tmode = ""
744
        elif tx_tmode == "DTCS":
745
            if _mem.txcode < len(DTCS_CODES):
746
                tx_tone = DTCS_CODES[_mem.txcode]
747
            else:
748
                tx_tone = 0
749
                tx_tmode = ""
750

    
751
        if rx_tmode == "Tone":
752
            if _mem.rxcode < len(CTCSS_TONES):
753
                rx_tone = CTCSS_TONES[_mem.rxcode]
754
            else:
755
                rx_tone = 0
756
                rx_tmode = ""
757
        elif rx_tmode == "DTCS":
758
            if _mem.rxcode < len(DTCS_CODES):
759
                rx_tone = DTCS_CODES[_mem.rxcode]
760
            else:
761
                rx_tone = 0
762
                rx_tmode = ""
763

    
764
        tx_pol = txtype == 0x03 and "R" or "N"
765
        rx_pol = rxtype == 0x03 and "R" or "N"
766

    
767
        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
768
                                       (rx_tmode, rx_tone, rx_pol))
769

    
770
    # Extract a high-level memory object from the low-level memory map
771
    # This is called to populate a memory in the UI
772
    def get_memory(self, number2):
773
        number = number2-1  # in the radio memories start with 0
774

    
775
        mem = chirp_common.Memory()
776

    
777
        # cutting and pasting configs from different radios
778
        # might try to set channel 0
779
        if number2 == 0:
780
            LOG.warning("Attempt to get channel 0")
781
            return mem
782

    
783
        _mem = self._memobj.channel[number]
784

    
785
        tmpcomment = ""
786

    
787
        mem.number = number2
788

    
789
        is_empty = False
790
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
791
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
792
            is_empty = True
793

    
794
        tmpscn = SCANLIST_LIST[0]
795

    
796
        # We'll also look at the channel attributes if a memory has them
797
        if number < 200:
798
            _mem3 = self._memobj.channel_attributes[number]
799
            # free memory bit
800
            if _mem3.is_free > 0:
801
                is_empty = True
802
            # scanlists
803
            if _mem3.is_scanlist1 > 0 and _mem3.is_scanlist2 > 0:
804
                tmpscn = SCANLIST_LIST[3]  # "1+2"
805
            elif _mem3.is_scanlist1 > 0:
806
                tmpscn = SCANLIST_LIST[1]  # "1"
807
            elif _mem3.is_scanlist2 > 0:
808
                tmpscn = SCANLIST_LIST[2]  # "2"
809

    
810
        if is_empty:
811
            mem.empty = True
812
            # set some sane defaults:
813
            mem.power = UVK5_POWER_LEVELS[2]
814
            mem.extra = RadioSettingGroup("Extra", "extra")
815
            rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(False))
816
            mem.extra.append(rs)
817
            rs = RadioSetting("frev", "FreqRev",
818
                              RadioSettingValueBoolean(False))
819
            mem.extra.append(rs)
820
            rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
821
                PTTID_LIST, PTTID_LIST[0]))
822
            mem.extra.append(rs)
823
            rs = RadioSetting("dtmfdecode", "DTMF decode",
824
                              RadioSettingValueBoolean(False))
825
            mem.extra.append(rs)
826
            rs = RadioSetting("scrambler", "Scrambler", RadioSettingValueList(
827
                SCRAMBLER_LIST, SCRAMBLER_LIST[0]))
828
            mem.extra.append(rs)
829

    
830
            rs = RadioSetting("scanlists", "Scanlists", RadioSettingValueList(
831
                SCANLIST_LIST, SCANLIST_LIST[0]))
832
            mem.extra.append(rs)
833

    
834
            # actually the step and duplex are overwritten by chirp based on
835
            # bandplan. they are here to document sane defaults for IARU r1
836
            # mem.tuning_step = 25.0
837
            # mem.duplex = "off"
838

    
839
            return mem
840

    
841
        if number > 199:
842
            mem.name = VFO_CHANNEL_NAMES[number-200]
843
            mem.immutable = ["name", "scanlists"]
844
        else:
845
            _mem2 = self._memobj.channelname[number]
846
            for char in _mem2.name:
847
                if str(char) == "\xFF" or str(char) == "\x00":
848
                    break
849
                mem.name += str(char)
850
            mem.name = mem.name.rstrip()
851

    
852
        # Convert your low-level frequency to Hertz
853
        mem.freq = int(_mem.freq)*10
854
        mem.offset = int(_mem.offset)*10
855

    
856
        if (mem.offset == 0):
857
            mem.duplex = ''
858
        else:
859
            if _mem.shift == FLAGS1_OFFSET_MINUS:
860
                mem.duplex = '-'
861
            elif _mem.shift == FLAGS1_OFFSET_PLUS:
862
                mem.duplex = '+'
863
            else:
864
                mem.duplex = ''
865

    
866
        # tone data
867
        self._get_tone(mem, _mem)
868

    
869
        # mode
870
        if _mem.enable_am > 0:
871
            if _mem.bandwidth > 0:
872
                mem.mode = "NAM"
873
            else:
874
                mem.mode = "AM"
875
        else:
876
            if _mem.bandwidth > 0:
877
                mem.mode = "NFM"
878
            else:
879
                mem.mode = "FM"
880

    
881
        # tuning step
882
        tstep = _mem.step & 0x7
883
        if tstep < len(STEPS):
884
            mem.tuning_step = STEPS[tstep]
885
        else:
886
            mem.tuning_step = 2.5
887

    
888
        # power
889
        if _mem.txpower == POWER_HIGH:
890
            mem.power = UVK5_POWER_LEVELS[2]
891
        elif _mem.txpower == POWER_MEDIUM:
892
            mem.power = UVK5_POWER_LEVELS[1]
893
        else:
894
            mem.power = UVK5_POWER_LEVELS[0]
895

    
896
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
897
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
898
            mem.empty = True
899
        else:
900
            mem.empty = False
901

    
902
        mem.extra = RadioSettingGroup("Extra", "extra")
903

    
904
        # BCLO
905
        is_bclo = bool(_mem.bclo > 0)
906
        rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(is_bclo))
907
        mem.extra.append(rs)
908
        tmpcomment += "BCLO:"+(is_bclo and "ON" or "off")+" "
909

    
910
        # Frequency reverse - whatever that means, don't see it in the manual
911
        is_frev = bool(_mem.freq_reverse > 0)
912
        rs = RadioSetting("frev", "FreqRev", RadioSettingValueBoolean(is_frev))
913
        mem.extra.append(rs)
914
        tmpcomment += "FreqReverse:"+(is_frev and "ON" or "off")+" "
915

    
916
        # PTTID
917
        pttid = _mem.dtmf_pttid
918
        rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
919
            PTTID_LIST, PTTID_LIST[pttid]))
920
        mem.extra.append(rs)
921
        tmpcomment += "PTTid:"+PTTID_LIST[pttid]+" "
922

    
923
        # DTMF DECODE
924
        is_dtmf = bool(_mem.dtmf_decode > 0)
925
        rs = RadioSetting("dtmfdecode", "DTMF decode",
926
                          RadioSettingValueBoolean(is_dtmf))
927
        mem.extra.append(rs)
928
        tmpcomment += "DTMFdecode:"+(is_dtmf and "ON" or "off")+" "
929

    
930
        # Scrambler
931
        if _mem.scrambler & 0x0f < len(SCRAMBLER_LIST):
932
            enc = _mem.scrambler & 0x0f
933
        else:
934
            enc = 0
935

    
936
        rs = RadioSetting("scrambler", "Scrambler", RadioSettingValueList(
937
            SCRAMBLER_LIST, SCRAMBLER_LIST[enc]))
938
        mem.extra.append(rs)
939
        tmpcomment += "Scrambler:"+SCRAMBLER_LIST[enc]+" "
940

    
941
        rs = RadioSetting("scanlists", "Scanlists", RadioSettingValueList(
942
            SCANLIST_LIST, tmpscn))
943
        mem.extra.append(rs)
944

    
945
        return mem
946

    
947
    def set_settings(self, settings):
948
        _mem = self._memobj
949
        for element in settings:
950
            if not isinstance(element, RadioSetting):
951
                self.set_settings(element)
952
                continue
953

    
954
            # basic settings
955

    
956
            # call channel
957
            if element.get_name() == "call_channel":
958
                _mem.call_channel = int(element.value)-1
959

    
960
            # squelch
961
            if element.get_name() == "squelch":
962
                _mem.squelch = int(element.value)
963
            # TOT
964
            if element.get_name() == "tot":
965
                _mem.max_talk_time = int(element.value)
966
            # NOAA autoscan
967
            if element.get_name() == "noaa_autoscan":
968
                _mem.noaa_autoscan = element.value and 1 or 0
969

    
970
            # vox level
971
            if element.get_name() == "vox_level":
972
                _mem.vox_level = int(element.value)-1
973

    
974
            # mic gain
975
            if element.get_name() == "mic_gain":
976
                _mem.mic_gain = int(element.value)
977

    
978
            # Channel display mode
979
            if element.get_name() == "channel_display_mode":
980
                _mem.channel_display_mode = CHANNELDISP_LIST.index(
981
                    str(element.value))
982

    
983
            # Crossband receiving/transmitting
984
            if element.get_name() == "crossband":
985
                _mem.crossband = CROSSBAND_LIST.index(str(element.value))
986

    
987
            # Battery Save
988
            if element.get_name() == "battery_save":
989
                _mem.battery_save = BATSAVE_LIST.index(str(element.value))
990
            # Dual Watch
991
            if element.get_name() == "dualwatch":
992
                _mem.dual_watch = DUALWATCH_LIST.index(str(element.value))
993

    
994
            # Tail tone elimination
995
            if element.get_name() == "tail_note_elimination":
996
                _mem.tail_note_elimination = element.value and 1 or 0
997

    
998
            # VFO Open
999
            if element.get_name() == "vfo_open":
1000
                _mem.vfo_open = element.value and 1 or 0
1001

    
1002
            # Beep control
1003
            if element.get_name() == "beep_control":
1004
                _mem.beep_control = element.value and 1 or 0
1005

    
1006
            # Scan resume mode
1007
            if element.get_name() == "scan_resume_mode":
1008
                _mem.scan_resume_mode = SCANRESUME_LIST.index(
1009
                    str(element.value))
1010

    
1011
            # Auto keypad lock
1012
            if element.get_name() == "auto_keypad_lock":
1013
                _mem.auto_keypad_lock = element.value and 1 or 0
1014

    
1015
            # Power on display mode
1016
            if element.get_name() == "welcome_mode":
1017
                _mem.power_on_dispmode = WELCOME_LIST.index(str(element.value))
1018

    
1019
            # Keypad Tone
1020
            if element.get_name() == "keypad_tone":
1021
                _mem.keypad_tone = KEYPADTONE_LIST.index(str(element.value))
1022

    
1023
            # Language
1024
            if element.get_name() == "language":
1025
                _mem.language = LANGUAGE_LIST.index(str(element.value))
1026

    
1027
            # Alarm mode
1028
            if element.get_name() == "alarm_mode":
1029
                _mem.alarm_mode = ALARMMODE_LIST.index(str(element.value))
1030

    
1031
            # Reminding of end of talk
1032
            if element.get_name() == "reminding_of_end_talk":
1033
                _mem.reminding_of_end_talk = REMENDOFTALK_LIST.index(
1034
                    str(element.value))
1035

    
1036
            # Repeater tail tone elimination
1037
            if element.get_name() == "repeater_tail_elimination":
1038
                _mem.repeater_tail_elimination = RTE_LIST.index(
1039
                    str(element.value))
1040

    
1041
            # Logo string 1
1042
            if element.get_name() == "logo1":
1043
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
1044
                _mem.logo_line1 = b[0:12]+"\xff\xff\xff\xff"
1045

    
1046
            # Logo string 2
1047
            if element.get_name() == "logo2":
1048
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
1049
                _mem.logo_line2 = b[0:12]+"\xff\xff\xff\xff"
1050

    
1051
            # unlock settings
1052

    
1053
            # FLOCK
1054
            if element.get_name() == "flock":
1055
                _mem.int_flock = FLOCK_LIST.index(str(element.value))
1056

    
1057
            # 350TX
1058
            if element.get_name() == "350tx":
1059
                _mem.int_350tx = element.value and 1 or 0
1060

    
1061
            # UNKNOWN1
1062
            if element.get_name() == "unknown1":
1063
                _mem.int_unknown1 = element.value and 1 or 0
1064

    
1065
            # 200TX
1066
            if element.get_name() == "200tx":
1067
                _mem.int_200tx = element.value and 1 or 0
1068

    
1069
            # 500TX
1070
            if element.get_name() == "500tx":
1071
                _mem.int_500tx = element.value and 1 or 0
1072

    
1073
            # 350EN
1074
            if element.get_name() == "350en":
1075
                _mem.int_350en = element.value and 1 or 0
1076

    
1077
            # fm radio
1078
            for i in range(1, 21):
1079
                freqname = "FM_" + str(i)
1080
                if element.get_name() == freqname:
1081
                    val = str(element.value).strip()
1082
                    try:
1083
                        val2 = int(float(val)*10)
1084
                    except Exception:
1085
                        val2 = 0xffff
1086

    
1087
                    if val2 < FMMIN*10 or val2 > FMMAX*10:
1088
                        val2 = 0xffff
1089
#                        raise errors.InvalidValueError(
1090
#                                "FM radio frequency should be a value "
1091
#                                "in the range %.1f - %.1f" % (FMMIN , FMMAX))
1092
                    _mem.fmfreq[i-1] = val2
1093

    
1094
            # dtmf settings
1095
            if element.get_name() == "dtmf_side_tone":
1096
                _mem.dtmf_settings.side_tone = \
1097
                        element.value and 1 or 0
1098

    
1099
            if element.get_name() == "dtmf_separate_code":
1100
                _mem.dtmf_settings.separate_code = str(element.value)
1101

    
1102
            if element.get_name() == "dtmf_group_call_code":
1103
                _mem.dtmf_settings.group_call_code = element.value
1104

    
1105
            if element.get_name() == "dtmf_decode_response":
1106
                _mem.dtmf_settings.decode_response = \
1107
                        DTMF_DECODE_RESPONSE_LIST.index(str(element.value))
1108

    
1109
            if element.get_name() == "dtmf_auto_reset_time":
1110
                _mem.dtmf_settings.auto_reset_time = \
1111
                        int(int(element.value)/10)
1112

    
1113
            if element.get_name() == "dtmf_preload_time":
1114
                _mem.dtmf_settings.preload_time = \
1115
                        int(int(element.value)/10)
1116

    
1117
            if element.get_name() == "dtmf_first_code_persist_time":
1118
                _mem.dtmf_settings.first_code_persist_time = \
1119
                        int(int(element.value)/10)
1120

    
1121
            if element.get_name() == "dtmf_hash_persist_time":
1122
                _mem.dtmf_settings.hash_persist_time = \
1123
                        int(int(element.value)/10)
1124

    
1125
            if element.get_name() == "dtmf_code_persist_time":
1126
                _mem.dtmf_settings.code_persist_time = \
1127
                        int(int(element.value)/10)
1128

    
1129
            if element.get_name() == "dtmf_code_interval_time":
1130
                _mem.dtmf_settings.code_interval_time = \
1131
                        int(int(element.value)/10)
1132

    
1133
            if element.get_name() == "dtmf_permit_remote_kill":
1134
                _mem.dtmf_settings.permit_remote_kill = \
1135
                        element.value and 1 or 0
1136

    
1137
            if element.get_name() == "dtmf_dtmf_local_code":
1138
                k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*3
1139
                _mem.dtmf_settings_numbers.dtmf_local_code = k[0:3]
1140

    
1141
            if element.get_name() == "dtmf_dtmf_up_code":
1142
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*16
1143
                _mem.dtmf_settings_numbers.dtmf_up_code = k[0:16]
1144

    
1145
            if element.get_name() == "dtmf_dtmf_down_code":
1146
                k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*16
1147
                _mem.dtmf_settings_numbers.dtmf_down_code = k[0:16]
1148

    
1149
            if element.get_name() == "dtmf_kill_code":
1150
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*5
1151
                _mem.dtmf_settings_numbers.kill_code = k[0:5]
1152

    
1153
            if element.get_name() == "dtmf_revive_code":
1154
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*5
1155
                _mem.dtmf_settings_numbers.revive_code = k[0:5]
1156

    
1157
            # dtmf contacts
1158
            for i in range(1, 17):
1159
                varname = "DTMF_" + str(i)
1160
                if element.get_name() == varname:
1161
                    k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*8
1162
                    _mem.dtmfcontact[i-1].name = k[0:8]
1163

    
1164
                varnumname = "DTMFNUM_" + str(i)
1165
                if element.get_name() == varnumname:
1166
                    k = str(element.value).rstrip("\x20\xff\x00") + "\xff"*3
1167
                    _mem.dtmfcontact[i-1].number = k[0:3]
1168

    
1169
            # scanlist stuff
1170
            if element.get_name() == "scanlist_default":
1171
                val = (int(element.value) == 2) and 1 or 0
1172
                _mem.scanlist_default = val
1173

    
1174
            if element.get_name() == "scanlist1_priority_scan":
1175
                _mem.scanlist1_priority_scan = \
1176
                        element.value and 1 or 0
1177

    
1178
            if element.get_name() == "scanlist2_priority_scan":
1179
                _mem.scanlist2_priority_scan = \
1180
                        element.value and 1 or 0
1181

    
1182
            if element.get_name() == "scanlist1_priority_ch1" or \
1183
                    element.get_name() == "scanlist1_priority_ch2" or \
1184
                    element.get_name() == "scanlist2_priority_ch1" or \
1185
                    element.get_name() == "scanlist2_priority_ch2":
1186

    
1187
                val = int(element.value)
1188

    
1189
                if val > 200 or val < 1:
1190
                    val = 0xff
1191
                else:
1192
                    val -= 1
1193

    
1194
                if element.get_name() == "scanlist1_priority_ch1":
1195
                    _mem.scanlist1_priority_ch1 = val
1196
                if element.get_name() == "scanlist1_priority_ch2":
1197
                    _mem.scanlist1_priority_ch2 = val
1198
                if element.get_name() == "scanlist2_priority_ch1":
1199
                    _mem.scanlist2_priority_ch1 = val
1200
                if element.get_name() == "scanlist2_priority_ch2":
1201
                    _mem.scanlist2_priority_ch2 = val
1202

    
1203
            if element.get_name() == "key1_shortpress_action":
1204
                _mem.key1_shortpress_action = KEYACTIONS_LIST.index(
1205
                        str(element.value))
1206

    
1207
            if element.get_name() == "key1_longpress_action":
1208
                _mem.key1_longpress_action = KEYACTIONS_LIST.index(
1209
                        str(element.value))
1210

    
1211
            if element.get_name() == "key2_shortpress_action":
1212
                _mem.key2_shortpress_action = KEYACTIONS_LIST.index(
1213
                        str(element.value))
1214

    
1215
            if element.get_name() == "key2_longpress_action":
1216
                _mem.key2_longpress_action = KEYACTIONS_LIST.index(
1217
                        str(element.value))
1218

    
1219
    def get_settings(self):
1220
        _mem = self._memobj
1221
        basic = RadioSettingGroup("basic", "Basic Settings")
1222
        keya = RadioSettingGroup("keya", "Programmable keys")
1223
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1224
        dtmfc = RadioSettingGroup("dtmfc", "DTMF Contacts")
1225
        scanl = RadioSettingGroup("scn", "Scan Lists")
1226
        unlock = RadioSettingGroup("unlock", "Unlock Settings")
1227
        fmradio = RadioSettingGroup("fmradio", "FM Radio")
1228

    
1229
        roinfo = RadioSettingGroup("roinfo", "Driver information")
1230

    
1231
        top = RadioSettings(
1232
                basic, keya, dtmf, dtmfc, scanl, unlock, fmradio, roinfo)
1233

    
1234
        # Programmable keys
1235
        tmpval = int(_mem.key1_shortpress_action)
1236
        if tmpval >= len(KEYACTIONS_LIST):
1237
            tmpval = 0
1238
        rs = RadioSetting("key1_shortpress_action", "Side key 1 short press",
1239
                          RadioSettingValueList(
1240
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1241
        keya.append(rs)
1242

    
1243
        tmpval = int(_mem.key1_longpress_action)
1244
        if tmpval >= len(KEYACTIONS_LIST):
1245
            tmpval = 0
1246
        rs = RadioSetting("key1_longpress_action", "Side key 1 long press",
1247
                          RadioSettingValueList(
1248
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1249
        keya.append(rs)
1250

    
1251
        tmpval = int(_mem.key2_shortpress_action)
1252
        if tmpval >= len(KEYACTIONS_LIST):
1253
            tmpval = 0
1254
        rs = RadioSetting("key2_shortpress_action", "Side key 2 short press",
1255
                          RadioSettingValueList(
1256
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1257
        keya.append(rs)
1258

    
1259
        tmpval = int(_mem.key2_longpress_action)
1260
        if tmpval >= len(KEYACTIONS_LIST):
1261
            tmpval = 0
1262
        rs = RadioSetting("key2_longpress_action", "Side key 2 long press",
1263
                          RadioSettingValueList(
1264
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1265
        keya.append(rs)
1266

    
1267
        # DTMF settings
1268
        tmppr = bool(_mem.dtmf_settings.side_tone > 0)
1269
        rs = RadioSetting(
1270
                "dtmf_side_tone",
1271
                "DTMF Sidetone",
1272
                RadioSettingValueBoolean(tmppr))
1273
        dtmf.append(rs)
1274

    
1275
        tmpval = str(_mem.dtmf_settings.separate_code)
1276
        if tmpval not in DTMF_CODE_CHARS:
1277
            tmpval = '*'
1278
        val = RadioSettingValueString(1, 1, tmpval)
1279
        val.set_charset(DTMF_CODE_CHARS)
1280
        rs = RadioSetting("dtmf_separate_code", "Separate Code", val)
1281
        dtmf.append(rs)
1282

    
1283
        tmpval = str(_mem.dtmf_settings.group_call_code)
1284
        if tmpval not in DTMF_CODE_CHARS:
1285
            tmpval = '#'
1286
        val = RadioSettingValueString(1, 1, tmpval)
1287
        val.set_charset(DTMF_CODE_CHARS)
1288
        rs = RadioSetting("dtmf_group_call_code", "Group Call Code", val)
1289
        dtmf.append(rs)
1290

    
1291
        tmpval = _mem.dtmf_settings.decode_response
1292
        if tmpval >= len(DTMF_DECODE_RESPONSE_LIST):
1293
            tmpval = 0
1294
        rs = RadioSetting("dtmf_decode_response", "Decode Response",
1295
                          RadioSettingValueList(
1296
                              DTMF_DECODE_RESPONSE_LIST,
1297
                              DTMF_DECODE_RESPONSE_LIST[tmpval]))
1298
        dtmf.append(rs)
1299

    
1300
        tmpval = _mem.dtmf_settings.auto_reset_time
1301
        if tmpval > 60 or tmpval < 5:
1302
            tmpval = 5
1303
        rs = RadioSetting("dtmf_auto_reset_time",
1304
                          "Auto reset time (s)",
1305
                          RadioSettingValueInteger(5, 60, tmpval))
1306
        dtmf.append(rs)
1307

    
1308
        tmpval = int(_mem.dtmf_settings.preload_time)
1309
        if tmpval > 100 or tmpval < 3:
1310
            tmpval = 30
1311
        tmpval *= 10
1312
        rs = RadioSetting("dtmf_preload_time",
1313
                          "Pre-load time (ms)",
1314
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1315
        dtmf.append(rs)
1316

    
1317
        tmpval = int(_mem.dtmf_settings.first_code_persist_time)
1318
        if tmpval > 100 or tmpval < 3:
1319
            tmpval = 30
1320
        tmpval *= 10
1321
        rs = RadioSetting("dtmf_first_code_persist_time",
1322
                          "First code persist time (ms)",
1323
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1324
        dtmf.append(rs)
1325

    
1326
        tmpval = int(_mem.dtmf_settings.hash_persist_time)
1327
        if tmpval > 100 or tmpval < 3:
1328
            tmpval = 30
1329
        tmpval *= 10
1330
        rs = RadioSetting("dtmf_hash_persist_time",
1331
                          "#/* persist time (ms)",
1332
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1333
        dtmf.append(rs)
1334

    
1335
        tmpval = int(_mem.dtmf_settings.code_persist_time)
1336
        if tmpval > 100 or tmpval < 3:
1337
            tmpval = 30
1338
        tmpval *= 10
1339
        rs = RadioSetting("dtmf_code_persist_time",
1340
                          "Code persist time (ms)",
1341
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1342
        dtmf.append(rs)
1343

    
1344
        tmpval = int(_mem.dtmf_settings.code_interval_time)
1345
        if tmpval > 100 or tmpval < 3:
1346
            tmpval = 30
1347
        tmpval *= 10
1348
        rs = RadioSetting("dtmf_code_interval_time",
1349
                          "Code interval time (ms)",
1350
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1351
        dtmf.append(rs)
1352

    
1353
        tmpval = bool(_mem.dtmf_settings.permit_remote_kill > 0)
1354
        rs = RadioSetting(
1355
                "dtmf_permit_remote_kill",
1356
                "Permit remote kill",
1357
                RadioSettingValueBoolean(tmpval))
1358
        dtmf.append(rs)
1359

    
1360
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_local_code).upper().strip(
1361
                "\x00\xff\x20")
1362
        for i in tmpval:
1363
            if i in DTMF_CHARS_ID:
1364
                continue
1365
            else:
1366
                tmpval = "103"
1367
                break
1368
        val = RadioSettingValueString(3, 3, tmpval)
1369
        val.set_charset(DTMF_CHARS_ID)
1370
        rs = RadioSetting("dtmf_dtmf_local_code",
1371
                          "Local code (3 chars 0-9 ABCD)", val)
1372
        dtmf.append(rs)
1373

    
1374
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_up_code).upper().strip(
1375
                "\x00\xff\x20")
1376
        for i in tmpval:
1377
            if i in DTMF_CHARS_UPDOWN or i == "":
1378
                continue
1379
            else:
1380
                tmpval = "123"
1381
                break
1382
        val = RadioSettingValueString(1, 16, tmpval)
1383
        val.set_charset(DTMF_CHARS_UPDOWN)
1384
        rs = RadioSetting("dtmf_dtmf_up_code",
1385
                          "Up code (1-16 chars 0-9 ABCD*#)", val)
1386
        dtmf.append(rs)
1387

    
1388
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_down_code).upper().strip(
1389
                "\x00\xff\x20")
1390
        for i in tmpval:
1391
            if i in DTMF_CHARS_UPDOWN:
1392
                continue
1393
            else:
1394
                tmpval = "456"
1395
                break
1396
        val = RadioSettingValueString(1, 16, tmpval)
1397
        val.set_charset(DTMF_CHARS_UPDOWN)
1398
        rs = RadioSetting("dtmf_dtmf_down_code",
1399
                          "Down code (1-16 chars 0-9 ABCD*#)", val)
1400
        dtmf.append(rs)
1401

    
1402
        tmpval = str(_mem.dtmf_settings_numbers.kill_code).upper().strip(
1403
                "\x00\xff\x20")
1404
        for i in tmpval:
1405
            if i in DTMF_CHARS_KILL:
1406
                continue
1407
            else:
1408
                tmpval = "77777"
1409
                break
1410
        if not len(tmpval) == 5:
1411
            tmpval = "77777"
1412
        val = RadioSettingValueString(5, 5, tmpval)
1413
        val.set_charset(DTMF_CHARS_KILL)
1414
        rs = RadioSetting("dtmf_kill_code",
1415
                          "Kill code (5 chars 0-9 ABCD)", val)
1416
        dtmf.append(rs)
1417

    
1418
        tmpval = str(_mem.dtmf_settings_numbers.revive_code).upper().strip(
1419
                "\x00\xff\x20")
1420
        for i in tmpval:
1421
            if i in DTMF_CHARS_KILL:
1422
                continue
1423
            else:
1424
                tmpval = "88888"
1425
                break
1426
        if not len(tmpval) == 5:
1427
            tmpval = "88888"
1428
        val = RadioSettingValueString(5, 5, tmpval)
1429
        val.set_charset(DTMF_CHARS_KILL)
1430
        rs = RadioSetting("dtmf_revive_code",
1431
                          "Revive code (5 chars 0-9 ABCD)", val)
1432
        dtmf.append(rs)
1433

    
1434
        val = RadioSettingValueString(0, 80,
1435
                                      "All DTMF Contacts are 3 codes "
1436
                                      "(valid: 0-9 * # ABCD), "
1437
                                      "or an empty string")
1438
        val.set_mutable(False)
1439
        rs = RadioSetting("dtmf_descr1", "DTMF Contacts", val)
1440
        dtmfc.append(rs)
1441

    
1442
        for i in range(1, 17):
1443
            varname = "DTMF_"+str(i)
1444
            varnumname = "DTMFNUM_"+str(i)
1445
            vardescr = "DTMF Contact "+str(i)+" name"
1446
            varinumdescr = "DTMF Contact "+str(i)+" number"
1447

    
1448
            cntn = str(_mem.dtmfcontact[i-1].name).strip("\x20\x00\xff")
1449
            cntnum = str(_mem.dtmfcontact[i-1].number).strip("\x20\x00\xff")
1450

    
1451
            val = RadioSettingValueString(0, 8, cntn)
1452
            rs = RadioSetting(varname, vardescr, val)
1453
            dtmfc.append(rs)
1454

    
1455
            val = RadioSettingValueString(0, 3, cntnum)
1456
            val.set_charset(DTMF_CHARS)
1457
            rs = RadioSetting(varnumname, varinumdescr, val)
1458
            dtmfc.append(rs)
1459

    
1460
        # scanlists
1461
        if _mem.scanlist_default == 1:
1462
            tmpsc = 2
1463
        else:
1464
            tmpsc = 1
1465
        rs = RadioSetting("scanlist_default",
1466
                          "Default scanlist",
1467
                          RadioSettingValueInteger(1, 2, tmpsc))
1468
        scanl.append(rs)
1469

    
1470
        tmppr = bool((_mem.scanlist1_priority_scan & 1) > 0)
1471
        rs = RadioSetting(
1472
                "scanlist1_priority_scan",
1473
                "Scanlist 1 priority channel scan",
1474
                RadioSettingValueBoolean(tmppr))
1475
        scanl.append(rs)
1476

    
1477
        tmpch = _mem.scanlist1_priority_ch1 + 1
1478
        if tmpch > 200:
1479
            tmpch = 0
1480
        rs = RadioSetting("scanlist1_priority_ch1",
1481
                          "Scanlist 1 priority channel 1 (0 - off)",
1482
                          RadioSettingValueInteger(0, 200, tmpch))
1483
        scanl.append(rs)
1484

    
1485
        tmpch = _mem.scanlist1_priority_ch2 + 1
1486
        if tmpch > 200:
1487
            tmpch = 0
1488
        rs = RadioSetting("scanlist1_priority_ch2",
1489
                          "Scanlist 1 priority channel 2 (0 - off)",
1490
                          RadioSettingValueInteger(0, 200, tmpch))
1491
        scanl.append(rs)
1492

    
1493
        tmppr = bool((_mem.scanlist2_priority_scan & 1) > 0)
1494
        rs = RadioSetting(
1495
                "scanlist2_priority_scan",
1496
                "Scanlist 2 priority channel scan",
1497
                RadioSettingValueBoolean(tmppr))
1498
        scanl.append(rs)
1499

    
1500
        tmpch = _mem.scanlist2_priority_ch1 + 1
1501
        if tmpch > 200:
1502
            tmpch = 0
1503
        rs = RadioSetting("scanlist2_priority_ch1",
1504
                          "Scanlist 2 priority channel 1 (0 - off)",
1505
                          RadioSettingValueInteger(0, 200, tmpch))
1506
        scanl.append(rs)
1507

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

    
1516
        # basic settings
1517

    
1518
        # call channel
1519
        tmpc = _mem.call_channel+1
1520
        if tmpc > 200:
1521
            tmpc = 1
1522
        rs = RadioSetting("call_channel", "One key call channel",
1523
                          RadioSettingValueInteger(1, 200, tmpc))
1524
        basic.append(rs)
1525

    
1526
        # squelch
1527
        tmpsq = _mem.squelch
1528
        if tmpsq > 9:
1529
            tmpsq = 1
1530
        rs = RadioSetting("squelch", "Squelch",
1531
                          RadioSettingValueInteger(0, 9, tmpsq))
1532
        basic.append(rs)
1533

    
1534
        # TOT
1535
        tmptot = _mem.max_talk_time
1536
        if tmptot > 10:
1537
            tmptot = 10
1538
        rs = RadioSetting(
1539
                "tot",
1540
                "Max talk time [min]",
1541
                RadioSettingValueInteger(0, 10, tmptot))
1542
        basic.append(rs)
1543

    
1544
        # NOAA autoscan
1545
        rs = RadioSetting(
1546
                "noaa_autoscan",
1547
                "NOAA Autoscan", RadioSettingValueBoolean(
1548
                    bool(_mem.noaa_autoscan > 0)))
1549
        basic.append(rs)
1550

    
1551
        # VOX Level
1552
        tmpvox = _mem.vox_level+1
1553
        if tmpvox > 10:
1554
            tmpvox = 10
1555
        rs = RadioSetting("vox_level", "VOX Level",
1556
                          RadioSettingValueInteger(1, 10, tmpvox))
1557
        basic.append(rs)
1558

    
1559
        # Mic gain
1560
        tmpmicgain = _mem.mic_gain
1561
        if tmpmicgain > 4:
1562
            tmpmicgain = 4
1563
        rs = RadioSetting("mic_gain", "Mic Gain",
1564
                          RadioSettingValueInteger(0, 4, tmpmicgain))
1565
        basic.append(rs)
1566

    
1567
        # Channel display mode
1568
        tmpchdispmode = _mem.channel_display_mode
1569
        if tmpchdispmode >= len(CHANNELDISP_LIST):
1570
            tmpchdispmode = 0
1571
        rs = RadioSetting(
1572
                "channel_display_mode",
1573
                "Channel display mode",
1574
                RadioSettingValueList(
1575
                    CHANNELDISP_LIST,
1576
                    CHANNELDISP_LIST[tmpchdispmode]))
1577
        basic.append(rs)
1578

    
1579
        # Crossband receiving/transmitting
1580
        tmpcross = _mem.crossband
1581
        if tmpcross >= len(CROSSBAND_LIST):
1582
            tmpcross = 0
1583
        rs = RadioSetting(
1584
                "crossband",
1585
                "Cross-band receiving/transmitting",
1586
                RadioSettingValueList(
1587
                    CROSSBAND_LIST,
1588
                    CROSSBAND_LIST[tmpcross]))
1589
        basic.append(rs)
1590

    
1591
        # Battery save
1592
        tmpbatsave = _mem.battery_save
1593
        if tmpbatsave >= len(BATSAVE_LIST):
1594
            tmpbatsave = BATSAVE_LIST.index("1:4")
1595
        rs = RadioSetting(
1596
                "battery_save",
1597
                "Battery Save",
1598
                RadioSettingValueList(
1599
                    BATSAVE_LIST,
1600
                    BATSAVE_LIST[tmpbatsave]))
1601
        basic.append(rs)
1602

    
1603
        # Dual watch
1604
        tmpdual = _mem.dual_watch
1605
        if tmpdual >= len(DUALWATCH_LIST):
1606
            tmpdual = 0
1607
        rs = RadioSetting("dualwatch", "Dual Watch", RadioSettingValueList(
1608
            DUALWATCH_LIST, DUALWATCH_LIST[tmpdual]))
1609
        basic.append(rs)
1610

    
1611
        # Tail tone elimination
1612
        rs = RadioSetting(
1613
                "tail_note_elimination",
1614
                "Tail tone elimination",
1615
                RadioSettingValueBoolean(
1616
                    bool(_mem.tail_note_elimination > 0)))
1617
        basic.append(rs)
1618

    
1619
        # VFO open
1620
        rs = RadioSetting("vfo_open", "VFO open",
1621
                          RadioSettingValueBoolean(bool(_mem.vfo_open > 0)))
1622
        basic.append(rs)
1623

    
1624
        # Beep control
1625
        rs = RadioSetting(
1626
                "beep_control",
1627
                "Beep control",
1628
                RadioSettingValueBoolean(bool(_mem.beep_control > 0)))
1629
        basic.append(rs)
1630

    
1631
        # Scan resume mode
1632
        tmpscanres = _mem.scan_resume_mode
1633
        if tmpscanres >= len(SCANRESUME_LIST):
1634
            tmpscanres = 0
1635
        rs = RadioSetting(
1636
                "scan_resume_mode",
1637
                "Scan resume mode",
1638
                RadioSettingValueList(
1639
                    SCANRESUME_LIST,
1640
                    SCANRESUME_LIST[tmpscanres]))
1641
        basic.append(rs)
1642

    
1643
        # Auto keypad lock
1644
        rs = RadioSetting(
1645
                "auto_keypad_lock",
1646
                "Auto keypad lock",
1647
                RadioSettingValueBoolean(bool(_mem.auto_keypad_lock > 0)))
1648
        basic.append(rs)
1649

    
1650
        # Power on display mode
1651
        tmpdispmode = _mem.power_on_dispmode
1652
        if tmpdispmode >= len(WELCOME_LIST):
1653
            tmpdispmode = 0
1654
        rs = RadioSetting(
1655
                "welcome_mode",
1656
                "Power on display mode",
1657
                RadioSettingValueList(
1658
                    WELCOME_LIST,
1659
                    WELCOME_LIST[tmpdispmode]))
1660
        basic.append(rs)
1661

    
1662
        # Keypad Tone
1663
        tmpkeypadtone = _mem.keypad_tone
1664
        if tmpkeypadtone >= len(KEYPADTONE_LIST):
1665
            tmpkeypadtone = 0
1666
        rs = RadioSetting("keypad_tone", "Keypad tone", RadioSettingValueList(
1667
            KEYPADTONE_LIST, KEYPADTONE_LIST[tmpkeypadtone]))
1668
        basic.append(rs)
1669

    
1670
        # Language
1671
        tmplanguage = _mem.language
1672
        if tmplanguage >= len(LANGUAGE_LIST):
1673
            tmplanguage = 0
1674
        rs = RadioSetting("language", "Language", RadioSettingValueList(
1675
            LANGUAGE_LIST, LANGUAGE_LIST[tmplanguage]))
1676
        basic.append(rs)
1677

    
1678
        # Alarm mode
1679
        tmpalarmmode = _mem.alarm_mode
1680
        if tmpalarmmode >= len(ALARMMODE_LIST):
1681
            tmpalarmmode = 0
1682
        rs = RadioSetting("alarm_mode", "Alarm mode", RadioSettingValueList(
1683
            ALARMMODE_LIST, ALARMMODE_LIST[tmpalarmmode]))
1684
        basic.append(rs)
1685

    
1686
        # Reminding of end of talk
1687
        tmpalarmmode = _mem.reminding_of_end_talk
1688
        if tmpalarmmode >= len(REMENDOFTALK_LIST):
1689
            tmpalarmmode = 0
1690
        rs = RadioSetting(
1691
                "reminding_of_end_talk",
1692
                "Reminding of end of talk",
1693
                RadioSettingValueList(
1694
                    REMENDOFTALK_LIST,
1695
                    REMENDOFTALK_LIST[tmpalarmmode]))
1696
        basic.append(rs)
1697

    
1698
        # Repeater tail tone elimination
1699
        tmprte = _mem.repeater_tail_elimination
1700
        if tmprte >= len(RTE_LIST):
1701
            tmprte = 0
1702
        rs = RadioSetting(
1703
                "repeater_tail_elimination",
1704
                "Repeater tail tone elimination",
1705
                RadioSettingValueList(RTE_LIST, RTE_LIST[tmprte]))
1706
        basic.append(rs)
1707

    
1708
        # Logo string 1
1709
        logo1 = str(_mem.logo_line1).strip("\x20\x00\xff") + "\x00"
1710
        logo1 = _getstring(logo1.encode('ascii', errors='ignore'), 0, 12)
1711
        rs = RadioSetting("logo1", "Logo string 1 (12 characters)",
1712
                          RadioSettingValueString(0, 12, logo1))
1713
        basic.append(rs)
1714

    
1715
        # Logo string 2
1716
        logo2 = str(_mem.logo_line2).strip("\x20\x00\xff") + "\x00"
1717
        logo2 = _getstring(logo2.encode('ascii', errors='ignore'), 0, 12)
1718
        rs = RadioSetting("logo2", "Logo string 2 (12 characters)",
1719
                          RadioSettingValueString(0, 12, logo2))
1720
        basic.append(rs)
1721

    
1722
        # FM radio
1723
        for i in range(1, 21):
1724
            freqname = "FM_"+str(i)
1725
            fmfreq = _mem.fmfreq[i-1]/10.0
1726
            if fmfreq < FMMIN or fmfreq > FMMAX:
1727
                rs = RadioSetting(freqname, freqname,
1728
                                  RadioSettingValueString(0, 5, ""))
1729
            else:
1730
                rs = RadioSetting(freqname, freqname,
1731
                                  RadioSettingValueString(0, 5, str(fmfreq)))
1732

    
1733
            fmradio.append(rs)
1734

    
1735
        # unlock settings
1736

    
1737
        # F-LOCK
1738
        tmpflock = _mem.int_flock
1739
        if tmpflock >= len(FLOCK_LIST):
1740
            tmpflock = 0
1741
        rs = RadioSetting(
1742
            "flock", "F-LOCK",
1743
            RadioSettingValueList(FLOCK_LIST, FLOCK_LIST[tmpflock]))
1744
        unlock.append(rs)
1745

    
1746
        # 350TX
1747
        rs = RadioSetting("350tx", "350TX", RadioSettingValueBoolean(
1748
            bool(_mem.int_350tx > 0)))
1749
        unlock.append(rs)
1750

    
1751
        # unknown1
1752
        rs = RadioSetting("unknown11", "UNKNOWN1",
1753
                          RadioSettingValueBoolean(
1754
                              bool(_mem.int_unknown1 > 0)))
1755
        unlock.append(rs)
1756

    
1757
        # 200TX
1758
        rs = RadioSetting("200tx", "200TX", RadioSettingValueBoolean(
1759
            bool(_mem.int_200tx > 0)))
1760
        unlock.append(rs)
1761

    
1762
        # 500TX
1763
        rs = RadioSetting("500tx", "500TX", RadioSettingValueBoolean(
1764
            bool(_mem.int_500tx > 0)))
1765
        unlock.append(rs)
1766

    
1767
        # 350EN
1768
        rs = RadioSetting("350en", "350EN", RadioSettingValueBoolean(
1769
            bool(_mem.int_350en > 0)))
1770
        unlock.append(rs)
1771

    
1772
        # SCREEN
1773
        rs = RadioSetting("screen", "SCREEN", RadioSettingValueBoolean(
1774
            bool(_mem.int_screen > 0)))
1775
        unlock.append(rs)
1776

    
1777
        # readonly info
1778
        # Firmware
1779
        if self.FIRMWARE_VERSION == "":
1780
            firmware = "To get the firmware version please download"
1781
            "the image from the radio first"
1782
        else:
1783
            firmware = self.FIRMWARE_VERSION
1784

    
1785
        val = RadioSettingValueString(0, 128, firmware)
1786
        val.set_mutable(False)
1787
        rs = RadioSetting("fw_ver", "Firmware Version", val)
1788
        roinfo.append(rs)
1789

    
1790
        # Driver version
1791
        val = RadioSettingValueString(0, 128, DRIVER_VERSION)
1792
        val.set_mutable(False)
1793
        rs = RadioSetting("driver_ver", "Driver version", val)
1794
        roinfo.append(rs)
1795

    
1796
        # No limits version for hacked firmware
1797
        val = RadioSettingValueBoolean(self.FIRMWARE_NOLIMITS)
1798
        val.set_mutable(False)
1799
        rs = RadioSetting("nolimits", "Limits disabled for modified firmware",
1800
                          val)
1801
        roinfo.append(rs)
1802

    
1803
        return top
1804

    
1805
    # Store details about a high-level memory to the memory map
1806
    # This is called when a user edits a memory in the UI
1807
    def set_memory(self, mem):
1808
        number = mem.number-1
1809

    
1810
        # Get a low-level memory object mapped to the image
1811
        _mem = self._memobj.channel[number]
1812
        _mem4 = self._memobj
1813
        # empty memory
1814
        if mem.empty:
1815
            _mem.set_raw("\xFF" * 16)
1816
            if number < 200:
1817
                _mem2 = self._memobj.channelname[number]
1818
                _mem2.set_raw("\xFF" * 16)
1819
                _mem4.channel_attributes[number].is_scanlist1 = 0
1820
                _mem4.channel_attributes[number].is_scanlist2 = 0
1821
                _mem4.channel_attributes[number].unknown1 = 0
1822
                _mem4.channel_attributes[number].unknown2 = 0
1823
                _mem4.channel_attributes[number].is_free = 1
1824
                _mem4.channel_attributes[number].band = 0x7
1825
            return mem
1826

    
1827
        # clean the channel memory, restore some bits if it was used before
1828
        if _mem.get_raw()[0] == "\xff":
1829
            # this was an empty memory
1830
            _mem.set_raw("\x00" * 16)
1831
        else:
1832
            # this memory was't empty, save some bits that we don't know the
1833
            # meaning of, or that we don't support yet
1834
            prev_0a = ord(_mem.get_raw()[0x0a]) & SAVE_MASK_0A
1835
            prev_0b = ord(_mem.get_raw()[0x0b]) & SAVE_MASK_0B
1836
            prev_0c = ord(_mem.get_raw()[0x0c]) & SAVE_MASK_0C
1837
            prev_0d = ord(_mem.get_raw()[0x0d]) & SAVE_MASK_0D
1838
            prev_0e = ord(_mem.get_raw()[0x0e]) & SAVE_MASK_0E
1839
            prev_0f = ord(_mem.get_raw()[0x0f]) & SAVE_MASK_0F
1840
            _mem.set_raw("\x00" * 10 +
1841
                         chr(prev_0a) + chr(prev_0b) + chr(prev_0c) +
1842
                         chr(prev_0d) + chr(prev_0e) + chr(prev_0f))
1843

    
1844
        if number < 200:
1845
            _mem4.channel_attributes[number].is_scanlist1 = 0
1846
            _mem4.channel_attributes[number].is_scanlist2 = 0
1847
            _mem4.channel_attributes[number].unknown1 = 0
1848
            _mem4.channel_attributes[number].unknown2 = 0
1849
            _mem4.channel_attributes[number].is_free = 1
1850
            _mem4.channel_attributes[number].band = 0x7
1851

    
1852
        # find tx frequency
1853
        if mem.duplex == '-':
1854
            txfreq = mem.freq - mem.offset
1855
        elif mem.duplex == '+':
1856
            txfreq = mem.freq + mem.offset
1857
        else:
1858
            txfreq = mem.freq
1859

    
1860
        # find band
1861
        band = _find_band(self, txfreq)
1862
        if band is False:
1863
            raise errors.RadioError(
1864
                    "Transmit frequency %.4fMHz is not supported by this radio"
1865
                    % txfreq/1000000.0)
1866

    
1867
        band = _find_band(self, mem.freq)
1868
        if band is False:
1869
            return mem
1870

    
1871
        # mode
1872
        if mem.mode == "NFM":
1873
            _mem.bandwidth = 1
1874
            _mem.enable_am = 0
1875
        elif mem.mode == "FM":
1876
            _mem.bandwidth = 0
1877
            _mem.enable_am = 0
1878
        elif mem.mode == "NAM":
1879
            _mem.bandwidth = 1
1880
            _mem.enable_am = 1
1881
        elif mem.mode == "AM":
1882
            _mem.bandwidth = 0
1883
            _mem.enable_am = 1
1884

    
1885
        # frequency/offset
1886
        _mem.freq = mem.freq/10
1887
        _mem.offset = mem.offset/10
1888

    
1889
        if mem.duplex == "off" or mem.duplex == "":
1890
            _mem.offset = 0
1891
            _mem.shift = 0
1892
        elif mem.duplex == '-':
1893
            _mem.shift = FLAGS1_OFFSET_MINUS
1894
        elif mem.duplex == '+':
1895
            _mem.shift = FLAGS1_OFFSET_PLUS
1896

    
1897
        # set band
1898
        if number < 200:
1899
            _mem4.channel_attributes[number].is_free = 0
1900
            _mem4.channel_attributes[number].band = band
1901

    
1902
        # channels >200 are the 14 VFO chanells and don't have names
1903
        if number < 200:
1904
            _mem2 = self._memobj.channelname[number]
1905
            tag = mem.name.ljust(16)[:16]
1906
            _mem2.name = tag  # Store the alpha tag
1907

    
1908
        # tone data
1909
        self._set_tone(mem, _mem)
1910

    
1911
        # step
1912
        _mem.step = STEPS.index(mem.tuning_step)
1913

    
1914
        # tx power
1915
        if str(mem.power) == str(UVK5_POWER_LEVELS[2]):
1916
            _mem.txpower = POWER_HIGH
1917
        elif str(mem.power) == str(UVK5_POWER_LEVELS[1]):
1918
            _mem.txpower = POWER_MEDIUM
1919
        else:
1920
            _mem.txpower = POWER_LOW
1921

    
1922
        for setting in mem.extra:
1923
            sname = setting.get_name()
1924
            svalue = setting.value.get_value()
1925

    
1926
            if sname == "bclo":
1927
                _mem.bclo = svalue and 1 or 0
1928

    
1929
            if sname == "pttid":
1930
                _mem.dtmf_pttid = PTTID_LIST.index(svalue)
1931

    
1932
            if sname == "frev":
1933
                _mem.freq_reverse = svalue and 1 or 0
1934

    
1935
            if sname == "dtmfdecode":
1936
                _mem.dtmf_decode = svalue and 1 or 0
1937

    
1938
            if sname == "scrambler":
1939
                _mem.scrambler = (
1940
                    _mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue)
1941

    
1942
            if number < 200 and sname == "scanlists":
1943
                if svalue == "1":
1944
                    _mem4.channel_attributes[number].is_scanlist1 = 1
1945
                    _mem4.channel_attributes[number].is_scanlist2 = 0
1946
                elif svalue == "2":
1947
                    _mem4.channel_attributes[number].is_scanlist1 = 0
1948
                    _mem4.channel_attributes[number].is_scanlist2 = 1
1949
                elif svalue == "1+2":
1950
                    _mem4.channel_attributes[number].is_scanlist1 = 1
1951
                    _mem4.channel_attributes[number].is_scanlist2 = 1
1952
                else:
1953
                    _mem4.channel_attributes[number].is_scanlist1 = 0
1954
                    _mem4.channel_attributes[number].is_scanlist2 = 0
1955

    
1956
        return mem
1957

    
1958

    
1959
@directory.register
1960
class UVK5Radio_nolimit(UVK5Radio):
1961
    VENDOR = "Quansheng"
1962
    MODEL = "UV-K5 (modified firmware)"
1963
    VARIANT = "nolimits"
1964
    FIRMWARE_NOLIMITS = True
1965

    
1966
    def get_features(self):
1967
        rf = UVK5Radio.get_features(self)
1968
        # This is what the BK4819 chip supports
1969
        rf.valid_bands = [(18000000,  620000000),
1970
                          (840000000, 1300000000)
1971
                          ]
1972
        return rf
(44-44/47)