Project

General

Profile

New Model #2999 » tk760g.py

Release candidate driver, Basic Settings, full bank support but will fail the tests in chirp due to different paradigm. - Pavel Milanes, 12/27/2015 05:43 PM

 
1
# Copyright 2012 Dan Smith <dsmith@danplanet.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
# driver author Pavel Milanes, CO7WT, pavelmc@gmail.com, co7wt@frcuba.co.cu
17

    
18
import logging
19
import struct
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
        raise Exception(mesg)
385

    
386

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

    
394
    # Explicit BAD checksum
395
    if ack == "\x15":
396
        _close_radio(r)
397
        raise errors.RadioError(
398
            "Bad checksum in block %02x write" % addr)
399

    
400
    # everything else
401
    _close_radio(r)
402
    raise errors.RadioError(
403
        "Problem with the ack to block %02x write, ack %03i" %
404
        (addr, int(ack)))
405

    
406

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

    
422
        if rcs != ccs:
423
            _close_radio(radio)
424
            raise errors.RadioError(
425
                "Block Checksum Error! real %02x, calculated %02x" %
426
                (rcs, ccs))
427

    
428
        _handshake(radio, "after checksum")
429
        return data
430

    
431

    
432
def _open_radio(radio):
433
    """Open the radio into program mode and check if it's the correct model"""
434
    radio.pipe.setTimeout(0.25)  # only works in the range 0.2 - 0.3
435
    radio.pipe.setParity("E")
436

    
437
    _raw_send(radio, "PROGRAM")
438
    ack = _raw_recv(radio, 1)
439

    
440
    if ack != ACK_CMD:
441
        # bad response, properly close the radio before exception
442
        _close_radio(radio)
443
        raise errors.RadioError("The radio doesn't accept program mode")
444

    
445
    _raw_send(radio, "\x02")
446
    rid = _raw_recv(radio, 8)
447

    
448
    if not (radio.TYPE in rid):
449
        # bad response, properly close the radio before exception
450
        _close_radio(radio)
451
        # LOG.debug("Incorrect model ID, got %s" % util.hexprint(rid))
452
        raise errors.RadioError(
453
            "Incorrect model ID, got %s, it not contains %s" %
454
            (rid.strip("\xff"), radio.TYPE))
455

    
456
    # DEBUG
457
    LOG.debug("Full ident string is: %s" % util.hexprint(rid))
458
    _handshake(radio)
459

    
460

    
461
def do_download(radio):
462
    """ The download function """
463
    _open_radio(radio)
464

    
465
    # speed up the reading
466
    radio.pipe.setTimeout(0.03)  # only works in the range 0.25 and up
467

    
468
    # UI progress
469
    status = chirp_common.Status()
470
    status.cur = 0
471
    status.max = MEM_SIZE / 256
472
    status.msg = "Cloning from radio..."
473
    radio.status_fn(status)
474
    data = ""
475
    count = 0
476

    
477
    for addr in MEM_BLOCKS:
478
        _send(radio, _make_frame("R", addr))
479
        d = _recv(radio)
480
        # if empty block, it return false
481
        # aka we asume a empty 256 xFF block
482
        if d is False:
483
            d = EMPTY_BLOCK
484

    
485
        data += d
486

    
487
        # UI Update
488
        status.cur = count
489
        status.msg = "Cloning from radio..."
490
        radio.status_fn(status)
491

    
492
        count += 1
493

    
494
    _close_radio(radio)
495
    return memmap.MemoryMap(data)
496

    
497

    
498
def do_upload(radio):
499
    """ The upload function """
500
    _open_radio(radio)
501

    
502
    # Radio need time to write data to eeprom
503
    # 0.55 seconds as per the original software...
504
    radio.pipe.setTimeout(0.55)
505

    
506
    # UI progress
507
    status = chirp_common.Status()
508
    status.cur = 0
509
    status.max = BLOCKS
510
    status.msg = "Cloning to radio..."
511
    radio.status_fn(status)
512

    
513
    count = 0
514
    raddr = 0
515

    
516
    for addr in MEM_BLOCKS:
517
        # this is the data block to write
518
        data = radio.get_mmap()[raddr:raddr+BLOCK_SIZE]
519

    
520
        # The blocks from x59-x5F are NOT programmable
521
        # The blocks from x11-x1F are writed only if not empty
522
        if addr in RO_BLOCKS:
523
            # checking if in the range of optional blocks
524
            if addr >= 0x10 and addr <= 0x1F:
525
                # block is empty ?
526
                if data == EMPTY_BLOCK:
527
                    # no write of this block
528
                    # but we have to continue updating the counters
529
                    count += 1
530
                    raddr = count * 256
531
                    continue
532
            else:
533
                count += 1
534
                raddr = count * 256
535
                continue
536

    
537
        if data == EMPTY_BLOCK:
538
            frame = _make_frame("Z", addr) + "\xFF"
539
        else:
540
            cs = _checksum(data)
541
            frame = _make_frame("W", addr) + data + chr(cs)
542

    
543
        _send(radio, frame)
544
        ack = _raw_recv(radio, 1)
545
        _check_write_ack(radio, ack, addr)
546

    
547
        # UI Update
548
        status.cur = count
549
        status.msg = "Cloning to radio..."
550
        radio.status_fn(status)
551

    
552
        count += 1
553
        raddr = count * 256
554

    
555
    _close_radio(radio)
556

    
557

    
558
def model_match(cls, data):
559
    """Match the opened/downloaded image to the correct version"""
560
    rid = data[0xA7:0xAE]
561
    if (rid in cls.VARIANTS):
562
        # correct model
563
        return True
564
    else:
565
        return False
566

    
567

    
568
class Kenwood60GBankModel(chirp_common.BankModel):
569
    """Testing the bank model on kennwood"""
570

    
571
    def get_num_mappings(self):
572
        return self._radio._num_banks
573

    
574
    def get_mappings(self):
575
        banks = []
576
        for i in range(0, self._radio._num_banks):
577
            bindex = i + 1
578
            bank = self._radio._bclass(self, i, "%03i" % bindex)
579
            bank.index = i
580
            banks.append(bank)
581
        return banks
582

    
583
    def add_memory_to_mapping(self, memory, bank):
584
        self._radio._set_bank(memory.number, bank.index)
585

    
586
    def remove_memory_from_mapping(self, memory, bank):
587
        # in thes case we pass it, because all channels must be assigned to
588
        # at least one channel
589
        pass
590

    
591
    def get_mapping_memories(self, bank):
592
        memories = []
593
        for i in range(0, self._radio._upper):
594
            if self._radio._get_bank(i) == bank.index:
595
                memories.append(self._radio.get_memory(i))
596
        return memories
597

    
598
    def get_memory_mappings(self, memory):
599
        index = self._radio._get_bank(memory.number)
600
        return [self.get_mappings()[index]]
601

    
602

    
603
class memBank(chirp_common.Bank):
604
    """A bank model for kenwood"""
605
    # Integral index of the bank (not to be confused with per-memory
606
    # bank indexes
607
    index = 1
608

    
609

    
610
class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
611
    """Kenwood Serie 60G Radios base class"""
612
    VENDOR = "Kenwood"
613
    BAUD_RATE = 9600
614
    _memsize = MEM_SIZE
615
    NAME_LENGTH = 8
616
    _range = [136000000, 162000000]
617
    _upper = 128
618
    _chs_progs = 0
619
    _num_banks = 128
620
    _bclass = memBank
621

    
622
    @classmethod
623
    def get_prompts(cls):
624
        rp = chirp_common.RadioPrompts()
625
        rp.experimental = \
626
            ('This driver is experimental and for personal use only.'
627
             'It has a limited set of features, but the most used.'
628
             ''
629
             'The most notorius missing features are this:'
630
             '=> PTT ID, in fact it is disabled if detected'
631
             '=> Priority / Home channel'
632
             '=> Bank names'
633
             '=> Others'
634
             ''
635
             'If you need one of this, get your official software to do it'
636
             'and raise and issue on the chirp site about it and maybe'
637
             'it will be implemented in the future.'
638
             )
639
        rp.pre_download = _(dedent("""\
640
            Follow this instructions to download your info:
641
            1 - Turn off your radio
642
            2 - Connect your interface cable
643
            3 - Turn on your radio (unblock it if password protected)
644
            4 - Do the download of your radio data
645
            """))
646
        rp.pre_upload = _(dedent("""\
647
            Follow this instructions to download your info:
648
            1 - Turn off your radio
649
            2 - Connect your interface cable
650
            3 - Turn on your radio (unblock it if password protected)
651
            4 - Do the download of your radio data
652
            """))
653
        return rp
654

    
655
    def get_features(self):
656
        """Return information about this radio's features"""
657
        rf = chirp_common.RadioFeatures()
658
        rf.has_settings = True
659
        rf.has_bank = True
660
        rf.has_tuning_step = False
661
        rf.has_name = True
662
        rf.has_offset = True
663
        rf.has_mode = True
664
        rf.has_dtcs = True
665
        rf.has_rx_dtcs = True
666
        rf.has_dtcs_polarity = True
667
        rf.has_ctone = True
668
        rf.has_cross = True
669
        rf.valid_modes = MODES
670
        rf.valid_duplexes = ["", "-", "+", "off"]
671
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
672
        rf.valid_cross_modes = [
673
            "Tone->Tone",
674
            "DTCS->",
675
            "->DTCS",
676
            "Tone->DTCS",
677
            "DTCS->Tone",
678
            "->Tone",
679
            "DTCS->DTCS"]
680
        rf.valid_power_levels = POWER_LEVELS
681
        rf.valid_characters = VALID_CHARS
682
        rf.valid_skips = SKIP_VALUES
683
        rf.valid_dtcs_codes = DTCS_CODES
684
        rf.valid_bands = [self._range]
685
        rf.valid_name_length = 8
686
        rf.memory_bounds = (1, self._upper)
687
        return rf
688

    
689
    def _fill(self, offset, data):
690
        """Fill an specified area of the memmap with the passed data"""
691
        for addr in range(0, len(data)):
692
            self._mmap[offset + addr] = data[addr]
693

    
694
    def _prep_data(self):
695
        """Prepare the areas in the memmap to do a consistend write
696
        it has to make an update on the x300 area with banks and channel
697
        info; other in the x1000 with banks and channel counts
698
        and a last one in x7000 with flog data"""
699
        rchs = 0
700
        data = dict()
701

    
702
        # sorting the data
703
        for ch in range(0, self._upper):
704
            mem = self._memobj.memory[ch]
705
            bnumb = int(mem.bnumb)
706
            bank = int(mem.bank)
707
            if bnumb != 255 and (bank != 255 and bank != 0):
708
                try:
709
                    data[bank].append(ch)
710
                except:
711
                    data[bank] = list()
712
                    data[bank].append(ch)
713
                data[bank].sort()
714
                # counting the real channels
715
                rchs = rchs + 1
716

    
717
        # updating the channel/bank count
718
        self._memobj.settings.channels = rchs
719
        self._chs_progs = rchs
720
        self._memobj.settings.banks = len(data)
721

    
722
        # building the data for the memmap
723
        fdata = ""
724

    
725
        for k, v in data.iteritems():
726
            # posible bad data
727
            if k == 0:
728
                k = 1
729
                raise errors.InvalidValueError(
730
                    "Invalid bank value '%k', bad data in the image? \
731
                    Triying to fix this, review your bank data!" % k)
732
            c = 1
733
            for i in v:
734
                fdata += chr(k) + chr(c) + chr(k - 1) + chr(i)
735
                c = c + 1
736

    
737
        # fill to match a full 256 bytes block
738
        fdata += (len(fdata) % 256) * "\xFF"
739

    
740
        # updating the data in the memmap [x300]
741
        self._fill(0x300, fdata)
742

    
743
        # update the info in x1000; it has 2 bytes with
744
        # x00 = bank , x01 = bank's channel count
745
        # the rest of the 14 bytes are \xff
746
        bdata = ""
747
        for i in range(1, len(data) + 1):
748
            line = chr(i) + chr(len(data[i]))
749
            line += "\xff" * 14
750
            bdata += line
751

    
752
        # fill to match a full 256 bytes block
753
        bdata += (256 - (len(bdata)) % 256) * "\xFF"
754

    
755
        # fill to match the whole area
756
        bdata += (16 - len(bdata) / 256) * EMPTY_BLOCK
757

    
758
        # updating the data in the memmap [x1000]
759
        self._fill(0x1000, bdata)
760

    
761
        # DTMF id for each channel, 5 bytes lbcd at x7000
762
        # ############## TODO ###################
763
        fldata = "\x00\xf0\xff\xff\xff" * self._chs_progs + \
764
            "\xff" * (5 * (self._upper - self._chs_progs))
765

    
766
        # write it
767
        # updating the data in the memmap [x7000]
768
        self._fill(0x7000, fldata)
769

    
770
    def _set_variant(self):
771
        """Select and set the correct variables for the class acording
772
        to the correct variant of the radio"""
773
        rid = self._mmap[0xA7:0xAE]
774

    
775
        # indentify the radio variant and set the enviroment to it's values
776
        try:
777
            self._upper, low, high, self._kind = self.VARIANTS[rid]
778
            self._range = [low * 1000000, high * 1000000]
779

    
780
            # setting the bank data in the features, 8 & 16 CH dont have banks
781
            if self._upper < 32:
782
                rf = chirp_common.RadioFeatures()
783
                rf.has_bank = False
784

    
785
        except KeyError:
786
            LOG.debug("Wrong Kenwood radio, ID or unknown variant")
787
            LOG.debug(util.hexprint(rid))
788
            raise errors.RadioError(
789
                "Wrong Kenwood radio, ID or unknown variant, see LOG output.")
790
            return False
791

    
792
    def sync_in(self):
793
        """Do a download of the radio eeprom"""
794
        self._mmap = do_download(self)
795
        self.process_mmap()
796

    
797
    def sync_out(self):
798
        """Do an upload to the radio eeprom"""
799

    
800
        # chirp signature on the eprom ;-)
801
        sign = "Chirp"
802
        self._fill(0xbb, sign)
803

    
804
        try:
805
            self._prep_data()
806
            do_upload(self)
807
        except errors.RadioError:
808
            raise
809
        except Exception, e:
810
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
811

    
812
    def process_mmap(self):
813
        """Process the memory object"""
814
        # how many channels are programed
815
        self._chs_progs = ord(self._mmap[15])
816
        # load the memobj
817
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
818
        # to ser the vars on the class to the correct ones
819
        self._set_variant()
820

    
821
    def get_raw_memory(self, number):
822
        """Return a raw representation of the memory object, which
823
        is very helpful for development"""
824
        return repr(self._memobj.memory[number])
825

    
826
    def _decode_tone(self, val):
827
        """Parse the tone data to decode from mem, it returns:
828
        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
829
        val = int(val)
830
        if val == 65535:
831
            return '', None, None
832
        elif val >= 0x2800:
833
            code = int("%03o" % (val & 0x07FF))
834
            pol = (val & 0x8000) and "R" or "N"
835
            return 'DTCS', code, pol
836
        else:
837
            a = val / 10.0
838
            return 'Tone', a, None
839

    
840
    def _encode_tone(self, memval, mode, value, pol):
841
        """Parse the tone data to encode from UI to mem"""
842
        if mode == '':
843
            memval.set_raw("\xff\xff")
844
        elif mode == 'Tone':
845
            memval.set_value(int(value * 10))
846
        elif mode == 'DTCS':
847
            val = int("%i" % value, 8) + 0x2800
848
            if pol == "R":
849
                val += 0xA000
850
            memval.set_value(val)
851
        else:
852
            raise Exception("Internal error: invalid mode `%s'" % mode)
853

    
854
    def _get_scan(self, chan):
855
        """Get the channel scan status from the 16 bytes array on the eeprom
856
        then from the bits on the byte, return '' or 'S' as needed"""
857
        result = "S"
858
        byte = int(chan/8)
859
        bit = chan % 8
860
        res = self._memobj.settings.add[byte] & (pow(2, bit))
861
        if res > 0:
862
            result = ""
863

    
864
        return result
865

    
866
    def _set_scan(self, chan, value):
867
        """Set the channel scan status from UI to the mem_map"""
868
        byte = int(chan/8)
869
        bit = chan % 8
870

    
871
        # get the actual value to see if I need to change anything
872
        actual = self._get_scan(chan)
873
        if actual != value:
874
            # I have to flip the value
875
            rbyte = self._memobj.settings.add[byte]
876
            rbyte = rbyte ^ pow(2, bit)
877
            self._memobj.settings.add[byte] = rbyte
878

    
879
    def get_memory(self, number):
880
        # Get a low-level memory object mapped to the image
881
        _mem = self._memobj.memory[number - 1]
882

    
883
        # Create a high-level memory object to return to the UI
884
        mem = chirp_common.Memory()
885

    
886
        # Memory number
887
        mem.number = number
888

    
889
        # this radio has a setting about the amount of real chans of the 128
890
        # olso in the channel has xff on the Rx freq it's empty
891
        if (number > (self._chs_progs + 1)) or (_mem.get_raw()[0] == "\xFF"):
892
            mem.empty = True
893
            # but is not enough, you have to crear the memory in the mmap
894
            # to get it ready for the sync_out process
895
            _mem.set_raw("\xFF" * 48)
896
            return mem
897

    
898
        # Freq and offset
899
        mem.freq = int(_mem.rxfreq) * 10
900
        # tx freq can be blank
901
        if _mem.get_raw()[16] == "\xFF":
902
            # TX freq not set
903
            mem.offset = 0
904
            mem.duplex = "off"
905
        else:
906
            # TX feq set
907
            offset = (int(_mem.txfreq) * 10) - mem.freq
908
            if offset < 0:
909
                mem.offset = abs(offset)
910
                mem.duplex = "-"
911
            elif offset > 0:
912
                mem.offset = offset
913
                mem.duplex = "+"
914
            else:
915
                mem.offset = 0
916

    
917
        # name TAG of the channel
918
        mem.name = str(_mem.name).rstrip()
919

    
920
        # power
921
        mem.power = POWER_LEVELS[_mem.power]
922

    
923
        # wide/marrow
924
        mem.mode = MODES[_mem.wide]
925

    
926
        # skip
927
        mem.skip = self._get_scan(number - 1)
928

    
929
        # tone data
930
        rxtone = txtone = None
931
        txtone = self._decode_tone(_mem.tx_tone)
932
        rxtone = self._decode_tone(_mem.rx_tone)
933
        chirp_common.split_tone_decode(mem, txtone, rxtone)
934

    
935
        # Extra
936
        # bank and number in the channel
937
        mem.extra = RadioSettingGroup("extra", "Extra")
938

    
939
        # validate bank
940
        b = int(_mem.bank)
941
        if b > 127 or b == 0:
942
            _mem.bank = b = 1
943

    
944
        bank = RadioSetting("bank", "Bank it belongs",
945
                            RadioSettingValueInteger(1, 128, b))
946
        mem.extra.append(bank)
947

    
948
        # validate bnumb
949
        if int(_mem.bnumb) > 127:
950
            _mem.bank = mem.number
951

    
952
        bnumb = RadioSetting("bnumb", "Ch number in the bank",
953
                             RadioSettingValueInteger(0, 127, _mem.bnumb))
954
        mem.extra.append(bnumb)
955

    
956
        bs = RadioSetting("beat_shift", "Beat shift",
957
                          RadioSettingValueBoolean(
958
                              not bool(_mem.beat_shift)))
959
        mem.extra.append(bs)
960

    
961
        cp = RadioSetting("compander", "Compander",
962
                          RadioSettingValueBoolean(
963
                              not bool(_mem.compander)))
964
        mem.extra.append(cp)
965

    
966
        bl = RadioSetting("busy_lock", "Busy Channel lock",
967
                          RadioSettingValueBoolean(
968
                              not bool(_mem.busy_lock)))
969
        mem.extra.append(bl)
970

    
971
        return mem
972

    
973
    def set_memory(self, mem):
974
        """Set the memory data in the eeprom img from the UI
975
        not ready yet, so it will return as is"""
976

    
977
        # get the eprom representation of this channel
978
        _mem = self._memobj.memory[mem.number - 1]
979

    
980
        # if empty memmory
981
        if mem.empty:
982
            _mem.set_raw("\xFF" * 48)
983
            return
984

    
985
        # frequency
986
        _mem.rxfreq = mem.freq / 10
987

    
988
        # this are a mistery yet, but so falr there is no impact
989
        # whit this default values for new channels
990
        if int(_mem.rx_unkw) == 0xff:
991
            _mem.rx_unkw = 0x35
992
            _mem.tx_unkw = 0x32
993

    
994
        # duplex
995
        if mem.duplex == "+":
996
            _mem.txfreq = (mem.freq + mem.offset) / 10
997
        elif mem.duplex == "-":
998
            _mem.txfreq = (mem.freq - mem.offset) / 10
999
        else:
1000
            _mem.txfreq = mem.freq / 10
1001

    
1002
        # tone data
1003
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
1004
            chirp_common.split_tone_encode(mem)
1005
        self._encode_tone(_mem.tx_tone, txmode, txtone, txpol)
1006
        self._encode_tone(_mem.rx_tone, rxmode, rxtone, rxpol)
1007

    
1008
        # name TAG of the channel
1009
        _namelength = self.get_features().valid_name_length
1010
        for i in range(_namelength):
1011
            try:
1012
                _mem.name[i] = mem.name[i]
1013
            except IndexError:
1014
                _mem.name[i] = "\x20"
1015

    
1016
        # power
1017
        # default power is low
1018
        if mem.power is None:
1019
            mem.power = POWER_LEVELS[0]
1020

    
1021
        _mem.power = POWER_LEVELS.index(mem.power)
1022

    
1023
        # wide/marrow
1024
        _mem.wide = MODES.index(mem.mode)
1025

    
1026
        # scan add property
1027
        self._set_scan(mem.number - 1, mem.skip)
1028

    
1029
        # bank and number in the channel
1030
        if int(_mem.bnumb) == 0xff:
1031
            _mem.bnumb = mem.number - 1
1032
            _mem.bank = 1
1033

    
1034
        # extra settings
1035
        for setting in mem.extra:
1036
            if setting != "bank" or setting != "bnumb":
1037
                setattr(_mem, setting.get_name(), not bool(setting.value))
1038

    
1039
        # all data get sync after channel mod
1040
        #self._prep_data()
1041

    
1042
        return mem
1043

    
1044
    @classmethod
1045
    def match_model(cls, filedata, filename):
1046
        match_size = False
1047
        match_model = False
1048

    
1049
        # testing the file data size
1050
        if len(filedata) == MEM_SIZE:
1051
            match_size = True
1052

    
1053
        # testing the firmware model fingerprint
1054
        match_model = model_match(cls, filedata)
1055

    
1056
        if match_size and match_model:
1057
            return True
1058
        else:
1059
            return False
1060

    
1061
    def get_settings(self):
1062
        """Translate the bit in the mem_struct into settings in the UI"""
1063
        sett = self._memobj.settings
1064
        mess = self._memobj.message
1065
        keys = self._memobj.keys
1066
        idm = self._memobj.id
1067
        passwd = self._memobj.passwords
1068

    
1069
        # basic features of the radio
1070
        basic = RadioSettingGroup("basic", "Basic Settings")
1071
        # dealer settings
1072
        dealer = RadioSettingGroup("dealer", "Dealer Settings")
1073
        # buttons
1074
        fkeys = RadioSettingGroup("keys", "Front keys config")
1075

    
1076
        # TODO / PLANED
1077
        # adjust feqs
1078
        #freqs = RadioSettingGroup("freqs", "Adjust Frequencies")
1079

    
1080
        top = RadioSettings(basic, dealer, fkeys)
1081

    
1082
        # Basic
1083
        tot = RadioSetting("settings.tot", "Time Out Timer (TOT)",
1084
                           RadioSettingValueList(TOT, TOT[
1085
                           TOT.index(str(int(sett.tot)))]))
1086
        basic.append(tot)
1087

    
1088
        totalert = RadioSetting("settings.tot_alert", "TOT pre alert",
1089
                                RadioSettingValueList(TOT_PRE,
1090
                                TOT_PRE[int(sett.tot_alert)]))
1091
        basic.append(totalert)
1092

    
1093
        totrekey = RadioSetting("settings.tot_rekey", "TOT re-key time",
1094
                                RadioSettingValueList(TOT_REKEY,
1095
                                TOT_REKEY[int(sett.tot_rekey)]))
1096
        basic.append(totrekey)
1097

    
1098
        totreset = RadioSetting("settings.tot_reset", "TOT reset time",
1099
                                RadioSettingValueList(TOT_RESET,
1100
                                TOT_RESET[int(sett.tot_reset)]))
1101
        basic.append(totreset)
1102

    
1103
        # this feature is for mobile only
1104
        if self.TYPE[0] == "M":
1105
            minvol = RadioSetting("settings.min_vol", "Minimum volume",
1106
                                  RadioSettingValueList(VOL,
1107
                                  VOL[int(sett.min_vol)]))
1108
            basic.append(minvol)
1109

    
1110
            tv = int(sett.tone_vol)
1111
            if tv == 255:
1112
                tv = 32
1113
            tvol = RadioSetting("settings.tone_vol", "Minimum tone volume",
1114
                                RadioSettingValueList(TVOL, TVOL[tv]))
1115
            basic.append(tvol)
1116

    
1117
        sql = RadioSetting("settings.sql_level", "SQL Ref Level",
1118
                           RadioSettingValueList(
1119
                           SQL, SQL[int(sett.sql_level)]))
1120
        basic.append(sql)
1121

    
1122
        #c2t = RadioSetting("settings.c2t", "Clear to Transpond",
1123
                           #RadioSettingValueBoolean(not sett.c2t))
1124
        #basic.append(c2t)
1125

    
1126
        ptone = RadioSetting("settings.poweron_tone", "Power On tone",
1127
                             RadioSettingValueBoolean(sett.poweron_tone))
1128
        basic.append(ptone)
1129

    
1130
        ctone = RadioSetting("settings.control_tone", "Control (key) tone",
1131
                             RadioSettingValueBoolean(sett.control_tone))
1132
        basic.append(ctone)
1133

    
1134
        wtone = RadioSetting("settings.warn_tone", "Warning tone",
1135
                             RadioSettingValueBoolean(sett.warn_tone))
1136
        basic.append(wtone)
1137

    
1138
        # Save Battery only for portables?
1139
        if self.TYPE[0] == "P":
1140
            bs = int(sett.battery_save) == 0x32 and True or False
1141
            bsave = RadioSetting("settings.battery_save", "Battery Saver",
1142
                                 RadioSettingValueBoolean(bs))
1143
            basic.append(bsave)
1144

    
1145
        ponm = str(sett.poweronmesg).strip("\xff")
1146
        pom = RadioSetting("settings.poweronmesg", "Power on message",
1147
                           RadioSettingValueString(0, 8, ponm, False))
1148
        basic.append(pom)
1149

    
1150
        # dealer
1151
        # clean the model / CHs / Type in the same layout as the KPG program
1152
        modelstr = str(idm.model) + " [" + str(self._upper) + "CH]: " + \
1153
            str(idm.type) + ", " + str(self._range[0]/1000000) + "-" + \
1154
            str(self._range[1]/1000000) + " Mhz"
1155
        mstr = ""
1156

    
1157
        valid_chars = [32, 44, 45, 47, 58, 91, 93] + range(48, 58) + \
1158
            range(65, 91) + range(97, 123)
1159

    
1160
        for i in range(0, len(modelstr)):
1161
            if ord(modelstr[i]) in valid_chars:
1162
                mstr += modelstr[i]
1163

    
1164
        val = RadioSettingValueString(0, 35, mstr)
1165
        val.set_mutable(False)
1166
        mod = RadioSetting("not.mod", "Radio Version", val)
1167
        dealer.append(mod)
1168

    
1169
        sn = str(idm.serial).strip(" \xff")
1170
        val = RadioSettingValueString(0, 8, sn)
1171
        val.set_mutable(False)
1172
        serial = RadioSetting("not.serial", "Serial number", val)
1173
        dealer.append(serial)
1174

    
1175
        svp = str(sett.lastsoftversion).strip(" \xff")
1176
        val = RadioSettingValueString(0, 5, svp)
1177
        val.set_mutable(False)
1178
        sver = RadioSetting("not.softver", "Software Version", val)
1179
        dealer.append(sver)
1180

    
1181
        l1 = str(mess.line1).strip(" \xff")
1182
        line1 = RadioSetting("message.line1", "Comment 1",
1183
                           RadioSettingValueString(0, 32, l1))
1184
        dealer.append(line1)
1185

    
1186
        l2 = str(mess.line2).strip(" \xff")
1187
        line2 = RadioSetting("message.line2", "Comment 2",
1188
                             RadioSettingValueString(0, 32, l2))
1189
        dealer.append(line2)
1190

    
1191
        sprog = RadioSetting("settings.self_prog", "Self program",
1192
                             RadioSettingValueBoolean(sett.self_prog))
1193
        dealer.append(sprog)
1194

    
1195
        clone = RadioSetting("settings.clone", "Allow clone",
1196
                             RadioSettingValueBoolean(sett.clone))
1197
        dealer.append(clone)
1198

    
1199
        panel = RadioSetting("settings.panel_test", "Panel Test",
1200
                             RadioSettingValueBoolean(sett.panel_test))
1201
        dealer.append(panel)
1202

    
1203
        fmw = RadioSetting("settings.firmware_prog", "Firmware program",
1204
                           RadioSettingValueBoolean(sett.firmware_prog))
1205
        dealer.append(fmw)
1206

    
1207
        #pwd =  str(passwd.radio).strip("\xff").ljust(6, "0")
1208
        #pwr = RadioSetting("passwords.radio",
1209
                           #"Radio Password (000000 disabled)",
1210
                           #RadioSettingValueInteger(0, 9, pwd[0]),
1211
                           #RadioSettingValueInteger(0, 9, pwd[1]),
1212
                           #RadioSettingValueInteger(0, 9, pwd[2]),
1213
                           #RadioSettingValueInteger(0, 9, pwd[3]),
1214
                           #RadioSettingValueInteger(0, 9, pwd[4]),
1215
                           #RadioSettingValueInteger(0, 9, pwd[5]))
1216
        #dealer.append(pwr)
1217

    
1218
        #pwd =  str(passwd.data).strip("\xff").ljust(6, "0")
1219
        #pwd = RadioSetting("passwords.data",
1220
                           #"Data Password (000000 disabled)",
1221
                           #RadioSettingValueInteger(0, 9, pwd[0]),
1222
                           #RadioSettingValueInteger(0, 9, pwd[1]),
1223
                           #RadioSettingValueInteger(0, 9, pwd[2]),
1224
                           #RadioSettingValueInteger(0, 9, pwd[3]),
1225
                           #RadioSettingValueInteger(0, 9, pwd[4]),
1226
                           #RadioSettingValueInteger(0, 9, pwd[5]))
1227
        #dealer.append(pwd)
1228

    
1229
        # front keys
1230
        # The Mobile only parameters are wraped here
1231
        if self.TYPE[0] == "M":
1232
            vu = RadioSetting("keys.kVOL_UP", "VOL UP",
1233
                              RadioSettingValueList(KEYS.values(),
1234
                              KEYS.values()[KEYS.keys().index(
1235
                                  int(keys.kVOL_UP))]))
1236
            fkeys.append(vu)
1237

    
1238
            vd = RadioSetting("keys.kVOL_DOWN", "VOL DOWN",
1239
                              RadioSettingValueList(KEYS.values(),
1240
                              KEYS.values()[KEYS.keys().index(
1241
                                  int(keys.kVOL_DOWN))]))
1242
            fkeys.append(vd)
1243

    
1244
            chu = RadioSetting("keys.kCH_UP", "CH UP",
1245
                               RadioSettingValueList(KEYS.values(),
1246
                               KEYS.values()[KEYS.keys().index(
1247
                                   int(keys.kCH_UP))]))
1248
            fkeys.append(chu)
1249

    
1250
            chd = RadioSetting("keys.kCH_DOWN", "CH DOWN",
1251
                               RadioSettingValueList(KEYS.values(),
1252
                               KEYS.values()[KEYS.keys().index(
1253
                                   int(keys.kCH_DOWN))]))
1254
            fkeys.append(chd)
1255

    
1256
            foot = RadioSetting("keys.kFOOT", "Foot switch",
1257
                               RadioSettingValueList(KEYS.values(),
1258
                               KEYS.values()[KEYS.keys().index(
1259
                                   int(keys.kCH_DOWN))]))
1260
            fkeys.append(foot)
1261

    
1262
        # this is the common buttons for all
1263

    
1264
        # 260G model don't have the front keys
1265
        if not "P2600" in self.TYPE:
1266
            scn_name = "SCN"
1267
            if self.TYPE[0] == "P":
1268
                scn_name = "Open Circle"
1269

    
1270
            scn = RadioSetting("keys.kSCN", scn_name,
1271
                               RadioSettingValueList(KEYS.values(),
1272
                               KEYS.values()[KEYS.keys().index(
1273
                                   int(keys.kSCN))]))
1274
            fkeys.append(scn)
1275

    
1276
            a_name = "A"
1277
            if self.TYPE[0] == "P":
1278
                a_name = "Closed circle"
1279

    
1280
            a = RadioSetting("keys.kA", a_name,
1281
                             RadioSettingValueList(KEYS.values(),
1282
                             KEYS.values()[KEYS.keys().index(
1283
                                 int(keys.kA))]))
1284
            fkeys.append(a)
1285

    
1286
            da_name = "D/A"
1287
            if self.TYPE[0] == "P":
1288
                da_name = "< key"
1289

    
1290
            da = RadioSetting("keys.kDA", da_name,
1291
                              RadioSettingValueList(KEYS.values(),
1292
                              KEYS.values()[KEYS.keys().index(
1293
                                  int(keys.kDA))]))
1294
            fkeys.append(da)
1295

    
1296
            gu_name = "Triangle up"
1297
            if self.TYPE[0] == "P":
1298
                gu_name = "> key"
1299

    
1300
            gu = RadioSetting("keys.kGROUP_UP", gu_name,
1301
                              RadioSettingValueList(KEYS.values(),
1302
                              KEYS.values()[KEYS.keys().index(
1303
                                  int(keys.kGROUP_UP))]))
1304
            fkeys.append(gu)
1305

    
1306
        # Side keys on portables
1307
        gd_name = "Triangle Down"
1308
        if self.TYPE[0] == "P":
1309
            gd_name = "Side 1"
1310

    
1311
        gd = RadioSetting("keys.kGROUP_DOWN", gd_name,
1312
                          RadioSettingValueList(KEYS.values(),
1313
                          KEYS.values()[KEYS.keys().index(
1314
                              int(keys.kGROUP_DOWN))]))
1315
        fkeys.append(gd)
1316

    
1317
        mon_name = "MON"
1318
        if self.TYPE[0] == "P":
1319
            mon_name = "Side 2"
1320

    
1321
        mon = RadioSetting("keys.kMON", mon_name,
1322
                           RadioSettingValueList(KEYS.values(),
1323
                           KEYS.values()[KEYS.keys().index(
1324
                               int(keys.kMON))]))
1325
        fkeys.append(mon)
1326

    
1327
        return top
1328

    
1329
    def set_settings(self, settings):
1330
        """Translate the settings in the UI into bit in the mem_struct
1331
        I don't understand well the method used in many drivers
1332
        so, I used mine, ugly but works ok"""
1333

    
1334
        mobj = self._memobj
1335

    
1336
        for element in settings:
1337
            if not isinstance(element, RadioSetting):
1338
                self.set_settings(element)
1339
                continue
1340

    
1341
            # Let's roll the ball
1342
            if "." in element.get_name():
1343
                inter, setting = element.get_name().split(".")
1344
                # you must ignore the settings with "not"
1345
                # this are READ ONLY attributes
1346
                if inter == "not":
1347
                    continue
1348

    
1349
                obj = getattr(mobj, inter)
1350
                value = element.value
1351

    
1352
                # integers case + special case
1353
                if setting in ["tot", "tot_alert", "min_vol", "tone_vol",
1354
                               "sql_level", "tot_rekey", "tot_reset"]:
1355
                    # catching the "off" values as zero
1356
                    try:
1357
                        value = int(value)
1358
                    except:
1359
                        value = 0
1360

    
1361
                    # tot case step 15
1362
                    if setting == "tot":
1363
                        value = value * 15
1364
                        # off is special
1365
                        if value == 0:
1366
                            value = 0x4b0
1367

    
1368
                    # Caso tone_vol
1369
                    if setting == "tone_vol":
1370
                        # off is special
1371
                        if value == 32:
1372
                            value = 0xff
1373

    
1374
                # Bool types + inverted
1375
                if setting in ["c2t", "poweron_tone", "control_tone",
1376
                               "warn_tone", "battery_save", "self_prog",
1377
                               "clone", "panel_test"]:
1378
                    value = bool(value)
1379

    
1380
                    # this cases are inverted
1381
                    if setting == "c2t":
1382
                        value = not value
1383

    
1384
                    # case battery save is special
1385
                    if setting == "battery_save":
1386
                        if bool(value) is True:
1387
                            value = 0x32
1388
                        else:
1389
                            value = 0xff
1390

    
1391
                # String cases
1392
                if setting in ["poweronmesg", "line1", "line2"]:
1393
                    # some vars
1394
                    value = str(value)
1395
                    just = 8
1396
                    # lines with 32
1397
                    if "line" in setting:
1398
                        just = 32
1399

    
1400
                    # empty case
1401
                    if len(value) == 0:
1402
                        value = "\xff" * just
1403
                    else:
1404
                        value = value.ljust(just)
1405

    
1406
                ## password with special case
1407
                #if setting == "radio" or setting == "data":
1408
                    #pass
1409

    
1410
                # case keys, with special config
1411
                if inter == "keys":
1412
                    value = KEYS.keys()[KEYS.values().index(str(value))]
1413

    
1414
            # Apply al configs done
1415
            setattr(obj, setting, value)
1416

    
1417
    def get_bank_model(self):
1418
        """Pass the bank model to the UI part"""
1419
        rf = self.get_features()
1420
        if rf.has_bank is True:
1421
            return Kenwood60GBankModel(self)
1422
        else:
1423
            return None
1424

    
1425
    def _get_bank(self, loc):
1426
        """Get the bank data for a specific channel"""
1427
        mem = self._memobj.memory[loc - 1]
1428
        bank = int(mem.bank) - 1
1429

    
1430
        if bank > self._num_banks or bank < 1:
1431
            # all channels must belong to a bank, even with just 1 bank
1432
            return 0
1433
        else:
1434
            return bank
1435

    
1436
    def _set_bank(self, loc, bank):
1437
        """Set the bank data for a specific channel"""
1438
        try:
1439
            b = int(bank)
1440
            if b > 127:
1441
                b = 0
1442
            mem = self._memobj.memory[loc - 1]
1443
            mem.bank = b + 1
1444
        except:
1445
            msg = "You can't have a channel without a bank, click another bank"
1446
            raise errors.InvalidDataError(msg)
1447

    
1448

    
1449
# This kenwwood family is known as "60-G Serie"
1450
# all this radios ending in G are compatible:
1451
#
1452
# Portables VHF TK-260/270/272/278
1453
# Portables UHF TK-360/370/372/378/388
1454
#
1455
# Mobiles VHF TK-760/762/768
1456
# Mobiles VHF TK-860/862/868
1457
#
1458
# Just dealing with VHF models at moment,
1459
# this are the radios I can get in hand
1460

    
1461
# WARNING !!!! Radios With Password in the data section <=###############
1462
#
1463
# when a radio has a data password (aka to program it) the last byte (#8)
1464
# in the id code change from \xf1 to \xb1; so we remove this last byte
1465
# from the identification procedures and variants.
1466
#
1467
# this effectively render the data password USELESS even if set.
1468
# this can change if user request it with high priority
1469

    
1470
@directory.register
1471
class TK768G_Radios(Kenwood_Serie_60G):
1472
    """Kenwood TK-768G Radios [M/C]"""
1473
    MODEL = "TK-768G"
1474
    TYPE = "M7680"
1475
    # Note that 8 CH don't have banks
1476
    VARIANTS = {
1477
        "M7680\x15\xff": (8, 136, 162, "M2"),
1478
        "M7680\x14\xff": (8, 148, 174, "M"),
1479
        "M76805\xff":    (128, 136, 162, "C2"),
1480
        "M76804\xff":    (128, 148, 174, "C"),
1481
        }
1482

    
1483

    
1484
@directory.register
1485
class TK762G_Radios(Kenwood_Serie_60G):
1486
    """Kenwood TK-762G Radios [K/E/NE]"""
1487
    MODEL = "TK-762G"
1488
    TYPE = "M7620"
1489
    # Note that 8 CH don't have banks
1490
    VARIANTS = {
1491
        "M7620\x05\xff": (8, 136, 162, "K2"),
1492
        "M7620\x04\xff": (8, 148, 172, "K"),
1493
        "M7620$\xff":    (8, 148, 172, "E"),
1494
        "M7620T\xff":    (8, 148, 172, "NE"),
1495
        }
1496

    
1497

    
1498
@directory.register
1499
class TK760G_Radios(Kenwood_Serie_60G):
1500
    """Kenwood TK-760G Radios [K/M/(N)E]"""
1501
    MODEL = "TK-760G"
1502
    TYPE = "M7600"
1503
    VARIANTS = {
1504
        "M7600\x05\xff": (128, 136, 162, "K2"),
1505
        "M7600\x04\xff": (128, 148, 174, "K"),
1506
        "M7600\x14\xff": (128, 146, 174, "M"),
1507
        "M7600T\xff":    (128, 146, 174, "NE")
1508
        }
1509

    
1510

    
1511
@directory.register
1512
class TK278G_Radios(Kenwood_Serie_60G):
1513
    """Kenwood TK-278G Radio C/C1/M/M1"""
1514
    MODEL = "TK-278G"
1515
    TYPE = "P2780"
1516
    # Note that 16 CH don't have banks
1517
    VARIANTS = {
1518
        "P27805\xff":    (128, 136, 150, "C1"),
1519
        "P27804\xff":    (128, 150, 174, "C"),
1520
        "P2780\x15\xff": (16,  136, 150, "M1"),
1521
        "P2780\x14\xff": (16,  150, 174, "M")
1522
        }
1523

    
1524

    
1525
@directory.register
1526
class TK272G_Radios(Kenwood_Serie_60G):
1527
    """Kenwood TK-272G Radio K/K1"""
1528
    MODEL = "TK-272G"
1529
    TYPE = "P2720"
1530
    VARIANTS = {
1531
        "P2720\x05\xfb": (32, 136, 150, "K1"),
1532
        "P2720\x04\xfb": (32, 150, 174, "K")
1533
        }
1534

    
1535

    
1536
@directory.register
1537
class TK270G_Radios(Kenwood_Serie_60G):
1538
    """Kenwood TK-270G Radio K/K1/M/E/NE/NT"""
1539
    MODEL = "TK-270G"
1540
    TYPE = "P2700"
1541
    VARIANTS = {
1542
        "P2700T\xff":    (128, 146, 174, "NE/NT"),
1543
        "P2700$\xff":    (128, 146, 174, "E"),
1544
        "P2700\x14\xff": (128, 150, 174, "M"),
1545
        "P2700\x05\xff": (128, 136, 150, "K1"),
1546
        "P2700\x04\xff": (128, 150, 174, "K"),
1547
        }
1548

    
1549

    
1550
@directory.register
1551
class TK260G_Radios(Kenwood_Serie_60G):
1552
    """Kenwood TK-260G Radio K/K1/M/E/NE/NT"""
1553
    MODEL = "TK-260G"
1554
    _hasbanks = False
1555
    TYPE = "P2600"
1556
    VARIANTS = {
1557
        "P2600U\xff":    (8, 136, 150, "N1"),
1558
        "P2600T\xff":    (8, 146, 174, "N"),
1559
        "P2600$\xff":    (8, 146, 174, "E"),
1560
        "P2600\x14\xff": (8, 150, 174, "M"),
1561
        "P2600\x05\xff": (8, 136, 150, "K1"),
1562
        "P2600\x04\xff": (8, 150, 174, "K")
1563
        }
(3-3/3)