Project

General

Profile

New Model #10478 » uvk5.py

chirp driver for UV-K5, version 20230524 - Jacek Lipkowski SQ5BPF, 05/24/2023 11:11 AM

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

    
29

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

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

    
42
LOG = logging.getLogger(__name__)
43

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

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

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

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

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

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

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

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

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

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

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

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

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

    
129
"""
130
# flags1
131
FLAGS1_OFFSET_MASK = 0b11
132
FLAGS1_OFFSET_NONE = 0b00
133
FLAGS1_OFFSET_MINUS = 0b10
134
FLAGS1_OFFSET_PLUS = 0b01
135

    
136
FLAGS1_ISSCANLIST = 0b100
137
FLAGS1_ISAM = 0b10000
138

    
139
# flags2
140
FLAGS2_BCLO = 0b10000
141
FLAGS2_POWER_MASK = 0b1100
142
FLAGS2_POWER_HIGH = 0b1000
143
FLAGS2_POWER_MEDIUM = 0b0100
144
FLAGS2_POWER_LOW = 0b0000
145
FLAGS2_BANDWIDTH = 0b10
146
FLAGS2_REVERSE = 0b1
147

    
148
# dtmf_flags
149
PTTID_LIST = ["off", "BOT", "EOT", "BOTH"]
150
FLAGS_DTMF_PTTID_MASK = 0b110  # PTTID: 00-disabled, 01-BOT, 10-EOT, 11-BOTH
151
FLAGS_DTMF_PTTID_DISABLED = 0b000
152
FLAGS_DTMF_PTTID_BOT = 0b010
153
FLAGS_DTMF_PTTID_EOT = 0b100
154
FLAGS_DTMF_PTTID_BOTH = 0b110
155
FLAGS_DTMF_DECODE = 0b1
156

    
157
# power
158
UVK5_POWER_LEVELS = [chirp_common.PowerLevel("Low",  watts=1.50),
159
                     chirp_common.PowerLevel("Med",  watts=3.00),
160
                     chirp_common.PowerLevel("High", watts=5.00),
161
                     ]
162

    
163
# scrambler
164
SCRAMBLER_LIST = ["off", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
165

    
166
# channel display mode
167
CHANNELDISP_LIST = ["Frequency", "Channel No", "Channel Name"]
168
# battery save
169
BATSAVE_LIST = ["OFF", "1:1", "1:2", "1:3", "1:4"]
170

    
171
# Crossband receiving/transmitting
172
CROSSBAND_LIST = ["Off", "Band A", "Band B"]
173
DUALWATCH_LIST = CROSSBAND_LIST
174

    
175
# steps
176
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 8.33]
177

    
178
# ctcss/dcs codes
179
TMODES = ["", "Tone", "DTCS", "DTCS"]
180
TONE_NONE = 0
181
TONE_CTCSS = 1
182
TONE_DCS = 2
183
TONE_RDCS = 3
184

    
185

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

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

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

    
222
MEM_SIZE = 0x2000  # size of all memory
223
PROG_SIZE = 0x1d00  # size of the memory that we will write
224
MEM_BLOCK = 0x80  # largest block of memory that we can reliably write
225

    
226
#bands supported by the UV-K5
227
BANDS = {
228
        0: [ 50.0 , 76.0 ],
229
        1: [ 108.0, 135.9999 ],
230
        2: [ 136.0, 199.9990 ],
231
        3: [ 200.0, 299.9999 ],
232
        4: [ 350.0, 399.9999 ],
233
        5: [ 400.0, 469.9999 ],
234
        6: [ 470.0, 600.0 ]
235
        }
236
BANDMASK=0b1111
237

    
238
# the communication is obfuscated using this fine mechanism
239

    
240

    
241
def xorarr(data: bytes):
242
    tbl = [22, 108, 20, 230, 46, 145, 13, 64, 33, 53, 213, 64, 19, 3, 233, 128]
243
    x = b""
244
    r = 0
245
    for byte in data:
246
        x += bytes([byte ^ tbl[r]])
247
        r = (r+1) % len(tbl)
248
    return x
249

    
250
# if this crc was used for communication to AND from the radio, then it
251
# would be a measure to increase reliability.
252
# but it's only used towards the radio, so it's for further obfuscation
253

    
254

    
255
def calculate_crc16_xmodem(data: bytes):
256
    poly = 0x1021
257
    crc = 0x0
258
    for byte in data:
259
        crc = crc ^ (byte << 8)
260
        for i in range(8):
261
            crc = crc << 1
262
            if (crc & 0x10000):
263
                crc = (crc ^ poly) & 0xFFFF
264
    return crc & 0xFFFF
265

    
266

    
267
def _send_command(serport, data: bytes):
268
    """Send a command to UV-K5 radio"""
269
    LOG.debug("Sending command (unobfuscated) len=0x%4.4x:\n%s" %
270
              (len(data), util.hexprint(data)))
271

    
272
    crc = calculate_crc16_xmodem(data)
273
    data2 = data+bytes([crc & 0xff, (crc >> 8) & 0xff])
274

    
275
    command = b"\xAB\xCD"+bytes([len(data)])+b"\x00"+xorarr(data2)+b"\xDC\xBA"
276
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
277
        LOG.debug("Sending command (obfuscated):\n%s" % util.hexprint(command))
278
    serport.write(command)
279

    
280

    
281
def _receive_reply(serport):
282
    header: bytes
283
    cmd: bytes
284
    footer: bytes
285

    
286
    header = serport.read(4)
287
    if header[0] != 0xAB or header[1] != 0xCD or header[3] != 0x00:
288
        LOG.warning("Bad response header: %s len=%i",
289
                    (util.hexprint(header), len(header)))
290
        raise errors.RadioError("Bad response header")
291

    
292
        return False
293

    
294
    cmd = serport.read(int(header[2]))
295
    footer = serport.read(4)
296

    
297
    if footer[2] != 0xDC or footer[3] != 0xBA:
298
        LOG.debug(
299
                "Reply before bad response footer (obfuscated)"
300
                "len=0x%4.4x:\n%s" % (len(cmd), util.hexprint(cmd)))
301
        LOG.warning("Bad response footer: %s len=%i",
302
                    (util.hexprint(footer), len(footer)))
303
        raise errors.RadioError("Bad response footer")
304
        return False
305

    
306
    if DEBUG_SHOW_OBFUSCATED_COMMANDS:
307
        LOG.debug("Received reply (obfuscated) len=0x%4.4x:\n%s" %
308
                  (len(cmd), util.hexprint(cmd)))
309

    
310
    cmd2 = xorarr(cmd)
311

    
312
    LOG.debug("Received reply (unobfuscated) len=0x%4.4x:\n%s" %
313
              (len(cmd2), util.hexprint(cmd2)))
314

    
315
    return cmd2
316

    
317

    
318
def _getstring(data: bytes, begin, maxlen):
319
    s = ""
320
    c = 0
321
    for i in data:
322
        c += 1
323
        if c < begin:
324
            continue
325
        if i < ord(' ') or i > ord('~'):
326
            break
327
        s += chr(i)
328
    return s
329

    
330

    
331
def _sayhello(serport):
332
    hellopacket = b"\x14\x05\x04\x00\x6a\x39\x57\x64"
333

    
334
    tries = 5
335
    while (True):
336
        LOG.debug("Sending hello packet")
337
        _send_command(serport, hellopacket)
338
        o = _receive_reply(serport)
339
        if (o):
340
            break
341
        tries -= 1
342
        if tries == 0:
343
            LOG.warning("Failed to initialise radio")
344
            raise errors.RadioError("Failed to initialize radio")
345
            return False
346
    firmware = _getstring(o, 5, 16)
347
    LOG.info("Found firmware: %s" % firmware)
348
    return firmware
349

    
350

    
351
def _readmem(serport, offset, length):
352
    LOG.debug("Sending readmem offset=0x%4.4x len=0x%4.4x" % (offset, length))
353

    
354
    readmem = b"\x1b\x05\x08\x00" + \
355
        bytes([offset & 0xff, (offset >> 8) & 0xff, length, 0]) + \
356
        b"\x6a\x39\x57\x64"
357
    _send_command(serport, readmem)
358
    o = _receive_reply(serport)
359
    if DEBUG_SHOW_MEMORY_ACTIONS:
360
        LOG.debug("readmem Received data len=0x%4.4x:\n%s" %
361
                  (len(o), util.hexprint(o)))
362
    return o[8:]
363

    
364

    
365
def _writemem(serport, data, offset):
366
    LOG.debug("Sending writemem offset=0x%4.4x len=0x%4.4x" %
367
              (offset, len(data)))
368

    
369
    if DEBUG_SHOW_MEMORY_ACTIONS:
370
        LOG.debug("writemem sent data offset=0x%4.4x len=0x%4.4x:\n%s" %
371
                  (offset, len(data), util.hexprint(data)))
372

    
373
    dlen = len(data)
374
    writemem = b"\x1d\x05"+bytes([dlen+8])+b"\x00" + \
375
        bytes([offset & 0xff, (offset >> 8) & 0xff, dlen, 1]) + \
376
        b"\x6a\x39\x57\x64"+data
377

    
378
    _send_command(serport, writemem)
379
    o = _receive_reply(serport)
380

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

    
383
    if (o[0] == 0x1e
384
            and
385
            o[4] == (offset & 0xff)
386
            and
387
            o[5] == (offset >> 8) & 0xff):
388
        return True
389
    else:
390
        LOG.warning("Bad data from writemem")
391
        raise errors.RadioError("Bad response to writemem")
392
    return False
393

    
394

    
395
def _resetradio(serport):
396
    resetpacket = b"\xdd\x05\x00\x00"
397
    _send_command(serport, resetpacket)
398

    
399

    
400
def do_download(radio):
401
    serport = radio.pipe
402
    serport.timeout = 0.5
403
    status = chirp_common.Status()
404
    status.cur = 0
405
    status.max = MEM_SIZE
406
    status.msg = "Downloading from radio"
407
    radio.status_fn(status)
408

    
409
    eeprom = b""
410
    f = _sayhello(serport)
411
    if f:
412
        radio.FIRMWARE_VERSION = f
413
    else:
414
        return False
415

    
416
    addr = 0
417
    while addr < MEM_SIZE:
418
        o = _readmem(serport, addr, MEM_BLOCK)
419
        status.cur = addr
420
        radio.status_fn(status)
421

    
422
        if o and len(o) == MEM_BLOCK:
423
            eeprom += o
424
            addr += MEM_BLOCK
425
        else:
426
            raise errors.RadioError("Memory download incomplete")
427

    
428
    return memmap.MemoryMapBytes(eeprom)
429

    
430

    
431
def do_upload(radio):
432
    serport = radio.pipe
433
    serport.timeout = 0.5
434
    status = chirp_common.Status()
435
    status.cur = 0
436
    status.max = PROG_SIZE
437
    status.msg = "Uploading to radio"
438
    radio.status_fn(status)
439

    
440
    f = _sayhello(serport)
441
    if f:
442
        radio.FIRMWARE_VERSION = f
443
    else:
444
        return False
445

    
446
    addr = 0
447
    while addr < PROG_SIZE:
448
        o = radio.get_mmap()[addr:addr+MEM_BLOCK]
449
        _writemem(serport, o, addr)
450
        status.cur = addr
451
        radio.status_fn(status)
452
        if o:
453
            addr += MEM_BLOCK
454
        else:
455
            raise errors.RadioError("Memory upload incomplete")
456
    status.msg = "Uploaded OK"
457

    
458
    _resetradio(serport)
459

    
460
    return True
461

    
462
def _find_band(hz):
463
    mhz=hz/1000000.0
464
    for a in BANDS:
465
        if mhz>=BANDS[a][0] and mhz<=BANDS[a][1]:
466
            return a
467
    return False
468

    
469

    
470

    
471
@directory.register
472
class TemplateRadio(chirp_common.CloneModeRadio):
473
    """Quansheng UV-K5"""
474
    VENDOR = "Quansheng"
475
    MODEL = "UV-K5"
476
    BAUD_RATE = 38400
477

    
478
    NEEDS_COMPAT_SERIAL = False
479
    FIRMWARE_VERSION = ""
480
    # If the user knows what he's doing, then he can expose this
481
    # functionality that goes against the spirit of chirp
482
    if os.getenv("UVK5_DATA_IN_COMMENTS"):
483
        COMMENT_HACK = True
484
    else:
485
        COMMENT_HACK = False
486

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

    
513
    # Return information about this radio's features, including
514
    # how many memories it has, what bands it supports, etc
515
    def get_features(self):
516
        rf = chirp_common.RadioFeatures()
517
        rf.has_bank = False
518
        rf.valid_dtcs_codes = DTCS_CODES
519
        rf.has_rx_dtcs = True
520
        rf.has_ctone = True
521
        rf.has_settings = True
522
        if self.COMMENT_HACK:
523
            rf.has_comment = True
524
        else:
525
            rf.has_comment = False
526
        rf.valid_name_length = 16
527
        rf.valid_power_levels = UVK5_POWER_LEVELS
528

    
529
        # hack so we can input any frequency,
530
        # the 0.1 and 0.01 steps don't work unfortunately
531
        rf.valid_tuning_steps = [0.01, 0.1, 1.0] + STEPS
532

    
533
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
534
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
535
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
536

    
537
        rf.valid_characters = chirp_common.CHARSET_ASCII
538
        rf.valid_modes = ["FM", "NFM", "AM"]
539
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "TSQL-R", "Cross"]
540

    
541
        # This radio supports memories 1-200, 201-214 are the VFO memories
542
        rf.memory_bounds = (1, 214)
543

    
544
        # For now everything that the BK4819 chip supports
545
        # in the future this driver should read the service settings
546
        # and modify the valid_bands accordingly
547
        rf.valid_bands = [(18000000,  620000000),
548
                          (840000000, 1300000000)
549
                          ]
550
        return rf
551

    
552
    # Do a download of the radio from the serial port
553
    def sync_in(self):
554
        self._mmap = do_download(self)
555
        self.process_mmap()
556

    
557
    # Do an upload of the radio to the serial port
558
    def sync_out(self):
559
        do_upload(self)
560

    
561
    # Convert the raw byte array into a memory object structure
562
    def process_mmap(self):
563
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
564

    
565
    # Return a raw representation of the memory object, which
566
    # is very helpful for development
567
    def get_raw_memory(self, number):
568
        return repr(self._memobj.channel[number-1])
569

    
570
    def validate_memory(self, mem):
571
        msgs = super().validate_memory(mem)
572
        return msgs
573

    
574
    def _set_tone(self, mem, _mem):
575
        ((txmode, txtone, txpol),
576
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
577

    
578
        if txmode == "Tone":
579
            txtoval = CTCSS_TONES.index(txtone)
580
            txmoval = 0b01
581
        elif txmode == "DTCS":
582
            txmoval = txpol == "R" and 0b11 or 0b10
583
            txtoval = DTCS_CODES.index(txtone)
584
        else:
585
            txmoval = 0
586
            txtoval = 0
587

    
588
        if rxmode == "Tone":
589
            rxtoval = CTCSS_TONES.index(rxtone)
590
            rxmoval = 0b01
591
        elif rxmode == "DTCS":
592
            rxmoval = rxpol == "R" and 0b11 or 0b10
593
            rxtoval = DTCS_CODES.index(rxtone)
594
        else:
595
            rxmoval = 0
596
            rxtoval = 0
597

    
598
        _mem.code_flag = (_mem.code_flag & 0b11001100) | (
599
            txmoval << 4) | rxmoval
600
        _mem.rxcode = rxtoval
601
        _mem.txcode = txtoval
602

    
603
    def _get_tone(self, mem, _mem):
604
        rxtype = _mem.code_flag & 0x03
605
        txtype = (_mem.code_flag >> 4) & 0x03
606
        rx_tmode = TMODES[rxtype]
607
        tx_tmode = TMODES[txtype]
608

    
609
        rx_tone = tx_tone = None
610

    
611
        if tx_tmode == "Tone":
612
            if _mem.txcode < len(CTCSS_TONES):
613
                tx_tone = CTCSS_TONES[_mem.txcode]
614
            else:
615
                tx_tone = 0
616
                tx_tmode = ""
617
        elif tx_tmode == "DTCS":
618
            if _mem.txcode < len(DTCS_CODES):
619
                tx_tone = DTCS_CODES[_mem.txcode]
620
            else:
621
                tx_tone = 0
622
                tx_tmode = ""
623

    
624
        if rx_tmode == "Tone":
625
            if _mem.rxcode < len(CTCSS_TONES):
626
                rx_tone = CTCSS_TONES[_mem.rxcode]
627
            else:
628
                rx_tone = 0
629
                rx_tmode = ""
630
        elif rx_tmode == "DTCS":
631
            if _mem.rxcode < len(DTCS_CODES):
632
                rx_tone = DTCS_CODES[_mem.rxcode]
633
            else:
634
                rx_tone = 0
635
                rx_tmode = ""
636

    
637
        tx_pol = txtype == 0x03 and "R" or "N"
638
        rx_pol = rxtype == 0x03 and "R" or "N"
639

    
640
        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
641
                                       (rx_tmode, rx_tone, rx_pol))
642

    
643
    # Extract a high-level memory object from the low-level memory map
644
    # This is called to populate a memory in the UI
645

    
646
    def get_memory(self, number2):
647

    
648
        number = number2-1  # in the radio memories start with 0
649

    
650
        mem = chirp_common.Memory()
651

    
652
        # cutting and pasting configs from different radios
653
        # might try to set channel 0
654
        if number2 == 0:
655
            LOG.warning("Attempt to set channel 0")
656
            return mem
657

    
658
        _mem = self._memobj.channel[number]
659
        tmpcomment = ""
660

    
661
        mem.number = number2
662

    
663
        if number > 199:
664
            mem.name = "VFO_"+str(number-199)
665
        else:
666
            _mem2 = self._memobj.channelname[number]
667
            for char in _mem2.name:
668
                if str(char) == "\xFF" or str(char) == "\x00":
669
                    break
670
                mem.name += str(char)
671
            mem.name = mem.name.rstrip()
672

    
673
        # Convert your low-level frequency to Hertz
674
        mem.freq = int(_mem.freq)*10
675
        mem.offset = int(_mem.offset)*10
676

    
677
        if (mem.offset == 0):
678
            mem.duplex = ''
679
        else:
680
            if (_mem.flags1 & FLAGS1_OFFSET_MASK) == FLAGS1_OFFSET_MINUS:
681
                mem.duplex = '-'
682
            elif (_mem.flags1 & FLAGS1_OFFSET_MASK) == FLAGS1_OFFSET_PLUS:
683
                mem.duplex = '+'
684
            else:
685
                mem.duplex = ''
686

    
687
        # tone data
688
        self._get_tone(mem, _mem)
689

    
690
        # mode
691
        if (_mem.flags1 & FLAGS1_ISAM) > 0:
692
            # Actually not sure if internally there aren't "Narrow AM"
693
            # and "Wide AM" modes. To be investigated.
694
            mem.mode = "AM"
695
        else:
696
            if (_mem.flags2 & FLAGS2_BANDWIDTH) > 0:
697
                mem.mode = "NFM"
698
            else:
699
                mem.mode = "FM"
700

    
701
        # tuning step
702
        tstep = (_mem.step >> 1) & 0x7
703
        if tstep < len(STEPS):
704
            mem.tuning_step = STEPS[tstep]
705
        else:
706
            mem.tuning_step = 2.5
707

    
708
        # power
709
        if (_mem.flags2 & FLAGS2_POWER_MASK) == FLAGS2_POWER_HIGH:
710
            mem.power = UVK5_POWER_LEVELS[2]
711
        elif (_mem.flags2 & FLAGS2_POWER_MASK) == FLAGS2_POWER_MEDIUM:
712
            mem.power = UVK5_POWER_LEVELS[1]
713
        else:
714
            mem.power = UVK5_POWER_LEVELS[0]
715

    
716
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
717
        if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
718
            mem.empty = True
719
        else:
720
            mem.empty = False
721

    
722
        mem.extra = RadioSettingGroup("Extra", "extra")
723

    
724
        # BCLO
725
        is_bclo = bool(_mem.flags2 & FLAGS2_BCLO > 0)
726
        rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(is_bclo))
727
        mem.extra.append(rs)
728
        tmpcomment += "BCLO:"+(is_bclo and "ON" or "off")+" "
729

    
730
        # Frequency reverse - whatever that means, don't see it in the manual
731
        is_frev = bool(_mem.flags2 & FLAGS2_REVERSE > 0)
732
        rs = RadioSetting("frev", "FreqRev", RadioSettingValueBoolean(is_frev))
733
        mem.extra.append(rs)
734
        tmpcomment += "FreqReverse:"+(is_frev and "ON" or "off")+" "
735

    
736
        # PTTID
737
        pttid = (_mem.dtmf_flags & FLAGS_DTMF_PTTID_MASK) >> 1
738
        rs = RadioSetting("pttid", "PTTID", RadioSettingValueList(
739
            PTTID_LIST, PTTID_LIST[pttid]))
740
        mem.extra.append(rs)
741
        tmpcomment += "PTTid:"+PTTID_LIST[pttid]+" "
742

    
743
        # DTMF DECODE
744
        is_dtmf = bool(_mem.dtmf_flags & FLAGS_DTMF_DECODE > 0)
745
        rs = RadioSetting("dtmfdecode", "DTMF decode",
746
                          RadioSettingValueBoolean(is_dtmf))
747
        mem.extra.append(rs)
748
        tmpcomment += "DTMFdecode:"+(is_dtmf and "ON" or "off")+" "
749

    
750
        # Scrambler
751
        if _mem.scrambler & 0x0f < len(SCRAMBLER_LIST):
752
            enc = _mem.scrambler & 0x0f
753
        else:
754
            enc = 0
755

    
756
        rs = RadioSetting("scrambler", "Scrambler", RadioSettingValueList(
757
            SCRAMBLER_LIST, SCRAMBLER_LIST[enc]))
758
        mem.extra.append(rs)
759
        tmpcomment += "Scrambler:"+SCRAMBLER_LIST[enc]+" "
760

    
761
        # Ugly hack: the extra parameters will be shown in the comments field,
762
        # because there is no other way to show them without the user
763
        # right-clicking every channel
764
        # Unfortunately this makes that field effectively read-only, so
765
        # it is only for those that can find it in the sources
766
        if self.COMMENT_HACK:
767
            mem.comment = tmpcomment
768

    
769
        return mem
770

    
771
    def set_settings(self, settings):
772
        _mem = self._memobj
773
        for element in settings:
774
            if not isinstance(element, RadioSetting):
775
                self.set_settings(element)
776
                continue
777

    
778
            # basic settings
779

    
780
            # call channel
781
            if element.get_name() == "call_channel":
782
                _mem.call_channel = int(element.value)-1
783

    
784
            # squelch
785
            if element.get_name() == "squelch":
786
                _mem.squelch = int(element.value)
787
            # TOT
788
            if element.get_name() == "tot":
789
                _mem.max_talk_time = int(element.value)
790
            # NOAA autoscan
791
            if element.get_name() == "noaa_autoscan":
792
                _mem.noaa_autoscan = element.value and 1 or 0
793

    
794
            # vox level
795
            if element.get_name() == "vox_level":
796
                _mem.vox_level = int(element.value)-1
797

    
798
            # mic gain
799
            if element.get_name() == "mic_gain":
800
                _mem.mic_gain = int(element.value)
801

    
802
            # Channel display mode
803
            if element.get_name() == "channel_display_mode":
804
                _mem.channel_display_mode = CHANNELDISP_LIST.index(
805
                    str(element.value))
806

    
807
            # Crossband receiving/transmitting
808
            if element.get_name() == "crossband":
809
                _mem.crossband = CROSSBAND_LIST.index(str(element.value))
810

    
811
            # Battery Save
812
            if element.get_name() == "battery_save":
813
                _mem.battery_save = BATSAVE_LIST.index(str(element.value))
814
            # Dual Watch
815
            if element.get_name() == "dualwatch":
816
                _mem.dual_watch = DUALWATCH_LIST.index(str(element.value))
817

    
818
            # Tail tone elimination
819
            if element.get_name() == "tail_note_elimination":
820
                _mem.tail_note_elimination = element.value and 1 or 0
821

    
822
            # VFO Open
823
            if element.get_name() == "vfo_open":
824
                _mem.vfo_open = element.value and 1 or 0
825

    
826
            # Beep control
827
            if element.get_name() == "beep_control":
828
                _mem.beep_control = element.value and 1 or 0
829

    
830
            # Scan resume mode
831
            if element.get_name() == "scan_resume_mode":
832
                _mem.scan_resume_mode = SCANRESUME_LIST.index(
833
                    str(element.value))
834

    
835
            # Auto keypad lock
836
            if element.get_name() == "auto_keypad_lock":
837
                _mem.auto_keypad_lock = element.value and 1 or 0
838

    
839
            # Power on display mode
840
            if element.get_name() == "welcome_mode":
841
                _mem.power_on_dispmode = WELCOME_LIST.index(str(element.value))
842

    
843
            # Keypad Tone
844
            if element.get_name() == "keypad_tone":
845
                _mem.keypad_tone = KEYPADTONE_LIST.index(str(element.value))
846

    
847
            # Language
848
            if element.get_name() == "language":
849
                _mem.language = LANGUAGE_LIST.index(str(element.value))
850

    
851
            # Alarm mode
852
            if element.get_name() == "alarm_mode":
853
                _mem.alarm_mode = ALARMMODE_LIST.index(str(element.value))
854

    
855
            # Reminding of end of talk
856
            if element.get_name() == "reminding_of_end_talk":
857
                _mem.reminding_of_end_talk = REMENDOFTALK_LIST.index(
858
                    str(element.value))
859

    
860
            # Repeater tail tone elimination
861
            if element.get_name() == "repeater_tail_elimination":
862
                _mem.repeater_tail_elimination = RTE_LIST.index(
863
                    str(element.value))
864

    
865
            # Logo string 1
866
            if element.get_name() == "logo1":
867
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
868
                _mem.logo_line1 = b[0:12]+"\xff\xff\xff\xff"
869

    
870
            # Logo string 2
871
            if element.get_name() == "logo2":
872
                b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12
873
                _mem.logo_line2 = b[0:12]+"\xff\xff\xff\xff"
874

    
875
            # unlock settings
876

    
877
            # FLOCK
878
            if element.get_name() == "flock":
879
                _mem.int_flock = FLOCK_LIST.index(str(element.value))
880

    
881
            # 350TX
882
            if element.get_name() == "350tx":
883
                _mem.int_350tx = element.value and 1 or 0
884

    
885
            # UNKNOWN1
886
            if element.get_name() == "unknown1":
887
                _mem.int_unknown1 = element.value and 1 or 0
888

    
889
            # 200TX
890
            if element.get_name() == "200tx":
891
                _mem.int_200tx = element.value and 1 or 0
892

    
893
            # 500TX
894
            if element.get_name() == "500tx":
895
                _mem.int_500tx = element.value and 1 or 0
896

    
897
            # 350EN
898
            if element.get_name() == "350en":
899
                _mem.int_350en = element.value and 1 or 0
900

    
901
    def get_settings(self):
902
        _mem = self._memobj
903
        basic = RadioSettingGroup("basic", "Basic Settings")
904
        unlock = RadioSettingGroup("unlock", "Unlock Settings")
905
        fmradio = RadioSettingGroup("fmradio", "FM Radio (unimplemented yet)")
906
        roinfo = RadioSettingGroup("roinfo", "Driver information")
907

    
908
        top = RadioSettings(basic, unlock, fmradio, roinfo)
909

    
910
        # basic settings
911

    
912
        # call channel
913
        tmpc = _mem.call_channel+1
914
        if tmpc > 200:
915
            tmpc = 1
916
        rs = RadioSetting("call_channel", "One key call channel",
917
                          RadioSettingValueInteger(1, 200, tmpc))
918
        basic.append(rs)
919

    
920
        # squelch
921
        tmpsq = _mem.squelch
922
        if tmpsq > 9:
923
            tmpsq = 1
924
        rs = RadioSetting("squelch", "Squelch",
925
                          RadioSettingValueInteger(0, 9, tmpsq))
926
        basic.append(rs)
927

    
928
        # TOT
929
        tmptot = _mem.max_talk_time
930
        if tmptot > 10:
931
            tmptot = 10
932
        rs = RadioSetting(
933
                "tot",
934
                "Max talk time [min]",
935
                RadioSettingValueInteger(0, 10, tmptot))
936
        basic.append(rs)
937

    
938
        # NOAA autoscan
939
        rs = RadioSetting(
940
                "noaa_autoscan",
941
                "NOAA Autoscan", RadioSettingValueBoolean(
942
                    bool(_mem.noaa_autoscan > 0)))
943
        basic.append(rs)
944

    
945
        # VOX Level
946
        tmpvox = _mem.vox_level+1
947
        if tmpvox > 10:
948
            tmpvox = 10
949
        rs = RadioSetting("vox_level", "VOX Level",
950
                          RadioSettingValueInteger(1, 10, tmpvox))
951
        basic.append(rs)
952

    
953
        # Mic gain
954
        tmpmicgain = _mem.mic_gain
955
        if tmpmicgain > 4:
956
            tmpmicgain = 4
957
        rs = RadioSetting("mic_gain", "Mic Gain",
958
                          RadioSettingValueInteger(0, 4, tmpmicgain))
959
        basic.append(rs)
960

    
961
        # Channel display mode
962
        tmpchdispmode = _mem.channel_display_mode
963
        if tmpchdispmode >= len(CHANNELDISP_LIST):
964
            tmpchdispmode = 0
965
        rs = RadioSetting(
966
                "channel_display_mode",
967
                "Channel display mode",
968
                RadioSettingValueList(
969
                    CHANNELDISP_LIST,
970
                    CHANNELDISP_LIST[tmpchdispmode]))
971
        basic.append(rs)
972

    
973
        # Crossband receiving/transmitting
974
        tmpcross = _mem.crossband
975
        if tmpcross >= len(CROSSBAND_LIST):
976
            tmpcross = 0
977
        rs = RadioSetting(
978
                "crossband",
979
                "Cross-band receiving/transmitting",
980
                RadioSettingValueList(
981
                    CROSSBAND_LIST,
982
                    CROSSBAND_LIST[tmpcross]))
983
        basic.append(rs)
984

    
985
        # Battery save
986
        rs = RadioSetting(
987
                "battery_save",
988
                "Battery Save",
989
                RadioSettingValueList(
990
                    BATSAVE_LIST,
991
                    BATSAVE_LIST[_mem.battery_save]))
992
        basic.append(rs)
993

    
994
        # Dual watch
995
        tmpdual = _mem.dual_watch
996
        if tmpdual >= len(DUALWATCH_LIST):
997
            tmpdual = 0
998
        rs = RadioSetting("dualwatch", "Dual Watch", RadioSettingValueList(
999
            DUALWATCH_LIST, DUALWATCH_LIST[tmpdual]))
1000
        basic.append(rs)
1001

    
1002
        # Tail tone elimination
1003
        rs = RadioSetting(
1004
                "tail_note_elimination",
1005
                "Tail tone elimination",
1006
                RadioSettingValueBoolean(
1007
                    bool(_mem.tail_note_elimination > 0)))
1008
        basic.append(rs)
1009

    
1010
        # VFO open
1011
        rs = RadioSetting("vfo_open", "VFO open",
1012
                          RadioSettingValueBoolean(bool(_mem.vfo_open > 0)))
1013
        basic.append(rs)
1014

    
1015
        # Beep control
1016
        rs = RadioSetting(
1017
                "beep_control",
1018
                "Beep control",
1019
                RadioSettingValueBoolean(bool(_mem.beep_control > 0)))
1020
        basic.append(rs)
1021

    
1022
        # Scan resume mode
1023
        tmpscanres = _mem.scan_resume_mode
1024
        if tmpscanres >= len(SCANRESUME_LIST):
1025
            tmpscanres = 0
1026
        rs = RadioSetting(
1027
                "scan_resume_mode",
1028
                "Scan resume mode",
1029
                RadioSettingValueList(
1030
                    SCANRESUME_LIST,
1031
                    SCANRESUME_LIST[tmpscanres]))
1032
        basic.append(rs)
1033

    
1034
        # Auto keypad lock
1035
        rs = RadioSetting(
1036
                "auto_keypad_lock",
1037
                "Auto keypad lock",
1038
                RadioSettingValueBoolean(bool(_mem.auto_keypad_lock > 0)))
1039
        basic.append(rs)
1040

    
1041
        # Power on display mode
1042
        tmpdispmode = _mem.power_on_dispmode
1043
        if tmpdispmode >= len(WELCOME_LIST):
1044
            tmpdispmode = 0
1045
        rs = RadioSetting(
1046
                "welcome_mode",
1047
                "Power on display mode",
1048
                RadioSettingValueList(
1049
                    WELCOME_LIST,
1050
                    WELCOME_LIST[tmpdispmode]))
1051
        basic.append(rs)
1052

    
1053
        # Keypad Tone
1054
        tmpkeypadtone = _mem.keypad_tone
1055
        if tmpkeypadtone >= len(KEYPADTONE_LIST):
1056
            tmpkeypadtone = 0
1057
        rs = RadioSetting("keypad_tone", "Keypad tone", RadioSettingValueList(
1058
            KEYPADTONE_LIST, KEYPADTONE_LIST[tmpkeypadtone]))
1059
        basic.append(rs)
1060

    
1061
        # Language
1062
        tmplanguage = _mem.language
1063
        if tmplanguage >= len(LANGUAGE_LIST):
1064
            tmplanguage = 0
1065
        rs = RadioSetting("language", "Language", RadioSettingValueList(
1066
            LANGUAGE_LIST, LANGUAGE_LIST[tmplanguage]))
1067
        basic.append(rs)
1068

    
1069
        # Alarm mode
1070
        tmpalarmmode = _mem.alarm_mode
1071
        if tmpalarmmode >= len(ALARMMODE_LIST):
1072
            tmpalarmmode = 0
1073
        rs = RadioSetting("alarm_mode", "Alarm mode", RadioSettingValueList(
1074
            ALARMMODE_LIST, ALARMMODE_LIST[tmpalarmmode]))
1075
        basic.append(rs)
1076

    
1077
        # Reminding of end of talk
1078
        tmpalarmmode = _mem.reminding_of_end_talk
1079
        if tmpalarmmode >= len(REMENDOFTALK_LIST):
1080
            tmpalarmmode = 0
1081
        rs = RadioSetting(
1082
                "reminding_of_end_talk",
1083
                "Reminding of end of talk",
1084
                RadioSettingValueList(
1085
                    REMENDOFTALK_LIST,
1086
                    REMENDOFTALK_LIST[tmpalarmmode]))
1087
        basic.append(rs)
1088

    
1089
        # Repeater tail tone elimination
1090
        tmprte = _mem.repeater_tail_elimination
1091
        if tmprte >= len(RTE_LIST):
1092
            tmprte = 0
1093
        rs = RadioSetting(
1094
                "repeater_tail_elimination",
1095
                "Repeater tail tone elimination",
1096
                RadioSettingValueList(RTE_LIST, RTE_LIST[tmprte]))
1097
        basic.append(rs)
1098

    
1099
        # Logo string 1
1100
        logo1 = str(_mem.logo_line1).strip("\x20\x00\xff")  # +"\x20"*12
1101
        logo1 = logo1[0:12]
1102
        rs = RadioSetting("logo1", "Logo string 1 (12 characters)",
1103
                          RadioSettingValueString(0, 12, logo1))
1104
        basic.append(rs)
1105

    
1106
        # Logo string 2
1107
        logo2 = str(_mem.logo_line2).strip("\x20\x00\xff")  # +"\x20"*12
1108
        logo2 = logo2[0:12]
1109
        rs = RadioSetting("logo2", "Logo string 2 (12 characters)",
1110
                          RadioSettingValueString(0, 12, logo2))
1111
        basic.append(rs)
1112

    
1113
        # unlock settings
1114

    
1115
        # F-LOCK
1116
        tmpflock = _mem.int_flock
1117
        if tmpflock >= len(FLOCK_LIST):
1118
            tmpflock = 0
1119
        rs = RadioSetting(
1120
            "flock", "F-LOCK",
1121
            RadioSettingValueList(FLOCK_LIST, FLOCK_LIST[tmpflock]))
1122
        unlock.append(rs)
1123

    
1124
        # 350TX
1125
        rs = RadioSetting("350tx", "350TX", RadioSettingValueBoolean(
1126
            bool(_mem.int_350tx > 0)))
1127
        unlock.append(rs)
1128

    
1129
        # unknown1
1130
        rs = RadioSetting("unknown11", "UNKNOWN1",
1131
                          RadioSettingValueBoolean(
1132
                              bool(_mem.int_unknown1 > 0)))
1133
        unlock.append(rs)
1134

    
1135
        # 200TX
1136
        rs = RadioSetting("200tx", "200TX", RadioSettingValueBoolean(
1137
            bool(_mem.int_200tx > 0)))
1138
        unlock.append(rs)
1139

    
1140
        # 500TX
1141
        rs = RadioSetting("500tx", "500TX", RadioSettingValueBoolean(
1142
            bool(_mem.int_500tx > 0)))
1143
        unlock.append(rs)
1144

    
1145
        # 350EN
1146
        rs = RadioSetting("350en", "350EN", RadioSettingValueBoolean(
1147
            bool(_mem.int_350en > 0)))
1148
        unlock.append(rs)
1149

    
1150
        # SCREEN
1151
        rs = RadioSetting("screen", "SCREEN", RadioSettingValueBoolean(
1152
            bool(_mem.int_screen > 0)))
1153
        unlock.append(rs)
1154

    
1155
        # readonly info
1156
        # Firmware
1157
        if self.FIRMWARE_VERSION == "":
1158
            firmware = "To get the firmware version please download"
1159
            "the image from the radio first"
1160
        else:
1161
            firmware = self.FIRMWARE_VERSION
1162
        rs = RadioSetting("fw_ver", "Firmware Version",
1163
                          RadioSettingValueString(0, 128, firmware))
1164
        roinfo.append(rs)
1165

    
1166
        # Driver version
1167
        rs = RadioSetting("driver_ver", "Driver version",
1168
                          RadioSettingValueString(0, 128, DRIVER_VERSION))
1169
        roinfo.append(rs)
1170

    
1171
        rs = RadioSetting(
1172
                "comment_hack", "Show extra channel settings in comments",
1173
                RadioSettingValueString(
1174
                    0, 4,
1175
                    self.COMMENT_HACK and "Yes" or "No"))
1176
        roinfo.append(rs)
1177

    
1178
        return top
1179

    
1180
    # Store details about a high-level memory to the memory map
1181
    # This is called when a user edits a memory in the UI
1182
    def set_memory(self, mem):
1183
        number = mem.number-1
1184

    
1185
        # Get a low-level memory object mapped to the image
1186
        _mem = self._memobj.channel[number]
1187
        _mem4 = self._memobj
1188
        # empty memory
1189
        if mem.empty:
1190
            _mem.set_raw("\xFF" * 16)
1191
            if number < 200:
1192
                _mem2 = self._memobj.channelname[number]
1193
                _mem2.set_raw("\xFF" * 16)
1194
                _mem4.channel_attributes[number] = 0x0f
1195
            return mem
1196

    
1197
        # find band
1198
        band=_find_band(mem.freq)
1199
        if band == False:
1200
            raise errors.RadioError(
1201
                    "Frequency is outside the supported bands")
1202
            return mem
1203

    
1204

    
1205

    
1206
        # mode
1207
        if mem.mode == "AM":
1208
            _mem.flags1 = _mem.flags1 | FLAGS1_ISAM
1209
            _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1210
        else:
1211
            _mem.flags1 = _mem.flags1 & ~FLAGS1_ISAM
1212
            if mem.mode == "NFM":
1213
                _mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
1214
            else:
1215
                _mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
1216

    
1217
        # frequency/offset
1218
        _mem.freq = mem.freq/10
1219
        _mem.offset = mem.offset/10
1220

    
1221
        if mem.duplex == "off" or mem.duplex == "":
1222
            _mem.offset = 0
1223
            _mem.flags1 = _mem.flags1 & ~FLAGS1_OFFSET_MASK
1224
        elif mem.duplex == '-':
1225
            _mem.flags1 = (
1226
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_MINUS
1227
        elif mem.duplex == '+':
1228
            _mem.flags1 = (
1229
                    _mem.flags1 & ~FLAGS1_OFFSET_MASK) | FLAGS1_OFFSET_PLUS
1230

    
1231
        # set band
1232
        if number < 200:
1233
            _mem4.channel_attributes[number] = (
1234
                    _mem4.channel_attributes[number] & ~BANDMASK) | band
1235

    
1236
        # channels >200 are the 14 VFO chanells and don't have names
1237
        if number < 200:
1238
            _mem2 = self._memobj.channelname[number]
1239
            tag = mem.name.ljust(16)[:16]
1240
            _mem2.name = tag  # Store the alpha tag
1241

    
1242
        # tone data
1243
        self._set_tone(mem, _mem)
1244

    
1245
        # step
1246
        _mem.step = STEPS.index(mem.tuning_step) << 1
1247

    
1248
        # tx power
1249
        if str(mem.power) == str(UVK5_POWER_LEVELS[2]):
1250
            _mem.flags2 = (
1251
                _mem.flags2 & ~FLAGS2_POWER_MASK) | FLAGS2_POWER_HIGH
1252
        elif str(mem.power) == str(UVK5_POWER_LEVELS[1]):
1253
            _mem.flags2 = (
1254
                _mem.flags2 & ~FLAGS2_POWER_MASK) | FLAGS2_POWER_MEDIUM
1255
        else:
1256
            _mem.flags2 = (_mem.flags2 & ~FLAGS2_POWER_MASK)
1257

    
1258
        for setting in mem.extra:
1259
            sname = setting.get_name()
1260
            svalue = setting.value.get_value()
1261

    
1262
            if sname == "bclo":
1263
                if svalue:
1264
                    _mem.flags2 = _mem.flags2 | FLAGS2_BCLO
1265
                else:
1266
                    _mem.flags2 = _mem.flags2 & ~FLAGS2_BCLO
1267

    
1268
            if sname == "pttid":
1269
                _mem.dtmf_flags = (
1270
                        (_mem.dtmf_flags & ~FLAGS_DTMF_PTTID_MASK)
1271
                        | (PTTID_LIST.index(svalue) << 1))
1272

    
1273
            if sname == "frev":
1274
                if svalue:
1275
                    _mem.flags2 = _mem.flags2 | FLAGS2_REVERSE
1276
                else:
1277
                    _mem.flags2 = _mem.flags2 & ~FLAGS2_REVERSE
1278

    
1279
            if sname == "dtmfdecode":
1280
                if svalue:
1281
                    _mem.dtmf_flags = _mem.dtmf_flags | FLAGS_DTMF_DECODE
1282
                else:
1283
                    _mem.dtmf_flags = _mem.dtmf_flags & ~FLAGS_DTMF_DECODE
1284

    
1285
            if sname == "scrambler":
1286
                _mem.scrambler = (
1287
                    _mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue)
1288

    
1289
        return mem
(6-6/47)