Project

General

Profile

New Model #4395 » tk981.py

Thomas P, 03/19/2023 03:42 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 = True
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")     #"E"
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: " + util.hexprint(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

    
454
    if not rxdata:
455
        raise errors.RadioError('No response from radio')
456
    #elif len(rxdata) < 258:    #ignoring smaller blocks while testing 981
457

    
458
    # when the RX block has two bytes and the first is #\x5A 'Z'
459
    # then the block is all \xFF
460
    elif len(rxdata) == 2 and rxdata[0] == "\x5a":
461
        # "Z"
462
        ## just return false to flag about empty block
463
        _handshake(radio, "after zero block")
464
        return b'\xff' * BLOCK_SIZE
465
        #return False
466
    elif rxdata[0] != b'W':
467
        # 57
468
        LOG.error('Got non-W command:')
469
        LOG.error(util.hexprint(rxdata))
470
        # raise errors.RadioError('Received unexpected response from radio')
471
        return False
472
    elif len(rxdata) != 258:
473
        _close_radio(radio)
474
        # not the amount of data we want
475
        msg = "The radio send %d bytes, we need 258" % len(rxdata)
476
        # DEBUG
477
        LOG.error(msg)
478
        LOG.debug(rxdata)
479
        raise errors.RadioError(msg)
480
    else:
481
        rcs = ord(rxdata[-1])
482
        data = rxdata[1:-1]
483
        ccs = _checksum(data)
484
	#LOG.debug("data: " + util.hexprint(rxdata))
485
        if rcs != ccs:
486
            _close_radio(radio)
487
            raise errors.RadioError(
488
                "Block Checksum Error! real %02x, calculated %02x" %
489
                (rcs, ccs))
490
	
491
        _handshake(radio, "after checksum")
492
        return data
493

    
494
def _open_radio(radio, status):
495
    """Open the radio into program mode and check if it's the correct model"""
496
    # The OEM sets the timeout to 0.550 second, we tested it and no joy.
497
    radio.pipe.baudrate = 9600
498
    radio.pipe.timeout = 0.7    # Originally 0.7, 1 in 760G radio
499
    radio.pipe.parity = "E"   # 'E' and 'O' work similarly but 'N' doesn't on 981
500

    
501
    # DEBUG
502
    LOG.debug("Entering program mode.")
503
    # max tries
504
    tries = 10
505

    
506
    # UI
507
    status.cur = 0
508
    status.max = tries
509
    status.msg = "Entering program mode..."
510

    
511
    # try a few times to get the radio into program mode
512
    exito = False
513
    for i in range(0, tries):
514
        # flush in pyserial 3.0 way
515
        #radio.pipe.reset_input_buffer()
516
        #radio.pipe.reset_output_buffer()
517
        time.sleep(0.02)
518

    
519
        # line dance between each try:
520
        radio.pipe.rts = True
521
        radio.pipe.dtr = True
522
        time.sleep(0.02)
523
        radio.pipe.dtr = False
524

    
525
        # now send the magic
526
        _raw_send(radio, "PROGRAM")
527
        ack = _raw_recv(radio, 1)
528

    
529
        if len(ack) == 0 or ack != ACK_CMD:
530
            # DEBUG
531
            #LOG.debug(ack % i)
532
            LOG.debug("Try %s failed, traying again..." % i)
533
            time.sleep(0.25)
534
        else:
535
            exito = True
536
            break
537

    
538
        status.cur += 1
539
        radio.status_fn(status)
540

    
541

    
542
    if exito is False:
543
        _close_radio(radio)
544
        LOG.debug("Radio did not accepted PROGRAM command in %s atempts" % tries)
545
        raise errors.RadioError("The radio doesn't accept program mode")
546

    
547
    # DEBUG
548
    LOG.debug("Received ACK to the PROGRAM command, send ID query.")
549

    
550

    
551
    # lest's check the radio model
552
    _raw_send(radio, "\x02")
553
    rid = _raw_recv(radio, 8)
554

    
555
    if not (radio.TYPE in rid):
556
        # bad response, properly close the radio before exception
557
        _close_radio(radio)
558

    
559
        # DEBUG
560
        LOG.debug("Incorrect model ID:")
561
        LOG.debug(util.hexprint(rid))
562

    
563
        raise errors.RadioError(
564
            "Incorrect model ID, got %s, it not contains %s" %
565
            (rid.strip("\xff"), radio.TYPE))
566

    
567
    # DEBUG
568
    LOG.debug("Full ident string is:")
569
    LOG.debug(util.hexprint(rid))
570
    _handshake(radio)
571

    
572
    # alert the user of the success
573
    status.msg = "Radio ident success!"
574
    radio.status_fn(status)
575

    
576
    # this radios checks some kind of version (radio version?)
577
    _raw_send(radio, "\x50")    # 'P'
578
    ver = _raw_recv(radio, 10)
579

    
580
    # DEBUG
581
    LOG.debug("Version returned by the radios is:")
582
    LOG.debug(util.hexprint(ver))
583
    _handshake(radio)
584
    # the radio that was procesed returned this:
585
    # v2.01k.... [76 32 2E 30 31 6B EF FF FF FF]
586

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

    
591

    
592
def do_download(radio):
593
    """ The download function """
594
    # UI progress
595
    status = chirp_common.Status()
596
    data = ""
597
    count = 0
598

    
599
    # open the radio
600
    _open_radio(radio, status)
601

    
602
    # reset UI data
603
    status.cur = 0
604
    status.max = MEM_SIZE / BLOCK_SIZE
605
    status.msg = "Cloning from radio..."
606
    radio.status_fn(status)
607

    
608
    # set the timeout and if windows keep it bigger
609
    if sys.platform in ["win32", "cygwin"]:
610
        # bigger timeout
611
        radio.pipe.timeout = 0.55
612
    else:
613
        # Linux can keep up, MAC?
614
        radio.pipe.timeout = 0.55     #changed from 0.05 for testing
615

    
616
    # DEBUG
617
    LOG.debug("Starting the download from radio")
618

    
619
    for addr in MEM_BLOCKS:
620
        # send request, but before flush the rx buffer
621
        radio.pipe.flush()
622
        _send(radio, _make_frame("R", addr))
623
        time.sleep(0.1)
624
        # now we get the data
625
        d = _recv(radio)
626
        # if empty block, it return false
627
        # aka we asume a empty 256 xFF block
628
        if d is False:
629
            d = EMPTY_BLOCK
630

    
631
        data += d
632
        # UI Update
633
        status.cur = count
634
        radio.status_fn(status)
635

    
636
        count += 1
637

    
638
    _close_radio(radio)
639
    return memmap.MemoryMap(data)
640

    
641

    
642
def do_upload(radio):
643
    """ The upload function """
644

    
645
    # DISABLED in this pre-alpha state_close_radio(radio)
646
    _close_radio(radio)
647
    raise errors.RadioError("No upload possible yet, this is a test driver")
648

    
649
    #~ # UI progress
650
    #~ status = chirp_common.Status()
651
    #~ data = ""
652
    #~ count = 0
653

    
654
    #~ # open the radio
655
    #~ _open_radio(radio, status)
656

    
657
    #~ # update UI
658
    #~ status.cur = 0
659
    #~ status.max = MEM_SIZE / BLOCK_SIZE
660
    #~ status.msg = "Cloning to radio..."
661
    #~ radio.status_fn(status)
662

    
663
    #~ # the default for the original soft as measured
664
    #~ radio.pipe.timeout = 0.5
665

    
666
    #~ # DEBUG
667
    #~ LOG.debug("Starting the upload to the radio")
668

    
669
    #~ count = 0
670
    #~ raddr = 0
671
    #~ for addr in MEM_BLOCKS:
672
        #~ # this is the data block to write
673
        #~ data = radio.get_mmap()[raddr:raddr+BLOCK_SIZE]
674

    
675
        #~ # The blocks from x59-x5F are NOT programmable
676
        #~ # The blocks from x11-x1F are writed only if not empty
677
        #~ if addr in RO_BLOCKS:
678
            #~ # checking if in the range of optional blocks
679
            #~ if addr >= 0x10 and addr <= 0x1F:
680
                #~ # block is empty ?
681
                #~ if data == EMPTY_BLOCK:
682
                    #~ # no write of this block
683
                    #~ # but we have to continue updating the counters
684
                    #~ count += 1
685
                    #~ raddr = count * 256
686
                    #~ continue
687
            #~ else:
688
                #~ count += 1
689
                #~ raddr = count * 256
690
                #~ continue
691

    
692
        #~ if data == EMPTY_BLOCK:
693
            #~ frame = _make_frame("Z", addr) + "\xFF"
694
        #~ else:
695
            #~ cs = _checksum(data)
696
            #~ frame = _make_frame("W", addr) + data + chr(cs)
697

    
698
        #~ _send(radio, frame)
699

    
700
        #~ # get the ACK
701
        #~ ack = _raw_recv(radio, 1)
702
        #~ _check_write_ack(radio, ack, addr)
703

    
704
        #~ # DEBUG
705
        #~ LOG.debug("Sending block %02x" % addr)
706

    
707
        #~ # UI Update
708
        #~ status.cur = count
709
        #~ radio.status_fn(status)
710

    
711
        #~ count += 1
712
        #~ raddr = count * 256
713

    
714
    #~ _close_radio(radio)
715

    
716

    
717
def model_match(cls, data):
718
    """Match the opened/downloaded image to the correct version"""
719
    rid = data[0xA7:0xAE]
720
    LOG.debug("model_match: " + util.hexprint(rid))
721
    if (rid in cls.VARIANTS):
722
        # correct model
723
        LOG.debug("Model_match successful")
724
        return True
725
    else:
726
        LOG.error("Model_match fail")
727
        return False
728

    
729

    
730
class Kenwood60GBankModel(chirp_common.BankModel):
731
    """Testing the bank model on kennwood"""
732
    channelAlwaysHasBank = True
733

    
734
    def get_num_mappings(self):
735
        return self._radio._num_banks
736

    
737
    def get_mappings(self):
738
        banks = []
739
        for i in range(0, self._radio._num_banks):
740
            bindex = i + 1
741
            bank = self._radio._bclass(self, i, "%03i" % bindex)
742
            bank.index = i
743
            banks.append(bank)
744
        return banks
745

    
746
    def add_memory_to_mapping(self, memory, bank):
747
        self._radio._set_bank(memory.number, bank.index)
748

    
749
    def remove_memory_from_mapping(self, memory, bank):
750
        if self._radio._get_bank(memory.number) != bank.index:
751
            raise Exception("Memory %i not in bank %s. Cannot remove." %
752
                            (memory.number, bank))
753

    
754
        # We can't "Remove" it for good
755
        # the kenwood paradigm don't allow it
756
        # instead we move it to bank 0
757
        self._radio._set_bank(memory.number, 0)
758

    
759
    def get_mapping_memories(self, bank):
760
        memories = []
761
        for i in range(0, self._radio._upper):
762
            if self._radio._get_bank(i) == bank.index:
763
                memories.append(self._radio.get_memory(i))
764
        return memories
765

    
766
    def get_memory_mappings(self, memory):
767
        index = self._radio._get_bank(memory.number)
768
        return [self.get_mappings()[index]]
769

    
770

    
771
class memBank(chirp_common.Bank):
772
    """A bank model for kenwood"""
773
    # Integral index of the bank (not to be confused with per-memory
774
    # bank indexes
775
    index = 0
776

    
777

    
778
class Kenwood_Serie_80(chirp_common.CloneModeRadio):
779
    """Kenwood Serie 80 Radios base class"""
780
    VENDOR = "Kenwood"
781
    BAUD_RATE = 9600
782
    _memsize = MEM_SIZE
783
    NAME_LENGTH = 8
784
    _range = [902000000, 941000000]
785
    _upper = 128         #originally 250
786
    _chs_progs = 0
787
    _num_banks = 128     #originally 250
788
    _bclass = memBank
789
    _kind = ""
790
    VARIANT = ""
791
    MODEL = ""
792

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

    
836
    def get_features(self):
837
        """Return information about this radio's features"""
838
        rf = chirp_common.RadioFeatures()
839
        rf.has_settings = True
840
        rf.has_bank = True
841
        rf.has_tuning_step = False
842
        rf.has_name = True
843
        rf.has_offset = True
844
        rf.has_mode = True
845
        rf.has_dtcs = True
846
        rf.has_rx_dtcs = True
847
        rf.has_dtcs_polarity = True
848
        rf.has_ctone = True
849
        rf.has_cross = True
850
        rf.valid_modes = MODES
851
        rf.valid_duplexes = ["", "-", "+", "off"]
852
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
853
        rf.valid_cross_modes = [
854
            "Tone->Tone",
855
            "DTCS->",
856
            "->DTCS",
857
            "Tone->DTCS",
858
            "DTCS->Tone",
859
            "->Tone",
860
            "DTCS->DTCS"]
861
        rf.valid_power_levels = POWER_LEVELS
862
        rf.valid_characters = VALID_CHARS
863
        rf.valid_skips = SKIP_VALUES
864
        rf.valid_dtcs_codes = DTCS_CODES
865
        rf.valid_bands = [self._range]
866
        rf.valid_name_length = 8
867
        rf.memory_bounds = (1, self._upper)
868
        return rf
869

    
870
    def _fill(self, offset, data):
871
        """Fill an specified area of the memmap with the passed data"""
872
        for addr in range(0, len(data)):
873
            self._mmap[offset + addr] = data[addr]
874

    
875
    def _prep_data(self):
876
        """Prepare the areas in the memmap to do a consistend write
877
        it has to make an update on the x300 area with banks and channel
878
        info; other in the x1000 with banks and channel counts
879
        and a last one in x7000 with flag data"""
880
        rchs = 0
881
        data = dict()
882

    
883
        # sorting the data
884
        for ch in range(0, self._upper):
885
            mem = self._memobj.memory[ch]
886
            bnumb = int(mem.bnumb)
887
            bank = int(mem.bank)
888
            if bnumb != 255 and (bank != 255 and bank != 0):
889
                try:
890
                    data[bank].append(ch)
891
                except:
892
                    data[bank] = list()
893
                    data[bank].append(ch)
894
                data[bank].sort()
895
                # counting the real channels
896
                rchs = rchs + 1
897

    
898
        # updating the channel/bank count
899
        self._memobj.settings.channels = rchs
900
        self._chs_progs = rchs
901
        self._memobj.settings.banks = len(data)
902

    
903
        # building the data for the memmap
904
        fdata = ""
905

    
906
        for k, v in data.iteritems():
907
            # posible bad data
908
            if k == 0:
909
                k = 1
910
                raise errors.InvalidValueError(
911
                    "Invalid bank value '%k', bad data in the image? \
912
                    Triying to fix this, review your bank data!" % k)
913
            c = 1
914
            for i in v:
915
                fdata += chr(k) + chr(c) + chr(k - 1) + chr(i)
916
                c = c + 1
917

    
918
        # fill to match a full 256 bytes block
919
        fdata += (len(fdata) % 256) * "\xFF"
920

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

    
924
        # update the info in x1000; it has 2 bytes with
925
        # x00 = bank , x01 = bank's channel count
926
        # the rest of the 14 bytes are \xff
927
        bdata = ""
928
        for i in range(1, len(data) + 1):
929
            line = chr(i) + chr(len(data[i]))
930
            line += "\xff" * 14
931
            bdata += line
932

    
933
        # fill to match a full 256 bytes block
934
        bdata += (256 - (len(bdata)) % 256) * "\xFF"
935

    
936
        # fill to match the whole area
937
        bdata += (16 - len(bdata) / 256) * EMPTY_BLOCK
938

    
939
        # updating the data in the memmap [x1000]
940
        self._fill(0x1000, bdata)
941

    
942
        # DTMF id for each channel, 5 bytes lbcd at x7000
943
        # ############## TODO ###################
944
        fldata = "\x00\xf0\xff\xff\xff" * self._chs_progs + \
945
            "\xff" * (5 * (self._upper - self._chs_progs))
946

    
947
        # write it
948
        # updating the data in the memmap [x7000]
949
        self._fill(0x7000, fldata)
950

    
951
    def _set_variant(self):
952
        """Select and set the correct variables for the class acording
953
        to the correct variant of the radio"""
954
        rid = self._mmap[0xA7:0xAE]
955

    
956
        # indentify the radio variant and set the enviroment to it's values
957
        try:
958
            self._upper, low, high, self._kind = self.VARIANTS[rid]
959
            self._range = [low * 1000000, high * 1000000]
960

    
961
            # setting the bank data in the features, 8 & 16 CH dont have banks
962
            if self._upper < 32:
963
                rf = chirp_common.RadioFeatures()
964
                rf.has_bank = False
965

    
966
            # put the VARIANT in the class, clean the model / CHs / Type
967
            # in the same layout as the KPG program
968
            self._VARIANT = self.MODEL + " [" + str(self._upper) + "CH]: "
969
            self._VARIANT += self._kind + ", " + str(self._range[0]/1000000) + "-"
970
            self._VARIANT += str(self._range[1]/1000000) + " Mhz"
971

    
972
        except KeyError:
973
            LOG.debug("Wrong Kenwood radio, ID or unknown variant")
974
            LOG.debug(util.hexprint(rid))
975
            #LOG.debug(self._VARIANT)
976
            raise errors.RadioError(
977
                "Wrong Kenwood radio, ID or unknown variant, see LOG output.")
978
            return False
979

    
980
    def sync_in(self):
981
        """Do a download of the radio eeprom"""
982
        self._mmap = do_download(self)
983
        self.process_mmap()
984

    
985
    def sync_out(self):
986
        """Do an upload to the radio eeprom"""
987

    
988
        # chirp signature on the eprom ;-)
989
        sign = "Chirp"
990
        self._fill(0xbb, sign)
991

    
992
        try:
993
            self._prep_data()
994
            do_upload(self)
995
        except errors.RadioError:
996
            raise
997
        except Exception, e:
998
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
999

    
1000
    def process_mmap(self):
1001
        """Process the memory object"""
1002
        # how many channels are programed
1003
        self._chs_progs = ord(self._mmap[15])
1004

    
1005
        # load the memobj
1006
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
1007

    
1008
        # to set the vars on the class to the correct ones
1009
        self._set_variant()
1010

    
1011
    def get_raw_memory(self, number):
1012
        """Return a raw representation of the memory object, which
1013
        is very helpful for development"""
1014
        return repr(self._memobj.memory[number])
1015

    
1016
    def _decode_tone(self, val):
1017
        """Parse the tone data to decode from mem, it returns:
1018
        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
1019
        val = int(val)
1020
        if val == 65535:
1021
            return '', None, None
1022
        elif val >= 0x2800:
1023
            code = int("%03o" % (val & 0x07FF))
1024
            pol = (val & 0x8000) and "R" or "N"
1025
            return 'DTCS', code, pol
1026
        else:
1027
            a = val / 10.0
1028
            return 'Tone', a, None
1029

    
1030
    def _encode_tone(self, memval, mode, value, pol):
1031
        """Parse the tone data to encode from UI to mem"""
1032
        if mode == '':
1033
            memval.set_raw("\xff\xff")
1034
        elif mode == 'Tone':
1035
            memval.set_value(int(value * 10))
1036
        elif mode == 'DTCS':
1037
            val = int("%i" % value, 8) + 0x2800
1038
            if pol == "R":
1039
                val += 0xA000
1040
            memval.set_value(val)
1041
        else:
1042
            raise Exception("Internal error: invalid mode `%s'" % mode)
1043

    
1044
    def _get_scan(self, chan):
1045
        """Get the channel scan status from the 16 bytes array on the eeprom
1046
        then from the bits on the byte, return '' or 'S' as needed"""
1047
        result = "S"
1048
        byte = int(chan/8)
1049
        bit = chan % 8
1050
        res = self._memobj.settings.add[byte] & (pow(2, bit))
1051
        if res > 0:
1052
            result = ""
1053

    
1054
        return result
1055

    
1056
    def _set_scan(self, chan, value):
1057
        """Set the channel scan status from UI to the mem_map"""
1058
        byte = int(chan/8)
1059
        bit = chan % 8
1060

    
1061
        # get the actual value to see if I need to change anything
1062
        actual = self._get_scan(chan)
1063
        if actual != value:
1064
            # I have to flip the value
1065
            rbyte = self._memobj.settings.add[byte]
1066
            rbyte = rbyte ^ pow(2, bit)
1067
            self._memobj.settings.add[byte] = rbyte
1068

    
1069
    def get_memory(self, number):
1070
        # Get a low-level memory object mapped to the image
1071
        _mem = self._memobj.memory[number - 1]
1072

    
1073
        # Create a high-level memory object to return to the UI
1074
        mem = chirp_common.Memory()
1075

    
1076
        # Memory number
1077
        mem.number = number
1078

    
1079
        # this radio has a setting about the amount of real chans of the 128
1080
        # olso in the channel has xff on the Rx freq it's empty
1081
        if (number > (self._chs_progs + 1)) or (_mem.get_raw()[0] == "\xFF"):
1082
            mem.empty = True
1083
            # but is not enough, you have to crear the memory in the mmap
1084
            # to get it ready for the sync_out process
1085
            _mem.set_raw("\xFF" * 48)
1086
            return mem
1087

    
1088
        # Freq and offset
1089
        mem.freq = int(_mem.rxfreq) * 10
1090
        # tx freq can be blank
1091
        if _mem.get_raw()[16] == "\xFF":
1092
            # TX freq not set
1093
            mem.offset = 0
1094
            mem.duplex = "off"
1095
        else:
1096
            # TX feq set
1097
            offset = (int(_mem.txfreq) * 10) - mem.freq
1098
            if offset < 0:
1099
                mem.offset = abs(offset)
1100
                mem.duplex = "-"
1101
            elif offset > 0:
1102
                mem.offset = offset
1103
                mem.duplex = "+"
1104
            else:
1105
                mem.offset = 0
1106

    
1107
        # name TAG of the channel
1108
        mem.name = str(_mem.name).rstrip()
1109

    
1110
        # power
1111
        mem.power = POWER_LEVELS[_mem.power]
1112

    
1113
        # wide/marrow
1114
        mem.mode = MODES[_mem.wide]
1115

    
1116
        # skip
1117
        mem.skip = self._get_scan(number - 1)
1118

    
1119
        # tone data
1120
        rxtone = txtone = None
1121
        txtone = self._decode_tone(_mem.tx_tone)
1122
        rxtone = self._decode_tone(_mem.rx_tone)
1123
        chirp_common.split_tone_decode(mem, txtone, rxtone)
1124

    
1125
        # Extra
1126
        # bank and number in the channel
1127
        mem.extra = RadioSettingGroup("extra", "Extra")
1128

    
1129
        # validate bank
1130
        b = int(_mem.bank)
1131
        if b > 127 or b == 0:
1132
            _mem.bank = b = 1
1133

    
1134
        bank = RadioSetting("bank", "Bank it belongs",
1135
                            RadioSettingValueInteger(1, 128, b))
1136
        mem.extra.append(bank)
1137

    
1138
        # validate bnumb
1139
        if int(_mem.bnumb) > 127:
1140
            _mem.bank = mem.number
1141

    
1142
        bnumb = RadioSetting("bnumb", "Ch number in the bank",
1143
                             RadioSettingValueInteger(0, 127, _mem.bnumb))
1144
        mem.extra.append(bnumb)
1145

    
1146
        bs = RadioSetting("beat_shift", "Beat shift",
1147
                          RadioSettingValueBoolean(
1148
                              not bool(_mem.beat_shift)))
1149
        mem.extra.append(bs)
1150

    
1151
        cp = RadioSetting("compander", "Compander",
1152
                          RadioSettingValueBoolean(
1153
                              not bool(_mem.compander)))
1154
        mem.extra.append(cp)
1155

    
1156
        bl = RadioSetting("busy_lock", "Busy Channel lock",
1157
                          RadioSettingValueBoolean(
1158
                              not bool(_mem.busy_lock)))
1159
        mem.extra.append(bl)
1160

    
1161
        return mem
1162

    
1163
    def set_memory(self, mem):
1164
        """Set the memory data in the eeprom img from the UI
1165
        not ready yet, so it will return as is"""
1166

    
1167
        # get the eprom representation of this channel
1168
        _mem = self._memobj.memory[mem.number - 1]
1169

    
1170
        # if empty memmory
1171
        if mem.empty:
1172
            _mem.set_raw("\xFF" * 48)
1173
            return
1174

    
1175
        # frequency
1176
        _mem.rxfreq = mem.freq / 10
1177

    
1178
        # this are a mistery yet, but so falr there is no impact
1179
        # whit this default values for new channels
1180
        if int(_mem.rx_unkw) == 0xff:
1181
            _mem.rx_unkw = 0x35
1182
            _mem.tx_unkw = 0x32
1183

    
1184
        # duplex
1185
        if mem.duplex == "+":
1186
            _mem.txfreq = (mem.freq + mem.offset) / 10
1187
        elif mem.duplex == "-":
1188
            _mem.txfreq = (mem.freq - mem.offset) / 10
1189
        elif mem.duplex == "off":
1190
            for byte in _mem.txfreq:
1191
                byte.set_raw("\xFF")
1192
        else:
1193
            _mem.txfreq = mem.freq / 10
1194

    
1195
        # tone data
1196
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
1197
            chirp_common.split_tone_encode(mem)
1198
        self._encode_tone(_mem.tx_tone, txmode, txtone, txpol)
1199
        self._encode_tone(_mem.rx_tone, rxmode, rxtone, rxpol)
1200

    
1201
        # name TAG of the channel
1202
        _namelength = self.get_features().valid_name_length
1203
        for i in range(_namelength):
1204
            try:
1205
                _mem.name[i] = mem.name[i]
1206
            except IndexError:
1207
                _mem.name[i] = "\x20"
1208

    
1209
        # power
1210
        # default power is low
1211
        if mem.power is None:
1212
            mem.power = POWER_LEVELS[0]
1213

    
1214
        _mem.power = POWER_LEVELS.index(mem.power)
1215

    
1216
        # wide/marrow
1217
        _mem.wide = MODES.index(mem.mode)
1218

    
1219
        # scan add property
1220
        self._set_scan(mem.number - 1, mem.skip)
1221

    
1222
        # bank and number in the channel
1223
        if int(_mem.bnumb) == 0xff:
1224
            _mem.bnumb = mem.number - 1
1225
            _mem.bank = 1
1226

    
1227
        # extra settings
1228
        for setting in mem.extra:
1229
            if setting != "bank" or setting != "bnumb":
1230
                setattr(_mem, setting.get_name(), not bool(setting.value))
1231

    
1232
        # all data get sync after channel mod
1233
        self._prep_data()
1234

    
1235
        return mem
1236

    
1237
    @classmethod
1238
    def match_model(cls, filedata, filename):
1239
        match_size = False
1240
        match_model = False
1241

    
1242
        # testing the file data size
1243
        if len(filedata) == MEM_SIZE:
1244
            match_size = True
1245

    
1246
        # testing the firmware model fingerprint
1247
        match_model = model_match(cls, filedata)
1248

    
1249
        if match_size and match_model:
1250
            return True
1251
        else:
1252
            return False
1253

    
1254
    def get_settings(self):
1255
        """Translate the bit in the mem_struct into settings in the UI"""
1256
        sett = self._memobj.settings
1257
        mess = self._memobj.message
1258
        keys = self._memobj.keys
1259
        idm = self._memobj.id
1260
        passwd = self._memobj.passwords
1261

    
1262
        # basic features of the radio
1263
        basic = RadioSettingGroup("basic", "Basic Settings")
1264
        # dealer settings
1265
        dealer = RadioSettingGroup("dealer", "Dealer Settings")
1266
        # buttons
1267
        fkeys = RadioSettingGroup("keys", "Front keys config")
1268

    
1269
        # TODO / PLANED
1270
        # adjust feqs
1271
        #freqs = RadioSettingGroup("freqs", "Adjust Frequencies")
1272

    
1273
        top = RadioSettings(basic, dealer, fkeys)
1274

    
1275
        # Basic
1276
        tot = RadioSetting("settings.tot", "Time Out Timer (TOT)",
1277
                           RadioSettingValueList(TOT, TOT[
1278
                           TOT.index(str(int(sett.tot)))]))
1279
        basic.append(tot)
1280

    
1281
        totalert = RadioSetting("settings.tot_alert", "TOT pre alert",
1282
                                RadioSettingValueList(TOT_PRE,
1283
                                TOT_PRE[int(sett.tot_alert)]))
1284
        basic.append(totalert)
1285

    
1286
        totrekey = RadioSetting("settings.tot_rekey", "TOT re-key time",
1287
                                RadioSettingValueList(TOT_REKEY,
1288
                                TOT_REKEY[int(sett.tot_rekey)]))
1289
        basic.append(totrekey)
1290

    
1291
        totreset = RadioSetting("settings.tot_reset", "TOT reset time",
1292
                                RadioSettingValueList(TOT_RESET,
1293
                                TOT_RESET[int(sett.tot_reset)]))
1294
        basic.append(totreset)
1295

    
1296
        # this feature is for mobile only
1297
        if self.TYPE[0] == "M":
1298
            minvol = RadioSetting("settings.min_vol", "Minimum volume",
1299
                                  RadioSettingValueList(VOL,
1300
                                  VOL[int(sett.min_vol)]))
1301
            basic.append(minvol)
1302

    
1303
            tv = int(sett.tone_vol)
1304
            if tv == 255:
1305
                tv = 32
1306
            tvol = RadioSetting("settings.tone_vol", "Minimum tone volume",
1307
                                RadioSettingValueList(TVOL, TVOL[tv]))
1308
            basic.append(tvol)
1309

    
1310
        sql = RadioSetting("settings.sql_level", "SQL Ref Level",
1311
                           RadioSettingValueList(
1312
                           SQL, SQL[int(sett.sql_level)]))
1313
        basic.append(sql)
1314

    
1315
        #c2t = RadioSetting("settings.c2t", "Clear to Transpond",
1316
                           #RadioSettingValueBoolean(not sett.c2t))
1317
        #basic.append(c2t)
1318

    
1319
        ptone = RadioSetting("settings.poweron_tone", "Power On tone",
1320
                             RadioSettingValueBoolean(sett.poweron_tone))
1321
        basic.append(ptone)
1322

    
1323
        ctone = RadioSetting("settings.control_tone", "Control (key) tone",
1324
                             RadioSettingValueBoolean(sett.control_tone))
1325
        basic.append(ctone)
1326

    
1327
        wtone = RadioSetting("settings.warn_tone", "Warning tone",
1328
                             RadioSettingValueBoolean(sett.warn_tone))
1329
        basic.append(wtone)
1330

    
1331
        # Save Battery only for portables?
1332
        if self.TYPE[0] == "P":
1333
            bs = int(sett.battery_save) == 0x32 and True or False
1334
            bsave = RadioSetting("settings.battery_save", "Battery Saver",
1335
                                 RadioSettingValueBoolean(bs))
1336
            basic.append(bsave)
1337

    
1338
        ponm = str(sett.poweronmesg).strip("\xff")
1339
        pom = RadioSetting("settings.poweronmesg", "Power on message",
1340
                           RadioSettingValueString(0, 8, ponm, False))
1341
        basic.append(pom)
1342

    
1343
        # dealer
1344
        mstr = ""
1345
        valid_chars = [32, 44, 45, 47, 58, 91, 93] + range(48, 58) + \
1346
            range(65, 91) + range(97, 123)
1347

    
1348
        for i in range(0, len(self._VARIANT)):
1349
            if ord(self._VARIANT[i]) in valid_chars:
1350
                mstr += self._VARIANT[i]
1351

    
1352
        val = RadioSettingValueString(0, 35, mstr)
1353
        val.set_mutable(False)
1354
        mod = RadioSetting("not.mod", "Radio Version", val)
1355
        dealer.append(mod)
1356

    
1357
        sn = str(idm.serial).strip(" \xff")
1358
        val = RadioSettingValueString(0, 8, sn)
1359
        val.set_mutable(False)
1360
        serial = RadioSetting("not.serial", "Serial number", val)
1361
        dealer.append(serial)
1362

    
1363
        svp = str(sett.lastsoftversion).strip(" \xff")
1364
        val = RadioSettingValueString(0, 5, svp)
1365
        val.set_mutable(False)
1366
        sver = RadioSetting("not.softver", "Software Version", val)
1367
        dealer.append(sver)
1368

    
1369
        l1 = str(mess.line1).strip(" \xff")
1370
        line1 = RadioSetting("message.line1", "Comment 1",
1371
                           RadioSettingValueString(0, 32, l1))
1372
        dealer.append(line1)
1373

    
1374
        l2 = str(mess.line2).strip(" \xff")
1375
        line2 = RadioSetting("message.line2", "Comment 2",
1376
                             RadioSettingValueString(0, 32, l2))
1377
        dealer.append(line2)
1378

    
1379
        sprog = RadioSetting("settings.self_prog", "Self program",
1380
                             RadioSettingValueBoolean(sett.self_prog))
1381
        dealer.append(sprog)
1382

    
1383
        clone = RadioSetting("settings.clone", "Allow clone",
1384
                             RadioSettingValueBoolean(sett.clone))
1385
        dealer.append(clone)
1386

    
1387
        panel = RadioSetting("settings.panel_test", "Panel Test",
1388
                             RadioSettingValueBoolean(sett.panel_test))
1389
        dealer.append(panel)
1390

    
1391
        fmw = RadioSetting("settings.firmware_prog", "Firmware program",
1392
                           RadioSettingValueBoolean(sett.firmware_prog))
1393
        dealer.append(fmw)
1394

    
1395
        # front keys
1396
        # The Mobile only parameters are wraped here
1397
        if self.TYPE[0] == "M":
1398
            vu = RadioSetting("keys.kVOL_UP", "VOL UP",
1399
                              RadioSettingValueList(KEYS.values(),
1400
                              KEYS.values()[KEYS.keys().index(
1401
                                  int(keys.kVOL_UP))]))
1402
            fkeys.append(vu)
1403

    
1404
            vd = RadioSetting("keys.kVOL_DOWN", "VOL DOWN",
1405
                              RadioSettingValueList(KEYS.values(),
1406
                              KEYS.values()[KEYS.keys().index(
1407
                                  int(keys.kVOL_DOWN))]))
1408
            fkeys.append(vd)
1409

    
1410
            chu = RadioSetting("keys.kCH_UP", "CH UP",
1411
                               RadioSettingValueList(KEYS.values(),
1412
                               KEYS.values()[KEYS.keys().index(
1413
                                   int(keys.kCH_UP))]))
1414
            fkeys.append(chu)
1415

    
1416
            chd = RadioSetting("keys.kCH_DOWN", "CH DOWN",
1417
                               RadioSettingValueList(KEYS.values(),
1418
                               KEYS.values()[KEYS.keys().index(
1419
                                   int(keys.kCH_DOWN))]))
1420
            fkeys.append(chd)
1421

    
1422
            foot = RadioSetting("keys.kFOOT", "Foot switch",
1423
                               RadioSettingValueList(KEYS.values(),
1424
                               KEYS.values()[KEYS.keys().index(
1425
                                   int(keys.kCH_DOWN))]))
1426
            fkeys.append(foot)
1427

    
1428
        # this is the common buttons for all
1429

    
1430
        # 260G model don't have the front keys
1431
        if not "P2600" in self.TYPE:
1432
            scn_name = "SCN"
1433
            if self.TYPE[0] == "P":
1434
                scn_name = "Open Circle"
1435

    
1436
            scn = RadioSetting("keys.kSCN", scn_name,
1437
                               RadioSettingValueList(KEYS.values(),
1438
                               KEYS.values()[KEYS.keys().index(
1439
                                   int(keys.kSCN))]))
1440
            fkeys.append(scn)
1441

    
1442
            a_name = "A"
1443
            if self.TYPE[0] == "P":
1444
                a_name = "Closed circle"
1445

    
1446
            a = RadioSetting("keys.kA", a_name,
1447
                             RadioSettingValueList(KEYS.values(),
1448
                             KEYS.values()[KEYS.keys().index(
1449
                                 int(keys.kA))]))
1450
            fkeys.append(a)
1451

    
1452
            da_name = "D/A"
1453
            if self.TYPE[0] == "P":
1454
                da_name = "< key"
1455

    
1456
            da = RadioSetting("keys.kDA", da_name,
1457
                              RadioSettingValueList(KEYS.values(),
1458
                              KEYS.values()[KEYS.keys().index(
1459
                                  int(keys.kDA))]))
1460
            fkeys.append(da)
1461

    
1462
            gu_name = "Triangle up"
1463
            if self.TYPE[0] == "P":
1464
                gu_name = "Side 1"
1465

    
1466
            gu = RadioSetting("keys.kGROUP_UP", gu_name,
1467
                              RadioSettingValueList(KEYS.values(),
1468
                              KEYS.values()[KEYS.keys().index(
1469
                                  int(keys.kGROUP_UP))]))
1470
            fkeys.append(gu)
1471

    
1472
        # Side keys on portables
1473
        gd_name = "Triangle Down"
1474
        if self.TYPE[0] == "P":
1475
            gd_name = "> key"
1476

    
1477
        gd = RadioSetting("keys.kGROUP_DOWN", gd_name,
1478
                          RadioSettingValueList(KEYS.values(),
1479
                          KEYS.values()[KEYS.keys().index(
1480
                              int(keys.kGROUP_DOWN))]))
1481
        fkeys.append(gd)
1482

    
1483
        mon_name = "MON"
1484
        if self.TYPE[0] == "P":
1485
            mon_name = "Side 2"
1486

    
1487
        mon = RadioSetting("keys.kMON", mon_name,
1488
                           RadioSettingValueList(KEYS.values(),
1489
                           KEYS.values()[KEYS.keys().index(
1490
                               int(keys.kMON))]))
1491
        fkeys.append(mon)
1492

    
1493
        return top
1494

    
1495
    def set_settings(self, settings):
1496
        """Translate the settings in the UI into bit in the mem_struct
1497
        I don't understand well the method used in many drivers
1498
        so, I used mine, ugly but works ok"""
1499

    
1500
        mobj = self._memobj
1501

    
1502
        for element in settings:
1503
            if not isinstance(element, RadioSetting):
1504
                self.set_settings(element)
1505
                continue
1506

    
1507
            # Let's roll the ball
1508
            if "." in element.get_name():
1509
                inter, setting = element.get_name().split(".")
1510
                # you must ignore the settings with "not"
1511
                # this are READ ONLY attributes
1512
                if inter == "not":
1513
                    continue
1514

    
1515
                obj = getattr(mobj, inter)
1516
                value = element.value
1517

    
1518
                # integers case + special case
1519
                if setting in ["tot", "tot_alert", "min_vol", "tone_vol",
1520
                               "sql_level", "tot_rekey", "tot_reset"]:
1521
                    # catching the "off" values as zero
1522
                    try:
1523
                        value = int(value)
1524
                    except:
1525
                        value = 0
1526

    
1527
                    # tot case step 15
1528
                    if setting == "tot":
1529
                        value = value * 15
1530
                        # off is special
1531
                        if value == 0:
1532
                            value = 0x4b0
1533

    
1534
                    # Caso tone_vol
1535
                    if setting == "tone_vol":
1536
                        # off is special
1537
                        if value == 32:
1538
                            value = 0xff
1539

    
1540
                # Bool types + inverted
1541
                if setting in ["c2t", "poweron_tone", "control_tone",
1542
                               "warn_tone", "battery_save", "self_prog",
1543
                               "clone", "panel_test"]:
1544
                    value = bool(value)
1545

    
1546
                    # this cases are inverted
1547
                    if setting == "c2t":
1548
                        value = not value
1549

    
1550
                    # case battery save is special
1551
                    if setting == "battery_save":
1552
                        if bool(value) is True:
1553
                            value = 0x32
1554
                        else:
1555
                            value = 0xff
1556

    
1557
                # String cases
1558
                if setting in ["poweronmesg", "line1", "line2"]:
1559
                    # some vars
1560
                    value = str(value)
1561
                    just = 8
1562
                    # lines with 32
1563
                    if "line" in setting:
1564
                        just = 32
1565

    
1566
                    # empty case
1567
                    if len(value) == 0:
1568
                        value = "\xff" * just
1569
                    else:
1570
                        value = value.ljust(just)
1571

    
1572
                # case keys, with special config
1573
                if inter == "keys":
1574
                    value = KEYS.keys()[KEYS.values().index(str(value))]
1575

    
1576
            # Apply al configs done
1577
            setattr(obj, setting, value)
1578

    
1579
    def get_bank_model(self):
1580
        """Pass the bank model to the UI part"""
1581
        rf = self.get_features()
1582
        if rf.has_bank is True:
1583
            return Kenwood60GBankModel(self)
1584
        else:
1585
            return None
1586

    
1587
    def _get_bank(self, loc):
1588
        """Get the bank data for a specific channel"""
1589
        mem = self._memobj.memory[loc - 1]
1590
        bank = int(mem.bank) - 1
1591

    
1592
        if bank > self._num_banks or bank < 1:
1593
            # all channels must belong to a bank, even with just 1 bank
1594
            return 0
1595
        else:
1596
            return bank
1597

    
1598
    def _set_bank(self, loc, bank):
1599
        """Set the bank data for a specific channel"""
1600
        try:
1601
            b = int(bank)
1602
            if b > 127:
1603
                b = 0
1604
            mem = self._memobj.memory[loc - 1]
1605
            mem.bank = b + 1
1606
        except:
1607
            msg = "You can't have a channel without a bank, click another bank"
1608
            raise errors.InvalidDataError(msg)
1609

    
1610

    
1611

    
1612
@directory.register
1613
class TK868G_Radios(Kenwood_Serie_80):
1614
    """Kenwood TK-981 Radio """
1615
    MODEL = "TK-981"
1616
    TYPE = "M0981"
1617
    VARIANTS = {
1618
        "M0981\x0a\xFF":    (128, 902, 941, "TK-981 test model to build the class"),
1619
        }
1620
    """  Notes on the above variables in VARIANTS
1621
    "M0981\x0a\xFF"    <--- Radio response to Ident query
1622
    (280, 136, 184, "Test model to build the class")    <------ (channels?/low freq/high freq)
1623
    """
(16-16/20)