Project

General

Profile

New Model #4395 » tk981.py

Added the rest of 80 series models - Thomas P, 03/20/2023 12:34 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)]   # define list of valid options
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.00k.. [76 32 2e 30 30 6b ef 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.30     #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 = 32     #was 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
            #LOG.debug(self._VARIANT)
972

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

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

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

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

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

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

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

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

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

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

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

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

    
1055
        return result
1056

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

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

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

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

    
1077
        # Memory number
1078
        mem.number = number
1079

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1162
        return mem
1163

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1236
        return mem
1237

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

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

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

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

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

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

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

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

    
1276
        # Basic
1277
        """tot = RadioSetting("settings.tot", "Time Out Timer (TOT)",
1278
                           RadioSettingValueList(TOT, TOT[
1279
                           TOT.index(str(int(sett.tot)))]))"""
1280
        tot = RadioSetting("settings.tot", "Time Out Timer (TOT)",
1281
                           RadioSettingValueList(TOT, TOT[
1282
                           TOT.index(str(int(15)))]))                   
1283
        LOG.debug(tot)
1284
        basic.append(tot)
1285

    
1286
        totalert = RadioSetting("settings.tot_alert", "TOT pre alert",
1287
                                RadioSettingValueList(TOT_PRE,
1288
                                TOT_PRE[int(sett.tot_alert)]))                     
1289
        LOG.debug(totalert)
1290
        basic.append(totalert)
1291

    
1292
        totrekey = RadioSetting("settings.tot_rekey", "TOT re-key time",
1293
                                RadioSettingValueList(TOT_REKEY,
1294
                                TOT_REKEY[int(sett.tot_rekey)]))                      
1295
        LOG.debug(totrekey)
1296
        basic.append(totrekey)
1297

    
1298
        totreset = RadioSetting("settings.tot_reset", "TOT reset time",
1299
                                RadioSettingValueList(TOT_RESET,
1300
                                TOT_RESET[int(sett.tot_reset)]))
1301
        LOG.debug(totreset)
1302
        basic.append(totreset)
1303

    
1304
        # this feature is for mobile only
1305
        if self.TYPE[0] == "M":
1306
            minvol = RadioSetting("settings.min_vol", "Minimum volume",
1307
                                  RadioSettingValueList(VOL,
1308
                                  VOL[int(sett.min_vol)]))
1309
            basic.append(minvol)
1310

    
1311
            tv = int(sett.tone_vol)
1312
            if tv == 255:
1313
                tv = 32
1314
            tvol = RadioSetting("settings.tone_vol", "Minimum tone volume",
1315
                                RadioSettingValueList(TVOL, TVOL[tv]))
1316
            basic.append(tvol)
1317

    
1318
        sql = RadioSetting("settings.sql_level", "SQL Ref Level",
1319
                           RadioSettingValueList(
1320
                           SQL, SQL[int(sett.sql_level)]))                
1321
        basic.append(sql)
1322

    
1323
        #c2t = RadioSetting("settings.c2t", "Clear to Transpond",
1324
                           #RadioSettingValueBoolean(not sett.c2t))
1325
        #basic.append(c2t)
1326

    
1327
        ptone = RadioSetting("settings.poweron_tone", "Power On tone",
1328
                             RadioSettingValueBoolean(sett.poweron_tone))
1329
        basic.append(ptone)
1330

    
1331
        ctone = RadioSetting("settings.control_tone", "Control (key) tone",
1332
                             RadioSettingValueBoolean(sett.control_tone))
1333
        basic.append(ctone)
1334

    
1335
        wtone = RadioSetting("settings.warn_tone", "Warning tone",
1336
                             RadioSettingValueBoolean(sett.warn_tone))
1337
        basic.append(wtone)
1338

    
1339
        # Save Battery only for portables?
1340
        if self.TYPE[0] == "P":
1341
            bs = int(sett.battery_save) == 0x32 and True or False
1342
            bsave = RadioSetting("settings.battery_save", "Battery Saver",
1343
                                 RadioSettingValueBoolean(bs))
1344
            basic.append(bsave)
1345

    
1346
        ponm = str(sett.poweronmesg).strip("\xff")
1347
        pom = RadioSetting("settings.poweronmesg", "Power on message",
1348
                           RadioSettingValueString(0, 8, ponm, False))
1349
        basic.append(pom)
1350

    
1351
        # dealer
1352
        mstr = ""
1353
        valid_chars = [32, 44, 45, 47, 58, 91, 93] + range(48, 58) + \
1354
            range(65, 91) + range(97, 123)
1355

    
1356
        for i in range(0, len(self._VARIANT)):
1357
            if ord(self._VARIANT[i]) in valid_chars:
1358
                mstr += self._VARIANT[i]
1359

    
1360
        val = RadioSettingValueString(0, 35, mstr)
1361
        val.set_mutable(False)
1362
        mod = RadioSetting("not.mod", "Radio Version", val)
1363
        dealer.append(mod)
1364

    
1365
        sn = str(idm.serial).strip(" \xff")
1366
        val = RadioSettingValueString(0, 8, sn)
1367
        val.set_mutable(False)
1368
        serial = RadioSetting("not.serial", "Serial number", val)
1369
        dealer.append(serial)
1370

    
1371
        svp = str(sett.lastsoftversion).strip(" \xff")
1372
        val = RadioSettingValueString(0, 5, svp)
1373
        val.set_mutable(False)
1374
        sver = RadioSetting("not.softver", "Software Version", val)
1375
        dealer.append(sver)
1376

    
1377
        l1 = str(mess.line1).strip(" \xff")
1378
        line1 = RadioSetting("message.line1", "Comment 1",
1379
                           RadioSettingValueString(0, 32, l1))
1380
        dealer.append(line1)
1381

    
1382
        l2 = str(mess.line2).strip(" \xff")
1383
        line2 = RadioSetting("message.line2", "Comment 2",
1384
                             RadioSettingValueString(0, 32, l2))
1385
        dealer.append(line2)
1386

    
1387
        sprog = RadioSetting("settings.self_prog", "Self program",
1388
                             RadioSettingValueBoolean(sett.self_prog))
1389
        dealer.append(sprog)
1390

    
1391
        clone = RadioSetting("settings.clone", "Allow clone",
1392
                             RadioSettingValueBoolean(sett.clone))
1393
        dealer.append(clone)
1394

    
1395
        panel = RadioSetting("settings.panel_test", "Panel Test",
1396
                             RadioSettingValueBoolean(sett.panel_test))
1397
        dealer.append(panel)
1398

    
1399
        fmw = RadioSetting("settings.firmware_prog", "Firmware program",
1400
                           RadioSettingValueBoolean(sett.firmware_prog))
1401
        dealer.append(fmw)
1402

    
1403
        # front keys
1404
        # The Mobile only parameters are wraped here
1405
        if self.TYPE[0] == "M":
1406
            vu = RadioSetting("keys.kVOL_UP", "VOL UP",
1407
                              RadioSettingValueList(KEYS.values(),
1408
                              KEYS.values()[KEYS.keys().index(
1409
                                  int(keys.kVOL_UP))]))
1410
            fkeys.append(vu)
1411

    
1412
            vd = RadioSetting("keys.kVOL_DOWN", "VOL DOWN",
1413
                              RadioSettingValueList(KEYS.values(),
1414
                              KEYS.values()[KEYS.keys().index(
1415
                                  int(keys.kVOL_DOWN))]))
1416
            fkeys.append(vd)
1417

    
1418
            chu = RadioSetting("keys.kCH_UP", "CH UP",
1419
                               RadioSettingValueList(KEYS.values(),
1420
                               KEYS.values()[KEYS.keys().index(
1421
                                   int(keys.kCH_UP))]))
1422
            fkeys.append(chu)
1423

    
1424
            chd = RadioSetting("keys.kCH_DOWN", "CH DOWN",
1425
                               RadioSettingValueList(KEYS.values(),
1426
                               KEYS.values()[KEYS.keys().index(
1427
                                   int(keys.kCH_DOWN))]))
1428
            fkeys.append(chd)
1429

    
1430
            foot = RadioSetting("keys.kFOOT", "Foot switch",
1431
                               RadioSettingValueList(KEYS.values(),
1432
                               KEYS.values()[KEYS.keys().index(
1433
                                   int(keys.kCH_DOWN))]))
1434
            fkeys.append(foot)
1435

    
1436
        # this is the common buttons for all
1437

    
1438
        # 260G model don't have the front keys
1439
        if not "P2600" in self.TYPE:
1440
            scn_name = "SCN"
1441
            if self.TYPE[0] == "P":
1442
                scn_name = "Open Circle"
1443

    
1444
            scn = RadioSetting("keys.kSCN", scn_name,
1445
                               RadioSettingValueList(KEYS.values(),
1446
                               KEYS.values()[KEYS.keys().index(
1447
                                   int(keys.kSCN))]))
1448
            fkeys.append(scn)
1449

    
1450
            a_name = "A"
1451
            if self.TYPE[0] == "P":
1452
                a_name = "Closed circle"
1453

    
1454
            a = RadioSetting("keys.kA", a_name,
1455
                             RadioSettingValueList(KEYS.values(),
1456
                             KEYS.values()[KEYS.keys().index(
1457
                                 int(keys.kA))]))
1458
            fkeys.append(a)
1459

    
1460
            da_name = "D/A"
1461
            if self.TYPE[0] == "P":
1462
                da_name = "< key"
1463

    
1464
            da = RadioSetting("keys.kDA", da_name,
1465
                              RadioSettingValueList(KEYS.values(),
1466
                              KEYS.values()[KEYS.keys().index(
1467
                                  int(keys.kDA))]))
1468
            fkeys.append(da)
1469

    
1470
            gu_name = "Triangle up"
1471
            if self.TYPE[0] == "P":
1472
                gu_name = "Side 1"
1473

    
1474
            gu = RadioSetting("keys.kGROUP_UP", gu_name,
1475
                              RadioSettingValueList(KEYS.values(),
1476
                              KEYS.values()[KEYS.keys().index(
1477
                                  int(keys.kGROUP_UP))]))
1478
            fkeys.append(gu)
1479

    
1480
        # Side keys on portables
1481
        gd_name = "Triangle Down"
1482
        if self.TYPE[0] == "P":
1483
            gd_name = "> key"
1484

    
1485
        gd = RadioSetting("keys.kGROUP_DOWN", gd_name,
1486
                          RadioSettingValueList(KEYS.values(),
1487
                          KEYS.values()[KEYS.keys().index(
1488
                              int(keys.kGROUP_DOWN))]))
1489
        fkeys.append(gd)
1490

    
1491
        mon_name = "MON"
1492
        if self.TYPE[0] == "P":
1493
            mon_name = "Side 2"
1494

    
1495
        mon = RadioSetting("keys.kMON", mon_name,
1496
                           RadioSettingValueList(KEYS.values(),
1497
                           KEYS.values()[KEYS.keys().index(
1498
                               int(keys.kMON))]))
1499
        fkeys.append(mon)
1500

    
1501
        return top
1502

    
1503
    def set_settings(self, settings):
1504
        """Translate the settings in the UI into bit in the mem_struct
1505
        I don't understand well the method used in many drivers
1506
        so, I used mine, ugly but works ok"""
1507

    
1508
        mobj = self._memobj
1509

    
1510
        for element in settings:
1511
            if not isinstance(element, RadioSetting):
1512
                self.set_settings(element)
1513
                continue
1514

    
1515
            # Let's roll the ball
1516
            if "." in element.get_name():
1517
                inter, setting = element.get_name().split(".")
1518
                # you must ignore the settings with "not"
1519
                # this are READ ONLY attributes
1520
                if inter == "not":
1521
                    continue
1522

    
1523
                obj = getattr(mobj, inter)
1524
                value = element.value
1525

    
1526
                # integers case + special case
1527
                if setting in ["tot", "tot_alert", "min_vol", "tone_vol",
1528
                               "sql_level", "tot_rekey", "tot_reset"]:
1529
                    # catching the "off" values as zero
1530
                    try:
1531
                        value = int(value)
1532
                    except:
1533
                        value = 0
1534

    
1535
                    # tot case step 15
1536
                    if setting == "tot":
1537
                        value = value * 15
1538
                        # off is special
1539
                        if value == 0:
1540
                            value = 0x4b0
1541

    
1542
                    # Caso tone_vol
1543
                    if setting == "tone_vol":
1544
                        # off is special
1545
                        if value == 32:
1546
                            value = 0xff
1547

    
1548
                # Bool types + inverted
1549
                if setting in ["c2t", "poweron_tone", "control_tone",
1550
                               "warn_tone", "battery_save", "self_prog",
1551
                               "clone", "panel_test"]:
1552
                    value = bool(value)
1553

    
1554
                    # this cases are inverted
1555
                    if setting == "c2t":
1556
                        value = not value
1557

    
1558
                    # case battery save is special
1559
                    if setting == "battery_save":
1560
                        if bool(value) is True:
1561
                            value = 0x32
1562
                        else:
1563
                            value = 0xff
1564

    
1565
                # String cases
1566
                if setting in ["poweronmesg", "line1", "line2"]:
1567
                    # some vars
1568
                    value = str(value)
1569
                    just = 8
1570
                    # lines with 32
1571
                    if "line" in setting:
1572
                        just = 32
1573

    
1574
                    # empty case
1575
                    if len(value) == 0:
1576
                        value = "\xff" * just
1577
                    else:
1578
                        value = value.ljust(just)
1579

    
1580
                # case keys, with special config
1581
                if inter == "keys":
1582
                    value = KEYS.keys()[KEYS.values().index(str(value))]
1583

    
1584
            # Apply al configs done
1585
            setattr(obj, setting, value)
1586

    
1587
    def get_bank_model(self):
1588
        """Pass the bank model to the UI part"""
1589
        rf = self.get_features()
1590
        if rf.has_bank is True:
1591
            return Kenwood60GBankModel(self)
1592
        else:
1593
            return None
1594

    
1595
    def _get_bank(self, loc):
1596
        """Get the bank data for a specific channel"""
1597
        mem = self._memobj.memory[loc - 1]
1598
        bank = int(mem.bank) - 1
1599

    
1600
        if bank > self._num_banks or bank < 1:
1601
            # all channels must belong to a bank, even with just 1 bank
1602
            return 0
1603
        else:
1604
            return bank
1605

    
1606
    def _set_bank(self, loc, bank):
1607
        """Set the bank data for a specific channel"""
1608
        try:
1609
            b = int(bank)
1610
            if b > 127:
1611
                b = 0
1612
            mem = self._memobj.memory[loc - 1]
1613
            mem.bank = b + 1
1614
        except:
1615
            msg = "You can't have a channel without a bank, click another bank"
1616
            raise errors.InvalidDataError(msg)
1617

    
1618

    
1619

    
1620
@directory.register
1621
class TK981_Radios(Kenwood_Serie_80):
1622
    """Kenwood TK-981 Radio """
1623
    MODEL = "TK-981"
1624
    TYPE = "M0981"
1625
    VARIANTS = {
1626
        "M0981\x0a\xFF":    (128, 902, 941, "K test model to build the class"),
1627
        }
1628
    """  Notes on the above variables in VARIANTS
1629
    "M0981\x0a\xFF"    <--- Radio response to Ident query
1630
    (280, 136, 184, "Test model to build the class")    <------ (channels?/low freq/high freq)
1631
    """
1632

    
1633
""" Future models need ident strings specified and ALL INFO verified. There may be more variants.
1634
@directory.register
1635
class TK780_Radios(Kenwood_Serie_80):
1636
    """"""Kenwood TK-780 Radio """"""
1637
    MODEL = "TK-780"
1638
    TYPE = "M0780"
1639
    VARIANTS = {
1640
        "M0780\x0a\xFF":    (128, 146, 174, "E"),
1641
        }
1642
        
1643
@directory.register
1644
class TK880_Radios(Kenwood_Serie_80):
1645
    """"""Kenwood TK-880 Radio """"""
1646
    MODEL = "TK-880"
1647
    TYPE = "M0880"
1648
    VARIANTS = {
1649
        "M0880\x0a\xFF":    (128, 440, 470, "E"),
1650
        "M0880\x0a\xFF":    (128, 406, 450, "E3"),
1651
        }
1652

    
1653
@directory.register
1654
class TK280_Radios(Kenwood_Serie_80):
1655
    """"""Kenwood TK-280 Radio""""""
1656
    MODEL = "TK-280"
1657
    TYPE = "P0280"
1658
    VARIANTS = {
1659
        "P0280\x05\xDF":    (128, 146, 174, "K Non-Keypad Model"),
1660
        "P0280\x05\xDF":    (128, 136, 162, "K2 Non-Keypad Model"),
1661
        "P0280\x05\xDF":    (128, 146, 174, "M Non-Keypad Model"),
1662
        "P0280\x05\xDF":    (128, 146, 174, "K3 Keypad Model"),
1663
        "P0280\x05\xDF":    (128, 136, 162, "K4 Keypad Model"),
1664
        }
1665

    
1666
@directory.register
1667
class TK380_Radios(Kenwood_Serie_80):
1668
    """"""Kenwood TK-380 Radio """"""
1669
    MODEL = "TK-380"
1670
    TYPE = "P0380"
1671
    VARIANTS = {
1672
        "P0380\x0a\xFF":    (128, 450, 490, "K Non-Keypad Model"),
1673
        "P0380\x0a\xFF":    (128, 470, 512, "K2 Non-Keypad Model"),
1674
        "P0380\x0a\xFF":    (128, 400, 430, "K3 Non-Keypad Model"),
1675
        "P0380\x0a\xFF":    (128, 450, 490, "M Non-Keypad Model"),
1676
        "P0380\x0a\xFF":    (128, 400, 430, "M3 Non-Keypad Model"),
1677
        "P0380\x0a\xFF":    (128, 470, 520, "E2 Keypad Model"),
1678
        }
1679

    
1680
@directory.register
1681
class TK980_Radios(Kenwood_Serie_80):
1682
    """"""Kenwood TK-980 Radio """"""
1683
    MODEL = "TK-980"
1684
    TYPE = "M0980"
1685
    VARIANTS = {
1686
        "M0980\x0a\xFF":    (128, 806, 870, "K"),
1687
        "M0980\x0a\xFF":    (128, 806, 870, "M"),
1688
        }
1689
        
1690
@directory.register
1691
class TK480_Radios(Kenwood_Serie_80):
1692
    """"""Kenwood TK-480 Radio """"""
1693
    MODEL = "TK-480"
1694
    TYPE = "P0480"
1695
    VARIANTS = {
1696
        "P0480\x0a\xFF":    (128, 806, 870, "K"),
1697
        "P0480\x0a\xFF":    (128, 806, 870, "K2 Non-Keypad Model"),
1698
        "P0480\x0a\xFF":    (128, 806, 870, "K3 Keypad Model"),
1699
        "P0480\x0a\xFF":    (128, 806, 870, "K4 Keypad Model"),
1700
        "P0480\x0a\xFF":    (128, 806, 870, "M2 Keypad Model"),
1701
        "P0480\x0a\xFF":    (128, 806, 870, "M4 Keypad Model"),
1702
        }
1703
        
1704
@directory.register
1705
class TK481_Radios(Kenwood_Serie_80):
1706
    """"""Kenwood TK-481 Radio """"""
1707
    MODEL = "TK-481"
1708
    TYPE = "P0481"
1709
    VARIANTS = {
1710
        "P0481\x0a\xFF":    (128, 896, 941, "K"),
1711
        "P0481\x0a\xFF":    (128, 896, 941, "K2"),
1712
        }
1713

    
1714
"""    
1715
    
(17-17/20)