Project

General

Profile

New Model #1003 » icomciv.py

icomciv.py with partial IC-7100 support - Nick Partofthething, 07/12/2015 06:12 PM

 
1

    
2
import struct
3
import logging
4
from chirp.drivers import icf
5
from chirp import chirp_common, util, errors, bitwise, directory
6
from chirp.memmap import MemoryMap
7
from chirp.drivers.icf import IcomBankModel
8

    
9
LOG = logging.getLogger(__name__)
10

    
11
MEM_FORMAT = """
12
bbcd number[2];
13
u8   unknown1;
14
lbcd freq[5];
15
u8   unknown2:5,
16
     mode:3;
17
"""
18
MEM_VFO_FORMAT = """
19
u8   vfo;
20
bbcd number[2];
21
u8   unknown1;
22
lbcd freq[5];
23
u8   unknown2:5,
24
     mode:3;
25
u8   unknown1;
26
u8   unknown2:2,
27
     duplex:2,
28
     unknown3:1,
29
     tmode:3;
30
u8   unknown4;
31
bbcd rtone[2];
32
u8   unknown5;
33
bbcd ctone[2];
34
u8   unknown6[2];
35
bbcd dtcs;
36
u8   unknown[17];
37
char name[9];
38
"""
39
mem_duptone_format = """
40
bbcd number[2];
41
u8   unknown1;
42
lbcd freq[5];
43
u8   unknown2:5,
44
     mode:3;
45
u8   unknown1;
46
u8   unknown2:2,
47
     duplex:2,
48
     unknown3:1,
49
     tmode:3;
50
u8   unknown4;
51
bbcd rtone[2];
52
u8   unknown5;
53
bbcd ctone[2];
54
u8   unknown6[2];
55
bbcd dtcs;
56
u8   unknown[11];
57
char name[9];
58
"""
59

    
60
MEM_IC7100_FORMAT = """
61
u8   bank;                 // 1 bank number
62
bbcd number[2];            // 2,3
63
u8   splitSelect;          // 4 split and select memory settings
64
lbcd freq[5];              // 5-9 operating freq
65
u8   mode;                 // 10 operating mode
66
u8   filter;               // 11 filter
67
u8   dataMode;             // 12 data mode setting (on or off)
68
u8   duplex:4,             // 13 duplex on/-/+
69
     tmode:4;              // 13 tone
70
u8   dsql:4,               // 14 digital squelch
71
     unknown1:4;           // 14 zero
72
bbcd rtone[3];             // 15-17 repeater tone freq
73
bbcd ctone[3];             // 18-20 tone squelch setting
74
u8   dtcsPolarity;         // 21 DTCS polarity
75
u8   unknown2:4,           // 22 zero
76
     firstDtcs:4;          // 22 first digit of DTCS code
77
u8   secondDtcs:4,         // 23 second digit DTCS
78
     thirdDtcs:4;          // 23 third digit DTCS
79
u8   digitalSquelch;       // 24 Digital code squelch setting
80
u8   duplexOffset[3];      // 25-27 duplex offset freq
81
char destCall[8];          // 28-35 destination call sign
82
char accessRepeaterCall[8];// 36-43 access repeater call sign
83
char linkRepeaterCall[8];  // 44-51 gateway/link repeater call sign
84
bbcd duplexSettings[47];   // repeat of 5-51 for duplex
85
char name[16];             // 52-60 Name of station
86
"""
87

    
88

    
89
class Frame:
90
    """Base class for an ICF frame"""
91
    _cmd = 0x00
92
    _sub = 0x00
93

    
94
    def __init__(self):
95
        self._data = ""
96

    
97
    def set_command(self, cmd, sub):
98
        """Set the command number (and optional subcommand)"""
99
        self._cmd = cmd
100
        self._sub = sub
101

    
102
    def get_data(self):
103
        """Return the data payload"""
104
        return self._data
105

    
106
    def set_data(self, data):
107
        """Set the data payload"""
108
        self._data = data
109

    
110
    def send(self, src, dst, serial, willecho=True):
111
        """Send the frame over @serial, using @src and @dst addresses"""
112
        raw = struct.pack("BBBBBB", 0xFE, 0xFE, src, dst, self._cmd, self._sub)
113
        raw += str(self._data) + chr(0xFD)
114

    
115
        LOG.debug("%02x -> %02x (%i):\n%s" %
116
                  (src, dst, len(raw), util.hexprint(raw)))
117

    
118
        serial.write(raw)
119
        if willecho:
120
            echo = serial.read(len(raw))
121
            if echo != raw and echo:
122
                LOG.debug("Echo differed (%i/%i)" % (len(raw), len(echo)))
123
                LOG.debug(util.hexprint(raw))
124
                LOG.debug(util.hexprint(echo))
125

    
126
    def read(self, serial):
127
        """Read the frame from @serial"""
128
        data = ""
129
        while not data.endswith(chr(0xFD)):
130
            char = serial.read(1)
131
            if not char:
132
                LOG.debug("Read %i bytes total" % len(data))
133
                raise errors.RadioError("Timeout")
134
            data += char
135

    
136
        if data == chr(0xFD):
137
            raise errors.RadioError("Radio reported error")
138

    
139
        src, dst = struct.unpack("BB", data[2:4])
140
        LOG.debug("%02x <- %02x:\n%s" % (src, dst, util.hexprint(data)))
141

    
142
        self._cmd = ord(data[4])
143
        self._sub = ord(data[5])
144
        self._data = data[6:-1]
145

    
146
        return src, dst
147

    
148
    def get_obj(self):
149
        raise errors.RadioError("Generic frame has no structure")
150

    
151

    
152
class MemFrame(Frame):
153
    """A memory frame"""
154
    _cmd = 0x1A
155
    _sub = 0x00
156
    _loc = 0
157

    
158
    def set_location(self, loc):
159
        """Set the memory location number"""
160
        self._loc = loc
161
        self._data = struct.pack(">H", int("%04i" % loc, 16))
162

    
163
    def make_empty(self):
164
        """Mark as empty so the radio will erase the memory"""
165
        self._data = struct.pack(">HB", int("%04i" % self._loc, 16), 0xFF)
166

    
167
    def is_empty(self):
168
        """Return True if memory is marked as empty"""
169
        return len(self._data) < 5
170

    
171
    def get_obj(self):
172
        """Return a bitwise parsed object"""
173
        self._data = MemoryMap(str(self._data))  # Make sure we're assignable
174
        return bitwise.parse(MEM_FORMAT, self._data)
175

    
176
    def initialize(self):
177
        """Initialize to sane values"""
178
        self._data = MemoryMap("".join(["\x00"] * (self.get_obj().size() / 8)))
179

    
180

    
181
class MultiVFOMemFrame(MemFrame):
182
    """A memory frame for radios with multiple VFOs"""
183
    def set_location(self, loc, vfo=1):
184
        self._loc = loc
185
        self._data = struct.pack(">BH", vfo, int("%04i" % loc, 16))
186

    
187
    def get_obj(self):
188
        self._data = MemoryMap(str(self._data))  # Make sure we're assignable
189
        return bitwise.parse(MEM_VFO_FORMAT, self._data)
190

    
191
class IC7100MemFrame(MultiVFOMemFrame):
192
    """A memory frame for IC-7100"""
193

    
194
    def get_obj(self):
195
        self._data = MemoryMap(str(self._data))  # Make sure we're assignable
196
        return bitwise.parse(MEM_IC7100_FORMAT, self._data)
197

    
198

    
199
class DupToneMemFrame(MemFrame):
200
    def get_obj(self):
201
        self._data = MemoryMap(str(self._data))
202
        return bitwise.parse(mem_duptone_format, self._data)
203

    
204

    
205
class IcomCIVRadio(icf.IcomLiveRadio):
206
    """Base class for ICOM CIV-based radios"""
207
    BAUD_RATE = 19200
208
    MODEL = "CIV Radio"
209
    _model = "\x00"
210
    _template = 0
211

    
212
    def _send_frame(self, frame):
213
        return frame.send(ord(self._model), 0xE0, self.pipe,
214
                          willecho=self._willecho)
215

    
216
    def _recv_frame(self, frame=None):
217
        if not frame:
218
            frame = Frame()
219
        frame.read(self.pipe)
220
        return frame
221

    
222
    def _initialize(self):
223
        pass
224

    
225
    def _detect_echo(self):
226
        echo_test = "\xfe\xfe\xe0\xe0\xfa\xfd"
227
        self.pipe.write(echo_test)
228
        resp = self.pipe.read(6)
229
        LOG.debug("Echo:\n%s" % util.hexprint(resp))
230
        return resp == echo_test
231

    
232
    def __init__(self, *args, **kwargs):
233
        icf.IcomLiveRadio.__init__(self, *args, **kwargs)
234

    
235
        self._classes = {
236
            "mem": MemFrame,
237
            }
238

    
239
        if self.pipe:
240
            self._willecho = self._detect_echo()
241
            LOG.debug("Interface echo: %s" % self._willecho)
242
            self.pipe.setTimeout(1)
243

    
244
        # f = Frame()
245
        # f.set_command(0x19, 0x00)
246
        # self._send_frame(f)
247
        #
248
        # res = f.read(self.pipe)
249
        # if res:
250
        #    LOG.debug("Result: %x->%x (%i)" %
251
        #              (res[0], res[1], len(f.get_data())))
252
        #    LOG.debug(util.hexprint(f.get_data()))
253
        #
254
        # self._id = f.get_data()[0]
255
        self._rf = chirp_common.RadioFeatures()
256

    
257
        self._initialize()
258

    
259
    def get_features(self):
260
        return self._rf
261

    
262
    def _get_template_memory(self):
263
        f = self._classes["mem"]()
264
        f.set_location(self._template)
265
        self._send_frame(f)
266
        f.read(self.pipe)
267
        return f
268

    
269
    def get_raw_memory(self, number):
270
        f = self._classes["mem"]()
271
        f.set_location(number)
272
        self._send_frame(f)
273
        f.read(self.pipe)
274
        return repr(f.get_obj())
275

    
276
    def get_memory(self, number):
277
        LOG.debug("Getting %i" % number)
278
        f = self._classes["mem"]()
279
        f.set_location(number)
280
        self._send_frame(f)
281

    
282
        mem = chirp_common.Memory()
283
        mem.number = number
284

    
285
        f = self._recv_frame(f)
286
        if len(f.get_data()) == 0:
287
            raise errors.RadioError("Radio reported error")
288
        if f.get_data() and f.get_data()[-1] == "\xFF":
289
            mem.empty = True
290
            return mem
291

    
292
        memobj = f.get_obj()
293
        LOG.debug(repr(memobj))
294

    
295
        mem.freq = int(memobj.freq)
296
        mem.mode = self._rf.valid_modes[memobj.mode]
297

    
298
        if self._rf.has_name:
299
            mem.name = str(memobj.name).rstrip()
300

    
301
        if self._rf.valid_tmodes:
302
            mem.tmode = self._rf.valid_tmodes[memobj.tmode]
303

    
304
        if self._rf.has_dtcs:
305
            # FIXME
306
            mem.dtcs = bitwise.bcd_to_int([memobj.dtcs])
307

    
308
        if "Tone" in self._rf.valid_tmodes:
309
            mem.rtone = int(memobj.rtone) / 10.0
310

    
311
        if "TSQL" in self._rf.valid_tmodes and self._rf.has_ctone:
312
            mem.ctone = int(memobj.ctone) / 10.0
313

    
314
        if self._rf.valid_duplexes:
315
            mem.duplex = self._rf.valid_duplexes[memobj.duplex]
316

    
317
        return mem
318

    
319
    def set_memory(self, mem):
320
        f = self._get_template_memory()
321
        if mem.empty:
322
            f.set_location(mem.number)
323
            f.make_empty()
324
            self._send_frame(f)
325
            return
326

    
327
        # f.set_data(MemoryMap(self.get_raw_memory(mem.number)))
328
        # f.initialize()
329

    
330
        memobj = f.get_obj()
331
        memobj.number = mem.number
332
        memobj.freq = int(mem.freq)
333
        memobj.mode = self._rf.valid_modes.index(mem.mode)
334
        if self._rf.has_name:
335
            memobj.name = mem.name.ljust(self._rf.valid_name_length)[:self._rf.valid_name_length]
336

    
337
        if self._rf.valid_tmodes:
338
            memobj.tmode = self._rf.valid_tmodes.index(mem.tmode)
339

    
340
        if self._rf.valid_duplexes:
341
            memobj.duplex = self._rf.valid_duplexes.index(mem.duplex)
342

    
343
        if self._rf.has_ctone:
344
            memobj.ctone = int(mem.ctone * 10)
345
            memobj.rtone = int(mem.rtone * 10)
346

    
347
        LOG.debug(repr(memobj))
348
        self._send_frame(f)
349

    
350
        f = self._recv_frame()
351
        LOG.debug("Result:\n%s" % util.hexprint(f.get_data()))
352

    
353

    
354
@directory.register
355
class Icom7200Radio(IcomCIVRadio):
356
    """Icom IC-7200"""
357
    MODEL = "7200"
358
    _model = "\x76"
359
    _template = 201
360

    
361
    def _initialize(self):
362
        self._rf.has_bank = False
363
        self._rf.has_dtcs_polarity = False
364
        self._rf.has_dtcs = False
365
        self._rf.has_ctone = False
366
        self._rf.has_offset = False
367
        self._rf.has_name = False
368
        self._rf.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY"]
369
        self._rf.valid_tmodes = []
370
        self._rf.valid_duplexes = []
371
        self._rf.valid_bands = [(1800000, 59000000)]
372
        self._rf.valid_tuning_steps = []
373
        self._rf.valid_skips = []
374
        self._rf.memory_bounds = (1, 200)
375

    
376

    
377
@directory.register
378
class Icom7000Radio(IcomCIVRadio):
379
    """Icom IC-7000"""
380
    MODEL = "7000"
381
    _model = "\x70"
382
    _template = 102
383

    
384
    def _initialize(self):
385
        self._classes["mem"] = MultiVFOMemFrame
386
        self._rf.has_bank = False
387
        self._rf.has_dtcs_polarity = True
388
        self._rf.has_dtcs = True
389
        self._rf.has_ctone = True
390
        self._rf.has_offset = False
391
        self._rf.has_name = True
392
        self._rf.has_tuning_step = False
393
        self._rf.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY", "FM"]
394
        self._rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
395
        self._rf.valid_duplexes = ["", "-", "+"]
396
        self._rf.valid_bands = [(30000, 199999999), (400000000, 470000000)]
397
        self._rf.valid_tuning_steps = []
398
        self._rf.valid_skips = []
399
        self._rf.valid_name_length = 9
400
        self._rf.valid_characters = chirp_common.CHARSET_ASCII
401
        self._rf.memory_bounds = (1, 99)
402

    
403
@directory.register
404
class Icom7100Radio(IcomCIVRadio):
405
    """Icom IC-7100"""
406
    MODEL = "7100"
407
    _model = "\x88"
408
    _template = 102
409
    _num_banks = 5
410

    
411
    def _initialize(self):
412
        self._classes["mem"] = IC7100MemFrame
413
        self._rf.has_bank = True
414
        self._rf.has_bank_index = False
415
        self._rf.has_bank_names = False
416
        self._rf.has_dtcs_polarity = False
417
        self._rf.has_dtcs = False
418
        self._rf.has_ctone = True
419
        self._rf.has_offset = False
420
        self._rf.has_name = True
421
        self._rf.has_tuning_step = False
422
        self._rf.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY", "FM", "WFM", "CW-R", "RTTY-R"]
423
        self._rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
424
        self._rf.valid_duplexes = ["", "-", "+"]
425
        self._rf.valid_bands = [(30000, 199999999), (400000000, 470000000)]
426
        self._rf.valid_tuning_steps = []
427
        self._rf.valid_skips = []
428
        self._rf.valid_name_length = 16
429
        self._rf.valid_characters = chirp_common.CHARSET_ASCII
430
        self._rf.memory_bounds = (1, 99)
431

    
432
    def get_bank_model(self):
433
        rf = self.get_features()
434
        if rf.has_bank:
435
            if not rf.has_bank_index:
436
                return IcomBankModel(self)
437

    
438

    
439
@directory.register
440
class Icom746Radio(IcomCIVRadio):
441
    """Icom IC-746"""
442
    MODEL = "746"
443
    BAUD_RATE = 9600
444
    _model = "\x56"
445
    _template = 102
446

    
447
    def _initialize(self):
448
        self._classes["mem"] = DupToneMemFrame
449
        self._rf.has_bank = False
450
        self._rf.has_dtcs_polarity = False
451
        self._rf.has_dtcs = False
452
        self._rf.has_ctone = True
453
        self._rf.has_offset = False
454
        self._rf.has_name = True
455
        self._rf.has_tuning_step = False
456
        self._rf.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY", "FM"]
457
        self._rf.valid_tmodes = ["", "Tone", "TSQL"]
458
        self._rf.valid_duplexes = ["", "-", "+"]
459
        self._rf.valid_bands = [(30000, 199999999)]
460
        self._rf.valid_tuning_steps = []
461
        self._rf.valid_skips = []
462
        self._rf.valid_name_length = 9
463
        self._rf.valid_characters = chirp_common.CHARSET_ASCII
464
        self._rf.memory_bounds = (1, 99)
465

    
466
CIV_MODELS = {
467
    (0x76, 0xE0): Icom7200Radio,
468
    (0x70, 0xE0): Icom7000Radio,
469
    (0x46, 0xE0): Icom746Radio,
470
    (0x88, 0xE0): Icom7100Radio
471
}
472

    
473

    
474
def probe_model(ser):
475
    """Probe the radio attatched to @ser for its model"""
476
    f = Frame()
477
    f.set_command(0x19, 0x00)
478

    
479
    for model, controller in CIV_MODELS.keys():
480
        f.send(model, controller, ser)
481
        try:
482
            f.read(ser)
483
        except errors.RadioError:
484
            continue
485

    
486
        if len(f.get_data()) == 1:
487
            model = ord(f.get_data()[0])
488
            return CIV_MODELS[(model, controller)]
489

    
490
        if f.get_data():
491
            LOG.debug("Got data, but not 1 byte:")
492
            LOG.debug(util.hexprint(f.get_data()))
493
            raise errors.RadioError("Unknown response")
494

    
495
    raise errors.RadioError("Unsupported model")
(1-1/2)