Project

General

Profile

New Model #11281 » hg_uv98.py

Test module with different model string - Dan Smith, 04/02/2024 04:58 PM

 
1
# -*- coding: utf-8 -*-
2
# Copyright 2022 Masen Furer <kf7hvm@0x26.net>
3
#
4
# This program is free software: you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation, either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
#
17
# Lanchonlh HG-UV98 driver written by
18
#   Masen Furer <kf7hvm@0x26.net>
19
# With assistance from
20
#  KG7KMV and Bartłomiej Zieliński
21
# Based on the implementation of Kenwood TK-8102
22

    
23
import logging
24
import struct
25

    
26
from chirp import chirp_common, directory, memmap, errors, util
27
from chirp import bitwise
28
from chirp.settings import RadioSettingGroup, RadioSetting
29
from chirp.settings import RadioSettingValueBoolean, RadioSettingValueList
30
from chirp.settings import RadioSettings
31

    
32
LOG = logging.getLogger(__name__)
33

    
34
MEM_FORMAT = """
35
struct {
36
  lbcd rx_freq[4];
37
  lbcd tx_freq[4];
38
  ul16 rx_tone;
39
  ul16 tx_tone;
40
  u8 unknown1:6,
41
     wide:1,
42
     highpower:1;
43
  u8 unknown2:5,
44
     bcl:1,
45
     scan:1,
46
     unknown3:1;
47
  u8 unknown4[2];
48
} memory[130];
49

    
50
#seekto 0x0a00;
51
struct {
52
  u8 abr;  // 0x0a00
53
  u8 save;
54
  u8 ch_a_step;
55
  u8 ch_b_step;
56
  u8 vox_grd;
57
  u8 ch_a_sql;
58
  u8 ch_b_sql;
59
  u8 roger;
60
  u8 ch_a_v_m;
61
  u8 ch_b_v_m;
62
  u8 ch_a_ch_mdf;
63
  u8 ch_b_ch_mdf;
64
  u8 tdr;
65
  u8 unknown5[3];
66
  u8 unknown6[5];  // 0x0a10
67
  u8 english;
68
  u8 beep;
69
  u8 voice;
70
  u8 night_mode;
71
  u8 abr_lv;  // backlight level
72
  u8 tot;
73
  u8 toa;
74
  u8 vox_dly;
75
  u8 sc_rev;
76
  u8 lockmode;
77
  u8 autolock;
78
  u8 unknown7;  // 0x0a20
79
  u8 pf1_short;
80
  u8 pf1_long;
81
  u8 pf2_short;
82
  u8 pf2_long;
83
  u8 top_short;
84
  u8 top_long;
85
  u8 rpt_rct;
86
  u8 sc_qt;
87
  u8 pri_ch;
88
  u8 pri_scn;
89
  u8 unknown8;
90
  u8 aprs_rx_band;
91
  u8 ch_a_mute;
92
  u8 ch_b_mute;
93
  u8 unknown9[7];  // 0x0a30
94
  u8 tx_priority;
95
  u8 aprs_rx_popup;
96
  u8 aprs_rx_tone;
97
  u8 aprs_tx_tone;
98
  u8 unknown10;
99
  u8 auto_lock_dly;
100
  u8 menu_dly;
101
  u8 beacon_exit_dly;
102
  u8 unknown11;
103
  u8 unknown12[2];        // 0x0a40
104
  u8 ch_a_mem_ch;
105
  u8 ch_b_mem_ch;
106
  u8 unknown13[12];
107
} settings;
108

    
109
#seekto 0x1000;
110
struct {
111
  char name[11];
112
  u8 unknown[5];
113
} name[128];
114

    
115
struct {
116
    char callsign[9];
117
    u8 null;
118
} aprs;
119

    
120
#seekto 0x1f80;
121
struct {
122
  ul32 unknown[8];
123
} unknown_settings;
124

    
125
"""
126

    
127
BOUNDS = [(136000000, 174000000), (400000000, 500000000)]
128
OFFSETS = [600000, 5000000]
129
MAX_CHANNELS = 128
130
MAX_NAME = 8
131
NAME_FIELD_SIZE = 11
132
CHUNK_SIZE = 64
133
MAX_ADDR = 0x2000
134
POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1),
135
                chirp_common.PowerLevel("High", watts=5)]
136
MODES = ["FM", "NFM"]
137
SPECIAL_CHANNELS = ['VFO-A', 'VFO-B']
138

    
139
# Settings maps
140
ABR_LIST = [str(v) for v in range(0, 151, 5)]
141
STEP_LIST = ["5.0", "6.25", "10.0", "12.5", "25.0", "50.0", "100.0"]
142
VOX_LIST = ["OFF", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
143
SQL_LIST = [str(v) for v in range(0, 10)]
144
ROGER_LIST = ["OFF", "BEGIN", "END", "BOTH"]
145
MDF_LIST = ["NUM+FREQ", "NUMBER", "NAME"]
146
VM_LIST = ["VFO", "MEMORY"]
147
LANG_LIST = ["CHINESE", "ENGLISH"]
148
ABR_LV_LIST = [str(v) for v in range(1, 11)]
149
TOT_LIST = [str(v) for v in range(0, 601, 15)]
150
TOA_LIST = ["OFF"] + ["{} S".format(v) for v in range(1, 11)]
151
VOX_DLY_LIST = [str(v) for v in range(1, 11)]
152
SC_REV_LIST = ["TIME", "BUSY", "HOLD"]
153
LOCKMODE_LIST = ["KEY", "KEY+DIAL", "KEY+DIAL+PTT"]
154
AUTOLOCK_LIST = ["AUTO", "Manual"]
155
PF1_LIST = ["BACK LIGHT", "SCAN", "SQUELCH", "TORCH", "BAND A/B"]
156
PF2_LIST = ["BEACON", "LIST", "TORCH", "BACK LIGHT"]
157
TOP_LIST = ["ALERT", "REMOTE ALERT", "TORCH", "TXP", "CH MDF", "OFF_NET_KEY"]
158
SC_QT_LIST = ["Decode", "Encode", "Decode+Encode"]
159
APRS_RX_LIST = ["OFF", "BAND A", "BAND B"]
160
TX_PRIORITY_LIST = ["VOICE", "APRS"]
161
AUTOLOCK_DLY_LIST = ["{} S".format(v) for v in range(5, 31)]
162
BEACON_EXIT_DLY_LIST = MENU_DLY_LIST = AUTOLOCK_DLY_LIST
163

    
164

    
165
def make_frame(cmd, addr, length, data=b""):
166
    if not isinstance(data, bytes):
167
        data = data.decode("ascii")
168
    return struct.pack(">BHB", ord(cmd), addr, length) + data
169

    
170

    
171
def send(radio, frame):
172
    LOG.debug("%04i P>R: %s" % (len(frame), util.hexprint(frame)))
173
    radio.pipe.write(frame)
174

    
175

    
176
def recv(radio, readdata=True):
177
    hdr = radio.pipe.read(4)
178
    cmd, addr, length = struct.unpack(">BHB", hdr)
179
    if readdata:
180
        data = radio.pipe.read(length)
181
        LOG.debug("     P<R: %s" % util.hexprint(hdr + data))
182
        if len(data) != length:
183
            raise errors.RadioError("Radio sent %i bytes (expected %i)" % (
184
                    len(data), length))
185
    else:
186
        data = b""
187
    radio.pipe.write(b"\x06")
188
    ack = radio.pipe.read(1)
189
    if ack != b"\x06":
190
        raise errors.RadioError("Radio didn't ack our read ack")
191
    return addr, bytes(data)
192

    
193

    
194
def do_ident(radio):
195
    send(radio, b"NiNHSG0N")
196
    ack = radio.pipe.read(1)
197
    if ack != b"\x06":
198
        raise errors.RadioError("Radio refused program mode: {}".format(ack))
199
    radio.pipe.write(b"\x02")
200
    ident = radio.pipe.read(8)
201
    LOG.debug('ident string was %r' % ident)
202
    if ident != radio.IDENT:
203
        raise errors.RadioError(
204
            "Incorrect model: %s, expected %r" % (
205
                util.hexprint(ident), radio.IDENT))
206
    LOG.info("Model: %s (%s)" % (radio.MODEL, util.hexprint(ident)))
207
    radio.pipe.write(b"\x06")
208
    ack = radio.pipe.read(1)
209
    if ack != b"\x06":
210
        raise errors.RadioError("Radio entered program mode, but didn't ack our ack")
211

    
212

    
213
def do_download(radio):
214
    radio.pipe.parity = "E"
215
    radio.pipe.timeout = 1
216
    do_ident(radio)
217

    
218
    data = bytes(b"")
219
    for addr in range(0, MAX_ADDR, CHUNK_SIZE):
220
        send(radio, make_frame(bytes(b"R"), addr, CHUNK_SIZE))
221
        _addr, _data = recv(radio)
222
        if _addr != addr:
223
            raise errors.RadioError("Radio sent unexpected address")
224
        data += _data
225
        radio.pipe.write(b"\x06")
226
        ack = radio.pipe.read(1)
227
        if ack != b"\x06":
228
            raise errors.RadioError("Radio refused block at %04x" % addr)
229

    
230
        status = chirp_common.Status()
231
        status.cur = addr
232
        status.max = MAX_ADDR
233
        status.msg = "Cloning from radio"
234
        radio.status_fn(status)
235

    
236
    radio.pipe.write(b"\x45")
237
    return memmap.MemoryMapBytes(data)
238

    
239

    
240
def do_upload(radio):
241
    radio.pipe.parity = "E"
242
    radio.pipe.timeout = 1
243
    do_ident(radio)
244

    
245
    mmap = radio._mmap
246
    for addr in range(0, MAX_ADDR, CHUNK_SIZE):
247
        send(radio, make_frame(b"W", addr, CHUNK_SIZE, mmap[addr:addr + CHUNK_SIZE]))
248
        ack = radio.pipe.read(1)
249
        if ack != b"\x06":
250
            raise errors.RadioError("Radio refused block at %04x" % addr)
251
        radio.pipe.write(b"\x06")
252
        ack = radio.pipe.read(1)
253
        if ack != b"\x06":
254
            raise errors.RadioError("Radio didn't ack our read ack")
255

    
256
        status = chirp_common.Status()
257
        status.cur = addr
258
        status.max = MAX_ADDR
259
        status.msg = "Cloning to radio"
260
        radio.status_fn(status)
261

    
262
    radio.pipe.write(b"\x45")
263

    
264

    
265
def offset_for(freq):
266
    for bounds, offset in zip(BOUNDS, OFFSETS):
267
        if bounds[0] <= freq <= bounds[1]:
268
            return offset
269
    return 0
270

    
271

    
272
class RadioSettingValueChannel(RadioSettingValueList):
273
    """A setting that points to a defined channel."""
274
    def __init__(self, radio, current_raw):
275
        current = int(current_raw)
276
        current_mem = radio.get_memory(current)
277
        lo, hi = radio.get_features().memory_bounds
278
        options = [
279
            self._format_memory(mem)
280
            for mem in [radio.get_memory(n)
281
                        for n in range(lo, hi + 1)]]
282
        RadioSettingValueList.__init__(self, options,
283
                                       self._format_memory(current_mem))
284

    
285
    @staticmethod
286
    def _format_memory(m):
287
        if m.empty:
288
            return str(int(m.number))
289
        return "%i %.4f %s" % (m.number, m.freq / 1e6, m.name)
290

    
291
    def __int__(self):
292
        return int(self.get_value().partition(" ")[0])
293

    
294

    
295
@directory.register
296
class LanchonlhHG_UV98(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
297
    """
298
    Lanchonlh HG-UV98
299

    
300
    Memory map decoding by KG7KMV
301
    Chirp integration by KF7HVM
302
    """
303
    VENDOR = "Lanchonlh"
304
    MODEL = "HG-UV98"
305
    IDENT = b"P3LL\x13\x10\x08\xF8"
306
    BAUD_RATE = 9600
307
    NEEDS_COMPAT_SERIAL = False
308

    
309
    _upper = MAX_CHANNELS
310

    
311
    @classmethod
312
    def get_prompts(cls):
313
        rp = chirp_common.RadioPrompts()
314
        rp.experimental = (
315
            "This Lanchonlh HG-UV98 driver is an alpha version. "
316
            "Proceed with Caution and backup your data. "
317
            "Always confirm the correctness of your settings with the "
318
            "official programming tool.")
319
        return rp
320

    
321
    def get_features(self):
322
        rf = chirp_common.RadioFeatures()
323
        rf.has_settings = True
324
        rf.has_cross = False
325
        rf.has_bank = False
326
        rf.has_tuning_step = False
327
        rf.has_name = True
328
        rf.has_rx_dtcs = True
329
        rf.valid_characters = chirp_common.CHARSET_ALPHANUMERIC
330
        rf.valid_tuning_steps = [5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0, 100.0]
331
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
332
        rf.valid_modes = MODES
333
        rf.valid_cross_modes = [
334
            "Tone->Tone",
335
            "DTCS->",
336
            "->DTCS",
337
            "Tone->DTCS",
338
            "DTCS->Tone",
339
            "->Tone",
340
            "DTCS->DTCS"]
341
        rf.valid_power_levels = POWER_LEVELS
342
        rf.valid_skips = ["", "S"]
343
        rf.valid_bands = [(136000000, 174000000), (400000000, 500000000)]
344
        rf.valid_name_length = 8
345
        rf.valid_special_chans = SPECIAL_CHANNELS
346
        rf.memory_bounds = (1, self._upper)
347
        return rf
348

    
349
    def sync_in(self):
350
        try:
351
            self._mmap = do_download(self)
352
        except errors.RadioError:
353
            self.pipe.write(b"\x45")
354
            raise
355
        except Exception as e:
356
            raise errors.RadioError("Failed to download from radio: %s" % e)
357
        self.process_mmap()
358

    
359
    def process_mmap(self):
360
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
361

    
362
    def sync_out(self):
363
        try:
364
            do_upload(self)
365
        except errors.RadioError:
366
            self.pipe.write(b"\x45")
367
            raise
368
        except Exception as e:
369
            raise errors.RadioError("Failed to upload to radio: %s" % e)
370

    
371
    def get_raw_memory(self, number):
372
        return repr(self._memobj.memory[number - 1])
373

    
374
    def _get_tone(self, _mem, mem):
375
        def _get_dcs(val):
376
            code = int("%03o" % (val & 0x07FF))
377
            pol = (val & 0x8000) and "R" or "N"
378
            return code, pol
379

    
380
        if _mem.tx_tone != 0xFFFF and _mem.tx_tone > 0x2800:
381
            tcode, tpol = _get_dcs(_mem.tx_tone)
382
            mem.dtcs = tcode
383
            txmode = "DTCS"
384
        elif _mem.tx_tone != 0xFFFF:
385
            mem.rtone = _mem.tx_tone / 10.0
386
            txmode = "Tone"
387
        else:
388
            txmode = ""
389

    
390
        if _mem.rx_tone != 0xFFFF and _mem.rx_tone > 0x2800:
391
            rcode, rpol = _get_dcs(_mem.rx_tone)
392
            mem.rx_dtcs = rcode
393
            rxmode = "DTCS"
394
        elif _mem.rx_tone != 0xFFFF:
395
            mem.ctone = _mem.rx_tone / 10.0
396
            rxmode = "Tone"
397
        else:
398
            rxmode = ""
399

    
400
        if txmode == "Tone" and not rxmode:
401
            mem.tmode = "Tone"
402
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
403
            mem.tmode = "TSQL"
404
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
405
            mem.tmode = "DTCS"
406
        elif rxmode or txmode:
407
            mem.tmode = "Cross"
408
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
409

    
410
        if mem.tmode == "DTCS":
411
            mem.dtcs_polarity = "%s%s" % (tpol, rpol)
412

    
413
    def get_memory(self, number):
414

    
415
        mem = chirp_common.Memory()
416

    
417
        if isinstance(number, str):
418
            mem.number = MAX_CHANNELS + SPECIAL_CHANNELS.index(number) + 1
419
            mem.extd_number = number
420
        elif number > MAX_CHANNELS:
421
            mem.number = number
422
        else:
423
            mem.number = number
424
            _name = self._memobj.name[mem.number - 1]
425

    
426
        _mem = self._memobj.memory[mem.number - 1]
427

    
428
        if mem.number > MAX_CHANNELS:
429
            mem.immutable = ['name']
430
        else:
431
            mem.name, _, _ = _name.name.get_raw().partition(b"\xFF")
432
            mem.name = mem.name.decode('ascii').rstrip()
433

    
434
        if _mem.get_raw()[:4] == b"\xFF\xFF\xFF\xFF":
435
            mem.empty = True
436
            return mem
437

    
438
        mem.freq = int(_mem.rx_freq) * 10
439
        offset = (int(_mem.tx_freq) * 10) - mem.freq
440
        if offset < 0:
441
            mem.offset = abs(offset)
442
            mem.duplex = "-"
443
        elif offset > 0:
444
            mem.offset = offset
445
            mem.duplex = "+"
446
        else:
447
            mem.offset = 0
448

    
449
        self._get_tone(_mem, mem)
450
        mem.power = POWER_LEVELS[_mem.highpower]
451
        mem.mode = MODES[_mem.wide]
452
        mem.skip = not _mem.scan and "S" or ""
453

    
454
        mem.extra = RadioSettingGroup("all", "All Settings")
455

    
456
        bcl = RadioSetting("bcl", "Busy Channel Lockout",
457
                           RadioSettingValueBoolean(bool(_mem.bcl)))
458
        mem.extra.append(bcl)
459

    
460
        return mem
461

    
462
    def _set_tone(self, mem, _mem):
463
        def _set_dcs(code, pol):
464
            val = int("%i" % code, 8) + 0x2800
465
            if pol == "R":
466
                val += 0xA000
467
            return val
468

    
469
        rx_mode = tx_mode = None
470
        rx_tone = tx_tone = 0xFFFF
471

    
472
        if mem.tmode == "Tone":
473
            tx_mode = "Tone"
474
            rx_mode = None
475
            tx_tone = int(mem.rtone * 10)
476
        elif mem.tmode == "TSQL":
477
            rx_mode = tx_mode = "Tone"
478
            rx_tone = tx_tone = int(mem.ctone * 10)
479
        elif mem.tmode == "DTCS":
480
            tx_mode = rx_mode = "DTCS"
481
            tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
482
            rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
483
        elif mem.tmode == "Cross":
484
            tx_mode, rx_mode = mem.cross_mode.split("->")
485
            if tx_mode == "DTCS":
486
                tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
487
            elif tx_mode == "Tone":
488
                tx_tone = int(mem.rtone * 10)
489
            if rx_mode == "DTCS":
490
                rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
491
            elif rx_mode == "Tone":
492
                rx_tone = int(mem.ctone * 10)
493

    
494
        _mem.rx_tone = rx_tone
495
        _mem.tx_tone = tx_tone
496

    
497
        LOG.debug("Set TX %s (%i) RX %s (%i)" %
498
                  (tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))
499

    
500
    def set_memory(self, mem):
501
        _mem = self._memobj.memory[mem.number - 1]
502

    
503
        if mem.empty:
504
            _mem.set_raw(b"\xFF" * 16)
505
            return
506

    
507
        if mem.number < 129:
508
            _name = self._memobj.name[mem.number - 1]
509
            _namelength = self.get_features().valid_name_length
510
            for i in range(NAME_FIELD_SIZE):
511
                try:
512
                    _name.name[i] = mem.name[i]
513
                except IndexError:
514
                    _name.name[i] = "\xFF"
515

    
516
        # clear reserved fields
517
        _mem.unknown1 = 0xFF
518
        _mem.unknown2 = 0xFF
519
        _mem.unknown3 = 0xFF
520
        _mem.unknown4 = (0xFF, 0xFF)
521
        _mem.rx_freq = mem.freq / 10
522
        mem_offset = mem.offset or offset_for(mem.freq)
523
        if mem.duplex == "+":
524
            _mem.tx_freq = (mem.freq + mem_offset) / 10
525
        elif mem.duplex == "-":
526
            _mem.tx_freq = (mem.freq - mem_offset) / 10
527
        else:
528
            _mem.tx_freq = mem.freq / 10
529

    
530
        self._set_tone(mem, _mem)
531

    
532
        _mem.highpower = mem.power == POWER_LEVELS[1]
533
        _mem.wide = mem.mode == "NFM"
534
        _mem.scan = mem.skip != "S"
535

    
536
        for setting in mem.extra:
537
            setattr(_mem, setting.get_name(), setting.value)
538

    
539
    def get_settings(self):
540
        _mem = self._memobj
541
        _settings = _mem.settings
542
        basic = RadioSettingGroup("basic", "Basic")
543
        display = RadioSettingGroup("display", "Display")
544
        scan = RadioSettingGroup("scan", "Scan")
545
        buttons = RadioSettingGroup("buttons", "Buttons")
546
        vfo = RadioSettingGroup("vfo", "VFO")
547
        advanced = RadioSettingGroup("advanced", "Advanced")
548
        aprs = RadioSettingGroup("aprs", "APRS")
549
        top = RadioSettings(basic, display, scan, buttons, vfo, advanced, aprs)
550

    
551
        basic.append(
552
            RadioSetting("save", "Power Save",
553
                         RadioSettingValueBoolean(_settings.save)))
554
        basic.append(
555
            RadioSetting("roger", "Roger Beep",
556
                         RadioSettingValueList(ROGER_LIST,
557
                                               ROGER_LIST[_settings.roger])))
558
        basic.append(
559
            RadioSetting("beep", "System Beep",
560
                         RadioSettingValueBoolean(_settings.beep)))
561
        basic.append(
562
            RadioSetting("tot", "Timeout Timer (sec)",
563
                         RadioSettingValueList(TOT_LIST,
564
                                               TOT_LIST[_settings.tot])))
565
        basic.append(
566
            RadioSetting("toa", "Timeout Timer Alarm",
567
                         RadioSettingValueList(TOA_LIST,
568
                                               TOA_LIST[_settings.toa])))
569
        basic.append(
570
            RadioSetting("lockmode", "Lock Mode",
571
                         RadioSettingValueList(
572
                            LOCKMODE_LIST,
573
                            LOCKMODE_LIST[_settings.lockmode])))
574
        basic.append(
575
            RadioSetting("autolock", "Auto Lock",
576
                         RadioSettingValueList(
577
                            AUTOLOCK_LIST,
578
                            AUTOLOCK_LIST[_settings.autolock])))
579
        basic.append(
580
            RadioSetting("auto_lock_dly", "Auto Lock Delay",
581
                         RadioSettingValueList(
582
                            AUTOLOCK_DLY_LIST,
583
                            AUTOLOCK_DLY_LIST[_settings.auto_lock_dly])))
584
        display.append(
585
            RadioSetting("abr", "Screen Save",
586
                         RadioSettingValueList(ABR_LIST,
587
                                               ABR_LIST[_settings.abr])))
588
        display.append(
589
            RadioSetting("abr_lv", "Back Light Brightness",
590
                         RadioSettingValueList(ABR_LV_LIST,
591
                                               ABR_LV_LIST[_settings.abr_lv])))
592
        display.append(
593
            RadioSetting("night_mode", "Night Mode (Light on Dark)",
594
                         RadioSettingValueBoolean(_settings.night_mode)))
595
        display.append(
596
            RadioSetting("menu_dly", "Menu Delay",
597
                         RadioSettingValueList(
598
                            MENU_DLY_LIST,
599
                            MENU_DLY_LIST[_settings.menu_dly])))
600
        display.append(
601
            RadioSetting("english", "Language",
602
                         RadioSettingValueList(LANG_LIST,
603
                                               LANG_LIST[_settings.english])))
604
        scan.append(
605
            RadioSetting("pri_scn", "Priority Scan",
606
                         RadioSettingValueBoolean(_settings.pri_scn)))
607
        scan.append(
608
            RadioSetting("pri_ch", "Priority Channel",
609
                         RadioSettingValueChannel(self, _settings.pri_ch)))
610
        scan.append(
611
            RadioSetting("sc_rev", "Scan Resume",
612
                         RadioSettingValueList(SC_REV_LIST,
613
                                               SC_REV_LIST[_settings.sc_rev])))
614
        scan.append(
615
            RadioSetting("sc_qt", "Code Save",
616
                         RadioSettingValueList(SC_QT_LIST,
617
                                               SC_QT_LIST[_settings.sc_qt])))
618
        buttons.append(
619
            RadioSetting("pf1_short", "PF1 (Side, Upper) Button Short Press",
620
                         RadioSettingValueList(PF1_LIST,
621
                                               PF1_LIST[_settings.pf1_short])))
622
        buttons.append(
623
            RadioSetting("pf1_long", "PF1 (Side, Upper) Button Long Press",
624
                         RadioSettingValueList(PF1_LIST,
625
                                               PF1_LIST[_settings.pf1_long])))
626
        buttons.append(
627
            RadioSetting("pf2_short", "PF2 (Side, Lower) Button Short Press",
628
                         RadioSettingValueList(PF2_LIST,
629
                                               PF2_LIST[_settings.pf2_short])))
630
        buttons.append(
631
            RadioSetting("pf2_long", "PF2 (Side, Lower) Button Long Press",
632
                         RadioSettingValueList(PF2_LIST,
633
                                               PF2_LIST[_settings.pf2_long])))
634
        buttons.append(
635
            RadioSetting("top_short", "Top Button Short Press",
636
                         RadioSettingValueList(TOP_LIST,
637
                                               TOP_LIST[_settings.top_short])))
638
        buttons.append(
639
            RadioSetting("top_long", "Top Button Long Press",
640
                         RadioSettingValueList(TOP_LIST,
641
                                               TOP_LIST[_settings.top_long])))
642
        vfo.append(
643
            RadioSetting("tdr", "VFO B Enabled",
644
                         RadioSettingValueBoolean(_settings.tdr)))
645
        vfo.append(
646
            RadioSetting("ch_a_step", "VFO Frequency Step (A)",
647
                         RadioSettingValueList(
648
                            STEP_LIST,
649
                            STEP_LIST[_settings.ch_a_step])))
650
        vfo.append(
651
            RadioSetting("ch_b_step", "VFO Frequency Step (B)",
652
                         RadioSettingValueList(
653
                            STEP_LIST,
654
                            STEP_LIST[_settings.ch_b_step])))
655
        vfo.append(
656
            RadioSetting("ch_a_sql", "Squelch (A)",
657
                RadioSettingValueList(SQL_LIST,
658
                                      SQL_LIST[_settings.ch_a_sql])))
659
        vfo.append(
660
            RadioSetting("ch_b_sql", "Squelch (B)",
661
                         RadioSettingValueList(SQL_LIST,
662
                                               SQL_LIST[_settings.ch_b_sql])))
663
        vfo.append(
664
            RadioSetting("ch_a_mem_ch", "Memory Channel (A)",
665
                         RadioSettingValueChannel(self,
666
                                                  _settings.ch_a_mem_ch)))
667
        vfo.append(
668
            RadioSetting("ch_b_mem_ch", "Memory Channel (B)",
669
                         RadioSettingValueChannel(self,
670
                                                  _settings.ch_b_mem_ch)))
671
        vfo.append(
672
            RadioSetting("ch_a_ch_mdf", "Memory Display Format (A)",
673
                         RadioSettingValueList(
674
                            MDF_LIST,
675
                            MDF_LIST[_settings.ch_a_ch_mdf])))
676
        vfo.append(
677
            RadioSetting("ch_b_ch_mdf", "Memory Display Format (B)",
678
                         RadioSettingValueList(
679
                            MDF_LIST,
680
                            MDF_LIST[_settings.ch_b_ch_mdf])))
681
        vfo.append(
682
            RadioSetting("ch_a_v_m", "VFO/MEM (A)",
683
                         RadioSettingValueList(
684
                             VM_LIST, VM_LIST[_settings.ch_a_v_m])))
685
        vfo.append(
686
            RadioSetting("ch_b_v_m", "VFO/MEM (B)",
687
                         RadioSettingValueList(
688
                             VM_LIST, VM_LIST[_settings.ch_b_v_m])))
689
        advanced.append(
690
            RadioSetting("vox_grd", "VOX Sensitivity",
691
                         RadioSettingValueList(
692
                             VOX_LIST, VOX_LIST[_settings.vox_grd])))
693
        advanced.append(
694
            RadioSetting("vox_dly", "VOX Delay",
695
                         RadioSettingValueList(
696
                             VOX_DLY_LIST, VOX_DLY_LIST[_settings.vox_dly])))
697
        advanced.append(
698
            RadioSetting("voice", "Voice Assist",
699
                         RadioSettingValueBoolean(_settings.voice)))
700
        advanced.append(
701
            RadioSetting("rpt_rct", "RPT Roger",
702
                         RadioSettingValueBoolean(_settings.rpt_rct)))
703
        aprs.append(
704
            RadioSetting("aprs_rx_band", "RX Band",
705
                         RadioSettingValueList(
706
                             APRS_RX_LIST,
707
                             APRS_RX_LIST[_settings.aprs_rx_band])))
708
        aprs.append(
709
            RadioSetting("ch_a_mute", "Band A Mute",
710
                         RadioSettingValueBoolean(_settings.ch_a_mute)))
711
        aprs.append(
712
            RadioSetting("ch_b_mute", "Band B Mute",
713
                         RadioSettingValueBoolean(_settings.ch_b_mute)))
714
        aprs.append(
715
            RadioSetting("tx_priority", "TX Priority",
716
                RadioSettingValueList(
717
                    TX_PRIORITY_LIST,
718
                    TX_PRIORITY_LIST[_settings.tx_priority])))
719
        aprs.append(
720
            RadioSetting("aprs_rx_popup", "APRS Popup",
721
                         RadioSettingValueBoolean(_settings.aprs_rx_popup)))
722
        aprs.append(
723
            RadioSetting("aprs_rx_tone", "RX Tone",
724
                         RadioSettingValueBoolean(_settings.aprs_rx_tone)))
725
        aprs.append(
726
            RadioSetting("aprs_tx_tone", "TX Tone",
727
                         RadioSettingValueBoolean(_settings.aprs_tx_tone)))
728
        aprs.append(
729
            RadioSetting("beacon_exit_dly", "Beacon Message Delay",
730
                         RadioSettingValueList(
731
                             BEACON_EXIT_DLY_LIST,
732
                             BEACON_EXIT_DLY_LIST[_settings.beacon_exit_dly])))
733

    
734
        return top
735

    
736
    def set_settings(self, settings):
737
        _mem = self._memobj
738
        _settings = _mem.settings
739

    
740
        for element in settings:
741
            if not isinstance(element, RadioSetting):
742
                self.set_settings(element)
743
                continue
744
            name = element.get_name()
745
            value = element.value
746

    
747
            if hasattr(_settings, name):
748
                setattr(_settings, name, value)
(4-4/5)