Project

General

Profile

New Model #10478 » uvk5.py

uvk5.py chirp driver for UV-K5, version 20230608 - Jacek Lipkowski SQ5BPF, 06/07/2023 03:34 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. Before even attempting
12
# to use it save a memory image from the radio using k5prog:
13
# https://github.com/sq5bpf/k5prog
14
#
15
#
16
# This program is free software: you can redistribute it and/or modify
17
# it under the terms of the GNU General Public License as published by
18
# the Free Software Foundation, either version 2 of the License, or
19
# (at your option) any later version.
20
#
21
# This program is distributed in the hope that it will be useful,
22
# but WITHOUT ANY WARRANTY; without even the implied warranty of
23
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
# GNU General Public License for more details.
25
#
26
# You should have received a copy of the GNU General Public License
27
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
28

    
29

    
30
import struct
31
import logging
32

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

    
39
LOG = logging.getLogger(__name__)
40

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

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

    
50
DRIVER_VERSION = "Quansheng UV-K5 driver v20230608 (c) Jacek Lipkowski SQ5BPF"
51
PRINT_CONSOLE = False
52

    
53
MEM_FORMAT = """
54
#seekto 0x0000;
55
struct {
56
  ul32 freq;
57
  ul32 offset;
58
  u8 rxcode;
59
  u8 txcode;
60
  u8 code_flag;
61
  u8 flags1;
62
  u8 flags2;
63
  u8 dtmf_flags;
64
  u8 step;
65
  u8 scrambler;
66
} channel[214];
67

    
68
#seekto 0xd60;
69
u8 channel_attributes[200];
70

    
71
#seekto 0xe40;
72
ul16 fmfreq[20];
73

    
74
#seekto 0xe70;
75
u8 call_channel;
76
u8 squelch;
77
u8 max_talk_time;
78
u8 noaa_autoscan;
79
u8 unknown1;
80
u8 unknown2;
81
u8 vox_level;
82
u8 mic_gain;
83
u8 unknown3;
84
u8 channel_display_mode;
85
u8 crossband;
86
u8 battery_save;
87
u8 dual_watch;
88
u8 tail_note_elimination;
89
u8 vfo_open;
90

    
91
#seekto 0xe90;
92
u8 beep_control;
93
#seekto 0xe95;
94
u8 scan_resume_mode;
95
u8 auto_keypad_lock;
96
u8 power_on_dispmode;
97
u8 password[4];
98

    
99
#seekto 0xea0;
100
u8 keypad_tone;
101
u8 language;
102

    
103
#seekto 0xea8;
104
u8 alarm_mode;
105
u8 reminding_of_end_talk;
106
u8 repeater_tail_elimination;
107

    
108
#seekto 0xeb0;
109
char logo_line1[16];
110
char logo_line2[16];
111

    
112
#seekto 0xf40;
113
u8 int_flock;
114
u8 int_350tx;
115
u8 int_unknown1;
116
u8 int_200tx;
117
u8 int_500tx;
118
u8 int_350en;
119
u8 int_screen;
120

    
121
#seekto 0xf50;
122
struct {
123
char name[16];
124
} channelname[200];
125

    
126
"""
127
# bits that we will save from the channel structure (mostly unknown)
128
SAVE_MASK_0A = 0b11001100
129
SAVE_MASK_0B = 0b11101100
130
SAVE_MASK_0C = 0b11100000
131
SAVE_MASK_0D = 0b11111000
132
SAVE_MASK_0E = 0b11110001
133
SAVE_MASK_0F = 0b11110000
134

    
135
# flags1
136
FLAGS1_OFFSET_MASK = 0b11
137
FLAGS1_OFFSET_NONE = 0b00
138
FLAGS1_OFFSET_MINUS = 0b10
139
FLAGS1_OFFSET_PLUS = 0b01
140

    
141
FLAGS1_ISSCANLIST = 0b100
142
FLAGS1_ISAM = 0b10000
143

    
144
# flags2
145
FLAGS2_BCLO = 0b10000
146
FLAGS2_POWER_MASK = 0b1100
147
FLAGS2_POWER_HIGH = 0b1000
148
FLAGS2_POWER_MEDIUM = 0b0100
149
FLAGS2_POWER_LOW = 0b0000
150
FLAGS2_BANDWIDTH = 0b10
151
FLAGS2_REVERSE = 0b1
152

    
153
# dtmf_flags
154
PTTID_LIST = ["off", "BOT", "EOT", "BOTH"]
155
FLAGS_DTMF_PTTID_MASK = 0b110  # PTTID: 00-disabled, 01-BOT, 10-EOT, 11-BOTH
156
FLAGS_DTMF_PTTID_DISABLED = 0b000
157
FLAGS_DTMF_PTTID_BOT = 0b010
158
FLAGS_DTMF_PTTID_EOT = 0b100
159
FLAGS_DTMF_PTTID_BOTH = 0b110
160
FLAGS_DTMF_DECODE = 0b1
161

    
162
# power
163
UVK5_POWER_LEVELS = [chirp_common.PowerLevel("Low",  watts=1.50),
164
                     chirp_common.PowerLevel("Med",  watts=3.00),
165
                     chirp_common.PowerLevel("High", watts=5.00),
166
                     ]
167

    
168
# scrambler
169
SCRAMBLER_LIST = ["off", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
170

    
171
# channel display mode
172
CHANNELDISP_LIST = ["Frequency", "Channel No", "Channel Name"]
173
# battery save
174
BATSAVE_LIST = ["OFF", "1:1", "1:2", "1:3", "1:4"]
175

    
176
# Crossband receiving/transmitting
177
CROSSBAND_LIST = ["Off", "Band A", "Band B"]
178
DUALWATCH_LIST = CROSSBAND_LIST
179

    
180
# steps
181
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 8.33]
182

    
183
# ctcss/dcs codes
184
TMODES = ["", "Tone", "DTCS", "DTCS"]
185
TONE_NONE = 0
186
TONE_CTCSS = 1
187
TONE_DCS = 2
188
TONE_RDCS = 3
189

    
190

    
191
CTCSS_TONES = [
192
    67.0, 69.3, 71.9, 74.4, 77.0, 79.7, 82.5, 85.4,
193
    88.5, 91.5, 94.8, 97.4, 100.0, 103.5, 107.2, 110.9,
194
    114.8, 118.8, 123.0, 127.3, 131.8, 136.5, 141.3, 146.2,
195
    151.4, 156.7, 159.8, 162.2, 165.5, 167.9, 171.3, 173.8,
196
    177.3, 179.9, 183.5, 186.2, 189.9, 192.8, 196.6, 199.5,
197
    203.5, 206.5, 210.7, 218.1, 225.7, 229.1, 233.6, 241.8,
198
    250.3, 254.1
199
]
200

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

    
215
FLOCK_LIST = ["Off", "FCC", "CE", "GB", "430", "438"]
216
SCANRESUME_LIST = ["TO: Resume after 5 seconds",
217
                   "CO: Resume after signal dissapears",
218
                   "SE: Stop scanning after receiving a signal"]
219
WELCOME_LIST = ["Full Screen", "Welcome Info", "Voltage"]
220
KEYPADTONE_LIST = ["Off", "Chinese", "English"]
221
LANGUAGE_LIST = ["Chinese", "English"]
222
ALARMMODE_LIST = ["SITE", "TONE"]
223
REMENDOFTALK_LIST = ["Off", "ROGER", "MDC"]
224
RTE_LIST = ["Off", "100ms", "200ms", "300ms", "400ms",
225
            "500ms", "600ms", "700ms", "800ms", "900ms"]
226

    
227
MEM_SIZE = 0x2000  # size of all memory
228
PROG_SIZE = 0x1d00  # size of the memory that we will write
229
MEM_BLOCK = 0x80  # largest block of memory that we can reliably write
230

    
231
# bands supported by the UV-K5
232
BANDS = {
233
        0: [50.0, 76.0],
234
        1: [108.0, 135.9999],
235
        2: [136.0, 199.9990],
236
        3: [200.0, 299.9999],
237
        4: [350.0, 399.9999],
238
        5: [400.0, 469.9999],
239
        6: [470.0, 600.0]
240
        }
241

    
242
# for radios with modified firmware:
243
BANDS_NOLIMITS = {
244
        0: [18.0, 76.0],
245
        1: [108.0, 135.9999],
246
        2: [136.0, 199.9990],
247
        3: [200.0, 299.9999],
248
        4: [350.0, 399.9999],
249
        5: [400.0, 469.9999],
250
        6: [470.0, 1300.0]
251
        }
252
BANDMASK = 0b1111
253

    
254
VFO_CHANNEL_NAMES = ["F1(50M-76M)A", "F1(50M-76M)B",
255
                     "F2(108M-136M)A", "F2(108M-136M)B",
256
                     "F3(136M-174M)A", "F3(136M-174M)B",
257
                     "F4(174M-350M)A", "F4(174M-350M)B",
258
                     "F5(350M-400M)A", "F5(350M-400M)B",
259
                     "F6(400M-470M)A", "F6(400M-470M)B",
260
                     "F7(470M-600M)A", "F7(470M-600M)B"]
261

    
262

    
263
# the communication is obfuscated using this fine mechanism
264
def xorarr(data: bytes):
265
    tbl = [22, 108, 20, 230, 46, 145, 13, 64, 33, 53, 213, 64, 19, 3, 233, 128]
266
    x = b""
267
    r = 0
268
    for byte in data:
269
        x += bytes([byte ^ tbl[r]])
270
        r = (r+1) % len(tbl)
271
    return x
272

    
273

    
274
# if this crc was used for communication to AND from the radio, then it
275
# would be a measure to increase reliability.
276
# but it's only used towards the radio, so it's for further obfuscation
277
def calculate_crc16_xmodem(data: bytes):
278
    poly = 0x1021
279
    crc = 0x0
280
    for byte in data:
281
        crc = crc ^ (byte << 8)
282
        for i in range(8):
283
            crc = crc << 1
284
            if (crc & 0x10000):
285
                crc = (crc ^ poly) & 0xFFFF
286
    return crc & 0xFFFF
287

    
288

    
289
def _send_command(serport, data: bytes):
290
    """Send a command to UV-K5 radio"""
291
    LOG.debug("Sending command (unobfuscated) len=0x%4.4x:\n%s" %
292
              (len(data), util.hexprint(data)))
293

    
294
    crc = calculate_crc16_xmodem(data)
295
    data2 = data + struct.pack("<H", crc)
296

    
297
    command = struct.pack(">HBB", 0xabcd, len(data), 0) + \
298
        xorarr(data2) + \
299
        struct.pack(">H", 0xdcba)
300
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
301
        LOG.debug("Sending command (obfuscated):\n%s" % util.hexprint(command))
302
    try:
303
        result = serport.write(command)
304
    except Exception:
305
        raise errors.RadioError("Error writing data to radio")
306
    return result
307

    
308

    
309
def _receive_reply(serport):
310
    header = serport.read(4)
311
    if len(header) != 4:
312
        LOG.warning("Header short read: [%s] len=%i" %
313
                    (util.hexprint(header), len(header)))
314
        raise errors.RadioError("Header short read")
315
    if header[0] != 0xAB or header[1] != 0xCD or header[3] != 0x00:
316
        LOG.warning("Bad response header: %s len=%i" %
317
                    (util.hexprint(header), len(header)))
318
        raise errors.RadioError("Bad response header")
319

    
320
        return False
321

    
322
    cmd = serport.read(int(header[2]))
323
    if len(cmd) != int(header[2]):
324
        LOG.warning("Body short read: [%s] len=%i" %
325
                    (util.hexprint(cmd), len(cmd)))
326
        raise errors.RadioError("Command body short read")
327

    
328
    footer = serport.read(4)
329

    
330
    if len(footer) != 4:
331
        LOG.warning("Footer short read: [%s] len=%i" %
332
                    (util.hexprint(footer), len(footer)))
333
        raise errors.RadioError("Footer short read")
334

    
335
    if footer[2] != 0xDC or footer[3] != 0xBA:
336
        LOG.debug(
337
                "Reply before bad response footer (obfuscated)"
338
                "len=0x%4.4x:\n%s" % (len(cmd), util.hexprint(cmd)))
339
        LOG.warning("Bad response footer: %s len=%i" %
340
                    (util.hexprint(footer), len(footer)))
341
        raise errors.RadioError("Bad response footer")
342
        return False
343

    
344
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
345
        LOG.debug("Received reply (obfuscated) len=0x%4.4x:\n%s" %
346
                  (len(cmd), util.hexprint(cmd)))
347

    
348
    cmd2 = xorarr(cmd)
349

    
350
    LOG.debug("Received reply (unobfuscated) len=0x%4.4x:\n%s" %
351
              (len(cmd2), util.hexprint(cmd2)))
352

    
353
    return cmd2
354

    
355

    
356
def _getstring(data: bytes, begin, maxlen):
357
    tmplen = min(maxlen+1, len(data))
358
    s = [data[i] for i in range(begin, tmplen)]
359
    for key, val in enumerate(s):
360
        if val < ord(' ') or val > ord('~'):
361
            break
362
    return ''.join(chr(x) for x in s[0:key])
363

    
364

    
365
def _sayhello(serport):
366
    hellopacket = b"\x14\x05\x04\x00\x6a\x39\x57\x64"
367

    
368
    tries = 5
369
    while (True):
370
        LOG.debug("Sending hello packet")
371
        _send_command(serport, hellopacket)
372
        o = _receive_reply(serport)
373
        if (o):
374
            break
375
        tries -= 1
376
        if tries == 0:
377
            LOG.warning("Failed to initialise radio")
378
            raise errors.RadioError("Failed to initialize radio")
379
            return False
380
    firmware = _getstring(o, 4, 16)
381
    LOG.info("Found firmware: %s" % firmware)
382
    return firmware
383

    
384

    
385
def _readmem(serport, offset, length):
386
    LOG.debug("Sending readmem offset=0x%4.4x len=0x%4.4x" % (offset, length))
387

    
388
    readmem = b"\x1b\x05\x08\x00" + \
389
        struct.pack("<HBB", offset, length, 0) + \
390
        b"\x6a\x39\x57\x64"
391
    _send_command(serport, readmem)
392
    o = _receive_reply(serport)
393
    if DEBUG_SHOW_MEMORY_ACTIONS:
394
        LOG.debug("readmem Received data len=0x%4.4x:\n%s" %
395
                  (len(o), util.hexprint(o)))
396
    return o[8:]
397

    
398

    
399
def _writemem(serport, data, offset):
400
    LOG.debug("Sending writemem offset=0x%4.4x len=0x%4.4x" %
401
              (offset, len(data)))
402

    
403
    if DEBUG_SHOW_MEMORY_ACTIONS:
404
        LOG.debug("writemem sent data offset=0x%4.4x len=0x%4.4x:\n%s" %
405
                  (offset, len(data), util.hexprint(data)))
406

    
407
    dlen = len(data)
408
    writemem = b"\x1d\x05" + \
409
        struct.pack("<BBHBB", dlen+8, 0, offset, dlen, 1) + \
410
        b"\x6a\x39\x57\x64"+data
411

    
412
    _send_command(serport, writemem)
413
    o = _receive_reply(serport)
414

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

    
417
    if (o[0] == 0x1e
418
            and
419
            o[4] == (offset & 0xff)
420
            and
421
            o[5] == (offset >> 8) & 0xff):
422
        return True
423
    else:
424
        LOG.warning("Bad data from writemem")
425
        raise errors.RadioError("Bad response to writemem")
426
    return False
427

    
428

    
429
def _resetradio(serport):
430
    resetpacket = b"\xdd\x05\x00\x00"
431
    _send_command(serport, resetpacket)
432

    
433

    
434
def do_download(radio):
435
    serport = radio.pipe
436
    serport.timeout = 0.5
437
    status = chirp_common.Status()
438
    status.cur = 0
439
    status.max = MEM_SIZE
440
    status.msg = "Downloading from radio"
441
    radio.status_fn(status)
442

    
443
    eeprom = b""
444
    f = _sayhello(serport)
445
    if f:
446
        radio.FIRMWARE_VERSION = f
447
    else:
448
        return False
449

    
450
    addr = 0
451
    while addr < MEM_SIZE:
452
        o = _readmem(serport, addr, MEM_BLOCK)
453
        status.cur = addr
454
        radio.status_fn(status)
455

    
456
        if o and len(o) == MEM_BLOCK:
457
            eeprom += o
458
            addr += MEM_BLOCK
459
        else:
460
            raise errors.RadioError("Memory download incomplete")
461

    
462
    return memmap.MemoryMapBytes(eeprom)
463

    
464

    
465
def do_upload(radio):
466
    serport = radio.pipe
467
    serport.timeout = 0.5
468
    status = chirp_common.Status()
469
    status.cur = 0
470
    status.max = PROG_SIZE
471
    status.msg = "Uploading to radio"
472
    radio.status_fn(status)
473

    
474
    f = _sayhello(serport)
475
    if f:
476
        radio.FIRMWARE_VERSION = f
477
    else:
478
        return False
479

    
480
    addr = 0
481
    while addr < PROG_SIZE:
482
        o = radio.get_mmap()[addr:addr+MEM_BLOCK]
483
        _writemem(serport, o, addr)
484
        status.cur = addr
485
        radio.status_fn(status)
486
        if o:
487
            addr += MEM_BLOCK
488
        else:
489
            raise errors.RadioError("Memory upload incomplete")
490
    status.msg = "Uploaded OK"
491

    
492
    _resetradio(serport)
493

    
494
    return True
495

    
496

    
497
def _find_band(self, hz):
498
    mhz = hz/1000000.0
499
    if self.FIRMWARE_NOLIMITS:
500
        B = BANDS_NOLIMITS
501
    else:
502
        B = BANDS
503
    for a in B:
504
        if mhz >= B[a][0] and mhz <= B[a][1]:
505
            return a
506
    return False
507

    
508

    
509
@directory.register
510
class UVK5Radio(chirp_common.CloneModeRadio):
511
    """Quansheng UV-K5"""
512
    VENDOR = "Quansheng"
513
    MODEL = "UV-K5"
514
    BAUD_RATE = 38400
515

    
516
    NEEDS_COMPAT_SERIAL = False
517
    FIRMWARE_VERSION = ""
518
    FIRMWARE_NOLIMITS = False
519

    
520
    def get_prompts(x=None):
521
        rp = chirp_common.RadioPrompts()
522
        rp.experimental = \
523
            ('This is an experimental driver for the Quanscheng UV-K5. '
524
             'It may harm your radio, or worse. Use at your own risk.\n\n'
525
             'Before attempting to do any changes please download'
526
             'the memory image from the radio with chirp or k5prog '
527
             'and keep it. This can be later used to recover the '
528
             'original settings. \n\n'
529
             'FM radio, DTMF settings and scanlists are not yet implemented')
530
        rp.pre_download = _(
531
            "1. Turn radio on.\n"
532
            "2. Connect cable to mic/spkr connector.\n"
533
            "3. Make sure connector is firmly connected.\n"
534
            "4. Click OK to download image from device.\n\n"
535
            "It will may not work if you turn o the radio "
536
            "with the cable already attached\n")
537
        rp.pre_upload = _(
538
            "1. Turn radio on.\n"
539
            "2. Connect cable to mic/spkr connector.\n"
540
            "3. Make sure connector is firmly connected.\n"
541
            "4. Click OK to upload the image to device.\n\n"
542
            "It will may not work if you turn o the radio "
543
            "with the cable already attached")
544
        return rp
545

    
546
    # Return information about this radio's features, including
547
    # how many memories it has, what bands it supports, etc
548
    def get_features(self):
549
        rf = chirp_common.RadioFeatures()
550
        rf.has_bank = False
551
        rf.valid_dtcs_codes = DTCS_CODES
552
        rf.has_rx_dtcs = True
553
        rf.has_ctone = True
554
        rf.has_settings = True
555
        rf.has_comment = False
556
        rf.valid_name_length = 16
557
        rf.valid_power_levels = UVK5_POWER_LEVELS
558

    
559
        # hack so we can input any frequency,
560
        # the 0.1 and 0.01 steps don't work unfortunately
561
        rf.valid_tuning_steps = [0.01, 0.1, 1.0] + STEPS
562

    
563
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
564
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
565
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
566

    
567
        rf.valid_characters = chirp_common.CHARSET_ASCII
568
        rf.valid_modes = ["FM", "NFM", "AM", "NAM"]
569
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
570

    
571
        rf.valid_skips = [""]
572

    
573
        # This radio supports memories 1-200, 201-214 are the VFO memories
574
        rf.memory_bounds = (1, 214)
575

    
576
        # This is what the BK4819 chip supports
577
        # Will leave it in a comment, might be useful someday
578
        # rf.valid_bands = [(18000000,  620000000),
579
        #                  (840000000, 1300000000)
580
        #                  ]
581
        rf.valid_bands = []
582
        for a in BANDS:
583
            rf.valid_bands.append(
584
                    (int(BANDS[a][0]*1000000), int(BANDS[a][1]*1000000)))
585
        return rf
586

    
587
    # Do a download of the radio from the serial port
588
    def sync_in(self):
589
        self._mmap = do_download(self)
590
        self.process_mmap()
591

    
592
    # Do an upload of the radio to the serial port
593
    def sync_out(self):
594
        do_upload(self)
595

    
596
    # Convert the raw byte array into a memory object structure
597
    def process_mmap(self):
598
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
599

    
600
    # Return a raw representation of the memory object, which
601
    # is very helpful for development
602
    def get_raw_memory(self, number):
603
        return repr(self._memobj.channel[number-1])
604

    
605
    def validate_memory(self, mem):
606
        msgs = super().validate_memory(mem)
607
        return msgs
608

    
609
    def _set_tone(self, mem, _mem):
610
        ((txmode, txtone, txpol),
611
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
612

    
613
        if txmode == "Tone":
614
            txtoval = CTCSS_TONES.index(txtone)
615
            txmoval = 0b01
616
        elif txmode == "DTCS":
617
            txmoval = txpol == "R" and 0b11 or 0b10
618
            txtoval = DTCS_CODES.index(txtone)
619
        else:
620
            txmoval = 0
621
            txtoval = 0
622

    
623
        if rxmode == "Tone":
624
            rxtoval = CTCSS_TONES.index(rxtone)
625
            rxmoval = 0b01
626
        elif rxmode == "DTCS":
627
            rxmoval = rxpol == "R" and 0b11 or 0b10
628
            rxtoval = DTCS_CODES.index(rxtone)
629
        else:
630
            rxmoval = 0
631
            rxtoval = 0
632

    
633
        _mem.code_flag = (_mem.code_flag & 0b11001100) | (
634
            txmoval << 4) | rxmoval
635
        _mem.rxcode = rxtoval
636
        _mem.txcode = txtoval
637

    
638
    def _get_tone(self, mem, _mem):
639
        rxtype = _mem.code_flag & 0x03
640
        txtype = (_mem.code_flag >> 4) & 0x03
641
        rx_tmode = TMODES[rxtype]
642
        tx_tmode = TMODES[txtype]
643

    
644
        rx_tone = tx_tone = None
645

    
646
        if tx_tmode == "Tone":
647
            if _mem.txcode < len(CTCSS_TONES):
648
                tx_tone = CTCSS_TONES[_mem.txcode]
649
            else:
650
                tx_tone = 0
651
                tx_tmode = ""
652
        elif tx_tmode == "DTCS":
653
            if _mem.txcode < len(DTCS_CODES):
654
                tx_tone = DTCS_CODES[_mem.txcode]
655
            else:
656
                tx_tone = 0
657
                tx_tmode = ""
658

    
659
        if rx_tmode == "Tone":
660
            if _mem.rxcode < len(CTCSS_TONES):
661
                rx_tone = CTCSS_TONES[_mem.rxcode]
662
            else:
663
                rx_tone = 0
664
                rx_tmode = ""
665
        elif rx_tmode == "DTCS":
666
            if _mem.rxcode < len(DTCS_CODES):
667
                rx_tone = DTCS_CODES[_mem.rxcode]
668
            else:
669
                rx_tone = 0
670
                rx_tmode = ""
671

    
672
        tx_pol = txtype == 0x03 and "R" or "N"
673
        rx_pol = rxtype == 0x03 and "R" or "N"
674

    
675
        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
676
                                       (rx_tmode, rx_tone, rx_pol))
677

    
678
    # Extract a high-level memory object from the low-level memory map
679
    # This is called to populate a memory in the UI
680

    
681
    def get_memory(self, number2):
682
        number = number2-1  # in the radio memories start with 0
683

    
684
        mem = chirp_common.Memory()
685

    
686
        # cutting and pasting configs from different radios
687
        # might try to set channel 0
688
        if number2 == 0:
689
            LOG.warning("Attempt to get channel 0")
690
            return mem
691

    
692
        _mem = self._memobj.channel[number]
693

    
694
        tmpcomment = ""
695

    
696
        mem.number = number2
697

    
698
        is_empty = False
699
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
700
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
701
            is_empty = True
702

    
703
        # We'll also look at the channel attributes if a memory has them
704
        if number < 200:
705
            _mem3 = self._memobj.channel_attributes[number]
706
            if _mem3 & 0x08 > 0:
707
                is_empty = True
708

    
709
        if is_empty:
710
            mem.empty = True
711
            # set some sane defaults:
712
            mem.power = UVK5_POWER_LEVELS[2]
713
            mem.extra = RadioSettingGroup("Extra", "extra")
714
            rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(False))
715
            mem.extra.append(rs)
716
            rs = RadioSetting("frev", "FreqRev",
717
                              RadioSettingValueBoolean(False))
718
            mem.extra.append(rs)
719
            rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
720
                PTTID_LIST, PTTID_LIST[0]))
721
            mem.extra.append(rs)
722
            rs = RadioSetting("dtmfdecode", "DTMF decode",
723
                              RadioSettingValueBoolean(False))
724
            mem.extra.append(rs)
725
            rs = RadioSetting("scrambler", "Scrambler", RadioSettingValueList(
726
                SCRAMBLER_LIST, SCRAMBLER_LIST[0]))
727
            mem.extra.append(rs)
728

    
729
            # actually the step and duplex are overwritten by chirp based on
730
            # bandplan. they are here to document sane defaults for IARU r1
731
            # mem.tuning_step = 25.0
732
            # mem.duplex = "off"
733

    
734
            return mem
735

    
736
        if number > 199:
737
            mem.name = VFO_CHANNEL_NAMES[number-200]
738
            mem.immutable = ["name"]
739
        else:
740
            _mem2 = self._memobj.channelname[number]
741
            for char in _mem2.name:
742
                if str(char) == "\xFF" or str(char) == "\x00":
743
                    break
744
                mem.name += str(char)
745
            mem.name = mem.name.rstrip()
746

    
747
        # Convert your low-level frequency to Hertz
748
        mem.freq = int(_mem.freq)*10
749
        mem.offset = int(_mem.offset)*10
750

    
751
        if (mem.offset == 0):
752
            mem.duplex = ''
753
        else:
754
            if (_mem.flags1 & FLAGS1_OFFSET_MASK) == FLAGS1_OFFSET_MINUS:
755
                mem.duplex = '-'
756
            elif (_mem.flags1 & FLAGS1_OFFSET_MASK) == FLAGS1_OFFSET_PLUS:
757
                mem.duplex = '+'
758
            else:
759
                mem.duplex = ''
760

    
761
        # tone data
762
        self._get_tone(mem, _mem)
763

    
764
        # mode
765
        if (_mem.flags1 & FLAGS1_ISAM) > 0:
766
            if (_mem.flags2 & FLAGS2_BANDWIDTH) > 0:
767
                mem.mode = "NAM"
768
            else:
769
                mem.mode = "AM"
770
        else:
771
            if (_mem.flags2 & FLAGS2_BANDWIDTH) > 0:
772
                mem.mode = "NFM"
773
            else:
774
                mem.mode = "FM"
775

    
776
        # tuning step
777
        tstep = _mem.step & 0x7
778
        if tstep < len(STEPS):
779
            mem.tuning_step = STEPS[tstep]
780
        else:
781
            mem.tuning_step = 2.5
782

    
783
        # power
784
        if (_mem.flags2 & FLAGS2_POWER_MASK) == FLAGS2_POWER_HIGH:
785
            mem.power = UVK5_POWER_LEVELS[2]
786
        elif (_mem.flags2 & FLAGS2_POWER_MASK) == FLAGS2_POWER_MEDIUM:
787
            mem.power = UVK5_POWER_LEVELS[1]
788
        else:
789
            mem.power = UVK5_POWER_LEVELS[0]
790

    
791
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
792
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
793
            mem.empty = True
794
        else:
795
            mem.empty = False
796

    
797
        mem.extra = RadioSettingGroup("Extra", "extra")
798

    
799
        # BCLO
800
        is_bclo = bool(_mem.flags2 & FLAGS2_BCLO > 0)
801
        rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(is_bclo))
802
        mem.extra.append(rs)
803
        tmpcomment += "BCLO:"+(is_bclo and "ON" or "off")+" "
804

    
805
        # Frequency reverse - whatever that means, don't see it in the manual
806
        is_frev = bool(_mem.flags2 & FLAGS2_REVERSE > 0)
807
        rs = RadioSetting("frev", "FreqRev", RadioSettingValueBoolean(is_frev))
808
        mem.extra.append(rs)
809
        tmpcomment += "FreqReverse:"+(is_frev and "ON" or "off")+" "
810

    
811
        # PTTID
812
        pttid = (_mem.dtmf_flags & FLAGS_DTMF_PTTID_MASK) >> 1
813
        rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
814
            PTTID_LIST, PTTID_LIST[pttid]))
815
        mem.extra.append(rs)
816
        tmpcomment += "PTTid:"+PTTID_LIST[pttid]+" "
817

    
818
        # DTMF DECODE
819
        is_dtmf = bool(_mem.dtmf_flags & FLAGS_DTMF_DECODE > 0)
820
        rs = RadioSetting("dtmfdecode", "DTMF decode",
821
                          RadioSettingValueBoolean(is_dtmf))
822
        mem.extra.append(rs)
823
        tmpcomment += "DTMFdecode:"+(is_dtmf and "ON" or "off")+" "
824

    
825
        # Scrambler
826
        if _mem.scrambler & 0x0f < len(SCRAMBLER_LIST):
827
            enc = _mem.scrambler & 0x0f
828
        else:
829
            enc = 0
830

    
831
        rs = RadioSetting("scrambler", "Scrambler", RadioSettingValueList(
832
            SCRAMBLER_LIST, SCRAMBLER_LIST[enc]))
833
        mem.extra.append(rs)
834
        tmpcomment += "Scrambler:"+SCRAMBLER_LIST[enc]+" "
835

    
836
        return mem
837

    
838
    def set_settings(self, settings):
839
        _mem = self._memobj
840
        for element in settings:
841
            if not isinstance(element, RadioSetting):
842
                self.set_settings(element)
843
                continue
844

    
845
            # basic settings
846

    
847
            # call channel
848
            if element.get_name() == "call_channel":
849
                _mem.call_channel = int(element.value)-1
850

    
851
            # squelch
852
            if element.get_name() == "squelch":
853
                _mem.squelch = int(element.value)
854
            # TOT
855
            if element.get_name() == "tot":
856
                _mem.max_talk_time = int(element.value)
857
            # NOAA autoscan
858
            if element.get_name() == "noaa_autoscan":
859
                _mem.noaa_autoscan = element.value and 1 or 0
860

    
861
            # vox level
862
            if element.get_name() == "vox_level":
863
                _mem.vox_level = int(element.value)-1
864

    
865
            # mic gain
866
            if element.get_name() == "mic_gain":
867
                _mem.mic_gain = int(element.value)
868

    
869
            # Channel display mode
870
            if element.get_name() == "channel_display_mode":
871
                _mem.channel_display_mode = CHANNELDISP_LIST.index(
872
                    str(element.value))
873

    
874
            # Crossband receiving/transmitting
875
            if element.get_name() == "crossband":
876
                _mem.crossband = CROSSBAND_LIST.index(str(element.value))
877

    
878
            # Battery Save
879
            if element.get_name() == "battery_save":
880
                _mem.battery_save = BATSAVE_LIST.index(str(element.value))
881
            # Dual Watch
882
            if element.get_name() == "dualwatch":
883
                _mem.dual_watch = DUALWATCH_LIST.index(str(element.value))
884

    
885
            # Tail tone elimination
886
            if element.get_name() == "tail_note_elimination":
887
                _mem.tail_note_elimination = element.value and 1 or 0
888

    
889
            # VFO Open
890
            if element.get_name() == "vfo_open":
891
                _mem.vfo_open = element.value and 1 or 0
892

    
893
            # Beep control
894
            if element.get_name() == "beep_control":
895
                _mem.beep_control = element.value and 1 or 0
896

    
897
            # Scan resume mode
898
            if element.get_name() == "scan_resume_mode":
899
                _mem.scan_resume_mode = SCANRESUME_LIST.index(
900
                    str(element.value))
901

    
902
            # Auto keypad lock
903
            if element.get_name() == "auto_keypad_lock":
904
                _mem.auto_keypad_lock = element.value and 1 or 0
905

    
906
            # Power on display mode
907
            if element.get_name() == "welcome_mode":
908
                _mem.power_on_dispmode = WELCOME_LIST.index(str(element.value))
909

    
910
            # Keypad Tone
911
            if element.get_name() == "keypad_tone":
912
                _mem.keypad_tone = KEYPADTONE_LIST.index(str(element.value))
913

    
914
            # Language
915
            if element.get_name() == "language":
916
                _mem.language = LANGUAGE_LIST.index(str(element.value))
917

    
918
            # Alarm mode
919
            if element.get_name() == "alarm_mode":
920
                _mem.alarm_mode = ALARMMODE_LIST.index(str(element.value))
921

    
922
            # Reminding of end of talk
923
            if element.get_name() == "reminding_of_end_talk":
924
                _mem.reminding_of_end_talk = REMENDOFTALK_LIST.index(
925
                    str(element.value))
926

    
927
            # Repeater tail tone elimination
928
            if element.get_name() == "repeater_tail_elimination":
929
                _mem.repeater_tail_elimination = RTE_LIST.index(
930
                    str(element.value))
931

    
932
            # Logo string 1
933
            if element.get_name() == "logo1":
934
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
935
                _mem.logo_line1 = b[0:12]+"\xff\xff\xff\xff"
936

    
937
            # Logo string 2
938
            if element.get_name() == "logo2":
939
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
940
                _mem.logo_line2 = b[0:12]+"\xff\xff\xff\xff"
941

    
942
            # unlock settings
943

    
944
            # FLOCK
945
            if element.get_name() == "flock":
946
                _mem.int_flock = FLOCK_LIST.index(str(element.value))
947

    
948
            # 350TX
949
            if element.get_name() == "350tx":
950
                _mem.int_350tx = element.value and 1 or 0
951

    
952
            # UNKNOWN1
953
            if element.get_name() == "unknown1":
954
                _mem.int_unknown1 = element.value and 1 or 0
955

    
956
            # 200TX
957
            if element.get_name() == "200tx":
958
                _mem.int_200tx = element.value and 1 or 0
959

    
960
            # 500TX
961
            if element.get_name() == "500tx":
962
                _mem.int_500tx = element.value and 1 or 0
963

    
964
            # 350EN
965
            if element.get_name() == "350en":
966
                _mem.int_350en = element.value and 1 or 0
967

    
968
    def get_settings(self):
969
        _mem = self._memobj
970
        basic = RadioSettingGroup("basic", "Basic Settings")
971
        unlock = RadioSettingGroup("unlock", "Unlock Settings")
972
        fmradio = RadioSettingGroup("fmradio", "FM Radio (unimplemented yet)")
973
        roinfo = RadioSettingGroup("roinfo", "Driver information")
974

    
975
        top = RadioSettings(basic, unlock, fmradio, roinfo)
976

    
977
        # basic settings
978

    
979
        # call channel
980
        tmpc = _mem.call_channel+1
981
        if tmpc > 200:
982
            tmpc = 1
983
        rs = RadioSetting("call_channel", "One key call channel",
984
                          RadioSettingValueInteger(1, 200, tmpc))
985
        basic.append(rs)
986

    
987
        # squelch
988
        tmpsq = _mem.squelch
989
        if tmpsq > 9:
990
            tmpsq = 1
991
        rs = RadioSetting("squelch", "Squelch",
992
                          RadioSettingValueInteger(0, 9, tmpsq))
993
        basic.append(rs)
994

    
995
        # TOT
996
        tmptot = _mem.max_talk_time
997
        if tmptot > 10:
998
            tmptot = 10
999
        rs = RadioSetting(
1000
                "tot",
1001
                "Max talk time [min]",
1002
                RadioSettingValueInteger(0, 10, tmptot))
1003
        basic.append(rs)
1004

    
1005
        # NOAA autoscan
1006
        rs = RadioSetting(
1007
                "noaa_autoscan",
1008
                "NOAA Autoscan", RadioSettingValueBoolean(
1009
                    bool(_mem.noaa_autoscan > 0)))
1010
        basic.append(rs)
1011

    
1012
        # VOX Level
1013
        tmpvox = _mem.vox_level+1
1014
        if tmpvox > 10:
1015
            tmpvox = 10
1016
        rs = RadioSetting("vox_level", "VOX Level",
1017
                          RadioSettingValueInteger(1, 10, tmpvox))
1018
        basic.append(rs)
1019

    
1020
        # Mic gain
1021
        tmpmicgain = _mem.mic_gain
1022
        if tmpmicgain > 4:
1023
            tmpmicgain = 4
1024
        rs = RadioSetting("mic_gain", "Mic Gain",
1025
                          RadioSettingValueInteger(0, 4, tmpmicgain))
1026
        basic.append(rs)
1027

    
1028
        # Channel display mode
1029
        tmpchdispmode = _mem.channel_display_mode
1030
        if tmpchdispmode >= len(CHANNELDISP_LIST):
1031
            tmpchdispmode = 0
1032
        rs = RadioSetting(
1033
                "channel_display_mode",
1034
                "Channel display mode",
1035
                RadioSettingValueList(
1036
                    CHANNELDISP_LIST,
1037
                    CHANNELDISP_LIST[tmpchdispmode]))
1038
        basic.append(rs)
1039

    
1040
        # Crossband receiving/transmitting
1041
        tmpcross = _mem.crossband
1042
        if tmpcross >= len(CROSSBAND_LIST):
1043
            tmpcross = 0
1044
        rs = RadioSetting(
1045
                "crossband",
1046
                "Cross-band receiving/transmitting",
1047
                RadioSettingValueList(
1048
                    CROSSBAND_LIST,
1049
                    CROSSBAND_LIST[tmpcross]))
1050
        basic.append(rs)
1051

    
1052
        # Battery save
1053
        tmpbatsave = _mem.battery_save
1054
        if tmpbatsave >= len(BATSAVE_LIST):
1055
            tmpbatsave = BATSAVE_LIST.index("1:4")
1056
        rs = RadioSetting(
1057
                "battery_save",
1058
                "Battery Save",
1059
                RadioSettingValueList(
1060
                    BATSAVE_LIST,
1061
                    BATSAVE_LIST[tmpbatsave]))
1062
        basic.append(rs)
1063

    
1064
        # Dual watch
1065
        tmpdual = _mem.dual_watch
1066
        if tmpdual >= len(DUALWATCH_LIST):
1067
            tmpdual = 0
1068
        rs = RadioSetting("dualwatch", "Dual Watch", RadioSettingValueList(
1069
            DUALWATCH_LIST, DUALWATCH_LIST[tmpdual]))
1070
        basic.append(rs)
1071

    
1072
        # Tail tone elimination
1073
        rs = RadioSetting(
1074
                "tail_note_elimination",
1075
                "Tail tone elimination",
1076
                RadioSettingValueBoolean(
1077
                    bool(_mem.tail_note_elimination > 0)))
1078
        basic.append(rs)
1079

    
1080
        # VFO open
1081
        rs = RadioSetting("vfo_open", "VFO open",
1082
                          RadioSettingValueBoolean(bool(_mem.vfo_open > 0)))
1083
        basic.append(rs)
1084

    
1085
        # Beep control
1086
        rs = RadioSetting(
1087
                "beep_control",
1088
                "Beep control",
1089
                RadioSettingValueBoolean(bool(_mem.beep_control > 0)))
1090
        basic.append(rs)
1091

    
1092
        # Scan resume mode
1093
        tmpscanres = _mem.scan_resume_mode
1094
        if tmpscanres >= len(SCANRESUME_LIST):
1095
            tmpscanres = 0
1096
        rs = RadioSetting(
1097
                "scan_resume_mode",
1098
                "Scan resume mode",
1099
                RadioSettingValueList(
1100
                    SCANRESUME_LIST,
1101
                    SCANRESUME_LIST[tmpscanres]))
1102
        basic.append(rs)
1103

    
1104
        # Auto keypad lock
1105
        rs = RadioSetting(
1106
                "auto_keypad_lock",
1107
                "Auto keypad lock",
1108
                RadioSettingValueBoolean(bool(_mem.auto_keypad_lock > 0)))
1109
        basic.append(rs)
1110

    
1111
        # Power on display mode
1112
        tmpdispmode = _mem.power_on_dispmode
1113
        if tmpdispmode >= len(WELCOME_LIST):
1114
            tmpdispmode = 0
1115
        rs = RadioSetting(
1116
                "welcome_mode",
1117
                "Power on display mode",
1118
                RadioSettingValueList(
1119
                    WELCOME_LIST,
1120
                    WELCOME_LIST[tmpdispmode]))
1121
        basic.append(rs)
1122

    
1123
        # Keypad Tone
1124
        tmpkeypadtone = _mem.keypad_tone
1125
        if tmpkeypadtone >= len(KEYPADTONE_LIST):
1126
            tmpkeypadtone = 0
1127
        rs = RadioSetting("keypad_tone", "Keypad tone", RadioSettingValueList(
1128
            KEYPADTONE_LIST, KEYPADTONE_LIST[tmpkeypadtone]))
1129
        basic.append(rs)
1130

    
1131
        # Language
1132
        tmplanguage = _mem.language
1133
        if tmplanguage >= len(LANGUAGE_LIST):
1134
            tmplanguage = 0
1135
        rs = RadioSetting("language", "Language", RadioSettingValueList(
1136
            LANGUAGE_LIST, LANGUAGE_LIST[tmplanguage]))
1137
        basic.append(rs)
1138

    
1139
        # Alarm mode
1140
        tmpalarmmode = _mem.alarm_mode
1141
        if tmpalarmmode >= len(ALARMMODE_LIST):
1142
            tmpalarmmode = 0
1143
        rs = RadioSetting("alarm_mode", "Alarm mode", RadioSettingValueList(
1144
            ALARMMODE_LIST, ALARMMODE_LIST[tmpalarmmode]))
1145
        basic.append(rs)
1146

    
1147
        # Reminding of end of talk
1148
        tmpalarmmode = _mem.reminding_of_end_talk
1149
        if tmpalarmmode >= len(REMENDOFTALK_LIST):
1150
            tmpalarmmode = 0
1151
        rs = RadioSetting(
1152
                "reminding_of_end_talk",
1153
                "Reminding of end of talk",
1154
                RadioSettingValueList(
1155
                    REMENDOFTALK_LIST,
1156
                    REMENDOFTALK_LIST[tmpalarmmode]))
1157
        basic.append(rs)
1158

    
1159
        # Repeater tail tone elimination
1160
        tmprte = _mem.repeater_tail_elimination
1161
        if tmprte >= len(RTE_LIST):
1162
            tmprte = 0
1163
        rs = RadioSetting(
1164
                "repeater_tail_elimination",
1165
                "Repeater tail tone elimination",
1166
                RadioSettingValueList(RTE_LIST, RTE_LIST[tmprte]))
1167
        basic.append(rs)
1168

    
1169
        # Logo string 1
1170
        logo1 = str(_mem.logo_line1).strip("\x20\x00\xff")  # +"\x20"*12
1171
        logo1 = logo1[0:12]
1172
        rs = RadioSetting("logo1", "Logo string 1 (12 characters)",
1173
                          RadioSettingValueString(0, 12, logo1))
1174
        basic.append(rs)
1175

    
1176
        # Logo string 2
1177
        logo2 = str(_mem.logo_line2).strip("\x20\x00\xff")  # +"\x20"*12
1178
        logo2 = logo2[0:12]
1179
        rs = RadioSetting("logo2", "Logo string 2 (12 characters)",
1180
                          RadioSettingValueString(0, 12, logo2))
1181
        basic.append(rs)
1182

    
1183
        # unlock settings
1184

    
1185
        # F-LOCK
1186
        tmpflock = _mem.int_flock
1187
        if tmpflock >= len(FLOCK_LIST):
1188
            tmpflock = 0
1189
        rs = RadioSetting(
1190
            "flock", "F-LOCK",
1191
            RadioSettingValueList(FLOCK_LIST, FLOCK_LIST[tmpflock]))
1192
        unlock.append(rs)
1193

    
1194
        # 350TX
1195
        rs = RadioSetting("350tx", "350TX", RadioSettingValueBoolean(
1196
            bool(_mem.int_350tx > 0)))
1197
        unlock.append(rs)
1198

    
1199
        # unknown1
1200
        rs = RadioSetting("unknown11", "UNKNOWN1",
1201
                          RadioSettingValueBoolean(
1202
                              bool(_mem.int_unknown1 > 0)))
1203
        unlock.append(rs)
1204

    
1205
        # 200TX
1206
        rs = RadioSetting("200tx", "200TX", RadioSettingValueBoolean(
1207
            bool(_mem.int_200tx > 0)))
1208
        unlock.append(rs)
1209

    
1210
        # 500TX
1211
        rs = RadioSetting("500tx", "500TX", RadioSettingValueBoolean(
1212
            bool(_mem.int_500tx > 0)))
1213
        unlock.append(rs)
1214

    
1215
        # 350EN
1216
        rs = RadioSetting("350en", "350EN", RadioSettingValueBoolean(
1217
            bool(_mem.int_350en > 0)))
1218
        unlock.append(rs)
1219

    
1220
        # SCREEN
1221
        rs = RadioSetting("screen", "SCREEN", RadioSettingValueBoolean(
1222
            bool(_mem.int_screen > 0)))
1223
        unlock.append(rs)
1224

    
1225
        # readonly info
1226
        # Firmware
1227
        if self.FIRMWARE_VERSION == "":
1228
            firmware = "To get the firmware version please download"
1229
            "the image from the radio first"
1230
        else:
1231
            firmware = self.FIRMWARE_VERSION
1232

    
1233
        val = RadioSettingValueString(0, 128, firmware)
1234
        val.set_mutable(False)
1235
        rs = RadioSetting("fw_ver", "Firmware Version", val)
1236
        roinfo.append(rs)
1237

    
1238
        # Driver version
1239
        val = RadioSettingValueString(0, 128, DRIVER_VERSION)
1240
        val.set_mutable(False)
1241
        rs = RadioSetting("driver_ver", "Driver version", val)
1242
        roinfo.append(rs)
1243

    
1244
        # No limits version for hacked firmware
1245
        val = RadioSettingValueBoolean(self.FIRMWARE_NOLIMITS)
1246
        val.set_mutable(False)
1247
        rs = RadioSetting("nolimits", "Limits disabled for modified firmware",
1248
                          val)
1249
        roinfo.append(rs)
1250

    
1251
        return top
1252

    
1253
    # Store details about a high-level memory to the memory map
1254
    # This is called when a user edits a memory in the UI
1255
    def set_memory(self, mem):
1256
        number = mem.number-1
1257

    
1258
        # Get a low-level memory object mapped to the image
1259
        _mem = self._memobj.channel[number]
1260
        _mem4 = self._memobj
1261
        # empty memory
1262
        if mem.empty:
1263
            _mem.set_raw("\xFF" * 16)
1264
            if number < 200:
1265
                _mem2 = self._memobj.channelname[number]
1266
                _mem2.set_raw("\xFF" * 16)
1267
                _mem4.channel_attributes[number] = 0x0f
1268
            return mem
1269

    
1270
        # clean the channel memory, restore some bits if it was used before
1271
        if _mem.get_raw()[0] == "\xff":
1272
            # this was an empty memory
1273
            _mem.set_raw("\x00" * 16)
1274
        else:
1275
            # this memory was't empty, save some bits that we don't know the
1276
            # meaning of, or that we don't support yet
1277
            prev_0a = ord(_mem.get_raw()[0x0a]) & SAVE_MASK_0A
1278
            prev_0b = ord(_mem.get_raw()[0x0b]) & SAVE_MASK_0B
1279
            prev_0c = ord(_mem.get_raw()[0x0c]) & SAVE_MASK_0C
1280
            prev_0d = ord(_mem.get_raw()[0x0d]) & SAVE_MASK_0D
1281
            prev_0e = ord(_mem.get_raw()[0x0e]) & SAVE_MASK_0E
1282
            prev_0f = ord(_mem.get_raw()[0x0f]) & SAVE_MASK_0F
1283
            _mem.set_raw("\x00" * 10 +
1284
                         chr(prev_0a) + chr(prev_0b) + chr(prev_0c) +
1285
                         chr(prev_0d) + chr(prev_0e) + chr(prev_0f))
1286

    
1287
        if number < 200:
1288
            _mem4.channel_attributes[number] = 0x0f
1289

    
1290
        # find tx frequency
1291
        if mem.duplex == '-':
1292
            txfreq = mem.freq - mem.offset
1293
        elif mem.duplex == '+':
1294
            txfreq = mem.freq + mem.offset
1295
        else:
1296
            txfreq = mem.freq
1297

    
1298
        # find band
1299
        band = _find_band(self, txfreq)
1300
        if band is False:
1301
            raise errors.RadioError(
1302
                    "Transmit frequency %.4fMHz is not supported by this radio"
1303
                    % txfreq/1000000.0)
1304

    
1305
        band = _find_band(self, mem.freq)
1306
        if band is False:
1307
            return mem
1308

    
1309
        # mode
1310
        if mem.mode == "NFM":
1311
            _mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
1312
            _mem.flags1 = _mem.flags1 & ~FLAGS1_ISAM
1313
        elif mem.mode == "FM":
1314
            _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1315
            _mem.flags1 = _mem.flags1 & ~FLAGS1_ISAM
1316
        elif mem.mode == "NAM":
1317
            _mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
1318
            _mem.flags1 = _mem.flags1 | FLAGS1_ISAM
1319
        elif mem.mode == "AM":
1320
            _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1321
            _mem.flags1 = _mem.flags1 | FLAGS1_ISAM
1322

    
1323
        # frequency/offset
1324
        _mem.freq = mem.freq/10
1325
        _mem.offset = mem.offset/10
1326

    
1327
        if mem.duplex == "off" or mem.duplex == "":
1328
            _mem.offset = 0
1329
            _mem.flags1 = _mem.flags1 & ~FLAGS1_OFFSET_MASK
1330
        elif mem.duplex == '-':
1331
            _mem.flags1 = (
1332
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_MINUS
1333
        elif mem.duplex == '+':
1334
            _mem.flags1 = (
1335
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_PLUS
1336

    
1337
        # set band
1338
        if number < 200:
1339
            _mem4.channel_attributes[number] = (
1340
                    _mem4.channel_attributes[number] & ~BANDMASK) | band
1341

    
1342
        # channels >200 are the 14 VFO chanells and don't have names
1343
        if number < 200:
1344
            _mem2 = self._memobj.channelname[number]
1345
            tag = mem.name.ljust(16)[:16]
1346
            _mem2.name = tag  # Store the alpha tag
1347

    
1348
        # tone data
1349
        self._set_tone(mem, _mem)
1350

    
1351
        # step
1352
        _mem.step = STEPS.index(mem.tuning_step)
1353

    
1354
        # tx power
1355
        if str(mem.power) == str(UVK5_POWER_LEVELS[2]):
1356
            _mem.flags2 = (
1357
                _mem.flags2 & ~FLAGS2_POWER_MASK) | FLAGS2_POWER_HIGH
1358
        elif str(mem.power) == str(UVK5_POWER_LEVELS[1]):
1359
            _mem.flags2 = (
1360
                _mem.flags2 & ~FLAGS2_POWER_MASK) | FLAGS2_POWER_MEDIUM
1361
        else:
1362
            _mem.flags2 = (_mem.flags2 & ~FLAGS2_POWER_MASK)
1363

    
1364
        for setting in mem.extra:
1365
            sname = setting.get_name()
1366
            svalue = setting.value.get_value()
1367

    
1368
            if sname == "bclo":
1369
                if svalue:
1370
                    _mem.flags2 = _mem.flags2 | FLAGS2_BCLO
1371
                else:
1372
                    _mem.flags2 = _mem.flags2 & ~FLAGS2_BCLO
1373

    
1374
            if sname == "pttid":
1375
                _mem.dtmf_flags = (
1376
                        (_mem.dtmf_flags & ~FLAGS_DTMF_PTTID_MASK)
1377
                        | (PTTID_LIST.index(svalue) << 1))
1378

    
1379
            if sname == "frev":
1380
                if svalue:
1381
                    _mem.flags2 = _mem.flags2 | FLAGS2_REVERSE
1382
                else:
1383
                    _mem.flags2 = _mem.flags2 & ~FLAGS2_REVERSE
1384

    
1385
            if sname == "dtmfdecode":
1386
                if svalue:
1387
                    _mem.dtmf_flags = _mem.dtmf_flags | FLAGS_DTMF_DECODE
1388
                else:
1389
                    _mem.dtmf_flags = _mem.dtmf_flags & ~FLAGS_DTMF_DECODE
1390

    
1391
            if sname == "scrambler":
1392
                _mem.scrambler = (
1393
                    _mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue)
1394

    
1395
        return mem
1396

    
1397

    
1398
@directory.register
1399
class UVK5Radio_nolimit(UVK5Radio):
1400
    VENDOR = "Quansheng"
1401
    MODEL = "UV-K5 (modified firmware)"
1402
    VARIANT = "nolimits"
1403
    FIRMWARE_NOLIMITS = True
1404

    
1405
    def get_features(self):
1406
        rf = UVK5Radio.get_features(self)
1407
        # This is what the BK4819 chip supports
1408
        rf.valid_bands = [(18000000,  620000000),
1409
                          (840000000, 1300000000)
1410
                          ]
1411
        return rf
(32-32/47)