Project

General

Profile

New Model #10478 » uvk5.py

uvk5.py chirp driver for UV-K5, version 20230604 - Jacek Lipkowski SQ5BPF, 06/04/2023 12:52 PM

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

    
29

    
30
# import struct
31
import logging
32
# 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 v20230604 (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
        is_empty = False
687
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
688
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
689
            is_empty = True
690

    
691
        # We'll also look at the channel attributes if a memory has them
692
        if number < 200:
693
            _mem3 = self._memobj.channel_attributes[number]
694
            if _mem3 & 0x08 > 0:
695
                is_empty = True
696

    
697
        if is_empty:
698
            mem.empty = True
699
            # set some sane defaults:
700
            mem.power = UVK5_POWER_LEVELS[2]
701
            mem.extra = RadioSettingGroup("Extra", "extra")
702
            rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(False))
703
            mem.extra.append(rs)
704
            rs = RadioSetting("frev", "FreqRev",
705
                              RadioSettingValueBoolean(False))
706
            mem.extra.append(rs)
707
            rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
708
                PTTID_LIST, PTTID_LIST[0]))
709
            mem.extra.append(rs)
710
            rs = RadioSetting("dtmfdecode", "DTMF decode",
711
                              RadioSettingValueBoolean(False))
712
            mem.extra.append(rs)
713
            rs = RadioSetting("scrambler", "Scrambler", RadioSettingValueList(
714
                SCRAMBLER_LIST, SCRAMBLER_LIST[0]))
715
            mem.extra.append(rs)
716

    
717
            # actually the step and duplex are overwritten by chirp based on
718
            # bandplan. they are here to document sane defaults for IARU r1
719
            # mem.tuning_step = 25.0
720
            # mem.duplex = "off"
721

    
722
            return mem
723

    
724
        if number > 199:
725
            mem.name = VFO_CHANNEL_NAMES[number-200]
726
            mem.immutable = ["name"]
727
        else:
728
            _mem2 = self._memobj.channelname[number]
729
            for char in _mem2.name:
730
                if str(char) == "\xFF" or str(char) == "\x00":
731
                    break
732
                mem.name += str(char)
733
            mem.name = mem.name.rstrip()
734

    
735
        # Convert your low-level frequency to Hertz
736
        mem.freq = int(_mem.freq)*10
737
        mem.offset = int(_mem.offset)*10
738

    
739
        if (mem.offset == 0):
740
            mem.duplex = ''
741
        else:
742
            if (_mem.flags1 & FLAGS1_OFFSET_MASK) == FLAGS1_OFFSET_MINUS:
743
                mem.duplex = '-'
744
            elif (_mem.flags1 & FLAGS1_OFFSET_MASK) == FLAGS1_OFFSET_PLUS:
745
                mem.duplex = '+'
746
            else:
747
                mem.duplex = ''
748

    
749
        # tone data
750
        self._get_tone(mem, _mem)
751

    
752
        # mode
753
        if (_mem.flags1 & FLAGS1_ISAM) > 0:
754
            if (_mem.flags2 & FLAGS2_BANDWIDTH) > 0:
755
                mem.mode = "NAM"
756
            else:
757
                mem.mode = "AM"
758
        else:
759
            if (_mem.flags2 & FLAGS2_BANDWIDTH) > 0:
760
                mem.mode = "NFM"
761
            else:
762
                mem.mode = "FM"
763

    
764
        # tuning step
765
        tstep = _mem.step & 0x7
766
        if tstep < len(STEPS):
767
            mem.tuning_step = STEPS[tstep]
768
        else:
769
            mem.tuning_step = 2.5
770

    
771
        # power
772
        if (_mem.flags2 & FLAGS2_POWER_MASK) == FLAGS2_POWER_HIGH:
773
            mem.power = UVK5_POWER_LEVELS[2]
774
        elif (_mem.flags2 & FLAGS2_POWER_MASK) == FLAGS2_POWER_MEDIUM:
775
            mem.power = UVK5_POWER_LEVELS[1]
776
        else:
777
            mem.power = UVK5_POWER_LEVELS[0]
778

    
779
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
780
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
781
            mem.empty = True
782
        else:
783
            mem.empty = False
784

    
785
        mem.extra = RadioSettingGroup("Extra", "extra")
786

    
787
        # BCLO
788
        is_bclo = bool(_mem.flags2 & FLAGS2_BCLO > 0)
789
        rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(is_bclo))
790
        mem.extra.append(rs)
791
        tmpcomment += "BCLO:"+(is_bclo and "ON" or "off")+" "
792

    
793
        # Frequency reverse - whatever that means, don't see it in the manual
794
        is_frev = bool(_mem.flags2 & FLAGS2_REVERSE > 0)
795
        rs = RadioSetting("frev", "FreqRev", RadioSettingValueBoolean(is_frev))
796
        mem.extra.append(rs)
797
        tmpcomment += "FreqReverse:"+(is_frev and "ON" or "off")+" "
798

    
799
        # PTTID
800
        pttid = (_mem.dtmf_flags & FLAGS_DTMF_PTTID_MASK) >> 1
801
        rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
802
            PTTID_LIST, PTTID_LIST[pttid]))
803
        mem.extra.append(rs)
804
        tmpcomment += "PTTid:"+PTTID_LIST[pttid]+" "
805

    
806
        # DTMF DECODE
807
        is_dtmf = bool(_mem.dtmf_flags & FLAGS_DTMF_DECODE > 0)
808
        rs = RadioSetting("dtmfdecode", "DTMF decode",
809
                          RadioSettingValueBoolean(is_dtmf))
810
        mem.extra.append(rs)
811
        tmpcomment += "DTMFdecode:"+(is_dtmf and "ON" or "off")+" "
812

    
813
        # Scrambler
814
        if _mem.scrambler & 0x0f < len(SCRAMBLER_LIST):
815
            enc = _mem.scrambler & 0x0f
816
        else:
817
            enc = 0
818

    
819
        rs = RadioSetting("scrambler", "Scrambler", RadioSettingValueList(
820
            SCRAMBLER_LIST, SCRAMBLER_LIST[enc]))
821
        mem.extra.append(rs)
822
        tmpcomment += "Scrambler:"+SCRAMBLER_LIST[enc]+" "
823

    
824
        return mem
825

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

    
833
            # basic settings
834

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
930
            # unlock settings
931

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

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

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

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

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

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

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

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

    
965
        # basic settings
966

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

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

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

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

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

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

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

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

    
1040
        # Battery save
1041
        tmpbatsave = _mem.battery_save
1042
        if tmpbatsave >= len(BATSAVE_LIST):
1043
            tmpbatsave = BATSAVE_LIST.index("1:4")
1044
        rs = RadioSetting(
1045
                "battery_save",
1046
                "Battery Save",
1047
                RadioSettingValueList(
1048
                    BATSAVE_LIST,
1049
                    BATSAVE_LIST[tmpbatsave]))
1050
        basic.append(rs)
1051

    
1052
        # Dual watch
1053
        tmpdual = _mem.dual_watch
1054
        if tmpdual >= len(DUALWATCH_LIST):
1055
            tmpdual = 0
1056
        rs = RadioSetting("dualwatch", "Dual Watch", RadioSettingValueList(
1057
            DUALWATCH_LIST, DUALWATCH_LIST[tmpdual]))
1058
        basic.append(rs)
1059

    
1060
        # Tail tone elimination
1061
        rs = RadioSetting(
1062
                "tail_note_elimination",
1063
                "Tail tone elimination",
1064
                RadioSettingValueBoolean(
1065
                    bool(_mem.tail_note_elimination > 0)))
1066
        basic.append(rs)
1067

    
1068
        # VFO open
1069
        rs = RadioSetting("vfo_open", "VFO open",
1070
                          RadioSettingValueBoolean(bool(_mem.vfo_open > 0)))
1071
        basic.append(rs)
1072

    
1073
        # Beep control
1074
        rs = RadioSetting(
1075
                "beep_control",
1076
                "Beep control",
1077
                RadioSettingValueBoolean(bool(_mem.beep_control > 0)))
1078
        basic.append(rs)
1079

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

    
1092
        # Auto keypad lock
1093
        rs = RadioSetting(
1094
                "auto_keypad_lock",
1095
                "Auto keypad lock",
1096
                RadioSettingValueBoolean(bool(_mem.auto_keypad_lock > 0)))
1097
        basic.append(rs)
1098

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

    
1111
        # Keypad Tone
1112
        tmpkeypadtone = _mem.keypad_tone
1113
        if tmpkeypadtone >= len(KEYPADTONE_LIST):
1114
            tmpkeypadtone = 0
1115
        rs = RadioSetting("keypad_tone", "Keypad tone", RadioSettingValueList(
1116
            KEYPADTONE_LIST, KEYPADTONE_LIST[tmpkeypadtone]))
1117
        basic.append(rs)
1118

    
1119
        # Language
1120
        tmplanguage = _mem.language
1121
        if tmplanguage >= len(LANGUAGE_LIST):
1122
            tmplanguage = 0
1123
        rs = RadioSetting("language", "Language", RadioSettingValueList(
1124
            LANGUAGE_LIST, LANGUAGE_LIST[tmplanguage]))
1125
        basic.append(rs)
1126

    
1127
        # Alarm mode
1128
        tmpalarmmode = _mem.alarm_mode
1129
        if tmpalarmmode >= len(ALARMMODE_LIST):
1130
            tmpalarmmode = 0
1131
        rs = RadioSetting("alarm_mode", "Alarm mode", RadioSettingValueList(
1132
            ALARMMODE_LIST, ALARMMODE_LIST[tmpalarmmode]))
1133
        basic.append(rs)
1134

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

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

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

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

    
1171
        # unlock settings
1172

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

    
1182
        # 350TX
1183
        rs = RadioSetting("350tx", "350TX", RadioSettingValueBoolean(
1184
            bool(_mem.int_350tx > 0)))
1185
        unlock.append(rs)
1186

    
1187
        # unknown1
1188
        rs = RadioSetting("unknown11", "UNKNOWN1",
1189
                          RadioSettingValueBoolean(
1190
                              bool(_mem.int_unknown1 > 0)))
1191
        unlock.append(rs)
1192

    
1193
        # 200TX
1194
        rs = RadioSetting("200tx", "200TX", RadioSettingValueBoolean(
1195
            bool(_mem.int_200tx > 0)))
1196
        unlock.append(rs)
1197

    
1198
        # 500TX
1199
        rs = RadioSetting("500tx", "500TX", RadioSettingValueBoolean(
1200
            bool(_mem.int_500tx > 0)))
1201
        unlock.append(rs)
1202

    
1203
        # 350EN
1204
        rs = RadioSetting("350en", "350EN", RadioSettingValueBoolean(
1205
            bool(_mem.int_350en > 0)))
1206
        unlock.append(rs)
1207

    
1208
        # SCREEN
1209
        rs = RadioSetting("screen", "SCREEN", RadioSettingValueBoolean(
1210
            bool(_mem.int_screen > 0)))
1211
        unlock.append(rs)
1212

    
1213
        # readonly info
1214
        # Firmware
1215
        if self.FIRMWARE_VERSION == "":
1216
            firmware = "To get the firmware version please download"
1217
            "the image from the radio first"
1218
        else:
1219
            firmware = self.FIRMWARE_VERSION
1220

    
1221
        val = RadioSettingValueString(0, 128, firmware)
1222
        val.set_mutable(False)
1223
        rs = RadioSetting("fw_ver", "Firmware Version", val)
1224
        roinfo.append(rs)
1225

    
1226
        # Driver version
1227
        val = RadioSettingValueString(0, 128, DRIVER_VERSION)
1228
        val.set_mutable(False)
1229
        rs = RadioSetting("driver_ver", "Driver version", val)
1230
        roinfo.append(rs)
1231

    
1232
        return top
1233

    
1234
    # Store details about a high-level memory to the memory map
1235
    # This is called when a user edits a memory in the UI
1236
    def set_memory(self, mem):
1237
        number = mem.number-1
1238

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

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

    
1268
        if number < 200:
1269
            _mem4.channel_attributes[number] = 0x0f
1270

    
1271
        # find band
1272
        band = _find_band(mem.freq)
1273
        if band is False:
1274
            return mem
1275

    
1276
        # mode
1277
        if mem.mode == "NFM":
1278
            _mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
1279
            _mem.flags1 = _mem.flags1 & ~FLAGS1_ISAM
1280
        elif mem.mode == "FM":
1281
            _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1282
            _mem.flags1 = _mem.flags1 & ~FLAGS1_ISAM
1283
        elif mem.mode == "NAM":
1284
            _mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
1285
            _mem.flags1 = _mem.flags1 | FLAGS1_ISAM
1286
        elif mem.mode == "AM":
1287
            _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1288
            _mem.flags1 = _mem.flags1 | FLAGS1_ISAM
1289

    
1290
        # frequency/offset
1291
        _mem.freq = mem.freq/10
1292
        _mem.offset = mem.offset/10
1293

    
1294
        if mem.duplex == "off" or mem.duplex == "":
1295
            _mem.offset = 0
1296
            _mem.flags1 = _mem.flags1 & ~FLAGS1_OFFSET_MASK
1297
        elif mem.duplex == '-':
1298
            _mem.flags1 = (
1299
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_MINUS
1300
        elif mem.duplex == '+':
1301
            _mem.flags1 = (
1302
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_PLUS
1303

    
1304
        # set band
1305
        if number < 200:
1306
            _mem4.channel_attributes[number] = (
1307
                    _mem4.channel_attributes[number] & ~BANDMASK) | band
1308

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

    
1315
        # tone data
1316
        self._set_tone(mem, _mem)
1317

    
1318
        # step
1319
        _mem.step = STEPS.index(mem.tuning_step)
1320

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

    
1331
        for setting in mem.extra:
1332
            sname = setting.get_name()
1333
            svalue = setting.value.get_value()
1334

    
1335
            if sname == "bclo":
1336
                if svalue:
1337
                    _mem.flags2 = _mem.flags2 | FLAGS2_BCLO
1338
                else:
1339
                    _mem.flags2 = _mem.flags2 & ~FLAGS2_BCLO
1340

    
1341
            if sname == "pttid":
1342
                _mem.dtmf_flags = (
1343
                        (_mem.dtmf_flags & ~FLAGS_DTMF_PTTID_MASK)
1344
                        | (PTTID_LIST.index(svalue) << 1))
1345

    
1346
            if sname == "frev":
1347
                if svalue:
1348
                    _mem.flags2 = _mem.flags2 | FLAGS2_REVERSE
1349
                else:
1350
                    _mem.flags2 = _mem.flags2 & ~FLAGS2_REVERSE
1351

    
1352
            if sname == "dtmfdecode":
1353
                if svalue:
1354
                    _mem.dtmf_flags = _mem.dtmf_flags | FLAGS_DTMF_DECODE
1355
                else:
1356
                    _mem.dtmf_flags = _mem.dtmf_flags & ~FLAGS_DTMF_DECODE
1357

    
1358
            if sname == "scrambler":
1359
                _mem.scrambler = (
1360
                    _mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue)
1361

    
1362
        return mem
(21-21/47)