Project

General

Profile

New Model #10478 » uvk5.py

uvk5.py chirp driver for UV-K5, version 20230619_2 - Jacek Lipkowski SQ5BPF, 06/19/2023 01:57 PM

 
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 v20230619 (c) Jacek Lipkowski SQ5BPF"
51
PRINT_CONSOLE = False
52

    
53
MEM_FORMAT = """
54
#seekto 0x0000;
55
struct {
56
  ul32 freq;
57
  ul32 offset;
58
  u8 rxcode;
59
  u8 txcode;
60
  u8 code_flag;
61
  u8 flags1;
62
  u8 flags2;
63
  u8 dtmf_flags;
64
  u8 step;
65
  u8 scrambler;
66
} channel[214];
67

    
68
#seekto 0xd60;
69
struct {
70
u8 is_scanlist1:1,
71
is_scanlist2:1,
72
unknown1:1,
73
unknown2:1,
74
is_free:1,
75
band:3;
76
} channel_attributes[200];
77

    
78
#seekto 0xe40;
79
ul16 fmfreq[20];
80

    
81
#seekto 0xe70;
82
u8 call_channel;
83
u8 squelch;
84
u8 max_talk_time;
85
u8 noaa_autoscan;
86
u8 unknown1;
87
u8 unknown2;
88
u8 vox_level;
89
u8 mic_gain;
90
u8 unknown3;
91
u8 channel_display_mode;
92
u8 crossband;
93
u8 battery_save;
94
u8 dual_watch;
95
u8 tail_note_elimination;
96
u8 vfo_open;
97

    
98
#seekto 0xe90;
99
u8 beep_control;
100
u8 key1_shortpress_action;
101
u8 key1_longpress_action;
102
u8 key2_shortpress_action;
103
u8 key2_longpress_action;
104
u8 scan_resume_mode;
105
u8 auto_keypad_lock;
106
u8 power_on_dispmode;
107
u8 password[4];
108

    
109
#seekto 0xea0;
110
u8 keypad_tone;
111
u8 language;
112

    
113
#seekto 0xea8;
114
u8 alarm_mode;
115
u8 reminding_of_end_talk;
116
u8 repeater_tail_elimination;
117

    
118
#seekto 0xeb0;
119
char logo_line1[16];
120
char logo_line2[16];
121

    
122
#seekto 0xed0;
123
struct {
124
u8 side_tone;
125
char separate_code;
126
char group_call_code;
127
u8 decode_response;
128
u8 auto_reset_time;
129
u8 preload_time;
130
u8 first_code_persist_time;
131
u8 hash_persist_time;
132
u8 code_persist_time;
133
u8 code_interval_time;
134
u8 permit_remote_kill;
135
} dtmf_settings;
136

    
137
#seekto 0xee0;
138
struct {
139
char dtmf_local_code[3];
140
char unused1[5];
141
char kill_code[5];
142
char unused2[3];
143
char revive_code[5];
144
char unused3[3];
145
char dtmf_up_code[16];
146
char dtmf_down_code[16];
147
} dtmf_settings_numbers;
148

    
149
#seekto 0xf18;
150
u8 scanlist_default;
151
u8 scanlist1_priority_scan;
152
u8 scanlist1_priority_ch1;
153
u8 scanlist1_priority_ch2;
154
u8 scanlist2_priority_scan;
155
u8 scanlist2_priority_ch1;
156
u8 scanlist2_priority_ch2;
157
u8 scanlist_unknown_0xff;
158

    
159

    
160
#seekto 0xf40;
161
u8 int_flock;
162
u8 int_350tx;
163
u8 int_unknown1;
164
u8 int_200tx;
165
u8 int_500tx;
166
u8 int_350en;
167
u8 int_screen;
168

    
169
#seekto 0xf50;
170
struct {
171
char name[16];
172
} channelname[200];
173

    
174
#seekto 0x1c00;
175
struct {
176
char name[8];
177
char number[3];
178
char unused_00[5];
179
} dtmfcontact[16];
180
"""
181
# bits that we will save from the channel structure (mostly unknown)
182
SAVE_MASK_0A = 0b11001100
183
SAVE_MASK_0B = 0b11101100
184
SAVE_MASK_0C = 0b11100000
185
SAVE_MASK_0D = 0b11111000
186
SAVE_MASK_0E = 0b11110001
187
SAVE_MASK_0F = 0b11110000
188

    
189
# flags1
190
FLAGS1_OFFSET_MASK = 0b11
191
FLAGS1_OFFSET_NONE = 0b00
192
FLAGS1_OFFSET_MINUS = 0b10
193
FLAGS1_OFFSET_PLUS = 0b01
194

    
195
FLAGS1_ISSCANLIST = 0b100
196
FLAGS1_ISAM = 0b10000
197

    
198
# flags2
199
FLAGS2_BCLO = 0b10000
200
FLAGS2_POWER_MASK = 0b1100
201
FLAGS2_POWER_HIGH = 0b1000
202
FLAGS2_POWER_MEDIUM = 0b0100
203
FLAGS2_POWER_LOW = 0b0000
204
FLAGS2_BANDWIDTH = 0b10
205
FLAGS2_REVERSE = 0b1
206

    
207
# dtmf_flags
208
PTTID_LIST = ["off", "BOT", "EOT", "BOTH"]
209
FLAGS_DTMF_PTTID_MASK = 0b110  # PTTID: 00-disabled, 01-BOT, 10-EOT, 11-BOTH
210
FLAGS_DTMF_PTTID_DISABLED = 0b000
211
FLAGS_DTMF_PTTID_BOT = 0b010
212
FLAGS_DTMF_PTTID_EOT = 0b100
213
FLAGS_DTMF_PTTID_BOTH = 0b110
214
FLAGS_DTMF_DECODE = 0b1
215

    
216
# power
217
UVK5_POWER_LEVELS = [chirp_common.PowerLevel("Low",  watts=1.50),
218
                     chirp_common.PowerLevel("Med",  watts=3.00),
219
                     chirp_common.PowerLevel("High", watts=5.00),
220
                     ]
221

    
222
# scrambler
223
SCRAMBLER_LIST = ["off", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
224

    
225
# channel display mode
226
CHANNELDISP_LIST = ["Frequency", "Channel No", "Channel Name"]
227
# battery save
228
BATSAVE_LIST = ["OFF", "1:1", "1:2", "1:3", "1:4"]
229

    
230
# Crossband receiving/transmitting
231
CROSSBAND_LIST = ["Off", "Band A", "Band B"]
232
DUALWATCH_LIST = CROSSBAND_LIST
233

    
234
# steps
235
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 8.33]
236

    
237
# ctcss/dcs codes
238
TMODES = ["", "Tone", "DTCS", "DTCS"]
239
TONE_NONE = 0
240
TONE_CTCSS = 1
241
TONE_DCS = 2
242
TONE_RDCS = 3
243

    
244

    
245
CTCSS_TONES = [
246
    67.0, 69.3, 71.9, 74.4, 77.0, 79.7, 82.5, 85.4,
247
    88.5, 91.5, 94.8, 97.4, 100.0, 103.5, 107.2, 110.9,
248
    114.8, 118.8, 123.0, 127.3, 131.8, 136.5, 141.3, 146.2,
249
    151.4, 156.7, 159.8, 162.2, 165.5, 167.9, 171.3, 173.8,
250
    177.3, 179.9, 183.5, 186.2, 189.9, 192.8, 196.6, 199.5,
251
    203.5, 206.5, 210.7, 218.1, 225.7, 229.1, 233.6, 241.8,
252
    250.3, 254.1
253
]
254

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

    
269
FLOCK_LIST = ["Off", "FCC", "CE", "GB", "430", "438"]
270
SCANRESUME_LIST = ["TO: Resume after 5 seconds",
271
                   "CO: Resume after signal dissapears",
272
                   "SE: Stop scanning after receiving a signal"]
273
WELCOME_LIST = ["Full Screen", "Welcome Info", "Voltage"]
274
KEYPADTONE_LIST = ["Off", "Chinese", "English"]
275
LANGUAGE_LIST = ["Chinese", "English"]
276
ALARMMODE_LIST = ["SITE", "TONE"]
277
REMENDOFTALK_LIST = ["Off", "ROGER", "MDC"]
278
RTE_LIST = ["Off", "100ms", "200ms", "300ms", "400ms",
279
            "500ms", "600ms", "700ms", "800ms", "900ms"]
280

    
281
MEM_SIZE = 0x2000  # size of all memory
282
PROG_SIZE = 0x1d00  # size of the memory that we will write
283
MEM_BLOCK = 0x80  # largest block of memory that we can reliably write
284

    
285
# fm radio supported frequencies
286
FMMIN = 76.0
287
FMMAX = 108.0
288

    
289
# bands supported by the UV-K5
290
BANDS = {
291
        0: [50.0, 76.0],
292
        1: [108.0, 135.9999],
293
        2: [136.0, 199.9990],
294
        3: [200.0, 299.9999],
295
        4: [350.0, 399.9999],
296
        5: [400.0, 469.9999],
297
        6: [470.0, 600.0]
298
        }
299

    
300
# for radios with modified firmware:
301
BANDS_NOLIMITS = {
302
        0: [18.0, 76.0],
303
        1: [108.0, 135.9999],
304
        2: [136.0, 199.9990],
305
        3: [200.0, 299.9999],
306
        4: [350.0, 399.9999],
307
        5: [400.0, 469.9999],
308
        6: [470.0, 1300.0]
309
        }
310
BANDMASK = 0b1111
311

    
312
VFO_CHANNEL_NAMES = ["F1(50M-76M)A", "F1(50M-76M)B",
313
                     "F2(108M-136M)A", "F2(108M-136M)B",
314
                     "F3(136M-174M)A", "F3(136M-174M)B",
315
                     "F4(174M-350M)A", "F4(174M-350M)B",
316
                     "F5(350M-400M)A", "F5(350M-400M)B",
317
                     "F6(400M-470M)A", "F6(400M-470M)B",
318
                     "F7(470M-600M)A", "F7(470M-600M)B"]
319

    
320
SCANLIST_LIST = ["None", "1", "2", "1+2"]
321

    
322
DTMF_CHARS = "0123456789ABCD*# "
323
DTMF_CHARS_ID = "0123456789ABCDabcd"
324
DTMF_CHARS_KILL = "0123456789ABCDabcd"
325
DTMF_CHARS_UPDOWN = "0123456789ABCDabcd#* "
326
DTMF_CODE_CHARS = "ABCD*# "
327
DTMF_DECODE_RESPONSE_LIST = ["None", "Ring", "Reply", "Both"]
328

    
329
KEYACTIONS_LIST = ["None", "Flashlight on/off", "Power select",
330
                   "Monitor", "Scan on/off", "VOX on/off",
331
                   "Alarm on/off", "FM radio on/off", "Transmit 1750Hz"]
332

    
333

    
334
# the communication is obfuscated using this fine mechanism
335
def xorarr(data: bytes):
336
    tbl = [22, 108, 20, 230, 46, 145, 13, 64, 33, 53, 213, 64, 19, 3, 233, 128]
337
    x = b""
338
    r = 0
339
    for byte in data:
340
        x += bytes([byte ^ tbl[r]])
341
        r = (r+1) % len(tbl)
342
    return x
343

    
344

    
345
# if this crc was used for communication to AND from the radio, then it
346
# would be a measure to increase reliability.
347
# but it's only used towards the radio, so it's for further obfuscation
348
def calculate_crc16_xmodem(data: bytes):
349
    poly = 0x1021
350
    crc = 0x0
351
    for byte in data:
352
        crc = crc ^ (byte << 8)
353
        for i in range(8):
354
            crc = crc << 1
355
            if (crc & 0x10000):
356
                crc = (crc ^ poly) & 0xFFFF
357
    return crc & 0xFFFF
358

    
359

    
360
def _send_command(serport, data: bytes):
361
    """Send a command to UV-K5 radio"""
362
    LOG.debug("Sending command (unobfuscated) len=0x%4.4x:\n%s" %
363
              (len(data), util.hexprint(data)))
364

    
365
    crc = calculate_crc16_xmodem(data)
366
    data2 = data + struct.pack("<H", crc)
367

    
368
    command = struct.pack(">HBB", 0xabcd, len(data), 0) + \
369
        xorarr(data2) + \
370
        struct.pack(">H", 0xdcba)
371
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
372
        LOG.debug("Sending command (obfuscated):\n%s" % util.hexprint(command))
373
    try:
374
        result = serport.write(command)
375
    except Exception:
376
        raise errors.RadioError("Error writing data to radio")
377
    return result
378

    
379

    
380
def _receive_reply(serport):
381
    header = serport.read(4)
382
    if len(header) != 4:
383
        LOG.warning("Header short read: [%s] len=%i" %
384
                    (util.hexprint(header), len(header)))
385
        raise errors.RadioError("Header short read")
386
    if header[0] != 0xAB or header[1] != 0xCD or header[3] != 0x00:
387
        LOG.warning("Bad response header: %s len=%i" %
388
                    (util.hexprint(header), len(header)))
389
        raise errors.RadioError("Bad response header")
390

    
391
        return False
392

    
393
    cmd = serport.read(int(header[2]))
394
    if len(cmd) != int(header[2]):
395
        LOG.warning("Body short read: [%s] len=%i" %
396
                    (util.hexprint(cmd), len(cmd)))
397
        raise errors.RadioError("Command body short read")
398

    
399
    footer = serport.read(4)
400

    
401
    if len(footer) != 4:
402
        LOG.warning("Footer short read: [%s] len=%i" %
403
                    (util.hexprint(footer), len(footer)))
404
        raise errors.RadioError("Footer short read")
405

    
406
    if footer[2] != 0xDC or footer[3] != 0xBA:
407
        LOG.debug(
408
                "Reply before bad response footer (obfuscated)"
409
                "len=0x%4.4x:\n%s" % (len(cmd), util.hexprint(cmd)))
410
        LOG.warning("Bad response footer: %s len=%i" %
411
                    (util.hexprint(footer), len(footer)))
412
        raise errors.RadioError("Bad response footer")
413
        return False
414

    
415
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
416
        LOG.debug("Received reply (obfuscated) len=0x%4.4x:\n%s" %
417
                  (len(cmd), util.hexprint(cmd)))
418

    
419
    cmd2 = xorarr(cmd)
420

    
421
    LOG.debug("Received reply (unobfuscated) len=0x%4.4x:\n%s" %
422
              (len(cmd2), util.hexprint(cmd2)))
423

    
424
    return cmd2
425

    
426

    
427
def _getstring(data: bytes, begin, maxlen):
428
    tmplen = min(maxlen+1, len(data))
429
    s = [data[i] for i in range(begin, tmplen)]
430
    for key, val in enumerate(s):
431
        if val < ord(' ') or val > ord('~'):
432
            break
433
    return ''.join(chr(x) for x in s[0:key])
434

    
435

    
436
def _sayhello(serport):
437
    hellopacket = b"\x14\x05\x04\x00\x6a\x39\x57\x64"
438

    
439
    tries = 5
440
    while (True):
441
        LOG.debug("Sending hello packet")
442
        _send_command(serport, hellopacket)
443
        o = _receive_reply(serport)
444
        if (o):
445
            break
446
        tries -= 1
447
        if tries == 0:
448
            LOG.warning("Failed to initialise radio")
449
            raise errors.RadioError("Failed to initialize radio")
450
            return False
451
    firmware = _getstring(o, 4, 16)
452
    LOG.info("Found firmware: %s" % firmware)
453
    return firmware
454

    
455

    
456
def _readmem(serport, offset, length):
457
    LOG.debug("Sending readmem offset=0x%4.4x len=0x%4.4x" % (offset, length))
458

    
459
    readmem = b"\x1b\x05\x08\x00" + \
460
        struct.pack("<HBB", offset, length, 0) + \
461
        b"\x6a\x39\x57\x64"
462
    _send_command(serport, readmem)
463
    o = _receive_reply(serport)
464
    if DEBUG_SHOW_MEMORY_ACTIONS:
465
        LOG.debug("readmem Received data len=0x%4.4x:\n%s" %
466
                  (len(o), util.hexprint(o)))
467
    return o[8:]
468

    
469

    
470
def _writemem(serport, data, offset):
471
    LOG.debug("Sending writemem offset=0x%4.4x len=0x%4.4x" %
472
              (offset, len(data)))
473

    
474
    if DEBUG_SHOW_MEMORY_ACTIONS:
475
        LOG.debug("writemem sent data offset=0x%4.4x len=0x%4.4x:\n%s" %
476
                  (offset, len(data), util.hexprint(data)))
477

    
478
    dlen = len(data)
479
    writemem = b"\x1d\x05" + \
480
        struct.pack("<BBHBB", dlen+8, 0, offset, dlen, 1) + \
481
        b"\x6a\x39\x57\x64"+data
482

    
483
    _send_command(serport, writemem)
484
    o = _receive_reply(serport)
485

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

    
488
    if (o[0] == 0x1e
489
            and
490
            o[4] == (offset & 0xff)
491
            and
492
            o[5] == (offset >> 8) & 0xff):
493
        return True
494
    else:
495
        LOG.warning("Bad data from writemem")
496
        raise errors.RadioError("Bad response to writemem")
497
    return False
498

    
499

    
500
def _resetradio(serport):
501
    resetpacket = b"\xdd\x05\x00\x00"
502
    _send_command(serport, resetpacket)
503

    
504

    
505
def do_download(radio):
506
    serport = radio.pipe
507
    serport.timeout = 0.5
508
    status = chirp_common.Status()
509
    status.cur = 0
510
    status.max = MEM_SIZE
511
    status.msg = "Downloading from radio"
512
    radio.status_fn(status)
513

    
514
    eeprom = b""
515
    f = _sayhello(serport)
516
    if f:
517
        radio.FIRMWARE_VERSION = f
518
    else:
519
        return False
520

    
521
    addr = 0
522
    while addr < MEM_SIZE:
523
        o = _readmem(serport, addr, MEM_BLOCK)
524
        status.cur = addr
525
        radio.status_fn(status)
526

    
527
        if o and len(o) == MEM_BLOCK:
528
            eeprom += o
529
            addr += MEM_BLOCK
530
        else:
531
            raise errors.RadioError("Memory download incomplete")
532

    
533
    return memmap.MemoryMapBytes(eeprom)
534

    
535

    
536
def do_upload(radio):
537
    serport = radio.pipe
538
    serport.timeout = 0.5
539
    status = chirp_common.Status()
540
    status.cur = 0
541
    status.max = PROG_SIZE
542
    status.msg = "Uploading to radio"
543
    radio.status_fn(status)
544

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

    
551
    addr = 0
552
    while addr < PROG_SIZE:
553
        o = radio.get_mmap()[addr:addr+MEM_BLOCK]
554
        _writemem(serport, o, addr)
555
        status.cur = addr
556
        radio.status_fn(status)
557
        if o:
558
            addr += MEM_BLOCK
559
        else:
560
            raise errors.RadioError("Memory upload incomplete")
561
    status.msg = "Uploaded OK"
562

    
563
    _resetradio(serport)
564

    
565
    return True
566

    
567

    
568
def _find_band(self, hz):
569
    mhz = hz/1000000.0
570
    if self.FIRMWARE_NOLIMITS:
571
        B = BANDS_NOLIMITS
572
    else:
573
        B = BANDS
574

    
575
    # currently the hacked firmware sets band=1 below 50MHz
576
    if self.FIRMWARE_NOLIMITS and mhz < 50.0:
577
        return 1
578

    
579
    for a in B:
580
        if mhz >= B[a][0] and mhz <= B[a][1]:
581
            return a
582
    return False
583

    
584

    
585
@directory.register
586
class UVK5Radio(chirp_common.CloneModeRadio):
587
    """Quansheng UV-K5"""
588
    VENDOR = "Quansheng"
589
    MODEL = "UV-K5"
590
    BAUD_RATE = 38400
591
    NEEDS_COMPAT_SERIAL = False
592
    FIRMWARE_VERSION = ""
593
    FIRMWARE_NOLIMITS = False
594

    
595
    def get_prompts(x=None):
596
        rp = chirp_common.RadioPrompts()
597
        rp.experimental = \
598
            ('This is an experimental driver for the Quanscheng UV-K5. '
599
             'It may harm your radio, or worse. Use at your own risk.\n\n'
600
             'Before attempting to do any changes please download'
601
             'the memory image from the radio with chirp or k5prog '
602
             'and keep it. This can be later used to recover the '
603
             'original settings. \n\n'
604
             'some details are not yet implemented')
605
        rp.pre_download = _(
606
            "1. Turn radio on.\n"
607
            "2. Connect cable to mic/spkr connector.\n"
608
            "3. Make sure connector is firmly connected.\n"
609
            "4. Click OK to download image from device.\n\n"
610
            "It will may not work if you turn o the radio "
611
            "with the cable already attached\n")
612
        rp.pre_upload = _(
613
            "1. Turn radio on.\n"
614
            "2. Connect cable to mic/spkr connector.\n"
615
            "3. Make sure connector is firmly connected.\n"
616
            "4. Click OK to upload the image to device.\n\n"
617
            "It will may not work if you turn o the radio "
618
            "with the cable already attached")
619
        return rp
620

    
621
    # Return information about this radio's features, including
622
    # how many memories it has, what bands it supports, etc
623
    def get_features(self):
624
        rf = chirp_common.RadioFeatures()
625
        rf.has_bank = False
626
        rf.valid_dtcs_codes = DTCS_CODES
627
        rf.has_rx_dtcs = True
628
        rf.has_ctone = True
629
        rf.has_settings = True
630
        rf.has_comment = False
631
        rf.valid_name_length = 16
632
        rf.valid_power_levels = UVK5_POWER_LEVELS
633

    
634
        # hack so we can input any frequency,
635
        # the 0.1 and 0.01 steps don't work unfortunately
636
        rf.valid_tuning_steps = [0.01, 0.1, 1.0] + STEPS
637

    
638
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
639
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
640
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
641

    
642
        rf.valid_characters = chirp_common.CHARSET_ASCII
643
        rf.valid_modes = ["FM", "NFM", "AM", "NAM"]
644
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
645

    
646
        rf.valid_skips = [""]
647

    
648
        # This radio supports memories 1-200, 201-214 are the VFO memories
649
        rf.memory_bounds = (1, 214)
650

    
651
        # This is what the BK4819 chip supports
652
        # Will leave it in a comment, might be useful someday
653
        # rf.valid_bands = [(18000000,  620000000),
654
        #                  (840000000, 1300000000)
655
        #                  ]
656
        rf.valid_bands = []
657
        for a in BANDS:
658
            rf.valid_bands.append(
659
                    (int(BANDS[a][0]*1000000), int(BANDS[a][1]*1000000)))
660
        return rf
661

    
662
    # Do a download of the radio from the serial port
663
    def sync_in(self):
664
        self._mmap = do_download(self)
665
        self.process_mmap()
666

    
667
    # Do an upload of the radio to the serial port
668
    def sync_out(self):
669
        do_upload(self)
670

    
671
    # Convert the raw byte array into a memory object structure
672
    def process_mmap(self):
673
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
674

    
675
    # Return a raw representation of the memory object, which
676
    # is very helpful for development
677
    def get_raw_memory(self, number):
678
        return repr(self._memobj.channel[number-1])
679

    
680
    def validate_memory(self, mem):
681
        msgs = super().validate_memory(mem)
682
        return msgs
683

    
684
    def _set_tone(self, mem, _mem):
685
        ((txmode, txtone, txpol),
686
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
687

    
688
        if txmode == "Tone":
689
            txtoval = CTCSS_TONES.index(txtone)
690
            txmoval = 0b01
691
        elif txmode == "DTCS":
692
            txmoval = txpol == "R" and 0b11 or 0b10
693
            txtoval = DTCS_CODES.index(txtone)
694
        else:
695
            txmoval = 0
696
            txtoval = 0
697

    
698
        if rxmode == "Tone":
699
            rxtoval = CTCSS_TONES.index(rxtone)
700
            rxmoval = 0b01
701
        elif rxmode == "DTCS":
702
            rxmoval = rxpol == "R" and 0b11 or 0b10
703
            rxtoval = DTCS_CODES.index(rxtone)
704
        else:
705
            rxmoval = 0
706
            rxtoval = 0
707

    
708
        _mem.code_flag = (_mem.code_flag & 0b11001100) | (
709
            txmoval << 4) | rxmoval
710
        _mem.rxcode = rxtoval
711
        _mem.txcode = txtoval
712

    
713
    def _get_tone(self, mem, _mem):
714
        rxtype = _mem.code_flag & 0x03
715
        txtype = (_mem.code_flag >> 4) & 0x03
716
        rx_tmode = TMODES[rxtype]
717
        tx_tmode = TMODES[txtype]
718

    
719
        rx_tone = tx_tone = None
720

    
721
        if tx_tmode == "Tone":
722
            if _mem.txcode < len(CTCSS_TONES):
723
                tx_tone = CTCSS_TONES[_mem.txcode]
724
            else:
725
                tx_tone = 0
726
                tx_tmode = ""
727
        elif tx_tmode == "DTCS":
728
            if _mem.txcode < len(DTCS_CODES):
729
                tx_tone = DTCS_CODES[_mem.txcode]
730
            else:
731
                tx_tone = 0
732
                tx_tmode = ""
733

    
734
        if rx_tmode == "Tone":
735
            if _mem.rxcode < len(CTCSS_TONES):
736
                rx_tone = CTCSS_TONES[_mem.rxcode]
737
            else:
738
                rx_tone = 0
739
                rx_tmode = ""
740
        elif rx_tmode == "DTCS":
741
            if _mem.rxcode < len(DTCS_CODES):
742
                rx_tone = DTCS_CODES[_mem.rxcode]
743
            else:
744
                rx_tone = 0
745
                rx_tmode = ""
746

    
747
        tx_pol = txtype == 0x03 and "R" or "N"
748
        rx_pol = rxtype == 0x03 and "R" or "N"
749

    
750
        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
751
                                       (rx_tmode, rx_tone, rx_pol))
752

    
753
    # Extract a high-level memory object from the low-level memory map
754
    # This is called to populate a memory in the UI
755
    def get_memory(self, number2):
756
        number = number2-1  # in the radio memories start with 0
757

    
758
        mem = chirp_common.Memory()
759

    
760
        # cutting and pasting configs from different radios
761
        # might try to set channel 0
762
        if number2 == 0:
763
            LOG.warning("Attempt to get channel 0")
764
            return mem
765

    
766
        _mem = self._memobj.channel[number]
767

    
768
        tmpcomment = ""
769

    
770
        mem.number = number2
771

    
772
        is_empty = False
773
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
774
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
775
            is_empty = True
776

    
777
        tmpscn = SCANLIST_LIST[0]
778

    
779
        # We'll also look at the channel attributes if a memory has them
780
        if number < 200:
781
            _mem3 = self._memobj.channel_attributes[number]
782
            # free memory bit
783
            if _mem3.is_free > 0:
784
                is_empty = True
785
            # scanlists
786
            if _mem3.is_scanlist1 > 0 and _mem3.is_scanlist2 > 0:
787
                tmpscn = SCANLIST_LIST[3]  # "1+2"
788
            elif _mem3.is_scanlist1 > 0:
789
                tmpscn = SCANLIST_LIST[1]  # "1"
790
            elif _mem3.is_scanlist2 > 0:
791
                tmpscn = SCANLIST_LIST[2]  # "2"
792

    
793
        if is_empty:
794
            mem.empty = True
795
            # set some sane defaults:
796
            mem.power = UVK5_POWER_LEVELS[2]
797
            mem.extra = RadioSettingGroup("Extra", "extra")
798
            rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(False))
799
            mem.extra.append(rs)
800
            rs = RadioSetting("frev", "FreqRev",
801
                              RadioSettingValueBoolean(False))
802
            mem.extra.append(rs)
803
            rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
804
                PTTID_LIST, PTTID_LIST[0]))
805
            mem.extra.append(rs)
806
            rs = RadioSetting("dtmfdecode", "DTMF decode",
807
                              RadioSettingValueBoolean(False))
808
            mem.extra.append(rs)
809
            rs = RadioSetting("scrambler", "Scrambler", RadioSettingValueList(
810
                SCRAMBLER_LIST, SCRAMBLER_LIST[0]))
811
            mem.extra.append(rs)
812

    
813
            rs = RadioSetting("scanlists", "Scanlists", RadioSettingValueList(
814
                SCANLIST_LIST, SCANLIST_LIST[0]))
815
            mem.extra.append(rs)
816

    
817
            # actually the step and duplex are overwritten by chirp based on
818
            # bandplan. they are here to document sane defaults for IARU r1
819
            # mem.tuning_step = 25.0
820
            # mem.duplex = "off"
821

    
822
            return mem
823

    
824
        if number > 199:
825
            mem.name = VFO_CHANNEL_NAMES[number-200]
826
            mem.immutable = ["name", "scanlists"]
827
        else:
828
            _mem2 = self._memobj.channelname[number]
829
            for char in _mem2.name:
830
                if str(char) == "\xFF" or str(char) == "\x00":
831
                    break
832
                mem.name += str(char)
833
            mem.name = mem.name.rstrip()
834

    
835
        # Convert your low-level frequency to Hertz
836
        mem.freq = int(_mem.freq)*10
837
        mem.offset = int(_mem.offset)*10
838

    
839
        if (mem.offset == 0):
840
            mem.duplex = ''
841
        else:
842
            if (_mem.flags1 & FLAGS1_OFFSET_MASK) == FLAGS1_OFFSET_MINUS:
843
                mem.duplex = '-'
844
            elif (_mem.flags1 & FLAGS1_OFFSET_MASK) == FLAGS1_OFFSET_PLUS:
845
                mem.duplex = '+'
846
            else:
847
                mem.duplex = ''
848

    
849
        # tone data
850
        self._get_tone(mem, _mem)
851

    
852
        # mode
853
        if (_mem.flags1 & FLAGS1_ISAM) > 0:
854
            if (_mem.flags2 & FLAGS2_BANDWIDTH) > 0:
855
                mem.mode = "NAM"
856
            else:
857
                mem.mode = "AM"
858
        else:
859
            if (_mem.flags2 & FLAGS2_BANDWIDTH) > 0:
860
                mem.mode = "NFM"
861
            else:
862
                mem.mode = "FM"
863

    
864
        # tuning step
865
        tstep = _mem.step & 0x7
866
        if tstep < len(STEPS):
867
            mem.tuning_step = STEPS[tstep]
868
        else:
869
            mem.tuning_step = 2.5
870

    
871
        # power
872
        if (_mem.flags2 & FLAGS2_POWER_MASK) == FLAGS2_POWER_HIGH:
873
            mem.power = UVK5_POWER_LEVELS[2]
874
        elif (_mem.flags2 & FLAGS2_POWER_MASK) == FLAGS2_POWER_MEDIUM:
875
            mem.power = UVK5_POWER_LEVELS[1]
876
        else:
877
            mem.power = UVK5_POWER_LEVELS[0]
878

    
879
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
880
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
881
            mem.empty = True
882
        else:
883
            mem.empty = False
884

    
885
        mem.extra = RadioSettingGroup("Extra", "extra")
886

    
887
        # BCLO
888
        is_bclo = bool(_mem.flags2 & FLAGS2_BCLO > 0)
889
        rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(is_bclo))
890
        mem.extra.append(rs)
891
        tmpcomment += "BCLO:"+(is_bclo and "ON" or "off")+" "
892

    
893
        # Frequency reverse - whatever that means, don't see it in the manual
894
        is_frev = bool(_mem.flags2 & FLAGS2_REVERSE > 0)
895
        rs = RadioSetting("frev", "FreqRev", RadioSettingValueBoolean(is_frev))
896
        mem.extra.append(rs)
897
        tmpcomment += "FreqReverse:"+(is_frev and "ON" or "off")+" "
898

    
899
        # PTTID
900
        pttid = (_mem.dtmf_flags & FLAGS_DTMF_PTTID_MASK) >> 1
901
        rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
902
            PTTID_LIST, PTTID_LIST[pttid]))
903
        mem.extra.append(rs)
904
        tmpcomment += "PTTid:"+PTTID_LIST[pttid]+" "
905

    
906
        # DTMF DECODE
907
        is_dtmf = bool(_mem.dtmf_flags & FLAGS_DTMF_DECODE > 0)
908
        rs = RadioSetting("dtmfdecode", "DTMF decode",
909
                          RadioSettingValueBoolean(is_dtmf))
910
        mem.extra.append(rs)
911
        tmpcomment += "DTMFdecode:"+(is_dtmf and "ON" or "off")+" "
912

    
913
        # Scrambler
914
        if _mem.scrambler & 0x0f < len(SCRAMBLER_LIST):
915
            enc = _mem.scrambler & 0x0f
916
        else:
917
            enc = 0
918

    
919
        rs = RadioSetting("scrambler", "Scrambler", RadioSettingValueList(
920
            SCRAMBLER_LIST, SCRAMBLER_LIST[enc]))
921
        mem.extra.append(rs)
922
        tmpcomment += "Scrambler:"+SCRAMBLER_LIST[enc]+" "
923

    
924
        # scanlists
925
        pttid = (_mem.dtmf_flags & FLAGS_DTMF_PTTID_MASK) >> 1
926
        rs = RadioSetting("scanlists", "Scanlists", RadioSettingValueList(
927
            SCANLIST_LIST, tmpscn))
928
        mem.extra.append(rs)
929

    
930
        return mem
931

    
932
    def set_settings(self, settings):
933
        _mem = self._memobj
934
        for element in settings:
935
            if not isinstance(element, RadioSetting):
936
                self.set_settings(element)
937
                continue
938

    
939
            # basic settings
940

    
941
            # call channel
942
            if element.get_name() == "call_channel":
943
                _mem.call_channel = int(element.value)-1
944

    
945
            # squelch
946
            if element.get_name() == "squelch":
947
                _mem.squelch = int(element.value)
948
            # TOT
949
            if element.get_name() == "tot":
950
                _mem.max_talk_time = int(element.value)
951
            # NOAA autoscan
952
            if element.get_name() == "noaa_autoscan":
953
                _mem.noaa_autoscan = element.value and 1 or 0
954

    
955
            # vox level
956
            if element.get_name() == "vox_level":
957
                _mem.vox_level = int(element.value)-1
958

    
959
            # mic gain
960
            if element.get_name() == "mic_gain":
961
                _mem.mic_gain = int(element.value)
962

    
963
            # Channel display mode
964
            if element.get_name() == "channel_display_mode":
965
                _mem.channel_display_mode = CHANNELDISP_LIST.index(
966
                    str(element.value))
967

    
968
            # Crossband receiving/transmitting
969
            if element.get_name() == "crossband":
970
                _mem.crossband = CROSSBAND_LIST.index(str(element.value))
971

    
972
            # Battery Save
973
            if element.get_name() == "battery_save":
974
                _mem.battery_save = BATSAVE_LIST.index(str(element.value))
975
            # Dual Watch
976
            if element.get_name() == "dualwatch":
977
                _mem.dual_watch = DUALWATCH_LIST.index(str(element.value))
978

    
979
            # Tail tone elimination
980
            if element.get_name() == "tail_note_elimination":
981
                _mem.tail_note_elimination = element.value and 1 or 0
982

    
983
            # VFO Open
984
            if element.get_name() == "vfo_open":
985
                _mem.vfo_open = element.value and 1 or 0
986

    
987
            # Beep control
988
            if element.get_name() == "beep_control":
989
                _mem.beep_control = element.value and 1 or 0
990

    
991
            # Scan resume mode
992
            if element.get_name() == "scan_resume_mode":
993
                _mem.scan_resume_mode = SCANRESUME_LIST.index(
994
                    str(element.value))
995

    
996
            # Auto keypad lock
997
            if element.get_name() == "auto_keypad_lock":
998
                _mem.auto_keypad_lock = element.value and 1 or 0
999

    
1000
            # Power on display mode
1001
            if element.get_name() == "welcome_mode":
1002
                _mem.power_on_dispmode = WELCOME_LIST.index(str(element.value))
1003

    
1004
            # Keypad Tone
1005
            if element.get_name() == "keypad_tone":
1006
                _mem.keypad_tone = KEYPADTONE_LIST.index(str(element.value))
1007

    
1008
            # Language
1009
            if element.get_name() == "language":
1010
                _mem.language = LANGUAGE_LIST.index(str(element.value))
1011

    
1012
            # Alarm mode
1013
            if element.get_name() == "alarm_mode":
1014
                _mem.alarm_mode = ALARMMODE_LIST.index(str(element.value))
1015

    
1016
            # Reminding of end of talk
1017
            if element.get_name() == "reminding_of_end_talk":
1018
                _mem.reminding_of_end_talk = REMENDOFTALK_LIST.index(
1019
                    str(element.value))
1020

    
1021
            # Repeater tail tone elimination
1022
            if element.get_name() == "repeater_tail_elimination":
1023
                _mem.repeater_tail_elimination = RTE_LIST.index(
1024
                    str(element.value))
1025

    
1026
            # Logo string 1
1027
            if element.get_name() == "logo1":
1028
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
1029
                _mem.logo_line1 = b[0:12]+"\xff\xff\xff\xff"
1030

    
1031
            # Logo string 2
1032
            if element.get_name() == "logo2":
1033
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
1034
                _mem.logo_line2 = b[0:12]+"\xff\xff\xff\xff"
1035

    
1036
            # unlock settings
1037

    
1038
            # FLOCK
1039
            if element.get_name() == "flock":
1040
                _mem.int_flock = FLOCK_LIST.index(str(element.value))
1041

    
1042
            # 350TX
1043
            if element.get_name() == "350tx":
1044
                _mem.int_350tx = element.value and 1 or 0
1045

    
1046
            # UNKNOWN1
1047
            if element.get_name() == "unknown1":
1048
                _mem.int_unknown1 = element.value and 1 or 0
1049

    
1050
            # 200TX
1051
            if element.get_name() == "200tx":
1052
                _mem.int_200tx = element.value and 1 or 0
1053

    
1054
            # 500TX
1055
            if element.get_name() == "500tx":
1056
                _mem.int_500tx = element.value and 1 or 0
1057

    
1058
            # 350EN
1059
            if element.get_name() == "350en":
1060
                _mem.int_350en = element.value and 1 or 0
1061

    
1062
            # fm radio
1063
            for i in range(1, 21):
1064
                freqname = "FM_" + str(i)
1065
                if element.get_name() == freqname:
1066
                    val = str(element.value).strip()
1067
                    try:
1068
                        val2 = int(float(val)*10)
1069
                    except Exception:
1070
                        val2 = 0xffff
1071

    
1072
                    if val2 < FMMIN*10 or val2 > FMMAX*10:
1073
                        val2 = 0xffff
1074
#                        raise errors.InvalidValueError(
1075
#                                "FM radio frequency should be a value "
1076
#                                "in the range %.1f - %.1f" % (FMMIN , FMMAX))
1077
                    _mem.fmfreq[i-1] = val2
1078

    
1079
            # dtmf settings
1080
            if element.get_name() == "dtmf_side_tone":
1081
                _mem.dtmf_settings.side_tone = \
1082
                        element.value and 1 or 0
1083

    
1084
            if element.get_name() == "dtmf_separate_code":
1085
                _mem.dtmf_settings.separate_code = str(element.value)
1086

    
1087
            if element.get_name() == "dtmf_group_call_code":
1088
                _mem.dtmf_settings.group_call_code = element.value
1089

    
1090
            if element.get_name() == "dtmf_decode_response":
1091
                _mem.dtmf_settings.decode_response = \
1092
                        DTMF_DECODE_RESPONSE_LIST.index(str(element.value))
1093

    
1094
            if element.get_name() == "dtmf_auto_reset_time":
1095
                _mem.dtmf_settings.auto_reset_time = \
1096
                        int(int(element.value)/10)
1097

    
1098
            if element.get_name() == "dtmf_preload_time":
1099
                _mem.dtmf_settings.preload_time = \
1100
                        int(int(element.value)/10)
1101

    
1102
            if element.get_name() == "dtmf_first_code_persist_time":
1103
                _mem.dtmf_settings.first_code_persist_time = \
1104
                        int(int(element.value)/10)
1105

    
1106
            if element.get_name() == "dtmf_hash_persist_time":
1107
                _mem.dtmf_settings.hash_persist_time = \
1108
                        int(int(element.value)/10)
1109

    
1110
            if element.get_name() == "dtmf_code_persist_time":
1111
                _mem.dtmf_settings.code_persist_time = \
1112
                        int(int(element.value)/10)
1113

    
1114
            if element.get_name() == "dtmf_code_interval_time":
1115
                _mem.dtmf_settings.code_interval_time = \
1116
                        int(int(element.value)/10)
1117

    
1118
            if element.get_name() == "dtmf_permit_remote_kill":
1119
                _mem.dtmf_settings.permit_remote_kill = \
1120
                        element.value and 1 or 0
1121

    
1122
            if element.get_name() == "dtmf_dtmf_local_code":
1123
                k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*3
1124
                _mem.dtmf_settings_numbers.dtmf_local_code = k[0:3]
1125

    
1126
            if element.get_name() == "dtmf_dtmf_up_code":
1127
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*16
1128
                _mem.dtmf_settings_numbers.dtmf_up_code = k[0:16]
1129

    
1130
            if element.get_name() == "dtmf_dtmf_down_code":
1131
                k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*16
1132
                _mem.dtmf_settings_numbers.dtmf_down_code = k[0:16]
1133

    
1134
            if element.get_name() == "dtmf_kill_code":
1135
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*5
1136
                _mem.dtmf_settings_numbers.kill_code = k[0:5]
1137

    
1138
            if element.get_name() == "dtmf_revive_code":
1139
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*5
1140
                _mem.dtmf_settings_numbers.revive_code = k[0:5]
1141

    
1142
            # dtmf contacts
1143
            for i in range(1, 17):
1144
                varname = "DTMF_" + str(i)
1145
                if element.get_name() == varname:
1146
                    k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*8
1147
                    _mem.dtmfcontact[i-1].name = k[0:8]
1148

    
1149
                varnumname = "DTMFNUM_" + str(i)
1150
                if element.get_name() == varnumname:
1151
                    k = str(element.value).rstrip("\x20\xff\x00") + "\xff"*3
1152
                    _mem.dtmfcontact[i-1].number = k[0:3]
1153

    
1154
            # scanlist stuff
1155
            if element.get_name() == "scanlist_default":
1156
                val = (int(element.value) == 2) and 1 or 0
1157
                _mem.scanlist_default = val
1158

    
1159
            if element.get_name() == "scanlist1_priority_scan":
1160
                _mem.scanlist1_priority_scan = \
1161
                        element.value and 1 or 0
1162

    
1163
            if element.get_name() == "scanlist2_priority_scan":
1164
                _mem.scanlist2_priority_scan = \
1165
                        element.value and 1 or 0
1166

    
1167
            if element.get_name() == "scanlist1_priority_ch1" or \
1168
                    element.get_name() == "scanlist1_priority_ch2" or \
1169
                    element.get_name() == "scanlist2_priority_ch1" or \
1170
                    element.get_name() == "scanlist2_priority_ch2":
1171

    
1172
                val = int(element.value)
1173

    
1174
                if val > 200 or val < 1:
1175
                    val = 0xff
1176
                else:
1177
                    val -= 1
1178

    
1179
                if element.get_name() == "scanlist1_priority_ch1":
1180
                    _mem.scanlist1_priority_ch1 = val
1181
                if element.get_name() == "scanlist1_priority_ch2":
1182
                    _mem.scanlist1_priority_ch2 = val
1183
                if element.get_name() == "scanlist2_priority_ch1":
1184
                    _mem.scanlist2_priority_ch1 = val
1185
                if element.get_name() == "scanlist2_priority_ch2":
1186
                    _mem.scanlist2_priority_ch2 = val
1187

    
1188
            if element.get_name() == "key1_shortpress_action":
1189
                _mem.key1_shortpress_action = KEYACTIONS_LIST.index(
1190
                        str(element.value))
1191

    
1192
            if element.get_name() == "key1_longpress_action":
1193
                _mem.key1_longpress_action = KEYACTIONS_LIST.index(
1194
                        str(element.value))
1195

    
1196
            if element.get_name() == "key2_shortpress_action":
1197
                _mem.key2_shortpress_action = KEYACTIONS_LIST.index(
1198
                        str(element.value))
1199

    
1200
            if element.get_name() == "key2_longpress_action":
1201
                _mem.key2_longpress_action = KEYACTIONS_LIST.index(
1202
                        str(element.value))
1203

    
1204
    def get_settings(self):
1205
        _mem = self._memobj
1206
        basic = RadioSettingGroup("basic", "Basic Settings")
1207
        keya = RadioSettingGroup("keya", "Programmable keys")
1208
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1209
        dtmfc = RadioSettingGroup("dtmfc", "DTMF Contacts")
1210
        scanl = RadioSettingGroup("scn", "Scan Lists")
1211
        unlock = RadioSettingGroup("unlock", "Unlock Settings")
1212
        fmradio = RadioSettingGroup("fmradio", "FM Radio")
1213

    
1214
        roinfo = RadioSettingGroup("roinfo", "Driver information")
1215

    
1216
        top = RadioSettings(
1217
                basic, keya, dtmf, dtmfc, scanl, unlock, fmradio, roinfo)
1218

    
1219
        # Programmable keys
1220
        tmpval = int(_mem.key1_shortpress_action)
1221
        if tmpval >= len(KEYACTIONS_LIST):
1222
            tmpval = 0
1223
        rs = RadioSetting("key1_shortpress_action", "Side key 1 short press",
1224
                          RadioSettingValueList(
1225
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1226
        keya.append(rs)
1227

    
1228
        tmpval = int(_mem.key1_longpress_action)
1229
        if tmpval >= len(KEYACTIONS_LIST):
1230
            tmpval = 0
1231
        rs = RadioSetting("key1_longpress_action", "Side key 1 long press",
1232
                          RadioSettingValueList(
1233
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1234
        keya.append(rs)
1235

    
1236
        tmpval = int(_mem.key2_shortpress_action)
1237
        if tmpval >= len(KEYACTIONS_LIST):
1238
            tmpval = 0
1239
        rs = RadioSetting("key2_shortpress_action", "Side key 2 short press",
1240
                          RadioSettingValueList(
1241
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1242
        keya.append(rs)
1243

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

    
1252
        # DTMF settings
1253
        tmppr = bool(_mem.dtmf_settings.side_tone > 0)
1254
        rs = RadioSetting(
1255
                "dtmf_side_tone",
1256
                "DTMF Sidetone",
1257
                RadioSettingValueBoolean(tmppr))
1258
        dtmf.append(rs)
1259

    
1260
        tmpval = str(_mem.dtmf_settings.separate_code)
1261
        if tmpval not in DTMF_CODE_CHARS:
1262
            tmpval = '*'
1263
        val = RadioSettingValueString(1, 1, tmpval)
1264
        val.set_charset(DTMF_CODE_CHARS)
1265
        rs = RadioSetting("dtmf_separate_code", "Separate Code", val)
1266
        dtmf.append(rs)
1267

    
1268
        tmpval = str(_mem.dtmf_settings.group_call_code)
1269
        if tmpval not in DTMF_CODE_CHARS:
1270
            tmpval = '#'
1271
        val = RadioSettingValueString(1, 1, tmpval)
1272
        val.set_charset(DTMF_CODE_CHARS)
1273
        rs = RadioSetting("dtmf_group_call_code", "Group Call Code", val)
1274
        dtmf.append(rs)
1275

    
1276
        tmpval = _mem.dtmf_settings.decode_response
1277
        if tmpval >= len(DTMF_DECODE_RESPONSE_LIST):
1278
            tmpval = 0
1279
        rs = RadioSetting("dtmf_decode_response", "Decode Response",
1280
                          RadioSettingValueList(
1281
                              DTMF_DECODE_RESPONSE_LIST,
1282
                              DTMF_DECODE_RESPONSE_LIST[tmpval]))
1283
        dtmf.append(rs)
1284

    
1285
        tmpval = _mem.dtmf_settings.auto_reset_time
1286
        if tmpval > 60 or tmpval < 5:
1287
            tmpval = 5
1288
        rs = RadioSetting("dtmf_auto_reset_time",
1289
                          "Auto reset time (s)",
1290
                          RadioSettingValueInteger(5, 60, tmpval))
1291
        dtmf.append(rs)
1292

    
1293
        tmpval = int(_mem.dtmf_settings.preload_time)
1294
        if tmpval > 100 or tmpval < 3:
1295
            tmpval = 30
1296
        tmpval *= 10
1297
        rs = RadioSetting("dtmf_preload_time",
1298
                          "Pre-load time (ms)",
1299
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1300
        dtmf.append(rs)
1301

    
1302
        tmpval = int(_mem.dtmf_settings.first_code_persist_time)
1303
        if tmpval > 100 or tmpval < 3:
1304
            tmpval = 30
1305
        tmpval *= 10
1306
        rs = RadioSetting("dtmf_first_code_persist_time",
1307
                          "First code persist time (ms)",
1308
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1309
        dtmf.append(rs)
1310

    
1311
        tmpval = int(_mem.dtmf_settings.hash_persist_time)
1312
        if tmpval > 100 or tmpval < 3:
1313
            tmpval = 30
1314
        tmpval *= 10
1315
        rs = RadioSetting("dtmf_hash_persist_time",
1316
                          "#/* persist time (ms)",
1317
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1318
        dtmf.append(rs)
1319

    
1320
        tmpval = int(_mem.dtmf_settings.code_persist_time)
1321
        if tmpval > 100 or tmpval < 3:
1322
            tmpval = 30
1323
        tmpval *= 10
1324
        rs = RadioSetting("dtmf_code_persist_time",
1325
                          "Code persist time (ms)",
1326
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1327
        dtmf.append(rs)
1328

    
1329
        tmpval = int(_mem.dtmf_settings.code_interval_time)
1330
        if tmpval > 100 or tmpval < 3:
1331
            tmpval = 30
1332
        tmpval *= 10
1333
        rs = RadioSetting("dtmf_code_interval_time",
1334
                          "Code interval time (ms)",
1335
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1336
        dtmf.append(rs)
1337

    
1338
        tmpval = bool(_mem.dtmf_settings.permit_remote_kill > 0)
1339
        rs = RadioSetting(
1340
                "dtmf_permit_remote_kill",
1341
                "Permit remote kill",
1342
                RadioSettingValueBoolean(tmpval))
1343
        dtmf.append(rs)
1344

    
1345
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_local_code).upper().strip(
1346
                "\x00\xff\x20")
1347
        for i in tmpval:
1348
            if i in DTMF_CHARS_ID:
1349
                continue
1350
            else:
1351
                tmpval = "103"
1352
                break
1353
        val = RadioSettingValueString(3, 3, tmpval)
1354
        val.set_charset(DTMF_CHARS_ID)
1355
        rs = RadioSetting("dtmf_dtmf_local_code",
1356
                          "Local code (3 chars 0-9 ABCD)", val)
1357
        dtmf.append(rs)
1358

    
1359
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_up_code).upper().strip(
1360
                "\x00\xff\x20")
1361
        for i in tmpval:
1362
            if i in DTMF_CHARS_UPDOWN or i == "":
1363
                continue
1364
            else:
1365
                tmpval = "123"
1366
                break
1367
        val = RadioSettingValueString(1, 16, tmpval)
1368
        val.set_charset(DTMF_CHARS_UPDOWN)
1369
        rs = RadioSetting("dtmf_dtmf_up_code",
1370
                          "Up code (1-16 chars 0-9 ABCD*#)", val)
1371
        dtmf.append(rs)
1372

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

    
1387
        tmpval = str(_mem.dtmf_settings_numbers.kill_code).upper().strip(
1388
                "\x00\xff\x20")
1389
        for i in tmpval:
1390
            if i in DTMF_CHARS_KILL:
1391
                continue
1392
            else:
1393
                tmpval = "77777"
1394
                break
1395
        if not len(tmpval) == 5:
1396
            tmpval = "77777"
1397
        val = RadioSettingValueString(5, 5, tmpval)
1398
        val.set_charset(DTMF_CHARS_KILL)
1399
        rs = RadioSetting("dtmf_kill_code",
1400
                          "Kill code (5 chars 0-9 ABCD)", val)
1401
        dtmf.append(rs)
1402

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

    
1419
        val = RadioSettingValueString(0, 80,
1420
                                      "All DTMF Contacts are 3 codes "
1421
                                      "(valid: 0-9 * # ABCD), "
1422
                                      "or an empty string")
1423
        val.set_mutable(False)
1424
        rs = RadioSetting("dtmf_descr1", "DTMF Contacts", val)
1425
        dtmfc.append(rs)
1426

    
1427
        for i in range(1, 17):
1428
            varname = "DTMF_"+str(i)
1429
            varnumname = "DTMFNUM_"+str(i)
1430
            vardescr = "DTMF Contact "+str(i)+" name"
1431
            varinumdescr = "DTMF Contact "+str(i)+" number"
1432

    
1433
            cntn = str(_mem.dtmfcontact[i-1].name).strip("\x20\x00\xff")
1434
            cntnum = str(_mem.dtmfcontact[i-1].number).strip("\x20\x00\xff")
1435

    
1436
            val = RadioSettingValueString(0, 8, cntn)
1437
            rs = RadioSetting(varname, vardescr, val)
1438
            dtmfc.append(rs)
1439

    
1440
            val = RadioSettingValueString(0, 3, cntnum)
1441
            val.set_charset(DTMF_CHARS)
1442
            rs = RadioSetting(varnumname, varinumdescr, val)
1443
            dtmfc.append(rs)
1444

    
1445
        # scanlists
1446
        if _mem.scanlist_default == 1:
1447
            tmpsc = 2
1448
        else:
1449
            tmpsc = 1
1450
        rs = RadioSetting("scanlist_default",
1451
                          "Default scanlist",
1452
                          RadioSettingValueInteger(1, 2, tmpsc))
1453
        scanl.append(rs)
1454

    
1455
        tmppr = bool((_mem.scanlist1_priority_scan & 1) > 0)
1456
        rs = RadioSetting(
1457
                "scanlist1_priority_scan",
1458
                "Scanlist 1 priority channel scan",
1459
                RadioSettingValueBoolean(tmppr))
1460
        scanl.append(rs)
1461

    
1462
        tmpch = _mem.scanlist1_priority_ch1 + 1
1463
        if tmpch > 200:
1464
            tmpch = 0
1465
        rs = RadioSetting("scanlist1_priority_ch1",
1466
                          "Scanlist 1 priority channel 1 (0 - off)",
1467
                          RadioSettingValueInteger(0, 200, tmpch))
1468
        scanl.append(rs)
1469

    
1470
        tmpch = _mem.scanlist1_priority_ch2 + 1
1471
        if tmpch > 200:
1472
            tmpch = 0
1473
        rs = RadioSetting("scanlist1_priority_ch2",
1474
                          "Scanlist 1 priority channel 2 (0 - off)",
1475
                          RadioSettingValueInteger(0, 200, tmpch))
1476
        scanl.append(rs)
1477

    
1478
        tmppr = bool((_mem.scanlist2_priority_scan & 1) > 0)
1479
        rs = RadioSetting(
1480
                "scanlist2_priority_scan",
1481
                "Scanlist 2 priority channel scan",
1482
                RadioSettingValueBoolean(tmppr))
1483
        scanl.append(rs)
1484

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

    
1493
        tmpch = _mem.scanlist2_priority_ch2 + 1
1494
        if tmpch > 200:
1495
            tmpch = 0
1496
        rs = RadioSetting("scanlist2_priority_ch2",
1497
                          "Scanlist 2 priority channel 2 (0 - off)",
1498
                          RadioSettingValueInteger(0, 200, tmpch))
1499
        scanl.append(rs)
1500

    
1501
        # basic settings
1502

    
1503
        # call channel
1504
        tmpc = _mem.call_channel+1
1505
        if tmpc > 200:
1506
            tmpc = 1
1507
        rs = RadioSetting("call_channel", "One key call channel",
1508
                          RadioSettingValueInteger(1, 200, tmpc))
1509
        basic.append(rs)
1510

    
1511
        # squelch
1512
        tmpsq = _mem.squelch
1513
        if tmpsq > 9:
1514
            tmpsq = 1
1515
        rs = RadioSetting("squelch", "Squelch",
1516
                          RadioSettingValueInteger(0, 9, tmpsq))
1517
        basic.append(rs)
1518

    
1519
        # TOT
1520
        tmptot = _mem.max_talk_time
1521
        if tmptot > 10:
1522
            tmptot = 10
1523
        rs = RadioSetting(
1524
                "tot",
1525
                "Max talk time [min]",
1526
                RadioSettingValueInteger(0, 10, tmptot))
1527
        basic.append(rs)
1528

    
1529
        # NOAA autoscan
1530
        rs = RadioSetting(
1531
                "noaa_autoscan",
1532
                "NOAA Autoscan", RadioSettingValueBoolean(
1533
                    bool(_mem.noaa_autoscan > 0)))
1534
        basic.append(rs)
1535

    
1536
        # VOX Level
1537
        tmpvox = _mem.vox_level+1
1538
        if tmpvox > 10:
1539
            tmpvox = 10
1540
        rs = RadioSetting("vox_level", "VOX Level",
1541
                          RadioSettingValueInteger(1, 10, tmpvox))
1542
        basic.append(rs)
1543

    
1544
        # Mic gain
1545
        tmpmicgain = _mem.mic_gain
1546
        if tmpmicgain > 4:
1547
            tmpmicgain = 4
1548
        rs = RadioSetting("mic_gain", "Mic Gain",
1549
                          RadioSettingValueInteger(0, 4, tmpmicgain))
1550
        basic.append(rs)
1551

    
1552
        # Channel display mode
1553
        tmpchdispmode = _mem.channel_display_mode
1554
        if tmpchdispmode >= len(CHANNELDISP_LIST):
1555
            tmpchdispmode = 0
1556
        rs = RadioSetting(
1557
                "channel_display_mode",
1558
                "Channel display mode",
1559
                RadioSettingValueList(
1560
                    CHANNELDISP_LIST,
1561
                    CHANNELDISP_LIST[tmpchdispmode]))
1562
        basic.append(rs)
1563

    
1564
        # Crossband receiving/transmitting
1565
        tmpcross = _mem.crossband
1566
        if tmpcross >= len(CROSSBAND_LIST):
1567
            tmpcross = 0
1568
        rs = RadioSetting(
1569
                "crossband",
1570
                "Cross-band receiving/transmitting",
1571
                RadioSettingValueList(
1572
                    CROSSBAND_LIST,
1573
                    CROSSBAND_LIST[tmpcross]))
1574
        basic.append(rs)
1575

    
1576
        # Battery save
1577
        tmpbatsave = _mem.battery_save
1578
        if tmpbatsave >= len(BATSAVE_LIST):
1579
            tmpbatsave = BATSAVE_LIST.index("1:4")
1580
        rs = RadioSetting(
1581
                "battery_save",
1582
                "Battery Save",
1583
                RadioSettingValueList(
1584
                    BATSAVE_LIST,
1585
                    BATSAVE_LIST[tmpbatsave]))
1586
        basic.append(rs)
1587

    
1588
        # Dual watch
1589
        tmpdual = _mem.dual_watch
1590
        if tmpdual >= len(DUALWATCH_LIST):
1591
            tmpdual = 0
1592
        rs = RadioSetting("dualwatch", "Dual Watch", RadioSettingValueList(
1593
            DUALWATCH_LIST, DUALWATCH_LIST[tmpdual]))
1594
        basic.append(rs)
1595

    
1596
        # Tail tone elimination
1597
        rs = RadioSetting(
1598
                "tail_note_elimination",
1599
                "Tail tone elimination",
1600
                RadioSettingValueBoolean(
1601
                    bool(_mem.tail_note_elimination > 0)))
1602
        basic.append(rs)
1603

    
1604
        # VFO open
1605
        rs = RadioSetting("vfo_open", "VFO open",
1606
                          RadioSettingValueBoolean(bool(_mem.vfo_open > 0)))
1607
        basic.append(rs)
1608

    
1609
        # Beep control
1610
        rs = RadioSetting(
1611
                "beep_control",
1612
                "Beep control",
1613
                RadioSettingValueBoolean(bool(_mem.beep_control > 0)))
1614
        basic.append(rs)
1615

    
1616
        # Scan resume mode
1617
        tmpscanres = _mem.scan_resume_mode
1618
        if tmpscanres >= len(SCANRESUME_LIST):
1619
            tmpscanres = 0
1620
        rs = RadioSetting(
1621
                "scan_resume_mode",
1622
                "Scan resume mode",
1623
                RadioSettingValueList(
1624
                    SCANRESUME_LIST,
1625
                    SCANRESUME_LIST[tmpscanres]))
1626
        basic.append(rs)
1627

    
1628
        # Auto keypad lock
1629
        rs = RadioSetting(
1630
                "auto_keypad_lock",
1631
                "Auto keypad lock",
1632
                RadioSettingValueBoolean(bool(_mem.auto_keypad_lock > 0)))
1633
        basic.append(rs)
1634

    
1635
        # Power on display mode
1636
        tmpdispmode = _mem.power_on_dispmode
1637
        if tmpdispmode >= len(WELCOME_LIST):
1638
            tmpdispmode = 0
1639
        rs = RadioSetting(
1640
                "welcome_mode",
1641
                "Power on display mode",
1642
                RadioSettingValueList(
1643
                    WELCOME_LIST,
1644
                    WELCOME_LIST[tmpdispmode]))
1645
        basic.append(rs)
1646

    
1647
        # Keypad Tone
1648
        tmpkeypadtone = _mem.keypad_tone
1649
        if tmpkeypadtone >= len(KEYPADTONE_LIST):
1650
            tmpkeypadtone = 0
1651
        rs = RadioSetting("keypad_tone", "Keypad tone", RadioSettingValueList(
1652
            KEYPADTONE_LIST, KEYPADTONE_LIST[tmpkeypadtone]))
1653
        basic.append(rs)
1654

    
1655
        # Language
1656
        tmplanguage = _mem.language
1657
        if tmplanguage >= len(LANGUAGE_LIST):
1658
            tmplanguage = 0
1659
        rs = RadioSetting("language", "Language", RadioSettingValueList(
1660
            LANGUAGE_LIST, LANGUAGE_LIST[tmplanguage]))
1661
        basic.append(rs)
1662

    
1663
        # Alarm mode
1664
        tmpalarmmode = _mem.alarm_mode
1665
        if tmpalarmmode >= len(ALARMMODE_LIST):
1666
            tmpalarmmode = 0
1667
        rs = RadioSetting("alarm_mode", "Alarm mode", RadioSettingValueList(
1668
            ALARMMODE_LIST, ALARMMODE_LIST[tmpalarmmode]))
1669
        basic.append(rs)
1670

    
1671
        # Reminding of end of talk
1672
        tmpalarmmode = _mem.reminding_of_end_talk
1673
        if tmpalarmmode >= len(REMENDOFTALK_LIST):
1674
            tmpalarmmode = 0
1675
        rs = RadioSetting(
1676
                "reminding_of_end_talk",
1677
                "Reminding of end of talk",
1678
                RadioSettingValueList(
1679
                    REMENDOFTALK_LIST,
1680
                    REMENDOFTALK_LIST[tmpalarmmode]))
1681
        basic.append(rs)
1682

    
1683
        # Repeater tail tone elimination
1684
        tmprte = _mem.repeater_tail_elimination
1685
        if tmprte >= len(RTE_LIST):
1686
            tmprte = 0
1687
        rs = RadioSetting(
1688
                "repeater_tail_elimination",
1689
                "Repeater tail tone elimination",
1690
                RadioSettingValueList(RTE_LIST, RTE_LIST[tmprte]))
1691
        basic.append(rs)
1692

    
1693
        # Logo string 1
1694
        logo1 = str(_mem.logo_line1).strip("\x20\x00\xff") + "\x00"
1695
        logo1 = _getstring(logo1.encode('ascii', errors='ignore'), 0, 12)
1696
        rs = RadioSetting("logo1", "Logo string 1 (12 characters)",
1697
                          RadioSettingValueString(0, 12, logo1))
1698
        basic.append(rs)
1699

    
1700
        # Logo string 2
1701
        logo2 = str(_mem.logo_line2).strip("\x20\x00\xff") + "\x00"
1702
        logo2 = _getstring(logo2.encode('ascii', errors='ignore'), 0, 12)
1703
        rs = RadioSetting("logo2", "Logo string 2 (12 characters)",
1704
                          RadioSettingValueString(0, 12, logo2))
1705
        basic.append(rs)
1706

    
1707
        # FM radio
1708
        for i in range(1, 21):
1709
            freqname = "FM_"+str(i)
1710
            fmfreq = _mem.fmfreq[i-1]/10.0
1711
            if fmfreq < FMMIN or fmfreq > FMMAX:
1712
                rs = RadioSetting(freqname, freqname,
1713
                                  RadioSettingValueString(0, 5, ""))
1714
            else:
1715
                rs = RadioSetting(freqname, freqname,
1716
                                  RadioSettingValueString(0, 5, str(fmfreq)))
1717

    
1718
            fmradio.append(rs)
1719

    
1720
        # unlock settings
1721

    
1722
        # F-LOCK
1723
        tmpflock = _mem.int_flock
1724
        if tmpflock >= len(FLOCK_LIST):
1725
            tmpflock = 0
1726
        rs = RadioSetting(
1727
            "flock", "F-LOCK",
1728
            RadioSettingValueList(FLOCK_LIST, FLOCK_LIST[tmpflock]))
1729
        unlock.append(rs)
1730

    
1731
        # 350TX
1732
        rs = RadioSetting("350tx", "350TX", RadioSettingValueBoolean(
1733
            bool(_mem.int_350tx > 0)))
1734
        unlock.append(rs)
1735

    
1736
        # unknown1
1737
        rs = RadioSetting("unknown11", "UNKNOWN1",
1738
                          RadioSettingValueBoolean(
1739
                              bool(_mem.int_unknown1 > 0)))
1740
        unlock.append(rs)
1741

    
1742
        # 200TX
1743
        rs = RadioSetting("200tx", "200TX", RadioSettingValueBoolean(
1744
            bool(_mem.int_200tx > 0)))
1745
        unlock.append(rs)
1746

    
1747
        # 500TX
1748
        rs = RadioSetting("500tx", "500TX", RadioSettingValueBoolean(
1749
            bool(_mem.int_500tx > 0)))
1750
        unlock.append(rs)
1751

    
1752
        # 350EN
1753
        rs = RadioSetting("350en", "350EN", RadioSettingValueBoolean(
1754
            bool(_mem.int_350en > 0)))
1755
        unlock.append(rs)
1756

    
1757
        # SCREEN
1758
        rs = RadioSetting("screen", "SCREEN", RadioSettingValueBoolean(
1759
            bool(_mem.int_screen > 0)))
1760
        unlock.append(rs)
1761

    
1762
        # readonly info
1763
        # Firmware
1764
        if self.FIRMWARE_VERSION == "":
1765
            firmware = "To get the firmware version please download"
1766
            "the image from the radio first"
1767
        else:
1768
            firmware = self.FIRMWARE_VERSION
1769

    
1770
        val = RadioSettingValueString(0, 128, firmware)
1771
        val.set_mutable(False)
1772
        rs = RadioSetting("fw_ver", "Firmware Version", val)
1773
        roinfo.append(rs)
1774

    
1775
        # Driver version
1776
        val = RadioSettingValueString(0, 128, DRIVER_VERSION)
1777
        val.set_mutable(False)
1778
        rs = RadioSetting("driver_ver", "Driver version", val)
1779
        roinfo.append(rs)
1780

    
1781
        # No limits version for hacked firmware
1782
        val = RadioSettingValueBoolean(self.FIRMWARE_NOLIMITS)
1783
        val.set_mutable(False)
1784
        rs = RadioSetting("nolimits", "Limits disabled for modified firmware",
1785
                          val)
1786
        roinfo.append(rs)
1787

    
1788
        return top
1789

    
1790
    # Store details about a high-level memory to the memory map
1791
    # This is called when a user edits a memory in the UI
1792
    def set_memory(self, mem):
1793
        number = mem.number-1
1794

    
1795
        # Get a low-level memory object mapped to the image
1796
        _mem = self._memobj.channel[number]
1797
        _mem4 = self._memobj
1798
        # empty memory
1799
        if mem.empty:
1800
            _mem.set_raw("\xFF" * 16)
1801
            if number < 200:
1802
                _mem2 = self._memobj.channelname[number]
1803
                _mem2.set_raw("\xFF" * 16)
1804
                _mem4.channel_attributes[number].is_scanlist1 = 0
1805
                _mem4.channel_attributes[number].is_scanlist2 = 0
1806
                _mem4.channel_attributes[number].unknown1 = 0
1807
                _mem4.channel_attributes[number].unknown2 = 0
1808
                _mem4.channel_attributes[number].is_free = 1
1809
                _mem4.channel_attributes[number].band = 0x7
1810
            return mem
1811

    
1812
        # clean the channel memory, restore some bits if it was used before
1813
        if _mem.get_raw()[0] == "\xff":
1814
            # this was an empty memory
1815
            _mem.set_raw("\x00" * 16)
1816
        else:
1817
            # this memory was't empty, save some bits that we don't know the
1818
            # meaning of, or that we don't support yet
1819
            prev_0a = ord(_mem.get_raw()[0x0a]) & SAVE_MASK_0A
1820
            prev_0b = ord(_mem.get_raw()[0x0b]) & SAVE_MASK_0B
1821
            prev_0c = ord(_mem.get_raw()[0x0c]) & SAVE_MASK_0C
1822
            prev_0d = ord(_mem.get_raw()[0x0d]) & SAVE_MASK_0D
1823
            prev_0e = ord(_mem.get_raw()[0x0e]) & SAVE_MASK_0E
1824
            prev_0f = ord(_mem.get_raw()[0x0f]) & SAVE_MASK_0F
1825
            _mem.set_raw("\x00" * 10 +
1826
                         chr(prev_0a) + chr(prev_0b) + chr(prev_0c) +
1827
                         chr(prev_0d) + chr(prev_0e) + chr(prev_0f))
1828

    
1829
        if number < 200:
1830
            _mem4.channel_attributes[number].is_scanlist1 = 0
1831
            _mem4.channel_attributes[number].is_scanlist2 = 0
1832
            _mem4.channel_attributes[number].unknown1 = 0
1833
            _mem4.channel_attributes[number].unknown2 = 0
1834
            _mem4.channel_attributes[number].is_free = 1
1835
            _mem4.channel_attributes[number].band = 0x7
1836

    
1837
        # find tx frequency
1838
        if mem.duplex == '-':
1839
            txfreq = mem.freq - mem.offset
1840
        elif mem.duplex == '+':
1841
            txfreq = mem.freq + mem.offset
1842
        else:
1843
            txfreq = mem.freq
1844

    
1845
        # find band
1846
        band = _find_band(self, txfreq)
1847
        if band is False:
1848
            raise errors.RadioError(
1849
                    "Transmit frequency %.4fMHz is not supported by this radio"
1850
                    % txfreq/1000000.0)
1851

    
1852
        band = _find_band(self, mem.freq)
1853
        if band is False:
1854
            return mem
1855

    
1856
        # mode
1857
        if mem.mode == "NFM":
1858
            _mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
1859
            _mem.flags1 = _mem.flags1 & ~FLAGS1_ISAM
1860
        elif mem.mode == "FM":
1861
            _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1862
            _mem.flags1 = _mem.flags1 & ~FLAGS1_ISAM
1863
        elif mem.mode == "NAM":
1864
            _mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
1865
            _mem.flags1 = _mem.flags1 | FLAGS1_ISAM
1866
        elif mem.mode == "AM":
1867
            _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1868
            _mem.flags1 = _mem.flags1 | FLAGS1_ISAM
1869

    
1870
        # frequency/offset
1871
        _mem.freq = mem.freq/10
1872
        _mem.offset = mem.offset/10
1873

    
1874
        if mem.duplex == "off" or mem.duplex == "":
1875
            _mem.offset = 0
1876
            _mem.flags1 = _mem.flags1 & ~FLAGS1_OFFSET_MASK
1877
        elif mem.duplex == '-':
1878
            _mem.flags1 = (
1879
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_MINUS
1880
        elif mem.duplex == '+':
1881
            _mem.flags1 = (
1882
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_PLUS
1883

    
1884
        # set band
1885
        if number < 200:
1886
            _mem4.channel_attributes[number].is_free = 0
1887
            _mem4.channel_attributes[number].band = band
1888

    
1889
        # channels >200 are the 14 VFO chanells and don't have names
1890
        if number < 200:
1891
            _mem2 = self._memobj.channelname[number]
1892
            tag = mem.name.ljust(16)[:16]
1893
            _mem2.name = tag  # Store the alpha tag
1894

    
1895
        # tone data
1896
        self._set_tone(mem, _mem)
1897

    
1898
        # step
1899
        _mem.step = STEPS.index(mem.tuning_step)
1900

    
1901
        # tx power
1902
        if str(mem.power) == str(UVK5_POWER_LEVELS[2]):
1903
            _mem.flags2 = (
1904
                _mem.flags2 & ~FLAGS2_POWER_MASK) | FLAGS2_POWER_HIGH
1905
        elif str(mem.power) == str(UVK5_POWER_LEVELS[1]):
1906
            _mem.flags2 = (
1907
                _mem.flags2 & ~FLAGS2_POWER_MASK) | FLAGS2_POWER_MEDIUM
1908
        else:
1909
            _mem.flags2 = (_mem.flags2 & ~FLAGS2_POWER_MASK)
1910

    
1911
        for setting in mem.extra:
1912
            sname = setting.get_name()
1913
            svalue = setting.value.get_value()
1914

    
1915
            if sname == "bclo":
1916
                if svalue:
1917
                    _mem.flags2 = _mem.flags2 | FLAGS2_BCLO
1918
                else:
1919
                    _mem.flags2 = _mem.flags2 & ~FLAGS2_BCLO
1920

    
1921
            if sname == "pttid":
1922
                _mem.dtmf_flags = (
1923
                        (_mem.dtmf_flags & ~FLAGS_DTMF_PTTID_MASK)
1924
                        | (PTTID_LIST.index(svalue) << 1))
1925

    
1926
            if sname == "frev":
1927
                if svalue:
1928
                    _mem.flags2 = _mem.flags2 | FLAGS2_REVERSE
1929
                else:
1930
                    _mem.flags2 = _mem.flags2 & ~FLAGS2_REVERSE
1931

    
1932
            if sname == "dtmfdecode":
1933
                if svalue:
1934
                    _mem.dtmf_flags = _mem.dtmf_flags | FLAGS_DTMF_DECODE
1935
                else:
1936
                    _mem.dtmf_flags = _mem.dtmf_flags & ~FLAGS_DTMF_DECODE
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
(43-43/47)