Project

General

Profile

New Model #4263 » vgc_vr6600_test1.py

proof of concept for download from both radios and image detecton - Jim Unroe, 11/27/2016 03:03 PM

 
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
UV50X3_id  = "USA6600A52013417"
350

    
351
# VGC VR-6600PRO id string
352
VR6600PRO_id  = "USA660052013417A"
353

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

    
361

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

    
370

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

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

    
386
    return data
387

    
388

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

    
396

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

    
406

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

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

    
420
    # read 64 bytes (0x40) of data
421
    data = _rawrecv(radio, (BLOCK_SIZE))
422

    
423
    # DEBUG
424
    LOG.info("Response:")
425
    LOG.debug(util.hexprint(hdr + data))
426

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

    
435
    return data
436

    
437

    
438
def _do_ident(radio):
439
    """Put the radio in PROGRAM mode & identify it"""
440
    #  set the serial discipline
441
    radio.pipe.baudrate = 115200
442
    radio.pipe.parity = "N"
443
    radio.pipe.timeout = STIMEOUT
444

    
445
    # flush input buffer
446
    _clean_buffer(radio)
447

    
448
    magic = "V66LINK"
449

    
450
    _rawsend(radio, magic)
451

    
452
    # Ok, get the ident string
453
    ident = _rawrecv(radio, 9)
454

    
455
    # check if ident is OK
456
    ####if ident != radio.IDENT:
457
    if ident != "VGC6600MD":
458
        # bad ident
459
        msg = "Incorrect model ID, got this:"
460
        msg +=  util.hexprint(ident)
461
        LOG.debug(msg)
462
        raise errors.RadioError("Radio identification failed.")
463

    
464
    # DEBUG
465
    LOG.info("Positive ident, got this:")
466
    LOG.debug(util.hexprint(ident))
467

    
468
    return True
469

    
470

    
471
def _exit_program_mode(radio):
472
    endframe = "\x45"
473
    _rawsend(radio, endframe)
474

    
475

    
476
def _download(radio):
477
    """Get the memory map"""
478

    
479
    # put radio in program mode and identify it
480
    _do_ident(radio)
481

    
482
    # UI progress
483
    status = chirp_common.Status()
484
    status.cur = 0
485
    status.max = MEM_SIZE / BLOCK_SIZE
486
    status.msg = "Cloning from radio..."
487
    radio.status_fn(status)
488

    
489
    data = ""
490
    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
491
        frame = _make_frame("R", addr, BLOCK_SIZE)
492
        # DEBUG
493
        LOG.info("Request sent:")
494
        LOG.debug(util.hexprint(frame))
495

    
496
        # sending the read request
497
        _rawsend(radio, frame)
498

    
499
        # now we read
500
        d = _recv(radio, addr)
501

    
502
        # aggregate the data
503
        data += d
504

    
505
        # UI Update
506
        status.cur = addr / BLOCK_SIZE
507
        status.msg = "Cloning from radio..."
508
        radio.status_fn(status)
509

    
510
    _exit_program_mode(radio)
511

    
512
    return data
513

    
514

    
515
def _upload(radio):
516
    """Upload procedure"""
517

    
518
    MEM_SIZE = 0x7000
519

    
520
    # put radio in program mode and identify it
521
    _do_ident(radio)
522

    
523
    # UI progress
524
    status = chirp_common.Status()
525
    status.cur = 0
526
    status.max = MEM_SIZE / BLOCK_SIZE
527
    status.msg = "Cloning to radio..."
528
    radio.status_fn(status)
529

    
530
    # the fun start here
531
    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
532
        # sending the data
533
        data = radio.get_mmap()[addr:addr + BLOCK_SIZE]
534

    
535
        frame = _make_frame("W", addr, BLOCK_SIZE, data)
536

    
537
        _rawsend(radio, frame)
538

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

    
546
        _check_for_double_ack(radio)
547

    
548
        # UI Update
549
        status.cur = addr / BLOCK_SIZE
550
        status.msg = "Cloning to radio..."
551
        radio.status_fn(status)
552

    
553
    _exit_program_mode(radio)
554

    
555

    
556
def model_match(cls, data):
557
    """Match the opened/downloaded image to the correct version"""
558
    ####rid = data[0x6140:0x6148]
559
    rid = data[0x7FF0:0x8000]
560

    
561
    #if rid in cls._fileid:
562
    if rid in cls.IDENT:
563
        return True
564

    
565
    return False
566

    
567

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

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

    
595
            1 - Turn off your radio
596
            2 - Connect your interface cable
597
            3 - Turn on your radio
598
            4 - Do the download of your radio data
599
            """))
600
        rp.pre_upload = _(dedent("""\
601
            Follow this instructions to upload your info:
602

    
603
            1 - Turn off your radio
604
            2 - Connect your interface cable
605
            3 - Turn on your radio
606
            4 - Do the upload of your radio data
607
            """))
608
        return rp
609

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

    
651
    def get_sub_devices(self):
652
        return [UV50X3Left(self._mmap), UV50X3Right(self._mmap)]
653

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

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

    
681
    def process_mmap(self):
682
        """Process the mem map into the mem object"""
683
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
684

    
685
    def get_raw_memory(self, number):
686
        return repr(self._memobj.memory[number])
687

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

    
694
        val = int(val)
695
        if val >= 12000:
696
            a = val - 12000
697
            return 'DTCS', a, 'R'
698
        elif val >= 8000:
699
            a = val - 8000
700
            return 'DTCS', a, 'N'
701
        else:
702
            a = val / 10.0
703
            return 'Tone', a, None
704

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

    
719
    def _memory_obj(self, suffix=""):
720
        return getattr(self._memobj, "%s_memory%s" % (self._vfo, suffix))
721

    
722
    def _name_obj(self, suffix=""):
723
        return getattr(self._memobj, "%s_names%s" % (self._vfo, suffix))
724

    
725
    def _scan_obj(self, suffix=""):
726
        return getattr(self._memobj, "%s_scanflags%s" % (self._vfo, suffix))
727

    
728
    def _used_obj(self, suffix=""):
729
        return getattr(self._memobj, "%s_usedflags%s" % (self._vfo, suffix))
730

    
731
    def get_memory(self, number):
732
        """Get the mem representation from the radio image"""
733
        bitpos = (1 << (number % 8))
734
        bytepos = (number / 8)
735

    
736
        _mem = self._memory_obj()[number]
737
        _names = self._name_obj()[number]
738
        _scn = self._scan_obj()[bytepos]
739
        _usd = self._used_obj()[bytepos]
740

    
741
        isused = bitpos & int(_usd)
742
        isscan = bitpos & int(_scn)
743

    
744
        # Create a high-level memory object to return to the UI
745
        mem = chirp_common.Memory()
746

    
747
        # Memory number
748
        mem.number = number
749

    
750
        if not isused:
751
            mem.empty = True
752
            return mem
753

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

    
773
        # skip
774
        if not isscan:
775
            mem.skip = "S"
776

    
777
        # name TAG of the channel
778
        mem.name = str(_names.name).strip("\xFF")
779

    
780
        # power
781
        mem.power = POWER_LEVELS[int(_mem.txp)]
782

    
783
        # wide/narrow
784
        mem.mode = MODES[int(_mem.wn)]
785

    
786
        # tone data
787
        rxtone = txtone = None
788
        txtone = self.decode_tone(_mem.txtone)
789
        rxtone = self.decode_tone(_mem.rxtone)
790
        chirp_common.split_tone_decode(mem, txtone, rxtone)
791

    
792
        # Extra
793
        mem.extra = RadioSettingGroup("extra", "Extra")
794

    
795
        bcl = RadioSetting("bcl", "Busy channel lockout",
796
                              RadioSettingValueBoolean(bool(_mem.bcl)))
797
        mem.extra.append(bcl)
798

    
799
        revert = RadioSetting("revert", "Revert",
800
                              RadioSettingValueBoolean(bool(_mem.revert)))
801
        mem.extra.append(revert)
802

    
803
        dname = RadioSetting("dname", "Display name",
804
                             RadioSettingValueBoolean(bool(_mem.dname)))
805
        mem.extra.append(dname)
806

    
807
        return mem
808

    
809
    def set_memory(self, mem):
810
        """Set the memory data in the eeprom img from the UI"""
811
        bitpos = (1 << (mem.number % 8))
812
        bytepos = (mem.number / 8)
813

    
814
        _mem = self._memory_obj()[mem.number]
815
        _names = self._name_obj()[mem.number]
816
        _scn = self._scan_obj()[bytepos]
817
        _usd = self._used_obj()[bytepos]
818

    
819
        if mem.empty:
820
            _usd &= ~bitpos
821
            _scn &= ~bitpos
822
            _mem.set_raw("\xFF" * 16)
823
            _names.name = ("\xFF" * 6)
824
            return
825
        else:
826
            _usd |= bitpos
827

    
828
        # frequency
829
        _mem.rxfreq = mem.freq / 10
830

    
831
        # duplex
832
        if mem.duplex == "+":
833
            _mem.txfreq = (mem.freq + mem.offset) / 10
834
        elif mem.duplex == "-":
835
            _mem.txfreq = (mem.freq - mem.offset) / 10
836
        elif mem.duplex == "off":
837
            for i in _mem.txfreq:
838
                i.set_raw("\xFF")
839
        elif mem.duplex == "split":
840
            _mem.txfreq = mem.offset / 10
841
        else:
842
            _mem.txfreq = mem.freq / 10
843

    
844
        # tone data
845
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
846
            chirp_common.split_tone_encode(mem)
847
        self.encode_tone(_mem.txtone, txmode, txtone, txpol)
848
        self.encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
849

    
850
        # name TAG of the channel
851
        _names.name = mem.name.ljust(6, "\xFF")
852

    
853
        # power level, # default power level is low
854
        _mem.txp = 0 if mem.power is None else POWER_LEVELS.index(mem.power)
855

    
856
        # wide/narrow
857
        _mem.wn = MODES.index(mem.mode)
858

    
859
        if mem.skip == "S":
860
            _scn &= ~bitpos
861
        else:
862
            _scn |= bitpos
863

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

    
879
        # reseting unknowns, this has to be set by hand
880
        _mem.unknown0 = 0
881
        _mem.unknown1 = 0
882
        _mem.unknown2 = 0
883
        _mem.unknown3 = 0
884

    
885
        # extra settings
886
        if len(mem.extra) > 0:
887
            # there are setting, parse
888
            for setting in mem.extra:
889
                setattr(_mem, setting.get_name(), setting.value)
890
        else:
891
            # there are no extra settings, load defaults
892
            _mem.bcl = 0
893
            _mem.revert = 0
894
            _mem.dname = 1
895

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

    
906
    def _dtmf2bbcd(self, value):
907
        dtmfstr = value.get_value()
908
        dtmfstr = dtmfstr.replace('*', 'E').replace('#', 'F')
909
        dtmfstr = str.ljust(dtmfstr.strip(), 16, "F")
910
        bcdarr = list(bytearray.fromhex(dtmfstr))
911
        LOG.debug("@_dtmf2bbcd, sending: %s" % bcdarr)
912
        return bcdarr
913

    
914
    def get_settings(self):
915
        """Translate the bit in the mem_struct into settings in the UI"""
916
        _mem = self._memobj
917
        basic = RadioSettingGroup("basic", "Basic Settings")
918
        other = RadioSettingGroup("other", "Other Settings")
919
        work = RadioSettingGroup("work", "Work Mode Settings")
920
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
921
        top = RadioSettings(basic, other, work, dtmf)
922

    
923
        # Basic
924

    
925
        # Audio: A01-A04
926

    
927
        aftone = RadioSetting("settings.aftone", "AF tone control",
928
                              RadioSettingValueList(LIST_AFTONE, LIST_AFTONE[
929
                                  _mem.settings.aftone]))
930
        basic.append(aftone)
931

    
932
        spkr = RadioSetting("settings.spkr", "Speaker",
933
                            RadioSettingValueList(LIST_SPKR,LIST_SPKR[
934
                                _mem.settings.spkr]))
935
        basic.append(spkr)
936

    
937
        audio = RadioSetting("settings.audio", "Stereo/Mono",
938
                             RadioSettingValueList(LIST_AUDIO, LIST_AUDIO[
939
                                 _mem.settings.audio]))
940
        basic.append(audio)
941

    
942
        sbmute = RadioSetting("settings.sbmute", "Sub band mute",
943
                              RadioSettingValueList(LIST_SBMUTE, LIST_SBMUTE[
944
                                  _mem.settings.sbmute]))
945
        basic.append(sbmute)
946

    
947
        # TX/RX: B01-B08
948

    
949
        mgain = RadioSetting("settings.mgain", "Mic gain",
950
                             RadioSettingValueList(LIST_MLNHM, LIST_MLNHM[
951
                                 _mem.settings.mgain]))
952
        basic.append(mgain)
953

    
954
        ptt = RadioSetting("settings.ptt", "PTT mode",
955
                           RadioSettingValueList(LIST_PTT,LIST_PTT[
956
                               _mem.settings.ptt]))
957
        basic.append(ptt)
958

    
959
        # B03 (per channel)
960
        # B04 (per channel)
961

    
962
        rxexp = RadioSetting("settings.rxexp", "RX expansion",
963
                             RadioSettingValueList(LIST_RXEXP,LIST_RXEXP[
964
                                 _mem.settings.rxexp]))
965
        basic.append(rxexp)
966

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

    
972
        voxs = RadioSetting("settings.voxs", "Vox sensitivity",
973
                            RadioSettingValueList(LIST_MLNHM, LIST_MLNHM[
974
                                _mem.settings.voxs]))
975
        basic.append(voxs)
976

    
977
        # B08 (per channel)
978

    
979
        # Display: C01-C06
980

    
981
        display = RadioSetting("settings.display", "Display select",
982
                               RadioSettingValueList(LIST_DISPLAY,
983
                                   LIST_DISPLAY[_mem.settings.display]))
984
        basic.append(display)
985

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

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

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

    
1001
        btime = RadioSetting("settings.btime", "LCD backlight time",
1002
                             RadioSettingValueList(LIST_BTIME, LIST_BTIME[
1003
                                 _mem.settings.btime]))
1004
        basic.append(btime)
1005

    
1006
        keyb = RadioSetting("settings.keyb", "Key brightness",
1007
                            RadioSettingValueList(LIST_MINMAX, LIST_MINMAX[
1008
                                _mem.settings.keyb]))
1009
        basic.append(keyb)
1010

    
1011
        # Memory: D01-D04
1012

    
1013
        # D01 (per channel)
1014
        # D02 (per channel)
1015

    
1016
        mrscan = RadioSetting("settings.mrscan", "Memory scan type",
1017
                              RadioSettingValueList(LIST_MRSCAN, LIST_MRSCAN[
1018
                                  _mem.settings.mrscan]))
1019
        basic.append(mrscan)
1020

    
1021
        # D04 (per channel)
1022

    
1023
        # Scan: E01-E04
1024

    
1025
        dwstop = RadioSetting("settings.dwstop", "Dual watch stop",
1026
                              RadioSettingValueList(LIST_DWSTOP, LIST_DWSTOP[
1027
                                  _mem.settings.dwstop]))
1028
        basic.append(dwstop)
1029

    
1030
        scand = RadioSetting("settings.scand", "Scan direction",
1031
                             RadioSettingValueList(LIST_SCAND,LIST_SCAND[
1032
                                 _mem.settings.scand]))
1033
        basic.append(scand)
1034

    
1035
        scanr = RadioSetting("settings.scanr", "Scan resume",
1036
                             RadioSettingValueList(LIST_SCANR,LIST_SCANR[
1037
                                 _mem.settings.scanr]))
1038
        basic.append(scanr)
1039

    
1040
        scansb = RadioSetting("settings.scansb", "Scan stop beep",
1041
                              RadioSettingValueBoolean(_mem.settings.scansb))
1042
        basic.append(scansb)
1043

    
1044
        # System: F01-F09
1045

    
1046
        apo = RadioSetting("settings.apo", "Automatic power off [hours]",
1047
                           RadioSettingValueList(LIST_APO, LIST_APO[
1048
                               _mem.settings.apo]))
1049
        basic.append(apo)
1050

    
1051
        ars = RadioSetting("settings.ars", "Automatic repeater shift",
1052
                           RadioSettingValueBoolean(_mem.settings.ars))
1053
        basic.append(ars)
1054

    
1055
        beep = RadioSetting("settings.beep", "Beep volume",
1056
                            RadioSettingValueList(LIST_BEEP,LIST_BEEP[
1057
                                _mem.settings.beep]))
1058
        basic.append(beep)
1059

    
1060
        fkey = RadioSetting("settings.fkey", "F key",
1061
                            RadioSettingValueList(LIST_FKEY,LIST_FKEY[
1062
                                _mem.settings.fkey]))
1063
        basic.append(fkey)
1064

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

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

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

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

    
1085
        omode = RadioSetting("settings.omode", "Operation mode",
1086
                             RadioSettingValueList(LIST_AB,LIST_AB[
1087
                                 _mem.settings.omode]))
1088
        basic.append(omode)
1089

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

    
1095
        rxcoverv = RadioSetting("settings.rxcoverv", "RX coverage - VFO",
1096
                                RadioSettingValueList(LIST_COVERAGE, 
1097
                                    LIST_COVERAGE[_mem.settings.rxcoverv]))
1098
        basic.append(rxcoverv)
1099

    
1100
        tot = RadioSetting("settings.tot", "Time out timer [min]",
1101
                           RadioSettingValueList(LIST_TOT, LIST_TOT[
1102
                               _mem.settings.tot]))
1103
        basic.append(tot)
1104

    
1105
        # Timer/Clock: G01-G04
1106

    
1107
        # G01
1108
        datefmt = RadioSetting("settings.datefmt", "Date format",
1109
                               RadioSettingValueList(LIST_DATEFMT,
1110
                                   LIST_DATEFMT[_mem.settings.datefmt]))
1111
        basic.append(datefmt)
1112

    
1113
        timefmt = RadioSetting("settings.timefmt", "Time format",
1114
                               RadioSettingValueList(LIST_TIMEFMT,
1115
                                   LIST_TIMEFMT[_mem.settings.timefmt]))
1116
        basic.append(timefmt)
1117

    
1118
        timesig = RadioSetting("settings.timesig", "Time signal",
1119
                               RadioSettingValueBoolean(_mem.settings.timesig))
1120
        basic.append(timesig)
1121

    
1122
        tz = RadioSetting("settings.tz", "Time zone",
1123
                          RadioSettingValueList(LIST_TZ, LIST_TZ[
1124
                              _mem.settings.tz]))
1125
        basic.append(tz)
1126

    
1127
        # Signaling: H01-H06
1128

    
1129
        bell = RadioSetting("settings.bell", "Bell ringer",
1130
                            RadioSettingValueList(LIST_BELL, LIST_BELL[
1131
                                _mem.settings.bell]))
1132
        basic.append(bell)
1133

    
1134
        # H02 (per channel)
1135

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

    
1141
        dtmfmoddec = RadioSetting("settings.dtmfmoddec", "DTMF mode decode",
1142
                                  RadioSettingValueBoolean(
1143
                                      _mem.settings.dtmfmoddec))
1144
        basic.append(dtmfmoddec)
1145

    
1146
        # H04 (per channel)
1147

    
1148
        decbandsel = RadioSetting("settings.decbandsel", "DTMF band select",
1149
                                  RadioSettingValueList(LIST_AB,LIST_AB[
1150
                                      _mem.settings.decbandsel]))
1151
        basic.append(decbandsel)
1152

    
1153
        sqlexp = RadioSetting("settings.sqlexp", "SQL expansion",
1154
                              RadioSettingValueBoolean(_mem.settings.sqlexp))
1155
        basic.append(sqlexp)
1156

    
1157
        # Pkt: I01-I03
1158

    
1159
        databnd = RadioSetting("settings.databnd", "Packet data band",
1160
                               RadioSettingValueList(LIST_DATABND,LIST_DATABND[
1161
                                   _mem.settings.databnd]))
1162
        basic.append(databnd)
1163

    
1164
        dataspd = RadioSetting("settings.dataspd", "Packet data speed",
1165
                               RadioSettingValueList(LIST_DATASPD,LIST_DATASPD[
1166
                                   _mem.settings.dataspd]))
1167
        basic.append(dataspd)
1168

    
1169
        datasql = RadioSetting("settings.datasql", "Packet data squelch",
1170
                               RadioSettingValueList(LIST_DATASQL,LIST_DATASQL[
1171
                                   _mem.settings.datasql]))
1172
        basic.append(datasql)
1173

    
1174
        # Other
1175

    
1176
        dw = RadioSetting("settings.dw", "Dual watch",
1177
                          RadioSettingValueBoolean(_mem.settings.dw))
1178
        other.append(dw)
1179

    
1180
        cpuclk = RadioSetting("settings.cpuclk", "CPU clock frequency",
1181
                              RadioSettingValueList(LIST_CPUCLK,LIST_CPUCLK[
1182
                                  _mem.settings.cpuclk]))
1183
        other.append(cpuclk)
1184

    
1185
        def _filter(name):
1186
            filtered = ""
1187
            for char in str(name):
1188
                if char in VALID_CHARS:
1189
                    filtered += char
1190
                else:
1191
                    filtered += " "
1192
            return filtered
1193

    
1194
        line16 = RadioSetting("poweron_msg.line16", "Power-on message",
1195
                              RadioSettingValueString(0, 16, _filter(
1196
                                  _mem.poweron_msg.line16)))
1197
        other.append(line16)
1198

    
1199
        line32 = RadioSetting("embedded_msg.line32", "Embedded message",
1200
                              RadioSettingValueString(0, 32, _filter(
1201
                                  _mem.embedded_msg.line32)))
1202
        other.append(line32)
1203

    
1204
        # Work
1205

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

    
1211
        workmodb = RadioSetting("settings.workmodb", "Work mode B",
1212
                                RadioSettingValueList(LIST_WORK,LIST_WORK[
1213
                                    _mem.settings.workmodb]))
1214
        work.append(workmodb)
1215

    
1216
        wbanda = RadioSetting("settings.wbanda", "Work band A",
1217
                              RadioSettingValueList(LIST_WBANDA, LIST_WBANDA[
1218
                                  (_mem.settings.wbanda) - 1]))
1219
        work.append(wbanda)
1220

    
1221
        wbandb = RadioSetting("settings.wbandb", "Work band B",
1222
                              RadioSettingValueList(LIST_WBANDB, LIST_WBANDB[
1223
                                  (_mem.settings.wbandb) - 4]))
1224
        work.append(wbandb)
1225

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

    
1231
        sqlb = RadioSetting("settings.sqlb", "Squelch B",
1232
                            RadioSettingValueList(LIST_SQL, LIST_SQL[
1233
                                _mem.settings.sqlb]))
1234
        work.append(sqlb)
1235

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

    
1241
        stepb = RadioSetting("settings.stepb", "Auto step B",
1242
                             RadioSettingValueList(LIST_STEP,LIST_STEP[
1243
                                 _mem.settings.stepb]))
1244
        work.append(stepb)
1245

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

    
1251
        mrchb = RadioSetting("settings.mrchb", "Current channel B",
1252
                             RadioSettingValueInteger(0, 499,
1253
                                 _mem.settings.mrchb))
1254
        work.append(mrchb)
1255

    
1256
        val = _mem.settings.offseta / 100.00
1257
        offseta = RadioSetting("settings.offseta", "Offset A (0-37.95)",
1258
                               RadioSettingValueFloat(0, 38.00, val, 0.05, 2))
1259
        work.append(offseta)
1260

    
1261
        val = _mem.settings.offsetb / 100.00
1262
        offsetb = RadioSetting("settings.offsetb", "Offset B (0-79.95)",
1263
                               RadioSettingValueFloat(0, 80.00, val, 0.05, 2))
1264
        work.append(offsetb)
1265

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

    
1271
        wprichb = RadioSetting("settings.wprichb", "Priority channel B",
1272
                               RadioSettingValueInteger(0, 499,
1273
                                   _mem.settings.wprichb))
1274
        work.append(wprichb)
1275

    
1276
        smode = RadioSetting("settings.smode", "Smart function mode",
1277
                             RadioSettingValueList(LIST_SMODE,LIST_SMODE[
1278
                                 _mem.settings.smode]))
1279
        work.append(smode)
1280

    
1281
        # dtmf
1282

    
1283
        ttdkey = RadioSetting("dtmf.ttdkey", "D key function",
1284
                              RadioSettingValueList(LIST_TTDKEY, LIST_TTDKEY[
1285
                                  _mem.dtmf.ttdkey]))
1286
        dtmf.append(ttdkey)
1287

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

    
1293
        ttint = RadioSetting("dtmf.ttint", "Interval time",
1294
                              RadioSettingValueList(LIST_TT200, LIST_TT200[
1295
                                  (_mem.dtmf.ttint) - 5]))
1296
        dtmf.append(ttint)
1297

    
1298
        tt1stdgt = RadioSetting("dtmf.tt1stdgt", "1st digit time",
1299
                                RadioSettingValueList(LIST_TT200, LIST_TT200[
1300
                                    (_mem.dtmf.tt1stdgt) - 5]))
1301
        dtmf.append(tt1stdgt)
1302

    
1303
        tt1stdly = RadioSetting("dtmf.tt1stdly", "1st digit delay time",
1304
                                RadioSettingValueList(LIST_TT1000, LIST_TT1000[
1305
                                    (_mem.dtmf.tt1stdly) - 2]))
1306
        dtmf.append(tt1stdly)
1307

    
1308
        ttdlyqt = RadioSetting("dtmf.ttdlyqt", "Digit delay when use qt",
1309
                               RadioSettingValueList(LIST_TT1000, LIST_TT1000[
1310
                                   (_mem.dtmf.ttdlyqt) - 2]))
1311
        dtmf.append(ttdlyqt)
1312

    
1313
        ttsig = RadioSetting("dtmf2.ttsig", "Signal",
1314
                             RadioSettingValueList(LIST_TTSIG, LIST_TTSIG[
1315
                                 _mem.dtmf2.ttsig]))
1316
        dtmf.append(ttsig)
1317

    
1318
        ttautorst = RadioSetting("dtmf2.ttautorst", "Auto reset time",
1319
                                 RadioSettingValueList(LIST_TTAUTORST,
1320
                                     LIST_TTAUTORST[_mem.dtmf2.ttautorst]))
1321
        dtmf.append(ttautorst)
1322

    
1323
        if _mem.dtmf2.ttgrpcode > 0x06:
1324
            val = 0x00
1325
        else:
1326
            val = _mem.dtmf2.ttgrpcode
1327
        ttgrpcode = RadioSetting("dtmf2.ttgrpcode", "Group code",
1328
                                 RadioSettingValueList(LIST_TTGRPCODE,
1329
                                     LIST_TTGRPCODE[val]))
1330
        dtmf.append(ttgrpcode)
1331

    
1332
        ttintcode = RadioSetting("dtmf2.ttintcode", "Interval code",
1333
                                 RadioSettingValueList(LIST_TTINTCODE,
1334
                                     LIST_TTINTCODE[_mem.dtmf2.ttintcode]))
1335
        dtmf.append(ttintcode)
1336

    
1337
        if _mem.dtmf2.ttalert > 0x04:
1338
            val = 0x00
1339
        else:
1340
            val = _mem.dtmf2.ttalert
1341
        ttalert = RadioSetting("dtmf2.ttalert", "Alert tone/transpond",
1342
                               RadioSettingValueList(LIST_TTALERT,
1343
                                   LIST_TTALERT[val]))
1344
        dtmf.append(ttalert)
1345

    
1346
        ttautod = RadioSetting("dtmf.ttautod", "Auto dial group",
1347
                               RadioSettingValueList(LIST_TTAUTOD,
1348
                                   LIST_TTAUTOD[_mem.dtmf.ttautod]))
1349
        dtmf.append(ttautod)
1350

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

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

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

    
1423

    
1424
    @classmethod
1425
    def match_model(cls, filedata, filename):
1426
        match_size = False
1427
        match_model = False
1428

    
1429
        # testing the file data size
1430
        if len(filedata) == MEM_SIZE:
1431
            match_size = True
1432

    
1433
        # testing the firmware model fingerprint
1434
        match_model = model_match(cls, filedata)
1435

    
1436
        if match_size and match_model:
1437
            return True
1438
        else:
1439
            return False
1440

    
1441

    
1442
@directory.register
1443
class UV50X3(VGCStyleRadio):
1444
    """BTech UV-50X3"""
1445
    VENDOR = "BTECH"
1446
    MODEL = "UV-50X3"
1447
    IDENT = UV50X3_id
1448

    
1449

    
1450
class UV50X3Left(UV50X3):
1451
    VARIANT = "Left"
1452
    _vfo = "left"
1453

    
1454

    
1455
class UV50X3Right(UV50X3):
1456
    VARIANT = "Right"
1457
    _vfo = "right"
1458

    
1459
@directory.register
1460
class VR6600PRO(VGCStyleRadio):
1461
    """VGC VR-6600PRO"""
1462
    VENDOR = "VGC"
1463
    MODEL = "VR-6600PRO"
1464
    IDENT = VR6600PRO_id
1465

    
1466

    
1467
class VR6600PROLeft(UV50X3):
1468
    VARIANT = "Left"
1469
    _vfo = "left"
1470

    
1471

    
1472
class VR6600PRORight(UV50X3):
1473
    VARIANT = "Right"
1474
    _vfo = "right"
(11-11/19)