Project

General

Profile

Bug #10860 » mml_jc8810_a37plus_v1_22.py

Jim Unroe, 09/23/2023 10:39 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[%d];
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:6,   // 900D
92
     mdfa:2;          //      Channel_A Display
93
  u8 unused_9003:6,   // 900E
94
     mdfb:2;          //      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:6,   // 9017
111
     roger:2;         //      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:6,   // 902F
147
     rxendtail:2;     //      RX END TAIL (RT-470)
148
                      //      TAIL PHASE (A36plus)
149
  u8 skey3_lp;        // 9030 Skey3 Long (RT-470L)
150
                      //      RX END TAIL (A36plus)
151
} settings;
152

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

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

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

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

    
181

    
182
CMD_ACK = b"\x06"
183

    
184
DTCS = tuple(sorted(chirp_common.DTCS_CODES + (645,)))
185

    
186
DTMF_CHARS = "0123456789 *#ABCD"
187

    
188
TXPOWER_HIGH = 0x00
189
TXPOWER_LOW = 0x01
190
TXPOWER_MID = 0x02
191

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

    
223
ALL_SKEY_CHOICES = ["OFF",
224
                    "FM Radio",
225
                    "TX Power Level",
226
                    "Scan",
227
                    "Search",
228
                    "Flashlight",
229
                    "NOAA Weather",
230
                    "Monitor",
231
                    "PTT B",
232
                    "SOS",
233
                    "DTMF",
234
                    "REVERSE",
235
                    "REMOTE Scan"]
236

    
237
ALL_SKEY_VALUES = [0xFF,
238
                   0x07,
239
                   0x0A,
240
                   0x1C,
241
                   0x1D,
242
                   0x08,
243
                   0x0C,
244
                   0x05,
245
                   0x01,
246
                   0x03,
247
                   0x2A,
248
                   0x2D,
249
                   0x23]
250

    
251

    
252
def _enter_programming_mode(radio):
253
    serial = radio.pipe
254

    
255
    exito = False
256
    for i in range(0, 5):
257
        serial.write(radio._magic)
258
        ack = serial.read(1)
259

    
260
        try:
261
            if ack == CMD_ACK:
262
                exito = True
263
                break
264
        except Exception:
265
            LOG.debug("Attempt #%s, failed, trying again" % i)
266
            pass
267

    
268
    # check if we had EXITO
269
    if exito is False:
270
        msg = "The radio did not accept program mode after five tries.\n"
271
        msg += "Check you interface cable and power cycle your radio."
272
        raise errors.RadioError(msg)
273

    
274
    try:
275
        serial.write(b"F")
276
        ident = serial.read(8)
277
    except Exception:
278
        raise errors.RadioError("Error communicating with radio")
279

    
280
    if ident not in radio._fingerprint:
281
        LOG.debug(util.hexprint(ident))
282
        raise errors.RadioError("Radio returned unknown identification string")
283

    
284

    
285
def _exit_programming_mode(radio):
286
    serial = radio.pipe
287
    try:
288
        serial.write(b"E")
289
    except Exception:
290
        raise errors.RadioError("Radio refused to exit programming mode")
291

    
292

    
293
def _read_block(radio, block_addr, block_size):
294
    serial = radio.pipe
295

    
296
    cmd = struct.pack(">cHb", b'R', block_addr, block_size)
297
    expectedresponse = b"R" + cmd[1:]
298
    LOG.debug("Reading block %04x..." % (block_addr))
299

    
300
    try:
301
        serial.write(cmd)
302
        response = serial.read(4 + block_size)
303
        if response[:4] != expectedresponse:
304
            raise Exception("Error reading block %04x." % (block_addr))
305

    
306
        block_data = response[4:]
307
    except Exception:
308
        raise errors.RadioError("Failed to read block at %04x" % block_addr)
309

    
310
    return block_data
311

    
312

    
313
def _write_block(radio, block_addr, block_size):
314
    serial = radio.pipe
315

    
316
    cmd = struct.pack(">cHb", b'W', block_addr, block_size)
317
    data = radio.get_mmap()[block_addr:block_addr + block_size]
318

    
319
    LOG.debug("Writing Data:")
320
    LOG.debug(util.hexprint(cmd + data))
321

    
322
    try:
323
        serial.write(cmd + data)
324
        if serial.read(1) != CMD_ACK:
325
            raise Exception("No ACK")
326
    except Exception:
327
        raise errors.RadioError("Failed to send block "
328
                                "to radio at %04x" % block_addr)
329

    
330

    
331
def do_download(radio):
332
    LOG.debug("download")
333
    _enter_programming_mode(radio)
334

    
335
    data = b""
336

    
337
    status = chirp_common.Status()
338
    status.msg = "Cloning from radio"
339

    
340
    status.cur = 0
341
    status.max = radio._memsize
342

    
343
    for addr in range(0, radio._memsize, radio.BLOCK_SIZE):
344
        status.cur = addr + radio.BLOCK_SIZE
345
        radio.status_fn(status)
346

    
347
        block = _read_block(radio, addr, radio.BLOCK_SIZE)
348
        data += block
349

    
350
        LOG.debug("Address: %04x" % addr)
351
        LOG.debug(util.hexprint(block))
352

    
353
    _exit_programming_mode(radio)
354

    
355
    return memmap.MemoryMapBytes(data)
356

    
357

    
358
def do_upload(radio):
359
    status = chirp_common.Status()
360
    status.msg = "Uploading to radio"
361

    
362
    _enter_programming_mode(radio)
363

    
364
    status.cur = 0
365
    status.max = radio._memsize
366

    
367
    for start_addr, end_addr in radio._ranges:
368
        for addr in range(start_addr, end_addr, radio.BLOCK_SIZE_UP):
369
            status.cur = addr + radio.BLOCK_SIZE_UP
370
            radio.status_fn(status)
371
            _write_block(radio, addr, radio.BLOCK_SIZE_UP)
372

    
373
    _exit_programming_mode(radio)
374

    
375

    
376
def _split(rf, f1, f2):
377
    """Returns False if the two freqs are in the same band (no split)
378
    or True otherwise"""
379

    
380
    # determine if the two freqs are in the same band
381
    for low, high in rf.valid_bands:
382
        if f1 >= low and f1 <= high and \
383
                f2 >= low and f2 <= high:
384
            # if the two freqs are on the same Band this is not a split
385
            return False
386

    
387
    # if you get here is because the freq pairs are split
388
    return True
389

    
390

    
391
class JC8810base(chirp_common.CloneModeRadio):
392
    """MML JC-8810"""
393
    VENDOR = "MML"
394
    MODEL = "JC-8810base"
395
    BAUD_RATE = 57600
396
    NEEDS_COMPAT_SERIAL = False
397
    BLOCK_SIZE = 0x40
398
    BLOCK_SIZE_UP = 0x40
399

    
400
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=10.00),
401
                    chirp_common.PowerLevel("M", watts=8.00),
402
                    chirp_common.PowerLevel("L", watts=4.00)]
403

    
404
    VALID_BANDS = [(108000000, 136000000),
405
                   (136000000, 180000000),
406
                   (200000000, 260000000),
407
                   (330000000, 400000000),
408
                   (400000000, 520000000)]
409

    
410
    _magic = b"PROGRAMJC81U"
411
    _fingerprint = [b"\x00\x00\x00\x26\x00\x20\xD8\x04",
412
                    b"\x00\x00\x00\x42\x00\x20\xF0\x04",
413
                    b"\x00\x00\x00\x4A\x00\x20\xF8\x04"]
414

    
415
    _ranges = [
416
               (0x0000, 0x2000),
417
               (0x8000, 0x8040),
418
               (0x9000, 0x9040),
419
               (0xA000, 0xA140),
420
               (0xB000, 0xB300)
421
              ]
422
    _memsize = 0xB300
423
    _upper = 256
424
    _mem_params = (_upper  # number of channels
425
                   )
426
    _valid_chars = chirp_common.CHARSET_ALPHANUMERIC + \
427
        "`~!@#$%^&*()-=_+[]\\{}|;':\",./<>?"
428

    
429
    def get_features(self):
430
        rf = chirp_common.RadioFeatures()
431
        rf.has_settings = True
432
        rf.has_bank = False
433
        rf.has_ctone = True
434
        rf.has_cross = True
435
        rf.has_rx_dtcs = True
436
        rf.has_tuning_step = False
437
        rf.can_odd_split = True
438
        rf.has_name = True
439
        rf.valid_name_length = 12
440
        rf.valid_characters = self._valid_chars
441
        rf.valid_skips = ["", "S"]
442
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
443
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
444
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
445
        rf.valid_power_levels = self.POWER_LEVELS
446
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
447
        rf.valid_modes = ["FM", "NFM"]  # 25 kHz, 12.5 kHz.
448
        rf.valid_dtcs_codes = DTCS
449
        rf.memory_bounds = (1, self._upper)
450
        rf.valid_tuning_steps = [2.5, 5., 6.25, 10., 12.5, 20., 25., 50.]
451
        rf.valid_bands = self.VALID_BANDS
452
        return rf
453

    
454
    def process_mmap(self):
455
        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
456

    
457
    def sync_in(self):
458
        """Download from radio"""
459
        try:
460
            data = do_download(self)
461
        except errors.RadioError:
462
            # Pass through any real errors we raise
463
            raise
464
        except Exception:
465
            # If anything unexpected happens, make sure we raise
466
            # a RadioError and log the problem
467
            LOG.exception('Unexpected error during download')
468
            raise errors.RadioError('Unexpected error communicating '
469
                                    'with the radio')
470
        self._mmap = data
471
        self.process_mmap()
472

    
473
    def sync_out(self):
474
        """Upload to radio"""
475
        try:
476
            do_upload(self)
477
        except Exception:
478
            # If anything unexpected happens, make sure we raise
479
            # a RadioError and log the problem
480
            LOG.exception('Unexpected error during upload')
481
            raise errors.RadioError('Unexpected error communicating '
482
                                    'with the radio')
483

    
484
    def _is_txinh(self, _mem):
485
        raw_tx = ""
486
        for i in range(0, 4):
487
            raw_tx += _mem.txfreq[i].get_raw()
488
        return raw_tx == "\xFF\xFF\xFF\xFF"
489

    
490
    def get_memory(self, number):
491
        """Get the mem representation from the radio image"""
492
        _mem = self._memobj.memory[number - 1]
493

    
494
        # Create a high-level memory object to return to the UI
495
        mem = chirp_common.Memory()
496

    
497
        # Memory number
498
        mem.number = number
499

    
500
        if _mem.get_raw()[0] == "\xff":
501
            mem.empty = True
502
            return mem
503

    
504
        # Freq and offset
505
        mem.freq = int(_mem.rxfreq) * 10
506
        # tx freq can be blank
507
        if _mem.get_raw()[4] == "\xFF":
508
            # TX freq not set
509
            mem.offset = 0
510
            mem.duplex = "off"
511
        else:
512
            # TX freq set
513
            offset = (int(_mem.txfreq) * 10) - mem.freq
514
            if offset != 0:
515
                if _split(self.get_features(), mem.freq, int(
516
                          _mem.txfreq) * 10):
517
                    mem.duplex = "split"
518
                    mem.offset = int(_mem.txfreq) * 10
519
                elif offset < 0:
520
                    mem.offset = abs(offset)
521
                    mem.duplex = "-"
522
                elif offset > 0:
523
                    mem.offset = offset
524
                    mem.duplex = "+"
525
            else:
526
                mem.offset = 0
527

    
528
        for char in _mem.name:
529
            if str(char) == "\xFF":
530
                char = " "  # may have 0xFF mid-name
531
            mem.name += str(char)
532
        mem.name = mem.name.rstrip()
533

    
534
        dtcs_pol = ["N", "N"]
535

    
536
        if _mem.txtone in [0, 0xFFFF]:
537
            txmode = ""
538
        elif _mem.txtone >= 0x0258:
539
            txmode = "Tone"
540
            mem.rtone = int(_mem.txtone) / 10.0
541
        elif _mem.txtone <= 0x0258:
542
            txmode = "DTCS"
543
            if _mem.txtone > 0x69:
544
                index = _mem.txtone - 0x6A
545
                dtcs_pol[0] = "R"
546
            else:
547
                index = _mem.txtone - 1
548
            mem.dtcs = DTCS[index]
549
        else:
550
            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
551

    
552
        if _mem.rxtone in [0, 0xFFFF]:
553
            rxmode = ""
554
        elif _mem.rxtone >= 0x0258:
555
            rxmode = "Tone"
556
            mem.ctone = int(_mem.rxtone) / 10.0
557
        elif _mem.rxtone <= 0x0258:
558
            rxmode = "DTCS"
559
            if _mem.rxtone >= 0x6A:
560
                index = _mem.rxtone - 0x6A
561
                dtcs_pol[1] = "R"
562
            else:
563
                index = _mem.rxtone - 1
564
            mem.rx_dtcs = DTCS[index]
565
        else:
566
            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
567

    
568
        if txmode == "Tone" and not rxmode:
569
            mem.tmode = "Tone"
570
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
571
            mem.tmode = "TSQL"
572
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
573
            mem.tmode = "DTCS"
574
        elif rxmode or txmode:
575
            mem.tmode = "Cross"
576
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
577

    
578
        mem.dtcs_polarity = "".join(dtcs_pol)
579

    
580
        if not _mem.scan:
581
            mem.skip = "S"
582

    
583
        _levels = self.POWER_LEVELS
584
        if self.MODEL in ["A36plus", "UV-A37", "AR-730"]:
585
            if _mem.txpower == TXPOWER_HIGH:
586
                mem.power = _levels[0]
587
            elif _mem.txpower == TXPOWER_LOW:
588
                mem.power = _levels[1]
589
            else:
590
                LOG.error('%s: get_mem: unhandled power level: 0x%02x' %
591
                          (mem.name, _mem.txpower))
592
        else:
593
            if _mem.txpower == TXPOWER_HIGH:
594
                mem.power = _levels[0]
595
            elif _mem.txpower == TXPOWER_MID:
596
                mem.power = _levels[1]
597
            elif _mem.txpower == TXPOWER_LOW:
598
                mem.power = _levels[2]
599
            else:
600
                LOG.error('%s: get_mem: unhandled power level: 0x%02x' %
601
                          (mem.name, _mem.txpower))
602

    
603
        mem.mode = _mem.narrow and "NFM" or "FM"
604

    
605
        mem.extra = RadioSettingGroup("Extra", "extra")
606

    
607
        # BCL (Busy Channel Lockout)
608
        rs = RadioSettingValueBoolean(_mem.bcl)
609
        rset = RadioSetting("bcl", "BCL", rs)
610
        mem.extra.append(rset)
611

    
612
        # PTT-ID
613
        rs = RadioSettingValueList(PTTID_LIST, PTTID_LIST[_mem.pttid])
614
        rset = RadioSetting("pttid", "PTT ID", rs)
615
        mem.extra.append(rset)
616

    
617
        # Signal (DTMF Encoder Group #)
618
        rs = RadioSettingValueList(PTTIDCODE_LIST, PTTIDCODE_LIST[_mem.scode])
619
        rset = RadioSetting("scode", "PTT ID Code", rs)
620
        mem.extra.append(rset)
621

    
622
        return mem
623

    
624
    def set_memory(self, mem):
625
        _mem = self._memobj.memory[mem.number - 1]
626

    
627
        if mem.empty:
628
            _mem.set_raw("\xff" * 32)
629
            return
630

    
631
        _mem.set_raw("\x00" * 16 + "\xFF" * 16)
632

    
633
        _mem.rxfreq = mem.freq / 10
634

    
635
        if mem.duplex == "off":
636
            for i in range(0, 4):
637
                _mem.txfreq[i].set_raw("\xFF")
638
        elif mem.duplex == "split":
639
            _mem.txfreq = mem.offset / 10
640
        elif mem.duplex == "+":
641
            _mem.txfreq = (mem.freq + mem.offset) / 10
642
        elif mem.duplex == "-":
643
            _mem.txfreq = (mem.freq - mem.offset) / 10
644
        else:
645
            _mem.txfreq = mem.freq / 10
646

    
647
        _namelength = self.get_features().valid_name_length
648
        for i in range(_namelength):
649
            try:
650
                _mem.name[i] = mem.name[i]
651
            except IndexError:
652
                _mem.name[i] = "\xFF"
653

    
654
        rxmode = txmode = ""
655
        if mem.tmode == "Tone":
656
            _mem.txtone = int(mem.rtone * 10)
657
            _mem.rxtone = 0
658
        elif mem.tmode == "TSQL":
659
            _mem.txtone = int(mem.ctone * 10)
660
            _mem.rxtone = int(mem.ctone * 10)
661
        elif mem.tmode == "DTCS":
662
            rxmode = txmode = "DTCS"
663
            _mem.txtone = DTCS.index(mem.dtcs) + 1
664
            _mem.rxtone = DTCS.index(mem.dtcs) + 1
665
        elif mem.tmode == "Cross":
666
            txmode, rxmode = mem.cross_mode.split("->", 1)
667
            if txmode == "Tone":
668
                _mem.txtone = int(mem.rtone * 10)
669
            elif txmode == "DTCS":
670
                _mem.txtone = DTCS.index(mem.dtcs) + 1
671
            else:
672
                _mem.txtone = 0
673
            if rxmode == "Tone":
674
                _mem.rxtone = int(mem.ctone * 10)
675
            elif rxmode == "DTCS":
676
                _mem.rxtone = DTCS.index(mem.rx_dtcs) + 1
677
            else:
678
                _mem.rxtone = 0
679
        else:
680
            _mem.rxtone = 0
681
            _mem.txtone = 0
682

    
683
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
684
            _mem.txtone += 0x69
685
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
686
            _mem.rxtone += 0x69
687

    
688
        _mem.scan = mem.skip != "S"
689
        _mem.narrow = mem.mode == "NFM"
690

    
691
        _levels = self.POWER_LEVELS
692
        if self.MODEL in ["A36plus", "UV-A37", "AR-730"]:
693
            if mem.power is None:
694
                _mem.txpower = TXPOWER_HIGH
695
            elif mem.power == _levels[0]:
696
                _mem.txpower = TXPOWER_HIGH
697
            elif mem.power == _levels[1]:
698
                _mem.txpower = TXPOWER_LOW
699
            else:
700
                LOG.error('%s: set_mem: unhandled power level: %s' %
701
                          (mem.name, mem.power))
702
        else:
703
            if mem.power is None:
704
                _mem.txpower = TXPOWER_HIGH
705
            elif mem.power == _levels[0]:
706
                _mem.txpower = TXPOWER_HIGH
707
            elif mem.power == _levels[1]:
708
                _mem.txpower = TXPOWER_MID
709
            elif mem.power == _levels[2]:
710
                _mem.txpower = TXPOWER_LOW
711
            else:
712
                LOG.error('%s: set_mem: unhandled power level: %s' %
713
                          (mem.name, mem.power))
714

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

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

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

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

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

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

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

    
753
        if self.MODEL not in ["A36plus", "UV-A37", "AR-730"]:
754
            # Menu 17: LANGUAGE
755
            rs = RadioSettingValueList(LANGUAGE_LIST,
756
                                       LANGUAGE_LIST[_settings.language])
757
            rset = RadioSetting("language", "Voice", rs)
758
            basic.append(rset)
759

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

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

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

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

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

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

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

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

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

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

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

    
820
        if self.MODEL in ["RT-470"]:
821
            unwanted = [0, 7, 9, 10, 11, 12]
822
        elif self.MODEL in ["HI-8811", "RT-470L", "RT-470X"]:
823
            unwanted = [9, 10, 11, 12]
824
        elif self.MODEL in ["UV-A37", "AR-730"]:
825
            unwanted = [0, 5, 7, 9, 10, 11, 12]
826
        elif self.MODEL in ["A36plus"]:
827
            unwanted = [0, 5, 7, 9, 10, 11]
828
        else:
829
            unwanted = []
830
        SKEY2S_CHOICES = ALL_SKEY_CHOICES.copy()
831
        SKEY2S_VALUES = ALL_SKEY_VALUES.copy()
832
        for ele in sorted(unwanted, reverse=True):
833
            del SKEY2S_CHOICES[ele]
834
            del SKEY2S_VALUES[ele]
835

    
836
        if _settings.skey2_sp in SKEY2S_VALUES:
837
            idx = SKEY2S_VALUES.index(_settings.skey2_sp)
838
        else:
839
            idx = SKEY2S_VALUES.index(0x07)  # default FM
840
        rs = RadioSettingValueList(SKEY2S_CHOICES, SKEY2S_CHOICES[idx])
841
        rset = RadioSetting("skey2_sp", "PF2 Key (Short Press)", rs)
842
        rset.set_apply_callback(apply_skey2s_listvalue, _settings.skey2_sp)
843
        basic.append(rset)
844

    
845
        # Menu 21: PF2 LONG PRESS
846
        def apply_skey2l_listvalue(setting, obj):
847
            LOG.debug("Setting value: " + str(setting.value) + " from list")
848
            val = str(setting.value)
849
            index = SKEY2L_CHOICES.index(val)
850
            val = SKEY2L_VALUES[index]
851
            obj.set_value(val)
852

    
853
        if self.MODEL in ["RT-470"]:
854
            unwanted = [0, 7, 8, 9, 10, 11, 12]
855
        elif self.MODEL in ["HI-8811", "RT-470L", "RT-470X"]:
856
            unwanted = [8, 9, 10, 11, 12]
857
        elif self.MODEL in ["UV-A37", "AR-730"]:
858
            unwanted = [0, 5, 7, 8, 10, 11, 12]
859
        elif self.MODEL in ["A36plus"]:
860
            unwanted = [0, 5, 7, 8, 11, 12]
861
        else:
862
            unwanted = []
863
        SKEY2L_CHOICES = ALL_SKEY_CHOICES.copy()
864
        SKEY2L_VALUES = ALL_SKEY_VALUES.copy()
865
        for ele in sorted(unwanted, reverse=True):
866
            del SKEY2L_CHOICES[ele]
867
            del SKEY2L_VALUES[ele]
868

    
869
        if _settings.skey2_lp in SKEY2L_VALUES:
870
            idx = SKEY2L_VALUES.index(_settings.skey2_lp)
871
        else:
872
            idx = SKEY2L_VALUES.index(0x1D)  # default Search
873
        rs = RadioSettingValueList(SKEY2L_CHOICES, SKEY2L_CHOICES[idx])
874
        rset = RadioSetting("skey2_lp", "PF2 Key (Long Press)", rs)
875
        rset.set_apply_callback(apply_skey2l_listvalue, _settings.skey2_lp)
876
        basic.append(rset)
877

    
878
        # Menu 22: PF3
879
        def apply_skey3s_listvalue(setting, obj):
880
            LOG.debug("Setting value: " + str(setting.value) + " from list")
881
            val = str(setting.value)
882
            index = SKEY3S_CHOICES.index(val)
883
            val = SKEY2S_VALUES[index]
884
            obj.set_value(val)
885

    
886
        if self.MODEL in ["RT-470"]:
887
            unwanted = [0, 7, 8, 9, 10, 11, 12]
888
        elif self.MODEL in ["HI-8811", "RT-470L", "RT-470X"]:
889
            unwanted = [8, 9, 10, 11, 12]
890
        elif self.MODEL in ["UV-A37", "AR-730"]:
891
            unwanted = [0, 5, 7, 8, 9, 10, 11, 12]
892
        elif self.MODEL in ["A36plus"]:
893
            unwanted = [0, 5, 7, 8, 11]
894
        else:
895
            unwanted = []
896
        SKEY3S_CHOICES = ALL_SKEY_CHOICES.copy()
897
        SKEY3S_VALUES = ALL_SKEY_VALUES.copy()
898
        for ele in sorted(unwanted, reverse=True):
899
            del SKEY3S_CHOICES[ele]
900
            del SKEY3S_VALUES[ele]
901

    
902
        if _settings.skey3_sp in SKEY3S_VALUES:
903
            idx = SKEY3S_VALUES.index(_settings.skey3_sp)
904
        else:
905
            idx = SKEY3S_VALUES.index(0x0C)  # default NOAA
906
        rs = RadioSettingValueList(SKEY3S_CHOICES, SKEY3S_CHOICES[idx])
907
        rset = RadioSetting("skey3_sp", "PF3 Key (Short Press)", rs)
908
        rset.set_apply_callback(apply_skey3s_listvalue, _settings.skey3_sp)
909
        basic.append(rset)
910

    
911
        if self.MODEL in ["HI-8811", "RT-470L", "RT-470X"]:
912
            # Menu 24: PF3 LONG PRESS (RT-470L)
913
            def apply_skey3l_listvalue(setting, obj):
914
                LOG.debug("Setting value: " + str(setting.value) +
915
                          " from list")
916
                val = str(setting.value)
917
                index = SKEY3L_CHOICES.index(val)
918
                val = SKEY2L_VALUES[index]
919
                obj.set_value(val)
920

    
921
            if self.MODEL in ["HI-8811", "RT-470L", "RT-470X"]:
922
                unwanted = [8, 9, 10, 11, 12]
923
            else:
924
                unwanted = []
925
            SKEY3L_CHOICES = ALL_SKEY_CHOICES.copy()
926
            SKEY3L_VALUES = ALL_SKEY_VALUES.copy()
927
            for ele in sorted(unwanted, reverse=True):
928
                del SKEY3L_CHOICES[ele]
929
                del SKEY3L_VALUES[ele]
930

    
931
            if _settings.skey3_lp in SKEY3L_VALUES:
932
                idx = SKEY3L_VALUES.index(_settings.skey3_lp)
933
            else:
934
                idx = SKEY3L_VALUES.index(0x1D)  # default SEARCH
935
            rs = RadioSettingValueList(SKEY3L_CHOICES, SKEY3L_CHOICES[idx])
936
            rset = RadioSetting("skey3_lp", "PF3 Key (Long Press)", rs)
937
            rset.set_apply_callback(apply_skey3l_listvalue,
938
                                    _settings.skey3_lp)
939
            basic.append(rset)
940

    
941
        # Menu 25: TOP KEY (RT-470L)
942
        def apply_skeytop_listvalue(setting, obj):
943
            LOG.debug("Setting value: " + str(setting.value) +
944
                      " from list")
945
            val = str(setting.value)
946
            index = SKEYTOP_CHOICES.index(val)
947
            val = SKEYTOP_VALUES[index]
948
            obj.set_value(val)
949

    
950
        if self.MODEL in ["RT-470"]:
951
            # ==========
952
            # Notice to developers:
953
            # The RT-470 v1.22 firmware added 'hidden' support for the
954
            # Top Key (Short Press) feature. RT-470 radios with a firmware
955
            # version prior to v1.22 will not honor the Top Key (Short
956
            # Press) setting in CHIRP.
957
            # ==========
958
            unwanted = [0, 7, 8, 9, 10, 11, 12]
959
        elif self.MODEL in ["HI-8811", "RT-470L", "RT-470X"]:
960
            unwanted = [8, 9, 10, 11, 12]
961
        elif self.MODEL in ["UV-A37", "AR-730"]:
962
            unwanted = [0, 5, 7, 8, 9, 10, 11, 12]
963
        elif self.MODEL in ["A36plus"]:
964
            unwanted = [0, 5, 7, 8, 11]
965
        else:
966
            unwanted = []
967
        SKEYTOP_CHOICES = ALL_SKEY_CHOICES.copy()
968
        SKEYTOP_VALUES = ALL_SKEY_VALUES.copy()
969
        for ele in sorted(unwanted, reverse=True):
970
            del SKEYTOP_CHOICES[ele]
971
            del SKEYTOP_VALUES[ele]
972

    
973
        if _settings.topkey_sp in SKEYTOP_VALUES:
974
            idx = SKEYTOP_VALUES.index(_settings.topkey_sp)
975
        else:
976
            idx = SKEYTOP_VALUES.index(0x1D)  # default SEARCH
977
        rs = RadioSettingValueList(SKEYTOP_CHOICES, SKEYTOP_CHOICES[idx])
978
        rset = RadioSetting("topkey_sp", "Top Key (Short Press)", rs)
979
        rset.set_apply_callback(apply_skeytop_listvalue,
980
                                _settings.topkey_sp)
981
        basic.append(rset)
982

    
983
        # Mneu 36: TONE
984
        rs = RadioSettingValueList(TONE_LIST, TONE_LIST[_settings.tone])
985
        rset = RadioSetting("tone", "Tone-burst Frequency", rs)
986
        basic.append(rset)
987

    
988
        # Mneu 29: POWER ON MSG
989
        rs = RadioSettingValueList(PONMSG_LIST, PONMSG_LIST[_settings.ponmsg])
990
        rset = RadioSetting("ponmsg", "Power On Message", rs)
991
        basic.append(rset)
992

    
993
        if self.MODEL in ["HI-8811", "RT-470L"]:
994
            rs = RadioSettingValueList(TAILCODE_LIST,
995
                                       TAILCODE_LIST[_settings.tailcode])
996
            rset = RadioSetting("tailcode", "Tail Code", rs)
997
            basic.append(rset)
998

    
999
        # Menu 46: STE
1000
        rs = RadioSettingValueBoolean(_settings.ste)
1001
        rset = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)", rs)
1002
        basic.append(rset)
1003

    
1004
        # Menu 40: RP-STE
1005
        rs = RadioSettingValueList(RPSTE_LIST, RPSTE_LIST[_settings.rpste])
1006
        rset = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)", rs)
1007
        basic.append(rset)
1008

    
1009
        # Menu 41: RPT-RL
1010
        rs = RadioSettingValueList(RPSTE_LIST, RPSTE_LIST[_settings.rptrl])
1011
        rset = RadioSetting("rptrl", "STE Repeater Delay", rs)
1012
        basic.append(rset)
1013

    
1014
        # Menu 38: MENU EXIT TIME
1015
        rs = RadioSettingValueList(MENUQUIT_LIST,
1016
                                   MENUQUIT_LIST[_settings.menuquit])
1017
        rset = RadioSetting("menuquit", "Menu Auto Quit", rs)
1018
        basic.append(rset)
1019

    
1020
        # Menu 34: AUTOLOCK
1021
        rs = RadioSettingValueList(AUTOLK_LIST, AUTOLK_LIST[_settings.autolk])
1022
        rset = RadioSetting("autolk", "Key Auto Lock", rs)
1023
        basic.append(rset)
1024

    
1025
        # Menu 28: CDCSS SAVE MODE
1026
        rs = RadioSettingValueList(QTSAVE_LIST, QTSAVE_LIST[_settings.qtsave])
1027
        rset = RadioSetting("qtsave", "QT Save Type", rs)
1028
        basic.append(rset)
1029

    
1030
        # Menu 45: TX-A/B
1031
        rs = RadioSettingValueList(DUALTX_LIST, DUALTX_LIST[_settings.dualtx])
1032
        rset = RadioSetting("dualtx", "Dual TX", rs)
1033
        basic.append(rset)
1034

    
1035
        # Menu 47: AL-MODE
1036
        rs = RadioSettingValueList(ALMODE_LIST, ALMODE_LIST[_settings.almode])
1037
        rset = RadioSetting("almode", "Alarm Mode", rs)
1038
        basic.append(rset)
1039

    
1040
        # Menu 11: ROGER
1041
        # ==========
1042
        # Notice to developers:
1043
        # The RT-470 v1.22 firmware expanded the ROGER menu with an additional
1044
        # choice, 'TONE1200'. RT-470 radios with a firmware version prior to
1045
        #  v1.22 will not honor the ROGER menu's 'TONE1200' choice in CHIRP.
1046
        # ==========
1047
        rs = RadioSettingValueList(ROGER_LIST, ROGER_LIST[_settings.roger])
1048
        rset = RadioSetting("roger", "Roger", rs)
1049
        basic.append(rset)
1050

    
1051
        rs = RadioSettingValueBoolean(_settings.alarmsound)
1052
        rset = RadioSetting("alarmsound", "Alarm Sound", rs)
1053
        basic.append(rset)
1054

    
1055
        # Menu 44: TDR
1056
        rs = RadioSettingValueBoolean(_settings.tdr)
1057
        rset = RadioSetting("tdr", "TDR", rs)
1058
        basic.append(rset)
1059

    
1060
        rs = RadioSettingValueBoolean(not _settings.fmradio)
1061
        rset = RadioSetting("fmradio", "FM Radio", rs)
1062
        basic.append(rset)
1063

    
1064
        rs = RadioSettingValueBoolean(_settings.kblock)
1065
        rset = RadioSetting("kblock", "KB Lock", rs)
1066
        basic.append(rset)
1067

    
1068
        # Menu 16: BEEP PROMPT
1069
        rs = RadioSettingValueBoolean(_settings.beep)
1070
        rset = RadioSetting("beep", "Beep", rs)
1071
        basic.append(rset)
1072

    
1073
        if self.MODEL not in ["A36plus", "UV-A37", "AR-730"]:
1074
            # Menu 48: RX END TAIL
1075
            rs = RadioSettingValueList(TONERXEND_LIST,
1076
                                       TONERXEND_LIST[_settings.rxendtail])
1077
            rset = RadioSetting("rxendtail", "Tone RX End", rs)
1078
            basic.append(rset)
1079

    
1080
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1081
        group.append(dtmf)
1082

    
1083
        def apply_code(setting, obj, length):
1084
            code = []
1085
            for j in range(0, length):
1086
                try:
1087
                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
1088
                except IndexError:
1089
                    code.append(0xFF)
1090
            obj.code = code
1091

    
1092
        for i in range(0, 15):
1093
            _codeobj = self._memobj.pttid[i].code
1094
            _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
1095
            rs = RadioSettingValueString(0, 5, _code, False)
1096
            rs.set_charset(DTMF_CHARS)
1097
            rset = RadioSetting("pttid/%i.code" % i,
1098
                                "PTT-ID Code %i" % (i + 1), rs)
1099
            rset.set_apply_callback(apply_code, self._memobj.pttid[i], 5)
1100
            dtmf.append(rset)
1101

    
1102
        rs = RadioSettingValueList(DTMFSPEED_LIST,
1103
                                   DTMFSPEED_LIST[_dtmf.dtmfon])
1104
        rset = RadioSetting("dtmf.dtmfon", "DTMF Speed (on)", rs)
1105
        dtmf.append(rset)
1106

    
1107
        rs = RadioSettingValueList(DTMFSPEED_LIST,
1108
                                   DTMFSPEED_LIST[_dtmf.dtmfoff])
1109
        rset = RadioSetting("dtmf.dtmfoff", "DTMF Speed (off)", rs)
1110
        dtmf.append(rset)
1111

    
1112
        rs = RadioSettingValueList(PTTID_LIST, PTTID_LIST[_dtmf.pttid])
1113
        rset = RadioSetting("dtmf.pttid", "PTT ID", rs)
1114
        dtmf.append(rset)
1115

    
1116
        ani = RadioSettingGroup("ani", "ANI Code List Settings")
1117
        group.append(ani)
1118

    
1119
        def _filter(name):
1120
            filtered = ""
1121
            for char in str(name):
1122
                if char in self._valid_chars:
1123
                    filtered += char
1124
                else:
1125
                    filtered += " "
1126
            return filtered
1127

    
1128
        for i in range(0, 30):
1129
            _codeobj = self._memobj.anicodes[i].code
1130
            _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
1131
            rs = RadioSettingValueString(0, 3, _code, False)
1132
            rs.set_charset(DTMF_CHARS)
1133
            rset = RadioSetting("anicodes/%i.code" % i,
1134
                                "ANI Code %i" % (i + 1), rs)
1135
            rset.set_apply_callback(apply_code, self._memobj.anicodes[i], 3)
1136
            ani.append(rset)
1137

    
1138
            _nameobj = "NUM.%i" % (i + 1)
1139
            rs = RadioSettingValueString(0, 6, _nameobj)
1140
            rs.set_mutable(False)
1141
            rset = RadioSetting("aninames/%i.code" % i,
1142
                                "ANI Code %i Name" % (i + 1), rs)
1143
            ani.append(rset)
1144

    
1145
        for i in range(0, 30):
1146
            _codeobj = self._memobj.anicodes[i + 30].code
1147
            _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
1148
            rs = RadioSettingValueString(0, 3, _code, False)
1149
            rs.set_charset(DTMF_CHARS)
1150
            rset = RadioSetting("anicodes/%i.code" % (i + 30),
1151
                                "ANI Code %i" % (i + 31), rs)
1152
            rset.set_apply_callback(apply_code,
1153
                                    self._memobj.anicodes[i + 30], 3)
1154
            ani.append(rset)
1155

    
1156
            _nameobj = self._memobj.aninames[i].name
1157
            rs = RadioSettingValueString(0, 6, _filter(_nameobj))
1158
            rset = RadioSetting("aninames/%i.name" % i,
1159
                                "ANI Code %i Name" % (i + 31), rs)
1160
            ani.append(rset)
1161

    
1162
        if self.MODEL == "A36plus":
1163
            # Menu 21: RX END TAIL
1164
            rs = RadioSettingValueList(TONERXEND_LIST,
1165
                                       TONERXEND_LIST[_settings.skey3_lp])
1166
            rset = RadioSetting("skey3_lp", "RX End Tail", rs)
1167
            basic.append(rset)
1168

    
1169
            # Menu 23: TAIL PHASE
1170
            rs = RadioSettingValueList(TAILPHASE_LIST,
1171
                                       TAILPHASE_LIST[_settings.rxendtail])
1172
            rset = RadioSetting("rxendtail", "Tail Phase", rs)
1173
            basic.append(rset)
1174

    
1175
        return group
1176

    
1177
    def set_settings(self, settings):
1178
        _settings = self._memobj.settings
1179
        for element in settings:
1180
            if not isinstance(element, RadioSetting):
1181
                self.set_settings(element)
1182
                continue
1183
            else:
1184
                try:
1185
                    name = element.get_name()
1186
                    if "." in name:
1187
                        bits = name.split(".")
1188
                        obj = self._memobj
1189
                        for bit in bits[:-1]:
1190
                            if "/" in bit:
1191
                                bit, index = bit.split("/", 1)
1192
                                index = int(index)
1193
                                obj = getattr(obj, bit)[index]
1194
                            else:
1195
                                obj = getattr(obj, bit)
1196
                        setting = bits[-1]
1197
                    else:
1198
                        obj = _settings
1199
                        setting = element.get_name()
1200

    
1201
                    if element.has_apply_callback():
1202
                        LOG.debug("Using apply callback")
1203
                        element.run_apply_callback()
1204
                    elif setting == "ani":
1205
                        setattr(obj, setting, int(element.value) - 1)
1206
                    elif setting == "fmradio":
1207
                        setattr(obj, setting, not int(element.value))
1208
                    elif element.value.get_mutable():
1209
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1210
                        setattr(obj, setting, element.value)
1211
                except Exception as e:
1212
                    LOG.debug(element.get_name(), e)
1213
                    raise
1214

    
1215
    @classmethod
1216
    def match_model(cls, filedata, filename):
1217
        # This radio has always been post-metadata, so never do
1218
        # old-school detection
1219
        return False
1220

    
1221

    
1222
@directory.register
1223
class RT470Radio(JC8810base):
1224
    """Radtel RT-470"""
1225
    VENDOR = "Radtel"
1226
    MODEL = "RT-470"
1227

    
1228
    # ==========
1229
    # Notice to developers:
1230
    # The RT-470 support in this driver is currently based upon v1.25 firmware.
1231
    # ==========
1232

    
1233
    _fingerprint = [b"\x00\x00\x00\x26\x00\x20\xD8\x04",
1234
                    b"\x00\x00\x00\x42\x00\x20\xF0\x04",
1235
                    b"\x00\x00\x00\x4A\x00\x20\xF8\x04",
1236
                    b"\x00\x00\x00\x3A\x00\x20\xE8\x04",  # fw 1.25A
1237
                    ]
1238

    
1239
    VALID_BANDS = [(16000000, 100000000),
1240
                   (100000000, 136000000),
1241
                   (136000000, 200000000),
1242
                   (200000000, 300000000),
1243
                   (300000000, 400000000),
1244
                   (400000000, 560000000),
1245
                   (740000000, 1000000000),
1246
                   ]
1247

    
1248

    
1249
@directory.register
1250
class RT470LRadio(JC8810base):
1251
    """Radtel RT-470L"""
1252
    VENDOR = "Radtel"
1253
    MODEL = "RT-470L"
1254

    
1255
    # ==========
1256
    # Notice to developers:
1257
    # The RT-470 support in this driver is currently based upon v1.17 firmware.
1258
    # ==========
1259

    
1260
    _fingerprint = [b"\x00\x00\x00\xfe\x00\x20\xAC\x04",
1261
                    b"\x00\x00\x00\x20\x00\x20\xCC\x04",
1262
                    b"\x00\x00\x00\x20\x00\x20\x07\x00"]
1263

    
1264
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
1265
                    chirp_common.PowerLevel("M", watts=4.00),
1266
                    chirp_common.PowerLevel("L", watts=2.00)]
1267

    
1268
    VALID_BANDS = [(108000000, 136000000),
1269
                   (136000000, 179000000),
1270
                   (220000000, 260000000),
1271
                   (330000000, 400000000),
1272
                   (400000000, 520000000)]
1273

    
1274

    
1275
@directory.register
1276
class RT470XRadio(RT470LRadio):
1277
    """Radtel RT-470X"""
1278
    VENDOR = "Radtel"
1279
    MODEL = "RT-470X"
1280

    
1281
    # ==========
1282
    # Notice to developers:
1283
    # The RT-470 support in this driver is currently based upon v1.18 firmware.
1284
    # ==========
1285

    
1286
    VALID_BANDS = [(100000000, 136000000),
1287
                   (136000000, 200000000),
1288
                   (200000000, 300000000),
1289
                   (300000000, 400000000),
1290
                   (400000000, 560000000)]
1291

    
1292

    
1293
@directory.register
1294
class HI8811Radio(RT470LRadio):
1295
    """Hiroyasu HI-8811"""
1296
    VENDOR = "Hiroyasu"
1297
    MODEL = "HI-8811"
1298

    
1299
    # ==========
1300
    # Notice to developers:
1301
    # The HI-8811 support in this driver is currently based upon v1.17
1302
    # firmware.
1303
    # ==========
1304

    
1305

    
1306
@directory.register
1307
class UVA37Radio(JC8810base):
1308
    """Anysecu UV-A37"""
1309
    VENDOR = "Anysecu"
1310
    MODEL = "UV-A37"
1311

    
1312
    # ==========
1313
    # Notice to developers:
1314
    # The UV-A37 support in this driver is currently based upon v1.24
1315
    # firmware.
1316
    # ==========
1317

    
1318
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
1319
                    chirp_common.PowerLevel("L", watts=1.00)]
1320

    
1321
    VALID_BANDS = [(108000000, 136000000),
1322
                   (136000000, 174000000),
1323
                   (200000000, 260000000),
1324
                   (350000000, 390000000),
1325
                   (400000000, 520000000)]
1326

    
1327
    _magic = b"PROGRAMJC37U"
1328
    _fingerprint = [b"\x00\x00\x00\xE4\x00\x20\x94\x04",
1329
                    b"\x00\x00\x00\xE8\x00\x20\x98\x04"]
1330

    
1331
    _ranges = [
1332
               (0x0000, 0x2000),
1333
               (0x8000, 0x8040),
1334
               (0x9000, 0x9040),
1335
               (0xA000, 0xA140),
1336
               (0xB000, 0xB440)
1337
              ]
1338
    _memsize = 0xB440
1339

    
1340

    
1341
@directory.register
1342
class A36plusRadio(JC8810base):
1343
    """Talkpod A36plus"""
1344
    VENDOR = "Talkpod"
1345
    MODEL = "A36plus"
1346

    
1347
    # ==========
1348
    # Notice to developers:
1349
    # The A36plus support in this driver is currently based upon v1.22
1350
    # firmware.
1351
    # ==========
1352

    
1353
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
1354
                    chirp_common.PowerLevel("L", watts=1.00)]
1355

    
1356
    VALID_BANDS = [(108000000, 136000000),
1357
                   (136000000, 180000000),
1358
                   (200000000, 260000000),
1359
                   (350000000, 400000000),
1360
                   (400000000, 520000000),
1361
                   ]
1362

    
1363
    _magic = b"PROGRAMJC37U"
1364
    _fingerprint = [b"\x00\x00\x00\x42\x00\x20\xF0\x04",
1365
                    b"\x00\x00\x00\x5A\x00\x20\x08\x05",  # fw 1.18
1366
                    b"\x00\x00\x00\x9E\x00\x20\x0C\x05",  # fw 1.22
1367
                    ]
1368

    
1369
    _ranges = [
1370
               (0x0000, 0x4000),
1371
               (0x8000, 0x8040),
1372
               (0x9000, 0x9040),
1373
               (0xA000, 0xA140),
1374
               (0xB000, 0xB440)
1375
              ]
1376
    _memsize = 0xB440
1377
    _upper = 512
1378
    _mem_params = (_upper  # number of channels
1379
                   )
1380

    
1381
@directory.register
1382
class AR730Radio(UVA37Radio):
1383
    """Abbree AR730"""
1384
    VENDOR = "Abbree"
1385
    MODEL = "AR-730"
1386

    
1387
    # ==========
1388
    # Notice to developers:
1389
    # The AR-730 support in this driver is currently based upon v1.24
1390
    # firmware.
1391
    # ==========
1392

    
1393
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
1394
                    chirp_common.PowerLevel("L", watts=1.00)]
1395

    
1396
    VALID_BANDS = [(108000000, 136000000),
1397
                   (136000000, 180000000),
1398
                   (200000000, 260000000),
1399
                   (350000000, 390000000),
1400
                   (400000000, 520000000)]
(7-7/13)