Project

General

Profile

New Model #4395 » tk981.py

Thomas P, 03/13/2023 12:06 PM

 
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 = "E"
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 len(ack) == 0 or ack != ACK_CMD:
514
            # DEBUG
515
            #LOG.debug(ack % i)
516
            LOG.debug("Try %s failed, traying again..." % i)
517
            time.sleep(0.25)
518
        else:
519
            exito = True
520
            break
521

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

    
525

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

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

    
534

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

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

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

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

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

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

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

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

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

    
575

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

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

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

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

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

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

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

    
615
        data += d
616

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

    
621
        count += 1
622

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

    
626

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

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

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

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

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

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

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

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

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

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

    
683
        #~ _send(radio, frame)
684

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

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

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

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

    
699
    #~ _close_radio(radio)
700

    
701

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

    
711

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

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

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

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

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

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

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

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

    
752

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

    
759

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1035
        return result
1036

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

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

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

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

    
1057
        # Memory number
1058
        mem.number = number
1059

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1142
        return mem
1143

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1216
        return mem
1217

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1409
        # this is the common buttons for all
1410

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

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

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

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

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

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

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

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

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

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

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

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

    
1474
        return top
1475

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

    
1481
        mobj = self._memobj
1482

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1591

    
1592

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