Project

General

Profile

Bug #291 » ft7800.py

Tom Hayward, 10/09/2012 02:31 PM

 
1
# Copyright 2010 Dan Smith <dsmith@danplanet.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 3 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
from chirp import chirp_common, yaesu_clone, memmap, directory
18
from chirp import bitwise, errors
19

    
20
ACK = chr(0x06)
21

    
22
MEM_FORMAT = """
23
#seekto 0x04C8;
24
struct {
25
  u8 used:1,
26
     unknown1:1,
27
     mode:2,
28
     unknown2:1,
29
     duplex:3;
30
  bbcd freq[3];
31
  u8 unknown3:1,
32
     tune_step:3,
33
     unknown5:2,
34
     tmode:2;
35
  bbcd split[3];
36
  u8 power:2,
37
     tone:6;
38
  u8 unknown6:1,
39
     dtcs:7;
40
  u8 unknown7[2];
41
  u8 offset;
42
  u8 unknown9[3];
43
} memory[1000];
44

    
45
#seekto 0x4988;
46
struct {
47
  char name[6];
48
  u8 enabled:1,
49
     unknown1:7;
50
  u8 used:1,
51
     unknown2:7;
52
} names[1000];
53

    
54
#seekto 0x6c48;
55
struct {
56
   u32 bitmap[32];
57
} bank_channels[20];
58

    
59
#seekto 0x7648;
60
struct {
61
  u8 skip0:2,
62
     skip1:2,
63
     skip2:2,
64
     skip3:2;
65
} flags[250];
66

    
67
#seekto 0x7B48;
68
u8 checksum;
69
"""
70

    
71
MODES = ["FM", "AM", "NFM"]
72
TMODES = ["", "Tone", "TSQL", "DTCS"]
73
DUPLEX = ["", "", "-", "+", "split"]
74
STEPS =  [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0]
75
SKIPS = ["", "S", "P", ""]
76

    
77
CHARSET = ["%i" % int(x) for x in range(0, 10)] + \
78
    [chr(x) for x in range(ord("A"), ord("Z")+1)] + \
79
    list(" " * 10) + \
80
    list("*+,- /|      [ ] _") + \
81
    list("\x00" * 100)
82

    
83
POWER_LEVELS_VHF = [chirp_common.PowerLevel("Hi", watts=50),
84
                    chirp_common.PowerLevel("Mid1", watts=20),
85
                    chirp_common.PowerLevel("Mid2", watts=10),
86
                    chirp_common.PowerLevel("Low", watts=5)]
87

    
88
POWER_LEVELS_UHF = [chirp_common.PowerLevel("Hi", watts=35),
89
                    chirp_common.PowerLevel("Mid1", watts=20),
90
                    chirp_common.PowerLevel("Mid2", watts=10),
91
                    chirp_common.PowerLevel("Low", watts=5)]
92

    
93
def _send(ser, data):
94
    for i in data:
95
        ser.write(i)
96
        time.sleep(0.002)
97
    echo = ser.read(len(data))
98
    if echo != data:
99
        raise errors.RadioError("Error reading echo (Bad cable?)")
100

    
101
def _download(radio):
102
    data = ""
103

    
104
    chunk = ""
105
    for i in range(0, 30):
106
        chunk += radio.pipe.read(radio._block_lengths[0])
107
        if chunk:
108
            break
109

    
110
    if len(chunk) != radio._block_lengths[0]:
111
        raise Exception("Failed to read header (%i)" % len(chunk))
112
    data += chunk
113

    
114
    _send(radio.pipe, ACK)
115

    
116
    for i in range(0, radio._block_lengths[1], 64):
117
        chunk = radio.pipe.read(64)
118
        data += chunk
119
        if len(chunk) != 64:
120
            break
121
        time.sleep(0.01)
122
        _send(radio.pipe, ACK)
123
        if radio.status_fn:
124
            status = chirp_common.Status()
125
            status.max = radio.get_memsize()
126
            status.cur = i+len(chunk)
127
            status.msg = "Cloning from radio"
128
            radio.status_fn(status)
129

    
130
    data += radio.pipe.read(1)
131
    _send(radio.pipe, ACK)
132

    
133
    return memmap.MemoryMap(data)
134

    
135
def _upload(radio):
136
    cur = 0
137
    for block in radio._block_lengths:
138
        for _i in range(0, block, 64):
139
            length = min(64, block)
140
            #print "i=%i length=%i range: %i-%i" % (i, length,
141
            #                                       cur, cur+length)
142
            _send(radio.pipe, radio.get_mmap()[cur:cur+length])
143
            if radio.pipe.read(1) != ACK:
144
                raise errors.RadioError("Radio did not ack block at %i" % cur)
145
            cur += length
146
            time.sleep(0.05)
147

    
148
            if radio.status_fn:
149
                status = chirp_common.Status()
150
                status.cur = cur
151
                status.max = radio.get_memsize()
152
                status.msg = "Cloning to radio"
153
                radio.status_fn(status)
154

    
155
def get_freq(rawfreq):
156
    """Decode a frequency that may include a fractional step flag"""
157
    # Ugh.  The 0x80 and 0x40 indicate values to add to get the
158
    # real frequency.  Gross.
159
    if rawfreq > 8000000000:
160
        rawfreq = (rawfreq - 8000000000) + 5000
161

    
162
    if rawfreq > 4000000000:
163
        rawfreq = (rawfreq - 4000000000) + 2500
164

    
165
    return rawfreq
166

    
167
def set_freq(freq, obj, field):
168
    """Encode a frequency with any necessary fractional step flags"""
169
    obj[field] = freq / 10000
170
    if (freq % 1000) == 500:
171
        obj[field][0].set_bits(0x40)
172

    
173
    if (freq % 10000) >= 5000:
174
        obj[field][0].set_bits(0x80)
175
        
176
    return freq
177

    
178
class FTx800Radio(yaesu_clone.YaesuCloneModeRadio):
179
    """Base class for FT-7800,7900,8800,8900 radios"""
180
    BAUD_RATE = 9600
181
    VENDOR = "Yaesu"
182

    
183
    def get_features(self):
184
        rf = chirp_common.RadioFeatures()
185
        rf.memory_bounds = (1, 999)
186
        rf.has_bank = False
187
        rf.has_ctone = False
188
        rf.has_dtcs_polarity = False
189
        rf.valid_modes = MODES
190
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
191
        rf.valid_duplexes = ["", "-", "+", "split"]
192
        rf.valid_tuning_steps = STEPS
193
        rf.valid_bands = [(108000000, 520000000), (700000000, 990000000)]
194
        rf.valid_skips = ["", "S", "P"]
195
        rf.valid_power_levels = POWER_LEVELS_VHF
196
        rf.valid_characters = "".join(CHARSET)
197
        rf.valid_name_length = 6
198
        rf.can_odd_split = True
199
        return rf
200

    
201
    def _checksums(self):
202
        return [ yaesu_clone.YaesuChecksum(0x0000, 0x7B47) ]
203

    
204
    def sync_in(self):
205
        start = time.time()
206
        try:
207
            self._mmap = _download(self)
208
        except errors.RadioError:
209
            raise
210
        except Exception, e:
211
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
212
        print "Download finished in %i seconds" % (time.time() - start)
213
        self.check_checksums()
214
        self.process_mmap()
215

    
216
    def process_mmap(self):
217
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
218

    
219
    def sync_out(self):
220
        self.update_checksums()
221
        start = time.time()
222
        try:
223
            _upload(self)
224
        except errors.RadioError:
225
            raise
226
        except Exception, e:
227
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
228
        print "Upload finished in %i seconds" % (time.time() - start)
229

    
230
    def get_raw_memory(self, number):
231
        return repr(self._memobj.memory[number-1])
232

    
233
    def _get_mem_offset(self, mem, _mem):
234
        if mem.duplex == "split":
235
            return get_freq(int(_mem.split) * 10000)
236
        else:
237
            return (_mem.offset * 5) * 10000
238

    
239
    def _set_mem_offset(self, mem, _mem):
240
        if mem.duplex == "split":
241
            set_freq(mem.offset, _mem, "split")
242
        else:
243
            _mem.offset = (int(mem.offset / 10000) / 5)
244

    
245
    def _get_mem_name(self, mem, _mem):
246
        _nam = self._memobj.names[mem.number - 1]
247

    
248
        name = ""
249
        if _nam.used:
250
            for i in str(_nam.name):
251
                name += CHARSET[ord(i)]
252

    
253
        return name.rstrip()
254

    
255
    def _set_mem_name(self, mem, _mem):
256
        _nam = self._memobj.names[mem.number - 1]
257

    
258
        if mem.name.rstrip():
259
            name = [chr(CHARSET.index(x)) for x in mem.name.ljust(6)[:6]]
260
            _nam.name = "".join(name)
261
            _nam.used = 1
262
            _nam.enabled = 1
263
        else:
264
            _nam.used = 0
265
            _nam.enabled = 0
266

    
267
    def _get_mem_skip(self, mem, _mem):
268
        _flg = self._memobj.flags[(mem.number - 1) / 4]
269
        flgidx = (mem.number - 1) % 4
270
        return SKIPS[_flg["skip%i" % flgidx]]
271

    
272
    def _set_mem_skip(self, mem, _mem):
273
        _flg = self._memobj.flags[(mem.number - 1) / 4]
274
        flgidx = (mem.number - 1) % 4
275
        _flg["skip%i" % flgidx] = SKIPS.index(mem.skip)
276

    
277
    def get_memory(self, number):
278
        _mem = self._memobj.memory[number - 1]
279

    
280
        mem = chirp_common.Memory()
281
        mem.number = number
282
        mem.empty = not _mem.used
283
        if mem.empty:
284
            return mem
285

    
286
        mem.freq = get_freq(int(_mem.freq) * 10000)
287
        mem.rtone = chirp_common.TONES[_mem.tone]
288
        mem.tmode = TMODES[_mem.tmode]
289
        mem.mode = MODES[_mem.mode]
290
        mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
291
        if self.get_features().has_tuning_step:
292
            mem.tuning_step = STEPS[_mem.tune_step]
293
        mem.duplex = DUPLEX[_mem.duplex]
294
        mem.offset = self._get_mem_offset(mem, _mem)
295
        mem.name = self._get_mem_name(mem, _mem)
296

    
297
        if int(mem.freq / 100) == 4:
298
            mem.power = POWER_LEVELS_UHF[_mem.power]
299
        else:
300
            mem.power = POWER_LEVELS_VHF[_mem.power]
301

    
302
        mem.skip = self._get_mem_skip(mem, _mem)
303

    
304
        return mem
305

    
306
    def set_memory(self, mem):
307
        _mem = self._memobj.memory[mem.number - 1]
308

    
309
        _mem.used = int(not mem.empty)
310
        if mem.empty:
311
            return
312

    
313
        set_freq(mem.freq, _mem, "freq")
314
        _mem.tone = chirp_common.TONES.index(mem.rtone)
315
        _mem.tmode = TMODES.index(mem.tmode)
316
        _mem.mode = MODES.index(mem.mode)
317
        _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
318
        if self.get_features().has_tuning_step:
319
            _mem.tune_step = STEPS.index(mem.tuning_step)
320
        _mem.duplex = DUPLEX.index(mem.duplex)
321
        _mem.split = mem.duplex == "split" and int (mem.offset / 10000) or 0
322
        if mem.power:
323
            _mem.power = POWER_LEVELS_VHF.index(mem.power)
324
        else:
325
            _mem.power = 0
326
        _mem.unknown5 = 0 # Make sure we don't leave garbage here
327

    
328
        # NB: Leave offset after mem name for the 8800!
329
        self._set_mem_name(mem, _mem)
330
        self._set_mem_offset(mem, _mem)
331

    
332
        self._set_mem_skip(mem, _mem)
333

    
334
class FT7800BankModel(chirp_common.BankModel):
335
    """Yaesu FT-7800/7900 bank model"""
336
    def get_num_banks(self):
337
        return 20
338

    
339
    def get_banks(self):
340
        banks = []
341
        for i in range(0, 20):
342
            bank = chirp_common.Bank(self, "%i" % i, "BANK-%i" % i)
343
            bank.index = i
344
            banks.append(bank)
345

    
346
        return banks
347

    
348
    def add_memory_to_bank(self, memory, bank):
349
        index = memory.number - 1
350
        _bitmap = self._radio._memobj.bank_channels[bank.index]
351
        ishft = 31 - (index % 32)
352
        _bitmap.bitmap[index / 32] |= (1 << ishft)
353

    
354
    def remove_memory_from_bank(self, memory, bank):
355
        index = memory.number - 1
356
        _bitmap = self._radio._memobj.bank_channels[bank.index]
357
        ishft = 31 - (index % 32)
358
        if not (_bitmap.bitmap[index / 32] & (1 << ishft)):
359
            raise Exception("Memory {num} is " +
360
                            "not in bank {bank}".format(num=memory.number,
361
                                                           bank=bank))
362
        _bitmap.bitmap[index / 32] &= ~(1 << ishft)
363

    
364
    def get_bank_memories(self, bank):
365
        memories = []
366
        for i in range(0, 1000):
367
            _bitmap = self._radio._memobj.bank_channels[bank.index].bitmap[i/32]
368
            ishft = 31 - (i % 32)
369
            if _bitmap & (1 << ishft):
370
                memories.append(self._radio.get_memory(i + 1))
371
        return memories
372

    
373
    def get_memory_banks(self, memory):
374
        banks = []
375
        for bank in self.get_banks():
376
            if memory.number in \
377
                    [x.number for x in self.get_bank_memories(bank)]:
378
                banks.append(bank)
379
        return banks
380

    
381
@directory.register
382
class FT7800Radio(FTx800Radio):
383
    """Yaesu FT-7800"""
384
    MODEL = "FT-7800"
385

    
386
    _model = "AH016"
387
    _memsize = 31561
388

    
389
    def get_bank_model(self):
390
        return FT7800BankModel(self)
391

    
392
    def get_features(self):
393
        rf = FTx800Radio.get_features(self)
394
        rf.has_bank = True
395
        return rf
396

    
397
    def set_memory(self, memory):
398
        if memory.empty:
399
            self._wipe_memory_banks(memory)
400
        FTx800Radio.set_memory(self, memory)
401

    
402
class FT7900Radio(FT7800Radio):
403
    """Yaesu FT-7900"""
404
    MODEL = "FT-7900"
405

    
406
MEM_FORMAT_8800 = """
407
#seekto %s;
408
struct {
409
  u8 used:1,
410
     unknown1:1,
411
     mode:2,
412
     unknown2:1,
413
     duplex:3;
414
  bbcd freq[3];
415
  u8 unknown3:1,
416
     tune_step:3,
417
     power:2,
418
     tmode:2;
419
  bbcd split[3];
420
  u8 nameused:1,
421
     unknown5:1,
422
     tone:6;
423
  u8 namevalid:1,
424
     dtcs:7;
425
  u8 name[6];
426
} memory[500];
427

    
428
#seekto 0x51C8;
429
struct {
430
  u8 skip0:2,
431
     skip1:2,
432
     skip2:2,
433
     skip3:2;
434
} flags[250];
435

    
436
#seekto 0x7B48;
437
u8 checksum;
438
"""
439

    
440
@directory.register
441
class FT8800Radio(FTx800Radio):
442
    """Base class for Yaesu FT-8800"""
443
    MODEL = "FT-8800"
444

    
445
    _model = "AH018"
446
    _memsize = 22217
447

    
448
    _block_lengths = [8, 22208, 1]
449
    _block_size = 64
450

    
451
    _memstart = ""
452

    
453
    def get_features(self):
454
        rf = FTx800Radio.get_features(self)
455
        rf.has_sub_devices = self.VARIANT == ""
456
        rf.memory_bounds = (1, 499)
457
        return rf
458

    
459
    def get_sub_devices(self):
460
        return [FT8800RadioLeft(self._mmap), FT8800RadioRight(self._mmap)]
461

    
462
    def _checksums(self):
463
        return [ yaesu_clone.YaesuChecksum(0x0000, 0x56C7) ]
464

    
465
    def process_mmap(self):
466
        if not self._memstart:
467
            return
468

    
469
        self._memobj = bitwise.parse(MEM_FORMAT_8800 % self._memstart,
470
                                     self._mmap)
471

    
472
    def _get_mem_offset(self, mem, _mem):
473
        if mem.duplex == "split":
474
            return get_freq(int(_mem.split) * 10000)
475

    
476
        # The offset is packed into the upper two bits of the last four
477
        # bytes of the name (?!)
478
        val = 0
479
        for i in _mem.name[2:6]:
480
            val <<= 2
481
            val |= ((i & 0xC0) >> 6)
482

    
483
        return (val * 5) * 10000
484

    
485
    def _set_mem_offset(self, mem, _mem):
486
        if mem.duplex == "split":
487
            set_freq(mem.offset, _mem, "split")
488
            return
489

    
490
        val = int(mem.offset / 10000) / 5
491
        for i in reversed(range(2, 6)):
492
            _mem.name[i] = (_mem.name[i] & 0x3F) | ((val & 0x03) << 6)
493
            val >>= 2
494

    
495
    def _get_mem_name(self, mem, _mem):
496
        name = ""
497
        if _mem.namevalid:
498
            for i in _mem.name:
499
                index = int(i) & 0x3F
500
                if index < len(CHARSET):
501
                    name += CHARSET[index]
502

    
503
        return name.rstrip()
504

    
505
    def _set_mem_name(self, mem, _mem):
506
        _mem.name = [CHARSET.index(x) for x in mem.name.ljust(6)[:6]]
507
        _mem.namevalid = 1
508
        _mem.nameused = bool(mem.name.rstrip())
509

    
510
class FT8800RadioLeft(FT8800Radio):
511
    """Yaesu FT-8800 Left VFO subdevice"""
512
    VARIANT = "Left"
513
    _memstart = "0x0948"
514

    
515
class FT8800RadioRight(FT8800Radio):
516
    """Yaesu FT-8800 Right VFO subdevice"""
517
    VARIANT = "Right"
518
    _memstart = "0x2948"
519

    
520
MEM_FORMAT_8900 = """
521
#seekto 0x0708;
522
struct {
523
  u8 used:1,
524
     skip:2,
525
     sub_used:1,
526
     unknown2:1,
527
     duplex:3;
528
  bbcd freq[3];
529
  u8 mode:2,
530
     nameused:1,
531
     unknown4:1,
532
     power:2,
533
     tmode:2;
534
  bbcd split[3];
535
  u8 unknown5:2,
536
     tone:6;
537
  u8 namevalid:1,
538
     dtcs:7;
539
  u8 name[6];
540
} memory[799];
541

    
542
#seekto 0x51C8;
543
struct {
544
  u8 skip0:2,
545
     skip1:2,
546
     skip2:2,
547
     skip3:2;
548
} flags[400];
549

    
550
#seekto 0x7B48;
551
u8 checksum;
552
"""
553

    
554
@directory.register
555
class FT8900Radio(FT8800Radio):
556
    """Yaesu FT-8900"""
557
    MODEL = "FT-8900"
558

    
559
    _model = "AH008"
560
    _memsize = 14793
561
    _block_lengths = [8, 14784, 1]
562

    
563
    def process_mmap(self):
564
        self._memobj = bitwise.parse(MEM_FORMAT_8900, self._mmap)
565

    
566
    def get_features(self):
567
        rf = FT8800Radio.get_features(self)
568
        rf.has_sub_devices = False
569
        rf.valid_modes = MODES
570
        rf.valid_bands = [( 28000000,  29700000),
571
                          ( 50000000,  54000000),
572
                          (108000000, 180000000),
573
                          (320000000, 480000000),
574
                          (700000000, 985000000)]
575
        rf.memory_bounds = (1, 799)
576
        rf.has_tuning_step = False
577

    
578
        return rf
579

    
580
    def _checksums(self):
581
        return [ yaesu_clone.YaesuChecksum(0x0000, 0x39C7) ]
582

    
583
    def _get_mem_skip(self, mem, _mem):
584
        return SKIPS[_mem.skip]
585

    
586
    def _set_mem_skip(self, mem, _mem):
587
        _mem.skip = SKIPS.index(mem.skip)
588

    
589
    def get_memory(self, number):
590
        mem = FT8800Radio.get_memory(self, number)
591

    
592
        _mem = self._memobj.memory[number - 1]
593

    
594
        return mem
595

    
596
    def set_memory(self, mem):
597
        FT8800Radio.set_memory(self, mem)
598

    
599
        # The 8900 has a bit flag that tells the radio whether or not
600
        # the memory should show up on the sub (right) band
601
        _mem = self._memobj.memory[mem.number - 1]
602
        if mem.freq < 108000000 or mem.freq > 480000000:
603
            _mem.sub_used = 0
604
        else:
605
            _mem.sub_used = 1
606

    
(3-3/4)