Project

General

Profile

New Model #7997 » senhaix_8800.py

the second rev - Jiaxun Yang, 11/07/2020 01:44 AM

 
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
ACK_RETRY = 10
34
BLOCK_SIZE_RX = 64
35
BLOCK_SIZE_TX = 32
36

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

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

    
50
    return data
51

    
52

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

    
60

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

    
68
    return frame
69

    
70

    
71
def SHX8800_prep(radio):
72
    """Prepare radio device for transmission"""
73
    for _i in range(0, ACK_RETRY):
74
        try:
75
            _rawsend(radio, "PROGROM")
76
            _rawsend(radio, "SHX")
77
            _rawsend(radio, "U")
78
            ack = _rawrecv(radio, 1)
79
            if ack != CMD_ACK:
80
                raise Exception("Radio did not properly ACK")
81
            break
82
        except Exception as inst:
83
                if _i < ACK_RETRY:
84
                    LOG.error("No ACK, retrying....")
85
                    SHX8800_exit(radio)
86
                    radio.pipe.flush()
87
                    time.sleep(0.100)
88
                    continue
89
                else:
90
                    raise errors.RadioError(inst)
91

    
92
    _rawsend(radio, "F")
93
    ident = _rawrecv(radio, 8)
94
    if len(ident) != 8:
95
        LOG.debug(util.hexprint(ident))
96
        raise errors.RadioError("Radio did not send identification")
97

    
98
    LOG.info("Ident: " + util.hexprint(ident))
99

    
100
def SHX8800_exit(radio):
101
    """Exit programming mode"""
102
    _rawsend(radio, "E")
103

    
104
def _recv_block(radio, addr, blocksize):
105
    """Receive a block from the radio ROM"""
106
    _rawsend(radio, _make_frame("R", addr, blocksize))
107

    
108
    # read 4 bytes of header
109
    hdr = _rawrecv(radio, 4)
110

    
111
    # read data
112
    data = _rawrecv(radio, blocksize)
113

    
114
    # DEBUG
115
    LOG.debug("Response:")
116
    LOG.debug("\n " + util.hexprint(data))
117

    
118
    c, a, l = struct.unpack(">BHB", hdr)
119
    if a != addr or l != blocksize or c != ord("R"):
120
        LOG.error("Invalid answer for block 0x%04x:" % addr)
121
        LOG.error("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
122
        raise errors.RadioError("Unknown response from the radio")
123

    
124
    return data
125

    
126
def _write_block(radio, addr, blocksize, data):
127
    """Write a block to the radio ROM"""
128
    # Send CMD + Data
129
    _rawsend(radio, _make_frame("W", addr, blocksize, data))
130

    
131
    # read response
132
    resp = _rawrecv(radio, 1)
133

    
134
    if resp != CMD_ACK:
135
        raise errors.RadioError("No ACK from the radio")
136

    
137

    
138
def do_download(radio):
139
    """ The download function """
140
    SHX8800_prep(radio)
141

    
142
    # UI progress
143
    status = chirp_common.Status()
144
    status.cur = 0
145
    status.max = MEM_SIZE
146
    status.msg = "Cloning from radio..."
147
    radio.status_fn(status)
148
    data = ""
149

    
150
    for addr in range(0x0000, MEM_SIZE, BLOCK_SIZE_RX):
151
        data += _recv_block(radio, addr, BLOCK_SIZE_RX)
152
        # UI Update
153
        status.cur = addr
154
        radio.status_fn(status)
155

    
156
    SHX8800_exit(radio)
157

    
158
    return memmap.MemoryMap(data)
159

    
160

    
161
def do_upload(radio):
162
    """The upload function"""
163
    SHX8800_prep(radio)
164

    
165
    # UI progress
166
    status = chirp_common.Status()
167
    status.cur = 0
168
    status.max = MEM_SIZE
169
    status.msg = "Cloning to radio..."
170
    radio.status_fn(status)
171

    
172
    for addr in range(0x0000, MEM_SIZE, BLOCK_SIZE_TX):
173
        _write_block(radio, addr, BLOCK_SIZE_TX,
174
                    radio._mmap[addr:addr+BLOCK_SIZE_TX])
175
        # UI Update
176
        status.cur = addr
177
        radio.status_fn(status)
178

    
179
    SHX8800_exit(radio)
180

    
181

    
182
SHX8800_MEM_FORMAT = """
183
struct {
184
  lbcd rxfreq[4];
185
  lbcd txfreq[4];
186
  ul16 rxtone;
187
  ul16 txtone;
188
  u8 scode;
189
  u8 pttid;
190
  u8 power_lvl;
191
  u8 spare1:1,
192
     narrow:1,
193
     spare0:2,
194
     bcl:1,
195
     scan:1,
196
     allow_tx:1,
197
     fhss:1;
198
} memory[128];
199

    
200
#seekto 0xc00;
201
struct {
202
  char name[16];
203
} names[128];
204

    
205
#seekto 0x1a00;
206
struct {
207
  u8 squelch;
208
  u8 battery_saver;
209
  u8 vox;
210
  u8 auto_bl;
211
  u8 tdr;
212
  u8 tot;
213
  u8 beep;
214
  u8 voice;
215
  u8 language;
216
  u8 dtmfst;
217
  u8 scan_mode;
218
  u8 pttid;
219
  u8 pttlt;
220
  u8 mdfa;
221
  u8 mdfb;
222
  u8 bcl;
223

    
224
  u8 autolk;
225
  u8 almod;
226
  u8 alsnd;
227
  u8 tx_under_tdr_start;
228
  u8 ste;
229
  u8 rpste;
230
  u8 rptrl;
231
  u8 roger;
232
  u8 unknown;
233
  u8 fmradio;
234
  u8 workmodeb:4,
235
     workmodea:4;
236
  u8 keylock;
237
  u8 unknown1[4];
238

    
239
  u8 voxdelay;
240
  u8 menu_timeout;
241
  u8 micgain;
242
} settings;
243

    
244
#seekto 0x1a40;
245
struct {
246
  u8 freq[8];
247
  ul16 rxtone;
248
  ul16 txtone;
249
  u8 unknown[2];
250
  u8 unused2:2,
251
     sftd:2,
252
     scode:4;
253
  u8 unknown1;
254
  u8 txpower;
255
  u8 widenarr:1,
256
     unknown2:4
257
     fhss:1;
258
  u8 band;
259
  u8 unknown3:5,
260
     step:3;
261
  u8 unknown4;
262
  u8 offset[6];
263
} vfoa;
264

    
265
#seekto 0x1a60;
266
struct {
267
  u8 freq[8];
268
  ul16 rxtone;
269
  ul16 txtone;
270
  u8 unknown[2];
271
  u8 unused2:2,
272
     sftd:2,
273
     scode:4;
274
  u8 unknown1;
275
  u8 txpower;
276
  u8 widenarr:1,
277
     unknown2:4
278
     fhss:1;
279
  u8 band;
280
  u8 unknown3:5,
281
     step:3;
282
  u8 unknown4;
283
  u8 offset[6];
284
} vfob;
285

    
286
#seekto 0x1a80;
287
struct {
288
    u8 sidekey;
289
    u8 sidekeyl;
290
} keymaps;
291

    
292
#seekto 0x1b00;
293
struct {
294
  u8 code[5];
295
  u8 unused[11];
296
} pttid[15];
297

    
298
struct {
299
  u8 code[5];
300
  u8 group_code;
301
  u8 aniid;
302
  u8 dtmfon;
303
  u8 dtmfoff;
304
} ani;
305

    
306
"""
307

    
308
SHX8800_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
309
                     chirp_common.PowerLevel("Low",  watts=1.00)]
310

    
311
SHX8800_DTCS = sorted(chirp_common.DTCS_CODES + [645])
312

    
313
AUTOBL_LIST = ["OFF", "5 sec", "10 sec", "15 sec", "20 sec", "30 sec", "1 min", "2 min", "3 min"]
314
TOT_LIST = ["OFF"] + ["%s sec" % x for x in range(30, 270, 30)]
315
VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 4)]
316
BANDWIDTH_LIST = ["Wide", "Narrow"]
317
LANGUAGE_LIST = ["English", "Chinese"]
318
DTMFST_LIST = ["OFF", "DT-ST", "ANI-ST", "DT+ANI"]
319
SCAN_MODE_LIST = ["TO", "CO", "SE"]
320
PTTID_LIST = ["OFF", "BOT", "EOT", "Both"]
321
PTTLT_LIST = ["%s ms" % x for x in range(0, 31)]
322
MODE_LIST = ["CH + Name", "CH + Frequency"]
323
ALMOD_LIST = ["SITE", "TOME", "CODE"]
324
RPSTE_LIST = ["OFF"] + ["%s" % x for x in range(1, 11)]
325
STEDELAY_LIST = ["%s ms" % x for x in range(0, 1100, 100)]
326
WORKMODE_LIST = ["VFO", "CH"]
327
VOX_DELAY_LIST = ["%s ms" % x for x in range(500, 2100, 100)]
328
MENU_TIMEOUT_LIST = ["%s sec" % x for x in range(5, 65, 5)]
329
MICGAIN_LIST = ["%s" % x for x in range(1, 6, 1)]
330
DTMFSPEED_LIST = ["%s ms" % x for x in range(50, 550, 50)]
331
PTTIDCODE_LIST = ["%s" % x for x in range(1, 16)]
332
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0]
333
STEP_LIST = [str(x) for x in STEPS]
334
TXPOWER_LIST = ["High", "Low"]
335
SHIFTD_LIST = ["Off", "+", "-"]
336
KEY_FUNCTIONS = [("Monitor", 5), ("Broadcast FM Radio", 7), ("Tx Power Switch", 10), ("Scan", 28), ("Match", 29)]
337

    
338

    
339
@directory.register
340
class SenHaiX8800Radio(chirp_common.CloneModeRadio):
341
    """SenHaiX 8800"""
342
    VENDOR = "SenHaiX"
343
    MODEL = "8800"
344
    BAUD_RATE = 9600
345

    
346
    @classmethod
347
    def get_prompts(cls):
348
        rp = chirp_common.RadioPrompts()
349
        rp.experimental = \
350
            ('This driver is experimental.\n'
351
             '\n'
352
             'Please keep a copy of your memories with the original software '
353
             'if you treasure them, this driver is new and may contain'
354
             ' bugs.\n'
355
             'It is known that cloning progress is not perfect. If stucking '
356
             'or error happens, please power cycle your radio before retry.'
357
             '\n'
358
             )
359
        return rp
360

    
361
    def get_features(self):
362
        rf = chirp_common.RadioFeatures()
363
        rf.has_settings = True
364
        rf.has_bank = False
365
        rf.has_cross = True
366
        rf.has_rx_dtcs = True
367
        rf.has_tuning_step = False
368
        rf.can_odd_split = True
369
        rf.valid_name_length = 7
370
        rf.valid_characters = chirp_common.CHARSET_ASCII
371
        rf.valid_skips = ["", "S"]
372
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
373
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
374
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
375
        rf.valid_power_levels = SHX8800_POWER_LEVELS
376
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
377
        rf.valid_modes = ["FM", "NFM"]
378
        rf.valid_tuning_steps = STEPS
379

    
380
        rf.valid_bands = [(100000000, 176000000), (400000000, 521000000)]
381
        rf.memory_bounds = (0, 127)
382

    
383
        return rf
384

    
385
    def sync_in(self):
386
        self._mmap = do_download(self)
387
        self.process_mmap()
388

    
389
    def sync_out(self):
390
        do_upload(self)
391

    
392
    def process_mmap(self):
393
        self._memobj = bitwise.parse(SHX8800_MEM_FORMAT, self._mmap)
394

    
395
    def _is_txinh(self, _mem):
396
        return _mem.allow_tx == False
397

    
398
    def _get_mem(self, number):
399
        return self._memobj.memory[number]
400

    
401
    def _get_nam(self, number):
402
        return self._memobj.names[number]
403

    
404
    def get_raw_memory(self, number):
405
        return repr(self._memobj.memory[number])
406

    
407
    def get_memory(self, number):
408
        _mem = self._get_mem(number)
409
        _nam = self._get_nam(number)
410

    
411
        mem = chirp_common.Memory()
412
        mem.number = number
413

    
414
        if _mem.get_raw()[0] == "\xff":
415
            mem.empty = True
416
            return mem
417

    
418
        mem.freq = int(_mem.rxfreq) * 10
419

    
420
        if self._is_txinh(_mem):
421
            mem.duplex = "off"
422
            mem.offset = 0
423
        elif int(_mem.rxfreq) == int(_mem.txfreq):
424
            mem.duplex = ""
425
            mem.offset = 0
426
        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
427
            mem.duplex = "split"
428
            mem.offset = int(_mem.txfreq) * 10
429
        else:
430
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
431
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
432

    
433
        for char in _nam.name:
434
            if str(char) == "\xFF":
435
                char = " "
436
            mem.name += str(char)
437
        mem.name = mem.name.rstrip()
438

    
439
        dtcs_pol = ["N", "N"]
440

    
441
        if _mem.txtone in [0, 0xFFFF]:
442
            txmode = ""
443
        elif _mem.txtone >= 0x0258:
444
            txmode = "Tone"
445
            mem.rtone = int(_mem.txtone) / 10.0
446
        elif _mem.txtone <= 0x0258:
447
            txmode = "DTCS"
448
            if _mem.txtone > 0x69:
449
                index = _mem.txtone - 0x6A
450
                dtcs_pol[0] = "R"
451
            else:
452
                index = _mem.txtone - 1
453
            mem.dtcs = SHX8800_DTCS[index]
454
        else:
455
            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
456

    
457
        if _mem.rxtone in [0, 0xFFFF]:
458
            rxmode = ""
459
        elif _mem.rxtone >= 0x0258:
460
            rxmode = "Tone"
461
            mem.ctone = int(_mem.rxtone) / 10.0
462
        elif _mem.rxtone <= 0x0258:
463
            rxmode = "DTCS"
464
            if _mem.rxtone >= 0x6A:
465
                index = _mem.rxtone - 0x6A
466
                dtcs_pol[1] = "R"
467
            else:
468
                index = _mem.rxtone - 1
469
            mem.rx_dtcs = SHX8800_DTCS[index]
470
        else:
471
            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
472

    
473
        if txmode == "Tone" and not rxmode:
474
            mem.tmode = "Tone"
475
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
476
            mem.tmode = "TSQL"
477
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
478
            mem.tmode = "DTCS"
479
        elif rxmode or txmode:
480
            mem.tmode = "Cross"
481
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
482

    
483
        mem.dtcs_polarity = "".join(dtcs_pol)
484

    
485
        if not _mem.scan:
486
            mem.skip = "S"
487

    
488
        mem.power = SHX8800_POWER_LEVELS[_mem.power_lvl]
489

    
490
        mem.mode = _mem.narrow and "NFM" or "FM"
491

    
492
        mem.extra = RadioSettingGroup("Extra", "extra")
493

    
494
        rs = RadioSetting("bcl", "bcl",
495
                          RadioSettingValueBoolean(_mem.bcl))
496
        mem.extra.append(rs)
497

    
498
        rs = RadioSetting("pttid", "PTT ID",
499
                          RadioSettingValueList(PTTID_LIST,
500
                                                PTTID_LIST[_mem.pttid]))
501
        mem.extra.append(rs)
502

    
503
        rs = RadioSetting("scode", "PTT ID Code",
504
                          RadioSettingValueList(PTTIDCODE_LIST,
505
                                                PTTIDCODE_LIST[_mem.scode]))
506
        mem.extra.append(rs)
507

    
508
        return mem
509

    
510
    def _set_mem(self, number):
511
        return self._memobj.memory[number]
512

    
513
    def _set_nam(self, number):
514
        return self._memobj.names[number]
515

    
516
    def set_memory(self, mem):
517
        _mem = self._get_mem(mem.number)
518
        _nam = self._get_nam(mem.number)
519

    
520
        if mem.empty:
521
            _mem.set_raw("\xff" * 16)
522
            _nam.set_raw("\xff" * 16)
523
            return
524

    
525
        was_empty = False
526
        # same method as used in get_memory to find
527
        # out whether a raw memory is empty
528
        if _mem.get_raw()[0] == "\xff":
529
            was_empty = True
530
            LOG.debug("SenHaiX 8800: this mem was empty")
531
        else:
532
            LOG.debug("mem was not empty, memorize extra-settings")
533
            prev_bcl = _mem.bcl.get_value()
534
            prev_scode = _mem.scode.get_value()
535
            prev_pttid = _mem.pttid.get_value()
536

    
537
        _mem.set_raw("\x00" * 16)
538

    
539
        _mem.rxfreq = mem.freq / 10
540

    
541
        _mem.allow_tx = True
542
        if mem.duplex == "off":
543
            _mem.allow_tx = False
544
            _mem.txfreq = mem.offset / 10
545
        elif mem.duplex == "split":
546
            _mem.txfreq = mem.offset / 10
547
        elif mem.duplex == "+":
548
            _mem.txfreq = (mem.freq + mem.offset) / 10
549
        elif mem.duplex == "-":
550
            _mem.txfreq = (mem.freq - mem.offset) / 10
551
        else:
552
            _mem.txfreq = mem.freq / 10
553

    
554
        _namelength = self.get_features().valid_name_length
555
        for i in range(_namelength):
556
            try:
557
                _nam.name[i] = mem.name[i]
558
            except IndexError:
559
                _nam.name[i] = "\xFF"
560

    
561
        rxmode = txmode = ""
562
        if mem.tmode == "Tone":
563
            _mem.txtone = int(mem.rtone * 10)
564
            _mem.rxtone = 0
565
        elif mem.tmode == "TSQL":
566
            _mem.txtone = int(mem.ctone * 10)
567
            _mem.rxtone = int(mem.ctone * 10)
568
        elif mem.tmode == "DTCS":
569
            rxmode = txmode = "DTCS"
570
            _mem.txtone = SHX8800_DTCS.index(mem.dtcs) + 1
571
            _mem.rxtone = SHX8800_DTCS.index(mem.dtcs) + 1
572
        elif mem.tmode == "Cross":
573
            txmode, rxmode = mem.cross_mode.split("->", 1)
574
            if txmode == "Tone":
575
                _mem.txtone = int(mem.rtone * 10)
576
            elif txmode == "DTCS":
577
                _mem.txtone = SHX8800_DTCS.index(mem.dtcs) + 1
578
            else:
579
                _mem.txtone = 0
580
            if rxmode == "Tone":
581
                _mem.rxtone = int(mem.ctone * 10)
582
            elif rxmode == "DTCS":
583
                _mem.rxtone = SHX8800_DTCS.index(mem.rx_dtcs) + 1
584
            else:
585
                _mem.rxtone = 0
586
        else:
587
            _mem.rxtone = 0
588
            _mem.txtone = 0
589

    
590
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
591
            _mem.txtone += 0x69
592
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
593
            _mem.rxtone += 0x69
594

    
595
        _mem.scan = mem.skip != "S"
596
        _mem.narrow = mem.mode == "NFM"
597
        _mem.power_lvl = SHX8800_POWER_LEVELS.index(mem.power)
598

    
599
        if not was_empty:
600
            # restoring old extra-settings
601
            _mem.bcl.set_value(prev_bcl)
602
            _mem.scode.set_value(prev_scode)
603
            _mem.pttid.set_value(prev_pttid)
604

    
605
        for setting in mem.extra:
606
            setattr(_mem, setting.get_name(), setting.value)
607

    
608
    def _get_settings(self):
609
        _ani = self._memobj.ani
610
        _settings = self._memobj.settings
611
        _vfoa = self._memobj.vfoa
612
        _vfob = self._memobj.vfob
613
        _keymaps = self._memobj.keymaps
614

    
615
        basic = RadioSettingGroup("basic", "Basic Settings")
616
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
617
        workmode = RadioSettingGroup("workmode", "Work Mode Settings")
618
        keymaps = RadioSettingGroup("keymaps", "KeyMaps")
619
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
620

    
621
        group = RadioSettings(basic, advanced, workmode, keymaps, dtmf)
622

    
623
        rs = RadioSetting("squelch", "Carrier Squelch Level",
624
                          RadioSettingValueInteger(0, 5, _settings.squelch))
625
        basic.append(rs)
626

    
627
        rs = RadioSetting("battery_saver", "Battery Save",
628
                        RadioSettingValueBoolean(_settings.battery_saver))
629
        advanced.append(rs)
630

    
631
        rs = RadioSetting("vox", "VOX Sensitivity",
632
                          RadioSettingValueList(
633
                          VOX_LIST, VOX_LIST[_settings.vox]))
634
        basic.append(rs)
635

    
636
        rs = RadioSetting("auto_bl", "Auto Backlight Timeout",
637
                          RadioSettingValueList(
638
                              AUTOBL_LIST, AUTOBL_LIST[_settings.auto_bl]))
639
        advanced.append(rs)
640

    
641
        rs = RadioSetting("tot", "TX Timeout Timer",
642
                          RadioSettingValueList(
643
                              TOT_LIST, TOT_LIST[_settings.tot]))
644
        basic.append(rs)
645

    
646
        rs = RadioSetting("beep", "Beep",
647
                          RadioSettingValueBoolean(_settings.beep))
648
        basic.append(rs)
649

    
650
        rs = RadioSetting("voice", "Voice",
651
                          RadioSettingValueBoolean(_settings.voice))
652
        advanced.append(rs)
653

    
654
        rs = RadioSetting("language", "Language",
655
                          RadioSettingValueList(
656
                          LANGUAGE_LIST, LANGUAGE_LIST[_settings.language]))
657
        advanced.append(rs)
658

    
659
        rs = RadioSetting("mdfa", "Display Mode (A)",
660
                            RadioSettingValueList(
661
                            MODE_LIST, MODE_LIST[_settings.mdfa]))
662
        basic.append(rs)
663

    
664
        rs = RadioSetting("mdfb", "Display Mode (B)",
665
                            RadioSettingValueList(
666
                            MODE_LIST, MODE_LIST[_settings.mdfb]))
667
        basic.append(rs)
668

    
669
        rs = RadioSetting("scan_mode", "Scan Mode",
670
                            RadioSettingValueList(
671
                            SCAN_MODE_LIST, SCAN_MODE_LIST[_settings.scan_mode]))
672
        basic.append(rs)
673

    
674
        rs = RadioSetting("bcl", "Busy Channel Lockout",
675
                          RadioSettingValueBoolean(_settings.bcl))
676
        advanced.append(rs)
677

    
678
        rs = RadioSetting("autolk", "Automatic Key Lock",
679
                            RadioSettingValueBoolean(_settings.autolk))
680
        advanced.append(rs)
681

    
682
        rs = RadioSetting("almod", "Alarm Mode",
683
                          RadioSettingValueList(
684
                            ALMOD_LIST, ALMOD_LIST[_settings.almod]))
685
        advanced.append(rs)
686

    
687
        rs = RadioSetting("alsnd", "Alarm Sound",
688
                          RadioSettingValueBoolean(_settings.alsnd))
689
        advanced.append(rs)
690

    
691
        rs = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)",
692
                          RadioSettingValueBoolean(_settings.ste))
693
        advanced.append(rs)
694

    
695
        rs = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)",
696
                          RadioSettingValueList(
697
                              RPSTE_LIST, RPSTE_LIST[_settings.rpste]))
698
        advanced.append(rs)
699

    
700
        rs = RadioSetting("rptrl", "STE Repeater Delay",
701
                          RadioSettingValueList(
702
                              STEDELAY_LIST, STEDELAY_LIST[_settings.rptrl]))
703
        advanced.append(rs)
704

    
705
        rs = RadioSetting("fmradio", "Disable Broadcast FM Radio",
706
                          RadioSettingValueBoolean(_settings.fmradio))
707
        advanced.append(rs)
708

    
709
        rs = RadioSetting("keylock", "Keypad Lock",
710
                            RadioSettingValueBoolean(_settings.keylock))
711
        advanced.append(rs)
712

    
713
        rs = RadioSetting("voxdelay", "VOX Delay",
714
                            RadioSettingValueList(
715
                               VOX_DELAY_LIST,
716
                               VOX_DELAY_LIST[_settings.voxdelay]))
717
        advanced.append(rs)
718

    
719
        rs = RadioSetting("menu_timeout", "Menu Timeout",
720
                            RadioSettingValueList(
721
                               MENU_TIMEOUT_LIST,
722
                               MENU_TIMEOUT_LIST[_settings.menu_timeout]))
723
        advanced.append(rs)
724

    
725
        rs = RadioSetting("micgain", "Mic Gain",
726
                            RadioSettingValueList(
727
                               MICGAIN_LIST,
728
                               MICGAIN_LIST[_settings.micgain]))
729
        advanced.append(rs)
730

    
731
        for entry in KEY_FUNCTIONS:
732
            if entry[1] == _keymaps.sidekey:
733
                rs = RadioSetting("keymaps.sidekey", "Side Key Short Press",
734
                                    RadioSettingValueMap(KEY_FUNCTIONS, _keymaps.sidekey))
735
                keymaps.append(rs)
736

    
737

    
738
        for entry in KEY_FUNCTIONS:
739
            if entry[1] == _keymaps.sidekeyl:
740
                rs = RadioSetting("keymaps.sidekeyl", "Side Key Long Press",
741
                                RadioSettingValueMap(KEY_FUNCTIONS, _keymaps.sidekeyl))
742
                keymaps.append(rs)
743

    
744
        rs = RadioSetting("workmodea", "Work Mode (A)",
745
                            RadioSettingValueList(
746
                                WORKMODE_LIST,
747
                                WORKMODE_LIST[_settings.workmodea]))
748
        workmode.append(rs)
749

    
750
        rs = RadioSetting("workmodeb", "Work Mode (B)",
751
                            RadioSettingValueList(
752
                                WORKMODE_LIST,
753
                                WORKMODE_LIST[_settings.workmodeb]))
754
        workmode.append(rs)
755

    
756
        def convert_bytes_to_freq(bytes):
757
            real_freq = 0
758
            for byte in bytes:
759
                real_freq = (real_freq * 10) + byte
760
            return chirp_common.format_freq(real_freq * 10)
761

    
762
        def my_validate(value):
763
            value = chirp_common.parse_freq(value)
764
            if 17400000 <= value and value < 40000000:
765
                msg = ("Can't be between 174.00000-400.00000")
766
                raise InvalidValueError(msg)
767
            return chirp_common.format_freq(value)
768

    
769
        def apply_freq(setting, obj):
770
            value = chirp_common.parse_freq(str(setting.value)) / 10
771
            obj.band = value >= 40000000
772
            for i in range(7, -1, -1):
773
                obj.freq[i] = value % 10
774
                value /= 10
775

    
776
        val1a = RadioSettingValueString(0, 10,
777
                                        convert_bytes_to_freq(_vfoa.freq))
778
        val1a.set_validate_callback(my_validate)
779
        rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a)
780
        rs.set_apply_callback(apply_freq, _vfoa)
781
        workmode.append(rs)
782

    
783
        val1b = RadioSettingValueString(0, 10,
784
                                        convert_bytes_to_freq(_vfob.freq))
785
        val1b.set_validate_callback(my_validate)
786
        rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b)
787
        rs.set_apply_callback(apply_freq, _vfob)
788
        workmode.append(rs)
789

    
790
        rs = RadioSetting("vfoa.sftd", "VFO A Shift",
791
                            RadioSettingValueList(
792
                                SHIFTD_LIST, SHIFTD_LIST[_vfoa.sftd]))
793
        workmode.append(rs)
794

    
795
        rs = RadioSetting("vfob.sftd", "VFO B Shift",
796
                            RadioSettingValueList(
797
                                SHIFTD_LIST, SHIFTD_LIST[_vfob.sftd]))
798
        workmode.append(rs)
799

    
800
        def convert_bytes_to_offset(bytes):
801
            real_offset = 0
802
            for byte in bytes:
803
                real_offset = (real_offset * 10) + byte
804
            return chirp_common.format_freq(real_offset * 100)
805

    
806
        def apply_offset(setting, obj):
807
            value = chirp_common.parse_freq(str(setting.value)) / 100
808
            for i in range(5, -1, -1):
809
                obj.offset[i] = value % 10
810
                value /= 10
811

    
812
        val1a = RadioSettingValueString(
813
            0, 10, convert_bytes_to_offset(_vfoa.offset))
814
        rs = RadioSetting("vfoa.offset",
815
                            "VFO A Offset (0.0-999.999)", val1a)
816
        rs.set_apply_callback(apply_offset, _vfoa)
817
        workmode.append(rs)
818

    
819
        val1b = RadioSettingValueString(
820
            0, 10, convert_bytes_to_offset(_vfob.offset))
821
        rs = RadioSetting("vfob.offset",
822
                            "VFO B Offset (0.0-999.999)", val1b)
823
        rs.set_apply_callback(apply_offset, _vfob)
824
        workmode.append(rs)
825

    
826
        rs = RadioSetting("vfoa.txpower", "VFO A Power",
827
                            RadioSettingValueList(
828
                                TXPOWER_LIST,
829
                                TXPOWER_LIST[_vfoa.txpower]))
830
        workmode.append(rs)
831

    
832
        rs = RadioSetting("vfob.txpower", "VFO B Power",
833
                            RadioSettingValueList(
834
                                TXPOWER_LIST,
835
                                TXPOWER_LIST[_vfob.txpower]))
836
        workmode.append(rs)
837

    
838
        rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth",
839
                            RadioSettingValueList(
840
                                BANDWIDTH_LIST,
841
                                BANDWIDTH_LIST[_vfoa.widenarr]))
842
        workmode.append(rs)
843

    
844
        rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
845
                            RadioSettingValueList(
846
                                BANDWIDTH_LIST,
847
                                BANDWIDTH_LIST[_vfob.widenarr]))
848
        workmode.append(rs)
849

    
850
        rs = RadioSetting("vfoa.scode", "VFO A PTT-ID",
851
                            RadioSettingValueList(
852
                                PTTIDCODE_LIST, PTTIDCODE_LIST[_vfoa.scode]))
853
        workmode.append(rs)
854

    
855
        rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
856
                            RadioSettingValueList(
857
                                PTTIDCODE_LIST, PTTIDCODE_LIST[_vfob.scode]))
858
        workmode.append(rs)
859

    
860
        rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
861
                            RadioSettingValueList(
862
                                STEP_LIST, STEP_LIST[_vfoa.step]))
863
        workmode.append(rs)
864
        rs = RadioSetting("vfob.step", "VFO B Tuning Step",
865
                            RadioSettingValueList(
866
                                STEP_LIST, STEP_LIST[_vfob.step]))
867
        workmode.append(rs)
868

    
869
        dtmfchars = "0123456789 *#ABCD"
870

    
871
        for i in range(0, 15):
872
            _codeobj = self._memobj.pttid[i].code
873
            _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
874
            val = RadioSettingValueString(0, 5, _code, False)
875
            val.set_charset(dtmfchars)
876
            rs = RadioSetting("pttid/%i.code" % i,
877
                              "PTT ID Code %i" % (i + 1), val)
878

    
879
            def apply_code(setting, obj):
880
                code = []
881
                for j in range(0, 5):
882
                    try:
883
                        code.append(dtmfchars.index(str(setting.value)[j]))
884
                    except IndexError:
885
                        code.append(0xFF)
886
                obj.code = code
887
            rs.set_apply_callback(apply_code, self._memobj.pttid[i])
888
            dtmf.append(rs)
889

    
890
        rs = RadioSetting("ani.aniid", "ANI ID",
891
                          RadioSettingValueList(PTTID_LIST,
892
                                                PTTID_LIST[_ani.aniid]))
893
        dtmf.append(rs)
894

    
895
        _codeobj = self._memobj.ani.code
896
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
897
        val = RadioSettingValueString(0, 5, _code, False)
898
        val.set_charset(dtmfchars)
899
        rs = RadioSetting("ani.code", "ANI Code", val)
900

    
901
        def apply_code(setting, obj):
902
            code = []
903
            for j in range(0, 5):
904
                try:
905
                    code.append(dtmfchars.index(str(setting.value)[j]))
906
                except IndexError:
907
                    code.append(0xFF)
908
            obj.code = code
909
        rs.set_apply_callback(apply_code, _ani)
910
        dtmf.append(rs)
911

    
912
        rs = RadioSetting("dtmfst", "DTMF Sidetone",
913
                          RadioSettingValueList(DTMFST_LIST,
914
                                                DTMFST_LIST[_settings.dtmfst]))
915
        dtmf.append(rs)
916

    
917
        if _ani.dtmfon > 0xC3:
918
            val = 0x00
919
        else:
920
            val = _ani.dtmfon
921
        rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
922
                          RadioSettingValueList(DTMFSPEED_LIST,
923
                                                DTMFSPEED_LIST[val]))
924
        dtmf.append(rs)
925

    
926
        if _ani.dtmfoff > 0xC3:
927
            val = 0x00
928
        else:
929
            val = _ani.dtmfoff
930
        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
931
                          RadioSettingValueList(DTMFSPEED_LIST,
932
                                                DTMFSPEED_LIST[val]))
933
        dtmf.append(rs)
934

    
935
        rs = RadioSetting("pttlt", "PTT ID Delay",
936
                          RadioSettingValueList(
937
                          PTTLT_LIST, PTTLT_LIST[_settings.pttlt]))
938
        dtmf.append(rs)
939

    
940
        return group
941

    
942
    def get_settings(self):
943
        try:
944
            return self._get_settings()
945
        except:
946
            import traceback
947
            LOG.error("Failed to parse settings: %s", traceback.format_exc())
948
            return None
949

    
950
    def set_settings(self, settings):
951
        _settings = self._memobj.settings
952
        for element in settings:
953
            if not isinstance(element, RadioSetting):
954
                    self.set_settings(element)
955
                    continue
956
            else:
957
                try:
958
                    name = element.get_name()
959
                    if "." in name:
960
                        bits = name.split(".")
961
                        obj = self._memobj
962
                        for bit in bits[:-1]:
963
                            if "/" in bit:
964
                                bit, index = bit.split("/", 1)
965
                                index = int(index)
966
                                obj = getattr(obj, bit)[index]
967
                            else:
968
                                obj = getattr(obj, bit)
969
                        setting = bits[-1]
970
                    else:
971
                        obj = _settings
972
                        setting = element.get_name()
973

    
974
                    if element.has_apply_callback():
975
                        LOG.debug("Using apply callback")
976
                        element.run_apply_callback()
977
                    elif element.value.get_mutable():
978
                        LOG.debug("Setting %s = %s" % (setting, element.value))
979
                        setattr(obj, setting, element.value)
980
                except Exception, e:
981
                    LOG.debug(element.get_name())
982
                    raise
983

    
984

    
985
    @classmethod
986
    def match_model(cls, filedata, filename):
987
        if len(filedata) in [MEM_SIZE]:
988
            return True
989

    
990
        return False
(5-5/10)