Project

General

Profile

New Model #7997 » senhaix_8800.py

rev 3 - Jiaxun Yang, 11/07/2020 07:16 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
ACK_RETRY = 10
34
BLOCK_SIZE_RX = 64
35
BLOCK_SIZE_TX = 32
36
EEPROM_TX_SKIP = [(0x820, 0xc00), (0x1400, 0x1a00)]
37

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

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

    
51
    return data
52

    
53

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

    
61

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

    
69
    return frame
70

    
71

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

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

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

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

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

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

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

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

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

    
125
    return data
126

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

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

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

    
138

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

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

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

    
157
    SHX8800_exit(radio)
158

    
159
    return memmap.MemoryMap(data)
160

    
161

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

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

    
173
    addr = 0x0
174
    while addr < MEM_SIZE:
175
        _write_block(radio, addr, BLOCK_SIZE_TX,
176
                    radio._mmap[addr:addr+BLOCK_SIZE_TX])
177
        # UI Update
178
        status.cur = addr
179
        radio.status_fn(status)
180
        addr += BLOCK_SIZE_TX
181
        # Skip specified range
182
        for range in EEPROM_TX_SKIP:
183
            if addr == range[0]:
184
                addr = range[1]
185

    
186
    SHX8800_exit(radio)
187

    
188

    
189
SHX8800_MEM_FORMAT = """
190
struct {
191
  lbcd rxfreq[4];
192
  lbcd txfreq[4];
193
  ul16 rxtone;
194
  ul16 txtone;
195
  u8 scode;
196
  u8 pttid;
197
  u8 power_lvl;
198
  u8 spare1:1,
199
     narrow:1,
200
     spare0:2,
201
     bcl:1,
202
     scan:1,
203
     allow_tx:1,
204
     fhss:1;
205
} memory[128];
206

    
207
#seekto 0xc00;
208
struct {
209
  char name[16];
210
} names[128];
211

    
212
#seekto 0x1a00;
213
struct {
214
  u8 squelch;
215
  u8 battery_saver;
216
  u8 vox;
217
  u8 auto_bl;
218
  u8 tdr;
219
  u8 tot;
220
  u8 beep;
221
  u8 voice;
222
  u8 language;
223
  u8 dtmfst;
224
  u8 scan_mode;
225
  u8 pttid;
226
  u8 pttlt;
227
  u8 mdfa;
228
  u8 mdfb;
229
  u8 bcl;
230

    
231
  u8 autolk;
232
  u8 almod;
233
  u8 alsnd;
234
  u8 tx_under_tdr_start;
235
  u8 ste;
236
  u8 rpste;
237
  u8 rptrl;
238
  u8 roger;
239
  u8 unknown;
240
  u8 fmradio;
241
  u8 workmodeb:4,
242
     workmodea:4;
243
  u8 keylock;
244
  u8 unknown1[4];
245

    
246
  u8 voxdelay;
247
  u8 menu_timeout;
248
  u8 micgain;
249
} settings;
250

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

    
272
#seekto 0x1a60;
273
struct {
274
  u8 freq[8];
275
  ul16 rxtone;
276
  ul16 txtone;
277
  u8 unknown[2];
278
  u8 unused2:2,
279
     sftd:2,
280
     scode:4;
281
  u8 unknown1;
282
  u8 txpower;
283
  u8 widenarr:1,
284
     unknown2:4
285
     fhss:1;
286
  u8 band;
287
  u8 unknown3:5,
288
     step:3;
289
  u8 unknown4;
290
  u8 offset[6];
291
} vfob;
292

    
293
#seekto 0x1a80;
294
struct {
295
    u8 sidekey;
296
    u8 sidekeyl;
297
} keymaps;
298

    
299
#seekto 0x1b00;
300
struct {
301
  u8 code[5];
302
  u8 unused[11];
303
} pttid[15];
304

    
305
struct {
306
  u8 code[5];
307
  u8 group_code;
308
  u8 aniid;
309
  u8 dtmfon;
310
  u8 dtmfoff;
311
} ani;
312

    
313
"""
314

    
315
SHX8800_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
316
                     chirp_common.PowerLevel("Low",  watts=1.00)]
317

    
318
SHX8800_DTCS = sorted(chirp_common.DTCS_CODES + [645])
319

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

    
345

    
346
@directory.register
347
class SenHaiX8800Radio(chirp_common.CloneModeRadio):
348
    """SenHaiX 8800"""
349
    VENDOR = "SenHaiX"
350
    MODEL = "8800"
351
    BAUD_RATE = 9600
352

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

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

    
387
        rf.valid_bands = [(100000000, 176000000), (400000000, 521000000)]
388
        rf.memory_bounds = (0, 127)
389

    
390
        return rf
391

    
392
    def sync_in(self):
393
        self._mmap = do_download(self)
394
        self.process_mmap()
395

    
396
    def sync_out(self):
397
        do_upload(self)
398

    
399
    def process_mmap(self):
400
        self._memobj = bitwise.parse(SHX8800_MEM_FORMAT, self._mmap)
401

    
402
    def _is_txinh(self, _mem):
403
        return _mem.allow_tx == False
404

    
405
    def _get_mem(self, number):
406
        return self._memobj.memory[number]
407

    
408
    def _get_nam(self, number):
409
        return self._memobj.names[number]
410

    
411
    def get_raw_memory(self, number):
412
        return repr(self._memobj.memory[number])
413

    
414
    def get_memory(self, number):
415
        _mem = self._get_mem(number)
416
        _nam = self._get_nam(number)
417

    
418
        mem = chirp_common.Memory()
419
        mem.number = number
420

    
421
        if _mem.get_raw()[0] == "\xff":
422
            mem.empty = True
423
            return mem
424

    
425
        mem.freq = int(_mem.rxfreq) * 10
426

    
427
        if self._is_txinh(_mem):
428
            mem.duplex = "off"
429
            mem.offset = 0
430
        elif int(_mem.rxfreq) == int(_mem.txfreq):
431
            mem.duplex = ""
432
            mem.offset = 0
433
        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
434
            mem.duplex = "split"
435
            mem.offset = int(_mem.txfreq) * 10
436
        else:
437
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
438
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
439

    
440
        for char in _nam.name:
441
            if str(char) == "\xFF":
442
                char = " "
443
            mem.name += str(char)
444
        mem.name = mem.name.rstrip()
445

    
446
        dtcs_pol = ["N", "N"]
447

    
448
        if _mem.txtone in [0, 0xFFFF]:
449
            txmode = ""
450
        elif _mem.txtone >= 0x0258:
451
            txmode = "Tone"
452
            mem.rtone = int(_mem.txtone) / 10.0
453
        elif _mem.txtone <= 0x0258:
454
            txmode = "DTCS"
455
            if _mem.txtone > 0x69:
456
                index = _mem.txtone - 0x6A
457
                dtcs_pol[0] = "R"
458
            else:
459
                index = _mem.txtone - 1
460
            mem.dtcs = SHX8800_DTCS[index]
461
        else:
462
            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
463

    
464
        if _mem.rxtone in [0, 0xFFFF]:
465
            rxmode = ""
466
        elif _mem.rxtone >= 0x0258:
467
            rxmode = "Tone"
468
            mem.ctone = int(_mem.rxtone) / 10.0
469
        elif _mem.rxtone <= 0x0258:
470
            rxmode = "DTCS"
471
            if _mem.rxtone >= 0x6A:
472
                index = _mem.rxtone - 0x6A
473
                dtcs_pol[1] = "R"
474
            else:
475
                index = _mem.rxtone - 1
476
            mem.rx_dtcs = SHX8800_DTCS[index]
477
        else:
478
            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
479

    
480
        if txmode == "Tone" and not rxmode:
481
            mem.tmode = "Tone"
482
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
483
            mem.tmode = "TSQL"
484
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
485
            mem.tmode = "DTCS"
486
        elif rxmode or txmode:
487
            mem.tmode = "Cross"
488
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
489

    
490
        mem.dtcs_polarity = "".join(dtcs_pol)
491

    
492
        if not _mem.scan:
493
            mem.skip = "S"
494

    
495
        mem.power = SHX8800_POWER_LEVELS[_mem.power_lvl]
496

    
497
        mem.mode = _mem.narrow and "NFM" or "FM"
498

    
499
        mem.extra = RadioSettingGroup("Extra", "extra")
500

    
501
        rs = RadioSetting("bcl", "bcl",
502
                          RadioSettingValueBoolean(_mem.bcl))
503
        mem.extra.append(rs)
504

    
505
        rs = RadioSetting("pttid", "PTT ID",
506
                          RadioSettingValueList(PTTID_LIST,
507
                                                PTTID_LIST[_mem.pttid]))
508
        mem.extra.append(rs)
509

    
510
        rs = RadioSetting("scode", "PTT ID Code",
511
                          RadioSettingValueList(PTTIDCODE_LIST,
512
                                                PTTIDCODE_LIST[_mem.scode]))
513
        mem.extra.append(rs)
514

    
515
        return mem
516

    
517
    def _set_mem(self, number):
518
        return self._memobj.memory[number]
519

    
520
    def _set_nam(self, number):
521
        return self._memobj.names[number]
522

    
523
    def set_memory(self, mem):
524
        _mem = self._get_mem(mem.number)
525
        _nam = self._get_nam(mem.number)
526

    
527
        if mem.empty:
528
            _mem.set_raw("\xff" * 16)
529
            _nam.set_raw("\xff" * 16)
530
            return
531

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

    
544
        _mem.set_raw("\x00" * 16)
545

    
546
        _mem.rxfreq = mem.freq / 10
547

    
548
        _mem.allow_tx = True
549
        if mem.duplex == "off":
550
            _mem.allow_tx = False
551
            _mem.txfreq = mem.offset / 10
552
        elif mem.duplex == "split":
553
            _mem.txfreq = mem.offset / 10
554
        elif mem.duplex == "+":
555
            _mem.txfreq = (mem.freq + mem.offset) / 10
556
        elif mem.duplex == "-":
557
            _mem.txfreq = (mem.freq - mem.offset) / 10
558
        else:
559
            _mem.txfreq = mem.freq / 10
560

    
561
        _namelength = self.get_features().valid_name_length
562
        for i in range(_namelength):
563
            try:
564
                _nam.name[i] = mem.name[i]
565
            except IndexError:
566
                _nam.name[i] = "\xFF"
567

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

    
597
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
598
            _mem.txtone += 0x69
599
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
600
            _mem.rxtone += 0x69
601

    
602
        _mem.scan = mem.skip != "S"
603
        _mem.narrow = mem.mode == "NFM"
604
        _mem.power_lvl = SHX8800_POWER_LEVELS.index(mem.power)
605

    
606
        if not was_empty:
607
            # restoring old extra-settings
608
            _mem.bcl.set_value(prev_bcl)
609
            _mem.scode.set_value(prev_scode)
610
            _mem.pttid.set_value(prev_pttid)
611

    
612
        for setting in mem.extra:
613
            setattr(_mem, setting.get_name(), setting.value)
614

    
615
    def _get_settings(self):
616
        _ani = self._memobj.ani
617
        _settings = self._memobj.settings
618
        _vfoa = self._memobj.vfoa
619
        _vfob = self._memobj.vfob
620
        _keymaps = self._memobj.keymaps
621

    
622
        basic = RadioSettingGroup("basic", "Basic Settings")
623
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
624
        workmode = RadioSettingGroup("workmode", "Work Mode Settings")
625
        keymaps = RadioSettingGroup("keymaps", "KeyMaps")
626
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
627

    
628
        group = RadioSettings(basic, advanced, workmode, keymaps, dtmf)
629

    
630
        rs = RadioSetting("squelch", "Carrier Squelch Level",
631
                          RadioSettingValueInteger(0, 5, _settings.squelch))
632
        basic.append(rs)
633

    
634
        rs = RadioSetting("battery_saver", "Battery Save",
635
                        RadioSettingValueBoolean(_settings.battery_saver))
636
        advanced.append(rs)
637

    
638
        rs = RadioSetting("vox", "VOX Sensitivity",
639
                          RadioSettingValueList(
640
                          VOX_LIST, VOX_LIST[_settings.vox]))
641
        basic.append(rs)
642

    
643
        rs = RadioSetting("auto_bl", "Auto Backlight Timeout",
644
                          RadioSettingValueList(
645
                              AUTOBL_LIST, AUTOBL_LIST[_settings.auto_bl]))
646
        advanced.append(rs)
647

    
648
        rs = RadioSetting("tot", "TX Timeout Timer",
649
                          RadioSettingValueList(
650
                              TOT_LIST, TOT_LIST[_settings.tot]))
651
        basic.append(rs)
652

    
653
        rs = RadioSetting("beep", "Beep",
654
                          RadioSettingValueBoolean(_settings.beep))
655
        basic.append(rs)
656

    
657
        rs = RadioSetting("voice", "Voice",
658
                          RadioSettingValueBoolean(_settings.voice))
659
        advanced.append(rs)
660

    
661
        rs = RadioSetting("language", "Language",
662
                          RadioSettingValueList(
663
                          LANGUAGE_LIST, LANGUAGE_LIST[_settings.language]))
664
        advanced.append(rs)
665

    
666
        rs = RadioSetting("mdfa", "Display Mode (A)",
667
                            RadioSettingValueList(
668
                            MODE_LIST, MODE_LIST[_settings.mdfa]))
669
        basic.append(rs)
670

    
671
        rs = RadioSetting("mdfb", "Display Mode (B)",
672
                            RadioSettingValueList(
673
                            MODE_LIST, MODE_LIST[_settings.mdfb]))
674
        basic.append(rs)
675

    
676
        rs = RadioSetting("scan_mode", "Scan Mode",
677
                            RadioSettingValueList(
678
                            SCAN_MODE_LIST, SCAN_MODE_LIST[_settings.scan_mode]))
679
        basic.append(rs)
680

    
681
        rs = RadioSetting("bcl", "Busy Channel Lockout",
682
                          RadioSettingValueBoolean(_settings.bcl))
683
        advanced.append(rs)
684

    
685
        rs = RadioSetting("autolk", "Automatic Key Lock",
686
                            RadioSettingValueBoolean(_settings.autolk))
687
        advanced.append(rs)
688

    
689
        rs = RadioSetting("almod", "Alarm Mode",
690
                          RadioSettingValueList(
691
                            ALMOD_LIST, ALMOD_LIST[_settings.almod]))
692
        advanced.append(rs)
693

    
694
        rs = RadioSetting("alsnd", "Alarm Sound",
695
                          RadioSettingValueBoolean(_settings.alsnd))
696
        advanced.append(rs)
697

    
698
        rs = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)",
699
                          RadioSettingValueBoolean(_settings.ste))
700
        advanced.append(rs)
701

    
702
        rs = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)",
703
                          RadioSettingValueList(
704
                              RPSTE_LIST, RPSTE_LIST[_settings.rpste]))
705
        advanced.append(rs)
706

    
707
        rs = RadioSetting("rptrl", "STE Repeater Delay",
708
                          RadioSettingValueList(
709
                              STEDELAY_LIST, STEDELAY_LIST[_settings.rptrl]))
710
        advanced.append(rs)
711

    
712
        rs = RadioSetting("fmradio", "Disable Broadcast FM Radio",
713
                          RadioSettingValueBoolean(_settings.fmradio))
714
        advanced.append(rs)
715

    
716
        rs = RadioSetting("keylock", "Keypad Lock",
717
                            RadioSettingValueBoolean(_settings.keylock))
718
        advanced.append(rs)
719

    
720
        rs = RadioSetting("voxdelay", "VOX Delay",
721
                            RadioSettingValueList(
722
                               VOX_DELAY_LIST,
723
                               VOX_DELAY_LIST[_settings.voxdelay]))
724
        advanced.append(rs)
725

    
726
        rs = RadioSetting("menu_timeout", "Menu Timeout",
727
                            RadioSettingValueList(
728
                               MENU_TIMEOUT_LIST,
729
                               MENU_TIMEOUT_LIST[_settings.menu_timeout]))
730
        advanced.append(rs)
731

    
732
        rs = RadioSetting("micgain", "Mic Gain",
733
                            RadioSettingValueList(
734
                               MICGAIN_LIST,
735
                               MICGAIN_LIST[_settings.micgain]))
736
        advanced.append(rs)
737

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

    
744

    
745
        for entry in KEY_FUNCTIONS:
746
            if entry[1] == _keymaps.sidekeyl:
747
                rs = RadioSetting("keymaps.sidekeyl", "Side Key Long Press",
748
                                RadioSettingValueMap(KEY_FUNCTIONS, _keymaps.sidekeyl))
749
                keymaps.append(rs)
750

    
751
        rs = RadioSetting("workmodea", "Work Mode (A)",
752
                            RadioSettingValueList(
753
                                WORKMODE_LIST,
754
                                WORKMODE_LIST[_settings.workmodea]))
755
        workmode.append(rs)
756

    
757
        rs = RadioSetting("workmodeb", "Work Mode (B)",
758
                            RadioSettingValueList(
759
                                WORKMODE_LIST,
760
                                WORKMODE_LIST[_settings.workmodeb]))
761
        workmode.append(rs)
762

    
763
        def convert_bytes_to_freq(bytes):
764
            real_freq = 0
765
            for byte in bytes:
766
                real_freq = (real_freq * 10) + byte
767
            return chirp_common.format_freq(real_freq * 10)
768

    
769
        def my_validate(value):
770
            value = chirp_common.parse_freq(value)
771
            if 17400000 <= value and value < 40000000:
772
                msg = ("Can't be between 174.00000-400.00000")
773
                raise InvalidValueError(msg)
774
            return chirp_common.format_freq(value)
775

    
776
        def apply_freq(setting, obj):
777
            value = chirp_common.parse_freq(str(setting.value)) / 10
778
            obj.band = value >= 40000000
779
            for i in range(7, -1, -1):
780
                obj.freq[i] = value % 10
781
                value /= 10
782

    
783
        val1a = RadioSettingValueString(0, 10,
784
                                        convert_bytes_to_freq(_vfoa.freq))
785
        val1a.set_validate_callback(my_validate)
786
        rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a)
787
        rs.set_apply_callback(apply_freq, _vfoa)
788
        workmode.append(rs)
789

    
790
        val1b = RadioSettingValueString(0, 10,
791
                                        convert_bytes_to_freq(_vfob.freq))
792
        val1b.set_validate_callback(my_validate)
793
        rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b)
794
        rs.set_apply_callback(apply_freq, _vfob)
795
        workmode.append(rs)
796

    
797
        rs = RadioSetting("vfoa.sftd", "VFO A Shift",
798
                            RadioSettingValueList(
799
                                SHIFTD_LIST, SHIFTD_LIST[_vfoa.sftd]))
800
        workmode.append(rs)
801

    
802
        rs = RadioSetting("vfob.sftd", "VFO B Shift",
803
                            RadioSettingValueList(
804
                                SHIFTD_LIST, SHIFTD_LIST[_vfob.sftd]))
805
        workmode.append(rs)
806

    
807
        def convert_bytes_to_offset(bytes):
808
            real_offset = 0
809
            for byte in bytes:
810
                real_offset = (real_offset * 10) + byte
811
            return chirp_common.format_freq(real_offset * 100)
812

    
813
        def apply_offset(setting, obj):
814
            value = chirp_common.parse_freq(str(setting.value)) / 100
815
            for i in range(5, -1, -1):
816
                obj.offset[i] = value % 10
817
                value /= 10
818

    
819
        val1a = RadioSettingValueString(
820
            0, 10, convert_bytes_to_offset(_vfoa.offset))
821
        rs = RadioSetting("vfoa.offset",
822
                            "VFO A Offset (0.0-999.999)", val1a)
823
        rs.set_apply_callback(apply_offset, _vfoa)
824
        workmode.append(rs)
825

    
826
        val1b = RadioSettingValueString(
827
            0, 10, convert_bytes_to_offset(_vfob.offset))
828
        rs = RadioSetting("vfob.offset",
829
                            "VFO B Offset (0.0-999.999)", val1b)
830
        rs.set_apply_callback(apply_offset, _vfob)
831
        workmode.append(rs)
832

    
833
        rs = RadioSetting("vfoa.txpower", "VFO A Power",
834
                            RadioSettingValueList(
835
                                TXPOWER_LIST,
836
                                TXPOWER_LIST[_vfoa.txpower]))
837
        workmode.append(rs)
838

    
839
        rs = RadioSetting("vfob.txpower", "VFO B Power",
840
                            RadioSettingValueList(
841
                                TXPOWER_LIST,
842
                                TXPOWER_LIST[_vfob.txpower]))
843
        workmode.append(rs)
844

    
845
        rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth",
846
                            RadioSettingValueList(
847
                                BANDWIDTH_LIST,
848
                                BANDWIDTH_LIST[_vfoa.widenarr]))
849
        workmode.append(rs)
850

    
851
        rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
852
                            RadioSettingValueList(
853
                                BANDWIDTH_LIST,
854
                                BANDWIDTH_LIST[_vfob.widenarr]))
855
        workmode.append(rs)
856

    
857
        rs = RadioSetting("vfoa.scode", "VFO A PTT-ID",
858
                            RadioSettingValueList(
859
                                PTTIDCODE_LIST, PTTIDCODE_LIST[_vfoa.scode]))
860
        workmode.append(rs)
861

    
862
        rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
863
                            RadioSettingValueList(
864
                                PTTIDCODE_LIST, PTTIDCODE_LIST[_vfob.scode]))
865
        workmode.append(rs)
866

    
867
        rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
868
                            RadioSettingValueList(
869
                                STEP_LIST, STEP_LIST[_vfoa.step]))
870
        workmode.append(rs)
871
        rs = RadioSetting("vfob.step", "VFO B Tuning Step",
872
                            RadioSettingValueList(
873
                                STEP_LIST, STEP_LIST[_vfob.step]))
874
        workmode.append(rs)
875

    
876
        dtmfchars = "0123456789 *#ABCD"
877

    
878
        for i in range(0, 15):
879
            _codeobj = self._memobj.pttid[i].code
880
            _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
881
            val = RadioSettingValueString(0, 5, _code, False)
882
            val.set_charset(dtmfchars)
883
            rs = RadioSetting("pttid/%i.code" % i,
884
                              "PTT ID Code %i" % (i + 1), val)
885

    
886
            def apply_code(setting, obj):
887
                code = []
888
                for j in range(0, 5):
889
                    try:
890
                        code.append(dtmfchars.index(str(setting.value)[j]))
891
                    except IndexError:
892
                        code.append(0xFF)
893
                obj.code = code
894
            rs.set_apply_callback(apply_code, self._memobj.pttid[i])
895
            dtmf.append(rs)
896

    
897
        rs = RadioSetting("ani.aniid", "ANI ID",
898
                          RadioSettingValueList(PTTID_LIST,
899
                                                PTTID_LIST[_ani.aniid]))
900
        dtmf.append(rs)
901

    
902
        _codeobj = self._memobj.ani.code
903
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
904
        val = RadioSettingValueString(0, 5, _code, False)
905
        val.set_charset(dtmfchars)
906
        rs = RadioSetting("ani.code", "ANI Code", val)
907

    
908
        def apply_code(setting, obj):
909
            code = []
910
            for j in range(0, 5):
911
                try:
912
                    code.append(dtmfchars.index(str(setting.value)[j]))
913
                except IndexError:
914
                    code.append(0xFF)
915
            obj.code = code
916
        rs.set_apply_callback(apply_code, _ani)
917
        dtmf.append(rs)
918

    
919
        rs = RadioSetting("dtmfst", "DTMF Sidetone",
920
                          RadioSettingValueList(DTMFST_LIST,
921
                                                DTMFST_LIST[_settings.dtmfst]))
922
        dtmf.append(rs)
923

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

    
933
        if _ani.dtmfoff > 0xC3:
934
            val = 0x00
935
        else:
936
            val = _ani.dtmfoff
937
        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
938
                          RadioSettingValueList(DTMFSPEED_LIST,
939
                                                DTMFSPEED_LIST[val]))
940
        dtmf.append(rs)
941

    
942
        rs = RadioSetting("pttlt", "PTT ID Delay",
943
                          RadioSettingValueList(
944
                          PTTLT_LIST, PTTLT_LIST[_settings.pttlt]))
945
        dtmf.append(rs)
946

    
947
        return group
948

    
949
    def get_settings(self):
950
        try:
951
            return self._get_settings()
952
        except:
953
            import traceback
954
            LOG.error("Failed to parse settings: %s", traceback.format_exc())
955
            return None
956

    
957
    def set_settings(self, settings):
958
        _settings = self._memobj.settings
959
        for element in settings:
960
            if not isinstance(element, RadioSetting):
961
                    self.set_settings(element)
962
                    continue
963
            else:
964
                try:
965
                    name = element.get_name()
966
                    if "." in name:
967
                        bits = name.split(".")
968
                        obj = self._memobj
969
                        for bit in bits[:-1]:
970
                            if "/" in bit:
971
                                bit, index = bit.split("/", 1)
972
                                index = int(index)
973
                                obj = getattr(obj, bit)[index]
974
                            else:
975
                                obj = getattr(obj, bit)
976
                        setting = bits[-1]
977
                    else:
978
                        obj = _settings
979
                        setting = element.get_name()
980

    
981
                    if element.has_apply_callback():
982
                        LOG.debug("Using apply callback")
983
                        element.run_apply_callback()
984
                    elif element.value.get_mutable():
985
                        LOG.debug("Setting %s = %s" % (setting, element.value))
986
                        setattr(obj, setting, element.value)
987
                except Exception, e:
988
                    LOG.debug(element.get_name())
989
                    raise
990

    
991

    
992
    @classmethod
993
    def match_model(cls, filedata, filename):
994
        if len(filedata) in [MEM_SIZE]:
995
            return True
996

    
997
        return False
(7-7/10)