Project

General

Profile

Bug #9007 ยป ft2900.py

Jon C, 04/24/2021 12:15 PM

 
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

    
    (1-1/1)