Project

General

Profile

New Model #7997 » senhaix_8800.py

Jiaxun Yang, 11/04/2020 11:34 PM

 
1
# Copyright 2020 Jiauxn Yang <jiaxun.yang@flygoat.com>
2
#
3
# This program is free software: you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation, either version 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
"""SenHaiX 8800 radio management module"""
17

    
18
import struct
19
import time
20
import os
21
import logging
22

    
23
from chirp import util, chirp_common, bitwise, errors, directory, memmap
24
from chirp.settings import RadioSetting, RadioSettingGroup, \
25
                RadioSettingValueBoolean, RadioSettingValueList, \
26
                RadioSettingValueInteger, RadioSettingValueString, \
27
                RadioSettingValueFloat, RadioSettingValueMap, RadioSettings
28

    
29
LOG = logging.getLogger(__name__)
30

    
31
MEM_SIZE = 0x1c00
32
CMD_ACK = "\x06"
33
BLOCK_SIZE = 64
34

    
35
def _rawrecv(radio, amount):
36
    """Raw read from the radio device"""
37
    data = ""
38
    try:
39
        data = radio.pipe.read(amount)
40
    except:
41
        msg = "Generic error reading data from radio; check your cable."
42
        raise errors.RadioError(msg)
43

    
44
    if len(data) != amount:
45
        msg = "Error reading data from radio: not the amount of data we want."
46
        raise errors.RadioError(msg)
47

    
48
    return data
49

    
50

    
51
def _rawsend(radio, data):
52
    """Raw send to the radio device"""
53
    try:
54
        radio.pipe.write(data)
55
    except:
56
        raise errors.RadioError("Error sending data to radio")
57

    
58

    
59
def _make_frame(cmd, addr, length, data=""):
60
    """Pack the info in the headder format"""
61
    frame = struct.pack(">BHB", ord(cmd), addr, length)
62
    # add the data if set
63
    if len(data) != 0:
64
        frame += data
65

    
66
    return frame
67

    
68
def SHX8800_prep(radio):
69
    """Prepare radio device for transmission"""
70
    _rawsend(radio, "PROGROM")
71
    _rawsend(radio, "SHX")
72
    _rawsend(radio, "U")
73
    ack = _rawrecv(radio, 1)
74
    if ack != CMD_ACK:
75
        raise errors.RadioError("Radio did not ACK first command")
76

    
77
    _rawsend(radio, "F")
78
    ident = _rawrecv(radio, 8)
79
    if len(ident) != 8:
80
        LOG.debug(util.hexprint(ident))
81
        raise errors.RadioError("Radio did not send identification")
82

    
83
    LOG.info("Ident: " + util.hexprint(ident))
84

    
85
def SHX8800_exit(radio):
86
    """Exit programming mode"""
87
    _rawsend(radio, "E")
88

    
89
def _recv_block(radio, addr, blocksize):
90
    """Receive a block from the radio ROM"""
91
    _rawsend(radio, _make_frame("R", addr, blocksize))
92

    
93
    # read 4 bytes of header
94
    hdr = _rawrecv(radio, 4)
95

    
96
    # read data
97
    data = _rawrecv(radio, blocksize)
98

    
99
    # DEBUG
100
    LOG.debug("Response:")
101
    LOG.debug("\n " + util.hexprint(data))
102

    
103
    c, a, l = struct.unpack(">BHB", hdr)
104
    if a != addr or l != blocksize or c != ord("R"):
105
        LOG.error("Invalid answer for block 0x%04x:" % addr)
106
        LOG.error("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
107
        raise errors.RadioError("Unknown response from the radio")
108

    
109
    return data
110

    
111
def _write_block(radio, addr, blocksize, data):
112
    """Write a block to the radio ROM"""
113
    # Send CMD + Data
114
    _rawsend(radio, _make_frame("W", addr, blocksize, data))
115

    
116
    # read response
117
    resp = _rawrecv(radio, 1)
118

    
119
    if resp != CMD_ACK:
120
        raise errors.RadioError("No ACK from the radio")
121

    
122

    
123
def do_download(radio):
124
    """ The download function """
125
    SHX8800_prep(radio)
126

    
127
    # UI progress
128
    status = chirp_common.Status()
129
    status.cur = 0
130
    status.max = MEM_SIZE
131
    status.msg = "Cloning from radio..."
132
    radio.status_fn(status)
133
    data = ""
134

    
135
    for addr in range(0x0000, MEM_SIZE, BLOCK_SIZE):
136
        data += _recv_block(radio, addr, BLOCK_SIZE)
137
        # UI Update
138
        status.cur = addr
139
        radio.status_fn(status)
140

    
141
    SHX8800_exit(radio)
142

    
143
    return memmap.MemoryMap(data)
144

    
145

    
146
def do_upload(radio):
147
    """The upload function"""
148
    SHX8800_prep(radio)
149

    
150
    # UI progress
151
    status = chirp_common.Status()
152
    status.cur = 0
153
    status.max = MEM_SIZE
154
    status.msg = "Cloning to radio..."
155
    radio.status_fn(status)
156

    
157
    for addr in range(0x0000, MEM_SIZE, BLOCK_SIZE):
158
        _write_block(radio, addr, BLOCK_SIZE, 
159
                    radio._mmap[addr:addr+BLOCK_SIZE])
160
        # UI Update
161
        status.cur = addr
162
        radio.status_fn(status)
163

    
164
    SHX8800_exit(radio)
165

    
166

    
167
SHX8800_MEM_FORMAT = """
168
struct {
169
  lbcd rxfreq[4];
170
  lbcd txfreq[4];
171
  ul16 rxtone;
172
  ul16 txtone;
173
  u8 scode;
174
  u8 pttid;
175
  u8 power_lvl;
176
  u8 spare1:1,
177
     narrow:1,
178
     spare0:2,
179
     bcl:1,
180
     scan:1,
181
     allow_tx:1,
182
     fhss:1;
183
} memory[128];
184

    
185
#seekto 0xc00;
186
struct {
187
  char name[16];
188
} names[128];
189

    
190
#seekto 0x1a00;
191
struct {
192
  u8 squelch;
193
  u8 battery_saver;
194
  u8 vox;
195
  u8 auto_bl;
196
  u8 tdr;
197
  u8 tot;
198
  u8 beep;
199
  u8 voice;
200
  u8 language;
201
  u8 dtmfst;
202
  u8 scan_mode;
203
  u8 pttid;
204
  u8 pttlt;
205
  u8 mdfa;
206
  u8 mdfb;
207
  u8 bcl;
208

    
209
  u8 autolk;
210
  u8 almod;
211
  u8 alsnd;
212
  u8 tx_under_tdr_start;
213
  u8 ste;
214
  u8 rpste;
215
  u8 rptrl;
216
  u8 roger;
217
  u8 unknown;
218
  u8 fmradio;
219
  u8 workmodeb:4,
220
     workmodea:4;
221
  u8 keylock;
222
  u8 unknown1[4];
223

    
224
  u8 voxdelay;
225
  u8 menu_timeout;
226
  u8 micgain;
227
} settings;
228

    
229
#seekto 0x1a40;
230
struct {
231
  u8 freq[8];
232
  ul16 rxtone;
233
  ul16 txtone;
234
  u8 unknown[2];
235
  u8 unused2:2,
236
     sftd:2,
237
     scode:4;
238
  u8 unknown1;
239
  u8 txpower;
240
  u8 widenarr:1,
241
     unknown2:4
242
     fhss:1;
243
  u8 band;
244
  u8 unknown3:5,
245
     step:3;
246
  u8 unknown4;
247
  u8 offset[6];
248
} vfoa;
249

    
250
#seekto 0x1a60;
251
struct {
252
  u8 freq[8];
253
  ul16 rxtone;
254
  ul16 txtone;
255
  u8 unknown[2];
256
  u8 unused2:2,
257
     sftd:2,
258
     scode:4;
259
  u8 unknown1;
260
  u8 txpower;
261
  u8 widenarr:1,
262
     unknown2:4
263
     fhss:1;
264
  u8 band;
265
  u8 unknown3:5,
266
     step:3;
267
  u8 unknown4;
268
  u8 offset[6];
269
} vfob;
270

    
271
#seekto 0x1a80;
272
struct {
273
    u8 sidekey;
274
    u8 sidekeyl;
275
} keymaps;
276

    
277
#seekto 0x1b00;
278
struct {
279
  u8 code[5];
280
  u8 unused[11];
281
} pttid[15];
282

    
283
struct {
284
  u8 code[5];
285
  u8 group_code;
286
  u8 aniid;
287
  u8 dtmfon;
288
  u8 dtmfoff;
289
} ani;
290

    
291
"""
292

    
293
SHX8800_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
294
                     chirp_common.PowerLevel("Low",  watts=1.00)]
295

    
296
SHX8800_DTCS = sorted(chirp_common.DTCS_CODES + [645])
297

    
298
AUTOBL_LIST = ["OFF", "5 sec", "10 sec", "15 sec", "20 sec", "30 sec", "1 min", "2 min", "3 min"]
299
TOT_LIST = ["OFF"] + ["%s sec" % x for x in range(30, 270, 30)]
300
VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 4)]
301
BANDWIDTH_LIST = ["Wide", "Narrow"]
302
LANGUAGE_LIST = ["English", "Chinese"]
303
DTMFST_LIST = ["OFF", "DT-ST", "ANI-ST", "DT+ANI"]
304
SCAN_MODE_LIST = ["TO", "CO", "SE"]
305
PTTID_LIST = ["OFF", "BOT", "EOT", "Both"]
306
PTTLT_LIST = ["%s ms" % x for x in range(0, 31)]
307
MODE_LIST = ["CH + Name", "CH + Frequency"]
308
ALMOD_LIST = ["SITE", "TOME", "CODE"]
309
RPSTE_LIST = ["OFF"] + ["%s" % x for x in range(1, 11)]
310
STEDELAY_LIST = ["%s ms" % x for x in range(0, 1100, 100)]
311
WORKMODE_LIST = ["VFO", "CH"]
312
VOX_DELAY_LIST = ["%s ms" % x for x in range(500, 2100, 100)]
313
MENU_TIMEOUT_LIST = ["%s sec" % x for x in range(5, 65, 5)]
314
MICGAIN_LIST = ["%s" % x for x in range(1, 6, 1)]
315
DTMFSPEED_LIST = ["%s ms" % x for x in range(50, 550, 50)]
316
PTTIDCODE_LIST = ["%s" % x for x in range(1, 16)]
317
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0]
318
STEP_LIST = [str(x) for x in STEPS]
319
TXPOWER_LIST = ["High", "Low"]
320
SHIFTD_LIST = ["Off", "+", "-"]
321
KEY_FUNCTIONS = [("Monitor", 5), ("Broadcast FM Radio", 7), ("Tx Power Switch", 10), ("Scan", 28), ("Match", 29)]
322

    
323

    
324
@directory.register
325
class SenHaiX8800Radio(chirp_common.CloneModeRadio):
326
    """SenHaiX 8800"""
327
    VENDOR = "SenHaiX"
328
    MODEL = "8800"
329
    BAUD_RATE = 9600
330

    
331
    def get_features(self):
332
        rf = chirp_common.RadioFeatures()
333
        rf.has_settings = True
334
        rf.has_bank = False
335
        rf.has_cross = True
336
        rf.has_rx_dtcs = True
337
        rf.has_tuning_step = False
338
        rf.can_odd_split = True
339
        rf.valid_name_length = 7
340
        rf.valid_characters = chirp_common.CHARSET_ASCII
341
        rf.valid_skips = ["", "S"]
342
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
343
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
344
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
345
        rf.valid_power_levels = SHX8800_POWER_LEVELS
346
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
347
        rf.valid_modes = ["FM", "NFM"]
348
        rf.valid_tuning_steps = STEPS
349

    
350
        rf.valid_bands = [(100000000, 176000000), (400000000, 521000000)]
351
        rf.memory_bounds = (0, 127)
352

    
353
        return rf
354

    
355
    def sync_in(self):
356
        self._mmap = do_download(self)
357
        self.process_mmap()
358

    
359
    def sync_out(self):
360
        do_upload(self)
361

    
362
    def process_mmap(self):
363
        self._memobj = bitwise.parse(SHX8800_MEM_FORMAT, self._mmap)
364

    
365
    def _is_txinh(self, _mem):
366
        return _mem.allow_tx == False
367

    
368
    def _get_mem(self, number):
369
        return self._memobj.memory[number]
370

    
371
    def _get_nam(self, number):
372
        return self._memobj.names[number]
373

    
374
    def get_raw_memory(self, number):
375
        return repr(self._memobj.memory[number])
376

    
377
    def get_memory(self, number):
378
        _mem = self._get_mem(number)
379
        _nam = self._get_nam(number)
380

    
381
        mem = chirp_common.Memory()
382
        mem.number = number
383

    
384
        if _mem.get_raw()[0] == "\xff":
385
            mem.empty = True
386
            return mem
387

    
388
        mem.freq = int(_mem.rxfreq) * 10
389

    
390
        if self._is_txinh(_mem):
391
            mem.duplex = "off"
392
            mem.offset = 0
393
        elif int(_mem.rxfreq) == int(_mem.txfreq):
394
            mem.duplex = ""
395
            mem.offset = 0
396
        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
397
            mem.duplex = "split"
398
            mem.offset = int(_mem.txfreq) * 10
399
        else:
400
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
401
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
402

    
403
        for char in _nam.name:
404
            if str(char) == "\xFF":
405
                char = " "
406
            mem.name += str(char)
407
        mem.name = mem.name.rstrip()
408

    
409
        dtcs_pol = ["N", "N"]
410

    
411
        if _mem.txtone in [0, 0xFFFF]:
412
            txmode = ""
413
        elif _mem.txtone >= 0x0258:
414
            txmode = "Tone"
415
            mem.rtone = int(_mem.txtone) / 10.0
416
        elif _mem.txtone <= 0x0258:
417
            txmode = "DTCS"
418
            if _mem.txtone > 0x69:
419
                index = _mem.txtone - 0x6A
420
                dtcs_pol[0] = "R"
421
            else:
422
                index = _mem.txtone - 1
423
            mem.dtcs = SHX8800_DTCS[index]
424
        else:
425
            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
426

    
427
        if _mem.rxtone in [0, 0xFFFF]:
428
            rxmode = ""
429
        elif _mem.rxtone >= 0x0258:
430
            rxmode = "Tone"
431
            mem.ctone = int(_mem.rxtone) / 10.0
432
        elif _mem.rxtone <= 0x0258:
433
            rxmode = "DTCS"
434
            if _mem.rxtone >= 0x6A:
435
                index = _mem.rxtone - 0x6A
436
                dtcs_pol[1] = "R"
437
            else:
438
                index = _mem.rxtone - 1
439
            mem.rx_dtcs = SHX8800_DTCS[index]
440
        else:
441
            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
442

    
443
        if txmode == "Tone" and not rxmode:
444
            mem.tmode = "Tone"
445
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
446
            mem.tmode = "TSQL"
447
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
448
            mem.tmode = "DTCS"
449
        elif rxmode or txmode:
450
            mem.tmode = "Cross"
451
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
452

    
453
        mem.dtcs_polarity = "".join(dtcs_pol)
454

    
455
        if not _mem.scan:
456
            mem.skip = "S"
457

    
458
        mem.power = SHX8800_POWER_LEVELS[_mem.power_lvl]
459

    
460
        mem.mode = _mem.narrow and "NFM" or "FM"
461

    
462
        mem.extra = RadioSettingGroup("Extra", "extra")
463

    
464
        rs = RadioSetting("bcl", "bcl",
465
                          RadioSettingValueBoolean(_mem.bcl))
466
        mem.extra.append(rs)
467

    
468
        rs = RadioSetting("pttid", "PTT ID",
469
                          RadioSettingValueList(PTTID_LIST,
470
                                                PTTID_LIST[_mem.pttid]))
471
        mem.extra.append(rs)
472

    
473
        rs = RadioSetting("scode", "PTT ID Code",
474
                          RadioSettingValueList(PTTIDCODE_LIST,
475
                                                PTTIDCODE_LIST[_mem.scode]))
476
        mem.extra.append(rs)
477

    
478
        return mem
479

    
480
    def _set_mem(self, number):
481
        return self._memobj.memory[number]
482

    
483
    def _set_nam(self, number):
484
        return self._memobj.names[number]
485

    
486
    def set_memory(self, mem):
487
        _mem = self._get_mem(mem.number)
488
        _nam = self._get_nam(mem.number)
489

    
490
        if mem.empty:
491
            _mem.set_raw("\xff" * 16)
492
            _nam.set_raw("\xff" * 16)
493
            return
494

    
495
        was_empty = False
496
        # same method as used in get_memory to find
497
        # out whether a raw memory is empty
498
        if _mem.get_raw()[0] == "\xff":
499
            was_empty = True
500
            LOG.debug("SenHaiX 8800: this mem was empty")
501
        else:
502
            LOG.debug("mem was not empty, memorize extra-settings")
503
            prev_bcl = _mem.bcl.get_value()
504
            prev_scode = _mem.scode.get_value()
505
            prev_pttid = _mem.pttid.get_value()
506

    
507
        _mem.set_raw("\x00" * 16)
508

    
509
        _mem.rxfreq = mem.freq / 10
510

    
511
        _mem.allow_tx = True
512
        if mem.duplex == "off":
513
            _mem.allow_tx = False
514
            _mem.txfreq = mem.offset / 10
515
        elif mem.duplex == "split":
516
            _mem.txfreq = mem.offset / 10
517
        elif mem.duplex == "+":
518
            _mem.txfreq = (mem.freq + mem.offset) / 10
519
        elif mem.duplex == "-":
520
            _mem.txfreq = (mem.freq - mem.offset) / 10
521
        else:
522
            _mem.txfreq = mem.freq / 10
523

    
524
        _namelength = self.get_features().valid_name_length
525
        for i in range(_namelength):
526
            try:
527
                _nam.name[i] = mem.name[i]
528
            except IndexError:
529
                _nam.name[i] = "\xFF"
530

    
531
        rxmode = txmode = ""
532
        if mem.tmode == "Tone":
533
            _mem.txtone = int(mem.rtone * 10)
534
            _mem.rxtone = 0
535
        elif mem.tmode == "TSQL":
536
            _mem.txtone = int(mem.ctone * 10)
537
            _mem.rxtone = int(mem.ctone * 10)
538
        elif mem.tmode == "DTCS":
539
            rxmode = txmode = "DTCS"
540
            _mem.txtone = SHX8800_DTCS.index(mem.dtcs) + 1
541
            _mem.rxtone = SHX8800_DTCS.index(mem.dtcs) + 1
542
        elif mem.tmode == "Cross":
543
            txmode, rxmode = mem.cross_mode.split("->", 1)
544
            if txmode == "Tone":
545
                _mem.txtone = int(mem.rtone * 10)
546
            elif txmode == "DTCS":
547
                _mem.txtone = SHX8800_DTCS.index(mem.dtcs) + 1
548
            else:
549
                _mem.txtone = 0
550
            if rxmode == "Tone":
551
                _mem.rxtone = int(mem.ctone * 10)
552
            elif rxmode == "DTCS":
553
                _mem.rxtone = SHX8800_DTCS.index(mem.rx_dtcs) + 1
554
            else:
555
                _mem.rxtone = 0
556
        else:
557
            _mem.rxtone = 0
558
            _mem.txtone = 0
559

    
560
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
561
            _mem.txtone += 0x69
562
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
563
            _mem.rxtone += 0x69
564

    
565
        _mem.scan = mem.skip != "S"
566
        _mem.narrow = mem.mode == "NFM"
567
        _mem.power_lvl = SHX8800_POWER_LEVELS.index(mem.power)
568

    
569
        if not was_empty:
570
            # restoring old extra-settings
571
            _mem.bcl.set_value(prev_bcl)
572
            _mem.scode.set_value(prev_scode)
573
            _mem.pttid.set_value(prev_pttid)
574

    
575
        for setting in mem.extra:
576
            setattr(_mem, setting.get_name(), setting.value)
577

    
578
    def _get_settings(self):
579
        _ani = self._memobj.ani
580
        _settings = self._memobj.settings
581
        _vfoa = self._memobj.vfoa
582
        _vfob = self._memobj.vfob
583
        _keymaps = self._memobj.keymaps
584

    
585
        basic = RadioSettingGroup("basic", "Basic Settings")
586
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
587
        workmode = RadioSettingGroup("workmode", "Work Mode Settings")
588
        keymaps = RadioSettingGroup("keymaps", "KeyMaps")
589
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
590

    
591
        group = RadioSettings(basic, advanced, workmode, keymaps, dtmf)
592

    
593
        rs = RadioSetting("squelch", "Carrier Squelch Level",
594
                          RadioSettingValueInteger(0, 5, _settings.squelch))
595
        basic.append(rs)
596

    
597
        rs = RadioSetting("battery_saver", "Battery Save",
598
                        RadioSettingValueBoolean(_settings.battery_saver))
599
        advanced.append(rs)
600

    
601
        rs = RadioSetting("vox", "VOX Sensitivity",
602
                          RadioSettingValueList(
603
                          VOX_LIST, VOX_LIST[_settings.vox]))
604
        basic.append(rs)
605

    
606
        rs = RadioSetting("auto_bl", "Auto Backlight Timeout",
607
                          RadioSettingValueList(
608
                              AUTOBL_LIST, AUTOBL_LIST[_settings.auto_bl]))
609
        advanced.append(rs)
610

    
611
        rs = RadioSetting("tot", "TX Timeout Timer",
612
                          RadioSettingValueList(
613
                              TOT_LIST, TOT_LIST[_settings.tot]))
614
        basic.append(rs)
615

    
616
        rs = RadioSetting("beep", "Beep",
617
                          RadioSettingValueBoolean(_settings.beep))
618
        basic.append(rs)
619

    
620
        rs = RadioSetting("voice", "Voice",
621
                          RadioSettingValueBoolean(_settings.voice))
622
        advanced.append(rs)
623

    
624
        rs = RadioSetting("language", "Language",
625
                          RadioSettingValueList(
626
                          LANGUAGE_LIST, LANGUAGE_LIST[_settings.language]))
627
        advanced.append(rs)
628

    
629
        rs = RadioSetting("mdfa", "Display Mode (A)",
630
                            RadioSettingValueList(
631
                            MODE_LIST, MODE_LIST[_settings.mdfa]))
632
        basic.append(rs)
633

    
634
        rs = RadioSetting("mdfb", "Display Mode (B)",
635
                            RadioSettingValueList(
636
                            MODE_LIST, MODE_LIST[_settings.mdfb]))
637
        basic.append(rs)
638

    
639
        rs = RadioSetting("scan_mode", "Scan Mode",
640
                            RadioSettingValueList(
641
                            SCAN_MODE_LIST, SCAN_MODE_LIST[_settings.scan_mode]))
642
        basic.append(rs)
643

    
644
        rs = RadioSetting("bcl", "Busy Channel Lockout",
645
                          RadioSettingValueBoolean(_settings.bcl))
646
        advanced.append(rs)
647

    
648
        rs = RadioSetting("autolk", "Automatic Key Lock",
649
                            RadioSettingValueBoolean(_settings.autolk))
650
        advanced.append(rs)
651

    
652
        rs = RadioSetting("almod", "Alarm Mode",
653
                          RadioSettingValueList(
654
                            ALMOD_LIST, ALMOD_LIST[_settings.almod]))
655
        advanced.append(rs)
656

    
657
        rs = RadioSetting("alsnd", "Alarm Sound",
658
                          RadioSettingValueBoolean(_settings.alsnd))
659
        advanced.append(rs)
660

    
661
        rs = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)",
662
                          RadioSettingValueBoolean(_settings.ste))
663
        advanced.append(rs)
664

    
665
        rs = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)",
666
                          RadioSettingValueList(
667
                              RPSTE_LIST, RPSTE_LIST[_settings.rpste]))
668
        advanced.append(rs)
669

    
670
        rs = RadioSetting("rptrl", "STE Repeater Delay",
671
                          RadioSettingValueList(
672
                              STEDELAY_LIST, STEDELAY_LIST[_settings.rptrl]))
673
        advanced.append(rs)
674

    
675
        rs = RadioSetting("fmradio", "Disable Broadcast FM Radio",
676
                          RadioSettingValueBoolean(_settings.fmradio))
677
        advanced.append(rs)
678

    
679
        rs = RadioSetting("keylock", "Keypad Lock",
680
                            RadioSettingValueBoolean(_settings.keylock))
681
        advanced.append(rs)
682

    
683
        rs = RadioSetting("voxdelay", "VOX Delay",
684
                            RadioSettingValueList(
685
                               VOX_DELAY_LIST,
686
                               VOX_DELAY_LIST[_settings.voxdelay]))
687
        advanced.append(rs)
688

    
689
        rs = RadioSetting("menu_timeout", "Menu Timeout",
690
                            RadioSettingValueList(
691
                               MENU_TIMEOUT_LIST,
692
                               MENU_TIMEOUT_LIST[_settings.menu_timeout]))
693
        advanced.append(rs)
694

    
695
        rs = RadioSetting("micgain", "Mic Gain",
696
                            RadioSettingValueList(
697
                               MICGAIN_LIST,
698
                               MICGAIN_LIST[_settings.micgain]))
699
        advanced.append(rs)
700

    
701
        rs = RadioSetting("keymaps.sidekey", "Side Key Short Press",
702
                           RadioSettingValueMap(KEY_FUNCTIONS, _keymaps.sidekey))
703
        keymaps.append(rs)
704

    
705
        rs = RadioSetting("keymaps.sidekeyl", "Side Key Long Press",
706
                           RadioSettingValueMap(KEY_FUNCTIONS, _keymaps.sidekeyl))
707
        keymaps.append(rs)
708

    
709
        rs = RadioSetting("workmodea", "Work Mode (A)",
710
                            RadioSettingValueList(
711
                                WORKMODE_LIST,
712
                                WORKMODE_LIST[_settings.workmodea]))
713
        workmode.append(rs)
714

    
715
        rs = RadioSetting("workmodeb", "Work Mode (B)",
716
                            RadioSettingValueList(
717
                                WORKMODE_LIST,
718
                                WORKMODE_LIST[_settings.workmodeb]))
719
        workmode.append(rs)
720

    
721
        def convert_bytes_to_freq(bytes):
722
            real_freq = 0
723
            for byte in bytes:
724
                real_freq = (real_freq * 10) + byte
725
            return chirp_common.format_freq(real_freq * 10)
726

    
727
        def my_validate(value):
728
            value = chirp_common.parse_freq(value)
729
            if 17400000 <= value and value < 40000000:
730
                msg = ("Can't be between 174.00000-400.00000")
731
                raise InvalidValueError(msg)
732
            return chirp_common.format_freq(value)
733

    
734
        def apply_freq(setting, obj):
735
            value = chirp_common.parse_freq(str(setting.value)) / 10
736
            obj.band = value >= 40000000
737
            for i in range(7, -1, -1):
738
                obj.freq[i] = value % 10
739
                value /= 10
740

    
741
        val1a = RadioSettingValueString(0, 10,
742
                                        convert_bytes_to_freq(_vfoa.freq))
743
        val1a.set_validate_callback(my_validate)
744
        rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a)
745
        rs.set_apply_callback(apply_freq, _vfoa)
746
        workmode.append(rs)
747

    
748
        val1b = RadioSettingValueString(0, 10,
749
                                        convert_bytes_to_freq(_vfob.freq))
750
        val1b.set_validate_callback(my_validate)
751
        rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b)
752
        rs.set_apply_callback(apply_freq, _vfob)
753
        workmode.append(rs)
754

    
755
        rs = RadioSetting("vfoa.sftd", "VFO A Shift",
756
                            RadioSettingValueList(
757
                                SHIFTD_LIST, SHIFTD_LIST[_vfoa.sftd]))
758
        workmode.append(rs)
759

    
760
        rs = RadioSetting("vfob.sftd", "VFO B Shift",
761
                            RadioSettingValueList(
762
                                SHIFTD_LIST, SHIFTD_LIST[_vfob.sftd]))
763
        workmode.append(rs)
764

    
765
        def convert_bytes_to_offset(bytes):
766
            real_offset = 0
767
            for byte in bytes:
768
                real_offset = (real_offset * 10) + byte
769
            return chirp_common.format_freq(real_offset * 100)
770

    
771
        def apply_offset(setting, obj):
772
            value = chirp_common.parse_freq(str(setting.value)) / 100
773
            for i in range(5, -1, -1):
774
                obj.offset[i] = value % 10
775
                value /= 10
776

    
777
        val1a = RadioSettingValueString(
778
            0, 10, convert_bytes_to_offset(_vfoa.offset))
779
        rs = RadioSetting("vfoa.offset",
780
                            "VFO A Offset (0.0-999.999)", val1a)
781
        rs.set_apply_callback(apply_offset, _vfoa)
782
        workmode.append(rs)
783

    
784
        val1b = RadioSettingValueString(
785
            0, 10, convert_bytes_to_offset(_vfob.offset))
786
        rs = RadioSetting("vfob.offset",
787
                            "VFO B Offset (0.0-999.999)", val1b)
788
        rs.set_apply_callback(apply_offset, _vfob)
789
        workmode.append(rs)
790

    
791
        rs = RadioSetting("vfoa.txpower", "VFO A Power",
792
                            RadioSettingValueList(
793
                                TXPOWER_LIST,
794
                                TXPOWER_LIST[_vfoa.txpower]))
795
        workmode.append(rs)
796

    
797
        rs = RadioSetting("vfob.txpower", "VFO B Power",
798
                            RadioSettingValueList(
799
                                TXPOWER_LIST,
800
                                TXPOWER_LIST[_vfob.txpower]))
801
        workmode.append(rs)
802

    
803
        rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth",
804
                            RadioSettingValueList(
805
                                BANDWIDTH_LIST,
806
                                BANDWIDTH_LIST[_vfoa.widenarr]))
807
        workmode.append(rs)
808

    
809
        rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
810
                            RadioSettingValueList(
811
                                BANDWIDTH_LIST,
812
                                BANDWIDTH_LIST[_vfob.widenarr]))
813
        workmode.append(rs)
814

    
815
        rs = RadioSetting("vfoa.scode", "VFO A PTT-ID",
816
                            RadioSettingValueList(
817
                                PTTIDCODE_LIST, PTTIDCODE_LIST[_vfoa.scode]))
818
        workmode.append(rs)
819

    
820
        rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
821
                            RadioSettingValueList(
822
                                PTTIDCODE_LIST, PTTIDCODE_LIST[_vfob.scode]))
823
        workmode.append(rs)
824

    
825
        rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
826
                            RadioSettingValueList(
827
                                STEP_LIST, STEP_LIST[_vfoa.step]))
828
        workmode.append(rs)
829
        rs = RadioSetting("vfob.step", "VFO B Tuning Step",
830
                            RadioSettingValueList(
831
                                STEP_LIST, STEP_LIST[_vfob.step]))
832
        workmode.append(rs)
833

    
834
        dtmfchars = "0123456789 *#ABCD"
835

    
836
        for i in range(0, 15):
837
            _codeobj = self._memobj.pttid[i].code
838
            _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
839
            val = RadioSettingValueString(0, 5, _code, False)
840
            val.set_charset(dtmfchars)
841
            rs = RadioSetting("pttid/%i.code" % i,
842
                              "PTT ID Code %i" % (i + 1), val)
843

    
844
            def apply_code(setting, obj):
845
                code = []
846
                for j in range(0, 5):
847
                    try:
848
                        code.append(dtmfchars.index(str(setting.value)[j]))
849
                    except IndexError:
850
                        code.append(0xFF)
851
                obj.code = code
852
            rs.set_apply_callback(apply_code, self._memobj.pttid[i])
853
            dtmf.append(rs)
854

    
855
        rs = RadioSetting("ani.aniid", "ANI ID",
856
                          RadioSettingValueList(PTTID_LIST,
857
                                                PTTID_LIST[_ani.aniid]))
858
        dtmf.append(rs)
859

    
860
        _codeobj = self._memobj.ani.code
861
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
862
        val = RadioSettingValueString(0, 5, _code, False)
863
        val.set_charset(dtmfchars)
864
        rs = RadioSetting("ani.code", "ANI Code", val)
865

    
866
        def apply_code(setting, obj):
867
            code = []
868
            for j in range(0, 5):
869
                try:
870
                    code.append(dtmfchars.index(str(setting.value)[j]))
871
                except IndexError:
872
                    code.append(0xFF)
873
            obj.code = code
874
        rs.set_apply_callback(apply_code, _ani)
875
        dtmf.append(rs)
876

    
877
        rs = RadioSetting("dtmfst", "DTMF Sidetone",
878
                          RadioSettingValueList(DTMFST_LIST,
879
                                                DTMFST_LIST[_settings.dtmfst]))
880
        dtmf.append(rs)
881

    
882
        if _ani.dtmfon > 0xC3:
883
            val = 0x00
884
        else:
885
            val = _ani.dtmfon
886
        rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
887
                          RadioSettingValueList(DTMFSPEED_LIST,
888
                                                DTMFSPEED_LIST[val]))
889
        dtmf.append(rs)
890

    
891
        if _ani.dtmfoff > 0xC3:
892
            val = 0x00
893
        else:
894
            val = _ani.dtmfoff
895
        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
896
                          RadioSettingValueList(DTMFSPEED_LIST,
897
                                                DTMFSPEED_LIST[val]))
898
        dtmf.append(rs)
899

    
900
        rs = RadioSetting("pttlt", "PTT ID Delay",
901
                          RadioSettingValueList(
902
                          PTTLT_LIST, PTTLT_LIST[_settings.pttlt]))
903
        dtmf.append(rs)
904

    
905
        return group
906

    
907
    def get_settings(self):
908
        try:
909
            return self._get_settings()
910
        except:
911
            import traceback
912
            LOG.error("Failed to parse settings: %s", traceback.format_exc())
913
            return None
914

    
915
    def set_settings(self, settings):
916
        _settings = self._memobj.settings
917
        for element in settings:
918
            if not isinstance(element, RadioSetting):
919
                    self.set_settings(element)
920
                    continue
921
            else:
922
                try:
923
                    name = element.get_name()
924
                    if "." in name:
925
                        bits = name.split(".")
926
                        obj = self._memobj
927
                        for bit in bits[:-1]:
928
                            if "/" in bit:
929
                                bit, index = bit.split("/", 1)
930
                                index = int(index)
931
                                obj = getattr(obj, bit)[index]
932
                            else:
933
                                obj = getattr(obj, bit)
934
                        setting = bits[-1]
935
                    else:
936
                        obj = _settings
937
                        setting = element.get_name()
938

    
939
                    if element.has_apply_callback():
940
                        LOG.debug("Using apply callback")
941
                        element.run_apply_callback()
942
                    elif element.value.get_mutable():
943
                        LOG.debug("Setting %s = %s" % (setting, element.value))
944
                        setattr(obj, setting, element.value)
945
                except Exception, e:
946
                    LOG.debug(element.get_name())
947
                    raise
948

    
949

    
950
    @classmethod
951
    def match_model(cls, filedata, filename):
952
        if len(filedata) in [MEM_SIZE]:
953
            return True
954

    
955
        return False
(1-1/10)