Project

General

Profile

New Model #2999 » tk760g.py

Beta version of the driver, little bank support yet. - Pavel Milanes, 12/25/2015 11:13 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 unknown1[4];           // x12-x15 unknown1[4]
44
  u8 tot_alert;             // x16 TOT pre alert: range(0,10); 0 = off
45
  u8 unknown2[8];           // x17-x1F unknown
46
  u8 battery_save;          // Only for portable: FF = off, x32 = on
47
  // --
48
  u8 unknown3[10];          // x20
49
  u8 unknown4:3,            // x2d
50
     c2t:1,                 // 1 bit clear to transpond: 1-off
51
                            // This is relative to DTMF / 2-Tone settings
52
     unknown5:4;
53
  u8 unknown6[5];           // x2b-x2f
54
  // --
55
  u8 unknown7[16];          // x30 ?
56
  u8 unknown8[16];          // x40 ?
57
  u8 unknown9[16];          // x50 ?
58
  u8 unknown10[16];         // x60 ?
59
  // --
60
  u8 add[16];               // x70-x7f 128 bits corresponding add/skip values
61
  // --
62
  u8 unknown11:4,           // x80
63
     off_hook_decode:1,     // 1 bit off hook decode enabled: 1-off
64
     off_hook_horn_alert:1, // 1 bit off hook horn alert: 1-off
65
     unknown12:2;
66
  u8 unknown13;             // x81
67
  u8 unknown14:3,           // x82
68
     self_prog:1,           // 1 bit Self programming enabled: 1-on
69
     clone:1,               // 1 bit clone enabled: 1-on
70
     firmware_prog:1,       // 1 bit firmware programming enabled: 1-on
71
     unknown15:1,
72
     panel_test:1;          // 1 bit panel test enabled
73
  u8 unknown16;             // x83
74
  u8 unknown17:5,           // x84
75
     warn_tone:1,           // 1 bit warning tone, enabled: 1-on
76
     control_tone:1,        // 1 bit control tone (key tone), enabled: 1-on
77
     poweron_tone:1;        // 1 bit power on tone, enabled: 1-on
78
  u8 unknown18[5];          // x85-x89
79
  u8 min_vol;               // minimum volume posible: range(0,32); 0 = off
80
  u8 tone_vol;              // minimum tone volume posible:
81
                                // xff = continous, range(0, 31)
82
  u8 unknown19[4];          // x8c-x8f
83
  // --
84
  u8 unknown20[4];          // x90-x93
85
  char poweronmesg[8];      // x94-x9b power on mesg 8 bytes, off is "\FF" * 8
86
  u8 unknown21[4];          // x9c-x9f
87
  // --
88
  u8 unknown22[7];          // xa0-xa6
89
  char ident[8];            // xa7-xae radio identification string
90
  u8 unknown23;             // xaf
91
  // --
92
  u8 unknown24[11];         // xaf-xba
93
  char lastsoftversion[5];  // software version employed to program the radio
94
} settings;
95

    
96
#seekto 0xd0;
97
struct {
98
  u8 unknown[4];
99
  char radio[6];
100
  char data[6];
101
} passwords;
102

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

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

    
191
#seekto 0x200;
192
struct {
193
  char line1[32];
194
  char line2[32];
195
} message;
196

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

    
226
#seekto 0x5900;
227
struct {
228
  char model[8];
229
  u8 unknown50[4];
230
  char type[2];
231
  u8 unknown51[2];
232
    // --
233
  char serial[8];
234
  u8 unknown52[8];
235
} id;
236

    
237
#seekto 0x7000;
238
struct {
239
  lbcd dt2_id[5];       // DTMF lbcd ID (000-9999999999)
240
                        // 2-Tone = "11 f1 ff ff ff" ???
241
                        // None = "00 f0 ff ff ff"
242
} dtmf;
243
"""
244

    
245
MEM_SIZE = 0x8000  # 32,768 bytes
246
BLOCK_SIZE = 256
247
BLOCKS = MEM_SIZE / BLOCK_SIZE
248
MEM_BLOCKS = range(0, BLOCKS)
249

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

    
253
RO_BLOCKS = range(0x10, 0x1F) + range(0x59, 0x5f)
254
ACK_CMD = "\x06"
255

    
256
POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1),
257
                chirp_common.PowerLevel("High", watts=5)]
258

    
259
MODES = ["NFM", "FM"]  # 12.5 / 25 Khz
260
VALID_CHARS = chirp_common.CHARSET_UPPER_NUMERIC + "-*()/\-+=)"
261
SKIP_VALUES = ["", "S"]
262

    
263
TONES = chirp_common.TONES
264
TONES.remove(254.1)
265
DTCS_CODES = chirp_common.DTCS_CODES
266

    
267
LIST_TOT = ["off"] + ["%s" % x for x in range(15, 615, 15)]
268
LIST_TOT_PRE = ["off"] + ["%s" % x for x in range(1, 11)]
269
VOL = ["off"] + ["%s" % x for x in range(1, 32)]
270
TVOL = ["%s" % x for x in range(0, 33)]
271
TVOL[32] = "Continous"
272

    
273
KEYS = {
274
    0x33: "Display character",
275
    0x35: "Home Channel",                   # Posible portable only, chek it
276
    0x37: "CH down",
277
    0x38: "CH up",
278
    0x39: "Key lock",
279
    0x3a: "Lamp",                           # Portable only
280
    0x3b: "Public address",
281
    0x3c: "Reverse",                        # Just in updated firmwares (768G)
282
    0x3d: "Horn alert",
283
    0x3e: "Selectable QT",                  # Just in updated firmwares (768G)
284
    0x3f: "2-tone encode",
285
    0x40: "Monitor A: open mommentary",
286
    0x41: "Monitor B: Open Toggle",
287
    0x42: "Monitor C: Carrier mommentary",
288
    0x43: "Monitor D: Carrier toogle",
289
    0x44: "Operator selectable tone",
290
    0x45: "Redial",
291
    0x46: "RF Power Low",                   # portable only ?
292
    0x47: "Scan",
293
    0x48: "Scan del/add",
294
    0x4a: "GROUP down",
295
    0x4b: "GROUP up",
296
    #0x4e: "Tone off (Experimental)",       # undocumented !!!!
297
    0x4f: "None",
298
    0x50: "VOL down",
299
    0x51: "VOL up",
300
    0x52: "Talk around",
301
    0x5d: "AUX",
302
    0xa1: "Channel Up/Down"                 # Knob for portables only
303
    }
304

    
305

    
306
def _raw_recv(radio, amount):
307
    """Raw read from the radio device"""
308
    data = ""
309
    try:
310
        data = radio.pipe.read(amount)
311
    except:
312
        raise errors.RadioError("Error reading data from radio")
313

    
314
    return data
315

    
316

    
317
def _raw_send(radio, data):
318
    """Raw send to the radio device"""
319
    try:
320
        radio.pipe.write(data)
321
    except:
322
        raise errors.RadioError("Error sending data to radio")
323

    
324

    
325
def _close_radio(radio):
326
    """Get the radio out of program mode"""
327
    # 3 times, it will don't harm in normal work,
328
    # but it help's a lot in the developer process
329
    _raw_send(radio, "\x45\x45\x45")
330

    
331

    
332
def _checksum(data):
333
    """the radio block checksum algorithm"""
334
    cs = 0
335
    for byte in data:
336
            cs += ord(byte)
337
    return cs % 256
338

    
339

    
340
def _send(radio, frame):
341
    """Generic send data to the radio"""
342
    _raw_send(radio, frame)
343

    
344

    
345
def _make_frame(cmd, addr):
346
    """Pack the info in the format it likes"""
347
    return struct.pack(">BH", ord(cmd), addr)
348

    
349

    
350
def _handshake(radio, msg=""):
351
    """Make a full handshake"""
352
    # send ACK
353
    _raw_send(radio, ACK_CMD)
354
    # receive ACK
355
    ack = _raw_recv(radio, 1)
356
    # check ACK
357
    if ack != ACK_CMD:
358
        _close_radio(radio)
359
        mesg = "Handshake failed " + msg
360
        raise Exception(mesg)
361

    
362

    
363
def _check_write_ack(r, ack, addr):
364
    """Process the ack from the flock write process
365
    this is half handshake needed in tx data block"""
366
    # all ok
367
    if ack == ACK_CMD:
368
        return
369

    
370
    # Explicit BAD checksum
371
    if ack == "\x15":
372
        _close_radio(r)
373
        raise errors.RadioError(
374
            "Bad checksum in block %02x write" % addr)
375

    
376
    # everything else
377
    _close_radio(r)
378
    raise errors.RadioError(
379
        "Problem with the ack to block %02x write, ack %03i" %
380
        (addr, int(ack)))
381

    
382

    
383
def _recv(radio):
384
    """Receive data from the radio, 258 bytes split in (cmd, data, checksum)
385
    checking the checksum to be correct, and returning just
386
    256 bytes of data or false if short empty block"""
387
    rxdata = _raw_recv(radio, BLOCK_SIZE + 2)
388
    # when the RX block has two bytes and the first is \x5A
389
    # then the block is all \xFF
390
    if len(rxdata) == 2 and rxdata[0] == "\x5A":
391
        _handshake(radio, "short block")
392
        return False
393
    else:
394
        rcs = ord(rxdata[-1])
395
        data = rxdata[1:-1]
396
        ccs = _checksum(data)
397

    
398
        if rcs != ccs:
399
            _close_radio(radio)
400
            raise errors.RadioError(
401
                "Block Checksum Error! real %02x, calculated %02x" %
402
                (rcs, ccs))
403

    
404
        _handshake(radio, "after checksum")
405
        return data
406

    
407

    
408
def _open_radio(radio):
409
    """Open the radio into program mode and check if it's the correct model"""
410
    radio.pipe.setTimeout(0.25)  # only works in the range 0.2 - 0.3
411
    radio.pipe.setParity("E")
412

    
413
    _raw_send(radio, "PROGRAM")
414
    ack = _raw_recv(radio, 1)
415

    
416
    if ack != ACK_CMD:
417
        # bad response, properly close the radio before exception
418
        _close_radio(radio)
419
        raise errors.RadioError("The radio doesn't accept program mode")
420

    
421
    _raw_send(radio, "\x02")
422
    rid = _raw_recv(radio, 8)
423

    
424
    if not (radio.TYPE in rid):
425
        # bad response, properly close the radio before exception
426
        _close_radio(radio)
427
        # LOG.debug("Incorrect model ID, got %s" % util.hexprint(rid))
428
        raise errors.RadioError(
429
            "Incorrect model ID, got %s, it not contains %s" %
430
            (rid.strip("\xff"), radio.TYPE))
431

    
432
    # DEBUG
433
    LOG.debug("Full ident string is: %s" % util.hexprint(rid))
434
    _handshake(radio)
435

    
436

    
437
def do_download(radio):
438
    """ The download function """
439
    _open_radio(radio)
440

    
441
    # speed up the reading
442
    radio.pipe.setTimeout(0.03)  # only works in the range 0.25 and up
443

    
444
    # UI progress
445
    status = chirp_common.Status()
446
    status.cur = 0
447
    status.max = MEM_SIZE / 256
448
    status.msg = "Cloning from radio..."
449
    radio.status_fn(status)
450
    data = ""
451
    count = 0
452

    
453
    for addr in MEM_BLOCKS:
454
        _send(radio, _make_frame("R", addr))
455
        d = _recv(radio)
456
        # if empty block, it return false
457
        # aka we asume a empty 256 xFF block
458
        if d is False:
459
            d = EMPTY_BLOCK
460

    
461
        data += d
462

    
463
        # UI Update
464
        status.cur = count
465
        status.msg = "Cloning from radio..."
466
        radio.status_fn(status)
467

    
468
        count += 1
469

    
470
    _close_radio(radio)
471
    return memmap.MemoryMap(data)
472

    
473

    
474
def do_upload(radio):
475
    """ The upload function """
476
    _open_radio(radio)
477

    
478
    # Radio need time to write data to eeprom
479
    # 0.55 seconds as per the original software...
480
    radio.pipe.setTimeout(0.55)
481

    
482
    # UI progress
483
    status = chirp_common.Status()
484
    status.cur = 0
485
    status.max = BLOCKS
486
    status.msg = "Cloning to radio..."
487
    radio.status_fn(status)
488

    
489
    count = 0
490
    raddr = 0
491

    
492
    for addr in MEM_BLOCKS:
493
        # this is the data block to write
494
        data = radio.get_mmap()[raddr:raddr+BLOCK_SIZE]
495

    
496
        # The blocks from x59-x5F are NOT programmable
497
        # The blocks from x11-x1F are writed only if not empty
498
        if addr in RO_BLOCKS:
499
            # checking if in the range of optional blocks
500
            if addr >= 0x10 and addr <= 0x1F:
501
                # block is empty ?
502
                if data == EMPTY_BLOCK:
503
                    # no write of this block
504
                    # but we have to continue updating the counters
505
                    count += 1
506
                    raddr = count * 256
507
                    continue
508
            else:
509
                count += 1
510
                raddr = count * 256
511
                continue
512

    
513
        if data == EMPTY_BLOCK:
514
            frame = _make_frame("Z", addr) + "\xFF"
515
        else:
516
            cs = _checksum(data)
517
            frame = _make_frame("W", addr) + data + chr(cs)
518

    
519
        _send(radio, frame)
520
        ack = _raw_recv(radio, 1)
521
        _check_write_ack(radio, ack, addr)
522

    
523
        # UI Update
524
        status.cur = count
525
        status.msg = "Cloning to radio..."
526
        radio.status_fn(status)
527

    
528
        count += 1
529
        raddr = count * 256
530

    
531
    _close_radio(radio)
532

    
533

    
534
def model_match(cls, data):
535
    """Match the opened/downloaded image to the correct version"""
536
    rid = data[0xA7:0xAE]
537
    if (rid in cls.VARIANTS):
538
        # correct model
539
        return True
540
    else:
541
        #LOG.debug("Wrong Kenwood radio, ID:")
542
        #LOG.debug(util.hexprint(rid))
543
        # DEBUG
544
        #print("Wrong Kenwood radio, ID:")
545
        #print util.hexprint(rid)
546
        return False
547

    
548

    
549
class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
550
    """Kenwood Serie 60G Radios base class"""
551
    VENDOR = "Kenwood"
552
    BAUD_RATE = 9600
553
    _range = [136000000, 162000000]
554
    _memsize = MEM_SIZE
555
    _upper = 128
556
    _chs_progs = 0
557
    NAME_LENGTH = 8
558

    
559
    @classmethod
560
    def get_prompts(cls):
561
        rp = chirp_common.RadioPrompts()
562
        rp.experimental = \
563
            ('This driver in experimental, it is Beta code do not trust it'
564
             'At the moment there is no support for banks, so all new'
565
             'channels will be assigned to bank #1')
566
        rp.pre_download = _(dedent("""\
567
            This is beta code, keep a backup of your data with the KPG
568
            software before doing any change to your radio.
569

    
570
            This beta code is intended to test against TK-760G ONLY !!!
571

    
572
            The settings are incomplete and read only by now"""))
573
        rp.pre_upload = _(dedent("""\
574
            This is beta code, keep a backup of your data with the KPG
575
            software before doing any change to your radio.
576

    
577
            This beta code is intended to test against TK-760G ONLY !!!
578

    
579
            The settings are incomplete and read only by now"""))
580
        return rp
581

    
582
    def get_features(self):
583
        """Return information about this radio's features"""
584
        rf = chirp_common.RadioFeatures()
585
        rf.has_settings = True
586
        rf.has_bank = False
587
        rf.has_tuning_step = False
588
        rf.has_name = True
589
        rf.has_offset = True
590
        rf.has_mode = True
591
        rf.has_dtcs = True
592
        rf.has_rx_dtcs = True
593
        rf.has_dtcs_polarity = True
594
        rf.has_ctone = True
595
        rf.has_cross = True
596
        rf.valid_modes = MODES
597
        rf.valid_duplexes = ["", "-", "+", "off"]
598
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
599
        rf.valid_cross_modes = [
600
            "Tone->Tone",
601
            "DTCS->",
602
            "->DTCS",
603
            "Tone->DTCS",
604
            "DTCS->Tone",
605
            "->Tone",
606
            "DTCS->DTCS"]
607
        rf.valid_power_levels = POWER_LEVELS
608
        rf.valid_characters = VALID_CHARS
609
        rf.valid_skips = SKIP_VALUES
610
        rf.valid_dtcs_codes = DTCS_CODES
611
        rf.valid_bands = [self._range]
612
        rf.valid_name_length = 8
613
        rf.memory_bounds = (1, self._upper)
614
        return rf
615

    
616
    def _fill(self, offset, data):
617
        """Fill an specified area of the memmap with the passed data"""
618
        for addr in range(0, len(data)):
619
            self._mmap[offset + addr] = data[addr]
620

    
621
    def _bank_data(self):
622
        """Prepare the areas in the memmap to do a consistend write
623
        it has to make an update on the x300 area with banks and channel
624
        info; other in the x1000 with banks and channel counts
625
        and a last one in x7000 with flog data"""
626
        rchs = 0
627
        data = dict()
628

    
629
        # sorting the data
630
        for ch in range(0, self._upper):
631
            mem = self._memobj.memory[ch]
632
            if mem.get_raw()[0] != "\xFF":
633
                bank = int(mem.bank)
634
                try:
635
                    data[bank].append(ch)
636
                except:
637
                    data[bank] = []
638
                    data[bank].append(ch)
639
                data[bank].sort()
640
                # counting the real channels
641
                rchs = rchs + 1
642

    
643
        # updating the channel/bank count
644
        self._memobj.settings.channels = rchs
645
        self._chs_progs = rchs
646
        self._memobj.settings.banks = len(data)
647

    
648
        # building the data for the memmap
649
        fdata = ""
650
        for k, v in data.iteritems():
651
            c = 1
652
            for i in v:
653
                fdata += chr(k) + chr(c) + chr(k - 1) + chr(i)
654
                c = c + 1
655

    
656
        # fill to match a full 256 bytes block
657
        fdata += (len(fdata) % 256) * "\xFF"
658

    
659
        # updating the data in the memmap [x300]
660
        self._fill(0x300, fdata)
661

    
662
        # update the info in x1000; it has 2 bytes with
663
        # x00 = bank , x01 = bank's channel count
664
        # the rest of the 14 bytes are \xff
665
        bdata = ""
666
        for i in range(1, len(data) + 1):
667
            line = chr(i) + chr(len(data[i]))
668
            line += "\xff" * 14
669
            bdata += line
670

    
671
        # fill to match a full 256 bytes block
672
        bdata += (256 - (len(bdata)) % 256) * "\xFF"
673

    
674
        # fill to match the whole area
675
        bdata += (16 - len(bdata) / 256) * EMPTY_BLOCK
676

    
677
        # updating the data in the memmap [x1000]
678
        self._fill(0x1000, bdata)
679

    
680
        # DTMF id for each channel, 5 bytes lbcd at x7000
681
        # ############## PLEASE MODIFICATE ###################
682
        fldata = "\x00\xf0\xff\xff\xff" * self._chs_progs + \
683
            "\xff" * (5 * (self._upper - self._chs_progs))
684

    
685
        # write it
686
        # updating the data in the memmap [x7000]
687
        self._fill(0x7000, fldata)
688

    
689
    def _set_variant(self):
690
        """Select and set the correct variables for the class acording
691
        to the correct variant of the radio"""
692
        rid = self._mmap[0xA7:0xAE]
693

    
694
        # indentify the radio variant and set the enviroment to it's values
695
        try:
696
            self._upper, low, high, self._kind = self.VARIANTS[rid]
697
            self._range = [low * 1000000, high * 1000000]
698
        except KeyError:
699
            LOG.debug("Wrong Kenwood radio, ID or unknown variant")
700
            LOG.debug(util.hexprint(rid))
701
            raise errors.RadioError(
702
                "Wrong Kenwood radio, ID or unknown variant")
703
            # DEBUG
704
            print("Wrong Kenwood radio, ID or unknown variant")
705
            print util.hexprint(fp)
706
            return False
707

    
708
    def sync_in(self):
709
        """Do a download of the radio eeprom"""
710
        self._mmap = do_download(self)
711
        self.process_mmap()
712

    
713
    def sync_out(self):
714
        """Do an upload to the radio eeprom"""
715
        try:
716
            self._bank_data()
717
            do_upload(self)
718
        except errors.RadioError:
719
            raise
720
        except Exception, e:
721
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
722

    
723
    def process_mmap(self):
724
        """Process the memory object"""
725
        # how many channels are programed
726
        self._chs_progs = ord(self._mmap[15])
727
        # load the memobj
728
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
729
        # DEBUG
730
        print("memobj is %s" % type(self._memobj))
731
        # to ser the vars on the class to the correct ones
732
        self._set_variant()
733

    
734
    def get_raw_memory(self, number):
735
        """Return a raw representation of the memory object, which
736
        is very helpful for development"""
737
        return repr(self._memobj.memory[number])
738

    
739
    def _decode_tone(self, val):
740
        """Parse the tone data to decode from mem, it returns:
741
        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
742
        val = int(val)
743
        if val == 65535:
744
            return '', None, None
745
        elif val >= 0x2800:
746
            code = int("%03o" % (val & 0x07FF))
747
            pol = (val & 0x8000) and "R" or "N"
748
            return 'DTCS', code, pol
749
        else:
750
            a = val / 10.0
751
            return 'Tone', a, None
752

    
753
    def _encode_tone(self, memval, mode, value, pol):
754
        """Parse the tone data to encode from UI to mem"""
755
        if mode == '':
756
            memval.set_raw("\xff\xff")
757
        elif mode == 'Tone':
758
            memval.set_value(int(value * 10))
759
        elif mode == 'DTCS':
760
            val = int("%i" % value, 8) + 0x2800
761
            if pol == "R":
762
                val += 0xA000
763
            memval.set_value(val)
764
        else:
765
            raise Exception("Internal error: invalid mode `%s'" % mode)
766

    
767
    def _get_scan(self, chan):
768
        """Get the channel scan status from the 16 bytes array on the eeprom
769
        then from the bits on the byte, return '' or 'S' as needed"""
770
        result = "S"
771
        byte = int(chan/8)
772
        bit = chan % 8
773
        res = self._memobj.settings.add[byte] & (pow(2, bit))
774
        if res > 0:
775
            result = ""
776

    
777
        return result
778

    
779
    def _set_scan(self, chan, value):
780
        """Set the channel scan status from UI to the mem_map"""
781
        byte = int(chan/8)
782
        bit = chan % 8
783

    
784
        # get the actual value to see if I need to change anything
785
        actual = self._get_scan(chan)
786
        if actual != value:
787
            # I have to flip the value
788
            rbyte = self._memobj.settings.add[byte]
789
            rbyte = rbyte ^ pow(2, bit)
790
            self._memobj.settings.add[byte] = rbyte
791

    
792
    def get_memory(self, number):
793
        # Get a low-level memory object mapped to the image
794
        _mem = self._memobj.memory[number - 1]
795

    
796
        # Create a high-level memory object to return to the UI
797
        mem = chirp_common.Memory()
798

    
799
        # Memory number
800
        mem.number = number
801

    
802
        # this radio has a setting about the amount of real chans of the 128
803
        # olso in the channel has xff on the Rx freq it's empty
804
        if (number > (self._chs_progs + 1)) or (_mem.get_raw()[0] == "\xFF"):
805
            mem.empty = True
806
            # but is not enough, you have to crear the memory in the mmap
807
            # to get it ready for the sync_out process
808
            _mem.set_raw("\xFF" * 48)
809
            return mem
810

    
811
        # Freq and offset
812
        mem.freq = int(_mem.rxfreq) * 10
813
        # tx freq can be blank
814
        if _mem.get_raw()[16] == "\xFF":
815
            # TX freq not set
816
            mem.offset = 0
817
            mem.duplex = "off"
818
        else:
819
            # TX feq set
820
            offset = (int(_mem.txfreq) * 10) - mem.freq
821
            if offset < 0:
822
                mem.offset = abs(offset)
823
                mem.duplex = "-"
824
            elif offset > 0:
825
                mem.offset = offset
826
                mem.duplex = "+"
827
            else:
828
                mem.offset = 0
829

    
830
        # name TAG of the channel
831
        mem.name = str(_mem.name).rstrip()
832

    
833
        # power
834
        mem.power = POWER_LEVELS[_mem.power]
835

    
836
        # wide/marrow
837
        mem.mode = MODES[_mem.wide]
838

    
839
        # skip
840
        mem.skip = self._get_scan(number - 1)
841

    
842
        # tone data
843
        rxtone = txtone = None
844
        txtone = self._decode_tone(_mem.tx_tone)
845
        rxtone = self._decode_tone(_mem.rx_tone)
846
        chirp_common.split_tone_decode(mem, txtone, rxtone)
847

    
848
        # bank and number in the channel
849
        mem.extra = RadioSettingGroup("extra", "Extra")
850
        bank = RadioSetting("bank", "Bank it belongs",
851
                            RadioSettingValueInteger(1, 128, _mem.bank))
852
        mem.extra.append(bank)
853
        bnumb = RadioSetting("bnumb", "Ch number in the bank",
854
                             RadioSettingValueInteger(1, 128, _mem.bnumb))
855
        mem.extra.append(bnumb)
856

    
857
        return mem
858

    
859
    def set_memory(self, mem):
860
        """Set the memory data in the eeprom img from the UI
861
        not ready yet, so it will return as is"""
862

    
863
        # get the eprom representation of this channel
864
        _mem = self._memobj.memory[mem.number - 1]
865

    
866
        # if empty memmory
867
        if mem.empty:
868
            _mem.set_raw("\xFF" * 48)
869
            return
870

    
871
        # frequency
872
        _mem.rxfreq = mem.freq / 10
873

    
874
        # this are a mistery yet, but so falr there is no impact
875
        # whit this default values for new channels
876
        if int(_mem.rx_unkw) == 0xff:
877
             _mem.rx_unkw = 0x35
878
             _mem.tx_unkw = 0x32
879

    
880
        # duplex
881
        if mem.duplex == "+":
882
            _mem.txfreq = (mem.freq + mem.offset) / 10
883
        elif mem.duplex == "-":
884
            _mem.txfreq = (mem.freq - mem.offset) / 10
885
        else:
886
            _mem.txfreq = mem.freq / 10
887
            # tx_unkw = xff if duplex = off
888
            if mem.duplex == "off":
889
                _mem.tx_unkw = "\xff"
890

    
891
        # tone data
892
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
893
            chirp_common.split_tone_encode(mem)
894
        self._encode_tone(_mem.tx_tone, txmode, txtone, txpol)
895
        self._encode_tone(_mem.rx_tone, rxmode, rxtone, rxpol)
896

    
897
        # name TAG of the channel
898
        _namelength = self.get_features().valid_name_length
899
        for i in range(_namelength):
900
            try:
901
                _mem.name[i] = mem.name[i]
902
            except IndexError:
903
                _mem.name[i] = "\x20"
904

    
905
        # power
906
        # default power is low
907
        if mem.power == None:
908
            mem.power = POWER_LEVELS[0]
909

    
910
        _mem.power = POWER_LEVELS.index(mem.power)
911

    
912
        # wide/marrow
913
        _mem.wide = MODES.index(mem.mode)
914

    
915
        # scan add property
916
        self._set_scan(mem.number - 1, mem.skip)
917

    
918
        # bank and number in the channel
919
        if int(_mem.bnumb) == 0xff:
920
            _mem.bnumb = mem.number - 1
921
            _mem.bank = 1
922
            # DEBUG
923
            print("Setting new channel %02i" % (mem.number - 1))
924

    
925
    @classmethod
926
    def match_model(cls, filedata, filename):
927
        match_size = False
928
        match_model = False
929

    
930
        # testing the file data size
931
        if len(filedata) == MEM_SIZE:
932
            match_size = True
933

    
934
        # testing the firmware model fingerprint
935
        match_model = model_match(cls, filedata)
936

    
937
        if match_size and match_model:
938
            return True
939
        else:
940
            return False
941

    
942
    def get_settings(self):
943
        """Translate the bit in the mem_struct into settings in the UI"""
944
        sett = self._memobj.settings
945
        mess = self._memobj.message
946
        keys = self._memobj.keys
947
        idm = self._memobj.id
948
        passwd = self._memobj.passwords
949

    
950
        # basic features of the radio
951
        basic = RadioSettingGroup("basic", "Basic Settings")
952
        # dealer settings
953
        dealer = RadioSettingGroup("dealer", "Dealer Settings")
954
        # buttons
955
        fkeys = RadioSettingGroup("keys", "Front keys config")
956

    
957
        # TODO / PLANED
958
        # adjust feqs
959
        #freqs = RadioSettingGroup("freqs", "Adjust Frequencies")
960

    
961
        top = RadioSettings(basic, dealer, fkeys)
962

    
963
        # Basic
964
        tot = RadioSetting("settings.tot", "Time out timer",
965
                           RadioSettingValueList(LIST_TOT, LIST_TOT[
966
                               LIST_TOT.index(str(int(sett.tot)))]))
967
        basic.append(tot)
968

    
969
        totalert = RadioSetting("settings.tot_alert", "TOT pre alert",
970
                           RadioSettingValueList(LIST_TOT_PRE,
971
                               LIST_TOT_PRE[int(sett.tot_alert)]))
972
        basic.append(totalert)
973

    
974
        minvol = RadioSetting("settings.min_vol", "Default volume",
975
                           RadioSettingValueList(VOL,
976
                               VOL[int(sett.min_vol)]))
977
        basic.append(minvol)
978

    
979
        tv = int(sett.tone_vol)
980
        if tv == 255: tv = 32
981
        tvol = RadioSetting("settings.tone_vol", "Tone volume",
982
                           RadioSettingValueList(TVOL, TVOL[tv]))
983
        basic.append(tvol)
984

    
985
        c2t = RadioSetting("settings.c2t", "Clear to Transpond",
986
                           RadioSettingValueBoolean(not sett.c2t))
987
        basic.append(c2t)
988

    
989
        ptone = RadioSetting("settings.poweron_tone", "Power On tone",
990
                                 RadioSettingValueBoolean(sett.poweron_tone))
991
        basic.append(ptone)
992

    
993
        ctone = RadioSetting("settings.control_tone", "Control (key) tone",
994
                                 RadioSettingValueBoolean(sett.control_tone))
995
        basic.append(ctone)
996

    
997
        wtone = RadioSetting("settings.warn_tone", "Warning tone",
998
                                 RadioSettingValueBoolean(sett.warn_tone))
999
        basic.append(wtone)
1000

    
1001
        # Save Battery only for portables?
1002
        if self.TYPE[0] == "P":
1003
            bs = int(sett.battery_save) == 0x32 and True or False
1004
            bsave = RadioSetting("settings.battery_save", "Battery Saver",
1005
                                 RadioSettingValueBoolean(bs))
1006
            basic.append(bsave)
1007

    
1008
        ponm = str(sett.poweronmesg).strip("\xff")
1009
        pom = RadioSetting("settings.poweronmesg", "Power on message",
1010
                           RadioSettingValueString(0, 8, ponm, False))
1011
        basic.append(pom)
1012

    
1013

    
1014
        # dealer
1015
        # clean the model / CHs / Type in the same layout as the KPG program
1016
        modelstr = str(idm.model) + " [" + str(self._upper) + "CH]: " + \
1017
            str(idm.type) + ", " + str(self._range[0]/1000000) + "-" + \
1018
            str(self._range[1]/1000000) + " Mhz"
1019
        mstr = ""
1020

    
1021
        valid_chars = [32, 44, 45, 47, 58, 91, 93] + range(48, 58) + \
1022
            range(65, 91) + range(97, 123)
1023

    
1024
        for i in range(0, len(modelstr)):
1025
            if ord(modelstr[i]) in valid_chars:
1026
                mstr += modelstr[i]
1027

    
1028
        val = RadioSettingValueString(0, 35, mstr)
1029
        val.set_mutable(False)
1030
        mod = RadioSetting("not.mod", "Radio Version", val)
1031
        dealer.append(mod)
1032

    
1033
        sn = str(idm.serial).strip(" \xff")
1034
        val = RadioSettingValueString(0, 8, sn)
1035
        val.set_mutable(False)
1036
        serial = RadioSetting("not.serial", "Serial number", val)
1037
        dealer.append(serial)
1038

    
1039
        svp = str(sett.lastsoftversion).strip(" \xff")
1040
        val = RadioSettingValueString(0, 5, svp)
1041
        val.set_mutable(False)
1042
        sver = RadioSetting("not.softver", "Software Version", val)
1043
        dealer.append(sver)
1044

    
1045
        l1 = str(mess.line1).strip(" \xff")
1046
        line1 = RadioSetting("message.line1", "Comment 1",
1047
                           RadioSettingValueString(0, 32, l1))
1048
        dealer.append(line1)
1049

    
1050
        l2 = str(mess.line2).strip(" \xff")
1051
        line2 = RadioSetting("message.line2", "Comment 2",
1052
                           RadioSettingValueString(0, 32, l2))
1053
        dealer.append(line2)
1054

    
1055
        sprog = RadioSetting("settings.self_prog", "Self program",
1056
                                 RadioSettingValueBoolean(sett.self_prog))
1057
        dealer.append(sprog)
1058

    
1059
        clone = RadioSetting("settings.clone", "Allow clone",
1060
                                 RadioSettingValueBoolean(sett.clone))
1061
        dealer.append(clone)
1062

    
1063
        panel = RadioSetting("settings.panel_test", "Panel Test",
1064
                                 RadioSettingValueBoolean(sett.panel_test))
1065
        dealer.append(panel)
1066

    
1067
        fmw = RadioSetting("settings.firmware_prog", "Firmware program",
1068
                                 RadioSettingValueBoolean(sett.firmware_prog))
1069
        dealer.append(fmw)
1070

    
1071
        rpass = RadioSetting("passwords.radio", "Radio Password",
1072
                                 RadioSettingValueString(0, 6,
1073
                                     str(passwd.radio).strip("\xff")))
1074
        dealer.append(rpass)
1075

    
1076
        dpass = RadioSetting("passwords.data", "Data Password",
1077
                                 RadioSettingValueString(0, 6,
1078
                                     str(passwd.data).strip("\xff")))
1079
        dealer.append(dpass)
1080

    
1081

    
1082
        # front keys
1083
        # The Mobile only parameters are wraped here
1084
        if self.TYPE[0] == "M":
1085
            vu = RadioSetting("keys.kVOL_UP", "Left UP",
1086
                               RadioSettingValueList(KEYS.values(),
1087
                                   KEYS.values()[KEYS.keys().index(
1088
                                       int(keys.kVOL_UP))]))
1089
            fkeys.append(vu)
1090

    
1091
            vd = RadioSetting("keys.kVOL_DOWN", "Left DOWN",
1092
                               RadioSettingValueList(KEYS.values(),
1093
                                   KEYS.values()[KEYS.keys().index(
1094
                                       int(keys.kVOL_DOWN))]))
1095
            fkeys.append(vd)
1096

    
1097
            chu = RadioSetting("keys.kCH_UP", "Right UP",
1098
                               RadioSettingValueList(KEYS.values(),
1099
                                   KEYS.values()[KEYS.keys().index(
1100
                                       int(keys.kCH_UP))]))
1101
            fkeys.append(chu)
1102

    
1103
            chd = RadioSetting("keys.kCH_DOWN", "Right DOWN",
1104
                               RadioSettingValueList(KEYS.values(),
1105
                                   KEYS.values()[KEYS.keys().index(
1106
                                       int(keys.kCH_DOWN))]))
1107
            fkeys.append(chd)
1108

    
1109
            foot = RadioSetting("keys.kFOOT", "Foot switch",
1110
                               RadioSettingValueList(KEYS.values(),
1111
                                   KEYS.values()[KEYS.keys().index(
1112
                                       int(keys.kCH_DOWN))]))
1113
            fkeys.append(foot)
1114

    
1115
        # this is the common buttons for all
1116

    
1117
        # 260G model don't have the front keys
1118
        if not "P2600" in self.TYPE:
1119
            scn_name = "SCN"
1120
            if self.TYPE[0] == "P":
1121
                scn_name = "Open Circle"
1122

    
1123
            scn = RadioSetting("keys.kSCN", scn_name,
1124
                               RadioSettingValueList(KEYS.values(),
1125
                                   KEYS.values()[KEYS.keys().index(
1126
                                       int(keys.kSCN))]))
1127
            fkeys.append(scn)
1128

    
1129
            a_name = "A"
1130
            if self.TYPE[0] == "P":
1131
                a_name = "Closed circle"
1132

    
1133
            a = RadioSetting("keys.kA", a_name,
1134
                               RadioSettingValueList(KEYS.values(),
1135
                                   KEYS.values()[KEYS.keys().index(
1136
                                       int(keys.kA))]))
1137
            fkeys.append(a)
1138

    
1139
            da_name = "D/A"
1140
            if self.TYPE[0] == "P":
1141
                da_name = "Triangle to Left"
1142

    
1143
            da = RadioSetting("keys.kDA", da_name,
1144
                               RadioSettingValueList(KEYS.values(),
1145
                                   KEYS.values()[KEYS.keys().index(
1146
                                       int(keys.kDA))]))
1147
            fkeys.append(da)
1148

    
1149
            gu_name = "Triangle up"
1150
            if self.TYPE[0] == "P":
1151
                gu_name = "Triangle to Right"
1152

    
1153
            gu = RadioSetting("keys.kGROUP_UP", gu_name,
1154
                               RadioSettingValueList(KEYS.values(),
1155
                                   KEYS.values()[KEYS.keys().index(
1156
                                       int(keys.kGROUP_UP))]))
1157
            fkeys.append(gu)
1158

    
1159
        # Side keys on portables
1160
        gd_name = "Triangle Down"
1161
        if self.TYPE[0] == "P":
1162
            gd_name = "Side 1"
1163

    
1164
        gd = RadioSetting("keys.kGROUP_DOWN", gd_name,
1165
                           RadioSettingValueList(KEYS.values(),
1166
                               KEYS.values()[KEYS.keys().index(
1167
                                   int(keys.kGROUP_DOWN))]))
1168
        fkeys.append(gd)
1169

    
1170
        mon_name = "MON"
1171
        if self.TYPE[0] == "P":
1172
            mon_name = "Side 2"
1173

    
1174
        mon = RadioSetting("keys.kMON", mon_name,
1175
                           RadioSettingValueList(KEYS.values(),
1176
                               KEYS.values()[KEYS.keys().index(
1177
                                   int(keys.kMON))]))
1178
        fkeys.append(mon)
1179

    
1180
        return top
1181

    
1182
    def set_settings(self, settings):
1183
        """Translate the settings in the UI into bit in the mem_struct
1184
        I don't understand well the method used in many drivers
1185
        so, I used mine, ugly but works ok"""
1186

    
1187
        mobj = self._memobj
1188

    
1189
        for element in settings:
1190
            if not isinstance(element, RadioSetting):
1191
                self.set_settings(element)
1192
                continue
1193

    
1194
            # Let's roll the ball
1195
            if "." in element.get_name():
1196
                inter, setting = element.get_name().split(".")
1197
                # you must ignore the settings with "not"
1198
                # this are READ ONLY attributes
1199
                if inter == "not":
1200
                    continue
1201

    
1202
                obj = getattr(mobj, inter)
1203
                value = element.value
1204

    
1205
                # integers case + special case
1206
                if setting in ["tot", "tot_alert", "min_vol", "tone_vol"]:
1207
                    value = int(value)
1208
                    # tot case step 15
1209
                    if setting == "tot":
1210
                        value = value * 15
1211
                        # off is special
1212
                        if value == 0:
1213
                            value = 0x4b0
1214

    
1215
                    # Caso tone_vol
1216
                    if setting == "tone_vol":
1217
                        # off is special
1218
                        if value == 32:
1219
                            value = 0xff
1220

    
1221
                # Bool types + inverted
1222
                if setting in ["c2t", "poweron_tone", "control_tone",
1223
                               "warn_tone", "battery_save", "self_prog",
1224
                               "clone", "panel_test"]:
1225
                    value = bool(value)
1226

    
1227
                    # this cases are inverted
1228
                    if setting == "c2t":
1229
                        value = not value
1230

    
1231
                    # case battery save is special
1232
                    if setting == "battery_save":
1233
                        if bool(value) == True:
1234
                            value = 0x32
1235
                        else:
1236
                            value = 0xff
1237

    
1238
                # String cases
1239
                if setting in ["poweronmesg", "line1", "line2"]:
1240
                    # poweron is 8 / linesX is 32
1241
                    just = 8
1242
                    # lines with 32
1243
                    if "line" in setting:
1244
                        just = 32
1245
                    # password with 6
1246
                    if "radio" in setting or "data" in setting:
1247
                        just = 6
1248

    
1249
                    # empty case
1250
                    if len(str(value)) == 0:
1251
                        value = "\xff" * just
1252
                    else:
1253
                        # password don't like spaces
1254
                        if not ("radio" in setting or "data" in setting):
1255
                            value = str(value).ljust(just)
1256
                        else:
1257
                            value = str(value).ljust("\xff")
1258

    
1259
                # case keys, with special config
1260
                if inter == "keys":
1261
                    value = KEYS.keys()[KEYS.values().index(str(value))]
1262

    
1263
            # Apply al configs done
1264
            setattr(obj, setting, value)
1265

    
1266

    
1267
# This kenwwood family is known as "60-G Serie"
1268
# all this radios ending in G are compatible:
1269
#
1270
# Portables VHF TK-260/270/272/278
1271
# Portables UHF TK-360/370/372/378/388
1272
#
1273
# Mobiles VHF TK-760/762/768
1274
# Mobiles VHF TK-860/862/868
1275
#
1276
# Just dealing with VHF models at moment,
1277
# this are the radios I can get in hand
1278

    
1279
# WARNING !!!! Radios With Password in the data section <=###############
1280
#
1281
# when a radio has a data password (aka to program it) the last byte (#8)
1282
# in the id code change from \xf1 to \xb1; so we remove this last byte
1283
# from the identification procedures and variants.
1284
#
1285
# this effectively render the data password USELESS even if set.
1286
# this can change if user request it with high priority
1287

    
1288
@directory.register
1289
class TK768G_Radios(Kenwood_Serie_60G):
1290
    """Kenwood TK-768G Radios [M/C]"""
1291
    MODEL = "TK-768G"
1292
    _memsize = MEM_SIZE
1293
    _hasbanks = False
1294
    TYPE = "M7680"
1295
    # type MAP
1296
    # ======================================================
1297
    # TK-768G[8CH] M2     (136-162)     "M7680\x15\xff"
1298
    # TK-768G[8CH] M      (148-172)     "M7680\x14\xff"
1299
    # TK-768G[128CH] C2   (136-162)     "M76805\xff"
1300
    # TK-768G[128CH] C2   (148-172)     "M76804\xff"
1301
    # ======================================================
1302
    VARIANTS = {
1303
        "M7680\x15\xff": (8, 136, 162, "M2"),
1304
        "M7680\x14\xff": (8, 148, 174, "M"),
1305
        "M76805\xff":    (128, 136, 162, "C2"),
1306
        "M76804\xff":    (128, 148, 174, "C"),
1307
        }
1308

    
1309

    
1310
@directory.register
1311
class TK762G_Radios(Kenwood_Serie_60G):
1312
    """Kenwood TK-762G Radios [K/E/NE]"""
1313
    MODEL = "TK-762G"
1314
    _memsize = MEM_SIZE
1315
    _hasbanks = False
1316
    TYPE = "M7620"
1317
    # type MAP
1318
    # ======================================================
1319
    # TK-762G[8CH] K2     (136-162)     "M7620\x05\xff"
1320
    # TK-762G[8CH] K      (148-174)     "M7620\x04\xff"
1321
    # TK-762G[8CH] E      (148-174)     "M7620$\xff"
1322
    # TK-762G[8CH] NE     (148-174)     "M7620T\xff"
1323
    # ======================================================
1324
    VARIANTS = {
1325
        "M7620\x05\xff": (8, 136, 162, "K2"),
1326
        "M7620\x04\xff": (8, 148, 172, "K"),
1327
        "M7620$\xff":    (8, 148, 172, "E"),
1328
        "M7620T\xff":    (8, 148, 172, "NE"),
1329
        }
1330

    
1331

    
1332
@directory.register
1333
class TK760G_Radios(Kenwood_Serie_60G):
1334
    """Kenwood TK-760G Radios [K/M/(N)E]"""
1335
    MODEL = "TK-760G"
1336
    _memsize = MEM_SIZE
1337
    _hasbanks = True
1338
    TYPE = "M7600"
1339
    # type MAP
1340
    # ======================================================
1341
    # TK-760G[128CH] K2     (136-162)     "M7600\x05\xff"
1342
    # TK-760G[128CH] K      (148-174)     "M7600\x04\xff"
1343
    # TK-760G[128CH] M      (146-174)     "M7600\x14\xff"
1344
    # TK-760G[128CH] N/E    (146-174)     "M7600T\xff"
1345
    # ======================================================
1346
    VARIANTS = {
1347
        "M7600\x05\xff": (128, 136, 162, "K2"),
1348
        "M7600\x04\xff": (128, 148, 174, "K"),
1349
        "M7600\x14\xff": (128, 146, 174, "M"),
1350
        "M7600T\xff":    (128, 146, 174, "NE")
1351
        }
1352

    
1353

    
1354
@directory.register
1355
class TK278G_Radios(Kenwood_Serie_60G):
1356
    """Kenwood TK-278G Radio C/C1/M/M1"""
1357
    MODEL = "TK-278G"
1358
    _memsize = MEM_SIZE
1359
    _hasbanks = True # but 16 CH has no banks
1360
    TYPE = "P2780"
1361
    # type MAP
1362
    # ======================================================
1363
    # TK-278G[128CH] C1     (136-150)     "P27805\xff"
1364
    # TK-278G[128CH] C      (150-174)     "P27804\xff"
1365
    # TK-278G [16CH] M1     (136-150)     "P2780\x15\xff"
1366
    # TK-278G [16CH] M      (150-174)     "P2780\x14\xff"
1367
    # ======================================================
1368
    VARIANTS = {
1369
        "P27805\xff":    (128, 136, 150, "C1"),
1370
        "P27804\xff":    (128, 150, 174, "C"),
1371
        "P2780\x15\xff": (16,  136, 150, "M1"),
1372
        "P2780\x14\xff": (16,  150, 174, "M")
1373
        }
1374

    
1375

    
1376
@directory.register
1377
class TK272G_Radios(Kenwood_Serie_60G):
1378
    """Kenwood TK-272G Radio K/K1"""
1379
    MODEL = "TK-272G"
1380
    _memsize = MEM_SIZE
1381
    TYPE = "P2720"
1382
    _hasbanks = True
1383
    # type MAP
1384
    # ======================================================
1385
    # TK-272G [32CH] K1     (136-150)     "P2720\x05\xfb"
1386
    # TK-272G [32CH] K      (150-174)     "P2720\x04\xfb"
1387
    # ======================================================
1388
    VARIANTS = {
1389
        "P2720\x05\xfb": (32, 136, 150, "K1"),
1390
        "P2720\x04\xfb": (32, 150, 174, "K")
1391
        }
1392

    
1393

    
1394
@directory.register
1395
class TK270G_Radios(Kenwood_Serie_60G):
1396
    """Kenwood TK-270G Radio K/K1/M/E/NE/NT"""
1397
    MODEL = "TK-270G"
1398
    _memsize = MEM_SIZE
1399
    _hasbanks = True
1400
    TYPE = "P2700"
1401
    # type MAP
1402
    # ======================================================
1403
    # TK-270G[128CH] NE/NT  (146-174)     "P2700T\xff"
1404
    # TK-270G[128CH] E      (146-174)     "P2700$\xff"
1405
    # TK-270G[128CH] M      (150-174)     "P2700\x14\xff"
1406
    # TK-270G[128CH] K1     (136-150)     "P2700\x05\xff"
1407
    # TK-270G[128CH] K      (150-174)     "P2700\x04\xff"
1408
    # ======================================================
1409
    VARIANTS = {
1410
        "P2700T\xff":    (128, 146, 174, "NE/NT"),
1411
        "P2700$\xff":    (128, 146, 174, "E"),
1412
        "P2700\x14\xff": (128, 150, 174, "M"),
1413
        "P2700\x05\xff": (128, 136, 150, "K1"),
1414
        "P2700\x04\xff": (128, 150, 174, "K"),
1415
        }
1416

    
1417

    
1418
@directory.register
1419
class TK260G_Radios(Kenwood_Serie_60G):
1420
    """Kenwood TK-260G Radio K/K1/M/E/NE/NT"""
1421
    MODEL = "TK-260G"
1422
    _memsize = MEM_SIZE
1423
    _hasbanks = False
1424
    TYPE = "P2600"
1425
    # type MAP
1426
    # ======================================================
1427
    # TK-260G  [8CH] NE/NT1 (136-150)     "P2600U\xff"
1428
    # TK-260G  [8CH] NE/NT  (146-174)     "P2600T\xff"
1429
    # TK-260G  [8CH] E      (146-174)     "P2600$\xff"
1430
    # TK-260G  [8CH] M      (150-174)     "P2600\x14\xff"
1431
    # TK-260G  [8CH] K1     (136-150)     "P2600\x05\xff"
1432
    # TK-260G  [8CH] K      (150-174)     "P2600\x04\xff"
1433
    # ======================================================
1434
    VARIANTS = {
1435
        "P2600U\xff":    (8, 136, 150, "N1"),
1436
        "P2600T\xff":    (8, 146, 174, "N"),
1437
        "P2600$\xff":    (8, 146, 174, "E"),
1438
        "P2600\x14\xff": (8, 150, 174, "M"),
1439
        "P2600\x05\xff": (8, 136, 150, "K1"),
1440
        "P2600\x04\xff": (8, 150, 174, "K")
1441
        }
1442

    
(1-1/3)