Project

General

Profile

Bug #10663 ยป rb75_full_band.py

Jim Unroe, 06/22/2023 04:52 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 struct
17
import logging
18

    
19
from chirp import chirp_common, directory, memmap
20
from chirp import bitwise, errors, util
21
from chirp.settings import RadioSetting, RadioSettingGroup, \
22
    RadioSettingValueInteger, RadioSettingValueList, \
23
    RadioSettingValueBoolean, RadioSettings
24

    
25
LOG = logging.getLogger(__name__)
26

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

    
74
#seekto 0x02B0;
75
struct {
76
    u8 voicesw;      // Voice SW            +
77
    u8 voiceselect;  // Voice Select
78
    u8 scan;         // Scan                +
79
    u8 vox;          // VOX                 +
80
    u8 voxgain;      // Vox Gain            +
81
    u8 voxnotxonrx;  // Rx Disable Vox      +
82
    u8 hivoltnotx;   // High Vol Inhibit TX +
83
    u8 lovoltnotx;   // Low Vol Inhibit TX  +
84
    u8 rxemergency;  // RX Emergency
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 = b"\x06"
136

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

    
152
SIDEKEY85SHORT_LIST = ["Off",
153
                       "Noise Cansellation On",
154
                       "Continuous Monitor",
155
                       "High/Low Power",
156
                       "Emergency Alarm",
157
                       "Show Battery",
158
                       "Scan",
159
                       "VOX",
160
                       "Busy Channel Lock"]
161
SIDEKEY85LONG_LIST = ["Off",
162
                      "Noise Cansellation On",
163
                      "Continuous Monitor",
164
                      "Monitor Momentary",
165
                      "High/Low Power",
166
                      "Emergency Alarm",
167
                      "Show Battery",
168
                      "Scan",
169
                      "VOX",
170
                      "Busy Channel Lock"]
171
SPECCODE_LIST = ["SpeCode 1", "SpeCode 2"]
172
SIDEKEY75_LIST = ["Off",
173
                  "Monitor Momentary",
174
                  "Scan",
175
                  "VOX",
176
                  "Monitor",
177
                  "Announciation"]
178
SIDEKEY47_LIST = ["Monitor Momentary",
179
                  "Channel Lock",
180
                  "Scan",
181
                  "VOX"]
182
SIDEKEYV8A_LIST = ["Off",
183
                   "Monitor",
184
                   "High/Low Power",
185
                   "Alarm"]
186
SIDEKEY87_LIST = ["Scan", "Emergency Alarm"]
187

    
188
SETTING_LISTS = {
189
    "voiceprompt": VOICE_LIST,
190
    "language": VOICE_LIST2,
191
    "timeouttimer": TIMEOUTTIMER_LIST,
192
    "scanmode": SCANMODE_LIST,
193
    "voxlevel": VOXLEVEL_LIST,
194
    "voxdelay": VOXDELAY_LIST,
195
    "sidekey2": SIDEKEY2_LIST,
196
    "sidekey2": SIDEKEY19_LIST,
197
    "sidekey2": SIDEKEY85SHORT_LIST,
198
    "sidekey2": SIDEKEYV8A_LIST,
199
    "sidekey1L": SIDEKEY85LONG_LIST,
200
    "sidekey2S": SIDEKEY29_LIST,
201
    "sidekey2S": SIDEKEY85SHORT_LIST,
202
    "sidekey2L": SIDEKEY85LONG_LIST,
203
    "speccode": SPECCODE_LIST
204
}
205

    
206
FRS_FREQS1 = [462562500, 462587500, 462612500, 462637500, 462662500,
207
              462687500, 462712500]
208
FRS_FREQS2 = [467562500, 467587500, 467612500, 467637500, 467662500,
209
              467687500, 467712500]
210
FRS_FREQS3 = [462550000, 462575000, 462600000, 462625000, 462650000,
211
              462675000, 462700000, 462725000]
212
FRS_FREQS = FRS_FREQS1 + FRS_FREQS2 + FRS_FREQS3
213

    
214
FRS16_FREQS = [462562500, 462587500, 462612500, 462637500,
215
               462662500, 462625000, 462725000, 462687500,
216
               462712500, 462550000, 462575000, 462600000,
217
               462650000, 462675000, 462700000, 462725000]
218

    
219
GMRS_FREQS = FRS_FREQS1 + FRS_FREQS2 + FRS_FREQS3 * 2
220

    
221
MURS_FREQS = [151820000, 151880000, 151940000, 154570000, 154600000]
222

    
223
PMR_FREQS1 = [446006250, 446018750, 446031250, 446043750, 446056250,
224
              446068750, 446081250, 446093750]
225
PMR_FREQS2 = [446106250, 446118750, 446131250, 446143750, 446156250,
226
              446168750, 446181250, 446193750]
227
PMR_FREQS = PMR_FREQS1 + PMR_FREQS2
228

    
229

    
230
def _t18_enter_programming_mode(radio):
231
    serial = radio.pipe
232

    
233
    _magic = b"\x02" + radio._magic
234

    
235
    try:
236
        serial.write(_magic)
237
        if radio._echo:
238
            chew = serial.read(len(_magic))  # Chew the echo
239
        ack = serial.read(1)
240
    except:
241
        raise errors.RadioError("Error communicating with radio")
242

    
243
    if not ack:
244
        raise errors.RadioError("No response from radio")
245
    elif ack != CMD_ACK:
246
        raise errors.RadioError("Radio refused to enter programming mode")
247

    
248
    try:
249
        serial.write(b"\x02")
250
        if radio._echo:
251
            serial.read(1)  # Chew the echo
252
        ident = serial.read(8)
253
    except:
254
        raise errors.RadioError("Error communicating with radio")
255

    
256
    # check if ident is OK
257
    for fp in radio._fingerprint:
258
        if ident.startswith(fp):
259
            break
260
    else:
261
        LOG.debug("Incorrect model ID, got this:\n\n" + util.hexprint(ident))
262
        raise errors.RadioError("Radio identification failed.")
263

    
264
    try:
265
        serial.write(CMD_ACK)
266
        if radio._echo:
267
            serial.read(1)  # Chew the echo
268
        ack = serial.read(1)
269
    except:
270
        raise errors.RadioError("Error communicating with radio")
271

    
272
    if radio.MODEL == "RT647":
273
        if ack != b"\xF0":
274
            raise errors.RadioError("Radio refused to enter programming mode")
275
    else:
276
        if ack != CMD_ACK:
277
            raise errors.RadioError("Radio refused to enter programming mode")
278

    
279

    
280
def _t18_exit_programming_mode(radio):
281
    serial = radio.pipe
282
    try:
283
        serial.write(radio.CMD_EXIT)
284
        if radio._echo:
285
            chew = serial.read(1)  # Chew the echo
286
    except:
287
        raise errors.RadioError("Radio refused to exit programming mode")
288

    
289

    
290
def _t18_read_block(radio, block_addr, block_size):
291
    serial = radio.pipe
292

    
293
    cmd = struct.pack(">cHb", b'R', block_addr, block_size)
294
    expectedresponse = b"W" + cmd[1:]
295
    LOG.debug("Reading block %04x..." % (block_addr))
296

    
297
    try:
298
        serial.write(cmd)
299
        if radio._echo:
300
            serial.read(4)  # Chew the echo
301
        response = serial.read(4 + block_size)
302
        if response[:4] != expectedresponse:
303
            raise Exception("Error reading block %04x." % (block_addr))
304

    
305
        block_data = response[4:]
306

    
307
        if radio.ACK_BLOCK:
308
            serial.write(CMD_ACK)
309
            if radio._echo:
310
                serial.read(1)  # Chew the echo
311
            ack = serial.read(1)
312
    except:
313
        raise errors.RadioError("Failed to read block at %04x" % block_addr)
314

    
315
    if radio.ACK_BLOCK:
316
        if ack != CMD_ACK:
317
            raise Exception("No ACK reading block %04x." % (block_addr))
318

    
319
    return block_data
320

    
321

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

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

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

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

    
341

    
342
def do_download(radio):
343
    LOG.debug("download")
344
    _t18_enter_programming_mode(radio)
345

    
346
    data = b""
347

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

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

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

    
358
        block = _t18_read_block(radio, addr, radio.BLOCK_SIZE)
359
        data += block
360

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

    
364
    _t18_exit_programming_mode(radio)
365

    
366
    return memmap.MemoryMapBytes(data)
367

    
368

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

    
373
    _t18_enter_programming_mode(radio)
374

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

    
378
    for start_addr, end_addr in radio._ranges:
379
        for addr in range(start_addr, end_addr, radio.BLOCK_SIZE):
380
            status.cur = addr + radio.BLOCK_SIZE
381
            radio.status_fn(status)
382
            _t18_write_block(radio, addr, radio.BLOCK_SIZE)
383

    
384
    _t18_exit_programming_mode(radio)
385

    
386

    
387
def model_match(cls, data):
388
    """Match the opened/downloaded image to the correct version"""
389

    
390
    if len(data) == cls._memsize:
391
        rid = data[0x03D0:0x03D8]
392
        return b"P558" in rid
393
    else:
394
        return False
395

    
396

    
397
@directory.register
398
class T18Radio(chirp_common.CloneModeRadio):
399
    """radtel T18"""
400
    VENDOR = "Radtel"
401
    MODEL = "T18"
402
    BAUD_RATE = 9600
403
    NEEDS_COMPAT_SERIAL = False
404
    BLOCK_SIZE = 0x08
405
    CMD_EXIT = b"b"
406
    ACK_BLOCK = True
407

    
408
    VALID_BANDS = [(400000000, 470000000)]
409

    
410
    _magic = b"1ROGRAM"
411
    _fingerprint = [b"SMP558" + b"\x00\x00"]
412
    _upper = 16
413
    _mem_params = (_upper  # number of channels
414
                   )
415
    _frs = _frs16 = _murs = _pmr = _gmrs = False
416
    _echo = False
417

    
418
    _ranges = [
419
        (0x0000, 0x03F0),
420
    ]
421
    _memsize = 0x03F0
422

    
423
    def get_features(self):
424
        rf = chirp_common.RadioFeatures()
425
        rf.has_settings = True
426
        rf.valid_modes = ["NFM", "FM"]  # 12.5 KHz, 25 kHz.
427
        rf.valid_skips = ["", "S"]
428
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
429
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
430
        if self.MODEL != "T18" and \
431
                self.MODEL != "RB618" and \
432
                self.MODEL != "RT647":
433
            rf.valid_power_levels = self.POWER_LEVELS
434
        rf.can_odd_split = True
435
        rf.has_rx_dtcs = True
436
        rf.has_ctone = True
437
        rf.has_cross = True
438
        rf.valid_cross_modes = [
439
            "Tone->Tone",
440
            "DTCS->",
441
            "->DTCS",
442
            "Tone->DTCS",
443
            "DTCS->Tone",
444
            "->Tone",
445
            "DTCS->DTCS"]
446
        rf.has_tuning_step = False
447
        rf.has_bank = False
448
        rf.has_name = False
449
        rf.memory_bounds = (1, self._upper)
450
        rf.valid_bands = self.VALID_BANDS
451
        rf.valid_tuning_steps = chirp_common.TUNING_STEPS
452

    
453
        return rf
454

    
455
    def process_mmap(self):
456
        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
457

    
458
    def sync_in(self):
459
        self._mmap = do_download(self)
460
        self.process_mmap()
461

    
462
    def sync_out(self):
463
        do_upload(self)
464

    
465
    def get_raw_memory(self, number):
466
        return repr(self._memobj.memory[number - 1])
467

    
468
    def _decode_tone(self, val):
469
        val = int(val)
470
        if val == 16665:
471
            return '', None, None
472
        elif val >= 12000:
473
            return 'DTCS', val - 12000, 'R'
474
        elif val >= 8000:
475
            return 'DTCS', val - 8000, 'N'
476
        else:
477
            return 'Tone', val / 10.0, None
478

    
479
    def _encode_tone(self, memval, mode, value, pol):
480
        if mode == '':
481
            memval[0].set_raw(0xFF)
482
            memval[1].set_raw(0xFF)
483
        elif mode == 'Tone':
484
            memval.set_value(int(value * 10))
485
        elif mode == 'DTCS':
486
            flag = 0x80 if pol == 'N' else 0xC0
487
            memval.set_value(value)
488
            memval[1].set_bits(flag)
489
        else:
490
            raise Exception("Internal error: invalid mode `%s'" % mode)
491

    
492
    def get_memory(self, number):
493
        _mem = self._memobj.memory[number - 1]
494

    
495
        mem = chirp_common.Memory()
496

    
497
        mem.number = number
498
        mem.freq = int(_mem.rxfreq) * 10
499

    
500
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
501
        if mem.freq == 0:
502
            mem.empty = True
503
            return mem
504

    
505
        if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF":
506
            mem.freq = 0
507
            mem.empty = True
508
            return mem
509

    
510
        if _mem.txfreq.get_raw() == "\xFF\xFF\xFF\xFF":
511
            mem.duplex = "off"
512
            mem.offset = 0
513
        elif int(_mem.rxfreq) == int(_mem.txfreq):
514
            mem.duplex = ""
515
            mem.offset = 0
516
        else:
517
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
518
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
519

    
520
        mem.mode = not _mem.narrow and "FM" or "NFM"
521

    
522
        mem.skip = _mem.skip and "S" or ""
523

    
524
        txtone = self._decode_tone(_mem.txtone)
525
        rxtone = self._decode_tone(_mem.rxtone)
526
        chirp_common.split_tone_decode(mem, txtone, rxtone)
527

    
528
        if self.MODEL != "T18" and self.MODEL != "RB618":
529
            mem.power = self.POWER_LEVELS[1 - _mem.highpower]
530

    
531
        mem.extra = RadioSettingGroup("Extra", "extra")
532
        rs = RadioSetting("bcl", "Busy Channel Lockout",
533
                          RadioSettingValueBoolean(not _mem.bcl))
534
        mem.extra.append(rs)
535
        if self.MODEL != "RB18" and self.MODEL != "RB618" and \
536
                self.MODEL != "FRS-B1" and self.MODEL != "BF-V8A" and \
537
                self.MODEL != "RB29" and self.MODEL != "RB629":
538
            if self.MODEL not in ["RB87",
539
                                  "RT47V"]:
540
                rs = RadioSetting("scramble", "Scramble",
541
                                  RadioSettingValueBoolean(not _mem.scramble))
542
                mem.extra.append(rs)
543
            rs = RadioSetting("compander", "Compander",
544
                              RadioSettingValueBoolean(not _mem.compander))
545
            mem.extra.append(rs)
546
            if self.MODEL != "RT47V" and self.MODEL != "T8" and \
547
                    self.MODEL != "RB17" and self.MODEL != "RB617" and \
548
                    self.MODEL != "RB75" and self.MODEL != "RB19P":
549
                if self.MODEL == "RB87":
550
                    rs = RadioSettingValueBoolean(not _mem.speccode)
551
                    rset = RadioSetting("speccode", "PTT-ID", rs)
552
                    mem.extra.append(rset)
553
                else:
554
                    rs = RadioSettingValueBoolean(not _mem.speccode)
555
                    rset = RadioSetting("speccode", "Spec Code", rs)
556
                    mem.extra.append(rset)
557

    
558
        immutable = []
559

    
560
        if self._frs:
561
            if mem.freq in FRS_FREQS:
562
                if mem.number >= 1 and mem.number <= 22:
563
                    FRS_FREQ = FRS_FREQS[mem.number - 1]
564
                    mem.freq = FRS_FREQ
565
                mem.duplex = ''
566
                mem.offset = 0
567
                mem.mode = "NFM"
568
                if mem.number >= 8 and mem.number <= 14:
569
                    mem.power = self.POWER_LEVELS[1]
570
                    immutable = ["empty", "freq", "duplex", "offset", "mode",
571
                                 "power"]
572
                else:
573
                    immutable = ["empty", "freq", "duplex", "offset", "mode"]
574
        elif self._frs16:
575
            if mem.freq in FRS16_FREQS:
576
                if mem.number >= 1 and mem.number <= 16:
577
                    FRS_FREQ = FRS16_FREQS[mem.number - 1]
578
                    mem.freq = FRS_FREQ
579
                mem.duplex = ''
580
                mem.offset = 0
581
                mem.mode = "NFM"
582
                immutable = ["empty", "freq", "duplex", "offset", "mode"]
583
        elif self._murs:
584
            if mem.freq in MURS_FREQS:
585
                if mem.number >= 1 and mem.number <= 5:
586
                    MURS_FREQ = MURS_FREQS[mem.number - 1]
587
                    mem.freq = MURS_FREQ
588
                mem.duplex = ''
589
                mem.offset = 0
590
                if mem.number <= 3:
591
                    mem.mode = "NFM"
592
                    immutable = ["empty", "freq", "duplex", "offset", "mode"]
593
                else:
594
                    immutable = ["empty", "freq", "duplex", "offset"]
595
        elif self._pmr:
596
            if mem.freq in PMR_FREQS:
597
                if mem.number >= 1 and mem.number <= 16:
598
                    PMR_FREQ = PMR_FREQS[mem.number - 1]
599
                    mem.freq = PMR_FREQ
600
                mem.duplex = ''
601
                mem.offset = 0
602
                mem.mode = "NFM"
603
                mem.power = self.POWER_LEVELS[1]
604
                immutable = ["empty", "freq", "duplex", "offset", "mode",
605
                             "power"]
606
        elif self._gmrs:
607
            if mem.freq in GMRS_FREQS:
608
                if mem.number >= 1 and mem.number <= 30:
609
                    GMRS_FREQ = GMRS_FREQS[mem.number - 1]
610
                    mem.freq = GMRS_FREQ
611
                    immutable = ["freq"]
612
                if mem.number >= 1 and mem.number <= 7:
613
                    mem.duplex = ''
614
                    mem.offset = 0
615
                    immutable += ["duplex", "offset"]
616
                elif mem.number >= 8 and mem.number <= 14:
617
                    mem.duplex = ''
618
                    mem.offset = 0
619
                    mem.mode = "NFM"
620
                    mem.power = self.POWER_LEVELS[1]
621
                    immutable += ["duplex", "offset", "mode", "power"]
622
                elif mem.number >= 15 and mem.number <= 22:
623
                    mem.duplex = ''
624
                    mem.offset = 0
625
                    immutable += ["duplex", "offset"]
626
                elif mem.number >= 23 and mem.number <= 30:
627
                    mem.duplex = '+'
628
                    mem.offset = 5000000
629
                    immutable += ["duplex", "offset"]
630

    
631
        mem.immutable = immutable
632

    
633
        return mem
634

    
635
    def set_memory(self, mem):
636
        # Get a low-level memory object mapped to the image
637
        _mem = self._memobj.memory[mem.number - 1]
638

    
639
        if mem.empty:
640
            _mem.set_raw("\xFF" * (_mem.size() // 8))
641

    
642
            return
643

    
644
        if self.MODEL == "BF-V8A":
645
            _mem.set_raw("\x00" * 12 + "\x09\xFF\xFF\xFF")
646
        else:
647
            _mem.set_raw("\x00" * 12 + "\xF9\xFF\xFF\xFF")
648

    
649
        _mem.rxfreq = mem.freq / 10
650

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

    
663
        txtone, rxtone = chirp_common.split_tone_encode(mem)
664
        self._encode_tone(_mem.txtone, *txtone)
665
        self._encode_tone(_mem.rxtone, *rxtone)
666

    
667
        if self.MODEL != "T18" and self.MODEL != "RB18":
668
            _mem.highpower = mem.power == self.POWER_LEVELS[0]
669

    
670
        _mem.narrow = 'N' in mem.mode
671
        _mem.skip = mem.skip == "S"
672

    
673
        for setting in mem.extra:
674
            # NOTE: Only three settings right now, all are inverted
675
            setattr(_mem, setting.get_name(), not int(setting.value))
676

    
677
    def get_settings(self):
678
        _settings = self._memobj.settings
679
        if self.MODEL == "FRS-B1" or self.MODEL == "BF-V8A":
680
            _settings2 = self._memobj.settings2
681
        basic = RadioSettingGroup("basic", "Basic Settings")
682
        top = RadioSettings(basic)
683

    
684
        rs = RadioSetting("squelchlevel", "Squelch level",
685
                          RadioSettingValueInteger(
686
                              0, 9, _settings.squelchlevel))
687
        basic.append(rs)
688

    
689
        rs = RadioSetting("timeouttimer", "Timeout timer",
690
                          RadioSettingValueList(
691
                              TIMEOUTTIMER_LIST,
692
                              TIMEOUTTIMER_LIST[
693
                                  _settings.timeouttimer]))
694
        basic.append(rs)
695

    
696
        if self.MODEL == "RB18" or self.MODEL == "RB618":
697
            rs = RadioSetting("scan", "Scan",
698
                              RadioSettingValueBoolean(_settings.scan))
699
            basic.append(rs)
700
        elif self.MODEL == "FRS-B1" or self.MODEL == "BF-V8A":
701
            rs = RadioSetting("settings2.scan", "Scan",
702
                              RadioSettingValueBoolean(_settings2.scan))
703
            basic.append(rs)
704
        else:
705
            rs = RadioSetting("scanmode", "Scan mode",
706
                              RadioSettingValueList(
707
                                  SCANMODE_LIST,
708
                                  SCANMODE_LIST[_settings.scanmode]))
709
            basic.append(rs)
710

    
711
        if self.MODEL == "RT22S":
712
            rs = RadioSetting("voiceprompt", "Voice prompts",
713
                              RadioSettingValueBoolean(_settings.voiceprompt))
714
            basic.append(rs)
715
        elif self.MODEL == "RB18" or self.MODEL == "RB618":
716
            rs = RadioSetting("voice", "Voice prompts",
717
                              RadioSettingValueBoolean(_settings.voice))
718
            basic.append(rs)
719
        elif self.MODEL == "FRS-B1" or self.MODEL == "BF-V8A":
720
            rs = RadioSetting("settings2.voicesw", "Voice prompts",
721
                              RadioSettingValueBoolean(_settings2.voicesw))
722
            basic.append(rs)
723
        elif self.MODEL == "RT15":
724
            rs = RadioSetting("voiceprompt", "Voice prompts",
725
                              RadioSettingValueList(
726
                                  VOICE_LIST3,
727
                                  VOICE_LIST3[_settings.voiceprompt]))
728
            basic.append(rs)
729
        else:
730
            if self.MODEL != "RB87":
731
                rs = RadioSetting("voiceprompt", "Voice prompts",
732
                                  RadioSettingValueList(
733
                                      VOICE_LIST,
734
                                      VOICE_LIST[_settings.voiceprompt]))
735
                basic.append(rs)
736

    
737
        rs = RadioSetting("batterysaver", "Battery saver",
738
                          RadioSettingValueBoolean(_settings.batterysaver))
739
        basic.append(rs)
740

    
741
        if self.MODEL not in ["RB29",
742
                              "RB75",
743
                              "RB87",
744
                              "RB629",
745
                              "RT15"
746
                              ]:
747
            rs = RadioSetting("beep", "Beep",
748
                              RadioSettingValueBoolean(_settings.beep))
749
            basic.append(rs)
750

    
751
        if self.MODEL == "RB19" or self.MODEL == "RB19P" \
752
                or self.MODEL == "RB619":
753
            rs = RadioSetting("vox", "VOX",
754
                              RadioSettingValueBoolean(_settings.vox))
755
            basic.append(rs)
756

    
757
        if self.MODEL == "RB87":
758
            rs = RadioSetting("beep", "VOX",
759
                              RadioSettingValueBoolean(_settings.beep))
760
            basic.append(rs)
761

    
762
        if self.MODEL != "RB18" and self.MODEL != "RB618" \
763
                and self.MODEL != "FRS-B1" \
764
                and self.MODEL != "BF-V8A":
765
            rs = RadioSetting("voxlevel", "Vox level",
766
                              RadioSettingValueList(
767
                                  VOXLEVEL_LIST,
768
                                  VOXLEVEL_LIST[_settings.voxlevel]))
769
            basic.append(rs)
770

    
771
            rs = RadioSetting("voxdelay", "VOX delay",
772
                              RadioSettingValueList(
773
                                  VOXDELAY_LIST,
774
                                  VOXDELAY_LIST[_settings.voxdelay]))
775
            basic.append(rs)
776

    
777
        if self.MODEL == "RT22S":
778
            rs = RadioSetting("sidekey2", "Side Key 2(Long)",
779
                              RadioSettingValueList(
780
                                  SIDEKEY2_LIST,
781
                                  SIDEKEY2_LIST[_settings.sidekey2]))
782
            basic.append(rs)
783

    
784
        if self.MODEL == "RB18" or self.MODEL == "RB618":
785
            rs = RadioSetting("language", "Language",
786
                              RadioSettingValueList(
787
                                  VOICE_LIST2,
788
                                  VOICE_LIST2[_settings.language]))
789
            basic.append(rs)
790

    
791
            rs = RadioSetting("tail", "Tail",
792
                              RadioSettingValueBoolean(_settings.tail))
793
            basic.append(rs)
794

    
795
            rs = RadioSetting("hivoltnotx", "High voltage no TX",
796
                              RadioSettingValueBoolean(_settings.hivoltnotx))
797
            basic.append(rs)
798

    
799
            rs = RadioSetting("lovoltnotx", "Low voltage no TX",
800
                              RadioSettingValueBoolean(_settings.lovoltnotx))
801
            basic.append(rs)
802

    
803
            rs = RadioSetting("vox", "VOX",
804
                              RadioSettingValueBoolean(_settings.vox))
805
            basic.append(rs)
806

    
807
            if _settings.vox_level > 4:
808
                val = 1
809
            else:
810
                val = _settings.vox_level + 1
811
            rs = RadioSetting("vox_level", "VOX level",
812
                              RadioSettingValueInteger(1, 5, val))
813
            basic.append(rs)
814

    
815
            rs = RadioSetting("rogerbeep", "Roger beep",
816
                              RadioSettingValueBoolean(_settings.rogerbeep))
817
            basic.append(rs)
818

    
819
        if self.MODEL == "RB85":
820
            rs = RadioSetting("speccode", "SpecCode Select",
821
                              RadioSettingValueList(
822
                                  SPECCODE_LIST,
823
                                  SPECCODE_LIST[_settings.speccode]))
824
            basic.append(rs)
825

    
826
            rs = RadioSetting("sidekey2", "Side Key 1(Short)",
827
                              RadioSettingValueList(
828
                                  SIDEKEY85SHORT_LIST,
829
                                  SIDEKEY85SHORT_LIST[_settings.sidekey2]))
830
            basic.append(rs)
831

    
832
            rs = RadioSetting("sidekey1L", "Side Key 1(Long)",
833
                              RadioSettingValueList(
834
                                  SIDEKEY85LONG_LIST,
835
                                  SIDEKEY85LONG_LIST[_settings.sidekey1L]))
836
            basic.append(rs)
837

    
838
            rs = RadioSetting("sidekey2S", "Side Key 2(Short)",
839
                              RadioSettingValueList(
840
                                  SIDEKEY85SHORT_LIST,
841
                                  SIDEKEY85SHORT_LIST[_settings.sidekey2S]))
842
            basic.append(rs)
843

    
844
            rs = RadioSetting("sidekey2L", "Side Key 2(Long)",
845
                              RadioSettingValueList(
846
                                  SIDEKEY85LONG_LIST,
847
                                  SIDEKEY85LONG_LIST[_settings.sidekey2L]))
848
            basic.append(rs)
849

    
850
            rs = RadioSetting("power10w", "Power 10W",
851
                              RadioSettingValueBoolean(_settings.power10w))
852
            basic.append(rs)
853

    
854
        if self.MODEL == "RB75":
855
            rs = RadioSetting("sidekey2", "Side Key 1(Short)",
856
                              RadioSettingValueList(
857
                                  SIDEKEY75_LIST,
858
                                  SIDEKEY75_LIST[_settings.sidekey2]))
859
            basic.append(rs)
860

    
861
            rs = RadioSetting("sidekey1L", "Side Key 1(Long)",
862
                              RadioSettingValueList(
863
                                  SIDEKEY75_LIST,
864
                                  SIDEKEY75_LIST[_settings.sidekey1L]))
865
            basic.append(rs)
866

    
867
            rs = RadioSetting("sidekey2S", "Side Key 2(Short)",
868
                              RadioSettingValueList(
869
                                  SIDEKEY75_LIST,
870
                                  SIDEKEY75_LIST[_settings.sidekey2S]))
871
            basic.append(rs)
872

    
873
            rs = RadioSetting("sidekey2L", "Side Key 2(Long)",
874
                              RadioSettingValueList(
875
                                  SIDEKEY75_LIST,
876
                                  SIDEKEY75_LIST[_settings.sidekey2L]))
877
            basic.append(rs)
878

    
879
            rs = RadioSetting("power10w", "Low Voltage Stop TX",
880
                              RadioSettingValueBoolean(_settings.power10w))
881
            basic.append(rs)
882

    
883
        if self.MODEL == "RB87":
884
            rs = RadioSetting("sidekey2", "Side Key 1(Long)",
885
                              RadioSettingValueList(
886
                                  SIDEKEY87_LIST,
887
                                  SIDEKEY87_LIST[_settings.sidekey2]))
888
            basic.append(rs)
889

    
890
        if self.MODEL == "FRS-B1":
891
            rs = RadioSetting("settings2.hivoltnotx",
892
                              "High Voltage Inhibit TX",
893
                              RadioSettingValueBoolean(_settings2.hivoltnotx))
894
            basic.append(rs)
895

    
896
            rs = RadioSetting("settings2.lovoltnotx", "Low Voltage Inhibit TX",
897
                              RadioSettingValueBoolean(_settings2.lovoltnotx))
898
            basic.append(rs)
899

    
900
            rs = RadioSetting("settings2.vox", "Vox",
901
                              RadioSettingValueBoolean(_settings2.vox))
902
            basic.append(rs)
903

    
904
            rs = RadioSetting("settings2.voxnotxonrx", "Rx Disable VOX",
905
                              RadioSettingValueBoolean(_settings2.voxnotxonrx))
906
            basic.append(rs)
907

    
908
            rs = RadioSetting("settings2.voxgain", "Vox Gain",
909
                              RadioSettingValueInteger(
910
                                  1, 5, _settings2.voxgain))
911
            basic.append(rs)
912

    
913
        if self.MODEL == "RB19" or self.MODEL == "RB19P" \
914
                or self.MODEL == "RB619":
915
            rs = RadioSetting("sidekey2", "Left Navigation Button(Long)",
916
                              RadioSettingValueList(
917
                                  SIDEKEY19_LIST,
918
                                  SIDEKEY19_LIST[_settings.sidekey2]))
919
            basic.append(rs)
920

    
921
        if self.MODEL == "RT47" or self.MODEL == "RT47V" or \
922
                self.MODEL == "RT647":
923
            rs = RadioSetting("sidekey2", "Side Key 1(Long)",
924
                              RadioSettingValueList(
925
                                  SIDEKEY47_LIST,
926
                                  SIDEKEY47_LIST[_settings.sidekey2]))
927
            basic.append(rs)
928

    
929
            rs = RadioSetting("sidekey2S", "Side Key 2(Long)",
930
                              RadioSettingValueList(
931
                                  SIDEKEY47_LIST,
932
                                  SIDEKEY47_LIST[_settings.sidekey2S]))
933
            basic.append(rs)
934

    
935
        if self.MODEL == "BF-V8A":
936
            rs = RadioSetting("sidekey2", "Side key",
937
                              RadioSettingValueList(
938
                                  SIDEKEYV8A_LIST,
939
                                  SIDEKEYV8A_LIST[_settings.sidekey2]))
940
            basic.append(rs)
941

    
942
            rs = RadioSetting("settings2.rxemergency", "RX emergency",
943
                              RadioSettingValueBoolean(_settings2.rxemergency))
944
            basic.append(rs)
945

    
946
            rs = RadioSetting("settings2.voiceselect", "Language",
947
                              RadioSettingValueList(
948
                                  VOICE_LIST2,
949
                                  VOICE_LIST2[_settings2.voiceselect]))
950
            basic.append(rs)
951

    
952
            rs = RadioSetting("settings2.hivoltnotx",
953
                              "High voltage inhibit TX",
954
                              RadioSettingValueBoolean(_settings2.hivoltnotx))
955
            basic.append(rs)
956

    
957
            rs = RadioSetting("settings2.lovoltnotx", "Low voltage inhibit TX",
958
                              RadioSettingValueBoolean(_settings2.lovoltnotx))
959
            basic.append(rs)
960

    
961
            rs = RadioSetting("settings2.vox", "VOX",
962
                              RadioSettingValueBoolean(_settings2.vox))
963
            basic.append(rs)
964

    
965
            rs = RadioSetting("settings2.voxnotxonrx", "RX disable VOX",
966
                              RadioSettingValueBoolean(_settings2.voxnotxonrx))
967
            basic.append(rs)
968

    
969
            rs = RadioSetting("settings2.voxgain", "Vox Gain",
970
                              RadioSettingValueInteger(
971
                                  1, 5, _settings2.voxgain))
972
            basic.append(rs)
973

    
974
        if self.MODEL == "RB29" or self.MODEL == "RB629":
975
            rs = RadioSetting("codesw", "Code Switch",
976
                              RadioSettingValueBoolean(_settings.codesw))
977
            basic.append(rs)
978

    
979
            rs = RadioSetting("sidekey2S", "Side Key(Short)",
980
                              RadioSettingValueList(
981
                                  SIDEKEY29_LIST,
982
                                  SIDEKEY29_LIST[_settings.sidekey2S]))
983
            basic.append(rs)
984

    
985
            rs = RadioSetting("sidekey2L", "Side Key(Long)",
986
                              RadioSettingValueList(
987
                                  SIDEKEY29_LIST,
988
                                  SIDEKEY29_LIST[_settings.sidekey2L]))
989
            basic.append(rs)
990

    
991
        return top
992

    
993
    def set_settings(self, settings):
994
        for element in settings:
995
            if not isinstance(element, RadioSetting):
996
                self.set_settings(element)
997
                continue
998
            else:
999
                try:
1000
                    if "." in element.get_name():
1001
                        bits = element.get_name().split(".")
1002
                        obj = self._memobj
1003
                        for bit in bits[:-1]:
1004
                            obj = getattr(obj, bit)
1005
                        setting = bits[-1]
1006
                    else:
1007
                        obj = self._memobj.settings
1008
                        setting = element.get_name()
1009

    
1010
                    if element.has_apply_callback():
1011
                        LOG.debug("Using apply callback")
1012
                        element.run_apply_callback()
1013
                    elif setting == "vox_level":
1014
                        setattr(obj, setting, int(element.value) - 1)
1015
                    else:
1016
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1017
                        setattr(obj, setting, element.value)
1018
                except Exception as e:
1019
                    LOG.debug(element.get_name())
1020
                    raise
1021

    
1022
    @classmethod
1023
    def match_model(cls, filedata, filename):
1024
        if cls.MODEL == "T18":
1025
            match_size = False
1026
            match_model = False
1027

    
1028
            # testing the file data size
1029
            if len(filedata) == cls._memsize:
1030
                match_size = True
1031

    
1032
            # testing the model fingerprint
1033
            match_model = model_match(cls, filedata)
1034

    
1035
            if match_size and match_model:
1036
                return True
1037
            else:
1038
                return False
1039
        else:
1040
            # Radios that have always been post-metadata, so never do
1041
            # old-school detection
1042
            return False
1043

    
1044

    
1045
@directory.register
1046
class RT22SRadio(T18Radio):
1047
    """RETEVIS RT22S"""
1048
    VENDOR = "Retevis"
1049
    MODEL = "RT22S"
1050
    ACK_BLOCK = False
1051

    
1052
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
1053
                    chirp_common.PowerLevel("Low",  watts=0.50)]
1054

    
1055
    _magic = b"9COGRAM"
1056
    _fingerprint = [b"SMP558" + b"\x02"]
1057
    _upper = 22
1058
    _mem_params = (_upper  # number of channels
1059
                   )
1060
    _frs = True
1061
    _pmr = False
1062

    
1063

    
1064
@directory.register
1065
class RB18Radio(T18Radio):
1066
    """RETEVIS RB18"""
1067
    VENDOR = "Retevis"
1068
    MODEL = "RB18"
1069
    BLOCK_SIZE = 0x10
1070
    CMD_EXIT = b"E"
1071

    
1072
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
1073
                    chirp_common.PowerLevel("Low",  watts=0.50)]
1074

    
1075
    _magic = b"PROGRAL"
1076
    _fingerprint = [b"P3107" + b"\xF7"]
1077
    _upper = 22
1078
    _mem_params = (_upper  # number of channels
1079
                   )
1080
    _frs = True
1081
    _pmr = False
1082

    
1083
    _ranges = [
1084
        (0x0000, 0x0660),
1085
    ]
1086
    _memsize = 0x0660
1087

    
1088
    def process_mmap(self):
1089
        self._memobj = bitwise.parse(MEM_FORMAT_RB18 %
1090
                                     self._mem_params, self._mmap)
1091

    
1092
    @classmethod
1093
    def match_model(cls, filedata, filename):
1094
        # This radio has always been post-metadata, so never do
1095
        # old-school detection
1096
        return False
1097

    
1098

    
1099
@directory.register
1100
class RB618Radio(RB18Radio):
1101
    """RETEVIS RB618"""
1102
    VENDOR = "Retevis"
1103
    MODEL = "RB618"
1104

    
1105
    _upper = 16
1106
    _mem_params = (_upper  # number of channels
1107
                   )
1108
    _frs = False
1109
    _pmr = True
1110

    
1111

    
1112
@directory.register
1113
class RT68Radio(T18Radio):
1114
    """RETEVIS RT68"""
1115
    VENDOR = "Retevis"
1116
    MODEL = "RT68"
1117
    ACK_BLOCK = False
1118
    CMD_EXIT = b""
1119

    
1120
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
1121
                    chirp_common.PowerLevel("Low",  watts=0.50)]
1122

    
1123
    _magic = b"83OGRAM"
1124
    _fingerprint = [b"\x06\x00\x00\x00\x00\x00\x00\x00"]
1125
    _upper = 16
1126
    _mem_params = (_upper  # number of channels
1127
                   )
1128
    _frs16 = True
1129
    _pmr = False
1130

    
1131
    @classmethod
1132
    def match_model(cls, filedata, filename):
1133
        # This radio has always been post-metadata, so never do
1134
        # old-school detection
1135
        return False
1136

    
1137

    
1138
@directory.register
1139
class RT668Radio(RT68Radio):
1140
    """RETEVIS RT668"""
1141
    VENDOR = "Retevis"
1142
    MODEL = "RT668"
1143

    
1144
    _frs16 = False
1145
    _pmr = True
1146

    
1147

    
1148
@directory.register
1149
class RB17Radio(RT68Radio):
1150
    """RETEVIS RB17"""
1151
    VENDOR = "Retevis"
1152
    MODEL = "RB17"
1153

    
1154
    _magic = b"A5OGRAM"
1155
    _fingerprint = [b"\x53\x00\x00\x00\x00\x00\x00\x00"]
1156

    
1157
    _frs16 = True
1158
    _pmr = False
1159
    _murs = False
1160

    
1161

    
1162
@directory.register
1163
class RB617Radio(RB17Radio):
1164
    """RETEVIS RB617"""
1165
    VENDOR = "Retevis"
1166
    MODEL = "RB617"
1167

    
1168
    _frs16 = False
1169
    _pmr = True
1170
    _murs = False
1171

    
1172

    
1173
@directory.register
1174
class RB17VRadio(RB17Radio):
1175
    """RETEVIS RB17V"""
1176
    VENDOR = "Retevis"
1177
    MODEL = "RB17V"
1178

    
1179
    VALID_BANDS = [(136000000, 174000000)]
1180

    
1181
    _upper = 5
1182

    
1183
    _frs16 = False
1184
    _pmr = False
1185
    _murs = True
1186

    
1187

    
1188
@directory.register
1189
class RB85Radio(T18Radio):
1190
    """Retevis RB85"""
1191
    VENDOR = "Retevis"
1192
    MODEL = "RB85"
1193
    ACK_BLOCK = False
1194

    
1195
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=10.00),
1196
                    chirp_common.PowerLevel("Low", watts=5.00)]
1197

    
1198
    _magic = b"H19GRAM"
1199
    _fingerprint = [b"SMP558" + b"\x02"]
1200

    
1201

    
1202
@directory.register
1203
class RB75Radio(T18Radio):
1204
    """Retevis RB75"""
1205
    VENDOR = "Retevis"
1206
    MODEL = "RB75"
1207
    ACK_BLOCK = False
1208

    
1209
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
1210
                    chirp_common.PowerLevel("Low", watts=0.50)]
1211

    
1212
    _magic = b"KVOGRAM"
1213
    _fingerprint = [b"SMP558" + b"\x00"]
1214
    _upper = 30
1215
    _mem_params = (_upper  # number of channels
1216
                   )
1217
    _gmrs = False  # sold as GMRS radio but supports full band TX/RX
1218

    
1219

    
1220
@directory.register
1221
class FRSB1Radio(T18Radio):
1222
    """BTECH FRS-B1"""
1223
    VENDOR = "BTECH"
1224
    MODEL = "FRS-B1"
1225
    ACK_BLOCK = True
1226

    
1227
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
1228
                    chirp_common.PowerLevel("Low", watts=0.50)]
1229

    
1230
    _magic = b"PROGRAM"
1231
    _fingerprint = [b"P3107" + b"\xF7\x00"]
1232
    _upper = 22
1233
    _mem_params = (_upper  # number of channels
1234
                   )
1235
    _frs = True
1236

    
1237

    
1238
@directory.register
1239
class RB19Radio(T18Radio):
1240
    """Retevis RB19"""
1241
    VENDOR = "Retevis"
1242
    MODEL = "RB19"
1243
    ACK_BLOCK = False
1244

    
1245
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
1246
                    chirp_common.PowerLevel("Low", watts=0.50)]
1247

    
1248
    _magic = b"9COGRAM"
1249
    _fingerprint = [b"SMP558" + b"\x02"]
1250
    _upper = 22
1251
    _mem_params = (_upper  # number of channels
1252
                   )
1253
    _frs = True
1254

    
1255

    
1256
@directory.register
1257
class RB19PRadio(T18Radio):
1258
    """Retevis RB19P"""
1259
    VENDOR = "Retevis"
1260
    MODEL = "RB19P"
1261
    ACK_BLOCK = False
1262

    
1263
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=3.00),
1264
                    chirp_common.PowerLevel("Low", watts=0.50)]
1265

    
1266
    _magic = b"70OGRAM"
1267
    _fingerprint = [b"SMP558" + b"\x02"]
1268
    _upper = 30
1269
    _mem_params = (_upper  # number of channels
1270
                   )
1271
    _gmrs = True
1272

    
1273

    
1274
@directory.register
1275
class RB619Radio(T18Radio):
1276
    """Retevis RB619"""
1277
    VENDOR = "Retevis"
1278
    MODEL = "RB619"
1279
    ACK_BLOCK = False
1280

    
1281
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=0.500),
1282
                    chirp_common.PowerLevel("Low", watts=0.499)]
1283

    
1284
    _magic = b"9COGRAM"
1285
    _fingerprint = [b"SMP558" + b"\x02"]
1286
    _upper = 16
1287
    _mem_params = (_upper  # number of channels
1288
                   )
1289
    _pmr = True
1290

    
1291

    
1292
@directory.register
1293
class RT47Radio(T18Radio):
1294
    """Retevis RT47"""
1295
    VENDOR = "Retevis"
1296
    MODEL = "RT47"
1297
    ACK_BLOCK = False
1298

    
1299
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.000),
1300
                    chirp_common.PowerLevel("Low", watts=0.500)]
1301

    
1302
    _magic = b"47OGRAM"
1303
    _fingerprint = [b"\x06\x00\x00\x00\x00\x00\x00\x00"]
1304
    _upper = 16
1305
    _mem_params = (_upper  # number of channels
1306
                   )
1307
    _frs16 = True
1308
    _echo = True
1309

    
1310

    
1311
@directory.register
1312
class RT47VRadio(RT47Radio):
1313
    """Retevis RT47V"""
1314
    VENDOR = "Retevis"
1315
    MODEL = "RT47V"
1316

    
1317
    VALID_BANDS = [(136000000, 174000000)]
1318

    
1319
    _upper = 5
1320
    _mem_params = (_upper  # number of channels
1321
                   )
1322
    _frs16 = False
1323
    _murs = True
1324

    
1325

    
1326
@directory.register
1327
class RT647Radio(RT47Radio):
1328
    """Retevis RT647"""
1329
    VENDOR = "Retevis"
1330
    MODEL = "RT647"
1331

    
1332
    _frs16 = False
1333
    _pmr = True
1334

    
1335

    
1336
@directory.register
1337
class BFV8ARadio(T18Radio):
1338
    """Baofeng BF-V8A"""
1339
    VENDOR = "Baofeng"
1340
    MODEL = "BF-V8A"
1341
    ACK_BLOCK = True
1342

    
1343
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.000),
1344
                    chirp_common.PowerLevel("Low", watts=0.500)]
1345

    
1346
    _magic = b"PROGRAM"
1347
    _fingerprint = [b"P3107" + b"\xF7\x00\x00"]
1348
    _upper = 16
1349
    _mem_params = (_upper  # number of channels
1350
                   )
1351
    _echo = False
1352

    
1353

    
1354
@directory.register
1355
class RB29Radio(T18Radio):
1356
    """Retevis RB29"""
1357
    VENDOR = "Retevis"
1358
    MODEL = "RB29"
1359
    ACK_BLOCK = False
1360

    
1361
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
1362
                    chirp_common.PowerLevel("Low", watts=0.50)]
1363

    
1364
    _magic = b"S19GRAM"
1365
    _fingerprint = [b"SMP558" + b"\x02"]
1366
    _upper = 16
1367
    _mem_params = (_upper  # number of channels
1368
                   )
1369
    _frs16 = True
1370

    
1371

    
1372
@directory.register
1373
class RB629Radio(RB29Radio):
1374
    """Retevis RB29"""
1375
    VENDOR = "Retevis"
1376
    MODEL = "RB629"
1377

    
1378
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=0.500),
1379
                    chirp_common.PowerLevel("Low", watts=0.499)]
1380

    
1381
    _frs16 = False
1382
    _pmr = True
1383

    
1384

    
1385
@directory.register
1386
class RT15Radio(T18Radio):
1387
    """RETEVIS RT15"""
1388
    VENDOR = "Retevis"
1389
    MODEL = "RT15"
1390
    ACK_BLOCK = False
1391
    CMD_EXIT = b"b"
1392

    
1393
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
1394
                    chirp_common.PowerLevel("Low",  watts=0.50)]
1395

    
1396
    _magic = b"KAOGRAM"
1397
    _fingerprint = [b"\x06\x00\x00\x00\x00\x00\x00\x00",
1398
                    b"\x06\x03\xE8\x08\xFF\xFF\xFF\xFF"]
1399
    _upper = 16
1400
    _mem_params = (_upper  # number of channels
1401
                   )
1402
    _frs16 = True
1403

    
1404
    @classmethod
1405
    def match_model(cls, filedata, filename):
1406
        # This radio has always been post-metadata, so never do
1407
        # old-school detection
1408
        return False
1409

    
1410

    
1411
@directory.register
1412
class RB87Radio(T18Radio):
1413
    """RETEVIS RB87"""
1414
    VENDOR = "Retevis"
1415
    MODEL = "RB87"
1416
    ACK_BLOCK = False
1417
    CMD_EXIT = b""
1418
    ACK_BLOCK = False
1419

    
1420
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
1421
                    chirp_common.PowerLevel("Low", watts=0.50)]
1422

    
1423
    _magic = b"C8OGRAN"
1424
    _fingerprint = [b"SMP558"]
1425
    _upper = 30
1426
    _mem_params = (_upper  # number of channels
1427
                   )
1428
    _gmrs = True
    (1-1/1)