Project

General

Profile

Bug #1751 » uv5r.py

Jim Unroe, 07/11/2014 06:54 PM

 
1
# Copyright 2012 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
import struct
17
import time
18
import os
19

    
20
from chirp import chirp_common, errors, util, directory, memmap
21
from chirp import bitwise
22
from chirp.settings import RadioSetting, RadioSettingGroup, \
23
    RadioSettingValueInteger, RadioSettingValueList, \
24
    RadioSettingValueBoolean, RadioSettingValueString, \
25
    RadioSettingValueFloat, InvalidValueError
26
from textwrap import dedent
27

    
28
if os.getenv("CHIRP_DEBUG"):
29
    CHIRP_DEBUG = True
30
else:
31
    CHIRP_DEBUG = False
32

    
33
MEM_FORMAT = """
34
#seekto 0x0008;
35
struct {
36
  lbcd rxfreq[4];
37
  lbcd txfreq[4];
38
  ul16 rxtone;
39
  ul16 txtone;
40
  u8 unused1:3,
41
     isuhf:1,
42
     scode:4;
43
  u8 unknown1:7,
44
     txtoneicon:1;
45
  u8 mailicon:3,
46
     unknown2:3,
47
     lowpower:2;
48
  u8 unknown3:1,
49
     wide:1,
50
     unknown4:2,
51
     bcl:1,
52
     scan:1,
53
     pttid:2;
54
} memory[128];
55

    
56
#seekto 0x0B08;
57
struct {
58
  u8 code[5];
59
  u8 unused[11];
60
} pttid[15];
61

    
62
#seekto 0x0C88;
63
struct {
64
  u8 code222[3];
65
  u8 unused222[2];
66
  u8 code333[3];
67
  u8 unused333[2];
68
  u8 alarmcode[3];
69
  u8 unused119[2];
70
  u8 unknown1;
71
  u8 code555[3];
72
  u8 unused555[2];
73
  u8 code666[3];
74
  u8 unused666[2];
75
  u8 code777[3];
76
  u8 unused777[2];
77
  u8 unknown2;
78
  u8 code60606[5];
79
  u8 code70707[5];
80
  u8 code[5];
81
  u8 unused1:6,
82
     aniid:2;
83
  u8 unknown[2];
84
  u8 dtmfon;
85
  u8 dtmfoff;
86
} ani;
87

    
88
#seekto 0x0E28;
89
struct {
90
  u8 squelch;
91
  u8 step;
92
  u8 unknown1;
93
  u8 save;
94
  u8 vox;
95
  u8 unknown2;
96
  u8 abr;
97
  u8 tdr;
98
  u8 beep;
99
  u8 timeout;
100
  u8 unknown3[4];
101
  u8 voice;
102
  u8 unknown4;
103
  u8 dtmfst;
104
  u8 unknown5;
105
  u8 unknown12:6,
106
     screv:2;
107
  u8 pttid;
108
  u8 pttlt;
109
  u8 mdfa;
110
  u8 mdfb;
111
  u8 bcl;
112
  u8 autolk; // NOTE: The UV-6 calls this byte voxenable, but the UV-5R calls
113
             // it autolk. Since this is a minor difference, it will be referred
114
             // to by the wrong name for the UV-6.
115
  u8 sftd;
116
  u8 unknown6[3];
117
  u8 wtled;
118
  u8 rxled;
119
  u8 txled;
120
  u8 almod;
121
  u8 band;
122
  u8 tdrab;
123
  u8 ste;
124
  u8 rpste;
125
  u8 rptrl;
126
  u8 ponmsg;
127
  u8 roger;
128
  u8 rogerrx;
129
  u8 tdrch;
130
  u8 displayab:1,
131
     unknown1:2,
132
     fmradio:1,
133
     alarm:1,
134
     unknown2:1,
135
     reset:1,
136
     menu:1;
137
  u8 unknown1:6,
138
     singleptt:1,
139
     vfomrlock:1;
140
  u8 workmode;
141
  u8 keylock;
142
} settings;
143

    
144
#seekto 0x0E7E;
145
struct {
146
  u8 unused1:1,
147
     mrcha:7;
148
  u8 unused2:1,
149
     mrchb:7;
150
} wmchannel;
151

    
152
#seekto 0x0F10;
153
struct {
154
  u8 freq[8];
155
  u8 unknown1;
156
  u8 offset[4];
157
  u8 unknown2;
158
  ul16 rxtone;
159
  ul16 txtone;
160
  u8 unused1:7,
161
     band:1;
162
  u8 unknown3;
163
  u8 unused2:2,
164
     sftd:2,
165
     scode:4;
166
  u8 unknown4;
167
  u8 unused3:1
168
     step:3,
169
     unused4:4;
170
  u8 txpower:1,
171
     widenarr:1,
172
     unknown5:4,
173
     txpower3:2;
174
} vfoa;
175

    
176
#seekto 0x0F30;
177
struct {
178
  u8 freq[8];
179
  u8 unknown1;
180
  u8 offset[4];
181
  u8 unknown2;
182
  ul16 rxtone;
183
  ul16 txtone;
184
  u8 unused1:7,
185
     band:1;
186
  u8 unknown3;
187
  u8 unused2:2,
188
     sftd:2,
189
     scode:4;
190
  u8 unknown4;
191
  u8 unused3:1
192
     step:3,
193
     unused4:4;
194
  u8 txpower:1,
195
     widenarr:1,
196
     unknown5:4,
197
     txpower3:2;
198
} vfob;
199

    
200
#seekto 0x0F56;
201
u16 fm_presets;
202

    
203
#seekto 0x1008;
204
struct {
205
  char name[7];
206
  u8 unknown2[9];
207
} names[128];
208

    
209
#seekto 0x1818;
210
struct {
211
  char line1[7];
212
  char line2[7];
213
} sixpoweron_msg;
214

    
215
#seekto 0x%04X;
216
struct {
217
  char line1[7];
218
  char line2[7];
219
} poweron_msg;
220

    
221
#seekto 0x1838;
222
struct {
223
  char line1[7];
224
  char line2[7];
225
} firmware_msg;
226

    
227
struct limit {
228
  u8 enable;
229
  bbcd lower[2];
230
  bbcd upper[2];
231
};
232

    
233
#seekto 0x1908;
234
struct {
235
  struct limit vhf;
236
  struct limit uhf;
237
} limits_new;
238

    
239
#seekto 0x1910;
240
struct {
241
  u8 unknown1[2];
242
  struct limit vhf;
243
  u8 unknown2;
244
  u8 unknown3[8];
245
  u8 unknown4[2];
246
  struct limit uhf;
247
} limits_old;
248

    
249
"""
250

    
251
# 0x1EC0 - 0x2000
252

    
253
vhf_220_radio = "\x02"
254

    
255
BASETYPE_UV5R = ["BFS", "BFB"]
256
BASETYPE_F11  = ["USA"]
257
BASETYPE_UV82 = ["US2S", "B82S", "BF82"]
258
BASETYPE_BJ55 = ["BJ55"]          # needed for for the Baojie UV-55 in bjuv55.py
259
BASETYPE_UV6  = ["BF1"]
260
BASETYPE_KT980HP = ["BFP3V3 B"]
261
BASETYPE_F8HP = ["BFP3V3 F"]
262
BASETYPE_LIST = BASETYPE_UV5R + BASETYPE_F11 + BASETYPE_UV82 + \
263
                BASETYPE_BJ55 + BASETYPE_UV6 + BASETYPE_KT980HP + \
264
                BASETYPE_F8HP
265

    
266
AB_LIST = ["A", "B"]
267
ALMOD_LIST = ["Site", "Tone", "Code", "_unknown_"]
268
BANDWIDTH_LIST = ["Wide", "Narrow"]
269
COLOR_LIST = ["Off", "Blue", "Orange", "Purple"]
270
DTMFSPEED_LIST = ["%s ms" % x for x in range(50, 2010, 10)]
271
DTMFST_LIST = ["OFF", "DT-ST", "ANI-ST", "DT+ANI"]
272
MODE_LIST = ["Channel", "Name", "Frequency"]
273
PONMSG_LIST = ["Full", "Message"]
274
PTTID_LIST = ["Off", "BOT", "EOT", "Both"]
275
PTTIDCODE_LIST = ["%s" % x for x in range(1, 16)]
276
RESUME_LIST = ["TO", "CO", "SE"]
277
ROGERRX_LIST = ["Off"] + AB_LIST
278
RPSTE_LIST = ["OFF"] + ["%s" % x for x in range(1, 11)]
279
SAVE_LIST = ["Off", "1:1", "1:2", "1:3", "1:4"]
280
SCODE_LIST = ["%s" % x for x in range(1, 16)]
281
SHIFTD_LIST = ["Off", "+", "-"]
282
STEDELAY_LIST = ["OFF"] + ["%s ms" % x for x in range(100, 1100, 100)]
283
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0]
284
STEP_LIST = [str(x) for x in STEPS]
285
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0]
286
STEP291_LIST = [str(x) for x in STEPS]
287
TDRAB_LIST = ["Off"] + AB_LIST
288
TDRCH_LIST = ["CH%s" % x for x in range(1, 129)]
289
TIMEOUT_LIST = ["%s sec" % x for x in range(15, 615, 15)]
290
TXPOWER_LIST = ["High", "Low"]
291
TXPOWER3_LIST = ["High", "Mid", "Low"]
292
VOICE_LIST = ["Off", "English", "Chinese"]
293
VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 11)]
294
WORKMODE_LIST = ["Frequency", "Channel"]
295

    
296
SETTING_LISTS = {
297
    "almod" : ALMOD_LIST,
298
    "aniid" : PTTID_LIST,
299
    "displayab" : AB_LIST,
300
    "dtmfst" : DTMFST_LIST,
301
    "dtmfspeed" : DTMFSPEED_LIST,
302
    "mdfa" : MODE_LIST,
303
    "mdfb" : MODE_LIST,
304
    "ponmsg" : PONMSG_LIST,
305
    "pttid" : PTTID_LIST,
306
    "rogerrx" : ROGERRX_LIST,
307
    "rpste" : RPSTE_LIST,
308
    "rxled" : COLOR_LIST,
309
    "save" : SAVE_LIST,
310
    "scode" : PTTIDCODE_LIST,
311
    "screv" : RESUME_LIST,
312
    "sftd" : SHIFTD_LIST,
313
    "stedelay" : STEDELAY_LIST,
314
    "step" : STEP_LIST,
315
    "step291" : STEP291_LIST,
316
    "tdrab" : TDRAB_LIST,
317
    "tdrch" : TDRCH_LIST,
318
    "timeout" : TIMEOUT_LIST,
319
    "txled" : COLOR_LIST,
320
    "txpower" : TXPOWER_LIST,
321
    "txpower3" : TXPOWER3_LIST,
322
    "voice" : VOICE_LIST,
323
    "vox" : VOX_LIST,
324
    "widenarr" : BANDWIDTH_LIST,
325
    "workmode" : WORKMODE_LIST,
326
    "wtled" : COLOR_LIST
327
}
328

    
329
def _do_status(radio, block):
330
    status = chirp_common.Status()
331
    status.msg = "Cloning"
332
    status.cur = block
333
    status.max = radio.get_memsize()
334
    radio.status_fn(status)
335

    
336
def validate_orig(ident):
337
    try:
338
        ver = int(ident[4:7])
339
        if ver >= 291:
340
            raise errors.RadioError("Radio version %i not supported" % ver)
341
    except ValueError:
342
        raise errors.RadioError("Radio reported invalid version string")
343

    
344
def validate_291(ident):
345
    if ident[4:7] != "\x30\x04\x50":
346
        raise errors.RadioError("Radio version not supported")
347

    
348
UV5R_MODEL_ORIG = "\x50\xBB\xFF\x01\x25\x98\x4D"
349
UV5R_MODEL_291  = "\x50\xBB\xFF\x20\x12\x07\x25"
350
UV5R_MODEL_F11  = "\x50\xBB\xFF\x13\xA1\x11\xDD"
351
UV5R_MODEL_UV82 = "\x50\xBB\xFF\x20\x13\x01\x05"
352
UV5R_MODEL_UV6  = "\x50\xBB\xFF\x20\x12\x08\x23"
353

    
354
def _upper_band_from_data(data):
355
    return data[0x03:0x04]
356

    
357
def _upper_band_from_image(radio):
358
    return _upper_band_from_data(radio.get_mmap())
359

    
360
def _firmware_version_from_data(data, version_start, version_stop):
361
    version_tag = data[version_start:version_stop]
362
    return version_tag
363

    
364
def _firmware_version_from_image(radio):
365
    version = _firmware_version_from_data(radio.get_mmap(),
366
                                          radio._fw_ver_file_start,
367
                                          radio._fw_ver_file_stop)
368
    if CHIRP_DEBUG:
369
        print "_firmware_version_from_image: " + util.hexprint(version)
370
    return version
371

    
372
def _do_ident(radio, magic):
373
    serial = radio.pipe
374
    serial.setTimeout(1)
375

    
376
    print "Sending Magic: %s" % util.hexprint(magic)
377
    for byte in magic:
378
        serial.write(byte)
379
        time.sleep(0.01)
380
    ack = serial.read(1)
381

    
382
    if ack != "\x06":
383
        if ack:
384
            print repr(ack)
385
        raise errors.RadioError("Radio did not respond")
386

    
387
    serial.write("\x02")
388
    ident = serial.read(8)
389

    
390
    print "Ident:\n%s" % util.hexprint(ident)
391

    
392
    serial.write("\x06")
393
    ack = serial.read(1)
394
    if ack != "\x06":
395
        raise errors.RadioError("Radio refused clone")
396

    
397
    return ident
398

    
399
def _read_block(radio, start, size):
400
    msg = struct.pack(">BHB", ord("S"), start, size)
401
    radio.pipe.write(msg)
402

    
403
    answer = radio.pipe.read(4)
404
    if len(answer) != 4:
405
        raise errors.RadioError("Radio refused to send block 0x%04x" % start)
406

    
407
    cmd, addr, length = struct.unpack(">BHB", answer)
408
    if cmd != ord("X") or addr != start or length != size:
409
        print "Invalid answer for block 0x%04x:" % start
410
        print "CMD: %s  ADDR: %04x  SIZE: %02x" % (cmd, addr, length)
411
        raise errors.RadioError("Unknown response from radio")
412

    
413
    chunk = radio.pipe.read(0x40)
414
    if not chunk:
415
        raise errors.RadioError("Radio did not send block 0x%04x" % start)
416
    elif len(chunk) != size:
417
        print "Chunk length was 0x%04i" % len(chunk)
418
        raise errors.RadioError("Radio sent incomplete block 0x%04x" % start)
419

    
420
    radio.pipe.write("\x06")
421

    
422
    ack = radio.pipe.read(1)
423
    if ack != "\x06":
424
        raise errors.RadioError("Radio refused to send block 0x%04x" % start)
425

    
426
    return chunk
427

    
428
def _get_radio_firmware_version(radio):
429
    if radio.MODEL == "BJ-UV55":
430
        block = _read_block(radio, 0x1FF0, 0x40)
431
        version = block[0:6]
432
    else:
433
        block1 = _read_block(radio, 0x1EC0, 0x40)
434
        block2 = _read_block(radio, 0x1F00, 0x40)
435
        block = block1 + block2
436
        version = block[48:64]
437
    return version
438

    
439
def _ident_radio(radio):
440
    for magic in radio._idents:
441
        error = None
442
        try:
443
            data = _do_ident(radio, magic)
444
            return data
445
        except errors.RadioError, e:
446
            print e
447
            error = e
448
            time.sleep(2)
449
    if error:
450
        raise error
451
    raise errors.RadioError("Radio did not respond")
452

    
453
def _do_download(radio):
454
    data = _ident_radio(radio)
455

    
456
    # Main block
457
    if CHIRP_DEBUG:
458
        print "downloading main block..."
459
    for i in range(0, 0x1800, 0x40):
460
        data += _read_block(radio, i, 0x40)
461
        _do_status(radio, i)
462
    if CHIRP_DEBUG:
463
        print "done."
464
        print "downloading aux block..."
465
    # Auxiliary block starts at 0x1ECO (?)
466
    for i in range(0x1EC0, 0x2000, 0x40):
467
        data += _read_block(radio, i, 0x40)
468
    if CHIRP_DEBUG:
469
        print "done."
470
    return memmap.MemoryMap(data)
471

    
472
def _send_block(radio, addr, data):
473
    msg = struct.pack(">BHB", ord("X"), addr, len(data))
474
    radio.pipe.write(msg + data)
475

    
476
    ack = radio.pipe.read(1)
477
    if ack != "\x06":
478
        raise errors.RadioError("Radio refused to accept block 0x%04x" % addr)
479

    
480
def _do_upload(radio):
481
    ident = _ident_radio(radio)
482
    radio_upper_band = ident[3:4]
483
    image_upper_band = _upper_band_from_image(radio)
484

    
485
    if image_upper_band == vhf_220_radio or radio_upper_band == vhf_220_radio:
486
        if image_upper_band != radio_upper_band:
487
            raise errors.RadioError("Image not supported by radio")
488

    
489
    image_version = _firmware_version_from_image(radio)
490
    radio_version = _get_radio_firmware_version(radio)
491
    print "Image is %s" % repr(image_version)
492
    print "Radio is %s" % repr(radio_version)
493

    
494
    if not any(type in radio_version for type in BASETYPE_LIST):
495
        raise errors.RadioError("Unsupported firmware version: `%s'" %
496
                                radio_version)
497

    
498
    # Main block
499
    for i in range(0x08, 0x1808, 0x10):
500
        _send_block(radio, i - 0x08, radio.get_mmap()[i:i + 0x10])
501
        _do_status(radio, i)
502

    
503
    if len(radio.get_mmap().get_packed()) == 0x1808:
504
        print "Old image, not writing aux block"
505
        return # Old image, no aux block
506

    
507
    if image_version != radio_version:
508
        msg = ("Upload finished, but the 'Other Settings' "
509
               "could not be sent because the firmware "
510
               "version of the image (%s) does not match "
511
               "that of the radio (%s).")
512
        raise errors.RadioError(msg % (image_version, radio_version))
513

    
514
    # Auxiliary block at radio address 0x1EC0, our offset 0x1808
515
    for i in range(0x1EC0, 0x2000, 0x10):
516
        addr = 0x1808 + (i - 0x1EC0)
517
        _send_block(radio, i, radio.get_mmap()[addr:addr + 0x10])
518

    
519
UV5R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=4.00),
520
                     chirp_common.PowerLevel("Low",  watts=1.00)]
521

    
522
UV5R_POWER_LEVELS3 = [chirp_common.PowerLevel("High", watts=8.00),
523
                      chirp_common.PowerLevel("Med",  watts=4.00),
524
                      chirp_common.PowerLevel("Low",  watts=1.00)]
525

    
526
UV5R_DTCS = sorted(chirp_common.DTCS_CODES + [645])
527

    
528
UV5R_CHARSET = chirp_common.CHARSET_UPPER_NUMERIC + \
529
    "!@#$%^&*()+-=[]:\";'<>?,./"
530

    
531
# Uncomment this to actually register this radio in CHIRP
532
@directory.register
533
class BaofengUV5R(chirp_common.CloneModeRadio,
534
                  chirp_common.ExperimentalRadio):
535
    """Baofeng UV-5R"""
536
    VENDOR = "Baofeng"
537
    MODEL = "UV-5R"
538
    BAUD_RATE = 9600
539

    
540
    _memsize = 0x1808
541
    _basetype = BASETYPE_UV5R
542
    _idents = [UV5R_MODEL_291,
543
               UV5R_MODEL_ORIG
544
               ]
545
    _vhf_range = (136000000, 174000000)
546
    _220_range = (220000000, 260000000)
547
    _uhf_range = (400000000, 520000000)
548
    _mem_params = ( 0x1828 # poweron_msg offset
549
                    )
550
    # offset of fw version in image file
551
    _fw_ver_file_start = 0x1838
552
    _fw_ver_file_stop = 0x1848
553

    
554
    @classmethod
555
    def get_prompts(cls):
556
        rp = chirp_common.RadioPrompts()
557
        rp.experimental = ('Due to the fact that the manufacturer continues to '
558
                'release new versions of the firmware with obscure and '
559
                'hard-to-track changes, this driver may not work with '
560
                'your device. Thus far and to the best knowledge of the '
561
                'author, no UV-5R radios have been harmed by using CHIRP. '
562
                'However, proceed at your own risk!')
563
        rp.pre_download = _(dedent("""\
564
            1. Turn radio off.
565
            2. Connect cable to mic/spkr connector.
566
            3. Make sure connector is firmly connected.
567
            4. Turn radio on.
568
            5. Ensure that the radio is tuned to channel with no activity.
569
            6. Click OK to download image from device."""))
570
        rp.pre_upload = _(dedent("""\
571
            1. Turn radio off.
572
            2. Connect cable to mic/spkr connector.
573
            3. Make sure connector is firmly connected.
574
            4. Turn radio on.
575
            5. Ensure that the radio is tuned to channel with no activity.
576
            6. Click OK to upload image to device."""))
577
        return rp
578

    
579
    def get_features(self):
580
        rf = chirp_common.RadioFeatures()
581
        rf.has_settings = True
582
        rf.has_bank = False
583
        rf.has_cross = True
584
        rf.has_rx_dtcs = True
585
        rf.has_tuning_step = False
586
        rf.can_odd_split = True
587
        rf.valid_name_length = 7
588
        rf.valid_characters = UV5R_CHARSET
589
        rf.valid_skips = ["", "S"]
590
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
591
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
592
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
593
        rf.valid_power_levels = UV5R_POWER_LEVELS
594
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
595
        rf.valid_modes = ["FM", "NFM"]
596

    
597
        normal_bands = [self._vhf_range, self._uhf_range]
598
        rax_bands = [self._vhf_range, self._220_range]
599

    
600
        if self._mmap is None:
601
            rf.valid_bands = [normal_bands[0], rax_bands[1], normal_bands[1]]
602
        elif not self._is_orig() and self._my_upper_band() == vhf_220_radio:
603
            rf.valid_bands = rax_bands
604
        else:
605
            rf.valid_bands = normal_bands
606
        rf.memory_bounds = (0, 127)
607
        return rf
608

    
609
    @classmethod
610
    def match_model(cls, filedata, filename):
611
        match_size = False
612
        match_model = False
613
        if len(filedata) in [0x1808, 0x1948]:
614
            match_size = True
615
        fwdata = _firmware_version_from_data(filedata,
616
                                             cls._fw_ver_file_start,
617
                                             cls._fw_ver_file_stop)
618
        if any(type in fwdata for type in cls._basetype):
619
            match_model = True
620
        if match_size and match_model:
621
            return True
622
        else:
623
            return False
624

    
625
    def process_mmap(self):
626
        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
627

    
628
    def sync_in(self):
629
        try:
630
            self._mmap = _do_download(self)
631
        except errors.RadioError:
632
            raise
633
        except Exception, e:
634
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
635
        self.process_mmap()
636

    
637
    def sync_out(self):
638
        try:
639
            _do_upload(self)
640
        except errors.RadioError:
641
            raise
642
        except Exception, e:
643
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
644

    
645
    def get_raw_memory(self, number):
646
        return repr(self._memobj.memory[number])
647

    
648
    def _is_txinh(self, _mem):
649
        raw_tx = ""
650
        for i in range(0, 4):
651
            raw_tx += _mem.txfreq[i].get_raw()
652
        return raw_tx == "\xFF\xFF\xFF\xFF"
653

    
654
    def _get_mem(self, number):
655
        return self._memobj.memory[number]
656

    
657
    def _get_nam(self, number):
658
        return self._memobj.names[number]
659

    
660
    def get_memory(self, number):
661
        _mem = self._get_mem(number)
662
        _nam = self._get_nam(number)
663

    
664
        mem = chirp_common.Memory()
665
        mem.number = number
666

    
667
        if _mem.get_raw()[0] == "\xff":
668
            mem.empty = True
669
            return mem
670

    
671
        mem.freq = int(_mem.rxfreq) * 10
672

    
673
        if self._is_txinh(_mem):
674
            mem.duplex = "off"
675
            mem.offset = 0
676
        elif int(_mem.rxfreq) == int(_mem.txfreq):
677
            mem.duplex = ""
678
            mem.offset = 0
679
        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
680
            mem.duplex = "split"
681
            mem.offset = int(_mem.txfreq) * 10
682
        else:
683
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
684
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
685

    
686
        for char in _nam.name:
687
            if str(char) == "\xFF":
688
                char = " " # The UV-5R software may have 0xFF mid-name
689
            mem.name += str(char)
690
        mem.name = mem.name.rstrip()
691

    
692
        dtcs_pol = ["N", "N"]
693

    
694
        if _mem.txtone in [0, 0xFFFF]:
695
            txmode = ""
696
        elif _mem.txtone >= 0x0258:
697
            txmode = "Tone"
698
            mem.rtone = int(_mem.txtone) / 10.0
699
        elif _mem.txtone <= 0x0258:
700
            txmode = "DTCS"
701
            if _mem.txtone > 0x69:
702
                index = _mem.txtone - 0x6A
703
                dtcs_pol[0] = "R"
704
            else:
705
                index = _mem.txtone - 1
706
            mem.dtcs = UV5R_DTCS[index]
707
        else:
708
            print "Bug: txtone is %04x" % _mem.txtone
709

    
710
        if _mem.rxtone in [0, 0xFFFF]:
711
            rxmode = ""
712
        elif _mem.rxtone >= 0x0258:
713
            rxmode = "Tone"
714
            mem.ctone = int(_mem.rxtone) / 10.0
715
        elif _mem.rxtone <= 0x0258:
716
            rxmode = "DTCS"
717
            if _mem.rxtone >= 0x6A:
718
                index = _mem.rxtone - 0x6A
719
                dtcs_pol[1] = "R"
720
            else:
721
                index = _mem.rxtone - 1
722
            mem.rx_dtcs = UV5R_DTCS[index]
723
        else:
724
            print "Bug: rxtone is %04x" % _mem.rxtone
725

    
726
        if txmode == "Tone" and not rxmode:
727
            mem.tmode = "Tone"
728
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
729
            mem.tmode = "TSQL"
730
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
731
            mem.tmode = "DTCS"
732
        elif rxmode or txmode:
733
            mem.tmode = "Cross"
734
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
735

    
736
        mem.dtcs_polarity = "".join(dtcs_pol)
737

    
738
        if not _mem.scan:
739
            mem.skip = "S"
740

    
741
        if self.MODEL == "KT-980HP" or self.MODEL == "BF-F8HP":                          
742
            levels = UV5R_POWER_LEVELS3
743
        else:
744
            levels = UV5R_POWER_LEVELS
745
        try:
746
            mem.power = levels[_mem.lowpower]
747
        except IndexError:
748
            print "Radio reported invalid power level %s (in %s)" % (
749
                _mem.power, levels)
750
            mem.power = levels[0]
751

    
752
        mem.mode = _mem.wide and "FM" or "NFM"
753

    
754
        mem.extra = RadioSettingGroup("Extra", "extra")
755

    
756
        rs = RadioSetting("bcl", "BCL",
757
                          RadioSettingValueBoolean(_mem.bcl))
758
        mem.extra.append(rs)
759

    
760
        rs = RadioSetting("pttid", "PTT ID",
761
                          RadioSettingValueList(PTTID_LIST,
762
                                                PTTID_LIST[_mem.pttid]))
763
        mem.extra.append(rs)
764

    
765
        rs = RadioSetting("scode", "PTT ID Code",
766
                          RadioSettingValueList(PTTIDCODE_LIST,
767
                                                PTTIDCODE_LIST[_mem.scode]))
768
        mem.extra.append(rs)
769

    
770
        return mem
771

    
772
    def _set_mem(self, number):
773
        return self._memobj.memory[number]
774

    
775
    def _set_nam(self, number):
776
        return self._memobj.names[number]
777

    
778
    def set_memory(self, mem):
779
        _mem = self._get_mem(mem.number)
780
        _nam = self._get_nam(mem.number)
781

    
782
        if mem.empty:
783
            _mem.set_raw("\xff" * 16)
784
            return
785

    
786
        _mem.set_raw("\x00" * 16)
787

    
788
        _mem.rxfreq = mem.freq / 10
789

    
790
        if mem.duplex == "off":
791
            for i in range(0, 4):
792
                _mem.txfreq[i].set_raw("\xFF")
793
        elif mem.duplex == "split":
794
            _mem.txfreq = mem.offset / 10
795
        elif mem.duplex == "+":
796
            _mem.txfreq = (mem.freq + mem.offset) / 10
797
        elif mem.duplex == "-":
798
            _mem.txfreq = (mem.freq - mem.offset) / 10
799
        else:
800
            _mem.txfreq = mem.freq / 10
801

    
802
        _namelength = self.get_features().valid_name_length
803
        for i in range(_namelength):
804
            try:
805
                _nam.name[i] = mem.name[i]
806
            except IndexError:
807
                _nam.name[i] = "\xFF"
808

    
809
        rxmode = txmode = ""
810
        if mem.tmode == "Tone":
811
            _mem.txtone = int(mem.rtone * 10)
812
            _mem.rxtone = 0
813
        elif mem.tmode == "TSQL":
814
            _mem.txtone = int(mem.ctone * 10)
815
            _mem.rxtone = int(mem.ctone * 10)
816
        elif mem.tmode == "DTCS":
817
            rxmode = txmode = "DTCS"
818
            _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
819
            _mem.rxtone = UV5R_DTCS.index(mem.dtcs) + 1
820
        elif mem.tmode == "Cross":
821
            txmode, rxmode = mem.cross_mode.split("->", 1)
822
            if txmode == "Tone":
823
                _mem.txtone = int(mem.rtone * 10)
824
            elif txmode == "DTCS":
825
                _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
826
            else:
827
                _mem.txtone = 0
828
            if rxmode == "Tone":
829
                _mem.rxtone = int(mem.ctone * 10)
830
            elif rxmode == "DTCS":
831
                _mem.rxtone = UV5R_DTCS.index(mem.rx_dtcs) + 1
832
            else:
833
                _mem.rxtone = 0
834
        else:
835
            _mem.rxtone = 0
836
            _mem.txtone = 0
837

    
838
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
839
            _mem.txtone += 0x69
840
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
841
            _mem.rxtone += 0x69
842

    
843
        _mem.scan = mem.skip != "S"
844
        _mem.wide = mem.mode == "FM"
845

    
846
        if mem.power:
847
            if self.MODEL == "KT-980HP" or self.MODEL == "BF-F8HP":
848
                levels = [str(l) for l in UV5R_POWER_LEVELS3]
849
                _mem.lowpower = levels.index(str(mem.power))
850
            else:
851
                _mem.lowpower = UV5R_POWER_LEVELS.index(mem.power)
852
        else:
853
            _mem.lowpower = 0
854

    
855
        for setting in mem.extra:
856
            setattr(_mem, setting.get_name(), setting.value)
857

    
858
    def _is_orig(self):
859
        version_tag = _firmware_version_from_image(self)
860
        if CHIRP_DEBUG:
861
            print "@_is_orig, version_tag:", util.hexprint(version_tag)
862
        try:
863
            if 'BFB' in version_tag and not 'BFS' in version_tag:
864
                idx = version_tag.index("BFB") + 3
865
                version = int(version_tag[idx:idx + 3])
866
                return version < 291
867
            return False
868
        except:
869
            pass
870
        raise errors.RadioError("Unable to parse version string %s" %
871
                                version_tag)
872

    
873
    def _my_version(self):
874
        version_tag = _firmware_version_from_image(self)
875
        if 'BFS' in version_tag:
876
            idx = version_tag.index("BFS") + 3
877
            return int(version_tag[idx:idx + 3])
878
        elif 'BFB' in version_tag:
879
            idx = version_tag.index("BFB") + 3
880
            return int(version_tag[idx:idx + 3])
881
        elif 'BF82' in version_tag:
882
            idx = version_tag.index("BF82") + 2
883
            return int(version_tag[idx:idx + 4])
884
        elif 'B82S' in version_tag:
885
            idx = version_tag.index("B82S") + 4
886
            return int(version_tag[idx:idx + 2]) + 8200
887
        elif 'US2S' in version_tag:
888
            idx = version_tag.index("US2S") + 4
889
            return int(version_tag[idx:idx + 2]) + 8200
890
        elif 'USA' in version_tag:
891
            idx = version_tag.index("USA") + 3
892
            return int(version_tag[idx:idx + 3]) + 11000
893
        elif 'BJ55' in version_tag:
894
            idx = version_tag.index("BJ55") + 2
895
            return int(version_tag[idx:idx + 4])
896
        elif 'BF1' in version_tag:
897
            idx = version_tag.index("BF1") + 2
898
            return int(version_tag[idx:idx + 4])
899
        elif 'BFP' in version_tag:
900
            idx = version_tag.index("BFP") + 5
901
            return int(version_tag[idx:idx + 1]) + 98000
902

    
903
        raise Exception("Unrecognized firmware version string")
904

    
905
    def _my_upper_band(self):
906
        band_tag = _upper_band_from_image(self)
907
        return band_tag
908

    
909
    def _get_settings(self):
910
        _ani = self._memobj.ani
911
        _settings = self._memobj.settings
912
        _vfoa = self._memobj.vfoa
913
        _vfob = self._memobj.vfob
914
        _wmchannel = self._memobj.wmchannel
915
        basic = RadioSettingGroup("basic", "Basic Settings")
916
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
917
        group = RadioSettingGroup("top", "All Settings", basic, advanced)
918

    
919
        rs = RadioSetting("squelch", "Carrier Squelch Level",
920
                          RadioSettingValueInteger(0, 9, _settings.squelch))
921
        basic.append(rs)
922

    
923
        rs = RadioSetting("save", "Battery Saver",
924
                          RadioSettingValueList(SAVE_LIST,
925
                                                SAVE_LIST[_settings.save]))
926
        basic.append(rs)
927

    
928
        rs = RadioSetting("vox", "VOX Sensitivity",
929
                          RadioSettingValueList(VOX_LIST,
930
                                                VOX_LIST[_settings.vox]))
931
        advanced.append(rs)
932

    
933
        if self.MODEL == "UV-6":
934
            # NOTE: The UV-6 calls this byte voxenable, but the UV-5R calls it
935
            # autolk. Since this is a minor difference, it will be referred to
936
            # by the wrong name for the UV-6.
937
            rs = RadioSetting("autolk", "Vox",
938
                              RadioSettingValueBoolean(_settings.autolk))
939
            advanced.append(rs)
940

    
941
        if self.MODEL != "UV-6":
942
            rs = RadioSetting("abr", "Backlight Timeout",
943
                              RadioSettingValueInteger(0, 24, _settings.abr))
944
            basic.append(rs)
945

    
946
        rs = RadioSetting("tdr", "Dual Watch",
947
                          RadioSettingValueBoolean(_settings.tdr))
948
        advanced.append(rs)
949

    
950
        if self.MODEL == "UV-6":
951
            rs = RadioSetting("tdrch", "Dual Watch Channel",
952
                              RadioSettingValueList(TDRCH_LIST,
953
                                                    TDRCH_LIST[
954
                                                    _settings.tdrch]))
955
            advanced.append(rs)
956

    
957
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
958
                              RadioSettingValueBoolean(_settings.tdrab))
959
            advanced.append(rs)
960
        else:
961
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
962
                              RadioSettingValueList(TDRAB_LIST,
963
                                                    TDRAB_LIST[
964
                                                    _settings.tdrab]))
965
            advanced.append(rs)
966

    
967
        if self.MODEL == "UV-6":
968
            rs = RadioSetting("alarm", "Alarm Sound",
969
                              RadioSettingValueBoolean(_settings.alarm))
970
            advanced.append(rs)
971

    
972
        rs = RadioSetting("almod", "Alarm Mode",
973
                          RadioSettingValueList(ALMOD_LIST,
974
                                                ALMOD_LIST[_settings.almod]))
975
        advanced.append(rs)
976

    
977
        rs = RadioSetting("beep", "Beep",
978
                          RadioSettingValueBoolean(_settings.beep))
979
        basic.append(rs)
980

    
981
        rs = RadioSetting("timeout", "Timeout Timer",
982
                          RadioSettingValueList(TIMEOUT_LIST,
983
                                                TIMEOUT_LIST[
984
                                                _settings.timeout]))
985
        basic.append(rs)
986

    
987
        if self._is_orig() and self._my_version() < 251:
988
            rs = RadioSetting("voice", "Voice",
989
                              RadioSettingValueBoolean(_settings.voice))
990
            advanced.append(rs)
991
        else:
992
            rs = RadioSetting("voice", "Voice",
993
                              RadioSettingValueList(VOICE_LIST,
994
                                                    VOICE_LIST[
995
                                                    _settings.voice]))
996
            advanced.append(rs)
997

    
998
        rs = RadioSetting("screv", "Scan Resume",
999
                          RadioSettingValueList(RESUME_LIST,
1000
                                                RESUME_LIST[_settings.screv]))
1001
        advanced.append(rs)
1002

    
1003
        if self.MODEL != "UV-6":
1004
            rs = RadioSetting("mdfa", "Display Mode (A)",
1005
                              RadioSettingValueList(MODE_LIST,
1006
                                                    MODE_LIST[_settings.mdfa]))
1007
            basic.append(rs)
1008

    
1009
            rs = RadioSetting("mdfb", "Display Mode (B)",
1010
                              RadioSettingValueList(MODE_LIST,
1011
                                                    MODE_LIST[_settings.mdfb]))
1012
            basic.append(rs)
1013

    
1014
        rs = RadioSetting("bcl", "Busy Channel Lockout",
1015
                          RadioSettingValueBoolean(_settings.bcl))
1016
        advanced.append(rs)
1017

    
1018
        if self.MODEL != "UV-6":
1019
            rs = RadioSetting("autolk", "Automatic Key Lock",
1020
                              RadioSettingValueBoolean(_settings.autolk))
1021
            advanced.append(rs)
1022

    
1023
        rs = RadioSetting("fmradio", "Broadcast FM Radio",
1024
                          RadioSettingValueBoolean(_settings.fmradio))
1025
        advanced.append(rs)
1026

    
1027
        if self.MODEL != "UV-6":
1028
            rs = RadioSetting("wtled", "Standby LED Color",
1029
                              RadioSettingValueList(COLOR_LIST,
1030
                                                    COLOR_LIST[
1031
                                                    _settings.wtled]))
1032
            basic.append(rs)
1033

    
1034
            rs = RadioSetting("rxled", "RX LED Color",
1035
                              RadioSettingValueList(COLOR_LIST,
1036
                                                    COLOR_LIST[
1037
                                                    _settings.rxled]))
1038
            basic.append(rs)
1039

    
1040
            rs = RadioSetting("txled", "TX LED Color",
1041
                              RadioSettingValueList(COLOR_LIST,
1042
                                                    COLOR_LIST[
1043
                                                    _settings.txled]))
1044
            basic.append(rs)
1045

    
1046
        if self.MODEL == "UV-82":
1047
            rs = RadioSetting("roger", "Roger Beep (TX)",
1048
                              RadioSettingValueBoolean(_settings.roger))
1049
            basic.append(rs)
1050
            rs = RadioSetting("rogerrx", "Roger Beep (RX)",
1051
                              RadioSettingValueList(ROGERRX_LIST,
1052
                                                    ROGERRX_LIST[
1053
                                                    _settings.rogerrx]))
1054
            basic.append(rs)
1055
        else:
1056
            rs = RadioSetting("roger", "Roger Beep",
1057
                              RadioSettingValueBoolean(_settings.roger))
1058
            basic.append(rs)
1059

    
1060
        rs = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)",
1061
                          RadioSettingValueBoolean(_settings.ste))
1062
        advanced.append(rs)
1063

    
1064
        rs = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)",
1065
                          RadioSettingValueList(RPSTE_LIST,
1066
                                                RPSTE_LIST[_settings.rpste]))
1067
        advanced.append(rs)
1068

    
1069
        rs = RadioSetting("rptrl", "STE Repeater Delay",
1070
                          RadioSettingValueList(STEDELAY_LIST,
1071
                                                STEDELAY_LIST[_settings.rptrl]))
1072
        advanced.append(rs)
1073

    
1074
        if self.MODEL != "UV-6":
1075
            rs = RadioSetting("reset", "RESET Menu",
1076
                              RadioSettingValueBoolean(_settings.reset))
1077
            advanced.append(rs)
1078

    
1079
            rs = RadioSetting("menu", "All Menus",
1080
                              RadioSettingValueBoolean(_settings.menu))
1081
            advanced.append(rs)
1082

    
1083
        if self.MODEL == "F-11":
1084
            # this is an F-11 only feature
1085
            rs = RadioSetting("vfomrlock", "VFO/MR Button",
1086
                              RadioSettingValueBoolean(_settings.vfomrlock))
1087
            advanced.append(rs)
1088

    
1089
        if self.MODEL == "UV-82":
1090
            # this is a UV-82C only feature
1091
            rs = RadioSetting("vfomrlock", "VFO/MR Switching (UV-82C only)",
1092
                              RadioSettingValueBoolean(_settings.vfomrlock))
1093
            advanced.append(rs)
1094

    
1095
        if self.MODEL == "UV-82":
1096
            # this is an UV-82C only feature
1097
            rs = RadioSetting("singleptt", "Single PTT (UV-82C only)",
1098
                              RadioSettingValueBoolean(_settings.singleptt))
1099
            advanced.append(rs)
1100

    
1101
        if len(self._mmap.get_packed()) == 0x1808:
1102
            # Old image, without aux block
1103
            return group
1104

    
1105
        other = RadioSettingGroup("other", "Other Settings")
1106
        group.append(other)
1107

    
1108
        def _filter(name):
1109
            filtered = ""
1110
            for char in str(name):
1111
                if char in chirp_common.CHARSET_ASCII:
1112
                    filtered += char
1113
                else:
1114
                    filtered += " "
1115
            return filtered
1116

    
1117
        _msg = self._memobj.firmware_msg
1118
        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
1119
        val.set_mutable(False)
1120
        rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
1121
        other.append(rs)
1122

    
1123
        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
1124
        val.set_mutable(False)
1125
        rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
1126
        other.append(rs)
1127

    
1128
        if self.MODEL != "UV-6":
1129
            _msg = self._memobj.sixpoweron_msg
1130
            rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1",
1131
                              RadioSettingValueString(0, 7, _filter(
1132
                                                      _msg.line1)))
1133
            other.append(rs)
1134
            rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2",
1135
                              RadioSettingValueString(0, 7, _filter(
1136
                                                      _msg.line2)))
1137
            other.append(rs)
1138

    
1139
            _msg = self._memobj.poweron_msg
1140
            rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
1141
                              RadioSettingValueString(0, 7, _filter(
1142
                                                      _msg.line1)))
1143
            other.append(rs)
1144
            rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
1145
                              RadioSettingValueString(0, 7, _filter(
1146
                                                      _msg.line2)))
1147
            other.append(rs)
1148

    
1149
            rs = RadioSetting("ponmsg", "Power-On Message",
1150
                              RadioSettingValueList(PONMSG_LIST,
1151
                                                    PONMSG_LIST[
1152
                                                    _settings.ponmsg]))
1153
            other.append(rs)
1154

    
1155
            if self._is_orig():
1156
                limit = "limits_old"
1157
            else:
1158
                limit = "limits_new"
1159

    
1160
            vhf_limit = getattr(self._memobj, limit).vhf
1161
            rs = RadioSetting("%s.vhf.lower" % limit, "VHF Lower Limit (MHz)",
1162
                              RadioSettingValueInteger(1, 1000,
1163
                                                       vhf_limit.lower))
1164
            other.append(rs)
1165

    
1166
            rs = RadioSetting("%s.vhf.upper" % limit, "VHF Upper Limit (MHz)",
1167
                              RadioSettingValueInteger(1, 1000,
1168
                                                       vhf_limit.upper))
1169
            other.append(rs)
1170

    
1171
            rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled",
1172
                              RadioSettingValueBoolean(vhf_limit.enable))
1173
            other.append(rs)
1174

    
1175
            uhf_limit = getattr(self._memobj, limit).uhf
1176
            rs = RadioSetting("%s.uhf.lower" % limit, "UHF Lower Limit (MHz)",
1177
                              RadioSettingValueInteger(1, 1000,
1178
                                                       uhf_limit.lower))
1179
            other.append(rs)
1180
            rs = RadioSetting("%s.uhf.upper" % limit, "UHF Upper Limit (MHz)",
1181
                              RadioSettingValueInteger(1, 1000,
1182
                                                       uhf_limit.upper))
1183
            other.append(rs)
1184
            rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled",
1185
                              RadioSettingValueBoolean(uhf_limit.enable))
1186
            other.append(rs)
1187

    
1188
        if self.MODEL != "UV-6":
1189
            workmode = RadioSettingGroup("workmode", "Work Mode Settings")
1190
            group.append(workmode)
1191

    
1192
            rs = RadioSetting("displayab", "Display",
1193
                              RadioSettingValueList(AB_LIST,
1194
                                                    AB_LIST[
1195
                                                    _settings.displayab]))
1196
            workmode.append(rs)
1197

    
1198
            rs = RadioSetting("workmode", "VFO/MR Mode",
1199
                              RadioSettingValueList(WORKMODE_LIST,
1200
                                                    WORKMODE_LIST[
1201
                                                    _settings.workmode]))
1202
            workmode.append(rs)
1203

    
1204
            rs = RadioSetting("keylock", "Keypad Lock",
1205
                              RadioSettingValueBoolean(_settings.keylock))
1206
            workmode.append(rs)
1207

    
1208
            rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
1209
                              RadioSettingValueInteger(0, 127,
1210
                                                       _wmchannel.mrcha))
1211
            workmode.append(rs)
1212

    
1213
            rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
1214
                              RadioSettingValueInteger(0, 127,
1215
                                                       _wmchannel.mrchb))
1216
            workmode.append(rs)
1217

    
1218
            def convert_bytes_to_freq(bytes):
1219
               real_freq = 0
1220
               for byte in bytes:
1221
                   real_freq = (real_freq * 10) + byte
1222
               return chirp_common.format_freq(real_freq * 10)
1223

    
1224
            def my_validate(value):
1225
                value = chirp_common.parse_freq(value)
1226
                if 17400000 <= value and value < 40000000:
1227
                    msg = ("Can't be between 174.00000-400.00000")
1228
                    raise InvalidValueError(msg)
1229
                return chirp_common.format_freq(value)
1230

    
1231
            def apply_freq(setting, obj):
1232
                value = chirp_common.parse_freq(str(setting.value)) / 10
1233
                obj.band = value >= 40000000
1234
                for i in range(7, -1, -1):
1235
                    obj.freq[i] = value % 10
1236
                    value /= 10
1237

    
1238
            val1a = RadioSettingValueString(0, 10,
1239
                                            convert_bytes_to_freq(_vfoa.freq))
1240
            val1a.set_validate_callback(my_validate)
1241
            rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a)
1242
            rs.set_apply_callback(apply_freq, _vfoa)
1243
            workmode.append(rs)
1244

    
1245
            val1b = RadioSettingValueString(0, 10,
1246
                                            convert_bytes_to_freq(_vfob.freq))
1247
            val1b.set_validate_callback(my_validate)
1248
            rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b)
1249
            rs.set_apply_callback(apply_freq, _vfob)
1250
            workmode.append(rs)
1251

    
1252
            rs = RadioSetting("vfoa.sftd", "VFO A Shift",
1253
                              RadioSettingValueList(SHIFTD_LIST,
1254
                                                    SHIFTD_LIST[_vfoa.sftd]))
1255
            workmode.append(rs)
1256

    
1257
            rs = RadioSetting("vfob.sftd", "VFO B Shift",
1258
                              RadioSettingValueList(SHIFTD_LIST,
1259
                                                    SHIFTD_LIST[_vfob.sftd]))
1260
            workmode.append(rs)
1261

    
1262
            def convert_bytes_to_offset(bytes):
1263
               real_offset = 0
1264
               for byte in bytes:
1265
                   real_offset = (real_offset * 10) + byte
1266
               return chirp_common.format_freq(real_offset * 10000)
1267

    
1268
            def apply_offset(setting, obj):
1269
                value = chirp_common.parse_freq(str(setting.value)) / 10000
1270
                for i in range(3, -1, -1):
1271
                    obj.offset[i] = value % 10
1272
                    value /= 10
1273

    
1274
            val1a = RadioSettingValueString(0, 10,
1275
                                            convert_bytes_to_offset(
1276
                                            _vfoa.offset))
1277
            rs = RadioSetting("vfoa.offset", "VFO A Offset (0.00-69.95)", val1a)
1278
            rs.set_apply_callback(apply_offset, _vfoa)
1279
            workmode.append(rs)
1280

    
1281
            val1b = RadioSettingValueString(0, 10,
1282
                                            convert_bytes_to_offset(
1283
                                            _vfob.offset))
1284
            rs = RadioSetting("vfob.offset", "VFO B Offset (0.00-69.95)", val1b)
1285
            rs.set_apply_callback(apply_offset, _vfob)
1286
            workmode.append(rs)
1287

    
1288
            if self.MODEL == "KT-980HP" or self.MODEL == "BF-F8HP":
1289
                rs = RadioSetting("vfoa.txpower3", "VFO A Power",
1290
                                  RadioSettingValueList(TXPOWER3_LIST,
1291
                                                        TXPOWER3_LIST[
1292
                                                        _vfoa.txpower3]))
1293
                workmode.append(rs)
1294

    
1295
                rs = RadioSetting("vfob.txpower3", "VFO B Power",
1296
                                  RadioSettingValueList(TXPOWER3_LIST,
1297
                                                        TXPOWER3_LIST[
1298
                                                        _vfob.txpower3]))
1299
                workmode.append(rs)
1300
            else:
1301
                rs = RadioSetting("vfoa.txpower", "VFO A Power",
1302
                                  RadioSettingValueList(TXPOWER_LIST,
1303
                                                        TXPOWER_LIST[
1304
                                                        _vfoa.txpower]))
1305
                workmode.append(rs)
1306

    
1307
                rs = RadioSetting("vfob.txpower", "VFO B Power",
1308
                                  RadioSettingValueList(TXPOWER_LIST,
1309
                                                        TXPOWER_LIST[
1310
                                                        _vfob.txpower]))
1311
                workmode.append(rs)
1312

    
1313

    
1314
            rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth",
1315
                              RadioSettingValueList(BANDWIDTH_LIST,
1316
                                                    BANDWIDTH_LIST[
1317
                                                    _vfoa.widenarr]))
1318
            workmode.append(rs)
1319

    
1320
            rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
1321
                              RadioSettingValueList(BANDWIDTH_LIST,
1322
                                                    BANDWIDTH_LIST[
1323
                                                    _vfob.widenarr]))
1324
            workmode.append(rs)
1325

    
1326
            rs = RadioSetting("vfoa.scode", "VFO A PTT-ID",
1327
                              RadioSettingValueList(PTTIDCODE_LIST,
1328
                                                    PTTIDCODE_LIST[
1329
                                                    _vfoa.scode]))
1330
            workmode.append(rs)
1331

    
1332
            rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
1333
                              RadioSettingValueList(PTTIDCODE_LIST,
1334
                                                    PTTIDCODE_LIST[
1335
                                                    _vfob.scode]))
1336
            workmode.append(rs)
1337

    
1338
            if not self._is_orig():
1339
                rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
1340
                                  RadioSettingValueList(STEP291_LIST,
1341
                                                        STEP291_LIST[
1342
                                                        _vfoa.step]))
1343
                workmode.append(rs)
1344
                rs = RadioSetting("vfob.step", "VFO B Tuning Step",
1345
                                  RadioSettingValueList(STEP291_LIST,
1346
                                                        STEP291_LIST[
1347
                                                        _vfob.step]))
1348
                workmode.append(rs)
1349
            else:
1350
                rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
1351
                                  RadioSettingValueList(STEP_LIST,
1352
                                                        STEP_LIST[_vfoa.step]))
1353
                workmode.append(rs)
1354
                rs = RadioSetting("vfob.step", "VFO B Tuning Step",
1355
                                  RadioSettingValueList(STEP_LIST,
1356
                                                        STEP_LIST[_vfob.step]))
1357
                workmode.append(rs)
1358

    
1359
        fm_preset = RadioSettingGroup("fm_preset", "FM Radio Preset")
1360
        group.append(fm_preset)
1361

    
1362
        if self._memobj.fm_presets <= 116.1 * 10 - 650:
1363
            preset = self._memobj.fm_presets / 10.0 + 65
1364
        else:
1365
            preset = 76.0
1366
        rs = RadioSetting("fm_presets", "FM Preset(MHz)",
1367
                      RadioSettingValueFloat(65, 116.1, preset, 0.1, 1))
1368
        fm_preset.append(rs)
1369

    
1370
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1371
        group.append(dtmf)
1372
        dtmfchars = "0123456789 *#ABCD"
1373

    
1374
        for i in range(0, 15):
1375
            _codeobj = self._memobj.pttid[i].code
1376
            _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1377
            val = RadioSettingValueString(0, 5, _code, False)
1378
            val.set_charset(dtmfchars)
1379
            rs = RadioSetting("pttid/%i.code" % i,
1380
                              "PTT ID Code %i" % (i + 1), val)
1381
            def apply_code(setting, obj):
1382
                code = []
1383
                for j in range(0, 5):
1384
                    try:
1385
                        code.append(dtmfchars.index(str(setting.value)[j]))
1386
                    except IndexError:
1387
                        code.append(0xFF)
1388
                obj.code = code
1389
            rs.set_apply_callback(apply_code, self._memobj.pttid[i])
1390
            dtmf.append(rs)
1391

    
1392
        _codeobj = self._memobj.ani.code
1393
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1394
        val = RadioSettingValueString(0, 5, _code, False)
1395
        val.set_charset(dtmfchars)
1396
        rs = RadioSetting("ani.code", "ANI Code", val)
1397
        def apply_code(setting, obj):
1398
            code = []
1399
            for j in range(0, 5):
1400
                try:
1401
                    code.append(dtmfchars.index(str(setting.value)[j]))
1402
                except IndexError:
1403
                    code.append(0xFF)
1404
            obj.code = code
1405
        rs.set_apply_callback(apply_code, _ani)
1406
        dtmf.append(rs)
1407

    
1408
        rs = RadioSetting("ani.aniid", "ANI ID",
1409
                          RadioSettingValueList(PTTID_LIST,
1410
                                                PTTID_LIST[_ani.aniid]))
1411
        dtmf.append(rs)
1412

    
1413
        _codeobj = self._memobj.ani.alarmcode
1414
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1415
        val = RadioSettingValueString(0, 3, _code, False)
1416
        val.set_charset(dtmfchars)
1417
        rs = RadioSetting("ani.alarmcode", "Alarm Code", val)
1418
        def apply_code(setting, obj):
1419
            alarmcode = []
1420
            for j in range(0, 3):
1421
                try:
1422
                    alarmcode.append(dtmfchars.index(str(setting.value)[j]))
1423
                except IndexError:
1424
                    alarmcode.append(0xFF)
1425
            obj.alarmcode = alarmcode
1426
        rs.set_apply_callback(apply_code, _ani)
1427
        dtmf.append(rs)
1428

    
1429
        rs = RadioSetting("dtmfst", "DTMF Sidetone",
1430
                          RadioSettingValueList(DTMFST_LIST,
1431
                                                DTMFST_LIST[_settings.dtmfst]))
1432
        dtmf.append(rs)
1433

    
1434
        if _ani.dtmfon > 0xC3:
1435
            val = 0x00
1436
        else:
1437
            val = _ani.dtmfon
1438
        rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
1439
                          RadioSettingValueList(DTMFSPEED_LIST,
1440
                                                DTMFSPEED_LIST[val]))
1441
        dtmf.append(rs)
1442

    
1443
        if _ani.dtmfoff > 0xC3:
1444
            val = 0x00
1445
        else:
1446
            val = _ani.dtmfoff
1447
        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
1448
                          RadioSettingValueList(DTMFSPEED_LIST,
1449
                                                DTMFSPEED_LIST[val]))
1450
        dtmf.append(rs)
1451

    
1452
        return group
1453

    
1454
    def get_settings(self):
1455
        try:
1456
            return self._get_settings()
1457
        except:
1458
            import traceback
1459
            print "Failed to parse settings:"
1460
            traceback.print_exc()
1461
            return None
1462

    
1463
    def set_settings(self, settings):
1464
        _settings = self._memobj.settings
1465
        for element in settings:
1466
            if not isinstance(element, RadioSetting):
1467
                if element.get_name() == "fm_preset" :
1468
                    self._set_fm_preset(element)
1469
                else:
1470
                    self.set_settings(element)
1471
                    continue
1472
            else:
1473
                try:
1474
                    name = element.get_name()
1475
                    if "." in name:
1476
                        bits = name.split(".")
1477
                        obj = self._memobj
1478
                        for bit in bits[:-1]:
1479
                            if "/" in bit:
1480
                                bit, index = bit.split("/", 1)
1481
                                index = int(index)
1482
                                obj = getattr(obj, bit)[index]
1483
                            else:
1484
                                obj = getattr(obj, bit)
1485
                        setting = bits[-1]
1486
                    else:
1487
                        obj = _settings
1488
                        setting = element.get_name()
1489

    
1490
                    if element.has_apply_callback():
1491
                        print "Using apply callback"
1492
                        element.run_apply_callback()
1493
                    else:
1494
                        print "Setting %s = %s" % (setting, element.value)
1495
                        setattr(obj, setting, element.value)
1496
                except Exception, e:
1497
                    print element.get_name()
1498
                    raise
1499

    
1500
    def _set_fm_preset(self, settings):
1501
        for element in settings:
1502
            try:
1503
                val = element.value
1504
                value = int(val.get_value() * 10 - 650)
1505
                print "Setting fm_presets = %s" % (value)
1506
                self._memobj.fm_presets = value
1507
            except Exception, e:
1508
                print element.get_name()
1509
                raise
1510

    
1511
@directory.register
1512
class BaofengF11Radio(BaofengUV5R):
1513
    VENDOR = "Baofeng"
1514
    MODEL = "F-11"
1515
    _basetype = BASETYPE_F11
1516
    _idents = [UV5R_MODEL_F11]
1517

    
1518
    def _is_orig(self):
1519
        # Override this for F11 to always return False
1520
        return False
1521

    
1522
@directory.register
1523
class BaofengUV82Radio(BaofengUV5R):
1524
    MODEL = "UV-82"
1525
    _basetype = BASETYPE_UV82
1526
    _idents = [UV5R_MODEL_UV82]
1527
    _vhf_range = (130000000, 176000000)
1528
    _uhf_range = (400000000, 521000000)
1529

    
1530
    def _is_orig(self):
1531
        # Override this for UV82 to always return False
1532
        return False
1533

    
1534
@directory.register
1535
class BaofengUV6Radio(BaofengUV5R):
1536
    """Baofeng UV-6/UV-7"""
1537
    VENDOR = "Baofeng"
1538
    MODEL = "UV-6"
1539
    _basetype = BASETYPE_UV6
1540
    _idents = [UV5R_MODEL_UV6]
1541

    
1542
    def get_features(self):
1543
        rf = BaofengUV5R.get_features(self)
1544
        rf.memory_bounds = (1, 128)
1545
        return rf
1546

    
1547
    def _get_mem(self, number):
1548
        return self._memobj.memory[number - 1]
1549

    
1550
    def _get_nam(self, number):
1551
        return self._memobj.names[number - 1]
1552

    
1553
    def _set_mem(self, number):
1554
        return self._memobj.memory[number - 1]
1555

    
1556
    def _set_nam(self, number):
1557
        return self._memobj.names[number - 1]
1558

    
1559
    def _is_orig(self):
1560
        # Override this for UV6 to always return False
1561
        return False
1562

    
1563
@directory.register
1564
class IntekKT980Radio(BaofengUV5R):
1565
    VENDOR = "Intek"
1566
    MODEL = "KT-980HP"
1567
    _basetype = BASETYPE_KT980HP
1568
    _idents = [UV5R_MODEL_291]
1569
    _vhf_range = (130000000, 180000000)
1570
    _uhf_range = (400000000, 521000000)
1571

    
1572
    def get_features(self):
1573
        rf = BaofengUV5R.get_features(self)
1574
        rf.valid_power_levels = UV5R_POWER_LEVELS3
1575
        return rf
1576

    
1577
    def _is_orig(self):
1578
        # Override this for KT980HP to always return False
1579
        return False
1580

    
1581
@directory.register
1582
class BaofengBFF8HPRadio(BaofengUV5R):
1583
    VENDOR = "Baofeng"
1584
    MODEL = "BF-F8HP"
1585
    _basetype = BASETYPE_F8HP
1586
    _idents = [UV5R_MODEL_291]
1587
    _vhf_range = (130000000, 180000000)
1588
    _uhf_range = (400000000, 521000000)
1589

    
1590
    def get_features(self):
1591
        rf = BaofengUV5R.get_features(self)
1592
        rf.valid_power_levels = UV5R_POWER_LEVELS3
1593
        return rf
1594

    
1595
    def _is_orig(self):
1596
        # Override this for BFF8HP to always return False
1597
        return False
(4-4/7)