Project

General

Profile

Bug #6195 ยป tk760g.py

Gene Vincent, 10/26/2018 12:09 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
from chirp import chirp_common, directory, memmap, errors, util, bitwise
17
from textwrap import dedent
18
from chirp.settings import RadioSettingGroup, RadioSetting, \
19
    RadioSettingValueBoolean, RadioSettingValueList, \
20
    RadioSettingValueString, RadioSettingValueInteger, \
21
    RadioSettings
22

    
23
import logging
24
import struct
25
import time
26
import sys
27

    
28
LOG = logging.getLogger(__name__)
29

    
30

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
299
# For debugging purposes
300
debug = False
301

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

    
334

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

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

    
347
    return data
348

    
349

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

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

    
361

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

    
366

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

    
374

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

    
379

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

    
384

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

    
399

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

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

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

    
419

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

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

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

    
452

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
520

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

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

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

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

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

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

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

    
560
        data += d
561

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

    
566
        count += 1
567

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

    
571

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

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

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

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

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

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

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

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

    
623
        _send(radio, frame)
624

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

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

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

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

    
639
    _close_radio(radio)
640

    
641

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

    
651

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

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

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

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

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

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

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

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

    
692

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

    
699

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

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

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

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

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

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

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

    
814
        # building the data for the memmap
815
        fdata = ""
816

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
900
    def sync_out(self):
901
        """Do an upload to the radio eeprom"""
902

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

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

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

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

    
923
        # to set the vars on the class to the correct ones
924
        self._set_variant()
925

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

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

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

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

    
969
        return result
970

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

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

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

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

    
991
        # Memory number
992
        mem.number = number
993

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

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

    
1022
        # name TAG of the channel
1023
        mem.name = str(_mem.name).rstrip()
1024

    
1025
        # power
1026
        mem.power = POWER_LEVELS[_mem.power]
1027

    
1028
        # wide/marrow
1029
        mem.mode = MODES[_mem.wide]
1030

    
1031
        # skip
1032
        mem.skip = self._get_scan(number - 1)
1033

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

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

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

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

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

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

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

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

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

    
1076
        return mem
1077

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

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

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

    
1090
        # frequency
1091
        _mem.rxfreq = mem.freq / 10
1092

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

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

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

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

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

    
1129
        _mem.power = POWER_LEVELS.index(mem.power)
1130

    
1131
        # wide/marrow
1132
        _mem.wide = MODES.index(mem.mode)
1133

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

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

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

    
1147
        # all data get sync after channel mod
1148
        self._prep_data()
1149

    
1150
        return mem
1151

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

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

    
1161
        # testing the firmware model fingerprint
1162
        match_model = model_match(cls, filedata)
1163

    
1164
        if match_size and match_model:
1165
            return True
1166
        else:
1167
            return False
1168

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

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

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

    
1188
        top = RadioSettings(basic, dealer, fkeys)
1189

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1338
        # this is the common buttons for all
1339

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

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

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

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

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

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

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

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

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

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

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

    
1396
        mon = RadioSetting("keys.kMON", mon_name,
1397
                           RadioSettingValueList(KEYS.values(), KEYS.values()
1398
                               [KEYS.keys().index(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
# A note on the ID codes aka "VARIANTS" list below:
1529
# the ID was at first 8 bytes long, but now it has only 6.
1530
# 
1531
# > The 7th byte of the ident was \xff when the radio is a vanilla version
1532
#   and change to other values when trunking or other board is in place.
1533
#
1534
#   Chirp ignore this byte, as the precence of such board has no effect
1535
#   on the normnal radio operation.
1536
#
1537
# > The 8th byte of the ident denoted the precence of a data password in the
1538
#   radio (aka to program it), we don't use it any more and this effectively
1539
#   render the data password USELESS even if set.
1540
#
1541
#   Translation: Chirps will read and write password protected radios
1542
#   with no problem.
1543

    
1544

    
1545
@directory.register
1546
class TK868G_Radios(Kenwood_Serie_60G):
1547
    """Kenwood TK-868G Radio M/C"""
1548
    MODEL = "TK-868G"
1549
    TYPE = "M8680"
1550
    VARIANTS = {
1551
        "M8680\x18": (8, 400, 490, "M"),
1552
        "M8680;":    (128, 350, 390, "C1"),
1553
        "M86808":    (128, 400, 430, "C2"),
1554
        "M86806":    (128, 450, 490, "C3"),
1555
        }
1556

    
1557

    
1558
@directory.register
1559
class TK862G_Radios(Kenwood_Serie_60G):
1560
    """Kenwood TK-862G Radio K/E/(N)E"""
1561
    MODEL = "TK-862G"
1562
    TYPE = "M8620"
1563
    VARIANTS = {
1564
        "M8620\x06": (8, 450, 490, "K"),
1565
        "M8620\x07": (8, 485, 512, "K2"),
1566
        "M8620&":    (8, 440, 470, "E"),
1567
        "M8620V":    (8, 440, 470, "(N)E"),
1568
        }
1569

    
1570

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

    
1585

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

    
1599

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

    
1613

    
1614
@directory.register
1615
class TK760G_Radios(Kenwood_Serie_60G):
1616
    """Kenwood TK-760G Radios [K/M/(N)E]"""
1617
    MODEL = "TK-760G"
1618
    TYPE = "M7600"
1619
    VARIANTS = {
1620
        "M7600\x05": (128, 136, 162, "K2"),
1621
        "M7600\x04": (128, 148, 174, "K"),
1622
        "M7600\x14": (128, 148, 174, "M"),
1623
        "M7600T":    (128, 148, 174, "NE")
1624
        }
1625

    
1626

    
1627
@directory.register
1628
class TK388G_Radios(Kenwood_Serie_60G):
1629
    """Kenwood TK-388 Radio [K/E/M/NE]"""
1630
    MODEL = "TK-388G"
1631
    TYPE = "P3880"
1632
    VARIANTS = {
1633
        "P3880\x1b": (128, 350, 370, "M")
1634
        }
1635

    
1636

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

    
1649

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

    
1662

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

    
1681

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

    
1701

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

    
1715

    
1716
@directory.register
1717
class TK272G_Radios(Kenwood_Serie_60G):
1718
    """Kenwood TK-272G Radio K/K1"""
1719
    MODEL = "TK-272G"
1720
    TYPE = "P2720"
1721
    VARIANTS = {
1722
        "P2720\x05": (32, 136, 150, "K1"),
1723
        "P2720\x04": (32, 150, 174, "K")
1724
        }
1725

    
1726

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

    
1740

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