Project

General

Profile

New Model #10478 » uvk5.py

uvk5.py chirp driver for UV-K5 for modded firmware, version 20230605 - Jacek Lipkowski SQ5BPF, 06/04/2023 11:36 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 v20230605 for modded firmware (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
        0: [18.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, 1300.0]
243
        }
244
BANDMASK = 0b1111
245

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

    
254

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

    
265

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

    
280

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

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

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

    
298

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

    
310
        return False
311

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

    
318
    footer = serport.read(4)
319

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

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

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

    
338
    cmd2 = xorarr(cmd)
339

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

    
343
    return cmd2
344

    
345

    
346
def _getstring(data: bytes, begin, maxlen):
347
    tmplen = min(maxlen+1, len(data))
348
    s = [data[i] for i in range(begin, tmplen)]
349
    for key, val in enumerate(s):
350
        if val < ord(' ') or val > ord('~'):
351
            break
352
    return ''.join(chr(x) for x in s[0:key])
353

    
354

    
355
def _sayhello(serport):
356
    hellopacket = b"\x14\x05\x04\x00\x6a\x39\x57\x64"
357

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

    
374

    
375
def _readmem(serport, offset, length):
376
    LOG.debug("Sending readmem offset=0x%4.4x len=0x%4.4x" % (offset, length))
377

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

    
388

    
389
def _writemem(serport, data, offset):
390
    LOG.debug("Sending writemem offset=0x%4.4x len=0x%4.4x" %
391
              (offset, len(data)))
392

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

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

    
402
    _send_command(serport, writemem)
403
    o = _receive_reply(serport)
404

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

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

    
418

    
419
def _resetradio(serport):
420
    resetpacket = b"\xdd\x05\x00\x00"
421
    _send_command(serport, resetpacket)
422

    
423

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

    
433
    eeprom = b""
434
    f = _sayhello(serport)
435
    if f:
436
        radio.FIRMWARE_VERSION = f
437
    else:
438
        return False
439

    
440
    addr = 0
441
    while addr < MEM_SIZE:
442
        o = _readmem(serport, addr, MEM_BLOCK)
443
        status.cur = addr
444
        radio.status_fn(status)
445

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

    
452
    return memmap.MemoryMapBytes(eeprom)
453

    
454

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

    
464
    f = _sayhello(serport)
465
    if f:
466
        radio.FIRMWARE_VERSION = f
467
    else:
468
        return False
469

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

    
482
    _resetradio(serport)
483

    
484
    return True
485

    
486

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

    
494

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

    
502
    NEEDS_COMPAT_SERIAL = False
503
    FIRMWARE_VERSION = ""
504

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

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

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

    
548
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
549
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
550
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
551

    
552
        rf.valid_characters = chirp_common.CHARSET_ASCII
553
        rf.valid_modes = ["FM", "NFM", "AM", "NAM"]
554
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
555

    
556
        rf.valid_skips = [""]
557

    
558
        # This radio supports memories 1-200, 201-214 are the VFO memories
559
        rf.memory_bounds = (1, 214)
560

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

    
572
    # Do a download of the radio from the serial port
573
    def sync_in(self):
574
        self._mmap = do_download(self)
575
        self.process_mmap()
576

    
577
    # Do an upload of the radio to the serial port
578
    def sync_out(self):
579
        do_upload(self)
580

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

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

    
590
    def validate_memory(self, mem):
591
        msgs = super().validate_memory(mem)
592
        return msgs
593

    
594
    def _set_tone(self, mem, _mem):
595
        ((txmode, txtone, txpol),
596
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
597

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

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

    
618
        _mem.code_flag = (_mem.code_flag & 0b11001100) | (
619
            txmoval << 4) | rxmoval
620
        _mem.rxcode = rxtoval
621
        _mem.txcode = txtoval
622

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

    
629
        rx_tone = tx_tone = None
630

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

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

    
657
        tx_pol = txtype == 0x03 and "R" or "N"
658
        rx_pol = rxtype == 0x03 and "R" or "N"
659

    
660
        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
661
                                       (rx_tmode, rx_tone, rx_pol))
662

    
663
    # Extract a high-level memory object from the low-level memory map
664
    # This is called to populate a memory in the UI
665

    
666
    def get_memory(self, number2):
667
        number = number2-1  # in the radio memories start with 0
668

    
669
        mem = chirp_common.Memory()
670

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

    
677
        _mem = self._memobj.channel[number]
678

    
679
        tmpcomment = ""
680

    
681
        mem.number = number2
682

    
683
        is_empty = False
684
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
685
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
686
            is_empty = True
687

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

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

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

    
719
            return mem
720

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

    
732
        # Convert your low-level frequency to Hertz
733
        mem.freq = int(_mem.freq)*10
734
        mem.offset = int(_mem.offset)*10
735

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

    
746
        # tone data
747
        self._get_tone(mem, _mem)
748

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

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

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

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

    
782
        mem.extra = RadioSettingGroup("Extra", "extra")
783

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

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

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

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

    
810
        # Scrambler
811
        if _mem.scrambler & 0x0f < len(SCRAMBLER_LIST):
812
            enc = _mem.scrambler & 0x0f
813
        else:
814
            enc = 0
815

    
816
        rs = RadioSetting("scrambler", "Scrambler", RadioSettingValueList(
817
            SCRAMBLER_LIST, SCRAMBLER_LIST[enc]))
818
        mem.extra.append(rs)
819
        tmpcomment += "Scrambler:"+SCRAMBLER_LIST[enc]+" "
820

    
821
        return mem
822

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

    
830
            # basic settings
831

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
927
            # unlock settings
928

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

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

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

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

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

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

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

    
960
        top = RadioSettings(basic, unlock, fmradio, roinfo)
961

    
962
        # basic settings
963

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1168
        # unlock settings
1169

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

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

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

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

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

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

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

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

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

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

    
1229
        return top
1230

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

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

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

    
1265
        if number < 200:
1266
            _mem4.channel_attributes[number] = 0x0f
1267

    
1268
        # find tx frequency
1269
        if mem.duplex == '-':
1270
            txfreq = mem.freq - mem.offset
1271
        elif mem.duplex == '+':
1272
            txfreq = mem.freq + mem.offset
1273
        else:
1274
            txfreq = mem.freq
1275

    
1276
        # find band
1277
        band = _find_band(txfreq)
1278
        if band is False:
1279
            raise errors.RadioError(
1280
                    "Transmit frequency %.4fMHz is not supported by this radio"
1281
                    % txfreq/1000000.0)
1282

    
1283
        band = _find_band(mem.freq)
1284
        if band is False:
1285
            return mem
1286

    
1287
        # mode
1288
        if mem.mode == "NFM":
1289
            _mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
1290
            _mem.flags1 = _mem.flags1 & ~FLAGS1_ISAM
1291
        elif mem.mode == "FM":
1292
            _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1293
            _mem.flags1 = _mem.flags1 & ~FLAGS1_ISAM
1294
        elif mem.mode == "NAM":
1295
            _mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
1296
            _mem.flags1 = _mem.flags1 | FLAGS1_ISAM
1297
        elif mem.mode == "AM":
1298
            _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1299
            _mem.flags1 = _mem.flags1 | FLAGS1_ISAM
1300

    
1301
        # frequency/offset
1302
        _mem.freq = mem.freq/10
1303
        _mem.offset = mem.offset/10
1304

    
1305
        if mem.duplex == "off" or mem.duplex == "":
1306
            _mem.offset = 0
1307
            _mem.flags1 = _mem.flags1 & ~FLAGS1_OFFSET_MASK
1308
        elif mem.duplex == '-':
1309
            _mem.flags1 = (
1310
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_MINUS
1311
        elif mem.duplex == '+':
1312
            _mem.flags1 = (
1313
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_PLUS
1314

    
1315
        # set band
1316
        if number < 200:
1317
            _mem4.channel_attributes[number] = (
1318
                    _mem4.channel_attributes[number] & ~BANDMASK) | band
1319

    
1320
        # channels >200 are the 14 VFO chanells and don't have names
1321
        if number < 200:
1322
            _mem2 = self._memobj.channelname[number]
1323
            tag = mem.name.ljust(16)[:16]
1324
            _mem2.name = tag  # Store the alpha tag
1325

    
1326
        # tone data
1327
        self._set_tone(mem, _mem)
1328

    
1329
        # step
1330
        _mem.step = STEPS.index(mem.tuning_step)
1331

    
1332
        # tx power
1333
        if str(mem.power) == str(UVK5_POWER_LEVELS[2]):
1334
            _mem.flags2 = (
1335
                _mem.flags2 & ~FLAGS2_POWER_MASK) | FLAGS2_POWER_HIGH
1336
        elif str(mem.power) == str(UVK5_POWER_LEVELS[1]):
1337
            _mem.flags2 = (
1338
                _mem.flags2 & ~FLAGS2_POWER_MASK) | FLAGS2_POWER_MEDIUM
1339
        else:
1340
            _mem.flags2 = (_mem.flags2 & ~FLAGS2_POWER_MASK)
1341

    
1342
        for setting in mem.extra:
1343
            sname = setting.get_name()
1344
            svalue = setting.value.get_value()
1345

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

    
1352
            if sname == "pttid":
1353
                _mem.dtmf_flags = (
1354
                        (_mem.dtmf_flags & ~FLAGS_DTMF_PTTID_MASK)
1355
                        | (PTTID_LIST.index(svalue) << 1))
1356

    
1357
            if sname == "frev":
1358
                if svalue:
1359
                    _mem.flags2 = _mem.flags2 | FLAGS2_REVERSE
1360
                else:
1361
                    _mem.flags2 = _mem.flags2 & ~FLAGS2_REVERSE
1362

    
1363
            if sname == "dtmfdecode":
1364
                if svalue:
1365
                    _mem.dtmf_flags = _mem.dtmf_flags | FLAGS_DTMF_DECODE
1366
                else:
1367
                    _mem.dtmf_flags = _mem.dtmf_flags & ~FLAGS_DTMF_DECODE
1368

    
1369
            if sname == "scrambler":
1370
                _mem.scrambler = (
1371
                    _mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue)
1372

    
1373
        return mem
(22-22/47)