Project

General

Profile

Bug #8527 » tk760g(m7600).py

Hacked driver to recognize TK760G-1 - Jim Unroe, 12/08/2020 06:01 PM

 
1
# Copyright 2016 Pavel Milanes, CO7WT, <pavelmc@gmail.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 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 logging
17
import struct
18
import time
19
import sys
20

    
21
from chirp import chirp_common, directory, memmap, errors, util, bitwise
22
from textwrap import dedent
23
from chirp.settings import RadioSettingGroup, RadioSetting, \
24
    RadioSettingValueBoolean, RadioSettingValueList, \
25
    RadioSettingValueString, RadioSettingValueInteger, \
26
    RadioSettings
27

    
28
LOG = logging.getLogger(__name__)
29

    
30
##### IMPORTANT DATA ##########################################
31
# This radios have a span of
32
# 0x00000 - 0x08000 => Radio Memory / Settings data
33
# 0x08000 - 0x10000 => FIRMWARE... hum...
34
###############################################################
35

    
36
MEM_FORMAT = """
37
#seekto 0x0000;
38
struct {
39
  u8 unknown0[14];          // x00-x0d unknown
40
  u8 banks;                 // x0e how many banks are programmed
41
  u8 channels;              // x0f how many total channels are programmed
42
  // --
43
  ul16 tot;                 // x10 TOT value: range(15, 600, 15); x04b0 = off
44
  u8 tot_rekey;             // x12 TOT Re-key value range(0, 60); off= 0
45
  u8 unknown1;              // x13 unknown
46
  u8 tot_reset;             // x14 TOT Re-key value range(0, 60); off= 0
47
  u8 unknown2;              // x15 unknows
48
  u8 tot_alert;             // x16 TOT pre alert: range(0,10); 0 = off
49
  u8 unknown3[7];           // x17-x1d unknown
50
  u8 sql_level;             // x1e  SQ reference level
51
  u8 battery_save;          // Only for portable: FF = off, x32 = on
52
  // --
53
  u8 unknown4[10];          // x20
54
  u8 unknown5:3,            // x2d
55
     c2t:1,                 // 1 bit clear to transpond: 1-off
56
                            // This is relative to DTMF / 2-Tone settings
57
     unknown6:4;
58
  u8 unknown7[5];           // x2b-x2f
59
  // --
60
  u8 unknown8[16];          // x30 ?
61
  u8 unknown9[16];          // x40 ?
62
  u8 unknown10[16];          // x50 ?
63
  u8 unknown11[16];         // x60 ?
64
  // --
65
  u8 add[16];               // x70-x7f 128 bits corresponding add/skip values
66
  // --
67
  u8 unknown12:4,           // x80
68
     off_hook_decode:1,     // 1 bit off hook decode enabled: 1-off
69
     off_hook_horn_alert:1, // 1 bit off hook horn alert: 1-off
70
     unknown13:2;
71
  u8 unknown14;             // x81
72
  u8 unknown15:3,           // x82
73
     self_prog:1,           // 1 bit Self programming enabled: 1-on
74
     clone:1,               // 1 bit clone enabled: 1-on
75
     firmware_prog:1,       // 1 bit firmware programming enabled: 1-on
76
     unknown16:1,
77
     panel_test:1;          // 1 bit panel test enabled
78
  u8 unknown17;             // x83
79
  u8 unknown18:5,           // x84
80
     warn_tone:1,           // 1 bit warning tone, enabled: 1-on
81
     control_tone:1,        // 1 bit control tone (key tone), enabled: 1-on
82
     poweron_tone:1;        // 1 bit power on tone, enabled: 1-on
83
  u8 unknown19[5];          // x85-x89
84
  u8 min_vol;               // minimum volume posible: range(0,32); 0 = off
85
  u8 tone_vol;              // minimum tone volume posible:
86
                                // xff = continous, range(0, 31)
87
  u8 unknown20[4];          // x8c-x8f
88
  // --
89
  u8 unknown21[4];          // x90-x93
90
  char poweronmesg[8];      // x94-x9b power on mesg 8 bytes, off is "\FF" * 8
91
  u8 unknown22[4];          // x9c-x9f
92
  // --
93
  u8 unknown23[7];          // xa0-xa6
94
  char ident[8];            // xa7-xae radio identification string
95
  u8 unknown24;             // xaf
96
  // --
97
  u8 unknown26[11];         // xaf-xba
98
  char lastsoftversion[5];  // software version employed to program the radio
99
} settings;
100

    
101
#seekto 0xd0;
102
struct {
103
  u8 unknown[4];
104
  char radio[6];
105
  char data[6];
106
} passwords;
107

    
108
#seekto 0x0110;
109
struct {
110
  u8 kA;                // Portable > Closed circle
111
  u8 kDA;               // Protable > Triangle to Left
112
  u8 kGROUP_DOWN;       // Protable > Triangle to Right
113
  u8 kGROUP_UP;         // Protable > Side 1
114
  u8 kSCN;              // Portable > Open Circle
115
  u8 kMON;              // Protable > Side 2
116
  u8 kFOOT;
117
  u8 kCH_UP;
118
  u8 kCH_DOWN;
119
  u8 kVOL_UP;
120
  u8 kVOL_DOWN;
121
  u8 unknown30[5];
122
  // --
123
  u8 unknown31[4];
124
  u8 kP_KNOB;           // Just portable: channel knob
125
  u8 unknown32[11];
126
} keys;
127

    
128
#seekto 0x0140;
129
struct {
130
  lbcd tf01_rx[4];
131
  lbcd tf01_tx[4];
132
  u8 tf01_u_rx;
133
  u8 tf01_u_tx;
134
  lbcd tf02_rx[4];
135
  lbcd tf02_tx[4];
136
  u8 tf02_u_rx;
137
  u8 tf02_u_tx;
138
  lbcd tf03_rx[4];
139
  lbcd tf03_tx[4];
140
  u8 tf03_u_rx;
141
  u8 tf03_u_tx;
142
  lbcd tf04_rx[4];
143
  lbcd tf04_tx[4];
144
  u8 tf04_u_rx;
145
  u8 tf04_u_tx;
146
  lbcd tf05_rx[4];
147
  lbcd tf05_tx[4];
148
  u8 tf05_u_rx;
149
  u8 tf05_u_tx;
150
  lbcd tf06_rx[4];
151
  lbcd tf06_tx[4];
152
  u8 tf06_u_rx;
153
  u8 tf06_u_tx;
154
  lbcd tf07_rx[4];
155
  lbcd tf07_tx[4];
156
  u8 tf07_u_rx;
157
  u8 tf07_u_tx;
158
  lbcd tf08_rx[4];
159
  lbcd tf08_tx[4];
160
  u8 tf08_u_rx;
161
  u8 tf08_u_tx;
162
  lbcd tf09_rx[4];
163
  lbcd tf09_tx[4];
164
  u8 tf09_u_rx;
165
  u8 tf09_u_tx;
166
  lbcd tf10_rx[4];
167
  lbcd tf10_tx[4];
168
  u8 tf10_u_rx;
169
  u8 tf10_u_tx;
170
  lbcd tf11_rx[4];
171
  lbcd tf11_tx[4];
172
  u8 tf11_u_rx;
173
  u8 tf11_u_tx;
174
  lbcd tf12_rx[4];
175
  lbcd tf12_tx[4];
176
  u8 tf12_u_rx;
177
  u8 tf12_u_tx;
178
  lbcd tf13_rx[4];
179
  lbcd tf13_tx[4];
180
  u8 tf13_u_rx;
181
  u8 tf13_u_tx;
182
  lbcd tf14_rx[4];
183
  lbcd tf14_tx[4];
184
  u8 tf14_u_rx;
185
  u8 tf14_u_tx;
186
  lbcd tf15_rx[4];
187
  lbcd tf15_tx[4];
188
  u8 tf15_u_rx;
189
  u8 tf15_u_tx;
190
  lbcd tf16_rx[4];
191
  lbcd tf16_tx[4];
192
  u8 tf16_u_rx;
193
  u8 tf16_u_tx;
194
} test_freq;
195

    
196
#seekto 0x200;
197
struct {
198
  char line1[32];
199
  char line2[32];
200
} message;
201

    
202
#seekto 0x2000;
203
struct {
204
  u8 bnumb;             // mem number
205
  u8 bank;              // to which bank it belongs
206
  char name[8];         // name 8 chars
207
  u8 unknown20[2];      // unknown yet
208
  lbcd rxfreq[4];       // rx freq
209
  // --
210
  lbcd txfreq[4];       // tx freq
211
  u8 rx_unkw;           // unknown yet
212
  u8 tx_unkw;           // unknown yet
213
  ul16 rx_tone;         // rx tone
214
  ul16 tx_tone;         // tx tone
215
  u8 unknown23[5];      // unknown yet
216
  u8 signaling;         // xFF = off, x30 DTMF, x31 2-Tone
217
                        // See the zone on x7000
218
  // --
219
  u8 ptt_id:2,       // ??? BOT = 0, EOT = 1, Both = 2, NONE = 3
220
     beat_shift:1,      // 1 = off
221
     unknown26:2        // ???
222
     power:1,           // power: 0 low / 1 high
223
     compander:1,       // 1 = off
224
     wide:1;            // wide 1 / 0 narrow
225
  u8 unknown27:6,       // ???
226
     busy_lock:1,       // 1 = off
227
     unknown28:1;       // ???
228
  u8 unknown29[14];     // unknown yet
229
} memory[128];
230

    
231
#seekto 0x5900;
232
struct {
233
  char model[8];
234
  u8 unknown50[4];
235
  char type[2];
236
  u8 unknown51[2];
237
    // --
238
  char serial[8];
239
  u8 unknown52[8];
240
} id;
241

    
242
#seekto 0x6000;
243
struct {
244
  u8 code[8];
245
  u8 unknown60[7];
246
  u8 count;
247
} bot[128];
248

    
249
#seekto 0x6800;
250
struct {
251
  u8 code[8];
252
  u8 unknown61[7];
253
  u8 count;
254
} eot[128];
255

    
256
#seekto 0x7000;
257
struct {
258
  lbcd dt2_id[5];       // DTMF lbcd ID (000-9999999999)
259
                        // 2-Tone = "11 f1 ff ff ff" ???
260
                        // None = "00 f0 ff ff ff"
261
} dtmf;
262
"""
263

    
264
MEM_SIZE = 0x8000  # 32,768 bytes
265
BLOCK_SIZE = 256
266
BLOCKS = MEM_SIZE / BLOCK_SIZE
267
MEM_BLOCKS = range(0, BLOCKS)
268

    
269
# define and empty block of data, as it will be used a lot in this code
270
EMPTY_BLOCK = "\xFF" * 256
271

    
272
RO_BLOCKS = range(0x10, 0x1F) + range(0x59, 0x5f)
273
ACK_CMD = "\x06"
274

    
275
POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1),
276
                chirp_common.PowerLevel("High", watts=5)]
277

    
278
MODES = ["NFM", "FM"]  # 12.5 / 25 Khz
279
VALID_CHARS = chirp_common.CHARSET_UPPER_NUMERIC + "_-*()/\-+=)"
280
SKIP_VALUES = ["", "S"]
281

    
282
TONES = chirp_common.TONES
283
# TONES.remove(254.1)
284
DTCS_CODES = chirp_common.DTCS_CODES
285

    
286
TOT = ["off"] + ["%s" % x for x in range(15, 615, 15)]
287
TOT_PRE = ["off"] + ["%s" % x for x in range(1, 11)]
288
TOT_REKEY = ["off"] + ["%s" % x for x in range(1, 61)]
289
TOT_RESET = ["off"] + ["%s" % x for x in range(1, 16)]
290
VOL = ["off"] + ["%s" % x for x in range(1, 32)]
291
TVOL = ["%s" % x for x in range(0, 33)]
292
TVOL[32] = "Continous"
293
SQL = ["off"] + ["%s" % x for x in range(1, 10)]
294

    
295
## BOT = 0, EOT = 1, Both = 2, NONE = 3
296
#PTTID = ["BOT", "EOT", "Both", "none"]
297

    
298
# For debugging purposes
299
debug = False
300

    
301
KEYS = {
302
    0x33: "Display character",
303
    0x35: "Home Channel",                   # Posible portable only, chek it
304
    0x37: "CH down",
305
    0x38: "CH up",
306
    0x39: "Key lock",
307
    0x3a: "Lamp",                           # Portable only
308
    0x3b: "Public address",
309
    0x3c: "Reverse",                        # Just in updated firmwares (768G)
310
    0x3d: "Horn alert",
311
    0x3e: "Selectable QT",                  # Just in updated firmwares (768G)
312
    0x3f: "2-tone encode",
313
    0x40: "Monitor A: open mommentary",
314
    0x41: "Monitor B: Open Toggle",
315
    0x42: "Monitor C: Carrier mommentary",
316
    0x43: "Monitor D: Carrier toogle",
317
    0x44: "Operator selectable tone",
318
    0x45: "Redial",
319
    0x46: "RF Power Low",                   # portable only ?
320
    0x47: "Scan",
321
    0x48: "Scan del/add",
322
    0x4a: "GROUP down",
323
    0x4b: "GROUP up",
324
    #0x4e: "Tone off (Experimental)",       # undocumented !!!!
325
    0x4f: "None",
326
    0x50: "VOL down",
327
    0x51: "VOL up",
328
    0x52: "Talk around",
329
    0x5d: "AUX",
330
    0xa1: "Channel Up/Down"                 # Knob for portables only
331
    }
332

    
333

    
334
def _raw_recv(radio, amount):
335
    """Raw read from the radio device"""
336
    data = ""
337
    try:
338
        data = radio.pipe.read(amount)
339
    except:
340
        raise errors.RadioError("Error reading data from radio")
341

    
342
    # DEBUG
343
    if debug is True:
344
        LOG.debug("<== (%d) bytes:\n\n%s" % (len(data), util.hexprint(data)))
345

    
346
    return data
347

    
348

    
349
def _raw_send(radio, data):
350
    """Raw send to the radio device"""
351
    try:
352
        radio.pipe.write(data)
353
    except:
354
        raise errors.RadioError("Error sending data to radio")
355

    
356
    # DEBUG
357
    if debug is True:
358
        LOG.debug("==> (%d) bytes:\n\n%s" % (len(data), util.hexprint(data)))
359

    
360

    
361
def _close_radio(radio):
362
    """Get the radio out of program mode"""
363
    _raw_send(radio, "\x45")
364

    
365

    
366
def _checksum(data):
367
    """the radio block checksum algorithm"""
368
    cs = 0
369
    for byte in data:
370
            cs += ord(byte)
371
    return cs % 256
372

    
373

    
374
def _send(radio, frame):
375
    """Generic send data to the radio"""
376
    _raw_send(radio, frame)
377

    
378

    
379
def _make_frame(cmd, addr):
380
    """Pack the info in the format it likes"""
381
    return struct.pack(">BH", ord(cmd), addr)
382

    
383

    
384
def _handshake(radio, msg=""):
385
    """Make a full handshake"""
386
    # send ACK
387
    _raw_send(radio, ACK_CMD)
388
    # receive ACK
389
    ack = _raw_recv(radio, 1)
390
    # check ACK
391
    if ack != ACK_CMD:
392
        _close_radio(radio)
393
        mesg = "Handshake failed " + msg
394
        # DEBUG
395
        LOG.debug(mesg)
396
        raise Exception(mesg)
397

    
398

    
399
def _check_write_ack(r, ack, addr):
400
    """Process the ack from the write process
401
    this is half handshake needed in tx data block"""
402
    # all ok
403
    if ack == ACK_CMD:
404
        return True
405

    
406
    # Explicit BAD checksum
407
    if ack == "\x15":
408
        _close_radio(r)
409
        raise errors.RadioError(
410
            "Bad checksum in block %02x write" % addr)
411

    
412
    # everything else
413
    _close_radio(r)
414
    raise errors.RadioError(
415
        "Problem with the ack to block %02x write, ack %03i" %
416
        (addr, int(ack)))
417

    
418

    
419
def _recv(radio):
420
    """Receive data from the radio, 258 bytes split in (cmd, data, checksum)
421
    checking the checksum to be correct, and returning just
422
    256 bytes of data or false if short empty block"""
423
    rxdata = _raw_recv(radio, BLOCK_SIZE + 2)
424
    # when the RX block has two bytes and the first is \x5A
425
    # then the block is all \xFF
426
    if len(rxdata) == 2 and rxdata[0] == "\x5A":
427
        # fast work in linux has to make the handshake, slow windows don't
428
        if not sys.platform in ["win32", "cygwin"]:
429
            _handshake(radio, "short block")
430
        return False
431
    elif len(rxdata) != 258:
432
        # not the amount of data we want
433
        msg = "The radio send %d bytes, we need 258" % len(rxdata)
434
        # DEBUG
435
        LOG.error(msg)
436
        raise errors.RadioError(msg)
437
    else:
438
        rcs = ord(rxdata[-1])
439
        data = rxdata[1:-1]
440
        ccs = _checksum(data)
441

    
442
        if rcs != ccs:
443
            _close_radio(radio)
444
            raise errors.RadioError(
445
                "Block Checksum Error! real %02x, calculated %02x" %
446
                (rcs, ccs))
447

    
448
        _handshake(radio, "after checksum")
449
        return data
450

    
451

    
452
def _open_radio(radio, status):
453
    """Open the radio into program mode and check if it's the correct model"""
454
    # linux min is 0.13, win min is 0.25; set to bigger to be safe
455
    radio.pipe.timeout = 0.4
456
    radio.pipe.parity = "E"
457

    
458
    # DEBUG
459
    LOG.debug("Entering program mode.")
460
    # max tries
461
    tries = 10
462

    
463
    # UI
464
    status.cur = 0
465
    status.max = tries
466
    status.msg = "Entering program mode..."
467

    
468
    # try a few times to get the radio into program mode
469
    exito = False
470
    for i in range(0, tries):
471
        _raw_send(radio, "PROGRAM")
472
        ack = _raw_recv(radio, 1)
473

    
474
        if ack != ACK_CMD:
475
            # DEBUG
476
            LOG.debug("Try %s failed, traying again..." % i)
477
            time.sleep(0.25)
478
        else:
479
            exito = True
480
            break
481

    
482
        status.cur += 1
483
        radio.status_fn(status)
484

    
485

    
486
    if exito is False:
487
        _close_radio(radio)
488
        LOG.debug("Radio did not accepted PROGRAM command in %s atempts" % tries)
489
        raise errors.RadioError("The radio doesn't accept program mode")
490

    
491
    # DEBUG
492
    LOG.debug("Received ACK to the PROGRAM command, send ID query.")
493

    
494
    _raw_send(radio, "\x02")
495
    rid = _raw_recv(radio, 8)
496

    
497
    if not (radio.TYPE in rid):
498
        # bad response, properly close the radio before exception
499
        _close_radio(radio)
500

    
501
        # DEBUG
502
        LOG.debug("Incorrect model ID:")
503
        LOG.debug(util.hexprint(rid))
504

    
505
        raise errors.RadioError(
506
            "Incorrect model ID, got %s, it not contains %s" %
507
            (rid.strip("\xff"), radio.TYPE))
508

    
509
    # DEBUG
510
    LOG.debug("Full ident string is:")
511
    LOG.debug(util.hexprint(rid))
512
    _handshake(radio)
513

    
514
    status.msg = "Radio ident success!"
515
    radio.status_fn(status)
516
    # a pause
517
    time.sleep(1)
518

    
519

    
520
def do_download(radio):
521
    """ The download function """
522
    # UI progress
523
    status = chirp_common.Status()
524
    data = ""
525
    count = 0
526

    
527
    # open the radio
528
    _open_radio(radio, status)
529

    
530
    # reset UI data
531
    status.cur = 0
532
    status.max = MEM_SIZE / 256
533
    status.msg = "Cloning from radio..."
534
    radio.status_fn(status)
535

    
536
    # set the timeout and if windows keep it bigger
537
    if sys.platform in ["win32", "cygwin"]:
538
        # bigger timeout
539
        radio.pipe.timeout = 0.55
540
    else:
541
        # Linux can keep up, MAC?
542
        radio.pipe.timeout = 0.05
543

    
544
    # DEBUG
545
    LOG.debug("Starting the download from radio")
546

    
547
    for addr in MEM_BLOCKS:
548
        # send request, but before flush the rx buffer
549
        radio.pipe.flush()
550
        _send(radio, _make_frame("R", addr))
551

    
552
        # now we get the data
553
        d = _recv(radio)
554
        # if empty block, it return false
555
        # aka we asume a empty 256 xFF block
556
        if d is False:
557
            d = EMPTY_BLOCK
558

    
559
        data += d
560

    
561
        # UI Update
562
        status.cur = count
563
        radio.status_fn(status)
564

    
565
        count += 1
566

    
567
    _close_radio(radio)
568
    return memmap.MemoryMap(data)
569

    
570

    
571
def do_upload(radio):
572
    """ The upload function """
573
    # UI progress
574
    status = chirp_common.Status()
575
    data = ""
576
    count = 0
577

    
578
    # open the radio
579
    _open_radio(radio, status)
580

    
581
    # update UI
582
    status.cur = 0
583
    status.max = MEM_SIZE / 256
584
    status.msg = "Cloning to radio..."
585
    radio.status_fn(status)
586

    
587
    # the default for the original soft as measured
588
    radio.pipe.timeout = 0.5
589

    
590
    # DEBUG
591
    LOG.debug("Starting the upload to the radio")
592

    
593
    count = 0
594
    raddr = 0
595
    for addr in MEM_BLOCKS:
596
        # this is the data block to write
597
        data = radio.get_mmap()[raddr:raddr+BLOCK_SIZE]
598

    
599
        # The blocks from x59-x5F are NOT programmable
600
        # The blocks from x11-x1F are writed only if not empty
601
        if addr in RO_BLOCKS:
602
            # checking if in the range of optional blocks
603
            if addr >= 0x10 and addr <= 0x1F:
604
                # block is empty ?
605
                if data == EMPTY_BLOCK:
606
                    # no write of this block
607
                    # but we have to continue updating the counters
608
                    count += 1
609
                    raddr = count * 256
610
                    continue
611
            else:
612
                count += 1
613
                raddr = count * 256
614
                continue
615

    
616
        if data == EMPTY_BLOCK:
617
            frame = _make_frame("Z", addr) + "\xFF"
618
        else:
619
            cs = _checksum(data)
620
            frame = _make_frame("W", addr) + data + chr(cs)
621

    
622
        _send(radio, frame)
623

    
624
        # get the ACK
625
        ack = _raw_recv(radio, 1)
626
        _check_write_ack(radio, ack, addr)
627

    
628
        # DEBUG
629
        LOG.debug("Sending block %02x" % addr)
630

    
631
        # UI Update
632
        status.cur = count
633
        radio.status_fn(status)
634

    
635
        count += 1
636
        raddr = count * 256
637

    
638
    _close_radio(radio)
639

    
640

    
641
def model_match(cls, data):
642
    """Match the opened/downloaded image to the correct version"""
643
    rid = data[0xA7:0xAE]
644
    if (rid in cls.VARIANTS):
645
        # correct model
646
        return True
647
    else:
648
        return False
649

    
650

    
651
class Kenwood60GBankModel(chirp_common.BankModel):
652
    """Testing the bank model on kennwood"""
653
    channelAlwaysHasBank = True
654

    
655
    def get_num_mappings(self):
656
        return self._radio._num_banks
657

    
658
    def get_mappings(self):
659
        banks = []
660
        for i in range(0, self._radio._num_banks):
661
            bindex = i + 1
662
            bank = self._radio._bclass(self, i, "%03i" % bindex)
663
            bank.index = i
664
            banks.append(bank)
665
        return banks
666

    
667
    def add_memory_to_mapping(self, memory, bank):
668
        self._radio._set_bank(memory.number, bank.index)
669

    
670
    def remove_memory_from_mapping(self, memory, bank):
671
        if self._radio._get_bank(memory.number) != bank.index:
672
            raise Exception("Memory %i not in bank %s. Cannot remove." %
673
                            (memory.number, bank))
674

    
675
        # We can't "Remove" it for good
676
        # the kenwood paradigm don't allow it
677
        # instead we move it to bank 0
678
        self._radio._set_bank(memory.number, 0)
679

    
680
    def get_mapping_memories(self, bank):
681
        memories = []
682
        for i in range(0, self._radio._upper):
683
            if self._radio._get_bank(i) == bank.index:
684
                memories.append(self._radio.get_memory(i))
685
        return memories
686

    
687
    def get_memory_mappings(self, memory):
688
        index = self._radio._get_bank(memory.number)
689
        return [self.get_mappings()[index]]
690

    
691

    
692
class memBank(chirp_common.Bank):
693
    """A bank model for kenwood"""
694
    # Integral index of the bank (not to be confused with per-memory
695
    # bank indexes
696
    index = 0
697

    
698

    
699
class Kenwood_Serie_60G(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
700
    """Kenwood Serie 60G Radios base class"""
701
    VENDOR = "Kenwood"
702
    BAUD_RATE = 9600
703
    _memsize = MEM_SIZE
704
    NAME_LENGTH = 8
705
    _range = [136000000, 162000000]
706
    _upper = 128
707
    _chs_progs = 0
708
    _num_banks = 128
709
    _bclass = memBank
710
    _kind = ""
711
    VARIANT = ""
712
    MODEL = ""
713

    
714
    @classmethod
715
    def get_prompts(cls):
716
        rp = chirp_common.RadioPrompts()
717
        rp.experimental = \
718
            ('This driver is experimental; not all features have been '
719
            'implemented, but it has those features most used by hams.\n'
720
             '\n'
721
             'This radios are able to work slightly outside the OEM '
722
             'frequency limits. After testing, the limit in Chirp has '
723
             'been set 4% outside the OEM limit. This allows you to use '
724
             'some models on the ham bands.\n'
725
             '\n'
726
             'Nevertheless, each radio has its own hardware limits and '
727
             'your mileage may vary.\n'
728
             )
729
        rp.pre_download = _(dedent("""\
730
            Follow this instructions to download your info:
731
            1 - Turn off your radio
732
            2 - Connect your interface cable
733
            3 - Turn on your radio (unblock it if password protected)
734
            4 - Do the download of your radio data
735
            """))
736
        rp.pre_upload = _(dedent("""\
737
            Follow this instructions to upload your info:
738
            1 - Turn off your radio
739
            2 - Connect your interface cable
740
            3 - Turn on your radio (unblock it if password protected)
741
            4 - Do the upload of your radio data
742
            """))
743
        return rp
744

    
745
    def get_features(self):
746
        """Return information about this radio's features"""
747
        rf = chirp_common.RadioFeatures()
748
        rf.has_settings = True
749
        rf.has_bank = True
750
        rf.has_tuning_step = False
751
        rf.has_name = True
752
        rf.has_offset = True
753
        rf.has_mode = True
754
        rf.has_dtcs = True
755
        rf.has_rx_dtcs = True
756
        rf.has_dtcs_polarity = True
757
        rf.has_ctone = True
758
        rf.has_cross = True
759
        rf.valid_modes = MODES
760
        rf.valid_duplexes = ["", "-", "+", "off"]
761
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
762
        rf.valid_cross_modes = [
763
            "Tone->Tone",
764
            "DTCS->",
765
            "->DTCS",
766
            "Tone->DTCS",
767
            "DTCS->Tone",
768
            "->Tone",
769
            "DTCS->DTCS"]
770
        rf.valid_power_levels = POWER_LEVELS
771
        rf.valid_characters = VALID_CHARS
772
        rf.valid_skips = SKIP_VALUES
773
        rf.valid_dtcs_codes = DTCS_CODES
774
        rf.valid_bands = [self._range]
775
        rf.valid_name_length = 8
776
        rf.memory_bounds = (1, self._upper)
777
        return rf
778

    
779
    def _fill(self, offset, data):
780
        """Fill an specified area of the memmap with the passed data"""
781
        for addr in range(0, len(data)):
782
            self._mmap[offset + addr] = data[addr]
783

    
784
    def _prep_data(self):
785
        """Prepare the areas in the memmap to do a consistend write
786
        it has to make an update on the x300 area with banks and channel
787
        info; other in the x1000 with banks and channel counts
788
        and a last one in x7000 with flag data"""
789
        rchs = 0
790
        data = dict()
791

    
792
        # sorting the data
793
        for ch in range(0, self._upper):
794
            mem = self._memobj.memory[ch]
795
            bnumb = int(mem.bnumb)
796
            bank = int(mem.bank)
797
            if bnumb != 255 and (bank != 255 and bank != 0):
798
                try:
799
                    data[bank].append(ch)
800
                except:
801
                    data[bank] = list()
802
                    data[bank].append(ch)
803
                data[bank].sort()
804
                # counting the real channels
805
                rchs = rchs + 1
806

    
807
        # updating the channel/bank count
808
        self._memobj.settings.channels = rchs
809
        self._chs_progs = rchs
810
        self._memobj.settings.banks = len(data)
811

    
812
        # building the data for the memmap
813
        fdata = ""
814

    
815
        for k, v in data.iteritems():
816
            # posible bad data
817
            if k == 0:
818
                k = 1
819
                raise errors.InvalidValueError(
820
                    "Invalid bank value '%k', bad data in the image? \
821
                    Trying to fix this, review your bank data!" % k)
822
            c = 1
823
            for i in v:
824
                fdata += chr(k) + chr(c) + chr(k - 1) + chr(i)
825
                c = c + 1
826

    
827
        # fill to match a full 256 bytes block
828
        fdata += (len(fdata) % 256) * "\xFF"
829

    
830
        # updating the data in the memmap [x300]
831
        self._fill(0x300, fdata)
832

    
833
        # update the info in x1000; it has 2 bytes with
834
        # x00 = bank , x01 = bank's channel count
835
        # the rest of the 14 bytes are \xff
836
        bdata = ""
837
        for i in range(1, len(data) + 1):
838
            line = chr(i) + chr(len(data[i]))
839
            line += "\xff" * 14
840
            bdata += line
841

    
842
        # fill to match a full 256 bytes block
843
        bdata += (256 - (len(bdata)) % 256) * "\xFF"
844

    
845
        # fill to match the whole area
846
        bdata += (16 - len(bdata) / 256) * EMPTY_BLOCK
847

    
848
        # updating the data in the memmap [x1000]
849
        self._fill(0x1000, bdata)
850

    
851
        # DTMF id for each channel, 5 bytes lbcd at x7000
852
        # ############## TODO ###################
853
        fldata = "\x00\xf0\xff\xff\xff" * self._chs_progs + \
854
            "\xff" * (5 * (self._upper - self._chs_progs))
855

    
856
        # write it
857
        # updating the data in the memmap [x7000]
858
        self._fill(0x7000, fldata)
859

    
860
    def _set_variant(self):
861
        """Select and set the correct variables for the class acording
862
        to the correct variant of the radio"""
863
        rid = self._mmap[0xA7:0xAE]
864

    
865
        # indentify the radio variant and set the enviroment to it's values
866
        try:
867
            self._upper, low, high, self._kind = self.VARIANTS[rid]
868

    
869
            # Frequency ranges: some model/variants are able to work the near
870
            # ham bands, even if they are outside the OEM ranges.
871
            # By experimentation we found that 4% at the edges is in most
872
            # cases safe and will cover the near ham bands in full
873
            self._range = [low * 1000000 * 0.96, high * 1000000 * 1.04]
874

    
875
            # setting the bank data in the features, 8 & 16 CH dont have banks
876
            if self._upper < 32:
877
                rf = chirp_common.RadioFeatures()
878
                rf.has_bank = False
879

    
880
            # put the VARIANT in the class, clean the model / CHs / Type
881
            # in the same layout as the KPG program
882
            self._VARIANT = self.MODEL + " [" + str(self._upper) + "CH]: "
883
            # In the OEM string we show the real OEM ranges
884
            self._VARIANT += self._kind + ", %d - %d MHz" % (low, high)
885

    
886
        except KeyError:
887
            LOG.debug("Wrong Kenwood radio, ID or unknown variant")
888
            LOG.debug(util.hexprint(rid))
889
            raise errors.RadioError(
890
                "Wrong Kenwood radio, ID or unknown variant, see LOG output.")
891
            return False
892

    
893
    def sync_in(self):
894
        """Do a download of the radio eeprom"""
895
        self._mmap = do_download(self)
896
        self.process_mmap()
897

    
898
    def sync_out(self):
899
        """Do an upload to the radio eeprom"""
900

    
901
        # chirp signature on the eprom ;-)
902
        sign = "Chirp"
903
        self._fill(0xbb, sign)
904

    
905
        try:
906
            self._prep_data()
907
            do_upload(self)
908
        except errors.RadioError:
909
            raise
910
        except Exception, e:
911
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
912

    
913
    def process_mmap(self):
914
        """Process the memory object"""
915
        # how many channels are programed
916
        self._chs_progs = ord(self._mmap[15])
917

    
918
        # load the memobj
919
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
920

    
921
        # to set the vars on the class to the correct ones
922
        self._set_variant()
923

    
924
    def get_raw_memory(self, number):
925
        """Return a raw representation of the memory object, which
926
        is very helpful for development"""
927
        return repr(self._memobj.memory[number])
928

    
929
    def _decode_tone(self, val):
930
        """Parse the tone data to decode from mem, it returns:
931
        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
932
        val = int(val)
933
        if val == 65535:
934
            return '', None, None
935
        elif val >= 0x2800:
936
            code = int("%03o" % (val & 0x07FF))
937
            pol = (val & 0x8000) and "R" or "N"
938
            return 'DTCS', code, pol
939
        else:
940
            a = val / 10.0
941
            return 'Tone', a, None
942

    
943
    def _encode_tone(self, memval, mode, value, pol):
944
        """Parse the tone data to encode from UI to mem"""
945
        if mode == '':
946
            memval.set_raw("\xff\xff")
947
        elif mode == 'Tone':
948
            memval.set_value(int(value * 10))
949
        elif mode == 'DTCS':
950
            val = int("%i" % value, 8) + 0x2800
951
            if pol == "R":
952
                val += 0xA000
953
            memval.set_value(val)
954
        else:
955
            raise Exception("Internal error: invalid mode `%s'" % mode)
956

    
957
    def _get_scan(self, chan):
958
        """Get the channel scan status from the 16 bytes array on the eeprom
959
        then from the bits on the byte, return '' or 'S' as needed"""
960
        result = "S"
961
        byte = int(chan/8)
962
        bit = chan % 8
963
        res = self._memobj.settings.add[byte] & (pow(2, bit))
964
        if res > 0:
965
            result = ""
966

    
967
        return result
968

    
969
    def _set_scan(self, chan, value):
970
        """Set the channel scan status from UI to the mem_map"""
971
        byte = int(chan/8)
972
        bit = chan % 8
973

    
974
        # get the actual value to see if I need to change anything
975
        actual = self._get_scan(chan)
976
        if actual != value:
977
            # I have to flip the value
978
            rbyte = self._memobj.settings.add[byte]
979
            rbyte = rbyte ^ pow(2, bit)
980
            self._memobj.settings.add[byte] = rbyte
981

    
982
    def get_memory(self, number):
983
        # Get a low-level memory object mapped to the image
984
        _mem = self._memobj.memory[number - 1]
985

    
986
        # Create a high-level memory object to return to the UI
987
        mem = chirp_common.Memory()
988

    
989
        # Memory number
990
        mem.number = number
991

    
992
        # this radio has a setting about the amount of real chans of the 128
993
        # olso in the channel has xff on the Rx freq it's empty
994
        if (number > (self._chs_progs + 1)) or (_mem.get_raw()[0] == "\xFF"):
995
            mem.empty = True
996
            # but is not enough, you have to crear the memory in the mmap
997
            # to get it ready for the sync_out process
998
            _mem.set_raw("\xFF" * 48)
999
            return mem
1000

    
1001
        # Freq and offset
1002
        mem.freq = int(_mem.rxfreq) * 10
1003
        # tx freq can be blank
1004
        if _mem.get_raw()[16] == "\xFF":
1005
            # TX freq not set
1006
            mem.offset = 0
1007
            mem.duplex = "off"
1008
        else:
1009
            # TX feq set
1010
            offset = (int(_mem.txfreq) * 10) - mem.freq
1011
            if offset < 0:
1012
                mem.offset = abs(offset)
1013
                mem.duplex = "-"
1014
            elif offset > 0:
1015
                mem.offset = offset
1016
                mem.duplex = "+"
1017
            else:
1018
                mem.offset = 0
1019

    
1020
        # name TAG of the channel
1021
        mem.name = str(_mem.name).rstrip()
1022

    
1023
        # power
1024
        mem.power = POWER_LEVELS[_mem.power]
1025

    
1026
        # wide/marrow
1027
        mem.mode = MODES[_mem.wide]
1028

    
1029
        # skip
1030
        mem.skip = self._get_scan(number - 1)
1031

    
1032
        # tone data
1033
        rxtone = txtone = None
1034
        txtone = self._decode_tone(_mem.tx_tone)
1035
        rxtone = self._decode_tone(_mem.rx_tone)
1036
        chirp_common.split_tone_decode(mem, txtone, rxtone)
1037

    
1038
        # Extra
1039
        # bank and number in the channel
1040
        mem.extra = RadioSettingGroup("extra", "Extra")
1041

    
1042
        # validate bank
1043
        b = int(_mem.bank)
1044
        if b > 127 or b == 0:
1045
            _mem.bank = b = 1
1046

    
1047
        bank = RadioSetting("bank", "Bank it belongs",
1048
                            RadioSettingValueInteger(1, 128, b))
1049
        mem.extra.append(bank)
1050

    
1051
        # validate bnumb
1052
        if int(_mem.bnumb) > 127:
1053
            _mem.bank = mem.number
1054

    
1055
        bnumb = RadioSetting("bnumb", "Ch number in the bank",
1056
                             RadioSettingValueInteger(0, 127, _mem.bnumb))
1057
        mem.extra.append(bnumb)
1058

    
1059
        bs = RadioSetting("beat_shift", "Beat shift",
1060
                          RadioSettingValueBoolean(
1061
                              not bool(_mem.beat_shift)))
1062
        mem.extra.append(bs)
1063

    
1064
        cp = RadioSetting("compander", "Compander",
1065
                          RadioSettingValueBoolean(
1066
                              not bool(_mem.compander)))
1067
        mem.extra.append(cp)
1068

    
1069
        bl = RadioSetting("busy_lock", "Busy Channel lock",
1070
                          RadioSettingValueBoolean(
1071
                              not bool(_mem.busy_lock)))
1072
        mem.extra.append(bl)
1073

    
1074
        return mem
1075

    
1076
    def set_memory(self, mem):
1077
        """Set the memory data in the eeprom img from the UI
1078
        not ready yet, so it will return as is"""
1079

    
1080
        # get the eprom representation of this channel
1081
        _mem = self._memobj.memory[mem.number - 1]
1082

    
1083
        # if empty memmory
1084
        if mem.empty:
1085
            _mem.set_raw("\xFF" * 48)
1086
            return
1087

    
1088
        # frequency
1089
        _mem.rxfreq = mem.freq / 10
1090

    
1091
        # this are a mistery yet, but so falr there is no impact
1092
        # whit this default values for new channels
1093
        if int(_mem.rx_unkw) == 0xff:
1094
            _mem.rx_unkw = 0x35
1095
            _mem.tx_unkw = 0x32
1096

    
1097
        # duplex
1098
        if mem.duplex == "+":
1099
            _mem.txfreq = (mem.freq + mem.offset) / 10
1100
        elif mem.duplex == "-":
1101
            _mem.txfreq = (mem.freq - mem.offset) / 10
1102
        elif mem.duplex == "off":
1103
            for byte in _mem.txfreq:
1104
                byte.set_raw("\xFF")
1105
        else:
1106
            _mem.txfreq = mem.freq / 10
1107

    
1108
        # tone data
1109
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
1110
            chirp_common.split_tone_encode(mem)
1111
        self._encode_tone(_mem.tx_tone, txmode, txtone, txpol)
1112
        self._encode_tone(_mem.rx_tone, rxmode, rxtone, rxpol)
1113

    
1114
        # name TAG of the channel
1115
        _namelength = self.get_features().valid_name_length
1116
        for i in range(_namelength):
1117
            try:
1118
                _mem.name[i] = mem.name[i]
1119
            except IndexError:
1120
                _mem.name[i] = "\x20"
1121

    
1122
        # power
1123
        # default power is low
1124
        if mem.power is None:
1125
            mem.power = POWER_LEVELS[0]
1126

    
1127
        _mem.power = POWER_LEVELS.index(mem.power)
1128

    
1129
        # wide/marrow
1130
        _mem.wide = MODES.index(mem.mode)
1131

    
1132
        # scan add property
1133
        self._set_scan(mem.number - 1, mem.skip)
1134

    
1135
        # bank and number in the channel
1136
        if int(_mem.bnumb) == 0xff:
1137
            _mem.bnumb = mem.number - 1
1138
            _mem.bank = 1
1139

    
1140
        # extra settings
1141
        for setting in mem.extra:
1142
            if setting != "bank" or setting != "bnumb":
1143
                setattr(_mem, setting.get_name(), not bool(setting.value))
1144

    
1145
        # all data get sync after channel mod
1146
        self._prep_data()
1147

    
1148
        return mem
1149

    
1150
    @classmethod
1151
    def match_model(cls, filedata, filename):
1152
        match_size = False
1153
        match_model = False
1154

    
1155
        # testing the file data size
1156
        if len(filedata) == MEM_SIZE:
1157
            match_size = True
1158

    
1159
        # testing the firmware model fingerprint
1160
        match_model = model_match(cls, filedata)
1161

    
1162
        if match_size and match_model:
1163
            return True
1164
        else:
1165
            return False
1166

    
1167
    def get_settings(self):
1168
        """Translate the bit in the mem_struct into settings in the UI"""
1169
        sett = self._memobj.settings
1170
        mess = self._memobj.message
1171
        keys = self._memobj.keys
1172
        idm = self._memobj.id
1173
        passwd = self._memobj.passwords
1174

    
1175
        # basic features of the radio
1176
        basic = RadioSettingGroup("basic", "Basic Settings")
1177
        # dealer settings
1178
        dealer = RadioSettingGroup("dealer", "Dealer Settings")
1179
        # buttons
1180
        fkeys = RadioSettingGroup("keys", "Front keys config")
1181

    
1182
        # TODO / PLANED
1183
        # adjust feqs
1184
        #freqs = RadioSettingGroup("freqs", "Adjust Frequencies")
1185

    
1186
        top = RadioSettings(basic, dealer, fkeys)
1187

    
1188
        # Basic
1189
        tot = RadioSetting("settings.tot", "Time Out Timer (TOT)",
1190
                           RadioSettingValueList(TOT, TOT[
1191
                           TOT.index(str(int(sett.tot)))]))
1192
        basic.append(tot)
1193

    
1194
        totalert = RadioSetting("settings.tot_alert", "TOT pre alert",
1195
                                RadioSettingValueList(TOT_PRE,
1196
                                TOT_PRE[int(sett.tot_alert)]))
1197
        basic.append(totalert)
1198

    
1199
        totrekey = RadioSetting("settings.tot_rekey", "TOT re-key time",
1200
                                RadioSettingValueList(TOT_REKEY,
1201
                                TOT_REKEY[int(sett.tot_rekey)]))
1202
        basic.append(totrekey)
1203

    
1204
        totreset = RadioSetting("settings.tot_reset", "TOT reset time",
1205
                                RadioSettingValueList(TOT_RESET,
1206
                                TOT_RESET[int(sett.tot_reset)]))
1207
        basic.append(totreset)
1208

    
1209
        # this feature is for mobile only
1210
        if self.TYPE[0] == "M":
1211
            minvol = RadioSetting("settings.min_vol", "Minimum volume",
1212
                                  RadioSettingValueList(VOL,
1213
                                  VOL[int(sett.min_vol)]))
1214
            basic.append(minvol)
1215

    
1216
            tv = int(sett.tone_vol)
1217
            if tv == 255:
1218
                tv = 32
1219
            tvol = RadioSetting("settings.tone_vol", "Minimum tone volume",
1220
                                RadioSettingValueList(TVOL, TVOL[tv]))
1221
            basic.append(tvol)
1222

    
1223
        sql = RadioSetting("settings.sql_level", "SQL Ref Level",
1224
                           RadioSettingValueList(
1225
                           SQL, SQL[int(sett.sql_level)]))
1226
        basic.append(sql)
1227

    
1228
        #c2t = RadioSetting("settings.c2t", "Clear to Transpond",
1229
                           #RadioSettingValueBoolean(not sett.c2t))
1230
        #basic.append(c2t)
1231

    
1232
        ptone = RadioSetting("settings.poweron_tone", "Power On tone",
1233
                             RadioSettingValueBoolean(sett.poweron_tone))
1234
        basic.append(ptone)
1235

    
1236
        ctone = RadioSetting("settings.control_tone", "Control (key) tone",
1237
                             RadioSettingValueBoolean(sett.control_tone))
1238
        basic.append(ctone)
1239

    
1240
        wtone = RadioSetting("settings.warn_tone", "Warning tone",
1241
                             RadioSettingValueBoolean(sett.warn_tone))
1242
        basic.append(wtone)
1243

    
1244
        # Save Battery only for portables?
1245
        if self.TYPE[0] == "P":
1246
            bs = int(sett.battery_save) == 0x32 and True or False
1247
            bsave = RadioSetting("settings.battery_save", "Battery Saver",
1248
                                 RadioSettingValueBoolean(bs))
1249
            basic.append(bsave)
1250

    
1251
        ponm = str(sett.poweronmesg).strip("\xff")
1252
        pom = RadioSetting("settings.poweronmesg", "Power on message",
1253
                           RadioSettingValueString(0, 8, ponm, False))
1254
        basic.append(pom)
1255

    
1256
        # dealer
1257
        valid_chars = ",-/:[]" + chirp_common.CHARSET_ALPHANUMERIC
1258
        mstr = "".join([c for c in self._VARIANT if c in valid_chars])
1259

    
1260
        val = RadioSettingValueString(0, 35, mstr)
1261
        val.set_mutable(False)
1262
        mod = RadioSetting("not.mod", "Radio Version", val)
1263
        dealer.append(mod)
1264

    
1265
        sn = str(idm.serial).strip(" \xff")
1266
        val = RadioSettingValueString(0, 8, sn)
1267
        val.set_mutable(False)
1268
        serial = RadioSetting("not.serial", "Serial number", val)
1269
        dealer.append(serial)
1270

    
1271
        svp = str(sett.lastsoftversion).strip(" \xff")
1272
        val = RadioSettingValueString(0, 5, svp)
1273
        val.set_mutable(False)
1274
        sver = RadioSetting("not.softver", "Software Version", val)
1275
        dealer.append(sver)
1276

    
1277
        l1 = str(mess.line1).strip(" \xff")
1278
        line1 = RadioSetting("message.line1", "Comment 1",
1279
                           RadioSettingValueString(0, 32, l1))
1280
        dealer.append(line1)
1281

    
1282
        l2 = str(mess.line2).strip(" \xff")
1283
        line2 = RadioSetting("message.line2", "Comment 2",
1284
                             RadioSettingValueString(0, 32, l2))
1285
        dealer.append(line2)
1286

    
1287
        sprog = RadioSetting("settings.self_prog", "Self program",
1288
                             RadioSettingValueBoolean(sett.self_prog))
1289
        dealer.append(sprog)
1290

    
1291
        clone = RadioSetting("settings.clone", "Allow clone",
1292
                             RadioSettingValueBoolean(sett.clone))
1293
        dealer.append(clone)
1294

    
1295
        panel = RadioSetting("settings.panel_test", "Panel Test",
1296
                             RadioSettingValueBoolean(sett.panel_test))
1297
        dealer.append(panel)
1298

    
1299
        fmw = RadioSetting("settings.firmware_prog", "Firmware program",
1300
                           RadioSettingValueBoolean(sett.firmware_prog))
1301
        dealer.append(fmw)
1302

    
1303
        # front keys
1304
        # The Mobile only parameters are wraped here
1305
        if self.TYPE[0] == "M":
1306
            vu = RadioSetting("keys.kVOL_UP", "VOL UP",
1307
                              RadioSettingValueList(KEYS.values(),
1308
                              KEYS.values()[KEYS.keys().index(
1309
                                  int(keys.kVOL_UP))]))
1310
            fkeys.append(vu)
1311

    
1312
            vd = RadioSetting("keys.kVOL_DOWN", "VOL DOWN",
1313
                              RadioSettingValueList(KEYS.values(),
1314
                              KEYS.values()[KEYS.keys().index(
1315
                                  int(keys.kVOL_DOWN))]))
1316
            fkeys.append(vd)
1317

    
1318
            chu = RadioSetting("keys.kCH_UP", "CH UP",
1319
                               RadioSettingValueList(KEYS.values(),
1320
                               KEYS.values()[KEYS.keys().index(
1321
                                   int(keys.kCH_UP))]))
1322
            fkeys.append(chu)
1323

    
1324
            chd = RadioSetting("keys.kCH_DOWN", "CH DOWN",
1325
                               RadioSettingValueList(KEYS.values(),
1326
                               KEYS.values()[KEYS.keys().index(
1327
                                   int(keys.kCH_DOWN))]))
1328
            fkeys.append(chd)
1329

    
1330
            foot = RadioSetting("keys.kFOOT", "Foot switch",
1331
                               RadioSettingValueList(KEYS.values(),
1332
                               KEYS.values()[KEYS.keys().index(
1333
                                   int(keys.kCH_DOWN))]))
1334
            fkeys.append(foot)
1335

    
1336
        # this is the common buttons for all
1337

    
1338
        # 260G model don't have the front keys
1339
        if not "P2600" in self.TYPE:
1340
            scn_name = "SCN"
1341
            if self.TYPE[0] == "P":
1342
                scn_name = "Open Circle"
1343

    
1344
            scn = RadioSetting("keys.kSCN", scn_name,
1345
                               RadioSettingValueList(KEYS.values(),
1346
                               KEYS.values()[KEYS.keys().index(
1347
                                   int(keys.kSCN))]))
1348
            fkeys.append(scn)
1349

    
1350
            a_name = "A"
1351
            if self.TYPE[0] == "P":
1352
                a_name = "Closed circle"
1353

    
1354
            a = RadioSetting("keys.kA", a_name,
1355
                             RadioSettingValueList(KEYS.values(),
1356
                             KEYS.values()[KEYS.keys().index(
1357
                                 int(keys.kA))]))
1358
            fkeys.append(a)
1359

    
1360
            da_name = "D/A"
1361
            if self.TYPE[0] == "P":
1362
                da_name = "< key"
1363

    
1364
            da = RadioSetting("keys.kDA", da_name,
1365
                              RadioSettingValueList(KEYS.values(),
1366
                              KEYS.values()[KEYS.keys().index(
1367
                                  int(keys.kDA))]))
1368
            fkeys.append(da)
1369

    
1370
            gu_name = "Triangle up"
1371
            if self.TYPE[0] == "P":
1372
                gu_name = "Side 1"
1373

    
1374
            gu = RadioSetting("keys.kGROUP_UP", gu_name,
1375
                              RadioSettingValueList(KEYS.values(),
1376
                              KEYS.values()[KEYS.keys().index(
1377
                                  int(keys.kGROUP_UP))]))
1378
            fkeys.append(gu)
1379

    
1380
        # Side keys on portables
1381
        gd_name = "Triangle Down"
1382
        if self.TYPE[0] == "P":
1383
            gd_name = "> key"
1384

    
1385
        gd = RadioSetting("keys.kGROUP_DOWN", gd_name,
1386
                          RadioSettingValueList(KEYS.values(),
1387
                          KEYS.values()[KEYS.keys().index(
1388
                              int(keys.kGROUP_DOWN))]))
1389
        fkeys.append(gd)
1390

    
1391
        mon_name = "MON"
1392
        if self.TYPE[0] == "P":
1393
            mon_name = "Side 2"
1394

    
1395
        mon = RadioSetting("keys.kMON", mon_name,
1396
                           RadioSettingValueList(KEYS.values(),
1397
                           KEYS.values()[KEYS.keys().index(
1398
                               int(keys.kMON))]))
1399
        fkeys.append(mon)
1400

    
1401
        return top
1402

    
1403
    def set_settings(self, settings):
1404
        """Translate the settings in the UI into bit in the mem_struct
1405
        I don't understand well the method used in many drivers
1406
        so, I used mine, ugly but works ok"""
1407

    
1408
        mobj = self._memobj
1409

    
1410
        for element in settings:
1411
            if not isinstance(element, RadioSetting):
1412
                self.set_settings(element)
1413
                continue
1414

    
1415
            # Let's roll the ball
1416
            if "." in element.get_name():
1417
                inter, setting = element.get_name().split(".")
1418
                # you must ignore the settings with "not"
1419
                # this are READ ONLY attributes
1420
                if inter == "not":
1421
                    continue
1422

    
1423
                obj = getattr(mobj, inter)
1424
                value = element.value
1425

    
1426
                # integers case + special case
1427
                if setting in ["tot", "tot_alert", "min_vol", "tone_vol",
1428
                               "sql_level", "tot_rekey", "tot_reset"]:
1429
                    # catching the "off" values as zero
1430
                    try:
1431
                        value = int(value)
1432
                    except:
1433
                        value = 0
1434

    
1435
                    # tot case step 15
1436
                    if setting == "tot":
1437
                        value = value * 15
1438
                        # off is special
1439
                        if value == 0:
1440
                            value = 0x4b0
1441

    
1442
                    # Caso tone_vol
1443
                    if setting == "tone_vol":
1444
                        # off is special
1445
                        if value == 32:
1446
                            value = 0xff
1447

    
1448
                # Bool types + inverted
1449
                if setting in ["c2t", "poweron_tone", "control_tone",
1450
                               "warn_tone", "battery_save", "self_prog",
1451
                               "clone", "panel_test"]:
1452
                    value = bool(value)
1453

    
1454
                    # this cases are inverted
1455
                    if setting == "c2t":
1456
                        value = not value
1457

    
1458
                    # case battery save is special
1459
                    if setting == "battery_save":
1460
                        if bool(value) is True:
1461
                            value = 0x32
1462
                        else:
1463
                            value = 0xff
1464

    
1465
                # String cases
1466
                if setting in ["poweronmesg", "line1", "line2"]:
1467
                    # some vars
1468
                    value = str(value)
1469
                    just = 8
1470
                    # lines with 32
1471
                    if "line" in setting:
1472
                        just = 32
1473

    
1474
                    # empty case
1475
                    if len(value) == 0:
1476
                        value = "\xff" * just
1477
                    else:
1478
                        value = value.ljust(just)
1479

    
1480
                # case keys, with special config
1481
                if inter == "keys":
1482
                    value = KEYS.keys()[KEYS.values().index(str(value))]
1483

    
1484
            # Apply al configs done
1485
            setattr(obj, setting, value)
1486

    
1487
    def get_bank_model(self):
1488
        """Pass the bank model to the UI part"""
1489
        rf = self.get_features()
1490
        if rf.has_bank is True:
1491
            return Kenwood60GBankModel(self)
1492
        else:
1493
            return None
1494

    
1495
    def _get_bank(self, loc):
1496
        """Get the bank data for a specific channel"""
1497
        mem = self._memobj.memory[loc - 1]
1498
        bank = int(mem.bank) - 1
1499

    
1500
        if bank > self._num_banks or bank < 1:
1501
            # all channels must belong to a bank, even with just 1 bank
1502
            return 0
1503
        else:
1504
            return bank
1505

    
1506
    def _set_bank(self, loc, bank):
1507
        """Set the bank data for a specific channel"""
1508
        try:
1509
            b = int(bank)
1510
            if b > 127:
1511
                b = 0
1512
            mem = self._memobj.memory[loc - 1]
1513
            mem.bank = b + 1
1514
        except:
1515
            msg = "You can't have a channel without a bank, click another bank"
1516
            raise errors.InvalidDataError(msg)
1517

    
1518

    
1519
# This kenwwood family is known as "60-G Serie"
1520
# all this radios ending in G are compatible:
1521
#
1522
# Portables VHF TK-260G/270G/272G/278G
1523
# Portables UHF TK-360G/370G/372G/378G/388G
1524
#
1525
# Mobiles VHF TK-760G/762G/768G
1526
# Mobiles VHF TK-860G/862G/868G
1527
#
1528
# WARNING !!!! Radios With Password in the data section ###############
1529
#
1530
# When a radio has a data password (aka to program it) the last byte (#8)
1531
# in the id code change from \xf1 to \xb1; so we remove this last byte
1532
# from the identification procedures and variants.
1533
#
1534
# This effectively render the data password USELESS even if set.
1535
# Translation: Chirps will read and write password protected radios
1536
# with no problem.
1537

    
1538

    
1539
@directory.register
1540
class TK868G_Radios(Kenwood_Serie_60G):
1541
    """Kenwood TK-868G Radio M/C"""
1542
    MODEL = "TK-868G"
1543
    TYPE = "M8680"
1544
    VARIANTS = {
1545
        "M8680\x18\xff":    (8, 400, 490, "M"),
1546
        "M8680;\xff":       (128, 350, 390, "C1"),
1547
        "M86808\xff":       (128, 400, 430, "C2"),
1548
        "M86806\xff":       (128, 450, 490, "C3"),
1549
        }
1550

    
1551

    
1552
@directory.register
1553
class TK862G_Radios(Kenwood_Serie_60G):
1554
    """Kenwood TK-862G Radio K/E/(N)E"""
1555
    MODEL = "TK-862G"
1556
    TYPE = "M8620"
1557
    VARIANTS = {
1558
        "M8620\x06\xff":    (8, 450, 490, "K"),
1559
        "M8620\x07\xff":    (8, 485, 512, "K2"),
1560
        "M8620&\xff":       (8, 440, 470, "E"),
1561
        "M8620V\xff":       (8, 440, 470, "(N)E"),
1562
        }
1563

    
1564

    
1565
@directory.register
1566
class TK860G_Radios(Kenwood_Serie_60G):
1567
    """Kenwood TK-860G Radio K"""
1568
    MODEL = "TK-860G"
1569
    TYPE = "M8600"
1570
    VARIANTS = {
1571
        "M8600\x08\xff":    (128, 400, 430, "K"),
1572
        "M8600\x06\xff":    (128, 450, 490, "K1"),
1573
        "M8600\x07\xff":    (128, 485, 512, "K2"),
1574
        "M8600\x18\xff":    (128, 400, 430, "M"),
1575
        "M8600\x16\xff":    (128, 450, 490, "M1"),
1576
        "M8600\x17\xff":    (128, 485, 520, "M2"),
1577
        }
1578

    
1579

    
1580
@directory.register
1581
class TK768G_Radios(Kenwood_Serie_60G):
1582
    """Kenwood TK-768G Radios [M/C]"""
1583
    MODEL = "TK-768G"
1584
    TYPE = "M7680"
1585
    # Note that 8 CH don't have banks
1586
    VARIANTS = {
1587
        "M7680\x15\xff": (8, 136, 162, "M2"),
1588
        "M7680\x14\xff": (8, 148, 174, "M"),
1589
        "M76805\xff":    (128, 136, 162, "C2"),
1590
        "M76804\xff":    (128, 148, 174, "C"),
1591
        }
1592

    
1593

    
1594
@directory.register
1595
class TK762G_Radios(Kenwood_Serie_60G):
1596
    """Kenwood TK-762G Radios [K/E/NE]"""
1597
    MODEL = "TK-762G"
1598
    TYPE = "M7620"
1599
    # Note that 8 CH don't have banks
1600
    VARIANTS = {
1601
        "M7620\x05\xff": (8, 136, 162, "K2"),
1602
        "M7620\x04\xff": (8, 148, 172, "K"),
1603
        "M7620$\xff":    (8, 148, 172, "E"),
1604
        "M7620T\xff":    (8, 148, 172, "NE"),
1605
        }
1606

    
1607

    
1608
@directory.register
1609
class TK760G_Radios(Kenwood_Serie_60G):
1610
    """Kenwood TK-760G Radios [K/M/(N)E]"""
1611
    MODEL = "TK-760G"
1612
    TYPE = "m7600"
1613
    VARIANTS = {
1614
        "M7600\x05\xff": (128, 136, 162, "K2"),
1615
        "M7600\x04\xff": (128, 148, 174, "K"),
1616
        "M7600\x14\xff": (128, 148, 174, "M"),
1617
        "M7600T\xff":    (128, 148, 174, "NE"),
1618
        "m7600$\xfb":    (128, 148, 174, "K")
1619
        }
1620

    
1621

    
1622
@directory.register
1623
class TK388G_Radios(Kenwood_Serie_60G):
1624
    """Kenwood TK-388 Radio [K/E/M/NE]"""
1625
    MODEL = "TK-388G"
1626
    TYPE = "P3880"
1627
    VARIANTS = {
1628
        "P3880\x1b\xff": (128, 350, 370, "M")
1629
        }
1630

    
1631

    
1632
@directory.register
1633
class TK378G_Radios(Kenwood_Serie_60G):
1634
    """Kenwood TK-378 Radio [K/E/M/NE]"""
1635
    MODEL = "TK-378G"
1636
    TYPE = "P3780"
1637
    VARIANTS = {
1638
        "P3780\x16\xff": (16, 450, 470, "M"),
1639
        "P3780\x17\xff": (16, 400, 420, "M1"),
1640
        "P3780\x36\xff": (128, 490, 512, "C"),
1641
        "P3780\x39\xff": (128, 403, 430, "C1")
1642
        }
1643

    
1644

    
1645
@directory.register
1646
class TK372G_Radios(Kenwood_Serie_60G):
1647
    """Kenwood TK-372 Radio [K/E/M/NE]"""
1648
    MODEL = "TK-372G"
1649
    TYPE = "P3720"
1650
    VARIANTS = {
1651
        "P3720\x06\xff": (32, 450, 470, "K"),
1652
        "P3720\x07\xff": (32, 470, 490, "K1"),
1653
        "P3720\x08\xff": (32, 490, 512, "K2"),
1654
        "P3720\x09\xff": (32, 403, 430, "K3")
1655
        }
1656

    
1657

    
1658
@directory.register
1659
class TK370G_Radios(Kenwood_Serie_60G):
1660
    """Kenwood TK-370 Radio [K/E/M/NE]"""
1661
    MODEL = "TK-370G"
1662
    TYPE = "P3700"
1663
    VARIANTS = {
1664
        "P3700\x06\xff": (128, 450, 470, "K"),
1665
        "P3700\x07\xff": (128, 470, 490, "K1"),
1666
        "P3700\x08\xff": (128, 490, 512, "K2"),
1667
        "P3700\x09\xff": (128, 403, 430, "K3"),
1668
        "P3700\x16\xff": (128, 450, 470, "M"),
1669
        "P3700\x17\xff": (128, 470, 490, "M1"),
1670
        "P3700\x18\xff": (128, 490, 520, "M2"),
1671
        "P3700\x19\xff": (128, 403, 430, "M3"),
1672
        "P3700&\xff": (128, 440, 470, "E"),
1673
        "P3700V\xff": (128, 440, 470, "NE")
1674
        }
1675

    
1676

    
1677
@directory.register
1678
class TK360G_Radios(Kenwood_Serie_60G):
1679
    """Kenwood TK-360 Radio [K/E/M/NE]"""
1680
    MODEL = "TK-360G"
1681
    TYPE = "P3600"
1682
    VARIANTS = {
1683
        "P3600\x06\xff": (8, 450, 470, "K"),
1684
        "P3600\x07\xff": (8, 470, 490, "K1"),
1685
        "P3600\x08\xff": (8, 490, 512, "K2"),
1686
        "P3600\x09\xff": (8, 403, 430, "K3"),
1687
        "P3600&\xff": (8, 440, 470, "E"),
1688
        "P3600)\xff": (8, 406, 430, "E1"),
1689
        "P3600\x16\xff": (8, 450, 470, "M"),
1690
        "P3600\x17\xff": (8, 470, 490, "M1"),
1691
        "P3600\x19\xff": (8, 403, 430, "M2"),
1692
        "P3600V\xff": (8, 440, 470, "NE"),
1693
        "P3600Y\xff": (8, 403, 430, "NE1")
1694
        }
1695

    
1696

    
1697
@directory.register
1698
class TK278G_Radios(Kenwood_Serie_60G):
1699
    """Kenwood TK-278G Radio C/C1/M/M1"""
1700
    MODEL = "TK-278G"
1701
    TYPE = "P2780"
1702
    # Note that 16 CH don't have banks
1703
    VARIANTS = {
1704
        "P27805\xff":    (128, 136, 150, "C1"),
1705
        "P27804\xff":    (128, 150, 174, "C"),
1706
        "P2780\x15\xff": (16,  136, 150, "M1"),
1707
        "P2780\x14\xff": (16,  150, 174, "M")
1708
        }
1709

    
1710

    
1711
@directory.register
1712
class TK272G_Radios(Kenwood_Serie_60G):
1713
    """Kenwood TK-272G Radio K/K1"""
1714
    MODEL = "TK-272G"
1715
    TYPE = "P2720"
1716
    VARIANTS = {
1717
        "P2720\x05\xfb": (32, 136, 150, "K1"),
1718
        "P2720\x04\xfb": (32, 150, 174, "K")
1719
        }
1720

    
1721

    
1722
@directory.register
1723
class TK270G_Radios(Kenwood_Serie_60G):
1724
    """Kenwood TK-270G Radio K/K1/M/E/NE/NT"""
1725
    MODEL = "TK-270G"
1726
    TYPE = "P2700"
1727
    VARIANTS = {
1728
        "P2700T\xff":    (128, 146, 174, "NE/NT"),
1729
        "P2700$\xff":    (128, 146, 174, "E"),
1730
        "P2700\x14\xff": (128, 150, 174, "M"),
1731
        "P2700\x05\xff": (128, 136, 150, "K1"),
1732
        "P2700\x04\xff": (128, 150, 174, "K")
1733
        }
1734

    
1735

    
1736
@directory.register
1737
class TK260G_Radios(Kenwood_Serie_60G):
1738
    """Kenwood TK-260G Radio K/K1/M/E/NE/NT"""
1739
    MODEL = "TK-260G"
1740
    _hasbanks = False
1741
    TYPE = "P2600"
1742
    VARIANTS = {
1743
        "P2600U\xff":    (8, 136, 150, "N1"),
1744
        "P2600T\xff":    (8, 146, 174, "N"),
1745
        "P2600$\xff":    (8, 150, 174, "E"),
1746
        "P2600\x14\xff": (8, 150, 174, "M"),
1747
        "P2600\x05\xff": (8, 136, 150, "K1"),
1748
        "P2600\x04\xff": (8, 150, 174, "K")
1749
        }
(4-4/5)