Project

General

Profile

New Model #10478 » uvk5.py

chirp driver for UV-K5, version 20230529 - Jacek Lipkowski SQ5BPF, 05/29/2023 07:26 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
import os
35
from chirp import chirp_common, directory, bitwise, memmap, errors, util
36
from chirp.settings import RadioSetting, RadioSettingGroup, \
37
    RadioSettingValueBoolean, RadioSettingValueList, \
38
    RadioSettingValueInteger, RadioSettingValueString, \
39
    RadioSettings
40
# from chirp.settings import RadioSettingValueFloat, RadioSettingValueMap
41

    
42
LOG = logging.getLogger(__name__)
43

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
144
FLAGS1_ISSCANLIST = 0b100
145
FLAGS1_ISAM = 0b10000
146

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

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

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

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

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

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

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

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

    
193

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

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

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

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

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

    
246

    
247
# the communication is obfuscated using this fine mechanism
248
def xorarr(data: bytes):
249
    tbl = [22, 108, 20, 230, 46, 145, 13, 64, 33, 53, 213, 64, 19, 3, 233, 128]
250
    x = b""
251
    r = 0
252
    for byte in data:
253
        x += bytes([byte ^ tbl[r]])
254
        r = (r+1) % len(tbl)
255
    return x
256

    
257

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

    
272

    
273
def _send_command(serport, data: bytes):
274
    """Send a command to UV-K5 radio"""
275
    LOG.debug("Sending command (unobfuscated) len=0x%4.4x:\n%s" %
276
              (len(data), util.hexprint(data)))
277

    
278
    crc = calculate_crc16_xmodem(data)
279
    data2 = data+bytes([crc & 0xff, (crc >> 8) & 0xff])
280

    
281
    command = b"\xAB\xCD"+bytes([len(data)])+b"\x00"+xorarr(data2)+b"\xDC\xBA"
282
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
283
        LOG.debug("Sending command (obfuscated):\n%s" % util.hexprint(command))
284
    try:
285
        result = serport.write(command)
286
    except Exception:
287
        raise errors.RadioError("Error writing data to radio")
288
    return result
289

    
290

    
291
def _receive_reply(serport):
292
    header = serport.read(4)
293
    if len(header) != 4:
294
        LOG.warning("Header short read: [%s] len=%i" %
295
                    (util.hexprint(header), len(header)))
296
        raise errors.RadioError("Header short read")
297
    if header[0] != 0xAB or header[1] != 0xCD or header[3] != 0x00:
298
        LOG.warning("Bad response header: %s len=%i" %
299
                    (util.hexprint(header), len(header)))
300
        raise errors.RadioError("Bad response header")
301

    
302
        return False
303

    
304
    cmd = serport.read(int(header[2]))
305
    if len(cmd) != int(header[2]):
306
        LOG.warning("Body short read: [%s] len=%i" %
307
                    (util.hexprint(cmd), len(cmd)))
308
        raise errors.RadioError("Command body short read")
309

    
310
    footer = serport.read(4)
311

    
312
    if len(footer) != 4:
313
        LOG.warning("Footer short read: [%s] len=%i" %
314
                    (util.hexprint(footer), len(footer)))
315
        raise errors.RadioError("Footer short read")
316

    
317
    if footer[2] != 0xDC or footer[3] != 0xBA:
318
        LOG.debug(
319
                "Reply before bad response footer (obfuscated)"
320
                "len=0x%4.4x:\n%s" % (len(cmd), util.hexprint(cmd)))
321
        LOG.warning("Bad response footer: %s len=%i" %
322
                    (util.hexprint(footer), len(footer)))
323
        raise errors.RadioError("Bad response footer")
324
        return False
325

    
326
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
327
        LOG.debug("Received reply (obfuscated) len=0x%4.4x:\n%s" %
328
                  (len(cmd), util.hexprint(cmd)))
329

    
330
    cmd2 = xorarr(cmd)
331

    
332
    LOG.debug("Received reply (unobfuscated) len=0x%4.4x:\n%s" %
333
              (len(cmd2), util.hexprint(cmd2)))
334

    
335
    return cmd2
336

    
337

    
338
def _getstring(data: bytes, begin, maxlen):
339
    s = ""
340
    c = 0
341
    for i in data:
342
        c += 1
343
        if c < begin:
344
            continue
345
        if i < ord(' ') or i > ord('~'):
346
            break
347
        s += chr(i)
348
    return s
349

    
350

    
351
def _sayhello(serport):
352
    hellopacket = b"\x14\x05\x04\x00\x6a\x39\x57\x64"
353

    
354
    tries = 5
355
    while (True):
356
        LOG.debug("Sending hello packet")
357
        _send_command(serport, hellopacket)
358
        o = _receive_reply(serport)
359
        if (o):
360
            break
361
        tries -= 1
362
        if tries == 0:
363
            LOG.warning("Failed to initialise radio")
364
            raise errors.RadioError("Failed to initialize radio")
365
            return False
366
    firmware = _getstring(o, 5, 16)
367
    LOG.info("Found firmware: %s" % firmware)
368
    return firmware
369

    
370

    
371
def _readmem(serport, offset, length):
372
    LOG.debug("Sending readmem offset=0x%4.4x len=0x%4.4x" % (offset, length))
373

    
374
    readmem = b"\x1b\x05\x08\x00" + \
375
        bytes([offset & 0xff, (offset >> 8) & 0xff, length, 0]) + \
376
        b"\x6a\x39\x57\x64"
377
    _send_command(serport, readmem)
378
    o = _receive_reply(serport)
379
    if DEBUG_SHOW_MEMORY_ACTIONS:
380
        LOG.debug("readmem Received data len=0x%4.4x:\n%s" %
381
                  (len(o), util.hexprint(o)))
382
    return o[8:]
383

    
384

    
385
def _writemem(serport, data, offset):
386
    LOG.debug("Sending writemem offset=0x%4.4x len=0x%4.4x" %
387
              (offset, len(data)))
388

    
389
    if DEBUG_SHOW_MEMORY_ACTIONS:
390
        LOG.debug("writemem sent data offset=0x%4.4x len=0x%4.4x:\n%s" %
391
                  (offset, len(data), util.hexprint(data)))
392

    
393
    dlen = len(data)
394
    writemem = b"\x1d\x05"+bytes([dlen+8])+b"\x00" + \
395
        bytes([offset & 0xff, (offset >> 8) & 0xff, dlen, 1]) + \
396
        b"\x6a\x39\x57\x64"+data
397

    
398
    _send_command(serport, writemem)
399
    o = _receive_reply(serport)
400

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

    
403
    if (o[0] == 0x1e
404
            and
405
            o[4] == (offset & 0xff)
406
            and
407
            o[5] == (offset >> 8) & 0xff):
408
        return True
409
    else:
410
        LOG.warning("Bad data from writemem")
411
        raise errors.RadioError("Bad response to writemem")
412
    return False
413

    
414

    
415
def _resetradio(serport):
416
    resetpacket = b"\xdd\x05\x00\x00"
417
    _send_command(serport, resetpacket)
418

    
419

    
420
def do_download(radio):
421
    serport = radio.pipe
422
    serport.timeout = 0.5
423
    status = chirp_common.Status()
424
    status.cur = 0
425
    status.max = MEM_SIZE
426
    status.msg = "Downloading from radio"
427
    radio.status_fn(status)
428

    
429
    eeprom = b""
430
    f = _sayhello(serport)
431
    if f:
432
        radio.FIRMWARE_VERSION = f
433
    else:
434
        return False
435

    
436
    addr = 0
437
    while addr < MEM_SIZE:
438
        o = _readmem(serport, addr, MEM_BLOCK)
439
        status.cur = addr
440
        radio.status_fn(status)
441

    
442
        if o and len(o) == MEM_BLOCK:
443
            eeprom += o
444
            addr += MEM_BLOCK
445
        else:
446
            raise errors.RadioError("Memory download incomplete")
447

    
448
    return memmap.MemoryMapBytes(eeprom)
449

    
450

    
451
def do_upload(radio):
452
    serport = radio.pipe
453
    serport.timeout = 0.5
454
    status = chirp_common.Status()
455
    status.cur = 0
456
    status.max = PROG_SIZE
457
    status.msg = "Uploading to radio"
458
    radio.status_fn(status)
459

    
460
    f = _sayhello(serport)
461
    if f:
462
        radio.FIRMWARE_VERSION = f
463
    else:
464
        return False
465

    
466
    addr = 0
467
    while addr < PROG_SIZE:
468
        o = radio.get_mmap()[addr:addr+MEM_BLOCK]
469
        _writemem(serport, o, addr)
470
        status.cur = addr
471
        radio.status_fn(status)
472
        if o:
473
            addr += MEM_BLOCK
474
        else:
475
            raise errors.RadioError("Memory upload incomplete")
476
    status.msg = "Uploaded OK"
477

    
478
    _resetradio(serport)
479

    
480
    return True
481

    
482

    
483
def _find_band(hz):
484
    mhz = hz/1000000.0
485
    for a in BANDS:
486
        if mhz >= BANDS[a][0] and mhz <= BANDS[a][1]:
487
            return a
488
    return False
489

    
490

    
491
@directory.register
492
class TemplateRadio(chirp_common.CloneModeRadio):
493
    """Quansheng UV-K5"""
494
    VENDOR = "Quansheng"
495
    MODEL = "UV-K5"
496
    BAUD_RATE = 38400
497

    
498
    NEEDS_COMPAT_SERIAL = False
499
    FIRMWARE_VERSION = ""
500
    # If the user knows what he's doing, then he can expose this
501
    # functionality that goes against the spirit of chirp
502
    if os.getenv("UVK5_DATA_IN_COMMENTS"):
503
        COMMENT_HACK = True
504
    else:
505
        COMMENT_HACK = False
506

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

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

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

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

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

    
561
        rf.valid_skips = [""]
562

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

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

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

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

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

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

    
595
    def validate_memory(self, mem):
596
        msgs = super().validate_memory(mem)
597
        return msgs
598

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

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

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

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

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

    
634
        rx_tone = tx_tone = None
635

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

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

    
662
        tx_pol = txtype == 0x03 and "R" or "N"
663
        rx_pol = rxtype == 0x03 and "R" or "N"
664

    
665
        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
666
                                       (rx_tmode, rx_tone, rx_pol))
667

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

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

    
674
        mem = chirp_common.Memory()
675

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

    
682
        _mem = self._memobj.channel[number]
683

    
684
        tmpcomment = ""
685

    
686
        mem.number = number2
687

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

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

    
714
            return mem
715

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

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

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

    
741
        # tone data
742
        self._get_tone(mem, _mem)
743

    
744
        # mode
745
        if (_mem.flags1 & FLAGS1_ISAM) > 0:
746
            # Actually not sure if internally there aren't "Narrow AM"
747
            # and "Wide AM" modes. To be investigated.
748
            mem.mode = "AM"
749
        else:
750
            if (_mem.flags2 & FLAGS2_BANDWIDTH) > 0:
751
                mem.mode = "NFM"
752
            else:
753
                mem.mode = "FM"
754

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

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

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

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

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

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

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

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

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

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

    
815
        # Ugly hack: the extra parameters will be shown in the comments field,
816
        # because there is no other way to show them without the user
817
        # right-clicking every channel
818
        # Unfortunately this makes that field effectively read-only, so
819
        # it is only for those that can find it in the sources
820
        if self.COMMENT_HACK:
821
            mem.comment = tmpcomment
822

    
823
        return mem
824

    
825
    def set_settings(self, settings):
826
        _mem = self._memobj
827
        for element in settings:
828
            if not isinstance(element, RadioSetting):
829
                self.set_settings(element)
830
                continue
831

    
832
            # basic settings
833

    
834
            # call channel
835
            if element.get_name() == "call_channel":
836
                _mem.call_channel = int(element.value)-1
837

    
838
            # squelch
839
            if element.get_name() == "squelch":
840
                _mem.squelch = int(element.value)
841
            # TOT
842
            if element.get_name() == "tot":
843
                _mem.max_talk_time = int(element.value)
844
            # NOAA autoscan
845
            if element.get_name() == "noaa_autoscan":
846
                _mem.noaa_autoscan = element.value and 1 or 0
847

    
848
            # vox level
849
            if element.get_name() == "vox_level":
850
                _mem.vox_level = int(element.value)-1
851

    
852
            # mic gain
853
            if element.get_name() == "mic_gain":
854
                _mem.mic_gain = int(element.value)
855

    
856
            # Channel display mode
857
            if element.get_name() == "channel_display_mode":
858
                _mem.channel_display_mode = CHANNELDISP_LIST.index(
859
                    str(element.value))
860

    
861
            # Crossband receiving/transmitting
862
            if element.get_name() == "crossband":
863
                _mem.crossband = CROSSBAND_LIST.index(str(element.value))
864

    
865
            # Battery Save
866
            if element.get_name() == "battery_save":
867
                _mem.battery_save = BATSAVE_LIST.index(str(element.value))
868
            # Dual Watch
869
            if element.get_name() == "dualwatch":
870
                _mem.dual_watch = DUALWATCH_LIST.index(str(element.value))
871

    
872
            # Tail tone elimination
873
            if element.get_name() == "tail_note_elimination":
874
                _mem.tail_note_elimination = element.value and 1 or 0
875

    
876
            # VFO Open
877
            if element.get_name() == "vfo_open":
878
                _mem.vfo_open = element.value and 1 or 0
879

    
880
            # Beep control
881
            if element.get_name() == "beep_control":
882
                _mem.beep_control = element.value and 1 or 0
883

    
884
            # Scan resume mode
885
            if element.get_name() == "scan_resume_mode":
886
                _mem.scan_resume_mode = SCANRESUME_LIST.index(
887
                    str(element.value))
888

    
889
            # Auto keypad lock
890
            if element.get_name() == "auto_keypad_lock":
891
                _mem.auto_keypad_lock = element.value and 1 or 0
892

    
893
            # Power on display mode
894
            if element.get_name() == "welcome_mode":
895
                _mem.power_on_dispmode = WELCOME_LIST.index(str(element.value))
896

    
897
            # Keypad Tone
898
            if element.get_name() == "keypad_tone":
899
                _mem.keypad_tone = KEYPADTONE_LIST.index(str(element.value))
900

    
901
            # Language
902
            if element.get_name() == "language":
903
                _mem.language = LANGUAGE_LIST.index(str(element.value))
904

    
905
            # Alarm mode
906
            if element.get_name() == "alarm_mode":
907
                _mem.alarm_mode = ALARMMODE_LIST.index(str(element.value))
908

    
909
            # Reminding of end of talk
910
            if element.get_name() == "reminding_of_end_talk":
911
                _mem.reminding_of_end_talk = REMENDOFTALK_LIST.index(
912
                    str(element.value))
913

    
914
            # Repeater tail tone elimination
915
            if element.get_name() == "repeater_tail_elimination":
916
                _mem.repeater_tail_elimination = RTE_LIST.index(
917
                    str(element.value))
918

    
919
            # Logo string 1
920
            if element.get_name() == "logo1":
921
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
922
                _mem.logo_line1 = b[0:12]+"\xff\xff\xff\xff"
923

    
924
            # Logo string 2
925
            if element.get_name() == "logo2":
926
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
927
                _mem.logo_line2 = b[0:12]+"\xff\xff\xff\xff"
928

    
929
            # unlock settings
930

    
931
            # FLOCK
932
            if element.get_name() == "flock":
933
                _mem.int_flock = FLOCK_LIST.index(str(element.value))
934

    
935
            # 350TX
936
            if element.get_name() == "350tx":
937
                _mem.int_350tx = element.value and 1 or 0
938

    
939
            # UNKNOWN1
940
            if element.get_name() == "unknown1":
941
                _mem.int_unknown1 = element.value and 1 or 0
942

    
943
            # 200TX
944
            if element.get_name() == "200tx":
945
                _mem.int_200tx = element.value and 1 or 0
946

    
947
            # 500TX
948
            if element.get_name() == "500tx":
949
                _mem.int_500tx = element.value and 1 or 0
950

    
951
            # 350EN
952
            if element.get_name() == "350en":
953
                _mem.int_350en = element.value and 1 or 0
954

    
955
    def get_settings(self):
956
        _mem = self._memobj
957
        basic = RadioSettingGroup("basic", "Basic Settings")
958
        unlock = RadioSettingGroup("unlock", "Unlock Settings")
959
        fmradio = RadioSettingGroup("fmradio", "FM Radio (unimplemented yet)")
960
        roinfo = RadioSettingGroup("roinfo", "Driver information")
961

    
962
        top = RadioSettings(basic, unlock, fmradio, roinfo)
963

    
964
        # basic settings
965

    
966
        # call channel
967
        tmpc = _mem.call_channel+1
968
        if tmpc > 200:
969
            tmpc = 1
970
        rs = RadioSetting("call_channel", "One key call channel",
971
                          RadioSettingValueInteger(1, 200, tmpc))
972
        basic.append(rs)
973

    
974
        # squelch
975
        tmpsq = _mem.squelch
976
        if tmpsq > 9:
977
            tmpsq = 1
978
        rs = RadioSetting("squelch", "Squelch",
979
                          RadioSettingValueInteger(0, 9, tmpsq))
980
        basic.append(rs)
981

    
982
        # TOT
983
        tmptot = _mem.max_talk_time
984
        if tmptot > 10:
985
            tmptot = 10
986
        rs = RadioSetting(
987
                "tot",
988
                "Max talk time [min]",
989
                RadioSettingValueInteger(0, 10, tmptot))
990
        basic.append(rs)
991

    
992
        # NOAA autoscan
993
        rs = RadioSetting(
994
                "noaa_autoscan",
995
                "NOAA Autoscan", RadioSettingValueBoolean(
996
                    bool(_mem.noaa_autoscan > 0)))
997
        basic.append(rs)
998

    
999
        # VOX Level
1000
        tmpvox = _mem.vox_level+1
1001
        if tmpvox > 10:
1002
            tmpvox = 10
1003
        rs = RadioSetting("vox_level", "VOX Level",
1004
                          RadioSettingValueInteger(1, 10, tmpvox))
1005
        basic.append(rs)
1006

    
1007
        # Mic gain
1008
        tmpmicgain = _mem.mic_gain
1009
        if tmpmicgain > 4:
1010
            tmpmicgain = 4
1011
        rs = RadioSetting("mic_gain", "Mic Gain",
1012
                          RadioSettingValueInteger(0, 4, tmpmicgain))
1013
        basic.append(rs)
1014

    
1015
        # Channel display mode
1016
        tmpchdispmode = _mem.channel_display_mode
1017
        if tmpchdispmode >= len(CHANNELDISP_LIST):
1018
            tmpchdispmode = 0
1019
        rs = RadioSetting(
1020
                "channel_display_mode",
1021
                "Channel display mode",
1022
                RadioSettingValueList(
1023
                    CHANNELDISP_LIST,
1024
                    CHANNELDISP_LIST[tmpchdispmode]))
1025
        basic.append(rs)
1026

    
1027
        # Crossband receiving/transmitting
1028
        tmpcross = _mem.crossband
1029
        if tmpcross >= len(CROSSBAND_LIST):
1030
            tmpcross = 0
1031
        rs = RadioSetting(
1032
                "crossband",
1033
                "Cross-band receiving/transmitting",
1034
                RadioSettingValueList(
1035
                    CROSSBAND_LIST,
1036
                    CROSSBAND_LIST[tmpcross]))
1037
        basic.append(rs)
1038

    
1039
        # Battery save
1040
        rs = RadioSetting(
1041
                "battery_save",
1042
                "Battery Save",
1043
                RadioSettingValueList(
1044
                    BATSAVE_LIST,
1045
                    BATSAVE_LIST[_mem.battery_save]))
1046
        basic.append(rs)
1047

    
1048
        # Dual watch
1049
        tmpdual = _mem.dual_watch
1050
        if tmpdual >= len(DUALWATCH_LIST):
1051
            tmpdual = 0
1052
        rs = RadioSetting("dualwatch", "Dual Watch", RadioSettingValueList(
1053
            DUALWATCH_LIST, DUALWATCH_LIST[tmpdual]))
1054
        basic.append(rs)
1055

    
1056
        # Tail tone elimination
1057
        rs = RadioSetting(
1058
                "tail_note_elimination",
1059
                "Tail tone elimination",
1060
                RadioSettingValueBoolean(
1061
                    bool(_mem.tail_note_elimination > 0)))
1062
        basic.append(rs)
1063

    
1064
        # VFO open
1065
        rs = RadioSetting("vfo_open", "VFO open",
1066
                          RadioSettingValueBoolean(bool(_mem.vfo_open > 0)))
1067
        basic.append(rs)
1068

    
1069
        # Beep control
1070
        rs = RadioSetting(
1071
                "beep_control",
1072
                "Beep control",
1073
                RadioSettingValueBoolean(bool(_mem.beep_control > 0)))
1074
        basic.append(rs)
1075

    
1076
        # Scan resume mode
1077
        tmpscanres = _mem.scan_resume_mode
1078
        if tmpscanres >= len(SCANRESUME_LIST):
1079
            tmpscanres = 0
1080
        rs = RadioSetting(
1081
                "scan_resume_mode",
1082
                "Scan resume mode",
1083
                RadioSettingValueList(
1084
                    SCANRESUME_LIST,
1085
                    SCANRESUME_LIST[tmpscanres]))
1086
        basic.append(rs)
1087

    
1088
        # Auto keypad lock
1089
        rs = RadioSetting(
1090
                "auto_keypad_lock",
1091
                "Auto keypad lock",
1092
                RadioSettingValueBoolean(bool(_mem.auto_keypad_lock > 0)))
1093
        basic.append(rs)
1094

    
1095
        # Power on display mode
1096
        tmpdispmode = _mem.power_on_dispmode
1097
        if tmpdispmode >= len(WELCOME_LIST):
1098
            tmpdispmode = 0
1099
        rs = RadioSetting(
1100
                "welcome_mode",
1101
                "Power on display mode",
1102
                RadioSettingValueList(
1103
                    WELCOME_LIST,
1104
                    WELCOME_LIST[tmpdispmode]))
1105
        basic.append(rs)
1106

    
1107
        # Keypad Tone
1108
        tmpkeypadtone = _mem.keypad_tone
1109
        if tmpkeypadtone >= len(KEYPADTONE_LIST):
1110
            tmpkeypadtone = 0
1111
        rs = RadioSetting("keypad_tone", "Keypad tone", RadioSettingValueList(
1112
            KEYPADTONE_LIST, KEYPADTONE_LIST[tmpkeypadtone]))
1113
        basic.append(rs)
1114

    
1115
        # Language
1116
        tmplanguage = _mem.language
1117
        if tmplanguage >= len(LANGUAGE_LIST):
1118
            tmplanguage = 0
1119
        rs = RadioSetting("language", "Language", RadioSettingValueList(
1120
            LANGUAGE_LIST, LANGUAGE_LIST[tmplanguage]))
1121
        basic.append(rs)
1122

    
1123
        # Alarm mode
1124
        tmpalarmmode = _mem.alarm_mode
1125
        if tmpalarmmode >= len(ALARMMODE_LIST):
1126
            tmpalarmmode = 0
1127
        rs = RadioSetting("alarm_mode", "Alarm mode", RadioSettingValueList(
1128
            ALARMMODE_LIST, ALARMMODE_LIST[tmpalarmmode]))
1129
        basic.append(rs)
1130

    
1131
        # Reminding of end of talk
1132
        tmpalarmmode = _mem.reminding_of_end_talk
1133
        if tmpalarmmode >= len(REMENDOFTALK_LIST):
1134
            tmpalarmmode = 0
1135
        rs = RadioSetting(
1136
                "reminding_of_end_talk",
1137
                "Reminding of end of talk",
1138
                RadioSettingValueList(
1139
                    REMENDOFTALK_LIST,
1140
                    REMENDOFTALK_LIST[tmpalarmmode]))
1141
        basic.append(rs)
1142

    
1143
        # Repeater tail tone elimination
1144
        tmprte = _mem.repeater_tail_elimination
1145
        if tmprte >= len(RTE_LIST):
1146
            tmprte = 0
1147
        rs = RadioSetting(
1148
                "repeater_tail_elimination",
1149
                "Repeater tail tone elimination",
1150
                RadioSettingValueList(RTE_LIST, RTE_LIST[tmprte]))
1151
        basic.append(rs)
1152

    
1153
        # Logo string 1
1154
        logo1 = str(_mem.logo_line1).strip("\x20\x00\xff")  # +"\x20"*12
1155
        logo1 = logo1[0:12]
1156
        rs = RadioSetting("logo1", "Logo string 1 (12 characters)",
1157
                          RadioSettingValueString(0, 12, logo1))
1158
        basic.append(rs)
1159

    
1160
        # Logo string 2
1161
        logo2 = str(_mem.logo_line2).strip("\x20\x00\xff")  # +"\x20"*12
1162
        logo2 = logo2[0:12]
1163
        rs = RadioSetting("logo2", "Logo string 2 (12 characters)",
1164
                          RadioSettingValueString(0, 12, logo2))
1165
        basic.append(rs)
1166

    
1167
        # unlock settings
1168

    
1169
        # F-LOCK
1170
        tmpflock = _mem.int_flock
1171
        if tmpflock >= len(FLOCK_LIST):
1172
            tmpflock = 0
1173
        rs = RadioSetting(
1174
            "flock", "F-LOCK",
1175
            RadioSettingValueList(FLOCK_LIST, FLOCK_LIST[tmpflock]))
1176
        unlock.append(rs)
1177

    
1178
        # 350TX
1179
        rs = RadioSetting("350tx", "350TX", RadioSettingValueBoolean(
1180
            bool(_mem.int_350tx > 0)))
1181
        unlock.append(rs)
1182

    
1183
        # unknown1
1184
        rs = RadioSetting("unknown11", "UNKNOWN1",
1185
                          RadioSettingValueBoolean(
1186
                              bool(_mem.int_unknown1 > 0)))
1187
        unlock.append(rs)
1188

    
1189
        # 200TX
1190
        rs = RadioSetting("200tx", "200TX", RadioSettingValueBoolean(
1191
            bool(_mem.int_200tx > 0)))
1192
        unlock.append(rs)
1193

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

    
1199
        # 350EN
1200
        rs = RadioSetting("350en", "350EN", RadioSettingValueBoolean(
1201
            bool(_mem.int_350en > 0)))
1202
        unlock.append(rs)
1203

    
1204
        # SCREEN
1205
        rs = RadioSetting("screen", "SCREEN", RadioSettingValueBoolean(
1206
            bool(_mem.int_screen > 0)))
1207
        unlock.append(rs)
1208

    
1209
        # readonly info
1210
        # Firmware
1211
        if self.FIRMWARE_VERSION == "":
1212
            firmware = "To get the firmware version please download"
1213
            "the image from the radio first"
1214
        else:
1215
            firmware = self.FIRMWARE_VERSION
1216

    
1217
        val = RadioSettingValueString(0, 128, firmware)
1218
        val.set_mutable(False)
1219
        rs = RadioSetting("fw_ver", "Firmware Version", val)
1220
        roinfo.append(rs)
1221

    
1222
        # Driver version
1223
        val = RadioSettingValueString(0, 128, DRIVER_VERSION)
1224
        val.set_mutable(False)
1225
        rs = RadioSetting("driver_ver", "Driver version", val)
1226
        roinfo.append(rs)
1227

    
1228
        # Comment hack (to be removed)
1229
        val = RadioSettingValueString(0, 4,
1230
                                      self.COMMENT_HACK and "Yes" or "No")
1231
        val.set_mutable(False)
1232
        rs = RadioSetting(
1233
                "comment_hack", "Show extra channel settings in comments",
1234
                val)
1235
        roinfo.append(rs)
1236

    
1237
        return top
1238

    
1239
    # Store details about a high-level memory to the memory map
1240
    # This is called when a user edits a memory in the UI
1241
    def set_memory(self, mem):
1242
        number = mem.number-1
1243

    
1244
        # Get a low-level memory object mapped to the image
1245
        _mem = self._memobj.channel[number]
1246
        _mem4 = self._memobj
1247
        # empty memory
1248
        if mem.empty:
1249
            _mem.set_raw("\xFF" * 16)
1250
            if number < 200:
1251
                _mem2 = self._memobj.channelname[number]
1252
                _mem2.set_raw("\xFF" * 16)
1253
                _mem4.channel_attributes[number] = 0x0f
1254
            return mem
1255

    
1256
        # clean the channel memory, restore some bits if it was used before
1257
        if _mem.get_raw()[0] == "\xff":
1258
            # this was an empty memory
1259
            _mem.set_raw("\x00" * 16)
1260
        else:
1261
            # this memory was't empty, save some bits that we don't know the
1262
            # meaning of, or that we don't support yet
1263
            prev_0a = ord(_mem.get_raw()[0x0a]) & SAVE_MASK_0A
1264
            prev_0b = ord(_mem.get_raw()[0x0b]) & SAVE_MASK_0B
1265
            prev_0c = ord(_mem.get_raw()[0x0c]) & SAVE_MASK_0C
1266
            prev_0d = ord(_mem.get_raw()[0x0d]) & SAVE_MASK_0D
1267
            prev_0e = ord(_mem.get_raw()[0x0e]) & SAVE_MASK_0E
1268
            prev_0f = ord(_mem.get_raw()[0x0f]) & SAVE_MASK_0F
1269
            _mem.set_raw("\x00" * 10 +
1270
                         chr(prev_0a) + chr(prev_0b) + chr(prev_0c) +
1271
                         chr(prev_0d) + chr(prev_0e) + chr(prev_0f))
1272

    
1273
        if number < 200:
1274
            _mem4.channel_attributes[number] = 0x0f
1275

    
1276
        # find band
1277
        band = _find_band(mem.freq)
1278
        if band is False:
1279
            #    raise errors.RadioError(
1280
            #            "Frequency is outside the supported bands")
1281
            return mem
1282

    
1283
        # mode
1284
        if mem.mode == "AM":
1285
            _mem.flags1 = _mem.flags1 | FLAGS1_ISAM
1286
            _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1287
        else:
1288
            _mem.flags1 = _mem.flags1 & ~FLAGS1_ISAM
1289
            if mem.mode == "NFM":
1290
                _mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
1291
            else:
1292
                _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1293

    
1294
        # frequency/offset
1295
        _mem.freq = mem.freq/10
1296
        _mem.offset = mem.offset/10
1297

    
1298
        if mem.duplex == "off" or mem.duplex == "":
1299
            _mem.offset = 0
1300
            _mem.flags1 = _mem.flags1 & ~FLAGS1_OFFSET_MASK
1301
        elif mem.duplex == '-':
1302
            _mem.flags1 = (
1303
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_MINUS
1304
        elif mem.duplex == '+':
1305
            _mem.flags1 = (
1306
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_PLUS
1307

    
1308
        # set band
1309
        if number < 200:
1310
            _mem4.channel_attributes[number] = (
1311
                    _mem4.channel_attributes[number] & ~BANDMASK) | band
1312

    
1313
        # channels >200 are the 14 VFO chanells and don't have names
1314
        if number < 200:
1315
            _mem2 = self._memobj.channelname[number]
1316
            tag = mem.name.ljust(16)[:16]
1317
            _mem2.name = tag  # Store the alpha tag
1318

    
1319
        # tone data
1320
        self._set_tone(mem, _mem)
1321

    
1322
        # step
1323
        _mem.step = STEPS.index(mem.tuning_step) << 1
1324

    
1325
        # tx power
1326
        if str(mem.power) == str(UVK5_POWER_LEVELS[2]):
1327
            _mem.flags2 = (
1328
                _mem.flags2 & ~FLAGS2_POWER_MASK) | FLAGS2_POWER_HIGH
1329
        elif str(mem.power) == str(UVK5_POWER_LEVELS[1]):
1330
            _mem.flags2 = (
1331
                _mem.flags2 & ~FLAGS2_POWER_MASK) | FLAGS2_POWER_MEDIUM
1332
        else:
1333
            _mem.flags2 = (_mem.flags2 & ~FLAGS2_POWER_MASK)
1334

    
1335
        for setting in mem.extra:
1336
            sname = setting.get_name()
1337
            svalue = setting.value.get_value()
1338

    
1339
            if sname == "bclo":
1340
                if svalue:
1341
                    _mem.flags2 = _mem.flags2 | FLAGS2_BCLO
1342
                else:
1343
                    _mem.flags2 = _mem.flags2 & ~FLAGS2_BCLO
1344

    
1345
            if sname == "pttid":
1346
                _mem.dtmf_flags = (
1347
                        (_mem.dtmf_flags & ~FLAGS_DTMF_PTTID_MASK)
1348
                        | (PTTID_LIST.index(svalue) << 1))
1349

    
1350
            if sname == "frev":
1351
                if svalue:
1352
                    _mem.flags2 = _mem.flags2 | FLAGS2_REVERSE
1353
                else:
1354
                    _mem.flags2 = _mem.flags2 & ~FLAGS2_REVERSE
1355

    
1356
            if sname == "dtmfdecode":
1357
                if svalue:
1358
                    _mem.dtmf_flags = _mem.dtmf_flags | FLAGS_DTMF_DECODE
1359
                else:
1360
                    _mem.dtmf_flags = _mem.dtmf_flags & ~FLAGS_DTMF_DECODE
1361

    
1362
            if sname == "scrambler":
1363
                _mem.scrambler = (
1364
                    _mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue)
1365

    
1366
        return mem
(10-10/47)