Project

General

Profile

Bug #11258 ยป mml_jc8810_RT470_fw200.py

Jim Unroe, 03/19/2024 05:57 PM

 
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_9005: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_900e: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 unlock_sw_ch;    // 9024 UNLOCK SW CH (A36plus 8w)
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
  u8 unknown_9031;    // 9031
152
  u8 unknown_9032;    // 9032
153
  u8 unknown_9033;    // 9033
154
  u8 unknown_9034;    // 9034
155
  u8 unknown_9035;    // 9035
156
  u8 unknown_9036;    // 9036
157
  u8 unknown_9037;    // 9037
158
  u8 unknown_9038;    // 9038
159
  u8 unknown_9039;    // 9039
160
  u8 unknown_903a;    // 903a
161
  u8 single_mode;     // 903b SINGLE MODE (A36plus 8w)
162
  u8 dis_s_table;     // 903b DIS S TABLE (A36plus 8w)
163
} settings;
164

    
165
#seekto 0xA006;
166
struct {
167
  u8 unknown_A006;    // A006
168
  u8 unused_a007:5,   // A007
169
     dtmfon:3;        //      DTMF Speed (on time)
170
  u8 unused_a008:5,   // A008
171
     dtmfoff:3;       //      DTMF Speed (off time)
172
} dtmf;
173

    
174
#seekto 0xA020;
175
struct {
176
  u8 code[5];         //      5-character DTMF Encoder Groups
177
  u8 unused[11];
178
} pttid[15];
179

    
180
#seekto 0xB000;
181
struct {
182
  u8 code[3];         //      3-character ANI Code Groups
183
} anicodes[60];
184

    
185
#seekto 0xB0C0;
186
struct {
187
  char name[10];      //      10-character ANI Code Group Names
188
  u8 unused[6];
189
} aninames[%d];
190

    
191
"""
192

    
193
MEM_FORMAT_A36PLUS = """
194
#seekto 0xB200;
195
struct {
196
  char name[10];      //      10-character Custom CH Names (Talkpod A36plus)
197
  u8 unused[6];
198
} customnames[30];
199

    
200
"""
201

    
202

    
203
CMD_ACK = b"\x06"
204

    
205
DTCS = tuple(sorted(chirp_common.DTCS_CODES + (645,)))
206

    
207
DTMF_CHARS = "0123456789 *#ABCD"
208

    
209
TXPOWER_HIGH = 0x00
210
TXPOWER_LOW = 0x01
211
TXPOWER_MID = 0x02
212

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

    
244
ALL_SKEY_CHOICES = ["OFF",
245
                    "FM Radio",
246
                    "TX Power Level",
247
                    "Scan",
248
                    "Search",
249
                    "Flashlight",
250
                    "NOAA Weather",
251
                    "Monitor",
252
                    "PTT B",
253
                    "SOS",
254
                    "DTMF",
255
                    "REVERSE",
256
                    "REMOTE Scan"]
257

    
258
ALL_SKEY_VALUES = [0xFF,
259
                   0x07,
260
                   0x0A,
261
                   0x1C,
262
                   0x1D,
263
                   0x08,
264
                   0x0C,
265
                   0x05,
266
                   0x01,
267
                   0x03,
268
                   0x2A,
269
                   0x2D,
270
                   0x23]
271

    
272

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

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

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

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

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

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

    
305
    if radio.MODEL == "RT-470X":
306
        if ident in radio._fingerprint_pcb1:
307
            LOG.info("Radtel RT-470X - original pcb")
308
            radio.RT470X_ORIG = True
309
        elif ident in radio._fingerprint_pcb2:
310
            LOG.info("Radtel RT-470X - pcb2")
311
            radio.RT470X_ORIG = False
312

    
313

    
314
def _exit_programming_mode(radio):
315
    serial = radio.pipe
316
    try:
317
        serial.write(b"E")
318
    except Exception:
319
        raise errors.RadioError("Radio refused to exit programming mode")
320

    
321

    
322
def _read_block(radio, block_addr, block_size):
323
    serial = radio.pipe
324

    
325
    cmd = struct.pack(">cHb", b'R', block_addr, block_size)
326
    expectedresponse = b"R" + cmd[1:]
327
    LOG.debug("Reading block %04x..." % (block_addr))
328

    
329
    try:
330
        serial.write(cmd)
331
        response = serial.read(4 + block_size)
332
        if response[:4] != expectedresponse:
333
            raise Exception("Error reading block %04x." % (block_addr))
334

    
335
        block_data = response[4:]
336
    except Exception:
337
        raise errors.RadioError("Failed to read block at %04x" % block_addr)
338

    
339
    return block_data
340

    
341

    
342
def _write_block(radio, block_addr, block_size):
343
    serial = radio.pipe
344

    
345
    cmd = struct.pack(">cHb", b'W', block_addr, block_size)
346
    data = radio.get_mmap()[block_addr:block_addr + block_size]
347

    
348
    LOG.debug("Writing Data:")
349
    LOG.debug(util.hexprint(cmd + data))
350

    
351
    try:
352
        serial.write(cmd + data)
353
        if serial.read(1) != CMD_ACK:
354
            raise Exception("No ACK")
355
    except Exception:
356
        raise errors.RadioError("Failed to send block "
357
                                "to radio at %04x" % block_addr)
358

    
359

    
360
def do_download(radio):
361
    LOG.debug("download")
362
    _enter_programming_mode(radio)
363

    
364
    data = b""
365

    
366
    status = chirp_common.Status()
367
    status.msg = "Cloning from radio"
368

    
369
    status.cur = 0
370
    status.max = radio._memsize
371

    
372
    for addr in range(0, radio._memsize, radio.BLOCK_SIZE):
373
        status.cur = addr + radio.BLOCK_SIZE
374
        radio.status_fn(status)
375

    
376
        block = _read_block(radio, addr, radio.BLOCK_SIZE)
377
        data += block
378

    
379
        LOG.debug("Address: %04x" % addr)
380
        LOG.debug(util.hexprint(block))
381

    
382
    _exit_programming_mode(radio)
383

    
384
    return memmap.MemoryMapBytes(data)
385

    
386

    
387
def do_upload(radio):
388
    status = chirp_common.Status()
389
    status.msg = "Uploading to radio"
390

    
391
    _enter_programming_mode(radio)
392

    
393
    status.cur = 0
394
    status.max = radio._memsize
395

    
396
    for start_addr, end_addr in radio._ranges:
397
        for addr in range(start_addr, end_addr, radio.BLOCK_SIZE_UP):
398
            status.cur = addr + radio.BLOCK_SIZE_UP
399
            radio.status_fn(status)
400
            _write_block(radio, addr, radio.BLOCK_SIZE_UP)
401

    
402
    _exit_programming_mode(radio)
403

    
404

    
405
def _split(rf, f1, f2):
406
    """Returns False if the two freqs are in the same band (no split)
407
    or True otherwise"""
408

    
409
    # determine if the two freqs are in the same band
410
    for low, high in rf.valid_bands:
411
        if f1 >= low and f1 <= high and \
412
                f2 >= low and f2 <= high:
413
            # if the two freqs are on the same Band this is not a split
414
            return False
415

    
416
    # if you get here is because the freq pairs are split
417
    return True
418

    
419

    
420
class JC8810base(chirp_common.CloneModeRadio):
421
    """MML JC-8810"""
422
    VENDOR = "MML"
423
    MODEL = "JC-8810base"
424
    BAUD_RATE = 57600
425
    NEEDS_COMPAT_SERIAL = False
426
    BLOCK_SIZE = 0x40
427
    BLOCK_SIZE_UP = 0x40
428

    
429
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=10.00),
430
                    chirp_common.PowerLevel("M", watts=8.00),
431
                    chirp_common.PowerLevel("L", watts=4.00)]
432

    
433
    VALID_BANDS = [(108000000, 136000000),
434
                   (136000000, 180000000),
435
                   (200000000, 260000000),
436
                   (330000000, 400000000),
437
                   (400000000, 520000000)]
438

    
439
    _magic = b"PROGRAMJC81U"
440
    _fingerprint = [b"\x00\x00\x00\x26\x00\x20\xD8\x04",
441
                    b"\x00\x00\x00\x42\x00\x20\xF0\x04",
442
                    b"\x00\x00\x00\x4A\x00\x20\xF8\x04"]
443

    
444
    _ranges = [
445
               (0x0000, 0x2000),
446
               (0x8000, 0x8040),
447
               (0x9000, 0x9040),
448
               (0xA000, 0xA140),
449
               (0xB000, 0xB300)
450
              ]
451
    _memsize = 0xB300
452
    _upper = 256
453
    _aninames = 30
454
    _mem_params = (_upper,  # number of channels
455
                   _aninames,  # number of aninames
456
                   )
457
    _valid_chars = chirp_common.CHARSET_ALPHANUMERIC + \
458
        "`~!@#$%^&*()-=_+[]\\{}|;':\",./<>?"
459

    
460
    def get_features(self):
461
        rf = chirp_common.RadioFeatures()
462
        rf.has_settings = True
463
        rf.has_bank = False
464
        rf.has_ctone = True
465
        rf.has_cross = True
466
        rf.has_rx_dtcs = True
467
        rf.has_tuning_step = False
468
        rf.can_odd_split = True
469
        rf.has_name = True
470
        rf.valid_name_length = 12
471
        rf.valid_characters = self._valid_chars
472
        rf.valid_skips = ["", "S"]
473
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
474
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
475
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
476
        rf.valid_power_levels = self.POWER_LEVELS
477
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
478
        rf.valid_modes = ["FM", "NFM"]  # 25 kHz, 12.5 kHz.
479
        rf.valid_dtcs_codes = DTCS
480
        rf.memory_bounds = (1, self._upper)
481
        rf.valid_tuning_steps = [2.5, 5., 6.25, 8.33, 10., 12.5, 20., 25., 50.]
482
        rf.valid_bands = self.VALID_BANDS
483
        return rf
484

    
485
    def process_mmap(self):
486
        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
487

    
488
    def sync_in(self):
489
        """Download from radio"""
490
        try:
491
            data = do_download(self)
492
        except errors.RadioError:
493
            # Pass through any real errors we raise
494
            raise
495
        except Exception:
496
            # If anything unexpected happens, make sure we raise
497
            # a RadioError and log the problem
498
            LOG.exception('Unexpected error during download')
499
            raise errors.RadioError('Unexpected error communicating '
500
                                    'with the radio')
501
        self._mmap = data
502
        self.process_mmap()
503

    
504
    def sync_out(self):
505
        """Upload to radio"""
506
        try:
507
            do_upload(self)
508
        except Exception:
509
            # If anything unexpected happens, make sure we raise
510
            # a RadioError and log the problem
511
            LOG.exception('Unexpected error during upload')
512
            raise errors.RadioError('Unexpected error communicating '
513
                                    'with the radio')
514

    
515
    def get_memory(self, number):
516
        """Get the mem representation from the radio image"""
517
        _mem = self._memobj.memory[number - 1]
518

    
519
        # Create a high-level memory object to return to the UI
520
        mem = chirp_common.Memory()
521

    
522
        # Memory number
523
        mem.number = number
524

    
525
        if _mem.get_raw()[:1] == b"\xFF":
526
            mem.empty = True
527
            return mem
528

    
529
        # Freq and offset
530
        mem.freq = int(_mem.rxfreq) * 10
531
        # tx freq can be blank
532
        if _mem.txfreq.get_raw() == b"\xFF\xFF\xFF\xFF":
533
            # TX freq not set
534
            mem.offset = 0
535
            mem.duplex = "off"
536
        else:
537
            # TX freq set
538
            offset = (int(_mem.txfreq) * 10) - mem.freq
539
            if offset != 0:
540
                if _split(self.get_features(), mem.freq, int(
541
                          _mem.txfreq) * 10):
542
                    mem.duplex = "split"
543
                    mem.offset = int(_mem.txfreq) * 10
544
                elif offset < 0:
545
                    mem.offset = abs(offset)
546
                    mem.duplex = "-"
547
                elif offset > 0:
548
                    mem.offset = offset
549
                    mem.duplex = "+"
550
            else:
551
                mem.offset = 0
552

    
553
        for char in _mem.name:
554
            if str(char) == "\xFF":
555
                char = " "  # may have 0xFF mid-name
556
            mem.name += str(char)
557
        mem.name = mem.name.rstrip()
558

    
559
        dtcs_pol = ["N", "N"]
560

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

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

    
593
        if txmode == "Tone" and not rxmode:
594
            mem.tmode = "Tone"
595
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
596
            mem.tmode = "TSQL"
597
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
598
            mem.tmode = "DTCS"
599
        elif rxmode or txmode:
600
            mem.tmode = "Cross"
601
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
602

    
603
        mem.dtcs_polarity = "".join(dtcs_pol)
604

    
605
        if not _mem.scan:
606
            mem.skip = "S"
607

    
608
        _levels = self.POWER_LEVELS
609
        if self.MODEL in ["A36plus", "A36plus_8w", "UV-A37", "AR-730"]:
610
            if _mem.txpower == TXPOWER_HIGH:
611
                mem.power = _levels[0]
612
            elif _mem.txpower == TXPOWER_LOW:
613
                mem.power = _levels[1]
614
            else:
615
                LOG.error('%s: get_mem: unhandled power level: 0x%02x' %
616
                          (mem.name, _mem.txpower))
617
        else:
618
            if _mem.txpower == TXPOWER_HIGH:
619
                mem.power = _levels[0]
620
            elif _mem.txpower == TXPOWER_MID:
621
                mem.power = _levels[1]
622
            elif _mem.txpower == TXPOWER_LOW:
623
                mem.power = _levels[2]
624
            else:
625
                LOG.error('%s: get_mem: unhandled power level: 0x%02x' %
626
                          (mem.name, _mem.txpower))
627

    
628
        mem.mode = _mem.narrow and "NFM" or "FM"
629

    
630
        mem.extra = RadioSettingGroup("Extra", "extra")
631

    
632
        # BCL (Busy Channel Lockout)
633
        rs = RadioSettingValueBoolean(_mem.bcl)
634
        rset = RadioSetting("bcl", "BCL", rs)
635
        mem.extra.append(rset)
636

    
637
        # PTT-ID
638
        rs = RadioSettingValueList(PTTID_LIST, PTTID_LIST[_mem.pttid])
639
        rset = RadioSetting("pttid", "PTT ID", rs)
640
        mem.extra.append(rset)
641

    
642
        # Signal (DTMF Encoder Group #)
643
        rs = RadioSettingValueList(PTTIDCODE_LIST, PTTIDCODE_LIST[_mem.scode])
644
        rset = RadioSetting("scode", "PTT ID Code", rs)
645
        mem.extra.append(rset)
646

    
647
        return mem
648

    
649
    def set_memory(self, mem):
650
        _mem = self._memobj.memory[mem.number - 1]
651

    
652
        if mem.empty:
653
            _mem.set_raw("\xff" * 32)
654
            return
655

    
656
        _mem.set_raw("\x00" * 16 + "\xFF" * 16)
657

    
658
        _mem.rxfreq = mem.freq / 10
659

    
660
        if mem.duplex == "off":
661
            _mem.txfreq.fill_raw(b"\xFF")
662
        elif mem.duplex == "split":
663
            _mem.txfreq = mem.offset / 10
664
        elif mem.duplex == "+":
665
            _mem.txfreq = (mem.freq + mem.offset) / 10
666
        elif mem.duplex == "-":
667
            _mem.txfreq = (mem.freq - mem.offset) / 10
668
        else:
669
            _mem.txfreq = mem.freq / 10
670

    
671
        _namelength = self.get_features().valid_name_length
672
        for i in range(_namelength):
673
            try:
674
                _mem.name[i] = mem.name[i]
675
            except IndexError:
676
                _mem.name[i] = "\xFF"
677

    
678
        rxmode = txmode = ""
679
        if mem.tmode == "Tone":
680
            _mem.txtone = int(mem.rtone * 10)
681
            _mem.rxtone = 0
682
        elif mem.tmode == "TSQL":
683
            _mem.txtone = int(mem.ctone * 10)
684
            _mem.rxtone = int(mem.ctone * 10)
685
        elif mem.tmode == "DTCS":
686
            rxmode = txmode = "DTCS"
687
            _mem.txtone = DTCS.index(mem.dtcs) + 1
688
            _mem.rxtone = DTCS.index(mem.dtcs) + 1
689
        elif mem.tmode == "Cross":
690
            txmode, rxmode = mem.cross_mode.split("->", 1)
691
            if txmode == "Tone":
692
                _mem.txtone = int(mem.rtone * 10)
693
            elif txmode == "DTCS":
694
                _mem.txtone = DTCS.index(mem.dtcs) + 1
695
            else:
696
                _mem.txtone = 0
697
            if rxmode == "Tone":
698
                _mem.rxtone = int(mem.ctone * 10)
699
            elif rxmode == "DTCS":
700
                _mem.rxtone = DTCS.index(mem.rx_dtcs) + 1
701
            else:
702
                _mem.rxtone = 0
703
        else:
704
            _mem.rxtone = 0
705
            _mem.txtone = 0
706

    
707
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
708
            _mem.txtone += 0x69
709
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
710
            _mem.rxtone += 0x69
711

    
712
        _mem.scan = mem.skip != "S"
713
        _mem.narrow = mem.mode == "NFM"
714

    
715
        _levels = self.POWER_LEVELS
716
        if self.MODEL in ["A36plus", "A36plus_8w", "UV-A37", "AR-730"]:
717
            if mem.power is None:
718
                _mem.txpower = TXPOWER_HIGH
719
            elif mem.power == _levels[0]:
720
                _mem.txpower = TXPOWER_HIGH
721
            elif mem.power == _levels[1]:
722
                _mem.txpower = TXPOWER_LOW
723
            else:
724
                LOG.error('%s: set_mem: unhandled power level: %s' %
725
                          (mem.name, mem.power))
726
        else:
727
            if mem.power is None:
728
                _mem.txpower = TXPOWER_HIGH
729
            elif mem.power == _levels[0]:
730
                _mem.txpower = TXPOWER_HIGH
731
            elif mem.power == _levels[1]:
732
                _mem.txpower = TXPOWER_MID
733
            elif mem.power == _levels[2]:
734
                _mem.txpower = TXPOWER_LOW
735
            else:
736
                LOG.error('%s: set_mem: unhandled power level: %s' %
737
                          (mem.name, mem.power))
738

    
739
        for setting in mem.extra:
740
            if setting.get_name() == "scramble_type":
741
                setattr(_mem, setting.get_name(), int(setting.value) + 8)
742
                setattr(_mem, "scramble_type2", int(setting.value) + 8)
743
            else:
744
                setattr(_mem, setting.get_name(), setting.value)
745

    
746
    def get_settings(self):
747
        _dtmf = self._memobj.dtmf
748
        _settings = self._memobj.settings
749
        basic = RadioSettingGroup("basic", "Basic Settings")
750
        group = RadioSettings(basic)
751

    
752
        # Menu 12: TOT
753
        rs = RadioSettingValueList(TOT_LIST, TOT_LIST[_settings.tot])
754
        rset = RadioSetting("tot", "Time Out Timer", rs)
755
        basic.append(rset)
756

    
757
        # Menu 00: SQL
758
        rs = RadioSettingValueInteger(0, 9, _settings.sql)
759
        rset = RadioSetting("sql", "Squelch Level", rs)
760
        basic.append(rset)
761

    
762
        # Menu 13: VOX
763
        rs = RadioSettingValueList(OFF1TO9_LIST, OFF1TO9_LIST[_settings.vox])
764
        rset = RadioSetting("vox", "VOX", rs)
765
        basic.append(rset)
766

    
767
        # Menu 39: VOX DELAY
768
        rs = RadioSettingValueList(VOXD_LIST, VOXD_LIST[_settings.voxd])
769
        rset = RadioSetting("voxd", "VOX Delay", rs)
770
        basic.append(rset)
771

    
772
        # Menu 15: VOICE
773
        rs = RadioSettingValueBoolean(_settings.voice)
774
        rset = RadioSetting("voice", "Voice Prompts", rs)
775
        basic.append(rset)
776

    
777
        if self.MODEL not in ["A36plus", "A36plus_8w", "UV-A37", "AR-730"]:
778
            # Menu 17: LANGUAGE
779
            rs = RadioSettingValueList(LANGUAGE_LIST,
780
                                       LANGUAGE_LIST[_settings.language])
781
            rset = RadioSetting("language", "Voice", rs)
782
            basic.append(rset)
783

    
784
        # Menu 23: ABR
785
        rs = RadioSettingValueList(ABR_LIST, ABR_LIST[_settings.abr])
786
        rset = RadioSetting("abr", "Auto BackLight", rs)
787
        basic.append(rset)
788

    
789
        # Work Mode A
790
        rs = RadioSettingValueList(WORKMODE_LIST,
791
                                   WORKMODE_LIST[_settings.vfomra])
792
        rset = RadioSetting("vfomra", "Work Mode A", rs)
793
        basic.append(rset)
794

    
795
        # Work Mode B
796
        rs = RadioSettingValueList(WORKMODE_LIST,
797
                                   WORKMODE_LIST[_settings.vfomrb])
798
        rset = RadioSetting("vfomrb", "Work Mode B", rs)
799
        basic.append(rset)
800

    
801
        # Menu 19: SC-REV
802
        rs = RadioSettingValueList(SCREV_LIST, SCREV_LIST[_settings.screv])
803
        rset = RadioSetting("screv", "Scan Resume Method", rs)
804
        basic.append(rset)
805

    
806
        # Menu 10: SAVE
807
        rs = RadioSettingValueList(SAVE_LIST,
808
                                   SAVE_LIST[_settings.save])
809
        rset = RadioSetting("save", "Battery Save Mode", rs)
810
        basic.append(rset)
811

    
812
        # Menu 42: MDF-A
813
        rs = RadioSettingValueList(MDF_LIST, MDF_LIST[_settings.mdfa])
814
        rset = RadioSetting("mdfa", "Memory Display Format A", rs)
815
        basic.append(rset)
816

    
817
        # Menu 43: MDF-B
818
        rs = RadioSettingValueList(MDF_LIST, MDF_LIST[_settings.mdfb])
819
        rset = RadioSetting("mdfb", "Memory Display Format B", rs)
820
        basic.append(rset)
821

    
822
        # Menu 33: DTMFST (DTMF ST)
823
        rs = RadioSettingValueList(DTMFST_LIST, DTMFST_LIST[_settings.dtmfst])
824
        rset = RadioSetting("dtmfst", "DTMF Side Tone", rs)
825
        basic.append(rset)
826

    
827
        # Menu 37: PTT-LT
828
        rs = RadioSettingValueList(PTTLT_LIST, PTTLT_LIST[_settings.pttlt])
829
        rset = RadioSetting("pttlt", "PTT Delay", rs)
830
        basic.append(rset)
831

    
832
        rs = RadioSettingValueInteger(1, 60, _settings.ani + 1)
833
        rset = RadioSetting("ani", "ANI", rs)
834
        basic.append(rset)
835

    
836
        # Menu 20: PF2
837
        def apply_skey2s_listvalue(setting, obj):
838
            LOG.debug("Setting value: " + str(setting.value) + " from list")
839
            val = str(setting.value)
840
            index = SKEY2S_CHOICES.index(val)
841
            val = SKEY2S_VALUES[index]
842
            obj.set_value(val)
843

    
844
        if self.MODEL in ["HI-8811", "RT-470L", "RT-470X", "RT-470"]:
845
            unwanted = [9, 10, 11, 12]
846
        elif self.MODEL in ["UV-A37", "AR-730"]:
847
            unwanted = [0, 5, 7, 9, 10, 11, 12]
848
        elif self.MODEL in ["A36plus", "A36plus_8w"]:
849
            unwanted = [0, 5, 7, 9, 10, 11]
850
        else:
851
            unwanted = []
852
        SKEY2S_CHOICES = ALL_SKEY_CHOICES.copy()
853
        SKEY2S_VALUES = ALL_SKEY_VALUES.copy()
854
        for ele in sorted(unwanted, reverse=True):
855
            del SKEY2S_CHOICES[ele]
856
            del SKEY2S_VALUES[ele]
857

    
858
        if _settings.skey2_sp in SKEY2S_VALUES:
859
            idx = SKEY2S_VALUES.index(_settings.skey2_sp)
860
        else:
861
            idx = SKEY2S_VALUES.index(0x07)  # default FM
862
        rs = RadioSettingValueList(SKEY2S_CHOICES, SKEY2S_CHOICES[idx])
863
        rset = RadioSetting("skey2_sp", "PF2 Key (Short Press)", rs)
864
        rset.set_apply_callback(apply_skey2s_listvalue, _settings.skey2_sp)
865
        basic.append(rset)
866

    
867
        # Menu 21: PF2 LONG PRESS
868
        def apply_skey2l_listvalue(setting, obj):
869
            LOG.debug("Setting value: " + str(setting.value) + " from list")
870
            val = str(setting.value)
871
            index = SKEY2L_CHOICES.index(val)
872
            val = SKEY2L_VALUES[index]
873
            obj.set_value(val)
874

    
875
        if self.MODEL in ["HI-8811", "RT-470L", "RT-470X", "RT-470"]:
876
            unwanted = [8, 9, 10, 11, 12]
877
        elif self.MODEL in ["UV-A37", "AR-730"]:
878
            unwanted = [0, 5, 7, 8, 10, 11, 12]
879
        elif self.MODEL in ["A36plus", "A36plus_8w"]:
880
            unwanted = [0, 5, 7, 8, 11, 12]
881
        else:
882
            unwanted = []
883
        SKEY2L_CHOICES = ALL_SKEY_CHOICES.copy()
884
        SKEY2L_VALUES = ALL_SKEY_VALUES.copy()
885
        for ele in sorted(unwanted, reverse=True):
886
            del SKEY2L_CHOICES[ele]
887
            del SKEY2L_VALUES[ele]
888

    
889
        if _settings.skey2_lp in SKEY2L_VALUES:
890
            idx = SKEY2L_VALUES.index(_settings.skey2_lp)
891
        else:
892
            idx = SKEY2L_VALUES.index(0x1D)  # default Search
893
        rs = RadioSettingValueList(SKEY2L_CHOICES, SKEY2L_CHOICES[idx])
894
        rset = RadioSetting("skey2_lp", "PF2 Key (Long Press)", rs)
895
        rset.set_apply_callback(apply_skey2l_listvalue, _settings.skey2_lp)
896
        basic.append(rset)
897

    
898
        # Menu 22: PF3
899
        def apply_skey3s_listvalue(setting, obj):
900
            LOG.debug("Setting value: " + str(setting.value) + " from list")
901
            val = str(setting.value)
902
            index = SKEY3S_CHOICES.index(val)
903
            val = SKEY3S_VALUES[index]
904
            obj.set_value(val)
905

    
906
        if self.MODEL in ["HI-8811", "RT-470L", "RT-470X", "RT-470"]:
907
            unwanted = [8, 9, 10, 11, 12]
908
        elif self.MODEL in ["UV-A37", "AR-730"]:
909
            unwanted = [0, 5, 7, 8, 9, 10, 11, 12]
910
        elif self.MODEL in ["A36plus", "A36plus_8w"]:
911
            unwanted = [0, 5, 7, 8, 11]
912
        else:
913
            unwanted = []
914
        SKEY3S_CHOICES = ALL_SKEY_CHOICES.copy()
915
        SKEY3S_VALUES = ALL_SKEY_VALUES.copy()
916
        for ele in sorted(unwanted, reverse=True):
917
            del SKEY3S_CHOICES[ele]
918
            del SKEY3S_VALUES[ele]
919

    
920
        if _settings.skey3_sp in SKEY3S_VALUES:
921
            idx = SKEY3S_VALUES.index(_settings.skey3_sp)
922
        else:
923
            idx = SKEY3S_VALUES.index(0x0C)  # default NOAA
924
        rs = RadioSettingValueList(SKEY3S_CHOICES, SKEY3S_CHOICES[idx])
925
        rset = RadioSetting("skey3_sp", "PF3 Key (Short Press)", rs)
926
        rset.set_apply_callback(apply_skey3s_listvalue, _settings.skey3_sp)
927
        basic.append(rset)
928

    
929
        if self.MODEL in ["HI-8811", "RT-470L", "RT-470X", "RT-470"]:
930
            # Menu 24: PF3 LONG PRESS (RT-470L)
931
            def apply_skey3l_listvalue(setting, obj):
932
                LOG.debug("Setting value: " + str(setting.value) +
933
                          " from list")
934
                val = str(setting.value)
935
                index = SKEY3L_CHOICES.index(val)
936
                val = SKEY2L_VALUES[index]
937
                obj.set_value(val)
938

    
939
            if self.MODEL in ["HI-8811", "RT-470L", "RT-470X", "RT-470"]:
940
                unwanted = [8, 9, 10, 11, 12]
941
            else:
942
                unwanted = []
943
            SKEY3L_CHOICES = ALL_SKEY_CHOICES.copy()
944
            SKEY3L_VALUES = ALL_SKEY_VALUES.copy()
945
            for ele in sorted(unwanted, reverse=True):
946
                del SKEY3L_CHOICES[ele]
947
                del SKEY3L_VALUES[ele]
948

    
949
            if _settings.skey3_lp in SKEY3L_VALUES:
950
                idx = SKEY3L_VALUES.index(_settings.skey3_lp)
951
            else:
952
                idx = SKEY3L_VALUES.index(0x1D)  # default SEARCH
953
            rs = RadioSettingValueList(SKEY3L_CHOICES, SKEY3L_CHOICES[idx])
954
            rset = RadioSetting("skey3_lp", "PF3 Key (Long Press)", rs)
955
            rset.set_apply_callback(apply_skey3l_listvalue,
956
                                    _settings.skey3_lp)
957
            basic.append(rset)
958

    
959
        # Menu 25: TOP KEY (RT-470L)
960
        def apply_skeytop_listvalue(setting, obj):
961
            LOG.debug("Setting value: " + str(setting.value) +
962
                      " from list")
963
            val = str(setting.value)
964
            index = SKEYTOP_CHOICES.index(val)
965
            val = SKEYTOP_VALUES[index]
966
            obj.set_value(val)
967

    
968
        if self.MODEL in ["HI-8811", "RT-470L", "RT-470X", "RT-470"]:
969
            unwanted = [8, 9, 10, 11, 12]
970
        elif self.MODEL in ["UV-A37", "AR-730"]:
971
            unwanted = [0, 5, 7, 8, 9, 10, 11, 12]
972
        elif self.MODEL in ["A36plus", "A36plus_8w"]:
973
            unwanted = [0, 5, 7, 8, 11]
974
        else:
975
            unwanted = []
976
        SKEYTOP_CHOICES = ALL_SKEY_CHOICES.copy()
977
        SKEYTOP_VALUES = ALL_SKEY_VALUES.copy()
978
        for ele in sorted(unwanted, reverse=True):
979
            del SKEYTOP_CHOICES[ele]
980
            del SKEYTOP_VALUES[ele]
981

    
982
        if _settings.topkey_sp in SKEYTOP_VALUES:
983
            idx = SKEYTOP_VALUES.index(_settings.topkey_sp)
984
        else:
985
            idx = SKEYTOP_VALUES.index(0x1D)  # default SEARCH
986
        rs = RadioSettingValueList(SKEYTOP_CHOICES, SKEYTOP_CHOICES[idx])
987
        rset = RadioSetting("topkey_sp", "Top Key (Short Press)", rs)
988
        rset.set_apply_callback(apply_skeytop_listvalue,
989
                                _settings.topkey_sp)
990
        basic.append(rset)
991

    
992
        # Mneu 36: TONE
993
        rs = RadioSettingValueList(TONE_LIST, TONE_LIST[_settings.tone])
994
        rset = RadioSetting("tone", "Tone-burst Frequency", rs)
995
        basic.append(rset)
996

    
997
        # Mneu 29: POWER ON MSG
998
        rs = RadioSettingValueList(PONMSG_LIST, PONMSG_LIST[_settings.ponmsg])
999
        rset = RadioSetting("ponmsg", "Power On Message", rs)
1000
        basic.append(rset)
1001

    
1002
        if self.MODEL in ["HI-8811", "RT-470L", "RT-470X"]:
1003
            rs = RadioSettingValueList(TAILCODE_LIST,
1004
                                       TAILCODE_LIST[_settings.tailcode])
1005
            rset = RadioSetting("tailcode", "Tail Code", rs)
1006
            basic.append(rset)
1007

    
1008
        # Menu 46: STE
1009
        rs = RadioSettingValueBoolean(_settings.ste)
1010
        rset = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)", rs)
1011
        basic.append(rset)
1012

    
1013
        # Menu 40: RP-STE
1014
        rs = RadioSettingValueList(RPSTE_LIST, RPSTE_LIST[_settings.rpste])
1015
        rset = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)", rs)
1016
        basic.append(rset)
1017

    
1018
        # Menu 41: RPT-RL
1019
        rs = RadioSettingValueList(RPSTE_LIST, RPSTE_LIST[_settings.rptrl])
1020
        rset = RadioSetting("rptrl", "STE Repeater Delay", rs)
1021
        basic.append(rset)
1022

    
1023
        # Menu 38: MENU EXIT TIME
1024
        rs = RadioSettingValueList(MENUQUIT_LIST,
1025
                                   MENUQUIT_LIST[_settings.menuquit])
1026
        rset = RadioSetting("menuquit", "Menu Auto Quit", rs)
1027
        basic.append(rset)
1028

    
1029
        # Menu 34: AUTOLOCK
1030
        rs = RadioSettingValueList(AUTOLK_LIST, AUTOLK_LIST[_settings.autolk])
1031
        rset = RadioSetting("autolk", "Key Auto Lock", rs)
1032
        basic.append(rset)
1033

    
1034
        # Menu 28: CDCSS SAVE MODE
1035
        rs = RadioSettingValueList(QTSAVE_LIST, QTSAVE_LIST[_settings.qtsave])
1036
        rset = RadioSetting("qtsave", "QT Save Type", rs)
1037
        basic.append(rset)
1038

    
1039
        # Menu 45: TX-A/B
1040
        rs = RadioSettingValueList(DUALTX_LIST, DUALTX_LIST[_settings.dualtx])
1041
        rset = RadioSetting("dualtx", "Dual TX", rs)
1042
        basic.append(rset)
1043

    
1044
        # Menu 47: AL-MODE
1045
        rs = RadioSettingValueList(ALMODE_LIST, ALMODE_LIST[_settings.almode])
1046
        rset = RadioSetting("almode", "Alarm Mode", rs)
1047
        basic.append(rset)
1048

    
1049
        # Menu 11: ROGER
1050
        # ==========
1051
        # Notice to developers:
1052
        # The RT-470 v1.22 firmware expanded the ROGER menu with an additional
1053
        # choice, 'TONE1200'. RT-470 radios with a firmware version prior to
1054
        #  v1.22 will not honor the ROGER menu's 'TONE1200' choice in CHIRP.
1055
        # ==========
1056
        rs = RadioSettingValueList(ROGER_LIST, ROGER_LIST[_settings.roger])
1057
        rset = RadioSetting("roger", "Roger", rs)
1058
        basic.append(rset)
1059

    
1060
        rs = RadioSettingValueBoolean(_settings.alarmsound)
1061
        rset = RadioSetting("alarmsound", "Alarm Sound", rs)
1062
        basic.append(rset)
1063

    
1064
        # Menu 44: TDR
1065
        rs = RadioSettingValueBoolean(_settings.tdr)
1066
        rset = RadioSetting("tdr", "TDR", rs)
1067
        basic.append(rset)
1068

    
1069
        rs = RadioSettingValueBoolean(not _settings.fmradio)
1070
        rset = RadioSetting("fmradio", "FM Radio", rs)
1071
        basic.append(rset)
1072

    
1073
        rs = RadioSettingValueBoolean(_settings.kblock)
1074
        rset = RadioSetting("kblock", "KB Lock", rs)
1075
        basic.append(rset)
1076

    
1077
        # Menu 16: BEEP PROMPT
1078
        rs = RadioSettingValueBoolean(_settings.beep)
1079
        rset = RadioSetting("beep", "Beep", rs)
1080
        basic.append(rset)
1081

    
1082
        if self.MODEL not in ["A36plus", "A36plus_8w", "UV-A37", "AR-730"]:
1083
            # Menu 48: RX END TAIL
1084
            rs = RadioSettingValueList(TONERXEND_LIST,
1085
                                       TONERXEND_LIST[_settings.rxendtail])
1086
            rset = RadioSetting("rxendtail", "Tone RX End", rs)
1087
            basic.append(rset)
1088

    
1089
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1090
        group.append(dtmf)
1091

    
1092
        def apply_code(setting, obj, length):
1093
            code = []
1094
            for j in range(0, length):
1095
                try:
1096
                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
1097
                except IndexError:
1098
                    code.append(0xFF)
1099
            obj.code = code
1100

    
1101
        for i in range(0, 15):
1102
            _codeobj = self._memobj.pttid[i].code
1103
            _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
1104
            rs = RadioSettingValueString(0, 5, _code, False)
1105
            rs.set_charset(DTMF_CHARS)
1106
            rset = RadioSetting("pttid/%i.code" % i,
1107
                                "PTT-ID Code %i" % (i + 1), rs)
1108
            rset.set_apply_callback(apply_code, self._memobj.pttid[i], 5)
1109
            dtmf.append(rset)
1110

    
1111
        rs = RadioSettingValueList(DTMFSPEED_LIST,
1112
                                   DTMFSPEED_LIST[_dtmf.dtmfon])
1113
        rset = RadioSetting("dtmf.dtmfon", "DTMF Speed (on)", rs)
1114
        dtmf.append(rset)
1115

    
1116
        rs = RadioSettingValueList(DTMFSPEED_LIST,
1117
                                   DTMFSPEED_LIST[_dtmf.dtmfoff])
1118
        rset = RadioSetting("dtmf.dtmfoff", "DTMF Speed (off)", rs)
1119
        dtmf.append(rset)
1120

    
1121
        rs = RadioSettingValueList(PTTID_LIST, PTTID_LIST[_settings.pttid])
1122
        rset = RadioSetting("pttid", "PTT ID", rs)
1123
        dtmf.append(rset)
1124

    
1125
        ani = RadioSettingGroup("ani", "ANI Code List Settings")
1126
        group.append(ani)
1127

    
1128
        def _filter(name):
1129
            filtered = ""
1130
            for char in str(name):
1131
                if char in self._valid_chars:
1132
                    filtered += char
1133
                else:
1134
                    filtered += " "
1135
            return filtered
1136

    
1137
        end = 60 - self._aninames  # end of immutable ANI names
1138
        for i in range(0, end):
1139
            _codeobj = self._memobj.anicodes[i].code
1140
            _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
1141
            rs = RadioSettingValueString(0, 3, _code, False)
1142
            rs.set_charset(DTMF_CHARS)
1143
            rset = RadioSetting("anicodes/%i.code" % i,
1144
                                "ANI Code %i (NUM.%i)" % (i + 1, i + 1), rs)
1145
            rset.set_apply_callback(apply_code, self._memobj.anicodes[i], 3)
1146
            ani.append(rset)
1147

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

    
1159
            _nameobj = self._memobj.aninames[i - end].name
1160
            rs = RadioSettingValueString(0, 10, _filter(_nameobj))
1161
            rset = RadioSetting("aninames/%i.name" % (i - end),
1162
                                "ANI Code %i Name" % (i + 1), rs)
1163
            ani.append(rset)
1164

    
1165
        if self.MODEL in ["A36plus", "A36plus_8w"]:
1166
            custom = RadioSettingGroup("custom", "Custom Channel Names")
1167
            group.append(custom)
1168

    
1169
            for i in range(0, 30):
1170
                _nameobj = self._memobj.customnames[i].name
1171
                rs = RadioSettingValueString(0, 10, _filter(_nameobj))
1172
                rset = RadioSetting("customnames/%i.name" % i,
1173
                                    "Custom Name %i" % (i + 1), rs)
1174
                custom.append(rset)
1175

    
1176
        if self.MODEL in ["A36plus", "A36plus_8w"]:
1177
            # Menu 21: RX END TAIL
1178
            rs = RadioSettingValueList(TONERXEND_LIST,
1179
                                       TONERXEND_LIST[_settings.skey3_lp])
1180
            rset = RadioSetting("skey3_lp", "RX End Tail", rs)
1181
            basic.append(rset)
1182

    
1183
            # Menu 23: TAIL PHASE
1184
            rs = RadioSettingValueList(TAILPHASE_LIST,
1185
                                       TAILPHASE_LIST[_settings.rxendtail])
1186
            rset = RadioSetting("rxendtail", "Tail Phase", rs)
1187
            basic.append(rset)
1188

    
1189
            # Menu 20: SINGLE MODE
1190
            rs = RadioSettingValueBoolean(_settings.single_mode)
1191
            rset = RadioSetting("single_mode", "Single Display Mode", rs)
1192
            basic.append(rset)
1193

    
1194
            # Menu 48: UNLOCK SW CH
1195
            rs = RadioSettingValueBoolean(_settings.unlock_sw_ch)
1196
            rset = RadioSetting("unlock_sw_ch",
1197
                                "Override KB Lock for Channel Keys", rs)
1198
            basic.append(rset)
1199

    
1200
            # Menu 49: DIS S TABLE
1201
            rs = RadioSettingValueBoolean(_settings.dis_s_table)
1202
            rset = RadioSetting("dis_s_table", "Display S Meter", rs)
1203
            basic.append(rset)
1204

    
1205
        return group
1206

    
1207
    def set_settings(self, settings):
1208
        _settings = self._memobj.settings
1209
        for element in settings:
1210
            if not isinstance(element, RadioSetting):
1211
                self.set_settings(element)
1212
                continue
1213
            else:
1214
                try:
1215
                    name = element.get_name()
1216
                    if "." in name:
1217
                        bits = name.split(".")
1218
                        obj = self._memobj
1219
                        for bit in bits[:-1]:
1220
                            if "/" in bit:
1221
                                bit, index = bit.split("/", 1)
1222
                                index = int(index)
1223
                                obj = getattr(obj, bit)[index]
1224
                            else:
1225
                                obj = getattr(obj, bit)
1226
                        setting = bits[-1]
1227
                    else:
1228
                        obj = _settings
1229
                        setting = element.get_name()
1230

    
1231
                    if element.has_apply_callback():
1232
                        LOG.debug("Using apply callback")
1233
                        element.run_apply_callback()
1234
                    elif setting == "ani":
1235
                        setattr(obj, setting, int(element.value) - 1)
1236
                    elif setting == "fmradio":
1237
                        setattr(obj, setting, not int(element.value))
1238
                    elif element.value.get_mutable():
1239
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1240
                        setattr(obj, setting, element.value)
1241
                except Exception as e:
1242
                    LOG.debug(element.get_name(), e)
1243
                    raise
1244

    
1245
    @classmethod
1246
    def match_model(cls, filedata, filename):
1247
        # This radio has always been post-metadata, so never do
1248
        # old-school detection
1249
        return False
1250

    
1251

    
1252
@directory.register
1253
class RT470Radio(JC8810base):
1254
    """Radtel RT-470"""
1255
    VENDOR = "Radtel"
1256
    MODEL = "RT-470"
1257

    
1258
    # ==========
1259
    # Notice to developers:
1260
    # The RT-470 support in this driver is currently based upon...
1261
    # - v1.25a firmware (original pcb)
1262
    # - v2.11a firmware (pcb2)
1263
    # ==========
1264

    
1265
    # original pcb
1266
    _fingerprint_pcb1 = [b"\x00\x00\x00\x26\x00\x20\xD8\x04",
1267
                         b"\x00\x00\x00\x42\x00\x20\xF0\x04",
1268
                         b"\x00\x00\x00\x4A\x00\x20\xF8\x04",
1269
                         b"\x00\x00\x00\x3A\x00\x20\xE8\x04",  # fw 1.25A
1270
                         ]
1271

    
1272
    # pcb 2
1273
    _fingerprint_pcb2 = [b"\x00\x00\x00\x2C\x00\x20\xD8\x04",  # fw v2.11A
1274
                         b"\x00\x00\x00\x28\x00\x20\xD4\x04",  # fw v2.00
1275
                         ]
1276

    
1277
    _fingerprint = _fingerprint_pcb1 + _fingerprint_pcb2
1278

    
1279
    VALID_BANDS = [(16000000, 100000000),
1280
                   (100000000, 136000000),
1281
                   (136000000, 200000000),
1282
                   (200000000, 300000000),
1283
                   (300000000, 400000000),
1284
                   (400000000, 560000000),
1285
                   (740000000, 1000000000),
1286
                   ]
1287

    
1288

    
1289
@directory.register
1290
class RT470LRadio(JC8810base):
1291
    """Radtel RT-470L"""
1292
    VENDOR = "Radtel"
1293
    MODEL = "RT-470L"
1294

    
1295
    # ==========
1296
    # Notice to developers:
1297
    # The RT-470 support in this driver is currently based upon v1.17 firmware.
1298
    # ==========
1299

    
1300
    _fingerprint = [b"\x00\x00\x00\xfe\x00\x20\xAC\x04",
1301
                    b"\x00\x00\x00\x20\x00\x20\xCC\x04",
1302
                    b"\x00\x00\x00\x20\x00\x20\x07\x00"]
1303

    
1304
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
1305
                    chirp_common.PowerLevel("M", watts=4.00),
1306
                    chirp_common.PowerLevel("L", watts=2.00)]
1307

    
1308
    VALID_BANDS = [(108000000, 136000000),
1309
                   (136000000, 179000000),
1310
                   (220000000, 260000000),
1311
                   (330000000, 400000000),
1312
                   (400000000, 520000000)]
1313

    
1314

    
1315
@directory.register
1316
class RT470XRadio(RT470LRadio):
1317
    """Radtel RT-470X"""
1318
    VENDOR = "Radtel"
1319
    MODEL = "RT-470X"
1320

    
1321
    # ==========
1322
    # Notice to developers:
1323
    # The RT-470X support in this driver is currently based upon...
1324
    # - v1.18a firmware (original pcb)
1325
    # - v2.13a firmware (pcb2)
1326
    # ==========
1327

    
1328
    # original pcb
1329
    _fingerprint_pcb1 = [b"\x00\x00\x00\x20\x00\x20\xCC\x04",
1330
                         ]
1331

    
1332
    # pcb 2
1333
    _fingerprint_pcb2 = [b"\x00\x00\x00\x2C\x00\x20\xD8\x04",  # fw v2.10A
1334
                         b"\x00\x00\x00\x36\x00\x20\xDC\x04",  # fw v2.13A
1335
                         ]
1336

    
1337
    _fingerprint = _fingerprint_pcb1 + _fingerprint_pcb2
1338

    
1339
    RT470X_ORIG = False
1340

    
1341
    VALID_BANDS = [(100000000, 136000000),
1342
                   (136000000, 200000000),
1343
                   (200000000, 300000000),
1344
                   (300000000, 400000000),
1345
                   (400000000, 560000000)]
1346

    
1347

    
1348
@directory.register
1349
class HI8811Radio(RT470LRadio):
1350
    """Hiroyasu HI-8811"""
1351
    VENDOR = "Hiroyasu"
1352
    MODEL = "HI-8811"
1353

    
1354
    # ==========
1355
    # Notice to developers:
1356
    # The HI-8811 support in this driver is currently based upon...
1357
    # - v1.17 firmware (original pcb)
1358
    # - v2.00 firmware (pcb2)
1359
    # ==========
1360

    
1361
    # original pcb
1362
    _fingerprint_pcb1 = [b"\x00\x00\x00\xfe\x00\x20\xAC\x04",
1363
                         b"\x00\x00\x00\x20\x00\x20\xCC\x04",  # fw v1.17
1364
                         b"\x00\x00\x00\x20\x00\x20\x07\x00",
1365
                         ]
1366

    
1367
    # pcb 2
1368
    _fingerprint_pcb2 = [b"\x00\x00\x00\x28\x00\x20\xD4\x04",  # fw v2.00
1369
                         b"\x00\x00\x00\x28\x00\x20\x07\x00",  # fw v2.00
1370
                         ]
1371

    
1372
    _fingerprint = _fingerprint_pcb1 + _fingerprint_pcb2
1373

    
1374

    
1375
@directory.register
1376
class UVA37Radio(JC8810base):
1377
    """Anysecu UV-A37"""
1378
    VENDOR = "Anysecu"
1379
    MODEL = "UV-A37"
1380

    
1381
    # ==========
1382
    # Notice to developers:
1383
    # The UV-A37 support in this driver is currently based upon v1.24
1384
    # firmware.
1385
    # ==========
1386

    
1387
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
1388
                    chirp_common.PowerLevel("L", watts=1.00)]
1389

    
1390
    VALID_BANDS = [(108000000, 136000000),
1391
                   (136000000, 174000000),
1392
                   (200000000, 260000000),
1393
                   (350000000, 390000000),
1394
                   (400000000, 520000000)]
1395

    
1396
    _magic = b"PROGRAMJC37U"
1397
    _fingerprint = [b"\x00\x00\x00\xE4\x00\x20\x94\x04",
1398
                    b"\x00\x00\x00\xE8\x00\x20\x98\x04"]
1399

    
1400
    _ranges = [
1401
               (0x0000, 0x2000),
1402
               (0x8000, 0x8040),
1403
               (0x9000, 0x9040),
1404
               (0xA000, 0xA140),
1405
               (0xB000, 0xB440)
1406
              ]
1407
    _memsize = 0xB440
1408

    
1409

    
1410
@directory.register
1411
class A36plusRadio(JC8810base):
1412
    """Talkpod A36plus"""
1413
    VENDOR = "Talkpod"
1414
    MODEL = "A36plus"
1415

    
1416
    # ==========
1417
    # Notice to developers:
1418
    # The A36plus support in this driver is currently based upon v1.22
1419
    # firmware.
1420
    # ==========
1421

    
1422
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
1423
                    chirp_common.PowerLevel("L", watts=1.00)]
1424

    
1425
    VALID_BANDS = [(108000000, 136000000),
1426
                   (136000000, 180000000),
1427
                   (200000000, 260000000),
1428
                   (350000000, 400000000),
1429
                   (400000000, 520000000),
1430
                   ]
1431

    
1432
    _magic = b"PROGRAMJC37U"
1433
    _fingerprint = [b"\x00\x00\x00\x42\x00\x20\xF0\x04",
1434
                    b"\x00\x00\x00\x5A\x00\x20\x08\x05",  # fw 1.18
1435
                    b"\x00\x00\x00\x9E\x00\x20\x0C\x05",  # fw 1.22
1436
                    b"\x00\x00\x00\xFA\x00\x20\x40\x05",  # fw 1.4
1437
                    ]
1438

    
1439
    _ranges = [
1440
               (0x0000, 0x4000),
1441
               (0x8000, 0x8040),
1442
               (0x9000, 0x9040),
1443
               (0xA000, 0xA140),
1444
               (0xB000, 0xB440)
1445
              ]
1446
    _memsize = 0xB440
1447
    _upper = 512  # fw 1.22 expands from 256 to 512 channels
1448
    _aninames = 10
1449
    _mem_params = (_upper,  # number of channels
1450
                   _aninames,  # number of aninames
1451
                   )
1452

    
1453
    def process_mmap(self):
1454
        mem_format = MEM_FORMAT % self._mem_params + MEM_FORMAT_A36PLUS
1455
        self._memobj = bitwise.parse(mem_format, self._mmap)
1456

    
1457

    
1458
@directory.register
1459
class A36plus8wRadio(A36plusRadio):
1460
    """Talkpod A36plus8w"""
1461
    VENDOR = "Talkpod"
1462
    MODEL = "A36plus_8w"
1463

    
1464
    # ==========
1465
    # Notice to developers:
1466
    # The A36plus 8w support in this driver is currently based upon v1.6
1467
    # firmware.
1468
    # ==========
1469

    
1470
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=8.00),
1471
                    chirp_common.PowerLevel("L", watts=1.00)]
1472

    
1473
    _fingerprint = [b"\x00\x00\x00\xFA\x00\x20\x40\x05",  # fw 1.4
1474
                    b"\x00\x00\x00\xD8\x00\x20\x58\x05",  # fw 1.6
1475
                    ]
1476

    
1477

    
1478
@directory.register
1479
class AR730Radio(UVA37Radio):
1480
    """Abbree AR730"""
1481
    VENDOR = "Abbree"
1482
    MODEL = "AR-730"
1483

    
1484
    # ==========
1485
    # Notice to developers:
1486
    # The AR-730 support in this driver is currently based upon v1.24
1487
    # firmware.
1488
    # ==========
1489

    
1490
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
1491
                    chirp_common.PowerLevel("L", watts=1.00)]
1492

    
1493
    VALID_BANDS = [(108000000, 136000000),
1494
                   (136000000, 180000000),
1495
                   (200000000, 260000000),
1496
                   (350000000, 390000000),
1497
                   (400000000, 520000000)]
    (1-1/1)