1
|
# Copyright 2011 Dan Smith <dsmith@danplanet.com>
|
2
|
#
|
3
|
# FT-2900-specific modifications by Richard Cochran, <ag6qr@sonic.net>
|
4
|
# Initial work on settings by Chris Fosnight, <chris.fosnight@gmail.com>
|
5
|
#
|
6
|
# This program is free software: you can redistribute it and/or modify
|
7
|
# it under the terms of the GNU General Public License as published by
|
8
|
# the Free Software Foundation, either version 3 of the License, or
|
9
|
# (at your option) any later version.
|
10
|
#
|
11
|
# This program is distributed in the hope that it will be useful,
|
12
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
# GNU General Public License for more details.
|
15
|
#
|
16
|
# You should have received a copy of the GNU General Public License
|
17
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
|
19
|
import time
|
20
|
import os
|
21
|
import logging
|
22
|
|
23
|
from chirp import util, memmap, chirp_common, bitwise, directory, errors
|
24
|
from chirp.drivers.yaesu_clone import YaesuCloneModeRadio
|
25
|
from chirp.settings import RadioSetting, RadioSettingGroup, \
|
26
|
RadioSettingValueList, RadioSettingValueString, RadioSettings
|
27
|
|
28
|
from textwrap import dedent
|
29
|
|
30
|
LOG = logging.getLogger(__name__)
|
31
|
|
32
|
|
33
|
def _send(s, data):
|
34
|
s.write(data)
|
35
|
echo = s.read(len(data))
|
36
|
if data != echo:
|
37
|
raise Exception("Failed to read echo")
|
38
|
LOG.debug("got echo\n%s\n" % util.hexprint(echo))
|
39
|
|
40
|
ACK = "\x06"
|
41
|
INITIAL_CHECKSUM = 0
|
42
|
|
43
|
|
44
|
def _download(radio):
|
45
|
|
46
|
blankChunk = ""
|
47
|
for _i in range(0, 32):
|
48
|
blankChunk += "\xff"
|
49
|
|
50
|
LOG.debug("in _download\n")
|
51
|
|
52
|
data = ""
|
53
|
for _i in range(0, 20):
|
54
|
data = radio.pipe.read(20)
|
55
|
LOG.debug("Header:\n%s" % util.hexprint(data))
|
56
|
LOG.debug("len(header) = %s\n" % len(data))
|
57
|
|
58
|
if data == radio.IDBLOCK:
|
59
|
break
|
60
|
|
61
|
if data != radio.IDBLOCK:
|
62
|
raise Exception("Failed to read header")
|
63
|
|
64
|
_send(radio.pipe, ACK)
|
65
|
|
66
|
# initialize data, the big var that holds all memory
|
67
|
data = ""
|
68
|
|
69
|
_blockNum = 0
|
70
|
|
71
|
while len(data) < radio._block_sizes[1]:
|
72
|
_blockNum += 1
|
73
|
time.sleep(0.03)
|
74
|
chunk = radio.pipe.read(32)
|
75
|
LOG.debug("Block %i " % (_blockNum))
|
76
|
if chunk == blankChunk:
|
77
|
LOG.debug("blank chunk\n")
|
78
|
else:
|
79
|
LOG.debug("Got: %i:\n%s" % (len(chunk), util.hexprint(chunk)))
|
80
|
if len(chunk) != 32:
|
81
|
LOG.debug("len chunk is %i\n" % (len(chunk)))
|
82
|
raise Exception("Failed to get full data block")
|
83
|
break
|
84
|
else:
|
85
|
data += chunk
|
86
|
|
87
|
if radio.status_fn:
|
88
|
status = chirp_common.Status()
|
89
|
status.max = radio._block_sizes[1]
|
90
|
status.cur = len(data)
|
91
|
status.msg = "Cloning from radio"
|
92
|
radio.status_fn(status)
|
93
|
|
94
|
LOG.debug("Total: %i" % len(data))
|
95
|
|
96
|
# radio should send us one final termination byte, containing
|
97
|
# checksum
|
98
|
chunk = radio.pipe.read(32)
|
99
|
if len(chunk) != 1:
|
100
|
LOG.debug("len(chunk) is %i\n" % len(chunk))
|
101
|
raise Exception("radio sent extra unknown data")
|
102
|
LOG.debug("Got: %i:\n%s" % (len(chunk), util.hexprint(chunk)))
|
103
|
|
104
|
# compute checksum
|
105
|
cs = INITIAL_CHECKSUM
|
106
|
for byte in radio.IDBLOCK:
|
107
|
cs += ord(byte)
|
108
|
for byte in data:
|
109
|
cs += ord(byte)
|
110
|
LOG.debug("calculated checksum is %x\n" % (cs & 0xff))
|
111
|
LOG.debug("Radio sent checksum is %x\n" % ord(chunk[0]))
|
112
|
|
113
|
if (cs & 0xff) != ord(chunk[0]):
|
114
|
raise Exception("Failed checksum on read.")
|
115
|
|
116
|
# for debugging purposes, dump the channels, in hex.
|
117
|
for _i in range(0, 200):
|
118
|
_startData = 1892 + 20 * _i
|
119
|
chunk = data[_startData:_startData + 20]
|
120
|
LOG.debug("channel %i:\n%s" % (_i, util.hexprint(chunk)))
|
121
|
|
122
|
return memmap.MemoryMap(data)
|
123
|
|
124
|
|
125
|
def _upload(radio):
|
126
|
for _i in range(0, 10):
|
127
|
data = radio.pipe.read(256)
|
128
|
if not data:
|
129
|
break
|
130
|
LOG.debug("What is this garbage?\n%s" % util.hexprint(data))
|
131
|
raise Exception("Radio sent unrecognized data")
|
132
|
|
133
|
_send(radio.pipe, radio.IDBLOCK)
|
134
|
time.sleep(.2)
|
135
|
ack = radio.pipe.read(300)
|
136
|
LOG.debug("Ack was (%i):\n%s" % (len(ack), util.hexprint(ack)))
|
137
|
if ack != ACK:
|
138
|
raise Exception("Radio did not ack ID. Check cable, verify"
|
139
|
" radio is not locked.\n"
|
140
|
" (press & Hold red \"*L\" button to unlock"
|
141
|
" radio if needed)")
|
142
|
|
143
|
block = 0
|
144
|
cs = INITIAL_CHECKSUM
|
145
|
for byte in radio.IDBLOCK:
|
146
|
cs += ord(byte)
|
147
|
|
148
|
while block < (radio.get_memsize() / 32):
|
149
|
data = radio.get_mmap()[block*32:(block+1)*32]
|
150
|
|
151
|
LOG.debug("Writing block %i:\n%s" % (block, util.hexprint(data)))
|
152
|
|
153
|
_send(radio.pipe, data)
|
154
|
time.sleep(0.03)
|
155
|
|
156
|
for byte in data:
|
157
|
cs += ord(byte)
|
158
|
|
159
|
if radio.status_fn:
|
160
|
status = chirp_common.Status()
|
161
|
status.max = radio._block_sizes[1]
|
162
|
status.cur = block * 32
|
163
|
status.msg = "Cloning to radio"
|
164
|
radio.status_fn(status)
|
165
|
block += 1
|
166
|
|
167
|
_send(radio.pipe, chr(cs & 0xFF))
|
168
|
|
169
|
MEM_FORMAT = """
|
170
|
#seekto 0x0080;
|
171
|
struct {
|
172
|
u8 apo;
|
173
|
u8 arts_beep;
|
174
|
u8 bell;
|
175
|
u8 dimmer;
|
176
|
u8 cw_id_string[16];
|
177
|
u8 cw_trng;
|
178
|
u8 x95;
|
179
|
u8 x96;
|
180
|
u8 x97;
|
181
|
u8 int_cd;
|
182
|
u8 int_set;
|
183
|
u8 x9A;
|
184
|
u8 x9B;
|
185
|
u8 lock;
|
186
|
u8 x9D;
|
187
|
u8 mic_gain;
|
188
|
u8 open_msg;
|
189
|
u8 openMsg_Text[6];
|
190
|
u8 rf_sql;
|
191
|
u8 unk:6,
|
192
|
pag_abk:1,
|
193
|
unk:1;
|
194
|
u8 pag_cdr_1;
|
195
|
u8 pag_cdr_2;
|
196
|
u8 pag_cdt_1;
|
197
|
u8 pag_cdt_2;
|
198
|
u8 prog_p1;
|
199
|
u8 xAD;
|
200
|
u8 prog_p2;
|
201
|
u8 xAF;
|
202
|
u8 prog_p3;
|
203
|
u8 xB1;
|
204
|
u8 prog_p4;
|
205
|
u8 xB3;
|
206
|
u8 resume;
|
207
|
u8 tot;
|
208
|
u8 unk:1,
|
209
|
cw_id:1,
|
210
|
unk:1,
|
211
|
ts_speed:1,
|
212
|
ars:1,
|
213
|
unk:2,
|
214
|
dtmf_mode:1;
|
215
|
u8 unk:1,
|
216
|
ts_mut:1
|
217
|
wires_auto:1,
|
218
|
busy_lockout:1,
|
219
|
edge_beep:1,
|
220
|
unk:3;
|
221
|
u8 unk:2,
|
222
|
s_search:1,
|
223
|
unk:2,
|
224
|
cw_trng_units:1,
|
225
|
unk:2;
|
226
|
u8 dtmf_speed:1,
|
227
|
unk:2,
|
228
|
arts_interval:1,
|
229
|
unk:1,
|
230
|
inverted_dcs:1,
|
231
|
unk:1,
|
232
|
mw_mode:1;
|
233
|
u8 unk:2,
|
234
|
wires_mode:1,
|
235
|
wx_alert:1,
|
236
|
unk:1,
|
237
|
wx_vol_max:1,
|
238
|
revert:1,
|
239
|
unk:1;
|
240
|
u8 vfo_scan;
|
241
|
u8 scan_mode;
|
242
|
u8 dtmf_delay;
|
243
|
u8 beep;
|
244
|
u8 xBF;
|
245
|
} settings;
|
246
|
|
247
|
#seekto 0x00d0;
|
248
|
u8 passwd[4];
|
249
|
u8 mbs;
|
250
|
|
251
|
#seekto 0x00c0;
|
252
|
struct {
|
253
|
u16 in_use;
|
254
|
} bank_used[8];
|
255
|
|
256
|
#seekto 0x00ef;
|
257
|
u8 currentTone;
|
258
|
|
259
|
#seekto 0x00f0;
|
260
|
u8 curChannelMem[20];
|
261
|
|
262
|
#seekto 0x1e0;
|
263
|
struct {
|
264
|
u8 dtmf_string[16];
|
265
|
} dtmf_strings[10];
|
266
|
|
267
|
#seekto 0x0127;
|
268
|
u8 curChannelNum;
|
269
|
|
270
|
#seekto 0x012a;
|
271
|
u8 banksoff1;
|
272
|
|
273
|
#seekto 0x15f;
|
274
|
u8 checksum1;
|
275
|
|
276
|
#seekto 0x16f;
|
277
|
u8 curentTone2;
|
278
|
|
279
|
#seekto 0x1aa;
|
280
|
u16 banksoff2;
|
281
|
|
282
|
#seekto 0x1df;
|
283
|
u8 checksum2;
|
284
|
|
285
|
#seekto 0x0360;
|
286
|
struct{
|
287
|
u8 name[6];
|
288
|
} bank_names[8];
|
289
|
|
290
|
|
291
|
#seekto 0x03c4;
|
292
|
struct{
|
293
|
u16 channels[50];
|
294
|
} banks[8];
|
295
|
|
296
|
#seekto 0x06e4;
|
297
|
struct {
|
298
|
u8 even_pskip:1,
|
299
|
even_skip:1,
|
300
|
even_valid:1,
|
301
|
even_masked:1,
|
302
|
odd_pskip:1,
|
303
|
odd_skip:1,
|
304
|
odd_valid:1,
|
305
|
odd_masked:1;
|
306
|
} flags[225];
|
307
|
|
308
|
#seekto 0x0764;
|
309
|
struct {
|
310
|
u8 unknown0:2,
|
311
|
isnarrow:1,
|
312
|
unknown1:5;
|
313
|
u8 unknown2:2,
|
314
|
duplex:2,
|
315
|
unknown3:1,
|
316
|
step:3;
|
317
|
bbcd freq[3];
|
318
|
u8 power:2,
|
319
|
unknown4:3,
|
320
|
tmode:3;
|
321
|
u8 name[6];
|
322
|
bbcd offset[3];
|
323
|
u8 ctonesplitflag:1,
|
324
|
ctone:7;
|
325
|
u8 rx_dtcssplitflag:1,
|
326
|
rx_dtcs:7;
|
327
|
u8 unknown5;
|
328
|
u8 rtonesplitflag:1,
|
329
|
rtone:7;
|
330
|
u8 dtcssplitflag:1,
|
331
|
dtcs:7;
|
332
|
} memory[200];
|
333
|
|
334
|
"""
|
335
|
|
336
|
MODES = ["FM", "NFM"]
|
337
|
TMODES = ["", "Tone", "TSQL", "DTCS", "TSQL-R", "Cross"]
|
338
|
CROSS_MODES = ["DTCS->", "Tone->DTCS", "DTCS->Tone",
|
339
|
"Tone->Tone", "DTCS->DTCS"]
|
340
|
DUPLEX = ["", "-", "+", "split"]
|
341
|
POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=75),
|
342
|
chirp_common.PowerLevel("Low3", watts=30),
|
343
|
chirp_common.PowerLevel("Low2", watts=10),
|
344
|
chirp_common.PowerLevel("Low1", watts=5),
|
345
|
]
|
346
|
|
347
|
CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ +-/?C[] _"
|
348
|
STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0]
|
349
|
|
350
|
|
351
|
def _decode_tone(radiotone):
|
352
|
try:
|
353
|
chirptone = chirp_common.TONES[radiotone]
|
354
|
except IndexError:
|
355
|
chirptone = 100
|
356
|
LOG.debug("found invalid radio tone: %i\n" % radiotone)
|
357
|
return chirptone
|
358
|
|
359
|
|
360
|
def _decode_dtcs(radiodtcs):
|
361
|
try:
|
362
|
chirpdtcs = chirp_common.DTCS_CODES[radiodtcs]
|
363
|
except IndexError:
|
364
|
chirpdtcs = 23
|
365
|
LOG.debug("found invalid radio dtcs code: %i\n" % radiodtcs)
|
366
|
return chirpdtcs
|
367
|
|
368
|
|
369
|
def _decode_name(mem):
|
370
|
name = ""
|
371
|
for i in mem:
|
372
|
if (i & 0x7F) == 0x7F:
|
373
|
break
|
374
|
try:
|
375
|
name += CHARSET[i & 0x7F]
|
376
|
except IndexError:
|
377
|
LOG.debug("Unknown char index: %x " % (i))
|
378
|
name = name.strip()
|
379
|
return name
|
380
|
|
381
|
|
382
|
def _encode_name(mem):
|
383
|
if(mem.strip() == ""):
|
384
|
return [0xff]*6
|
385
|
|
386
|
name = [None]*6
|
387
|
for i in range(0, 6):
|
388
|
try:
|
389
|
name[i] = CHARSET.index(mem[i])
|
390
|
except IndexError:
|
391
|
name[i] = CHARSET.index(" ")
|
392
|
|
393
|
name[0] = name[0] | 0x80
|
394
|
return name
|
395
|
|
396
|
|
397
|
def _wipe_memory(mem):
|
398
|
mem.set_raw("\xff" * (mem.size() / 8))
|
399
|
|
400
|
|
401
|
class FT2900Bank(chirp_common.NamedBank):
|
402
|
def get_name(self):
|
403
|
_bank = self._model._radio._memobj.bank_names[self.index]
|
404
|
name = ""
|
405
|
for i in _bank.name:
|
406
|
if i == 0xff:
|
407
|
break
|
408
|
name += CHARSET[i & 0x7f]
|
409
|
|
410
|
return name.rstrip()
|
411
|
|
412
|
def set_name(self, name):
|
413
|
name = name.upper().ljust(6)[:6]
|
414
|
_bank = self._model._radio._memobj.bank_names[self.index]
|
415
|
_bank.name = [CHARSET.index(x) for x in name.ljust(6)[:6]]
|
416
|
|
417
|
|
418
|
class FT2900BankModel(chirp_common.BankModel):
|
419
|
def get_num_mappings(self):
|
420
|
return 8
|
421
|
|
422
|
def get_mappings(self):
|
423
|
banks = self._radio._memobj.banks
|
424
|
bank_mappings = []
|
425
|
for index, _bank in enumerate(banks):
|
426
|
bank = FT2900Bank(self, "%i" % index, "b%i" % (index + 1))
|
427
|
bank.index = index
|
428
|
bank_mappings.append(bank)
|
429
|
|
430
|
return bank_mappings
|
431
|
|
432
|
def _get_channel_numbers_in_bank(self, bank):
|
433
|
_bank_used = self._radio._memobj.bank_used[bank.index]
|
434
|
if _bank_used.in_use == 0xffff:
|
435
|
return set()
|
436
|
|
437
|
_members = self._radio._memobj.banks[bank.index]
|
438
|
return set([int(ch) for ch in _members.channels if ch != 0xffff])
|
439
|
|
440
|
def _update_bank_with_channel_numbers(self, bank, channels_in_bank):
|
441
|
_members = self._radio._memobj.banks[bank.index]
|
442
|
if len(channels_in_bank) > len(_members.channels):
|
443
|
raise Exception("More than %i entries in bank %d" %
|
444
|
(len(_members.channels), bank.index))
|
445
|
|
446
|
empty = 0
|
447
|
for index, channel_number in enumerate(sorted(channels_in_bank)):
|
448
|
_members.channels[index] = channel_number
|
449
|
empty = index + 1
|
450
|
for index in range(empty, len(_members.channels)):
|
451
|
_members.channels[index] = 0xffff
|
452
|
|
453
|
_bank_used = self._radio._memobj.bank_used[bank.index]
|
454
|
if empty == 0:
|
455
|
_bank_used.in_use = 0xffff
|
456
|
else:
|
457
|
_bank_used.in_use = empty - 1
|
458
|
|
459
|
def add_memory_to_mapping(self, memory, bank):
|
460
|
channels_in_bank = self._get_channel_numbers_in_bank(bank)
|
461
|
channels_in_bank.add(memory.number)
|
462
|
self._update_bank_with_channel_numbers(bank, channels_in_bank)
|
463
|
|
464
|
# tells radio that banks are active
|
465
|
self._radio._memobj.banksoff1 = bank.index
|
466
|
self._radio._memobj.banksoff2 = bank.index
|
467
|
|
468
|
def remove_memory_from_mapping(self, memory, bank):
|
469
|
channels_in_bank = self._get_channel_numbers_in_bank(bank)
|
470
|
try:
|
471
|
channels_in_bank.remove(memory.number)
|
472
|
except KeyError:
|
473
|
raise Exception("Memory %i is not in bank %s. Cannot remove" %
|
474
|
(memory.number, bank))
|
475
|
self._update_bank_with_channel_numbers(bank, channels_in_bank)
|
476
|
|
477
|
def get_mapping_memories(self, bank):
|
478
|
memories = []
|
479
|
for channel in self._get_channel_numbers_in_bank(bank):
|
480
|
memories.append(self._radio.get_memory(channel))
|
481
|
|
482
|
return memories
|
483
|
|
484
|
def get_memory_mappings(self, memory):
|
485
|
banks = []
|
486
|
for bank in self.get_mappings():
|
487
|
if memory.number in self._get_channel_numbers_in_bank(bank):
|
488
|
banks.append(bank)
|
489
|
|
490
|
return banks
|
491
|
|
492
|
|
493
|
@directory.register
|
494
|
class FT2900Radio(YaesuCloneModeRadio):
|
495
|
"""Yaesu FT-2900"""
|
496
|
VENDOR = "Yaesu"
|
497
|
MODEL = "FT-2900R/1900R"
|
498
|
IDBLOCK = "\x56\x43\x32\x33\x00\x02\x46\x01\x01\x01"
|
499
|
BAUD_RATE = 19200
|
500
|
|
501
|
_memsize = 8000
|
502
|
_block_sizes = [8, 8000]
|
503
|
|
504
|
def get_features(self):
|
505
|
rf = chirp_common.RadioFeatures()
|
506
|
|
507
|
rf.memory_bounds = (0, 199)
|
508
|
|
509
|
rf.can_odd_split = True
|
510
|
rf.has_ctone = True
|
511
|
rf.has_rx_dtcs = True
|
512
|
rf.has_cross = True
|
513
|
rf.has_dtcs_polarity = False
|
514
|
rf.has_bank = True
|
515
|
rf.has_bank_names = True
|
516
|
rf.has_settings = True
|
517
|
|
518
|
rf.valid_tuning_steps = STEPS
|
519
|
rf.valid_modes = MODES
|
520
|
rf.valid_tmodes = TMODES
|
521
|
rf.valid_cross_modes = CROSS_MODES
|
522
|
rf.valid_bands = [(136000000, 174000000)]
|
523
|
rf.valid_power_levels = POWER_LEVELS
|
524
|
rf.valid_duplexes = DUPLEX
|
525
|
rf.valid_skips = ["", "S", "P"]
|
526
|
rf.valid_name_length = 6
|
527
|
rf.valid_characters = CHARSET
|
528
|
|
529
|
return rf
|
530
|
|
531
|
def sync_in(self):
|
532
|
start = time.time()
|
533
|
try:
|
534
|
self._mmap = _download(self)
|
535
|
except errors.RadioError:
|
536
|
raise
|
537
|
except Exception, e:
|
538
|
raise errors.RadioError("Failed to communicate with radio: %s" % e)
|
539
|
LOG.info("Downloaded in %.2f sec" % (time.time() - start))
|
540
|
self.process_mmap()
|
541
|
|
542
|
def sync_out(self):
|
543
|
self.pipe.timeout = 1
|
544
|
start = time.time()
|
545
|
try:
|
546
|
_upload(self)
|
547
|
except errors.RadioError:
|
548
|
raise
|
549
|
except Exception, e:
|
550
|
raise errors.RadioError("Failed to communicate with radio: %s" % e)
|
551
|
LOG.info("Uploaded in %.2f sec" % (time.time() - start))
|
552
|
|
553
|
def process_mmap(self):
|
554
|
self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
|
555
|
|
556
|
def get_raw_memory(self, number):
|
557
|
return repr(self._memobj.memory[number])
|
558
|
|
559
|
def get_memory(self, number):
|
560
|
_mem = self._memobj.memory[number]
|
561
|
_flag = self._memobj.flags[(number)/2]
|
562
|
|
563
|
nibble = ((number) % 2) and "even" or "odd"
|
564
|
used = _flag["%s_masked" % nibble]
|
565
|
valid = _flag["%s_valid" % nibble]
|
566
|
pskip = _flag["%s_pskip" % nibble]
|
567
|
skip = _flag["%s_skip" % nibble]
|
568
|
|
569
|
mem = chirp_common.Memory()
|
570
|
|
571
|
mem.number = number
|
572
|
|
573
|
if _mem.get_raw()[0] == "\xFF" or not valid or not used:
|
574
|
mem.empty = True
|
575
|
return mem
|
576
|
|
577
|
mem.tuning_step = STEPS[_mem.step]
|
578
|
mem.freq = int(_mem.freq) * 1000
|
579
|
|
580
|
# compensate for 12.5 kHz tuning steps, add 500 Hz if needed
|
581
|
if(mem.tuning_step == 12.5):
|
582
|
lastdigit = int(_mem.freq) % 10
|
583
|
if (lastdigit == 2 or lastdigit == 7):
|
584
|
mem.freq += 500
|
585
|
|
586
|
mem.offset = chirp_common.fix_rounded_step(int(_mem.offset) * 1000)
|
587
|
mem.duplex = DUPLEX[_mem.duplex]
|
588
|
if _mem.tmode < TMODES.index("Cross"):
|
589
|
mem.tmode = TMODES[_mem.tmode]
|
590
|
mem.cross_mode = CROSS_MODES[0]
|
591
|
else:
|
592
|
mem.tmode = "Cross"
|
593
|
mem.cross_mode = CROSS_MODES[_mem.tmode - TMODES.index("Cross")]
|
594
|
|
595
|
mem.rtone = _decode_tone(_mem.rtone)
|
596
|
mem.ctone = _decode_tone(_mem.ctone)
|
597
|
|
598
|
# check for unequal ctone/rtone in TSQL mode. map it as a
|
599
|
# cross tone mode
|
600
|
if mem.rtone != mem.ctone and (mem.tmode == "TSQL" or
|
601
|
mem.tmode == "Tone"):
|
602
|
mem.tmode = "Cross"
|
603
|
mem.cross_mode = "Tone->Tone"
|
604
|
|
605
|
mem.dtcs = _decode_dtcs(_mem.dtcs)
|
606
|
mem.rx_dtcs = _decode_dtcs(_mem.rx_dtcs)
|
607
|
|
608
|
# check for unequal dtcs/rx_dtcs in DTCS mode. map it as a
|
609
|
# cross tone mode
|
610
|
if mem.dtcs != mem.rx_dtcs and mem.tmode == "DTCS":
|
611
|
mem.tmode = "Cross"
|
612
|
mem.cross_mode = "DTCS->DTCS"
|
613
|
|
614
|
if (int(_mem.name[0]) & 0x80) != 0:
|
615
|
mem.name = _decode_name(_mem.name)
|
616
|
|
617
|
mem.mode = _mem.isnarrow and "NFM" or "FM"
|
618
|
mem.skip = pskip and "P" or skip and "S" or ""
|
619
|
mem.power = POWER_LEVELS[3 - _mem.power]
|
620
|
|
621
|
return mem
|
622
|
|
623
|
def set_memory(self, mem):
|
624
|
_mem = self._memobj.memory[mem.number]
|
625
|
_flag = self._memobj.flags[(mem.number)/2]
|
626
|
|
627
|
nibble = ((mem.number) % 2) and "even" or "odd"
|
628
|
|
629
|
valid = _flag["%s_valid" % nibble]
|
630
|
used = _flag["%s_masked" % nibble]
|
631
|
|
632
|
if not valid:
|
633
|
_wipe_memory(_mem)
|
634
|
|
635
|
if mem.empty and valid and not used:
|
636
|
_flag["%s_valid" % nibble] = False
|
637
|
return
|
638
|
|
639
|
_flag["%s_masked" % nibble] = not mem.empty
|
640
|
|
641
|
if mem.empty:
|
642
|
return
|
643
|
|
644
|
_flag["%s_valid" % nibble] = True
|
645
|
|
646
|
_mem.freq = mem.freq / 1000
|
647
|
_mem.offset = mem.offset / 1000
|
648
|
_mem.duplex = DUPLEX.index(mem.duplex)
|
649
|
|
650
|
# clear all the split tone flags -- we'll set them as needed below
|
651
|
_mem.ctonesplitflag = 0
|
652
|
_mem.rx_dtcssplitflag = 0
|
653
|
_mem.rtonesplitflag = 0
|
654
|
_mem.dtcssplitflag = 0
|
655
|
|
656
|
if mem.tmode != "Cross":
|
657
|
_mem.tmode = TMODES.index(mem.tmode)
|
658
|
# for the non-cross modes, use ONE tone for both send
|
659
|
# and receive but figure out where to get it from.
|
660
|
if mem.tmode == "TSQL" or mem.tmode == "TSQL-R":
|
661
|
_mem.rtone = chirp_common.TONES.index(mem.ctone)
|
662
|
_mem.ctone = chirp_common.TONES.index(mem.ctone)
|
663
|
else:
|
664
|
_mem.rtone = chirp_common.TONES.index(mem.rtone)
|
665
|
_mem.ctone = chirp_common.TONES.index(mem.rtone)
|
666
|
|
667
|
# and one tone for dtcs, but this is always the sending one
|
668
|
_mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
|
669
|
_mem.rx_dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
|
670
|
|
671
|
else:
|
672
|
_mem.rtone = chirp_common.TONES.index(mem.rtone)
|
673
|
_mem.ctone = chirp_common.TONES.index(mem.ctone)
|
674
|
_mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
|
675
|
_mem.rx_dtcs = chirp_common.DTCS_CODES.index(mem.rx_dtcs)
|
676
|
if mem.cross_mode == "Tone->Tone":
|
677
|
# tone->tone cross mode is treated as
|
678
|
# TSQL, but with separate tones for
|
679
|
# send and receive
|
680
|
_mem.tmode = TMODES.index("TSQL")
|
681
|
_mem.rtonesplitflag = 1
|
682
|
elif mem.cross_mode == "DTCS->DTCS":
|
683
|
# DTCS->DTCS cross mode is treated as
|
684
|
# DTCS, but with separate codes for
|
685
|
# send and receive
|
686
|
_mem.tmode = TMODES.index("DTCS")
|
687
|
_mem.dtcssplitflag = 1
|
688
|
else:
|
689
|
_mem.tmode = TMODES.index("Cross") + \
|
690
|
CROSS_MODES.index(mem.cross_mode)
|
691
|
|
692
|
_mem.isnarrow = MODES.index(mem.mode)
|
693
|
_mem.step = STEPS.index(mem.tuning_step)
|
694
|
_flag["%s_pskip" % nibble] = mem.skip == "P"
|
695
|
_flag["%s_skip" % nibble] = mem.skip == "S"
|
696
|
if mem.power:
|
697
|
_mem.power = 3 - POWER_LEVELS.index(mem.power)
|
698
|
else:
|
699
|
_mem.power = 3
|
700
|
|
701
|
_mem.name = _encode_name(mem.name)
|
702
|
|
703
|
# set all unknown areas of the memory map to 0
|
704
|
_mem.unknown0 = 0
|
705
|
_mem.unknown1 = 0
|
706
|
_mem.unknown2 = 0
|
707
|
_mem.unknown3 = 0
|
708
|
_mem.unknown4 = 0
|
709
|
_mem.unknown5 = 0
|
710
|
|
711
|
LOG.debug("encoded mem\n%s\n" % (util.hexprint(_mem.get_raw()[0:20])))
|
712
|
|
713
|
def get_settings(self):
|
714
|
_settings = self._memobj.settings
|
715
|
_dtmf_strings = self._memobj.dtmf_strings
|
716
|
_passwd = self._memobj.passwd
|
717
|
|
718
|
repeater = RadioSettingGroup("repeater", "Repeater Settings")
|
719
|
ctcss = RadioSettingGroup("ctcss", "CTCSS/DCS/EPCS Settings")
|
720
|
arts = RadioSettingGroup("arts", "ARTS Settings")
|
721
|
mbls = RadioSettingGroup("banks", "Memory Settings")
|
722
|
scan = RadioSettingGroup("scan", "Scan Settings")
|
723
|
dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
|
724
|
wires = RadioSettingGroup("wires", "WiRES(tm) Settings")
|
725
|
switch = RadioSettingGroup("switch", "Switch/Knob Settings")
|
726
|
disp = RadioSettingGroup("disp", "Display Settings")
|
727
|
misc = RadioSettingGroup("misc", "Miscellaneous Settings")
|
728
|
|
729
|
setmode = RadioSettings(repeater, ctcss, arts, mbls, scan,
|
730
|
dtmf, wires, switch, disp, misc)
|
731
|
|
732
|
# numbers and names of settings refer to the way they're
|
733
|
# presented in the set menu, as well as the list starting on
|
734
|
# page 74 of the manual
|
735
|
|
736
|
# 1 APO
|
737
|
opts = ["Off", "30 Min", "1 Hour", "3 Hour", "5 Hour", "8 Hour"]
|
738
|
misc.append(
|
739
|
RadioSetting(
|
740
|
"apo", "Automatic Power Off",
|
741
|
RadioSettingValueList(opts, opts[_settings.apo])))
|
742
|
|
743
|
# 2 AR.BEP
|
744
|
opts = ["Off", "In Range", "Always"]
|
745
|
arts.append(
|
746
|
RadioSetting(
|
747
|
"arts_beep", "ARTS Beep",
|
748
|
RadioSettingValueList(opts, opts[_settings.arts_beep])))
|
749
|
|
750
|
# 3 AR.INT
|
751
|
opts = ["15 Sec", "25 Sec"]
|
752
|
arts.append(
|
753
|
RadioSetting(
|
754
|
"arts_interval", "ARTS Polling Interval",
|
755
|
RadioSettingValueList(opts, opts[_settings.arts_interval])))
|
756
|
|
757
|
# 4 ARS
|
758
|
opts = ["Off", "On"]
|
759
|
repeater.append(
|
760
|
RadioSetting(
|
761
|
"ars", "Automatic Repeater Shift",
|
762
|
RadioSettingValueList(opts, opts[_settings.ars])))
|
763
|
|
764
|
# 5 BCLO
|
765
|
opts = ["Off", "On"]
|
766
|
misc.append(RadioSetting(
|
767
|
"busy_lockout", "Busy Channel Lock-Out",
|
768
|
RadioSettingValueList(opts, opts[_settings.busy_lockout])))
|
769
|
|
770
|
# 6 BEEP
|
771
|
opts = ["Off", "Key+Scan", "Key"]
|
772
|
switch.append(RadioSetting(
|
773
|
"beep", "Enable the Beeper",
|
774
|
RadioSettingValueList(opts, opts[_settings.beep])))
|
775
|
|
776
|
# 7 BELL
|
777
|
opts = ["Off", "1", "3", "5", "8", "Continuous"]
|
778
|
ctcss.append(RadioSetting("bell", "Bell Repetitions",
|
779
|
RadioSettingValueList(opts, opts[_settings.bell])))
|
780
|
|
781
|
# 8 BNK.LNK
|
782
|
for i in range(0, 8):
|
783
|
opts = ["Off", "On"]
|
784
|
mbs = (self._memobj.mbs >> i) & 1
|
785
|
rs = RadioSetting("mbs%i" % i, "Bank %s Scan" % (i + 1),
|
786
|
RadioSettingValueList(opts, opts[mbs]))
|
787
|
|
788
|
def apply_mbs(s, index):
|
789
|
if int(s.value):
|
790
|
self._memobj.mbs |= (1 << index)
|
791
|
else:
|
792
|
self._memobj.mbs &= ~(1 << index)
|
793
|
rs.set_apply_callback(apply_mbs, i)
|
794
|
mbls.append(rs)
|
795
|
|
796
|
# 9 BNK.NM - A per-bank attribute, nothing to do here.
|
797
|
|
798
|
# 10 CLK.SFT - A per-channel attribute, nothing to do here.
|
799
|
|
800
|
# 11 CW.ID
|
801
|
opts = ["Off", "On"]
|
802
|
arts.append(RadioSetting("cw_id", "CW ID Enable",
|
803
|
RadioSettingValueList(opts, opts[_settings.cw_id])))
|
804
|
|
805
|
cw_id_text = ""
|
806
|
for i in _settings.cw_id_string:
|
807
|
try:
|
808
|
cw_id_text += CHARSET[i & 0x7F]
|
809
|
except IndexError:
|
810
|
if i != 0xff:
|
811
|
LOG.debug("unknown char index in cw id: %x " % (i))
|
812
|
|
813
|
val = RadioSettingValueString(0, 16, cw_id_text, True)
|
814
|
val.set_charset(CHARSET + "abcdefghijklmnopqrstuvwxyz")
|
815
|
rs = RadioSetting("cw_id_string", "CW Identifier Text", val)
|
816
|
|
817
|
def apply_cw_id(s):
|
818
|
str = s.value.get_value().upper().rstrip()
|
819
|
mval = ""
|
820
|
mval = [chr(CHARSET.index(x)) for x in str]
|
821
|
for x in range(len(mval), 16):
|
822
|
mval.append(chr(0xff))
|
823
|
for x in range(0, 16):
|
824
|
_settings.cw_id_string[x] = ord(mval[x])
|
825
|
rs.set_apply_callback(apply_cw_id)
|
826
|
arts.append(rs)
|
827
|
|
828
|
# 12 CWTRNG
|
829
|
opts = ["Off", "4WPM", "5WPM", "6WPM", "7WPM", "8WPM", "9WPM",
|
830
|
"10WPM", "11WPM", "12WPM", "13WPM", "15WPM", "17WPM",
|
831
|
"20WPM", "24WPM", "30WPM", "40WPM"]
|
832
|
misc.append(RadioSetting("cw_trng", "CW Training",
|
833
|
RadioSettingValueList(opts, opts[_settings.cw_trng])))
|
834
|
|
835
|
# todo: make the setting of the units here affect the display
|
836
|
# of the speed. Not critical, but would be slick.
|
837
|
opts = ["CPM", "WPM"]
|
838
|
misc.append(RadioSetting("cw_trng_units", "CW Training Units",
|
839
|
RadioSettingValueList(opts,
|
840
|
opts[_settings.cw_trng_units])))
|
841
|
|
842
|
# 13 DC VLT - a read-only status, so nothing to do here
|
843
|
|
844
|
# 14 DCS CD - A per-channel attribute, nothing to do here
|
845
|
|
846
|
# 15 DCS.RV
|
847
|
opts = ["Disabled", "Enabled"]
|
848
|
ctcss.append(RadioSetting(
|
849
|
"inverted_dcs",
|
850
|
"\"Inverted\" DCS Code Decoding",
|
851
|
RadioSettingValueList(opts,
|
852
|
opts[_settings.inverted_dcs])))
|
853
|
|
854
|
# 16 DIMMER
|
855
|
opts = ["Off"] + ["Level %d" % (x) for x in range(1, 11)]
|
856
|
disp.append(RadioSetting("dimmer", "Dimmer",
|
857
|
RadioSettingValueList(opts,
|
858
|
opts[_settings
|
859
|
.dimmer])))
|
860
|
|
861
|
# 17 DT.A/M
|
862
|
opts = ["Manual", "Auto"]
|
863
|
dtmf.append(RadioSetting("dtmf_mode", "DTMF Autodialer",
|
864
|
RadioSettingValueList(opts,
|
865
|
opts[_settings
|
866
|
.dtmf_mode])))
|
867
|
|
868
|
# 18 DT.DLY
|
869
|
opts = ["50 ms", "250 ms", "450 ms", "750 ms", "1000 ms"]
|
870
|
dtmf.append(RadioSetting("dtmf_delay", "DTMF Autodialer Delay Time",
|
871
|
RadioSettingValueList(opts,
|
872
|
opts[_settings
|
873
|
.dtmf_delay])))
|
874
|
|
875
|
# 19 DT.SET
|
876
|
for memslot in range(0, 10):
|
877
|
dtmf_memory = ""
|
878
|
for i in _dtmf_strings[memslot].dtmf_string:
|
879
|
if i != 0xFF:
|
880
|
try:
|
881
|
dtmf_memory += CHARSET[i]
|
882
|
except IndexError:
|
883
|
LOG.debug("unknown char index in dtmf: %x " % (i))
|
884
|
|
885
|
val = RadioSettingValueString(0, 16, dtmf_memory, True)
|
886
|
val.set_charset(CHARSET + "abcdef")
|
887
|
rs = RadioSetting("dtmf_string_%d" % memslot,
|
888
|
"DTMF Memory %d" % memslot, val)
|
889
|
|
890
|
def apply_dtmf(s, i):
|
891
|
LOG.debug("applying dtmf for %x\n" % i)
|
892
|
str = s.value.get_value().upper().rstrip()
|
893
|
LOG.debug("str is %s\n" % str)
|
894
|
mval = ""
|
895
|
mval = [chr(CHARSET.index(x)) for x in str]
|
896
|
for x in range(len(mval), 16):
|
897
|
mval.append(chr(0xff))
|
898
|
for x in range(0, 16):
|
899
|
_dtmf_strings[i].dtmf_string[x] = ord(mval[x])
|
900
|
rs.set_apply_callback(apply_dtmf, memslot)
|
901
|
dtmf.append(rs)
|
902
|
|
903
|
# 20 DT.SPD
|
904
|
opts = ["50 ms", "100 ms"]
|
905
|
dtmf.append(RadioSetting("dtmf_speed",
|
906
|
"DTMF Autodialer Sending Speed",
|
907
|
RadioSettingValueList(opts,
|
908
|
opts[_settings.
|
909
|
dtmf_speed])))
|
910
|
|
911
|
# 21 EDG.BEP
|
912
|
opts = ["Off", "On"]
|
913
|
mbls.append(RadioSetting("edge_beep", "Band Edge Beeper",
|
914
|
RadioSettingValueList(opts,
|
915
|
opts[_settings.
|
916
|
edge_beep])))
|
917
|
|
918
|
# 22 INT.CD
|
919
|
opts = ["DTMF %X" % (x) for x in range(0, 16)]
|
920
|
wires.append(RadioSetting("int_cd", "Access Number for WiRES(TM)",
|
921
|
RadioSettingValueList(opts, opts[_settings.int_cd])))
|
922
|
|
923
|
# 23 ING MD
|
924
|
opts = ["Sister Radio Group", "Friends Radio Group"]
|
925
|
wires.append(RadioSetting("wires_mode",
|
926
|
"Internet Link Connection Mode",
|
927
|
RadioSettingValueList(opts,
|
928
|
opts[_settings.
|
929
|
wires_mode])))
|
930
|
|
931
|
# 24 INT.A/M
|
932
|
opts = ["Manual", "Auto"]
|
933
|
wires.append(RadioSetting("wires_auto", "Internet Link Autodialer",
|
934
|
RadioSettingValueList(opts,
|
935
|
opts[_settings
|
936
|
.wires_auto])))
|
937
|
# 25 INT.SET
|
938
|
opts = ["F%d" % (x) for x in range(0, 10)]
|
939
|
|
940
|
wires.append(RadioSetting("int_set", "Memory Register for "
|
941
|
"non-WiRES Internet",
|
942
|
RadioSettingValueList(opts,
|
943
|
opts[_settings
|
944
|
.int_set])))
|
945
|
|
946
|
# 26 LOCK
|
947
|
opts = ["Key", "Dial", "Key + Dial", "PTT",
|
948
|
"Key + PTT", "Dial + PTT", "All"]
|
949
|
switch.append(RadioSetting("lock", "Control Locking",
|
950
|
RadioSettingValueList(opts,
|
951
|
opts[_settings
|
952
|
.lock])))
|
953
|
|
954
|
# 27 MCGAIN
|
955
|
opts = ["Level %d" % (x) for x in range(1, 10)]
|
956
|
misc.append(RadioSetting("mic_gain", "Microphone Gain",
|
957
|
RadioSettingValueList(opts,
|
958
|
opts[_settings
|
959
|
.mic_gain])))
|
960
|
|
961
|
# 28 MEM.SCN
|
962
|
opts = ["Tag 1", "Tag 2", "All Channels"]
|
963
|
rs = RadioSetting("scan_mode", "Memory Scan Mode",
|
964
|
RadioSettingValueList(opts,
|
965
|
opts[_settings
|
966
|
.scan_mode - 1]))
|
967
|
# this setting is unusual in that it starts at 1 instead of 0.
|
968
|
# that is, index 1 corresponds to "Tag 1", and index 0 is invalid.
|
969
|
# so we create a custom callback to handle this.
|
970
|
|
971
|
def apply_scan_mode(s):
|
972
|
myopts = ["Tag 1", "Tag 2", "All Channels"]
|
973
|
_settings.scan_mode = myopts.index(s.value.get_value()) + 1
|
974
|
rs.set_apply_callback(apply_scan_mode)
|
975
|
mbls.append(rs)
|
976
|
|
977
|
# 29 MW MD
|
978
|
opts = ["Lower", "Next"]
|
979
|
mbls.append(RadioSetting("mw_mode", "Memory Write Mode",
|
980
|
RadioSettingValueList(opts,
|
981
|
opts[_settings
|
982
|
.mw_mode])))
|
983
|
|
984
|
# 30 NM SET - This is per channel, so nothing to do here
|
985
|
|
986
|
# 31 OPN.MSG
|
987
|
opts = ["Off", "DC Supply Voltage", "Text Message"]
|
988
|
disp.append(RadioSetting("open_msg", "Opening Message Type",
|
989
|
RadioSettingValueList(opts,
|
990
|
opts[_settings.
|
991
|
open_msg])))
|
992
|
|
993
|
openmsg = ""
|
994
|
for i in _settings.openMsg_Text:
|
995
|
try:
|
996
|
openmsg += CHARSET[i & 0x7F]
|
997
|
except IndexError:
|
998
|
if i != 0xff:
|
999
|
LOG.debug("unknown char index in openmsg: %x " % (i))
|
1000
|
|
1001
|
val = RadioSettingValueString(0, 6, openmsg, True)
|
1002
|
val.set_charset(CHARSET + "abcdefghijklmnopqrstuvwxyz")
|
1003
|
rs = RadioSetting("openMsg_Text", "Opening Message Text", val)
|
1004
|
|
1005
|
def apply_openmsg(s):
|
1006
|
str = s.value.get_value().upper().rstrip()
|
1007
|
mval = ""
|
1008
|
mval = [chr(CHARSET.index(x)) for x in str]
|
1009
|
for x in range(len(mval), 6):
|
1010
|
mval.append(chr(0xff))
|
1011
|
for x in range(0, 6):
|
1012
|
_settings.openMsg_Text[x] = ord(mval[x])
|
1013
|
rs.set_apply_callback(apply_openmsg)
|
1014
|
disp.append(rs)
|
1015
|
|
1016
|
# 32 PAGER - a per-channel attribute
|
1017
|
|
1018
|
# 33 PAG.ABK
|
1019
|
opts = ["Off", "On"]
|
1020
|
ctcss.append(RadioSetting("pag_abk", "Paging Answer Back",
|
1021
|
RadioSettingValueList(opts,
|
1022
|
opts[_settings
|
1023
|
.pag_abk])))
|
1024
|
|
1025
|
# 34 PAG.CDR
|
1026
|
opts = ["%2.2d" % (x) for x in range(1, 50)]
|
1027
|
ctcss.append(RadioSetting("pag_cdr_1", "Receive Page Code 1",
|
1028
|
RadioSettingValueList(opts,
|
1029
|
opts[_settings
|
1030
|
.pag_cdr_1])))
|
1031
|
|
1032
|
ctcss.append(RadioSetting("pag_cdr_2", "Receive Page Code 2",
|
1033
|
RadioSettingValueList(opts,
|
1034
|
opts[_settings
|
1035
|
.pag_cdr_2])))
|
1036
|
|
1037
|
# 35 PAG.CDT
|
1038
|
opts = ["%2.2d" % (x) for x in range(1, 50)]
|
1039
|
ctcss.append(RadioSetting("pag_cdt_1", "Transmit Page Code 1",
|
1040
|
RadioSettingValueList(opts,
|
1041
|
opts[_settings
|
1042
|
.pag_cdt_1])))
|
1043
|
|
1044
|
ctcss.append(RadioSetting("pag_cdt_2", "Transmit Page Code 2",
|
1045
|
RadioSettingValueList(opts,
|
1046
|
opts[_settings
|
1047
|
.pag_cdt_2])))
|
1048
|
|
1049
|
# Common Button Options
|
1050
|
button_opts = ["Squelch Off", "Weather", "Smart Search",
|
1051
|
"Tone Scan", "Scan", "T Call", "ARTS"]
|
1052
|
|
1053
|
# 36 PRG P1
|
1054
|
opts = button_opts + ["DC Volts"]
|
1055
|
switch.append(RadioSetting(
|
1056
|
"prog_p1", "P1 Button",
|
1057
|
RadioSettingValueList(opts, opts[_settings.prog_p1])))
|
1058
|
|
1059
|
# 37 PRG P2
|
1060
|
opts = button_opts + ["Dimmer"]
|
1061
|
switch.append(RadioSetting(
|
1062
|
"prog_p2", "P2 Button",
|
1063
|
RadioSettingValueList(opts, opts[_settings.prog_p2])))
|
1064
|
|
1065
|
# 38 PRG P3
|
1066
|
opts = button_opts + ["Mic Gain"]
|
1067
|
switch.append(RadioSetting(
|
1068
|
"prog_p3", "P3 Button",
|
1069
|
RadioSettingValueList(opts, opts[_settings.prog_p3])))
|
1070
|
|
1071
|
# 39 PRG P4
|
1072
|
opts = button_opts + ["Skip"]
|
1073
|
switch.append(RadioSetting(
|
1074
|
"prog_p4", "P4 Button",
|
1075
|
RadioSettingValueList(opts, opts[_settings.prog_p4])))
|
1076
|
|
1077
|
# 40 PSWD
|
1078
|
password = ""
|
1079
|
for i in _passwd:
|
1080
|
if i != 0xFF:
|
1081
|
try:
|
1082
|
password += CHARSET[i]
|
1083
|
except IndexError:
|
1084
|
LOG.debug("unknown char index in password: %x " % (i))
|
1085
|
|
1086
|
val = RadioSettingValueString(0, 4, password, True)
|
1087
|
val.set_charset(CHARSET[0:15] + "abcdef ")
|
1088
|
rs = RadioSetting("passwd", "Password", val)
|
1089
|
|
1090
|
def apply_password(s):
|
1091
|
str = s.value.get_value().upper().rstrip()
|
1092
|
mval = ""
|
1093
|
mval = [chr(CHARSET.index(x)) for x in str]
|
1094
|
for x in range(len(mval), 4):
|
1095
|
mval.append(chr(0xff))
|
1096
|
for x in range(0, 4):
|
1097
|
_passwd[x] = ord(mval[x])
|
1098
|
rs.set_apply_callback(apply_password)
|
1099
|
misc.append(rs)
|
1100
|
|
1101
|
# 41 RESUME
|
1102
|
opts = ["3 Sec", "5 Sec", "10 Sec", "Busy", "Hold"]
|
1103
|
scan.append(RadioSetting("resume", "Scan Resume Mode",
|
1104
|
RadioSettingValueList(opts, opts[_settings.resume])))
|
1105
|
|
1106
|
# 42 RF.SQL
|
1107
|
opts = ["Off"] + ["S-%d" % (x) for x in range(1, 10)]
|
1108
|
misc.append(RadioSetting("rf_sql", "RF Squelch Threshold",
|
1109
|
RadioSettingValueList(opts, opts[_settings.rf_sql])))
|
1110
|
|
1111
|
# 43 RPT - per channel attribute, nothing to do here
|
1112
|
|
1113
|
# 44 RVRT
|
1114
|
opts = ["Off", "On"]
|
1115
|
misc.append(RadioSetting("revert", "Priority Revert",
|
1116
|
RadioSettingValueList(opts, opts[_settings.revert])))
|
1117
|
|
1118
|
# 45 S.SRCH
|
1119
|
opts = ["Single", "Continuous"]
|
1120
|
misc.append(RadioSetting("s_search", "Smart Search Sweep Mode",
|
1121
|
RadioSettingValueList(opts, opts[_settings.s_search])))
|
1122
|
|
1123
|
# 46 SHIFT - per channel setting, nothing to do here
|
1124
|
|
1125
|
# 47 SKIP = per channel setting, nothing to do here
|
1126
|
|
1127
|
# 48 SPLIT - per channel attribute, nothing to do here
|
1128
|
|
1129
|
# 49 SQL.TYP - per channel attribute, nothing to do here
|
1130
|
|
1131
|
# 50 STEP - per channel attribute, nothing to do here
|
1132
|
|
1133
|
# 51 TEMP - read-only status, nothing to do here
|
1134
|
|
1135
|
# 52 TN FRQ - per channel attribute, nothing to do here
|
1136
|
|
1137
|
# 53 TOT
|
1138
|
opts = ["Off", "1 Min", "3 Min", "5 Min", "10 Min"]
|
1139
|
misc.append(RadioSetting("tot", "Timeout Timer",
|
1140
|
RadioSettingValueList(opts,
|
1141
|
opts[_settings.tot])))
|
1142
|
|
1143
|
# 54 TS MUT
|
1144
|
opts = ["Off", "On"]
|
1145
|
ctcss.append(RadioSetting("ts_mut", "Tone Search Mute",
|
1146
|
RadioSettingValueList(opts,
|
1147
|
opts[_settings
|
1148
|
.ts_mut])))
|
1149
|
|
1150
|
# 55 TS SPEED
|
1151
|
opts = ["Fast", "Slow"]
|
1152
|
ctcss.append(RadioSetting("ts_speed", "Tone Search Scanner Speed",
|
1153
|
RadioSettingValueList(opts,
|
1154
|
opts[_settings
|
1155
|
.ts_speed])))
|
1156
|
|
1157
|
# 56 VFO.SCN
|
1158
|
opts = ["+/- 1MHz", "+/- 2MHz", "+/-5MHz", "All"]
|
1159
|
scan.append(RadioSetting("vfo_scan", "VFO Scanner Width",
|
1160
|
RadioSettingValueList(opts,
|
1161
|
opts[_settings
|
1162
|
.vfo_scan])))
|
1163
|
|
1164
|
# 57 WX.ALT
|
1165
|
opts = ["Off", "On"]
|
1166
|
misc.append(RadioSetting("wx_alert", "Weather Alert Scan",
|
1167
|
RadioSettingValueList(opts, opts[_settings.wx_alert])))
|
1168
|
|
1169
|
# 58 WX.VOL
|
1170
|
opts = ["Normal", "Maximum"]
|
1171
|
misc.append(RadioSetting("wx_vol_max", "Weather Alert Volume",
|
1172
|
RadioSettingValueList(opts, opts[_settings.wx_vol_max])))
|
1173
|
|
1174
|
# 59 W/N DV - this is a per-channel attribute, nothing to do here
|
1175
|
|
1176
|
return setmode
|
1177
|
|
1178
|
def set_settings(self, uisettings):
|
1179
|
_settings = self._memobj.settings
|
1180
|
for element in uisettings:
|
1181
|
if not isinstance(element, RadioSetting):
|
1182
|
self.set_settings(element)
|
1183
|
continue
|
1184
|
if not element.changed():
|
1185
|
continue
|
1186
|
|
1187
|
try:
|
1188
|
name = element.get_name()
|
1189
|
value = element.value
|
1190
|
|
1191
|
if element.has_apply_callback():
|
1192
|
LOG.debug("Using apply callback")
|
1193
|
element.run_apply_callback()
|
1194
|
else:
|
1195
|
obj = getattr(_settings, name)
|
1196
|
setattr(_settings, name, value)
|
1197
|
|
1198
|
LOG.debug("Setting %s: %s" % (name, value))
|
1199
|
except Exception, e:
|
1200
|
LOG.debug(element.get_name())
|
1201
|
raise
|
1202
|
|
1203
|
def get_bank_model(self):
|
1204
|
return FT2900BankModel(self)
|
1205
|
|
1206
|
@classmethod
|
1207
|
def match_model(cls, filedata, filename):
|
1208
|
return len(filedata) == cls._memsize
|
1209
|
|
1210
|
@classmethod
|
1211
|
def get_prompts(cls):
|
1212
|
rp = chirp_common.RadioPrompts()
|
1213
|
rp.pre_download = _(dedent("""\
|
1214
|
1. Turn Radio off.
|
1215
|
2. Connect data cable.
|
1216
|
3. While holding "A/N LOW" button, turn radio on.
|
1217
|
4. <b>After clicking OK</b>, press "SET MHz" to send image."""))
|
1218
|
rp.pre_upload = _(dedent("""\
|
1219
|
1. Turn Radio off.
|
1220
|
2. Connect data cable.
|
1221
|
3. While holding "A/N LOW" button, turn radio on.
|
1222
|
4. Press "MW D/MR" to receive image.
|
1223
|
5. Make sure display says "-WAIT-" (see note below if not)
|
1224
|
6. Click OK to dismiss this dialog and start transfer.
|
1225
|
|
1226
|
Note: if you don't see "-WAIT-" at step 5, try cycling
|
1227
|
power and pressing and holding red "*L" button to unlock
|
1228
|
radio, then start back at step 1."""))
|
1229
|
return rp
|
1230
|
|
1231
|
|
1232
|
# the FT2900E is the European version of the radio, almost identical
|
1233
|
# to the R (USA) version, except for the model number and ID Block. We
|
1234
|
# create and register a class for it, with only the needed overrides
|
1235
|
# NOTE: Disabled until detection is fixed
|
1236
|
#@directory.register
|
1237
|
class FT2900ERadio(FT2900Radio):
|
1238
|
"""Yaesu FT-2900E"""
|
1239
|
MODEL = "FT-2900E/1900E"
|
1240
|
VARIANT = "E"
|
1241
|
IDBLOCK = "\x56\x43\x32\x33\x00\x02\x41\x02\x01\x01"
|
1242
|
#MODDED BIT STARTS HERE
|
1243
|
# the FT2900Mod is a version of the radio that has been modified to
|
1244
|
# allow transmit on a greater range of frequencies. It is almost
|
1245
|
# identical to the standard version, except for the model number and
|
1246
|
# ID Block. We create and register a class for it, with only the
|
1247
|
# needed overrides
|
1248
|
@directory.register
|
1249
|
class FT2900ModRadio(FT2900Radio):
|
1250
|
"""Yaesu FT-2900Mod"""
|
1251
|
MODEL = "FT-2900/1900 (Modded)"
|
1252
|
VARIANT = "Opened Xmit"
|
1253
|
IDBLOCK = "\x56\x43\x32\x33\x00\x02\xc7\x01\x01\x01"
|
1254
|
|