Project

General

Profile

New Model #11067 » mml_jc8810 - a36plus8w.py

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

    
63
#seekto 0x9000;
64
struct {
65
  u8 unused:4,        // 9000
66
     sql:4;           //      Squelch Level
67
  u8 unused_9001:6,   // 9001
68
     save:2;          //      Battery Save
69
  u8 unused_9002:4,   // 9002
70
     vox:4;           //      VOX Level
71
  u8 unused_9003:4,   // 9003
72
     abr:4;           //      Auto BackLight
73
  u8 unused_9004:7,   // 9004
74
     tdr:1;           //      TDR
75
  u8 unused:5,        // 9005
76
     tot:3;           //      Time-out Timer
77
  u8 unused_9006:7,   // 9006
78
     beep:1;          //      Beep
79
  u8 unused_9007:7,   // 9007
80
     voice:1;         //      Voice Prompt
81
  u8 unused_9008:7,   // 9008
82
     language:1;      //      Language
83
  u8 unused_9009:6,   // 9009
84
     dtmfst:2;        //      DTMF ST
85
  u8 unused_900a:6,   // 900A
86
     screv:2;         //      Scan Mode
87
  u8 unused_900B:4,   // 900B
88
     pttid:4;         //      PTT-ID
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 0xA006;
154
struct {
155
  u8 unknown_A006;    // A006
156
  u8 unused_a007:5,   // A007
157
     dtmfon:3;        //      DTMF Speed (on time)
158
  u8 unused_a008:5,   // A008
159
     dtmfoff:3;       //      DTMF Speed (off time)
160
} dtmf;
161

    
162
#seekto 0xA020;
163
struct {
164
  u8 code[5];         //      5-character DTMF Encoder Groups
165
  u8 unused[11];
166
} pttid[15];
167

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

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

    
179
"""
180

    
181
MEM_FORMAT_A36PLUS = """
182
#seekto 0xB200;
183
struct {
184
  char name[10];      //      10-character Custom CH Names (Talkpod A36plus)
185
  u8 unused[6];
186
} customnames[30];
187

    
188
"""
189

    
190

    
191
CMD_ACK = b"\x06"
192

    
193
DTCS = tuple(sorted(chirp_common.DTCS_CODES + (645,)))
194

    
195
DTMF_CHARS = "0123456789 *#ABCD"
196

    
197
TXPOWER_HIGH = 0x00
198
TXPOWER_LOW = 0x01
199
TXPOWER_MID = 0x02
200

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

    
232
ALL_SKEY_CHOICES = ["OFF",
233
                    "FM Radio",
234
                    "TX Power Level",
235
                    "Scan",
236
                    "Search",
237
                    "Flashlight",
238
                    "NOAA Weather",
239
                    "Monitor",
240
                    "PTT B",
241
                    "SOS",
242
                    "DTMF",
243
                    "REVERSE",
244
                    "REMOTE Scan"]
245

    
246
ALL_SKEY_VALUES = [0xFF,
247
                   0x07,
248
                   0x0A,
249
                   0x1C,
250
                   0x1D,
251
                   0x08,
252
                   0x0C,
253
                   0x05,
254
                   0x01,
255
                   0x03,
256
                   0x2A,
257
                   0x2D,
258
                   0x23]
259

    
260

    
261
def _enter_programming_mode(radio):
262
    serial = radio.pipe
263

    
264
    exito = False
265
    for i in range(0, 5):
266
        serial.write(radio._magic)
267
        ack = serial.read(1)
268

    
269
        try:
270
            if ack == CMD_ACK:
271
                exito = True
272
                break
273
        except Exception:
274
            LOG.debug("Attempt #%s, failed, trying again" % i)
275
            pass
276

    
277
    # check if we had EXITO
278
    if exito is False:
279
        msg = "The radio did not accept program mode after five tries.\n"
280
        msg += "Check you interface cable and power cycle your radio."
281
        raise errors.RadioError(msg)
282

    
283
    try:
284
        serial.write(b"F")
285
        ident = serial.read(8)
286
    except Exception:
287
        raise errors.RadioError("Error communicating with radio")
288

    
289
    if ident not in radio._fingerprint:
290
        LOG.debug(util.hexprint(ident))
291
        raise errors.RadioError("Radio returned unknown identification string")
292

    
293
    if radio.MODEL == "RT-470X":
294
        if ident in radio._fingerprint_pcb1:
295
            LOG.info("Radtel RT-470X - original pcb")
296
            radio.RT470X_ORIG = True
297
        elif ident in radio._fingerprint_pcb2:
298
            LOG.info("Radtel RT-470X - pcb2")
299
            radio.RT470X_ORIG = False
300

    
301

    
302
def _exit_programming_mode(radio):
303
    serial = radio.pipe
304
    try:
305
        serial.write(b"E")
306
    except Exception:
307
        raise errors.RadioError("Radio refused to exit programming mode")
308

    
309

    
310
def _read_block(radio, block_addr, block_size):
311
    serial = radio.pipe
312

    
313
    cmd = struct.pack(">cHb", b'R', block_addr, block_size)
314
    expectedresponse = b"R" + cmd[1:]
315
    LOG.debug("Reading block %04x..." % (block_addr))
316

    
317
    try:
318
        serial.write(cmd)
319
        response = serial.read(4 + block_size)
320
        if response[:4] != expectedresponse:
321
            raise Exception("Error reading block %04x." % (block_addr))
322

    
323
        block_data = response[4:]
324
    except Exception:
325
        raise errors.RadioError("Failed to read block at %04x" % block_addr)
326

    
327
    return block_data
328

    
329

    
330
def _write_block(radio, block_addr, block_size):
331
    serial = radio.pipe
332

    
333
    cmd = struct.pack(">cHb", b'W', block_addr, block_size)
334
    data = radio.get_mmap()[block_addr:block_addr + block_size]
335

    
336
    LOG.debug("Writing Data:")
337
    LOG.debug(util.hexprint(cmd + data))
338

    
339
    try:
340
        serial.write(cmd + data)
341
        if serial.read(1) != CMD_ACK:
342
            raise Exception("No ACK")
343
    except Exception:
344
        raise errors.RadioError("Failed to send block "
345
                                "to radio at %04x" % block_addr)
346

    
347

    
348
def do_download(radio):
349
    LOG.debug("download")
350
    _enter_programming_mode(radio)
351

    
352
    data = b""
353

    
354
    status = chirp_common.Status()
355
    status.msg = "Cloning from radio"
356

    
357
    status.cur = 0
358
    status.max = radio._memsize
359

    
360
    for addr in range(0, radio._memsize, radio.BLOCK_SIZE):
361
        status.cur = addr + radio.BLOCK_SIZE
362
        radio.status_fn(status)
363

    
364
        block = _read_block(radio, addr, radio.BLOCK_SIZE)
365
        data += block
366

    
367
        LOG.debug("Address: %04x" % addr)
368
        LOG.debug(util.hexprint(block))
369

    
370
    _exit_programming_mode(radio)
371

    
372
    return memmap.MemoryMapBytes(data)
373

    
374

    
375
def do_upload(radio):
376
    raise errors.RadioError("Uploading is currently disabled")
377

    
378

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

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

    
390
    # if you get here is because the freq pairs are split
391
    return True
392

    
393

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

    
403
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=10.00),
404
                    chirp_common.PowerLevel("M", watts=8.00),
405
                    chirp_common.PowerLevel("L", watts=4.00)]
406

    
407
    VALID_BANDS = [(108000000, 136000000),
408
                   (136000000, 180000000),
409
                   (200000000, 260000000),
410
                   (330000000, 400000000),
411
                   (400000000, 520000000)]
412

    
413
    _magic = b"PROGRAMJC81U"
414
    _fingerprint = [b"\x00\x00\x00\x26\x00\x20\xD8\x04",
415
                    b"\x00\x00\x00\x42\x00\x20\xF0\x04",
416
                    b"\x00\x00\x00\x4A\x00\x20\xF8\x04"]
417

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

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

    
459
    def process_mmap(self):
460
        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
461

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

    
478
    def sync_out(self):
479
        """Upload to radio"""
480
        try:
481
            do_upload(self)
482
        except Exception:
483
            # If anything unexpected happens, make sure we raise
484
            # a RadioError and log the problem
485
            LOG.exception('Unexpected error during upload')
486
            raise errors.RadioError('Uploading is currently disabled')
487

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

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

    
495
        # Memory number
496
        mem.number = number
497

    
498
        if _mem.get_raw()[:1] == b"\xFF":
499
            mem.empty = True
500
            return mem
501

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

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

    
532
        dtcs_pol = ["N", "N"]
533

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

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

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

    
576
        mem.dtcs_polarity = "".join(dtcs_pol)
577

    
578
        if not _mem.scan:
579
            mem.skip = "S"
580

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

    
601
        mem.mode = _mem.narrow and "NFM" or "FM"
602

    
603
        mem.extra = RadioSettingGroup("Extra", "extra")
604

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

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

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

    
620
        return mem
621

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

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

    
629
        _mem.set_raw("\x00" * 16 + "\xFF" * 16)
630

    
631
        _mem.rxfreq = mem.freq / 10
632

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

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

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

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

    
686
        _mem.scan = mem.skip != "S"
687
        _mem.narrow = mem.mode == "NFM"
688

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

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

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

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

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

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

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

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

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

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

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

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

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

    
872
        # Menu 22: PF3
873
        def apply_skey3s_listvalue(setting, obj):
874
            LOG.debug("Setting value: " + str(setting.value) + " from list")
875
            val = str(setting.value)
876
            index = SKEY3S_CHOICES.index(val)
877
            val = SKEY3S_VALUES[index]
878
            obj.set_value(val)
879

    
880
        if self.MODEL in ["HI-8811", "RT-470L", "RT-470X", "RT-470"]:
881
            unwanted = [8, 9, 10, 11, 12]
882
        elif self.MODEL in ["UV-A37", "AR-730"]:
883
            unwanted = [0, 5, 7, 8, 9, 10, 11, 12]
884
        elif self.MODEL in ["A36plus", "A36plus_8w"]:
885
            unwanted = [0, 5, 7, 8, 11]
886
        else:
887
            unwanted = []
888
        SKEY3S_CHOICES = ALL_SKEY_CHOICES.copy()
889
        SKEY3S_VALUES = ALL_SKEY_VALUES.copy()
890
        for ele in sorted(unwanted, reverse=True):
891
            del SKEY3S_CHOICES[ele]
892
            del SKEY3S_VALUES[ele]
893

    
894
        if _settings.skey3_sp in SKEY3S_VALUES:
895
            idx = SKEY3S_VALUES.index(_settings.skey3_sp)
896
        else:
897
            idx = SKEY3S_VALUES.index(0x0C)  # default NOAA
898
        rs = RadioSettingValueList(SKEY3S_CHOICES, SKEY3S_CHOICES[idx])
899
        rset = RadioSetting("skey3_sp", "PF3 Key (Short Press)", rs)
900
        rset.set_apply_callback(apply_skey3s_listvalue, _settings.skey3_sp)
901
        basic.append(rset)
902

    
903
        if self.MODEL in ["HI-8811", "RT-470L", "RT-470X", "RT-470"]:
904
            # Menu 24: PF3 LONG PRESS (RT-470L)
905
            def apply_skey3l_listvalue(setting, obj):
906
                LOG.debug("Setting value: " + str(setting.value) +
907
                          " from list")
908
                val = str(setting.value)
909
                index = SKEY3L_CHOICES.index(val)
910
                val = SKEY2L_VALUES[index]
911
                obj.set_value(val)
912

    
913
            if self.MODEL in ["HI-8811", "RT-470L", "RT-470X", "RT-470"]:
914
                unwanted = [8, 9, 10, 11, 12]
915
            else:
916
                unwanted = []
917
            SKEY3L_CHOICES = ALL_SKEY_CHOICES.copy()
918
            SKEY3L_VALUES = ALL_SKEY_VALUES.copy()
919
            for ele in sorted(unwanted, reverse=True):
920
                del SKEY3L_CHOICES[ele]
921
                del SKEY3L_VALUES[ele]
922

    
923
            if _settings.skey3_lp in SKEY3L_VALUES:
924
                idx = SKEY3L_VALUES.index(_settings.skey3_lp)
925
            else:
926
                idx = SKEY3L_VALUES.index(0x1D)  # default SEARCH
927
            rs = RadioSettingValueList(SKEY3L_CHOICES, SKEY3L_CHOICES[idx])
928
            rset = RadioSetting("skey3_lp", "PF3 Key (Long Press)", rs)
929
            rset.set_apply_callback(apply_skey3l_listvalue,
930
                                    _settings.skey3_lp)
931
            basic.append(rset)
932

    
933
        # Menu 25: TOP KEY (RT-470L)
934
        def apply_skeytop_listvalue(setting, obj):
935
            LOG.debug("Setting value: " + str(setting.value) +
936
                      " from list")
937
            val = str(setting.value)
938
            index = SKEYTOP_CHOICES.index(val)
939
            val = SKEYTOP_VALUES[index]
940
            obj.set_value(val)
941

    
942
        if self.MODEL in ["HI-8811", "RT-470L", "RT-470X", "RT-470"]:
943
            unwanted = [8, 9, 10, 11, 12]
944
        elif self.MODEL in ["UV-A37", "AR-730"]:
945
            unwanted = [0, 5, 7, 8, 9, 10, 11, 12]
946
        elif self.MODEL in ["A36plus", "A36plus_8w"]:
947
            unwanted = [0, 5, 7, 8, 11]
948
        else:
949
            unwanted = []
950
        SKEYTOP_CHOICES = ALL_SKEY_CHOICES.copy()
951
        SKEYTOP_VALUES = ALL_SKEY_VALUES.copy()
952
        for ele in sorted(unwanted, reverse=True):
953
            del SKEYTOP_CHOICES[ele]
954
            del SKEYTOP_VALUES[ele]
955

    
956
        if _settings.topkey_sp in SKEYTOP_VALUES:
957
            idx = SKEYTOP_VALUES.index(_settings.topkey_sp)
958
        else:
959
            idx = SKEYTOP_VALUES.index(0x1D)  # default SEARCH
960
        rs = RadioSettingValueList(SKEYTOP_CHOICES, SKEYTOP_CHOICES[idx])
961
        rset = RadioSetting("topkey_sp", "Top Key (Short Press)", rs)
962
        rset.set_apply_callback(apply_skeytop_listvalue,
963
                                _settings.topkey_sp)
964
        basic.append(rset)
965

    
966
        # Mneu 36: TONE
967
        rs = RadioSettingValueList(TONE_LIST, TONE_LIST[_settings.tone])
968
        rset = RadioSetting("tone", "Tone-burst Frequency", rs)
969
        basic.append(rset)
970

    
971
        # Mneu 29: POWER ON MSG
972
        rs = RadioSettingValueList(PONMSG_LIST, PONMSG_LIST[_settings.ponmsg])
973
        rset = RadioSetting("ponmsg", "Power On Message", rs)
974
        basic.append(rset)
975

    
976
        if self.MODEL in ["HI-8811", "RT-470L", "RT-470X"]:
977
            rs = RadioSettingValueList(TAILCODE_LIST,
978
                                       TAILCODE_LIST[_settings.tailcode])
979
            rset = RadioSetting("tailcode", "Tail Code", rs)
980
            basic.append(rset)
981

    
982
        # Menu 46: STE
983
        rs = RadioSettingValueBoolean(_settings.ste)
984
        rset = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)", rs)
985
        basic.append(rset)
986

    
987
        # Menu 40: RP-STE
988
        rs = RadioSettingValueList(RPSTE_LIST, RPSTE_LIST[_settings.rpste])
989
        rset = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)", rs)
990
        basic.append(rset)
991

    
992
        # Menu 41: RPT-RL
993
        rs = RadioSettingValueList(RPSTE_LIST, RPSTE_LIST[_settings.rptrl])
994
        rset = RadioSetting("rptrl", "STE Repeater Delay", rs)
995
        basic.append(rset)
996

    
997
        # Menu 38: MENU EXIT TIME
998
        rs = RadioSettingValueList(MENUQUIT_LIST,
999
                                   MENUQUIT_LIST[_settings.menuquit])
1000
        rset = RadioSetting("menuquit", "Menu Auto Quit", rs)
1001
        basic.append(rset)
1002

    
1003
        # Menu 34: AUTOLOCK
1004
        rs = RadioSettingValueList(AUTOLK_LIST, AUTOLK_LIST[_settings.autolk])
1005
        rset = RadioSetting("autolk", "Key Auto Lock", rs)
1006
        basic.append(rset)
1007

    
1008
        # Menu 28: CDCSS SAVE MODE
1009
        rs = RadioSettingValueList(QTSAVE_LIST, QTSAVE_LIST[_settings.qtsave])
1010
        rset = RadioSetting("qtsave", "QT Save Type", rs)
1011
        basic.append(rset)
1012

    
1013
        # Menu 45: TX-A/B
1014
        rs = RadioSettingValueList(DUALTX_LIST, DUALTX_LIST[_settings.dualtx])
1015
        rset = RadioSetting("dualtx", "Dual TX", rs)
1016
        basic.append(rset)
1017

    
1018
        # Menu 47: AL-MODE
1019
        rs = RadioSettingValueList(ALMODE_LIST, ALMODE_LIST[_settings.almode])
1020
        rset = RadioSetting("almode", "Alarm Mode", rs)
1021
        basic.append(rset)
1022

    
1023
        # Menu 11: ROGER
1024
        # ==========
1025
        # Notice to developers:
1026
        # The RT-470 v1.22 firmware expanded the ROGER menu with an additional
1027
        # choice, 'TONE1200'. RT-470 radios with a firmware version prior to
1028
        #  v1.22 will not honor the ROGER menu's 'TONE1200' choice in CHIRP.
1029
        # ==========
1030
        rs = RadioSettingValueList(ROGER_LIST, ROGER_LIST[_settings.roger])
1031
        rset = RadioSetting("roger", "Roger", rs)
1032
        basic.append(rset)
1033

    
1034
        rs = RadioSettingValueBoolean(_settings.alarmsound)
1035
        rset = RadioSetting("alarmsound", "Alarm Sound", rs)
1036
        basic.append(rset)
1037

    
1038
        # Menu 44: TDR
1039
        rs = RadioSettingValueBoolean(_settings.tdr)
1040
        rset = RadioSetting("tdr", "TDR", rs)
1041
        basic.append(rset)
1042

    
1043
        rs = RadioSettingValueBoolean(not _settings.fmradio)
1044
        rset = RadioSetting("fmradio", "FM Radio", rs)
1045
        basic.append(rset)
1046

    
1047
        rs = RadioSettingValueBoolean(_settings.kblock)
1048
        rset = RadioSetting("kblock", "KB Lock", rs)
1049
        basic.append(rset)
1050

    
1051
        # Menu 16: BEEP PROMPT
1052
        rs = RadioSettingValueBoolean(_settings.beep)
1053
        rset = RadioSetting("beep", "Beep", rs)
1054
        basic.append(rset)
1055

    
1056
        if self.MODEL not in ["A36plus", "A36plus_8w", "UV-A37", "AR-730"]:
1057
            # Menu 48: RX END TAIL
1058
            rs = RadioSettingValueList(TONERXEND_LIST,
1059
                                       TONERXEND_LIST[_settings.rxendtail])
1060
            rset = RadioSetting("rxendtail", "Tone RX End", rs)
1061
            basic.append(rset)
1062

    
1063
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1064
        group.append(dtmf)
1065

    
1066
        def apply_code(setting, obj, length):
1067
            code = []
1068
            for j in range(0, length):
1069
                try:
1070
                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
1071
                except IndexError:
1072
                    code.append(0xFF)
1073
            obj.code = code
1074

    
1075
        for i in range(0, 15):
1076
            _codeobj = self._memobj.pttid[i].code
1077
            _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
1078
            rs = RadioSettingValueString(0, 5, _code, False)
1079
            rs.set_charset(DTMF_CHARS)
1080
            rset = RadioSetting("pttid/%i.code" % i,
1081
                                "PTT-ID Code %i" % (i + 1), rs)
1082
            rset.set_apply_callback(apply_code, self._memobj.pttid[i], 5)
1083
            dtmf.append(rset)
1084

    
1085
        rs = RadioSettingValueList(DTMFSPEED_LIST,
1086
                                   DTMFSPEED_LIST[_dtmf.dtmfon])
1087
        rset = RadioSetting("dtmf.dtmfon", "DTMF Speed (on)", rs)
1088
        dtmf.append(rset)
1089

    
1090
        rs = RadioSettingValueList(DTMFSPEED_LIST,
1091
                                   DTMFSPEED_LIST[_dtmf.dtmfoff])
1092
        rset = RadioSetting("dtmf.dtmfoff", "DTMF Speed (off)", rs)
1093
        dtmf.append(rset)
1094

    
1095
        rs = RadioSettingValueList(PTTID_LIST, PTTID_LIST[_settings.pttid])
1096
        rset = RadioSetting("pttid", "PTT ID", rs)
1097
        dtmf.append(rset)
1098

    
1099
        ani = RadioSettingGroup("ani", "ANI Code List Settings")
1100
        group.append(ani)
1101

    
1102
        def _filter(name):
1103
            filtered = ""
1104
            for char in str(name):
1105
                if char in self._valid_chars:
1106
                    filtered += char
1107
                else:
1108
                    filtered += " "
1109
            return filtered
1110

    
1111
        end = 60 - self._aninames  # end of immutable ANI names
1112
        for i in range(0, end):
1113
            _codeobj = self._memobj.anicodes[i].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,
1118
                                "ANI Code %i (NUM.%i)" % (i + 1, i + 1), rs)
1119
            rset.set_apply_callback(apply_code, self._memobj.anicodes[i], 3)
1120
            ani.append(rset)
1121

    
1122
        for i in range(end, 60):
1123
            _codeobj = self._memobj.anicodes[i].code
1124
            _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
1125
            rs = RadioSettingValueString(0, 3, _code, False)
1126
            rs.set_charset(DTMF_CHARS)
1127
            rset = RadioSetting("anicodes/%i.code" % (i),
1128
                                "ANI Code %i" % (i + 1), rs)
1129
            rset.set_apply_callback(apply_code,
1130
                                    self._memobj.anicodes[i], 3)
1131
            ani.append(rset)
1132

    
1133
            _nameobj = self._memobj.aninames[i - end].name
1134
            rs = RadioSettingValueString(0, 10, _filter(_nameobj))
1135
            rset = RadioSetting("aninames/%i.name" % (i - end),
1136
                                "ANI Code %i Name" % (i + 1), rs)
1137
            ani.append(rset)
1138

    
1139
        if self.MODEL in ["A36plus", "A36plus_8w"]:
1140
            custom = RadioSettingGroup("custom", "Custom Channel Names")
1141
            group.append(custom)
1142

    
1143
            for i in range(0, 30):
1144
                _nameobj = self._memobj.customnames[i].name
1145
                rs = RadioSettingValueString(0, 10, _filter(_nameobj))
1146
                rset = RadioSetting("customnames/%i.name" % i,
1147
                                    "Custom Name %i" % (i + 1), rs)
1148
                custom.append(rset)
1149

    
1150
        if self.MODEL in ["A36plus", "A36plus_8w"]:
1151
            # Menu 21: RX END TAIL
1152
            rs = RadioSettingValueList(TONERXEND_LIST,
1153
                                       TONERXEND_LIST[_settings.skey3_lp])
1154
            rset = RadioSetting("skey3_lp", "RX End Tail", rs)
1155
            basic.append(rset)
1156

    
1157
            # Menu 23: TAIL PHASE
1158
            rs = RadioSettingValueList(TAILPHASE_LIST,
1159
                                       TAILPHASE_LIST[_settings.rxendtail])
1160
            rset = RadioSetting("rxendtail", "Tail Phase", rs)
1161
            basic.append(rset)
1162

    
1163
        return group
1164

    
1165
    def set_settings(self, settings):
1166
        _settings = self._memobj.settings
1167
        for element in settings:
1168
            if not isinstance(element, RadioSetting):
1169
                self.set_settings(element)
1170
                continue
1171
            else:
1172
                try:
1173
                    name = element.get_name()
1174
                    if "." in name:
1175
                        bits = name.split(".")
1176
                        obj = self._memobj
1177
                        for bit in bits[:-1]:
1178
                            if "/" in bit:
1179
                                bit, index = bit.split("/", 1)
1180
                                index = int(index)
1181
                                obj = getattr(obj, bit)[index]
1182
                            else:
1183
                                obj = getattr(obj, bit)
1184
                        setting = bits[-1]
1185
                    else:
1186
                        obj = _settings
1187
                        setting = element.get_name()
1188

    
1189
                    if element.has_apply_callback():
1190
                        LOG.debug("Using apply callback")
1191
                        element.run_apply_callback()
1192
                    elif setting == "ani":
1193
                        setattr(obj, setting, int(element.value) - 1)
1194
                    elif setting == "fmradio":
1195
                        setattr(obj, setting, not int(element.value))
1196
                    elif element.value.get_mutable():
1197
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1198
                        setattr(obj, setting, element.value)
1199
                except Exception as e:
1200
                    LOG.debug(element.get_name(), e)
1201
                    raise
1202

    
1203
    @classmethod
1204
    def match_model(cls, filedata, filename):
1205
        # This radio has always been post-metadata, so never do
1206
        # old-school detection
1207
        return False
1208

    
1209

    
1210
@directory.register
1211
class RT470Radio(JC8810base):
1212
    """Radtel RT-470"""
1213
    VENDOR = "Radtel"
1214
    MODEL = "RT-470"
1215

    
1216
    # ==========
1217
    # Notice to developers:
1218
    # The RT-470 support in this driver is currently based upon...
1219
    # - v1.25a firmware (original pcb)
1220
    # - v2.11a firmware (pcb2)
1221
    # ==========
1222

    
1223
    # original pcb
1224
    _fingerprint_pcb1 = [b"\x00\x00\x00\x26\x00\x20\xD8\x04",
1225
                         b"\x00\x00\x00\x42\x00\x20\xF0\x04",
1226
                         b"\x00\x00\x00\x4A\x00\x20\xF8\x04",
1227
                         b"\x00\x00\x00\x3A\x00\x20\xE8\x04",  # fw 1.25A
1228
                         ]
1229

    
1230
    # pcb 2
1231
    _fingerprint_pcb2 = [b"\x00\x00\x00\x2C\x00\x20\xD8\x04",  # fw v2.11A
1232
                         ]
1233

    
1234
    _fingerprint = _fingerprint_pcb1 + _fingerprint_pcb2
1235

    
1236
    VALID_BANDS = [(16000000, 100000000),
1237
                   (100000000, 136000000),
1238
                   (136000000, 200000000),
1239
                   (200000000, 300000000),
1240
                   (300000000, 400000000),
1241
                   (400000000, 560000000),
1242
                   (740000000, 1000000000),
1243
                   ]
1244

    
1245

    
1246
@directory.register
1247
class RT470LRadio(JC8810base):
1248
    """Radtel RT-470L"""
1249
    VENDOR = "Radtel"
1250
    MODEL = "RT-470L"
1251

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

    
1257
    _fingerprint = [b"\x00\x00\x00\xfe\x00\x20\xAC\x04",
1258
                    b"\x00\x00\x00\x20\x00\x20\xCC\x04",
1259
                    b"\x00\x00\x00\x20\x00\x20\x07\x00"]
1260

    
1261
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
1262
                    chirp_common.PowerLevel("M", watts=4.00),
1263
                    chirp_common.PowerLevel("L", watts=2.00)]
1264

    
1265
    VALID_BANDS = [(108000000, 136000000),
1266
                   (136000000, 179000000),
1267
                   (220000000, 260000000),
1268
                   (330000000, 400000000),
1269
                   (400000000, 520000000)]
1270

    
1271

    
1272
@directory.register
1273
class RT470XRadio(RT470LRadio):
1274
    """Radtel RT-470X"""
1275
    VENDOR = "Radtel"
1276
    MODEL = "RT-470X"
1277

    
1278
    # ==========
1279
    # Notice to developers:
1280
    # The RT-470X support in this driver is currently based upon...
1281
    # - v1.18a firmware (original pcb)
1282
    # - v2.13a firmware (pcb2)
1283
    # ==========
1284

    
1285
    # original pcb
1286
    _fingerprint_pcb1 = [b"\x00\x00\x00\x20\x00\x20\xCC\x04",
1287
                         ]
1288

    
1289
    # pcb 2
1290
    _fingerprint_pcb2 = [b"\x00\x00\x00\x2C\x00\x20\xD8\x04",  # fw v2.10A
1291
                         b"\x00\x00\x00\x36\x00\x20\xDC\x04",  # fw v2.13A
1292
                         ]
1293

    
1294
    _fingerprint = _fingerprint_pcb1 + _fingerprint_pcb2
1295

    
1296
    RT470X_ORIG = False
1297

    
1298
    VALID_BANDS = [(100000000, 136000000),
1299
                   (136000000, 200000000),
1300
                   (200000000, 300000000),
1301
                   (300000000, 400000000),
1302
                   (400000000, 560000000)]
1303

    
1304

    
1305
@directory.register
1306
class HI8811Radio(RT470LRadio):
1307
    """Hiroyasu HI-8811"""
1308
    VENDOR = "Hiroyasu"
1309
    MODEL = "HI-8811"
1310

    
1311
    # ==========
1312
    # Notice to developers:
1313
    # The HI-8811 support in this driver is currently based upon...
1314
    # - v1.17 firmware (original pcb)
1315
    # - v2.00 firmware (pcb2)
1316
    # ==========
1317

    
1318
    # original pcb
1319
    _fingerprint_pcb1 = [b"\x00\x00\x00\xfe\x00\x20\xAC\x04",
1320
                         b"\x00\x00\x00\x20\x00\x20\xCC\x04",  # fw v1.17
1321
                         b"\x00\x00\x00\x20\x00\x20\x07\x00",
1322
                         ]
1323

    
1324
    # pcb 2
1325
    _fingerprint_pcb2 = [b"\x00\x00\x00\x28\x00\x20\xD4\x04",  # fw v2.00
1326
                         ]
1327

    
1328
    _fingerprint = _fingerprint_pcb1 + _fingerprint_pcb2
1329

    
1330

    
1331
@directory.register
1332
class UVA37Radio(JC8810base):
1333
    """Anysecu UV-A37"""
1334
    VENDOR = "Anysecu"
1335
    MODEL = "UV-A37"
1336

    
1337
    # ==========
1338
    # Notice to developers:
1339
    # The UV-A37 support in this driver is currently based upon v1.24
1340
    # firmware.
1341
    # ==========
1342

    
1343
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
1344
                    chirp_common.PowerLevel("L", watts=1.00)]
1345

    
1346
    VALID_BANDS = [(108000000, 136000000),
1347
                   (136000000, 174000000),
1348
                   (200000000, 260000000),
1349
                   (350000000, 390000000),
1350
                   (400000000, 520000000)]
1351

    
1352
    _magic = b"PROGRAMJC37U"
1353
    _fingerprint = [b"\x00\x00\x00\xE4\x00\x20\x94\x04",
1354
                    b"\x00\x00\x00\xE8\x00\x20\x98\x04"]
1355

    
1356
    _ranges = [
1357
               (0x0000, 0x2000),
1358
               (0x8000, 0x8040),
1359
               (0x9000, 0x9040),
1360
               (0xA000, 0xA140),
1361
               (0xB000, 0xB440)
1362
              ]
1363
    _memsize = 0xB440
1364

    
1365

    
1366
@directory.register
1367
class A36plusRadio(JC8810base):
1368
    """Talkpod A36plus"""
1369
    VENDOR = "Talkpod"
1370
    MODEL = "A36plus"
1371

    
1372
    # ==========
1373
    # Notice to developers:
1374
    # The A36plus support in this driver is currently based upon v1.22
1375
    # firmware.
1376
    # ==========
1377

    
1378
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
1379
                    chirp_common.PowerLevel("L", watts=1.00)]
1380

    
1381
    VALID_BANDS = [(108000000, 136000000),
1382
                   (136000000, 180000000),
1383
                   (200000000, 260000000),
1384
                   (350000000, 400000000),
1385
                   (400000000, 520000000),
1386
                   ]
1387

    
1388
    _magic = b"PROGRAMJC37U"
1389
    _fingerprint = [b"\x00\x00\x00\x42\x00\x20\xF0\x04",
1390
                    b"\x00\x00\x00\x5A\x00\x20\x08\x05",  # fw 1.18
1391
                    b"\x00\x00\x00\x9E\x00\x20\x0C\x05",  # fw 1.22
1392
                    b"\x00\x00\x00\xFA\x00\x20\x40\x05",  # fw 1.4
1393
                    ]
1394

    
1395
    _ranges = [
1396
               (0x0000, 0x4000),
1397
               (0x8000, 0x8040),
1398
               (0x9000, 0x9040),
1399
               (0xA000, 0xA140),
1400
               (0xB000, 0xB440)
1401
              ]
1402
    _memsize = 0xB440
1403
    _upper = 512  # fw 1.22 expands from 256 to 512 channels
1404
    _aninames = 10
1405
    _mem_params = (_upper,  # number of channels
1406
                   _aninames,  # number of aninames
1407
                   )
1408

    
1409
    def process_mmap(self):
1410
        mem_format = MEM_FORMAT % self._mem_params + MEM_FORMAT_A36PLUS
1411
        self._memobj = bitwise.parse(mem_format, self._mmap)
1412

    
1413

    
1414
@directory.register
1415
class A36plus8wRadio(A36plusRadio):
1416
    """Talkpod A36plus8w"""
1417
    VENDOR = "Talkpod"
1418
    MODEL = "A36plus_8w"
1419

    
1420
    # ==========
1421
    # Notice to developers:
1422
    # The A36plus 8w support in this driver is currently based upon v1.4
1423
    # firmware.
1424
    # ==========
1425

    
1426
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=8.00),
1427
                    chirp_common.PowerLevel("L", watts=1.00)]
1428

    
1429
    _fingerprint = [b"\x00\x00\x00\xFA\x00\x20\x40\x05",  # fw 1.4
1430
                    ]
1431

    
1432

    
1433
@directory.register
1434
class AR730Radio(UVA37Radio):
1435
    """Abbree AR730"""
1436
    VENDOR = "Abbree"
1437
    MODEL = "AR-730"
1438

    
1439
    # ==========
1440
    # Notice to developers:
1441
    # The AR-730 support in this driver is currently based upon v1.24
1442
    # firmware.
1443
    # ==========
1444

    
1445
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
1446
                    chirp_common.PowerLevel("L", watts=1.00)]
1447

    
1448
    VALID_BANDS = [(108000000, 136000000),
1449
                   (136000000, 180000000),
1450
                   (200000000, 260000000),
1451
                   (350000000, 390000000),
1452
                   (400000000, 520000000)]
(5-5/7)