Project

General

Profile

Bug #805 » wouxun.py

Tom Hayward, 04/23/2013 09:13 AM

 
1
# Copyright 2011 Dan Smith <dsmith@danplanet.com>
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 3 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
"""Wouxun radios management module"""
17

    
18
import time
19
import os
20
import sys
21
from chirp import util, chirp_common, bitwise, memmap, errors, directory
22
from chirp.settings import RadioSetting, RadioSettingGroup, \
23
                RadioSettingValueBoolean, RadioSettingValueList, \
24
                RadioSettingValueInteger, RadioSettingValueString
25
from chirp.wouxun_common import wipe_memory, do_download, do_upload
26

    
27
FREQ_ENCODE_TABLE = [ 0x7, 0xa, 0x0, 0x9, 0xb, 0x2, 0xe, 0x1, 0x3, 0xf ]
28
 
29
def encode_freq(freq):
30
    """Convert frequency (4 decimal digits) to wouxun format (2 bytes)"""
31
    enc = 0
32
    div = 1000
33
    for i in range(0, 4):
34
        enc <<= 4
35
        enc |= FREQ_ENCODE_TABLE[ (freq/div) % 10 ]
36
        div /= 10
37
    return enc
38

    
39
def decode_freq(data):
40
    """Convert from wouxun format (2 bytes) to frequency (4 decimal digits)"""
41
    freq = 0
42
    shift = 12
43
    for i in range(0, 4):
44
        freq *= 10
45
        freq += FREQ_ENCODE_TABLE.index( (data>>shift) & 0xf )
46
        shift -= 4
47
        # print "data %04x freq %d shift %d" % (data, freq, shift)
48
    return freq
49

    
50
@directory.register
51
class KGUVD1PRadio(chirp_common.CloneModeRadio,
52
        chirp_common.ExperimentalRadio):
53
    """Wouxun KG-UVD1P,UV2,UV3"""
54
    VENDOR = "Wouxun"
55
    MODEL = "KG-UVD1P"
56
    _model = "KG669V"
57
    
58
    _querymodel = "HiWOUXUN\x02"
59
    
60
    CHARSET = list("0123456789") + [chr(x + ord("A")) for x in range(0, 26)] + \
61
        list("?+-")
62

    
63
    POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
64
                    chirp_common.PowerLevel("Low", watts=1.00)]
65
                    
66
    valid_freq = [(136000000, 175000000), (216000000, 520000000)]
67

    
68

    
69
    _MEM_FORMAT = """
70
        #seekto 0x0010;
71
        struct {
72
          lbcd rx_freq[4];
73
          lbcd tx_freq[4];
74
          ul16 rx_tone;
75
          ul16 tx_tone;
76
          u8 _3_unknown_1:4,
77
             bcl:1,
78
             _3_unknown_2:3;
79
          u8 splitdup:1,
80
             skip:1,
81
             power_high:1,
82
             iswide:1,
83
             _2_unknown_2:4;
84
          u8 unknown[2];
85
        } memory[199];
86
        
87
        #seekto 0x0970;
88
        struct {
89
            u16 vhf_rx_start;
90
            u16 vhf_rx_stop;
91
            u16 uhf_rx_start;
92
            u16 uhf_rx_stop;
93
            u16 vhf_tx_start;
94
            u16 vhf_tx_stop;
95
            u16 uhf_tx_start;
96
            u16 uhf_tx_stop;
97
        } freq_ranges;
98

    
99
        #seekto 0x0E5C;
100
        struct {
101
          u8 unknown_flag1:7,
102
             menu_available:1;
103
        } settings;
104

    
105
        #seekto 0x1008;
106
        struct {
107
          u8 unknown[8];
108
          u8 name[6];
109
          u8 pad[2];
110
        } names[199];
111
    """
112

    
113
    @classmethod
114
    def get_experimental_warning(cls):
115
        return ('This version of the Wouxun driver allows you to modify the '
116
                'frequency range settings of your radio. This has been tested '
117
                'and reports from other users indicate that it is a safe '
118
                'thing to do. However, modifications to this value may have '
119
                'unintended consequences, including damage to your device. '
120
                'You have been warned. Proceed at your own risk!')
121

    
122
    def _identify(self):
123
        """Do the original wouxun identification dance"""
124
        for _i in range(0, 5):
125
            self.pipe.write(self._querymodel)
126
            resp = self.pipe.read(9)
127
            if len(resp) != 9:
128
                print "Got:\n%s" % util.hexprint(resp)
129
                print "Retrying identification..."
130
                time.sleep(1)
131
                continue
132
            if resp[2:8] != self._model:
133
                raise Exception("I can't talk to this model (%s)" % 
134
                    util.hexprint(resp))
135
            return
136
        if len(resp) == 0:
137
            raise Exception("Radio not responding")
138
        else:
139
            raise Exception("Unable to identify radio")
140

    
141
    def _start_transfer(self):
142
        """Tell the radio to go into transfer mode"""
143
        self.pipe.write("\x02\x06")
144
        time.sleep(0.05)
145
        ack = self.pipe.read(1)
146
        if ack != "\x06":
147
            raise Exception("Radio refused transfer mode")    
148
    def _download(self):
149
        """Talk to an original wouxun and do a download"""
150
        try:
151
            self._identify()
152
            self._start_transfer()
153
            return do_download(self, 0x0000, 0x2000, 0x0040)
154
        except errors.RadioError:
155
            raise
156
        except Exception, e:
157
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
158

    
159
    def _upload(self):
160
        """Talk to an original wouxun and do an upload"""
161
        try:
162
            self._identify()
163
            self._start_transfer()
164
            return do_upload(self, 0x0000, 0x2000, 0x0010)
165
        except errors.RadioError:
166
            raise
167
        except Exception, e:
168
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
169

    
170

    
171
    def sync_in(self):
172
        self._mmap = self._download()
173
        self.process_mmap()
174

    
175
    def sync_out(self):
176
        self._upload()
177

    
178
    def process_mmap(self):
179
        if len(self._mmap.get_packed()) != 8192:
180
            print "NOTE: Fixing old-style Wouxun image"
181
            # Originally, CHIRP's wouxun image had eight bytes of
182
            # static data, followed by the first memory at offset
183
            # 0x0008.  Between 0.1.11 and 0.1.12, this was fixed to 16
184
            # bytes of (whatever) followed by the first memory at
185
            # offset 0x0010, like the radio actually stores it.  So,
186
            # if we find one of those old ones, convert it to the new
187
            # format, padding 16 bytes of 0xFF in front.
188
            self._mmap = memmap.MemoryMap(("\xFF" * 16) + \
189
                                              self._mmap.get_packed()[8:8184])
190
        self._memobj = bitwise.parse(self._MEM_FORMAT, self._mmap)
191

    
192
    def get_features(self):
193
        rf = chirp_common.RadioFeatures()
194
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
195
        rf.valid_cross_modes = [
196
                        "Tone->Tone",
197
                        "Tone->DTCS",
198
                        "DTCS->Tone",
199
                        "DTCS->",
200
                        "->Tone",
201
                        "->DTCS",
202
                        "DTCS->DTCS",
203
                    ]
204
        rf.valid_modes = ["FM", "NFM"]
205
        rf.valid_power_levels = self.POWER_LEVELS
206
        rf.valid_bands = self.valid_freq
207
        rf.valid_characters = "".join(self.CHARSET)
208
        rf.valid_name_length = 6
209
        rf.valid_duplexes = ["", "+", "-", "split", "off"]
210
        rf.has_ctone = True
211
        rf.has_rx_dtcs = True
212
        rf.has_cross = True
213
        rf.has_tuning_step = False
214
        rf.has_bank = False
215
        rf.has_settings = True
216
        rf.memory_bounds = (1, 128)
217
        rf.can_odd_split = True
218
        return rf
219

    
220
    def get_settings(self):
221
        freqranges = RadioSettingGroup("freqranges", "Freq ranges")
222
        top = RadioSettingGroup("top", "All Settings", freqranges)
223

    
224
        rs = RadioSetting("menu_available", "Menu Available",
225
                          RadioSettingValueBoolean(
226
                            self._memobj.settings.menu_available))
227
        top.append(rs)
228

    
229
        rs = RadioSetting("vhf_rx_start", "VHF RX Lower Limit (MHz)",
230
                          RadioSettingValueInteger(136, 174, 
231
                                decode_freq(
232
                                    self._memobj.freq_ranges.vhf_rx_start)))
233
        freqranges.append(rs)
234
        rs = RadioSetting("vhf_rx_stop", "VHF RX Upper Limit (MHz)",
235
                          RadioSettingValueInteger(136, 174, 
236
                                decode_freq(
237
                                    self._memobj.freq_ranges.vhf_rx_stop)))
238
        freqranges.append(rs)
239
        rs = RadioSetting("uhf_rx_start", "UHF RX Lower Limit (MHz)",
240
                          RadioSettingValueInteger(216, 520, 
241
                                decode_freq(
242
                                    self._memobj.freq_ranges.uhf_rx_start)))
243
        freqranges.append(rs)
244
        rs = RadioSetting("uhf_rx_stop", "UHF RX Upper Limit (MHz)",
245
                          RadioSettingValueInteger(216, 520, 
246
                                decode_freq(
247
                                    self._memobj.freq_ranges.uhf_rx_stop)))
248
        freqranges.append(rs)
249
        rs = RadioSetting("vhf_tx_start", "VHF TX Lower Limit (MHz)",
250
                          RadioSettingValueInteger(136, 174, 
251
                                decode_freq(
252
                                    self._memobj.freq_ranges.vhf_tx_start)))
253
        freqranges.append(rs)
254
        rs = RadioSetting("vhf_tx_stop", "VHF TX Upper Limit (MHz)",
255
                          RadioSettingValueInteger(136, 174, 
256
                                decode_freq(
257
                                    self._memobj.freq_ranges.vhf_tx_stop)))
258
        freqranges.append(rs)
259
        rs = RadioSetting("uhf_tx_start", "UHF TX Lower Limit (MHz)",
260
                          RadioSettingValueInteger(216, 520, 
261
                                decode_freq(
262
                                    self._memobj.freq_ranges.uhf_tx_start)))
263
        freqranges.append(rs)
264
        rs = RadioSetting("uhf_tx_stop", "UHF TX Upper Limit (MHz)",
265
                          RadioSettingValueInteger(216, 520, 
266
                                decode_freq(
267
                                    self._memobj.freq_ranges.uhf_tx_stop)))
268
        freqranges.append(rs)
269
        
270
        # tell the decoded ranges to UI
271
        self.valid_freq = [
272
            ( decode_freq(self._memobj.freq_ranges.vhf_rx_start) * 1000000, 
273
             (decode_freq(self._memobj.freq_ranges.vhf_rx_stop)+1) * 1000000), 
274
            ( decode_freq(self._memobj.freq_ranges.uhf_rx_start)  * 1000000,
275
             (decode_freq(self._memobj.freq_ranges.uhf_rx_stop)+1) * 1000000)]
276

    
277
        return top
278

    
279
    def set_settings(self, settings):
280
        for element in settings:
281
            if not isinstance(element, RadioSetting):
282
                if element.get_name() != "freqranges" :
283
                    self.set_settings(element)
284
                else:
285
                    self._set_freq_settings(element)
286
            else:
287
                try:
288
                    if "." in element.get_name():
289
                        bits = element.get_name().split(".")
290
                        obj = self._memobj
291
                        for bit in bits[:-1]:
292
                            obj = getattr(obj, bit)
293
                        setting = bits[-1]
294
                    else:
295
                        obj = self._memobj.settings
296
                        setting = element.get_name()
297
                    print "Setting %s = %s" % (setting, element.value)
298
                    setattr(obj, setting, element.value)
299
                except Exception, e:
300
                    print element.get_name()
301
                    raise
302

    
303
    def _set_freq_settings(self, settings):
304
        for element in settings:
305
            try:
306
                setattr(self._memobj.freq_ranges,
307
                        element.get_name(),
308
                        encode_freq(int(element.value)))
309
            except Exception, e:
310
                print element.get_name()
311
                raise
312

    
313
    def get_raw_memory(self, number):
314
        return repr(self._memobj.memory[number - 1])
315

    
316
    def _get_tone(self, _mem, mem):
317
        def _get_dcs(val):
318
            code = int("%03o" % (val & 0x07FF))
319
            pol = (val & 0x8000) and "R" or "N"
320
            return code, pol
321
            
322
        if _mem.tx_tone != 0xFFFF and _mem.tx_tone > 0x2800:
323
            tcode, tpol = _get_dcs(_mem.tx_tone)
324
            mem.dtcs = tcode
325
            txmode = "DTCS"
326
        elif _mem.tx_tone != 0xFFFF:
327
            mem.rtone = _mem.tx_tone / 10.0
328
            txmode = "Tone"
329
        else:
330
            txmode = ""
331

    
332
        if _mem.rx_tone != 0xFFFF and _mem.rx_tone > 0x2800:
333
            rcode, rpol = _get_dcs(_mem.rx_tone)
334
            mem.rx_dtcs = rcode
335
            rxmode = "DTCS"
336
        elif _mem.rx_tone != 0xFFFF:
337
            mem.ctone = _mem.rx_tone / 10.0
338
            rxmode = "Tone"
339
        else:
340
            rxmode = ""
341

    
342
        if txmode == "Tone" and not rxmode:
343
            mem.tmode = "Tone"
344
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
345
            mem.tmode = "TSQL"
346
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
347
            mem.tmode = "DTCS"
348
        elif rxmode or txmode:
349
            mem.tmode = "Cross"
350
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
351

    
352
        if mem.tmode == "DTCS":
353
            mem.dtcs_polarity = "%s%s" % (tpol, rpol)
354

    
355
        if os.getenv("CHIRP_DEBUG"):
356
            print "Got TX %s (%i) RX %s (%i)" % (txmode, _mem.tx_tone,
357
                                                 rxmode, _mem.rx_tone)
358

    
359
    def _is_txinh(self, _mem):
360
        raw_tx = ""
361
        for i in range(0, 4):
362
            raw_tx += _mem.tx_freq[i].get_raw()
363
        return raw_tx == "\xFF\xFF\xFF\xFF"
364

    
365
    def get_memory(self, number):
366
        _mem = self._memobj.memory[number - 1]
367
        _nam = self._memobj.names[number - 1]
368

    
369
        mem = chirp_common.Memory()
370
        mem.number = number
371

    
372
        if _mem.get_raw() == ("\xff" * 16):
373
            mem.empty = True
374
            return mem
375

    
376
        mem.freq = int(_mem.rx_freq) * 10
377
        if _mem.splitdup:
378
            mem.duplex = "split"
379
        elif self._is_txinh(_mem):
380
            mem.duplex = "off"
381
        elif int(_mem.rx_freq) < int(_mem.tx_freq):
382
            mem.duplex = "+"
383
        elif int(_mem.rx_freq) > int(_mem.tx_freq):
384
            mem.duplex = "-"
385

    
386
        if mem.duplex == "" or mem.duplex == "off":
387
            mem.offset = 0
388
        elif mem.duplex == "split":
389
            mem.offset = int(_mem.tx_freq) * 10
390
        else:
391
            mem.offset = abs(int(_mem.tx_freq) - int(_mem.rx_freq)) * 10
392

    
393
        if not _mem.skip:
394
            mem.skip = "S"
395
        if not _mem.iswide:
396
            mem.mode = "NFM"
397

    
398
        self._get_tone(_mem, mem)
399

    
400
        mem.power = self.POWER_LEVELS[not _mem.power_high]
401

    
402
        for i in _nam.name:
403
            if i == 0xFF:
404
                break
405
            mem.name += self.CHARSET[i]
406

    
407
        mem.extra = RadioSettingGroup("extra", "Extra")
408
        bcl = RadioSetting("bcl", "BCL",
409
                           RadioSettingValueBoolean(bool(_mem.bcl)))
410
        bcl.set_doc("Busy Channel Lockout")
411
        mem.extra.append(bcl)
412

    
413
        return mem
414

    
415
    def _set_tone(self, mem, _mem):
416
        def _set_dcs(code, pol):
417
            val = int("%i" % code, 8) + 0x2800
418
            if pol == "R":
419
                val += 0xA000
420
            return val
421

    
422
        if mem.tmode == "Cross":
423
            tx_mode, rx_mode = mem.cross_mode.split("->")
424
        elif mem.tmode == "Tone":
425
            tx_mode = mem.tmode
426
            rx_mode = None
427
        else:
428
            tx_mode = rx_mode = mem.tmode
429

    
430

    
431
        if tx_mode == "DTCS":
432
            _mem.tx_tone = mem.tmode != "DTCS" and \
433
                _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) or \
434
                _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[0])
435
        elif tx_mode:
436
            _mem.tx_tone = tx_mode == "Tone" and \
437
                int(mem.rtone * 10) or int(mem.ctone * 10)
438
        else:
439
            _mem.tx_tone = 0xFFFF
440

    
441
        if rx_mode == "DTCS":
442
            _mem.rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
443
        elif rx_mode:
444
            _mem.rx_tone = int(mem.ctone * 10)
445
        else:
446
            _mem.rx_tone = 0xFFFF
447

    
448
        if os.getenv("CHIRP_DEBUG"):
449
            print "Set TX %s (%i) RX %s (%i)" % (tx_mode, _mem.tx_tone,
450
                                                 rx_mode, _mem.rx_tone)
451

    
452
    def set_memory(self, mem):
453
        _mem = self._memobj.memory[mem.number - 1]
454
        _nam = self._memobj.names[mem.number - 1]
455

    
456
        if mem.empty:
457
            wipe_memory(_mem, "\xFF")
458
            return
459

    
460
        if _mem.get_raw() == ("\xFF" * 16):
461
            wipe_memory(_mem, "\x00")
462

    
463
        _mem.rx_freq = int(mem.freq / 10)
464
        if mem.duplex == "split":
465
            _mem.tx_freq = int(mem.offset / 10)
466
        elif mem.duplex == "off":
467
            for i in range(0, 4):
468
                _mem.tx_freq[i].set_raw("\xFF")
469
        elif mem.duplex == "+":
470
            _mem.tx_freq = int(mem.freq / 10) + int(mem.offset / 10)
471
        elif mem.duplex == "-":
472
            _mem.tx_freq = int(mem.freq / 10) - int(mem.offset / 10)
473
        else:
474
            _mem.tx_freq = int(mem.freq / 10)
475
        _mem.splitdup = mem.duplex == "split"
476
        _mem.skip = mem.skip != "S"
477
        _mem.iswide = mem.mode != "NFM"
478

    
479
        self._set_tone(mem, _mem)
480

    
481
        if mem.power:
482
            _mem.power_high = not self.POWER_LEVELS.index(mem.power)
483
        else:
484
            _mem.power_high = True
485

    
486
        _nam.name = [0xFF] * 6
487
        for i in range(0, len(mem.name)):
488
            try:
489
                _nam.name[i] = self.CHARSET.index(mem.name[i])
490
            except IndexError:
491
                raise Exception("Character `%s' not supported")
492

    
493
        for setting in mem.extra:
494
            setattr(_mem, setting.get_name(), setting.value)
495

    
496
    @classmethod
497
    def match_model(cls, filedata, filename):
498
        # New-style image (CHIRP 0.1.12)
499
        if len(filedata) == 8192 and \
500
                filedata[0x60:0x64] != "2009" and \
501
                filedata[0x1f77:0x1f7d] == "\xff\xff\xff\xff\xff\xff" and \
502
                filedata[0x0d70:0x0d80] == "\xff\xff\xff\xff\xff\xff\xff\xff" \
503
                                           "\xff\xff\xff\xff\xff\xff\xff\xff": 
504
                # those areas are (seems to be) unused
505
            return True
506
        # Old-style image (CHIRP 0.1.11)
507
        if len(filedata) == 8200 and \
508
                filedata[0:4] == "\x01\x00\x00\x00":
509
            return True
510
        return False
511

    
512
@directory.register
513
class KGUV6DRadio(KGUVD1PRadio):
514
    """Wouxun KG-UV6 (D and X variants)"""
515
    MODEL = "KG-UV6"
516
    HARDWARE_FLOW = sys.platform == "darwin"  # only OS X driver needs hw flow
517

    
518
    
519
    _querymodel = "HiWXUVD1\x02"
520
    
521
    _MEM_FORMAT = """
522
        #seekto 0x0010;
523
        struct {
524
          lbcd rx_freq[4];
525
          lbcd tx_freq[4];
526
          ul16 rx_tone;
527
          ul16 tx_tone;
528
          u8 _3_unknown_1:4,
529
             bcl:1,
530
             _3_unknown_2:3;
531
          u8 splitdup:1,
532
             skip:1,
533
             power_high:1,
534
             iswide:1,
535
             _2_unknown_2:4;
536
          u8 pad[2];
537
        } memory[199];
538

    
539
        #seekto 0x0F00;
540
        struct {
541
          char welcome1[6];
542
          char welcome2[6];
543
          char single_band[6];
544
        } strings;
545

    
546
        #seekto 0x0F20;
547
        struct {
548
          u8 unknown_flag_01:6,
549
             vfo_b_ch_disp:2;
550
          u8 unknown_flag_02:5,
551
             vfo_a_fr_step:3;
552
          u8 unknown_flag_03:4,
553
             vfo_a_squelch:4;
554
          u8 unknown_flag_04:7,
555
             power_save:1;
556
          u8 unknown_flag_05:5,
557
             pf2_function:3;
558
          u8 unknown_flag_06:6,
559
             roger_beep:2;
560
          u8 unknown_flag_07:2,
561
             transmit_time_out:6;
562
          u8 unknown_flag_08:4,
563
             vox:4;
564
          u8 unknown_1[4];
565
          u8 unknown_flag_09:6,
566
             voice:2;
567
          u8 unknown_flag_10:7,
568
             beep:1;
569
          u8 unknown_flag_11:7,
570
             ani_id_enable:1;
571
          u8 unknown_2[2];
572
          u8 unknown_flag_12:5,
573
             vfo_b_fr_step:3;
574
          u8 unknown_3[1];
575
          u8 unknown_flag_13:3,
576
             ani_id_tx_delay:5;
577
          u8 unknown_4[1];
578
          u8 unknown_flag_14:6,
579
             ani_id_sidetone:2;
580
          u8 unknown_flag_15:4,
581
             tx_time_out_alert:4;
582
          u8 unknown_flag_16:6,
583
             vfo_a_ch_disp:2;
584
          u8 unknown_flag_15:6,
585
             scan_mode:2;
586
          u8 unknown_flag_16:7,
587
             kbd_lock:1;
588
          u8 unknown_flag_17:6,
589
             ponmsg:2;
590
          u8 unknown_flag_18:5,
591
             pf1_function:3;
592
          u8 unknown_5[1];
593
          u8 unknown_flag_19:7,
594
             auto_backlight:1;
595
          u8 unknown_flag_20:7,
596
             sos_ch:1;
597
          u8 unknown_6[2];
598
          u8 unknown_flag_21:7,
599
             auto_lock_kbd:1;
600
          u8 unknown_flag_22:4,
601
             vfo_b_squelch:4;
602
          u8 unknown_7[1];
603
          u8 unknown_flag_23:7,
604
             stopwatch:1;
605
          u8 vfo_a_cur_chan;
606
          u8 unknown_flag_24:7,
607
             dual_band_receive:1;
608
          u8 current_vfo:1,
609
             unknown_flag_24:7;
610
          u8 unknown_8[2];
611
          u8 mode_password[6];
612
          u8 reset_password[6];
613
          u8 ani_id_content[6];
614
          u8 unknown_flag_25:7,
615
             menu_available:1;
616
          u8 unknown_9[1];
617
          u8 priority_chan;
618
          u8 vfo_b_cur_chan;
619
        } settings;
620

    
621
        #seekto 0x0f60;
622
        struct {
623
          lbcd rx_freq[4];
624
          lbcd tx_freq[4];
625
          ul16 rx_tone;
626
          ul16 tx_tone;
627
          u8 _3_unknown_3:4,
628
             bcl:1,
629
             _3_unknown_4:3;
630
          u8 splitdup:1,
631
             _2_unknown_3:1,
632
             power_high:1,
633
             iswide:1,
634
             _2_unknown_4:4;
635
          u8 pad[2];
636
        } vfo_settings[2];
637
	
638
        #seekto 0x0f80;
639
        u16 fm_presets_0[9];
640

    
641
        #seekto 0x0ff0;
642
        struct {
643
            u16 vhf_rx_start;
644
            u16 vhf_rx_stop;
645
            u16 uhf_rx_start;
646
            u16 uhf_rx_stop;
647
            u16 vhf_tx_start;
648
            u16 vhf_tx_stop;
649
            u16 uhf_tx_start;
650
            u16 uhf_tx_stop;
651
        } freq_ranges;
652

    
653
        #seekto 0x1010;
654
        struct {
655
          u8 name[6];
656
          u8 pad[10];
657
        } names[199];
658

    
659
        #seekto 0x1f60;
660
        struct {
661
            u8 unknown_flag_26:6,
662
               tx_offset_dir:2;
663
            u8 tx_offset[6];
664
            u8 pad[9];
665
        } vfo_offset[2];
666

    
667
        #seekto 0x1f80;
668
        u16 fm_presets_1[9];
669
    """
670

    
671

    
672
    def get_features(self):
673
        rf = KGUVD1PRadio.get_features(self)
674
        rf.memory_bounds = (1, 199)
675
        return rf
676

    
677
    def get_settings(self):
678
        freqranges = RadioSettingGroup("freqranges", "Freq ranges")
679
        top = RadioSettingGroup("top", "All Settings", freqranges)
680

    
681
        rs = RadioSetting("menu_available", "Menu Available",
682
                          RadioSettingValueBoolean(
683
                            self._memobj.settings.menu_available))
684
        top.append(rs)
685

    
686
        rs = RadioSetting("vhf_rx_start", "VHF RX Lower Limit (MHz)",
687
                          RadioSettingValueInteger(1, 1000, 
688
                                decode_freq(
689
                                    self._memobj.freq_ranges.vhf_rx_start)))
690
        freqranges.append(rs)
691
        rs = RadioSetting("vhf_rx_stop", "VHF RX Upper Limit (MHz)",
692
                          RadioSettingValueInteger(1, 1000, 
693
                                decode_freq(
694
                                    self._memobj.freq_ranges.vhf_rx_stop)))
695
        freqranges.append(rs)
696
        rs = RadioSetting("uhf_rx_start", "UHF RX Lower Limit (MHz)",
697
                          RadioSettingValueInteger(1, 1000, 
698
                                decode_freq(
699
                                    self._memobj.freq_ranges.uhf_rx_start)))
700
        freqranges.append(rs)
701
        rs = RadioSetting("uhf_rx_stop", "UHF RX Upper Limit (MHz)",
702
                          RadioSettingValueInteger(1, 1000, 
703
                                decode_freq(
704
                                    self._memobj.freq_ranges.uhf_rx_stop)))
705
        freqranges.append(rs)
706
        rs = RadioSetting("vhf_tx_start", "VHF TX Lower Limit (MHz)",
707
                          RadioSettingValueInteger(1, 1000, 
708
                                decode_freq(
709
                                    self._memobj.freq_ranges.vhf_tx_start)))
710
        freqranges.append(rs)
711
        rs = RadioSetting("vhf_tx_stop", "VHF TX Upper Limit (MHz)",
712
                          RadioSettingValueInteger(1, 1000, 
713
                                decode_freq(
714
                                    self._memobj.freq_ranges.vhf_tx_stop)))
715
        freqranges.append(rs)
716
        rs = RadioSetting("uhf_tx_start", "UHF TX Lower Limit (MHz)",
717
                          RadioSettingValueInteger(1, 1000, 
718
                                decode_freq(
719
                                    self._memobj.freq_ranges.uhf_tx_start)))
720
        freqranges.append(rs)
721
        rs = RadioSetting("uhf_tx_stop", "UHF TX Upper Limit (MHz)",
722
                          RadioSettingValueInteger(1, 1000, 
723
                                decode_freq(
724
                                    self._memobj.freq_ranges.uhf_tx_stop)))
725
        freqranges.append(rs)
726
        
727
        # tell the decoded ranges to UI
728
        self.valid_freq = [
729
            ( decode_freq(self._memobj.freq_ranges.vhf_rx_start) * 1000000, 
730
             (decode_freq(self._memobj.freq_ranges.vhf_rx_stop)+1) * 1000000), 
731
            ( decode_freq(self._memobj.freq_ranges.uhf_rx_start)  * 1000000,
732
             (decode_freq(self._memobj.freq_ranges.uhf_rx_stop)+1) * 1000000)]
733

    
734
        def _filter(name):
735
            filtered = ""
736
            for char in str(name):
737
                if char in chirp_common.CHARSET_ASCII:
738
                    filtered += char
739
                else:
740
                    filtered += " "
741
            return filtered
742

    
743
        # add some radio specific settings
744
        options = ["Off", "Welcome", "V bat"]
745
        rs = RadioSetting("ponmsg", "Poweron message",
746
                          RadioSettingValueList(options,
747
                                        options[self._memobj.settings.ponmsg]))
748
        top.append(rs)
749
        rs = RadioSetting("strings.welcome1", "Power-On Message 1",
750
                          RadioSettingValueString(0, 6, _filter(self._memobj.strings.welcome1)))
751
        top.append(rs)
752
        rs = RadioSetting("strings.welcome2", "Power-On Message 2",
753
                          RadioSettingValueString(0, 6, _filter(self._memobj.strings.welcome2)))
754
        top.append(rs)
755
        rs = RadioSetting("strings.single_band", "Single Band Message",
756
                          RadioSettingValueString(0, 6, _filter(self._memobj.strings.single_band)))
757
        top.append(rs)
758
        options = ["Channel", "ch/freq","Name", "VFO"]
759
        rs = RadioSetting("vfo_a_ch_disp", "VFO A Channel disp mode",
760
                          RadioSettingValueList(options,
761
                                        options[self._memobj.settings.vfo_a_ch_disp]))
762
        top.append(rs)
763
        rs = RadioSetting("vfo_b_ch_disp", "VFO B Channel disp mode",
764
                          RadioSettingValueList(options,
765
                                        options[self._memobj.settings.vfo_b_ch_disp]))
766
        top.append(rs)
767
	# TODO - vfo_a_fr_step
768
	# TODO -vfo_b_fr_step:3;
769
        rs = RadioSetting("vfo_a_squelch", "VFO A Squelch",
770
                          RadioSettingValueInteger(0, 9, self._memobj.settings.vfo_a_squelch))
771
        top.append(rs)
772
        rs = RadioSetting("vfo_b_squelch", "VFO B Squelch",
773
                          RadioSettingValueInteger(0, 9, self._memobj.settings.vfo_b_squelch))
774
        top.append(rs)
775
        rs = RadioSetting("vfo_a_cur_chan", "VFO A current channel",
776
                          RadioSettingValueInteger(1, 199, self._memobj.settings.vfo_a_cur_chan))
777
        top.append(rs)
778
        rs = RadioSetting("vfo_b_cur_chan", "VFO B current channel",
779
                          RadioSettingValueInteger(0, 199, self._memobj.settings.vfo_b_cur_chan))
780
        top.append(rs)
781
        rs = RadioSetting("priority_chan", "Priority channel",
782
                          RadioSettingValueInteger(0, 199, self._memobj.settings.priority_chan))
783
        top.append(rs)
784
        rs = RadioSetting("power_save", "Power save",
785
                          RadioSettingValueBoolean(self._memobj.settings.power_save))
786
        top.append(rs)
787
        options = ["Off", "Scan", "Lamp", "SOS", "Radio"]
788
        rs = RadioSetting("pf1_function", "PF1 Function select",
789
                          RadioSettingValueList(options,
790
                                        options[self._memobj.settings.pf1_function]))
791
        top.append(rs)
792
        options = ["Off", "Radio", "fr/ch", "Rpt", "Stopwatch", "Lamp", "SOS"]
793
        rs = RadioSetting("pf2_function", "PF2 Function select",
794
                          RadioSettingValueList(options,
795
                                        options[self._memobj.settings.pf2_function]))
796
        top.append(rs)
797
        options = ["Off", "Begin", "End", "Both"]
798
        rs = RadioSetting("roger_beep", "Roger beep select",
799
                          RadioSettingValueList(options,
800
                                        options[self._memobj.settings.roger_beep]))
801
        top.append(rs)
802
        # TODO - transmit_time_out:6;
803
        rs = RadioSetting("vox", "Vox",
804
                          RadioSettingValueInteger(0, 10, self._memobj.settings.vox))
805
        top.append(rs)
806
        options = ["Off", "Chinese", "English"]
807
        rs = RadioSetting("voice", "Voice",
808
                          RadioSettingValueList(options,
809
                                        options[self._memobj.settings.voice]))
810
        top.append(rs)
811
        rs = RadioSetting("beep", "Beep",
812
                          RadioSettingValueBoolean(self._memobj.settings.beep))
813
        top.append(rs)
814
        rs = RadioSetting("ani_id_enable", "ANI id enable",
815
                          RadioSettingValueBoolean(self._memobj.settings.ani_id_enable))
816
        top.append(rs)
817
        rs = RadioSetting("ani_id_tx_delay", "ANI id tx delay",
818
                          RadioSettingValueInteger(0, 30, self._memobj.settings.ani_id_tx_delay))
819
        top.append(rs)
820
        options = ["Off", "Key", "ANI", "Key+ANI"]
821
        rs = RadioSetting("ani_id_sidetone", "ANI id sidetone",
822
                          RadioSettingValueList(options,
823
                                        options[self._memobj.settings.ani_id_sidetone]))
824
        top.append(rs)
825
        # TODO tx_time_out_alert:4;
826
        options = ["Time", "Carrier", "Search"]
827
        rs = RadioSetting("scan_mode", "Scan mode",
828
                          RadioSettingValueList(options,
829
                                        options[self._memobj.settings.scan_mode]))
830
        top.append(rs)
831
        rs = RadioSetting("kbd_lock", "Keyboard lock",
832
                          RadioSettingValueBoolean(self._memobj.settings.kbd_lock))
833
        top.append(rs)
834
        rs = RadioSetting("auto_lock_kbd", "Auto lock keyboard",
835
                          RadioSettingValueBoolean(self._memobj.settings.auto_lock_kbd))
836
        top.append(rs)
837
        rs = RadioSetting("auto_backlight", "Auto backlight",
838
                          RadioSettingValueBoolean(self._memobj.settings.auto_backlight))
839
        top.append(rs)
840
        options = ["CH A", "CH B"]
841
        rs = RadioSetting("sos_ch", "SOS CH",
842
                          RadioSettingValueList(options,
843
                                        options[self._memobj.settings.sos_ch]))
844
        top.append(rs)
845
        rs = RadioSetting("stopwatch", "Stopwatch",
846
                          RadioSettingValueBoolean(self._memobj.settings.stopwatch))
847
        top.append(rs)
848
        rs = RadioSetting("dual_band_receive", "Dual band receive",
849
                          RadioSettingValueBoolean(self._memobj.settings.dual_band_receive))
850
        top.append(rs)
851
        options = ["VFO A", "VFO B"]
852
        rs = RadioSetting("current_vfo", "Current VFO",
853
                          RadioSettingValueList(options,
854
                                        options[self._memobj.settings.current_vfo]))
855
        top.append(rs)
856
        _pwd = self._memobj.settings.mode_password
857
        rs = RadioSetting("mode_password", "Mode password (000000 disabled)",
858
                  RadioSettingValueInteger(0, 9, _pwd[0]),
859
                  RadioSettingValueInteger(0, 9, _pwd[1]),
860
                  RadioSettingValueInteger(0, 9, _pwd[2]),
861
                  RadioSettingValueInteger(0, 9, _pwd[3]),
862
                  RadioSettingValueInteger(0, 9, _pwd[4]),
863
                  RadioSettingValueInteger(0, 9, _pwd[5]))
864
        top.append(rs)
865
        _pwd = self._memobj.settings.reset_password
866
        rs = RadioSetting("reset_password", "Reset password (000000 disabled)",
867
                  RadioSettingValueInteger(0, 9, _pwd[0]),
868
                  RadioSettingValueInteger(0, 9, _pwd[1]),
869
                  RadioSettingValueInteger(0, 9, _pwd[2]),
870
                  RadioSettingValueInteger(0, 9, _pwd[3]),
871
                  RadioSettingValueInteger(0, 9, _pwd[4]),
872
                  RadioSettingValueInteger(0, 9, _pwd[5]))
873
        top.append(rs)
874
        try:
875
            _ani = self._memobj.settings.ani_id_content
876
            rs = RadioSetting("ani_id_content", "ANI Code",
877
                              RadioSettingValueInteger(0, 9, _ani[0]),
878
                              RadioSettingValueInteger(0, 9, _ani[1]),
879
                              RadioSettingValueInteger(0, 9, _ani[2]),
880
                              RadioSettingValueInteger(0, 9, _ani[3]),
881
                              RadioSettingValueInteger(0, 9, _ani[4]),
882
                              RadioSettingValueInteger(0, 9, _ani[5]))
883
            top.append(rs)
884
        except Exception:
885
            print ("Your ANI code is not five digits, which is not currently"
886
                   " supported in CHIRP.")            
887

    
888
        return top
889

    
890
    @classmethod
891
    def match_model(cls, filedata, filename):
892
        if len(filedata) == 8192 and \
893
                filedata[0x1f77:0x1f7d] == "WELCOM":
894
            return True
895
        return False
896

    
897
@directory.register
898
class KG816Radio(KGUVD1PRadio,
899
        chirp_common.ExperimentalRadio):
900
    """Wouxun KG-816"""
901
    MODEL = "KG-816"
902

    
903
    _MEM_FORMAT = """
904
        #seekto 0x0010;
905
        struct {
906
          lbcd rx_freq[4];
907
          lbcd tx_freq[4];
908
          ul16 rx_tone;
909
          ul16 tx_tone;
910
          u8 _3_unknown_1:4,
911
             bcl:1,
912
             _3_unknown_2:3;
913
          u8 splitdup:1,
914
             skip:1,
915
             power_high:1,
916
             iswide:1,
917
             _2_unknown_2:4;
918
          u8 unknown[2];
919
        } memory[199];
920
        
921
        #seekto 0x0d70;
922
        struct {
923
            u16 vhf_rx_start;
924
            u16 vhf_rx_stop;
925
            u16 uhf_rx_start;
926
            u16 uhf_rx_stop;
927
            u16 vhf_tx_start;
928
            u16 vhf_tx_stop;
929
            u16 uhf_tx_start;
930
            u16 uhf_tx_stop;
931
        } freq_ranges;
932

    
933
        #seekto 0x1010;
934
        struct {
935
		u8 name[6];
936
		u8 pad[10];
937
        } names[199];
938
	
939
    """
940

    
941
    @classmethod
942
    def get_experimental_warning(cls):
943
        return ('We have not that much information on this model '
944
                'up to now we only know it has the same memory '
945
                'organization of KGUVD1 but uses 199 memories. '
946
                'it has been reported to work but '
947
                'proceed at your own risk!')
948
    
949
    def get_features(self):
950
        rf = KGUVD1PRadio.get_features(self)
951
        rf.memory_bounds = (1, 199) # this is the only known difference
952
        return rf
953

    
954
    def get_settings(self):
955
        freqranges = RadioSettingGroup("freqranges", "Freq ranges (read only)")
956
        top = RadioSettingGroup("top", "All Settings", freqranges)
957

    
958
        rs = RadioSetting("vhf_rx_start", "vhf rx start",
959
                          RadioSettingValueInteger(66, 520,
960
                                decode_freq(
961
                                    self._memobj.freq_ranges.vhf_rx_start)))
962
        freqranges.append(rs)
963
        rs = RadioSetting("vhf_rx_stop", "vhf rx stop",
964
                          RadioSettingValueInteger(66, 520,
965
                                decode_freq(
966
                                    self._memobj.freq_ranges.vhf_rx_stop)))
967
        freqranges.append(rs)
968
        rs = RadioSetting("uhf_rx_start", "uhf rx start",
969
                          RadioSettingValueInteger(66, 520,
970
                                decode_freq(
971
                                    self._memobj.freq_ranges.uhf_rx_start)))
972
        freqranges.append(rs)
973
        rs = RadioSetting("uhf_rx_stop", "uhf rx stop",
974
                          RadioSettingValueInteger(66, 520,
975
                                decode_freq(
976
                                    self._memobj.freq_ranges.uhf_rx_stop)))
977
        freqranges.append(rs)
978
        rs = RadioSetting("vhf_tx_start", "vhf tx start",
979
                          RadioSettingValueInteger(66, 520,
980
                                decode_freq(
981
                                    self._memobj.freq_ranges.vhf_tx_start)))
982
        freqranges.append(rs)
983
        rs = RadioSetting("vhf_tx_stop", "vhf tx stop",
984
                          RadioSettingValueInteger(66, 520,
985
                                decode_freq(
986
                                    self._memobj.freq_ranges.vhf_tx_stop)))
987
        freqranges.append(rs)
988
        rs = RadioSetting("uhf_tx_start", "uhf tx start",
989
                          RadioSettingValueInteger(66, 520,
990
                                decode_freq(
991
                                    self._memobj.freq_ranges.uhf_tx_start)))
992
        freqranges.append(rs)
993
        rs = RadioSetting("uhf_tx_stop", "uhf tx stop",
994
                          RadioSettingValueInteger(66, 520,
995
                                decode_freq(
996
                                    self._memobj.freq_ranges.uhf_tx_stop)))
997
        freqranges.append(rs)
998
        
999
        # tell the decoded ranges to UI
1000
        self.valid_freq = [
1001
            ( decode_freq(self._memobj.freq_ranges.vhf_rx_start) * 1000000, 
1002
             (decode_freq(self._memobj.freq_ranges.vhf_rx_stop)+1) * 1000000)]
1003

    
1004
        return top
1005

    
1006
    @classmethod
1007
    def match_model(cls, filedata, filename):
1008
        if len(filedata) == 8192 and \
1009
                filedata[0x60:0x64] != "2009" and \
1010
                filedata[0x1f77:0x1f7d] == "\xff\xff\xff\xff\xff\xff" and \
1011
                filedata[0x0d70:0x0d80] != "\xff\xff\xff\xff\xff\xff\xff\xff" \
1012
                                           "\xff\xff\xff\xff\xff\xff\xff\xff": 
1013
            return True
1014
        return False
1015

    
1016

    
1017
@directory.register
1018
class KG818Radio(KG816Radio):
1019
    """Wouxun KG-818"""
1020
    MODEL = "KG-818"
(2-2/2)