Project

General

Profile

New Model #9794 » radtel_t18 rb19_series_draft_#1.py

Rough Draft with RB19, RB19P and RB619 - Jim Unroe, 06/13/2022 02:30 AM

 
1
# Copyright 2021 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 time
17
import os
18
import struct
19
import unittest
20
import logging
21

    
22
from chirp import chirp_common, directory, memmap
23
from chirp import bitwise, errors, util
24
from chirp.settings import RadioSetting, RadioSettingGroup, \
25
    RadioSettingValueInteger, RadioSettingValueList, \
26
    RadioSettingValueBoolean, RadioSettings
27

    
28
LOG = logging.getLogger(__name__)
29

    
30
MEM_FORMAT = """
31
#seekto 0x0010;
32
struct {
33
    lbcd rxfreq[4];
34
    lbcd txfreq[4];
35
    lbcd rxtone[2];
36
    lbcd txtone[2];
37
    u8 unknown1:1,
38
       compander:1,
39
       scramble:1,
40
       skip:1,
41
       highpower:1,
42
       narrow:1,
43
       unknown2:1,
44
       bcl:1;
45
    u8 unknown3[3];
46
} memory[%d];
47
#seekto 0x03C0;
48
struct {
49
    u8 unknown1:1,
50
       scanmode:1,
51
       vox:1,            // Retevis RB19/RB19P/RB619 VOX
52
       speccode:1,
53
       voiceprompt:2,
54
       batterysaver:1,
55
       beep:1;
56
    u8 squelchlevel;
57
    u8 sidekey2;         // Retevis RT22S setting
58
                         // Retevis RB85 sidekey 1 short
59
                         // Retevis RB19/RB19P/RB619 sidekey 2 long
60
    u8 timeouttimer;
61
    u8 voxlevel;
62
    u8 sidekey2S;
63
    u8 volumelevel;      // Retevis RB19/RB19P/RB619 volume level
64
    u8 voxdelay;
65
    u8 sidekey1L;
66
    u8 sidekey2L;
67
    u8 unused2[3];
68
    u8 unknown3:4,
69
       unknown4:1,
70
       unknown5:2,
71
       power10w:1;       // Retevis RT85 power 10w on/off
72
                         // Retevis RT75 stop TX with low voltage
73
} settings;
74

    
75
#seekto 0x02B0;
76
struct {
77
    u8 voicesw;      // Voice SW            +
78
    u8 unknown1;
79
    u8 scan;         // Scan                +
80
    u8 vox;          // VOX                 +
81
    u8 voxgain;      // Vox Gain            +
82
    u8 voxnotxonrx;  // Rx Disable Vox      +
83
    u8 hivoltnotx;   // High Vol Inhibit TX +
84
    u8 lovoltnotx;   // Low Vol Inhibit TX  +
85
} settings2;
86
"""
87

    
88
MEM_FORMAT_RB18 = """
89
#seekto 0x0000;
90
struct {
91
    lbcd rxfreq[4];
92
    lbcd txfreq[4];
93
    lbcd rxtone[2];
94
    lbcd txtone[2];
95
    u8 jumpcode:1,
96
       unknown1:2,
97
       skip:1,
98
       highpower:1,
99
       narrow:1,
100
       unknown2:1,
101
       bcl:1;
102
    u8 unknown3[3];
103
} memory[%d];
104
#seekto 0x0630;
105
struct {
106
    u8 unk630:7,
107
       voice:1;
108
    u8 unk631:7,
109
       language:1;
110
    u8 unk632:7,
111
       scan:1;
112
    u8 unk633:7,
113
       vox:1;
114
    u8 unk634:5,
115
       vox_level:3;
116
    u8 unk635;
117
    u8 unk636:7,
118
       lovoltnotx:1;
119
    u8 unk637:7,
120
       hivoltnotx:1;
121
    u8 unknown2[8];
122
    u8 unk640:5,
123
       rogerbeep:1,
124
       batterysaver:1,
125
       beep:1;
126
    u8 squelchlevel;
127
    u8 unk642;
128
    u8 timeouttimer;
129
    u8 unk644:7,
130
       tail:1;
131
    u8 channel;
132
} settings;
133
"""
134

    
135
CMD_ACK = "\x06"
136

    
137
VOICE_LIST = ["Off", "Chinese", "English"]
138
VOICE_LIST2 = ["English", "Chinese"]
139
TIMEOUTTIMER_LIST = ["Off", "30 seconds", "60 seconds", "90 seconds",
140
                     "120 seconds", "150 seconds", "180 seconds",
141
                     "210 seconds", "240 seconds", "270 seconds",
142
                     "300 seconds"]
143
SCANMODE_LIST = ["Carrier", "Time"]
144
VOXLEVEL_LIST = ["Off", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
145
VOXDELAY_LIST = ["0.5 seconds", "1.0 seconds", "1.5 seconds",
146
                 "2.0 seconds", "2.5 seconds", "3.0 seconds"]
147
SIDEKEY2_LIST = ["Off", "Scan", "Emergency Alarm", "Display Battery"]
148

    
149
SIDEKEY85SHORT_LIST = ["Off",
150
                       "Noise Cansellation On",
151
                       "Continuous Monitor",
152
                       "High/Low Power",
153
                       "Emergency Alarm",
154
                       "Show Battery",
155
                       "Scan",
156
                       "VOX",
157
                       "Busy Channel Lock"]
158
SIDEKEY85LONG_LIST = ["Off",
159
                      "Noise Cansellation On",
160
                      "Continuous Monitor",
161
                      "Monitor Momentary",
162
                      "High/Low Power",
163
                      "Emergency Alarm",
164
                      "Show Battery",
165
                      "Scan",
166
                      "VOX",
167
                      "Busy Channel Lock"]
168
SPECCODE_LIST = ["SpeCode 1", "SpeCode 2"]
169
SIDEKEY75_LIST = ["Off",
170
                  "Monitor Momentary",
171
                  "Scan",
172
                  "VOX",
173
                  "Monitor",
174
                  "Announciation"]
175

    
176
SETTING_LISTS = {
177
    "voiceprompt": VOICE_LIST,
178
    "language": VOICE_LIST2,
179
    "timeouttimer": TIMEOUTTIMER_LIST,
180
    "scanmode": SCANMODE_LIST,
181
    "voxlevel": VOXLEVEL_LIST,
182
    "voxdelay": VOXDELAY_LIST,
183
    "sidekey2": SIDEKEY2_LIST,
184
    "sidekey2": SIDEKEY85SHORT_LIST,
185
    "sidekey1L": SIDEKEY85LONG_LIST,
186
    "sidekey2S": SIDEKEY85SHORT_LIST,
187
    "sidekey2L": SIDEKEY85LONG_LIST,
188
    "speccode": SPECCODE_LIST
189
}
190

    
191
FRS_FREQS1 = [462.5625, 462.5875, 462.6125, 462.6375, 462.6625,
192
              462.6875, 462.7125]
193
FRS_FREQS2 = [467.5625, 467.5875, 467.6125, 467.6375, 467.6625,
194
              467.6875, 467.7125]
195
FRS_FREQS3 = [462.5500, 462.5750, 462.6000, 462.6250, 462.6500,
196
              462.6750, 462.7000, 462.7250]
197
FRS_FREQS = FRS_FREQS1 + FRS_FREQS2 + FRS_FREQS3
198

    
199
FRS16_FREQS = [462.5625, 462.5875, 462.6125, 462.6375,
200
               462.6625, 462.6250, 462.7250, 462.6875,
201
               462.7125, 462.5500, 462.5750, 462.6000,
202
               462.6500, 462.6750, 462.7000, 462.7250]
203

    
204
GMRS_FREQS = FRS_FREQS1 + FRS_FREQS2 + FRS_FREQS3 * 2
205

    
206
MURS_FREQS = [151.820, 151.880, 151.940, 154.570, 154.600]
207

    
208
PMR_FREQS1 = [446.00625, 446.01875, 446.03125, 446.04375, 446.05625,
209
              446.06875, 446.08125, 446.09375]
210
PMR_FREQS2 = [446.10625, 446.11875, 446.13125, 446.14375, 446.15625,
211
              446.16875, 446.18125, 446.19375]
212
PMR_FREQS = PMR_FREQS1 + PMR_FREQS2
213

    
214

    
215
def _t18_enter_programming_mode(radio):
216
    serial = radio.pipe
217

    
218
    try:
219
        serial.write("\x02")
220
        time.sleep(0.01)
221
        serial.write(radio._magic)
222
        ack = serial.read(1)
223
    except:
224
        raise errors.RadioError("Error communicating with radio")
225

    
226
    if not ack:
227
        raise errors.RadioError("No response from radio")
228
    elif ack != CMD_ACK:
229
        raise errors.RadioError("Radio refused to enter programming mode")
230

    
231
    try:
232
        serial.write("\x02")
233
        ident = serial.read(8)
234
    except:
235
        raise errors.RadioError("Error communicating with radio")
236

    
237
    if not ident.startswith(radio._fingerprint):
238
        LOG.debug(util.hexprint(ident))
239
        raise errors.RadioError("Radio returned unknown identification string")
240

    
241
    try:
242
        serial.write(CMD_ACK)
243
        ack = serial.read(1)
244
    except:
245
        raise errors.RadioError("Error communicating with radio")
246

    
247
    if ack != CMD_ACK:
248
        raise errors.RadioError("Radio refused to enter programming mode")
249

    
250

    
251
def _t18_exit_programming_mode(radio):
252
    serial = radio.pipe
253
    try:
254
        serial.write(radio.CMD_EXIT)
255
    except:
256
        raise errors.RadioError("Radio refused to exit programming mode")
257

    
258

    
259
def _t18_read_block(radio, block_addr, block_size):
260
    serial = radio.pipe
261

    
262
    cmd = struct.pack(">cHb", 'R', block_addr, block_size)
263
    expectedresponse = "W" + cmd[1:]
264
    LOG.debug("Reading block %04x..." % (block_addr))
265

    
266
    try:
267
        serial.write(cmd)
268
        response = serial.read(4 + block_size)
269
        if response[:4] != expectedresponse:
270
            raise Exception("Error reading block %04x." % (block_addr))
271

    
272
        block_data = response[4:]
273

    
274
        #if radio.MODEL != "RT22S":
275
        if radio.ACK_BLOCK:
276
            serial.write(CMD_ACK)
277
            ack = serial.read(1)
278
    except:
279
        raise errors.RadioError("Failed to read block at %04x" % block_addr)
280

    
281
    if radio.ACK_BLOCK:
282
        if ack != CMD_ACK:
283
            raise Exception("No ACK reading block %04x." % (block_addr))
284

    
285
    return block_data
286

    
287

    
288
def _t18_write_block(radio, block_addr, block_size):
289
    serial = radio.pipe
290

    
291
    cmd = struct.pack(">cHb", 'W', block_addr, block_size)
292
    data = radio.get_mmap()[block_addr:block_addr + block_size]
293

    
294
    LOG.debug("Writing Data:")
295
    LOG.debug(util.hexprint(cmd + data))
296

    
297
    try:
298
        serial.write(cmd + data)
299
        if serial.read(1) != CMD_ACK:
300
            raise Exception("No ACK")
301
    except:
302
        raise errors.RadioError("Failed to send block "
303
                                "to radio at %04x" % block_addr)
304

    
305

    
306
def do_download(radio):
307
    LOG.debug("download")
308
    _t18_enter_programming_mode(radio)
309

    
310
    data = ""
311

    
312
    status = chirp_common.Status()
313
    status.msg = "Cloning from radio"
314

    
315
    status.cur = 0
316
    status.max = radio._memsize
317

    
318
    for addr in range(0, radio._memsize, radio.BLOCK_SIZE):
319
        status.cur = addr + radio.BLOCK_SIZE
320
        radio.status_fn(status)
321

    
322
        block = _t18_read_block(radio, addr, radio.BLOCK_SIZE)
323
        data += block
324

    
325
        LOG.debug("Address: %04x" % addr)
326
        LOG.debug(util.hexprint(block))
327

    
328
    _t18_exit_programming_mode(radio)
329

    
330
    return memmap.MemoryMap(data)
331

    
332

    
333
def do_upload(radio):
334
    status = chirp_common.Status()
335
    status.msg = "Uploading to radio"
336

    
337
    _t18_enter_programming_mode(radio)
338

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

    
342
    for start_addr, end_addr in radio._ranges:
343
        for addr in range(start_addr, end_addr, radio.BLOCK_SIZE):
344
            status.cur = addr + radio.BLOCK_SIZE
345
            radio.status_fn(status)
346
            _t18_write_block(radio, addr, radio.BLOCK_SIZE)
347

    
348
    _t18_exit_programming_mode(radio)
349

    
350

    
351
def model_match(cls, data):
352
    """Match the opened/downloaded image to the correct version"""
353

    
354
    if len(data) == cls._memsize:
355
        rid = data[0x03D0:0x03D8]
356
        return "P558" in rid
357
    else:
358
        return False
359

    
360

    
361
@directory.register
362
class T18Radio(chirp_common.CloneModeRadio):
363
    """radtel T18"""
364
    VENDOR = "Radtel"
365
    MODEL = "T18"
366
    BAUD_RATE = 9600
367
    BLOCK_SIZE = 0x08
368
    CMD_EXIT = "b"
369
    ACK_BLOCK = True
370

    
371
    VALID_BANDS = [(400000000, 470000000)]
372

    
373
    _magic = "1ROGRAM"
374
    _fingerprint = "SMP558" + "\x00\x00"
375
    _upper = 16
376
    _mem_params = (_upper  # number of channels
377
                   )
378
    _frs = _murs = _pmr = _gmrs = False
379

    
380
    _ranges = [
381
        (0x0000, 0x03F0),
382
    ]
383
    _memsize = 0x03F0
384

    
385
    def get_features(self):
386
        rf = chirp_common.RadioFeatures()
387
        rf.has_settings = True
388
        rf.valid_modes = ["NFM", "FM"]  # 12.5 KHz, 25 kHz.
389
        rf.valid_skips = ["", "S"]
390
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
391
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
392
        if self.MODEL != "T18" and self.MODEL != "RB618":
393
            rf.valid_power_levels = self.POWER_LEVELS
394
        rf.can_odd_split = True
395
        rf.has_rx_dtcs = True
396
        rf.has_ctone = True
397
        rf.has_cross = True
398
        rf.valid_cross_modes = [
399
            "Tone->Tone",
400
            "DTCS->",
401
            "->DTCS",
402
            "Tone->DTCS",
403
            "DTCS->Tone",
404
            "->Tone",
405
            "DTCS->DTCS"]
406
        rf.has_tuning_step = False
407
        rf.has_bank = False
408
        rf.has_name = False
409
        rf.memory_bounds = (1, self._upper)
410
        rf.valid_bands = self.VALID_BANDS
411
        rf.valid_tuning_steps = chirp_common.TUNING_STEPS
412

    
413
        return rf
414

    
415
    def process_mmap(self):
416
        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
417

    
418
    def validate_memory(self, mem):
419
        msgs = ""
420
        msgs = chirp_common.CloneModeRadio.validate_memory(self, mem)
421

    
422
        _msg_freq = 'Memory location cannot change frequency'
423
        _msg_simplex = 'Memory location only supports Duplex:(None)'
424
        _msg_duplex = 'Memory location only supports Duplex: +'
425
        _msg_nfm = 'Memory location only supports Mode: NFM'
426
        _msg_txp = 'Memory location only supports Power: Low'
427

    
428
        # FRS only models
429
        if self._frs:
430
            # range of memories with values set by FCC rules
431
            if self._upper == 22:
432
                if mem.freq != int(FRS_FREQS[mem.number - 1] * 1000000):
433
                    # warn user can't change frequency
434
                    msgs.append(chirp_common.ValidationError(_msg_freq))
435
                if mem.number >= 8 and mem.number <= 14:
436
                    if str(mem.power) != "Low":
437
                        # warn user can't change power
438
                        msgs.append(chirp_common.ValidationError(_msg_txp))
439
            else:
440
                if mem.freq != int(FRS16_FREQS[mem.number - 1] * 1000000):
441
                    # warn user can't change frequency
442
                    msgs.append(chirp_common.ValidationError(_msg_freq))
443

    
444
            # channels 1 - 16/22 are simplex only
445
            if str(mem.duplex) != "":
446
                # warn user can't change duplex
447
                msgs.append(chirp_common.ValidationError(_msg_simplex))
448

    
449
            # channels 1 - 16/22 are NFM only
450
            if str(mem.mode) != "NFM":
451
                # warn user can't change mode
452
                msgs.append(chirp_common.ValidationError(_msg_nfm))
453

    
454
        # GMRS only models
455
        if self._gmrs:
456
            # range of memories with values set by FCC rules
457
            if mem.freq != int(GMRS_FREQS[mem.number - 1] * 1000000):
458
                # warn user can't change frequency
459
                msgs.append(chirp_common.ValidationError(_msg_freq))
460
            if mem.number >= 8 and mem.number <= 14:
461
                if str(mem.power) != "Low":
462
                    # warn user can't change power
463
                    msgs.append(chirp_common.ValidationError(_msg_txp))
464

    
465
                if str(mem.mode) != "NFM":
466
                    # warn user can't change mode
467
                    msgs.append(chirp_common.ValidationError(_msg_nfm))
468

    
469
            if mem.number >= 1 and mem.number <= 22:
470
                # channels 1 - 22 are simplex only
471
                if str(mem.duplex) != "":
472
                    # warn user can't change duplex
473
                    msgs.append(chirp_common.ValidationError(_msg_simplex))
474

    
475
            if mem.number >= 23 and mem.number <= 30:
476
                # channels 23 - 30 are duplex + only
477
                if str(mem.duplex) != "+":
478
                    # warn user can't change duplex
479
                    msgs.append(chirp_common.ValidationError(_msg_duplex))
480

    
481
        # MURS only models
482
        if self._murs:
483
            # range of memories with values set by FCC rules
484
            if mem.freq != int(MURS_FREQS[mem.number - 1] * 1000000):
485
                # warn user can't change frequency
486
                msgs.append(chirp_common.ValidationError(_msg_freq))
487

    
488
            # channels 1 - 5 are simplex only
489
            if str(mem.duplex) != "":
490
                # warn user can't change duplex
491
                msgs.append(chirp_common.ValidationError(_msg_simplex))
492

    
493
            # channels 1 - 3 are NFM only
494
            if mem.number <= 3:
495
                if mem.mode != "NFM":
496
                    # warn user can't change mode
497
                    msgs.append(chirp_common.ValidationError(_msg_nfm))
498

    
499
        # PMR only models
500
        if self._pmr:
501
            # range of memories with values set by PMR rules
502
            if mem.freq != int(PMR_FREQS[mem.number - 1] * 1000000):
503
                # warn user can't change frequency
504
                msgs.append(chirp_common.ValidationError(_msg_freq))
505

    
506
            # channels 1 - 16 are simplex only
507
            if str(mem.duplex) != "":
508
                # warn user can't change duplex
509
                msgs.append(chirp_common.ValidationError(_msg_simplex))
510

    
511
            # channels 1 - 16 are NFM only
512
            if str(mem.mode) != "NFM":
513
                # warn user can't change mode
514
                msgs.append(chirp_common.ValidationError(_msg_nfm))
515

    
516
        return msgs
517

    
518
    def sync_in(self):
519
        self._mmap = do_download(self)
520
        self.process_mmap()
521

    
522
    def sync_out(self):
523
        do_upload(self)
524

    
525
    def get_raw_memory(self, number):
526
        return repr(self._memobj.memory[number - 1])
527

    
528
    def _decode_tone(self, val):
529
        val = int(val)
530
        if val == 16665:
531
            return '', None, None
532
        elif val >= 12000:
533
            return 'DTCS', val - 12000, 'R'
534
        elif val >= 8000:
535
            return 'DTCS', val - 8000, 'N'
536
        else:
537
            return 'Tone', val / 10.0, None
538

    
539
    def _encode_tone(self, memval, mode, value, pol):
540
        if mode == '':
541
            memval[0].set_raw(0xFF)
542
            memval[1].set_raw(0xFF)
543
        elif mode == 'Tone':
544
            memval.set_value(int(value * 10))
545
        elif mode == 'DTCS':
546
            flag = 0x80 if pol == 'N' else 0xC0
547
            memval.set_value(value)
548
            memval[1].set_bits(flag)
549
        else:
550
            raise Exception("Internal error: invalid mode `%s'" % mode)
551

    
552
    def get_memory(self, number):
553
        _mem = self._memobj.memory[number - 1]
554

    
555
        mem = chirp_common.Memory()
556

    
557
        mem.number = number
558
        mem.freq = int(_mem.rxfreq) * 10
559

    
560
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
561
        if mem.freq == 0:
562
            mem.empty = True
563
            return mem
564

    
565
        if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF":
566
            mem.freq = 0
567
            mem.empty = True
568
            return mem
569

    
570
        if _mem.txfreq.get_raw() == "\xFF\xFF\xFF\xFF":
571
            mem.duplex = "off"
572
            mem.offset = 0
573
        elif int(_mem.rxfreq) == int(_mem.txfreq):
574
            mem.duplex = ""
575
            mem.offset = 0
576
        else:
577
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
578
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
579

    
580
        mem.mode = not _mem.narrow and "FM" or "NFM"
581

    
582
        mem.skip = _mem.skip and "S" or ""
583

    
584
        txtone = self._decode_tone(_mem.txtone)
585
        rxtone = self._decode_tone(_mem.rxtone)
586
        chirp_common.split_tone_decode(mem, txtone, rxtone)
587

    
588
        if self.MODEL != "T18" and self.MODEL != "RB618":
589
            mem.power = self.POWER_LEVELS[_mem.highpower]
590

    
591
        if self._frs:
592
            FRS_IMMUTABLE = ["freq", "duplex", "offset", "mode"]
593
            if self._upper == 22:
594
                if mem.number >= 8 and mem.number <= 14:
595
                    FRS_IMMUTABLE = FRS_IMMUTABLE + ["power"]
596

    
597
            mem.immutable = FRS_IMMUTABLE
598

    
599
        if self._gmrs:
600
            GMRS_IMMUTABLE = ["freq", "duplex", "offset"]
601
            if mem.number >= 8 and mem.number <= 14:
602
                GMRS_IMMUTABLE = GMRS_IMMUTABLE + ["mode", "power"]
603

    
604
            mem.immutable = GMRS_IMMUTABLE
605

    
606
        if self._murs:
607
            MURS_IMMUTABLE = ["freq", "duplex", "offset"]
608
            if mem.number <= 3:
609
                MURS_IMMUTABLE = MURS_IMMUTABLE + ["mode"]
610

    
611
            mem.immutable = MURS_IMMUTABLE
612

    
613
        if self._pmr:
614
            PMR_IMMUTABLE = ["freq", "duplex", "offset", "mode", "power"]
615
            mem.immutable = PMR_IMMUTABLE
616

    
617
        mem.extra = RadioSettingGroup("Extra", "extra")
618
        rs = RadioSetting("bcl", "Busy Channel Lockout",
619
                          RadioSettingValueBoolean(not _mem.bcl))
620
        mem.extra.append(rs)
621
        if self.MODEL != "RB18" and self.MODEL != "RB618":
622
            rs = RadioSetting("scramble", "Scramble",
623
                              RadioSettingValueBoolean(not _mem.scramble))
624
            mem.extra.append(rs)
625
            rs = RadioSetting("compander", "Compander",
626
                              RadioSettingValueBoolean(not _mem.compander))
627
            mem.extra.append(rs)
628

    
629
        return mem
630

    
631
    def set_memory(self, mem):
632
        # Get a low-level memory object mapped to the image
633
        _mem = self._memobj.memory[mem.number - 1]
634

    
635
        if mem.empty:
636
            if self._frs:
637
                _mem.set_raw("\xFF" * 12 + "\x00" + "\xFF" * 3)
638
                if self._upper == 22:
639
                    FRS_FREQ = int(FRS_FREQS[mem.number - 1] * 100000)
640
                else:
641
                    FRS_FREQ = int(FRS16_FREQS[mem.number - 1] * 100000)
642
                _mem.rxfreq = _mem.txfreq = FRS_FREQ
643
                _mem.narrow = True
644
                _mem.highpower = True
645
                if self._upper == 22:
646
                    if mem.number >= 8 and mem.number <= 14:
647
                        _mem.highpower = False
648
            elif self._gmrs:
649
                _mem.set_raw("\xFF" * 12 + "\x00" + "\xFF" * 3)
650
                GMRS_FREQ = int(GMRS_FREQS[mem.number - 1] * 100000)
651
                if mem.number > 22:
652
                    _mem.rxfreq = GMRS_FREQ
653
                    _mem.txfreq = int(_mem.rxfreq) + 500000
654
                else:
655
                    _mem.rxfreq = _mem.txfreq = GMRS_FREQ
656
                if mem.number >= 8 and mem.number <= 14:
657
                    _mem.narrow = True
658
                    _mem.highpower = False
659
                else:
660
                    _mem.narrow = False
661
                    _mem.highpower = True
662
            elif self._murs:
663
                _mem.set_raw("\xFF" * 12 + "\x00" + "\xFF" * 3)
664
                MURS_FREQ = int(MURS_FREQS[mem.number - 1] * 100000)
665
                _mem.rxfreq = _mem.txfreq = MURS_FREQ
666
                _mem.highpower = True
667
                if mem.number <= 3:
668
                    _mem.narrow = True
669
                else:
670
                    _mem.narrow = False
671
            elif self._pmr:
672
                _mem.set_raw("\xFF" * 12 + "\x00" + "\xFF" * 3)
673
                PMR_FREQ = int(PMR_FREQS[mem.number - 1] * 100000)
674
                _mem.rxfreq = _mem.txfreq = PMR_FREQ
675
                _mem.narrow = True
676
                _mem.highpower = False
677
            else:
678
                _mem.set_raw("\xFF" * (_mem.size() / 8))
679

    
680
            return
681

    
682
        _mem.rxfreq = mem.freq / 10
683

    
684
        if mem.duplex == "off":
685
            for i in range(0, 4):
686
                _mem.txfreq[i].set_raw("\xFF")
687
        elif mem.duplex == "split":
688
            _mem.txfreq = mem.offset / 10
689
        elif mem.duplex == "+":
690
            _mem.txfreq = (mem.freq + mem.offset) / 10
691
        elif mem.duplex == "-":
692
            _mem.txfreq = (mem.freq - mem.offset) / 10
693
        else:
694
            _mem.txfreq = mem.freq / 10
695

    
696
        txtone, rxtone = chirp_common.split_tone_encode(mem)
697
        self._encode_tone(_mem.txtone, *txtone)
698
        self._encode_tone(_mem.rxtone, *rxtone)
699

    
700
        if self.MODEL != "T18" and self.MODEL != "RB18":
701
            _mem.highpower = mem.power == self.POWER_LEVELS[1]
702

    
703
        _mem.narrow = 'N' in mem.mode
704
        _mem.skip = mem.skip == "S"
705

    
706
        for setting in mem.extra:
707
            # NOTE: Only three settings right now, all are inverted
708
            setattr(_mem, setting.get_name(), not int(setting.value))
709

    
710
    def get_settings(self):
711
        _settings = self._memobj.settings
712
        if self.MODEL == "FRS-B1":
713
            _settings2 = self._memobj.settings2
714
        basic = RadioSettingGroup("basic", "Basic Settings")
715
        top = RadioSettings(basic)
716

    
717
        rs = RadioSetting("squelchlevel", "Squelch level",
718
                          RadioSettingValueInteger(
719
                              0, 9, _settings.squelchlevel))
720
        basic.append(rs)
721

    
722
        rs = RadioSetting("timeouttimer", "Timeout timer",
723
                          RadioSettingValueList(
724
                              TIMEOUTTIMER_LIST,
725
                              TIMEOUTTIMER_LIST[
726
                                  _settings.timeouttimer]))
727
        basic.append(rs)
728

    
729
        if self.MODEL == "RB18" or self.MODEL == "RB618":
730
            rs = RadioSetting("scan", "Scan",
731
                              RadioSettingValueBoolean(_settings.scan))
732
            basic.append(rs)
733
        elif self.MODEL == "FRS-B1":
734
            rs = RadioSetting("settings2.scan", "Scan",
735
                              RadioSettingValueBoolean(_settings2.scan))
736
            basic.append(rs)
737
        else:
738
            rs = RadioSetting("scanmode", "Scan mode",
739
                              RadioSettingValueList(
740
                                  SCANMODE_LIST,
741
                                  SCANMODE_LIST[_settings.scanmode]))
742
            basic.append(rs)
743

    
744
        if self.MODEL == "RT22S":
745
            rs = RadioSetting("voiceprompt", "Voice prompts",
746
                              RadioSettingValueBoolean(_settings.voiceprompt))
747
            basic.append(rs)
748
        elif self.MODEL == "RB18" or self.MODEL == "RB618":
749
            rs = RadioSetting("voice", "Voice prompts",
750
                              RadioSettingValueBoolean(_settings.voice))
751
            basic.append(rs)
752
        elif self.MODEL == "FRS-B1":
753
            rs = RadioSetting("settings2.voicesw", "Voice prompts",
754
                              RadioSettingValueBoolean(_settings2.voicesw))
755
            basic.append(rs)
756
        else:
757
            rs = RadioSetting("voiceprompt", "Voice prompts",
758
                              RadioSettingValueList(
759
                                  VOICE_LIST,
760
                                  VOICE_LIST[_settings.voiceprompt]))
761
            basic.append(rs)
762

    
763
        rs = RadioSetting("batterysaver", "Battery saver",
764
                          RadioSettingValueBoolean(_settings.batterysaver))
765
        basic.append(rs)
766

    
767
        if self.MODEL != "RB75":
768
            rs = RadioSetting("beep", "Beep",
769
                              RadioSettingValueBoolean(_settings.beep))
770
            basic.append(rs)
771

    
772
        if self.MODEL == "RB19" or self.MODEL == "RB19P" or self.MODEL == "RB619":
773
            rs = RadioSetting("vox", "VOX",
774
                              RadioSettingValueBoolean(_settings.vox))
775
            basic.append(rs)
776

    
777
        if self.MODEL != "RB18" and self.MODEL != "RB618" \
778
                and self.MODEL != "FRS-B1":
779
            rs = RadioSetting("voxlevel", "Vox level",
780
                              RadioSettingValueList(
781
                                  VOXLEVEL_LIST,
782
                                  VOXLEVEL_LIST[_settings.voxlevel]))
783
            basic.append(rs)
784

    
785
            rs = RadioSetting("voxdelay", "VOX delay",
786
                              RadioSettingValueList(
787
                                  VOXDELAY_LIST,
788
                                  VOXDELAY_LIST[_settings.voxdelay]))
789
            basic.append(rs)
790

    
791
        if self.MODEL == "RT22S":
792
            rs = RadioSetting("sidekey2", "Side Key 2(Long)",
793
                              RadioSettingValueList(
794
                                  SIDEKEY2_LIST,
795
                                  SIDEKEY2_LIST[_settings.sidekey2]))
796
            basic.append(rs)
797

    
798
        if self.MODEL == "RB18" or self.MODEL == "RB618":
799
            rs = RadioSetting("language", "Language",
800
                              RadioSettingValueList(
801
                                  VOICE_LIST2,
802
                                  VOICE_LIST2[_settings.language]))
803
            basic.append(rs)
804

    
805
            rs = RadioSetting("tail", "Tail",
806
                              RadioSettingValueBoolean(_settings.tail))
807
            basic.append(rs)
808

    
809
            rs = RadioSetting("hivoltnotx", "High voltage no TX",
810
                              RadioSettingValueBoolean(_settings.hivoltnotx))
811
            basic.append(rs)
812

    
813
            rs = RadioSetting("lovoltnotx", "Low voltage no TX",
814
                              RadioSettingValueBoolean(_settings.lovoltnotx))
815
            basic.append(rs)
816

    
817
            rs = RadioSetting("vox", "VOX",
818
                              RadioSettingValueBoolean(_settings.vox))
819
            basic.append(rs)
820

    
821
            if _settings.vox_level > 4:
822
                val = 1
823
            else:
824
                val = _settings.vox_level + 1
825
            rs = RadioSetting("vox_level", "VOX level",
826
                              RadioSettingValueInteger(1, 5, val))
827
            basic.append(rs)
828

    
829
            rs = RadioSetting("rogerbeep", "Roger beep",
830
                              RadioSettingValueBoolean(_settings.rogerbeep))
831
            basic.append(rs)
832

    
833
        if self.MODEL == "RB85":
834
            rs = RadioSetting("speccode", "SpecCode Select",
835
                              RadioSettingValueList(
836
                                  SPECCODE_LIST,
837
                                  SPECCODE_LIST[_settings.speccode]))
838
            basic.append(rs)
839

    
840
            rs = RadioSetting("sidekey2", "Side Key 1(Short)",
841
                              RadioSettingValueList(
842
                                  SIDEKEY85SHORT_LIST,
843
                                  SIDEKEY85SHORT_LIST[_settings.sidekey2]))
844
            basic.append(rs)
845

    
846
            rs = RadioSetting("sidekey1L", "Side Key 1(Long)",
847
                              RadioSettingValueList(
848
                                  SIDEKEY85LONG_LIST,
849
                                  SIDEKEY85LONG_LIST[_settings.sidekey1L]))
850
            basic.append(rs)
851

    
852
            rs = RadioSetting("sidekey2S", "Side Key 2(Short)",
853
                              RadioSettingValueList(
854
                                  SIDEKEY85SHORT_LIST,
855
                                  SIDEKEY85SHORT_LIST[_settings.sidekey2S]))
856
            basic.append(rs)
857

    
858
            rs = RadioSetting("sidekey2L", "Side Key 2(Long)",
859
                              RadioSettingValueList(
860
                                  SIDEKEY85LONG_LIST,
861
                                  SIDEKEY85LONG_LIST[_settings.sidekey2L]))
862
            basic.append(rs)
863

    
864
            rs = RadioSetting("power10w", "Power 10W",
865
                              RadioSettingValueBoolean(_settings.power10w))
866
            basic.append(rs)
867

    
868
        if self.MODEL == "RB75":
869
            rs = RadioSetting("sidekey2", "Side Key 1(Short)",
870
                              RadioSettingValueList(
871
                                  SIDEKEY75_LIST,
872
                                  SIDEKEY75_LIST[_settings.sidekey2]))
873
            basic.append(rs)
874

    
875
            rs = RadioSetting("sidekey1L", "Side Key 1(Long)",
876
                              RadioSettingValueList(
877
                                  SIDEKEY75_LIST,
878
                                  SIDEKEY75_LIST[_settings.sidekey1L]))
879
            basic.append(rs)
880

    
881
            rs = RadioSetting("sidekey2S", "Side Key 2(Short)",
882
                              RadioSettingValueList(
883
                                  SIDEKEY75_LIST,
884
                                  SIDEKEY75_LIST[_settings.sidekey2S]))
885
            basic.append(rs)
886

    
887
            rs = RadioSetting("sidekey2L", "Side Key 2(Long)",
888
                              RadioSettingValueList(
889
                                  SIDEKEY75_LIST,
890
                                  SIDEKEY75_LIST[_settings.sidekey2L]))
891
            basic.append(rs)
892

    
893
            rs = RadioSetting("power10w", "Low Voltage Stop TX",
894
                              RadioSettingValueBoolean(_settings.power10w))
895
            basic.append(rs)
896

    
897
        if self.MODEL == "FRS-B1":
898
            rs = RadioSetting("settings2.hivoltnotx",
899
                              "High Voltage Inhibit TX",
900
                              RadioSettingValueBoolean(_settings2.hivoltnotx))
901
            basic.append(rs)
902

    
903
            rs = RadioSetting("settings2.lovoltnotx", "Low Voltage Inhibit TX",
904
                              RadioSettingValueBoolean(_settings2.lovoltnotx))
905
            basic.append(rs)
906

    
907
            rs = RadioSetting("settings2.vox", "Vox",
908
                              RadioSettingValueBoolean(_settings2.vox))
909
            basic.append(rs)
910

    
911
            rs = RadioSetting("settings2.voxnotxonrx", "Rx Disable VOX",
912
                              RadioSettingValueBoolean(_settings2.voxnotxonrx))
913
            basic.append(rs)
914

    
915
            rs = RadioSetting("settings2.voxgain", "Vox Gain",
916
                              RadioSettingValueInteger(
917
                                  1, 5, _settings2.voxgain))
918
            basic.append(rs)
919

    
920
        if self.MODEL == "RB19" or self.MODEL == "RB19P" or self.MODEL == "RB619":
921
            SIDEKEY2_LIST.pop()
922
            rs = RadioSetting("sidekey2", "Left Navigation Button(Long)",
923
                              RadioSettingValueList(
924
                                  SIDEKEY2_LIST,
925
                                  SIDEKEY2_LIST[_settings.sidekey2]))
926
            basic.append(rs)
927

    
928
            rs = RadioSetting("volumelevel", "Volume level",
929
                              RadioSettingValueInteger(
930
                                  0, 7, _settings.volumelevel))
931
            basic.append(rs)
932

    
933
        return top
934

    
935
    def set_settings(self, settings):
936
        for element in settings:
937
            if not isinstance(element, RadioSetting):
938
                self.set_settings(element)
939
                continue
940
            else:
941
                try:
942
                    if "." in element.get_name():
943
                        bits = element.get_name().split(".")
944
                        obj = self._memobj
945
                        for bit in bits[:-1]:
946
                            obj = getattr(obj, bit)
947
                        setting = bits[-1]
948
                    else:
949
                        obj = self._memobj.settings
950
                        setting = element.get_name()
951

    
952
                    if element.has_apply_callback():
953
                        LOG.debug("Using apply callback")
954
                        element.run_apply_callback()
955
                    elif setting == "vox_level":
956
                        setattr(obj, setting, int(element.value) - 1)
957
                    else:
958
                        LOG.debug("Setting %s = %s" % (setting, element.value))
959
                        setattr(obj, setting, element.value)
960
                except Exception, e:
961
                    LOG.debug(element.get_name())
962
                    raise
963

    
964
    @classmethod
965
    def match_model(cls, filedata, filename):
966
        if cls.MODEL == "T18":
967
            match_size = False
968
            match_model = False
969

    
970
            # testing the file data size
971
            if len(filedata) == cls._memsize:
972
                match_size = True
973

    
974
            # testing the model fingerprint
975
            match_model = model_match(cls, filedata)
976

    
977
            if match_size and match_model:
978
                return True
979
            else:
980
                return False
981
        else:
982
            # Radios that have always been post-metadata, so never do
983
            # old-school detection
984
            return False
985

    
986

    
987
@directory.register
988
class RT22SRadio(T18Radio):
989
    """RETEVIS RT22S"""
990
    VENDOR = "Retevis"
991
    MODEL = "RT22S"
992
    ACK_BLOCK = False
993

    
994
    POWER_LEVELS = [chirp_common.PowerLevel("Low",  watts=0.50),
995
                    chirp_common.PowerLevel("High", watts=2.00)]
996

    
997
    _magic = "9COGRAM"
998
    _fingerprint = "SMP558" + "\x02"
999
    _upper = 22
1000
    _mem_params = (_upper  # number of channels
1001
                   )
1002
    _frs = True
1003
    _pmr = False
1004

    
1005

    
1006
@directory.register
1007
class RB18Radio(T18Radio):
1008
    """RETEVIS RB18"""
1009
    VENDOR = "Retevis"
1010
    MODEL = "RB18"
1011
    BLOCK_SIZE = 0x10
1012
    CMD_EXIT = "E"
1013

    
1014
    POWER_LEVELS = [chirp_common.PowerLevel("Low",  watts=0.50),
1015
                    chirp_common.PowerLevel("High", watts=2.00)]
1016

    
1017
    _magic = "PROGRAL"
1018
    _fingerprint = "P3107" + "\xF7"
1019
    _upper = 22
1020
    _mem_params = (_upper  # number of channels
1021
                   )
1022
    _frs = True
1023
    _pmr = False
1024

    
1025
    _ranges = [
1026
        (0x0000, 0x0660),
1027
    ]
1028
    _memsize = 0x0660
1029

    
1030
    def process_mmap(self):
1031
        self._memobj = bitwise.parse(MEM_FORMAT_RB18 %
1032
                                     self._mem_params, self._mmap)
1033

    
1034
    @classmethod
1035
    def match_model(cls, filedata, filename):
1036
        # This radio has always been post-metadata, so never do
1037
        # old-school detection
1038
        return False
1039

    
1040

    
1041
@directory.register
1042
class RB618Radio(RB18Radio):
1043
    """RETEVIS RB618"""
1044
    VENDOR = "Retevis"
1045
    MODEL = "RB618"
1046

    
1047
    _upper = 16
1048
    _mem_params = (_upper  # number of channels
1049
                   )
1050
    _frs = False
1051
    _pmr = True
1052

    
1053

    
1054
@directory.register
1055
class RT68Radio(T18Radio):
1056
    """RETEVIS RT68"""
1057
    VENDOR = "Retevis"
1058
    MODEL = "RT68"
1059
    ACK_BLOCK = False
1060
    CMD_EXIT = ""
1061

    
1062
    POWER_LEVELS = [chirp_common.PowerLevel("Low",  watts=0.50),
1063
                    chirp_common.PowerLevel("High", watts=2.00)]
1064

    
1065
    _magic = "83OGRAM"
1066
    _fingerprint = "\x06\x00\x00\x00\x00\x00\x00\x00"
1067
    _upper = 16
1068
    _mem_params = (_upper  # number of channels
1069
                   )
1070
    _frs = True
1071
    _pmr = False
1072

    
1073
    @classmethod
1074
    def match_model(cls, filedata, filename):
1075
        # This radio has always been post-metadata, so never do
1076
        # old-school detection
1077
        return False
1078

    
1079

    
1080
@directory.register
1081
class RT668Radio(RT68Radio):
1082
    """RETEVIS RT668"""
1083
    VENDOR = "Retevis"
1084
    MODEL = "RT668"
1085

    
1086
    _frs = False
1087
    _pmr = True
1088

    
1089

    
1090
@directory.register
1091
class RB17Radio(RT68Radio):
1092
    """RETEVIS RB17"""
1093
    VENDOR = "Retevis"
1094
    MODEL = "RB17"
1095

    
1096
    _magic = "A5OGRAM"
1097
    _fingerprint = "\x53\x00\x00\x00\x00\x00\x00\x00"
1098

    
1099
    _frs = True
1100
    _pmr = False
1101
    _murs = False
1102

    
1103

    
1104
@directory.register
1105
class RB617Radio(RB17Radio):
1106
    """RETEVIS RB617"""
1107
    VENDOR = "Retevis"
1108
    MODEL = "RB617"
1109

    
1110
    _frs = False
1111
    _pmr = True
1112
    _murs = False
1113

    
1114

    
1115
@directory.register
1116
class RB17VRadio(RB17Radio):
1117
    """RETEVIS RB17V"""
1118
    VENDOR = "Retevis"
1119
    MODEL = "RB17V"
1120

    
1121
    VALID_BANDS = [(136000000, 174000000)]
1122

    
1123
    _upper = 5
1124

    
1125
    _frs = False
1126
    _pmr = False
1127
    _murs = True
1128

    
1129

    
1130
@directory.register
1131
class RB85Radio(T18Radio):
1132
    """Retevis RB85"""
1133
    VENDOR = "Retevis"
1134
    MODEL = "RB85"
1135
    ACK_BLOCK = False
1136

    
1137
    POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5.00),
1138
                    chirp_common.PowerLevel("High", watts=10.00)]
1139

    
1140
    _magic = "H19GRAM"
1141
    _fingerprint = "SMP558" + "\x02"
1142

    
1143

    
1144
@directory.register
1145
class RB75Radio(T18Radio):
1146
    """Retevis RB75"""
1147
    VENDOR = "Retevis"
1148
    MODEL = "RB75"
1149
    ACK_BLOCK = False
1150

    
1151
    POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=0.50),
1152
                    chirp_common.PowerLevel("High", watts=5.00)]
1153

    
1154
    _magic = "KVOGRAM"
1155
    _fingerprint = "SMP558" + "\x00"
1156
    _upper = 30
1157
    _mem_params = (_upper  # number of channels
1158
                   )
1159
    _gmrs = True
1160

    
1161

    
1162
@directory.register
1163
class FRSB1Radio(T18Radio):
1164
    """BTECH FRS-B1"""
1165
    VENDOR = "BTECH"
1166
    MODEL = "FRS-B1"
1167
    ACK_BLOCK = False
1168

    
1169
    POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=0.50),
1170
                    chirp_common.PowerLevel("High", watts=2.00)]
1171

    
1172
    _magic = "PROGRAM"
1173
    _fingerprint = "P3107" + "\xF7\x00"
1174
    _upper = 22
1175
    _mem_params = (_upper  # number of channels
1176
                   )
1177
    _frs = True
1178

    
1179

    
1180
@directory.register
1181
class RB19Radio(T18Radio):
1182
    """Retevis RB19"""
1183
    VENDOR = "Retevis"
1184
    MODEL = "RB19"
1185
    ACK_BLOCK = False
1186

    
1187
    POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=0.50),
1188
                    chirp_common.PowerLevel("High", watts=2.00)]
1189

    
1190
    _magic = "9COGRAM"
1191
    _fingerprint = "SMP558" + "\x02"
1192
    _upper = 22
1193
    _mem_params = (_upper  # number of channels
1194
                   )
1195
    _frs = True
1196

    
1197

    
1198
@directory.register
1199
class RB19PRadio(T18Radio):
1200
    """Retevis RB19P"""
1201
    VENDOR = "Retevis"
1202
    MODEL = "RB19P"
1203
    ACK_BLOCK = False
1204

    
1205
    POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=0.50),
1206
                    chirp_common.PowerLevel("High", watts=3.00)]
1207

    
1208
    _magic = "70OGRAM"
1209
    _fingerprint = "SMP558" + "\x02"
1210
    _upper = 30
1211
    _mem_params = (_upper  # number of channels
1212
                   )
1213
    _gmrs = True
1214

    
1215

    
1216
@directory.register
1217
class RB619Radio(T18Radio):
1218
    """Retevis RB619"""
1219
    VENDOR = "Retevis"
1220
    MODEL = "RB619"
1221
    ACK_BLOCK = False
1222

    
1223
    POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=0.499),
1224
                    chirp_common.PowerLevel("High", watts=0.500)]
1225

    
1226
    _magic = "9COGRAM"
1227
    _fingerprint = "SMP558" + "\x02"
1228
    _upper = 16
1229
    _mem_params = (_upper  # number of channels
1230
                   )
1231
    _pmr = True
(1-1/4)