baofeng_wp970i_uv-9g_#1.py

Jim Unroe, 12/08/2021 07:14 pm

Download (35.6 kB)

 
1
# Copyright 2021:
2
# * Jim Unroe KC9HI, <rock.unroe@gmail.com>
3
#
4
# This program is free software: you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation, either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

    
17
import time
18
import struct
19
import logging
20
import re
21

    
22
from chirp.drivers import baofeng_common
23
from chirp import chirp_common, directory, memmap
24
from chirp import bitwise, errors, util
25
from chirp.settings import RadioSettingGroup, RadioSetting, \
26
    RadioSettingValueBoolean, RadioSettingValueList, \
27
    RadioSettingValueString, RadioSettingValueInteger, \
28
    RadioSettingValueFloat, RadioSettings, \
29
    InvalidValueError
30
from textwrap import dedent
31

    
32
LOG = logging.getLogger(__name__)
33

    
34
# #### MAGICS #########################################################
35

    
36
# Baofeng WP970I magic string
37
MSTRING_WP970I = "\x50\xBB\xFF\x20\x14\x04\x13"
38

    
39
# Baofeng UV-9G magic string
40
MSTRING_UV9G = "\x50\xBB\xFF\x20\x12\x05\x25"
41

    
42

    
43
DTMF_CHARS = "0123456789 *#ABCD"
44
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0]
45

    
46
LIST_AB = ["A", "B"]
47
LIST_ALMOD = ["Site", "Tone", "Code"]
48
LIST_BANDWIDTH = ["Wide", "Narrow"]
49
LIST_COLOR = ["Off", "Blue", "Orange", "Purple"]
50
LIST_DTMFSPEED = ["%s ms" % x for x in range(50, 2010, 10)]
51
LIST_DTMFST = ["Off", "DT-ST", "ANI-ST", "DT+ANI"]
52
LIST_MODE = ["Channel", "Name", "Frequency"]
53
LIST_OFF1TO9 = ["Off"] + list("123456789")
54
LIST_OFF1TO10 = LIST_OFF1TO9 + ["10"]
55
LIST_OFFAB = ["Off"] + LIST_AB
56
LIST_RESUME = ["TO", "CO", "SE"]
57
LIST_PONMSG = ["Full", "Message"]
58
LIST_PTTID = ["Off", "BOT", "EOT", "Both"]
59
LIST_SCODE = ["%s" % x for x in range(1, 16)]
60
LIST_RPSTE = ["Off"] + ["%s" % x for x in range(1, 11)]
61
LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"]
62
LIST_SHIFTD = ["Off", "+", "-"]
63
LIST_STEDELAY = ["Off"] + ["%s ms" % x for x in range(100, 1100, 100)]
64
LIST_STEP = [str(x) for x in STEPS]
65
LIST_TIMEOUT = ["%s sec" % x for x in range(15, 615, 15)]
66
LIST_TXPOWER = ["High", "Mid", "Low"]
67
LIST_VOICE = ["Off", "English", "Chinese"]
68
LIST_WORKMODE = ["Frequency", "Channel"]
69

    
70
GMRS_FREQS1 = [462.5625, 462.5875, 462.6125, 462.6375, 462.6625,
71
               462.6875, 462.7125]
72
GMRS_FREQS2 = [467.5625, 467.5875, 467.6125, 467.6375, 467.6625,
73
               467.6875, 467.7125]
74
GMRS_FREQS3 = [462.5500, 462.5750, 462.6000, 462.6250, 462.6500,
75
               462.6750, 462.7000, 462.7250]
76
GMRS_FREQS = GMRS_FREQS1 + GMRS_FREQS2 + GMRS_FREQS3 * 2
77

    
78

    
79
def model_match(cls, data):
80
    """Match the opened/downloaded image to the correct version"""
81

    
82
    if len(data) > 0x2008:
83
        rid = data[0x2008:0x2010]
84
        return rid.startswith(cls.MODEL)
85
    elif len(data) == 0x2008:
86
        rid = data[0x1EF0:0x1EF7]
87
        return rid in cls._fileid
88
    else:
89
        return False
90

    
91
def _split(rf, f1, f2):
92
    """Returns False if the two freqs are in the same band (no split)
93
    or True otherwise"""
94

    
95
    # determine if the two freqs are in the same band
96
    for low, high in rf.valid_bands:
97
        if f1 >= low and f1 <= high and \
98
                f2 >= low and f2 <= high:
99
            # if the two freqs are on the same Band this is not a split
100
            return False
101

    
102
    # if you get here is because the freq pairs are split
103
    return True
104

    
105

    
106
class WP970I(baofeng_common.BaofengCommonHT):
107
    """Baofeng WP970I"""
108
    VENDOR = "Baofeng"
109
    MODEL = "WP970I"
110

    
111
    _tri_band = False
112
    _fileid = []
113
    _magic = [MSTRING_WP970I, ]
114
    _magic_response_length = 8
115
    _fw_ver_start = 0x1EF0
116
    _recv_block_size = 0x40
117
    _mem_size = 0x2000
118
    _ack_block = True
119

    
120
    _ranges = [(0x0000, 0x0DF0),
121
               (0x0E00, 0x1800),
122
               (0x1EE0, 0x1EF0),
123
               (0x1F60, 0x1F70),
124
               (0x1F80, 0x1F90),
125
               (0x1FC0, 0x1FD0)]
126
    _send_block_size = 0x10
127

    
128
    MODES = ["NFM", "FM"]
129
    VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
130
        "!@#$%^&*()+-=[]:\";'<>?,./"
131
    LENGTH_NAME = 6
132
    SKIP_VALUES = ["", "S"]
133
    DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
134
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
135
                    chirp_common.PowerLevel("Med",  watts=3.00),
136
                    chirp_common.PowerLevel("Low",  watts=1.00)]
137
    _vhf_range = (130000000, 180000000)
138
    _vhf2_range = (200000000, 260000000)
139
    _uhf_range = (400000000, 521000000)
140
    _gmrs = False
141
    VALID_BANDS = [_vhf_range,
142
                   _uhf_range]
143
    PTTID_LIST = LIST_PTTID
144
    SCODE_LIST = LIST_SCODE
145

    
146
    MEM_FORMAT = """
147
    #seekto 0x0000;
148
    struct {
149
      lbcd rxfreq[4];
150
      lbcd txfreq[4];
151
      ul16 rxtone;
152
      ul16 txtone;
153
      u8 unused1:3,
154
         isuhf:1,
155
         scode:4;
156
      u8 unknown1:7,
157
         txtoneicon:1;
158
      u8 mailicon:3,
159
         unknown2:3,
160
         lowpower:2;
161
      u8 unknown3:1,
162
         wide:1,
163
         unknown4:2,
164
         bcl:1,
165
         scan:1,
166
         pttid:2;
167
    } memory[128];
168

    
169
    #seekto 0x0B00;
170
    struct {
171
      u8 code[5];
172
      u8 unused[11];
173
    } pttid[15];
174

    
175
    #seekto 0x0CAA;
176
    struct {
177
      u8 code[5];
178
      u8 unused1:6,
179
         aniid:2;
180
      u8 unknown[2];
181
      u8 dtmfon;
182
      u8 dtmfoff;
183
    } ani;
184

    
185
    #seekto 0x0E20;
186
    struct {
187
      u8 squelch;
188
      u8 step;
189
      u8 unknown1;
190
      u8 save;
191
      u8 vox;
192
      u8 unknown2;
193
      u8 abr;
194
      u8 tdr;
195
      u8 beep;
196
      u8 timeout;
197
      u8 unknown3[4];
198
      u8 voice;
199
      u8 unknown4;
200
      u8 dtmfst;
201
      u8 unknown5;
202
      u8 unknown12:6,
203
         screv:2;
204
      u8 pttid;
205
      u8 pttlt;
206
      u8 mdfa;
207
      u8 mdfb;
208
      u8 bcl;
209
      u8 autolk;
210
      u8 sftd;
211
      u8 unknown6[3];
212
      u8 wtled;
213
      u8 rxled;
214
      u8 txled;
215
      u8 almod;
216
      u8 band;
217
      u8 tdrab;
218
      u8 ste;
219
      u8 rpste;
220
      u8 rptrl;
221
      u8 ponmsg;
222
      u8 roger;
223
      u8 rogerrx;
224
      u8 tdrch;
225
      u8 displayab:1,
226
         unknown1:2,
227
         fmradio:1,
228
         alarm:1,
229
         unknown2:1,
230
         reset:1,
231
         menu:1;
232
      u8 unknown1:6,
233
         singleptt:1,
234
         vfomrlock:1;
235
      u8 workmode;
236
      u8 keylock;
237
    } settings;
238

    
239
    #seekto 0x0E76;
240
    struct {
241
      u8 unused1:1,
242
         mrcha:7;
243
      u8 unused2:1,
244
         mrchb:7;
245
    } wmchannel;
246

    
247
    struct vfo {
248
      u8 unknown0[8];
249
      u8 freq[8];
250
      u8 offset[6];
251
      ul16 rxtone;
252
      ul16 txtone;
253
      u8 unused1:7,
254
         band:1;
255
      u8 unknown3;
256
      u8 unused2:2,
257
         sftd:2,
258
         scode:4;
259
      u8 unknown4;
260
      u8 unused3:1
261
         step:3,
262
         unused4:4;
263
      u8 unused5:1,
264
         widenarr:1,
265
         unused6:4,
266
         txpower3:2;
267
    };
268

    
269
    #seekto 0x0F00;
270
    struct {
271
      struct vfo a;
272
      struct vfo b;
273
    } vfo;
274

    
275
    #seekto 0x0F4E;
276
    u16 fm_presets;
277

    
278
    #seekto 0x1000;
279
    struct {
280
      char name[7];
281
      u8 unknown1[9];
282
    } names[128];
283

    
284
    #seekto 0x1ED0;
285
    struct {
286
      char line1[7];
287
      char line2[7];
288
    } sixpoweron_msg;
289

    
290
    #seekto 0x1EE0;
291
    struct {
292
      char line1[7];
293
      char line2[7];
294
    } poweron_msg;
295

    
296
    #seekto 0x1EF0;
297
    struct {
298
      char line1[7];
299
      char line2[7];
300
    } firmware_msg;
301

    
302
    struct squelch {
303
      u8 sql0;
304
      u8 sql1;
305
      u8 sql2;
306
      u8 sql3;
307
      u8 sql4;
308
      u8 sql5;
309
      u8 sql6;
310
      u8 sql7;
311
      u8 sql8;
312
      u8 sql9;
313
    };
314

    
315
    #seekto 0x1F60;
316
    struct {
317
      struct squelch vhf;
318
      u8 unknown1[6];
319
      u8 unknown2[16];
320
      struct squelch uhf;
321
    } squelch;
322

    
323
    struct limit {
324
      u8 enable;
325
      bbcd lower[2];
326
      bbcd upper[2];
327
    };
328

    
329
    #seekto 0x1FC0;
330
    struct {
331
      struct limit vhf;
332
      struct limit uhf;
333
      struct limit vhf2;
334
    } limits;
335

    
336
    """
337

    
338
    @classmethod
339
    def get_prompts(cls):
340
        rp = chirp_common.RadioPrompts()
341
        rp.experimental = \
342
            ('This driver is a beta version.\n'
343
             '\n'
344
             'Please save an unedited copy of your first successful\n'
345
             'download to a CHIRP Radio Images(*.img) file.'
346
             )
347
        rp.pre_download = _(dedent("""\
348
            Follow these instructions to download your info:
349

    
350
            1 - Turn off your radio
351
            2 - Connect your interface cable
352
            3 - Turn on your radio
353
            4 - Do the download of your radio data
354
            """))
355
        rp.pre_upload = _(dedent("""\
356
            Follow this instructions to upload your info:
357

    
358
            1 - Turn off your radio
359
            2 - Connect your interface cable
360
            3 - Turn on your radio
361
            4 - Do the upload of your radio data
362
            """))
363
        return rp
364

    
365
    def get_features(self):
366
        rf = baofeng_common.BaofengCommonHT.get_features(self)
367
        rf.valid_tuning_steps = STEPS
368
        return rf
369

    
370
    def process_mmap(self):
371
        """Process the mem map into the mem object"""
372
        self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap)
373

    
374
    def validate_memory(self, mem):
375
        msgs = baofeng_common.BaofengCommonHT.validate_memory(self, mem)
376

    
377
        _msg_duplex2 = 'Memory location only supports "(None)" or "off"'
378
        _msg_duplex3 = 'Memory location only supports "(None)", "+" or "off"'
379

    
380
        if self._gmrs:
381
            if mem.number < 1 or mem.number > 30:
382
                if float(mem.freq) / 1000000 in GMRS_FREQS1:
383
                    if mem.duplex not in ['', 'off']:
384
                        # warn user wrong Duplex
385
                        msgs.append(chirp_common.ValidationError(_msg_duplex2))
386

    
387
                if float(mem.freq) / 1000000 in GMRS_FREQS2:
388
                    if mem.duplex not in ['', 'off']:
389
                        # warn user wrong Duplex
390
                        msgs.append(chirp_common.ValidationError(_msg_duplex2))
391

    
392
                if float(mem.freq) / 1000000 in GMRS_FREQS3:
393
                    if mem.duplex not in ['', '+', 'off']:
394
                        # warn user wrong Duplex
395
                        msgs.append(chirp_common.ValidationError(_msg_duplex3))
396

    
397
        return msgs
398

    
399
    def set_memory(self, mem):
400
        _mem = self._memobj.memory[mem.number]
401
        _nam = self._memobj.names[mem.number]
402

    
403
        if mem.empty:
404
            _mem.set_raw("\xff" * 16)
405
            _nam.set_raw("\xff" * 16)
406
            return
407

    
408
        _mem.set_raw("\x00" * 16)
409

    
410
        if self._gmrs:
411
            if mem.number >= 1 and mem.number <= 30:
412
                GMRS_FREQ = int(GMRS_FREQS[mem.number - 1] * 1000000)
413
                mem.freq = GMRS_FREQ
414
                if mem.number <= 22:
415
                    mem.duplex = ''
416
                    mem.offset = 0
417
                    if mem.number >= 8 and mem.number <= 14:
418
                        mem.mode = "NFM"
419
                        mem.power = self.POWER_LEVELS[2]
420
                if mem.number > 22:
421
                    mem.duplex = '+'
422
                    mem.offset = 5000000
423
            elif float(mem.freq) / 1000000 in GMRS_FREQS:
424
                if float(mem.freq) / 1000000 in GMRS_FREQS2:
425
                    mem.offset = 0
426
                    mem.mode = "NFM"
427
                    mem.power = self.POWER_LEVELS[2]
428
                if float(mem.freq) / 1000000 in GMRS_FREQS3:
429
                    if mem.duplex == '+':
430
                        mem.offset = 5000000
431
                    else:
432
                        mem.offset = 0
433
            else:
434
                mem.duplex = 'off'
435
                mem.offset = 0
436

    
437
        _mem.rxfreq = mem.freq / 10
438

    
439
        if mem.duplex == "off":
440
            for i in range(0, 4):
441
                _mem.txfreq[i].set_raw("\xFF")
442
        elif mem.duplex == "split":
443
            _mem.txfreq = mem.offset / 10
444
        elif mem.duplex == "+":
445
            _mem.txfreq = (mem.freq + mem.offset) / 10
446
        elif mem.duplex == "-":
447
            _mem.txfreq = (mem.freq - mem.offset) / 10
448
        else:
449
            _mem.txfreq = mem.freq / 10
450

    
451
        _namelength = self.get_features().valid_name_length
452
        for i in range(_namelength):
453
            try:
454
                _nam.name[i] = mem.name[i]
455
            except IndexError:
456
                _nam.name[i] = "\xFF"
457

    
458
        rxmode = txmode = ""
459
        if mem.tmode == "Tone":
460
            _mem.txtone = int(mem.rtone * 10)
461
            _mem.rxtone = 0
462
        elif mem.tmode == "TSQL":
463
            _mem.txtone = int(mem.ctone * 10)
464
            _mem.rxtone = int(mem.ctone * 10)
465
        elif mem.tmode == "DTCS":
466
            rxmode = txmode = "DTCS"
467
            _mem.txtone = self.DTCS_CODES.index(mem.dtcs) + 1
468
            _mem.rxtone = self.DTCS_CODES.index(mem.dtcs) + 1
469
        elif mem.tmode == "Cross":
470
            txmode, rxmode = mem.cross_mode.split("->", 1)
471
            if txmode == "Tone":
472
                _mem.txtone = int(mem.rtone * 10)
473
            elif txmode == "DTCS":
474
                _mem.txtone = self.DTCS_CODES.index(mem.dtcs) + 1
475
            else:
476
                _mem.txtone = 0
477
            if rxmode == "Tone":
478
                _mem.rxtone = int(mem.ctone * 10)
479
            elif rxmode == "DTCS":
480
                _mem.rxtone = self.DTCS_CODES.index(mem.rx_dtcs) + 1
481
            else:
482
                _mem.rxtone = 0
483
        else:
484
            _mem.rxtone = 0
485
            _mem.txtone = 0
486

    
487
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
488
            _mem.txtone += 0x69
489
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
490
            _mem.rxtone += 0x69
491

    
492
        _mem.scan = mem.skip != "S"
493
        _mem.wide = mem.mode == "FM"
494

    
495
        if mem.power:
496
            _mem.lowpower = self.POWER_LEVELS.index(mem.power)
497
        else:
498
            _mem.lowpower = 0
499

    
500
        # extra settings
501
        if len(mem.extra) > 0:
502
            # there are setting, parse
503
            for setting in mem.extra:
504
                setattr(_mem, setting.get_name(), setting.value)
505
        else:
506
            # there are no extra settings, load defaults
507
            _mem.bcl = 0
508
            _mem.pttid = 0
509
            _mem.scode = 0
510

    
511
    def get_settings(self):
512
        """Translate the bit in the mem_struct into settings in the UI"""
513
        _mem = self._memobj
514
        basic = RadioSettingGroup("basic", "Basic Settings")
515
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
516
        other = RadioSettingGroup("other", "Other Settings")
517
        work = RadioSettingGroup("work", "Work Mode Settings")
518
        fm_preset = RadioSettingGroup("fm_preset", "FM Preset")
519
        dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings")
520
        service = RadioSettingGroup("service", "Service Settings")
521
        top = RadioSettings(basic, advanced, other, work, fm_preset, dtmfe,
522
                            service)
523

    
524
        # Basic settings
525
        if _mem.settings.squelch > 0x09:
526
            val = 0x00
527
        else:
528
            val = _mem.settings.squelch
529
        rs = RadioSetting("settings.squelch", "Squelch",
530
                          RadioSettingValueList(
531
                              LIST_OFF1TO9, LIST_OFF1TO9[val]))
532
        basic.append(rs)
533

    
534
        if _mem.settings.save > 0x04:
535
            val = 0x00
536
        else:
537
            val = _mem.settings.save
538
        rs = RadioSetting("settings.save", "Battery Saver",
539
                          RadioSettingValueList(
540
                              LIST_SAVE, LIST_SAVE[val]))
541
        basic.append(rs)
542

    
543
        if _mem.settings.vox > 0x0A:
544
            val = 0x00
545
        else:
546
            val = _mem.settings.vox
547
        rs = RadioSetting("settings.vox", "Vox",
548
                          RadioSettingValueList(
549
                              LIST_OFF1TO10, LIST_OFF1TO10[val]))
550
        basic.append(rs)
551

    
552
        if _mem.settings.abr > 0x0A:
553
            val = 0x00
554
        else:
555
            val = _mem.settings.abr
556
        rs = RadioSetting("settings.abr", "Backlight Timeout",
557
                          RadioSettingValueList(
558
                              LIST_OFF1TO10, LIST_OFF1TO10[val]))
559
        basic.append(rs)
560

    
561
        rs = RadioSetting("settings.tdr", "Dual Watch",
562
                          RadioSettingValueBoolean(_mem.settings.tdr))
563
        basic.append(rs)
564

    
565
        rs = RadioSetting("settings.beep", "Beep",
566
                          RadioSettingValueBoolean(_mem.settings.beep))
567
        basic.append(rs)
568

    
569
        if _mem.settings.timeout > 0x27:
570
            val = 0x03
571
        else:
572
            val = _mem.settings.timeout
573
        rs = RadioSetting("settings.timeout", "Timeout Timer",
574
                          RadioSettingValueList(
575
                              LIST_TIMEOUT, LIST_TIMEOUT[val]))
576
        basic.append(rs)
577

    
578
        if _mem.settings.voice > 0x02:
579
            val = 0x01
580
        else:
581
            val = _mem.settings.voice
582
        rs = RadioSetting("settings.voice", "Voice Prompt",
583
                          RadioSettingValueList(
584
                              LIST_VOICE, LIST_VOICE[val]))
585
        basic.append(rs)
586

    
587
        rs = RadioSetting("settings.dtmfst", "DTMF Sidetone",
588
                          RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
589
                              _mem.settings.dtmfst]))
590
        basic.append(rs)
591

    
592
        if _mem.settings.screv > 0x02:
593
            val = 0x01
594
        else:
595
            val = _mem.settings.screv
596
        rs = RadioSetting("settings.screv", "Scan Resume",
597
                          RadioSettingValueList(
598
                              LIST_RESUME, LIST_RESUME[val]))
599
        basic.append(rs)
600

    
601
        rs = RadioSetting("settings.pttid", "When to send PTT ID",
602
                          RadioSettingValueList(LIST_PTTID, LIST_PTTID[
603
                              _mem.settings.pttid]))
604
        basic.append(rs)
605

    
606
        if _mem.settings.pttlt > 0x1E:
607
            val = 0x05
608
        else:
609
            val = _mem.settings.pttlt
610
        rs = RadioSetting("pttlt", "PTT ID Delay",
611
                          RadioSettingValueInteger(0, 50, val))
612
        basic.append(rs)
613

    
614
        rs = RadioSetting("settings.mdfa", "Display Mode (A)",
615
                          RadioSettingValueList(LIST_MODE, LIST_MODE[
616
                              _mem.settings.mdfa]))
617
        basic.append(rs)
618

    
619
        rs = RadioSetting("settings.mdfb", "Display Mode (B)",
620
                          RadioSettingValueList(LIST_MODE, LIST_MODE[
621
                              _mem.settings.mdfb]))
622
        basic.append(rs)
623

    
624
        rs = RadioSetting("settings.autolk", "Automatic Key Lock",
625
                          RadioSettingValueBoolean(_mem.settings.autolk))
626
        basic.append(rs)
627

    
628
        rs = RadioSetting("settings.wtled", "Standby LED Color",
629
                          RadioSettingValueList(
630
                              LIST_COLOR, LIST_COLOR[_mem.settings.wtled]))
631
        basic.append(rs)
632

    
633
        rs = RadioSetting("settings.rxled", "RX LED Color",
634
                          RadioSettingValueList(
635
                              LIST_COLOR, LIST_COLOR[_mem.settings.rxled]))
636
        basic.append(rs)
637

    
638
        rs = RadioSetting("settings.txled", "TX LED Color",
639
                          RadioSettingValueList(
640
                              LIST_COLOR, LIST_COLOR[_mem.settings.txled]))
641
        basic.append(rs)
642

    
643
        val = _mem.settings.almod
644
        rs = RadioSetting("settings.almod", "Alarm Mode",
645
                          RadioSettingValueList(
646
                              LIST_ALMOD, LIST_ALMOD[val]))
647
        basic.append(rs)
648

    
649
        if _mem.settings.tdrab > 0x02:
650
            val = 0x00
651
        else:
652
            val = _mem.settings.tdrab
653
        rs = RadioSetting("settings.tdrab", "Dual Watch TX Priority",
654
                          RadioSettingValueList(
655
                              LIST_OFFAB, LIST_OFFAB[val]))
656
        basic.append(rs)
657

    
658
        rs = RadioSetting("settings.ste", "Squelch Tail Eliminate (HT to HT)",
659
                          RadioSettingValueBoolean(_mem.settings.ste))
660
        basic.append(rs)
661

    
662
        if _mem.settings.rpste > 0x0A:
663
            val = 0x00
664
        else:
665
            val = _mem.settings.rpste
666
        rs = RadioSetting("settings.rpste",
667
                          "Squelch Tail Eliminate (repeater)",
668
                          RadioSettingValueList(
669
                              LIST_RPSTE, LIST_RPSTE[val]))
670
        basic.append(rs)
671

    
672
        if _mem.settings.rptrl > 0x0A:
673
            val = 0x00
674
        else:
675
            val = _mem.settings.rptrl
676
        rs = RadioSetting("settings.rptrl", "STE Repeater Delay",
677
                          RadioSettingValueList(
678
                              LIST_STEDELAY, LIST_STEDELAY[val]))
679
        basic.append(rs)
680

    
681
        rs = RadioSetting("settings.ponmsg", "Power-On Message",
682
                          RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
683
                              _mem.settings.ponmsg]))
684
        basic.append(rs)
685

    
686
        rs = RadioSetting("settings.roger", "Roger Beep",
687
                          RadioSettingValueBoolean(_mem.settings.roger))
688
        basic.append(rs)
689

    
690
        # Advanced settings
691
        rs = RadioSetting("settings.reset", "RESET Menu",
692
                          RadioSettingValueBoolean(_mem.settings.reset))
693
        advanced.append(rs)
694

    
695
        rs = RadioSetting("settings.menu", "All Menus",
696
                          RadioSettingValueBoolean(_mem.settings.menu))
697
        advanced.append(rs)
698

    
699
        rs = RadioSetting("settings.fmradio", "Broadcast FM Radio",
700
                          RadioSettingValueBoolean(_mem.settings.fmradio))
701
        advanced.append(rs)
702

    
703
        rs = RadioSetting("settings.alarm", "Alarm Sound",
704
                          RadioSettingValueBoolean(_mem.settings.alarm))
705
        advanced.append(rs)
706

    
707
        # Other settings
708
        def _filter(name):
709
            filtered = ""
710
            for char in str(name):
711
                if char in chirp_common.CHARSET_ASCII:
712
                    filtered += char
713
                else:
714
                    filtered += " "
715
            return filtered
716

    
717
        _msg = _mem.firmware_msg
718
        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
719
        val.set_mutable(False)
720
        rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
721
        other.append(rs)
722

    
723
        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
724
        val.set_mutable(False)
725
        rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
726
        other.append(rs)
727

    
728
        _msg = _mem.sixpoweron_msg
729
        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
730
        val.set_mutable(False)
731
        rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", val)
732
        other.append(rs)
733
        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
734
        val.set_mutable(False)
735
        rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", val)
736
        other.append(rs)
737

    
738
        _msg = _mem.poweron_msg
739
        rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
740
                          RadioSettingValueString(
741
                              0, 7, _filter(_msg.line1)))
742
        other.append(rs)
743
        rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
744
                          RadioSettingValueString(
745
                              0, 7, _filter(_msg.line2)))
746
        other.append(rs)
747

    
748
        lower = 130
749
        upper = 179
750
        rs = RadioSetting("limits.vhf.lower", "VHF Lower Limit (MHz)",
751
                          RadioSettingValueInteger(
752
                              lower, upper, _mem.limits.vhf.lower))
753
        other.append(rs)
754

    
755
        rs = RadioSetting("limits.vhf.upper", "VHF Upper Limit (MHz)",
756
                          RadioSettingValueInteger(
757
                              lower, upper, _mem.limits.vhf.upper))
758
        other.append(rs)
759

    
760
        if self._tri_band:
761
            lower = 200
762
            upper = 260
763
            rs = RadioSetting("limits.vhf2.lower", "VHF2 Lower Limit (MHz)",
764
                              RadioSettingValueInteger(
765
                                  lower, upper, _mem.limits.vhf2.lower))
766
            other.append(rs)
767

    
768
            rs = RadioSetting("limits.vhf2.upper", "VHF2 Upper Limit (MHz)",
769
                              RadioSettingValueInteger(
770
                                  lower, upper, _mem.limits.vhf2.upper))
771
            other.append(rs)
772

    
773
        lower = 400
774
        upper = 520
775
        rs = RadioSetting("limits.uhf.lower", "UHF Lower Limit (MHz)",
776
                          RadioSettingValueInteger(
777
                              lower, upper, _mem.limits.uhf.lower))
778
        other.append(rs)
779

    
780
        rs = RadioSetting("limits.uhf.upper", "UHF Upper Limit (MHz)",
781
                          RadioSettingValueInteger(
782
                              lower, upper, _mem.limits.uhf.upper))
783
        other.append(rs)
784

    
785
        # Work mode settings
786
        rs = RadioSetting("settings.displayab", "Display",
787
                          RadioSettingValueList(
788
                              LIST_AB, LIST_AB[_mem.settings.displayab]))
789
        work.append(rs)
790

    
791
        rs = RadioSetting("settings.workmode", "VFO/MR Mode",
792
                          RadioSettingValueList(
793
                              LIST_WORKMODE,
794
                              LIST_WORKMODE[_mem.settings.workmode]))
795
        work.append(rs)
796

    
797
        rs = RadioSetting("settings.keylock", "Keypad Lock",
798
                          RadioSettingValueBoolean(_mem.settings.keylock))
799
        work.append(rs)
800

    
801
        rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
802
                          RadioSettingValueInteger(0, 127,
803
                                                   _mem.wmchannel.mrcha))
804
        work.append(rs)
805

    
806
        rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
807
                          RadioSettingValueInteger(0, 127,
808
                                                   _mem.wmchannel.mrchb))
809
        work.append(rs)
810

    
811
        def convert_bytes_to_freq(bytes):
812
            real_freq = 0
813
            for byte in bytes:
814
                real_freq = (real_freq * 10) + byte
815
            return chirp_common.format_freq(real_freq * 10)
816

    
817
        def my_validate(value):
818
            value = chirp_common.parse_freq(value)
819
            msg = ("Can't be less than %i.0000")
820
            if value > 99000000 and value < 130 * 1000000:
821
                raise InvalidValueError(msg % (130))
822
            msg = ("Can't be between %i.9975-%i.0000")
823
            if (179 + 1) * 1000000 <= value and value < 400 * 1000000:
824
                raise InvalidValueError(msg % (179, 400))
825
            msg = ("Can't be greater than %i.9975")
826
            if value > 99000000 and value > (520 + 1) * 1000000:
827
                raise InvalidValueError(msg % (520))
828
            return chirp_common.format_freq(value)
829

    
830
        def apply_freq(setting, obj):
831
            value = chirp_common.parse_freq(str(setting.value)) / 10
832
            for i in range(7, -1, -1):
833
                obj.freq[i] = value % 10
834
                value /= 10
835

    
836
        val1a = RadioSettingValueString(0, 10,
837
                                        convert_bytes_to_freq(_mem.vfo.a.freq))
838
        val1a.set_validate_callback(my_validate)
839
        rs = RadioSetting("vfo.a.freq", "VFO A Frequency", val1a)
840
        rs.set_apply_callback(apply_freq, _mem.vfo.a)
841
        work.append(rs)
842

    
843
        val1b = RadioSettingValueString(0, 10,
844
                                        convert_bytes_to_freq(_mem.vfo.b.freq))
845
        val1b.set_validate_callback(my_validate)
846
        rs = RadioSetting("vfo.b.freq", "VFO B Frequency", val1b)
847
        rs.set_apply_callback(apply_freq, _mem.vfo.b)
848
        work.append(rs)
849

    
850
        rs = RadioSetting("vfo.a.sftd", "VFO A Shift",
851
                          RadioSettingValueList(
852
                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.a.sftd]))
853
        work.append(rs)
854

    
855
        rs = RadioSetting("vfo.b.sftd", "VFO B Shift",
856
                          RadioSettingValueList(
857
                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.b.sftd]))
858
        work.append(rs)
859

    
860
        def convert_bytes_to_offset(bytes):
861
            real_offset = 0
862
            for byte in bytes:
863
                real_offset = (real_offset * 10) + byte
864
            return chirp_common.format_freq(real_offset * 1000)
865

    
866
        def apply_offset(setting, obj):
867
            value = chirp_common.parse_freq(str(setting.value)) / 1000
868
            for i in range(5, -1, -1):
869
                obj.offset[i] = value % 10
870
                value /= 10
871

    
872
        val1a = RadioSettingValueString(
873
                    0, 10, convert_bytes_to_offset(_mem.vfo.a.offset))
874
        rs = RadioSetting("vfo.a.offset",
875
                          "VFO A Offset", val1a)
876
        rs.set_apply_callback(apply_offset, _mem.vfo.a)
877
        work.append(rs)
878

    
879
        val1b = RadioSettingValueString(
880
                    0, 10, convert_bytes_to_offset(_mem.vfo.b.offset))
881
        rs = RadioSetting("vfo.b.offset",
882
                          "VFO B Offset", val1b)
883
        rs.set_apply_callback(apply_offset, _mem.vfo.b)
884
        work.append(rs)
885

    
886
        rs = RadioSetting("vfo.a.txpower3", "VFO A Power",
887
                          RadioSettingValueList(
888
                              LIST_TXPOWER,
889
                              LIST_TXPOWER[_mem.vfo.a.txpower3]))
890
        work.append(rs)
891

    
892
        rs = RadioSetting("vfo.b.txpower3", "VFO B Power",
893
                          RadioSettingValueList(
894
                              LIST_TXPOWER,
895
                              LIST_TXPOWER[_mem.vfo.b.txpower3]))
896
        work.append(rs)
897

    
898
        rs = RadioSetting("vfo.a.widenarr", "VFO A Bandwidth",
899
                          RadioSettingValueList(
900
                              LIST_BANDWIDTH,
901
                              LIST_BANDWIDTH[_mem.vfo.a.widenarr]))
902
        work.append(rs)
903

    
904
        rs = RadioSetting("vfo.b.widenarr", "VFO B Bandwidth",
905
                          RadioSettingValueList(
906
                              LIST_BANDWIDTH,
907
                              LIST_BANDWIDTH[_mem.vfo.b.widenarr]))
908
        work.append(rs)
909

    
910
        rs = RadioSetting("vfo.a.scode", "VFO A S-CODE",
911
                          RadioSettingValueList(
912
                              LIST_SCODE,
913
                              LIST_SCODE[_mem.vfo.a.scode]))
914
        work.append(rs)
915

    
916
        rs = RadioSetting("vfo.b.scode", "VFO B S-CODE",
917
                          RadioSettingValueList(
918
                              LIST_SCODE,
919
                              LIST_SCODE[_mem.vfo.b.scode]))
920
        work.append(rs)
921

    
922
        rs = RadioSetting("vfo.a.step", "VFO A Tuning Step",
923
                          RadioSettingValueList(
924
                              LIST_STEP, LIST_STEP[_mem.vfo.a.step]))
925
        work.append(rs)
926
        rs = RadioSetting("vfo.b.step", "VFO B Tuning Step",
927
                          RadioSettingValueList(
928
                              LIST_STEP, LIST_STEP[_mem.vfo.b.step]))
929
        work.append(rs)
930

    
931
        # broadcast FM settings
932
        _fm_presets = self._memobj.fm_presets
933
        if _fm_presets <= 108.0 * 10 - 650:
934
            preset = _fm_presets / 10.0 + 65
935
        elif _fm_presets >= 65.0 * 10 and _fm_presets <= 108.0 * 10:
936
            preset = _fm_presets / 10.0
937
        else:
938
            preset = 76.0
939
        rs = RadioSetting("fm_presets", "FM Preset(MHz)",
940
                          RadioSettingValueFloat(65, 108.0, preset, 0.1, 1))
941
        fm_preset.append(rs)
942

    
943
        # DTMF settings
944
        def apply_code(setting, obj, length):
945
            code = []
946
            for j in range(0, length):
947
                try:
948
                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
949
                except IndexError:
950
                    code.append(0xFF)
951
            obj.code = code
952

    
953
        for i in range(0, 15):
954
            _codeobj = self._memobj.pttid[i].code
955
            _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
956
            val = RadioSettingValueString(0, 5, _code, False)
957
            val.set_charset(DTMF_CHARS)
958
            pttid = RadioSetting("pttid/%i.code" % i,
959
                                 "Signal Code %i" % (i + 1), val)
960
            pttid.set_apply_callback(apply_code, self._memobj.pttid[i], 5)
961
            dtmfe.append(pttid)
962

    
963
        if _mem.ani.dtmfon > 0xC3:
964
            val = 0x03
965
        else:
966
            val = _mem.ani.dtmfon
967
        rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
968
                          RadioSettingValueList(LIST_DTMFSPEED,
969
                                                LIST_DTMFSPEED[val]))
970
        dtmfe.append(rs)
971

    
972
        if _mem.ani.dtmfoff > 0xC3:
973
            val = 0x03
974
        else:
975
            val = _mem.ani.dtmfoff
976
        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
977
                          RadioSettingValueList(LIST_DTMFSPEED,
978
                                                LIST_DTMFSPEED[val]))
979
        dtmfe.append(rs)
980

    
981
        _codeobj = self._memobj.ani.code
982
        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
983
        val = RadioSettingValueString(0, 5, _code, False)
984
        val.set_charset(DTMF_CHARS)
985
        rs = RadioSetting("ani.code", "ANI Code", val)
986
        rs.set_apply_callback(apply_code, self._memobj.ani, 5)
987
        dtmfe.append(rs)
988

    
989
        rs = RadioSetting("ani.aniid", "When to send ANI ID",
990
                          RadioSettingValueList(LIST_PTTID,
991
                                                LIST_PTTID[_mem.ani.aniid]))
992
        dtmfe.append(rs)
993

    
994
        # Service settings
995
        for band in ["vhf", "uhf"]:
996
            for index in range(0, 10):
997
                key = "squelch.%s.sql%i" % (band, index)
998
                if band == "vhf":
999
                    _obj = self._memobj.squelch.vhf
1000
                elif band == "uhf":
1001
                    _obj = self._memobj.squelch.uhf
1002
                val = RadioSettingValueInteger(0, 123,
1003
                                               getattr(
1004
                                                   _obj, "sql%i" % (index)))
1005
                if index == 0:
1006
                    val.set_mutable(False)
1007
                name = "%s Squelch %i" % (band.upper(), index)
1008
                rs = RadioSetting(key, name, val)
1009
                service.append(rs)
1010

    
1011
        return top
1012

    
1013
    @classmethod
1014
    def match_model(cls, filedata, filename):
1015
        match_size = False
1016
        match_model = False
1017

    
1018
        # testing the file data size
1019
        if len(filedata) in [0x2008, 0x2010]:
1020
            match_size = True
1021

    
1022
        # testing the firmware model fingerprint
1023
        match_model = model_match(cls, filedata)
1024

    
1025
        if match_size and match_model:
1026
            return True
1027
        else:
1028
            return False
1029

    
1030

    
1031
class RH5XAlias(chirp_common.Alias):
1032
    VENDOR = "Rugged"
1033
    MODEL = "RH5X"
1034

    
1035

    
1036
class UV82IIIAlias(chirp_common.Alias):
1037
    VENDOR = "Baofeng"
1038
    MODEL = "UV-82III"
1039

    
1040

    
1041
@directory.register
1042
class BFA58(WP970I):
1043
    """Baofeng BF-A58"""
1044
    VENDOR = "Baofeng"
1045
    MODEL = "BF-A58"
1046
    ALIASES = [RH5XAlias]
1047

    
1048
    _fileid = ["BFT515 ", "BFT517 "]
1049

    
1050

    
1051
@directory.register
1052
class UV82WP(WP970I):
1053
    """Baofeng UV82-WP"""
1054
    VENDOR = "Baofeng"
1055
    MODEL = "UV-82WP"
1056

    
1057

    
1058
@directory.register
1059
class GT3WP(WP970I):
1060
    """Baofeng GT-3WP"""
1061
    VENDOR = "Baofeng"
1062
    MODEL = "GT-3WP"
1063
    LENGTH_NAME = 7
1064

    
1065

    
1066
@directory.register
1067
class RT6(WP970I):
1068
    """Retevis RT6"""
1069
    VENDOR = "Retevis"
1070
    MODEL = "RT6"
1071

    
1072

    
1073
@directory.register
1074
class BFA58S(WP970I):
1075
    VENDOR = "Baofeng"
1076
    MODEL = "BF-A58S"
1077
    LENGTH_NAME = 7
1078
    ALIASES = [UV82IIIAlias]
1079
    _tri_band = True
1080

    
1081
    def get_features(self):
1082
        rf = WP970I.get_features(self)
1083
        rf.valid_bands = [self._vhf_range,
1084
                          self._vhf2_range,
1085
                          self._uhf_range]
1086
        return rf
1087

    
1088

    
1089
@directory.register
1090
class UV9R(WP970I):
1091
    """Baofeng UV-9R"""
1092
    VENDOR = "Baofeng"
1093
    MODEL = "UV-9R"
1094
    LENGTH_NAME = 7
1095

    
1096

    
1097
@directory.register
1098
class UV9G(WP970I):
1099
    """Baofeng UV-9G"""
1100
    VENDOR = "Baofeng"
1101
    MODEL = "UV-9G"
1102
    LENGTH_NAME = 7
1103

    
1104
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
1105
                    chirp_common.PowerLevel("Med",  watts=0.50),
1106
                    chirp_common.PowerLevel("Low",  watts=0.50)]
1107
    _magic = [MSTRING_UV9G, ]
1108
    _gmrs = True
1109

    
1110
    
1111
    @classmethod
1112
    def match_model(cls, filedata, filename):
1113
        # This radio has always been post-metadata, so never do
1114
        # old-school detection
1115
        return False
1116