Project

General

Profile

New Model #10648 » baofeng_uv17Pro.py

Sander van der Wel, 09/25/2023 05: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
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, 0x0280, 0x0040]
374

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

    
380
    _gmrs = False
381
    _bw_shift = False
382

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

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

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

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

    
439
    @classmethod
440
    def get_prompts(cls):
441
        rp = chirp_common.RadioPrompts()
442
        rp.experimental = \
443
            ('This driver is a beta version.\n'
444
             '\n'
445
             'Please save an unedited copy of your first successful\n'
446
             'download to a CHIRP Radio Images(*.img) file.'
447
             )
448
        rp.pre_download = _(
449
            "Follow these instructions to download your info:\n"
450
            "1 - Turn off your radio\n"
451
            "2 - Connect your interface cable\n"
452
            "3 - Turn on your radio\n"
453
            "4 - Do the download of your radio data\n")
454
        rp.pre_upload = _(
455
            "Follow this instructions to upload your info:\n"
456
            "1 - Turn off your radio\n"
457
            "2 - Connect your interface cable\n"
458
            "3 - Turn on your radio\n"
459
            "4 - Do the upload of your radio data\n")
460
        return rp
461

    
462
    def process_mmap(self):
463
        """Process the mem map into the mem object"""
464
        self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap)
465

    
466
    def get_settings(self):
467
        """Translate the bit in the mem_struct into settings in the UI"""
468
        _mem = self._memobj
469
        basic = RadioSettingGroup("basic", "Basic Settings")
470
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
471
        other = RadioSettingGroup("other", "Other Settings")
472
        work = RadioSettingGroup("work", "Work Mode Settings")
473
        fm_preset = RadioSettingGroup("fm_preset", "FM Preset")
474
        dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings")
475
        service = RadioSettingGroup("service", "Service Settings")
476
        top = RadioSettings(basic, advanced, other, work, fm_preset, dtmfe,
477
                            service)
478
        return top
479
    
480
        # TODO: implement settings 
481

    
482
        # Basic settings
483
        if _mem.settings.squelch > 0x09:
484
            val = 0x00
485
        else:
486
            val = _mem.settings.squelch
487
        rs = RadioSetting("settings.squelch", "Squelch",
488
                          RadioSettingValueList(
489
                              LIST_OFF1TO9, LIST_OFF1TO9[val]))
490
        basic.append(rs)
491

    
492
        if _mem.settings.save > 0x04:
493
            val = 0x00
494
        else:
495
            val = _mem.settings.save
496
        rs = RadioSetting("settings.save", "Battery Saver",
497
                          RadioSettingValueList(
498
                              LIST_SAVE, LIST_SAVE[val]))
499
        basic.append(rs)
500

    
501
        if _mem.settings.vox > 0x0A:
502
            val = 0x00
503
        else:
504
            val = _mem.settings.vox
505
        rs = RadioSetting("settings.vox", "Vox",
506
                          RadioSettingValueList(
507
                              LIST_OFF1TO10, LIST_OFF1TO10[val]))
508
        basic.append(rs)
509

    
510
        if _mem.settings.abr > 0x0A:
511
            val = 0x00
512
        else:
513
            val = _mem.settings.abr
514
        rs = RadioSetting("settings.abr", "Backlight Timeout",
515
                          RadioSettingValueList(
516
                              LIST_OFF1TO10, LIST_OFF1TO10[val]))
517
        basic.append(rs)
518

    
519
        rs = RadioSetting("settings.tdr", "Dual Watch",
520
                          RadioSettingValueBoolean(_mem.settings.tdr))
521
        basic.append(rs)
522

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

    
527
        if _mem.settings.timeout > 0x27:
528
            val = 0x03
529
        else:
530
            val = _mem.settings.timeout
531
        rs = RadioSetting("settings.timeout", "Timeout Timer",
532
                          RadioSettingValueList(
533
                              LIST_TIMEOUT, LIST_TIMEOUT[val]))
534
        basic.append(rs)
535

    
536
        if _mem.settings.voice > 0x02:
537
            val = 0x01
538
        else:
539
            val = _mem.settings.voice
540
        rs = RadioSetting("settings.voice", "Voice Prompt",
541
                          RadioSettingValueList(
542
                              LIST_VOICE, LIST_VOICE[val]))
543
        basic.append(rs)
544

    
545
        rs = RadioSetting("settings.dtmfst", "DTMF Sidetone",
546
                          RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
547
                              _mem.settings.dtmfst]))
548
        basic.append(rs)
549

    
550
        if _mem.settings.screv > 0x02:
551
            val = 0x01
552
        else:
553
            val = _mem.settings.screv
554
        rs = RadioSetting("settings.screv", "Scan Resume",
555
                          RadioSettingValueList(
556
                              LIST_RESUME, LIST_RESUME[val]))
557
        basic.append(rs)
558

    
559
        rs = RadioSetting("settings.pttid", "When to send PTT ID",
560
                          RadioSettingValueList(LIST_PTTID, LIST_PTTID[
561
                              _mem.settings.pttid]))
562
        basic.append(rs)
563

    
564
        if _mem.settings.pttlt > 0x1E:
565
            val = 0x05
566
        else:
567
            val = _mem.settings.pttlt
568
        rs = RadioSetting("pttlt", "PTT ID Delay",
569
                          RadioSettingValueInteger(0, 50, val))
570
        basic.append(rs)
571

    
572
        rs = RadioSetting("settings.mdfa", "Display Mode (A)",
573
                          RadioSettingValueList(LIST_MODE, LIST_MODE[
574
                              _mem.settings.mdfa]))
575
        basic.append(rs)
576

    
577
        rs = RadioSetting("settings.mdfb", "Display Mode (B)",
578
                          RadioSettingValueList(LIST_MODE, LIST_MODE[
579
                              _mem.settings.mdfb]))
580
        basic.append(rs)
581

    
582
        rs = RadioSetting("settings.autolk", "Automatic Key Lock",
583
                          RadioSettingValueBoolean(_mem.settings.autolk))
584
        basic.append(rs)
585

    
586
        rs = RadioSetting("settings.wtled", "Standby LED Color",
587
                          RadioSettingValueList(
588
                              LIST_COLOR, LIST_COLOR[_mem.settings.wtled]))
589
        basic.append(rs)
590

    
591
        rs = RadioSetting("settings.rxled", "RX LED Color",
592
                          RadioSettingValueList(
593
                              LIST_COLOR, LIST_COLOR[_mem.settings.rxled]))
594
        basic.append(rs)
595

    
596
        rs = RadioSetting("settings.txled", "TX LED Color",
597
                          RadioSettingValueList(
598
                              LIST_COLOR, LIST_COLOR[_mem.settings.txled]))
599
        basic.append(rs)
600

    
601
        val = _mem.settings.almod
602
        rs = RadioSetting("settings.almod", "Alarm Mode",
603
                          RadioSettingValueList(
604
                              LIST_ALMOD, LIST_ALMOD[val]))
605
        basic.append(rs)
606

    
607
        if _mem.settings.tdrab > 0x02:
608
            val = 0x00
609
        else:
610
            val = _mem.settings.tdrab
611
        rs = RadioSetting("settings.tdrab", "Dual Watch TX Priority",
612
                          RadioSettingValueList(
613
                              LIST_OFFAB, LIST_OFFAB[val]))
614
        basic.append(rs)
615

    
616
        rs = RadioSetting("settings.ste", "Squelch Tail Eliminate (HT to HT)",
617
                          RadioSettingValueBoolean(_mem.settings.ste))
618
        basic.append(rs)
619

    
620
        if _mem.settings.rpste > 0x0A:
621
            val = 0x00
622
        else:
623
            val = _mem.settings.rpste
624
        rs = RadioSetting("settings.rpste",
625
                          "Squelch Tail Eliminate (repeater)",
626
                          RadioSettingValueList(
627
                              LIST_RPSTE, LIST_RPSTE[val]))
628
        basic.append(rs)
629

    
630
        if _mem.settings.rptrl > 0x0A:
631
            val = 0x00
632
        else:
633
            val = _mem.settings.rptrl
634
        rs = RadioSetting("settings.rptrl", "STE Repeater Delay",
635
                          RadioSettingValueList(
636
                              LIST_STEDELAY, LIST_STEDELAY[val]))
637
        basic.append(rs)
638

    
639
        rs = RadioSetting("settings.ponmsg", "Power-On Message",
640
                          RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
641
                              _mem.settings.ponmsg]))
642
        basic.append(rs)
643

    
644
        rs = RadioSetting("settings.roger", "Roger Beep",
645
                          RadioSettingValueBoolean(_mem.settings.roger))
646
        basic.append(rs)
647

    
648
        # Advanced settings
649
        rs = RadioSetting("settings.reset", "RESET Menu",
650
                          RadioSettingValueBoolean(_mem.settings.reset))
651
        advanced.append(rs)
652

    
653
        rs = RadioSetting("settings.menu", "All Menus",
654
                          RadioSettingValueBoolean(_mem.settings.menu))
655
        advanced.append(rs)
656

    
657
        rs = RadioSetting("settings.fmradio", "Broadcast FM Radio",
658
                          RadioSettingValueBoolean(_mem.settings.fmradio))
659
        advanced.append(rs)
660

    
661
        rs = RadioSetting("settings.alarm", "Alarm Sound",
662
                          RadioSettingValueBoolean(_mem.settings.alarm))
663
        advanced.append(rs)
664

    
665
        # Other settings
666
        def _filter(name):
667
            filtered = ""
668
            for char in str(name):
669
                if char in chirp_common.CHARSET_ASCII:
670
                    filtered += char
671
                else:
672
                    filtered += " "
673
            return filtered
674

    
675
        _msg = _mem.firmware_msg
676
        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
677
        val.set_mutable(False)
678
        rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
679
        other.append(rs)
680

    
681
        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
682
        val.set_mutable(False)
683
        rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
684
        other.append(rs)
685

    
686
        _msg = _mem.sixpoweron_msg
687
        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
688
        val.set_mutable(False)
689
        rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", val)
690
        other.append(rs)
691
        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
692
        val.set_mutable(False)
693
        rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", val)
694
        other.append(rs)
695

    
696
        _msg = _mem.poweron_msg
697
        rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
698
                          RadioSettingValueString(
699
                              0, 7, _filter(_msg.line1)))
700
        other.append(rs)
701
        rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
702
                          RadioSettingValueString(
703
                              0, 7, _filter(_msg.line2)))
704
        other.append(rs)
705

    
706
        lower = 130
707
        upper = 179
708
        rs = RadioSetting("limits.vhf.lower", "VHF Lower Limit (MHz)",
709
                          RadioSettingValueInteger(
710
                              lower, upper, _mem.limits.vhf.lower))
711
        other.append(rs)
712

    
713
        rs = RadioSetting("limits.vhf.upper", "VHF Upper Limit (MHz)",
714
                          RadioSettingValueInteger(
715
                              lower, upper, _mem.limits.vhf.upper))
716
        other.append(rs)
717

    
718
        if self._tri_band:
719
            lower = 200
720
            upper = 260
721
            rs = RadioSetting("limits.vhf2.lower", "VHF2 Lower Limit (MHz)",
722
                              RadioSettingValueInteger(
723
                                  lower, upper, _mem.limits.vhf2.lower))
724
            other.append(rs)
725

    
726
            rs = RadioSetting("limits.vhf2.upper", "VHF2 Upper Limit (MHz)",
727
                              RadioSettingValueInteger(
728
                                  lower, upper, _mem.limits.vhf2.upper))
729
            other.append(rs)
730

    
731
        lower = 400
732
        upper = 520
733
        rs = RadioSetting("limits.uhf.lower", "UHF Lower Limit (MHz)",
734
                          RadioSettingValueInteger(
735
                              lower, upper, _mem.limits.uhf.lower))
736
        other.append(rs)
737

    
738
        rs = RadioSetting("limits.uhf.upper", "UHF Upper Limit (MHz)",
739
                          RadioSettingValueInteger(
740
                              lower, upper, _mem.limits.uhf.upper))
741
        other.append(rs)
742

    
743
        # Work mode settings
744
        rs = RadioSetting("settings.displayab", "Display",
745
                          RadioSettingValueList(
746
                              LIST_AB, LIST_AB[_mem.settings.displayab]))
747
        work.append(rs)
748

    
749
        rs = RadioSetting("settings.workmode", "VFO/MR Mode",
750
                          RadioSettingValueList(
751
                              LIST_WORKMODE,
752
                              LIST_WORKMODE[_mem.settings.workmode]))
753
        work.append(rs)
754

    
755
        rs = RadioSetting("settings.keylock", "Keypad Lock",
756
                          RadioSettingValueBoolean(_mem.settings.keylock))
757
        work.append(rs)
758

    
759
        rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
760
                          RadioSettingValueInteger(0, 127,
761
                                                   _mem.wmchannel.mrcha))
762
        work.append(rs)
763

    
764
        rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
765
                          RadioSettingValueInteger(0, 127,
766
                                                   _mem.wmchannel.mrchb))
767
        work.append(rs)
768

    
769
        def convert_bytes_to_freq(bytes):
770
            real_freq = 0
771
            for byte in bytes:
772
                real_freq = (real_freq * 10) + byte
773
            return chirp_common.format_freq(real_freq * 10)
774

    
775
        def my_validate(value):
776
            value = chirp_common.parse_freq(value)
777
            msg = ("Can't be less than %i.0000")
778
            if value > 99000000 and value < 130 * 1000000:
779
                raise InvalidValueError(msg % (130))
780
            msg = ("Can't be between %i.9975-%i.0000")
781
            if (179 + 1) * 1000000 <= value and value < 400 * 1000000:
782
                raise InvalidValueError(msg % (179, 400))
783
            msg = ("Can't be greater than %i.9975")
784
            if value > 99000000 and value > (520 + 1) * 1000000:
785
                raise InvalidValueError(msg % (520))
786
            return chirp_common.format_freq(value)
787

    
788
        def apply_freq(setting, obj):
789
            value = chirp_common.parse_freq(str(setting.value)) / 10
790
            for i in range(7, -1, -1):
791
                obj.freq[i] = value % 10
792
                value /= 10
793

    
794
        val1a = RadioSettingValueString(0, 10,
795
                                        convert_bytes_to_freq(_mem.vfo.a.freq))
796
        val1a.set_validate_callback(my_validate)
797
        rs = RadioSetting("vfo.a.freq", "VFO A Frequency", val1a)
798
        rs.set_apply_callback(apply_freq, _mem.vfo.a)
799
        work.append(rs)
800

    
801
        val1b = RadioSettingValueString(0, 10,
802
                                        convert_bytes_to_freq(_mem.vfo.b.freq))
803
        val1b.set_validate_callback(my_validate)
804
        rs = RadioSetting("vfo.b.freq", "VFO B Frequency", val1b)
805
        rs.set_apply_callback(apply_freq, _mem.vfo.b)
806
        work.append(rs)
807

    
808
        rs = RadioSetting("vfo.a.sftd", "VFO A Shift",
809
                          RadioSettingValueList(
810
                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.a.sftd]))
811
        work.append(rs)
812

    
813
        rs = RadioSetting("vfo.b.sftd", "VFO B Shift",
814
                          RadioSettingValueList(
815
                              LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.b.sftd]))
816
        work.append(rs)
817

    
818
        def convert_bytes_to_offset(bytes):
819
            real_offset = 0
820
            for byte in bytes:
821
                real_offset = (real_offset * 10) + byte
822
            return chirp_common.format_freq(real_offset * 1000)
823

    
824
        def apply_offset(setting, obj):
825
            value = chirp_common.parse_freq(str(setting.value)) / 1000
826
            for i in range(5, -1, -1):
827
                obj.offset[i] = value % 10
828
                value /= 10
829

    
830
        val1a = RadioSettingValueString(
831
                    0, 10, convert_bytes_to_offset(_mem.vfo.a.offset))
832
        rs = RadioSetting("vfo.a.offset",
833
                          "VFO A Offset", val1a)
834
        rs.set_apply_callback(apply_offset, _mem.vfo.a)
835
        work.append(rs)
836

    
837
        val1b = RadioSettingValueString(
838
                    0, 10, convert_bytes_to_offset(_mem.vfo.b.offset))
839
        rs = RadioSetting("vfo.b.offset",
840
                          "VFO B Offset", val1b)
841
        rs.set_apply_callback(apply_offset, _mem.vfo.b)
842
        work.append(rs)
843

    
844
        def apply_txpower_listvalue(setting, obj):
845
            LOG.debug("Setting value: " + str(
846
                      setting.value) + " from list")
847
            val = str(setting.value)
848
            index = TXP_CHOICES.index(val)
849
            val = TXP_VALUES[index]
850
            obj.set_value(val)
851

    
852
        if self._tri_band:
853
            if _mem.vfo.a.txpower3 in TXP_VALUES:
854
                idx = TXP_VALUES.index(_mem.vfo.a.txpower3)
855
            else:
856
                idx = TXP_VALUES.index(0x00)
857
            rs = RadioSettingValueList(TXP_CHOICES, TXP_CHOICES[idx])
858
            rset = RadioSetting("vfo.a.txpower3", "VFO A Power", rs)
859
            rset.set_apply_callback(apply_txpower_listvalue,
860
                                    _mem.vfo.a.txpower3)
861
            work.append(rset)
862

    
863
            if _mem.vfo.b.txpower3 in TXP_VALUES:
864
                idx = TXP_VALUES.index(_mem.vfo.b.txpower3)
865
            else:
866
                idx = TXP_VALUES.index(0x00)
867
            rs = RadioSettingValueList(TXP_CHOICES, TXP_CHOICES[idx])
868
            rset = RadioSetting("vfo.b.txpower3", "VFO B Power", rs)
869
            rset.set_apply_callback(apply_txpower_listvalue,
870
                                    _mem.vfo.b.txpower3)
871
            work.append(rset)
872
        else:
873
            rs = RadioSetting("vfo.a.txpower3", "VFO A Power",
874
                              RadioSettingValueList(
875
                                  LIST_TXPOWER,
876
                                  LIST_TXPOWER[min(_mem.vfo.a.txpower3, 0x02)]
877
                                               ))
878
            work.append(rs)
879

    
880
            rs = RadioSetting("vfo.b.txpower3", "VFO B Power",
881
                              RadioSettingValueList(
882
                                  LIST_TXPOWER,
883
                                  LIST_TXPOWER[min(_mem.vfo.b.txpower3, 0x02)]
884
                                               ))
885
            work.append(rs)
886

    
887
        rs = RadioSetting("vfo.a.widenarr", "VFO A Bandwidth",
888
                          RadioSettingValueList(
889
                              LIST_BANDWIDTH,
890
                              LIST_BANDWIDTH[_mem.vfo.a.widenarr]))
891
        work.append(rs)
892

    
893
        rs = RadioSetting("vfo.b.widenarr", "VFO B Bandwidth",
894
                          RadioSettingValueList(
895
                              LIST_BANDWIDTH,
896
                              LIST_BANDWIDTH[_mem.vfo.b.widenarr]))
897
        work.append(rs)
898

    
899
        rs = RadioSetting("vfo.a.scode", "VFO A S-CODE",
900
                          RadioSettingValueList(
901
                              LIST_SCODE,
902
                              LIST_SCODE[_mem.vfo.a.scode]))
903
        work.append(rs)
904

    
905
        rs = RadioSetting("vfo.b.scode", "VFO B S-CODE",
906
                          RadioSettingValueList(
907
                              LIST_SCODE,
908
                              LIST_SCODE[_mem.vfo.b.scode]))
909
        work.append(rs)
910

    
911
        rs = RadioSetting("vfo.a.step", "VFO A Tuning Step",
912
                          RadioSettingValueList(
913
                              LIST_STEP, LIST_STEP[_mem.vfo.a.step]))
914
        work.append(rs)
915
        rs = RadioSetting("vfo.b.step", "VFO B Tuning Step",
916
                          RadioSettingValueList(
917
                              LIST_STEP, LIST_STEP[_mem.vfo.b.step]))
918
        work.append(rs)
919

    
920
        # broadcast FM settings
921
        value = self._memobj.fm_presets
922
        value_shifted = ((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8)
923
        if value_shifted >= 65.0 * 10 and value_shifted <= 108.0 * 10:
924
            # storage method 3 (discovered 2022)
925
            self._bw_shift = True
926
            preset = value_shifted / 10.0
927
        elif value >= 65.0 * 10 and value <= 108.0 * 10:
928
            # storage method 2
929
            preset = value / 10.0
930
        elif value <= 108.0 * 10 - 650:
931
            # original storage method (2012)
932
            preset = value / 10.0 + 65
933
        else:
934
            # unknown (undiscovered method or no FM chip?)
935
            preset = False
936
        if preset:
937
            rs = RadioSettingValueFloat(65, 108.0, preset, 0.1, 1)
938
            rset = RadioSetting("fm_presets", "FM Preset(MHz)", rs)
939
            fm_preset.append(rset)
940

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

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

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

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

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

    
987
        rs = RadioSetting("ani.aniid", "When to send ANI ID",
988
                          RadioSettingValueList(LIST_PTTID,
989
                                                LIST_PTTID[_mem.ani.aniid]))
990
        dtmfe.append(rs)
991

    
992
        # Service settings
993
        for band in ["vhf", "uhf"]:
994
            for index in range(0, 10):
995
                key = "squelch.%s.sql%i" % (band, index)
996
                if band == "vhf":
997
                    _obj = self._memobj.squelch.vhf
998
                elif band == "uhf":
999
                    _obj = self._memobj.squelch.uhf
1000
                val = RadioSettingValueInteger(0, 123,
1001
                                               getattr(
1002
                                                   _obj, "sql%i" % (index)))
1003
                if index == 0:
1004
                    val.set_mutable(False)
1005
                name = "%s Squelch %i" % (band.upper(), index)
1006
                rs = RadioSetting(key, name, val)
1007
                service.append(rs)
1008

    
1009
        return top
1010
    
1011
    def sync_in(self):
1012
        """Download from radio"""
1013
        try:
1014
            data = _download(self)
1015
        except errors.RadioError:
1016
            # Pass through any real errors we raise
1017
            raise
1018
        except:
1019
            # If anything unexpected happens, make sure we raise
1020
            # a RadioError and log the problem
1021
            LOG.exception('Unexpected error during download')
1022
            raise errors.RadioError('Unexpected error communicating '
1023
                                    'with the radio')
1024
        self._mmap = memmap.MemoryMapBytes(data)
1025

    
1026
        self.process_mmap()
1027

    
1028
    def sync_out(self):
1029
        """Upload to radio"""
1030
        try:
1031
            _upload(self)
1032
        except errors.RadioError:
1033
            raise
1034
        except Exception:
1035
            # If anything unexpected happens, make sure we raise
1036
            # a RadioError and log the problem
1037
            LOG.exception('Unexpected error during upload')
1038
            raise errors.RadioError('Unexpected error communicating '
1039
                                    'with the radio')
1040

    
1041
    def get_features(self):
1042
        """Get the radio's features"""
1043

    
1044
        rf = chirp_common.RadioFeatures()
1045
        rf.has_settings = True
1046
        rf.has_bank = False
1047
        rf.has_tuning_step = False
1048
        rf.can_odd_split = True
1049
        rf.has_name = True
1050
        rf.has_offset = True
1051
        rf.has_mode = True
1052
        rf.has_dtcs = True
1053
        rf.has_rx_dtcs = True
1054
        rf.has_dtcs_polarity = True
1055
        rf.has_ctone = True
1056
        rf.has_cross = True
1057
        rf.valid_modes = self.MODES
1058
        rf.valid_characters = self.VALID_CHARS
1059
        rf.valid_name_length = self.LENGTH_NAME
1060
        if self._gmrs:
1061
            rf.valid_duplexes = ["", "+", "off"]
1062
        else:
1063
            rf.valid_duplexes = ["", "-", "+", "split", "off"]
1064
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
1065
        rf.valid_cross_modes = [
1066
            "Tone->Tone",
1067
            "DTCS->",
1068
            "->DTCS",
1069
            "Tone->DTCS",
1070
            "DTCS->Tone",
1071
            "->Tone",
1072
            "DTCS->DTCS"]
1073
        rf.valid_skips = self.SKIP_VALUES
1074
        rf.valid_dtcs_codes = self.DTCS_CODES
1075
        rf.memory_bounds = (0, 999)
1076
        rf.valid_power_levels = self.POWER_LEVELS
1077
        rf.valid_bands = self.VALID_BANDS
1078
        rf.valid_tuning_steps = STEPS
1079

    
1080
        return rf
1081

    
1082
    def _is_txinh(self, _mem):
1083
        raw_tx = ""
1084
        for i in range(0, 4):
1085
            raw_tx += _mem.txfreq[i].get_raw()
1086
        return raw_tx == "\xFF\xFF\xFF\xFF"
1087

    
1088
    def get_memory(self, number):
1089
        _mem = self._memobj.memory[number]
1090
        #_nam = self._memobj.names[number]
1091
        #print(number)
1092
        #print(_mem)
1093
        #print(_nam)
1094

    
1095
        mem = chirp_common.Memory()
1096
        mem.number = number
1097

    
1098
        if _mem.get_raw()[0] == "\xff":
1099
            mem.empty = True
1100
            return mem
1101

    
1102
        mem.freq = int(_mem.rxfreq) * 10
1103

    
1104
        if self._is_txinh(_mem):
1105
            # TX freq not set
1106
            mem.duplex = "off"
1107
            mem.offset = 0
1108
        else:
1109
            # TX freq set
1110
            offset = (int(_mem.txfreq) * 10) - mem.freq
1111
            if offset != 0:
1112
                if _split(self.get_features(), mem.freq, int(
1113
                          _mem.txfreq) * 10):
1114
                    mem.duplex = "split"
1115
                    mem.offset = int(_mem.txfreq) * 10
1116
                elif offset < 0:
1117
                    mem.offset = abs(offset)
1118
                    mem.duplex = "-"
1119
                elif offset > 0:
1120
                    mem.offset = offset
1121
                    mem.duplex = "+"
1122
            else:
1123
                mem.offset = 0
1124
    
1125
        for char in _mem.name:
1126
            if str(char) == "\xFF":
1127
                char = " "  # The OEM software may have 0xFF mid-name
1128
            mem.name += str(char)
1129
        mem.name = mem.name.rstrip()
1130

    
1131
        if not _mem.scan:
1132
            mem.skip = "S"
1133

    
1134
        levels = self.POWER_LEVELS
1135
        try:
1136
            mem.power = levels[_mem.lowpower]
1137
        except IndexError:
1138
            LOG.error("Radio reported invalid power level %s (in %s)" %
1139
                        (_mem.power, levels))
1140
            mem.power = levels[0]
1141

    
1142
        mem.mode = _mem.wide and "NFM" or  "FM"
1143

    
1144
        dtcs_pol = ["N", "N"]
1145

    
1146
        if _mem.txtone in [0, 0xFFFF]:
1147
            txmode = ""
1148
        elif _mem.txtone >= 0x0258:
1149
            txmode = "Tone"
1150
            mem.rtone = int(_mem.txtone) / 10.0
1151
        elif _mem.txtone <= 0x0258:
1152
            txmode = "DTCS"
1153
            if _mem.txtone > 0x69:
1154
                index = _mem.txtone - 0x6A
1155
                dtcs_pol[0] = "R"
1156
            else:
1157
                index = _mem.txtone - 1
1158
            mem.dtcs = self.DTCS_CODES[index]
1159
        else:
1160
            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
1161

    
1162
        if _mem.rxtone in [0, 0xFFFF]:
1163
            rxmode = ""
1164
        elif _mem.rxtone >= 0x0258:
1165
            rxmode = "Tone"
1166
            mem.ctone = int(_mem.rxtone) / 10.0
1167
        elif _mem.rxtone <= 0x0258:
1168
            rxmode = "DTCS"
1169
            if _mem.rxtone >= 0x6A:
1170
                index = _mem.rxtone - 0x6A
1171
                dtcs_pol[1] = "R"
1172
            else:
1173
                index = _mem.rxtone - 1
1174
            mem.rx_dtcs = self.DTCS_CODES[index]
1175
        else:
1176
            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
1177

    
1178
        if txmode == "Tone" and not rxmode:
1179
            mem.tmode = "Tone"
1180
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
1181
            mem.tmode = "TSQL"
1182
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
1183
            mem.tmode = "DTCS"
1184
        elif rxmode or txmode:
1185
            mem.tmode = "Cross"
1186
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
1187

    
1188
        mem.dtcs_polarity = "".join(dtcs_pol)
1189

    
1190
        mem.extra = RadioSettingGroup("Extra", "extra")
1191

    
1192
        rs = RadioSetting("bcl", "BCL",
1193
                          RadioSettingValueBoolean(_mem.bcl))
1194
        mem.extra.append(rs)
1195

    
1196
        rs = RadioSetting("pttid", "PTT ID",
1197
                          RadioSettingValueList(self.PTTID_LIST,
1198
                                                self.PTTID_LIST[_mem.pttid]))
1199
        mem.extra.append(rs)
1200

    
1201
        rs = RadioSetting("scode", "S-CODE",
1202
                          RadioSettingValueList(self.SCODE_LIST,
1203
                                                self.SCODE_LIST[_mem.scode]))
1204
        mem.extra.append(rs)
1205

    
1206
        return mem
1207

    
1208
    def set_memory(self, mem):
1209
        _mem = self._memobj.memory[mem.number]
1210

    
1211
        _mem.set_raw("\x00"*16 + "\xff" * 16)
1212

    
1213
        if mem.empty:
1214
            _mem.set_raw("\xff" * 32)
1215
            return
1216

    
1217
        _mem.rxfreq = mem.freq / 10
1218
        
1219
        if mem.duplex == "off":
1220
            for i in range(0, 4):
1221
                _mem.txfreq[i].set_raw("\xFF")
1222
        elif mem.duplex == "split":
1223
            _mem.txfreq = mem.offset / 10
1224
        elif mem.duplex == "+":
1225
            _mem.txfreq = (mem.freq + mem.offset) / 10
1226
        elif mem.duplex == "-":
1227
            _mem.txfreq = (mem.freq - mem.offset) / 10
1228
        else:
1229
            _mem.txfreq = mem.freq / 10
1230

    
1231
        _namelength = self.get_features().valid_name_length
1232
        for i in range(_namelength):
1233
            try:
1234
                _mem.name[i] = mem.name[i]
1235
            except IndexError:
1236
                _mem.name[i] = "\xFF"
1237

    
1238
        rxmode = txmode = ""
1239
        if mem.tmode == "Tone":
1240
            _mem.txtone = int(mem.rtone * 10)
1241
            _mem.rxtone = 0
1242
        elif mem.tmode == "TSQL":
1243
            _mem.txtone = int(mem.ctone * 10)
1244
            _mem.rxtone = int(mem.ctone * 10)
1245
        elif mem.tmode == "DTCS":
1246
            rxmode = txmode = "DTCS"
1247
            _mem.txtone = self.DTCS_CODES.index(mem.dtcs) + 1
1248
            _mem.rxtone = self.DTCS_CODES.index(mem.dtcs) + 1
1249
        elif mem.tmode == "Cross":
1250
            txmode, rxmode = mem.cross_mode.split("->", 1)
1251
            if txmode == "Tone":
1252
                _mem.txtone = int(mem.rtone * 10)
1253
            elif txmode == "DTCS":
1254
                _mem.txtone = self.DTCS_CODES.index(mem.dtcs) + 1
1255
            else:
1256
                _mem.txtone = 0
1257
            if rxmode == "Tone":
1258
                _mem.rxtone = int(mem.ctone * 10)
1259
            elif rxmode == "DTCS":
1260
                _mem.rxtone = self.DTCS_CODES.index(mem.rx_dtcs) + 1
1261
            else:
1262
                _mem.rxtone = 0
1263
        else:
1264
            _mem.rxtone = 0
1265
            _mem.txtone = 0
1266

    
1267
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
1268
            _mem.txtone += 0x69
1269
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
1270
            _mem.rxtone += 0x69
1271

    
1272
        _mem.scan = mem.skip != "S"
1273
        _mem.wide = mem.mode == "NFM"
1274

    
1275
        if mem.power:
1276
            _mem.lowpower = self.POWER_LEVELS.index(mem.power)
1277
        else:
1278
            _mem.lowpower = 0
1279

    
1280
        # extra settings
1281
        if len(mem.extra) > 0:
1282
            # there are setting, parse
1283
            for setting in mem.extra:
1284
                if setting.get_name() == "scode":
1285
                    setattr(_mem, setting.get_name(), str(int(setting.value)))
1286
                else:
1287
                    setattr(_mem, setting.get_name(), setting.value)
1288
        else:
1289
            # there are no extra settings, load defaults
1290
            _mem.bcl = 0
1291
            _mem.pttid = 0
1292
            _mem.scode = 0
1293

    
1294
    def set_settings(self, settings):
1295
        _settings = self._memobj.settings
1296
        _mem = self._memobj
1297
        for element in settings:
1298
            if not isinstance(element, RadioSetting):
1299
                if element.get_name() == "fm_preset":
1300
                    self._set_fm_preset(element)
1301
                else:
1302
                    self.set_settings(element)
1303
                    continue
1304
            else:
1305
                try:
1306
                    name = element.get_name()
1307
                    if "." in name:
1308
                        bits = name.split(".")
1309
                        obj = self._memobj
1310
                        for bit in bits[:-1]:
1311
                            if "/" in bit:
1312
                                bit, index = bit.split("/", 1)
1313
                                index = int(index)
1314
                                obj = getattr(obj, bit)[index]
1315
                            else:
1316
                                obj = getattr(obj, bit)
1317
                        setting = bits[-1]
1318
                    else:
1319
                        obj = _settings
1320
                        setting = element.get_name()
1321

    
1322
                    if element.has_apply_callback():
1323
                        LOG.debug("Using apply callback")
1324
                        element.run_apply_callback()
1325
                    elif element.value.get_mutable():
1326
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1327
                        setattr(obj, setting, element.value)
1328
                except Exception:
1329
                    LOG.debug(element.get_name())
1330
                    raise
1331

    
1332
    def _set_fm_preset(self, settings):
1333
        for element in settings:
1334
            try:
1335
                val = element.value
1336
                if self._memobj.fm_presets <= 108.0 * 10 - 650:
1337
                    value = int(val.get_value() * 10 - 650)
1338
                else:
1339
                    value = int(val.get_value() * 10)
1340
                LOG.debug("Setting fm_presets = %s" % (value))
1341
                if self._bw_shift:
1342
                    value = ((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8)
1343
                self._memobj.fm_presets = value
1344
            except Exception:
1345
                LOG.debug(element.get_name())
1346
                raise
1347

    
1348
    @classmethod
1349
    def match_model(cls, filedata, filename):
1350
        match_size = False
1351
        match_model = False
1352

    
1353
        # testing the file data size
1354
        if len(filedata) in [cls.MEM_TOTAL + len(cls.MODEL)]:
1355
            match_size = True
1356

    
1357
        # testing the firmware model fingerprint
1358
        match_model = model_match(cls, filedata)
1359
        if match_size and match_model:
1360
            return True
1361
        else:
1362
            return False
1363

    
1364

    
1365
@directory.register
1366
class UV17ProGPS(UV17Pro):
1367
    VENDOR = "Baofeng"
1368
    MODEL = "UV-17ProGPS"
1369
    _magic = MSTRING_UV17PROGPS
1370
    _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"]
1371
    _magicResponseLengths = [16, 7, 1]
1372
    VALID_BANDS = [UV17Pro._airband, UV17Pro._vhf_range, UV17Pro._vhf2_range,
1373
                   UV17Pro._uhf_range, UV17Pro._uhf2_range]
1374

    
1375
class UV17L(UV17Pro):
1376
    VENDOR = "Baofeng"
1377
    MODEL = "UV-17L"
1378

    
1379

    
(6-6/17)