Project

General

Profile

New Model #4395 » tk981.py

Thomas P, 03/16/2023 10:13 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 = 0x08000  # 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
        LOG.debug("ack: " + ack)
425
        raise Exception(mesg)
426

    
427

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

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

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

    
447

    
448
def _recv(radio):
449
    """Receive data from the radio, 258 bytes split in (cmd, data, checksum)
450
    checking the checksum to be correct, and returning just
451
    256 bytes of data or false if short empty block"""
452
    rxdata = _raw_recv(radio, BLOCK_SIZE + 2)
453
    # when the RX block has two bytes and the first is \x5A
454
    # then the block is all \xFF
455
    #if len(rxdata) == 2 and rxdata[0] == "\x5A":
456
    if len(rxdata) < 258:    #ignoring smaller blocks while testing 981
457
        ## just return false to flag about empty block
458
        return False
459
    elif len(rxdata) != 258:
460
        _close_radio(radio)
461
        # not the amount of data we want
462
        msg = "The radio send %d bytes, we need 258" % len(rxdata)
463
        # DEBUG
464
        LOG.error(msg)
465
        LOG.debug(rxdata)
466
        raise errors.RadioError(msg)
467
    else:
468
        rcs = ord(rxdata[-1])
469
        data = rxdata[1:-1]
470
        ccs = _checksum(data)
471
	LOG.debug("data: " + util.hexprint(rxdata))
472
	#LOG.debug("ccs %02x" % ccs)
473
        if rcs != ccs:
474
            _close_radio(radio)
475
            raise errors.RadioError(
476
                "Block Checksum Error! real %02x, calculated %02x" %
477
                (rcs, ccs))
478
	
479
        _handshake(radio, "after checksum")
480
        return data
481

    
482

    
483
def _open_radio(radio, status):
484
    """Open the radio into program mode and check if it's the correct model"""
485
    # The OEM sets the timeout to 0.550 second, we tested it and no joy.
486
    radio.pipe.baudrate = 9600
487
    radio.pipe.timeout = 0.7
488
    radio.pipe.parity = "E"   # 'E' and 'O' work similarly but 'N' doesn't on 981
489

    
490
    # DEBUG
491
    LOG.debug("Entering program mode.")
492
    # max tries
493
    tries = 10
494

    
495
    # UI
496
    status.cur = 0
497
    status.max = tries
498
    status.msg = "Entering program mode..."
499

    
500
    # try a few times to get the radio into program mode
501
    exito = False
502
    for i in range(0, tries):
503
        # flush in pyserial 3.0 way
504
        #radio.pipe.reset_input_buffer()
505
        #radio.pipe.reset_output_buffer()
506
        time.sleep(0.02)
507

    
508
        # line dance between each try:
509
        radio.pipe.rts = True
510
        radio.pipe.dtr = True
511
        time.sleep(0.02)
512
        radio.pipe.dtr = False
513

    
514
        # now send the magic
515
        _raw_send(radio, "PROGRAM")
516
        ack = _raw_recv(radio, 1)
517

    
518
        if len(ack) == 0 or ack != ACK_CMD:
519
            # DEBUG
520
            #LOG.debug(ack % i)
521
            LOG.debug("Try %s failed, traying again..." % i)
522
            time.sleep(0.25)
523
        else:
524
            exito = True
525
            break
526

    
527
        status.cur += 1
528
        radio.status_fn(status)
529

    
530

    
531
    if exito is False:
532
        _close_radio(radio)
533
        LOG.debug("Radio did not accepted PROGRAM command in %s atempts" % tries)
534
        raise errors.RadioError("The radio doesn't accept program mode")
535

    
536
    # DEBUG
537
    LOG.debug("Received ACK to the PROGRAM command, send ID query.")
538

    
539

    
540
    # lest's check the radio model
541
    _raw_send(radio, "\x02")
542
    rid = _raw_recv(radio, 8)
543

    
544
    if not (radio.TYPE in rid):
545
        # bad response, properly close the radio before exception
546
        _close_radio(radio)
547

    
548
        # DEBUG
549
        LOG.debug("Incorrect model ID:")
550
        LOG.debug(util.hexprint(rid))
551

    
552
        raise errors.RadioError(
553
            "Incorrect model ID, got %s, it not contains %s" %
554
            (rid.strip("\xff"), radio.TYPE))
555

    
556
    # DEBUG
557
    LOG.debug("Full ident string is:")
558
    LOG.debug(util.hexprint(rid))
559
    _handshake(radio)
560

    
561
    # alert the user of the success
562
    status.msg = "Radio ident success!"
563
    radio.status_fn(status)
564

    
565
    # this radios checks some kind of version (radio version?)
566
    _raw_send(radio, "\x50")    # 'P'
567
    ver = _raw_recv(radio, 10)
568

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

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

    
580

    
581
def do_download(radio):
582
    """ The download function """
583
    # UI progress
584
    status = chirp_common.Status()
585
    data = ""
586
    count = 0
587

    
588
    # open the radio
589
    _open_radio(radio, status)
590

    
591
    # reset UI data
592
    status.cur = 0
593
    status.max = MEM_SIZE / BLOCK_SIZE
594
    status.msg = "Cloning from radio..."
595
    radio.status_fn(status)
596

    
597
    # set the timeout and if windows keep it bigger
598
    if sys.platform in ["win32", "cygwin"]:
599
        # bigger timeout
600
        radio.pipe.timeout = 0.55
601
    else:
602
        # Linux can keep up, MAC?
603
        radio.pipe.timeout = 0.55     #changed from 0.05 for testing
604

    
605
    # DEBUG
606
    LOG.debug("Starting the download from radio")
607

    
608
    for addr in MEM_BLOCKS:
609
        # send request, but before flush the rx buffer
610
        radio.pipe.flush()
611
        _send(radio, _make_frame("R", addr))
612

    
613
        # now we get the data
614
        d = _recv(radio)
615
        # if empty block, it return false
616
        # aka we asume a empty 256 xFF block
617
        if d is False:
618
            d = EMPTY_BLOCK
619

    
620
        data += d
621

    
622
        # UI Update
623
        status.cur = count
624
        radio.status_fn(status)
625

    
626
        count += 1
627

    
628
    _close_radio(radio)
629
    return memmap.MemoryMap(data)
630

    
631

    
632
def do_upload(radio):
633
    """ The upload function """
634

    
635
    # DISABLED in this pre-alpha state_close_radio(radio)
636
    _close_radio(radio)
637
    raise errors.RadioError("No upload possible yet, this is a test driver")
638

    
639
    #~ # UI progress
640
    #~ status = chirp_common.Status()
641
    #~ data = ""
642
    #~ count = 0
643

    
644
    #~ # open the radio
645
    #~ _open_radio(radio, status)
646

    
647
    #~ # update UI
648
    #~ status.cur = 0
649
    #~ status.max = MEM_SIZE / BLOCK_SIZE
650
    #~ status.msg = "Cloning to radio..."
651
    #~ radio.status_fn(status)
652

    
653
    #~ # the default for the original soft as measured
654
    #~ radio.pipe.timeout = 0.5
655

    
656
    #~ # DEBUG
657
    #~ LOG.debug("Starting the upload to the radio")
658

    
659
    #~ count = 0
660
    #~ raddr = 0
661
    #~ for addr in MEM_BLOCKS:
662
        #~ # this is the data block to write
663
        #~ data = radio.get_mmap()[raddr:raddr+BLOCK_SIZE]
664

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

    
682
        #~ if data == EMPTY_BLOCK:
683
            #~ frame = _make_frame("Z", addr) + "\xFF"
684
        #~ else:
685
            #~ cs = _checksum(data)
686
            #~ frame = _make_frame("W", addr) + data + chr(cs)
687

    
688
        #~ _send(radio, frame)
689

    
690
        #~ # get the ACK
691
        #~ ack = _raw_recv(radio, 1)
692
        #~ _check_write_ack(radio, ack, addr)
693

    
694
        #~ # DEBUG
695
        #~ LOG.debug("Sending block %02x" % addr)
696

    
697
        #~ # UI Update
698
        #~ status.cur = count
699
        #~ radio.status_fn(status)
700

    
701
        #~ count += 1
702
        #~ raddr = count * 256
703

    
704
    #~ _close_radio(radio)
705

    
706

    
707
def model_match(cls, data):
708
    """Match the opened/downloaded image to the correct version"""
709
    rid = data[0xA7:0xAE]
710
    if (rid in cls.VARIANTS):
711
        # correct model
712
        return True
713
    else:
714
        return False
715

    
716

    
717
class Kenwood60GBankModel(chirp_common.BankModel):
718
    """Testing the bank model on kennwood"""
719
    channelAlwaysHasBank = True
720

    
721
    def get_num_mappings(self):
722
        return self._radio._num_banks
723

    
724
    def get_mappings(self):
725
        banks = []
726
        for i in range(0, self._radio._num_banks):
727
            bindex = i + 1
728
            bank = self._radio._bclass(self, i, "%03i" % bindex)
729
            bank.index = i
730
            banks.append(bank)
731
        return banks
732

    
733
    def add_memory_to_mapping(self, memory, bank):
734
        self._radio._set_bank(memory.number, bank.index)
735

    
736
    def remove_memory_from_mapping(self, memory, bank):
737
        if self._radio._get_bank(memory.number) != bank.index:
738
            raise Exception("Memory %i not in bank %s. Cannot remove." %
739
                            (memory.number, bank))
740

    
741
        # We can't "Remove" it for good
742
        # the kenwood paradigm don't allow it
743
        # instead we move it to bank 0
744
        self._radio._set_bank(memory.number, 0)
745

    
746
    def get_mapping_memories(self, bank):
747
        memories = []
748
        for i in range(0, self._radio._upper):
749
            if self._radio._get_bank(i) == bank.index:
750
                memories.append(self._radio.get_memory(i))
751
        return memories
752

    
753
    def get_memory_mappings(self, memory):
754
        index = self._radio._get_bank(memory.number)
755
        return [self.get_mappings()[index]]
756

    
757

    
758
class memBank(chirp_common.Bank):
759
    """A bank model for kenwood"""
760
    # Integral index of the bank (not to be confused with per-memory
761
    # bank indexes
762
    index = 0
763

    
764

    
765
class Kenwood_Serie_80(chirp_common.CloneModeRadio):
766
    """Kenwood Serie 80 Radios base class"""
767
    VENDOR = "Kenwood"
768
    BAUD_RATE = 9600
769
    _memsize = MEM_SIZE
770
    NAME_LENGTH = 8
771
    _range = [902000000, 941000000]
772
    _upper = 250
773
    _chs_progs = 0
774
    _num_banks = 250
775
    _bclass = memBank
776
    _kind = ""
777
    VARIANT = ""
778
    MODEL = ""
779

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

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

    
857
    def _fill(self, offset, data):
858
        """Fill an specified area of the memmap with the passed data"""
859
        for addr in range(0, len(data)):
860
            self._mmap[offset + addr] = data[addr]
861

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

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

    
885
        # updating the channel/bank count
886
        self._memobj.settings.channels = rchs
887
        self._chs_progs = rchs
888
        self._memobj.settings.banks = len(data)
889

    
890
        # building the data for the memmap
891
        fdata = ""
892

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

    
905
        # fill to match a full 256 bytes block
906
        fdata += (len(fdata) % 256) * "\xFF"
907

    
908
        # updating the data in the memmap [x300]
909
        self._fill(0x300, fdata)
910

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

    
920
        # fill to match a full 256 bytes block
921
        bdata += (256 - (len(bdata)) % 256) * "\xFF"
922

    
923
        # fill to match the whole area
924
        bdata += (16 - len(bdata) / 256) * EMPTY_BLOCK
925

    
926
        # updating the data in the memmap [x1000]
927
        self._fill(0x1000, bdata)
928

    
929
        # DTMF id for each channel, 5 bytes lbcd at x7000
930
        # ############## TODO ###################
931
        fldata = "\x00\xf0\xff\xff\xff" * self._chs_progs + \
932
            "\xff" * (5 * (self._upper - self._chs_progs))
933

    
934
        # write it
935
        # updating the data in the memmap [x7000]
936
        self._fill(0x7000, fldata)
937

    
938
    def _set_variant(self):
939
        """Select and set the correct variables for the class acording
940
        to the correct variant of the radio"""
941
        rid = self._mmap[0xA7:0xAE]
942

    
943
        # indentify the radio variant and set the enviroment to it's values
944
        try:
945
            self._upper, low, high, self._kind = self.VARIANTS[rid]
946
            self._range = [low * 1000000, high * 1000000]
947

    
948
            # setting the bank data in the features, 8 & 16 CH dont have banks
949
            if self._upper < 32:
950
                rf = chirp_common.RadioFeatures()
951
                rf.has_bank = False
952

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

    
959
        except KeyError:
960
            LOG.debug("Wrong Kenwood radio, ID or unknown variant")
961
            LOG.debug(util.hexprint(rid))
962
            raise errors.RadioError(
963
                "Wrong Kenwood radio, ID or unknown variant, see LOG output.")
964
            return False
965

    
966
    def sync_in(self):
967
        """Do a download of the radio eeprom"""
968
        self._mmap = do_download(self)
969
        self.process_mmap()
970

    
971
    def sync_out(self):
972
        """Do an upload to the radio eeprom"""
973

    
974
        # chirp signature on the eprom ;-)
975
        sign = "Chirp"
976
        self._fill(0xbb, sign)
977

    
978
        try:
979
            self._prep_data()
980
            do_upload(self)
981
        except errors.RadioError:
982
            raise
983
        except Exception, e:
984
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
985

    
986
    def process_mmap(self):
987
        """Process the memory object"""
988
        # how many channels are programed
989
        self._chs_progs = ord(self._mmap[15])
990

    
991
        # load the memobj
992
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
993

    
994
        # to set the vars on the class to the correct ones
995
        self._set_variant()
996

    
997
    def get_raw_memory(self, number):
998
        """Return a raw representation of the memory object, which
999
        is very helpful for development"""
1000
        return repr(self._memobj.memory[number])
1001

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

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

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

    
1040
        return result
1041

    
1042
    def _set_scan(self, chan, value):
1043
        """Set the channel scan status from UI to the mem_map"""
1044
        byte = int(chan/8)
1045
        bit = chan % 8
1046

    
1047
        # get the actual value to see if I need to change anything
1048
        actual = self._get_scan(chan)
1049
        if actual != value:
1050
            # I have to flip the value
1051
            rbyte = self._memobj.settings.add[byte]
1052
            rbyte = rbyte ^ pow(2, bit)
1053
            self._memobj.settings.add[byte] = rbyte
1054

    
1055
    def get_memory(self, number):
1056
        # Get a low-level memory object mapped to the image
1057
        _mem = self._memobj.memory[number - 1]
1058

    
1059
        # Create a high-level memory object to return to the UI
1060
        mem = chirp_common.Memory()
1061

    
1062
        # Memory number
1063
        mem.number = number
1064

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

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

    
1093
        # name TAG of the channel
1094
        mem.name = str(_mem.name).rstrip()
1095

    
1096
        # power
1097
        mem.power = POWER_LEVELS[_mem.power]
1098

    
1099
        # wide/marrow
1100
        mem.mode = MODES[_mem.wide]
1101

    
1102
        # skip
1103
        mem.skip = self._get_scan(number - 1)
1104

    
1105
        # tone data
1106
        rxtone = txtone = None
1107
        txtone = self._decode_tone(_mem.tx_tone)
1108
        rxtone = self._decode_tone(_mem.rx_tone)
1109
        chirp_common.split_tone_decode(mem, txtone, rxtone)
1110

    
1111
        # Extra
1112
        # bank and number in the channel
1113
        mem.extra = RadioSettingGroup("extra", "Extra")
1114

    
1115
        # validate bank
1116
        b = int(_mem.bank)
1117
        if b > 127 or b == 0:
1118
            _mem.bank = b = 1
1119

    
1120
        bank = RadioSetting("bank", "Bank it belongs",
1121
                            RadioSettingValueInteger(1, 128, b))
1122
        mem.extra.append(bank)
1123

    
1124
        # validate bnumb
1125
        if int(_mem.bnumb) > 127:
1126
            _mem.bank = mem.number
1127

    
1128
        bnumb = RadioSetting("bnumb", "Ch number in the bank",
1129
                             RadioSettingValueInteger(0, 127, _mem.bnumb))
1130
        mem.extra.append(bnumb)
1131

    
1132
        bs = RadioSetting("beat_shift", "Beat shift",
1133
                          RadioSettingValueBoolean(
1134
                              not bool(_mem.beat_shift)))
1135
        mem.extra.append(bs)
1136

    
1137
        cp = RadioSetting("compander", "Compander",
1138
                          RadioSettingValueBoolean(
1139
                              not bool(_mem.compander)))
1140
        mem.extra.append(cp)
1141

    
1142
        bl = RadioSetting("busy_lock", "Busy Channel lock",
1143
                          RadioSettingValueBoolean(
1144
                              not bool(_mem.busy_lock)))
1145
        mem.extra.append(bl)
1146

    
1147
        return mem
1148

    
1149
    def set_memory(self, mem):
1150
        """Set the memory data in the eeprom img from the UI
1151
        not ready yet, so it will return as is"""
1152

    
1153
        # get the eprom representation of this channel
1154
        _mem = self._memobj.memory[mem.number - 1]
1155

    
1156
        # if empty memmory
1157
        if mem.empty:
1158
            _mem.set_raw("\xFF" * 48)
1159
            return
1160

    
1161
        # frequency
1162
        _mem.rxfreq = mem.freq / 10
1163

    
1164
        # this are a mistery yet, but so falr there is no impact
1165
        # whit this default values for new channels
1166
        if int(_mem.rx_unkw) == 0xff:
1167
            _mem.rx_unkw = 0x35
1168
            _mem.tx_unkw = 0x32
1169

    
1170
        # duplex
1171
        if mem.duplex == "+":
1172
            _mem.txfreq = (mem.freq + mem.offset) / 10
1173
        elif mem.duplex == "-":
1174
            _mem.txfreq = (mem.freq - mem.offset) / 10
1175
        elif mem.duplex == "off":
1176
            for byte in _mem.txfreq:
1177
                byte.set_raw("\xFF")
1178
        else:
1179
            _mem.txfreq = mem.freq / 10
1180

    
1181
        # tone data
1182
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
1183
            chirp_common.split_tone_encode(mem)
1184
        self._encode_tone(_mem.tx_tone, txmode, txtone, txpol)
1185
        self._encode_tone(_mem.rx_tone, rxmode, rxtone, rxpol)
1186

    
1187
        # name TAG of the channel
1188
        _namelength = self.get_features().valid_name_length
1189
        for i in range(_namelength):
1190
            try:
1191
                _mem.name[i] = mem.name[i]
1192
            except IndexError:
1193
                _mem.name[i] = "\x20"
1194

    
1195
        # power
1196
        # default power is low
1197
        if mem.power is None:
1198
            mem.power = POWER_LEVELS[0]
1199

    
1200
        _mem.power = POWER_LEVELS.index(mem.power)
1201

    
1202
        # wide/marrow
1203
        _mem.wide = MODES.index(mem.mode)
1204

    
1205
        # scan add property
1206
        self._set_scan(mem.number - 1, mem.skip)
1207

    
1208
        # bank and number in the channel
1209
        if int(_mem.bnumb) == 0xff:
1210
            _mem.bnumb = mem.number - 1
1211
            _mem.bank = 1
1212

    
1213
        # extra settings
1214
        for setting in mem.extra:
1215
            if setting != "bank" or setting != "bnumb":
1216
                setattr(_mem, setting.get_name(), not bool(setting.value))
1217

    
1218
        # all data get sync after channel mod
1219
        self._prep_data()
1220

    
1221
        return mem
1222

    
1223
    @classmethod
1224
    def match_model(cls, filedata, filename):
1225
        match_size = False
1226
        match_model = False
1227

    
1228
        # testing the file data size
1229
        if len(filedata) == MEM_SIZE:
1230
            match_size = True
1231

    
1232
        # testing the firmware model fingerprint
1233
        match_model = model_match(cls, filedata)
1234

    
1235
        if match_size and match_model:
1236
            return True
1237
        else:
1238
            return False
1239

    
1240
    def get_settings(self):
1241
        """Translate the bit in the mem_struct into settings in the UI"""
1242
        sett = self._memobj.settings
1243
        mess = self._memobj.message
1244
        keys = self._memobj.keys
1245
        idm = self._memobj.id
1246
        passwd = self._memobj.passwords
1247

    
1248
        # basic features of the radio
1249
        basic = RadioSettingGroup("basic", "Basic Settings")
1250
        # dealer settings
1251
        dealer = RadioSettingGroup("dealer", "Dealer Settings")
1252
        # buttons
1253
        fkeys = RadioSettingGroup("keys", "Front keys config")
1254

    
1255
        # TODO / PLANED
1256
        # adjust feqs
1257
        #freqs = RadioSettingGroup("freqs", "Adjust Frequencies")
1258

    
1259
        top = RadioSettings(basic, dealer, fkeys)
1260

    
1261
        # Basic
1262
        tot = RadioSetting("settings.tot", "Time Out Timer (TOT)",
1263
                           RadioSettingValueList(TOT, TOT[
1264
                           TOT.index(str(int(sett.tot)))]))
1265
        basic.append(tot)
1266

    
1267
        totalert = RadioSetting("settings.tot_alert", "TOT pre alert",
1268
                                RadioSettingValueList(TOT_PRE,
1269
                                TOT_PRE[int(sett.tot_alert)]))
1270
        basic.append(totalert)
1271

    
1272
        totrekey = RadioSetting("settings.tot_rekey", "TOT re-key time",
1273
                                RadioSettingValueList(TOT_REKEY,
1274
                                TOT_REKEY[int(sett.tot_rekey)]))
1275
        basic.append(totrekey)
1276

    
1277
        totreset = RadioSetting("settings.tot_reset", "TOT reset time",
1278
                                RadioSettingValueList(TOT_RESET,
1279
                                TOT_RESET[int(sett.tot_reset)]))
1280
        basic.append(totreset)
1281

    
1282
        # this feature is for mobile only
1283
        if self.TYPE[0] == "M":
1284
            minvol = RadioSetting("settings.min_vol", "Minimum volume",
1285
                                  RadioSettingValueList(VOL,
1286
                                  VOL[int(sett.min_vol)]))
1287
            basic.append(minvol)
1288

    
1289
            tv = int(sett.tone_vol)
1290
            if tv == 255:
1291
                tv = 32
1292
            tvol = RadioSetting("settings.tone_vol", "Minimum tone volume",
1293
                                RadioSettingValueList(TVOL, TVOL[tv]))
1294
            basic.append(tvol)
1295

    
1296
        sql = RadioSetting("settings.sql_level", "SQL Ref Level",
1297
                           RadioSettingValueList(
1298
                           SQL, SQL[int(sett.sql_level)]))
1299
        basic.append(sql)
1300

    
1301
        #c2t = RadioSetting("settings.c2t", "Clear to Transpond",
1302
                           #RadioSettingValueBoolean(not sett.c2t))
1303
        #basic.append(c2t)
1304

    
1305
        ptone = RadioSetting("settings.poweron_tone", "Power On tone",
1306
                             RadioSettingValueBoolean(sett.poweron_tone))
1307
        basic.append(ptone)
1308

    
1309
        ctone = RadioSetting("settings.control_tone", "Control (key) tone",
1310
                             RadioSettingValueBoolean(sett.control_tone))
1311
        basic.append(ctone)
1312

    
1313
        wtone = RadioSetting("settings.warn_tone", "Warning tone",
1314
                             RadioSettingValueBoolean(sett.warn_tone))
1315
        basic.append(wtone)
1316

    
1317
        # Save Battery only for portables?
1318
        if self.TYPE[0] == "P":
1319
            bs = int(sett.battery_save) == 0x32 and True or False
1320
            bsave = RadioSetting("settings.battery_save", "Battery Saver",
1321
                                 RadioSettingValueBoolean(bs))
1322
            basic.append(bsave)
1323

    
1324
        ponm = str(sett.poweronmesg).strip("\xff")
1325
        pom = RadioSetting("settings.poweronmesg", "Power on message",
1326
                           RadioSettingValueString(0, 8, ponm, False))
1327
        basic.append(pom)
1328

    
1329
        # dealer
1330
        mstr = ""
1331
        valid_chars = [32, 44, 45, 47, 58, 91, 93] + range(48, 58) + \
1332
            range(65, 91) + range(97, 123)
1333

    
1334
        for i in range(0, len(self._VARIANT)):
1335
            if ord(self._VARIANT[i]) in valid_chars:
1336
                mstr += self._VARIANT[i]
1337

    
1338
        val = RadioSettingValueString(0, 35, mstr)
1339
        val.set_mutable(False)
1340
        mod = RadioSetting("not.mod", "Radio Version", val)
1341
        dealer.append(mod)
1342

    
1343
        sn = str(idm.serial).strip(" \xff")
1344
        val = RadioSettingValueString(0, 8, sn)
1345
        val.set_mutable(False)
1346
        serial = RadioSetting("not.serial", "Serial number", val)
1347
        dealer.append(serial)
1348

    
1349
        svp = str(sett.lastsoftversion).strip(" \xff")
1350
        val = RadioSettingValueString(0, 5, svp)
1351
        val.set_mutable(False)
1352
        sver = RadioSetting("not.softver", "Software Version", val)
1353
        dealer.append(sver)
1354

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

    
1360
        l2 = str(mess.line2).strip(" \xff")
1361
        line2 = RadioSetting("message.line2", "Comment 2",
1362
                             RadioSettingValueString(0, 32, l2))
1363
        dealer.append(line2)
1364

    
1365
        sprog = RadioSetting("settings.self_prog", "Self program",
1366
                             RadioSettingValueBoolean(sett.self_prog))
1367
        dealer.append(sprog)
1368

    
1369
        clone = RadioSetting("settings.clone", "Allow clone",
1370
                             RadioSettingValueBoolean(sett.clone))
1371
        dealer.append(clone)
1372

    
1373
        panel = RadioSetting("settings.panel_test", "Panel Test",
1374
                             RadioSettingValueBoolean(sett.panel_test))
1375
        dealer.append(panel)
1376

    
1377
        fmw = RadioSetting("settings.firmware_prog", "Firmware program",
1378
                           RadioSettingValueBoolean(sett.firmware_prog))
1379
        dealer.append(fmw)
1380

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

    
1390
            vd = RadioSetting("keys.kVOL_DOWN", "VOL DOWN",
1391
                              RadioSettingValueList(KEYS.values(),
1392
                              KEYS.values()[KEYS.keys().index(
1393
                                  int(keys.kVOL_DOWN))]))
1394
            fkeys.append(vd)
1395

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

    
1402
            chd = RadioSetting("keys.kCH_DOWN", "CH DOWN",
1403
                               RadioSettingValueList(KEYS.values(),
1404
                               KEYS.values()[KEYS.keys().index(
1405
                                   int(keys.kCH_DOWN))]))
1406
            fkeys.append(chd)
1407

    
1408
            foot = RadioSetting("keys.kFOOT", "Foot switch",
1409
                               RadioSettingValueList(KEYS.values(),
1410
                               KEYS.values()[KEYS.keys().index(
1411
                                   int(keys.kCH_DOWN))]))
1412
            fkeys.append(foot)
1413

    
1414
        # this is the common buttons for all
1415

    
1416
        # 260G model don't have the front keys
1417
        if not "P2600" in self.TYPE:
1418
            scn_name = "SCN"
1419
            if self.TYPE[0] == "P":
1420
                scn_name = "Open Circle"
1421

    
1422
            scn = RadioSetting("keys.kSCN", scn_name,
1423
                               RadioSettingValueList(KEYS.values(),
1424
                               KEYS.values()[KEYS.keys().index(
1425
                                   int(keys.kSCN))]))
1426
            fkeys.append(scn)
1427

    
1428
            a_name = "A"
1429
            if self.TYPE[0] == "P":
1430
                a_name = "Closed circle"
1431

    
1432
            a = RadioSetting("keys.kA", a_name,
1433
                             RadioSettingValueList(KEYS.values(),
1434
                             KEYS.values()[KEYS.keys().index(
1435
                                 int(keys.kA))]))
1436
            fkeys.append(a)
1437

    
1438
            da_name = "D/A"
1439
            if self.TYPE[0] == "P":
1440
                da_name = "< key"
1441

    
1442
            da = RadioSetting("keys.kDA", da_name,
1443
                              RadioSettingValueList(KEYS.values(),
1444
                              KEYS.values()[KEYS.keys().index(
1445
                                  int(keys.kDA))]))
1446
            fkeys.append(da)
1447

    
1448
            gu_name = "Triangle up"
1449
            if self.TYPE[0] == "P":
1450
                gu_name = "Side 1"
1451

    
1452
            gu = RadioSetting("keys.kGROUP_UP", gu_name,
1453
                              RadioSettingValueList(KEYS.values(),
1454
                              KEYS.values()[KEYS.keys().index(
1455
                                  int(keys.kGROUP_UP))]))
1456
            fkeys.append(gu)
1457

    
1458
        # Side keys on portables
1459
        gd_name = "Triangle Down"
1460
        if self.TYPE[0] == "P":
1461
            gd_name = "> key"
1462

    
1463
        gd = RadioSetting("keys.kGROUP_DOWN", gd_name,
1464
                          RadioSettingValueList(KEYS.values(),
1465
                          KEYS.values()[KEYS.keys().index(
1466
                              int(keys.kGROUP_DOWN))]))
1467
        fkeys.append(gd)
1468

    
1469
        mon_name = "MON"
1470
        if self.TYPE[0] == "P":
1471
            mon_name = "Side 2"
1472

    
1473
        mon = RadioSetting("keys.kMON", mon_name,
1474
                           RadioSettingValueList(KEYS.values(),
1475
                           KEYS.values()[KEYS.keys().index(
1476
                               int(keys.kMON))]))
1477
        fkeys.append(mon)
1478

    
1479
        return top
1480

    
1481
    def set_settings(self, settings):
1482
        """Translate the settings in the UI into bit in the mem_struct
1483
        I don't understand well the method used in many drivers
1484
        so, I used mine, ugly but works ok"""
1485

    
1486
        mobj = self._memobj
1487

    
1488
        for element in settings:
1489
            if not isinstance(element, RadioSetting):
1490
                self.set_settings(element)
1491
                continue
1492

    
1493
            # Let's roll the ball
1494
            if "." in element.get_name():
1495
                inter, setting = element.get_name().split(".")
1496
                # you must ignore the settings with "not"
1497
                # this are READ ONLY attributes
1498
                if inter == "not":
1499
                    continue
1500

    
1501
                obj = getattr(mobj, inter)
1502
                value = element.value
1503

    
1504
                # integers case + special case
1505
                if setting in ["tot", "tot_alert", "min_vol", "tone_vol",
1506
                               "sql_level", "tot_rekey", "tot_reset"]:
1507
                    # catching the "off" values as zero
1508
                    try:
1509
                        value = int(value)
1510
                    except:
1511
                        value = 0
1512

    
1513
                    # tot case step 15
1514
                    if setting == "tot":
1515
                        value = value * 15
1516
                        # off is special
1517
                        if value == 0:
1518
                            value = 0x4b0
1519

    
1520
                    # Caso tone_vol
1521
                    if setting == "tone_vol":
1522
                        # off is special
1523
                        if value == 32:
1524
                            value = 0xff
1525

    
1526
                # Bool types + inverted
1527
                if setting in ["c2t", "poweron_tone", "control_tone",
1528
                               "warn_tone", "battery_save", "self_prog",
1529
                               "clone", "panel_test"]:
1530
                    value = bool(value)
1531

    
1532
                    # this cases are inverted
1533
                    if setting == "c2t":
1534
                        value = not value
1535

    
1536
                    # case battery save is special
1537
                    if setting == "battery_save":
1538
                        if bool(value) is True:
1539
                            value = 0x32
1540
                        else:
1541
                            value = 0xff
1542

    
1543
                # String cases
1544
                if setting in ["poweronmesg", "line1", "line2"]:
1545
                    # some vars
1546
                    value = str(value)
1547
                    just = 8
1548
                    # lines with 32
1549
                    if "line" in setting:
1550
                        just = 32
1551

    
1552
                    # empty case
1553
                    if len(value) == 0:
1554
                        value = "\xff" * just
1555
                    else:
1556
                        value = value.ljust(just)
1557

    
1558
                # case keys, with special config
1559
                if inter == "keys":
1560
                    value = KEYS.keys()[KEYS.values().index(str(value))]
1561

    
1562
            # Apply al configs done
1563
            setattr(obj, setting, value)
1564

    
1565
    def get_bank_model(self):
1566
        """Pass the bank model to the UI part"""
1567
        rf = self.get_features()
1568
        if rf.has_bank is True:
1569
            return Kenwood60GBankModel(self)
1570
        else:
1571
            return None
1572

    
1573
    def _get_bank(self, loc):
1574
        """Get the bank data for a specific channel"""
1575
        mem = self._memobj.memory[loc - 1]
1576
        bank = int(mem.bank) - 1
1577

    
1578
        if bank > self._num_banks or bank < 1:
1579
            # all channels must belong to a bank, even with just 1 bank
1580
            return 0
1581
        else:
1582
            return bank
1583

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

    
1596

    
1597

    
1598
@directory.register
1599
class TK868G_Radios(Kenwood_Serie_80):
1600
    """Kenwood TK-981 Radio """
1601
    MODEL = "TK-981"
1602
    TYPE = "M0981"
1603
    VARIANTS = {
1604
        "M0981\x05\xDF\xF1":    (981, 136, 184, "Test model to build the class"),
1605
        }
(12-12/20)