Project

General

Profile

New Model #10648 » baofeng_uv17Pro.py

Sander van der Wel, 11/16/2023 12:05 PM

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

    
19
import logging
20

    
21
from chirp import chirp_common, directory, memmap
22
from chirp import bitwise
23
from chirp.settings import RadioSettingGroup, RadioSetting, \
24
    RadioSettingValueBoolean, RadioSettingValueList, \
25
    RadioSettingValueString, RadioSettingValueInteger, \
26
    RadioSettingValueFloat, RadioSettings, \
27
    InvalidValueError, RadioSettingValue
28
import time
29
import struct
30
import logging
31
from chirp import errors, util
32

    
33
LOG = logging.getLogger(__name__)
34

    
35
# #### MAGICS #########################################################
36

    
37
# Baofeng UV-17L magic string
38
MSTRING_UV17L = b"PROGRAMBFNORMALU"
39
MSTRING_UV17PROGPS = b"PROGRAMCOLORPROU"
40

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

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

    
89
TXP_CHOICES = ["High", "Low"]
90
TXP_VALUES = [0x00, 0x02]
91

    
92
STIMEOUT = 1.5
93

    
94
def model_match(cls, data):
95
    """Match the opened image to the correct version"""
96
    return data[cls.MEM_TOTAL:] == bytes(cls.MODEL, 'utf-8')
97

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

    
118
def _clean_buffer(radio):
119
    radio.pipe.timeout = 0.005
120
    junk = radio.pipe.read(256)
121
    radio.pipe.timeout = STIMEOUT
122
    if junk:
123
        LOG.debug("Got %i bytes of junk before starting" % len(junk))
124

    
125

    
126
def _rawrecv(radio, amount):
127
    """Raw read from the radio device"""
128
    data = ""
129
    try:
130
        data = radio.pipe.read(amount)
131
    except:
132
        msg = "Generic error reading data from radio; check your cable."
133
        raise errors.RadioError(msg)
134

    
135
    if len(data) != amount:
136
        msg = "Error reading data from radio: not the amount of data we want."
137
        raise errors.RadioError(msg)
138

    
139
    return data
140

    
141

    
142
def _rawsend(radio, data):
143
    """Raw send to the radio device"""
144
    try:
145
        #print(data)
146
        #print(radio)
147
        radio.pipe.write(data)
148
    except:
149
        raise errors.RadioError("Error sending data to radio")
150

    
151
def _make_read_frame(addr, length):
152
    """Pack the info in the header format"""
153
    frame = _make_frame(b"\x52", addr, length)
154
    # Return the data
155

    
156
    return frame
157

    
158
def _make_frame(cmd, addr, length, data=""):
159
    """Pack the info in the header format"""
160
    frame = cmd+struct.pack(">i",addr)[2:]+struct.pack("b", length)
161
    # add the data if set
162
    if len(data) != 0:
163
        frame += data
164
    # return the data
165
    return frame
166

    
167

    
168
def _recv(radio, addr, length):
169
    """Get data from the radio """
170
    # read 4 bytes of header
171
    hdr = _rawrecv(radio, 4)
172

    
173
    # read data
174
    data = _rawrecv(radio, length)
175

    
176
    # DEBUG
177
    LOG.info("Response:")
178
    LOG.debug(util.hexprint(hdr + data))
179

    
180
    c, a, l = struct.unpack(">BHB", hdr)
181
    if a != addr or l != length or c != ord("X"):
182
        LOG.error("Invalid answer for block 0x%04x:" % addr)
183
        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
184
        raise errors.RadioError("Unknown response from the radio")
185

    
186
    return data
187

    
188

    
189
def _get_radio_firmware_version(radio):
190
    # There is a problem in new radios where a different firmware version is
191
    # received when directly reading a single block as compared to what is
192
    # received when reading sequential blocks. This causes a mismatch between
193
    # the image firmware version and the radio firmware version when uploading
194
    # an image that came from the same radio. The workaround is to read 1 or
195
    # more consecutive blocks prior to reading the block with the firmware
196
    # version.
197
    #
198
    # Read 2 consecutive blocks to get the radio firmware version.
199
    for addr in range(0x1E80, 0x1F00, radio._recv_block_size):
200
        frame = _make_frame("S", addr, radio._recv_block_size)
201

    
202
        # sending the read request
203
        _rawsend(radio, frame)
204

    
205
        if radio._ack_block and addr != 0x1E80:
206
            ack = _rawrecv(radio, 1)
207
            if ack != b"\x06":
208
                raise errors.RadioError(
209
                    "Radio refused to send block 0x%04x" % addr)
210

    
211
        # now we read
212
        block = _recv(radio, addr, radio._recv_block_size)
213

    
214
        _rawsend(radio, b"\x06")
215
        time.sleep(0.05)
216

    
217
    # get firmware version from the last block read
218
    version = block[48:64]
219
    return version
220

    
221

    
222
def _image_ident_from_data(data, start, stop):
223
    return data[start:stop]
224

    
225

    
226
def _get_image_firmware_version(radio):
227
    return _image_ident_from_data(radio.get_mmap(), radio._fw_ver_start,
228
                                  radio._fw_ver_start + 0x10)
229

    
230
def _sendmagic(radio, magic, response):
231
    _rawsend(radio, magic)
232
    ack = _rawrecv(radio, len(response))
233
    if ack != response:
234
        if ack:
235
            LOG.debug(repr(ack))
236
        raise errors.RadioError("Radio did not respond to enter read mode")
237
    
238
def _do_ident(radio):
239
    """Put the radio in PROGRAM mode & identify it"""
240
    radio.pipe.baudrate = radio.BAUDRATE
241
    radio.pipe.parity = "N"
242
    radio.pipe.timeout = STIMEOUT
243

    
244
    # Flush input buffer
245
    _clean_buffer(radio)
246

    
247
    # Ident radio
248
    magic = radio._magic
249
    _rawsend(radio, magic)
250
    ack = _rawrecv(radio, radio._magic_response_length)
251

    
252
    if not ack.startswith(radio._fingerprint):
253
        if ack:
254
            LOG.debug(repr(ack))
255
        raise errors.RadioError("Radio did not respond as expected (A)")
256

    
257
    return True
258

    
259

    
260
def _ident_radio(radio):
261
    for magic in radio._magic:
262
        error = None
263
        try:
264
            data = _do_ident(radio, magic)
265
            return data
266
        except errors.RadioError as e:
267
            print(e)
268
            error = e
269
            time.sleep(2)
270
    if error:
271
        raise error
272
    raise errors.RadioError("Radio did not respond")
273

    
274

    
275
def _download(radio):
276
    """Get the memory map"""
277

    
278
    # Put radio in program mode and identify it
279
    _do_ident(radio)
280
    for index in range(len(radio._magics)):
281
        _rawsend(radio, radio._magics[index])
282
        _rawrecv(radio, radio._magicResponseLengths[index])
283

    
284
    data = b""
285

    
286
    # UI progress
287
    status = chirp_common.Status()
288
    status.cur = 0
289
    status.max = radio.MEM_TOTAL // radio.BLOCK_SIZE
290
    status.msg = "Cloning from radio..."
291
    radio.status_fn(status)
292

    
293
    for i in range(len(radio.MEM_SIZES)):
294
        MEM_SIZE = radio.MEM_SIZES[i]
295
        MEM_START = radio.MEM_STARTS[i]
296
        for addr in range(MEM_START, MEM_START + MEM_SIZE, radio.BLOCK_SIZE):
297
            frame = _make_read_frame(addr, radio.BLOCK_SIZE)
298
            # DEBUG
299
            LOG.debug("Frame=" + util.hexprint(frame))
300

    
301
            # Sending the read request
302
            _rawsend(radio, frame)
303

    
304
            # Now we read data
305
            d = _rawrecv(radio, radio.BLOCK_SIZE + 4)
306

    
307
            LOG.debug("Response Data= " + util.hexprint(d))
308
            d = _crypt(1, d[4:])
309

    
310
            # Aggregate the data
311
            data += d
312

    
313
            # UI Update
314
            status.cur = len(data) // radio.BLOCK_SIZE
315
            status.msg = "Cloning from radio..."
316
            radio.status_fn(status)
317
    data += bytes(radio.MODEL, 'utf-8')
318
    return data
319

    
320
def _upload(radio):
321
    # Put radio in program mode and identify it
322
    _do_ident(radio)
323
    for index in range(len(radio._magics)):
324
        _rawsend(radio, radio._magics[index])
325
        _rawrecv(radio, radio._magicResponseLengths[index])
326

    
327
    data = b""
328

    
329
    # UI progress
330
    status = chirp_common.Status()
331
    status.cur = 0
332
    status.max = radio.MEM_TOTAL // radio.BLOCK_SIZE
333
    status.msg = "Cloning from radio..."
334
    radio.status_fn(status)
335

    
336
    data_addr = 0x00
337
    for i in range(len(radio.MEM_SIZES)):
338
        MEM_SIZE = radio.MEM_SIZES[i]
339
        MEM_START = radio.MEM_STARTS[i]
340
        for addr in range(MEM_START, MEM_START + MEM_SIZE, radio.BLOCK_SIZE):
341
            data = radio.get_mmap()[data_addr:data_addr + radio.BLOCK_SIZE]
342
            data = _crypt(1, data)
343
            data_addr += radio.BLOCK_SIZE
344

    
345
            frame = _make_frame(b"W", addr, radio.BLOCK_SIZE, data)
346
            # DEBUG
347
            LOG.debug("Frame=" + util.hexprint(frame))
348
            #print()
349
            #print(hex(data_addr))
350
            #print(util.hexprint(frame))
351

    
352
            # Sending the read request
353
            _rawsend(radio, frame)
354

    
355
            # receiving the response
356
            ack = _rawrecv(radio, 1)
357
            #ack = b"\x06"
358
            if ack != b"\x06":
359
                msg = "Bad ack writing block 0x%04x" % addr
360
                raise errors.RadioError(msg)
361

    
362
            # UI Update
363
            status.cur = data_addr // radio.BLOCK_SIZE
364
            status.msg = "Cloning to radio..."
365
            radio.status_fn(status)
366
    data += bytes(radio.MODEL, 'utf-8')
367
    return data
368

    
369

    
370
def _split(rf, f1, f2):
371
    """Returns False if the two freqs are in the same band (no split)
372
    or True otherwise"""
373

    
374
    # determine if the two freqs are in the same band
375
    for low, high in rf.valid_bands:
376
        if f1 >= low and f1 <= high and \
377
                f2 >= low and f2 <= high:
378
            # if the two freqs are on the same Band this is not a split
379
            return False
380

    
381
    # if you get here is because the freq pairs are split
382
    return True
383

    
384
@directory.register
385
class UV17Pro(chirp_common.CloneModeRadio, 
386
              chirp_common.ExperimentalRadio):
387
    """Baofeng UV-17Pro"""
388
    VENDOR = "Baofeng"
389
    MODEL = "UV-17Pro"
390
    NEEDS_COMPAT_SERIAL = False
391

    
392
    MEM_STARTS = [0x0000, 0x9000, 0xA000, 0xD000]
393
    MEM_SIZES =  [0x8040, 0x0040, 0x02C0, 0x0040]
394

    
395
    MEM_TOTAL = 0x8380
396
    BLOCK_SIZE = 0x40
397
    STIMEOUT = 2
398
    BAUDRATE = 115200
399

    
400
    _gmrs = False
401
    _bw_shift = False
402
    _support_banknames = False
403

    
404
    _tri_band = True
405
    _fileid = []
406
    _magic = MSTRING_UV17L
407
    _magic_response_length = 1
408
    _fingerprint = b"\x06"
409
    _magics = [b"\x46", b"\x4d", b"\x53\x45\x4E\x44\x21\x05\x0D\x01\x01\x01\x04\x11\x08\x05\x0D\x0D\x01\x11\x0F\x09\x12\x09\x10\x04\x00"]
410
    _magicResponseLengths = [16, 15, 1]
411
    _fw_ver_start = 0x1EF0
412
    _recv_block_size = 0x40
413
    _mem_size = MEM_TOTAL
414
    _ack_block = True
415
    _aniid = True
416
    _vfoscan = False
417
    _has_gps = False
418
    _voxsw = False
419
    _pilot_tone = False
420
    _send_id_delay = False
421
    _skey2_short = False
422

    
423
    MODES = ["NFM", "FM"]
424
    VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
425
        "!@#$%^&*()+-=[]:\";'<>?,./"
426
    LENGTH_NAME = 12
427
    SKIP_VALUES = ["", "S"]
428
    DTCS_CODES = tuple(sorted(chirp_common.DTCS_CODES + (645,)))
429
    RXTX_CODES = ('Off', )
430
    for code in chirp_common.TONES:
431
        RXTX_CODES = (RXTX_CODES + (str(code), ))
432
    for code in DTCS_CODES:
433
        RXTX_CODES = (RXTX_CODES + ('D' + str(code) + 'N', ))
434
    for code in DTCS_CODES:
435
        RXTX_CODES = (RXTX_CODES + ('D' + str(code) + 'I', ))
436
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
437
                    chirp_common.PowerLevel("Low",  watts=1.00)]
438
    _airband = (108000000, 136000000)
439
    _vhf_range = (136000000, 174000000)
440
    _vhf2_range = (200000000, 260000000)
441
    _uhf_range = (400000000, 520000000)
442
    _uhf2_range = (350000000, 390000000)
443

    
444
    VALID_BANDS = [_vhf_range, _vhf2_range,
445
                   _uhf_range]
446
    PTTID_LIST = LIST_PTTID
447
    SCODE_LIST = LIST_SCODE
448

    
449
    MEM_FORMAT = """
450
    #seekto 0x0000;
451
    struct {
452
      lbcd rxfreq[4];
453
      lbcd txfreq[4];
454
      ul16 rxtone;
455
      ul16 txtone;
456
      u8 scode:8;
457
      u8 pttid:8;
458
      u8 lowpower:8;
459
      u8 unknown1:1,
460
         wide:1,
461
         sqmode:2,
462
         bcl:1,
463
         scan:1,
464
         unknown2:1,
465
         fhss:1;
466
      u8 unknown3:8;
467
      u8 unknown4:8;
468
      u8 unknown5:8;
469
      u8 unknown6:8;
470
      char name[12];
471
    } memory[1000];
472

    
473
    #seekto 0x8080;
474
    struct {
475
      u8 code[5];
476
      u8 unknown[1];
477
      u8 unused1:6,
478
         aniid:2;
479
      u8 dtmfon;
480
      u8 dtmfoff;
481
    } ani;
482

    
483
    #seekto 0x80A0;
484
    struct {
485
      u8 code[5];
486
      u8 name[10];
487
      u8 unused:8;
488
    } pttid[20];
489

    
490
    
491
    #seekto 0x8280;
492
    struct {
493
      char name1[12];
494
      u32 unknown1;
495
      char name2[12];
496
      u32 unknown2;
497
      char name3[12];
498
      u32 unknown3;
499
      char name4[12];
500
      u32 unknown4;
501
      char name5[12];
502
      u32 unknown5;
503
      char name6[12];
504
      u32 unknown6;
505
      char name7[12];
506
      u32 unknown7;
507
      char name8[12];
508
      u32 unknown8;
509
      char name9[12];
510
      u32 unknown9;
511
      char name10[12];
512
      u32 unknown10;
513
    } bank_names;
514

    
515
    struct vfo {
516
      u8 freq[8];
517
      ul16 rxtone;
518
      ul16 txtone;
519
      u8 unknown0;
520
      u8 bcl;
521
      u8 sftd:3,
522
         scode:5;
523
      u8 unknown1;
524
      u8 lowpower;
525
      u8 unknown2:1, 
526
         wide:1,
527
         unknown3:5,
528
         fhss:1;
529
      u8 unknown4;
530
      u8 step;
531
      u8 offset[6];
532
      u8 unknown5[2];
533
      u8 sqmode;
534
      u8 unknown6[3];
535
    };
536

    
537
    #seekto 0x8000;
538
    struct {
539
      struct vfo a;
540
      struct vfo b;
541
    } vfo;
542

    
543
    #seekto 0x8040;
544
    struct {
545
      u8 squelch;
546
      u8 savemode;
547
      u8 vox;
548
      u8 backlight;
549
      u8 dualstandby;
550
      u8 tot;
551
      u8 beep;
552
      u8 voicesw;
553
      u8 voice;
554
      u8 sidetone;
555
      u8 scanmode;
556
      u8 pttid;
557
      u8 pttdly;
558
      u8 chadistype;
559
      u8 chbdistype;
560
      u8 bcl;
561
      u8 autolock;
562
      u8 alarmmode;
563
      u8 alarmtone;
564
      u8 unknown1;
565
      u8 tailclear;
566
      u8 rpttailclear;
567
      u8 rpttaildet;
568
      u8 roger;
569
      u8 unknown2;
570
      u8 fmenable;
571
      u8 chaworkmode:4,
572
         chbworkmode:4;
573
      u8 keylock;
574
      u8 powerondistype;
575
      u8 tone;
576
      u8 unknown4[2];
577
      u8 voxdlytime;
578
      u8 menuquittime;
579
      u8 unknown5[6];
580
      u8 totalarm;
581
      u8 unknown6[2];
582
      u8 ctsdcsscantype;
583
      ul16 vfoscanmin;
584
      ul16 vfoscanmax;
585
      u8 gpsw;
586
      u8 gpsmode;
587
      u8 unknown7[2];
588
      u8 key2short;
589
      u8 unknown8[2];
590
      u8 rstmenu;  
591
      u8 unknown9;
592
      u8 hangup;
593
      u8 voxsw;
594
      u8 gpstimezone;
595
    } settings;
596
    """
597

    
598
    @classmethod
599
    def get_prompts(cls):
600
        rp = chirp_common.RadioPrompts()
601
        rp.experimental = \
602
            ('This driver is a beta version.\n'
603
             '\n'
604
             'Please save an unedited copy of your first successful\n'
605
             'download to a CHIRP Radio Images(*.img) file.'
606
             )
607
        rp.pre_download = _(
608
            "Follow these instructions to download your info:\n"
609
            "1 - Turn off your radio\n"
610
            "2 - Connect your interface cable\n"
611
            "3 - Turn on your radio\n"
612
            "4 - Do the download of your radio data\n")
613
        rp.pre_upload = _(
614
            "Follow this instructions to upload your info:\n"
615
            "1 - Turn off your radio\n"
616
            "2 - Connect your interface cable\n"
617
            "3 - Turn on your radio\n"
618
            "4 - Do the upload of your radio data\n")
619
        return rp
620

    
621
    def process_mmap(self):
622
        """Process the mem map into the mem object"""
623
        if (len(self._mmap) == self.MEM_TOTAL) or (len(self._mmap) == self.MEM_TOTAL + len(self.MODEL)):
624
            self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap)
625
        else:
626
            raise errors.ImageDetectFailed('Image length mismatch.\nTry reloading the configuration'
627
                                           ' from the radio.')
628

    
629
    def get_settings(self):
630
        """Translate the bit in the mem_struct into settings in the UI"""
631
        _mem = self._memobj
632
        basic = RadioSettingGroup("basic", "Basic Settings")
633
        bank = RadioSettingGroup("bank", "Bank names")
634
        work = RadioSettingGroup("work", "VFO Mode Settings")
635
        dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings")
636
        top = RadioSettings(basic, bank, work, dtmfe)
637
     
638
        if _mem.settings.squelch > 0x05:
639
            val = 0x00
640
        else:
641
            val = _mem.settings.squelch
642
        rs = RadioSetting("settings.squelch", "Squelch",
643
                          RadioSettingValueList(
644
                              LIST_OFF1TO5, LIST_OFF1TO5[val]))
645
        basic.append(rs)
646

    
647
        rs = RadioSetting("settings.tot", "Timeout Timer",
648
                          RadioSettingValueList(
649
                              LIST_TIMEOUT, LIST_TIMEOUT[_mem.settings.tot]))
650
        basic.append(rs)
651

    
652
        rs = RadioSetting("settings.savemode", "Save Mode",
653
                          RadioSettingValueBoolean(_mem.settings.savemode))
654
        basic.append(rs)
655

    
656
        rs = RadioSetting("settings.totalarm", "Timeout Timer Alarm",
657
                          RadioSettingValueList(
658
                              LIST_TIMEOUT_ALARM, LIST_TIMEOUT_ALARM[_mem.settings.totalarm]))
659
        basic.append(rs)
660

    
661
        rs = RadioSetting("settings.dualstandby", "Dual Watch",
662
                          RadioSettingValueBoolean(_mem.settings.dualstandby))
663
        basic.append(rs)
664

    
665
        if self._pilot_tone:
666
            rs = RadioSetting("settings.tone", "Pilot Tone",
667
                            RadioSettingValueList(
668
                                LIST_PILOT_TONE, LIST_PILOT_TONE[_mem.settings.tone]))
669
            basic.append(rs)
670

    
671
        rs = RadioSetting("settings.sidetone", "Side Tone",
672
                          RadioSettingValueList(
673
                              LIST_SIDE_TONE, LIST_SIDE_TONE[_mem.settings.sidetone]))
674
        basic.append(rs)
675

    
676
        rs = RadioSetting("settings.tailclear", "Tail Clear",
677
                          RadioSettingValueBoolean(_mem.settings.tailclear))
678
        basic.append(rs)
679

    
680
        rs = RadioSetting("settings.powerondistype", "Power On Display Type",
681
                          RadioSettingValueList(
682
                              LIST_POWERON_DISPLAY_TYPE, LIST_POWERON_DISPLAY_TYPE[_mem.settings.powerondistype]))
683
        basic.append(rs)
684

    
685
        rs = RadioSetting("settings.beep", "Beep",
686
                          RadioSettingValueList(
687
                              LIST_BEEP, LIST_BEEP[_mem.settings.beep]))
688
        basic.append(rs)
689

    
690
        rs = RadioSetting("settings.roger", "Roger",
691
                          RadioSettingValueBoolean(_mem.settings.roger))
692
        basic.append(rs)
693

    
694
        rs = RadioSetting("settings.voice", "Language",
695
                          RadioSettingValueList(
696
                              LIST_LANGUAGE, LIST_LANGUAGE[_mem.settings.voice]))
697
        basic.append(rs)
698

    
699
        rs = RadioSetting("settings.voicesw", "Enable Voice",
700
                          RadioSettingValueBoolean(_mem.settings.voicesw))
701
        basic.append(rs)
702

    
703
        rs = RadioSetting("settings.scanmode", "Scan Mode",
704
                          RadioSettingValueList(
705
                              LIST_SCANMODE, LIST_SCANMODE[_mem.settings.scanmode]))
706
        basic.append(rs)
707

    
708
        rs = RadioSetting("settings.alarmmode", "Alarm Mode",
709
                          RadioSettingValueList(
710
                              LIST_ALARMMODE, LIST_ALARMMODE[_mem.settings.alarmmode]))
711
        basic.append(rs)
712

    
713
        rs = RadioSetting("settings.alarmtone", "Sound Alarm",
714
                          RadioSettingValueBoolean(_mem.settings.alarmtone))
715
        basic.append(rs)
716

    
717
        rs = RadioSetting("settings.keylock", "Key Lock",
718
                          RadioSettingValueBoolean(_mem.settings.keylock))
719
        basic.append(rs)
720

    
721
        rs = RadioSetting("settings.fmenable", "Disable FM radio",
722
                          RadioSettingValueBoolean(_mem.settings.fmenable))
723
        basic.append(rs)
724

    
725
        rs = RadioSetting("settings.autolock", "Key Auto Lock",
726
                          RadioSettingValueBoolean(_mem.settings.autolock))
727
        basic.append(rs)
728

    
729
        rs = RadioSetting("settings.menuquittime", "Menu Quit Timer",
730
                          RadioSettingValueList(
731
                              LIST_MENU_QUIT_TIME, LIST_MENU_QUIT_TIME[_mem.settings.menuquittime]))
732
        basic.append(rs)
733

    
734
        rs = RadioSetting("settings.backlight", "Backlight Timer",
735
                          RadioSettingValueList(
736
                              LIST_BACKLIGHT_TIMER, LIST_BACKLIGHT_TIMER[_mem.settings.backlight]))
737
        basic.append(rs)
738

    
739
        if self._send_id_delay:
740
            rs = RadioSetting("settings.pttdly", "Send ID Delay",
741
                            RadioSettingValueList(
742
                                LIST_ID_DELAY, LIST_ID_DELAY[_mem.settings.pttdly]))
743
            basic.append(rs)
744

    
745
        rs = RadioSetting("settings.ctsdcsscantype", "QT Save Mode",
746
                          RadioSettingValueList(
747
                              LIST_QT_SAVEMODE, LIST_QT_SAVEMODE[_mem.settings.ctsdcsscantype]))
748
        basic.append(rs)
749

    
750
        def getKey2shortIndex(value):
751
            if value == 0x07:
752
                return 0
753
            if value == 0x1C:
754
                return 1
755
            if value == 0x1D:
756
                return 2
757
            if value == 0x2D:
758
                return 3
759
            return 0
760
        
761
        def apply_Key2short(setting, obj):
762
            val = str(setting.value)
763
            if val == "FM":
764
                obj.key2short = 0x07
765
            if val == "Scan":
766
                obj.key2short = 0x1C
767
            if val == "Search":
768
                obj.key2short = 0x1D
769
            if val == "Vox":
770
                obj.key2short = 0x2D
771

    
772
        if self._skey2_short:
773
            rs = RadioSetting("settings.key2short", "Skey2 Short",
774
                            RadioSettingValueList(
775
                                LIST_SKEY2_SHORT, LIST_SKEY2_SHORT[getKey2shortIndex(_mem.settings.key2short)]))
776
            rs.set_apply_callback(apply_Key2short, _mem.settings)
777
            basic.append(rs)
778

    
779
        rs = RadioSetting("settings.chadistype", "Channel A display type",
780
                          RadioSettingValueList(
781
                              LIST_MODE, LIST_MODE[_mem.settings.chadistype]))
782
        basic.append(rs)
783

    
784
        rs = RadioSetting("settings.chaworkmode", "Channel A work mode",
785
                          RadioSettingValueList(
786
                              LIST_WORKMODE, LIST_WORKMODE[_mem.settings.chaworkmode]))
787
        basic.append(rs)
788

    
789
        rs = RadioSetting("settings.chbdistype", "Channel B display type",
790
                          RadioSettingValueList(
791
                              LIST_MODE, LIST_MODE[_mem.settings.chbdistype]))
792
        basic.append(rs)
793

    
794
        rs = RadioSetting("settings.chbworkmode", "Channel B work mode",
795
                          RadioSettingValueList(
796
                              LIST_WORKMODE, LIST_WORKMODE[_mem.settings.chbworkmode]))
797
        basic.append(rs)
798

    
799
        rs = RadioSetting("settings.rpttailclear", "Rpt Tail Clear",
800
                          RadioSettingValueList(
801
                              LIST_RPT_TAIL_CLEAR, LIST_RPT_TAIL_CLEAR[_mem.settings.rpttailclear]))
802
        basic.append(rs)
803

    
804
        rs = RadioSetting("settings.rpttaildet", "Rpt Tail Delay",
805
                          RadioSettingValueList(
806
                              LIST_RPT_TAIL_CLEAR, LIST_RPT_TAIL_CLEAR[_mem.settings.rpttaildet]))
807
        basic.append(rs)
808

    
809
        rs = RadioSetting("settings.rstmenu", "Enable Menu Rst",
810
                          RadioSettingValueBoolean(_mem.settings.rstmenu))
811
        basic.append(rs)
812

    
813
        if self._voxsw:
814
            rs = RadioSetting("settings.voxsw", "Vox Switch",
815
                            RadioSettingValueBoolean(_mem.settings.voxsw))
816
            basic.append(rs)
817

    
818
            rs = RadioSetting("settings.vox", "Vox Level",
819
                            RadioSettingValueList(
820
                                LIST_VOX_LEVEL_ALT, LIST_VOX_LEVEL_ALT[_mem.settings.vox]))
821
            basic.append(rs)
822
        else:
823
            rs = RadioSetting("settings.vox", "Vox Level",
824
                            RadioSettingValueList(
825
                                LIST_VOX_LEVEL, LIST_VOX_LEVEL[_mem.settings.vox]))
826
            basic.append(rs)
827
            
828

    
829
        rs = RadioSetting("settings.voxdlytime", "Vox Delay Time",
830
                          RadioSettingValueList(
831
                              LIST_VOX_DELAY_TIME, LIST_VOX_DELAY_TIME[_mem.settings.voxdlytime]))
832
        basic.append(rs)
833

    
834
        if self._has_gps:
835
            rs = RadioSetting("settings.gpsw", "GPS On",
836
                            RadioSettingValueBoolean(_mem.settings.gpsw))
837
            basic.append(rs)
838
            
839
            rs = RadioSetting("settings.gpsmode", "GPS Mode",
840
                            RadioSettingValueList(
841
                                LIST_GPS_MODE, LIST_GPS_MODE[_mem.settings.gpsmode]))
842
            basic.append(rs)
843

    
844
            rs = RadioSetting("settings.gpstimezone", "GPS Timezone",
845
                            RadioSettingValueList(
846
                                LIST_GPS_TIMEZONE, LIST_GPS_TIMEZONE[_mem.settings.gpstimezone]))
847
            basic.append(rs)
848

    
849
        def _filterName(name, zone):
850
            fname = ""
851
            charset=chirp_common.CHARSET_ASCII
852
            for char in name:
853
                if ord(str(char)) == 255:
854
                    break
855
                if str(char) not in charset:
856
                    char = "X"
857
                fname += str(char)
858
            if fname == "XXXXXX":
859
                fname = "ZONE" + zone
860
            return fname
861
        
862
        if self._support_banknames:
863
            _zone="01"
864
            _msg = _mem.bank_names
865
            rs = RadioSetting("bank_names.name1", "Bank name 1",
866
                            RadioSettingValueString(
867
                                0, 12, _filterName(_msg.name1, _zone)))
868
            bank.append(rs)
869

    
870
            _zone="02"
871
            _msg = _mem.bank_names
872
            rs = RadioSetting("bank_names.name2", "Bank name 2",
873
                            RadioSettingValueString(
874
                                0, 12, _filterName(_msg.name2, _zone)))
875
            
876
            bank.append(rs)
877
            _zone="03"
878
            _msg = _mem.bank_names
879
            rs = RadioSetting("bank_names.name3", "Bank name 3",
880
                            RadioSettingValueString(
881
                                0, 12, _filterName(_msg.name3, _zone)))
882
            bank.append(rs)
883

    
884
            _zone="04"
885
            _msg = _mem.bank_names
886
            rs = RadioSetting("bank_names.name4", "Bank name 4",
887
                            RadioSettingValueString(
888
                                0, 12, _filterName(_msg.name4, _zone)))            
889
            bank.append(rs)
890

    
891
            _zone="05"
892
            _msg = _mem.bank_names
893
            rs = RadioSetting("bank_names.name5", "Bank name 5",
894
                            RadioSettingValueString(
895
                                0, 12, _filterName(_msg.name5, _zone)))
896
            bank.append(rs)
897

    
898
            _zone="06"
899
            _msg = _mem.bank_names
900
            rs = RadioSetting("bank_names.name6", "Bank name 6",
901
                            RadioSettingValueString(
902
                                0, 12, _filterName(_msg.name6, _zone)))
903
            bank.append(rs)
904

    
905
            _zone="07"
906
            _msg = _mem.bank_names
907
            rs = RadioSetting("bank_names.name7", "Bank name 7",
908
                            RadioSettingValueString(
909
                                0, 12, _filterName(_msg.name7, _zone)))
910
            bank.append(rs)
911

    
912
            _zone="08"
913
            _msg = _mem.bank_names
914
            rs = RadioSetting("bank_names.name8", "Bank name 8",
915
                            RadioSettingValueString(
916
                                0, 12, _filterName(_msg.name8, _zone)))
917
            bank.append(rs)
918

    
919
            _zone="09"
920
            _msg = _mem.bank_names
921
            rs = RadioSetting("bank_names.name9", "Bank name 9",
922
                            RadioSettingValueString(
923
                                0, 12, _filterName(_msg.name9, _zone)))
924
            bank.append(rs)
925

    
926
            _zone="10"
927
            _msg = _mem.bank_names
928
            rs = RadioSetting("bank_names.name10", "Bank name 10",
929
                            RadioSettingValueString(
930
                                0, 12, _filterName(_msg.name10, _zone)))
931
            bank.append(rs)
932

    
933
        _codeobj = self._memobj.ani.code
934
        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
935
        val = RadioSettingValueString(0, 5, _code, False)
936
        val.set_charset(DTMF_CHARS)
937
        rs = RadioSetting("ani.code", "ANI Code", val)
938

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

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

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

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

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

    
985
        if self._aniid:
986
            rs = RadioSetting("ani.aniid", "When to send ANI ID",
987
                            RadioSettingValueList(LIST_PTTID,
988
                                                    LIST_PTTID[_mem.ani.aniid]))
989
            dtmfe.append(rs)
990

    
991
        rs = RadioSetting("settings.hangup", "Hang-up time",
992
                        RadioSettingValueList(LIST_HANGUPTIME,
993
                                                LIST_HANGUPTIME[_mem.settings.hangup]))
994
        dtmfe.append(rs)
995

    
996
        def convert_bytes_to_freq(bytes):
997
            real_freq = 0
998
            for byte in bytes:
999
                real_freq = (real_freq * 10) + byte
1000
            return chirp_common.format_freq(real_freq * 10)
1001

    
1002
        def my_validate(value):
1003
            value = chirp_common.parse_freq(value)
1004
            freqOk = False
1005
            for band in self.VALID_BANDS:
1006
                if value > band[0] and value < band[1]:
1007
                    freqOk = True
1008
            if not freqOk:
1009
                raise InvalidValueError("Invalid frequency!")
1010
            return chirp_common.format_freq(value)
1011

    
1012
        def apply_freq(setting, obj):
1013
            value = chirp_common.parse_freq(str(setting.value)) / 10
1014
            for i in range(7, -1, -1):
1015
                obj.freq[i] = value % 10
1016
                value /= 10
1017

    
1018
        val1a = RadioSettingValueString(0, 10,
1019
                                        convert_bytes_to_freq(_mem.vfo.a.freq))
1020
        val1a.set_validate_callback(my_validate)
1021
        rs = RadioSetting("vfo.a.freq", "VFO A Frequency", val1a)
1022
        rs.set_apply_callback(apply_freq, _mem.vfo.a)
1023
        work.append(rs)
1024

    
1025
        rs = RadioSetting("vfo.a.sftd", "VFO A Offset dir",
1026
                          RadioSettingValueList(
1027
                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.a.sftd]))
1028
        work.append(rs)
1029

    
1030
        def convert_bytes_to_offset(bytes):
1031
            real_offset = 0
1032
            for byte in bytes:
1033
                real_offset = (real_offset * 10) + byte
1034
            return chirp_common.format_freq(real_offset * 1000)
1035

    
1036
        def apply_offset(setting, obj):
1037
            value = chirp_common.parse_freq(str(setting.value)) / 1000
1038
            for i in range(5, -1, -1):
1039
                obj.offset[i] = value % 10
1040
                value /= 10
1041

    
1042
        val1a = RadioSettingValueString(
1043
                    0, 10, convert_bytes_to_offset(_mem.vfo.a.offset))
1044
        rs = RadioSetting("vfo.a.offset",
1045
                          "VFO A Offset", val1a)
1046
        rs.set_apply_callback(apply_offset, _mem.vfo.a)
1047
        work.append(rs)
1048

    
1049
        def apply_txpower_listvalue(setting, obj):
1050
            LOG.debug("Setting value: " + str(
1051
                      setting.value) + " from list")
1052
            val = str(setting.value)
1053
            index = TXP_CHOICES.index(val)
1054
            val = TXP_VALUES[index]
1055
            obj.set_value(val)
1056

    
1057
        rs = RadioSetting("vfo.a.lowpower", "VFO A Power",
1058
                            RadioSettingValueList(
1059
                                LIST_TXPOWER,
1060
                                LIST_TXPOWER[min(_mem.vfo.a.lowpower, 0x02)]
1061
                                            ))
1062
        work.append(rs)
1063

    
1064
        rs = RadioSetting("vfo.a.wide", "VFO A Bandwidth",
1065
                          RadioSettingValueList(
1066
                              LIST_BANDWIDTH,
1067
                              LIST_BANDWIDTH[_mem.vfo.a.wide]))
1068
        work.append(rs)
1069

    
1070
        rs = RadioSetting("vfo.a.scode", "VFO A S-CODE",
1071
                          RadioSettingValueList(
1072
                              LIST_SCODE,
1073
                              LIST_SCODE[_mem.vfo.a.scode]))
1074
        work.append(rs)
1075

    
1076
        rs = RadioSetting("vfo.a.step", "VFO A Tuning Step",
1077
                          RadioSettingValueList(
1078
                              LIST_STEP, LIST_STEP[_mem.vfo.a.step]))
1079
        work.append(rs)
1080

    
1081
        rs = RadioSetting("vfo.a.fhss", "VFO A FHSS",
1082
                          RadioSettingValueBoolean(_mem.vfo.a.fhss))
1083
        work.append(rs)
1084

    
1085
        def getToneIndex(tone):            
1086
            if tone in [0, 0xFFFF]:
1087
                index = 0
1088
            elif tone >= 0x0258:
1089
                index = self.RXTX_CODES.index(str(tone / 10.0))
1090
            elif tone <= 0x0258:
1091
                index = 50 + tone
1092
                if tone > 0x69:
1093
                    index = tone - 0x6A + 156
1094
            return self.RXTX_CODES[index]
1095
        
1096
        def apply_rxtone(setting, obj):
1097
            index = self.RXTX_CODES.index(str(setting.value))
1098
            if index > 156:
1099
                obj.rxtone = index - 156 + 0x6A
1100
            elif index > 50:
1101
                obj.rxtone = index - 50
1102
            elif index == 0:
1103
                obj.rxtone = 0
1104
            else:
1105
                obj.rxtone = int(float(setting.value)*10) 
1106

    
1107
        def apply_txtone(setting, obj):
1108
            index = self.RXTX_CODES.index(str(setting.value))
1109
            if index > 156:
1110
                obj.txtone = index - 156 + 0x6A
1111
            elif index > 50:
1112
                obj.txtone = index - 50
1113
            elif index == 0:
1114
                obj.txtone = 0
1115
            else:
1116
                obj.txtone = int(float(setting.value)*10) 
1117
        
1118
        rs = RadioSetting("vfo.a.rxtone", "VFA A RX QT/DQT",
1119
                          RadioSettingValueList(self.RXTX_CODES,
1120
                                                getToneIndex(_mem.vfo.a.rxtone)))
1121
        rs.set_apply_callback(apply_rxtone, _mem.vfo.a)
1122
        work.append(rs)
1123

    
1124
        rs = RadioSetting("vfo.a.txtone", "VFA A TX QT/DQT",
1125
                          RadioSettingValueList(self.RXTX_CODES,
1126
                                                getToneIndex(_mem.vfo.a.txtone)))
1127
        rs.set_apply_callback(apply_txtone, _mem.vfo.a)
1128
        work.append(rs)
1129

    
1130
        val1b = RadioSettingValueString(0, 10,
1131
                                        convert_bytes_to_freq(_mem.vfo.b.freq))
1132
        val1b.set_validate_callback(my_validate)
1133
        rs = RadioSetting("vfo.b.freq", "VFO B Frequency", val1b)
1134
        rs.set_apply_callback(apply_freq, _mem.vfo.b)
1135
        work.append(rs)
1136

    
1137
        rs = RadioSetting("vfo.b.sftd", "VFO B Offset dir",
1138
                          RadioSettingValueList(
1139
                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.b.sftd]))
1140
        work.append(rs)
1141

    
1142
        val1b = RadioSettingValueString(
1143
                    0, 10, convert_bytes_to_offset(_mem.vfo.b.offset))
1144
        rs = RadioSetting("vfo.b.offset",
1145
                          "VFO B Offset", val1b)
1146
        rs.set_apply_callback(apply_offset, _mem.vfo.b)
1147
        work.append(rs)
1148

    
1149
        rs = RadioSetting("vfo.b.lowpower", "VFO B Power",
1150
                            RadioSettingValueList(
1151
                                LIST_TXPOWER,
1152
                                LIST_TXPOWER[min(_mem.vfo.b.lowpower, 0x02)]
1153
                                            ))
1154
        work.append(rs)
1155

    
1156
        rs = RadioSetting("vfo.b.wide", "VFO B Bandwidth",
1157
                          RadioSettingValueList(
1158
                              LIST_BANDWIDTH,
1159
                              LIST_BANDWIDTH[_mem.vfo.b.wide]))
1160
        work.append(rs)
1161

    
1162
        rs = RadioSetting("vfo.b.scode", "VFO B S-CODE",
1163
                          RadioSettingValueList(
1164
                              LIST_SCODE,
1165
                              LIST_SCODE[_mem.vfo.b.scode]))
1166
        work.append(rs)
1167

    
1168
        rs = RadioSetting("vfo.b.step", "VFO B Tuning Step",
1169
                          RadioSettingValueList(
1170
                              LIST_STEP, LIST_STEP[_mem.vfo.b.step]))
1171
        work.append(rs)
1172

    
1173
        rs = RadioSetting("vfo.b.fhss", "VFO B FHSS",
1174
                          RadioSettingValueBoolean(_mem.vfo.b.fhss))
1175
        work.append(rs)
1176

    
1177
        rs = RadioSetting("vfo.b.rxtone", "VFA B RX QT/DQT",
1178
                          RadioSettingValueList(self.RXTX_CODES,
1179
                                                getToneIndex(_mem.vfo.b.rxtone)))
1180
        rs.set_apply_callback(apply_rxtone, _mem.vfo.b)
1181
        work.append(rs)
1182

    
1183
        rs = RadioSetting("vfo.b.txtone", "VFA B TX QT/DQT",
1184
                          RadioSettingValueList(self.RXTX_CODES,
1185
                                                getToneIndex(_mem.vfo.b.txtone)))
1186
        rs.set_apply_callback(apply_txtone, _mem.vfo.b)
1187
        work.append(rs)
1188

    
1189
        if self._vfoscan:
1190
            def scan_validate(value):
1191
                freqOk = False
1192
                for band in self.VALID_BANDS:
1193
                    if value >= (band[0]/1000000) and value <= (band[1]/1000000):
1194
                        freqOk = True
1195
                if not freqOk:
1196
                    raise InvalidValueError("Invalid frequency!")
1197
                return value
1198

    
1199
            scanMin = RadioSettingValueInteger(0, 800,
1200
                                            _mem.settings.vfoscanmin)
1201
            scanMin.set_validate_callback(scan_validate)
1202
            rs = RadioSetting("settings.vfoscanmin", "VFO scan range minimum", scanMin)
1203
            work.append(rs)
1204

    
1205
            scanMax = RadioSettingValueInteger(0, 800,
1206
                                            _mem.settings.vfoscanmax)
1207
            scanMax.set_validate_callback(scan_validate)
1208
            rs = RadioSetting("settings.vfoscanmax", "VFO scan range maximum", scanMax)
1209
            work.append(rs)
1210

    
1211
        rs = RadioSetting("settings.bcl", "BCL",
1212
                          RadioSettingValueBoolean(_mem.settings.bcl))
1213
        work.append(rs)
1214

    
1215
        rs = RadioSetting("settings.pttid", "PTT ID",
1216
                          RadioSettingValueList(self.PTTID_LIST,
1217
                                                self.PTTID_LIST[_mem.settings.pttid]))
1218
        work.append(rs)
1219
        
1220
        return top
1221
    
1222

    
1223
    
1224
        # TODO: implement settings 
1225

    
1226
    
1227
    def sync_in(self):
1228
        """Download from radio"""
1229
        try:
1230
            data = _download(self)
1231
        except errors.RadioError:
1232
            # Pass through any real errors we raise
1233
            raise
1234
        except:
1235
            # If anything unexpected happens, make sure we raise
1236
            # a RadioError and log the problem
1237
            LOG.exception('Unexpected error during download')
1238
            raise errors.RadioError('Unexpected error communicating '
1239
                                    'with the radio')
1240
        self._mmap = memmap.MemoryMapBytes(data)
1241

    
1242
        self.process_mmap()
1243

    
1244
    def sync_out(self):
1245
        """Upload to radio"""
1246
        try:
1247
            _upload(self)
1248
        except errors.RadioError:
1249
            raise
1250
        except Exception:
1251
            # If anything unexpected happens, make sure we raise
1252
            # a RadioError and log the problem
1253
            LOG.exception('Unexpected error during upload')
1254
            raise errors.RadioError('Unexpected error communicating '
1255
                                    'with the radio')
1256

    
1257
    def get_features(self):
1258
        """Get the radio's features"""
1259

    
1260
        rf = chirp_common.RadioFeatures()
1261
        rf.has_settings = True
1262
        rf.has_bank = False
1263
        rf.has_tuning_step = False
1264
        rf.can_odd_split = True
1265
        rf.has_name = True
1266
        rf.has_offset = True
1267
        rf.has_mode = True
1268
        rf.has_dtcs = True
1269
        rf.has_rx_dtcs = True
1270
        rf.has_dtcs_polarity = True
1271
        rf.has_ctone = True
1272
        rf.has_cross = True
1273
        rf.valid_modes = self.MODES
1274
        rf.valid_characters = self.VALID_CHARS
1275
        rf.valid_name_length = self.LENGTH_NAME
1276
        if self._gmrs:
1277
            rf.valid_duplexes = ["", "+", "off"]
1278
        else:
1279
            rf.valid_duplexes = ["", "-", "+", "split", "off"]
1280
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
1281
        rf.valid_cross_modes = [
1282
            "Tone->Tone",
1283
            "DTCS->",
1284
            "->DTCS",
1285
            "Tone->DTCS",
1286
            "DTCS->Tone",
1287
            "->Tone",
1288
            "DTCS->DTCS"]
1289
        rf.valid_skips = self.SKIP_VALUES
1290
        rf.valid_dtcs_codes = self.DTCS_CODES
1291
        rf.memory_bounds = (0, 999)
1292
        rf.valid_power_levels = self.POWER_LEVELS
1293
        rf.valid_bands = self.VALID_BANDS
1294
        rf.valid_tuning_steps = STEPS
1295

    
1296
        return rf
1297

    
1298
    def _is_txinh(self, _mem):
1299
        raw_tx = b""
1300
        for i in range(0, 4):
1301
            raw_tx += _mem.txfreq[i].get_raw()
1302
        return raw_tx == b"\xFF\xFF\xFF\xFF"
1303

    
1304
    def get_memory(self, number):
1305
        _mem = self._memobj.memory[number]
1306
        #_nam = self._memobj.names[number]
1307
        #print(number)
1308
        #print(_mem)
1309
        #print(_nam)
1310

    
1311
        mem = chirp_common.Memory()
1312
        mem.number = number
1313

    
1314
        if _mem.get_raw()[0] == 255:
1315
            mem.empty = True
1316
            return mem
1317

    
1318
        mem.freq = int(_mem.rxfreq) * 10
1319

    
1320
        if self._is_txinh(_mem):
1321
            # TX freq not set
1322
            mem.duplex = "off"
1323
            mem.offset = 0
1324
        else:
1325
            # TX freq set
1326
            offset = (int(_mem.txfreq) * 10) - mem.freq
1327
            if offset != 0:
1328
                if _split(self.get_features(), mem.freq, int(
1329
                          _mem.txfreq) * 10):
1330
                    mem.duplex = "split"
1331
                    mem.offset = int(_mem.txfreq) * 10
1332
                elif offset < 0:
1333
                    mem.offset = abs(offset)
1334
                    mem.duplex = "-"
1335
                elif offset > 0:
1336
                    mem.offset = offset
1337
                    mem.duplex = "+"
1338
            else:
1339
                mem.offset = 0
1340
    
1341
        for char in _mem.name:
1342
            if str(char) == "\xFF":
1343
                char = " "  # The OEM software may have 0xFF mid-name
1344
            mem.name += str(char)
1345
        mem.name = mem.name.rstrip()
1346

    
1347
        if not _mem.scan:
1348
            mem.skip = "S"
1349

    
1350
        levels = self.POWER_LEVELS
1351
        try:
1352
            mem.power = levels[_mem.lowpower]
1353
        except IndexError:
1354
            mem.power = levels[0]
1355

    
1356
        mem.mode = _mem.wide and "NFM" or  "FM"
1357

    
1358
        dtcs_pol = ["N", "N"]
1359

    
1360
        if _mem.txtone in [0, 0xFFFF]:
1361
            txmode = ""
1362
        elif _mem.txtone >= 0x0258:
1363
            txmode = "Tone"
1364
            mem.rtone = int(_mem.txtone) / 10.0
1365
        elif _mem.txtone <= 0x0258:
1366
            txmode = "DTCS"
1367
            if _mem.txtone > 0x69:
1368
                index = _mem.txtone - 0x6A
1369
                dtcs_pol[0] = "R"
1370
            else:
1371
                index = _mem.txtone - 1
1372
            mem.dtcs = self.DTCS_CODES[index]
1373
        else:
1374
            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
1375

    
1376
        if _mem.rxtone in [0, 0xFFFF]:
1377
            rxmode = ""
1378
        elif _mem.rxtone >= 0x0258:
1379
            rxmode = "Tone"
1380
            mem.ctone = int(_mem.rxtone) / 10.0
1381
        elif _mem.rxtone <= 0x0258:
1382
            rxmode = "DTCS"
1383
            if _mem.rxtone >= 0x6A:
1384
                index = _mem.rxtone - 0x6A
1385
                dtcs_pol[1] = "R"
1386
            else:
1387
                index = _mem.rxtone - 1
1388
            mem.rx_dtcs = self.DTCS_CODES[index]
1389
        else:
1390
            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
1391

    
1392
        if txmode == "Tone" and not rxmode:
1393
            mem.tmode = "Tone"
1394
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
1395
            mem.tmode = "TSQL"
1396
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
1397
            mem.tmode = "DTCS"
1398
        elif rxmode or txmode:
1399
            mem.tmode = "Cross"
1400
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
1401

    
1402
        mem.dtcs_polarity = "".join(dtcs_pol)
1403

    
1404
        mem.extra = RadioSettingGroup("Extra", "extra")
1405

    
1406
        rs = RadioSetting("bcl", "BCL",
1407
                          RadioSettingValueBoolean(_mem.bcl))
1408
        mem.extra.append(rs)
1409

    
1410
        if (_mem.pttid != 0xFF):
1411
            rs = RadioSetting("pttid", "PTT ID",
1412
                            RadioSettingValueList(self.PTTID_LIST,
1413
                                                    self.PTTID_LIST[_mem.pttid]))
1414
            mem.extra.append(rs)
1415

    
1416
        if (_mem.scode != 0xFF):
1417
            rs = RadioSetting("scode", "S-CODE",
1418
                            RadioSettingValueList(self.SCODE_LIST,
1419
                                                    self.SCODE_LIST[_mem.scode]))
1420
            mem.extra.append(rs)
1421

    
1422
        return mem
1423

    
1424
    def set_memory(self, mem):
1425
        _mem = self._memobj.memory[mem.number]
1426

    
1427
        _mem.set_raw("\x00"*16 + "\xff" * 16)
1428

    
1429
        if mem.empty:
1430
            _mem.set_raw("\xff" * 32)
1431
            return
1432

    
1433
        _mem.rxfreq = mem.freq / 10
1434
        
1435
        if mem.duplex == "off":
1436
            for i in range(0, 4):
1437
                _mem.txfreq[i].set_raw("\xFF")
1438
        elif mem.duplex == "split":
1439
            _mem.txfreq = mem.offset / 10
1440
        elif mem.duplex == "+":
1441
            _mem.txfreq = (mem.freq + mem.offset) / 10
1442
        elif mem.duplex == "-":
1443
            _mem.txfreq = (mem.freq - mem.offset) / 10
1444
        else:
1445
            _mem.txfreq = mem.freq / 10
1446

    
1447
        _namelength = self.get_features().valid_name_length
1448
        for i in range(_namelength):
1449
            try:
1450
                _mem.name[i] = mem.name[i]
1451
            except IndexError:
1452
                _mem.name[i] = "\xFF"
1453

    
1454
        rxmode = txmode = ""
1455
        if mem.tmode == "Tone":
1456
            _mem.txtone = int(mem.rtone * 10)
1457
            _mem.rxtone = 0
1458
        elif mem.tmode == "TSQL":
1459
            _mem.txtone = int(mem.ctone * 10)
1460
            _mem.rxtone = int(mem.ctone * 10)
1461
        elif mem.tmode == "DTCS":
1462
            rxmode = txmode = "DTCS"
1463
            _mem.txtone = self.DTCS_CODES.index(mem.dtcs) + 1
1464
            _mem.rxtone = self.DTCS_CODES.index(mem.dtcs) + 1
1465
        elif mem.tmode == "Cross":
1466
            txmode, rxmode = mem.cross_mode.split("->", 1)
1467
            if txmode == "Tone":
1468
                _mem.txtone = int(mem.rtone * 10)
1469
            elif txmode == "DTCS":
1470
                _mem.txtone = self.DTCS_CODES.index(mem.dtcs) + 1
1471
            else:
1472
                _mem.txtone = 0
1473
            if rxmode == "Tone":
1474
                _mem.rxtone = int(mem.ctone * 10)
1475
            elif rxmode == "DTCS":
1476
                _mem.rxtone = self.DTCS_CODES.index(mem.rx_dtcs) + 1
1477
            else:
1478
                _mem.rxtone = 0
1479
        else:
1480
            _mem.rxtone = 0
1481
            _mem.txtone = 0
1482

    
1483
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
1484
            _mem.txtone += 0x69
1485
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
1486
            _mem.rxtone += 0x69
1487

    
1488
        _mem.scan = mem.skip != "S"
1489
        _mem.wide = mem.mode == "NFM"
1490

    
1491
        if mem.power:
1492
            _mem.lowpower = self.POWER_LEVELS.index(mem.power)
1493
        else:
1494
            _mem.lowpower = 0
1495

    
1496
        # extra settings
1497
        if len(mem.extra) > 0:
1498
            # there are setting, parse
1499
            for setting in mem.extra:
1500
                if setting.get_name() == "scode":
1501
                    setattr(_mem, setting.get_name(), str(int(setting.value)))
1502
                else:
1503
                    setattr(_mem, setting.get_name(), setting.value)
1504
        else:
1505
            # there are no extra settings, load defaults
1506
            _mem.bcl = 0
1507
            _mem.pttid = 0
1508
            _mem.scode = 0
1509

    
1510
    def set_settings(self, settings):
1511
        _settings = self._memobj.settings
1512
        _mem = self._memobj
1513
        for element in settings:
1514
            if not isinstance(element, RadioSetting):
1515
                if element.get_name() == "fm_preset":
1516
                    self._set_fm_preset(element)
1517
                else:
1518
                    self.set_settings(element)
1519
                    continue
1520
            else:
1521
                try:
1522
                    name = element.get_name()
1523
                    if "." in name:
1524
                        bits = name.split(".")
1525
                        obj = self._memobj
1526
                        for bit in bits[:-1]:
1527
                            if "/" in bit:
1528
                                bit, index = bit.split("/", 1)
1529
                                index = int(index)
1530
                                obj = getattr(obj, bit)[index]
1531
                            else:
1532
                                obj = getattr(obj, bit)
1533
                        setting = bits[-1]
1534
                    else:
1535
                        obj = _settings
1536
                        setting = element.get_name()
1537

    
1538
                    if element.has_apply_callback():
1539
                        LOG.debug("Using apply callback")
1540
                        element.run_apply_callback()
1541
                    elif element.value.get_mutable():
1542
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1543
                        setattr(obj, setting, element.value)
1544
                except Exception:
1545
                    LOG.debug(element.get_name())
1546
                    raise
1547

    
1548
    def _set_fm_preset(self, settings):
1549
        for element in settings:
1550
            try:
1551
                val = element.value
1552
                if self._memobj.fm_presets <= 108.0 * 10 - 650:
1553
                    value = int(val.get_value() * 10 - 650)
1554
                else:
1555
                    value = int(val.get_value() * 10)
1556
                LOG.debug("Setting fm_presets = %s" % (value))
1557
                if self._bw_shift:
1558
                    value = ((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8)
1559
                self._memobj.fm_presets = value
1560
            except Exception:
1561
                LOG.debug(element.get_name())
1562
                raise
1563

    
1564
    @classmethod
1565
    def match_model(cls, filedata, filename):
1566
        match_size = False
1567
        match_model = False
1568

    
1569
        # testing the file data size
1570
        if len(filedata) in [cls.MEM_TOTAL + len(cls.MODEL)]:
1571
            match_size = True
1572

    
1573
        # testing the firmware model fingerprint
1574
        match_model = model_match(cls, filedata)
1575
        if match_size and match_model:
1576
            return True
1577
        else:
1578
            return False
1579

    
1580

    
1581
@directory.register
1582
class UV17ProGPS(UV17Pro):
1583
    VENDOR = "Baofeng"
1584
    MODEL = "UV-17ProGPS"
1585
    _support_banknames = True
1586
    _magic = MSTRING_UV17PROGPS
1587
    _magics = [b"\x46", b"\x4d", b"\x53\x45\x4E\x44\x21\x05\x0D\x01\x01\x01\x04\x11\x08\x05\x0D\x0D\x01\x11\x0F\x09\x12\x09\x10\x04\x00"]
1588
    _magicResponseLengths = [16, 7, 1]
1589
    _aniid = False
1590
    _vfoscan = True
1591
    _has_gps = True
1592
    _voxsw = True
1593
    _pilot_tone = True
1594
    _send_id_delay = True
1595
    _skey2_short = True
1596
    VALID_BANDS = [UV17Pro._airband, UV17Pro._vhf_range, UV17Pro._vhf2_range,
1597
                   UV17Pro._uhf_range, UV17Pro._uhf2_range]
1598

    
1599
@directory.register
1600
class UV17L(UV17Pro):
1601
    VENDOR = "Baofeng"
1602
    MODEL = "UV-17L"
1603

    
1604

    
(14-14/17)