Project

General

Profile

New Model #10478 » uvk5.py

chirp driver for UV-K5, version 20230602 - Jacek Lipkowski SQ5BPF, 06/03/2023 12:32 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
# import serial
33

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

    
41
LOG = logging.getLogger(__name__)
42

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

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

    
52
DRIVER_VERSION = "Quansheng UV-K5 driver v20230602 (c) Jacek Lipkowski SQ5BPF"
53
PRINT_CONSOLE = False
54

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

    
70
#seekto 0xd60;
71
u8 channel_attributes[200];
72

    
73
#seekto 0xe40;
74
ul16 fmfreq[20];
75

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

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

    
101
#seekto 0xea0;
102
u8 keypad_tone;
103
u8 language;
104

    
105
#seekto 0xea8;
106
u8 alarm_mode;
107
u8 reminding_of_end_talk;
108
u8 repeater_tail_elimination;
109

    
110
#seekto 0xeb0;
111
char logo_line1[16];
112
char logo_line2[16];
113

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

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

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

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

    
143
FLAGS1_ISSCANLIST = 0b100
144
FLAGS1_ISAM = 0b10000
145

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

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

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

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

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

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

    
182
# steps
183
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 8.33]
184

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

    
192

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

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

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

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

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

    
245
VFO_CHANNEL_NAMES = ["F1(50M-76M)A", "F1(50M-76M)B",
246
                     "F2(108M-136M)A", "F2(108M-136M)B",
247
                     "F3(136M-174M)A", "F3(136M-174M)B",
248
                     "F4(174M-350M)A", "F4(174M-350M)B",
249
                     "F5(350M-400M)A", "F5(350M-400M)B",
250
                     "F6(400M-470M)A", "F6(400M-470M)B",
251
                     "F7(470M-600M)A", "F7(470M-600M)B"]
252

    
253

    
254
# the communication is obfuscated using this fine mechanism
255
def xorarr(data: bytes):
256
    tbl = [22, 108, 20, 230, 46, 145, 13, 64, 33, 53, 213, 64, 19, 3, 233, 128]
257
    x = b""
258
    r = 0
259
    for byte in data:
260
        x += bytes([byte ^ tbl[r]])
261
        r = (r+1) % len(tbl)
262
    return x
263

    
264

    
265
# if this crc was used for communication to AND from the radio, then it
266
# would be a measure to increase reliability.
267
# but it's only used towards the radio, so it's for further obfuscation
268
def calculate_crc16_xmodem(data: bytes):
269
    poly = 0x1021
270
    crc = 0x0
271
    for byte in data:
272
        crc = crc ^ (byte << 8)
273
        for i in range(8):
274
            crc = crc << 1
275
            if (crc & 0x10000):
276
                crc = (crc ^ poly) & 0xFFFF
277
    return crc & 0xFFFF
278

    
279

    
280
def _send_command(serport, data: bytes):
281
    """Send a command to UV-K5 radio"""
282
    LOG.debug("Sending command (unobfuscated) len=0x%4.4x:\n%s" %
283
              (len(data), util.hexprint(data)))
284

    
285
    crc = calculate_crc16_xmodem(data)
286
    data2 = data+bytes([crc & 0xff, (crc >> 8) & 0xff])
287

    
288
    command = b"\xAB\xCD"+bytes([len(data)])+b"\x00"+xorarr(data2)+b"\xDC\xBA"
289
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
290
        LOG.debug("Sending command (obfuscated):\n%s" % util.hexprint(command))
291
    try:
292
        result = serport.write(command)
293
    except Exception:
294
        raise errors.RadioError("Error writing data to radio")
295
    return result
296

    
297

    
298
def _receive_reply(serport):
299
    header = serport.read(4)
300
    if len(header) != 4:
301
        LOG.warning("Header short read: [%s] len=%i" %
302
                    (util.hexprint(header), len(header)))
303
        raise errors.RadioError("Header short read")
304
    if header[0] != 0xAB or header[1] != 0xCD or header[3] != 0x00:
305
        LOG.warning("Bad response header: %s len=%i" %
306
                    (util.hexprint(header), len(header)))
307
        raise errors.RadioError("Bad response header")
308

    
309
        return False
310

    
311
    cmd = serport.read(int(header[2]))
312
    if len(cmd) != int(header[2]):
313
        LOG.warning("Body short read: [%s] len=%i" %
314
                    (util.hexprint(cmd), len(cmd)))
315
        raise errors.RadioError("Command body short read")
316

    
317
    footer = serport.read(4)
318

    
319
    if len(footer) != 4:
320
        LOG.warning("Footer short read: [%s] len=%i" %
321
                    (util.hexprint(footer), len(footer)))
322
        raise errors.RadioError("Footer short read")
323

    
324
    if footer[2] != 0xDC or footer[3] != 0xBA:
325
        LOG.debug(
326
                "Reply before bad response footer (obfuscated)"
327
                "len=0x%4.4x:\n%s" % (len(cmd), util.hexprint(cmd)))
328
        LOG.warning("Bad response footer: %s len=%i" %
329
                    (util.hexprint(footer), len(footer)))
330
        raise errors.RadioError("Bad response footer")
331
        return False
332

    
333
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
334
        LOG.debug("Received reply (obfuscated) len=0x%4.4x:\n%s" %
335
                  (len(cmd), util.hexprint(cmd)))
336

    
337
    cmd2 = xorarr(cmd)
338

    
339
    LOG.debug("Received reply (unobfuscated) len=0x%4.4x:\n%s" %
340
              (len(cmd2), util.hexprint(cmd2)))
341

    
342
    return cmd2
343

    
344

    
345
def _getstring(data: bytes, begin, maxlen):
346
    s = ""
347
    c = 0
348
    for i in data:
349
        c += 1
350
        if c < begin:
351
            continue
352
        if i < ord(' ') or i > ord('~'):
353
            break
354
        s += chr(i)
355
    return s
356

    
357

    
358
def _sayhello(serport):
359
    hellopacket = b"\x14\x05\x04\x00\x6a\x39\x57\x64"
360

    
361
    tries = 5
362
    while (True):
363
        LOG.debug("Sending hello packet")
364
        _send_command(serport, hellopacket)
365
        o = _receive_reply(serport)
366
        if (o):
367
            break
368
        tries -= 1
369
        if tries == 0:
370
            LOG.warning("Failed to initialise radio")
371
            raise errors.RadioError("Failed to initialize radio")
372
            return False
373
    firmware = _getstring(o, 5, 16)
374
    LOG.info("Found firmware: %s" % firmware)
375
    return firmware
376

    
377

    
378
def _readmem(serport, offset, length):
379
    LOG.debug("Sending readmem offset=0x%4.4x len=0x%4.4x" % (offset, length))
380

    
381
    readmem = b"\x1b\x05\x08\x00" + \
382
        bytes([offset & 0xff, (offset >> 8) & 0xff, length, 0]) + \
383
        b"\x6a\x39\x57\x64"
384
    _send_command(serport, readmem)
385
    o = _receive_reply(serport)
386
    if DEBUG_SHOW_MEMORY_ACTIONS:
387
        LOG.debug("readmem Received data len=0x%4.4x:\n%s" %
388
                  (len(o), util.hexprint(o)))
389
    return o[8:]
390

    
391

    
392
def _writemem(serport, data, offset):
393
    LOG.debug("Sending writemem offset=0x%4.4x len=0x%4.4x" %
394
              (offset, len(data)))
395

    
396
    if DEBUG_SHOW_MEMORY_ACTIONS:
397
        LOG.debug("writemem sent data offset=0x%4.4x len=0x%4.4x:\n%s" %
398
                  (offset, len(data), util.hexprint(data)))
399

    
400
    dlen = len(data)
401
    writemem = b"\x1d\x05"+bytes([dlen+8])+b"\x00" + \
402
        bytes([offset & 0xff, (offset >> 8) & 0xff, dlen, 1]) + \
403
        b"\x6a\x39\x57\x64"+data
404

    
405
    _send_command(serport, writemem)
406
    o = _receive_reply(serport)
407

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

    
410
    if (o[0] == 0x1e
411
            and
412
            o[4] == (offset & 0xff)
413
            and
414
            o[5] == (offset >> 8) & 0xff):
415
        return True
416
    else:
417
        LOG.warning("Bad data from writemem")
418
        raise errors.RadioError("Bad response to writemem")
419
    return False
420

    
421

    
422
def _resetradio(serport):
423
    resetpacket = b"\xdd\x05\x00\x00"
424
    _send_command(serport, resetpacket)
425

    
426

    
427
def do_download(radio):
428
    serport = radio.pipe
429
    serport.timeout = 0.5
430
    status = chirp_common.Status()
431
    status.cur = 0
432
    status.max = MEM_SIZE
433
    status.msg = "Downloading from radio"
434
    radio.status_fn(status)
435

    
436
    eeprom = b""
437
    f = _sayhello(serport)
438
    if f:
439
        radio.FIRMWARE_VERSION = f
440
    else:
441
        return False
442

    
443
    addr = 0
444
    while addr < MEM_SIZE:
445
        o = _readmem(serport, addr, MEM_BLOCK)
446
        status.cur = addr
447
        radio.status_fn(status)
448

    
449
        if o and len(o) == MEM_BLOCK:
450
            eeprom += o
451
            addr += MEM_BLOCK
452
        else:
453
            raise errors.RadioError("Memory download incomplete")
454

    
455
    return memmap.MemoryMapBytes(eeprom)
456

    
457

    
458
def do_upload(radio):
459
    serport = radio.pipe
460
    serport.timeout = 0.5
461
    status = chirp_common.Status()
462
    status.cur = 0
463
    status.max = PROG_SIZE
464
    status.msg = "Uploading to radio"
465
    radio.status_fn(status)
466

    
467
    f = _sayhello(serport)
468
    if f:
469
        radio.FIRMWARE_VERSION = f
470
    else:
471
        return False
472

    
473
    addr = 0
474
    while addr < PROG_SIZE:
475
        o = radio.get_mmap()[addr:addr+MEM_BLOCK]
476
        _writemem(serport, o, addr)
477
        status.cur = addr
478
        radio.status_fn(status)
479
        if o:
480
            addr += MEM_BLOCK
481
        else:
482
            raise errors.RadioError("Memory upload incomplete")
483
    status.msg = "Uploaded OK"
484

    
485
    _resetradio(serport)
486

    
487
    return True
488

    
489

    
490
def _find_band(hz):
491
    mhz = hz/1000000.0
492
    for a in BANDS:
493
        if mhz >= BANDS[a][0] and mhz <= BANDS[a][1]:
494
            return a
495
    return False
496

    
497

    
498
@directory.register
499
class TemplateRadio(chirp_common.CloneModeRadio):
500
    """Quansheng UV-K5"""
501
    VENDOR = "Quansheng"
502
    MODEL = "UV-K5"
503
    BAUD_RATE = 38400
504

    
505
    NEEDS_COMPAT_SERIAL = False
506
    FIRMWARE_VERSION = ""
507

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

    
534
    # Return information about this radio's features, including
535
    # how many memories it has, what bands it supports, etc
536
    def get_features(self):
537
        rf = chirp_common.RadioFeatures()
538
        rf.has_bank = False
539
        rf.valid_dtcs_codes = DTCS_CODES
540
        rf.has_rx_dtcs = True
541
        rf.has_ctone = True
542
        rf.has_settings = True
543
        rf.has_comment = False
544
        rf.valid_name_length = 16
545
        rf.valid_power_levels = UVK5_POWER_LEVELS
546

    
547
        # hack so we can input any frequency,
548
        # the 0.1 and 0.01 steps don't work unfortunately
549
        rf.valid_tuning_steps = [0.01, 0.1, 1.0] + STEPS
550

    
551
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
552
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
553
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
554

    
555
        rf.valid_characters = chirp_common.CHARSET_ASCII
556
        rf.valid_modes = ["FM", "NFM", "AM", "NAM"]
557
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
558

    
559
        rf.valid_skips = [""]
560

    
561
        # This radio supports memories 1-200, 201-214 are the VFO memories
562
        rf.memory_bounds = (1, 214)
563

    
564
        # This is what the BK4819 chip supports
565
        # Will leave it in a comment, might be useful someday
566
        # rf.valid_bands = [(18000000,  620000000),
567
        #                  (840000000, 1300000000)
568
        #                  ]
569
        rf.valid_bands = []
570
        for a in BANDS:
571
            rf.valid_bands.append(
572
                    (int(BANDS[a][0]*1000000), int(BANDS[a][1]*1000000)))
573
        return rf
574

    
575
    # Do a download of the radio from the serial port
576
    def sync_in(self):
577
        self._mmap = do_download(self)
578
        self.process_mmap()
579

    
580
    # Do an upload of the radio to the serial port
581
    def sync_out(self):
582
        do_upload(self)
583

    
584
    # Convert the raw byte array into a memory object structure
585
    def process_mmap(self):
586
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
587

    
588
    # Return a raw representation of the memory object, which
589
    # is very helpful for development
590
    def get_raw_memory(self, number):
591
        return repr(self._memobj.channel[number-1])
592

    
593
    def validate_memory(self, mem):
594
        msgs = super().validate_memory(mem)
595
        return msgs
596

    
597
    def _set_tone(self, mem, _mem):
598
        ((txmode, txtone, txpol),
599
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
600

    
601
        if txmode == "Tone":
602
            txtoval = CTCSS_TONES.index(txtone)
603
            txmoval = 0b01
604
        elif txmode == "DTCS":
605
            txmoval = txpol == "R" and 0b11 or 0b10
606
            txtoval = DTCS_CODES.index(txtone)
607
        else:
608
            txmoval = 0
609
            txtoval = 0
610

    
611
        if rxmode == "Tone":
612
            rxtoval = CTCSS_TONES.index(rxtone)
613
            rxmoval = 0b01
614
        elif rxmode == "DTCS":
615
            rxmoval = rxpol == "R" and 0b11 or 0b10
616
            rxtoval = DTCS_CODES.index(rxtone)
617
        else:
618
            rxmoval = 0
619
            rxtoval = 0
620

    
621
        _mem.code_flag = (_mem.code_flag & 0b11001100) | (
622
            txmoval << 4) | rxmoval
623
        _mem.rxcode = rxtoval
624
        _mem.txcode = txtoval
625

    
626
    def _get_tone(self, mem, _mem):
627
        rxtype = _mem.code_flag & 0x03
628
        txtype = (_mem.code_flag >> 4) & 0x03
629
        rx_tmode = TMODES[rxtype]
630
        tx_tmode = TMODES[txtype]
631

    
632
        rx_tone = tx_tone = None
633

    
634
        if tx_tmode == "Tone":
635
            if _mem.txcode < len(CTCSS_TONES):
636
                tx_tone = CTCSS_TONES[_mem.txcode]
637
            else:
638
                tx_tone = 0
639
                tx_tmode = ""
640
        elif tx_tmode == "DTCS":
641
            if _mem.txcode < len(DTCS_CODES):
642
                tx_tone = DTCS_CODES[_mem.txcode]
643
            else:
644
                tx_tone = 0
645
                tx_tmode = ""
646

    
647
        if rx_tmode == "Tone":
648
            if _mem.rxcode < len(CTCSS_TONES):
649
                rx_tone = CTCSS_TONES[_mem.rxcode]
650
            else:
651
                rx_tone = 0
652
                rx_tmode = ""
653
        elif rx_tmode == "DTCS":
654
            if _mem.rxcode < len(DTCS_CODES):
655
                rx_tone = DTCS_CODES[_mem.rxcode]
656
            else:
657
                rx_tone = 0
658
                rx_tmode = ""
659

    
660
        tx_pol = txtype == 0x03 and "R" or "N"
661
        rx_pol = rxtype == 0x03 and "R" or "N"
662

    
663
        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
664
                                       (rx_tmode, rx_tone, rx_pol))
665

    
666
    # Extract a high-level memory object from the low-level memory map
667
    # This is called to populate a memory in the UI
668

    
669
    def get_memory(self, number2):
670
        number = number2-1  # in the radio memories start with 0
671

    
672
        mem = chirp_common.Memory()
673

    
674
        # cutting and pasting configs from different radios
675
        # might try to set channel 0
676
        if number2 == 0:
677
            LOG.warning("Attempt to get channel 0")
678
            return mem
679

    
680
        _mem = self._memobj.channel[number]
681

    
682
        tmpcomment = ""
683

    
684
        mem.number = number2
685

    
686
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
687
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
688
            mem.empty = True
689
            # set some sane defaults:
690
            mem.power = UVK5_POWER_LEVELS[2]
691
            mem.extra = RadioSettingGroup("Extra", "extra")
692
            rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(False))
693
            mem.extra.append(rs)
694
            rs = RadioSetting("frev", "FreqRev",
695
                              RadioSettingValueBoolean(False))
696
            mem.extra.append(rs)
697
            rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
698
                PTTID_LIST, PTTID_LIST[0]))
699
            mem.extra.append(rs)
700
            rs = RadioSetting("dtmfdecode", "DTMF decode",
701
                              RadioSettingValueBoolean(False))
702
            mem.extra.append(rs)
703
            rs = RadioSetting("scrambler", "Scrambler", RadioSettingValueList(
704
                SCRAMBLER_LIST, SCRAMBLER_LIST[0]))
705
            mem.extra.append(rs)
706

    
707
            # actually the step and duplex are overwritten by chirp based on
708
            # bandplan. they are here to document sane defaults for IARU r1
709
            # mem.tuning_step = 25.0
710
            # mem.duplex = "off"
711

    
712
            return mem
713

    
714
        if number > 199:
715
            mem.name = VFO_CHANNEL_NAMES[number-200]
716
            mem.immutable = ["name"]
717
        else:
718
            _mem2 = self._memobj.channelname[number]
719
            for char in _mem2.name:
720
                if str(char) == "\xFF" or str(char) == "\x00":
721
                    break
722
                mem.name += str(char)
723
            mem.name = mem.name.rstrip()
724

    
725
        # Convert your low-level frequency to Hertz
726
        mem.freq = int(_mem.freq)*10
727
        mem.offset = int(_mem.offset)*10
728

    
729
        if (mem.offset == 0):
730
            mem.duplex = ''
731
        else:
732
            if (_mem.flags1 & FLAGS1_OFFSET_MASK) == FLAGS1_OFFSET_MINUS:
733
                mem.duplex = '-'
734
            elif (_mem.flags1 & FLAGS1_OFFSET_MASK) == FLAGS1_OFFSET_PLUS:
735
                mem.duplex = '+'
736
            else:
737
                mem.duplex = ''
738

    
739
        # tone data
740
        self._get_tone(mem, _mem)
741

    
742
        # mode
743
        if (_mem.flags1 & FLAGS1_ISAM) > 0:
744
            if (_mem.flags2 & FLAGS2_BANDWIDTH) > 0:
745
                mem.mode = "NAM"
746
            else:
747
                mem.mode = "AM"
748
        else:
749
            if (_mem.flags2 & FLAGS2_BANDWIDTH) > 0:
750
                mem.mode = "NFM"
751
            else:
752
                mem.mode = "FM"
753

    
754
        # tuning step
755
        tstep = _mem.step & 0x7
756
        if tstep < len(STEPS):
757
            mem.tuning_step = STEPS[tstep]
758
        else:
759
            mem.tuning_step = 2.5
760

    
761
        # power
762
        if (_mem.flags2 & FLAGS2_POWER_MASK) == FLAGS2_POWER_HIGH:
763
            mem.power = UVK5_POWER_LEVELS[2]
764
        elif (_mem.flags2 & FLAGS2_POWER_MASK) == FLAGS2_POWER_MEDIUM:
765
            mem.power = UVK5_POWER_LEVELS[1]
766
        else:
767
            mem.power = UVK5_POWER_LEVELS[0]
768

    
769
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
770
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
771
            mem.empty = True
772
        else:
773
            mem.empty = False
774

    
775
        mem.extra = RadioSettingGroup("Extra", "extra")
776

    
777
        # BCLO
778
        is_bclo = bool(_mem.flags2 & FLAGS2_BCLO > 0)
779
        rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(is_bclo))
780
        mem.extra.append(rs)
781
        tmpcomment += "BCLO:"+(is_bclo and "ON" or "off")+" "
782

    
783
        # Frequency reverse - whatever that means, don't see it in the manual
784
        is_frev = bool(_mem.flags2 & FLAGS2_REVERSE > 0)
785
        rs = RadioSetting("frev", "FreqRev", RadioSettingValueBoolean(is_frev))
786
        mem.extra.append(rs)
787
        tmpcomment += "FreqReverse:"+(is_frev and "ON" or "off")+" "
788

    
789
        # PTTID
790
        pttid = (_mem.dtmf_flags & FLAGS_DTMF_PTTID_MASK) >> 1
791
        rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
792
            PTTID_LIST, PTTID_LIST[pttid]))
793
        mem.extra.append(rs)
794
        tmpcomment += "PTTid:"+PTTID_LIST[pttid]+" "
795

    
796
        # DTMF DECODE
797
        is_dtmf = bool(_mem.dtmf_flags & FLAGS_DTMF_DECODE > 0)
798
        rs = RadioSetting("dtmfdecode", "DTMF decode",
799
                          RadioSettingValueBoolean(is_dtmf))
800
        mem.extra.append(rs)
801
        tmpcomment += "DTMFdecode:"+(is_dtmf and "ON" or "off")+" "
802

    
803
        # Scrambler
804
        if _mem.scrambler & 0x0f < len(SCRAMBLER_LIST):
805
            enc = _mem.scrambler & 0x0f
806
        else:
807
            enc = 0
808

    
809
        rs = RadioSetting("scrambler", "Scrambler", RadioSettingValueList(
810
            SCRAMBLER_LIST, SCRAMBLER_LIST[enc]))
811
        mem.extra.append(rs)
812
        tmpcomment += "Scrambler:"+SCRAMBLER_LIST[enc]+" "
813

    
814
        return mem
815

    
816
    def set_settings(self, settings):
817
        _mem = self._memobj
818
        for element in settings:
819
            if not isinstance(element, RadioSetting):
820
                self.set_settings(element)
821
                continue
822

    
823
            # basic settings
824

    
825
            # call channel
826
            if element.get_name() == "call_channel":
827
                _mem.call_channel = int(element.value)-1
828

    
829
            # squelch
830
            if element.get_name() == "squelch":
831
                _mem.squelch = int(element.value)
832
            # TOT
833
            if element.get_name() == "tot":
834
                _mem.max_talk_time = int(element.value)
835
            # NOAA autoscan
836
            if element.get_name() == "noaa_autoscan":
837
                _mem.noaa_autoscan = element.value and 1 or 0
838

    
839
            # vox level
840
            if element.get_name() == "vox_level":
841
                _mem.vox_level = int(element.value)-1
842

    
843
            # mic gain
844
            if element.get_name() == "mic_gain":
845
                _mem.mic_gain = int(element.value)
846

    
847
            # Channel display mode
848
            if element.get_name() == "channel_display_mode":
849
                _mem.channel_display_mode = CHANNELDISP_LIST.index(
850
                    str(element.value))
851

    
852
            # Crossband receiving/transmitting
853
            if element.get_name() == "crossband":
854
                _mem.crossband = CROSSBAND_LIST.index(str(element.value))
855

    
856
            # Battery Save
857
            if element.get_name() == "battery_save":
858
                _mem.battery_save = BATSAVE_LIST.index(str(element.value))
859
            # Dual Watch
860
            if element.get_name() == "dualwatch":
861
                _mem.dual_watch = DUALWATCH_LIST.index(str(element.value))
862

    
863
            # Tail tone elimination
864
            if element.get_name() == "tail_note_elimination":
865
                _mem.tail_note_elimination = element.value and 1 or 0
866

    
867
            # VFO Open
868
            if element.get_name() == "vfo_open":
869
                _mem.vfo_open = element.value and 1 or 0
870

    
871
            # Beep control
872
            if element.get_name() == "beep_control":
873
                _mem.beep_control = element.value and 1 or 0
874

    
875
            # Scan resume mode
876
            if element.get_name() == "scan_resume_mode":
877
                _mem.scan_resume_mode = SCANRESUME_LIST.index(
878
                    str(element.value))
879

    
880
            # Auto keypad lock
881
            if element.get_name() == "auto_keypad_lock":
882
                _mem.auto_keypad_lock = element.value and 1 or 0
883

    
884
            # Power on display mode
885
            if element.get_name() == "welcome_mode":
886
                _mem.power_on_dispmode = WELCOME_LIST.index(str(element.value))
887

    
888
            # Keypad Tone
889
            if element.get_name() == "keypad_tone":
890
                _mem.keypad_tone = KEYPADTONE_LIST.index(str(element.value))
891

    
892
            # Language
893
            if element.get_name() == "language":
894
                _mem.language = LANGUAGE_LIST.index(str(element.value))
895

    
896
            # Alarm mode
897
            if element.get_name() == "alarm_mode":
898
                _mem.alarm_mode = ALARMMODE_LIST.index(str(element.value))
899

    
900
            # Reminding of end of talk
901
            if element.get_name() == "reminding_of_end_talk":
902
                _mem.reminding_of_end_talk = REMENDOFTALK_LIST.index(
903
                    str(element.value))
904

    
905
            # Repeater tail tone elimination
906
            if element.get_name() == "repeater_tail_elimination":
907
                _mem.repeater_tail_elimination = RTE_LIST.index(
908
                    str(element.value))
909

    
910
            # Logo string 1
911
            if element.get_name() == "logo1":
912
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
913
                _mem.logo_line1 = b[0:12]+"\xff\xff\xff\xff"
914

    
915
            # Logo string 2
916
            if element.get_name() == "logo2":
917
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
918
                _mem.logo_line2 = b[0:12]+"\xff\xff\xff\xff"
919

    
920
            # unlock settings
921

    
922
            # FLOCK
923
            if element.get_name() == "flock":
924
                _mem.int_flock = FLOCK_LIST.index(str(element.value))
925

    
926
            # 350TX
927
            if element.get_name() == "350tx":
928
                _mem.int_350tx = element.value and 1 or 0
929

    
930
            # UNKNOWN1
931
            if element.get_name() == "unknown1":
932
                _mem.int_unknown1 = element.value and 1 or 0
933

    
934
            # 200TX
935
            if element.get_name() == "200tx":
936
                _mem.int_200tx = element.value and 1 or 0
937

    
938
            # 500TX
939
            if element.get_name() == "500tx":
940
                _mem.int_500tx = element.value and 1 or 0
941

    
942
            # 350EN
943
            if element.get_name() == "350en":
944
                _mem.int_350en = element.value and 1 or 0
945

    
946
    def get_settings(self):
947
        _mem = self._memobj
948
        basic = RadioSettingGroup("basic", "Basic Settings")
949
        unlock = RadioSettingGroup("unlock", "Unlock Settings")
950
        fmradio = RadioSettingGroup("fmradio", "FM Radio (unimplemented yet)")
951
        roinfo = RadioSettingGroup("roinfo", "Driver information")
952

    
953
        top = RadioSettings(basic, unlock, fmradio, roinfo)
954

    
955
        # basic settings
956

    
957
        # call channel
958
        tmpc = _mem.call_channel+1
959
        if tmpc > 200:
960
            tmpc = 1
961
        rs = RadioSetting("call_channel", "One key call channel",
962
                          RadioSettingValueInteger(1, 200, tmpc))
963
        basic.append(rs)
964

    
965
        # squelch
966
        tmpsq = _mem.squelch
967
        if tmpsq > 9:
968
            tmpsq = 1
969
        rs = RadioSetting("squelch", "Squelch",
970
                          RadioSettingValueInteger(0, 9, tmpsq))
971
        basic.append(rs)
972

    
973
        # TOT
974
        tmptot = _mem.max_talk_time
975
        if tmptot > 10:
976
            tmptot = 10
977
        rs = RadioSetting(
978
                "tot",
979
                "Max talk time [min]",
980
                RadioSettingValueInteger(0, 10, tmptot))
981
        basic.append(rs)
982

    
983
        # NOAA autoscan
984
        rs = RadioSetting(
985
                "noaa_autoscan",
986
                "NOAA Autoscan", RadioSettingValueBoolean(
987
                    bool(_mem.noaa_autoscan > 0)))
988
        basic.append(rs)
989

    
990
        # VOX Level
991
        tmpvox = _mem.vox_level+1
992
        if tmpvox > 10:
993
            tmpvox = 10
994
        rs = RadioSetting("vox_level", "VOX Level",
995
                          RadioSettingValueInteger(1, 10, tmpvox))
996
        basic.append(rs)
997

    
998
        # Mic gain
999
        tmpmicgain = _mem.mic_gain
1000
        if tmpmicgain > 4:
1001
            tmpmicgain = 4
1002
        rs = RadioSetting("mic_gain", "Mic Gain",
1003
                          RadioSettingValueInteger(0, 4, tmpmicgain))
1004
        basic.append(rs)
1005

    
1006
        # Channel display mode
1007
        tmpchdispmode = _mem.channel_display_mode
1008
        if tmpchdispmode >= len(CHANNELDISP_LIST):
1009
            tmpchdispmode = 0
1010
        rs = RadioSetting(
1011
                "channel_display_mode",
1012
                "Channel display mode",
1013
                RadioSettingValueList(
1014
                    CHANNELDISP_LIST,
1015
                    CHANNELDISP_LIST[tmpchdispmode]))
1016
        basic.append(rs)
1017

    
1018
        # Crossband receiving/transmitting
1019
        tmpcross = _mem.crossband
1020
        if tmpcross >= len(CROSSBAND_LIST):
1021
            tmpcross = 0
1022
        rs = RadioSetting(
1023
                "crossband",
1024
                "Cross-band receiving/transmitting",
1025
                RadioSettingValueList(
1026
                    CROSSBAND_LIST,
1027
                    CROSSBAND_LIST[tmpcross]))
1028
        basic.append(rs)
1029

    
1030
        # Battery save
1031
        rs = RadioSetting(
1032
                "battery_save",
1033
                "Battery Save",
1034
                RadioSettingValueList(
1035
                    BATSAVE_LIST,
1036
                    BATSAVE_LIST[_mem.battery_save]))
1037
        basic.append(rs)
1038

    
1039
        # Dual watch
1040
        tmpdual = _mem.dual_watch
1041
        if tmpdual >= len(DUALWATCH_LIST):
1042
            tmpdual = 0
1043
        rs = RadioSetting("dualwatch", "Dual Watch", RadioSettingValueList(
1044
            DUALWATCH_LIST, DUALWATCH_LIST[tmpdual]))
1045
        basic.append(rs)
1046

    
1047
        # Tail tone elimination
1048
        rs = RadioSetting(
1049
                "tail_note_elimination",
1050
                "Tail tone elimination",
1051
                RadioSettingValueBoolean(
1052
                    bool(_mem.tail_note_elimination > 0)))
1053
        basic.append(rs)
1054

    
1055
        # VFO open
1056
        rs = RadioSetting("vfo_open", "VFO open",
1057
                          RadioSettingValueBoolean(bool(_mem.vfo_open > 0)))
1058
        basic.append(rs)
1059

    
1060
        # Beep control
1061
        rs = RadioSetting(
1062
                "beep_control",
1063
                "Beep control",
1064
                RadioSettingValueBoolean(bool(_mem.beep_control > 0)))
1065
        basic.append(rs)
1066

    
1067
        # Scan resume mode
1068
        tmpscanres = _mem.scan_resume_mode
1069
        if tmpscanres >= len(SCANRESUME_LIST):
1070
            tmpscanres = 0
1071
        rs = RadioSetting(
1072
                "scan_resume_mode",
1073
                "Scan resume mode",
1074
                RadioSettingValueList(
1075
                    SCANRESUME_LIST,
1076
                    SCANRESUME_LIST[tmpscanres]))
1077
        basic.append(rs)
1078

    
1079
        # Auto keypad lock
1080
        rs = RadioSetting(
1081
                "auto_keypad_lock",
1082
                "Auto keypad lock",
1083
                RadioSettingValueBoolean(bool(_mem.auto_keypad_lock > 0)))
1084
        basic.append(rs)
1085

    
1086
        # Power on display mode
1087
        tmpdispmode = _mem.power_on_dispmode
1088
        if tmpdispmode >= len(WELCOME_LIST):
1089
            tmpdispmode = 0
1090
        rs = RadioSetting(
1091
                "welcome_mode",
1092
                "Power on display mode",
1093
                RadioSettingValueList(
1094
                    WELCOME_LIST,
1095
                    WELCOME_LIST[tmpdispmode]))
1096
        basic.append(rs)
1097

    
1098
        # Keypad Tone
1099
        tmpkeypadtone = _mem.keypad_tone
1100
        if tmpkeypadtone >= len(KEYPADTONE_LIST):
1101
            tmpkeypadtone = 0
1102
        rs = RadioSetting("keypad_tone", "Keypad tone", RadioSettingValueList(
1103
            KEYPADTONE_LIST, KEYPADTONE_LIST[tmpkeypadtone]))
1104
        basic.append(rs)
1105

    
1106
        # Language
1107
        tmplanguage = _mem.language
1108
        if tmplanguage >= len(LANGUAGE_LIST):
1109
            tmplanguage = 0
1110
        rs = RadioSetting("language", "Language", RadioSettingValueList(
1111
            LANGUAGE_LIST, LANGUAGE_LIST[tmplanguage]))
1112
        basic.append(rs)
1113

    
1114
        # Alarm mode
1115
        tmpalarmmode = _mem.alarm_mode
1116
        if tmpalarmmode >= len(ALARMMODE_LIST):
1117
            tmpalarmmode = 0
1118
        rs = RadioSetting("alarm_mode", "Alarm mode", RadioSettingValueList(
1119
            ALARMMODE_LIST, ALARMMODE_LIST[tmpalarmmode]))
1120
        basic.append(rs)
1121

    
1122
        # Reminding of end of talk
1123
        tmpalarmmode = _mem.reminding_of_end_talk
1124
        if tmpalarmmode >= len(REMENDOFTALK_LIST):
1125
            tmpalarmmode = 0
1126
        rs = RadioSetting(
1127
                "reminding_of_end_talk",
1128
                "Reminding of end of talk",
1129
                RadioSettingValueList(
1130
                    REMENDOFTALK_LIST,
1131
                    REMENDOFTALK_LIST[tmpalarmmode]))
1132
        basic.append(rs)
1133

    
1134
        # Repeater tail tone elimination
1135
        tmprte = _mem.repeater_tail_elimination
1136
        if tmprte >= len(RTE_LIST):
1137
            tmprte = 0
1138
        rs = RadioSetting(
1139
                "repeater_tail_elimination",
1140
                "Repeater tail tone elimination",
1141
                RadioSettingValueList(RTE_LIST, RTE_LIST[tmprte]))
1142
        basic.append(rs)
1143

    
1144
        # Logo string 1
1145
        logo1 = str(_mem.logo_line1).strip("\x20\x00\xff")  # +"\x20"*12
1146
        logo1 = logo1[0:12]
1147
        rs = RadioSetting("logo1", "Logo string 1 (12 characters)",
1148
                          RadioSettingValueString(0, 12, logo1))
1149
        basic.append(rs)
1150

    
1151
        # Logo string 2
1152
        logo2 = str(_mem.logo_line2).strip("\x20\x00\xff")  # +"\x20"*12
1153
        logo2 = logo2[0:12]
1154
        rs = RadioSetting("logo2", "Logo string 2 (12 characters)",
1155
                          RadioSettingValueString(0, 12, logo2))
1156
        basic.append(rs)
1157

    
1158
        # unlock settings
1159

    
1160
        # F-LOCK
1161
        tmpflock = _mem.int_flock
1162
        if tmpflock >= len(FLOCK_LIST):
1163
            tmpflock = 0
1164
        rs = RadioSetting(
1165
            "flock", "F-LOCK",
1166
            RadioSettingValueList(FLOCK_LIST, FLOCK_LIST[tmpflock]))
1167
        unlock.append(rs)
1168

    
1169
        # 350TX
1170
        rs = RadioSetting("350tx", "350TX", RadioSettingValueBoolean(
1171
            bool(_mem.int_350tx > 0)))
1172
        unlock.append(rs)
1173

    
1174
        # unknown1
1175
        rs = RadioSetting("unknown11", "UNKNOWN1",
1176
                          RadioSettingValueBoolean(
1177
                              bool(_mem.int_unknown1 > 0)))
1178
        unlock.append(rs)
1179

    
1180
        # 200TX
1181
        rs = RadioSetting("200tx", "200TX", RadioSettingValueBoolean(
1182
            bool(_mem.int_200tx > 0)))
1183
        unlock.append(rs)
1184

    
1185
        # 500TX
1186
        rs = RadioSetting("500tx", "500TX", RadioSettingValueBoolean(
1187
            bool(_mem.int_500tx > 0)))
1188
        unlock.append(rs)
1189

    
1190
        # 350EN
1191
        rs = RadioSetting("350en", "350EN", RadioSettingValueBoolean(
1192
            bool(_mem.int_350en > 0)))
1193
        unlock.append(rs)
1194

    
1195
        # SCREEN
1196
        rs = RadioSetting("screen", "SCREEN", RadioSettingValueBoolean(
1197
            bool(_mem.int_screen > 0)))
1198
        unlock.append(rs)
1199

    
1200
        # readonly info
1201
        # Firmware
1202
        if self.FIRMWARE_VERSION == "":
1203
            firmware = "To get the firmware version please download"
1204
            "the image from the radio first"
1205
        else:
1206
            firmware = self.FIRMWARE_VERSION
1207

    
1208
        val = RadioSettingValueString(0, 128, firmware)
1209
        val.set_mutable(False)
1210
        rs = RadioSetting("fw_ver", "Firmware Version", val)
1211
        roinfo.append(rs)
1212

    
1213
        # Driver version
1214
        val = RadioSettingValueString(0, 128, DRIVER_VERSION)
1215
        val.set_mutable(False)
1216
        rs = RadioSetting("driver_ver", "Driver version", val)
1217
        roinfo.append(rs)
1218

    
1219
        return top
1220

    
1221
    # Store details about a high-level memory to the memory map
1222
    # This is called when a user edits a memory in the UI
1223
    def set_memory(self, mem):
1224
        number = mem.number-1
1225

    
1226
        # Get a low-level memory object mapped to the image
1227
        _mem = self._memobj.channel[number]
1228
        _mem4 = self._memobj
1229
        # empty memory
1230
        if mem.empty:
1231
            _mem.set_raw("\xFF" * 16)
1232
            if number < 200:
1233
                _mem2 = self._memobj.channelname[number]
1234
                _mem2.set_raw("\xFF" * 16)
1235
                _mem4.channel_attributes[number] = 0x0f
1236
            return mem
1237

    
1238
        # clean the channel memory, restore some bits if it was used before
1239
        if _mem.get_raw()[0] == "\xff":
1240
            # this was an empty memory
1241
            _mem.set_raw("\x00" * 16)
1242
        else:
1243
            # this memory was't empty, save some bits that we don't know the
1244
            # meaning of, or that we don't support yet
1245
            prev_0a = ord(_mem.get_raw()[0x0a]) & SAVE_MASK_0A
1246
            prev_0b = ord(_mem.get_raw()[0x0b]) & SAVE_MASK_0B
1247
            prev_0c = ord(_mem.get_raw()[0x0c]) & SAVE_MASK_0C
1248
            prev_0d = ord(_mem.get_raw()[0x0d]) & SAVE_MASK_0D
1249
            prev_0e = ord(_mem.get_raw()[0x0e]) & SAVE_MASK_0E
1250
            prev_0f = ord(_mem.get_raw()[0x0f]) & SAVE_MASK_0F
1251
            _mem.set_raw("\x00" * 10 +
1252
                         chr(prev_0a) + chr(prev_0b) + chr(prev_0c) +
1253
                         chr(prev_0d) + chr(prev_0e) + chr(prev_0f))
1254

    
1255
        if number < 200:
1256
            _mem4.channel_attributes[number] = 0x0f
1257

    
1258
        # find band
1259
        band = _find_band(mem.freq)
1260
        if band is False:
1261
            return mem
1262

    
1263
        # mode
1264
        if mem.mode == "NFM":
1265
            _mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
1266
            _mem.flags1 = _mem.flags1 & ~FLAGS1_ISAM
1267
        elif mem.mode == "FM":
1268
            _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1269
            _mem.flags1 = _mem.flags1 & ~FLAGS1_ISAM
1270
        elif mem.mode == "NAM":
1271
            _mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
1272
            _mem.flags1 = _mem.flags1 | FLAGS1_ISAM
1273
        elif mem.mode == "AM":
1274
            _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1275
            _mem.flags1 = _mem.flags1 | FLAGS1_ISAM
1276

    
1277
        # frequency/offset
1278
        _mem.freq = mem.freq/10
1279
        _mem.offset = mem.offset/10
1280

    
1281
        if mem.duplex == "off" or mem.duplex == "":
1282
            _mem.offset = 0
1283
            _mem.flags1 = _mem.flags1 & ~FLAGS1_OFFSET_MASK
1284
        elif mem.duplex == '-':
1285
            _mem.flags1 = (
1286
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_MINUS
1287
        elif mem.duplex == '+':
1288
            _mem.flags1 = (
1289
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_PLUS
1290

    
1291
        # set band
1292
        if number < 200:
1293
            _mem4.channel_attributes[number] = (
1294
                    _mem4.channel_attributes[number] & ~BANDMASK) | band
1295

    
1296
        # channels >200 are the 14 VFO chanells and don't have names
1297
        if number < 200:
1298
            _mem2 = self._memobj.channelname[number]
1299
            tag = mem.name.ljust(16)[:16]
1300
            _mem2.name = tag  # Store the alpha tag
1301

    
1302
        # tone data
1303
        self._set_tone(mem, _mem)
1304

    
1305
        # step
1306
        _mem.step = STEPS.index(mem.tuning_step)
1307

    
1308
        # tx power
1309
        if str(mem.power) == str(UVK5_POWER_LEVELS[2]):
1310
            _mem.flags2 = (
1311
                _mem.flags2 & ~FLAGS2_POWER_MASK) | FLAGS2_POWER_HIGH
1312
        elif str(mem.power) == str(UVK5_POWER_LEVELS[1]):
1313
            _mem.flags2 = (
1314
                _mem.flags2 & ~FLAGS2_POWER_MASK) | FLAGS2_POWER_MEDIUM
1315
        else:
1316
            _mem.flags2 = (_mem.flags2 & ~FLAGS2_POWER_MASK)
1317

    
1318
        for setting in mem.extra:
1319
            sname = setting.get_name()
1320
            svalue = setting.value.get_value()
1321

    
1322
            if sname == "bclo":
1323
                if svalue:
1324
                    _mem.flags2 = _mem.flags2 | FLAGS2_BCLO
1325
                else:
1326
                    _mem.flags2 = _mem.flags2 & ~FLAGS2_BCLO
1327

    
1328
            if sname == "pttid":
1329
                _mem.dtmf_flags = (
1330
                        (_mem.dtmf_flags & ~FLAGS_DTMF_PTTID_MASK)
1331
                        | (PTTID_LIST.index(svalue) << 1))
1332

    
1333
            if sname == "frev":
1334
                if svalue:
1335
                    _mem.flags2 = _mem.flags2 | FLAGS2_REVERSE
1336
                else:
1337
                    _mem.flags2 = _mem.flags2 & ~FLAGS2_REVERSE
1338

    
1339
            if sname == "dtmfdecode":
1340
                if svalue:
1341
                    _mem.dtmf_flags = _mem.dtmf_flags | FLAGS_DTMF_DECODE
1342
                else:
1343
                    _mem.dtmf_flags = _mem.dtmf_flags & ~FLAGS_DTMF_DECODE
1344

    
1345
            if sname == "scrambler":
1346
                _mem.scrambler = (
1347
                    _mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue)
1348

    
1349
        return mem
(19-19/47)