Project

General

Profile

Bug #9773 » kguv9dplus_new_rev_patchLEGACY2.py

Use with Chirp LEGACY ONLY - Mel Terechenok, 01/25/2023 03:38 PM

 
1
# Copyright 2018 Jim Lieb <lieb@sea-troll.net>
2
#
3
# Driver for Wouxon KG-UV9D Plus
4
#
5
# Borrowed from other chirp drivers, especially the KG-UV8D Plus
6
# by Krystian Struzik <toner_82@tlen.pl>
7
#
8
# This program is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU General Public License as published by
10
# the Free Software Foundation, either version 3 of the License, or
11
# (at your option) any later version.
12
#
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
# GNU General Public License for more details.
17
#
18
# You should have received a copy of the GNU General Public License
19
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
20

    
21
"""Wouxun KG-UV9D Plus radio management module"""
22

    
23
import time
24
import os
25
import logging
26
import struct
27
import string
28
from chirp import util, chirp_common, bitwise, memmap, errors, directory
29
from chirp.settings import RadioSetting, RadioSettingValue, \
30
     RadioSettingGroup, \
31
     RadioSettingValueBoolean, RadioSettingValueList, \
32
     RadioSettingValueInteger, RadioSettingValueString, \
33
     RadioSettings, InvalidValueError
34

    
35
LOG = logging.getLogger(__name__)
36

    
37
CMD_IDENT = 0x80
38
CMD_HANGUP = 0x81
39
CMD_RCONF = 0x82
40
CMD_WCONF = 0x83
41
CMD_RCHAN = 0x84
42
CMD_WCHAN = 0x85
43

    
44
cmd_name = {
45
    CMD_IDENT:  "ident",
46
    CMD_HANGUP: "hangup",
47
    CMD_RCONF:  "read config",
48
    CMD_WCONF:  "write config",
49
    CMD_RCHAN:  "read channel memory",  # Unused
50
    CMD_WCHAN:  "write channel memory"  # Unused because it is a hack.
51
    }
52

    
53
# This is used to write the configuration of the radio base on info
54
# gleaned from the downloaded app. There are empty spaces and we honor
55
# them because we don't know what they are (yet) although we read the
56
# whole of memory.
57
#
58
# Channel memory is separate. There are 1000 (1-999) channels.
59
# These are read/written to the radio in 4 channel (96 byte)
60
# records starting at address 0xa00 and ending at
61
# 0x4800 (presuming the end of channel 1000 is 0x4860-1
62

    
63
config_map = (          # map address, write size, write count
64
    (0x40,   16, 1),    # Passwords
65
    (0x740,  40, 1),    # FM chan 1-20
66
    (0x780,  16, 1),    # vfo-b-150
67
    (0x790,  16, 1),    # vfo-b-450
68
    (0x800,  16, 1),    # vfo-a-150
69
    (0x810,  16, 1),    # vfo-a-450
70
    (0x820,  16, 1),    # vfo-a-300
71
    (0x830,  16, 1),    # vfo-a-700
72
    (0x840,  16, 1),    # vfo-a-200
73
    (0x860,  16, 1),    # area-a-conf
74
    (0x870,  16, 1),    # area-b-conf
75
    (0x880,  16, 1),    # radio conf 0
76
    (0x890,  16, 1),    # radio conf 1
77
    (0x8a0,  16, 1),    # radio conf 2
78
    (0x8b0,  16, 1),    # radio conf 3
79
    (0x8c0,  16, 1),    # PTT-ANI
80
    (0x8d0,  16, 1),    # SCC
81
    (0x8e0,  16, 1),    # power save
82
    (0x8f0,  16, 1),    # Display banner
83
    (0x940,  64, 2),    # Scan groups and names
84
    (0xa00,  64, 249),  # Memory Channels 1-996
85
    (0x4840, 48, 1),    # Memory Channels 997-999
86
    (0x4900, 32, 249),  # Memory Names    1-996
87
    (0x6820, 24, 1),    # Memory Names    997-999
88
    (0x7400, 64, 5),    # CALL-ID 1-20, names 1-20
89
    )
90

    
91

    
92
MEM_VALID = 0xfc
93
MEM_INVALID = 0xff
94

    
95
# Radio memory map. This matches the reads/writes above.
96
# structure elements whose name starts with x are currently unidentified
97

    
98
_MEM_FORMAT02 = """
99
#seekto 0x40;
100

    
101
struct {
102
    char reset[6];
103
    char x46[2];
104
    char mode_sw[6];
105
    char x4e;
106
}  passwords;
107

    
108
#seekto 0x740;
109

    
110
struct {
111
    u16 fm_freq;
112
} fm_chans[20];
113

    
114
// each band has its own configuration, essentially its default params
115

    
116
struct vfo {
117
    u32 freq;
118
    u32 offset;
119
    u16 encqt;
120
    u16 decqt;
121
    u8  bit7_4:3,
122
        qt:3,
123
        bit1_0:2;
124
    u8  bit7:1,
125
        scan:1,
126
        bit5:1,
127
        pwr:2,
128
        mod:1,
129
        fm_dev:2;
130
    u8  pad2:6,
131
        shift:2;
132
    u8  zeros;
133
};
134

    
135
#seekto 0x780;
136

    
137
struct {
138
    struct vfo band_150;
139
    struct vfo band_450;
140
} vfo_b;
141

    
142
#seekto 0x800;
143

    
144
struct {
145
    struct vfo band_150;
146
    struct vfo band_450;
147
    struct vfo band_300;
148
    struct vfo band_700;
149
    struct vfo band_200;
150
} vfo_a;
151

    
152
// There are two independent radios, aka areas (as described
153
// in the manual as the upper and lower portions of the display...
154

    
155
struct area_conf {
156
    u8 w_mode;
157
    u8 x861;
158
    u8 w_chan;
159
    u8 scan_grp;
160
    u8 bcl;
161
    u8 sql;
162
    u8 cset;
163
    u8 step;
164
    u8 scan_mode;
165
    u8 x869;
166
    u8 scan_range;
167
    u8 x86b;
168
    u8 x86c;
169
    u8 x86d;
170
    u8 x86e;
171
    u8 x86f;
172
};
173

    
174
#seekto 0x860;
175

    
176
struct area_conf a_conf;
177

    
178
#seekto 0x870;
179

    
180
struct area_conf b_conf;
181

    
182
#seekto 0x880;
183

    
184
struct {
185
    u8 menu_avail;
186
    u8 reset_avail;
187
    u8 x882;
188
    u8 x883;
189
    u8 lang;
190
    u8 x885;
191
    u8 beep;
192
    u8 auto_am;
193
    u8 qt_sw;
194
    u8 lock;
195
    u8 x88a;
196
    u8 pf1;
197
    u8 pf2;
198
    u8 pf3;
199
    u8 s_mute;
200
    u8 type_set;
201
    u8 tot;
202
    u8 toa;
203
    u8 ptt_id;
204
    u8 x893;
205
    u8 id_dly;
206
    u8 x895;
207
    u8 voice_sw;
208
    u8 s_tone;
209
    u8 abr_lvl;
210
    u8 ring_time;
211
    u8 roger;
212
    u8 x89b;
213
    u8 abr;
214
    u8 save_m;
215
    u8 lock_m;
216
    u8 auto_lk;
217
    u8 rpt_ptt;
218
    u8 rpt_spk;
219
    u8 rpt_rct;
220
    u8 prich_sw;
221
    u16 pri_ch;
222
    u8 x8a6;
223
    u8 x8a7;
224
    u8 dtmf_st;
225
    u8 dtmf_tx;
226
    u8 x8aa;
227
    u8 sc_qt;
228
    u8 apo_tmr;
229
    u8 vox_grd;
230
    u8 vox_dly;
231
    u8 rpt_kpt;
232
    struct {
233
        u16 scan_st;
234
        u16 scan_end;
235
    } a;
236
    struct {
237
        u16 scan_st;
238
        u16 scan_end;
239
    } b;
240
    u8 x8b8;
241
    u8 x8b9;
242
    u8 x8ba;
243
    u8 ponmsg;
244
    u8 blcdsw;
245
    u8 bledsw;
246
    u8 x8be;
247
    u8 x8bf;
248
} settings;
249

    
250

    
251
#seekto 0x8c0;
252
struct {
253
    u8 code[6];
254
    char x8c6[10];
255
} my_callid;
256

    
257
#seekto 0x8d0;
258
struct {
259
    u8 scc[6];
260
    char x8d6[10];
261
} stun;
262

    
263
#seekto 0x8e0;
264
struct {
265
    u16 wake;
266
    u16 sleep;
267
} save[4];
268

    
269
#seekto 0x8f0;
270
struct {
271
    char banner[16];
272
} display;
273

    
274
#seekto 0x940;
275
struct {
276
    struct {
277
        i16 scan_st;
278
        i16 scan_end;
279
    } addrs[10];
280
    u8 x0968[8];
281
    struct {
282
        char name[8];
283
    } names[10];
284
} scn_grps;
285

    
286
// this array of structs is marshalled via the R/WCHAN commands
287
#seekto 0xa00;
288
struct {
289
    u32 rxfreq;
290
    u32 txfreq;
291
    u16 encQT;
292
    u16 decQT;
293
    u8  bit7_5:3,  // all ones
294
        qt:3,
295
        bit1_0:2;
296
    u8  bit7:1,
297
        scan:1,
298
        bit5:1,
299
        pwr:2,
300
        mod:1,
301
        fm_dev:2;
302
    u8  state;
303
    u8  c3;
304
} chan_blk[999];
305

    
306
// nobody really sees this. It is marshalled with chan_blk
307
// in 4 entry chunks
308
#seekto 0x4900;
309

    
310
// Tracks with the index of  chan_blk[]
311
struct {
312
    char name[8];
313
} chan_name[999];
314

    
315
#seekto 0x7400;
316
struct {
317
    u8 cid[6];
318
    u8 pad[2];
319
}call_ids[20];
320

    
321
// This array tracks with the index of call_ids[]
322
struct {
323
    char name[6];
324
    char pad[2];
325
} cid_names[20];
326
    """
327

    
328

    
329
# Support for the Wouxun KG-UV9D Plus radio
330
# Serial coms are at 19200 baud
331
# The data is passed in variable length records
332
# Record structure:
333
#  Offset   Usage
334
#    0      start of record (\x7d)
335
#    1      Command (6 commands, see above)
336
#    2      direction (\xff PC-> Radio, \x00 Radio -> PC)
337
#    3      length of payload (excluding header/checksum) (n)
338
#    4      payload (n bytes)
339
#    4+n+1  checksum - byte sum (% 256) of bytes 1 -> 4+n
340
#
341
# Memory Read Records:
342
# the payload is 3 bytes, first 2 are offset (big endian),
343
# 3rd is number of bytes to read
344
# Memory Write Records:
345
# the maximum payload size (from the Wouxun software)
346
# seems to be 66 bytes (2 bytes location + 64 bytes data).
347

    
348
def _pkt_encode(op, payload):
349
    """Assemble a packet for the radio and encode it for transmission.
350
    Yes indeed, the checksum we store is only 4 bits. Why?
351
    I suspect it's a bug in the radio firmware guys didn't want to fix,
352
    i.e. a typo 0xff -> 0xf..."""
353

    
354
    data = bytearray()
355
    data.append(0x7d)  # tag that marks the beginning of the packet
356
    data.append(op)
357
    data.append(0xff)  # 0xff is from app to radio
358
    # calc checksum from op to end
359
    cksum = op + 0xff
360
    if (payload):
361
        data.append(len(payload))
362
        cksum += len(payload)
363
        for byte in payload:
364
            cksum += byte
365
            data.append(byte)
366
    else:
367
        data.append(0x00)
368
        # Yea, this is a 4 bit cksum (also known as a bug)
369
    data.append(cksum & 0xf)
370

    
371
    # now obfuscate by an xor starting with first payload byte ^ 0x52
372
    # including the trailing cksum.
373
    xorbits = 0x52
374
    for i, byte in enumerate(data[4:]):
375
        xord = xorbits ^ byte
376
        data[i + 4] = xord
377
        xorbits = xord
378
    return(data)
379

    
380

    
381
def _pkt_decode(data):
382
    """Take a packet hot off the wire and decode it into clear text
383
    and return the fields. We say <<cleartext>> here because all it
384
    turns out to be is annoying obfuscation.
385
    This is the inverse of pkt_decode"""
386

    
387
    # we don't care about data[0].
388
    # It is always 0x7d and not included in checksum
389
    op = data[1]
390
    direction = data[2]
391
    bytecount = data[3]
392

    
393
    # First un-obfuscate the payload and cksum
394
    payload = bytearray()
395
    xorbits = 0x52
396
    for i, byte in enumerate(data[4:]):
397
        payload.append(xorbits ^ byte)
398
        xorbits = byte
399

    
400
    # Calculate the checksum starting with the 3 bytes of the header
401
    cksum = op + direction + bytecount
402
    for byte in payload[:-1]:
403
        cksum += byte
404
    # yes, a 4 bit cksum to match the encode
405
    cksum_match = (cksum & 0xf) == payload[-1]
406
    if (not cksum_match):
407
        LOG.debug(
408
            "Checksum mismatch: %x != %x; " % (cksum, payload[-1]))
409
    return (cksum_match, op, payload[:-1])
410

    
411
# UI callbacks to process input for mapping UI fields to memory cells
412

    
413

    
414
def freq2int(val, min, max):
415
    """Convert a frequency as a string to a u32. Units is Hz
416
    """
417
    _freq = chirp_common.parse_freq(str(val))
418
    if _freq > max or _freq < min:
419
        raise InvalidValueError("Frequency %s is not with in %s-%s" %
420
                                (chirp_common.format_freq(_freq),
421
                                 chirp_common.format_freq(min),
422
                                 chirp_common.format_freq(max)))
423
    return _freq
424

    
425

    
426
def int2freq(freq):
427
    """
428
    Convert a u32 frequency to a string for UI data entry/display
429
    This is stored in the radio as units of 10Hz which we compensate to Hz.
430
    A value of -1 indicates <no frequency>, i.e. unused channel.
431
    """
432
    if (int(freq) > 0):
433
        f = chirp_common.format_freq(freq)
434
        return f
435
    else:
436
        return ""
437

    
438

    
439
def freq2short(val, min, max):
440
    """Convert a frequency as a string to a u16 which is units of 10KHz
441
    """
442
    _freq = chirp_common.parse_freq(str(val))
443
    if _freq > max or _freq < min:
444
        raise InvalidValueError("Frequency %s is not with in %s-%s" %
445
                                (chirp_common.format_freq(_freq),
446
                                 chirp_common.format_freq(min),
447
                                 chirp_common.format_freq(max)))
448
    return _freq/100000 & 0xFFFF
449

    
450

    
451
def short2freq(freq):
452
    """
453
       Convert a short frequency to a string for UI data entry/display
454
       This is stored in the radio as units of 10KHz which we
455
       compensate to Hz.
456
       A value of -1 indicates <no frequency>, i.e. unused channel.
457
    """
458
    if (int(freq) > 0):
459
        f = chirp_common.format_freq(freq * 100000)
460
        return f
461
    else:
462
        return ""
463

    
464

    
465
def tone2short(t):
466
    """Convert a string tone or DCS to an encoded u16
467
    """
468
    tone = str(t)
469
    if tone == "----":
470
        u16tone = 0x0000
471
    elif tone[0] == 'D':  # This is a DCS code
472
        c = tone[1: -1]
473
        code = int(c, 8)
474
        if tone[-1] == 'I':
475
            code |= 0x4000
476
        u16tone = code | 0x8000
477
    else:              # This is an analog CTCSS
478
        u16tone = int(tone[0:-2]+tone[-1]) & 0xffff  # strip the '.'
479
    return u16tone
480

    
481

    
482
def short2tone(tone):
483
    """ Map a binary CTCSS/DCS to a string name for the tone
484
    """
485
    if tone == 0 or tone == 0xffff:
486
        ret = "----"
487
    else:
488
        code = tone & 0x3fff
489
        if tone & 0x8000:      # This is a DCS
490
            if tone & 0x4000:  # This is an inverse code
491
                ret = "D%0.3oI" % code
492
            else:
493
                ret = "D%0.3oN" % code
494
        else:   # Just plain old analog CTCSS
495
            ret = "%4.1f" % (code / 10.0)
496
    return ret
497

    
498

    
499
def callid2str(cid):
500
    """Caller ID per MDC-1200 spec? Must be 3-6 digits (100 - 999999).
501
       One digit (binary) per byte, terminated with '0xc'
502
    """
503

    
504
    bin2ascii = " 1234567890"
505
    cidstr = ""
506
    for i in range(0, 6):
507
        b = cid[i].get_value()
508
        if b == 0xc:  # the cid EOL
509
            break
510
        if b == 0 or b > 0xa:
511
            raise InvalidValueError(
512
                "Caller ID code has illegal byte 0x%x" % b)
513
        cidstr += bin2ascii[b]
514
    return cidstr
515

    
516

    
517
def str2callid(val):
518
    """ Convert caller id strings from callid2str.
519
    """
520
    ascii2bin = "0123456789"
521
    s = str(val).strip()
522
    if len(s) < 3 or len(s) > 6:
523
        raise InvalidValueError(
524
            "Caller ID must be at least 3 and no more than 6 digits")
525
    if s[0] == '0':
526
        raise InvalidValueError(
527
            "First digit of a Caller ID cannot be a zero '0'")
528
    blk = bytearray()
529
    for c in s:
530
        if c not in ascii2bin:
531
            raise InvalidValueError(
532
                "Caller ID must be all digits 0x%x" % c)
533
        b = (0xa, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9)[int(c)]
534
        blk.append(b)
535
    if len(blk) < 6:
536
        blk.append(0xc)  # EOL a short ID
537
    if len(blk) < 6:
538
        for i in range(0, (6 - len(blk))):
539
            blk.append(0xf0)
540
    return blk
541

    
542

    
543
def digits2str(digits, padding=' ', width=6):
544
    """Convert a password or SCC digit string to a string
545
    Passwords are expanded to and must be 6 chars. Fill them with '0'
546
    """
547

    
548
    bin2ascii = "0123456789"
549
    digitsstr = ""
550
    for i in range(0, 6):
551
        b = digits[i].get_value()
552
        if b == 0xc:  # the digits EOL
553
            break
554
        if b >= 0xa:
555
            raise InvalidValueError(
556
                "Value has illegal byte 0x%x" % ord(b))
557
        digitsstr += bin2ascii[b]
558
    digitsstr = digitsstr.ljust(width, padding)
559
    return digitsstr
560

    
561

    
562
def str2digits(val):
563
    """ Callback for edited strings from digits2str.
564
    """
565
    ascii2bin = " 0123456789"
566
    s = str(val).strip()
567
    if len(s) < 3 or len(s) > 6:
568
        raise InvalidValueError(
569
            "Value must be at least 3 and no more than 6 digits")
570
    blk = bytearray()
571
    for c in s:
572
        if c not in ascii2bin:
573
            raise InvalidValueError("Value must be all digits 0x%x" % c)
574
        blk.append(int(c))
575
    for i in range(len(blk), 6):
576
        blk.append(0xc)  # EOL a short ID
577
    return blk
578

    
579

    
580
def name2str(name):
581
    """ Convert a callid or scan group name to a string
582
    Deal with fixed field padding (\0 or \0xff)
583
    """
584

    
585
    namestr = ""
586
    for i in range(0, len(name)):
587
        b = ord(name[i].get_value())
588
        if b != 0 and b != 0xff:
589
            namestr += chr(b)
590
    return namestr
591

    
592

    
593
def str2name(val, size=6, fillchar='\0', emptyfill='\0'):
594
    """ Convert a string to a name. A name is a 6 element bytearray
595
    with ascii chars.
596
    """
597
    val = str(val).rstrip(' \t\r\n\0\0xff')
598
    if len(val) == 0:
599
        name = "".ljust(size, emptyfill)
600
    else:
601
        name = val.ljust(size, fillchar)
602
    return name
603

    
604

    
605
def pw2str(pw):
606
    """Convert a password string (6 digits) to a string
607
    Passwords must be 6 digits. If it is shorter, pad right with '0'
608
    """
609
    pwstr = ""
610
    ascii2bin = "0123456789"
611
    for i in range(0, len(pw)):
612
        b = pw[i].get_value()
613
        if b not in ascii2bin:
614
            raise InvalidValueError("Value must be digits 0-9")
615
        pwstr += b
616
    pwstr = pwstr.ljust(6, '0')
617
    return pwstr
618

    
619

    
620
def str2pw(val):
621
    """Store a password from UI to memory obj
622
    If we clear the password (make it go away), change the
623
    empty string to '000000' since the radio must have *something*
624
    Also, fill a < 6 digit pw with 0's
625
    """
626
    ascii2bin = "0123456789"
627
    val = str(val).rstrip(' \t\r\n\0\0xff')
628
    if len(val) == 0:  # a null password
629
        val = "000000"
630
    for i in range(0, len(val)):
631
        b = val[i]
632
        if b not in ascii2bin:
633
            raise InvalidValueError("Value must be digits 0-9")
634
    if len(val) == 0:
635
        pw = "".ljust(6, '\0')
636
    else:
637
        pw = val.ljust(6, '0')
638
    return pw
639

    
640

    
641
# Helpers to replace python2 things like confused str/byte
642

    
643
def _hex_print(data, addrfmt=None):
644
    """Return a hexdump-like encoding of @data
645
    We expect data to be a bytearray, not a string.
646
    Expanded from borrowed code to use the first 2 bytes as the address
647
    per comm packet format.
648
    """
649
    if addrfmt is None:
650
        addrfmt = '%(addr)03i'
651
        addr = 0
652
    else:  # assume first 2 bytes are address
653
        a = struct.unpack(">H", data[0:2])
654
        addr = a[0]
655
        data = data[2:]
656

    
657
    block_size = 16
658

    
659
    lines = (len(data) / block_size)
660
    if (len(data) % block_size > 0):
661
        lines += 1
662

    
663
    out = ""
664
    left = len(data)
665
    for block in range(0, lines):
666
        addr += block * block_size
667
        try:
668
            out += addrfmt % locals()
669
        except (OverflowError, ValueError, TypeError, KeyError):
670
            out += "%03i" % addr
671
        out += ': '
672

    
673
        if left < block_size:
674
            limit = left
675
        else:
676
            limit = block_size
677

    
678
        for j in range(0, block_size):
679
            if (j < limit):
680
                out += "%02x " % data[(block * block_size) + j]
681
            else:
682
                out += "   "
683

    
684
        out += "  "
685

    
686
        for j in range(0, block_size):
687

    
688
            if (j < limit):
689
                _byte = data[(block * block_size) + j]
690
                if _byte >= 0x20 and _byte < 0x7F:
691
                    out += "%s" % chr(_byte)
692
                else:
693
                    out += "."
694
            else:
695
                out += " "
696
        out += "\n"
697
        if (left > block_size):
698
            left -= block_size
699

    
700
    return out
701

    
702

    
703
# Useful UI lists
704
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 50.0, 100.0]
705
S_TONES = [str(x) for x in [1000, 1450, 1750, 2100]]
706
STEP_LIST = [str(x)+"kHz" for x in STEPS]
707
ROGER_LIST = ["Off", "Begin", "End", "Both"]
708
TIMEOUT_LIST = [str(x) + "s" for x in range(15, 601, 15)]
709
TOA_LIST = ["Off"] + ["%ds" % t for t in range(1, 10)]
710
BANDWIDTH_LIST = ["Wide", "Narrow"]
711
LANGUAGE_LIST = ["English", "Chinese"]
712
PF1KEY_LIST = ["OFF", "call id", "r-alarm", "SOS", "SF-TX"]
713
PF2KEY_LIST = ["OFF", "Scan", "Second", "lamp", "SDF-DIR", "K-lamp"]
714
PF3KEY_LIST = ["OFF", "Call ID", "R-ALARM", "SOS", "SF-TX"]
715
WORKMODE_LIST = ["VFO freq", "Channel No.", "Ch. No.+Freq.",
716
                 "Ch. No.+Name"]
717
BACKLIGHT_LIST = ["Off"] + ["%sS" % t for t in range(1, 31)] + \
718
                 ["Always On"]
719
SAVE_MODES = ["Off", "1", "2", "3", "4"]
720
LOCK_MODES = ["key-lk", "key+pg", "key+ptt", "all"]
721
APO_TIMES = ["Off"] + ["%dm" % t for t in range(15, 151, 15)]
722
OFFSET_LIST = ["none", "+", "-"]
723
PONMSG_LIST = ["Battery Volts", "Bitmap"]
724
SPMUTE_LIST = ["QT", "QT*T", "QT&T"]
725
DTMFST_LIST = ["Off", "DT-ST", "ANI-ST", "DT-ANI"]
726
DTMF_TIMES = ["%d" % x for x in range(80, 501, 20)]
727
PTTID_LIST = ["Off", "Begin", "End", "Both"]
728
ID_DLY_LIST = ["%dms" % t for t in range(100, 3001, 100)]
729
VOX_GRDS = ["Off"] + ["%dlevel" % l for l in range(1, 11)]
730
VOX_DLYS = ["Off"] + ["%ds" % t for t in range(1, 5)]
731
RPT_KPTS = ["Off"] + ["%dms" % t for t in range(100, 5001, 100)]
732
LIST_1_5 = ["%s" % x for x in range(1, 6)]
733
LIST_0_9 = ["%s" % x for x in range(0, 10)]
734
LIST_1_20 = ["%s" % x for x in range(1, 21)]
735
LIST_OFF_10 = ["Off"] + ["%s" % x for x in range(1, 11)]
736
SCANGRP_LIST = ["All"] + ["%s" % x for x in range(1, 11)]
737
SCANMODE_LIST = ["TO", "CO", "SE"]
738
SCANRANGE_LIST = ["Current band", "freq range", "ALL"]
739
SCQT_LIST = ["Decoder", "Encoder", "Both"]
740
S_MUTE_LIST = ["off", "rx mute", "tx mute", "r/t mute"]
741
POWER_LIST = ["Low", "Med", "High"]
742
RPTMODE_LIST = ["Radio", "One direction Repeater",
743
                "Two direction repeater"]
744
TONE_LIST = ["----"] + ["%s" % str(t) for t in chirp_common.TONES] + \
745
            ["D%0.3dN" % dts for dts in chirp_common.DTCS_CODES] + \
746
            ["D%0.3dI" % dts for dts in chirp_common.DTCS_CODES]
747

    
748

    
749
@directory.register
750
class KGUV9DPlusRadio(chirp_common.CloneModeRadio,
751
                      chirp_common.ExperimentalRadio):
752

    
753
    """Wouxun KG-UV9D Plus"""
754
    VENDOR = "Wouxun"
755
    MODEL = "KG-UV9D Plus"
756
    _model = "KG-UV9D"
757
    _rev = "00"  # default rev for the radio I know about...
758
    _file_ident = "kg-uv9d"
759
    BAUD_RATE = 19200
760
    POWER_LEVELS = [chirp_common.PowerLevel("L", watts=1),
761
                    chirp_common.PowerLevel("M", watts=2),
762
                    chirp_common.PowerLevel("H", watts=5)]
763
    _mmap = ""
764

    
765
    def _read_record(self):
766
        """ Read and validate the header of a radio reply.
767
        A record is a formatted byte stream as follows:
768
            0x7D   All records start with this
769
            opcode This is in the set of legal commands.
770
                   The radio reply matches the request
771
            dir    This is the direction, 0xFF to the radio,
772
                   0x00 from the radio.
773
            cnt    Count of bytes in payload
774
                   (not including the trailing checksum byte)
775
            <cnt bytes>
776
            <checksum byte>
777
        """
778

    
779
        # first get the header and validate it
780
        data = bytearray(self.pipe.read(4))
781
        if (len(data) < 4):
782
            raise errors.RadioError('Radio did not respond')
783
        if (data[0] != 0x7D):
784
            raise errors.RadioError(
785
                'Radio reply garbled (%02x)' % data[0])
786
        if (data[1] not in cmd_name):
787
            raise errors.RadioError(
788
                "Unrecognized opcode (%02x)" % data[1])
789
        if (data[2] != 0x00):
790
            raise errors.RadioError(
791
                "Direction incorrect. Got (%02x)" % data[2])
792
        payload_len = data[3]
793
        # don't forget to read the checksum byte
794
        data.extend(self.pipe.read(payload_len + 1))
795
        if (len(data) != (payload_len + 5)):  # we got a short read
796
            raise errors.RadioError(
797
                "Radio reply wrong size. Wanted %d, got %d" %
798
                ((payload_len + 1), (len(data) - 4)))
799
        return _pkt_decode(data)
800

    
801
    def _write_record(self, cmd, payload=None):
802
        """ Write a request packet to the radio.
803
        """
804

    
805
        packet = _pkt_encode(cmd, payload)
806
        self.pipe.write(packet)
807

    
808
    @classmethod
809
    def match_model(cls, filedata, filename):
810
        """Look for bits in the file image and see if it looks
811
        like ours...
812
        TODO: there is a bunch of rubbish between 0x50 and 0x160
813
        that is still a known unknown
814
        """
815
        return cls._file_ident in filedata[0x51:0x59].lower()
816

    
817
    def _identify(self):
818
        """ Identify the radio
819
        The ident block identifies the radio and its capabilities.
820
        This block is always 78 bytes. The rev == '01' is the base
821
        radio and '02' seems to be the '-Plus' version.
822
        I don't really trust the content after the model and revision.
823
        One would assume this is pretty much constant data but I have
824
        seen differences between my radio and the dump named
825
        KG-UV9D-Plus-OutOfBox-Read.txt from bug #3509. The first
826
        five bands match the OEM windows
827
        app except the 350-400 band. The OOB trace has the 700MHz
828
        band different. This is speculation at this point.
829

    
830
        TODO: This could be smarter and reject a radio not actually
831
        a UV9D...
832
        """
833

    
834
        for _i in range(0, 10):  # retry 10 times if we get junk
835
            self._write_record(CMD_IDENT)
836
            chksum_match, op, _resp = self._read_record()
837
            if len(_resp) == 0:
838
                raise Exception("Radio not responding")
839
            if len(_resp) != 74:
840
                LOG.error(
841
                    "Expected and IDENT reply of 78 bytes. Got (%d)" %
842
                    len(_resp))
843
                continue
844
            if not chksum_match:
845
                LOG.error("Checksum error: retrying ident...")
846
                time.sleep(0.100)
847
                continue
848
            if op != CMD_IDENT:
849
                LOG.error("Expected IDENT reply. Got (%02x)" % op)
850
                continue
851
            LOG.debug("Got:\n%s" % _hex_print(_resp))
852
            (mod, rev) = struct.unpack(">7s2s", _resp[0:9])
853
            LOG.debug("Model %s, rev %s" % (mod, rev))
854
            if mod == self._model:
855
                self._rev = rev
856
                return
857
            else:
858
                raise Exception("Unable to identify radio")
859
        raise Exception("All retries to identify failed")
860

    
861
    def process_mmap(self):
862
        if self._rev != "02" and self._rev != "00":
863
            # new revision found - log it and assume same map and proceed
864
            LOG.debug("Unrecognized model variation (%s) Using default Map" %
865
                      self._rev)
866
        self._memobj = bitwise.parse(_MEM_FORMAT02, self._mmap)
867

    
868
    def sync_in(self):
869
        """ Public sync_in
870
            Download contents of the radio. Throw errors back
871
            to the core if the radio does not respond.
872
            """
873
        try:
874
            self._identify()
875
            self._mmap = self._do_download()
876
            self._write_record(CMD_HANGUP)
877
        except errors.RadioError:
878
            raise
879
        except Exception, e:
880
            LOG.exception('Unknown error during download process')
881
            raise errors.RadioError(
882
                "Failed to communicate with radio: %s" % e)
883
        self.process_mmap()
884

    
885
    def sync_out(self):
886
        """ Public sync_out
887
            Upload the modified memory image into the radio.
888
            """
889

    
890
        try:
891
            self._identify()
892
            self._do_upload()
893
            self._write_record(CMD_HANGUP)
894
        except errors.RadioError:
895
            raise
896
        except Exception, e:
897
            raise errors.RadioError(
898
                "Failed to communicate with radio: %s" % e)
899
        return
900

    
901
    def _do_download(self):
902
        """ Read the whole of radio memory in 64 byte chunks.
903
        We load the config space followed by loading memory channels.
904
        The radio seems to be a "clone" type and the memory channels
905
        are actually within the config space. There are separate
906
        commands (CMD_RCHAN, CMD_WCHAN) for reading channel memory but
907
        these seem to be a hack that can only do 4 channels at a time.
908
        Since the radio only supports 999, (can only support 3 chars
909
        in the display UI?) although the vendors app reads 1000
910
        channels, it hacks back to config writes (CMD_WCONF) for the
911
        last 3 channels and names. We keep it simple and just read
912
        the whole thing even though the vendor app doesn't. Channels
913
        are separate in their app simply because the radio protocol
914
        has read/write commands to access it. What they do is simply
915
        marshal the frequency+mode bits in 4 channel chunks followed
916
        by a separate chunk of for names. In config space, they are two
917
        separate arrays 1..999. Given that this space is not a
918
        multiple of 4, there is hackery on upload to do the writes to
919
        config space. See upload for this.
920
        """
921

    
922
        mem = bytearray(0x8000)  # The radio's memory map is 32k
923
        for addr in range(0, 0x8000, 64):
924
            req = bytearray(struct.pack(">HB", addr, 64))
925
            self._write_record(CMD_RCONF, req)
926
            chksum_match, op, resp = self._read_record()
927
            if not chksum_match:
928
                LOG.debug(_hex_print(resp))
929
                raise Exception(
930
                    "Checksum error while reading configuration (0x%x)" %
931
                    addr)
932
            pa = struct.unpack(">H", resp[0:2])
933
            pkt_addr = pa[0]
934
            payload = resp[2:]
935
            if op != CMD_RCONF or addr != pkt_addr:
936
                raise Exception(
937
                    "Expected CMD_RCONF (%x) reply. Got (%02x: %x)" %
938
                    (addr, op, pkt_addr))
939
            LOG.debug("Config read (0x%x):\n%s" %
940
                      (addr, _hex_print(resp, '0x%(addr)04x')))
941
            for i in range(0, len(payload) - 1):
942
                mem[addr + i] = payload[i]
943
            if self.status_fn:
944
                status = chirp_common.Status()
945
                status.cur = addr
946
                status.max = 0x8000
947
                status.msg = "Cloning from radio"
948
                self.status_fn(status)
949
        strmem = "".join([chr(x) for x in mem])
950
        return memmap.MemoryMap(strmem)
951

    
952
    def _do_upload(self):
953
        """Walk through the config map and write updated records to
954
        the radio. The config map contains only the regions we know
955
        about. We don't use the channel memory commands to avoid the
956
        hackery of using config write commands to fill in the last
957
        3 channel memory and names slots. As we discover other useful
958
        goodies in the map, we can add more slots...
959
        """
960
        for ar, size, count in config_map:
961
            for addr in range(ar, ar + (size*count), size):
962
                req = bytearray(struct.pack(">H", addr))
963
                req.extend(self.get_mmap()[addr:addr + size])
964
                self._write_record(CMD_WCONF, req)
965
                LOG.debug("Config write (0x%x):\n%s" %
966
                          (addr, _hex_print(req)))
967
                chksum_match, op, ack = self._read_record()
968
                LOG.debug("Config write ack [%x]\n%s" %
969
                          (addr, _hex_print(ack)))
970
                a = struct.unpack(">H", ack)  # big endian short...
971
                ack = a[0]
972
                if not chksum_match or op != CMD_WCONF or addr != ack:
973
                    msg = ""
974
                    if not chksum_match:
975
                        msg += "Checksum err, "
976
                    if op != CMD_WCONF:
977
                        msg += "cmd mismatch %x != %x, " % \
978
                               (op, CMD_WCONF)
979
                    if addr != ack:
980
                        msg += "ack error %x != %x, " % (addr, ack)
981
                    raise Exception("Radio did not ack block: %s error" % msg)
982
                if self.status_fn:
983
                    status = chirp_common.Status()
984
                    status.cur = addr
985
                    status.max = 0x8000
986
                    status.msg = "Update radio"
987
                    self.status_fn(status)
988

    
989
    def get_features(self):
990
        """ Public get_features
991
            Return the features of this radio once we have identified
992
            it and gotten its bits
993
            """
994
        rf = chirp_common.RadioFeatures()
995
        rf.has_settings = True
996
        rf.has_ctone = True
997
        rf.has_rx_dtcs = True
998
        rf.has_cross = True
999
        rf.has_tuning_step = False
1000
        rf.has_bank = False
1001
        rf.can_odd_split = True
1002
        rf.valid_skips = ["", "S"]
1003
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
1004
        rf.valid_cross_modes = [
1005
            "Tone->Tone",
1006
            "Tone->DTCS",
1007
            "DTCS->Tone",
1008
            "DTCS->",
1009
            "->Tone",
1010
            "->DTCS",
1011
            "DTCS->DTCS",
1012
        ]
1013
        rf.valid_modes = ["FM", "NFM", "AM"]
1014
        rf.valid_power_levels = self.POWER_LEVELS
1015
        rf.valid_name_length = 8
1016
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
1017
        rf.valid_bands = [(108000000, 136000000),  # Aircraft  AM
1018
                          (136000000, 180000000),  # supports 2m
1019
                          (230000000, 250000000),
1020
                          (350000000, 400000000),
1021
                          (400000000, 520000000),  # supports 70cm
1022
                          (700000000, 985000000)]
1023
        rf.valid_characters = chirp_common.CHARSET_ASCII
1024
        rf.valid_tuning_steps = STEPS
1025
        rf.memory_bounds = (1, 999)  # 999 memories
1026
        return rf
1027

    
1028
    @classmethod
1029
    def get_prompts(cls):
1030
        rp = chirp_common.RadioPrompts()
1031
        rp.experimental = ("This radio driver is currently under development. "
1032
                           "There are no known issues with it, but you should "
1033
                           "proceed with caution.")
1034
        return rp
1035

    
1036
    def get_raw_memory(self, number):
1037
        return repr(self._memobj.chan_blk[number])
1038

    
1039
    def _get_tone(self, _mem, mem):
1040
        """Decode both the encode and decode CTSS/DCS codes from
1041
        the memory channel and stuff them into the UI
1042
        memory channel row.
1043
        """
1044
        txtone = short2tone(_mem.encQT)
1045
        rxtone = short2tone(_mem.decQT)
1046
        pt = "N"
1047
        pr = "N"
1048

    
1049
        if txtone == "----":
1050
            txmode = ""
1051
        elif txtone[0] == "D":
1052
            mem.dtcs = int(txtone[1:4])
1053
            if txtone[4] == "I":
1054
                pt = "R"
1055
            txmode = "DTCS"
1056
        else:
1057
            mem.rtone = float(txtone)
1058
            txmode = "Tone"
1059

    
1060
        if rxtone == "----":
1061
            rxmode = ""
1062
        elif rxtone[0] == "D":
1063
            mem.rx_dtcs = int(rxtone[1:4])
1064
            if rxtone[4] == "I":
1065
                pr = "R"
1066
            rxmode = "DTCS"
1067
        else:
1068
            mem.ctone = float(rxtone)
1069
            rxmode = "Tone"
1070

    
1071
        if txmode == "Tone" and len(rxmode) == 0:
1072
            mem.tmode = "Tone"
1073
        elif (txmode == rxmode and txmode == "Tone" and
1074
              mem.rtone == mem.ctone):
1075
            mem.tmode = "TSQL"
1076
        elif (txmode == rxmode and txmode == "DTCS" and
1077
              mem.dtcs == mem.rx_dtcs):
1078
            mem.tmode = "DTCS"
1079
        elif (len(rxmode) + len(txmode)) > 0:
1080
            mem.tmode = "Cross"
1081
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
1082

    
1083
        mem.dtcs_polarity = pt + pr
1084

    
1085
        LOG.debug("_get_tone: Got TX %s (%i) RX %s (%i)" %
1086
                  (txmode, _mem.encQT, rxmode, _mem.decQT))
1087

    
1088
    def get_memory(self, number):
1089
        """ Public get_memory
1090
            Return the channel memory referenced by number to the UI.
1091
        """
1092
        _mem = self._memobj.chan_blk[number - 1]
1093
        _nam = self._memobj.chan_name[number - 1]
1094

    
1095
        mem = chirp_common.Memory()
1096
        mem.number = number
1097
        _valid = _mem.state
1098
        if _valid != MEM_VALID and _valid != 0 and _valid != 2:
1099
            # In Issue #6995 we can find _valid values of 0 and 2 in the IMG
1100
            # so these values should be treated like MEM_VALID.
1101
            mem.empty = True
1102
            return mem
1103
        else:
1104
            mem.empty = False
1105

    
1106
        mem.freq = int(_mem.rxfreq) * 10
1107

    
1108
        if _mem.txfreq == 0xFFFFFFFF:
1109
            # TX freq not set
1110
            mem.duplex = "off"
1111
            mem.offset = 0
1112
        elif int(_mem.rxfreq) == int(_mem.txfreq):
1113
            mem.duplex = ""
1114
            mem.offset = 0
1115
        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
1116
            mem.duplex = "split"
1117
            mem.offset = int(_mem.txfreq) * 10
1118
        else:
1119
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
1120
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
1121

    
1122
        mem.name = name2str(_nam.name)
1123

    
1124
        self._get_tone(_mem, mem)
1125

    
1126
        mem.skip = "" if bool(_mem.scan) else "S"
1127

    
1128
        mem.power = self.POWER_LEVELS[_mem.pwr]
1129
        if _mem.mod == 1:
1130
            mem.mode = "AM"
1131
        elif _mem.fm_dev == 0:
1132
            mem.mode = "FM"
1133
        else:
1134
            mem.mode = "NFM"
1135
        #  qt has no home in the UI
1136
        return mem
1137

    
1138
    def _set_tone(self, mem, _mem):
1139
        """Update the memory channel block CTCC/DCS tones
1140
        from the UI fields
1141
        """
1142
        def _set_dcs(code, pol):
1143
            val = int("%i" % code, 8) | 0x8000
1144
            if pol == "R":
1145
                val |= 0x4000
1146
            return val
1147

    
1148
        rx_mode = tx_mode = None
1149
        rxtone = txtone = 0x0000
1150

    
1151
        if mem.tmode == "Tone":
1152
            tx_mode = "Tone"
1153
            txtone = int(mem.rtone * 10)
1154
        elif mem.tmode == "TSQL":
1155
            rx_mode = tx_mode = "Tone"
1156
            rxtone = txtone = int(mem.ctone * 10)
1157
        elif mem.tmode == "DTCS":
1158
            tx_mode = rx_mode = "DTCS"
1159
            txtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
1160
            rxtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
1161
        elif mem.tmode == "Cross":
1162
            tx_mode, rx_mode = mem.cross_mode.split("->")
1163
            if tx_mode == "DTCS":
1164
                txtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
1165
            elif tx_mode == "Tone":
1166
                txtone = int(mem.rtone * 10)
1167
            if rx_mode == "DTCS":
1168
                rxtone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
1169
            elif rx_mode == "Tone":
1170
                rxtone = int(mem.ctone * 10)
1171

    
1172
        _mem.decQT = rxtone
1173
        _mem.encQT = txtone
1174

    
1175
        LOG.debug("Set TX %s (%i) RX %s (%i)" %
1176
                  (tx_mode, _mem.encQT, rx_mode, _mem.decQT))
1177

    
1178
    def set_memory(self, mem):
1179
        """ Public set_memory
1180
            Inverse of get_memory. Update the radio memory image
1181
            from the mem object
1182
            """
1183
        number = mem.number
1184

    
1185
        _mem = self._memobj.chan_blk[number - 1]
1186
        _nam = self._memobj.chan_name[number - 1]
1187

    
1188
        if mem.empty:
1189
            _mem.set_raw("\xFF" * (_mem.size() / 8))
1190
            _nam.name = str2name("", 8, '\0', '\0')
1191
            _mem.state = MEM_INVALID
1192
            return
1193

    
1194
        _mem.rxfreq = int(mem.freq / 10)
1195
        if mem.duplex == "off":
1196
            _mem.txfreq = 0xFFFFFFFF
1197
        elif mem.duplex == "split":
1198
            _mem.txfreq = int(mem.offset / 10)
1199
        elif mem.duplex == "+":
1200
            _mem.txfreq = int(mem.freq / 10) + int(mem.offset / 10)
1201
        elif mem.duplex == "-":
1202
            _mem.txfreq = int(mem.freq / 10) - int(mem.offset / 10)
1203
        else:
1204
            _mem.txfreq = int(mem.freq / 10)
1205
        _mem.scan = int(mem.skip != "S")
1206
        if mem.mode == "FM":
1207
            _mem.mod = 0    # make sure forced AM is off
1208
            _mem.fm_dev = 0
1209
        elif mem.mode == "NFM":
1210
            _mem.mod = 0
1211
            _mem.fm_dev = 1
1212
        elif mem.mode == "AM":
1213
            _mem.mod = 1     # AM on
1214
            _mem.fm_dev = 1  # set NFM bandwidth
1215
        else:
1216
            _mem.mod = 0
1217
            _mem.fm_dev = 0  # Catchall default is FM
1218
        # set the tone
1219
        self._set_tone(mem, _mem)
1220
        # set the power
1221
        if mem.power:
1222
            _mem.pwr = self.POWER_LEVELS.index(mem.power)
1223
        else:
1224
            _mem.pwr = True
1225

    
1226
        # Set fields we can't access via the UI table to safe defaults
1227
        _mem.qt = 0   # mute mode to QT
1228

    
1229
        _nam.name = str2name(mem.name, 8, '\0', '\0')
1230
        _mem.state = MEM_VALID
1231

    
1232
# Build the UI configuration tabs
1233
# the channel memory tab is built by the core.
1234
# We have no control over it
1235

    
1236
    def _core_tab(self):
1237
        """ Build Core Configuration tab
1238
        Radio settings common to all modes and areas go here.
1239
        """
1240
        s = self._memobj.settings
1241

    
1242
        cf = RadioSettingGroup("cfg_grp", "Configuration")
1243

    
1244
        cf.append(RadioSetting("auto_am",
1245
                               "Auto detect AM(53)",
1246
                               RadioSettingValueBoolean(s.auto_am)))
1247
        cf.append(RadioSetting("qt_sw",
1248
                               "Scan tone detect(59)",
1249
                               RadioSettingValueBoolean(s.qt_sw)))
1250
        cf.append(
1251
            RadioSetting("s_mute",
1252
                         "SubFreq Mute(60)",
1253
                         RadioSettingValueList(S_MUTE_LIST,
1254
                                               S_MUTE_LIST[s.s_mute])))
1255
        cf.append(
1256
            RadioSetting("tot",
1257
                         "Transmit timeout Timer(10)",
1258
                         RadioSettingValueList(TIMEOUT_LIST,
1259
                                               TIMEOUT_LIST[s.tot])))
1260
        cf.append(
1261
            RadioSetting("toa",
1262
                         "Transmit Timeout Alarm(11)",
1263
                         RadioSettingValueList(TOA_LIST,
1264
                                               TOA_LIST[s.toa])))
1265
        cf.append(
1266
            RadioSetting("ptt_id",
1267
                         "PTT Caller ID mode(23)",
1268
                         RadioSettingValueList(PTTID_LIST,
1269
                                               PTTID_LIST[s.ptt_id])))
1270
        cf.append(
1271
            RadioSetting("id_dly",
1272
                         "Caller ID Delay time(25)",
1273
                         RadioSettingValueList(ID_DLY_LIST,
1274
                                               ID_DLY_LIST[s.id_dly])))
1275
        cf.append(RadioSetting("voice_sw",
1276
                               "Voice Guide(12)",
1277
                               RadioSettingValueBoolean(s.voice_sw)))
1278
        cf.append(RadioSetting("beep",
1279
                               "Keypad Beep(13)",
1280
                               RadioSettingValueBoolean(s.beep)))
1281
        cf.append(
1282
            RadioSetting("s_tone",
1283
                         "Side Tone(36)",
1284
                         RadioSettingValueList(S_TONES,
1285
                                               S_TONES[s.s_tone])))
1286
        cf.append(
1287
            RadioSetting("ring_time",
1288
                         "Ring Time(26)",
1289
                         RadioSettingValueList(
1290
                             LIST_OFF_10,
1291
                             LIST_OFF_10[s.ring_time])))
1292
        cf.append(
1293
            RadioSetting("roger",
1294
                         "Roger Beep(9)",
1295
                         RadioSettingValueList(ROGER_LIST,
1296
                                               ROGER_LIST[s.roger])))
1297
        cf.append(RadioSetting("blcdsw",
1298
                               "Backlight(41)",
1299
                               RadioSettingValueBoolean(s.blcdsw)))
1300
        cf.append(
1301
            RadioSetting("abr",
1302
                         "Auto Backlight Time(1)",
1303
                         RadioSettingValueList(BACKLIGHT_LIST,
1304
                                               BACKLIGHT_LIST[s.abr])))
1305
        cf.append(
1306
            RadioSetting("abr_lvl",
1307
                         "Backlight Brightness(27)",
1308
                         RadioSettingValueList(LIST_1_5,
1309
                                               LIST_1_5[s.abr_lvl])))
1310
        cf.append(RadioSetting("lock",
1311
                               "Keypad Lock",
1312
                               RadioSettingValueBoolean(s.lock)))
1313
        cf.append(
1314
            RadioSetting("lock_m",
1315
                         "Keypad Lock Mode(35)",
1316
                         RadioSettingValueList(LOCK_MODES,
1317
                                               LOCK_MODES[s.lock_m])))
1318
        cf.append(RadioSetting("auto_lk",
1319
                               "Keypad Autolock(34)",
1320
                               RadioSettingValueBoolean(s.auto_lk)))
1321
        cf.append(RadioSetting("prich_sw",
1322
                               "Priority Channel Scan(33)",
1323
                               RadioSettingValueBoolean(s.prich_sw)))
1324
        cf.append(RadioSetting("pri_ch",
1325
                               "Priority Channel(32)",
1326
                               RadioSettingValueInteger(1, 999,
1327
                                                        s.pri_ch)))
1328
        cf.append(
1329
            RadioSetting("dtmf_st",
1330
                         "DTMF Sidetone(22)",
1331
                         RadioSettingValueList(DTMFST_LIST,
1332
                                               DTMFST_LIST[s.dtmf_st])))
1333
        cf.append(RadioSetting("sc_qt",
1334
                               "Scan QT Save Mode(38)",
1335
                               RadioSettingValueList(
1336
                                   SCQT_LIST,
1337
                                   SCQT_LIST[s.sc_qt])))
1338
        cf.append(
1339
            RadioSetting("apo_tmr",
1340
                         "Automatic Power-off(39)",
1341
                         RadioSettingValueList(APO_TIMES,
1342
                                               APO_TIMES[s.apo_tmr])))
1343
        cf.append(  # VOX "guard" is really VOX trigger audio level
1344
            RadioSetting("vox_grd",
1345
                         "VOX level(7)",
1346
                         RadioSettingValueList(VOX_GRDS,
1347
                                               VOX_GRDS[s.vox_grd])))
1348
        cf.append(
1349
            RadioSetting("vox_dly",
1350
                         "VOX Delay(37)",
1351
                         RadioSettingValueList(VOX_DLYS,
1352
                                               VOX_DLYS[s.vox_dly])))
1353
        cf.append(
1354
            RadioSetting("lang",
1355
                         "Menu Language(14)",
1356
                         RadioSettingValueList(LANGUAGE_LIST,
1357
                                               LANGUAGE_LIST[s.lang])))
1358
        cf.append(RadioSetting("ponmsg",
1359
                               "Poweron message(40)",
1360
                               RadioSettingValueList(
1361
                                   PONMSG_LIST, PONMSG_LIST[s.ponmsg])))
1362
        cf.append(RadioSetting("bledsw",
1363
                               "Receive LED(42)",
1364
                               RadioSettingValueBoolean(s.bledsw)))
1365
        return cf
1366

    
1367
    def _repeater_tab(self):
1368
        """Repeater mode functions
1369
        """
1370
        s = self._memobj.settings
1371
        cf = RadioSettingGroup("repeater", "Repeater Functions")
1372

    
1373
        cf.append(
1374
            RadioSetting("type_set",
1375
                         "Radio Mode(43)",
1376
                         RadioSettingValueList(
1377
                             RPTMODE_LIST,
1378
                             RPTMODE_LIST[s.type_set])))
1379
        cf.append(RadioSetting("rpt_ptt",
1380
                               "Repeater PTT(45)",
1381
                               RadioSettingValueBoolean(s.rpt_ptt)))
1382
        cf.append(RadioSetting("rpt_spk",
1383
                               "Repeater Mode Speaker(44)",
1384
                               RadioSettingValueBoolean(s.rpt_spk)))
1385
        cf.append(
1386
            RadioSetting("rpt_kpt",
1387
                         "Repeater Hold Time(46)",
1388
                         RadioSettingValueList(RPT_KPTS,
1389
                                               RPT_KPTS[s.rpt_kpt])))
1390
        cf.append(RadioSetting("rpt_rct",
1391
                               "Repeater Receipt Tone(47)",
1392
                               RadioSettingValueBoolean(s.rpt_rct)))
1393
        return cf
1394

    
1395
    def _admin_tab(self):
1396
        """Admin functions not present in radio menu...
1397
        These are admin functions not radio operation configuration
1398
        """
1399

    
1400
        def apply_cid(setting, obj):
1401
            c = str2callid(setting.value)
1402
            obj.code = c
1403

    
1404
        def apply_scc(setting, obj):
1405
            c = str2digits(setting.value)
1406
            obj.scc = c
1407

    
1408
        def apply_mode_sw(setting, obj):
1409
            pw = str2pw(setting.value)
1410
            obj.mode_sw = pw
1411
            setting.value = pw2str(obj.mode_sw)
1412

    
1413
        def apply_reset(setting, obj):
1414
            pw = str2pw(setting.value)
1415
            obj.reset = pw
1416
            setting.value = pw2str(obj.reset)
1417

    
1418
        def apply_wake(setting, obj):
1419
            obj.wake = int(setting.value)/10
1420

    
1421
        def apply_sleep(setting, obj):
1422
            obj.sleep = int(setting.value)/10
1423

    
1424
        pw = self._memobj.passwords  # admin passwords
1425
        s = self._memobj.settings
1426

    
1427
        cf = RadioSettingGroup("admin", "Admin Functions")
1428

    
1429
        cf.append(RadioSetting("menu_avail",
1430
                               "Menu available in channel mode",
1431
                               RadioSettingValueBoolean(s.menu_avail)))
1432
        mode_sw = RadioSettingValueString(0, 6,
1433
                                          pw2str(pw.mode_sw), False)
1434
        rs = RadioSetting("passwords.mode_sw",
1435
                          "Mode Switch Password", mode_sw)
1436
        rs.set_apply_callback(apply_mode_sw, pw)
1437
        cf.append(rs)
1438

    
1439
        cf.append(RadioSetting("reset_avail",
1440
                               "Radio Reset Available",
1441
                               RadioSettingValueBoolean(s.reset_avail)))
1442
        reset = RadioSettingValueString(0, 6, pw2str(pw.reset), False)
1443
        rs = RadioSetting("passwords.reset",
1444
                          "Radio Reset Password", reset)
1445
        rs.set_apply_callback(apply_reset, pw)
1446
        cf.append(rs)
1447

    
1448
        cf.append(
1449
            RadioSetting("dtmf_tx",
1450
                         "DTMF Tx Duration",
1451
                         RadioSettingValueList(DTMF_TIMES,
1452
                                               DTMF_TIMES[s.dtmf_tx])))
1453
        cid = self._memobj.my_callid
1454
        my_callid = RadioSettingValueString(3, 6,
1455
                                            callid2str(cid.code), False)
1456
        rs = RadioSetting("my_callid.code",
1457
                          "PTT Caller ID code(24)", my_callid)
1458
        rs.set_apply_callback(apply_cid, cid)
1459
        cf.append(rs)
1460

    
1461
        stun = self._memobj.stun
1462
        st = RadioSettingValueString(0, 6, digits2str(stun.scc), False)
1463
        rs = RadioSetting("stun.scc", "Security code", st)
1464
        rs.set_apply_callback(apply_scc, stun)
1465
        cf.append(rs)
1466

    
1467
        cf.append(
1468
            RadioSetting("settings.save_m",
1469
                         "Save Mode (2)",
1470
                         RadioSettingValueList(SAVE_MODES,
1471
                                               SAVE_MODES[s.save_m])))
1472
        for i in range(0, 4):
1473
            sm = self._memobj.save[i]
1474
            wake = RadioSettingValueInteger(0, 18000, sm.wake * 10, 1)
1475
            wf = RadioSetting("save[%i].wake" % i,
1476
                              "Save Mode %d Wake Time" % (i+1), wake)
1477
            wf.set_apply_callback(apply_wake, sm)
1478
            cf.append(wf)
1479

    
1480
            slp = RadioSettingValueInteger(0, 18000, sm.sleep * 10, 1)
1481
            wf = RadioSetting("save[%i].sleep" % i,
1482
                              "Save Mode %d Sleep Time" % (i+1), slp)
1483
            wf.set_apply_callback(apply_sleep, sm)
1484
            cf.append(wf)
1485

    
1486
        _msg = str(self._memobj.display.banner).split("\0")[0]
1487
        val = RadioSettingValueString(0, 16, _msg)
1488
        val.set_mutable(True)
1489
        cf.append(RadioSetting("display.banner",
1490
                               "Display Message", val))
1491
        return cf
1492

    
1493
    def _fm_tab(self):
1494
        """FM Broadcast channels
1495
        """
1496
        def apply_fm(setting, obj):
1497
            f = freq2short(setting.value, 76000000, 108000000)
1498
            obj.fm_freq = f
1499

    
1500
        fm = RadioSettingGroup("fm_chans", "FM Broadcast")
1501
        for ch in range(0, 20):
1502
            chan = self._memobj.fm_chans[ch]
1503
            freq = RadioSettingValueString(0, 20,
1504
                                           short2freq(chan.fm_freq))
1505
            rs = RadioSetting("fm_%d" % (ch + 1),
1506
                              "FM Channel %d" % (ch + 1), freq)
1507
            rs.set_apply_callback(apply_fm, chan)
1508
            fm.append(rs)
1509
        return fm
1510

    
1511
    def _scan_grp(self):
1512
        """Scan groups
1513
        """
1514
        def apply_name(setting, obj):
1515
            name = str2name(setting.value, 8, '\0', '\0')
1516
            obj.name = name
1517

    
1518
        def apply_start(setting, obj):
1519
            """Do a callback to deal with RadioSettingInteger limitation
1520
            on memory address resolution
1521
            """
1522
            obj.scan_st = int(setting.value)
1523

    
1524
        def apply_end(setting, obj):
1525
            """Do a callback to deal with RadioSettingInteger limitation
1526
            on memory address resolution
1527
            """
1528
            obj.scan_end = int(setting.value)
1529

    
1530
        sgrp = self._memobj.scn_grps
1531
        scan = RadioSettingGroup("scn_grps", "Channel Scanner Groups")
1532
        for i in range(0, 10):
1533
            s_grp = sgrp.addrs[i]
1534
            s_name = sgrp.names[i]
1535
            rs_name = RadioSettingValueString(0, 8,
1536
                                              name2str(s_name.name))
1537
            rs = RadioSetting("scn_grps.names[%i].name" % i,
1538
                              "Group %i Name" % (i + 1), rs_name)
1539
            rs.set_apply_callback(apply_name, s_name)
1540
            scan.append(rs)
1541
            rs_st = RadioSettingValueInteger(1, 999, s_grp.scan_st)
1542
            rs = RadioSetting("scn_grps.addrs[%i].scan_st" % i,
1543
                              "Starting Channel", rs_st)
1544
            rs.set_apply_callback(apply_start, s_grp)
1545
            scan.append(rs)
1546
            rs_end = RadioSettingValueInteger(1, 999, s_grp.scan_end)
1547
            rs = RadioSetting("scn_grps.addrs[%i].scan_end" % i,
1548
                              "Last Channel", rs_end)
1549
            rs.set_apply_callback(apply_end, s_grp)
1550
            scan.append(rs)
1551
        return scan
1552

    
1553
    def _callid_grp(self):
1554
        """Caller IDs to be recognized by radio
1555
        This really should be a table in the UI
1556
        """
1557
        def apply_callid(setting, obj):
1558
            c = str2callid(setting.value)
1559
            obj.cid = c
1560

    
1561
        def apply_name(setting, obj):
1562
            name = str2name(setting.value, 6, '\0', '\xff')
1563
            obj.name = name
1564

    
1565
        cid = RadioSettingGroup("callids", "Caller IDs")
1566
        for i in range(0, 20):
1567
            callid = self._memobj.call_ids[i]
1568
            name = self._memobj.cid_names[i]
1569
            c_name = RadioSettingValueString(0, 6, name2str(name.name))
1570
            rs = RadioSetting("cid_names[%i].name" % i,
1571
                              "Caller ID %i Name" % (i + 1), c_name)
1572
            rs.set_apply_callback(apply_name, name)
1573
            cid.append(rs)
1574
            c_id = RadioSettingValueString(0, 6,
1575
                                           callid2str(callid.cid),
1576
                                           False)
1577
            rs = RadioSetting("call_ids[%i].cid" % i,
1578
                              "Caller ID Code", c_id)
1579
            rs.set_apply_callback(apply_callid, callid)
1580
            cid.append(rs)
1581
        return cid
1582

    
1583
    def _band_tab(self, area, band):
1584
        """ Build a band tab inside a VFO/Area
1585
        """
1586
        def apply_freq(setting, lo, hi, obj):
1587
            f = freq2int(setting.value, lo, hi)
1588
            obj.freq = f/10
1589

    
1590
        def apply_offset(setting, obj):
1591
            f = freq2int(setting.value, 0, 5000000)
1592
            obj.offset = f/10
1593

    
1594
        def apply_enc(setting, obj):
1595
            t = tone2short(setting.value)
1596
            obj.encqt = t
1597

    
1598
        def apply_dec(setting, obj):
1599
            t = tone2short(setting.value)
1600
            obj.decqt = t
1601

    
1602
        if area == "a":
1603
            if band == 150:
1604
                c = self._memobj.vfo_a.band_150
1605
                lo = 108000000
1606
                hi = 180000000
1607
            elif band == 200:
1608
                c = self._memobj.vfo_a.band_200
1609
                lo = 230000000
1610
                hi = 250000000
1611
            elif band == 300:
1612
                c = self._memobj.vfo_a.band_300
1613
                lo = 350000000
1614
                hi = 400000000
1615
            elif band == 450:
1616
                c = self._memobj.vfo_a.band_450
1617
                lo = 400000000
1618
                hi = 512000000
1619
            else:   # 700
1620
                c = self._memobj.vfo_a.band_700
1621
                lo = 700000000
1622
                hi = 985000000
1623
        else:  # area 'b'
1624
            if band == 150:
1625
                c = self._memobj.vfo_b.band_150
1626
                lo = 136000000
1627
                hi = 180000000
1628
            else:  # 450
1629
                c = self._memobj.vfo_b.band_450
1630
                lo = 400000000
1631
                hi = 512000000
1632

    
1633
        prefix = "vfo_%s.band_%d" % (area, band)
1634
        bf = RadioSettingGroup(prefix, "%dMHz Band" % band)
1635
        freq = RadioSettingValueString(0, 15, int2freq(c.freq * 10))
1636
        rs = RadioSetting(prefix + ".freq", "Rx Frequency", freq)
1637
        rs.set_apply_callback(apply_freq, lo, hi, c)
1638
        bf.append(rs)
1639

    
1640
        off = RadioSettingValueString(0, 15, int2freq(c.offset * 10))
1641
        rs = RadioSetting(prefix + ".offset", "Tx Offset(28)", off)
1642
        rs.set_apply_callback(apply_offset, c)
1643
        bf.append(rs)
1644

    
1645
        rs = RadioSetting(prefix + ".encqt",
1646
                          "Encode QT(17,19)",
1647
                          RadioSettingValueList(TONE_LIST,
1648
                                                short2tone(c.encqt)))
1649
        rs.set_apply_callback(apply_enc, c)
1650
        bf.append(rs)
1651

    
1652
        rs = RadioSetting(prefix + ".decqt",
1653
                          "Decode QT(16,18)",
1654
                          RadioSettingValueList(TONE_LIST,
1655
                                                short2tone(c.decqt)))
1656
        rs.set_apply_callback(apply_dec, c)
1657
        bf.append(rs)
1658

    
1659
        bf.append(RadioSetting(prefix + ".qt",
1660
                               "Mute Mode(21)",
1661
                               RadioSettingValueList(SPMUTE_LIST,
1662
                                                     SPMUTE_LIST[c.qt])))
1663
        bf.append(RadioSetting(prefix + ".scan",
1664
                               "Scan this(48)",
1665
                               RadioSettingValueBoolean(c.scan)))
1666
        bf.append(RadioSetting(prefix + ".pwr",
1667
                               "Power(5)",
1668
                               RadioSettingValueList(
1669
                                   POWER_LIST, POWER_LIST[c.pwr])))
1670
        bf.append(RadioSetting(prefix + ".mod",
1671
                               "AM Modulation(54)",
1672
                               RadioSettingValueBoolean(c.mod)))
1673
        bf.append(RadioSetting(prefix + ".fm_dev",
1674
                               "FM Deviation(4)",
1675
                               RadioSettingValueList(
1676
                                   BANDWIDTH_LIST,
1677
                                   BANDWIDTH_LIST[c.fm_dev])))
1678
        bf.append(
1679
            RadioSetting(prefix + ".shift",
1680
                         "Frequency Shift(6)",
1681
                         RadioSettingValueList(OFFSET_LIST,
1682
                                               OFFSET_LIST[c.shift])))
1683
        return bf
1684

    
1685
    def _area_tab(self, area):
1686
        """Build a VFO tab
1687
        """
1688
        def apply_scan_st(setting, scan_lo, scan_hi, obj):
1689
            f = freq2short(setting.value, scan_lo, scan_hi)
1690
            obj.scan_st = f
1691

    
1692
        def apply_scan_end(setting, scan_lo, scan_hi, obj):
1693
            f = freq2short(setting.value, scan_lo, scan_hi)
1694
            obj.scan_end = f
1695

    
1696
        if area == "a":
1697
            desc = "Area A Settings"
1698
            c = self._memobj.a_conf
1699
            scan_lo = 108000000
1700
            scan_hi = 985000000
1701
            scan_rng = self._memobj.settings.a
1702
            band_list = (150, 200, 300, 450, 700)
1703
        else:
1704
            desc = "Area B Settings"
1705
            c = self._memobj.b_conf
1706
            scan_lo = 136000000
1707
            scan_hi = 512000000
1708
            scan_rng = self._memobj.settings.b
1709
            band_list = (150, 450)
1710

    
1711
        prefix = "%s_conf" % area
1712
        af = RadioSettingGroup(prefix, desc)
1713
        af.append(
1714
            RadioSetting(prefix + ".w_mode",
1715
                         "Workmode",
1716
                         RadioSettingValueList(
1717
                             WORKMODE_LIST,
1718
                             WORKMODE_LIST[c.w_mode])))
1719
        af.append(RadioSetting(prefix + ".w_chan",
1720
                               "Channel",
1721
                               RadioSettingValueInteger(1, 999,
1722
                                                        c.w_chan)))
1723
        af.append(
1724
            RadioSetting(prefix + ".scan_grp",
1725
                         "Scan Group(49)",
1726
                         RadioSettingValueList(
1727
                             SCANGRP_LIST,
1728
                             SCANGRP_LIST[c.scan_grp])))
1729
        af.append(RadioSetting(prefix + ".bcl",
1730
                               "Busy Channel Lock-out(15)",
1731
                               RadioSettingValueBoolean(c.bcl)))
1732
        af.append(
1733
            RadioSetting(prefix + ".sql",
1734
                         "Squelch Level(8)",
1735
                         RadioSettingValueList(LIST_0_9,
1736
                                               LIST_0_9[c.sql])))
1737
        af.append(
1738
            RadioSetting(prefix + ".cset",
1739
                         "Call ID Group(52)",
1740
                         RadioSettingValueList(LIST_1_20,
1741
                                               LIST_1_20[c.cset])))
1742
        af.append(
1743
            RadioSetting(prefix + ".step",
1744
                         "Frequency Step(3)",
1745
                         RadioSettingValueList(
1746
                             STEP_LIST, STEP_LIST[c.step])))
1747
        af.append(
1748
            RadioSetting(prefix + ".scan_mode",
1749
                         "Scan Mode(20)",
1750
                         RadioSettingValueList(
1751
                             SCANMODE_LIST,
1752
                             SCANMODE_LIST[c.scan_mode])))
1753
        af.append(
1754
            RadioSetting(prefix + ".scan_range",
1755
                         "Scan Range(50)",
1756
                         RadioSettingValueList(
1757
                             SCANRANGE_LIST,
1758
                             SCANRANGE_LIST[c.scan_range])))
1759
        st = RadioSettingValueString(0, 15,
1760
                                     short2freq(scan_rng.scan_st))
1761
        rs = RadioSetting("settings.%s.scan_st" % area,
1762
                          "Frequency Scan Start", st)
1763
        rs.set_apply_callback(apply_scan_st, scan_lo, scan_hi, scan_rng)
1764
        af.append(rs)
1765

    
1766
        end = RadioSettingValueString(0, 15,
1767
                                      short2freq(scan_rng.scan_end))
1768
        rs = RadioSetting("settings.%s.scan_end" % area,
1769
                          "Frequency Scan End", end)
1770
        rs.set_apply_callback(apply_scan_end, scan_lo, scan_hi,
1771
                              scan_rng)
1772
        af.append(rs)
1773
        # Each area has its own set of bands
1774
        for band in (band_list):
1775
            af.append(self._band_tab(area, band))
1776
        return af
1777

    
1778
    def _key_tab(self):
1779
        """Build radio key/button menu
1780
        """
1781
        s = self._memobj.settings
1782
        kf = RadioSettingGroup("key_grp", "Key Settings")
1783

    
1784
        kf.append(RadioSetting("settings.pf1",
1785
                               "PF1 Key function(55)",
1786
                               RadioSettingValueList(
1787
                                   PF1KEY_LIST,
1788
                                   PF1KEY_LIST[s.pf1])))
1789
        kf.append(RadioSetting("settings.pf2",
1790
                               "PF2 Key function(56)",
1791
                               RadioSettingValueList(
1792
                                   PF2KEY_LIST,
1793
                                   PF2KEY_LIST[s.pf2])))
1794
        kf.append(RadioSetting("settings.pf3",
1795
                               "PF3 Key function(57)",
1796
                               RadioSettingValueList(
1797
                                   PF3KEY_LIST,
1798
                                   PF3KEY_LIST[s.pf3])))
1799
        return kf
1800

    
1801
    def _get_settings(self):
1802
        """Build the radio configuration settings menus
1803
        """
1804

    
1805
        core_grp = self._core_tab()
1806
        fm_grp = self._fm_tab()
1807
        area_a_grp = self._area_tab("a")
1808
        area_b_grp = self._area_tab("b")
1809
        key_grp = self._key_tab()
1810
        scan_grp = self._scan_grp()
1811
        callid_grp = self._callid_grp()
1812
        admin_grp = self._admin_tab()
1813
        rpt_grp = self._repeater_tab()
1814

    
1815
        core_grp.append(key_grp)
1816
        core_grp.append(admin_grp)
1817
        core_grp.append(rpt_grp)
1818
        group = RadioSettings(core_grp,
1819
                              area_a_grp,
1820
                              area_b_grp,
1821
                              fm_grp,
1822
                              scan_grp,
1823
                              callid_grp
1824
                              )
1825
        return group
1826

    
1827
    def get_settings(self):
1828
        """ Public build out linkage between radio settings and UI
1829
        """
1830
        try:
1831
            return self._get_settings()
1832
        except Exception:
1833
            import traceback
1834
            LOG.error("Failed to parse settings: %s",
1835
                      traceback.format_exc())
1836
            return None
1837

    
1838
    def _is_freq(self, element):
1839
        """This is a hack to smoke out whether we need to do
1840
        frequency translations for otherwise innocent u16s and u32s
1841
        """
1842
        return "rxfreq" in element.get_name() or \
1843
               "txfreq" in element.get_name() or \
1844
               "scan_st" in element.get_name() or \
1845
               "scan_end" in element.get_name() or \
1846
               "offset" in element.get_name() or \
1847
               "fm_stop" in element.get_name()
1848

    
1849
    def set_settings(self, settings):
1850
        """ Public update radio settings via UI callback
1851
        A lot of this should be in common code....
1852
        """
1853

    
1854
        for element in settings:
1855
            if not isinstance(element, RadioSetting):
1856
                LOG.debug("set_settings: not instance %s" %
1857
                          element.get_name())
1858
                self.set_settings(element)
1859
                continue
1860
            else:
1861
                try:
1862
                    if "." in element.get_name():
1863
                        bits = element.get_name().split(".")
1864
                        obj = self._memobj
1865
                        for bit in bits[:-1]:
1866
                            # decode an array index
1867
                            if "[" in bit and "]" in bit:
1868
                                bit, index = bit.split("[", 1)
1869
                                index, junk = index.split("]", 1)
1870
                                index = int(index)
1871
                                obj = getattr(obj, bit)[index]
1872
                            else:
1873
                                obj = getattr(obj, bit)
1874
                        setting = bits[-1]
1875
                    else:
1876
                        obj = self._memobj.settings
1877
                        setting = element.get_name()
1878

    
1879
                    if element.has_apply_callback():
1880
                        LOG.debug("Using apply callback")
1881
                        element.run_apply_callback()
1882
                    else:
1883
                        LOG.debug("Setting %s = %s" %
1884
                                  (setting, element.value))
1885
                        if self._is_freq(element):
1886
                            setattr(obj, setting, int(element.value)/10)
1887
                        else:
1888
                            setattr(obj, setting, element.value)
1889
                except Exception, e:
1890
                    LOG.debug("set_settings: Exception with %s" %
1891
                              element.get_name())
1892
                    raise
(7-7/7)