Project

General

Profile

New Model #10648 » baofeng_uv17Pro.py

Driver staged for implementing in Chirp - Sander van der Wel, 11/17/2023 11:59 PM

 
1
# Copyright 2023:
2
# * Sander van der Wel, <svdwel@icloud.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
22
from chirp.settings import RadioSetting, \
23
    RadioSettingValueBoolean, RadioSettingValueList, \
24
    RadioSettingValueString, RadioSettingValueInteger, \
25
    RadioSettings, RadioSettingGroup, \
26
    InvalidValueError
27
import struct
28
from chirp import errors, util
29

    
30
LOG = logging.getLogger(__name__)
31

    
32
# Baofeng UV-17L magic string
33
MSTRING_UV17L = b"PROGRAMBFNORMALU"
34
MSTRING_UV17PROGPS = b"PROGRAMCOLORPROU"
35

    
36
DTMF_CHARS = "0123456789 *#ABCD"
37
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0]
38

    
39
LIST_AB = ["A", "B"]
40
LIST_ALMOD = ["Site", "Tone", "Code"]
41
LIST_BANDWIDTH = ["Wide", "Narrow"]
42
LIST_COLOR = ["Off", "Blue", "Orange", "Purple"]
43
LIST_DTMFSPEED = ["%s ms" % x for x in [50, 100, 200, 300, 500]]
44
LIST_HANGUPTIME = ["%s s" % x for x in [3, 4, 5, 6, 7, 8, 9, 10]]
45
LIST_SIDE_TONE = ["Off", "Side Key", "ID", "Side + ID"]
46
LIST_DTMFST = ["Off", "DT-ST", "ANI-ST", "DT+ANI"]
47
LIST_MODE = ["Name", "Frequency", "Channel Number"]
48
LIST_OFF1TO5 = ["Off"] + list("12345")
49
LIST_OFF1TO9 = LIST_OFF1TO5 + list("6789")
50
LIST_OFF1TO10 = LIST_OFF1TO9 + ["10"]
51
LIST_OFFAB = ["Off"] + LIST_AB
52
LIST_RESUME = ["TO", "CO", "SE"]
53
LIST_PONMSG = ["Full", "Message"]
54
LIST_PTTID = ["Off", "BOT", "EOT", "Both"]
55
LIST_SCODE = ["%s" % x for x in range(1, 21)]
56
LIST_RPSTE = ["Off"] + ["%s" % x for x in range(1, 11)]
57
LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"]
58
LIST_SHIFTD = ["Off", "+", "-"]
59
LIST_STEDELAY = ["Off"] + ["%s ms" % x for x in range(100, 1100, 100)]
60
LIST_STEP = [str(x) for x in STEPS]
61
LIST_TIMEOUT = ["Off"] + ["%s sec" % x for x in range(15, 195, 15)]
62
LIST_TIMEOUT_ALARM = ["Off"] + ["%s sec" % x for x in range(1, 11)]
63
LIST_PILOT_TONE = ["1000 Hz", "1450 Hz", "1750 Hz", "2100 Hz"]
64
LIST_TXPOWER = ["High", "Low"]
65
LIST_VOICE = ["Off", "English", "Chinese"]
66
LIST_WORKMODE = ["Frequency", "Channel"]
67
LIST_POWERON_DISPLAY_TYPE = ["LOGO", "BATT voltage"]
68
LIST_BEEP = ["Off", "Beep", "Voice", "Both"]
69
LIST_LANGUAGE = ["English", "Chinese"]
70
LIST_SCANMODE = ["Time", "Carrier", "Search"]
71
LIST_ALARMMODE = ["Local", "Send Tone", "Send Code"]
72
LIST_MENU_QUIT_TIME = ["%s sec" % x for x in range(5, 55, 5)] + ["60 sec"]
73
LIST_BACKLIGHT_TIMER = ["Always On"] + ["%s sec" % x for x in range(5, 25, 5)]
74
LIST_ID_DELAY = ["%s ms" % x for x in range(100, 3100, 100)]
75
LIST_QT_SAVEMODE = ["Both", "RX", "TX"]
76
LIST_SKEY2_SHORT = ["FM", "Scan", "Search", "Vox"]
77
LIST_RPT_TAIL_CLEAR = ["%s ms" % x for x in range(0, 1100, 100)]
78
LIST_VOX_DELAY_TIME = ["%s ms" % x for x in range(500, 2100, 100)]
79
LIST_VOX_LEVEL = ["Off"] + ["%s" % x for x in range(1, 10, 1)]
80
LIST_VOX_LEVEL_ALT = ["%s" % x for x in range(1, 10, 1)]
81
LIST_GPS_MODE = ["GPS", "Beidou", "GPS + Beidou"]
82
LIST_GPS_TIMEZONE = ["%s" % x for x in range(-12, 13, 1)]
83

    
84
TXP_CHOICES = ["High", "Low"]
85
TXP_VALUES = [0x00, 0x02]
86

    
87
STIMEOUT = 1.5
88

    
89

    
90
def model_match(cls, data):
91
    """Match the opened image to the correct version"""
92
    return data[cls.MEM_TOTAL:] == bytes(cls.MODEL, 'utf-8')
93

    
94

    
95
def _crypt(symbolIndex, buffer):
96
    # Some weird encryption is used. From the table below, we only use "CO 7".
97
    tblEncrySymbol = [b"BHT ", b"CO 7", b"A ES", b" EIY", b"M PQ",
98
                      b"XN Y", b"RVB ", b" HQP", b"W RC", b"MS N",
99
                      b" SAT", b"K DH", b"ZO R", b"C SL", b"6RB ",
100
                      b" JCG", b"PN V", b"J PK", b"EK L", b"I LZ"]
101
    tblEncrySymbols = tblEncrySymbol[symbolIndex]
102
    decBuffer = b""
103
    index1 = 0
104
    for index2 in range(len(buffer)):
105
        boolEncryptChar = ((tblEncrySymbols[index1] != 32) &
106
                           (buffer[index2] != 0) &
107
                           (buffer[index2] != 255) &
108
                           (buffer[index2] != tblEncrySymbols[index1]) &
109
                           (buffer[index2] != (tblEncrySymbols[index1] ^ 255))
110
                           )
111
        if (boolEncryptChar):
112
            decByte = buffer[index2] ^ tblEncrySymbols[index1]
113
            decBuffer += decByte.to_bytes(1, 'big')
114
        else:
115
            decBuffer += buffer[index2].to_bytes(1, 'big')
116
        index1 = (index1 + 1) % 4
117
    return decBuffer
118

    
119

    
120
def _make_read_frame(addr, length):
121
    """Pack the info in the header format"""
122
    frame = _make_frame(b"\x52", addr, length)
123
    # Return the data
124
    return frame
125

    
126

    
127
def _make_frame(cmd, addr, length, data=""):
128
    """Pack the info in the header format"""
129
    frame = cmd+struct.pack(">i", addr)[2:]+struct.pack("b", length)
130
    # add the data if set
131
    if len(data) != 0:
132
        frame += data
133
    # return the data
134
    return frame
135

    
136

    
137
def _do_ident(radio):
138
    """Put the radio in PROGRAM mode & identify it"""
139
    radio.pipe.baudrate = radio.BAUDRATE
140
    radio.pipe.parity = "N"
141
    radio.pipe.timeout = STIMEOUT
142

    
143
    # Flush input buffer
144
    baofeng_common._clean_buffer(radio)
145

    
146
    # Ident radio
147
    magic = radio._magic
148
    baofeng_common._rawsend(radio, magic)
149
    ack = baofeng_common._rawrecv(radio, radio._magic_response_length)
150

    
151
    if not ack.startswith(radio._fingerprint):
152
        if ack:
153
            LOG.debug(repr(ack))
154
        raise errors.RadioError("Radio did not respond as expected (A)")
155

    
156
    return True
157

    
158

    
159
def _download(radio):
160
    """Get the memory map"""
161

    
162
    # Put radio in program mode and identify it
163
    _do_ident(radio)
164
    for index in range(len(radio._magics)):
165
        baofeng_common._rawsend(radio, radio._magics[index])
166
        baofeng_common._rawrecv(radio, radio._magicResponseLengths[index])
167

    
168
    data = b""
169

    
170
    # UI progress
171
    status = chirp_common.Status()
172
    status.cur = 0
173
    status.max = radio.MEM_TOTAL // radio.BLOCK_SIZE
174
    status.msg = "Cloning from radio..."
175
    radio.status_fn(status)
176

    
177
    for i in range(len(radio.MEM_SIZES)):
178
        MEM_SIZE = radio.MEM_SIZES[i]
179
        MEM_START = radio.MEM_STARTS[i]
180
        for addr in range(MEM_START, MEM_START + MEM_SIZE, radio.BLOCK_SIZE):
181
            frame = _make_read_frame(addr, radio.BLOCK_SIZE)
182
            # DEBUG
183
            LOG.debug("Frame=" + util.hexprint(frame))
184

    
185
            # Sending the read request
186
            baofeng_common._rawsend(radio, frame)
187

    
188
            # Now we read data
189
            d = baofeng_common._rawrecv(radio, radio.BLOCK_SIZE + 4)
190

    
191
            LOG.debug("Response Data= " + util.hexprint(d))
192
            d = _crypt(1, d[4:])
193

    
194
            # Aggregate the data
195
            data += d
196

    
197
            # UI Update
198
            status.cur = len(data) // radio.BLOCK_SIZE
199
            status.msg = "Cloning from radio..."
200
            radio.status_fn(status)
201
    data += bytes(radio.MODEL, 'utf-8')
202
    return data
203

    
204

    
205
def _upload(radio):
206
    # Put radio in program mode and identify it
207
    _do_ident(radio)
208
    for index in range(len(radio._magics)):
209
        baofeng_common._rawsend(radio, radio._magics[index])
210
        baofeng_common._rawrecv(radio, radio._magicResponseLengths[index])
211

    
212
    data = b""
213

    
214
    # UI progress
215
    status = chirp_common.Status()
216
    status.cur = 0
217
    status.max = radio.MEM_TOTAL // radio.BLOCK_SIZE
218
    status.msg = "Cloning from radio..."
219
    radio.status_fn(status)
220

    
221
    data_addr = 0x00
222
    for i in range(len(radio.MEM_SIZES)):
223
        MEM_SIZE = radio.MEM_SIZES[i]
224
        MEM_START = radio.MEM_STARTS[i]
225
        for addr in range(MEM_START, MEM_START + MEM_SIZE, radio.BLOCK_SIZE):
226
            data = radio.get_mmap()[data_addr:data_addr + radio.BLOCK_SIZE]
227
            data = _crypt(1, data)
228
            data_addr += radio.BLOCK_SIZE
229

    
230
            frame = _make_frame(b"W", addr, radio.BLOCK_SIZE, data)
231
            # DEBUG
232
            LOG.debug("Frame=" + util.hexprint(frame))
233

    
234
            # Sending the read request
235
            baofeng_common._rawsend(radio, frame)
236

    
237
            # receiving the response
238
            ack = baofeng_common._rawrecv(radio, 1)
239
            if ack != b"\x06":
240
                msg = "Bad ack writing block 0x%04x" % addr
241
                raise errors.RadioError(msg)
242

    
243
            # UI Update
244
            status.cur = data_addr // radio.BLOCK_SIZE
245
            status.msg = "Cloning to radio..."
246
            radio.status_fn(status)
247
    data += bytes(radio.MODEL, 'utf-8')
248
    return data
249

    
250

    
251
@directory.register
252
class UV17Pro(baofeng_common.BaofengCommonHT):
253
    """Baofeng UV-17Pro"""
254
    VENDOR = "Baofeng"
255
    MODEL = "UV-17Pro"
256
    NEEDS_COMPAT_SERIAL = False
257

    
258
    MEM_STARTS = [0x0000, 0x9000, 0xA000, 0xD000]
259
    MEM_SIZES = [0x8040, 0x0040, 0x02C0, 0x0040]
260

    
261
    MEM_TOTAL = 0x8380
262
    BLOCK_SIZE = 0x40
263
    STIMEOUT = 2
264
    BAUDRATE = 115200
265

    
266
    _gmrs = False
267
    _bw_shift = False
268
    _support_banknames = False
269

    
270
    _tri_band = True
271
    _fileid = []
272
    _magic = MSTRING_UV17L
273
    _magic_response_length = 1
274
    _fingerprint = b"\x06"
275
    _magics = [b"\x46", b"\x4d",
276
               b"\x53\x45\x4E\x44\x21\x05\x0D\x01\x01\x01\x04\x11\x08\x05" +
277
               b"\x0D\x0D\x01\x11\x0F\x09\x12\x09\x10\x04\x00"]
278
    _magicResponseLengths = [16, 15, 1]
279
    _fw_ver_start = 0x1EF0
280
    _recv_block_size = 0x40
281
    _mem_size = MEM_TOTAL
282
    _ack_block = True
283
    _aniid = True
284
    _vfoscan = False
285
    _has_gps = False
286
    _voxsw = False
287
    _pilot_tone = False
288
    _send_id_delay = False
289
    _skey2_short = False
290

    
291
    MODES = ["NFM", "FM"]
292
    VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
293
        "!@#$%^&*()+-=[]:\";'<>?,./"
294
    LENGTH_NAME = 12
295
    SKIP_VALUES = ["", "S"]
296
    DTCS_CODES = tuple(sorted(chirp_common.DTCS_CODES + (645,)))
297
    RXTX_CODES = ('Off', )
298
    for code in chirp_common.TONES:
299
        RXTX_CODES = (RXTX_CODES + (str(code), ))
300
    for code in DTCS_CODES:
301
        RXTX_CODES = (RXTX_CODES + ('D' + str(code) + 'N', ))
302
    for code in DTCS_CODES:
303
        RXTX_CODES = (RXTX_CODES + ('D' + str(code) + 'I', ))
304
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
305
                    chirp_common.PowerLevel("Low",  watts=1.00)]
306
    _airband = (108000000, 136000000)
307
    _vhf_range = (136000000, 174000000)
308
    _vhf2_range = (200000000, 260000000)
309
    _uhf_range = (400000000, 520000000)
310
    _uhf2_range = (350000000, 390000000)
311

    
312
    VALID_BANDS = [_vhf_range, _vhf2_range,
313
                   _uhf_range]
314
    PTTID_LIST = LIST_PTTID
315
    SCODE_LIST = LIST_SCODE
316

    
317
    MEM_FORMAT = """
318
    #seekto 0x0000;
319
    struct {
320
      lbcd rxfreq[4];
321
      lbcd txfreq[4];
322
      ul16 rxtone;
323
      ul16 txtone;
324
      u8 scode:8;
325
      u8 pttid:8;
326
      u8 lowpower:8;
327
      u8 unknown1:1,
328
         wide:1,
329
         sqmode:2,
330
         bcl:1,
331
         scan:1,
332
         unknown2:1,
333
         fhss:1;
334
      u8 unknown3:8;
335
      u8 unknown4:8;
336
      u8 unknown5:8;
337
      u8 unknown6:8;
338
      char name[12];
339
    } memory[1000];
340

    
341
    #seekto 0x8080;
342
    struct {
343
      u8 code[5];
344
      u8 unknown[1];
345
      u8 unused1:6,
346
         aniid:2;
347
      u8 dtmfon;
348
      u8 dtmfoff;
349
    } ani;
350

    
351
    #seekto 0x80A0;
352
    struct {
353
      u8 code[5];
354
      u8 name[10];
355
      u8 unused:8;
356
    } pttid[20];
357

    
358
    #seekto 0x8280;
359
    struct {
360
      char name1[12];
361
      u32 unknown1;
362
      char name2[12];
363
      u32 unknown2;
364
      char name3[12];
365
      u32 unknown3;
366
      char name4[12];
367
      u32 unknown4;
368
      char name5[12];
369
      u32 unknown5;
370
      char name6[12];
371
      u32 unknown6;
372
      char name7[12];
373
      u32 unknown7;
374
      char name8[12];
375
      u32 unknown8;
376
      char name9[12];
377
      u32 unknown9;
378
      char name10[12];
379
      u32 unknown10;
380
    } bank_names;
381

    
382
    struct vfo {
383
      u8 freq[8];
384
      ul16 rxtone;
385
      ul16 txtone;
386
      u8 unknown0;
387
      u8 bcl;
388
      u8 sftd:3,
389
         scode:5;
390
      u8 unknown1;
391
      u8 lowpower;
392
      u8 unknown2:1,
393
         wide:1,
394
         unknown3:5,
395
         fhss:1;
396
      u8 unknown4;
397
      u8 step;
398
      u8 offset[6];
399
      u8 unknown5[2];
400
      u8 sqmode;
401
      u8 unknown6[3];
402
    };
403

    
404
    #seekto 0x8000;
405
    struct {
406
      struct vfo a;
407
      struct vfo b;
408
    } vfo;
409

    
410
    #seekto 0x8040;
411
    struct {
412
      u8 squelch;
413
      u8 savemode;
414
      u8 vox;
415
      u8 backlight;
416
      u8 dualstandby;
417
      u8 tot;
418
      u8 beep;
419
      u8 voicesw;
420
      u8 voice;
421
      u8 sidetone;
422
      u8 scanmode;
423
      u8 pttid;
424
      u8 pttdly;
425
      u8 chadistype;
426
      u8 chbdistype;
427
      u8 bcl;
428
      u8 autolock;
429
      u8 alarmmode;
430
      u8 alarmtone;
431
      u8 unknown1;
432
      u8 tailclear;
433
      u8 rpttailclear;
434
      u8 rpttaildet;
435
      u8 roger;
436
      u8 unknown2;
437
      u8 fmenable;
438
      u8 chaworkmode:4,
439
         chbworkmode:4;
440
      u8 keylock;
441
      u8 powerondistype;
442
      u8 tone;
443
      u8 unknown4[2];
444
      u8 voxdlytime;
445
      u8 menuquittime;
446
      u8 unknown5[6];
447
      u8 totalarm;
448
      u8 unknown6[2];
449
      u8 ctsdcsscantype;
450
      ul16 vfoscanmin;
451
      ul16 vfoscanmax;
452
      u8 gpsw;
453
      u8 gpsmode;
454
      u8 unknown7[2];
455
      u8 key2short;
456
      u8 unknown8[2];
457
      u8 rstmenu;
458
      u8 unknown9;
459
      u8 hangup;
460
      u8 voxsw;
461
      u8 gpstimezone;
462
    } settings;
463
    """
464

    
465
    @classmethod
466
    def get_prompts(cls):
467
        rp = chirp_common.RadioPrompts()
468
        rp.experimental = \
469
            ('This driver is a beta version.\n'
470
             '\n'
471
             'Please save an unedited copy of your first successful\n'
472
             'download to a CHIRP Radio Images(*.img) file.'
473
             )
474
        rp.pre_download = _(
475
            "Follow these instructions to download your info:\n"
476
            "1 - Turn off your radio\n"
477
            "2 - Connect your interface cable\n"
478
            "3 - Turn on your radio\n"
479
            "4 - Do the download of your radio data\n")
480
        rp.pre_upload = _(
481
            "Follow this instructions to upload your info:\n"
482
            "1 - Turn off your radio\n"
483
            "2 - Connect your interface cable\n"
484
            "3 - Turn on your radio\n"
485
            "4 - Do the upload of your radio data\n")
486
        return rp
487

    
488
    def process_mmap(self):
489
        """Process the mem map into the mem object"""
490
        # make lines shorter for style check.
491
        check1 = (len(self._mmap) == self.MEM_TOTAL)
492
        check2 = (len(self._mmap) == self.MEM_TOTAL + len(self.MODEL))
493
        if (check1 or check2):
494
            self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap)
495
        else:
496
            raise errors.ImageDetectFailed('Image length mismatch.\n'
497
                                           'Try reloading the configuration'
498
                                           ' from the radio.')
499

    
500
    def get_settings(self):
501
        """Translate the bit in the mem_struct into settings in the UI"""
502
        _mem = self._memobj
503
        basic = RadioSettingGroup("basic", "Basic Settings")
504
        bank = RadioSettingGroup("bank", "Bank names")
505
        work = RadioSettingGroup("work", "VFO Mode Settings")
506
        dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings")
507
        top = RadioSettings(basic, bank, work, dtmfe)
508

    
509
        if _mem.settings.squelch > 0x05:
510
            val = 0x00
511
        else:
512
            val = _mem.settings.squelch
513
        rs = RadioSetting("settings.squelch", "Squelch",
514
                          RadioSettingValueList(
515
                              LIST_OFF1TO5, LIST_OFF1TO5[val]))
516
        basic.append(rs)
517

    
518
        rs = RadioSetting("settings.tot", "Timeout Timer",
519
                          RadioSettingValueList(
520
                              LIST_TIMEOUT, LIST_TIMEOUT[_mem.settings.tot]))
521
        basic.append(rs)
522

    
523
        rs = RadioSetting("settings.savemode", "Save Mode",
524
                          RadioSettingValueBoolean(_mem.settings.savemode))
525
        basic.append(rs)
526

    
527
        rs = RadioSetting("settings.totalarm", "Timeout Timer Alarm",
528
                          RadioSettingValueList(
529
                              LIST_TIMEOUT_ALARM,
530
                              LIST_TIMEOUT_ALARM[_mem.settings.totalarm]))
531
        basic.append(rs)
532

    
533
        rs = RadioSetting("settings.dualstandby", "Dual Watch",
534
                          RadioSettingValueBoolean(_mem.settings.dualstandby))
535
        basic.append(rs)
536

    
537
        if self._pilot_tone:
538
            rs = RadioSetting("settings.tone", "Pilot Tone",
539
                              RadioSettingValueList(
540
                                LIST_PILOT_TONE,
541
                                LIST_PILOT_TONE[_mem.settings.tone]))
542
            basic.append(rs)
543

    
544
        rs = RadioSetting("settings.sidetone", "Side Tone",
545
                          RadioSettingValueList(
546
                              LIST_SIDE_TONE,
547
                              LIST_SIDE_TONE[_mem.settings.sidetone]))
548
        basic.append(rs)
549

    
550
        rs = RadioSetting("settings.tailclear", "Tail Clear",
551
                          RadioSettingValueBoolean(_mem.settings.tailclear))
552
        basic.append(rs)
553

    
554
        rs = RadioSetting("settings.powerondistype", "Power On Display Type",
555
                          RadioSettingValueList(
556
                              LIST_POWERON_DISPLAY_TYPE,
557
                              LIST_POWERON_DISPLAY_TYPE[
558
                                  _mem.settings.powerondistype]))
559
        basic.append(rs)
560

    
561
        rs = RadioSetting("settings.beep", "Beep",
562
                          RadioSettingValueList(
563
                              LIST_BEEP, LIST_BEEP[_mem.settings.beep]))
564
        basic.append(rs)
565

    
566
        rs = RadioSetting("settings.roger", "Roger",
567
                          RadioSettingValueBoolean(_mem.settings.roger))
568
        basic.append(rs)
569

    
570
        rs = RadioSetting("settings.voice", "Language",
571
                          RadioSettingValueList(
572
                              LIST_LANGUAGE,
573
                              LIST_LANGUAGE[_mem.settings.voice]))
574
        basic.append(rs)
575

    
576
        rs = RadioSetting("settings.voicesw", "Enable Voice",
577
                          RadioSettingValueBoolean(_mem.settings.voicesw))
578
        basic.append(rs)
579

    
580
        rs = RadioSetting("settings.scanmode", "Scan Mode",
581
                          RadioSettingValueList(
582
                              LIST_SCANMODE,
583
                              LIST_SCANMODE[_mem.settings.scanmode]))
584
        basic.append(rs)
585

    
586
        rs = RadioSetting("settings.alarmmode", "Alarm Mode",
587
                          RadioSettingValueList(
588
                              LIST_ALARMMODE,
589
                              LIST_ALARMMODE[_mem.settings.alarmmode]))
590
        basic.append(rs)
591

    
592
        rs = RadioSetting("settings.alarmtone", "Sound Alarm",
593
                          RadioSettingValueBoolean(_mem.settings.alarmtone))
594
        basic.append(rs)
595

    
596
        rs = RadioSetting("settings.keylock", "Key Lock",
597
                          RadioSettingValueBoolean(_mem.settings.keylock))
598
        basic.append(rs)
599

    
600
        rs = RadioSetting("settings.fmenable", "Disable FM radio",
601
                          RadioSettingValueBoolean(_mem.settings.fmenable))
602
        basic.append(rs)
603

    
604
        rs = RadioSetting("settings.autolock", "Key Auto Lock",
605
                          RadioSettingValueBoolean(_mem.settings.autolock))
606
        basic.append(rs)
607

    
608
        rs = RadioSetting("settings.menuquittime", "Menu Quit Timer",
609
                          RadioSettingValueList(
610
                              LIST_MENU_QUIT_TIME,
611
                              LIST_MENU_QUIT_TIME[
612
                                  _mem.settings.menuquittime]))
613
        basic.append(rs)
614

    
615
        rs = RadioSetting("settings.backlight", "Backlight Timer",
616
                          RadioSettingValueList(
617
                              LIST_BACKLIGHT_TIMER,
618
                              LIST_BACKLIGHT_TIMER[_mem.settings.backlight]))
619
        basic.append(rs)
620

    
621
        if self._send_id_delay:
622
            rs = RadioSetting("settings.pttdly", "Send ID Delay",
623
                              RadioSettingValueList(
624
                                LIST_ID_DELAY,
625
                                LIST_ID_DELAY[_mem.settings.pttdly]))
626
            basic.append(rs)
627

    
628
        rs = RadioSetting("settings.ctsdcsscantype", "QT Save Mode",
629
                          RadioSettingValueList(
630
                              LIST_QT_SAVEMODE,
631
                              LIST_QT_SAVEMODE[_mem.settings.ctsdcsscantype]))
632
        basic.append(rs)
633

    
634
        def getKey2shortIndex(value):
635
            if value == 0x07:
636
                return 0
637
            if value == 0x1C:
638
                return 1
639
            if value == 0x1D:
640
                return 2
641
            if value == 0x2D:
642
                return 3
643
            return 0
644

    
645
        def apply_Key2short(setting, obj):
646
            val = str(setting.value)
647
            if val == "FM":
648
                obj.key2short = 0x07
649
            if val == "Scan":
650
                obj.key2short = 0x1C
651
            if val == "Search":
652
                obj.key2short = 0x1D
653
            if val == "Vox":
654
                obj.key2short = 0x2D
655

    
656
        if self._skey2_short:
657
            rs = RadioSetting("settings.key2short", "Skey2 Short",
658
                              RadioSettingValueList(
659
                                LIST_SKEY2_SHORT,
660
                                LIST_SKEY2_SHORT[
661
                                    getKey2shortIndex(
662
                                        _mem.settings.key2short)]))
663
            rs.set_apply_callback(apply_Key2short, _mem.settings)
664
            basic.append(rs)
665

    
666
        rs = RadioSetting("settings.chadistype", "Channel A display type",
667
                          RadioSettingValueList(
668
                              LIST_MODE, LIST_MODE[_mem.settings.chadistype]))
669
        basic.append(rs)
670

    
671
        rs = RadioSetting("settings.chaworkmode", "Channel A work mode",
672
                          RadioSettingValueList(
673
                              LIST_WORKMODE,
674
                              LIST_WORKMODE[_mem.settings.chaworkmode]))
675
        basic.append(rs)
676

    
677
        rs = RadioSetting("settings.chbdistype", "Channel B display type",
678
                          RadioSettingValueList(
679
                              LIST_MODE, LIST_MODE[_mem.settings.chbdistype]))
680
        basic.append(rs)
681

    
682
        rs = RadioSetting("settings.chbworkmode", "Channel B work mode",
683
                          RadioSettingValueList(
684
                              LIST_WORKMODE,
685
                              LIST_WORKMODE[_mem.settings.chbworkmode]))
686
        basic.append(rs)
687

    
688
        rs = RadioSetting("settings.rpttailclear", "Rpt Tail Clear",
689
                          RadioSettingValueList(
690
                              LIST_RPT_TAIL_CLEAR,
691
                              LIST_RPT_TAIL_CLEAR[
692
                                  _mem.settings.rpttailclear]))
693
        basic.append(rs)
694

    
695
        rs = RadioSetting("settings.rpttaildet", "Rpt Tail Delay",
696
                          RadioSettingValueList(
697
                              LIST_RPT_TAIL_CLEAR,
698
                              LIST_RPT_TAIL_CLEAR[_mem.settings.rpttaildet]))
699
        basic.append(rs)
700

    
701
        rs = RadioSetting("settings.rstmenu", "Enable Menu Rst",
702
                          RadioSettingValueBoolean(_mem.settings.rstmenu))
703
        basic.append(rs)
704

    
705
        if self._voxsw:
706
            rs = RadioSetting("settings.voxsw", "Vox Switch",
707
                              RadioSettingValueBoolean(_mem.settings.voxsw))
708
            basic.append(rs)
709

    
710
            rs = RadioSetting("settings.vox", "Vox Level",
711
                              RadioSettingValueList(
712
                                LIST_VOX_LEVEL_ALT,
713
                                LIST_VOX_LEVEL_ALT[_mem.settings.vox]))
714
            basic.append(rs)
715
        else:
716
            rs = RadioSetting("settings.vox", "Vox Level",
717
                              RadioSettingValueList(
718
                                LIST_VOX_LEVEL,
719
                                LIST_VOX_LEVEL[_mem.settings.vox]))
720
            basic.append(rs)
721

    
722
        rs = RadioSetting("settings.voxdlytime", "Vox Delay Time",
723
                          RadioSettingValueList(
724
                              LIST_VOX_DELAY_TIME,
725
                              LIST_VOX_DELAY_TIME[_mem.settings.voxdlytime]))
726
        basic.append(rs)
727

    
728
        if self._has_gps:
729
            rs = RadioSetting("settings.gpsw", "GPS On",
730
                              RadioSettingValueBoolean(_mem.settings.gpsw))
731
            basic.append(rs)
732

    
733
            rs = RadioSetting("settings.gpsmode", "GPS Mode",
734
                              RadioSettingValueList(
735
                                LIST_GPS_MODE,
736
                                LIST_GPS_MODE[_mem.settings.gpsmode]))
737
            basic.append(rs)
738

    
739
            rs = RadioSetting("settings.gpstimezone", "GPS Timezone",
740
                              RadioSettingValueList(
741
                                LIST_GPS_TIMEZONE,
742
                                LIST_GPS_TIMEZONE[_mem.settings.gpstimezone]))
743
            basic.append(rs)
744

    
745
        def _filterName(name, zone):
746
            fname = ""
747
            charset = chirp_common.CHARSET_ASCII
748
            for char in name:
749
                if ord(str(char)) == 255:
750
                    break
751
                if str(char) not in charset:
752
                    char = "X"
753
                fname += str(char)
754
            if fname == "XXXXXX":
755
                fname = "ZONE" + zone
756
            return fname
757

    
758
        if self._support_banknames:
759
            _zone = "01"
760
            _msg = _mem.bank_names
761
            rs = RadioSetting("bank_names.name1", "Bank name 1",
762
                              RadioSettingValueString(
763
                                0, 12, _filterName(_msg.name1, _zone)))
764
            bank.append(rs)
765

    
766
            _zone = "02"
767
            _msg = _mem.bank_names
768
            rs = RadioSetting("bank_names.name2", "Bank name 2",
769
                              RadioSettingValueString(
770
                                0, 12, _filterName(_msg.name2, _zone)))
771

    
772
            bank.append(rs)
773
            _zone = "03"
774
            _msg = _mem.bank_names
775
            rs = RadioSetting("bank_names.name3", "Bank name 3",
776
                              RadioSettingValueString(
777
                                0, 12, _filterName(_msg.name3, _zone)))
778
            bank.append(rs)
779

    
780
            _zone = "04"
781
            _msg = _mem.bank_names
782
            rs = RadioSetting("bank_names.name4", "Bank name 4",
783
                              RadioSettingValueString(
784
                                0, 12, _filterName(_msg.name4, _zone)))
785
            bank.append(rs)
786

    
787
            _zone = "05"
788
            _msg = _mem.bank_names
789
            rs = RadioSetting("bank_names.name5", "Bank name 5",
790
                              RadioSettingValueString(
791
                                0, 12, _filterName(_msg.name5, _zone)))
792
            bank.append(rs)
793

    
794
            _zone = "06"
795
            _msg = _mem.bank_names
796
            rs = RadioSetting("bank_names.name6", "Bank name 6",
797
                              RadioSettingValueString(
798
                                0, 12, _filterName(_msg.name6, _zone)))
799
            bank.append(rs)
800

    
801
            _zone = "07"
802
            _msg = _mem.bank_names
803
            rs = RadioSetting("bank_names.name7", "Bank name 7",
804
                              RadioSettingValueString(
805
                                0, 12, _filterName(_msg.name7, _zone)))
806
            bank.append(rs)
807

    
808
            _zone = "08"
809
            _msg = _mem.bank_names
810
            rs = RadioSetting("bank_names.name8", "Bank name 8",
811
                              RadioSettingValueString(
812
                                0, 12, _filterName(_msg.name8, _zone)))
813
            bank.append(rs)
814

    
815
            _zone = "09"
816
            _msg = _mem.bank_names
817
            rs = RadioSetting("bank_names.name9", "Bank name 9",
818
                              RadioSettingValueString(
819
                                0, 12, _filterName(_msg.name9, _zone)))
820
            bank.append(rs)
821

    
822
            _zone = "10"
823
            _msg = _mem.bank_names
824
            rs = RadioSetting("bank_names.name10", "Bank name 10",
825
                              RadioSettingValueString(
826
                                0, 12, _filterName(_msg.name10, _zone)))
827
            bank.append(rs)
828

    
829
        _codeobj = self._memobj.ani.code
830
        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
831
        val = RadioSettingValueString(0, 5, _code, False)
832
        val.set_charset(DTMF_CHARS)
833
        rs = RadioSetting("ani.code", "ANI Code", val)
834

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

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

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

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

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

    
882
        if self._aniid:
883
            rs = RadioSetting("ani.aniid", "When to send ANI ID",
884
                              RadioSettingValueList(LIST_PTTID,
885
                                                    LIST_PTTID[
886
                                                        _mem.ani.aniid]))
887
            dtmfe.append(rs)
888

    
889
        rs = RadioSetting("settings.hangup", "Hang-up time",
890
                          RadioSettingValueList(LIST_HANGUPTIME,
891
                                                LIST_HANGUPTIME[
892
                                                    _mem.settings.hangup]))
893
        dtmfe.append(rs)
894

    
895
        def convert_bytes_to_freq(bytes):
896
            real_freq = 0
897
            for byte in bytes:
898
                real_freq = (real_freq * 10) + byte
899
            return chirp_common.format_freq(real_freq * 10)
900

    
901
        def my_validate(value):
902
            value = chirp_common.parse_freq(value)
903
            freqOk = False
904
            for band in self.VALID_BANDS:
905
                if value > band[0] and value < band[1]:
906
                    freqOk = True
907
            if not freqOk:
908
                raise InvalidValueError("Invalid frequency!")
909
            return chirp_common.format_freq(value)
910

    
911
        def apply_freq(setting, obj):
912
            value = chirp_common.parse_freq(str(setting.value)) / 10
913
            for i in range(7, -1, -1):
914
                obj.freq[i] = value % 10
915
                value /= 10
916

    
917
        val1a = RadioSettingValueString(0, 10,
918
                                        convert_bytes_to_freq(
919
                                            _mem.vfo.a.freq))
920
        val1a.set_validate_callback(my_validate)
921
        rs = RadioSetting("vfo.a.freq", "VFO A Frequency", val1a)
922
        rs.set_apply_callback(apply_freq, _mem.vfo.a)
923
        work.append(rs)
924

    
925
        rs = RadioSetting("vfo.a.sftd", "VFO A Offset dir",
926
                          RadioSettingValueList(
927
                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.a.sftd]))
928
        work.append(rs)
929

    
930
        def convert_bytes_to_offset(bytes):
931
            real_offset = 0
932
            for byte in bytes:
933
                real_offset = (real_offset * 10) + byte
934
            return chirp_common.format_freq(real_offset * 1000)
935

    
936
        def apply_offset(setting, obj):
937
            value = chirp_common.parse_freq(str(setting.value)) / 1000
938
            for i in range(5, -1, -1):
939
                obj.offset[i] = value % 10
940
                value /= 10
941

    
942
        val1a = RadioSettingValueString(
943
                    0, 10, convert_bytes_to_offset(_mem.vfo.a.offset))
944
        rs = RadioSetting("vfo.a.offset",
945
                          "VFO A Offset", val1a)
946
        rs.set_apply_callback(apply_offset, _mem.vfo.a)
947
        work.append(rs)
948

    
949
        def apply_txpower_listvalue(setting, obj):
950
            LOG.debug("Setting value: " + str(
951
                      setting.value) + " from list")
952
            val = str(setting.value)
953
            index = TXP_CHOICES.index(val)
954
            val = TXP_VALUES[index]
955
            obj.set_value(val)
956

    
957
        rs = RadioSetting("vfo.a.lowpower", "VFO A Power",
958
                          RadioSettingValueList(
959
                                LIST_TXPOWER,
960
                                LIST_TXPOWER[
961
                                    min(_mem.vfo.a.lowpower, 0x02)]
962
                                            ))
963
        work.append(rs)
964

    
965
        rs = RadioSetting("vfo.a.wide", "VFO A Bandwidth",
966
                          RadioSettingValueList(
967
                              LIST_BANDWIDTH,
968
                              LIST_BANDWIDTH[_mem.vfo.a.wide]))
969
        work.append(rs)
970

    
971
        rs = RadioSetting("vfo.a.scode", "VFO A S-CODE",
972
                          RadioSettingValueList(
973
                              LIST_SCODE,
974
                              LIST_SCODE[_mem.vfo.a.scode]))
975
        work.append(rs)
976

    
977
        rs = RadioSetting("vfo.a.step", "VFO A Tuning Step",
978
                          RadioSettingValueList(
979
                              LIST_STEP, LIST_STEP[_mem.vfo.a.step]))
980
        work.append(rs)
981

    
982
        rs = RadioSetting("vfo.a.fhss", "VFO A FHSS",
983
                          RadioSettingValueBoolean(_mem.vfo.a.fhss))
984
        work.append(rs)
985

    
986
        def getToneIndex(tone):
987
            if tone in [0, 0xFFFF]:
988
                index = 0
989
            elif tone >= 0x0258:
990
                index = self.RXTX_CODES.index(str(tone / 10.0))
991
            elif tone <= 0x0258:
992
                index = 50 + tone
993
                if tone > 0x69:
994
                    index = tone - 0x6A + 156
995
            return self.RXTX_CODES[index]
996

    
997
        def apply_rxtone(setting, obj):
998
            index = self.RXTX_CODES.index(str(setting.value))
999
            if index > 156:
1000
                obj.rxtone = index - 156 + 0x6A
1001
            elif index > 50:
1002
                obj.rxtone = index - 50
1003
            elif index == 0:
1004
                obj.rxtone = 0
1005
            else:
1006
                obj.rxtone = int(float(setting.value)*10)
1007

    
1008
        def apply_txtone(setting, obj):
1009
            index = self.RXTX_CODES.index(str(setting.value))
1010
            if index > 156:
1011
                obj.txtone = index - 156 + 0x6A
1012
            elif index > 50:
1013
                obj.txtone = index - 50
1014
            elif index == 0:
1015
                obj.txtone = 0
1016
            else:
1017
                obj.txtone = int(float(setting.value)*10)
1018

    
1019
        rs = RadioSetting("vfo.a.rxtone", "VFA A RX QT/DQT",
1020
                          RadioSettingValueList(self.RXTX_CODES,
1021
                                                getToneIndex(
1022
                                                    _mem.vfo.a.rxtone)))
1023
        rs.set_apply_callback(apply_rxtone, _mem.vfo.a)
1024
        work.append(rs)
1025

    
1026
        rs = RadioSetting("vfo.a.txtone", "VFA A TX QT/DQT",
1027
                          RadioSettingValueList(self.RXTX_CODES,
1028
                                                getToneIndex(
1029
                                                    _mem.vfo.a.txtone)))
1030
        rs.set_apply_callback(apply_txtone, _mem.vfo.a)
1031
        work.append(rs)
1032

    
1033
        val1b = RadioSettingValueString(0, 10,
1034
                                        convert_bytes_to_freq(
1035
                                            _mem.vfo.b.freq))
1036
        val1b.set_validate_callback(my_validate)
1037
        rs = RadioSetting("vfo.b.freq", "VFO B Frequency", val1b)
1038
        rs.set_apply_callback(apply_freq, _mem.vfo.b)
1039
        work.append(rs)
1040

    
1041
        rs = RadioSetting("vfo.b.sftd", "VFO B Offset dir",
1042
                          RadioSettingValueList(
1043
                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.b.sftd]))
1044
        work.append(rs)
1045

    
1046
        val1b = RadioSettingValueString(
1047
                    0, 10, convert_bytes_to_offset(_mem.vfo.b.offset))
1048
        rs = RadioSetting("vfo.b.offset",
1049
                          "VFO B Offset", val1b)
1050
        rs.set_apply_callback(apply_offset, _mem.vfo.b)
1051
        work.append(rs)
1052

    
1053
        rs = RadioSetting("vfo.b.lowpower", "VFO B Power",
1054
                          RadioSettingValueList(
1055
                                LIST_TXPOWER,
1056
                                LIST_TXPOWER[min(_mem.vfo.b.lowpower, 0x02)]
1057
                                            ))
1058
        work.append(rs)
1059

    
1060
        rs = RadioSetting("vfo.b.wide", "VFO B Bandwidth",
1061
                          RadioSettingValueList(
1062
                              LIST_BANDWIDTH,
1063
                              LIST_BANDWIDTH[_mem.vfo.b.wide]))
1064
        work.append(rs)
1065

    
1066
        rs = RadioSetting("vfo.b.scode", "VFO B S-CODE",
1067
                          RadioSettingValueList(
1068
                              LIST_SCODE,
1069
                              LIST_SCODE[_mem.vfo.b.scode]))
1070
        work.append(rs)
1071

    
1072
        rs = RadioSetting("vfo.b.step", "VFO B Tuning Step",
1073
                          RadioSettingValueList(
1074
                              LIST_STEP, LIST_STEP[_mem.vfo.b.step]))
1075
        work.append(rs)
1076

    
1077
        rs = RadioSetting("vfo.b.fhss", "VFO B FHSS",
1078
                          RadioSettingValueBoolean(_mem.vfo.b.fhss))
1079
        work.append(rs)
1080

    
1081
        rs = RadioSetting("vfo.b.rxtone", "VFA B RX QT/DQT",
1082
                          RadioSettingValueList(self.RXTX_CODES,
1083
                                                getToneIndex(
1084
                                                    _mem.vfo.b.rxtone)))
1085
        rs.set_apply_callback(apply_rxtone, _mem.vfo.b)
1086
        work.append(rs)
1087

    
1088
        rs = RadioSetting("vfo.b.txtone", "VFA B TX QT/DQT",
1089
                          RadioSettingValueList(self.RXTX_CODES,
1090
                                                getToneIndex(
1091
                                                    _mem.vfo.b.txtone)))
1092
        rs.set_apply_callback(apply_txtone, _mem.vfo.b)
1093
        work.append(rs)
1094

    
1095
        if self._vfoscan:
1096
            def scan_validate(value):
1097
                freqOk = False
1098
                for band in self.VALID_BANDS:
1099
                    minBand = (band[0]/1000000)
1100
                    maxBand = (band[1]/1000000)
1101
                    if value >= minBand and value <= maxBand:
1102
                        freqOk = True
1103
                if not freqOk:
1104
                    raise InvalidValueError("Invalid frequency!")
1105
                return value
1106

    
1107
            scanMin = RadioSettingValueInteger(0, 800,
1108
                                               _mem.settings.vfoscanmin)
1109
            scanMin.set_validate_callback(scan_validate)
1110
            rs = RadioSetting("settings.vfoscanmin", "VFO scan range minimum",
1111
                              scanMin)
1112
            work.append(rs)
1113

    
1114
            scanMax = RadioSettingValueInteger(0, 800,
1115
                                               _mem.settings.vfoscanmax)
1116
            scanMax.set_validate_callback(scan_validate)
1117
            rs = RadioSetting("settings.vfoscanmax", "VFO scan range maximum",
1118
                              scanMax)
1119
            work.append(rs)
1120

    
1121
        rs = RadioSetting("settings.bcl", "BCL",
1122
                          RadioSettingValueBoolean(_mem.settings.bcl))
1123
        work.append(rs)
1124

    
1125
        rs = RadioSetting("settings.pttid", "PTT ID",
1126
                          RadioSettingValueList(self.PTTID_LIST,
1127
                                                self.PTTID_LIST[
1128
                                                    _mem.settings.pttid]))
1129
        work.append(rs)
1130

    
1131
        return top
1132

    
1133
    def sync_in(self):
1134
        """Download from radio"""
1135
        try:
1136
            data = _download(self)
1137
        except errors.RadioError:
1138
            # Pass through any real errors we raise
1139
            raise
1140
        except Exception:
1141
            # If anything unexpected happens, make sure we raise
1142
            # a RadioError and log the problem
1143
            LOG.exception('Unexpected error during download')
1144
            raise errors.RadioError('Unexpected error communicating '
1145
                                    'with the radio')
1146
        self._mmap = memmap.MemoryMapBytes(data)
1147

    
1148
        self.process_mmap()
1149

    
1150
    def sync_out(self):
1151
        """Upload to radio"""
1152
        try:
1153
            _upload(self)
1154
        except errors.RadioError:
1155
            raise
1156
        except Exception:
1157
            # If anything unexpected happens, make sure we raise
1158
            # a RadioError and log the problem
1159
            LOG.exception('Unexpected error during upload')
1160
            raise errors.RadioError('Unexpected error communicating '
1161
                                    'with the radio')
1162

    
1163
    def get_features(self):
1164
        """Get the radio's features"""
1165

    
1166
        rf = chirp_common.RadioFeatures()
1167
        rf.has_settings = True
1168
        rf.has_bank = False
1169
        rf.has_tuning_step = False
1170
        rf.can_odd_split = True
1171
        rf.has_name = True
1172
        rf.has_offset = True
1173
        rf.has_mode = True
1174
        rf.has_dtcs = True
1175
        rf.has_rx_dtcs = True
1176
        rf.has_dtcs_polarity = True
1177
        rf.has_ctone = True
1178
        rf.has_cross = True
1179
        rf.valid_modes = self.MODES
1180
        rf.valid_characters = self.VALID_CHARS
1181
        rf.valid_name_length = self.LENGTH_NAME
1182
        if self._gmrs:
1183
            rf.valid_duplexes = ["", "+", "off"]
1184
        else:
1185
            rf.valid_duplexes = ["", "-", "+", "split", "off"]
1186
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
1187
        rf.valid_cross_modes = [
1188
            "Tone->Tone",
1189
            "DTCS->",
1190
            "->DTCS",
1191
            "Tone->DTCS",
1192
            "DTCS->Tone",
1193
            "->Tone",
1194
            "DTCS->DTCS"]
1195
        rf.valid_skips = self.SKIP_VALUES
1196
        rf.valid_dtcs_codes = self.DTCS_CODES
1197
        rf.memory_bounds = (0, 999)
1198
        rf.valid_power_levels = self.POWER_LEVELS
1199
        rf.valid_bands = self.VALID_BANDS
1200
        rf.valid_tuning_steps = STEPS
1201

    
1202
        return rf
1203

    
1204
    def get_memory(self, number):
1205
        _mem = self._memobj.memory[number]
1206

    
1207
        mem = chirp_common.Memory()
1208
        mem.number = number
1209

    
1210
        if _mem.get_raw()[0] == 255:
1211
            mem.empty = True
1212
            return mem
1213

    
1214
        mem.freq = int(_mem.rxfreq) * 10
1215

    
1216
        if self._is_txinh(_mem):
1217
            # TX freq not set
1218
            mem.duplex = "off"
1219
            mem.offset = 0
1220
        else:
1221
            # TX freq set
1222
            offset = (int(_mem.txfreq) * 10) - mem.freq
1223
            if offset != 0:
1224
                if baofeng_common._split(self.get_features(), mem.freq, int(
1225
                          _mem.txfreq) * 10):
1226
                    mem.duplex = "split"
1227
                    mem.offset = int(_mem.txfreq) * 10
1228
                elif offset < 0:
1229
                    mem.offset = abs(offset)
1230
                    mem.duplex = "-"
1231
                elif offset > 0:
1232
                    mem.offset = offset
1233
                    mem.duplex = "+"
1234
            else:
1235
                mem.offset = 0
1236

    
1237
        for char in _mem.name:
1238
            if str(char) == "\xFF":
1239
                char = " "  # The OEM software may have 0xFF mid-name
1240
            mem.name += str(char)
1241
        mem.name = mem.name.rstrip()
1242

    
1243
        if not _mem.scan:
1244
            mem.skip = "S"
1245

    
1246
        levels = self.POWER_LEVELS
1247
        try:
1248
            mem.power = levels[_mem.lowpower]
1249
        except IndexError:
1250
            mem.power = levels[0]
1251

    
1252
        mem.mode = _mem.wide and "NFM" or "FM"
1253

    
1254
        dtcs_pol = ["N", "N"]
1255

    
1256
        if _mem.txtone in [0, 0xFFFF]:
1257
            txmode = ""
1258
        elif _mem.txtone >= 0x0258:
1259
            txmode = "Tone"
1260
            mem.rtone = int(_mem.txtone) / 10.0
1261
        elif _mem.txtone <= 0x0258:
1262
            txmode = "DTCS"
1263
            if _mem.txtone > 0x69:
1264
                index = _mem.txtone - 0x6A
1265
                dtcs_pol[0] = "R"
1266
            else:
1267
                index = _mem.txtone - 1
1268
            mem.dtcs = self.DTCS_CODES[index]
1269
        else:
1270
            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
1271

    
1272
        if _mem.rxtone in [0, 0xFFFF]:
1273
            rxmode = ""
1274
        elif _mem.rxtone >= 0x0258:
1275
            rxmode = "Tone"
1276
            mem.ctone = int(_mem.rxtone) / 10.0
1277
        elif _mem.rxtone <= 0x0258:
1278
            rxmode = "DTCS"
1279
            if _mem.rxtone >= 0x6A:
1280
                index = _mem.rxtone - 0x6A
1281
                dtcs_pol[1] = "R"
1282
            else:
1283
                index = _mem.rxtone - 1
1284
            mem.rx_dtcs = self.DTCS_CODES[index]
1285
        else:
1286
            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
1287

    
1288
        if txmode == "Tone" and not rxmode:
1289
            mem.tmode = "Tone"
1290
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
1291
            mem.tmode = "TSQL"
1292
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
1293
            mem.tmode = "DTCS"
1294
        elif rxmode or txmode:
1295
            mem.tmode = "Cross"
1296
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
1297

    
1298
        mem.dtcs_polarity = "".join(dtcs_pol)
1299

    
1300
        mem.extra = RadioSettingGroup("Extra", "extra")
1301

    
1302
        rs = RadioSetting("bcl", "BCL",
1303
                          RadioSettingValueBoolean(_mem.bcl))
1304
        mem.extra.append(rs)
1305

    
1306
        if (_mem.pttid != 0xFF):
1307
            rs = RadioSetting("pttid", "PTT ID",
1308
                              RadioSettingValueList(self.PTTID_LIST,
1309
                                                    self.PTTID_LIST[
1310
                                                        _mem.pttid]))
1311
            mem.extra.append(rs)
1312

    
1313
        if (_mem.scode != 0xFF):
1314
            rs = RadioSetting("scode", "S-CODE",
1315
                              RadioSettingValueList(self.SCODE_LIST,
1316
                                                    self.SCODE_LIST[
1317
                                                        _mem.scode]))
1318
            mem.extra.append(rs)
1319

    
1320
        return mem
1321

    
1322
    def set_memory(self, mem):
1323
        _mem = self._memobj.memory[mem.number]
1324

    
1325
        _mem.set_raw("\x00"*16 + "\xff" * 16)
1326

    
1327
        if mem.empty:
1328
            _mem.set_raw("\xff" * 32)
1329
            return
1330

    
1331
        _mem.rxfreq = mem.freq / 10
1332

    
1333
        if mem.duplex == "off":
1334
            for i in range(0, 4):
1335
                _mem.txfreq[i].set_raw("\xFF")
1336
        elif mem.duplex == "split":
1337
            _mem.txfreq = mem.offset / 10
1338
        elif mem.duplex == "+":
1339
            _mem.txfreq = (mem.freq + mem.offset) / 10
1340
        elif mem.duplex == "-":
1341
            _mem.txfreq = (mem.freq - mem.offset) / 10
1342
        else:
1343
            _mem.txfreq = mem.freq / 10
1344

    
1345
        _namelength = self.get_features().valid_name_length
1346
        for i in range(_namelength):
1347
            try:
1348
                _mem.name[i] = mem.name[i]
1349
            except IndexError:
1350
                _mem.name[i] = "\xFF"
1351

    
1352
        rxmode = txmode = ""
1353
        if mem.tmode == "Tone":
1354
            _mem.txtone = int(mem.rtone * 10)
1355
            _mem.rxtone = 0
1356
        elif mem.tmode == "TSQL":
1357
            _mem.txtone = int(mem.ctone * 10)
1358
            _mem.rxtone = int(mem.ctone * 10)
1359
        elif mem.tmode == "DTCS":
1360
            rxmode = txmode = "DTCS"
1361
            _mem.txtone = self.DTCS_CODES.index(mem.dtcs) + 1
1362
            _mem.rxtone = self.DTCS_CODES.index(mem.dtcs) + 1
1363
        elif mem.tmode == "Cross":
1364
            txmode, rxmode = mem.cross_mode.split("->", 1)
1365
            if txmode == "Tone":
1366
                _mem.txtone = int(mem.rtone * 10)
1367
            elif txmode == "DTCS":
1368
                _mem.txtone = self.DTCS_CODES.index(mem.dtcs) + 1
1369
            else:
1370
                _mem.txtone = 0
1371
            if rxmode == "Tone":
1372
                _mem.rxtone = int(mem.ctone * 10)
1373
            elif rxmode == "DTCS":
1374
                _mem.rxtone = self.DTCS_CODES.index(mem.rx_dtcs) + 1
1375
            else:
1376
                _mem.rxtone = 0
1377
        else:
1378
            _mem.rxtone = 0
1379
            _mem.txtone = 0
1380

    
1381
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
1382
            _mem.txtone += 0x69
1383
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
1384
            _mem.rxtone += 0x69
1385

    
1386
        _mem.scan = mem.skip != "S"
1387
        _mem.wide = mem.mode == "NFM"
1388

    
1389
        if mem.power:
1390
            _mem.lowpower = self.POWER_LEVELS.index(mem.power)
1391
        else:
1392
            _mem.lowpower = 0
1393

    
1394
        # extra settings
1395
        if len(mem.extra) > 0:
1396
            # there are setting, parse
1397
            for setting in mem.extra:
1398
                if setting.get_name() == "scode":
1399
                    setattr(_mem, setting.get_name(), str(int(setting.value)))
1400
                else:
1401
                    setattr(_mem, setting.get_name(), setting.value)
1402
        else:
1403
            # there are no extra settings, load defaults
1404
            _mem.bcl = 0
1405
            _mem.pttid = 0
1406
            _mem.scode = 0
1407

    
1408

    
1409
@directory.register
1410
class UV17ProGPS(UV17Pro):
1411
    VENDOR = "Baofeng"
1412
    MODEL = "UV-17ProGPS"
1413
    _support_banknames = True
1414
    _magic = MSTRING_UV17PROGPS
1415
    _magics = [b"\x46", b"\x4d", b"\x53\x45\x4E\x44\x21\x05\x0D\x01\x01" +
1416
               b"\x01\x04\x11\x08\x05\x0D\x0D\x01\x11\x0F\x09\x12\x09" +
1417
               b"\x10\x04\x00"]
1418
    _magicResponseLengths = [16, 7, 1]
1419
    _aniid = False
1420
    _vfoscan = True
1421
    _has_gps = True
1422
    _voxsw = True
1423
    _pilot_tone = True
1424
    _send_id_delay = True
1425
    _skey2_short = True
1426
    VALID_BANDS = [UV17Pro._airband, UV17Pro._vhf_range, UV17Pro._vhf2_range,
1427
                   UV17Pro._uhf_range, UV17Pro._uhf2_range]
(17-17/17)