Project

General

Profile

New Model #8591 » tg_uv2p_fix_8591_v1.py

Ran Katz, 03/15/2022 09:32 PM

 
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

    
23
import serial
24

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

    
32
LOG = logging.getLogger(__name__)
33

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

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

    
61
struct tguv2_config {
62
    u8 unknown1;
63
    u8 squelch;
64
    u8 time_out_timer;
65
    u8 priority_channel;
66

    
67
    u8 unknown2:7,
68
        keyunlocked:1;
69
    u8 busy_lockout;
70
    u8 vox;
71
    u8 unknown3;
72

    
73
    u8 beep_tone_disabled;
74
    u8 display;
75
    u8 step;
76
    u8 unknown4;
77

    
78
    u8 unknown5;
79
    u8 rxmode;
80
    u8 unknown6:7,
81
        no_end_tone:1;
82
    u8 vfo_model;
83
};
84

    
85
struct vfo {
86
    u8 current;
87
    u8 chan;
88
    u8 memno;
89
};
90

    
91
struct name {
92
  u8 name[6];
93
  u8 unknown1[10];
94
};
95

    
96
#seekto 0x0000;
97
char ident[32];
98
u8 blank[16];
99

    
100
struct memory channels[200];
101
struct memory bands[5];
102

    
103
#seekto 0x0D30;
104
struct bandflag bandflags[200];
105

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

    
114
#seekto 0x0F30;
115
struct name names[200];
116

    
117
"""
118

    
119

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

    
140

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

    
148

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

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

    
174
    return memmap.MemoryMap(data)
175

    
176

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

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

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

    
206

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

    
215
    _memsize = 0x2000
216

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

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

    
256
        rf.valid_modes = ["FM", "NFM"]
257
        rf.valid_power_levels = POWER_LEVELS
258
        rf.has_ctone = True
259
        rf.has_bank = False
260
        rf.has_tuning_step = True
261
        rf.memory_bounds = (1, 200)
262
        return rf
263

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

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

    
281
    def process_mmap(self):
282
        self._memobj = bitwise.parse(mem_format, self._mmap)
283

    
284
    def get_raw_memory(self, number):
285
        return repr(self._memobj.channels[number - 1])
286

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

    
291
        value = _get('tone')
292
        tmode = _get('tmode')
293

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

    
308
        return mode, val, pol
309

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

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

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

    
331
        else:
332
            return (self._memobj.channels[number - 1],
333
                    self._memobj.bandflags[number - 1],
334
                    self._memobj.names[number - 1].name)
335

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

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

    
348
        mem.freq = int(_mem.freq) * 10
349

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

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

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

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

    
376
        mem.extra = RadioSettingGroup("Extra", "extra")
377

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

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

    
386
        return mem
387

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

    
391
        _bf.set_raw("\xFF")
392

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

    
397
        _mem.set_raw("\x00" * 12 + "\xFF" * 2 + "\x00"*2)
398

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

    
405
        _mem.freq = mem.freq / 10
406
        _mem.offset = mem.offset / 10
407

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

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

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

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

    
428
        for setting in mem.extra:
429
            setattr(_mem, setting.get_name(), setting.value)
430

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

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

    
443
        group = RadioSettings(cfg_grp, vfoa_grp, vfob_grp)
444
        #
445
        # Configuration Settings
446
        #
447

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

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

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

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

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

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

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

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

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

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

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

    
518
        # Step
519
        mem_vals = range(0, len(TGUV2P_STEPS))
520
        user_options = [(str(x) + " kHz") for x in TGUV2P_STEPS]
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
        #
528
        # VFO Settings
529
        #
530

    
531
        vfo_groups = [vfoa_grp, vfob_grp]
532
        vfo_mem = [_vfoa, _vfob]
533
        vfo_lower = ["vfoa", "vfob"]
534
        vfo_upper = ["VFOA", "VFOB"]
535

    
536
        for idx, vfo_group in enumerate(vfo_groups):
537

    
538
            options = ["Channel", "Frequency"]
539
            tempvar = 0 if (vfo_mem[idx].current < 200) else 1
540
            rs = RadioSetting(vfo_lower[idx] + "_mode", vfo_upper[idx]+" Mode",
541
                              RadioSettingValueList(
542
                                  options, options[tempvar]))
543
            vfo_group.append(rs)
544

    
545
            if tempvar == 0:
546
                rs = RadioSetting(vfo_lower[idx] + "_ch",
547
                                  vfo_upper[idx] + " Channel",
548
                                  RadioSettingValueInteger(
549
                                      0, 199, vfo_mem[idx].current))
550
                vfo_group.append(rs)
551
            else:
552
                band_num = vfo_mem[idx].current - 200
553
                freq = int(_bandsettings[band_num].freq) * 10
554
                offset = int(_bandsettings[band_num].offset) * 10
555
                txtmode = _bandsettings[band_num].txtmode
556
                rxtmode = _bandsettings[band_num].rxtmode
557

    
558
                rs = RadioSetting(vfo_lower[idx] + "_freq",
559
                                  vfo_upper[idx] + " Frequency",
560
                                  RadioSettingValueFloat(
561
                                      0.0, 520.0, freq / 1000000.0,
562
                                      precision=6))
563
                vfo_group.append(rs)
564

    
565
                if offset > 70e6:
566
                    offset = 0
567
                rs = RadioSetting(vfo_lower[idx] + "_offset",
568
                                  vfo_upper[idx] + " Offset",
569
                                  RadioSettingValueFloat(
570
                                      0.0, 69.995, offset / 100000.0,
571
                                      resolution=0.005))
572
                vfo_group.append(rs)
573

    
574
                rs = RadioSetting(vfo_lower[idx] + "_duplex",
575
                                  vfo_upper[idx] + " Shift",
576
                                  RadioSettingValueList(
577
                                      DUPLEX,
578
                                      DUPLEX[_bandsettings[band_num].duplex]))
579
                vfo_group.append(rs)
580

    
581
                rs = RadioSetting(
582
                    vfo_lower[idx] + "_step",
583
                    vfo_upper[idx] + " Step",
584
                    RadioSettingValueFloat(
585
                        0.0, 1000.0,
586
                        TGUV2P_STEPS[_bandsettings[band_num].step],
587
                        resolution=0.25))
588
                vfo_group.append(rs)
589

    
590
                rs = RadioSetting(
591
                    vfo_lower[idx] + "_pwr",
592
                    vfo_upper[idx] + " Power",
593
                    RadioSettingValueList(
594
                        POWER_LEVELS_STR,
595
                        POWER_LEVELS_STR[_bandsettings[band_num].power]))
596
                vfo_group.append(rs)
597

    
598
                options = ["None", "Tone", "DTCS-N", "DTCS-I"]
599
                rs = RadioSetting(vfo_lower[idx] + "_ttmode",
600
                                  vfo_upper[idx]+" TX tone mode",
601
                                  RadioSettingValueList(
602
                                      options, options[txtmode]))
603
                vfo_group.append(rs)
604
                if txtmode == 1:
605
                    rs = RadioSetting(
606
                        vfo_lower[idx] + "_ttone",
607
                        vfo_upper[idx] + " TX tone",
608
                        RadioSettingValueFloat(
609
                            0.0, 1000.0,
610
                            chirp_common.TONES[_bandsettings[band_num].txtone],
611
                            resolution=0.1))
612
                    vfo_group.append(rs)
613
                elif txtmode >= 2:
614
                    txtone = _bandsettings[band_num].txtone
615
                    rs = RadioSetting(
616
                        vfo_lower[idx] + "_tdtcs",
617
                        vfo_upper[idx] + " TX DTCS",
618
                        RadioSettingValueInteger(
619
                            0, 1000, chirp_common.DTCS_CODES[txtone]))
620
                    vfo_group.append(rs)
621

    
622
                options = ["None", "Tone", "DTCS-N", "DTCS-I"]
623
                rs = RadioSetting(vfo_lower[idx] + "_rtmode",
624
                                  vfo_upper[idx] + " RX tone mode",
625
                                  RadioSettingValueList(options,
626
                                                        options[rxtmode]))
627
                vfo_group.append(rs)
628

    
629
                if rxtmode == 1:
630
                    rs = RadioSetting(
631
                        vfo_lower[idx] + "_rtone",
632
                        vfo_upper[idx] + " RX tone",
633
                        RadioSettingValueFloat(
634
                            0.0, 1000.0,
635
                            chirp_common.TONES[_bandsettings[band_num].rxtone],
636
                            resolution=0.1))
637
                    vfo_group.append(rs)
638
                elif rxtmode >= 2:
639
                    rxtone = _bandsettings[band_num].rxtone
640
                    rs = RadioSetting(vfo_lower[idx] + "_rdtcs",
641
                                      vfo_upper[idx] + " TX rTCS",
642
                                      RadioSettingValueInteger(
643
                                          0, 1000,
644
                                          chirp_common.DTCS_CODES[rxtone]))
645
                    vfo_group.append(rs)
646

    
647
                options = ["FM", "NFM"]
648
                rs = RadioSetting(
649
                    vfo_lower[idx] + "_fm",
650
                    vfo_upper[idx] + " FM BW ",
651
                    RadioSettingValueList(
652
                        options, options[_bandsettings[band_num].isnarrow]))
653
                vfo_group.append(rs)
654

    
655
        return group
656

    
657
    def set_settings(self, settings):
658
        for element in settings:
659
            if not isinstance(element, RadioSetting):
660
                self.set_settings(element)
661
                continue
662
            else:
663
                try:
664
                    if "vfo" in element.get_name():
665
                        continue
666
                    elif "." in element.get_name():
667
                        bits = element.get_name().split(".")
668
                        obj = self._memobj
669
                        for bit in bits[:-1]:
670
                            obj = getattr(obj, bit)
671
                        setting = bits[-1]
672
                    else:
673
                        obj = self._memobj.settings
674
                        setting = element.get_name()
675

    
676
                    if element.has_apply_callback():
677
                        LOG.debug("using apply callback")
678
                        element.run_apply_callback()
679
                    elif setting == "beep_tone_disabled":
680
                        setattr(obj, setting, not int(element.value))
681
                    elif setting == "busy_lockout":
682
                        setattr(obj, setting, not int(element.value))
683
                    elif setting == "keyunlocked":
684
                        setattr(obj, setting, not int(element.value))
685
                    elif element.value.get_mutable():
686
                        LOG.debug("Setting %s = %s" % (setting, element.value))
687
                        setattr(obj, setting, element.value)
688
                except Exception, e:
689
                    LOG.debug(element.get_name())
690
                    raise
691

    
692
    @classmethod
693
    def match_model(cls, filedata, filename):
694
        return (filedata.startswith("TG-UV2+ Radio Program Data") and
695
                len(filedata) == (cls._memsize + 0x30))
(4-4/5)