Project

General

Profile

Bug #10915 » mml_jc8810_rt470x-v2.py

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

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

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

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

    
172
#seekto 0xB000;
173
struct {
174
  u8 code[3];         //      3-character ANI Code Groups
175
} anicodes[60];
176

    
177
#seekto 0xB0C0;
178
struct {
179
  char name[10];      //      10-character ANI Code Group Names
180
  u8 unused[6];
181
} aninames[%d];
182

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

    
189
"""
190

    
191

    
192
CMD_ACK = b"\x06"
193

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

    
196
DTMF_CHARS = "0123456789 *#ABCD"
197

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

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

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

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

    
261

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

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

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

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

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

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

    
294

    
295
def _exit_programming_mode(radio):
296
    serial = radio.pipe
297
    try:
298
        serial.write(b"E")
299
    except Exception:
300
        raise errors.RadioError("Radio refused to exit programming mode")
301

    
302

    
303
def _read_block(radio, block_addr, block_size):
304
    serial = radio.pipe
305

    
306
    cmd = struct.pack(">cHb", b'R', block_addr, block_size)
307
    expectedresponse = b"R" + cmd[1:]
308
    LOG.debug("Reading block %04x..." % (block_addr))
309

    
310
    try:
311
        serial.write(cmd)
312
        response = serial.read(4 + block_size)
313
        if response[:4] != expectedresponse:
314
            raise Exception("Error reading block %04x." % (block_addr))
315

    
316
        block_data = response[4:]
317
    except Exception:
318
        raise errors.RadioError("Failed to read block at %04x" % block_addr)
319

    
320
    return block_data
321

    
322

    
323
def _write_block(radio, block_addr, block_size):
324
    serial = radio.pipe
325

    
326
    cmd = struct.pack(">cHb", b'W', block_addr, block_size)
327
    data = radio.get_mmap()[block_addr:block_addr + block_size]
328

    
329
    LOG.debug("Writing Data:")
330
    LOG.debug(util.hexprint(cmd + data))
331

    
332
    try:
333
        serial.write(cmd + data)
334
        if serial.read(1) != CMD_ACK:
335
            raise Exception("No ACK")
336
    except Exception:
337
        raise errors.RadioError("Failed to send block "
338
                                "to radio at %04x" % block_addr)
339

    
340

    
341
def do_download(radio):
342
    LOG.debug("download")
343
    _enter_programming_mode(radio)
344

    
345
    data = b""
346

    
347
    status = chirp_common.Status()
348
    status.msg = "Cloning from radio"
349

    
350
    status.cur = 0
351
    status.max = radio._memsize
352

    
353
    for addr in range(0, radio._memsize, radio.BLOCK_SIZE):
354
        status.cur = addr + radio.BLOCK_SIZE
355
        radio.status_fn(status)
356

    
357
        block = _read_block(radio, addr, radio.BLOCK_SIZE)
358
        data += block
359

    
360
        LOG.debug("Address: %04x" % addr)
361
        LOG.debug(util.hexprint(block))
362

    
363
    _exit_programming_mode(radio)
364

    
365
    return memmap.MemoryMapBytes(data)
366

    
367

    
368
def do_upload(radio):
369
    status = chirp_common.Status()
370
    status.msg = "Uploading to radio"
371

    
372
    _enter_programming_mode(radio)
373

    
374
    status.cur = 0
375
    status.max = radio._memsize
376

    
377
    for start_addr, end_addr in radio._ranges:
378
        for addr in range(start_addr, end_addr, radio.BLOCK_SIZE_UP):
379
            status.cur = addr + radio.BLOCK_SIZE_UP
380
            radio.status_fn(status)
381
            _write_block(radio, addr, radio.BLOCK_SIZE_UP)
382

    
383
    _exit_programming_mode(radio)
384

    
385

    
386
def _split(rf, f1, f2):
387
    """Returns False if the two freqs are in the same band (no split)
388
    or True otherwise"""
389

    
390
    # determine if the two freqs are in the same band
391
    for low, high in rf.valid_bands:
392
        if f1 >= low and f1 <= high and \
393
                f2 >= low and f2 <= high:
394
            # if the two freqs are on the same Band this is not a split
395
            return False
396

    
397
    # if you get here is because the freq pairs are split
398
    return True
399

    
400

    
401
class JC8810base(chirp_common.CloneModeRadio):
402
    """MML JC-8810"""
403
    VENDOR = "MML"
404
    MODEL = "JC-8810base"
405
    BAUD_RATE = 57600
406
    NEEDS_COMPAT_SERIAL = False
407
    BLOCK_SIZE = 0x40
408
    BLOCK_SIZE_UP = 0x40
409

    
410
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=10.00),
411
                    chirp_common.PowerLevel("M", watts=8.00),
412
                    chirp_common.PowerLevel("L", watts=4.00)]
413

    
414
    VALID_BANDS = [(108000000, 136000000),
415
                   (136000000, 180000000),
416
                   (200000000, 260000000),
417
                   (330000000, 400000000),
418
                   (400000000, 520000000)]
419

    
420
    _magic = b"PROGRAMJC81U"
421
    _fingerprint = [b"\x00\x00\x00\x26\x00\x20\xD8\x04",
422
                    b"\x00\x00\x00\x42\x00\x20\xF0\x04",
423
                    b"\x00\x00\x00\x4A\x00\x20\xF8\x04"]
424

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

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

    
466
    def process_mmap(self):
467
        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
468

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

    
485
    def sync_out(self):
486
        """Upload to radio"""
487
        try:
488
            do_upload(self)
489
        except Exception:
490
            # If anything unexpected happens, make sure we raise
491
            # a RadioError and log the problem
492
            LOG.exception('Unexpected error during upload')
493
            raise errors.RadioError('Unexpected error communicating '
494
                                    'with the radio')
495

    
496
    def _is_txinh(self, _mem):
497
        raw_tx = ""
498
        for i in range(0, 4):
499
            raw_tx += _mem.txfreq[i].get_raw()
500
        return raw_tx == "\xFF\xFF\xFF\xFF"
501

    
502
    def get_memory(self, number):
503
        """Get the mem representation from the radio image"""
504
        _mem = self._memobj.memory[number - 1]
505

    
506
        # Create a high-level memory object to return to the UI
507
        mem = chirp_common.Memory()
508

    
509
        # Memory number
510
        mem.number = number
511

    
512
        if _mem.get_raw()[0] == "\xff":
513
            mem.empty = True
514
            return mem
515

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

    
540
        for char in _mem.name:
541
            if str(char) == "\xFF":
542
                char = " "  # may have 0xFF mid-name
543
            mem.name += str(char)
544
        mem.name = mem.name.rstrip()
545

    
546
        dtcs_pol = ["N", "N"]
547

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

    
564
        if _mem.rxtone in [0, 0xFFFF]:
565
            rxmode = ""
566
        elif _mem.rxtone >= 0x0258:
567
            rxmode = "Tone"
568
            mem.ctone = int(_mem.rxtone) / 10.0
569
        elif _mem.rxtone <= 0x0258:
570
            rxmode = "DTCS"
571
            if _mem.rxtone >= 0x6A:
572
                index = _mem.rxtone - 0x6A
573
                dtcs_pol[1] = "R"
574
            else:
575
                index = _mem.rxtone - 1
576
            mem.rx_dtcs = DTCS[index]
577
        else:
578
            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
579

    
580
        if txmode == "Tone" and not rxmode:
581
            mem.tmode = "Tone"
582
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
583
            mem.tmode = "TSQL"
584
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
585
            mem.tmode = "DTCS"
586
        elif rxmode or txmode:
587
            mem.tmode = "Cross"
588
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
589

    
590
        mem.dtcs_polarity = "".join(dtcs_pol)
591

    
592
        if not _mem.scan:
593
            mem.skip = "S"
594

    
595
        _levels = self.POWER_LEVELS
596
        if self.MODEL in ["A36plus", "UV-A37", "AR-730"]:
597
            if _mem.txpower == TXPOWER_HIGH:
598
                mem.power = _levels[0]
599
            elif _mem.txpower == TXPOWER_LOW:
600
                mem.power = _levels[1]
601
            else:
602
                LOG.error('%s: get_mem: unhandled power level: 0x%02x' %
603
                          (mem.name, _mem.txpower))
604
        else:
605
            if _mem.txpower == TXPOWER_HIGH:
606
                mem.power = _levels[0]
607
            elif _mem.txpower == TXPOWER_MID:
608
                mem.power = _levels[1]
609
            elif _mem.txpower == TXPOWER_LOW:
610
                mem.power = _levels[2]
611
            else:
612
                LOG.error('%s: get_mem: unhandled power level: 0x%02x' %
613
                          (mem.name, _mem.txpower))
614

    
615
        mem.mode = _mem.narrow and "NFM" or "FM"
616

    
617
        mem.extra = RadioSettingGroup("Extra", "extra")
618

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

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

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

    
634
        return mem
635

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

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

    
643
        _mem.set_raw("\x00" * 16 + "\xFF" * 16)
644

    
645
        _mem.rxfreq = mem.freq / 10
646

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

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

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

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

    
700
        _mem.scan = mem.skip != "S"
701
        _mem.narrow = mem.mode == "NFM"
702

    
703
        _levels = self.POWER_LEVELS
704
        if self.MODEL in ["A36plus", "UV-A37", "AR-730"]:
705
            if mem.power is None:
706
                _mem.txpower = TXPOWER_HIGH
707
            elif mem.power == _levels[0]:
708
                _mem.txpower = TXPOWER_HIGH
709
            elif mem.power == _levels[1]:
710
                _mem.txpower = TXPOWER_LOW
711
            else:
712
                LOG.error('%s: set_mem: unhandled power level: %s' %
713
                          (mem.name, mem.power))
714
        else:
715
            if mem.power is None:
716
                _mem.txpower = TXPOWER_HIGH
717
            elif mem.power == _levels[0]:
718
                _mem.txpower = TXPOWER_HIGH
719
            elif mem.power == _levels[1]:
720
                _mem.txpower = TXPOWER_MID
721
            elif mem.power == _levels[2]:
722
                _mem.txpower = TXPOWER_LOW
723
            else:
724
                LOG.error('%s: set_mem: unhandled power level: %s' %
725
                          (mem.name, mem.power))
726

    
727
        for setting in mem.extra:
728
            if setting.get_name() == "scramble_type":
729
                setattr(_mem, setting.get_name(), int(setting.value) + 8)
730
                setattr(_mem, "scramble_type2", int(setting.value) + 8)
731
            else:
732
                setattr(_mem, setting.get_name(), setting.value)
733

    
734
    def get_settings(self):
735
        _dtmf = self._memobj.dtmf
736
        _settings = self._memobj.settings
737
        basic = RadioSettingGroup("basic", "Basic Settings")
738
        group = RadioSettings(basic)
739

    
740
        # Menu 12: TOT
741
        rs = RadioSettingValueList(TOT_LIST, TOT_LIST[_settings.tot])
742
        rset = RadioSetting("tot", "Time Out Timer", rs)
743
        basic.append(rset)
744

    
745
        # Menu 00: SQL
746
        rs = RadioSettingValueInteger(0, 9, _settings.sql)
747
        rset = RadioSetting("sql", "Squelch Level", rs)
748
        basic.append(rset)
749

    
750
        # Menu 13: VOX
751
        rs = RadioSettingValueList(OFF1TO9_LIST, OFF1TO9_LIST[_settings.vox])
752
        rset = RadioSetting("vox", "VOX", rs)
753
        basic.append(rset)
754

    
755
        # Menu 39: VOX DELAY
756
        rs = RadioSettingValueList(VOXD_LIST, VOXD_LIST[_settings.voxd])
757
        rset = RadioSetting("voxd", "VOX Delay", rs)
758
        basic.append(rset)
759

    
760
        # Menu 15: VOICE
761
        rs = RadioSettingValueBoolean(_settings.voice)
762
        rset = RadioSetting("voice", "Voice Prompts", rs)
763
        basic.append(rset)
764

    
765
        if self.MODEL not in ["A36plus", "UV-A37", "AR-730"]:
766
            # Menu 17: LANGUAGE
767
            rs = RadioSettingValueList(LANGUAGE_LIST,
768
                                       LANGUAGE_LIST[_settings.language])
769
            rset = RadioSetting("language", "Voice", rs)
770
            basic.append(rset)
771

    
772
        # Menu 23: ABR
773
        rs = RadioSettingValueList(ABR_LIST, ABR_LIST[_settings.abr])
774
        rset = RadioSetting("abr", "Auto BackLight", rs)
775
        basic.append(rset)
776

    
777
        # Work Mode A
778
        rs = RadioSettingValueList(WORKMODE_LIST,
779
                                   WORKMODE_LIST[_settings.vfomra])
780
        rset = RadioSetting("vfomra", "Work Mode A", rs)
781
        basic.append(rset)
782

    
783
        # Work Mode B
784
        rs = RadioSettingValueList(WORKMODE_LIST,
785
                                   WORKMODE_LIST[_settings.vfomrb])
786
        rset = RadioSetting("vfomrb", "Work Mode B", rs)
787
        basic.append(rset)
788

    
789
        # Menu 19: SC-REV
790
        rs = RadioSettingValueList(SCREV_LIST, SCREV_LIST[_settings.screv])
791
        rset = RadioSetting("screv", "Scan Resume Method", rs)
792
        basic.append(rset)
793

    
794
        # Menu 10: SAVE
795
        rs = RadioSettingValueList(SAVE_LIST,
796
                                   SAVE_LIST[_settings.save])
797
        rset = RadioSetting("save", "Battery Save Mode", rs)
798
        basic.append(rset)
799

    
800
        # Menu 42: MDF-A
801
        rs = RadioSettingValueList(MDF_LIST, MDF_LIST[_settings.mdfa])
802
        rset = RadioSetting("mdfa", "Memory Display Format A", rs)
803
        basic.append(rset)
804

    
805
        # Menu 43: MDF-B
806
        rs = RadioSettingValueList(MDF_LIST, MDF_LIST[_settings.mdfb])
807
        rset = RadioSetting("mdfb", "Memory Display Format B", rs)
808
        basic.append(rset)
809

    
810
        # Menu 33: DTMFST (DTMF ST)
811
        rs = RadioSettingValueList(DTMFST_LIST, DTMFST_LIST[_settings.dtmfst])
812
        rset = RadioSetting("dtmfst", "DTMF Side Tone", rs)
813
        basic.append(rset)
814

    
815
        # Menu 37: PTT-LT
816
        rs = RadioSettingValueList(PTTLT_LIST, PTTLT_LIST[_settings.pttlt])
817
        rset = RadioSetting("pttlt", "PTT Delay", rs)
818
        basic.append(rset)
819

    
820
        rs = RadioSettingValueInteger(1, 60, _settings.ani + 1)
821
        rset = RadioSetting("ani", "ANI", rs)
822
        basic.append(rset)
823

    
824
        # Menu 20: PF2
825
        def apply_skey2s_listvalue(setting, obj):
826
            LOG.debug("Setting value: " + str(setting.value) + " from list")
827
            val = str(setting.value)
828
            index = SKEY2S_CHOICES.index(val)
829
            val = SKEY2S_VALUES[index]
830
            obj.set_value(val)
831

    
832
        if self.MODEL in ["RT-470"]:
833
            unwanted = [0, 7, 9, 10, 11, 12]
834
        elif self.MODEL in ["HI-8811", "RT-470L", "RT-470X"]:
835
            unwanted = [9, 10, 11, 12]
836
        elif self.MODEL in ["UV-A37", "AR-730"]:
837
            unwanted = [0, 5, 7, 9, 10, 11, 12]
838
        elif self.MODEL in ["A36plus"]:
839
            unwanted = [0, 5, 7, 9, 10, 11]
840
        else:
841
            unwanted = []
842
        SKEY2S_CHOICES = ALL_SKEY_CHOICES.copy()
843
        SKEY2S_VALUES = ALL_SKEY_VALUES.copy()
844
        for ele in sorted(unwanted, reverse=True):
845
            del SKEY2S_CHOICES[ele]
846
            del SKEY2S_VALUES[ele]
847

    
848
        if _settings.skey2_sp in SKEY2S_VALUES:
849
            idx = SKEY2S_VALUES.index(_settings.skey2_sp)
850
        else:
851
            idx = SKEY2S_VALUES.index(0x07)  # default FM
852
        rs = RadioSettingValueList(SKEY2S_CHOICES, SKEY2S_CHOICES[idx])
853
        rset = RadioSetting("skey2_sp", "PF2 Key (Short Press)", rs)
854
        rset.set_apply_callback(apply_skey2s_listvalue, _settings.skey2_sp)
855
        basic.append(rset)
856

    
857
        # Menu 21: PF2 LONG PRESS
858
        def apply_skey2l_listvalue(setting, obj):
859
            LOG.debug("Setting value: " + str(setting.value) + " from list")
860
            val = str(setting.value)
861
            index = SKEY2L_CHOICES.index(val)
862
            val = SKEY2L_VALUES[index]
863
            obj.set_value(val)
864

    
865
        if self.MODEL in ["RT-470"]:
866
            unwanted = [0, 7, 8, 9, 10, 11, 12]
867
        elif self.MODEL in ["HI-8811", "RT-470L", "RT-470X"]:
868
            unwanted = [8, 9, 10, 11, 12]
869
        elif self.MODEL in ["UV-A37", "AR-730"]:
870
            unwanted = [0, 5, 7, 8, 10, 11, 12]
871
        elif self.MODEL in ["A36plus"]:
872
            unwanted = [0, 5, 7, 8, 11, 12]
873
        else:
874
            unwanted = []
875
        SKEY2L_CHOICES = ALL_SKEY_CHOICES.copy()
876
        SKEY2L_VALUES = ALL_SKEY_VALUES.copy()
877
        for ele in sorted(unwanted, reverse=True):
878
            del SKEY2L_CHOICES[ele]
879
            del SKEY2L_VALUES[ele]
880

    
881
        if _settings.skey2_lp in SKEY2L_VALUES:
882
            idx = SKEY2L_VALUES.index(_settings.skey2_lp)
883
        else:
884
            idx = SKEY2L_VALUES.index(0x1D)  # default Search
885
        rs = RadioSettingValueList(SKEY2L_CHOICES, SKEY2L_CHOICES[idx])
886
        rset = RadioSetting("skey2_lp", "PF2 Key (Long Press)", rs)
887
        rset.set_apply_callback(apply_skey2l_listvalue, _settings.skey2_lp)
888
        basic.append(rset)
889

    
890
        # Menu 22: PF3
891
        def apply_skey3s_listvalue(setting, obj):
892
            LOG.debug("Setting value: " + str(setting.value) + " from list")
893
            val = str(setting.value)
894
            index = SKEY3S_CHOICES.index(val)
895
            val = SKEY3S_VALUES[index]
896
            obj.set_value(val)
897

    
898
        if self.MODEL in ["RT-470"]:
899
            unwanted = [0, 7, 8, 9, 10, 11, 12]
900
        elif self.MODEL in ["HI-8811", "RT-470L", "RT-470X"]:
901
            unwanted = [8, 9, 10, 11, 12]
902
        elif self.MODEL in ["UV-A37", "AR-730"]:
903
            unwanted = [0, 5, 7, 8, 9, 10, 11, 12]
904
        elif self.MODEL in ["A36plus"]:
905
            unwanted = [0, 5, 7, 8, 11]
906
        else:
907
            unwanted = []
908
        SKEY3S_CHOICES = ALL_SKEY_CHOICES.copy()
909
        SKEY3S_VALUES = ALL_SKEY_VALUES.copy()
910
        for ele in sorted(unwanted, reverse=True):
911
            del SKEY3S_CHOICES[ele]
912
            del SKEY3S_VALUES[ele]
913

    
914
        if _settings.skey3_sp in SKEY3S_VALUES:
915
            idx = SKEY3S_VALUES.index(_settings.skey3_sp)
916
        else:
917
            idx = SKEY3S_VALUES.index(0x0C)  # default NOAA
918
        rs = RadioSettingValueList(SKEY3S_CHOICES, SKEY3S_CHOICES[idx])
919
        rset = RadioSetting("skey3_sp", "PF3 Key (Short Press)", rs)
920
        rset.set_apply_callback(apply_skey3s_listvalue, _settings.skey3_sp)
921
        basic.append(rset)
922

    
923
        if self.MODEL in ["HI-8811", "RT-470L", "RT-470X"]:
924
            # Menu 24: PF3 LONG PRESS (RT-470L)
925
            def apply_skey3l_listvalue(setting, obj):
926
                LOG.debug("Setting value: " + str(setting.value) +
927
                          " from list")
928
                val = str(setting.value)
929
                index = SKEY3L_CHOICES.index(val)
930
                val = SKEY2L_VALUES[index]
931
                obj.set_value(val)
932

    
933
            if self.MODEL in ["HI-8811", "RT-470L", "RT-470X"]:
934
                unwanted = [8, 9, 10, 11, 12]
935
            else:
936
                unwanted = []
937
            SKEY3L_CHOICES = ALL_SKEY_CHOICES.copy()
938
            SKEY3L_VALUES = ALL_SKEY_VALUES.copy()
939
            for ele in sorted(unwanted, reverse=True):
940
                del SKEY3L_CHOICES[ele]
941
                del SKEY3L_VALUES[ele]
942

    
943
            if _settings.skey3_lp in SKEY3L_VALUES:
944
                idx = SKEY3L_VALUES.index(_settings.skey3_lp)
945
            else:
946
                idx = SKEY3L_VALUES.index(0x1D)  # default SEARCH
947
            rs = RadioSettingValueList(SKEY3L_CHOICES, SKEY3L_CHOICES[idx])
948
            rset = RadioSetting("skey3_lp", "PF3 Key (Long Press)", rs)
949
            rset.set_apply_callback(apply_skey3l_listvalue,
950
                                    _settings.skey3_lp)
951
            basic.append(rset)
952

    
953
        # Menu 25: TOP KEY (RT-470L)
954
        def apply_skeytop_listvalue(setting, obj):
955
            LOG.debug("Setting value: " + str(setting.value) +
956
                      " from list")
957
            val = str(setting.value)
958
            index = SKEYTOP_CHOICES.index(val)
959
            val = SKEYTOP_VALUES[index]
960
            obj.set_value(val)
961

    
962
        if self.MODEL in ["RT-470"]:
963
            # ==========
964
            # Notice to developers:
965
            # The RT-470 v1.22 firmware added 'hidden' support for the
966
            # Top Key (Short Press) feature. RT-470 radios with a firmware
967
            # version prior to v1.22 will not honor the Top Key (Short
968
            # Press) setting in CHIRP.
969
            # ==========
970
            unwanted = [0, 7, 8, 9, 10, 11, 12]
971
        elif self.MODEL in ["HI-8811", "RT-470L", "RT-470X"]:
972
            unwanted = [8, 9, 10, 11, 12]
973
        elif self.MODEL in ["UV-A37", "AR-730"]:
974
            unwanted = [0, 5, 7, 8, 9, 10, 11, 12]
975
        elif self.MODEL in ["A36plus"]:
976
            unwanted = [0, 5, 7, 8, 11]
977
        else:
978
            unwanted = []
979
        SKEYTOP_CHOICES = ALL_SKEY_CHOICES.copy()
980
        SKEYTOP_VALUES = ALL_SKEY_VALUES.copy()
981
        for ele in sorted(unwanted, reverse=True):
982
            del SKEYTOP_CHOICES[ele]
983
            del SKEYTOP_VALUES[ele]
984

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1063
        rs = RadioSettingValueBoolean(_settings.alarmsound)
1064
        rset = RadioSetting("alarmsound", "Alarm Sound", rs)
1065
        basic.append(rset)
1066

    
1067
        # Menu 44: TDR
1068
        rs = RadioSettingValueBoolean(_settings.tdr)
1069
        rset = RadioSetting("tdr", "TDR", rs)
1070
        basic.append(rset)
1071

    
1072
        rs = RadioSettingValueBoolean(not _settings.fmradio)
1073
        rset = RadioSetting("fmradio", "FM Radio", rs)
1074
        basic.append(rset)
1075

    
1076
        rs = RadioSettingValueBoolean(_settings.kblock)
1077
        rset = RadioSetting("kblock", "KB Lock", rs)
1078
        basic.append(rset)
1079

    
1080
        # Menu 16: BEEP PROMPT
1081
        rs = RadioSettingValueBoolean(_settings.beep)
1082
        rset = RadioSetting("beep", "Beep", rs)
1083
        basic.append(rset)
1084

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

    
1092
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1093
        group.append(dtmf)
1094

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

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

    
1114
        rs = RadioSettingValueList(DTMFSPEED_LIST,
1115
                                   DTMFSPEED_LIST[_dtmf.dtmfon])
1116
        rset = RadioSetting("dtmf.dtmfon", "DTMF Speed (on)", rs)
1117
        dtmf.append(rset)
1118

    
1119
        rs = RadioSettingValueList(DTMFSPEED_LIST,
1120
                                   DTMFSPEED_LIST[_dtmf.dtmfoff])
1121
        rset = RadioSetting("dtmf.dtmfoff", "DTMF Speed (off)", rs)
1122
        dtmf.append(rset)
1123

    
1124
        rs = RadioSettingValueList(PTTID_LIST, PTTID_LIST[_settings.pttid])
1125
        rset = RadioSetting("pttid", "PTT ID", rs)
1126
        dtmf.append(rset)
1127

    
1128
        ani = RadioSettingGroup("ani", "ANI Code List Settings")
1129
        group.append(ani)
1130

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

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

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

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

    
1168
        if self.MODEL == "A36plus":
1169
            custom = RadioSettingGroup("custom", "Custom Channel Names")
1170
            group.append(custom)
1171

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

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

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

    
1192
        return group
1193

    
1194
    def set_settings(self, settings):
1195
        _settings = self._memobj.settings
1196
        for element in settings:
1197
            if not isinstance(element, RadioSetting):
1198
                self.set_settings(element)
1199
                continue
1200
            else:
1201
                try:
1202
                    name = element.get_name()
1203
                    if "." in name:
1204
                        bits = name.split(".")
1205
                        obj = self._memobj
1206
                        for bit in bits[:-1]:
1207
                            if "/" in bit:
1208
                                bit, index = bit.split("/", 1)
1209
                                index = int(index)
1210
                                obj = getattr(obj, bit)[index]
1211
                            else:
1212
                                obj = getattr(obj, bit)
1213
                        setting = bits[-1]
1214
                    else:
1215
                        obj = _settings
1216
                        setting = element.get_name()
1217

    
1218
                    if element.has_apply_callback():
1219
                        LOG.debug("Using apply callback")
1220
                        element.run_apply_callback()
1221
                    elif setting == "ani":
1222
                        setattr(obj, setting, int(element.value) - 1)
1223
                    elif setting == "fmradio":
1224
                        setattr(obj, setting, not int(element.value))
1225
                    elif element.value.get_mutable():
1226
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1227
                        setattr(obj, setting, element.value)
1228
                except Exception as e:
1229
                    LOG.debug(element.get_name(), e)
1230
                    raise
1231

    
1232
    @classmethod
1233
    def match_model(cls, filedata, filename):
1234
        # This radio has always been post-metadata, so never do
1235
        # old-school detection
1236
        return False
1237

    
1238

    
1239
@directory.register
1240
class RT470Radio(JC8810base):
1241
    """Radtel RT-470"""
1242
    VENDOR = "Radtel"
1243
    MODEL = "RT-470"
1244

    
1245
    # ==========
1246
    # Notice to developers:
1247
    # The RT-470 support in this driver is currently based upon v1.25 firmware.
1248
    # ==========
1249

    
1250
    _fingerprint = [b"\x00\x00\x00\x26\x00\x20\xD8\x04",
1251
                    b"\x00\x00\x00\x42\x00\x20\xF0\x04",
1252
                    b"\x00\x00\x00\x4A\x00\x20\xF8\x04",
1253
                    b"\x00\x00\x00\x3A\x00\x20\xE8\x04",  # fw 1.25A
1254
                    ]
1255

    
1256
    VALID_BANDS = [(16000000, 100000000),
1257
                   (100000000, 136000000),
1258
                   (136000000, 200000000),
1259
                   (200000000, 300000000),
1260
                   (300000000, 400000000),
1261
                   (400000000, 560000000),
1262
                   (740000000, 1000000000),
1263
                   ]
1264

    
1265

    
1266
@directory.register
1267
class RT470LRadio(JC8810base):
1268
    """Radtel RT-470L"""
1269
    VENDOR = "Radtel"
1270
    MODEL = "RT-470L"
1271

    
1272
    # ==========
1273
    # Notice to developers:
1274
    # The RT-470 support in this driver is currently based upon v1.17 firmware.
1275
    # ==========
1276

    
1277
    _fingerprint = [b"\x00\x00\x00\xfe\x00\x20\xAC\x04",
1278
                    b"\x00\x00\x00\x20\x00\x20\xCC\x04",
1279
                    b"\x00\x00\x00\x20\x00\x20\x07\x00"]
1280

    
1281
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
1282
                    chirp_common.PowerLevel("M", watts=4.00),
1283
                    chirp_common.PowerLevel("L", watts=2.00)]
1284

    
1285
    VALID_BANDS = [(108000000, 136000000),
1286
                   (136000000, 179000000),
1287
                   (220000000, 260000000),
1288
                   (330000000, 400000000),
1289
                   (400000000, 520000000)]
1290

    
1291

    
1292
@directory.register
1293
class RT470XRadio(RT470LRadio):
1294
    """Radtel RT-470X"""
1295
    VENDOR = "Radtel"
1296
    MODEL = "RT-470X"
1297

    
1298
    # ==========
1299
    # Notice to developers:
1300
    # The RT-470 support in this driver is currently based upon v1.18 firmware.
1301
    # ==========
1302

    
1303
    _fingerprint = [b"\x00\x00\x00\x20\x00\x20\xCC\x04",
1304
                    b"\x00\x00\x00\x2C\x00\x20\xD8\x04",  # fw v2.10A
1305
                    ]
1306

    
1307
    VALID_BANDS = [(100000000, 136000000),
1308
                   (136000000, 200000000),
1309
                   (200000000, 300000000),
1310
                   (300000000, 400000000),
1311
                   (400000000, 560000000)]
1312

    
1313

    
1314
@directory.register
1315
class HI8811Radio(RT470LRadio):
1316
    """Hiroyasu HI-8811"""
1317
    VENDOR = "Hiroyasu"
1318
    MODEL = "HI-8811"
1319

    
1320
    # ==========
1321
    # Notice to developers:
1322
    # The HI-8811 support in this driver is currently based upon v1.17
1323
    # firmware.
1324
    # ==========
1325

    
1326

    
1327
@directory.register
1328
class UVA37Radio(JC8810base):
1329
    """Anysecu UV-A37"""
1330
    VENDOR = "Anysecu"
1331
    MODEL = "UV-A37"
1332

    
1333
    # ==========
1334
    # Notice to developers:
1335
    # The UV-A37 support in this driver is currently based upon v1.24
1336
    # firmware.
1337
    # ==========
1338

    
1339
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
1340
                    chirp_common.PowerLevel("L", watts=1.00)]
1341

    
1342
    VALID_BANDS = [(108000000, 136000000),
1343
                   (136000000, 174000000),
1344
                   (200000000, 260000000),
1345
                   (350000000, 390000000),
1346
                   (400000000, 520000000)]
1347

    
1348
    _magic = b"PROGRAMJC37U"
1349
    _fingerprint = [b"\x00\x00\x00\xE4\x00\x20\x94\x04",
1350
                    b"\x00\x00\x00\xE8\x00\x20\x98\x04"]
1351

    
1352
    _ranges = [
1353
               (0x0000, 0x2000),
1354
               (0x8000, 0x8040),
1355
               (0x9000, 0x9040),
1356
               (0xA000, 0xA140),
1357
               (0xB000, 0xB440)
1358
              ]
1359
    _memsize = 0xB440
1360

    
1361

    
1362
@directory.register
1363
class A36plusRadio(JC8810base):
1364
    """Talkpod A36plus"""
1365
    VENDOR = "Talkpod"
1366
    MODEL = "A36plus"
1367

    
1368
    # ==========
1369
    # Notice to developers:
1370
    # The A36plus support in this driver is currently based upon v1.22
1371
    # firmware.
1372
    # ==========
1373

    
1374
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
1375
                    chirp_common.PowerLevel("L", watts=1.00)]
1376

    
1377
    VALID_BANDS = [(108000000, 136000000),
1378
                   (136000000, 180000000),
1379
                   (200000000, 260000000),
1380
                   (350000000, 400000000),
1381
                   (400000000, 520000000),
1382
                   ]
1383

    
1384
    _magic = b"PROGRAMJC37U"
1385
    _fingerprint = [b"\x00\x00\x00\x42\x00\x20\xF0\x04",
1386
                    b"\x00\x00\x00\x5A\x00\x20\x08\x05",  # fw 1.18
1387
                    b"\x00\x00\x00\x9E\x00\x20\x0C\x05",  # fw 1.22
1388
                    ]
1389

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

    
1404

    
1405
@directory.register
1406
class AR730Radio(UVA37Radio):
1407
    """Abbree AR730"""
1408
    VENDOR = "Abbree"
1409
    MODEL = "AR-730"
1410

    
1411
    # ==========
1412
    # Notice to developers:
1413
    # The AR-730 support in this driver is currently based upon v1.24
1414
    # firmware.
1415
    # ==========
1416

    
1417
    POWER_LEVELS = [chirp_common.PowerLevel("H", watts=5.00),
1418
                    chirp_common.PowerLevel("L", watts=1.00)]
1419

    
1420
    VALID_BANDS = [(108000000, 136000000),
1421
                   (136000000, 180000000),
1422
                   (200000000, 260000000),
1423
                   (350000000, 390000000),
1424
                   (400000000, 520000000)]
(10-10/17)