Project

General

Profile

New Model #10648 » baofeng_uv17Pro.py

Sander van der Wel, 09/26/2023 09:20 AM

 
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 range(50, 2010, 10)]
49
LIST_DTMFST = ["Off", "DT-ST", "ANI-ST", "DT+ANI"]
50
LIST_MODE = ["Channel", "Name", "Frequency"]
51
LIST_OFF1TO9 = ["Off"] + list("123456789")
52
LIST_OFF1TO10 = LIST_OFF1TO9 + ["10"]
53
LIST_OFFAB = ["Off"] + LIST_AB
54
LIST_RESUME = ["TO", "CO", "SE"]
55
LIST_PONMSG = ["Full", "Message"]
56
LIST_PTTID = ["Off", "BOT", "EOT", "Both"]
57
LIST_SCODE = ["%s" % x for x in range(1, 21)]
58
LIST_RPSTE = ["Off"] + ["%s" % x for x in range(1, 11)]
59
LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"]
60
LIST_SHIFTD = ["Off", "+", "-"]
61
LIST_STEDELAY = ["Off"] + ["%s ms" % x for x in range(100, 1100, 100)]
62
LIST_STEP = [str(x) for x in STEPS]
63
LIST_TIMEOUT = ["%s sec" % x for x in range(15, 615, 15)]
64
LIST_TXPOWER = ["High", "Mid", "Low"]
65
LIST_VOICE = ["Off", "English", "Chinese"]
66
LIST_WORKMODE = ["Frequency", "Channel"]
67

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

    
71
STIMEOUT = 1.5
72

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

    
76
    return data[cls.MEM_TOTAL:] == bytes(cls.MODEL, 'utf-8')
77

    
78
    
79
def _crypt(symbolIndex, buffer):
80
    #Some weird encryption is used. From the table below, we only use "CO 7".
81
    tblEncrySymbol = [b"BHT ", b"CO 7", b"A ES", b" EIY", b"M PQ", 
82
                    b"XN Y", b"RVB ", b" HQP", b"W RC", b"MS N", 
83
                    b" SAT", b"K DH", b"ZO R", b"C SL", b"6RB ", 
84
                    b" JCG", b"PN V", b"J PK", b"EK L", b"I LZ"]
85
    tblEncrySymbols = tblEncrySymbol[symbolIndex]
86
    decBuffer=b""
87
    index1 = 0
88
    for index2 in range(len(buffer)):
89
        if ((tblEncrySymbols[index1] != 32) & (buffer[index2] != 0) & (buffer[index2] != 255) & 
90
            (buffer[index2] != tblEncrySymbols[index1]) & (buffer[index2] != (tblEncrySymbols[index1] ^ 255))):
91
            decByte = buffer[index2] ^ tblEncrySymbols[index1]
92
            decBuffer += decByte.to_bytes(1, 'big')
93
        else:
94
            decBuffer += buffer[index2].to_bytes(1, 'big')
95
        index1 = (index1 + 1) % 4
96
    return decBuffer
97

    
98
def _clean_buffer(radio):
99
    radio.pipe.timeout = 0.005
100
    junk = radio.pipe.read(256)
101
    radio.pipe.timeout = STIMEOUT
102
    if junk:
103
        LOG.debug("Got %i bytes of junk before starting" % len(junk))
104

    
105

    
106
def _rawrecv(radio, amount):
107
    """Raw read from the radio device"""
108
    data = ""
109
    try:
110
        data = radio.pipe.read(amount)
111
    except:
112
        msg = "Generic error reading data from radio; check your cable."
113
        raise errors.RadioError(msg)
114

    
115
    if len(data) != amount:
116
        msg = "Error reading data from radio: not the amount of data we want."
117
        raise errors.RadioError(msg)
118

    
119
    return data
120

    
121

    
122
def _rawsend(radio, data):
123
    """Raw send to the radio device"""
124
    try:
125
        #print(data)
126
        #print(radio)
127
        radio.pipe.write(data)
128
    except:
129
        raise errors.RadioError("Error sending data to radio")
130

    
131
def _make_read_frame(addr, length):
132
    """Pack the info in the header format"""
133
    frame = _make_frame(b"\x52", addr, length)
134
    # Return the data
135

    
136
    return frame
137

    
138
def _make_frame(cmd, addr, length, data=""):
139
    """Pack the info in the header format"""
140
    frame = cmd+struct.pack(">i",addr)[2:]+struct.pack("b", length)
141
    # add the data if set
142
    if len(data) != 0:
143
        frame += data
144
    # return the data
145
    return frame
146

    
147

    
148
def _recv(radio, addr, length):
149
    """Get data from the radio """
150
    # read 4 bytes of header
151
    hdr = _rawrecv(radio, 4)
152

    
153
    # read data
154
    data = _rawrecv(radio, length)
155

    
156
    # DEBUG
157
    LOG.info("Response:")
158
    LOG.debug(util.hexprint(hdr + data))
159

    
160
    c, a, l = struct.unpack(">BHB", hdr)
161
    if a != addr or l != length or c != ord("X"):
162
        LOG.error("Invalid answer for block 0x%04x:" % addr)
163
        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
164
        raise errors.RadioError("Unknown response from the radio")
165

    
166
    return data
167

    
168

    
169
def _get_radio_firmware_version(radio):
170
    # There is a problem in new radios where a different firmware version is
171
    # received when directly reading a single block as compared to what is
172
    # received when reading sequential blocks. This causes a mismatch between
173
    # the image firmware version and the radio firmware version when uploading
174
    # an image that came from the same radio. The workaround is to read 1 or
175
    # more consecutive blocks prior to reading the block with the firmware
176
    # version.
177
    #
178
    # Read 2 consecutive blocks to get the radio firmware version.
179
    for addr in range(0x1E80, 0x1F00, radio._recv_block_size):
180
        frame = _make_frame("S", addr, radio._recv_block_size)
181

    
182
        # sending the read request
183
        _rawsend(radio, frame)
184

    
185
        if radio._ack_block and addr != 0x1E80:
186
            ack = _rawrecv(radio, 1)
187
            if ack != b"\x06":
188
                raise errors.RadioError(
189
                    "Radio refused to send block 0x%04x" % addr)
190

    
191
        # now we read
192
        block = _recv(radio, addr, radio._recv_block_size)
193

    
194
        _rawsend(radio, b"\x06")
195
        time.sleep(0.05)
196

    
197
    # get firmware version from the last block read
198
    version = block[48:64]
199
    return version
200

    
201

    
202
def _image_ident_from_data(data, start, stop):
203
    return data[start:stop]
204

    
205

    
206
def _get_image_firmware_version(radio):
207
    return _image_ident_from_data(radio.get_mmap(), radio._fw_ver_start,
208
                                  radio._fw_ver_start + 0x10)
209

    
210
def _sendmagic(radio, magic, response):
211
    _rawsend(radio, magic)
212
    ack = _rawrecv(radio, len(response))
213
    if ack != response:
214
        if ack:
215
            LOG.debug(repr(ack))
216
        raise errors.RadioError("Radio did not respond to enter read mode")
217
    
218
def _do_ident(radio):
219
    """Put the radio in PROGRAM mode & identify it"""
220
    radio.pipe.baudrate = radio.BAUDRATE
221
    radio.pipe.parity = "N"
222
    radio.pipe.timeout = STIMEOUT
223

    
224
    # Flush input buffer
225
    _clean_buffer(radio)
226

    
227
    # Ident radio
228
    magic = radio._magic
229
    _rawsend(radio, magic)
230
    ack = _rawrecv(radio, radio._magic_response_length)
231

    
232
    if not ack.startswith(radio._fingerprint):
233
        if ack:
234
            LOG.debug(repr(ack))
235
        raise errors.RadioError("Radio did not respond as expected (A)")
236

    
237
    return True
238

    
239

    
240
def _ident_radio(radio):
241
    for magic in radio._magic:
242
        error = None
243
        try:
244
            data = _do_ident(radio, magic)
245
            return data
246
        except errors.RadioError as e:
247
            print(e)
248
            error = e
249
            time.sleep(2)
250
    if error:
251
        raise error
252
    raise errors.RadioError("Radio did not respond")
253

    
254

    
255
def _download(radio):
256
    """Get the memory map"""
257

    
258
    # Put radio in program mode and identify it
259
    _do_ident(radio)
260
    for index in range(len(radio._magics)):
261
        _rawsend(radio, radio._magics[index])
262
        _rawrecv(radio, radio._magicResponseLengths[index])
263

    
264
    data = b""
265

    
266
    # UI progress
267
    status = chirp_common.Status()
268
    status.cur = 0
269
    status.max = radio.MEM_TOTAL // radio.BLOCK_SIZE
270
    status.msg = "Cloning from radio..."
271
    radio.status_fn(status)
272

    
273
    for i in range(len(radio.MEM_SIZES)):
274
        MEM_SIZE = radio.MEM_SIZES[i]
275
        MEM_START = radio.MEM_STARTS[i]
276
        for addr in range(MEM_START, MEM_START + MEM_SIZE, radio.BLOCK_SIZE):
277
            frame = _make_read_frame(addr, radio.BLOCK_SIZE)
278
            # DEBUG
279
            LOG.debug("Frame=" + util.hexprint(frame))
280

    
281
            # Sending the read request
282
            _rawsend(radio, frame)
283

    
284
            # Now we read data
285
            d = _rawrecv(radio, radio.BLOCK_SIZE + 4)
286

    
287
            LOG.debug("Response Data= " + util.hexprint(d))
288
            d = _crypt(1, d[4:])
289

    
290
            # Aggregate the data
291
            data += d
292

    
293
            # UI Update
294
            status.cur = len(data) // radio.BLOCK_SIZE
295
            status.msg = "Cloning from radio..."
296
            radio.status_fn(status)
297
    data += bytes(radio.MODEL, 'utf-8')
298
    return data
299

    
300
def _upload(radio):
301
    # Put radio in program mode and identify it
302
    _do_ident(radio)
303
    for index in range(len(radio._magics)):
304
        _rawsend(radio, radio._magics[index])
305
        _rawrecv(radio, radio._magicResponseLengths[index])
306

    
307
    data = b""
308

    
309
    # UI progress
310
    status = chirp_common.Status()
311
    status.cur = 0
312
    status.max = radio.MEM_TOTAL // radio.BLOCK_SIZE
313
    status.msg = "Cloning from radio..."
314
    radio.status_fn(status)
315

    
316
    data_addr = 0x00
317
    for i in range(len(radio.MEM_SIZES)):
318
        MEM_SIZE = radio.MEM_SIZES[i]
319
        MEM_START = radio.MEM_STARTS[i]
320
        for addr in range(MEM_START, MEM_START + MEM_SIZE, radio.BLOCK_SIZE):
321
            data = radio.get_mmap()[data_addr:data_addr + radio.BLOCK_SIZE]
322
            data = _crypt(1, data)
323
            data_addr += radio.BLOCK_SIZE
324

    
325
            frame = _make_frame(b"W", addr, radio.BLOCK_SIZE, data)
326
            # DEBUG
327
            LOG.debug("Frame=" + util.hexprint(frame))
328
            #print()
329
            #print(hex(data_addr))
330
            #print(util.hexprint(frame))
331

    
332
            # Sending the read request
333
            _rawsend(radio, frame)
334

    
335
            # receiving the response
336
            ack = _rawrecv(radio, 1)
337
            #ack = b"\x06"
338
            if ack != b"\x06":
339
                msg = "Bad ack writing block 0x%04x" % addr
340
                raise errors.RadioError(msg)
341

    
342
            # UI Update
343
            status.cur = data_addr // radio.BLOCK_SIZE
344
            status.msg = "Cloning to radio..."
345
            radio.status_fn(status)
346
    data += bytes(radio.MODEL, 'utf-8')
347
    return data
348

    
349

    
350
def _split(rf, f1, f2):
351
    """Returns False if the two freqs are in the same band (no split)
352
    or True otherwise"""
353

    
354
    # determine if the two freqs are in the same band
355
    for low, high in rf.valid_bands:
356
        if f1 >= low and f1 <= high and \
357
                f2 >= low and f2 <= high:
358
            # if the two freqs are on the same Band this is not a split
359
            return False
360

    
361
    # if you get here is because the freq pairs are split
362
    return True
363

    
364
@directory.register
365
class UV17Pro(chirp_common.CloneModeRadio, 
366
              chirp_common.ExperimentalRadio):
367
    """Baofeng UV-17Pro"""
368
    VENDOR = "Baofeng"
369
    MODEL = "UV-17Pro"
370
    NEEDS_COMPAT_SERIAL = False
371

    
372
    MEM_STARTS = [0x0000, 0x9000, 0xA000, 0xD000]
373
    MEM_SIZES =  [0x8000, 0x0040, 0x02C0, 0x0040]
374

    
375
    MEM_TOTAL = 0x8340
376
    BLOCK_SIZE = 0x40
377
    STIMEOUT = 2
378
    BAUDRATE = 115200
379

    
380
    _gmrs = False
381
    _bw_shift = False
382
    _support_banknames = False
383

    
384
    _tri_band = True
385
    _fileid = []
386
    _magic = MSTRING_UV17L
387
    _magic_response_length = 1
388
    _fingerprint = b"\x06"
389
    _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"]
390
    _magicResponseLengths = [16, 15, 1]
391
    _fw_ver_start = 0x1EF0
392
    _recv_block_size = 0x40
393
    _mem_size = MEM_TOTAL
394
    _ack_block = True
395

    
396
    MODES = ["NFM", "FM"]
397
    VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
398
        "!@#$%^&*()+-=[]:\";'<>?,./"
399
    LENGTH_NAME = 12
400
    SKIP_VALUES = ["", "S"]
401
    DTCS_CODES = tuple(sorted(chirp_common.DTCS_CODES + (645,)))
402
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
403
                    chirp_common.PowerLevel("Low",  watts=1.00)]
404
    _airband = (108000000, 136000000)
405
    _vhf_range = (136000000, 174000000)
406
    _vhf2_range = (200000000, 260000000)
407
    _uhf_range = (400000000, 520000000)
408
    _uhf2_range = (350000000, 390000000)
409

    
410
    VALID_BANDS = [_vhf_range, _vhf2_range,
411
                   _uhf_range]
412
    PTTID_LIST = LIST_PTTID
413
    SCODE_LIST = LIST_SCODE
414

    
415
    MEM_FORMAT = """
416
    #seekto 0x0000;
417
    struct {
418
      lbcd rxfreq[4];
419
      lbcd txfreq[4];
420
      ul16 rxtone;
421
      ul16 txtone;
422
      u8 scode:8;
423
      u8 pttid:8;
424
      u8 lowpower:8;
425
      u8 unknown1:1,
426
         wide:1,
427
         sqmode:2,
428
         bcl:1,
429
         scan:1,
430
         unknown2:1,
431
         fhss:1;
432
      u8 unknown3:8;
433
      u8 unknown4:8;
434
      u8 unknown5:8;
435
      u8 unknown6:8;
436
      char name[12];
437
    } memory[1000];
438

    
439
    #seekto 0x8240;
440
    struct {
441
      char name1[12];
442
      u32 unknown1;
443
      char name2[12];
444
      u32 unknown2;
445
      char name3[12];
446
      u32 unknown3;
447
      char name4[12];
448
      u32 unknown4;
449
      char name5[12];
450
      u32 unknown5;
451
      char name6[12];
452
      u32 unknown6;
453
      char name7[12];
454
      u32 unknown7;
455
      char name8[12];
456
      u32 unknown8;
457
      char name9[12];
458
      u32 unknown9;
459
      char name10[12];
460
      u32 unknown10;
461
    } bank_names;
462

    
463
    #seekto 0x8000;
464
    struct {
465
      char unknown[64];  
466
    } settings;
467
    """
468

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

    
492
    def process_mmap(self):
493
        """Process the mem map into the mem object"""
494
        self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap)
495

    
496
    def get_settings(self):
497
        """Translate the bit in the mem_struct into settings in the UI"""
498
        _mem = self._memobj
499
        basic = RadioSettingGroup("basic", "Basic Settings")
500
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
501
        other = RadioSettingGroup("other", "Other Settings")
502
        work = RadioSettingGroup("work", "Work Mode Settings")
503
        fm_preset = RadioSettingGroup("fm_preset", "FM Preset")
504
        dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings")
505
        service = RadioSettingGroup("service", "Service Settings")
506
        top = RadioSettings(basic, advanced, other, work, fm_preset, dtmfe,
507
                            service)
508
     
509
        def _filterName(name):
510
            fname = ""
511
            for char in name:
512
                if ord(str(char)) == 255:
513
                    break
514
                fname += str(char)
515
            return fname
516
        
517
        if self._support_banknames:
518
            _msg = _mem.bank_names
519
            rs = RadioSetting("bank_names.name1", "Bank name 1",
520
                            RadioSettingValueString(
521
                                0, 12, _filterName(_msg.name1)))
522
            other.append(rs)
523

    
524
            _msg = _mem.bank_names
525
            rs = RadioSetting("bank_names.name2", "Bank name 2",
526
                            RadioSettingValueString(
527
                                0, 12, _filterName(_msg.name2)))
528
            
529
            other.append(rs)
530
            _msg = _mem.bank_names
531
            rs = RadioSetting("bank_names.name3", "Bank name 3",
532
                            RadioSettingValueString(
533
                                0, 12, _filterName(_msg.name3)))
534
            
535
            other.append(rs)
536
            _msg = _mem.bank_names
537
            rs = RadioSetting("bank_names.name4", "Bank name 4",
538
                            RadioSettingValueString(
539
                                0, 12, _filterName(_msg.name4)))
540
            
541
            other.append(rs)
542
            _msg = _mem.bank_names
543
            rs = RadioSetting("bank_names.name5", "Bank name 5",
544
                            RadioSettingValueString(
545
                                0, 12, _filterName(_msg.name5)))
546
            
547
            other.append(rs)
548
            _msg = _mem.bank_names
549
            rs = RadioSetting("bank_names.name6", "Bank name 6",
550
                            RadioSettingValueString(
551
                                0, 12, _filterName(_msg.name6)))
552
            
553
            other.append(rs)
554
            _msg = _mem.bank_names
555
            rs = RadioSetting("bank_names.name7", "Bank name 7",
556
                            RadioSettingValueString(
557
                                0, 12, _filterName(_msg.name7)))
558
            
559
            other.append(rs)
560
            _msg = _mem.bank_names
561
            rs = RadioSetting("bank_names.name8", "Bank name 8",
562
                            RadioSettingValueString(
563
                                0, 12, _filterName(_msg.name8)))
564
            
565
            other.append(rs)
566
            _msg = _mem.bank_names
567
            rs = RadioSetting("bank_names.name9", "Bank name 9",
568
                            RadioSettingValueString(
569
                                0, 12, _filterName(_msg.name9)))
570
            other.append(rs)
571

    
572
            _msg = _mem.bank_names
573
            rs = RadioSetting("bank_names.name10", "Bank name 10",
574
                            RadioSettingValueString(
575
                                0, 12, _filterName(_msg.name10)))
576
            other.append(rs)
577

    
578

    
579
        return top
580
    
581
        # TODO: implement settings 
582

    
583
        # Basic settings
584
        if _mem.settings.squelch > 0x09:
585
            val = 0x00
586
        else:
587
            val = _mem.settings.squelch
588
        rs = RadioSetting("settings.squelch", "Squelch",
589
                          RadioSettingValueList(
590
                              LIST_OFF1TO9, LIST_OFF1TO9[val]))
591
        basic.append(rs)
592

    
593
        if _mem.settings.save > 0x04:
594
            val = 0x00
595
        else:
596
            val = _mem.settings.save
597
        rs = RadioSetting("settings.save", "Battery Saver",
598
                          RadioSettingValueList(
599
                              LIST_SAVE, LIST_SAVE[val]))
600
        basic.append(rs)
601

    
602
        if _mem.settings.vox > 0x0A:
603
            val = 0x00
604
        else:
605
            val = _mem.settings.vox
606
        rs = RadioSetting("settings.vox", "Vox",
607
                          RadioSettingValueList(
608
                              LIST_OFF1TO10, LIST_OFF1TO10[val]))
609
        basic.append(rs)
610

    
611
        if _mem.settings.abr > 0x0A:
612
            val = 0x00
613
        else:
614
            val = _mem.settings.abr
615
        rs = RadioSetting("settings.abr", "Backlight Timeout",
616
                          RadioSettingValueList(
617
                              LIST_OFF1TO10, LIST_OFF1TO10[val]))
618
        basic.append(rs)
619

    
620
        rs = RadioSetting("settings.tdr", "Dual Watch",
621
                          RadioSettingValueBoolean(_mem.settings.tdr))
622
        basic.append(rs)
623

    
624
        rs = RadioSetting("settings.beep", "Beep",
625
                          RadioSettingValueBoolean(_mem.settings.beep))
626
        basic.append(rs)
627

    
628
        if _mem.settings.timeout > 0x27:
629
            val = 0x03
630
        else:
631
            val = _mem.settings.timeout
632
        rs = RadioSetting("settings.timeout", "Timeout Timer",
633
                          RadioSettingValueList(
634
                              LIST_TIMEOUT, LIST_TIMEOUT[val]))
635
        basic.append(rs)
636

    
637
        if _mem.settings.voice > 0x02:
638
            val = 0x01
639
        else:
640
            val = _mem.settings.voice
641
        rs = RadioSetting("settings.voice", "Voice Prompt",
642
                          RadioSettingValueList(
643
                              LIST_VOICE, LIST_VOICE[val]))
644
        basic.append(rs)
645

    
646
        rs = RadioSetting("settings.dtmfst", "DTMF Sidetone",
647
                          RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
648
                              _mem.settings.dtmfst]))
649
        basic.append(rs)
650

    
651
        if _mem.settings.screv > 0x02:
652
            val = 0x01
653
        else:
654
            val = _mem.settings.screv
655
        rs = RadioSetting("settings.screv", "Scan Resume",
656
                          RadioSettingValueList(
657
                              LIST_RESUME, LIST_RESUME[val]))
658
        basic.append(rs)
659

    
660
        rs = RadioSetting("settings.pttid", "When to send PTT ID",
661
                          RadioSettingValueList(LIST_PTTID, LIST_PTTID[
662
                              _mem.settings.pttid]))
663
        basic.append(rs)
664

    
665
        if _mem.settings.pttlt > 0x1E:
666
            val = 0x05
667
        else:
668
            val = _mem.settings.pttlt
669
        rs = RadioSetting("pttlt", "PTT ID Delay",
670
                          RadioSettingValueInteger(0, 50, val))
671
        basic.append(rs)
672

    
673
        rs = RadioSetting("settings.mdfa", "Display Mode (A)",
674
                          RadioSettingValueList(LIST_MODE, LIST_MODE[
675
                              _mem.settings.mdfa]))
676
        basic.append(rs)
677

    
678
        rs = RadioSetting("settings.mdfb", "Display Mode (B)",
679
                          RadioSettingValueList(LIST_MODE, LIST_MODE[
680
                              _mem.settings.mdfb]))
681
        basic.append(rs)
682

    
683
        rs = RadioSetting("settings.autolk", "Automatic Key Lock",
684
                          RadioSettingValueBoolean(_mem.settings.autolk))
685
        basic.append(rs)
686

    
687
        rs = RadioSetting("settings.wtled", "Standby LED Color",
688
                          RadioSettingValueList(
689
                              LIST_COLOR, LIST_COLOR[_mem.settings.wtled]))
690
        basic.append(rs)
691

    
692
        rs = RadioSetting("settings.rxled", "RX LED Color",
693
                          RadioSettingValueList(
694
                              LIST_COLOR, LIST_COLOR[_mem.settings.rxled]))
695
        basic.append(rs)
696

    
697
        rs = RadioSetting("settings.txled", "TX LED Color",
698
                          RadioSettingValueList(
699
                              LIST_COLOR, LIST_COLOR[_mem.settings.txled]))
700
        basic.append(rs)
701

    
702
        val = _mem.settings.almod
703
        rs = RadioSetting("settings.almod", "Alarm Mode",
704
                          RadioSettingValueList(
705
                              LIST_ALMOD, LIST_ALMOD[val]))
706
        basic.append(rs)
707

    
708
        if _mem.settings.tdrab > 0x02:
709
            val = 0x00
710
        else:
711
            val = _mem.settings.tdrab
712
        rs = RadioSetting("settings.tdrab", "Dual Watch TX Priority",
713
                          RadioSettingValueList(
714
                              LIST_OFFAB, LIST_OFFAB[val]))
715
        basic.append(rs)
716

    
717
        rs = RadioSetting("settings.ste", "Squelch Tail Eliminate (HT to HT)",
718
                          RadioSettingValueBoolean(_mem.settings.ste))
719
        basic.append(rs)
720

    
721
        if _mem.settings.rpste > 0x0A:
722
            val = 0x00
723
        else:
724
            val = _mem.settings.rpste
725
        rs = RadioSetting("settings.rpste",
726
                          "Squelch Tail Eliminate (repeater)",
727
                          RadioSettingValueList(
728
                              LIST_RPSTE, LIST_RPSTE[val]))
729
        basic.append(rs)
730

    
731
        if _mem.settings.rptrl > 0x0A:
732
            val = 0x00
733
        else:
734
            val = _mem.settings.rptrl
735
        rs = RadioSetting("settings.rptrl", "STE Repeater Delay",
736
                          RadioSettingValueList(
737
                              LIST_STEDELAY, LIST_STEDELAY[val]))
738
        basic.append(rs)
739

    
740
        rs = RadioSetting("settings.ponmsg", "Power-On Message",
741
                          RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
742
                              _mem.settings.ponmsg]))
743
        basic.append(rs)
744

    
745
        rs = RadioSetting("settings.roger", "Roger Beep",
746
                          RadioSettingValueBoolean(_mem.settings.roger))
747
        basic.append(rs)
748

    
749
        # Advanced settings
750
        rs = RadioSetting("settings.reset", "RESET Menu",
751
                          RadioSettingValueBoolean(_mem.settings.reset))
752
        advanced.append(rs)
753

    
754
        rs = RadioSetting("settings.menu", "All Menus",
755
                          RadioSettingValueBoolean(_mem.settings.menu))
756
        advanced.append(rs)
757

    
758
        rs = RadioSetting("settings.fmradio", "Broadcast FM Radio",
759
                          RadioSettingValueBoolean(_mem.settings.fmradio))
760
        advanced.append(rs)
761

    
762
        rs = RadioSetting("settings.alarm", "Alarm Sound",
763
                          RadioSettingValueBoolean(_mem.settings.alarm))
764
        advanced.append(rs)
765

    
766
        # Other settings
767
        def _filter(name):
768
            filtered = ""
769
            for char in str(name):
770
                if char in chirp_common.CHARSET_ASCII:
771
                    filtered += char
772
                else:
773
                    filtered += " "
774
            return filtered
775

    
776
        _msg = _mem.firmware_msg
777
        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
778
        val.set_mutable(False)
779
        rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
780
        other.append(rs)
781

    
782
        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
783
        val.set_mutable(False)
784
        rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
785
        other.append(rs)
786

    
787
        _msg = _mem.sixpoweron_msg
788
        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
789
        val.set_mutable(False)
790
        rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", val)
791
        other.append(rs)
792
        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
793
        val.set_mutable(False)
794
        rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", val)
795
        other.append(rs)
796

    
797
        _msg = _mem.poweron_msg
798
        rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
799
                          RadioSettingValueString(
800
                              0, 7, _filter(_msg.line1)))
801
        other.append(rs)
802
        rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
803
                          RadioSettingValueString(
804
                              0, 7, _filter(_msg.line2)))
805
        other.append(rs)
806

    
807
        lower = 130
808
        upper = 179
809
        rs = RadioSetting("limits.vhf.lower", "VHF Lower Limit (MHz)",
810
                          RadioSettingValueInteger(
811
                              lower, upper, _mem.limits.vhf.lower))
812
        other.append(rs)
813

    
814
        rs = RadioSetting("limits.vhf.upper", "VHF Upper Limit (MHz)",
815
                          RadioSettingValueInteger(
816
                              lower, upper, _mem.limits.vhf.upper))
817
        other.append(rs)
818

    
819
        if self._tri_band:
820
            lower = 200
821
            upper = 260
822
            rs = RadioSetting("limits.vhf2.lower", "VHF2 Lower Limit (MHz)",
823
                              RadioSettingValueInteger(
824
                                  lower, upper, _mem.limits.vhf2.lower))
825
            other.append(rs)
826

    
827
            rs = RadioSetting("limits.vhf2.upper", "VHF2 Upper Limit (MHz)",
828
                              RadioSettingValueInteger(
829
                                  lower, upper, _mem.limits.vhf2.upper))
830
            other.append(rs)
831

    
832
        lower = 400
833
        upper = 520
834
        rs = RadioSetting("limits.uhf.lower", "UHF Lower Limit (MHz)",
835
                          RadioSettingValueInteger(
836
                              lower, upper, _mem.limits.uhf.lower))
837
        other.append(rs)
838

    
839
        rs = RadioSetting("limits.uhf.upper", "UHF Upper Limit (MHz)",
840
                          RadioSettingValueInteger(
841
                              lower, upper, _mem.limits.uhf.upper))
842
        other.append(rs)
843

    
844
        # Work mode settings
845
        rs = RadioSetting("settings.displayab", "Display",
846
                          RadioSettingValueList(
847
                              LIST_AB, LIST_AB[_mem.settings.displayab]))
848
        work.append(rs)
849

    
850
        rs = RadioSetting("settings.workmode", "VFO/MR Mode",
851
                          RadioSettingValueList(
852
                              LIST_WORKMODE,
853
                              LIST_WORKMODE[_mem.settings.workmode]))
854
        work.append(rs)
855

    
856
        rs = RadioSetting("settings.keylock", "Keypad Lock",
857
                          RadioSettingValueBoolean(_mem.settings.keylock))
858
        work.append(rs)
859

    
860
        rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
861
                          RadioSettingValueInteger(0, 127,
862
                                                   _mem.wmchannel.mrcha))
863
        work.append(rs)
864

    
865
        rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
866
                          RadioSettingValueInteger(0, 127,
867
                                                   _mem.wmchannel.mrchb))
868
        work.append(rs)
869

    
870
        def convert_bytes_to_freq(bytes):
871
            real_freq = 0
872
            for byte in bytes:
873
                real_freq = (real_freq * 10) + byte
874
            return chirp_common.format_freq(real_freq * 10)
875

    
876
        def my_validate(value):
877
            value = chirp_common.parse_freq(value)
878
            msg = ("Can't be less than %i.0000")
879
            if value > 99000000 and value < 130 * 1000000:
880
                raise InvalidValueError(msg % (130))
881
            msg = ("Can't be between %i.9975-%i.0000")
882
            if (179 + 1) * 1000000 <= value and value < 400 * 1000000:
883
                raise InvalidValueError(msg % (179, 400))
884
            msg = ("Can't be greater than %i.9975")
885
            if value > 99000000 and value > (520 + 1) * 1000000:
886
                raise InvalidValueError(msg % (520))
887
            return chirp_common.format_freq(value)
888

    
889
        def apply_freq(setting, obj):
890
            value = chirp_common.parse_freq(str(setting.value)) / 10
891
            for i in range(7, -1, -1):
892
                obj.freq[i] = value % 10
893
                value /= 10
894

    
895
        val1a = RadioSettingValueString(0, 10,
896
                                        convert_bytes_to_freq(_mem.vfo.a.freq))
897
        val1a.set_validate_callback(my_validate)
898
        rs = RadioSetting("vfo.a.freq", "VFO A Frequency", val1a)
899
        rs.set_apply_callback(apply_freq, _mem.vfo.a)
900
        work.append(rs)
901

    
902
        val1b = RadioSettingValueString(0, 10,
903
                                        convert_bytes_to_freq(_mem.vfo.b.freq))
904
        val1b.set_validate_callback(my_validate)
905
        rs = RadioSetting("vfo.b.freq", "VFO B Frequency", val1b)
906
        rs.set_apply_callback(apply_freq, _mem.vfo.b)
907
        work.append(rs)
908

    
909
        rs = RadioSetting("vfo.a.sftd", "VFO A Shift",
910
                          RadioSettingValueList(
911
                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.a.sftd]))
912
        work.append(rs)
913

    
914
        rs = RadioSetting("vfo.b.sftd", "VFO B Shift",
915
                          RadioSettingValueList(
916
                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.b.sftd]))
917
        work.append(rs)
918

    
919
        def convert_bytes_to_offset(bytes):
920
            real_offset = 0
921
            for byte in bytes:
922
                real_offset = (real_offset * 10) + byte
923
            return chirp_common.format_freq(real_offset * 1000)
924

    
925
        def apply_offset(setting, obj):
926
            value = chirp_common.parse_freq(str(setting.value)) / 1000
927
            for i in range(5, -1, -1):
928
                obj.offset[i] = value % 10
929
                value /= 10
930

    
931
        val1a = RadioSettingValueString(
932
                    0, 10, convert_bytes_to_offset(_mem.vfo.a.offset))
933
        rs = RadioSetting("vfo.a.offset",
934
                          "VFO A Offset", val1a)
935
        rs.set_apply_callback(apply_offset, _mem.vfo.a)
936
        work.append(rs)
937

    
938
        val1b = RadioSettingValueString(
939
                    0, 10, convert_bytes_to_offset(_mem.vfo.b.offset))
940
        rs = RadioSetting("vfo.b.offset",
941
                          "VFO B Offset", val1b)
942
        rs.set_apply_callback(apply_offset, _mem.vfo.b)
943
        work.append(rs)
944

    
945
        def apply_txpower_listvalue(setting, obj):
946
            LOG.debug("Setting value: " + str(
947
                      setting.value) + " from list")
948
            val = str(setting.value)
949
            index = TXP_CHOICES.index(val)
950
            val = TXP_VALUES[index]
951
            obj.set_value(val)
952

    
953
        if self._tri_band:
954
            if _mem.vfo.a.txpower3 in TXP_VALUES:
955
                idx = TXP_VALUES.index(_mem.vfo.a.txpower3)
956
            else:
957
                idx = TXP_VALUES.index(0x00)
958
            rs = RadioSettingValueList(TXP_CHOICES, TXP_CHOICES[idx])
959
            rset = RadioSetting("vfo.a.txpower3", "VFO A Power", rs)
960
            rset.set_apply_callback(apply_txpower_listvalue,
961
                                    _mem.vfo.a.txpower3)
962
            work.append(rset)
963

    
964
            if _mem.vfo.b.txpower3 in TXP_VALUES:
965
                idx = TXP_VALUES.index(_mem.vfo.b.txpower3)
966
            else:
967
                idx = TXP_VALUES.index(0x00)
968
            rs = RadioSettingValueList(TXP_CHOICES, TXP_CHOICES[idx])
969
            rset = RadioSetting("vfo.b.txpower3", "VFO B Power", rs)
970
            rset.set_apply_callback(apply_txpower_listvalue,
971
                                    _mem.vfo.b.txpower3)
972
            work.append(rset)
973
        else:
974
            rs = RadioSetting("vfo.a.txpower3", "VFO A Power",
975
                              RadioSettingValueList(
976
                                  LIST_TXPOWER,
977
                                  LIST_TXPOWER[min(_mem.vfo.a.txpower3, 0x02)]
978
                                               ))
979
            work.append(rs)
980

    
981
            rs = RadioSetting("vfo.b.txpower3", "VFO B Power",
982
                              RadioSettingValueList(
983
                                  LIST_TXPOWER,
984
                                  LIST_TXPOWER[min(_mem.vfo.b.txpower3, 0x02)]
985
                                               ))
986
            work.append(rs)
987

    
988
        rs = RadioSetting("vfo.a.widenarr", "VFO A Bandwidth",
989
                          RadioSettingValueList(
990
                              LIST_BANDWIDTH,
991
                              LIST_BANDWIDTH[_mem.vfo.a.widenarr]))
992
        work.append(rs)
993

    
994
        rs = RadioSetting("vfo.b.widenarr", "VFO B Bandwidth",
995
                          RadioSettingValueList(
996
                              LIST_BANDWIDTH,
997
                              LIST_BANDWIDTH[_mem.vfo.b.widenarr]))
998
        work.append(rs)
999

    
1000
        rs = RadioSetting("vfo.a.scode", "VFO A S-CODE",
1001
                          RadioSettingValueList(
1002
                              LIST_SCODE,
1003
                              LIST_SCODE[_mem.vfo.a.scode]))
1004
        work.append(rs)
1005

    
1006
        rs = RadioSetting("vfo.b.scode", "VFO B S-CODE",
1007
                          RadioSettingValueList(
1008
                              LIST_SCODE,
1009
                              LIST_SCODE[_mem.vfo.b.scode]))
1010
        work.append(rs)
1011

    
1012
        rs = RadioSetting("vfo.a.step", "VFO A Tuning Step",
1013
                          RadioSettingValueList(
1014
                              LIST_STEP, LIST_STEP[_mem.vfo.a.step]))
1015
        work.append(rs)
1016
        rs = RadioSetting("vfo.b.step", "VFO B Tuning Step",
1017
                          RadioSettingValueList(
1018
                              LIST_STEP, LIST_STEP[_mem.vfo.b.step]))
1019
        work.append(rs)
1020

    
1021
        # broadcast FM settings
1022
        value = self._memobj.fm_presets
1023
        value_shifted = ((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8)
1024
        if value_shifted >= 65.0 * 10 and value_shifted <= 108.0 * 10:
1025
            # storage method 3 (discovered 2022)
1026
            self._bw_shift = True
1027
            preset = value_shifted / 10.0
1028
        elif value >= 65.0 * 10 and value <= 108.0 * 10:
1029
            # storage method 2
1030
            preset = value / 10.0
1031
        elif value <= 108.0 * 10 - 650:
1032
            # original storage method (2012)
1033
            preset = value / 10.0 + 65
1034
        else:
1035
            # unknown (undiscovered method or no FM chip?)
1036
            preset = False
1037
        if preset:
1038
            rs = RadioSettingValueFloat(65, 108.0, preset, 0.1, 1)
1039
            rset = RadioSetting("fm_presets", "FM Preset(MHz)", rs)
1040
            fm_preset.append(rset)
1041

    
1042
        # DTMF settings
1043
        def apply_code(setting, obj, length):
1044
            code = []
1045
            for j in range(0, length):
1046
                try:
1047
                    code.append(DTMF_CHARS.index(str(setting.value)[j]))
1048
                except IndexError:
1049
                    code.append(0xFF)
1050
            obj.code = code
1051

    
1052
        for i in range(0, 15):
1053
            _codeobj = self._memobj.pttid[i].code
1054
            _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
1055
            val = RadioSettingValueString(0, 5, _code, False)
1056
            val.set_charset(DTMF_CHARS)
1057
            pttid = RadioSetting("pttid/%i.code" % i,
1058
                                 "Signal Code %i" % (i + 1), val)
1059
            pttid.set_apply_callback(apply_code, self._memobj.pttid[i], 5)
1060
            dtmfe.append(pttid)
1061

    
1062
        if _mem.ani.dtmfon > 0xC3:
1063
            val = 0x03
1064
        else:
1065
            val = _mem.ani.dtmfon
1066
        rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
1067
                          RadioSettingValueList(LIST_DTMFSPEED,
1068
                                                LIST_DTMFSPEED[val]))
1069
        dtmfe.append(rs)
1070

    
1071
        if _mem.ani.dtmfoff > 0xC3:
1072
            val = 0x03
1073
        else:
1074
            val = _mem.ani.dtmfoff
1075
        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
1076
                          RadioSettingValueList(LIST_DTMFSPEED,
1077
                                                LIST_DTMFSPEED[val]))
1078
        dtmfe.append(rs)
1079

    
1080
        _codeobj = self._memobj.ani.code
1081
        _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
1082
        val = RadioSettingValueString(0, 5, _code, False)
1083
        val.set_charset(DTMF_CHARS)
1084
        rs = RadioSetting("ani.code", "ANI Code", val)
1085
        rs.set_apply_callback(apply_code, self._memobj.ani, 5)
1086
        dtmfe.append(rs)
1087

    
1088
        rs = RadioSetting("ani.aniid", "When to send ANI ID",
1089
                          RadioSettingValueList(LIST_PTTID,
1090
                                                LIST_PTTID[_mem.ani.aniid]))
1091
        dtmfe.append(rs)
1092

    
1093
        # Service settings
1094
        for band in ["vhf", "uhf"]:
1095
            for index in range(0, 10):
1096
                key = "squelch.%s.sql%i" % (band, index)
1097
                if band == "vhf":
1098
                    _obj = self._memobj.squelch.vhf
1099
                elif band == "uhf":
1100
                    _obj = self._memobj.squelch.uhf
1101
                val = RadioSettingValueInteger(0, 123,
1102
                                               getattr(
1103
                                                   _obj, "sql%i" % (index)))
1104
                if index == 0:
1105
                    val.set_mutable(False)
1106
                name = "%s Squelch %i" % (band.upper(), index)
1107
                rs = RadioSetting(key, name, val)
1108
                service.append(rs)
1109

    
1110
        return top
1111
    
1112
    def sync_in(self):
1113
        """Download from radio"""
1114
        try:
1115
            data = _download(self)
1116
        except errors.RadioError:
1117
            # Pass through any real errors we raise
1118
            raise
1119
        except:
1120
            # If anything unexpected happens, make sure we raise
1121
            # a RadioError and log the problem
1122
            LOG.exception('Unexpected error during download')
1123
            raise errors.RadioError('Unexpected error communicating '
1124
                                    'with the radio')
1125
        self._mmap = memmap.MemoryMapBytes(data)
1126

    
1127
        self.process_mmap()
1128

    
1129
    def sync_out(self):
1130
        """Upload to radio"""
1131
        try:
1132
            _upload(self)
1133
        except errors.RadioError:
1134
            raise
1135
        except Exception:
1136
            # If anything unexpected happens, make sure we raise
1137
            # a RadioError and log the problem
1138
            LOG.exception('Unexpected error during upload')
1139
            raise errors.RadioError('Unexpected error communicating '
1140
                                    'with the radio')
1141

    
1142
    def get_features(self):
1143
        """Get the radio's features"""
1144

    
1145
        rf = chirp_common.RadioFeatures()
1146
        rf.has_settings = True
1147
        rf.has_bank = False
1148
        rf.has_tuning_step = False
1149
        rf.can_odd_split = True
1150
        rf.has_name = True
1151
        rf.has_offset = True
1152
        rf.has_mode = True
1153
        rf.has_dtcs = True
1154
        rf.has_rx_dtcs = True
1155
        rf.has_dtcs_polarity = True
1156
        rf.has_ctone = True
1157
        rf.has_cross = True
1158
        rf.valid_modes = self.MODES
1159
        rf.valid_characters = self.VALID_CHARS
1160
        rf.valid_name_length = self.LENGTH_NAME
1161
        if self._gmrs:
1162
            rf.valid_duplexes = ["", "+", "off"]
1163
        else:
1164
            rf.valid_duplexes = ["", "-", "+", "split", "off"]
1165
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
1166
        rf.valid_cross_modes = [
1167
            "Tone->Tone",
1168
            "DTCS->",
1169
            "->DTCS",
1170
            "Tone->DTCS",
1171
            "DTCS->Tone",
1172
            "->Tone",
1173
            "DTCS->DTCS"]
1174
        rf.valid_skips = self.SKIP_VALUES
1175
        rf.valid_dtcs_codes = self.DTCS_CODES
1176
        rf.memory_bounds = (0, 999)
1177
        rf.valid_power_levels = self.POWER_LEVELS
1178
        rf.valid_bands = self.VALID_BANDS
1179
        rf.valid_tuning_steps = STEPS
1180

    
1181
        return rf
1182

    
1183
    def _is_txinh(self, _mem):
1184
        raw_tx = ""
1185
        for i in range(0, 4):
1186
            raw_tx += _mem.txfreq[i].get_raw()
1187
        return raw_tx == "\xFF\xFF\xFF\xFF"
1188

    
1189
    def get_memory(self, number):
1190
        _mem = self._memobj.memory[number]
1191
        #_nam = self._memobj.names[number]
1192
        #print(number)
1193
        #print(_mem)
1194
        #print(_nam)
1195

    
1196
        mem = chirp_common.Memory()
1197
        mem.number = number
1198

    
1199
        if _mem.get_raw()[0] == "\xff":
1200
            mem.empty = True
1201
            return mem
1202

    
1203
        mem.freq = int(_mem.rxfreq) * 10
1204

    
1205
        if self._is_txinh(_mem):
1206
            # TX freq not set
1207
            mem.duplex = "off"
1208
            mem.offset = 0
1209
        else:
1210
            # TX freq set
1211
            offset = (int(_mem.txfreq) * 10) - mem.freq
1212
            if offset != 0:
1213
                if _split(self.get_features(), mem.freq, int(
1214
                          _mem.txfreq) * 10):
1215
                    mem.duplex = "split"
1216
                    mem.offset = int(_mem.txfreq) * 10
1217
                elif offset < 0:
1218
                    mem.offset = abs(offset)
1219
                    mem.duplex = "-"
1220
                elif offset > 0:
1221
                    mem.offset = offset
1222
                    mem.duplex = "+"
1223
            else:
1224
                mem.offset = 0
1225
    
1226
        for char in _mem.name:
1227
            if str(char) == "\xFF":
1228
                char = " "  # The OEM software may have 0xFF mid-name
1229
            mem.name += str(char)
1230
        mem.name = mem.name.rstrip()
1231

    
1232
        if not _mem.scan:
1233
            mem.skip = "S"
1234

    
1235
        levels = self.POWER_LEVELS
1236
        try:
1237
            mem.power = levels[_mem.lowpower]
1238
        except IndexError:
1239
            LOG.error("Radio reported invalid power level %s (in %s)" %
1240
                        (_mem.power, levels))
1241
            mem.power = levels[0]
1242

    
1243
        mem.mode = _mem.wide and "NFM" or  "FM"
1244

    
1245
        dtcs_pol = ["N", "N"]
1246

    
1247
        if _mem.txtone in [0, 0xFFFF]:
1248
            txmode = ""
1249
        elif _mem.txtone >= 0x0258:
1250
            txmode = "Tone"
1251
            mem.rtone = int(_mem.txtone) / 10.0
1252
        elif _mem.txtone <= 0x0258:
1253
            txmode = "DTCS"
1254
            if _mem.txtone > 0x69:
1255
                index = _mem.txtone - 0x6A
1256
                dtcs_pol[0] = "R"
1257
            else:
1258
                index = _mem.txtone - 1
1259
            mem.dtcs = self.DTCS_CODES[index]
1260
        else:
1261
            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
1262

    
1263
        if _mem.rxtone in [0, 0xFFFF]:
1264
            rxmode = ""
1265
        elif _mem.rxtone >= 0x0258:
1266
            rxmode = "Tone"
1267
            mem.ctone = int(_mem.rxtone) / 10.0
1268
        elif _mem.rxtone <= 0x0258:
1269
            rxmode = "DTCS"
1270
            if _mem.rxtone >= 0x6A:
1271
                index = _mem.rxtone - 0x6A
1272
                dtcs_pol[1] = "R"
1273
            else:
1274
                index = _mem.rxtone - 1
1275
            mem.rx_dtcs = self.DTCS_CODES[index]
1276
        else:
1277
            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
1278

    
1279
        if txmode == "Tone" and not rxmode:
1280
            mem.tmode = "Tone"
1281
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
1282
            mem.tmode = "TSQL"
1283
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
1284
            mem.tmode = "DTCS"
1285
        elif rxmode or txmode:
1286
            mem.tmode = "Cross"
1287
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
1288

    
1289
        mem.dtcs_polarity = "".join(dtcs_pol)
1290

    
1291
        mem.extra = RadioSettingGroup("Extra", "extra")
1292

    
1293
        rs = RadioSetting("bcl", "BCL",
1294
                          RadioSettingValueBoolean(_mem.bcl))
1295
        mem.extra.append(rs)
1296

    
1297
        rs = RadioSetting("pttid", "PTT ID",
1298
                          RadioSettingValueList(self.PTTID_LIST,
1299
                                                self.PTTID_LIST[_mem.pttid]))
1300
        mem.extra.append(rs)
1301

    
1302
        rs = RadioSetting("scode", "S-CODE",
1303
                          RadioSettingValueList(self.SCODE_LIST,
1304
                                                self.SCODE_LIST[_mem.scode]))
1305
        mem.extra.append(rs)
1306

    
1307
        return mem
1308

    
1309
    def set_memory(self, mem):
1310
        _mem = self._memobj.memory[mem.number]
1311

    
1312
        _mem.set_raw("\x00"*16 + "\xff" * 16)
1313

    
1314
        if mem.empty:
1315
            _mem.set_raw("\xff" * 32)
1316
            return
1317

    
1318
        _mem.rxfreq = mem.freq / 10
1319
        
1320
        if mem.duplex == "off":
1321
            for i in range(0, 4):
1322
                _mem.txfreq[i].set_raw("\xFF")
1323
        elif mem.duplex == "split":
1324
            _mem.txfreq = mem.offset / 10
1325
        elif mem.duplex == "+":
1326
            _mem.txfreq = (mem.freq + mem.offset) / 10
1327
        elif mem.duplex == "-":
1328
            _mem.txfreq = (mem.freq - mem.offset) / 10
1329
        else:
1330
            _mem.txfreq = mem.freq / 10
1331

    
1332
        _namelength = self.get_features().valid_name_length
1333
        for i in range(_namelength):
1334
            try:
1335
                _mem.name[i] = mem.name[i]
1336
            except IndexError:
1337
                _mem.name[i] = "\xFF"
1338

    
1339
        rxmode = txmode = ""
1340
        if mem.tmode == "Tone":
1341
            _mem.txtone = int(mem.rtone * 10)
1342
            _mem.rxtone = 0
1343
        elif mem.tmode == "TSQL":
1344
            _mem.txtone = int(mem.ctone * 10)
1345
            _mem.rxtone = int(mem.ctone * 10)
1346
        elif mem.tmode == "DTCS":
1347
            rxmode = txmode = "DTCS"
1348
            _mem.txtone = self.DTCS_CODES.index(mem.dtcs) + 1
1349
            _mem.rxtone = self.DTCS_CODES.index(mem.dtcs) + 1
1350
        elif mem.tmode == "Cross":
1351
            txmode, rxmode = mem.cross_mode.split("->", 1)
1352
            if txmode == "Tone":
1353
                _mem.txtone = int(mem.rtone * 10)
1354
            elif txmode == "DTCS":
1355
                _mem.txtone = self.DTCS_CODES.index(mem.dtcs) + 1
1356
            else:
1357
                _mem.txtone = 0
1358
            if rxmode == "Tone":
1359
                _mem.rxtone = int(mem.ctone * 10)
1360
            elif rxmode == "DTCS":
1361
                _mem.rxtone = self.DTCS_CODES.index(mem.rx_dtcs) + 1
1362
            else:
1363
                _mem.rxtone = 0
1364
        else:
1365
            _mem.rxtone = 0
1366
            _mem.txtone = 0
1367

    
1368
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
1369
            _mem.txtone += 0x69
1370
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
1371
            _mem.rxtone += 0x69
1372

    
1373
        _mem.scan = mem.skip != "S"
1374
        _mem.wide = mem.mode == "NFM"
1375

    
1376
        if mem.power:
1377
            _mem.lowpower = self.POWER_LEVELS.index(mem.power)
1378
        else:
1379
            _mem.lowpower = 0
1380

    
1381
        # extra settings
1382
        if len(mem.extra) > 0:
1383
            # there are setting, parse
1384
            for setting in mem.extra:
1385
                if setting.get_name() == "scode":
1386
                    setattr(_mem, setting.get_name(), str(int(setting.value)))
1387
                else:
1388
                    setattr(_mem, setting.get_name(), setting.value)
1389
        else:
1390
            # there are no extra settings, load defaults
1391
            _mem.bcl = 0
1392
            _mem.pttid = 0
1393
            _mem.scode = 0
1394

    
1395
    def set_settings(self, settings):
1396
        _settings = self._memobj.settings
1397
        _mem = self._memobj
1398
        for element in settings:
1399
            if not isinstance(element, RadioSetting):
1400
                if element.get_name() == "fm_preset":
1401
                    self._set_fm_preset(element)
1402
                else:
1403
                    self.set_settings(element)
1404
                    continue
1405
            else:
1406
                try:
1407
                    name = element.get_name()
1408
                    if "." in name:
1409
                        bits = name.split(".")
1410
                        obj = self._memobj
1411
                        for bit in bits[:-1]:
1412
                            if "/" in bit:
1413
                                bit, index = bit.split("/", 1)
1414
                                index = int(index)
1415
                                obj = getattr(obj, bit)[index]
1416
                            else:
1417
                                obj = getattr(obj, bit)
1418
                        setting = bits[-1]
1419
                    else:
1420
                        obj = _settings
1421
                        setting = element.get_name()
1422

    
1423
                    if element.has_apply_callback():
1424
                        LOG.debug("Using apply callback")
1425
                        element.run_apply_callback()
1426
                    elif element.value.get_mutable():
1427
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1428
                        setattr(obj, setting, element.value)
1429
                except Exception:
1430
                    LOG.debug(element.get_name())
1431
                    raise
1432

    
1433
    def _set_fm_preset(self, settings):
1434
        for element in settings:
1435
            try:
1436
                val = element.value
1437
                if self._memobj.fm_presets <= 108.0 * 10 - 650:
1438
                    value = int(val.get_value() * 10 - 650)
1439
                else:
1440
                    value = int(val.get_value() * 10)
1441
                LOG.debug("Setting fm_presets = %s" % (value))
1442
                if self._bw_shift:
1443
                    value = ((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8)
1444
                self._memobj.fm_presets = value
1445
            except Exception:
1446
                LOG.debug(element.get_name())
1447
                raise
1448

    
1449
    @classmethod
1450
    def match_model(cls, filedata, filename):
1451
        match_size = False
1452
        match_model = False
1453

    
1454
        # testing the file data size
1455
        if len(filedata) in [cls.MEM_TOTAL + len(cls.MODEL)]:
1456
            match_size = True
1457

    
1458
        # testing the firmware model fingerprint
1459
        match_model = model_match(cls, filedata)
1460
        if match_size and match_model:
1461
            return True
1462
        else:
1463
            return False
1464

    
1465

    
1466
@directory.register
1467
class UV17ProGPS(UV17Pro):
1468
    VENDOR = "Baofeng"
1469
    MODEL = "UV-17ProGPS"
1470
    _support_banknames = True
1471
    _magic = MSTRING_UV17PROGPS
1472
    _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"]
1473
    _magicResponseLengths = [16, 7, 1]
1474
    VALID_BANDS = [UV17Pro._airband, UV17Pro._vhf_range, UV17Pro._vhf2_range,
1475
                   UV17Pro._uhf_range, UV17Pro._uhf2_range]
1476

    
1477
@directory.register
1478
class UV17L(UV17Pro):
1479
    VENDOR = "Baofeng"
1480
    MODEL = "UV-17L"
1481

    
1482

    
(8-8/17)