Project

General

Profile

Bug #4175 » kguv8d.py

Test Driver for Wouxun KG-UV8D Tx Inhibit - Jim Unroe, 11/01/2016 03:15 PM

 
1
# Copyright 2014 Ron Wellsted <ron@m0rnw.uk> M0RNW
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 3 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
"""Wouxun KG-UV8D radio management module"""
17

    
18
import time
19
import os
20
import logging
21
from chirp import util, chirp_common, bitwise, memmap, errors, directory
22
from chirp.settings import RadioSetting, RadioSettingGroup, \
23
                RadioSettingValueBoolean, RadioSettingValueList, \
24
                RadioSettingValueInteger, RadioSettingValueString, \
25
                RadioSettings
26

    
27
LOG = logging.getLogger(__name__)
28

    
29
CMD_ID = 128
30
CMD_END = 129
31
CMD_RD = 130
32
CMD_WR = 131
33

    
34
MEM_VALID = 158
35

    
36
AB_LIST = ["A", "B"]
37
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 50.0, 100.0]
38
STEP_LIST = [str(x) for x in STEPS]
39
ROGER_LIST = ["Off", "BOT", "EOT", "Both"]
40
TIMEOUT_LIST = ["Off"] + [str(x) + "s" for x in range(15, 901, 15)]
41
VOX_LIST = ["Off"] + ["%s" % x for x in range(1, 10)]
42
BANDWIDTH_LIST = ["Narrow", "Wide"]
43
VOICE_LIST = ["Off", "On"]
44
LANGUAGE_LIST = ["Chinese", "English"]
45
SCANMODE_LIST = ["TO", "CO", "SE"]
46
PF1KEY_LIST = ["Call", "VFTX"]
47
PF3KEY_LIST = ["Scan", "Lamp", "Tele Alarm", "SOS-CH", "Radio", "Disable"]
48
WORKMODE_LIST = ["VFO", "Channel No.", "Ch. No.+Freq.", "Ch. No.+Name"]
49
BACKLIGHT_LIST = ["Always On"] + [str(x) + "s" for x in range(1, 21)] + \
50
                ["Always Off"]
51
OFFSET_LIST = ["+", "-"]
52
PONMSG_LIST = ["Bitmap", "Battery Volts"]
53
SPMUTE_LIST = ["QT", "QT+DTMF", "QT*DTMF"]
54
DTMFST_LIST = ["DT-ST", "ANI-ST", "DT-ANI", "Off"]
55
DTMF_TIMES = ["%s" % x for x in range(50, 501, 10)]
56
RPTSET_LIST = ["X-TWRPT", "X-DIRRPT"]
57
ALERTS = [1750, 2100, 1000, 1450]
58
ALERTS_LIST = [str(x) for x in ALERTS]
59
PTTID_LIST = ["BOT", "EOT", "Both"]
60
LIST_10 = ["Off"] + ["%s" % x for x in range(1, 11)]
61
SCANGRP_LIST = ["All"] + ["%s" % x for x in range(1, 11)]
62
SCQT_LIST = ["All", "Decoder", "Encoder"]
63
SMUTESET_LIST = ["Off", "Tx", "Rx", "Tx/Rx"]
64
POWER_LIST = ["Lo", "Hi"]
65
HOLD_TIMES = ["Off"] + ["%s" % x for x in range(100, 5001, 100)]
66
RPTMODE_LIST = ["Radio", "Repeater"]
67

    
68
# memory slot 0 is not used, start at 1 (so need 1000 slots, not 999)
69
# structure elements whose name starts with x are currently unidentified
70
_MEM_FORMAT = """
71
    #seekto 0x0044;
72
    struct {
73
        u32    rx_start;
74
        u32    rx_stop;
75
        u32    tx_start;
76
        u32    tx_stop;
77
    } uhf_limits;
78

    
79
    #seekto 0x0054;
80
    struct {
81
        u32    rx_start;
82
        u32    rx_stop;
83
        u32    tx_start;
84
        u32    tx_stop;
85
    } vhf_limits;
86

    
87
    #seekto 0x0400;
88
    struct {
89
        u8     model[8];
90
        u8     unknown[2];
91
        u8     oem1[10];
92
        u8     oem2[10];
93
        u8     unknown2[8];
94
        u8     version[10];
95
        u8     unknown3[6];
96
        u8     date[8];
97
    } oem_info;
98

    
99
    #seekto 0x0480;
100
    struct {
101
        u16    lower;
102
        u16    upper;
103
    } scan_groups[10];
104

    
105
    #seekto 0x0500;
106
    struct {
107
        u8    call_code[6];
108
    } call_groups[20];
109

    
110
    #seekto 0x0580;
111
    struct {
112
        char    call_name[6];
113
    } call_group_name[20];
114

    
115
    #seekto 0x0800;
116
    struct {
117
        u8      ponmsg;
118
        char    dispstr[15];
119
        u8 x0810;
120
        u8 x0811;
121
        u8 x0812;
122
        u8 x0813;
123
        u8 x0814;
124
        u8      voice;
125
        u8      timeout;
126
        u8      toalarm;
127
        u8      channel_menu;
128
        u8      power_save;
129
        u8      autolock;
130
        u8      keylock;
131
        u8      beep;
132
        u8      stopwatch;
133
        u8      vox;
134
        u8      scan_rev;
135
        u8      backlight;
136
        u8      roger_beep;
137
        u8      mode_sw_pwd[6];
138
        u8      reset_pwd[6];
139
        u16     pri_ch;
140
        u8      ani_sw;
141
        u8      ptt_delay;
142
        u8      ani[6];
143
        u8      dtmf_st;
144
        u8      bcl_a;
145
        u8      bcl_b;
146
        u8      ptt_id;
147
        u8      prich_sw;
148
        u8      rpt_set;
149
        u8      rpt_spk;
150
        u8      rpt_ptt;
151
        u8      alert;
152
        u8      pf1_func;
153
        u8      pf3_func;
154
        u8      workmode_b;
155
        u8      workmode_a;
156
        u8 x0845;
157
        u8      dtmf_tx_time;
158
        u8      dtmf_interval;
159
        u8      main_ab;
160
        u16     work_cha;
161
        u16     work_chb;
162
        u8 x084d;
163
        u8 x084e;
164
        u8 x084f;
165
        u8 x0850;
166
        u8 x0851;
167
        u8 x0852;
168
        u8 x0853;
169
        u8 x0854;
170
        u8      rpt_mode;
171
        u8      language;
172
        u8 x0857;
173
        u8 x0858;
174
        u8 x0859;
175
        u8 x085a;
176
        u8 x085b;
177
        u8 x085c;
178
        u8 x085d;
179
        u8 x085e;
180
        u8      single_display;
181
        u8      ring_time;
182
        u8      scg_a;
183
        u8      scg_b;
184
        u8 x0863;
185
        u8      rpt_tone;
186
        u8      rpt_hold;
187
        u8      scan_det;
188
        u8      sc_qt;
189
        u8 x0868;
190
        u8      smuteset;
191
        u8      callcode;
192
    } settings;
193

    
194
    #seekto 0x0880;
195
    struct {
196
        u32     rxfreq;
197
        u32     txoffset;
198
        u16     rxtone;
199
        u16     txtone;
200
        u8      unknown1:6,
201
                power:1,
202
                unknown2:1;
203
        u8      unknown3:1,
204
                shift_dir:2
205
                unknown4:2,
206
                mute_mode:2,
207
                iswide:1;
208
        u8      step;
209
        u8      squelch;
210
      } vfoa;
211

    
212
    #seekto 0x08c0;
213
    struct {
214
        u32     rxfreq;
215
        u32     txoffset;
216
        u16     rxtone;
217
        u16     txtone;
218
        u8      unknown1:6,
219
                power:1,
220
                unknown2:1;
221
        u8      unknown3:1,
222
                shift_dir:2
223
                unknown4:2,
224
                mute_mode:2,
225
                iswide:1;
226
        u8      step;
227
        u8      squelch;
228
    } vfob;
229

    
230
    #seekto 0x0900;
231
    struct {
232
        u32     rxfreq;
233
        u32     txfreq;
234
        u16     rxtone;
235
        u16     txtone;
236
        u8      unknown1:6,
237
                power:1,
238
                unknown2:1;
239
        u8      unknown3:2,
240
                scan_add:1,
241
                unknown4:2,
242
                mute_mode:2,
243
                iswide:1;
244
        u16     padding;
245
    } memory[1000];
246

    
247
    #seekto 0x4780;
248
    struct {
249
        u8    name[8];
250
    } names[1000];
251

    
252
    #seekto 0x6700;
253
    u8          valid[1000];
254
    """
255

    
256
# Support for the Wouxun KG-UV8D radio
257
# Serial coms are at 19200 baud
258
# The data is passed in variable length records
259
# Record structure:
260
#  Offset   Usage
261
#    0      start of record (\x7d)
262
#    1      Command (\x80 Identify \x81 End/Reboot \x82 Read \x83 Write)
263
#    2      direction (\xff PC-> Radio, \x00 Radio -> PC)
264
#    3      length of payload (excluding header/checksum) (n)
265
#    4      payload (n bytes)
266
#    4+n+1  checksum - byte sum (% 256) of bytes 1 -> 4+n
267
#
268
# Memory Read Records:
269
# the payload is 3 bytes, first 2 are offset (big endian),
270
# 3rd is number of bytes to read
271
# Memory Write Records:
272
# the maximum payload size (from the Wouxun software) seems to be 66 bytes
273
#  (2 bytes location + 64 bytes data).
274

    
275

    
276
@directory.register
277
class KGUV8DRadio(chirp_common.CloneModeRadio,
278
                  chirp_common.ExperimentalRadio):
279
    """Wouxun KG-UV8D"""
280
    VENDOR = "Wouxun"
281
    MODEL = "KG-UV8D"
282
    _model = "KG-UV8D"
283
    _file_ident = "KGUV8D"
284
    BAUD_RATE = 19200
285
    POWER_LEVELS = [chirp_common.PowerLevel("L", watts=1),
286
                    chirp_common.PowerLevel("H", watts=5)]
287
    _mmap = ""
288

    
289
    def _checksum(self, data):
290
        cs = 0
291
        for byte in data:
292
            cs += ord(byte)
293
        return cs % 256
294

    
295
    def _write_record(self, cmd, payload=None):
296
        # build the packet
297
        _packet = '\x7d' + chr(cmd) + '\xff'
298
        _length = 0
299
        if payload:
300
            _length = len(payload)
301
        # update the length field
302
        _packet += chr(_length)
303
        if payload:
304
            # add the chars to the packet
305
            _packet += payload
306
        # calculate and add the checksum to the packet
307
        _packet += chr(self._checksum(_packet[1:]))
308
        LOG.debug("Sent:\n%s" % util.hexprint(_packet))
309
        self.pipe.write(_packet)
310

    
311
    def _read_record(self):
312
        # read 4 chars for the header
313
        _header = self.pipe.read(4)
314
        if len(_header) != 4:
315
            raise errors.RadioError('Radio did not respond')
316
        _length = ord(_header[3])
317
        _packet = self.pipe.read(_length)
318
        _cs = self._checksum(_header[1:])
319
        _cs += self._checksum(_packet)
320
        _cs %= 256
321
        _rcs = ord(self.pipe.read(1))
322
        LOG.debug("_cs =%x", _cs)
323
        LOG.debug("_rcs=%x", _rcs)
324
        return (_rcs != _cs, _packet)
325

    
326
# Identify the radio
327
#
328
# A Gotcha: the first identify packet returns a bad checksum, subsequent
329
# attempts return the correct checksum... (well it does on my radio!)
330
#
331
# The ID record returned by the radio also includes the current frequency range
332
# as 4 bytes big-endian in 10Hz increments
333
#
334
# Offset
335
#  0:10     Model, zero padded (Use first 7 chars for 'KG-UV8D')
336
#  11:14    UHF rx lower limit (in units of 10Hz)
337
#  15:18    UHF rx upper limit
338
#  19:22    UHF tx lower limit
339
#  23:26    UHF tx upper limit
340
#  27:30    VHF rx lower limit
341
#  31:34    VHF rx upper limit
342
#  35:38    VHF tx lower limit
343
#  39:42    VHF tx upper limit
344
##
345
    @classmethod
346
    def match_model(cls, filedata, filename):
347
        return cls._file_ident in filedata[0x400:0x408]
348

    
349
    def _identify(self):
350
        """Do the identification dance"""
351
        for _i in range(0, 10):
352
            self._write_record(CMD_ID)
353
            _chksum_err, _resp = self._read_record()
354
            LOG.debug("Got:\n%s" % util.hexprint(_resp))
355
            if _chksum_err:
356
                LOG.error("Checksum error: retrying ident...")
357
                time.sleep(0.100)
358
                continue
359
            LOG.debug("Model %s" % util.hexprint(_resp[0:7]))
360
            if _resp[0:7] == self._model:
361
                return
362
            if len(_resp) == 0:
363
                raise Exception("Radio not responding")
364
            else:
365
                raise Exception("Unable to identify radio")
366

    
367
    def _finish(self):
368
        self._write_record(CMD_END)
369

    
370
    def process_mmap(self):
371
        self._memobj = bitwise.parse(_MEM_FORMAT, self._mmap)
372

    
373
    def sync_in(self):
374
        try:
375
            self._mmap = self._download()
376
        except errors.RadioError:
377
            raise
378
        except Exception, e:
379
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
380
        self.process_mmap()
381

    
382
    def sync_out(self):
383
        self._upload()
384

    
385
    # TODO: This is a dumb, brute force method of downlolading the memory.
386
    # it would be smarter to only load the active areas and none of
387
    # the padding/unused areas.
388
    def _download(self):
389
        """Talk to a wouxun KG-UV8D and do a download"""
390
        try:
391
            self._identify()
392
            return self._do_download(0, 32768, 64)
393
        except errors.RadioError:
394
            raise
395
        except Exception, e:
396
            LOG.exception('Unknown error during download process')
397
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
398

    
399
    def _do_download(self, start, end, blocksize):
400
        # allocate & fill memory
401
        image = ""
402
        for i in range(start, end, blocksize):
403
            req = chr(i / 256) + chr(i % 256) + chr(blocksize)
404
            self._write_record(CMD_RD, req)
405
            cs_error, resp = self._read_record()
406
            if cs_error:
407
                # TODO: probably should retry a few times here
408
                LOG.debug(util.hexprint(resp))
409
                raise Exception("Checksum error on read")
410
            LOG.debug("Got:\n%s" % util.hexprint(resp))
411
            image += resp[2:]
412
            if self.status_fn:
413
                status = chirp_common.Status()
414
                status.cur = i
415
                status.max = end
416
                status.msg = "Cloning from radio"
417
                self.status_fn(status)
418
        self._finish()
419
        return memmap.MemoryMap(''.join(image))
420

    
421
    def _upload(self):
422
        """Talk to a wouxun KG-UV8D and do a upload"""
423
        try:
424
            self._identify()
425
            self._do_upload(0, 32768, 64)
426
        except errors.RadioError:
427
            raise
428
        except Exception, e:
429
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
430
        return
431

    
432
    def _do_upload(self, start, end, blocksize):
433
        ptr = start
434
        for i in range(start, end, blocksize):
435
            req = chr(i / 256) + chr(i % 256)
436
            chunk = self.get_mmap()[ptr:ptr + blocksize]
437
            self._write_record(CMD_WR, req + chunk)
438
            LOG.debug(util.hexprint(req + chunk))
439
            cserr, ack = self._read_record()
440
            LOG.debug(util.hexprint(ack))
441
            j = ord(ack[0]) * 256 + ord(ack[1])
442
            if cserr or j != ptr:
443
                raise Exception("Radio did not ack block %i" % ptr)
444
            ptr += blocksize
445
            if self.status_fn:
446
                status = chirp_common.Status()
447
                status.cur = i
448
                status.max = end
449
                status.msg = "Cloning to radio"
450
                self.status_fn(status)
451
        self._finish()
452

    
453
    def get_features(self):
454
        # TODO: This probably needs to be setup correctly to match the true
455
        # features of the radio
456
        rf = chirp_common.RadioFeatures()
457
        rf.has_settings = True
458
        rf.has_ctone = True
459
        rf.has_rx_dtcs = True
460
        rf.has_cross = True
461
        rf.has_tuning_step = False
462
        rf.has_bank = False
463
        rf.can_odd_split = True
464
        rf.valid_skips = ["", "S"]
465
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
466
        rf.valid_cross_modes = [
467
                        "Tone->Tone",
468
                        "Tone->DTCS",
469
                        "DTCS->Tone",
470
                        "DTCS->",
471
                        "->Tone",
472
                        "->DTCS",
473
                        "DTCS->DTCS",
474
                    ]
475
        rf.valid_modes = ["FM", "NFM"]
476
        rf.valid_power_levels = self.POWER_LEVELS
477
        rf.valid_name_length = 8
478
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
479
        rf.valid_bands = [(134000000, 175000000),  # supports 2m
480
                          (400000000, 520000000)]  # supports 70cm
481
        rf.valid_characters = chirp_common.CHARSET_ASCII
482
        rf.memory_bounds = (1, 999)  # 999 memories
483
        return rf
484

    
485
    @classmethod
486
    def get_prompts(cls):
487
        rp = chirp_common.RadioPrompts()
488
        rp.experimental = ("This radio driver is currently under development. "
489
                           "There are no known issues with it, but you should "
490
                           "proceed with caution.")
491
        return rp
492

    
493
    def get_raw_memory(self, number):
494
        return repr(self._memobj.memory[number])
495

    
496
    def _get_tone(self, _mem, mem):
497
        def _get_dcs(val):
498
            code = int("%03o" % (val & 0x07FF))
499
            pol = (val & 0x8000) and "R" or "N"
500
            return code, pol
501

    
502
        tpol = False
503
        if _mem.txtone != 0xFFFF and (_mem.txtone & 0x2800) == 0x2800:
504
            tcode, tpol = _get_dcs(_mem.txtone)
505
            mem.dtcs = tcode
506
            txmode = "DTCS"
507
        elif _mem.txtone != 0xFFFF and _mem.txtone != 0x0:
508
            mem.rtone = (_mem.txtone & 0x7fff) / 10.0
509
            txmode = "Tone"
510
        else:
511
            txmode = ""
512

    
513
        rpol = False
514
        if _mem.rxtone != 0xFFFF and (_mem.rxtone & 0x2800) == 0x2800:
515
            rcode, rpol = _get_dcs(_mem.rxtone)
516
            mem.rx_dtcs = rcode
517
            rxmode = "DTCS"
518
        elif _mem.rxtone != 0xFFFF and _mem.rxtone != 0x0:
519
            mem.ctone = (_mem.rxtone & 0x7fff) / 10.0
520
            rxmode = "Tone"
521
        else:
522
            rxmode = ""
523

    
524
        if txmode == "Tone" and not rxmode:
525
            mem.tmode = "Tone"
526
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
527
            mem.tmode = "TSQL"
528
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
529
            mem.tmode = "DTCS"
530
        elif rxmode or txmode:
531
            mem.tmode = "Cross"
532
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
533

    
534
        # always set it even if no dtcs is used
535
        mem.dtcs_polarity = "%s%s" % (tpol or "N", rpol or "N")
536

    
537
        LOG.debug("Got TX %s (%i) RX %s (%i)" %
538
                  (txmode, _mem.txtone, rxmode, _mem.rxtone))
539

    
540
    def get_memory(self, number):
541
        _mem = self._memobj.memory[number]
542
        _nam = self._memobj.names[number]
543

    
544
        mem = chirp_common.Memory()
545
        mem.number = number
546
        _valid = self._memobj.valid[mem.number]
547

    
548
        LOG.debug("%d %s", number, _valid == MEM_VALID)
549
        if _valid != MEM_VALID:
550
            mem.empty = True
551
            return mem
552
        else:
553
            mem.empty = False
554

    
555
        mem.freq = int(_mem.rxfreq) * 10
556

    
557
        if _mem.txfreq == 0xFFFFFFFF:
558
            # TX freq not set
559
            mem.duplex = "off"
560
            mem.offset = 0
561
        elif int(_mem.rxfreq) == int(_mem.txfreq):
562
            mem.duplex = ""
563
            mem.offset = 0
564
        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
565
            mem.duplex = "split"
566
            mem.offset = int(_mem.txfreq) * 10
567
        else:
568
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
569
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
570

    
571
        for char in _nam.name:
572
            if char != 0:
573
                mem.name += chr(char)
574
        mem.name = mem.name.rstrip()
575

    
576
        self._get_tone(_mem, mem)
577

    
578
        mem.skip = "" if bool(_mem.scan_add) else "S"
579

    
580
        mem.power = self.POWER_LEVELS[_mem.power]
581
        mem.mode = _mem.iswide and "FM" or "NFM"
582
        return mem
583

    
584
    def _set_tone(self, mem, _mem):
585
        def _set_dcs(code, pol):
586
            val = int("%i" % code, 8) + 0x2800
587
            if pol == "R":
588
                val += 0x8000
589
            return val
590

    
591
        if mem.tmode == "Cross":
592
            tx_mode, rx_mode = mem.cross_mode.split("->")
593
        elif mem.tmode == "Tone":
594
            tx_mode = mem.tmode
595
            rx_mode = None
596
        else:
597
            tx_mode = rx_mode = mem.tmode
598

    
599
        if tx_mode == "DTCS":
600
            _mem.txtone = mem.tmode != "DTCS" and \
601
                _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) or \
602
                _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[0])
603
            _mem.txtone += 0x4000
604
        elif tx_mode:
605
            _mem.txtone = tx_mode == "Tone" and \
606
                int(mem.rtone * 10) or int(mem.ctone * 10)
607
            _mem.txtone += 0x8000
608
        else:
609
            _mem.txtone = 0
610

    
611
        if rx_mode == "DTCS":
612
            _mem.rxtone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
613
            _mem.rxtone += 0x4000
614
        elif rx_mode:
615
            _mem.rxtone = int(mem.ctone * 10)
616
            _mem.rxtone += 0x8000
617
        else:
618
            _mem.rxtone = 0
619

    
620
        LOG.debug("Set TX %s (%i) RX %s (%i)" %
621
                  (tx_mode, _mem.txtone, rx_mode, _mem.rxtone))
622

    
623
    def set_memory(self, mem):
624
        number = mem.number
625

    
626
        _mem = self._memobj.memory[number]
627
        _nam = self._memobj.names[number]
628

    
629
        if mem.empty:
630
            _mem.set_raw("\x00" * (_mem.size() / 8))
631
            self._memobj.valid[number] = 0
632
            self._memobj.names[number].set_raw("\x00" * (_nam.size() / 8))
633
            return
634

    
635
        _mem.rxfreq = int(mem.freq / 10)
636
        if mem.duplex == "off":
637
            _mem.txfreq = 0xFFFFFFFF
638
        elif mem.duplex == "split":
639
            _mem.txfreq = int(mem.offset / 10)
640
        elif mem.duplex == "+":
641
            _mem.txfreq = int(mem.freq / 10) + int(mem.offset / 10)
642
        elif mem.duplex == "-":
643
            _mem.txfreq = int(mem.freq / 10) - int(mem.offset / 10)
644
        else:
645
            _mem.txfreq = int(mem.freq / 10)
646
        _mem.scan_add = int(mem.skip != "S")
647
        _mem.iswide = int(mem.mode == "FM")
648
        # set the tone
649
        self._set_tone(mem, _mem)
650
        # set the power
651
        if mem.power:
652
            _mem.power = self.POWER_LEVELS.index(mem.power)
653
        else:
654
            _mem.power = True
655
        # TODO: set the correct mute mode, for now just
656
        # set to mute mode to QT (not QT+DTMF or QT*DTMF)
657
        _mem.mute_mode = 0
658

    
659
        for i in range(0, len(_nam.name)):
660
            if i < len(mem.name) and mem.name[i]:
661
                _nam.name[i] = ord(mem.name[i])
662
            else:
663
                _nam.name[i] = 0x0
664
        self._memobj.valid[mem.number] = MEM_VALID
665

    
666
    def _get_settings(self):
667
        _settings = self._memobj.settings
668
        _vfoa = self._memobj.vfoa
669
        _vfob = self._memobj.vfob
670
        cfg_grp = RadioSettingGroup("cfg_grp", "Configuration")
671
        vfoa_grp = RadioSettingGroup("vfoa_grp", "VFO A Settings")
672
        vfob_grp = RadioSettingGroup("vfob_grp", "VFO B Settings")
673
        key_grp = RadioSettingGroup("key_grp", "Key Settings")
674
        lmt_grp = RadioSettingGroup("lmt_grp", "Frequency Limits")
675
        oem_grp = RadioSettingGroup("oem_grp", "OEM Info")
676

    
677
        group = RadioSettings(cfg_grp, vfoa_grp, vfob_grp,
678
                              key_grp, lmt_grp, oem_grp)
679

    
680
        #
681
        # Configuration Settings
682
        #
683
        rs = RadioSetting("channel_menu", "Menu available in channel mode",
684
                          RadioSettingValueBoolean(_settings.channel_menu))
685
        cfg_grp.append(rs)
686
        rs = RadioSetting("ponmsg", "Poweron message",
687
                        RadioSettingValueList(
688
                            PONMSG_LIST, PONMSG_LIST[_settings.ponmsg]))
689
        cfg_grp.append(rs)
690
        rs = RadioSetting("voice", "Voice Guide",
691
                        RadioSettingValueBoolean(_settings.voice))
692
        cfg_grp.append(rs)
693
        rs = RadioSetting("language", "Language",
694
                        RadioSettingValueList(LANGUAGE_LIST,
695
                            LANGUAGE_LIST[_settings.language]))
696
        cfg_grp.append(rs)
697
        rs = RadioSetting("timeout", "Timeout Timer",
698
                        RadioSettingValueInteger(15, 900,
699
                            _settings.timeout * 15, 15))
700
        cfg_grp.append(rs)
701
        rs = RadioSetting("toalarm", "Timeout Alarm",
702
                        RadioSettingValueInteger(0, 10, _settings.toalarm))
703
        cfg_grp.append(rs)
704
        rs = RadioSetting("roger_beep", "Roger Beep",
705
                        RadioSettingValueBoolean(_settings.roger_beep))
706
        cfg_grp.append(rs)
707
        rs = RadioSetting("power_save", "Power save",
708
                        RadioSettingValueBoolean(_settings.power_save))
709
        cfg_grp.append(rs)
710
        rs = RadioSetting("autolock", "Autolock",
711
                        RadioSettingValueBoolean(_settings.autolock))
712
        cfg_grp.append(rs)
713
        rs = RadioSetting("keylock", "Keypad Lock",
714
                        RadioSettingValueBoolean(_settings.keylock))
715
        cfg_grp.append(rs)
716
        rs = RadioSetting("beep", "Keypad Beep",
717
                        RadioSettingValueBoolean(_settings.beep))
718
        cfg_grp.append(rs)
719
        rs = RadioSetting("stopwatch", "Stopwatch",
720
                        RadioSettingValueBoolean(_settings.stopwatch))
721
        cfg_grp.append(rs)
722
        rs = RadioSetting("backlight", "Backlight",
723
                        RadioSettingValueList(BACKLIGHT_LIST,
724
                            BACKLIGHT_LIST[_settings.backlight]))
725
        cfg_grp.append(rs)
726
        rs = RadioSetting("dtmf_st", "DTMF Sidetone",
727
                        RadioSettingValueList(DTMFST_LIST,
728
                            DTMFST_LIST[_settings.dtmf_st]))
729
        cfg_grp.append(rs)
730
        rs = RadioSetting("ani-id_sw", "ANI-ID Switch",
731
                        RadioSettingValueBoolean(_settings.ani_sw))
732
        cfg_grp.append(rs)
733
        rs = RadioSetting("ptt-id_delay", "PTT-ID Delay",
734
                        RadioSettingValueList(PTTID_LIST,
735
                            PTTID_LIST[_settings.ptt_id]))
736
        cfg_grp.append(rs)
737
        rs = RadioSetting("ring_time", "Ring Time",
738
                        RadioSettingValueList(LIST_10,
739
                            LIST_10[_settings.ring_time]))
740
        cfg_grp.append(rs)
741
        rs = RadioSetting("scan_rev", "Scan Mode",
742
                        RadioSettingValueList(SCANMODE_LIST,
743
                            SCANMODE_LIST[_settings.scan_rev]))
744
        cfg_grp.append(rs)
745
        rs = RadioSetting("vox", "VOX",
746
                        RadioSettingValueList(LIST_10,
747
                            LIST_10[_settings.vox]))
748
        cfg_grp.append(rs)
749
        rs = RadioSetting("prich_sw", "Priority Channel Switch",
750
                        RadioSettingValueBoolean(_settings.prich_sw))
751
        cfg_grp.append(rs)
752
        rs = RadioSetting("pri_ch", "Priority Channel",
753
                        RadioSettingValueInteger(1, 999, _settings.pri_ch))
754
        cfg_grp.append(rs)
755
        rs = RadioSetting("rpt_mode", "Radio Mode",
756
                        RadioSettingValueList(RPTMODE_LIST,
757
                            RPTMODE_LIST[_settings.rpt_mode]))
758
        cfg_grp.append(rs)
759
        rs = RadioSetting("rpt_set", "Repeater Setting",
760
                        RadioSettingValueList(RPTSET_LIST,
761
                            RPTSET_LIST[_settings.rpt_set]))
762
        cfg_grp.append(rs)
763
        rs = RadioSetting("rpt_spk", "Repeater Mode Speaker",
764
                        RadioSettingValueBoolean(_settings.rpt_spk))
765
        cfg_grp.append(rs)
766
        rs = RadioSetting("rpt_ptt", "Repeater PTT",
767
                        RadioSettingValueBoolean(_settings.rpt_ptt))
768
        cfg_grp.append(rs)
769
        rs = RadioSetting("dtmf_tx_time", "DTMF Tx Duration",
770
                        RadioSettingValueList(DTMF_TIMES,
771
                            DTMF_TIMES[_settings.dtmf_tx_time]))
772
        cfg_grp.append(rs)
773
        rs = RadioSetting("dtmf_interval", "DTMF Interval",
774
                        RadioSettingValueList(DTMF_TIMES,
775
                            DTMF_TIMES[_settings.dtmf_interval]))
776
        cfg_grp.append(rs)
777
        rs = RadioSetting("alert", "Alert Tone",
778
                        RadioSettingValueList(ALERTS_LIST,
779
                            ALERTS_LIST[_settings.alert]))
780
        cfg_grp.append(rs)
781
        rs = RadioSetting("rpt_tone", "Repeater Tone",
782
                        RadioSettingValueBoolean(_settings.rpt_tone))
783
        cfg_grp.append(rs)
784
        rs = RadioSetting("rpt_hold", "Repeater Hold Time",
785
                        RadioSettingValueList(HOLD_TIMES,
786
                            HOLD_TIMES[_settings.rpt_hold]))
787
        cfg_grp.append(rs)
788
        rs = RadioSetting("scan_det", "Scan DET",
789
                        RadioSettingValueBoolean(_settings.scan_det))
790
        cfg_grp.append(rs)
791
        rs = RadioSetting("sc_qt", "SC-QT",
792
                        RadioSettingValueList(SCQT_LIST,
793
                            SCQT_LIST[_settings.smuteset]))
794
        cfg_grp.append(rs)
795
        rs = RadioSetting("smuteset", "SubFreq Mute",
796
                        RadioSettingValueList(SMUTESET_LIST,
797
                            SMUTESET_LIST[_settings.smuteset]))
798
        cfg_grp.append(rs)
799
        _pwd = "".join(map(chr, _settings.mode_sw_pwd))
800
        val = RadioSettingValueString(0, 6, _pwd)
801
        val.set_mutable(True)
802
        rs = RadioSetting("mode_sw_pwd", "Mode Switch Password", val)
803
        cfg_grp.append(rs)
804
        _pwd = "".join(map(chr, _settings.reset_pwd))
805
        val = RadioSettingValueString(0, 6, _pwd)
806
        val.set_mutable(True)
807
        rs = RadioSetting("reset_pwd", "Reset Password", val)
808
        cfg_grp.append(rs)
809
        #
810
        # VFO A Settings
811
        #
812
        rs = RadioSetting("vfoa_mode", "VFO A Workmode",
813
                        RadioSettingValueList(WORKMODE_LIST,
814
                              WORKMODE_LIST[_settings.workmode_a]))
815
        vfoa_grp.append(rs)
816
        rs = RadioSetting("vfoa_chan", "VFO A Channel",
817
                        RadioSettingValueInteger(1, 999, _settings.work_cha))
818
        vfoa_grp.append(rs)
819
        rs = RadioSetting("rxfreqa", "VFO A Rx Frequency",
820
                        RadioSettingValueInteger(
821
                              134000000, 520000000, _vfoa.rxfreq * 10, 5000))
822
        vfoa_grp.append(rs)
823
        rs = RadioSetting("txoffa", "VFO A Tx Offset",
824
                        RadioSettingValueInteger(
825
                              0, 520000000, _vfoa.txoffset * 10, 5000))
826
        vfoa_grp.append(rs)
827
        #   u16   rxtone;
828
        #   u16   txtone;
829
        rs = RadioSetting("vfoa_power", "VFO A Power",
830
                        RadioSettingValueList(
831
                              POWER_LIST, POWER_LIST[_vfoa.power]))
832
        vfoa_grp.append(rs)
833
        #         shift_dir:2
834
        rs = RadioSetting("vfoa_iswide", "VFO A NBFM",
835
                        RadioSettingValueList(
836
                              BANDWIDTH_LIST, BANDWIDTH_LIST[_vfoa.iswide]))
837
        vfoa_grp.append(rs)
838
        rs = RadioSetting("vfoa_mute_mode", "VFO A Mute",
839
                        RadioSettingValueList(
840
                              SPMUTE_LIST, SPMUTE_LIST[_vfoa.mute_mode]))
841
        vfoa_grp.append(rs)
842
        rs = RadioSetting("vfoa_step", "VFO A Step (kHz)",
843
                        RadioSettingValueList(
844
                              STEP_LIST, STEP_LIST[_vfoa.step]))
845
        vfoa_grp.append(rs)
846
        rs = RadioSetting("vfoa_squelch", "VFO A Squelch",
847
                        RadioSettingValueList(
848
                              LIST_10, LIST_10[_vfoa.squelch]))
849
        vfoa_grp.append(rs)
850
        rs = RadioSetting("bcl_a", "Busy Channel Lock-out A",
851
                        RadioSettingValueBoolean(_settings.bcl_a))
852
        vfoa_grp.append(rs)
853
        #
854
        # VFO B Settings
855
        #
856
        rs = RadioSetting("vfob_mode", "VFO B Workmode",
857
                          RadioSettingValueList(
858
                              WORKMODE_LIST,
859
                              WORKMODE_LIST[_settings.workmode_b]))
860
        vfob_grp.append(rs)
861
        rs = RadioSetting("vfob_chan", "VFO B Channel",
862
                          RadioSettingValueInteger(1, 999, _settings.work_chb))
863
        vfob_grp.append(rs)
864
        rs = RadioSetting("rxfreqb", "VFO B Rx Frequency",
865
                          RadioSettingValueInteger(
866
                              134000000, 520000000, _vfob.rxfreq * 10, 5000))
867
        vfob_grp.append(rs)
868
        rs = RadioSetting("txoffb", "VFO B Tx Offset",
869
                          RadioSettingValueInteger(
870
                              0, 520000000, _vfob.txoffset * 10, 5000))
871
        vfob_grp.append(rs)
872
        #   u16   rxtone;
873
        #   u16   txtone;
874
        rs = RadioSetting("vfob_power", "VFO B Power",
875
                          RadioSettingValueList(
876
                              POWER_LIST, POWER_LIST[_vfob.power]))
877
        vfob_grp.append(rs)
878
        #         shift_dir:2
879
        rs = RadioSetting("vfob_iswide", "VFO B NBFM",
880
                          RadioSettingValueList(
881
                              BANDWIDTH_LIST, BANDWIDTH_LIST[_vfob.iswide]))
882
        vfob_grp.append(rs)
883
        rs = RadioSetting("vfob_mute_mode", "VFO B Mute",
884
                          RadioSettingValueList(
885
                              SPMUTE_LIST, SPMUTE_LIST[_vfob.mute_mode]))
886
        vfob_grp.append(rs)
887
        rs = RadioSetting("vfob_step", "VFO B Step (kHz)",
888
                          RadioSettingValueList(
889
                              STEP_LIST, STEP_LIST[_vfob.step]))
890
        vfob_grp.append(rs)
891
        rs = RadioSetting("vfob_squelch", "VFO B Squelch",
892
                          RadioSettingValueList(
893
                              LIST_10, LIST_10[_vfob.squelch]))
894
        vfob_grp.append(rs)
895
        rs = RadioSetting("bcl_b", "Busy Channel Lock-out B",
896
                        RadioSettingValueBoolean(_settings.bcl_b))
897
        vfob_grp.append(rs)
898
        #
899
        # Key Settings
900
        #
901
        _msg = str(_settings.dispstr).split("\0")[0]
902
        val = RadioSettingValueString(0, 15, _msg)
903
        val.set_mutable(True)
904
        rs = RadioSetting("dispstr", "Display Message", val)
905
        key_grp.append(rs)
906
        _ani = ""
907
        for i in _settings.ani:
908
            if i < 10:
909
                _ani += chr(i + 0x30)
910
            else:
911
                break
912
        val = RadioSettingValueString(0, 6, _ani)
913
        val.set_mutable(True)
914
        rs = RadioSetting("ani", "ANI code", val)
915
        key_grp.append(rs)
916
        rs = RadioSetting("pf1_func", "PF1 Key function",
917
                          RadioSettingValueList(
918
                              PF1KEY_LIST,
919
                              PF1KEY_LIST[self._memobj.settings.pf1_func]))
920
        key_grp.append(rs)
921
        rs = RadioSetting("pf3_func", "PF3 Key function",
922
                          RadioSettingValueList(
923
                              PF3KEY_LIST,
924
                              PF3KEY_LIST[self._memobj.settings.pf3_func]))
925
        key_grp.append(rs)
926

    
927
        #
928
        # Scan Group Settings
929
        #
930
        # settings:
931
        #   u8    scg_a;
932
        #   u8    scg_b;
933
        #
934
        #   struct {
935
        #       u16    lower;
936
        #       u16    upper;
937
        #   } scan_groups[10];
938

    
939
        #
940
        # Call group settings
941
        #
942

    
943
        #
944
        # Limits settings
945
        #
946
        rs = RadioSetting("urx_start", "UHF RX Lower Limit",
947
                          RadioSettingValueInteger(
948
                              400000000, 520000000,
949
                              self._memobj.uhf_limits.rx_start * 10, 5000))
950
        lmt_grp.append(rs)
951
        rs = RadioSetting("urx_stop", "UHF RX Upper Limit",
952
                          RadioSettingValueInteger(
953
                              400000000, 520000000,
954
                              self._memobj.uhf_limits.rx_stop * 10, 5000))
955
        lmt_grp.append(rs)
956
        rs = RadioSetting("utx_start", "UHF TX Lower Limit",
957
                          RadioSettingValueInteger(
958
                              400000000, 520000000,
959
                              self._memobj.uhf_limits.tx_start * 10, 5000))
960
        lmt_grp.append(rs)
961
        rs = RadioSetting("utx_stop", "UHF TX Upper Limit",
962
                          RadioSettingValueInteger(
963
                              400000000, 520000000,
964
                              self._memobj.uhf_limits.tx_stop * 10, 5000))
965
        lmt_grp.append(rs)
966
        rs = RadioSetting("vrx_start", "VHF RX Lower Limit",
967
                          RadioSettingValueInteger(
968
                              134000000, 174997500,
969
                              self._memobj.vhf_limits.rx_start * 10, 5000))
970
        lmt_grp.append(rs)
971
        rs = RadioSetting("vrx_stop", "VHF RX Upper Limit",
972
                          RadioSettingValueInteger(
973
                              134000000, 174997500,
974
                              self._memobj.vhf_limits.rx_stop * 10, 5000))
975
        lmt_grp.append(rs)
976
        rs = RadioSetting("vtx_start", "VHF TX Lower Limit",
977
                          RadioSettingValueInteger(
978
                              134000000, 174997500,
979
                              self._memobj.vhf_limits.tx_start * 10, 5000))
980
        lmt_grp.append(rs)
981
        rs = RadioSetting("vtx_stop", "VHF TX Upper Limit",
982
                          RadioSettingValueInteger(
983
                              134000000, 174997500,
984
                              self._memobj.vhf_limits.tx_stop * 10, 5000))
985
        lmt_grp.append(rs)
986

    
987
        #
988
        # OEM info
989
        #
990
        def _decode(lst):
991
            _str = ''.join([chr(c) for c in lst
992
                            if chr(c) in chirp_common.CHARSET_ASCII])
993
            return _str
994

    
995
        _str = _decode(self._memobj.oem_info.model)
996
        val = RadioSettingValueString(0, 15, _str)
997
        val.set_mutable(False)
998
        rs = RadioSetting("model", "Model", val)
999
        oem_grp.append(rs)
1000
        _str = _decode(self._memobj.oem_info.oem1)
1001
        val = RadioSettingValueString(0, 15, _str)
1002
        val.set_mutable(False)
1003
        rs = RadioSetting("oem1", "OEM String 1", val)
1004
        oem_grp.append(rs)
1005
        _str = _decode(self._memobj.oem_info.oem2)
1006
        val = RadioSettingValueString(0, 15, _str)
1007
        val.set_mutable(False)
1008
        rs = RadioSetting("oem2", "OEM String 2", val)
1009
        oem_grp.append(rs)
1010
        _str = _decode(self._memobj.oem_info.version)
1011
        val = RadioSettingValueString(0, 15, _str)
1012
        val.set_mutable(False)
1013
        rs = RadioSetting("version", "Software Version", val)
1014
        oem_grp.append(rs)
1015
        _str = _decode(self._memobj.oem_info.date)
1016
        val = RadioSettingValueString(0, 15, _str)
1017
        val.set_mutable(False)
1018
        rs = RadioSetting("date", "OEM Date", val)
1019
        oem_grp.append(rs)
1020

    
1021
        return group
1022

    
1023
    def get_settings(self):
1024
        try:
1025
            return self._get_settings()
1026
        except:
1027
            import traceback
1028
            LOG.error("Failed to parse settings: %s", traceback.format_exc())
1029
            return None
(2-2/2)