Project

General

Profile

Bug #11165 » uvk5.py

re-fixed module - Dan Smith, 02/13/2024 06:29 PM

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

    
27

    
28
import struct
29
import logging
30

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

    
37
LOG = logging.getLogger(__name__)
38

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

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

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

    
56
  u8 txcodeflag:4,
57
     rxcodeflag:4;
58

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

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

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

    
86

    
87
  u8 step;
88
  u8 scrambler;
89
} channel[214];
90

    
91
#seekto 0xd60;
92
struct {
93
u8 is_scanlist1:1,
94
is_scanlist2:1,
95
compander:2,
96
is_free:1,
97
band:3;
98
} channel_attributes[200];
99

    
100
#seekto 0xe40;
101
ul16 fmfreq[20];
102

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

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

    
132
#seekto 0xea0;
133
u8 keypad_tone;
134
u8 language;
135

    
136
#seekto 0xea8;
137
u8 alarm_mode;
138
u8 reminding_of_end_talk;
139
u8 repeater_tail_elimination;
140

    
141
#seekto 0xeb0;
142
char logo_line1[16];
143
char logo_line2[16];
144

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

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

    
172
#seekto 0xf18;
173
u8 scanlist_default;
174
u8 scanlist1_priority_scan;
175
u8 scanlist1_priority_ch1;
176
u8 scanlist1_priority_ch2;
177
u8 scanlist2_priority_scan;
178
u8 scanlist2_priority_ch1;
179
u8 scanlist2_priority_ch2;
180
u8 scanlist_unknown_0xff;
181

    
182

    
183
#seekto 0xf40;
184
struct {
185
u8 flock;
186
u8 tx350;
187
u8 killed;
188
u8 tx200;
189
u8 tx500;
190
u8 en350;
191
u8 enscramble;
192
} lock;
193

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

    
199
#seekto 0x1c00;
200
struct {
201
char name[8];
202
char number[3];
203
char unused_00[5];
204
} dtmfcontact[16];
205

    
206
#seekto 0x1ed0;
207
struct {
208
struct {
209
    u8 start;
210
    u8 mid;
211
    u8 end;
212
} low;
213
struct {
214
    u8 start;
215
    u8 mid;
216
    u8 end;
217
} medium;
218
struct {
219
    u8 start;
220
    u8 mid;
221
    u8 end;
222
} high;
223
u8 unused_00[7];
224
} perbandpowersettings[7];
225

    
226
#seekto 0x1f40;
227
ul16 battery_level[6];
228
"""
229
# bits that we will save from the channel structure (mostly unknown)
230
SAVE_MASK_0A = 0b11001100
231
SAVE_MASK_0B = 0b11101100
232
SAVE_MASK_0C = 0b11100000
233
SAVE_MASK_0D = 0b11111000
234
SAVE_MASK_0E = 0b11110001
235
SAVE_MASK_0F = 0b11110000
236

    
237
# flags1
238
FLAGS1_OFFSET_NONE = 0b00
239
FLAGS1_OFFSET_MINUS = 0b10
240
FLAGS1_OFFSET_PLUS = 0b01
241

    
242
POWER_HIGH = 0b10
243
POWER_MEDIUM = 0b01
244
POWER_LOW = 0b00
245

    
246
# power
247
UVK5_POWER_LEVELS = [chirp_common.PowerLevel("Low",  watts=1.50),
248
                     chirp_common.PowerLevel("Med",  watts=3.00),
249
                     chirp_common.PowerLevel("High", watts=5.00),
250
                     ]
251

    
252
# scrambler
253
SCRAMBLER_LIST = ["off", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
254

    
255
# channel display mode
256
CHANNELDISP_LIST = ["Frequency", "Channel No", "Channel Name"]
257
# battery save
258
BATSAVE_LIST = ["OFF", "1:1", "1:2", "1:3", "1:4"]
259

    
260
# Backlight auto mode
261
BACKLIGHT_LIST = ["Off", "1s", "2s", "3s", "4s", "5s"]
262

    
263
# Crossband receiving/transmitting
264
CROSSBAND_LIST = ["Off", "Band A", "Band B"]
265
DUALWATCH_LIST = CROSSBAND_LIST
266

    
267
# ctcss/dcs codes
268
TMODES = ["", "Tone", "DTCS", "DTCS"]
269
TONE_NONE = 0
270
TONE_CTCSS = 1
271
TONE_DCS = 2
272
TONE_RDCS = 3
273

    
274

    
275
CTCSS_TONES = [
276
    67.0, 69.3, 71.9, 74.4, 77.0, 79.7, 82.5, 85.4,
277
    88.5, 91.5, 94.8, 97.4, 100.0, 103.5, 107.2, 110.9,
278
    114.8, 118.8, 123.0, 127.3, 131.8, 136.5, 141.3, 146.2,
279
    151.4, 156.7, 159.8, 162.2, 165.5, 167.9, 171.3, 173.8,
280
    177.3, 179.9, 183.5, 186.2, 189.9, 192.8, 196.6, 199.5,
281
    203.5, 206.5, 210.7, 218.1, 225.7, 229.1, 233.6, 241.8,
282
    250.3, 254.1
283
]
284

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

    
299
FLOCK_LIST = ["Off", "FCC", "CE", "GB", "430", "438"]
300

    
301
SCANRESUME_LIST = ["TO: Resume after 5 seconds",
302
                   "CO: Resume after signal disappears",
303
                   "SE: Stop scanning after receiving a signal"]
304

    
305
WELCOME_LIST = ["Full Screen", "Welcome Info", "Voltage"]
306
KEYPADTONE_LIST = ["Off", "Chinese", "English"]
307
LANGUAGE_LIST = ["Chinese", "English"]
308
ALARMMODE_LIST = ["SITE", "TONE"]
309
REMENDOFTALK_LIST = ["Off", "ROGER", "MDC"]
310
RTE_LIST = ["Off", "100ms", "200ms", "300ms", "400ms",
311
            "500ms", "600ms", "700ms", "800ms", "900ms"]
312

    
313
MEM_SIZE = 0x2000  # size of all memory
314
PROG_SIZE = 0x1d00  # size of the memory that we will write
315
MEM_BLOCK = 0x80  # largest block of memory that we can reliably write
316

    
317
# fm radio supported frequencies
318
FMMIN = 76.0
319
FMMAX = 108.0
320

    
321
# bands supported by the UV-K5
322
BANDS = {
323
        0: [50.0, 76.0],
324
        1: [108.0, 135.9999],
325
        2: [136.0, 199.9990],
326
        3: [200.0, 299.9999],
327
        4: [350.0, 399.9999],
328
        5: [400.0, 469.9999],
329
        6: [470.0, 600.0]
330
        }
331

    
332
# for radios with modified firmware:
333
BANDS_NOLIMITS = {
334
        0: [18.0, 76.0],
335
        1: [108.0, 135.9999],
336
        2: [136.0, 199.9990],
337
        3: [200.0, 299.9999],
338
        4: [350.0, 399.9999],
339
        5: [400.0, 469.9999],
340
        6: [470.0, 1300.0]
341
        }
342

    
343
SPECIALS = {
344
        "F1(50M-76M)A": 200,
345
        "F1(50M-76M)B": 201,
346
        "F2(108M-136M)A": 202,
347
        "F2(108M-136M)B": 203,
348
        "F3(136M-174M)A": 204,
349
        "F3(136M-174M)B": 205,
350
        "F4(174M-350M)A": 206,
351
        "F4(174M-350M)B": 207,
352
        "F5(350M-400M)A": 208,
353
        "F5(350M-400M)B": 209,
354
        "F6(400M-470M)A": 210,
355
        "F6(400M-470M)B": 211,
356
        "F7(470M-600M)A": 212,
357
        "F7(470M-600M)B": 213
358
        }
359

    
360
VFO_CHANNEL_NAMES = ["F1(50M-76M)A", "F1(50M-76M)B",
361
                     "F2(108M-136M)A", "F2(108M-136M)B",
362
                     "F3(136M-174M)A", "F3(136M-174M)B",
363
                     "F4(174M-350M)A", "F4(174M-350M)B",
364
                     "F5(350M-400M)A", "F5(350M-400M)B",
365
                     "F6(400M-470M)A", "F6(400M-470M)B",
366
                     "F7(470M-600M)A", "F7(470M-600M)B"]
367

    
368
SCANLIST_LIST = ["None", "1", "2", "1+2"]
369

    
370
DTMF_CHARS = "0123456789ABCD*# "
371
DTMF_CHARS_ID = "0123456789ABCDabcd"
372
DTMF_CHARS_KILL = "0123456789ABCDabcd"
373
DTMF_CHARS_UPDOWN = "0123456789ABCDabcd#* "
374
DTMF_CODE_CHARS = "ABCD*# "
375
DTMF_DECODE_RESPONSE_LIST = ["None", "Ring", "Reply", "Both"]
376

    
377
KEYACTIONS_LIST = ["None", "Flashlight on/off", "Power select",
378
                   "Monitor", "Scan on/off", "VOX on/off",
379
                   "Alarm on/off", "FM radio on/off", "Transmit 1750 Hz"]
380

    
381

    
382
def xorarr(data: bytes):
383
    """the communication is obfuscated using this fine mechanism"""
384
    tbl = [22, 108, 20, 230, 46, 145, 13, 64, 33, 53, 213, 64, 19, 3, 233, 128]
385
    ret = b""
386
    idx = 0
387
    for byte in data:
388
        ret += bytes([byte ^ tbl[idx]])
389
        idx = (idx+1) % len(tbl)
390
    return ret
391

    
392

    
393
def calculate_crc16_xmodem(data: bytes):
394
    """
395
    if this crc was used for communication to AND from the radio, then it
396
    would be a measure to increase reliability.
397
    but it's only used towards the radio, so it's for further obfuscation
398
    """
399
    poly = 0x1021
400
    crc = 0x0
401
    for byte in data:
402
        crc = crc ^ (byte << 8)
403
        for _ in range(8):
404
            crc = crc << 1
405
            if crc & 0x10000:
406
                crc = (crc ^ poly) & 0xFFFF
407
    return crc & 0xFFFF
408

    
409

    
410
def _send_command(serport, data: bytes):
411
    """Send a command to UV-K5 radio"""
412
    LOG.debug("Sending command (unobfuscated) len=0x%4.4x:\n%s",
413
              len(data), util.hexprint(data))
414

    
415
    crc = calculate_crc16_xmodem(data)
416
    data2 = data + struct.pack("<H", crc)
417

    
418
    command = struct.pack(">HBB", 0xabcd, len(data), 0) + \
419
        xorarr(data2) + \
420
        struct.pack(">H", 0xdcba)
421
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
422
        LOG.debug("Sending command (obfuscated):\n%s", util.hexprint(command))
423
    try:
424
        result = serport.write(command)
425
    except Exception as e:
426
        raise errors.RadioError("Error writing data to radio") from e
427
    return result
428

    
429

    
430
def _receive_reply(serport):
431
    header = serport.read(4)
432
    if len(header) != 4:
433
        LOG.warning("Header short read: [%s] len=%i",
434
                    util.hexprint(header), len(header))
435
        raise errors.RadioError("Header short read")
436
    if header[0] != 0xAB or header[1] != 0xCD or header[3] != 0x00:
437
        LOG.warning("Bad response header: %s len=%i",
438
                    util.hexprint(header), len(header))
439
        raise errors.RadioError("Bad response header")
440

    
441
    cmd = serport.read(int(header[2]))
442
    if len(cmd) != int(header[2]):
443
        LOG.warning("Body short read: [%s] len=%i",
444
                    util.hexprint(cmd), len(cmd))
445
        raise errors.RadioError("Command body short read")
446

    
447
    footer = serport.read(4)
448

    
449
    if len(footer) != 4:
450
        LOG.warning("Footer short read: [%s] len=%i",
451
                    util.hexprint(footer), len(footer))
452
        raise errors.RadioError("Footer short read")
453

    
454
    if footer[2] != 0xDC or footer[3] != 0xBA:
455
        LOG.debug("Reply before bad response footer (obfuscated)"
456
                  "len=0x%4.4x:\n%s", len(cmd), util.hexprint(cmd))
457
        LOG.warning("Bad response footer: %s len=%i",
458
                    util.hexprint(footer), len(footer))
459
        raise errors.RadioError("Bad response footer")
460

    
461
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
462
        LOG.debug("Received reply (obfuscated) len=0x%4.4x:\n%s",
463
                  len(cmd), util.hexprint(cmd))
464

    
465
    cmd2 = xorarr(cmd)
466

    
467
    LOG.debug("Received reply (unobfuscated) len=0x%4.4x:\n%s",
468
              len(cmd2), util.hexprint(cmd2))
469

    
470
    return cmd2
471

    
472

    
473
def _getstring(data: bytes, begin, maxlen):
474
    tmplen = min(maxlen+1, len(data))
475
    ss = [data[i] for i in range(begin, tmplen)]
476
    key = 0
477
    for key, val in enumerate(ss):
478
        if val < ord(' ') or val > ord('~'):
479
            return ''.join(chr(x) for x in ss[0:key])
480
    return ''
481

    
482

    
483
def _sayhello(serport):
484
    hellopacket = b"\x14\x05\x04\x00\x6a\x39\x57\x64"
485

    
486
    tries = 5
487
    while True:
488
        LOG.debug("Sending hello packet")
489
        _send_command(serport, hellopacket)
490
        rep = _receive_reply(serport)
491
        if rep:
492
            break
493
        tries -= 1
494
        if tries == 0:
495
            LOG.warning("Failed to initialise radio")
496
            raise errors.RadioError("Failed to initialize radio")
497
    if rep.startswith(b'\x18\x05'):
498
        raise errors.RadioError("Radio is in programming mode, "
499
                                "restart radio into normal mode")
500
    firmware = _getstring(rep, 4, 24)
501

    
502
    LOG.info("Found firmware: %s", firmware)
503
    return firmware
504

    
505

    
506
def _readmem(serport, offset, length):
507
    LOG.debug("Sending readmem offset=0x%4.4x len=0x%4.4x", offset, length)
508

    
509
    readmem = b"\x1b\x05\x08\x00" + \
510
        struct.pack("<HBB", offset, length, 0) + \
511
        b"\x6a\x39\x57\x64"
512
    _send_command(serport, readmem)
513
    rep = _receive_reply(serport)
514
    if DEBUG_SHOW_MEMORY_ACTIONS:
515
        LOG.debug("readmem Received data len=0x%4.4x:\n%s",
516
                  len(rep), util.hexprint(rep))
517
    return rep[8:]
518

    
519

    
520
def _writemem(serport, data, offset):
521
    LOG.debug("Sending writemem offset=0x%4.4x len=0x%4.4x",
522
              offset, len(data))
523

    
524
    if DEBUG_SHOW_MEMORY_ACTIONS:
525
        LOG.debug("writemem sent data offset=0x%4.4x len=0x%4.4x:\n%s",
526
                  offset, len(data), util.hexprint(data))
527

    
528
    dlen = len(data)
529
    writemem = b"\x1d\x05" + \
530
        struct.pack("<BBHBB", dlen+8, 0, offset, dlen, 1) + \
531
        b"\x6a\x39\x57\x64"+data
532

    
533
    _send_command(serport, writemem)
534
    rep = _receive_reply(serport)
535

    
536
    LOG.debug("writemem Received data: %s len=%i",
537
              util.hexprint(rep), len(rep))
538

    
539
    if (rep[0] == 0x1e and
540
       rep[4] == (offset & 0xff) and
541
       rep[5] == (offset >> 8) & 0xff):
542
        return True
543

    
544
    LOG.warning("Bad data from writemem")
545
    raise errors.RadioError("Bad response to writemem")
546

    
547

    
548
def _resetradio(serport):
549
    resetpacket = b"\xdd\x05\x00\x00"
550
    _send_command(serport, resetpacket)
551

    
552

    
553
def do_download(radio):
554
    """download eeprom from radio"""
555
    serport = radio.pipe
556
    serport.timeout = 0.5
557
    status = chirp_common.Status()
558
    status.cur = 0
559
    status.max = MEM_SIZE
560
    status.msg = "Downloading from radio"
561
    radio.status_fn(status)
562

    
563
    eeprom = b""
564
    f = _sayhello(serport)
565
    if not f:
566
        raise errors.RadioError('Unable to determine firmware version')
567

    
568
    if not radio.k5_approve_firmware(f):
569
        raise errors.RadioError(
570
            'Firmware version is not supported by this driver')
571

    
572
    radio.metadata = {'uvk5_firmware': f}
573

    
574
    addr = 0
575
    while addr < MEM_SIZE:
576
        data = _readmem(serport, addr, MEM_BLOCK)
577
        status.cur = addr
578
        radio.status_fn(status)
579

    
580
        if data and len(data) == MEM_BLOCK:
581
            eeprom += data
582
            addr += MEM_BLOCK
583
        else:
584
            raise errors.RadioError("Memory download incomplete")
585

    
586
    return memmap.MemoryMapBytes(eeprom)
587

    
588

    
589
def do_upload(radio):
590
    """upload configuration to radio eeprom"""
591
    serport = radio.pipe
592
    serport.timeout = 0.5
593
    status = chirp_common.Status()
594
    status.cur = 0
595
    status.msg = "Uploading to radio"
596

    
597
    if radio._upload_calibration:
598
        status.max = MEM_SIZE - radio._cal_start
599
        start_addr = radio._cal_start
600
        stop_addr = MEM_SIZE
601
    else:
602
        status.max = PROG_SIZE
603
        start_addr = 0
604
        stop_addr = PROG_SIZE
605

    
606
    radio.status_fn(status)
607

    
608
    f = _sayhello(serport)
609
    if not f:
610
        raise errors.RadioError('Unable to determine firmware version')
611

    
612
    if not radio.k5_approve_firmware(f):
613
        raise errors.RadioError(
614
            'Firmware version is not supported by this driver')
615
    LOG.info('Uploading image from firmware %r to radio with %r',
616
             radio.metadata.get('uvk5_firmware', 'unknown'), f)
617
    addr = start_addr
618
    while addr < stop_addr:
619
        dat = radio.get_mmap()[addr:addr+MEM_BLOCK]
620
        _writemem(serport, dat, addr)
621
        status.cur = addr - start_addr
622
        radio.status_fn(status)
623
        if dat:
624
            addr += MEM_BLOCK
625
        else:
626
            raise errors.RadioError("Memory upload incomplete")
627
    status.msg = "Uploaded OK"
628

    
629
    _resetradio(serport)
630

    
631
    return True
632

    
633

    
634
def _find_band(nolimits, hz):
635
    mhz = hz/1000000.0
636
    if nolimits:
637
        B = BANDS_NOLIMITS
638
    else:
639
        B = BANDS
640

    
641
    # currently the hacked firmware sets band=1 below 50 MHz
642
    if nolimits and mhz < 50.0:
643
        return 1
644

    
645
    for a in B:
646
        if mhz >= B[a][0] and mhz <= B[a][1]:
647
            return a
648
    return False
649

    
650

    
651
class UVK5RadioBase(chirp_common.CloneModeRadio):
652
    """Quansheng UV-K5"""
653
    VENDOR = "Quansheng"
654
    MODEL = "UV-K5"
655
    BAUD_RATE = 38400
656
    NEEDS_COMPAT_SERIAL = False
657
    _cal_start = 0
658
    _expanded_limits = False
659
    _upload_calibration = False
660
    _pttid_list = ["off", "BOT", "EOT", "BOTH"]
661
    _steps = [1.0, 2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 8.33]
662

    
663
    @classmethod
664
    def k5_approve_firmware(cls, firmware):
665
        # All subclasses must implement this
666
        raise NotImplementedError()
667

    
668
    @classmethod
669
    def detect_from_serial(cls, pipe):
670
        firmware = _sayhello(pipe)
671
        print(UVK5Radio)
672
        print(cls.DETECTED_MODELS)
673
        for rclass in [UVK5Radio] + (cls.DETECTED_MODELS or []):
674
            if rclass.k5_approve_firmware(firmware):
675
                return rclass
676
        raise errors.RadioError('Firmware %r not supported' % firmware)
677

    
678
    def get_prompts(x=None):
679
        rp = chirp_common.RadioPrompts()
680
        rp.experimental = _(
681
            'This is an experimental driver for the Quansheng UV-K5. '
682
            'It may harm your radio, or worse. Use at your own risk.\n\n'
683
            'Before attempting to do any changes please download '
684
            'the memory image from the radio with chirp '
685
            'and keep it. This can be later used to recover the '
686
            'original settings. \n\n'
687
            'some details are not yet implemented')
688
        rp.pre_download = _(
689
            "1. Turn radio on.\n"
690
            "2. Connect cable to mic/spkr connector.\n"
691
            "3. Make sure connector is firmly connected.\n"
692
            "4. Click OK to download image from device.\n\n"
693
            "It will may not work if you turn on the radio "
694
            "with the cable already attached\n")
695
        rp.pre_upload = _(
696
            "1. Turn radio on.\n"
697
            "2. Connect cable to mic/spkr connector.\n"
698
            "3. Make sure connector is firmly connected.\n"
699
            "4. Click OK to upload the image to device.\n\n"
700
            "It will may not work if you turn on the radio "
701
            "with the cable already attached")
702
        return rp
703

    
704
    # Return information about this radio's features, including
705
    # how many memories it has, what bands it supports, etc
706
    def get_features(self):
707
        rf = chirp_common.RadioFeatures()
708
        rf.has_bank = False
709
        rf.valid_dtcs_codes = DTCS_CODES
710
        rf.has_rx_dtcs = True
711
        rf.has_ctone = True
712
        rf.has_settings = True
713
        rf.has_comment = False
714
        rf.valid_name_length = 10
715
        rf.valid_power_levels = UVK5_POWER_LEVELS
716
        rf.valid_special_chans = list(SPECIALS.keys())
717
        rf.valid_duplexes = ["", "-", "+", "off"]
718

    
719
        # hack so we can input any frequency,
720
        # the 0.1 and 0.01 steps don't work unfortunately
721
        rf.valid_tuning_steps = list(self._steps)
722

    
723
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
724
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
725
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
726

    
727
        rf.valid_characters = chirp_common.CHARSET_ASCII
728
        rf.valid_modes = ["FM", "NFM", "AM", "NAM"]
729
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
730

    
731
        rf.valid_skips = [""]
732

    
733
        # This radio supports memories 1-200, 201-214 are the VFO memories
734
        rf.memory_bounds = (1, 200)
735

    
736
        rf.valid_bands = []
737
        for a in BANDS_NOLIMITS:
738
            rf.valid_bands.append(
739
                    (int(BANDS_NOLIMITS[a][0]*1000000),
740
                     int(BANDS_NOLIMITS[a][1]*1000000)))
741
        return rf
742

    
743
    # Do a download of the radio from the serial port
744
    def sync_in(self):
745
        self._mmap = do_download(self)
746
        self.process_mmap()
747

    
748
    # Do an upload of the radio to the serial port
749
    def sync_out(self):
750
        do_upload(self)
751

    
752
    def _check_firmware_at_load(self):
753
        firmware = self.metadata.get('uvk5_firmware')
754
        if not firmware:
755
            LOG.warning(_('This image is missing firmware information. '
756
                          'It may have been generated with an old or '
757
                          'modified version of CHIRP. It is advised that '
758
                          'you download a fresh image from your radio and '
759
                          'use that going forward for the best safety and '
760
                          'compatibility.'))
761
        elif not self.k5_approve_firmware(self.metadata['uvk5_firmware']):
762
            raise errors.RadioError(
763
                'Image firmware is %r but is not supported by '
764
                'this driver' % firmware)
765

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

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

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

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

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

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

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

    
803
        return msgs
804

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

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

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

    
829
        _mem.rxcodeflag = rxmoval
830
        _mem.txcodeflag = txmoval
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
    def _get_mem_extra(self, mem, _mem):
875
        tmpscn = SCANLIST_LIST[0]
876

    
877
        # We'll also look at the channel attributes if a memory has them
878
        if mem.number <= 200:
879
            _mem3 = self._memobj.channel_attributes[mem.number - 1]
880
            # free memory bit
881
            if _mem3.is_free > 0:
882
                mem.empty = True
883
            # scanlists
884
            if _mem3.is_scanlist1 > 0 and _mem3.is_scanlist2 > 0:
885
                tmpscn = SCANLIST_LIST[3]  # "1+2"
886
            elif _mem3.is_scanlist1 > 0:
887
                tmpscn = SCANLIST_LIST[1]  # "1"
888
            elif _mem3.is_scanlist2 > 0:
889
                tmpscn = SCANLIST_LIST[2]  # "2"
890

    
891
        mem.extra = RadioSettingGroup("Extra", "extra")
892

    
893
        # BCLO
894
        is_bclo = bool(_mem.bclo > 0)
895
        rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(is_bclo))
896
        mem.extra.append(rs)
897

    
898
        # Frequency reverse - whatever that means, don't see it in the manual
899
        is_frev = bool(_mem.freq_reverse > 0)
900
        rs = RadioSetting("frev", "FreqRev", RadioSettingValueBoolean(is_frev))
901
        mem.extra.append(rs)
902

    
903
        # PTTID
904
        try:
905
            pttid = self._pttid_list[_mem.dtmf_pttid]
906
        except IndexError:
907
            pttid = 0
908
        rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
909
            self._pttid_list, pttid))
910
        mem.extra.append(rs)
911

    
912
        # DTMF DECODE
913
        is_dtmf = bool(_mem.dtmf_decode > 0)
914
        rs = RadioSetting("dtmfdecode", _("DTMF decode"),
915
                          RadioSettingValueBoolean(is_dtmf))
916
        mem.extra.append(rs)
917

    
918
        # Scrambler
919
        if _mem.scrambler & 0x0f < len(SCRAMBLER_LIST):
920
            enc = _mem.scrambler & 0x0f
921
        else:
922
            enc = 0
923

    
924
        rs = RadioSetting("scrambler", _("Scrambler"), RadioSettingValueList(
925
            SCRAMBLER_LIST, SCRAMBLER_LIST[enc]))
926
        mem.extra.append(rs)
927

    
928
        rs = RadioSetting("scanlists", _("Scanlists"), RadioSettingValueList(
929
            SCANLIST_LIST, tmpscn))
930
        mem.extra.append(rs)
931

    
932
    def _get_mem_mode(self, _mem):
933
        if _mem.enable_am > 0:
934
            if _mem.bandwidth > 0:
935
                return "NAM"
936
            else:
937
                return "AM"
938
        else:
939
            if _mem.bandwidth > 0:
940
                return "NFM"
941
            else:
942
                return "FM"
943

    
944
    def _get_specials(self):
945
        return dict(SPECIALS)
946

    
947
    # Extract a high-level memory object from the low-level memory map
948
    # This is called to populate a memory in the UI
949
    def get_memory(self, number2):
950

    
951
        mem = chirp_common.Memory()
952

    
953
        if isinstance(number2, str):
954
            number = self._get_specials()[number2]
955
            mem.extd_number = number2
956
        else:
957
            number = number2 - 1
958

    
959
        mem.number = number + 1
960

    
961
        _mem = self._memobj.channel[number]
962

    
963
        # We'll consider any blank (i.e. 0 MHz frequency) to be empty
964
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
965
            mem.empty = True
966

    
967
        self._get_mem_extra(mem, _mem)
968

    
969
        if mem.empty:
970
            return mem
971

    
972
        if number > 199:
973
            mem.immutable = ["name", "scanlists"]
974
        else:
975
            _mem2 = self._memobj.channelname[number]
976
            for char in _mem2.name:
977
                if str(char) == "\xFF" or str(char) == "\x00":
978
                    break
979
                mem.name += str(char)
980
            mem.name = mem.name.rstrip()
981

    
982
        # Convert your low-level frequency to Hertz
983
        mem.freq = int(_mem.freq)*10
984
        mem.offset = int(_mem.offset)*10
985

    
986
        if (mem.offset == 0):
987
            mem.duplex = ''
988
        else:
989
            if _mem.shift == FLAGS1_OFFSET_MINUS:
990
                if _mem.freq == _mem.offset:
991
                    # fake tx disable by setting tx to 0 MHz
992
                    mem.duplex = 'off'
993
                    mem.offset = 0
994
                else:
995
                    mem.duplex = '-'
996
            elif _mem.shift == FLAGS1_OFFSET_PLUS:
997
                mem.duplex = '+'
998
            else:
999
                mem.duplex = ''
1000

    
1001
        # tone data
1002
        self._get_tone(mem, _mem)
1003

    
1004
        mem.mode = self._get_mem_mode(_mem)
1005

    
1006
        # tuning step
1007
        try:
1008
            mem.tuning_step = self._steps[_mem.step]
1009
        except IndexError:
1010
            mem.tuning_step = 2.5
1011

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

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

    
1026
        return mem
1027

    
1028
    def set_settings(self, settings):
1029
        _mem = self._memobj
1030
        for element in settings:
1031
            if not isinstance(element, RadioSetting):
1032
                self.set_settings(element)
1033
                continue
1034

    
1035
            # basic settings
1036

    
1037
            # call channel
1038
            if element.get_name() == "call_channel":
1039
                _mem.call_channel = int(element.value)-1
1040

    
1041
            # squelch
1042
            if element.get_name() == "squelch":
1043
                _mem.squelch = int(element.value)
1044
            # TOT
1045
            if element.get_name() == "tot":
1046
                _mem.max_talk_time = int(element.value)
1047

    
1048
            # NOAA autoscan
1049
            if element.get_name() == "noaa_autoscan":
1050
                _mem.noaa_autoscan = element.value and 1 or 0
1051

    
1052
            # VOX switch
1053
            if element.get_name() == "vox_switch":
1054
                _mem.vox_switch = element.value and 1 or 0
1055

    
1056
            # vox level
1057
            if element.get_name() == "vox_level":
1058
                _mem.vox_level = int(element.value)-1
1059

    
1060
            # mic gain
1061
            if element.get_name() == "mic_gain":
1062
                _mem.mic_gain = int(element.value)
1063

    
1064
            # Channel display mode
1065
            if element.get_name() == "channel_display_mode":
1066
                _mem.channel_display_mode = CHANNELDISP_LIST.index(
1067
                    str(element.value))
1068

    
1069
            # Crossband receiving/transmitting
1070
            if element.get_name() == "crossband":
1071
                _mem.crossband = CROSSBAND_LIST.index(str(element.value))
1072

    
1073
            # Battery Save
1074
            if element.get_name() == "battery_save":
1075
                _mem.battery_save = BATSAVE_LIST.index(str(element.value))
1076
            # Dual Watch
1077
            if element.get_name() == "dualwatch":
1078
                _mem.dual_watch = DUALWATCH_LIST.index(str(element.value))
1079

    
1080
            # Backlight auto mode
1081
            if element.get_name() == "backlight_auto_mode":
1082
                _mem.backlight_auto_mode = \
1083
                        BACKLIGHT_LIST.index(str(element.value))
1084

    
1085
            # Tail tone elimination
1086
            if element.get_name() == "tail_note_elimination":
1087
                _mem.tail_note_elimination = element.value and 1 or 0
1088

    
1089
            # VFO Open
1090
            if element.get_name() == "vfo_open":
1091
                _mem.vfo_open = element.value and 1 or 0
1092

    
1093
            # Beep control
1094
            if element.get_name() == "beep_control":
1095
                _mem.beep_control = element.value and 1 or 0
1096

    
1097
            # Scan resume mode
1098
            if element.get_name() == "scan_resume_mode":
1099
                _mem.scan_resume_mode = SCANRESUME_LIST.index(
1100
                    str(element.value))
1101

    
1102
            # Keypad lock
1103
            if element.get_name() == "key_lock":
1104
                _mem.key_lock = element.value and 1 or 0
1105

    
1106
            # Auto keypad lock
1107
            if element.get_name() == "auto_keypad_lock":
1108
                _mem.auto_keypad_lock = element.value and 1 or 0
1109

    
1110
            # Power on display mode
1111
            if element.get_name() == "welcome_mode":
1112
                _mem.power_on_dispmode = WELCOME_LIST.index(str(element.value))
1113

    
1114
            # Keypad Tone
1115
            if element.get_name() == "keypad_tone":
1116
                _mem.keypad_tone = KEYPADTONE_LIST.index(str(element.value))
1117

    
1118
            # Language
1119
            if element.get_name() == "language":
1120
                _mem.language = LANGUAGE_LIST.index(str(element.value))
1121

    
1122
            # Alarm mode
1123
            if element.get_name() == "alarm_mode":
1124
                _mem.alarm_mode = ALARMMODE_LIST.index(str(element.value))
1125

    
1126
            # Reminding of end of talk
1127
            if element.get_name() == "reminding_of_end_talk":
1128
                _mem.reminding_of_end_talk = REMENDOFTALK_LIST.index(
1129
                    str(element.value))
1130

    
1131
            # Repeater tail tone elimination
1132
            if element.get_name() == "repeater_tail_elimination":
1133
                _mem.repeater_tail_elimination = RTE_LIST.index(
1134
                    str(element.value))
1135

    
1136
            # Logo string 1
1137
            if element.get_name() == "logo1":
1138
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
1139
                _mem.logo_line1 = b[0:12]+"\x00\xff\xff\xff"
1140

    
1141
            # Logo string 2
1142
            if element.get_name() == "logo2":
1143
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
1144
                _mem.logo_line2 = b[0:12]+"\x00\xff\xff\xff"
1145

    
1146
            # unlock settings
1147

    
1148
            # FLOCK
1149
            if element.get_name() == "flock":
1150
                _mem.lock.flock = FLOCK_LIST.index(str(element.value))
1151

    
1152
            # 350TX
1153
            if element.get_name() == "tx350":
1154
                _mem.lock.tx350 = element.value and 1 or 0
1155

    
1156
            # 200TX
1157
            if element.get_name() == "tx200":
1158
                _mem.lock.tx200 = element.value and 1 or 0
1159

    
1160
            # 500TX
1161
            if element.get_name() == "tx500":
1162
                _mem.lock.tx500 = element.value and 1 or 0
1163

    
1164
            # 350EN
1165
            if element.get_name() == "en350":
1166
                _mem.lock.en350 = element.value and 1 or 0
1167

    
1168
            # SCREN
1169
            if element.get_name() == "enscramble":
1170
                _mem.lock.enscramble = element.value and 1 or 0
1171

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

    
1176
            # fm radio
1177
            for i in range(1, 21):
1178
                freqname = "FM_" + str(i)
1179
                if element.get_name() == freqname:
1180
                    val = str(element.value).strip()
1181
                    try:
1182
                        val2 = int(float(val)*10)
1183
                    except Exception:
1184
                        val2 = 0xffff
1185

    
1186
                    if val2 < FMMIN*10 or val2 > FMMAX*10:
1187
                        val2 = 0xffff
1188
#                        raise errors.InvalidValueError(
1189
#                                "FM radio frequency should be a value "
1190
#                                "in the range %.1f - %.1f" % (FMMIN , FMMAX))
1191
                    _mem.fmfreq[i-1] = val2
1192

    
1193
            # dtmf settings
1194
            if element.get_name() == "dtmf_side_tone":
1195
                _mem.dtmf_settings.side_tone = \
1196
                        element.value and 1 or 0
1197

    
1198
            if element.get_name() == "dtmf_separate_code":
1199
                _mem.dtmf_settings.separate_code = str(element.value)
1200

    
1201
            if element.get_name() == "dtmf_group_call_code":
1202
                _mem.dtmf_settings.group_call_code = element.value
1203

    
1204
            if element.get_name() == "dtmf_decode_response":
1205
                _mem.dtmf_settings.decode_response = \
1206
                        DTMF_DECODE_RESPONSE_LIST.index(str(element.value))
1207

    
1208
            if element.get_name() == "dtmf_auto_reset_time":
1209
                _mem.dtmf_settings.auto_reset_time = \
1210
                        int(int(element.value)/10)
1211

    
1212
            if element.get_name() == "dtmf_preload_time":
1213
                _mem.dtmf_settings.preload_time = \
1214
                        int(int(element.value)/10)
1215

    
1216
            if element.get_name() == "dtmf_first_code_persist_time":
1217
                _mem.dtmf_settings.first_code_persist_time = \
1218
                        int(int(element.value)/10)
1219

    
1220
            if element.get_name() == "dtmf_hash_persist_time":
1221
                _mem.dtmf_settings.hash_persist_time = \
1222
                        int(int(element.value)/10)
1223

    
1224
            if element.get_name() == "dtmf_code_persist_time":
1225
                _mem.dtmf_settings.code_persist_time = \
1226
                        int(int(element.value)/10)
1227

    
1228
            if element.get_name() == "dtmf_code_interval_time":
1229
                _mem.dtmf_settings.code_interval_time = \
1230
                        int(int(element.value)/10)
1231

    
1232
            if element.get_name() == "dtmf_permit_remote_kill":
1233
                _mem.dtmf_settings.permit_remote_kill = \
1234
                        element.value and 1 or 0
1235

    
1236
            if element.get_name() == "dtmf_dtmf_local_code":
1237
                k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*3
1238
                _mem.dtmf_settings_numbers.dtmf_local_code = k[0:3]
1239

    
1240
            if element.get_name() == "dtmf_dtmf_up_code":
1241
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*16
1242
                _mem.dtmf_settings_numbers.dtmf_up_code = k[0:16]
1243

    
1244
            if element.get_name() == "dtmf_dtmf_down_code":
1245
                k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*16
1246
                _mem.dtmf_settings_numbers.dtmf_down_code = k[0:16]
1247

    
1248
            if element.get_name() == "dtmf_kill_code":
1249
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*5
1250
                _mem.dtmf_settings_numbers.kill_code = k[0:5]
1251

    
1252
            if element.get_name() == "dtmf_revive_code":
1253
                k = str(element.value).strip("\x20\xff\x00") + "\x00"*5
1254
                _mem.dtmf_settings_numbers.revive_code = k[0:5]
1255

    
1256
            # dtmf contacts
1257
            for i in range(1, 17):
1258
                varname = "DTMF_" + str(i)
1259
                if element.get_name() == varname:
1260
                    k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*8
1261
                    _mem.dtmfcontact[i-1].name = k[0:8]
1262

    
1263
                varnumname = "DTMFNUM_" + str(i)
1264
                if element.get_name() == varnumname:
1265
                    k = str(element.value).rstrip("\x20\xff\x00") + "\xff"*3
1266
                    _mem.dtmfcontact[i-1].number = k[0:3]
1267

    
1268
            # scanlist stuff
1269
            if element.get_name() == "scanlist_default":
1270
                val = (int(element.value) == 2) and 1 or 0
1271
                _mem.scanlist_default = val
1272

    
1273
            if element.get_name() == "scanlist1_priority_scan":
1274
                _mem.scanlist1_priority_scan = \
1275
                        element.value and 1 or 0
1276

    
1277
            if element.get_name() == "scanlist2_priority_scan":
1278
                _mem.scanlist2_priority_scan = \
1279
                        element.value and 1 or 0
1280

    
1281
            if element.get_name() == "scanlist1_priority_ch1" or \
1282
                    element.get_name() == "scanlist1_priority_ch2" or \
1283
                    element.get_name() == "scanlist2_priority_ch1" or \
1284
                    element.get_name() == "scanlist2_priority_ch2":
1285

    
1286
                val = int(element.value)
1287

    
1288
                if val > 200 or val < 1:
1289
                    val = 0xff
1290
                else:
1291
                    val -= 1
1292

    
1293
                if element.get_name() == "scanlist1_priority_ch1":
1294
                    _mem.scanlist1_priority_ch1 = val
1295
                if element.get_name() == "scanlist1_priority_ch2":
1296
                    _mem.scanlist1_priority_ch2 = val
1297
                if element.get_name() == "scanlist2_priority_ch1":
1298
                    _mem.scanlist2_priority_ch1 = val
1299
                if element.get_name() == "scanlist2_priority_ch2":
1300
                    _mem.scanlist2_priority_ch2 = val
1301

    
1302
            if element.get_name() == "key1_shortpress_action":
1303
                _mem.key1_shortpress_action = KEYACTIONS_LIST.index(
1304
                        str(element.value))
1305

    
1306
            if element.get_name() == "key1_longpress_action":
1307
                _mem.key1_longpress_action = KEYACTIONS_LIST.index(
1308
                        str(element.value))
1309

    
1310
            if element.get_name() == "key2_shortpress_action":
1311
                _mem.key2_shortpress_action = KEYACTIONS_LIST.index(
1312
                        str(element.value))
1313

    
1314
            if element.get_name() == "key2_longpress_action":
1315
                _mem.key2_longpress_action = KEYACTIONS_LIST.index(
1316
                        str(element.value))
1317

    
1318
            if element.get_name() == "nolimits":
1319
                LOG.warning("User expanded band limits")
1320
                self._expanded_limits = bool(element.value)
1321

    
1322
    def get_settings(self):
1323
        _mem = self._memobj
1324
        basic = RadioSettingGroup("basic", "Basic Settings")
1325
        keya = RadioSettingGroup("keya", "Programmable keys")
1326
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1327
        dtmfc = RadioSettingGroup("dtmfc", "DTMF Contacts")
1328
        scanl = RadioSettingGroup("scn", "Scan Lists")
1329
        unlock = RadioSettingGroup("unlock", "Unlock Settings")
1330
        fmradio = RadioSettingGroup("fmradio", _("FM Radio"))
1331

    
1332
        roinfo = RadioSettingGroup("roinfo", _("Driver information"))
1333

    
1334
        top = RadioSettings(
1335
                basic, keya, dtmf, dtmfc, scanl, unlock, fmradio, roinfo)
1336

    
1337
        # Programmable keys
1338
        tmpval = int(_mem.key1_shortpress_action)
1339
        if tmpval >= len(KEYACTIONS_LIST):
1340
            tmpval = 0
1341
        rs = RadioSetting("key1_shortpress_action", "Side key 1 short press",
1342
                          RadioSettingValueList(
1343
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1344
        keya.append(rs)
1345

    
1346
        tmpval = int(_mem.key1_longpress_action)
1347
        if tmpval >= len(KEYACTIONS_LIST):
1348
            tmpval = 0
1349
        rs = RadioSetting("key1_longpress_action", "Side key 1 long press",
1350
                          RadioSettingValueList(
1351
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1352
        keya.append(rs)
1353

    
1354
        tmpval = int(_mem.key2_shortpress_action)
1355
        if tmpval >= len(KEYACTIONS_LIST):
1356
            tmpval = 0
1357
        rs = RadioSetting("key2_shortpress_action", "Side key 2 short press",
1358
                          RadioSettingValueList(
1359
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1360
        keya.append(rs)
1361

    
1362
        tmpval = int(_mem.key2_longpress_action)
1363
        if tmpval >= len(KEYACTIONS_LIST):
1364
            tmpval = 0
1365
        rs = RadioSetting("key2_longpress_action", "Side key 2 long press",
1366
                          RadioSettingValueList(
1367
                              KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval]))
1368
        keya.append(rs)
1369

    
1370
        # DTMF settings
1371
        tmppr = bool(_mem.dtmf_settings.side_tone > 0)
1372
        rs = RadioSetting(
1373
                "dtmf_side_tone",
1374
                "DTMF Sidetone",
1375
                RadioSettingValueBoolean(tmppr))
1376
        dtmf.append(rs)
1377

    
1378
        tmpval = str(_mem.dtmf_settings.separate_code)
1379
        if tmpval not in DTMF_CODE_CHARS:
1380
            tmpval = '*'
1381
        val = RadioSettingValueString(1, 1, tmpval)
1382
        val.set_charset(DTMF_CODE_CHARS)
1383
        rs = RadioSetting("dtmf_separate_code", "Separate Code", val)
1384
        dtmf.append(rs)
1385

    
1386
        tmpval = str(_mem.dtmf_settings.group_call_code)
1387
        if tmpval not in DTMF_CODE_CHARS:
1388
            tmpval = '#'
1389
        val = RadioSettingValueString(1, 1, tmpval)
1390
        val.set_charset(DTMF_CODE_CHARS)
1391
        rs = RadioSetting("dtmf_group_call_code", "Group Call Code", val)
1392
        dtmf.append(rs)
1393

    
1394
        tmpval = _mem.dtmf_settings.decode_response
1395
        if tmpval >= len(DTMF_DECODE_RESPONSE_LIST):
1396
            tmpval = 0
1397
        rs = RadioSetting("dtmf_decode_response", "Decode Response",
1398
                          RadioSettingValueList(
1399
                              DTMF_DECODE_RESPONSE_LIST,
1400
                              DTMF_DECODE_RESPONSE_LIST[tmpval]))
1401
        dtmf.append(rs)
1402

    
1403
        tmpval = _mem.dtmf_settings.auto_reset_time
1404
        if tmpval > 60 or tmpval < 5:
1405
            tmpval = 5
1406
        rs = RadioSetting("dtmf_auto_reset_time",
1407
                          "Auto reset time (s)",
1408
                          RadioSettingValueInteger(5, 60, tmpval))
1409
        dtmf.append(rs)
1410

    
1411
        tmpval = int(_mem.dtmf_settings.preload_time)
1412
        if tmpval > 100 or tmpval < 3:
1413
            tmpval = 30
1414
        tmpval *= 10
1415
        rs = RadioSetting("dtmf_preload_time",
1416
                          "Pre-load time (ms)",
1417
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1418
        dtmf.append(rs)
1419

    
1420
        tmpval = int(_mem.dtmf_settings.first_code_persist_time)
1421
        if tmpval > 100 or tmpval < 3:
1422
            tmpval = 30
1423
        tmpval *= 10
1424
        rs = RadioSetting("dtmf_first_code_persist_time",
1425
                          "First code persist time (ms)",
1426
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1427
        dtmf.append(rs)
1428

    
1429
        tmpval = int(_mem.dtmf_settings.hash_persist_time)
1430
        if tmpval > 100 or tmpval < 3:
1431
            tmpval = 30
1432
        tmpval *= 10
1433
        rs = RadioSetting("dtmf_hash_persist_time",
1434
                          "#/* persist time (ms)",
1435
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1436
        dtmf.append(rs)
1437

    
1438
        tmpval = int(_mem.dtmf_settings.code_persist_time)
1439
        if tmpval > 100 or tmpval < 3:
1440
            tmpval = 30
1441
        tmpval *= 10
1442
        rs = RadioSetting("dtmf_code_persist_time",
1443
                          "Code persist time (ms)",
1444
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1445
        dtmf.append(rs)
1446

    
1447
        tmpval = int(_mem.dtmf_settings.code_interval_time)
1448
        if tmpval > 100 or tmpval < 3:
1449
            tmpval = 30
1450
        tmpval *= 10
1451
        rs = RadioSetting("dtmf_code_interval_time",
1452
                          "Code interval time (ms)",
1453
                          RadioSettingValueInteger(30, 1000, tmpval, 10))
1454
        dtmf.append(rs)
1455

    
1456
        tmpval = bool(_mem.dtmf_settings.permit_remote_kill > 0)
1457
        rs = RadioSetting(
1458
                "dtmf_permit_remote_kill",
1459
                "Permit remote kill",
1460
                RadioSettingValueBoolean(tmpval))
1461
        dtmf.append(rs)
1462

    
1463
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_local_code).upper().strip(
1464
                "\x00\xff\x20")
1465
        for i in tmpval:
1466
            if i in DTMF_CHARS_ID:
1467
                continue
1468
            else:
1469
                tmpval = "103"
1470
                break
1471
        val = RadioSettingValueString(3, 3, tmpval)
1472
        val.set_charset(DTMF_CHARS_ID)
1473
        rs = RadioSetting("dtmf_dtmf_local_code",
1474
                          "Local code (3 chars 0-9 ABCD)", val)
1475
        dtmf.append(rs)
1476

    
1477
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_up_code).upper().strip(
1478
                "\x00\xff\x20")
1479
        for i in tmpval:
1480
            if i in DTMF_CHARS_UPDOWN or i == "":
1481
                continue
1482
            else:
1483
                tmpval = "123"
1484
                break
1485
        val = RadioSettingValueString(1, 16, tmpval)
1486
        val.set_charset(DTMF_CHARS_UPDOWN)
1487
        rs = RadioSetting("dtmf_dtmf_up_code",
1488
                          "Up code (1-16 chars 0-9 ABCD*#)", val)
1489
        dtmf.append(rs)
1490

    
1491
        tmpval = str(_mem.dtmf_settings_numbers.dtmf_down_code).upper().strip(
1492
                "\x00\xff\x20")
1493
        for i in tmpval:
1494
            if i in DTMF_CHARS_UPDOWN:
1495
                continue
1496
            else:
1497
                tmpval = "456"
1498
                break
1499
        val = RadioSettingValueString(1, 16, tmpval)
1500
        val.set_charset(DTMF_CHARS_UPDOWN)
1501
        rs = RadioSetting("dtmf_dtmf_down_code",
1502
                          "Down code (1-16 chars 0-9 ABCD*#)", val)
1503
        dtmf.append(rs)
1504

    
1505
        tmpval = str(_mem.dtmf_settings_numbers.kill_code).upper().strip(
1506
                "\x00\xff\x20")
1507
        for i in tmpval:
1508
            if i in DTMF_CHARS_KILL:
1509
                continue
1510
            else:
1511
                tmpval = "77777"
1512
                break
1513
        if not len(tmpval) == 5:
1514
            tmpval = "77777"
1515
        val = RadioSettingValueString(5, 5, tmpval)
1516
        val.set_charset(DTMF_CHARS_KILL)
1517
        rs = RadioSetting("dtmf_kill_code",
1518
                          "Kill code (5 chars 0-9 ABCD)", val)
1519
        dtmf.append(rs)
1520

    
1521
        tmpval = str(_mem.dtmf_settings_numbers.revive_code).upper().strip(
1522
                "\x00\xff\x20")
1523
        for i in tmpval:
1524
            if i in DTMF_CHARS_KILL:
1525
                continue
1526
            else:
1527
                tmpval = "88888"
1528
                break
1529
        if not len(tmpval) == 5:
1530
            tmpval = "88888"
1531
        val = RadioSettingValueString(5, 5, tmpval)
1532
        val.set_charset(DTMF_CHARS_KILL)
1533
        rs = RadioSetting("dtmf_revive_code",
1534
                          "Revive code (5 chars 0-9 ABCD)", val)
1535
        dtmf.append(rs)
1536

    
1537
        for i in range(1, 17):
1538
            varname = "DTMF_"+str(i)
1539
            varnumname = "DTMFNUM_"+str(i)
1540
            vardescr = "DTMF Contact "+str(i)+" name"
1541
            varinumdescr = "DTMF Contact "+str(i)+" number"
1542

    
1543
            cntn = str(_mem.dtmfcontact[i-1].name).strip("\x20\x00\xff")
1544
            cntnum = str(_mem.dtmfcontact[i-1].number).strip("\x20\x00\xff")
1545

    
1546
            val = RadioSettingValueString(0, 8, cntn)
1547
            rs = RadioSetting(varname, vardescr, val)
1548
            dtmfc.append(rs)
1549

    
1550
            val = RadioSettingValueString(0, 3, cntnum)
1551
            val.set_charset(DTMF_CHARS)
1552
            rs = RadioSetting(varnumname, varinumdescr, val)
1553
            dtmfc.append(rs)
1554
            rs.set_doc("DTMF Contacts are 3 codes (valid: 0-9 * # ABCD), "
1555
                       "or an empty string")
1556

    
1557
        # scanlists
1558
        if _mem.scanlist_default == 1:
1559
            tmpsc = 2
1560
        else:
1561
            tmpsc = 1
1562
        rs = RadioSetting("scanlist_default",
1563
                          "Default scanlist",
1564
                          RadioSettingValueInteger(1, 2, tmpsc))
1565
        scanl.append(rs)
1566

    
1567
        tmppr = bool((_mem.scanlist1_priority_scan & 1) > 0)
1568
        rs = RadioSetting(
1569
                "scanlist1_priority_scan",
1570
                "Scanlist 1 priority channel scan",
1571
                RadioSettingValueBoolean(tmppr))
1572
        scanl.append(rs)
1573

    
1574
        tmpch = _mem.scanlist1_priority_ch1 + 1
1575
        if tmpch > 200:
1576
            tmpch = 0
1577
        rs = RadioSetting("scanlist1_priority_ch1",
1578
                          "Scanlist 1 priority channel 1 (0 - off)",
1579
                          RadioSettingValueInteger(0, 200, tmpch))
1580
        scanl.append(rs)
1581

    
1582
        tmpch = _mem.scanlist1_priority_ch2 + 1
1583
        if tmpch > 200:
1584
            tmpch = 0
1585
        rs = RadioSetting("scanlist1_priority_ch2",
1586
                          "Scanlist 1 priority channel 2 (0 - off)",
1587
                          RadioSettingValueInteger(0, 200, tmpch))
1588
        scanl.append(rs)
1589

    
1590
        tmppr = bool((_mem.scanlist2_priority_scan & 1) > 0)
1591
        rs = RadioSetting(
1592
                "scanlist2_priority_scan",
1593
                "Scanlist 2 priority channel scan",
1594
                RadioSettingValueBoolean(tmppr))
1595
        scanl.append(rs)
1596

    
1597
        tmpch = _mem.scanlist2_priority_ch1 + 1
1598
        if tmpch > 200:
1599
            tmpch = 0
1600
        rs = RadioSetting("scanlist2_priority_ch1",
1601
                          "Scanlist 2 priority channel 1 (0 - off)",
1602
                          RadioSettingValueInteger(0, 200, tmpch))
1603
        scanl.append(rs)
1604

    
1605
        tmpch = _mem.scanlist2_priority_ch2 + 1
1606
        if tmpch > 200:
1607
            tmpch = 0
1608
        rs = RadioSetting("scanlist2_priority_ch2",
1609
                          "Scanlist 2 priority channel 2 (0 - off)",
1610
                          RadioSettingValueInteger(0, 200, tmpch))
1611
        scanl.append(rs)
1612

    
1613
        # basic settings
1614

    
1615
        # call channel
1616
        tmpc = _mem.call_channel+1
1617
        if tmpc > 200:
1618
            tmpc = 1
1619
        rs = RadioSetting("call_channel", "One key call channel",
1620
                          RadioSettingValueInteger(1, 200, tmpc))
1621
        basic.append(rs)
1622

    
1623
        # squelch
1624
        tmpsq = _mem.squelch
1625
        if tmpsq > 9:
1626
            tmpsq = 1
1627
        rs = RadioSetting("squelch", "Squelch",
1628
                          RadioSettingValueInteger(0, 9, tmpsq))
1629
        basic.append(rs)
1630

    
1631
        # TOT
1632
        tmptot = _mem.max_talk_time
1633
        if tmptot > 10:
1634
            tmptot = 10
1635
        rs = RadioSetting(
1636
                "tot",
1637
                "Max talk time [min]",
1638
                RadioSettingValueInteger(0, 10, tmptot))
1639
        basic.append(rs)
1640

    
1641
        # NOAA autoscan
1642
        rs = RadioSetting(
1643
                "noaa_autoscan",
1644
                "NOAA Autoscan", RadioSettingValueBoolean(
1645
                    bool(_mem.noaa_autoscan > 0)))
1646
        basic.append(rs)
1647

    
1648
        # VOX switch
1649
        rs = RadioSetting(
1650
                "vox_switch",
1651
                "VOX enabled", RadioSettingValueBoolean(
1652
                    bool(_mem.vox_switch > 0)))
1653
        basic.append(rs)
1654

    
1655
        # VOX Level
1656
        tmpvox = _mem.vox_level+1
1657
        if tmpvox > 10:
1658
            tmpvox = 10
1659
        rs = RadioSetting("vox_level", "VOX Level",
1660
                          RadioSettingValueInteger(1, 10, tmpvox))
1661
        basic.append(rs)
1662

    
1663
        # Mic gain
1664
        tmpmicgain = _mem.mic_gain
1665
        if tmpmicgain > 4:
1666
            tmpmicgain = 4
1667
        rs = RadioSetting("mic_gain", "Mic Gain",
1668
                          RadioSettingValueInteger(0, 4, tmpmicgain))
1669
        basic.append(rs)
1670

    
1671
        # Channel display mode
1672
        tmpchdispmode = _mem.channel_display_mode
1673
        if tmpchdispmode >= len(CHANNELDISP_LIST):
1674
            tmpchdispmode = 0
1675
        rs = RadioSetting(
1676
                "channel_display_mode",
1677
                "Channel display mode",
1678
                RadioSettingValueList(
1679
                    CHANNELDISP_LIST,
1680
                    CHANNELDISP_LIST[tmpchdispmode]))
1681
        basic.append(rs)
1682

    
1683
        # Crossband receiving/transmitting
1684
        tmpcross = _mem.crossband
1685
        if tmpcross >= len(CROSSBAND_LIST):
1686
            tmpcross = 0
1687
        rs = RadioSetting(
1688
                "crossband",
1689
                "Cross-band receiving/transmitting",
1690
                RadioSettingValueList(
1691
                    CROSSBAND_LIST,
1692
                    CROSSBAND_LIST[tmpcross]))
1693
        basic.append(rs)
1694

    
1695
        # Battery save
1696
        tmpbatsave = _mem.battery_save
1697
        if tmpbatsave >= len(BATSAVE_LIST):
1698
            tmpbatsave = BATSAVE_LIST.index("1:4")
1699
        rs = RadioSetting(
1700
                "battery_save",
1701
                "Battery Save",
1702
                RadioSettingValueList(
1703
                    BATSAVE_LIST,
1704
                    BATSAVE_LIST[tmpbatsave]))
1705
        basic.append(rs)
1706

    
1707
        # Dual watch
1708
        tmpdual = _mem.dual_watch
1709
        if tmpdual >= len(DUALWATCH_LIST):
1710
            tmpdual = 0
1711
        rs = RadioSetting("dualwatch", "Dual Watch", RadioSettingValueList(
1712
            DUALWATCH_LIST, DUALWATCH_LIST[tmpdual]))
1713
        basic.append(rs)
1714

    
1715
        # Backlight auto mode
1716
        tmpback = _mem.backlight_auto_mode
1717
        if tmpback >= len(BACKLIGHT_LIST):
1718
            tmpback = 0
1719
        rs = RadioSetting("backlight_auto_mode",
1720
                          "Backlight auto mode",
1721
                          RadioSettingValueList(
1722
                              BACKLIGHT_LIST,
1723
                              BACKLIGHT_LIST[tmpback]))
1724
        basic.append(rs)
1725

    
1726
        # Tail tone elimination
1727
        rs = RadioSetting(
1728
                "tail_note_elimination",
1729
                "Tail tone elimination",
1730
                RadioSettingValueBoolean(
1731
                    bool(_mem.tail_note_elimination > 0)))
1732
        basic.append(rs)
1733

    
1734
        # VFO open
1735
        rs = RadioSetting("vfo_open", "VFO open",
1736
                          RadioSettingValueBoolean(bool(_mem.vfo_open > 0)))
1737
        basic.append(rs)
1738

    
1739
        # Beep control
1740
        rs = RadioSetting(
1741
                "beep_control",
1742
                "Beep control",
1743
                RadioSettingValueBoolean(bool(_mem.beep_control > 0)))
1744
        basic.append(rs)
1745

    
1746
        # Scan resume mode
1747
        tmpscanres = _mem.scan_resume_mode
1748
        if tmpscanres >= len(SCANRESUME_LIST):
1749
            tmpscanres = 0
1750
        rs = RadioSetting(
1751
                "scan_resume_mode",
1752
                "Scan resume mode",
1753
                RadioSettingValueList(
1754
                    SCANRESUME_LIST,
1755
                    SCANRESUME_LIST[tmpscanres]))
1756
        basic.append(rs)
1757

    
1758
        # Keypad locked
1759
        rs = RadioSetting(
1760
                "key_lock",
1761
                "Keypad lock",
1762
                RadioSettingValueBoolean(bool(_mem.key_lock > 0)))
1763
        basic.append(rs)
1764

    
1765
        # Auto keypad lock
1766
        rs = RadioSetting(
1767
                "auto_keypad_lock",
1768
                "Auto keypad lock",
1769
                RadioSettingValueBoolean(bool(_mem.auto_keypad_lock > 0)))
1770
        basic.append(rs)
1771

    
1772
        # Power on display mode
1773
        tmpdispmode = _mem.power_on_dispmode
1774
        if tmpdispmode >= len(WELCOME_LIST):
1775
            tmpdispmode = 0
1776
        rs = RadioSetting(
1777
                "welcome_mode",
1778
                "Power on display mode",
1779
                RadioSettingValueList(
1780
                    WELCOME_LIST,
1781
                    WELCOME_LIST[tmpdispmode]))
1782
        basic.append(rs)
1783

    
1784
        # Keypad Tone
1785
        tmpkeypadtone = _mem.keypad_tone
1786
        if tmpkeypadtone >= len(KEYPADTONE_LIST):
1787
            tmpkeypadtone = 0
1788
        rs = RadioSetting("keypad_tone", "Keypad tone", RadioSettingValueList(
1789
            KEYPADTONE_LIST, KEYPADTONE_LIST[tmpkeypadtone]))
1790
        basic.append(rs)
1791

    
1792
        # Language
1793
        tmplanguage = _mem.language
1794
        if tmplanguage >= len(LANGUAGE_LIST):
1795
            tmplanguage = 0
1796
        rs = RadioSetting("language", "Language", RadioSettingValueList(
1797
            LANGUAGE_LIST, LANGUAGE_LIST[tmplanguage]))
1798
        basic.append(rs)
1799

    
1800
        # Alarm mode
1801
        tmpalarmmode = _mem.alarm_mode
1802
        if tmpalarmmode >= len(ALARMMODE_LIST):
1803
            tmpalarmmode = 0
1804
        rs = RadioSetting("alarm_mode", "Alarm mode", RadioSettingValueList(
1805
            ALARMMODE_LIST, ALARMMODE_LIST[tmpalarmmode]))
1806
        basic.append(rs)
1807

    
1808
        # Reminding of end of talk
1809
        tmpalarmmode = _mem.reminding_of_end_talk
1810
        if tmpalarmmode >= len(REMENDOFTALK_LIST):
1811
            tmpalarmmode = 0
1812
        rs = RadioSetting(
1813
                "reminding_of_end_talk",
1814
                "Reminding of end of talk",
1815
                RadioSettingValueList(
1816
                    REMENDOFTALK_LIST,
1817
                    REMENDOFTALK_LIST[tmpalarmmode]))
1818
        basic.append(rs)
1819

    
1820
        # Repeater tail tone elimination
1821
        tmprte = _mem.repeater_tail_elimination
1822
        if tmprte >= len(RTE_LIST):
1823
            tmprte = 0
1824
        rs = RadioSetting(
1825
                "repeater_tail_elimination",
1826
                "Repeater tail tone elimination",
1827
                RadioSettingValueList(RTE_LIST, RTE_LIST[tmprte]))
1828
        basic.append(rs)
1829

    
1830
        # Logo string 1
1831
        logo1 = str(_mem.logo_line1).strip("\x20\x00\xff") + "\x00"
1832
        logo1 = _getstring(logo1.encode('ascii', errors='ignore'), 0, 12)
1833
        rs = RadioSetting("logo1", _("Logo string 1 (12 characters)"),
1834
                          RadioSettingValueString(0, 12, logo1))
1835
        basic.append(rs)
1836

    
1837
        # Logo string 2
1838
        logo2 = str(_mem.logo_line2).strip("\x20\x00\xff") + "\x00"
1839
        logo2 = _getstring(logo2.encode('ascii', errors='ignore'), 0, 12)
1840
        rs = RadioSetting("logo2", _("Logo string 2 (12 characters)"),
1841
                          RadioSettingValueString(0, 12, logo2))
1842
        basic.append(rs)
1843

    
1844
        # FM radio
1845
        for i in range(1, 21):
1846
            freqname = "FM_"+str(i)
1847
            fmfreq = _mem.fmfreq[i-1]/10.0
1848
            if fmfreq < FMMIN or fmfreq > FMMAX:
1849
                rs = RadioSetting(freqname, freqname,
1850
                                  RadioSettingValueString(0, 5, ""))
1851
            else:
1852
                rs = RadioSetting(freqname, freqname,
1853
                                  RadioSettingValueString(0, 5, str(fmfreq)))
1854

    
1855
            fmradio.append(rs)
1856

    
1857
        # unlock settings
1858

    
1859
        # F-LOCK
1860
        tmpflock = _mem.lock.flock
1861
        if tmpflock >= len(FLOCK_LIST):
1862
            tmpflock = 0
1863
        rs = RadioSetting(
1864
            "flock", "F-LOCK",
1865
            RadioSettingValueList(FLOCK_LIST, FLOCK_LIST[tmpflock]))
1866
        unlock.append(rs)
1867

    
1868
        # 350TX
1869
        rs = RadioSetting("tx350", "350TX - unlock 350-400 MHz TX",
1870
                          RadioSettingValueBoolean(
1871
                              bool(_mem.lock.tx350 > 0)))
1872
        unlock.append(rs)
1873

    
1874
        # Killed
1875
        rs = RadioSetting("Killed", "KILLED Device was disabled (via DTMF)",
1876
                          RadioSettingValueBoolean(
1877
                              bool(_mem.lock.killed > 0)))
1878
        unlock.append(rs)
1879

    
1880
        # 200TX
1881
        rs = RadioSetting("tx200", "200TX - unlock 174-350 MHz TX",
1882
                          RadioSettingValueBoolean(
1883
                              bool(_mem.lock.tx200 > 0)))
1884
        unlock.append(rs)
1885

    
1886
        # 500TX
1887
        rs = RadioSetting("tx500", "500TX - unlock 500-600 MHz TX",
1888
                          RadioSettingValueBoolean(
1889
                              bool(_mem.lock.tx500 > 0)))
1890
        unlock.append(rs)
1891

    
1892
        # 350EN
1893
        rs = RadioSetting("en350", "350EN - unlock 350-400 MHz RX",
1894
                          RadioSettingValueBoolean(
1895
                              bool(_mem.lock.en350 > 0)))
1896
        unlock.append(rs)
1897

    
1898
        # SCREEN
1899
        rs = RadioSetting("scrambler", "SCREN - scrambler enable",
1900
                          RadioSettingValueBoolean(
1901
                              bool(_mem.lock.enscramble > 0)))
1902
        unlock.append(rs)
1903

    
1904
        # readonly info
1905
        # Firmware
1906
        firmware = self.metadata.get('uvk5_firmware', 'UNKNOWN')
1907

    
1908
        val = RadioSettingValueString(0, 128, firmware)
1909
        val.set_mutable(False)
1910
        rs = RadioSetting("fw_ver", "Firmware Version", val)
1911
        roinfo.append(rs)
1912

    
1913
        # No limits version for hacked firmware
1914
        val = RadioSettingValueBoolean(self._expanded_limits)
1915
        rs = RadioSetting("nolimits", "Limits disabled for modified firmware",
1916
                          val)
1917
        rs.set_warning(_(
1918
            'This should only be enabled if you are using modified firmware '
1919
            'that supports wider frequency coverage. Enabling this will cause '
1920
            'CHIRP not to enforce OEM restrictions and may lead to undefined '
1921
            'or unregulated behavior. Use at your own risk!'),
1922
            safe_value=False)
1923
        roinfo.append(rs)
1924

    
1925
        return top
1926

    
1927
    def _set_mem_mode(self, _mem, mode):
1928
        if mode == "NFM":
1929
            _mem.bandwidth = 1
1930
            _mem.enable_am = 0
1931
        elif mode == "FM":
1932
            _mem.bandwidth = 0
1933
            _mem.enable_am = 0
1934
        elif mode == "NAM":
1935
            _mem.bandwidth = 1
1936
            _mem.enable_am = 1
1937
        elif mode == "AM":
1938
            _mem.bandwidth = 0
1939
            _mem.enable_am = 1
1940

    
1941
    # Store details about a high-level memory to the memory map
1942
    # This is called when a user edits a memory in the UI
1943
    def set_memory(self, mem):
1944
        number = mem.number-1
1945

    
1946
        # Get a low-level memory object mapped to the image
1947
        _mem = self._memobj.channel[number]
1948
        _mem4 = self._memobj
1949
        # empty memory
1950
        if mem.empty:
1951
            _mem.set_raw("\xFF" * 16)
1952
            if number < 200:
1953
                _mem2 = self._memobj.channelname[number]
1954
                _mem2.set_raw("\xFF" * 16)
1955
                _mem4.channel_attributes[number].is_scanlist1 = 0
1956
                _mem4.channel_attributes[number].is_scanlist2 = 0
1957
                # Compander in other models, not supported here
1958
                _mem4.channel_attributes[number].compander = 0
1959
                _mem4.channel_attributes[number].is_free = 1
1960
                _mem4.channel_attributes[number].band = 0x7
1961
            return mem
1962

    
1963
        # clean the channel memory, restore some bits if it was used before
1964
        if _mem.get_raw(asbytes=False)[0] == "\xff":
1965
            # this was an empty memory
1966
            _mem.set_raw("\x00" * 16)
1967
        else:
1968
            # this memory wasn't empty, save some bits that we don't know the
1969
            # meaning of, or that we don't support yet
1970
            prev_0a = _mem.get_raw()[0x0a] & SAVE_MASK_0A
1971
            prev_0b = _mem.get_raw()[0x0b] & SAVE_MASK_0B
1972
            prev_0c = _mem.get_raw()[0x0c] & SAVE_MASK_0C
1973
            prev_0d = _mem.get_raw()[0x0d] & SAVE_MASK_0D
1974
            prev_0e = _mem.get_raw()[0x0e] & SAVE_MASK_0E
1975
            prev_0f = _mem.get_raw()[0x0f] & SAVE_MASK_0F
1976
            _mem.set_raw("\x00" * 10 +
1977
                         chr(prev_0a) + chr(prev_0b) + chr(prev_0c) +
1978
                         chr(prev_0d) + chr(prev_0e) + chr(prev_0f))
1979

    
1980
        if number < 200:
1981
            _mem4.channel_attributes[number].is_scanlist1 = 0
1982
            _mem4.channel_attributes[number].is_scanlist2 = 0
1983
            _mem4.channel_attributes[number].compander = 0
1984
            _mem4.channel_attributes[number].is_free = 1
1985
            _mem4.channel_attributes[number].band = 0x7
1986

    
1987
        # find band
1988
        band = _find_band(self, mem.freq)
1989

    
1990
        self._set_mem_mode(_mem, mem.mode)
1991

    
1992
        # frequency/offset
1993
        _mem.freq = mem.freq/10
1994
        _mem.offset = mem.offset/10
1995

    
1996
        if mem.duplex == "":
1997
            _mem.offset = 0
1998
            _mem.shift = 0
1999
        elif mem.duplex == '-':
2000
            _mem.shift = FLAGS1_OFFSET_MINUS
2001
        elif mem.duplex == '+':
2002
            _mem.shift = FLAGS1_OFFSET_PLUS
2003
        elif mem.duplex == 'off':
2004
            # we fake tx disable by setting the tx freq to 0 MHz
2005
            _mem.shift = FLAGS1_OFFSET_MINUS
2006
            _mem.offset = _mem.freq
2007

    
2008
        # set band
2009
        if number < 200:
2010
            _mem4.channel_attributes[number].is_free = 0
2011
            _mem4.channel_attributes[number].band = band
2012

    
2013
        # channels >200 are the 14 VFO chanells and don't have names
2014
        if number < 200:
2015
            _mem2 = self._memobj.channelname[number]
2016
            tag = mem.name.ljust(10) + "\x00"*6
2017
            _mem2.name = tag  # Store the alpha tag
2018

    
2019
        # tone data
2020
        self._set_tone(mem, _mem)
2021

    
2022
        # step
2023
        _mem.step = self._steps.index(mem.tuning_step)
2024

    
2025
        # tx power
2026
        if str(mem.power) == str(UVK5_POWER_LEVELS[2]):
2027
            _mem.txpower = POWER_HIGH
2028
        elif str(mem.power) == str(UVK5_POWER_LEVELS[1]):
2029
            _mem.txpower = POWER_MEDIUM
2030
        else:
2031
            _mem.txpower = POWER_LOW
2032

    
2033
        for setting in mem.extra:
2034
            sname = setting.get_name()
2035
            svalue = setting.value.get_value()
2036

    
2037
            if sname == "bclo":
2038
                _mem.bclo = svalue and 1 or 0
2039

    
2040
            if sname == "pttid":
2041
                _mem.dtmf_pttid = self._pttid_list.index(svalue)
2042

    
2043
            if sname == "frev":
2044
                _mem.freq_reverse = svalue and 1 or 0
2045

    
2046
            if sname == "dtmfdecode":
2047
                _mem.dtmf_decode = svalue and 1 or 0
2048

    
2049
            if sname == "scrambler":
2050
                _mem.scrambler = (
2051
                    _mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue)
2052

    
2053
            if number < 200 and sname == "scanlists":
2054
                if svalue == "1":
2055
                    _mem4.channel_attributes[number].is_scanlist1 = 1
2056
                    _mem4.channel_attributes[number].is_scanlist2 = 0
2057
                elif svalue == "2":
2058
                    _mem4.channel_attributes[number].is_scanlist1 = 0
2059
                    _mem4.channel_attributes[number].is_scanlist2 = 1
2060
                elif svalue == "1+2":
2061
                    _mem4.channel_attributes[number].is_scanlist1 = 1
2062
                    _mem4.channel_attributes[number].is_scanlist2 = 1
2063
                else:
2064
                    _mem4.channel_attributes[number].is_scanlist1 = 0
2065
                    _mem4.channel_attributes[number].is_scanlist2 = 0
2066

    
2067
        return mem
2068

    
2069

    
2070
@directory.register
2071
class UVK5Radio(UVK5RadioBase):
2072
    @classmethod
2073
    def k5_approve_firmware(cls, firmware):
2074
        approved_prefixes = ('k5_2.01.', 'app_2.01.', '2.01.',
2075
                             '1o11', '4.00.')
2076
        return any(firmware.startswith(x) for x in approved_prefixes)
2077

    
2078

    
2079
@directory.register
2080
class RA79Radio(UVK5Radio):
2081
    """Retevis RA79"""
2082
    VENDOR = "Retevis"
2083
    MODEL = "RA79"
(3-3/4)