Project

General

Profile

New Model #9607 » bf-t8_2 rb27 - gmrs.py

test driver #3 - Jim Unroe, 12/22/2021 01:53 PM

 
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
  u16 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
           "CH08 - 161.650",
114
           "CH09 - 161.775",
115
           "CH10 - 161.750",
116
           "CH11 - 162.000"
117
           ]
118

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

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

    
141
GMRS_FREQS = FRS_FREQS + FRS_FREQS3
142

    
143

    
144
def _enter_programming_mode(radio):
145
    serial = radio.pipe
146

    
147
    exito = False
148
    for i in range(0, 5):
149
        serial.write(radio._magic)
150
        ack = serial.read(1)
151

    
152
        try:
153
            if ack == CMD_ACK:
154
                exito = True
155
                break
156
        except:
157
            LOG.debug("Attempt #%s, failed, trying again" % i)
158
            pass
159

    
160
    # check if we had EXITO
161
    if exito is False:
162
        _exit_programming_mode(radio)
163
        msg = "The radio did not accept program mode after five tries.\n"
164
        msg += "Check you interface cable and power cycle your radio."
165
        raise errors.RadioError(msg)
166

    
167
    try:
168
        serial.write("\x02")
169
        ident = serial.read(len(radio._fingerprint))
170
    except:
171
        _exit_programming_mode(radio)
172
        raise errors.RadioError("Error communicating with radio")
173

    
174
    if not ident == radio._fingerprint:
175
        _exit_programming_mode(radio)
176
        LOG.debug(util.hexprint(ident))
177
        raise errors.RadioError("Radio returned unknown identification string")
178

    
179
    try:
180
        serial.write(CMD_ACK)
181
        ack = serial.read(1)
182
    except:
183
        _exit_programming_mode(radio)
184
        raise errors.RadioError("Error communicating with radio")
185

    
186
    if ack != CMD_ACK:
187
        _exit_programming_mode(radio)
188
        raise errors.RadioError("Radio refused to enter programming mode")
189

    
190

    
191
def _exit_programming_mode(radio):
192
    serial = radio.pipe
193
    try:
194
        serial.write("E")
195
    except:
196
        raise errors.RadioError("Radio refused to exit programming mode")
197

    
198

    
199
def _read_block(radio, block_addr, block_size):
200
    serial = radio.pipe
201

    
202
    cmd = struct.pack(">cHb", 'R', block_addr, block_size)
203
    expectedresponse = "W" + cmd[1:]
204
    LOG.debug("Reading block %04x..." % (block_addr))
205

    
206
    try:
207
        serial.write(cmd)
208
        response = serial.read(4 + block_size)
209
        if response[:4] != expectedresponse:
210
            raise Exception("Error reading block %04x." % (block_addr))
211

    
212
        block_data = response[4:]
213

    
214
        serial.write(CMD_ACK)
215
        ack = serial.read(1)
216
    except:
217
        _exit_programming_mode(radio)
218
        raise errors.RadioError("Failed to read block at %04x" % block_addr)
219

    
220
    if ack != CMD_ACK:
221
        _exit_programming_mode(radio)
222
        raise Exception("No ACK reading block %04x." % (block_addr))
223

    
224
    return block_data
225

    
226

    
227
def _write_block(radio, block_addr, block_size):
228
    serial = radio.pipe
229

    
230
    cmd = struct.pack(">cHb", 'W', block_addr, block_size)
231
    data = radio.get_mmap()[block_addr:block_addr + block_size]
232

    
233
    LOG.debug("Writing Data:")
234
    LOG.debug(util.hexprint(cmd + data))
235

    
236
    try:
237
        serial.write(cmd + data)
238
        if serial.read(1) != CMD_ACK:
239
            raise Exception("No ACK")
240
    except:
241
        _exit_programming_mode(radio)
242
        raise errors.RadioError("Failed to send block "
243
                                "to radio at %04x" % block_addr)
244

    
245

    
246
def do_download(radio):
247
    LOG.debug("download")
248
    _enter_programming_mode(radio)
249

    
250
    data = ""
251

    
252
    status = chirp_common.Status()
253
    status.msg = "Cloning from radio"
254

    
255
    status.cur = 0
256
    status.max = radio._memsize
257

    
258
    for addr in range(0, radio._memsize, radio.BLOCK_SIZE):
259
        status.cur = addr + radio.BLOCK_SIZE
260
        radio.status_fn(status)
261

    
262
        block = _read_block(radio, addr, radio.BLOCK_SIZE)
263
        data += block
264

    
265
        LOG.debug("Address: %04x" % addr)
266
        LOG.debug(util.hexprint(block))
267

    
268
    _exit_programming_mode(radio)
269

    
270
    return memmap.MemoryMap(data)
271

    
272

    
273
def do_upload(radio):
274
    status = chirp_common.Status()
275
    status.msg = "Uploading to radio"
276

    
277
    _enter_programming_mode(radio)
278

    
279
    status.cur = 0
280
    status.max = radio._memsize
281

    
282
    for start_addr, end_addr in radio._ranges:
283
        for addr in range(start_addr, end_addr, radio.BLOCK_SIZE_UP):
284
            status.cur = addr + radio.BLOCK_SIZE_UP
285
            radio.status_fn(status)
286
            _write_block(radio, addr, radio.BLOCK_SIZE_UP)
287

    
288
    _exit_programming_mode(radio)
289

    
290

    
291
class BFT8Radio(chirp_common.CloneModeRadio):
292
    """Baofeng BF-T8"""
293
    VENDOR = "Baofeng"
294
    MODEL = "BF-T8"
295
    BAUD_RATE = 9600
296
    BLOCK_SIZE = BLOCK_SIZE_UP = 0x10
297
    ODD_SPLIT = True
298
    HAS_NAMES = False
299
    NAME_LENGTH = 0
300
    VALID_CHARS = ""
301
    CH_OFFSET = False
302
    SKIP_VALUES = []
303
    DTCS_CODES = sorted(chirp_common.DTCS_CODES)
304
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
305
                    chirp_common.PowerLevel("Low", watts=0.50)]
306
    VALID_BANDS = [(400000000, 470000000)]
307

    
308
    _magic = "\x02" + "PROGRAM"
309
    _fingerprint = "\x2E" + "BF-T6" + "\x2E"
310
    _upper = 99
311
    _mem_params = (_upper,  # number of channels
312
                   _upper   # number of names
313
                   )
314
    _frs = _gmrs = False
315

    
316
    _ranges = [
317
               (0x0000, 0x0B60),
318
              ]
319
    _memsize = 0x0B60
320

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

    
345
        return rf
346

    
347
    def process_mmap(self):
348
        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
349

    
350
    def validate_memory(self, mem):
351
        msgs = ""
352
        msgs = chirp_common.CloneModeRadio.validate_memory(self, mem)
353

    
354
        _msg_freq = 'Memory location cannot change frequency'
355
        _msg_simplex = 'Memory location only supports Duplex:(None)'
356
        _msg_duplex = 'Memory location only supports Duplex: +'
357
        _msg_nfm = 'Memory location only supports Mode: NFM'
358
        _msg_txp = 'Memory location only supports Power: Low'
359

    
360
        """
361
        # FRS only models
362
        if self._frs:
363
            # range of memories with values set by FCC rules
364
            if mem.freq != int(FRS_FREQS[mem.number - 1] * 1000000):
365
                # warn user can't change frequency
366
                msgs.append(chirp_common.ValidationError(_msg_freq))
367

    
368
            # channels 1 - 22 are simplex only
369
            if str(mem.duplex) != "":
370
                # warn user can't change duplex
371
                msgs.append(chirp_common.ValidationError(_msg_simplex))
372

    
373
            # channels 1 - 22 are NFM only
374
            if str(mem.mode) != "NFM":
375
                # warn user can't change mode
376
                msgs.append(chirp_common.ValidationError(_msg_nfm))
377

    
378
            # channels 8 - 14 are low power only
379
            if mem.number >= 8 and mem.number <= 14:
380
                if str(mem.power) != "Low":
381
                    # warn user can't change power
382
                    msgs.append(chirp_common.ValidationError(_msg_txp))
383

    
384
        # GMRS only models
385
        if self._gmrs and mem.number <= 30:
386
            # range of memories with values set by FCC rules
387
            if mem.freq != int(GMRS_FREQS[mem.number - 1] * 1000000):
388
                # warn user can't change frequency
389
                msgs.append(chirp_common.ValidationError(_msg_freq))
390

    
391
            # channels 1 - 22 are simplex only
392
            if mem.number >= 1 and mem.number <= 22:
393
                if str(mem.duplex) != "":
394
                    # warn user can't change duplex
395
                    msgs.append(chirp_common.ValidationError(_msg_simplex))
396

    
397
            # channels 21 - 30 are + duplex only
398
            if mem.number >= 21 and mem.number <= 30:
399
                if str(mem.duplex) != "+":
400
                    # warn user can't change duplex
401
                    msgs.append(chirp_common.ValidationError(_msg_duplex))
402

    
403
            # channels 8 - 14 are low power NFM only
404
            if mem.number >= 8 and mem.number <= 14:
405
                if str(mem.mode) != "NFM":
406
                    # warn user can't change mode
407
                    msgs.append(chirp_common.ValidationError(_msg_nfm))
408

    
409
                if str(mem.power) != "Low":
410
                    # warn user can't change power
411
                    msgs.append(chirp_common.ValidationError(_msg_txp))
412
        """
413

    
414
        return msgs
415

    
416
    def sync_in(self):
417
        """Download from radio"""
418
        try:
419
            data = do_download(self)
420
        except errors.RadioError:
421
            # Pass through any real errors we raise
422
            raise
423
        except:
424
            # If anything unexpected happens, make sure we raise
425
            # a RadioError and log the problem
426
            LOG.exception('Unexpected error during download')
427
            raise errors.RadioError('Unexpected error communicating '
428
                                    'with the radio')
429
        self._mmap = data
430
        self.process_mmap()
431

    
432
    def sync_out(self):
433
        """Upload to radio"""
434
        try:
435
            do_upload(self)
436
        except:
437
            # If anything unexpected happens, make sure we raise
438
            # a RadioError and log the problem
439
            LOG.exception('Unexpected error during upload')
440
            raise errors.RadioError('Unexpected error communicating '
441
                                    'with the radio')
442

    
443
    def get_raw_memory(self, number):
444
        return repr(self._memobj.memory[number - 1])
445

    
446
    def _get_tone(self, mem, _mem):
447
        rx_tone = tx_tone = None
448

    
449
        tx_tmode = TMODES[_mem.tx_tmode]
450
        rx_tmode = TMODES[_mem.rx_tmode]
451

    
452
        if tx_tmode == "Tone":
453
            tx_tone = TONES[_mem.tx_tone]
454
        elif tx_tmode == "DTCS":
455
            tx_tone = self.DTCS_CODES[_mem.tx_tone]
456

    
457
        if rx_tmode == "Tone":
458
            rx_tone = TONES[_mem.rx_tone]
459
        elif rx_tmode == "DTCS":
460
            rx_tone = self.DTCS_CODES[_mem.rx_tone]
461

    
462
        tx_pol = _mem.tx_tmode == 0x03 and "R" or "N"
463
        rx_pol = _mem.rx_tmode == 0x03 and "R" or "N"
464

    
465
        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
466
                                       (rx_tmode, rx_tone, rx_pol))
467

    
468
    def _is_txinh(self, _mem):
469
        raw_tx = ""
470
        for i in range(0, 4):
471
            raw_tx += _mem.txfreq[i].get_raw()
472
        return raw_tx == "\xFF\xFF\xFF\xFF"
473

    
474
    def _get_mem(self, number):
475
        return self._memobj.memory[number - 1]
476

    
477
    def _get_nam(self, number):
478
        return self._memobj.names[number - 1]
479

    
480
    def get_memory(self, number):
481
        _mem = self._get_mem(number)
482
        if self.HAS_NAMES:
483
            _nam = self._get_nam(number)
484

    
485
        mem = chirp_common.Memory()
486

    
487
        mem.number = number
488
        mem.freq = int(_mem.rxfreq) * 10
489

    
490
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
491
        if mem.freq == 0:
492
            mem.empty = True
493
            return mem
494

    
495
        if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF":
496
            mem.freq = 0
497
            mem.empty = True
498
            return mem
499

    
500
        if _mem.get_raw() == ("\xFF" * 16):
501
            LOG.debug("Initializing empty memory")
502
            _mem.set_raw("\x00" * 13 + "\xFF" * 3)
503

    
504
        if self._is_txinh(_mem):
505
            mem.duplex = "off"
506
            mem.offset = 0
507
        elif int(_mem.rxfreq) == int(_mem.txfreq):
508
            mem.duplex = ""
509
            mem.offset = 0
510
        else:
511
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
512
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
513

    
514
        # wide/narrow
515
        mem.mode = _mem.isnarrow and "NFM" or "FM"
516

    
517
        mem.skip = _mem.skip and "S" or ""
518

    
519
        if self.HAS_NAMES:
520
            for char in _nam.name:
521
                if str(char) == "\xFF":
522
                    char = " "  # The OEM software may have 0xFF mid-name
523
                mem.name += str(char)
524
            mem.name = mem.name.rstrip()
525

    
526
        # tone data
527
        self._get_tone(mem, _mem)
528

    
529
        # tx power
530
        levels = self.POWER_LEVELS
531
        try:
532
            mem.power = levels[_mem.lowpower]
533
        except IndexError:
534
            LOG.error("Radio reported invalid power level %s (in %s)" %
535
                      (_mem.lowpower, levels))
536
            mem.power = levels[0]
537

    
538
        if mem.number <= 22 and self._frs:
539
            FRS_IMMUTABLE = ["freq", "duplex", "offset", "mode"]
540
            if mem.number >= 8 and mem.number <= 14:
541
                mem.immutable = FRS_IMMUTABLE + ["power"]
542
            else:
543
                mem.immutable = FRS_IMMUTABLE
544

    
545
        if mem.number <= 30 and self._gmrs:
546
            GMRS_IMMUTABLE = ["freq", "duplex", "offset"]
547
            if mem.number >= 8 and mem.number <= 14:
548
                mem.immutable = GMRS_IMMUTABLE + ["mode", "power"]
549
            else:
550
                mem.immutable = GMRS_IMMUTABLE
551

    
552
        return mem
553

    
554
    def _set_tone(self, mem, _mem):
555
        ((txmode, txtone, txpol),
556
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
557

    
558
        _mem.tx_tmode = TMODES.index(txmode)
559
        _mem.rx_tmode = TMODES.index(rxmode)
560
        if txmode == "Tone":
561
            _mem.tx_tone = TONES.index(txtone)
562
        elif txmode == "DTCS":
563
            _mem.tx_tmode = txpol == "R" and 0x03 or 0x02
564
            _mem.tx_tone = self.DTCS_CODES.index(txtone)
565
        if rxmode == "Tone":
566
            _mem.rx_tone = TONES.index(rxtone)
567
        elif rxmode == "DTCS":
568
            _mem.rx_tmode = rxpol == "R" and 0x03 or 0x02
569
            _mem.rx_tone = self.DTCS_CODES.index(rxtone)
570

    
571
    def _set_mem(self, number):
572
        return self._memobj.memory[number - 1]
573

    
574
    def _set_nam(self, number):
575
        return self._memobj.names[number - 1]
576

    
577
    def set_memory(self, mem):
578
        _mem = self._set_mem(mem.number)
579
        if self.HAS_NAMES:
580
            _nam = self._set_nam(mem.number)
581

    
582
        # if empty memmory
583
        if mem.empty:
584
            _mem.set_raw("\xff" * 16)
585
            if self.HAS_NAMES:
586
                for i in range(0, self.NAME_LENGTH):
587
                    _nam.name[i].set_raw("\xFF")
588
            #if self._frs:
589
            #    if mem.number <= 22:
590
            #        _mem.set_raw("\xFF" * 8 + "\x00" * 5 + "\xFF" * 3)
591
            #        FRS_FREQ = int(FRS_FREQS[mem.number - 1] * 100000)
592
            #        _mem.rxfreq = _mem.txfreq = FRS_FREQ
593
            #        _mem.isnarrow = True
594
            #        if mem.number >= 8 and mem.number <= 14:
595
            #            _mem.lowpower = True
596
            #        else:
597
            #            _mem.lowpower = False
598
            #    else:
599
            #        _mem.set_raw("\xff" * 16)
600
            #else:
601
            #    _mem.set_raw("\xFF" * 8 + "\x00" * 4 + "\x03" + "\xFF" * 3)
602

    
603
            #_mem.set_raw("\xFF" * 8 + "\x00" * 4 + "\x03" + "\xFF" * 3)
604

    
605
            return mem
606

    
607
        _mem.set_raw("\x00" * 13 + "\xFF" * 3)
608

    
609
        if self._frs:
610
            if mem.number >= 1 and mem.number <= 22:
611
                FRS_FREQ = int(FRS_FREQS[mem.number - 1] * 1000000)
612
                mem.freq = FRS_FREQ
613
                if mem.number <= 22:
614
                    mem.duplex = ''
615
                    mem.offset = 0
616
                    if mem.number >= 8 and mem.number <= 14:
617
                        mem.mode = "NFM"
618
                        mem.power = self.POWER_LEVELS[1]
619

    
620
        print "frequency"
621
        print mem.freq / 10
622
        print mem.number
623

    
624
        if self._gmrs:
625
            if mem.number >= 1 and mem.number <= 30:
626
                GMRS_FREQ = int(GMRS_FREQS[mem.number - 1] * 1000000)
627
                mem.freq = GMRS_FREQ
628
                if mem.number <= 22:
629
                    mem.duplex = ''
630
                    mem.offset = 0
631
                    if mem.number >= 8 and mem.number <= 14:
632
                        mem.mode = "NFM"
633
                        mem.power = self.POWER_LEVELS[1]
634
                if mem.number > 22:
635
                    mem.duplex = '+'
636
                    mem.offset = 5000000
637
            elif float(mem.freq) / 1000000 in GMRS_FREQS:
638
                if float(mem.freq) / 1000000 in FRS_FREQS1:
639
                    mem.duplex = ''
640
                    mem.offset = 0
641
                if float(mem.freq) / 1000000 in FRS_FREQS2:
642
                    mem.duplex = ''
643
                    mem.offset = 0
644
                    mem.mode = "NFM"
645
                    mem.power = self.POWER_LEVELS[1]
646
                if float(mem.freq) / 1000000 in FRS_FREQS3:
647
                    if mem.duplex == '+':
648
                        mem.offset = 5000000
649
                    #elif mem.duplex == '':
650
                    #    mem.offset = 0
651
                    else:
652
                        mem.duplex = ''
653
                        mem.offset = 0
654
            else:
655
                mem.duplex = 'off'
656
                mem.offset = 0
657
                
658
        print "frequency"
659
        print mem.freq / 10
660

    
661
        # frequency
662
        _mem.rxfreq = mem.freq / 10
663

    
664
        if mem.duplex == "off":
665
            for i in range(0, 4):
666
                _mem.txfreq[i].set_raw("\xFF")
667
        elif mem.duplex == "split":
668
            _mem.txfreq = mem.offset / 10
669
        elif mem.duplex == "+":
670
            _mem.txfreq = (mem.freq + mem.offset) / 10
671
        elif mem.duplex == "-":
672
            _mem.txfreq = (mem.freq - mem.offset) / 10
673
        else:
674
            _mem.txfreq = mem.freq / 10
675

    
676
        # wide/narrow
677
        _mem.isnarrow = mem.mode == "NFM"
678

    
679
        _mem.skip = mem.skip == "S"
680

    
681
        if self.HAS_NAMES:
682
            for i in range(self.NAME_LENGTH):
683
                try:
684
                    _nam.name[i] = mem.name[i]
685
                except IndexError:
686
                    _nam.name[i] = "\xFF"
687

    
688
        # tone data
689
        self._set_tone(mem, _mem)
690

    
691
        # tx power
692
        if mem.power:
693
            _mem.lowpower = self.POWER_LEVELS.index(mem.power)
694
        else:
695
            _mem.lowpower = 0
696

    
697
        return mem
698

    
699
    def get_settings(self):
700
        _settings = self._memobj.settings
701
        basic = RadioSettingGroup("basic", "Basic Settings")
702
        top = RadioSettings(basic)
703

    
704
        # Menu 03
705
        rs = RadioSettingValueInteger(0, 9, _settings.squelch)
706
        rset = RadioSetting("squelch", "Squelch Level", rs)
707
        basic.append(rset)
708

    
709
        model_list = ["RB27B", ]
710
        if self.MODEL in model_list:
711
            # Menu 09 (RB27x)
712
            rs = RadioSettingValueList(TOT2_LIST, TOT2_LIST[_settings.tot])
713
        else:
714
            # Menu 11 / 09 (RB27)
715
            rs = RadioSettingValueList(TOT_LIST, TOT_LIST[_settings.tot])
716
        rset = RadioSetting("tot", "Time-out timer", rs)
717
        basic.append(rset)
718

    
719
        # Menu 06
720
        rs = RadioSettingValueList(VOX_LIST, VOX_LIST[_settings.vox])
721
        rset = RadioSetting("vox", "VOX Level", rs)
722
        basic.append(rset)
723

    
724
        # Menu 15 (BF-T8) / 12 (RB-27/RB627)
725
        rs = RadioSettingValueList(VOICE_LIST, VOICE_LIST[_settings.voice])
726
        rset = RadioSetting("voice", "Voice", rs)
727
        basic.append(rset)
728

    
729
        # Menu 12
730
        rs = RadioSettingValueBoolean(_settings.bcl)
731
        rset = RadioSetting("bcl", "Busy Channel Lockout", rs)
732
        basic.append(rset)
733

    
734
        # Menu 10 / 08 (RB27/RB627)
735
        rs = RadioSettingValueBoolean(_settings.save)
736
        rset = RadioSetting("save", "Battery Saver", rs)
737
        basic.append(rset)
738

    
739
        # Menu 08 / 07 (RB-27/RB627)
740
        rs = RadioSettingValueBoolean(_settings.tdr)
741
        rset = RadioSetting("tdr", "Dual Watch", rs)
742
        basic.append(rset)
743

    
744
        # Menu 05
745
        rs = RadioSettingValueBoolean(_settings.beep)
746
        rset = RadioSetting("beep", "Beep", rs)
747
        basic.append(rset)
748

    
749
        # Menu 04
750
        rs = RadioSettingValueList(ABR_LIST, ABR_LIST[_settings.abr])
751
        rset = RadioSetting("abr", "Back Light", rs)
752
        basic.append(rset)
753

    
754
        # Menu 13 / 11 (RB-27/RB627)
755
        rs = RadioSettingValueList(RING_LIST, RING_LIST[_settings.ring])
756
        rset = RadioSetting("ring", "Ring", rs)
757
        basic.append(rset)
758

    
759
        rs = RadioSettingValueBoolean(not _settings.ste)
760
        rset = RadioSetting("ste", "Squelch Tail Eliminate", rs)
761
        basic.append(rset)
762

    
763
        #
764

    
765
        if self.CH_OFFSET:
766
            rs = RadioSettingValueInteger(1, self._upper, _settings.mra + 1)
767
        else:
768
            rs = RadioSettingValueInteger(1, self._upper, _settings.mra)
769
        rset = RadioSetting("mra", "MR A Channel #", rs)
770
        basic.append(rset)
771

    
772
        if self.CH_OFFSET:
773
            rs = RadioSettingValueInteger(1, self._upper, _settings.mrb + 1)
774
        else:
775
            rs = RadioSettingValueInteger(1, self._upper, _settings.mrb)
776
        rset = RadioSetting("mrb", "MR B Channel #", rs)
777
        basic.append(rset)
778

    
779
        rs = RadioSettingValueList(AB_LIST, AB_LIST[_settings.disp_ab])
780
        rset = RadioSetting("disp_ab", "Selected Display Line", rs)
781
        basic.append(rset)
782

    
783
        rs = RadioSettingValueList(WX_LIST, WX_LIST[_settings.wx])
784
        rset = RadioSetting("wx", "NOAA WX Radio", rs)
785
        basic.append(rset)
786

    
787
        def myset_freq(setting, obj, atrb, mult):
788
            """ Callback to set frequency by applying multiplier"""
789
            value = int(float(str(setting.value)) * mult)
790
            setattr(obj, atrb, value)
791
            return
792

    
793
        # FM Broadcast Settings
794
        val = _settings.fmcur
795
        val = val / 10.0
796
        val_low = 76.0
797
        if val < val_low or val > 108.0:
798
            val = 90.4
799
        rx = RadioSettingValueFloat(val_low, 108.0, val, 0.1, 1)
800
        rset = RadioSetting("settings.fmcur", "Broadcast FM Radio (MHz)", rx)
801
        rset.set_apply_callback(myset_freq, _settings, "fmcur", 10)
802
        basic.append(rset)
803

    
804
        model_list = ["BF-T8", "BF-U9", "AR-8"]
805
        if self.MODEL in model_list:
806
            rs = RadioSettingValueList(WORKMODE_LIST,
807
                                       WORKMODE_LIST[_settings.workmode])
808
            rset = RadioSetting("workmode", "Work Mode", rs)
809
            basic.append(rset)
810

    
811
            rs = RadioSettingValueList(AREA_LIST, AREA_LIST[_settings.area])
812
            rs.set_mutable(False)
813
            rset = RadioSetting("area", "Area", rs)
814
            basic.append(rset)
815

    
816
        return top
817

    
818
    def set_settings(self, settings):
819
        for element in settings:
820
            if not isinstance(element, RadioSetting):
821
                self.set_settings(element)
822
                continue
823
            else:
824
                try:
825
                    if "." in element.get_name():
826
                        bits = element.get_name().split(".")
827
                        obj = self._memobj
828
                        for bit in bits[:-1]:
829
                            obj = getattr(obj, bit)
830
                        setting = bits[-1]
831
                    else:
832
                        obj = self._memobj.settings
833
                        setting = element.get_name()
834

    
835
                    if element.has_apply_callback():
836
                        LOG.debug("Using apply callback")
837
                        element.run_apply_callback()
838
                    elif setting == "mra" and self.CH_OFFSET:
839
                        setattr(obj, setting, int(element.value) - 1)
840
                    elif setting == "mrb" and self.CH_OFFSET:
841
                        setattr(obj, setting, int(element.value) - 1)
842
                    elif setting == "ste":
843
                        setattr(obj, setting, not int(element.value))
844
                    elif element.value.get_mutable():
845
                        LOG.debug("Setting %s = %s" % (setting, element.value))
846
                        setattr(obj, setting, element.value)
847
                except Exception, e:
848
                    LOG.debug(element.get_name())
849
                    raise
850

    
851
    @classmethod
852
    def match_model(cls, filedata, filename):
853
        # This radio has always been post-metadata, so never do
854
        # old-school detection
855
        return False
856

    
857

    
858
class BFU9Alias(chirp_common.Alias):
859
    VENDOR = "Baofeng"
860
    MODEL = "BF-U9"
861

    
862

    
863
class AR8Alias(chirp_common.Alias):
864
    VENDOR = "Arcshell"
865
    MODEL = "AR-8"
866

    
867

    
868
@directory.register
869
class BaofengBFT8Generic(BFT8Radio):
870
    ALIASES = [BFU9Alias, AR8Alias, ]
871

    
872

    
873
@directory.register
874
class RetevisRT16(BFT8Radio):
875
    VENDOR = "Retevis"
876
    MODEL = "RT16"
877

    
878
    _upper = 22
879
    _frs = True
880

    
881

    
882
@directory.register
883
class RetevisRT27B(BFT8Radio):
884
    VENDOR = "Retevis"
885
    MODEL = "RB27B"
886
    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
887
    HAS_NAMES = True
888
    NAME_LENGTH = 6
889
    VALID_CHARS = chirp_common.CHARSET_UPPER_NUMERIC + "-"
890
    CH_OFFSET = True
891
    SKIP_VALUES = ["", "S"]
892
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
893
                    chirp_common.PowerLevel("Low", watts=0.50)]
894
    VALID_BANDS = [(400000000, 520000000)]
895

    
896
    _upper = 22
897
    _frs = True
898
    _gmrs = False
899

    
900
    _ranges = [
901
               (0x0000, 0x0640),
902
               (0x0D00, 0x1040),
903
              ]
904
    _memsize = 0x1040
905

    
906

    
907
@directory.register
908
class RetevisRT27(RetevisRT27B):
909
    VENDOR = "Retevis"
910
    MODEL = "RB27"
911
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
912
                    chirp_common.PowerLevel("Low", watts=0.50)]
913
    VALID_BANDS = [(136000000, 174000000),
914
                   (400000000, 520000000)]
915

    
916
    _upper = 99
917
    _gmrs = True
918
    _frs = False
(3-3/3)