tk790.py

Tom Hayward, 02/20/2016 07:58 pm

Download (16.5 kB)

 
1
# Copyright 2013 Tom Hayward <tom@tomh.us>
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 2 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 struct
17
import os
18

    
19
from chirp import chirp_common, directory, memmap, errors, util
20
from chirp import bitwise
21
from chirp.settings import RadioSettingGroup, RadioSetting
22
from chirp.settings import RadioSettingValueBoolean, RadioSettingValueList
23
from chirp.settings import RadioSettingValueString
24

    
25
MEM_FORMAT = """
26
#seekto 0x0040;
27
struct {
28
    u8 unknown40[16];
29
    u8 channel_name_max;
30
    u8 group_name_max;
31
    u8 unknown52[4];
32
    u8 min_volume;              // 0-31
33
    u8 bcl_override;
34
    u8 squelch;                 // 0-15
35
    u8 unknown59[2];
36
    u8 clear_to_transpond;
37
    u8 unknown5c[4];
38
    u8 unknown60[3];
39
    u8 small_lcd_display;       // [Off, Group, Channel]
40
    u8 unknown64[4];
41
    u8 unknown68[4];
42
    u8 roll_over;               // 0 == dead end
43
    u8 off_hook_decode;
44
    u8 off_hook_horn_alert;
45
    u8 unknown6f;
46
    u8 unknown70[8];
47
    u8 horn_alert_backup;       // Enabled: 0x00, Disabled: 0xFF
48
    u8 horn_alert_logic_signal; // 1-30 sec, 0 is pulse, FF is "until reset"
49
    u8 timed_power_off;         // hours
50
    u8 channel_tracking;
51
    u8 unknown7c;
52
    u8 ext_ptt_with_squelch_tail_eliminator;
53
    u8 ext_ptt_with_mic_mute;
54
    u8 ext_ptt_with_qt;
55
    u8 unknown80[16];
56
} settings;
57

    
58
#seekto 0x0440;
59
struct {
60
    u8 tot;                     // * 10 seconds, 30 sec increments
61
    u8 tot_pre_alert;           // seconds, off - 10, default 5
62
    u8 tot_rekey_time;          // seconds, off - 60, default off
63
    u8 tot_reset_time;          // seconds, off - 15, default off
64
    u8 unknown[4];
65
} group_settings[160];
66
// end 0x0940
67

    
68
#seekto 0x14c0;
69
struct {
70
    u8 start;
71
    u8 length;
72
} group_boundary[160];
73
// end 0x1600
74

    
75
#seekto 0x1640;
76
struct {
77
    u8 channel_number;          // relative to group, 1-160
78
    u8 channel_index;           // memory[channel_index]
79
} group_membership[160];
80
// end 0x1780
81

    
82
#seekto 0x1840;
83
struct {
84
  lbcd rx_freq[4];
85
  lbcd tx_freq[4];
86
  ul16 rx_tone;
87
  ul16 tx_tone;
88
  u8 unknown1:1,
89
     lowpower:1,
90
     beatshift:1,
91
     bcl:1,
92
     pttid:1,
93
     signaling:3;
94
  u8 unknown2:4,
95
     scan:1,
96
     wide:1,
97
     unknown2b:2;
98
  u8 unknown3;
99
  u8 unknown4a:6,
100
     companderoff:1,
101
     unknown5b:1;
102
} memory[160];
103

    
104
#seekto 0x3e30;
105
char power_on_msg[14];
106

    
107
#seekto 0x3ec0;
108
struct {
109
    char line[16];
110
} embedded_message[2];
111

    
112
#seekto 0x3f1a;
113
char kenwood_software[6];
114
char ident[5];
115

    
116
#seekto 0x3f3b;
117
char kenwood_software_ver[5];
118

    
119
#seekto 0x40e0;
120
struct {
121
  char name[16];
122
} group_name[160];
123

    
124
struct {
125
  char name[16];
126
} channel_name[160];
127

    
128
"""
129

    
130
POWER_LEVELS = [chirp_common.PowerLevel("High", watts=100),
131
                chirp_common.PowerLevel("Low", watts=50)]
132
MODES = ["NFM", "FM"]
133
SIGNAL = ["", "DTMF", "2-Tone 1", "2-Tone 2", "2-Tone 3"]
134

    
135
def make_frame(cmd, unk, addr, data=""):
136
    return struct.pack(">BBH", ord(cmd), ord(unk), addr) + data
137

    
138
def send(radio, frame):
139
    print "%04i P>R: %s" % (len(frame), util.hexprint(frame))
140
    radio.pipe.write(frame)
141

    
142
def recv(radio, readdata=True):
143
    hdr = radio.pipe.read(4)
144
    cmd, addr, length = struct.unpack(">BHB", hdr)
145
    if readdata:
146
        data = radio.pipe.read(length)
147
        #print "     P<R: %s" % util.hexprint(hdr + data)
148
        if len(data) != length:
149
            raise errors.RadioError("Radio sent %i bytes (expected %i)" % (
150
                    len(data), length))
151
    else:
152
        data = ""
153
    send(radio, "\x06")
154
    return addr, data
155

    
156
def do_ident(radio):
157
    print "clear", util.hexprint(radio.pipe.read(2000), "      ")
158
    send(radio, "PROGRAM")
159
    ack = radio.pipe.read(1)
160
    if ack != "\x06":
161
        raise errors.RadioError("Radio refused program mode")
162
    send(radio, "\x02")
163
    send(radio, "\x0F")
164
    ident = radio.pipe.read(10)
165
    print "ident:", ident
166
    print "ident:", ident[:5]
167
    if ident[:5] != radio._ident:
168
        raise errors.RadioError("Incorrect model: %s, expected %s" % (
169
                ident[:5], radio._ident))
170
    print "Model: %s" % util.hexprint(ident)
171
    send(radio, "\x06")
172
    ack = radio.pipe.read(1)
173

    
174
def do_download(radio):
175
    radio.pipe.setTimeout(1)
176
    do_ident(radio)
177

    
178
    data = "\xFF" * 0x40
179
    for addr in range(0x380, 0x400):
180
        send(radio, make_frame("R", "\x0F", addr))
181
        cmd = radio.pipe.read(1)
182
        if cmd == "W":
183
            _data = radio.pipe.read(128)
184
            print "     P<R: %s" % util.hexprint(_data, "          ")
185
            data += _data
186
            wtf = radio.pipe.read(1)
187
            print "     P<R: %s" % util.hexprint(wtf, "          ")
188
        elif cmd == "Z":
189
            print "     P<R: %s" % util.hexprint(radio.pipe.read(1))
190
            data += "\xFF" * 128
191
        else:
192
            raise errors.RadioError("Unknown reply: %s" % util.hexprint(cmd))
193
        send(radio, "\x06")
194
        ack = radio.pipe.read(1)
195
        if ack != "\x06":
196
            raise errors.RadioError("Radio refused block at %04x" % addr)
197

    
198
        status = chirp_common.Status()
199
        status.cur = addr - 0x380
200
        status.max = 0x0400 - 0x0380
201
        status.msg = "Reading channels from radio"
202
        radio.status_fn(status)
203

    
204
    # FIXME: Download this data from radio
205
    data += "\xFF" * 160
206

    
207
    length = 0x20
208
    for addr in range(0x0000, 0x1fe0, length):
209
        send(radio, make_frame("S", "\x8F", addr, chr(length)))
210
        cmd, _addr = struct.unpack(">BH", radio.pipe.read(3))
211
        cmd = chr(cmd)
212
        if addr != _addr:
213
            raise errors.RadioError("Wrong addr in reply")
214
        elif cmd == "X":
215
            _data = radio.pipe.read(length)
216
            data += _data
217
        elif cmd == "[":
218
            radio.pipe.read(1)
219
            data += "\xFF" * length
220
        else:
221
            raise errors.RadioError("Unknown reply: %s" % util.hexprint(cmd))
222
        send(radio, "\x06")
223
        ack = radio.pipe.read(1)
224
        if ack != "\x06":
225
            raise errors.RadioError("Radio refused block at %04x" % addr)
226

    
227
        status = chirp_common.Status()
228
        status.cur = addr
229
        status.max = 0x1fe0
230
        status.msg = "Reading other stuff from radio"
231
        radio.status_fn(status)
232

    
233
    send(radio, "E")
234

    
235
    return memmap.MemoryMap(data)
236

    
237
def do_upload(radio):
238
    radio.pipe.setTimeout(1)
239
    do_ident(radio)
240

    
241
    for addr in range(0, 0x0400, 8):
242
        eaddr = addr + 16
243
        send(radio, make_frame("W", addr, 8, radio._mmap[eaddr:eaddr + 8]))
244
        ack = radio.pipe.read(1)
245
        if ack != "\x06":
246
            raise errors.RadioError("Radio refused block at %04x" % addr)
247
        send(radio, "\x06")
248

    
249
        status = chirp_common.Status()
250
        status.cur = addr
251
        status.max = 0x0400
252
        status.msg = "Cloning to radio"
253
        radio.status_fn(status)
254

    
255
    send(radio, "\x45")
256

    
257

    
258
class KenwoodTKx90Radio(chirp_common.CloneModeRadio):
259
    """Kenwood TK-x90"""
260
    VENDOR = "Kenwood"
261
    MODEL = "TK-x90"
262
    BAUD_RATE = 9600
263

    
264
    _memsize = 0x60E0
265

    
266
    def get_features(self):
267
        rf = chirp_common.RadioFeatures()
268
        rf.has_settings = True
269
        rf.has_cross = True
270
        rf.has_bank = False
271
        rf.has_tuning_step = False
272
        rf.has_name = True
273
        rf.has_rx_dtcs = True
274
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
275
        rf.valid_modes = MODES
276
        rf.valid_power_levels = POWER_LEVELS
277
        rf.valid_skips = ["", "S"]
278
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
279
        rf.valid_bands = [self._range]
280
        rf.memory_bounds = (1, self._upper)
281

    
282
        rf.has_comment = True
283
        return rf
284

    
285
    def sync_in(self):
286
        try:
287
            self._mmap = do_download(self)
288
        except errors.RadioError:
289
            self.pipe.write("E")
290
            raise
291
        # except Exception, e:
292
        #     raise errors.RadioError("Failed to download from radio: %s" % e)
293
        self.process_mmap()
294

    
295
    def process_mmap(self):
296
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
297

    
298
    def sync_out(self):
299
        try:
300
            do_upload(self)
301
        except errors.RadioError:
302
            self.pipe.write("E")
303
            raise
304
        except Exception, e:
305
            raise errors.RadioError("Failed to upload to radio: %s" % e)
306

    
307
    def get_raw_memory(self, number):
308
        _group_member = self._memobj.group_membership[number - 1]
309
        _num = _group_member.channel_index
310
        return "\n".join([repr(_group_member),
311
                          repr(self._memobj.memory[_num]),
312
                          repr(self._memobj.channel_name[_num])])
313

    
314
    def _get_tone(self, _mem, mem):
315
        def _get_dcs(val):
316
            code = int("%03o" % (val & 0x07FF))
317
            pol = (val & 0x8000) and "R" or "N"
318
            return code, pol
319
            
320
        if _mem.tx_tone != 0xFFFF and _mem.tx_tone > 0x2800:
321
            tcode, tpol = _get_dcs(_mem.tx_tone)
322
            mem.dtcs = tcode
323
            txmode = "DTCS"
324
        elif _mem.tx_tone != 0xFFFF:
325
            mem.rtone = _mem.tx_tone / 10.0
326
            txmode = "Tone"
327
        else:
328
            txmode = ""
329

    
330
        if _mem.rx_tone != 0xFFFF and _mem.rx_tone > 0x2800:
331
            rcode, rpol = _get_dcs(_mem.rx_tone)
332
            mem.rx_dtcs = rcode
333
            rxmode = "DTCS"
334
        elif _mem.rx_tone != 0xFFFF:
335
            mem.ctone = _mem.rx_tone / 10.0
336
            rxmode = "Tone"
337
        else:
338
            rxmode = ""
339

    
340
        if txmode == "Tone" and not rxmode:
341
            mem.tmode = "Tone"
342
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
343
            mem.tmode = "TSQL"
344
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
345
            mem.tmode = "DTCS"
346
        elif rxmode or txmode:
347
            mem.tmode = "Cross"
348
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
349

    
350
        if mem.tmode == "DTCS":
351
            mem.dtcs_polarity = "%s%s" % (tpol, rpol)
352

    
353
    def _get_group(self, index):
354
        """Returns the group number for a given channel number"""
355
        for i, group in enumerate(self._memobj.group_boundary, 1):
356
            if group.start <= index and (group.start + group.length) >= index:
357
                return i
358
        raise ValueError("Channel index out of group range.")
359

    
360
    def get_memory(self, number):
361
        _group_member = self._memobj.group_membership[number - 1]
362
        _num = _group_member.channel_index
363

    
364
        mem = chirp_common.Memory()
365
        mem.number = number
366

    
367
        if _num == 0xFF:
368
            mem.empty = True
369
            return mem
370

    
371
        _mem = self._memobj.memory[_num]
372

    
373
        mem.name = str(self._memobj.channel_name[_num].name).rstrip()
374
        group = self._get_group(number)
375
        mem.comment = "%d %s %d" % (group,
376
            str(self._memobj.group_name[group - 1].name).rstrip(),
377
            _group_member.channel_number)
378

    
379
        mem.freq = int(_mem.rx_freq) * 10
380
        offset = (int(_mem.tx_freq) * 10) - mem.freq
381
        if _mem.tx_freq.get_raw() == "\xFF" * 4:
382
            mem.offset = 0
383
            mem.duplex = "off"
384
        elif offset < 0:
385
            mem.offset = abs(offset)
386
            mem.duplex = "-"
387
        elif offset > 0:
388
            mem.offset = offset
389
            mem.duplex = "+"
390
        else:
391
            mem.offset = 0
392

    
393
        self._get_tone(_mem, mem)
394
        mem.power = POWER_LEVELS[_mem.lowpower]
395
        mem.mode = MODES[_mem.wide]
396
        mem.skip = not _mem.scan and "S" or ""
397

    
398
        mem.extra = RadioSettingGroup("all", "All Settings")
399

    
400
        bcl = RadioSetting("bcl", "Busy Channel Lockout",
401
                           RadioSettingValueBoolean(bool(_mem.bcl)))
402
        mem.extra.append(bcl)
403

    
404
        beat = RadioSetting("beatshift", "Beat Shift",
405
                            RadioSettingValueBoolean(bool(_mem.beatshift)))
406
        mem.extra.append(beat)
407

    
408
        pttid = RadioSetting("pttid", "PTT ID",
409
                             RadioSettingValueBoolean(bool(_mem.pttid)))
410
        mem.extra.append(pttid)
411

    
412
        signal = RadioSetting("signaling", "Signaling",
413
                              RadioSettingValueList(SIGNAL,
414
                                                    SIGNAL[_mem.signaling]))
415
        mem.extra.append(signal)
416

    
417
        return mem
418

    
419
    def _set_tone(self, mem, _mem):
420
        def _set_dcs(code, pol):
421
            val = int("%i" % code, 8) + 0x2800
422
            if pol == "R":
423
                val += 0xA000
424
            return val
425

    
426
        if mem.tmode == "Cross":
427
            tx_mode, rx_mode = mem.cross_mode.split("->")
428
        elif mem.tmode == "Tone":
429
            tx_mode = mem.tmode
430
            rx_mode = None
431
        else:
432
            tx_mode = rx_mode = mem.tmode
433

    
434
        if tx_mode == "DTCS":
435
            _mem.tx_tone = mem.tmode != "DTCS" and \
436
                _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) or \
437
                _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[0])
438
        elif tx_mode:
439
            _mem.tx_tone = tx_mode == "Tone" and \
440
                int(mem.rtone * 10) or int(mem.ctone * 10)
441
        else:
442
            _mem.tx_tone = 0xFFFF
443

    
444
        if rx_mode == "DTCS":
445
            _mem.rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
446
        elif rx_mode:
447
            _mem.rx_tone = int(mem.ctone * 10)
448
        else:
449
            _mem.rx_tone = 0xFFFF
450

    
451
        if os.getenv("CHIRP_DEBUG"):
452
            print "Set TX %s (%i) RX %s (%i)" % (tx_mode, _mem.tx_tone,
453
                                                 rx_mode, _mem.rx_tone)
454
    def set_memory(self, mem):
455
        _mem = self._memobj.memory[mem.number - 1]
456

    
457
        if mem.empty:
458
            _mem.set_raw("\xFF" * 16)
459
            return
460

    
461
        _mem.unknown3[0] = 0x07
462
        _mem.unknown3[1] = 0x22
463
        _mem.rx_freq = mem.freq / 10
464
        if mem.duplex == "+":
465
            _mem.tx_freq = (mem.freq + mem.offset) / 10
466
        elif mem.duplex == "-":
467
            _mem.tx_freq = (mem.freq - mem.offset) / 10
468
        else:
469
            _mem.tx_freq = mem.freq / 10
470

    
471
        self._set_tone(mem, _mem)
472
        
473

    
474
        _mem.highpower = mem.power == POWER_LEVELS[1]
475
        _mem.wide = mem.mode == "FM"
476
        _mem.scan = mem.skip != "S"
477

    
478
        for setting in mem.extra:
479
            if setting.get_name == "signaling":
480
                if setting.value == "DTMF":
481
                    _mem.signaling = 0x03
482
                else:
483
                    _mem.signaling = 0x00
484
            else:
485
                setattr(_mem, setting.get_name(), setting.value)
486

    
487
    # def get_settings(self):
488
    #     _mem = self._memobj
489
    #     top = RadioSettingGroup("all", "All Settings")
490

    
491
    #     def _f(val):
492
    #         string = ""
493
    #         for char in str(val):
494
    #             if char == "\xFF":
495
    #                 break
496
    #             string += char
497
    #         return string
498

    
499
    #     line1 = RadioSetting("messages.line1", "Message Line 1",
500
    #                          RadioSettingValueString(0, 32,
501
    #                                                  _f(_mem.messages.line1),
502
    #                                                  autopad=False))
503
    #     top.append(line1)
504

    
505
    #     line2 = RadioSetting("messages.line2", "Message Line 2",
506
    #                          RadioSettingValueString(0, 32,
507
    #                                                  _f(_mem.messages.line2),
508
    #                                                  autopad=False))
509
    #     top.append(line2)
510

    
511
    #     return top
512

    
513
    # def set_settings(self, settings):
514
    #     for element in settings:
515
    #         if "." in element.get_name():
516
    #             bits = element.get_name().split(".")
517
    #             obj = self._memobj
518
    #             for bit in bits[:-1]:
519
    #                 obj = getattr(obj, bit)
520
    #             setting = bits[-1]
521
    #         else:
522
    #             obj = _settings
523
    #             setting = element.get_name()
524

    
525
    #         if "line" in setting:
526
    #             value = str(element.value).ljust(32, "\xFF")
527
    #         else:
528
    #             value = element.value
529
    #         setattr(obj, setting, value)
530
            
531
    @classmethod
532
    def match_model(cls, filedata, filename):
533
        model = filedata[0x3f20:0x3f25]
534
        return model == cls._ident
535

    
536

    
537
@directory.register
538
class KenwoodTK790Radio(KenwoodTKx90Radio):
539
    MODEL = "TK-790"
540
    _ident = "M0790"
541
    _range = (136000000, 174000000)
542
    _upper = 160