Project

General

Profile

Bug #3349 » tk760g.py

Modified driver to test, the fix is related to a serial flush before every read. - Pavel Milanes, 02/26/2016 08:00 PM

 
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

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

    
27
LOG = logging.getLogger(__name__)
28

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
329

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

    
338
    return data
339

    
340

    
341
def _raw_send(radio, data):
342
    """Raw send to the radio device"""
343
    try:
344
        radio.pipe.write(data)
345
    except:
346
        raise errors.RadioError("Error sending data to radio")
347

    
348

    
349
def _close_radio(radio):
350
    """Get the radio out of program mode"""
351
    # 3 times, it will don't harm in normal work,
352
    # but it help's a lot in the developer process
353
    _raw_send(radio, "\x45\x45\x45")
354

    
355

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

    
363

    
364
def _send(radio, frame):
365
    """Generic send data to the radio"""
366
    _raw_send(radio, frame)
367

    
368

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

    
373

    
374
def _handshake(radio, msg=""):
375
    """Make a full handshake"""
376
    # send ACK
377
    _raw_send(radio, ACK_CMD)
378
    # receive ACK
379
    ack = _raw_recv(radio, 1)
380
    # check ACK
381
    if ack != ACK_CMD:
382
        _close_radio(radio)
383
        mesg = "Handshake failed " + msg
384

    
385
        # DEBUG
386
        LOG.debug(mesg)
387

    
388
        raise Exception(mesg)
389

    
390

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

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

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

    
410

    
411
def _recv(radio):
412
    """Receive data from the radio, 258 bytes split in (cmd, data, checksum)
413
    checking the checksum to be correct, and returning just
414
    256 bytes of data or false if short empty block"""
415
    rxdata = _raw_recv(radio, BLOCK_SIZE + 2)
416
    # when the RX block has two bytes and the first is \x5A
417
    # then the block is all \xFF
418
    if len(rxdata) == 2 and rxdata[0] == "\x5A":
419
        _handshake(radio, "short block")
420
        return False
421
    else:
422
        rcs = ord(rxdata[-1])
423
        data = rxdata[1:-1]
424
        ccs = _checksum(data)
425

    
426
        if rcs != ccs:
427
            _close_radio(radio)
428
            raise errors.RadioError(
429
                "Block Checksum Error! real %02x, calculated %02x" %
430
                (rcs, ccs))
431

    
432
        _handshake(radio, "after checksum")
433
        return data
434

    
435

    
436
def _open_radio(radio):
437
    """Open the radio into program mode and check if it's the correct model"""
438
    # minimum timeout is 0.13, set to 0.25 to be safe
439
    radio.pipe.setTimeout(0.25)
440
    radio.pipe.setParity("E")
441

    
442
    # DEBUG
443
    LOG.debug("Entering program mode.")
444

    
445
    # try a few times to get the radio into program mode
446
    exito = False
447
    for i in range(0, 10):
448
        _raw_send(radio, "PROGRAM")
449
        ack = _raw_recv(radio, 1)
450

    
451
        if ack != ACK_CMD:
452
            # DEBUG
453
            LOG.debug("Try %s failed, traying again...")
454
            time.sleep(0.25)
455
        else:
456
            exito = True
457
            break
458

    
459
    if exito is False:
460
        _close_radio(radio)
461
        LOG.debug("Radio did not accepted PROGRAM command in five atempts")
462
        raise errors.RadioError("The radio doesn't accept program mode")
463

    
464
    # DEBUG
465
    LOG.debug("Received ACK to the PROGRAM command, send ID query.")
466

    
467
    _raw_send(radio, "\x02")
468
    rid = _raw_recv(radio, 8)
469

    
470
    if not (radio.TYPE in rid):
471
        # bad response, properly close the radio before exception
472
        _close_radio(radio)
473

    
474
        # DEBUG
475
        LOG.debug("Incorrect model ID:")
476
        LOG.debug(util.hexprint(rid))
477

    
478
        raise errors.RadioError(
479
            "Incorrect model ID, got %s, it not contains %s" %
480
            (rid.strip("\xff"), radio.TYPE))
481

    
482
    # DEBUG
483
    LOG.debug("Full ident string is:")
484
    LOG.debug(util.hexprint(rid))
485

    
486
    _handshake(radio)
487

    
488

    
489
def do_download(radio):
490
    """ The download function """
491
    # UI progress
492
    status = chirp_common.Status()
493
    status.cur = 0
494
    status.max = MEM_SIZE / 256
495
    status.msg = "Getting the radio into program mode."
496
    radio.status_fn(status)
497
    data = ""
498
    count = 0
499

    
500
    # open the radio
501
    _open_radio(radio)
502
    # speed up the reading
503
    radio.pipe.setTimeout(0.3)
504

    
505
    # DEBUG
506
    LOG.debug("Starting the download from radio")
507

    
508
    for addr in MEM_BLOCKS:
509
        _send(radio, _make_frame("R", addr))
510
        # flush input
511
        radio.pipe.flushInput()
512
        # receive
513
        d = _recv(radio)
514
        # if empty block, it return false
515
        # aka we asume a empty 256 xFF block
516
        if d is False:
517
            d = EMPTY_BLOCK
518
            # DEBUG
519
            LOG.debug("Receiving block %02x empty." % addr)
520
        else:
521
            # DEBUG
522
            LOG.debug("Receiving block %02x ok." % addr)
523

    
524
        data += d
525

    
526
        # UI Update
527
        status.cur = count
528
        status.msg = "Cloning from radio..."
529
        radio.status_fn(status)
530

    
531
        count += 1
532

    
533
    _close_radio(radio)
534
    return memmap.MemoryMap(data)
535

    
536

    
537
def do_upload(radio):
538
    """ The upload function """
539
    # UI progress
540
    status = chirp_common.Status()
541
    status.cur = 0
542
    status.max = MEM_SIZE / 256
543
    status.msg = "Getting the radio into program mode."
544
    radio.status_fn(status)
545
    data = ""
546
    count = 0
547

    
548
    # open the radio
549
    _open_radio(radio)
550
    # the default for the original soft as measured
551
    radio.pipe.setTimeout(0.5)
552

    
553
    # DEBUG
554
    LOG.debug("Starting the download to the radio")
555

    
556
    count = 0
557
    raddr = 0
558
    for addr in MEM_BLOCKS:
559
        # this is the data block to write
560
        data = radio.get_mmap()[raddr:raddr+BLOCK_SIZE]
561

    
562
        # The blocks from x59-x5F are NOT programmable
563
        # The blocks from x11-x1F are writed only if not empty
564
        if addr in RO_BLOCKS:
565
            # checking if in the range of optional blocks
566
            if addr >= 0x10 and addr <= 0x1F:
567
                # block is empty ?
568
                if data == EMPTY_BLOCK:
569
                    # no write of this block
570
                    # but we have to continue updating the counters
571
                    count += 1
572
                    raddr = count * 256
573
                    continue
574
            else:
575
                count += 1
576
                raddr = count * 256
577
                continue
578

    
579
        if data == EMPTY_BLOCK:
580
            frame = _make_frame("Z", addr) + "\xFF"
581
        else:
582
            cs = _checksum(data)
583
            frame = _make_frame("W", addr) + data + chr(cs)
584

    
585
        _send(radio, frame)
586
        ack = _raw_recv(radio, 1)
587
        _check_write_ack(radio, ack, addr)
588

    
589
        # DEBUG
590
        LOG.debug("Sending block %02x" % addr)
591

    
592
        # UI Update
593
        status.cur = count
594
        status.msg = "Cloning to radio..."
595
        radio.status_fn(status)
596

    
597
        count += 1
598
        raddr = count * 256
599

    
600
    _close_radio(radio)
601

    
602

    
603
def model_match(cls, data):
604
    """Match the opened/downloaded image to the correct version"""
605
    rid = data[0xA7:0xAE]
606
    if (rid in cls.VARIANTS):
607
        # correct model
608
        return True
609
    else:
610
        return False
611

    
612

    
613
class Kenwood60GBankModel(chirp_common.BankModel):
614
    """Testing the bank model on kennwood"""
615
    channelAlwaysHasBank = True
616

    
617
    def get_num_mappings(self):
618
        return self._radio._num_banks
619

    
620
    def get_mappings(self):
621
        banks = []
622
        for i in range(0, self._radio._num_banks):
623
            bindex = i + 1
624
            bank = self._radio._bclass(self, i, "%03i" % bindex)
625
            bank.index = i
626
            banks.append(bank)
627
        return banks
628

    
629
    def add_memory_to_mapping(self, memory, bank):
630
        self._radio._set_bank(memory.number, bank.index)
631

    
632
    def remove_memory_from_mapping(self, memory, bank):
633
        if self._radio._get_bank(memory.number) != bank.index:
634
            raise Exception("Memory %i not in bank %s. Cannot remove." %
635
                            (memory.number, bank))
636

    
637
        # Warning about removing a channel on bank 0
638
        #if bank.index == self._radio._get_bank(memory.number) == 0:
639
            #mesg = "Can't remove, this is the default bank for "
640
            #mesg += "all channels"
641
            #raise Exception(mesg)
642

    
643
        # We can't "Remove" it for good
644
        # the kenwood paradigm don't allow it
645
        # instead we move it to bank 0
646
        self._radio._set_bank(memory.number, 0)
647

    
648
    def get_mapping_memories(self, bank):
649
        memories = []
650
        for i in range(0, self._radio._upper):
651
            if self._radio._get_bank(i) == bank.index:
652
                memories.append(self._radio.get_memory(i))
653
        return memories
654

    
655
    def get_memory_mappings(self, memory):
656
        index = self._radio._get_bank(memory.number)
657
        return [self.get_mappings()[index]]
658

    
659

    
660
class memBank(chirp_common.Bank):
661
    """A bank model for kenwood"""
662
    # Integral index of the bank (not to be confused with per-memory
663
    # bank indexes
664
    index = 0
665

    
666

    
667
class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
668
    """Kenwood Serie 60G Radios base class"""
669
    VENDOR = "Kenwood"
670
    BAUD_RATE = 9600
671
    _memsize = MEM_SIZE
672
    NAME_LENGTH = 8
673
    _range = [136000000, 162000000]
674
    _upper = 128
675
    _chs_progs = 0
676
    _num_banks = 128
677
    _bclass = memBank
678
    _kind = ""
679
    VARIANT = ""
680
    MODEL = ""
681

    
682
    @classmethod
683
    def get_prompts(cls):
684
        rp = chirp_common.RadioPrompts()
685
        rp.experimental = \
686
            ('This driver is experimental and for personal use only.'
687
             'It has a limited set of features, but the most used.'
688
             ''
689
             'The most notorius missing features are this:'
690
             '=> PTT ID, in fact it is disabled if detected'
691
             '=> Priority / Home channel'
692
             '=> Bank names'
693
             '=> Others'
694
             ''
695
             'If you need one of this, get your official software to do it'
696
             'and raise and issue on the chirp site about it and maybe'
697
             'it will be implemented in the future.'
698
             )
699
        rp.pre_download = _(dedent("""\
700
            Follow this instructions to download your info:
701
            1 - Turn off your radio
702
            2 - Connect your interface cable
703
            3 - Turn on your radio (unblock it if password protected)
704
            4 - Do the download of your radio data
705
            """))
706
        rp.pre_upload = _(dedent("""\
707
            Follow this instructions to download your info:
708
            1 - Turn off your radio
709
            2 - Connect your interface cable
710
            3 - Turn on your radio (unblock it if password protected)
711
            4 - Do the download of your radio data
712
            """))
713
        return rp
714

    
715
    def get_features(self):
716
        """Return information about this radio's features"""
717
        rf = chirp_common.RadioFeatures()
718
        rf.has_settings = True
719
        rf.has_bank = True
720
        rf.has_tuning_step = False
721
        rf.has_name = True
722
        rf.has_offset = True
723
        rf.has_mode = True
724
        rf.has_dtcs = True
725
        rf.has_rx_dtcs = True
726
        rf.has_dtcs_polarity = True
727
        rf.has_ctone = True
728
        rf.has_cross = True
729
        rf.valid_modes = MODES
730
        rf.valid_duplexes = ["", "-", "+", "off"]
731
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
732
        rf.valid_cross_modes = [
733
            "Tone->Tone",
734
            "DTCS->",
735
            "->DTCS",
736
            "Tone->DTCS",
737
            "DTCS->Tone",
738
            "->Tone",
739
            "DTCS->DTCS"]
740
        rf.valid_power_levels = POWER_LEVELS
741
        rf.valid_characters = VALID_CHARS
742
        rf.valid_skips = SKIP_VALUES
743
        rf.valid_dtcs_codes = DTCS_CODES
744
        rf.valid_bands = [self._range]
745
        rf.valid_name_length = 8
746
        rf.memory_bounds = (1, self._upper)
747
        return rf
748

    
749
    def _fill(self, offset, data):
750
        """Fill an specified area of the memmap with the passed data"""
751
        for addr in range(0, len(data)):
752
            self._mmap[offset + addr] = data[addr]
753

    
754
    def _prep_data(self):
755
        """Prepare the areas in the memmap to do a consistend write
756
        it has to make an update on the x300 area with banks and channel
757
        info; other in the x1000 with banks and channel counts
758
        and a last one in x7000 with flog data"""
759
        rchs = 0
760
        data = dict()
761

    
762
        # sorting the data
763
        for ch in range(0, self._upper):
764
            mem = self._memobj.memory[ch]
765
            bnumb = int(mem.bnumb)
766
            bank = int(mem.bank)
767
            if bnumb != 255 and (bank != 255 and bank != 0):
768
                try:
769
                    data[bank].append(ch)
770
                except:
771
                    data[bank] = list()
772
                    data[bank].append(ch)
773
                data[bank].sort()
774
                # counting the real channels
775
                rchs = rchs + 1
776

    
777
        # updating the channel/bank count
778
        self._memobj.settings.channels = rchs
779
        self._chs_progs = rchs
780
        self._memobj.settings.banks = len(data)
781

    
782
        # building the data for the memmap
783
        fdata = ""
784

    
785
        for k, v in data.iteritems():
786
            # posible bad data
787
            if k == 0:
788
                k = 1
789
                raise errors.InvalidValueError(
790
                    "Invalid bank value '%k', bad data in the image? \
791
                    Triying to fix this, review your bank data!" % k)
792
            c = 1
793
            for i in v:
794
                fdata += chr(k) + chr(c) + chr(k - 1) + chr(i)
795
                c = c + 1
796

    
797
        # fill to match a full 256 bytes block
798
        fdata += (len(fdata) % 256) * "\xFF"
799

    
800
        # updating the data in the memmap [x300]
801
        self._fill(0x300, fdata)
802

    
803
        # update the info in x1000; it has 2 bytes with
804
        # x00 = bank , x01 = bank's channel count
805
        # the rest of the 14 bytes are \xff
806
        bdata = ""
807
        for i in range(1, len(data) + 1):
808
            line = chr(i) + chr(len(data[i]))
809
            line += "\xff" * 14
810
            bdata += line
811

    
812
        # fill to match a full 256 bytes block
813
        bdata += (256 - (len(bdata)) % 256) * "\xFF"
814

    
815
        # fill to match the whole area
816
        bdata += (16 - len(bdata) / 256) * EMPTY_BLOCK
817

    
818
        # updating the data in the memmap [x1000]
819
        self._fill(0x1000, bdata)
820

    
821
        # DTMF id for each channel, 5 bytes lbcd at x7000
822
        # ############## TODO ###################
823
        fldata = "\x00\xf0\xff\xff\xff" * self._chs_progs + \
824
            "\xff" * (5 * (self._upper - self._chs_progs))
825

    
826
        # write it
827
        # updating the data in the memmap [x7000]
828
        self._fill(0x7000, fldata)
829

    
830
    def _set_variant(self):
831
        """Select and set the correct variables for the class acording
832
        to the correct variant of the radio"""
833
        rid = self._mmap[0xA7:0xAE]
834

    
835
        # indentify the radio variant and set the enviroment to it's values
836
        try:
837
            self._upper, low, high, self._kind = self.VARIANTS[rid]
838
            self._range = [low * 1000000, high * 1000000]
839

    
840
            # setting the bank data in the features, 8 & 16 CH dont have banks
841
            if self._upper < 32:
842
                rf = chirp_common.RadioFeatures()
843
                rf.has_bank = False
844

    
845
            # put the VARIANT in the class, clean the model / CHs / Type
846
            # in the same layout as the KPG program
847
            self._VARIANT = self.MODEL + " [" + str(self._upper) + "CH]: "
848
            self._VARIANT += self._kind + ", " + str(self._range[0]/1000000) + "-"
849
            self._VARIANT += str(self._range[1]/1000000) + " Mhz"
850

    
851
        except KeyError:
852
            LOG.debug("Wrong Kenwood radio, ID or unknown variant")
853
            LOG.debug(util.hexprint(rid))
854
            raise errors.RadioError(
855
                "Wrong Kenwood radio, ID or unknown variant, see LOG output.")
856
            return False
857

    
858
    def sync_in(self):
859
        """Do a download of the radio eeprom"""
860
        self._mmap = do_download(self)
861
        self.process_mmap()
862

    
863
    def sync_out(self):
864
        """Do an upload to the radio eeprom"""
865

    
866
        # chirp signature on the eprom ;-)
867
        sign = "Chirp"
868
        self._fill(0xbb, sign)
869

    
870
        try:
871
            self._prep_data()
872
            do_upload(self)
873
        except errors.RadioError:
874
            raise
875
        except Exception, e:
876
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
877

    
878
    def process_mmap(self):
879
        """Process the memory object"""
880
        # how many channels are programed
881
        self._chs_progs = ord(self._mmap[15])
882
        # load the memobj
883
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
884
        # to ser the vars on the class to the correct ones
885
        self._set_variant()
886

    
887
    def get_raw_memory(self, number):
888
        """Return a raw representation of the memory object, which
889
        is very helpful for development"""
890
        return repr(self._memobj.memory[number])
891

    
892
    def _decode_tone(self, val):
893
        """Parse the tone data to decode from mem, it returns:
894
        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
895
        val = int(val)
896
        if val == 65535:
897
            return '', None, None
898
        elif val >= 0x2800:
899
            code = int("%03o" % (val & 0x07FF))
900
            pol = (val & 0x8000) and "R" or "N"
901
            return 'DTCS', code, pol
902
        else:
903
            a = val / 10.0
904
            return 'Tone', a, None
905

    
906
    def _encode_tone(self, memval, mode, value, pol):
907
        """Parse the tone data to encode from UI to mem"""
908
        if mode == '':
909
            memval.set_raw("\xff\xff")
910
        elif mode == 'Tone':
911
            memval.set_value(int(value * 10))
912
        elif mode == 'DTCS':
913
            val = int("%i" % value, 8) + 0x2800
914
            if pol == "R":
915
                val += 0xA000
916
            memval.set_value(val)
917
        else:
918
            raise Exception("Internal error: invalid mode `%s'" % mode)
919

    
920
    def _get_scan(self, chan):
921
        """Get the channel scan status from the 16 bytes array on the eeprom
922
        then from the bits on the byte, return '' or 'S' as needed"""
923
        result = "S"
924
        byte = int(chan/8)
925
        bit = chan % 8
926
        res = self._memobj.settings.add[byte] & (pow(2, bit))
927
        if res > 0:
928
            result = ""
929

    
930
        return result
931

    
932
    def _set_scan(self, chan, value):
933
        """Set the channel scan status from UI to the mem_map"""
934
        byte = int(chan/8)
935
        bit = chan % 8
936

    
937
        # get the actual value to see if I need to change anything
938
        actual = self._get_scan(chan)
939
        if actual != value:
940
            # I have to flip the value
941
            rbyte = self._memobj.settings.add[byte]
942
            rbyte = rbyte ^ pow(2, bit)
943
            self._memobj.settings.add[byte] = rbyte
944

    
945
    def get_memory(self, number):
946
        # Get a low-level memory object mapped to the image
947
        _mem = self._memobj.memory[number - 1]
948

    
949
        # Create a high-level memory object to return to the UI
950
        mem = chirp_common.Memory()
951

    
952
        # Memory number
953
        mem.number = number
954

    
955
        # this radio has a setting about the amount of real chans of the 128
956
        # olso in the channel has xff on the Rx freq it's empty
957
        if (number > (self._chs_progs + 1)) or (_mem.get_raw()[0] == "\xFF"):
958
            mem.empty = True
959
            # but is not enough, you have to crear the memory in the mmap
960
            # to get it ready for the sync_out process
961
            _mem.set_raw("\xFF" * 48)
962
            return mem
963

    
964
        # Freq and offset
965
        mem.freq = int(_mem.rxfreq) * 10
966
        # tx freq can be blank
967
        if _mem.get_raw()[16] == "\xFF":
968
            # TX freq not set
969
            mem.offset = 0
970
            mem.duplex = "off"
971
        else:
972
            # TX feq set
973
            offset = (int(_mem.txfreq) * 10) - mem.freq
974
            if offset < 0:
975
                mem.offset = abs(offset)
976
                mem.duplex = "-"
977
            elif offset > 0:
978
                mem.offset = offset
979
                mem.duplex = "+"
980
            else:
981
                mem.offset = 0
982

    
983
        # name TAG of the channel
984
        mem.name = str(_mem.name).rstrip()
985

    
986
        # power
987
        mem.power = POWER_LEVELS[_mem.power]
988

    
989
        # wide/marrow
990
        mem.mode = MODES[_mem.wide]
991

    
992
        # skip
993
        mem.skip = self._get_scan(number - 1)
994

    
995
        # tone data
996
        rxtone = txtone = None
997
        txtone = self._decode_tone(_mem.tx_tone)
998
        rxtone = self._decode_tone(_mem.rx_tone)
999
        chirp_common.split_tone_decode(mem, txtone, rxtone)
1000

    
1001
        # Extra
1002
        # bank and number in the channel
1003
        mem.extra = RadioSettingGroup("extra", "Extra")
1004

    
1005
        # validate bank
1006
        b = int(_mem.bank)
1007
        if b > 127 or b == 0:
1008
            _mem.bank = b = 1
1009

    
1010
        bank = RadioSetting("bank", "Bank it belongs",
1011
                            RadioSettingValueInteger(1, 128, b))
1012
        mem.extra.append(bank)
1013

    
1014
        # validate bnumb
1015
        if int(_mem.bnumb) > 127:
1016
            _mem.bank = mem.number
1017

    
1018
        bnumb = RadioSetting("bnumb", "Ch number in the bank",
1019
                             RadioSettingValueInteger(0, 127, _mem.bnumb))
1020
        mem.extra.append(bnumb)
1021

    
1022
        bs = RadioSetting("beat_shift", "Beat shift",
1023
                          RadioSettingValueBoolean(
1024
                              not bool(_mem.beat_shift)))
1025
        mem.extra.append(bs)
1026

    
1027
        cp = RadioSetting("compander", "Compander",
1028
                          RadioSettingValueBoolean(
1029
                              not bool(_mem.compander)))
1030
        mem.extra.append(cp)
1031

    
1032
        bl = RadioSetting("busy_lock", "Busy Channel lock",
1033
                          RadioSettingValueBoolean(
1034
                              not bool(_mem.busy_lock)))
1035
        mem.extra.append(bl)
1036

    
1037
        return mem
1038

    
1039
    def set_memory(self, mem):
1040
        """Set the memory data in the eeprom img from the UI
1041
        not ready yet, so it will return as is"""
1042

    
1043
        # get the eprom representation of this channel
1044
        _mem = self._memobj.memory[mem.number - 1]
1045

    
1046
        # if empty memmory
1047
        if mem.empty:
1048
            _mem.set_raw("\xFF" * 48)
1049
            return
1050

    
1051
        # frequency
1052
        _mem.rxfreq = mem.freq / 10
1053

    
1054
        # this are a mistery yet, but so falr there is no impact
1055
        # whit this default values for new channels
1056
        if int(_mem.rx_unkw) == 0xff:
1057
            _mem.rx_unkw = 0x35
1058
            _mem.tx_unkw = 0x32
1059

    
1060
        # duplex
1061
        if mem.duplex == "+":
1062
            _mem.txfreq = (mem.freq + mem.offset) / 10
1063
        elif mem.duplex == "-":
1064
            _mem.txfreq = (mem.freq - mem.offset) / 10
1065
        else:
1066
            _mem.txfreq = mem.freq / 10
1067

    
1068
        # tone data
1069
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
1070
            chirp_common.split_tone_encode(mem)
1071
        self._encode_tone(_mem.tx_tone, txmode, txtone, txpol)
1072
        self._encode_tone(_mem.rx_tone, rxmode, rxtone, rxpol)
1073

    
1074
        # name TAG of the channel
1075
        _namelength = self.get_features().valid_name_length
1076
        for i in range(_namelength):
1077
            try:
1078
                _mem.name[i] = mem.name[i]
1079
            except IndexError:
1080
                _mem.name[i] = "\x20"
1081

    
1082
        # power
1083
        # default power is low
1084
        if mem.power is None:
1085
            mem.power = POWER_LEVELS[0]
1086

    
1087
        _mem.power = POWER_LEVELS.index(mem.power)
1088

    
1089
        # wide/marrow
1090
        _mem.wide = MODES.index(mem.mode)
1091

    
1092
        # scan add property
1093
        self._set_scan(mem.number - 1, mem.skip)
1094

    
1095
        # bank and number in the channel
1096
        if int(_mem.bnumb) == 0xff:
1097
            _mem.bnumb = mem.number - 1
1098
            _mem.bank = 1
1099

    
1100
        # extra settings
1101
        for setting in mem.extra:
1102
            if setting != "bank" or setting != "bnumb":
1103
                setattr(_mem, setting.get_name(), not bool(setting.value))
1104

    
1105
        # all data get sync after channel mod
1106
        #self._prep_data()
1107

    
1108
        return mem
1109

    
1110
    @classmethod
1111
    def match_model(cls, filedata, filename):
1112
        match_size = False
1113
        match_model = False
1114

    
1115
        # testing the file data size
1116
        if len(filedata) == MEM_SIZE:
1117
            match_size = True
1118

    
1119
        # testing the firmware model fingerprint
1120
        match_model = model_match(cls, filedata)
1121

    
1122
        if match_size and match_model:
1123
            return True
1124
        else:
1125
            return False
1126

    
1127
    def get_settings(self):
1128
        """Translate the bit in the mem_struct into settings in the UI"""
1129
        sett = self._memobj.settings
1130
        mess = self._memobj.message
1131
        keys = self._memobj.keys
1132
        idm = self._memobj.id
1133
        passwd = self._memobj.passwords
1134

    
1135
        # basic features of the radio
1136
        basic = RadioSettingGroup("basic", "Basic Settings")
1137
        # dealer settings
1138
        dealer = RadioSettingGroup("dealer", "Dealer Settings")
1139
        # buttons
1140
        fkeys = RadioSettingGroup("keys", "Front keys config")
1141

    
1142
        # TODO / PLANED
1143
        # adjust feqs
1144
        #freqs = RadioSettingGroup("freqs", "Adjust Frequencies")
1145

    
1146
        top = RadioSettings(basic, dealer, fkeys)
1147

    
1148
        # Basic
1149
        tot = RadioSetting("settings.tot", "Time Out Timer (TOT)",
1150
                           RadioSettingValueList(TOT, TOT[
1151
                           TOT.index(str(int(sett.tot)))]))
1152
        basic.append(tot)
1153

    
1154
        totalert = RadioSetting("settings.tot_alert", "TOT pre alert",
1155
                                RadioSettingValueList(TOT_PRE,
1156
                                TOT_PRE[int(sett.tot_alert)]))
1157
        basic.append(totalert)
1158

    
1159
        totrekey = RadioSetting("settings.tot_rekey", "TOT re-key time",
1160
                                RadioSettingValueList(TOT_REKEY,
1161
                                TOT_REKEY[int(sett.tot_rekey)]))
1162
        basic.append(totrekey)
1163

    
1164
        totreset = RadioSetting("settings.tot_reset", "TOT reset time",
1165
                                RadioSettingValueList(TOT_RESET,
1166
                                TOT_RESET[int(sett.tot_reset)]))
1167
        basic.append(totreset)
1168

    
1169
        # this feature is for mobile only
1170
        if self.TYPE[0] == "M":
1171
            minvol = RadioSetting("settings.min_vol", "Minimum volume",
1172
                                  RadioSettingValueList(VOL,
1173
                                  VOL[int(sett.min_vol)]))
1174
            basic.append(minvol)
1175

    
1176
            tv = int(sett.tone_vol)
1177
            if tv == 255:
1178
                tv = 32
1179
            tvol = RadioSetting("settings.tone_vol", "Minimum tone volume",
1180
                                RadioSettingValueList(TVOL, TVOL[tv]))
1181
            basic.append(tvol)
1182

    
1183
        sql = RadioSetting("settings.sql_level", "SQL Ref Level",
1184
                           RadioSettingValueList(
1185
                           SQL, SQL[int(sett.sql_level)]))
1186
        basic.append(sql)
1187

    
1188
        #c2t = RadioSetting("settings.c2t", "Clear to Transpond",
1189
                           #RadioSettingValueBoolean(not sett.c2t))
1190
        #basic.append(c2t)
1191

    
1192
        ptone = RadioSetting("settings.poweron_tone", "Power On tone",
1193
                             RadioSettingValueBoolean(sett.poweron_tone))
1194
        basic.append(ptone)
1195

    
1196
        ctone = RadioSetting("settings.control_tone", "Control (key) tone",
1197
                             RadioSettingValueBoolean(sett.control_tone))
1198
        basic.append(ctone)
1199

    
1200
        wtone = RadioSetting("settings.warn_tone", "Warning tone",
1201
                             RadioSettingValueBoolean(sett.warn_tone))
1202
        basic.append(wtone)
1203

    
1204
        # Save Battery only for portables?
1205
        if self.TYPE[0] == "P":
1206
            bs = int(sett.battery_save) == 0x32 and True or False
1207
            bsave = RadioSetting("settings.battery_save", "Battery Saver",
1208
                                 RadioSettingValueBoolean(bs))
1209
            basic.append(bsave)
1210

    
1211
        ponm = str(sett.poweronmesg).strip("\xff")
1212
        pom = RadioSetting("settings.poweronmesg", "Power on message",
1213
                           RadioSettingValueString(0, 8, ponm, False))
1214
        basic.append(pom)
1215

    
1216
        # dealer
1217
        mstr = ""
1218
        valid_chars = [32, 44, 45, 47, 58, 91, 93] + range(48, 58) + \
1219
            range(65, 91) + range(97, 123)
1220

    
1221
        for i in range(0, len(self._VARIANT)):
1222
            if ord(self._VARIANT[i]) in valid_chars:
1223
                mstr += self._VARIANT[i]
1224

    
1225
        val = RadioSettingValueString(0, 35, mstr)
1226
        val.set_mutable(False)
1227
        mod = RadioSetting("not.mod", "Radio Version", val)
1228
        dealer.append(mod)
1229

    
1230
        sn = str(idm.serial).strip(" \xff")
1231
        val = RadioSettingValueString(0, 8, sn)
1232
        val.set_mutable(False)
1233
        serial = RadioSetting("not.serial", "Serial number", val)
1234
        dealer.append(serial)
1235

    
1236
        svp = str(sett.lastsoftversion).strip(" \xff")
1237
        val = RadioSettingValueString(0, 5, svp)
1238
        val.set_mutable(False)
1239
        sver = RadioSetting("not.softver", "Software Version", val)
1240
        dealer.append(sver)
1241

    
1242
        l1 = str(mess.line1).strip(" \xff")
1243
        line1 = RadioSetting("message.line1", "Comment 1",
1244
                           RadioSettingValueString(0, 32, l1))
1245
        dealer.append(line1)
1246

    
1247
        l2 = str(mess.line2).strip(" \xff")
1248
        line2 = RadioSetting("message.line2", "Comment 2",
1249
                             RadioSettingValueString(0, 32, l2))
1250
        dealer.append(line2)
1251

    
1252
        sprog = RadioSetting("settings.self_prog", "Self program",
1253
                             RadioSettingValueBoolean(sett.self_prog))
1254
        dealer.append(sprog)
1255

    
1256
        clone = RadioSetting("settings.clone", "Allow clone",
1257
                             RadioSettingValueBoolean(sett.clone))
1258
        dealer.append(clone)
1259

    
1260
        panel = RadioSetting("settings.panel_test", "Panel Test",
1261
                             RadioSettingValueBoolean(sett.panel_test))
1262
        dealer.append(panel)
1263

    
1264
        fmw = RadioSetting("settings.firmware_prog", "Firmware program",
1265
                           RadioSettingValueBoolean(sett.firmware_prog))
1266
        dealer.append(fmw)
1267

    
1268
        # front keys
1269
        # The Mobile only parameters are wraped here
1270
        if self.TYPE[0] == "M":
1271
            vu = RadioSetting("keys.kVOL_UP", "VOL UP",
1272
                              RadioSettingValueList(KEYS.values(),
1273
                              KEYS.values()[KEYS.keys().index(
1274
                                  int(keys.kVOL_UP))]))
1275
            fkeys.append(vu)
1276

    
1277
            vd = RadioSetting("keys.kVOL_DOWN", "VOL DOWN",
1278
                              RadioSettingValueList(KEYS.values(),
1279
                              KEYS.values()[KEYS.keys().index(
1280
                                  int(keys.kVOL_DOWN))]))
1281
            fkeys.append(vd)
1282

    
1283
            chu = RadioSetting("keys.kCH_UP", "CH UP",
1284
                               RadioSettingValueList(KEYS.values(),
1285
                               KEYS.values()[KEYS.keys().index(
1286
                                   int(keys.kCH_UP))]))
1287
            fkeys.append(chu)
1288

    
1289
            chd = RadioSetting("keys.kCH_DOWN", "CH DOWN",
1290
                               RadioSettingValueList(KEYS.values(),
1291
                               KEYS.values()[KEYS.keys().index(
1292
                                   int(keys.kCH_DOWN))]))
1293
            fkeys.append(chd)
1294

    
1295
            foot = RadioSetting("keys.kFOOT", "Foot switch",
1296
                               RadioSettingValueList(KEYS.values(),
1297
                               KEYS.values()[KEYS.keys().index(
1298
                                   int(keys.kCH_DOWN))]))
1299
            fkeys.append(foot)
1300

    
1301
        # this is the common buttons for all
1302

    
1303
        # 260G model don't have the front keys
1304
        if not "P2600" in self.TYPE:
1305
            scn_name = "SCN"
1306
            if self.TYPE[0] == "P":
1307
                scn_name = "Open Circle"
1308

    
1309
            scn = RadioSetting("keys.kSCN", scn_name,
1310
                               RadioSettingValueList(KEYS.values(),
1311
                               KEYS.values()[KEYS.keys().index(
1312
                                   int(keys.kSCN))]))
1313
            fkeys.append(scn)
1314

    
1315
            a_name = "A"
1316
            if self.TYPE[0] == "P":
1317
                a_name = "Closed circle"
1318

    
1319
            a = RadioSetting("keys.kA", a_name,
1320
                             RadioSettingValueList(KEYS.values(),
1321
                             KEYS.values()[KEYS.keys().index(
1322
                                 int(keys.kA))]))
1323
            fkeys.append(a)
1324

    
1325
            da_name = "D/A"
1326
            if self.TYPE[0] == "P":
1327
                da_name = "< key"
1328

    
1329
            da = RadioSetting("keys.kDA", da_name,
1330
                              RadioSettingValueList(KEYS.values(),
1331
                              KEYS.values()[KEYS.keys().index(
1332
                                  int(keys.kDA))]))
1333
            fkeys.append(da)
1334

    
1335
            gu_name = "Triangle up"
1336
            if self.TYPE[0] == "P":
1337
                gu_name = "Side 1"
1338

    
1339
            gu = RadioSetting("keys.kGROUP_UP", gu_name,
1340
                              RadioSettingValueList(KEYS.values(),
1341
                              KEYS.values()[KEYS.keys().index(
1342
                                  int(keys.kGROUP_UP))]))
1343
            fkeys.append(gu)
1344

    
1345
        # Side keys on portables
1346
        gd_name = "Triangle Down"
1347
        if self.TYPE[0] == "P":
1348
            gd_name = "> key"
1349

    
1350
        gd = RadioSetting("keys.kGROUP_DOWN", gd_name,
1351
                          RadioSettingValueList(KEYS.values(),
1352
                          KEYS.values()[KEYS.keys().index(
1353
                              int(keys.kGROUP_DOWN))]))
1354
        fkeys.append(gd)
1355

    
1356
        mon_name = "MON"
1357
        if self.TYPE[0] == "P":
1358
            mon_name = "Side 2"
1359

    
1360
        mon = RadioSetting("keys.kMON", mon_name,
1361
                           RadioSettingValueList(KEYS.values(),
1362
                           KEYS.values()[KEYS.keys().index(
1363
                               int(keys.kMON))]))
1364
        fkeys.append(mon)
1365

    
1366
        return top
1367

    
1368
    def set_settings(self, settings):
1369
        """Translate the settings in the UI into bit in the mem_struct
1370
        I don't understand well the method used in many drivers
1371
        so, I used mine, ugly but works ok"""
1372

    
1373
        mobj = self._memobj
1374

    
1375
        for element in settings:
1376
            if not isinstance(element, RadioSetting):
1377
                self.set_settings(element)
1378
                continue
1379

    
1380
            # Let's roll the ball
1381
            if "." in element.get_name():
1382
                inter, setting = element.get_name().split(".")
1383
                # you must ignore the settings with "not"
1384
                # this are READ ONLY attributes
1385
                if inter == "not":
1386
                    continue
1387

    
1388
                obj = getattr(mobj, inter)
1389
                value = element.value
1390

    
1391
                # integers case + special case
1392
                if setting in ["tot", "tot_alert", "min_vol", "tone_vol",
1393
                               "sql_level", "tot_rekey", "tot_reset"]:
1394
                    # catching the "off" values as zero
1395
                    try:
1396
                        value = int(value)
1397
                    except:
1398
                        value = 0
1399

    
1400
                    # tot case step 15
1401
                    if setting == "tot":
1402
                        value = value * 15
1403
                        # off is special
1404
                        if value == 0:
1405
                            value = 0x4b0
1406

    
1407
                    # Caso tone_vol
1408
                    if setting == "tone_vol":
1409
                        # off is special
1410
                        if value == 32:
1411
                            value = 0xff
1412

    
1413
                # Bool types + inverted
1414
                if setting in ["c2t", "poweron_tone", "control_tone",
1415
                               "warn_tone", "battery_save", "self_prog",
1416
                               "clone", "panel_test"]:
1417
                    value = bool(value)
1418

    
1419
                    # this cases are inverted
1420
                    if setting == "c2t":
1421
                        value = not value
1422

    
1423
                    # case battery save is special
1424
                    if setting == "battery_save":
1425
                        if bool(value) is True:
1426
                            value = 0x32
1427
                        else:
1428
                            value = 0xff
1429

    
1430
                # String cases
1431
                if setting in ["poweronmesg", "line1", "line2"]:
1432
                    # some vars
1433
                    value = str(value)
1434
                    just = 8
1435
                    # lines with 32
1436
                    if "line" in setting:
1437
                        just = 32
1438

    
1439
                    # empty case
1440
                    if len(value) == 0:
1441
                        value = "\xff" * just
1442
                    else:
1443
                        value = value.ljust(just)
1444

    
1445
                ## password with special case
1446
                #if setting == "radio" or setting == "data":
1447
                    #pass
1448

    
1449
                # case keys, with special config
1450
                if inter == "keys":
1451
                    value = KEYS.keys()[KEYS.values().index(str(value))]
1452

    
1453
            # Apply al configs done
1454
            setattr(obj, setting, value)
1455

    
1456
    def get_bank_model(self):
1457
        """Pass the bank model to the UI part"""
1458
        rf = self.get_features()
1459
        if rf.has_bank is True:
1460
            return Kenwood60GBankModel(self)
1461
        else:
1462
            return None
1463

    
1464
    def _get_bank(self, loc):
1465
        """Get the bank data for a specific channel"""
1466
        mem = self._memobj.memory[loc - 1]
1467
        bank = int(mem.bank) - 1
1468

    
1469
        if bank > self._num_banks or bank < 1:
1470
            # all channels must belong to a bank, even with just 1 bank
1471
            return 0
1472
        else:
1473
            return bank
1474

    
1475
    def _set_bank(self, loc, bank):
1476
        """Set the bank data for a specific channel"""
1477
        try:
1478
            b = int(bank)
1479
            if b > 127:
1480
                b = 0
1481
            mem = self._memobj.memory[loc - 1]
1482
            mem.bank = b + 1
1483
        except:
1484
            msg = "You can't have a channel without a bank, click another bank"
1485
            raise errors.InvalidDataError(msg)
1486

    
1487

    
1488
# This kenwwood family is known as "60-G Serie"
1489
# all this radios ending in G are compatible:
1490
#
1491
# Portables VHF TK-260/270/272/278
1492
# Portables UHF TK-360/370/372/378/388
1493
#
1494
# Mobiles VHF TK-760/762/768
1495
# Mobiles VHF TK-860/862/868
1496
#
1497
# Just dealing with VHF models at moment,
1498
# this are the radios I can get in hand
1499

    
1500
# WARNING !!!! Radios With Password in the data section <=###############
1501
#
1502
# when a radio has a data password (aka to program it) the last byte (#8)
1503
# in the id code change from \xf1 to \xb1; so we remove this last byte
1504
# from the identification procedures and variants.
1505
#
1506
# this effectively render the data password USELESS even if set.
1507
# this can change if user request it with high priority
1508

    
1509
@directory.register
1510
class TK868G_Radios(Kenwood_Serie_60G):
1511
    """Kenwood TK-868G Radio M/C"""
1512
    MODEL = "TK-868G"
1513
    TYPE = "M8680"
1514
    VARIANTS = {
1515
        "M8680\x18\xff":    (8, 400, 490, "M"),
1516
        "M8680;\xff":       (128, 350, 390, "C1"),
1517
        "M86808\xff":       (128, 400, 430, "C2"),
1518
        "M86806\xff":       (128, 450, 490, "C3"),
1519
        }
1520

    
1521

    
1522
@directory.register
1523
class TK862G_Radios(Kenwood_Serie_60G):
1524
    """Kenwood TK-862G Radio K/E/(N)E"""
1525
    MODEL = "TK-862G"
1526
    TYPE = "M8620"
1527
    VARIANTS = {
1528
        "M8620\x06\xff":    (8, 450, 490, "K"),
1529
        "M8620\x07\xff":    (8, 485, 512, "K2"),
1530
        "M8620&\xff":       (8, 440, 470, "E"),
1531
        "M8620V\xff":       (8, 440, 470, "(N)E"),
1532
        }
1533

    
1534

    
1535
@directory.register
1536
class TK860G_Radios(Kenwood_Serie_60G):
1537
    """Kenwood TK-860G Radio K"""
1538
    MODEL = "TK-860G"
1539
    TYPE = "M8600"
1540
    VARIANTS = {
1541
        "M8600\x08\xff":    (128, 400, 430, "K"),
1542
        "M8600\x06\xff":    (128, 450, 490, "K1"),
1543
        "M8600\x07\xff":    (128, 485, 512, "K2"),
1544
        "M8600\x18\xff":    (128, 400, 430, "M"),
1545
        "M8600\x16\xff":    (128, 450, 490, "M1"),
1546
        "M8600\x17\xff":    (128, 485, 520, "M2"),
1547
        }
1548

    
1549

    
1550
@directory.register
1551
class TK768G_Radios(Kenwood_Serie_60G):
1552
    """Kenwood TK-768G Radios [M/C]"""
1553
    MODEL = "TK-768G"
1554
    TYPE = "M7680"
1555
    # Note that 8 CH don't have banks
1556
    VARIANTS = {
1557
        "M7680\x15\xff": (8, 136, 162, "M2"),
1558
        "M7680\x14\xff": (8, 148, 174, "M"),
1559
        "M76805\xff":    (128, 136, 162, "C2"),
1560
        "M76804\xff":    (128, 148, 174, "C"),
1561
        }
1562

    
1563

    
1564
@directory.register
1565
class TK762G_Radios(Kenwood_Serie_60G):
1566
    """Kenwood TK-762G Radios [K/E/NE]"""
1567
    MODEL = "TK-762G"
1568
    TYPE = "M7620"
1569
    # Note that 8 CH don't have banks
1570
    VARIANTS = {
1571
        "M7620\x05\xff": (8, 136, 162, "K2"),
1572
        "M7620\x04\xff": (8, 148, 172, "K"),
1573
        "M7620$\xff":    (8, 148, 172, "E"),
1574
        "M7620T\xff":    (8, 148, 172, "NE"),
1575
        }
1576

    
1577

    
1578
@directory.register
1579
class TK760G_Radios(Kenwood_Serie_60G):
1580
    """Kenwood TK-760G Radios [K/M/(N)E]"""
1581
    MODEL = "TK-760G"
1582
    TYPE = "M7600"
1583
    VARIANTS = {
1584
        "M7600\x05\xff": (128, 136, 162, "K2"),
1585
        "M7600\x04\xff": (128, 148, 174, "K"),
1586
        "M7600\x14\xff": (128, 146, 174, "M"),
1587
        "M7600T\xff":    (128, 146, 174, "NE")
1588
        }
1589

    
1590

    
1591
@directory.register
1592
class TK278G_Radios(Kenwood_Serie_60G):
1593
    """Kenwood TK-278G Radio C/C1/M/M1"""
1594
    MODEL = "TK-278G"
1595
    TYPE = "P2780"
1596
    # Note that 16 CH don't have banks
1597
    VARIANTS = {
1598
        "P27805\xff":    (128, 136, 150, "C1"),
1599
        "P27804\xff":    (128, 150, 174, "C"),
1600
        "P2780\x15\xff": (16,  136, 150, "M1"),
1601
        "P2780\x14\xff": (16,  150, 174, "M")
1602
        }
1603

    
1604

    
1605
@directory.register
1606
class TK272G_Radios(Kenwood_Serie_60G):
1607
    """Kenwood TK-272G Radio K/K1"""
1608
    MODEL = "TK-272G"
1609
    TYPE = "P2720"
1610
    VARIANTS = {
1611
        "P2720\x05\xfb": (32, 136, 150, "K1"),
1612
        "P2720\x04\xfb": (32, 150, 174, "K")
1613
        }
1614

    
1615

    
1616
@directory.register
1617
class TK270G_Radios(Kenwood_Serie_60G):
1618
    """Kenwood TK-270G Radio K/K1/M/E/NE/NT"""
1619
    MODEL = "TK-270G"
1620
    TYPE = "P2700"
1621
    VARIANTS = {
1622
        "P2700T\xff":    (128, 146, 174, "NE/NT"),
1623
        "P2700$\xff":    (128, 146, 174, "E"),
1624
        "P2700\x14\xff": (128, 150, 174, "M"),
1625
        "P2700\x05\xff": (128, 136, 150, "K1"),
1626
        "P2700\x04\xff": (128, 150, 174, "K"),
1627
        }
1628

    
1629

    
1630
@directory.register
1631
class TK260G_Radios(Kenwood_Serie_60G):
1632
    """Kenwood TK-260G Radio K/K1/M/E/NE/NT"""
1633
    MODEL = "TK-260G"
1634
    _hasbanks = False
1635
    TYPE = "P2600"
1636
    VARIANTS = {
1637
        "P2600U\xff":    (8, 136, 150, "N1"),
1638
        "P2600T\xff":    (8, 146, 174, "N"),
1639
        "P2600$\xff":    (8, 146, 174, "E"),
1640
        "P2600\x14\xff": (8, 150, 174, "M"),
1641
        "P2600\x05\xff": (8, 136, 150, "K1"),
1642
        "P2600\x04\xff": (8, 150, 174, "K")
1643
        }
(4-4/7)