1
|
# Copyright 2016 Jim Unroe <rock.unroe@gmail.com>
|
2
|
#
|
3
|
# This program is free software: you can redistribute it and/or modify
|
4
|
# it under the terms of the GNU General Public License as published by
|
5
|
# the Free Software Foundation, either version 2 of the License, or
|
6
|
# (at your option) any later version.
|
7
|
#
|
8
|
# This program is distributed in the hope that it will be useful,
|
9
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
# GNU General Public License for more details.
|
12
|
#
|
13
|
# You should have received a copy of the GNU General Public License
|
14
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
|
16
|
import time
|
17
|
import os
|
18
|
import struct
|
19
|
import re
|
20
|
import logging
|
21
|
|
22
|
from chirp import chirp_common, directory, memmap
|
23
|
from chirp import bitwise, errors, util
|
24
|
from chirp.settings import RadioSetting, RadioSettingGroup, \
|
25
|
RadioSettingValueInteger, RadioSettingValueList, \
|
26
|
RadioSettingValueBoolean, RadioSettingValueString, \
|
27
|
RadioSettings
|
28
|
|
29
|
LOG = logging.getLogger(__name__)
|
30
|
|
31
|
MEM_FORMAT = """
|
32
|
struct memory {
|
33
|
lbcd rxfreq[4];
|
34
|
lbcd txfreq[4];
|
35
|
lbcd rxtone[2];
|
36
|
lbcd txtone[2];
|
37
|
u8 unknown1;
|
38
|
u8 pttid:2, // PTT-ID
|
39
|
unknown2:1,
|
40
|
signaling:1, // Signaling(ANI)
|
41
|
unknown3:1,
|
42
|
bcl:1, // Busy Channel Lockout
|
43
|
unknown4:2;
|
44
|
u8 unknown5:3,
|
45
|
highpower:1, // Power Level
|
46
|
isnarrow:1, // Bandwidth
|
47
|
scan:1, // Scan Add
|
48
|
unknown6:2;
|
49
|
u8 unknown7;
|
50
|
};
|
51
|
|
52
|
#seekto 0x0010;
|
53
|
struct memory channels[128];
|
54
|
|
55
|
#seekto 0x0810;
|
56
|
struct memory vfo_a;
|
57
|
struct memory vfo_b;
|
58
|
|
59
|
#seekto 0x0830;
|
60
|
struct {
|
61
|
u8 unknown_0830_1:4, // 0x0830
|
62
|
color:2, // Background Color
|
63
|
dst:1, // DTMF Side Tone
|
64
|
txsel:1; // Priority TX Channel Select
|
65
|
u8 scans:2, // Scan Mode - 0x0831
|
66
|
unknown_0831:1,
|
67
|
autolk:1, // Auto Key Lock
|
68
|
save:1, // Battery Save
|
69
|
beep:1, // Key Beep
|
70
|
voice:2; // Voice Prompt
|
71
|
u8 vfomr_fm:1, // FM Radio Display Mode - 0x0832
|
72
|
led:2, // Background Light
|
73
|
unknown_0832_2:1,
|
74
|
dw:1, // FM Radio Dual Watch
|
75
|
name:1, // Display Names
|
76
|
vfomr_a:2; // Display Mode A
|
77
|
u8 opnset:2, // Power On Message - 0x0833
|
78
|
unknown_0833_1:3,
|
79
|
dwait:1, // Dual Standby
|
80
|
vfomr_b:2; // Display Mode B
|
81
|
u8 mrcha; // mr a ch num - 0x0834
|
82
|
u8 mrchb; // mr b ch num - 0x0835
|
83
|
u8 fmch; // fm radio ch num - 0x0836
|
84
|
u8 unknown_0837_1:1, // 0x0837
|
85
|
ste:1, // Squelch Tail Eliminate
|
86
|
roger:1, // Roger Beep
|
87
|
unknown_0837_2:1,
|
88
|
vox:4; // VOX
|
89
|
u8 step:4, // Step - 0x0838
|
90
|
unknown_0838_1:4;
|
91
|
u8 squelch; // Squelch - 0x0839
|
92
|
u8 tot; // Time Out Timer - 0x083A
|
93
|
u8 rptmod:1, // Repeater Mode - 0x083B
|
94
|
volmod:2, // Volume Mode
|
95
|
rptptt:1, // Repeater PTT Switch
|
96
|
rptspk:1, // Repeater Speaker
|
97
|
relay:3; // Cross Band Repeater Enable
|
98
|
u8 unknown_083C:4, // 0x083C
|
99
|
rptrl:4; // Repeater TX Delay
|
100
|
u8 pf1:4, // Function Key 1 - 0x083D
|
101
|
pf2:4; // Function Key 2
|
102
|
u8 vot; // VOX Delay Time - 0x083E
|
103
|
} settings;
|
104
|
|
105
|
#seekto 0x0848;
|
106
|
struct {
|
107
|
char line1[7];
|
108
|
} poweron_msg;
|
109
|
|
110
|
struct limit {
|
111
|
bbcd lower[2];
|
112
|
bbcd upper[2];
|
113
|
};
|
114
|
|
115
|
#seekto 0x0850;
|
116
|
struct {
|
117
|
struct limit vhf;
|
118
|
struct limit uhf;
|
119
|
} limits;
|
120
|
|
121
|
struct fmmemory {
|
122
|
lbcd fmfreq[4];
|
123
|
};
|
124
|
|
125
|
#seekto 0x085E;
|
126
|
struct fmmemory fmchannels[25];
|
127
|
struct fmmemory fmvfo;
|
128
|
|
129
|
#seekto 0x08D0;
|
130
|
struct {
|
131
|
char name[7]; // Channel Names
|
132
|
u8 unknown2[1];
|
133
|
} names[128];
|
134
|
|
135
|
#seekto 0x0D20;
|
136
|
u8 usedflags[16];
|
137
|
u8 scanflags[16];
|
138
|
|
139
|
//#seekto 0x0E50;
|
140
|
//struct memory rpt_vfo;
|
141
|
//struct memory rpt_1;
|
142
|
//struct memory rpt_2;
|
143
|
//struct memory rpt_3;
|
144
|
//struct memory rpt_4;
|
145
|
//struct memory rpt_5;
|
146
|
|
147
|
//#seekto 0x0EB0;
|
148
|
//struct {
|
149
|
// u8 unknown:2,
|
150
|
// rpt_5_enable:1,
|
151
|
// rpt_4_enable:1,
|
152
|
// rpt_3_enable:1,
|
153
|
// rpt_2_enable:1,
|
154
|
// rpt_1_enable:1,
|
155
|
// rpt_vfo_enable:1;
|
156
|
//} special_enables;
|
157
|
|
158
|
#seekto 0x0FA0;
|
159
|
struct {
|
160
|
u8 unknown_0FA0_1:4,
|
161
|
dispab:1, // select a/b
|
162
|
unknown_0FA0_2:3;
|
163
|
} settings2;
|
164
|
"""
|
165
|
|
166
|
CMD_ACK = "\x06"
|
167
|
BLOCK_SIZE = 0x10
|
168
|
|
169
|
RT23_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00),
|
170
|
chirp_common.PowerLevel("High", watts=2.50)]
|
171
|
|
172
|
|
173
|
RT23_DTCS = sorted(chirp_common.DTCS_CODES +
|
174
|
[17, 50, 55, 135, 217, 254, 305, 645, 765])
|
175
|
|
176
|
RT23_CHARSET = chirp_common.CHARSET_UPPER_NUMERIC + \
|
177
|
":;<=>?@ !\"#$%&'()*+,-./"
|
178
|
|
179
|
LIST_SIGNALING = ["No", "DTMF"]
|
180
|
LIST_STEP = ["2.50K", "5.00K", "6.25K", "10.00K", "12,50K", "20.00K", "25.00K",
|
181
|
"50.00K"]
|
182
|
####LIST_OFFON = ["OFF", "ON"]
|
183
|
LIST_VOX = ["OFF"] + ["%s" % x for x in range(1, 6)]
|
184
|
LIST_TOT = ["OFF"] + ["%s seconds" % x for x in range(30, 300, 30)]
|
185
|
LIST_COLOR = ["Blue", "Orange", "Purple"]
|
186
|
LIST_VOT = ["0.5S", "1.0S", "1.5S", "2.0S", "3.0S"]
|
187
|
LIST_LED = ["Off", "On", "Auto"]
|
188
|
LIST_VOICE = ["Off", "Chinese", "English"]
|
189
|
LIST_OPNSET = ["Full", "Voltage", "Message"]
|
190
|
LIST_SCANS = ["Time Operated", "Carrier Operated", "Search"]
|
191
|
|
192
|
LIST_RPTRL = ["0.5S", "1.0S", "1.5S", "2.0S", "2.5S", "3.0S", "3.5S", "4.0S",
|
193
|
"4.5S"]
|
194
|
LIST_RPTMOD = ["Single", "Double"]
|
195
|
LIST_VOLMOD = ["Off", "Sub", "Main"]
|
196
|
LIST_TXSEL = ["Edit", "Busy"]
|
197
|
|
198
|
LIST_VFOMR = ["VFO", "MR(Frequency)", "MR(Channel #/Name)"]
|
199
|
LIST_VFOMRFM = ["VFO", "Channel"]
|
200
|
|
201
|
LIST_PTTID = ["Off", "BOT", "EOT", "Both"]
|
202
|
|
203
|
LIST_PFKEY = [
|
204
|
"Radio",
|
205
|
"Sub-channel Sent",
|
206
|
"Scan",
|
207
|
"Alarm",
|
208
|
"DTMF",
|
209
|
"Squelch Off Momentarily",
|
210
|
"Battery Power Indicator",
|
211
|
"Tone 1750",
|
212
|
"Tone 2100",
|
213
|
"Tone 1000",
|
214
|
"Tone 1450"]
|
215
|
|
216
|
####RT23_SPECIAL = ["rpt_vfo", "rpt_1", "rpt_2", "rpt_3", "rpt_4", "rpt_5",
|
217
|
#### "vfo_a", "vfo_b"]
|
218
|
|
219
|
|
220
|
def _rt23_enter_programming_mode(radio):
|
221
|
serial = radio.pipe
|
222
|
|
223
|
#magic = "PROIUAM"
|
224
|
try:
|
225
|
serial.write("PROIUAM")
|
226
|
#for j in range(0, len(magic)):
|
227
|
# time.sleep(0.002)
|
228
|
# serial.write(magic[j])
|
229
|
ack = serial.read(1)
|
230
|
except:
|
231
|
raise errors.RadioError("Error communicating with radio")
|
232
|
|
233
|
if not ack:
|
234
|
raise errors.RadioError("No response from radio")
|
235
|
elif ack != CMD_ACK:
|
236
|
raise errors.RadioError("Radio refused to enter programming mode")
|
237
|
|
238
|
try:
|
239
|
serial.write("\x02")
|
240
|
ident = serial.read(8)
|
241
|
except:
|
242
|
raise errors.RadioError("Error communicating with radio")
|
243
|
|
244
|
if not ident.startswith("P31183"):
|
245
|
LOG.debug(util.hexprint(ident))
|
246
|
raise errors.RadioError("Radio returned unknown identification string")
|
247
|
|
248
|
try:
|
249
|
serial.write(CMD_ACK)
|
250
|
ack = serial.read(1)
|
251
|
except:
|
252
|
raise errors.RadioError("Error communicating with radio")
|
253
|
|
254
|
if ack != CMD_ACK:
|
255
|
raise errors.RadioError("Radio refused to enter programming mode")
|
256
|
|
257
|
|
258
|
def _rt23_exit_programming_mode(radio):
|
259
|
serial = radio.pipe
|
260
|
try:
|
261
|
serial.write("E")
|
262
|
except:
|
263
|
raise errors.RadioError("Radio refused to exit programming mode")
|
264
|
|
265
|
|
266
|
def _rt23_read_block(radio, block_addr, block_size):
|
267
|
serial = radio.pipe
|
268
|
|
269
|
cmd = struct.pack(">cHb", 'R', block_addr, BLOCK_SIZE)
|
270
|
expectedresponse = "W" + cmd[1:]
|
271
|
LOG.debug("Reading block %04x..." % (block_addr))
|
272
|
|
273
|
try:
|
274
|
serial.write(cmd)
|
275
|
response = serial.read(4 + BLOCK_SIZE + 1)
|
276
|
if response[:4] != expectedresponse:
|
277
|
raise Exception("Error reading block %04x." % (block_addr))
|
278
|
|
279
|
chunk = response[4:]
|
280
|
|
281
|
cs = 0
|
282
|
for byte in chunk[:-1]:
|
283
|
cs += ord(byte)
|
284
|
if ord(chunk[-1]) != (cs & 0xFF):
|
285
|
raise Exception("Block failed checksum!")
|
286
|
|
287
|
block_data = chunk[:-1]
|
288
|
except:
|
289
|
raise errors.RadioError("Failed to read block at %04x" % block_addr)
|
290
|
|
291
|
return block_data
|
292
|
|
293
|
|
294
|
def _rt23_write_block(radio, block_addr, block_size):
|
295
|
serial = radio.pipe
|
296
|
|
297
|
cmd = struct.pack(">cHb", 'W', block_addr, BLOCK_SIZE)
|
298
|
data = radio.get_mmap()[block_addr:block_addr + BLOCK_SIZE]
|
299
|
cs = 0
|
300
|
for byte in data:
|
301
|
cs += ord(byte)
|
302
|
data += chr(cs & 0xFF)
|
303
|
|
304
|
LOG.debug("Writing Data:")
|
305
|
LOG.debug(util.hexprint(cmd + data))
|
306
|
|
307
|
try:
|
308
|
#serial.write(cmd + data)
|
309
|
for j in range(0, len(cmd)):
|
310
|
time.sleep(0.002)
|
311
|
serial.write(cmd[j])
|
312
|
for j in range(0, len(data)):
|
313
|
time.sleep(0.002)
|
314
|
serial.write(data[j])
|
315
|
if serial.read(1) != CMD_ACK:
|
316
|
raise Exception("No ACK")
|
317
|
except:
|
318
|
raise errors.RadioError("Failed to send block "
|
319
|
"to radio at %04x" % block_addr)
|
320
|
|
321
|
|
322
|
def do_download(radio):
|
323
|
LOG.debug("download")
|
324
|
_rt23_enter_programming_mode(radio)
|
325
|
|
326
|
data = ""
|
327
|
|
328
|
status = chirp_common.Status()
|
329
|
status.msg = "Cloning from radio"
|
330
|
|
331
|
status.cur = 0
|
332
|
status.max = radio._memsize
|
333
|
|
334
|
for addr in range(0, radio._memsize, BLOCK_SIZE):
|
335
|
status.cur = addr + BLOCK_SIZE
|
336
|
radio.status_fn(status)
|
337
|
|
338
|
####print "Address: %s" % addr
|
339
|
block = _rt23_read_block(radio, addr, BLOCK_SIZE)
|
340
|
if addr == 0 and block.startswith("\xFF" * 6):
|
341
|
block = "P31183" + "\xFF" * 10
|
342
|
data += block
|
343
|
|
344
|
LOG.debug("Address: %04x" % addr)
|
345
|
LOG.debug(util.hexprint(block))
|
346
|
|
347
|
####data += radio.MODEL.ljust(8)
|
348
|
|
349
|
_rt23_exit_programming_mode(radio)
|
350
|
|
351
|
return memmap.MemoryMap(data)
|
352
|
|
353
|
|
354
|
def do_upload(radio):
|
355
|
status = chirp_common.Status()
|
356
|
status.msg = "Uploading to radio"
|
357
|
|
358
|
_rt23_enter_programming_mode(radio)
|
359
|
|
360
|
status.cur = 0
|
361
|
status.max = radio._memsize
|
362
|
|
363
|
for start_addr, end_addr in radio._ranges:
|
364
|
for addr in range(start_addr, end_addr, BLOCK_SIZE):
|
365
|
status.cur = addr + BLOCK_SIZE
|
366
|
radio.status_fn(status)
|
367
|
_rt23_write_block(radio, addr, BLOCK_SIZE)
|
368
|
|
369
|
####_rt23_exit_programming_mode(radio)
|
370
|
|
371
|
|
372
|
def model_match(cls, data):
|
373
|
"""Match the opened/downloaded image to the correct version"""
|
374
|
|
375
|
if len(data) == 0x1000:
|
376
|
rid = data[0x0000:0x0006]
|
377
|
return rid == "P31183"
|
378
|
else:
|
379
|
return False
|
380
|
|
381
|
|
382
|
def _split(rf, f1, f2):
|
383
|
"""Returns False if the two freqs are in the same band (no split)
|
384
|
or True otherwise"""
|
385
|
|
386
|
# determine if the two freqs are in the same band
|
387
|
for low, high in rf.valid_bands:
|
388
|
if f1 >= low and f1 <= high and \
|
389
|
f2 >= low and f2 <= high:
|
390
|
# if the two freqs are on the same Band this is not a split
|
391
|
return False
|
392
|
|
393
|
# if you get here is because the freq pairs are split
|
394
|
return True
|
395
|
|
396
|
|
397
|
@directory.register
|
398
|
class RT23Radio(chirp_common.CloneModeRadio):
|
399
|
"""RETEVIS RT23"""
|
400
|
VENDOR = "Retevis"
|
401
|
MODEL = "RT23"
|
402
|
BAUD_RATE = 9600
|
403
|
|
404
|
_ranges = [
|
405
|
(0x0000, 0x0EC0),
|
406
|
]
|
407
|
_memsize = 0x1000
|
408
|
|
409
|
def get_features(self):
|
410
|
rf = chirp_common.RadioFeatures()
|
411
|
rf.has_settings = True
|
412
|
rf.has_bank = False
|
413
|
rf.has_ctone = True
|
414
|
rf.has_cross = True
|
415
|
rf.has_rx_dtcs = True
|
416
|
rf.has_tuning_step = False
|
417
|
rf.can_odd_split = True
|
418
|
rf.valid_name_length = 7
|
419
|
rf.valid_characters = RT23_CHARSET
|
420
|
rf.has_name = True
|
421
|
rf.valid_skips = ["", "S"]
|
422
|
rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
|
423
|
rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
|
424
|
"->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
|
425
|
rf.valid_power_levels = RT23_POWER_LEVELS
|
426
|
rf.valid_duplexes = ["", "-", "+", "split", "off"]
|
427
|
rf.valid_modes = ["FM", "NFM"] # 25 KHz, 12.5 KHz.
|
428
|
rf.memory_bounds = (1, 128)
|
429
|
rf.valid_bands = [
|
430
|
(136000000, 174000000),
|
431
|
(400000000, 480000000)]
|
432
|
####rf.valid_special_chans = RT23_SPECIAL
|
433
|
|
434
|
return rf
|
435
|
|
436
|
def process_mmap(self):
|
437
|
self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
|
438
|
|
439
|
def sync_in(self):
|
440
|
self._mmap = do_download(self)
|
441
|
self.process_mmap()
|
442
|
|
443
|
def sync_out(self):
|
444
|
do_upload(self)
|
445
|
|
446
|
def get_raw_memory(self, number):
|
447
|
return repr(self._memobj.memory[number - 1])
|
448
|
|
449
|
def decode_tone(self, val):
|
450
|
"""Parse the tone data to decode from mem, it returns:
|
451
|
Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
|
452
|
if val.get_raw() == "\xFF\xFF":
|
453
|
return '', None, None
|
454
|
|
455
|
val = int(val)
|
456
|
if val >= 12000:
|
457
|
a = val - 12000
|
458
|
return 'DTCS', a, 'R'
|
459
|
elif val >= 8000:
|
460
|
a = val - 8000
|
461
|
return 'DTCS', a, 'N'
|
462
|
else:
|
463
|
a = val / 10.0
|
464
|
return 'Tone', a, None
|
465
|
|
466
|
def encode_tone(self, memval, mode, value, pol):
|
467
|
"""Parse the tone data to encode from UI to mem"""
|
468
|
if mode == '':
|
469
|
memval[0].set_raw(0xFF)
|
470
|
memval[1].set_raw(0xFF)
|
471
|
elif mode == 'Tone':
|
472
|
memval.set_value(int(value * 10))
|
473
|
elif mode == 'DTCS':
|
474
|
flag = 0x80 if pol == 'N' else 0xC0
|
475
|
memval.set_value(value)
|
476
|
memval[1].set_bits(flag)
|
477
|
else:
|
478
|
raise Exception("Internal error: invalid mode `%s'" % mode)
|
479
|
|
480
|
def get_memory(self, number):
|
481
|
mem = chirp_common.Memory()
|
482
|
_mem = self._memobj.channels[number-1]
|
483
|
_nam = self._memobj.names[number - 1]
|
484
|
mem.number = number
|
485
|
bitpos = (1 << ((number - 1) % 8))
|
486
|
bytepos = ((number - 1) / 8)
|
487
|
_scn = self._memobj.scanflags[bytepos]
|
488
|
_usd = self._memobj.usedflags[bytepos]
|
489
|
isused = bitpos & int(_usd)
|
490
|
isscan = bitpos & int(_scn)
|
491
|
|
492
|
if not isused:
|
493
|
mem.empty = True
|
494
|
return mem
|
495
|
|
496
|
mem.freq = int(_mem.rxfreq) * 10
|
497
|
|
498
|
# We'll consider any blank (i.e. 0MHz frequency) to be empty
|
499
|
if mem.freq == 0:
|
500
|
mem.empty = True
|
501
|
return mem
|
502
|
|
503
|
if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF":
|
504
|
mem.empty = True
|
505
|
return mem
|
506
|
|
507
|
if _mem.get_raw() == ("\xFF" * 16):
|
508
|
LOG.debug("Initializing empty memory")
|
509
|
_mem.set_raw("\x00" * 16)
|
510
|
|
511
|
# Freq and offset
|
512
|
mem.freq = int(_mem.rxfreq) * 10
|
513
|
# tx freq can be blank
|
514
|
if _mem.get_raw()[4] == "\xFF":
|
515
|
# TX freq not set
|
516
|
mem.offset = 0
|
517
|
mem.duplex = "off"
|
518
|
else:
|
519
|
# TX freq set
|
520
|
offset = (int(_mem.txfreq) * 10) - mem.freq
|
521
|
if offset != 0:
|
522
|
if _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10):
|
523
|
mem.duplex = "split"
|
524
|
mem.offset = int(_mem.txfreq) * 10
|
525
|
elif offset < 0:
|
526
|
mem.offset = abs(offset)
|
527
|
mem.duplex = "-"
|
528
|
elif offset > 0:
|
529
|
mem.offset = offset
|
530
|
mem.duplex = "+"
|
531
|
else:
|
532
|
mem.offset = 0
|
533
|
|
534
|
for char in _nam.name:
|
535
|
if str(char) == "\xFF":
|
536
|
char = " "
|
537
|
mem.name += str(char)
|
538
|
mem.name = mem.name.rstrip()
|
539
|
|
540
|
mem.mode = _mem.isnarrow and "NFM" or "FM"
|
541
|
|
542
|
rxtone = txtone = None
|
543
|
txtone = self.decode_tone(_mem.txtone)
|
544
|
rxtone = self.decode_tone(_mem.rxtone)
|
545
|
chirp_common.split_tone_decode(mem, txtone, rxtone)
|
546
|
|
547
|
mem.power = RT23_POWER_LEVELS[_mem.highpower]
|
548
|
|
549
|
if not isscan:
|
550
|
mem.skip = "S"
|
551
|
|
552
|
mem.extra = RadioSettingGroup("Extra", "extra")
|
553
|
|
554
|
rs = RadioSetting("bcl", "BCL",
|
555
|
RadioSettingValueBoolean(_mem.bcl))
|
556
|
mem.extra.append(rs)
|
557
|
|
558
|
rs = RadioSetting("pttid", "PTT ID",
|
559
|
RadioSettingValueList(
|
560
|
LIST_PTTID, LIST_PTTID[_mem.pttid]))
|
561
|
mem.extra.append(rs)
|
562
|
|
563
|
rs = RadioSetting("signaling", "Optional Signaling",
|
564
|
RadioSettingValueList(LIST_SIGNALING,
|
565
|
LIST_SIGNALING[_mem.signaling]))
|
566
|
mem.extra.append(rs)
|
567
|
|
568
|
return mem
|
569
|
|
570
|
def set_memory(self, mem):
|
571
|
LOG.debug("Setting %i(%s)" % (mem.number, mem.extd_number))
|
572
|
_mem = self._memobj.channels[mem.number - 1]
|
573
|
_nam = self._memobj.names[mem.number - 1]
|
574
|
bitpos = (1 << ((mem.number - 1) % 8))
|
575
|
bytepos = ((mem.number - 1) / 8)
|
576
|
_scn = self._memobj.scanflags[bytepos]
|
577
|
_usd = self._memobj.usedflags[bytepos]
|
578
|
|
579
|
if mem.empty:
|
580
|
_mem.set_raw("\xFF" * 16)
|
581
|
_nam.name = ("\xFF" * 7)
|
582
|
_usd &= ~bitpos
|
583
|
_scn &= ~bitpos
|
584
|
return
|
585
|
else:
|
586
|
_usd |= bitpos
|
587
|
|
588
|
if _mem.get_raw() == ("\xFF" * 16):
|
589
|
LOG.debug("Initializing empty memory")
|
590
|
_mem.set_raw("\x00" * 16)
|
591
|
_scn |= bitpos
|
592
|
|
593
|
##_mem.set_raw("\x00" * 16)
|
594
|
|
595
|
_mem.rxfreq = mem.freq / 10
|
596
|
|
597
|
if mem.duplex == "off":
|
598
|
for i in range(0, 4):
|
599
|
_mem.txfreq[i].set_raw("\xFF")
|
600
|
elif mem.duplex == "split":
|
601
|
_mem.txfreq = mem.offset / 10
|
602
|
elif mem.duplex == "+":
|
603
|
_mem.txfreq = (mem.freq + mem.offset) / 10
|
604
|
elif mem.duplex == "-":
|
605
|
_mem.txfreq = (mem.freq - mem.offset) / 10
|
606
|
else:
|
607
|
_mem.txfreq = mem.freq / 10
|
608
|
|
609
|
_namelength = self.get_features().valid_name_length
|
610
|
for i in range(_namelength):
|
611
|
try:
|
612
|
_nam.name[i] = mem.name[i]
|
613
|
except IndexError:
|
614
|
_nam.name[i] = "\xFF"
|
615
|
|
616
|
_mem.scan = mem.skip != "S"
|
617
|
if mem.skip == "S":
|
618
|
_scn &= ~bitpos
|
619
|
else:
|
620
|
_scn |= bitpos
|
621
|
_mem.isnarrow = mem.mode == "NFM"
|
622
|
|
623
|
((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
|
624
|
chirp_common.split_tone_encode(mem)
|
625
|
self.encode_tone(_mem.txtone, txmode, txtone, txpol)
|
626
|
self.encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
|
627
|
|
628
|
_mem.highpower = mem.power == RT23_POWER_LEVELS[1]
|
629
|
|
630
|
for setting in mem.extra:
|
631
|
setattr(_mem, setting.get_name(), setting.value)
|
632
|
|
633
|
def get_settings(self):
|
634
|
####_keys = self._memobj.keys
|
635
|
_settings = self._memobj.settings
|
636
|
_mem = self._memobj
|
637
|
basic = RadioSettingGroup("basic", "Basic Settings")
|
638
|
advanced = RadioSettingGroup("advanced", "Advanced Settings")
|
639
|
other = RadioSettingGroup("other", "Other Settings")
|
640
|
workmode = RadioSettingGroup("workmode", "Workmode Settings")
|
641
|
fmradio = RadioSettingGroup("fmradio", "FM Radio Settings")
|
642
|
top = RadioSettings(basic, advanced, other, workmode, fmradio)
|
643
|
|
644
|
#step = RadioSetting("step", "Step", RadioSettingValueList(
|
645
|
# LIST_STEP, LIST_STEP[_settings.step]))
|
646
|
#basic.append(step)
|
647
|
|
648
|
save = RadioSetting("save", "Battery Saver",
|
649
|
RadioSettingValueBoolean(_settings.save))
|
650
|
basic.append(save)
|
651
|
|
652
|
vox = RadioSetting("vox", "VOX Gain",
|
653
|
RadioSettingValueList(
|
654
|
LIST_VOX, LIST_VOX[_settings.vox]))
|
655
|
basic.append(vox)
|
656
|
|
657
|
squelch = RadioSetting("squelch", "Squelch Level",
|
658
|
RadioSettingValueInteger(
|
659
|
0, 9, _settings.squelch))
|
660
|
basic.append(squelch)
|
661
|
|
662
|
relay = RadioSetting("relay", "Repeater",
|
663
|
RadioSettingValueBoolean(_settings.relay))
|
664
|
basic.append(relay)
|
665
|
|
666
|
tot = RadioSetting("tot", "Time-out timer", RadioSettingValueList(
|
667
|
LIST_TOT, LIST_TOT[_settings.tot]))
|
668
|
basic.append(tot)
|
669
|
|
670
|
beep = RadioSetting("beep", "Key Beep",
|
671
|
RadioSettingValueBoolean(_settings.beep))
|
672
|
basic.append(beep)
|
673
|
|
674
|
color = RadioSetting("color", "Background Color", RadioSettingValueList(
|
675
|
LIST_COLOR, LIST_COLOR[_settings.color - 1]))
|
676
|
basic.append(color)
|
677
|
|
678
|
vot = RadioSetting("vot", "VOX Delay Time", RadioSettingValueList(
|
679
|
LIST_VOT, LIST_VOT[_settings.vot]))
|
680
|
basic.append(vot)
|
681
|
|
682
|
dwait = RadioSetting("dwait", "Dual Standby",
|
683
|
RadioSettingValueBoolean(_settings.dwait))
|
684
|
basic.append(dwait)
|
685
|
|
686
|
led = RadioSetting("led", "Background Light", RadioSettingValueList(
|
687
|
LIST_LED, LIST_LED[_settings.led]))
|
688
|
basic.append(led)
|
689
|
|
690
|
voice = RadioSetting("voice", "Voice Prompt", RadioSettingValueList(
|
691
|
LIST_VOICE, LIST_VOICE[_settings.voice]))
|
692
|
basic.append(voice)
|
693
|
|
694
|
roger = RadioSetting("roger", "Roger Beep",
|
695
|
RadioSettingValueBoolean(_settings.roger))
|
696
|
basic.append(roger)
|
697
|
|
698
|
autolk = RadioSetting("autolk", "Auto Key Lock",
|
699
|
RadioSettingValueBoolean(_settings.autolk))
|
700
|
basic.append(autolk)
|
701
|
|
702
|
opnset = RadioSetting("opnset", "Power On Message",
|
703
|
RadioSettingValueList(
|
704
|
LIST_OPNSET, LIST_OPNSET[_settings.opnset]))
|
705
|
basic.append(opnset)
|
706
|
|
707
|
def _filter(name):
|
708
|
filtered = ""
|
709
|
for char in str(name):
|
710
|
if char in chirp_common.CHARSET_ASCII:
|
711
|
filtered += char
|
712
|
else:
|
713
|
filtered += " "
|
714
|
return filtered
|
715
|
|
716
|
_msg = self._memobj.poweron_msg
|
717
|
ponmsg = RadioSetting("poweron_msg.line1", "Power-On Message 1",
|
718
|
RadioSettingValueString(
|
719
|
0, 7, _filter(_msg.line1)))
|
720
|
basic.append(ponmsg)
|
721
|
|
722
|
|
723
|
scans = RadioSetting("scans", "Scan Mode", RadioSettingValueList(
|
724
|
LIST_SCANS, LIST_SCANS[_settings.scans]))
|
725
|
basic.append(scans)
|
726
|
|
727
|
dw = RadioSetting("dw", "FM Radio Dual Watch",
|
728
|
RadioSettingValueBoolean(_settings.dw))
|
729
|
basic.append(dw)
|
730
|
|
731
|
name = RadioSetting("name", "Display Names",
|
732
|
RadioSettingValueBoolean(_settings.name))
|
733
|
basic.append(name)
|
734
|
|
735
|
rptrl = RadioSetting("rptrl", "Repeater TX Delay", RadioSettingValueList(
|
736
|
LIST_RPTRL, LIST_RPTRL[_settings.rptrl]))
|
737
|
basic.append(rptrl)
|
738
|
|
739
|
rptspk = RadioSetting("rptspk", "Repeater Speaker",
|
740
|
RadioSettingValueBoolean(_settings.rptspk))
|
741
|
basic.append(rptspk)
|
742
|
|
743
|
rptptt = RadioSetting("rptptt", "Repeater PTT Switch",
|
744
|
RadioSettingValueBoolean(_settings.rptptt))
|
745
|
basic.append(rptptt)
|
746
|
|
747
|
rptmod = RadioSetting("rptmod", "Repeater Mode",
|
748
|
RadioSettingValueList(
|
749
|
LIST_RPTMOD, LIST_RPTMOD[_settings.rptmod]))
|
750
|
basic.append(rptmod)
|
751
|
|
752
|
volmod = RadioSetting("volmod", "Volume Mode",
|
753
|
RadioSettingValueList(
|
754
|
LIST_VOLMOD, LIST_VOLMOD[_settings.volmod]))
|
755
|
basic.append(volmod)
|
756
|
|
757
|
dst = RadioSetting("dst", "DTMF Side Tone",
|
758
|
RadioSettingValueBoolean(_settings.dst))
|
759
|
basic.append(dst)
|
760
|
|
761
|
txsel = RadioSetting("txsel", "Priority TX Channel",
|
762
|
RadioSettingValueList(
|
763
|
LIST_TXSEL, LIST_TXSEL[_settings.txsel]))
|
764
|
basic.append(txsel)
|
765
|
|
766
|
ste = RadioSetting("ste", "Squelch Tail Eliminate",
|
767
|
RadioSettingValueBoolean(_settings.ste))
|
768
|
basic.append(ste)
|
769
|
|
770
|
#advanced
|
771
|
if _settings.pf1 > 0x0A:
|
772
|
val = 0x00
|
773
|
else:
|
774
|
val = _settings.pf1
|
775
|
pf1 = RadioSetting("pf1", "PF1 Key",
|
776
|
RadioSettingValueList(
|
777
|
LIST_PFKEY, LIST_PFKEY[val]))
|
778
|
advanced.append(pf1)
|
779
|
|
780
|
if _settings.pf2 > 0x0A:
|
781
|
val = 0x00
|
782
|
else:
|
783
|
val = _settings.pf2
|
784
|
pf2 = RadioSetting("pf2", "PF2 Key",
|
785
|
RadioSettingValueList(
|
786
|
LIST_PFKEY, LIST_PFKEY[val]))
|
787
|
advanced.append(pf2)
|
788
|
|
789
|
# other
|
790
|
_limit = str(int(_mem.limits.vhf.lower) / 10)
|
791
|
val = RadioSettingValueString(0, 3, _limit)
|
792
|
val.set_mutable(False)
|
793
|
rs = RadioSetting("limits.vhf.lower", "VHF low", val)
|
794
|
other.append(rs)
|
795
|
|
796
|
_limit = str(int(_mem.limits.vhf.upper) / 10)
|
797
|
val = RadioSettingValueString(0, 3, _limit)
|
798
|
val.set_mutable(False)
|
799
|
rs = RadioSetting("limits.vhf.upper", "VHF high", val)
|
800
|
other.append(rs)
|
801
|
|
802
|
_limit = str(int(_mem.limits.uhf.lower) / 10)
|
803
|
val = RadioSettingValueString(0, 3, _limit)
|
804
|
val.set_mutable(False)
|
805
|
rs = RadioSetting("limits.uhf.lower", "UHF low", val)
|
806
|
other.append(rs)
|
807
|
|
808
|
_limit = str(int(_mem.limits.uhf.upper) / 10)
|
809
|
val = RadioSettingValueString(0, 3, _limit)
|
810
|
val.set_mutable(False)
|
811
|
rs = RadioSetting("limits.uhf.upper", "UHF high", val)
|
812
|
other.append(rs)
|
813
|
|
814
|
#work mode
|
815
|
vfomr_a = RadioSetting("vfomr_a", "Display Mode A",
|
816
|
RadioSettingValueList(
|
817
|
LIST_VFOMR, LIST_VFOMR[_settings.vfomr_a]))
|
818
|
workmode.append(vfomr_a)
|
819
|
|
820
|
vfomr_b = RadioSetting("vfomr_b", "Display Mode B",
|
821
|
RadioSettingValueList(
|
822
|
LIST_VFOMR, LIST_VFOMR[_settings.vfomr_b]))
|
823
|
workmode.append(vfomr_b)
|
824
|
|
825
|
mrcha = RadioSetting("mrcha", "Channel # A",
|
826
|
RadioSettingValueInteger(
|
827
|
1, 128, _settings.mrcha))
|
828
|
workmode.append(mrcha)
|
829
|
|
830
|
mrchb = RadioSetting("mrchb", "Channel # B",
|
831
|
RadioSettingValueInteger(
|
832
|
1, 128, _settings.mrchb))
|
833
|
workmode.append(mrchb)
|
834
|
|
835
|
#fm radio
|
836
|
vfomr_fm = RadioSetting("vfomr_fm", "FM Radio Display Mode",
|
837
|
RadioSettingValueList(
|
838
|
LIST_VFOMRFM, LIST_VFOMRFM[_settings.vfomr_fm]))
|
839
|
fmradio.append(vfomr_fm)
|
840
|
|
841
|
fmch = RadioSetting("fmch", "FM Radio Channel #",
|
842
|
RadioSettingValueInteger(
|
843
|
1, 25, _settings.fmch))
|
844
|
fmradio.append(fmch)
|
845
|
|
846
|
return top
|
847
|
|
848
|
def set_settings(self, settings):
|
849
|
for element in settings:
|
850
|
if not isinstance(element, RadioSetting):
|
851
|
self.set_settings(element)
|
852
|
continue
|
853
|
else:
|
854
|
try:
|
855
|
if "." in element.get_name():
|
856
|
bits = element.get_name().split(".")
|
857
|
obj = self._memobj
|
858
|
for bit in bits[:-1]:
|
859
|
obj = getattr(obj, bit)
|
860
|
setting = bits[-1]
|
861
|
else:
|
862
|
obj = self._memobj.settings
|
863
|
setting = element.get_name()
|
864
|
|
865
|
if element.has_apply_callback():
|
866
|
LOG.debug("Using apply callback")
|
867
|
element.run_apply_callback()
|
868
|
elif setting == "color":
|
869
|
setattr(obj, setting, int(element.value) + 1)
|
870
|
elif element.value.get_mutable():
|
871
|
LOG.debug("Setting %s = %s" % (setting, element.value))
|
872
|
setattr(obj, setting, element.value)
|
873
|
except Exception, e:
|
874
|
LOG.debug(element.get_name())
|
875
|
raise
|
876
|
|
877
|
@classmethod
|
878
|
def match_model(cls, filedata, filename):
|
879
|
match_size = False
|
880
|
match_model = False
|
881
|
|
882
|
# testing the file data size
|
883
|
if len(filedata) in [0x1000, ]:
|
884
|
match_size = True
|
885
|
|
886
|
# testing the model fingerprint
|
887
|
match_model = model_match(cls, filedata)
|
888
|
|
889
|
if match_size and match_model:
|
890
|
return True
|
891
|
else:
|
892
|
return False
|