Project

General

Profile

New Model #10478 » uvk5.py

uvk5.py chirp driver for UV-K5, version 20230610 - Jacek Lipkowski SQ5BPF, 06/10/2023 01:03 AM

 
1
# Quansheng UV-K5 driver (c) 2023 Jacek Lipkowski <sq5bpf@lipkowski.org>
2
#
3
# based on template.py Copyright 2012 Dan Smith <dsmith@danplanet.com>
4
#
5
#
6
# This is a preliminary version of a driver for the UV-K5
7
# It is based on my reverse engineering effort described here:
8
# https://github.com/sq5bpf/uvk5-reverse-engineering
9
#
10
# Warning: this driver is experimental, it may brick your radio,
11
# eat your lunch and mess up your configuration. 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 v20230610 (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
# fm radio supported frequencies
232
FMMIN = 76.0
233
FMMAX = 108.0
234

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

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

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

    
266

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

    
277

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

    
292

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

    
298
    crc = calculate_crc16_xmodem(data)
299
    data2 = data + struct.pack("<H", crc)
300

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

    
312

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

    
324
        return False
325

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

    
332
    footer = serport.read(4)
333

    
334
    if len(footer) != 4:
335
        LOG.warning("Footer short read: [%s] len=%i" %
336
                    (util.hexprint(footer), len(footer)))
337
        raise errors.RadioError("Footer short read")
338

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

    
348
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
349
        LOG.debug("Received reply (obfuscated) len=0x%4.4x:\n%s" %
350
                  (len(cmd), util.hexprint(cmd)))
351

    
352
    cmd2 = xorarr(cmd)
353

    
354
    LOG.debug("Received reply (unobfuscated) len=0x%4.4x:\n%s" %
355
              (len(cmd2), util.hexprint(cmd2)))
356

    
357
    return cmd2
358

    
359

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

    
368

    
369
def _sayhello(serport):
370
    hellopacket = b"\x14\x05\x04\x00\x6a\x39\x57\x64"
371

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

    
388

    
389
def _readmem(serport, offset, length):
390
    LOG.debug("Sending readmem offset=0x%4.4x len=0x%4.4x" % (offset, length))
391

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

    
402

    
403
def _writemem(serport, data, offset):
404
    LOG.debug("Sending writemem offset=0x%4.4x len=0x%4.4x" %
405
              (offset, len(data)))
406

    
407
    if DEBUG_SHOW_MEMORY_ACTIONS:
408
        LOG.debug("writemem sent data offset=0x%4.4x len=0x%4.4x:\n%s" %
409
                  (offset, len(data), util.hexprint(data)))
410

    
411
    dlen = len(data)
412
    writemem = b"\x1d\x05" + \
413
        struct.pack("<BBHBB", dlen+8, 0, offset, dlen, 1) + \
414
        b"\x6a\x39\x57\x64"+data
415

    
416
    _send_command(serport, writemem)
417
    o = _receive_reply(serport)
418

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

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

    
432

    
433
def _resetradio(serport):
434
    resetpacket = b"\xdd\x05\x00\x00"
435
    _send_command(serport, resetpacket)
436

    
437

    
438
def do_download(radio):
439
    serport = radio.pipe
440
    serport.timeout = 0.5
441
    status = chirp_common.Status()
442
    status.cur = 0
443
    status.max = MEM_SIZE
444
    status.msg = "Downloading from radio"
445
    radio.status_fn(status)
446

    
447
    eeprom = b""
448
    f = _sayhello(serport)
449
    if f:
450
        radio.FIRMWARE_VERSION = f
451
    else:
452
        return False
453

    
454
    addr = 0
455
    while addr < MEM_SIZE:
456
        o = _readmem(serport, addr, MEM_BLOCK)
457
        status.cur = addr
458
        radio.status_fn(status)
459

    
460
        if o and len(o) == MEM_BLOCK:
461
            eeprom += o
462
            addr += MEM_BLOCK
463
        else:
464
            raise errors.RadioError("Memory download incomplete")
465

    
466
    return memmap.MemoryMapBytes(eeprom)
467

    
468

    
469
def do_upload(radio):
470
    serport = radio.pipe
471
    serport.timeout = 0.5
472
    status = chirp_common.Status()
473
    status.cur = 0
474
    status.max = PROG_SIZE
475
    status.msg = "Uploading to radio"
476
    radio.status_fn(status)
477

    
478
    f = _sayhello(serport)
479
    if f:
480
        radio.FIRMWARE_VERSION = f
481
    else:
482
        return False
483

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

    
496
    _resetradio(serport)
497

    
498
    return True
499

    
500

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

    
512

    
513
@directory.register
514
class UVK5Radio(chirp_common.CloneModeRadio):
515
    """Quansheng UV-K5"""
516
    VENDOR = "Quansheng"
517
    MODEL = "UV-K5"
518
    BAUD_RATE = 38400
519

    
520
    NEEDS_COMPAT_SERIAL = False
521
    FIRMWARE_VERSION = ""
522
    FIRMWARE_NOLIMITS = False
523

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

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

    
563
        # hack so we can input any frequency,
564
        # the 0.1 and 0.01 steps don't work unfortunately
565
        rf.valid_tuning_steps = [0.01, 0.1, 1.0] + STEPS
566

    
567
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
568
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
569
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
570

    
571
        rf.valid_characters = chirp_common.CHARSET_ASCII
572
        rf.valid_modes = ["FM", "NFM", "AM", "NAM"]
573
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
574

    
575
        rf.valid_skips = [""]
576

    
577
        # This radio supports memories 1-200, 201-214 are the VFO memories
578
        rf.memory_bounds = (1, 214)
579

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

    
591
    # Do a download of the radio from the serial port
592
    def sync_in(self):
593
        self._mmap = do_download(self)
594
        self.process_mmap()
595

    
596
    # Do an upload of the radio to the serial port
597
    def sync_out(self):
598
        do_upload(self)
599

    
600
    # Convert the raw byte array into a memory object structure
601
    def process_mmap(self):
602
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
603

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

    
609
    def validate_memory(self, mem):
610
        msgs = super().validate_memory(mem)
611
        return msgs
612

    
613
    def _set_tone(self, mem, _mem):
614
        ((txmode, txtone, txpol),
615
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
616

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

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

    
637
        _mem.code_flag = (_mem.code_flag & 0b11001100) | (
638
            txmoval << 4) | rxmoval
639
        _mem.rxcode = rxtoval
640
        _mem.txcode = txtoval
641

    
642
    def _get_tone(self, mem, _mem):
643
        rxtype = _mem.code_flag & 0x03
644
        txtype = (_mem.code_flag >> 4) & 0x03
645
        rx_tmode = TMODES[rxtype]
646
        tx_tmode = TMODES[txtype]
647

    
648
        rx_tone = tx_tone = None
649

    
650
        if tx_tmode == "Tone":
651
            if _mem.txcode < len(CTCSS_TONES):
652
                tx_tone = CTCSS_TONES[_mem.txcode]
653
            else:
654
                tx_tone = 0
655
                tx_tmode = ""
656
        elif tx_tmode == "DTCS":
657
            if _mem.txcode < len(DTCS_CODES):
658
                tx_tone = DTCS_CODES[_mem.txcode]
659
            else:
660
                tx_tone = 0
661
                tx_tmode = ""
662

    
663
        if rx_tmode == "Tone":
664
            if _mem.rxcode < len(CTCSS_TONES):
665
                rx_tone = CTCSS_TONES[_mem.rxcode]
666
            else:
667
                rx_tone = 0
668
                rx_tmode = ""
669
        elif rx_tmode == "DTCS":
670
            if _mem.rxcode < len(DTCS_CODES):
671
                rx_tone = DTCS_CODES[_mem.rxcode]
672
            else:
673
                rx_tone = 0
674
                rx_tmode = ""
675

    
676
        tx_pol = txtype == 0x03 and "R" or "N"
677
        rx_pol = rxtype == 0x03 and "R" or "N"
678

    
679
        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
680
                                       (rx_tmode, rx_tone, rx_pol))
681

    
682
    # Extract a high-level memory object from the low-level memory map
683
    # This is called to populate a memory in the UI
684

    
685
    def get_memory(self, number2):
686
        number = number2-1  # in the radio memories start with 0
687

    
688
        mem = chirp_common.Memory()
689

    
690
        # cutting and pasting configs from different radios
691
        # might try to set channel 0
692
        if number2 == 0:
693
            LOG.warning("Attempt to get channel 0")
694
            return mem
695

    
696
        _mem = self._memobj.channel[number]
697

    
698
        tmpcomment = ""
699

    
700
        mem.number = number2
701

    
702
        is_empty = False
703
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
704
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
705
            is_empty = True
706

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

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

    
733
            # actually the step and duplex are overwritten by chirp based on
734
            # bandplan. they are here to document sane defaults for IARU r1
735
            # mem.tuning_step = 25.0
736
            # mem.duplex = "off"
737

    
738
            return mem
739

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

    
751
        # Convert your low-level frequency to Hertz
752
        mem.freq = int(_mem.freq)*10
753
        mem.offset = int(_mem.offset)*10
754

    
755
        if (mem.offset == 0):
756
            mem.duplex = ''
757
        else:
758
            if (_mem.flags1 & FLAGS1_OFFSET_MASK) == FLAGS1_OFFSET_MINUS:
759
                mem.duplex = '-'
760
            elif (_mem.flags1 & FLAGS1_OFFSET_MASK) == FLAGS1_OFFSET_PLUS:
761
                mem.duplex = '+'
762
            else:
763
                mem.duplex = ''
764

    
765
        # tone data
766
        self._get_tone(mem, _mem)
767

    
768
        # mode
769
        if (_mem.flags1 & FLAGS1_ISAM) > 0:
770
            if (_mem.flags2 & FLAGS2_BANDWIDTH) > 0:
771
                mem.mode = "NAM"
772
            else:
773
                mem.mode = "AM"
774
        else:
775
            if (_mem.flags2 & FLAGS2_BANDWIDTH) > 0:
776
                mem.mode = "NFM"
777
            else:
778
                mem.mode = "FM"
779

    
780
        # tuning step
781
        tstep = _mem.step & 0x7
782
        if tstep < len(STEPS):
783
            mem.tuning_step = STEPS[tstep]
784
        else:
785
            mem.tuning_step = 2.5
786

    
787
        # power
788
        if (_mem.flags2 & FLAGS2_POWER_MASK) == FLAGS2_POWER_HIGH:
789
            mem.power = UVK5_POWER_LEVELS[2]
790
        elif (_mem.flags2 & FLAGS2_POWER_MASK) == FLAGS2_POWER_MEDIUM:
791
            mem.power = UVK5_POWER_LEVELS[1]
792
        else:
793
            mem.power = UVK5_POWER_LEVELS[0]
794

    
795
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
796
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
797
            mem.empty = True
798
        else:
799
            mem.empty = False
800

    
801
        mem.extra = RadioSettingGroup("Extra", "extra")
802

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

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

    
815
        # PTTID
816
        pttid = (_mem.dtmf_flags & FLAGS_DTMF_PTTID_MASK) >> 1
817
        rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
818
            PTTID_LIST, PTTID_LIST[pttid]))
819
        mem.extra.append(rs)
820
        tmpcomment += "PTTid:"+PTTID_LIST[pttid]+" "
821

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

    
829
        # Scrambler
830
        if _mem.scrambler & 0x0f < len(SCRAMBLER_LIST):
831
            enc = _mem.scrambler & 0x0f
832
        else:
833
            enc = 0
834

    
835
        rs = RadioSetting("scrambler", "Scrambler", RadioSettingValueList(
836
            SCRAMBLER_LIST, SCRAMBLER_LIST[enc]))
837
        mem.extra.append(rs)
838
        tmpcomment += "Scrambler:"+SCRAMBLER_LIST[enc]+" "
839

    
840
        return mem
841

    
842
    def set_settings(self, settings):
843
        _mem = self._memobj
844
        for element in settings:
845
            if not isinstance(element, RadioSetting):
846
                self.set_settings(element)
847
                continue
848

    
849
            # basic settings
850

    
851
            # call channel
852
            if element.get_name() == "call_channel":
853
                _mem.call_channel = int(element.value)-1
854

    
855
            # squelch
856
            if element.get_name() == "squelch":
857
                _mem.squelch = int(element.value)
858
            # TOT
859
            if element.get_name() == "tot":
860
                _mem.max_talk_time = int(element.value)
861
            # NOAA autoscan
862
            if element.get_name() == "noaa_autoscan":
863
                _mem.noaa_autoscan = element.value and 1 or 0
864

    
865
            # vox level
866
            if element.get_name() == "vox_level":
867
                _mem.vox_level = int(element.value)-1
868

    
869
            # mic gain
870
            if element.get_name() == "mic_gain":
871
                _mem.mic_gain = int(element.value)
872

    
873
            # Channel display mode
874
            if element.get_name() == "channel_display_mode":
875
                _mem.channel_display_mode = CHANNELDISP_LIST.index(
876
                    str(element.value))
877

    
878
            # Crossband receiving/transmitting
879
            if element.get_name() == "crossband":
880
                _mem.crossband = CROSSBAND_LIST.index(str(element.value))
881

    
882
            # Battery Save
883
            if element.get_name() == "battery_save":
884
                _mem.battery_save = BATSAVE_LIST.index(str(element.value))
885
            # Dual Watch
886
            if element.get_name() == "dualwatch":
887
                _mem.dual_watch = DUALWATCH_LIST.index(str(element.value))
888

    
889
            # Tail tone elimination
890
            if element.get_name() == "tail_note_elimination":
891
                _mem.tail_note_elimination = element.value and 1 or 0
892

    
893
            # VFO Open
894
            if element.get_name() == "vfo_open":
895
                _mem.vfo_open = element.value and 1 or 0
896

    
897
            # Beep control
898
            if element.get_name() == "beep_control":
899
                _mem.beep_control = element.value and 1 or 0
900

    
901
            # Scan resume mode
902
            if element.get_name() == "scan_resume_mode":
903
                _mem.scan_resume_mode = SCANRESUME_LIST.index(
904
                    str(element.value))
905

    
906
            # Auto keypad lock
907
            if element.get_name() == "auto_keypad_lock":
908
                _mem.auto_keypad_lock = element.value and 1 or 0
909

    
910
            # Power on display mode
911
            if element.get_name() == "welcome_mode":
912
                _mem.power_on_dispmode = WELCOME_LIST.index(str(element.value))
913

    
914
            # Keypad Tone
915
            if element.get_name() == "keypad_tone":
916
                _mem.keypad_tone = KEYPADTONE_LIST.index(str(element.value))
917

    
918
            # Language
919
            if element.get_name() == "language":
920
                _mem.language = LANGUAGE_LIST.index(str(element.value))
921

    
922
            # Alarm mode
923
            if element.get_name() == "alarm_mode":
924
                _mem.alarm_mode = ALARMMODE_LIST.index(str(element.value))
925

    
926
            # Reminding of end of talk
927
            if element.get_name() == "reminding_of_end_talk":
928
                _mem.reminding_of_end_talk = REMENDOFTALK_LIST.index(
929
                    str(element.value))
930

    
931
            # Repeater tail tone elimination
932
            if element.get_name() == "repeater_tail_elimination":
933
                _mem.repeater_tail_elimination = RTE_LIST.index(
934
                    str(element.value))
935

    
936
            # Logo string 1
937
            if element.get_name() == "logo1":
938
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
939
                _mem.logo_line1 = b[0:12]+"\xff\xff\xff\xff"
940

    
941
            # Logo string 2
942
            if element.get_name() == "logo2":
943
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
944
                _mem.logo_line2 = b[0:12]+"\xff\xff\xff\xff"
945

    
946
            # unlock settings
947

    
948
            # FLOCK
949
            if element.get_name() == "flock":
950
                _mem.int_flock = FLOCK_LIST.index(str(element.value))
951

    
952
            # 350TX
953
            if element.get_name() == "350tx":
954
                _mem.int_350tx = element.value and 1 or 0
955

    
956
            # UNKNOWN1
957
            if element.get_name() == "unknown1":
958
                _mem.int_unknown1 = element.value and 1 or 0
959

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

    
964
            # 500TX
965
            if element.get_name() == "500tx":
966
                _mem.int_500tx = element.value and 1 or 0
967

    
968
            # 350EN
969
            if element.get_name() == "350en":
970
                _mem.int_350en = element.value and 1 or 0
971

    
972
            # fm radio
973
            for i in range(1, 21):
974
                freqname = "FM_" + str(i)
975
                if element.get_name() == freqname:
976
                    val = str(element.value).strip()
977
                    try:
978
                        val2 = int(float(val)*10)
979
                    except Exception:
980
                        val2 = 0xffff
981

    
982
                    if val2 < FMMIN*10 or val2 > FMMAX*10:
983
                        val2 = 0xffff
984
#                        raise errors.InvalidValueError(
985
#                                "FM radio frequency should be a value "
986
#                                "in the range %.1f - %.1f" % (FMMIN , FMMAX))
987
                    _mem.fmfreq[i-1] = val2
988

    
989
    def get_settings(self):
990
        _mem = self._memobj
991
        basic = RadioSettingGroup("basic", "Basic Settings")
992
        unlock = RadioSettingGroup("unlock", "Unlock Settings")
993
        fmradio = RadioSettingGroup("fmradio", "FM Radio")
994

    
995
        roinfo = RadioSettingGroup("roinfo", "Driver information")
996

    
997
        top = RadioSettings(basic, unlock, fmradio, roinfo)
998

    
999
        # basic settings
1000

    
1001
        # call channel
1002
        tmpc = _mem.call_channel+1
1003
        if tmpc > 200:
1004
            tmpc = 1
1005
        rs = RadioSetting("call_channel", "One key call channel",
1006
                          RadioSettingValueInteger(1, 200, tmpc))
1007
        basic.append(rs)
1008

    
1009
        # squelch
1010
        tmpsq = _mem.squelch
1011
        if tmpsq > 9:
1012
            tmpsq = 1
1013
        rs = RadioSetting("squelch", "Squelch",
1014
                          RadioSettingValueInteger(0, 9, tmpsq))
1015
        basic.append(rs)
1016

    
1017
        # TOT
1018
        tmptot = _mem.max_talk_time
1019
        if tmptot > 10:
1020
            tmptot = 10
1021
        rs = RadioSetting(
1022
                "tot",
1023
                "Max talk time [min]",
1024
                RadioSettingValueInteger(0, 10, tmptot))
1025
        basic.append(rs)
1026

    
1027
        # NOAA autoscan
1028
        rs = RadioSetting(
1029
                "noaa_autoscan",
1030
                "NOAA Autoscan", RadioSettingValueBoolean(
1031
                    bool(_mem.noaa_autoscan > 0)))
1032
        basic.append(rs)
1033

    
1034
        # VOX Level
1035
        tmpvox = _mem.vox_level+1
1036
        if tmpvox > 10:
1037
            tmpvox = 10
1038
        rs = RadioSetting("vox_level", "VOX Level",
1039
                          RadioSettingValueInteger(1, 10, tmpvox))
1040
        basic.append(rs)
1041

    
1042
        # Mic gain
1043
        tmpmicgain = _mem.mic_gain
1044
        if tmpmicgain > 4:
1045
            tmpmicgain = 4
1046
        rs = RadioSetting("mic_gain", "Mic Gain",
1047
                          RadioSettingValueInteger(0, 4, tmpmicgain))
1048
        basic.append(rs)
1049

    
1050
        # Channel display mode
1051
        tmpchdispmode = _mem.channel_display_mode
1052
        if tmpchdispmode >= len(CHANNELDISP_LIST):
1053
            tmpchdispmode = 0
1054
        rs = RadioSetting(
1055
                "channel_display_mode",
1056
                "Channel display mode",
1057
                RadioSettingValueList(
1058
                    CHANNELDISP_LIST,
1059
                    CHANNELDISP_LIST[tmpchdispmode]))
1060
        basic.append(rs)
1061

    
1062
        # Crossband receiving/transmitting
1063
        tmpcross = _mem.crossband
1064
        if tmpcross >= len(CROSSBAND_LIST):
1065
            tmpcross = 0
1066
        rs = RadioSetting(
1067
                "crossband",
1068
                "Cross-band receiving/transmitting",
1069
                RadioSettingValueList(
1070
                    CROSSBAND_LIST,
1071
                    CROSSBAND_LIST[tmpcross]))
1072
        basic.append(rs)
1073

    
1074
        # Battery save
1075
        tmpbatsave = _mem.battery_save
1076
        if tmpbatsave >= len(BATSAVE_LIST):
1077
            tmpbatsave = BATSAVE_LIST.index("1:4")
1078
        rs = RadioSetting(
1079
                "battery_save",
1080
                "Battery Save",
1081
                RadioSettingValueList(
1082
                    BATSAVE_LIST,
1083
                    BATSAVE_LIST[tmpbatsave]))
1084
        basic.append(rs)
1085

    
1086
        # Dual watch
1087
        tmpdual = _mem.dual_watch
1088
        if tmpdual >= len(DUALWATCH_LIST):
1089
            tmpdual = 0
1090
        rs = RadioSetting("dualwatch", "Dual Watch", RadioSettingValueList(
1091
            DUALWATCH_LIST, DUALWATCH_LIST[tmpdual]))
1092
        basic.append(rs)
1093

    
1094
        # Tail tone elimination
1095
        rs = RadioSetting(
1096
                "tail_note_elimination",
1097
                "Tail tone elimination",
1098
                RadioSettingValueBoolean(
1099
                    bool(_mem.tail_note_elimination > 0)))
1100
        basic.append(rs)
1101

    
1102
        # VFO open
1103
        rs = RadioSetting("vfo_open", "VFO open",
1104
                          RadioSettingValueBoolean(bool(_mem.vfo_open > 0)))
1105
        basic.append(rs)
1106

    
1107
        # Beep control
1108
        rs = RadioSetting(
1109
                "beep_control",
1110
                "Beep control",
1111
                RadioSettingValueBoolean(bool(_mem.beep_control > 0)))
1112
        basic.append(rs)
1113

    
1114
        # Scan resume mode
1115
        tmpscanres = _mem.scan_resume_mode
1116
        if tmpscanres >= len(SCANRESUME_LIST):
1117
            tmpscanres = 0
1118
        rs = RadioSetting(
1119
                "scan_resume_mode",
1120
                "Scan resume mode",
1121
                RadioSettingValueList(
1122
                    SCANRESUME_LIST,
1123
                    SCANRESUME_LIST[tmpscanres]))
1124
        basic.append(rs)
1125

    
1126
        # Auto keypad lock
1127
        rs = RadioSetting(
1128
                "auto_keypad_lock",
1129
                "Auto keypad lock",
1130
                RadioSettingValueBoolean(bool(_mem.auto_keypad_lock > 0)))
1131
        basic.append(rs)
1132

    
1133
        # Power on display mode
1134
        tmpdispmode = _mem.power_on_dispmode
1135
        if tmpdispmode >= len(WELCOME_LIST):
1136
            tmpdispmode = 0
1137
        rs = RadioSetting(
1138
                "welcome_mode",
1139
                "Power on display mode",
1140
                RadioSettingValueList(
1141
                    WELCOME_LIST,
1142
                    WELCOME_LIST[tmpdispmode]))
1143
        basic.append(rs)
1144

    
1145
        # Keypad Tone
1146
        tmpkeypadtone = _mem.keypad_tone
1147
        if tmpkeypadtone >= len(KEYPADTONE_LIST):
1148
            tmpkeypadtone = 0
1149
        rs = RadioSetting("keypad_tone", "Keypad tone", RadioSettingValueList(
1150
            KEYPADTONE_LIST, KEYPADTONE_LIST[tmpkeypadtone]))
1151
        basic.append(rs)
1152

    
1153
        # Language
1154
        tmplanguage = _mem.language
1155
        if tmplanguage >= len(LANGUAGE_LIST):
1156
            tmplanguage = 0
1157
        rs = RadioSetting("language", "Language", RadioSettingValueList(
1158
            LANGUAGE_LIST, LANGUAGE_LIST[tmplanguage]))
1159
        basic.append(rs)
1160

    
1161
        # Alarm mode
1162
        tmpalarmmode = _mem.alarm_mode
1163
        if tmpalarmmode >= len(ALARMMODE_LIST):
1164
            tmpalarmmode = 0
1165
        rs = RadioSetting("alarm_mode", "Alarm mode", RadioSettingValueList(
1166
            ALARMMODE_LIST, ALARMMODE_LIST[tmpalarmmode]))
1167
        basic.append(rs)
1168

    
1169
        # Reminding of end of talk
1170
        tmpalarmmode = _mem.reminding_of_end_talk
1171
        if tmpalarmmode >= len(REMENDOFTALK_LIST):
1172
            tmpalarmmode = 0
1173
        rs = RadioSetting(
1174
                "reminding_of_end_talk",
1175
                "Reminding of end of talk",
1176
                RadioSettingValueList(
1177
                    REMENDOFTALK_LIST,
1178
                    REMENDOFTALK_LIST[tmpalarmmode]))
1179
        basic.append(rs)
1180

    
1181
        # Repeater tail tone elimination
1182
        tmprte = _mem.repeater_tail_elimination
1183
        if tmprte >= len(RTE_LIST):
1184
            tmprte = 0
1185
        rs = RadioSetting(
1186
                "repeater_tail_elimination",
1187
                "Repeater tail tone elimination",
1188
                RadioSettingValueList(RTE_LIST, RTE_LIST[tmprte]))
1189
        basic.append(rs)
1190

    
1191
        # Logo string 1
1192
        logo1 = str(_mem.logo_line1).strip("\x20\x00\xff")  # +"\x20"*12
1193
        logo1 = logo1[0:12]
1194
        rs = RadioSetting("logo1", "Logo string 1 (12 characters)",
1195
                          RadioSettingValueString(0, 12, logo1))
1196
        basic.append(rs)
1197

    
1198
        # Logo string 2
1199
        logo2 = str(_mem.logo_line2).strip("\x20\x00\xff")  # +"\x20"*12
1200
        logo2 = logo2[0:12]
1201
        rs = RadioSetting("logo2", "Logo string 2 (12 characters)",
1202
                          RadioSettingValueString(0, 12, logo2))
1203
        basic.append(rs)
1204

    
1205
        for i in range(1, 21):
1206
            freqname = "FM_"+str(i)
1207
            fmfreq = _mem.fmfreq[i-1]/10.0
1208
            if fmfreq < FMMIN or fmfreq > FMMAX:
1209
                rs = RadioSetting(freqname, freqname,
1210
                                  RadioSettingValueString(0, 5, ""))
1211
            else:
1212
                rs = RadioSetting(freqname, freqname,
1213
                                  RadioSettingValueString(0, 5, str(fmfreq)))
1214

    
1215
            fmradio.append(rs)
1216

    
1217
        # unlock settings
1218

    
1219
        # F-LOCK
1220
        tmpflock = _mem.int_flock
1221
        if tmpflock >= len(FLOCK_LIST):
1222
            tmpflock = 0
1223
        rs = RadioSetting(
1224
            "flock", "F-LOCK",
1225
            RadioSettingValueList(FLOCK_LIST, FLOCK_LIST[tmpflock]))
1226
        unlock.append(rs)
1227

    
1228
        # 350TX
1229
        rs = RadioSetting("350tx", "350TX", RadioSettingValueBoolean(
1230
            bool(_mem.int_350tx > 0)))
1231
        unlock.append(rs)
1232

    
1233
        # unknown1
1234
        rs = RadioSetting("unknown11", "UNKNOWN1",
1235
                          RadioSettingValueBoolean(
1236
                              bool(_mem.int_unknown1 > 0)))
1237
        unlock.append(rs)
1238

    
1239
        # 200TX
1240
        rs = RadioSetting("200tx", "200TX", RadioSettingValueBoolean(
1241
            bool(_mem.int_200tx > 0)))
1242
        unlock.append(rs)
1243

    
1244
        # 500TX
1245
        rs = RadioSetting("500tx", "500TX", RadioSettingValueBoolean(
1246
            bool(_mem.int_500tx > 0)))
1247
        unlock.append(rs)
1248

    
1249
        # 350EN
1250
        rs = RadioSetting("350en", "350EN", RadioSettingValueBoolean(
1251
            bool(_mem.int_350en > 0)))
1252
        unlock.append(rs)
1253

    
1254
        # SCREEN
1255
        rs = RadioSetting("screen", "SCREEN", RadioSettingValueBoolean(
1256
            bool(_mem.int_screen > 0)))
1257
        unlock.append(rs)
1258

    
1259
        # readonly info
1260
        # Firmware
1261
        if self.FIRMWARE_VERSION == "":
1262
            firmware = "To get the firmware version please download"
1263
            "the image from the radio first"
1264
        else:
1265
            firmware = self.FIRMWARE_VERSION
1266

    
1267
        val = RadioSettingValueString(0, 128, firmware)
1268
        val.set_mutable(False)
1269
        rs = RadioSetting("fw_ver", "Firmware Version", val)
1270
        roinfo.append(rs)
1271

    
1272
        # Driver version
1273
        val = RadioSettingValueString(0, 128, DRIVER_VERSION)
1274
        val.set_mutable(False)
1275
        rs = RadioSetting("driver_ver", "Driver version", val)
1276
        roinfo.append(rs)
1277

    
1278
        # No limits version for hacked firmware
1279
        val = RadioSettingValueBoolean(self.FIRMWARE_NOLIMITS)
1280
        val.set_mutable(False)
1281
        rs = RadioSetting("nolimits", "Limits disabled for modified firmware",
1282
                          val)
1283
        roinfo.append(rs)
1284

    
1285
        return top
1286

    
1287
    # Store details about a high-level memory to the memory map
1288
    # This is called when a user edits a memory in the UI
1289
    def set_memory(self, mem):
1290
        number = mem.number-1
1291

    
1292
        # Get a low-level memory object mapped to the image
1293
        _mem = self._memobj.channel[number]
1294
        _mem4 = self._memobj
1295
        # empty memory
1296
        if mem.empty:
1297
            _mem.set_raw("\xFF" * 16)
1298
            if number < 200:
1299
                _mem2 = self._memobj.channelname[number]
1300
                _mem2.set_raw("\xFF" * 16)
1301
                _mem4.channel_attributes[number] = 0x0f
1302
            return mem
1303

    
1304
        # clean the channel memory, restore some bits if it was used before
1305
        if _mem.get_raw()[0] == "\xff":
1306
            # this was an empty memory
1307
            _mem.set_raw("\x00" * 16)
1308
        else:
1309
            # this memory was't empty, save some bits that we don't know the
1310
            # meaning of, or that we don't support yet
1311
            prev_0a = ord(_mem.get_raw()[0x0a]) & SAVE_MASK_0A
1312
            prev_0b = ord(_mem.get_raw()[0x0b]) & SAVE_MASK_0B
1313
            prev_0c = ord(_mem.get_raw()[0x0c]) & SAVE_MASK_0C
1314
            prev_0d = ord(_mem.get_raw()[0x0d]) & SAVE_MASK_0D
1315
            prev_0e = ord(_mem.get_raw()[0x0e]) & SAVE_MASK_0E
1316
            prev_0f = ord(_mem.get_raw()[0x0f]) & SAVE_MASK_0F
1317
            _mem.set_raw("\x00" * 10 +
1318
                         chr(prev_0a) + chr(prev_0b) + chr(prev_0c) +
1319
                         chr(prev_0d) + chr(prev_0e) + chr(prev_0f))
1320

    
1321
        if number < 200:
1322
            _mem4.channel_attributes[number] = 0x0f
1323

    
1324
        # find tx frequency
1325
        if mem.duplex == '-':
1326
            txfreq = mem.freq - mem.offset
1327
        elif mem.duplex == '+':
1328
            txfreq = mem.freq + mem.offset
1329
        else:
1330
            txfreq = mem.freq
1331

    
1332
        # find band
1333
        band = _find_band(self, txfreq)
1334
        if band is False:
1335
            raise errors.RadioError(
1336
                    "Transmit frequency %.4fMHz is not supported by this radio"
1337
                    % txfreq/1000000.0)
1338

    
1339
        band = _find_band(self, mem.freq)
1340
        if band is False:
1341
            return mem
1342

    
1343
        # mode
1344
        if mem.mode == "NFM":
1345
            _mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
1346
            _mem.flags1 = _mem.flags1 & ~FLAGS1_ISAM
1347
        elif mem.mode == "FM":
1348
            _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1349
            _mem.flags1 = _mem.flags1 & ~FLAGS1_ISAM
1350
        elif mem.mode == "NAM":
1351
            _mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
1352
            _mem.flags1 = _mem.flags1 | FLAGS1_ISAM
1353
        elif mem.mode == "AM":
1354
            _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1355
            _mem.flags1 = _mem.flags1 | FLAGS1_ISAM
1356

    
1357
        # frequency/offset
1358
        _mem.freq = mem.freq/10
1359
        _mem.offset = mem.offset/10
1360

    
1361
        if mem.duplex == "off" or mem.duplex == "":
1362
            _mem.offset = 0
1363
            _mem.flags1 = _mem.flags1 & ~FLAGS1_OFFSET_MASK
1364
        elif mem.duplex == '-':
1365
            _mem.flags1 = (
1366
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_MINUS
1367
        elif mem.duplex == '+':
1368
            _mem.flags1 = (
1369
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_PLUS
1370

    
1371
        # set band
1372
        if number < 200:
1373
            _mem4.channel_attributes[number] = (
1374
                    _mem4.channel_attributes[number] & ~BANDMASK) | band
1375

    
1376
        # channels >200 are the 14 VFO chanells and don't have names
1377
        if number < 200:
1378
            _mem2 = self._memobj.channelname[number]
1379
            tag = mem.name.ljust(16)[:16]
1380
            _mem2.name = tag  # Store the alpha tag
1381

    
1382
        # tone data
1383
        self._set_tone(mem, _mem)
1384

    
1385
        # step
1386
        _mem.step = STEPS.index(mem.tuning_step)
1387

    
1388
        # tx power
1389
        if str(mem.power) == str(UVK5_POWER_LEVELS[2]):
1390
            _mem.flags2 = (
1391
                _mem.flags2 & ~FLAGS2_POWER_MASK) | FLAGS2_POWER_HIGH
1392
        elif str(mem.power) == str(UVK5_POWER_LEVELS[1]):
1393
            _mem.flags2 = (
1394
                _mem.flags2 & ~FLAGS2_POWER_MASK) | FLAGS2_POWER_MEDIUM
1395
        else:
1396
            _mem.flags2 = (_mem.flags2 & ~FLAGS2_POWER_MASK)
1397

    
1398
        for setting in mem.extra:
1399
            sname = setting.get_name()
1400
            svalue = setting.value.get_value()
1401

    
1402
            if sname == "bclo":
1403
                if svalue:
1404
                    _mem.flags2 = _mem.flags2 | FLAGS2_BCLO
1405
                else:
1406
                    _mem.flags2 = _mem.flags2 & ~FLAGS2_BCLO
1407

    
1408
            if sname == "pttid":
1409
                _mem.dtmf_flags = (
1410
                        (_mem.dtmf_flags & ~FLAGS_DTMF_PTTID_MASK)
1411
                        | (PTTID_LIST.index(svalue) << 1))
1412

    
1413
            if sname == "frev":
1414
                if svalue:
1415
                    _mem.flags2 = _mem.flags2 | FLAGS2_REVERSE
1416
                else:
1417
                    _mem.flags2 = _mem.flags2 & ~FLAGS2_REVERSE
1418

    
1419
            if sname == "dtmfdecode":
1420
                if svalue:
1421
                    _mem.dtmf_flags = _mem.dtmf_flags | FLAGS_DTMF_DECODE
1422
                else:
1423
                    _mem.dtmf_flags = _mem.dtmf_flags & ~FLAGS_DTMF_DECODE
1424

    
1425
            if sname == "scrambler":
1426
                _mem.scrambler = (
1427
                    _mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue)
1428

    
1429
        return mem
1430

    
1431

    
1432
@directory.register
1433
class UVK5Radio_nolimit(UVK5Radio):
1434
    VENDOR = "Quansheng"
1435
    MODEL = "UV-K5 (modified firmware)"
1436
    VARIANT = "nolimits"
1437
    FIRMWARE_NOLIMITS = True
1438

    
1439
    def get_features(self):
1440
        rf = UVK5Radio.get_features(self)
1441
        # This is what the BK4819 chip supports
1442
        rf.valid_bands = [(18000000,  620000000),
1443
                          (840000000, 1300000000)
1444
                          ]
1445
        return rf
(38-38/47)