Project

General

Profile

New Model #1343 » polmar-db50m.py

Not working Polmar DB50M *.py file - Robert Elsinga, 01/02/2014 04:52 AM

 
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
import time
19

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

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

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

    
73
class FlagObj(object):
74
    def __init__(self, flagobj, which):
75
        self._flagobj = flagobj
76
        self._which = which
77

    
78
    def _get(self, flag):
79
        return getattr(self._flagobj, "%s_%s" % (self._which, flag))
80

    
81
    def _set(self, flag, value):
82
        return setattr(self._flagobj, "%s_%s" % (self._which, flag), value)
83

    
84
    def get_skip(self):
85
        return self._get("skip")
86

    
87
    def set_skip(self, value):
88
        self._set("skip", value)
89

    
90
    skip = property(get_skip, set_skip)
91

    
92
    def get_pskip(self):
93
        return self._get("pskip")
94

    
95
    def set_pskip(self, value):
96
        self._set("pskip", value)
97

    
98
    pskip = property(get_pskip, set_pskip)
99

    
100
    def set(self):
101
        self._set("unknown", 3)
102
        self._set("skip", 1)
103
        self._set("pskip", 1)
104

    
105
    def clear(self):
106
        self._set("unknown", 0)
107
        self._set("skip", 0)
108
        self._set("pskip", 0)
109

    
110
    def get(self):
111
        return (self._get("unknown") << 2 |
112
                self._get("skip") << 1 |
113
                self._get("pskip"))
114

    
115
    def __repr__(self):
116
        return repr(self._flagobj)
117

    
118
def _is_loc_used(memobj, loc):
119
    return memobj.flags[loc / 2].get_raw() != "\xFF"
120

    
121
def _addr_to_loc(addr):
122
    return (addr - 0x2000) / 32
123

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

    
130
def _debug(string):
131
    if "CHIRP_DEBUG" in os.environ or True:
132
        print string
133

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

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

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

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

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

    
178
def _checksum(data):
179
    cs = 0
180
    for byte in data:
181
        cs += ord(byte)
182
    return cs % 256
183

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

    
219
def _download(radio):
220
    _ident(radio)
221

    
222
    memobj = None
223

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

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

    
239
            if addr == 0x19F0:
240
                memobj = bitwise.parse(_mem_format, data)
241

    
242
    _finish(radio)
243

    
244
    return memmap.MemoryMap(data)
245

    
246
def _upload(radio):
247
    _ident(radio)
248

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

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

    
264
    _finish(radio)
265

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

    
271
@directory.register
272
class PolmarDB50Madio(chirp_common.CloneModeRadio,
273
                         chirp_common.ExperimentalRadio):
274
    """Polmar DB-50M"""
275
    VENDOR = "Polmar"
276
    MODEL = "DB-50M"
277
    BAUD_RATE = 9600
278

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

    
284
    @classmethod
285
    def get_experimental_warning(cls):
286
        return "FOO"
287

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

    
306
    def sync_in(self):
307
        self._mmap = _download(self)
308
        self.process_mmap()
309

    
310
    def sync_out(self):
311
        _upload(self)
312

    
313
    def process_mmap(self):
314
        self._memobj = bitwise.parse(mem_format, self._mmap)
315

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

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

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

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

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

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

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

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

    
367
        rxpol = _mem.rxinv and "R" or "N"
368
        txpol = _mem.txinv and "R" or "N"
369

    
370
        chirp_common.split_tone_decode(mem,
371
                                       (txmode, txtone, txpol),
372
                                       (rxmode, rxtone, rxpol))
373

    
374
        mem.skip = _flg.get_skip() and "S" or _flg.get_pskip() and "P" or ""
375

    
376
        return mem
377

    
378
    def set_memory(self, mem):
379
        _mem, _flg = self._get_memobjs(mem.number)
380
        if mem.empty:
381
            _flg.set()
382
            return
383
        _flg.clear()
384
        _mem.set_raw("\x00" * 32)
385

    
386
        _mem.freq = mem.freq / 100
387
        _mem.offset = mem.offset / 100
388
        _mem.name = mem.name.ljust(7)
389
        _mem.is_am = mem.mode == "AM"
390
        _mem.duplex = DUPLEXES.index(mem.duplex)
391

    
392
        try:
393
            _mem.channel_width = MODES.index(mem.mode)
394
        except ValueError:
395
            _mem.channel_width = 0
396

    
397
        ((txmode, txtone, txpol),
398
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
399

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

    
413
        _mem.txinv = txpol == "R"
414
        _mem.rxinv = rxpol == "R"
415

    
416
        _flg.set_skip(mem.skip == "S")
417
        _flg.set_pskip(mem.skip == "P")
418

    
419
    @classmethod
420
    def match_model(cls, filedata, filename):
421
        return filedata[0x21:0x28] == "QX588UV"
(2-2/3)