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)
|