Project

General

Profile

Feature #11072 » uvk5_IJV v2.9R3.py

Supports Quansheng K5 - IU0IJV firmware v2.9R3 - Julian Lilov, 01/24/2024 04:03 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
  flags1_unknown5: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_WIDE_PLUS = 0b11
272
BANDWIDTH_NARROW = 0b01
273
BANDWIDTH_NARROW_MINUS = 0b10
274

    
275
# dtmf_flags
276
PTTID_LIST = ["off", "BOT", "EOT", "BOTH"]
277

    
278
# power
279
UVK5_POWER_LEVELS = [chirp_common.PowerLevel("Low",  watts=1.00),
280
                     chirp_common.PowerLevel("Med",  watts=2.50),
281
                     chirp_common.PowerLevel("High", watts=5.00)
282
                    ]
283

    
284
# scrambler
285
SCRAMBLER_LIST = ["Off", "2600Hz", "2700Hz", "2800Hz", "2900Hz", "3000Hz", "3100Hz", "3200Hz", "3300Hz", "3400Hz", "3500Hz"]
286

    
287
# channel display mode
288
CHANNELDISP_LIST = ["Frequency", "Channel No", "Channel Name", "Name_S Freq_L", "Name_L Freq_S"]
289

    
290
# Beacon
291
BEACON_LIST = ["Off","5 Sec","10 Sec","30 Sec","5 min","10 min","20 min"]
292

    
293
# battery save
294
BATSAVE_LIST = ["Off", "50%", "67%", "75%", "80%"]
295

    
296
# compander
297
COMPANDER_LIST = ["Off", "TX", "RX", "RX/TX"]
298

    
299
# mic gain
300
MICGAIN_LIST = ["+1.1dB","+4.0dB","+8.0dB","+12.0dB","+15.1dB"]
301

    
302

    
303
# Talk Time
304
TALKTIME_LIST = ["Off","30 Sec","1 min","3 min","5 min"]
305

    
306
# Backlight auto mode
307
BACKLIGHT_LIST = ["Off", "5 sec", "10 sec", "20 sec", "1 min", "3 min", "RX/TX", "ON", "NIGHT"]
308

    
309
# Crossband receiving/transmitting
310
CROSSBAND_LIST = ["Same VFO", "VFO A", "VFO B"]
311
DUALWATCH_LIST = ["Off", "On"]
312

    
313
# steps
314
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]
315
                   
316
# ctcss/dcs codes
317
TMODES = ["", "Tone", "DTCS", "DTCS"]
318
TONE_NONE = 0
319
TONE_CTCSS = 1
320
TONE_DCS = 2
321
TONE_RDCS = 3
322

    
323

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

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

    
348
FLOCK_LIST = ["Off", "FCC", "CE", "GB", "430", "438"]
349

    
350
SCANRESUME_LIST = ["TIME: Resume after 5 seconds",
351
                   "CARRIER: Resume after signal disappears",
352
                   "SEARCH: Stop scanning after receiving a signal",
353
                   "LOG"]
354

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

    
366
MEM_SIZE = 0x2000  # size of all memory
367
PROG_SIZE = 0x1d00  # size of the memory that we will write
368
MEM_BLOCK = 0x80  # largest block of memory that we can reliably write
369

    
370
# fm radio supported frequencies
371
FMMIN = 76.0
372
FMMAX = 108.0
373

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

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

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

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

    
421
SCANLIST_LIST = ["None", "1", "2", "1+2"]
422

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

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

    
435

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

    
446

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

    
461

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

    
467
    crc = calculate_crc16_xmodem(data)
468
    data2 = data + struct.pack("<H", crc)
469

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

    
481

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

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

    
499
    footer = serport.read(4)
500

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

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

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

    
518
    cmd2 = xorarr(cmd)
519

    
520
    LOG.debug("Received reply (unobfuscated) len=0x%4.4x:\n%s" %
521
              (len(cmd2), util.hexprint(cmd2)))
522

    
523
    return cmd2
524

    
525

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

    
534

    
535
def _sayhello(serport):
536
    hellopacket = b"\x14\x05\x04\x00\x6a\x39\x57\x64"
537

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

    
553

    
554
def _readmem(serport, offset, length):
555
    LOG.debug("Sending readmem offset=0x%4.4x len=0x%4.4x" % (offset, length))
556

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

    
567

    
568
def _writemem(serport, data, offset):
569
    LOG.debug("Sending writemem offset=0x%4.4x len=0x%4.4x" %
570
              (offset, len(data)))
571

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

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

    
581
    _send_command(serport, writemem)
582
    o = _receive_reply(serport)
583

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

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

    
596

    
597
def _resetradio(serport):
598
    resetpacket = b"\xdd\x05\x00\x00"
599
    _send_command(serport, resetpacket)
600

    
601

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

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

    
618
    addr = 0
619
    while addr < MEM_SIZE:
620
        o = _readmem(serport, addr, MEM_BLOCK)
621
        status.cur = addr
622
        radio.status_fn(status)
623

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

    
630
    return memmap.MemoryMapBytes(eeprom)
631

    
632

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

    
642
    f = _sayhello(serport)
643
    if f:
644
        radio.FIRMWARE_VERSION = f
645
    else:
646
        return False
647

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

    
660
    _resetradio(serport)
661

    
662
    return True
663

    
664

    
665
def _find_band(nolimits, hz):
666
    mhz = hz/1000000.0
667
    if nolimits:
668
        B = BANDS_NOLIMITS
669
    else:
670
        B = BANDS
671

    
672
    # currently the hacked firmware sets band=1 below 50 MHz
673
    if nolimits and mhz < 50.0:
674
        return 1
675

    
676
    for a in B:
677
        if mhz >= B[a][0] and mhz <= B[a][1]:
678
            return a
679
    return False
680

    
681

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

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

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

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

    
737
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
738
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
739
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
740

    
741
        rf.valid_characters = chirp_common.CHARSET_ASCII
742
        rf.valid_modes = ["FM", "NFM", "WFM", "AM", "NAM", "USB", "CW"]
743

    
744
        rf.valid_skips = [""]
745

    
746
        # This radio supports memories 1-200, 201-214 are the VFO memories
747
        rf.memory_bounds = (1, 200)
748

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

    
756
    # Do a download of the radio from the serial port
757
    def sync_in(self):
758
        self._mmap = do_download(self)
759
        self.process_mmap()
760

    
761
    # Do an upload of the radio to the serial port
762
    def sync_out(self):
763
        do_upload(self)
764

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

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

    
774
    def validate_memory(self, mem):
775
        msgs = super().validate_memory(mem)
776

    
777
        if mem.duplex == 'off':
778
            return msgs
779

    
780
        # find tx frequency
781
        if mem.duplex == '-':
782
            txfreq = mem.freq - mem.offset
783
        elif mem.duplex == '+':
784
            txfreq = mem.freq + mem.offset
785
        else:
786
            txfreq = mem.freq
787

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

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

    
801
        return msgs
802

    
803
    def _set_tone(self, mem, _mem):
804
        ((txmode, txtone, txpol),
805
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
806

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

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

    
827
        _mem.rxcodeflag = rxmoval
828
        _mem.txcodeflag = txmoval
829
        _mem.unknown1 = 0
830
        _mem.unknown2 = 0
831
        _mem.rxcode = rxtoval
832
        _mem.txcode = txtoval
833

    
834
    def _get_tone(self, mem, _mem):
835
        rxtype = _mem.rxcodeflag
836
        txtype = _mem.txcodeflag
837
        rx_tmode = TMODES[rxtype]
838
        tx_tmode = TMODES[txtype]
839

    
840
        rx_tone = tx_tone = None
841

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

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

    
868
        tx_pol = txtype == 0x03 and "R" or "N"
869
        rx_pol = rxtype == 0x03 and "R" or "N"
870

    
871
        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
872
                                       (rx_tmode, rx_tone, rx_pol))
873

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

    
878
        mem = chirp_common.Memory()
879

    
880
        if isinstance(number2, str):
881
            number = SPECIALS[number2]
882
            mem.extd_number = number2
883
        else:
884
            number = number2 - 1
885

    
886
        mem.number = number + 1
887

    
888
        _mem = self._memobj.channel[number]
889

    
890
        tmpcomment = ""
891

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

    
897
        tmpscn = SCANLIST_LIST[0]
898

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

    
913
        if is_empty:
914
            mem.empty = True
915
            # set some sane defaults:
916
            mem.power = UVK5_POWER_LEVELS[2]
917
            mem.extra = RadioSettingGroup("Extra", "extra")
918
            rs = RadioSetting(
919
                "bclo", "BCLO",
920
                RadioSettingValueBoolean(False))
921
            mem.extra.append(rs)
922
            rs = RadioSetting(
923
                "frev", "FreqRev",
924
                RadioSettingValueBoolean(False))
925
            mem.extra.append(rs)
926
            rs = RadioSetting(
927
                "pttid", "PTTID",
928
                RadioSettingValueList(PTTID_LIST, PTTID_LIST[0]))
929
            mem.extra.append(rs)
930
            rs = RadioSetting(
931
                "dtmfdecode", _("DTMF decode"),
932
                RadioSettingValueBoolean(False))
933
            mem.extra.append(rs)
934
            rs = RadioSetting(
935
                "scrambler", _("Scrambler"),
936
                RadioSettingValueList(SCRAMBLER_LIST, SCRAMBLER_LIST[0]))
937
            mem.extra.append(rs)
938

    
939
            rs = RadioSetting(
940
                "scanlists", _("Scanlists"),
941
                RadioSettingValueList(SCANLIST_LIST, SCANLIST_LIST[0]))
942
            mem.extra.append(rs)
943

    
944
            # actually the step and duplex are overwritten by chirp based on
945
            # bandplan. they are here to document sane defaults for IARU r1
946
            # mem.tuning_step = 25.0
947
            # mem.duplex = ""
948

    
949
            return mem
950

    
951
        if number > 199:
952
            mem.immutable = ["name", "scanlists"]
953
        else:
954
            _mem2 = self._memobj.channelname[number]
955
            for char in _mem2.name:
956
                if str(char) == "\xFF" or str(char) == "\x00":
957
                    break
958
                mem.name += str(char)
959
            mem.name = mem.name.rstrip()
960

    
961
        # Convert your low-level frequency to Hertz
962
        mem.freq = int(_mem.freq)*10
963
        mem.offset = int(_mem.offset)*10
964

    
965
        if (mem.offset == 0):
966
            mem.duplex = ''
967
        else:
968
            if _mem.shift == FLAGS1_OFFSET_MINUS:
969
                if _mem.freq == _mem.offset:
970
                    # fake tx disable by setting tx to 0 MHz
971
                    mem.duplex = 'off'
972
                    mem.offset = 0
973
                else:
974
                    mem.duplex = '-'
975
            elif _mem.shift == FLAGS1_OFFSET_PLUS:
976
                mem.duplex = '+'
977
            else:
978
                mem.duplex = ''
979

    
980
        # tone data
981
        self._get_tone(mem, _mem)
982

    
983
        # mode
984
        if _mem.enable_am > 0:
985
            if _mem.bandwidth == BANDWIDTH_NARROW:
986
                mem.mode = "NAM"
987
            else:
988
                mem.mode = "AM"
989
        else:
990
            if _mem.bandwidth == BANDWIDTH_NARROW:
991
                mem.mode = "NFM"
992
            else:
993
                if _mem.bandwidth == BANDWIDTH_NARROW_MINUS:
994
                    mem.mode = "NFM"
995
                else:
996
                    if (_mem.bandwidth == BANDWIDTH_WIDE):
997
                        mem.mode = "FM"
998
                    else:
999
                        mem.mode = "WFM"
1000

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

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

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

    
1022
        mem.extra = RadioSettingGroup("Extra", "extra")
1023

    
1024
        # BCLO
1025
        is_bclo = bool(_mem.bclo > 0)
1026
        rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(is_bclo))
1027
        mem.extra.append(rs)
1028
        tmpcomment += "BCLO:"+(is_bclo and "ON" or "off")+" "
1029

    
1030
        # Frequency reverse - whatever that means, don't see it in the manual
1031
        is_frev = bool(_mem.freq_reverse > 0)
1032
        rs = RadioSetting("frev", "FreqRev", RadioSettingValueBoolean(is_frev))
1033
        mem.extra.append(rs)
1034
        tmpcomment += "FreqReverse:"+(is_frev and "ON" or "off")+" "
1035

    
1036
        # PTTID
1037
        pttid = _mem.dtmf_pttid
1038
        rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
1039
            PTTID_LIST, PTTID_LIST[pttid]))
1040
        mem.extra.append(rs)
1041
        tmpcomment += "PTTid:"+PTTID_LIST[pttid]+" "
1042

    
1043
        # DTMF DECODE
1044
        is_dtmf = bool(_mem.dtmf_decode > 0)
1045
        rs = RadioSetting("dtmfdecode", _("DTMF decode"),
1046
                          RadioSettingValueBoolean(is_dtmf))
1047
        mem.extra.append(rs)
1048
        tmpcomment += "DTMFdecode:"+(is_dtmf and "ON" or "off")+" "
1049

    
1050
        # Scrambler
1051
        if _mem.scrambler & 0x0f < len(SCRAMBLER_LIST):
1052
            enc = _mem.scrambler & 0x0f
1053
        else:
1054
            enc = 0
1055

    
1056
        rs = RadioSetting("scrambler", _("Scrambler"), RadioSettingValueList(
1057
            SCRAMBLER_LIST, SCRAMBLER_LIST[enc]))
1058
        mem.extra.append(rs)
1059
        tmpcomment += "Scrambler:"+SCRAMBLER_LIST[enc]+" "
1060

    
1061
        rs = RadioSetting("scanlists", _("Scanlists"), RadioSettingValueList(
1062
            SCANLIST_LIST, tmpscn))
1063
        mem.extra.append(rs)
1064

    
1065
        return mem
1066

    
1067
    def set_settings(self, settings):
1068
        _mem = self._memobj
1069
        
1070
        s1 = False
1071
        s2 = False
1072

    
1073
        for element in settings:
1074
            if not isinstance(element, RadioSetting):
1075
                self.set_settings(element)
1076
                continue
1077

    
1078
            # basic settings
1079

    
1080
            # call channel
1081
            if element.get_name() == "call_channel":
1082
                _mem.call_channel = int(element.value)-1
1083

    
1084
            # squelch
1085
            if element.get_name() == "squelch":
1086
                _mem.squelch = int(element.value)
1087

    
1088
            # TOT
1089
            if element.get_name() == "max_talk_time":
1090
                _mem.max_talk_time = TALKTIME_LIST.index(str(element.value))
1091

    
1092
            # Beacon
1093
            if element.get_name() == "beacon":
1094
                _mem.beacon = BEACON_LIST.index(str(element.value))
1095

    
1096
            # NOAA autoscan
1097
#            if element.get_name() == "noaa_autoscan":
1098
#                _mem.noaa_autoscan = element.value and 1 or 0
1099

    
1100
            # VOX switch
1101
            if element.get_name() == "vox_switch":
1102
                _mem.vox_switch = element.value and 1 or 0
1103

    
1104
            # vox level
1105
            if element.get_name() == "vox_level":
1106
                _mem.vox_level = int(element.value)-1
1107

    
1108
            # mic gain
1109
            if element.get_name() == "mic_gain":
1110
                _mem.mic_gain = MICGAIN_LIST.index(str(element.value))
1111

    
1112
            # MicBar
1113
            if element.get_name() == "micbar":
1114
                tmp_micbar = element.value and 0x10 or 0x00
1115
                s1 = True
1116

    
1117
            if element.get_name() == "bl_mode":
1118
                tmp_bl_mode = element.value and 0x20 or 0x00
1119
                s2 = True
1120

    
1121
            if s1 and s2:
1122
                _mem.micbar = tmp_micbar | tmp_bl_mode
1123
                _mem.bl_mode = tmp_micbar | tmp_bl_mode
1124

    
1125
            # Signal Meter
1126
            if element.get_name() == "signal_meter":
1127
                _mem.signal_meter = (element.value and 0x04 or 0x00) | 0x02
1128

    
1129
            # Channel display mode
1130
            if element.get_name() == "channel_display_mode":
1131
                _mem.channel_display_mode = CHANNELDISP_LIST.index(
1132
                    str(element.value))
1133

    
1134
            # Crossband receiving/transmitting
1135
            if element.get_name() == "crossband":
1136
                _mem.crossband = CROSSBAND_LIST.index(str(element.value))
1137

    
1138
            # Battery Save
1139
            if element.get_name() == "battery_save":
1140
                _mem.battery_save = BATSAVE_LIST.index(str(element.value))
1141

    
1142
            # Compander
1143
            if element.get_name() == "compander":
1144
                _mem.compander = (COMPANDER_LIST.index(str(element.value)) << 4) | 0x03
1145

    
1146
            # Dual Watch
1147
            if element.get_name() == "dualwatch":
1148
                _mem.dual_watch = DUALWATCH_LIST.index(str(element.value))
1149

    
1150
            # Backlight auto mode
1151
            if element.get_name() == "backlight_auto_mode":
1152
                _mem.backlight_auto_mode = \
1153
                        BACKLIGHT_LIST.index(str(element.value))
1154

    
1155
            # Tail tone elimination
1156
            if element.get_name() == "tail_note_elimination":
1157
                _mem.tail_note_elimination = element.value and 1 or 0
1158

    
1159
            # VFO Open
1160
#            if element.get_name() == "vfo_open":
1161
#                _mem.vfo_open = element.value and 1 or 0
1162

    
1163
            # Beep control
1164
            if element.get_name() == "beep_control":
1165
                _mem.beep_control = element.value and 1 or 0
1166

    
1167
            # Scan resume mode
1168
            if element.get_name() == "scan_resume_mode":
1169
                _mem.scan_resume_mode = SCANRESUME_LIST.index(
1170
                    str(element.value))
1171

    
1172
            # Keypad lock
1173
            if element.get_name() == "key_lock":
1174
                _mem.key_lock = element.value and 1 or 0
1175

    
1176
            # Auto keypad lock
1177
            if element.get_name() == "auto_keypad_lock":
1178
                _mem.auto_keypad_lock = element.value and 1 or 0
1179

    
1180
            # Power on display mode
1181
            if element.get_name() == "welcome_mode":
1182
                _mem.power_on_dispmode = WELCOME_LIST.index(str(element.value))
1183

    
1184
            # Keypad Tone
1185
#            if element.get_name() == "keypad_tone":
1186
#                _mem.keypad_tone = KEYPADTONE_LIST.index(str(element.value))
1187

    
1188
            # Language
1189
#            if element.get_name() == "language":
1190
#                _mem.language = LANGUAGE_LIST.index(str(element.value))
1191

    
1192
            # Alarm mode
1193
#            if element.get_name() == "alarm_mode":
1194
#                _mem.alarm_mode = ALARMMODE_LIST.index(str(element.value))
1195

    
1196
            # Reminding of end of talk
1197
            if element.get_name() == "reminding_of_end_talk":
1198
                _mem.reminding_of_end_talk = REMENDOFTALK_LIST.index(
1199
                    str(element.value))
1200

    
1201
            # Repeater tail tone elimination
1202
            if element.get_name() == "repeater_tail_elimination":
1203
                _mem.repeater_tail_elimination = RTE_LIST.index(
1204
                    str(element.value))
1205

    
1206

    
1207

    
1208
            # Logo string 1
1209
            if element.get_name() == "logo1":
1210
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
1211
                _mem.logo_line1 = b[0:12]+"\x00\xff\xff\xff"
1212

    
1213
            # Logo string 2
1214
            if element.get_name() == "logo2":
1215
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
1216
                _mem.logo_line2 = b[0:12]+"\x00\xff\xff\xff"
1217

    
1218
            # QRZ label
1219
            if element.get_name() == "qrz_label":
1220
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*8
1221
                _mem.qrz_label = b[0:8]
1222

    
1223
            # unlock settings
1224

    
1225
            # FLOCK
1226
            if element.get_name() == "flock":
1227
                _mem.lock.flock = FLOCK_LIST.index(str(element.value))
1228

    
1229
            # 350TX
1230
#            if element.get_name() == "tx350":
1231
#                _mem.lock.tx350 = element.value and 1 or 0
1232

    
1233
            # 200TX
1234
#            if element.get_name() == "tx200":
1235
#                _mem.lock.tx200 = element.value and 1 or 0
1236

    
1237
            # 500TX
1238
#            if element.get_name() == "tx500":
1239
#                _mem.lock.tx500 = element.value and 1 or 0
1240

    
1241
            # 350EN
1242
#            if element.get_name() == "en350":
1243
#                _mem.lock.en350 = element.value and 1 or 0
1244

    
1245
            # SCREN
1246
#            if element.get_name() == "enscramble":
1247
#                _mem.lock.enscramble = element.value and 1 or 0
1248

    
1249
            # KILLED
1250
            if element.get_name() == "killed":
1251
                _mem.lock.killed = element.value and 1 or 0
1252

    
1253
            # fm radio
1254
            for i in range(1, 21):
1255
                freqname = "FM_" + str(i)
1256
                if element.get_name() == freqname:
1257
                    val = str(element.value).strip()
1258
                    try:
1259
                        val2 = int(float(val)*10)
1260
                    except Exception:
1261
                        val2 = 0xffff
1262

    
1263
                    if val2 < FMMIN*10 or val2 > FMMAX*10:
1264
                        val2 = 0xffff
1265
#                        raise errors.InvalidValueError(
1266
#                                "FM radio frequency should be a value "
1267
#                                "in the range %.1f - %.1f" % (FMMIN , FMMAX))
1268
                    _mem.fmfreq[i-1] = val2
1269

    
1270
            # dtmf settings
1271
            if element.get_name() == "dtmf_side_tone":
1272
                _mem.dtmf_settings.side_tone = \
1273
                        element.value and 1 or 0
1274

    
1275
            if element.get_name() == "dtmf_separate_code":
1276
                _mem.dtmf_settings.separate_code = str(element.value)
1277

    
1278
            if element.get_name() == "dtmf_group_call_code":
1279
                _mem.dtmf_settings.group_call_code = element.value
1280

    
1281
            if element.get_name() == "dtmf_decode_response":
1282
                _mem.dtmf_settings.decode_response = \
1283
                        DTMF_DECODE_RESPONSE_LIST.index(str(element.value))
1284

    
1285
            if element.get_name() == "dtmf_auto_reset_time":
1286
                _mem.dtmf_settings.auto_reset_time = \
1287
                        int(int(element.value)/10)
1288

    
1289
            if element.get_name() == "dtmf_preload_time":
1290
                _mem.dtmf_settings.preload_time = \
1291
                        int(int(element.value)/10)
1292

    
1293
            if element.get_name() == "dtmf_first_code_persist_time":
1294
                _mem.dtmf_settings.first_code_persist_time = \
1295
                        int(int(element.value)/10)
1296

    
1297
            if element.get_name() == "dtmf_hash_persist_time":
1298
                _mem.dtmf_settings.hash_persist_time = \
1299
                        int(int(element.value)/10)
1300

    
1301
            if element.get_name() == "dtmf_code_persist_time":
1302
                _mem.dtmf_settings.code_persist_time = \
1303
                        int(int(element.value)/10)
1304

    
1305
            if element.get_name() == "dtmf_code_interval_time":
1306
                _mem.dtmf_settings.code_interval_time = \
1307
                        int(int(element.value)/10)
1308

    
1309
            if element.get_name() == "dtmf_permit_remote_kill":
1310
                _mem.dtmf_settings.permit_remote_kill = \
1311
                        element.value and 1 or 0
1312

    
1313
            if element.get_name() == "dtmf_dtmf_local_code":
1314
                k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*3
1315
                _mem.dtmf_settings_numbers.dtmf_local_code = k[0:3]
1316

    
1317
            if element.get_name() == "dtmf_dtmf_up_code":
1318
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*16
1319
                _mem.dtmf_settings_numbers.dtmf_up_code = k[0:16]
1320

    
1321
            if element.get_name() == "dtmf_dtmf_down_code":
1322
                k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*16
1323
                _mem.dtmf_settings_numbers.dtmf_down_code = k[0:16]
1324

    
1325
            if element.get_name() == "dtmf_kill_code":
1326
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*5
1327
                _mem.dtmf_settings_numbers.kill_code = k[0:5]
1328

    
1329
            if element.get_name() == "dtmf_revive_code":
1330
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*5
1331
                _mem.dtmf_settings_numbers.revive_code = k[0:5]
1332

    
1333
            # dtmf contacts
1334
            for i in range(1, 17):
1335
                varname = "DTMF_" + str(i)
1336
                if element.get_name() == varname:
1337
                    k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*8
1338
                    _mem.dtmfcontact[i-1].name = k[0:8]
1339

    
1340
                varnumname = "DTMFNUM_" + str(i)
1341
                if element.get_name() == varnumname:
1342
                    k = str(element.value).rstrip("\x20\xff\x00") + "\xff"*3
1343
                    _mem.dtmfcontact[i-1].number = k[0:3]
1344

    
1345
            # scanlist stuff
1346
            if element.get_name() == "scanlist_default":
1347
                val = (int(element.value) == 2) and 1 or 0
1348
                _mem.scanlist_default = val
1349

    
1350
            if element.get_name() == "scanlist1_priority_scan":
1351
                _mem.scanlist1_priority_scan = \
1352
                        element.value and 1 or 0
1353

    
1354
            if element.get_name() == "scanlist2_priority_scan":
1355
                _mem.scanlist2_priority_scan = \
1356
                        element.value and 1 or 0
1357

    
1358
            if element.get_name() == "scanlist1_priority_ch1" or \
1359
                    element.get_name() == "scanlist1_priority_ch2" or \
1360
                    element.get_name() == "scanlist2_priority_ch1" or \
1361
                    element.get_name() == "scanlist2_priority_ch2":
1362

    
1363
                val = int(element.value)
1364

    
1365
                if val > 200 or val < 1:
1366
                    val = 0xff
1367
                else:
1368
                    val -= 1
1369

    
1370
                if element.get_name() == "scanlist1_priority_ch1":
1371
                    _mem.scanlist1_priority_ch1 = val
1372
                if element.get_name() == "scanlist1_priority_ch2":
1373
                    _mem.scanlist1_priority_ch2 = val
1374
                if element.get_name() == "scanlist2_priority_ch1":
1375
                    _mem.scanlist2_priority_ch1 = val
1376
                if element.get_name() == "scanlist2_priority_ch2":
1377
                    _mem.scanlist2_priority_ch2 = val
1378

    
1379
            if element.get_name() == "key1_shortpress_action":
1380
                _mem.key1_shortpress_action = KEYACTIONS_LIST.index(
1381
                        str(element.value))
1382

    
1383
            if element.get_name() == "key1_longpress_action":
1384
                _mem.key1_longpress_action = KEYACTIONS_LIST.index(
1385
                        str(element.value))
1386

    
1387
            if element.get_name() == "key2_shortpress_action":
1388
                _mem.key2_shortpress_action = KEYACTIONS_LIST.index(
1389
                        str(element.value))
1390

    
1391
            if element.get_name() == "key2_longpress_action":
1392
                _mem.key2_longpress_action = KEYACTIONS_LIST.index(
1393
                        str(element.value))
1394

    
1395
            if element.get_name() == "nolimits":
1396
                LOG.warning("User expanded band limits")
1397
                self._expanded_limits = bool(element.value)
1398

    
1399
    def get_settings(self):
1400
        _mem = self._memobj
1401
        basic = RadioSettingGroup("basic", "Basic Settings")
1402
        keya = RadioSettingGroup("keya", "Programmable keys")
1403
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1404
        dtmfc = RadioSettingGroup("dtmfc", "DTMF Contacts")
1405
        scanl = RadioSettingGroup("scn", "Scan Lists")
1406
        unlock = RadioSettingGroup("unlock", "Unlock Settings")
1407
        fmradio = RadioSettingGroup("fmradio", _("FM Radio"))
1408

    
1409
        roinfo = RadioSettingGroup("roinfo", _("Driver information"))
1410

    
1411
        top = RadioSettings(
1412
                basic, keya, dtmf, dtmfc, scanl, unlock, fmradio, roinfo)
1413

    
1414
        # Programmable keys
1415
        tmpval = int(_mem.key1_shortpress_action)
1416
        if tmpval >= len(KEYACTIONS_LIST):
1417
            tmpval = 0
1418
        rs = RadioSetting("key1_shortpress_action", "Side key 1 short press",
1419
                          RadioSettingValueList(
1420
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1421
        keya.append(rs)
1422

    
1423
        tmpval = int(_mem.key1_longpress_action)
1424
        if tmpval >= len(KEYACTIONS_LIST):
1425
            tmpval = 0
1426
        rs = RadioSetting("key1_longpress_action", "Side key 1 long press",
1427
                          RadioSettingValueList(
1428
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1429
        keya.append(rs)
1430

    
1431
        tmpval = int(_mem.key2_shortpress_action)
1432
        if tmpval >= len(KEYACTIONS_LIST):
1433
            tmpval = 0
1434
        rs = RadioSetting("key2_shortpress_action", "Side key 2 short press",
1435
                          RadioSettingValueList(
1436
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1437
        keya.append(rs)
1438

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

    
1447
        # DTMF settings
1448
        tmppr = bool(_mem.dtmf_settings.side_tone > 0)
1449
        rs = RadioSetting(
1450
                "dtmf_side_tone",
1451
                "DTMF Sidetone",
1452
                RadioSettingValueBoolean(tmppr))
1453
        dtmf.append(rs)
1454

    
1455
        tmpval = str(_mem.dtmf_settings.separate_code)
1456
        if tmpval not in DTMF_CODE_CHARS:
1457
            tmpval = '*'
1458
        val = RadioSettingValueString(1, 1, tmpval)
1459
        val.set_charset(DTMF_CODE_CHARS)
1460
        rs = RadioSetting("dtmf_separate_code", "Separate Code", val)
1461
        dtmf.append(rs)
1462

    
1463
        tmpval = str(_mem.dtmf_settings.group_call_code)
1464
        if tmpval not in DTMF_CODE_CHARS:
1465
            tmpval = '#'
1466
        val = RadioSettingValueString(1, 1, tmpval)
1467
        val.set_charset(DTMF_CODE_CHARS)
1468
        rs = RadioSetting("dtmf_group_call_code", "Group Call Code", val)
1469
        dtmf.append(rs)
1470

    
1471
        tmpval = _mem.dtmf_settings.decode_response
1472
        if tmpval >= len(DTMF_DECODE_RESPONSE_LIST):
1473
            tmpval = 0
1474
        rs = RadioSetting("dtmf_decode_response", "Decode Response",
1475
                          RadioSettingValueList(
1476
                              DTMF_DECODE_RESPONSE_LIST,
1477
                              DTMF_DECODE_RESPONSE_LIST[tmpval]))
1478
        dtmf.append(rs)
1479

    
1480
        tmpval = _mem.dtmf_settings.auto_reset_time
1481
        if tmpval > 60 or tmpval < 5:
1482
            tmpval = 5
1483
        rs = RadioSetting("dtmf_auto_reset_time",
1484
                          "Auto reset time (s)",
1485
                          RadioSettingValueInteger(5, 60, tmpval))
1486
        dtmf.append(rs)
1487

    
1488
        tmpval = int(_mem.dtmf_settings.preload_time)
1489
        if tmpval > 100 or tmpval < 3:
1490
            tmpval = 30
1491
        tmpval *= 10
1492
        rs = RadioSetting("dtmf_preload_time",
1493
                          "Pre-load time (ms)",
1494
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1495
        dtmf.append(rs)
1496

    
1497
        tmpval = int(_mem.dtmf_settings.first_code_persist_time)
1498
        if tmpval > 100 or tmpval < 3:
1499
            tmpval = 30
1500
        tmpval *= 10
1501
        rs = RadioSetting("dtmf_first_code_persist_time",
1502
                          "First code persist time (ms)",
1503
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1504
        dtmf.append(rs)
1505

    
1506
        tmpval = int(_mem.dtmf_settings.hash_persist_time)
1507
        if tmpval > 100 or tmpval < 3:
1508
            tmpval = 30
1509
        tmpval *= 10
1510
        rs = RadioSetting("dtmf_hash_persist_time",
1511
                          "#/* persist time (ms)",
1512
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1513
        dtmf.append(rs)
1514

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

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

    
1533
        tmpval = bool(_mem.dtmf_settings.permit_remote_kill > 0)
1534
        rs = RadioSetting(
1535
                "dtmf_permit_remote_kill",
1536
                "Permit remote kill",
1537
                RadioSettingValueBoolean(tmpval))
1538
        dtmf.append(rs)
1539

    
1540
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_local_code).upper().strip(
1541
                "\x00\xff\x20")
1542
        for i in tmpval:
1543
            if i in DTMF_CHARS_ID:
1544
                continue
1545
            else:
1546
                tmpval = "103"
1547
                break
1548
        val = RadioSettingValueString(3, 3, tmpval)
1549
        val.set_charset(DTMF_CHARS_ID)
1550
        rs = RadioSetting("dtmf_dtmf_local_code",
1551
                          "Local code (3 chars 0-9 ABCD)", val)
1552
        dtmf.append(rs)
1553

    
1554
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_up_code).upper().strip(
1555
                "\x00\xff\x20")
1556
        for i in tmpval:
1557
            if i in DTMF_CHARS_UPDOWN or i == "":
1558
                continue
1559
            else:
1560
                tmpval = "123"
1561
                break
1562
        val = RadioSettingValueString(1, 16, tmpval)
1563
        val.set_charset(DTMF_CHARS_UPDOWN)
1564
        rs = RadioSetting("dtmf_dtmf_up_code",
1565
                          "Up code (1-16 chars 0-9 ABCD*#)", val)
1566
        dtmf.append(rs)
1567

    
1568
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_down_code).upper().strip(
1569
                "\x00\xff\x20")
1570
        for i in tmpval:
1571
            if i in DTMF_CHARS_UPDOWN:
1572
                continue
1573
            else:
1574
                tmpval = "456"
1575
                break
1576
        val = RadioSettingValueString(1, 16, tmpval)
1577
        val.set_charset(DTMF_CHARS_UPDOWN)
1578
        rs = RadioSetting("dtmf_dtmf_down_code",
1579
                          "Down code (1-16 chars 0-9 ABCD*#)", val)
1580
        dtmf.append(rs)
1581

    
1582
        tmpval = str(_mem.dtmf_settings_numbers.kill_code).upper().strip(
1583
                "\x00\xff\x20")
1584
        for i in tmpval:
1585
            if i in DTMF_CHARS_KILL:
1586
                continue
1587
            else:
1588
                tmpval = "77777"
1589
                break
1590
        if not len(tmpval) == 5:
1591
            tmpval = "77777"
1592
        val = RadioSettingValueString(5, 5, tmpval)
1593
        val.set_charset(DTMF_CHARS_KILL)
1594
        rs = RadioSetting("dtmf_kill_code",
1595
                          "Kill code (5 chars 0-9 ABCD)", val)
1596
        dtmf.append(rs)
1597

    
1598
        tmpval = str(_mem.dtmf_settings_numbers.revive_code).upper().strip(
1599
                "\x00\xff\x20")
1600
        for i in tmpval:
1601
            if i in DTMF_CHARS_KILL:
1602
                continue
1603
            else:
1604
                tmpval = "88888"
1605
                break
1606
        if not len(tmpval) == 5:
1607
            tmpval = "88888"
1608
        val = RadioSettingValueString(5, 5, tmpval)
1609
        val.set_charset(DTMF_CHARS_KILL)
1610
        rs = RadioSetting("dtmf_revive_code",
1611
                          "Revive code (5 chars 0-9 ABCD)", val)
1612
        dtmf.append(rs)
1613

    
1614
        val = RadioSettingValueString(0, 80,
1615
                                      "All DTMF Contacts are 3 codes "
1616
                                      "(valid: 0-9 * # ABCD), "
1617
                                      "or an empty string")
1618
        val.set_mutable(False)
1619
        rs = RadioSetting("dtmf_descr1", "DTMF Contacts", val)
1620
        dtmfc.append(rs)
1621

    
1622
        for i in range(1, 17):
1623
            varname = "DTMF_"+str(i)
1624
            varnumname = "DTMFNUM_"+str(i)
1625
            vardescr = "DTMF Contact "+str(i)+" name"
1626
            varinumdescr = "DTMF Contact "+str(i)+" number"
1627

    
1628
            cntn = str(_mem.dtmfcontact[i-1].name).strip("\x20\x00\xff")
1629
            cntnum = str(_mem.dtmfcontact[i-1].number).strip("\x20\x00\xff")
1630

    
1631
            val = RadioSettingValueString(0, 8, cntn)
1632
            rs = RadioSetting(varname, vardescr, val)
1633
            dtmfc.append(rs)
1634

    
1635
            val = RadioSettingValueString(0, 3, cntnum)
1636
            val.set_charset(DTMF_CHARS)
1637
            rs = RadioSetting(varnumname, varinumdescr, val)
1638
            dtmfc.append(rs)
1639

    
1640
        # scanlists
1641
        if _mem.scanlist_default == 1:
1642
            tmpsc = 2
1643
        else:
1644
            tmpsc = 1
1645
        rs = RadioSetting("scanlist_default",
1646
                          "Default scanlist",
1647
                          RadioSettingValueInteger(1, 2, tmpsc))
1648
        scanl.append(rs)
1649

    
1650
        tmppr = bool((_mem.scanlist1_priority_scan & 1) > 0)
1651
        rs = RadioSetting(
1652
                "scanlist1_priority_scan",
1653
                "Scanlist 1 priority channel scan",
1654
                RadioSettingValueBoolean(tmppr))
1655
        scanl.append(rs)
1656

    
1657
        tmpch = _mem.scanlist1_priority_ch1 + 1
1658
        if tmpch > 200:
1659
            tmpch = 0
1660
        rs = RadioSetting("scanlist1_priority_ch1",
1661
                          "Scanlist 1 priority channel 1 (0 - off)",
1662
                          RadioSettingValueInteger(0, 200, tmpch))
1663
        scanl.append(rs)
1664

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

    
1673
        tmppr = bool((_mem.scanlist2_priority_scan & 1) > 0)
1674
        rs = RadioSetting(
1675
                "scanlist2_priority_scan",
1676
                "Scanlist 2 priority channel scan",
1677
                RadioSettingValueBoolean(tmppr))
1678
        scanl.append(rs)
1679

    
1680
        tmpch = _mem.scanlist2_priority_ch1 + 1
1681
        if tmpch > 200:
1682
            tmpch = 0
1683
        rs = RadioSetting("scanlist2_priority_ch1",
1684
                          "Scanlist 2 priority channel 1 (0 - off)",
1685
                          RadioSettingValueInteger(0, 200, tmpch))
1686
        scanl.append(rs)
1687

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

    
1696
        # basic settings
1697

    
1698
        # squelch
1699
        tmpsq = _mem.squelch
1700
        if tmpsq > 9:
1701
            tmpsq = 1
1702
        rs = RadioSetting("squelch", "Squelch",
1703
                          RadioSettingValueInteger(0, 9, tmpsq))
1704
        basic.append(rs)
1705

    
1706
        # TOT
1707
        tmptot = _mem.max_talk_time
1708
        if tmptot >= len(TALKTIME_LIST):
1709
            tmptot = TALKTIME_LIST.index("2min")
1710
        rs = RadioSetting(
1711
                "max_talk_time",
1712
                "Max talk time",
1713
                RadioSettingValueList(
1714
                    TALKTIME_LIST,
1715
                    TALKTIME_LIST[tmptot]))
1716
        basic.append(rs)
1717

    
1718
        # Channel display mode
1719
        tmpchdispmode = _mem.channel_display_mode
1720
        if tmpchdispmode >= len(CHANNELDISP_LIST):
1721
            tmpchdispmode = 0
1722
        rs = RadioSetting(
1723
                "channel_display_mode",
1724
                "Channel display mode",
1725
                RadioSettingValueList(
1726
                    CHANNELDISP_LIST,
1727
                    CHANNELDISP_LIST[tmpchdispmode]))
1728
        basic.append(rs)
1729

    
1730
        # Backlight auto mode
1731
        tmpback = _mem.backlight_auto_mode
1732
        if tmpback >= len(BACKLIGHT_LIST):
1733
            tmpback = 0
1734
        rs = RadioSetting("backlight_auto_mode",
1735
                          "Backlight time",
1736
                          RadioSettingValueList(
1737
                              BACKLIGHT_LIST,
1738
                              BACKLIGHT_LIST[tmpback]))
1739
        basic.append(rs)
1740

    
1741
        # BLMode
1742
        rs = RadioSetting(
1743
                "bl_mode",
1744
                "BLmode (TX/RX)", RadioSettingValueBoolean(bool((_mem.bl_mode & 0x20) > 0)))
1745
        basic.append(rs)
1746

    
1747
        # Beep control
1748
        rs = RadioSetting(
1749
                "beep_control",
1750
                "Beep control",
1751
                RadioSettingValueBoolean(bool(_mem.beep_control > 0)))
1752
        basic.append(rs)
1753

    
1754
        # Scan resume mode
1755
        tmpscanres = _mem.scan_resume_mode
1756
        if tmpscanres >= len(SCANRESUME_LIST):
1757
            tmpscanres = 0
1758
        rs = RadioSetting(
1759
                "scan_resume_mode",
1760
                "Scan resume mode (Sc REV)",
1761
                RadioSettingValueList(
1762
                    SCANRESUME_LIST,
1763
                    SCANRESUME_LIST[tmpscanres]))
1764
        basic.append(rs)
1765

    
1766
        # Keypad locked
1767
        rs = RadioSetting(
1768
                "key_lock",
1769
                "Keypad lock",
1770
                RadioSettingValueBoolean(bool(_mem.key_lock > 0)))
1771
        basic.append(rs)
1772

    
1773
        # Auto keypad lock
1774
        rs = RadioSetting(
1775
                "auto_keypad_lock",
1776
                "Auto keypad lock",
1777
                RadioSettingValueBoolean(bool(_mem.auto_keypad_lock > 0)))
1778
        basic.append(rs)
1779

    
1780
        # Tail tone elimination
1781
        rs = RadioSetting(
1782
                "tail_note_elimination",
1783
                "Tail tone elimination",
1784
                RadioSettingValueBoolean(
1785
                    bool(_mem.tail_note_elimination > 0)))
1786
        basic.append(rs)
1787

    
1788
        # Repeater tail tone elimination
1789
        tmprte = _mem.repeater_tail_elimination
1790
        if tmprte >= len(RTE_LIST):
1791
            tmprte = 0
1792
        rs = RadioSetting(
1793
                "repeater_tail_elimination",
1794
                "Repeater tail tone elimination",
1795
                RadioSettingValueList(RTE_LIST, RTE_LIST[tmprte]))
1796
        basic.append(rs)
1797

    
1798
        # Mic gain
1799
        tmpmicgain = _mem.mic_gain
1800
        if tmpmicgain >= len(MICGAIN_LIST):
1801
            tmpmicgain = MICGAIN_LIST.index("+12.0dB")
1802
        rs = RadioSetting(
1803
                "mic_gain",
1804
                "Mic Gain",
1805
                RadioSettingValueList(
1806
                    MICGAIN_LIST,
1807
                    MICGAIN_LIST[tmpmicgain]))
1808
        basic.append(rs)
1809

    
1810
        # MicBar
1811
        rs = RadioSetting(
1812
                "micbar",
1813
                "MicBar", RadioSettingValueBoolean(bool((_mem.micbar & 0x10) > 0)))
1814
        basic.append(rs)
1815

    
1816
        # Compander
1817
        tmpcompander = _mem.compander >> 4
1818
        if tmpcompander >= len(COMPANDER_LIST):
1819
            tmpcompander = COMPANDER_LIST.index("Off")
1820
        rs = RadioSetting(
1821
                "compander",
1822
                "Compander",
1823
                RadioSettingValueList(
1824
                    COMPANDER_LIST,
1825
                    COMPANDER_LIST[tmpcompander]))
1826
        basic.append(rs)
1827

    
1828
        # VOX switch
1829
        rs = RadioSetting(
1830
                "vox_switch",
1831
                "VOX enabled", RadioSettingValueBoolean(
1832
                    bool(_mem.vox_switch > 0)))
1833
        basic.append(rs)
1834

    
1835
        # VOX Level
1836
        tmpvox = _mem.vox_level+1
1837
        if tmpvox > 10:
1838
            tmpvox = 10
1839
        rs = RadioSetting("vox_level", "VOX Level",
1840
                          RadioSettingValueInteger(1, 10, tmpvox))
1841
        basic.append(rs)
1842

    
1843
        # call channel
1844
        tmpc = _mem.call_channel+1
1845
        if tmpc > 200:
1846
            tmpc = 1
1847
        rs = RadioSetting("call_channel", "One key call channel",
1848
                          RadioSettingValueInteger(1, 200, tmpc))
1849
        basic.append(rs)
1850

    
1851
        # Reminding of end of talk
1852
        tmpalarmmode = _mem.reminding_of_end_talk
1853
        if tmpalarmmode >= len(REMENDOFTALK_LIST):
1854
            tmpalarmmode = 0
1855
        rs = RadioSetting(
1856
                "reminding_of_end_talk",
1857
                "Reminding of end of talk (DigSRV)",
1858
                RadioSettingValueList(
1859
                    REMENDOFTALK_LIST,
1860
                    REMENDOFTALK_LIST[tmpalarmmode]))
1861
        basic.append(rs)
1862

    
1863
        # Beacon
1864
        tmpbeacon = _mem.beacon
1865
        if tmpbeacon >= len(BEACON_LIST):
1866
            tmpbeacon = BEACON_LIST.index("Off")
1867
        rs = RadioSetting(
1868
                "beacon",
1869
                "Beacon",
1870
                RadioSettingValueList(
1871
                    BEACON_LIST,
1872
                    BEACON_LIST[tmpbeacon]))
1873
        basic.append(rs)
1874

    
1875
        # Battery save
1876
        tmpbatsave = _mem.battery_save
1877
        if tmpbatsave >= len(BATSAVE_LIST):
1878
            tmpbatsave = BATSAVE_LIST.index("80%")
1879
        rs = RadioSetting(
1880
                "battery_save",
1881
                "Battery Save",
1882
                RadioSettingValueList(
1883
                    BATSAVE_LIST,
1884
                    BATSAVE_LIST[tmpbatsave]))
1885
        basic.append(rs)
1886

    
1887
        # RSSI / S Meter
1888
        rs = RadioSetting(
1889
                "signal_meter",
1890
                "SMeter (instead of RSSI)", RadioSettingValueBoolean(bool((_mem.signal_meter & 0x04) > 0)))
1891
        basic.append(rs)
1892

    
1893
        # Crossband receiving/transmitting
1894
        tmpcross = _mem.crossband
1895
        if tmpcross >= len(CROSSBAND_LIST):
1896
            tmpcross = 0
1897
        rs = RadioSetting(
1898
                "crossband",
1899
                "Cross-band receiving/transmitting (Tx VFO)",
1900
                RadioSettingValueList(
1901
                    CROSSBAND_LIST,
1902
                    CROSSBAND_LIST[tmpcross]))
1903
        basic.append(rs)
1904

    
1905
        # Dual watch
1906
        tmpdual = _mem.dual_watch
1907
        if tmpdual >= len(DUALWATCH_LIST):
1908
            tmpdual = 0
1909
        rs = RadioSetting("dualwatch", "Dual Watch (DualRX)", RadioSettingValueList(
1910
            DUALWATCH_LIST, DUALWATCH_LIST[tmpdual]))
1911
        basic.append(rs)
1912

    
1913
        # Power on display mode
1914
        tmpdispmode = _mem.power_on_dispmode
1915
        if tmpdispmode >= len(WELCOME_LIST):
1916
            tmpdispmode = 0
1917
        rs = RadioSetting(
1918
                "welcome_mode",
1919
                "Power on display mode",
1920
                RadioSettingValueList(
1921
                    WELCOME_LIST,
1922
                    WELCOME_LIST[tmpdispmode]))
1923
        basic.append(rs)
1924

    
1925
        # QRZ label
1926
        qrz_label = str(_mem.qrz_label).rstrip("\x20\x00\xff") + "\x00"
1927
        qrz_label = _getstring(qrz_label.encode('ascii', errors='ignore'), 0, 8)
1928
        rs = RadioSetting("qrz_label", _("QRA (8 characters)"),
1929
                          RadioSettingValueString(0, 8, qrz_label))
1930
        basic.append(rs)
1931

    
1932
        # Logo string 1
1933
        logo1 = str(_mem.logo_line1).strip("\x20\x00\xff") + "\x00"
1934
        logo1 = _getstring(logo1.encode('ascii', errors='ignore'), 0, 12)
1935
        rs = RadioSetting("logo1", _("Logo string 1 (12 characters)"),
1936
                          RadioSettingValueString(0, 12, logo1))
1937
        basic.append(rs)
1938

    
1939
        # Logo string 2
1940
        logo2 = str(_mem.logo_line2).strip("\x20\x00\xff") + "\x00"
1941
        logo2 = _getstring(logo2.encode('ascii', errors='ignore'), 0, 12)
1942
        rs = RadioSetting("logo2", _("Logo string 2 (12 characters)"),
1943
                          RadioSettingValueString(0, 12, logo2))
1944
        basic.append(rs)
1945

    
1946
        # NOAA autoscan
1947
#        rs = RadioSetting(
1948
#                "noaa_autoscan",
1949
#                "NOAA Autoscan (not implemented)", RadioSettingValueBoolean(
1950
#                    bool(_mem.noaa_autoscan > 0)))
1951
#        basic.append(rs)
1952

    
1953

    
1954
        # VFO open
1955
#        rs = RadioSetting("vfo_open", "VFO open (???)",
1956
#                          RadioSettingValueBoolean(bool(_mem.vfo_open > 0)))
1957
#        basic.append(rs)
1958

    
1959
        # Keypad Tone
1960
#        tmpkeypadtone = _mem.keypad_tone
1961
#        if tmpkeypadtone >= len(KEYPADTONE_LIST):
1962
#            tmpkeypadtone = 0
1963
#        rs = RadioSetting("keypad_tone", "Voice prompts (not implemented)", RadioSettingValueList(
1964
#            KEYPADTONE_LIST, KEYPADTONE_LIST[tmpkeypadtone]))
1965
#        basic.append(rs)
1966

    
1967
        # Language
1968
#        tmplanguage = _mem.language
1969
#        if tmplanguage >= len(LANGUAGE_LIST):
1970
#            tmplanguage = 0
1971
#        rs = RadioSetting("language", "Language (not implemented)", RadioSettingValueList(
1972
#            LANGUAGE_LIST, LANGUAGE_LIST[tmplanguage]))
1973
#        basic.append(rs)
1974

    
1975
        # Alarm mode
1976
#        tmpalarmmode = _mem.alarm_mode
1977
#        if tmpalarmmode >= len(ALARMMODE_LIST):
1978
#            tmpalarmmode = 0
1979
#        rs = RadioSetting("alarm_mode", "Alarm mode (not implemented)", RadioSettingValueList(
1980
#            ALARMMODE_LIST, ALARMMODE_LIST[tmpalarmmode]))
1981
#        basic.append(rs)
1982

    
1983
        # FM radio
1984
        for i in range(1, 21):
1985
            freqname = "FM_"+str(i)
1986
            fmfreq = _mem.fmfreq[i-1]/10.0
1987
            if fmfreq < FMMIN or fmfreq > FMMAX:
1988
                rs = RadioSetting(freqname, freqname,
1989
                                  RadioSettingValueString(0, 5, ""))
1990
            else:
1991
                rs = RadioSetting(freqname, freqname,
1992
                                  RadioSettingValueString(0, 5, str(fmfreq)))
1993

    
1994
            fmradio.append(rs)
1995

    
1996
        # unlock settings
1997

    
1998
        # F-LOCK
1999
        tmpflock = _mem.lock.flock
2000
        if tmpflock >= len(FLOCK_LIST):
2001
            tmpflock = 0
2002
        rs = RadioSetting(
2003
            "flock", "F-LOCK",
2004
            RadioSettingValueList(FLOCK_LIST, FLOCK_LIST[tmpflock]))
2005
        unlock.append(rs)
2006

    
2007
        # 350TX
2008
#        rs = RadioSetting("tx350", "350TX - unlock 350-400 MHz TX",
2009
#                          RadioSettingValueBoolean(
2010
#                              bool(_mem.lock.tx350 > 0)))
2011
#        unlock.append(rs)
2012

    
2013
        # Killed
2014
        rs = RadioSetting("Killed", "KILLED Device was disabled (via DTMF)",
2015
                          RadioSettingValueBoolean(
2016
                              bool(_mem.lock.killed > 0)))
2017
        unlock.append(rs)
2018

    
2019
        # 200TX
2020
#        rs = RadioSetting("tx200", "200TX - unlock 174-350 MHz TX",
2021
#                          RadioSettingValueBoolean(
2022
#                              bool(_mem.lock.tx200 > 0)))
2023
#        unlock.append(rs)
2024

    
2025
        # 500TX
2026
#        rs = RadioSetting("tx500", "500TX - unlock 500-600 MHz TX",
2027
#                          RadioSettingValueBoolean(
2028
#                              bool(_mem.lock.tx500 > 0)))
2029
#        unlock.append(rs)
2030

    
2031
        # 350EN
2032
#        rs = RadioSetting("en350", "350EN - unlock 350-400 MHz RX",
2033
#                          RadioSettingValueBoolean(
2034
#                              bool(_mem.lock.en350 > 0)))
2035
#        unlock.append(rs)
2036

    
2037
        # SCREEN
2038
#        rs = RadioSetting("scrambler", "SCREN - scrambler enable",
2039
#                          RadioSettingValueBoolean(
2040
#                              bool(_mem.lock.enscramble > 0)))
2041
#        unlock.append(rs)
2042

    
2043
        # readonly info
2044
        # Firmware
2045
        if self.FIRMWARE_VERSION == "":
2046
            firmware = "To get the firmware version please download"
2047
            "the image from the radio first"
2048
        else:
2049
            firmware = self.FIRMWARE_VERSION
2050

    
2051
        val = RadioSettingValueString(0, 128, firmware)
2052
        val.set_mutable(False)
2053
        rs = RadioSetting("fw_ver", "Firmware Version", val)
2054
        roinfo.append(rs)
2055

    
2056
        # No limits version for hacked firmware
2057
        val = RadioSettingValueBoolean(self._expanded_limits)
2058
        rs = RadioSetting("nolimits", "Limits disabled for modified firmware",
2059
                          val)
2060
        rs.set_warning(_(
2061
            'This should only be enabled if you are using modified firmware '
2062
            'that supports wider frequency coverage. Enabling this will cause '
2063
            'CHIRP not to enforce OEM restrictions and may lead to undefined '
2064
            'or unregulated behavior. Use at your own risk!'),
2065
            safe_value=False)
2066
        roinfo.append(rs)
2067

    
2068
        return top
2069

    
2070
    # Store details about a high-level memory to the memory map
2071
    # This is called when a user edits a memory in the UI
2072
    def set_memory(self, mem):
2073
        number = mem.number-1
2074

    
2075
        # Get a low-level memory object mapped to the image
2076
        _mem = self._memobj.channel[number]
2077
        _mem4 = self._memobj
2078
        # empty memory
2079
        if mem.empty:
2080
            _mem.set_raw("\xFF" * 16)
2081
            if number < 200:
2082
                _mem2 = self._memobj.channelname[number]
2083
                _mem2.set_raw("\xFF" * 16)
2084
                _mem4.channel_attributes[number].is_scanlist1 = 0
2085
                _mem4.channel_attributes[number].is_scanlist2 = 0
2086
                _mem4.channel_attributes[number].unknown1 = 0
2087
                _mem4.channel_attributes[number].unknown2 = 0
2088
                _mem4.channel_attributes[number].is_free = 1
2089
                _mem4.channel_attributes[number].band = 0x7
2090
            return mem
2091

    
2092
        # clean the channel memory, restore some bits if it was used before
2093
        if _mem.get_raw(asbytes=False)[0] == "\xff":
2094
            # this was an empty memory
2095
            _mem.set_raw("\x00" * 16)
2096
        else:
2097
            # this memory wasn't empty, save some bits that we don't know the
2098
            # meaning of, or that we don't support yet
2099
            prev_0a = _mem.get_raw()[0x0a] & SAVE_MASK_0A
2100
            prev_0b = _mem.get_raw()[0x0b] & SAVE_MASK_0B
2101
            prev_0c = _mem.get_raw()[0x0c] & SAVE_MASK_0C
2102
            prev_0d = _mem.get_raw()[0x0d] & SAVE_MASK_0D
2103
            prev_0e = _mem.get_raw()[0x0e] & SAVE_MASK_0E
2104
            prev_0f = _mem.get_raw()[0x0f] & SAVE_MASK_0F
2105
            _mem.set_raw("\x00" * 10 +
2106
                         chr(prev_0a) + chr(prev_0b) + chr(prev_0c) +
2107
                         chr(prev_0d) + chr(prev_0e) + chr(prev_0f))
2108

    
2109
        if number < 200:
2110
            _mem4.channel_attributes[number].is_scanlist1 = 0
2111
            _mem4.channel_attributes[number].is_scanlist2 = 0
2112
            _mem4.channel_attributes[number].unknown1 = 0
2113
            _mem4.channel_attributes[number].unknown2 = 0
2114
            _mem4.channel_attributes[number].is_free = 1
2115
            _mem4.channel_attributes[number].band = 0x7
2116

    
2117
        # find band
2118
        band = _find_band(self, mem.freq)
2119

    
2120
        # mode
2121
        if mem.mode == "NFM":
2122
            _mem.bandwidth = BANDWIDTH_NARROW
2123
            _mem.enable_am = 0
2124
        elif mem.mode == "FM":
2125
            _mem.bandwidth = BANDWIDTH_WIDE
2126
            _mem.enable_am = 0
2127
        elif mem.mode == "WFM":
2128
            _mem.bandwidth = BANDWIDTH_WIDE_PLUS
2129
            _mem.enable_am = 0
2130
        elif mem.mode == "AM":
2131
            _mem.bandwidth = BANDWIDTH_WIDE
2132
            _mem.enable_am = 1
2133
        elif mem.mode == "NAM":
2134
            _mem.bandwidth = BANDWIDTH_NARROW
2135
            _mem.enable_am = 1
2136

    
2137
        # frequency/offset
2138
        _mem.freq = mem.freq/10
2139
        _mem.offset = mem.offset/10
2140

    
2141
        if mem.duplex == "":
2142
            _mem.offset = 0
2143
            _mem.shift = 0
2144
        elif mem.duplex == '-':
2145
            _mem.shift = FLAGS1_OFFSET_MINUS
2146
        elif mem.duplex == '+':
2147
            _mem.shift = FLAGS1_OFFSET_PLUS
2148
        elif mem.duplex == 'off':
2149
            # we fake tx disable by setting the tx freq to 0 MHz
2150
            _mem.shift = FLAGS1_OFFSET_MINUS
2151
            _mem.offset = _mem.freq
2152

    
2153
        # set band
2154
        if number < 200:
2155
            _mem4.channel_attributes[number].is_free = 0
2156
            _mem4.channel_attributes[number].band = band
2157

    
2158
        # channels >200 are the 14 VFO chanells and don't have names
2159
        if number < 200:
2160
            _mem2 = self._memobj.channelname[number]
2161
            tag = mem.name.ljust(10) + "\x00"*6
2162
            _mem2.name = tag  # Store the alpha tag
2163

    
2164
        # tone data
2165
        self._set_tone(mem, _mem)
2166

    
2167
        # step
2168
        _mem.step = STEPS.index(mem.tuning_step)
2169

    
2170
        # tx power
2171
        if str(mem.power) == str(UVK5_POWER_LEVELS[2]):
2172
            _mem.txpower = POWER_HIGH
2173
        elif str(mem.power) == str(UVK5_POWER_LEVELS[1]):
2174
            _mem.txpower = POWER_MEDIUM
2175
        else:
2176
            _mem.txpower = POWER_LOW
2177

    
2178
        for setting in mem.extra:
2179
            sname = setting.get_name()
2180
            svalue = setting.value.get_value()
2181

    
2182
            if sname == "bclo":
2183
                _mem.bclo = svalue and 1 or 0
2184

    
2185
            if sname == "pttid":
2186
                _mem.dtmf_pttid = PTTID_LIST.index(svalue)
2187

    
2188
            if sname == "frev":
2189
                _mem.freq_reverse = svalue and 1 or 0
2190

    
2191
            if sname == "dtmfdecode":
2192
                _mem.dtmf_decode = svalue and 1 or 0
2193

    
2194
            if sname == "scrambler":
2195
                _mem.scrambler = (
2196
                    _mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue)
2197

    
2198
            if number < 200 and sname == "scanlists":
2199
                if svalue == "1":
2200
                    _mem4.channel_attributes[number].is_scanlist1 = 1
2201
                    _mem4.channel_attributes[number].is_scanlist2 = 0
2202
                elif svalue == "2":
2203
                    _mem4.channel_attributes[number].is_scanlist1 = 0
2204
                    _mem4.channel_attributes[number].is_scanlist2 = 1
2205
                elif svalue == "1+2":
2206
                    _mem4.channel_attributes[number].is_scanlist1 = 1
2207
                    _mem4.channel_attributes[number].is_scanlist2 = 1
2208
                else:
2209
                    _mem4.channel_attributes[number].is_scanlist1 = 0
2210
                    _mem4.channel_attributes[number].is_scanlist2 = 0
2211

    
2212
        return mem
2213

    
2214

    
2215
@directory.register
2216
class RA79Radio(UVK5Radio):
2217
    """Retevis RA79"""
2218
    VENDOR = "Retevis"
2219
    MODEL = "RA79"
(1-1/2)