Project

General

Profile

Bug #1751 » uv5r.py

Jim Unroe, 07/17/2014 05:57 PM

 
1
# Copyright 2012 Dan Smith <dsmith@danplanet.com>
2
#
3
# This program is free software: you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation, either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
15

    
16
import struct
17
import time
18
import os
19

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

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

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

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

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

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

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

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

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

    
200
#seekto 0x0F56;
201
u16 fm_presets;
202

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

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

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

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

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

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

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

    
249
"""
250

    
251
# 0x1EC0 - 0x2000
252

    
253
vhf_220_radio = "\x02"
254

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
407
    return ident
408

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

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

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

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

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

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

    
436
    return chunk
437

    
438
def _get_radio_firmware_version(radio):
439
    if radio.MODEL == "BJ-UV55":
440
        block = _read_block(radio, 0x1FF0, 0x40)
441
        version = block[0:6]
442
    else:
443
        block1 = _read_block(radio, 0x1EC0, 0x40)
444
        block2 = _read_block(radio, 0x1F00, 0x40)
445
        block = block1 + block2
446
        #version = block[48:64]
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
    # Main block
473
    if CHIRP_DEBUG:
474
        print "downloading main block..."
475
    for i in range(0, 0x1800, 0x40):
476
        data += _read_block(radio, i, 0x40)
477
        _do_status(radio, i)
478
    if CHIRP_DEBUG:
479
        print "done."
480
        print "downloading aux block..."
481
    # Auxiliary block starts at 0x1ECO (?)
482
    for i in range(0x1EC0, 0x2000, 0x40):
483
        data += _read_block(radio, i, 0x40)
484
    if CHIRP_DEBUG:
485
        print "done."
486
    return memmap.MemoryMap(data)
487

    
488
def _send_block(radio, addr, data):
489
    msg = struct.pack(">BHB", ord("X"), addr, len(data))
490
    radio.pipe.write(msg + data)
491

    
492
    ack = radio.pipe.read(1)
493
    if ack != "\x06":
494
        raise errors.RadioError("Radio refused to accept block 0x%04x" % addr)
495

    
496
def _do_upload(radio):
497
    ident = _ident_radio(radio)
498
    radio_upper_band = ident[3:4]
499
    image_upper_band = _upper_band_from_image(radio)
500

    
501
    if image_upper_band == vhf_220_radio or radio_upper_band == vhf_220_radio:
502
        if image_upper_band != radio_upper_band:
503
            raise errors.RadioError("Image not supported by radio")
504

    
505
    image_version = _firmware_version_from_image(radio)
506
    radio_version = _get_radio_firmware_version(radio)
507
    print "Image Version is %s" % repr(image_version)
508
    print "Radio Version is %s" % repr(radio_version)
509

    
510
    if not any(type in radio_version for type in BASETYPE_LIST):
511
        raise errors.RadioError("Unsupported firmware version: `%s'" %
512
                                radio_version)
513

    
514
    image_special_block = _special_block_from_image(radio)
515
    radio_special_block = _get_radio_special_block(radio)
516
    print "Image Special Block is " + util.hexprint(image_special_block)
517
    print "Radio Special Block is " + util.hexprint(radio_special_block)
518

    
519
    if image_special_block != radio_special_block:
520
        raise errors.RadioError("Image not supported by radio: `%s'" %
521
                                radio_special_block)
522

    
523
    # Main block
524
    for i in range(0x08, 0x1808, 0x10):
525
        _send_block(radio, i - 0x08, radio.get_mmap()[i:i + 0x10])
526
        _do_status(radio, i)
527

    
528
    if len(radio.get_mmap().get_packed()) == 0x1808:
529
        print "Old image, not writing aux block"
530
        return # Old image, no aux block
531

    
532
    if image_version != radio_version:
533
        msg = ("Upload finished, but the 'Other Settings' "
534
               "could not be sent because the firmware "
535
               "version of the image (%s) does not match "
536
               "that of the radio (%s).")
537
        raise errors.RadioError(msg % (image_version, radio_version))
538

    
539
    # Auxiliary block at radio address 0x1EC0, our offset 0x1808
540
    for i in range(0x1EC0, 0x2000, 0x10):
541
        addr = 0x1808 + (i - 0x1EC0)
542
        _send_block(radio, i, radio.get_mmap()[addr:addr + 0x10])
543

    
544
UV5R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=4.00),
545
                     chirp_common.PowerLevel("Low",  watts=1.00)]
546

    
547
UV5R_POWER_LEVELS3 = [chirp_common.PowerLevel("High", watts=8.00),
548
                      chirp_common.PowerLevel("Med",  watts=4.00),
549
                      chirp_common.PowerLevel("Low",  watts=1.00)]
550

    
551
UV5R_DTCS = sorted(chirp_common.DTCS_CODES + [645])
552

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

    
556
# Uncomment this to actually register this radio in CHIRP
557
@directory.register
558
class BaofengUV5R(chirp_common.CloneModeRadio,
559
                  chirp_common.ExperimentalRadio):
560
    """Baofeng UV-5R"""
561
    VENDOR = "Baofeng"
562
    MODEL = "UV-5R"
563
    BAUD_RATE = 9600
564

    
565
    _memsize = 0x1808
566
    _basetype = BASETYPE_UV5R
567
    _idents = [UV5R_MODEL_291,
568
               UV5R_MODEL_ORIG
569
               ]
570
    _vhf_range = (136000000, 174000000)
571
    _220_range = (220000000, 260000000)
572
    _uhf_range = (400000000, 520000000)
573
    _mem_params = ( 0x1828 # poweron_msg offset
574
                    )
575
    # offset of fw version in image file
576
    _fw_ver_file_start = 0x1838
577
    #_fw_ver_file_stop = 0x1848
578
    _fw_ver_file_stop = 0x1846
579

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

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

    
623
        normal_bands = [self._vhf_range, self._uhf_range]
624
        rax_bands = [self._vhf_range, self._220_range]
625

    
626
        if self._mmap is None:
627
            rf.valid_bands = [normal_bands[0], rax_bands[1], normal_bands[1]]
628
        elif not self._is_orig() and self._my_upper_band() == vhf_220_radio:
629
            rf.valid_bands = rax_bands
630
        else:
631
            rf.valid_bands = normal_bands
632
        rf.memory_bounds = (0, 127)
633
        return rf
634

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

    
651
    def process_mmap(self):
652
        self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
653

    
654
    def sync_in(self):
655
        try:
656
            self._mmap = _do_download(self)
657
        except errors.RadioError:
658
            raise
659
        except Exception, e:
660
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
661
        self.process_mmap()
662

    
663
    def sync_out(self):
664
        try:
665
            _do_upload(self)
666
        except errors.RadioError:
667
            raise
668
        except Exception, e:
669
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
670

    
671
    def get_raw_memory(self, number):
672
        return repr(self._memobj.memory[number])
673

    
674
    def _is_txinh(self, _mem):
675
        raw_tx = ""
676
        for i in range(0, 4):
677
            raw_tx += _mem.txfreq[i].get_raw()
678
        return raw_tx == "\xFF\xFF\xFF\xFF"
679

    
680
    def _get_mem(self, number):
681
        return self._memobj.memory[number]
682

    
683
    def _get_nam(self, number):
684
        return self._memobj.names[number]
685

    
686
    def get_memory(self, number):
687
        _mem = self._get_mem(number)
688
        _nam = self._get_nam(number)
689

    
690
        mem = chirp_common.Memory()
691
        mem.number = number
692

    
693
        if _mem.get_raw()[0] == "\xff":
694
            mem.empty = True
695
            return mem
696

    
697
        mem.freq = int(_mem.rxfreq) * 10
698

    
699
        if self._is_txinh(_mem):
700
            mem.duplex = "off"
701
            mem.offset = 0
702
        elif int(_mem.rxfreq) == int(_mem.txfreq):
703
            mem.duplex = ""
704
            mem.offset = 0
705
        elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
706
            mem.duplex = "split"
707
            mem.offset = int(_mem.txfreq) * 10
708
        else:
709
            mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
710
            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
711

    
712
        for char in _nam.name:
713
            if str(char) == "\xFF":
714
                char = " " # The UV-5R software may have 0xFF mid-name
715
            mem.name += str(char)
716
        mem.name = mem.name.rstrip()
717

    
718
        dtcs_pol = ["N", "N"]
719

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

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

    
752
        if txmode == "Tone" and not rxmode:
753
            mem.tmode = "Tone"
754
        elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
755
            mem.tmode = "TSQL"
756
        elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
757
            mem.tmode = "DTCS"
758
        elif rxmode or txmode:
759
            mem.tmode = "Cross"
760
            mem.cross_mode = "%s->%s" % (txmode, rxmode)
761

    
762
        mem.dtcs_polarity = "".join(dtcs_pol)
763

    
764
        if not _mem.scan:
765
            mem.skip = "S"
766

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

    
778
        mem.mode = _mem.wide and "FM" or "NFM"
779

    
780
        mem.extra = RadioSettingGroup("Extra", "extra")
781

    
782
        rs = RadioSetting("bcl", "BCL",
783
                          RadioSettingValueBoolean(_mem.bcl))
784
        mem.extra.append(rs)
785

    
786
        rs = RadioSetting("pttid", "PTT ID",
787
                          RadioSettingValueList(PTTID_LIST,
788
                                                PTTID_LIST[_mem.pttid]))
789
        mem.extra.append(rs)
790

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

    
796
        return mem
797

    
798
    def _set_mem(self, number):
799
        return self._memobj.memory[number]
800

    
801
    def _set_nam(self, number):
802
        return self._memobj.names[number]
803

    
804
    def set_memory(self, mem):
805
        _mem = self._get_mem(mem.number)
806
        _nam = self._get_nam(mem.number)
807

    
808
        if mem.empty:
809
            _mem.set_raw("\xff" * 16)
810
            return
811

    
812
        _mem.set_raw("\x00" * 16)
813

    
814
        _mem.rxfreq = mem.freq / 10
815

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

    
828
        _namelength = self.get_features().valid_name_length
829
        for i in range(_namelength):
830
            try:
831
                _nam.name[i] = mem.name[i]
832
            except IndexError:
833
                _nam.name[i] = "\xFF"
834

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

    
864
        if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
865
            _mem.txtone += 0x69
866
        if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
867
            _mem.rxtone += 0x69
868

    
869
        _mem.scan = mem.skip != "S"
870
        _mem.wide = mem.mode == "FM"
871

    
872
        if mem.power:
873
            if self.MODEL == "KT-980HP" or self.MODEL == "BF-F8HP":
874
                levels = [str(l) for l in UV5R_POWER_LEVELS3]
875
                _mem.lowpower = levels.index(str(mem.power))
876
            else:
877
                _mem.lowpower = UV5R_POWER_LEVELS.index(mem.power)
878
        else:
879
            _mem.lowpower = 0
880

    
881
        for setting in mem.extra:
882
            setattr(_mem, setting.get_name(), setting.value)
883

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

    
900
    def _my_version(self):
901
        version_tag = _firmware_version_from_image(self)
902
        #if 'BFB' in version_tag:
903
        #    idx = version_tag.index("BFB") + 3
904
        if 'BFS' in version_tag:
905
            idx = version_tag.index("BFS") + 3
906
            return int(version_tag[idx:idx + 3])
907
        #elif 'BFS' in version_tag:
908
        #    idx = version_tag.index("BFS") + 3
909
        elif 'BFB' in version_tag:
910
            idx = version_tag.index("BFB") + 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

    
934
        raise Exception("Unrecognized firmware version string")
935

    
936
    def _my_upper_band(self):
937
        band_tag = _upper_band_from_image(self)
938
        return band_tag
939

    
940
    def _get_settings(self):
941
        _ani = self._memobj.ani
942
        _settings = self._memobj.settings
943
        _vfoa = self._memobj.vfoa
944
        _vfob = self._memobj.vfob
945
        _wmchannel = self._memobj.wmchannel
946
        basic = RadioSettingGroup("basic", "Basic Settings")
947
        advanced = RadioSettingGroup("advanced", "Advanced Settings")
948
        group = RadioSettingGroup("top", "All Settings", basic, advanced)
949

    
950
        rs = RadioSetting("squelch", "Carrier Squelch Level",
951
                          RadioSettingValueInteger(0, 9, _settings.squelch))
952
        basic.append(rs)
953

    
954
        rs = RadioSetting("save", "Battery Saver",
955
                          RadioSettingValueList(SAVE_LIST,
956
                                                SAVE_LIST[_settings.save]))
957
        basic.append(rs)
958

    
959
        rs = RadioSetting("vox", "VOX Sensitivity",
960
                          RadioSettingValueList(VOX_LIST,
961
                                                VOX_LIST[_settings.vox]))
962
        advanced.append(rs)
963

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

    
972
        if self.MODEL != "UV-6":
973
            rs = RadioSetting("abr", "Backlight Timeout",
974
                              RadioSettingValueInteger(0, 24, _settings.abr))
975
            basic.append(rs)
976

    
977
        rs = RadioSetting("tdr", "Dual Watch",
978
                          RadioSettingValueBoolean(_settings.tdr))
979
        advanced.append(rs)
980

    
981
        if self.MODEL == "UV-6":
982
            rs = RadioSetting("tdrch", "Dual Watch Channel",
983
                              RadioSettingValueList(TDRCH_LIST,
984
                                                    TDRCH_LIST[
985
                                                    _settings.tdrch]))
986
            advanced.append(rs)
987

    
988
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
989
                              RadioSettingValueBoolean(_settings.tdrab))
990
            advanced.append(rs)
991
        else:
992
            rs = RadioSetting("tdrab", "Dual Watch TX Priority",
993
                              RadioSettingValueList(TDRAB_LIST,
994
                                                    TDRAB_LIST[
995
                                                    _settings.tdrab]))
996
            advanced.append(rs)
997

    
998
        if self.MODEL == "UV-6":
999
            rs = RadioSetting("alarm", "Alarm Sound",
1000
                              RadioSettingValueBoolean(_settings.alarm))
1001
            advanced.append(rs)
1002

    
1003
        rs = RadioSetting("almod", "Alarm Mode",
1004
                          RadioSettingValueList(ALMOD_LIST,
1005
                                                ALMOD_LIST[_settings.almod]))
1006
        advanced.append(rs)
1007

    
1008
        rs = RadioSetting("beep", "Beep",
1009
                          RadioSettingValueBoolean(_settings.beep))
1010
        basic.append(rs)
1011

    
1012
        rs = RadioSetting("timeout", "Timeout Timer",
1013
                          RadioSettingValueList(TIMEOUT_LIST,
1014
                                                TIMEOUT_LIST[
1015
                                                _settings.timeout]))
1016
        basic.append(rs)
1017

    
1018
        if self._is_orig() and self._my_version() < 251:
1019
            rs = RadioSetting("voice", "Voice",
1020
                              RadioSettingValueBoolean(_settings.voice))
1021
            advanced.append(rs)
1022
        else:
1023
            rs = RadioSetting("voice", "Voice",
1024
                              RadioSettingValueList(VOICE_LIST,
1025
                                                    VOICE_LIST[
1026
                                                    _settings.voice]))
1027
            advanced.append(rs)
1028

    
1029
        rs = RadioSetting("screv", "Scan Resume",
1030
                          RadioSettingValueList(RESUME_LIST,
1031
                                                RESUME_LIST[_settings.screv]))
1032
        advanced.append(rs)
1033

    
1034
        if self.MODEL != "UV-6":
1035
            rs = RadioSetting("mdfa", "Display Mode (A)",
1036
                              RadioSettingValueList(MODE_LIST,
1037
                                                    MODE_LIST[_settings.mdfa]))
1038
            basic.append(rs)
1039

    
1040
            rs = RadioSetting("mdfb", "Display Mode (B)",
1041
                              RadioSettingValueList(MODE_LIST,
1042
                                                    MODE_LIST[_settings.mdfb]))
1043
            basic.append(rs)
1044

    
1045
        rs = RadioSetting("bcl", "Busy Channel Lockout",
1046
                          RadioSettingValueBoolean(_settings.bcl))
1047
        advanced.append(rs)
1048

    
1049
        if self.MODEL != "UV-6":
1050
            rs = RadioSetting("autolk", "Automatic Key Lock",
1051
                              RadioSettingValueBoolean(_settings.autolk))
1052
            advanced.append(rs)
1053

    
1054
        rs = RadioSetting("fmradio", "Broadcast FM Radio",
1055
                          RadioSettingValueBoolean(_settings.fmradio))
1056
        advanced.append(rs)
1057

    
1058
        if self.MODEL != "UV-6":
1059
            rs = RadioSetting("wtled", "Standby LED Color",
1060
                              RadioSettingValueList(COLOR_LIST,
1061
                                                    COLOR_LIST[
1062
                                                    _settings.wtled]))
1063
            basic.append(rs)
1064

    
1065
            rs = RadioSetting("rxled", "RX LED Color",
1066
                              RadioSettingValueList(COLOR_LIST,
1067
                                                    COLOR_LIST[
1068
                                                    _settings.rxled]))
1069
            basic.append(rs)
1070

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

    
1077
        if self.MODEL == "UV-82":
1078
            rs = RadioSetting("roger", "Roger Beep (TX)",
1079
                              RadioSettingValueBoolean(_settings.roger))
1080
            basic.append(rs)
1081
            rs = RadioSetting("rogerrx", "Roger Beep (RX)",
1082
                              RadioSettingValueList(ROGERRX_LIST,
1083
                                                    ROGERRX_LIST[
1084
                                                    _settings.rogerrx]))
1085
            basic.append(rs)
1086
        else:
1087
            rs = RadioSetting("roger", "Roger Beep",
1088
                              RadioSettingValueBoolean(_settings.roger))
1089
            basic.append(rs)
1090

    
1091
        rs = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)",
1092
                          RadioSettingValueBoolean(_settings.ste))
1093
        advanced.append(rs)
1094

    
1095
        rs = RadioSetting("rpste", "Squelch Tail Eliminate (repeater)",
1096
                          RadioSettingValueList(RPSTE_LIST,
1097
                                                RPSTE_LIST[_settings.rpste]))
1098
        advanced.append(rs)
1099

    
1100
        rs = RadioSetting("rptrl", "STE Repeater Delay",
1101
                          RadioSettingValueList(STEDELAY_LIST,
1102
                                                STEDELAY_LIST[_settings.rptrl]))
1103
        advanced.append(rs)
1104

    
1105
        if self.MODEL != "UV-6":
1106
            rs = RadioSetting("reset", "RESET Menu",
1107
                              RadioSettingValueBoolean(_settings.reset))
1108
            advanced.append(rs)
1109

    
1110
            rs = RadioSetting("menu", "All Menus",
1111
                              RadioSettingValueBoolean(_settings.menu))
1112
            advanced.append(rs)
1113

    
1114
        if self.MODEL == "F-11":
1115
            # this is an F-11 only feature
1116
            rs = RadioSetting("vfomrlock", "VFO/MR Button",
1117
                              RadioSettingValueBoolean(_settings.vfomrlock))
1118
            advanced.append(rs)
1119

    
1120
        if self.MODEL == "UV-82":
1121
            # this is a UV-82C only feature
1122
            rs = RadioSetting("vfomrlock", "VFO/MR Switching (UV-82C only)",
1123
                              RadioSettingValueBoolean(_settings.vfomrlock))
1124
            advanced.append(rs)
1125

    
1126
        if self.MODEL == "UV-82":
1127
            # this is an UV-82C only feature
1128
            rs = RadioSetting("singleptt", "Single PTT (UV-82C only)",
1129
                              RadioSettingValueBoolean(_settings.singleptt))
1130
            advanced.append(rs)
1131

    
1132
        if len(self._mmap.get_packed()) == 0x1808:
1133
            # Old image, without aux block
1134
            return group
1135

    
1136
        other = RadioSettingGroup("other", "Other Settings")
1137
        group.append(other)
1138

    
1139
        def _filter(name):
1140
            filtered = ""
1141
            for char in str(name):
1142
                if char in chirp_common.CHARSET_ASCII:
1143
                    filtered += char
1144
                else:
1145
                    filtered += " "
1146
            return filtered
1147

    
1148
        _msg = self._memobj.firmware_msg
1149
        val = RadioSettingValueString(0, 7, _filter(_msg.line1))
1150
        val.set_mutable(False)
1151
        rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
1152
        other.append(rs)
1153

    
1154
        val = RadioSettingValueString(0, 7, _filter(_msg.line2))
1155
        val.set_mutable(False)
1156
        rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
1157
        other.append(rs)
1158

    
1159
        if self.MODEL != "UV-6":
1160
            _msg = self._memobj.sixpoweron_msg
1161
            rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1",
1162
                              RadioSettingValueString(0, 7, _filter(
1163
                                                      _msg.line1)))
1164
            other.append(rs)
1165
            rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2",
1166
                              RadioSettingValueString(0, 7, _filter(
1167
                                                      _msg.line2)))
1168
            other.append(rs)
1169

    
1170
            _msg = self._memobj.poweron_msg
1171
            rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
1172
                              RadioSettingValueString(0, 7, _filter(
1173
                                                      _msg.line1)))
1174
            other.append(rs)
1175
            rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
1176
                              RadioSettingValueString(0, 7, _filter(
1177
                                                      _msg.line2)))
1178
            other.append(rs)
1179

    
1180
            rs = RadioSetting("ponmsg", "Power-On Message",
1181
                              RadioSettingValueList(PONMSG_LIST,
1182
                                                    PONMSG_LIST[
1183
                                                    _settings.ponmsg]))
1184
            other.append(rs)
1185

    
1186
            if self._is_orig():
1187
                limit = "limits_old"
1188
            else:
1189
                limit = "limits_new"
1190

    
1191
            vhf_limit = getattr(self._memobj, limit).vhf
1192
            rs = RadioSetting("%s.vhf.lower" % limit, "VHF Lower Limit (MHz)",
1193
                              RadioSettingValueInteger(1, 1000,
1194
                                                       vhf_limit.lower))
1195
            other.append(rs)
1196

    
1197
            rs = RadioSetting("%s.vhf.upper" % limit, "VHF Upper Limit (MHz)",
1198
                              RadioSettingValueInteger(1, 1000,
1199
                                                       vhf_limit.upper))
1200
            other.append(rs)
1201

    
1202
            rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled",
1203
                              RadioSettingValueBoolean(vhf_limit.enable))
1204
            other.append(rs)
1205

    
1206
            uhf_limit = getattr(self._memobj, limit).uhf
1207
            rs = RadioSetting("%s.uhf.lower" % limit, "UHF Lower Limit (MHz)",
1208
                              RadioSettingValueInteger(1, 1000,
1209
                                                       uhf_limit.lower))
1210
            other.append(rs)
1211
            rs = RadioSetting("%s.uhf.upper" % limit, "UHF Upper Limit (MHz)",
1212
                              RadioSettingValueInteger(1, 1000,
1213
                                                       uhf_limit.upper))
1214
            other.append(rs)
1215
            rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled",
1216
                              RadioSettingValueBoolean(uhf_limit.enable))
1217
            other.append(rs)
1218

    
1219
        if self.MODEL != "UV-6":
1220
            workmode = RadioSettingGroup("workmode", "Work Mode Settings")
1221
            group.append(workmode)
1222

    
1223
            rs = RadioSetting("displayab", "Display",
1224
                              RadioSettingValueList(AB_LIST,
1225
                                                    AB_LIST[
1226
                                                    _settings.displayab]))
1227
            workmode.append(rs)
1228

    
1229
            rs = RadioSetting("workmode", "VFO/MR Mode",
1230
                              RadioSettingValueList(WORKMODE_LIST,
1231
                                                    WORKMODE_LIST[
1232
                                                    _settings.workmode]))
1233
            workmode.append(rs)
1234

    
1235
            rs = RadioSetting("keylock", "Keypad Lock",
1236
                              RadioSettingValueBoolean(_settings.keylock))
1237
            workmode.append(rs)
1238

    
1239
            rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
1240
                              RadioSettingValueInteger(0, 127,
1241
                                                       _wmchannel.mrcha))
1242
            workmode.append(rs)
1243

    
1244
            rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
1245
                              RadioSettingValueInteger(0, 127,
1246
                                                       _wmchannel.mrchb))
1247
            workmode.append(rs)
1248

    
1249
            def convert_bytes_to_freq(bytes):
1250
               real_freq = 0
1251
               for byte in bytes:
1252
                   real_freq = (real_freq * 10) + byte
1253
               return chirp_common.format_freq(real_freq * 10)
1254

    
1255
            def my_validate(value):
1256
                value = chirp_common.parse_freq(value)
1257
                if 17400000 <= value and value < 40000000:
1258
                    msg = ("Can't be between 174.00000-400.00000")
1259
                    raise InvalidValueError(msg)
1260
                return chirp_common.format_freq(value)
1261

    
1262
            def apply_freq(setting, obj):
1263
                value = chirp_common.parse_freq(str(setting.value)) / 10
1264
                obj.band = value >= 40000000
1265
                for i in range(7, -1, -1):
1266
                    obj.freq[i] = value % 10
1267
                    value /= 10
1268

    
1269
            val1a = RadioSettingValueString(0, 10,
1270
                                            convert_bytes_to_freq(_vfoa.freq))
1271
            val1a.set_validate_callback(my_validate)
1272
            rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a)
1273
            rs.set_apply_callback(apply_freq, _vfoa)
1274
            workmode.append(rs)
1275

    
1276
            val1b = RadioSettingValueString(0, 10,
1277
                                            convert_bytes_to_freq(_vfob.freq))
1278
            val1b.set_validate_callback(my_validate)
1279
            rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b)
1280
            rs.set_apply_callback(apply_freq, _vfob)
1281
            workmode.append(rs)
1282

    
1283
            rs = RadioSetting("vfoa.sftd", "VFO A Shift",
1284
                              RadioSettingValueList(SHIFTD_LIST,
1285
                                                    SHIFTD_LIST[_vfoa.sftd]))
1286
            workmode.append(rs)
1287

    
1288
            rs = RadioSetting("vfob.sftd", "VFO B Shift",
1289
                              RadioSettingValueList(SHIFTD_LIST,
1290
                                                    SHIFTD_LIST[_vfob.sftd]))
1291
            workmode.append(rs)
1292

    
1293
            def convert_bytes_to_offset(bytes):
1294
               real_offset = 0
1295
               for byte in bytes:
1296
                   real_offset = (real_offset * 10) + byte
1297
               return chirp_common.format_freq(real_offset * 10000)
1298

    
1299
            def apply_offset(setting, obj):
1300
                value = chirp_common.parse_freq(str(setting.value)) / 10000
1301
                for i in range(3, -1, -1):
1302
                    obj.offset[i] = value % 10
1303
                    value /= 10
1304

    
1305
            val1a = RadioSettingValueString(0, 10,
1306
                                            convert_bytes_to_offset(
1307
                                            _vfoa.offset))
1308
            rs = RadioSetting("vfoa.offset", "VFO A Offset (0.00-69.95)", val1a)
1309
            rs.set_apply_callback(apply_offset, _vfoa)
1310
            workmode.append(rs)
1311

    
1312
            val1b = RadioSettingValueString(0, 10,
1313
                                            convert_bytes_to_offset(
1314
                                            _vfob.offset))
1315
            rs = RadioSetting("vfob.offset", "VFO B Offset (0.00-69.95)", val1b)
1316
            rs.set_apply_callback(apply_offset, _vfob)
1317
            workmode.append(rs)
1318

    
1319
            if self.MODEL == "KT-980HP" or self.MODEL == "BF-F8HP":
1320
                rs = RadioSetting("vfoa.txpower3", "VFO A Power",
1321
                                  RadioSettingValueList(TXPOWER3_LIST,
1322
                                                        TXPOWER3_LIST[
1323
                                                        _vfoa.txpower3]))
1324
                workmode.append(rs)
1325

    
1326
                rs = RadioSetting("vfob.txpower3", "VFO B Power",
1327
                                  RadioSettingValueList(TXPOWER3_LIST,
1328
                                                        TXPOWER3_LIST[
1329
                                                        _vfob.txpower3]))
1330
                workmode.append(rs)
1331
            else:
1332
                rs = RadioSetting("vfoa.txpower", "VFO A Power",
1333
                                  RadioSettingValueList(TXPOWER_LIST,
1334
                                                        TXPOWER_LIST[
1335
                                                        _vfoa.txpower]))
1336
                workmode.append(rs)
1337

    
1338
                rs = RadioSetting("vfob.txpower", "VFO B Power",
1339
                                  RadioSettingValueList(TXPOWER_LIST,
1340
                                                        TXPOWER_LIST[
1341
                                                        _vfob.txpower]))
1342
                workmode.append(rs)
1343

    
1344

    
1345
            rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth",
1346
                              RadioSettingValueList(BANDWIDTH_LIST,
1347
                                                    BANDWIDTH_LIST[
1348
                                                    _vfoa.widenarr]))
1349
            workmode.append(rs)
1350

    
1351
            rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
1352
                              RadioSettingValueList(BANDWIDTH_LIST,
1353
                                                    BANDWIDTH_LIST[
1354
                                                    _vfob.widenarr]))
1355
            workmode.append(rs)
1356

    
1357
            rs = RadioSetting("vfoa.scode", "VFO A PTT-ID",
1358
                              RadioSettingValueList(PTTIDCODE_LIST,
1359
                                                    PTTIDCODE_LIST[
1360
                                                    _vfoa.scode]))
1361
            workmode.append(rs)
1362

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

    
1369
            if not self._is_orig():
1370
                rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
1371
                                  RadioSettingValueList(STEP291_LIST,
1372
                                                        STEP291_LIST[
1373
                                                        _vfoa.step]))
1374
                workmode.append(rs)
1375
                rs = RadioSetting("vfob.step", "VFO B Tuning Step",
1376
                                  RadioSettingValueList(STEP291_LIST,
1377
                                                        STEP291_LIST[
1378
                                                        _vfob.step]))
1379
                workmode.append(rs)
1380
            else:
1381
                rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
1382
                                  RadioSettingValueList(STEP_LIST,
1383
                                                        STEP_LIST[_vfoa.step]))
1384
                workmode.append(rs)
1385
                rs = RadioSetting("vfob.step", "VFO B Tuning Step",
1386
                                  RadioSettingValueList(STEP_LIST,
1387
                                                        STEP_LIST[_vfob.step]))
1388
                workmode.append(rs)
1389

    
1390
        fm_preset = RadioSettingGroup("fm_preset", "FM Radio Preset")
1391
        group.append(fm_preset)
1392

    
1393
        if self._memobj.fm_presets <= 116.1 * 10 - 650:
1394
            preset = self._memobj.fm_presets / 10.0 + 65
1395
        else:
1396
            preset = 76.0
1397
        rs = RadioSetting("fm_presets", "FM Preset(MHz)",
1398
                      RadioSettingValueFloat(65, 116.1, preset, 0.1, 1))
1399
        fm_preset.append(rs)
1400

    
1401
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
1402
        group.append(dtmf)
1403
        dtmfchars = "0123456789 *#ABCD"
1404

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

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

    
1439
        rs = RadioSetting("ani.aniid", "ANI ID",
1440
                          RadioSettingValueList(PTTID_LIST,
1441
                                                PTTID_LIST[_ani.aniid]))
1442
        dtmf.append(rs)
1443

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

    
1460
        rs = RadioSetting("dtmfst", "DTMF Sidetone",
1461
                          RadioSettingValueList(DTMFST_LIST,
1462
                                                DTMFST_LIST[_settings.dtmfst]))
1463
        dtmf.append(rs)
1464

    
1465
        if _ani.dtmfon > 0xC3:
1466
            val = 0x00
1467
        else:
1468
            val = _ani.dtmfon
1469
        rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
1470
                          RadioSettingValueList(DTMFSPEED_LIST,
1471
                                                DTMFSPEED_LIST[val]))
1472
        dtmf.append(rs)
1473

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

    
1483
        return group
1484

    
1485
    def get_settings(self):
1486
        try:
1487
            return self._get_settings()
1488
        except:
1489
            import traceback
1490
            print "Failed to parse settings:"
1491
            traceback.print_exc()
1492
            return None
1493

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

    
1521
                    if element.has_apply_callback():
1522
                        print "Using apply callback"
1523
                        element.run_apply_callback()
1524
                    else:
1525
                        print "Setting %s = %s" % (setting, element.value)
1526
                        setattr(obj, setting, element.value)
1527
                except Exception, e:
1528
                    print element.get_name()
1529
                    raise
1530

    
1531
    def _set_fm_preset(self, settings):
1532
        for element in settings:
1533
            try:
1534
                val = element.value
1535
                value = int(val.get_value() * 10 - 650)
1536
                print "Setting fm_presets = %s" % (value)
1537
                self._memobj.fm_presets = value
1538
            except Exception, e:
1539
                print element.get_name()
1540
                raise
1541

    
1542
@directory.register
1543
class BaofengF11Radio(BaofengUV5R):
1544
    VENDOR = "Baofeng"
1545
    MODEL = "F-11"
1546
    _basetype = BASETYPE_F11
1547
    _idents = [UV5R_MODEL_F11]
1548

    
1549
    def _is_orig(self):
1550
        # Override this for F11 to always return False
1551
        return False
1552

    
1553
@directory.register
1554
class BaofengUV82Radio(BaofengUV5R):
1555
    MODEL = "UV-82"
1556
    _basetype = BASETYPE_UV82
1557
    _idents = [UV5R_MODEL_UV82]
1558
    _vhf_range = (130000000, 176000000)
1559
    _uhf_range = (400000000, 521000000)
1560

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

    
1565
@directory.register
1566
class BaofengUV6Radio(BaofengUV5R):
1567
    """Baofeng UV-6/UV-7"""
1568
    VENDOR = "Baofeng"
1569
    MODEL = "UV-6"
1570
    _basetype = BASETYPE_UV6
1571
    _idents = [UV5R_MODEL_UV6]
1572

    
1573
    def get_features(self):
1574
        rf = BaofengUV5R.get_features(self)
1575
        rf.memory_bounds = (1, 128)
1576
        return rf
1577

    
1578
    def _get_mem(self, number):
1579
        return self._memobj.memory[number - 1]
1580

    
1581
    def _get_nam(self, number):
1582
        return self._memobj.names[number - 1]
1583

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

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

    
1590
    def _is_orig(self):
1591
        # Override this for UV6 to always return False
1592
        return False
1593

    
1594
@directory.register
1595
class IntekKT980Radio(BaofengUV5R):
1596
    VENDOR = "Intek"
1597
    MODEL = "KT-980HP"
1598
    _basetype = BASETYPE_KT980HP
1599
    _idents = [UV5R_MODEL_291]
1600
    _vhf_range = (130000000, 180000000)
1601
    _uhf_range = (400000000, 521000000)
1602

    
1603
    def get_features(self):
1604
        rf = BaofengUV5R.get_features(self)
1605
        rf.valid_power_levels = UV5R_POWER_LEVELS3
1606
        return rf
1607

    
1608
    def _is_orig(self):
1609
        # Override this for KT980HP to always return False
1610
        return False
1611

    
1612
@directory.register
1613
class BaofengBFF8HPRadio(BaofengUV5R):
1614
    VENDOR = "Baofeng"
1615
    MODEL = "BF-F8HP"
1616
    _basetype = BASETYPE_F8HP
1617
    _idents = [UV5R_MODEL_291]
1618
    _vhf_range = (130000000, 180000000)
1619
    _uhf_range = (400000000, 521000000)
1620

    
1621
    def get_features(self):
1622
        rf = BaofengUV5R.get_features(self)
1623
        rf.valid_power_levels = UV5R_POWER_LEVELS3
1624
        return rf
1625

    
1626
    def _is_orig(self):
1627
        # Override this for BFF8HP to always return False
1628
        return False
(7-7/7)