tk760g_with_valid_steps_#2.py

Jim Unroe, 02/01/2021 07:05 am

Download (54 kB)

 
1
# Copyright 2016 Pavel Milanes, CO7WT, <pavelmc@gmail.com>
2
#
3
# This program is free software: you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation, either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
15

    
16
import logging
17
import struct
18
import time
19
import sys
20

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

    
28
LOG = logging.getLogger(__name__)
29

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
298
# For debugging purposes
299
debug = False
300

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

    
333

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

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

    
346
    return data
347

    
348

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

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

    
360

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

    
365

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

    
373

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

    
378

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

    
383

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

    
398

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

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

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

    
418

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

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

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

    
451

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

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

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

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

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

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

    
485

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

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

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

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

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

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

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

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

    
519

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

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

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

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

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

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

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

    
559
        data += d
560

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

    
565
        count += 1
566

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

    
570

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

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

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

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

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

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

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

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

    
622
        _send(radio, frame)
623

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

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

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

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

    
638
    _close_radio(radio)
639

    
640

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

    
650

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

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

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

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

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

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

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

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

    
691

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

    
698

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
968
        return result
969

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

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

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

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

    
990
        # Memory number
991
        mem.number = number
992

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1075
        return mem
1076

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1149
        return mem
1150

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1337
        # this is the common buttons for all
1338

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

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

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

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

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

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

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

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

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

    
1386
        gd = RadioSetting("keys.kGROUP_DOWN", gd_name,
1387
                          RadioSettingValueList(KEYS.values(),
1388
                          KEYS.values()[KEYS.keys().index(
1389
                              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(),
1398
                           KEYS.values()[KEYS.keys().index(
1399
                               int(keys.kMON))]))
1400
        fkeys.append(mon)
1401

    
1402
        return top
1403

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

    
1409
        mobj = self._memobj
1410

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1519

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

    
1539

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

    
1552

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

    
1565

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

    
1580

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

    
1594

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

    
1608

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

    
1621

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

    
1631

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

    
1644

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

    
1657

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

    
1676

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

    
1696

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

    
1710

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

    
1721

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

    
1735

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