bf-t8_1 rb27b - frs.py

Jim Unroe, 12/20/2021 02:11 pm

Download (24.5 kB)

 
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 logging
20

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

    
40
LOG = logging.getLogger(__name__)
41

    
42
MEM_FORMAT = """
43
#seekto 0x0000;
44
struct {
45
  lbcd rxfreq[4];       // RX Frequency
46
  lbcd txfreq[4];       // TX Frequency
47
  u8 rx_tmode;          // RX Tone Mode
48
  u8 rx_tone;           // PL/DPL Decode
49
  u8 tx_tmode;          // TX Tone Mode
50
  u8 tx_tone;           // PL/DPL Encode
51
  u8 unknown1:3,        //
52
     skip:1,            // Scan Add: 1 = Skip, 0 = Scan
53
     unknown2:2,
54
     isnarrow:1,        // W/N: 1 = Narrow, 0 = Wide
55
     lowpower:1;        // TX Power: 1 = Low, 0 = High
56
  u8 unknown3[3];       //
57
} memory[%d];
58

    
59
#seekto 0x0630;
60
struct {
61
  u8 squelch;           // SQL
62
  u8 vox;               // Vox Lv
63
  u8 tot;               // TOT
64
  u8 unk1:3,            //
65
     ste:1,             // Tail Clear
66
     bcl:1,             // BCL
67
     save:1,            // Save
68
     tdr:1,             // TDR
69
     beep:1;            // Beep
70
  u8 voice;             // Voice
71
  u8 abr;               // Back Light
72
  u8 ring;              // Ring
73
  u8 unknown;           //
74
  u8 mra;               // MR Channel A
75
  u8 mrb;               // MR Channel B
76
  u8 disp_ab;           // Display A/B Selected
77
  ul16 fmcur;           // Broadcast FM station
78
  u8 workmode;          // Work Mode
79
  u8 wx;                // NOAA WX ch#
80
  u8 area;              // Area Selected
81
} settings;
82

    
83
#seekto 0x0D00;
84
struct {
85
  char name[6];
86
  u8 unknown1[2];
87
} names[%d];
88
"""
89

    
90
CMD_ACK = "\x06"
91

    
92
TONES = chirp_common.TONES
93
TMODES = ["", "Tone", "DTCS", "DTCS"]
94

    
95
AB_LIST = ["A", "B"]
96
ABR_LIST = ["OFF", "ON", "Key"]
97
AREA_LIST = ["China", "Japan", "Korea", "Malaysia", "American",
98
             "Australia", "Iran", "Taiwan", "Europe", "Russia"]
99
MDF_LIST = ["Frequency", "Channel #", "Name"]
100
RING_LIST = ["OFF"] + ["%s" % x for x in range(1, 11)]
101
TOT_LIST = ["OFF"] + ["%s seconds" % x for x in range(30, 210, 30)]
102
TOT2_LIST = TOT_LIST[1:]
103
VOICE_LIST = ["Off", "Chinese", "English"]
104
VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 6)]
105
WORKMODE_LIST = ["General", "PMR"]
106
WX_LIST = ["CH01 - 162.550",
107
           "CH02 - 162.400",
108
           "CH03 - 162.475",
109
           "CH04 - 162.425",
110
           "CH05 - 162.450",
111
           "CH06 - 162.500",
112
           "CH07 - 162.525"
113
           ]
114

    
115
SETTING_LISTS = {
116
    "ab": AB_LIST,
117
    "abr": ABR_LIST,
118
    "area": AREA_LIST,
119
    "mdf": MDF_LIST,
120
    "ring": RING_LIST,
121
    "tot": TOT_LIST,
122
    "tot": TOT2_LIST,
123
    "voice": VOICE_LIST,
124
    "vox": VOX_LIST,
125
    "workmode": WORKMODE_LIST,
126
    "wx": WX_LIST,
127
    }
128

    
129
FRS_FREQS1 = [462.5625, 462.5875, 462.6125, 462.6375, 462.6625,
130
              462.6875, 462.7125]
131
FRS_FREQS2 = [467.5625, 467.5875, 467.6125, 467.6375, 467.6625,
132
              467.6875, 467.7125]
133
FRS_FREQS3 = [462.5500, 462.5750, 462.6000, 462.6250, 462.6500,
134
              462.6750, 462.7000, 462.7250]
135
FRS_FREQS = FRS_FREQS1 + FRS_FREQS2 + FRS_FREQS3
136

    
137

    
138
def _enter_programming_mode(radio):
139
    serial = radio.pipe
140

    
141
    exito = False
142
    for i in range(0, 5):
143
        serial.write(radio._magic)
144
        ack = serial.read(1)
145

    
146
        try:
147
            if ack == CMD_ACK:
148
                exito = True
149
                break
150
        except:
151
            LOG.debug("Attempt #%s, failed, trying again" % i)
152
            pass
153

    
154
    # check if we had EXITO
155
    if exito is False:
156
        _exit_programming_mode(radio)
157
        msg = "The radio did not accept program mode after five tries.\n"
158
        msg += "Check you interface cable and power cycle your radio."
159
        raise errors.RadioError(msg)
160

    
161
    try:
162
        serial.write("\x02")
163
        ident = serial.read(len(radio._fingerprint))
164
    except:
165
        _exit_programming_mode(radio)
166
        raise errors.RadioError("Error communicating with radio")
167

    
168
    if not ident == radio._fingerprint:
169
        _exit_programming_mode(radio)
170
        LOG.debug(util.hexprint(ident))
171
        raise errors.RadioError("Radio returned unknown identification string")
172

    
173
    try:
174
        serial.write(CMD_ACK)
175
        ack = serial.read(1)
176
    except:
177
        _exit_programming_mode(radio)
178
        raise errors.RadioError("Error communicating with radio")
179

    
180
    if ack != CMD_ACK:
181
        _exit_programming_mode(radio)
182
        raise errors.RadioError("Radio refused to enter programming mode")
183

    
184

    
185
def _exit_programming_mode(radio):
186
    serial = radio.pipe
187
    try:
188
        serial.write("E")
189
    except:
190
        raise errors.RadioError("Radio refused to exit programming mode")
191

    
192

    
193
def _read_block(radio, block_addr, block_size):
194
    serial = radio.pipe
195

    
196
    cmd = struct.pack(">cHb", 'R', block_addr, block_size)
197
    expectedresponse = "W" + cmd[1:]
198
    LOG.debug("Reading block %04x..." % (block_addr))
199

    
200
    try:
201
        serial.write(cmd)
202
        response = serial.read(4 + block_size)
203
        if response[:4] != expectedresponse:
204
            raise Exception("Error reading block %04x." % (block_addr))
205

    
206
        block_data = response[4:]
207

    
208
        serial.write(CMD_ACK)
209
        ack = serial.read(1)
210
    except:
211
        _exit_programming_mode(radio)
212
        raise errors.RadioError("Failed to read block at %04x" % block_addr)
213

    
214
    if ack != CMD_ACK:
215
        _exit_programming_mode(radio)
216
        raise Exception("No ACK reading block %04x." % (block_addr))
217

    
218
    return block_data
219

    
220

    
221
def _write_block(radio, block_addr, block_size):
222
    serial = radio.pipe
223

    
224
    cmd = struct.pack(">cHb", 'W', block_addr, block_size)
225
    data = radio.get_mmap()[block_addr:block_addr + block_size]
226

    
227
    LOG.debug("Writing Data:")
228
    LOG.debug(util.hexprint(cmd + data))
229

    
230
    try:
231
        serial.write(cmd + data)
232
        if serial.read(1) != CMD_ACK:
233
            raise Exception("No ACK")
234
    except:
235
        _exit_programming_mode(radio)
236
        raise errors.RadioError("Failed to send block "
237
                                "to radio at %04x" % block_addr)
238

    
239

    
240
def do_download(radio):
241
    LOG.debug("download")
242
    _enter_programming_mode(radio)
243

    
244
    data = ""
245

    
246
    status = chirp_common.Status()
247
    status.msg = "Cloning from radio"
248

    
249
    status.cur = 0
250
    status.max = radio._memsize
251

    
252
    for addr in range(0, radio._memsize, radio.BLOCK_SIZE):
253
        status.cur = addr + radio.BLOCK_SIZE
254
        radio.status_fn(status)
255

    
256
        block = _read_block(radio, addr, radio.BLOCK_SIZE)
257
        data += block
258

    
259
        LOG.debug("Address: %04x" % addr)
260
        LOG.debug(util.hexprint(block))
261

    
262
    _exit_programming_mode(radio)
263

    
264
    return memmap.MemoryMap(data)
265

    
266

    
267
def do_upload(radio):
268
    status = chirp_common.Status()
269
    status.msg = "Uploading to radio"
270

    
271
    _enter_programming_mode(radio)
272

    
273
    status.cur = 0
274
    status.max = radio._memsize
275

    
276
    for start_addr, end_addr in radio._ranges:
277
        for addr in range(start_addr, end_addr, radio.BLOCK_SIZE_UP):
278
            status.cur = addr + radio.BLOCK_SIZE_UP
279
            radio.status_fn(status)
280
            _write_block(radio, addr, radio.BLOCK_SIZE_UP)
281

    
282
    _exit_programming_mode(radio)
283

    
284

    
285
class BFT8Radio(chirp_common.CloneModeRadio):
286
    """Baofeng BF-T8"""
287
    VENDOR = "Baofeng"
288
    MODEL = "BF-T8"
289
    BAUD_RATE = 9600
290
    BLOCK_SIZE = BLOCK_SIZE_UP = 0x10
291
    ODD_SPLIT = True
292
    HAS_NAMES = False
293
    NAME_LENGTH = 0
294
    VALID_CHARS = []
295
    CH_OFFSET = False
296
    SKIP_VALUES = []
297
    DTCS_CODES = sorted(chirp_common.DTCS_CODES)
298

    
299
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
300
                    chirp_common.PowerLevel("Low", watts=0.50)]
301
    VALID_BANDS = [(400000000, 470000000)]
302

    
303
    _magic = "\x02" + "PROGRAM"
304
    _fingerprint = "\x2E" + "BF-T6" + "\x2E"
305
    _upper = 99
306
    _mem_params = (_upper,  # number of channels
307
                   _upper   # number of names
308
                   )
309
    _frs = False
310

    
311
    _ranges = [
312
               (0x0000, 0x0B60),
313
              ]
314
    _memsize = 0x0B60
315

    
316
    def get_features(self):
317
        rf = chirp_common.RadioFeatures()
318
        rf.has_settings = True
319
        rf.has_bank = False
320
        rf.has_ctone = True
321
        rf.has_cross = True
322
        rf.has_rx_dtcs = True
323
        rf.has_tuning_step = False
324
        rf.has_name = self.HAS_NAMES
325
        rf.can_odd_split = self.ODD_SPLIT
326
        rf.valid_name_length = self.NAME_LENGTH
327
        rf.valid_characters = self.VALID_CHARS
328
        rf.valid_skips = self.SKIP_VALUES
329
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
330
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
331
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
332
        rf.valid_dtcs_codes = self.DTCS_CODES
333
        rf.valid_power_levels = self.POWER_LEVELS
334
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
335
        rf.valid_modes = ["FM", "NFM"]  # 25 kHz, 12.5 KHz.
336
        rf.memory_bounds = (1, self._upper)
337
        rf.valid_tuning_steps = [2.5, 5., 6.25, 10., 12.5, 25.]
338
        rf.valid_bands = self.VALID_BANDS
339

    
340
        return rf
341

    
342
    def process_mmap(self):
343
        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
344

    
345
    def validate_memory(self, mem):
346
        msgs = ""
347
        msgs = chirp_common.CloneModeRadio.validate_memory(self, mem)
348

    
349
        _msg_freq = 'Memory location cannot change frequency'
350
        _msg_simplex = 'Memory location only supports Duplex:(None)'
351
        _msg_nfm = 'Memory location only supports Mode: NFM'
352
        _msg_txp = 'Memory location only supports Power: Low'
353

    
354
        # FRS only models
355
        if self._frs:
356
            # range of memories with values set by FCC rules
357
            if mem.freq != int(FRS_FREQS[mem.number - 1] * 1000000):
358
                # warn user can't change frequency
359
                msgs.append(chirp_common.ValidationError(_msg_freq))
360

    
361
            # channels 1 - 22 are simplex only
362
            if str(mem.duplex) != "":
363
                # warn user can't change duplex
364
                msgs.append(chirp_common.ValidationError(_msg_simplex))
365

    
366
            # channels 1 - 22 are NFM only
367
            if str(mem.mode) != "NFM":
368
                # warn user can't change mode
369
                msgs.append(chirp_common.ValidationError(_msg_nfm))
370

    
371
            # channels 8 - 14 are low power only
372
            if mem.number >= 8 and mem.number <= 14:
373
                if str(mem.power) != "Low":
374
                    # warn user can't change power
375
                    msgs.append(chirp_common.ValidationError(_msg_txp))
376

    
377
        return msgs
378

    
379
    def sync_in(self):
380
        """Download from radio"""
381
        try:
382
            data = do_download(self)
383
        except errors.RadioError:
384
            # Pass through any real errors we raise
385
            raise
386
        except:
387
            # If anything unexpected happens, make sure we raise
388
            # a RadioError and log the problem
389
            LOG.exception('Unexpected error during download')
390
            raise errors.RadioError('Unexpected error communicating '
391
                                    'with the radio')
392
        self._mmap = data
393
        self.process_mmap()
394

    
395
    def sync_out(self):
396
        """Upload to radio"""
397
        try:
398
            do_upload(self)
399
        except:
400
            # If anything unexpected happens, make sure we raise
401
            # a RadioError and log the problem
402
            LOG.exception('Unexpected error during upload')
403
            raise errors.RadioError('Unexpected error communicating '
404
                                    'with the radio')
405

    
406
    def get_raw_memory(self, number):
407
        return repr(self._memobj.memory[number - 1])
408

    
409
    def _get_tone(self, mem, _mem):
410
        rx_tone = tx_tone = None
411

    
412
        tx_tmode = TMODES[_mem.tx_tmode]
413
        rx_tmode = TMODES[_mem.rx_tmode]
414

    
415
        if tx_tmode == "Tone":
416
            tx_tone = TONES[_mem.tx_tone]
417
        elif tx_tmode == "DTCS":
418
            tx_tone = self.DTCS_CODES[_mem.tx_tone]
419

    
420
        if rx_tmode == "Tone":
421
            rx_tone = TONES[_mem.rx_tone]
422
        elif rx_tmode == "DTCS":
423
            rx_tone = self.DTCS_CODES[_mem.rx_tone]
424

    
425
        tx_pol = _mem.tx_tmode == 0x03 and "R" or "N"
426
        rx_pol = _mem.rx_tmode == 0x03 and "R" or "N"
427

    
428
        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
429
                                       (rx_tmode, rx_tone, rx_pol))
430

    
431
    def _get_mem(self, number):
432
        return self._memobj.memory[number - 1]
433

    
434
    def _get_nam(self, number):
435
        return self._memobj.names[number - 1]
436

    
437
    def get_memory(self, number):
438
        _mem = self._get_mem(number)
439
        if self.HAS_NAMES:
440
            _nam = self._get_nam(number)
441

    
442
        mem = chirp_common.Memory()
443

    
444
        mem.number = number
445
        mem.freq = int(_mem.rxfreq) * 10
446

    
447
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
448
        if mem.freq == 0:
449
            mem.empty = True
450
            return mem
451

    
452
        if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF":
453
            mem.freq = 0
454
            mem.empty = True
455
            return mem
456

    
457
        if _mem.get_raw() == ("\xFF" * 16):
458
            LOG.debug("Initializing empty memory")
459
            _mem.set_raw("\x00" * 13 + "\xFF" * 3)
460

    
461
        if int(_mem.rxfreq) == int(_mem.txfreq):
462
            mem.duplex = ""
463
            mem.offset = 0
464
        else:
465
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
466
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
467

    
468
        # wide/narrow
469
        mem.mode = _mem.isnarrow and "NFM" or "FM"
470

    
471
        mem.skip = _mem.skip and "S" or ""
472

    
473
        if self.HAS_NAMES:
474
            for char in _nam.name:
475
                if str(char) == "\xFF":
476
                    char = " "  # The OEM software may have 0xFF mid-name
477
                mem.name += str(char)
478
            mem.name = mem.name.rstrip()
479

    
480
        # tone data
481
        self._get_tone(mem, _mem)
482

    
483
        # tx power
484
        levels = self.POWER_LEVELS
485
        try:
486
            mem.power = levels[_mem.lowpower]
487
        except IndexError:
488
            LOG.error("Radio reported invalid power level %s (in %s)" %
489
                      (_mem.lowpower, levels))
490
            mem.power = levels[0]
491

    
492
        if mem.number <= 22 and self._frs:
493
            FRS_IMMUTABLE = ["freq", "duplex", "offset", "mode"]
494
            if mem.number >= 8 and mem.number <= 14:
495
                mem.immutable = FRS_IMMUTABLE + ["power"]
496
            else:
497
                mem.immutable = FRS_IMMUTABLE
498

    
499
        return mem
500

    
501
    def _set_tone(self, mem, _mem):
502
        ((txmode, txtone, txpol),
503
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
504

    
505
        _mem.tx_tmode = TMODES.index(txmode)
506
        _mem.rx_tmode = TMODES.index(rxmode)
507
        if txmode == "Tone":
508
            _mem.tx_tone = TONES.index(txtone)
509
        elif txmode == "DTCS":
510
            _mem.tx_tmode = txpol == "R" and 0x03 or 0x02
511
            _mem.tx_tone = self.DTCS_CODES.index(txtone)
512
        if rxmode == "Tone":
513
            _mem.rx_tone = TONES.index(rxtone)
514
        elif rxmode == "DTCS":
515
            _mem.rx_tmode = rxpol == "R" and 0x03 or 0x02
516
            _mem.rx_tone = self.DTCS_CODES.index(rxtone)
517

    
518
    def _set_mem(self, number):
519
        return self._memobj.memory[number - 1]
520

    
521
    def _set_nam(self, number):
522
        return self._memobj.names[number - 1]
523

    
524
    def set_memory(self, mem):
525
        _mem = self._set_mem(mem.number)
526
        if self.HAS_NAMES:
527
            _nam = self._set_nam(mem.number)
528

    
529
        # if empty memmory
530
        if mem.empty:
531
            if self.HAS_NAMES:
532
                for i in range(0, self.NAME_LENGTH):
533
                    _nam.name[i].set_raw("\xFF")
534
            if self._frs:
535
                if mem.number <= 22:
536
                    _mem.set_raw("\xFF" * 8 + "\x00" * 5 + "\xFF" * 3)
537
                    FRS_FREQ = int(FRS_FREQS[mem.number - 1] * 100000)
538
                    _mem.rxfreq = _mem.txfreq = FRS_FREQ
539
                    _mem.isnarrow = True
540
                    if mem.number >= 8 and mem.number <= 14:
541
                        _mem.lowpower = True
542
                    else:
543
                        _mem.lowpower = False
544
                else:
545
                    _mem.set_raw("\xff" * 16)
546
            else:
547
                _mem.set_raw("\xFF" * 8 + "\x00" * 4 + "\x03" + "\xFF" * 3)
548

    
549
            return mem
550

    
551
        _mem.set_raw("\x00" * 13 + "\xFF" * 3)
552

    
553
        # frequency
554
        _mem.rxfreq = mem.freq / 10
555

    
556
        if mem.duplex == "off":
557
            for i in range(0, 4):
558
                _mem.txfreq[i].set_raw("\xFF")
559
        elif mem.duplex == "split":
560
            _mem.txfreq = mem.offset / 10
561
        elif mem.duplex == "+":
562
            _mem.txfreq = (mem.freq + mem.offset) / 10
563
        elif mem.duplex == "-":
564
            _mem.txfreq = (mem.freq - mem.offset) / 10
565
        else:
566
            _mem.txfreq = mem.freq / 10
567

    
568
        # wide/narrow
569
        _mem.isnarrow = mem.mode == "NFM"
570

    
571
        _mem.skip = mem.skip == "S"
572

    
573
        if self.HAS_NAMES:
574
            for i in range(self.NAME_LENGTH):
575
                try:
576
                    _nam.name[i] = mem.name[i]
577
                except IndexError:
578
                    _nam.name[i] = "\xFF"
579

    
580
        # tone data
581
        self._set_tone(mem, _mem)
582

    
583
        # tx power
584
        if mem.power:
585
            _mem.lowpower = self.POWER_LEVELS.index(mem.power)
586
        else:
587
            _mem.lowpower = 0
588

    
589
        return mem
590

    
591
    def get_settings(self):
592
        _settings = self._memobj.settings
593
        basic = RadioSettingGroup("basic", "Basic Settings")
594
        top = RadioSettings(basic)
595

    
596
        # Menu 03
597
        rs = RadioSettingValueInteger(0, 9, _settings.squelch)
598
        rset = RadioSetting("squelch", "Squelch Level", rs)
599
        basic.append(rset)
600

    
601
        model_list = ["RB27B", ]
602
        if self.MODEL in model_list:
603
            # Menu 09 (RB27x)
604
            rs = RadioSettingValueList(TOT2_LIST, TOT2_LIST[_settings.tot])
605
        else:
606
            # Menu 11
607
            rs = RadioSettingValueList(TOT_LIST, TOT_LIST[_settings.tot])
608
        rset = RadioSetting("tot", "Time-out timer", rs)
609
        basic.append(rset)
610

    
611
        # Menu 06
612
        rs = RadioSettingValueList(VOX_LIST, VOX_LIST[_settings.vox])
613
        rset = RadioSetting("vox", "VOX Level", rs)
614
        basic.append(rset)
615

    
616
        # Menu 15 (BF-T8) / 12 (RB-27/RB627)
617
        rs = RadioSettingValueList(VOICE_LIST, VOICE_LIST[_settings.voice])
618
        rset = RadioSetting("voice", "Voice", rs)
619
        basic.append(rset)
620

    
621
        # Menu 12
622
        rs = RadioSettingValueBoolean(_settings.bcl)
623
        rset = RadioSetting("bcl", "Busy Channel Lockout", rs)
624
        basic.append(rset)
625

    
626
        # Menu 10 / 08 (RB27/RB627)
627
        rs = RadioSettingValueBoolean(_settings.save)
628
        rset = RadioSetting("save", "Battery Saver", rs)
629
        basic.append(rset)
630

    
631
        # Menu 08 / 07 (RB-27/RB627)
632
        rs = RadioSettingValueBoolean(_settings.tdr)
633
        rset = RadioSetting("tdr", "Dual Watch", rs)
634
        basic.append(rset)
635

    
636
        # Menu 05
637
        rs = RadioSettingValueBoolean(_settings.beep)
638
        rset = RadioSetting("beep", "Beep", rs)
639
        basic.append(rset)
640

    
641
        # Menu 04
642
        rs = RadioSettingValueList(ABR_LIST, ABR_LIST[_settings.abr])
643
        rset = RadioSetting("abr", "Back Light", rs)
644
        basic.append(rset)
645

    
646
        # Menu 13 / 11 (RB-27/RB627)
647
        rs = RadioSettingValueList(RING_LIST, RING_LIST[_settings.ring])
648
        rset = RadioSetting("ring", "Ring", rs)
649
        basic.append(rset)
650

    
651
        rs = RadioSettingValueBoolean(not _settings.ste)
652
        rset = RadioSetting("ste", "Squelch Tail Eliminate", rs)
653
        basic.append(rset)
654

    
655
        #
656

    
657
        if self.CH_OFFSET:
658
            rs = RadioSettingValueInteger(1, self._upper, _settings.mra + 1)
659
        else:
660
            rs = RadioSettingValueInteger(1, self._upper, _settings.mra)
661
        rset = RadioSetting("mra", "MR A Channel #", rs)
662
        basic.append(rset)
663

    
664
        if self.CH_OFFSET:
665
            rs = RadioSettingValueInteger(1, self._upper, _settings.mrb + 1)
666
        else:
667
            rs = RadioSettingValueInteger(1, self._upper, _settings.mrb)
668
        rset = RadioSetting("mrb", "MR B Channel #", rs)
669
        basic.append(rset)
670

    
671
        rs = RadioSettingValueList(AB_LIST, AB_LIST[_settings.disp_ab])
672
        rset = RadioSetting("disp_ab", "Selected Display Line", rs)
673
        basic.append(rset)
674

    
675
        rs = RadioSettingValueList(WX_LIST, WX_LIST[_settings.wx])
676
        rset = RadioSetting("wx", "NOAA WX Radio", rs)
677
        basic.append(rset)
678

    
679
        def myset_freq(setting, obj, atrb, mult):
680
            """ Callback to set frequency by applying multiplier"""
681
            value = int(float(str(setting.value)) * mult)
682
            setattr(obj, atrb, value)
683
            return
684

    
685
        # FM Broadcast Settings
686
        val = _settings.fmcur
687
        val = val / 10.0
688
        val_low = 76.0
689
        if val < val_low or val > 108.0:
690
            val = 90.4
691
        rx = RadioSettingValueFloat(val_low, 108.0, val, 0.1, 1)
692
        rset = RadioSetting("settings.fmcur", "Broadcast FM Radio (MHz)", rx)
693
        rset.set_apply_callback(myset_freq, _settings, "fmcur", 10)
694
        basic.append(rset)
695

    
696
        model_list = ["BF-T8", "BF-U9", "AR-8"]
697
        if self.MODEL in model_list:
698
            rs = RadioSettingValueList(WORKMODE_LIST,
699
                                       WORKMODE_LIST[_settings.workmode])
700
            rset = RadioSetting("workmode", "Work Mode", rs)
701
            basic.append(rset)
702

    
703
            rs = RadioSettingValueList(AREA_LIST, AREA_LIST[_settings.area])
704
            rs.set_mutable(False)
705
            rset = RadioSetting("area", "Area", rs)
706
            basic.append(rset)
707

    
708
        return top
709

    
710
    def set_settings(self, settings):
711
        for element in settings:
712
            if not isinstance(element, RadioSetting):
713
                self.set_settings(element)
714
                continue
715
            else:
716
                try:
717
                    if "." in element.get_name():
718
                        bits = element.get_name().split(".")
719
                        obj = self._memobj
720
                        for bit in bits[:-1]:
721
                            obj = getattr(obj, bit)
722
                        setting = bits[-1]
723
                    else:
724
                        obj = self._memobj.settings
725
                        setting = element.get_name()
726

    
727
                    if element.has_apply_callback():
728
                        LOG.debug("Using apply callback")
729
                        element.run_apply_callback()
730
                    elif setting == "mra" and self.CH_OFFSET:
731
                        setattr(obj, setting, int(element.value) - 1)
732
                    elif setting == "mrb" and self.CH_OFFSET:
733
                        setattr(obj, setting, int(element.value) - 1)
734
                    elif setting == "ste":
735
                        setattr(obj, setting, not int(element.value))
736
                    elif element.value.get_mutable():
737
                        LOG.debug("Setting %s = %s" % (setting, element.value))
738
                        setattr(obj, setting, element.value)
739
                except Exception, e:
740
                    LOG.debug(element.get_name())
741
                    raise
742

    
743
    @classmethod
744
    def match_model(cls, filedata, filename):
745
        # This radio has always been post-metadata, so never do
746
        # old-school detection
747
        return False
748

    
749

    
750
class BFU9Alias(chirp_common.Alias):
751
    VENDOR = "Baofeng"
752
    MODEL = "BF-U9"
753

    
754

    
755
class AR8Alias(chirp_common.Alias):
756
    VENDOR = "Arcshell"
757
    MODEL = "AR-8"
758

    
759

    
760
@directory.register
761
class BaofengBFT8Generic(BFT8Radio):
762
    ALIASES = [BFU9Alias, AR8Alias, ]
763

    
764

    
765
@directory.register
766
class RetevisRT16(BFT8Radio):
767
    VENDOR = "Retevis"
768
    MODEL = "RT16"
769

    
770
    _upper = 22
771
    _frs = True
772

    
773

    
774
@directory.register
775
class RetevisRT27B(BFT8Radio):
776
    VENDOR = "Retevis"
777
    MODEL = "RB27B"
778
    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
779
    HAS_NAMES = True
780
    NAME_LENGTH = 6
781
    VALID_CHARS = chirp_common.CHARSET_UPPER_NUMERIC + "-"
782
    CH_OFFSET = True
783
    SKIP_VALUES = ["", "S"]
784
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
785
                    chirp_common.PowerLevel("Low", watts=0.50)]
786
    VALID_BANDS = [(400000000, 520000000)]
787

    
788
    _upper = 22
789
    _frs = True
790

    
791
    _ranges = [
792
               (0x0000, 0x0640),
793
               (0x0D00, 0x1040),
794
              ]
795
    _memsize = 0x1040