Project

General

Profile

New Model #5825 » tk760g.py

Latest dev driver with support and bug fixes - Pavel Milanes, 06/04/2018 07:03 AM

 
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 sys.platform not 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
    if exito is False:
486
        _close_radio(radio)
487
        LOG.debug("Radio did not accepted PROGRAM command in %s atempts" %
488
            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)[0:4]
496

    
497
    if not (rid in radio.TYPE):
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 does not match %s" %
507
            (rid.strip("\xff"), str(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:0xAD]
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,
700
        chirp_common.ExperimentalRadio):
701
    """Kenwood Serie 60G Radios base class"""
702
    VENDOR = "Kenwood"
703
    BAUD_RATE = 9600
704
    _memsize = MEM_SIZE
705
    NAME_LENGTH = 8
706
    _range = [136000000, 162000000]
707
    _upper = 128
708
    _chs_progs = 0
709
    _num_banks = 128
710
    _bclass = memBank
711
    _kind = ""
712
    VARIANT = ""
713
    MODEL = ""
714

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

    
746
    def get_features(self):
747
        """Return information about this radio's features"""
748
        rf = chirp_common.RadioFeatures()
749
        rf.has_settings = True
750

    
751
        # has banks? 8 & 16 CH don't have banks
752
        if self._upper < 32:
753
            rf.has_bank = False
754
        else:
755
            rf.has_bank = True
756

    
757
        rf.has_tuning_step = False
758

    
759
        # there is a known TK-360G with no LCD, hence no names
760
        if "p3600&" in self.VARIANTS:
761
            rf.has_name = False
762
        else:
763
            # the rest has names an LCDs..
764
            rf.has_name = True
765

    
766
        rf.has_offset = True
767
        rf.has_mode = True
768
        rf.has_dtcs = True
769
        rf.has_rx_dtcs = True
770
        rf.has_dtcs_polarity = True
771
        rf.has_ctone = True
772
        rf.has_cross = True
773
        rf.valid_modes = MODES
774
        rf.valid_duplexes = ["", "-", "+", "off"]
775
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
776
        rf.valid_cross_modes = [
777
            "Tone->Tone",
778
            "DTCS->",
779
            "->DTCS",
780
            "Tone->DTCS",
781
            "DTCS->Tone",
782
            "->Tone",
783
            "DTCS->DTCS"]
784
        rf.valid_power_levels = POWER_LEVELS
785
        rf.valid_characters = VALID_CHARS
786
        rf.valid_skips = SKIP_VALUES
787
        rf.valid_dtcs_codes = DTCS_CODES
788
        rf.valid_bands = [self._range]
789
        rf.valid_name_length = 8
790
        rf.memory_bounds = (1, self._upper)
791
        return rf
792

    
793
    def _fill(self, offset, data):
794
        """Fill an specified area of the memmap with the passed data"""
795
        for addr in range(0, len(data)):
796
            self._mmap[offset + addr] = data[addr]
797

    
798
    def _prep_data(self):
799
        """Prepare the areas in the memmap to do a consistend write
800
        it has to make an update on the x300 area with banks and channel
801
        info; other in the x1000 with banks and channel counts
802
        and a last one in x7000 with flag data"""
803
        rchs = 0
804
        data = dict()
805

    
806
        # sorting the data
807
        for ch in range(0, self._upper):
808
            mem = self._memobj.memory[ch]
809
            bnumb = int(mem.bnumb)
810
            bank = int(mem.bank)
811
            if bnumb != 255 and (bank != 255 and bank != 0):
812
                try:
813
                    data[bank].append(ch)
814
                except:
815
                    data[bank] = list()
816
                    data[bank].append(ch)
817
                data[bank].sort()
818
                # counting the real channels
819
                rchs = rchs + 1
820

    
821
        # updating the channel/bank count
822
        self._memobj.settings.channels = rchs
823
        self._chs_progs = rchs
824
        self._memobj.settings.banks = len(data)
825

    
826
        # building the data for the memmap
827
        fdata = ""
828

    
829
        for k, v in data.iteritems():
830
            # posible bad data
831
            if k == 0:
832
                k = 1
833
                raise errors.InvalidValueError(
834
                    "Invalid bank value '%k', bad data in the image? \
835
                    Trying to fix this, review your bank data!" % k)
836
            c = 1
837
            for i in v:
838
                fdata += chr(k) + chr(c) + chr(k - 1) + chr(i)
839
                c = c + 1
840

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

    
844
        # updating the data in the memmap [x300]
845
        self._fill(0x300, fdata)
846

    
847
        # update the info in x1000; it has 2 bytes with
848
        # x00 = bank , x01 = bank's channel count
849
        # the rest of the 14 bytes are \xff
850
        bdata = ""
851
        for i in range(1, len(data) + 1):
852
            line = chr(i) + chr(len(data[i]))
853
            line += "\xff" * 14
854
            bdata += line
855

    
856
        # fill to match a full 256 bytes block
857
        bdata += (256 - (len(bdata)) % 256) * "\xFF"
858

    
859
        # fill to match the whole area
860
        bdata += (16 - len(bdata) / 256) * EMPTY_BLOCK
861

    
862
        # updating the data in the memmap [x1000]
863
        self._fill(0x1000, bdata)
864

    
865
        # DTMF id for each channel, 5 bytes lbcd at x7000
866
        # ############## TODO ###################
867
        fldata = "\x00\xf0\xff\xff\xff" * self._chs_progs + \
868
            "\xff" * (5 * (self._upper - self._chs_progs))
869

    
870
        # write it
871
        # updating the data in the memmap [x7000]
872
        self._fill(0x7000, fldata)
873

    
874
    def _set_variant(self):
875
        """Select and set the correct variables for the class acording
876
        to the correct variant of the radio"""
877
        rid = self._mmap[0xA7:0xAD]
878

    
879
        # indentify the radio variant and set the enviroment to it's values
880
        try:
881
            self._upper, low, high, self._kind = self.VARIANTS[rid]
882

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

    
889
            # put the VARIANT in the class, clean the model / CHs / Type
890
            # in the same layout as the KPG program
891
            self._VARIANT = self.MODEL + " [" + str(self._upper) + "CH]: "
892
            # In the OEM string we show the real OEM ranges
893
            self._VARIANT += self._kind + ", %d - %d MHz" % (low, high)
894

    
895
        except KeyError:
896
            LOG.debug("Wrong Kenwood radio, ID or unknown variant")
897
            LOG.debug(util.hexprint(rid))
898
            raise errors.RadioError(
899
                "Wrong Kenwood radio, ID or unknown variant, see LOG output.")
900
            return False
901

    
902
    def sync_in(self):
903
        """Do a download of the radio eeprom"""
904
        self._mmap = do_download(self)
905
        self.process_mmap()
906

    
907
    def sync_out(self):
908
        """Do an upload to the radio eeprom"""
909

    
910
        # chirp signature on the eprom ;-)
911
        sign = "Chirp"
912
        self._fill(0xbb, sign)
913

    
914
        try:
915
            self._prep_data()
916
            do_upload(self)
917
        except errors.RadioError:
918
            raise
919
        except Exception, e:
920
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
921

    
922
    def process_mmap(self):
923
        """Process the memory object"""
924
        # how many channels are programed
925
        self._chs_progs = ord(self._mmap[15])
926

    
927
        # load the memobj
928
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
929

    
930
        # to set the vars on the class to the correct ones
931
        self._set_variant()
932

    
933
    def get_raw_memory(self, number):
934
        """Return a raw representation of the memory object, which
935
        is very helpful for development"""
936
        return repr(self._memobj.memory[number])
937

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

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

    
966
    def _get_scan(self, chan):
967
        """Get the channel scan status from the 16 bytes array on the eeprom
968
        then from the bits on the byte, return '' or 'S' as needed"""
969
        result = "S"
970
        byte = int(chan/8)
971
        bit = chan % 8
972
        res = self._memobj.settings.add[byte] & (pow(2, bit))
973
        if res > 0:
974
            result = ""
975

    
976
        return result
977

    
978
    def _set_scan(self, chan, value):
979
        """Set the channel scan status from UI to the mem_map"""
980
        byte = int(chan/8)
981
        bit = chan % 8
982

    
983
        # get the actual value to see if I need to change anything
984
        actual = self._get_scan(chan)
985
        if actual != value:
986
            # I have to flip the value
987
            rbyte = self._memobj.settings.add[byte]
988
            rbyte = rbyte ^ pow(2, bit)
989
            self._memobj.settings.add[byte] = rbyte
990

    
991
    def get_memory(self, number):
992
        # Get a low-level memory object mapped to the image
993
        _mem = self._memobj.memory[number - 1]
994

    
995
        # Create a high-level memory object to return to the UI
996
        mem = chirp_common.Memory()
997

    
998
        # Memory number
999
        mem.number = number
1000

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

    
1010
        # Freq and offset
1011
        mem.freq = int(_mem.rxfreq) * 10
1012
        # tx freq can be blank
1013
        if _mem.get_raw()[16] == "\xFF":
1014
            # TX freq not set
1015
            mem.offset = 0
1016
            mem.duplex = "off"
1017
        else:
1018
            # TX feq set
1019
            offset = (int(_mem.txfreq) * 10) - mem.freq
1020
            if offset < 0:
1021
                mem.offset = abs(offset)
1022
                mem.duplex = "-"
1023
            elif offset > 0:
1024
                mem.offset = offset
1025
                mem.duplex = "+"
1026
            else:
1027
                mem.offset = 0
1028

    
1029
        # name TAG of the channel
1030
        mem.name = str(_mem.name).rstrip()
1031

    
1032
        # power
1033
        mem.power = POWER_LEVELS[_mem.power]
1034

    
1035
        # wide/marrow
1036
        mem.mode = MODES[_mem.wide]
1037

    
1038
        # skip
1039
        mem.skip = self._get_scan(number - 1)
1040

    
1041
        # tone data
1042
        rxtone = txtone = None
1043
        txtone = self._decode_tone(_mem.tx_tone)
1044
        rxtone = self._decode_tone(_mem.rx_tone)
1045
        chirp_common.split_tone_decode(mem, txtone, rxtone)
1046

    
1047
        # Extra
1048
        # bank and number in the channel
1049
        mem.extra = RadioSettingGroup("extra", "Extra")
1050

    
1051
        # validate bank
1052
        b = int(_mem.bank)
1053
        if b > 127 or b == 0:
1054
            _mem.bank = b = 1
1055

    
1056
        bank = RadioSetting("bank", "Bank it belongs",
1057
                            RadioSettingValueInteger(1, 128, b))
1058
        mem.extra.append(bank)
1059

    
1060
        # validate bnumb
1061
        if int(_mem.bnumb) > 127:
1062
            _mem.bank = mem.number
1063

    
1064
        bnumb = RadioSetting("bnumb", "Ch number in the bank",
1065
                             RadioSettingValueInteger(0, 127, _mem.bnumb))
1066
        mem.extra.append(bnumb)
1067

    
1068
        bs = RadioSetting("beat_shift", "Beat shift",
1069
                          RadioSettingValueBoolean(
1070
                              not bool(_mem.beat_shift)))
1071
        mem.extra.append(bs)
1072

    
1073
        cp = RadioSetting("compander", "Compander",
1074
                          RadioSettingValueBoolean(
1075
                              not bool(_mem.compander)))
1076
        mem.extra.append(cp)
1077

    
1078
        bl = RadioSetting("busy_lock", "Busy Channel lock",
1079
                          RadioSettingValueBoolean(
1080
                              not bool(_mem.busy_lock)))
1081
        mem.extra.append(bl)
1082

    
1083
        return mem
1084

    
1085
    def set_memory(self, mem):
1086
        """Set the memory data in the eeprom img from the UI
1087
        not ready yet, so it will return as is"""
1088

    
1089
        # get the eprom representation of this channel
1090
        _mem = self._memobj.memory[mem.number - 1]
1091

    
1092
        # if empty memmory
1093
        if mem.empty:
1094
            _mem.set_raw("\xFF" * 48)
1095
            return
1096

    
1097
        # frequency
1098
        _mem.rxfreq = mem.freq / 10
1099

    
1100
        # this are a mistery yet, but so falr there is no impact
1101
        # whit this default values for new channels
1102
        if int(_mem.rx_unkw) == 0xff:
1103
            _mem.rx_unkw = 0x35
1104
            _mem.tx_unkw = 0x32
1105

    
1106
        # duplex
1107
        if mem.duplex == "+":
1108
            _mem.txfreq = (mem.freq + mem.offset) / 10
1109
        elif mem.duplex == "-":
1110
            _mem.txfreq = (mem.freq - mem.offset) / 10
1111
        elif mem.duplex == "off":
1112
            for byte in _mem.txfreq:
1113
                byte.set_raw("\xFF")
1114
        else:
1115
            _mem.txfreq = mem.freq / 10
1116

    
1117
        # tone data
1118
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
1119
            chirp_common.split_tone_encode(mem)
1120
        self._encode_tone(_mem.tx_tone, txmode, txtone, txpol)
1121
        self._encode_tone(_mem.rx_tone, rxmode, rxtone, rxpol)
1122

    
1123
        # name TAG of the channel
1124
        _namelength = self.get_features().valid_name_length
1125
        for i in range(_namelength):
1126
            try:
1127
                _mem.name[i] = mem.name[i]
1128
            except IndexError:
1129
                _mem.name[i] = "\x20"
1130

    
1131
        # power
1132
        # default power is low
1133
        if mem.power is None:
1134
            mem.power = POWER_LEVELS[0]
1135

    
1136
        _mem.power = POWER_LEVELS.index(mem.power)
1137

    
1138
        # wide/marrow
1139
        _mem.wide = MODES.index(mem.mode)
1140

    
1141
        # scan add property
1142
        self._set_scan(mem.number - 1, mem.skip)
1143

    
1144
        # bank and number in the channel
1145
        if int(_mem.bnumb) == 0xff:
1146
            _mem.bnumb = mem.number - 1
1147
            _mem.bank = 1
1148

    
1149
        # extra settings
1150
        for setting in mem.extra:
1151
            if setting != "bank" or setting != "bnumb":
1152
                setattr(_mem, setting.get_name(), not bool(setting.value))
1153

    
1154
        # all data get sync after channel mod
1155
        self._prep_data()
1156

    
1157
        return mem
1158

    
1159
    @classmethod
1160
    def match_model(cls, filedata, filename):
1161
        match_size = False
1162
        match_model = False
1163

    
1164
        # testing the file data size
1165
        if len(filedata) == MEM_SIZE:
1166
            match_size = True
1167

    
1168
        # testing the firmware model fingerprint
1169
        match_model = model_match(cls, filedata)
1170

    
1171
        if match_size and match_model:
1172
            return True
1173
        else:
1174
            return False
1175

    
1176
    def get_settings(self):
1177
        """Translate the bit in the mem_struct into settings in the UI"""
1178
        sett = self._memobj.settings
1179
        mess = self._memobj.message
1180
        keys = self._memobj.keys
1181
        idm = self._memobj.id
1182
        passwd = self._memobj.passwords
1183

    
1184
        # test to find if it's a mobile or portable
1185
        ismobile = False     # hence portable
1186
        if "M" in self.TYPE[0][0]:
1187
            ismobile = True
1188

    
1189
        # basic features of the radio
1190
        basic = RadioSettingGroup("basic", "Basic Settings")
1191
        # dealer settings
1192
        dealer = RadioSettingGroup("dealer", "Dealer Settings")
1193
        # buttons
1194
        fkeys = RadioSettingGroup("keys", "Front keys config")
1195

    
1196
        # TODO / PLANED
1197
        # adjust feqs
1198
        # freqs = RadioSettingGroup("freqs", "Adjust Frequencies")
1199

    
1200
        top = RadioSettings(basic, dealer, fkeys)
1201

    
1202
        # Basic
1203
        tot = RadioSetting("settings.tot", "Time Out Timer (TOT)",
1204
                           RadioSettingValueList(TOT, TOT[TOT.index(str(int(
1205
                           sett.tot)))]))
1206
        basic.append(tot)
1207

    
1208
        totalert = RadioSetting("settings.tot_alert", "TOT pre alert",
1209
                                RadioSettingValueList(TOT_PRE,
1210
                                TOT_PRE[int(sett.tot_alert)]))
1211
        basic.append(totalert)
1212

    
1213
        totrekey = RadioSetting("settings.tot_rekey", "TOT re-key time",
1214
                                RadioSettingValueList(TOT_REKEY,
1215
                                TOT_REKEY[int(sett.tot_rekey)]))
1216
        basic.append(totrekey)
1217

    
1218
        totreset = RadioSetting("settings.tot_reset", "TOT reset time",
1219
                                RadioSettingValueList(TOT_RESET,
1220
                                TOT_RESET[int(sett.tot_reset)]))
1221
        basic.append(totreset)
1222

    
1223
        # this feature is for mobile only
1224
        if ismobile:
1225
            minvol = RadioSetting("settings.min_vol", "Minimum volume",
1226
                                  RadioSettingValueList(VOL,
1227
                                  VOL[int(sett.min_vol)]))
1228
            basic.append(minvol)
1229

    
1230
            tv = int(sett.tone_vol)
1231
            if tv == 255:
1232
                tv = 32
1233
            tvol = RadioSetting("settings.tone_vol", "Minimum tone volume",
1234
                                RadioSettingValueList(TVOL, TVOL[tv]))
1235
            basic.append(tvol)
1236

    
1237
        sql = RadioSetting("settings.sql_level", "SQL Ref Level",
1238
                           RadioSettingValueList(SQL, SQL[int(sett.sql_level)]))
1239
        basic.append(sql)
1240

    
1241
        # c2t = RadioSetting("settings.c2t", "Clear to Transpond",
1242
        #                    RadioSettingValueBoolean(not sett.c2t))
1243
        # basic.append(c2t)
1244

    
1245
        ptone = RadioSetting("settings.poweron_tone", "Power On tone",
1246
                             RadioSettingValueBoolean(sett.poweron_tone))
1247
        basic.append(ptone)
1248

    
1249
        ctone = RadioSetting("settings.control_tone", "Control (key) tone",
1250
                             RadioSettingValueBoolean(sett.control_tone))
1251
        basic.append(ctone)
1252

    
1253
        wtone = RadioSetting("settings.warn_tone", "Warning tone",
1254
                             RadioSettingValueBoolean(sett.warn_tone))
1255
        basic.append(wtone)
1256

    
1257
        # Save Battery only for portables?
1258
        if not ismobile:
1259
            bs = int(sett.battery_save) == 0x32 and True or False
1260
            bsave = RadioSetting("settings.battery_save", "Battery Saver",
1261
                                 RadioSettingValueBoolean(bs))
1262
            basic.append(bsave)
1263

    
1264
        ponm = str(sett.poweronmesg).strip("\xff")
1265
        pom = RadioSetting("settings.poweronmesg", "Power on message",
1266
                           RadioSettingValueString(0, 8, ponm, False))
1267
        basic.append(pom)
1268

    
1269
        # dealer
1270
        valid_chars = ",-/:[]" + chirp_common.CHARSET_ALPHANUMERIC
1271
        mstr = "".join([c for c in self._VARIANT if c in valid_chars])
1272

    
1273
        val = RadioSettingValueString(0, 35, mstr)
1274
        val.set_mutable(False)
1275
        mod = RadioSetting("not.mod", "Radio Version", val)
1276
        dealer.append(mod)
1277

    
1278
        sn = str(idm.serial).strip(" \xff")
1279
        val = RadioSettingValueString(0, 8, sn)
1280
        val.set_mutable(False)
1281
        serial = RadioSetting("not.serial", "Serial number", val)
1282
        dealer.append(serial)
1283

    
1284
        svp = str(sett.lastsoftversion).strip(" \xff")
1285
        val = RadioSettingValueString(0, 5, svp)
1286
        val.set_mutable(False)
1287
        sver = RadioSetting("not.softver", "Software Version", val)
1288
        dealer.append(sver)
1289

    
1290
        l1 = str(mess.line1).strip(" \xff")
1291
        line1 = RadioSetting("message.line1", "Comment 1",
1292
                           RadioSettingValueString(0, 32, l1))
1293
        dealer.append(line1)
1294

    
1295
        l2 = str(mess.line2).strip(" \xff")
1296
        line2 = RadioSetting("message.line2", "Comment 2",
1297
                             RadioSettingValueString(0, 32, l2))
1298
        dealer.append(line2)
1299

    
1300
        sprog = RadioSetting("settings.self_prog", "Self program",
1301
                             RadioSettingValueBoolean(sett.self_prog))
1302
        dealer.append(sprog)
1303

    
1304
        clone = RadioSetting("settings.clone", "Allow clone",
1305
                             RadioSettingValueBoolean(sett.clone))
1306
        dealer.append(clone)
1307

    
1308
        panel = RadioSetting("settings.panel_test", "Panel Test",
1309
                             RadioSettingValueBoolean(sett.panel_test))
1310
        dealer.append(panel)
1311

    
1312
        fmw = RadioSetting("settings.firmware_prog", "Firmware program",
1313
                           RadioSettingValueBoolean(sett.firmware_prog))
1314
        dealer.append(fmw)
1315

    
1316
        # front keys
1317
        # The Mobile only parameters are wraped here
1318
        if ismobile:
1319
            vu = RadioSetting("keys.kVOL_UP", "VOL UP",
1320
                              RadioSettingValueList(KEYS.values(),
1321
                              KEYS.values()[KEYS.keys().index(
1322
                                  int(keys.kVOL_UP))]))
1323
            fkeys.append(vu)
1324

    
1325
            vd = RadioSetting("keys.kVOL_DOWN", "VOL DOWN",
1326
                              RadioSettingValueList(KEYS.values(),
1327
                              KEYS.values()[KEYS.keys().index(
1328
                                  int(keys.kVOL_DOWN))]))
1329
            fkeys.append(vd)
1330

    
1331
            chu = RadioSetting("keys.kCH_UP", "CH UP",
1332
                               RadioSettingValueList(KEYS.values(),
1333
                               KEYS.values()[KEYS.keys().index(
1334
                                   int(keys.kCH_UP))]))
1335
            fkeys.append(chu)
1336

    
1337
            chd = RadioSetting("keys.kCH_DOWN", "CH DOWN",
1338
                               RadioSettingValueList(KEYS.values(),
1339
                               KEYS.values()[KEYS.keys().index(
1340
                                   int(keys.kCH_DOWN))]))
1341
            fkeys.append(chd)
1342

    
1343
            foot = RadioSetting("keys.kFOOT", "Foot switch",
1344
                               RadioSettingValueList(KEYS.values(),
1345
                               KEYS.values()[KEYS.keys().index(
1346
                                   int(keys.kCH_DOWN))]))
1347
            fkeys.append(foot)
1348

    
1349
        # this is the common buttons for all
1350

    
1351
        # 260G model don't have the front keys
1352
        if "P2600" not in self.TYPE:
1353
            scn_name = "SCN"
1354
            if not ismobile:
1355
                scn_name = "Open Circle"
1356

    
1357
            scn = RadioSetting("keys.kSCN", scn_name,
1358
                               RadioSettingValueList(KEYS.values(),
1359
                               KEYS.values()[KEYS.keys().index(
1360
                                   int(keys.kSCN))]))
1361
            fkeys.append(scn)
1362

    
1363
            a_name = "A"
1364
            if not ismobile:
1365
                a_name = "Closed circle"
1366

    
1367
            a = RadioSetting("keys.kA", a_name,
1368
                             RadioSettingValueList(KEYS.values(),
1369
                             KEYS.values()[KEYS.keys().index(
1370
                                 int(keys.kA))]))
1371
            fkeys.append(a)
1372

    
1373
            da_name = "D/A"
1374
            if not ismobile:
1375
                da_name = "< key"
1376

    
1377
            da = RadioSetting("keys.kDA", da_name,
1378
                              RadioSettingValueList(KEYS.values(),
1379
                              KEYS.values()[KEYS.keys().index(
1380
                                  int(keys.kDA))]))
1381
            fkeys.append(da)
1382

    
1383
            gu_name = "Triangle up"
1384
            if not ismobile:
1385
                gu_name = "Side 1"
1386

    
1387
            gu = RadioSetting("keys.kGROUP_UP", gu_name,
1388
                              RadioSettingValueList(KEYS.values(),
1389
                              KEYS.values()[KEYS.keys().index(
1390
                                  int(keys.kGROUP_UP))]))
1391
            fkeys.append(gu)
1392

    
1393
        # Side keys on portables
1394
        gd_name = "Triangle Down"
1395
        if not ismobile:
1396
            gd_name = "> key"
1397

    
1398
        gd = RadioSetting("keys.kGROUP_DOWN", gd_name,
1399
                          RadioSettingValueList(KEYS.values(),
1400
                          KEYS.values()[KEYS.keys().index(
1401
                              int(keys.kGROUP_DOWN))]))
1402
        fkeys.append(gd)
1403

    
1404
        mon_name = "MON"
1405
        if not ismobile:
1406
            mon_name = "Side 2"
1407

    
1408
        mon = RadioSetting("keys.kMON", mon_name,
1409
                           RadioSettingValueList(KEYS.values(),
1410
                           KEYS.values()[KEYS.keys().index(
1411
                               int(keys.kMON))]))
1412
        fkeys.append(mon)
1413

    
1414
        return top
1415

    
1416
    def set_settings(self, settings):
1417
        """Translate the settings in the UI into bit in the mem_struct
1418
        I don't understand well the method used in many drivers
1419
        so, I used mine, ugly but works ok"""
1420

    
1421
        mobj = self._memobj
1422

    
1423
        for element in settings:
1424
            if not isinstance(element, RadioSetting):
1425
                self.set_settings(element)
1426
                continue
1427

    
1428
            # Let's roll the ball
1429
            if "." in element.get_name():
1430
                inter, setting = element.get_name().split(".")
1431
                # you must ignore the settings with "not"
1432
                # this are READ ONLY attributes
1433
                if inter == "not":
1434
                    continue
1435

    
1436
                obj = getattr(mobj, inter)
1437
                value = element.value
1438

    
1439
                # integers case + special case
1440
                if setting in ["tot", "tot_alert", "min_vol", "tone_vol",
1441
                               "sql_level", "tot_rekey", "tot_reset"]:
1442
                    # catching the "off" values as zero
1443
                    try:
1444
                        value = int(value)
1445
                    except:
1446
                        value = 0
1447

    
1448
                    # tot case step 15
1449
                    if setting == "tot":
1450
                        value = value * 15
1451
                        # off is special
1452
                        if value == 0:
1453
                            value = 0x4b0
1454

    
1455
                    # Caso tone_vol
1456
                    if setting == "tone_vol":
1457
                        # off is special
1458
                        if value == 32:
1459
                            value = 0xff
1460

    
1461
                # Bool types + inverted
1462
                if setting in ["c2t", "poweron_tone", "control_tone",
1463
                               "warn_tone", "battery_save", "self_prog",
1464
                               "clone", "panel_test"]:
1465
                    value = bool(value)
1466

    
1467
                    # this cases are inverted
1468
                    if setting == "c2t":
1469
                        value = not value
1470

    
1471
                    # case battery save is special
1472
                    if setting == "battery_save":
1473
                        if bool(value) is True:
1474
                            value = 0x32
1475
                        else:
1476
                            value = 0xff
1477

    
1478
                # String cases
1479
                if setting in ["poweronmesg", "line1", "line2"]:
1480
                    # some vars
1481
                    value = str(value)
1482
                    just = 8
1483
                    # lines with 32
1484
                    if "line" in setting:
1485
                        just = 32
1486

    
1487
                    # empty case
1488
                    if len(value) == 0:
1489
                        value = "\xff" * just
1490
                    else:
1491
                        value = value.ljust(just)
1492

    
1493
                # case keys, with special config
1494
                if inter == "keys":
1495
                    value = KEYS.keys()[KEYS.values().index(str(value))]
1496

    
1497
            # Apply al configs done
1498
            setattr(obj, setting, value)
1499

    
1500
    def get_bank_model(self):
1501
        """Pass the bank model to the UI part"""
1502
        rf = self.get_features()
1503
        if rf.has_bank is True:
1504
            return Kenwood60GBankModel(self)
1505
        else:
1506
            return None
1507

    
1508
    def _get_bank(self, loc):
1509
        """Get the bank data for a specific channel"""
1510
        mem = self._memobj.memory[loc - 1]
1511
        bank = int(mem.bank) - 1
1512

    
1513
        if bank > self._num_banks or bank < 1:
1514
            # all channels must belong to a bank, even with just 1 bank
1515
            return 0
1516
        else:
1517
            return bank
1518

    
1519
    def _set_bank(self, loc, bank):
1520
        """Set the bank data for a specific channel"""
1521
        try:
1522
            b = int(bank)
1523
            if b > 127:
1524
                b = 0
1525
            mem = self._memobj.memory[loc - 1]
1526
            mem.bank = b + 1
1527
        except:
1528
            msg = "You can't have a channel without a bank, click another bank"
1529
            raise errors.InvalidDataError(msg)
1530

    
1531

    
1532
# This kenwwood family is known as "60-G Serie"
1533
# all this radios ending in G are compatible:
1534
#
1535
# Portables VHF TK-260G/270G/272G/278G
1536
# Portables UHF TK-360G/370G/372G/378G/388G
1537
#
1538
# Mobiles VHF TK-760G/762G/768G
1539
# Mobiles VHF TK-860G/862G/868G
1540
#
1541
# WARNING !!!! Radios With Password in the data section ###############
1542
#
1543
# When a radio has a data password (aka to program it) the last byte (#8)
1544
# in the id code change from \xf1 to \xb1; so we remove this last byte
1545
# from the identification procedures and variants.
1546
#
1547
# This effectively render the data password USELESS even if set.
1548
# Translation: Chirps will read and write password protected radios
1549
# with no problem.
1550

    
1551

    
1552
@directory.register
1553
class TK868G_Radios(Kenwood_Serie_60G):
1554
    """Kenwood TK-868G Radio M/C"""
1555
    MODEL = "TK-868G"
1556
    TYPE = ["M8680", ]
1557
    VARIANTS = {
1558
        "M8680\x18":    (8, 400, 490, "M"),
1559
        "M8680;":       (128, 350, 390, "C1"),
1560
        "M86808":       (128, 400, 430, "C2"),
1561
        "M86806":       (128, 450, 490, "C3"),
1562
        }
1563

    
1564

    
1565
@directory.register
1566
class TK862G_Radios(Kenwood_Serie_60G):
1567
    """Kenwood TK-862G Radio K/E/(N)E"""
1568
    MODEL = "TK-862G"
1569
    TYPE = ["M8620", ]
1570
    VARIANTS = {
1571
        "M8620\x06":    (8, 450, 490, "K"),
1572
        "M8620\x07":    (8, 485, 512, "K2"),
1573
        "M8620&":       (8, 440, 470, "E"),
1574
        "M8620V":       (8, 440, 470, "(N)E"),
1575
        }
1576

    
1577

    
1578
@directory.register
1579
class TK860G_Radios(Kenwood_Serie_60G):
1580
    """Kenwood TK-860G Radio K"""
1581
    MODEL = "TK-860G"
1582
    TYPE = ["M8600", ]
1583
    VARIANTS = {
1584
        "M8600\x08":    (128, 400, 430, "K"),
1585
        "M8600\x06":    (128, 450, 490, "K1"),
1586
        "M8600\x07":    (128, 485, 512, "K2"),
1587
        "M8600\x18":    (128, 400, 430, "M"),
1588
        "M8600\x16":    (128, 450, 490, "M1"),
1589
        "M8600\x17":    (128, 485, 520, "M2"),
1590
        }
1591

    
1592

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

    
1606

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

    
1620

    
1621
@directory.register
1622
class TK760G_Radios(Kenwood_Serie_60G):
1623
    """Kenwood TK-760G Radios [K/M/(N)E]"""
1624
    MODEL = "TK-760G"
1625
    TYPE = ["M7600", ]
1626
    VARIANTS = {
1627
        "M7600\x05": (128, 136, 162, "K2"),
1628
        "M7600\x04": (128, 148, 174, "K"),
1629
        "M7600\x14": (128, 148, 174, "M"),
1630
        "M7600T":    (128, 148, 174, "NE")
1631
        }
1632

    
1633

    
1634
@directory.register
1635
class TK388G_Radios(Kenwood_Serie_60G):
1636
    """Kenwood TK-388 Radio [K/E/M/NE]"""
1637
    MODEL = "TK-388G"
1638
    TYPE = ["P3880", ]
1639
    VARIANTS = {
1640
        "P3880\x1b": (128, 350, 370, "M")
1641
        }
1642

    
1643

    
1644
@directory.register
1645
class TK378G_Radios(Kenwood_Serie_60G):
1646
    """Kenwood TK-378 Radio [K/E/M/NE]"""
1647
    MODEL = "TK-378G"
1648
    TYPE = ["P3780", ]
1649
    VARIANTS = {
1650
        "P3780\x16": (16, 450, 470, "M"),
1651
        "P3780\x17": (16, 400, 420, "M1"),
1652
        "P3780\x36": (128, 490, 512, "C"),
1653
        "P3780\x39": (128, 403, 430, "C1")
1654
        }
1655

    
1656

    
1657
@directory.register
1658
class TK372G_Radios(Kenwood_Serie_60G):
1659
    """Kenwood TK-372 Radio [K/E/M/NE]"""
1660
    MODEL = "TK-372G"
1661
    TYPE = ["P3720", ]
1662
    VARIANTS = {
1663
        "P3720\x06": (32, 450, 470, "K"),
1664
        "P3720\x07": (32, 470, 490, "K1"),
1665
        "P3720\x08": (32, 490, 512, "K2"),
1666
        "P3720\x09": (32, 403, 430, "K3")
1667
        }
1668

    
1669

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

    
1688

    
1689
@directory.register
1690
class TK360G_Radios(Kenwood_Serie_60G):
1691
    """Kenwood TK-360 Radio [K/E/M/NE]"""
1692
    MODEL = "TK-360G"
1693
    TYPE = ["P3600", "p3600"]
1694
    VARIANTS = {
1695
        "P3600\x06": (8, 450, 470, "K"),
1696
        "P3600\x07": (8, 470, 490, "K1"),
1697
        "P3600\x08": (8, 490, 512, "K2"),
1698
        "P3600\x09": (8, 403, 430, "K3"),
1699
        "P3600&": (8, 440, 470, "E"),
1700
        "P3600)": (8, 403, 430, "E1"),
1701
        "P3600\x16": (8, 450, 470, "M"),
1702
        "P3600\x17": (8, 470, 490, "M1"),
1703
        "P3600\x19": (8, 403, 430, "M2"),
1704
        "P3600V": (8, 440, 470, "NE"),
1705
        "P3600Y": (8, 403, 430, "NE1"),
1706
        "p3600&": (8, 403, 470, "E4")
1707
        }
1708

    
1709

    
1710
@directory.register
1711
class TK278G_Radios(Kenwood_Serie_60G):
1712
    """Kenwood TK-278G Radio C/C1/M/M1"""
1713
    MODEL = "TK-278G"
1714
    TYPE = ["P2780", ]
1715
    # Note that 16 CH don't have banks
1716
    VARIANTS = {
1717
        "P27805":    (128, 136, 150, "C1"),
1718
        "P27804":    (128, 150, 174, "C"),
1719
        "P2780\x15": (16,  136, 150, "M1"),
1720
        "P2780\x14": (16,  150, 174, "M")
1721
        }
1722

    
1723

    
1724
@directory.register
1725
class TK272G_Radios(Kenwood_Serie_60G):
1726
    """Kenwood TK-272G Radio K/K1"""
1727
    MODEL = "TK-272G"
1728
    TYPE = ["P2720", ]
1729
    VARIANTS = {
1730
        "P2720\x05": (32, 136, 150, "K1"),
1731
        "P2720\x04": (32, 150, 174, "K")
1732
        }
1733

    
1734

    
1735
@directory.register
1736
class TK270G_Radios(Kenwood_Serie_60G):
1737
    """Kenwood TK-270G Radio K/K1/M/E/NE/NT"""
1738
    MODEL = "TK-270G"
1739
    TYPE = ["P2700", ]
1740
    VARIANTS = {
1741
        "P2700T":    (128, 146, 174, "NE/NT"),
1742
        "P2700$":    (128, 146, 174, "E"),
1743
        "P2700\x14": (128, 150, 174, "M"),
1744
        "P2700\x05": (128, 136, 150, "K1"),
1745
        "P2700\x04": (128, 150, 174, "K")
1746
        }
1747

    
1748

    
1749
@directory.register
1750
class TK260G_Radios(Kenwood_Serie_60G):
1751
    """Kenwood TK-260G Radio K/K1/M/E/NE/NT"""
1752
    MODEL = "TK-260G"
1753
    _hasbanks = False
1754
    TYPE = ["P2600", ]
1755
    VARIANTS = {
1756
        "P2600U":    (8, 136, 150, "N1"),
1757
        "P2600T":    (8, 146, 174, "N"),
1758
        "P2600$":    (8, 150, 174, "E"),
1759
        "P2600\x14": (8, 150, 174, "M"),
1760
        "P2600\x05": (8, 136, 150, "K1"),
1761
        "P2600\x04": (8, 150, 174, "K")
1762
        }
(15-15/23)