Project

General

Profile

Bug #10386 » ft90.py

6f287d5c - Dan Smith, 02/23/2023 02:26 AM

 
1
# Copyright 2011 Dan Smith <dsmith@danplanet.com>
2
# Copyright 2013 Jens Jensen AF5MI <kd4tjx@yahoo.com>
3
#
4
# This program is free software: you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

    
17
from chirp.drivers import yaesu_clone
18
from chirp import chirp_common, bitwise, memmap, directory, errors, util
19
from chirp.settings import RadioSetting, RadioSettingGroup, \
20
    RadioSettingValueInteger, RadioSettingValueList, \
21
    RadioSettingValueBoolean, RadioSettingValueString, \
22
    RadioSettings
23

    
24
import time
25
import os
26
import traceback
27
import string
28
import re
29
import logging
30

    
31
from textwrap import dedent
32

    
33
LOG = logging.getLogger(__name__)
34

    
35
CMD_ACK = b'\x06'
36

    
37
FT90_STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0]
38
FT90_MODES = ["AM", "FM", "Auto"]
39
# idx 3 (Bell) not supported yet
40
FT90_TMODES = ["", "Tone", "TSQL", "", "DTCS"]
41
FT90_TONES = list(chirp_common.TONES)
42
for tone in [165.5, 171.3, 177.3]:
43
    FT90_TONES.remove(tone)
44
FT90_POWER_LEVELS_VHF = [chirp_common.PowerLevel("Hi", watts=50),
45
                         chirp_common.PowerLevel("Mid1", watts=20),
46
                         chirp_common.PowerLevel("Mid2", watts=10),
47
                         chirp_common.PowerLevel("Low", watts=5)]
48

    
49
FT90_POWER_LEVELS_UHF = [chirp_common.PowerLevel("Hi", watts=35),
50
                         chirp_common.PowerLevel("Mid1", watts=20),
51
                         chirp_common.PowerLevel("Mid2", watts=10),
52
                         chirp_common.PowerLevel("Low", watts=5)]
53

    
54
FT90_DUPLEX = ["", "-", "+", "split"]
55
FT90_CWID_CHARS = list(string.digits) + list(string.ascii_uppercase) \
56
                  + list(" ")
57
FT90_DTMF_CHARS = list("0123456789ABCD*#")
58
FT90_SPECIAL = ["vfo_vhf", "home_vhf", "vfo_uhf", "home_uhf",
59
                "pms_1L", "pms_1U", "pms_2L", "pms_2U"]
60

    
61

    
62
@directory.register
63
class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
64
    VENDOR = "Yaesu"
65
    MODEL = "FT-90"
66
    ID = b"\x8E\xF6"
67
    NEEDS_COMPAT_SERIAL = False
68

    
69
    _memsize = 4063
70
    # block 03 (200 Bytes long) repeats 18 times; channel memories
71
    _block_lengths = [2, 232, 24] + ([200] * 18) + [205]
72

    
73
    mem_format = """
74
u16 id;
75
#seekto 0x22;
76
struct {
77
    u8 dtmf_active;
78
    u8 dtmf1_len;
79
    u8 dtmf2_len;
80
    u8 dtmf3_len;
81
    u8 dtmf4_len;
82
    u8 dtmf5_len;
83
    u8 dtmf6_len;
84
    u8 dtmf7_len;
85
    u8 dtmf8_len;
86
    u8 dtmf1[8];
87
    u8 dtmf2[8];
88
    u8 dtmf3[8];
89
    u8 dtmf4[8];
90
    u8 dtmf5[8];
91
    u8 dtmf6[8];
92
    u8 dtmf7[8];
93
    u8 dtmf8[8];
94
    char cwid[7];
95
    u8 unk1;
96
    u8 scan1:2,
97
       beep:1,
98
       unk3:3,
99
       rfsqlvl:2;
100
    u8 unk4:2,
101
       scan2:1,
102
       cwid_en:1,
103
       txnarrow:1,
104
       dtmfspeed:1,
105
       pttlock:2;
106
    u8 dtmftxdelay:3,
107
       fancontrol:2,
108
       unk5:3;
109
    u8 dimmer:3,
110
       unk6:1,
111
       lcdcontrast:4;
112
    u8 dcsmode:2,
113
       unk16:2,
114
       tot:4;
115
    u8 unk14;
116
    u8 unk8:1,
117
       ars:1,
118
       lock:1,
119
       txpwrsave:1,
120
       apo:4;
121
    u8 unk15;
122
    u8 unk9:4,
123
       key_lt:4;
124
    u8 unk10:4,
125
       key_rt:4;
126
    u8 unk11:4,
127
       key_p1:4;
128
    u8 unk12:4,
129
       key_p2:4;
130
    u8 unk13:4,
131
       key_acc:4;
132
} settings;
133

    
134
struct mem_struct {
135
    u8 mode:2,
136
       isUhf1:1,
137
       unknown1:2,
138
       step:3;
139
    u8 artsmode:2,
140
       unknown2:1,
141
       isUhf2:1
142
       power:2,
143
       shift:2;
144
    u8 skip:1,
145
       showname:1,
146
       unknown3:1,
147
       isUhfHi:1,
148
       unknown4:1,
149
       tmode:3;
150
    u32 rxfreq;
151
    u32 txfreqoffset;
152
    u8 UseDefaultName:1,
153
       ars:1,
154
       tone:6;
155
    u8 packetmode:1,
156
       unknown5:1,
157
       dcstone:6;
158
    char name[7];
159
};
160

    
161
#seekto 0x86;
162
struct mem_struct vfo_vhf;
163
struct mem_struct home_vhf;
164
struct mem_struct vfo_uhf;
165
struct mem_struct home_uhf;
166

    
167
#seekto 0xEB;
168
u8 chan_enable[23];
169

    
170
#seekto 0x101;
171
struct {
172
    u8 pms_2U_enable:1,
173
       pms_2L_enable:1,
174
       pms_1U_enable:1,
175
       pms_1L_enable:1,
176
       unknown6:4;
177
} special_enables;
178

    
179
#seekto 0x102;
180
struct mem_struct memory[180];
181

    
182
#seekto 0xf12;
183
struct mem_struct pms_1L;
184
struct mem_struct pms_1U;
185
struct mem_struct pms_2L;
186
struct mem_struct pms_2U;
187

    
188
#seekto 0x0F7B;
189
struct  {
190
    char demomsg1[50];
191
    char demomsg2[50];
192
} demomsg;
193
"""
194

    
195
    @classmethod
196
    def get_prompts(cls):
197
        rp = chirp_common.RadioPrompts()
198
        rp.pre_download = _(dedent("""\
199
1. Turn radio off.
200
2. Connect mic and hold [ACC] on mic while powering on.
201
    ("CLONE" will appear on the display)
202
3. Replace mic with PC programming cable.
203
4. <b>After clicking OK</b>, press the [SET] key to send image."""))
204
        rp.pre_upload = _(dedent("""\
205
1. Turn radio off.
206
2. Connect mic and hold [ACC] on mic while powering on.
207
    ("CLONE" will appear on the display)
208
3. Replace mic with PC programming cable.
209
4. Press the [DISP/SS] key
210
    ("R" will appear on the lower left of LCD)."""))
211
        rp.display_pre_upload_prompt_before_opening_port = False
212
        return rp
213

    
214
    @classmethod
215
    def match_model(cls, filedata, filename):
216
        return len(filedata) == cls._memsize
217

    
218
    def get_features(self):
219
        rf = chirp_common.RadioFeatures()
220
        rf.has_settings = True
221
        rf.has_ctone = False
222
        rf.has_bank = False
223
        rf.has_dtcs_polarity = False
224
        rf.has_dtcs = True
225
        rf.valid_modes = FT90_MODES
226
        rf.valid_tmodes = FT90_TMODES
227
        rf.valid_duplexes = FT90_DUPLEX
228
        rf.valid_tuning_steps = FT90_STEPS
229
        rf.valid_power_levels = FT90_POWER_LEVELS_VHF
230
        rf.valid_name_length = 7
231
        rf.valid_characters = chirp_common.CHARSET_ASCII
232
        rf.valid_skips = ["", "S"]
233
        rf.valid_special_chans = FT90_SPECIAL
234
        rf.memory_bounds = (1, 180)
235
        rf.valid_bands = [(100000000, 230000000),
236
                          (300000000, 530000000),
237
                          (810000000, 999975000)]
238

    
239
        return rf
240

    
241
    def _read(self, blocksize, blocknum):
242
        data = self.pipe.read(blocksize+2)
243

    
244
        # chew echo'd ack
245
        self.pipe.write(CMD_ACK)
246
        time.sleep(0.02)
247
        self.pipe.read(1)  # chew echoed ACK from 1-wire serial
248

    
249
        if len(data) == blocksize + 2 and data[0] == blocknum:
250
            checksum = yaesu_clone.YaesuChecksum(1, blocksize)
251
            if checksum.get_existing(data) != checksum.get_calculated(data):
252
                raise Exception("Checksum Failed [%02X<>%02X] block %02X, "
253
                                "data len: %i" %
254
                                (checksum.get_existing(data),
255
                                 checksum.get_calculated(data),
256
                                 blocknum, len(data)))
257
            data = data[1:blocksize + 1]  # Chew blocknum and checksum
258

    
259
        else:
260
            raise Exception("Unable to read blocknum %02X "
261
                            "expected blocksize %i got %i." %
262
                            (blocknum, blocksize+2, len(data)))
263

    
264
        return data
265

    
266
    def _clone_in(self):
267
        # Be very patient with the radio
268
        self.pipe.timeout = 4
269
        start = time.time()
270

    
271
        data = b""
272
        blocknum = 0
273
        status = chirp_common.Status()
274
        status.msg = "Cloning..."
275
        self.status_fn(status)
276
        status.max = len(self._block_lengths)
277
        for blocksize in self._block_lengths:
278
            data += self._read(blocksize, blocknum)
279
            blocknum += 1
280
            status.cur = blocknum
281
            self.status_fn(status)
282

    
283
        LOG.info("Clone completed in %i seconds, blocks read: %i" %
284
                 (time.time() - start, blocknum))
285

    
286
        return memmap.MemoryMapBytes(data)
287

    
288
    def _clone_out(self):
289
        looppredelay = 0.4
290
        looppostdelay = 1.9
291
        self.pipe.timeout = 4
292
        start = time.time()
293

    
294
        LOG.debug('Delaying clone out...')
295
        time.sleep(5)
296
        LOG.debug('Proceeding')
297

    
298
        blocknum = 0
299
        pos = 0
300
        status = chirp_common.Status()
301
        status.msg = "Cloning to radio..."
302
        self.status_fn(status)
303
        status.max = len(self._block_lengths)
304

    
305
        for blocksize in self._block_lengths:
306
            checksum = yaesu_clone.YaesuChecksum(pos, pos+blocksize-1)
307
            blocknumbyte = bytes([blocknum])
308
            payloadbytes = self.get_mmap()[pos:pos+blocksize]
309
            checksumbyte = bytes([checksum.get_calculated(self.get_mmap())])
310
            LOG.debug("Block %i - will send from %i to %i byte " %
311
                      (blocknum, pos, pos + blocksize))
312
            LOG.debug(util.hexprint(blocknumbyte))
313
            LOG.debug(util.hexprint(payloadbytes))
314
            LOG.debug(util.hexprint(checksumbyte))
315
            # send wrapped bytes
316
            time.sleep(looppredelay)
317
            self.pipe.write(blocknumbyte)
318
            self.pipe.write(payloadbytes)
319
            self.pipe.write(checksumbyte)
320
            tmp = self.pipe.read(blocksize + 2)  # chew echo
321
            LOG.debug("bytes echoed: ")
322
            LOG.debug(util.hexprint(tmp))
323
            # radio is slow to write/ack:
324
            time.sleep(looppostdelay)
325
            buf = self.pipe.read(1)
326
            LOG.debug("ack recd:")
327
            LOG.debug(util.hexprint(buf))
328
            if buf != CMD_ACK:
329
                raise Exception("Radio did not ack block %i" % blocknum)
330
            pos += blocksize
331
            blocknum += 1
332
            status.cur = blocknum
333
            self.status_fn(status)
334

    
335
        LOG.info("Clone completed in %i seconds" % (time.time() - start))
336

    
337
    def sync_in(self):
338
        try:
339
            self._mmap = self._clone_in()
340
        except errors.RadioError:
341
            raise
342
        except Exception as e:
343
            trace = traceback.format_exc()
344
            raise errors.RadioError(
345
                    "Failed to communicate with radio: %s" % trace)
346
        self.process_mmap()
347

    
348
    def sync_out(self):
349
        try:
350
            self._clone_out()
351
        except errors.RadioError:
352
            raise
353
        except Exception as e:
354
            trace = traceback.format_exc()
355
            raise errors.RadioError(
356
                    "Failed to communicate with radio: %s" % trace)
357

    
358
    def process_mmap(self):
359
        self._memobj = bitwise.parse(self.mem_format, self._mmap)
360

    
361
    def _get_chan_enable(self, number):
362
        number = number - 1
363
        bytepos = number // 8
364
        bitpos = number % 8
365
        chan_enable = self._memobj.chan_enable[bytepos]
366
        if chan_enable & (1 << bitpos):
367
            return True
368
        else:
369
            return False
370

    
371
    def _set_chan_enable(self, number, enable):
372
        number = number - 1
373
        bytepos = number // 8
374
        bitpos = number % 8
375
        chan_enable = self._memobj.chan_enable[bytepos]
376
        if enable:
377
            chan_enable = chan_enable | (1 << bitpos)  # enable
378
        else:
379
            chan_enable = chan_enable & ~(1 << bitpos)  # disable
380
        self._memobj.chan_enable[bytepos] = chan_enable
381

    
382
    def get_memory(self, number):
383
        mem = chirp_common.Memory()
384
        if isinstance(number, str):
385
            # special channel
386
            _mem = getattr(self._memobj, number)
387
            mem.number = - len(FT90_SPECIAL) + FT90_SPECIAL.index(number)
388
            mem.extd_number = number
389
            if re.match('^pms', mem.extd_number):
390
                # enable pms_XY channel flag
391
                _special_enables = self._memobj.special_enables
392
                mem.empty = not getattr(_special_enables,
393
                                        mem.extd_number + "_enable")
394
        else:
395
            # regular memory
396
            _mem = self._memobj.memory[number-1]
397
            mem.number = number
398
            mem.empty = not self._get_chan_enable(number)
399
        if mem.empty:
400
            return mem  # bail out, do not parse junk
401
        mem.freq = _mem.rxfreq * 10
402
        mem.offset = _mem.txfreqoffset * 10
403
        if not _mem.tmode < len(FT90_TMODES):
404
            _mem.tmode = 0
405
        mem.tmode = FT90_TMODES[_mem.tmode]
406
        mem.rtone = FT90_TONES[_mem.tone]
407
        mem.dtcs = chirp_common.DTCS_CODES[_mem.dcstone]
408
        mem.mode = FT90_MODES[_mem.mode]
409
        mem.duplex = FT90_DUPLEX[_mem.shift]
410
        if mem.freq / 1000000 > 300:
411
            mem.power = FT90_POWER_LEVELS_UHF[_mem.power]
412
        else:
413
            mem.power = FT90_POWER_LEVELS_VHF[_mem.power]
414

    
415
        # radio has a known bug with 5khz step and squelch
416
        if _mem.step == 0 or _mem.step > len(FT90_STEPS)-1:
417
            _mem.step = 2
418
        mem.tuning_step = FT90_STEPS[_mem.step]
419
        mem.skip = _mem.skip and "S" or ""
420
        if not all(char in chirp_common.CHARSET_ASCII
421
                   for char in str(_mem.name)):
422
            # dont display blank/junk name
423
            mem.name = ""
424
        else:
425
            mem.name = str(_mem.name)
426
        return mem
427

    
428
    def get_raw_memory(self, number):
429
        return repr(self._memobj.memory[number-1])
430

    
431
    def set_memory(self, mem):
432
        if mem.number < 0:      # special channels
433
            _mem = getattr(self._memobj, mem.extd_number)
434
            if re.match('^pms', mem.extd_number):
435
                # enable pms_XY channel flag
436
                _special_enables = self._memobj.special_enables
437
                setattr(_special_enables, mem.extd_number + "_enable", True)
438
        else:
439
            _mem = self._memobj.memory[mem.number - 1]
440
            self._set_chan_enable(mem.number, not mem.empty)
441
        _mem.skip = mem.skip == "S"
442
        # radio has a known bug with 5khz step and dead squelch
443
        if not mem.tuning_step or mem.tuning_step == FT90_STEPS[0]:
444
            _mem.step = 2
445
        else:
446
            _mem.step = FT90_STEPS.index(mem.tuning_step)
447
        _mem.rxfreq = mem.freq / 10
448
        # vfo will unlock if not in right band?
449
        if mem.freq > 300000000:
450
            # uhf
451
            _mem.isUhf1 = 1
452
            _mem.isUhf2 = 1
453
            if mem.freq > 810000000:
454
                # uhf hiband
455
                _mem.isUhfHi = 1
456
            else:
457
                _mem.isUhfHi = 0
458
        else:
459
            # vhf
460
            _mem.isUhf1 = 0
461
            _mem.isUhf2 = 0
462
            _mem.isUhfHi = 0
463
        _mem.txfreqoffset = mem.offset / 10
464
        _mem.tone = FT90_TONES.index(mem.rtone)
465
        _mem.tmode = FT90_TMODES.index(mem.tmode)
466
        _mem.mode = FT90_MODES.index(mem.mode)
467
        _mem.shift = FT90_DUPLEX.index(mem.duplex)
468
        _mem.dcstone = chirp_common.DTCS_CODES.index(mem.dtcs)
469
        _mem.step = FT90_STEPS.index(mem.tuning_step)
470
        _mem.shift = FT90_DUPLEX.index(mem.duplex)
471
        if mem.power:
472
            _mem.power = FT90_POWER_LEVELS_VHF.index(mem.power)
473
        else:
474
            _mem.power = 3  # default to low power
475
        if (len(mem.name) == 0):
476
            _mem.name = bytearray.fromhex("80ffffffffffff")
477
            _mem.showname = 0
478
        else:
479
            _mem.name = str(mem.name).ljust(7)
480
            _mem.showname = 1
481
            _mem.UseDefaultName = 0
482

    
483
    def _decode_cwid(self, cwidarr):
484
        cwid = ""
485
        LOG.debug("@ +_decode_cwid:")
486
        for byte in cwidarr.get_value():
487
            char = int(byte)
488
            LOG.debug(char)
489
            # bitwise wraps in quotes! get rid of those
490
            if char < len(FT90_CWID_CHARS):
491
                cwid += FT90_CWID_CHARS[char]
492
        return cwid
493

    
494
    def _encode_cwid(self, cwidarr):
495
        cwid = ""
496
        LOG.debug("@ _encode_cwid:")
497
        for char in cwidarr.get_value():
498
            cwid += chr(FT90_CWID_CHARS.index(char))
499
        LOG.debug(cwid)
500
        return cwid
501

    
502
    def _bbcd2dtmf(self, bcdarr, strlen=16):
503
        # doing bbcd, but with support for ABCD*#
504
        LOG.debug(bcdarr.get_value())
505
        string = ''.join("%02X" % b for b in bcdarr)
506
        LOG.debug("@_bbcd2dtmf, received: %s" % string)
507
        string = string.replace('E', '*').replace('F', '#')
508
        if strlen <= 16:
509
            string = string[:strlen]
510
        return string
511

    
512
    def _dtmf2bbcd(self, dtmf):
513
        dtmfstr = dtmf.get_value()
514
        dtmfstr = dtmfstr.replace('*', 'E').replace('#', 'F')
515
        dtmfstr = str.ljust(dtmfstr.strip(), 16, "0")
516
        bcdarr = list(bytearray.fromhex(dtmfstr))
517
        LOG.debug("@_dtmf2bbcd, sending: %s" % bcdarr)
518
        return bcdarr
519

    
520
    def get_settings(self):
521
        _settings = self._memobj.settings
522
        basic = RadioSettingGroup("basic", "Basic")
523
        autodial = RadioSettingGroup("autodial", "AutoDial")
524
        keymaps = RadioSettingGroup("keymaps", "KeyMaps")
525

    
526
        top = RadioSettings(basic, keymaps, autodial)
527

    
528
        rs = RadioSetting(
529
                "beep", "Beep",
530
                RadioSettingValueBoolean(_settings.beep))
531
        basic.append(rs)
532
        rs = RadioSetting(
533
                "lock", "Lock",
534
                RadioSettingValueBoolean(_settings.lock))
535
        basic.append(rs)
536
        rs = RadioSetting(
537
                "ars", "Auto Repeater Shift",
538
                RadioSettingValueBoolean(_settings.ars))
539
        basic.append(rs)
540
        rs = RadioSetting(
541
                "txpwrsave", "TX Power Save",
542
                RadioSettingValueBoolean(_settings.txpwrsave))
543
        basic.append(rs)
544
        rs = RadioSetting(
545
                "txnarrow", "TX Narrow",
546
                RadioSettingValueBoolean(_settings.txnarrow))
547
        basic.append(rs)
548
        options = ["Off", "S-3", "S-5", "S-Full"]
549
        rs = RadioSetting(
550
                "rfsqlvl", "RF Squelch Level",
551
                RadioSettingValueList(options, options[_settings.rfsqlvl]))
552
        basic.append(rs)
553
        options = ["Off", "Band A", "Band B", "Both"]
554
        rs = RadioSetting(
555
                "pttlock", "PTT Lock",
556
                RadioSettingValueList(options, options[_settings.pttlock]))
557
        basic.append(rs)
558

    
559
        rs = RadioSetting(
560
                "cwid_en", "CWID Enable",
561
                RadioSettingValueBoolean(_settings.cwid_en))
562
        basic.append(rs)
563

    
564
        cwid = RadioSettingValueString(0, 7, self._decode_cwid(_settings.cwid))
565
        cwid.set_charset(FT90_CWID_CHARS)
566
        rs = RadioSetting("cwid", "CWID", cwid)
567
        basic.append(rs)
568

    
569
        options = ["OFF"] + [str(x) for x in (range(1, 12+1))]
570
        rs = RadioSetting(
571
                "apo", "APO time (hrs)",
572
                RadioSettingValueList(options, options[_settings.apo]))
573
        basic.append(rs)
574

    
575
        options = ["Off"] + [str(x) for x in (range(1, 60+1))]
576
        rs = RadioSetting(
577
                "tot", "Time Out Timer (mins)",
578
                RadioSettingValueList(options, options[_settings.tot]))
579
        basic.append(rs)
580

    
581
        options = ["off", "Auto/TX", "Auto", "TX"]
582
        rs = RadioSetting(
583
                "fancontrol", "Fan Control",
584
                RadioSettingValueList(options, options[_settings.fancontrol]))
585
        basic.append(rs)
586

    
587
        keyopts = ["Scan Up", "Scan Down", "Repeater", "Reverse", "Tone Burst",
588
                   "Tx Power", "Home Ch", "VFO/MR", "Tone", "Priority"]
589
        rs = RadioSetting(
590
                "key_lt", "Left Key",
591
                RadioSettingValueList(keyopts, keyopts[_settings.key_lt]))
592
        keymaps.append(rs)
593
        rs = RadioSetting(
594
                "key_rt", "Right Key",
595
                RadioSettingValueList(keyopts, keyopts[_settings.key_rt]))
596
        keymaps.append(rs)
597
        rs = RadioSetting(
598
                "key_p1", "P1 Key",
599
                RadioSettingValueList(keyopts, keyopts[_settings.key_p1]))
600
        keymaps.append(rs)
601
        rs = RadioSetting(
602
                "key_p2", "P2 Key",
603
                RadioSettingValueList(keyopts, keyopts[_settings.key_p2]))
604
        keymaps.append(rs)
605
        rs = RadioSetting(
606
                "key_acc", "ACC Key",
607
                RadioSettingValueList(keyopts, keyopts[_settings.key_acc]))
608
        keymaps.append(rs)
609

    
610
        options = [str(x) for x in range(0, 12+1)]
611
        rs = RadioSetting(
612
                "lcdcontrast", "LCD Contrast",
613
                RadioSettingValueList(options, options[_settings.lcdcontrast]))
614
        basic.append(rs)
615

    
616
        options = ["off", "d4", "d3", "d2", "d1"]
617
        rs = RadioSetting(
618
                "dimmer", "Dimmer",
619
                RadioSettingValueList(options, options[_settings.dimmer]))
620
        basic.append(rs)
621

    
622
        options = ["TRX Normal", "RX Reverse", "TX Reverse", "TRX Reverse"]
623
        rs = RadioSetting(
624
                "dcsmode", "DCS Mode",
625
                RadioSettingValueList(options, options[_settings.dcsmode]))
626
        basic.append(rs)
627

    
628
        options = ["50 ms", "100 ms"]
629
        rs = RadioSetting(
630
                "dtmfspeed", "DTMF Speed",
631
                RadioSettingValueList(options, options[_settings.dtmfspeed]))
632
        autodial.append(rs)
633

    
634
        options = ["50 ms", "250 ms", "450 ms", "750 ms", "1 sec"]
635
        rs = RadioSetting(
636
                "dtmftxdelay", "DTMF TX Delay",
637
                RadioSettingValueList(options, options[_settings.dtmftxdelay]))
638
        autodial.append(rs)
639

    
640
        options = [str(x) for x in range(1, 8 + 1)]
641
        rs = RadioSetting(
642
                "dtmf_active", "DTMF Active",
643
                RadioSettingValueList(options, options[_settings.dtmf_active]))
644
        autodial.append(rs)
645

    
646
        # setup 8 dtmf autodial entries
647
        for i in range(1, 9):
648
            objname = "dtmf" + str(i)
649
            dtmfsetting = getattr(_settings, objname)
650
            dtmflen = getattr(_settings, objname + "_len")
651
            dtmfstr = self._bbcd2dtmf(dtmfsetting, dtmflen)
652
            dtmf = RadioSettingValueString(0, 16, dtmfstr)
653
            dtmf.set_charset(FT90_DTMF_CHARS + list(" "))
654
            rs = RadioSetting(objname, objname.upper(), dtmf)
655
            autodial.append(rs)
656

    
657
        return top
658

    
659
    def set_settings(self, uisettings):
660
        _settings = self._memobj.settings
661
        for element in uisettings:
662
            if not isinstance(element, RadioSetting):
663
                self.set_settings(element)
664
                continue
665
            if not element.changed():
666
                continue
667
            try:
668
                setting = element.get_name()
669
                oldval = getattr(_settings, setting)
670
                newval = element.value
671
                if setting == "cwid":
672
                    newval = self._encode_cwid(newval)
673
                if re.match('dtmf\d', setting):
674
                    # set dtmf length field and then get bcd dtmf
675
                    dtmfstrlen = len(str(newval).strip())
676
                    setattr(_settings, setting + "_len", dtmfstrlen)
677
                    newval = self._dtmf2bbcd(newval)
678
                LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval))
679
                setattr(_settings, setting, newval)
680
            except Exception as e:
681
                LOG.debug(element.get_name())
682
                raise
(8-8/10)