Project

General

Profile

Bug #631 » anytone.py

Dan Smith, 02/28/2013 05:34 PM

 
1
# Copyright 2013 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 os
17
import struct
18

    
19
from chirp import bitwise
20
from chirp import chirp_common
21
from chirp import directory
22
from chirp import errors
23
from chirp import memmap
24
from chirp import util
25

    
26
mem_format = """
27
#seekto 0x0100;
28
struct {
29
  u8 even_unknown:2,
30
     even_pskip:1,
31
     even_skip:1,
32
     odd_unknown:2,
33
     odd_pskip:1,
34
     odd_skip:1;
35
} flags[379];
36

    
37
struct memory {
38
  bbcd freq[4];
39
  bbcd offset[4];
40
  u8 unknownA:4,
41
     tune_step:4;
42
  u8 rxdcsextra:1,
43
     txdcsextra:1,
44
     rxinv:1,
45
     txinv:1,
46
     channel_width:2,
47
     unknownB:2;
48
  u8 unknown8:3,
49
     is_am:1,
50
     power:2,
51
     duplex:2;
52
  u8 unknown4:4,
53
     rxtmode:2,
54
     txtmode:2;
55
  u8 unknown5:2,
56
     txtone:6;
57
  u8 unknown6:2,
58
     rxtone:6;
59
  u8 txcode;
60
  u8 rxcode;
61
  u8 unknown7[2];
62
  u8 unknown2[5];
63
  char name[7];
64
  u8 unknownZ[2];
65
};
66
#seekto 0x2000;
67
struct memory memory[758];
68
"""
69

    
70
class FlagObj(object):
71
    def __init__(self, flagobj, which):
72
        self._flagobj = flagobj
73
        self._which = which
74

    
75
    def _get(self, flag):
76
        return getattr(self._flagobj, "%s_%s" % (self._which, flag))
77

    
78
    def _set(self, flag, value):
79
        return setattr(self._flagobj, "%s_%s" % (self._which, flag), value)
80

    
81
    def get_skip(self):
82
        return self._get("skip")
83

    
84
    def set_skip(self, value):
85
        self._set("skip", value)
86

    
87
    skip = property(get_skip, set_skip)
88

    
89
    def get_pskip(self):
90
        return self._get("pskip")
91

    
92
    def set_pskip(self, value):
93
        self._set("pskip", value)
94

    
95
    pskip = property(get_pskip, set_pskip)
96

    
97
    def set(self):
98
        self._set("unknown", 3)
99
        self._set("skip", 1)
100
        self._set("pskip", 1)
101

    
102
    def clear(self):
103
        self._set("unknown", 0)
104
        self._set("skip", 0)
105
        self._set("pskip", 0)
106

    
107
    def get(self):
108
        return (self._get("unknown") << 2 |
109
                self._get("skip") << 1 |
110
                self._get("pskip"))
111

    
112
    def __repr__(self):
113
        return repr(self._flagobj)
114

    
115
def _is_loc_used(memobj, loc):
116
    return memobj.flags[loc / 2].get_raw() != "\xFF"
117

    
118
def _addr_to_loc(addr):
119
    return (addr - 0x2000) / 32
120

    
121
def _should_send_addr(memobj, addr):
122
    if addr < 0x2000 or addr >= 0x7EC0:
123
        return True
124
    else:
125
        return _is_loc_used(memobj, _addr_to_loc(addr))
126

    
127
def _debug(string):
128
    if "CHIRP_DEBUG" in os.environ or True:
129
        print string
130

    
131
def _echo_write(radio, data):
132
    try:
133
        radio.pipe.write(data)
134
        radio.pipe.read(len(data))
135
    except Exception, e:
136
        print "Error writing to radio: %s" % e
137
        raise errors.RadioError("Unable to write to radio")
138

    
139
def _read(radio, length):
140
    try:
141
        data = radio.pipe.read(length)
142
    except Exception, e:
143
        print "Error reading from radio: %s" % e
144
        raise errors.RadioError("Unable to read from radio")
145

    
146
    if len(data) != length:
147
        print "Short read from radio (%i, expected %i)" % (len(data),
148
                                                           length)
149
        print util.hexprint(data)
150
        raise errors.RadioError("Short read from radio")
151
    return data
152

    
153
def _ident(radio):
154
    radio.pipe.setTimeout(1)
155
    _echo_write(radio, "PROGRAM")
156
    response = radio.pipe.read(3)
157
    if response != "QX\x06":
158
        print "Response was:\n%s" % util.hexprint(response)
159
        raise errors.RadioError("Unsupported model")
160
    _echo_write(radio, "\x02")
161
    response = radio.pipe.read(16)
162
    _debug(util.hexprint(response))
163
    if response[1:8] != "QX588UV":
164
        print "Response was:\n%s" % util.hexprint(response)
165
        raise errors.RadioError("Unsupported model")
166

    
167
def _finish(radio):
168
    endframe = "\x45\x4E\x44"
169
    _echo_write(radio, endframe)
170
    result = radio.pipe.read(1)
171
    if result != "\x06":
172
        print "Got:\n%s" % util.hexprint(result)
173
        raise errors.RadioError("Radio did not finish cleanly")
174

    
175
def _checksum(data):
176
    cs = 0
177
    for byte in data:
178
        cs += ord(byte)
179
    return cs % 256
180

    
181
def _send(radio, cmd, addr, length, data=None):
182
    frame = struct.pack(">cHb", cmd, addr, length)
183
    if data:
184
        frame += data
185
        frame += chr(_checksum(frame[1:]))
186
        frame += "\x06"
187
    _echo_write(radio, frame)
188
    _debug("Sent:\n%s" % util.hexprint(frame))
189
    if data:
190
        result = radio.pipe.read(1)
191
        if result != "\x06":
192
            print "Ack was: %s" % repr(result)
193
            raise errors.RadioError("Radio did not accept block at %04x" % addr)
194
        return
195
    result = _read(radio, length + 6)
196
    _debug("Got:\n%s" % util.hexprint(result))
197
    header = result[0:4]
198
    data = result[4:-2]
199
    ack = result[-1]
200
    if ack != "\x06":
201
        print "Ack was: %s" % repr(ack)
202
        raise errors.RadioError("Radio NAK'd block at %04x" % addr)
203
    _cmd, _addr, _length = struct.unpack(">cHb", header)
204
    if _addr != addr or _length != _length:
205
        print "Expected/Received:"
206
        print " Length: %02x/%02x" % (length, _length)
207
        print " Addr: %04x/%04x" % (addr, _addr)
208
        raise errors.RadioError("Radio send unexpected block")
209
    cs = _checksum(result[1:-2])
210
    if cs != ord(result[-2]):
211
        print "Calculated: %02x" % cs
212
        print "Actual:     %02x" % ord(result[-2])
213
        raise errors.RadioError("Block at 0x%04x failed checksum" % addr)
214
    return data
215

    
216
def _download(radio):
217
    _ident(radio)
218

    
219
    memobj = None
220

    
221
    data = ""
222
    for start, end in radio._ranges:
223
        for addr in range(start, end, 0x10):
224
            print "Getting %04x" % addr
225
            if memobj is not None and not _should_send_addr(memobj, addr):
226
                block = "\xFF" * 0x10
227
            else:
228
                block = _send(radio, 'R', addr, 0x10)
229
            data += block
230

    
231
            status = chirp_common.Status()
232
            status.cur = len(data)
233
            status.max = end
234
            status.msg = "Cloning from radio"
235
            radio.status_fn(status)
236

    
237
            #if addr == 0x19F0:
238
            #    memobj = bitwise.parse(mem_format, data)
239

    
240
    _finish(radio)
241

    
242
    return memmap.MemoryMap(data)
243

    
244
def _upload(radio):
245
    _ident(radio)
246

    
247
    for start, end in radio._ranges:
248
        for addr in range(start, end, 0x10):
249
            if addr < 0x0100:
250
                continue
251
            if not _should_send_addr(radio._memobj, addr):
252
                continue
253
            block = radio._mmap[addr:addr + 0x10]
254
            _send(radio, 'W', addr, len(block), block)
255

    
256
            status = chirp_common.Status()
257
            status.cur = addr
258
            status.max = end
259
            status.msg = "Cloning to radio"
260
            radio.status_fn(status)
261

    
262
    _finish(radio)
263

    
264
TONES = [62.5] + list(chirp_common.TONES)
265
TMODES = ['', 'Tone', 'DTCS']
266
DUPLEXES = ['', '-', '+']
267
MODES = ["FM", "FM", "NFM"]
268

    
269
@directory.register
270
class AnyTone5888UVRadio(chirp_common.CloneModeRadio,
271
                         chirp_common.ExperimentalRadio):
272
    """AnyTone 5888UV"""
273
    VENDOR = "AnyTone"
274
    MODEL = "5888UV"
275
    BAUD_RATE = 9600
276

    
277
    # May try to mirror the OEM behavior later
278
    _ranges = [
279
        (0x0000, 0x8000),
280
        ]
281

    
282
    @classmethod
283
    def get_experimental_warning(cls):
284
        return "FOO"
285

    
286
    def get_features(self):
287
        rf = chirp_common.RadioFeatures()
288
        rf.has_bank = False
289
        rf.has_cross = True
290
        rf.has_tuning_step = False
291
        rf.has_rx_dtcs = True
292
        rf.valid_skips = ["", "S", "P"]
293
        rf.valid_modes = ["FM", "NFM", "AM"]
294
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
295
        rf.valid_cross_modes = ['Tone->DTCS', 'DTCS->Tone',
296
                                '->Tone', '->DTCS', 'Tone->Tone']
297
        rf.valid_dtcs_codes = chirp_common.ALL_DTCS_CODES
298
        rf.valid_bands = [(136000000, 500000000)]
299
        rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC
300
        rf.valid_name_length = 7
301
        rf.memory_bounds = (1, 758)
302
        return rf
303

    
304
    def sync_in(self):
305
        self._mmap = _download(self)
306
        self.process_mmap()
307

    
308
    def sync_out(self):
309
        _upload(self)
310

    
311
    def process_mmap(self):
312
        self._memobj = bitwise.parse(mem_format, self._mmap)
313

    
314
    def _get_memobjs(self, number):
315
        number -= 1
316
        _mem = self._memobj.memory[number]
317
        _flg = FlagObj(self._memobj.flags[number / 2],
318
                       number % 2 and "even" or "odd")
319
        return _mem, _flg
320

    
321
    def _get_dcs_index(self, _mem, which):
322
        base = getattr(_mem, '%scode' % which)
323
        extra = getattr(_mem, '%sdcsextra' % which)
324
        return (int(extra) << 8) | int(base)
325

    
326
    def _set_dcs_index(self, _mem, which, index):
327
        base = getattr(_mem, '%scode' % which)
328
        extra = getattr(_mem, '%sdcsextra' % which)
329
        base.set_value(index & 0xFF)
330
        extra.set_value(index >> 8)
331

    
332
    def get_raw_memory(self, number):
333
        _mem, _flg = self._get_memobjs(number)
334
        return repr(_mem) + repr(_flg)
335

    
336
    def get_memory(self, number):
337
        _mem, _flg = self._get_memobjs(number)
338
        mem = chirp_common.Memory()
339
        mem.number = number
340

    
341
        if _flg.get() == 0x0F:
342
            mem.empty = True
343
            return mem
344

    
345
        mem.freq = int(_mem.freq) * 100
346
        mem.offset = int(_mem.offset) * 100
347
        mem.name = str(_mem.name).rstrip()
348
        mem.duplex = DUPLEXES[_mem.duplex]
349
        mem.mode = _mem.is_am and "AM" or MODES[_mem.channel_width]
350

    
351
        rxtone = txtone = None
352
        rxmode = TMODES[_mem.rxtmode]
353
        txmode = TMODES[_mem.txtmode]
354
        if txmode == "Tone":
355
            txtone = TONES[_mem.txtone]
356
        elif txmode == "DTCS":
357
            txtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(_mem,
358
                                                                     'tx')]
359
        if rxmode == "Tone":
360
            rxtone = TONES[_mem.rxtone]
361
        elif rxmode == "DTCS":
362
            rxtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(_mem,
363
                                                                     'rx')]
364

    
365
        rxpol = _mem.rxinv and "R" or "N"
366
        txpol = _mem.txinv and "R" or "N"
367

    
368
        chirp_common.split_tone_decode(mem,
369
                                       (txmode, txtone, txpol),
370
                                       (rxmode, rxtone, rxpol))
371

    
372
        mem.skip = _flg.get_skip() and "S" or _flg.get_pskip() and "P" or ""
373

    
374
        return mem
375

    
376
    def set_memory(self, mem):
377
        _mem, _flg = self._get_memobjs(mem.number)
378
        if mem.empty:
379
            _flg.set()
380
            return
381
        _flg.clear()
382

    
383
        _mem.freq = mem.freq / 100
384
        _mem.offset = mem.offset / 100
385
        _mem.name = mem.name.ljust(7)
386
        _mem.is_am = mem.mode == "AM"
387
        _mem.duplex = DUPLEXES.index(mem.duplex)
388

    
389
        try:
390
            _mem.channel_width = MODES.index(mem.mode)
391
        except ValueError:
392
            _mem.channel_width = 0
393

    
394
        ((txmode, txtone, txpol),
395
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
396

    
397
        _mem.txtmode = TMODES.index(txmode)
398
        _mem.rxtmode = TMODES.index(rxmode)
399
        if txmode == "Tone":
400
            _mem.txtone = TONES.index(txtone)
401
        elif txmode == "DTCS":
402
            self._set_dcs_index(_mem, 'tx',
403
                                chirp_common.ALL_DTCS_CODES.index(txtone))
404
        if rxmode == "Tone":
405
            _mem.rxtone = TONES.index(rxtone)
406
        elif rxmode == "DTCS":
407
            self._set_dcs_index(_mem, 'rx',
408
                                chirp_common.ALL_DTCS_CODES.index(rxtone))
409

    
410
        _mem.txinv = txpol == "R"
411
        _mem.rxinv = rxpol == "R"
412

    
413
        _flg.set_skip(mem.skip == "S")
414
        _flg.set_pskip(mem.skip == "P")
415

    
416
    @classmethod
417
    def match_model(cls, filedata, filename):
418
        return filedata[0x21:0x28] == "QX588UV"
(7-7/7)