Project

General

Profile

Bug #10572 » mml_jc8810_0000004a0020f804.py

Jim Unroe, 05/13/2023 09:30 AM

 
1
# Copyright 2023 Jim Unroe <rock.unroe@gmail.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 2 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
import logging
17
import struct
18

    
19
from chirp import (
20
    bitwise,
21
    chirp_common,
22
    directory,
23
    errors,
24
    memmap,
25
    util,
26
)
27
from chirp.settings import (
28
    RadioSetting,
29
    RadioSettingGroup,
30
    RadioSettings,
31
    RadioSettingValueBoolean,
32
    RadioSettingValueInteger,
33
    RadioSettingValueList,
34
    RadioSettingValueString,
35
)
36

    
37
LOG = logging.getLogger(__name__)
38

    
39
MEM_FORMAT = """
40
#seekto 0x0000;
41
struct {
42
  lbcd rxfreq[4];     // 0-3
43
  lbcd txfreq[4];     // 4-7
44
  ul16 rxtone;        // 8-9
45
  ul16 txtone;        // A-B
46
  u8 unknown1:4,      // C
47
     scode:4;         //     Signaling
48
  u8 unknown2:6,      // D
49
     pttid:2;         //     PTT-ID
50
  u8 unknown3:6,      // E
51
     txpower:2;       //     Power Level  0 = H, 1 = L, 2 = M
52
  u8 unknown4:1,      // F
53
     narrow:1,        //     Bandwidth  0 = Wide, 1 = Narrow
54
     encrypt:2,       //     Encrypt
55
     bcl:1,           //     BCL
56
     scan:1,          //     Scan  0 = Skip, 1 = Scan
57
     unknown5:1,      //
58
     learning:1;      //     Learning
59
  lbcd code[3];       // 0-2 Code
60
  u8 unknown6;        // 3
61
  char name[12];      // 4-F 12-character Alpha Tag
62
} memory[256];
63

    
64
#seekto 0x9000;
65
struct {
66
  u8 unused:4,        // 9000
67
     sql:4;           //      Squelch Level
68
  u8 unused_9001:6,   // 9001
69
     save:2;          //      Battery Save
70
  u8 unused_9002:4,   // 9002
71
     vox:4;           //      VOX Level
72
  u8 unused_9003:4,   // 9003
73
     abr:4;           //      Auto BackLight
74
  u8 unused_9004:7,   // 9004
75
     tdr:1;           //      TDR
76
  u8 unused:5,        // 9005
77
     tot:3;           //      Time-out Timer
78
  u8 unused_9006:7,   // 9006
79
     beep:1;          //      Beep
80
  u8 unused_9007:7,   // 9007
81
     voice:1;         //      Voice Prompt
82
  u8 unused_9008:7,   // 9008
83
     language:1;      //      Language
84
  u8 unused_9009:6,   // 9009
85
     dtmfst:2;        //      DTMF ST
86
  u8 unused_900a:6,   // 900A
87
     screv:2;         //      Scan Mode
88
  u8 unknown_900b;    // 900B
89
  u8 unused_900c:5,   // 900C
90
     pttlt:3;         //      PTT Delay
91
  u8 unused_900d:7,   // 900D
92
     mdfa:1;          //      Channel_A Display
93
  u8 unused_9003:7,   // 900E
94
     mdfb:1;          //      Channel_B Display
95
  u8 unknown_900f;    // 900F
96
  u8 unused_9010:4,   // 9010
97
     autolk:4;        //      Key Auto Lock
98
  u8 unused_9011:6,   // 9011
99
     almode:2;        //      Alarm Mode
100
  u8 unused_9012:7,   // 9012
101
     alarmsound:1;    //      Alarm Sound
102
  u8 unused_9013:6,   // 9013
103
     dualtx:2;        //      Dual TX
104
  u8 unused_9014:7,   // 9014
105
     ste:1;           //      Tail Noise Clear
106
  u8 unused_9015:4,   // 9015
107
     rpste:4;         //      RPT Noise Clear
108
  u8 unused_9016:4,   // 9016
109
     rptrl:4;         //      RPT Noise Delay
110
  u8 unused_9017:7,   // 9017
111
     roger:1;         //      Roger
112
  u8 unknown_9018;    // 9018
113
  u8 unused_9019:7,   // 9019
114
     fmradio:1;       //      FM Radio
115
  u8 unused_901a1:3,  // 901A
116
     vfomrb:1,        //      WorkMode B
117
     unused_901a2:3,  //
118
     vfomra:1;        //      WorkMode A
119
  u8 unused_901b:7,   // 901B
120
     kblock:1;        //      KB Lock
121
  u8 unused_901c:7,   // 901C
122
     ponmsg:1;        //      Power On Msg
123
  u8 unknown_901d;    // 901D
124
  u8 unused_901e:6,   // 901E
125
     tone:2;          //      Pilot
126
  u8 unknown_901f;    // 901F
127
  u8 unused_9020:4,   // 9020
128
     voxd:4;          //      VOX Delay
129
  u8 unused_9021:4,   // 9021
130
     menuquit:4;      //      Menu Auto Quit
131
  u8 unused_9022:7,   // 9022
132
     tailcode:1;      //      Tail Code (RT-470L)
133
  u8 unknown_9023;    // 9023
134
  u8 unknown_9024;    // 9024
135
  u8 unknown_9025;    // 9025
136
  u8 unknown_9026;    // 9026
137
  u8 unknown_9027;    // 9027
138
  u8 unknown_9028;    // 9028
139
  u8 unused_9029:6,   // 9029
140
     qtsave:2;        //      QT Save Type
141
  u8 ani;             // 902A ANI
142
  u8 skey2_sp;        // 902B Skey2 Short
143
  u8 skey2_lp;        // 902C Skey2 Long
144
  u8 skey3_sp;        // 902D Skey3 Short
145
  u8 topkey_sp;       // 902E Top Key (RT-470L)
146
  u8 unused_902f:7,   // 902F
147
     rxendtail:1;     //      Tone Rx End
148
  u8 skey3_lp;        // 9030 Skey3 Long (RT-470L)
149
} settings;
150

    
151
#seekto 0xA020;
152
struct {
153
  u8 code[5];         //      5-character DTMF Encoder Groups
154
  u8 unused[11];
155
} pttid[15];
156

    
157
#seekto 0xA006;
158
struct {
159
  u8 unused_a006:4,   // A006
160
     pttid:4;         //      PTT-ID
161
  u8 unused_a007:5,   // A007
162
     dtmfon:3;        //      DTMF Speed (on time)
163
  u8 unused_a008:5,   // A008
164
     dtmfoff:3;       //      DTMF Speed (off time)
165
} dtmf;
166

    
167
#seekto 0xB000;
168
struct {
169
  u8 code[3];         //      3-character ANI Code Groups
170
} anicodes[60];
171

    
172
#seekto 0xB0C0;
173
struct {
174
  char name[6];       //      6-character ANI Code Group Names
175
  u8 unused[10];
176
} aninames[30];
177
"""
178

    
179

    
180
CMD_ACK = b"\x06"
181

    
182
DTCS = tuple(sorted(chirp_common.DTCS_CODES + (645,)))
183

    
184
DTMF_CHARS = "0123456789 *#ABCD"
185

    
186
TXPOWER_HIGH = 0x00
187
TXPOWER_LOW = 0x01
188
TXPOWER_MID = 0x02
189

    
190
ABR_LIST = ["On", "5 seconds", "10 seconds", "15 seconds", "20 seconds",
191
            "30 seconds", "1 minute", "2 minutes", "3 minutes"]
192
ALMODE_LIST = ["Site", "Tone", "Code"]
193
AUTOLK_LIST = ABR_LIST[:4]
194
DTMFSPEED_LIST = ["50 ms", "100 ms", "200 ms", "300 ms", "500 ms"]
195
DTMFST_LIST = ["Off", "KeyBoard Side Tone", "ANI Side Tone", "KB ST + ANI ST"]
196
DUALTX_LIST = ["Off", "A", "B"]
197
ENCRYPT_LIST = ["Off", "DCP1", "DCP2", "DCP3"]
198
LANGUAGE_LIST = ["English", "Chinese"]
199
MDF_LIST = ["Name", "Frequency"]
200
MENUQUIT_LIST = ["%s seconds" % x for x in range(5, 55, 5)] + ["60 seconds"]
201
OFF1TO9_LIST = ["Off"] + ["%s" % x for x in range(1, 10)]
202
PONMSG_LIST = ["Logo", "Voltage"]
203
PTTID_LIST = ["Off", "BOT", "EOT", "Both"]
204
PTTIDCODE_LIST = ["%s" % x for x in range(1, 16)]
205
PTTLT_LIST = ["None", "100 seconds"] + \
206
             ["%s seconds" % x for x in range(200, 1200, 200)]
207
QTSAVE_LIST = ["All", "RX", "TX"]
208
RPSTE_LIST = ["Off"] + ["%s ms" % x for x in range(100, 1100, 100)]
209
SAVE_LIST = ["Off", "Normal", "Super", "Deep"]
210
SCREV_LIST = ["Time (TO)", "Carrier (CO)", "Search (SE)"]
211
TAILCODE_LIST = ["55 Hz", "62.5 Hz"]
212
TONERXEND_LIST = ["Off", "MDC-1200"]
213
TONE_LIST = ["1000 Hz", "1450 Hz", "1750 Hz", "2100 Hz"]
214
TOT_LIST = ["Off", "30 seconds", "60 seconds", "120 seconds", "240 seconds",
215
            "480 seconds"]
216
VOXD_LIST = ["%s seconds" % str(x / 10) for x in range(5, 21)]
217
WORKMODE_LIST = ["VFO Mode", "Channel Mode"]
218

    
219
ALL_SKEY_CHOICES = ["OFF",
220
                    "FM Radio",
221
                    "TX Power Level",
222
                    "Scan",
223
                    "Search",
224
                    "Flashlight",
225
                    "NOAA Weather",
226
                    "Monitor",
227
                    "PTT B",
228
                    "SOS"]
229

    
230
ALL_SKEY_VALUES = [0xFF,
231
                   0x07,
232
                   0x0A,
233
                   0x1C,
234
                   0x1D,
235
                   0x08,
236
                   0x0C,
237
                   0x05,
238
                   0x01,
239
                   0x03]
240

    
241

    
242
SETTING_LISTS = {
243
    "abr": ABR_LIST,
244
    "almod": ALMODE_LIST,
245
    "autolk": AUTOLK_LIST,
246
    "dtmfoff": DTMFSPEED_LIST,
247
    "dtmfon": DTMFSPEED_LIST,
248
    "dtmfst": DTMFST_LIST,
249
    "dualtx": DUALTX_LIST,
250
    "encrypt": ENCRYPT_LIST,
251
    "language": LANGUAGE_LIST,
252
    "mdfa": MDF_LIST,
253
    "mdfb": MDF_LIST,
254
    "menuquit": MENUQUIT_LIST,
255
    "ponmsg": PONMSG_LIST,
256
    "pttid": PTTID_LIST,
257
    "pttlt": PTTLT_LIST,
258
    "qtsave": QTSAVE_LIST,
259
    "rpste": RPSTE_LIST,
260
    "rptrl": RPSTE_LIST,
261
    "rxendtail": TONERXEND_LIST,
262
    "save": SAVE_LIST,
263
    "scode": PTTIDCODE_LIST,
264
    "screv": SCREV_LIST,
265
    "tailcode": TAILCODE_LIST,
266
    "tone": TONE_LIST,
267
    "tot": TOT_LIST,
268
    "vox": OFF1TO9_LIST,
269
    "voxd": VOXD_LIST,
270
    "workmode": WORKMODE_LIST
271
    }
272

    
273

    
274
def _enter_programming_mode(radio):
275
    serial = radio.pipe
276

    
277
    exito = False
278
    for i in range(0, 5):
279
        serial.write(radio._magic)
280
        ack = serial.read(1)
281

    
282
        try:
283
            if ack == CMD_ACK:
284
                exito = True
285
                break
286
        except Exception:
287
            LOG.debug("Attempt #%s, failed, trying again" % i)
288
            pass
289

    
290
    # check if we had EXITO
291
    if exito is False:
292
        msg = "The radio did not accept program mode after five tries.\n"
293
        msg += "Check you interface cable and power cycle your radio."
294
        raise errors.RadioError(msg)
295

    
296
    try:
297
        serial.write(b"F")
298
        ident = serial.read(8)
299
    except Exception:
300
        raise errors.RadioError("Error communicating with radio")
301

    
302
    if ident not in radio._fingerprint:
303
        LOG.debug(util.hexprint(ident))
304
        raise errors.RadioError("Radio returned unknown identification string")
305

    
306

    
307
def _exit_programming_mode(radio):
308
    serial = radio.pipe
309
    try:
310
        serial.write(b"E")
311
    except Exception:
312
        raise errors.RadioError("Radio refused to exit programming mode")
313

    
314

    
315
def _read_block(radio, block_addr, block_size):
316
    serial = radio.pipe
317

    
318
    cmd = struct.pack(">cHb", b'R', block_addr, block_size)
319
    expectedresponse = b"R" + cmd[1:]
320
    LOG.debug("Reading block %04x..." % (block_addr))
321

    
322
    try:
323
        serial.write(cmd)
324
        response = serial.read(4 + block_size)
325
        if response[:4] != expectedresponse:
326
            raise Exception("Error reading block %04x." % (block_addr))
327

    
328
        block_data = response[4:]
329
    except Exception:
330
        raise errors.RadioError("Failed to read block at %04x" % block_addr)
331

    
332
    return block_data
333

    
334

    
335
def _write_block(radio, block_addr, block_size):
336
    serial = radio.pipe
337

    
338
    cmd = struct.pack(">cHb", b'W', block_addr, block_size)
339
    data = radio.get_mmap()[block_addr:block_addr + block_size]
340

    
341
    LOG.debug("Writing Data:")
342
    LOG.debug(util.hexprint(cmd + data))
343

    
344
    try:
345
        serial.write(cmd + data)
346
        if serial.read(1) != CMD_ACK:
347
            raise Exception("No ACK")
348
    except Exception:
349
        raise errors.RadioError("Failed to send block "
350
                                "to radio at %04x" % block_addr)
351

    
352

    
353
def do_download(radio):
354
    LOG.debug("download")
355
    _enter_programming_mode(radio)
356

    
357
    data = b""
358

    
359
    status = chirp_common.Status()
360
    status.msg = "Cloning from radio"
361

    
362
    status.cur = 0
363
    status.max = radio._memsize
364

    
365
    for addr in range(0, radio._memsize, radio.BLOCK_SIZE):
366
        status.cur = addr + radio.BLOCK_SIZE
367
        radio.status_fn(status)
368

    
369
        block = _read_block(radio, addr, radio.BLOCK_SIZE)
370
        data += block
371

    
372
        LOG.debug("Address: %04x" % addr)
373
        LOG.debug(util.hexprint(block))
374

    
375
    _exit_programming_mode(radio)
376

    
377
    return memmap.MemoryMapBytes(data)
378

    
379

    
380
def do_upload(radio):
381
    status = chirp_common.Status()
382
    status.msg = "Uploading to radio"
383

    
384
    _enter_programming_mode(radio)
385

    
386
    status.cur = 0
387
    status.max = radio._memsize
388

    
389
    for start_addr, end_addr in radio._ranges:
390
        for addr in range(start_addr, end_addr, radio.BLOCK_SIZE_UP):
391
            status.cur = addr + radio.BLOCK_SIZE_UP
392
            radio.status_fn(status)
393
            _write_block(radio, addr, radio.BLOCK_SIZE_UP)
394

    
395
    _exit_programming_mode(radio)
396

    
397

    
398
def _split(rf, f1, f2):
399
    """Returns False if the two freqs are in the same band (no split)
400
    or True otherwise"""
401

    
402
    # determine if the two freqs are in the same band
403
    for low, high in rf.valid_bands:
404
        if f1 >= low and f1 <= high and \
405
                f2 >= low and f2 <= high:
406
            # if the two freqs are on the same Band this is not a split
407
            return False
408

    
409
    # if you get here is because the freq pairs are split
410
    return True
411

    
412

    
413
class JC8810base(chirp_common.CloneModeRadio):
414
    """MML JC-8810"""
415
    VENDOR = "MML"
416
    MODEL = "JC-8810base"
417
    BAUD_RATE = 57600
418
    NEEDS_COMPAT_SERIAL = False
419
    BLOCK_SIZE = 0x40
420
    BLOCK_SIZE_UP = 0x40
421

    
422
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=10.00),
423
                    chirp_common.PowerLevel("M", watts=8.00),
424
                    chirp_common.PowerLevel("L", watts=4.00)]
425

    
426
    VALID_BANDS = [(108000000, 136000000),
427
                   (136000000, 180000000),
428
                   (200000000, 260000000),
429
                   (330000000, 400000000),
430
                   (400000000, 520000000)]
431

    
432
    _magic = b"PROGRAMJC81U"
433
    _fingerprint = [b"\x00\x00\x00\x26\x00\x20\xD8\x04",
434
                    b"\x00\x00\x00\x42\x00\x20\xF0\x04",
435
                    b"\x00\x00\x00\x4A\x00\x20\xF8\x04"]
436

    
437
    _ranges = [
438
               (0x0000, 0x2000),
439
               (0x8000, 0x8040),
440
               (0x9000, 0x9040),
441
               (0xA000, 0xA140),
442
               (0xB000, 0xB300)
443
              ]
444
    _memsize = 0xB300
445
    _valid_chars = chirp_common.CHARSET_ALPHANUMERIC + \
446
        "`~!@#$%^&*()-=_+[]\\{}|;':\",./<>?"
447

    
448
    def get_features(self):
449
        rf = chirp_common.RadioFeatures()
450
        rf.has_settings = True
451
        rf.has_bank = False
452
        rf.has_ctone = True
453
        rf.has_cross = True
454
        rf.has_rx_dtcs = True
455
        rf.has_tuning_step = False
456
        rf.can_odd_split = True
457
        rf.has_name = True
458
        rf.valid_name_length = 12
459
        rf.valid_characters = self._valid_chars
460
        rf.valid_skips = ["", "S"]
461
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
462
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
463
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
464
        rf.valid_power_levels = self.POWER_LEVELS
465
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
466
        rf.valid_modes = ["FM", "NFM"]  # 25 kHz, 12.5 KHz.
467
        rf.valid_dtcs_codes = DTCS
468
        rf.memory_bounds = (1, 256)
469
        rf.valid_tuning_steps = [2.5, 5., 6.25, 10., 12.5, 20., 25., 50.]
470
        rf.valid_bands = self.VALID_BANDS
471
        return rf
472

    
473
    def process_mmap(self):
474
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
475

    
476
    def sync_in(self):
477
        """Download from radio"""
478
        try:
479
            data = do_download(self)
480
        except errors.RadioError:
481
            # Pass through any real errors we raise
482
            raise
483
        except Exception:
484
            # If anything unexpected happens, make sure we raise
485
            # a RadioError and log the problem
486
            LOG.exception('Unexpected error during download')
487
            raise errors.RadioError('Unexpected error communicating '
488
                                    'with the radio')
489
        self._mmap = data
490
        self.process_mmap()
491

    
492
    def sync_out(self):
493
        """Upload to radio"""
494
        try:
495
            do_upload(self)
496
        except Exception:
497
            # If anything unexpected happens, make sure we raise
498
            # a RadioError and log the problem
499
            LOG.exception('Unexpected error during upload')
500
            raise errors.RadioError('Unexpected error communicating '
501
                                    'with the radio')
502

    
503
    def _is_txinh(self, _mem):
504
        raw_tx = ""
505
        for i in range(0, 4):
506
            raw_tx += _mem.txfreq[i].get_raw()
507
        return raw_tx == "\xFF\xFF\xFF\xFF"
508

    
509
    def get_memory(self, number):
510
        """Get the mem representation from the radio image"""
511
        _mem = self._memobj.memory[number - 1]
512

    
513
        # Create a high-level memory object to return to the UI
514
        mem = chirp_common.Memory()
515

    
516
        # Memory number
517
        mem.number = number
518

    
519
        if _mem.get_raw()[0] == "\xff":
520
            mem.empty = True
521
            return mem
522

    
523
        # Freq and offset
524
        mem.freq = int(_mem.rxfreq) * 10
525
        # tx freq can be blank
526
        if _mem.get_raw()[4] == "\xFF":
527
            # TX freq not set
528
            mem.offset = 0
529
            mem.duplex = "off"
530
        else:
531
            # TX freq set
532
            offset = (int(_mem.txfreq) * 10) - mem.freq
533
            if offset != 0:
534
                if _split(self.get_features(), mem.freq, int(
535
                          _mem.txfreq) * 10):
536
                    mem.duplex = "split"
537
                    mem.offset = int(_mem.txfreq) * 10
538
                elif offset < 0:
539
                    mem.offset = abs(offset)
540
                    mem.duplex = "-"
541
                elif offset > 0:
542
                    mem.offset = offset
543
                    mem.duplex = "+"
544
            else:
545
                mem.offset = 0
546

    
547
        for char in _mem.name:
548
            if str(char) == "\xFF":
549
                char = " "  # may have 0xFF mid-name
550
            mem.name += str(char)
551
        mem.name = mem.name.rstrip()
552

    
553
        dtcs_pol = ["N", "N"]
554

    
555
        if _mem.txtone in [0, 0xFFFF]:
556
            txmode = ""
557
        elif _mem.txtone >= 0x0258:
558
            txmode = "Tone"
559
            mem.rtone = int(_mem.txtone) / 10.0
560
        elif _mem.txtone <= 0x0258:
561
            txmode = "DTCS"
562
            if _mem.txtone > 0x69:
563
                index = _mem.txtone - 0x6A
564
                dtcs_pol[0] = "R"
565
            else:
566
                index = _mem.txtone - 1
567
            mem.dtcs = DTCS[index]
568
        else:
569
            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
570

    
571
        if _mem.rxtone in [0, 0xFFFF]:
572
            rxmode = ""
573
        elif _mem.rxtone >= 0x0258:
574
            rxmode = "Tone"
575
            mem.ctone = int(_mem.rxtone) / 10.0
576
        elif _mem.rxtone <= 0x0258:
577
            rxmode = "DTCS"
578
            if _mem.rxtone >= 0x6A:
579
                index = _mem.rxtone - 0x6A
580
                dtcs_pol[1] = "R"
581
            else:
582
                index = _mem.rxtone - 1
583
            mem.rx_dtcs = DTCS[index]
584
        else:
585
            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
586

    
587
        if txmode == "Tone" and not rxmode:
588
            mem.tmode = "Tone"
589
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
590
            mem.tmode = "TSQL"
591
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
592
            mem.tmode = "DTCS"
593
        elif rxmode or txmode:
594
            mem.tmode = "Cross"
595
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
596

    
597
        mem.dtcs_polarity = "".join(dtcs_pol)
598

    
599
        if not _mem.scan:
600
            mem.skip = "S"
601

    
602
        _levels = self.POWER_LEVELS
603
        if _mem.txpower == TXPOWER_HIGH:
604
            mem.power = _levels[0]
605
        elif _mem.txpower == TXPOWER_MID:
606
            mem.power = _levels[1]
607
        elif _mem.txpower == TXPOWER_LOW:
608
            mem.power = _levels[2]
609
        else:
610
            LOG.error('%s: get_mem: unhandled power level: 0x%02x' %
611
                      (mem.name, _mem.txpower))
612

    
613
        mem.mode = _mem.narrow and "NFM" or "FM"
614

    
615
        mem.extra = RadioSettingGroup("Extra", "extra")
616

    
617
        # BCL (Busy Channel Lockout)
618
        rs = RadioSettingValueBoolean(_mem.bcl)
619
        rset = RadioSetting("bcl", "BCL", rs)
620
        mem.extra.append(rset)
621

    
622
        # PTT-ID
623
        rs = RadioSettingValueList(PTTID_LIST, PTTID_LIST[_mem.pttid])
624
        rset = RadioSetting("pttid", "PTT ID", rs)
625
        mem.extra.append(rset)
626

    
627
        # Signal (DTMF Encoder Group #)
628
        rs = RadioSettingValueList(PTTIDCODE_LIST, PTTIDCODE_LIST[_mem.scode])
629
        rset = RadioSetting("scode", "PTT ID Code", rs)
630
        mem.extra.append(rset)
631

    
632
        return mem
633

    
634
    def set_memory(self, mem):
635
        _mem = self._memobj.memory[mem.number - 1]
636

    
637
        if mem.empty:
638
            _mem.set_raw("\xff" * 32)
639
            return
640

    
641
        _mem.set_raw("\x00" * 16 + "\xFF" * 16)
642

    
643
        _mem.rxfreq = mem.freq / 10
644

    
645
        if mem.duplex == "off":
646
            for i in range(0, 4):
647
                _mem.txfreq[i].set_raw("\xFF")
648
        elif mem.duplex == "split":
649
            _mem.txfreq = mem.offset / 10
650
        elif mem.duplex == "+":
651
            _mem.txfreq = (mem.freq + mem.offset) / 10
652
        elif mem.duplex == "-":
653
            _mem.txfreq = (mem.freq - mem.offset) / 10
654
        else:
655
            _mem.txfreq = mem.freq / 10
656

    
657
        _namelength = self.get_features().valid_name_length
658
        for i in range(_namelength):
659
            try:
660
                _mem.name[i] = mem.name[i]
661
            except IndexError:
662
                _mem.name[i] = "\xFF"
663

    
664
        rxmode = txmode = ""
665
        if mem.tmode == "Tone":
666
            _mem.txtone = int(mem.rtone * 10)
667
            _mem.rxtone = 0
668
        elif mem.tmode == "TSQL":
669
            _mem.txtone = int(mem.ctone * 10)
670
            _mem.rxtone = int(mem.ctone * 10)
671
        elif mem.tmode == "DTCS":
672
            rxmode = txmode = "DTCS"
673
            _mem.txtone = DTCS.index(mem.dtcs) + 1
674
            _mem.rxtone = DTCS.index(mem.dtcs) + 1
675
        elif mem.tmode == "Cross":
676
            txmode, rxmode = mem.cross_mode.split("->", 1)
677
            if txmode == "Tone":
678
                _mem.txtone = int(mem.rtone * 10)
679
            elif txmode == "DTCS":
680
                _mem.txtone = DTCS.index(mem.dtcs) + 1
681
            else:
682
                _mem.txtone = 0
683
            if rxmode == "Tone":
684
                _mem.rxtone = int(mem.ctone * 10)
685
            elif rxmode == "DTCS":
686
                _mem.rxtone = DTCS.index(mem.rx_dtcs) + 1
687
            else:
688
                _mem.rxtone = 0
689
        else:
690
            _mem.rxtone = 0
691
            _mem.txtone = 0
692

    
693
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
694
            _mem.txtone += 0x69
695
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
696
            _mem.rxtone += 0x69
697

    
698
        _mem.scan = mem.skip != "S"
699
        _mem.narrow = mem.mode == "NFM"
700

    
701
        _levels = self.POWER_LEVELS
702
        if mem.power is None:
703
            _mem.txpower = TXPOWER_HIGH
704
        elif mem.power == _levels[0]:
705
            _mem.txpower = TXPOWER_HIGH
706
        elif mem.power == _levels[1]:
707
            _mem.txpower = TXPOWER_MID
708
        elif mem.power == _levels[2]:
709
            _mem.txpower = TXPOWER_LOW
710
        else:
711
            LOG.error('%s: set_mem: unhandled power level: %s' %
712
                      (mem.name, mem.power))
713

    
714
        for setting in mem.extra:
715
            if setting.get_name() == "scramble_type":
716
                setattr(_mem, setting.get_name(), int(setting.value) + 8)
717
                setattr(_mem, "scramble_type2", int(setting.value) + 8)
718
            else:
719
                setattr(_mem, setting.get_name(), setting.value)
720

    
721
    def get_settings(self):
722
        _dtmf = self._memobj.dtmf
723
        _settings = self._memobj.settings
724
        basic = RadioSettingGroup("basic", "Basic Settings")
725
        group = RadioSettings(basic)
726

    
727
        # Menu 12: TOT
728
        rs = RadioSettingValueList(TOT_LIST, TOT_LIST[_settings.tot])
729
        rset = RadioSetting("tot", "Time Out Timer", rs)
730
        basic.append(rset)
731

    
732
        # Menu 00: SQL
733
        rs = RadioSettingValueInteger(0, 9, _settings.sql)
734
        rset = RadioSetting("sql", "Squelch Level", rs)
735
        basic.append(rset)
736

    
737
        # Menu 13: VOX
738
        rs = RadioSettingValueList(OFF1TO9_LIST, OFF1TO9_LIST[_settings.vox])
739
        rset = RadioSetting("vox", "VOX", rs)
740
        basic.append(rset)
741

    
742
        # Menu 39: VOX DELAY
743
        rs = RadioSettingValueList(VOXD_LIST, VOXD_LIST[_settings.voxd])
744
        rset = RadioSetting("voxd", "VOX Delay", rs)
745
        basic.append(rset)
746

    
747
        # Menu 15: VOICE
748
        rs = RadioSettingValueBoolean(_settings.voice)
749
        rset = RadioSetting("voice", "Voice Prompts", rs)
750
        basic.append(rset)
751

    
752
        # Menu 17: LANGUAGE
753
        rs = RadioSettingValueList(LANGUAGE_LIST,
754
                                   LANGUAGE_LIST[_settings.language])
755
        rset = RadioSetting("language", "Voice", rs)
756
        basic.append(rset)
757

    
758
        # Menu 23: ABR
759
        rs = RadioSettingValueList(ABR_LIST, ABR_LIST[_settings.abr])
760
        rset = RadioSetting("abr", "Auto BackLight", rs)
761
        basic.append(rset)
762

    
763
        # Work Mode A
764
        rs = RadioSettingValueList(WORKMODE_LIST,
765
                                   WORKMODE_LIST[_settings.vfomra])
766
        rset = RadioSetting("vfomra", "Work Mode A", rs)
767
        basic.append(rset)
768

    
769
        # Work Mode B
770
        rs = RadioSettingValueList(WORKMODE_LIST,
771
                                   WORKMODE_LIST[_settings.vfomrb])
772
        rset = RadioSetting("vfomrb", "Work Mode B", rs)
773
        basic.append(rset)
774

    
775
        # Menu 19: SC-REV
776
        rs = RadioSettingValueList(SCREV_LIST, SCREV_LIST[_settings.screv])
777
        rset = RadioSetting("screv", "Scan Resume Method", rs)
778
        basic.append(rset)
779

    
780
        # Menu 10: SAVE
781
        rs = RadioSettingValueList(SAVE_LIST,
782
                                   SAVE_LIST[_settings.save])
783
        rset = RadioSetting("save", "Battery Save Mode", rs)
784
        basic.append(rset)
785

    
786
        # Menu 42: MDF-A
787
        rs = RadioSettingValueList(MDF_LIST, MDF_LIST[_settings.mdfa])
788
        rset = RadioSetting("mdfa", "Memory Display Format A", rs)
789
        basic.append(rset)
790

    
791
        # Menu 43: MDF-B
792
        rs = RadioSettingValueList(MDF_LIST, MDF_LIST[_settings.mdfb])
793
        rset = RadioSetting("mdfb", "Memory Display Format B", rs)
794
        basic.append(rset)
795

    
796
        # Menu 33: DTMFST (DTMF ST)
797
        rs = RadioSettingValueList(DTMFST_LIST, DTMFST_LIST[_settings.dtmfst])
798
        rset = RadioSetting("dtmfst", "DTMF Side Tone", rs)
799
        basic.append(rset)
800

    
801
        # Menu 37: PTT-LT
802
        rs = RadioSettingValueList(PTTLT_LIST, PTTLT_LIST[_settings.pttlt])
803
        rset = RadioSetting("pttlt", "PTT Delay", rs)
804
        basic.append(rset)
805

    
806
        rs = RadioSettingValueInteger(1, 60, _settings.ani + 1)
807
        rset = RadioSetting("ani", "ANI", rs)
808
        basic.append(rset)
809

    
810
        # Menu 20: PF2
811
        def apply_skey2s_listvalue(setting, obj):
812
            LOG.debug("Setting value: " + str(setting.value) + " from list")
813
            val = str(setting.value)
814
            index = SKEY2S_CHOICES.index(val)
815
            val = SKEY2S_VALUES[index]
816
            obj.set_value(val)
817

    
818
        if self.MODEL in ["RT-470"]:
819
            unwanted = [0, 7, 9]
820
        elif self.MODEL in ["RT-470L"]:
821
            unwanted = [9]
822
        else:
823
            unwanted = []
824
        SKEY2S_CHOICES = ALL_SKEY_CHOICES.copy()
825
        SKEY2S_VALUES = ALL_SKEY_VALUES.copy()
826
        for ele in sorted(unwanted, reverse=True):
827
            del SKEY2S_CHOICES[ele]
828
            del SKEY2S_VALUES[ele]
829

    
830
        if _settings.skey2_sp in SKEY2S_VALUES:
831
            idx = SKEY2S_VALUES.index(_settings.skey2_sp)
832
        else:
833
            idx = SKEY2S_VALUES.index(0x07)  # default FM
834
        rs = RadioSettingValueList(SKEY2S_CHOICES, SKEY2S_CHOICES[idx])
835
        rset = RadioSetting("skey2_sp", "PF2 Key (Short Press)", rs)
836
        rset.set_apply_callback(apply_skey2s_listvalue, _settings.skey2_sp)
837
        basic.append(rset)
838

    
839
        # Menu 21: PF2 LONG PRESS
840
        def apply_skey2l_listvalue(setting, obj):
841
            LOG.debug("Setting value: " + str(setting.value) + " from list")
842
            val = str(setting.value)
843
            index = SKEY2L_CHOICES.index(val)
844
            val = SKEY2L_VALUES[index]
845
            obj.set_value(val)
846

    
847
        if self.MODEL in ["RT-470"]:
848
            unwanted = [0, 7, 8, 9]
849
        elif self.MODEL in ["RT-470L"]:
850
            unwanted = [8, 9]
851
        else:
852
            unwanted = []
853
        SKEY2L_CHOICES = ALL_SKEY_CHOICES.copy()
854
        SKEY2L_VALUES = ALL_SKEY_VALUES.copy()
855
        for ele in sorted(unwanted, reverse=True):
856
            del SKEY2L_CHOICES[ele]
857
            del SKEY2L_VALUES[ele]
858

    
859
        if _settings.skey2_lp in SKEY2L_VALUES:
860
            idx = SKEY2L_VALUES.index(_settings.skey2_lp)
861
        else:
862
            idx = SKEY2L_VALUES.index(0x1D)  # default Search
863
        rs = RadioSettingValueList(SKEY2L_CHOICES, SKEY2L_CHOICES[idx])
864
        rset = RadioSetting("skey2_lp", "PF2 Key (Long Press)", rs)
865
        rset.set_apply_callback(apply_skey2l_listvalue, _settings.skey2_lp)
866
        basic.append(rset)
867

    
868
        # Menu 22: PF3
869
        def apply_skey3s_listvalue(setting, obj):
870
            LOG.debug("Setting value: " + str(setting.value) + " from list")
871
            val = str(setting.value)
872
            index = SKEY3S_CHOICES.index(val)
873
            val = SKEY2S_VALUES[index]
874
            obj.set_value(val)
875

    
876
        if self.MODEL in ["RT-470"]:
877
            unwanted = [0, 7, 8, 9]
878
        elif self.MODEL in ["RT-470L"]:
879
            unwanted = [8, 9]
880
        else:
881
            unwanted = []
882
        SKEY3S_CHOICES = ALL_SKEY_CHOICES.copy()
883
        SKEY3S_VALUES = ALL_SKEY_VALUES.copy()
884
        for ele in sorted(unwanted, reverse=True):
885
            del SKEY3S_CHOICES[ele]
886
            del SKEY3S_VALUES[ele]
887

    
888
        if _settings.skey3_sp in SKEY3S_VALUES:
889
            idx = SKEY3S_VALUES.index(_settings.skey3_sp)
890
        else:
891
            idx = SKEY3S_VALUES.index(0x0C)  # default NOAA
892
        rs = RadioSettingValueList(SKEY3S_CHOICES, SKEY3S_CHOICES[idx])
893
        rset = RadioSetting("skey3_sp", "PF3 Key (Short Press)", rs)
894
        rset.set_apply_callback(apply_skey3s_listvalue, _settings.skey3_sp)
895
        basic.append(rset)
896

    
897
        if self.MODEL in ["RT-470L"]:
898
            # Menu 24: PF3 LONG PRESS (RT-470L)
899
            def apply_skey3l_listvalue(setting, obj):
900
                LOG.debug("Setting value: " + str(setting.value) +
901
                          " from list")
902
                val = str(setting.value)
903
                index = SKEY3L_CHOICES.index(val)
904
                val = SKEY2L_VALUES[index]
905
                obj.set_value(val)
906

    
907
            if self.MODEL in ["RT-470L"]:
908
                unwanted = [8, 9]
909
            else:
910
                unwanted = []
911
            SKEY3L_CHOICES = ALL_SKEY_CHOICES.copy()
912
            SKEY3L_VALUES = ALL_SKEY_VALUES.copy()
913
            for ele in sorted(unwanted, reverse=True):
914
                del SKEY3L_CHOICES[ele]
915
                del SKEY3L_VALUES[ele]
916

    
917
            if _settings.skey3_lp in SKEY3L_VALUES:
918
                idx = SKEY3L_VALUES.index(_settings.skey3_lp)
919
            else:
920
                idx = SKEY3L_VALUES.index(0x1D)  # default SEARCH
921
            rs = RadioSettingValueList(SKEY3L_CHOICES, SKEY3L_CHOICES[idx])
922
            rset = RadioSetting("skey3_lp", "PF3 Key (Long Press)", rs)
923
            rset.set_apply_callback(apply_skey3l_listvalue,
924
                                    _settings.skey3_lp)
925
            basic.append(rset)
926

    
927
        if self.MODEL in ["RT-470L"]:
928
            # Menu 25: TOP KEY (RT-470L)
929
            def apply_skeytop_listvalue(setting, obj):
930
                LOG.debug("Setting value: " + str(setting.value) +
931
                          " from list")
932
                val = str(setting.value)
933
                index = SKEYTOP_CHOICES.index(val)
934
                val = SKEYTOP_VALUES[index]
935
                obj.set_value(val)
936

    
937
            if self.MODEL in ["RT-470L"]:
938
                unwanted = [8, 9]
939
            else:
940
                unwanted = []
941
            SKEYTOP_CHOICES = ALL_SKEY_CHOICES.copy()
942
            SKEYTOP_VALUES = ALL_SKEY_VALUES.copy()
943
            for ele in sorted(unwanted, reverse=True):
944
                del SKEYTOP_CHOICES[ele]
945
                del SKEYTOP_VALUES[ele]
946

    
947
            if _settings.topkey_sp in SKEYTOP_VALUES:
948
                idx = SKEYTOP_VALUES.index(_settings.topkey_sp)
949
            else:
950
                idx = SKEYTOP_VALUES.index(0x1D)  # default SEARCH
951
            rs = RadioSettingValueList(SKEYTOP_CHOICES, SKEYTOP_CHOICES[idx])
952
            rset = RadioSetting("topkey_sp", "Top Key (Short Press)", rs)
953
            rset.set_apply_callback(apply_skeytop_listvalue,
954
                                    _settings.topkey_sp)
955
            basic.append(rset)
956

    
957
        # Mneu 36: TONE
958
        rs = RadioSettingValueList(TONE_LIST, TONE_LIST[_settings.tone])
959
        rset = RadioSetting("tone", "Tone-burst Frequency", rs)
960
        basic.append(rset)
961

    
962
        # Mneu 29: POWER ON MSG
963
        rs = RadioSettingValueList(PONMSG_LIST, PONMSG_LIST[_settings.ponmsg])
964
        rset = RadioSetting("ponmsg", "Power On Message", rs)
965
        basic.append(rset)
966

    
967
        if self.MODEL == "RT-470L":
968
            rs = RadioSettingValueList(TAILCODE_LIST,
969
                                       TAILCODE_LIST[_settings.tailcode])
970
            rset = RadioSetting("tailcode", "Tail Code", rs)
971
            basic.append(rset)
972

    
973
        # Menu 46: STE
974
        rs = RadioSettingValueBoolean(_settings.ste)
975
        rset = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)", rs)
976
        basic.append(rset)
977

    
978
        # Menu 40: RP-STE
979
        rs = RadioSettingValueList(RPSTE_LIST, RPSTE_LIST[_settings.rpste])
980
        rset = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)", rs)
981
        basic.append(rset)
982

    
983
        # Menu 41: RPT-RL
984
        rs = RadioSettingValueList(RPSTE_LIST, RPSTE_LIST[_settings.rptrl])
985
        rset = RadioSetting("rptrl", "STE Repeater Delay", rs)
986
        basic.append(rset)
987

    
988
        # Menu 38: MENU EXIT TIME
989
        rs = RadioSettingValueList(MENUQUIT_LIST,
990
                                   MENUQUIT_LIST[_settings.menuquit])
991
        rset = RadioSetting("menuquit", "Menu Auto Quit", rs)
992
        basic.append(rset)
993

    
994
        # Menu 34: AUTOLOCK
995
        rs = RadioSettingValueList(AUTOLK_LIST, AUTOLK_LIST[_settings.autolk])
996
        rset = RadioSetting("autolk", "Key Auto Lock", rs)
997
        basic.append(rset)
998

    
999
        # Menu 28: CDCSS SAVE MODE
1000
        rs = RadioSettingValueList(QTSAVE_LIST, QTSAVE_LIST[_settings.qtsave])
1001
        rset = RadioSetting("qtsave", "QT Save Type", rs)
1002
        basic.append(rset)
1003

    
1004
        # Menu 45: TX-A/B
1005
        rs = RadioSettingValueList(DUALTX_LIST, DUALTX_LIST[_settings.dualtx])
1006
        rset = RadioSetting("dualtx", "Dual TX", rs)
1007
        basic.append(rset)
1008

    
1009
        # Menu 47: AL-MODE
1010
        rs = RadioSettingValueList(ALMODE_LIST, ALMODE_LIST[_settings.almode])
1011
        rset = RadioSetting("almode", "Alarm Mode", rs)
1012
        basic.append(rset)
1013

    
1014
        # Menu 11: ROGER
1015
        rs = RadioSettingValueBoolean(_settings.roger)
1016
        rset = RadioSetting("roger", "Roger", rs)
1017
        basic.append(rset)
1018

    
1019
        rs = RadioSettingValueBoolean(_settings.alarmsound)
1020
        rset = RadioSetting("alarmsound", "Alarm Sound", rs)
1021
        basic.append(rset)
1022

    
1023
        # Menu 44: TDR
1024
        rs = RadioSettingValueBoolean(_settings.tdr)
1025
        rset = RadioSetting("tdr", "TDR", rs)
1026
        basic.append(rset)
1027

    
1028
        rs = RadioSettingValueBoolean(not _settings.fmradio)
1029
        rset = RadioSetting("fmradio", "FM Radio", rs)
1030
        basic.append(rset)
1031

    
1032
        rs = RadioSettingValueBoolean(_settings.kblock)
1033
        rset = RadioSetting("kblock", "KB Lock", rs)
1034
        basic.append(rset)
1035

    
1036
        # Menu 16: BEEP PROMPT
1037
        rs = RadioSettingValueBoolean(_settings.beep)
1038
        rset = RadioSetting("beep", "Beep", rs)
1039
        basic.append(rset)
1040

    
1041
        # Menu 48: RX END TAIL
1042
        rs = RadioSettingValueList(TONERXEND_LIST,
1043
                                   TONERXEND_LIST[_settings.rxendtail])
1044
        rset = RadioSetting("rxendtail", "Tone RX End", rs)
1045
        basic.append(rset)
1046

    
1047
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1048
        group.append(dtmf)
1049

    
1050
        def apply_code(setting, obj, length):
1051
            code = []
1052
            for j in range(0, length):
1053
                try:
1054
                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
1055
                except IndexError:
1056
                    code.append(0xFF)
1057
            obj.code = code
1058

    
1059
        for i in range(0, 15):
1060
            _codeobj = self._memobj.pttid[i].code
1061
            _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
1062
            rs = RadioSettingValueString(0, 5, _code, False)
1063
            rs.set_charset(DTMF_CHARS)
1064
            rset = RadioSetting("pttid/%i.code" % i,
1065
                                "PTT-ID Code %i" % (i + 1), rs)
1066
            rset.set_apply_callback(apply_code, self._memobj.pttid[i], 5)
1067
            dtmf.append(rset)
1068

    
1069
        rs = RadioSettingValueList(DTMFSPEED_LIST,
1070
                                   DTMFSPEED_LIST[_dtmf.dtmfon])
1071
        rset = RadioSetting("dtmf.dtmfon", "DTMF Speed (on)", rs)
1072
        dtmf.append(rset)
1073

    
1074
        rs = RadioSettingValueList(DTMFSPEED_LIST,
1075
                                   DTMFSPEED_LIST[_dtmf.dtmfoff])
1076
        rset = RadioSetting("dtmf.dtmfoff", "DTMF Speed (off)", rs)
1077
        dtmf.append(rset)
1078

    
1079
        rs = RadioSettingValueList(PTTID_LIST, PTTID_LIST[_dtmf.pttid])
1080
        rset = RadioSetting("dtmf.pttid", "PTT ID", rs)
1081
        dtmf.append(rset)
1082

    
1083
        ani = RadioSettingGroup("ani", "ANI Code List Settings")
1084
        group.append(ani)
1085

    
1086
        def _filter(name):
1087
            filtered = ""
1088
            for char in str(name):
1089
                if char in self._valid_chars:
1090
                    filtered += char
1091
                else:
1092
                    filtered += " "
1093
            return filtered
1094

    
1095
        for i in range(0, 30):
1096
            _codeobj = self._memobj.anicodes[i].code
1097
            _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
1098
            rs = RadioSettingValueString(0, 3, _code, False)
1099
            rs.set_charset(DTMF_CHARS)
1100
            rset = RadioSetting("anicodes/%i.code" % i,
1101
                                "ANI Code %i" % (i + 1), rs)
1102
            rset.set_apply_callback(apply_code, self._memobj.anicodes[i], 3)
1103
            ani.append(rset)
1104

    
1105
            _nameobj = "NUM.%i" % (i + 1)
1106
            rs = RadioSettingValueString(0, 6, _nameobj)
1107
            rs.set_mutable(False)
1108
            rset = RadioSetting("aninames/%i.code" % i,
1109
                                "ANI Code %i Name" % (i + 1), rs)
1110
            ani.append(rset)
1111

    
1112
        for i in range(0, 30):
1113
            _codeobj = self._memobj.anicodes[i + 30].code
1114
            _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
1115
            rs = RadioSettingValueString(0, 3, _code, False)
1116
            rs.set_charset(DTMF_CHARS)
1117
            rset = RadioSetting("anicodes/%i.code" % (i + 30),
1118
                                "ANI Code %i" % (i + 31), rs)
1119
            rset.set_apply_callback(apply_code,
1120
                                    self._memobj.anicodes[i + 30], 3)
1121
            ani.append(rset)
1122

    
1123
            _nameobj = self._memobj.aninames[i].name
1124
            rs = RadioSettingValueString(0, 6, _filter(_nameobj))
1125
            rset = RadioSetting("aninames/%i.name" % i,
1126
                                "ANI Code %i Name" % (i + 31), rs)
1127
            ani.append(rset)
1128

    
1129
        return group
1130

    
1131
    def set_settings(self, settings):
1132
        _settings = self._memobj.settings
1133
        for element in settings:
1134
            if not isinstance(element, RadioSetting):
1135
                self.set_settings(element)
1136
                continue
1137
            else:
1138
                try:
1139
                    name = element.get_name()
1140
                    if "." in name:
1141
                        bits = name.split(".")
1142
                        obj = self._memobj
1143
                        for bit in bits[:-1]:
1144
                            if "/" in bit:
1145
                                bit, index = bit.split("/", 1)
1146
                                index = int(index)
1147
                                obj = getattr(obj, bit)[index]
1148
                            else:
1149
                                obj = getattr(obj, bit)
1150
                        setting = bits[-1]
1151
                    else:
1152
                        obj = _settings
1153
                        setting = element.get_name()
1154

    
1155
                    if element.has_apply_callback():
1156
                        LOG.debug("Using apply callback")
1157
                        element.run_apply_callback()
1158
                    elif setting == "ani":
1159
                        setattr(obj, setting, int(element.value) - 1)
1160
                    elif setting == "fmradio":
1161
                        setattr(obj, setting, not int(element.value))
1162
                    elif element.value.get_mutable():
1163
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1164
                        setattr(obj, setting, element.value)
1165
                except Exception as e:
1166
                    LOG.debug(element.get_name(), e)
1167
                    raise
1168

    
1169
    @classmethod
1170
    def match_model(cls, filedata, filename):
1171
        # This radio has always been post-metadata, so never do
1172
        # old-school detection
1173
        return False
1174

    
1175

    
1176
@directory.register
1177
class RT470Radio(JC8810base):
1178
    """Radtel RT-470"""
1179
    VENDOR = "Radtel"
1180
    MODEL = "RT-470"
1181

    
1182

    
1183
@directory.register
1184
class RT470LRadio(JC8810base):
1185
    """Radtel RT-470L"""
1186
    VENDOR = "Radtel"
1187
    MODEL = "RT-470L"
1188

    
1189
    _fingerprint = [b"\x00\x00\x00\xfe\x00\x20\xAC\x04",
1190
                    b"\x00\x00\x00\x20\x00\x20\xCC\x04"]
1191

    
1192
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
1193
                    chirp_common.PowerLevel("M", watts=4.00),
1194
                    chirp_common.PowerLevel("L", watts=2.00)]
1195

    
1196
    VALID_BANDS = [(108000000, 136000000),
1197
                   (136000000, 179000000),
1198
                   (220000000, 260000000),
1199
                   (330000000, 400000000),
1200
                   (400000000, 520000000)]
(4-4/4)