Project

General

Profile

New Model #1225 » uv5r.py

Temporary test driver for reading BaoFeng UV-8 - #2 - Jim Unroe, 09/25/2014 10:25 AM

 
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(8)
401

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

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

    
411
    return ident
412

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

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

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

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

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

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

    
440
    return chunk
441

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
804
        return mem
805

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

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

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

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

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

    
822
        _mem.rxfreq = mem.freq / 10
823

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

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

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

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

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

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

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

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

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

    
943
        raise Exception("Unrecognized firmware version string")
944

    
945
    def _my_upper_band(self):
946
        band_tag = _upper_band_from_image(self)
947
        return band_tag
948

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1145
        other = RadioSettingGroup("other", "Other Settings")
1146
        group.append(other)
1147

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1353

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

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

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

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

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

    
1399
        fm_preset = RadioSettingGroup("fm_preset", "FM Radio Preset")
1400
        group.append(fm_preset)
1401

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

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

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

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

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

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

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

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

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

    
1492
        return group
1493

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1638
    def _is_orig(self):
1639
        # Override this for BFF8HP to always return False
1640
        return False
(9-9/16)