leixen_107.py

Brian Dickman, 10/12/2016 10:10 pm

Download (36.4 kB)

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

    
16
import struct
17
import os
18
import logging
19

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

    
28
LOG = logging.getLogger(__name__)
29

    
30
MEM_FORMAT = """
31
#seekto 0x0184;
32
struct {
33
  u8 unknown:4,
34
     sql:4;              // squelch level
35
  u8 unknown0x0185;
36
  u8 obeep:1,            // open beep
37
     dw_off:1,           // dual watch (inverted)
38
     kbeep:1,            // key beep
39
     rbeep:1,            // roger beep
40
     unknown:2,
41
     ctdcsb:1,           // ct/dcs busy lock
42
     unknown:1;
43
  u8 alarm:1,            // alarm key
44
     unknown1:1,
45
     aliasen_off:1,      // alias enable (inverted)
46
     save:1,             // battery save
47
     unknown2:2,
48
     mrcha:1,            // mr/cha
49
     vfomr:1;            // vfo/mr
50
  u8 keylock_off:1,      // key lock (inverted)
51
     txstop_off:1,       // tx stop (inverted)
52
     scanm:1,            // scan key/mode
53
     vir:1,              // vox inhibit on receive
54
     keylockm:2,         // key lock mode
55
     lamp:2;             // backlight
56
  u8 opendis:2,          // open display
57
     fmen_off:1,         // fm enable (inverted)
58
     unknown1:1,
59
     fmscan_off:1,       // fm scan (inverted)
60
     fmdw:1,             // fm dual watch
61
     unknown2:2;
62
  u8 step:4,             // step
63
     vol:4;              // volume
64
  u8 apo:4,              // auto power off
65
     tot:4;              // time out timer
66
  u8 unknown0x018C;
67
  u8 voxdt:4,            // vox delay time
68
     voxgain:4;          // vox gain
69
  u8 unknown0x018E;
70
  u8 unknown0x018F;
71
  u8 unknown:3,
72
     lptime:5;           // long press time
73
  u8 keyp2long:4,        // p2 key long press
74
     keyp2short:4;       // p2 key short press
75
  u8 keyp1long:4,        // p1 key long press
76
     keyp1short:4;       // p1 key short press
77
  u8 keyp3long:4,        // p3 key long press
78
     keyp3short:4;       // p3 key short press
79
  u8 unknown0x0194;
80
  u8 menuen:1,           // menu enable
81
     absel:1,            // a/b select
82
     unknown:2
83
     keymshort:4;        // m key short press
84
  u8 unknown:4,
85
     dtmfst:1,           // dtmf sidetone
86
     ackdecode:1,        // ack decode
87
     monitor:2;          // monitor
88
  u8 unknown1:3,
89
     reset:1,            // reset enable
90
     unknown2:1,
91
     keypadmic_off:1,    // keypad mic (inverted)
92
     unknown3:2;
93
  u8 unknown0x0198;
94
  u8 unknown1:3,
95
     dtmftime:5;         // dtmf digit time
96
  u8 unknown1:3,
97
     dtmfspace:5;        // dtmf digit space time
98
  u8 unknown1:2,
99
     dtmfdelay:6;        // dtmf first digit delay
100
  u8 unknown1:1,
101
     dtmfpretime:7;      // dtmf pretime
102
  u8 unknown1:2,
103
     dtmfdelay2:6;       // dtmf * and # digit delay
104
  u8 unknown1:3,
105
     smfont_off:1,       // small font (inverted)
106
     unknown:4;
107
} settings;
108

    
109
#seekto 0x01cd;
110
struct {
111
  u8 rssi136;            // squelch base level (vhf)
112
  u8 unknown0x01ce;
113
  u8 rssi400;            // squelch base level (uhf)
114
} service;
115

    
116
#seekto 0x0900;
117
struct {
118
  char user1[7];         // user message 1
119
  char unknown0x0907;
120
  char unknown0x0908[8];
121
  char unknown0x0910[8];
122
  char system[7];        // system message
123
  char unknown0x091F;
124
  char user2[7];         // user message 2
125
  char unknown0x0927;
126
} messages;
127
"""
128

    
129
MEM_CHANNEL = """
130
struct channel {
131
  bbcd rx_freq[4];
132
  bbcd tx_freq[4];
133
  u8 rx_tone;
134
  u8 rx_tmode;
135
  u8 tx_tone;
136
  u8 tx_tmode;
137
  u8 unknown5;
138
  u8 pttidoff:1,
139
     dtmfoff:1,
140
     %(unknownormode)s,
141
     tailcut:1,
142
     aliasop:1,
143
     talkaroundoff:1,
144
     voxoff:1,
145
     skip:1;
146
  u8 %(modeorpower)s,
147
     reverseoff:1,
148
     blckoff:1,
149
     unknown7:1,
150
     apro:3;
151
  u8 unknown8;
152
};
153

    
154
struct name {
155
    char name[7];
156
    u8 pad;
157
};
158

    
159
#seekto 0x0d00;
160
struct channel default[3];
161
struct channel memory[199];
162

    
163
#seekto 0x19b0;
164
struct name defaultname[3];
165
struct name name[199];
166
"""
167

    
168
### channel formatter for 898S/E v1.07
169
MEM_CHANNEL_107 = """
170
struct channel {
171
  bbcd rx_freq[4];
172
  bbcd tx_freq[4];
173
  u8 rx_tone;
174
  u8 rx_tmode;
175
  u8 tx_tone;
176
  u8 tx_tmode;
177
  u8 unknown5;
178
  u8 pttidoff:1,
179
     dtmfoff:1,
180
     mode:1,
181
     tailcut:1,
182
     aliasop:1,
183
     talkaroundoff:1,
184
     voxoff:1,
185
     skip:1;
186
  u8 power:2,
187
     reverseoff:1,
188
     blckoff:1,
189
     unknown7:1,
190
     apro:3;
191
  u8 unknown8;
192
#seek 16;
193
};
194

    
195
struct name {
196
    char name[7];
197
    u8 pad;
198
#seek 8;
199
};
200

    
201
#seekto 0x%(chan_offset)x;
202
struct channel default[3];
203
struct channel memory[99];
204

    
205
#seekto 0x%(name_offset)x;
206
struct name defaultname[3];
207
struct name name[99];
208
"""
209

    
210
APO_LIST = ["OFF", "10M", "20M", "30M", "40M", "50M", "60M", "90M",
211
            "2H", "4H", "6H", "8H", "10H", "12H", "14H", "16H"]
212
SQL_LIST = ["%s" % x for x in range(0, 10)]
213
SCANM_LIST = ["CO", "TO"]
214
TOT_LIST = ["OFF"] + ["%s seconds" % x for x in range(10, 130, 10)]
215
STEP_LIST = ["2.5 KHz", "5 KHz", "6.25 KHz", "10 KHz", "12.5 KHz", "25 KHz"]
216
MONITOR_LIST = ["CTC/DCS", "DTMF", "CTC/DCS and DTMF", "CTC/DCS or DTMF"]
217
VFOMR_LIST = ["MR", "VFO"]
218
MRCHA_LIST = ["MR CHA", "Freq. MR"]
219
VOL_LIST = ["OFF"] + ["%s" % x for x in range(1, 16)]
220
OPENDIS_LIST = ["All", "Lease Time", "User-defined", "Leixen"]
221
LAMP_LIST = ["OFF", "KEY", "CONT"]
222
KEYLOCKM_LIST = ["K+S", "PTT", "KEY", "ALL"]
223
ABSEL_LIST = ["B Channel",  "A Channel"]
224
VOXGAIN_LIST = ["%s" % x for x in range(1, 9)]
225
VOXDT_LIST = ["%s seconds" % x for x in range(1, 5)]
226
DTMFTIME_LIST = ["%i milliseconds" % x for x in range(50, 210, 10)]
227
DTMFDELAY_LIST = ["%i milliseconds" % x for x in range(0, 550, 50)]
228
DTMFPRETIME_LIST = ["%i milliseconds" % x for x in range(100, 1100, 100)]
229
DTMFDELAY2_LIST = ["%i milliseconds" % x for x in range(0, 450, 50)]
230

    
231
LPTIME_LIST = ["%i milliseconds" % x for x in range(500, 2600, 100)]
232
PFKEYLONG_LIST = ["OFF",
233
                  "FM",
234
                  "Monitor Momentary",
235
                  "Monitor Lock",
236
                  "SQ Off Momentary",
237
                  "Mute",
238
                  "SCAN",
239
                  "TX Power",
240
                  "EMG",
241
                  "VFO/MR",
242
                  "DTMF",
243
                  "CALL",
244
                  "Transmit 1750Hz",
245
                  "A/B",
246
                  "Talk Around",
247
                  "Reverse"
248
                  ]
249

    
250
PFKEYSHORT_LIST = ["OFF",
251
                   "FM",
252
                   "BandChange",
253
                   "Time",
254
                   "Monitor Lock",
255
                   "Mute",
256
                   "SCAN",
257
                   "TX Power",
258
                   "EMG",
259
                   "VFO/MR",
260
                   "DTMF",
261
                   "CALL",
262
                   "Transmit 1750Hz",
263
                   "A/B",
264
                   "Talk Around",
265
                   "Reverse"
266
                   ]
267

    
268
MODES = ["NFM", "FM"]
269
WTFTONES = map(float, xrange(56, 64))
270
TONES = WTFTONES + chirp_common.TONES
271
DTCS_CODES = [17, 50, 645] + chirp_common.DTCS_CODES
272
DTCS_CODES.sort()
273
TMODES = ["", "Tone", "DTCS", "DTCS"]
274

    
275

    
276
def _image_ident_from_data(data):
277
    return data[0x168:0x178]
278

    
279

    
280
def _image_ident_from_image(radio):
281
    return _image_ident_from_data(radio.get_mmap())
282

    
283

    
284
def checksum(frame):
285
    x = 0
286
    for b in frame:
287
        x ^= ord(b)
288
    return chr(x)
289

    
290

    
291
def make_frame(cmd, addr, data=""):
292
    payload = struct.pack(">H", addr) + data
293
    header = struct.pack(">BB", ord(cmd), len(payload))
294
    frame = header + payload
295
    return frame + checksum(frame)
296

    
297

    
298
def send(radio, frame):
299
    # LOG.debug("%04i P>R: %s" %
300
    #           (len(frame),
301
    #            util.hexprint(frame).replace("\n", "\n          ")))
302
    try:
303
        radio.pipe.write(frame)
304
    except Exception, e:
305
        raise errors.RadioError("Failed to communicate with radio: %s" % e)
306

    
307

    
308
def recv(radio, readdata=True):
309
    hdr = radio.pipe.read(4)
310
    # LOG.debug("%04i P<R: %s" %
311
    #           (len(hdr), util.hexprint(hdr).replace("\n", "\n          ")))
312
    if hdr == "\x09\x00\x09":
313
        raise errors.RadioError("Radio rejected command.")
314
    cmd, length, addr = struct.unpack(">BBH", hdr)
315
    length -= 2
316
    if readdata:
317
        data = radio.pipe.read(length)
318
        # LOG.debug("     P<R: %s" %
319
        #           util.hexprint(hdr + data).replace("\n", "\n          "))
320
        if len(data) != length:
321
            raise errors.RadioError("Radio sent %i bytes (expected %i)" % (
322
                    len(data), length))
323
        chk = radio.pipe.read(1)
324
    else:
325
        data = ""
326
    return addr, data
327

    
328

    
329
def do_ident(radio):
330
    send(radio, "\x02\x06LEIXEN\x17")
331
    ident = radio.pipe.read(9)
332
    LOG.debug("     P<R: %s" %
333
              util.hexprint(ident).replace("\n", "\n          "))
334
    if ident != "\x06\x06leixen\x13":
335
        raise errors.RadioError("Radio refused program mode")
336
    radio.pipe.write("\x06\x00\x06")
337
    ack = radio.pipe.read(3)
338
    if ack != "\x06\x00\x06":
339
        raise errors.RadioError("Radio did not ack.")
340

    
341

    
342
def do_download(radio):
343
    do_ident(radio)
344

    
345
    data = ""
346
    data += "\xFF" * (0 - len(data))
347
    for addr in range(0, radio._memsize, 0x10):
348
        send(radio, make_frame("R", addr, chr(0x10)))
349
        _addr, _data = recv(radio)
350
        if _addr != addr:
351
            raise errors.RadioError("Radio sent unexpected address")
352
        data += _data
353

    
354
        status = chirp_common.Status()
355
        status.cur = addr
356
        status.max = radio._memsize
357
        status.msg = "Cloning from radio"
358
        radio.status_fn(status)
359

    
360
    finish(radio)
361

    
362
    return memmap.MemoryMap(data)
363

    
364

    
365
def do_upload(radio):
366
    _ranges = [(0x0d00, 0x2000)]
367

    
368
    image_ident = _image_ident_from_image(radio)
369
    if image_ident.startswith(radio._file_ident) and \
370
       radio._model_ident in image_ident:
371
        _ranges = radio._ranges
372

    
373
    do_ident(radio)
374

    
375
    for start, end in _ranges:
376
        for addr in range(start, end, 0x10):
377
            frame = make_frame("W", addr, radio._mmap[addr:addr + 0x10])
378
            send(radio, frame)
379
            # LOG.debug("     P<R: %s" %
380
            #           util.hexprint(frame).replace("\n", "\n          "))
381
            radio.pipe.write("\x06\x00\x06")
382
            ack = radio.pipe.read(3)
383
            if ack != "\x06\x00\x06":
384
                raise errors.RadioError("Radio refused block at %04x" % addr)
385

    
386
            status = chirp_common.Status()
387
            status.cur = addr
388
            status.max = radio._memsize
389
            status.msg = "Cloning to radio"
390
            radio.status_fn(status)
391

    
392
    finish(radio)
393

    
394

    
395
def finish(radio):
396
    send(radio, "\x64\x01\x6F\x0A")
397
    ack = radio.pipe.read(8)
398

    
399

    
400
# Declaring Aliases
401
class LT898UV(chirp_common.Alias):
402
    VENDOR = "LUITON"
403
    MODEL = "LT-898UV"
404

    
405

    
406
@directory.register
407
class LeixenVV898Radio(chirp_common.CloneModeRadio):
408
    """Leixen VV-898"""
409
    VENDOR = "Leixen"
410
    MODEL = "VV-898"
411
    ALIASES = [LT898UV, ]
412
    BAUD_RATE = 9600
413

    
414
    _file_ident = "Leixen"
415
    _model_ident = 'LX-\x89\x85\x63'
416

    
417
    _memsize = 0x2000
418
    _ranges = [
419
        (0x0000, 0x013f),
420
        (0x0148, 0x0167),
421
        (0x0184, 0x018f),
422
        (0x0190, 0x01cf),
423
        (0x0900, 0x090f),
424
        (0x0920, 0x0927),
425
        (0x0d00, 0x2000),
426
    ]
427

    
428
    _mem_formatter = {'unknownormode': 'unknown6:1',
429
                      'modeorpower': 'mode:1, power:1'}
430
    _power_levels = [chirp_common.PowerLevel("Low", watts=4),
431
                     chirp_common.PowerLevel("High", watts=10)]
432

    
433
    def get_features(self):
434
        rf = chirp_common.RadioFeatures()
435
        rf.has_settings = True
436
        rf.has_cross = True
437
        rf.has_bank = False
438
        rf.has_tuning_step = False
439
        rf.can_odd_split = True
440
        rf.has_rx_dtcs = True
441
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
442
        rf.valid_modes = MODES
443
        rf.valid_cross_modes = [
444
            "Tone->Tone",
445
            "DTCS->",
446
            "->DTCS",
447
            "Tone->DTCS",
448
            "DTCS->Tone",
449
            "->Tone",
450
            "DTCS->DTCS"]
451
        rf.valid_characters = chirp_common.CHARSET_ASCII
452
        rf.valid_name_length = 7
453
        rf.valid_power_levels = self._power_levels
454
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
455
        rf.valid_skips = ["", "S"]
456
        rf.valid_bands = [(136000000, 174000000),
457
                          (400000000, 470000000)]
458
        rf.memory_bounds = (1, 199)
459
        return rf
460

    
461
    def sync_in(self):
462
        try:
463
            self._mmap = do_download(self)
464
        except Exception, e:
465
            finish(self)
466
            raise errors.RadioError("Failed to download from radio: %s" % e)
467
        self.process_mmap()
468

    
469
    def process_mmap(self):
470
        self._memobj = bitwise.parse(MEM_FORMAT + MEM_CHANNEL % self._mem_formatter, self._mmap)
471

    
472
    def sync_out(self):
473
        try:
474
            do_upload(self)
475
        except errors.RadioError:
476
            finish(self)
477
            raise
478
        except Exception, e:
479
            raise errors.RadioError("Failed to upload to radio: %s" % e)
480

    
481
    def get_raw_memory(self, number):
482
        return repr(self._memobj.name[number - 1]) + \
483
               repr(self._memobj.memory[number - 1])
484

    
485
    def _get_tone(self, mem, _mem):
486
        rx_tone = tx_tone = None
487

    
488
        tx_tmode = TMODES[_mem.tx_tmode]
489
        rx_tmode = TMODES[_mem.rx_tmode]
490

    
491
        if tx_tmode == "Tone":
492
            tx_tone = TONES[_mem.tx_tone - 1]
493
        elif tx_tmode == "DTCS":
494
            tx_tone = DTCS_CODES[_mem.tx_tone - 1]
495

    
496
        if rx_tmode == "Tone":
497
            rx_tone = TONES[_mem.rx_tone - 1]
498
        elif rx_tmode == "DTCS":
499
            rx_tone = DTCS_CODES[_mem.rx_tone - 1]
500

    
501
        tx_pol = _mem.tx_tmode == 0x03 and "R" or "N"
502
        rx_pol = _mem.rx_tmode == 0x03 and "R" or "N"
503

    
504
        chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
505
                                            (rx_tmode, rx_tone, rx_pol))
506

    
507
    def _is_txinh(self, _mem):
508
        raw_tx = ""
509
        for i in range(0, 4):
510
            raw_tx += _mem.tx_freq[i].get_raw()
511
        return raw_tx == "\xFF\xFF\xFF\xFF"
512

    
513
    def get_memory(self, number):
514
        _mem = self._memobj.memory[number - 1]
515
        _name = self._memobj.name[number - 1]
516

    
517
        mem = chirp_common.Memory()
518
        mem.number = number
519

    
520
        if _mem.get_raw()[:4] == "\xFF\xFF\xFF\xFF":
521
            mem.empty = True
522
            return mem
523

    
524
        mem.freq = int(_mem.rx_freq) * 10
525

    
526
        if self._is_txinh(_mem):
527
            mem.duplex = "off"
528
            mem.offset = 0
529
        elif int(_mem.rx_freq) == int(_mem.tx_freq):
530
            mem.duplex = ""
531
            mem.offset = 0
532
        elif abs(int(_mem.rx_freq) * 10 - int(_mem.tx_freq) * 10) > 70000000:
533
            mem.duplex = "split"
534
            mem.offset = int(_mem.tx_freq) * 10
535
        else:
536
            mem.duplex = int(_mem.rx_freq) > int(_mem.tx_freq) and "-" or "+"
537
            mem.offset = abs(int(_mem.rx_freq) - int(_mem.tx_freq)) * 10
538

    
539
        mem.name = str(_name.name).rstrip()
540

    
541
        self._get_tone(mem, _mem)
542
        mem.mode = MODES[_mem.mode]
543
        powerindex = _mem.power if _mem.power < len(self._power_levels) else -1
544
        mem.power = self._power_levels[powerindex]
545
        mem.skip = _mem.skip and "S" or ""
546

    
547
        mem.extra = RadioSettingGroup("Extra", "extra")
548

    
549
        opts = ["On", "Off"]
550
        rs = RadioSetting("blckoff", "Busy Channel Lockout",
551
                          RadioSettingValueList(
552
                              opts, opts[_mem.blckoff]))
553
        mem.extra.append(rs)
554
        opts = ["Off", "On"]
555
        rs = RadioSetting("tailcut", "Squelch Tail Elimination",
556
                          RadioSettingValueList(
557
                              opts, opts[_mem.tailcut]))
558
        mem.extra.append(rs)
559
        apro = _mem.apro if _mem.apro < 0x5 else 0
560
        opts = ["Off", "Compander", "Scrambler", "TX Scrambler",
561
                "RX Scrambler"]
562
        rs = RadioSetting("apro", "Audio Processing",
563
                          RadioSettingValueList(
564
                              opts, opts[apro]))
565
        mem.extra.append(rs)
566
        opts = ["On", "Off"]
567
        rs = RadioSetting("voxoff", "VOX",
568
                          RadioSettingValueList(
569
                              opts, opts[_mem.voxoff]))
570
        mem.extra.append(rs)
571
        opts = ["On", "Off"]
572
        rs = RadioSetting("pttidoff", "PTT ID",
573
                          RadioSettingValueList(
574
                              opts, opts[_mem.pttidoff]))
575
        mem.extra.append(rs)
576
        opts = ["On", "Off"]
577
        rs = RadioSetting("dtmfoff", "DTMF",
578
                          RadioSettingValueList(
579
                              opts, opts[_mem.dtmfoff]))
580
        mem.extra.append(rs)
581
        opts = ["Name", "Frequency"]
582
        aliasop = RadioSetting("aliasop", "Display",
583
                               RadioSettingValueList(
584
                                   opts, opts[_mem.aliasop]))
585
        mem.extra.append(aliasop)
586
        opts = ["On", "Off"]
587
        rs = RadioSetting("reverseoff", "Reverse Frequency",
588
                          RadioSettingValueList(
589
                              opts, opts[_mem.reverseoff]))
590
        mem.extra.append(rs)
591
        opts = ["On", "Off"]
592
        rs = RadioSetting("talkaroundoff", "Talk Around",
593
                          RadioSettingValueList(
594
                              opts, opts[_mem.talkaroundoff]))
595
        mem.extra.append(rs)
596

    
597
        return mem
598

    
599
    def _set_tone(self, mem, _mem):
600
        ((txmode, txtone, txpol),
601
         (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
602

    
603
        _mem.tx_tmode = TMODES.index(txmode)
604
        _mem.rx_tmode = TMODES.index(rxmode)
605
        if txmode == "Tone":
606
            _mem.tx_tone = TONES.index(txtone) + 1
607
        elif txmode == "DTCS":
608
            _mem.tx_tmode = txpol == "R" and 0x03 or 0x02
609
            _mem.tx_tone = DTCS_CODES.index(txtone) + 1
610
        if rxmode == "Tone":
611
            _mem.rx_tone = TONES.index(rxtone) + 1
612
        elif rxmode == "DTCS":
613
            _mem.rx_tmode = rxpol == "R" and 0x03 or 0x02
614
            _mem.rx_tone = DTCS_CODES.index(rxtone) + 1
615

    
616
    def set_memory(self, mem):
617
        _mem = self._memobj.memory[mem.number - 1]
618
        _name = self._memobj.name[mem.number - 1]
619

    
620
        if mem.empty:
621
            _mem.set_raw("\xFF" * 16)
622
            return
623
        elif _mem.get_raw() == ("\xFF" * 16):
624
            _mem.set_raw("\xFF" * 8 + "\xFF\x00\xFF\x00\xFF\xFE\xF0\xFC")
625

    
626
        _mem.rx_freq = mem.freq / 10
627

    
628
        if mem.duplex == "off":
629
            for i in range(0, 4):
630
                _mem.tx_freq[i].set_raw("\xFF")
631
        elif mem.duplex == "split":
632
            _mem.tx_freq = mem.offset / 10
633
        elif mem.duplex == "+":
634
            _mem.tx_freq = (mem.freq + mem.offset) / 10
635
        elif mem.duplex == "-":
636
            _mem.tx_freq = (mem.freq - mem.offset) / 10
637
        else:
638
            _mem.tx_freq = mem.freq / 10
639

    
640
        self._set_tone(mem, _mem)
641

    
642
        _mem.power = mem.power and self._power_levels.index(mem.power) or 0
643
        _mem.mode = MODES.index(mem.mode)
644
        _mem.skip = mem.skip == "S"
645
        _name.name = mem.name.ljust(7)
646

    
647
        # autoset display to name if filled, else show frequency
648
        if mem.extra:
649
            # mem.extra only seems to be populated when called from edit panel
650
            aliasop = mem.extra["aliasop"]
651
        else:
652
            aliasop = None
653
        if mem.name:
654
            _mem.aliasop = False
655
            if aliasop and not aliasop.changed():
656
                aliasop.value = "Name"
657
        else:
658
            _mem.aliasop = True
659
            if aliasop and not aliasop.changed():
660
                aliasop.value = "Frequency"
661

    
662
        for setting in mem.extra:
663
            setattr(_mem, setting.get_name(), setting.value)
664

    
665
    def _get_settings(self):
666
        _settings = self._memobj.settings
667
        _service = self._memobj.service
668
        _msg = self._memobj.messages
669
        cfg_grp = RadioSettingGroup("cfg_grp", "Basic Settings")
670
        adv_grp = RadioSettingGroup("adv_grp", "Advanced Settings")
671
        key_grp = RadioSettingGroup("key_grp", "Key Assignment")
672
        group = RadioSettings(cfg_grp, adv_grp, key_grp)
673

    
674
        #
675
        # Basic Settings
676
        #
677
        rs = RadioSetting("apo", "Auto Power Off",
678
                          RadioSettingValueList(
679
                              APO_LIST, APO_LIST[_settings.apo]))
680
        cfg_grp.append(rs)
681
        rs = RadioSetting("sql", "Squelch Level",
682
                          RadioSettingValueList(
683
                              SQL_LIST, SQL_LIST[_settings.sql]))
684
        cfg_grp.append(rs)
685
        rs = RadioSetting("scanm", "Scan Mode",
686
                          RadioSettingValueList(
687
                              SCANM_LIST, SCANM_LIST[_settings.scanm]))
688
        cfg_grp.append(rs)
689
        rs = RadioSetting("tot", "Time Out Timer",
690
                          RadioSettingValueList(
691
                              TOT_LIST, TOT_LIST[_settings.tot]))
692
        cfg_grp.append(rs)
693
        rs = RadioSetting("step", "Step",
694
                          RadioSettingValueList(
695
                              STEP_LIST, STEP_LIST[_settings.step]))
696
        cfg_grp.append(rs)
697
        rs = RadioSetting("monitor", "Monitor",
698
                          RadioSettingValueList(
699
                              MONITOR_LIST, MONITOR_LIST[_settings.monitor]))
700
        cfg_grp.append(rs)
701
        rs = RadioSetting("vfomr", "VFO/MR",
702
                          RadioSettingValueList(
703
                              VFOMR_LIST, VFOMR_LIST[_settings.vfomr]))
704
        cfg_grp.append(rs)
705
        rs = RadioSetting("mrcha", "MR/CHA",
706
                          RadioSettingValueList(
707
                              MRCHA_LIST, MRCHA_LIST[_settings.mrcha]))
708
        cfg_grp.append(rs)
709
        rs = RadioSetting("vol", "Volume",
710
                          RadioSettingValueList(
711
                              VOL_LIST, VOL_LIST[_settings.vol]))
712
        cfg_grp.append(rs)
713
        rs = RadioSetting("opendis", "Open Display",
714
                          RadioSettingValueList(
715
                              OPENDIS_LIST, OPENDIS_LIST[_settings.opendis]))
716
        cfg_grp.append(rs)
717

    
718
        def _filter(name):
719
            filtered = ""
720
            for char in str(name):
721
                if char in chirp_common.CHARSET_ASCII:
722
                    filtered += char
723
                else:
724
                    filtered += " "
725
            LOG.debug("Filtered: %s" % filtered)
726
            return filtered
727

    
728
        rs = RadioSetting("messages.user1", "User-defined Message 1",
729
                          RadioSettingValueString(0, 7, _filter(_msg.user1)))
730
        cfg_grp.append(rs)
731
        rs = RadioSetting("messages.user2", "User-defined Message 2",
732
                          RadioSettingValueString(0, 7, _filter(_msg.user2)))
733
        cfg_grp.append(rs)
734

    
735
        val = RadioSettingValueString(0, 7, _filter(_msg.system))
736
        val.set_mutable(False)
737
        rs = RadioSetting("messages.system", "System Message", val)
738
        cfg_grp.append(rs)
739

    
740
        rs = RadioSetting("lamp", "Backlight",
741
                          RadioSettingValueList(
742
                              LAMP_LIST, LAMP_LIST[_settings.lamp]))
743
        cfg_grp.append(rs)
744
        rs = RadioSetting("keylockm", "Key Lock Mode",
745
                          RadioSettingValueList(
746
                              KEYLOCKM_LIST,
747
                              KEYLOCKM_LIST[_settings.keylockm]))
748
        cfg_grp.append(rs)
749
        rs = RadioSetting("absel", "A/B Select",
750
                          RadioSettingValueList(ABSEL_LIST,
751
                                                ABSEL_LIST[_settings.absel]))
752
        cfg_grp.append(rs)
753

    
754
        rs = RadioSetting("obeep", "Open Beep",
755
                          RadioSettingValueBoolean(_settings.obeep))
756
        cfg_grp.append(rs)
757
        rs = RadioSetting("rbeep", "Roger Beep",
758
                          RadioSettingValueBoolean(_settings.rbeep))
759
        cfg_grp.append(rs)
760
        rs = RadioSetting("keylock_off", "Key Lock",
761
                          RadioSettingValueBoolean(not _settings.keylock_off))
762
        cfg_grp.append(rs)
763
        rs = RadioSetting("ctdcsb", "CT/DCS Busy Lock",
764
                          RadioSettingValueBoolean(_settings.ctdcsb))
765
        cfg_grp.append(rs)
766
        rs = RadioSetting("alarm", "Alarm Key",
767
                          RadioSettingValueBoolean(_settings.alarm))
768
        cfg_grp.append(rs)
769
        rs = RadioSetting("save", "Battery Save",
770
                          RadioSettingValueBoolean(_settings.save))
771
        cfg_grp.append(rs)
772
        rs = RadioSetting("kbeep", "Key Beep",
773
                          RadioSettingValueBoolean(_settings.kbeep))
774
        cfg_grp.append(rs)
775
        rs = RadioSetting("reset", "Reset Enable",
776
                          RadioSettingValueBoolean(_settings.reset))
777
        cfg_grp.append(rs)
778
        rs = RadioSetting("smfont_off", "Small Font",
779
                          RadioSettingValueBoolean(not _settings.smfont_off))
780
        cfg_grp.append(rs)
781
        rs = RadioSetting("aliasen_off", "Alias Enable",
782
                          RadioSettingValueBoolean(not _settings.aliasen_off))
783
        cfg_grp.append(rs)
784
        rs = RadioSetting("txstop_off", "TX Stop",
785
                          RadioSettingValueBoolean(not _settings.txstop_off))
786
        cfg_grp.append(rs)
787
        rs = RadioSetting("dw_off", "Dual Watch",
788
                          RadioSettingValueBoolean(not _settings.dw_off))
789
        cfg_grp.append(rs)
790
        rs = RadioSetting("fmen_off", "FM Enable",
791
                          RadioSettingValueBoolean(not _settings.fmen_off))
792
        cfg_grp.append(rs)
793
        rs = RadioSetting("fmdw", "FM Dual Watch",
794
                          RadioSettingValueBoolean(_settings.fmdw))
795
        cfg_grp.append(rs)
796
        rs = RadioSetting("fmscan_off", "FM Scan",
797
                          RadioSettingValueBoolean(
798
                              not _settings.fmscan_off))
799
        cfg_grp.append(rs)
800
        rs = RadioSetting("keypadmic_off", "Keypad MIC",
801
                          RadioSettingValueBoolean(
802
                              not _settings.keypadmic_off))
803
        cfg_grp.append(rs)
804
        rs = RadioSetting("voxgain", "VOX Gain",
805
                          RadioSettingValueList(
806
                              VOXGAIN_LIST, VOXGAIN_LIST[_settings.voxgain]))
807
        cfg_grp.append(rs)
808
        rs = RadioSetting("voxdt", "VOX Delay Time",
809
                          RadioSettingValueList(
810
                              VOXDT_LIST, VOXDT_LIST[_settings.voxdt]))
811
        cfg_grp.append(rs)
812
        rs = RadioSetting("vir", "VOX Inhibit on Receive",
813
                          RadioSettingValueBoolean(_settings.vir))
814
        cfg_grp.append(rs)
815

    
816
        #
817
        # Advanced Settings
818
        #
819
        val = (_settings.dtmftime) - 5
820
        rs = RadioSetting("dtmftime", "DTMF Digit Time",
821
                          RadioSettingValueList(
822
                              DTMFTIME_LIST, DTMFTIME_LIST[val]))
823
        adv_grp.append(rs)
824
        val = (_settings.dtmfspace) - 5
825
        rs = RadioSetting("dtmfspace", "DTMF Digit Space Time",
826
                          RadioSettingValueList(
827
                              DTMFTIME_LIST, DTMFTIME_LIST[val]))
828
        adv_grp.append(rs)
829
        val = (_settings.dtmfdelay) / 5
830
        rs = RadioSetting("dtmfdelay", "DTMF 1st Digit Delay",
831
                          RadioSettingValueList(
832
                              DTMFDELAY_LIST, DTMFDELAY_LIST[val]))
833
        adv_grp.append(rs)
834
        val = (_settings.dtmfpretime) / 10 - 1
835
        rs = RadioSetting("dtmfpretime", "DTMF Pretime",
836
                          RadioSettingValueList(
837
                              DTMFPRETIME_LIST, DTMFPRETIME_LIST[val]))
838
        adv_grp.append(rs)
839
        val = (_settings.dtmfdelay2) / 5
840
        rs = RadioSetting("dtmfdelay2", "DTMF * and # Digit Delay",
841
                          RadioSettingValueList(
842
                              DTMFDELAY2_LIST, DTMFDELAY2_LIST[val]))
843
        adv_grp.append(rs)
844
        rs = RadioSetting("ackdecode", "ACK Decode",
845
                          RadioSettingValueBoolean(_settings.ackdecode))
846
        adv_grp.append(rs)
847
        rs = RadioSetting("dtmfst", "DTMF Sidetone",
848
                          RadioSettingValueBoolean(_settings.dtmfst))
849
        adv_grp.append(rs)
850

    
851
        rs = RadioSetting("service.rssi400", "Squelch Base Level (UHF)",
852
                          RadioSettingValueInteger(0, 255, _service.rssi400))
853
        adv_grp.append(rs)
854
        rs = RadioSetting("service.rssi136", "Squelch Base Level (VHF)",
855
                          RadioSettingValueInteger(0, 255, _service.rssi136))
856
        adv_grp.append(rs)
857

    
858
        #
859
        # Key Settings
860
        #
861
        val = (_settings.lptime) - 5
862
        rs = RadioSetting("lptime", "Long Press Time",
863
                          RadioSettingValueList(
864
                              LPTIME_LIST, LPTIME_LIST[val]))
865
        key_grp.append(rs)
866
        rs = RadioSetting("keyp1long", "P1 Long Key",
867
                          RadioSettingValueList(
868
                              PFKEYLONG_LIST,
869
                              PFKEYLONG_LIST[_settings.keyp1long]))
870
        key_grp.append(rs)
871
        rs = RadioSetting("keyp1short", "P1 Short Key",
872
                          RadioSettingValueList(
873
                              PFKEYSHORT_LIST,
874
                              PFKEYSHORT_LIST[_settings.keyp1short]))
875
        key_grp.append(rs)
876
        rs = RadioSetting("keyp2long", "P2 Long Key",
877
                          RadioSettingValueList(
878
                              PFKEYLONG_LIST,
879
                              PFKEYLONG_LIST[_settings.keyp2long]))
880
        key_grp.append(rs)
881
        rs = RadioSetting("keyp2short", "P2 Short Key",
882
                          RadioSettingValueList(
883
                              PFKEYSHORT_LIST,
884
                              PFKEYSHORT_LIST[_settings.keyp2short]))
885
        key_grp.append(rs)
886
        rs = RadioSetting("keyp3long", "P3 Long Key",
887
                          RadioSettingValueList(
888
                              PFKEYLONG_LIST,
889
                              PFKEYLONG_LIST[_settings.keyp3long]))
890
        key_grp.append(rs)
891
        rs = RadioSetting("keyp3short", "P3 Short Key",
892
                          RadioSettingValueList(
893
                              PFKEYSHORT_LIST,
894
                              PFKEYSHORT_LIST[_settings.keyp3short]))
895
        key_grp.append(rs)
896

    
897
        val = RadioSettingValueList(PFKEYSHORT_LIST,
898
                                    PFKEYSHORT_LIST[_settings.keymshort])
899
        val.set_mutable(_settings.menuen == 0)
900
        rs = RadioSetting("keymshort", "M Short Key", val)
901
        key_grp.append(rs)
902
        val = RadioSettingValueBoolean(_settings.menuen)
903
        rs = RadioSetting("menuen", "Menu Enable", val)
904
        key_grp.append(rs)
905

    
906
        return group
907

    
908
    def get_settings(self):
909
        try:
910
            return self._get_settings()
911
        except:
912
            import traceback
913
            LOG.error("Failed to parse settings: %s", traceback.format_exc())
914
            return None
915

    
916
    def set_settings(self, settings):
917
        _settings = self._memobj.settings
918
        for element in settings:
919
            if not isinstance(element, RadioSetting):
920
                self.set_settings(element)
921
                continue
922
            else:
923
                try:
924
                    name = element.get_name()
925
                    if "." in name:
926
                        bits = name.split(".")
927
                        obj = self._memobj
928
                        for bit in bits[:-1]:
929
                            if "/" in bit:
930
                                bit, index = bit.split("/", 1)
931
                                index = int(index)
932
                                obj = getattr(obj, bit)[index]
933
                            else:
934
                                obj = getattr(obj, bit)
935
                        setting = bits[-1]
936
                    else:
937
                        obj = _settings
938
                        setting = element.get_name()
939

    
940
                    if element.has_apply_callback():
941
                        LOG.debug("Using apply callback")
942
                        element.run_apply_callback()
943
                    elif setting == "keylock_off":
944
                        setattr(obj, setting, not int(element.value))
945
                    elif setting == "smfont_off":
946
                        setattr(obj, setting, not int(element.value))
947
                    elif setting == "aliasen_off":
948
                        setattr(obj, setting, not int(element.value))
949
                    elif setting == "txstop_off":
950
                        setattr(obj, setting, not int(element.value))
951
                    elif setting == "dw_off":
952
                        setattr(obj, setting, not int(element.value))
953
                    elif setting == "fmen_off":
954
                        setattr(obj, setting, not int(element.value))
955
                    elif setting == "fmscan_off":
956
                        setattr(obj, setting, not int(element.value))
957
                    elif setting == "keypadmic_off":
958
                        setattr(obj, setting, not int(element.value))
959
                    elif setting == "dtmftime":
960
                        setattr(obj, setting, int(element.value) + 5)
961
                    elif setting == "dtmfspace":
962
                        setattr(obj, setting, int(element.value) + 5)
963
                    elif setting == "dtmfdelay":
964
                        setattr(obj, setting, int(element.value) * 5)
965
                    elif setting == "dtmfpretime":
966
                        setattr(obj, setting, (int(element.value) + 1) * 10)
967
                    elif setting == "dtmfdelay2":
968
                        setattr(obj, setting, int(element.value) * 5)
969
                    elif setting == "lptime":
970
                        setattr(obj, setting, int(element.value) + 5)
971
                    else:
972
                        LOG.debug("Setting %s = %s" % (setting, element.value))
973
                        setattr(obj, setting, element.value)
974
                except Exception, e:
975
                    LOG.debug(element.get_name())
976
                    raise
977

    
978
    @classmethod
979
    def match_model(cls, filedata, filename):
980
        if filedata[0x168:0x170].startswith(cls._file_ident) and \
981
           filedata[0x170:0x178].startswith(cls._model_ident):
982
            return True
983
        else:
984
            return False
985

    
986

    
987
@directory.register
988
class JetstreamJT270MRadio(LeixenVV898Radio):
989
    """Jetstream JT270M"""
990
    VENDOR = "Jetstream"
991
    MODEL = "JT270M"
992

    
993
    _file_ident = "JET"
994
    _model_ident = 'LX-\x89\x85\x53'
995

    
996

    
997
class VV898E(chirp_common.Alias):
998
    '''Leixen has called this radio both 898E and S historically, ident is identical'''
999
    VENDOR = "Leixen"
1000
    MODEL = "VV-898E"
1001

    
1002

    
1003
@directory.register
1004
class LeixenVV898SRadio(LeixenVV898Radio):
1005
    """Leixen VV-898S, also VV-898E which is identical"""
1006
    VENDOR = "Leixen"
1007
    MODEL = "VV-898S"
1008
    ALIASES = [VV898E, ]
1009

    
1010
    _model_ident = 'LX-\x89\x85\x75'
1011
    _mem_formatter = {'unknownormode': 'mode:1',
1012
                      'modeorpower': 'power:2'}
1013
    _power_levels = [chirp_common.PowerLevel("Low", watts=5),
1014
                     chirp_common.PowerLevel("Med", watts=10),
1015
                     chirp_common.PowerLevel("High", watts=25)]
1016

    
1017

    
1018
@directory.register
1019
class VV898S107Radio(LeixenVV898Radio):
1020
    """Leixen VV-898S v1.07 / E v1.05 firmware, which has a different channel memory layout"""
1021
    VENDOR = "Leixen"
1022
    MODEL = "VV-898S107"
1023
    ALIASES = [VV898E, ]
1024

    
1025
    _model_ident = 'LX-\x89\x85\x85'
1026

    
1027
    ## default offsets, for VFOa
1028
    _mem_formatter = {'chan_offset': 0x0c10,
1029
                      'name_offset': 0x1908}
1030
    _power_levels = [chirp_common.PowerLevel("Low", watts=5),
1031
                     chirp_common.PowerLevel("Med", watts=10),
1032
                     chirp_common.PowerLevel("High", watts=25)]
1033

    
1034
    def get_features(self):
1035
      rf = LeixenVV898Radio.get_features(self)
1036
      rf.memory_bounds = (1, 99)
1037
      rf.has_sub_devices = self.VARIANT == ""
1038
      return rf
1039

    
1040
    def get_sub_devices(self):
1041
        return [VV898S107RadioVFOA(self._mmap), VV898S107RadioVFOB(self._mmap)]
1042

    
1043
    def process_mmap(self):
1044
        self._memobj = bitwise.parse(MEM_FORMAT + MEM_CHANNEL_107 % self._mem_formatter, self._mmap)
1045

    
1046

    
1047

    
1048
class VV898S107RadioVFOA(VV898S107Radio):
1049
    """VV898S VFO A subdevice"""
1050
    VARIANT = "VFOa"
1051

    
1052
class VV898S107RadioVFOB(VV898S107Radio):
1053
    """VV898S VFO B subdevice"""
1054
    VARIANT = "VFOb"
1055
    _mem_formatter = {'chan_offset': 0x0c00,
1056
                      'name_offset': 0x1900}
1057
    # VFOb uses the base class mem_formatter
1058

    
1059

    
1060