Project

General

Profile

New Model #8591 » tg_uv2p_fix_8591_v2.py

Ran Katz, 03/15/2022 10:26 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 = (0, 199)
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
        return repr(self._memobj.channels[number])
287

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

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

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

    
309
        return mode, val, pol
310

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

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

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

    
332
        else:
333
            #return (self._memobj.channels[number - 1],
334
            #        self._memobj.bandflags[number - 1],
335
             #       self._memobj.names[number - 1].name)
336
            return (self._memobj.channels[number],
337
                    self._memobj.bandflags[number],
338
                    self._memobj.names[number].name)
339

    
340
    def get_memory(self, number):
341
        _mem, _bf, _nam = self._get_memobjs(number)
342
        mem = chirp_common.Memory()
343
        if isinstance(number, str):
344
            mem.extd_number = number
345
        else:
346
            mem.number = number
347

    
348
        if (_mem.freq.get_raw()[0] == "\xFF") or (_bf.band == "\x0F"):
349
            mem.empty = True
350
            return mem
351

    
352
        mem.freq = int(_mem.freq) * 10
353

    
354
        if _mem.offset.get_raw()[0] == "\xFF":
355
            mem.offset = 0
356
        else:
357
            mem.offset = int(_mem.offset) * 10
358

    
359
        chirp_common.split_tone_decode(
360
            mem,
361
            self._decode_tone(_mem, "tx"),
362
            self._decode_tone(_mem, "rx"))
363

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

    
372
        if _nam:
373
            for char in _nam:
374
                try:
375
                    mem.name += CHARSET[char]
376
                except IndexError:
377
                    break
378
            mem.name = mem.name.rstrip()
379

    
380
        mem.extra = RadioSettingGroup("Extra", "extra")
381

    
382
        rs = RadioSetting("not_scramble", "(not)SCRAMBLE",
383
                          RadioSettingValueBoolean(_mem.not_scramble))
384
        mem.extra.append(rs)
385

    
386
        rs = RadioSetting("not_revfreq", "(not)Reverse Duplex",
387
                          RadioSettingValueBoolean(_mem.not_revfreq))
388
        mem.extra.append(rs)
389

    
390
        return mem
391

    
392
    def set_memory(self, mem):
393
        _mem, _bf, _nam = self._get_memobjs(mem.number)
394

    
395
        _bf.set_raw("\xFF")
396

    
397
        if mem.empty:
398
            _mem.set_raw("\xFF" * 16)
399
            return
400

    
401
        _mem.set_raw("\x00" * 12 + "\xFF" * 2 + "\x00"*2)
402

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

    
409
        _mem.freq = mem.freq / 10
410
        _mem.offset = mem.offset / 10
411

    
412
        tx, rx = chirp_common.split_tone_encode(mem)
413
        self._encode_tone(_mem, 'tx', *tx)
414
        self._encode_tone(_mem, 'rx', *rx)
415

    
416
        _mem.duplex = DUPLEX.index(mem.duplex)
417
        _mem.isnarrow = mem.mode == "NFM"
418
        _mem.step = TGUV2P_STEPS.index(mem.tuning_step)
419

    
420
        if mem.power is None:
421
            _mem.power = 0
422
        else:
423
            _mem.power = POWER_LEVELS.index(mem.power)
424

    
425
        if _nam:
426
            for i in range(0, 6):
427
                try:
428
                    _nam[i] = CHARSET.index(mem.name[i])
429
                except IndexError:
430
                    _nam[i] = 0xFF
431

    
432
        for setting in mem.extra:
433
            setattr(_mem, setting.get_name(), setting.value)
434

    
435
    def get_settings(self):
436
        _settings = self._memobj.settings
437
        _vfoa = self._memobj.vfos[0]
438
        _vfob = self._memobj.vfos[1]
439
        _bandsettings = self._memobj.bands
440

    
441
        cfg_grp = RadioSettingGroup("cfg_grp", "Configuration")
442
        vfoa_grp = RadioSettingGroup(
443
            "vfoa_grp", "VFO A Settings\n  (Current Status, Read Only)")
444
        vfob_grp = RadioSettingGroup(
445
            "vfob_grp", "VFO B Settings\n  (Current Status, Read Only)")
446

    
447
        group = RadioSettings(cfg_grp, vfoa_grp, vfob_grp)
448
        #
449
        # Configuration Settings
450
        #
451

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

    
459
        # Display mode
460
        options = ["Frequency", "Channel", "Name"]
461
        rs = RadioSetting("display", "Channel Display Mode",
462
                          RadioSettingValueList(
463
                              options, options[_settings.display]))
464
        cfg_grp.append(rs)
465

    
466
        # Squelch level
467
        rs = RadioSetting("squelch", "Squelch Level",
468
                          RadioSettingValueInteger(0, 9, _settings.squelch))
469
        cfg_grp.append(rs)
470

    
471
        # Vox level
472
        mem_vals = range(10)
473
        user_options = [str(x) for x in mem_vals]
474
        user_options[0] = "Off"
475
        options_map = zip(user_options, mem_vals)
476

    
477
        rs = RadioSetting("vox", "VOX Level",
478
                          RadioSettingValueMap(options_map, _settings.vox))
479
        cfg_grp.append(rs)
480

    
481
        # Keypad beep
482
        rs = RadioSetting("beep_tone_disabled", "Beep Prompt",
483
                          RadioSettingValueBoolean(
484
                               not _settings.beep_tone_disabled))
485
        cfg_grp.append(rs)
486

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

    
498
        # Busy chanel lock
499
        rs = RadioSetting("busy_lockout", "Busy Channel Lock",
500
                          RadioSettingValueBoolean(
501
                             not _settings.busy_lockout))
502
        cfg_grp.append(rs)
503

    
504
        # Keypad lock
505
        rs = RadioSetting("keyunlocked", "Keypad Lock",
506
                          RadioSettingValueBoolean(
507
                              not _settings.keyunlocked))
508
        cfg_grp.append(rs)
509

    
510
        # Priority channel
511
        mem_vals = range(200)
512
        user_options = [str(x) for x in mem_vals]
513
        mem_vals.insert(0, 0xFF)
514
        user_options.insert(0, "Not Set")
515
        options_map = zip(user_options, mem_vals)
516

    
517
        rs = RadioSetting("priority_channel", "Priority Channel",
518
                          RadioSettingValueMap(options_map,
519
                                               _settings.priority_channel))
520
        cfg_grp.append(rs)
521

    
522
        # Step
523
        mem_vals = range(0, len(TGUV2P_STEPS))
524
        user_options = [(str(x) + " kHz") for x in TGUV2P_STEPS]
525
        options_map = zip(user_options, mem_vals)
526

    
527
        rs = RadioSetting("step", "Current (VFO?) step size",
528
                          RadioSettingValueMap(options_map, _settings.step))
529
        cfg_grp.append(rs)
530

    
531
        #
532
        # VFO Settings
533
        #
534

    
535
        vfo_groups = [vfoa_grp, vfob_grp]
536
        vfo_mem = [_vfoa, _vfob]
537
        vfo_lower = ["vfoa", "vfob"]
538
        vfo_upper = ["VFOA", "VFOB"]
539

    
540
        for idx, vfo_group in enumerate(vfo_groups):
541

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

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

    
562
                rs = RadioSetting(vfo_lower[idx] + "_freq",
563
                                  vfo_upper[idx] + " Frequency",
564
                                  RadioSettingValueFloat(
565
                                      0.0, 520.0, freq / 1000000.0,
566
                                      precision=6))
567
                vfo_group.append(rs)
568

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

    
578
                rs = RadioSetting(vfo_lower[idx] + "_duplex",
579
                                  vfo_upper[idx] + " Shift",
580
                                  RadioSettingValueList(
581
                                      DUPLEX,
582
                                      DUPLEX[_bandsettings[band_num].duplex]))
583
                vfo_group.append(rs)
584

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

    
594
                rs = RadioSetting(
595
                    vfo_lower[idx] + "_pwr",
596
                    vfo_upper[idx] + " Power",
597
                    RadioSettingValueList(
598
                        POWER_LEVELS_STR,
599
                        POWER_LEVELS_STR[_bandsettings[band_num].power]))
600
                vfo_group.append(rs)
601

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

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

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

    
651
                options = ["FM", "NFM"]
652
                rs = RadioSetting(
653
                    vfo_lower[idx] + "_fm",
654
                    vfo_upper[idx] + " FM BW ",
655
                    RadioSettingValueList(
656
                        options, options[_bandsettings[band_num].isnarrow]))
657
                vfo_group.append(rs)
658

    
659
        return group
660

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

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

    
696
    @classmethod
697
    def match_model(cls, filedata, filename):
698
        return (filedata.startswith("TG-UV2+ Radio Program Data") and
699
                len(filedata) == (cls._memsize + 0x30))
(5-5/5)