uv5r.py

Temporary test driver for reading BaoFeng UV-8 - #4 - Jim Unroe, 09/26/2014 02:10 am

Download (56.7 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_UV8  = ["UV8S"]
261
BASETYPE_KT980HP = ["BFP3V3 B"]
262
BASETYPE_F8HP = ["BFP3V3 F", "N5R-3"]
263
BASETYPE_LIST = BASETYPE_UV5R + BASETYPE_F11 + BASETYPE_UV82 + \
264
                BASETYPE_BJ55 + BASETYPE_UV6 + BASETYPE_UV8 + \
265
                BASETYPE_KT980HP + BASETYPE_F8HP
266

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

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

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

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

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

    
349
UV5R_MODEL_ORIG = "\x50\xBB\xFF\x01\x25\x98\x4D"
350
UV5R_MODEL_291  = "\x50\xBB\xFF\x20\x12\x07\x25"
351
UV5R_MODEL_F11  = "\x50\xBB\xFF\x13\xA1\x11\xDD"
352
UV5R_MODEL_UV82 = "\x50\xBB\xFF\x20\x13\x01\x05"
353
UV5R_MODEL_UV6  = "\x50\xBB\xFF\x20\x12\x08\x23"
354
UV5R_MODEL_UV6_ORIG  = "\x50\xBB\xFF\x12\x03\x98\x4D"
355
UV5R_MODEL_UV8  = "\x50\xBB\xFF\x20\x12\x11\x19"
356

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

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

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

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

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

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

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

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

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

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

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

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

    
410
    return ident
411

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

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

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

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

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

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

    
439
    return chunk
440

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
803
        return mem
804

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

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

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

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

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

    
821
        _mem.rxfreq = mem.freq / 10
822

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

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

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

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

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

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

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

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

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

    
945
        raise Exception("Unrecognized firmware version string")
946

    
947
    def _my_upper_band(self):
948
        band_tag = _upper_band_from_image(self)
949
        return band_tag
950

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

    
961
        rs = RadioSetting("squelch", "Carrier Squelch Level",
962
                          RadioSettingValueInteger(0, 9, _settings.squelch))
963
        basic.append(rs)
964

    
965
        rs = RadioSetting("save", "Battery Saver",
966
                          RadioSettingValueList(SAVE_LIST,
967
                                                SAVE_LIST[_settings.save]))
968
        basic.append(rs)
969

    
970
        rs = RadioSetting("vox", "VOX Sensitivity",
971
                          RadioSettingValueList(VOX_LIST,
972
                                                VOX_LIST[_settings.vox]))
973
        advanced.append(rs)
974

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

    
983
        if self.MODEL != "UV-6":
984
            rs = RadioSetting("abr", "Backlight Timeout",
985
                              RadioSettingValueInteger(0, 24, _settings.abr))
986
            basic.append(rs)
987

    
988
        rs = RadioSetting("tdr", "Dual Watch",
989
                          RadioSettingValueBoolean(_settings.tdr))
990
        advanced.append(rs)
991

    
992
        if self.MODEL == "UV-6":
993
            rs = RadioSetting("tdrch", "Dual Watch Channel",
994
                              RadioSettingValueList(TDRCH_LIST,
995
                                                    TDRCH_LIST[
996
                                                    _settings.tdrch]))
997
            advanced.append(rs)
998

    
999
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
1000
                              RadioSettingValueBoolean(_settings.tdrab))
1001
            advanced.append(rs)
1002
        else:
1003
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
1004
                              RadioSettingValueList(TDRAB_LIST,
1005
                                                    TDRAB_LIST[
1006
                                                    _settings.tdrab]))
1007
            advanced.append(rs)
1008

    
1009
        if self.MODEL == "UV-6":
1010
            rs = RadioSetting("alarm", "Alarm Sound",
1011
                              RadioSettingValueBoolean(_settings.alarm))
1012
            advanced.append(rs)
1013

    
1014
        rs = RadioSetting("almod", "Alarm Mode",
1015
                          RadioSettingValueList(ALMOD_LIST,
1016
                                                ALMOD_LIST[_settings.almod]))
1017
        advanced.append(rs)
1018

    
1019
        rs = RadioSetting("beep", "Beep",
1020
                          RadioSettingValueBoolean(_settings.beep))
1021
        basic.append(rs)
1022

    
1023
        rs = RadioSetting("timeout", "Timeout Timer",
1024
                          RadioSettingValueList(TIMEOUT_LIST,
1025
                                                TIMEOUT_LIST[
1026
                                                _settings.timeout]))
1027
        basic.append(rs)
1028

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

    
1040
        rs = RadioSetting("screv", "Scan Resume",
1041
                          RadioSettingValueList(RESUME_LIST,
1042
                                                RESUME_LIST[_settings.screv]))
1043
        advanced.append(rs)
1044

    
1045
        if self.MODEL != "UV-6":
1046
            rs = RadioSetting("mdfa", "Display Mode (A)",
1047
                              RadioSettingValueList(MODE_LIST,
1048
                                                    MODE_LIST[_settings.mdfa]))
1049
            basic.append(rs)
1050

    
1051
            rs = RadioSetting("mdfb", "Display Mode (B)",
1052
                              RadioSettingValueList(MODE_LIST,
1053
                                                    MODE_LIST[_settings.mdfb]))
1054
            basic.append(rs)
1055

    
1056
        rs = RadioSetting("bcl", "Busy Channel Lockout",
1057
                          RadioSettingValueBoolean(_settings.bcl))
1058
        advanced.append(rs)
1059

    
1060
        if self.MODEL != "UV-6":
1061
            rs = RadioSetting("autolk", "Automatic Key Lock",
1062
                              RadioSettingValueBoolean(_settings.autolk))
1063
            advanced.append(rs)
1064

    
1065
        rs = RadioSetting("fmradio", "Broadcast FM Radio",
1066
                          RadioSettingValueBoolean(_settings.fmradio))
1067
        advanced.append(rs)
1068

    
1069
        if self.MODEL != "UV-6":
1070
            rs = RadioSetting("wtled", "Standby LED Color",
1071
                              RadioSettingValueList(COLOR_LIST,
1072
                                                    COLOR_LIST[
1073
                                                    _settings.wtled]))
1074
            basic.append(rs)
1075

    
1076
            rs = RadioSetting("rxled", "RX LED Color",
1077
                              RadioSettingValueList(COLOR_LIST,
1078
                                                    COLOR_LIST[
1079
                                                    _settings.rxled]))
1080
            basic.append(rs)
1081

    
1082
            rs = RadioSetting("txled", "TX LED Color",
1083
                              RadioSettingValueList(COLOR_LIST,
1084
                                                    COLOR_LIST[
1085
                                                    _settings.txled]))
1086
            basic.append(rs)
1087

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

    
1102
        rs = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)",
1103
                          RadioSettingValueBoolean(_settings.ste))
1104
        advanced.append(rs)
1105

    
1106
        rs = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)",
1107
                          RadioSettingValueList(RPSTE_LIST,
1108
                                                RPSTE_LIST[_settings.rpste]))
1109
        advanced.append(rs)
1110

    
1111
        rs = RadioSetting("rptrl", "STE Repeater Delay",
1112
                          RadioSettingValueList(STEDELAY_LIST,
1113
                                                STEDELAY_LIST[_settings.rptrl]))
1114
        advanced.append(rs)
1115

    
1116
        if self.MODEL != "UV-6":
1117
            rs = RadioSetting("reset", "RESET Menu",
1118
                              RadioSettingValueBoolean(_settings.reset))
1119
            advanced.append(rs)
1120

    
1121
            rs = RadioSetting("menu", "All Menus",
1122
                              RadioSettingValueBoolean(_settings.menu))
1123
            advanced.append(rs)
1124

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

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

    
1137
        if self.MODEL == "UV-82":
1138
            # this is an UV-82C only feature
1139
            rs = RadioSetting("singleptt", "Single PTT (UV-82C only)",
1140
                              RadioSettingValueBoolean(_settings.singleptt))
1141
            advanced.append(rs)
1142

    
1143
        if len(self._mmap.get_packed()) == 0x1808:
1144
            # Old image, without aux block
1145
            return group
1146

    
1147
        other = RadioSettingGroup("other", "Other Settings")
1148
        group.append(other)
1149

    
1150
        def _filter(name):
1151
            filtered = ""
1152
            for char in str(name):
1153
                if char in chirp_common.CHARSET_ASCII:
1154
                    filtered += char
1155
                else:
1156
                    filtered += " "
1157
            return filtered
1158

    
1159
        _msg = self._memobj.firmware_msg
1160
        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
1161
        val.set_mutable(False)
1162
        rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
1163
        other.append(rs)
1164

    
1165
        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
1166
        val.set_mutable(False)
1167
        rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
1168
        other.append(rs)
1169

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

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

    
1191
            rs = RadioSetting("ponmsg", "Power-On Message",
1192
                              RadioSettingValueList(PONMSG_LIST,
1193
                                                    PONMSG_LIST[
1194
                                                    _settings.ponmsg]))
1195
            other.append(rs)
1196

    
1197
            if self._is_orig():
1198
                limit = "limits_old"
1199
            else:
1200
                limit = "limits_new"
1201

    
1202
            vhf_limit = getattr(self._memobj, limit).vhf
1203
            rs = RadioSetting("%s.vhf.lower" % limit, "VHF Lower Limit (MHz)",
1204
                              RadioSettingValueInteger(1, 1000,
1205
                                                       vhf_limit.lower))
1206
            other.append(rs)
1207

    
1208
            rs = RadioSetting("%s.vhf.upper" % limit, "VHF Upper Limit (MHz)",
1209
                              RadioSettingValueInteger(1, 1000,
1210
                                                       vhf_limit.upper))
1211
            other.append(rs)
1212

    
1213
            rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled",
1214
                              RadioSettingValueBoolean(vhf_limit.enable))
1215
            other.append(rs)
1216

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

    
1230
        if self.MODEL != "UV-6":
1231
            workmode = RadioSettingGroup("workmode", "Work Mode Settings")
1232
            group.append(workmode)
1233

    
1234
            rs = RadioSetting("displayab", "Display",
1235
                              RadioSettingValueList(AB_LIST,
1236
                                                    AB_LIST[
1237
                                                    _settings.displayab]))
1238
            workmode.append(rs)
1239

    
1240
            rs = RadioSetting("workmode", "VFO/MR Mode",
1241
                              RadioSettingValueList(WORKMODE_LIST,
1242
                                                    WORKMODE_LIST[
1243
                                                    _settings.workmode]))
1244
            workmode.append(rs)
1245

    
1246
            rs = RadioSetting("keylock", "Keypad Lock",
1247
                              RadioSettingValueBoolean(_settings.keylock))
1248
            workmode.append(rs)
1249

    
1250
            rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
1251
                              RadioSettingValueInteger(0, 127,
1252
                                                       _wmchannel.mrcha))
1253
            workmode.append(rs)
1254

    
1255
            rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
1256
                              RadioSettingValueInteger(0, 127,
1257
                                                       _wmchannel.mrchb))
1258
            workmode.append(rs)
1259

    
1260
            def convert_bytes_to_freq(bytes):
1261
               real_freq = 0
1262
               for byte in bytes:
1263
                   real_freq = (real_freq * 10) + byte
1264
               return chirp_common.format_freq(real_freq * 10)
1265

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

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

    
1280
            val1a = RadioSettingValueString(0, 10,
1281
                                            convert_bytes_to_freq(_vfoa.freq))
1282
            val1a.set_validate_callback(my_validate)
1283
            rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a)
1284
            rs.set_apply_callback(apply_freq, _vfoa)
1285
            workmode.append(rs)
1286

    
1287
            val1b = RadioSettingValueString(0, 10,
1288
                                            convert_bytes_to_freq(_vfob.freq))
1289
            val1b.set_validate_callback(my_validate)
1290
            rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b)
1291
            rs.set_apply_callback(apply_freq, _vfob)
1292
            workmode.append(rs)
1293

    
1294
            rs = RadioSetting("vfoa.sftd", "VFO A Shift",
1295
                              RadioSettingValueList(SHIFTD_LIST,
1296
                                                    SHIFTD_LIST[_vfoa.sftd]))
1297
            workmode.append(rs)
1298

    
1299
            rs = RadioSetting("vfob.sftd", "VFO B Shift",
1300
                              RadioSettingValueList(SHIFTD_LIST,
1301
                                                    SHIFTD_LIST[_vfob.sftd]))
1302
            workmode.append(rs)
1303

    
1304
            def convert_bytes_to_offset(bytes):
1305
               real_offset = 0
1306
               for byte in bytes:
1307
                   real_offset = (real_offset * 10) + byte
1308
               return chirp_common.format_freq(real_offset * 10000)
1309

    
1310
            def apply_offset(setting, obj):
1311
                value = chirp_common.parse_freq(str(setting.value)) / 10000
1312
                for i in range(3, -1, -1):
1313
                    obj.offset[i] = value % 10
1314
                    value /= 10
1315

    
1316
            val1a = RadioSettingValueString(0, 10,
1317
                                            convert_bytes_to_offset(
1318
                                            _vfoa.offset))
1319
            rs = RadioSetting("vfoa.offset", "VFO A Offset (0.00-69.95)", val1a)
1320
            rs.set_apply_callback(apply_offset, _vfoa)
1321
            workmode.append(rs)
1322

    
1323
            val1b = RadioSettingValueString(0, 10,
1324
                                            convert_bytes_to_offset(
1325
                                            _vfob.offset))
1326
            rs = RadioSetting("vfob.offset", "VFO B Offset (0.00-69.95)", val1b)
1327
            rs.set_apply_callback(apply_offset, _vfob)
1328
            workmode.append(rs)
1329

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

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

    
1349
                rs = RadioSetting("vfob.txpower", "VFO B Power",
1350
                                  RadioSettingValueList(TXPOWER_LIST,
1351
                                                        TXPOWER_LIST[
1352
                                                        _vfob.txpower]))
1353
                workmode.append(rs)
1354

    
1355

    
1356
            rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth",
1357
                              RadioSettingValueList(BANDWIDTH_LIST,
1358
                                                    BANDWIDTH_LIST[
1359
                                                    _vfoa.widenarr]))
1360
            workmode.append(rs)
1361

    
1362
            rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
1363
                              RadioSettingValueList(BANDWIDTH_LIST,
1364
                                                    BANDWIDTH_LIST[
1365
                                                    _vfob.widenarr]))
1366
            workmode.append(rs)
1367

    
1368
            rs = RadioSetting("vfoa.scode", "VFO A PTT-ID",
1369
                              RadioSettingValueList(PTTIDCODE_LIST,
1370
                                                    PTTIDCODE_LIST[
1371
                                                    _vfoa.scode]))
1372
            workmode.append(rs)
1373

    
1374
            rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
1375
                              RadioSettingValueList(PTTIDCODE_LIST,
1376
                                                    PTTIDCODE_LIST[
1377
                                                    _vfob.scode]))
1378
            workmode.append(rs)
1379

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

    
1401
        fm_preset = RadioSettingGroup("fm_preset", "FM Radio Preset")
1402
        group.append(fm_preset)
1403

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

    
1412
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1413
        group.append(dtmf)
1414
        dtmfchars = "0123456789 *#ABCD"
1415

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

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

    
1450
        rs = RadioSetting("ani.aniid", "ANI ID",
1451
                          RadioSettingValueList(PTTID_LIST,
1452
                                                PTTID_LIST[_ani.aniid]))
1453
        dtmf.append(rs)
1454

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

    
1471
        rs = RadioSetting("dtmfst", "DTMF Sidetone",
1472
                          RadioSettingValueList(DTMFST_LIST,
1473
                                                DTMFST_LIST[_settings.dtmfst]))
1474
        dtmf.append(rs)
1475

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

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

    
1494
        return group
1495

    
1496
    def get_settings(self):
1497
        try:
1498
            return self._get_settings()
1499
        except:
1500
            import traceback
1501
            print "Failed to parse settings:"
1502
            traceback.print_exc()
1503
            return None
1504

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

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

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

    
1553
@directory.register
1554
class BaofengF11Radio(BaofengUV5R):
1555
    VENDOR = "Baofeng"
1556
    MODEL = "F-11"
1557
    _basetype = BASETYPE_F11
1558
    _idents = [UV5R_MODEL_F11]
1559

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

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

    
1572
    def _is_orig(self):
1573
        # Override this for UV82 to always return False
1574
        return False
1575

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

    
1586
    def get_features(self):
1587
        rf = BaofengUV5R.get_features(self)
1588
        rf.memory_bounds = (1, 128)
1589
        return rf
1590

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

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

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

    
1600
    def _set_nam(self, number):
1601
        return self._memobj.names[number - 1]
1602

    
1603
    def _is_orig(self):
1604
        # Override this for UV6 to always return False
1605
        return False
1606

    
1607
@directory.register
1608
class BaofengUV8Radio(BaofengUV5R):
1609
    """Baofeng UV-8"""
1610
    VENDOR = "Baofeng"
1611
    MODEL = "UV-8"
1612
    _basetype = BASETYPE_UV8
1613
    _idents = [UV5R_MODEL_UV8
1614
               ]
1615

    
1616
    def get_features(self):
1617
        rf = BaofengUV5R.get_features(self)
1618
        rf.memory_bounds = (1, 128)
1619
        return rf
1620

    
1621
    def _get_mem(self, number):
1622
        return self._memobj.memory[number - 1]
1623

    
1624
    def _get_nam(self, number):
1625
        return self._memobj.names[number - 1]
1626

    
1627
    def _set_mem(self, number):
1628
        return self._memobj.memory[number - 1]
1629

    
1630
    def _set_nam(self, number):
1631
        return self._memobj.names[number - 1]
1632

    
1633
    def _is_orig(self):
1634
        # Override this for UV8 to always return False
1635
        return False
1636

    
1637
@directory.register
1638
class IntekKT980Radio(BaofengUV5R):
1639
    VENDOR = "Intek"
1640
    MODEL = "KT-980HP"
1641
    _basetype = BASETYPE_KT980HP
1642
    _idents = [UV5R_MODEL_291]
1643
    _vhf_range = (130000000, 180000000)
1644
    _uhf_range = (400000000, 521000000)
1645

    
1646
    def get_features(self):
1647
        rf = BaofengUV5R.get_features(self)
1648
        rf.valid_power_levels = UV5R_POWER_LEVELS3
1649
        return rf
1650

    
1651
    def _is_orig(self):
1652
        # Override this for KT980HP to always return False
1653
        return False
1654

    
1655
@directory.register
1656
class BaofengBFF8HPRadio(BaofengUV5R):
1657
    VENDOR = "Baofeng"
1658
    MODEL = "BF-F8HP"
1659
    _basetype = BASETYPE_F8HP
1660
    _idents = [UV5R_MODEL_291]
1661
    _vhf_range = (130000000, 180000000)
1662
    _uhf_range = (400000000, 521000000)
1663

    
1664
    def get_features(self):
1665
        rf = BaofengUV5R.get_features(self)
1666
        rf.valid_power_levels = UV5R_POWER_LEVELS3
1667
        return rf
1668

    
1669
    def _is_orig(self):
1670
        # Override this for BFF8HP to always return False
1671
        return False