uv5r.py

Temporary test driver for reading BaoFeng UV-8 - #3 - Jim Unroe, 09/25/2014 04:20 pm

Download (55.8 kB)

 
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", "N5R-2"]
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", "N5R-3"]
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
UV5R_MODEL_UV6_ORIG  = "\x50\xBB\xFF\x12\x03\x98\x4D"
354
UV5R_MODEL_UV8  = "\x50\xBB\xFF\x20\x12\x11\x19"
355

    
356
def _upper_band_from_data(data):
357
    return data[0x03:0x04]
358

    
359
def _upper_band_from_image(radio):
360
    return _upper_band_from_data(radio.get_mmap())
361

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

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

    
374
def _special_block_from_data(data, special_block_start, special_block_stop):
375
    special_block_tag = data[special_block_start:special_block_stop]
376
    return special_block_tag
377

    
378
def _special_block_from_image(radio):
379
    special_block = _special_block_from_data(radio.get_mmap(), 0x0CFA, 0x0D01)
380
    if CHIRP_DEBUG:
381
        print "_special_block_from_image: " + util.hexprint(special_block)
382
    return special_block
383

    
384
def _do_ident(radio, magic):
385
    serial = radio.pipe
386
    serial.setTimeout(1)
387

    
388
    print "Sending Magic: %s" % util.hexprint(magic)
389
    for byte in magic:
390
        serial.write(byte)
391
        time.sleep(0.01)
392
    ack = serial.read(1)
393

    
394
    if ack != "\x06":
395
        if ack:
396
            print repr(ack)
397
        raise errors.RadioError("Radio did not respond")
398

    
399
    serial.write("\x02")
400
    ident = serial.read(12)
401

    
402
    print "Ident:\n%s" % util.hexprint(ident)
403

    
404
    serial.write("\x06")
405
    ack = serial.read(1)
406
    if ack != "\x06":
407
        raise errors.RadioError("Radio refused clone")
408

    
409
    return ident
410

    
411
def _read_block(radio, start, size):
412
    msg = struct.pack(">BHB", ord("S"), start, size)
413
    radio.pipe.write(msg)
414

    
415
    answer = radio.pipe.read(4)
416
    if len(answer) != 4:
417
        raise errors.RadioError("Radio refused to send block 0x%04x" % start)
418

    
419
    cmd, addr, length = struct.unpack(">BHB", answer)
420
    if cmd != ord("X") or addr != start or length != size:
421
        print "Invalid answer for block 0x%04x:" % start
422
        print "CMD: %s  ADDR: %04x  SIZE: %02x" % (cmd, addr, length)
423
        raise errors.RadioError("Unknown response from radio")
424

    
425
    chunk = radio.pipe.read(0x40)
426
    if not chunk:
427
        raise errors.RadioError("Radio did not send block 0x%04x" % start)
428
    elif len(chunk) != size:
429
        print "Chunk length was 0x%04i" % len(chunk)
430
        raise errors.RadioError("Radio sent incomplete block 0x%04x" % start)
431

    
432
    radio.pipe.write("\x06")
433

    
434
    ack = radio.pipe.read(1)
435
    if ack != "\x06":
436
        raise errors.RadioError("Radio refused to send block 0x%04x" % start)
437

    
438
    return chunk
439

    
440
def _get_radio_firmware_version(radio):
441
    if radio.MODEL == "BJ-UV55":
442
        block = _read_block(radio, 0x1FF0, 0x40)
443
        version = block[0:6]
444
    else:
445
        block1 = _read_block(radio, 0x1EC0, 0x40)
446
        block2 = _read_block(radio, 0x1F00, 0x40)
447
        block = block1 + block2
448
        version = block[48:62]
449
    return version
450

    
451
def _get_radio_special_block(radio):
452
    block = _read_block(radio, 0xCF0, 0x40)
453
    special_block = block[2:9]
454
    return special_block
455

    
456
def _ident_radio(radio):
457
    for magic in radio._idents:
458
        error = None
459
        try:
460
            data = _do_ident(radio, magic)
461
            return data
462
        except errors.RadioError, e:
463
            print e
464
            error = e
465
            time.sleep(2)
466
    if error:
467
        raise error
468
    raise errors.RadioError("Radio did not respond")
469

    
470
def _do_download(radio):
471
    data = _ident_radio(radio)
472

    
473
    radio_version = _get_radio_firmware_version(radio)
474
    print "Radio Version is %s" % repr(radio_version)
475

    
476
    if not any(type in radio_version for type in radio._basetype):
477
        raise errors.RadioError("Incorrect 'Model' selected.")
478

    
479
    # Main block
480
    if CHIRP_DEBUG:
481
        print "downloading main block..."
482
    for i in range(0, 0x1800, 0x40):
483
        data += _read_block(radio, i, 0x40)
484
        _do_status(radio, i)
485
    if CHIRP_DEBUG:
486
        print "done."
487
        print "downloading aux block..."
488
    # Auxiliary block starts at 0x1ECO (?)
489
    for i in range(0x1EC0, 0x2000, 0x40):
490
        data += _read_block(radio, i, 0x40)
491
    if CHIRP_DEBUG:
492
        print "done."
493
    return memmap.MemoryMap(data)
494

    
495
def _send_block(radio, addr, data):
496
    msg = struct.pack(">BHB", ord("X"), addr, len(data))
497
    radio.pipe.write(msg + data)
498

    
499
    ack = radio.pipe.read(1)
500
    if ack != "\x06":
501
        raise errors.RadioError("Radio refused to accept block 0x%04x" % addr)
502

    
503
def _do_upload(radio):
504
    ident = _ident_radio(radio)
505
    radio_upper_band = ident[3:4]
506
    image_upper_band = _upper_band_from_image(radio)
507

    
508
    if image_upper_band == vhf_220_radio or radio_upper_band == vhf_220_radio:
509
        if image_upper_band != radio_upper_band:
510
            raise errors.RadioError("Image not supported by radio")
511

    
512
    image_version = _firmware_version_from_image(radio)
513
    radio_version = _get_radio_firmware_version(radio)
514
    print "Image Version is %s" % repr(image_version)
515
    print "Radio Version is %s" % repr(radio_version)
516

    
517
    if not any(type in radio_version for type in BASETYPE_LIST):
518
        raise errors.RadioError("Unsupported firmware version: `%s'" %
519
                                radio_version)
520

    
521
    image_special_block = _special_block_from_image(radio)
522
    radio_special_block = _get_radio_special_block(radio)
523
    print "Image Special Block is " + util.hexprint(image_special_block)
524
    print "Radio Special Block is " + util.hexprint(radio_special_block)
525

    
526
    if image_special_block != radio_special_block:
527
        raise errors.RadioError("Image not supported by radio: `%s'" %
528
                                radio_special_block)
529

    
530
    # Main block
531
    for i in range(0x08, 0x1808, 0x10):
532
        _send_block(radio, i - 0x08, radio.get_mmap()[i:i + 0x10])
533
        _do_status(radio, i)
534

    
535
    if len(radio.get_mmap().get_packed()) == 0x1808:
536
        print "Old image, not writing aux block"
537
        return # Old image, no aux block
538

    
539
    if image_version != radio_version:
540
        msg = ("Upload finished, but the 'Other Settings' "
541
               "could not be sent because the firmware "
542
               "version of the image (%s) does not match "
543
               "that of the radio (%s).")
544
        raise errors.RadioError(msg % (image_version, radio_version))
545

    
546
    # Auxiliary block at radio address 0x1EC0, our offset 0x1808
547
    for i in range(0x1EC0, 0x2000, 0x10):
548
        addr = 0x1808 + (i - 0x1EC0)
549
        _send_block(radio, i, radio.get_mmap()[addr:addr + 0x10])
550

    
551
UV5R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=4.00),
552
                     chirp_common.PowerLevel("Low",  watts=1.00)]
553

    
554
UV5R_POWER_LEVELS3 = [chirp_common.PowerLevel("High", watts=8.00),
555
                      chirp_common.PowerLevel("Med",  watts=4.00),
556
                      chirp_common.PowerLevel("Low",  watts=1.00)]
557

    
558
UV5R_DTCS = sorted(chirp_common.DTCS_CODES + [645])
559

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

    
563
# Uncomment this to actually register this radio in CHIRP
564
@directory.register
565
class BaofengUV5R(chirp_common.CloneModeRadio,
566
                  chirp_common.ExperimentalRadio):
567
    """Baofeng UV-5R"""
568
    VENDOR = "Baofeng"
569
    MODEL = "UV-5R"
570
    BAUD_RATE = 9600
571

    
572
    _memsize = 0x1808
573
    _basetype = BASETYPE_UV5R
574
    _idents = [UV5R_MODEL_291,
575
               UV5R_MODEL_ORIG
576
               ]
577
    _vhf_range = (136000000, 174000000)
578
    _220_range = (220000000, 260000000)
579
    _uhf_range = (400000000, 520000000)
580
    _mem_params = ( 0x1828 # poweron_msg offset
581
                    )
582
    # offset of fw version in image file
583
    _fw_ver_file_start = 0x1838
584
    _fw_ver_file_stop = 0x1846
585

    
586
    @classmethod
587
    def get_prompts(cls):
588
        rp = chirp_common.RadioPrompts()
589
        rp.experimental = ('Due to the fact that the manufacturer continues to '
590
                'release new versions of the firmware with obscure and '
591
                'hard-to-track changes, this driver may not work with '
592
                'your device. Thus far and to the best knowledge of the '
593
                'author, no UV-5R radios have been harmed by using CHIRP. '
594
                'However, proceed at your own risk!')
595
        rp.pre_download = _(dedent("""\
596
            1. Turn radio off.
597
            2. Connect cable to mic/spkr connector.
598
            3. Make sure connector is firmly connected.
599
            4. Turn radio on.
600
            5. Ensure that the radio is tuned to channel with no activity.
601
            6. Click OK to download image from device."""))
602
        rp.pre_upload = _(dedent("""\
603
            1. Turn radio off.
604
            2. Connect cable to mic/spkr connector.
605
            3. Make sure connector is firmly connected.
606
            4. Turn radio on.
607
            5. Ensure that the radio is tuned to channel with no activity.
608
            6. Click OK to upload image to device."""))
609
        return rp
610

    
611
    def get_features(self):
612
        rf = chirp_common.RadioFeatures()
613
        rf.has_settings = True
614
        rf.has_bank = False
615
        rf.has_cross = True
616
        rf.has_rx_dtcs = True
617
        rf.has_tuning_step = False
618
        rf.can_odd_split = True
619
        rf.valid_name_length = 7
620
        rf.valid_characters = UV5R_CHARSET
621
        rf.valid_skips = ["", "S"]
622
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
623
        rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
624
                                "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
625
        rf.valid_power_levels = UV5R_POWER_LEVELS
626
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
627
        rf.valid_modes = ["FM", "NFM"]
628

    
629
        normal_bands = [self._vhf_range, self._uhf_range]
630
        rax_bands = [self._vhf_range, self._220_range]
631

    
632
        if self._mmap is None:
633
            rf.valid_bands = [normal_bands[0], rax_bands[1], normal_bands[1]]
634
        elif not self._is_orig() and self._my_upper_band() == vhf_220_radio:
635
            rf.valid_bands = rax_bands
636
        else:
637
            rf.valid_bands = normal_bands
638
        rf.memory_bounds = (0, 127)
639
        return rf
640

    
641
    @classmethod
642
    def match_model(cls, filedata, filename):
643
        match_size = False
644
        match_model = False
645
        if len(filedata) in [0x1808, 0x1948]:
646
            match_size = True
647
        fwdata = _firmware_version_from_data(filedata,
648
                                             cls._fw_ver_file_start,
649
                                             cls._fw_ver_file_stop)
650
        if any(type in fwdata for type in cls._basetype):
651
            match_model = True
652
        if match_size and match_model:
653
            return True
654
        else:
655
            return False
656

    
657
    def process_mmap(self):
658
        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
659

    
660
    def sync_in(self):
661
        try:
662
            self._mmap = _do_download(self)
663
        except errors.RadioError:
664
            raise
665
        except Exception, e:
666
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
667
        self.process_mmap()
668

    
669
    def sync_out(self):
670
        try:
671
            _do_upload(self)
672
        except errors.RadioError:
673
            raise
674
        except Exception, e:
675
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
676

    
677
    def get_raw_memory(self, number):
678
        return repr(self._memobj.memory[number])
679

    
680
    def _is_txinh(self, _mem):
681
        raw_tx = ""
682
        for i in range(0, 4):
683
            raw_tx += _mem.txfreq[i].get_raw()
684
        return raw_tx == "\xFF\xFF\xFF\xFF"
685

    
686
    def _get_mem(self, number):
687
        return self._memobj.memory[number]
688

    
689
    def _get_nam(self, number):
690
        return self._memobj.names[number]
691

    
692
    def get_memory(self, number):
693
        _mem = self._get_mem(number)
694
        _nam = self._get_nam(number)
695

    
696
        mem = chirp_common.Memory()
697
        mem.number = number
698

    
699
        if _mem.get_raw()[0] == "\xff":
700
            mem.empty = True
701
            return mem
702

    
703
        mem.freq = int(_mem.rxfreq) * 10
704

    
705
        if self._is_txinh(_mem):
706
            mem.duplex = "off"
707
            mem.offset = 0
708
        elif int(_mem.rxfreq) == int(_mem.txfreq):
709
            mem.duplex = ""
710
            mem.offset = 0
711
        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
712
            mem.duplex = "split"
713
            mem.offset = int(_mem.txfreq) * 10
714
        else:
715
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
716
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
717

    
718
        for char in _nam.name:
719
            if str(char) == "\xFF":
720
                char = " " # The UV-5R software may have 0xFF mid-name
721
            mem.name += str(char)
722
        mem.name = mem.name.rstrip()
723

    
724
        dtcs_pol = ["N", "N"]
725

    
726
        if _mem.txtone in [0, 0xFFFF]:
727
            txmode = ""
728
        elif _mem.txtone >= 0x0258:
729
            txmode = "Tone"
730
            mem.rtone = int(_mem.txtone) / 10.0
731
        elif _mem.txtone <= 0x0258:
732
            txmode = "DTCS"
733
            if _mem.txtone > 0x69:
734
                index = _mem.txtone - 0x6A
735
                dtcs_pol[0] = "R"
736
            else:
737
                index = _mem.txtone - 1
738
            mem.dtcs = UV5R_DTCS[index]
739
        else:
740
            print "Bug: txtone is %04x" % _mem.txtone
741

    
742
        if _mem.rxtone in [0, 0xFFFF]:
743
            rxmode = ""
744
        elif _mem.rxtone >= 0x0258:
745
            rxmode = "Tone"
746
            mem.ctone = int(_mem.rxtone) / 10.0
747
        elif _mem.rxtone <= 0x0258:
748
            rxmode = "DTCS"
749
            if _mem.rxtone >= 0x6A:
750
                index = _mem.rxtone - 0x6A
751
                dtcs_pol[1] = "R"
752
            else:
753
                index = _mem.rxtone - 1
754
            mem.rx_dtcs = UV5R_DTCS[index]
755
        else:
756
            print "Bug: rxtone is %04x" % _mem.rxtone
757

    
758
        if txmode == "Tone" and not rxmode:
759
            mem.tmode = "Tone"
760
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
761
            mem.tmode = "TSQL"
762
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
763
            mem.tmode = "DTCS"
764
        elif rxmode or txmode:
765
            mem.tmode = "Cross"
766
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
767

    
768
        mem.dtcs_polarity = "".join(dtcs_pol)
769

    
770
        if not _mem.scan:
771
            mem.skip = "S"
772

    
773
        if self.MODEL == "KT-980HP" or self.MODEL == "BF-F8HP":                          
774
            levels = UV5R_POWER_LEVELS3
775
        else:
776
            levels = UV5R_POWER_LEVELS
777
        try:
778
            mem.power = levels[_mem.lowpower]
779
        except IndexError:
780
            print "Radio reported invalid power level %s (in %s)" % (
781
                _mem.power, levels)
782
            mem.power = levels[0]
783

    
784
        mem.mode = _mem.wide and "FM" or "NFM"
785

    
786
        mem.extra = RadioSettingGroup("Extra", "extra")
787

    
788
        rs = RadioSetting("bcl", "BCL",
789
                          RadioSettingValueBoolean(_mem.bcl))
790
        mem.extra.append(rs)
791

    
792
        rs = RadioSetting("pttid", "PTT ID",
793
                          RadioSettingValueList(PTTID_LIST,
794
                                                PTTID_LIST[_mem.pttid]))
795
        mem.extra.append(rs)
796

    
797
        rs = RadioSetting("scode", "PTT ID Code",
798
                          RadioSettingValueList(PTTIDCODE_LIST,
799
                                                PTTIDCODE_LIST[_mem.scode]))
800
        mem.extra.append(rs)
801

    
802
        return mem
803

    
804
    def _set_mem(self, number):
805
        return self._memobj.memory[number]
806

    
807
    def _set_nam(self, number):
808
        return self._memobj.names[number]
809

    
810
    def set_memory(self, mem):
811
        _mem = self._get_mem(mem.number)
812
        _nam = self._get_nam(mem.number)
813

    
814
        if mem.empty:
815
            _mem.set_raw("\xff" * 16)
816
            return
817

    
818
        _mem.set_raw("\x00" * 16)
819

    
820
        _mem.rxfreq = mem.freq / 10
821

    
822
        if mem.duplex == "off":
823
            for i in range(0, 4):
824
                _mem.txfreq[i].set_raw("\xFF")
825
        elif mem.duplex == "split":
826
            _mem.txfreq = mem.offset / 10
827
        elif mem.duplex == "+":
828
            _mem.txfreq = (mem.freq + mem.offset) / 10
829
        elif mem.duplex == "-":
830
            _mem.txfreq = (mem.freq - mem.offset) / 10
831
        else:
832
            _mem.txfreq = mem.freq / 10
833

    
834
        _namelength = self.get_features().valid_name_length
835
        for i in range(_namelength):
836
            try:
837
                _nam.name[i] = mem.name[i]
838
            except IndexError:
839
                _nam.name[i] = "\xFF"
840

    
841
        rxmode = txmode = ""
842
        if mem.tmode == "Tone":
843
            _mem.txtone = int(mem.rtone * 10)
844
            _mem.rxtone = 0
845
        elif mem.tmode == "TSQL":
846
            _mem.txtone = int(mem.ctone * 10)
847
            _mem.rxtone = int(mem.ctone * 10)
848
        elif mem.tmode == "DTCS":
849
            rxmode = txmode = "DTCS"
850
            _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
851
            _mem.rxtone = UV5R_DTCS.index(mem.dtcs) + 1
852
        elif mem.tmode == "Cross":
853
            txmode, rxmode = mem.cross_mode.split("->", 1)
854
            if txmode == "Tone":
855
                _mem.txtone = int(mem.rtone * 10)
856
            elif txmode == "DTCS":
857
                _mem.txtone = UV5R_DTCS.index(mem.dtcs) + 1
858
            else:
859
                _mem.txtone = 0
860
            if rxmode == "Tone":
861
                _mem.rxtone = int(mem.ctone * 10)
862
            elif rxmode == "DTCS":
863
                _mem.rxtone = UV5R_DTCS.index(mem.rx_dtcs) + 1
864
            else:
865
                _mem.rxtone = 0
866
        else:
867
            _mem.rxtone = 0
868
            _mem.txtone = 0
869

    
870
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
871
            _mem.txtone += 0x69
872
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
873
            _mem.rxtone += 0x69
874

    
875
        _mem.scan = mem.skip != "S"
876
        _mem.wide = mem.mode == "FM"
877

    
878
        if mem.power:
879
            if self.MODEL == "KT-980HP" or self.MODEL == "BF-F8HP":
880
                levels = [str(l) for l in UV5R_POWER_LEVELS3]
881
                _mem.lowpower = levels.index(str(mem.power))
882
            else:
883
                _mem.lowpower = UV5R_POWER_LEVELS.index(mem.power)
884
        else:
885
            _mem.lowpower = 0
886

    
887
        for setting in mem.extra:
888
            setattr(_mem, setting.get_name(), setting.value)
889

    
890
    def _is_orig(self):
891
        version_tag = _firmware_version_from_image(self)
892
        if CHIRP_DEBUG:
893
            print "@_is_orig, version_tag:", util.hexprint(version_tag)
894
        try:
895
            if 'BFB' in version_tag:
896
                idx = version_tag.index("BFB") + 3
897
                version = int(version_tag[idx:idx + 3])
898
                return version < 291
899
            return False
900
        except:
901
            pass
902
        raise errors.RadioError("Unable to parse version string %s" %
903
                                version_tag)
904

    
905
    def _my_version(self):
906
        version_tag = _firmware_version_from_image(self)
907
        if 'BFS' in version_tag:
908
            idx = version_tag.index("BFS") + 3
909
            return int(version_tag[idx:idx + 3])
910
        elif 'BF82' in version_tag:
911
            idx = version_tag.index("BF82") + 2
912
            return int(version_tag[idx:idx + 4])
913
        elif 'B82S' in version_tag:
914
            idx = version_tag.index("B82S") + 4
915
            return int(version_tag[idx:idx + 2]) + 8200
916
        elif 'US2S' in version_tag:
917
            idx = version_tag.index("US2S") + 4
918
            return int(version_tag[idx:idx + 2]) + 8200
919
        elif 'USA' in version_tag:
920
            idx = version_tag.index("USA") + 3
921
            return int(version_tag[idx:idx + 3]) + 11000
922
        elif 'BJ55' in version_tag:
923
            idx = version_tag.index("BJ55") + 2
924
            return int(version_tag[idx:idx + 4])
925
        elif 'BF1' in version_tag:
926
            idx = version_tag.index("BF1") + 2
927
            return int(version_tag[idx:idx + 4])
928
        elif 'BFP' in version_tag:
929
            idx = version_tag.index("BFP") + 5
930
            return int(version_tag[idx:idx + 1]) + 98000
931
        elif 'N5R-2' in version_tag:
932
            idx = version_tag.index("N5R-2") + 4
933
            return int(version_tag[idx:idx + 2]) + 300
934
        elif 'N5R-3' in version_tag:
935
            idx = version_tag.index("N5R-3") + 4
936
            return int(version_tag[idx:idx + 2]) + 98000
937
        elif 'BFB' in version_tag:
938
            idx = version_tag.index("BFB") + 3
939
            return int(version_tag[idx:idx + 3])
940

    
941
        raise Exception("Unrecognized firmware version string")
942

    
943
    def _my_upper_band(self):
944
        band_tag = _upper_band_from_image(self)
945
        return band_tag
946

    
947
    def _get_settings(self):
948
        _ani = self._memobj.ani
949
        _settings = self._memobj.settings
950
        _vfoa = self._memobj.vfoa
951
        _vfob = self._memobj.vfob
952
        _wmchannel = self._memobj.wmchannel
953
        basic = RadioSettingGroup("basic", "Basic Settings")
954
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
955
        group = RadioSettingGroup("top", "All Settings", basic, advanced)
956

    
957
        rs = RadioSetting("squelch", "Carrier Squelch Level",
958
                          RadioSettingValueInteger(0, 9, _settings.squelch))
959
        basic.append(rs)
960

    
961
        rs = RadioSetting("save", "Battery Saver",
962
                          RadioSettingValueList(SAVE_LIST,
963
                                                SAVE_LIST[_settings.save]))
964
        basic.append(rs)
965

    
966
        rs = RadioSetting("vox", "VOX Sensitivity",
967
                          RadioSettingValueList(VOX_LIST,
968
                                                VOX_LIST[_settings.vox]))
969
        advanced.append(rs)
970

    
971
        if self.MODEL == "UV-6":
972
            # NOTE: The UV-6 calls this byte voxenable, but the UV-5R calls it
973
            # autolk. Since this is a minor difference, it will be referred to
974
            # by the wrong name for the UV-6.
975
            rs = RadioSetting("autolk", "Vox",
976
                              RadioSettingValueBoolean(_settings.autolk))
977
            advanced.append(rs)
978

    
979
        if self.MODEL != "UV-6":
980
            rs = RadioSetting("abr", "Backlight Timeout",
981
                              RadioSettingValueInteger(0, 24, _settings.abr))
982
            basic.append(rs)
983

    
984
        rs = RadioSetting("tdr", "Dual Watch",
985
                          RadioSettingValueBoolean(_settings.tdr))
986
        advanced.append(rs)
987

    
988
        if self.MODEL == "UV-6":
989
            rs = RadioSetting("tdrch", "Dual Watch Channel",
990
                              RadioSettingValueList(TDRCH_LIST,
991
                                                    TDRCH_LIST[
992
                                                    _settings.tdrch]))
993
            advanced.append(rs)
994

    
995
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
996
                              RadioSettingValueBoolean(_settings.tdrab))
997
            advanced.append(rs)
998
        else:
999
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
1000
                              RadioSettingValueList(TDRAB_LIST,
1001
                                                    TDRAB_LIST[
1002
                                                    _settings.tdrab]))
1003
            advanced.append(rs)
1004

    
1005
        if self.MODEL == "UV-6":
1006
            rs = RadioSetting("alarm", "Alarm Sound",
1007
                              RadioSettingValueBoolean(_settings.alarm))
1008
            advanced.append(rs)
1009

    
1010
        rs = RadioSetting("almod", "Alarm Mode",
1011
                          RadioSettingValueList(ALMOD_LIST,
1012
                                                ALMOD_LIST[_settings.almod]))
1013
        advanced.append(rs)
1014

    
1015
        rs = RadioSetting("beep", "Beep",
1016
                          RadioSettingValueBoolean(_settings.beep))
1017
        basic.append(rs)
1018

    
1019
        rs = RadioSetting("timeout", "Timeout Timer",
1020
                          RadioSettingValueList(TIMEOUT_LIST,
1021
                                                TIMEOUT_LIST[
1022
                                                _settings.timeout]))
1023
        basic.append(rs)
1024

    
1025
        if self._is_orig() and self._my_version() < 251:
1026
            rs = RadioSetting("voice", "Voice",
1027
                              RadioSettingValueBoolean(_settings.voice))
1028
            advanced.append(rs)
1029
        else:
1030
            rs = RadioSetting("voice", "Voice",
1031
                              RadioSettingValueList(VOICE_LIST,
1032
                                                    VOICE_LIST[
1033
                                                    _settings.voice]))
1034
            advanced.append(rs)
1035

    
1036
        rs = RadioSetting("screv", "Scan Resume",
1037
                          RadioSettingValueList(RESUME_LIST,
1038
                                                RESUME_LIST[_settings.screv]))
1039
        advanced.append(rs)
1040

    
1041
        if self.MODEL != "UV-6":
1042
            rs = RadioSetting("mdfa", "Display Mode (A)",
1043
                              RadioSettingValueList(MODE_LIST,
1044
                                                    MODE_LIST[_settings.mdfa]))
1045
            basic.append(rs)
1046

    
1047
            rs = RadioSetting("mdfb", "Display Mode (B)",
1048
                              RadioSettingValueList(MODE_LIST,
1049
                                                    MODE_LIST[_settings.mdfb]))
1050
            basic.append(rs)
1051

    
1052
        rs = RadioSetting("bcl", "Busy Channel Lockout",
1053
                          RadioSettingValueBoolean(_settings.bcl))
1054
        advanced.append(rs)
1055

    
1056
        if self.MODEL != "UV-6":
1057
            rs = RadioSetting("autolk", "Automatic Key Lock",
1058
                              RadioSettingValueBoolean(_settings.autolk))
1059
            advanced.append(rs)
1060

    
1061
        rs = RadioSetting("fmradio", "Broadcast FM Radio",
1062
                          RadioSettingValueBoolean(_settings.fmradio))
1063
        advanced.append(rs)
1064

    
1065
        if self.MODEL != "UV-6":
1066
            rs = RadioSetting("wtled", "Standby LED Color",
1067
                              RadioSettingValueList(COLOR_LIST,
1068
                                                    COLOR_LIST[
1069
                                                    _settings.wtled]))
1070
            basic.append(rs)
1071

    
1072
            rs = RadioSetting("rxled", "RX LED Color",
1073
                              RadioSettingValueList(COLOR_LIST,
1074
                                                    COLOR_LIST[
1075
                                                    _settings.rxled]))
1076
            basic.append(rs)
1077

    
1078
            rs = RadioSetting("txled", "TX LED Color",
1079
                              RadioSettingValueList(COLOR_LIST,
1080
                                                    COLOR_LIST[
1081
                                                    _settings.txled]))
1082
            basic.append(rs)
1083

    
1084
        if self.MODEL == "UV-82":
1085
            rs = RadioSetting("roger", "Roger Beep (TX)",
1086
                              RadioSettingValueBoolean(_settings.roger))
1087
            basic.append(rs)
1088
            rs = RadioSetting("rogerrx", "Roger Beep (RX)",
1089
                              RadioSettingValueList(ROGERRX_LIST,
1090
                                                    ROGERRX_LIST[
1091
                                                    _settings.rogerrx]))
1092
            basic.append(rs)
1093
        else:
1094
            rs = RadioSetting("roger", "Roger Beep",
1095
                              RadioSettingValueBoolean(_settings.roger))
1096
            basic.append(rs)
1097

    
1098
        rs = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)",
1099
                          RadioSettingValueBoolean(_settings.ste))
1100
        advanced.append(rs)
1101

    
1102
        rs = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)",
1103
                          RadioSettingValueList(RPSTE_LIST,
1104
                                                RPSTE_LIST[_settings.rpste]))
1105
        advanced.append(rs)
1106

    
1107
        rs = RadioSetting("rptrl", "STE Repeater Delay",
1108
                          RadioSettingValueList(STEDELAY_LIST,
1109
                                                STEDELAY_LIST[_settings.rptrl]))
1110
        advanced.append(rs)
1111

    
1112
        if self.MODEL != "UV-6":
1113
            rs = RadioSetting("reset", "RESET Menu",
1114
                              RadioSettingValueBoolean(_settings.reset))
1115
            advanced.append(rs)
1116

    
1117
            rs = RadioSetting("menu", "All Menus",
1118
                              RadioSettingValueBoolean(_settings.menu))
1119
            advanced.append(rs)
1120

    
1121
        if self.MODEL == "F-11":
1122
            # this is an F-11 only feature
1123
            rs = RadioSetting("vfomrlock", "VFO/MR Button",
1124
                              RadioSettingValueBoolean(_settings.vfomrlock))
1125
            advanced.append(rs)
1126

    
1127
        if self.MODEL == "UV-82":
1128
            # this is a UV-82C only feature
1129
            rs = RadioSetting("vfomrlock", "VFO/MR Switching (UV-82C only)",
1130
                              RadioSettingValueBoolean(_settings.vfomrlock))
1131
            advanced.append(rs)
1132

    
1133
        if self.MODEL == "UV-82":
1134
            # this is an UV-82C only feature
1135
            rs = RadioSetting("singleptt", "Single PTT (UV-82C only)",
1136
                              RadioSettingValueBoolean(_settings.singleptt))
1137
            advanced.append(rs)
1138

    
1139
        if len(self._mmap.get_packed()) == 0x1808:
1140
            # Old image, without aux block
1141
            return group
1142

    
1143
        other = RadioSettingGroup("other", "Other Settings")
1144
        group.append(other)
1145

    
1146
        def _filter(name):
1147
            filtered = ""
1148
            for char in str(name):
1149
                if char in chirp_common.CHARSET_ASCII:
1150
                    filtered += char
1151
                else:
1152
                    filtered += " "
1153
            return filtered
1154

    
1155
        _msg = self._memobj.firmware_msg
1156
        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
1157
        val.set_mutable(False)
1158
        rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
1159
        other.append(rs)
1160

    
1161
        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
1162
        val.set_mutable(False)
1163
        rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
1164
        other.append(rs)
1165

    
1166
        if self.MODEL != "UV-6":
1167
            _msg = self._memobj.sixpoweron_msg
1168
            rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1",
1169
                              RadioSettingValueString(0, 7, _filter(
1170
                                                      _msg.line1)))
1171
            other.append(rs)
1172
            rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2",
1173
                              RadioSettingValueString(0, 7, _filter(
1174
                                                      _msg.line2)))
1175
            other.append(rs)
1176

    
1177
            _msg = self._memobj.poweron_msg
1178
            rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
1179
                              RadioSettingValueString(0, 7, _filter(
1180
                                                      _msg.line1)))
1181
            other.append(rs)
1182
            rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
1183
                              RadioSettingValueString(0, 7, _filter(
1184
                                                      _msg.line2)))
1185
            other.append(rs)
1186

    
1187
            rs = RadioSetting("ponmsg", "Power-On Message",
1188
                              RadioSettingValueList(PONMSG_LIST,
1189
                                                    PONMSG_LIST[
1190
                                                    _settings.ponmsg]))
1191
            other.append(rs)
1192

    
1193
            if self._is_orig():
1194
                limit = "limits_old"
1195
            else:
1196
                limit = "limits_new"
1197

    
1198
            vhf_limit = getattr(self._memobj, limit).vhf
1199
            rs = RadioSetting("%s.vhf.lower" % limit, "VHF Lower Limit (MHz)",
1200
                              RadioSettingValueInteger(1, 1000,
1201
                                                       vhf_limit.lower))
1202
            other.append(rs)
1203

    
1204
            rs = RadioSetting("%s.vhf.upper" % limit, "VHF Upper Limit (MHz)",
1205
                              RadioSettingValueInteger(1, 1000,
1206
                                                       vhf_limit.upper))
1207
            other.append(rs)
1208

    
1209
            rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled",
1210
                              RadioSettingValueBoolean(vhf_limit.enable))
1211
            other.append(rs)
1212

    
1213
            uhf_limit = getattr(self._memobj, limit).uhf
1214
            rs = RadioSetting("%s.uhf.lower" % limit, "UHF Lower Limit (MHz)",
1215
                              RadioSettingValueInteger(1, 1000,
1216
                                                       uhf_limit.lower))
1217
            other.append(rs)
1218
            rs = RadioSetting("%s.uhf.upper" % limit, "UHF Upper Limit (MHz)",
1219
                              RadioSettingValueInteger(1, 1000,
1220
                                                       uhf_limit.upper))
1221
            other.append(rs)
1222
            rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled",
1223
                              RadioSettingValueBoolean(uhf_limit.enable))
1224
            other.append(rs)
1225

    
1226
        if self.MODEL != "UV-6":
1227
            workmode = RadioSettingGroup("workmode", "Work Mode Settings")
1228
            group.append(workmode)
1229

    
1230
            rs = RadioSetting("displayab", "Display",
1231
                              RadioSettingValueList(AB_LIST,
1232
                                                    AB_LIST[
1233
                                                    _settings.displayab]))
1234
            workmode.append(rs)
1235

    
1236
            rs = RadioSetting("workmode", "VFO/MR Mode",
1237
                              RadioSettingValueList(WORKMODE_LIST,
1238
                                                    WORKMODE_LIST[
1239
                                                    _settings.workmode]))
1240
            workmode.append(rs)
1241

    
1242
            rs = RadioSetting("keylock", "Keypad Lock",
1243
                              RadioSettingValueBoolean(_settings.keylock))
1244
            workmode.append(rs)
1245

    
1246
            rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
1247
                              RadioSettingValueInteger(0, 127,
1248
                                                       _wmchannel.mrcha))
1249
            workmode.append(rs)
1250

    
1251
            rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
1252
                              RadioSettingValueInteger(0, 127,
1253
                                                       _wmchannel.mrchb))
1254
            workmode.append(rs)
1255

    
1256
            def convert_bytes_to_freq(bytes):
1257
               real_freq = 0
1258
               for byte in bytes:
1259
                   real_freq = (real_freq * 10) + byte
1260
               return chirp_common.format_freq(real_freq * 10)
1261

    
1262
            def my_validate(value):
1263
                value = chirp_common.parse_freq(value)
1264
                if 17400000 <= value and value < 40000000:
1265
                    msg = ("Can't be between 174.00000-400.00000")
1266
                    raise InvalidValueError(msg)
1267
                return chirp_common.format_freq(value)
1268

    
1269
            def apply_freq(setting, obj):
1270
                value = chirp_common.parse_freq(str(setting.value)) / 10
1271
                obj.band = value >= 40000000
1272
                for i in range(7, -1, -1):
1273
                    obj.freq[i] = value % 10
1274
                    value /= 10
1275

    
1276
            val1a = RadioSettingValueString(0, 10,
1277
                                            convert_bytes_to_freq(_vfoa.freq))
1278
            val1a.set_validate_callback(my_validate)
1279
            rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a)
1280
            rs.set_apply_callback(apply_freq, _vfoa)
1281
            workmode.append(rs)
1282

    
1283
            val1b = RadioSettingValueString(0, 10,
1284
                                            convert_bytes_to_freq(_vfob.freq))
1285
            val1b.set_validate_callback(my_validate)
1286
            rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b)
1287
            rs.set_apply_callback(apply_freq, _vfob)
1288
            workmode.append(rs)
1289

    
1290
            rs = RadioSetting("vfoa.sftd", "VFO A Shift",
1291
                              RadioSettingValueList(SHIFTD_LIST,
1292
                                                    SHIFTD_LIST[_vfoa.sftd]))
1293
            workmode.append(rs)
1294

    
1295
            rs = RadioSetting("vfob.sftd", "VFO B Shift",
1296
                              RadioSettingValueList(SHIFTD_LIST,
1297
                                                    SHIFTD_LIST[_vfob.sftd]))
1298
            workmode.append(rs)
1299

    
1300
            def convert_bytes_to_offset(bytes):
1301
               real_offset = 0
1302
               for byte in bytes:
1303
                   real_offset = (real_offset * 10) + byte
1304
               return chirp_common.format_freq(real_offset * 10000)
1305

    
1306
            def apply_offset(setting, obj):
1307
                value = chirp_common.parse_freq(str(setting.value)) / 10000
1308
                for i in range(3, -1, -1):
1309
                    obj.offset[i] = value % 10
1310
                    value /= 10
1311

    
1312
            val1a = RadioSettingValueString(0, 10,
1313
                                            convert_bytes_to_offset(
1314
                                            _vfoa.offset))
1315
            rs = RadioSetting("vfoa.offset", "VFO A Offset (0.00-69.95)", val1a)
1316
            rs.set_apply_callback(apply_offset, _vfoa)
1317
            workmode.append(rs)
1318

    
1319
            val1b = RadioSettingValueString(0, 10,
1320
                                            convert_bytes_to_offset(
1321
                                            _vfob.offset))
1322
            rs = RadioSetting("vfob.offset", "VFO B Offset (0.00-69.95)", val1b)
1323
            rs.set_apply_callback(apply_offset, _vfob)
1324
            workmode.append(rs)
1325

    
1326
            if self.MODEL == "KT-980HP" or self.MODEL == "BF-F8HP":
1327
                rs = RadioSetting("vfoa.txpower3", "VFO A Power",
1328
                                  RadioSettingValueList(TXPOWER3_LIST,
1329
                                                        TXPOWER3_LIST[
1330
                                                        _vfoa.txpower3]))
1331
                workmode.append(rs)
1332

    
1333
                rs = RadioSetting("vfob.txpower3", "VFO B Power",
1334
                                  RadioSettingValueList(TXPOWER3_LIST,
1335
                                                        TXPOWER3_LIST[
1336
                                                        _vfob.txpower3]))
1337
                workmode.append(rs)
1338
            else:
1339
                rs = RadioSetting("vfoa.txpower", "VFO A Power",
1340
                                  RadioSettingValueList(TXPOWER_LIST,
1341
                                                        TXPOWER_LIST[
1342
                                                        _vfoa.txpower]))
1343
                workmode.append(rs)
1344

    
1345
                rs = RadioSetting("vfob.txpower", "VFO B Power",
1346
                                  RadioSettingValueList(TXPOWER_LIST,
1347
                                                        TXPOWER_LIST[
1348
                                                        _vfob.txpower]))
1349
                workmode.append(rs)
1350

    
1351

    
1352
            rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth",
1353
                              RadioSettingValueList(BANDWIDTH_LIST,
1354
                                                    BANDWIDTH_LIST[
1355
                                                    _vfoa.widenarr]))
1356
            workmode.append(rs)
1357

    
1358
            rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
1359
                              RadioSettingValueList(BANDWIDTH_LIST,
1360
                                                    BANDWIDTH_LIST[
1361
                                                    _vfob.widenarr]))
1362
            workmode.append(rs)
1363

    
1364
            rs = RadioSetting("vfoa.scode", "VFO A PTT-ID",
1365
                              RadioSettingValueList(PTTIDCODE_LIST,
1366
                                                    PTTIDCODE_LIST[
1367
                                                    _vfoa.scode]))
1368
            workmode.append(rs)
1369

    
1370
            rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
1371
                              RadioSettingValueList(PTTIDCODE_LIST,
1372
                                                    PTTIDCODE_LIST[
1373
                                                    _vfob.scode]))
1374
            workmode.append(rs)
1375

    
1376
            if not self._is_orig():
1377
                rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
1378
                                  RadioSettingValueList(STEP291_LIST,
1379
                                                        STEP291_LIST[
1380
                                                        _vfoa.step]))
1381
                workmode.append(rs)
1382
                rs = RadioSetting("vfob.step", "VFO B Tuning Step",
1383
                                  RadioSettingValueList(STEP291_LIST,
1384
                                                        STEP291_LIST[
1385
                                                        _vfob.step]))
1386
                workmode.append(rs)
1387
            else:
1388
                rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
1389
                                  RadioSettingValueList(STEP_LIST,
1390
                                                        STEP_LIST[_vfoa.step]))
1391
                workmode.append(rs)
1392
                rs = RadioSetting("vfob.step", "VFO B Tuning Step",
1393
                                  RadioSettingValueList(STEP_LIST,
1394
                                                        STEP_LIST[_vfob.step]))
1395
                workmode.append(rs)
1396

    
1397
        fm_preset = RadioSettingGroup("fm_preset", "FM Radio Preset")
1398
        group.append(fm_preset)
1399

    
1400
        if self._memobj.fm_presets <= 116.1 * 10 - 650:
1401
            preset = self._memobj.fm_presets / 10.0 + 65
1402
        else:
1403
            preset = 76.0
1404
        rs = RadioSetting("fm_presets", "FM Preset(MHz)",
1405
                      RadioSettingValueFloat(65, 116.1, preset, 0.1, 1))
1406
        fm_preset.append(rs)
1407

    
1408
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1409
        group.append(dtmf)
1410
        dtmfchars = "0123456789 *#ABCD"
1411

    
1412
        for i in range(0, 15):
1413
            _codeobj = self._memobj.pttid[i].code
1414
            _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1415
            val = RadioSettingValueString(0, 5, _code, False)
1416
            val.set_charset(dtmfchars)
1417
            rs = RadioSetting("pttid/%i.code" % i,
1418
                              "PTT ID Code %i" % (i + 1), val)
1419
            def apply_code(setting, obj):
1420
                code = []
1421
                for j in range(0, 5):
1422
                    try:
1423
                        code.append(dtmfchars.index(str(setting.value)[j]))
1424
                    except IndexError:
1425
                        code.append(0xFF)
1426
                obj.code = code
1427
            rs.set_apply_callback(apply_code, self._memobj.pttid[i])
1428
            dtmf.append(rs)
1429

    
1430
        _codeobj = self._memobj.ani.code
1431
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1432
        val = RadioSettingValueString(0, 5, _code, False)
1433
        val.set_charset(dtmfchars)
1434
        rs = RadioSetting("ani.code", "ANI Code", val)
1435
        def apply_code(setting, obj):
1436
            code = []
1437
            for j in range(0, 5):
1438
                try:
1439
                    code.append(dtmfchars.index(str(setting.value)[j]))
1440
                except IndexError:
1441
                    code.append(0xFF)
1442
            obj.code = code
1443
        rs.set_apply_callback(apply_code, _ani)
1444
        dtmf.append(rs)
1445

    
1446
        rs = RadioSetting("ani.aniid", "ANI ID",
1447
                          RadioSettingValueList(PTTID_LIST,
1448
                                                PTTID_LIST[_ani.aniid]))
1449
        dtmf.append(rs)
1450

    
1451
        _codeobj = self._memobj.ani.alarmcode
1452
        _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
1453
        val = RadioSettingValueString(0, 3, _code, False)
1454
        val.set_charset(dtmfchars)
1455
        rs = RadioSetting("ani.alarmcode", "Alarm Code", val)
1456
        def apply_code(setting, obj):
1457
            alarmcode = []
1458
            for j in range(0, 3):
1459
                try:
1460
                    alarmcode.append(dtmfchars.index(str(setting.value)[j]))
1461
                except IndexError:
1462
                    alarmcode.append(0xFF)
1463
            obj.alarmcode = alarmcode
1464
        rs.set_apply_callback(apply_code, _ani)
1465
        dtmf.append(rs)
1466

    
1467
        rs = RadioSetting("dtmfst", "DTMF Sidetone",
1468
                          RadioSettingValueList(DTMFST_LIST,
1469
                                                DTMFST_LIST[_settings.dtmfst]))
1470
        dtmf.append(rs)
1471

    
1472
        if _ani.dtmfon > 0xC3:
1473
            val = 0x00
1474
        else:
1475
            val = _ani.dtmfon
1476
        rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
1477
                          RadioSettingValueList(DTMFSPEED_LIST,
1478
                                                DTMFSPEED_LIST[val]))
1479
        dtmf.append(rs)
1480

    
1481
        if _ani.dtmfoff > 0xC3:
1482
            val = 0x00
1483
        else:
1484
            val = _ani.dtmfoff
1485
        rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
1486
                          RadioSettingValueList(DTMFSPEED_LIST,
1487
                                                DTMFSPEED_LIST[val]))
1488
        dtmf.append(rs)
1489

    
1490
        return group
1491

    
1492
    def get_settings(self):
1493
        try:
1494
            return self._get_settings()
1495
        except:
1496
            import traceback
1497
            print "Failed to parse settings:"
1498
            traceback.print_exc()
1499
            return None
1500

    
1501
    def set_settings(self, settings):
1502
        _settings = self._memobj.settings
1503
        for element in settings:
1504
            if not isinstance(element, RadioSetting):
1505
                if element.get_name() == "fm_preset" :
1506
                    self._set_fm_preset(element)
1507
                else:
1508
                    self.set_settings(element)
1509
                    continue
1510
            else:
1511
                try:
1512
                    name = element.get_name()
1513
                    if "." in name:
1514
                        bits = name.split(".")
1515
                        obj = self._memobj
1516
                        for bit in bits[:-1]:
1517
                            if "/" in bit:
1518
                                bit, index = bit.split("/", 1)
1519
                                index = int(index)
1520
                                obj = getattr(obj, bit)[index]
1521
                            else:
1522
                                obj = getattr(obj, bit)
1523
                        setting = bits[-1]
1524
                    else:
1525
                        obj = _settings
1526
                        setting = element.get_name()
1527

    
1528
                    if element.has_apply_callback():
1529
                        print "Using apply callback"
1530
                        element.run_apply_callback()
1531
                    else:
1532
                        print "Setting %s = %s" % (setting, element.value)
1533
                        setattr(obj, setting, element.value)
1534
                except Exception, e:
1535
                    print element.get_name()
1536
                    raise
1537

    
1538
    def _set_fm_preset(self, settings):
1539
        for element in settings:
1540
            try:
1541
                val = element.value
1542
                value = int(val.get_value() * 10 - 650)
1543
                print "Setting fm_presets = %s" % (value)
1544
                self._memobj.fm_presets = value
1545
            except Exception, e:
1546
                print element.get_name()
1547
                raise
1548

    
1549
@directory.register
1550
class BaofengF11Radio(BaofengUV5R):
1551
    VENDOR = "Baofeng"
1552
    MODEL = "F-11"
1553
    _basetype = BASETYPE_F11
1554
    _idents = [UV5R_MODEL_F11]
1555

    
1556
    def _is_orig(self):
1557
        # Override this for F11 to always return False
1558
        return False
1559

    
1560
@directory.register
1561
class BaofengUV82Radio(BaofengUV5R):
1562
    MODEL = "UV-82"
1563
    _basetype = BASETYPE_UV82
1564
    _idents = [UV5R_MODEL_UV82]
1565
    _vhf_range = (130000000, 176000000)
1566
    _uhf_range = (400000000, 521000000)
1567

    
1568
    def _is_orig(self):
1569
        # Override this for UV82 to always return False
1570
        return False
1571

    
1572
@directory.register
1573
class BaofengUV6Radio(BaofengUV5R):
1574
    """Baofeng UV-6/UV-7"""
1575
    VENDOR = "Baofeng"
1576
    MODEL = "UV-6"
1577
    _basetype = BASETYPE_UV6
1578
    _idents = [UV5R_MODEL_UV6,
1579
               UV5R_MODEL_UV6_ORIG,
1580
               UV5R_MODEL_UV8
1581
               ]
1582

    
1583
    def get_features(self):
1584
        rf = BaofengUV5R.get_features(self)
1585
        rf.memory_bounds = (1, 128)
1586
        return rf
1587

    
1588
    def _get_mem(self, number):
1589
        return self._memobj.memory[number - 1]
1590

    
1591
    def _get_nam(self, number):
1592
        return self._memobj.names[number - 1]
1593

    
1594
    def _set_mem(self, number):
1595
        return self._memobj.memory[number - 1]
1596

    
1597
    def _set_nam(self, number):
1598
        return self._memobj.names[number - 1]
1599

    
1600
    def _is_orig(self):
1601
        # Override this for UV6 to always return False
1602
        return False
1603

    
1604
@directory.register
1605
class IntekKT980Radio(BaofengUV5R):
1606
    VENDOR = "Intek"
1607
    MODEL = "KT-980HP"
1608
    _basetype = BASETYPE_KT980HP
1609
    _idents = [UV5R_MODEL_291]
1610
    _vhf_range = (130000000, 180000000)
1611
    _uhf_range = (400000000, 521000000)
1612

    
1613
    def get_features(self):
1614
        rf = BaofengUV5R.get_features(self)
1615
        rf.valid_power_levels = UV5R_POWER_LEVELS3
1616
        return rf
1617

    
1618
    def _is_orig(self):
1619
        # Override this for KT980HP to always return False
1620
        return False
1621

    
1622
@directory.register
1623
class BaofengBFF8HPRadio(BaofengUV5R):
1624
    VENDOR = "Baofeng"
1625
    MODEL = "BF-F8HP"
1626
    _basetype = BASETYPE_F8HP
1627
    _idents = [UV5R_MODEL_291]
1628
    _vhf_range = (130000000, 180000000)
1629
    _uhf_range = (400000000, 521000000)
1630

    
1631
    def get_features(self):
1632
        rf = BaofengUV5R.get_features(self)
1633
        rf.valid_power_levels = UV5R_POWER_LEVELS3
1634
        return rf
1635

    
1636
    def _is_orig(self):
1637
        # Override this for BFF8HP to always return False
1638
        return False