retevis_rt23_beta_20170502.py

Jim Unroe, 05/02/2017 06:43 pm

Download (28.6 kB)

 
1
# Copyright 2016 Jim Unroe <rock.unroe@gmail.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 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 time
17
import os
18
import struct
19
import re
20
import logging
21

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

    
29
LOG = logging.getLogger(__name__)
30

    
31
MEM_FORMAT = """
32
struct memory {
33
  lbcd rxfreq[4];
34
  lbcd txfreq[4];
35
  lbcd rxtone[2];
36
  lbcd txtone[2];
37
  u8 unknown1;
38
  u8 pttid:2,     // PTT-ID
39
     unknown2:1,
40
     signaling:1, // Signaling(ANI)
41
     unknown3:1,
42
     bcl:1,       // Busy Channel Lockout
43
     unknown4:2;
44
  u8 unknown5:3,
45
     highpower:1, // Power Level
46
     isnarrow:1,  // Bandwidth
47
     scan:1,      // Scan Add
48
     unknown6:2;
49
  u8 unknown7;
50
};
51

    
52
#seekto 0x0010;
53
struct memory channels[128];
54

    
55
#seekto 0x0810;
56
struct memory vfo_a;
57
struct memory vfo_b;
58

    
59
#seekto 0x0830;
60
struct {
61
  u8 unknown_0830_1:4,      // 0x0830
62
     color:2,               // Background Color
63
     dst:1,                 // DTMF Side Tone
64
     txsel:1;               // Priority TX Channel Select
65
  u8 scans:2,               // Scan Mode - 0x0831
66
     unknown_0831:1,
67
     autolk:1,              // Auto Key Lock
68
     save:1,                // Battery Save
69
     beep:1,                // Key Beep
70
     voice:2;               // Voice Prompt
71
  u8 vfomr_fm:1,            // FM Radio Display Mode - 0x0832
72
     led:2,                 // Background Light
73
     unknown_0832_2:1,
74
     dw:1,                  // FM Radio Dual Watch
75
     name:1,                // Display Names
76
     vfomr_a:2;             // Display Mode A
77
  u8 opnset:2,              // Power On Message - 0x0833
78
     unknown_0833_1:3,
79
     dwait:1,               // Dual Standby
80
     vfomr_b:2;             // Display Mode B
81
  u8 mrcha;                 // mr a ch num - 0x0834
82
  u8 mrchb;                 // mr b ch num - 0x0835
83
  u8 fmch;                  // fm radio ch num - 0x0836
84
  u8 unknown_0837_1:1,      // 0x0837
85
     ste:1,                 // Squelch Tail Eliminate
86
     roger:1,               // Roger Beep
87
     unknown_0837_2:1,
88
     vox:4;                 // VOX
89
  u8 step:4,                // Step - 0x0838
90
     unknown_0838_1:4;
91
  u8 squelch;               // Squelch - 0x0839
92
  u8 tot;                   // Time Out Timer - 0x083A
93
  u8 rptmod:1,              // Repeater Mode - 0x083B
94
     volmod:2,              // Volume Mode
95
     rptptt:1,              // Repeater PTT Switch
96
     rptspk:1,              // Repeater Speaker
97
     relay:3;               // Cross Band Repeater Enable
98
  u8 unknown_083C:4,        // 0x083C
99
     rptrl:4;               // Repeater TX Delay
100
  u8 pf1:4,                 // Function Key 1 - 0x083D
101
     pf2:4;                 // Function Key 2
102
  u8 vot;                   // VOX Delay Time - 0x083E
103
} settings;
104

    
105
#seekto 0x0848;
106
struct {
107
  char line1[7];
108
} poweron_msg;
109

    
110
struct limit {
111
  bbcd lower[2];
112
  bbcd upper[2];
113
};
114

    
115
#seekto 0x0850;
116
struct {
117
  struct limit vhf;
118
  struct limit uhf;
119
} limits;
120

    
121
struct fmmemory {
122
  lbcd fmfreq[4];
123
};
124

    
125
#seekto 0x085E;
126
struct fmmemory fmchannels[25];
127
struct fmmemory fmvfo;
128

    
129
#seekto 0x08D0;
130
struct {
131
  char name[7];             // Channel Names
132
  u8 unknown2[1];
133
} names[128];
134

    
135
#seekto 0x0D20;
136
u8 usedflags[16];
137
u8 scanflags[16];
138

    
139
#seekto 0x0E50;
140
struct memory rpt_vfo;
141
struct memory rpt_1;
142
struct memory rpt_2;
143
struct memory rpt_3;
144
struct memory rpt_4;
145
struct memory rpt_5;
146

    
147
#seekto 0x0EB0;
148
struct {
149
    u8 unknown:2,
150
       rpt_5_enable:1,
151
       rpt_4_enable:1,
152
       rpt_3_enable:1,
153
       rpt_2_enable:1,
154
       rpt_1_enable:1,
155
       rpt_vfo_enable:1;
156
} special_enables;
157

    
158
#seekto 0x0FA0;
159
struct {
160
  u8 unknown_0FA0_1:4,
161
     dispab:1,              // select a/b
162
     unknown_0FA0_2:3;
163
} settings2;
164
"""
165

    
166
CMD_ACK = "\x06"
167
BLOCK_SIZE = 0x10
168

    
169
RT23_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00),
170
                     chirp_common.PowerLevel("High", watts=2.50)]
171

    
172

    
173
RT23_DTCS = sorted(chirp_common.DTCS_CODES + 
174
                   [17, 50, 55, 135, 217, 254, 305, 645, 765])
175

    
176
RT23_CHARSET = chirp_common.CHARSET_UPPER_NUMERIC + \
177
    ":;<=>?@ !\"#$%&'()*+,-./"
178

    
179
LIST_SIGNALING = ["No", "DTMF"]
180
LIST_STEP = ["2.50K", "5.00K", "6.25K", "10.00K", "12,50K", "20.00K", "25.00K", 
181
             "50.00K"]
182
####LIST_OFFON = ["OFF", "ON"]
183
LIST_VOX = ["OFF"] + ["%s" % x for x in range(1, 6)]
184
LIST_TOT = ["OFF"] + ["%s seconds" % x for x in range(30, 300, 30)]
185
LIST_COLOR = ["Blue", "Orange", "Purple"]
186
LIST_VOT = ["0.5S", "1.0S", "1.5S", "2.0S", "3.0S"]
187
LIST_LED = ["Off", "On", "Auto"]
188
LIST_VOICE = ["Off", "Chinese", "English"]
189
LIST_OPNSET = ["Full", "Voltage", "Message"]
190
LIST_SCANS = ["Time Operated", "Carrier Operated", "Search"]
191

    
192
LIST_RPTRL = ["0.5S", "1.0S", "1.5S", "2.0S", "2.5S", "3.0S", "3.5S", "4.0S", 
193
              "4.5S"]
194
LIST_RPTMOD = ["Single", "Double"]
195
LIST_VOLMOD = ["Off", "Sub", "Main"]
196
LIST_TXSEL = ["Edit", "Busy"]
197

    
198
LIST_VFOMR = ["VFO", "MR(Frequency)", "MR(Channel #/Name)"]
199
LIST_VFOMRFM = ["VFO", "Channel"]
200

    
201
LIST_PTTID = ["Off", "BOT", "EOT", "Both"]
202

    
203
LIST_PFKEY = [
204
    "Radio", 
205
    "Sub-channel Sent", 
206
    "Scan", 
207
    "Alarm", 
208
    "DTMF", 
209
    "Squelch Off Momentarily", 
210
    "Battery Power Indicator",
211
    "Tone 1750",
212
    "Tone 2100",
213
    "Tone 1000",
214
    "Tone 1450"]
215

    
216
RT23_SPECIAL = ["rpt_vfo", "rpt_1", "rpt_2", "rpt_3", "rpt_4", "rpt_5",
217
                "vfo_a", "vfo_b"]
218

    
219

    
220
def _rt23_enter_programming_mode(radio):
221
    serial = radio.pipe
222

    
223
    #magic = "PROIUAM"
224
    try:
225
        serial.write("PROIUAM")
226
        #for j in range(0, len(magic)):
227
        #    time.sleep(0.002)
228
        #    serial.write(magic[j])
229
        ack = serial.read(1)
230
    except:
231
        raise errors.RadioError("Error communicating with radio")
232

    
233
    if not ack:
234
        raise errors.RadioError("No response from radio")
235
    elif ack != CMD_ACK:
236
        raise errors.RadioError("Radio refused to enter programming mode")
237

    
238
    try:
239
        serial.write("\x02")
240
        ident = serial.read(8)
241
    except:
242
        raise errors.RadioError("Error communicating with radio")
243

    
244
    if not ident.startswith("P31183"):
245
        LOG.debug(util.hexprint(ident))
246
        raise errors.RadioError("Radio returned unknown identification string")
247

    
248
    try:
249
        serial.write(CMD_ACK)
250
        ack = serial.read(1)
251
    except:
252
        raise errors.RadioError("Error communicating with radio")
253

    
254
    if ack != CMD_ACK:
255
        raise errors.RadioError("Radio refused to enter programming mode")
256

    
257

    
258
def _rt23_exit_programming_mode(radio):
259
    serial = radio.pipe
260
    try:
261
        serial.write("E")
262
    except:
263
        raise errors.RadioError("Radio refused to exit programming mode")
264

    
265

    
266
def _rt23_read_block(radio, block_addr, block_size):
267
    serial = radio.pipe
268

    
269
    cmd = struct.pack(">cHb", 'R', block_addr, BLOCK_SIZE)
270
    expectedresponse = "W" + cmd[1:]
271
    LOG.debug("Reading block %04x..." % (block_addr))
272

    
273
    try:
274
        serial.write(cmd)
275
        response = serial.read(4 + BLOCK_SIZE + 1)
276
        if response[:4] != expectedresponse:
277
            raise Exception("Error reading block %04x." % (block_addr))
278

    
279
        chunk = response[4:]
280

    
281
        cs = 0
282
        for byte in chunk[:-1]:
283
            cs += ord(byte)
284
        if ord(chunk[-1]) != (cs & 0xFF):
285
            raise Exception("Block failed checksum!")
286

    
287
        block_data = chunk[:-1]
288
    except:
289
        raise errors.RadioError("Failed to read block at %04x" % block_addr)
290

    
291
    return block_data
292

    
293

    
294
def _rt23_write_block(radio, block_addr, block_size):
295
    serial = radio.pipe
296

    
297
    cmd = struct.pack(">cHb", 'W', block_addr, BLOCK_SIZE)
298
    data = radio.get_mmap()[block_addr:block_addr + BLOCK_SIZE]
299
    cs = 0
300
    for byte in data:
301
        cs += ord(byte)
302
    data += chr(cs & 0xFF)
303

    
304
    LOG.debug("Writing Data:")
305
    LOG.debug(util.hexprint(cmd + data))
306

    
307
    try:
308
        #serial.write(cmd + data)
309
        for j in range(0, len(cmd)):
310
            time.sleep(0.002)
311
            serial.write(cmd[j])
312
        for j in range(0, len(data)):
313
            time.sleep(0.002)
314
            serial.write(data[j])
315
        if serial.read(1) != CMD_ACK:
316
            raise Exception("No ACK")
317
    except:
318
        raise errors.RadioError("Failed to send block "
319
                                "to radio at %04x" % block_addr)
320

    
321

    
322
def do_download(radio):
323
    LOG.debug("download")
324
    _rt23_enter_programming_mode(radio)
325

    
326
    data = ""
327

    
328
    status = chirp_common.Status()
329
    status.msg = "Cloning from radio"
330

    
331
    status.cur = 0
332
    status.max = radio._memsize
333

    
334
    for addr in range(0, radio._memsize, BLOCK_SIZE):
335
        status.cur = addr + BLOCK_SIZE
336
        radio.status_fn(status)
337

    
338
        ####print "Address: %s" % addr
339
        block = _rt23_read_block(radio, addr, BLOCK_SIZE)
340
        if addr == 0 and block.startswith("\xFF" * 6):
341
            block = "P31183" + "\xFF" * 10
342
        data += block
343

    
344
        LOG.debug("Address: %04x" % addr)
345
        LOG.debug(util.hexprint(block))
346

    
347
    ####data += radio.MODEL.ljust(8)
348

    
349
    _rt23_exit_programming_mode(radio)
350

    
351
    return memmap.MemoryMap(data)
352

    
353

    
354
def do_upload(radio):
355
    status = chirp_common.Status()
356
    status.msg = "Uploading to radio"
357

    
358
    _rt23_enter_programming_mode(radio)
359

    
360
    status.cur = 0
361
    status.max = radio._memsize
362

    
363
    for start_addr, end_addr in radio._ranges:
364
        for addr in range(start_addr, end_addr, BLOCK_SIZE):
365
            status.cur = addr + BLOCK_SIZE
366
            radio.status_fn(status)
367
            _rt23_write_block(radio, addr, BLOCK_SIZE)
368

    
369
    ####_rt23_exit_programming_mode(radio)
370

    
371

    
372
def model_match(cls, data):
373
    """Match the opened/downloaded image to the correct version"""
374

    
375
    if len(data) == 0x1000:
376
        rid = data[0x0000:0x0006]
377
        return rid == "P31183"
378
    else:
379
        return False
380

    
381

    
382
@directory.register
383
class RT23Radio(chirp_common.CloneModeRadio):
384
    """RETEVIS RT23"""
385
    VENDOR = "Retevis"
386
    MODEL = "RT23"
387
    BAUD_RATE = 9600
388

    
389
    _ranges = [
390
               (0x0000, 0x0EC0),
391
              ]
392
    _memsize = 0x1000
393

    
394
    def get_features(self):
395
        rf = chirp_common.RadioFeatures()
396
        rf.has_settings = True
397
        rf.has_bank = False
398
        rf.has_ctone = True
399
        rf.has_cross = True
400
        rf.has_rx_dtcs = True
401
        rf.has_tuning_step = False
402
        rf.can_odd_split = True
403
        rf.valid_name_length = 7
404
        rf.valid_characters = RT23_CHARSET
405
        rf.has_name = True
406
        rf.valid_skips = ["", "S"]
407
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
408
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
409
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
410
        rf.valid_power_levels = RT23_POWER_LEVELS
411
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
412
        rf.valid_modes = ["FM", "NFM"]  # 25 KHz, 12.5 KHz.
413
        rf.memory_bounds = (1, 128)
414
        rf.valid_bands = [
415
            (136000000, 174000000),
416
            (400000000, 480000000)]
417
        rf.valid_special_chans = RT23_SPECIAL
418

    
419
        return rf
420

    
421
    def process_mmap(self):
422
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
423

    
424
    def sync_in(self):
425
        self._mmap = do_download(self)
426
        self.process_mmap()
427

    
428
    def sync_out(self):
429
        do_upload(self)
430

    
431
    def get_raw_memory(self, number):
432
        return repr(self._memobj.memory[number - 1])
433

    
434
    def decode_tone(self, val):
435
        """Parse the tone data to decode from mem, it returns:
436
        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
437
        if val.get_raw() == "\xFF\xFF":
438
            return '', None, None
439

    
440
        val = int(val)
441
        if val >= 12000:
442
            a = val - 12000
443
            return 'DTCS', a, 'R'
444
        elif val >= 8000:
445
            a = val - 8000
446
            return 'DTCS', a, 'N'
447
        else:
448
            a = val / 10.0
449
            return 'Tone', a, None
450

    
451
    def encode_tone(self, memval, mode, value, pol):
452
        """Parse the tone data to encode from UI to mem"""
453
        if mode == '':
454
            memval[0].set_raw(0xFF)
455
            memval[1].set_raw(0xFF)
456
        elif mode == 'Tone':
457
            memval.set_value(int(value * 10))
458
        elif mode == 'DTCS':
459
            flag = 0x80 if pol == 'N' else 0xC0
460
            memval.set_value(value)
461
            memval[1].set_bits(flag)
462
        else:
463
            raise Exception("Internal error: invalid mode `%s'" % mode)
464

    
465
    def get_memory(self, number):
466
        mem = chirp_common.Memory()
467
        if isinstance(number, str):
468
            # special channel
469
            _mem = getattr(self._memobj, number)
470
            _nam = None
471
            mem.number = - len(RT23_SPECIAL) + RT23_SPECIAL.index(number)
472
            mem.extd_number = number
473
            _scn = None
474
            _usd = None
475
            if re.match('^rpt', mem.extd_number):
476
                _special_enables = self._memobj.special_enables
477
                isused = getattr(_special_enables,
478
                                 mem.extd_number + "_enable")
479
            else:
480
                isused = True
481
            isscan = True
482
        else:
483
            # regular memory
484
            _mem = self._memobj.channels[number-1]
485
            _nam = self._memobj.names[number - 1]
486
            mem.number = number
487
            bitpos = (1 << ((number - 1) % 8))
488
            bytepos = ((number - 1) / 8)
489
            _scn = self._memobj.scanflags[bytepos]
490
            _usd = self._memobj.usedflags[bytepos]
491
            isused = bitpos & int(_usd)
492
            isscan = bitpos & int(_scn)
493

    
494
        if not isused:
495
            mem.empty = True
496
            return mem
497

    
498
        mem.freq = int(_mem.rxfreq) * 10
499

    
500
        # We'll consider any blank (i.e. 0MHz frequency) to be empty
501
        if mem.freq == 0:
502
            mem.empty = True
503
            return mem
504

    
505
        if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF":
506
            mem.empty = True
507
            return mem
508

    
509
        if _mem.get_raw() == ("\xFF" * 16):
510
            LOG.debug("Initializing empty memory")
511
            _mem.set_raw("\x00" * 16)
512

    
513
        if int(_mem.rxfreq) == int(_mem.txfreq):
514
            mem.duplex = ""
515
            mem.offset = 0
516
        else:
517
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
518
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
519

    
520
        if _nam:
521
            for char in _nam.name:
522
                if str(char) == "\xFF":
523
                    char = " "
524
                mem.name += str(char)
525
            mem.name = mem.name.rstrip()
526

    
527
        mem.mode = _mem.isnarrow and "NFM" or "FM"
528

    
529
        rxtone = txtone = None
530
        txtone = self.decode_tone(_mem.txtone)
531
        rxtone = self.decode_tone(_mem.rxtone)
532
        chirp_common.split_tone_decode(mem, txtone, rxtone)
533

    
534
        mem.power = RT23_POWER_LEVELS[_mem.highpower]
535

    
536
        if _scn:
537
            if not isscan:
538
                mem.skip = "S"
539

    
540
        mem.extra = RadioSettingGroup("Extra", "extra")
541

    
542
        rs = RadioSetting("bcl", "BCL",
543
                          RadioSettingValueBoolean(_mem.bcl))
544
        mem.extra.append(rs)
545

    
546
        rs = RadioSetting("pttid", "PTT ID",
547
                          RadioSettingValueList(
548
                              LIST_PTTID, LIST_PTTID[_mem.pttid]))
549
        mem.extra.append(rs)
550

    
551
        rs = RadioSetting("signaling", "Optional Signaling",
552
                          RadioSettingValueList(LIST_SIGNALING,
553
                              LIST_SIGNALING[_mem.signaling]))
554
        mem.extra.append(rs)
555

    
556
        return mem
557

    
558
    def set_memory(self, mem):
559
        LOG.debug("Setting %i(%s)" % (mem.number, mem.extd_number))
560
        if mem.number < 0:
561
            # special channels
562
            val = str(RT23_SPECIAL[mem.number + len(RT23_SPECIAL)])
563
            _mem = getattr(self._memobj, val)
564
            #_nam = None
565
            #_scn = None
566
            #_usd = None
567
            if re.match('^rpt', val):
568
                _special_enables = self._memobj.special_enables
569
                setattr(_special_enables, val + "_enable", True)
570
        else:
571
            _mem = self._memobj.channels[mem.number - 1]
572
            _nam = self._memobj.names[mem.number - 1]
573
            bitpos = (1 << ((mem.number - 1) % 8))
574
            bytepos = ((mem.number - 1) / 8)
575
            _scn = self._memobj.scanflags[bytepos]
576
            _usd = self._memobj.usedflags[bytepos]
577

    
578
        if mem.empty:
579
            _mem.set_raw("\xFF" * 16)
580
            if mem.number < 0:
581
                val = str(RT23_SPECIAL[mem.number + 6])
582
                _special_enables = self._memobj.special_enables
583
                setattr(_special_enables, val + "_enable", False)
584
                return
585
            else:
586
                _nam.name = ("\xFF" * 7)
587
                _usd &= ~bitpos
588
                _scn &= ~bitpos
589
                return
590
        else:
591
            if mem.number < 0:
592
                val = str(RT23_SPECIAL[mem.number + 6])
593
                _special_enables = self._memobj.special_enables
594
                setattr(_special_enables, val + "_enable", True)
595
            else:
596
                _usd |= bitpos
597

    
598
        if _mem.get_raw() == ("\xFF" * 16):
599
            LOG.debug("Initializing empty memory")
600
            _mem.set_raw("\x00" * 16)
601
            if _scn:
602
                _scn |= bitpos
603

    
604
        ##_mem.set_raw("\x00" * 16)
605

    
606
        _mem.rxfreq = mem.freq / 10
607

    
608
        if mem.duplex == "off":
609
            for i in range(0, 4):
610
                _mem.txfreq[i].set_raw("\xFF")
611
        elif mem.duplex == "split":
612
            _mem.txfreq = mem.offset / 10
613
        elif mem.duplex == "+":
614
            _mem.txfreq = (mem.freq + mem.offset) / 10
615
        elif mem.duplex == "-":
616
            _mem.txfreq = (mem.freq - mem.offset) / 10
617
        else:
618
            _mem.txfreq = mem.freq / 10
619

    
620
        if mem.number >= 0:
621
            _namelength = self.get_features().valid_name_length
622
            for i in range(_namelength):
623
                try:
624
                    _nam.name[i] = mem.name[i]
625
                except IndexError:
626
                    _nam.name[i] = "\xFF"
627

    
628
            ####_mem.scan = mem.skip != "S"
629
            if mem.skip == "S":
630
                _scn &= ~bitpos
631
            else:
632
                _scn |= bitpos
633
        _mem.isnarrow = mem.mode == "NFM"
634

    
635
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
636
            chirp_common.split_tone_encode(mem)
637
        self.encode_tone(_mem.txtone, txmode, txtone, txpol)
638
        self.encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
639

    
640
        _mem.highpower = mem.power == RT23_POWER_LEVELS[1]
641

    
642
        for setting in mem.extra:
643
            setattr(_mem, setting.get_name(), setting.value)
644

    
645
    def get_settings(self):
646
        ####_keys = self._memobj.keys
647
        _settings = self._memobj.settings
648
        _mem = self._memobj
649
        basic = RadioSettingGroup("basic", "Basic Settings")
650
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
651
        other = RadioSettingGroup("other", "Other Settings")
652
        workmode = RadioSettingGroup("workmode", "Workmode Settings")
653
        fmradio = RadioSettingGroup("fmradio", "FM Radio Settings")
654
        top = RadioSettings(basic, advanced, other, workmode, fmradio)
655

    
656
        #step = RadioSetting("step", "Step", RadioSettingValueList(
657
        #                    LIST_STEP, LIST_STEP[_settings.step]))
658
        #basic.append(step)
659

    
660
        save = RadioSetting("save", "Battery Saver",
661
                            RadioSettingValueBoolean(_settings.save))
662
        basic.append(save)
663

    
664
        vox = RadioSetting("vox", "VOX Gain",
665
                           RadioSettingValueList(
666
                               LIST_VOX, LIST_VOX[_settings.vox]))
667
        basic.append(vox)
668

    
669
        squelch = RadioSetting("squelch", "Squelch Level",
670
                               RadioSettingValueInteger(
671
                                   0, 9, _settings.squelch))
672
        basic.append(squelch)
673

    
674
        relay = RadioSetting("relay", "Repeater",
675
                             RadioSettingValueBoolean(_settings.relay))
676
        basic.append(relay)
677

    
678
        tot = RadioSetting("tot", "Time-out timer", RadioSettingValueList(
679
                           LIST_TOT, LIST_TOT[_settings.tot]))
680
        basic.append(tot)
681

    
682
        beep = RadioSetting("beep", "Key Beep",
683
                            RadioSettingValueBoolean(_settings.beep))
684
        basic.append(beep)
685

    
686
        color = RadioSetting("color", "Background Color", RadioSettingValueList(
687
                             LIST_COLOR, LIST_COLOR[_settings.color - 1]))
688
        basic.append(color)
689

    
690
        vot = RadioSetting("vot", "VOX Delay Time", RadioSettingValueList(
691
                           LIST_VOT, LIST_VOT[_settings.vot]))
692
        basic.append(vot)
693

    
694
        dwait = RadioSetting("dwait", "Dual Standby",
695
                             RadioSettingValueBoolean(_settings.dwait))
696
        basic.append(dwait)
697

    
698
        led = RadioSetting("led", "Background Light", RadioSettingValueList(
699
                           LIST_LED, LIST_LED[_settings.led]))
700
        basic.append(led)
701

    
702
        voice = RadioSetting("voice", "Voice Prompt", RadioSettingValueList(
703
                             LIST_VOICE, LIST_VOICE[_settings.voice]))
704
        basic.append(voice)
705
        
706
        roger = RadioSetting("roger", "Roger Beep",
707
                             RadioSettingValueBoolean(_settings.roger))
708
        basic.append(roger)
709

    
710
        autolk = RadioSetting("autolk", "Auto Key Lock",
711
                              RadioSettingValueBoolean(_settings.autolk))
712
        basic.append(autolk)
713

    
714
        opnset = RadioSetting("opnset", "Power On Message",
715
                              RadioSettingValueList(
716
                                  LIST_OPNSET, LIST_OPNSET[_settings.opnset]))
717
        basic.append(opnset)
718

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

    
728
        _msg = self._memobj.poweron_msg
729
        ponmsg = RadioSetting("poweron_msg.line1", "Power-On Message 1",
730
                              RadioSettingValueString(
731
                                  0, 7, _filter(_msg.line1)))
732
        basic.append(ponmsg)
733

    
734

    
735
        scans = RadioSetting("scans", "Scan Mode", RadioSettingValueList(
736
                             LIST_SCANS, LIST_SCANS[_settings.scans]))
737
        basic.append(scans)
738

    
739
        dw = RadioSetting("dw", "FM Radio Dual Watch",
740
                          RadioSettingValueBoolean(_settings.dw))
741
        basic.append(dw)
742

    
743
        name = RadioSetting("name", "Display Names",
744
                            RadioSettingValueBoolean(_settings.name))
745
        basic.append(name)
746

    
747
        rptrl = RadioSetting("rptrl", "Repeater TX Delay", RadioSettingValueList(
748
                             LIST_RPTRL, LIST_RPTRL[_settings.rptrl]))
749
        basic.append(rptrl)
750

    
751
        rptspk = RadioSetting("rptspk", "Repeater Speaker",
752
                              RadioSettingValueBoolean(_settings.rptspk))
753
        basic.append(rptspk)
754

    
755
        rptptt = RadioSetting("rptptt", "Repeater PTT Switch",
756
                            RadioSettingValueBoolean(_settings.rptptt))
757
        basic.append(rptptt)
758

    
759
        rptmod = RadioSetting("rptmod", "Repeater Mode",
760
                              RadioSettingValueList(
761
                                  LIST_RPTMOD, LIST_RPTMOD[_settings.rptmod]))
762
        basic.append(rptmod)
763

    
764
        volmod = RadioSetting("volmod", "Volume Mode",
765
                              RadioSettingValueList(
766
                                  LIST_VOLMOD, LIST_VOLMOD[_settings.volmod]))
767
        basic.append(volmod)
768

    
769
        dst = RadioSetting("dst", "DTMF Side Tone",
770
                            RadioSettingValueBoolean(_settings.dst))
771
        basic.append(dst)
772

    
773
        txsel = RadioSetting("txsel", "Priority TX Channel",
774
                             RadioSettingValueList(
775
                                 LIST_TXSEL, LIST_TXSEL[_settings.txsel]))
776
        basic.append(txsel)
777

    
778
        ste = RadioSetting("ste", "Squelch Tail Eliminate",
779
                           RadioSettingValueBoolean(_settings.ste))
780
        basic.append(ste)
781

    
782
        #advanced
783
        if _settings.pf1 > 0x0A:
784
            val = 0x00
785
        else:
786
            val = _settings.pf1
787
        pf1 = RadioSetting("pf1", "PF1 Key",
788
                           RadioSettingValueList(
789
                               LIST_PFKEY, LIST_PFKEY[val]))
790
        advanced.append(pf1)
791

    
792
        if _settings.pf2 > 0x0A:
793
            val = 0x00
794
        else:
795
            val = _settings.pf2
796
        pf2 = RadioSetting("pf2", "PF2 Key",
797
                           RadioSettingValueList(
798
                               LIST_PFKEY, LIST_PFKEY[val]))
799
        advanced.append(pf2)
800

    
801
        # other
802
        _limit = str(int(_mem.limits.vhf.lower) / 10)
803
        val = RadioSettingValueString(0, 3, _limit)
804
        val.set_mutable(False)
805
        rs = RadioSetting("limits.vhf.lower", "VHF low", val)
806
        other.append(rs)
807

    
808
        _limit = str(int(_mem.limits.vhf.upper) / 10)
809
        val = RadioSettingValueString(0, 3, _limit)
810
        val.set_mutable(False)
811
        rs = RadioSetting("limits.vhf.upper", "VHF high", val)
812
        other.append(rs)
813

    
814
        _limit = str(int(_mem.limits.uhf.lower) / 10)
815
        val = RadioSettingValueString(0, 3, _limit)
816
        val.set_mutable(False)
817
        rs = RadioSetting("limits.uhf.lower", "UHF low", val)
818
        other.append(rs)
819

    
820
        _limit = str(int(_mem.limits.uhf.upper) / 10)
821
        val = RadioSettingValueString(0, 3, _limit)
822
        val.set_mutable(False)
823
        rs = RadioSetting("limits.uhf.upper", "UHF high", val)
824
        other.append(rs)
825

    
826
        #work mode
827
        vfomr_a = RadioSetting("vfomr_a", "Display Mode A",
828
                               RadioSettingValueList(
829
                                   LIST_VFOMR, LIST_VFOMR[_settings.vfomr_a]))
830
        workmode.append(vfomr_a)
831

    
832
        vfomr_b = RadioSetting("vfomr_b", "Display Mode B",
833
                               RadioSettingValueList(
834
                                   LIST_VFOMR, LIST_VFOMR[_settings.vfomr_b]))
835
        workmode.append(vfomr_b)
836

    
837
        mrcha = RadioSetting("mrcha", "Channel # A",
838
                             RadioSettingValueInteger(
839
                                 1, 128, _settings.mrcha))
840
        workmode.append(mrcha)
841

    
842
        mrchb = RadioSetting("mrchb", "Channel # B",
843
                             RadioSettingValueInteger(
844
                                 1, 128, _settings.mrchb))
845
        workmode.append(mrchb)
846

    
847
        #fm radio
848
        vfomr_fm = RadioSetting("vfomr_fm", "FM Radio Display Mode",
849
                                RadioSettingValueList(
850
                                    LIST_VFOMRFM, LIST_VFOMRFM[_settings.vfomr_fm]))
851
        fmradio.append(vfomr_fm)
852

    
853
        fmch = RadioSetting("fmch", "FM Radio Channel #",
854
                            RadioSettingValueInteger(
855
                                 1, 25, _settings.fmch))
856
        fmradio.append(fmch)
857

    
858
        return top
859

    
860
    def set_settings(self, settings):
861
        for element in settings:
862
            if not isinstance(element, RadioSetting):
863
                self.set_settings(element)
864
                continue
865
            else:
866
                try:
867
                    if "." in element.get_name():
868
                        bits = element.get_name().split(".")
869
                        obj = self._memobj
870
                        for bit in bits[:-1]:
871
                            obj = getattr(obj, bit)
872
                        setting = bits[-1]
873
                    else:
874
                        obj = self._memobj.settings
875
                        setting = element.get_name()
876

    
877
                    if element.has_apply_callback():
878
                        LOG.debug("Using apply callback")
879
                        element.run_apply_callback()
880
                    elif setting == "color":
881
                        setattr(obj, setting, int(element.value) + 1)
882
                    elif element.value.get_mutable():
883
                        LOG.debug("Setting %s = %s" % (setting, element.value))
884
                        setattr(obj, setting, element.value)
885
                except Exception, e:
886
                    LOG.debug(element.get_name())
887
                    raise
888

    
889
    @classmethod
890
    def match_model(cls, filedata, filename):
891
        match_size = False
892
        match_model = False
893

    
894
        # testing the file data size
895
        if len(filedata) in [0x1000, ]:
896
            match_size = True
897

    
898
        # testing the model fingerprint
899
        match_model = model_match(cls, filedata)
900

    
901
        if match_size and match_model:
902
            return True
903
        else:
904
            return False