Project

General

Profile

Bug #10978 ยป baofeng_wp970i.py

fix issue where a couple of settings aren't loaded. - Albert Linga, 11/30/2023 11:09 AM

 
1
# Copyright 2016:
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 logging
18

    
19
from chirp.drivers import baofeng_common
20
from chirp import chirp_common, directory
21
from chirp import bitwise
22
from chirp.settings import RadioSettingGroup, RadioSetting, \
23
    RadioSettingValueBoolean, RadioSettingValueList, \
24
    RadioSettingValueString, RadioSettingValueInteger, \
25
    RadioSettingValueFloat, RadioSettings, \
26
    InvalidValueError
27

    
28
LOG = logging.getLogger(__name__)
29

    
30
# #### MAGICS #########################################################
31

    
32
# Baofeng WP970I magic string
33
MSTRING_WP970I = b"\x50\xBB\xFF\x20\x14\x04\x13"
34

    
35
# Baofeng UV-9G magic string
36
MSTRING_UV9G = b"\x50\xBB\xFF\x20\x12\x05\x25"
37

    
38
# Baofeng UV-S9X3 magic string
39
MSTRING_UVS9X3 = b"\x50\xBB\xFF\x20\x12\x07\x25"
40

    
41

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

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

    
69
TXP_CHOICES = ["High", "Low"]
70
TXP_VALUES = [0x00, 0x02]
71

    
72

    
73
def model_match(cls, data):
74
    """Match the opened/downloaded image to the correct version"""
75

    
76
    if len(data) > 0x2008:
77
        rid = data[0x2008:0x2010]
78
        return rid.startswith(cls.MODEL.encode())
79
    elif len(data) == 0x2008:
80
        rid = data[0x1EF0:0x1EF7]
81
        return rid in cls._fileid
82
    else:
83
        return False
84

    
85

    
86
class WP970I(baofeng_common.BaofengCommonHT):
87
    """Baofeng WP970I"""
88
    VENDOR = "Baofeng"
89
    MODEL = "WP970I"
90
    NEEDS_COMPAT_SERIAL = False
91

    
92
    _tri_band = False
93
    _fileid = []
94
    _magic = [MSTRING_WP970I, ]
95
    _magic_response_length = 8
96
    _fw_ver_start = 0x1EF0
97
    _recv_block_size = 0x40
98
    _mem_size = 0x2000
99
    _ack_block = True
100

    
101
    _ranges = [(0x0000, 0x0DF0),
102
               (0x0E00, 0x1800),
103
               (0x1EE0, 0x1EF0),
104
               (0x1F60, 0x1F70),
105
               (0x1F80, 0x1F90),
106
               (0x1FC0, 0x1FD0)]
107
    _send_block_size = 0x10
108

    
109
    MODES = ["NFM", "FM"]
110
    VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
111
        "!@#$%^&*()+-=[]:\";'<>?,./"
112
    LENGTH_NAME = 6
113
    SKIP_VALUES = ["", "S"]
114
    DTCS_CODES = tuple(sorted(chirp_common.DTCS_CODES + (645,)))
115
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
116
                    chirp_common.PowerLevel("Med",  watts=3.00),
117
                    chirp_common.PowerLevel("Low",  watts=1.00)]
118
    _vhf_range = (130000000, 180000000)
119
    _vhf2_range = (200000000, 260000000)
120
    _uhf_range = (400000000, 521000000)
121
    VALID_BANDS = [_vhf_range,
122
                   _uhf_range]
123
    PTTID_LIST = LIST_PTTID
124
    SCODE_LIST = LIST_SCODE
125

    
126
    MEM_FORMAT = """
127
    #seekto 0x0000;
128
    struct {
129
      lbcd rxfreq[4];
130
      lbcd txfreq[4];
131
      ul16 rxtone;
132
      ul16 txtone;
133
      u8 unused1:3,
134
         isuhf:1,
135
         scode:4;
136
      u8 unknown1:7,
137
         txtoneicon:1;
138
      u8 mailicon:3,
139
         unknown2:3,
140
         lowpower:2;
141
      u8 unknown3:1,
142
         wide:1,
143
         unknown4:2,
144
         bcl:1,
145
         scan:1,
146
         pttid:2;
147
    } memory[128];
148

    
149
    #seekto 0x0B00;
150
    struct {
151
      u8 code[5];
152
      u8 unused[11];
153
    } pttid[15];
154

    
155
    #seekto 0x0CAA;
156
    struct {
157
      u8 code[5];
158
      u8 unused1:6,
159
         aniid:2;
160
      u8 unknown[2];
161
      u8 dtmfon;
162
      u8 dtmfoff;
163
    } ani;
164

    
165
    #seekto 0x0E20;
166
    struct {
167
      u8 squelch;
168
      u8 step;
169
      u8 unknown1;
170
      u8 save;
171
      u8 vox;
172
      u8 unknown2;
173
      u8 abr;
174
      u8 tdr;
175
      u8 beep;
176
      u8 timeout;
177
      u8 unknown3[4];
178
      u8 voice;
179
      u8 unknown4;
180
      u8 dtmfst;
181
      u8 unknown5;
182
      u8 unknown12:6,
183
         screv:2;
184
      u8 pttid;
185
      u8 pttlt;
186
      u8 mdfa;
187
      u8 mdfb;
188
      u8 bcl;
189
      u8 autolk;
190
      u8 sftd;
191
      u8 unknown6[3];
192
      u8 wtled;
193
      u8 rxled;
194
      u8 txled;
195
      u8 almod;
196
      u8 band;
197
      u8 tdrab;
198
      u8 ste;
199
      u8 rpste;
200
      u8 rptrl;
201
      u8 ponmsg;
202
      u8 roger;
203
      u8 rogerrx;
204
      u8 tdrch;
205
      u8 displayab:1,
206
         unknown1:2,
207
         fmradio:1,
208
         alarm:1,
209
         unknown2:1,
210
         reset:1,
211
         menu:1;
212
      u8 unknown1:6,
213
         singleptt:1,
214
         vfomrlock:1;
215
      u8 workmode;
216
      u8 keylock;
217
    } settings;
218

    
219
    #seekto 0x0E76;
220
    struct {
221
      u8 unused1:1,
222
         mrcha:7;
223
      u8 unused2:1,
224
         mrchb:7;
225
    } wmchannel;
226

    
227
    struct vfo {
228
      u8 unknown0[8];
229
      u8 freq[8];
230
      u8 offset[6];
231
      ul16 rxtone;
232
      ul16 txtone;
233
      u8 unused1:7,
234
         band:1;
235
      u8 unknown3;
236
      u8 unused2:2,
237
         sftd:2,
238
         scode:4;
239
      u8 unknown4;
240
      u8 unused3:1,
241
         step:3,
242
         unused4:4;
243
      u8 unused5:1,
244
         widenarr:1,
245
         unused6:4,
246
         txpower3:2;
247
    };
248

    
249
    #seekto 0x0F00;
250
    struct {
251
      struct vfo a;
252
      struct vfo b;
253
    } vfo;
254

    
255
    #seekto 0x0F4E;
256
    u16 fm_presets;
257

    
258
    #seekto 0x1000;
259
    struct {
260
      char name[7];
261
      u8 unknown1[9];
262
    } names[128];
263

    
264
    #seekto 0x1ED0;
265
    struct {
266
      char line1[7];
267
      char line2[7];
268
    } sixpoweron_msg;
269

    
270
    #seekto 0x1EE0;
271
    struct {
272
      char line1[7];
273
      char line2[7];
274
    } poweron_msg;
275

    
276
    #seekto 0x1EF0;
277
    struct {
278
      char line1[7];
279
      char line2[7];
280
    } firmware_msg;
281

    
282
    struct squelch {
283
      u8 sql0;
284
      u8 sql1;
285
      u8 sql2;
286
      u8 sql3;
287
      u8 sql4;
288
      u8 sql5;
289
      u8 sql6;
290
      u8 sql7;
291
      u8 sql8;
292
      u8 sql9;
293
    };
294

    
295
    #seekto 0x1F60;
296
    struct {
297
      struct squelch vhf;
298
      u8 unknown1[6];
299
      u8 unknown2[16];
300
      struct squelch uhf;
301
    } squelch;
302

    
303
    struct limit {
304
      u8 enable;
305
      bbcd lower[2];
306
      bbcd upper[2];
307
    };
308

    
309
    #seekto 0x1FC0;
310
    struct {
311
      struct limit vhf;
312
      struct limit uhf;
313
      struct limit vhf2;
314
    } limits;
315

    
316
    """
317

    
318
    @classmethod
319
    def get_prompts(cls):
320
        rp = chirp_common.RadioPrompts()
321
        rp.experimental = \
322
            ('This driver is a beta version.\n'
323
             '\n'
324
             'Please save an unedited copy of your first successful\n'
325
             'download to a CHIRP Radio Images(*.img) file.'
326
             )
327
        rp.pre_download = _(
328
            "Follow these instructions to download your info:\n"
329
            "1 - Turn off your radio\n"
330
            "2 - Connect your interface cable\n"
331
            "3 - Turn on your radio\n"
332
            "4 - Do the download of your radio data\n")
333
        rp.pre_upload = _(
334
            "Follow this instructions to upload your info:\n"
335
            "1 - Turn off your radio\n"
336
            "2 - Connect your interface cable\n"
337
            "3 - Turn on your radio\n"
338
            "4 - Do the upload of your radio data\n")
339
        return rp
340

    
341
    def get_features(self):
342
        rf = baofeng_common.BaofengCommonHT.get_features(self)
343
        rf.valid_tuning_steps = STEPS
344
        return rf
345

    
346
    def process_mmap(self):
347
        """Process the mem map into the mem object"""
348
        self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap)
349

    
350
    def get_settings(self):
351
        """Translate the bit in the mem_struct into settings in the UI"""
352
        _mem = self._memobj
353
        basic = RadioSettingGroup("basic", "Basic Settings")
354
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
355
        other = RadioSettingGroup("other", "Other Settings")
356
        work = RadioSettingGroup("work", "Work Mode Settings")
357
        fm_preset = RadioSettingGroup("fm_preset", "FM Preset")
358
        dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings")
359
        service = RadioSettingGroup("service", "Service Settings")
360
        top = RadioSettings(basic, advanced, other, work, fm_preset, dtmfe,
361
                            service)
362

    
363
        # Basic settings
364
        if _mem.settings.squelch > 0x09:
365
            val = 0x00
366
        else:
367
            val = _mem.settings.squelch
368
        rs = RadioSetting("settings.squelch", "Squelch",
369
                          RadioSettingValueList(
370
                              LIST_OFF1TO9, LIST_OFF1TO9[val]))
371
        basic.append(rs)
372

    
373
        if _mem.settings.save > 0x04:
374
            val = 0x00
375
        else:
376
            val = _mem.settings.save
377
        rs = RadioSetting("settings.save", "Battery Saver",
378
                          RadioSettingValueList(
379
                              LIST_SAVE, LIST_SAVE[val]))
380
        basic.append(rs)
381

    
382
        if _mem.settings.vox > 0x0A:
383
            val = 0x00
384
        else:
385
            val = _mem.settings.vox
386
        rs = RadioSetting("settings.vox", "Vox",
387
                          RadioSettingValueList(
388
                              LIST_OFF1TO10, LIST_OFF1TO10[val]))
389
        basic.append(rs)
390

    
391
        if _mem.settings.abr > 0x0A:
392
            val = 0x00
393
        else:
394
            val = _mem.settings.abr
395
        rs = RadioSetting("settings.abr", "Backlight Timeout",
396
                          RadioSettingValueList(
397
                              LIST_OFF1TO10, LIST_OFF1TO10[val]))
398
        basic.append(rs)
399

    
400
        rs = RadioSetting("settings.tdr", "Dual Watch",
401
                          RadioSettingValueBoolean(_mem.settings.tdr))
402
        basic.append(rs)
403

    
404
        rs = RadioSetting("settings.beep", "Beep",
405
                          RadioSettingValueBoolean(_mem.settings.beep))
406
        basic.append(rs)
407

    
408
        if _mem.settings.timeout > 0x27:
409
            val = 0x03
410
        else:
411
            val = _mem.settings.timeout
412
        rs = RadioSetting("settings.timeout", "Timeout Timer",
413
                          RadioSettingValueList(
414
                              LIST_TIMEOUT, LIST_TIMEOUT[val]))
415
        basic.append(rs)
416

    
417
        if _mem.settings.voice > 0x02:
418
            val = 0x01
419
        else:
420
            val = _mem.settings.voice
421
        rs = RadioSetting("settings.voice", "Voice Prompt",
422
                          RadioSettingValueList(
423
                              LIST_VOICE, LIST_VOICE[val]))
424
        basic.append(rs)
425

    
426
        rs = RadioSetting("settings.dtmfst", "DTMF Sidetone",
427
                          RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
428
                              _mem.settings.dtmfst]))
429
        basic.append(rs)
430

    
431
        if _mem.settings.screv > 0x02:
432
            val = 0x01
433
        else:
434
            val = _mem.settings.screv
435
        rs = RadioSetting("settings.screv", "Scan Resume",
436
                          RadioSettingValueList(
437
                              LIST_RESUME, LIST_RESUME[val]))
438
        basic.append(rs)
439

    
440
        rs = RadioSetting("settings.pttid", "When to send PTT ID",
441
                          RadioSettingValueList(LIST_PTTID, LIST_PTTID[
442
                              _mem.settings.pttid]))
443
        basic.append(rs)
444

    
445
        if _mem.settings.pttlt > 0x1E:
446
            val = 0x05
447
        else:
448
            val = _mem.settings.pttlt
449
        rs = RadioSetting("pttlt", "PTT ID Delay",
450
                          RadioSettingValueInteger(0, 50, val))
451
        basic.append(rs)
452

    
453
        rs = RadioSetting("settings.mdfa", "Display Mode (A)",
454
                          RadioSettingValueList(LIST_MODE, LIST_MODE[
455
                              _mem.settings.mdfa]))
456
        basic.append(rs)
457

    
458
        rs = RadioSetting("settings.mdfb", "Display Mode (B)",
459
                          RadioSettingValueList(LIST_MODE, LIST_MODE[
460
                              _mem.settings.mdfb]))
461
        basic.append(rs)
462

    
463
        rs = RadioSetting("settings.autolk", "Automatic Key Lock",
464
                          RadioSettingValueBoolean(_mem.settings.autolk))
465
        basic.append(rs)
466

    
467
        rs = RadioSetting("settings.wtled", "Standby LED Color",
468
                          RadioSettingValueList(
469
                              LIST_COLOR, LIST_COLOR[_mem.settings.wtled]))
470
        basic.append(rs)
471

    
472
        rs = RadioSetting("settings.rxled", "RX LED Color",
473
                          RadioSettingValueList(
474
                              LIST_COLOR, LIST_COLOR[_mem.settings.rxled]))
475
        basic.append(rs)
476

    
477
        rs = RadioSetting("settings.txled", "TX LED Color",
478
                          RadioSettingValueList(
479
                              LIST_COLOR, LIST_COLOR[_mem.settings.txled]))
480
        basic.append(rs)
481

    
482
        val = _mem.settings.almod
483
        rs = RadioSetting("settings.almod", "Alarm Mode",
484
                          RadioSettingValueList(
485
                              LIST_ALMOD, LIST_ALMOD[val]))
486
        basic.append(rs)
487

    
488
        if _mem.settings.tdrab > 0x02:
489
            val = 0x00
490
        else:
491
            val = _mem.settings.tdrab
492
        rs = RadioSetting("settings.tdrab", "Dual Watch TX Priority",
493
                          RadioSettingValueList(
494
                              LIST_OFFAB, LIST_OFFAB[val]))
495
        basic.append(rs)
496

    
497
        rs = RadioSetting("settings.ste", "Squelch Tail Eliminate (HT to HT)",
498
                          RadioSettingValueBoolean(_mem.settings.ste))
499
        basic.append(rs)
500

    
501
        if _mem.settings.rpste > 0x0A:
502
            val = 0x00
503
        else:
504
            val = _mem.settings.rpste
505
        rs = RadioSetting("settings.rpste",
506
                          "Squelch Tail Eliminate (repeater)",
507
                          RadioSettingValueList(
508
                              LIST_RPSTE, LIST_RPSTE[val]))
509
        basic.append(rs)
510

    
511
        if _mem.settings.rptrl > 0x0A:
512
            val = 0x00
513
        else:
514
            val = _mem.settings.rptrl
515
        rs = RadioSetting("settings.rptrl", "STE Repeater Delay",
516
                          RadioSettingValueList(
517
                              LIST_STEDELAY, LIST_STEDELAY[val]))
518
        basic.append(rs)
519

    
520
        rs = RadioSetting("settings.ponmsg", "Power-On Message",
521
                          RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
522
                              _mem.settings.ponmsg]))
523
        basic.append(rs)
524

    
525
        rs = RadioSetting("settings.roger", "Roger Beep",
526
                          RadioSettingValueBoolean(_mem.settings.roger))
527
        basic.append(rs)
528

    
529
        # Advanced settings
530
        rs = RadioSetting("settings.reset", "RESET Menu",
531
                          RadioSettingValueBoolean(_mem.settings.reset))
532
        advanced.append(rs)
533

    
534
        rs = RadioSetting("settings.menu", "All Menus",
535
                          RadioSettingValueBoolean(_mem.settings.menu))
536
        advanced.append(rs)
537

    
538
        rs = RadioSetting("settings.fmradio", "Broadcast FM Radio",
539
                          RadioSettingValueBoolean(_mem.settings.fmradio))
540
        advanced.append(rs)
541

    
542
        rs = RadioSetting("settings.alarm", "Alarm Sound",
543
                          RadioSettingValueBoolean(_mem.settings.alarm))
544
        advanced.append(rs)
545

    
546
        # Other settings
547
        def _filter(name):
548
            filtered = ""
549
            for char in str(name):
550
                if char in chirp_common.CHARSET_ASCII:
551
                    filtered += char
552
                else:
553
                    filtered += " "
554
            return filtered
555

    
556
        _msg = _mem.firmware_msg
557
        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
558
        val.set_mutable(False)
559
        rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
560
        other.append(rs)
561

    
562
        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
563
        val.set_mutable(False)
564
        rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
565
        other.append(rs)
566

    
567
        _msg = _mem.sixpoweron_msg
568
        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
569
        val.set_mutable(False)
570
        rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", val)
571
        other.append(rs)
572
        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
573
        val.set_mutable(False)
574
        rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", val)
575
        other.append(rs)
576

    
577
        _msg = _mem.poweron_msg
578
        rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
579
                          RadioSettingValueString(
580
                              0, 7, _filter(_msg.line1)))
581
        other.append(rs)
582
        rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
583
                          RadioSettingValueString(
584
                              0, 7, _filter(_msg.line2)))
585
        other.append(rs)
586

    
587
        lower = 130
588
        upper = 179
589
        rs = RadioSetting("limits.vhf.lower", "VHF Lower Limit (MHz)",
590
                          RadioSettingValueInteger(
591
                              lower, upper, _mem.limits.vhf.lower))
592
        other.append(rs)
593

    
594
        rs = RadioSetting("limits.vhf.upper", "VHF Upper Limit (MHz)",
595
                          RadioSettingValueInteger(
596
                              lower, upper, _mem.limits.vhf.upper))
597
        other.append(rs)
598

    
599
        rs = RadioSetting("limits.vhf.enable", "VHF TX Enable",
600
                          RadioSettingValueBoolean(_mem.limits.vhf.enable))
601
        other.append(rs)
602

    
603
        if self._tri_band:
604
            lower = 200
605
            upper = 260
606
            rs = RadioSetting("limits.vhf2.lower", "VHF2 Lower Limit (MHz)",
607
                              RadioSettingValueInteger(
608
                                  lower, upper, _mem.limits.vhf2.lower))
609
            other.append(rs)
610

    
611
            rs = RadioSetting("limits.vhf2.upper", "VHF2 Upper Limit (MHz)",
612
                              RadioSettingValueInteger(
613
                                  lower, upper, _mem.limits.vhf2.upper))
614
            other.append(rs)
615
            rs = RadioSetting("limits.vhf2.enable", "VHF2 TX Enable",
616
                          RadioSettingValueBoolean(_mem.limits.vhf2.enable))
617
            other.append(rs)
618

    
619
        lower = 400
620
        upper = 520
621
        rs = RadioSetting("limits.uhf.lower", "UHF Lower Limit (MHz)",
622
                          RadioSettingValueInteger(
623
                              lower, upper, _mem.limits.uhf.lower))
624
        other.append(rs)
625

    
626
        rs = RadioSetting("limits.uhf.upper", "UHF Upper Limit (MHz)",
627
                          RadioSettingValueInteger(
628
                              lower, upper, _mem.limits.uhf.upper))
629
        other.append(rs)
630

    
631
        rs = RadioSetting("limits.uhf.enable", "UHF TX Enable",
632
                          RadioSettingValueBoolean(_mem.limits.uhf.enable))
633
        other.append(rs)
634

    
635
        # Work mode settings
636
        rs = RadioSetting("settings.displayab", "Display",
637
                          RadioSettingValueList(
638
                              LIST_AB, LIST_AB[_mem.settings.displayab]))
639
        work.append(rs)
640

    
641
        rs = RadioSetting("settings.workmode", "VFO/MR Mode",
642
                          RadioSettingValueList(
643
                              LIST_WORKMODE,
644
                              LIST_WORKMODE[_mem.settings.workmode]))
645
        work.append(rs)
646

    
647
        rs = RadioSetting("settings.keylock", "Keypad Lock",
648
                          RadioSettingValueBoolean(_mem.settings.keylock))
649
        work.append(rs)
650

    
651
        rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
652
                          RadioSettingValueInteger(0, 127,
653
                                                   _mem.wmchannel.mrcha))
654
        work.append(rs)
655

    
656
        rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
657
                          RadioSettingValueInteger(0, 127,
658
                                                   _mem.wmchannel.mrchb))
659
        work.append(rs)
660

    
661
        def convert_bytes_to_freq(bytes):
662
            real_freq = 0
663
            for byte in bytes:
664
                real_freq = (real_freq * 10) + byte
665
            return chirp_common.format_freq(real_freq * 10)
666

    
667
        def my_validate(value):
668
            value = chirp_common.parse_freq(value)
669
            msg = ("Can't be less than %i.0000")
670
            if value > 99000000 and value < 130 * 1000000:
671
                raise InvalidValueError(msg % (130))
672
            msg = ("Can't be between %i.9975-%i.0000")
673
            if (179 + 1) * 1000000 <= value and value < 400 * 1000000:
674
                raise InvalidValueError(msg % (179, 400))
675
            msg = ("Can't be greater than %i.9975")
676
            if value > 99000000 and value > (520 + 1) * 1000000:
677
                raise InvalidValueError(msg % (520))
678
            return chirp_common.format_freq(value)
679

    
680
        def apply_freq(setting, obj):
681
            value = chirp_common.parse_freq(str(setting.value)) / 10
682
            for i in range(7, -1, -1):
683
                obj.freq[i] = value % 10
684
                value /= 10
685

    
686
        val1a = RadioSettingValueString(0, 10,
687
                                        convert_bytes_to_freq(_mem.vfo.a.freq))
688
        val1a.set_validate_callback(my_validate)
689
        rs = RadioSetting("vfo.a.freq", "VFO A Frequency", val1a)
690
        rs.set_apply_callback(apply_freq, _mem.vfo.a)
691
        work.append(rs)
692

    
693
        val1b = RadioSettingValueString(0, 10,
694
                                        convert_bytes_to_freq(_mem.vfo.b.freq))
695
        val1b.set_validate_callback(my_validate)
696
        rs = RadioSetting("vfo.b.freq", "VFO B Frequency", val1b)
697
        rs.set_apply_callback(apply_freq, _mem.vfo.b)
698
        work.append(rs)
699

    
700
        rs = RadioSetting("vfo.a.sftd", "VFO A Shift",
701
                          RadioSettingValueList(
702
                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.a.sftd]))
703
        work.append(rs)
704

    
705
        rs = RadioSetting("vfo.b.sftd", "VFO B Shift",
706
                          RadioSettingValueList(
707
                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.b.sftd]))
708
        work.append(rs)
709

    
710
        def convert_bytes_to_offset(bytes):
711
            real_offset = 0
712
            for byte in bytes:
713
                real_offset = (real_offset * 10) + byte
714
            return chirp_common.format_freq(real_offset * 1000)
715

    
716
        def apply_offset(setting, obj):
717
            value = chirp_common.parse_freq(str(setting.value)) / 1000
718
            for i in range(5, -1, -1):
719
                obj.offset[i] = value % 10
720
                value /= 10
721

    
722
        val1a = RadioSettingValueString(
723
                    0, 10, convert_bytes_to_offset(_mem.vfo.a.offset))
724
        rs = RadioSetting("vfo.a.offset",
725
                          "VFO A Offset", val1a)
726
        rs.set_apply_callback(apply_offset, _mem.vfo.a)
727
        work.append(rs)
728

    
729
        val1b = RadioSettingValueString(
730
                    0, 10, convert_bytes_to_offset(_mem.vfo.b.offset))
731
        rs = RadioSetting("vfo.b.offset",
732
                          "VFO B Offset", val1b)
733
        rs.set_apply_callback(apply_offset, _mem.vfo.b)
734
        work.append(rs)
735

    
736
        def apply_txpower_listvalue(setting, obj):
737
            LOG.debug("Setting value: " + str(
738
                      setting.value) + " from list")
739
            val = str(setting.value)
740
            index = TXP_CHOICES.index(val)
741
            val = TXP_VALUES[index]
742
            obj.set_value(val)
743

    
744
        if self._tri_band:
745
            if _mem.vfo.a.txpower3 in TXP_VALUES:
746
                idx = TXP_VALUES.index(_mem.vfo.a.txpower3)
747
            else:
748
                idx = TXP_VALUES.index(0x00)
749
            rs = RadioSettingValueList(TXP_CHOICES, TXP_CHOICES[idx])
750
            rset = RadioSetting("vfo.a.txpower3", "VFO A Power", rs)
751
            rset.set_apply_callback(apply_txpower_listvalue,
752
                                    _mem.vfo.a.txpower3)
753
            work.append(rset)
754

    
755
            if _mem.vfo.b.txpower3 in TXP_VALUES:
756
                idx = TXP_VALUES.index(_mem.vfo.b.txpower3)
757
            else:
758
                idx = TXP_VALUES.index(0x00)
759
            rs = RadioSettingValueList(TXP_CHOICES, TXP_CHOICES[idx])
760
            rset = RadioSetting("vfo.b.txpower3", "VFO B Power", rs)
761
            rset.set_apply_callback(apply_txpower_listvalue,
762
                                    _mem.vfo.b.txpower3)
763
            work.append(rset)
764
        else:
765
            rs = RadioSetting("vfo.a.txpower3", "VFO A Power",
766
                              RadioSettingValueList(
767
                                  LIST_TXPOWER,
768
                                  LIST_TXPOWER[min(_mem.vfo.a.txpower3, 0x02)]
769
                                               ))
770
            work.append(rs)
771

    
772
            rs = RadioSetting("vfo.b.txpower3", "VFO B Power",
773
                              RadioSettingValueList(
774
                                  LIST_TXPOWER,
775
                                  LIST_TXPOWER[min(_mem.vfo.b.txpower3, 0x02)]
776
                                               ))
777
            work.append(rs)
778

    
779
        rs = RadioSetting("vfo.a.widenarr", "VFO A Bandwidth",
780
                          RadioSettingValueList(
781
                              LIST_BANDWIDTH,
782
                              LIST_BANDWIDTH[_mem.vfo.a.widenarr]))
783
        work.append(rs)
784

    
785
        rs = RadioSetting("vfo.b.widenarr", "VFO B Bandwidth",
786
                          RadioSettingValueList(
787
                              LIST_BANDWIDTH,
788
                              LIST_BANDWIDTH[_mem.vfo.b.widenarr]))
789
        work.append(rs)
790

    
791
        rs = RadioSetting("vfo.a.scode", "VFO A S-CODE",
792
                          RadioSettingValueList(
793
                              LIST_SCODE,
794
                              LIST_SCODE[_mem.vfo.a.scode]))
795
        work.append(rs)
796

    
797
        rs = RadioSetting("vfo.b.scode", "VFO B S-CODE",
798
                          RadioSettingValueList(
799
                              LIST_SCODE,
800
                              LIST_SCODE[_mem.vfo.b.scode]))
801
        work.append(rs)
802

    
803
        rs = RadioSetting("vfo.a.step", "VFO A Tuning Step",
804
                          RadioSettingValueList(
805
                              LIST_STEP, LIST_STEP[_mem.vfo.a.step]))
806
        work.append(rs)
807
        rs = RadioSetting("vfo.b.step", "VFO B Tuning Step",
808
                          RadioSettingValueList(
809
                              LIST_STEP, LIST_STEP[_mem.vfo.b.step]))
810
        work.append(rs)
811

    
812
        # broadcast FM settings
813
        value = self._memobj.fm_presets
814
        value_shifted = ((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8)
815
        if value_shifted >= 65.0 * 10 and value_shifted <= 108.0 * 10:
816
            # storage method 3 (discovered 2022)
817
            self._bw_shift = True
818
            preset = value_shifted / 10.0
819
        elif value >= 65.0 * 10 and value <= 108.0 * 10:
820
            # storage method 2
821
            preset = value / 10.0
822
        elif value <= 108.0 * 10 - 650:
823
            # original storage method (2012)
824
            preset = value / 10.0 + 65
825
        else:
826
            # unknown (undiscovered method or no FM chip?)
827
            preset = False
828
        if preset:
829
            rs = RadioSettingValueFloat(65, 108.0, preset, 0.1, 1)
830
            rset = RadioSetting("fm_presets", "FM Preset(MHz)", rs)
831
            fm_preset.append(rset)
832

    
833
        # DTMF settings
834
        def apply_code(setting, obj, length):
835
            code = []
836
            for j in range(0, length):
837
                try:
838
                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
839
                except IndexError:
840
                    code.append(0xFF)
841
            obj.code = code
842

    
843
        for i in range(0, 15):
844
            _codeobj = self._memobj.pttid[i].code
845
            _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
846
            val = RadioSettingValueString(0, 5, _code, False)
847
            val.set_charset(DTMF_CHARS)
848
            pttid = RadioSetting("pttid/%i.code" % i,
849
                                 "Signal Code %i" % (i + 1), val)
850
            pttid.set_apply_callback(apply_code, self._memobj.pttid[i], 5)
851
            dtmfe.append(pttid)
852

    
853
        if _mem.ani.dtmfon > 0xC3:
854
            val = 0x03
855
        else:
856
            val = _mem.ani.dtmfon
857
        rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
858
                          RadioSettingValueList(LIST_DTMFSPEED,
859
                                                LIST_DTMFSPEED[val]))
860
        dtmfe.append(rs)
861

    
862
        if _mem.ani.dtmfoff > 0xC3:
863
            val = 0x03
864
        else:
865
            val = _mem.ani.dtmfoff
866
        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
867
                          RadioSettingValueList(LIST_DTMFSPEED,
868
                                                LIST_DTMFSPEED[val]))
869
        dtmfe.append(rs)
870

    
871
        _codeobj = self._memobj.ani.code
872
        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
873
        val = RadioSettingValueString(0, 5, _code, False)
874
        val.set_charset(DTMF_CHARS)
875
        rs = RadioSetting("ani.code", "ANI Code", val)
876
        rs.set_apply_callback(apply_code, self._memobj.ani, 5)
877
        dtmfe.append(rs)
878

    
879
        rs = RadioSetting("ani.aniid", "When to send ANI ID",
880
                          RadioSettingValueList(LIST_PTTID,
881
                                                LIST_PTTID[_mem.ani.aniid]))
882
        dtmfe.append(rs)
883

    
884
        # Service settings
885
        for band in ["vhf", "uhf"]:
886
            for index in range(0, 10):
887
                key = "squelch.%s.sql%i" % (band, index)
888
                if band == "vhf":
889
                    _obj = self._memobj.squelch.vhf
890
                elif band == "uhf":
891
                    _obj = self._memobj.squelch.uhf
892
                val = RadioSettingValueInteger(0, 123,
893
                                               getattr(
894
                                                   _obj, "sql%i" % (index)))
895
                if index == 0:
896
                    val.set_mutable(False)
897
                name = "%s Squelch %i" % (band.upper(), index)
898
                rs = RadioSetting(key, name, val)
899
                service.append(rs)
900

    
901
        return top
902

    
903
    @classmethod
904
    def match_model(cls, filedata, filename):
905
        match_size = False
906
        match_model = False
907

    
908
        # testing the file data size
909
        if len(filedata) in [0x2008, 0x2010]:
910
            match_size = True
911

    
912
        # testing the firmware model fingerprint
913
        match_model = model_match(cls, filedata)
914

    
915
        if match_size and match_model:
916
            return True
917
        else:
918
            return False
919

    
920

    
921
class RH5XAlias(chirp_common.Alias):
922
    VENDOR = "Rugged"
923
    MODEL = "RH5X"
924

    
925

    
926
class UV82IIIAlias(chirp_common.Alias):
927
    VENDOR = "Baofeng"
928
    MODEL = "UV-82III"
929

    
930

    
931
class UV9RPROAlias(chirp_common.Alias):
932
    VENDOR = "Baofeng"
933
    MODEL = "UV-9R Pro"
934

    
935

    
936
@directory.register
937
class BFA58(WP970I):
938
    """Baofeng BF-A58"""
939
    VENDOR = "Baofeng"
940
    MODEL = "BF-A58"
941
    LENGTH_NAME = 7
942
    ALIASES = [RH5XAlias, UV9RPROAlias]
943

    
944
    _fileid = ["BFT515 ", "BFT517 "]
945

    
946

    
947
@directory.register
948
class UV82WP(WP970I):
949
    """Baofeng UV82-WP"""
950
    VENDOR = "Baofeng"
951
    MODEL = "UV-82WP"
952

    
953

    
954
@directory.register
955
class GT3WP(WP970I):
956
    """Baofeng GT-3WP"""
957
    VENDOR = "Baofeng"
958
    MODEL = "GT-3WP"
959
    LENGTH_NAME = 7
960

    
961

    
962
@directory.register
963
class RT6(WP970I):
964
    """Retevis RT6"""
965
    VENDOR = "Retevis"
966
    MODEL = "RT6"
967

    
968

    
969
@directory.register
970
class BFA58S(WP970I):
971
    VENDOR = "Baofeng"
972
    MODEL = "BF-A58S"
973
    LENGTH_NAME = 7
974
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
975
                    chirp_common.PowerLevel("Low", watts=1.00)]
976
    ALIASES = [UV82IIIAlias]
977
    _tri_band = True
978

    
979
    def get_features(self):
980
        rf = WP970I.get_features(self)
981
        rf.valid_bands = [self._vhf_range,
982
                          self._vhf2_range,
983
                          self._uhf_range]
984
        return rf
985

    
986

    
987
@directory.register
988
class UVS9X3(BFA58S):
989
    VENDOR = "Baofeng"
990
    MODEL = "UV-S9X3"
991
    ALIASES = []
992
    _magic = [MSTRING_UVS9X3, ]
993

    
994

    
995
@directory.register
996
class UV9R(WP970I):
997
    """Baofeng UV-9R"""
998
    VENDOR = "Baofeng"
999
    MODEL = "UV-9R"
1000
    LENGTH_NAME = 7
1001

    
1002

    
1003
@directory.register
1004
class UV9G(WP970I):
1005
    """Baofeng UV-9G"""
1006
    VENDOR = "Baofeng"
1007
    MODEL = "UV-9G"
1008
    LENGTH_NAME = 7
1009

    
1010
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
1011
                    chirp_common.PowerLevel("Med",  watts=1.00),
1012
                    chirp_common.PowerLevel("Low",  watts=0.50)]
1013
    _magic = [MSTRING_UV9G, ]
1014
    _gmrs = False  # sold as GMRS radio but supports full band TX/RX
1015

    
1016
    @classmethod
1017
    def match_model(cls, filedata, filename):
1018
        # This radio has always been post-metadata, so never do
1019
        # old-school detection
1020
        return False
    (1-1/1)