Project

General

Profile

New Model #9489 » kg935g MRT test b1.1.py

Mel Terechenok, 04/18/2022 09:23 PM

 
1
# MRT 935G BETA1.1 - Update to handle 935G
2
# Copyright 2019 Pavel Milanes CO7WT <pavelmc@gmail.com>
3
#
4
# Based on the work of Krystian Struzik <toner_82@tlen.pl>
5
# who figured out the crypt used and made possible the
6
# Wuoxun KG-UV8D Plus driver, in which this work is based.
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-935G radio management module"""
22

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

    
32
LOG = logging.getLogger(__name__)
33

    
34
CMD_ID = 128    # \x80
35
CMD_END = 129   # \x81
36
CMD_RD = 130    # \82
37
CMD_WR = 131    # \83
38

    
39
MEM_VALID = 158
40

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

    
73
# memory slot 0 is not used, start at 1 (so need 1000 slots, not 999)
74
# structure elements whose name starts with x are currently unidentified
75

    
76
# MRT made Power = 2 bits to handle 935G's 3 power levels
77
_MEM_FORMAT = """
78
    #seekto 0x0044;
79
    struct {
80
        u32    rx_start;
81
        u32    rx_stop;
82
        u32    tx_start;
83
        u32    tx_stop;
84
    } uhf_limits;
85

    
86
    #seekto 0x0054;
87
    struct {
88
        u32    rx_start;
89
        u32    rx_stop;
90
        u32    tx_start;
91
        u32    tx_stop;
92
    } vhf_limits;
93

    
94
    #seekto 0x0400;
95
    struct {
96
        u8     oem1[8];
97
        u8     unknown[2];
98
        u8     unknown2[10];
99
        u8     unknown3[10];
100
        u8     unknown4[8];
101
        u8     oem2[10];
102
        u8     version[6];
103
        u8     date[8];
104
        u8     unknown5[1];
105
        u8     model[8];     
106
    } oem_info;
107

    
108
    #seekto 0x0480;
109
    struct {
110
        u16    lower;
111
        u16    upper;
112
    } scan_groups[10];
113

    
114
    #seekto 0x0500;
115
    struct {
116
        u8    call_code[6];
117
    } call_groups[20];
118

    
119
    #seekto 0x0580;
120
    struct {
121
        char    call_name[6];
122
    } call_group_name[20];
123

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

    
203
    #seekto 0x0880;
204
    struct {
205
        u32     rxfreq;
206
        u32     txoffset;
207
        u16     rxtone;
208
        u16     txtone;
209
        u8      scrambler:4,
210
                unknown1:2,
211
                power:2; 
212
        u8      unknown3:1,
213
                shift_dir:2
214
                unknown4:1,
215
                compander:1,
216
                mute_mode:2,
217
                iswide:1;
218
        u8      step;
219
        u8      squelch;
220
      } vfoa;
221

    
222
    #seekto 0x08c0;
223
    struct {
224
        u32     rxfreq;
225
        u32     txoffset;
226
        u16     rxtone;
227
        u16     txtone;
228
        u8      scrambler:4,
229
                unknown1:2,
230
                power:2; 
231
        u8      unknown3:1,
232
                shift_dir:2
233
                unknown4:1,
234
                compander:1,
235
                mute_mode:2,
236
                iswide:1;
237
        u8      step;
238
        u8      squelch;
239
    } vfob;
240

    
241
    #seekto 0x0900;
242
    struct {
243
        u32     rxfreq;
244
        u32     txfreq;
245
        u16     rxtone;
246
        u16     txtone;
247
        u8      scrambler:4,
248
                unknown1:2,
249
                power:2; 
250
        u8      unknown3:2,
251
                scan_add:1,
252
                unknown4:1,
253
                compander:1,
254
                mute_mode:2,
255
                iswide:1;
256
        u16     padding;
257
    } memory[1000];
258

    
259
    #seekto 0x4780;
260
    struct {
261
        u8    name[8];
262
                u8    unknown[4];
263
    } names[1000];
264

    
265
    #seekto 0x7670;
266
    u8          valid[1000];
267
    """
268

    
269
    # Support for the Wouxun KG-935G radio
270
    # Serial coms are at 19200 baud
271
    # The data is passed in variable length records
272
    # Record structure:
273
    #  Offset   Usage
274
    #    0      start of record (\x7c)
275
    #    1      Command (\x80 Identify \x81 End/Reboot \x82 Read \x83 Write)
276
    #    2      direction (\xff PC-> Radio, \x00 Radio -> PC)
277
    #    3      length of payload (excluding header/checksum) (n)
278
    #    4      payload (n bytes)
279
    #    4+n+1  checksum - byte sum (% 256) of bytes 1 -> 4+n
280
    #
281
    # Memory Read Records:
282
    # the payload is 3 bytes, first 2 are offset (big endian),
283
    # 3rd is number of bytes to read
284
    # Memory Write Records:
285
    # the maximum payload size (from the Wouxun software) seems to be 66 bytes
286
    #  (2 bytes location + 64 bytes data).
287

    
288
class KGUV8TRadio(chirp_common.Alias):
289
    VENDOR = "Wuoxun"
290
    MODEL = "KG-935G"
291

    
292
@directory.register
293
class KG935GRadio(chirp_common.CloneModeRadio,
294
                  chirp_common.ExperimentalRadio):
295

    
296
    """Wouxun KG-935G"""
297
    VENDOR = "Wouxun"
298
    MODEL = "KG-935G"
299
    _model = "KG-UV8D-B"
300
    _file_ident = "935G"
301
    BAUD_RATE = 19200
302
# MRT - Added Medium Power level for 935G support
303
    POWER_LEVELS = [chirp_common.PowerLevel("L", watts=0.5),
304
                    chirp_common.PowerLevel("M", watts=4.5),
305
                    chirp_common.PowerLevel("H", watts=5.5)]
306
    _mmap = ""
307
    ALIASES = [KGUV8TRadio,]
308

    
309
    def _checksum(self, data):
310
        cs = 0
311
        for byte in data:
312
            cs += ord(byte)
313
        return chr(cs % 256)
314

    
315
    def _write_record(self, cmd, payload = None):
316
        # build the packet
317
        _header = '\x7c' + chr(cmd) + '\xff'
318

    
319
        _length = 0
320
        if payload:
321
            _length = len(payload)
322

    
323
        # update the length field
324
        _header += chr(_length)
325

    
326
        if payload:
327
            # calculate checksum then add it with the payload to the packet and encrypt
328
            crc = self._checksum(_header[1:] + payload)
329
            payload += crc
330
            _header += self.encrypt(payload)
331
        else:
332
            # calculate and add encrypted checksum to the packet
333
            crc = self._checksum(_header[1:])
334
            _header += self.strxor(crc, '\x57')
335

    
336
        try:
337
            self.pipe.write(_header)
338
        except Exception, e:
339
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
340

    
341
    def _read_record(self):
342
        # read 4 chars for the header
343
        _header = self.pipe.read(4)
344
        if len(_header) != 4:
345
            raise errors.RadioError('Radio did not respond')
346
        _length = ord(_header[3])
347
        _packet = self.pipe.read(_length)
348
        _rcs_xor = _packet[-1]
349
        _packet = self.decrypt(_packet)
350
        _cs = ord(self._checksum(_header[1:] + _packet))
351
        # read the checksum and decrypt it
352
        _rcs = ord(self.strxor(self.pipe.read(1), _rcs_xor))
353
        return (_rcs != _cs, _packet)
354

    
355
    def decrypt(self, data):
356
        result = ''
357
        for i in range(len(data)-1, 0, -1):
358
            result += self.strxor(data[i], data[i - 1])
359
        result += self.strxor(data[0], '\x57')
360
        return result[::-1]
361

    
362
    def encrypt(self, data):
363
        result = self.strxor('\x57', data[0])
364
        for i in range(1, len(data), 1):
365
            result += self.strxor(result[i - 1], data[i])
366
        return result
367

    
368
    def strxor (self, xora, xorb):
369
        return chr(ord(xora) ^ ord(xorb))
370

    
371
    # Identify the radio
372
    #
373
    # A Gotcha: the first identify packet returns a bad checksum, subsequent
374
    # attempts return the correct checksum... (well it does on my radio!)
375
    #
376
    # The ID record returned by the radio also includes the current frequency range
377
    # as 4 bytes big-endian in 10Hz increments
378
    #
379
    # Offset
380
    #  0:10     Model, zero padded (Looks for 'KG-UV8D-B')
381

    
382
    @classmethod
383
    def match_model(cls, filedata, filename):
384
        id = cls._file_ident 
385
        return cls._file_ident in filedata[0x426:0x430]
386

    
387
    def _identify(self):
388
        """Do the identification dance"""
389
        for _i in range(0, 10):
390
            self._write_record(CMD_ID)
391
            _chksum_err, _resp = self._read_record()
392
            LOG.debug("Got:\n%s" % util.hexprint(_resp))
393
            if _chksum_err:
394
                LOG.error("Checksum error: retrying ident...")
395
                time.sleep(0.100)
396
                continue
397
            LOG.debug("Model %s" % util.hexprint(_resp[0:9]))
398
            if _resp[0:9] == self._model:
399
                return
400
            if len(_resp) == 0:
401
                raise Exception("Radio not responding")
402
            else:
403
                raise Exception("Unable to identify radio")
404

    
405
    def _finish(self):
406
        self._write_record(CMD_END)
407

    
408
    def process_mmap(self):
409
        self._memobj = bitwise.parse(_MEM_FORMAT, self._mmap)
410

    
411
    def sync_in(self):
412
        try:
413
            self._mmap = self._download()
414
        except errors.RadioError:
415
            raise
416
        except Exception, e:
417
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
418
        self.process_mmap()
419

    
420
    def sync_out(self):
421
        self._upload()
422

    
423
    # TODO: Load all memory.
424
    # It would be smarter to only load the active areas and none of
425
    # the padding/unused areas. Padding still need to be investigated.
426
    def _download(self):
427
        """Talk to a wouxun KG-935G and do a download"""
428
        try:
429
            self._identify()
430
            return self._do_download(0, 32768, 64)
431
        except errors.RadioError:
432
            raise
433
        except Exception, e:
434
            LOG.exception('Unknown error during download process')
435
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
436

    
437
    def _do_download(self, start, end, blocksize):
438
        # allocate & fill memory
439
        image = ""
440
        for i in range(start, end, blocksize):
441
            req = chr(i / 256) + chr(i % 256) + chr(blocksize)
442
            self._write_record(CMD_RD, req)
443
            cs_error, resp = self._read_record()
444
            if cs_error:
445
                LOG.debug(util.hexprint(resp))
446
                raise Exception("Checksum error on read")
447
            # LOG.debug("Got:\n%s" % util.hexprint(resp))
448
            image += resp[2:]
449
            if self.status_fn:
450
                status = chirp_common.Status()
451
                status.cur = i
452
                status.max = end
453
                status.msg = "Cloning from radio"
454
                self.status_fn(status)
455
        self._finish()
456
        return memmap.MemoryMap(''.join(image))
457

    
458
    def _upload(self):
459
        """Talk to a wouxun KG-935G and do a upload"""
460
        try:
461
            self._identify()
462
            self._do_upload(0, 32768, 64)
463
        except errors.RadioError:
464
            raise
465
        except Exception, e:
466
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
467
        return
468

    
469
    def _do_upload(self, start, end, blocksize):
470
        ptr = start
471
        for i in range(start, end, blocksize):
472
            req = chr(i / 256) + chr(i % 256)
473
            chunk = self.get_mmap()[ptr:ptr + blocksize]
474
            self._write_record(CMD_WR, req + chunk)
475
            LOG.debug(util.hexprint(req + chunk))
476
            cserr, ack = self._read_record()
477
            LOG.debug(util.hexprint(ack))
478
            j = ord(ack[0]) * 256 + ord(ack[1])
479
            if cserr or j != ptr:
480
                raise Exception("Radio did not ack block %i" % ptr)
481
            ptr += blocksize
482
            if self.status_fn:
483
                status = chirp_common.Status()
484
                status.cur = i
485
                status.max = end
486
                status.msg = "Cloning to radio"
487
                self.status_fn(status)
488
        self._finish()
489

    
490
    def get_features(self):
491
        rf = chirp_common.RadioFeatures()
492
        rf.has_settings = True
493
        rf.has_ctone = True
494
        rf.has_rx_dtcs = True
495
        rf.has_cross = True
496
        rf.has_tuning_step = False
497
        rf.has_bank = False
498
        rf.can_odd_split = True
499
        rf.valid_skips = ["", "S"]
500
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
501
        rf.valid_cross_modes = [
502
            "Tone->Tone",
503
            "Tone->DTCS",
504
            "DTCS->Tone",
505
            "DTCS->",
506
            "->Tone",
507
            "->DTCS",
508
            "DTCS->DTCS",
509
        ]
510
        rf.valid_modes = ["FM", "NFM"]
511
        rf.valid_power_levels = self.POWER_LEVELS
512
        rf.valid_name_length = 8
513
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
514
        rf.valid_bands = [(137000000, 175000000),  # supports 2m
515
                          (400000000, 480000000)]  # supports 70cm
516
        rf.valid_characters = chirp_common.CHARSET_ASCII
517
        rf.memory_bounds = (1, 999)  # 999 memories
518
        rf.valid_tuning_steps = STEPS
519
        return rf
520

    
521
    @classmethod
522
    def get_prompts(cls):
523
        rp = chirp_common.RadioPrompts()
524
        rp.experimental = \
525
            ('This driver is experimental.\n'
526
             '\n'
527
             'Please keep a copy of your memories with the original software '
528
             'if you treasure them, this driver is new and may contain'
529
             ' bugs.\n'
530
             '\n'
531
             ' USE FOR DOWNLOAD FROM RADIO ONLY - \n'
532
             ' DO NOT UPLOAD AS THIS HAS NOT BEEN TESTED\n'
533
             ' USE AT YOUR OWN RISK\n'
534
             '\n'
535
             )
536
        return rp
537

    
538
    def get_raw_memory(self, number):
539
        return repr(self._memobj.memory[number])
540
# MRT - corrected the Polarity decoding to match 935G implementation use 0x2000 bit mask for R
541
# MRT - 0x2000 appears to be the bit mask for Inverted DCS tones
542
# MRT - n DCS Tone will be 0x4xxx values - i DCS Tones will be 0x6xxx values.
543
# MRT - Chirp Uses N for n DCS Tones and R for i DCS Tones
544
    def _get_tone(self, _mem, mem):
545
        def _get_dcs(val):
546
            code = int("%03o" % (val & 0x07FF))
547
            pol = (val & 0x2000) and "R" or "N"
548
            return code, pol
549
# MRT - Modified the function below to bitwise AND with 0x4000 to check for 935G DCS Tone decoding
550
# MRT 0x4000 appears to be the bit mask for DCS tones
551
        tpol = False
552
# MRT Beta 1.1 - Fix the txtone compare to 0x4000 - was rxtone.
553
        if _mem.txtone != 0xFFFF and (_mem.txtone & 0x4000) == 0x4000:
554
            tcode, tpol = _get_dcs(_mem.txtone)
555
            mem.dtcs = tcode
556
            txmode = "DTCS"
557
        elif _mem.txtone != 0xFFFF and _mem.txtone != 0x0:
558
            mem.rtone = (_mem.txtone & 0x7fff) / 10.0
559
            txmode = "Tone"
560
        else:
561
            txmode = ""
562
# MRT - Modified the function below to bitwise AND with 0x4000 to check for 935G DCS Tone decoding
563
        rpol = False
564
        if _mem.rxtone != 0xFFFF and (_mem.rxtone & 0x4000) == 0x4000:
565
            rcode, rpol = _get_dcs(_mem.rxtone)
566
            mem.rx_dtcs = rcode
567
            rxmode = "DTCS"
568
        elif _mem.rxtone != 0xFFFF and _mem.rxtone != 0x0:
569
            mem.ctone = (_mem.rxtone & 0x7fff) / 10.0
570
            rxmode = "Tone"
571
        else:
572
            rxmode = ""
573

    
574
        if txmode == "Tone" and not rxmode:
575
            mem.tmode = "Tone"
576
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
577
            mem.tmode = "TSQL"
578
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
579
            mem.tmode = "DTCS"
580
        elif rxmode or txmode:
581
            mem.tmode = "Cross"
582
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
583

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

    
587
        LOG.debug("Got TX %s (%i) RX %s (%i)" %
588
                  (txmode, _mem.txtone, rxmode, _mem.rxtone))
589

    
590
    def get_memory(self, number):
591
        _mem = self._memobj.memory[number]
592
        _nam = self._memobj.names[number]
593

    
594
        mem = chirp_common.Memory()
595
        mem.number = number
596
        _valid = self._memobj.valid[mem.number]
597
        LOG.debug("%d %s", number, _valid == MEM_VALID)
598
        if _valid != MEM_VALID:
599
            mem.empty = True
600
            return mem
601
        else:
602
            mem.empty = False
603

    
604
        mem.freq = int(_mem.rxfreq) * 10
605

    
606
        if _mem.txfreq == 0xFFFFFFFF:
607
            # TX freq not set
608
            mem.duplex = "off"
609
            mem.offset = 0
610
        elif int(_mem.rxfreq) == int(_mem.txfreq):
611
            mem.duplex = ""
612
            mem.offset = 0
613
        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
614
            mem.duplex = "split"
615
            mem.offset = int(_mem.txfreq) * 10
616
        else:
617
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
618
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
619

    
620
        for char in _nam.name:
621
            if char != 0:
622
                mem.name += chr(char)
623
        mem.name = mem.name.rstrip()
624

    
625
        self._get_tone(_mem, mem)
626

    
627
        mem.skip = "" if bool(_mem.scan_add) else "S"
628

    
629
        mem.power = self.POWER_LEVELS[_mem.power]
630
        mem.mode = _mem.iswide and "FM" or "NFM"
631
        return mem
632

    
633
    def _set_tone(self, mem, _mem):
634
        def _set_dcs(code, pol):
635
#MRT Change from + 0x2800 to bitwise OR with 0x4000 to set the bit for DCS
636
            val = int("%i" % code, 8) | 0x4000
637
            if pol == "R":
638
#MRT Change to 0x2000 from 0x8000 to set the bit for i/R polarity
639
               val += 0x2000
640
            return val
641

    
642
        rx_mode = tx_mode = None
643
        rxtone = txtone = 0x0000
644

    
645
        if mem.tmode == "Tone":
646
            tx_mode = "Tone"
647
            rx_mode = None
648
            txtone = int(mem.rtone * 10) + 0x8000
649
        elif mem.tmode == "TSQL":
650
            rx_mode = tx_mode = "Tone"
651
            rxtone = txtone = int(mem.ctone * 10) + 0x8000
652
        elif mem.tmode == "DTCS":
653
            tx_mode = rx_mode = "DTCS"
654
            txtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
655
            rxtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
656
        elif mem.tmode == "Cross":
657
            tx_mode, rx_mode = mem.cross_mode.split("->")
658
            if tx_mode == "DTCS":
659
                txtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
660
            elif tx_mode == "Tone":
661
                txtone = int(mem.rtone * 10) + 0x8000
662
            if rx_mode == "DTCS":
663
                rxtone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
664
            elif rx_mode == "Tone":
665
                rxtone = int(mem.ctone * 10) + 0x8000
666

    
667
        _mem.rxtone = rxtone
668
        _mem.txtone = txtone
669

    
670
        LOG.debug("Set TX %s (%i) RX %s (%i)" %
671
                  (tx_mode, _mem.txtone, rx_mode, _mem.rxtone))
672

    
673
    def set_memory(self, mem):
674
        number = mem.number
675

    
676
        _mem = self._memobj.memory[number]
677
        _nam = self._memobj.names[number]
678

    
679
        if mem.empty:
680
            _mem.set_raw("\x00" * (_mem.size() / 8))
681
            self._memobj.valid[number] = 0
682
            self._memobj.names[number].set_raw("\x00" * (_nam.size() / 8))
683
            return
684

    
685
        _mem.rxfreq = int(mem.freq / 10)
686
        if mem.duplex == "off":
687
            _mem.txfreq = 0xFFFFFFFF
688
        elif mem.duplex == "split":
689
            _mem.txfreq = int(mem.offset / 10)
690
        elif mem.duplex == "off":
691
            for i in range(0, 4):
692
                _mem.txfreq[i].set_raw("\xFF")
693
        elif mem.duplex == "+":
694
            _mem.txfreq = int(mem.freq / 10) + int(mem.offset / 10)
695
        elif mem.duplex == "-":
696
            _mem.txfreq = int(mem.freq / 10) - int(mem.offset / 10)
697
        else:
698
            _mem.txfreq = int(mem.freq / 10)
699
        _mem.scan_add = int(mem.skip != "S")
700
        _mem.iswide = int(mem.mode == "FM")
701
        # set the tone
702
        self._set_tone(mem, _mem)
703
        # set the scrambler and compander to off by default
704
        _mem.scrambler = 0
705
        _mem.compander = 0
706
        # set the power
707
        if mem.power:
708
            _mem.power = self.POWER_LEVELS.index(mem.power)
709
        else:
710
            _mem.power = True
711
        # set to mute mode to QT (not QT+DTMF or QT*DTMF) by default
712
        _mem.mute_mode = 0
713

    
714
        for i in range(0, len(_nam.name)):
715
            if i < len(mem.name) and mem.name[i]:
716
                _nam.name[i] = ord(mem.name[i])
717
            else:
718
                _nam.name[i] = 0x0
719
        self._memobj.valid[mem.number] = MEM_VALID
720

    
721
    def _get_settings(self):
722
        _settings = self._memobj.settings
723
        _vfoa = self._memobj.vfoa
724
        _vfob = self._memobj.vfob
725
        cfg_grp = RadioSettingGroup("cfg_grp", "Configuration")
726
        vfoa_grp = RadioSettingGroup("vfoa_grp", "VFO A Settings")
727
        vfob_grp = RadioSettingGroup("vfob_grp", "VFO B Settings")
728
        key_grp = RadioSettingGroup("key_grp", "Key Settings")
729
        lmt_grp = RadioSettingGroup("lmt_grp", "Frequency Limits")
730
        uhf_lmt_grp = RadioSettingGroup("uhf_lmt_grp", "UHF")
731
        vhf_lmt_grp = RadioSettingGroup("vhf_lmt_grp", "VHF")
732
        oem_grp = RadioSettingGroup("oem_grp", "OEM Info")
733

    
734
        lmt_grp.append(vhf_lmt_grp);
735
        lmt_grp.append(uhf_lmt_grp);
736
        group = RadioSettings(cfg_grp, vfoa_grp, vfob_grp,
737
                              key_grp, lmt_grp, oem_grp)
738

    
739
        #
740
        # Configuration Settings
741
        #
742
        rs = RadioSetting("channel_menu", "Menu available in channel mode",
743
                          RadioSettingValueBoolean(_settings.channel_menu))
744
        cfg_grp.append(rs)
745
        rs = RadioSetting("ponmsg", "Poweron message",
746
                          RadioSettingValueList(
747
                              PONMSG_LIST, PONMSG_LIST[_settings.ponmsg]))
748
        cfg_grp.append(rs)
749
        rs = RadioSetting("voice", "Voice Guide",
750
                          RadioSettingValueBoolean(_settings.voice))
751
        cfg_grp.append(rs)
752
        rs = RadioSetting("language", "Language",
753
                          RadioSettingValueList(LANGUAGE_LIST,
754
                                                LANGUAGE_LIST[_settings.
755
                                                              language]))
756
        cfg_grp.append(rs)
757
        rs = RadioSetting("timeout", "Timeout Timer",
758
                          RadioSettingValueList(
759
                              TIMEOUT_LIST, TIMEOUT_LIST[_settings.timeout]))
760
        cfg_grp.append(rs)
761
        rs = RadioSetting("toalarm", "Timeout Alarm",
762
                          RadioSettingValueInteger(0, 10, _settings.toalarm))
763
        cfg_grp.append(rs)
764
        rs = RadioSetting("roger_beep", "Roger Beep",
765
                          RadioSettingValueList(ROGER_LIST,
766
                                                ROGER_LIST[_settings.roger_beep]))
767
        cfg_grp.append(rs)
768
        rs = RadioSetting("power_save", "Power save",
769
                          RadioSettingValueBoolean(_settings.power_save))
770
        cfg_grp.append(rs)
771
        rs = RadioSetting("autolock", "Autolock",
772
                          RadioSettingValueBoolean(_settings.autolock))
773
        cfg_grp.append(rs)
774
        rs = RadioSetting("keylock", "Keypad Lock",
775
                          RadioSettingValueBoolean(_settings.keylock))
776
        cfg_grp.append(rs)
777
        rs = RadioSetting("beep", "Keypad Beep",
778
                          RadioSettingValueBoolean(_settings.beep))
779
        cfg_grp.append(rs)
780
        rs = RadioSetting("stopwatch", "Stopwatch",
781
                          RadioSettingValueBoolean(_settings.stopwatch))
782
        cfg_grp.append(rs)
783
        rs = RadioSetting("backlight", "Backlight",
784
                          RadioSettingValueList(BACKLIGHT_LIST,
785
                                                BACKLIGHT_LIST[_settings.
786
                                                               backlight]))
787
        cfg_grp.append(rs)
788
        rs = RadioSetting("dtmf_st", "DTMF Sidetone",
789
                          RadioSettingValueList(DTMFST_LIST,
790
                                                DTMFST_LIST[_settings.
791
                                                            dtmf_st]))
792
        cfg_grp.append(rs)
793
        rs = RadioSetting("ani_sw", "ANI-ID Switch",
794
                          RadioSettingValueBoolean(_settings.ani_sw))
795
        cfg_grp.append(rs)
796
        rs = RadioSetting("ptt_id", "PTT-ID Delay",
797
                          RadioSettingValueList(PTTID_LIST,
798
                                                PTTID_LIST[_settings.ptt_id]))
799
        cfg_grp.append(rs)
800
        rs = RadioSetting("ring_time", "Ring Time",
801
                          RadioSettingValueList(LIST_10,
802
                                                LIST_10[_settings.ring_time]))
803
        cfg_grp.append(rs)
804
        rs = RadioSetting("scan_rev", "Scan Mode",
805
                          RadioSettingValueList(SCANMODE_LIST,
806
                                                SCANMODE_LIST[_settings.
807
                                                              scan_rev]))
808
        cfg_grp.append(rs)
809
        rs = RadioSetting("vox", "VOX",
810
                          RadioSettingValueList(LIST_10,
811
                                                LIST_10[_settings.vox]))
812
        cfg_grp.append(rs)
813
        rs = RadioSetting("prich_sw", "Priority Channel Switch",
814
                          RadioSettingValueBoolean(_settings.prich_sw))
815
        cfg_grp.append(rs)
816
        rs = RadioSetting("pri_ch", "Priority Channel",
817
                          RadioSettingValueInteger(1, 999, _settings.pri_ch))
818
        cfg_grp.append(rs)
819
        rs = RadioSetting("rpt_mode", "Radio Mode",
820
                          RadioSettingValueList(RPTMODE_LIST,
821
                                                RPTMODE_LIST[_settings.
822
                                                             rpt_mode]))
823
        cfg_grp.append(rs)
824
        rs = RadioSetting("rpt_set", "Repeater Setting",
825
                          RadioSettingValueList(RPTSET_LIST,
826
                                                RPTSET_LIST[_settings.
827
                                                            rpt_set]))
828
        cfg_grp.append(rs)
829
        rs = RadioSetting("rpt_spk", "Repeater Mode Speaker",
830
                          RadioSettingValueBoolean(_settings.rpt_spk))
831
        cfg_grp.append(rs)
832
        rs = RadioSetting("rpt_ptt", "Repeater PTT",
833
                          RadioSettingValueBoolean(_settings.rpt_ptt))
834
        cfg_grp.append(rs)
835
        rs = RadioSetting("dtmf_tx_time", "DTMF Tx Duration",
836
                          RadioSettingValueList(DTMF_TIMES,
837
                                                DTMF_TIMES[_settings.
838
                                                           dtmf_tx_time]))
839
        cfg_grp.append(rs)
840
        rs = RadioSetting("dtmf_interval", "DTMF Interval",
841
                          RadioSettingValueList(DTMF_TIMES,
842
                                                DTMF_TIMES[_settings.
843
                                                           dtmf_interval]))
844
        cfg_grp.append(rs)
845
        rs = RadioSetting("alert", "Alert Tone",
846
                          RadioSettingValueList(ALERTS_LIST,
847
                                                ALERTS_LIST[_settings.alert]))
848
        cfg_grp.append(rs)
849
        rs = RadioSetting("rpt_tone", "Repeater Tone",
850
                          RadioSettingValueBoolean(_settings.rpt_tone))
851
        cfg_grp.append(rs)
852
        rs = RadioSetting("rpt_hold", "Repeater Hold Time",
853
                          RadioSettingValueList(HOLD_TIMES,
854
                                                HOLD_TIMES[_settings.
855
                                                           rpt_hold]))
856
        cfg_grp.append(rs)
857
        rs = RadioSetting("scan_det", "Scan DET",
858
                          RadioSettingValueBoolean(_settings.scan_det))
859
        cfg_grp.append(rs)
860
        rs = RadioSetting("sc_qt", "SC-QT",
861
                          RadioSettingValueList(SCQT_LIST,
862
                                                SCQT_LIST[_settings.sc_qt]))
863
        cfg_grp.append(rs)
864
        rs = RadioSetting("smuteset", "SubFreq Mute",
865
                          RadioSettingValueList(SMUTESET_LIST,
866
                                                SMUTESET_LIST[_settings.
867
                                                              smuteset]))
868
        cfg_grp.append(rs)
869

    
870
                #
871
        # VFO A Settings
872
        #
873
        rs = RadioSetting("workmode_a", "VFO A Workmode",
874
                          RadioSettingValueList(WORKMODE_LIST, WORKMODE_LIST[_settings.workmode_a]))
875
        vfoa_grp.append(rs)
876
        rs = RadioSetting("work_cha", "VFO A Channel",
877
                          RadioSettingValueInteger(1, 999, _settings.work_cha))
878
        vfoa_grp.append(rs)
879
        rs = RadioSetting("vfoa.rxfreq", "VFO A Rx Frequency",
880
                          RadioSettingValueInteger(
881
                              134000000, 520000000, _vfoa.rxfreq * 10, 5000))
882
        vfoa_grp.append(rs)
883
        rs = RadioSetting("vfoa.txoffset", "VFO A Tx Offset",
884
                          RadioSettingValueInteger(
885
                              0, 520000000, _vfoa.txoffset * 10, 5000))
886
        vfoa_grp.append(rs)
887
        #   u16   rxtone;
888
        #   u16   txtone;
889
        rs = RadioSetting("vfoa.power", "VFO A Power",
890
                          RadioSettingValueList(
891
                              POWER_LIST, POWER_LIST[_vfoa.power]))
892
        vfoa_grp.append(rs)
893
        #         shift_dir:2
894
        rs = RadioSetting("vfoa.iswide", "VFO A NBFM",
895
                          RadioSettingValueList(
896
                              BANDWIDTH_LIST, BANDWIDTH_LIST[_vfoa.iswide]))
897
        vfoa_grp.append(rs)
898
        rs = RadioSetting("vfoa.mute_mode", "VFO A Mute",
899
                          RadioSettingValueList(
900
                              SPMUTE_LIST, SPMUTE_LIST[_vfoa.mute_mode]))
901
        vfoa_grp.append(rs)
902
        rs = RadioSetting("vfoa.step", "VFO A Step (kHz)",
903
                          RadioSettingValueList(
904
                              STEP_LIST, STEP_LIST[_vfoa.step]))
905
        vfoa_grp.append(rs)
906
        rs = RadioSetting("vfoa.squelch", "VFO A Squelch",
907
                          RadioSettingValueList(
908
                              LIST_10, LIST_10[_vfoa.squelch]))
909
        vfoa_grp.append(rs)
910
        rs = RadioSetting("bcl_a", "Busy Channel Lock-out A",
911
                          RadioSettingValueBoolean(_settings.bcl_a))
912
        vfoa_grp.append(rs)
913

    
914
                #
915
        # VFO B Settings
916
        #
917
        rs = RadioSetting("workmode_b", "VFO B Workmode",
918
                          RadioSettingValueList(WORKMODE_LIST, WORKMODE_LIST[_settings.workmode_b]))
919
        vfob_grp.append(rs)
920
        rs = RadioSetting("work_chb", "VFO B Channel",
921
                          RadioSettingValueInteger(1, 999, _settings.work_chb))
922
        vfob_grp.append(rs)
923
        rs = RadioSetting("vfob.rxfreq", "VFO B Rx Frequency",
924
                          RadioSettingValueInteger(
925
                              134000000, 520000000, _vfob.rxfreq * 10, 5000))
926
        vfob_grp.append(rs)
927
        rs = RadioSetting("vfob.txoffset", "VFO B Tx Offset",
928
                          RadioSettingValueInteger(
929
                              0, 520000000, _vfob.txoffset * 10, 5000))
930
        vfob_grp.append(rs)
931
        #   u16   rxtone;
932
        #   u16   txtone;
933
        rs = RadioSetting("vfob.power", "VFO B Power",
934
                          RadioSettingValueList(
935
                              POWER_LIST, POWER_LIST[_vfob.power]))
936
        vfob_grp.append(rs)
937
        #         shift_dir:2
938
        rs = RadioSetting("vfob.iswide", "VFO B NBFM",
939
                          RadioSettingValueList(
940
                              BANDWIDTH_LIST, BANDWIDTH_LIST[_vfob.iswide]))
941
        vfob_grp.append(rs)
942
        rs = RadioSetting("vfob.mute_mode", "VFO B Mute",
943
                          RadioSettingValueList(
944
                              SPMUTE_LIST, SPMUTE_LIST[_vfob.mute_mode]))
945
        vfob_grp.append(rs)
946
        rs = RadioSetting("vfob.step", "VFO B Step (kHz)",
947
                          RadioSettingValueList(
948
                              STEP_LIST, STEP_LIST[_vfob.step]))
949
        vfob_grp.append(rs)
950
        rs = RadioSetting("vfob.squelch", "VFO B Squelch",
951
                          RadioSettingValueList(
952
                              LIST_10, LIST_10[_vfob.squelch]))
953
        vfob_grp.append(rs)
954
        rs = RadioSetting("bcl_b", "Busy Channel Lock-out B",
955
                          RadioSettingValueBoolean(_settings.bcl_b))
956
        vfob_grp.append(rs)
957

    
958
                #
959
        # Key Settings
960
        #
961
        _msg = str(_settings.dispstr).split("\0")[0]
962
        val = RadioSettingValueString(0, 15, _msg)
963
        val.set_mutable(True)
964
        rs = RadioSetting("dispstr", "Display Message", val)
965
        key_grp.append(rs)
966

    
967
        dtmfchars = "0123456789"
968
        _codeobj = _settings.ani_code
969
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x0A])
970
        val = RadioSettingValueString(3, 6, _code, False)
971
        val.set_charset(dtmfchars)
972
        rs = RadioSetting("ani_code", "ANI Code", val)
973
        def apply_ani_id(setting, obj):
974
            value = []
975
            for j in range(0, 6):
976
                try:
977
                    value.append(dtmfchars.index(str(setting.value)[j]))
978
                except IndexError:
979
                    value.append(0xFF)
980
            obj.ani_code = value
981
        rs.set_apply_callback(apply_ani_id, _settings)
982
        key_grp.append(rs)
983

    
984
        rs = RadioSetting("pf1_func", "PF1 Key function",
985
                          RadioSettingValueList(
986
                              PF1KEY_LIST,
987
                              PF1KEY_LIST[_settings.pf1_func]))
988
        key_grp.append(rs)
989
        rs = RadioSetting("pf3_func", "PF3 Key function",
990
                          RadioSettingValueList(
991
                              PF3KEY_LIST,
992
                              PF3KEY_LIST[_settings.pf3_func]))
993
        key_grp.append(rs)
994

    
995
        #
996
        # Limits settings
997
        #
998
        rs = RadioSetting("vhf_limits.rx_start", "VHF RX Lower Limit",
999
                          RadioSettingValueInteger(
1000
                              134000000, 174997500,
1001
                              self._memobj.vhf_limits.rx_start * 10, 5000))
1002
        vhf_lmt_grp.append(rs)
1003
        rs = RadioSetting("vhf_limits.rx_stop", "VHF RX Upper Limit",
1004
                          RadioSettingValueInteger(
1005
                              134000000, 174997500,
1006
                              self._memobj.vhf_limits.rx_stop * 10, 5000))
1007
        vhf_lmt_grp.append(rs)
1008
        rs = RadioSetting("vhf_limits.tx_start", "VHF TX Lower Limit",
1009
                          RadioSettingValueInteger(
1010
                              134000000, 174997500,
1011
                              self._memobj.vhf_limits.tx_start * 10, 5000))
1012
        vhf_lmt_grp.append(rs)
1013
        rs = RadioSetting("vhf_limits.tx_stop", "VHF TX Upper Limit",
1014
                          RadioSettingValueInteger(
1015
                              134000000, 174997500,
1016
                              self._memobj.vhf_limits.tx_stop * 10, 5000))
1017
        vhf_lmt_grp.append(rs)
1018

    
1019
        rs = RadioSetting("uhf_limits.rx_start", "UHF RX Lower Limit",
1020
                          RadioSettingValueInteger(
1021
                              400000000, 520000000,
1022
                              self._memobj.uhf_limits.rx_start * 10, 5000))
1023
        uhf_lmt_grp.append(rs)
1024
        rs = RadioSetting("uhf_limits.rx_stop", "UHF RX Upper Limit",
1025
                          RadioSettingValueInteger(
1026
                              400000000, 520000000,
1027
                              self._memobj.uhf_limits.rx_stop * 10, 5000))
1028
        uhf_lmt_grp.append(rs)
1029
        rs = RadioSetting("uhf_limits.tx_start", "UHF TX Lower Limit",
1030
                          RadioSettingValueInteger(
1031
                              400000000, 520000000,
1032
                              self._memobj.uhf_limits.tx_start * 10, 5000))
1033
        uhf_lmt_grp.append(rs)
1034
        rs = RadioSetting("uhf_limits.tx_stop", "UHF TX Upper Limit",
1035
                          RadioSettingValueInteger(
1036
                              400000000, 520000000,
1037
                              self._memobj.uhf_limits.tx_stop * 10, 5000))
1038
        uhf_lmt_grp.append(rs)
1039

    
1040
        #
1041
        # OEM info
1042
        #
1043
        def _decode(lst):
1044
            _str = ''.join([chr(c) for c in lst
1045
                            if chr(c) in chirp_common.CHARSET_ASCII])
1046
            return _str
1047

    
1048
        def do_nothing(setting, obj):
1049
            return
1050

    
1051
        _str = _decode(self._memobj.oem_info.model)
1052
        val = RadioSettingValueString(0, 15, _str)
1053
        val.set_mutable(False)
1054
        rs = RadioSetting("oem_info.model", "Model", val)
1055
        rs.set_apply_callback(do_nothing, _settings)
1056
        oem_grp.append(rs)
1057
        _str = _decode(self._memobj.oem_info.oem1)
1058
        val = RadioSettingValueString(0, 15, _str)
1059
        val.set_mutable(False)
1060
        rs = RadioSetting("oem_info.oem1", "OEM String 1", val)
1061
        rs.set_apply_callback(do_nothing, _settings)
1062
        oem_grp.append(rs)
1063
        _str = _decode(self._memobj.oem_info.oem2)
1064
        val = RadioSettingValueString(0, 15, _str)
1065
        val.set_mutable(False)
1066
        rs = RadioSetting("oem_info.oem2", "OEM String 2", val)
1067
        rs.set_apply_callback(do_nothing, _settings)
1068
        oem_grp.append(rs)
1069
        _str = _decode(self._memobj.oem_info.version)
1070
        val = RadioSettingValueString(0, 15, _str)
1071
        val.set_mutable(False)
1072
        rs = RadioSetting("oem_info.version", "Software Version", val)
1073
        rs.set_apply_callback(do_nothing, _settings)
1074
        oem_grp.append(rs)
1075
        _str = _decode(self._memobj.oem_info.date)
1076
        val = RadioSettingValueString(0, 15, _str)
1077
        val.set_mutable(False)
1078
        rs = RadioSetting("oem_info.date", "OEM Date", val)
1079
        rs.set_apply_callback(do_nothing, _settings)
1080
        oem_grp.append(rs)
1081

    
1082
        return group
1083

    
1084
    def get_settings(self):
1085
        try:
1086
            return self._get_settings()
1087
        except:
1088
            import traceback
1089
            LOG.error("Failed to parse settings: %s", traceback.format_exc())
1090
            return None
1091

    
1092
    def set_settings(self, settings):
1093
        for element in settings:
1094
            if not isinstance(element, RadioSetting):
1095
                self.set_settings(element)
1096
                continue
1097
            else:
1098
                try:
1099
                    if "." in element.get_name():
1100
                        bits = element.get_name().split(".")
1101
                        obj = self._memobj
1102
                        for bit in bits[:-1]:
1103
                            obj = getattr(obj, bit)
1104
                        setting = bits[-1]
1105
                    else:
1106
                        obj = self._memobj.settings
1107
                        setting = element.get_name()
1108

    
1109
                    if element.has_apply_callback():
1110
                        LOG.debug("Using apply callback")
1111
                        element.run_apply_callback()
1112
                    else:
1113
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1114
                        if self._is_freq(element):
1115
                            setattr(obj, setting, int(element.value)/10)
1116
                        else:
1117
                            setattr(obj, setting, element.value)
1118
                except Exception, e:
1119
                    LOG.debug(element.get_name())
1120
                    raise
1121

    
1122
    def _is_freq(self, element):
1123
        return "rxfreq" in element.get_name() or "txoffset" in element.get_name() or "rx_start" in element.get_name() or "rx_stop" in element.get_name() or "tx_start" in element.get_name() or "tx_stop" in element.get_name()
(2-2/18)