vgc_ww.py

Jim Unroe, 11/27/2016 11:48 am

Download (48.6 kB)

 
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
LOG = logging.getLogger(__name__)
24

    
25
from chirp import chirp_common, directory, memmap
26
from chirp import bitwise, errors, util
27
from chirp.settings import RadioSettingGroup, RadioSetting, \
28
    RadioSettingValueBoolean, RadioSettingValueList, \
29
    RadioSettingValueString, RadioSettingValueInteger, \
30
    RadioSettingValueFloat, RadioSettings
31
from textwrap import dedent
32

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
232
"""
233

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

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

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

    
316
# Work mode settings lists
317
LIST_WORK = ["VFO", "Memory System"]
318
LIST_WBANDB = ["Air", "H-V", "GR1-V", "GR1-U", "H-U", "GR2"]
319
LIST_WBANDA = ["Line-in", "AM", "FM"] + LIST_WBANDB
320
LIST_SQL = ["Open"] + ["%s" % x for x in range(1, 10)]
321
LIST_STEP = ["Auto", "2.50 KHz", "5.00 KHz", "6.25 KHz", "8.33 KHz",
322
             "9.00 KHz", "10.00 KHz", "12.50 KHz", "15.00 KHz", "20.00 KHz",
323
             "25.00 KHz", "50.00 KHz", "100.00 KHz", "200.00 KHz"]
324
LIST_SMODE = ["F-1", "F-2"]
325

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

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

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

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

    
350

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

    
358

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

    
367

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

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

    
383
    return data
384

    
385

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

    
393

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

    
403

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

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

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

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

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

    
431
    return data
432

    
433

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

    
441
    # flush input buffer
442
    _clean_buffer(radio)
443

    
444
    magic = "V66LINK"
445

    
446
    _rawsend(radio, magic)
447

    
448
    # Ok, get the ident string
449
    ident = _rawrecv(radio, 9)
450

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

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

    
463
    return True
464

    
465

    
466
def _exit_program_mode(radio):
467
    endframe = "\x45"
468
    _rawsend(radio, endframe)
469

    
470

    
471
def _download(radio):
472
    """Get the memory map"""
473

    
474
    # put radio in program mode and identify it
475
    _do_ident(radio)
476

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

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

    
491
        # sending the read request
492
        _rawsend(radio, frame)
493

    
494
        # now we read
495
        d = _recv(radio, addr)
496
        time.sleep(0.1)
497

    
498
        # aggregate the data
499
        data += d
500

    
501
        # UI Update
502
        status.cur = addr / BLOCK_SIZE
503
        status.msg = "Cloning from radio..."
504
        radio.status_fn(status)
505

    
506
    _exit_program_mode(radio)
507

    
508
    return data
509

    
510

    
511
def _upload(radio):
512
    """Upload procedure"""
513

    
514
    MEM_SIZE = 0x7000
515

    
516
    # put radio in program mode and identify it
517
    _do_ident(radio)
518

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

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

    
531
        frame = _make_frame("W", addr, BLOCK_SIZE, data)
532

    
533
        _rawsend(radio, frame)
534

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

    
542
        _check_for_double_ack(radio)
543

    
544
        # UI Update
545
        status.cur = addr / BLOCK_SIZE
546
        status.msg = "Cloning to radio..."
547
        radio.status_fn(status)
548

    
549
    _exit_program_mode(radio)
550

    
551

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

    
556
    #if rid in cls._fileid:
557
    if rid in cls.IDENT:
558
        return True
559

    
560
    return False
561

    
562

    
563
class VGCStyleRadio(chirp_common.CloneModeRadio,
564
                    chirp_common.ExperimentalRadio):
565
    """BTECH's UV-50X3"""
566
    VENDOR = "BTECH"
567
    _air_range = (108000000, 136000000)
568
    _vhf_range = (136000000, 174000000)
569
    _vhf2_range = (174000000, 250000000)
570
    _220_range = (222000000, 225000000)
571
    _gen1_range = (300000000, 400000000)
572
    _uhf_range = (400000000, 480000000)
573
    _gen2_range = (480000000, 520000000)
574
    _upper = 499
575
    MODEL = ""
576
    IDENT = ""
577

    
578
    @classmethod
579
    def get_prompts(cls):
580
        rp = chirp_common.RadioPrompts()
581
        rp.experimental = \
582
            ('The UV-50X3 driver is a beta version.\n'
583
             '\n'
584
             'Please save an unedited copy of your first successful\n'
585
             'download to a CHIRP Radio Images(*.img) file.'
586
             )
587
        rp.pre_download = _(dedent("""\
588
            Follow this instructions to download your info:
589

    
590
            1 - Turn off your radio
591
            2 - Connect your interface cable
592
            3 - Turn on your radio
593
            4 - Do the download of your radio data
594
            """))
595
        rp.pre_upload = _(dedent("""\
596
            Follow this instructions to upload your info:
597

    
598
            1 - Turn off your radio
599
            2 - Connect your interface cable
600
            3 - Turn on your radio
601
            4 - Do the upload of your radio data
602
            """))
603
        return rp
604

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

    
646
    def get_sub_devices(self):
647
        return [UV50X3Left(self._mmap), UV50X3Right(self._mmap)]
648

    
649
    def sync_in(self):
650
        """Download from radio"""
651
        try:
652
            data = _download(self)
653
        except errors.RadioError:
654
            # Pass through any real errors we raise
655
            raise
656
        except:
657
            # If anything unexpected happens, make sure we raise
658
            # a RadioError and log the problem
659
            LOG.exception('Unexpected error during download')
660
            raise errors.RadioError('Unexpected error communicating '
661
                                    'with the radio')
662
        self._mmap = memmap.MemoryMap(data)
663
        self.process_mmap()
664

    
665
    def sync_out(self):
666
        """Upload to radio"""
667
        try:
668
            _upload(self)
669
        except:
670
            # If anything unexpected happens, make sure we raise
671
            # a RadioError and log the problem
672
            LOG.exception('Unexpected error during upload')
673
            raise errors.RadioError('Unexpected error communicating '
674
                                    'with the radio')
675

    
676
    def process_mmap(self):
677
        """Process the mem map into the mem object"""
678
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
679

    
680
    def get_raw_memory(self, number):
681
        return repr(self._memobj.memory[number])
682

    
683
    def decode_tone(self, val):
684
        """Parse the tone data to decode from mem, it returns:
685
        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
686
        if val.get_raw() == "\xFF\xFF":
687
            return '', None, None
688

    
689
        val = int(val)
690
        if val >= 12000:
691
            a = val - 12000
692
            return 'DTCS', a, 'R'
693
        elif val >= 8000:
694
            a = val - 8000
695
            return 'DTCS', a, 'N'
696
        else:
697
            a = val / 10.0
698
            return 'Tone', a, None
699

    
700
    def encode_tone(self, memval, mode, value, pol):
701
        """Parse the tone data to encode from UI to mem"""
702
        if mode == '':
703
            memval[0].set_raw(0xFF)
704
            memval[1].set_raw(0xFF)
705
        elif mode == 'Tone':
706
            memval.set_value(int(value * 10))
707
        elif mode == 'DTCS':
708
            flag = 0x80 if pol == 'N' else 0xC0
709
            memval.set_value(value)
710
            memval[1].set_bits(flag)
711
        else:
712
            raise Exception("Internal error: invalid mode `%s'" % mode)
713

    
714
    def _memory_obj(self, suffix=""):
715
        return getattr(self._memobj, "%s_memory%s" % (self._vfo, suffix))
716

    
717
    def _name_obj(self, suffix=""):
718
        return getattr(self._memobj, "%s_names%s" % (self._vfo, suffix))
719

    
720
    def _scan_obj(self, suffix=""):
721
        return getattr(self._memobj, "%s_scanflags%s" % (self._vfo, suffix))
722

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

    
726
    def get_memory(self, number):
727
        """Get the mem representation from the radio image"""
728
        bitpos = (1 << (number % 8))
729
        bytepos = (number / 8)
730

    
731
        _mem = self._memory_obj()[number]
732
        _names = self._name_obj()[number]
733
        _scn = self._scan_obj()[bytepos]
734
        _usd = self._used_obj()[bytepos]
735

    
736
        isused = bitpos & int(_usd)
737
        isscan = bitpos & int(_scn)
738

    
739
        # Create a high-level memory object to return to the UI
740
        mem = chirp_common.Memory()
741

    
742
        # Memory number
743
        mem.number = number
744

    
745
        if not isused:
746
            mem.empty = True
747
            return mem
748

    
749
        # Freq and offset
750
        mem.freq = int(_mem.rxfreq) * 10
751
        # tx freq can be blank
752
        if _mem.get_raw()[4] == "\xFF":
753
            # TX freq not set
754
            mem.offset = 0
755
            mem.duplex = "off"
756
        else:
757
            # TX feq set
758
            offset = (int(_mem.txfreq) * 10) - mem.freq
759
            if offset < 0:
760
                mem.offset = abs(offset)
761
                mem.duplex = "-"
762
            elif offset > 0:
763
                mem.offset = offset
764
                mem.duplex = "+"
765
            else:
766
                mem.offset = 0
767

    
768
        # skip
769
        if not isscan:
770
            mem.skip = "S"
771

    
772
        # name TAG of the channel
773
        mem.name = str(_names.name).strip("\xFF")
774

    
775
        # power
776
        mem.power = POWER_LEVELS[int(_mem.txp)]
777

    
778
        # wide/narrow
779
        mem.mode = MODES[int(_mem.wn)]
780

    
781
        # tone data
782
        rxtone = txtone = None
783
        txtone = self.decode_tone(_mem.txtone)
784
        rxtone = self.decode_tone(_mem.rxtone)
785
        chirp_common.split_tone_decode(mem, txtone, rxtone)
786

    
787
        # Extra
788
        mem.extra = RadioSettingGroup("extra", "Extra")
789

    
790
        bcl = RadioSetting("bcl", "Busy channel lockout",
791
                              RadioSettingValueBoolean(bool(_mem.bcl)))
792
        mem.extra.append(bcl)
793

    
794
        revert = RadioSetting("revert", "Revert",
795
                              RadioSettingValueBoolean(bool(_mem.revert)))
796
        mem.extra.append(revert)
797

    
798
        dname = RadioSetting("dname", "Display name",
799
                             RadioSettingValueBoolean(bool(_mem.dname)))
800
        mem.extra.append(dname)
801

    
802
        return mem
803

    
804
    def set_memory(self, mem):
805
        """Set the memory data in the eeprom img from the UI"""
806
        bitpos = (1 << (mem.number % 8))
807
        bytepos = (mem.number / 8)
808

    
809
        _mem = self._memory_obj()[mem.number]
810
        _names = self._name_obj()[mem.number]
811
        _scn = self._scan_obj()[bytepos]
812
        _usd = self._used_obj()[bytepos]
813

    
814
        if mem.empty:
815
            _usd &= ~bitpos
816
            _scn &= ~bitpos
817
            _mem.set_raw("\xFF" * 16)
818
            _names.name = ("\xFF" * 6)
819
            return
820
        else:
821
            _usd |= bitpos
822

    
823
        # frequency
824
        _mem.rxfreq = mem.freq / 10
825

    
826
        # duplex
827
        if mem.duplex == "+":
828
            _mem.txfreq = (mem.freq + mem.offset) / 10
829
        elif mem.duplex == "-":
830
            _mem.txfreq = (mem.freq - mem.offset) / 10
831
        elif mem.duplex == "off":
832
            for i in _mem.txfreq:
833
                i.set_raw("\xFF")
834
        elif mem.duplex == "split":
835
            _mem.txfreq = mem.offset / 10
836
        else:
837
            _mem.txfreq = mem.freq / 10
838

    
839
        # tone data
840
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
841
            chirp_common.split_tone_encode(mem)
842
        self.encode_tone(_mem.txtone, txmode, txtone, txpol)
843
        self.encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
844

    
845
        # name TAG of the channel
846
        _names.name = mem.name.ljust(6, "\xFF")
847

    
848
        # power level, # default power level is low
849
        _mem.txp = 0 if mem.power is None else POWER_LEVELS.index(mem.power)
850

    
851
        # wide/narrow
852
        _mem.wn = MODES.index(mem.mode)
853

    
854
        if mem.skip == "S":
855
            _scn &= ~bitpos
856
        else:
857
            _scn |= bitpos
858

    
859
        # autoset display to display name if filled
860
        if mem.extra:
861
            # mem.extra only seems to be populated when called from edit panel
862
            dname = mem.extra["dname"]
863
        else:
864
            dname = None
865
        if mem.name:
866
            _mem.dname = True
867
            if dname and not dname.changed():
868
                dname.value = True
869
        else:
870
            _mem.dname = False
871
            if dname and not dname.changed():
872
                dname.value = False
873

    
874
        # reseting unknowns, this has to be set by hand
875
        _mem.unknown0 = 0
876
        _mem.unknown1 = 0
877
        _mem.unknown2 = 0
878
        _mem.unknown3 = 0
879

    
880
        # extra settings
881
        if len(mem.extra) > 0:
882
            # there are setting, parse
883
            for setting in mem.extra:
884
                setattr(_mem, setting.get_name(), setting.value)
885
        else:
886
            # there are no extra settings, load defaults
887
            _mem.bcl = 0
888
            _mem.revert = 0
889
            _mem.dname = 1
890

    
891
    def _bbcd2dtmf(self, bcdarr, strlen=16):
892
        # doing bbcd, but with support for ABCD*#
893
        LOG.debug(bcdarr.get_value())
894
        string = ''.join("%02X" % b for b in bcdarr)
895
        LOG.debug("@_bbcd2dtmf, received: %s" % string)
896
        string = string.replace('E', '*').replace('F', '#')
897
        if strlen <= 16:
898
            string = string[:strlen]
899
        return string
900

    
901
    def _dtmf2bbcd(self, value):
902
        dtmfstr = value.get_value()
903
        dtmfstr = dtmfstr.replace('*', 'E').replace('#', 'F')
904
        dtmfstr = str.ljust(dtmfstr.strip(), 16, "F")
905
        bcdarr = list(bytearray.fromhex(dtmfstr))
906
        LOG.debug("@_dtmf2bbcd, sending: %s" % bcdarr)
907
        return bcdarr
908

    
909
    def get_settings(self):
910
        """Translate the bit in the mem_struct into settings in the UI"""
911
        _mem = self._memobj
912
        basic = RadioSettingGroup("basic", "Basic Settings")
913
        other = RadioSettingGroup("other", "Other Settings")
914
        work = RadioSettingGroup("work", "Work Mode Settings")
915
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
916
        top = RadioSettings(basic, other, work, dtmf)
917

    
918
        # Basic
919

    
920
        # Audio: A01-A04
921

    
922
        aftone = RadioSetting("settings.aftone", "AF tone control",
923
                              RadioSettingValueList(LIST_AFTONE, LIST_AFTONE[
924
                                  _mem.settings.aftone]))
925
        basic.append(aftone)
926

    
927
        spkr = RadioSetting("settings.spkr", "Speaker",
928
                            RadioSettingValueList(LIST_SPKR,LIST_SPKR[
929
                                _mem.settings.spkr]))
930
        basic.append(spkr)
931

    
932
        audio = RadioSetting("settings.audio", "Stereo/Mono",
933
                             RadioSettingValueList(LIST_AUDIO, LIST_AUDIO[
934
                                 _mem.settings.audio]))
935
        basic.append(audio)
936

    
937
        sbmute = RadioSetting("settings.sbmute", "Sub band mute",
938
                              RadioSettingValueList(LIST_SBMUTE, LIST_SBMUTE[
939
                                  _mem.settings.sbmute]))
940
        basic.append(sbmute)
941

    
942
        # TX/RX: B01-B08
943

    
944
        mgain = RadioSetting("settings.mgain", "Mic gain",
945
                             RadioSettingValueList(LIST_MLNHM, LIST_MLNHM[
946
                                 _mem.settings.mgain]))
947
        basic.append(mgain)
948

    
949
        ptt = RadioSetting("settings.ptt", "PTT mode",
950
                           RadioSettingValueList(LIST_PTT,LIST_PTT[
951
                               _mem.settings.ptt]))
952
        basic.append(ptt)
953

    
954
        # B03 (per channel)
955
        # B04 (per channel)
956

    
957
        rxexp = RadioSetting("settings.rxexp", "RX expansion",
958
                             RadioSettingValueList(LIST_RXEXP,LIST_RXEXP[
959
                                 _mem.settings.rxexp]))
960
        basic.append(rxexp)
961

    
962
        vox = RadioSetting("settings.vox", "Vox",
963
                           RadioSettingValueList(LIST_VOX, LIST_VOX[
964
                               _mem.settings.vox]))
965
        basic.append(vox)
966

    
967
        voxs = RadioSetting("settings.voxs", "Vox sensitivity",
968
                            RadioSettingValueList(LIST_MLNHM, LIST_MLNHM[
969
                                _mem.settings.voxs]))
970
        basic.append(voxs)
971

    
972
        # B08 (per channel)
973

    
974
        # Display: C01-C06
975

    
976
        display = RadioSetting("settings.display", "Display select",
977
                               RadioSettingValueList(LIST_DISPLAY,
978
                                   LIST_DISPLAY[_mem.settings.display]))
979
        basic.append(display)
980

    
981
        lcdb = RadioSetting("settings.lcdb", "LCD brightness",
982
                            RadioSettingValueList(LIST_MINMAX, LIST_MINMAX[
983
                                _mem.settings.lcdb]))
984
        basic.append(lcdb)
985

    
986
        color = RadioSetting("settings.color", "LCD color",
987
                             RadioSettingValueList(LIST_COLOR, LIST_COLOR[
988
                                 _mem.settings.color]))
989
        basic.append(color)
990

    
991
        lcdc = RadioSetting("settings.lcdc", "LCD contrast",
992
                            RadioSettingValueList(LIST_MINMAX, LIST_MINMAX[
993
                                _mem.settings.lcdc]))
994
        basic.append(lcdc)
995

    
996
        btime = RadioSetting("settings.btime", "LCD backlight time",
997
                             RadioSettingValueList(LIST_BTIME, LIST_BTIME[
998
                                 _mem.settings.btime]))
999
        basic.append(btime)
1000

    
1001
        keyb = RadioSetting("settings.keyb", "Key brightness",
1002
                            RadioSettingValueList(LIST_MINMAX, LIST_MINMAX[
1003
                                _mem.settings.keyb]))
1004
        basic.append(keyb)
1005

    
1006
        # Memory: D01-D04
1007

    
1008
        # D01 (per channel)
1009
        # D02 (per channel)
1010

    
1011
        mrscan = RadioSetting("settings.mrscan", "Memory scan type",
1012
                              RadioSettingValueList(LIST_MRSCAN, LIST_MRSCAN[
1013
                                  _mem.settings.mrscan]))
1014
        basic.append(mrscan)
1015

    
1016
        # D04 (per channel)
1017

    
1018
        # Scan: E01-E04
1019

    
1020
        dwstop = RadioSetting("settings.dwstop", "Dual watch stop",
1021
                              RadioSettingValueList(LIST_DWSTOP, LIST_DWSTOP[
1022
                                  _mem.settings.dwstop]))
1023
        basic.append(dwstop)
1024

    
1025
        scand = RadioSetting("settings.scand", "Scan direction",
1026
                             RadioSettingValueList(LIST_SCAND,LIST_SCAND[
1027
                                 _mem.settings.scand]))
1028
        basic.append(scand)
1029

    
1030
        scanr = RadioSetting("settings.scanr", "Scan resume",
1031
                             RadioSettingValueList(LIST_SCANR,LIST_SCANR[
1032
                                 _mem.settings.scanr]))
1033
        basic.append(scanr)
1034

    
1035
        scansb = RadioSetting("settings.scansb", "Scan stop beep",
1036
                              RadioSettingValueBoolean(_mem.settings.scansb))
1037
        basic.append(scansb)
1038

    
1039
        # System: F01-F09
1040

    
1041
        apo = RadioSetting("settings.apo", "Automatic power off [hours]",
1042
                           RadioSettingValueList(LIST_APO, LIST_APO[
1043
                               _mem.settings.apo]))
1044
        basic.append(apo)
1045

    
1046
        ars = RadioSetting("settings.ars", "Automatic repeater shift",
1047
                           RadioSettingValueBoolean(_mem.settings.ars))
1048
        basic.append(ars)
1049

    
1050
        beep = RadioSetting("settings.beep", "Beep volume",
1051
                            RadioSettingValueList(LIST_BEEP,LIST_BEEP[
1052
                                _mem.settings.beep]))
1053
        basic.append(beep)
1054

    
1055
        fkey = RadioSetting("settings.fkey", "F key",
1056
                            RadioSettingValueList(LIST_FKEY,LIST_FKEY[
1057
                                _mem.settings.fkey]))
1058
        basic.append(fkey)
1059

    
1060
        pfkey1 = RadioSetting("settings.pfkey1", "Mic P1 key",
1061
                              RadioSettingValueList(LIST_PFKEY, LIST_PFKEY[
1062
                                  _mem.settings.pfkey1]))
1063
        basic.append(pfkey1)
1064

    
1065
        pfkey2 = RadioSetting("settings.pfkey2", "Mic P2 key",
1066
                              RadioSettingValueList(LIST_PFKEY, LIST_PFKEY[
1067
                                  _mem.settings.pfkey2]))
1068
        basic.append(pfkey2)
1069

    
1070
        pfkey3 = RadioSetting("settings.pfkey3", "Mic P3 key",
1071
                              RadioSettingValueList(LIST_PFKEY, LIST_PFKEY[
1072
                                  _mem.settings.pfkey3]))
1073
        basic.append(pfkey3)
1074

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

    
1080
        omode = RadioSetting("settings.omode", "Operation mode",
1081
                             RadioSettingValueList(LIST_AB,LIST_AB[
1082
                                 _mem.settings.omode]))
1083
        basic.append(omode)
1084

    
1085
        rxcoverm = RadioSetting("settings.rxcoverm", "RX coverage - memory",
1086
                                RadioSettingValueList(LIST_COVERAGE, 
1087
                                    LIST_COVERAGE[_mem.settings.rxcoverm]))
1088
        basic.append(rxcoverm)
1089

    
1090
        rxcoverv = RadioSetting("settings.rxcoverv", "RX coverage - VFO",
1091
                                RadioSettingValueList(LIST_COVERAGE, 
1092
                                    LIST_COVERAGE[_mem.settings.rxcoverv]))
1093
        basic.append(rxcoverv)
1094

    
1095
        tot = RadioSetting("settings.tot", "Time out timer [min]",
1096
                           RadioSettingValueList(LIST_TOT, LIST_TOT[
1097
                               _mem.settings.tot]))
1098
        basic.append(tot)
1099

    
1100
        # Timer/Clock: G01-G04
1101

    
1102
        # G01
1103
        datefmt = RadioSetting("settings.datefmt", "Date format",
1104
                               RadioSettingValueList(LIST_DATEFMT,
1105
                                   LIST_DATEFMT[_mem.settings.datefmt]))
1106
        basic.append(datefmt)
1107

    
1108
        timefmt = RadioSetting("settings.timefmt", "Time format",
1109
                               RadioSettingValueList(LIST_TIMEFMT,
1110
                                   LIST_TIMEFMT[_mem.settings.timefmt]))
1111
        basic.append(timefmt)
1112

    
1113
        timesig = RadioSetting("settings.timesig", "Time signal",
1114
                               RadioSettingValueBoolean(_mem.settings.timesig))
1115
        basic.append(timesig)
1116

    
1117
        tz = RadioSetting("settings.tz", "Time zone",
1118
                          RadioSettingValueList(LIST_TZ, LIST_TZ[
1119
                              _mem.settings.tz]))
1120
        basic.append(tz)
1121

    
1122
        # Signaling: H01-H06
1123

    
1124
        bell = RadioSetting("settings.bell", "Bell ringer",
1125
                            RadioSettingValueList(LIST_BELL, LIST_BELL[
1126
                                _mem.settings.bell]))
1127
        basic.append(bell)
1128

    
1129
        # H02 (per channel)
1130

    
1131
        dtmfmodenc = RadioSetting("settings.dtmfmodenc", "DTMF mode encode",
1132
                                  RadioSettingValueBoolean(
1133
                                      _mem.settings.dtmfmodenc))
1134
        basic.append(dtmfmodenc)
1135

    
1136
        dtmfmoddec = RadioSetting("settings.dtmfmoddec", "DTMF mode decode",
1137
                                  RadioSettingValueBoolean(
1138
                                      _mem.settings.dtmfmoddec))
1139
        basic.append(dtmfmoddec)
1140

    
1141
        # H04 (per channel)
1142

    
1143
        decbandsel = RadioSetting("settings.decbandsel", "DTMF band select",
1144
                                  RadioSettingValueList(LIST_AB,LIST_AB[
1145
                                      _mem.settings.decbandsel]))
1146
        basic.append(decbandsel)
1147

    
1148
        sqlexp = RadioSetting("settings.sqlexp", "SQL expansion",
1149
                              RadioSettingValueBoolean(_mem.settings.sqlexp))
1150
        basic.append(sqlexp)
1151

    
1152
        # Pkt: I01-I03
1153

    
1154
        databnd = RadioSetting("settings.databnd", "Packet data band",
1155
                               RadioSettingValueList(LIST_DATABND,LIST_DATABND[
1156
                                   _mem.settings.databnd]))
1157
        basic.append(databnd)
1158

    
1159
        dataspd = RadioSetting("settings.dataspd", "Packet data speed",
1160
                               RadioSettingValueList(LIST_DATASPD,LIST_DATASPD[
1161
                                   _mem.settings.dataspd]))
1162
        basic.append(dataspd)
1163

    
1164
        datasql = RadioSetting("settings.datasql", "Packet data squelch",
1165
                               RadioSettingValueList(LIST_DATASQL,LIST_DATASQL[
1166
                                   _mem.settings.datasql]))
1167
        basic.append(datasql)
1168

    
1169
        # Other
1170

    
1171
        dw = RadioSetting("settings.dw", "Dual watch",
1172
                          RadioSettingValueBoolean(_mem.settings.dw))
1173
        other.append(dw)
1174

    
1175
        cpuclk = RadioSetting("settings.cpuclk", "CPU clock frequency",
1176
                              RadioSettingValueList(LIST_CPUCLK,LIST_CPUCLK[
1177
                                  _mem.settings.cpuclk]))
1178
        other.append(cpuclk)
1179

    
1180
        def _filter(name):
1181
            filtered = ""
1182
            for char in str(name):
1183
                if char in VALID_CHARS:
1184
                    filtered += char
1185
                else:
1186
                    filtered += " "
1187
            return filtered
1188

    
1189
        line16 = RadioSetting("poweron_msg.line16", "Power-on message",
1190
                              RadioSettingValueString(0, 16, _filter(
1191
                                  _mem.poweron_msg.line16)))
1192
        other.append(line16)
1193

    
1194
        line32 = RadioSetting("embedded_msg.line32", "Embedded message",
1195
                              RadioSettingValueString(0, 32, _filter(
1196
                                  _mem.embedded_msg.line32)))
1197
        other.append(line32)
1198

    
1199
        # Work
1200

    
1201
        workmoda = RadioSetting("settings.workmoda", "Work mode A",
1202
                                RadioSettingValueList(LIST_WORK,LIST_WORK[
1203
                                    _mem.settings.workmoda]))
1204
        work.append(workmoda)
1205

    
1206
        workmodb = RadioSetting("settings.workmodb", "Work mode B",
1207
                                RadioSettingValueList(LIST_WORK,LIST_WORK[
1208
                                    _mem.settings.workmodb]))
1209
        work.append(workmodb)
1210

    
1211
        wbanda = RadioSetting("settings.wbanda", "Work band A",
1212
                              RadioSettingValueList(LIST_WBANDA, LIST_WBANDA[
1213
                                  (_mem.settings.wbanda) - 1]))
1214
        work.append(wbanda)
1215

    
1216
        wbandb = RadioSetting("settings.wbandb", "Work band B",
1217
                              RadioSettingValueList(LIST_WBANDB, LIST_WBANDB[
1218
                                  (_mem.settings.wbandb) - 4]))
1219
        work.append(wbandb)
1220

    
1221
        sqla = RadioSetting("settings.sqla", "Squelch A",
1222
                            RadioSettingValueList(LIST_SQL, LIST_SQL[
1223
                                _mem.settings.sqla]))
1224
        work.append(sqla)
1225

    
1226
        sqlb = RadioSetting("settings.sqlb", "Squelch B",
1227
                            RadioSettingValueList(LIST_SQL, LIST_SQL[
1228
                                _mem.settings.sqlb]))
1229
        work.append(sqlb)
1230

    
1231
        stepa = RadioSetting("settings.stepa", "Auto step A",
1232
                             RadioSettingValueList(LIST_STEP,LIST_STEP[
1233
                                 _mem.settings.stepa]))
1234
        work.append(stepa)
1235

    
1236
        stepb = RadioSetting("settings.stepb", "Auto step B",
1237
                             RadioSettingValueList(LIST_STEP,LIST_STEP[
1238
                                 _mem.settings.stepb]))
1239
        work.append(stepb)
1240

    
1241
        mrcha = RadioSetting("settings.mrcha", "Current channel A",
1242
                             RadioSettingValueInteger(0, 499,
1243
                                 _mem.settings.mrcha))
1244
        work.append(mrcha)
1245

    
1246
        mrchb = RadioSetting("settings.mrchb", "Current channel B",
1247
                             RadioSettingValueInteger(0, 499,
1248
                                 _mem.settings.mrchb))
1249
        work.append(mrchb)
1250

    
1251
        val = _mem.settings.offseta / 100.00
1252
        offseta = RadioSetting("settings.offseta", "Offset A (0-37.95)",
1253
                               RadioSettingValueFloat(0, 38.00, val, 0.05, 2))
1254
        work.append(offseta)
1255

    
1256
        val = _mem.settings.offsetb / 100.00
1257
        offsetb = RadioSetting("settings.offsetb", "Offset B (0-79.95)",
1258
                               RadioSettingValueFloat(0, 80.00, val, 0.05, 2))
1259
        work.append(offsetb)
1260

    
1261
        wpricha = RadioSetting("settings.wpricha", "Priority channel A",
1262
                               RadioSettingValueInteger(0, 499,
1263
                                   _mem.settings.wpricha))
1264
        work.append(wpricha)
1265

    
1266
        wprichb = RadioSetting("settings.wprichb", "Priority channel B",
1267
                               RadioSettingValueInteger(0, 499,
1268
                                   _mem.settings.wprichb))
1269
        work.append(wprichb)
1270

    
1271
        smode = RadioSetting("settings.smode", "Smart function mode",
1272
                             RadioSettingValueList(LIST_SMODE,LIST_SMODE[
1273
                                 _mem.settings.smode]))
1274
        work.append(smode)
1275

    
1276
        # dtmf
1277

    
1278
        ttdkey = RadioSetting("dtmf.ttdkey", "D key function",
1279
                              RadioSettingValueList(LIST_TTDKEY, LIST_TTDKEY[
1280
                                  _mem.dtmf.ttdkey]))
1281
        dtmf.append(ttdkey)
1282

    
1283
        ttdgt = RadioSetting("dtmf.ttdgt", "Digit time",
1284
                              RadioSettingValueList(LIST_TT200, LIST_TT200[
1285
                                  (_mem.dtmf.ttdgt) - 5]))
1286
        dtmf.append(ttdgt)
1287

    
1288
        ttint = RadioSetting("dtmf.ttint", "Interval time",
1289
                              RadioSettingValueList(LIST_TT200, LIST_TT200[
1290
                                  (_mem.dtmf.ttint) - 5]))
1291
        dtmf.append(ttint)
1292

    
1293
        tt1stdgt = RadioSetting("dtmf.tt1stdgt", "1st digit time",
1294
                                RadioSettingValueList(LIST_TT200, LIST_TT200[
1295
                                    (_mem.dtmf.tt1stdgt) - 5]))
1296
        dtmf.append(tt1stdgt)
1297

    
1298
        tt1stdly = RadioSetting("dtmf.tt1stdly", "1st digit delay time",
1299
                                RadioSettingValueList(LIST_TT1000, LIST_TT1000[
1300
                                    (_mem.dtmf.tt1stdly) - 2]))
1301
        dtmf.append(tt1stdly)
1302

    
1303
        ttdlyqt = RadioSetting("dtmf.ttdlyqt", "Digit delay when use qt",
1304
                               RadioSettingValueList(LIST_TT1000, LIST_TT1000[
1305
                                   (_mem.dtmf.ttdlyqt) - 2]))
1306
        dtmf.append(ttdlyqt)
1307

    
1308
        ttsig = RadioSetting("dtmf2.ttsig", "Signal",
1309
                             RadioSettingValueList(LIST_TTSIG, LIST_TTSIG[
1310
                                 _mem.dtmf2.ttsig]))
1311
        dtmf.append(ttsig)
1312

    
1313
        ttautorst = RadioSetting("dtmf2.ttautorst", "Auto reset time",
1314
                                 RadioSettingValueList(LIST_TTAUTORST,
1315
                                     LIST_TTAUTORST[_mem.dtmf2.ttautorst]))
1316
        dtmf.append(ttautorst)
1317

    
1318
        if _mem.dtmf2.ttgrpcode > 0x06:
1319
            val = 0x00
1320
        else:
1321
            val = _mem.dtmf2.ttgrpcode
1322
        ttgrpcode = RadioSetting("dtmf2.ttgrpcode", "Group code",
1323
                                 RadioSettingValueList(LIST_TTGRPCODE,
1324
                                     LIST_TTGRPCODE[val]))
1325
        dtmf.append(ttgrpcode)
1326

    
1327
        ttintcode = RadioSetting("dtmf2.ttintcode", "Interval code",
1328
                                 RadioSettingValueList(LIST_TTINTCODE,
1329
                                     LIST_TTINTCODE[_mem.dtmf2.ttintcode]))
1330
        dtmf.append(ttintcode)
1331

    
1332
        if _mem.dtmf2.ttalert > 0x04:
1333
            val = 0x00
1334
        else:
1335
            val = _mem.dtmf2.ttalert
1336
        ttalert = RadioSetting("dtmf2.ttalert", "Alert tone/transpond",
1337
                               RadioSettingValueList(LIST_TTALERT,
1338
                                   LIST_TTALERT[val]))
1339
        dtmf.append(ttalert)
1340

    
1341
        ttautod = RadioSetting("dtmf.ttautod", "Auto dial group",
1342
                               RadioSettingValueList(LIST_TTAUTOD,
1343
                                   LIST_TTAUTOD[_mem.dtmf.ttautod]))
1344
        dtmf.append(ttautod)
1345

    
1346
        # setup 9 dtmf autodial entries
1347
        for i in map(str, range(1, 10)):
1348
            objname = "code" + i
1349
            strname = "Code " + str(i)
1350
            dtmfsetting = getattr(_mem.dtmfcode, objname)
1351
            dtmflen = getattr(_mem.dtmfcode, objname + "_len")
1352
            dtmfstr = self._bbcd2dtmf(dtmfsetting, dtmflen)
1353
            code = RadioSettingValueString(0, 16, dtmfstr)
1354
            code.set_charset(DTMF_CHARS + list(" "))
1355
            rs = RadioSetting("dtmfcode." + objname, strname, code)
1356
            dtmf.append(rs)
1357
        return top
1358

    
1359
    def set_settings(self, settings):
1360
        _settings = self._memobj.settings
1361
        _mem = self._memobj
1362
        for element in settings:
1363
            if not isinstance(element, RadioSetting):
1364
                self.set_settings(element)
1365
                continue
1366
            else:
1367
                try:
1368
                    name = element.get_name()
1369
                    if "." in name:
1370
                        bits = name.split(".")
1371
                        obj = self._memobj
1372
                        for bit in bits[:-1]:
1373
                            if "/" in bit:
1374
                                bit, index = bit.split("/", 1)
1375
                                index = int(index)
1376
                                obj = getattr(obj, bit)[index]
1377
                            else:
1378
                                obj = getattr(obj, bit)
1379
                        setting = bits[-1]
1380
                    else:
1381
                        obj = _settings
1382
                        setting = element.get_name()
1383

    
1384
                    if element.has_apply_callback():
1385
                        LOG.debug("Using apply callback")
1386
                        element.run_apply_callback()
1387
                    elif setting == "line16":
1388
                        setattr(obj, setting, str(element.value).rstrip(
1389
                            " ").ljust(16, "\xFF"))
1390
                    elif setting == "line32":
1391
                        setattr(obj, setting, str(element.value).rstrip(
1392
                            " ").ljust(32, "\xFF"))
1393
                    elif setting == "wbanda":
1394
                        setattr(obj, setting, int(element.value) + 1)
1395
                    elif setting == "wbandb":
1396
                        setattr(obj, setting, int(element.value) + 4)
1397
                    elif setting in ["offseta", "offsetb"]:
1398
                        val = element.value
1399
                        value = int(val.get_value() * 100)
1400
                        setattr(obj, setting, value)
1401
                    elif setting in ["ttdgt", "ttint", "tt1stdgt"]:
1402
                        setattr(obj, setting, int(element.value) + 5)
1403
                    elif setting in ["tt1stdly", "ttdlyqt"]:
1404
                        setattr(obj, setting, int(element.value) + 2)
1405
                    elif re.match('code\d', setting):
1406
                        # set dtmf length field and then get bcd dtmf
1407
                        dtmfstrlen = len(str(element.value).strip())
1408
                        setattr(_mem.dtmfcode, setting + "_len", dtmfstrlen)
1409
                        dtmfstr = self._dtmf2bbcd(element.value)
1410
                        setattr(_mem.dtmfcode, setting, dtmfstr)
1411
                    elif element.value.get_mutable():
1412
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1413
                        setattr(obj, setting, element.value)
1414
                except Exception, e:
1415
                    LOG.debug(element.get_name())
1416
                    raise
1417

    
1418

    
1419
    @classmethod
1420
    def match_model(cls, filedata, filename):
1421
        match_size = False
1422
        match_model = False
1423

    
1424
        # testing the file data size
1425
        if len(filedata) == MEM_SIZE:
1426
            match_size = True
1427

    
1428
        # testing the firmware model fingerprint
1429
        match_model = model_match(cls, filedata)
1430

    
1431
        if match_size and match_model:
1432
            return True
1433
        else:
1434
            return False
1435

    
1436

    
1437
@directory.register
1438
class UV50X3(VGCStyleRadio):
1439
    """BTech UV-50X3"""
1440
    MODEL = "UV-50X3"
1441
    IDENT = UV50X3_id
1442

    
1443

    
1444
class UV50X3Left(UV50X3):
1445
    VARIANT = "Left"
1446
    _vfo = "left"
1447

    
1448

    
1449
class UV50X3Right(UV50X3):
1450
    VARIANT = "Right"
1451
    _vfo = "right"