Project

General

Profile

Bug #1707 » uv5r.py

Jim Unroe, 09/03/2014 03:46 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  = "\x50\xBB\xFF\x12\x03\x98\x4D"
354

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

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

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

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

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

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

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

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

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

    
398
    serial.write("\x02")
399
    ident = serial.read(8)
400

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

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

    
408
    return ident
409

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

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

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

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

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

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

    
437
    return chunk
438

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
801
        return mem
802

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

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

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

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

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

    
819
        _mem.rxfreq = mem.freq / 10
820

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1350

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1489
        return group
1490

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

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

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

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

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

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

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

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

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

    
1579
    def get_features(self):
1580
        rf = BaofengUV5R.get_features(self)
1581
        rf.memory_bounds = (1, 128)
1582
        return rf
1583

    
1584
    def _get_mem(self, number):
1585
        return self._memobj.memory[number - 1]
1586

    
1587
    def _get_nam(self, number):
1588
        return self._memobj.names[number - 1]
1589

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

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

    
1596
    def _is_orig(self):
1597
        # Override this for UV6 to always return False
1598
        return False
1599

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

    
1609
    def get_features(self):
1610
        rf = BaofengUV5R.get_features(self)
1611
        rf.valid_power_levels = UV5R_POWER_LEVELS3
1612
        return rf
1613

    
1614
    def _is_orig(self):
1615
        # Override this for KT980HP to always return False
1616
        return False
1617

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

    
1627
    def get_features(self):
1628
        rf = BaofengUV5R.get_features(self)
1629
        rf.valid_power_levels = UV5R_POWER_LEVELS3
1630
        return rf
1631

    
1632
    def _is_orig(self):
1633
        # Override this for BFF8HP to always return False
1634
        return False
(8-8/32)