1
|
# Copyright 2016:
|
2
|
# * Jim Unroe KC9HI, <rock.unroe@gmail.com>
|
3
|
# * Pavel Milanes CO7WT <pavelmc@gmail.com>
|
4
|
#
|
5
|
# This program is free software: you can redistribute it and/or modify
|
6
|
# it under the terms of the GNU General Public License as published by
|
7
|
# the Free Software Foundation, either version 2 of the License, or
|
8
|
# (at your option) any later version.
|
9
|
#
|
10
|
# This program is distributed in the hope that it will be useful,
|
11
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
# GNU General Public License for more details.
|
14
|
#
|
15
|
# You should have received a copy of the GNU General Public License
|
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
|
18
|
import time
|
19
|
import struct
|
20
|
import logging
|
21
|
import re
|
22
|
|
23
|
LOG = logging.getLogger(__name__)
|
24
|
|
25
|
from chirp import chirp_common, directory, memmap
|
26
|
from chirp import bitwise, errors, util
|
27
|
from chirp.settings import RadioSettingGroup, RadioSetting, \
|
28
|
RadioSettingValueBoolean, RadioSettingValueList, \
|
29
|
RadioSettingValueString, RadioSettingValueInteger, \
|
30
|
RadioSettingValueFloat, RadioSettings
|
31
|
from textwrap import dedent
|
32
|
|
33
|
MEM_FORMAT = """
|
34
|
struct mem {
|
35
|
lbcd rxfreq[4];
|
36
|
lbcd txfreq[4];
|
37
|
lbcd rxtone[2];
|
38
|
lbcd txtone[2];
|
39
|
u8 unknown0:2,
|
40
|
txp:2,
|
41
|
wn:2,
|
42
|
unknown1:1,
|
43
|
bcl:1;
|
44
|
u8 unknown2:2,
|
45
|
revert:1,
|
46
|
dname:1,
|
47
|
unknown3:4;
|
48
|
u8 unknown4[2];
|
49
|
};
|
50
|
|
51
|
struct nam {
|
52
|
char name[6];
|
53
|
u8 unknown1[2];
|
54
|
};
|
55
|
|
56
|
#seekto 0x0000;
|
57
|
struct mem left_memory[500];
|
58
|
|
59
|
#seekto 0x2000;
|
60
|
struct mem right_memory[500];
|
61
|
|
62
|
#seekto 0x4000;
|
63
|
struct nam left_names[500];
|
64
|
|
65
|
#seekto 0x5000;
|
66
|
struct nam right_names[500];
|
67
|
|
68
|
#seekto 0x6000;
|
69
|
u8 left_usedflags[64];
|
70
|
|
71
|
#seekto 0x6040;
|
72
|
u8 left_scanflags[64];
|
73
|
|
74
|
#seekto 0x6080;
|
75
|
u8 right_usedflags[64];
|
76
|
|
77
|
#seekto 0x60C0;
|
78
|
u8 right_scanflags[64];
|
79
|
|
80
|
#seekto 0x6160;
|
81
|
struct {
|
82
|
char line32[32];
|
83
|
} embedded_msg;
|
84
|
|
85
|
#seekto 0x6180;
|
86
|
struct {
|
87
|
u8 sbmute:2, // sub band mute
|
88
|
unknown1:1,
|
89
|
workmodb:1, // work mode (right side)
|
90
|
dw:1, // dual watch
|
91
|
audio:1, // audio output mode (stereo/mono)
|
92
|
unknown2:1,
|
93
|
workmoda:1; // work mode (left side)
|
94
|
u8 scansb:1, // scan stop beep
|
95
|
aftone:3, // af tone control
|
96
|
scand:1, // scan directon
|
97
|
scanr:3; // scan resume
|
98
|
u8 rxexp:1, // rx expansion
|
99
|
ptt:1, // ptt mode
|
100
|
display:1, // display select (frequency/clock)
|
101
|
omode:1, // operaton mode
|
102
|
beep:2, // beep volume
|
103
|
spkr:2; // speaker
|
104
|
u8 cpuclk:1, // operating mode(cpu clock)
|
105
|
fkey:3, // fkey function
|
106
|
mrscan:1, // memory scan type
|
107
|
color:3; // lcd backlight color
|
108
|
u8 vox:2, // vox
|
109
|
voxs:3, // vox sensitivity
|
110
|
mgain:3; // mic gain
|
111
|
u8 wbandb:4, // work band (right side)
|
112
|
wbanda:4; // work band (left side)
|
113
|
u8 sqlb:4, // squelch level (right side)
|
114
|
sqla:4; // squelch level (left side)
|
115
|
u8 apo:4, // auto power off
|
116
|
ars:1, // automatic repeater shift
|
117
|
tot:3; // time out timer
|
118
|
u8 stepb:4, // auto step (right side)
|
119
|
stepa:4; // auto step (left side)
|
120
|
u8 rxcoverm:1, // rx coverage-memory
|
121
|
lcdc:3, // lcd contrast
|
122
|
rxcoverv:1, // rx coverage-vfo
|
123
|
lcdb:3; // lcd brightness
|
124
|
u8 smode:1, // smart function mode
|
125
|
timefmt:1, // time format
|
126
|
datefmt:2, // date format
|
127
|
timesig:1, // time signal
|
128
|
keyb:3; // key/led brightness
|
129
|
u8 dwstop:1, // dual watch stop
|
130
|
unknown3:1,
|
131
|
sqlexp:1, // sql expansion
|
132
|
decbandsel:1, // decoding band select
|
133
|
dtmfmodenc:1, // dtmf mode encode
|
134
|
bell:3; // bell ringer
|
135
|
u8 unknown4:2,
|
136
|
btime:6; // lcd backlight time
|
137
|
u8 unknown5:2,
|
138
|
tz:6; // time zone
|
139
|
u8 unknown618E;
|
140
|
u8 unknown618F;
|
141
|
ul16 offseta; // work offset (left side)
|
142
|
ul16 offsetb; // work offset (right side)
|
143
|
ul16 mrcha; // selected memory channel (left)
|
144
|
ul16 mrchb; // selected memory channel (right)
|
145
|
ul16 wpricha; // work priority channel (left)
|
146
|
ul16 wprichb; // work priority channel (right)
|
147
|
u8 unknown6:3,
|
148
|
datasql:2, // data squelch
|
149
|
dataspd:1, // data speed
|
150
|
databnd:2; // data band select
|
151
|
u8 unknown7:1,
|
152
|
pfkey2:3, // mic p2 key
|
153
|
unknown8:1,
|
154
|
pfkey1:3; // mic p1 key
|
155
|
u8 unknown9:1,
|
156
|
pfkey4:3, // mic p4 key
|
157
|
unknowna:1,
|
158
|
pfkey3:3; // mic p3 key
|
159
|
u8 unknownb:7,
|
160
|
dtmfmoddec:1; // dtmf mode decode
|
161
|
} settings;
|
162
|
|
163
|
#seekto 0x61B0;
|
164
|
struct {
|
165
|
char line16[16];
|
166
|
} poweron_msg;
|
167
|
|
168
|
#seekto 0x6300;
|
169
|
struct {
|
170
|
u8 unknown1:3,
|
171
|
ttdgt:5; // dtmf digit time
|
172
|
u8 unknown2:3,
|
173
|
ttint:5; // dtmf interval time
|
174
|
u8 unknown3:3,
|
175
|
tt1stdgt:5; // dtmf 1st digit time
|
176
|
u8 unknown4:3,
|
177
|
tt1stdly:5; // dtmf 1st digit delay
|
178
|
u8 unknown5:3,
|
179
|
ttdlyqt:5; // dtmf delay when use qt
|
180
|
u8 unknown6:3,
|
181
|
ttdkey:5; // dtmf d key function
|
182
|
u8 unknown7;
|
183
|
u8 unknown8:4,
|
184
|
ttautod:4; // dtmf auto dial group
|
185
|
} dtmf;
|
186
|
|
187
|
#seekto 0x6330;
|
188
|
struct {
|
189
|
u8 unknown1:7,
|
190
|
ttsig:1; // dtmf signal
|
191
|
u8 unknown2:4,
|
192
|
ttintcode:4; // dtmf interval code
|
193
|
u8 unknown3:5,
|
194
|
ttgrpcode:3; // dtmf group code
|
195
|
u8 unknown4:4,
|
196
|
ttautorst:4; // dtmf auto reset time
|
197
|
u8 unknown5:5,
|
198
|
ttalert:3; // dtmf alert tone/transpond
|
199
|
} dtmf2;
|
200
|
|
201
|
#seekto 0x6360;
|
202
|
struct {
|
203
|
u8 code1[8]; // dtmf code
|
204
|
u8 code1_len; // dtmf code length
|
205
|
u8 unknown1[7];
|
206
|
u8 code2[8]; // dtmf code
|
207
|
u8 code2_len; // dtmf code length
|
208
|
u8 unknown2[7];
|
209
|
u8 code3[8]; // dtmf code
|
210
|
u8 code3_len; // dtmf code length
|
211
|
u8 unknown3[7];
|
212
|
u8 code4[8]; // dtmf code
|
213
|
u8 code4_len; // dtmf code length
|
214
|
u8 unknown4[7];
|
215
|
u8 code5[8]; // dtmf code
|
216
|
u8 code5_len; // dtmf code length
|
217
|
u8 unknown5[7];
|
218
|
u8 code6[8]; // dtmf code
|
219
|
u8 code6_len; // dtmf code length
|
220
|
u8 unknown6[7];
|
221
|
u8 code7[8]; // dtmf code
|
222
|
u8 code7_len; // dtmf code length
|
223
|
u8 unknown7[7];
|
224
|
u8 code8[8]; // dtmf code
|
225
|
u8 code8_len; // dtmf code length
|
226
|
u8 unknown8[7];
|
227
|
u8 code9[8]; // dtmf code
|
228
|
u8 code9_len; // dtmf code length
|
229
|
u8 unknown9[7];
|
230
|
} dtmfcode;
|
231
|
|
232
|
"""
|
233
|
|
234
|
MEM_SIZE = 0x8000
|
235
|
BLOCK_SIZE = 0x40
|
236
|
MODES = ["FM", "Auto", "NFM", "AM"]
|
237
|
SKIP_VALUES = ["", "S"]
|
238
|
TONES = chirp_common.TONES
|
239
|
DTCS_CODES = chirp_common.DTCS_CODES
|
240
|
NAME_LENGTH = 6
|
241
|
DTMF_CHARS = list("0123456789ABCD*#")
|
242
|
STIMEOUT = 1
|
243
|
|
244
|
# Basic settings lists
|
245
|
LIST_AFTONE = ["Low-3", "Low-2", "Low-1", "Normal", "High-1", "High-2"]
|
246
|
LIST_SPKR = ["Off", "Front", "Rear", "Front + Rear"]
|
247
|
LIST_AUDIO = ["Monaural", "Stereo"]
|
248
|
LIST_SBMUTE = ["Off", "TX", "RX", "Both"]
|
249
|
LIST_MLNHM = ["Min", "Low", "Normal", "High", "Max"]
|
250
|
LIST_PTT = ["Momentary", "Toggle"]
|
251
|
LIST_RXEXP = ["General", "Wide coverage"]
|
252
|
LIST_VOX = ["Off", "Internal mic", "Front hand-mic", "Rear hand-mic"]
|
253
|
LIST_DISPLAY = ["Frequency", "Timer/Clock"]
|
254
|
LIST_MINMAX = ["Min"] + ["%s" % x for x in range(2, 8)] + ["Max"]
|
255
|
LIST_COLOR = ["White-Blue", "Sky-Blue", "Marine-Blue", "Green",
|
256
|
"Yellow-Green", "Orange", "Amber", "White"]
|
257
|
LIST_BTIME = ["Continuous"] + ["%s" % x for x in range(1, 61)]
|
258
|
LIST_MRSCAN = ["All", "Selected"]
|
259
|
LIST_DWSTOP = ["Auto", "Hold"]
|
260
|
LIST_SCAND = ["Down", "Up"]
|
261
|
LIST_SCANR = ["Busy", "Hold", "1 sec", "3 sec", "5 sec"]
|
262
|
LIST_APO = ["Off", ".5", "1", "1.5"] + ["%s" % x for x in range(2, 13)]
|
263
|
LIST_BEEP = ["Off", "Low", "High"]
|
264
|
LIST_FKEY = ["MHz/AD-F", "AF Dual 1(line-in)", "AF Dual 2(AM)", "AF Dual 3(FM)",
|
265
|
"PA", "SQL off", "T-call", "WX"]
|
266
|
LIST_PFKEY = ["Off", "SQL off", "TX power", "Scan", "RPT shift", "Reverse",
|
267
|
"T-Call"]
|
268
|
LIST_AB = ["A", "B"]
|
269
|
LIST_COVERAGE = ["In band", "All"]
|
270
|
LIST_TOT = ["Off"] + ["%s" % x for x in range(5, 25, 5)] + ["30"]
|
271
|
LIST_DATEFMT = ["yyyy/mm/dd", "yyyy/dd/mm", "mm/dd/yyyy", "dd/mm/yyyy"]
|
272
|
LIST_TIMEFMT = ["24H", "12H"]
|
273
|
LIST_TZ = ["-12 INT DL W",
|
274
|
"-11 MIDWAY",
|
275
|
"-10 HAST",
|
276
|
"-9 AKST",
|
277
|
"-8 PST",
|
278
|
"-7 MST",
|
279
|
"-6 CST",
|
280
|
"-5 EST",
|
281
|
"-4:30 CARACAS",
|
282
|
"-4 AST",
|
283
|
"-3:30 NST",
|
284
|
"-3 BRASILIA",
|
285
|
"-2 MATLANTIC",
|
286
|
"-1 AZORES",
|
287
|
"-0 LONDON",
|
288
|
"+0 LONDON",
|
289
|
"+1 ROME",
|
290
|
"+2 ATHENS",
|
291
|
"+3 MOSCOW",
|
292
|
"+3:30 REHRW",
|
293
|
"+4 ABUDNABI",
|
294
|
"+4:30 KABUL",
|
295
|
"+5 ISLMABAD",
|
296
|
"+5:30 NEWDELHI",
|
297
|
"+6 DHAKA",
|
298
|
"+6:30 YANGON",
|
299
|
"+7 BANKOK",
|
300
|
"+8 BEIJING",
|
301
|
"+9 TOKYO",
|
302
|
"+10 ADELAIDE",
|
303
|
"+10 SYDNET",
|
304
|
"+11 NWCLDNIA",
|
305
|
"+12 FIJI",
|
306
|
"+13 NUKALOFA"
|
307
|
]
|
308
|
LIST_BELL = ["Off", "1 time", "3 times", "5 times", "8 times", "Continuous"]
|
309
|
LIST_DATABND = ["Main band", "Sub band", "Left band-fixed", "Right band-fixed"]
|
310
|
LIST_DATASPD = ["1200 bps", "9600 bps"]
|
311
|
LIST_DATASQL = ["Busy/TX", "Busy", "TX"]
|
312
|
|
313
|
# Other settings lists
|
314
|
LIST_CPUCLK = ["Clock frequency 1", "Clock frequency 2"]
|
315
|
|
316
|
# Work mode settings lists
|
317
|
LIST_WORK = ["VFO", "Memory System"]
|
318
|
LIST_WBANDB = ["Air", "H-V", "GR1-V", "GR1-U", "H-U", "GR2"]
|
319
|
LIST_WBANDA = ["Line-in", "AM", "FM"] + LIST_WBANDB
|
320
|
LIST_SQL = ["Open"] + ["%s" % x for x in range(1, 10)]
|
321
|
LIST_STEP = ["Auto", "2.50 KHz", "5.00 KHz", "6.25 KHz", "8.33 KHz",
|
322
|
"9.00 KHz", "10.00 KHz", "12.50 KHz", "15.00 KHz", "20.00 KHz",
|
323
|
"25.00 KHz", "50.00 KHz", "100.00 KHz", "200.00 KHz"]
|
324
|
LIST_SMODE = ["F-1", "F-2"]
|
325
|
|
326
|
# DTMF settings lists
|
327
|
LIST_TTDKEY = ["D code"] + ["Send delay %s s" % x for x in range(1, 17)]
|
328
|
LIST_TT200 = ["%s ms" % x for x in range(50, 210, 10)]
|
329
|
LIST_TT1000 = ["%s ms" % x for x in range(100, 1050, 50)]
|
330
|
LIST_TTSIG = ["Code squelch", "Select call"]
|
331
|
LIST_TTAUTORST = ["Off"] + ["%s s" % x for x in range(1, 16)]
|
332
|
LIST_TTGRPCODE = ["Off"] + list("ABCD*#")
|
333
|
LIST_TTINTCODE = DTMF_CHARS
|
334
|
LIST_TTALERT = ["Off", "Alert tone", "Transpond", "Transpond-ID code",
|
335
|
"Transpond-transpond code"]
|
336
|
LIST_TTAUTOD = ["%s" % x for x in range(1, 10)]
|
337
|
|
338
|
# valid chars on the LCD
|
339
|
VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
|
340
|
"`{|}!\"#$%&'()*+,-./:;<=>?@[]^_"
|
341
|
|
342
|
# Power Levels
|
343
|
POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5),
|
344
|
chirp_common.PowerLevel("Mid", watts=20),
|
345
|
chirp_common.PowerLevel("High", watts=50)]
|
346
|
|
347
|
# B-TECH UV-50X3 id string
|
348
|
UV50X3_id = "VGC6600MD"
|
349
|
|
350
|
|
351
|
def _clean_buffer(radio):
|
352
|
radio.pipe.timeout = 0.005
|
353
|
junk = radio.pipe.read(256)
|
354
|
radio.pipe.timeout = STIMEOUT
|
355
|
if junk:
|
356
|
Log.debug("Got %i bytes of junk before starting" % len(junk))
|
357
|
|
358
|
|
359
|
def _check_for_double_ack(radio):
|
360
|
radio.pipe.timeout = 0.005
|
361
|
c = radio.pipe.read(1)
|
362
|
radio.pipe.timeout = STIMEOUT
|
363
|
if c and c != '\x06':
|
364
|
_exit_program_mode(radio)
|
365
|
raise errors.RadioError('Expected nothing or ACK, got %r' % c)
|
366
|
|
367
|
|
368
|
def _rawrecv(radio, amount):
|
369
|
"""Raw read from the radio device"""
|
370
|
data = ""
|
371
|
try:
|
372
|
data = radio.pipe.read(amount)
|
373
|
except:
|
374
|
_exit_program_mode(radio)
|
375
|
msg = "Generic error reading data from radio; check your cable."
|
376
|
raise errors.RadioError(msg)
|
377
|
|
378
|
if len(data) != amount:
|
379
|
_exit_program_mode(radio)
|
380
|
msg = "Error reading data from radio: not the amount of data we want."
|
381
|
raise errors.RadioError(msg)
|
382
|
|
383
|
return data
|
384
|
|
385
|
|
386
|
def _rawsend(radio, data):
|
387
|
"""Raw send to the radio device"""
|
388
|
try:
|
389
|
radio.pipe.write(data)
|
390
|
except:
|
391
|
raise errors.RadioError("Error sending data to radio")
|
392
|
|
393
|
|
394
|
def _make_frame(cmd, addr, length, data=""):
|
395
|
"""Pack the info in the headder format"""
|
396
|
frame = struct.pack(">BHB", ord(cmd), addr, length)
|
397
|
# add the data if set
|
398
|
if len(data) != 0:
|
399
|
frame += data
|
400
|
# return the data
|
401
|
return frame
|
402
|
|
403
|
|
404
|
def _recv(radio, addr, length=BLOCK_SIZE):
|
405
|
"""Get data from the radio """
|
406
|
# read 4 bytes of header
|
407
|
hdr = _rawrecv(radio, 4)
|
408
|
|
409
|
## check for unexpected extra command byte
|
410
|
#c, a, l = struct.unpack(">BHB", hdr)
|
411
|
#if hdr[0:2] == "WW" and a != addr:
|
412
|
# # extra command byte detected
|
413
|
# # throw away the 1st byte and add the next byte in the buffer
|
414
|
# hdr = hdr[1:] + _rawrecv(radio, 1)
|
415
|
|
416
|
# read 64 bytes (0x40) of data
|
417
|
data = _rawrecv(radio, (BLOCK_SIZE))
|
418
|
|
419
|
# DEBUG
|
420
|
LOG.info("Response:")
|
421
|
LOG.debug(util.hexprint(hdr + data))
|
422
|
|
423
|
c, a, l = struct.unpack(">BHB", hdr)
|
424
|
####if a != addr or l != length or c != ord("W"):
|
425
|
if l != length or c != ord("W"):
|
426
|
_exit_program_mode(radio)
|
427
|
LOG.error("Invalid answer for block 0x%04x:" % addr)
|
428
|
LOG.debug("CMD: %s ADDR: %04x SIZE: %02x" % (c, a, l))
|
429
|
raise errors.RadioError("Unknown response from the radio")
|
430
|
|
431
|
return data
|
432
|
|
433
|
|
434
|
def _do_ident(radio):
|
435
|
"""Put the radio in PROGRAM mode & identify it"""
|
436
|
# set the serial discipline
|
437
|
radio.pipe.baudrate = 115200
|
438
|
radio.pipe.parity = "N"
|
439
|
radio.pipe.timeout = STIMEOUT
|
440
|
|
441
|
# flush input buffer
|
442
|
_clean_buffer(radio)
|
443
|
|
444
|
magic = "V66LINK"
|
445
|
|
446
|
_rawsend(radio, magic)
|
447
|
|
448
|
# Ok, get the ident string
|
449
|
ident = _rawrecv(radio, 9)
|
450
|
|
451
|
# check if ident is OK
|
452
|
if ident != radio.IDENT:
|
453
|
# bad ident
|
454
|
msg = "Incorrect model ID, got this:"
|
455
|
msg += util.hexprint(ident)
|
456
|
LOG.debug(msg)
|
457
|
raise errors.RadioError("Radio identification failed.")
|
458
|
|
459
|
# DEBUG
|
460
|
LOG.info("Positive ident, got this:")
|
461
|
LOG.debug(util.hexprint(ident))
|
462
|
|
463
|
return True
|
464
|
|
465
|
|
466
|
def _exit_program_mode(radio):
|
467
|
endframe = "\x45"
|
468
|
_rawsend(radio, endframe)
|
469
|
|
470
|
|
471
|
def _download(radio):
|
472
|
"""Get the memory map"""
|
473
|
|
474
|
# put radio in program mode and identify it
|
475
|
_do_ident(radio)
|
476
|
|
477
|
# UI progress
|
478
|
status = chirp_common.Status()
|
479
|
status.cur = 0
|
480
|
status.max = MEM_SIZE / BLOCK_SIZE
|
481
|
status.msg = "Cloning from radio..."
|
482
|
radio.status_fn(status)
|
483
|
|
484
|
data = ""
|
485
|
for addr in range(0, MEM_SIZE, BLOCK_SIZE):
|
486
|
frame = _make_frame("R", addr, BLOCK_SIZE)
|
487
|
# DEBUG
|
488
|
LOG.info("Request sent:")
|
489
|
LOG.debug(util.hexprint(frame))
|
490
|
|
491
|
# sending the read request
|
492
|
_rawsend(radio, frame)
|
493
|
|
494
|
# now we read
|
495
|
d = _recv(radio, addr)
|
496
|
time.sleep(0.1)
|
497
|
|
498
|
# aggregate the data
|
499
|
data += d
|
500
|
|
501
|
# UI Update
|
502
|
status.cur = addr / BLOCK_SIZE
|
503
|
status.msg = "Cloning from radio..."
|
504
|
radio.status_fn(status)
|
505
|
|
506
|
_exit_program_mode(radio)
|
507
|
|
508
|
return data
|
509
|
|
510
|
|
511
|
def _upload(radio):
|
512
|
"""Upload procedure"""
|
513
|
|
514
|
MEM_SIZE = 0x7000
|
515
|
|
516
|
# put radio in program mode and identify it
|
517
|
_do_ident(radio)
|
518
|
|
519
|
# UI progress
|
520
|
status = chirp_common.Status()
|
521
|
status.cur = 0
|
522
|
status.max = MEM_SIZE / BLOCK_SIZE
|
523
|
status.msg = "Cloning to radio..."
|
524
|
radio.status_fn(status)
|
525
|
|
526
|
# the fun start here
|
527
|
for addr in range(0, MEM_SIZE, BLOCK_SIZE):
|
528
|
# sending the data
|
529
|
data = radio.get_mmap()[addr:addr + BLOCK_SIZE]
|
530
|
|
531
|
frame = _make_frame("W", addr, BLOCK_SIZE, data)
|
532
|
|
533
|
_rawsend(radio, frame)
|
534
|
|
535
|
# receiving the response
|
536
|
ack = _rawrecv(radio, 1)
|
537
|
if ack != "\x06":
|
538
|
_exit_program_mode(radio)
|
539
|
msg = "Bad ack writing block 0x%04x" % addr
|
540
|
raise errors.RadioError(msg)
|
541
|
|
542
|
_check_for_double_ack(radio)
|
543
|
|
544
|
# UI Update
|
545
|
status.cur = addr / BLOCK_SIZE
|
546
|
status.msg = "Cloning to radio..."
|
547
|
radio.status_fn(status)
|
548
|
|
549
|
_exit_program_mode(radio)
|
550
|
|
551
|
|
552
|
def model_match(cls, data):
|
553
|
"""Match the opened/downloaded image to the correct version"""
|
554
|
rid = data[0x6140:0x6148]
|
555
|
|
556
|
#if rid in cls._fileid:
|
557
|
if rid in cls.IDENT:
|
558
|
return True
|
559
|
|
560
|
return False
|
561
|
|
562
|
|
563
|
class VGCStyleRadio(chirp_common.CloneModeRadio,
|
564
|
chirp_common.ExperimentalRadio):
|
565
|
"""BTECH's UV-50X3"""
|
566
|
VENDOR = "BTECH"
|
567
|
_air_range = (108000000, 136000000)
|
568
|
_vhf_range = (136000000, 174000000)
|
569
|
_vhf2_range = (174000000, 250000000)
|
570
|
_220_range = (222000000, 225000000)
|
571
|
_gen1_range = (300000000, 400000000)
|
572
|
_uhf_range = (400000000, 480000000)
|
573
|
_gen2_range = (480000000, 520000000)
|
574
|
_upper = 499
|
575
|
MODEL = ""
|
576
|
IDENT = ""
|
577
|
|
578
|
@classmethod
|
579
|
def get_prompts(cls):
|
580
|
rp = chirp_common.RadioPrompts()
|
581
|
rp.experimental = \
|
582
|
('The UV-50X3 driver is a beta version.\n'
|
583
|
'\n'
|
584
|
'Please save an unedited copy of your first successful\n'
|
585
|
'download to a CHIRP Radio Images(*.img) file.'
|
586
|
)
|
587
|
rp.pre_download = _(dedent("""\
|
588
|
Follow this instructions to download your info:
|
589
|
|
590
|
1 - Turn off your radio
|
591
|
2 - Connect your interface cable
|
592
|
3 - Turn on your radio
|
593
|
4 - Do the download of your radio data
|
594
|
"""))
|
595
|
rp.pre_upload = _(dedent("""\
|
596
|
Follow this instructions to upload your info:
|
597
|
|
598
|
1 - Turn off your radio
|
599
|
2 - Connect your interface cable
|
600
|
3 - Turn on your radio
|
601
|
4 - Do the upload of your radio data
|
602
|
"""))
|
603
|
return rp
|
604
|
|
605
|
def get_features(self):
|
606
|
rf = chirp_common.RadioFeatures()
|
607
|
rf.has_settings = True
|
608
|
rf.has_bank = False
|
609
|
rf.has_tuning_step = False
|
610
|
rf.can_odd_split = True
|
611
|
rf.has_name = True
|
612
|
rf.has_offset = True
|
613
|
rf.has_mode = True
|
614
|
rf.has_dtcs = True
|
615
|
rf.has_rx_dtcs = True
|
616
|
rf.has_dtcs_polarity = True
|
617
|
rf.has_ctone = True
|
618
|
rf.has_cross = True
|
619
|
rf.has_sub_devices = self.VARIANT == ""
|
620
|
rf.valid_modes = MODES
|
621
|
rf.valid_characters = VALID_CHARS
|
622
|
rf.valid_duplexes = ["", "-", "+", "split", "off"]
|
623
|
rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
|
624
|
rf.valid_cross_modes = [
|
625
|
"Tone->Tone",
|
626
|
"DTCS->",
|
627
|
"->DTCS",
|
628
|
"Tone->DTCS",
|
629
|
"DTCS->Tone",
|
630
|
"->Tone",
|
631
|
"DTCS->DTCS"]
|
632
|
rf.valid_power_levels = POWER_LEVELS
|
633
|
rf.valid_skips = SKIP_VALUES
|
634
|
rf.valid_name_length = NAME_LENGTH
|
635
|
rf.valid_dtcs_codes = DTCS_CODES
|
636
|
rf.valid_bands = [self._air_range,
|
637
|
self._vhf_range,
|
638
|
self._vhf2_range,
|
639
|
self._220_range,
|
640
|
self._gen1_range,
|
641
|
self._uhf_range,
|
642
|
self._gen2_range]
|
643
|
rf.memory_bounds = (0, self._upper)
|
644
|
return rf
|
645
|
|
646
|
def get_sub_devices(self):
|
647
|
return [UV50X3Left(self._mmap), UV50X3Right(self._mmap)]
|
648
|
|
649
|
def sync_in(self):
|
650
|
"""Download from radio"""
|
651
|
try:
|
652
|
data = _download(self)
|
653
|
except errors.RadioError:
|
654
|
# Pass through any real errors we raise
|
655
|
raise
|
656
|
except:
|
657
|
# If anything unexpected happens, make sure we raise
|
658
|
# a RadioError and log the problem
|
659
|
LOG.exception('Unexpected error during download')
|
660
|
raise errors.RadioError('Unexpected error communicating '
|
661
|
'with the radio')
|
662
|
self._mmap = memmap.MemoryMap(data)
|
663
|
self.process_mmap()
|
664
|
|
665
|
def sync_out(self):
|
666
|
"""Upload to radio"""
|
667
|
try:
|
668
|
_upload(self)
|
669
|
except:
|
670
|
# If anything unexpected happens, make sure we raise
|
671
|
# a RadioError and log the problem
|
672
|
LOG.exception('Unexpected error during upload')
|
673
|
raise errors.RadioError('Unexpected error communicating '
|
674
|
'with the radio')
|
675
|
|
676
|
def process_mmap(self):
|
677
|
"""Process the mem map into the mem object"""
|
678
|
self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
|
679
|
|
680
|
def get_raw_memory(self, number):
|
681
|
return repr(self._memobj.memory[number])
|
682
|
|
683
|
def decode_tone(self, val):
|
684
|
"""Parse the tone data to decode from mem, it returns:
|
685
|
Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
|
686
|
if val.get_raw() == "\xFF\xFF":
|
687
|
return '', None, None
|
688
|
|
689
|
val = int(val)
|
690
|
if val >= 12000:
|
691
|
a = val - 12000
|
692
|
return 'DTCS', a, 'R'
|
693
|
elif val >= 8000:
|
694
|
a = val - 8000
|
695
|
return 'DTCS', a, 'N'
|
696
|
else:
|
697
|
a = val / 10.0
|
698
|
return 'Tone', a, None
|
699
|
|
700
|
def encode_tone(self, memval, mode, value, pol):
|
701
|
"""Parse the tone data to encode from UI to mem"""
|
702
|
if mode == '':
|
703
|
memval[0].set_raw(0xFF)
|
704
|
memval[1].set_raw(0xFF)
|
705
|
elif mode == 'Tone':
|
706
|
memval.set_value(int(value * 10))
|
707
|
elif mode == 'DTCS':
|
708
|
flag = 0x80 if pol == 'N' else 0xC0
|
709
|
memval.set_value(value)
|
710
|
memval[1].set_bits(flag)
|
711
|
else:
|
712
|
raise Exception("Internal error: invalid mode `%s'" % mode)
|
713
|
|
714
|
def _memory_obj(self, suffix=""):
|
715
|
return getattr(self._memobj, "%s_memory%s" % (self._vfo, suffix))
|
716
|
|
717
|
def _name_obj(self, suffix=""):
|
718
|
return getattr(self._memobj, "%s_names%s" % (self._vfo, suffix))
|
719
|
|
720
|
def _scan_obj(self, suffix=""):
|
721
|
return getattr(self._memobj, "%s_scanflags%s" % (self._vfo, suffix))
|
722
|
|
723
|
def _used_obj(self, suffix=""):
|
724
|
return getattr(self._memobj, "%s_usedflags%s" % (self._vfo, suffix))
|
725
|
|
726
|
def get_memory(self, number):
|
727
|
"""Get the mem representation from the radio image"""
|
728
|
bitpos = (1 << (number % 8))
|
729
|
bytepos = (number / 8)
|
730
|
|
731
|
_mem = self._memory_obj()[number]
|
732
|
_names = self._name_obj()[number]
|
733
|
_scn = self._scan_obj()[bytepos]
|
734
|
_usd = self._used_obj()[bytepos]
|
735
|
|
736
|
isused = bitpos & int(_usd)
|
737
|
isscan = bitpos & int(_scn)
|
738
|
|
739
|
# Create a high-level memory object to return to the UI
|
740
|
mem = chirp_common.Memory()
|
741
|
|
742
|
# Memory number
|
743
|
mem.number = number
|
744
|
|
745
|
if not isused:
|
746
|
mem.empty = True
|
747
|
return mem
|
748
|
|
749
|
# Freq and offset
|
750
|
mem.freq = int(_mem.rxfreq) * 10
|
751
|
# tx freq can be blank
|
752
|
if _mem.get_raw()[4] == "\xFF":
|
753
|
# TX freq not set
|
754
|
mem.offset = 0
|
755
|
mem.duplex = "off"
|
756
|
else:
|
757
|
# TX feq set
|
758
|
offset = (int(_mem.txfreq) * 10) - mem.freq
|
759
|
if offset < 0:
|
760
|
mem.offset = abs(offset)
|
761
|
mem.duplex = "-"
|
762
|
elif offset > 0:
|
763
|
mem.offset = offset
|
764
|
mem.duplex = "+"
|
765
|
else:
|
766
|
mem.offset = 0
|
767
|
|
768
|
# skip
|
769
|
if not isscan:
|
770
|
mem.skip = "S"
|
771
|
|
772
|
# name TAG of the channel
|
773
|
mem.name = str(_names.name).strip("\xFF")
|
774
|
|
775
|
# power
|
776
|
mem.power = POWER_LEVELS[int(_mem.txp)]
|
777
|
|
778
|
# wide/narrow
|
779
|
mem.mode = MODES[int(_mem.wn)]
|
780
|
|
781
|
# tone data
|
782
|
rxtone = txtone = None
|
783
|
txtone = self.decode_tone(_mem.txtone)
|
784
|
rxtone = self.decode_tone(_mem.rxtone)
|
785
|
chirp_common.split_tone_decode(mem, txtone, rxtone)
|
786
|
|
787
|
# Extra
|
788
|
mem.extra = RadioSettingGroup("extra", "Extra")
|
789
|
|
790
|
bcl = RadioSetting("bcl", "Busy channel lockout",
|
791
|
RadioSettingValueBoolean(bool(_mem.bcl)))
|
792
|
mem.extra.append(bcl)
|
793
|
|
794
|
revert = RadioSetting("revert", "Revert",
|
795
|
RadioSettingValueBoolean(bool(_mem.revert)))
|
796
|
mem.extra.append(revert)
|
797
|
|
798
|
dname = RadioSetting("dname", "Display name",
|
799
|
RadioSettingValueBoolean(bool(_mem.dname)))
|
800
|
mem.extra.append(dname)
|
801
|
|
802
|
return mem
|
803
|
|
804
|
def set_memory(self, mem):
|
805
|
"""Set the memory data in the eeprom img from the UI"""
|
806
|
bitpos = (1 << (mem.number % 8))
|
807
|
bytepos = (mem.number / 8)
|
808
|
|
809
|
_mem = self._memory_obj()[mem.number]
|
810
|
_names = self._name_obj()[mem.number]
|
811
|
_scn = self._scan_obj()[bytepos]
|
812
|
_usd = self._used_obj()[bytepos]
|
813
|
|
814
|
if mem.empty:
|
815
|
_usd &= ~bitpos
|
816
|
_scn &= ~bitpos
|
817
|
_mem.set_raw("\xFF" * 16)
|
818
|
_names.name = ("\xFF" * 6)
|
819
|
return
|
820
|
else:
|
821
|
_usd |= bitpos
|
822
|
|
823
|
# frequency
|
824
|
_mem.rxfreq = mem.freq / 10
|
825
|
|
826
|
# duplex
|
827
|
if mem.duplex == "+":
|
828
|
_mem.txfreq = (mem.freq + mem.offset) / 10
|
829
|
elif mem.duplex == "-":
|
830
|
_mem.txfreq = (mem.freq - mem.offset) / 10
|
831
|
elif mem.duplex == "off":
|
832
|
for i in _mem.txfreq:
|
833
|
i.set_raw("\xFF")
|
834
|
elif mem.duplex == "split":
|
835
|
_mem.txfreq = mem.offset / 10
|
836
|
else:
|
837
|
_mem.txfreq = mem.freq / 10
|
838
|
|
839
|
# tone data
|
840
|
((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
|
841
|
chirp_common.split_tone_encode(mem)
|
842
|
self.encode_tone(_mem.txtone, txmode, txtone, txpol)
|
843
|
self.encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
|
844
|
|
845
|
# name TAG of the channel
|
846
|
_names.name = mem.name.ljust(6, "\xFF")
|
847
|
|
848
|
# power level, # default power level is low
|
849
|
_mem.txp = 0 if mem.power is None else POWER_LEVELS.index(mem.power)
|
850
|
|
851
|
# wide/narrow
|
852
|
_mem.wn = MODES.index(mem.mode)
|
853
|
|
854
|
if mem.skip == "S":
|
855
|
_scn &= ~bitpos
|
856
|
else:
|
857
|
_scn |= bitpos
|
858
|
|
859
|
# autoset display to display name if filled
|
860
|
if mem.extra:
|
861
|
# mem.extra only seems to be populated when called from edit panel
|
862
|
dname = mem.extra["dname"]
|
863
|
else:
|
864
|
dname = None
|
865
|
if mem.name:
|
866
|
_mem.dname = True
|
867
|
if dname and not dname.changed():
|
868
|
dname.value = True
|
869
|
else:
|
870
|
_mem.dname = False
|
871
|
if dname and not dname.changed():
|
872
|
dname.value = False
|
873
|
|
874
|
# reseting unknowns, this has to be set by hand
|
875
|
_mem.unknown0 = 0
|
876
|
_mem.unknown1 = 0
|
877
|
_mem.unknown2 = 0
|
878
|
_mem.unknown3 = 0
|
879
|
|
880
|
# extra settings
|
881
|
if len(mem.extra) > 0:
|
882
|
# there are setting, parse
|
883
|
for setting in mem.extra:
|
884
|
setattr(_mem, setting.get_name(), setting.value)
|
885
|
else:
|
886
|
# there are no extra settings, load defaults
|
887
|
_mem.bcl = 0
|
888
|
_mem.revert = 0
|
889
|
_mem.dname = 1
|
890
|
|
891
|
def _bbcd2dtmf(self, bcdarr, strlen=16):
|
892
|
# doing bbcd, but with support for ABCD*#
|
893
|
LOG.debug(bcdarr.get_value())
|
894
|
string = ''.join("%02X" % b for b in bcdarr)
|
895
|
LOG.debug("@_bbcd2dtmf, received: %s" % string)
|
896
|
string = string.replace('E', '*').replace('F', '#')
|
897
|
if strlen <= 16:
|
898
|
string = string[:strlen]
|
899
|
return string
|
900
|
|
901
|
def _dtmf2bbcd(self, value):
|
902
|
dtmfstr = value.get_value()
|
903
|
dtmfstr = dtmfstr.replace('*', 'E').replace('#', 'F')
|
904
|
dtmfstr = str.ljust(dtmfstr.strip(), 16, "F")
|
905
|
bcdarr = list(bytearray.fromhex(dtmfstr))
|
906
|
LOG.debug("@_dtmf2bbcd, sending: %s" % bcdarr)
|
907
|
return bcdarr
|
908
|
|
909
|
def get_settings(self):
|
910
|
"""Translate the bit in the mem_struct into settings in the UI"""
|
911
|
_mem = self._memobj
|
912
|
basic = RadioSettingGroup("basic", "Basic Settings")
|
913
|
other = RadioSettingGroup("other", "Other Settings")
|
914
|
work = RadioSettingGroup("work", "Work Mode Settings")
|
915
|
dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
|
916
|
top = RadioSettings(basic, other, work, dtmf)
|
917
|
|
918
|
# Basic
|
919
|
|
920
|
# Audio: A01-A04
|
921
|
|
922
|
aftone = RadioSetting("settings.aftone", "AF tone control",
|
923
|
RadioSettingValueList(LIST_AFTONE, LIST_AFTONE[
|
924
|
_mem.settings.aftone]))
|
925
|
basic.append(aftone)
|
926
|
|
927
|
spkr = RadioSetting("settings.spkr", "Speaker",
|
928
|
RadioSettingValueList(LIST_SPKR,LIST_SPKR[
|
929
|
_mem.settings.spkr]))
|
930
|
basic.append(spkr)
|
931
|
|
932
|
audio = RadioSetting("settings.audio", "Stereo/Mono",
|
933
|
RadioSettingValueList(LIST_AUDIO, LIST_AUDIO[
|
934
|
_mem.settings.audio]))
|
935
|
basic.append(audio)
|
936
|
|
937
|
sbmute = RadioSetting("settings.sbmute", "Sub band mute",
|
938
|
RadioSettingValueList(LIST_SBMUTE, LIST_SBMUTE[
|
939
|
_mem.settings.sbmute]))
|
940
|
basic.append(sbmute)
|
941
|
|
942
|
# TX/RX: B01-B08
|
943
|
|
944
|
mgain = RadioSetting("settings.mgain", "Mic gain",
|
945
|
RadioSettingValueList(LIST_MLNHM, LIST_MLNHM[
|
946
|
_mem.settings.mgain]))
|
947
|
basic.append(mgain)
|
948
|
|
949
|
ptt = RadioSetting("settings.ptt", "PTT mode",
|
950
|
RadioSettingValueList(LIST_PTT,LIST_PTT[
|
951
|
_mem.settings.ptt]))
|
952
|
basic.append(ptt)
|
953
|
|
954
|
# B03 (per channel)
|
955
|
# B04 (per channel)
|
956
|
|
957
|
rxexp = RadioSetting("settings.rxexp", "RX expansion",
|
958
|
RadioSettingValueList(LIST_RXEXP,LIST_RXEXP[
|
959
|
_mem.settings.rxexp]))
|
960
|
basic.append(rxexp)
|
961
|
|
962
|
vox = RadioSetting("settings.vox", "Vox",
|
963
|
RadioSettingValueList(LIST_VOX, LIST_VOX[
|
964
|
_mem.settings.vox]))
|
965
|
basic.append(vox)
|
966
|
|
967
|
voxs = RadioSetting("settings.voxs", "Vox sensitivity",
|
968
|
RadioSettingValueList(LIST_MLNHM, LIST_MLNHM[
|
969
|
_mem.settings.voxs]))
|
970
|
basic.append(voxs)
|
971
|
|
972
|
# B08 (per channel)
|
973
|
|
974
|
# Display: C01-C06
|
975
|
|
976
|
display = RadioSetting("settings.display", "Display select",
|
977
|
RadioSettingValueList(LIST_DISPLAY,
|
978
|
LIST_DISPLAY[_mem.settings.display]))
|
979
|
basic.append(display)
|
980
|
|
981
|
lcdb = RadioSetting("settings.lcdb", "LCD brightness",
|
982
|
RadioSettingValueList(LIST_MINMAX, LIST_MINMAX[
|
983
|
_mem.settings.lcdb]))
|
984
|
basic.append(lcdb)
|
985
|
|
986
|
color = RadioSetting("settings.color", "LCD color",
|
987
|
RadioSettingValueList(LIST_COLOR, LIST_COLOR[
|
988
|
_mem.settings.color]))
|
989
|
basic.append(color)
|
990
|
|
991
|
lcdc = RadioSetting("settings.lcdc", "LCD contrast",
|
992
|
RadioSettingValueList(LIST_MINMAX, LIST_MINMAX[
|
993
|
_mem.settings.lcdc]))
|
994
|
basic.append(lcdc)
|
995
|
|
996
|
btime = RadioSetting("settings.btime", "LCD backlight time",
|
997
|
RadioSettingValueList(LIST_BTIME, LIST_BTIME[
|
998
|
_mem.settings.btime]))
|
999
|
basic.append(btime)
|
1000
|
|
1001
|
keyb = RadioSetting("settings.keyb", "Key brightness",
|
1002
|
RadioSettingValueList(LIST_MINMAX, LIST_MINMAX[
|
1003
|
_mem.settings.keyb]))
|
1004
|
basic.append(keyb)
|
1005
|
|
1006
|
# Memory: D01-D04
|
1007
|
|
1008
|
# D01 (per channel)
|
1009
|
# D02 (per channel)
|
1010
|
|
1011
|
mrscan = RadioSetting("settings.mrscan", "Memory scan type",
|
1012
|
RadioSettingValueList(LIST_MRSCAN, LIST_MRSCAN[
|
1013
|
_mem.settings.mrscan]))
|
1014
|
basic.append(mrscan)
|
1015
|
|
1016
|
# D04 (per channel)
|
1017
|
|
1018
|
# Scan: E01-E04
|
1019
|
|
1020
|
dwstop = RadioSetting("settings.dwstop", "Dual watch stop",
|
1021
|
RadioSettingValueList(LIST_DWSTOP, LIST_DWSTOP[
|
1022
|
_mem.settings.dwstop]))
|
1023
|
basic.append(dwstop)
|
1024
|
|
1025
|
scand = RadioSetting("settings.scand", "Scan direction",
|
1026
|
RadioSettingValueList(LIST_SCAND,LIST_SCAND[
|
1027
|
_mem.settings.scand]))
|
1028
|
basic.append(scand)
|
1029
|
|
1030
|
scanr = RadioSetting("settings.scanr", "Scan resume",
|
1031
|
RadioSettingValueList(LIST_SCANR,LIST_SCANR[
|
1032
|
_mem.settings.scanr]))
|
1033
|
basic.append(scanr)
|
1034
|
|
1035
|
scansb = RadioSetting("settings.scansb", "Scan stop beep",
|
1036
|
RadioSettingValueBoolean(_mem.settings.scansb))
|
1037
|
basic.append(scansb)
|
1038
|
|
1039
|
# System: F01-F09
|
1040
|
|
1041
|
apo = RadioSetting("settings.apo", "Automatic power off [hours]",
|
1042
|
RadioSettingValueList(LIST_APO, LIST_APO[
|
1043
|
_mem.settings.apo]))
|
1044
|
basic.append(apo)
|
1045
|
|
1046
|
ars = RadioSetting("settings.ars", "Automatic repeater shift",
|
1047
|
RadioSettingValueBoolean(_mem.settings.ars))
|
1048
|
basic.append(ars)
|
1049
|
|
1050
|
beep = RadioSetting("settings.beep", "Beep volume",
|
1051
|
RadioSettingValueList(LIST_BEEP,LIST_BEEP[
|
1052
|
_mem.settings.beep]))
|
1053
|
basic.append(beep)
|
1054
|
|
1055
|
fkey = RadioSetting("settings.fkey", "F key",
|
1056
|
RadioSettingValueList(LIST_FKEY,LIST_FKEY[
|
1057
|
_mem.settings.fkey]))
|
1058
|
basic.append(fkey)
|
1059
|
|
1060
|
pfkey1 = RadioSetting("settings.pfkey1", "Mic P1 key",
|
1061
|
RadioSettingValueList(LIST_PFKEY, LIST_PFKEY[
|
1062
|
_mem.settings.pfkey1]))
|
1063
|
basic.append(pfkey1)
|
1064
|
|
1065
|
pfkey2 = RadioSetting("settings.pfkey2", "Mic P2 key",
|
1066
|
RadioSettingValueList(LIST_PFKEY, LIST_PFKEY[
|
1067
|
_mem.settings.pfkey2]))
|
1068
|
basic.append(pfkey2)
|
1069
|
|
1070
|
pfkey3 = RadioSetting("settings.pfkey3", "Mic P3 key",
|
1071
|
RadioSettingValueList(LIST_PFKEY, LIST_PFKEY[
|
1072
|
_mem.settings.pfkey3]))
|
1073
|
basic.append(pfkey3)
|
1074
|
|
1075
|
pfkey4 = RadioSetting("settings.pfkey4", "Mic P4 key",
|
1076
|
RadioSettingValueList(LIST_PFKEY, LIST_PFKEY[
|
1077
|
_mem.settings.pfkey4]))
|
1078
|
basic.append(pfkey4)
|
1079
|
|
1080
|
omode = RadioSetting("settings.omode", "Operation mode",
|
1081
|
RadioSettingValueList(LIST_AB,LIST_AB[
|
1082
|
_mem.settings.omode]))
|
1083
|
basic.append(omode)
|
1084
|
|
1085
|
rxcoverm = RadioSetting("settings.rxcoverm", "RX coverage - memory",
|
1086
|
RadioSettingValueList(LIST_COVERAGE,
|
1087
|
LIST_COVERAGE[_mem.settings.rxcoverm]))
|
1088
|
basic.append(rxcoverm)
|
1089
|
|
1090
|
rxcoverv = RadioSetting("settings.rxcoverv", "RX coverage - VFO",
|
1091
|
RadioSettingValueList(LIST_COVERAGE,
|
1092
|
LIST_COVERAGE[_mem.settings.rxcoverv]))
|
1093
|
basic.append(rxcoverv)
|
1094
|
|
1095
|
tot = RadioSetting("settings.tot", "Time out timer [min]",
|
1096
|
RadioSettingValueList(LIST_TOT, LIST_TOT[
|
1097
|
_mem.settings.tot]))
|
1098
|
basic.append(tot)
|
1099
|
|
1100
|
# Timer/Clock: G01-G04
|
1101
|
|
1102
|
# G01
|
1103
|
datefmt = RadioSetting("settings.datefmt", "Date format",
|
1104
|
RadioSettingValueList(LIST_DATEFMT,
|
1105
|
LIST_DATEFMT[_mem.settings.datefmt]))
|
1106
|
basic.append(datefmt)
|
1107
|
|
1108
|
timefmt = RadioSetting("settings.timefmt", "Time format",
|
1109
|
RadioSettingValueList(LIST_TIMEFMT,
|
1110
|
LIST_TIMEFMT[_mem.settings.timefmt]))
|
1111
|
basic.append(timefmt)
|
1112
|
|
1113
|
timesig = RadioSetting("settings.timesig", "Time signal",
|
1114
|
RadioSettingValueBoolean(_mem.settings.timesig))
|
1115
|
basic.append(timesig)
|
1116
|
|
1117
|
tz = RadioSetting("settings.tz", "Time zone",
|
1118
|
RadioSettingValueList(LIST_TZ, LIST_TZ[
|
1119
|
_mem.settings.tz]))
|
1120
|
basic.append(tz)
|
1121
|
|
1122
|
# Signaling: H01-H06
|
1123
|
|
1124
|
bell = RadioSetting("settings.bell", "Bell ringer",
|
1125
|
RadioSettingValueList(LIST_BELL, LIST_BELL[
|
1126
|
_mem.settings.bell]))
|
1127
|
basic.append(bell)
|
1128
|
|
1129
|
# H02 (per channel)
|
1130
|
|
1131
|
dtmfmodenc = RadioSetting("settings.dtmfmodenc", "DTMF mode encode",
|
1132
|
RadioSettingValueBoolean(
|
1133
|
_mem.settings.dtmfmodenc))
|
1134
|
basic.append(dtmfmodenc)
|
1135
|
|
1136
|
dtmfmoddec = RadioSetting("settings.dtmfmoddec", "DTMF mode decode",
|
1137
|
RadioSettingValueBoolean(
|
1138
|
_mem.settings.dtmfmoddec))
|
1139
|
basic.append(dtmfmoddec)
|
1140
|
|
1141
|
# H04 (per channel)
|
1142
|
|
1143
|
decbandsel = RadioSetting("settings.decbandsel", "DTMF band select",
|
1144
|
RadioSettingValueList(LIST_AB,LIST_AB[
|
1145
|
_mem.settings.decbandsel]))
|
1146
|
basic.append(decbandsel)
|
1147
|
|
1148
|
sqlexp = RadioSetting("settings.sqlexp", "SQL expansion",
|
1149
|
RadioSettingValueBoolean(_mem.settings.sqlexp))
|
1150
|
basic.append(sqlexp)
|
1151
|
|
1152
|
# Pkt: I01-I03
|
1153
|
|
1154
|
databnd = RadioSetting("settings.databnd", "Packet data band",
|
1155
|
RadioSettingValueList(LIST_DATABND,LIST_DATABND[
|
1156
|
_mem.settings.databnd]))
|
1157
|
basic.append(databnd)
|
1158
|
|
1159
|
dataspd = RadioSetting("settings.dataspd", "Packet data speed",
|
1160
|
RadioSettingValueList(LIST_DATASPD,LIST_DATASPD[
|
1161
|
_mem.settings.dataspd]))
|
1162
|
basic.append(dataspd)
|
1163
|
|
1164
|
datasql = RadioSetting("settings.datasql", "Packet data squelch",
|
1165
|
RadioSettingValueList(LIST_DATASQL,LIST_DATASQL[
|
1166
|
_mem.settings.datasql]))
|
1167
|
basic.append(datasql)
|
1168
|
|
1169
|
# Other
|
1170
|
|
1171
|
dw = RadioSetting("settings.dw", "Dual watch",
|
1172
|
RadioSettingValueBoolean(_mem.settings.dw))
|
1173
|
other.append(dw)
|
1174
|
|
1175
|
cpuclk = RadioSetting("settings.cpuclk", "CPU clock frequency",
|
1176
|
RadioSettingValueList(LIST_CPUCLK,LIST_CPUCLK[
|
1177
|
_mem.settings.cpuclk]))
|
1178
|
other.append(cpuclk)
|
1179
|
|
1180
|
def _filter(name):
|
1181
|
filtered = ""
|
1182
|
for char in str(name):
|
1183
|
if char in VALID_CHARS:
|
1184
|
filtered += char
|
1185
|
else:
|
1186
|
filtered += " "
|
1187
|
return filtered
|
1188
|
|
1189
|
line16 = RadioSetting("poweron_msg.line16", "Power-on message",
|
1190
|
RadioSettingValueString(0, 16, _filter(
|
1191
|
_mem.poweron_msg.line16)))
|
1192
|
other.append(line16)
|
1193
|
|
1194
|
line32 = RadioSetting("embedded_msg.line32", "Embedded message",
|
1195
|
RadioSettingValueString(0, 32, _filter(
|
1196
|
_mem.embedded_msg.line32)))
|
1197
|
other.append(line32)
|
1198
|
|
1199
|
# Work
|
1200
|
|
1201
|
workmoda = RadioSetting("settings.workmoda", "Work mode A",
|
1202
|
RadioSettingValueList(LIST_WORK,LIST_WORK[
|
1203
|
_mem.settings.workmoda]))
|
1204
|
work.append(workmoda)
|
1205
|
|
1206
|
workmodb = RadioSetting("settings.workmodb", "Work mode B",
|
1207
|
RadioSettingValueList(LIST_WORK,LIST_WORK[
|
1208
|
_mem.settings.workmodb]))
|
1209
|
work.append(workmodb)
|
1210
|
|
1211
|
wbanda = RadioSetting("settings.wbanda", "Work band A",
|
1212
|
RadioSettingValueList(LIST_WBANDA, LIST_WBANDA[
|
1213
|
(_mem.settings.wbanda) - 1]))
|
1214
|
work.append(wbanda)
|
1215
|
|
1216
|
wbandb = RadioSetting("settings.wbandb", "Work band B",
|
1217
|
RadioSettingValueList(LIST_WBANDB, LIST_WBANDB[
|
1218
|
(_mem.settings.wbandb) - 4]))
|
1219
|
work.append(wbandb)
|
1220
|
|
1221
|
sqla = RadioSetting("settings.sqla", "Squelch A",
|
1222
|
RadioSettingValueList(LIST_SQL, LIST_SQL[
|
1223
|
_mem.settings.sqla]))
|
1224
|
work.append(sqla)
|
1225
|
|
1226
|
sqlb = RadioSetting("settings.sqlb", "Squelch B",
|
1227
|
RadioSettingValueList(LIST_SQL, LIST_SQL[
|
1228
|
_mem.settings.sqlb]))
|
1229
|
work.append(sqlb)
|
1230
|
|
1231
|
stepa = RadioSetting("settings.stepa", "Auto step A",
|
1232
|
RadioSettingValueList(LIST_STEP,LIST_STEP[
|
1233
|
_mem.settings.stepa]))
|
1234
|
work.append(stepa)
|
1235
|
|
1236
|
stepb = RadioSetting("settings.stepb", "Auto step B",
|
1237
|
RadioSettingValueList(LIST_STEP,LIST_STEP[
|
1238
|
_mem.settings.stepb]))
|
1239
|
work.append(stepb)
|
1240
|
|
1241
|
mrcha = RadioSetting("settings.mrcha", "Current channel A",
|
1242
|
RadioSettingValueInteger(0, 499,
|
1243
|
_mem.settings.mrcha))
|
1244
|
work.append(mrcha)
|
1245
|
|
1246
|
mrchb = RadioSetting("settings.mrchb", "Current channel B",
|
1247
|
RadioSettingValueInteger(0, 499,
|
1248
|
_mem.settings.mrchb))
|
1249
|
work.append(mrchb)
|
1250
|
|
1251
|
val = _mem.settings.offseta / 100.00
|
1252
|
offseta = RadioSetting("settings.offseta", "Offset A (0-37.95)",
|
1253
|
RadioSettingValueFloat(0, 38.00, val, 0.05, 2))
|
1254
|
work.append(offseta)
|
1255
|
|
1256
|
val = _mem.settings.offsetb / 100.00
|
1257
|
offsetb = RadioSetting("settings.offsetb", "Offset B (0-79.95)",
|
1258
|
RadioSettingValueFloat(0, 80.00, val, 0.05, 2))
|
1259
|
work.append(offsetb)
|
1260
|
|
1261
|
wpricha = RadioSetting("settings.wpricha", "Priority channel A",
|
1262
|
RadioSettingValueInteger(0, 499,
|
1263
|
_mem.settings.wpricha))
|
1264
|
work.append(wpricha)
|
1265
|
|
1266
|
wprichb = RadioSetting("settings.wprichb", "Priority channel B",
|
1267
|
RadioSettingValueInteger(0, 499,
|
1268
|
_mem.settings.wprichb))
|
1269
|
work.append(wprichb)
|
1270
|
|
1271
|
smode = RadioSetting("settings.smode", "Smart function mode",
|
1272
|
RadioSettingValueList(LIST_SMODE,LIST_SMODE[
|
1273
|
_mem.settings.smode]))
|
1274
|
work.append(smode)
|
1275
|
|
1276
|
# dtmf
|
1277
|
|
1278
|
ttdkey = RadioSetting("dtmf.ttdkey", "D key function",
|
1279
|
RadioSettingValueList(LIST_TTDKEY, LIST_TTDKEY[
|
1280
|
_mem.dtmf.ttdkey]))
|
1281
|
dtmf.append(ttdkey)
|
1282
|
|
1283
|
ttdgt = RadioSetting("dtmf.ttdgt", "Digit time",
|
1284
|
RadioSettingValueList(LIST_TT200, LIST_TT200[
|
1285
|
(_mem.dtmf.ttdgt) - 5]))
|
1286
|
dtmf.append(ttdgt)
|
1287
|
|
1288
|
ttint = RadioSetting("dtmf.ttint", "Interval time",
|
1289
|
RadioSettingValueList(LIST_TT200, LIST_TT200[
|
1290
|
(_mem.dtmf.ttint) - 5]))
|
1291
|
dtmf.append(ttint)
|
1292
|
|
1293
|
tt1stdgt = RadioSetting("dtmf.tt1stdgt", "1st digit time",
|
1294
|
RadioSettingValueList(LIST_TT200, LIST_TT200[
|
1295
|
(_mem.dtmf.tt1stdgt) - 5]))
|
1296
|
dtmf.append(tt1stdgt)
|
1297
|
|
1298
|
tt1stdly = RadioSetting("dtmf.tt1stdly", "1st digit delay time",
|
1299
|
RadioSettingValueList(LIST_TT1000, LIST_TT1000[
|
1300
|
(_mem.dtmf.tt1stdly) - 2]))
|
1301
|
dtmf.append(tt1stdly)
|
1302
|
|
1303
|
ttdlyqt = RadioSetting("dtmf.ttdlyqt", "Digit delay when use qt",
|
1304
|
RadioSettingValueList(LIST_TT1000, LIST_TT1000[
|
1305
|
(_mem.dtmf.ttdlyqt) - 2]))
|
1306
|
dtmf.append(ttdlyqt)
|
1307
|
|
1308
|
ttsig = RadioSetting("dtmf2.ttsig", "Signal",
|
1309
|
RadioSettingValueList(LIST_TTSIG, LIST_TTSIG[
|
1310
|
_mem.dtmf2.ttsig]))
|
1311
|
dtmf.append(ttsig)
|
1312
|
|
1313
|
ttautorst = RadioSetting("dtmf2.ttautorst", "Auto reset time",
|
1314
|
RadioSettingValueList(LIST_TTAUTORST,
|
1315
|
LIST_TTAUTORST[_mem.dtmf2.ttautorst]))
|
1316
|
dtmf.append(ttautorst)
|
1317
|
|
1318
|
if _mem.dtmf2.ttgrpcode > 0x06:
|
1319
|
val = 0x00
|
1320
|
else:
|
1321
|
val = _mem.dtmf2.ttgrpcode
|
1322
|
ttgrpcode = RadioSetting("dtmf2.ttgrpcode", "Group code",
|
1323
|
RadioSettingValueList(LIST_TTGRPCODE,
|
1324
|
LIST_TTGRPCODE[val]))
|
1325
|
dtmf.append(ttgrpcode)
|
1326
|
|
1327
|
ttintcode = RadioSetting("dtmf2.ttintcode", "Interval code",
|
1328
|
RadioSettingValueList(LIST_TTINTCODE,
|
1329
|
LIST_TTINTCODE[_mem.dtmf2.ttintcode]))
|
1330
|
dtmf.append(ttintcode)
|
1331
|
|
1332
|
if _mem.dtmf2.ttalert > 0x04:
|
1333
|
val = 0x00
|
1334
|
else:
|
1335
|
val = _mem.dtmf2.ttalert
|
1336
|
ttalert = RadioSetting("dtmf2.ttalert", "Alert tone/transpond",
|
1337
|
RadioSettingValueList(LIST_TTALERT,
|
1338
|
LIST_TTALERT[val]))
|
1339
|
dtmf.append(ttalert)
|
1340
|
|
1341
|
ttautod = RadioSetting("dtmf.ttautod", "Auto dial group",
|
1342
|
RadioSettingValueList(LIST_TTAUTOD,
|
1343
|
LIST_TTAUTOD[_mem.dtmf.ttautod]))
|
1344
|
dtmf.append(ttautod)
|
1345
|
|
1346
|
# setup 9 dtmf autodial entries
|
1347
|
for i in map(str, range(1, 10)):
|
1348
|
objname = "code" + i
|
1349
|
strname = "Code " + str(i)
|
1350
|
dtmfsetting = getattr(_mem.dtmfcode, objname)
|
1351
|
dtmflen = getattr(_mem.dtmfcode, objname + "_len")
|
1352
|
dtmfstr = self._bbcd2dtmf(dtmfsetting, dtmflen)
|
1353
|
code = RadioSettingValueString(0, 16, dtmfstr)
|
1354
|
code.set_charset(DTMF_CHARS + list(" "))
|
1355
|
rs = RadioSetting("dtmfcode." + objname, strname, code)
|
1356
|
dtmf.append(rs)
|
1357
|
return top
|
1358
|
|
1359
|
def set_settings(self, settings):
|
1360
|
_settings = self._memobj.settings
|
1361
|
_mem = self._memobj
|
1362
|
for element in settings:
|
1363
|
if not isinstance(element, RadioSetting):
|
1364
|
self.set_settings(element)
|
1365
|
continue
|
1366
|
else:
|
1367
|
try:
|
1368
|
name = element.get_name()
|
1369
|
if "." in name:
|
1370
|
bits = name.split(".")
|
1371
|
obj = self._memobj
|
1372
|
for bit in bits[:-1]:
|
1373
|
if "/" in bit:
|
1374
|
bit, index = bit.split("/", 1)
|
1375
|
index = int(index)
|
1376
|
obj = getattr(obj, bit)[index]
|
1377
|
else:
|
1378
|
obj = getattr(obj, bit)
|
1379
|
setting = bits[-1]
|
1380
|
else:
|
1381
|
obj = _settings
|
1382
|
setting = element.get_name()
|
1383
|
|
1384
|
if element.has_apply_callback():
|
1385
|
LOG.debug("Using apply callback")
|
1386
|
element.run_apply_callback()
|
1387
|
elif setting == "line16":
|
1388
|
setattr(obj, setting, str(element.value).rstrip(
|
1389
|
" ").ljust(16, "\xFF"))
|
1390
|
elif setting == "line32":
|
1391
|
setattr(obj, setting, str(element.value).rstrip(
|
1392
|
" ").ljust(32, "\xFF"))
|
1393
|
elif setting == "wbanda":
|
1394
|
setattr(obj, setting, int(element.value) + 1)
|
1395
|
elif setting == "wbandb":
|
1396
|
setattr(obj, setting, int(element.value) + 4)
|
1397
|
elif setting in ["offseta", "offsetb"]:
|
1398
|
val = element.value
|
1399
|
value = int(val.get_value() * 100)
|
1400
|
setattr(obj, setting, value)
|
1401
|
elif setting in ["ttdgt", "ttint", "tt1stdgt"]:
|
1402
|
setattr(obj, setting, int(element.value) + 5)
|
1403
|
elif setting in ["tt1stdly", "ttdlyqt"]:
|
1404
|
setattr(obj, setting, int(element.value) + 2)
|
1405
|
elif re.match('code\d', setting):
|
1406
|
# set dtmf length field and then get bcd dtmf
|
1407
|
dtmfstrlen = len(str(element.value).strip())
|
1408
|
setattr(_mem.dtmfcode, setting + "_len", dtmfstrlen)
|
1409
|
dtmfstr = self._dtmf2bbcd(element.value)
|
1410
|
setattr(_mem.dtmfcode, setting, dtmfstr)
|
1411
|
elif element.value.get_mutable():
|
1412
|
LOG.debug("Setting %s = %s" % (setting, element.value))
|
1413
|
setattr(obj, setting, element.value)
|
1414
|
except Exception, e:
|
1415
|
LOG.debug(element.get_name())
|
1416
|
raise
|
1417
|
|
1418
|
|
1419
|
@classmethod
|
1420
|
def match_model(cls, filedata, filename):
|
1421
|
match_size = False
|
1422
|
match_model = False
|
1423
|
|
1424
|
# testing the file data size
|
1425
|
if len(filedata) == MEM_SIZE:
|
1426
|
match_size = True
|
1427
|
|
1428
|
# testing the firmware model fingerprint
|
1429
|
match_model = model_match(cls, filedata)
|
1430
|
|
1431
|
if match_size and match_model:
|
1432
|
return True
|
1433
|
else:
|
1434
|
return False
|
1435
|
|
1436
|
|
1437
|
@directory.register
|
1438
|
class UV50X3(VGCStyleRadio):
|
1439
|
"""BTech UV-50X3"""
|
1440
|
MODEL = "UV-50X3"
|
1441
|
IDENT = UV50X3_id
|
1442
|
|
1443
|
|
1444
|
class UV50X3Left(UV50X3):
|
1445
|
VARIANT = "Left"
|
1446
|
_vfo = "left"
|
1447
|
|
1448
|
|
1449
|
class UV50X3Right(UV50X3):
|
1450
|
VARIANT = "Right"
|
1451
|
_vfo = "right"
|