Project

General

Profile

New Model #7997 » radioddityGS5B.py

New driver file, revised from ga510.py - Anonymous, 01/18/2023 07:13 PM

 
1
# Version 1.0 for Radioddity GS-5B
2
# Initial radio protocol decode, channels and memory layout
3
# by Dave Liske <dave@micuisine.com>, January 2023
4
#
5
# This radio is sold under the following names on the market: 
6
# Radioddity GS-5B, Senhaix 8800, Signus XTR-5, 
7
# Anysecu AC-580
8
#
9
# Modified from ga510.py provided in Chirp's repository
10
# at https://chirp.danplanet.com/projects/chirp/repository
11
#
12
# As ga510.py contained no copyright, the 2023 Copyright 
13
# of this modified file is hereby attributed to the 
14
# Chirp project and Dan Smith <dsmith@danplanet.com>
15
# 
16
# This program is free software: you can redistribute it and/or modify
17
# it under the terms of the GNU General Public License as published by
18
# the Free Software Foundation, either version 2 of the License, or
19
# (at your option) any later version.
20
#
21
# This program is distributed in the hope that it will be useful,
22
# but WITHOUT ANY WARRANTY; without even the implied warranty of
23
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
# GNU General Public License for more details.
25
#
26
# You should have received a copy of the GNU General Public License
27
# along with this program.
28

    
29
import logging
30
import struct
31

    
32
from chirp import bitwise
33
from chirp import chirp_common
34
from chirp import directory
35
from chirp import errors
36
from chirp import memmap
37
from chirp.settings import RadioSetting, RadioSettingGroup, RadioSettings
38
from chirp.settings import RadioSettingValueBoolean, RadioSettingValueList
39
from chirp.settings import RadioSettingValueInteger, RadioSettingValueString
40

    
41
LOG = logging.getLogger(__name__)
42

    
43
try:
44
    from builtins import bytes
45
    has_future = True
46
except ImportError:
47
    has_future = False
48
    LOG.debug('python-future package is not available; '
49
              '%s requires it' % __name__)
50

    
51
# GA510 also has DTCS code 645
52
DTCS_CODES = list(sorted(chirp_common.DTCS_CODES + [645]))
53

    
54
DTMFCHARS = '0123456789ABCD*#'
55

    
56

    
57
def reset(radio):
58
    radio.pipe.write(b'E')
59

    
60

    
61
def start_program(radio):
62
    reset(radio)
63
    radio.pipe.read(256)
64
    radio.pipe.write(radio._magic)
65
    ack = radio.pipe.read(256)
66
    if not ack.endswith(b'\x06'):
67
        LOG.debug('Ack was %r' % ack)
68
        raise errors.RadioError('Radio did not respond to clone request. Ensure Bluetooth on the radio is Off.')
69

    
70
    radio.pipe.write(b'F')
71

    
72
    ident = radio.pipe.read(8)
73
    LOG.debug('Radio ident string is %r' % ident)
74

    
75
    return ident
76

    
77

    
78
def do_download(radio):
79
    ident = start_program(radio)
80

    
81
    s = chirp_common.Status()
82
    s.msg = 'Downloading'
83
    s.max = 0x1C00
84

    
85
    data = bytes()
86
    for addr in range(0, 0x1C40, 0x40):
87
        cmd = struct.pack('>cHB', b'R', addr, 0x40)
88
        LOG.debug('Reading block at %04x: %r' % (addr, cmd))
89
        radio.pipe.write(cmd)
90

    
91
        block = radio.pipe.read(0x44)
92
        header = block[:4]
93
        rcmd, raddr, rlen = struct.unpack('>BHB', header)
94
        block = block[4:]
95
        if raddr != addr:
96
            raise errors.RadioError('Radio send address %04x, expected %04x' %
97
                                    (raddr, addr))
98
        if rlen != 0x40 or len(block) != 0x40:
99
            raise errors.RadioError('Radio sent %02x (%02x) bytes, '
100
                                    'expected %02x' % (rlen, len(block), 0x40))
101

    
102
        data += block
103

    
104
        s.cur = addr
105
        radio.status_fn(s)
106

    
107
    reset(radio)
108

    
109
    return data
110

    
111

    
112
def do_upload(radio):
113
    ident = start_program(radio)
114

    
115
    s = chirp_common.Status()
116
    s.msg = 'Uploading'
117
    s.max = 0x1C00
118

    
119
    # The factory software downloads 0x40 for the block
120
    # at 0x1C00, but only uploads 0x20 there. Mimic that
121
    # here.
122
    for addr in range(0, 0x1C20, 0x20):
123
        cmd = struct.pack('>cHB', b'W', addr, 0x20)
124
        LOG.debug('Writing block at %04x: %r' % (addr, cmd))
125
        block = radio._mmap[addr:addr + 0x20]
126
        radio.pipe.write(cmd)
127
        radio.pipe.write(block)
128

    
129
        ack = radio.pipe.read(1)
130
        if ack != b'\x06':
131
            raise errors.RadioError('Radio refused block at addr %04x' % addr)
132

    
133
        s.cur = addr
134
        radio.status_fn(s)
135

    
136

    
137
MEM_FORMAT = """
138
struct {
139
  lbcd rxfreq[4];
140
  lbcd txfreq[4];
141
  ul16 rxtone;
142
  ul16 txtone;
143
  u8 signal;
144
  u8 unknown1:6,
145
     pttid:2;
146
  u8 unknown2:6,
147
     power:2;
148
  u8 unknown3_0:1,
149
     narrow:1,
150
     unknown3_1:2,
151
     bcl:1,
152
     scan:1,
153
     unknown3_2:1,
154
     fhss:1;
155
} memories[128];
156

    
157
#seekto 0x0C00;
158
struct {
159
  char name[10];
160
  u8 pad[6];
161
} names[128];
162

    
163
#seekto 0x1A00;
164
struct {
165
  // 0x1A00
166
  u8 squelch;
167
  u8 savemode; // [off, mode1, mode2, mode3]
168
  u8 vox; // off=0
169
  u8 backlight;
170
  u8 tdr; // bool
171
  u8 timeout; // n*15 = seconds
172
  u8 beep; // bool
173
  u8 voice;
174

    
175
  // 0x1A08
176
  u8 language; // [eng, chin]
177
  u8 dtmfst;
178
  u8 scanmode; // [TO, CO, SE]
179
  u8 pttid; // [off, BOT, EOT, Both]
180
  u8 pttdelay; // 0-30
181
  u8 cha_disp; // [ch-name, ch-freq]
182
               // [ch, ch-name]; retevis
183
  u8 chb_disp;
184
  u8 bcl; // bool
185

    
186
  // 0x1A10
187
  u8 autolock; // bool
188
  u8 alarm_mode; // [site, tone, code]
189
  u8 alarmsound; // bool
190
  u8 txundertdr; // [off, bandA, bandB]
191
  u8 tailnoiseclear; // [off, on]
192
  u8 rptnoiseclr; // 10*ms, 0-1000
193
  u8 rptnoisedet;
194
  u8 roger; // bool
195

    
196
  // 0x1A18
197
  u8 unknown1a10;
198
  u8 fmradio; // boolean, inverted
199
  u8 workmode; // [vfo, chan]; 1A30-1A31 related?
200
  u8 kblock; // boolean
201
} settings;
202

    
203
#seekto 0x1A80;
204
struct {
205
  u8 skey1sp; // [off, lamp, sos, fm, noaa, moni, search]
206
  u8 skey1lp; // [off, lamp, sos, fm, noaa, moni, search]
207
  u8 skey2sp; // [off, lamp, sos, fm, noaa, moni, search]
208
  u8 skey2lp; // [off, lamp, sos, fm, noaa, moni, search]
209
} skey;
210

    
211
struct dtmfcode {
212
  u8 code[5];
213
  u8 ffpad[11]; // always 0xFF
214
};
215
#seekto 0x1B00;
216
struct dtmfcode dtmfgroup[15];
217
struct {
218
  u8 code[5];
219
  u8 groupcode; // 0->D, *, #
220
  u8 nothing:6,
221
     releasetosend:1,
222
     presstosend:1;
223
  u8 dtmfspeedon; // 80 + n*10, up to [194]
224
  u8 dtmfspeedoff;
225
} anicode;
226

    
227
//dtmf on -> 90ms
228
//dtmf off-> 120ms
229
//group code *->0
230
//press 0->1
231
//release 1->0
232

    
233
"""
234

    
235

    
236
PTTID = ['Off', 'BOT', 'EOT', 'Both']
237
SIGNAL = [str(i) for i in range(1, 16)]
238

    
239
GMRS_FREQS1 = [462562500, 462587500, 462612500, 462637500, 462662500,
240
               462687500, 462712500]
241
GMRS_FREQS2 = [467562500, 467587500, 467612500, 467637500, 467662500,
242
               467687500, 467712500]
243
GMRS_FREQS3 = [462550000, 462575000, 462600000, 462625000, 462650000,
244
               462675000, 462700000, 462725000]
245
GMRS_FREQS = GMRS_FREQS1 + GMRS_FREQS2 + GMRS_FREQS3
246

    
247
@directory.register
248
class RadioddityGS5BRadio(chirp_common.CloneModeRadio):
249
    VENDOR = "Radioddity"
250
    MODEL = "GS-5B"
251
    BAUD_RATE = 9600
252
    NEEDS_COMPAT_SERIAL = False
253
    POWER_LEVELS = [
254
        chirp_common.PowerLevel('H', watts=5),
255
        chirp_common.PowerLevel('L', watts=1)]
256

    
257
    _magic = b'PROGROMSHXU'
258

    
259
    _gmrs = False
260

    
261
    def sync_in(self):
262
        try:
263
            data = do_download(self)
264
            self._mmap = memmap.MemoryMapBytes(data)
265
        except errors.RadioError:
266
            raise
267
        except Exception as e:
268
            LOG.exception('General failure')
269
            raise errors.RadioError('Failed to download from radio: %s' % e)
270
        self.process_mmap()
271

    
272
    def sync_out(self):
273
        try:
274
            do_upload(self)
275
        except errors.RadioError:
276
            raise
277
        except Exception as e:
278
            LOG.exception('General failure')
279
            raise errors.RadioError('Failed to upload to radio: %s' % e)
280

    
281
    def process_mmap(self):
282
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
283

    
284
    def get_features(self):
285
        rf = chirp_common.RadioFeatures()
286
        rf.memory_bounds = (0, 127)
287
        rf.has_ctone = True
288
        rf.has_cross = True
289
        rf.has_tuning_step = False
290
        rf.has_settings = True
291
        rf.has_bank = False
292
        rf.has_sub_devices = False
293
        rf.has_dtcs_polarity = True
294
        rf.has_rx_dtcs = True
295
        rf.can_odd_split = True
296
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
297
        rf.valid_cross_modes = ['Tone->Tone', 'DTCS->', '->DTCS', 'Tone->DTCS',
298
                                'DTCS->Tone', '->Tone', 'DTCS->DTCS']
299
        rf.valid_modes = ['FM', 'NFM']
300
        rf.valid_tuning_steps = [2.5, 5.0, 6.25, 12.5, 10.0, 15.0, 20.0,
301
                                 25.0, 50.0, 100.0]
302
        rf.valid_dtcs_codes = DTCS_CODES
303
        rf.valid_duplexes = ['', '-', '+', 'split', 'off']
304
        rf.valid_power_levels = self.POWER_LEVELS
305
        rf.valid_name_length = 10
306
        rf.valid_characters = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
307
                               'abcdefghijklmnopqrstuvwxyz'
308
                               '0123456789'
309
                               '!"#$%&\'()~+-,./:;<=>?@[\\]^`{}*| ')
310
        rf.valid_bands = [(136000000, 174000000),
311
                          (400000000, 480000000)]
312
        return rf
313

    
314
    def get_raw_memory(self, num):
315
        return repr(self._memobj.memories[num]) + repr(self._memobj.names[num])
316

    
317
    @staticmethod
318
    def _decode_tone(toneval):
319
        if toneval in (0, 0xFFFF):
320
            LOG.debug('no tone value: %s' % toneval)
321
            return '', None, None
322
        elif toneval < 670:
323
            toneval = toneval - 1
324
            index = toneval % len(DTCS_CODES)
325
            if index != int(toneval):
326
                pol = 'R'
327
                # index -= 1
328
            else:
329
                pol = 'N'
330
            return 'DTCS', DTCS_CODES[index], pol
331
        else:
332
            return 'Tone', toneval / 10.0, 'N'
333

    
334
    @staticmethod
335
    def _encode_tone(mode, val, pol):
336
        if not mode:
337
            return 0x0000
338
        elif mode == 'Tone':
339
            return int(val * 10)
340
        elif mode == 'DTCS':
341
            index = DTCS_CODES.index(val)
342
            if pol == 'R':
343
                index += len(DTCS_CODES)
344
            index += 1
345
            LOG.debug('Encoded dtcs %s/%s to %04x' % (val, pol, index))
346
            return index
347
        else:
348
            raise errors.RadioError('Unsupported tone mode %r' % mode)
349

    
350
    def _get_extra(self, _mem):
351
        group = RadioSettingGroup('extra', 'Extra')
352

    
353
        s = RadioSetting('bcl', 'Busy Channel Lockout',
354
                         RadioSettingValueBoolean(_mem.bcl))
355
        group.append(s)
356

    
357
        s = RadioSetting('fhss', 'FHSS',
358
                         RadioSettingValueBoolean(_mem.fhss))
359
        group.append(s)
360

    
361
        # pttid, signal
362

    
363
        cur = PTTID[int(_mem.pttid)]
364
        s = RadioSetting('pttid', 'PTTID',
365
                         RadioSettingValueList(PTTID, cur))
366
        group.append(s)
367

    
368
        cur = SIGNAL[int(_mem.signal)]
369
        s = RadioSetting('signal', 'Signal',
370
                         RadioSettingValueList(SIGNAL, cur))
371
        group.append(s)
372

    
373
        return group
374

    
375
    def _set_extra(self, _mem, mem):
376
        _mem.bcl = int(mem.extra['bcl'].value)
377
        _mem.fhss = int(mem.extra['fhss'].value)
378
        _mem.pttid = int(mem.extra['pttid'].value)
379
        _mem.signal = int(mem.extra['signal'].value)
380

    
381
    def _is_txinh(self, _mem):
382
        raw_tx = ""
383
        for i in range(0, 4):
384
            raw_tx += _mem.txfreq[i].get_raw()
385
        return raw_tx == "\xFF\xFF\xFF\xFF"
386

    
387
    def _get_mem(self, num):
388
        return self._memobj.memories[num]
389

    
390
    def _get_nam(self, num):
391
        return self._memobj.names[num]
392

    
393
    def get_memory(self, num):
394
        _mem = self._get_mem(num)
395
        _nam = self._get_nam(num)
396
        mem = chirp_common.Memory()
397
        mem.number = num
398
        if int(_mem.rxfreq) == 166666665:
399
            mem.empty = True
400
            return mem
401

    
402
        mem.name = ''.join([str(c) for c in _nam.name
403
                            if ord(str(c)) < 127]).rstrip()
404
        mem.freq = int(_mem.rxfreq) * 10
405
        offset = (int(_mem.txfreq) - int(_mem.rxfreq)) * 10
406
        if self._is_txinh(_mem):
407
            mem.duplex = 'off'
408
            mem.offset = 0
409
        elif offset == 0:
410
            mem.duplex = ''
411
        elif abs(offset) < 100000000:
412
            mem.duplex = offset < 0 and '-' or '+'
413
            mem.offset = abs(offset)
414
        else:
415
            mem.duplex = 'split'
416
            mem.offset = int(_mem.txfreq) * 10
417

    
418
        mem.power = self.POWER_LEVELS[_mem.power]
419
        mem.mode = 'NFM' if _mem.narrow else 'FM'
420
        mem.skip = '' if _mem.scan else 'S'
421

    
422
        LOG.debug('got txtone: %s' % repr(self._decode_tone(_mem.txtone)))
423
        LOG.debug('got rxtone: %s' % repr(self._decode_tone(_mem.rxtone)))
424
        chirp_common.split_tone_decode(mem,
425
                                       self._decode_tone(_mem.txtone),
426
                                       self._decode_tone(_mem.rxtone))
427
        try:
428
            mem.extra = self._get_extra(_mem)
429
        except:
430
            LOG.exception('Failed to get extra for %i' % num)
431
        return mem
432

    
433
    def _set_mem(self, number):
434
        return self._memobj.memories[number]
435

    
436
    def _set_nam(self, number):
437
        return self._memobj.names[number]
438

    
439
    def set_memory(self, mem):
440
        _mem = self._set_mem(mem.number)
441
        _nam = self._set_nam(mem.number)
442

    
443
        if mem.empty:
444
            _mem.set_raw(b'\xff' * 16)
445
            _nam.set_raw(b'\xff' * 16)
446
            return
447

    
448
        if int(_mem.rxfreq) == 166666665:
449
            LOG.debug('Initializing new memory %i' % mem.number)
450
            _mem.set_raw(b'\x00' * 16)
451

    
452
        if self._gmrs:
453
            if mem.freq in GMRS_FREQS:
454
                if mem.freq in GMRS_FREQS1:
455
                    mem.duplex = ''
456
                    mem.offset = 0
457
                if mem.freq in GMRS_FREQS2:
458
                    mem.duplex = ''
459
                    mem.offset = 0
460
                    mem.mode = "NFM"
461
                    mem.power = self.POWER_LEVELS[1]
462
                if mem.freq in GMRS_FREQS3:
463
                    if mem.duplex == '+':
464
                        mem.offset = 5000000
465
                    else:
466
                        mem.duplex = ''
467
                        mem.offset = 0
468
            else:
469
                mem.duplex = 'off'
470
                mem.offset = 0
471

    
472
        _nam.name = mem.name.ljust(10)
473

    
474
        _mem.rxfreq = mem.freq // 10
475
        if mem.duplex == '':
476
            _mem.txfreq = mem.freq // 10
477
        elif mem.duplex == 'split':
478
            _mem.txfreq = mem.offset // 10
479
        elif mem.duplex == 'off':
480
            for i in range(0, 4):
481
                _mem.txfreq[i].set_raw(b'\xFF')
482
        elif mem.duplex == '-':
483
            _mem.txfreq = (mem.freq - mem.offset) // 10
484
        elif mem.duplex == '+':
485
            _mem.txfreq = (mem.freq + mem.offset) // 10
486
        else:
487
            raise errors.RadioError('Unsupported duplex mode %r' % mem.duplex)
488

    
489
        txtone, rxtone = chirp_common.split_tone_encode(mem)
490
        LOG.debug('tx tone is %s' % repr(txtone))
491
        LOG.debug('rx tone is %s' % repr(rxtone))
492
        _mem.txtone = self._encode_tone(*txtone)
493
        _mem.rxtone = self._encode_tone(*rxtone)
494

    
495
        try:
496
            _mem.power = self.POWER_LEVELS.index(mem.power)
497
        except ValueError:
498
            _mem.power = 0
499
        _mem.narrow = mem.mode == 'NFM'
500
        _mem.scan = mem.skip != 'S'
501
        if mem.extra:
502
            self._set_extra(_mem, mem)
503

    
504
    def get_settings(self):
505
        _set = self._memobj.settings
506

    
507
        basic = RadioSettingGroup('basic', 'Basic')
508
        adv = RadioSettingGroup('advanced', 'Advanced')
509
        dtmf = RadioSettingGroup('dtmf', 'DTMF')
510

    
511
        radioddity_settings = {
512
            'savemode': ['Off', 'Mode 1', 'Mode 2', 'Mode 3'],
513
            'cha_disp': ['CH+Name', 'CH+Freq'],
514
            'chb_disp': ['CH+Name', 'CH+Freq'],
515
            'txundertdr': ['Off', 'Band A', 'Band B'],
516
            'rptnoiseclr': ['Off'] + ['%i' % i for i in range(100, 1001, 100)],
517
            'rptnoisedet': ['Off'] + ['%i' % i for i in range(100, 1001, 100)],
518
        }
519

    
520
        retevis_settings = {
521
            'savemode': ['Off', 'On'],
522
            'cha_disp': ['CH', 'CH+Name'],
523
            'chb_disp': ['CH', 'CH+Name'],
524
        }
525

    
526
        choice_settings = {
527
            'vox': ['Off'] + ['%i' % i for i in range(1, 11)],
528
            'backlight': ['Off'] + ['%i' % i for i in range(1, 11)],
529
            'timeout': ['Off'] + ['%i' % i for i in range(15, 615, 15)],
530
            'language': ['English', 'Chinese'],
531
            'dtmfst': ['OFF', 'KB Side Tone', 'ANI Side Tone',
532
                       'KB ST+ANI ST', 'Both'],
533
            'scanmode': ['TO', 'CO', 'SE'],
534
            'pttid': ['Off', 'BOT', 'EOT', 'Both'],
535
            'alarm_mode': ['Site', 'Tone', 'Code'],
536
            'workmode': ['VFO', 'Chan'],
537
        }
538

    
539
        if self.VENDOR == "Retevis":
540
            choice_settings.update(retevis_settings)
541
        else:
542
            choice_settings.update(radioddity_settings)
543

    
544
        basic_settings = ['timeout', 'vox', 'backlight', 'language',
545
                          'cha_disp', 'chb_disp', 'workmode']
546
        titles = {
547
            'savemode': 'Save Mode',
548
            'vox': 'VOX',
549
            'backlight': 'Auto Backlight',
550
            'timeout': 'Time Out Timer (s)',
551
            'language': 'Language',
552
            'dtmfst': 'DTMF-ST',
553
            'scanmode': 'Scan Mode',
554
            'pttid': 'PTT-ID',
555
            'cha_disp': 'Channel A Display',
556
            'chb_disp': 'Channel B Display',
557
            'alarm_mode': 'Alarm Mode',
558
            'txundertdr': 'TX Under TDR',
559
            'rptnoiseclr': 'RPT Noise Clear (ms)',
560
            'rptnoisedet': 'RPT Noise Detect (ms)',
561
            'workmode': 'Work Mode',
562
        }
563

    
564
        basic.append(
565
            RadioSetting('squelch', 'Squelch Level',
566
                         RadioSettingValueInteger(0, 9, int(_set.squelch))))
567
        adv.append(
568
            RadioSetting('pttdelay', 'PTT Delay',
569
                         RadioSettingValueInteger(0, 30, int(_set.pttdelay))))
570
        adv.append(
571
            RadioSetting('tdr', 'TDR',
572
                         RadioSettingValueBoolean(
573
                             int(_set.tdr))))
574
        adv.append(
575
            RadioSetting('beep', 'Beep',
576
                         RadioSettingValueBoolean(
577
                             int(_set.beep))))
578
        basic.append(
579
            RadioSetting('voice', 'Voice Enable',
580
                         RadioSettingValueBoolean(
581
                             int(_set.voice))))
582
        adv.append(
583
            RadioSetting('bcl', 'BCL',
584
                         RadioSettingValueBoolean(
585
                             int(_set.bcl))))
586
        adv.append(
587
            RadioSetting('autolock', 'Auto Lock',
588
                         RadioSettingValueBoolean(
589
                             int(_set.autolock))))
590
        adv.append(
591
            RadioSetting('alarmsound', 'Alarm Sound',
592
                         RadioSettingValueBoolean(
593
                             int(_set.alarmsound))))
594
        adv.append(
595
            RadioSetting('tailnoiseclear', 'Tail Noise Clear',
596
                         RadioSettingValueBoolean(
597
                             int(_set.tailnoiseclear))))
598
        adv.append(
599
            RadioSetting('roger', 'Roger',
600
                         RadioSettingValueBoolean(
601
                             int(_set.roger))))
602
        adv.append(
603
            RadioSetting('fmradio', 'FM Radio Disabled',
604
                         RadioSettingValueBoolean(
605
                             int(_set.fmradio))))
606
        adv.append(
607
            RadioSetting('kblock', 'KB Lock',
608
                         RadioSettingValueBoolean(
609
                             int(_set.kblock))))
610

    
611
        for key in sorted(choice_settings):
612
            choices = choice_settings[key]
613
            title = titles[key]
614
            if key in basic_settings:
615
                group = basic
616
            else:
617
                group = adv
618

    
619
            val = int(getattr(_set, key))
620
            try:
621
                cur = choices[val]
622
            except IndexError:
623
                LOG.error('Value %i for %s out of range for list (%i): %s' % (
624
                    val, key, len(choices), choices))
625
                raise
626
            group.append(
627
                RadioSetting(key, title,
628
                             RadioSettingValueList(
629
                                 choices,
630
                                 choices[val])))
631

    
632
        if self.VENDOR == "Retevis":
633
            # Side Keys
634
            _skey = self._memobj.skey
635
            SK_CHOICES = ['OFF', 'LAMP', 'SOS', 'FM', 'NOAA', 'MONI', 'SEARCH']
636
            SK_VALUES = [0xFF, 0x08, 0x03, 0x07, 0x0C, 0x05, 0x1D]
637

    
638
            def apply_sk_listvalue(setting, obj):
639
                LOG.debug("Setting value: " + str(setting.value) +
640
                          " from list")
641
                val = str(setting.value)
642
                index = SK_CHOICES.index(val)
643
                val = SK_VALUES[index]
644
                obj.set_value(val)
645

    
646
            # Side Key 1 - Short Press
647
            if _skey.skey1sp in SK_VALUES:
648
                idx = SK_VALUES.index(_skey.skey1sp)
649
            else:
650
                idx = SK_VALUES.index(0xFF)
651
            rs = RadioSetting('skey.skey1sp', 'Side Key 1 - Short Press',
652
                              RadioSettingValueList(SK_CHOICES,
653
                                                    SK_CHOICES[idx]))
654
            rs.set_apply_callback(apply_sk_listvalue, _skey.skey1sp)
655
            adv.append(rs)
656

    
657
            # Side Key 1 - Long Press
658
            if _skey.skey1lp in SK_VALUES:
659
                idx = SK_VALUES.index(_skey.skey1lp)
660
            else:
661
                idx = SK_VALUES.index(0xFF)
662
            rs = RadioSetting('skey.skey1lp', 'Side Key 1 - Long Press',
663
                              RadioSettingValueList(SK_CHOICES,
664
                                                    SK_CHOICES[idx]))
665
            rs.set_apply_callback(apply_sk_listvalue, _skey.skey1lp)
666
            adv.append(rs)
667

    
668
            # Side Key 2 - Short Press
669
            if _skey.skey2sp in SK_VALUES:
670
                idx = SK_VALUES.index(_skey.skey2sp)
671
            else:
672
                idx = SK_VALUES.index(0xFF)
673
            rs = RadioSetting('skey.skey2sp', 'Side Key 2 - Short Press',
674
                              RadioSettingValueList(SK_CHOICES,
675
                                                    SK_CHOICES[idx]))
676
            rs.set_apply_callback(apply_sk_listvalue, _skey.skey2sp)
677
            adv.append(rs)
678

    
679
            # Side Key 1 - Long Press
680
            if _skey.skey2lp in SK_VALUES:
681
                idx = SK_VALUES.index(_skey.skey2lp)
682
            else:
683
                idx = SK_VALUES.index(0xFF)
684
            rs = RadioSetting('skey.skey2lp', 'Side Key 2 - Long Press',
685
                              RadioSettingValueList(SK_CHOICES,
686
                                                    SK_CHOICES[idx]))
687
            rs.set_apply_callback(apply_sk_listvalue, _skey.skey2lp)
688
            adv.append(rs)
689

    
690
        for i in range(1, 16):
691
            cur = ''.join(
692
                DTMFCHARS[i]
693
                for i in self._memobj.dtmfgroup[i - 1].code if int(i) < 0xF)
694
            dtmf.append(
695
                RadioSetting(
696
                    'dtmf.code@%i' % i, 'DTMF Group %i' % i,
697
                    RadioSettingValueString(0, 5, cur,
698
                                            autopad=False,
699
                                            charset=DTMFCHARS)))
700
        cur = ''.join(
701
            '%X' % i
702
            for i in self._memobj.anicode.code if int(i) < 0xE)
703
        dtmf.append(
704
            RadioSetting(
705
                'anicode.code', 'ANI Code',
706
                RadioSettingValueString(0, 5, cur,
707
                                        autopad=False,
708
                                        charset=DTMFCHARS)))
709

    
710
        anicode = self._memobj.anicode
711

    
712
        dtmf.append(
713
            RadioSetting(
714
                'anicode.groupcode', 'Group Code',
715
                RadioSettingValueList(
716
                    list(DTMFCHARS),
717
                    DTMFCHARS[int(anicode.groupcode)])))
718

    
719
        dtmf.append(
720
            RadioSetting(
721
                'anicode.releasetosend', 'Release To Send',
722
                RadioSettingValueBoolean(
723
                    int(anicode.releasetosend))))
724
        dtmf.append(
725
            RadioSetting(
726
                'anicode.presstosend', 'Press To Send',
727
                RadioSettingValueBoolean(
728
                    int(anicode.presstosend))))
729
        cur = int(anicode.dtmfspeedon) * 10 + 80
730
        dtmf.append(
731
            RadioSetting(
732
                'anicode.dtmfspeedon', 'DTMF Speed (on time in ms)',
733
                RadioSettingValueInteger(60, 2000, cur, 10)))
734
        cur = int(anicode.dtmfspeedoff) * 10 + 80
735
        dtmf.append(
736
            RadioSetting(
737
                'anicode.dtmfspeedoff', 'DTMF Speed (off time in ms)',
738
                RadioSettingValueInteger(60, 2000, cur, 10)))
739

    
740
        top = RadioSettings(basic, adv, dtmf)
741
        return top
742

    
743
    def set_settings(self, settings):
744
        for element in settings:
745
            if element.get_name().startswith('anicode.'):
746
                self._set_anicode(element)
747
            elif element.get_name().startswith('dtmf.code'):
748
                self._set_dtmfcode(element)
749
            elif element.get_name().startswith('skey.'):
750
                self._set_skey(element)
751
            elif not isinstance(element, RadioSetting):
752
                self.set_settings(element)
753
                continue
754
            else:
755
                self._set_setting(element)
756

    
757
    def _set_setting(self, setting):
758
        key = setting.get_name()
759
        val = setting.value
760

    
761
        setattr(self._memobj.settings, key, int(val))
762

    
763
    def _set_anicode(self, setting):
764
        name = setting.get_name().split('.', 1)[1]
765
        if name == 'code':
766
            val = [DTMFCHARS.index(c) for c in str(setting.value)]
767
            for i in range(0, 5):
768
                try:
769
                    value = val[i]
770
                except IndexError:
771
                    value = 0xFF
772
                self._memobj.anicode.code[i] = value
773
        elif name.startswith('dtmfspeed'):
774
            setattr(self._memobj.anicode, name,
775
                    (int(setting.value) - 80) // 10)
776
        else:
777
            setattr(self._memobj.anicode, name, int(setting.value))
778

    
779
    def _set_dtmfcode(self, setting):
780
        index = int(setting.get_name().split('@', 1)[1]) - 1
781
        val = [DTMFCHARS.index(c) for c in str(setting.value)]
782
        for i in range(0, 5):
783
            try:
784
                value = val[i]
785
            except IndexError:
786
                value = 0xFF
787
            self._memobj.dtmfgroup[index].code[i] = value
788

    
789
    def _set_skey(self, setting):
790
        if setting.has_apply_callback():
791
            LOG.debug("Using apply callback")
792
            setting.run_apply_callback()
793
            
794

    
795
@directory.register
796
class Senhaix8800(RadioddityGS5BRadio):
797
    """Senhaix 8800"""
798
    VENDOR = "Senhaix"
799
    MODEL = "8800"
800
    
801
@directory.register
802
class SignusXTR5(RadioddityGS5BRadio):
803
    """Signus XTR-5"""
804
    VENDOR = "Signus"
805
    MODEL = "XTR-5"
806
    
807
@directory.register
808
class AnysecuAC580(RadioddityGS5BRadio):
809
    """Anysecu AC-580"""
810
    VENDOR = "Anysecu"
811
    MODEL = "AC-580"
(8-8/10)