Project

General

Profile

Bug #10388 » baofeng_uv-9g_full_band.py

Jim Unroe, 06/21/2023 06:59 PM

 
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, memmap
21
from chirp import bitwise, errors, util
22
from chirp import bandplan_na
23
from chirp.settings import RadioSettingGroup, RadioSetting, \
24
    RadioSettingValueBoolean, RadioSettingValueList, \
25
    RadioSettingValueString, RadioSettingValueInteger, \
26
    RadioSettingValueFloat, RadioSettings, \
27
    InvalidValueError
28

    
29
LOG = logging.getLogger(__name__)
30

    
31
# #### MAGICS #########################################################
32

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

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

    
39

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

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

    
67
TXP_CHOICES = ["High", "Low"]
68
TXP_VALUES = [0x00, 0x02]
69

    
70

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

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

    
83

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

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

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

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

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

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

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

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

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

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

    
247
    #seekto 0x0F00;
248
    struct {
249
      struct vfo a;
250
      struct vfo b;
251
    } vfo;
252

    
253
    #seekto 0x0F4E;
254
    u16 fm_presets;
255

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

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

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

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

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

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

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

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

    
314
    """
315

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
597
        if self._tri_band:
598
            lower = 200
599
            upper = 260
600
            rs = RadioSetting("limits.vhf2.lower", "VHF2 Lower Limit (MHz)",
601
                              RadioSettingValueInteger(
602
                                  lower, upper, _mem.limits.vhf2.lower))
603
            other.append(rs)
604

    
605
            rs = RadioSetting("limits.vhf2.upper", "VHF2 Upper Limit (MHz)",
606
                              RadioSettingValueInteger(
607
                                  lower, upper, _mem.limits.vhf2.upper))
608
            other.append(rs)
609

    
610
        lower = 400
611
        upper = 520
612
        rs = RadioSetting("limits.uhf.lower", "UHF Lower Limit (MHz)",
613
                          RadioSettingValueInteger(
614
                              lower, upper, _mem.limits.uhf.lower))
615
        other.append(rs)
616

    
617
        rs = RadioSetting("limits.uhf.upper", "UHF Upper Limit (MHz)",
618
                          RadioSettingValueInteger(
619
                              lower, upper, _mem.limits.uhf.upper))
620
        other.append(rs)
621

    
622
        # Work mode settings
623
        rs = RadioSetting("settings.displayab", "Display",
624
                          RadioSettingValueList(
625
                              LIST_AB, LIST_AB[_mem.settings.displayab]))
626
        work.append(rs)
627

    
628
        rs = RadioSetting("settings.workmode", "VFO/MR Mode",
629
                          RadioSettingValueList(
630
                              LIST_WORKMODE,
631
                              LIST_WORKMODE[_mem.settings.workmode]))
632
        work.append(rs)
633

    
634
        rs = RadioSetting("settings.keylock", "Keypad Lock",
635
                          RadioSettingValueBoolean(_mem.settings.keylock))
636
        work.append(rs)
637

    
638
        rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
639
                          RadioSettingValueInteger(0, 127,
640
                                                   _mem.wmchannel.mrcha))
641
        work.append(rs)
642

    
643
        rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
644
                          RadioSettingValueInteger(0, 127,
645
                                                   _mem.wmchannel.mrchb))
646
        work.append(rs)
647

    
648
        def convert_bytes_to_freq(bytes):
649
            real_freq = 0
650
            for byte in bytes:
651
                real_freq = (real_freq * 10) + byte
652
            return chirp_common.format_freq(real_freq * 10)
653

    
654
        def my_validate(value):
655
            value = chirp_common.parse_freq(value)
656
            msg = ("Can't be less than %i.0000")
657
            if value > 99000000 and value < 130 * 1000000:
658
                raise InvalidValueError(msg % (130))
659
            msg = ("Can't be between %i.9975-%i.0000")
660
            if (179 + 1) * 1000000 <= value and value < 400 * 1000000:
661
                raise InvalidValueError(msg % (179, 400))
662
            msg = ("Can't be greater than %i.9975")
663
            if value > 99000000 and value > (520 + 1) * 1000000:
664
                raise InvalidValueError(msg % (520))
665
            return chirp_common.format_freq(value)
666

    
667
        def apply_freq(setting, obj):
668
            value = chirp_common.parse_freq(str(setting.value)) / 10
669
            for i in range(7, -1, -1):
670
                obj.freq[i] = value % 10
671
                value /= 10
672

    
673
        val1a = RadioSettingValueString(0, 10,
674
                                        convert_bytes_to_freq(_mem.vfo.a.freq))
675
        val1a.set_validate_callback(my_validate)
676
        rs = RadioSetting("vfo.a.freq", "VFO A Frequency", val1a)
677
        rs.set_apply_callback(apply_freq, _mem.vfo.a)
678
        work.append(rs)
679

    
680
        val1b = RadioSettingValueString(0, 10,
681
                                        convert_bytes_to_freq(_mem.vfo.b.freq))
682
        val1b.set_validate_callback(my_validate)
683
        rs = RadioSetting("vfo.b.freq", "VFO B Frequency", val1b)
684
        rs.set_apply_callback(apply_freq, _mem.vfo.b)
685
        work.append(rs)
686

    
687
        rs = RadioSetting("vfo.a.sftd", "VFO A Shift",
688
                          RadioSettingValueList(
689
                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.a.sftd]))
690
        work.append(rs)
691

    
692
        rs = RadioSetting("vfo.b.sftd", "VFO B Shift",
693
                          RadioSettingValueList(
694
                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.b.sftd]))
695
        work.append(rs)
696

    
697
        def convert_bytes_to_offset(bytes):
698
            real_offset = 0
699
            for byte in bytes:
700
                real_offset = (real_offset * 10) + byte
701
            return chirp_common.format_freq(real_offset * 1000)
702

    
703
        def apply_offset(setting, obj):
704
            value = chirp_common.parse_freq(str(setting.value)) / 1000
705
            for i in range(5, -1, -1):
706
                obj.offset[i] = value % 10
707
                value /= 10
708

    
709
        val1a = RadioSettingValueString(
710
                    0, 10, convert_bytes_to_offset(_mem.vfo.a.offset))
711
        rs = RadioSetting("vfo.a.offset",
712
                          "VFO A Offset", val1a)
713
        rs.set_apply_callback(apply_offset, _mem.vfo.a)
714
        work.append(rs)
715

    
716
        val1b = RadioSettingValueString(
717
                    0, 10, convert_bytes_to_offset(_mem.vfo.b.offset))
718
        rs = RadioSetting("vfo.b.offset",
719
                          "VFO B Offset", val1b)
720
        rs.set_apply_callback(apply_offset, _mem.vfo.b)
721
        work.append(rs)
722

    
723
        def apply_txpower_listvalue(setting, obj):
724
            LOG.debug("Setting value: " + str(
725
                      setting.value) + " from list")
726
            val = str(setting.value)
727
            index = TXP_CHOICES.index(val)
728
            val = TXP_VALUES[index]
729
            obj.set_value(val)
730

    
731
        if self._tri_band:
732
            if _mem.vfo.a.txpower3 in TXP_VALUES:
733
                idx = TXP_VALUES.index(_mem.vfo.a.txpower3)
734
            else:
735
                idx = TXP_VALUES.index(0x00)
736
            rs = RadioSettingValueList(TXP_CHOICES, TXP_CHOICES[idx])
737
            rset = RadioSetting("vfo.a.txpower3", "VFO A Power", rs)
738
            rset.set_apply_callback(apply_txpower_listvalue,
739
                                    _mem.vfo.a.txpower3)
740
            work.append(rset)
741

    
742
            if _mem.vfo.b.txpower3 in TXP_VALUES:
743
                idx = TXP_VALUES.index(_mem.vfo.b.txpower3)
744
            else:
745
                idx = TXP_VALUES.index(0x00)
746
            rs = RadioSettingValueList(TXP_CHOICES, TXP_CHOICES[idx])
747
            rset = RadioSetting("vfo.b.txpower3", "VFO B Power", rs)
748
            rset.set_apply_callback(apply_txpower_listvalue,
749
                                    _mem.vfo.b.txpower3)
750
            work.append(rset)
751
        else:
752
            rs = RadioSetting("vfo.a.txpower3", "VFO A Power",
753
                              RadioSettingValueList(
754
                                  LIST_TXPOWER,
755
                                  LIST_TXPOWER[min(_mem.vfo.a.txpower3, 0x02)]
756
                                               ))
757
            work.append(rs)
758

    
759
            rs = RadioSetting("vfo.b.txpower3", "VFO B Power",
760
                              RadioSettingValueList(
761
                                  LIST_TXPOWER,
762
                                  LIST_TXPOWER[min(_mem.vfo.b.txpower3, 0x02)]
763
                                               ))
764
            work.append(rs)
765

    
766
        rs = RadioSetting("vfo.a.widenarr", "VFO A Bandwidth",
767
                          RadioSettingValueList(
768
                              LIST_BANDWIDTH,
769
                              LIST_BANDWIDTH[_mem.vfo.a.widenarr]))
770
        work.append(rs)
771

    
772
        rs = RadioSetting("vfo.b.widenarr", "VFO B Bandwidth",
773
                          RadioSettingValueList(
774
                              LIST_BANDWIDTH,
775
                              LIST_BANDWIDTH[_mem.vfo.b.widenarr]))
776
        work.append(rs)
777

    
778
        rs = RadioSetting("vfo.a.scode", "VFO A S-CODE",
779
                          RadioSettingValueList(
780
                              LIST_SCODE,
781
                              LIST_SCODE[_mem.vfo.a.scode]))
782
        work.append(rs)
783

    
784
        rs = RadioSetting("vfo.b.scode", "VFO B S-CODE",
785
                          RadioSettingValueList(
786
                              LIST_SCODE,
787
                              LIST_SCODE[_mem.vfo.b.scode]))
788
        work.append(rs)
789

    
790
        rs = RadioSetting("vfo.a.step", "VFO A Tuning Step",
791
                          RadioSettingValueList(
792
                              LIST_STEP, LIST_STEP[_mem.vfo.a.step]))
793
        work.append(rs)
794
        rs = RadioSetting("vfo.b.step", "VFO B Tuning Step",
795
                          RadioSettingValueList(
796
                              LIST_STEP, LIST_STEP[_mem.vfo.b.step]))
797
        work.append(rs)
798

    
799
        # broadcast FM settings
800
        value = self._memobj.fm_presets
801
        value_shifted = ((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8)
802
        if value_shifted >= 65.0 * 10 and value_shifted <= 108.0 * 10:
803
            # storage method 3 (discovered 2022)
804
            self._bw_shift = True
805
            preset = value_shifted / 10.0
806
        elif value >= 65.0 * 10 and value <= 108.0 * 10:
807
            # storage method 2
808
            preset = value / 10.0
809
        elif value <= 108.0 * 10 - 650:
810
            # original storage method (2012)
811
            preset = value / 10.0 + 65
812
        else:
813
            # unknown (undiscovered method or no FM chip?)
814
            preset = False
815
        if preset:
816
            rs = RadioSettingValueFloat(65, 108.0, preset, 0.1, 1)
817
            rset = RadioSetting("fm_presets", "FM Preset(MHz)", rs)
818
            fm_preset.append(rset)
819

    
820
        # DTMF settings
821
        def apply_code(setting, obj, length):
822
            code = []
823
            for j in range(0, length):
824
                try:
825
                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
826
                except IndexError:
827
                    code.append(0xFF)
828
            obj.code = code
829

    
830
        for i in range(0, 15):
831
            _codeobj = self._memobj.pttid[i].code
832
            _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
833
            val = RadioSettingValueString(0, 5, _code, False)
834
            val.set_charset(DTMF_CHARS)
835
            pttid = RadioSetting("pttid/%i.code" % i,
836
                                 "Signal Code %i" % (i + 1), val)
837
            pttid.set_apply_callback(apply_code, self._memobj.pttid[i], 5)
838
            dtmfe.append(pttid)
839

    
840
        if _mem.ani.dtmfon > 0xC3:
841
            val = 0x03
842
        else:
843
            val = _mem.ani.dtmfon
844
        rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
845
                          RadioSettingValueList(LIST_DTMFSPEED,
846
                                                LIST_DTMFSPEED[val]))
847
        dtmfe.append(rs)
848

    
849
        if _mem.ani.dtmfoff > 0xC3:
850
            val = 0x03
851
        else:
852
            val = _mem.ani.dtmfoff
853
        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
854
                          RadioSettingValueList(LIST_DTMFSPEED,
855
                                                LIST_DTMFSPEED[val]))
856
        dtmfe.append(rs)
857

    
858
        _codeobj = self._memobj.ani.code
859
        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
860
        val = RadioSettingValueString(0, 5, _code, False)
861
        val.set_charset(DTMF_CHARS)
862
        rs = RadioSetting("ani.code", "ANI Code", val)
863
        rs.set_apply_callback(apply_code, self._memobj.ani, 5)
864
        dtmfe.append(rs)
865

    
866
        rs = RadioSetting("ani.aniid", "When to send ANI ID",
867
                          RadioSettingValueList(LIST_PTTID,
868
                                                LIST_PTTID[_mem.ani.aniid]))
869
        dtmfe.append(rs)
870

    
871
        # Service settings
872
        for band in ["vhf", "uhf"]:
873
            for index in range(0, 10):
874
                key = "squelch.%s.sql%i" % (band, index)
875
                if band == "vhf":
876
                    _obj = self._memobj.squelch.vhf
877
                elif band == "uhf":
878
                    _obj = self._memobj.squelch.uhf
879
                val = RadioSettingValueInteger(0, 123,
880
                                               getattr(
881
                                                   _obj, "sql%i" % (index)))
882
                if index == 0:
883
                    val.set_mutable(False)
884
                name = "%s Squelch %i" % (band.upper(), index)
885
                rs = RadioSetting(key, name, val)
886
                service.append(rs)
887

    
888
        return top
889

    
890
    @classmethod
891
    def match_model(cls, filedata, filename):
892
        match_size = False
893
        match_model = False
894

    
895
        # testing the file data size
896
        if len(filedata) in [0x2008, 0x2010]:
897
            match_size = True
898

    
899
        # testing the firmware model fingerprint
900
        match_model = model_match(cls, filedata)
901

    
902
        if match_size and match_model:
903
            return True
904
        else:
905
            return False
906

    
907

    
908
class RH5XAlias(chirp_common.Alias):
909
    VENDOR = "Rugged"
910
    MODEL = "RH5X"
911

    
912

    
913
class UV82IIIAlias(chirp_common.Alias):
914
    VENDOR = "Baofeng"
915
    MODEL = "UV-82III"
916

    
917

    
918
class UV9RPROAlias(chirp_common.Alias):
919
    VENDOR = "Baofeng"
920
    MODEL = "UV-9R Pro"
921

    
922

    
923
@directory.register
924
class BFA58(WP970I):
925
    """Baofeng BF-A58"""
926
    VENDOR = "Baofeng"
927
    MODEL = "BF-A58"
928
    LENGTH_NAME = 7
929
    ALIASES = [RH5XAlias, UV9RPROAlias]
930

    
931
    _fileid = ["BFT515 ", "BFT517 "]
932

    
933

    
934
@directory.register
935
class UV82WP(WP970I):
936
    """Baofeng UV82-WP"""
937
    VENDOR = "Baofeng"
938
    MODEL = "UV-82WP"
939

    
940

    
941
@directory.register
942
class GT3WP(WP970I):
943
    """Baofeng GT-3WP"""
944
    VENDOR = "Baofeng"
945
    MODEL = "GT-3WP"
946
    LENGTH_NAME = 7
947

    
948

    
949
@directory.register
950
class RT6(WP970I):
951
    """Retevis RT6"""
952
    VENDOR = "Retevis"
953
    MODEL = "RT6"
954

    
955

    
956
@directory.register
957
class BFA58S(WP970I):
958
    VENDOR = "Baofeng"
959
    MODEL = "BF-A58S"
960
    LENGTH_NAME = 7
961
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
962
                    chirp_common.PowerLevel("Low", watts=1.00)]
963
    ALIASES = [UV82IIIAlias]
964
    _tri_band = True
965

    
966
    def get_features(self):
967
        rf = WP970I.get_features(self)
968
        rf.valid_bands = [self._vhf_range,
969
                          self._vhf2_range,
970
                          self._uhf_range]
971
        return rf
972

    
973

    
974
@directory.register
975
class UV9R(WP970I):
976
    """Baofeng UV-9R"""
977
    VENDOR = "Baofeng"
978
    MODEL = "UV-9R"
979
    LENGTH_NAME = 7
980

    
981

    
982
@directory.register
983
class UV9G(WP970I):
984
    """Baofeng UV-9G"""
985
    VENDOR = "Baofeng"
986
    MODEL = "UV-9G"
987
    LENGTH_NAME = 7
988

    
989
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
990
                    chirp_common.PowerLevel("Med",  watts=0.50),
991
                    chirp_common.PowerLevel("Low",  watts=0.50)]
992
    _magic = [MSTRING_UV9G, ]
993
    _gmrs = False  # sold as GMRS radio but supports full band TX/RX
994

    
995
    @classmethod
996
    def match_model(cls, filedata, filename):
997
        # This radio has always been post-metadata, so never do
998
        # old-school detection
999
        return False
(2-2/2)