Project

General

Profile

New Model #4395 » tk280.py

latest dev driver to test. - Pavel Milanes, 01/31/2017 07:44 AM

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

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

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

    
28
LOG = logging.getLogger(__name__)
29

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

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

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

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

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

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

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

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

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

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

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

    
264
NOTE = """ MENTAL NOTE ABOUT RADIO MEM
265

    
266
The OEM insist on not reading/writing some mem segments, see below
267

    
268
read: (hex)
269
    00 - 03
270
    07 - 08
271
    10
272
    20 - 21
273
    58 - 7F
274

    
275
write: (hex)
276
    00 - 03
277
    07 - 08
278
    10
279
    20 - 21
280
    60 - 7F
281

    
282

    
283
This can be an artifact to just read/write in the needed mem space and speed
284
up things, if so the first read blocks has all the data about channel groups
285
and freq/tones & names employed.
286

    
287
This is a copied trick from the "60G series" ones and may use the same schema.
288

    
289
I must investigate further on this.
290
"""
291

    
292
MEM_SIZE = 0x8000  # 32,768 bytes (128 blocks of 256 bytes)
293
BLOCK_SIZE = 256
294
MEM_BLOCKS = range(0, MEM_SIZE / BLOCK_SIZE)
295
# undefined yet...
296
#~ RO_BLOCKS = range(0x10, 0x1F) + range(0x59, 0x5f)
297

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

    
301
ACK_CMD = "\x06"
302

    
303
POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1),
304
                chirp_common.PowerLevel("High", watts=5)]
305

    
306
MODES = ["NFM", "FM"]  # 12.5 / 25 Khz
307
VALID_CHARS = chirp_common.CHARSET_UPPER_NUMERIC + "_-*()/\-+=)"
308
SKIP_VALUES = ["", "S"]
309

    
310
TONES = chirp_common.TONES
311
# TONES.remove(254.1)
312
DTCS_CODES = chirp_common.DTCS_CODES
313

    
314
TOT = ["off"] + ["%s" % x for x in range(15, 615, 15)]
315
TOT_PRE = ["off"] + ["%s" % x for x in range(1, 11)]
316
TOT_REKEY = ["off"] + ["%s" % x for x in range(1, 61)]
317
TOT_RESET = ["off"] + ["%s" % x for x in range(1, 16)]
318
VOL = ["off"] + ["%s" % x for x in range(1, 32)]
319
TVOL = ["%s" % x for x in range(0, 33)]
320
TVOL[32] = "Continous"
321
SQL = ["off"] + ["%s" % x for x in range(1, 10)]
322

    
323
## BOT = 0, EOT = 1, Both = 2, NONE = 3
324
#PTTID = ["BOT", "EOT", "Both", "none"]
325

    
326
# For debugging purposes
327
debug = False
328

    
329
#~ KEYS = {
330
    #~ 0x33: "Display character",
331
    #~ 0x35: "Home Channel",                   # Posible portable only, chek it
332
    #~ 0x37: "CH down",
333
    #~ 0x38: "CH up",
334
    #~ 0x39: "Key lock",
335
    #~ 0x3a: "Lamp",                           # Portable only
336
    #~ 0x3b: "Public address",
337
    #~ 0x3c: "Reverse",                        # Just in updated firmwares (768G)
338
    #~ 0x3d: "Horn alert",
339
    #~ 0x3e: "Selectable QT",                  # Just in updated firmwares (768G)
340
    #~ 0x3f: "2-tone encode",
341
    #~ 0x40: "Monitor A: open mommentary",
342
    #~ 0x41: "Monitor B: Open Toggle",
343
    #~ 0x42: "Monitor C: Carrier mommentary",
344
    #~ 0x43: "Monitor D: Carrier toogle",
345
    #~ 0x44: "Operator selectable tone",
346
    #~ 0x45: "Redial",
347
    #~ 0x46: "RF Power Low",                   # portable only ?
348
    #~ 0x47: "Scan",
349
    #~ 0x48: "Scan del/add",
350
    #~ 0x4a: "GROUP down",
351
    #~ 0x4b: "GROUP up",
352
    #~ #0x4e: "Tone off (Experimental)",       # undocumented !!!!
353
    #~ 0x4f: "None",
354
    #~ 0x50: "VOL down",
355
    #~ 0x51: "VOL up",
356
    #~ 0x52: "Talk around",
357
    #~ 0x5d: "AUX",
358
    #~ 0xa1: "Channel Up/Down"                 # Knob for portables only
359
    #~ }
360

    
361

    
362
def _raw_recv(radio, amount):
363
    """Raw read from the radio device"""
364
    data = ""
365
    try:
366
        data = radio.pipe.read(amount)
367
    except:
368
        raise errors.RadioError("Error reading data from radio")
369

    
370
    # DEBUG
371
    if debug is True:
372
        LOG.debug("<== (%d) bytes:\n\n%s" % (len(data), util.hexprint(data)))
373

    
374
    return data
375

    
376

    
377
def _raw_send(radio, data):
378
    """Raw send to the radio device"""
379
    try:
380
        radio.pipe.write(data)
381
    except:
382
        raise errors.RadioError("Error sending data to radio")
383

    
384
    # DEBUG
385
    if debug is True:
386
        LOG.debug("==> (%d) bytes:\n\n%s" % (len(data), util.hexprint(data)))
387

    
388

    
389
def _close_radio(radio):
390
    """Get the radio out of program mode"""
391
    _raw_send(radio, "\x45")
392

    
393

    
394
def _checksum(data):
395
    """the radio block checksum algorithm"""
396
    cs = 0
397
    for byte in data:
398
            cs += ord(byte)
399
    return cs % 256
400

    
401

    
402
def _send(radio, frame):
403
    """Generic send data to the radio"""
404
    _raw_send(radio, frame)
405

    
406

    
407
def _make_frame(cmd, addr):
408
    """Pack the info in the format it likes"""
409
    return struct.pack(">BH", ord(cmd), addr)
410

    
411

    
412
def _handshake(radio, msg=""):
413
    """Make a full handshake"""
414
    # send ACK
415
    _raw_send(radio, ACK_CMD)
416
    # receive ACK
417
    ack = _raw_recv(radio, 1)
418
    # check ACK
419
    if ack != ACK_CMD:
420
        _close_radio(radio)
421
        mesg = "Handshake failed " + msg
422
        # DEBUG
423
        LOG.debug(mesg)
424
        raise Exception(mesg)
425

    
426

    
427
def _check_write_ack(r, ack, addr):
428
    """Process the ack from the write process
429
    this is half handshake needed in tx data block"""
430
    # all ok
431
    if ack == ACK_CMD:
432
        return True
433

    
434
    # Explicit BAD checksum
435
    if ack == "\x15":
436
        _close_radio(r)
437
        raise errors.RadioError(
438
            "Bad checksum in block %02x write" % addr)
439

    
440
    # everything else
441
    _close_radio(r)
442
    raise errors.RadioError(
443
        "Problem with the ack to block %02x write, ack %03i" %
444
        (addr, int(ack)))
445

    
446

    
447
def _recv(radio):
448
    """Receive data from the radio, 258 bytes split in (cmd, data, checksum)
449
    checking the checksum to be correct, and returning just
450
    256 bytes of data or false if short empty block"""
451
    rxdata = _raw_recv(radio, BLOCK_SIZE + 2)
452
    # when the RX block has two bytes and the first is \x5A
453
    # then the block is all \xFF
454
    if len(rxdata) == 2 and rxdata[0] == "\x5A":
455
        # just return false to flag about empty block
456
        return False
457
    elif len(rxdata) != 258:
458
        # not the amount of data we want
459
        msg = "The radio send %d bytes, we need 258" % len(rxdata)
460
        # DEBUG
461
        LOG.error(msg)
462
        raise errors.RadioError(msg)
463
    else:
464
        rcs = ord(rxdata[-1])
465
        data = rxdata[1:-1]
466
        ccs = _checksum(data)
467

    
468
        if rcs != ccs:
469
            _close_radio(radio)
470
            raise errors.RadioError(
471
                "Block Checksum Error! real %02x, calculated %02x" %
472
                (rcs, ccs))
473

    
474
        _handshake(radio, "after checksum")
475
        return data
476

    
477

    
478
def _open_radio(radio, status):
479
    """Open the radio into program mode and check if it's the correct model"""
480
    # The OEM sets the timeout to 0.550 second, we tested it and no joy.
481
    radio.pipe.baudrate = 9600
482
    radio.pipe.timeout = 0.7
483
    radio.pipe.parity = "N"
484

    
485
    # DEBUG
486
    LOG.debug("Entering program mode.")
487
    # max tries
488
    tries = 10
489

    
490
    # UI
491
    status.cur = 0
492
    status.max = tries
493
    status.msg = "Entering program mode..."
494

    
495
    # try a few times to get the radio into program mode
496
    exito = False
497
    for i in range(0, tries):
498
        # flush in pyserial 3.0 way
499
        radio.pipe.reset_input_buffer()
500
        radio.pipe.reset_output_buffer()
501
        time.sleep(0.02)
502

    
503
        # line dance between each try:
504
        radio.pipe.rts = True
505
        radio.pipe.dtr = True
506
        time.sleep(0.02)
507
        radio.pipe.dtr = False
508

    
509
        # now send the magic
510
        _raw_send(radio, "PROGRAM")
511
        ack = _raw_recv(radio, 1)
512

    
513
        if length(ack) == 0 or ack != ACK_CMD:
514
            # DEBUG
515
            LOG.debug("Try %s failed, traying again..." % i)
516
            time.sleep(0.25)
517
        else:
518
            exito = True
519
            break
520

    
521
        status.cur += 1
522
        radio.status_fn(status)
523

    
524

    
525
    if exito is False:
526
        _close_radio(radio)
527
        LOG.debug("Radio did not accepted PROGRAM command in %s atempts" % tries)
528
        raise errors.RadioError("The radio doesn't accept program mode")
529

    
530
    # DEBUG
531
    LOG.debug("Received ACK to the PROGRAM command, send ID query.")
532

    
533

    
534
    # lest's check the radio model
535
    _raw_send(radio, "\x02")
536
    rid = _raw_recv(radio, 8)
537

    
538
    if not (radio.TYPE in rid):
539
        # bad response, properly close the radio before exception
540
        _close_radio(radio)
541

    
542
        # DEBUG
543
        LOG.debug("Incorrect model ID:")
544
        LOG.debug(util.hexprint(rid))
545

    
546
        raise errors.RadioError(
547
            "Incorrect model ID, got %s, it not contains %s" %
548
            (rid.strip("\xff"), radio.TYPE))
549

    
550
    # DEBUG
551
    LOG.debug("Full ident string is:")
552
    LOG.debug(util.hexprint(rid))
553
    _handshake(radio)
554

    
555
    # alert the user of the success
556
    status.msg = "Radio ident success!"
557
    radio.status_fn(status)
558

    
559
    # this radios checks some kind of version (radio version?)
560
    _raw_send(radio, "\x50")    # 'P'
561
    ver = _raw_recv(radio, 10)
562

    
563
    # DEBUG
564
    LOG.debug("Version returned by the radios is:")
565
    LOG.debug(util.hexprint(ver))
566
    _handshake(radio)
567
    # the radio that was procesed returned this:
568
    # v2.01k.... [76 32 2E 30 31 6B EF FF FF FF]
569

    
570
    # now the OEM writes simpy "O" and get no answer...
571
    # after that we are ready to receive the radio image or to write to it
572
    _raw_send(radio, "\x4F")    # 'O'
573

    
574

    
575
def do_download(radio):
576
    """ The download function """
577
    # UI progress
578
    status = chirp_common.Status()
579
    data = ""
580
    count = 0
581

    
582
    # open the radio
583
    _open_radio(radio, status)
584

    
585
    # reset UI data
586
    status.cur = 0
587
    status.max = MEM_SIZE / BLOCK_SIZE
588
    status.msg = "Cloning from radio..."
589
    radio.status_fn(status)
590

    
591
    # set the timeout and if windows keep it bigger
592
    if sys.platform in ["win32", "cygwin"]:
593
        # bigger timeout
594
        radio.pipe.timeout = 0.55
595
    else:
596
        # Linux can keep up, MAC?
597
        radio.pipe.timeout = 0.05
598

    
599
    # DEBUG
600
    LOG.debug("Starting the download from radio")
601

    
602
    for addr in MEM_BLOCKS:
603
        # send request, but before flush the rx buffer
604
        radio.pipe.flush()
605
        _send(radio, _make_frame("R", addr))
606

    
607
        # now we get the data
608
        d = _recv(radio)
609
        # if empty block, it return false
610
        # aka we asume a empty 256 xFF block
611
        if d is False:
612
            d = EMPTY_BLOCK
613

    
614
        data += d
615

    
616
        # UI Update
617
        status.cur = count
618
        radio.status_fn(status)
619

    
620
        count += 1
621

    
622
    _close_radio(radio)
623
    return memmap.MemoryMap(data)
624

    
625

    
626
def do_upload(radio):
627
    """ The upload function """
628

    
629
    # DISABLED in this pre-alpha state_close_radio(radio)
630
    _close_radio(radio)
631
    raise errors.RadioError("No upload possible yet, this is a test driver")
632

    
633
    #~ # UI progress
634
    #~ status = chirp_common.Status()
635
    #~ data = ""
636
    #~ count = 0
637

    
638
    #~ # open the radio
639
    #~ _open_radio(radio, status)
640

    
641
    #~ # update UI
642
    #~ status.cur = 0
643
    #~ status.max = MEM_SIZE / BLOCK_SIZE
644
    #~ status.msg = "Cloning to radio..."
645
    #~ radio.status_fn(status)
646

    
647
    #~ # the default for the original soft as measured
648
    #~ radio.pipe.timeout = 0.5
649

    
650
    #~ # DEBUG
651
    #~ LOG.debug("Starting the upload to the radio")
652

    
653
    #~ count = 0
654
    #~ raddr = 0
655
    #~ for addr in MEM_BLOCKS:
656
        #~ # this is the data block to write
657
        #~ data = radio.get_mmap()[raddr:raddr+BLOCK_SIZE]
658

    
659
        #~ # The blocks from x59-x5F are NOT programmable
660
        #~ # The blocks from x11-x1F are writed only if not empty
661
        #~ if addr in RO_BLOCKS:
662
            #~ # checking if in the range of optional blocks
663
            #~ if addr >= 0x10 and addr <= 0x1F:
664
                #~ # block is empty ?
665
                #~ if data == EMPTY_BLOCK:
666
                    #~ # no write of this block
667
                    #~ # but we have to continue updating the counters
668
                    #~ count += 1
669
                    #~ raddr = count * 256
670
                    #~ continue
671
            #~ else:
672
                #~ count += 1
673
                #~ raddr = count * 256
674
                #~ continue
675

    
676
        #~ if data == EMPTY_BLOCK:
677
            #~ frame = _make_frame("Z", addr) + "\xFF"
678
        #~ else:
679
            #~ cs = _checksum(data)
680
            #~ frame = _make_frame("W", addr) + data + chr(cs)
681

    
682
        #~ _send(radio, frame)
683

    
684
        #~ # get the ACK
685
        #~ ack = _raw_recv(radio, 1)
686
        #~ _check_write_ack(radio, ack, addr)
687

    
688
        #~ # DEBUG
689
        #~ LOG.debug("Sending block %02x" % addr)
690

    
691
        #~ # UI Update
692
        #~ status.cur = count
693
        #~ radio.status_fn(status)
694

    
695
        #~ count += 1
696
        #~ raddr = count * 256
697

    
698
    #~ _close_radio(radio)
699

    
700

    
701
def model_match(cls, data):
702
    """Match the opened/downloaded image to the correct version"""
703
    rid = data[0xA7:0xAE]
704
    if (rid in cls.VARIANTS):
705
        # correct model
706
        return True
707
    else:
708
        return False
709

    
710

    
711
class Kenwood60GBankModel(chirp_common.BankModel):
712
    """Testing the bank model on kennwood"""
713
    channelAlwaysHasBank = True
714

    
715
    def get_num_mappings(self):
716
        return self._radio._num_banks
717

    
718
    def get_mappings(self):
719
        banks = []
720
        for i in range(0, self._radio._num_banks):
721
            bindex = i + 1
722
            bank = self._radio._bclass(self, i, "%03i" % bindex)
723
            bank.index = i
724
            banks.append(bank)
725
        return banks
726

    
727
    def add_memory_to_mapping(self, memory, bank):
728
        self._radio._set_bank(memory.number, bank.index)
729

    
730
    def remove_memory_from_mapping(self, memory, bank):
731
        if self._radio._get_bank(memory.number) != bank.index:
732
            raise Exception("Memory %i not in bank %s. Cannot remove." %
733
                            (memory.number, bank))
734

    
735
        # We can't "Remove" it for good
736
        # the kenwood paradigm don't allow it
737
        # instead we move it to bank 0
738
        self._radio._set_bank(memory.number, 0)
739

    
740
    def get_mapping_memories(self, bank):
741
        memories = []
742
        for i in range(0, self._radio._upper):
743
            if self._radio._get_bank(i) == bank.index:
744
                memories.append(self._radio.get_memory(i))
745
        return memories
746

    
747
    def get_memory_mappings(self, memory):
748
        index = self._radio._get_bank(memory.number)
749
        return [self.get_mappings()[index]]
750

    
751

    
752
class memBank(chirp_common.Bank):
753
    """A bank model for kenwood"""
754
    # Integral index of the bank (not to be confused with per-memory
755
    # bank indexes
756
    index = 0
757

    
758

    
759
class Kenwood_Serie_80(chirp_common.CloneModeRadio):
760
    """Kenwood Serie 80 Radios base class"""
761
    VENDOR = "Kenwood"
762
    BAUD_RATE = 9600
763
    _memsize = MEM_SIZE
764
    NAME_LENGTH = 8
765
    _range = [136000000, 174000000]
766
    _upper = 250
767
    _chs_progs = 0
768
    _num_banks = 250
769
    _bclass = memBank
770
    _kind = ""
771
    VARIANT = ""
772
    MODEL = ""
773

    
774
    @classmethod
775
    def get_prompts(cls):
776
        rp = chirp_common.RadioPrompts()
777
        rp.experimental = \
778
            ('This driver is experimental and for personal use only.'
779
             'It has a limited set of features, but the most used.'
780
             ''
781
             'The most notorius missing features are this:'
782
             '=> PTT ID, in fact it is disabled if detected'
783
             '=> Priority / Home channel'
784
             '=> Bank names'
785
             '=> Others'
786
             ''
787
             'If you need one of this, get your official software to do it'
788
             'and raise and issue on the chirp site about it and maybe'
789
             'it will be implemented in the future.'
790
             ''
791
             'The band limits on some of this radios are set here far from'
792
             'the vendor limits to cover most of the ham bands. It is a'
793
             'known fact that this radios can work safely outside the OEM'
794
             'limits, but each radio is different, you may find in trouble'
795
             'with some particular radios, but this is also possible with'
796
             'the OEM software; as usual YMMV.'
797
             ''
798
             'A detail: this driver is slow reading from the radio in'
799
             'Windows, due to the way windows serial works.'
800
             )
801
        rp.pre_download = _(dedent("""\
802
            Follow this instructions to download your info:
803
            1 - Turn off your radio
804
            2 - Connect your interface cable
805
            3 - Turn on your radio (unblock it if password protected)
806
            4 - Do the download of your radio data
807
            """))
808
        rp.pre_upload = _(dedent("""\
809
            Follow this instructions to upload your info:
810
            1 - Turn off your radio
811
            2 - Connect your interface cable
812
            3 - Turn on your radio (unblock it if password protected)
813
            4 - Do the upload of your radio data
814
            """))
815
        return rp
816

    
817
    def get_features(self):
818
        """Return information about this radio's features"""
819
        rf = chirp_common.RadioFeatures()
820
        rf.has_settings = True
821
        rf.has_bank = True
822
        rf.has_tuning_step = False
823
        rf.has_name = True
824
        rf.has_offset = True
825
        rf.has_mode = True
826
        rf.has_dtcs = True
827
        rf.has_rx_dtcs = True
828
        rf.has_dtcs_polarity = True
829
        rf.has_ctone = True
830
        rf.has_cross = True
831
        rf.valid_modes = MODES
832
        rf.valid_duplexes = ["", "-", "+", "off"]
833
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
834
        rf.valid_cross_modes = [
835
            "Tone->Tone",
836
            "DTCS->",
837
            "->DTCS",
838
            "Tone->DTCS",
839
            "DTCS->Tone",
840
            "->Tone",
841
            "DTCS->DTCS"]
842
        rf.valid_power_levels = POWER_LEVELS
843
        rf.valid_characters = VALID_CHARS
844
        rf.valid_skips = SKIP_VALUES
845
        rf.valid_dtcs_codes = DTCS_CODES
846
        rf.valid_bands = [self._range]
847
        rf.valid_name_length = 8
848
        rf.memory_bounds = (1, self._upper)
849
        return rf
850

    
851
    def _fill(self, offset, data):
852
        """Fill an specified area of the memmap with the passed data"""
853
        for addr in range(0, len(data)):
854
            self._mmap[offset + addr] = data[addr]
855

    
856
    def _prep_data(self):
857
        """Prepare the areas in the memmap to do a consistend write
858
        it has to make an update on the x300 area with banks and channel
859
        info; other in the x1000 with banks and channel counts
860
        and a last one in x7000 with flag data"""
861
        rchs = 0
862
        data = dict()
863

    
864
        # sorting the data
865
        for ch in range(0, self._upper):
866
            mem = self._memobj.memory[ch]
867
            bnumb = int(mem.bnumb)
868
            bank = int(mem.bank)
869
            if bnumb != 255 and (bank != 255 and bank != 0):
870
                try:
871
                    data[bank].append(ch)
872
                except:
873
                    data[bank] = list()
874
                    data[bank].append(ch)
875
                data[bank].sort()
876
                # counting the real channels
877
                rchs = rchs + 1
878

    
879
        # updating the channel/bank count
880
        self._memobj.settings.channels = rchs
881
        self._chs_progs = rchs
882
        self._memobj.settings.banks = len(data)
883

    
884
        # building the data for the memmap
885
        fdata = ""
886

    
887
        for k, v in data.iteritems():
888
            # posible bad data
889
            if k == 0:
890
                k = 1
891
                raise errors.InvalidValueError(
892
                    "Invalid bank value '%k', bad data in the image? \
893
                    Triying to fix this, review your bank data!" % k)
894
            c = 1
895
            for i in v:
896
                fdata += chr(k) + chr(c) + chr(k - 1) + chr(i)
897
                c = c + 1
898

    
899
        # fill to match a full 256 bytes block
900
        fdata += (len(fdata) % 256) * "\xFF"
901

    
902
        # updating the data in the memmap [x300]
903
        self._fill(0x300, fdata)
904

    
905
        # update the info in x1000; it has 2 bytes with
906
        # x00 = bank , x01 = bank's channel count
907
        # the rest of the 14 bytes are \xff
908
        bdata = ""
909
        for i in range(1, len(data) + 1):
910
            line = chr(i) + chr(len(data[i]))
911
            line += "\xff" * 14
912
            bdata += line
913

    
914
        # fill to match a full 256 bytes block
915
        bdata += (256 - (len(bdata)) % 256) * "\xFF"
916

    
917
        # fill to match the whole area
918
        bdata += (16 - len(bdata) / 256) * EMPTY_BLOCK
919

    
920
        # updating the data in the memmap [x1000]
921
        self._fill(0x1000, bdata)
922

    
923
        # DTMF id for each channel, 5 bytes lbcd at x7000
924
        # ############## TODO ###################
925
        fldata = "\x00\xf0\xff\xff\xff" * self._chs_progs + \
926
            "\xff" * (5 * (self._upper - self._chs_progs))
927

    
928
        # write it
929
        # updating the data in the memmap [x7000]
930
        self._fill(0x7000, fldata)
931

    
932
    def _set_variant(self):
933
        """Select and set the correct variables for the class acording
934
        to the correct variant of the radio"""
935
        rid = self._mmap[0xA7:0xAE]
936

    
937
        # indentify the radio variant and set the enviroment to it's values
938
        try:
939
            self._upper, low, high, self._kind = self.VARIANTS[rid]
940
            self._range = [low * 1000000, high * 1000000]
941

    
942
            # setting the bank data in the features, 8 & 16 CH dont have banks
943
            if self._upper < 32:
944
                rf = chirp_common.RadioFeatures()
945
                rf.has_bank = False
946

    
947
            # put the VARIANT in the class, clean the model / CHs / Type
948
            # in the same layout as the KPG program
949
            self._VARIANT = self.MODEL + " [" + str(self._upper) + "CH]: "
950
            self._VARIANT += self._kind + ", " + str(self._range[0]/1000000) + "-"
951
            self._VARIANT += str(self._range[1]/1000000) + " Mhz"
952

    
953
        except KeyError:
954
            LOG.debug("Wrong Kenwood radio, ID or unknown variant")
955
            LOG.debug(util.hexprint(rid))
956
            raise errors.RadioError(
957
                "Wrong Kenwood radio, ID or unknown variant, see LOG output.")
958
            return False
959

    
960
    def sync_in(self):
961
        """Do a download of the radio eeprom"""
962
        self._mmap = do_download(self)
963
        self.process_mmap()
964

    
965
    def sync_out(self):
966
        """Do an upload to the radio eeprom"""
967

    
968
        # chirp signature on the eprom ;-)
969
        sign = "Chirp"
970
        self._fill(0xbb, sign)
971

    
972
        try:
973
            self._prep_data()
974
            do_upload(self)
975
        except errors.RadioError:
976
            raise
977
        except Exception, e:
978
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
979

    
980
    def process_mmap(self):
981
        """Process the memory object"""
982
        # how many channels are programed
983
        self._chs_progs = ord(self._mmap[15])
984

    
985
        # load the memobj
986
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
987

    
988
        # to set the vars on the class to the correct ones
989
        self._set_variant()
990

    
991
    def get_raw_memory(self, number):
992
        """Return a raw representation of the memory object, which
993
        is very helpful for development"""
994
        return repr(self._memobj.memory[number])
995

    
996
    def _decode_tone(self, val):
997
        """Parse the tone data to decode from mem, it returns:
998
        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
999
        val = int(val)
1000
        if val == 65535:
1001
            return '', None, None
1002
        elif val >= 0x2800:
1003
            code = int("%03o" % (val & 0x07FF))
1004
            pol = (val & 0x8000) and "R" or "N"
1005
            return 'DTCS', code, pol
1006
        else:
1007
            a = val / 10.0
1008
            return 'Tone', a, None
1009

    
1010
    def _encode_tone(self, memval, mode, value, pol):
1011
        """Parse the tone data to encode from UI to mem"""
1012
        if mode == '':
1013
            memval.set_raw("\xff\xff")
1014
        elif mode == 'Tone':
1015
            memval.set_value(int(value * 10))
1016
        elif mode == 'DTCS':
1017
            val = int("%i" % value, 8) + 0x2800
1018
            if pol == "R":
1019
                val += 0xA000
1020
            memval.set_value(val)
1021
        else:
1022
            raise Exception("Internal error: invalid mode `%s'" % mode)
1023

    
1024
    def _get_scan(self, chan):
1025
        """Get the channel scan status from the 16 bytes array on the eeprom
1026
        then from the bits on the byte, return '' or 'S' as needed"""
1027
        result = "S"
1028
        byte = int(chan/8)
1029
        bit = chan % 8
1030
        res = self._memobj.settings.add[byte] & (pow(2, bit))
1031
        if res > 0:
1032
            result = ""
1033

    
1034
        return result
1035

    
1036
    def _set_scan(self, chan, value):
1037
        """Set the channel scan status from UI to the mem_map"""
1038
        byte = int(chan/8)
1039
        bit = chan % 8
1040

    
1041
        # get the actual value to see if I need to change anything
1042
        actual = self._get_scan(chan)
1043
        if actual != value:
1044
            # I have to flip the value
1045
            rbyte = self._memobj.settings.add[byte]
1046
            rbyte = rbyte ^ pow(2, bit)
1047
            self._memobj.settings.add[byte] = rbyte
1048

    
1049
    def get_memory(self, number):
1050
        # Get a low-level memory object mapped to the image
1051
        _mem = self._memobj.memory[number - 1]
1052

    
1053
        # Create a high-level memory object to return to the UI
1054
        mem = chirp_common.Memory()
1055

    
1056
        # Memory number
1057
        mem.number = number
1058

    
1059
        # this radio has a setting about the amount of real chans of the 128
1060
        # olso in the channel has xff on the Rx freq it's empty
1061
        if (number > (self._chs_progs + 1)) or (_mem.get_raw()[0] == "\xFF"):
1062
            mem.empty = True
1063
            # but is not enough, you have to crear the memory in the mmap
1064
            # to get it ready for the sync_out process
1065
            _mem.set_raw("\xFF" * 48)
1066
            return mem
1067

    
1068
        # Freq and offset
1069
        mem.freq = int(_mem.rxfreq) * 10
1070
        # tx freq can be blank
1071
        if _mem.get_raw()[16] == "\xFF":
1072
            # TX freq not set
1073
            mem.offset = 0
1074
            mem.duplex = "off"
1075
        else:
1076
            # TX feq set
1077
            offset = (int(_mem.txfreq) * 10) - mem.freq
1078
            if offset < 0:
1079
                mem.offset = abs(offset)
1080
                mem.duplex = "-"
1081
            elif offset > 0:
1082
                mem.offset = offset
1083
                mem.duplex = "+"
1084
            else:
1085
                mem.offset = 0
1086

    
1087
        # name TAG of the channel
1088
        mem.name = str(_mem.name).rstrip()
1089

    
1090
        # power
1091
        mem.power = POWER_LEVELS[_mem.power]
1092

    
1093
        # wide/marrow
1094
        mem.mode = MODES[_mem.wide]
1095

    
1096
        # skip
1097
        mem.skip = self._get_scan(number - 1)
1098

    
1099
        # tone data
1100
        rxtone = txtone = None
1101
        txtone = self._decode_tone(_mem.tx_tone)
1102
        rxtone = self._decode_tone(_mem.rx_tone)
1103
        chirp_common.split_tone_decode(mem, txtone, rxtone)
1104

    
1105
        # Extra
1106
        # bank and number in the channel
1107
        mem.extra = RadioSettingGroup("extra", "Extra")
1108

    
1109
        # validate bank
1110
        b = int(_mem.bank)
1111
        if b > 127 or b == 0:
1112
            _mem.bank = b = 1
1113

    
1114
        bank = RadioSetting("bank", "Bank it belongs",
1115
                            RadioSettingValueInteger(1, 128, b))
1116
        mem.extra.append(bank)
1117

    
1118
        # validate bnumb
1119
        if int(_mem.bnumb) > 127:
1120
            _mem.bank = mem.number
1121

    
1122
        bnumb = RadioSetting("bnumb", "Ch number in the bank",
1123
                             RadioSettingValueInteger(0, 127, _mem.bnumb))
1124
        mem.extra.append(bnumb)
1125

    
1126
        bs = RadioSetting("beat_shift", "Beat shift",
1127
                          RadioSettingValueBoolean(
1128
                              not bool(_mem.beat_shift)))
1129
        mem.extra.append(bs)
1130

    
1131
        cp = RadioSetting("compander", "Compander",
1132
                          RadioSettingValueBoolean(
1133
                              not bool(_mem.compander)))
1134
        mem.extra.append(cp)
1135

    
1136
        bl = RadioSetting("busy_lock", "Busy Channel lock",
1137
                          RadioSettingValueBoolean(
1138
                              not bool(_mem.busy_lock)))
1139
        mem.extra.append(bl)
1140

    
1141
        return mem
1142

    
1143
    def set_memory(self, mem):
1144
        """Set the memory data in the eeprom img from the UI
1145
        not ready yet, so it will return as is"""
1146

    
1147
        # get the eprom representation of this channel
1148
        _mem = self._memobj.memory[mem.number - 1]
1149

    
1150
        # if empty memmory
1151
        if mem.empty:
1152
            _mem.set_raw("\xFF" * 48)
1153
            return
1154

    
1155
        # frequency
1156
        _mem.rxfreq = mem.freq / 10
1157

    
1158
        # this are a mistery yet, but so falr there is no impact
1159
        # whit this default values for new channels
1160
        if int(_mem.rx_unkw) == 0xff:
1161
            _mem.rx_unkw = 0x35
1162
            _mem.tx_unkw = 0x32
1163

    
1164
        # duplex
1165
        if mem.duplex == "+":
1166
            _mem.txfreq = (mem.freq + mem.offset) / 10
1167
        elif mem.duplex == "-":
1168
            _mem.txfreq = (mem.freq - mem.offset) / 10
1169
        elif mem.duplex == "off":
1170
            for byte in _mem.txfreq:
1171
                byte.set_raw("\xFF")
1172
        else:
1173
            _mem.txfreq = mem.freq / 10
1174

    
1175
        # tone data
1176
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
1177
            chirp_common.split_tone_encode(mem)
1178
        self._encode_tone(_mem.tx_tone, txmode, txtone, txpol)
1179
        self._encode_tone(_mem.rx_tone, rxmode, rxtone, rxpol)
1180

    
1181
        # name TAG of the channel
1182
        _namelength = self.get_features().valid_name_length
1183
        for i in range(_namelength):
1184
            try:
1185
                _mem.name[i] = mem.name[i]
1186
            except IndexError:
1187
                _mem.name[i] = "\x20"
1188

    
1189
        # power
1190
        # default power is low
1191
        if mem.power is None:
1192
            mem.power = POWER_LEVELS[0]
1193

    
1194
        _mem.power = POWER_LEVELS.index(mem.power)
1195

    
1196
        # wide/marrow
1197
        _mem.wide = MODES.index(mem.mode)
1198

    
1199
        # scan add property
1200
        self._set_scan(mem.number - 1, mem.skip)
1201

    
1202
        # bank and number in the channel
1203
        if int(_mem.bnumb) == 0xff:
1204
            _mem.bnumb = mem.number - 1
1205
            _mem.bank = 1
1206

    
1207
        # extra settings
1208
        for setting in mem.extra:
1209
            if setting != "bank" or setting != "bnumb":
1210
                setattr(_mem, setting.get_name(), not bool(setting.value))
1211

    
1212
        # all data get sync after channel mod
1213
        self._prep_data()
1214

    
1215
        return mem
1216

    
1217
    @classmethod
1218
    def match_model(cls, filedata, filename):
1219
        match_size = False
1220
        match_model = False
1221

    
1222
        # testing the file data size
1223
        if len(filedata) == MEM_SIZE:
1224
            match_size = True
1225

    
1226
        # testing the firmware model fingerprint
1227
        match_model = model_match(cls, filedata)
1228

    
1229
        if match_size and match_model:
1230
            return True
1231
        else:
1232
            return False
1233

    
1234
    def get_settings(self):
1235
        """Translate the bit in the mem_struct into settings in the UI"""
1236
        sett = self._memobj.settings
1237
        mess = self._memobj.message
1238
        keys = self._memobj.keys
1239
        idm = self._memobj.id
1240
        passwd = self._memobj.passwords
1241

    
1242
        # basic features of the radio
1243
        basic = RadioSettingGroup("basic", "Basic Settings")
1244
        # dealer settings
1245
        dealer = RadioSettingGroup("dealer", "Dealer Settings")
1246
        # buttons
1247
        fkeys = RadioSettingGroup("keys", "Front keys config")
1248

    
1249
        # TODO / PLANED
1250
        # adjust feqs
1251
        #freqs = RadioSettingGroup("freqs", "Adjust Frequencies")
1252

    
1253
        top = RadioSettings(basic, dealer, fkeys)
1254

    
1255
        # Basic
1256
        tot = RadioSetting("settings.tot", "Time Out Timer (TOT)",
1257
                           RadioSettingValueList(TOT, TOT[
1258
                           TOT.index(str(int(sett.tot)))]))
1259
        basic.append(tot)
1260

    
1261
        totalert = RadioSetting("settings.tot_alert", "TOT pre alert",
1262
                                RadioSettingValueList(TOT_PRE,
1263
                                TOT_PRE[int(sett.tot_alert)]))
1264
        basic.append(totalert)
1265

    
1266
        totrekey = RadioSetting("settings.tot_rekey", "TOT re-key time",
1267
                                RadioSettingValueList(TOT_REKEY,
1268
                                TOT_REKEY[int(sett.tot_rekey)]))
1269
        basic.append(totrekey)
1270

    
1271
        totreset = RadioSetting("settings.tot_reset", "TOT reset time",
1272
                                RadioSettingValueList(TOT_RESET,
1273
                                TOT_RESET[int(sett.tot_reset)]))
1274
        basic.append(totreset)
1275

    
1276
        # this feature is for mobile only
1277
        if self.TYPE[0] == "M":
1278
            minvol = RadioSetting("settings.min_vol", "Minimum volume",
1279
                                  RadioSettingValueList(VOL,
1280
                                  VOL[int(sett.min_vol)]))
1281
            basic.append(minvol)
1282

    
1283
            tv = int(sett.tone_vol)
1284
            if tv == 255:
1285
                tv = 32
1286
            tvol = RadioSetting("settings.tone_vol", "Minimum tone volume",
1287
                                RadioSettingValueList(TVOL, TVOL[tv]))
1288
            basic.append(tvol)
1289

    
1290
        sql = RadioSetting("settings.sql_level", "SQL Ref Level",
1291
                           RadioSettingValueList(
1292
                           SQL, SQL[int(sett.sql_level)]))
1293
        basic.append(sql)
1294

    
1295
        #c2t = RadioSetting("settings.c2t", "Clear to Transpond",
1296
                           #RadioSettingValueBoolean(not sett.c2t))
1297
        #basic.append(c2t)
1298

    
1299
        ptone = RadioSetting("settings.poweron_tone", "Power On tone",
1300
                             RadioSettingValueBoolean(sett.poweron_tone))
1301
        basic.append(ptone)
1302

    
1303
        ctone = RadioSetting("settings.control_tone", "Control (key) tone",
1304
                             RadioSettingValueBoolean(sett.control_tone))
1305
        basic.append(ctone)
1306

    
1307
        wtone = RadioSetting("settings.warn_tone", "Warning tone",
1308
                             RadioSettingValueBoolean(sett.warn_tone))
1309
        basic.append(wtone)
1310

    
1311
        # Save Battery only for portables?
1312
        if self.TYPE[0] == "P":
1313
            bs = int(sett.battery_save) == 0x32 and True or False
1314
            bsave = RadioSetting("settings.battery_save", "Battery Saver",
1315
                                 RadioSettingValueBoolean(bs))
1316
            basic.append(bsave)
1317

    
1318
        ponm = str(sett.poweronmesg).strip("\xff")
1319
        pom = RadioSetting("settings.poweronmesg", "Power on message",
1320
                           RadioSettingValueString(0, 8, ponm, False))
1321
        basic.append(pom)
1322

    
1323
        # dealer
1324
        mstr = ""
1325
        valid_chars = [32, 44, 45, 47, 58, 91, 93] + range(48, 58) + \
1326
            range(65, 91) + range(97, 123)
1327

    
1328
        for i in range(0, len(self._VARIANT)):
1329
            if ord(self._VARIANT[i]) in valid_chars:
1330
                mstr += self._VARIANT[i]
1331

    
1332
        val = RadioSettingValueString(0, 35, mstr)
1333
        val.set_mutable(False)
1334
        mod = RadioSetting("not.mod", "Radio Version", val)
1335
        dealer.append(mod)
1336

    
1337
        sn = str(idm.serial).strip(" \xff")
1338
        val = RadioSettingValueString(0, 8, sn)
1339
        val.set_mutable(False)
1340
        serial = RadioSetting("not.serial", "Serial number", val)
1341
        dealer.append(serial)
1342

    
1343
        svp = str(sett.lastsoftversion).strip(" \xff")
1344
        val = RadioSettingValueString(0, 5, svp)
1345
        val.set_mutable(False)
1346
        sver = RadioSetting("not.softver", "Software Version", val)
1347
        dealer.append(sver)
1348

    
1349
        l1 = str(mess.line1).strip(" \xff")
1350
        line1 = RadioSetting("message.line1", "Comment 1",
1351
                           RadioSettingValueString(0, 32, l1))
1352
        dealer.append(line1)
1353

    
1354
        l2 = str(mess.line2).strip(" \xff")
1355
        line2 = RadioSetting("message.line2", "Comment 2",
1356
                             RadioSettingValueString(0, 32, l2))
1357
        dealer.append(line2)
1358

    
1359
        sprog = RadioSetting("settings.self_prog", "Self program",
1360
                             RadioSettingValueBoolean(sett.self_prog))
1361
        dealer.append(sprog)
1362

    
1363
        clone = RadioSetting("settings.clone", "Allow clone",
1364
                             RadioSettingValueBoolean(sett.clone))
1365
        dealer.append(clone)
1366

    
1367
        panel = RadioSetting("settings.panel_test", "Panel Test",
1368
                             RadioSettingValueBoolean(sett.panel_test))
1369
        dealer.append(panel)
1370

    
1371
        fmw = RadioSetting("settings.firmware_prog", "Firmware program",
1372
                           RadioSettingValueBoolean(sett.firmware_prog))
1373
        dealer.append(fmw)
1374

    
1375
        # front keys
1376
        # The Mobile only parameters are wraped here
1377
        if self.TYPE[0] == "M":
1378
            vu = RadioSetting("keys.kVOL_UP", "VOL UP",
1379
                              RadioSettingValueList(KEYS.values(),
1380
                              KEYS.values()[KEYS.keys().index(
1381
                                  int(keys.kVOL_UP))]))
1382
            fkeys.append(vu)
1383

    
1384
            vd = RadioSetting("keys.kVOL_DOWN", "VOL DOWN",
1385
                              RadioSettingValueList(KEYS.values(),
1386
                              KEYS.values()[KEYS.keys().index(
1387
                                  int(keys.kVOL_DOWN))]))
1388
            fkeys.append(vd)
1389

    
1390
            chu = RadioSetting("keys.kCH_UP", "CH UP",
1391
                               RadioSettingValueList(KEYS.values(),
1392
                               KEYS.values()[KEYS.keys().index(
1393
                                   int(keys.kCH_UP))]))
1394
            fkeys.append(chu)
1395

    
1396
            chd = RadioSetting("keys.kCH_DOWN", "CH DOWN",
1397
                               RadioSettingValueList(KEYS.values(),
1398
                               KEYS.values()[KEYS.keys().index(
1399
                                   int(keys.kCH_DOWN))]))
1400
            fkeys.append(chd)
1401

    
1402
            foot = RadioSetting("keys.kFOOT", "Foot switch",
1403
                               RadioSettingValueList(KEYS.values(),
1404
                               KEYS.values()[KEYS.keys().index(
1405
                                   int(keys.kCH_DOWN))]))
1406
            fkeys.append(foot)
1407

    
1408
        # this is the common buttons for all
1409

    
1410
        # 260G model don't have the front keys
1411
        if not "P2600" in self.TYPE:
1412
            scn_name = "SCN"
1413
            if self.TYPE[0] == "P":
1414
                scn_name = "Open Circle"
1415

    
1416
            scn = RadioSetting("keys.kSCN", scn_name,
1417
                               RadioSettingValueList(KEYS.values(),
1418
                               KEYS.values()[KEYS.keys().index(
1419
                                   int(keys.kSCN))]))
1420
            fkeys.append(scn)
1421

    
1422
            a_name = "A"
1423
            if self.TYPE[0] == "P":
1424
                a_name = "Closed circle"
1425

    
1426
            a = RadioSetting("keys.kA", a_name,
1427
                             RadioSettingValueList(KEYS.values(),
1428
                             KEYS.values()[KEYS.keys().index(
1429
                                 int(keys.kA))]))
1430
            fkeys.append(a)
1431

    
1432
            da_name = "D/A"
1433
            if self.TYPE[0] == "P":
1434
                da_name = "< key"
1435

    
1436
            da = RadioSetting("keys.kDA", da_name,
1437
                              RadioSettingValueList(KEYS.values(),
1438
                              KEYS.values()[KEYS.keys().index(
1439
                                  int(keys.kDA))]))
1440
            fkeys.append(da)
1441

    
1442
            gu_name = "Triangle up"
1443
            if self.TYPE[0] == "P":
1444
                gu_name = "Side 1"
1445

    
1446
            gu = RadioSetting("keys.kGROUP_UP", gu_name,
1447
                              RadioSettingValueList(KEYS.values(),
1448
                              KEYS.values()[KEYS.keys().index(
1449
                                  int(keys.kGROUP_UP))]))
1450
            fkeys.append(gu)
1451

    
1452
        # Side keys on portables
1453
        gd_name = "Triangle Down"
1454
        if self.TYPE[0] == "P":
1455
            gd_name = "> key"
1456

    
1457
        gd = RadioSetting("keys.kGROUP_DOWN", gd_name,
1458
                          RadioSettingValueList(KEYS.values(),
1459
                          KEYS.values()[KEYS.keys().index(
1460
                              int(keys.kGROUP_DOWN))]))
1461
        fkeys.append(gd)
1462

    
1463
        mon_name = "MON"
1464
        if self.TYPE[0] == "P":
1465
            mon_name = "Side 2"
1466

    
1467
        mon = RadioSetting("keys.kMON", mon_name,
1468
                           RadioSettingValueList(KEYS.values(),
1469
                           KEYS.values()[KEYS.keys().index(
1470
                               int(keys.kMON))]))
1471
        fkeys.append(mon)
1472

    
1473
        return top
1474

    
1475
    def set_settings(self, settings):
1476
        """Translate the settings in the UI into bit in the mem_struct
1477
        I don't understand well the method used in many drivers
1478
        so, I used mine, ugly but works ok"""
1479

    
1480
        mobj = self._memobj
1481

    
1482
        for element in settings:
1483
            if not isinstance(element, RadioSetting):
1484
                self.set_settings(element)
1485
                continue
1486

    
1487
            # Let's roll the ball
1488
            if "." in element.get_name():
1489
                inter, setting = element.get_name().split(".")
1490
                # you must ignore the settings with "not"
1491
                # this are READ ONLY attributes
1492
                if inter == "not":
1493
                    continue
1494

    
1495
                obj = getattr(mobj, inter)
1496
                value = element.value
1497

    
1498
                # integers case + special case
1499
                if setting in ["tot", "tot_alert", "min_vol", "tone_vol",
1500
                               "sql_level", "tot_rekey", "tot_reset"]:
1501
                    # catching the "off" values as zero
1502
                    try:
1503
                        value = int(value)
1504
                    except:
1505
                        value = 0
1506

    
1507
                    # tot case step 15
1508
                    if setting == "tot":
1509
                        value = value * 15
1510
                        # off is special
1511
                        if value == 0:
1512
                            value = 0x4b0
1513

    
1514
                    # Caso tone_vol
1515
                    if setting == "tone_vol":
1516
                        # off is special
1517
                        if value == 32:
1518
                            value = 0xff
1519

    
1520
                # Bool types + inverted
1521
                if setting in ["c2t", "poweron_tone", "control_tone",
1522
                               "warn_tone", "battery_save", "self_prog",
1523
                               "clone", "panel_test"]:
1524
                    value = bool(value)
1525

    
1526
                    # this cases are inverted
1527
                    if setting == "c2t":
1528
                        value = not value
1529

    
1530
                    # case battery save is special
1531
                    if setting == "battery_save":
1532
                        if bool(value) is True:
1533
                            value = 0x32
1534
                        else:
1535
                            value = 0xff
1536

    
1537
                # String cases
1538
                if setting in ["poweronmesg", "line1", "line2"]:
1539
                    # some vars
1540
                    value = str(value)
1541
                    just = 8
1542
                    # lines with 32
1543
                    if "line" in setting:
1544
                        just = 32
1545

    
1546
                    # empty case
1547
                    if len(value) == 0:
1548
                        value = "\xff" * just
1549
                    else:
1550
                        value = value.ljust(just)
1551

    
1552
                # case keys, with special config
1553
                if inter == "keys":
1554
                    value = KEYS.keys()[KEYS.values().index(str(value))]
1555

    
1556
            # Apply al configs done
1557
            setattr(obj, setting, value)
1558

    
1559
    def get_bank_model(self):
1560
        """Pass the bank model to the UI part"""
1561
        rf = self.get_features()
1562
        if rf.has_bank is True:
1563
            return Kenwood60GBankModel(self)
1564
        else:
1565
            return None
1566

    
1567
    def _get_bank(self, loc):
1568
        """Get the bank data for a specific channel"""
1569
        mem = self._memobj.memory[loc - 1]
1570
        bank = int(mem.bank) - 1
1571

    
1572
        if bank > self._num_banks or bank < 1:
1573
            # all channels must belong to a bank, even with just 1 bank
1574
            return 0
1575
        else:
1576
            return bank
1577

    
1578
    def _set_bank(self, loc, bank):
1579
        """Set the bank data for a specific channel"""
1580
        try:
1581
            b = int(bank)
1582
            if b > 127:
1583
                b = 0
1584
            mem = self._memobj.memory[loc - 1]
1585
            mem.bank = b + 1
1586
        except:
1587
            msg = "You can't have a channel without a bank, click another bank"
1588
            raise errors.InvalidDataError(msg)
1589

    
1590

    
1591

    
1592
@directory.register
1593
class TK868G_Radios(Kenwood_Serie_80):
1594
    """Kenwood TK-280 Radio """
1595
    MODEL = "TK-280"
1596
    TYPE = "P0280"
1597
    VARIANTS = {
1598
        "P0280\x05\xDF\xF1":    (280, 136, 184, "Test model to build the class"),
1599
        }
(5-5/20)