Project

General

Profile

Feature #9939 » tg_uv2p_Jul11.py

Ran Katz, 07/11/2022 11:05 AM

 
1
# Copyright 2013 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 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
# This driver was derived from the:
17
# Quansheng TG-UV2 Utility by Mike Nix <mnix@wanm.com.au>
18
# (So thanks Mike!)
19

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

    
30
LOG = logging.getLogger(__name__)
31

    
32
mem_format = """
33
struct memory {
34
  bbcd freq[4];
35
  bbcd offset[4];
36
  u8 rxtone;
37
  u8 txtone;
38
  u8 unknown1:2,
39
     txtmode:2,
40
     unknown2:2,
41
     rxtmode:2;
42
  u8 duplex;
43
  u8 unknown3:3,
44
     isnarrow:1,
45
     unknown4:2,
46
     not_scramble:1,
47
     not_revfreq:1;
48
  u8 flag3;
49
  u8 step;
50
  u8 power;
51
};
52

    
53
struct bandflag {
54
    u8 scanadd:1,
55
        unknown1:3,
56
        band:4;
57
};
58

    
59
struct tguv2_config {
60
    u8 unknown1;
61
    u8 squelch;
62
    u8 time_out_timer;
63
    u8 priority_channel;
64

    
65
    u8 unknown2:7,
66
        keyunlocked:1;
67
    u8 busy_lockout;
68
    u8 vox;
69
    u8 unknown3;
70

    
71
    u8 beep_tone_disabled;
72
    u8 display;
73
    u8 step;
74
    u8 unknown4;
75

    
76
    u8 unknown5;
77
    u8 rxmode;
78
    u8 unknown6:7,
79
        not_end_tone_elim:1;
80
    u8 vfo_mode;
81
};
82

    
83
struct vfo {
84
    u8 current;
85
    u8 chan;
86
    u8 memno;
87
};
88

    
89
struct name {
90
  u8 name[6];
91
  u8 unknown1[10];
92
};
93

    
94
#seekto 0x0000;
95
char ident[32];
96
u8 blank[16];
97

    
98
struct memory channels[200];
99
struct memory bands[5];
100

    
101
#seekto 0x0D30;
102
struct bandflag bandflags[200];
103

    
104
#seekto 0x0E30;
105
struct tguv2_config settings;
106
struct vfo vfos[2];
107
u8 unk5;
108
u8 reserved2[9];
109
u8 band_restrict;
110
u8 txen350390;
111

    
112
#seekto 0x0F30;
113
struct name names[200];
114

    
115
"""
116

    
117

    
118
def do_ident(radio):
119
    radio.pipe.timeout = 3
120
    radio.pipe.stopbits = serial.STOPBITS_TWO
121
    radio.pipe.write("\x02PnOGdAM")
122
    for x in xrange(10):
123
        ack = radio.pipe.read(1)
124
        if ack == '\x06':
125
            break
126
    else:
127
        raise errors.RadioError("Radio did not ack programming mode")
128
    radio.pipe.write("\x40\x02")
129
    ident = radio.pipe.read(8)
130
    LOG.debug(util.hexprint(ident))
131
    if not ident.startswith('P5555'):
132
        raise errors.RadioError("Unsupported model")
133
    radio.pipe.write("\x06")
134
    ack = radio.pipe.read(1)
135
    if ack != "\x06":
136
        raise errors.RadioError("Radio did not ack ident")
137

    
138

    
139
def do_status(radio, direction, addr):
140
    status = chirp_common.Status()
141
    status.msg = "Cloning %s radio" % direction
142
    status.cur = addr
143
    status.max = 0x2000
144
    radio.status_fn(status)
145

    
146

    
147
def do_download(radio):
148
    do_ident(radio)
149
    data = "TG-UV2+ Radio Program Data v1.0\x00"
150
    data += ("\x00" * 16)
151

    
152
    firstack = None
153
    for i in range(0, 0x2000, 8):
154
        frame = struct.pack(">cHB", "R", i, 8)
155
        radio.pipe.write(frame)
156
        result = radio.pipe.read(12)
157
        if not (result[0] == "W" and frame[1:4] == result[1:4]):
158
            LOG.debug(util.hexprint(result))
159
            raise errors.RadioError("Invalid response for address 0x%04x" % i)
160
        radio.pipe.write("\x06")
161
        ack = radio.pipe.read(1)
162
        if not firstack:
163
            firstack = ack
164
        else:
165
            if not ack == firstack:
166
                LOG.debug("first ack: %s ack received: %s",
167
                          util.hexprint(firstack), util.hexprint(ack))
168
                raise errors.RadioError("Unexpected response")
169
        data += result[4:]
170
        do_status(radio, "from", i)
171

    
172
    return memmap.MemoryMap(data)
173

    
174

    
175
def do_upload(radio):
176
    do_ident(radio)
177
    data = radio._mmap[0x0030:]
178

    
179
    for i in range(0, 0x2000, 8):
180
        frame = struct.pack(">cHB", "W", i, 8)
181
        frame += data[i:i + 8]
182
        radio.pipe.write(frame)
183
        ack = radio.pipe.read(1)
184
        if ack != "\x06":
185
            LOG.debug("Radio NAK'd block at address 0x%04x" % i)
186
            raise errors.RadioError(
187
                    "Radio NAK'd block at address 0x%04x" % i)
188
        LOG.debug("Radio ACK'd block at address 0x%04x" % i)
189
        do_status(radio, "to", i)
190

    
191
DUPLEX = ["", "+", "-"]
192
TGUV2P_STEPS = [5, 6.25, 10, 12.5, 15, 20, 25, 30, 50, 100]
193
CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_|* +-"
194
POWER_LEVELS = [chirp_common.PowerLevel("High", watts=10),
195
                chirp_common.PowerLevel("Med", watts=5),
196
                chirp_common.PowerLevel("Low", watts=1)]
197
POWER_LEVELS_STR = ["High", "Med", "Low"]
198
VALID_BANDS = [(88000000, 108000000),
199
               (136000000, 174000000),
200
               (350000000, 390000000),
201
               (400000000, 470000000),
202
               (470000000, 520000000)]
203

    
204

    
205
@directory.register
206
class QuanshengTGUV2P(chirp_common.CloneModeRadio,
207
                      chirp_common.ExperimentalRadio):
208
    """Quansheng TG-UV2+"""
209
    VENDOR = "Quansheng"
210
    MODEL = "TG-UV2+"
211
    BAUD_RATE = 9600
212

    
213
    _memsize = 0x2000
214

    
215
    @classmethod
216
    def get_prompts(cls):
217
        rp = chirp_common.RadioPrompts()
218
        rp.experimental = \
219
            ('Experimental version for TG-UV2/2+ radios '
220
             'Proceed at your own risk!')
221
        rp.pre_download = _(dedent("""\
222
            1. Turn radio off.
223
            2. Connect cable to mic/spkr connector.
224
            3. Make sure connector is firmly connected.
225
            4. Turn radio on.
226
            5. Ensure that the radio is tuned to channel with no activity.
227
            6. Click OK to download image from device."""))
228
        rp.pre_upload = _(dedent("""\
229
            1. Turn radio off.
230
            2. Connect cable to mic/spkr connector.
231
            3. Make sure connector is firmly connected.
232
            4. Turn radio on.
233
            5. Ensure that the radio is tuned to channel with no activity.
234
            6. Click OK to upload image to device."""))
235
        return rp
236

    
237
    def get_features(self):
238
        rf = chirp_common.RadioFeatures()
239
        rf.has_settings = True
240
        rf.has_cross = True
241
        rf.has_rx_dtcs = True
242
        rf.has_dtcs_polarity = True
243
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
244
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
245
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
246
        rf.valid_duplexes = DUPLEX
247
        rf.can_odd_split = False
248
        rf.valid_skips = ["", "S"]
249
        rf.valid_characters = CHARSET
250
        rf.valid_name_length = 6
251
        rf.valid_tuning_steps = TGUV2P_STEPS
252
        rf.valid_bands = VALID_BANDS
253

    
254
        rf.valid_modes = ["FM", "NFM"]
255
        rf.valid_power_levels = POWER_LEVELS
256
        rf.has_ctone = True
257
        rf.has_bank = False
258
        rf.has_tuning_step = True
259
        rf.memory_bounds = (0, 199)
260
        return rf
261

    
262
    def sync_in(self):
263
        try:
264
            self._mmap = do_download(self)
265
        except errors.RadioError:
266
            raise
267
        except Exception, e:
268
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
269
        self.process_mmap()
270

    
271
    def sync_out(self):
272
        try:
273
            do_upload(self)
274
        except errors.RadioError:
275
            raise
276
        except Exception, e:
277
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
278

    
279
    def process_mmap(self):
280
        self._memobj = bitwise.parse(mem_format, self._mmap)
281

    
282
    def get_raw_memory(self, number):
283
        return repr(self._memobj.channels[number])
284

    
285
    def _decode_tone(self, _mem, which):
286
        def _get(field):
287
            return getattr(_mem, "%s%s" % (which, field))
288

    
289
        value = _get('tone')
290
        tmode = _get('tmode')
291

    
292
        if (value <= 104) and (tmode <= 3):
293
            if tmode == 0:
294
                mode = val = pol = None
295
            elif tmode == 1:
296
                mode = 'Tone'
297
                val = chirp_common.TONES[value]
298
                pol = None
299
            else:
300
                mode = 'DTCS'
301
                val = chirp_common.DTCS_CODES[value]
302
                pol = "N" if (tmode == 2) else "R"
303
        else:
304
            mode = val = pol = None
305

    
306
        return mode, val, pol
307

    
308
    def _encode_tone(self, _mem, which, mode, val, pol):
309
        def _set(field, value):
310
            setattr(_mem, "%s%s" % (which, field), value)
311

    
312
        if (mode == "Tone"):
313
            _set("tone", chirp_common.TONES.index(val))
314
            _set("tmode", 0x01)
315
        elif mode == "DTCS":
316
            _set("tone", chirp_common.DTCS_CODES.index(val))
317
            if pol == "N":
318
                _set("tmode", 0x02)
319
            else:
320
                _set("tmode", 0x03)
321
        else:
322
            _set("tone", 0)
323
            _set("tmode", 0)
324

    
325
    def _get_memobjs(self, number):
326
        if isinstance(number, str):
327
            return (getattr(self._memobj, number.lower()), None)
328

    
329
        else:
330
            return (self._memobj.channels[number],
331
                    self._memobj.bandflags[number],
332
                    self._memobj.names[number].name)
333

    
334
    def get_memory(self, number):
335
        _mem, _bf, _nam = self._get_memobjs(number)
336
        mem = chirp_common.Memory()
337
        if isinstance(number, str):
338
            mem.extd_number = number
339
        else:
340
            mem.number = number
341

    
342
        if (_mem.freq.get_raw()[0] == "\xFF") or (_bf.band == "\x0F"):
343
            mem.empty = True
344
            return mem
345

    
346
        mem.freq = int(_mem.freq) * 10
347

    
348
        if _mem.offset.get_raw()[0] == "\xFF":
349
            mem.offset = 0
350
        else:
351
            mem.offset = int(_mem.offset) * 10
352

    
353
        chirp_common.split_tone_decode(
354
            mem,
355
            self._decode_tone(_mem, "tx"),
356
            self._decode_tone(_mem, "rx"))
357

    
358
        if 'step' in _mem and _mem.step > len(TGUV2P_STEPS):
359
            _mem.step = 0x00
360
        mem.tuning_step = TGUV2P_STEPS[_mem.step]
361
        mem.duplex = DUPLEX[_mem.duplex]
362
        mem.mode = _mem.isnarrow and "NFM" or "FM"
363
        mem.skip = "" if bool(_bf.scanadd) else "S"
364
        mem.power = POWER_LEVELS[_mem.power]
365

    
366
        if _nam:
367
            for char in _nam:
368
                try:
369
                    mem.name += CHARSET[char]
370
                except IndexError:
371
                    break
372
            mem.name = mem.name.rstrip()
373

    
374
        mem.extra = RadioSettingGroup("Extra", "extra")
375

    
376
        rs = RadioSetting("not_scramble", "(not)SCRAMBLE",
377
                          RadioSettingValueBoolean(_mem.not_scramble))
378
        mem.extra.append(rs)
379

    
380
        rs = RadioSetting("not_revfreq", "(not)Reverse Duplex",
381
                          RadioSettingValueBoolean(_mem.not_revfreq))
382
        mem.extra.append(rs)
383

    
384
        return mem
385

    
386
    def set_memory(self, mem):
387
        _mem, _bf, _nam = self._get_memobjs(mem.number)
388

    
389
        _bf.set_raw("\xFF")
390

    
391
        if mem.empty:
392
            _mem.set_raw("\xFF" * 16)
393
            return
394

    
395
        _mem.set_raw("\x00" * 12 + "\xFF" * 2 + "\x00"*2)
396

    
397
        _bf.scanadd = int(mem.skip != "S")
398
        _bf.band = 0x0F
399
        for idx, ele in enumerate(VALID_BANDS):
400
            if mem.freq >= ele[0] and mem.freq <= ele[1]:
401
                _bf.band = idx
402

    
403
        _mem.freq = mem.freq / 10
404
        _mem.offset = mem.offset / 10
405

    
406
        tx, rx = chirp_common.split_tone_encode(mem)
407
        self._encode_tone(_mem, 'tx', *tx)
408
        self._encode_tone(_mem, 'rx', *rx)
409

    
410
        _mem.duplex = DUPLEX.index(mem.duplex)
411
        _mem.isnarrow = mem.mode == "NFM"
412
        _mem.step = TGUV2P_STEPS.index(mem.tuning_step)
413

    
414
        if mem.power is None:
415
            _mem.power = 0
416
        else:
417
            _mem.power = POWER_LEVELS.index(mem.power)
418

    
419
        if _nam:
420
            for i in range(0, 6):
421
                try:
422
                    _nam[i] = CHARSET.index(mem.name[i])
423
                except IndexError:
424
                    _nam[i] = 0xFF
425

    
426
        for setting in mem.extra:
427
            setattr(_mem, setting.get_name(), setting.value)
428

    
429
    def get_settings(self):
430
        _settings = self._memobj.settings
431
        _vfoa = self._memobj.vfos[0]
432
        _vfob = self._memobj.vfos[1]
433
        _bandsettings = self._memobj.bands
434

    
435
        cfg_grp = RadioSettingGroup("cfg_grp", "Configuration")
436
        vfoa_grp = RadioSettingGroup(
437
            "vfoa_grp", "VFO A Settings\n  (Current Status, Read Only)")
438
        vfob_grp = RadioSettingGroup(
439
            "vfob_grp", "VFO B Settings\n  (Current Status, Read Only)")
440

    
441
        group = RadioSettings(cfg_grp, vfoa_grp, vfob_grp)
442
        #
443
        # Configuration Settings
444
        #
445

    
446
        # TX time out timer:
447
        options = ["Off"] + ["%s min" % x for x in range(1, 10)]
448
        rs = RadioSetting("time_out_timer", "TX Time Out Timer",
449
                          RadioSettingValueList(
450
                              options, options[_settings.time_out_timer]))
451
        cfg_grp.append(rs)
452

    
453
        # Display mode
454
        options = ["Frequency", "Channel", "Name"]
455
        rs = RadioSetting("display", "Channel Display Mode",
456
                          RadioSettingValueList(
457
                              options, options[_settings.display]))
458
        cfg_grp.append(rs)
459

    
460
        # Squelch level
461
        rs = RadioSetting("squelch", "Squelch Level",
462
                          RadioSettingValueInteger(0, 9, _settings.squelch))
463
        cfg_grp.append(rs)
464

    
465
        # Vox level
466
        mem_vals = range(10)
467
        user_options = [str(x) for x in mem_vals]
468
        user_options[0] = "Off"
469
        options_map = zip(user_options, mem_vals)
470

    
471
        rs = RadioSetting("vox", "VOX Level",
472
                          RadioSettingValueMap(options_map, _settings.vox))
473
        cfg_grp.append(rs)
474

    
475
        # Keypad beep
476
        rs = RadioSetting("beep_tone_disabled", "Beep Prompt",
477
                          RadioSettingValueBoolean(
478
                               not _settings.beep_tone_disabled))
479
        cfg_grp.append(rs)
480

    
481
        # Dual watch/crossband
482
        options = ["Dual Watch", "CrossBand", "Normal"]
483
        if _settings.rxmode >= 2:
484
            _rxmode = 2
485
        else:
486
            _rxmode = _settings.rxmode
487
        rs = RadioSetting("rxmode", "Dual Watch/CrossBand Monitor",
488
                          RadioSettingValueList(
489
                            options, options[_rxmode]))
490
        cfg_grp.append(rs)
491

    
492
        # Busy chanel lock
493
        rs = RadioSetting("busy_lockout", "Busy Channel Lock",
494
                          RadioSettingValueBoolean(
495
                             not _settings.busy_lockout))
496
        cfg_grp.append(rs)
497

    
498
        # Keypad lock
499
        rs = RadioSetting("keyunlocked", "Keypad Lock",
500
                          RadioSettingValueBoolean(
501
                              not _settings.keyunlocked))
502
        cfg_grp.append(rs)
503

    
504
        # Priority channel
505
        mem_vals = range(200)
506
        user_options = [str(x) for x in mem_vals]
507
        mem_vals.insert(0, 0xFF)
508
        user_options.insert(0, "Not Set")
509
        options_map = zip(user_options, mem_vals)
510

    
511
        rs = RadioSetting("priority_channel", "Priority Channel",
512
                          RadioSettingValueMap(options_map,
513
                                               _settings.priority_channel))
514
        cfg_grp.append(rs)
515

    
516
        # Step
517
        mem_vals = range(0, len(TGUV2P_STEPS))
518
        mem_vals.append(0xFF)
519
        user_options = [(str(x) + " kHz") for x in TGUV2P_STEPS]
520
        user_options.append("Unknown")
521
        options_map = zip(user_options, mem_vals)
522

    
523
        rs = RadioSetting("step", "Current (VFO?) step size",
524
                          RadioSettingValueMap(options_map, _settings.step))
525
        cfg_grp.append(rs)
526

    
527
        # End (Tail) tone elimination
528
        mem_vals = [0, 1]
529
        user_options = ["Tone Elimination On", "Tone Elimination Off"]
530
        options_map = zip(user_options, mem_vals)
531

    
532
        rs = RadioSetting("not_end_tone_elim", "Tx End Tone Elimination",
533
                          RadioSettingValueMap(options_map,
534
                                               _settings.not_end_tone_elim))
535
        cfg_grp.append(rs)
536

    
537
        # VFO mode
538

    
539
        if _settings.vfo_mode >= 1:
540
            _vfo_mode = 0xFF
541
        else:
542
            _vfo_mode = _settings.vfo_mode
543
        mem_vals = [0xFF, 0]
544
        user_options = ["VFO Mode Enabled", "VFO Mode Disabled"]
545
        options_map = zip(user_options, mem_vals)
546

    
547
        rs = RadioSetting("vfo_mode", "VFO (CH only) mode",
548
                          RadioSettingValueMap(options_map, _vfo_mode))
549
        cfg_grp.append(rs)
550

    
551
        #
552
        # VFO Settings
553
        #
554

    
555
        vfo_groups = [vfoa_grp, vfob_grp]
556
        vfo_mem = [_vfoa, _vfob]
557
        vfo_lower = ["vfoa", "vfob"]
558
        vfo_upper = ["VFOA", "VFOB"]
559

    
560
        for idx, vfo_group in enumerate(vfo_groups):
561

    
562
            options = ["Channel", "Frequency"]
563
            tempvar = 0 if (vfo_mem[idx].current < 200) else 1
564
            rs = RadioSetting(vfo_lower[idx] + "_mode", vfo_upper[idx]+" Mode",
565
                              RadioSettingValueList(
566
                                  options, options[tempvar]))
567
            vfo_group.append(rs)
568

    
569
            if tempvar == 0:
570
                rs = RadioSetting(vfo_lower[idx] + "_ch",
571
                                  vfo_upper[idx] + " Channel",
572
                                  RadioSettingValueInteger(
573
                                      0, 199, vfo_mem[idx].current))
574
                vfo_group.append(rs)
575
            else:
576
                band_num = vfo_mem[idx].current - 200
577
                freq = int(_bandsettings[band_num].freq) * 10
578
                offset = int(_bandsettings[band_num].offset) * 10
579
                txtmode = _bandsettings[band_num].txtmode
580
                rxtmode = _bandsettings[band_num].rxtmode
581

    
582
                rs = RadioSetting(vfo_lower[idx] + "_freq",
583
                                  vfo_upper[idx] + " Frequency",
584
                                  RadioSettingValueFloat(
585
                                      0.0, 520.0, freq / 1000000.0,
586
                                      precision=6))
587
                vfo_group.append(rs)
588

    
589
                if offset > 70e6:
590
                    offset = 0
591
                rs = RadioSetting(vfo_lower[idx] + "_offset",
592
                                  vfo_upper[idx] + " Offset",
593
                                  RadioSettingValueFloat(
594
                                      0.0, 69.995, offset / 100000.0,
595
                                      resolution=0.005))
596
                vfo_group.append(rs)
597

    
598
                rs = RadioSetting(vfo_lower[idx] + "_duplex",
599
                                  vfo_upper[idx] + " Shift",
600
                                  RadioSettingValueList(
601
                                      DUPLEX,
602
                                      DUPLEX[_bandsettings[band_num].duplex]))
603
                vfo_group.append(rs)
604

    
605
                rs = RadioSetting(
606
                    vfo_lower[idx] + "_step",
607
                    vfo_upper[idx] + " Step",
608
                    RadioSettingValueFloat(
609
                        0.0, 1000.0,
610
                        TGUV2P_STEPS[_bandsettings[band_num].step],
611
                        resolution=0.25))
612
                vfo_group.append(rs)
613

    
614
                rs = RadioSetting(
615
                    vfo_lower[idx] + "_pwr",
616
                    vfo_upper[idx] + " Power",
617
                    RadioSettingValueList(
618
                        POWER_LEVELS_STR,
619
                        POWER_LEVELS_STR[_bandsettings[band_num].power]))
620
                vfo_group.append(rs)
621

    
622
                options = ["None", "Tone", "DTCS-N", "DTCS-I"]
623
                rs = RadioSetting(vfo_lower[idx] + "_ttmode",
624
                                  vfo_upper[idx]+" TX tone mode",
625
                                  RadioSettingValueList(
626
                                      options, options[txtmode]))
627
                vfo_group.append(rs)
628
                if txtmode == 1:
629
                    rs = RadioSetting(
630
                        vfo_lower[idx] + "_ttone",
631
                        vfo_upper[idx] + " TX tone",
632
                        RadioSettingValueFloat(
633
                            0.0, 1000.0,
634
                            chirp_common.TONES[_bandsettings[band_num].txtone],
635
                            resolution=0.1))
636
                    vfo_group.append(rs)
637
                elif txtmode >= 2:
638
                    txtone = _bandsettings[band_num].txtone
639
                    rs = RadioSetting(
640
                        vfo_lower[idx] + "_tdtcs",
641
                        vfo_upper[idx] + " TX DTCS",
642
                        RadioSettingValueInteger(
643
                            0, 1000, chirp_common.DTCS_CODES[txtone]))
644
                    vfo_group.append(rs)
645

    
646
                options = ["None", "Tone", "DTCS-N", "DTCS-I"]
647
                rs = RadioSetting(vfo_lower[idx] + "_rtmode",
648
                                  vfo_upper[idx] + " RX tone mode",
649
                                  RadioSettingValueList(options,
650
                                                        options[rxtmode]))
651
                vfo_group.append(rs)
652

    
653
                if rxtmode == 1:
654
                    rs = RadioSetting(
655
                        vfo_lower[idx] + "_rtone",
656
                        vfo_upper[idx] + " RX tone",
657
                        RadioSettingValueFloat(
658
                            0.0, 1000.0,
659
                            chirp_common.TONES[_bandsettings[band_num].rxtone],
660
                            resolution=0.1))
661
                    vfo_group.append(rs)
662
                elif rxtmode >= 2:
663
                    rxtone = _bandsettings[band_num].rxtone
664
                    rs = RadioSetting(vfo_lower[idx] + "_rdtcs",
665
                                      vfo_upper[idx] + " TX rTCS",
666
                                      RadioSettingValueInteger(
667
                                          0, 1000,
668
                                          chirp_common.DTCS_CODES[rxtone]))
669
                    vfo_group.append(rs)
670

    
671
                options = ["FM", "NFM"]
672
                rs = RadioSetting(
673
                    vfo_lower[idx] + "_fm",
674
                    vfo_upper[idx] + " FM BW ",
675
                    RadioSettingValueList(
676
                        options, options[_bandsettings[band_num].isnarrow]))
677
                vfo_group.append(rs)
678

    
679
        return group
680

    
681
    def set_settings(self, settings):
682
        for element in settings:
683
            if not isinstance(element, RadioSetting):
684
                self.set_settings(element)
685
                continue
686
            else:
687
                try:
688
                    if "vfoa" in element.get_name():
689
                        continue
690
                    if "vfob" in element.get_name():
691
                        continue
692
                    elif "." in element.get_name():
693
                        bits = element.get_name().split(".")
694
                        obj = self._memobj
695
                        for bit in bits[:-1]:
696
                            obj = getattr(obj, bit)
697
                        setting = bits[-1]
698
                    else:
699
                        obj = self._memobj.settings
700
                        setting = element.get_name()
701

    
702
                    if element.has_apply_callback():
703
                        LOG.debug("using apply callback")
704
                        element.run_apply_callback()
705
                    elif setting == "beep_tone_disabled":
706
                        setattr(obj, setting, not int(element.value))
707
                    elif setting == "busy_lockout":
708
                        setattr(obj, setting, not int(element.value))
709
                    elif setting == "keyunlocked":
710
                        setattr(obj, setting, not int(element.value))
711
                    elif element.value.get_mutable():
712
                        LOG.debug("Setting %s = %s" % (setting, element.value))
713
                        setattr(obj, setting, element.value)
714
                except Exception, e:
715
                    LOG.debug(element.get_name())
716
                    raise
717

    
718
    @classmethod
719
    def match_model(cls, filedata, filename):
720
        return (filedata.startswith("TG-UV2+ Radio Program Data") and
721
                len(filedata) == (cls._memsize + 0x30))
(5-5/5)