Project

General

Profile

Feature #11072 » uvk5_IJV v2.9R3 (fix).py

Mode: FM, AM, CW, USB / Bandwidth: W, W+, N, N- - Julian Lilov, 01/25/2024 03:27 AM

 
1
# Quansheng UV-K5 driver (c) 2023 Jacek Lipkowski <sq5bpf@lipkowski.org>
2
#
3
# based on template.py Copyright 2012 Dan Smith <dsmith@danplanet.com>
4
#
5
#
6
# This is a preliminary version of a driver for the UV-K5
7
# It is based on my reverse engineering effort described here:
8
# https://github.com/sq5bpf/uvk5-reverse-engineering
9
#
10
# Warning: this driver is experimental, it may brick your radio,
11
# eat your lunch and mess up your configuration.
12
#
13
#
14
# This program is free software: you can redistribute it and/or modify
15
# it under the terms of the GNU General Public License as published by
16
# the Free Software Foundation, either version 2 of the License, or
17
# (at your option) any later version.
18
#
19
# This program is distributed in the hope that it will be useful,
20
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
# GNU General Public License for more details.
23
#
24
# You should have received a copy of the GNU General Public License
25
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
26
#
27
# Adapted to IJV firmware v2.9R3 by Julian Lilov (LZ1JDL)
28
# https://www.universirius.com/en_gb/preppers/quansheng-uv-k5-manuale-del-firmware-ijv/#Firmware-IJV
29

    
30

    
31
import struct
32
import logging
33

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

    
40
LOG = logging.getLogger(__name__)
41

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

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

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

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

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

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

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

    
90

    
91
  u8 step;
92
  u8 scrambler;
93
} channel[214];
94

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

    
105
#seekto 0xd7e;
106
u8 compander;
107

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

    
111
#seekto 0xe70;
112
u8 call_channel;
113
u8 squelch;
114
u8 max_talk_time;
115
u8 noaa_autoscan;
116
u8 key_lock;
117
u8 vox_switch;
118
u8 vox_level;
119
u8 mic_gain;
120
u8 unknown3;
121
u8 channel_display_mode;
122
u8 crossband;
123
u8 battery_save;
124
u8 dual_watch;
125
u8 backlight_auto_mode;
126
u8 tail_note_elimination;
127
u8 vfo_open;
128

    
129
#seekto 0xe90;
130
u8 beep_control;
131
u8 key1_shortpress_action;
132
u8 key1_longpress_action;
133
u8 key2_shortpress_action;
134
u8 key2_longpress_action;
135
u8 scan_resume_mode;
136
u8 auto_keypad_lock;
137
u8 power_on_dispmode;
138
u8 password[4];
139

    
140
#seekto 0xea0;
141
u8 keypad_tone;
142
u8 language;
143

    
144
#seekto 0xea0;
145
char qrz_label[8];
146

    
147
#seekto 0xea8;
148
u8 alarm_mode;
149
u8 reminding_of_end_talk;
150
u8 repeater_tail_elimination;
151

    
152
#seekto 0xeb0;
153
char logo_line1[16];
154
char logo_line2[16];
155

    
156
#seekto 0xed0;
157
struct {
158
u8 side_tone;
159
char separate_code;
160
char group_call_code;
161
u8 decode_response;
162
u8 auto_reset_time;
163
u8 preload_time;
164
u8 first_code_persist_time;
165
u8 hash_persist_time;
166
u8 code_persist_time;
167
u8 code_interval_time;
168
u8 permit_remote_kill;
169
} dtmf_settings;
170

    
171
#seekto 0xee0;
172
struct {
173
char dtmf_local_code[3];
174
char unused1[5];
175
char kill_code[5];
176
char unused2[3];
177
char revive_code[5];
178
char unused3[3];
179
char dtmf_up_code[16];
180
char dtmf_down_code[16];
181
} dtmf_settings_numbers;
182

    
183
#seekto 0xf18;
184
u8 scanlist_default;
185
u8 scanlist1_priority_scan;
186
u8 scanlist1_priority_ch1;
187
u8 scanlist1_priority_ch2;
188
u8 scanlist2_priority_scan;
189
u8 scanlist2_priority_ch1;
190
u8 scanlist2_priority_ch2;
191
u8 scanlist_unknown_0xff;
192

    
193

    
194
#seekto 0xf40;
195
struct {
196
u8 flock;
197
u8 tx350;
198
u8 killed;
199
u8 tx200;
200
u8 tx500;
201
u8 en350;
202
} lock;
203

    
204
#seekto 0xf46;
205
u8 beacon;
206

    
207
#seekto 0xf47;
208
u8 micbar;
209

    
210
#seekto 0xf47;
211
u8 bl_mode;
212

    
213
#seekto 0xf4f;
214
u8 signal_meter;
215

    
216
#seekto 0xf50;
217
struct {
218
char name[16];
219
} channelname[200];
220

    
221
#seekto 0x1c00;
222
struct {
223
char name[8];
224
char number[3];
225
char unused_00[5];
226
} dtmfcontact[16];
227

    
228
#seekto 0x1ed0;
229
struct {
230
struct {
231
    u8 start;
232
    u8 mid;
233
    u8 end;
234
} low;
235
struct {
236
    u8 start;
237
    u8 mid;
238
    u8 end;
239
} medium;
240
struct {
241
    u8 start;
242
    u8 mid;
243
    u8 end;
244
} high;
245
u8 unused_00[7];
246
} perbandpowersettings[7];
247

    
248
#seekto 0x1f40;
249
ul16 battery_level[6];
250
"""
251
# bits that we will save from the channel structure (mostly unknown)
252
SAVE_MASK_0A = 0b11001100
253
SAVE_MASK_0B = 0b11101100
254
SAVE_MASK_0C = 0b11100000
255
SAVE_MASK_0D = 0b11111000
256
SAVE_MASK_0E = 0b11110001
257
SAVE_MASK_0F = 0b11110000
258

    
259
# flags1
260
FLAGS1_OFFSET_NONE = 0b00
261
FLAGS1_OFFSET_MINUS = 0b10
262
FLAGS1_OFFSET_PLUS = 0b01
263

    
264
# flags2
265

    
266
POWER_HIGH = 0b10
267
POWER_MEDIUM = 0b01
268
POWER_LOW = 0b00
269

    
270
BANDWIDTH_WIDE = 0b00
271
BANDWIDTH_NARROW = 0b01
272
BANDWIDTH_NARROW_MINUS = 0b10
273
BANDWIDTH_WIDE_PLUS = 0b11
274

    
275
# bandwidth
276
BANDWIDTH_LIST = ["W", "N", "N-", "W+"]
277

    
278
# dtmf_flags
279
PTTID_LIST = ["off", "BOT", "EOT", "BOTH"]
280

    
281
# power
282
UVK5_POWER_LEVELS = [chirp_common.PowerLevel("Low",  watts=1.00),
283
                     chirp_common.PowerLevel("Med",  watts=2.50),
284
                     chirp_common.PowerLevel("High", watts=5.00)
285
                    ]
286

    
287
# scrambler
288
SCRAMBLER_LIST = ["Off", "2600Hz", "2700Hz", "2800Hz", "2900Hz", "3000Hz", "3100Hz", "3200Hz", "3300Hz", "3400Hz", "3500Hz"]
289

    
290
# channel display mode
291
CHANNELDISP_LIST = ["Frequency", "Channel No", "Channel Name", "Name_S Freq_L", "Name_L Freq_S"]
292

    
293
# Beacon
294
BEACON_LIST = ["Off","5 Sec","10 Sec","30 Sec","5 min","10 min","20 min"]
295

    
296
# battery save
297
BATSAVE_LIST = ["Off", "50%", "67%", "75%", "80%"]
298

    
299
# compander
300
COMPANDER_LIST = ["Off", "TX", "RX", "RX/TX"]
301

    
302
# mic gain
303
MICGAIN_LIST = ["+1.1dB","+4.0dB","+8.0dB","+12.0dB","+15.1dB"]
304

    
305

    
306
# Talk Time
307
TALKTIME_LIST = ["Off","30 Sec","1 min","3 min","5 min"]
308

    
309
# Backlight auto mode
310
BACKLIGHT_LIST = ["Off", "5 sec", "10 sec", "20 sec", "1 min", "3 min", "RX/TX", "ON", "NIGHT"]
311

    
312
# Crossband receiving/transmitting
313
CROSSBAND_LIST = ["Same VFO", "VFO A", "VFO B"]
314
DUALWATCH_LIST = ["Off", "On"]
315

    
316
# steps
317
STEPS = [0.02, 0.05, 0.10, 0.50, 1.00, 2.50, 5.00, 6.25, 8.33, 9.00, 10.00, 12.50, 25.00, 100.00, 1000.00]
318
                   
319
# ctcss/dcs codes
320
TMODES = ["", "Tone", "DTCS", "DTCS"]
321
TONE_NONE = 0
322
TONE_CTCSS = 1
323
TONE_DCS = 2
324
TONE_RDCS = 3
325

    
326

    
327
CTCSS_TONES = [
328
    67.0, 69.3, 71.9, 74.4, 77.0, 79.7, 82.5, 85.4,
329
    88.5, 91.5, 94.8, 97.4, 100.0, 103.5, 107.2, 110.9,
330
    114.8, 118.8, 123.0, 127.3, 131.8, 136.5, 141.3, 146.2,
331
    151.4, 156.7, 159.8, 162.2, 165.5, 167.9, 171.3, 173.8,
332
    177.3, 179.9, 183.5, 186.2, 189.9, 192.8, 196.6, 199.5,
333
    203.5, 206.5, 210.7, 218.1, 225.7, 229.1, 233.6, 241.8,
334
    250.3, 254.1
335
]
336

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

    
351
FLOCK_LIST = ["Off", "FCC", "CE", "GB", "430", "438"]
352

    
353
SCANRESUME_LIST = ["TIME: Resume after 5 seconds",
354
                   "CARRIER: Resume after signal disappears",
355
                   "SEARCH: Stop scanning after receiving a signal",
356
                   "LOG"]
357

    
358
WELCOME_LIST = ["None", "FW Mod", "Message"]
359
KEYPADTONE_LIST = ["Off", "Chinese", "English"]
360
LANGUAGE_LIST = ["Chinese", "English"]
361
ALARMMODE_LIST = ["SITE", "TONE"]
362
REMENDOFTALK_LIST = ["Off", "Single", "Roger", "MDC 1200", "Apollo Quindar", "Digital Code ID"]
363
RTE_LIST = ["Off", 
364
            "1*100ms", "2*100ms", "3*100ms", "4*100ms", "5*100ms",
365
            "6*100ms", "7*100ms", "8*100ms", "9*100ms", "10*100ms",
366
            "11*100ms", "12*100ms", "13*100ms", "14*100ms", "15*100ms",
367
            "16*100ms", "17*100ms", "18*100ms", "19*100ms", "20*100ms"]
368

    
369
MEM_SIZE = 0x2000  # size of all memory
370
PROG_SIZE = 0x1d00  # size of the memory that we will write
371
MEM_BLOCK = 0x80  # largest block of memory that we can reliably write
372

    
373
# fm radio supported frequencies
374
FMMIN = 76.0
375
FMMAX = 108.0
376

    
377
# bands supported by the UV-K5
378
BANDS = {
379
        0: [50.0, 76.0],
380
        1: [108.0, 135.9999],
381
        2: [136.0, 199.9990],
382
        3: [200.0, 299.9999],
383
        4: [350.0, 399.9999],
384
        5: [400.0, 469.9999],
385
        6: [470.0, 600.0]
386
        }
387

    
388
# for radios with modified firmware:
389
BANDS_NOLIMITS = {
390
        0: [18.0, 76.0],
391
        1: [108.0, 135.9999],
392
        2: [136.0, 199.9990],
393
        3: [200.0, 299.9999],
394
        4: [350.0, 399.9999],
395
        5: [400.0, 469.9999],
396
        6: [470.0, 1300.0]
397
        }
398

    
399
SPECIALS = {
400
        "F1(50M-76M)A": 200,
401
        "F1(50M-76M)B": 201,
402
        "F2(108M-136M)A": 202,
403
        "F2(108M-136M)B": 203,
404
        "F3(136M-174M)A": 204,
405
        "F3(136M-174M)B": 205,
406
        "F4(174M-350M)A": 206,
407
        "F4(174M-350M)B": 207,
408
        "F5(350M-400M)A": 208,
409
        "F5(350M-400M)B": 209,
410
        "F6(400M-470M)A": 210,
411
        "F6(400M-470M)B": 211,
412
        "F7(470M-600M)A": 212,
413
        "F7(470M-600M)B": 213
414
        }
415

    
416
VFO_CHANNEL_NAMES = ["F1(50M-76M)A", "F1(50M-76M)B",
417
                     "F2(108M-136M)A", "F2(108M-136M)B",
418
                     "F3(136M-174M)A", "F3(136M-174M)B",
419
                     "F4(174M-350M)A", "F4(174M-350M)B",
420
                     "F5(350M-400M)A", "F5(350M-400M)B",
421
                     "F6(400M-470M)A", "F6(400M-470M)B",
422
                     "F7(470M-600M)A", "F7(470M-600M)B"]
423

    
424
SCANLIST_LIST = ["None", "1", "2", "1+2"]
425

    
426
DTMF_CHARS = "0123456789ABCD*# "
427
DTMF_CHARS_ID = "0123456789ABCDabcd"
428
DTMF_CHARS_KILL = "0123456789ABCDabcd"
429
DTMF_CHARS_UPDOWN = "0123456789ABCDabcd#* "
430
DTMF_CODE_CHARS = "ABCD*# "
431
DTMF_DECODE_RESPONSE_LIST = ["None", "Ring", "Reply", "Both"]
432

    
433
KEYACTIONS_LIST = ["None", "Flashlight", "TX Power",
434
                   "Monitor", "Scan on/off", "VOX on/off",
435
                   "FM radio on/off", "VFO Change", "VFO Swap", 
436
                   "SQL +", "SQL -", "REGA Test", "REGA Alarm", "CW Call CQ"]
437

    
438

    
439
# the communication is obfuscated using this fine mechanism
440
def xorarr(data: bytes):
441
    tbl = [22, 108, 20, 230, 46, 145, 13, 64, 33, 53, 213, 64, 19, 3, 233, 128]
442
    x = b""
443
    r = 0
444
    for byte in data:
445
        x += bytes([byte ^ tbl[r]])
446
        r = (r+1) % len(tbl)
447
    return x
448

    
449

    
450
# if this crc was used for communication to AND from the radio, then it
451
# would be a measure to increase reliability.
452
# but it's only used towards the radio, so it's for further obfuscation
453
def calculate_crc16_xmodem(data: bytes):
454
    poly = 0x1021
455
    crc = 0x0
456
    for byte in data:
457
        crc = crc ^ (byte << 8)
458
        for i in range(8):
459
            crc = crc << 1
460
            if (crc & 0x10000):
461
                crc = (crc ^ poly) & 0xFFFF
462
    return crc & 0xFFFF
463

    
464

    
465
def _send_command(serport, data: bytes):
466
    """Send a command to UV-K5 radio"""
467
    LOG.debug("Sending command (unobfuscated) len=0x%4.4x:\n%s" %
468
              (len(data), util.hexprint(data)))
469

    
470
    crc = calculate_crc16_xmodem(data)
471
    data2 = data + struct.pack("<H", crc)
472

    
473
    command = struct.pack(">HBB", 0xabcd, len(data), 0) + \
474
        xorarr(data2) + \
475
        struct.pack(">H", 0xdcba)
476
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
477
        LOG.debug("Sending command (obfuscated):\n%s" % util.hexprint(command))
478
    try:
479
        result = serport.write(command)
480
    except Exception:
481
        raise errors.RadioError("Error writing data to radio")
482
    return result
483

    
484

    
485
def _receive_reply(serport):
486
    header = serport.read(4)
487
    if len(header) != 4:
488
        LOG.warning("Header short read: [%s] len=%i" %
489
                    (util.hexprint(header), len(header)))
490
        raise errors.RadioError("Header short read")
491
    if header[0] != 0xAB or header[1] != 0xCD or header[3] != 0x00:
492
        LOG.warning("Bad response header: %s len=%i" %
493
                    (util.hexprint(header), len(header)))
494
        raise errors.RadioError("Bad response header")
495

    
496
    cmd = serport.read(int(header[2]))
497
    if len(cmd) != int(header[2]):
498
        LOG.warning("Body short read: [%s] len=%i" %
499
                    (util.hexprint(cmd), len(cmd)))
500
        raise errors.RadioError("Command body short read")
501

    
502
    footer = serport.read(4)
503

    
504
    if len(footer) != 4:
505
        LOG.warning("Footer short read: [%s] len=%i" %
506
                    (util.hexprint(footer), len(footer)))
507
        raise errors.RadioError("Footer short read")
508

    
509
    if footer[2] != 0xDC or footer[3] != 0xBA:
510
        LOG.debug(
511
                "Reply before bad response footer (obfuscated)"
512
                "len=0x%4.4x:\n%s" % (len(cmd), util.hexprint(cmd)))
513
        LOG.warning("Bad response footer: %s len=%i" %
514
                    (util.hexprint(footer), len(footer)))
515
        raise errors.RadioError("Bad response footer")
516

    
517
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
518
        LOG.debug("Received reply (obfuscated) len=0x%4.4x:\n%s" %
519
                  (len(cmd), util.hexprint(cmd)))
520

    
521
    cmd2 = xorarr(cmd)
522

    
523
    LOG.debug("Received reply (unobfuscated) len=0x%4.4x:\n%s" %
524
              (len(cmd2), util.hexprint(cmd2)))
525

    
526
    return cmd2
527

    
528

    
529
def _getstring(data: bytes, begin, maxlen):
530
    tmplen = min(maxlen+1, len(data))
531
    s = [data[i] for i in range(begin, tmplen)]
532
    for key, val in enumerate(s):
533
        if val < ord(' ') or val > ord('~'):
534
            break
535
    return ''.join(chr(x) for x in s[0:key])
536

    
537

    
538
def _sayhello(serport):
539
    hellopacket = b"\x14\x05\x04\x00\x6a\x39\x57\x64"
540

    
541
    tries = 5
542
    while True:
543
        LOG.debug("Sending hello packet")
544
        _send_command(serport, hellopacket)
545
        o = _receive_reply(serport)
546
        if (o):
547
            break
548
        tries -= 1
549
        if tries == 0:
550
            LOG.warning("Failed to initialise radio")
551
            raise errors.RadioError("Failed to initialize radio")
552
    firmware = _getstring(o, 4, 16)
553
    LOG.info("Found firmware: %s" % firmware)
554
    return firmware
555

    
556

    
557
def _readmem(serport, offset, length):
558
    LOG.debug("Sending readmem offset=0x%4.4x len=0x%4.4x" % (offset, length))
559

    
560
    readmem = b"\x1b\x05\x08\x00" + \
561
        struct.pack("<HBB", offset, length, 0) + \
562
        b"\x6a\x39\x57\x64"
563
    _send_command(serport, readmem)
564
    o = _receive_reply(serport)
565
    if DEBUG_SHOW_MEMORY_ACTIONS:
566
        LOG.debug("readmem Received data len=0x%4.4x:\n%s" %
567
                  (len(o), util.hexprint(o)))
568
    return o[8:]
569

    
570

    
571
def _writemem(serport, data, offset):
572
    LOG.debug("Sending writemem offset=0x%4.4x len=0x%4.4x" %
573
              (offset, len(data)))
574

    
575
    if DEBUG_SHOW_MEMORY_ACTIONS:
576
        LOG.debug("writemem sent data offset=0x%4.4x len=0x%4.4x:\n%s" %
577
                  (offset, len(data), util.hexprint(data)))
578

    
579
    dlen = len(data)
580
    writemem = b"\x1d\x05" + \
581
        struct.pack("<BBHBB", dlen+8, 0, offset, dlen, 1) + \
582
        b"\x6a\x39\x57\x64"+data
583

    
584
    _send_command(serport, writemem)
585
    o = _receive_reply(serport)
586

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

    
589
    if (o[0] == 0x1e
590
            and
591
            o[4] == (offset & 0xff)
592
            and
593
            o[5] == (offset >> 8) & 0xff):
594
        return True
595
    else:
596
        LOG.warning("Bad data from writemem")
597
        raise errors.RadioError("Bad response to writemem")
598

    
599

    
600
def _resetradio(serport):
601
    resetpacket = b"\xdd\x05\x00\x00"
602
    _send_command(serport, resetpacket)
603

    
604

    
605
def do_download(radio):
606
    serport = radio.pipe
607
    serport.timeout = 0.5
608
    status = chirp_common.Status()
609
    status.cur = 0
610
    status.max = MEM_SIZE
611
    status.msg = "Downloading from radio"
612
    radio.status_fn(status)
613

    
614
    eeprom = b""
615
    f = _sayhello(serport)
616
    if f:
617
        radio.FIRMWARE_VERSION = f
618
    else:
619
        raise errors.RadioError('Unable to determine firmware version')
620

    
621
    addr = 0
622
    while addr < MEM_SIZE:
623
        o = _readmem(serport, addr, MEM_BLOCK)
624
        status.cur = addr
625
        radio.status_fn(status)
626

    
627
        if o and len(o) == MEM_BLOCK:
628
            eeprom += o
629
            addr += MEM_BLOCK
630
        else:
631
            raise errors.RadioError("Memory download incomplete")
632

    
633
    return memmap.MemoryMapBytes(eeprom)
634

    
635

    
636
def do_upload(radio):
637
    serport = radio.pipe
638
    serport.timeout = 0.5
639
    status = chirp_common.Status()
640
    status.cur = 0
641
    status.max = PROG_SIZE
642
    status.msg = "Uploading to radio"
643
    radio.status_fn(status)
644

    
645
    f = _sayhello(serport)
646
    if f:
647
        radio.FIRMWARE_VERSION = f
648
    else:
649
        return False
650

    
651
    addr = 0
652
    while addr < PROG_SIZE:
653
        o = radio.get_mmap()[addr:addr+MEM_BLOCK]
654
        _writemem(serport, o, addr)
655
        status.cur = addr
656
        radio.status_fn(status)
657
        if o:
658
            addr += MEM_BLOCK
659
        else:
660
            raise errors.RadioError("Memory upload incomplete")
661
    status.msg = "Uploaded OK"
662

    
663
    _resetradio(serport)
664

    
665
    return True
666

    
667

    
668
def _find_band(nolimits, hz):
669
    mhz = hz/1000000.0
670
    if nolimits:
671
        B = BANDS_NOLIMITS
672
    else:
673
        B = BANDS
674

    
675
    # currently the hacked firmware sets band=1 below 50 MHz
676
    if nolimits and mhz < 50.0:
677
        return 1
678

    
679
    for a in B:
680
        if mhz >= B[a][0] and mhz <= B[a][1]:
681
            return a
682
    return False
683

    
684

    
685
@directory.register
686
class UVK5Radio(chirp_common.CloneModeRadio):
687
    """Quansheng UV-K5"""
688
    VENDOR = "Quansheng"
689
    MODEL = "UV-K5-IJV v2.9R3 (fix)"
690
    BAUD_RATE = 38400
691
    NEEDS_COMPAT_SERIAL = False
692
    FIRMWARE_VERSION = ""
693
    _expanded_limits = False
694

    
695
    def get_prompts(x=None):
696
        rp = chirp_common.RadioPrompts()
697
        rp.experimental = _(
698
            'This is an experimental driver for the Quansheng UV-K5. '
699
            'It may harm your radio, or worse. Use at your own risk.\n\n'
700
            'Before attempting to do any changes please download '
701
            'the memory image from the radio with chirp '
702
            'and keep it. This can be later used to recover the '
703
            'original settings. \n\n'
704
            'some details are not yet implemented')
705
        rp.pre_download = _(
706
            "1. Turn radio on.\n"
707
            "2. Connect cable to mic/spkr connector.\n"
708
            "3. Make sure connector is firmly connected.\n"
709
            "4. Click OK to download image from device.\n\n"
710
            "It will may not work if you turn on the radio "
711
            "with the cable already attached\n")
712
        rp.pre_upload = _(
713
            "1. Turn radio on.\n"
714
            "2. Connect cable to mic/spkr connector.\n"
715
            "3. Make sure connector is firmly connected.\n"
716
            "4. Click OK to upload the image to device.\n\n"
717
            "It will may not work if you turn on the radio "
718
            "with the cable already attached")
719
        return rp
720

    
721
    # Return information about this radio's features, including
722
    # how many memories it has, what bands it supports, etc
723
    def get_features(self):
724
        rf = chirp_common.RadioFeatures()
725
        rf.has_bank = False
726
        rf.valid_dtcs_codes = DTCS_CODES
727
        rf.has_rx_dtcs = True
728
        rf.has_ctone = True
729
        rf.has_settings = True
730
        rf.has_comment = False
731
        rf.valid_name_length = 10
732
        rf.valid_power_levels = UVK5_POWER_LEVELS
733
        rf.valid_special_chans = list(SPECIALS.keys())
734
        rf.valid_duplexes = ["", "-", "+", "off"]
735

    
736
        # hack so we can input any frequency,
737
        # the 0.1 and 0.01 steps don't work unfortunately
738
        rf.valid_tuning_steps = [0.01, 0.1, 1.0] + STEPS
739

    
740
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
741
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
742
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
743

    
744
        rf.valid_characters = chirp_common.CHARSET_ASCII
745
        rf.valid_modes = ["FM", "AM", "USB", "CW"]
746

    
747
        rf.valid_skips = [""]
748

    
749
        # This radio supports memories 1-200, 201-214 are the VFO memories
750
        rf.memory_bounds = (1, 200)
751

    
752
        rf.valid_bands = []
753
        for a in BANDS_NOLIMITS:
754
            rf.valid_bands.append(
755
                    (int(BANDS_NOLIMITS[a][0]*1000000),
756
                     int(BANDS_NOLIMITS[a][1]*1000000)))
757
        return rf
758

    
759
    # Do a download of the radio from the serial port
760
    def sync_in(self):
761
        self._mmap = do_download(self)
762
        self.process_mmap()
763

    
764
    # Do an upload of the radio to the serial port
765
    def sync_out(self):
766
        do_upload(self)
767

    
768
    # Convert the raw byte array into a memory object structure
769
    def process_mmap(self):
770
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
771

    
772
    # Return a raw representation of the memory object, which
773
    # is very helpful for development
774
    def get_raw_memory(self, number):
775
        return repr(self._memobj.channel[number-1])
776

    
777
    def validate_memory(self, mem):
778
        msgs = super().validate_memory(mem)
779

    
780
        if mem.duplex == 'off':
781
            return msgs
782

    
783
        # find tx frequency
784
        if mem.duplex == '-':
785
            txfreq = mem.freq - mem.offset
786
        elif mem.duplex == '+':
787
            txfreq = mem.freq + mem.offset
788
        else:
789
            txfreq = mem.freq
790

    
791
        # find band
792
        band = _find_band(self._expanded_limits, txfreq)
793
        if band is False:
794
            msg = "Transmit frequency %.4f MHz is not supported by this radio"\
795
                   % (txfreq/1000000.0)
796
            msgs.append(chirp_common.ValidationError(msg))
797

    
798
        band = _find_band(self._expanded_limits, mem.freq)
799
        if band is False:
800
            msg = "The frequency %.4f MHz is not supported by this radio" \
801
                   % (mem.freq/1000000.0)
802
            msgs.append(chirp_common.ValidationError(msg))
803

    
804
        return msgs
805

    
806
    def _set_tone(self, mem, _mem):
807
        ((txmode, txtone, txpol),
808
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
809

    
810
        if txmode == "Tone":
811
            txtoval = CTCSS_TONES.index(txtone)
812
            txmoval = 0b01
813
        elif txmode == "DTCS":
814
            txmoval = txpol == "R" and 0b11 or 0b10
815
            txtoval = DTCS_CODES.index(txtone)
816
        else:
817
            txmoval = 0
818
            txtoval = 0
819

    
820
        if rxmode == "Tone":
821
            rxtoval = CTCSS_TONES.index(rxtone)
822
            rxmoval = 0b01
823
        elif rxmode == "DTCS":
824
            rxmoval = rxpol == "R" and 0b11 or 0b10
825
            rxtoval = DTCS_CODES.index(rxtone)
826
        else:
827
            rxmoval = 0
828
            rxtoval = 0
829

    
830
        _mem.rxcodeflag = rxmoval
831
        _mem.txcodeflag = txmoval
832
        _mem.unknown1 = 0
833
        _mem.unknown2 = 0
834
        _mem.rxcode = rxtoval
835
        _mem.txcode = txtoval
836

    
837
    def _get_tone(self, mem, _mem):
838
        rxtype = _mem.rxcodeflag
839
        txtype = _mem.txcodeflag
840
        rx_tmode = TMODES[rxtype]
841
        tx_tmode = TMODES[txtype]
842

    
843
        rx_tone = tx_tone = None
844

    
845
        if tx_tmode == "Tone":
846
            if _mem.txcode < len(CTCSS_TONES):
847
                tx_tone = CTCSS_TONES[_mem.txcode]
848
            else:
849
                tx_tone = 0
850
                tx_tmode = ""
851
        elif tx_tmode == "DTCS":
852
            if _mem.txcode < len(DTCS_CODES):
853
                tx_tone = DTCS_CODES[_mem.txcode]
854
            else:
855
                tx_tone = 0
856
                tx_tmode = ""
857

    
858
        if rx_tmode == "Tone":
859
            if _mem.rxcode < len(CTCSS_TONES):
860
                rx_tone = CTCSS_TONES[_mem.rxcode]
861
            else:
862
                rx_tone = 0
863
                rx_tmode = ""
864
        elif rx_tmode == "DTCS":
865
            if _mem.rxcode < len(DTCS_CODES):
866
                rx_tone = DTCS_CODES[_mem.rxcode]
867
            else:
868
                rx_tone = 0
869
                rx_tmode = ""
870

    
871
        tx_pol = txtype == 0x03 and "R" or "N"
872
        rx_pol = rxtype == 0x03 and "R" or "N"
873

    
874
        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
875
                                       (rx_tmode, rx_tone, rx_pol))
876

    
877
    # Extract a high-level memory object from the low-level memory map
878
    # This is called to populate a memory in the UI
879
    def get_memory(self, number2):
880

    
881
        mem = chirp_common.Memory()
882

    
883
        if isinstance(number2, str):
884
            number = SPECIALS[number2]
885
            mem.extd_number = number2
886
        else:
887
            number = number2 - 1
888

    
889
        mem.number = number + 1
890

    
891
        _mem = self._memobj.channel[number]
892

    
893
        tmpcomment = ""
894

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

    
900
        tmpscn = SCANLIST_LIST[0]
901

    
902
        # We'll also look at the channel attributes if a memory has them
903
        if number < 200:
904
            _mem3 = self._memobj.channel_attributes[number]
905
            # free memory bit
906
            if _mem3.is_free > 0:
907
                is_empty = True
908
            # scanlists
909
            if _mem3.is_scanlist1 > 0 and _mem3.is_scanlist2 > 0:
910
                tmpscn = SCANLIST_LIST[3]  # "1+2"
911
            elif _mem3.is_scanlist1 > 0:
912
                tmpscn = SCANLIST_LIST[1]  # "1"
913
            elif _mem3.is_scanlist2 > 0:
914
                tmpscn = SCANLIST_LIST[2]  # "2"
915

    
916
        if is_empty:
917
            mem.empty = True
918
            # set some sane defaults:
919
            mem.power = UVK5_POWER_LEVELS[2]
920
            mem.extra = RadioSettingGroup("Extra", "extra")
921

    
922
            rs = RadioSetting(
923
                "bandwidth", "Bandwidth",
924
                RadioSettingValueList(BANDWIDTH_LIST, BANDWIDTH_LIST[0]))
925
            mem.extra.append(rs)
926

    
927
            rs = RadioSetting(
928
                "bclo", "BCLO",
929
                RadioSettingValueBoolean(False))
930
            mem.extra.append(rs)
931
            rs = RadioSetting(
932
                "frev", "FreqRev",
933
                RadioSettingValueBoolean(False))
934
            mem.extra.append(rs)
935
            rs = RadioSetting(
936
                "pttid", "PTTID",
937
                RadioSettingValueList(PTTID_LIST, PTTID_LIST[0]))
938
            mem.extra.append(rs)
939
            rs = RadioSetting(
940
                "dtmfdecode", _("DTMF decode"),
941
                RadioSettingValueBoolean(False))
942
            mem.extra.append(rs)
943
            rs = RadioSetting(
944
                "scrambler", _("Scrambler"),
945
                RadioSettingValueList(SCRAMBLER_LIST, SCRAMBLER_LIST[0]))
946
            mem.extra.append(rs)
947

    
948
            rs = RadioSetting(
949
                "scanlists", _("Scanlists"),
950
                RadioSettingValueList(SCANLIST_LIST, SCANLIST_LIST[0]))
951
            mem.extra.append(rs)
952

    
953
            # actually the step and duplex are overwritten by chirp based on
954
            # bandplan. they are here to document sane defaults for IARU r1
955
            # mem.tuning_step = 25.0
956
            # mem.duplex = ""
957

    
958
            return mem
959

    
960
        if number > 199:
961
            mem.immutable = ["name", "scanlists"]
962
        else:
963
            _mem2 = self._memobj.channelname[number]
964
            for char in _mem2.name:
965
                if str(char) == "\xFF" or str(char) == "\x00":
966
                    break
967
                mem.name += str(char)
968
            mem.name = mem.name.rstrip()
969

    
970
        # Convert your low-level frequency to Hertz
971
        mem.freq = int(_mem.freq)*10
972
        mem.offset = int(_mem.offset)*10
973

    
974
        if (mem.offset == 0):
975
            mem.duplex = ''
976
        else:
977
            if _mem.shift == FLAGS1_OFFSET_MINUS:
978
                if _mem.freq == _mem.offset:
979
                    # fake tx disable by setting tx to 0 MHz
980
                    mem.duplex = 'off'
981
                    mem.offset = 0
982
                else:
983
                    mem.duplex = '-'
984
            elif _mem.shift == FLAGS1_OFFSET_PLUS:
985
                mem.duplex = '+'
986
            else:
987
                mem.duplex = ''
988

    
989
        # tone data
990
        self._get_tone(mem, _mem)
991

    
992
        # mode
993
        if _mem.enable_extramodes > 0:
994
            if _mem.enable_am > 0:
995
                mem.mode = "CW"
996
            else:
997
                mem.mode = "USB"
998
        else:
999
            if _mem.enable_am > 0:
1000
                mem.mode = "AM"
1001
            else:
1002
                mem.mode = "FM"
1003

    
1004
        # tuning step
1005
        tstep = _mem.step & 0xF
1006
        if tstep < len(STEPS):
1007
            mem.tuning_step = STEPS[tstep]
1008
        else:
1009
            mem.tuning_step = 0.02
1010

    
1011
        # power
1012
        if _mem.txpower == POWER_HIGH:
1013
            mem.power = UVK5_POWER_LEVELS[2]
1014
        elif _mem.txpower == POWER_MEDIUM:
1015
            mem.power = UVK5_POWER_LEVELS[1]
1016
        else:
1017
            mem.power = UVK5_POWER_LEVELS[0]
1018

    
1019
        # We'll consider any blank (i.e. 0 MHz frequency) to be empty
1020
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
1021
            mem.empty = True
1022
        else:
1023
            mem.empty = False
1024

    
1025
        mem.extra = RadioSettingGroup("Extra", "extra")
1026

    
1027
        # bandwidth
1028
        bwidth = _mem.bandwidth
1029
        rs = RadioSetting("bandwidth", "Bandwidth", RadioSettingValueList(
1030
            BANDWIDTH_LIST, BANDWIDTH_LIST[bwidth]))
1031
        mem.extra.append(rs)
1032
        tmpcomment += "bandwidth:"+BANDWIDTH_LIST[bwidth]+" "
1033

    
1034
        # BCLO
1035
        is_bclo = bool(_mem.bclo > 0)
1036
        rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(is_bclo))
1037
        mem.extra.append(rs)
1038
        tmpcomment += "BCLO:"+(is_bclo and "ON" or "off")+" "
1039

    
1040
        # Frequency reverse - whatever that means, don't see it in the manual
1041
        is_frev = bool(_mem.freq_reverse > 0)
1042
        rs = RadioSetting("frev", "FreqRev", RadioSettingValueBoolean(is_frev))
1043
        mem.extra.append(rs)
1044
        tmpcomment += "FreqReverse:"+(is_frev and "ON" or "off")+" "
1045

    
1046
        # PTTID
1047
        pttid = _mem.dtmf_pttid
1048
        rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
1049
            PTTID_LIST, PTTID_LIST[pttid]))
1050
        mem.extra.append(rs)
1051
        tmpcomment += "PTTid:"+PTTID_LIST[pttid]+" "
1052

    
1053
        # DTMF DECODE
1054
        is_dtmf = bool(_mem.dtmf_decode > 0)
1055
        rs = RadioSetting("dtmfdecode", _("DTMF decode"),
1056
                          RadioSettingValueBoolean(is_dtmf))
1057
        mem.extra.append(rs)
1058
        tmpcomment += "DTMFdecode:"+(is_dtmf and "ON" or "off")+" "
1059

    
1060
        # Scrambler
1061
        if _mem.scrambler & 0x0f < len(SCRAMBLER_LIST):
1062
            enc = _mem.scrambler & 0x0f
1063
        else:
1064
            enc = 0
1065

    
1066
        rs = RadioSetting("scrambler", _("Scrambler"), RadioSettingValueList(
1067
            SCRAMBLER_LIST, SCRAMBLER_LIST[enc]))
1068
        mem.extra.append(rs)
1069
        tmpcomment += "Scrambler:"+SCRAMBLER_LIST[enc]+" "
1070

    
1071
        rs = RadioSetting("scanlists", _("Scanlists"), RadioSettingValueList(
1072
            SCANLIST_LIST, tmpscn))
1073
        mem.extra.append(rs)
1074

    
1075
        return mem
1076

    
1077
    def set_settings(self, settings):
1078
        _mem = self._memobj
1079
        
1080
        s1 = False
1081
        s2 = False
1082

    
1083
        for element in settings:
1084
            if not isinstance(element, RadioSetting):
1085
                self.set_settings(element)
1086
                continue
1087

    
1088
            # basic settings
1089

    
1090
            # call channel
1091
            if element.get_name() == "call_channel":
1092
                _mem.call_channel = int(element.value)-1
1093

    
1094
            # squelch
1095
            if element.get_name() == "squelch":
1096
                _mem.squelch = int(element.value)
1097

    
1098
            # TOT
1099
            if element.get_name() == "max_talk_time":
1100
                _mem.max_talk_time = TALKTIME_LIST.index(str(element.value))
1101

    
1102
            # Beacon
1103
            if element.get_name() == "beacon":
1104
                _mem.beacon = BEACON_LIST.index(str(element.value))
1105

    
1106
            # NOAA autoscan
1107
#            if element.get_name() == "noaa_autoscan":
1108
#                _mem.noaa_autoscan = element.value and 1 or 0
1109

    
1110
            # VOX switch
1111
            if element.get_name() == "vox_switch":
1112
                _mem.vox_switch = element.value and 1 or 0
1113

    
1114
            # vox level
1115
            if element.get_name() == "vox_level":
1116
                _mem.vox_level = int(element.value)-1
1117

    
1118
            # mic gain
1119
            if element.get_name() == "mic_gain":
1120
                _mem.mic_gain = MICGAIN_LIST.index(str(element.value))
1121

    
1122
            # MicBar
1123
            if element.get_name() == "micbar":
1124
                tmp_micbar = element.value and 0x10 or 0x00
1125
                s1 = True
1126

    
1127
            if element.get_name() == "bl_mode":
1128
                tmp_bl_mode = element.value and 0x20 or 0x00
1129
                s2 = True
1130

    
1131
            if s1 and s2:
1132
                _mem.micbar = tmp_micbar | tmp_bl_mode
1133
                _mem.bl_mode = tmp_micbar | tmp_bl_mode
1134

    
1135
            # Signal Meter
1136
            if element.get_name() == "signal_meter":
1137
                _mem.signal_meter = (element.value and 0x04 or 0x00) | 0x02
1138

    
1139
            # Channel display mode
1140
            if element.get_name() == "channel_display_mode":
1141
                _mem.channel_display_mode = CHANNELDISP_LIST.index(
1142
                    str(element.value))
1143

    
1144
            # Crossband receiving/transmitting
1145
            if element.get_name() == "crossband":
1146
                _mem.crossband = CROSSBAND_LIST.index(str(element.value))
1147

    
1148
            # Battery Save
1149
            if element.get_name() == "battery_save":
1150
                _mem.battery_save = BATSAVE_LIST.index(str(element.value))
1151

    
1152
            # Compander
1153
            if element.get_name() == "compander":
1154
                _mem.compander = (COMPANDER_LIST.index(str(element.value)) << 4) | 0x03
1155

    
1156
            # Dual Watch
1157
            if element.get_name() == "dualwatch":
1158
                _mem.dual_watch = DUALWATCH_LIST.index(str(element.value))
1159

    
1160
            # Backlight auto mode
1161
            if element.get_name() == "backlight_auto_mode":
1162
                _mem.backlight_auto_mode = \
1163
                        BACKLIGHT_LIST.index(str(element.value))
1164

    
1165
            # Tail tone elimination
1166
            if element.get_name() == "tail_note_elimination":
1167
                _mem.tail_note_elimination = element.value and 1 or 0
1168

    
1169
            # VFO Open
1170
#            if element.get_name() == "vfo_open":
1171
#                _mem.vfo_open = element.value and 1 or 0
1172

    
1173
            # Beep control
1174
            if element.get_name() == "beep_control":
1175
                _mem.beep_control = element.value and 1 or 0
1176

    
1177
            # Scan resume mode
1178
            if element.get_name() == "scan_resume_mode":
1179
                _mem.scan_resume_mode = SCANRESUME_LIST.index(
1180
                    str(element.value))
1181

    
1182
            # Keypad lock
1183
            if element.get_name() == "key_lock":
1184
                _mem.key_lock = element.value and 1 or 0
1185

    
1186
            # Auto keypad lock
1187
            if element.get_name() == "auto_keypad_lock":
1188
                _mem.auto_keypad_lock = element.value and 1 or 0
1189

    
1190
            # Power on display mode
1191
            if element.get_name() == "welcome_mode":
1192
                _mem.power_on_dispmode = WELCOME_LIST.index(str(element.value))
1193

    
1194
            # Keypad Tone
1195
#            if element.get_name() == "keypad_tone":
1196
#                _mem.keypad_tone = KEYPADTONE_LIST.index(str(element.value))
1197

    
1198
            # Language
1199
#            if element.get_name() == "language":
1200
#                _mem.language = LANGUAGE_LIST.index(str(element.value))
1201

    
1202
            # Alarm mode
1203
#            if element.get_name() == "alarm_mode":
1204
#                _mem.alarm_mode = ALARMMODE_LIST.index(str(element.value))
1205

    
1206
            # Reminding of end of talk
1207
            if element.get_name() == "reminding_of_end_talk":
1208
                _mem.reminding_of_end_talk = REMENDOFTALK_LIST.index(
1209
                    str(element.value))
1210

    
1211
            # Repeater tail tone elimination
1212
            if element.get_name() == "repeater_tail_elimination":
1213
                _mem.repeater_tail_elimination = RTE_LIST.index(
1214
                    str(element.value))
1215

    
1216

    
1217

    
1218
            # Logo string 1
1219
            if element.get_name() == "logo1":
1220
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
1221
                _mem.logo_line1 = b[0:12]+"\x00\xff\xff\xff"
1222

    
1223
            # Logo string 2
1224
            if element.get_name() == "logo2":
1225
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
1226
                _mem.logo_line2 = b[0:12]+"\x00\xff\xff\xff"
1227

    
1228
            # QRZ label
1229
            if element.get_name() == "qrz_label":
1230
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*8
1231
                _mem.qrz_label = b[0:8]
1232

    
1233
            # unlock settings
1234

    
1235
            # FLOCK
1236
            if element.get_name() == "flock":
1237
                _mem.lock.flock = FLOCK_LIST.index(str(element.value))
1238

    
1239
            # 350TX
1240
#            if element.get_name() == "tx350":
1241
#                _mem.lock.tx350 = element.value and 1 or 0
1242

    
1243
            # 200TX
1244
#            if element.get_name() == "tx200":
1245
#                _mem.lock.tx200 = element.value and 1 or 0
1246

    
1247
            # 500TX
1248
#            if element.get_name() == "tx500":
1249
#                _mem.lock.tx500 = element.value and 1 or 0
1250

    
1251
            # 350EN
1252
#            if element.get_name() == "en350":
1253
#                _mem.lock.en350 = element.value and 1 or 0
1254

    
1255
            # SCREN
1256
#            if element.get_name() == "enscramble":
1257
#                _mem.lock.enscramble = element.value and 1 or 0
1258

    
1259
            # KILLED
1260
            if element.get_name() == "killed":
1261
                _mem.lock.killed = element.value and 1 or 0
1262

    
1263
            # fm radio
1264
            for i in range(1, 21):
1265
                freqname = "FM_" + str(i)
1266
                if element.get_name() == freqname:
1267
                    val = str(element.value).strip()
1268
                    try:
1269
                        val2 = int(float(val)*10)
1270
                    except Exception:
1271
                        val2 = 0xffff
1272

    
1273
                    if val2 < FMMIN*10 or val2 > FMMAX*10:
1274
                        val2 = 0xffff
1275
#                        raise errors.InvalidValueError(
1276
#                                "FM radio frequency should be a value "
1277
#                                "in the range %.1f - %.1f" % (FMMIN , FMMAX))
1278
                    _mem.fmfreq[i-1] = val2
1279

    
1280
            # dtmf settings
1281
            if element.get_name() == "dtmf_side_tone":
1282
                _mem.dtmf_settings.side_tone = \
1283
                        element.value and 1 or 0
1284

    
1285
            if element.get_name() == "dtmf_separate_code":
1286
                _mem.dtmf_settings.separate_code = str(element.value)
1287

    
1288
            if element.get_name() == "dtmf_group_call_code":
1289
                _mem.dtmf_settings.group_call_code = element.value
1290

    
1291
            if element.get_name() == "dtmf_decode_response":
1292
                _mem.dtmf_settings.decode_response = \
1293
                        DTMF_DECODE_RESPONSE_LIST.index(str(element.value))
1294

    
1295
            if element.get_name() == "dtmf_auto_reset_time":
1296
                _mem.dtmf_settings.auto_reset_time = \
1297
                        int(int(element.value)/10)
1298

    
1299
            if element.get_name() == "dtmf_preload_time":
1300
                _mem.dtmf_settings.preload_time = \
1301
                        int(int(element.value)/10)
1302

    
1303
            if element.get_name() == "dtmf_first_code_persist_time":
1304
                _mem.dtmf_settings.first_code_persist_time = \
1305
                        int(int(element.value)/10)
1306

    
1307
            if element.get_name() == "dtmf_hash_persist_time":
1308
                _mem.dtmf_settings.hash_persist_time = \
1309
                        int(int(element.value)/10)
1310

    
1311
            if element.get_name() == "dtmf_code_persist_time":
1312
                _mem.dtmf_settings.code_persist_time = \
1313
                        int(int(element.value)/10)
1314

    
1315
            if element.get_name() == "dtmf_code_interval_time":
1316
                _mem.dtmf_settings.code_interval_time = \
1317
                        int(int(element.value)/10)
1318

    
1319
            if element.get_name() == "dtmf_permit_remote_kill":
1320
                _mem.dtmf_settings.permit_remote_kill = \
1321
                        element.value and 1 or 0
1322

    
1323
            if element.get_name() == "dtmf_dtmf_local_code":
1324
                k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*3
1325
                _mem.dtmf_settings_numbers.dtmf_local_code = k[0:3]
1326

    
1327
            if element.get_name() == "dtmf_dtmf_up_code":
1328
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*16
1329
                _mem.dtmf_settings_numbers.dtmf_up_code = k[0:16]
1330

    
1331
            if element.get_name() == "dtmf_dtmf_down_code":
1332
                k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*16
1333
                _mem.dtmf_settings_numbers.dtmf_down_code = k[0:16]
1334

    
1335
            if element.get_name() == "dtmf_kill_code":
1336
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*5
1337
                _mem.dtmf_settings_numbers.kill_code = k[0:5]
1338

    
1339
            if element.get_name() == "dtmf_revive_code":
1340
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*5
1341
                _mem.dtmf_settings_numbers.revive_code = k[0:5]
1342

    
1343
            # dtmf contacts
1344
            for i in range(1, 17):
1345
                varname = "DTMF_" + str(i)
1346
                if element.get_name() == varname:
1347
                    k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*8
1348
                    _mem.dtmfcontact[i-1].name = k[0:8]
1349

    
1350
                varnumname = "DTMFNUM_" + str(i)
1351
                if element.get_name() == varnumname:
1352
                    k = str(element.value).rstrip("\x20\xff\x00") + "\xff"*3
1353
                    _mem.dtmfcontact[i-1].number = k[0:3]
1354

    
1355
            # scanlist stuff
1356
            if element.get_name() == "scanlist_default":
1357
                val = (int(element.value) == 2) and 1 or 0
1358
                _mem.scanlist_default = val
1359

    
1360
            if element.get_name() == "scanlist1_priority_scan":
1361
                _mem.scanlist1_priority_scan = \
1362
                        element.value and 1 or 0
1363

    
1364
            if element.get_name() == "scanlist2_priority_scan":
1365
                _mem.scanlist2_priority_scan = \
1366
                        element.value and 1 or 0
1367

    
1368
            if element.get_name() == "scanlist1_priority_ch1" or \
1369
                    element.get_name() == "scanlist1_priority_ch2" or \
1370
                    element.get_name() == "scanlist2_priority_ch1" or \
1371
                    element.get_name() == "scanlist2_priority_ch2":
1372

    
1373
                val = int(element.value)
1374

    
1375
                if val > 200 or val < 1:
1376
                    val = 0xff
1377
                else:
1378
                    val -= 1
1379

    
1380
                if element.get_name() == "scanlist1_priority_ch1":
1381
                    _mem.scanlist1_priority_ch1 = val
1382
                if element.get_name() == "scanlist1_priority_ch2":
1383
                    _mem.scanlist1_priority_ch2 = val
1384
                if element.get_name() == "scanlist2_priority_ch1":
1385
                    _mem.scanlist2_priority_ch1 = val
1386
                if element.get_name() == "scanlist2_priority_ch2":
1387
                    _mem.scanlist2_priority_ch2 = val
1388

    
1389
            if element.get_name() == "key1_shortpress_action":
1390
                _mem.key1_shortpress_action = KEYACTIONS_LIST.index(
1391
                        str(element.value))
1392

    
1393
            if element.get_name() == "key1_longpress_action":
1394
                _mem.key1_longpress_action = KEYACTIONS_LIST.index(
1395
                        str(element.value))
1396

    
1397
            if element.get_name() == "key2_shortpress_action":
1398
                _mem.key2_shortpress_action = KEYACTIONS_LIST.index(
1399
                        str(element.value))
1400

    
1401
            if element.get_name() == "key2_longpress_action":
1402
                _mem.key2_longpress_action = KEYACTIONS_LIST.index(
1403
                        str(element.value))
1404

    
1405
            if element.get_name() == "nolimits":
1406
                LOG.warning("User expanded band limits")
1407
                self._expanded_limits = bool(element.value)
1408

    
1409
    def get_settings(self):
1410
        _mem = self._memobj
1411
        basic = RadioSettingGroup("basic", "Basic Settings")
1412
        keya = RadioSettingGroup("keya", "Programmable keys")
1413
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1414
        dtmfc = RadioSettingGroup("dtmfc", "DTMF Contacts")
1415
        scanl = RadioSettingGroup("scn", "Scan Lists")
1416
        unlock = RadioSettingGroup("unlock", "Unlock Settings")
1417
        fmradio = RadioSettingGroup("fmradio", _("FM Radio"))
1418

    
1419
        roinfo = RadioSettingGroup("roinfo", _("Driver information"))
1420

    
1421
        top = RadioSettings(
1422
                basic, keya, dtmf, dtmfc, scanl, unlock, fmradio, roinfo)
1423

    
1424
        # Programmable keys
1425
        tmpval = int(_mem.key1_shortpress_action)
1426
        if tmpval >= len(KEYACTIONS_LIST):
1427
            tmpval = 0
1428
        rs = RadioSetting("key1_shortpress_action", "Side key 1 short press",
1429
                          RadioSettingValueList(
1430
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1431
        keya.append(rs)
1432

    
1433
        tmpval = int(_mem.key1_longpress_action)
1434
        if tmpval >= len(KEYACTIONS_LIST):
1435
            tmpval = 0
1436
        rs = RadioSetting("key1_longpress_action", "Side key 1 long press",
1437
                          RadioSettingValueList(
1438
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1439
        keya.append(rs)
1440

    
1441
        tmpval = int(_mem.key2_shortpress_action)
1442
        if tmpval >= len(KEYACTIONS_LIST):
1443
            tmpval = 0
1444
        rs = RadioSetting("key2_shortpress_action", "Side key 2 short press",
1445
                          RadioSettingValueList(
1446
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1447
        keya.append(rs)
1448

    
1449
        tmpval = int(_mem.key2_longpress_action)
1450
        if tmpval >= len(KEYACTIONS_LIST):
1451
            tmpval = 0
1452
        rs = RadioSetting("key2_longpress_action", "Side key 2 long press",
1453
                          RadioSettingValueList(
1454
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1455
        keya.append(rs)
1456

    
1457
        # DTMF settings
1458
        tmppr = bool(_mem.dtmf_settings.side_tone > 0)
1459
        rs = RadioSetting(
1460
                "dtmf_side_tone",
1461
                "DTMF Sidetone",
1462
                RadioSettingValueBoolean(tmppr))
1463
        dtmf.append(rs)
1464

    
1465
        tmpval = str(_mem.dtmf_settings.separate_code)
1466
        if tmpval not in DTMF_CODE_CHARS:
1467
            tmpval = '*'
1468
        val = RadioSettingValueString(1, 1, tmpval)
1469
        val.set_charset(DTMF_CODE_CHARS)
1470
        rs = RadioSetting("dtmf_separate_code", "Separate Code", val)
1471
        dtmf.append(rs)
1472

    
1473
        tmpval = str(_mem.dtmf_settings.group_call_code)
1474
        if tmpval not in DTMF_CODE_CHARS:
1475
            tmpval = '#'
1476
        val = RadioSettingValueString(1, 1, tmpval)
1477
        val.set_charset(DTMF_CODE_CHARS)
1478
        rs = RadioSetting("dtmf_group_call_code", "Group Call Code", val)
1479
        dtmf.append(rs)
1480

    
1481
        tmpval = _mem.dtmf_settings.decode_response
1482
        if tmpval >= len(DTMF_DECODE_RESPONSE_LIST):
1483
            tmpval = 0
1484
        rs = RadioSetting("dtmf_decode_response", "Decode Response",
1485
                          RadioSettingValueList(
1486
                              DTMF_DECODE_RESPONSE_LIST,
1487
                              DTMF_DECODE_RESPONSE_LIST[tmpval]))
1488
        dtmf.append(rs)
1489

    
1490
        tmpval = _mem.dtmf_settings.auto_reset_time
1491
        if tmpval > 60 or tmpval < 5:
1492
            tmpval = 5
1493
        rs = RadioSetting("dtmf_auto_reset_time",
1494
                          "Auto reset time (s)",
1495
                          RadioSettingValueInteger(5, 60, tmpval))
1496
        dtmf.append(rs)
1497

    
1498
        tmpval = int(_mem.dtmf_settings.preload_time)
1499
        if tmpval > 100 or tmpval < 3:
1500
            tmpval = 30
1501
        tmpval *= 10
1502
        rs = RadioSetting("dtmf_preload_time",
1503
                          "Pre-load time (ms)",
1504
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1505
        dtmf.append(rs)
1506

    
1507
        tmpval = int(_mem.dtmf_settings.first_code_persist_time)
1508
        if tmpval > 100 or tmpval < 3:
1509
            tmpval = 30
1510
        tmpval *= 10
1511
        rs = RadioSetting("dtmf_first_code_persist_time",
1512
                          "First code persist time (ms)",
1513
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1514
        dtmf.append(rs)
1515

    
1516
        tmpval = int(_mem.dtmf_settings.hash_persist_time)
1517
        if tmpval > 100 or tmpval < 3:
1518
            tmpval = 30
1519
        tmpval *= 10
1520
        rs = RadioSetting("dtmf_hash_persist_time",
1521
                          "#/* persist time (ms)",
1522
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1523
        dtmf.append(rs)
1524

    
1525
        tmpval = int(_mem.dtmf_settings.code_persist_time)
1526
        if tmpval > 100 or tmpval < 3:
1527
            tmpval = 30
1528
        tmpval *= 10
1529
        rs = RadioSetting("dtmf_code_persist_time",
1530
                          "Code persist time (ms)",
1531
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1532
        dtmf.append(rs)
1533

    
1534
        tmpval = int(_mem.dtmf_settings.code_interval_time)
1535
        if tmpval > 100 or tmpval < 3:
1536
            tmpval = 30
1537
        tmpval *= 10
1538
        rs = RadioSetting("dtmf_code_interval_time",
1539
                          "Code interval time (ms)",
1540
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1541
        dtmf.append(rs)
1542

    
1543
        tmpval = bool(_mem.dtmf_settings.permit_remote_kill > 0)
1544
        rs = RadioSetting(
1545
                "dtmf_permit_remote_kill",
1546
                "Permit remote kill",
1547
                RadioSettingValueBoolean(tmpval))
1548
        dtmf.append(rs)
1549

    
1550
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_local_code).upper().strip(
1551
                "\x00\xff\x20")
1552
        for i in tmpval:
1553
            if i in DTMF_CHARS_ID:
1554
                continue
1555
            else:
1556
                tmpval = "103"
1557
                break
1558
        val = RadioSettingValueString(3, 3, tmpval)
1559
        val.set_charset(DTMF_CHARS_ID)
1560
        rs = RadioSetting("dtmf_dtmf_local_code",
1561
                          "Local code (3 chars 0-9 ABCD)", val)
1562
        dtmf.append(rs)
1563

    
1564
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_up_code).upper().strip(
1565
                "\x00\xff\x20")
1566
        for i in tmpval:
1567
            if i in DTMF_CHARS_UPDOWN or i == "":
1568
                continue
1569
            else:
1570
                tmpval = "123"
1571
                break
1572
        val = RadioSettingValueString(1, 16, tmpval)
1573
        val.set_charset(DTMF_CHARS_UPDOWN)
1574
        rs = RadioSetting("dtmf_dtmf_up_code",
1575
                          "Up code (1-16 chars 0-9 ABCD*#)", val)
1576
        dtmf.append(rs)
1577

    
1578
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_down_code).upper().strip(
1579
                "\x00\xff\x20")
1580
        for i in tmpval:
1581
            if i in DTMF_CHARS_UPDOWN:
1582
                continue
1583
            else:
1584
                tmpval = "456"
1585
                break
1586
        val = RadioSettingValueString(1, 16, tmpval)
1587
        val.set_charset(DTMF_CHARS_UPDOWN)
1588
        rs = RadioSetting("dtmf_dtmf_down_code",
1589
                          "Down code (1-16 chars 0-9 ABCD*#)", val)
1590
        dtmf.append(rs)
1591

    
1592
        tmpval = str(_mem.dtmf_settings_numbers.kill_code).upper().strip(
1593
                "\x00\xff\x20")
1594
        for i in tmpval:
1595
            if i in DTMF_CHARS_KILL:
1596
                continue
1597
            else:
1598
                tmpval = "77777"
1599
                break
1600
        if not len(tmpval) == 5:
1601
            tmpval = "77777"
1602
        val = RadioSettingValueString(5, 5, tmpval)
1603
        val.set_charset(DTMF_CHARS_KILL)
1604
        rs = RadioSetting("dtmf_kill_code",
1605
                          "Kill code (5 chars 0-9 ABCD)", val)
1606
        dtmf.append(rs)
1607

    
1608
        tmpval = str(_mem.dtmf_settings_numbers.revive_code).upper().strip(
1609
                "\x00\xff\x20")
1610
        for i in tmpval:
1611
            if i in DTMF_CHARS_KILL:
1612
                continue
1613
            else:
1614
                tmpval = "88888"
1615
                break
1616
        if not len(tmpval) == 5:
1617
            tmpval = "88888"
1618
        val = RadioSettingValueString(5, 5, tmpval)
1619
        val.set_charset(DTMF_CHARS_KILL)
1620
        rs = RadioSetting("dtmf_revive_code",
1621
                          "Revive code (5 chars 0-9 ABCD)", val)
1622
        dtmf.append(rs)
1623

    
1624
        val = RadioSettingValueString(0, 80,
1625
                                      "All DTMF Contacts are 3 codes "
1626
                                      "(valid: 0-9 * # ABCD), "
1627
                                      "or an empty string")
1628
        val.set_mutable(False)
1629
        rs = RadioSetting("dtmf_descr1", "DTMF Contacts", val)
1630
        dtmfc.append(rs)
1631

    
1632
        for i in range(1, 17):
1633
            varname = "DTMF_"+str(i)
1634
            varnumname = "DTMFNUM_"+str(i)
1635
            vardescr = "DTMF Contact "+str(i)+" name"
1636
            varinumdescr = "DTMF Contact "+str(i)+" number"
1637

    
1638
            cntn = str(_mem.dtmfcontact[i-1].name).strip("\x20\x00\xff")
1639
            cntnum = str(_mem.dtmfcontact[i-1].number).strip("\x20\x00\xff")
1640

    
1641
            val = RadioSettingValueString(0, 8, cntn)
1642
            rs = RadioSetting(varname, vardescr, val)
1643
            dtmfc.append(rs)
1644

    
1645
            val = RadioSettingValueString(0, 3, cntnum)
1646
            val.set_charset(DTMF_CHARS)
1647
            rs = RadioSetting(varnumname, varinumdescr, val)
1648
            dtmfc.append(rs)
1649

    
1650
        # scanlists
1651
        if _mem.scanlist_default == 1:
1652
            tmpsc = 2
1653
        else:
1654
            tmpsc = 1
1655
        rs = RadioSetting("scanlist_default",
1656
                          "Default scanlist",
1657
                          RadioSettingValueInteger(1, 2, tmpsc))
1658
        scanl.append(rs)
1659

    
1660
        tmppr = bool((_mem.scanlist1_priority_scan & 1) > 0)
1661
        rs = RadioSetting(
1662
                "scanlist1_priority_scan",
1663
                "Scanlist 1 priority channel scan",
1664
                RadioSettingValueBoolean(tmppr))
1665
        scanl.append(rs)
1666

    
1667
        tmpch = _mem.scanlist1_priority_ch1 + 1
1668
        if tmpch > 200:
1669
            tmpch = 0
1670
        rs = RadioSetting("scanlist1_priority_ch1",
1671
                          "Scanlist 1 priority channel 1 (0 - off)",
1672
                          RadioSettingValueInteger(0, 200, tmpch))
1673
        scanl.append(rs)
1674

    
1675
        tmpch = _mem.scanlist1_priority_ch2 + 1
1676
        if tmpch > 200:
1677
            tmpch = 0
1678
        rs = RadioSetting("scanlist1_priority_ch2",
1679
                          "Scanlist 1 priority channel 2 (0 - off)",
1680
                          RadioSettingValueInteger(0, 200, tmpch))
1681
        scanl.append(rs)
1682

    
1683
        tmppr = bool((_mem.scanlist2_priority_scan & 1) > 0)
1684
        rs = RadioSetting(
1685
                "scanlist2_priority_scan",
1686
                "Scanlist 2 priority channel scan",
1687
                RadioSettingValueBoolean(tmppr))
1688
        scanl.append(rs)
1689

    
1690
        tmpch = _mem.scanlist2_priority_ch1 + 1
1691
        if tmpch > 200:
1692
            tmpch = 0
1693
        rs = RadioSetting("scanlist2_priority_ch1",
1694
                          "Scanlist 2 priority channel 1 (0 - off)",
1695
                          RadioSettingValueInteger(0, 200, tmpch))
1696
        scanl.append(rs)
1697

    
1698
        tmpch = _mem.scanlist2_priority_ch2 + 1
1699
        if tmpch > 200:
1700
            tmpch = 0
1701
        rs = RadioSetting("scanlist2_priority_ch2",
1702
                          "Scanlist 2 priority channel 2 (0 - off)",
1703
                          RadioSettingValueInteger(0, 200, tmpch))
1704
        scanl.append(rs)
1705

    
1706
        # basic settings
1707

    
1708
        # squelch
1709
        tmpsq = _mem.squelch
1710
        if tmpsq > 9:
1711
            tmpsq = 1
1712
        rs = RadioSetting("squelch", "Squelch",
1713
                          RadioSettingValueInteger(0, 9, tmpsq))
1714
        basic.append(rs)
1715

    
1716
        # TOT
1717
        tmptot = _mem.max_talk_time
1718
        if tmptot >= len(TALKTIME_LIST):
1719
            tmptot = TALKTIME_LIST.index("2min")
1720
        rs = RadioSetting(
1721
                "max_talk_time",
1722
                "Max talk time",
1723
                RadioSettingValueList(
1724
                    TALKTIME_LIST,
1725
                    TALKTIME_LIST[tmptot]))
1726
        basic.append(rs)
1727

    
1728
        # Channel display mode
1729
        tmpchdispmode = _mem.channel_display_mode
1730
        if tmpchdispmode >= len(CHANNELDISP_LIST):
1731
            tmpchdispmode = 0
1732
        rs = RadioSetting(
1733
                "channel_display_mode",
1734
                "Channel display mode",
1735
                RadioSettingValueList(
1736
                    CHANNELDISP_LIST,
1737
                    CHANNELDISP_LIST[tmpchdispmode]))
1738
        basic.append(rs)
1739

    
1740
        # Backlight auto mode
1741
        tmpback = _mem.backlight_auto_mode
1742
        if tmpback >= len(BACKLIGHT_LIST):
1743
            tmpback = 0
1744
        rs = RadioSetting("backlight_auto_mode",
1745
                          "Backlight time",
1746
                          RadioSettingValueList(
1747
                              BACKLIGHT_LIST,
1748
                              BACKLIGHT_LIST[tmpback]))
1749
        basic.append(rs)
1750

    
1751
        # BLMode
1752
        rs = RadioSetting(
1753
                "bl_mode",
1754
                "BLmode (TX/RX)", RadioSettingValueBoolean(bool((_mem.bl_mode & 0x20) > 0)))
1755
        basic.append(rs)
1756

    
1757
        # Beep control
1758
        rs = RadioSetting(
1759
                "beep_control",
1760
                "Beep control",
1761
                RadioSettingValueBoolean(bool(_mem.beep_control > 0)))
1762
        basic.append(rs)
1763

    
1764
        # Scan resume mode
1765
        tmpscanres = _mem.scan_resume_mode
1766
        if tmpscanres >= len(SCANRESUME_LIST):
1767
            tmpscanres = 0
1768
        rs = RadioSetting(
1769
                "scan_resume_mode",
1770
                "Scan resume mode (Sc REV)",
1771
                RadioSettingValueList(
1772
                    SCANRESUME_LIST,
1773
                    SCANRESUME_LIST[tmpscanres]))
1774
        basic.append(rs)
1775

    
1776
        # Keypad locked
1777
        rs = RadioSetting(
1778
                "key_lock",
1779
                "Keypad lock",
1780
                RadioSettingValueBoolean(bool(_mem.key_lock > 0)))
1781
        basic.append(rs)
1782

    
1783
        # Auto keypad lock
1784
        rs = RadioSetting(
1785
                "auto_keypad_lock",
1786
                "Auto keypad lock",
1787
                RadioSettingValueBoolean(bool(_mem.auto_keypad_lock > 0)))
1788
        basic.append(rs)
1789

    
1790
        # Tail tone elimination
1791
        rs = RadioSetting(
1792
                "tail_note_elimination",
1793
                "Tail tone elimination",
1794
                RadioSettingValueBoolean(
1795
                    bool(_mem.tail_note_elimination > 0)))
1796
        basic.append(rs)
1797

    
1798
        # Repeater tail tone elimination
1799
        tmprte = _mem.repeater_tail_elimination
1800
        if tmprte >= len(RTE_LIST):
1801
            tmprte = 0
1802
        rs = RadioSetting(
1803
                "repeater_tail_elimination",
1804
                "Repeater tail tone elimination",
1805
                RadioSettingValueList(RTE_LIST, RTE_LIST[tmprte]))
1806
        basic.append(rs)
1807

    
1808
        # Mic gain
1809
        tmpmicgain = _mem.mic_gain
1810
        if tmpmicgain >= len(MICGAIN_LIST):
1811
            tmpmicgain = MICGAIN_LIST.index("+12.0dB")
1812
        rs = RadioSetting(
1813
                "mic_gain",
1814
                "Mic Gain",
1815
                RadioSettingValueList(
1816
                    MICGAIN_LIST,
1817
                    MICGAIN_LIST[tmpmicgain]))
1818
        basic.append(rs)
1819

    
1820
        # MicBar
1821
        rs = RadioSetting(
1822
                "micbar",
1823
                "MicBar", RadioSettingValueBoolean(bool((_mem.micbar & 0x10) > 0)))
1824
        basic.append(rs)
1825

    
1826
        # Compander
1827
        tmpcompander = _mem.compander >> 4
1828
        if tmpcompander >= len(COMPANDER_LIST):
1829
            tmpcompander = COMPANDER_LIST.index("Off")
1830
        rs = RadioSetting(
1831
                "compander",
1832
                "Compander",
1833
                RadioSettingValueList(
1834
                    COMPANDER_LIST,
1835
                    COMPANDER_LIST[tmpcompander]))
1836
        basic.append(rs)
1837

    
1838
        # VOX switch
1839
        rs = RadioSetting(
1840
                "vox_switch",
1841
                "VOX enabled", RadioSettingValueBoolean(
1842
                    bool(_mem.vox_switch > 0)))
1843
        basic.append(rs)
1844

    
1845
        # VOX Level
1846
        tmpvox = _mem.vox_level+1
1847
        if tmpvox > 10:
1848
            tmpvox = 10
1849
        rs = RadioSetting("vox_level", "VOX Level",
1850
                          RadioSettingValueInteger(1, 10, tmpvox))
1851
        basic.append(rs)
1852

    
1853
        # call channel
1854
        tmpc = _mem.call_channel+1
1855
        if tmpc > 200:
1856
            tmpc = 1
1857
        rs = RadioSetting("call_channel", "One key call channel",
1858
                          RadioSettingValueInteger(1, 200, tmpc))
1859
        basic.append(rs)
1860

    
1861
        # Reminding of end of talk
1862
        tmpalarmmode = _mem.reminding_of_end_talk
1863
        if tmpalarmmode >= len(REMENDOFTALK_LIST):
1864
            tmpalarmmode = 0
1865
        rs = RadioSetting(
1866
                "reminding_of_end_talk",
1867
                "Reminding of end of talk (DigSRV)",
1868
                RadioSettingValueList(
1869
                    REMENDOFTALK_LIST,
1870
                    REMENDOFTALK_LIST[tmpalarmmode]))
1871
        basic.append(rs)
1872

    
1873
        # Beacon
1874
        tmpbeacon = _mem.beacon
1875
        if tmpbeacon >= len(BEACON_LIST):
1876
            tmpbeacon = BEACON_LIST.index("Off")
1877
        rs = RadioSetting(
1878
                "beacon",
1879
                "Beacon",
1880
                RadioSettingValueList(
1881
                    BEACON_LIST,
1882
                    BEACON_LIST[tmpbeacon]))
1883
        basic.append(rs)
1884

    
1885
        # Battery save
1886
        tmpbatsave = _mem.battery_save
1887
        if tmpbatsave >= len(BATSAVE_LIST):
1888
            tmpbatsave = BATSAVE_LIST.index("80%")
1889
        rs = RadioSetting(
1890
                "battery_save",
1891
                "Battery Save",
1892
                RadioSettingValueList(
1893
                    BATSAVE_LIST,
1894
                    BATSAVE_LIST[tmpbatsave]))
1895
        basic.append(rs)
1896

    
1897
        # RSSI / S Meter
1898
        rs = RadioSetting(
1899
                "signal_meter",
1900
                "SMeter (instead of RSSI)", RadioSettingValueBoolean(bool((_mem.signal_meter & 0x04) > 0)))
1901
        basic.append(rs)
1902

    
1903
        # Crossband receiving/transmitting
1904
        tmpcross = _mem.crossband
1905
        if tmpcross >= len(CROSSBAND_LIST):
1906
            tmpcross = 0
1907
        rs = RadioSetting(
1908
                "crossband",
1909
                "Cross-band receiving/transmitting (Tx VFO)",
1910
                RadioSettingValueList(
1911
                    CROSSBAND_LIST,
1912
                    CROSSBAND_LIST[tmpcross]))
1913
        basic.append(rs)
1914

    
1915
        # Dual watch
1916
        tmpdual = _mem.dual_watch
1917
        if tmpdual >= len(DUALWATCH_LIST):
1918
            tmpdual = 0
1919
        rs = RadioSetting("dualwatch", "Dual Watch (DualRX)", RadioSettingValueList(
1920
            DUALWATCH_LIST, DUALWATCH_LIST[tmpdual]))
1921
        basic.append(rs)
1922

    
1923
        # Power on display mode
1924
        tmpdispmode = _mem.power_on_dispmode
1925
        if tmpdispmode >= len(WELCOME_LIST):
1926
            tmpdispmode = 0
1927
        rs = RadioSetting(
1928
                "welcome_mode",
1929
                "Power on display mode",
1930
                RadioSettingValueList(
1931
                    WELCOME_LIST,
1932
                    WELCOME_LIST[tmpdispmode]))
1933
        basic.append(rs)
1934

    
1935
        # QRZ label
1936
        qrz_label = str(_mem.qrz_label).rstrip("\x20\x00\xff") + "\x00"
1937
        qrz_label = _getstring(qrz_label.encode('ascii', errors='ignore'), 0, 8)
1938
        rs = RadioSetting("qrz_label", _("QRA (8 characters)"),
1939
                          RadioSettingValueString(0, 8, qrz_label))
1940
        basic.append(rs)
1941

    
1942
        # Logo string 1
1943
        logo1 = str(_mem.logo_line1).strip("\x20\x00\xff") + "\x00"
1944
        logo1 = _getstring(logo1.encode('ascii', errors='ignore'), 0, 12)
1945
        rs = RadioSetting("logo1", _("Logo string 1 (12 characters)"),
1946
                          RadioSettingValueString(0, 12, logo1))
1947
        basic.append(rs)
1948

    
1949
        # Logo string 2
1950
        logo2 = str(_mem.logo_line2).strip("\x20\x00\xff") + "\x00"
1951
        logo2 = _getstring(logo2.encode('ascii', errors='ignore'), 0, 12)
1952
        rs = RadioSetting("logo2", _("Logo string 2 (12 characters)"),
1953
                          RadioSettingValueString(0, 12, logo2))
1954
        basic.append(rs)
1955

    
1956
        # NOAA autoscan
1957
#        rs = RadioSetting(
1958
#                "noaa_autoscan",
1959
#                "NOAA Autoscan (not implemented)", RadioSettingValueBoolean(
1960
#                    bool(_mem.noaa_autoscan > 0)))
1961
#        basic.append(rs)
1962

    
1963

    
1964
        # VFO open
1965
#        rs = RadioSetting("vfo_open", "VFO open (???)",
1966
#                          RadioSettingValueBoolean(bool(_mem.vfo_open > 0)))
1967
#        basic.append(rs)
1968

    
1969
        # Keypad Tone
1970
#        tmpkeypadtone = _mem.keypad_tone
1971
#        if tmpkeypadtone >= len(KEYPADTONE_LIST):
1972
#            tmpkeypadtone = 0
1973
#        rs = RadioSetting("keypad_tone", "Voice prompts (not implemented)", RadioSettingValueList(
1974
#            KEYPADTONE_LIST, KEYPADTONE_LIST[tmpkeypadtone]))
1975
#        basic.append(rs)
1976

    
1977
        # Language
1978
#        tmplanguage = _mem.language
1979
#        if tmplanguage >= len(LANGUAGE_LIST):
1980
#            tmplanguage = 0
1981
#        rs = RadioSetting("language", "Language (not implemented)", RadioSettingValueList(
1982
#            LANGUAGE_LIST, LANGUAGE_LIST[tmplanguage]))
1983
#        basic.append(rs)
1984

    
1985
        # Alarm mode
1986
#        tmpalarmmode = _mem.alarm_mode
1987
#        if tmpalarmmode >= len(ALARMMODE_LIST):
1988
#            tmpalarmmode = 0
1989
#        rs = RadioSetting("alarm_mode", "Alarm mode (not implemented)", RadioSettingValueList(
1990
#            ALARMMODE_LIST, ALARMMODE_LIST[tmpalarmmode]))
1991
#        basic.append(rs)
1992

    
1993
        # FM radio
1994
        for i in range(1, 21):
1995
            freqname = "FM_"+str(i)
1996
            fmfreq = _mem.fmfreq[i-1]/10.0
1997
            if fmfreq < FMMIN or fmfreq > FMMAX:
1998
                rs = RadioSetting(freqname, freqname,
1999
                                  RadioSettingValueString(0, 5, ""))
2000
            else:
2001
                rs = RadioSetting(freqname, freqname,
2002
                                  RadioSettingValueString(0, 5, str(fmfreq)))
2003

    
2004
            fmradio.append(rs)
2005

    
2006
        # unlock settings
2007

    
2008
        # F-LOCK
2009
        tmpflock = _mem.lock.flock
2010
        if tmpflock >= len(FLOCK_LIST):
2011
            tmpflock = 0
2012
        rs = RadioSetting(
2013
            "flock", "F-LOCK",
2014
            RadioSettingValueList(FLOCK_LIST, FLOCK_LIST[tmpflock]))
2015
        unlock.append(rs)
2016

    
2017
        # 350TX
2018
#        rs = RadioSetting("tx350", "350TX - unlock 350-400 MHz TX",
2019
#                          RadioSettingValueBoolean(
2020
#                              bool(_mem.lock.tx350 > 0)))
2021
#        unlock.append(rs)
2022

    
2023
        # Killed
2024
        rs = RadioSetting("Killed", "KILLED Device was disabled (via DTMF)",
2025
                          RadioSettingValueBoolean(
2026
                              bool(_mem.lock.killed > 0)))
2027
        unlock.append(rs)
2028

    
2029
        # 200TX
2030
#        rs = RadioSetting("tx200", "200TX - unlock 174-350 MHz TX",
2031
#                          RadioSettingValueBoolean(
2032
#                              bool(_mem.lock.tx200 > 0)))
2033
#        unlock.append(rs)
2034

    
2035
        # 500TX
2036
#        rs = RadioSetting("tx500", "500TX - unlock 500-600 MHz TX",
2037
#                          RadioSettingValueBoolean(
2038
#                              bool(_mem.lock.tx500 > 0)))
2039
#        unlock.append(rs)
2040

    
2041
        # 350EN
2042
#        rs = RadioSetting("en350", "350EN - unlock 350-400 MHz RX",
2043
#                          RadioSettingValueBoolean(
2044
#                              bool(_mem.lock.en350 > 0)))
2045
#        unlock.append(rs)
2046

    
2047
        # SCREEN
2048
#        rs = RadioSetting("scrambler", "SCREN - scrambler enable",
2049
#                          RadioSettingValueBoolean(
2050
#                              bool(_mem.lock.enscramble > 0)))
2051
#        unlock.append(rs)
2052

    
2053
        # readonly info
2054
        # Firmware
2055
        if self.FIRMWARE_VERSION == "":
2056
            firmware = "To get the firmware version please download"
2057
            "the image from the radio first"
2058
        else:
2059
            firmware = self.FIRMWARE_VERSION
2060

    
2061
        val = RadioSettingValueString(0, 128, firmware)
2062
        val.set_mutable(False)
2063
        rs = RadioSetting("fw_ver", "Firmware Version", val)
2064
        roinfo.append(rs)
2065

    
2066
        # No limits version for hacked firmware
2067
        val = RadioSettingValueBoolean(self._expanded_limits)
2068
        rs = RadioSetting("nolimits", "Limits disabled for modified firmware",
2069
                          val)
2070
        rs.set_warning(_(
2071
            'This should only be enabled if you are using modified firmware '
2072
            'that supports wider frequency coverage. Enabling this will cause '
2073
            'CHIRP not to enforce OEM restrictions and may lead to undefined '
2074
            'or unregulated behavior. Use at your own risk!'),
2075
            safe_value=False)
2076
        roinfo.append(rs)
2077

    
2078
        return top
2079

    
2080
    # Store details about a high-level memory to the memory map
2081
    # This is called when a user edits a memory in the UI
2082
    def set_memory(self, mem):
2083
        number = mem.number-1
2084

    
2085
        # Get a low-level memory object mapped to the image
2086
        _mem = self._memobj.channel[number]
2087
        _mem4 = self._memobj
2088
        # empty memory
2089
        if mem.empty:
2090
            _mem.set_raw("\xFF" * 16)
2091
            if number < 200:
2092
                _mem2 = self._memobj.channelname[number]
2093
                _mem2.set_raw("\xFF" * 16)
2094
                _mem4.channel_attributes[number].is_scanlist1 = 0
2095
                _mem4.channel_attributes[number].is_scanlist2 = 0
2096
                _mem4.channel_attributes[number].unknown1 = 0
2097
                _mem4.channel_attributes[number].unknown2 = 0
2098
                _mem4.channel_attributes[number].is_free = 1
2099
                _mem4.channel_attributes[number].band = 0x7
2100
            return mem
2101

    
2102
        # clean the channel memory, restore some bits if it was used before
2103
        if _mem.get_raw(asbytes=False)[0] == "\xff":
2104
            # this was an empty memory
2105
            _mem.set_raw("\x00" * 16)
2106
        else:
2107
            # this memory wasn't empty, save some bits that we don't know the
2108
            # meaning of, or that we don't support yet
2109
            prev_0a = _mem.get_raw()[0x0a] & SAVE_MASK_0A
2110
            prev_0b = _mem.get_raw()[0x0b] & SAVE_MASK_0B
2111
            prev_0c = _mem.get_raw()[0x0c] & SAVE_MASK_0C
2112
            prev_0d = _mem.get_raw()[0x0d] & SAVE_MASK_0D
2113
            prev_0e = _mem.get_raw()[0x0e] & SAVE_MASK_0E
2114
            prev_0f = _mem.get_raw()[0x0f] & SAVE_MASK_0F
2115
            _mem.set_raw("\x00" * 10 +
2116
                         chr(prev_0a) + chr(prev_0b) + chr(prev_0c) +
2117
                         chr(prev_0d) + chr(prev_0e) + chr(prev_0f))
2118

    
2119
        if number < 200:
2120
            _mem4.channel_attributes[number].is_scanlist1 = 0
2121
            _mem4.channel_attributes[number].is_scanlist2 = 0
2122
            _mem4.channel_attributes[number].unknown1 = 0
2123
            _mem4.channel_attributes[number].unknown2 = 0
2124
            _mem4.channel_attributes[number].is_free = 1
2125
            _mem4.channel_attributes[number].band = 0x7
2126

    
2127
        # find band
2128
        band = _find_band(self, mem.freq)
2129

    
2130
        # mode
2131
        if mem.mode == "FM":
2132
            _mem.enable_am = 0
2133
            _mem.enable_extramodes = 0
2134
        elif mem.mode == "AM":
2135
            _mem.enable_am = 1
2136
            _mem.enable_extramodes = 0
2137
        elif mem.mode == "USB":
2138
            _mem.enable_am = 0
2139
            _mem.enable_extramodes = 1
2140
        elif mem.mode == "CW":
2141
            _mem.enable_am = 1
2142
            _mem.enable_extramodes = 1
2143

    
2144
        # frequency/offset
2145
        _mem.freq = mem.freq/10
2146
        _mem.offset = mem.offset/10
2147

    
2148
        if mem.duplex == "":
2149
            _mem.offset = 0
2150
            _mem.shift = 0
2151
        elif mem.duplex == '-':
2152
            _mem.shift = FLAGS1_OFFSET_MINUS
2153
        elif mem.duplex == '+':
2154
            _mem.shift = FLAGS1_OFFSET_PLUS
2155
        elif mem.duplex == 'off':
2156
            # we fake tx disable by setting the tx freq to 0 MHz
2157
            _mem.shift = FLAGS1_OFFSET_MINUS
2158
            _mem.offset = _mem.freq
2159

    
2160
        # set band
2161
        if number < 200:
2162
            _mem4.channel_attributes[number].is_free = 0
2163
            _mem4.channel_attributes[number].band = band
2164

    
2165
        # channels >200 are the 14 VFO chanells and don't have names
2166
        if number < 200:
2167
            _mem2 = self._memobj.channelname[number]
2168
            tag = mem.name.ljust(10) + "\x00"*6
2169
            _mem2.name = tag  # Store the alpha tag
2170

    
2171
        # tone data
2172
        self._set_tone(mem, _mem)
2173

    
2174
        # step
2175
        _mem.step = STEPS.index(mem.tuning_step)
2176

    
2177
        # tx power
2178
        if str(mem.power) == str(UVK5_POWER_LEVELS[2]):
2179
            _mem.txpower = POWER_HIGH
2180
        elif str(mem.power) == str(UVK5_POWER_LEVELS[1]):
2181
            _mem.txpower = POWER_MEDIUM
2182
        else:
2183
            _mem.txpower = POWER_LOW
2184

    
2185
        for setting in mem.extra:
2186
            sname = setting.get_name()
2187
            svalue = setting.value.get_value()
2188

    
2189
            if sname == "bandwidth":
2190
                _mem.bandwidth = BANDWIDTH_LIST.index(svalue)
2191

    
2192
            if sname == "bclo":
2193
                _mem.bclo = svalue and 1 or 0
2194

    
2195
            if sname == "pttid":
2196
                _mem.dtmf_pttid = PTTID_LIST.index(svalue)
2197

    
2198
            if sname == "frev":
2199
                _mem.freq_reverse = svalue and 1 or 0
2200

    
2201
            if sname == "dtmfdecode":
2202
                _mem.dtmf_decode = svalue and 1 or 0
2203

    
2204
            if sname == "scrambler":
2205
                _mem.scrambler = (
2206
                    _mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue)
2207

    
2208
            if number < 200 and sname == "scanlists":
2209
                if svalue == "1":
2210
                    _mem4.channel_attributes[number].is_scanlist1 = 1
2211
                    _mem4.channel_attributes[number].is_scanlist2 = 0
2212
                elif svalue == "2":
2213
                    _mem4.channel_attributes[number].is_scanlist1 = 0
2214
                    _mem4.channel_attributes[number].is_scanlist2 = 1
2215
                elif svalue == "1+2":
2216
                    _mem4.channel_attributes[number].is_scanlist1 = 1
2217
                    _mem4.channel_attributes[number].is_scanlist2 = 1
2218
                else:
2219
                    _mem4.channel_attributes[number].is_scanlist1 = 0
2220
                    _mem4.channel_attributes[number].is_scanlist2 = 0
2221

    
2222
        return mem
2223

    
2224

    
2225
@directory.register
2226
class RA79Radio(UVK5Radio):
2227
    """Retevis RA79"""
2228
    VENDOR = "Retevis"
2229
    MODEL = "RA79"
(2-2/2)