Project

General

Profile

Bug #10569 » vgc_split_v0.1.py

Jim Unroe, 05/11/2023 11:59 AM

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

    
18
import time
19
import struct
20
import logging
21
import re
22

    
23
from chirp import chirp_common, directory, memmap
24
from chirp import bitwise, errors, util
25
from chirp.settings import RadioSettingGroup, RadioSetting, \
26
    RadioSettingValueBoolean, RadioSettingValueList, \
27
    RadioSettingValueString, RadioSettingValueInteger, \
28
    RadioSettingValueFloat, RadioSettings
29

    
30
LOG = logging.getLogger(__name__)
31

    
32
MEM_FORMAT = """
33
struct mem {
34
  lbcd rxfreq[4];
35
  lbcd txfreq[4];
36
  lbcd rxtone[2];
37
  lbcd txtone[2];
38
  u8 unknown0:2,
39
     txp:2,
40
     wn:2,
41
     unknown1:1,
42
     bcl:1;
43
  u8 unknown2:2,
44
     revert:1,
45
     dname:1,
46
     unknown3:4;
47
  u8 unknown4[2];
48
};
49

    
50
struct nam {
51
  char name[6];
52
  u8 unknown1[2];
53
};
54

    
55
#seekto 0x0000;
56
struct mem left_memory[500];
57

    
58
#seekto 0x2000;
59
struct mem right_memory[500];
60

    
61
#seekto 0x4000;
62
struct nam left_names[500];
63

    
64
#seekto 0x5000;
65
struct nam right_names[500];
66

    
67
#seekto 0x6000;
68
u8 left_usedflags[64];
69

    
70
#seekto 0x6040;
71
u8 left_scanflags[64];
72

    
73
#seekto 0x6080;
74
u8 right_usedflags[64];
75

    
76
#seekto 0x60C0;
77
u8 right_scanflags[64];
78

    
79
#seekto 0x6160;
80
struct {
81
  char line32[32];
82
} embedded_msg;
83

    
84
#seekto 0x6180;
85
struct {
86
  u8  sbmute:2,        // sub band mute
87
      unknown1:1,
88
      workmodb:1,      // work mode (right side)
89
      dw:1,            // dual watch
90
      audio:1,         // audio output mode (stereo/mono)
91
      unknown2:1,
92
      workmoda:1;      // work mode (left side)
93
  u8  scansb:1,        // scan stop beep
94
      aftone:3,        // af tone control
95
      scand:1,         // scan direction
96
      scanr:3;         // scan resume
97
  u8  rxexp:1,         // rx expansion
98
      ptt:1,           // ptt mode
99
      display:1,       // display select (frequency/clock)
100
      omode:1,         // operation mode
101
      beep:2,          // beep volume
102
      spkr:2;          // speaker
103
  u8  cpuclk:1,        // operating mode(cpu clock)
104
      fkey:3,          // fkey function
105
      mrscan:1,        // memory scan type
106
      color:3;         // lcd backlight color
107
  u8  vox:2,           // vox
108
      voxs:3,          // vox sensitivity
109
      mgain:3;         // mic gain
110
  u8  wbandb:4,        // work band (right side)
111
      wbanda:4;        // work band (left side)
112
  u8  sqlb:4,          // squelch level (right side)
113
      sqla:4;          // squelch level (left side)
114
  u8  apo:4,           // auto power off
115
      ars:1,           // automatic repeater shift
116
      tot:3;           // time out timer
117
  u8  stepb:4,         // auto step (right side)
118
      stepa:4;         // auto step (left side)
119
  u8  rxcoverm:1,      // rx coverage-memory
120
      lcdc:3,          // lcd contrast
121
      rxcoverv:1,      // rx coverage-vfo
122
      lcdb:3;          // lcd brightness
123
  u8  smode:1,         // smart function mode
124
      timefmt:1,       // time format
125
      datefmt:2,       // date format
126
      timesig:1,       // time signal
127
      keyb:3;          // key/led brightness
128
  u8  dwstop:1,        // dual watch stop
129
      unknown3:1,
130
      sqlexp:1,        // sql expansion
131
      decbandsel:1,    // decoding band select
132
      dtmfmodenc:1,    // dtmf mode encode
133
      bell:3;          // bell ringer
134
  u8  unknown4:2,
135
      btime:6;         // lcd backlight time
136
  u8  unknown5:2,
137
      tz:6;            // time zone
138
  u8  unknown618E;
139
  u8  unknown618F;
140
  ul16  offseta;       // work offset (left side)
141
  ul16  offsetb;       // work offset (right side)
142
  ul16  mrcha;         // selected memory channel (left)
143
  ul16  mrchb;         // selected memory channel (right)
144
  ul16  wpricha;       // work priority channel (left)
145
  ul16  wprichb;       // work priority channel (right)
146
  u8  unknown6:3,
147
      datasql:2,       // data squelch
148
      dataspd:1,       // data speed
149
      databnd:2;       // data band select
150
  u8  unknown7:1,
151
      pfkey2:3,        // mic p2 key
152
      unknown8:1,
153
      pfkey1:3;        // mic p1 key
154
  u8  unknown9:1,
155
      pfkey4:3,        // mic p4 key
156
      unknowna:1,
157
      pfkey3:3;        // mic p3 key
158
  u8  unknownb:7,
159
      dtmfmoddec:1;    // dtmf mode decode
160
} settings;
161

    
162
#seekto 0x61B0;
163
struct {
164
  char line16[16];
165
} poweron_msg;
166

    
167
#seekto 0x6300;
168
struct {
169
  u8  unknown1:3,
170
      ttdgt:5;         // dtmf digit time
171
  u8  unknown2:3,
172
      ttint:5;         // dtmf interval time
173
  u8  unknown3:3,
174
      tt1stdgt:5;      // dtmf 1st digit time
175
  u8  unknown4:3,
176
      tt1stdly:5;      // dtmf 1st digit delay
177
  u8  unknown5:3,
178
      ttdlyqt:5;       // dtmf delay when use qt
179
  u8  unknown6:3,
180
      ttdkey:5;        // dtmf d key function
181
  u8  unknown7;
182
  u8  unknown8:4,
183
      ttautod:4;       // dtmf auto dial group
184
} dtmf;
185

    
186
#seekto 0x6330;
187
struct {
188
  u8  unknown1:7,
189
      ttsig:1;         // dtmf signal
190
  u8  unknown2:4,
191
      ttintcode:4;     // dtmf interval code
192
  u8  unknown3:5,
193
      ttgrpcode:3;     // dtmf group code
194
  u8  unknown4:4,
195
      ttautorst:4;     // dtmf auto reset time
196
  u8  unknown5:5,
197
      ttalert:3;       // dtmf alert tone/transpond
198
} dtmf2;
199

    
200
#seekto 0x6360;
201
struct {
202
  u8 code1[8];         // dtmf code
203
  u8 code1_len;        // dtmf code length
204
  u8 unknown1[7];
205
  u8 code2[8];         // dtmf code
206
  u8 code2_len;        // dtmf code length
207
  u8 unknown2[7];
208
  u8 code3[8];         // dtmf code
209
  u8 code3_len;        // dtmf code length
210
  u8 unknown3[7];
211
  u8 code4[8];         // dtmf code
212
  u8 code4_len;        // dtmf code length
213
  u8 unknown4[7];
214
  u8 code5[8];         // dtmf code
215
  u8 code5_len;        // dtmf code length
216
  u8 unknown5[7];
217
  u8 code6[8];         // dtmf code
218
  u8 code6_len;        // dtmf code length
219
  u8 unknown6[7];
220
  u8 code7[8];         // dtmf code
221
  u8 code7_len;        // dtmf code length
222
  u8 unknown7[7];
223
  u8 code8[8];         // dtmf code
224
  u8 code8_len;        // dtmf code length
225
  u8 unknown8[7];
226
  u8 code9[8];         // dtmf code
227
  u8 code9_len;        // dtmf code length
228
  u8 unknown9[7];
229
} dtmfcode;
230

    
231
"""
232

    
233
MEM_SIZE = 0x8000
234
BLOCK_SIZE = 0x40
235
MODES = ["FM", "Auto", "NFM", "AM"]
236
SKIP_VALUES = ["", "S"]
237
TONES = chirp_common.TONES
238
DTCS_CODES = chirp_common.DTCS_CODES
239
NAME_LENGTH = 6
240
DTMF_CHARS = list("0123456789ABCD*#")
241
STIMEOUT = 1
242

    
243
# Basic settings lists
244
LIST_AFTONE = ["Low-3", "Low-2", "Low-1", "Normal", "High-1", "High-2"]
245
LIST_SPKR = ["Off", "Front", "Rear", "Front + Rear"]
246
LIST_AUDIO = ["Monaural", "Stereo"]
247
LIST_SBMUTE = ["Off", "TX", "RX", "Both"]
248
LIST_MLNHM = ["Min", "Low", "Normal", "High", "Max"]
249
LIST_PTT = ["Momentary", "Toggle"]
250
LIST_RXEXP = ["General", "Wide coverage"]
251
LIST_VOX = ["Off", "Internal mic", "Front hand-mic", "Rear hand-mic"]
252
LIST_DISPLAY = ["Frequency", "Timer/Clock"]
253
LIST_MINMAX = ["Min"] + ["%s" % x for x in range(2, 8)] + ["Max"]
254
LIST_COLOR = ["White-Blue", "Sky-Blue", "Marine-Blue", "Green",
255
              "Yellow-Green", "Orange", "Amber", "White"]
256
LIST_BTIME = ["Continuous"] + ["%s" % x for x in range(1, 61)]
257
LIST_MRSCAN = ["All", "Selected"]
258
LIST_DWSTOP = ["Auto", "Hold"]
259
LIST_SCAND = ["Down", "Up"]
260
LIST_SCANR = ["Busy", "Hold", "1 sec", "3 sec", "5 sec"]
261
LIST_APO = ["Off", ".5", "1", "1.5"] + ["%s" % x for x in range(2, 13)]
262
LIST_BEEP = ["Off", "Low", "High"]
263
LIST_FKEY = ["MHz/AD-F", "AF Dual 1(line-in)", "AF Dual 2(AM)",
264
             "AF Dual 3(FM)", "PA", "SQL off", "T-call", "WX"]
265
LIST_PFKEY = ["Off", "SQL off", "TX power", "Scan", "RPT shift", "Reverse",
266
              "T-Call"]
267
LIST_AB = ["A", "B"]
268
LIST_COVERAGE = ["In band", "All"]
269
LIST_TOT = ["Off"] + ["%s" % x for x in range(5, 25, 5)] + ["30"]
270
LIST_DATEFMT = ["yyyy/mm/dd", "yyyy/dd/mm", "mm/dd/yyyy", "dd/mm/yyyy"]
271
LIST_TIMEFMT = ["24H", "12H"]
272
LIST_TZ = ["-12 INT DL W",
273
           "-11 MIDWAY",
274
           "-10 HAST",
275
           "-9 AKST",
276
           "-8 PST",
277
           "-7 MST",
278
           "-6 CST",
279
           "-5 EST",
280
           "-4:30 CARACAS",
281
           "-4 AST",
282
           "-3:30 NST",
283
           "-3 BRASILIA",
284
           "-2 MATLANTIC",
285
           "-1 AZORES",
286
           "-0 LONDON",
287
           "+0 LONDON",
288
           "+1 ROME",
289
           "+2 ATHENS",
290
           "+3 MOSCOW",
291
           "+3:30 REHRW",
292
           "+4 ABUDNABI",
293
           "+4:30 KABUL",
294
           "+5 ISLMABAD",
295
           "+5:30 NEWDELHI",
296
           "+6 DHAKA",
297
           "+6:30 YANGON",
298
           "+7 BANKOK",
299
           "+8 BEIJING",
300
           "+9 TOKYO",
301
           "+10 ADELAIDE",
302
           "+10 SYDNET",
303
           "+11 NWCLDNIA",
304
           "+12 FIJI",
305
           "+13 NUKALOFA"
306
           ]
307
LIST_BELL = ["Off", "1 time", "3 times", "5 times", "8 times", "Continuous"]
308
LIST_DATABND = ["Main band", "Sub band", "Left band-fixed", "Right band-fixed"]
309
LIST_DATASPD = ["1200 bps", "9600 bps"]
310
LIST_DATASQL = ["Busy/TX", "Busy", "TX"]
311

    
312
# Other settings lists
313
LIST_CPUCLK = ["Clock frequency 1", "Clock frequency 2"]
314

    
315
# Work mode settings lists
316
LIST_WORK = ["VFO", "Memory System"]
317
LIST_WBANDB = ["Air", "H-V", "GR1-V", "GR1-U", "H-U", "GR2"]
318
LIST_WBANDA = ["Line-in", "AM", "FM"] + LIST_WBANDB
319
LIST_SQL = ["Open"] + ["%s" % x for x in range(1, 10)]
320
_STEP_LIST = [2.5, 5., 6.25, 8.33, 9., 10., 12.5, 15., 20., 25., 50., 100.,
321
              200.]
322
LIST_STEP = ["Auto"] + ["{0:.2f} KHz".format(x) for x in _STEP_LIST]
323
LIST_SMODE = ["F-1", "F-2"]
324

    
325
# DTMF settings lists
326
LIST_TTDKEY = ["D code"] + ["Send delay %s s" % x for x in range(1, 17)]
327
LIST_TT200 = ["%s ms" % x for x in range(50, 210, 10)]
328
LIST_TT1000 = ["%s ms" % x for x in range(100, 1050, 50)]
329
LIST_TTSIG = ["Code squelch", "Select call"]
330
LIST_TTAUTORST = ["Off"] + ["%s s" % x for x in range(1, 16)]
331
LIST_TTGRPCODE = ["Off"] + list("ABCD*#")
332
LIST_TTINTCODE = DTMF_CHARS
333
LIST_TTALERT = ["Off", "Alert tone", "Transpond", "Transpond-ID code",
334
                "Transpond-transpond code"]
335
LIST_TTAUTOD = ["%s" % x for x in range(1, 10)]
336

    
337
# valid chars on the LCD
338
VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
339
    "`{|}!\"#$%&'()*+,-./:;<=>?@[]^_"
340

    
341
# Power Levels
342
POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5),
343
                chirp_common.PowerLevel("Mid", watts=20),
344
                chirp_common.PowerLevel("High", watts=50)]
345

    
346
# B-TECH UV-50X3 id string
347
UV50X3_id = b"VGC6600MD"
348

    
349

    
350
def _clean_buffer(radio):
351
    radio.pipe.timeout = 0.005
352
    junk = radio.pipe.read(256)
353
    radio.pipe.timeout = STIMEOUT
354
    if junk:
355
        Log.debug("Got %i bytes of junk before starting" % len(junk))
356

    
357

    
358
def _check_for_double_ack(radio):
359
    radio.pipe.timeout = 0.005
360
    c = radio.pipe.read(1)
361
    radio.pipe.timeout = STIMEOUT
362
    if c and c != b'\x06':
363
        _exit_program_mode(radio)
364
        raise errors.RadioError('Expected nothing or ACK, got %r' % c)
365

    
366

    
367
def _rawrecv(radio, amount):
368
    """Raw read from the radio device"""
369
    data = b""
370
    try:
371
        data = radio.pipe.read(amount)
372
    except:
373
        _exit_program_mode(radio)
374
        msg = "Generic error reading data from radio; check your cable."
375
        raise errors.RadioError(msg)
376

    
377
    if len(data) != amount:
378
        _exit_program_mode(radio)
379
        msg = "Error reading data from radio: not the amount of data we want."
380
        raise errors.RadioError(msg)
381

    
382
    return data
383

    
384

    
385
def _rawsend(radio, data):
386
    """Raw send to the radio device"""
387
    try:
388
        radio.pipe.write(data)
389
    except:
390
        raise errors.RadioError("Error sending data to radio")
391

    
392

    
393
def _make_frame(cmd, addr, length, data=""):
394
    """Pack the info in the headder format"""
395
    frame = struct.pack(">BHB", ord(cmd), addr, length)
396
    # add the data if set
397
    if len(data) != 0:
398
        frame += data
399
    # return the data
400
    return frame
401

    
402

    
403
def _recv(radio, addr, length=BLOCK_SIZE):
404
    """Get data from the radio """
405
    # read 4 bytes of header
406
    hdr = _rawrecv(radio, 4)
407

    
408
    # check for unexpected extra command byte
409
    c, a, l = struct.unpack(">BHB", hdr)
410
    if hdr[0:2] == b"WW" and a != addr:
411
        # extra command byte detected
412
        # throw away the 1st byte and add the next byte in the buffer
413
        hdr = hdr[1:] + _rawrecv(radio, 1)
414

    
415
    # read 64 bytes (0x40) of data
416
    data = _rawrecv(radio, (BLOCK_SIZE))
417

    
418
    # DEBUG
419
    LOG.info("Response:")
420
    LOG.debug(util.hexprint(hdr + data))
421

    
422
    c, a, l = struct.unpack(">BHB", hdr)
423
    if a != addr or l != length or c != ord(b"W"):
424
        _exit_program_mode(radio)
425
        LOG.error("Invalid answer for block 0x%04x:" % addr)
426
        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
427
        raise errors.RadioError("Unknown response from the radio")
428

    
429
    return data
430

    
431

    
432
def _do_ident(radio):
433
    """Put the radio in PROGRAM mode & identify it"""
434
    #  set the serial discipline
435
    radio.pipe.baudrate = 115200
436
    radio.pipe.parity = "N"
437
    radio.pipe.timeout = STIMEOUT
438

    
439
    # flush input buffer
440
    _clean_buffer(radio)
441

    
442
    magic = b"V66LINK"
443

    
444
    _rawsend(radio, magic)
445

    
446
    # Ok, get the ident string
447
    ident = _rawrecv(radio, 9)
448

    
449
    # check if ident is OK
450
    if ident != radio.IDENT:
451
        # bad ident
452
        msg = "Incorrect model ID, got this:"
453
        msg += util.hexprint(ident)
454
        LOG.debug(msg)
455
        raise errors.RadioError("Radio identification failed.")
456

    
457
    # DEBUG
458
    LOG.info("Positive ident, got this:")
459
    LOG.debug(util.hexprint(ident))
460

    
461
    return True
462

    
463

    
464
def _exit_program_mode(radio):
465
    endframe = b"\x45"
466
    _rawsend(radio, endframe)
467

    
468

    
469
def _download(radio):
470
    """Get the memory map"""
471

    
472
    # put radio in program mode and identify it
473
    _do_ident(radio)
474

    
475
    # UI progress
476
    status = chirp_common.Status()
477
    status.cur = 0
478
    status.max = MEM_SIZE // BLOCK_SIZE
479
    status.msg = "Cloning from radio..."
480
    radio.status_fn(status)
481

    
482
    data = b""
483
    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
484
        frame = _make_frame(b"R", addr, BLOCK_SIZE)
485
        # DEBUG
486
        LOG.info("Request sent:")
487
        LOG.debug(util.hexprint(frame))
488

    
489
        # sending the read request
490
        _rawsend(radio, frame)
491

    
492
        # now we read
493
        d = _recv(radio, addr)
494

    
495
        # aggregate the data
496
        data += d
497

    
498
        # UI Update
499
        status.cur = addr // BLOCK_SIZE
500
        status.msg = "Cloning from radio..."
501
        radio.status_fn(status)
502

    
503
    _exit_program_mode(radio)
504

    
505
    return data
506

    
507

    
508
def _upload(radio):
509
    """Upload procedure"""
510

    
511
    MEM_SIZE = 0x7000
512

    
513
    # put radio in program mode and identify it
514
    _do_ident(radio)
515

    
516
    # UI progress
517
    status = chirp_common.Status()
518
    status.cur = 0
519
    status.max = MEM_SIZE // BLOCK_SIZE
520
    status.msg = "Cloning to radio..."
521
    radio.status_fn(status)
522

    
523
    # the fun start here
524
    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
525
        # sending the data
526
        data = radio.get_mmap()[addr:addr + BLOCK_SIZE]
527

    
528
        frame = _make_frame(b"W", addr, BLOCK_SIZE, data)
529

    
530
        _rawsend(radio, frame)
531

    
532
        # receiving the response
533
        ack = _rawrecv(radio, 1)
534
        if ack != b"\x06":
535
            _exit_program_mode(radio)
536
            msg = "Bad ack writing block 0x%04x" % addr
537
            raise errors.RadioError(msg)
538

    
539
        _check_for_double_ack(radio)
540

    
541
        # UI Update
542
        status.cur = addr // BLOCK_SIZE
543
        status.msg = "Cloning to radio..."
544
        radio.status_fn(status)
545

    
546
    _exit_program_mode(radio)
547

    
548

    
549
def model_match(cls, data):
550
    """Match the opened/downloaded image to the correct version"""
551
    rid = data[0x6140:0x6148]
552

    
553
    if rid in cls.IDENT:
554
        return True
555

    
556
    return False
557

    
558

    
559
def _split(rf, f1, f2):
560
    """Returns False if the two freqs are in the same band (no split)
561
    or True otherwise"""
562

    
563
    # determine if the two freqs are in the same band
564
    for low, high in rf.valid_bands:
565
        if f1 >= low and f1 <= high and \
566
                f2 >= low and f2 <= high:
567
            # if the two freqs are on the same Band this is not a split
568
            return False
569

    
570
    # if you get here is because the freq pairs are split
571
    return True
572

    
573

    
574
class VGCStyleRadio(chirp_common.CloneModeRadio,
575
                    chirp_common.ExperimentalRadio):
576
    """BTECH's UV-50X3"""
577
    VENDOR = "BTECH"
578
    NEEDS_COMPAT_SERIAL = False
579
    _air_range = (108000000, 136000000)
580
    _vhf_range = (136000000, 174000000)
581
    _vhf2_range = (174000000, 250000000)
582
    _220_range = (222000000, 225000000)
583
    _gen1_range = (300000000, 400000000)
584
    _uhf_range = (400000000, 480000000)
585
    _gen2_range = (480000000, 520000000)
586
    _upper = 499
587
    MODEL = ""
588
    IDENT = ""
589

    
590
    @classmethod
591
    def get_prompts(cls):
592
        rp = chirp_common.RadioPrompts()
593
        rp.experimental = \
594
            ('The UV-50X3 driver is a beta version.\n'
595
             '\n'
596
             'Please save an unedited copy of your first successful\n'
597
             'download to a CHIRP Radio Images(*.img) file.'
598
             )
599
        rp.pre_download = _(
600
            "Follow this instructions to download your info:\n"
601
            "1 - Turn off your radio\n"
602
            "2 - Connect your interface cable\n"
603
            "3 - Turn on your radio\n"
604
            "4 - Do the download of your radio data\n")
605
        rp.pre_upload = _(
606
            "Follow this instructions to upload your info:\n"
607
            "1 - Turn off your radio\n"
608
            "2 - Connect your interface cable\n"
609
            "3 - Turn on your radio\n"
610
            "4 - Do the upload of your radio data\n")
611
        return rp
612

    
613
    def get_features(self):
614
        rf = chirp_common.RadioFeatures()
615
        rf.has_settings = True
616
        rf.has_bank = False
617
        rf.has_tuning_step = False
618
        rf.can_odd_split = True
619
        rf.has_name = True
620
        rf.has_offset = True
621
        rf.has_mode = True
622
        rf.has_dtcs = True
623
        rf.has_rx_dtcs = True
624
        rf.has_dtcs_polarity = True
625
        rf.has_ctone = True
626
        rf.has_cross = True
627
        rf.has_sub_devices = self.VARIANT == ""
628
        rf.valid_modes = MODES
629
        rf.valid_characters = VALID_CHARS
630
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
631
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
632
        rf.valid_cross_modes = [
633
            "Tone->Tone",
634
            "DTCS->",
635
            "->DTCS",
636
            "Tone->DTCS",
637
            "DTCS->Tone",
638
            "->Tone",
639
            "DTCS->DTCS"]
640
        rf.valid_power_levels = POWER_LEVELS
641
        rf.valid_skips = SKIP_VALUES
642
        rf.valid_name_length = NAME_LENGTH
643
        rf.valid_dtcs_codes = DTCS_CODES
644
        rf.valid_tuning_steps = _STEP_LIST
645
        rf.valid_bands = [self._air_range,
646
                          self._vhf_range,
647
                          self._vhf2_range,
648
                          self._220_range,
649
                          self._gen1_range,
650
                          self._uhf_range,
651
                          self._gen2_range]
652
        rf.memory_bounds = (0, self._upper)
653
        return rf
654

    
655
    def get_sub_devices(self):
656
        return [UV50X3Left(self._mmap), UV50X3Right(self._mmap)]
657

    
658
    def sync_in(self):
659
        """Download from radio"""
660
        try:
661
            data = _download(self)
662
        except errors.RadioError:
663
            # Pass through any real errors we raise
664
            raise
665
        except:
666
            # If anything unexpected happens, make sure we raise
667
            # a RadioError and log the problem
668
            LOG.exception('Unexpected error during download')
669
            raise errors.RadioError('Unexpected error communicating '
670
                                    'with the radio')
671
        self._mmap = memmap.MemoryMapBytes(data)
672
        self.process_mmap()
673

    
674
    def sync_out(self):
675
        """Upload to radio"""
676
        try:
677
            _upload(self)
678
        except:
679
            # If anything unexpected happens, make sure we raise
680
            # a RadioError and log the problem
681
            LOG.exception('Unexpected error during upload')
682
            raise errors.RadioError('Unexpected error communicating '
683
                                    'with the radio')
684

    
685
    def process_mmap(self):
686
        """Process the mem map into the mem object"""
687
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
688

    
689
    def get_raw_memory(self, number):
690
        return repr(self._memobj.memory[number])
691

    
692
    def decode_tone(self, val):
693
        """Parse the tone data to decode from mem, it returns:
694
        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
695
        if val.get_raw() == "\xFF\xFF":
696
            return '', None, None
697

    
698
        val = int(val)
699
        if val >= 12000:
700
            a = val - 12000
701
            return 'DTCS', a, 'R'
702
        elif val >= 8000:
703
            a = val - 8000
704
            return 'DTCS', a, 'N'
705
        else:
706
            a = val / 10.0
707
            return 'Tone', a, None
708

    
709
    def encode_tone(self, memval, mode, value, pol):
710
        """Parse the tone data to encode from UI to mem"""
711
        if mode == '':
712
            memval[0].set_raw(0xFF)
713
            memval[1].set_raw(0xFF)
714
        elif mode == 'Tone':
715
            memval.set_value(int(value * 10))
716
        elif mode == 'DTCS':
717
            flag = 0x80 if pol == 'N' else 0xC0
718
            memval.set_value(value)
719
            memval[1].set_bits(flag)
720
        else:
721
            raise Exception("Internal error: invalid mode `%s'" % mode)
722

    
723
    def _memory_obj(self, suffix=""):
724
        return getattr(self._memobj, "%s_memory%s" % (self._vfo, suffix))
725

    
726
    def _name_obj(self, suffix=""):
727
        return getattr(self._memobj, "%s_names%s" % (self._vfo, suffix))
728

    
729
    def _scan_obj(self, suffix=""):
730
        return getattr(self._memobj, "%s_scanflags%s" % (self._vfo, suffix))
731

    
732
    def _used_obj(self, suffix=""):
733
        return getattr(self._memobj, "%s_usedflags%s" % (self._vfo, suffix))
734

    
735
    def get_memory(self, number):
736
        """Get the mem representation from the radio image"""
737
        bitpos = (1 << (number % 8))
738
        bytepos = (number / 8)
739

    
740
        _mem = self._memory_obj()[number]
741
        _names = self._name_obj()[number]
742
        _scn = self._scan_obj()[bytepos]
743
        _usd = self._used_obj()[bytepos]
744

    
745
        isused = bitpos & int(_usd)
746
        isscan = bitpos & int(_scn)
747

    
748
        # Create a high-level memory object to return to the UI
749
        mem = chirp_common.Memory()
750

    
751
        # Memory number
752
        mem.number = number
753

    
754
        if not isused:
755
            mem.empty = True
756
            return mem
757

    
758
        # Freq and offset
759
        mem.freq = int(_mem.rxfreq) * 10
760
        # tx freq can be blank
761
        if _mem.get_raw()[4] == "\xFF":
762
            # TX freq not set
763
            mem.offset = 0
764
            mem.duplex = "off"
765
        else:
766
            # TX feq set
767
            offset = (int(_mem.txfreq) * 10) - mem.freq
768
            if offset != 0:
769
                if _split(self.get_features(), mem.freq, int(
770
                          _mem.txfreq) * 10):
771
                    mem.duplex = "split"
772
                    mem.offset = int(_mem.txfreq) * 10
773
            elif offset < 0:
774
                mem.offset = abs(offset)
775
                mem.duplex = "-"
776
            elif offset > 0:
777
                mem.offset = offset
778
                mem.duplex = "+"
779
            else:
780
                mem.offset = 0
781

    
782
        # skip
783
        if not isscan:
784
            mem.skip = "S"
785

    
786
        # name TAG of the channel
787
        mem.name = str(_names.name).strip("\xFF")
788

    
789
        # power
790
        mem.power = POWER_LEVELS[int(_mem.txp)]
791

    
792
        # wide/narrow
793
        mem.mode = MODES[int(_mem.wn)]
794

    
795
        # tone data
796
        rxtone = txtone = None
797
        txtone = self.decode_tone(_mem.txtone)
798
        rxtone = self.decode_tone(_mem.rxtone)
799
        chirp_common.split_tone_decode(mem, txtone, rxtone)
800

    
801
        # Extra
802
        mem.extra = RadioSettingGroup("extra", "Extra")
803

    
804
        bcl = RadioSetting("bcl", "Busy channel lockout",
805
                           RadioSettingValueBoolean(bool(_mem.bcl)))
806
        mem.extra.append(bcl)
807

    
808
        revert = RadioSetting("revert", "Revert",
809
                              RadioSettingValueBoolean(bool(_mem.revert)))
810
        mem.extra.append(revert)
811

    
812
        dname = RadioSetting("dname", "Display name",
813
                             RadioSettingValueBoolean(bool(_mem.dname)))
814
        mem.extra.append(dname)
815

    
816
        return mem
817

    
818
    def set_memory(self, mem):
819
        """Set the memory data in the eeprom img from the UI"""
820
        bitpos = (1 << (mem.number % 8))
821
        bytepos = (mem.number / 8)
822

    
823
        _mem = self._memory_obj()[mem.number]
824
        _names = self._name_obj()[mem.number]
825
        _scn = self._scan_obj()[bytepos]
826
        _usd = self._used_obj()[bytepos]
827

    
828
        if mem.empty:
829
            _usd &= ~bitpos
830
            _scn &= ~bitpos
831
            _mem.set_raw("\xFF" * 16)
832
            _names.name = ("\xFF" * 6)
833
            return
834
        else:
835
            _usd |= bitpos
836

    
837
        # frequency
838
        _mem.rxfreq = mem.freq / 10
839

    
840
        # duplex
841
        if mem.duplex == "+":
842
            _mem.txfreq = (mem.freq + mem.offset) / 10
843
        elif mem.duplex == "-":
844
            _mem.txfreq = (mem.freq - mem.offset) / 10
845
        elif mem.duplex == "off":
846
            for i in _mem.txfreq:
847
                i.set_raw("\xFF")
848
        elif mem.duplex == "split":
849
            _mem.txfreq = mem.offset / 10
850
        else:
851
            _mem.txfreq = mem.freq / 10
852

    
853
        # tone data
854
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
855
            chirp_common.split_tone_encode(mem)
856
        self.encode_tone(_mem.txtone, txmode, txtone, txpol)
857
        self.encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
858

    
859
        # name TAG of the channel
860
        _names.name = mem.name.rstrip().ljust(6, "\xFF")
861

    
862
        # power level, # default power level is low
863
        _mem.txp = 0 if mem.power is None else POWER_LEVELS.index(mem.power)
864

    
865
        # wide/narrow
866
        _mem.wn = MODES.index(mem.mode)
867

    
868
        if mem.skip == "S":
869
            _scn &= ~bitpos
870
        else:
871
            _scn |= bitpos
872

    
873
        # autoset display to display name if filled
874
        if mem.extra:
875
            # mem.extra only seems to be populated when called from edit panel
876
            dname = mem.extra["dname"]
877
        else:
878
            dname = None
879
        if mem.name:
880
            _mem.dname = True
881
            if dname and not dname.changed():
882
                dname.value = True
883
        else:
884
            _mem.dname = False
885
            if dname and not dname.changed():
886
                dname.value = False
887

    
888
        # resetting unknowns, this has to be set by hand
889
        _mem.unknown0 = 0
890
        _mem.unknown1 = 0
891
        _mem.unknown2 = 0
892
        _mem.unknown3 = 0
893

    
894
        # extra settings
895
        if len(mem.extra) > 0:
896
            # there are setting, parse
897
            for setting in mem.extra:
898
                setattr(_mem, setting.get_name(), setting.value)
899
        else:
900
            # there are no extra settings, load defaults
901
            _mem.bcl = 0
902
            _mem.revert = 0
903
            _mem.dname = 1
904

    
905
    def _bbcd2dtmf(self, bcdarr, strlen=16):
906
        # doing bbcd, but with support for ABCD*#
907
        LOG.debug(bcdarr.get_value())
908
        string = ''.join("%02X" % b for b in bcdarr)
909
        LOG.debug("@_bbcd2dtmf, received: %s" % string)
910
        string = string.replace('E', '*').replace('F', '#')
911
        if strlen <= 16:
912
            string = string[:strlen]
913
        return string
914

    
915
    def _dtmf2bbcd(self, value):
916
        dtmfstr = value.get_value()
917
        dtmfstr = dtmfstr.replace('*', 'E').replace('#', 'F')
918
        dtmfstr = str.ljust(dtmfstr.strip(), 16, "F")
919
        bcdarr = list(bytearray.fromhex(dtmfstr))
920
        LOG.debug("@_dtmf2bbcd, sending: %s" % bcdarr)
921
        return bcdarr
922

    
923
    def get_settings(self):
924
        """Translate the bit in the mem_struct into settings in the UI"""
925
        _mem = self._memobj
926
        basic = RadioSettingGroup("basic", "Basic Settings")
927
        other = RadioSettingGroup("other", "Other Settings")
928
        work = RadioSettingGroup("work", "Work Mode Settings")
929
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
930
        top = RadioSettings(basic, other, work, dtmf)
931

    
932
        # Basic
933

    
934
        # Audio: A01-A04
935

    
936
        aftone = RadioSetting("settings.aftone", "AF tone control",
937
                              RadioSettingValueList(LIST_AFTONE, LIST_AFTONE[
938
                                  _mem.settings.aftone]))
939
        basic.append(aftone)
940

    
941
        spkr = RadioSetting("settings.spkr", "Speaker",
942
                            RadioSettingValueList(LIST_SPKR, LIST_SPKR[
943
                                _mem.settings.spkr]))
944
        basic.append(spkr)
945

    
946
        audio = RadioSetting("settings.audio", "Stereo/Mono",
947
                             RadioSettingValueList(LIST_AUDIO, LIST_AUDIO[
948
                                 _mem.settings.audio]))
949
        basic.append(audio)
950

    
951
        sbmute = RadioSetting("settings.sbmute", "Sub band mute",
952
                              RadioSettingValueList(LIST_SBMUTE, LIST_SBMUTE[
953
                                  _mem.settings.sbmute]))
954
        basic.append(sbmute)
955

    
956
        # TX/RX: B01-B08
957

    
958
        mgain = RadioSetting("settings.mgain", "Mic gain",
959
                             RadioSettingValueList(LIST_MLNHM, LIST_MLNHM[
960
                                 _mem.settings.mgain]))
961
        basic.append(mgain)
962

    
963
        ptt = RadioSetting("settings.ptt", "PTT mode",
964
                           RadioSettingValueList(LIST_PTT, LIST_PTT[
965
                               _mem.settings.ptt]))
966
        basic.append(ptt)
967

    
968
        # B03 (per channel)
969
        # B04 (per channel)
970

    
971
        rxexp = RadioSetting("settings.rxexp", "RX expansion",
972
                             RadioSettingValueList(LIST_RXEXP, LIST_RXEXP[
973
                                 _mem.settings.rxexp]))
974
        basic.append(rxexp)
975

    
976
        vox = RadioSetting("settings.vox", "Vox",
977
                           RadioSettingValueList(LIST_VOX, LIST_VOX[
978
                               _mem.settings.vox]))
979
        basic.append(vox)
980

    
981
        voxs = RadioSetting("settings.voxs", "Vox sensitivity",
982
                            RadioSettingValueList(LIST_MLNHM, LIST_MLNHM[
983
                                _mem.settings.voxs]))
984
        basic.append(voxs)
985

    
986
        # B08 (per channel)
987

    
988
        # Display: C01-C06
989

    
990
        display = RadioSetting("settings.display", "Display select",
991
                               RadioSettingValueList(
992
                                   LIST_DISPLAY, LIST_DISPLAY[
993
                                       _mem.settings.display]))
994
        basic.append(display)
995

    
996
        lcdb = RadioSetting("settings.lcdb", "LCD brightness",
997
                            RadioSettingValueList(LIST_MINMAX, LIST_MINMAX[
998
                                _mem.settings.lcdb]))
999
        basic.append(lcdb)
1000

    
1001
        color = RadioSetting("settings.color", "LCD color",
1002
                             RadioSettingValueList(LIST_COLOR, LIST_COLOR[
1003
                                 _mem.settings.color]))
1004
        basic.append(color)
1005

    
1006
        lcdc = RadioSetting("settings.lcdc", "LCD contrast",
1007
                            RadioSettingValueList(LIST_MINMAX, LIST_MINMAX[
1008
                                _mem.settings.lcdc]))
1009
        basic.append(lcdc)
1010

    
1011
        btime = RadioSetting("settings.btime", "LCD backlight time",
1012
                             RadioSettingValueList(LIST_BTIME, LIST_BTIME[
1013
                                 _mem.settings.btime]))
1014
        basic.append(btime)
1015

    
1016
        keyb = RadioSetting("settings.keyb", "Key brightness",
1017
                            RadioSettingValueList(LIST_MINMAX, LIST_MINMAX[
1018
                                _mem.settings.keyb]))
1019
        basic.append(keyb)
1020

    
1021
        # Memory: D01-D04
1022

    
1023
        # D01 (per channel)
1024
        # D02 (per channel)
1025

    
1026
        mrscan = RadioSetting("settings.mrscan", "Memory scan type",
1027
                              RadioSettingValueList(LIST_MRSCAN, LIST_MRSCAN[
1028
                                  _mem.settings.mrscan]))
1029
        basic.append(mrscan)
1030

    
1031
        # D04 (per channel)
1032

    
1033
        # Scan: E01-E04
1034

    
1035
        dwstop = RadioSetting("settings.dwstop", "Dual watch stop",
1036
                              RadioSettingValueList(LIST_DWSTOP, LIST_DWSTOP[
1037
                                  _mem.settings.dwstop]))
1038
        basic.append(dwstop)
1039

    
1040
        scand = RadioSetting("settings.scand", "Scan direction",
1041
                             RadioSettingValueList(LIST_SCAND, LIST_SCAND[
1042
                                 _mem.settings.scand]))
1043
        basic.append(scand)
1044

    
1045
        scanr = RadioSetting("settings.scanr", "Scan resume",
1046
                             RadioSettingValueList(LIST_SCANR, LIST_SCANR[
1047
                                 _mem.settings.scanr]))
1048
        basic.append(scanr)
1049

    
1050
        scansb = RadioSetting("settings.scansb", "Scan stop beep",
1051
                              RadioSettingValueBoolean(_mem.settings.scansb))
1052
        basic.append(scansb)
1053

    
1054
        # System: F01-F09
1055

    
1056
        apo = RadioSetting("settings.apo", "Automatic power off [hours]",
1057
                           RadioSettingValueList(LIST_APO, LIST_APO[
1058
                               _mem.settings.apo]))
1059
        basic.append(apo)
1060

    
1061
        ars = RadioSetting("settings.ars", "Automatic repeater shift",
1062
                           RadioSettingValueBoolean(_mem.settings.ars))
1063
        basic.append(ars)
1064

    
1065
        beep = RadioSetting("settings.beep", "Beep volume",
1066
                            RadioSettingValueList(LIST_BEEP, LIST_BEEP[
1067
                                _mem.settings.beep]))
1068
        basic.append(beep)
1069

    
1070
        fkey = RadioSetting("settings.fkey", "F key",
1071
                            RadioSettingValueList(LIST_FKEY, LIST_FKEY[
1072
                                _mem.settings.fkey]))
1073
        basic.append(fkey)
1074

    
1075
        pfkey1 = RadioSetting("settings.pfkey1", "Mic P1 key",
1076
                              RadioSettingValueList(LIST_PFKEY, LIST_PFKEY[
1077
                                  _mem.settings.pfkey1]))
1078
        basic.append(pfkey1)
1079

    
1080
        pfkey2 = RadioSetting("settings.pfkey2", "Mic P2 key",
1081
                              RadioSettingValueList(LIST_PFKEY, LIST_PFKEY[
1082
                                  _mem.settings.pfkey2]))
1083
        basic.append(pfkey2)
1084

    
1085
        pfkey3 = RadioSetting("settings.pfkey3", "Mic P3 key",
1086
                              RadioSettingValueList(LIST_PFKEY, LIST_PFKEY[
1087
                                  _mem.settings.pfkey3]))
1088
        basic.append(pfkey3)
1089

    
1090
        pfkey4 = RadioSetting("settings.pfkey4", "Mic P4 key",
1091
                              RadioSettingValueList(LIST_PFKEY, LIST_PFKEY[
1092
                                  _mem.settings.pfkey4]))
1093
        basic.append(pfkey4)
1094

    
1095
        omode = RadioSetting("settings.omode", "Operation mode",
1096
                             RadioSettingValueList(LIST_AB, LIST_AB[
1097
                                 _mem.settings.omode]))
1098
        basic.append(omode)
1099

    
1100
        rxcoverm = RadioSetting("settings.rxcoverm", "RX coverage - memory",
1101
                                RadioSettingValueList(
1102
                                    LIST_COVERAGE, LIST_COVERAGE[
1103
                                        _mem.settings.rxcoverm]))
1104
        basic.append(rxcoverm)
1105

    
1106
        rxcoverv = RadioSetting("settings.rxcoverv", "RX coverage - VFO",
1107
                                RadioSettingValueList(
1108
                                    LIST_COVERAGE, LIST_COVERAGE[
1109
                                        _mem.settings.rxcoverv]))
1110
        basic.append(rxcoverv)
1111

    
1112
        tot = RadioSetting("settings.tot", "Time out timer [min]",
1113
                           RadioSettingValueList(LIST_TOT, LIST_TOT[
1114
                                                 _mem.settings.tot]))
1115
        basic.append(tot)
1116

    
1117
        # Timer/Clock: G01-G04
1118

    
1119
        # G01
1120
        datefmt = RadioSetting("settings.datefmt", "Date format",
1121
                               RadioSettingValueList(
1122
                                   LIST_DATEFMT, LIST_DATEFMT[
1123
                                       _mem.settings.datefmt]))
1124
        basic.append(datefmt)
1125

    
1126
        timefmt = RadioSetting("settings.timefmt", "Time format",
1127
                               RadioSettingValueList(
1128
                                   LIST_TIMEFMT, LIST_TIMEFMT[
1129
                                       _mem.settings.timefmt]))
1130
        basic.append(timefmt)
1131

    
1132
        timesig = RadioSetting("settings.timesig", "Time signal",
1133
                               RadioSettingValueBoolean(_mem.settings.timesig))
1134
        basic.append(timesig)
1135

    
1136
        tz = RadioSetting("settings.tz", "Time zone",
1137
                          RadioSettingValueList(LIST_TZ, LIST_TZ[
1138
                              _mem.settings.tz]))
1139
        basic.append(tz)
1140

    
1141
        # Signaling: H01-H06
1142

    
1143
        bell = RadioSetting("settings.bell", "Bell ringer",
1144
                            RadioSettingValueList(LIST_BELL, LIST_BELL[
1145
                                _mem.settings.bell]))
1146
        basic.append(bell)
1147

    
1148
        # H02 (per channel)
1149

    
1150
        dtmfmodenc = RadioSetting("settings.dtmfmodenc", "DTMF mode encode",
1151
                                  RadioSettingValueBoolean(
1152
                                      _mem.settings.dtmfmodenc))
1153
        basic.append(dtmfmodenc)
1154

    
1155
        dtmfmoddec = RadioSetting("settings.dtmfmoddec", "DTMF mode decode",
1156
                                  RadioSettingValueBoolean(
1157
                                      _mem.settings.dtmfmoddec))
1158
        basic.append(dtmfmoddec)
1159

    
1160
        # H04 (per channel)
1161

    
1162
        decbandsel = RadioSetting("settings.decbandsel", "DTMF band select",
1163
                                  RadioSettingValueList(LIST_AB, LIST_AB[
1164
                                      _mem.settings.decbandsel]))
1165
        basic.append(decbandsel)
1166

    
1167
        sqlexp = RadioSetting("settings.sqlexp", "SQL expansion",
1168
                              RadioSettingValueBoolean(_mem.settings.sqlexp))
1169
        basic.append(sqlexp)
1170

    
1171
        # Pkt: I01-I03
1172

    
1173
        databnd = RadioSetting("settings.databnd", "Packet data band",
1174
                               RadioSettingValueList(
1175
                                   LIST_DATABND, LIST_DATABND[
1176
                                       _mem.settings.databnd]))
1177
        basic.append(databnd)
1178

    
1179
        dataspd = RadioSetting("settings.dataspd", "Packet data speed",
1180
                               RadioSettingValueList(
1181
                                   LIST_DATASPD, LIST_DATASPD[
1182
                                       _mem.settings.dataspd]))
1183
        basic.append(dataspd)
1184

    
1185
        datasql = RadioSetting("settings.datasql", "Packet data squelch",
1186
                               RadioSettingValueList(
1187
                                   LIST_DATASQL, LIST_DATASQL[
1188
                                       _mem.settings.datasql]))
1189
        basic.append(datasql)
1190

    
1191
        # Other
1192

    
1193
        dw = RadioSetting("settings.dw", "Dual watch",
1194
                          RadioSettingValueBoolean(_mem.settings.dw))
1195
        other.append(dw)
1196

    
1197
        cpuclk = RadioSetting("settings.cpuclk", "CPU clock frequency",
1198
                              RadioSettingValueList(LIST_CPUCLK, LIST_CPUCLK[
1199
                                  _mem.settings.cpuclk]))
1200
        other.append(cpuclk)
1201

    
1202
        def _filter(name):
1203
            filtered = ""
1204
            for char in str(name):
1205
                if char in VALID_CHARS:
1206
                    filtered += char
1207
                else:
1208
                    filtered += " "
1209
            return filtered
1210

    
1211
        line16 = RadioSetting("poweron_msg.line16", "Power-on message",
1212
                              RadioSettingValueString(0, 16, _filter(
1213
                                  _mem.poweron_msg.line16)))
1214
        other.append(line16)
1215

    
1216
        line32 = RadioSetting("embedded_msg.line32", "Embedded message",
1217
                              RadioSettingValueString(0, 32, _filter(
1218
                                  _mem.embedded_msg.line32)))
1219
        other.append(line32)
1220

    
1221
        # Work
1222

    
1223
        workmoda = RadioSetting("settings.workmoda", "Work mode A",
1224
                                RadioSettingValueList(LIST_WORK, LIST_WORK[
1225
                                    _mem.settings.workmoda]))
1226
        work.append(workmoda)
1227

    
1228
        workmodb = RadioSetting("settings.workmodb", "Work mode B",
1229
                                RadioSettingValueList(LIST_WORK, LIST_WORK[
1230
                                    _mem.settings.workmodb]))
1231
        work.append(workmodb)
1232

    
1233
        wbanda = RadioSetting("settings.wbanda", "Work band A",
1234
                              RadioSettingValueList(LIST_WBANDA, LIST_WBANDA[
1235
                                  (_mem.settings.wbanda) - 1]))
1236
        work.append(wbanda)
1237

    
1238
        wbandb = RadioSetting("settings.wbandb", "Work band B",
1239
                              RadioSettingValueList(LIST_WBANDB, LIST_WBANDB[
1240
                                  (_mem.settings.wbandb) - 4]))
1241
        work.append(wbandb)
1242

    
1243
        sqla = RadioSetting("settings.sqla", "Squelch A",
1244
                            RadioSettingValueList(LIST_SQL, LIST_SQL[
1245
                                _mem.settings.sqla]))
1246
        work.append(sqla)
1247

    
1248
        sqlb = RadioSetting("settings.sqlb", "Squelch B",
1249
                            RadioSettingValueList(LIST_SQL, LIST_SQL[
1250
                                _mem.settings.sqlb]))
1251
        work.append(sqlb)
1252

    
1253
        stepa = RadioSetting("settings.stepa", "Auto step A",
1254
                             RadioSettingValueList(LIST_STEP, LIST_STEP[
1255
                                 _mem.settings.stepa]))
1256
        work.append(stepa)
1257

    
1258
        stepb = RadioSetting("settings.stepb", "Auto step B",
1259
                             RadioSettingValueList(LIST_STEP, LIST_STEP[
1260
                                 _mem.settings.stepb]))
1261
        work.append(stepb)
1262

    
1263
        mrcha = RadioSetting("settings.mrcha", "Current channel A",
1264
                             RadioSettingValueInteger(0, 499,
1265
                                                      _mem.settings.mrcha))
1266
        work.append(mrcha)
1267

    
1268
        mrchb = RadioSetting("settings.mrchb", "Current channel B",
1269
                             RadioSettingValueInteger(0, 499,
1270
                                                      _mem.settings.mrchb))
1271
        work.append(mrchb)
1272

    
1273
        val = _mem.settings.offseta / 100.00
1274
        offseta = RadioSetting("settings.offseta", "Offset A (0-37.95)",
1275
                               RadioSettingValueFloat(0, 38.00, val, 0.05, 2))
1276
        work.append(offseta)
1277

    
1278
        val = _mem.settings.offsetb / 100.00
1279
        offsetb = RadioSetting("settings.offsetb", "Offset B (0-79.95)",
1280
                               RadioSettingValueFloat(0, 80.00, val, 0.05, 2))
1281
        work.append(offsetb)
1282

    
1283
        wpricha = RadioSetting("settings.wpricha", "Priority channel A",
1284
                               RadioSettingValueInteger(0, 499,
1285
                                                        _mem.settings.wpricha))
1286
        work.append(wpricha)
1287

    
1288
        wprichb = RadioSetting("settings.wprichb", "Priority channel B",
1289
                               RadioSettingValueInteger(0, 499,
1290
                                                        _mem.settings.wprichb))
1291
        work.append(wprichb)
1292

    
1293
        smode = RadioSetting("settings.smode", "Smart function mode",
1294
                             RadioSettingValueList(LIST_SMODE, LIST_SMODE[
1295
                                 _mem.settings.smode]))
1296
        work.append(smode)
1297

    
1298
        # dtmf
1299

    
1300
        ttdkey = RadioSetting("dtmf.ttdkey", "D key function",
1301
                              RadioSettingValueList(LIST_TTDKEY, LIST_TTDKEY[
1302
                                  _mem.dtmf.ttdkey]))
1303
        dtmf.append(ttdkey)
1304

    
1305
        ttdgt = RadioSetting("dtmf.ttdgt", "Digit time",
1306
                             RadioSettingValueList(LIST_TT200, LIST_TT200[
1307
                                 (_mem.dtmf.ttdgt) - 5]))
1308
        dtmf.append(ttdgt)
1309

    
1310
        ttint = RadioSetting("dtmf.ttint", "Interval time",
1311
                             RadioSettingValueList(LIST_TT200, LIST_TT200[
1312
                                 (_mem.dtmf.ttint) - 5]))
1313
        dtmf.append(ttint)
1314

    
1315
        tt1stdgt = RadioSetting("dtmf.tt1stdgt", "1st digit time",
1316
                                RadioSettingValueList(LIST_TT200, LIST_TT200[
1317
                                    (_mem.dtmf.tt1stdgt) - 5]))
1318
        dtmf.append(tt1stdgt)
1319

    
1320
        tt1stdly = RadioSetting("dtmf.tt1stdly", "1st digit delay time",
1321
                                RadioSettingValueList(LIST_TT1000, LIST_TT1000[
1322
                                    (_mem.dtmf.tt1stdly) - 2]))
1323
        dtmf.append(tt1stdly)
1324

    
1325
        ttdlyqt = RadioSetting("dtmf.ttdlyqt", "Digit delay when use qt",
1326
                               RadioSettingValueList(LIST_TT1000, LIST_TT1000[
1327
                                   (_mem.dtmf.ttdlyqt) - 2]))
1328
        dtmf.append(ttdlyqt)
1329

    
1330
        ttsig = RadioSetting("dtmf2.ttsig", "Signal",
1331
                             RadioSettingValueList(LIST_TTSIG, LIST_TTSIG[
1332
                                 _mem.dtmf2.ttsig]))
1333
        dtmf.append(ttsig)
1334

    
1335
        ttautorst = RadioSetting("dtmf2.ttautorst", "Auto reset time",
1336
                                 RadioSettingValueList(
1337
                                     LIST_TTAUTORST, LIST_TTAUTORST[
1338
                                         _mem.dtmf2.ttautorst]))
1339
        dtmf.append(ttautorst)
1340

    
1341
        if _mem.dtmf2.ttgrpcode > 0x06:
1342
            val = 0x00
1343
        else:
1344
            val = _mem.dtmf2.ttgrpcode
1345
        ttgrpcode = RadioSetting("dtmf2.ttgrpcode", "Group code",
1346
                                 RadioSettingValueList(LIST_TTGRPCODE,
1347
                                                       LIST_TTGRPCODE[val]))
1348
        dtmf.append(ttgrpcode)
1349

    
1350
        ttintcode = RadioSetting("dtmf2.ttintcode", "Interval code",
1351
                                 RadioSettingValueList(
1352
                                     LIST_TTINTCODE, LIST_TTINTCODE[
1353
                                         _mem.dtmf2.ttintcode]))
1354
        dtmf.append(ttintcode)
1355

    
1356
        if _mem.dtmf2.ttalert > 0x04:
1357
            val = 0x00
1358
        else:
1359
            val = _mem.dtmf2.ttalert
1360
        ttalert = RadioSetting("dtmf2.ttalert", "Alert tone/transpond",
1361
                               RadioSettingValueList(LIST_TTALERT,
1362
                                                     LIST_TTALERT[val]))
1363
        dtmf.append(ttalert)
1364

    
1365
        ttautod = RadioSetting("dtmf.ttautod", "Auto dial group",
1366
                               RadioSettingValueList(
1367
                                   LIST_TTAUTOD, LIST_TTAUTOD[
1368
                                       _mem.dtmf.ttautod]))
1369
        dtmf.append(ttautod)
1370

    
1371
        # setup 9 dtmf autodial entries
1372
        for i in map(str, list(range(1, 10))):
1373
            objname = "code" + i
1374
            strname = "Code " + str(i)
1375
            dtmfsetting = getattr(_mem.dtmfcode, objname)
1376
            dtmflen = getattr(_mem.dtmfcode, objname + "_len")
1377
            dtmfstr = self._bbcd2dtmf(dtmfsetting, dtmflen)
1378
            code = RadioSettingValueString(0, 16, dtmfstr)
1379
            code.set_charset(DTMF_CHARS + list(" "))
1380
            rs = RadioSetting("dtmfcode." + objname, strname, code)
1381
            dtmf.append(rs)
1382
        return top
1383

    
1384
    def set_settings(self, settings):
1385
        _settings = self._memobj.settings
1386
        _mem = self._memobj
1387
        for element in settings:
1388
            if not isinstance(element, RadioSetting):
1389
                self.set_settings(element)
1390
                continue
1391
            else:
1392
                try:
1393
                    name = element.get_name()
1394
                    if "." in name:
1395
                        bits = name.split(".")
1396
                        obj = self._memobj
1397
                        for bit in bits[:-1]:
1398
                            if "/" in bit:
1399
                                bit, index = bit.split("/", 1)
1400
                                index = int(index)
1401
                                obj = getattr(obj, bit)[index]
1402
                            else:
1403
                                obj = getattr(obj, bit)
1404
                        setting = bits[-1]
1405
                    else:
1406
                        obj = _settings
1407
                        setting = element.get_name()
1408

    
1409
                    if element.has_apply_callback():
1410
                        LOG.debug("Using apply callback")
1411
                        element.run_apply_callback()
1412
                    elif setting == "line16":
1413
                        setattr(obj, setting, str(element.value).rstrip(
1414
                            " ").ljust(16, "\xFF"))
1415
                    elif setting == "line32":
1416
                        setattr(obj, setting, str(element.value).rstrip(
1417
                            " ").ljust(32, "\xFF"))
1418
                    elif setting == "wbanda":
1419
                        setattr(obj, setting, int(element.value) + 1)
1420
                    elif setting == "wbandb":
1421
                        setattr(obj, setting, int(element.value) + 4)
1422
                    elif setting in ["offseta", "offsetb"]:
1423
                        val = element.value
1424
                        value = int(val.get_value() * 100)
1425
                        setattr(obj, setting, value)
1426
                    elif setting in ["ttdgt", "ttint", "tt1stdgt"]:
1427
                        setattr(obj, setting, int(element.value) + 5)
1428
                    elif setting in ["tt1stdly", "ttdlyqt"]:
1429
                        setattr(obj, setting, int(element.value) + 2)
1430
                    elif re.match('code\d', setting):
1431
                        # set dtmf length field and then get bcd dtmf
1432
                        dtmfstrlen = len(str(element.value).strip())
1433
                        setattr(_mem.dtmfcode, setting + "_len", dtmfstrlen)
1434
                        dtmfstr = self._dtmf2bbcd(element.value)
1435
                        setattr(_mem.dtmfcode, setting, dtmfstr)
1436
                    elif element.value.get_mutable():
1437
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1438
                        setattr(obj, setting, element.value)
1439
                except Exception as e:
1440
                    LOG.debug(element.get_name())
1441
                    raise
1442

    
1443
    @classmethod
1444
    def match_model(cls, filedata, filename):
1445
        match_size = False
1446
        match_model = False
1447

    
1448
        # testing the file data size
1449
        if len(filedata) == MEM_SIZE:
1450
            match_size = True
1451

    
1452
        # testing the firmware model fingerprint
1453
        match_model = model_match(cls, filedata)
1454

    
1455
        if match_size and match_model:
1456
            return True
1457
        else:
1458
            return False
1459

    
1460

    
1461
@directory.register
1462
class UV50X3(VGCStyleRadio):
1463
    """BTech UV-50X3"""
1464
    MODEL = "UV-50X3"
1465
    IDENT = UV50X3_id
1466

    
1467

    
1468
class UV50X3Left(UV50X3):
1469
    VARIANT = "Left"
1470
    _vfo = "left"
1471

    
1472

    
1473
class UV50X3Right(UV50X3):
1474
    VARIANT = "Right"
1475
    _vfo = "right"
(3-3/3)