Project

General

Profile

Bug #9081 » kguv8d.py

Fixes Tone Decoding - Mel Terechenok, 02/21/2024 02:45 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 logging
20
from chirp import util, chirp_common, bitwise, memmap, errors, directory
21
from chirp.settings import RadioSetting, RadioSettingGroup, \
22
    RadioSettingValueBoolean, RadioSettingValueList, \
23
    RadioSettingValueInteger, RadioSettingValueString, \
24
    RadioSettings
25
import struct
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

    
280
    """Wouxun KG-UV8D"""
281
    VENDOR = "Wouxun"
282
    MODEL = "KG-UV8D"
283
    _model = b"KG-UV8D"
284
    _file_ident = b"KGUV8D"
285
    BAUD_RATE = 19200
286
    POWER_LEVELS = [chirp_common.PowerLevel("L", watts=1),
287
                    chirp_common.PowerLevel("H", watts=5)]
288
    NEEDS_COMPAT_SERIAL = False
289
    _record_start = 0x7D
290

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

    
297
    def _write_record(self, cmd, payload=None):
298
        # build the packet
299
        _length = 0
300
        if payload:
301
            _length = len(payload)
302
        _packet = struct.pack('BBBB', self._record_start, cmd, 0xFF, _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 += bytes([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 = struct.unpack('xxxB', _header)[0]
317
        _packet = self.pipe.read(_length)
318
        _cs = self._checksum(_header[1:])
319
        _cs += self._checksum(_packet)
320
        _cs %= 256
321
        _rcs = self.pipe.read(1)[0]
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 10 Hz 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 10 Hz)
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 as 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 downloading 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 as 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 = b""
402
        for i in range(start, end, blocksize):
403
            req = struct.pack('>HB', i, 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.MemoryMapBytes(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 as 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 = struct.pack('>H', i)
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 = struct.unpack('>H', ack)[0]
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_tuning_steps = STEPS
467
        rf.valid_cross_modes = [
468
            "Tone->Tone",
469
            "Tone->DTCS",
470
            "DTCS->Tone",
471
            "DTCS->",
472
            "->Tone",
473
            "->DTCS",
474
            "DTCS->DTCS",
475
        ]
476
        rf.valid_modes = ["FM", "NFM"]
477
        rf.valid_power_levels = self.POWER_LEVELS
478
        rf.valid_name_length = 8
479
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
480
        rf.valid_bands = [(134000000, 175000000),  # supports 2m
481
                          (400000000, 520000000)]  # supports 70cm
482
        rf.valid_characters = chirp_common.CHARSET_ASCII
483
        rf.memory_bounds = (1, 999)  # 999 memories
484
        return rf
485

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

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

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

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

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

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

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

    
540
        LOG.debug("Got TX %s (%i) RX %s (%i)" %
541
                  (txmode, _mem.txtone, rxmode, _mem.rxtone))
542

    
543
    def get_memory(self, number):
544
        _mem = self._memobj.memory[number]
545
        _nam = self._memobj.names[number]
546

    
547
        mem = chirp_common.Memory()
548
        mem.number = number
549
        _valid = self._memobj.valid[mem.number]
550

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

    
558
        mem.freq = int(_mem.rxfreq) * 10
559

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

    
574
        for char in _nam.name:
575
            if char != 0:
576
                mem.name += chr(char)
577
        mem.name = mem.name.rstrip()
578

    
579
        self._get_tone(_mem, mem)
580

    
581
        mem.skip = "" if bool(_mem.scan_add) else "S"
582

    
583
        mem.power = self.POWER_LEVELS[_mem.power]
584
        mem.mode = _mem.iswide and "FM" or "NFM"
585
        return mem
586

    
587
    def _set_tone(self, mem, _mem):
588
        def _set_dcs(code, pol):
589
            val = int("%i" % code, 8) + 0x4000
590
            if pol == "R":
591
                val += 0x2000
592
            return val
593

    
594
        rx_mode = tx_mode = None
595
        rxtone = txtone = 0x0000
596

    
597
        if mem.tmode == "Tone":
598
            tx_mode = "Tone"
599
            rx_mode = None
600
            txtone = int(mem.rtone * 10) + 0x8000
601
        elif mem.tmode == "TSQL":
602
            rx_mode = tx_mode = "Tone"
603
            rxtone = txtone = int(mem.ctone * 10) + 0x8000
604
        elif mem.tmode == "DTCS":
605
            tx_mode = rx_mode = "DTCS"
606
            txtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
607
            rxtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
608
        elif mem.tmode == "Cross":
609
            tx_mode, rx_mode = mem.cross_mode.split("->")
610
            if tx_mode == "DTCS":
611
                txtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
612
            elif tx_mode == "Tone":
613
                txtone = int(mem.rtone * 10) + 0x8000
614
            if rx_mode == "DTCS":
615
                rxtone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
616
            elif rx_mode == "Tone":
617
                rxtone = int(mem.ctone * 10) + 0x8000
618

    
619
        _mem.rxtone = rxtone
620
        _mem.txtone = txtone
621

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

    
625
    def set_memory(self, mem):
626
        number = mem.number
627

    
628
        _mem = self._memobj.memory[number]
629
        _nam = self._memobj.names[number]
630

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

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

    
664
        for i in range(0, len(_nam.name)):
665
            if i < len(mem.name) and mem.name[i]:
666
                _nam.name[i] = ord(mem.name[i])
667
            else:
668
                _nam.name[i] = 0x0
669
        self._memobj.valid[mem.number] = MEM_VALID
670

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

    
682
        group = RadioSettings(cfg_grp, vfoa_grp, vfob_grp,
683
                              key_grp, lmt_grp, oem_grp)
684

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

    
943
        #
944
        # Scan Group Settings
945
        #
946
        # settings:
947
        #   u8    scg_a;
948
        #   u8    scg_b;
949
        #
950
        #   struct {
951
        #       u16    lower;
952
        #       u16    upper;
953
        #   } scan_groups[10];
954

    
955
        #
956
        # Call group settings
957
        #
958

    
959
        #
960
        # Limits settings
961
        #
962
        rs = RadioSetting("urx_start", "UHF RX Lower Limit",
963
                          RadioSettingValueInteger(
964
                              400000000, 520000000,
965
                              self._memobj.uhf_limits.rx_start * 10, 5000))
966
        lmt_grp.append(rs)
967
        rs = RadioSetting("urx_stop", "UHF RX Upper Limit",
968
                          RadioSettingValueInteger(
969
                              400000000, 520000000,
970
                              self._memobj.uhf_limits.rx_stop * 10, 5000))
971
        lmt_grp.append(rs)
972
        rs = RadioSetting("utx_start", "UHF TX Lower Limit",
973
                          RadioSettingValueInteger(
974
                              400000000, 520000000,
975
                              self._memobj.uhf_limits.tx_start * 10, 5000))
976
        lmt_grp.append(rs)
977
        rs = RadioSetting("utx_stop", "UHF TX Upper Limit",
978
                          RadioSettingValueInteger(
979
                              400000000, 520000000,
980
                              self._memobj.uhf_limits.tx_stop * 10, 5000))
981
        lmt_grp.append(rs)
982
        rs = RadioSetting("vrx_start", "VHF RX Lower Limit",
983
                          RadioSettingValueInteger(
984
                              134000000, 174997500,
985
                              self._memobj.vhf_limits.rx_start * 10, 5000))
986
        lmt_grp.append(rs)
987
        rs = RadioSetting("vrx_stop", "VHF RX Upper Limit",
988
                          RadioSettingValueInteger(
989
                              134000000, 174997500,
990
                              self._memobj.vhf_limits.rx_stop * 10, 5000))
991
        lmt_grp.append(rs)
992
        rs = RadioSetting("vtx_start", "VHF TX Lower Limit",
993
                          RadioSettingValueInteger(
994
                              134000000, 174997500,
995
                              self._memobj.vhf_limits.tx_start * 10, 5000))
996
        lmt_grp.append(rs)
997
        rs = RadioSetting("vtx_stop", "VHF TX Upper Limit",
998
                          RadioSettingValueInteger(
999
                              134000000, 174997500,
1000
                              self._memobj.vhf_limits.tx_stop * 10, 5000))
1001
        lmt_grp.append(rs)
1002

    
1003
        #
1004
        # OEM info
1005
        #
1006
        def _decode(lst):
1007
            _str = ''.join([chr(c) for c in lst
1008
                            if chr(c) in chirp_common.CHARSET_ASCII])
1009
            return _str
1010

    
1011
        _str = _decode(self._memobj.oem_info.model)
1012
        val = RadioSettingValueString(0, 15, _str)
1013
        val.set_mutable(False)
1014
        rs = RadioSetting("model", "Model", val)
1015
        oem_grp.append(rs)
1016
        _str = _decode(self._memobj.oem_info.oem1)
1017
        val = RadioSettingValueString(0, 15, _str)
1018
        val.set_mutable(False)
1019
        rs = RadioSetting("oem1", "OEM String 1", val)
1020
        oem_grp.append(rs)
1021
        _str = _decode(self._memobj.oem_info.oem2)
1022
        val = RadioSettingValueString(0, 15, _str)
1023
        val.set_mutable(False)
1024
        rs = RadioSetting("oem2", "OEM String 2", val)
1025
        oem_grp.append(rs)
1026
        _str = _decode(self._memobj.oem_info.version)
1027
        val = RadioSettingValueString(0, 15, _str)
1028
        val.set_mutable(False)
1029
        rs = RadioSetting("version", "Software Version", val)
1030
        oem_grp.append(rs)
1031
        _str = _decode(self._memobj.oem_info.date)
1032
        val = RadioSettingValueString(0, 15, _str)
1033
        val.set_mutable(False)
1034
        rs = RadioSetting("date", "OEM Date", val)
1035
        oem_grp.append(rs)
1036

    
1037
        return group
1038

    
1039
    def get_settings(self):
1040
        try:
1041
            return self._get_settings()
1042
        except Exception:
1043
            import traceback
1044
            LOG.error("Failed to parse settings: %s", traceback.format_exc())
1045
            return None
(4-4/4)