Project

General

Profile

New Model #2107 ยป tdxone_tdq8a_beta1.py

first test driver with TDXone TD-Q8A support - Jim Unroe, 12/27/2016 03:22 PM

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

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

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

    
31
LOG = logging.getLogger(__name__)
32

    
33
MEM_FORMAT = """
34
#seekto 0x0010;
35
struct {
36
  lbcd rxfreq[4];
37
  lbcd txfreq[4];
38
  ul16 rxtone;
39
  ul16 txtone;
40
  u8 unknown1:2,
41
     dtmf:1,          // DTMF
42
     unknown2:1,
43
     bcl:1,           // Busy Channel Lockout
44
     unknown3:3;
45
  u8 unknown4:1,
46
     scan:1,          // Scan Add
47
     highpower:1,     // TX Power Level
48
     wide:1,          // BandWidth
49
     unknown5:4;
50
  u8 unknown6[2];
51
} memory[128];
52

    
53
#seekto 0x0E17;
54
struct {
55
  u8 displayab:1,  // Selected Display
56
     unknown1:6,
57
     unknown2:1;
58
} settings1;
59

    
60
#seekto 0x0E22;
61
struct {
62
  u8 squelcha;      // menu 02a Squelch Level              0xe22
63
  u8 unknown1;
64
  u8 tdrab;         //          TDR A/B                    0xe24
65
  u8 roger;         // menu 20  Roger Beep                 0xe25
66
  u8 timeout;       // menu 16  TOT                        0xe26
67
  u8 vox;           // menu 05  VOX                        0xe27
68
  u8 unknown2;
69
  u8 mdfb;          // menu 27b Memory Display Format B    0xe37
70
  u8 dw;            // menu 37  DW                         0xe2a
71
  u8 tdr;           // menu 29  Dual Watch                 0xe2b
72
  u8 voice;         // menu 03  Voice Prompts              0xe2c
73
  u8 beep;          // menu 01  Key Beep                   0xe2d
74
  u8 ani;           // menu 30  ANI                        0xe2e
75
  u8 unknown3[4];
76
  u8 pttdly;        // menu 31  PTT-ID Delay               0xe33
77
  u8 unknown4;
78
  u8 dtmfst;        // menu 33  DTMF Side Tone             0xe35
79
  u8 toa;           // menu 15  TOT Pre-Alert              0xe36
80
  u8 mdfa;          // menu 27a Memory Display Format A    0xe37
81
  u8 screv;         // menu 09  Scan Resume Method         0xe38
82
  u8 pttid;         // menu 32  PTT-ID Enable              0xe39
83
  u8 ponmsg;        // menu 36  Power-on Message           0xe3a
84
  u8 pf1;           // menu 28  Programmable Function Key  0xe3b
85
  u8 unknown5;
86
  u8 wtled;         // menu 17  Standby LED Color          0xe3d
87
  u8 rxled;         // menu 18  RX LED Color               0xe3e
88
  u8 txled;         // menu 19  TX LED Color               0xe3f
89
  u8 unknown6;
90
  u8 autolk;        // menu 06  Auto Key Lock              0xe41
91
  u8 squelchb;      // menu 02b Squelch Level              0xe42
92
  u8 control;       //          Control Code               0xe43
93
  u8 unknown7;
94
  u8 ach;           //          Selected A channel Number  0xe45
95
  u8 unknown8[4];
96
  u8 password[6];   //          Control Password           0xe4a-0xe4f
97
  u8 unknown9[7];
98
  u8 code[3];       //          PTT ID Code                0xe57-0xe59
99
  u8 vfomr;         //          Frequency/Channel Modevel  0xe5a
100
  u8 keylk;         //          Key Lock                   0xe5b
101
  u8 unknown10[2];
102
  u8 prioritych;    //          Priority Channel           0xe5e
103
  u8 bch;           //          Selected B channel Number  0xe5f
104
} settings;
105

    
106
struct vfo {
107
  u8 unknown0[8];
108
  u8 freq[8];
109
  u8 offset[6];
110
  ul16 rxtone;
111
  ul16 txtone;
112
  u8 unused0:7,
113
     band:1;
114
  u8 unknown3;
115
  u8 unknown4:2,
116
     sftd:2,
117
     scode:4;
118
  u8 unknown5;
119
  u8 unknown6:1,
120
     step:3,
121
     unknown7:4;
122
  u8 txpower:1,
123
     widenarr:1,
124
     unknown8:6;
125
};
126

    
127
#seekto 0x0F10;
128
struct {
129
  struct vfo a;
130
  struct vfo b;
131
} vfo;
132

    
133
#seekto 0x1010;
134
struct {
135
  u8 name[6];
136
  u8 unknown[10];
137
} names[128];
138

    
139
"""
140

    
141
##### MAGICS #########################################################
142

    
143
# TDXone TD-Q8A magic string
144
MSTRING_TDQ8A = "\x02PYNCRAM"
145

    
146
#STIMEOUT = 2
147

    
148
LIST_DTMF = ["QT", "QT+DTMF"]
149
LIST_VOICE = ["Off", "Chinese", "English"]
150
LIST_OFF1TO9 = ["Off"] + list("123456789")
151
LIST_OFF1TO10 = LIST_OFF1TO9 + ["10"]
152
LIST_RESUME = ["Time Operated(TO)", "Carrier Operated(CO)", "Search(SE)"]
153
LIST_COLOR = ["Off", "Blue", "Orange", "Purple"]
154
LIST_MODE = ["Channel", "Frequency", "Name"]
155
LIST_PF1 = ["Off", "Scan", "Lamp", "FM Radio", "Alarm"]
156
LIST_OFF1TO30 = ["OFF"] + ["%s" % x for x in range(1, 31)]
157
LIST_DTMFST = ["Off", "DTMF Sidetone", "ANI Sidetone", "DTMF+ANI Sidetone"]
158
LIST_PONMSG = ["Full", "Welcome", "Battery Voltage"]
159
LIST_TIMEOUT = ["Off"] + ["%s sec" % x for x in range(15, 615, 15)]
160
LIST_PTTID = ["BOT", "EOT", "Both"]
161
LIST_ROGER = ["Off"] + LIST_PTTID
162
LIST_PRIORITY = ["Off"] + ["%s" % x for x in range(1, 129)]
163
LIST_WORKMODE = ["Frequency", "Channel"]
164
LIST_AB = ["A", "B"]
165

    
166
LIST_ALMOD = ["Site", "Tone", "Code"]
167
LIST_BANDWIDTH = ["Wide", "Narrow"]
168
LIST_DELAYPROCTIME = ["%s ms" % x for x in range(100, 4100, 100)]
169
LIST_DTMFSPEED = ["%s ms" % x for x in range(50, 2010, 10)]
170
LIST_OFFAB = ["Off"] + LIST_AB
171
LIST_RESETTIME = ["%s ms" % x for x in range(100, 16100, 100)]
172
LIST_SCODE = ["%s" % x for x in range(1, 16)]
173
LIST_RPSTE = ["Off"] + ["%s" % x for x in range(1, 11)]
174
LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"]
175
LIST_SHIFTD = ["Off", "+", "-"]
176
LIST_STEDELAY = ["Off"] + ["%s ms" % x for x in range(100, 1100, 100)]
177
#LIST_STEP = [str(x) for x in STEPS]
178
LIST_TXPOWER = ["High", "Low"]
179
LIST_DTMF_SPECIAL_DIGITS = [ "*", "#", "A", "B", "C", "D"]
180
LIST_DTMF_SPECIAL_VALUES = [ 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00]
181

    
182
CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ?+-*"
183
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0]
184
POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5),
185
                chirp_common.PowerLevel("Low", watts=1)]
186
VALID_BANDS = [(136000000, 174000000),
187
               (400000000, 520000000)]
188

    
189

    
190
#def _clean_buffer(radio):
191
#    radio.pipe.timeout = 0.005
192
#    junk = radio.pipe.read(256)
193
#    radio.pipe.timeout = STIMEOUT
194
#    if junk:
195
#        LOG.debug("Got %i bytes of junk before starting" % len(junk))
196

    
197

    
198
def _rawrecv(radio, amount):
199
    """Raw read from the radio device"""
200
    data = ""
201
    try:
202
        data = radio.pipe.read(amount)
203
    except:
204
        msg = "Generic error reading data from radio; check your cable."
205
        raise errors.RadioError(msg)
206

    
207
    if len(data) != amount:
208
        msg = "Error reading data from radio: not the amount of data we want."
209
        raise errors.RadioError(msg)
210

    
211
    return data
212

    
213

    
214
def _rawsend(radio, data):
215
    """Raw send to the radio device"""
216
    try:
217
        radio.pipe.write(data)
218
    except:
219
        raise errors.RadioError("Error sending data to radio")
220

    
221

    
222
def _make_frame(cmd, addr, length, data=""):
223
    """Pack the info in the headder format"""
224
    frame = struct.pack(">BHB", ord(cmd), addr, length)
225
    # add the data if set
226
    if len(data) != 0:
227
        frame += data
228
    # return the data
229
    return frame
230

    
231

    
232
def _recv(radio, addr, length):
233
    """Get data from the radio """
234
    # read 4 bytes of header
235
    hdr = _rawrecv(radio, 4)
236

    
237
    # read data
238
    data = _rawrecv(radio, length)
239

    
240
    # DEBUG
241
    LOG.info("Response:")
242
    LOG.debug(util.hexprint(hdr + data))
243

    
244
    c, a, l = struct.unpack(">BHB", hdr)
245
    if a != addr or l != length or c != ord("W"):
246
        LOG.error("Invalid answer for block 0x%04x:" % addr)
247
        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
248
        raise errors.RadioError("Unknown response from the radio")
249

    
250
    return data
251

    
252

    
253
def _do_ident(radio, magic):
254
    """Put the radio in PROGRAM mode"""
255
    #  set the serial discipline
256
    radio.pipe.baudrate = 9600
257
    ####radio.pipe.timeout = STIMEOUT
258

    
259
    ## flush input buffer
260
    #_clean_buffer(radio)
261

    
262
    # send request to enter program mode
263
    _rawsend(radio, magic)
264

    
265
    ack = _rawrecv(radio, 1)
266
    if ack != "\x06":
267
        if ack:
268
            LOG.debug(repr(ack))
269
        raise errors.RadioError("Radio did not respond")
270

    
271
    _rawsend(radio, "\x02")
272

    
273
    # Ok, get the response
274
    ident = _rawrecv(radio, radio._magic_response_length)
275

    
276
    # check if response is OK
277
    if not ident.startswith("P3107"):
278
        # bad response
279
        msg = "Unexpected response, got this:"
280
        msg +=  util.hexprint(ident)
281
        LOG.debug(msg)
282
        raise errors.RadioError("Unexpected response from radio.")
283

    
284
    # DEBUG
285
    LOG.info("Valid response, got this:")
286
    LOG.debug(util.hexprint(ident))
287

    
288
    _rawsend(radio, "\x06")
289
    ack = _rawrecv(radio, 1)
290
    if ack != "\x06":
291
        if ack:
292
            LOG.debug(repr(ack))
293
        raise errors.RadioError("Radio refused clone")
294

    
295
    return ident
296

    
297

    
298
def _ident_radio(radio):
299
    for magic in radio._magic:
300
        error = None
301
        try:
302
            data = _do_ident(radio, magic)
303
            return data
304
        except errors.RadioError, e:
305
            print e
306
            error = e
307
            time.sleep(2)
308
    if error:
309
        raise error
310
    raise errors.RadioError("Radio did not respond")
311

    
312

    
313
def _download(radio):
314
    """Get the memory map"""
315
    # put radio in program mode
316
    ident = _ident_radio(radio)
317

    
318
    # UI progress
319
    status = chirp_common.Status()
320
    status.cur = 0
321
    status.max = radio._mem_size / radio._recv_block_size
322
    status.msg = "Cloning from radio..."
323
    radio.status_fn(status)
324

    
325
    data = ""
326
    for addr in range(0, radio._mem_size, radio._recv_block_size):
327
        frame = _make_frame("R", addr, radio._recv_block_size)
328
        # DEBUG
329
        LOG.info("Request sent:")
330
        LOG.debug(util.hexprint(frame))
331

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

    
335
        # now we read
336
        d = _recv(radio, addr, radio._recv_block_size)
337

    
338
        time.sleep(0.05)
339

    
340
        _rawsend(radio, "\x06")
341

    
342
        ack = _rawrecv(radio, 1)
343
        if ack != "\x06":
344
            raise errors.RadioError(
345
                "Radio refused to send block 0x%04x" % addr)
346

    
347
        ####time.sleep(0.05)
348

    
349
        # aggregate the data
350
        data += d
351

    
352
        # UI Update
353
        status.cur = addr / radio._recv_block_size
354
        status.msg = "Cloning from radio..."
355
        radio.status_fn(status)
356

    
357
    data += radio.MODEL.ljust(8)
358

    
359
    return data
360

    
361

    
362
def _upload(radio):
363
    """Upload procedure"""
364
    # put radio in program mode
365
    _ident_radio(radio)
366

    
367

    
368

    
369
    addr = 0x0f80
370
    frame = _make_frame("R", addr, radio._recv_block_size)
371
    # DEBUG
372
    LOG.info("Request sent:")
373
    LOG.debug(util.hexprint(frame))
374

    
375
    # sending the read request
376
    _rawsend(radio, frame)
377

    
378
    # now we read
379
    d = _recv(radio, addr, radio._recv_block_size)
380

    
381
    time.sleep(0.05)
382

    
383
    _rawsend(radio, "\x06")
384

    
385
    ack = _rawrecv(radio, 1)
386
    if ack != "\x06":
387
        raise errors.RadioError(
388
            "Radio refused to send block 0x%04x" % addr)
389

    
390

    
391

    
392
    _ranges = radio._ranges
393

    
394
    # UI progress
395
    status = chirp_common.Status()
396
    status.cur = 0
397
    status.max = radio._mem_size / radio._send_block_size
398
    status.msg = "Cloning to radio..."
399
    radio.status_fn(status)
400

    
401
    # the fun start here
402
    for start, end in _ranges:
403
        for addr in range(start, end, radio._send_block_size):
404
            # sending the data
405
            data = radio.get_mmap()[addr:addr + radio._send_block_size]
406

    
407
            frame = _make_frame("W", addr, radio._send_block_size, data)
408

    
409
            _rawsend(radio, frame)
410
            #time.sleep(0.05)
411

    
412
            # receiving the response
413
            ack = _rawrecv(radio, 1)
414
            if ack != "\x06":
415
                msg = "Bad ack writing block 0x%04x" % addr
416
                raise errors.RadioError(msg)
417

    
418
            # UI Update
419
            status.cur = addr / radio._send_block_size
420
            status.msg = "Cloning to radio..."
421
            radio.status_fn(status)
422

    
423

    
424
def model_match(cls, data):
425
    """Match the opened/downloaded image to the correct version"""
426

    
427
    if len(data) == 0x2008:
428
        rid = data[0x2000:0x2008]
429
        print rid
430
        return rid.startswith(cls.MODEL)
431
    else:
432
        return False
433

    
434

    
435
def _split(rf, f1, f2):
436
    """Returns False if the two freqs are in the same band (no split)
437
    or True otherwise"""
438

    
439
    # determine if the two freqs are in the same band
440
    for low, high in rf.valid_bands:
441
        if f1 >= low and f1 <= high and \
442
                f2 >= low and f2 <= high:
443
            # if the two freqs are on the same Band this is not a split
444
            return False
445

    
446
    # if you get here is because the freq pairs are split
447
    return True
448

    
449

    
450
@directory.register
451
class TDXoneTDQ8A(chirp_common.CloneModeRadio,
452
                  chirp_common.ExperimentalRadio):
453
    """TDXone TD-Q8A Radio"""
454
    VENDOR = "TDXone"
455
    MODEL = "TD-Q8A"
456

    
457
    ####_fileid = [TDQ8A_fp1, ]
458

    
459
    _magic = [MSTRING_TDQ8A, MSTRING_TDQ8A,]
460
    _magic_response_length = 8
461
    _fw_ver_start = 0x1EF0
462
    _recv_block_size = 0x40
463
    _mem_size = 0x2000
464

    
465
    #_ranges = [(0x0000, 0x2000)]
466
    # same as radio
467
    #_ranges = [(0x0010, 0x0810),
468
    #           (0x0F10, 0x0F30),
469
    #           (0x1010, 0x1810),
470
    #           (0x0E20, 0x0E60),
471
    #           (0x1F10, 0x1F30)]
472
    # in increasing order
473
    _ranges = [(0x0010, 0x0810),
474
               (0x0E20, 0x0E60),
475
               (0x0F10, 0x0F30),
476
               (0x1010, 0x1810),
477
               (0x1F10, 0x1F30)]
478
    _send_block_size = 0x10
479

    
480
    #DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
481
    #DTCS_CODES = sorted(chirp_common.ALL_DTCS_CODES)
482
    #POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00),
483
    #                chirp_common.PowerLevel("High", watts=5.00)]
484
    #VALID_BANDS = [(136000000, 174000000),
485
    #               (400000000, 520000000)]
486

    
487

    
488
    @classmethod
489
    def get_prompts(cls):
490
        rp = chirp_common.RadioPrompts()
491
        rp.experimental = \
492
            ('The TDXone TD-Q8A driver is a beta version.\n'
493
             '\n'
494
             'Please save an unedited copy of your first successful\n'
495
             'download to a CHIRP Radio Images(*.img) file.'
496
             )
497
        rp.pre_download = _(dedent("""\
498
            Follow these instructions to download your info:
499

    
500
            1 - Turn off your radio
501
            2 - Connect your interface cable
502
            3 - Turn on your radio
503
            4 - Do the download of your radio data
504
            """))
505
        rp.pre_upload = _(dedent("""\
506
            Follow this instructions to upload your info:
507

    
508
            1 - Turn off your radio
509
            2 - Connect your interface cable
510
            3 - Turn on your radio
511
            4 - Do the upload of your radio data
512
            """))
513
        return rp
514

    
515

    
516
    def get_features(self):
517
        """Get the radio's features"""
518

    
519
        rf = chirp_common.RadioFeatures()
520
        rf.has_settings = True
521
        rf.has_bank = False
522
        rf.has_tuning_step = False
523
        rf.can_odd_split = True
524
        rf.has_name = True
525
        rf.has_offset = True
526
        rf.has_mode = True
527
        rf.has_dtcs = False #True
528
        rf.has_rx_dtcs = False #True
529
        rf.has_dtcs_polarity = False #True
530
        rf.has_ctone = True
531
        rf.has_cross = True
532
        rf.valid_modes = ["FM", "NFM"]
533
        #rf.valid_characters = self.VALID_CHARS
534
        rf.valid_characters = CHARSET
535
        rf.valid_name_length = 6
536
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
537
        #rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
538
        #rf.valid_cross_modes = [
539
        #    "Tone->Tone",
540
        #    "DTCS->",
541
        #    "->DTCS",
542
        #    "Tone->DTCS",
543
        #    "DTCS->Tone",
544
        #    "->Tone",
545
        #    "DTCS->DTCS"]
546
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'Cross']
547
        rf.valid_cross_modes = [
548
            "Tone->Tone",
549
            "->Tone"]
550
        rf.valid_skips = ["", "S"]
551
        #rf.valid_dtcs_codes = self.DTCS_CODES
552
        rf.memory_bounds = (1, 128)
553
        rf.valid_power_levels = POWER_LEVELS
554
        rf.valid_bands = VALID_BANDS
555

    
556
        return rf
557

    
558

    
559
    def process_mmap(self):
560
        """Process the mem map into the mem object"""
561
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
562

    
563

    
564
    def sync_in(self):
565
        """Download from radio"""
566
        try:
567
            data = _download(self)
568
        except errors.RadioError:
569
            # Pass through any real errors we raise
570
            raise
571
        except:
572
            # If anything unexpected happens, make sure we raise
573
            # a RadioError and log the problem
574
            LOG.exception('Unexpected error during download')
575
            raise errors.RadioError('Unexpected error communicating '
576
                                    'with the radio')
577
        self._mmap = memmap.MemoryMap(data)
578
        self.process_mmap()
579

    
580

    
581
    def sync_out(self):
582
        """Upload to radio"""
583
        try:
584
            _upload(self)
585
        except:
586
            # If anything unexpected happens, make sure we raise
587
            # a RadioError and log the problem
588
            LOG.exception('Unexpected error during upload')
589
            raise errors.RadioError('Unexpected error communicating '
590
                                    'with the radio')
591

    
592

    
593
    def _is_txinh(self, _mem):
594
        raw_tx = ""
595
        for i in range(0, 4):
596
            raw_tx += _mem.txfreq[i].get_raw()
597
        return raw_tx == "\xFF\xFF\xFF\xFF"
598

    
599

    
600
    def _get_mem(self, number):
601
        return self._memobj.memory[number - 1]
602

    
603
    def _get_nam(self, number):
604
        return self._memobj.names[number - 1]
605

    
606
    def get_memory(self, number):
607
        _mem = self._get_mem(number)
608
        _nam = self._get_nam(number)
609

    
610
        mem = chirp_common.Memory()
611
        mem.number = number
612

    
613
        if _mem.get_raw()[0] == "\xff":
614
            mem.empty = True
615
            return mem
616

    
617
        mem.freq = int(_mem.rxfreq) * 10
618

    
619
        if self._is_txinh(_mem):
620
            # TX freq not set
621
            mem.duplex = "off"
622
            mem.offset = 0
623
        else:
624
            # TX freq set
625
            offset = (int(_mem.txfreq) * 10) - mem.freq
626
            if offset != 0:
627
                if _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10):
628
                    mem.duplex = "split"
629
                    mem.offset = int(_mem.txfreq) * 10
630
                elif offset < 0:
631
                    mem.offset = abs(offset)
632
                    mem.duplex = "-"
633
                elif offset > 0:
634
                    mem.offset = offset
635
                    mem.duplex = "+"
636
            else:
637
                mem.offset = 0
638

    
639
        if _nam.name:
640
            for char in _nam.name:
641
                try:
642
                    mem.name += CHARSET[char]
643
                except IndexError:
644
                    break
645
            mem.name = mem.name.rstrip()
646

    
647
        #dtcs_pol = ["N", "N"]
648

    
649
        if _mem.txtone in [0, 0xFFFF]:
650
            txmode = ""
651
        elif _mem.txtone >= 0x0258:
652
            txmode = "Tone"
653
            mem.rtone = int(_mem.txtone) / 10.0
654
        else:
655
            LOG.warn("Bug: txtone is %04x" % _mem.txtone)
656

    
657
        #elif _mem.txtone <= 0x0258:
658
        #    txmode = "DTCS"
659
        #    if _mem.txtone > 0x69:
660
        #        index = _mem.txtone - 0x6A
661
        #        dtcs_pol[0] = "R"
662
        #    else:
663
        #        index = _mem.txtone - 1
664
        #    mem.dtcs = self.DTCS_CODES[index]
665
        #else:
666
        #    LOG.warn("Bug: txtone is %04x" % _mem.txtone)
667

    
668
        if _mem.rxtone in [0, 0xFFFF]:
669
            rxmode = ""
670
        elif _mem.rxtone >= 0x0258:
671
            rxmode = "Tone"
672
            mem.ctone = int(_mem.rxtone) / 10.0
673
        else:
674
            LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
675

    
676
        #elif _mem.rxtone <= 0x0258:
677
        #    rxmode = "DTCS"
678
        #    if _mem.rxtone >= 0x6A:
679
        #        index = _mem.rxtone - 0x6A
680
        #        dtcs_pol[1] = "R"
681
        #    else:
682
        #        index = _mem.rxtone - 1
683
        #    mem.rx_dtcs = self.DTCS_CODES[index]
684
        #else:
685
        #    LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
686

    
687
        if txmode == "Tone" and not rxmode:
688
            mem.tmode = "Tone"
689
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
690
            mem.tmode = "TSQL"
691
        elif rxmode or txmode:
692
            mem.tmode = "Cross"
693
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
694

    
695
        #elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
696
        #    mem.tmode = "DTCS"
697
        #elif rxmode or txmode:
698
        #    mem.tmode = "Cross"
699
        #    mem.cross_mode = "%s->%s" % (txmode, rxmode)
700

    
701
        #mem.dtcs_polarity = "".join(dtcs_pol)
702

    
703
        if not _mem.scan:
704
            mem.skip = "S"
705

    
706
        mem.power = POWER_LEVELS[1 - _mem.highpower]
707

    
708
        mem.mode = _mem.wide and "FM" or "NFM"
709

    
710
        mem.extra = RadioSettingGroup("Extra", "extra")
711

    
712
        rs = RadioSetting("dtmf", "DTMF",
713
                          RadioSettingValueList(LIST_DTMF,
714
                                                LIST_DTMF[_mem.dtmf]))
715
        mem.extra.append(rs)
716

    
717
        rs = RadioSetting("bcl", "BCL",
718
                          RadioSettingValueBoolean(_mem.bcl))
719
        mem.extra.append(rs)
720

    
721
        return mem
722

    
723

    
724
    def _set_mem(self, number):
725
        return self._memobj.memory[number - 1]
726

    
727
    def _set_nam(self, number):
728
        return self._memobj.names[number - 1]
729

    
730
    def set_memory(self, mem):
731
        _mem = self._get_mem(mem.number)
732
        _nam = self._get_nam(mem.number)
733

    
734
        if mem.empty:
735
            _mem.set_raw("\xff" * 12 + "\xbf" +"\xff" * 3)
736
            _nam.set_raw("\xff" * 16)
737
            return
738

    
739
        #_mem.set_raw("\x00" * 16)
740
        _mem.set_raw("\xff" * 12 + "\x9f" +"\xff" * 3)
741

    
742
        _mem.rxfreq = mem.freq / 10
743

    
744
        if mem.duplex == "off":
745
            for i in range(0, 4):
746
                _mem.txfreq[i].set_raw("\xFF")
747
        elif mem.duplex == "split":
748
            _mem.txfreq = mem.offset / 10
749
        elif mem.duplex == "+":
750
            _mem.txfreq = (mem.freq + mem.offset) / 10
751
        elif mem.duplex == "-":
752
            _mem.txfreq = (mem.freq - mem.offset) / 10
753
        else:
754
            _mem.txfreq = mem.freq / 10
755

    
756
        if _nam.name:
757
            for i in range(0, 6):
758
                try:
759
                    _nam.name[i] = CHARSET.index(mem.name[i])
760
                except IndexError:
761
                    _nam.name[i] = 0xFF
762

    
763
        rxmode = txmode = ""
764
        if mem.tmode == "Tone":
765
            _mem.txtone = int(mem.rtone * 10)
766
            _mem.rxtone = 0
767
        elif mem.tmode == "TSQL":
768
            _mem.txtone = int(mem.ctone * 10)
769
            _mem.rxtone = int(mem.ctone * 10)
770
            #elif mem.tmode == "DTCS":
771
            #    rxmode = txmode = "DTCS"
772
            #    _mem.txtone = self.DTCS_CODES.index(mem.dtcs) + 1
773
            #    _mem.rxtone = self.DTCS_CODES.index(mem.dtcs) + 1
774
        elif mem.tmode == "Cross":
775
            txmode, rxmode = mem.cross_mode.split("->", 1)
776
            if txmode == "Tone":
777
                _mem.txtone = int(mem.rtone * 10)
778
                #elif txmode == "DTCS":
779
                #    _mem.txtone = self.DTCS_CODES.index(mem.dtcs) + 1
780
            else:
781
                _mem.txtone = 0
782
            if rxmode == "Tone":
783
                _mem.rxtone = int(mem.ctone * 10)
784
                #elif rxmode == "DTCS":
785
                #    _mem.rxtone = self.DTCS_CODES.index(mem.rx_dtcs) + 1
786
            else:
787
                _mem.rxtone = 0
788
        else:
789
            _mem.rxtone = 0
790
            _mem.txtone = 0
791

    
792
        #if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
793
        #    _mem.txtone += 0x69
794
        #if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
795
        #    _mem.rxtone += 0x69
796

    
797
        _mem.scan = mem.skip != "S"
798
        _mem.wide = mem.mode == "FM"
799

    
800
        _mem.highpower = mem.power == POWER_LEVELS[0]
801

    
802
        for setting in mem.extra:
803
            setattr(_mem, setting.get_name(), setting.value)
804

    
805

    
806
    def get_settings(self):
807
    #    """Translate the bit in the mem_struct into settings in the UI"""
808
        _mem = self._memobj
809
        basic = RadioSettingGroup("basic", "Basic Settings")
810
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
811
        #other = RadioSettingGroup("other", "Other Settings")
812
        #work = RadioSettingGroup("work", "Work Mode Settings")
813
        #fm_preset = RadioSettingGroup("fm_preset", "FM Preset")
814
        #dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings")
815
        #dtmfd = RadioSettingGroup("dtmfd", "DTMF Decode Settings")
816
        #service = RadioSettingGroup("service", "Service Settings")
817
        #top = RadioSettings(basic, advanced, other, work, fm_preset, dtmfe,
818
        #                    dtmfd, service)
819
        top = RadioSettings(basic, advanced, )
820

    
821
        # Basic settings
822
        rs = RadioSetting("settings.beep", "Beep",
823
                           RadioSettingValueBoolean(_mem.settings.beep))
824
        basic.append(rs)
825

    
826
        if _mem.settings.squelcha > 0x09:
827
            val = 0x00
828
        else:
829
            val = _mem.settings.squelcha
830
        rs = RadioSetting("squelcha", "Squelch Level A",
831
                          RadioSettingValueInteger(0, 9, _mem.settings.squelcha))
832
        basic.append(rs)
833

    
834

    
835
        if _mem.settings.squelchb > 0x09:
836
            val = 0x00
837
        else:
838
            val = _mem.settings.squelchb
839
        rs = RadioSetting("squelchb", "Squelch Level B",
840
                          RadioSettingValueInteger(0, 9, _mem.settings.squelchb))
841
        basic.append(rs)
842

    
843

    
844
        if _mem.settings.voice > 0x02:
845
            val = 0x01
846
        else:
847
            val = _mem.settings.voice
848
        rs = RadioSetting("settings.voice", "Voice Prompt",
849
                          RadioSettingValueList(
850
                              LIST_VOICE, LIST_VOICE[val]))
851
        basic.append(rs)
852

    
853
        if _mem.settings.vox > 0x0A:
854
            val = 0x00
855
        else:
856
            val = _mem.settings.vox
857
        rs = RadioSetting("settings.vox", "VOX",
858
                          RadioSettingValueList(
859
                              LIST_OFF1TO10, LIST_OFF1TO10[val]))
860
        basic.append(rs)
861

    
862
        rs = RadioSetting("settings.autolk", "Automatic Key Lock",
863
                          RadioSettingValueBoolean(_mem.settings.autolk))
864
        basic.append(rs)
865

    
866
        if _mem.settings.screv > 0x02:
867
            val = 0x01
868
        else:
869
            val = _mem.settings.screv
870
        rs = RadioSetting("settings.screv", "Scan Resume",
871
                          RadioSettingValueList(
872
                              LIST_RESUME, LIST_RESUME[val]))
873
        basic.append(rs)
874

    
875
        if _mem.settings.toa > 0x0A:
876
            val = 0x00
877
        else:
878
            val = _mem.settings.toa
879
        rs = RadioSetting("settings.toa", "Time-out Pre-Alert",
880
                          RadioSettingValueList(
881
                              LIST_OFF1TO10, LIST_OFF1TO10[val]))
882
        basic.append(rs)
883

    
884
        if _mem.settings.timeout > 0x28:
885
            val = 0x03
886
        else:
887
            val = _mem.settings.timeout
888
        rs = RadioSetting("settings.timeout", "Timeout Timer",
889
                          RadioSettingValueList(
890
                              LIST_TIMEOUT, LIST_TIMEOUT[val]))
891
        basic.append(rs)
892

    
893
        rs = RadioSetting("settings.wtled", "Standby LED Color",
894
                          RadioSettingValueList(
895
                              LIST_COLOR, LIST_COLOR[_mem.settings.wtled]))
896
        basic.append(rs)
897

    
898
        rs = RadioSetting("settings.rxled", "RX LED Color",
899
                          RadioSettingValueList(
900
                              LIST_COLOR, LIST_COLOR[_mem.settings.rxled]))
901
        basic.append(rs)
902

    
903
        rs = RadioSetting("settings.txled", "TX LED Color",
904
                          RadioSettingValueList(
905
                              LIST_COLOR, LIST_COLOR[_mem.settings.txled]))
906
        basic.append(rs)
907

    
908
        rs = RadioSetting("settings.roger", "Roger Beep",
909
                          RadioSettingValueList(LIST_ROGER, LIST_ROGER[
910
                              _mem.settings.roger]))
911
        basic.append(rs)
912

    
913
        rs = RadioSetting("settings.mdfa", "Display Mode (A)",
914
                          RadioSettingValueList(LIST_MODE, LIST_MODE[
915
                              _mem.settings.mdfa]))
916
        basic.append(rs)
917

    
918
        rs = RadioSetting("settings.mdfb", "Display Mode (B)",
919
                          RadioSettingValueList(LIST_MODE, LIST_MODE[
920
                              _mem.settings.mdfb]))
921
        basic.append(rs)
922

    
923
        rs = RadioSetting("settings.pf1", "PF1 Key Assignment",
924
                          RadioSettingValueList(LIST_PF1, LIST_PF1[
925
                              _mem.settings.pf1]))
926
        basic.append(rs)
927

    
928
        rs = RadioSetting("settings.tdr", "Dual Watch(TDR)",
929
                          RadioSettingValueBoolean(_mem.settings.tdr))
930
        basic.append(rs)
931

    
932
        rs = RadioSetting("settings.ani", "ANI",
933
                          RadioSettingValueBoolean(_mem.settings.ani))
934
        basic.append(rs)
935

    
936
        if _mem.settings.pttdly > 0x0A:
937
            val = 0x00
938
        else:
939
            val = _mem.settings.pttdly
940
        rs = RadioSetting("settings.pttdly", "PTT ID Delay",
941
                          RadioSettingValueList(
942
                              LIST_OFF1TO30, LIST_OFF1TO30[val]))
943
        basic.append(rs)
944

    
945
        rs = RadioSetting("settings.pttid", "When to send PTT ID",
946
                          RadioSettingValueList(LIST_PTTID,
947
                                                LIST_PTTID[_mem.settings.pttid]))
948
        basic.append(rs)
949

    
950
        rs = RadioSetting("settings.dtmfst", "DTMF Sidetone",
951
                          RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
952
                              _mem.settings.dtmfst]))
953
        basic.append(rs)
954

    
955
        rs = RadioSetting("settings.ponmsg", "Power-On Message",
956
                          RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
957
                              _mem.settings.ponmsg]))
958
        basic.append(rs)
959

    
960
        rs = RadioSetting("settings.dw", "DW",
961
                          RadioSettingValueBoolean(_mem.settings.dw))
962
        basic.append(rs)
963

    
964
        # Advanced settings
965
        rs = RadioSetting("settings.prioritych", "Priority Channel",
966
                          RadioSettingValueList(LIST_PRIORITY, LIST_PRIORITY[
967
                              _mem.settings.prioritych]))
968
        advanced.append(rs)
969

    
970
        rs = RadioSetting("settings.vfomr", "Work Mode",
971
                          RadioSettingValueList(LIST_WORKMODE, LIST_WORKMODE[
972
                              _mem.settings.vfomr]))
973
        advanced.append(rs)
974

    
975
        dtmfchars = "0123456789"
976
        _codeobj = _mem.settings.code
977
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
978
        val = RadioSettingValueString(0, 3, _code, False)
979
        val.set_charset(dtmfchars)
980
        rs = RadioSetting("settings.code", "PTT-ID Code", val)
981

    
982
        def apply_code(setting, obj):
983
            code = []
984
            for j in range(0, 3):
985
                try:
986
                    code.append(dtmfchars.index(str(setting.value)[j]))
987
                except IndexError:
988
                    code.append(0xFF)
989
            obj.code = code
990
        rs.set_apply_callback(apply_code, _mem.settings)
991
        advanced.append(rs)
992

    
993
        _codeobj = _mem.settings.password
994
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
995
        val = RadioSettingValueString(0, 6, _code, False)
996
        val.set_charset(dtmfchars)
997
        rs = RadioSetting("settings.password", "Control Password", val)
998

    
999
        def apply_code(setting, obj):
1000
            code = []
1001
            for j in range(0, 6):
1002
                try:
1003
                    code.append(dtmfchars.index(str(setting.value)[j]))
1004
                except IndexError:
1005
                    code.append(0xFF)
1006
            obj.password = code
1007
        rs.set_apply_callback(apply_code, _mem.settings)
1008
        advanced.append(rs)
1009

    
1010
        if _mem.settings.tdrab > 0x01:
1011
            val = 0x00
1012
        else:
1013
            val = _mem.settings.tdrab
1014
        rs = RadioSetting("settings.tdrab", "Dual Watch TX Priority",
1015
                          RadioSettingValueList(
1016
                              LIST_AB, LIST_AB[val]))
1017
        advanced.append(rs)
1018

    
1019
        rs = RadioSetting("settings.keylk", "Key Lock",
1020
                          RadioSettingValueBoolean(_mem.settings.keylk))
1021
        advanced.append(rs)
1022

    
1023
        rs = RadioSetting("settings.control", "Control Code",
1024
                          RadioSettingValueBoolean(_mem.settings.control))
1025
        advanced.append(rs)
1026

    
1027
        return top
1028

    
1029

    
1030

    
1031
        """
1032
        # Other settings
1033
        def _filter(name):
1034
            filtered = ""
1035
            for char in str(name):
1036
                if char in chirp_common.CHARSET_ASCII:
1037
                    filtered += char
1038
                else:
1039
                    filtered += " "
1040
            return filtered
1041

    
1042
        _msg = _mem.sixpoweron_msg
1043
        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
1044
        val.set_mutable(False)
1045
        rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", val)
1046
        other.append(rs)
1047
        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
1048
        val.set_mutable(False)
1049
        rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", val)
1050
        other.append(rs)
1051

    
1052
        _msg = _mem.poweron_msg
1053
        rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
1054
                          RadioSettingValueString(
1055
                              0, 7, _filter(_msg.line1)))
1056
        other.append(rs)
1057
        rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
1058
                          RadioSettingValueString(
1059
                              0, 7, _filter(_msg.line2)))
1060
        other.append(rs)
1061

    
1062
        # DTMF encode settings
1063

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

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

    
1082
    """
1083

    
1084

    
1085
    def set_settings(self, settings):
1086
        _settings = self._memobj.settings
1087
        _mem = self._memobj
1088
        for element in settings:
1089
            if not isinstance(element, RadioSetting):
1090
                if element.get_name() == "fm_preset":
1091
                    self._set_fm_preset(element)
1092
                else:
1093
                    self.set_settings(element)
1094
                    continue
1095
            else:
1096
                try:
1097
                    name = element.get_name()
1098
                    if "." in name:
1099
                        bits = name.split(".")
1100
                        obj = self._memobj
1101
                        for bit in bits[:-1]:
1102
                            if "/" in bit:
1103
                                bit, index = bit.split("/", 1)
1104
                                index = int(index)
1105
                                obj = getattr(obj, bit)[index]
1106
                            else:
1107
                                obj = getattr(obj, bit)
1108
                        setting = bits[-1]
1109
                    else:
1110
                        obj = _settings
1111
                        setting = element.get_name()
1112

    
1113
                    if element.has_apply_callback():
1114
                        LOG.debug("Using apply callback")
1115
                        element.run_apply_callback()
1116
                    elif element.value.get_mutable():
1117
                        LOG.debug("Setting %s = %s" % (setting, element.value))
1118
                        setattr(obj, setting, element.value)
1119
                except Exception, e:
1120
                    LOG.debug(element.get_name())
1121
                    raise
1122

    
1123
    def _set_fm_preset(self, settings):
1124
        for element in settings:
1125
            try:
1126
                val = element.value
1127
                if self._memobj.fm_presets <= 108.0 * 10 - 650:
1128
                    value = int(val.get_value() * 10 - 650)
1129
                else:
1130
                    value = int(val.get_value() * 10)
1131
                LOG.debug("Setting fm_presets = %s" % (value))
1132
                self._memobj.fm_presets = value
1133
            except Exception, e:
1134
                LOG.debug(element.get_name())
1135
                raise
1136

    
1137

    
1138
    @classmethod
1139
    def match_model(cls, filedata, filename):
1140
        match_size = False
1141
        match_model = False
1142

    
1143
        # testing the file data size
1144
        if len(filedata) == 0x2008:
1145
            match_size = True
1146

    
1147
        # testing the model fingerprint
1148
        match_model = model_match(cls, filedata)
1149

    
1150
        #if match_size and match_model:
1151
        if match_size and match_model:
1152
            return True
1153
        else:
1154
            return False
    (1-1/1)