Project

General

Profile

Bug #10702 » uvk5.py

UV-K5 driver with experimental TX disable feature - Jacek Lipkowski SQ5BPF, 07/09/2023 02:21 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.
12
#
13
#
14
# This program is free software: you can redistribute it and/or modify
15
# it under the terms of the GNU General Public License as published by
16
# the Free Software Foundation, either version 2 of the License, or
17
# (at your option) any later version.
18
#
19
# This program is distributed in the hope that it will be useful,
20
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
# GNU General Public License for more details.
23
#
24
# You should have received a copy of the GNU General Public License
25
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
26

    
27

    
28
import struct
29
import logging
30

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

    
37
LOG = logging.getLogger(__name__)
38

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

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

    
48
MEM_FORMAT = """
49
#seekto 0x0000;
50
struct {
51
  ul32 freq;
52
  ul32 offset;
53
  u8 rxcode;
54
  u8 txcode;
55

    
56
  u8 unknown1:2,
57
  txcodeflag:2,
58
  unknown2:2,
59
  rxcodeflag:2;
60

    
61
  //u8 flags1;
62
  u8 flags1_unknown7:1,
63
  flags1_unknown6:1,
64
  flags1_unknown5:1,
65
  enable_am:1,
66
  flags1_unknown3:1,
67
  is_in_scanlist:1,
68
  shift:2;
69

    
70
  //u8 flags2;
71
  u8 flags2_unknown7:1,
72
  flags2_unknown6:1,
73
  flags2_unknown5:1,
74
  bclo:1,
75
  txpower:2,
76
  bandwidth:1,
77
  freq_reverse:1;
78

    
79
  //u8 dtmf_flags;
80
  u8 dtmf_flags_unknown7:1,
81
  dtmf_flags_unknown6:1,
82
  dtmf_flags_unknown5:1,
83
  dtmf_flags_unknown4:1,
84
  dtmf_flags_unknown3:1,
85
  dtmf_pttid:2,
86
  dtmf_decode:1;
87

    
88

    
89
  u8 step;
90
  u8 scrambler;
91
} channel[214];
92

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

    
103
#seekto 0xe40;
104
ul16 fmfreq[20];
105

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

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

    
135
#seekto 0xea0;
136
u8 keypad_tone;
137
u8 language;
138

    
139
#seekto 0xea8;
140
u8 alarm_mode;
141
u8 reminding_of_end_talk;
142
u8 repeater_tail_elimination;
143

    
144
#seekto 0xeb0;
145
char logo_line1[16];
146
char logo_line2[16];
147

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

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

    
175
#seekto 0xf18;
176
u8 scanlist_default;
177
u8 scanlist1_priority_scan;
178
u8 scanlist1_priority_ch1;
179
u8 scanlist1_priority_ch2;
180
u8 scanlist2_priority_scan;
181
u8 scanlist2_priority_ch1;
182
u8 scanlist2_priority_ch2;
183
u8 scanlist_unknown_0xff;
184

    
185

    
186
#seekto 0xf40;
187
u8 int_flock;
188
u8 int_350tx;
189
u8 int_unknown1;
190
u8 int_200tx;
191
u8 int_500tx;
192
u8 int_350en;
193
u8 int_scren;
194

    
195
#seekto 0xf50;
196
struct {
197
char name[16];
198
} channelname[200];
199

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

    
215
# flags1
216
FLAGS1_OFFSET_NONE = 0b00
217
FLAGS1_OFFSET_MINUS = 0b10
218
FLAGS1_OFFSET_PLUS = 0b01
219

    
220
POWER_HIGH = 0b10
221
POWER_MEDIUM = 0b01
222
POWER_LOW = 0b00
223

    
224
# dtmf_flags
225
PTTID_LIST = ["off", "BOT", "EOT", "BOTH"]
226

    
227
# power
228
UVK5_POWER_LEVELS = [chirp_common.PowerLevel("Low",  watts=1.50),
229
                     chirp_common.PowerLevel("Med",  watts=3.00),
230
                     chirp_common.PowerLevel("High", watts=5.00),
231
                     ]
232

    
233
# scrambler
234
SCRAMBLER_LIST = ["off", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
235

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

    
241
# Backlight auto mode
242
BACKLIGHT_LIST = ["Off", "1s", "2s", "3s", "4s", "5s"]
243

    
244
# Crossband receiving/transmitting
245
CROSSBAND_LIST = ["Off", "Band A", "Band B"]
246
DUALWATCH_LIST = CROSSBAND_LIST
247

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

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

    
258

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

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

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

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

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

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

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

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

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

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

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

    
352
SCANLIST_LIST = ["None", "1", "2", "1+2"]
353

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

    
361
KEYACTIONS_LIST = ["None", "Flashlight on/off", "Power select",
362
                   "Monitor", "Scan on/off", "VOX on/off",
363
                   "Alarm on/off", "FM radio on/off", "Transmit 1750Hz"]
364

    
365

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

    
376

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

    
391

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

    
397
    crc = calculate_crc16_xmodem(data)
398
    data2 = data + struct.pack("<H", crc)
399

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

    
411

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

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

    
429
    footer = serport.read(4)
430

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

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

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

    
448
    cmd2 = xorarr(cmd)
449

    
450
    LOG.debug("Received reply (unobfuscated) len=0x%4.4x:\n%s" %
451
              (len(cmd2), util.hexprint(cmd2)))
452

    
453
    return cmd2
454

    
455

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

    
464

    
465
def _sayhello(serport):
466
    hellopacket = b"\x14\x05\x04\x00\x6a\x39\x57\x64"
467

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

    
483

    
484
def _readmem(serport, offset, length):
485
    LOG.debug("Sending readmem offset=0x%4.4x len=0x%4.4x" % (offset, length))
486

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

    
497

    
498
def _writemem(serport, data, offset):
499
    LOG.debug("Sending writemem offset=0x%4.4x len=0x%4.4x" %
500
              (offset, len(data)))
501

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

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

    
511
    _send_command(serport, writemem)
512
    o = _receive_reply(serport)
513

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

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

    
526

    
527
def _resetradio(serport):
528
    resetpacket = b"\xdd\x05\x00\x00"
529
    _send_command(serport, resetpacket)
530

    
531

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

    
541
    eeprom = b""
542
    f = _sayhello(serport)
543
    if f:
544
        radio.FIRMWARE_VERSION = f
545
    else:
546
        return False
547

    
548
    addr = 0
549
    while addr < MEM_SIZE:
550
        o = _readmem(serport, addr, MEM_BLOCK)
551
        status.cur = addr
552
        radio.status_fn(status)
553

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

    
560
    return memmap.MemoryMapBytes(eeprom)
561

    
562

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

    
572
    f = _sayhello(serport)
573
    if f:
574
        radio.FIRMWARE_VERSION = f
575
    else:
576
        return False
577

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

    
590
    _resetradio(serport)
591

    
592
    return True
593

    
594

    
595
def _find_band(nolimits, hz):
596
    mhz = hz/1000000.0
597
    if nolimits:
598
        B = BANDS_NOLIMITS
599
    else:
600
        B = BANDS
601

    
602
    # currently the hacked firmware sets band=1 below 50MHz
603
    if nolimits and mhz < 50.0:
604
        return 1
605

    
606
    for a in B:
607
        if mhz >= B[a][0] and mhz <= B[a][1]:
608
            return a
609
    return False
610

    
611

    
612
@directory.register
613
class UVK5Radio(chirp_common.CloneModeRadio):
614
    """Quansheng UV-K5"""
615
    VENDOR = "Quansheng"
616
    MODEL = "UV-K5"
617
    BAUD_RATE = 38400
618
    NEEDS_COMPAT_SERIAL = False
619
    FIRMWARE_VERSION = ""
620
    _expanded_limits = False
621

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

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

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

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

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

    
675
        rf.valid_skips = [""]
676

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

    
680
        rf.valid_bands = []
681
        for a in BANDS_NOLIMITS:
682
            rf.valid_bands.append(
683
                    (int(BANDS_NOLIMITS[a][0]*1000000),
684
                     int(BANDS_NOLIMITS[a][1]*1000000)))
685
        return rf
686

    
687
    # Do a download of the radio from the serial port
688
    def sync_in(self):
689
        self._mmap = do_download(self)
690
        self.process_mmap()
691

    
692
    # Do an upload of the radio to the serial port
693
    def sync_out(self):
694
        do_upload(self)
695

    
696
    # Convert the raw byte array into a memory object structure
697
    def process_mmap(self):
698
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
699

    
700
    # Return a raw representation of the memory object, which
701
    # is very helpful for development
702
    def get_raw_memory(self, number):
703
        return repr(self._memobj.channel[number-1])
704

    
705
    def validate_memory(self, mem):
706
        msgs = super().validate_memory(mem)
707

    
708
        if mem.duplex == 'off':
709
            return msgs
710

    
711
        # find tx frequency
712
        if mem.duplex == '-':
713
            txfreq = mem.freq - mem.offset
714
        elif mem.duplex == '+':
715
            txfreq = mem.freq + mem.offset
716
        else:
717
            txfreq = mem.freq
718

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

    
726
        band = _find_band(self._expanded_limits, mem.freq)
727
        if band is False:
728
            msg = "The frequency %.4fMHz is not supported by this radio" \
729
                   % (mem.freq/1000000.0)
730
            msgs.append(chirp_common.ValidationError(msg))
731

    
732
        return msgs
733

    
734
    def _set_tone(self, mem, _mem):
735
        ((txmode, txtone, txpol),
736
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
737

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

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

    
758
        _mem.rxcodeflag = rxmoval
759
        _mem.txcodeflag = txmoval
760
        _mem.unknown1 = 0
761
        _mem.unknown2 = 0
762
        _mem.rxcode = rxtoval
763
        _mem.txcode = txtoval
764

    
765
    def _get_tone(self, mem, _mem):
766
        rxtype = _mem.rxcodeflag
767
        txtype = _mem.txcodeflag
768
        rx_tmode = TMODES[rxtype]
769
        tx_tmode = TMODES[txtype]
770

    
771
        rx_tone = tx_tone = None
772

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

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

    
799
        tx_pol = txtype == 0x03 and "R" or "N"
800
        rx_pol = rxtype == 0x03 and "R" or "N"
801

    
802
        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
803
                                       (rx_tmode, rx_tone, rx_pol))
804

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

    
809
        mem = chirp_common.Memory()
810

    
811
        if isinstance(number2, str):
812
            number = SPECIALS[number2]
813
            mem.extd_number = number2
814
        else:
815
            number = number2 - 1
816

    
817
        mem.number = number + 1
818

    
819
        _mem = self._memobj.channel[number]
820

    
821
        tmpcomment = ""
822

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

    
828
        tmpscn = SCANLIST_LIST[0]
829

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

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

    
864
            rs = RadioSetting("scanlists", "Scanlists", RadioSettingValueList(
865
                SCANLIST_LIST, SCANLIST_LIST[0]))
866
            mem.extra.append(rs)
867

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

    
873
            return mem
874

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

    
885
        # Convert your low-level frequency to Hertz
886
        mem.freq = int(_mem.freq)*10
887
        mem.offset = int(_mem.offset)*10
888

    
889
        if (mem.offset == 0):
890
            mem.duplex = ''
891
        else:
892
            if _mem.shift == FLAGS1_OFFSET_MINUS:
893
                if _mem.freq == _mem.offset:
894
                    # fake tx disable by setting tx to 0MHz
895
                    mem.duplex = 'off'
896
                    mem.offset = 0
897
                else:
898
                    mem.duplex = '-'
899
            elif _mem.shift == FLAGS1_OFFSET_PLUS:
900
                mem.duplex = '+'
901
            else:
902
                mem.duplex = ''
903

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

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

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

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

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

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

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

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

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

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

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

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

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

    
983
        return mem
984

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

    
992
            # basic settings
993

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1103
            # unlock settings
1104

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1239
                val = int(element.value)
1240

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

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

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

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

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

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

    
1271
            if element.get_name() == "nolimits":
1272
                LOG.warning("User expanded band limits")
1273
                self._expanded_limits = bool(element.value)
1274

    
1275
    def get_settings(self):
1276
        _mem = self._memobj
1277
        basic = RadioSettingGroup("basic", "Basic Settings")
1278
        keya = RadioSettingGroup("keya", "Programmable keys")
1279
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1280
        dtmfc = RadioSettingGroup("dtmfc", "DTMF Contacts")
1281
        scanl = RadioSettingGroup("scn", "Scan Lists")
1282
        unlock = RadioSettingGroup("unlock", "Unlock Settings")
1283
        fmradio = RadioSettingGroup("fmradio", "FM Radio")
1284

    
1285
        roinfo = RadioSettingGroup("roinfo", "Driver information")
1286

    
1287
        top = RadioSettings(
1288
                basic, keya, dtmf, dtmfc, scanl, unlock, fmradio, roinfo)
1289

    
1290
        # Programmable keys
1291
        tmpval = int(_mem.key1_shortpress_action)
1292
        if tmpval >= len(KEYACTIONS_LIST):
1293
            tmpval = 0
1294
        rs = RadioSetting("key1_shortpress_action", "Side key 1 short press",
1295
                          RadioSettingValueList(
1296
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1297
        keya.append(rs)
1298

    
1299
        tmpval = int(_mem.key1_longpress_action)
1300
        if tmpval >= len(KEYACTIONS_LIST):
1301
            tmpval = 0
1302
        rs = RadioSetting("key1_longpress_action", "Side key 1 long press",
1303
                          RadioSettingValueList(
1304
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1305
        keya.append(rs)
1306

    
1307
        tmpval = int(_mem.key2_shortpress_action)
1308
        if tmpval >= len(KEYACTIONS_LIST):
1309
            tmpval = 0
1310
        rs = RadioSetting("key2_shortpress_action", "Side key 2 short press",
1311
                          RadioSettingValueList(
1312
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1313
        keya.append(rs)
1314

    
1315
        tmpval = int(_mem.key2_longpress_action)
1316
        if tmpval >= len(KEYACTIONS_LIST):
1317
            tmpval = 0
1318
        rs = RadioSetting("key2_longpress_action", "Side key 2 long press",
1319
                          RadioSettingValueList(
1320
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1321
        keya.append(rs)
1322

    
1323
        # DTMF settings
1324
        tmppr = bool(_mem.dtmf_settings.side_tone > 0)
1325
        rs = RadioSetting(
1326
                "dtmf_side_tone",
1327
                "DTMF Sidetone",
1328
                RadioSettingValueBoolean(tmppr))
1329
        dtmf.append(rs)
1330

    
1331
        tmpval = str(_mem.dtmf_settings.separate_code)
1332
        if tmpval not in DTMF_CODE_CHARS:
1333
            tmpval = '*'
1334
        val = RadioSettingValueString(1, 1, tmpval)
1335
        val.set_charset(DTMF_CODE_CHARS)
1336
        rs = RadioSetting("dtmf_separate_code", "Separate Code", val)
1337
        dtmf.append(rs)
1338

    
1339
        tmpval = str(_mem.dtmf_settings.group_call_code)
1340
        if tmpval not in DTMF_CODE_CHARS:
1341
            tmpval = '#'
1342
        val = RadioSettingValueString(1, 1, tmpval)
1343
        val.set_charset(DTMF_CODE_CHARS)
1344
        rs = RadioSetting("dtmf_group_call_code", "Group Call Code", val)
1345
        dtmf.append(rs)
1346

    
1347
        tmpval = _mem.dtmf_settings.decode_response
1348
        if tmpval >= len(DTMF_DECODE_RESPONSE_LIST):
1349
            tmpval = 0
1350
        rs = RadioSetting("dtmf_decode_response", "Decode Response",
1351
                          RadioSettingValueList(
1352
                              DTMF_DECODE_RESPONSE_LIST,
1353
                              DTMF_DECODE_RESPONSE_LIST[tmpval]))
1354
        dtmf.append(rs)
1355

    
1356
        tmpval = _mem.dtmf_settings.auto_reset_time
1357
        if tmpval > 60 or tmpval < 5:
1358
            tmpval = 5
1359
        rs = RadioSetting("dtmf_auto_reset_time",
1360
                          "Auto reset time (s)",
1361
                          RadioSettingValueInteger(5, 60, tmpval))
1362
        dtmf.append(rs)
1363

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

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

    
1382
        tmpval = int(_mem.dtmf_settings.hash_persist_time)
1383
        if tmpval > 100 or tmpval < 3:
1384
            tmpval = 30
1385
        tmpval *= 10
1386
        rs = RadioSetting("dtmf_hash_persist_time",
1387
                          "#/* persist time (ms)",
1388
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1389
        dtmf.append(rs)
1390

    
1391
        tmpval = int(_mem.dtmf_settings.code_persist_time)
1392
        if tmpval > 100 or tmpval < 3:
1393
            tmpval = 30
1394
        tmpval *= 10
1395
        rs = RadioSetting("dtmf_code_persist_time",
1396
                          "Code persist time (ms)",
1397
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1398
        dtmf.append(rs)
1399

    
1400
        tmpval = int(_mem.dtmf_settings.code_interval_time)
1401
        if tmpval > 100 or tmpval < 3:
1402
            tmpval = 30
1403
        tmpval *= 10
1404
        rs = RadioSetting("dtmf_code_interval_time",
1405
                          "Code interval time (ms)",
1406
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1407
        dtmf.append(rs)
1408

    
1409
        tmpval = bool(_mem.dtmf_settings.permit_remote_kill > 0)
1410
        rs = RadioSetting(
1411
                "dtmf_permit_remote_kill",
1412
                "Permit remote kill",
1413
                RadioSettingValueBoolean(tmpval))
1414
        dtmf.append(rs)
1415

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

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

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

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

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

    
1490
        val = RadioSettingValueString(0, 80,
1491
                                      "All DTMF Contacts are 3 codes "
1492
                                      "(valid: 0-9 * # ABCD), "
1493
                                      "or an empty string")
1494
        val.set_mutable(False)
1495
        rs = RadioSetting("dtmf_descr1", "DTMF Contacts", val)
1496
        dtmfc.append(rs)
1497

    
1498
        for i in range(1, 17):
1499
            varname = "DTMF_"+str(i)
1500
            varnumname = "DTMFNUM_"+str(i)
1501
            vardescr = "DTMF Contact "+str(i)+" name"
1502
            varinumdescr = "DTMF Contact "+str(i)+" number"
1503

    
1504
            cntn = str(_mem.dtmfcontact[i-1].name).strip("\x20\x00\xff")
1505
            cntnum = str(_mem.dtmfcontact[i-1].number).strip("\x20\x00\xff")
1506

    
1507
            val = RadioSettingValueString(0, 8, cntn)
1508
            rs = RadioSetting(varname, vardescr, val)
1509
            dtmfc.append(rs)
1510

    
1511
            val = RadioSettingValueString(0, 3, cntnum)
1512
            val.set_charset(DTMF_CHARS)
1513
            rs = RadioSetting(varnumname, varinumdescr, val)
1514
            dtmfc.append(rs)
1515

    
1516
        # scanlists
1517
        if _mem.scanlist_default == 1:
1518
            tmpsc = 2
1519
        else:
1520
            tmpsc = 1
1521
        rs = RadioSetting("scanlist_default",
1522
                          "Default scanlist",
1523
                          RadioSettingValueInteger(1, 2, tmpsc))
1524
        scanl.append(rs)
1525

    
1526
        tmppr = bool((_mem.scanlist1_priority_scan & 1) > 0)
1527
        rs = RadioSetting(
1528
                "scanlist1_priority_scan",
1529
                "Scanlist 1 priority channel scan",
1530
                RadioSettingValueBoolean(tmppr))
1531
        scanl.append(rs)
1532

    
1533
        tmpch = _mem.scanlist1_priority_ch1 + 1
1534
        if tmpch > 200:
1535
            tmpch = 0
1536
        rs = RadioSetting("scanlist1_priority_ch1",
1537
                          "Scanlist 1 priority channel 1 (0 - off)",
1538
                          RadioSettingValueInteger(0, 200, tmpch))
1539
        scanl.append(rs)
1540

    
1541
        tmpch = _mem.scanlist1_priority_ch2 + 1
1542
        if tmpch > 200:
1543
            tmpch = 0
1544
        rs = RadioSetting("scanlist1_priority_ch2",
1545
                          "Scanlist 1 priority channel 2 (0 - off)",
1546
                          RadioSettingValueInteger(0, 200, tmpch))
1547
        scanl.append(rs)
1548

    
1549
        tmppr = bool((_mem.scanlist2_priority_scan & 1) > 0)
1550
        rs = RadioSetting(
1551
                "scanlist2_priority_scan",
1552
                "Scanlist 2 priority channel scan",
1553
                RadioSettingValueBoolean(tmppr))
1554
        scanl.append(rs)
1555

    
1556
        tmpch = _mem.scanlist2_priority_ch1 + 1
1557
        if tmpch > 200:
1558
            tmpch = 0
1559
        rs = RadioSetting("scanlist2_priority_ch1",
1560
                          "Scanlist 2 priority channel 1 (0 - off)",
1561
                          RadioSettingValueInteger(0, 200, tmpch))
1562
        scanl.append(rs)
1563

    
1564
        tmpch = _mem.scanlist2_priority_ch2 + 1
1565
        if tmpch > 200:
1566
            tmpch = 0
1567
        rs = RadioSetting("scanlist2_priority_ch2",
1568
                          "Scanlist 2 priority channel 2 (0 - off)",
1569
                          RadioSettingValueInteger(0, 200, tmpch))
1570
        scanl.append(rs)
1571

    
1572
        # basic settings
1573

    
1574
        # call channel
1575
        tmpc = _mem.call_channel+1
1576
        if tmpc > 200:
1577
            tmpc = 1
1578
        rs = RadioSetting("call_channel", "One key call channel",
1579
                          RadioSettingValueInteger(1, 200, tmpc))
1580
        basic.append(rs)
1581

    
1582
        # squelch
1583
        tmpsq = _mem.squelch
1584
        if tmpsq > 9:
1585
            tmpsq = 1
1586
        rs = RadioSetting("squelch", "Squelch",
1587
                          RadioSettingValueInteger(0, 9, tmpsq))
1588
        basic.append(rs)
1589

    
1590
        # TOT
1591
        tmptot = _mem.max_talk_time
1592
        if tmptot > 10:
1593
            tmptot = 10
1594
        rs = RadioSetting(
1595
                "tot",
1596
                "Max talk time [min]",
1597
                RadioSettingValueInteger(0, 10, tmptot))
1598
        basic.append(rs)
1599

    
1600
        # NOAA autoscan
1601
        rs = RadioSetting(
1602
                "noaa_autoscan",
1603
                "NOAA Autoscan", RadioSettingValueBoolean(
1604
                    bool(_mem.noaa_autoscan > 0)))
1605
        basic.append(rs)
1606

    
1607
        # VOX switch
1608
        rs = RadioSetting(
1609
                "vox_switch",
1610
                "VOX enabled", RadioSettingValueBoolean(
1611
                    bool(_mem.vox_switch > 0)))
1612
        basic.append(rs)
1613

    
1614
        # VOX Level
1615
        tmpvox = _mem.vox_level+1
1616
        if tmpvox > 10:
1617
            tmpvox = 10
1618
        rs = RadioSetting("vox_level", "VOX Level",
1619
                          RadioSettingValueInteger(1, 10, tmpvox))
1620
        basic.append(rs)
1621

    
1622
        # Mic gain
1623
        tmpmicgain = _mem.mic_gain
1624
        if tmpmicgain > 4:
1625
            tmpmicgain = 4
1626
        rs = RadioSetting("mic_gain", "Mic Gain",
1627
                          RadioSettingValueInteger(0, 4, tmpmicgain))
1628
        basic.append(rs)
1629

    
1630
        # Channel display mode
1631
        tmpchdispmode = _mem.channel_display_mode
1632
        if tmpchdispmode >= len(CHANNELDISP_LIST):
1633
            tmpchdispmode = 0
1634
        rs = RadioSetting(
1635
                "channel_display_mode",
1636
                "Channel display mode",
1637
                RadioSettingValueList(
1638
                    CHANNELDISP_LIST,
1639
                    CHANNELDISP_LIST[tmpchdispmode]))
1640
        basic.append(rs)
1641

    
1642
        # Crossband receiving/transmitting
1643
        tmpcross = _mem.crossband
1644
        if tmpcross >= len(CROSSBAND_LIST):
1645
            tmpcross = 0
1646
        rs = RadioSetting(
1647
                "crossband",
1648
                "Cross-band receiving/transmitting",
1649
                RadioSettingValueList(
1650
                    CROSSBAND_LIST,
1651
                    CROSSBAND_LIST[tmpcross]))
1652
        basic.append(rs)
1653

    
1654
        # Battery save
1655
        tmpbatsave = _mem.battery_save
1656
        if tmpbatsave >= len(BATSAVE_LIST):
1657
            tmpbatsave = BATSAVE_LIST.index("1:4")
1658
        rs = RadioSetting(
1659
                "battery_save",
1660
                "Battery Save",
1661
                RadioSettingValueList(
1662
                    BATSAVE_LIST,
1663
                    BATSAVE_LIST[tmpbatsave]))
1664
        basic.append(rs)
1665

    
1666
        # Dual watch
1667
        tmpdual = _mem.dual_watch
1668
        if tmpdual >= len(DUALWATCH_LIST):
1669
            tmpdual = 0
1670
        rs = RadioSetting("dualwatch", "Dual Watch", RadioSettingValueList(
1671
            DUALWATCH_LIST, DUALWATCH_LIST[tmpdual]))
1672
        basic.append(rs)
1673

    
1674
        # Backlight auto mode
1675
        tmpback = _mem.backlight_auto_mode
1676
        if tmpback >= len(BACKLIGHT_LIST):
1677
            tmpback = 0
1678
        rs = RadioSetting("backlight_auto_mode",
1679
                          "Backlight auto mode",
1680
                          RadioSettingValueList(
1681
                              BACKLIGHT_LIST,
1682
                              BACKLIGHT_LIST[tmpback]))
1683
        basic.append(rs)
1684

    
1685
        # Tail tone elimination
1686
        rs = RadioSetting(
1687
                "tail_note_elimination",
1688
                "Tail tone elimination",
1689
                RadioSettingValueBoolean(
1690
                    bool(_mem.tail_note_elimination > 0)))
1691
        basic.append(rs)
1692

    
1693
        # VFO open
1694
        rs = RadioSetting("vfo_open", "VFO open",
1695
                          RadioSettingValueBoolean(bool(_mem.vfo_open > 0)))
1696
        basic.append(rs)
1697

    
1698
        # Beep control
1699
        rs = RadioSetting(
1700
                "beep_control",
1701
                "Beep control",
1702
                RadioSettingValueBoolean(bool(_mem.beep_control > 0)))
1703
        basic.append(rs)
1704

    
1705
        # Scan resume mode
1706
        tmpscanres = _mem.scan_resume_mode
1707
        if tmpscanres >= len(SCANRESUME_LIST):
1708
            tmpscanres = 0
1709
        rs = RadioSetting(
1710
                "scan_resume_mode",
1711
                "Scan resume mode",
1712
                RadioSettingValueList(
1713
                    SCANRESUME_LIST,
1714
                    SCANRESUME_LIST[tmpscanres]))
1715
        basic.append(rs)
1716

    
1717
        # Keypad locked
1718
        rs = RadioSetting(
1719
                "key_lock",
1720
                "Keypad lock",
1721
                RadioSettingValueBoolean(bool(_mem.key_lock > 0)))
1722
        basic.append(rs)
1723

    
1724
        # Auto keypad lock
1725
        rs = RadioSetting(
1726
                "auto_keypad_lock",
1727
                "Auto keypad lock",
1728
                RadioSettingValueBoolean(bool(_mem.auto_keypad_lock > 0)))
1729
        basic.append(rs)
1730

    
1731
        # Power on display mode
1732
        tmpdispmode = _mem.power_on_dispmode
1733
        if tmpdispmode >= len(WELCOME_LIST):
1734
            tmpdispmode = 0
1735
        rs = RadioSetting(
1736
                "welcome_mode",
1737
                "Power on display mode",
1738
                RadioSettingValueList(
1739
                    WELCOME_LIST,
1740
                    WELCOME_LIST[tmpdispmode]))
1741
        basic.append(rs)
1742

    
1743
        # Keypad Tone
1744
        tmpkeypadtone = _mem.keypad_tone
1745
        if tmpkeypadtone >= len(KEYPADTONE_LIST):
1746
            tmpkeypadtone = 0
1747
        rs = RadioSetting("keypad_tone", "Keypad tone", RadioSettingValueList(
1748
            KEYPADTONE_LIST, KEYPADTONE_LIST[tmpkeypadtone]))
1749
        basic.append(rs)
1750

    
1751
        # Language
1752
        tmplanguage = _mem.language
1753
        if tmplanguage >= len(LANGUAGE_LIST):
1754
            tmplanguage = 0
1755
        rs = RadioSetting("language", "Language", RadioSettingValueList(
1756
            LANGUAGE_LIST, LANGUAGE_LIST[tmplanguage]))
1757
        basic.append(rs)
1758

    
1759
        # Alarm mode
1760
        tmpalarmmode = _mem.alarm_mode
1761
        if tmpalarmmode >= len(ALARMMODE_LIST):
1762
            tmpalarmmode = 0
1763
        rs = RadioSetting("alarm_mode", "Alarm mode", RadioSettingValueList(
1764
            ALARMMODE_LIST, ALARMMODE_LIST[tmpalarmmode]))
1765
        basic.append(rs)
1766

    
1767
        # Reminding of end of talk
1768
        tmpalarmmode = _mem.reminding_of_end_talk
1769
        if tmpalarmmode >= len(REMENDOFTALK_LIST):
1770
            tmpalarmmode = 0
1771
        rs = RadioSetting(
1772
                "reminding_of_end_talk",
1773
                "Reminding of end of talk",
1774
                RadioSettingValueList(
1775
                    REMENDOFTALK_LIST,
1776
                    REMENDOFTALK_LIST[tmpalarmmode]))
1777
        basic.append(rs)
1778

    
1779
        # Repeater tail tone elimination
1780
        tmprte = _mem.repeater_tail_elimination
1781
        if tmprte >= len(RTE_LIST):
1782
            tmprte = 0
1783
        rs = RadioSetting(
1784
                "repeater_tail_elimination",
1785
                "Repeater tail tone elimination",
1786
                RadioSettingValueList(RTE_LIST, RTE_LIST[tmprte]))
1787
        basic.append(rs)
1788

    
1789
        # Logo string 1
1790
        logo1 = str(_mem.logo_line1).strip("\x20\x00\xff") + "\x00"
1791
        logo1 = _getstring(logo1.encode('ascii', errors='ignore'), 0, 12)
1792
        rs = RadioSetting("logo1", "Logo string 1 (12 characters)",
1793
                          RadioSettingValueString(0, 12, logo1))
1794
        basic.append(rs)
1795

    
1796
        # Logo string 2
1797
        logo2 = str(_mem.logo_line2).strip("\x20\x00\xff") + "\x00"
1798
        logo2 = _getstring(logo2.encode('ascii', errors='ignore'), 0, 12)
1799
        rs = RadioSetting("logo2", "Logo string 2 (12 characters)",
1800
                          RadioSettingValueString(0, 12, logo2))
1801
        basic.append(rs)
1802

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

    
1814
            fmradio.append(rs)
1815

    
1816
        # unlock settings
1817

    
1818
        # F-LOCK
1819
        tmpflock = _mem.int_flock
1820
        if tmpflock >= len(FLOCK_LIST):
1821
            tmpflock = 0
1822
        rs = RadioSetting(
1823
            "flock", "F-LOCK",
1824
            RadioSettingValueList(FLOCK_LIST, FLOCK_LIST[tmpflock]))
1825
        unlock.append(rs)
1826

    
1827
        # 350TX
1828
        rs = RadioSetting("350tx", "350TX - unlock 350-400MHz TX",
1829
                          RadioSettingValueBoolean(
1830
                              bool(_mem.int_350tx > 0)))
1831
        unlock.append(rs)
1832

    
1833
        # 200TX
1834
        rs = RadioSetting("200tx", "200TX - unlock 174-350MHz TX",
1835
                          RadioSettingValueBoolean(
1836
                              bool(_mem.int_200tx > 0)))
1837
        unlock.append(rs)
1838

    
1839
        # 500TX
1840
        rs = RadioSetting("500tx", "500TX - unlock 500-600MHz TX",
1841
                          RadioSettingValueBoolean(
1842
                              bool(_mem.int_500tx > 0)))
1843
        unlock.append(rs)
1844

    
1845
        # 350EN
1846
        rs = RadioSetting("350en", "350EN - unlock 350-400MHz RX",
1847
                          RadioSettingValueBoolean(
1848
                              bool(_mem.int_350en > 0)))
1849
        unlock.append(rs)
1850

    
1851
        # SCREEN
1852
        rs = RadioSetting("scren", "SCREN - scrambler enable",
1853
                          RadioSettingValueBoolean(
1854
                              bool(_mem.int_scren > 0)))
1855
        unlock.append(rs)
1856

    
1857
        # readonly info
1858
        # Firmware
1859
        if self.FIRMWARE_VERSION == "":
1860
            firmware = "To get the firmware version please download"
1861
            "the image from the radio first"
1862
        else:
1863
            firmware = self.FIRMWARE_VERSION
1864

    
1865
        val = RadioSettingValueString(0, 128, firmware)
1866
        val.set_mutable(False)
1867
        rs = RadioSetting("fw_ver", "Firmware Version", val)
1868
        roinfo.append(rs)
1869

    
1870
        # No limits version for hacked firmware
1871
        val = RadioSettingValueBoolean(self._expanded_limits)
1872
        rs = RadioSetting("nolimits", "Limits disabled for modified firmware",
1873
                          val)
1874
        rs.set_warning(_(
1875
            'This should only be enabled if you are using modified firmware '
1876
            'that supports wider frequency coverage. Enabling this will cause '
1877
            'CHIRP not to enforce OEM restrictions and may lead to undefined '
1878
            'or unregulated behavior. Use at your own risk!'),
1879
            safe_value=False)
1880
        roinfo.append(rs)
1881

    
1882
        return top
1883

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

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

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

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

    
1931
        # find band
1932
        band = _find_band(self, mem.freq)
1933

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

    
1948
        # frequency/offset
1949
        _mem.freq = mem.freq/10
1950
        _mem.offset = mem.offset/10
1951

    
1952
        if mem.duplex == "":
1953
            _mem.offset = 0
1954
            _mem.shift = 0
1955
        elif mem.duplex == '-':
1956
            _mem.shift = FLAGS1_OFFSET_MINUS
1957
        elif mem.duplex == '+':
1958
            _mem.shift = FLAGS1_OFFSET_PLUS
1959
        elif mem.duplex == 'off':
1960
            # we fake tx disable by setting the tx freq to 0MHz
1961
            _mem.shift = FLAGS1_OFFSET_MINUS
1962
            _mem.offset = _mem.freq
1963

    
1964

    
1965
        # set band
1966
        if number < 200:
1967
            _mem4.channel_attributes[number].is_free = 0
1968
            _mem4.channel_attributes[number].band = band
1969

    
1970
        # channels >200 are the 14 VFO chanells and don't have names
1971
        if number < 200:
1972
            _mem2 = self._memobj.channelname[number]
1973
            tag = mem.name.ljust(10) + "\x00"*6
1974
            _mem2.name = tag  # Store the alpha tag
1975

    
1976
        # tone data
1977
        self._set_tone(mem, _mem)
1978

    
1979
        # step
1980
        _mem.step = STEPS.index(mem.tuning_step)
1981

    
1982
        # tx power
1983
        if str(mem.power) == str(UVK5_POWER_LEVELS[2]):
1984
            _mem.txpower = POWER_HIGH
1985
        elif str(mem.power) == str(UVK5_POWER_LEVELS[1]):
1986
            _mem.txpower = POWER_MEDIUM
1987
        else:
1988
            _mem.txpower = POWER_LOW
1989

    
1990
        for setting in mem.extra:
1991
            sname = setting.get_name()
1992
            svalue = setting.value.get_value()
1993

    
1994
            if sname == "bclo":
1995
                _mem.bclo = svalue and 1 or 0
1996

    
1997
            if sname == "pttid":
1998
                _mem.dtmf_pttid = PTTID_LIST.index(svalue)
1999

    
2000
            if sname == "frev":
2001
                _mem.freq_reverse = svalue and 1 or 0
2002

    
2003
            if sname == "dtmfdecode":
2004
                _mem.dtmf_decode = svalue and 1 or 0
2005

    
2006
            if sname == "scrambler":
2007
                _mem.scrambler = (
2008
                    _mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue)
2009

    
2010
            if number < 200 and sname == "scanlists":
2011
                if svalue == "1":
2012
                    _mem4.channel_attributes[number].is_scanlist1 = 1
2013
                    _mem4.channel_attributes[number].is_scanlist2 = 0
2014
                elif svalue == "2":
2015
                    _mem4.channel_attributes[number].is_scanlist1 = 0
2016
                    _mem4.channel_attributes[number].is_scanlist2 = 1
2017
                elif svalue == "1+2":
2018
                    _mem4.channel_attributes[number].is_scanlist1 = 1
2019
                    _mem4.channel_attributes[number].is_scanlist2 = 1
2020
                else:
2021
                    _mem4.channel_attributes[number].is_scanlist1 = 0
2022
                    _mem4.channel_attributes[number].is_scanlist2 = 0
2023

    
2024
        return mem
(5-5/5)