Project

General

Profile

New Model #10478 » uvk5.py

chirp driver for UV-K5, version 20230529_2 - Jacek Lipkowski SQ5BPF, 05/29/2023 11:19 AM

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

    
29

    
30
# import struct
31
import logging
32
# import serial
33

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

    
41
LOG = logging.getLogger(__name__)
42

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

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

    
52
DRIVER_VERSION = "Quansheng UV-K5 driver v20230529 (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

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

    
256

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

    
271

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

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

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

    
289

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

    
301
        return False
302

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

    
309
    footer = serport.read(4)
310

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

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

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

    
329
    cmd2 = xorarr(cmd)
330

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

    
334
    return cmd2
335

    
336

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

    
349

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

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

    
369

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

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

    
383

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

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

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

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

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

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

    
413

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

    
418

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

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

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

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

    
447
    return memmap.MemoryMapBytes(eeprom)
448

    
449

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

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

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

    
477
    _resetradio(serport)
478

    
479
    return True
480

    
481

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

    
489

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

    
497
    NEEDS_COMPAT_SERIAL = False
498
    FIRMWARE_VERSION = ""
499

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

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

    
539
        # hack so we can input any frequency,
540
        # the 0.1 and 0.01 steps don't work unfortunately
541
        rf.valid_tuning_steps = [0.01, 0.1, 1.0] + STEPS
542

    
543
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
544
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
545
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
546

    
547
        rf.valid_characters = chirp_common.CHARSET_ASCII
548
        rf.valid_modes = ["FM", "NFM", "AM"]
549
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
550

    
551
        rf.valid_skips = [""]
552

    
553
        # This radio supports memories 1-200, 201-214 are the VFO memories
554
        rf.memory_bounds = (1, 214)
555

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

    
567
    # Do a download of the radio from the serial port
568
    def sync_in(self):
569
        self._mmap = do_download(self)
570
        self.process_mmap()
571

    
572
    # Do an upload of the radio to the serial port
573
    def sync_out(self):
574
        do_upload(self)
575

    
576
    # Convert the raw byte array into a memory object structure
577
    def process_mmap(self):
578
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
579

    
580
    # Return a raw representation of the memory object, which
581
    # is very helpful for development
582
    def get_raw_memory(self, number):
583
        return repr(self._memobj.channel[number-1])
584

    
585
    def validate_memory(self, mem):
586
        msgs = super().validate_memory(mem)
587
        return msgs
588

    
589
    def _set_tone(self, mem, _mem):
590
        ((txmode, txtone, txpol),
591
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
592

    
593
        if txmode == "Tone":
594
            txtoval = CTCSS_TONES.index(txtone)
595
            txmoval = 0b01
596
        elif txmode == "DTCS":
597
            txmoval = txpol == "R" and 0b11 or 0b10
598
            txtoval = DTCS_CODES.index(txtone)
599
        else:
600
            txmoval = 0
601
            txtoval = 0
602

    
603
        if rxmode == "Tone":
604
            rxtoval = CTCSS_TONES.index(rxtone)
605
            rxmoval = 0b01
606
        elif rxmode == "DTCS":
607
            rxmoval = rxpol == "R" and 0b11 or 0b10
608
            rxtoval = DTCS_CODES.index(rxtone)
609
        else:
610
            rxmoval = 0
611
            rxtoval = 0
612

    
613
        _mem.code_flag = (_mem.code_flag & 0b11001100) | (
614
            txmoval << 4) | rxmoval
615
        _mem.rxcode = rxtoval
616
        _mem.txcode = txtoval
617

    
618
    def _get_tone(self, mem, _mem):
619
        rxtype = _mem.code_flag & 0x03
620
        txtype = (_mem.code_flag >> 4) & 0x03
621
        rx_tmode = TMODES[rxtype]
622
        tx_tmode = TMODES[txtype]
623

    
624
        rx_tone = tx_tone = None
625

    
626
        if tx_tmode == "Tone":
627
            if _mem.txcode < len(CTCSS_TONES):
628
                tx_tone = CTCSS_TONES[_mem.txcode]
629
            else:
630
                tx_tone = 0
631
                tx_tmode = ""
632
        elif tx_tmode == "DTCS":
633
            if _mem.txcode < len(DTCS_CODES):
634
                tx_tone = DTCS_CODES[_mem.txcode]
635
            else:
636
                tx_tone = 0
637
                tx_tmode = ""
638

    
639
        if rx_tmode == "Tone":
640
            if _mem.rxcode < len(CTCSS_TONES):
641
                rx_tone = CTCSS_TONES[_mem.rxcode]
642
            else:
643
                rx_tone = 0
644
                rx_tmode = ""
645
        elif rx_tmode == "DTCS":
646
            if _mem.rxcode < len(DTCS_CODES):
647
                rx_tone = DTCS_CODES[_mem.rxcode]
648
            else:
649
                rx_tone = 0
650
                rx_tmode = ""
651

    
652
        tx_pol = txtype == 0x03 and "R" or "N"
653
        rx_pol = rxtype == 0x03 and "R" or "N"
654

    
655
        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
656
                                       (rx_tmode, rx_tone, rx_pol))
657

    
658
    # Extract a high-level memory object from the low-level memory map
659
    # This is called to populate a memory in the UI
660

    
661
    def get_memory(self, number2):
662
        number = number2-1  # in the radio memories start with 0
663

    
664
        mem = chirp_common.Memory()
665

    
666
        # cutting and pasting configs from different radios
667
        # might try to set channel 0
668
        if number2 == 0:
669
            LOG.warning("Attempt to get channel 0")
670
            return mem
671

    
672
        _mem = self._memobj.channel[number]
673

    
674
        tmpcomment = ""
675

    
676
        mem.number = number2
677

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

    
699
            # actually the step and duplex are overwritten by chirp based on
700
            # bandplan. they are here to document sane defaults for IARU r1
701
            # mem.tuning_step = 25.0
702
            # mem.duplex = "off"
703

    
704
            return mem
705

    
706
        if number > 199:
707
            mem.name = "VFO_"+str(number-199)
708
            mem.immutable = ["name"]
709
        else:
710
            _mem2 = self._memobj.channelname[number]
711
            for char in _mem2.name:
712
                if str(char) == "\xFF" or str(char) == "\x00":
713
                    break
714
                mem.name += str(char)
715
            mem.name = mem.name.rstrip()
716

    
717
        # Convert your low-level frequency to Hertz
718
        mem.freq = int(_mem.freq)*10
719
        mem.offset = int(_mem.offset)*10
720

    
721
        if (mem.offset == 0):
722
            mem.duplex = ''
723
        else:
724
            if (_mem.flags1 & FLAGS1_OFFSET_MASK) == FLAGS1_OFFSET_MINUS:
725
                mem.duplex = '-'
726
            elif (_mem.flags1 & FLAGS1_OFFSET_MASK) == FLAGS1_OFFSET_PLUS:
727
                mem.duplex = '+'
728
            else:
729
                mem.duplex = ''
730

    
731
        # tone data
732
        self._get_tone(mem, _mem)
733

    
734
        # mode
735
        if (_mem.flags1 & FLAGS1_ISAM) > 0:
736
            # Actually not sure if internally there aren't "Narrow AM"
737
            # and "Wide AM" modes. To be investigated.
738
            mem.mode = "AM"
739
        else:
740
            if (_mem.flags2 & FLAGS2_BANDWIDTH) > 0:
741
                mem.mode = "NFM"
742
            else:
743
                mem.mode = "FM"
744

    
745
        # tuning step
746
        tstep = _mem.step & 0x7
747
        if tstep < len(STEPS):
748
            mem.tuning_step = STEPS[tstep]
749
        else:
750
            mem.tuning_step = 2.5
751

    
752
        # power
753
        if (_mem.flags2 & FLAGS2_POWER_MASK) == FLAGS2_POWER_HIGH:
754
            mem.power = UVK5_POWER_LEVELS[2]
755
        elif (_mem.flags2 & FLAGS2_POWER_MASK) == FLAGS2_POWER_MEDIUM:
756
            mem.power = UVK5_POWER_LEVELS[1]
757
        else:
758
            mem.power = UVK5_POWER_LEVELS[0]
759

    
760
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
761
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
762
            mem.empty = True
763
        else:
764
            mem.empty = False
765

    
766
        mem.extra = RadioSettingGroup("Extra", "extra")
767

    
768
        # BCLO
769
        is_bclo = bool(_mem.flags2 & FLAGS2_BCLO > 0)
770
        rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(is_bclo))
771
        mem.extra.append(rs)
772
        tmpcomment += "BCLO:"+(is_bclo and "ON" or "off")+" "
773

    
774
        # Frequency reverse - whatever that means, don't see it in the manual
775
        is_frev = bool(_mem.flags2 & FLAGS2_REVERSE > 0)
776
        rs = RadioSetting("frev", "FreqRev", RadioSettingValueBoolean(is_frev))
777
        mem.extra.append(rs)
778
        tmpcomment += "FreqReverse:"+(is_frev and "ON" or "off")+" "
779

    
780
        # PTTID
781
        pttid = (_mem.dtmf_flags & FLAGS_DTMF_PTTID_MASK) >> 1
782
        rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
783
            PTTID_LIST, PTTID_LIST[pttid]))
784
        mem.extra.append(rs)
785
        tmpcomment += "PTTid:"+PTTID_LIST[pttid]+" "
786

    
787
        # DTMF DECODE
788
        is_dtmf = bool(_mem.dtmf_flags & FLAGS_DTMF_DECODE > 0)
789
        rs = RadioSetting("dtmfdecode", "DTMF decode",
790
                          RadioSettingValueBoolean(is_dtmf))
791
        mem.extra.append(rs)
792
        tmpcomment += "DTMFdecode:"+(is_dtmf and "ON" or "off")+" "
793

    
794
        # Scrambler
795
        if _mem.scrambler & 0x0f < len(SCRAMBLER_LIST):
796
            enc = _mem.scrambler & 0x0f
797
        else:
798
            enc = 0
799

    
800
        rs = RadioSetting("scrambler", "Scrambler", RadioSettingValueList(
801
            SCRAMBLER_LIST, SCRAMBLER_LIST[enc]))
802
        mem.extra.append(rs)
803
        tmpcomment += "Scrambler:"+SCRAMBLER_LIST[enc]+" "
804

    
805
        return mem
806

    
807
    def set_settings(self, settings):
808
        _mem = self._memobj
809
        for element in settings:
810
            if not isinstance(element, RadioSetting):
811
                self.set_settings(element)
812
                continue
813

    
814
            # basic settings
815

    
816
            # call channel
817
            if element.get_name() == "call_channel":
818
                _mem.call_channel = int(element.value)-1
819

    
820
            # squelch
821
            if element.get_name() == "squelch":
822
                _mem.squelch = int(element.value)
823
            # TOT
824
            if element.get_name() == "tot":
825
                _mem.max_talk_time = int(element.value)
826
            # NOAA autoscan
827
            if element.get_name() == "noaa_autoscan":
828
                _mem.noaa_autoscan = element.value and 1 or 0
829

    
830
            # vox level
831
            if element.get_name() == "vox_level":
832
                _mem.vox_level = int(element.value)-1
833

    
834
            # mic gain
835
            if element.get_name() == "mic_gain":
836
                _mem.mic_gain = int(element.value)
837

    
838
            # Channel display mode
839
            if element.get_name() == "channel_display_mode":
840
                _mem.channel_display_mode = CHANNELDISP_LIST.index(
841
                    str(element.value))
842

    
843
            # Crossband receiving/transmitting
844
            if element.get_name() == "crossband":
845
                _mem.crossband = CROSSBAND_LIST.index(str(element.value))
846

    
847
            # Battery Save
848
            if element.get_name() == "battery_save":
849
                _mem.battery_save = BATSAVE_LIST.index(str(element.value))
850
            # Dual Watch
851
            if element.get_name() == "dualwatch":
852
                _mem.dual_watch = DUALWATCH_LIST.index(str(element.value))
853

    
854
            # Tail tone elimination
855
            if element.get_name() == "tail_note_elimination":
856
                _mem.tail_note_elimination = element.value and 1 or 0
857

    
858
            # VFO Open
859
            if element.get_name() == "vfo_open":
860
                _mem.vfo_open = element.value and 1 or 0
861

    
862
            # Beep control
863
            if element.get_name() == "beep_control":
864
                _mem.beep_control = element.value and 1 or 0
865

    
866
            # Scan resume mode
867
            if element.get_name() == "scan_resume_mode":
868
                _mem.scan_resume_mode = SCANRESUME_LIST.index(
869
                    str(element.value))
870

    
871
            # Auto keypad lock
872
            if element.get_name() == "auto_keypad_lock":
873
                _mem.auto_keypad_lock = element.value and 1 or 0
874

    
875
            # Power on display mode
876
            if element.get_name() == "welcome_mode":
877
                _mem.power_on_dispmode = WELCOME_LIST.index(str(element.value))
878

    
879
            # Keypad Tone
880
            if element.get_name() == "keypad_tone":
881
                _mem.keypad_tone = KEYPADTONE_LIST.index(str(element.value))
882

    
883
            # Language
884
            if element.get_name() == "language":
885
                _mem.language = LANGUAGE_LIST.index(str(element.value))
886

    
887
            # Alarm mode
888
            if element.get_name() == "alarm_mode":
889
                _mem.alarm_mode = ALARMMODE_LIST.index(str(element.value))
890

    
891
            # Reminding of end of talk
892
            if element.get_name() == "reminding_of_end_talk":
893
                _mem.reminding_of_end_talk = REMENDOFTALK_LIST.index(
894
                    str(element.value))
895

    
896
            # Repeater tail tone elimination
897
            if element.get_name() == "repeater_tail_elimination":
898
                _mem.repeater_tail_elimination = RTE_LIST.index(
899
                    str(element.value))
900

    
901
            # Logo string 1
902
            if element.get_name() == "logo1":
903
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
904
                _mem.logo_line1 = b[0:12]+"\xff\xff\xff\xff"
905

    
906
            # Logo string 2
907
            if element.get_name() == "logo2":
908
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
909
                _mem.logo_line2 = b[0:12]+"\xff\xff\xff\xff"
910

    
911
            # unlock settings
912

    
913
            # FLOCK
914
            if element.get_name() == "flock":
915
                _mem.int_flock = FLOCK_LIST.index(str(element.value))
916

    
917
            # 350TX
918
            if element.get_name() == "350tx":
919
                _mem.int_350tx = element.value and 1 or 0
920

    
921
            # UNKNOWN1
922
            if element.get_name() == "unknown1":
923
                _mem.int_unknown1 = element.value and 1 or 0
924

    
925
            # 200TX
926
            if element.get_name() == "200tx":
927
                _mem.int_200tx = element.value and 1 or 0
928

    
929
            # 500TX
930
            if element.get_name() == "500tx":
931
                _mem.int_500tx = element.value and 1 or 0
932

    
933
            # 350EN
934
            if element.get_name() == "350en":
935
                _mem.int_350en = element.value and 1 or 0
936

    
937
    def get_settings(self):
938
        _mem = self._memobj
939
        basic = RadioSettingGroup("basic", "Basic Settings")
940
        unlock = RadioSettingGroup("unlock", "Unlock Settings")
941
        fmradio = RadioSettingGroup("fmradio", "FM Radio (unimplemented yet)")
942
        roinfo = RadioSettingGroup("roinfo", "Driver information")
943

    
944
        top = RadioSettings(basic, unlock, fmradio, roinfo)
945

    
946
        # basic settings
947

    
948
        # call channel
949
        tmpc = _mem.call_channel+1
950
        if tmpc > 200:
951
            tmpc = 1
952
        rs = RadioSetting("call_channel", "One key call channel",
953
                          RadioSettingValueInteger(1, 200, tmpc))
954
        basic.append(rs)
955

    
956
        # squelch
957
        tmpsq = _mem.squelch
958
        if tmpsq > 9:
959
            tmpsq = 1
960
        rs = RadioSetting("squelch", "Squelch",
961
                          RadioSettingValueInteger(0, 9, tmpsq))
962
        basic.append(rs)
963

    
964
        # TOT
965
        tmptot = _mem.max_talk_time
966
        if tmptot > 10:
967
            tmptot = 10
968
        rs = RadioSetting(
969
                "tot",
970
                "Max talk time [min]",
971
                RadioSettingValueInteger(0, 10, tmptot))
972
        basic.append(rs)
973

    
974
        # NOAA autoscan
975
        rs = RadioSetting(
976
                "noaa_autoscan",
977
                "NOAA Autoscan", RadioSettingValueBoolean(
978
                    bool(_mem.noaa_autoscan > 0)))
979
        basic.append(rs)
980

    
981
        # VOX Level
982
        tmpvox = _mem.vox_level+1
983
        if tmpvox > 10:
984
            tmpvox = 10
985
        rs = RadioSetting("vox_level", "VOX Level",
986
                          RadioSettingValueInteger(1, 10, tmpvox))
987
        basic.append(rs)
988

    
989
        # Mic gain
990
        tmpmicgain = _mem.mic_gain
991
        if tmpmicgain > 4:
992
            tmpmicgain = 4
993
        rs = RadioSetting("mic_gain", "Mic Gain",
994
                          RadioSettingValueInteger(0, 4, tmpmicgain))
995
        basic.append(rs)
996

    
997
        # Channel display mode
998
        tmpchdispmode = _mem.channel_display_mode
999
        if tmpchdispmode >= len(CHANNELDISP_LIST):
1000
            tmpchdispmode = 0
1001
        rs = RadioSetting(
1002
                "channel_display_mode",
1003
                "Channel display mode",
1004
                RadioSettingValueList(
1005
                    CHANNELDISP_LIST,
1006
                    CHANNELDISP_LIST[tmpchdispmode]))
1007
        basic.append(rs)
1008

    
1009
        # Crossband receiving/transmitting
1010
        tmpcross = _mem.crossband
1011
        if tmpcross >= len(CROSSBAND_LIST):
1012
            tmpcross = 0
1013
        rs = RadioSetting(
1014
                "crossband",
1015
                "Cross-band receiving/transmitting",
1016
                RadioSettingValueList(
1017
                    CROSSBAND_LIST,
1018
                    CROSSBAND_LIST[tmpcross]))
1019
        basic.append(rs)
1020

    
1021
        # Battery save
1022
        rs = RadioSetting(
1023
                "battery_save",
1024
                "Battery Save",
1025
                RadioSettingValueList(
1026
                    BATSAVE_LIST,
1027
                    BATSAVE_LIST[_mem.battery_save]))
1028
        basic.append(rs)
1029

    
1030
        # Dual watch
1031
        tmpdual = _mem.dual_watch
1032
        if tmpdual >= len(DUALWATCH_LIST):
1033
            tmpdual = 0
1034
        rs = RadioSetting("dualwatch", "Dual Watch", RadioSettingValueList(
1035
            DUALWATCH_LIST, DUALWATCH_LIST[tmpdual]))
1036
        basic.append(rs)
1037

    
1038
        # Tail tone elimination
1039
        rs = RadioSetting(
1040
                "tail_note_elimination",
1041
                "Tail tone elimination",
1042
                RadioSettingValueBoolean(
1043
                    bool(_mem.tail_note_elimination > 0)))
1044
        basic.append(rs)
1045

    
1046
        # VFO open
1047
        rs = RadioSetting("vfo_open", "VFO open",
1048
                          RadioSettingValueBoolean(bool(_mem.vfo_open > 0)))
1049
        basic.append(rs)
1050

    
1051
        # Beep control
1052
        rs = RadioSetting(
1053
                "beep_control",
1054
                "Beep control",
1055
                RadioSettingValueBoolean(bool(_mem.beep_control > 0)))
1056
        basic.append(rs)
1057

    
1058
        # Scan resume mode
1059
        tmpscanres = _mem.scan_resume_mode
1060
        if tmpscanres >= len(SCANRESUME_LIST):
1061
            tmpscanres = 0
1062
        rs = RadioSetting(
1063
                "scan_resume_mode",
1064
                "Scan resume mode",
1065
                RadioSettingValueList(
1066
                    SCANRESUME_LIST,
1067
                    SCANRESUME_LIST[tmpscanres]))
1068
        basic.append(rs)
1069

    
1070
        # Auto keypad lock
1071
        rs = RadioSetting(
1072
                "auto_keypad_lock",
1073
                "Auto keypad lock",
1074
                RadioSettingValueBoolean(bool(_mem.auto_keypad_lock > 0)))
1075
        basic.append(rs)
1076

    
1077
        # Power on display mode
1078
        tmpdispmode = _mem.power_on_dispmode
1079
        if tmpdispmode >= len(WELCOME_LIST):
1080
            tmpdispmode = 0
1081
        rs = RadioSetting(
1082
                "welcome_mode",
1083
                "Power on display mode",
1084
                RadioSettingValueList(
1085
                    WELCOME_LIST,
1086
                    WELCOME_LIST[tmpdispmode]))
1087
        basic.append(rs)
1088

    
1089
        # Keypad Tone
1090
        tmpkeypadtone = _mem.keypad_tone
1091
        if tmpkeypadtone >= len(KEYPADTONE_LIST):
1092
            tmpkeypadtone = 0
1093
        rs = RadioSetting("keypad_tone", "Keypad tone", RadioSettingValueList(
1094
            KEYPADTONE_LIST, KEYPADTONE_LIST[tmpkeypadtone]))
1095
        basic.append(rs)
1096

    
1097
        # Language
1098
        tmplanguage = _mem.language
1099
        if tmplanguage >= len(LANGUAGE_LIST):
1100
            tmplanguage = 0
1101
        rs = RadioSetting("language", "Language", RadioSettingValueList(
1102
            LANGUAGE_LIST, LANGUAGE_LIST[tmplanguage]))
1103
        basic.append(rs)
1104

    
1105
        # Alarm mode
1106
        tmpalarmmode = _mem.alarm_mode
1107
        if tmpalarmmode >= len(ALARMMODE_LIST):
1108
            tmpalarmmode = 0
1109
        rs = RadioSetting("alarm_mode", "Alarm mode", RadioSettingValueList(
1110
            ALARMMODE_LIST, ALARMMODE_LIST[tmpalarmmode]))
1111
        basic.append(rs)
1112

    
1113
        # Reminding of end of talk
1114
        tmpalarmmode = _mem.reminding_of_end_talk
1115
        if tmpalarmmode >= len(REMENDOFTALK_LIST):
1116
            tmpalarmmode = 0
1117
        rs = RadioSetting(
1118
                "reminding_of_end_talk",
1119
                "Reminding of end of talk",
1120
                RadioSettingValueList(
1121
                    REMENDOFTALK_LIST,
1122
                    REMENDOFTALK_LIST[tmpalarmmode]))
1123
        basic.append(rs)
1124

    
1125
        # Repeater tail tone elimination
1126
        tmprte = _mem.repeater_tail_elimination
1127
        if tmprte >= len(RTE_LIST):
1128
            tmprte = 0
1129
        rs = RadioSetting(
1130
                "repeater_tail_elimination",
1131
                "Repeater tail tone elimination",
1132
                RadioSettingValueList(RTE_LIST, RTE_LIST[tmprte]))
1133
        basic.append(rs)
1134

    
1135
        # Logo string 1
1136
        logo1 = str(_mem.logo_line1).strip("\x20\x00\xff")  # +"\x20"*12
1137
        logo1 = logo1[0:12]
1138
        rs = RadioSetting("logo1", "Logo string 1 (12 characters)",
1139
                          RadioSettingValueString(0, 12, logo1))
1140
        basic.append(rs)
1141

    
1142
        # Logo string 2
1143
        logo2 = str(_mem.logo_line2).strip("\x20\x00\xff")  # +"\x20"*12
1144
        logo2 = logo2[0:12]
1145
        rs = RadioSetting("logo2", "Logo string 2 (12 characters)",
1146
                          RadioSettingValueString(0, 12, logo2))
1147
        basic.append(rs)
1148

    
1149
        # unlock settings
1150

    
1151
        # F-LOCK
1152
        tmpflock = _mem.int_flock
1153
        if tmpflock >= len(FLOCK_LIST):
1154
            tmpflock = 0
1155
        rs = RadioSetting(
1156
            "flock", "F-LOCK",
1157
            RadioSettingValueList(FLOCK_LIST, FLOCK_LIST[tmpflock]))
1158
        unlock.append(rs)
1159

    
1160
        # 350TX
1161
        rs = RadioSetting("350tx", "350TX", RadioSettingValueBoolean(
1162
            bool(_mem.int_350tx > 0)))
1163
        unlock.append(rs)
1164

    
1165
        # unknown1
1166
        rs = RadioSetting("unknown11", "UNKNOWN1",
1167
                          RadioSettingValueBoolean(
1168
                              bool(_mem.int_unknown1 > 0)))
1169
        unlock.append(rs)
1170

    
1171
        # 200TX
1172
        rs = RadioSetting("200tx", "200TX", RadioSettingValueBoolean(
1173
            bool(_mem.int_200tx > 0)))
1174
        unlock.append(rs)
1175

    
1176
        # 500TX
1177
        rs = RadioSetting("500tx", "500TX", RadioSettingValueBoolean(
1178
            bool(_mem.int_500tx > 0)))
1179
        unlock.append(rs)
1180

    
1181
        # 350EN
1182
        rs = RadioSetting("350en", "350EN", RadioSettingValueBoolean(
1183
            bool(_mem.int_350en > 0)))
1184
        unlock.append(rs)
1185

    
1186
        # SCREEN
1187
        rs = RadioSetting("screen", "SCREEN", RadioSettingValueBoolean(
1188
            bool(_mem.int_screen > 0)))
1189
        unlock.append(rs)
1190

    
1191
        # readonly info
1192
        # Firmware
1193
        if self.FIRMWARE_VERSION == "":
1194
            firmware = "To get the firmware version please download"
1195
            "the image from the radio first"
1196
        else:
1197
            firmware = self.FIRMWARE_VERSION
1198

    
1199
        val = RadioSettingValueString(0, 128, firmware)
1200
        val.set_mutable(False)
1201
        rs = RadioSetting("fw_ver", "Firmware Version", val)
1202
        roinfo.append(rs)
1203

    
1204
        # Driver version
1205
        val = RadioSettingValueString(0, 128, DRIVER_VERSION)
1206
        val.set_mutable(False)
1207
        rs = RadioSetting("driver_ver", "Driver version", val)
1208
        roinfo.append(rs)
1209

    
1210
        return top
1211

    
1212
    # Store details about a high-level memory to the memory map
1213
    # This is called when a user edits a memory in the UI
1214
    def set_memory(self, mem):
1215
        number = mem.number-1
1216

    
1217
        # Get a low-level memory object mapped to the image
1218
        _mem = self._memobj.channel[number]
1219
        _mem4 = self._memobj
1220
        # empty memory
1221
        if mem.empty:
1222
            _mem.set_raw("\xFF" * 16)
1223
            if number < 200:
1224
                _mem2 = self._memobj.channelname[number]
1225
                _mem2.set_raw("\xFF" * 16)
1226
                _mem4.channel_attributes[number] = 0x0f
1227
            return mem
1228

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

    
1246
        if number < 200:
1247
            _mem4.channel_attributes[number] = 0x0f
1248

    
1249
        # find band
1250
        band = _find_band(mem.freq)
1251
        if band is False:
1252
            #    raise errors.RadioError(
1253
            #            "Frequency is outside the supported bands")
1254
            return mem
1255

    
1256
        # mode
1257
        if mem.mode == "AM":
1258
            _mem.flags1 = _mem.flags1 | FLAGS1_ISAM
1259
            _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1260
        else:
1261
            _mem.flags1 = _mem.flags1 & ~FLAGS1_ISAM
1262
            if mem.mode == "NFM":
1263
                _mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
1264
            else:
1265
                _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1266

    
1267
        # frequency/offset
1268
        _mem.freq = mem.freq/10
1269
        _mem.offset = mem.offset/10
1270

    
1271
        if mem.duplex == "off" or mem.duplex == "":
1272
            _mem.offset = 0
1273
            _mem.flags1 = _mem.flags1 & ~FLAGS1_OFFSET_MASK
1274
        elif mem.duplex == '-':
1275
            _mem.flags1 = (
1276
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_MINUS
1277
        elif mem.duplex == '+':
1278
            _mem.flags1 = (
1279
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_PLUS
1280

    
1281
        # set band
1282
        if number < 200:
1283
            _mem4.channel_attributes[number] = (
1284
                    _mem4.channel_attributes[number] & ~BANDMASK) | band
1285

    
1286
        # channels >200 are the 14 VFO chanells and don't have names
1287
        if number < 200:
1288
            _mem2 = self._memobj.channelname[number]
1289
            tag = mem.name.ljust(16)[:16]
1290
            _mem2.name = tag  # Store the alpha tag
1291

    
1292
        # tone data
1293
        self._set_tone(mem, _mem)
1294

    
1295
        # step
1296
        _mem.step = STEPS.index(mem.tuning_step)
1297

    
1298
        # tx power
1299
        if str(mem.power) == str(UVK5_POWER_LEVELS[2]):
1300
            _mem.flags2 = (
1301
                _mem.flags2 & ~FLAGS2_POWER_MASK) | FLAGS2_POWER_HIGH
1302
        elif str(mem.power) == str(UVK5_POWER_LEVELS[1]):
1303
            _mem.flags2 = (
1304
                _mem.flags2 & ~FLAGS2_POWER_MASK) | FLAGS2_POWER_MEDIUM
1305
        else:
1306
            _mem.flags2 = (_mem.flags2 & ~FLAGS2_POWER_MASK)
1307

    
1308
        for setting in mem.extra:
1309
            sname = setting.get_name()
1310
            svalue = setting.value.get_value()
1311

    
1312
            if sname == "bclo":
1313
                if svalue:
1314
                    _mem.flags2 = _mem.flags2 | FLAGS2_BCLO
1315
                else:
1316
                    _mem.flags2 = _mem.flags2 & ~FLAGS2_BCLO
1317

    
1318
            if sname == "pttid":
1319
                _mem.dtmf_flags = (
1320
                        (_mem.dtmf_flags & ~FLAGS_DTMF_PTTID_MASK)
1321
                        | (PTTID_LIST.index(svalue) << 1))
1322

    
1323
            if sname == "frev":
1324
                if svalue:
1325
                    _mem.flags2 = _mem.flags2 | FLAGS2_REVERSE
1326
                else:
1327
                    _mem.flags2 = _mem.flags2 & ~FLAGS2_REVERSE
1328

    
1329
            if sname == "dtmfdecode":
1330
                if svalue:
1331
                    _mem.dtmf_flags = _mem.dtmf_flags | FLAGS_DTMF_DECODE
1332
                else:
1333
                    _mem.dtmf_flags = _mem.dtmf_flags & ~FLAGS_DTMF_DECODE
1334

    
1335
            if sname == "scrambler":
1336
                _mem.scrambler = (
1337
                    _mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue)
1338

    
1339
        return mem
(11-11/47)