Project

General

Profile

New Model #5825 » tk760g.py

dev driver with a lot of debug power to inspect the back checksum error. - Pavel Milanes, 06/05/2018 09:38 AM

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

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

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

    
28
LOG = logging.getLogger(__name__)
29

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

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

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

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

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

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

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

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

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

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

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

    
264
# debug flag
265
debug = True
266

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

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

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

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

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

    
285
TONES = chirp_common.TONES
286
# TONES.remove(254.1)
287
DTCS_CODES = chirp_common.DTCS_CODES
288

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

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

    
301
# For debugging purposes
302
debug = False
303

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

    
336

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

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

    
349
    return data
350

    
351

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

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

    
363

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

    
368

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

    
376

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

    
381

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

    
386

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

    
401

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

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

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

    
421

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

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

    
451
        _handshake(radio, "after checksum")
452
        return data
453

    
454

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

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

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

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

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

    
485
        status.cur += 1
486
        radio.status_fn(status)
487

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

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

    
497
    _raw_send(radio, "\x02")
498
    rid = _raw_recv(radio, 8)[0:5]
499

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

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

    
508
        raise errors.RadioError(
509
            "Incorrect model ID, got %s, it does not match against %s" %
510
            (rid.strip("\xff"), str(radio.TYPE)))
511

    
512
    # DEBUG
513
    LOG.debug("Full ident string is:")
514
    LOG.debug(util.hexprint(rid))
515
    LOG.debug("Success.")
516
    _handshake(radio)
517

    
518
    status.msg = "Radio ident success!"
519
    radio.status_fn(status)
520
    # a pause
521
    time.sleep(1)
522

    
523

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

    
531
    # open the radio
532
    _open_radio(radio, status)
533

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

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

    
548
    # debug
549
    if debug:
550
        LOG.debug("Starting the download from radio.")
551
        LOG.debug("Timeout is: %1.3f seconds." % float(radio.pipe.timeout))
552

    
553
    for addr in MEM_BLOCKS:
554
        # send request, but before flush the rx buffer
555
        radio.pipe.flush()
556
        _send(radio, _make_frame("R", addr))
557

    
558
        # debug
559
        if debug:
560
            LOG.debug("Read Request block at 0x%.4x" % addr)
561

    
562

    
563
        # now we get the data
564
        d = _recv(radio)
565
        # if empty block, it return false
566
        # aka we asume a empty 256 xFF block
567
        if d is False:
568
            d = EMPTY_BLOCK
569

    
570
        # debug
571
        if debug:
572
            LOG.debug("Got \n%s" % util.hexprint(d))
573

    
574
        data += d
575

    
576
        # UI Update
577
        status.cur = count
578
        radio.status_fn(status)
579

    
580
        count += 1
581

    
582
    _close_radio(radio)
583

    
584
    # debug
585
    if debug:
586
        LOG.debug("Closing radio.")
587

    
588
    return memmap.MemoryMap(data)
589

    
590

    
591
def do_upload(radio):
592
    """ The upload function """
593
    # UI progress
594
    status = chirp_common.Status()
595
    data = ""
596
    count = 0
597

    
598
    # open the radio
599
    _open_radio(radio, status)
600

    
601
    # update UI
602
    status.cur = 0
603
    status.max = MEM_SIZE / 256
604
    status.msg = "Cloning to radio..."
605
    radio.status_fn(status)
606

    
607
    # the default for the original soft as measured
608
    radio.pipe.timeout = 0.5
609

    
610
    # debug
611
    if debug:
612
        LOG.debug("Starting the upload to the radio")
613
        LOG.debug("Timeout is: %1.3f seconds." % float(radio.pipe.timeout))
614

    
615
    count = 0
616
    raddr = 0
617
    for addr in MEM_BLOCKS:
618
        # this is the data block to write
619
        data = radio.get_mmap()[raddr:raddr+BLOCK_SIZE]
620

    
621
        # The blocks from x59-x5F are NOT programmable
622
        # The blocks from x11-x1F are writed only if not empty
623
        if addr in RO_BLOCKS:
624
            # debug
625
            if debug:
626
                LOG.debug("Block 0x%.2x is a conditional write one" % addr)
627

    
628
            # checking if in the range of optional blocks
629
            if addr >= 0x10 and addr <= 0x1F:
630
                # block is empty ?
631
                if data == EMPTY_BLOCK:
632
                    # no write of this block
633
                    # but we have to continue updating the counters
634
                    count += 1
635
                    raddr = count * 256
636
                    continue
637
            else:
638
                count += 1
639
                raddr = count * 256
640
                continue
641

    
642
        if data == EMPTY_BLOCK:
643
            frame = _make_frame("Z", addr) + "\xFF"
644
        else:
645
            cs = _checksum(data)
646
            frame = _make_frame("W", addr) + data + chr(cs)
647

    
648
        _send(radio, frame)
649

    
650
        # debug
651
        if debug:
652
            LOG.debug("Writing at block 0x%.2x this:\n%s" % (addr, util.hexprint(frame)))
653

    
654
        # get the ACK
655
        ack = _raw_recv(radio, 1)
656
        _check_write_ack(radio, ack, addr)
657

    
658
        # debug
659
        if debug:
660
            LOG.debug("Got ACK.")
661

    
662
        # UI Update
663
        status.cur = count
664
        radio.status_fn(status)
665

    
666
        count += 1
667
        raddr = count * 256
668

    
669
    _close_radio(radio)
670

    
671
    # debug
672
    if debug:
673
        LOG.debug("Closing Radio.")
674

    
675

    
676
def model_match(cls, data):
677
    """Match the opened/downloaded image to the correct version"""
678
    rid = data[0xA7:0xAD]
679
    if (rid in cls.VARIANTS):
680
        # correct model
681
        return True
682
    else:
683
        return False
684

    
685

    
686
class Kenwood60GBankModel(chirp_common.BankModel):
687
    """Testing the bank model on kennwood"""
688
    channelAlwaysHasBank = True
689

    
690
    def get_num_mappings(self):
691
        return self._radio._num_banks
692

    
693
    def get_mappings(self):
694
        banks = []
695
        for i in range(0, self._radio._num_banks):
696
            bindex = i + 1
697
            bank = self._radio._bclass(self, i, "%03i" % bindex)
698
            bank.index = i
699
            banks.append(bank)
700
        return banks
701

    
702
    def add_memory_to_mapping(self, memory, bank):
703
        self._radio._set_bank(memory.number, bank.index)
704

    
705
    def remove_memory_from_mapping(self, memory, bank):
706
        if self._radio._get_bank(memory.number) != bank.index:
707
            raise Exception("Memory %i not in bank %s. Cannot remove." %
708
                            (memory.number, bank))
709

    
710
        # We can't "Remove" it for good
711
        # the kenwood paradigm don't allow it
712
        # instead we move it to bank 0
713
        self._radio._set_bank(memory.number, 0)
714

    
715
    def get_mapping_memories(self, bank):
716
        memories = []
717
        for i in range(0, self._radio._upper):
718
            if self._radio._get_bank(i) == bank.index:
719
                memories.append(self._radio.get_memory(i))
720
        return memories
721

    
722
    def get_memory_mappings(self, memory):
723
        index = self._radio._get_bank(memory.number)
724
        return [self.get_mappings()[index]]
725

    
726

    
727
class memBank(chirp_common.Bank):
728
    """A bank model for kenwood"""
729
    # Integral index of the bank (not to be confused with per-memory
730
    # bank indexes
731
    index = 0
732

    
733

    
734
class Kenwood_Serie_60G(chirp_common.CloneModeRadio,
735
        chirp_common.ExperimentalRadio):
736
    """Kenwood Serie 60G Radios base class"""
737
    VENDOR = "Kenwood"
738
    BAUD_RATE = 9600
739
    _memsize = MEM_SIZE
740
    NAME_LENGTH = 8
741
    _range = [136000000, 162000000]
742
    _upper = 128
743
    _chs_progs = 0
744
    _num_banks = 128
745
    _bclass = memBank
746
    _kind = ""
747
    VARIANT = ""
748
    MODEL = ""
749

    
750
    @classmethod
751
    def get_prompts(cls):
752
        rp = chirp_common.RadioPrompts()
753
        rp.experimental = \
754
            ('This driver is experimental; not all features have been '
755
            'implemented, but it has those features most used by hams.\n'
756
             '\n'
757
             'This radios are able to work slightly outside the OEM '
758
             'frequency limits. After testing, the limit in Chirp has '
759
             'been set 4% outside the OEM limit. This allows you to use '
760
             'some models on the ham bands.\n'
761
             '\n'
762
             'Nevertheless, each radio has its own hardware limits and '
763
             'your mileage may vary.\n'
764
             )
765
        rp.pre_download = _(dedent("""\
766
            Follow this instructions to download your info:
767
            1 - Turn off your radio
768
            2 - Connect your interface cable
769
            3 - Turn on your radio (unblock it if password protected)
770
            4 - Do the download of your radio data
771
            """))
772
        rp.pre_upload = _(dedent("""\
773
            Follow this instructions to upload your info:
774
            1 - Turn off your radio
775
            2 - Connect your interface cable
776
            3 - Turn on your radio (unblock it if password protected)
777
            4 - Do the upload of your radio data
778
            """))
779
        return rp
780

    
781
    def get_features(self):
782
        """Return information about this radio's features"""
783
        rf = chirp_common.RadioFeatures()
784
        rf.has_settings = True
785

    
786
        # has banks? 8 & 16 CH don't have banks
787
        if self._upper < 32:
788
            rf.has_bank = False
789
        else:
790
            rf.has_bank = True
791

    
792
        rf.has_tuning_step = False
793

    
794
        # there is a known TK-360G with no LCD, hence no names
795
        if "p3600&" in self.VARIANTS:
796
            rf.has_name = False
797
        else:
798
            # the rest has names an LCDs..
799
            rf.has_name = True
800

    
801
        rf.has_offset = True
802
        rf.has_mode = True
803
        rf.has_dtcs = True
804
        rf.has_rx_dtcs = True
805
        rf.has_dtcs_polarity = True
806
        rf.has_ctone = True
807
        rf.has_cross = True
808
        rf.valid_modes = MODES
809
        rf.valid_duplexes = ["", "-", "+", "off"]
810
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
811
        rf.valid_cross_modes = [
812
            "Tone->Tone",
813
            "DTCS->",
814
            "->DTCS",
815
            "Tone->DTCS",
816
            "DTCS->Tone",
817
            "->Tone",
818
            "DTCS->DTCS"]
819
        rf.valid_power_levels = POWER_LEVELS
820
        rf.valid_characters = VALID_CHARS
821
        rf.valid_skips = SKIP_VALUES
822
        rf.valid_dtcs_codes = DTCS_CODES
823
        rf.valid_bands = [self._range]
824
        rf.valid_name_length = 8
825
        rf.memory_bounds = (1, self._upper)
826
        return rf
827

    
828
    def _fill(self, offset, data):
829
        """Fill an specified area of the memmap with the passed data"""
830
        for addr in range(0, len(data)):
831
            self._mmap[offset + addr] = data[addr]
832

    
833
    def _prep_data(self):
834
        """Prepare the areas in the memmap to do a consistend write
835
        it has to make an update on the x300 area with banks and channel
836
        info; other in the x1000 with banks and channel counts
837
        and a last one in x7000 with flag data"""
838
        rchs = 0
839
        data = dict()
840

    
841
        # sorting the data
842
        for ch in range(0, self._upper):
843
            mem = self._memobj.memory[ch]
844
            bnumb = int(mem.bnumb)
845
            bank = int(mem.bank)
846
            if bnumb != 255 and (bank != 255 and bank != 0):
847
                try:
848
                    data[bank].append(ch)
849
                except:
850
                    data[bank] = list()
851
                    data[bank].append(ch)
852
                data[bank].sort()
853
                # counting the real channels
854
                rchs = rchs + 1
855

    
856
        # updating the channel/bank count
857
        self._memobj.settings.channels = rchs
858
        self._chs_progs = rchs
859
        self._memobj.settings.banks = len(data)
860

    
861
        # building the data for the memmap
862
        fdata = ""
863

    
864
        for k, v in data.iteritems():
865
            # posible bad data
866
            if k == 0:
867
                k = 1
868
                raise errors.InvalidValueError(
869
                    "Invalid bank value '%k', bad data in the image? \
870
                    Trying to fix this, review your bank data!" % k)
871
            c = 1
872
            for i in v:
873
                fdata += chr(k) + chr(c) + chr(k - 1) + chr(i)
874
                c = c + 1
875

    
876
        # fill to match a full 256 bytes block
877
        fdata += (len(fdata) % 256) * "\xFF"
878

    
879
        # updating the data in the memmap [x300]
880
        self._fill(0x300, fdata)
881

    
882
        # update the info in x1000; it has 2 bytes with
883
        # x00 = bank , x01 = bank's channel count
884
        # the rest of the 14 bytes are \xff
885
        bdata = ""
886
        for i in range(1, len(data) + 1):
887
            line = chr(i) + chr(len(data[i]))
888
            line += "\xff" * 14
889
            bdata += line
890

    
891
        # fill to match a full 256 bytes block
892
        bdata += (256 - (len(bdata)) % 256) * "\xFF"
893

    
894
        # fill to match the whole area
895
        bdata += (16 - len(bdata) / 256) * EMPTY_BLOCK
896

    
897
        # updating the data in the memmap [x1000]
898
        self._fill(0x1000, bdata)
899

    
900
        # DTMF id for each channel, 5 bytes lbcd at x7000
901
        # ############## TODO ###################
902
        fldata = "\x00\xf0\xff\xff\xff" * self._chs_progs + \
903
            "\xff" * (5 * (self._upper - self._chs_progs))
904

    
905
        # write it
906
        # updating the data in the memmap [x7000]
907
        self._fill(0x7000, fldata)
908

    
909
    def _set_variant(self):
910
        """Select and set the correct variables for the class acording
911
        to the correct variant of the radio"""
912
        rid = self._mmap[0xA7:0xAD]
913

    
914
        # indentify the radio variant and set the enviroment to it's values
915
        try:
916
            self._upper, low, high, self._kind = self.VARIANTS[rid]
917

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

    
924
            # put the VARIANT in the class, clean the model / CHs / Type
925
            # in the same layout as the KPG program
926
            self._VARIANT = self.MODEL + " [" + str(self._upper) + "CH]: "
927
            # In the OEM string we show the real OEM ranges
928
            self._VARIANT += self._kind + ", %d - %d MHz" % (low, high)
929

    
930
        except KeyError:
931
            LOG.debug("Wrong Kenwood radio, ID or unknown variant")
932
            LOG.debug(util.hexprint(rid))
933
            raise errors.RadioError(
934
                "Wrong Kenwood radio, ID or unknown variant, see LOG output.")
935
            return False
936

    
937
    def sync_in(self):
938
        """Do a download of the radio eeprom"""
939
        self._mmap = do_download(self)
940
        self.process_mmap()
941

    
942
    def sync_out(self):
943
        """Do an upload to the radio eeprom"""
944

    
945
        # chirp signature on the eprom ;-)
946
        sign = "Chirp"
947
        self._fill(0xbb, sign)
948

    
949
        try:
950
            self._prep_data()
951
            do_upload(self)
952
        except errors.RadioError:
953
            raise
954
        except Exception, e:
955
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
956

    
957
    def process_mmap(self):
958
        """Process the memory object"""
959
        # how many channels are programed
960
        self._chs_progs = ord(self._mmap[15])
961

    
962
        # load the memobj
963
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
964

    
965
        # to set the vars on the class to the correct ones
966
        self._set_variant()
967

    
968
    def get_raw_memory(self, number):
969
        """Return a raw representation of the memory object, which
970
        is very helpful for development"""
971
        return repr(self._memobj.memory[number])
972

    
973
    def _decode_tone(self, val):
974
        """Parse the tone data to decode from mem, it returns:
975
        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
976
        val = int(val)
977
        if val == 65535:
978
            return '', None, None
979
        elif val >= 0x2800:
980
            code = int("%03o" % (val & 0x07FF))
981
            pol = (val & 0x8000) and "R" or "N"
982
            return 'DTCS', code, pol
983
        else:
984
            a = val / 10.0
985
            return 'Tone', a, None
986

    
987
    def _encode_tone(self, memval, mode, value, pol):
988
        """Parse the tone data to encode from UI to mem"""
989
        if mode == '':
990
            memval.set_raw("\xff\xff")
991
        elif mode == 'Tone':
992
            memval.set_value(int(value * 10))
993
        elif mode == 'DTCS':
994
            val = int("%i" % value, 8) + 0x2800
995
            if pol == "R":
996
                val += 0xA000
997
            memval.set_value(val)
998
        else:
999
            raise Exception("Internal error: invalid mode `%s'" % mode)
1000

    
1001
    def _get_scan(self, chan):
1002
        """Get the channel scan status from the 16 bytes array on the eeprom
1003
        then from the bits on the byte, return '' or 'S' as needed"""
1004
        result = "S"
1005
        byte = int(chan/8)
1006
        bit = chan % 8
1007
        res = self._memobj.settings.add[byte] & (pow(2, bit))
1008
        if res > 0:
1009
            result = ""
1010

    
1011
        return result
1012

    
1013
    def _set_scan(self, chan, value):
1014
        """Set the channel scan status from UI to the mem_map"""
1015
        byte = int(chan/8)
1016
        bit = chan % 8
1017

    
1018
        # get the actual value to see if I need to change anything
1019
        actual = self._get_scan(chan)
1020
        if actual != value:
1021
            # I have to flip the value
1022
            rbyte = self._memobj.settings.add[byte]
1023
            rbyte = rbyte ^ pow(2, bit)
1024
            self._memobj.settings.add[byte] = rbyte
1025

    
1026
    def get_memory(self, number):
1027
        # Get a low-level memory object mapped to the image
1028
        _mem = self._memobj.memory[number - 1]
1029

    
1030
        # Create a high-level memory object to return to the UI
1031
        mem = chirp_common.Memory()
1032

    
1033
        # Memory number
1034
        mem.number = number
1035

    
1036
        # this radio has a setting about the amount of real chans of the 128
1037
        # olso in the channel has xff on the Rx freq it's empty
1038
        if (number > (self._chs_progs + 1)) or (_mem.get_raw()[0] == "\xFF"):
1039
            mem.empty = True
1040
            # but is not enough, you have to crear the memory in the mmap
1041
            # to get it ready for the sync_out process
1042
            _mem.set_raw("\xFF" * 48)
1043
            return mem
1044

    
1045
        # Freq and offset
1046
        mem.freq = int(_mem.rxfreq) * 10
1047
        # tx freq can be blank
1048
        if _mem.get_raw()[16] == "\xFF":
1049
            # TX freq not set
1050
            mem.offset = 0
1051
            mem.duplex = "off"
1052
        else:
1053
            # TX feq set
1054
            offset = (int(_mem.txfreq) * 10) - mem.freq
1055
            if offset < 0:
1056
                mem.offset = abs(offset)
1057
                mem.duplex = "-"
1058
            elif offset > 0:
1059
                mem.offset = offset
1060
                mem.duplex = "+"
1061
            else:
1062
                mem.offset = 0
1063

    
1064
        # name TAG of the channel
1065
        mem.name = str(_mem.name).rstrip()
1066

    
1067
        # power
1068
        mem.power = POWER_LEVELS[_mem.power]
1069

    
1070
        # wide/marrow
1071
        mem.mode = MODES[_mem.wide]
1072

    
1073
        # skip
1074
        mem.skip = self._get_scan(number - 1)
1075

    
1076
        # tone data
1077
        rxtone = txtone = None
1078
        txtone = self._decode_tone(_mem.tx_tone)
1079
        rxtone = self._decode_tone(_mem.rx_tone)
1080
        chirp_common.split_tone_decode(mem, txtone, rxtone)
1081

    
1082
        # Extra
1083
        # bank and number in the channel
1084
        mem.extra = RadioSettingGroup("extra", "Extra")
1085

    
1086
        # validate bank
1087
        b = int(_mem.bank)
1088
        if b > 127 or b == 0:
1089
            _mem.bank = b = 1
1090

    
1091
        bank = RadioSetting("bank", "Bank it belongs",
1092
                            RadioSettingValueInteger(1, 128, b))
1093
        mem.extra.append(bank)
1094

    
1095
        # validate bnumb
1096
        if int(_mem.bnumb) > 127:
1097
            _mem.bank = mem.number
1098

    
1099
        bnumb = RadioSetting("bnumb", "Ch number in the bank",
1100
                             RadioSettingValueInteger(0, 127, _mem.bnumb))
1101
        mem.extra.append(bnumb)
1102

    
1103
        bs = RadioSetting("beat_shift", "Beat shift",
1104
                          RadioSettingValueBoolean(
1105
                              not bool(_mem.beat_shift)))
1106
        mem.extra.append(bs)
1107

    
1108
        cp = RadioSetting("compander", "Compander",
1109
                          RadioSettingValueBoolean(
1110
                              not bool(_mem.compander)))
1111
        mem.extra.append(cp)
1112

    
1113
        bl = RadioSetting("busy_lock", "Busy Channel lock",
1114
                          RadioSettingValueBoolean(
1115
                              not bool(_mem.busy_lock)))
1116
        mem.extra.append(bl)
1117

    
1118
        return mem
1119

    
1120
    def set_memory(self, mem):
1121
        """Set the memory data in the eeprom img from the UI
1122
        not ready yet, so it will return as is"""
1123

    
1124
        # get the eprom representation of this channel
1125
        _mem = self._memobj.memory[mem.number - 1]
1126

    
1127
        # if empty memmory
1128
        if mem.empty:
1129
            _mem.set_raw("\xFF" * 48)
1130
            return
1131

    
1132
        # frequency
1133
        _mem.rxfreq = mem.freq / 10
1134

    
1135
        # this are a mistery yet, but so falr there is no impact
1136
        # whit this default values for new channels
1137
        if int(_mem.rx_unkw) == 0xff:
1138
            _mem.rx_unkw = 0x35
1139
            _mem.tx_unkw = 0x32
1140

    
1141
        # duplex
1142
        if mem.duplex == "+":
1143
            _mem.txfreq = (mem.freq + mem.offset) / 10
1144
        elif mem.duplex == "-":
1145
            _mem.txfreq = (mem.freq - mem.offset) / 10
1146
        elif mem.duplex == "off":
1147
            for byte in _mem.txfreq:
1148
                byte.set_raw("\xFF")
1149
        else:
1150
            _mem.txfreq = mem.freq / 10
1151

    
1152
        # tone data
1153
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
1154
            chirp_common.split_tone_encode(mem)
1155
        self._encode_tone(_mem.tx_tone, txmode, txtone, txpol)
1156
        self._encode_tone(_mem.rx_tone, rxmode, rxtone, rxpol)
1157

    
1158
        # name TAG of the channel
1159
        _namelength = self.get_features().valid_name_length
1160
        for i in range(_namelength):
1161
            try:
1162
                _mem.name[i] = mem.name[i]
1163
            except IndexError:
1164
                _mem.name[i] = "\x20"
1165

    
1166
        # power
1167
        # default power is low
1168
        if mem.power is None:
1169
            mem.power = POWER_LEVELS[0]
1170

    
1171
        _mem.power = POWER_LEVELS.index(mem.power)
1172

    
1173
        # wide/marrow
1174
        _mem.wide = MODES.index(mem.mode)
1175

    
1176
        # scan add property
1177
        self._set_scan(mem.number - 1, mem.skip)
1178

    
1179
        # bank and number in the channel
1180
        if int(_mem.bnumb) == 0xff:
1181
            _mem.bnumb = mem.number - 1
1182
            _mem.bank = 1
1183

    
1184
        # extra settings
1185
        for setting in mem.extra:
1186
            if setting != "bank" or setting != "bnumb":
1187
                setattr(_mem, setting.get_name(), not bool(setting.value))
1188

    
1189
        # all data get sync after channel mod
1190
        self._prep_data()
1191

    
1192
        return mem
1193

    
1194
    @classmethod
1195
    def match_model(cls, filedata, filename):
1196
        match_size = False
1197
        match_model = False
1198

    
1199
        # testing the file data size
1200
        if len(filedata) == MEM_SIZE:
1201
            match_size = True
1202

    
1203
        # testing the firmware model fingerprint
1204
        match_model = model_match(cls, filedata)
1205

    
1206
        if match_size and match_model:
1207
            return True
1208
        else:
1209
            return False
1210

    
1211
    def get_settings(self):
1212
        """Translate the bit in the mem_struct into settings in the UI"""
1213
        sett = self._memobj.settings
1214
        mess = self._memobj.message
1215
        keys = self._memobj.keys
1216
        idm = self._memobj.id
1217
        passwd = self._memobj.passwords
1218

    
1219
        # test to find if it's a mobile or portable
1220
        ismobile = False     # hence portable
1221
        if "M" in self.TYPE[0][0]:
1222
            ismobile = True
1223

    
1224
        # basic features of the radio
1225
        basic = RadioSettingGroup("basic", "Basic Settings")
1226
        # dealer settings
1227
        dealer = RadioSettingGroup("dealer", "Dealer Settings")
1228
        # buttons
1229
        fkeys = RadioSettingGroup("keys", "Front keys config")
1230

    
1231
        # TODO / PLANED
1232
        # adjust feqs
1233
        # freqs = RadioSettingGroup("freqs", "Adjust Frequencies")
1234

    
1235
        top = RadioSettings(basic, dealer, fkeys)
1236

    
1237
        # Basic
1238
        tot = RadioSetting("settings.tot", "Time Out Timer (TOT)",
1239
                           RadioSettingValueList(TOT, TOT[TOT.index(str(int(
1240
                           sett.tot)))]))
1241
        basic.append(tot)
1242

    
1243
        totalert = RadioSetting("settings.tot_alert", "TOT pre alert",
1244
                                RadioSettingValueList(TOT_PRE,
1245
                                TOT_PRE[int(sett.tot_alert)]))
1246
        basic.append(totalert)
1247

    
1248
        totrekey = RadioSetting("settings.tot_rekey", "TOT re-key time",
1249
                                RadioSettingValueList(TOT_REKEY,
1250
                                TOT_REKEY[int(sett.tot_rekey)]))
1251
        basic.append(totrekey)
1252

    
1253
        totreset = RadioSetting("settings.tot_reset", "TOT reset time",
1254
                                RadioSettingValueList(TOT_RESET,
1255
                                TOT_RESET[int(sett.tot_reset)]))
1256
        basic.append(totreset)
1257

    
1258
        # this feature is for mobile only
1259
        if ismobile:
1260
            minvol = RadioSetting("settings.min_vol", "Minimum volume",
1261
                                  RadioSettingValueList(VOL,
1262
                                  VOL[int(sett.min_vol)]))
1263
            basic.append(minvol)
1264

    
1265
            tv = int(sett.tone_vol)
1266
            if tv == 255:
1267
                tv = 32
1268
            tvol = RadioSetting("settings.tone_vol", "Minimum tone volume",
1269
                                RadioSettingValueList(TVOL, TVOL[tv]))
1270
            basic.append(tvol)
1271

    
1272
        sql = RadioSetting("settings.sql_level", "SQL Ref Level",
1273
                           RadioSettingValueList(SQL, SQL[int(sett.sql_level)]))
1274
        basic.append(sql)
1275

    
1276
        # c2t = RadioSetting("settings.c2t", "Clear to Transpond",
1277
        #                    RadioSettingValueBoolean(not sett.c2t))
1278
        # basic.append(c2t)
1279

    
1280
        ptone = RadioSetting("settings.poweron_tone", "Power On tone",
1281
                             RadioSettingValueBoolean(sett.poweron_tone))
1282
        basic.append(ptone)
1283

    
1284
        ctone = RadioSetting("settings.control_tone", "Control (key) tone",
1285
                             RadioSettingValueBoolean(sett.control_tone))
1286
        basic.append(ctone)
1287

    
1288
        wtone = RadioSetting("settings.warn_tone", "Warning tone",
1289
                             RadioSettingValueBoolean(sett.warn_tone))
1290
        basic.append(wtone)
1291

    
1292
        # Save Battery only for portables?
1293
        if not ismobile:
1294
            bs = int(sett.battery_save) == 0x32 and True or False
1295
            bsave = RadioSetting("settings.battery_save", "Battery Saver",
1296
                                 RadioSettingValueBoolean(bs))
1297
            basic.append(bsave)
1298

    
1299
        ponm = str(sett.poweronmesg).strip("\xff")
1300
        pom = RadioSetting("settings.poweronmesg", "Power on message",
1301
                           RadioSettingValueString(0, 8, ponm, False))
1302
        basic.append(pom)
1303

    
1304
        # dealer
1305
        valid_chars = ",-/:[]" + chirp_common.CHARSET_ALPHANUMERIC
1306
        mstr = "".join([c for c in self._VARIANT if c in valid_chars])
1307

    
1308
        val = RadioSettingValueString(0, 35, mstr)
1309
        val.set_mutable(False)
1310
        mod = RadioSetting("not.mod", "Radio Version", val)
1311
        dealer.append(mod)
1312

    
1313
        sn = str(idm.serial).strip(" \xff")
1314
        val = RadioSettingValueString(0, 8, sn)
1315
        val.set_mutable(False)
1316
        serial = RadioSetting("not.serial", "Serial number", val)
1317
        dealer.append(serial)
1318

    
1319
        svp = str(sett.lastsoftversion).strip(" \xff")
1320
        val = RadioSettingValueString(0, 5, svp)
1321
        val.set_mutable(False)
1322
        sver = RadioSetting("not.softver", "Software Version", val)
1323
        dealer.append(sver)
1324

    
1325
        l1 = str(mess.line1).strip(" \xff")
1326
        line1 = RadioSetting("message.line1", "Comment 1",
1327
                           RadioSettingValueString(0, 32, l1))
1328
        dealer.append(line1)
1329

    
1330
        l2 = str(mess.line2).strip(" \xff")
1331
        line2 = RadioSetting("message.line2", "Comment 2",
1332
                             RadioSettingValueString(0, 32, l2))
1333
        dealer.append(line2)
1334

    
1335
        sprog = RadioSetting("settings.self_prog", "Self program",
1336
                             RadioSettingValueBoolean(sett.self_prog))
1337
        dealer.append(sprog)
1338

    
1339
        clone = RadioSetting("settings.clone", "Allow clone",
1340
                             RadioSettingValueBoolean(sett.clone))
1341
        dealer.append(clone)
1342

    
1343
        panel = RadioSetting("settings.panel_test", "Panel Test",
1344
                             RadioSettingValueBoolean(sett.panel_test))
1345
        dealer.append(panel)
1346

    
1347
        fmw = RadioSetting("settings.firmware_prog", "Firmware program",
1348
                           RadioSettingValueBoolean(sett.firmware_prog))
1349
        dealer.append(fmw)
1350

    
1351
        # front keys
1352
        # The Mobile only parameters are wraped here
1353
        if ismobile:
1354
            vu = RadioSetting("keys.kVOL_UP", "VOL UP",
1355
                              RadioSettingValueList(KEYS.values(),
1356
                              KEYS.values()[KEYS.keys().index(
1357
                                  int(keys.kVOL_UP))]))
1358
            fkeys.append(vu)
1359

    
1360
            vd = RadioSetting("keys.kVOL_DOWN", "VOL DOWN",
1361
                              RadioSettingValueList(KEYS.values(),
1362
                              KEYS.values()[KEYS.keys().index(
1363
                                  int(keys.kVOL_DOWN))]))
1364
            fkeys.append(vd)
1365

    
1366
            chu = RadioSetting("keys.kCH_UP", "CH UP",
1367
                               RadioSettingValueList(KEYS.values(),
1368
                               KEYS.values()[KEYS.keys().index(
1369
                                   int(keys.kCH_UP))]))
1370
            fkeys.append(chu)
1371

    
1372
            chd = RadioSetting("keys.kCH_DOWN", "CH DOWN",
1373
                               RadioSettingValueList(KEYS.values(),
1374
                               KEYS.values()[KEYS.keys().index(
1375
                                   int(keys.kCH_DOWN))]))
1376
            fkeys.append(chd)
1377

    
1378
            foot = RadioSetting("keys.kFOOT", "Foot switch",
1379
                               RadioSettingValueList(KEYS.values(),
1380
                               KEYS.values()[KEYS.keys().index(
1381
                                   int(keys.kCH_DOWN))]))
1382
            fkeys.append(foot)
1383

    
1384
        # this is the common buttons for all
1385

    
1386
        # 260G model don't have the front keys
1387
        if "P2600" not in self.TYPE:
1388
            scn_name = "SCN"
1389
            if not ismobile:
1390
                scn_name = "Open Circle"
1391

    
1392
            scn = RadioSetting("keys.kSCN", scn_name,
1393
                               RadioSettingValueList(KEYS.values(),
1394
                               KEYS.values()[KEYS.keys().index(
1395
                                   int(keys.kSCN))]))
1396
            fkeys.append(scn)
1397

    
1398
            a_name = "A"
1399
            if not ismobile:
1400
                a_name = "Closed circle"
1401

    
1402
            a = RadioSetting("keys.kA", a_name,
1403
                             RadioSettingValueList(KEYS.values(),
1404
                             KEYS.values()[KEYS.keys().index(
1405
                                 int(keys.kA))]))
1406
            fkeys.append(a)
1407

    
1408
            da_name = "D/A"
1409
            if not ismobile:
1410
                da_name = "< key"
1411

    
1412
            da = RadioSetting("keys.kDA", da_name,
1413
                              RadioSettingValueList(KEYS.values(),
1414
                              KEYS.values()[KEYS.keys().index(
1415
                                  int(keys.kDA))]))
1416
            fkeys.append(da)
1417

    
1418
            gu_name = "Triangle up"
1419
            if not ismobile:
1420
                gu_name = "Side 1"
1421

    
1422
            gu = RadioSetting("keys.kGROUP_UP", gu_name,
1423
                              RadioSettingValueList(KEYS.values(),
1424
                              KEYS.values()[KEYS.keys().index(
1425
                                  int(keys.kGROUP_UP))]))
1426
            fkeys.append(gu)
1427

    
1428
        # Side keys on portables
1429
        gd_name = "Triangle Down"
1430
        if not ismobile:
1431
            gd_name = "> key"
1432

    
1433
        gd = RadioSetting("keys.kGROUP_DOWN", gd_name,
1434
                          RadioSettingValueList(KEYS.values(),
1435
                          KEYS.values()[KEYS.keys().index(
1436
                              int(keys.kGROUP_DOWN))]))
1437
        fkeys.append(gd)
1438

    
1439
        mon_name = "MON"
1440
        if not ismobile:
1441
            mon_name = "Side 2"
1442

    
1443
        mon = RadioSetting("keys.kMON", mon_name,
1444
                           RadioSettingValueList(KEYS.values(),
1445
                           KEYS.values()[KEYS.keys().index(
1446
                               int(keys.kMON))]))
1447
        fkeys.append(mon)
1448

    
1449
        return top
1450

    
1451
    def set_settings(self, settings):
1452
        """Translate the settings in the UI into bit in the mem_struct
1453
        I don't understand well the method used in many drivers
1454
        so, I used mine, ugly but works ok"""
1455

    
1456
        mobj = self._memobj
1457

    
1458
        for element in settings:
1459
            if not isinstance(element, RadioSetting):
1460
                self.set_settings(element)
1461
                continue
1462

    
1463
            # Let's roll the ball
1464
            if "." in element.get_name():
1465
                inter, setting = element.get_name().split(".")
1466
                # you must ignore the settings with "not"
1467
                # this are READ ONLY attributes
1468
                if inter == "not":
1469
                    continue
1470

    
1471
                obj = getattr(mobj, inter)
1472
                value = element.value
1473

    
1474
                # integers case + special case
1475
                if setting in ["tot", "tot_alert", "min_vol", "tone_vol",
1476
                               "sql_level", "tot_rekey", "tot_reset"]:
1477
                    # catching the "off" values as zero
1478
                    try:
1479
                        value = int(value)
1480
                    except:
1481
                        value = 0
1482

    
1483
                    # tot case step 15
1484
                    if setting == "tot":
1485
                        value = value * 15
1486
                        # off is special
1487
                        if value == 0:
1488
                            value = 0x4b0
1489

    
1490
                    # Caso tone_vol
1491
                    if setting == "tone_vol":
1492
                        # off is special
1493
                        if value == 32:
1494
                            value = 0xff
1495

    
1496
                # Bool types + inverted
1497
                if setting in ["c2t", "poweron_tone", "control_tone",
1498
                               "warn_tone", "battery_save", "self_prog",
1499
                               "clone", "panel_test"]:
1500
                    value = bool(value)
1501

    
1502
                    # this cases are inverted
1503
                    if setting == "c2t":
1504
                        value = not value
1505

    
1506
                    # case battery save is special
1507
                    if setting == "battery_save":
1508
                        if bool(value) is True:
1509
                            value = 0x32
1510
                        else:
1511
                            value = 0xff
1512

    
1513
                # String cases
1514
                if setting in ["poweronmesg", "line1", "line2"]:
1515
                    # some vars
1516
                    value = str(value)
1517
                    just = 8
1518
                    # lines with 32
1519
                    if "line" in setting:
1520
                        just = 32
1521

    
1522
                    # empty case
1523
                    if len(value) == 0:
1524
                        value = "\xff" * just
1525
                    else:
1526
                        value = value.ljust(just)
1527

    
1528
                # case keys, with special config
1529
                if inter == "keys":
1530
                    value = KEYS.keys()[KEYS.values().index(str(value))]
1531

    
1532
            # Apply al configs done
1533
            setattr(obj, setting, value)
1534

    
1535
    def get_bank_model(self):
1536
        """Pass the bank model to the UI part"""
1537
        rf = self.get_features()
1538
        if rf.has_bank is True:
1539
            return Kenwood60GBankModel(self)
1540
        else:
1541
            return None
1542

    
1543
    def _get_bank(self, loc):
1544
        """Get the bank data for a specific channel"""
1545
        mem = self._memobj.memory[loc - 1]
1546
        bank = int(mem.bank) - 1
1547

    
1548
        if bank > self._num_banks or bank < 1:
1549
            # all channels must belong to a bank, even with just 1 bank
1550
            return 0
1551
        else:
1552
            return bank
1553

    
1554
    def _set_bank(self, loc, bank):
1555
        """Set the bank data for a specific channel"""
1556
        try:
1557
            b = int(bank)
1558
            if b > 127:
1559
                b = 0
1560
            mem = self._memobj.memory[loc - 1]
1561
            mem.bank = b + 1
1562
        except:
1563
            msg = "You can't have a channel without a bank, click another bank"
1564
            raise errors.InvalidDataError(msg)
1565

    
1566

    
1567
# This kenwwood family is known as "60-G Serie"
1568
# all this radios ending in G are compatible:
1569
#
1570
# Portables VHF TK-260G/270G/272G/278G
1571
# Portables UHF TK-360G/370G/372G/378G/388G
1572
#
1573
# Mobiles VHF TK-760G/762G/768G
1574
# Mobiles VHF TK-860G/862G/868G
1575
#
1576
# WARNING !!!! Radios With Password in the data section ###############
1577
#
1578
# When a radio has a data password (aka to program it) the last byte (#8)
1579
# in the id code change from \xf1 to \xb1; so we remove this last byte
1580
# from the identification procedures and variants.
1581
#
1582
# This effectively render the data password USELESS even if set.
1583
# Translation: Chirps will read and write password protected radios
1584
# with no problem.
1585

    
1586

    
1587
@directory.register
1588
class TK868G_Radios(Kenwood_Serie_60G):
1589
    """Kenwood TK-868G Radio M/C"""
1590
    MODEL = "TK-868G"
1591
    TYPE = ["M8680", ]
1592
    VARIANTS = {
1593
        "M8680\x18":    (8, 400, 490, "M"),
1594
        "M8680;":       (128, 350, 390, "C1"),
1595
        "M86808":       (128, 400, 430, "C2"),
1596
        "M86806":       (128, 450, 490, "C3"),
1597
        }
1598

    
1599

    
1600
@directory.register
1601
class TK862G_Radios(Kenwood_Serie_60G):
1602
    """Kenwood TK-862G Radio K/E/(N)E"""
1603
    MODEL = "TK-862G"
1604
    TYPE = ["M8620", ]
1605
    VARIANTS = {
1606
        "M8620\x06":    (8, 450, 490, "K"),
1607
        "M8620\x07":    (8, 485, 512, "K2"),
1608
        "M8620&":       (8, 440, 470, "E"),
1609
        "M8620V":       (8, 440, 470, "(N)E"),
1610
        }
1611

    
1612

    
1613
@directory.register
1614
class TK860G_Radios(Kenwood_Serie_60G):
1615
    """Kenwood TK-860G Radio K"""
1616
    MODEL = "TK-860G"
1617
    TYPE = ["M8600", ]
1618
    VARIANTS = {
1619
        "M8600\x08":    (128, 400, 430, "K"),
1620
        "M8600\x06":    (128, 450, 490, "K1"),
1621
        "M8600\x07":    (128, 485, 512, "K2"),
1622
        "M8600\x18":    (128, 400, 430, "M"),
1623
        "M8600\x16":    (128, 450, 490, "M1"),
1624
        "M8600\x17":    (128, 485, 520, "M2"),
1625
        }
1626

    
1627

    
1628
@directory.register
1629
class TK768G_Radios(Kenwood_Serie_60G):
1630
    """Kenwood TK-768G Radios [M/C]"""
1631
    MODEL = "TK-768G"
1632
    TYPE = ["M7680", ]
1633
    # Note that 8 CH don't have banks
1634
    VARIANTS = {
1635
        "M7680\x15": (8, 136, 162, "M2"),
1636
        "M7680\x14": (8, 148, 174, "M"),
1637
        "M76805":    (128, 136, 162, "C2"),
1638
        "M76804":    (128, 148, 174, "C"),
1639
        }
1640

    
1641

    
1642
@directory.register
1643
class TK762G_Radios(Kenwood_Serie_60G):
1644
    """Kenwood TK-762G Radios [K/E/NE]"""
1645
    MODEL = "TK-762G"
1646
    TYPE = ["M7620", ]
1647
    # Note that 8 CH don't have banks
1648
    VARIANTS = {
1649
        "M7620\x05": (8, 136, 162, "K2"),
1650
        "M7620\x04": (8, 148, 172, "K"),
1651
        "M7620$":    (8, 148, 172, "E"),
1652
        "M7620T":    (8, 148, 172, "NE"),
1653
        }
1654

    
1655

    
1656
@directory.register
1657
class TK760G_Radios(Kenwood_Serie_60G):
1658
    """Kenwood TK-760G Radios [K/M/(N)E]"""
1659
    MODEL = "TK-760G"
1660
    TYPE = ["M7600", ]
1661
    VARIANTS = {
1662
        "M7600\x05": (128, 136, 162, "K2"),
1663
        "M7600\x04": (128, 148, 174, "K"),
1664
        "M7600\x14": (128, 148, 174, "M"),
1665
        "M7600T":    (128, 148, 174, "NE")
1666
        }
1667

    
1668

    
1669
@directory.register
1670
class TK388G_Radios(Kenwood_Serie_60G):
1671
    """Kenwood TK-388 Radio [K/E/M/NE]"""
1672
    MODEL = "TK-388G"
1673
    TYPE = ["P3880", ]
1674
    VARIANTS = {
1675
        "P3880\x1b": (128, 350, 370, "M")
1676
        }
1677

    
1678

    
1679
@directory.register
1680
class TK378G_Radios(Kenwood_Serie_60G):
1681
    """Kenwood TK-378 Radio [K/E/M/NE]"""
1682
    MODEL = "TK-378G"
1683
    TYPE = ["P3780", ]
1684
    VARIANTS = {
1685
        "P3780\x16": (16, 450, 470, "M"),
1686
        "P3780\x17": (16, 400, 420, "M1"),
1687
        "P3780\x36": (128, 490, 512, "C"),
1688
        "P3780\x39": (128, 403, 430, "C1")
1689
        }
1690

    
1691

    
1692
@directory.register
1693
class TK372G_Radios(Kenwood_Serie_60G):
1694
    """Kenwood TK-372 Radio [K/E/M/NE]"""
1695
    MODEL = "TK-372G"
1696
    TYPE = ["P3720", ]
1697
    VARIANTS = {
1698
        "P3720\x06": (32, 450, 470, "K"),
1699
        "P3720\x07": (32, 470, 490, "K1"),
1700
        "P3720\x08": (32, 490, 512, "K2"),
1701
        "P3720\x09": (32, 403, 430, "K3")
1702
        }
1703

    
1704

    
1705
@directory.register
1706
class TK370G_Radios(Kenwood_Serie_60G):
1707
    """Kenwood TK-370 Radio [K/E/M/NE]"""
1708
    MODEL = "TK-370G"
1709
    TYPE = ["P3700", ]
1710
    VARIANTS = {
1711
        "P3700\x06": (128, 450, 470, "K"),
1712
        "P3700\x07": (128, 470, 490, "K1"),
1713
        "P3700\x08": (128, 490, 512, "K2"),
1714
        "P3700\x09": (128, 403, 430, "K3"),
1715
        "P3700\x16": (128, 450, 470, "M"),
1716
        "P3700\x17": (128, 470, 490, "M1"),
1717
        "P3700\x18": (128, 490, 520, "M2"),
1718
        "P3700\x19": (128, 403, 430, "M3"),
1719
        "P3700&": (128, 440, 470, "E"),
1720
        "P3700V": (128, 440, 470, "NE")
1721
        }
1722

    
1723

    
1724
@directory.register
1725
class TK360G_Radios(Kenwood_Serie_60G):
1726
    """Kenwood TK-360 Radio [K/E/M/NE]"""
1727
    MODEL = "TK-360G"
1728
    TYPE = ["P3600", "p3600"]
1729
    VARIANTS = {
1730
        "P3600\x06": (8, 450, 470, "K"),
1731
        "P3600\x07": (8, 470, 490, "K1"),
1732
        "P3600\x08": (8, 490, 512, "K2"),
1733
        "P3600\x09": (8, 403, 430, "K3"),
1734
        "P3600&": (8, 440, 470, "E"),
1735
        "P3600)": (8, 403, 430, "E1"),
1736
        "P3600\x16": (8, 450, 470, "M"),
1737
        "P3600\x17": (8, 470, 490, "M1"),
1738
        "P3600\x19": (8, 403, 430, "M2"),
1739
        "P3600V": (8, 440, 470, "NE"),
1740
        "P3600Y": (8, 403, 430, "NE1"),
1741
        "p3600&": (8, 403, 470, "E4")
1742
        }
1743

    
1744

    
1745
@directory.register
1746
class TK278G_Radios(Kenwood_Serie_60G):
1747
    """Kenwood TK-278G Radio C/C1/M/M1"""
1748
    MODEL = "TK-278G"
1749
    TYPE = ["P2780", ]
1750
    # Note that 16 CH don't have banks
1751
    VARIANTS = {
1752
        "P27805":    (128, 136, 150, "C1"),
1753
        "P27804":    (128, 150, 174, "C"),
1754
        "P2780\x15": (16,  136, 150, "M1"),
1755
        "P2780\x14": (16,  150, 174, "M")
1756
        }
1757

    
1758

    
1759
@directory.register
1760
class TK272G_Radios(Kenwood_Serie_60G):
1761
    """Kenwood TK-272G Radio K/K1"""
1762
    MODEL = "TK-272G"
1763
    TYPE = ["P2720", ]
1764
    VARIANTS = {
1765
        "P2720\x05": (32, 136, 150, "K1"),
1766
        "P2720\x04": (32, 150, 174, "K")
1767
        }
1768

    
1769

    
1770
@directory.register
1771
class TK270G_Radios(Kenwood_Serie_60G):
1772
    """Kenwood TK-270G Radio K/K1/M/E/NE/NT"""
1773
    MODEL = "TK-270G"
1774
    TYPE = ["P2700", ]
1775
    VARIANTS = {
1776
        "P2700T":    (128, 146, 174, "NE/NT"),
1777
        "P2700$":    (128, 146, 174, "E"),
1778
        "P2700\x14": (128, 150, 174, "M"),
1779
        "P2700\x05": (128, 136, 150, "K1"),
1780
        "P2700\x04": (128, 150, 174, "K")
1781
        }
1782

    
1783

    
1784
@directory.register
1785
class TK260G_Radios(Kenwood_Serie_60G):
1786
    """Kenwood TK-260G Radio K/K1/M/E/NE/NT"""
1787
    MODEL = "TK-260G"
1788
    _hasbanks = False
1789
    TYPE = ["P2600", ]
1790
    VARIANTS = {
1791
        "P2600U":    (8, 136, 150, "N1"),
1792
        "P2600T":    (8, 146, 174, "N"),
1793
        "P2600$":    (8, 150, 174, "E"),
1794
        "P2600\x14": (8, 150, 174, "M"),
1795
        "P2600\x05": (8, 136, 150, "K1"),
1796
        "P2600\x04": (8, 150, 174, "K")
1797
        }
(21-21/23)