Project

General

Profile

Feature #9265 » retevis_rt76p_128_#1.py

Jim Unroe, 10/03/2022 02:02 AM

 
1
# Copyright 2021-2022 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 os
18
import struct
19
import time
20

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

    
40
LOG = logging.getLogger(__name__)
41

    
42
MEM_FORMAT = """
43
#seekto 0x0000;
44
struct {
45
  lbcd rxfreq[4];     // 0-3
46
  lbcd txfreq[4];     // 4-7
47
  ul16 rxtone;        // 8-9
48
  ul16 txtone;        // A-B
49
  u8 unknown1:4,      // C
50
     scode:4;         //     Signal
51
  u8 unknown2:6,      // D
52
     pttid:2;         //     PTT-ID
53
  u8 unknown3:7,      // E
54
     lowpower:1;      //     Power Level 0 = High, 1 = Low
55
  u8 ani:1,           // F   ANI
56
     narrow:1,        //     Bandwidth  0 = Wide, 1 = Narrow
57
     unknown4:2,
58
     bcl:1,           //     BCL
59
     scan:1,          //     Scan  0 = Skip, 1 = Scan
60
     unknown5:1,
61
     compand:1;       //     Compand
62
} memory[128];
63

    
64
#seekto 0x0C00;
65
struct {
66
  char name[10];      // 10-character Alpha Tag
67
  u8 unused[6];
68
} names[128];
69

    
70
#seekto 0x1A00;
71
struct {
72
  u8 unknown:4,       // 1A00
73
     squelch:4;       //      Squelch Level
74
  u8 unknown_1a01:7,  // 1A01
75
     save:1;          //      Save Mode
76
  u8 unknown_1a02:4,  // 1A04
77
     vox:4;           // 1A02 VOX Level
78
  u8 unknown_1a03:4,  // 1A03
79
     abr:4;           //      Auto Backlight Time-out
80
  u8 unknown_1a04:7,  // 1A04
81
     tdr:1;           //      Dual Standby
82
  u8 tot;             // 1A05 Time-out Timer
83
  u8 unknown_1a06:7,  // 1A06
84
     beep:1;          //      Beep
85
  u8 unknown_1a07:7,  // 1A07
86
     voice:1;         //      Voice Switch
87
  u8 unknown_1a08:7,  // 1A08
88
     language:1;      //      Language
89
  u8 unknown_1a09:6,  // 1A09
90
     dtmfst:2;        //      DTMF ST
91
  u8 unknown_101a:6,  // 1A0A
92
     scmode:2;        //      Scan Mode
93
  u8 unknown_1a0a;    // 1A0B
94
  u8 pttlt;           // 1A0C PTT Delay
95
  u8 unknown_1a0d:6,  // 1A0D
96
     mdfa:2;          //      Channel A Display
97
  u8 unknown_1a0e:6,  // 1A0E
98
     mdfb:2;          //      Channel B Display
99
  u8 unknown_1a0f:7,  // 1A0F
100
     bcl:1;           //      BCL
101
  u8 unknown_1a10:7,  // 1A10
102
     autolock:1;      //      AutoLock
103
  u8 unknown_1a11:6,  // 1A11
104
     almod:2;         //      Alarm Mode
105
  u8 unknown_1a12:7,  // 1A12
106
     alarm:1;         //      Alarm Sound
107
  u8 unknown_1a13:6,  // 1A13
108
     tdrab:2;         //      Tx Under TDR Start
109
  u8 unknown_1a14:7,  // 1A14
110
     ste:1;           //      Tail Noise Clear
111
  u8 unknown_1a15:4,  // 1A15
112
     rpste:4;         //      Pass Repeat Noise
113
  u8 unknown_1a16:4,  // 1A16
114
     rptrl:4;         //      Pass Repeat Noise
115
  u8 unknown_1a17:7,  // 1A17
116
     roger:1;         //      Roger
117
  u8 unknown_1a18;    // 1A18
118
  u8 unknown_1a19:7,  // 1A19
119
     fmradio:1;       //      FM Radio (inverted)
120
  u8 unknown_1a1a:7,  // 1A1A
121
     workmode:1;      //      Work Mode
122
  u8 unknown_1a1b:7,  // 1A1B
123
     kblock:1;        //      KB_Lock
124
  u8 unknown_1a1c:6,  // 1A1C
125
     pwronmsg:2;      //      Pwr On Msg
126
  u8 unknown_1a1d;    // 1A1D
127
  u8 unknown_1a1e:6,  // 1A1E
128
     tone:2;          //      Tone
129
  u8 unknown_1a1f;    // 1A1F
130
  u8 unknown_1a20[7]; // 1A20-1A26
131
  u8 unknown_1a27:6,  // 1A27
132
     wtled:2;         //      Wait Backlight Color
133
  u8 unknown_1a28:6,  // 1A28
134
     rxled:2;         //      Rx Backlight Color
135
  u8 unknown_1a29:6,  // 1A29
136
     txled:2;         //      Tx Backlight Color
137
} settings;
138

    
139
#seekto 0x1A80;
140
struct {
141
  u8 shortp;          // 1A80 Skey Short
142
  u8 longp;           // 1A81 Skey Long
143
} skey;
144

    
145
#seekto 0x1B00;
146
struct {
147
  u8 code[6];         // 6-character PTT-ID Code
148
  u8 unused[10];
149
} pttid[15];
150

    
151
#seekto 0x1BF0;
152
struct {
153
  u8 code[6];         // ANI Code
154
  u8 unknown111;
155
  u8 dtmfon;          // DTMF Speed (on time)
156
  u8 dtmfoff;         // DTMR Speed (off time)
157
  u8 unused222[7];
158
  u8 killword[6];     // Kill Word
159
  u8 unused333[2];
160
  u8 revive[6];       // Revive
161
  u8 unused444[2];
162
} dtmf;
163

    
164
#seekto 0x1FE0;
165
struct {
166
  char line1[16];     // Power-on Message Line 1
167
  char line2[16];     // Power-on Message Line 2
168
} poweron_msg;
169
"""
170

    
171

    
172
CMD_ACK = "\x06"
173

    
174
RT76P_DTCS = sorted(chirp_common.DTCS_CODES + [645])
175

    
176
DTMF_CHARS = "0123456789 *#ABCD"
177

    
178
ALMOD_LIST = ["On Site", "Send Sound", "Send Code"]
179
BACKLIGHT_LIST = ["Off", "Blue", "Orange", "Purple"]
180
DTMFSPEED_LIST = ["%s ms" % x for x in range(50, 2010, 10)]
181
DTMFST_LIST = ["Off", "KeyBboard Side Tone", "ANI Side Tone", "KB ST + ANI ST"]
182
LANGUAGE_LIST = ["English", "China"]
183
MDF_LIST = ["Name", "Frequency", "Number"]
184
OFF1TO10_LIST = ["Off"] + ["%s" % x for x in range(1, 11)]
185
PTTID_LIST = ["Off", "BOT", "EOT", "Both"]
186
PTTIDCODE_LIST = ["%s" % x for x in range(1, 16)]
187
PWRONMSG_LIST = ["Picture", "Message", "Voltage"]
188
RPSTE_LIST = ["Off"] + ["%s" % x for x in range(100, 1100, 100)]
189
SCMODE_LIST = ["Time (TO)", "Carrier (CO)", "Search (SE)"]
190
TDRAB_LIST = ["Off", "A Band", "B Band"]
191
TIMEOUTTIMER_LIST = ["%s seconds" % x for x in range(15, 615, 15)]
192
TONE_LIST = ["1000Hz", "1450Hz", "1750Hz", "2100Hz"]
193
VOICE_LIST = ["Off", "On"]
194
WORKMODE_LIST = ["VFO Mode", "Channel Mode"]
195

    
196
SKEY_CHOICES = ["FM", "Tx Power", "Moni", "Scan", "Offline", "Weather"]
197
SKEY_VALUES = [0x07, 0x0A, 0x05, 0x1C, 0x0B, 0x0C]
198

    
199

    
200
SETTING_LISTS = {
201
    "abr": OFF1TO10_LIST,
202
    "almod": ALMOD_LIST,
203
    "dtmfspeed": DTMFSPEED_LIST,
204
    "language": LANGUAGE_LIST,
205
    "mdfa": MDF_LIST,
206
    "mdfb": MDF_LIST,
207
    "pttid": PTTID_LIST,
208
    "rpste": RPSTE_LIST,
209
    "rptrl": RPSTE_LIST,
210
    "rxled": BACKLIGHT_LIST,
211
    "scode": PTTIDCODE_LIST,
212
    "scmode": SCMODE_LIST,
213
    "tdrab": TDRAB_LIST,
214
    "tot": TIMEOUTTIMER_LIST,
215
    "tone": TONE_LIST,
216
    "txled": BACKLIGHT_LIST,
217
    "voice": VOICE_LIST,
218
    "vox": OFF1TO10_LIST,
219
    "workmode": WORKMODE_LIST,
220
    "wtled": BACKLIGHT_LIST,
221
    }
222

    
223
GMRS_FREQS1 = [462.5625, 462.5875, 462.6125, 462.6375, 462.6625,
224
               462.6875, 462.7125]
225
GMRS_FREQS2 = [467.5625, 467.5875, 467.6125, 467.6375, 467.6625,
226
               467.6875, 467.7125]
227
GMRS_FREQS3 = [462.5500, 462.5750, 462.6000, 462.6250, 462.6500,
228
               462.6750, 462.7000, 462.7250]
229
GMRS_FREQS = GMRS_FREQS1 + GMRS_FREQS2 + GMRS_FREQS3 * 2
230

    
231

    
232
def _rt76p_enter_programming_mode(radio):
233
    serial = radio.pipe
234

    
235
    exito = False
236
    for i in range(0, 5):
237
        serial.write(radio._magic)
238
        ack = serial.read(1)
239

    
240
        try:
241
            if ack == CMD_ACK:
242
                exito = True
243
                break
244
        except:
245
            LOG.debug("Attempt #%s, failed, trying again" % i)
246
            pass
247

    
248
    # check if we had EXITO
249
    if exito is False:
250
        msg = "The radio did not accept program mode after five tries.\n"
251
        msg += "Check you interface cable and power cycle your radio."
252
        raise errors.RadioError(msg)
253

    
254
    try:
255
        serial.write("F")
256
        ident = serial.read(8)
257
    except:
258
        raise errors.RadioError("Error communicating with radio")
259

    
260
    if not ident == radio._fingerprint:
261
        LOG.debug(util.hexprint(ident))
262
        raise errors.RadioError("Radio returned unknown identification string")
263

    
264

    
265
def _rt76p_exit_programming_mode(radio):
266
    serial = radio.pipe
267
    try:
268
        serial.write("E")
269
    except:
270
        raise errors.RadioError("Radio refused to exit programming mode")
271

    
272

    
273
def _rt76p_read_block(radio, block_addr, block_size):
274
    serial = radio.pipe
275

    
276
    cmd = struct.pack(">cHb", 'R', block_addr, block_size)
277
    expectedresponse = "R" + cmd[1:]
278
    LOG.debug("Reading block %04x..." % (block_addr))
279

    
280
    try:
281
        serial.write(cmd)
282
        response = serial.read(4 + block_size)
283
        if response[:4] != expectedresponse:
284
            raise Exception("Error reading block %04x." % (block_addr))
285

    
286
        block_data = response[4:]
287
    except:
288
        raise errors.RadioError("Failed to read block at %04x" % block_addr)
289

    
290
    return block_data
291

    
292

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

    
296
    cmd = struct.pack(">cHb", 'W', block_addr, block_size)
297
    data = radio.get_mmap()[block_addr:block_addr + block_size]
298

    
299
    LOG.debug("Writing Data:")
300
    LOG.debug(util.hexprint(cmd + data))
301

    
302
    try:
303
        serial.write(cmd + data)
304
        if serial.read(1) != CMD_ACK:
305
            raise Exception("No ACK")
306
    except:
307
        raise errors.RadioError("Failed to send block "
308
                                "to radio at %04x" % block_addr)
309

    
310

    
311
def do_download(radio):
312
    serial = radio.pipe
313
    LOG.debug("download")
314
    _rt76p_enter_programming_mode(radio)
315

    
316
    data = ""
317

    
318
    status = chirp_common.Status()
319
    status.msg = "Cloning from radio"
320

    
321
    status.cur = 0
322
    status.max = radio._memsize
323

    
324
    for addr in range(0, radio._memsize, radio.BLOCK_SIZE):
325
        status.cur = addr + radio.BLOCK_SIZE
326
        radio.status_fn(status)
327

    
328
        block = _rt76p_read_block(radio, addr, radio.BLOCK_SIZE)
329
        data += block
330

    
331
        LOG.debug("Address: %04x" % addr)
332
        LOG.debug(util.hexprint(block))
333

    
334
    _rt76p_exit_programming_mode(radio)
335

    
336
    return memmap.MemoryMap(data)
337

    
338

    
339
def do_upload(radio):
340
    status = chirp_common.Status()
341
    status.msg = "Uploading to radio"
342

    
343
    _rt76p_enter_programming_mode(radio)
344

    
345
    status.cur = 0
346
    status.max = radio._memsize
347

    
348
    for start_addr, end_addr in radio._ranges:
349
        for addr in range(start_addr, end_addr, radio.BLOCK_SIZE_UP):
350
            status.cur = addr + radio.BLOCK_SIZE_UP
351
            radio.status_fn(status)
352
            _rt76p_write_block(radio, addr, radio.BLOCK_SIZE_UP)
353

    
354
    _rt76p_exit_programming_mode(radio)
355

    
356

    
357
@directory.register
358
class RT76PRadio(chirp_common.CloneModeRadio):
359
    """RETEVIS RT76P"""
360
    VENDOR = "Retevis"
361
    MODEL = "RT76P"
362
    BAUD_RATE = 9600
363
    BLOCK_SIZE = 0x40
364
    BLOCK_SIZE_UP = 0x20
365

    
366
    RT76P_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
367
                          chirp_common.PowerLevel("Low", watts=0.50)]
368

    
369
    _magic = "PROGROMCD2U"
370
    _fingerprint = "\x01\x36\x01\x74\x04\x00\x05\x20"
371

    
372
    _ranges = [
373
               (0x0000, 0x0820),
374
               (0x0C00, 0x1400),
375
               (0x1A00, 0x1C20),
376
              ]
377
    _memsize = 0x2000
378

    
379
    def get_features(self):
380
        rf = chirp_common.RadioFeatures()
381
        rf.has_settings = True
382
        rf.has_bank = False
383
        rf.has_ctone = True
384
        rf.has_cross = True
385
        rf.has_rx_dtcs = True
386
        rf.has_tuning_step = False
387
        rf.can_odd_split = True
388
        rf.has_name = True
389
        rf.valid_name_length = 10
390
        rf.valid_skips = ["", "S"]
391
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
392
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
393
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
394
        rf.valid_power_levels = self.RT76P_POWER_LEVELS
395
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
396
        rf.valid_modes = ["FM", "NFM"]  # 25 kHz, 12.5 KHz.
397
        rf.valid_dtcs_codes = RT76P_DTCS
398
        rf.memory_bounds = (1, 128)
399
        rf.valid_tuning_steps = [2.5, 5., 6.25, 10., 12.5, 20., 25., 50.]
400
        rf.valid_bands = [(136000000, 174000000),
401
                          (400000000, 480000000)]
402
        return rf
403

    
404
    def process_mmap(self):
405
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
406

    
407
    def sync_in(self):
408
        """Download from radio"""
409
        try:
410
            data = do_download(self)
411
        except errors.RadioError:
412
            # Pass through any real errors we raise
413
            raise
414
        except:
415
            # If anything unexpected happens, make sure we raise
416
            # a RadioError and log the problem
417
            LOG.exception('Unexpected error during download')
418
            raise errors.RadioError('Unexpected error communicating '
419
                                    'with the radio')
420
        self._mmap = data
421
        self.process_mmap()
422

    
423
    def sync_out(self):
424
        """Upload to radio"""
425
        try:
426
            do_upload(self)
427
        except:
428
            # If anything unexpected happens, make sure we raise
429
            # a RadioError and log the problem
430
            LOG.exception('Unexpected error during upload')
431
            raise errors.RadioError('Unexpected error communicating '
432
                                    'with the radio')
433

    
434
    def _is_txinh(self, _mem):
435
        raw_tx = ""
436
        for i in range(0, 4):
437
            raw_tx += _mem.txfreq[i].get_raw()
438
        return raw_tx == "\xFF\xFF\xFF\xFF"
439

    
440
    def get_memory(self, number):
441
        _mem = self._memobj.memory[number - 1]
442
        _nam = self._memobj.names[number - 1]
443

    
444
        mem = chirp_common.Memory()
445
        mem.number = number
446

    
447
        if _mem.get_raw()[0] == "\xff":
448
            mem.empty = True
449
            return mem
450

    
451
        mem.freq = int(_mem.rxfreq) * 10
452

    
453
        if self._is_txinh(_mem):
454
            # TX freq not set
455
            mem.duplex = "off"
456
            mem.offset = 0
457
        else:
458
            # TX freq set
459
            offset = (int(_mem.txfreq) * 10) - mem.freq
460
            if offset != 0:
461
                if offset > 0:
462
                    mem.duplex = "+"
463
                    mem.offset = 5000000
464
            else:
465
                mem.duplex = ""
466
                mem.offset = 0
467

    
468
        for char in _nam.name:
469
            if str(char) == "\xFF":
470
                char = " "  # may have 0xFF mid-name
471
            mem.name += str(char)
472
        mem.name = mem.name.rstrip()
473

    
474
        dtcs_pol = ["N", "N"]
475

    
476
        if _mem.txtone in [0, 0xFFFF]:
477
            txmode = ""
478
        elif _mem.txtone >= 0x0258:
479
            txmode = "Tone"
480
            mem.rtone = int(_mem.txtone) / 10.0
481
        elif _mem.txtone <= 0x0258:
482
            txmode = "DTCS"
483
            if _mem.txtone > 0x69:
484
                index = _mem.txtone - 0x6A
485
                dtcs_pol[0] = "R"
486
            else:
487
                index = _mem.txtone - 1
488
            mem.dtcs = RT76P_DTCS[index]
489
        else:
490
            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
491

    
492
        if _mem.rxtone in [0, 0xFFFF]:
493
            rxmode = ""
494
        elif _mem.rxtone >= 0x0258:
495
            rxmode = "Tone"
496
            mem.ctone = int(_mem.rxtone) / 10.0
497
        elif _mem.rxtone <= 0x0258:
498
            rxmode = "DTCS"
499
            if _mem.rxtone >= 0x6A:
500
                index = _mem.rxtone - 0x6A
501
                dtcs_pol[1] = "R"
502
            else:
503
                index = _mem.rxtone - 1
504
            mem.rx_dtcs = RT76P_DTCS[index]
505
        else:
506
            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
507

    
508
        if txmode == "Tone" and not rxmode:
509
            mem.tmode = "Tone"
510
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
511
            mem.tmode = "TSQL"
512
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
513
            mem.tmode = "DTCS"
514
        elif rxmode or txmode:
515
            mem.tmode = "Cross"
516
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
517

    
518
        mem.dtcs_polarity = "".join(dtcs_pol)
519

    
520
        if not _mem.scan:
521
            mem.skip = "S"
522

    
523
        mem.power = self.RT76P_POWER_LEVELS[_mem.lowpower]
524

    
525
        mem.mode = _mem.narrow and "NFM" or "FM"
526

    
527
        mem.extra = RadioSettingGroup("Extra", "extra")
528

    
529
        # BCL (Busy Channel Lockout)
530
        rs = RadioSettingValueBoolean(_mem.bcl)
531
        rset = RadioSetting("bcl", "BCL", rs)
532
        mem.extra.append(rset)
533

    
534
        # PTT-ID
535
        rs = RadioSettingValueList(PTTID_LIST, PTTID_LIST[_mem.pttid])
536
        rset = RadioSetting("pttid", "PTT ID", rs)
537
        mem.extra.append(rset)
538

    
539
        # Signal (DTMF Encoder Group #)
540
        rs = RadioSettingValueList(PTTIDCODE_LIST, PTTIDCODE_LIST[_mem.scode])
541
        rset = RadioSetting("scode", "PTT ID Code", rs)
542
        mem.extra.append(rset)
543

    
544
        # Compand
545
        rs = RadioSettingValueBoolean(_mem.compand)
546
        rset = RadioSetting("compand", "Compand", rs)
547
        mem.extra.append(rset)
548

    
549
        # ANI
550
        rs = RadioSettingValueBoolean(_mem.ani)
551
        rset = RadioSetting("ani", "ANI", rs)
552
        mem.extra.append(rset)
553

    
554
        return mem
555

    
556
    def set_memory(self, mem):
557
        _mem = self._memobj.memory[mem.number - 1]
558
        _nam = self._memobj.names[mem.number - 1]
559

    
560
        if mem.empty:
561
            _mem.set_raw("\xff" * 16)
562
            _nam.set_raw("\xff" * 16)
563
            return
564

    
565
        _mem.set_raw("\x00" * 16)
566

    
567
        if float(mem.freq) / 1000000 in GMRS_FREQS1:
568
            mem.duplex == ''
569
            mem.offset = 0
570
        elif float(mem.freq) / 1000000 in GMRS_FREQS2:
571
            mem.duplex == ''
572
            mem.offset = 0
573
            mem.mode = "NFM"
574
            mem.power = self.POWER_LEVELS[1]
575
        elif float(mem.freq) / 1000000 in GMRS_FREQS3:
576
            if mem.duplex == '+':
577
                mem.offset = 5000000
578
            else:
579
                mem.duplex == ''
580
                mem.offset = 0
581
        else:
582
            mem.duplex = 'off'
583
            mem.offset = 0
584

    
585
        _mem.rxfreq = mem.freq / 10
586

    
587
        if mem.duplex == "off":
588
            for i in range(0, 4):
589
                _mem.txfreq[i].set_raw("\xFF")
590
        elif mem.duplex == "split":
591
            _mem.txfreq = mem.offset / 10
592
        elif mem.duplex == "+":
593
            _mem.txfreq = (mem.freq + mem.offset) / 10
594
        elif mem.duplex == "-":
595
            _mem.txfreq = (mem.freq - mem.offset) / 10
596
        else:
597
            _mem.txfreq = mem.freq / 10
598

    
599
        _namelength = self.get_features().valid_name_length
600
        for i in range(_namelength):
601
            try:
602
                _nam.name[i] = mem.name[i]
603
            except IndexError:
604
                _nam.name[i] = "\xFF"
605

    
606
        rxmode = txmode = ""
607
        if mem.tmode == "Tone":
608
            _mem.txtone = int(mem.rtone * 10)
609
            _mem.rxtone = 0
610
        elif mem.tmode == "TSQL":
611
            _mem.txtone = int(mem.ctone * 10)
612
            _mem.rxtone = int(mem.ctone * 10)
613
        elif mem.tmode == "DTCS":
614
            rxmode = txmode = "DTCS"
615
            _mem.txtone = RT76P_DTCS.index(mem.dtcs) + 1
616
            _mem.rxtone = RT76P_DTCS.index(mem.dtcs) + 1
617
        elif mem.tmode == "Cross":
618
            txmode, rxmode = mem.cross_mode.split("->", 1)
619
            if txmode == "Tone":
620
                _mem.txtone = int(mem.rtone * 10)
621
            elif txmode == "DTCS":
622
                _mem.txtone = RT76P_DTCS.index(mem.dtcs) + 1
623
            else:
624
                _mem.txtone = 0
625
            if rxmode == "Tone":
626
                _mem.rxtone = int(mem.ctone * 10)
627
            elif rxmode == "DTCS":
628
                _mem.rxtone = RT76P_DTCS.index(mem.rx_dtcs) + 1
629
            else:
630
                _mem.rxtone = 0
631
        else:
632
            _mem.rxtone = 0
633
            _mem.txtone = 0
634

    
635
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
636
            _mem.txtone += 0x69
637
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
638
            _mem.rxtone += 0x69
639

    
640
        _mem.scan = mem.skip != "S"
641
        _mem.narrow = mem.mode == "NFM"
642

    
643
        _mem.lowpower = mem.power == self.RT76P_POWER_LEVELS[1]
644

    
645
        for setting in mem.extra:
646
            if setting.get_name() == "scramble_type":
647
                setattr(_mem, setting.get_name(), int(setting.value) + 8)
648
                setattr(_mem, "scramble_type2", int(setting.value) + 8)
649
            else:
650
                setattr(_mem, setting.get_name(), setting.value)
651

    
652
    def get_settings(self):
653
        _dtmf = self._memobj.dtmf
654
        _settings = self._memobj.settings
655
        _skey = self._memobj.skey
656
        basic = RadioSettingGroup("basic", "Basic Settings")
657
        group = RadioSettings(basic)
658

    
659
        # Menu 00: Squelch (Squelch Level)
660
        rs = RadioSettingValueInteger(0, 9, _settings.squelch)
661
        rset = RadioSetting("squelch", "Squelch Level", rs)
662
        basic.append(rset)
663

    
664
        # Menu 01: Step (VFO setting)
665
        # Menu 02: Tx Power (VFO setting - not available)
666

    
667
        # Menu 03: Power save (Save Mode)
668
        rs = RadioSettingValueBoolean(_settings.save)
669
        rset = RadioSetting("save", "Power Save", rs)
670
        basic.append(rset)
671

    
672
        # Menu 04: Vox Level (VOX)
673
        rs = RadioSettingValueList(OFF1TO10_LIST, OFF1TO10_LIST[_settings.vox])
674
        rset = RadioSetting("vox", "VOX Level", rs)
675
        basic.append(rset)
676

    
677
        # Menu 05: Bandwidth
678

    
679
        # Menu 06: Backlight (Auto Backlight)
680
        rs = RadioSettingValueList(OFF1TO10_LIST,
681
                                   OFF1TO10_LIST[_settings.abr])
682
        rset = RadioSetting("abr", "Backlight Time-out", rs)
683
        basic.append(rset)
684

    
685
        # Menu 07: Dual Standby (TDR)
686
        rs = RadioSettingValueBoolean(_settings.tdr)
687
        rset = RadioSetting("tdr", "Dual Standby", rs)
688
        basic.append(rset)
689

    
690
        # Menu 08: Beep Prompt
691
        rs = RadioSettingValueBoolean(_settings.beep)
692
        rset = RadioSetting("beep", "Beep Prompt", rs)
693
        basic.append(rset)
694

    
695
        # Menu 09: Voice (Voice Switch)
696
        rs = RadioSettingValueBoolean(_settings.voice)
697
        rset = RadioSetting("voice", "Voice Prompts", rs)
698
        basic.append(rset)
699

    
700
        # Menu 10: Tx over time (Time Out)
701
        rs = RadioSettingValueList(TIMEOUTTIMER_LIST,
702
                                   TIMEOUTTIMER_LIST[_settings.tot - 1])
703
        rset = RadioSetting("tot", "Time-out Timer", rs)
704
        basic.append(rset)
705

    
706
        # Menu 11: Rx DCS
707
        # Menu 12: Rx CTCSS
708
        # Menu 13: Tx DCS
709
        # Menu 14: Tx CTCSS
710
        # Menu 15: Voice Compand
711

    
712
        # Menu 16: DTMFST (DTMF ST)
713
        rs = RadioSettingValueList(DTMFST_LIST, DTMFST_LIST[_settings.dtmfst])
714
        rset = RadioSetting("dtmfst", "DTMF Side Tone", rs)
715
        basic.append(rset)
716

    
717
        # Mneu 17: R-TONE (Tone)
718
        rs = RadioSettingValueList(TONE_LIST, TONE_LIST[_settings.tone])
719
        rset = RadioSetting("tone", "Tone-burst Frequency", rs)
720
        basic.append(rset)
721

    
722
        # Menu 18: S-CODE
723

    
724
        # Menu 19: Scan Mode
725
        rs = RadioSettingValueList(SCMODE_LIST, SCMODE_LIST[_settings.scmode])
726
        rset = RadioSetting("scmode", "Scan Resume Method", rs)
727
        basic.append(rset)
728

    
729
        # Menu 20: ANI Match
730
        # Menu 21: PTT-ID
731

    
732
        # Menu 22: MDF-A (Channle_A Display)
733
        rs = RadioSettingValueList(MDF_LIST, MDF_LIST[_settings.mdfa])
734
        rset = RadioSetting("mdfa", "Memory Display Format A", rs)
735
        basic.append(rset)
736

    
737
        # Menu 23: MDF-B (Channle_B Display)
738
        rs = RadioSettingValueList(MDF_LIST, MDF_LIST[_settings.mdfb])
739
        rset = RadioSetting("mdfb", "Memory Display Format B", rs)
740
        basic.append(rset)
741

    
742
        # Menu 24: Busy Lockout
743

    
744
        # Menu 25: Key Auto Lock (AutoLock)
745
        rs = RadioSettingValueBoolean(_settings.autolock)
746
        rset = RadioSetting("autolock", "Keypad Auto Lock", rs)
747
        basic.append(rset)
748

    
749
        # Menu 26: WT-LED (Wait Backlight)
750
        rs = RadioSettingValueList(BACKLIGHT_LIST,
751
                                   BACKLIGHT_LIST[_settings.wtled])
752
        rset = RadioSetting("wtled", "Wait Backlight Color", rs)
753
        basic.append(rset)
754

    
755
        # Menu 27: RX-LED (Rx Backlight)
756
        rs = RadioSettingValueList(BACKLIGHT_LIST,
757
                                   BACKLIGHT_LIST[_settings.rxled])
758
        rset = RadioSetting("rxled", "RX Backlight Color", rs)
759
        basic.append(rset)
760

    
761
        # Menu 28: TX-LED (Tx Backlight)
762
        rs = RadioSettingValueList(BACKLIGHT_LIST,
763
                                   BACKLIGHT_LIST[_settings.txled])
764
        rset = RadioSetting("txled", "TX Backlight Color", rs)
765
        basic.append(rset)
766

    
767
        # Menu 29: Alarm Mode
768
        rs = RadioSettingValueList(ALMOD_LIST, ALMOD_LIST[_settings.almod])
769
        rset = RadioSetting("almod", "Alarm Mode", rs)
770
        basic.append(rset)
771

    
772
        # Menu 30: TAIL (Tail Noise Clear)
773
        rs = RadioSettingValueBoolean(_settings.ste)
774
        rset = RadioSetting("ste", "Squelch Tail Eliminate", rs)
775
        basic.append(rset)
776

    
777
        # Menu 31: PROGRE (Roger)
778
        rs = RadioSettingValueBoolean(_settings.roger)
779
        rset = RadioSetting("roger", "Roger Beep", rs)
780
        basic.append(rset)
781

    
782
        # Menu 32: Language
783
        rs = RadioSettingValueList(LANGUAGE_LIST,
784
                                   LANGUAGE_LIST[_settings.language])
785
        rset = RadioSetting("language", "Language", rs)
786
        basic.append(rset)
787

    
788
        # Menu 33: OPENMGS (Pwr On Msg)
789
        rs = RadioSettingValueList(PWRONMSG_LIST,
790
                                   PWRONMSG_LIST[_settings.pwronmsg])
791
        rset = RadioSetting("pwronmsg", "Power On Message", rs)
792
        basic.append(rset)
793

    
794
        dtmfchars = "0123456789ABCD*#"
795

    
796
        # Menu 34: ANI ID (display only)
797
        _codeobj = self._memobj.dtmf.code
798
        print "_codeobj"
799
        print _codeobj
800
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
801
        val = RadioSettingValueString(0, 6, _code, False)
802
        val.set_charset(dtmfchars)
803
        val.set_mutable(False)
804
        rs = RadioSetting("dtmf.code", "Query ANI ID", val)
805
        basic.append(rs)
806

    
807
        # Menu 35: Reset
808

    
809
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
810
        group.append(advanced)
811

    
812
        # Work Mode
813
        rs = RadioSettingValueList(WORKMODE_LIST,
814
                                   WORKMODE_LIST[_settings.workmode])
815
        rset = RadioSetting("workmode", "Work Mode", rs)
816
        advanced.append(rset)
817

    
818
        # PTT Delay
819
        rs = RadioSettingValueInteger(0, 30, _settings.pttlt)
820
        rset = RadioSetting("pttlt", "PTT ID Delay", rs)
821
        advanced.append(rset)
822

    
823
        def apply_skey_listvalue(setting, obj):
824
            LOG.debug("Setting value: " + str(setting.value) + " from list")
825
            val = str(setting.value)
826
            index = SKEY_CHOICES.index(val)
827
            val = SKEY_VALUES[index]
828
            obj.set_value(val)
829

    
830
        # Skey Short
831
        if _skey.shortp in SKEY_VALUES:
832
            idx = SKEY_VALUES.index(_skey.shortp)
833
        else:
834
            idx = SKEY_VALUES.index(0x0C)
835
        rs = RadioSettingValueList(SKEY_CHOICES, SKEY_CHOICES[idx])
836
        rset = RadioSetting("skey.shortp", "Side Key (Short Press)", rs)
837
        rset.set_apply_callback(apply_skey_listvalue, _skey.shortp)
838
        advanced.append(rset)
839

    
840
        # Skey Long
841
        if _skey.longp in SKEY_VALUES:
842
            idx = SKEY_VALUES.index(_skey.longp)
843
        else:
844
            idx = SKEY_VALUES.index(0x0C)
845
        rs = RadioSettingValueList(SKEY_CHOICES, SKEY_CHOICES[idx])
846
        rset = RadioSetting("skey.longp", "Side Key (Long Press)", rs)
847
        rset.set_apply_callback(apply_skey_listvalue, _skey.longp)
848
        advanced.append(rset)
849

    
850
        # Pass Repeat Noise
851
        rs = RadioSettingValueList(RPSTE_LIST, RPSTE_LIST[_settings.rpste])
852
        rset = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)", rs)
853
        advanced.append(rset)
854

    
855
        # Pass Repeat Noise
856
        rs = RadioSettingValueList(RPSTE_LIST, RPSTE_LIST[_settings.rptrl])
857
        rset = RadioSetting("rptrl", "STE Repeater Delay", rs)
858
        advanced.append(rset)
859

    
860
        # KB_Lock
861
        rs = RadioSettingValueBoolean(_settings.kblock)
862
        rset = RadioSetting("kblock", "Keypad Lock", rs)
863
        advanced.append(rset)
864

    
865
        # FM Radio Enable
866
        rs = RadioSettingValueBoolean(not _settings.fmradio)
867
        rset = RadioSetting("fmradio", "Broadcast FM Radio", rs)
868
        advanced.append(rset)
869

    
870
        # Alarm Sound
871
        rs = RadioSettingValueBoolean(_settings.alarm)
872
        rset = RadioSetting("alarm", "Alarm Sound", rs)
873
        advanced.append(rset)
874

    
875
        # Tx Under TDR Start
876
        rs = RadioSettingValueList(TDRAB_LIST, TDRAB_LIST[_settings.tdrab])
877
        rset = RadioSetting("tdrab", "Dual Standby TX Priority", rs)
878
        advanced.append(rset)
879

    
880
        def _filter(name):
881
            filtered = ""
882
            for char in str(name):
883
                if char in chirp_common.CHARSET_ASCII:
884
                    filtered += char
885
                else:
886
                    filtered += " "
887
            return filtered
888

    
889
        _msg = self._memobj.poweron_msg
890
        rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
891
                          RadioSettingValueString(
892
                              0, 16, _filter(_msg.line1)))
893
        advanced.append(rs)
894
        rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
895
                          RadioSettingValueString(
896
                              0, 16, _filter(_msg.line2)))
897
        advanced.append(rs)
898

    
899
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
900
        group.append(dtmf)
901

    
902
        def apply_code(setting, obj, length):
903
            code = []
904
            for j in range(0, length):
905
                try:
906
                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
907
                except IndexError:
908
                    code.append(0xFF)
909
            obj.code = code
910

    
911
        for i in range(0, 15):
912
            _codeobj = self._memobj.pttid[i].code
913
            _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
914
            val = RadioSettingValueString(0, 6, _code, False)
915
            val.set_charset(DTMF_CHARS)
916
            pttid = RadioSetting("pttid/%i.code" % i,
917
                                 "Signal Code %i" % (i + 1), val)
918
            pttid.set_apply_callback(apply_code, self._memobj.pttid[i], 6)
919
            dtmf.append(pttid)
920

    
921
        _codeobj = self._memobj.dtmf.killword
922
        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
923
        val = RadioSettingValueString(0, 6, _code, False)
924
        val.set_charset(DTMF_CHARS)
925
        rs = RadioSetting("dtmf.killword", "Kill Word", val)
926
        rs.set_apply_callback(apply_code, self._memobj.dtmf, 6)
927
        dtmf.append(rs)
928

    
929
        _codeobj = self._memobj.dtmf.revive
930
        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
931
        val = RadioSettingValueString(0, 6, _code, False)
932
        val.set_charset(DTMF_CHARS)
933
        rs = RadioSetting("dtmf.revive", "Revive Word", val)
934
        rs.set_apply_callback(apply_code, self._memobj.dtmf, 6)
935
        dtmf.append(rs)
936

    
937
        _codeobj = self._memobj.dtmf.code
938
        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
939
        val = RadioSettingValueString(0, 6, _code, False)
940
        val.set_charset(DTMF_CHARS)
941
        rs = RadioSetting("dtmf.code", "ANI Code", val)
942
        rs.set_apply_callback(apply_code, self._memobj.dtmf, 6)
943
        dtmf.append(rs)
944

    
945
        if _dtmf.dtmfon > 0xC3:
946
            val = 0x00
947
        else:
948
            val = _dtmf.dtmfon
949
        rs = RadioSetting("dtmf.dtmfon", "DTMF Speed (on)",
950
                          RadioSettingValueList(DTMFSPEED_LIST,
951
                                                DTMFSPEED_LIST[val]))
952
        dtmf.append(rs)
953

    
954
        if _dtmf.dtmfoff > 0xC3:
955
            val = 0x00
956
        else:
957
            val = _dtmf.dtmfoff
958
        rs = RadioSetting("dtmf.dtmfoff", "DTMF Speed (off)",
959
                          RadioSettingValueList(DTMFSPEED_LIST,
960
                                                DTMFSPEED_LIST[val]))
961
        dtmf.append(rs)
962

    
963
        return group
964

    
965
    def set_settings(self, settings):
966
        _settings = self._memobj.settings
967
        _mem = self._memobj
968
        for element in settings:
969
            if not isinstance(element, RadioSetting):
970
                self.set_settings(element)
971
                continue
972
            else:
973
                try:
974
                    name = element.get_name()
975
                    if "." in name:
976
                        bits = name.split(".")
977
                        obj = self._memobj
978
                        for bit in bits[:-1]:
979
                            if "/" in bit:
980
                                bit, index = bit.split("/", 1)
981
                                index = int(index)
982
                                obj = getattr(obj, bit)[index]
983
                            else:
984
                                obj = getattr(obj, bit)
985
                        setting = bits[-1]
986
                    else:
987
                        obj = _settings
988
                        setting = element.get_name()
989

    
990
                    if element.has_apply_callback():
991
                        LOG.debug("Using apply callback")
992
                        element.run_apply_callback()
993
                    elif setting == "fmradio":
994
                        setattr(obj, setting, not int(element.value))
995
                    elif setting == "tot":
996
                        setattr(obj, setting, int(element.value) + 1)
997
                    elif element.value.get_mutable():
998
                        LOG.debug("Setting %s = %s" % (setting, element.value))
999
                        setattr(obj, setting, element.value)
1000
                except Exception as e:
1001
                    LOG.debug(element.get_name())
1002
                    raise
1003

    
1004
    @classmethod
1005
    def match_model(cls, filedata, filename):
1006
        # This radio has always been post-metadata, so never do
1007
        # old-school detection
1008
        return False
(3-3/3)