Project

General

Profile

Bug #3349 » tk760g.py

Fixed driver, it now works on windows. - Pavel Milanes, 03/03/2016 05:32 AM

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

    
330

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

    
339
    # DEBUG
340
    #LOG.debug("<== (%d) bytes: %s" % (len(data), util.hexprint(data)))
341
    return data
342

    
343

    
344
def _raw_send(radio, data):
345
    """Raw send to the radio device"""
346
    try:
347
        radio.pipe.write(data)
348
        # DEBUG
349
        #LOG.debug("==> (%d) bytes: %s" % (len(data), util.hexprint(data)))
350
    except:
351
        raise errors.RadioError("Error sending data to radio")
352

    
353

    
354
def _close_radio(radio):
355
    """Get the radio out of program mode"""
356
    _raw_send(radio, "\x45")
357

    
358

    
359
def _checksum(data):
360
    """the radio block checksum algorithm"""
361
    cs = 0
362
    for byte in data:
363
            cs += ord(byte)
364
    return cs % 256
365

    
366

    
367
def _send(radio, frame):
368
    """Generic send data to the radio"""
369
    _raw_send(radio, frame)
370

    
371

    
372
def _make_frame(cmd, addr):
373
    """Pack the info in the format it likes"""
374
    return struct.pack(">BH", ord(cmd), addr)
375

    
376

    
377
def _handshake(radio, msg=""):
378
    """Make a full handshake"""
379
    # send ACK
380
    _raw_send(radio, ACK_CMD)
381
    # receive ACK
382
    ack = _raw_recv(radio, 1)
383
    # check ACK
384
    if ack != ACK_CMD:
385
        _close_radio(radio)
386
        mesg = "Handshake failed " + msg
387
        # DEBUG
388
        LOG.debug(mesg)
389
        raise Exception(mesg)
390

    
391

    
392
def _check_write_ack(r, ack, addr):
393
    """Process the ack from the write process
394
    this is half handshake needed in tx data block"""
395
    # all ok
396
    if ack == ACK_CMD:
397
        return True
398

    
399
    # Explicit BAD checksum
400
    if ack == "\x15":
401
        _close_radio(r)
402
        raise errors.RadioError(
403
            "Bad checksum in block %02x write" % addr)
404

    
405
    # everything else
406
    _close_radio(r)
407
    raise errors.RadioError(
408
        "Problem with the ack to block %02x write, ack %03i" %
409
        (addr, int(ack)))
410

    
411

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

    
435
        if rcs != ccs:
436
            _close_radio(radio)
437
            raise errors.RadioError(
438
                "Block Checksum Error! real %02x, calculated %02x" %
439
                (rcs, ccs))
440

    
441
        _handshake(radio, "after checksum")
442
        return data
443

    
444

    
445
def _open_radio(radio, status):
446
    """Open the radio into program mode and check if it's the correct model"""
447
    # linux min is 0.13, win min is 0.25; set to bigger to be safe
448
    radio.pipe.setTimeout(0.25)
449
    radio.pipe.setParity("E")
450

    
451
    # DEBUG
452
    LOG.debug("Entering program mode.")
453
    # max tries
454
    tries = 10
455

    
456
    # UI
457
    status.cur = 0
458
    status.max = tries
459
    status.msg = "Entering program mode..."
460

    
461
    # try a few times to get the radio into program mode
462
    exito = False
463
    for i in range(0, tries):
464
        _raw_send(radio, "PROGRAM")
465
        ack = _raw_recv(radio, 1)
466

    
467
        if ack != ACK_CMD:
468
            # DEBUG
469
            LOG.debug("Try %s failed, traying again...")
470
            time.sleep(0.25)
471
        else:
472
            exito = True
473
            break
474

    
475
        status.cur += 1
476
        radio.status_fn(status)
477

    
478

    
479
    if exito is False:
480
        _close_radio(radio)
481
        LOG.debug("Radio did not accepted PROGRAM command in five atempts")
482
        raise errors.RadioError("The radio doesn't accept program mode")
483

    
484
    # DEBUG
485
    LOG.debug("Received ACK to the PROGRAM command, send ID query.")
486

    
487
    _raw_send(radio, "\x02")
488
    rid = _raw_recv(radio, 8)
489

    
490
    if not (radio.TYPE in rid):
491
        # bad response, properly close the radio before exception
492
        _close_radio(radio)
493

    
494
        # DEBUG
495
        LOG.debug("Incorrect model ID:")
496
        LOG.debug(util.hexprint(rid))
497

    
498
        raise errors.RadioError(
499
            "Incorrect model ID, got %s, it not contains %s" %
500
            (rid.strip("\xff"), radio.TYPE))
501

    
502
    # DEBUG
503
    LOG.debug("Full ident string is:")
504
    LOG.debug(util.hexprint(rid))
505
    _handshake(radio)
506

    
507
    status.msg = "Radio ident success!"
508
    radio.status_fn(status)
509
    # a pause
510
    time.sleep(1)
511

    
512

    
513
def do_download(radio):
514
    """ The download function """
515
    # UI progress
516
    status = chirp_common.Status()
517
    data = ""
518
    count = 0
519

    
520
    # open the radio
521
    _open_radio(radio, status)
522

    
523
    # reset UI data
524
    status.cur = 0
525
    status.max = MEM_SIZE / 256
526
    status.msg = "Cloning from radio..."
527
    radio.status_fn(status)
528

    
529
    # set the timeout and if windows keep it bigger
530
    if sys.platform in ["win32", "cygwin"]:
531
        # bigger timeout
532
        radio.pipe.setTimeout(0.55)
533
    else:
534
        # Linux can keep up, MAC?
535
        radio.pipe.setTimeout(0.05)
536

    
537
    # DEBUG
538
    LOG.debug("Starting the download from radio")
539

    
540
    for addr in MEM_BLOCKS:
541
        # send request, but before flush the rx buffer
542
        radio.pipe.flushInput()
543
        _send(radio, _make_frame("R", addr))
544

    
545
        # now we get the data
546
        d = _recv(radio)
547
        # if empty block, it return false
548
        # aka we asume a empty 256 xFF block
549
        if d is False:
550
            d = EMPTY_BLOCK
551

    
552
        data += d
553

    
554
        # UI Update
555
        status.cur = count
556
        radio.status_fn(status)
557

    
558
        count += 1
559

    
560
    _close_radio(radio)
561
    return memmap.MemoryMap(data)
562

    
563

    
564
def do_upload(radio):
565
    """ The upload function """
566
    # UI progress
567
    status = chirp_common.Status()
568
    data = ""
569
    count = 0
570

    
571
    # open the radio
572
    _open_radio(radio, status)
573

    
574
    # update UI
575
    status.cur = 0
576
    status.max = MEM_SIZE / 256
577
    status.msg = "Cloning to radio..."
578
    radio.status_fn(status)
579

    
580
    # the default for the original soft as measured
581
    radio.pipe.setTimeout(0.5)
582

    
583
    # DEBUG
584
    LOG.debug("Starting the upload to the radio")
585

    
586
    count = 0
587
    raddr = 0
588
    for addr in MEM_BLOCKS:
589
        # this is the data block to write
590
        data = radio.get_mmap()[raddr:raddr+BLOCK_SIZE]
591

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

    
609
        if data == EMPTY_BLOCK:
610
            frame = _make_frame("Z", addr) + "\xFF"
611
        else:
612
            cs = _checksum(data)
613
            frame = _make_frame("W", addr) + data + chr(cs)
614

    
615
        _send(radio, frame)
616

    
617
        ## windows need a pause here to gatter data
618
        #if sys.platform in ["win32", "cygwin"]:
619
            ## we are in windows, we need a pause
620
            #time.sleep(0.1)
621

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

    
626
        # DEBUG
627
        LOG.debug("Sending block %02x" % addr)
628

    
629
        # UI Update
630
        status.cur = count
631
        radio.status_fn(status)
632

    
633
        count += 1
634
        raddr = count * 256
635

    
636
    _close_radio(radio)
637

    
638

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

    
648

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

    
653
    def get_num_mappings(self):
654
        return self._radio._num_banks
655

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

    
665
    def add_memory_to_mapping(self, memory, bank):
666
        self._radio._set_bank(memory.number, bank.index)
667

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

    
673
        # Warning about removing a channel on bank 0
674
        #if bank.index == self._radio._get_bank(memory.number) == 0:
675
            #mesg = "Can't remove, this is the default bank for "
676
            #mesg += "all channels"
677
            #raise Exception(mesg)
678

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

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

    
691
    def get_memory_mappings(self, memory):
692
        index = self._radio._get_bank(memory.number)
693
        return [self.get_mappings()[index]]
694

    
695

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

    
702

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

    
718
    @classmethod
719
    def get_prompts(cls):
720
        rp = chirp_common.RadioPrompts()
721
        rp.experimental = \
722
            ('This driver is experimental and for personal use only.'
723
             'It has a limited set of features, but the most used.'
724
             ''
725
             'The most notorius missing features are this:'
726
             '=> PTT ID, in fact it is disabled if detected'
727
             '=> Priority / Home channel'
728
             '=> Bank names'
729
             '=> Others'
730
             ''
731
             'If you need one of this, get your official software to do it'
732
             'and raise and issue on the chirp site about it and maybe'
733
             'it will be implemented in the future.'
734
             ''
735
             'A detail: this driver is slow reading from the radio in'
736
             'Windows, due to the way windows serial works.'
737
             )
738
        rp.pre_download = _(dedent("""\
739
            Follow this instructions to download your info:
740
            1 - Turn off your radio
741
            2 - Connect your interface cable
742
            3 - Turn on your radio (unblock it if password protected)
743
            4 - Do the download of your radio data
744
            """))
745
        rp.pre_upload = _(dedent("""\
746
            Follow this instructions to upload your info:
747
            1 - Turn off your radio
748
            2 - Connect your interface cable
749
            3 - Turn on your radio (unblock it if password protected)
750
            4 - Do the upload of your radio data
751
            """))
752
        return rp
753

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

    
788
    def _fill(self, offset, data):
789
        """Fill an specified area of the memmap with the passed data"""
790
        for addr in range(0, len(data)):
791
            self._mmap[offset + addr] = data[addr]
792

    
793
    def _prep_data(self):
794
        """Prepare the areas in the memmap to do a consistend write
795
        it has to make an update on the x300 area with banks and channel
796
        info; other in the x1000 with banks and channel counts
797
        and a last one in x7000 with flog data"""
798
        rchs = 0
799
        data = dict()
800

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

    
816
        # updating the channel/bank count
817
        self._memobj.settings.channels = rchs
818
        self._chs_progs = rchs
819
        self._memobj.settings.banks = len(data)
820

    
821
        # building the data for the memmap
822
        fdata = ""
823

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

    
836
        # fill to match a full 256 bytes block
837
        fdata += (len(fdata) % 256) * "\xFF"
838

    
839
        # updating the data in the memmap [x300]
840
        self._fill(0x300, fdata)
841

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

    
851
        # fill to match a full 256 bytes block
852
        bdata += (256 - (len(bdata)) % 256) * "\xFF"
853

    
854
        # fill to match the whole area
855
        bdata += (16 - len(bdata) / 256) * EMPTY_BLOCK
856

    
857
        # updating the data in the memmap [x1000]
858
        self._fill(0x1000, bdata)
859

    
860
        # DTMF id for each channel, 5 bytes lbcd at x7000
861
        # ############## TODO ###################
862
        fldata = "\x00\xf0\xff\xff\xff" * self._chs_progs + \
863
            "\xff" * (5 * (self._upper - self._chs_progs))
864

    
865
        # write it
866
        # updating the data in the memmap [x7000]
867
        self._fill(0x7000, fldata)
868

    
869
    def _set_variant(self):
870
        """Select and set the correct variables for the class acording
871
        to the correct variant of the radio"""
872
        rid = self._mmap[0xA7:0xAE]
873

    
874
        # indentify the radio variant and set the enviroment to it's values
875
        try:
876
            self._upper, low, high, self._kind = self.VARIANTS[rid]
877
            self._range = [low * 1000000, high * 1000000]
878

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

    
884
            # put the VARIANT in the class, clean the model / CHs / Type
885
            # in the same layout as the KPG program
886
            self._VARIANT = self.MODEL + " [" + str(self._upper) + "CH]: "
887
            self._VARIANT += self._kind + ", " + str(self._range[0]/1000000) + "-"
888
            self._VARIANT += str(self._range[1]/1000000) + " Mhz"
889

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

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

    
902
    def sync_out(self):
903
        """Do an upload to the radio eeprom"""
904

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

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

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

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

    
925
        # to set the vars on the class to the correct ones
926
        self._set_variant()
927

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

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

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

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

    
971
        return result
972

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

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

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

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

    
993
        # Memory number
994
        mem.number = number
995

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

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

    
1024
        # name TAG of the channel
1025
        mem.name = str(_mem.name).rstrip()
1026

    
1027
        # power
1028
        mem.power = POWER_LEVELS[_mem.power]
1029

    
1030
        # wide/marrow
1031
        mem.mode = MODES[_mem.wide]
1032

    
1033
        # skip
1034
        mem.skip = self._get_scan(number - 1)
1035

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

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

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

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

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

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

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

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

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

    
1078
        return mem
1079

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

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

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

    
1092
        # frequency
1093
        _mem.rxfreq = mem.freq / 10
1094

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

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

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

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

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

    
1131
        _mem.power = POWER_LEVELS.index(mem.power)
1132

    
1133
        # wide/marrow
1134
        _mem.wide = MODES.index(mem.mode)
1135

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

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

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

    
1149
        # all data get sync after channel mod
1150
        self._prep_data()
1151

    
1152
        return mem
1153

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

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

    
1163
        # testing the firmware model fingerprint
1164
        match_model = model_match(cls, filedata)
1165

    
1166
        if match_size and match_model:
1167
            return True
1168
        else:
1169
            return False
1170

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

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

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

    
1190
        top = RadioSettings(basic, dealer, fkeys)
1191

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1260
        # dealer
1261
        mstr = ""
1262
        valid_chars = [32, 44, 45, 47, 58, 91, 93] + range(48, 58) + \
1263
            range(65, 91) + range(97, 123)
1264

    
1265
        for i in range(0, len(self._VARIANT)):
1266
            if ord(self._VARIANT[i]) in valid_chars:
1267
                mstr += self._VARIANT[i]
1268

    
1269
        val = RadioSettingValueString(0, 35, mstr)
1270
        val.set_mutable(False)
1271
        mod = RadioSetting("not.mod", "Radio Version", val)
1272
        dealer.append(mod)
1273

    
1274
        sn = str(idm.serial).strip(" \xff")
1275
        val = RadioSettingValueString(0, 8, sn)
1276
        val.set_mutable(False)
1277
        serial = RadioSetting("not.serial", "Serial number", val)
1278
        dealer.append(serial)
1279

    
1280
        svp = str(sett.lastsoftversion).strip(" \xff")
1281
        val = RadioSettingValueString(0, 5, svp)
1282
        val.set_mutable(False)
1283
        sver = RadioSetting("not.softver", "Software Version", val)
1284
        dealer.append(sver)
1285

    
1286
        l1 = str(mess.line1).strip(" \xff")
1287
        line1 = RadioSetting("message.line1", "Comment 1",
1288
                           RadioSettingValueString(0, 32, l1))
1289
        dealer.append(line1)
1290

    
1291
        l2 = str(mess.line2).strip(" \xff")
1292
        line2 = RadioSetting("message.line2", "Comment 2",
1293
                             RadioSettingValueString(0, 32, l2))
1294
        dealer.append(line2)
1295

    
1296
        sprog = RadioSetting("settings.self_prog", "Self program",
1297
                             RadioSettingValueBoolean(sett.self_prog))
1298
        dealer.append(sprog)
1299

    
1300
        clone = RadioSetting("settings.clone", "Allow clone",
1301
                             RadioSettingValueBoolean(sett.clone))
1302
        dealer.append(clone)
1303

    
1304
        panel = RadioSetting("settings.panel_test", "Panel Test",
1305
                             RadioSettingValueBoolean(sett.panel_test))
1306
        dealer.append(panel)
1307

    
1308
        fmw = RadioSetting("settings.firmware_prog", "Firmware program",
1309
                           RadioSettingValueBoolean(sett.firmware_prog))
1310
        dealer.append(fmw)
1311

    
1312
        # front keys
1313
        # The Mobile only parameters are wraped here
1314
        if self.TYPE[0] == "M":
1315
            vu = RadioSetting("keys.kVOL_UP", "VOL UP",
1316
                              RadioSettingValueList(KEYS.values(),
1317
                              KEYS.values()[KEYS.keys().index(
1318
                                  int(keys.kVOL_UP))]))
1319
            fkeys.append(vu)
1320

    
1321
            vd = RadioSetting("keys.kVOL_DOWN", "VOL DOWN",
1322
                              RadioSettingValueList(KEYS.values(),
1323
                              KEYS.values()[KEYS.keys().index(
1324
                                  int(keys.kVOL_DOWN))]))
1325
            fkeys.append(vd)
1326

    
1327
            chu = RadioSetting("keys.kCH_UP", "CH UP",
1328
                               RadioSettingValueList(KEYS.values(),
1329
                               KEYS.values()[KEYS.keys().index(
1330
                                   int(keys.kCH_UP))]))
1331
            fkeys.append(chu)
1332

    
1333
            chd = RadioSetting("keys.kCH_DOWN", "CH DOWN",
1334
                               RadioSettingValueList(KEYS.values(),
1335
                               KEYS.values()[KEYS.keys().index(
1336
                                   int(keys.kCH_DOWN))]))
1337
            fkeys.append(chd)
1338

    
1339
            foot = RadioSetting("keys.kFOOT", "Foot switch",
1340
                               RadioSettingValueList(KEYS.values(),
1341
                               KEYS.values()[KEYS.keys().index(
1342
                                   int(keys.kCH_DOWN))]))
1343
            fkeys.append(foot)
1344

    
1345
        # this is the common buttons for all
1346

    
1347
        # 260G model don't have the front keys
1348
        if not "P2600" in self.TYPE:
1349
            scn_name = "SCN"
1350
            if self.TYPE[0] == "P":
1351
                scn_name = "Open Circle"
1352

    
1353
            scn = RadioSetting("keys.kSCN", scn_name,
1354
                               RadioSettingValueList(KEYS.values(),
1355
                               KEYS.values()[KEYS.keys().index(
1356
                                   int(keys.kSCN))]))
1357
            fkeys.append(scn)
1358

    
1359
            a_name = "A"
1360
            if self.TYPE[0] == "P":
1361
                a_name = "Closed circle"
1362

    
1363
            a = RadioSetting("keys.kA", a_name,
1364
                             RadioSettingValueList(KEYS.values(),
1365
                             KEYS.values()[KEYS.keys().index(
1366
                                 int(keys.kA))]))
1367
            fkeys.append(a)
1368

    
1369
            da_name = "D/A"
1370
            if self.TYPE[0] == "P":
1371
                da_name = "< key"
1372

    
1373
            da = RadioSetting("keys.kDA", da_name,
1374
                              RadioSettingValueList(KEYS.values(),
1375
                              KEYS.values()[KEYS.keys().index(
1376
                                  int(keys.kDA))]))
1377
            fkeys.append(da)
1378

    
1379
            gu_name = "Triangle up"
1380
            if self.TYPE[0] == "P":
1381
                gu_name = "Side 1"
1382

    
1383
            gu = RadioSetting("keys.kGROUP_UP", gu_name,
1384
                              RadioSettingValueList(KEYS.values(),
1385
                              KEYS.values()[KEYS.keys().index(
1386
                                  int(keys.kGROUP_UP))]))
1387
            fkeys.append(gu)
1388

    
1389
        # Side keys on portables
1390
        gd_name = "Triangle Down"
1391
        if self.TYPE[0] == "P":
1392
            gd_name = "> key"
1393

    
1394
        gd = RadioSetting("keys.kGROUP_DOWN", gd_name,
1395
                          RadioSettingValueList(KEYS.values(),
1396
                          KEYS.values()[KEYS.keys().index(
1397
                              int(keys.kGROUP_DOWN))]))
1398
        fkeys.append(gd)
1399

    
1400
        mon_name = "MON"
1401
        if self.TYPE[0] == "P":
1402
            mon_name = "Side 2"
1403

    
1404
        mon = RadioSetting("keys.kMON", mon_name,
1405
                           RadioSettingValueList(KEYS.values(),
1406
                           KEYS.values()[KEYS.keys().index(
1407
                               int(keys.kMON))]))
1408
        fkeys.append(mon)
1409

    
1410
        return top
1411

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

    
1417
        mobj = self._memobj
1418

    
1419
        for element in settings:
1420
            if not isinstance(element, RadioSetting):
1421
                self.set_settings(element)
1422
                continue
1423

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

    
1432
                obj = getattr(mobj, inter)
1433
                value = element.value
1434

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

    
1444
                    # tot case step 15
1445
                    if setting == "tot":
1446
                        value = value * 15
1447
                        # off is special
1448
                        if value == 0:
1449
                            value = 0x4b0
1450

    
1451
                    # Caso tone_vol
1452
                    if setting == "tone_vol":
1453
                        # off is special
1454
                        if value == 32:
1455
                            value = 0xff
1456

    
1457
                # Bool types + inverted
1458
                if setting in ["c2t", "poweron_tone", "control_tone",
1459
                               "warn_tone", "battery_save", "self_prog",
1460
                               "clone", "panel_test"]:
1461
                    value = bool(value)
1462

    
1463
                    # this cases are inverted
1464
                    if setting == "c2t":
1465
                        value = not value
1466

    
1467
                    # case battery save is special
1468
                    if setting == "battery_save":
1469
                        if bool(value) is True:
1470
                            value = 0x32
1471
                        else:
1472
                            value = 0xff
1473

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

    
1483
                    # empty case
1484
                    if len(value) == 0:
1485
                        value = "\xff" * just
1486
                    else:
1487
                        value = value.ljust(just)
1488

    
1489
                ## password with special case
1490
                #if setting == "radio" or setting == "data":
1491
                    #pass
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-260/270/272/278
1536
# Portables UHF TK-360/370/372/378/388
1537
#
1538
# Mobiles VHF TK-760/762/768
1539
# Mobiles VHF TK-860/862/868
1540
#
1541
# Just dealing with VHF models at moment,
1542
# this are the radios I can get in hand
1543

    
1544
# WARNING !!!! Radios With Password in the data section <=###############
1545
#
1546
# when a radio has a data password (aka to program it) the last byte (#8)
1547
# in the id code change from \xf1 to \xb1; so we remove this last byte
1548
# from the identification procedures and variants.
1549
#
1550
# this effectively render the data password USELESS even if set.
1551
# this can change if user request it with high priority
1552

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

    
1565

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

    
1578

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

    
1593

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

    
1607

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

    
1621

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

    
1634

    
1635
@directory.register
1636
class TK278G_Radios(Kenwood_Serie_60G):
1637
    """Kenwood TK-278G Radio C/C1/M/M1"""
1638
    MODEL = "TK-278G"
1639
    TYPE = "P2780"
1640
    # Note that 16 CH don't have banks
1641
    VARIANTS = {
1642
        "P27805\xff":    (128, 136, 150, "C1"),
1643
        "P27804\xff":    (128, 150, 174, "C"),
1644
        "P2780\x15\xff": (16,  136, 150, "M1"),
1645
        "P2780\x14\xff": (16,  150, 174, "M")
1646
        }
1647

    
1648

    
1649
@directory.register
1650
class TK272G_Radios(Kenwood_Serie_60G):
1651
    """Kenwood TK-272G Radio K/K1"""
1652
    MODEL = "TK-272G"
1653
    TYPE = "P2720"
1654
    VARIANTS = {
1655
        "P2720\x05\xfb": (32, 136, 150, "K1"),
1656
        "P2720\x04\xfb": (32, 150, 174, "K")
1657
        }
1658

    
1659

    
1660
@directory.register
1661
class TK270G_Radios(Kenwood_Serie_60G):
1662
    """Kenwood TK-270G Radio K/K1/M/E/NE/NT"""
1663
    MODEL = "TK-270G"
1664
    TYPE = "P2700"
1665
    VARIANTS = {
1666
        "P2700T\xff":    (128, 144, 174, "NE/NT"),   # 146-174 original
1667
        "P2700$\xff":    (128, 144, 174, "E"),   # 146-174 original
1668
        "P2700\x14\xff": (128, 150, 174, "M"),
1669
        "P2700\x05\xff": (128, 136, 150, "K1"),
1670
        "P2700\x04\xff": (128, 150, 174, "K"),
1671
        }
1672

    
1673

    
1674
@directory.register
1675
class TK260G_Radios(Kenwood_Serie_60G):
1676
    """Kenwood TK-260G Radio K/K1/M/E/NE/NT"""
1677
    MODEL = "TK-260G"
1678
    _hasbanks = False
1679
    TYPE = "P2600"
1680
    VARIANTS = {
1681
        "P2600U\xff":    (8, 136, 150, "N1"),
1682
        "P2600T\xff":    (8, 144, 174, "N"),   # 146-174 original
1683
        "P2600$\xff":    (8, 144, 174, "E"),   # 144-174 original
1684
        "P2600\x14\xff": (8, 150, 174, "M"),
1685
        "P2600\x05\xff": (8, 136, 150, "K1"),
1686
        "P2600\x04\xff": (8, 150, 174, "K")
1687
        }
(7-7/7)