Project

General

Profile

New Model #10478 » uvk5.py

uvk5.py chirp driver for UV-K5, version 20230619 - Jacek Lipkowski SQ5BPF, 06/19/2023 08:29 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 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
    for a in B:
575
        if mhz >= B[a][0] and mhz <= B[a][1]:
576
            return a
577
    return False
578

    
579

    
580
@directory.register
581
class UVK5Radio(chirp_common.CloneModeRadio):
582
    """Quansheng UV-K5"""
583
    VENDOR = "Quansheng"
584
    MODEL = "UV-K5"
585
    BAUD_RATE = 38400
586
    NEEDS_COMPAT_SERIAL = False
587
    FIRMWARE_VERSION = ""
588
    FIRMWARE_NOLIMITS = False
589

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

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

    
629
        # hack so we can input any frequency,
630
        # the 0.1 and 0.01 steps don't work unfortunately
631
        rf.valid_tuning_steps = [0.01, 0.1, 1.0] + STEPS
632

    
633
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
634
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
635
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
636

    
637
        rf.valid_characters = chirp_common.CHARSET_ASCII
638
        rf.valid_modes = ["FM", "NFM", "AM", "NAM"]
639
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
640

    
641
        rf.valid_skips = [""]
642

    
643
        # This radio supports memories 1-200, 201-214 are the VFO memories
644
        rf.memory_bounds = (1, 214)
645

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

    
657
    # Do a download of the radio from the serial port
658
    def sync_in(self):
659
        self._mmap = do_download(self)
660
        self.process_mmap()
661

    
662
    # Do an upload of the radio to the serial port
663
    def sync_out(self):
664
        do_upload(self)
665

    
666
    # Convert the raw byte array into a memory object structure
667
    def process_mmap(self):
668
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
669

    
670
    # Return a raw representation of the memory object, which
671
    # is very helpful for development
672
    def get_raw_memory(self, number):
673
        return repr(self._memobj.channel[number-1])
674

    
675
    def validate_memory(self, mem):
676
        msgs = super().validate_memory(mem)
677
        return msgs
678

    
679
    def _set_tone(self, mem, _mem):
680
        ((txmode, txtone, txpol),
681
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
682

    
683
        if txmode == "Tone":
684
            txtoval = CTCSS_TONES.index(txtone)
685
            txmoval = 0b01
686
        elif txmode == "DTCS":
687
            txmoval = txpol == "R" and 0b11 or 0b10
688
            txtoval = DTCS_CODES.index(txtone)
689
        else:
690
            txmoval = 0
691
            txtoval = 0
692

    
693
        if rxmode == "Tone":
694
            rxtoval = CTCSS_TONES.index(rxtone)
695
            rxmoval = 0b01
696
        elif rxmode == "DTCS":
697
            rxmoval = rxpol == "R" and 0b11 or 0b10
698
            rxtoval = DTCS_CODES.index(rxtone)
699
        else:
700
            rxmoval = 0
701
            rxtoval = 0
702

    
703
        _mem.code_flag = (_mem.code_flag & 0b11001100) | (
704
            txmoval << 4) | rxmoval
705
        _mem.rxcode = rxtoval
706
        _mem.txcode = txtoval
707

    
708
    def _get_tone(self, mem, _mem):
709
        rxtype = _mem.code_flag & 0x03
710
        txtype = (_mem.code_flag >> 4) & 0x03
711
        rx_tmode = TMODES[rxtype]
712
        tx_tmode = TMODES[txtype]
713

    
714
        rx_tone = tx_tone = None
715

    
716
        if tx_tmode == "Tone":
717
            if _mem.txcode < len(CTCSS_TONES):
718
                tx_tone = CTCSS_TONES[_mem.txcode]
719
            else:
720
                tx_tone = 0
721
                tx_tmode = ""
722
        elif tx_tmode == "DTCS":
723
            if _mem.txcode < len(DTCS_CODES):
724
                tx_tone = DTCS_CODES[_mem.txcode]
725
            else:
726
                tx_tone = 0
727
                tx_tmode = ""
728

    
729
        if rx_tmode == "Tone":
730
            if _mem.rxcode < len(CTCSS_TONES):
731
                rx_tone = CTCSS_TONES[_mem.rxcode]
732
            else:
733
                rx_tone = 0
734
                rx_tmode = ""
735
        elif rx_tmode == "DTCS":
736
            if _mem.rxcode < len(DTCS_CODES):
737
                rx_tone = DTCS_CODES[_mem.rxcode]
738
            else:
739
                rx_tone = 0
740
                rx_tmode = ""
741

    
742
        tx_pol = txtype == 0x03 and "R" or "N"
743
        rx_pol = rxtype == 0x03 and "R" or "N"
744

    
745
        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
746
                                       (rx_tmode, rx_tone, rx_pol))
747

    
748
    # Extract a high-level memory object from the low-level memory map
749
    # This is called to populate a memory in the UI
750
    def get_memory(self, number2):
751
        number = number2-1  # in the radio memories start with 0
752

    
753
        mem = chirp_common.Memory()
754

    
755
        # cutting and pasting configs from different radios
756
        # might try to set channel 0
757
        if number2 == 0:
758
            LOG.warning("Attempt to get channel 0")
759
            return mem
760

    
761
        _mem = self._memobj.channel[number]
762

    
763
        tmpcomment = ""
764

    
765
        mem.number = number2
766

    
767
        is_empty = False
768
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
769
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
770
            is_empty = True
771

    
772
        tmpscn = SCANLIST_LIST[0]
773

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

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

    
808
            rs = RadioSetting("scanlists", "Scanlists", RadioSettingValueList(
809
                SCANLIST_LIST, SCANLIST_LIST[0]))
810
            mem.extra.append(rs)
811

    
812
            # actually the step and duplex are overwritten by chirp based on
813
            # bandplan. they are here to document sane defaults for IARU r1
814
            # mem.tuning_step = 25.0
815
            # mem.duplex = "off"
816

    
817
            return mem
818

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

    
830
        # Convert your low-level frequency to Hertz
831
        mem.freq = int(_mem.freq)*10
832
        mem.offset = int(_mem.offset)*10
833

    
834
        if (mem.offset == 0):
835
            mem.duplex = ''
836
        else:
837
            if (_mem.flags1 & FLAGS1_OFFSET_MASK) == FLAGS1_OFFSET_MINUS:
838
                mem.duplex = '-'
839
            elif (_mem.flags1 & FLAGS1_OFFSET_MASK) == FLAGS1_OFFSET_PLUS:
840
                mem.duplex = '+'
841
            else:
842
                mem.duplex = ''
843

    
844
        # tone data
845
        self._get_tone(mem, _mem)
846

    
847
        # mode
848
        if (_mem.flags1 & FLAGS1_ISAM) > 0:
849
            if (_mem.flags2 & FLAGS2_BANDWIDTH) > 0:
850
                mem.mode = "NAM"
851
            else:
852
                mem.mode = "AM"
853
        else:
854
            if (_mem.flags2 & FLAGS2_BANDWIDTH) > 0:
855
                mem.mode = "NFM"
856
            else:
857
                mem.mode = "FM"
858

    
859
        # tuning step
860
        tstep = _mem.step & 0x7
861
        if tstep < len(STEPS):
862
            mem.tuning_step = STEPS[tstep]
863
        else:
864
            mem.tuning_step = 2.5
865

    
866
        # power
867
        if (_mem.flags2 & FLAGS2_POWER_MASK) == FLAGS2_POWER_HIGH:
868
            mem.power = UVK5_POWER_LEVELS[2]
869
        elif (_mem.flags2 & FLAGS2_POWER_MASK) == FLAGS2_POWER_MEDIUM:
870
            mem.power = UVK5_POWER_LEVELS[1]
871
        else:
872
            mem.power = UVK5_POWER_LEVELS[0]
873

    
874
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
875
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
876
            mem.empty = True
877
        else:
878
            mem.empty = False
879

    
880
        mem.extra = RadioSettingGroup("Extra", "extra")
881

    
882
        # BCLO
883
        is_bclo = bool(_mem.flags2 & FLAGS2_BCLO > 0)
884
        rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(is_bclo))
885
        mem.extra.append(rs)
886
        tmpcomment += "BCLO:"+(is_bclo and "ON" or "off")+" "
887

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

    
894
        # PTTID
895
        pttid = (_mem.dtmf_flags & FLAGS_DTMF_PTTID_MASK) >> 1
896
        rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
897
            PTTID_LIST, PTTID_LIST[pttid]))
898
        mem.extra.append(rs)
899
        tmpcomment += "PTTid:"+PTTID_LIST[pttid]+" "
900

    
901
        # DTMF DECODE
902
        is_dtmf = bool(_mem.dtmf_flags & FLAGS_DTMF_DECODE > 0)
903
        rs = RadioSetting("dtmfdecode", "DTMF decode",
904
                          RadioSettingValueBoolean(is_dtmf))
905
        mem.extra.append(rs)
906
        tmpcomment += "DTMFdecode:"+(is_dtmf and "ON" or "off")+" "
907

    
908
        # Scrambler
909
        if _mem.scrambler & 0x0f < len(SCRAMBLER_LIST):
910
            enc = _mem.scrambler & 0x0f
911
        else:
912
            enc = 0
913

    
914
        rs = RadioSetting("scrambler", "Scrambler", RadioSettingValueList(
915
            SCRAMBLER_LIST, SCRAMBLER_LIST[enc]))
916
        mem.extra.append(rs)
917
        tmpcomment += "Scrambler:"+SCRAMBLER_LIST[enc]+" "
918

    
919
        # scanlists
920
        pttid = (_mem.dtmf_flags & FLAGS_DTMF_PTTID_MASK) >> 1
921
        rs = RadioSetting("scanlists", "Scanlists", RadioSettingValueList(
922
            SCANLIST_LIST, tmpscn))
923
        mem.extra.append(rs)
924

    
925
        return mem
926

    
927
    def set_settings(self, settings):
928
        _mem = self._memobj
929
        for element in settings:
930
            if not isinstance(element, RadioSetting):
931
                self.set_settings(element)
932
                continue
933

    
934
            # basic settings
935

    
936
            # call channel
937
            if element.get_name() == "call_channel":
938
                _mem.call_channel = int(element.value)-1
939

    
940
            # squelch
941
            if element.get_name() == "squelch":
942
                _mem.squelch = int(element.value)
943
            # TOT
944
            if element.get_name() == "tot":
945
                _mem.max_talk_time = int(element.value)
946
            # NOAA autoscan
947
            if element.get_name() == "noaa_autoscan":
948
                _mem.noaa_autoscan = element.value and 1 or 0
949

    
950
            # vox level
951
            if element.get_name() == "vox_level":
952
                _mem.vox_level = int(element.value)-1
953

    
954
            # mic gain
955
            if element.get_name() == "mic_gain":
956
                _mem.mic_gain = int(element.value)
957

    
958
            # Channel display mode
959
            if element.get_name() == "channel_display_mode":
960
                _mem.channel_display_mode = CHANNELDISP_LIST.index(
961
                    str(element.value))
962

    
963
            # Crossband receiving/transmitting
964
            if element.get_name() == "crossband":
965
                _mem.crossband = CROSSBAND_LIST.index(str(element.value))
966

    
967
            # Battery Save
968
            if element.get_name() == "battery_save":
969
                _mem.battery_save = BATSAVE_LIST.index(str(element.value))
970
            # Dual Watch
971
            if element.get_name() == "dualwatch":
972
                _mem.dual_watch = DUALWATCH_LIST.index(str(element.value))
973

    
974
            # Tail tone elimination
975
            if element.get_name() == "tail_note_elimination":
976
                _mem.tail_note_elimination = element.value and 1 or 0
977

    
978
            # VFO Open
979
            if element.get_name() == "vfo_open":
980
                _mem.vfo_open = element.value and 1 or 0
981

    
982
            # Beep control
983
            if element.get_name() == "beep_control":
984
                _mem.beep_control = element.value and 1 or 0
985

    
986
            # Scan resume mode
987
            if element.get_name() == "scan_resume_mode":
988
                _mem.scan_resume_mode = SCANRESUME_LIST.index(
989
                    str(element.value))
990

    
991
            # Auto keypad lock
992
            if element.get_name() == "auto_keypad_lock":
993
                _mem.auto_keypad_lock = element.value and 1 or 0
994

    
995
            # Power on display mode
996
            if element.get_name() == "welcome_mode":
997
                _mem.power_on_dispmode = WELCOME_LIST.index(str(element.value))
998

    
999
            # Keypad Tone
1000
            if element.get_name() == "keypad_tone":
1001
                _mem.keypad_tone = KEYPADTONE_LIST.index(str(element.value))
1002

    
1003
            # Language
1004
            if element.get_name() == "language":
1005
                _mem.language = LANGUAGE_LIST.index(str(element.value))
1006

    
1007
            # Alarm mode
1008
            if element.get_name() == "alarm_mode":
1009
                _mem.alarm_mode = ALARMMODE_LIST.index(str(element.value))
1010

    
1011
            # Reminding of end of talk
1012
            if element.get_name() == "reminding_of_end_talk":
1013
                _mem.reminding_of_end_talk = REMENDOFTALK_LIST.index(
1014
                    str(element.value))
1015

    
1016
            # Repeater tail tone elimination
1017
            if element.get_name() == "repeater_tail_elimination":
1018
                _mem.repeater_tail_elimination = RTE_LIST.index(
1019
                    str(element.value))
1020

    
1021
            # Logo string 1
1022
            if element.get_name() == "logo1":
1023
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
1024
                _mem.logo_line1 = b[0:12]+"\xff\xff\xff\xff"
1025

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

    
1031
            # unlock settings
1032

    
1033
            # FLOCK
1034
            if element.get_name() == "flock":
1035
                _mem.int_flock = FLOCK_LIST.index(str(element.value))
1036

    
1037
            # 350TX
1038
            if element.get_name() == "350tx":
1039
                _mem.int_350tx = element.value and 1 or 0
1040

    
1041
            # UNKNOWN1
1042
            if element.get_name() == "unknown1":
1043
                _mem.int_unknown1 = element.value and 1 or 0
1044

    
1045
            # 200TX
1046
            if element.get_name() == "200tx":
1047
                _mem.int_200tx = element.value and 1 or 0
1048

    
1049
            # 500TX
1050
            if element.get_name() == "500tx":
1051
                _mem.int_500tx = element.value and 1 or 0
1052

    
1053
            # 350EN
1054
            if element.get_name() == "350en":
1055
                _mem.int_350en = element.value and 1 or 0
1056

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

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

    
1074
            # dtmf settings
1075
            if element.get_name() == "dtmf_side_tone":
1076
                _mem.dtmf_settings.side_tone = \
1077
                        element.value and 1 or 0
1078

    
1079
            if element.get_name() == "dtmf_separate_code":
1080
                _mem.dtmf_settings.separate_code = str(element.value)
1081

    
1082
            if element.get_name() == "dtmf_group_call_code":
1083
                _mem.dtmf_settings.group_call_code = element.value
1084

    
1085
            if element.get_name() == "dtmf_decode_response":
1086
                _mem.dtmf_settings.decode_response = \
1087
                        DTMF_DECODE_RESPONSE_LIST.index(str(element.value))
1088

    
1089
            if element.get_name() == "dtmf_auto_reset_time":
1090
                _mem.dtmf_settings.auto_reset_time = \
1091
                        int(int(element.value)/10)
1092

    
1093
            if element.get_name() == "dtmf_preload_time":
1094
                _mem.dtmf_settings.preload_time = \
1095
                        int(int(element.value)/10)
1096

    
1097
            if element.get_name() == "dtmf_first_code_persist_time":
1098
                _mem.dtmf_settings.first_code_persist_time = \
1099
                        int(int(element.value)/10)
1100

    
1101
            if element.get_name() == "dtmf_hash_persist_time":
1102
                _mem.dtmf_settings.hash_persist_time = \
1103
                        int(int(element.value)/10)
1104

    
1105
            if element.get_name() == "dtmf_code_persist_time":
1106
                _mem.dtmf_settings.code_persist_time = \
1107
                        int(int(element.value)/10)
1108

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

    
1113
            if element.get_name() == "dtmf_permit_remote_kill":
1114
                _mem.dtmf_settings.permit_remote_kill = \
1115
                        element.value and 1 or 0
1116

    
1117
            if element.get_name() == "dtmf_dtmf_local_code":
1118
                k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*3
1119
                _mem.dtmf_settings_numbers.dtmf_local_code = k[0:3]
1120

    
1121
            if element.get_name() == "dtmf_dtmf_up_code":
1122
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*16
1123
                _mem.dtmf_settings_numbers.dtmf_up_code = k[0:16]
1124

    
1125
            if element.get_name() == "dtmf_dtmf_down_code":
1126
                k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*16
1127
                _mem.dtmf_settings_numbers.dtmf_down_code = k[0:16]
1128

    
1129
            if element.get_name() == "dtmf_kill_code":
1130
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*5
1131
                _mem.dtmf_settings_numbers.kill_code = k[0:5]
1132

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

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

    
1144
                varnumname = "DTMFNUM_" + str(i)
1145
                if element.get_name() == varnumname:
1146
                    k = str(element.value).rstrip("\x20\xff\x00") + "\xff"*3
1147
                    _mem.dtmfcontact[i-1].number = k[0:3]
1148

    
1149
            # scanlist stuff
1150
            if element.get_name() == "scanlist_default":
1151
                val = (int(element.value) == 2) and 1 or 0
1152
                _mem.scanlist_default = val
1153

    
1154
            if element.get_name() == "scanlist1_priority_scan":
1155
                _mem.scanlist1_priority_scan = \
1156
                        element.value and 1 or 0
1157

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

    
1162
            if element.get_name() == "scanlist1_priority_ch1" or \
1163
                    element.get_name() == "scanlist1_priority_ch2" or \
1164
                    element.get_name() == "scanlist2_priority_ch1" or \
1165
                    element.get_name() == "scanlist2_priority_ch2":
1166

    
1167
                val = int(element.value)
1168

    
1169
                if val > 200 or val < 1:
1170
                    val = 0xff
1171
                else:
1172
                    val -= 1
1173

    
1174
                if element.get_name() == "scanlist1_priority_ch1":
1175
                    _mem.scanlist1_priority_ch1 = val
1176
                if element.get_name() == "scanlist1_priority_ch2":
1177
                    _mem.scanlist1_priority_ch2 = val
1178
                if element.get_name() == "scanlist2_priority_ch1":
1179
                    _mem.scanlist2_priority_ch1 = val
1180
                if element.get_name() == "scanlist2_priority_ch2":
1181
                    _mem.scanlist2_priority_ch2 = val
1182

    
1183
            if element.get_name() == "key1_shortpress_action":
1184
                _mem.key1_shortpress_action = KEYACTIONS_LIST.index(
1185
                        str(element.value))
1186

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

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

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

    
1199
    def get_settings(self):
1200
        _mem = self._memobj
1201
        basic = RadioSettingGroup("basic", "Basic Settings")
1202
        keya = RadioSettingGroup("keya", "Programmable keys")
1203
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1204
        dtmfc = RadioSettingGroup("dtmfc", "DTMF Contacts")
1205
        scanl = RadioSettingGroup("scn", "Scan Lists")
1206
        unlock = RadioSettingGroup("unlock", "Unlock Settings")
1207
        fmradio = RadioSettingGroup("fmradio", "FM Radio")
1208

    
1209
        roinfo = RadioSettingGroup("roinfo", "Driver information")
1210

    
1211
        top = RadioSettings(
1212
                basic, keya, dtmf, dtmfc, scanl, unlock, fmradio, roinfo)
1213

    
1214
        # Programmable keys
1215
        tmpval = int(_mem.key1_shortpress_action)
1216
        if tmpval >= len(KEYACTIONS_LIST):
1217
            tmpval = 0
1218
        rs = RadioSetting("key1_shortpress_action", "Side key 1 short press",
1219
                          RadioSettingValueList(
1220
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1221
        keya.append(rs)
1222

    
1223
        tmpval = int(_mem.key1_longpress_action)
1224
        if tmpval >= len(KEYACTIONS_LIST):
1225
            tmpval = 0
1226
        rs = RadioSetting("key1_longpress_action", "Side key 1 long press",
1227
                          RadioSettingValueList(
1228
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1229
        keya.append(rs)
1230

    
1231
        tmpval = int(_mem.key2_shortpress_action)
1232
        if tmpval >= len(KEYACTIONS_LIST):
1233
            tmpval = 0
1234
        rs = RadioSetting("key2_shortpress_action", "Side key 2 short press",
1235
                          RadioSettingValueList(
1236
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1237
        keya.append(rs)
1238

    
1239
        tmpval = int(_mem.key2_longpress_action)
1240
        if tmpval >= len(KEYACTIONS_LIST):
1241
            tmpval = 0
1242
        rs = RadioSetting("key2_longpress_action", "Side key 2 long press",
1243
                          RadioSettingValueList(
1244
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1245
        keya.append(rs)
1246

    
1247
        # DTMF settings
1248
        tmppr = bool(_mem.dtmf_settings.side_tone > 0)
1249
        rs = RadioSetting(
1250
                "dtmf_side_tone",
1251
                "DTMF Sidetone",
1252
                RadioSettingValueBoolean(tmppr))
1253
        dtmf.append(rs)
1254

    
1255
        tmpval = str(_mem.dtmf_settings.separate_code)
1256
        if tmpval not in DTMF_CODE_CHARS:
1257
            tmpval = '*'
1258
        val = RadioSettingValueString(1, 1, tmpval)
1259
        val.set_charset(DTMF_CODE_CHARS)
1260
        rs = RadioSetting("dtmf_separate_code", "Separate Code", val)
1261
        dtmf.append(rs)
1262

    
1263
        tmpval = str(_mem.dtmf_settings.group_call_code)
1264
        if tmpval not in DTMF_CODE_CHARS:
1265
            tmpval = '#'
1266
        val = RadioSettingValueString(1, 1, tmpval)
1267
        val.set_charset(DTMF_CODE_CHARS)
1268
        rs = RadioSetting("dtmf_group_call_code", "Group Call Code", val)
1269
        dtmf.append(rs)
1270

    
1271
        tmpval = _mem.dtmf_settings.decode_response
1272
        if tmpval >= len(DTMF_DECODE_RESPONSE_LIST):
1273
            tmpval = 0
1274
        rs = RadioSetting("dtmf_decode_response", "Decode Response",
1275
                          RadioSettingValueList(
1276
                              DTMF_DECODE_RESPONSE_LIST,
1277
                              DTMF_DECODE_RESPONSE_LIST[tmpval]))
1278
        dtmf.append(rs)
1279

    
1280
        tmpval = _mem.dtmf_settings.auto_reset_time
1281
        if tmpval > 60 or tmpval < 5:
1282
            tmpval = 5
1283
        rs = RadioSetting("dtmf_auto_reset_time",
1284
                          "Auto reset time (s)",
1285
                          RadioSettingValueInteger(5, 60, tmpval))
1286
        dtmf.append(rs)
1287

    
1288
        tmpval = int(_mem.dtmf_settings.preload_time)
1289
        if tmpval > 100 or tmpval < 3:
1290
            tmpval = 30
1291
        tmpval *= 10
1292
        rs = RadioSetting("dtmf_preload_time",
1293
                          "Pre-load time (ms)",
1294
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1295
        dtmf.append(rs)
1296

    
1297
        tmpval = int(_mem.dtmf_settings.first_code_persist_time)
1298
        if tmpval > 100 or tmpval < 3:
1299
            tmpval = 30
1300
        tmpval *= 10
1301
        rs = RadioSetting("dtmf_first_code_persist_time",
1302
                          "First code persist time (ms)",
1303
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1304
        dtmf.append(rs)
1305

    
1306
        tmpval = int(_mem.dtmf_settings.hash_persist_time)
1307
        if tmpval > 100 or tmpval < 3:
1308
            tmpval = 30
1309
        tmpval *= 10
1310
        rs = RadioSetting("dtmf_hash_persist_time",
1311
                          "#/* persist time (ms)",
1312
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1313
        dtmf.append(rs)
1314

    
1315
        tmpval = int(_mem.dtmf_settings.code_persist_time)
1316
        if tmpval > 100 or tmpval < 3:
1317
            tmpval = 30
1318
        tmpval *= 10
1319
        rs = RadioSetting("dtmf_code_persist_time",
1320
                          "Code persist time (ms)",
1321
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1322
        dtmf.append(rs)
1323

    
1324
        tmpval = int(_mem.dtmf_settings.code_interval_time)
1325
        if tmpval > 100 or tmpval < 3:
1326
            tmpval = 30
1327
        tmpval *= 10
1328
        rs = RadioSetting("dtmf_code_interval_time",
1329
                          "Code interval time (ms)",
1330
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1331
        dtmf.append(rs)
1332

    
1333
        tmpval = bool(_mem.dtmf_settings.permit_remote_kill > 0)
1334
        rs = RadioSetting(
1335
                "dtmf_permit_remote_kill",
1336
                "Permit remote kill",
1337
                RadioSettingValueBoolean(tmpval))
1338
        dtmf.append(rs)
1339

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

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

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

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

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

    
1414
        val = RadioSettingValueString(0, 80,
1415
                                      "All DTMF Contacts are 3 codes "
1416
                                      "(valid: 0-9 * # ABCD), "
1417
                                      "or an empty string")
1418
        val.set_mutable(False)
1419
        rs = RadioSetting("dtmf_descr1", "DTMF Contacts", val)
1420
        dtmfc.append(rs)
1421

    
1422
        for i in range(1, 17):
1423
            varname = "DTMF_"+str(i)
1424
            varnumname = "DTMFNUM_"+str(i)
1425
            vardescr = "DTMF Contact "+str(i)+" name"
1426
            varinumdescr = "DTMF Contact "+str(i)+" number"
1427

    
1428
            cntn = str(_mem.dtmfcontact[i-1].name).strip("\x20\x00\xff")
1429
            cntnum = str(_mem.dtmfcontact[i-1].number).strip("\x20\x00\xff")
1430

    
1431
            val = RadioSettingValueString(0, 8, cntn)
1432
            rs = RadioSetting(varname, vardescr, val)
1433
            dtmfc.append(rs)
1434

    
1435
            val = RadioSettingValueString(0, 3, cntnum)
1436
            val.set_charset(DTMF_CHARS)
1437
            rs = RadioSetting(varnumname, varinumdescr, val)
1438
            dtmfc.append(rs)
1439

    
1440
        # scanlists
1441
        if _mem.scanlist_default == 1:
1442
            tmpsc = 2
1443
        else:
1444
            tmpsc = 1
1445
        rs = RadioSetting("scanlist_default",
1446
                          "Default scanlist",
1447
                          RadioSettingValueInteger(1, 2, tmpsc))
1448
        scanl.append(rs)
1449

    
1450
        tmppr = bool((_mem.scanlist1_priority_scan & 1) > 0)
1451
        rs = RadioSetting(
1452
                "scanlist1_priority_scan",
1453
                "Scanlist 1 priority channel scan",
1454
                RadioSettingValueBoolean(tmppr))
1455
        scanl.append(rs)
1456

    
1457
        tmpch = _mem.scanlist1_priority_ch1 + 1
1458
        if tmpch > 200:
1459
            tmpch = 0
1460
        rs = RadioSetting("scanlist1_priority_ch1",
1461
                          "Scanlist 1 priority channel 1 (0 - off)",
1462
                          RadioSettingValueInteger(0, 200, tmpch))
1463
        scanl.append(rs)
1464

    
1465
        tmpch = _mem.scanlist1_priority_ch2 + 1
1466
        if tmpch > 200:
1467
            tmpch = 0
1468
        rs = RadioSetting("scanlist1_priority_ch2",
1469
                          "Scanlist 1 priority channel 2 (0 - off)",
1470
                          RadioSettingValueInteger(0, 200, tmpch))
1471
        scanl.append(rs)
1472

    
1473
        tmppr = bool((_mem.scanlist2_priority_scan & 1) > 0)
1474
        rs = RadioSetting(
1475
                "scanlist2_priority_scan",
1476
                "Scanlist 2 priority channel scan",
1477
                RadioSettingValueBoolean(tmppr))
1478
        scanl.append(rs)
1479

    
1480
        tmpch = _mem.scanlist2_priority_ch1 + 1
1481
        if tmpch > 200:
1482
            tmpch = 0
1483
        rs = RadioSetting("scanlist2_priority_ch1",
1484
                          "Scanlist 2 priority channel 1 (0 - off)",
1485
                          RadioSettingValueInteger(0, 200, tmpch))
1486
        scanl.append(rs)
1487

    
1488
        tmpch = _mem.scanlist2_priority_ch2 + 1
1489
        if tmpch > 200:
1490
            tmpch = 0
1491
        rs = RadioSetting("scanlist2_priority_ch2",
1492
                          "Scanlist 2 priority channel 2 (0 - off)",
1493
                          RadioSettingValueInteger(0, 200, tmpch))
1494
        scanl.append(rs)
1495

    
1496
        # basic settings
1497

    
1498
        # call channel
1499
        tmpc = _mem.call_channel+1
1500
        if tmpc > 200:
1501
            tmpc = 1
1502
        rs = RadioSetting("call_channel", "One key call channel",
1503
                          RadioSettingValueInteger(1, 200, tmpc))
1504
        basic.append(rs)
1505

    
1506
        # squelch
1507
        tmpsq = _mem.squelch
1508
        if tmpsq > 9:
1509
            tmpsq = 1
1510
        rs = RadioSetting("squelch", "Squelch",
1511
                          RadioSettingValueInteger(0, 9, tmpsq))
1512
        basic.append(rs)
1513

    
1514
        # TOT
1515
        tmptot = _mem.max_talk_time
1516
        if tmptot > 10:
1517
            tmptot = 10
1518
        rs = RadioSetting(
1519
                "tot",
1520
                "Max talk time [min]",
1521
                RadioSettingValueInteger(0, 10, tmptot))
1522
        basic.append(rs)
1523

    
1524
        # NOAA autoscan
1525
        rs = RadioSetting(
1526
                "noaa_autoscan",
1527
                "NOAA Autoscan", RadioSettingValueBoolean(
1528
                    bool(_mem.noaa_autoscan > 0)))
1529
        basic.append(rs)
1530

    
1531
        # VOX Level
1532
        tmpvox = _mem.vox_level+1
1533
        if tmpvox > 10:
1534
            tmpvox = 10
1535
        rs = RadioSetting("vox_level", "VOX Level",
1536
                          RadioSettingValueInteger(1, 10, tmpvox))
1537
        basic.append(rs)
1538

    
1539
        # Mic gain
1540
        tmpmicgain = _mem.mic_gain
1541
        if tmpmicgain > 4:
1542
            tmpmicgain = 4
1543
        rs = RadioSetting("mic_gain", "Mic Gain",
1544
                          RadioSettingValueInteger(0, 4, tmpmicgain))
1545
        basic.append(rs)
1546

    
1547
        # Channel display mode
1548
        tmpchdispmode = _mem.channel_display_mode
1549
        if tmpchdispmode >= len(CHANNELDISP_LIST):
1550
            tmpchdispmode = 0
1551
        rs = RadioSetting(
1552
                "channel_display_mode",
1553
                "Channel display mode",
1554
                RadioSettingValueList(
1555
                    CHANNELDISP_LIST,
1556
                    CHANNELDISP_LIST[tmpchdispmode]))
1557
        basic.append(rs)
1558

    
1559
        # Crossband receiving/transmitting
1560
        tmpcross = _mem.crossband
1561
        if tmpcross >= len(CROSSBAND_LIST):
1562
            tmpcross = 0
1563
        rs = RadioSetting(
1564
                "crossband",
1565
                "Cross-band receiving/transmitting",
1566
                RadioSettingValueList(
1567
                    CROSSBAND_LIST,
1568
                    CROSSBAND_LIST[tmpcross]))
1569
        basic.append(rs)
1570

    
1571
        # Battery save
1572
        tmpbatsave = _mem.battery_save
1573
        if tmpbatsave >= len(BATSAVE_LIST):
1574
            tmpbatsave = BATSAVE_LIST.index("1:4")
1575
        rs = RadioSetting(
1576
                "battery_save",
1577
                "Battery Save",
1578
                RadioSettingValueList(
1579
                    BATSAVE_LIST,
1580
                    BATSAVE_LIST[tmpbatsave]))
1581
        basic.append(rs)
1582

    
1583
        # Dual watch
1584
        tmpdual = _mem.dual_watch
1585
        if tmpdual >= len(DUALWATCH_LIST):
1586
            tmpdual = 0
1587
        rs = RadioSetting("dualwatch", "Dual Watch", RadioSettingValueList(
1588
            DUALWATCH_LIST, DUALWATCH_LIST[tmpdual]))
1589
        basic.append(rs)
1590

    
1591
        # Tail tone elimination
1592
        rs = RadioSetting(
1593
                "tail_note_elimination",
1594
                "Tail tone elimination",
1595
                RadioSettingValueBoolean(
1596
                    bool(_mem.tail_note_elimination > 0)))
1597
        basic.append(rs)
1598

    
1599
        # VFO open
1600
        rs = RadioSetting("vfo_open", "VFO open",
1601
                          RadioSettingValueBoolean(bool(_mem.vfo_open > 0)))
1602
        basic.append(rs)
1603

    
1604
        # Beep control
1605
        rs = RadioSetting(
1606
                "beep_control",
1607
                "Beep control",
1608
                RadioSettingValueBoolean(bool(_mem.beep_control > 0)))
1609
        basic.append(rs)
1610

    
1611
        # Scan resume mode
1612
        tmpscanres = _mem.scan_resume_mode
1613
        if tmpscanres >= len(SCANRESUME_LIST):
1614
            tmpscanres = 0
1615
        rs = RadioSetting(
1616
                "scan_resume_mode",
1617
                "Scan resume mode",
1618
                RadioSettingValueList(
1619
                    SCANRESUME_LIST,
1620
                    SCANRESUME_LIST[tmpscanres]))
1621
        basic.append(rs)
1622

    
1623
        # Auto keypad lock
1624
        rs = RadioSetting(
1625
                "auto_keypad_lock",
1626
                "Auto keypad lock",
1627
                RadioSettingValueBoolean(bool(_mem.auto_keypad_lock > 0)))
1628
        basic.append(rs)
1629

    
1630
        # Power on display mode
1631
        tmpdispmode = _mem.power_on_dispmode
1632
        if tmpdispmode >= len(WELCOME_LIST):
1633
            tmpdispmode = 0
1634
        rs = RadioSetting(
1635
                "welcome_mode",
1636
                "Power on display mode",
1637
                RadioSettingValueList(
1638
                    WELCOME_LIST,
1639
                    WELCOME_LIST[tmpdispmode]))
1640
        basic.append(rs)
1641

    
1642
        # Keypad Tone
1643
        tmpkeypadtone = _mem.keypad_tone
1644
        if tmpkeypadtone >= len(KEYPADTONE_LIST):
1645
            tmpkeypadtone = 0
1646
        rs = RadioSetting("keypad_tone", "Keypad tone", RadioSettingValueList(
1647
            KEYPADTONE_LIST, KEYPADTONE_LIST[tmpkeypadtone]))
1648
        basic.append(rs)
1649

    
1650
        # Language
1651
        tmplanguage = _mem.language
1652
        if tmplanguage >= len(LANGUAGE_LIST):
1653
            tmplanguage = 0
1654
        rs = RadioSetting("language", "Language", RadioSettingValueList(
1655
            LANGUAGE_LIST, LANGUAGE_LIST[tmplanguage]))
1656
        basic.append(rs)
1657

    
1658
        # Alarm mode
1659
        tmpalarmmode = _mem.alarm_mode
1660
        if tmpalarmmode >= len(ALARMMODE_LIST):
1661
            tmpalarmmode = 0
1662
        rs = RadioSetting("alarm_mode", "Alarm mode", RadioSettingValueList(
1663
            ALARMMODE_LIST, ALARMMODE_LIST[tmpalarmmode]))
1664
        basic.append(rs)
1665

    
1666
        # Reminding of end of talk
1667
        tmpalarmmode = _mem.reminding_of_end_talk
1668
        if tmpalarmmode >= len(REMENDOFTALK_LIST):
1669
            tmpalarmmode = 0
1670
        rs = RadioSetting(
1671
                "reminding_of_end_talk",
1672
                "Reminding of end of talk",
1673
                RadioSettingValueList(
1674
                    REMENDOFTALK_LIST,
1675
                    REMENDOFTALK_LIST[tmpalarmmode]))
1676
        basic.append(rs)
1677

    
1678
        # Repeater tail tone elimination
1679
        tmprte = _mem.repeater_tail_elimination
1680
        if tmprte >= len(RTE_LIST):
1681
            tmprte = 0
1682
        rs = RadioSetting(
1683
                "repeater_tail_elimination",
1684
                "Repeater tail tone elimination",
1685
                RadioSettingValueList(RTE_LIST, RTE_LIST[tmprte]))
1686
        basic.append(rs)
1687

    
1688
        # Logo string 1
1689
        logo1 = str(_mem.logo_line1).strip("\x20\x00\xff") + "\x00"
1690
        logo1 = _getstring(logo1.encode('ascii', errors='ignore'), 0, 12)
1691
        rs = RadioSetting("logo1", "Logo string 1 (12 characters)",
1692
                          RadioSettingValueString(0, 12, logo1))
1693
        basic.append(rs)
1694

    
1695
        # Logo string 2
1696
        logo2 = str(_mem.logo_line2).strip("\x20\x00\xff") + "\x00"
1697
        logo2 = _getstring(logo2.encode('ascii', errors='ignore'), 0, 12)
1698
        rs = RadioSetting("logo2", "Logo string 2 (12 characters)",
1699
                          RadioSettingValueString(0, 12, logo2))
1700
        basic.append(rs)
1701

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

    
1713
            fmradio.append(rs)
1714

    
1715
        # unlock settings
1716

    
1717
        # F-LOCK
1718
        tmpflock = _mem.int_flock
1719
        if tmpflock >= len(FLOCK_LIST):
1720
            tmpflock = 0
1721
        rs = RadioSetting(
1722
            "flock", "F-LOCK",
1723
            RadioSettingValueList(FLOCK_LIST, FLOCK_LIST[tmpflock]))
1724
        unlock.append(rs)
1725

    
1726
        # 350TX
1727
        rs = RadioSetting("350tx", "350TX", RadioSettingValueBoolean(
1728
            bool(_mem.int_350tx > 0)))
1729
        unlock.append(rs)
1730

    
1731
        # unknown1
1732
        rs = RadioSetting("unknown11", "UNKNOWN1",
1733
                          RadioSettingValueBoolean(
1734
                              bool(_mem.int_unknown1 > 0)))
1735
        unlock.append(rs)
1736

    
1737
        # 200TX
1738
        rs = RadioSetting("200tx", "200TX", RadioSettingValueBoolean(
1739
            bool(_mem.int_200tx > 0)))
1740
        unlock.append(rs)
1741

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

    
1747
        # 350EN
1748
        rs = RadioSetting("350en", "350EN", RadioSettingValueBoolean(
1749
            bool(_mem.int_350en > 0)))
1750
        unlock.append(rs)
1751

    
1752
        # SCREEN
1753
        rs = RadioSetting("screen", "SCREEN", RadioSettingValueBoolean(
1754
            bool(_mem.int_screen > 0)))
1755
        unlock.append(rs)
1756

    
1757
        # readonly info
1758
        # Firmware
1759
        if self.FIRMWARE_VERSION == "":
1760
            firmware = "To get the firmware version please download"
1761
            "the image from the radio first"
1762
        else:
1763
            firmware = self.FIRMWARE_VERSION
1764

    
1765
        val = RadioSettingValueString(0, 128, firmware)
1766
        val.set_mutable(False)
1767
        rs = RadioSetting("fw_ver", "Firmware Version", val)
1768
        roinfo.append(rs)
1769

    
1770
        # Driver version
1771
        val = RadioSettingValueString(0, 128, DRIVER_VERSION)
1772
        val.set_mutable(False)
1773
        rs = RadioSetting("driver_ver", "Driver version", val)
1774
        roinfo.append(rs)
1775

    
1776
        # No limits version for hacked firmware
1777
        val = RadioSettingValueBoolean(self.FIRMWARE_NOLIMITS)
1778
        val.set_mutable(False)
1779
        rs = RadioSetting("nolimits", "Limits disabled for modified firmware",
1780
                          val)
1781
        roinfo.append(rs)
1782

    
1783
        return top
1784

    
1785
    # Store details about a high-level memory to the memory map
1786
    # This is called when a user edits a memory in the UI
1787
    def set_memory(self, mem):
1788
        number = mem.number-1
1789

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

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

    
1824
        if number < 200:
1825
            _mem4.channel_attributes[number].is_scanlist1 = 0
1826
            _mem4.channel_attributes[number].is_scanlist2 = 0
1827
            _mem4.channel_attributes[number].unknown1 = 0
1828
            _mem4.channel_attributes[number].unknown2 = 0
1829
            _mem4.channel_attributes[number].is_free = 1
1830
            _mem4.channel_attributes[number].band = 0x7
1831

    
1832
        # find tx frequency
1833
        if mem.duplex == '-':
1834
            txfreq = mem.freq - mem.offset
1835
        elif mem.duplex == '+':
1836
            txfreq = mem.freq + mem.offset
1837
        else:
1838
            txfreq = mem.freq
1839

    
1840
        # find band
1841
        band = _find_band(self, txfreq)
1842
        if band is False:
1843
            raise errors.RadioError(
1844
                    "Transmit frequency %.4fMHz is not supported by this radio"
1845
                    % txfreq/1000000.0)
1846

    
1847
        band = _find_band(self, mem.freq)
1848
        if band is False:
1849
            return mem
1850

    
1851
        # mode
1852
        if mem.mode == "NFM":
1853
            _mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
1854
            _mem.flags1 = _mem.flags1 & ~FLAGS1_ISAM
1855
        elif mem.mode == "FM":
1856
            _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1857
            _mem.flags1 = _mem.flags1 & ~FLAGS1_ISAM
1858
        elif mem.mode == "NAM":
1859
            _mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
1860
            _mem.flags1 = _mem.flags1 | FLAGS1_ISAM
1861
        elif mem.mode == "AM":
1862
            _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1863
            _mem.flags1 = _mem.flags1 | FLAGS1_ISAM
1864

    
1865
        # frequency/offset
1866
        _mem.freq = mem.freq/10
1867
        _mem.offset = mem.offset/10
1868

    
1869
        if mem.duplex == "off" or mem.duplex == "":
1870
            _mem.offset = 0
1871
            _mem.flags1 = _mem.flags1 & ~FLAGS1_OFFSET_MASK
1872
        elif mem.duplex == '-':
1873
            _mem.flags1 = (
1874
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_MINUS
1875
        elif mem.duplex == '+':
1876
            _mem.flags1 = (
1877
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_PLUS
1878

    
1879
        # set band
1880
        if number < 200:
1881
            _mem4.channel_attributes[number].is_free = 0
1882
            _mem4.channel_attributes[number].band = band
1883

    
1884
        # channels >200 are the 14 VFO chanells and don't have names
1885
        if number < 200:
1886
            _mem2 = self._memobj.channelname[number]
1887
            tag = mem.name.ljust(16)[:16]
1888
            _mem2.name = tag  # Store the alpha tag
1889

    
1890
        # tone data
1891
        self._set_tone(mem, _mem)
1892

    
1893
        # step
1894
        _mem.step = STEPS.index(mem.tuning_step)
1895

    
1896
        # tx power
1897
        if str(mem.power) == str(UVK5_POWER_LEVELS[2]):
1898
            _mem.flags2 = (
1899
                _mem.flags2 & ~FLAGS2_POWER_MASK) | FLAGS2_POWER_HIGH
1900
        elif str(mem.power) == str(UVK5_POWER_LEVELS[1]):
1901
            _mem.flags2 = (
1902
                _mem.flags2 & ~FLAGS2_POWER_MASK) | FLAGS2_POWER_MEDIUM
1903
        else:
1904
            _mem.flags2 = (_mem.flags2 & ~FLAGS2_POWER_MASK)
1905

    
1906
        for setting in mem.extra:
1907
            sname = setting.get_name()
1908
            svalue = setting.value.get_value()
1909

    
1910
            if sname == "bclo":
1911
                if svalue:
1912
                    _mem.flags2 = _mem.flags2 | FLAGS2_BCLO
1913
                else:
1914
                    _mem.flags2 = _mem.flags2 & ~FLAGS2_BCLO
1915

    
1916
            if sname == "pttid":
1917
                _mem.dtmf_flags = (
1918
                        (_mem.dtmf_flags & ~FLAGS_DTMF_PTTID_MASK)
1919
                        | (PTTID_LIST.index(svalue) << 1))
1920

    
1921
            if sname == "frev":
1922
                if svalue:
1923
                    _mem.flags2 = _mem.flags2 | FLAGS2_REVERSE
1924
                else:
1925
                    _mem.flags2 = _mem.flags2 & ~FLAGS2_REVERSE
1926

    
1927
            if sname == "dtmfdecode":
1928
                if svalue:
1929
                    _mem.dtmf_flags = _mem.dtmf_flags | FLAGS_DTMF_DECODE
1930
                else:
1931
                    _mem.dtmf_flags = _mem.dtmf_flags & ~FLAGS_DTMF_DECODE
1932

    
1933
            if sname == "scrambler":
1934
                _mem.scrambler = (
1935
                    _mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue)
1936

    
1937
            if number < 200 and sname == "scanlists":
1938
                if svalue == "1":
1939
                    _mem4.channel_attributes[number].is_scanlist1 = 1
1940
                    _mem4.channel_attributes[number].is_scanlist2 = 0
1941
                elif svalue == "2":
1942
                    _mem4.channel_attributes[number].is_scanlist1 = 0
1943
                    _mem4.channel_attributes[number].is_scanlist2 = 1
1944
                elif svalue == "1+2":
1945
                    _mem4.channel_attributes[number].is_scanlist1 = 1
1946
                    _mem4.channel_attributes[number].is_scanlist2 = 1
1947
                else:
1948
                    _mem4.channel_attributes[number].is_scanlist1 = 0
1949
                    _mem4.channel_attributes[number].is_scanlist2 = 0
1950

    
1951
        return mem
1952

    
1953

    
1954
@directory.register
1955
class UVK5Radio_nolimit(UVK5Radio):
1956
    VENDOR = "Quansheng"
1957
    MODEL = "UV-K5 (modified firmware)"
1958
    VARIANT = "nolimits"
1959
    FIRMWARE_NOLIMITS = True
1960

    
1961
    def get_features(self):
1962
        rf = UVK5Radio.get_features(self)
1963
        # This is what the BK4819 chip supports
1964
        rf.valid_bands = [(18000000,  620000000),
1965
                          (840000000, 1300000000)
1966
                          ]
1967
        return rf
(41-41/47)