Project

General

Profile

Bug #3527 » btech.py

Chris Bates, 03/28/2016 05:52 PM

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

    
18
import time
19
import struct
20
import logging
21

    
22
LOG = logging.getLogger(__name__)
23

    
24
from chirp import chirp_common, directory, memmap
25
from chirp import bitwise, errors, util
26
from chirp.settings import RadioSettingGroup, RadioSetting, \
27
    RadioSettingValueBoolean, RadioSettingValueList, \
28
    RadioSettingValueString, RadioSettingValueInteger, \
29
    RadioSettings
30
from textwrap import dedent
31

    
32
MEM_FORMAT = """
33
#seekto 0x0000;
34
struct {
35
  lbcd rxfreq[4];
36
  lbcd txfreq[4];
37
  ul16 rxtone;
38
  ul16 txtone;
39
  u8 unknown0:4,
40
     scode:4;
41
  u8 unknown1:2,
42
     spmute:1,
43
     unknown2:3,
44
     optsig:2;
45
  u8 unknown3:3,
46
     scramble:1,
47
     unknown4:3,
48
     power:1;
49
  u8 unknown5:1,
50
     wide:1,
51
     unknown6:2,
52
     bcl:1,
53
     add:1,
54
     pttid:2;
55
} memory[200];
56

    
57
#seekto 0x1000;
58
struct {
59
  char name[6];
60
  u8 unknown1[10];
61
} names[200];
62

    
63
#seekto 0x3C90;
64
struct {
65
  u8 vhf_low[3];
66
  u8 vhf_high[3];
67
  u8 uhf_low[3];
68
  u8 uhf_high[3];
69
} ranges;
70

    
71
// the 2501+220 has a different zone for storing ranges
72

    
73
#seekto 0x3CD0;
74
struct {
75
  u8 vhf_low[3];
76
  u8 vhf_high[3];
77
  u8 unknown1[4];
78
  u8 unknown2[6];
79
  u8 vhf2_low[3];
80
  u8 vhf2_high[3];
81
  u8 unknown3[4];
82
  u8 unknown4[6];
83
  u8 uhf_low[3];
84
  u8 uhf_high[3];
85
} ranges220;
86

    
87
"""
88

    
89
# A note about the memmory in these radios
90
#
91
# The real memory of these radios extends to 0x4000
92
# On read the factory software only uses up to 0x3200
93
# On write it just uploads the contents up to 0x3100
94
#
95
# The mem beyond 0x3200 holds the ID data
96

    
97
MEM_SIZE = 0x4000
98
BLOCK_SIZE = 0x40
99
TX_BLOCK_SIZE = 0x10
100
ACK_CMD = "\x06"
101
MODES = ["FM", "NFM"]
102
SKIP_VALUES = ["S", ""]
103
TONES = chirp_common.TONES
104
DTCS = sorted(chirp_common.DTCS_CODES + [645])
105
NAME_LENGTH = 6
106
PTTID_LIST = ["OFF", "BOT", "EOT", "BOTH"]
107
PTTIDCODE_LIST = ["%s" % x for x in range(1, 16)]
108
OPTSIG_LIST = ["OFF", "DTMF", "2TONE", "5TONE"]
109

    
110
# this var controls the verbosity in the debug and by default it's low (False)
111
# make it True and you will to get a very verbose debug.log
112
debug = False
113

    
114
# Power Levels
115
NORMAL_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=25),
116
                       chirp_common.PowerLevel("Low", watts=10)]
117
UV5001_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=50),
118
                       chirp_common.PowerLevel("Low", watts=10)]
119

    
120
# this must be defined globaly
121
POWER_LEVELS = None
122

    
123
# valid chars on the LCD, Note that " " (space) is stored as "\xFF"
124
VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
125
    "`{|}!\"#$%&'()*+,-./:;<=>?@[]^_"
126

    
127

    
128
##### ID strings #####################################################
129

    
130
# BTECH UV2501 pre-production units
131
UV2501pp_id = "\x01\x03\x00\x01\x07\x09\x04\x00"
132
UV2501pp_id += "\x00\x05\x02\x00\x57\x48\x4B\x4A"
133
UV2501pp_id += "\x31\x36\x38\x4D\x49\x4E\x4D\x32"
134
UV2501pp_id += "\x43\x32\x39\x34\x55\x38\x38\x30"
135
UV2501pp_id += "\x30\x30\x30\x30\x30\x30\x30\x30"
136
UV2501pp_id += "\x30\x30\x30\x30\x30\x30\x30\x30"
137
UV2501pp_id += "\x55"
138
# fingerprint for the saved images (pre-production units)
139
UV2501pp_fid = "M2C294"
140

    
141

    
142
# BTECH UV2501 pre-production units
143
UV2501pp2_id = "\x01\x03\x06\x01\x07\x04\x04\x00"
144
UV2501pp2_id += "\x00\x04\x08\x00\x57\x48\x4B\x4A"
145
UV2501pp2_id += "\x31\x36\x38\x4D\x49\x4E\x4D\x32"
146
UV2501pp2_id += "\x39\x32\x30\x34\x55\x38\x38\x30"
147
UV2501pp2_id += "\x30\x30\x30\x30\x30\x30\x30\x30"
148
UV2501pp2_id += "\x30\x30\x30\x30\x30\x30\x30\x30"
149
UV2501pp2_id += "\x55"
150
# fingerprint for the saved images (pre-production units)
151
UV2501pp2_fid = "M29204"
152

    
153

    
154
# B-TECH UV-2501 first generation (1G)
155
UV2501_id = "\x01\x03\x00\x01\x07\x09\x04\x00"
156
UV2501_id += "\x00\x05\x02\x00\x57\x48\x4B\x4A"
157
UV2501_id += "\x31\x36\x38\x4D\x49\x4E\x4D\x32"
158
UV2501_id += "\x39\x32\x30\x34\x55\x38\x38\x30"
159
UV2501_id += "\x30\x30\x30\x30\x30\x30\x30\x30"
160
UV2501_id += "\x30\x30\x30\x30\x30\x30\x30\x30"
161
UV2501_id += "\x55"
162
# fingerprint for the saved images
163
UV2501_fid = "M29204"
164

    
165

    
166
# B-TECH UV-2501 second generation (2G)
167
UV2501G2_id = "\x01\x03\x00\x01\x07\x09\x04\x00"
168
UV2501G2_id += "\x00\x05\x02\x00\x57\x48\x4B\x4A"
169
UV2501G2_id += "\x31\x36\x38\x4D\x49\x4E\x42\x54"
170
UV2501G2_id += "\x47\x32\x31\x34\x55\x38\x38\x30"
171
UV2501G2_id += "\x30\x30\x30\x30\x30\x30\x30\x30"
172
UV2501G2_id += "\x30\x30\x30\x30\x30\x30\x30\x30"
173
UV2501G2_id += "\x55"
174
# fingerprint for the saved images
175
UV2501G2_fid = "BTG214"
176

    
177

    
178
# NOTE:
179
# About the ID2 for the 2501+220, that is a representative amount of data
180
# that must no pass the 16 bytes count, if right padding on the log
181
# please remove it from here, alse remove the header
182

    
183
# B-TECH UV-2501+220 pre-production units
184
UV2501_220pp_id = "\x01\x03\x00\x01\x07\x09\x00\x00"
185
UV2501_220pp_id += "\x00\x00\x4D\x49\x4E\x31\x32\x35"
186
UV2501_220pp_id += "\x02\x01\x00\x02\x03\x00\x00\x00"
187
UV2501_220pp_id += "\x00\x00\x4D\x33\x43\x32\x38\x31"
188
UV2501_220pp_id += "\x04\x00\x00\x05\x02\x00\x00\x00"
189
UV2501_220pp_id += "\x00\x00\x00\x00\x00\x00\x00\x00"
190
UV2501_220pp_id += "\x55"
191
# fingerprint for the saved images (pre-production units)
192
UV2501_220pp_fid = "M3C281"
193
# extra block read for the 2501+220 pre-production units
194
UV2501_220pp_id2 = "      280528"
195

    
196

    
197
# B-TECH UV-2501+220
198
UV2501_220_id = "\x01\x03\x00\x01\x07\x09\x00\x00"
199
UV2501_220_id += "\x00\x00\x4D\x49\x4E\x31\x32\x35"
200
UV2501_220_id += "\x02\x01\x00\x02\x03\x00\x00\x00"
201
UV2501_220_id += "\x00\x00\x4D\x33\x47\x32\x30\x31"
202
UV2501_220_id += "\x04\x00\x00\x05\x02\x00\x00\x00"
203
UV2501_220_id += "\x00\x00\x00\x00\x00\x00\x00\x00"
204
UV2501_220_id += "\x55"
205
# fingerprint for the saved images
206
UV2501_220_fid = "M3G201"
207
# extra block read for the 2501+220
208
UV2501_220_id2 = UV2501_220pp_id2
209

    
210

    
211
# B-TECH UV-5001 pre-production units
212
UV5001pp_id = "\x01\x03\x06\x01\x07\x04\x04\x00"
213
UV5001pp_id += "\x00\x04\x08\x00\x57\x48\x4B\x4A"
214
UV5001pp_id += "\x55\x56\x2D\x31\x36\x38\x56\x31"
215
UV5001pp_id += "\x39\x32\x30\x34\x55\x38\x38\x30"
216
UV5001pp_id += "\x30\x30\x30\x30\x30\x30\x30\x30"
217
UV5001pp_id += "\x30\x30\x30\x30\x30\x30\x30\x30"
218
UV5001pp_id += "\x55"
219
# fingerprint for the saved images
220
UV5001pp_fid = "V19204"
221

    
222

    
223
# B-TECH UV-5001 alpha units
224
UV5001alpha_id = "\x01\x03\x00\x01\x07\x09\x04\x00"
225
UV5001alpha_id += "\x00\x05\x02\x00\x57\x48\x4B\x4A"
226
UV5001alpha_id += "\x55\x56\x31\x36\x38\x38\x56\x32"
227
UV5001alpha_id += "\x38\x32\x30\x34\x55\x38\x38\x30"
228
UV5001alpha_id += "\x30\x30\x30\x30\x30\x30\x30\x30"
229
UV5001alpha_id += "\x30\x30\x30\x30\x30\x30\x30\x30"
230
UV5001alpha_id += "\x55"
231
# fingerprint for the saved images
232
UV5001alpha_fid = "V19204"
233

    
234

    
235
# B-TECH UV-5001 first generation (1G)
236
UV5001_id = "\x01\x03\x00\x01\x07\x09\x04\x00"
237
UV5001_id += "\x00\x05\x02\x00\x57\x48\x4B\x4A"
238
UV5001_id += "\x55\x56\x2D\x31\x36\x38\x56\x31"
239
UV5001_id += "\x39\x32\x30\x34\x55\x38\x38\x30"
240
UV5001_id += "\x30\x30\x30\x30\x30\x30\x30\x30"
241
UV5001_id += "\x30\x30\x30\x30\x30\x30\x30\x30"
242
UV5001_id += "\x55"
243
# fingerprint for the saved images
244
UV5001_fid = "V19204"
245

    
246

    
247
# B-TECH UV-5001 second generation (2G)
248
UV5001G2_id = "\x01\x03\x06\x01\x07\x04\x04\x00"
249
UV5001G2_id += "\x00\x04\x08\x00\x57\x48\x4B\x4A"
250
UV5001G2_id += "\x55\x56\x35\x30\x30\x31\x42\x54"
251
UV5001G2_id += "\x47\x32\x31\x34\x55\x38\x38\x30"
252
UV5001G2_id += "\x30\x30\x30\x30\x30\x30\x30\x30"
253
UV5001G2_id += "\x30\x30\x30\x30\x30\x30\x30\x30"
254
UV5001G2_id += "\x55"
255
# fingerprint for the saved images
256
UV5001G2_fid = "V2G204"
257

    
258

    
259
# WACCOM Mini-8900
260
MINI8900_id = "\x01\x03\x06\x01\x07\x04\x04\x00"
261
MINI8900_id += "\x00\x04\x08\x00\x57\x48\x4B\x4A"
262
MINI8900_id += "\x48\x54\x59\x32\x38\x38\x4D\x32"
263
MINI8900_id += "\x38\x38\x35\x34\x55\x38\x38\x30"
264
MINI8900_id += "\x30\x30\x30\x30\x30\x30\x30\x30"
265
MINI8900_id += "\x30\x30\x30\x30\x30\x30\x30\x30"
266
MINI8900_id += "\x55"
267
# fingerprint for the saved images
268
MINI8900_fid = "M28854"
269

    
270

    
271
#### MAGICS
272
# for the Waccom Mini-8900
273
MSTRING_MINI8900 = "\x55\xA5\xB5\x45\x55\x45\x4d\x02"
274
# for the B-TECH UV-2501+220 (including pre production ones)
275
MSTRING_220 = "\x55\x20\x15\x12\x12\x01\x4d\x02"
276
# magic string for all other models
277
MSTRING = "\x55\x20\x15\x09\x20\x45\x4d\x02"
278

    
279

    
280
def _rawrecv(radio, amount):
281
    """Raw read from the radio device, new approach, this time a byte at
282
    a time as the original driver, the receive data has to be atomic"""
283
    data = ""
284

    
285
    try:
286
        tdiff = 0
287
        start = time.time()
288
        maxtime = amount * 0.009
289

    
290
        while len(data) < amount and tdiff < maxtime:
291
            d = radio.pipe.read(1)
292
            if len(d) == 1:
293
                data += d
294

    
295
            # Delta time
296
            tdiff = time.time() - start
297

    
298
            # DEBUG
299
            if debug is True:
300
                LOG.debug("time diff %.04f maxtime %.04f, data: %d" %
301
                          (tdiff, maxtime, len(data)))
302

    
303
        # DEBUG
304
        if debug is True:
305
            LOG.debug("<== (%d) bytes:\n\n%s" %
306
                      (len(data), util.hexprint(data)))
307

    
308
        if len(data) < amount:
309
            LOG.error("Short reading %d bytes from the %d requested." %
310
                      (len(data), amount))
311

    
312
    except:
313
        raise errors.RadioError("Error reading data from radio")
314

    
315
    return data
316

    
317

    
318
def _rawsend(radio, data):
319
    """Raw send to the radio device"""
320
    try:
321
        for byte in data:
322
            radio.pipe.write(byte)
323
            time.sleep(0.003)
324

    
325
        # DEBUG
326
        if debug is True:
327
            LOG.debug("==> (%d) bytes:\n\n%s" %
328
                      (len(data), util.hexprint(data)))
329
    except:
330
        raise errors.RadioError("Error sending data to radio")
331

    
332

    
333
def _make_frame(cmd, addr, length, data=""):
334
    """Pack the info in the headder format"""
335
    frame = "\x06" + struct.pack(">BHB", ord(cmd), addr, length)
336
    # add the data if set
337
    if len(data) != 0:
338
        frame += data
339

    
340
    return frame
341

    
342

    
343
def _send(radio, frame, pause=0):
344
    """Generic send data to the radio"""
345
    _rawsend(radio, frame)
346

    
347
    # make a *optional* pause, to allow to build for an answer
348
    if pause != 0:
349
        time.sleep(pause)
350

    
351

    
352
def _recv(radio, addr):
353
    """Get data from the radio """
354
    # 1 byte ACK +
355
    # 4 bytes header +
356
    # data of length of data (as I see always 0x40 = 64 bytes)
357

    
358
    # catching ack
359
    ack = _rawrecv(radio, 1)
360

    
361
    # checking for a response
362
    if len(ack) != 1:
363
        msg = "No response in the read of the block #0x%04x" % addr
364
        LOG.error(msg)
365
        raise errors.RadioError(msg)
366

    
367
    # valid data
368
    if ack != ACK_CMD:
369
        msg = "Bad ack received from radio in block 0x%04x" % addr
370
        LOG.error(msg)
371
        LOG.debug("Bad ACK was 0x%02x" % ord(ack))
372
        raise errors.RadioError(msg)
373

    
374
    # Get the header + basic sanitize
375
    hdr = _rawrecv(radio, 4)
376
    if len(hdr) != 4:
377
        msg = "Short header for block: 0x%04x" % addr
378
        LOG.error(msg)
379
        raise errors.RadioError(msg)
380

    
381
    # receive and validate the header
382
    c, a, l = struct.unpack(">BHB", hdr)
383
    if a != addr or l != BLOCK_SIZE or c != ord("X"):
384
        msg = "Invalid answer for block 0x%04x:" % addr
385
        LOG.error(msg)
386
        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
387
        raise errors.RadioError(msg)
388

    
389
    # Get the data
390
    data = _rawrecv(radio, l)
391

    
392
    # basic validation
393
    if len(data) != l:
394
        msg = "Short block of data in block #0x%04x" % addr
395
        LOG.error(msg)
396
        raise errors.RadioError(msg)
397

    
398
    return data
399

    
400

    
401
def _do_magic(radio, status):
402
    """Try to put the radio in program mode and get the ident string
403
    it will make multiple tries"""
404

    
405
    # how many tries
406
    tries = 5
407

    
408
    # prep the data to show in the UI
409
    status.cur = 0
410
    status.msg = "Identifying the radio..."
411
    status.max = len(radio._magic) * tries
412
    radio.status_fn(status)
413
    mc = 0
414

    
415
    try:
416
        # do the magic
417
        for magic in radio._magic:
418
            # we try a few times
419
            for a in range(0, tries):
420
                # Update the UI
421
                status.cur = (mc * tries) + a
422
                radio.status_fn(status)
423

    
424
                # cleaning the serial buffer, try wrapped
425
                try:
426
                    radio.pipe.flushInput()
427
                except:
428
                    msg = "Error with a serial rx buffer flush at _do_magic"
429
                    LOG.error(msg)
430
                    raise errors.RadioError(msg)
431

    
432
                # send the magic a byte at a time
433
                for byte in magic:
434
                    ack = _rawrecv(radio, 1)
435
                    _send(radio, byte)
436

    
437
                # A explicit time delay, with a longer one for the UV-5001
438
                if "5001" in radio.MODEL:
439
                    time.sleep(0.5)
440
                else:
441
                    time.sleep(0.1)
442

    
443
                # Now you get a x06 of ACK if all goes well
444
                ack = _rawrecv(radio, 1)
445

    
446
                if ack == "\x06":
447
                    # DEBUG
448
                    LOG.info("Magic ACK received")
449
                    status.msg = "Positive Ident!"
450
                    status.cur = status.max
451
                    radio.status_fn(status)
452

    
453
                    return True
454

    
455
            # increment the count of magics to send, this is for the UI status
456
            mc += 1
457

    
458
            # wait between tries for different MAGICs to allow the radio to
459
            # timeout, this is an experimental fature
460
            time.sleep(3)
461

    
462
    except errors.RadioError:
463
        raise
464
    except Exception, e:
465
        msg = "Unknown error sending Magic to radio:\n%s" % e
466
        raise errors.RadioError(msg)
467

    
468
    return False
469

    
470

    
471
def _do_ident(radio, status):
472
    """Put the radio in PROGRAM mode & identify it"""
473
    #  set the serial discipline
474
    radio.pipe.setBaudrate(9600)
475
    radio.pipe.setParity("N")
476
    radio.pipe.setTimeout(0.005)
477
    # cleaning the serial buffer, try wrapped
478
    try:
479
        radio.pipe.flushInput()
480
    except:
481
        msg = "Error with a serial rx buffer flush at _do_ident"
482
        LOG.error(msg)
483
        raise errors.RadioError(msg)
484

    
485
    # do the magic trick
486
    if _do_magic(radio, status) is False:
487
        msg = "Radio did not respond to magic string, check your cable."
488
        LOG.error(msg)
489
        raise errors.RadioError(msg)
490

    
491
    # Ok, get the ident string
492
    ident = _rawrecv(radio, 49)
493

    
494
    # basic check for the ident
495
    if len(ident) != 49:
496
        msg = "Radio send a sort ident block, you need to increase maxtime."
497
        LOG.error(msg)
498
        raise errors.RadioError(msg)
499

    
500
    # check if ident is OK
501
    if not ident in radio.IDENT:
502
        # bad ident
503
        msg = "Incorrect model ID, got this:\n\n"
504
        msg += util.hexprint(ident)
505
        LOG.debug(msg)
506
        raise errors.RadioError("Radio identification failed.")
507

    
508
    # DEBUG
509
    LOG.info("Positive ident, this is a %s" % radio.MODEL)
510

    
511
    # Ok, we have a radio in the other end, we need a pause here
512
    time.sleep(0.01)
513

    
514
    # the 2501+220 has one more check:
515
    # reading the block 0x3DF0 to see if it's a code inside
516
    if "+220" in radio.MODEL:
517
        # DEBUG
518
        LOG.debug("This is a BTECH UV-2501+220, requesting the extra ID")
519
        # send the read request
520
        _send(radio, _make_frame("S", 0x3DF0, 16), 0.04)
521
        id2 = _rawrecv(radio, 20)
522
        # WARNING !!!!!!
523
        # Different versions send as response with a different amount of data
524
        # it seems that it's padded with \xff, \x20 and some times with \x00
525
        # we just care about the first 16, our magic string is in there
526
        if len(id2) < 16:
527
            msg = "The extra UV-2501+220 ID is short, aborting."
528
            # DEBUG
529
            LOG.error(msg)
530
            raise errors.RadioError(msg)
531

    
532
        # ok, check for it, any of the correct If must be in the received data
533
        itis = False
534
        for eid in radio._id2:
535
            if eid in id2:
536
                # DEBUG
537
                LOG.info("Confirmed, this is a BTECH UV-2501+220")
538
                # set the flag and exit
539
                itis = True
540
                break
541

    
542
        # It is a UV-2501+220?
543
        if itis is False:
544
            msg = "The extra UV-2501+220 ID is wrong, aborting."
545
            # DEBUG
546
            LOG.error(msg)
547
            LOG.debug("Full extra ID on the 2501+220 is: \n%s" %
548
                      util.hexprint(id2))
549
            raise errors.RadioError(msg)
550

    
551
    return True
552

    
553

    
554
def _download(radio):
555
    """Get the memory map"""
556

    
557
    # UI progress
558
    status = chirp_common.Status()
559

    
560
    # put radio in program mode and identify it
561
    _do_ident(radio, status)
562

    
563
    # the first dummy packet for all model but the 2501+220
564
    if not "+220" in radio.MODEL:
565
        # In the logs we have found that the first block is discarded
566
        # this is the \x05 in ack one, so we will simulate it here
567
        _send(radio, _make_frame("S", 0, BLOCK_SIZE), 0.1)
568
        discard = _rawrecv(radio, BLOCK_SIZE)
569

    
570
        if debug is True:
571
            LOG.info("Dummy first block read done, got this:\n\n")
572
            LOG.debug(util.hexprint(discard))
573

    
574
    # reset the progress bar in the UI
575
    status.max = MEM_SIZE / BLOCK_SIZE
576
    status.msg = "Cloning from radio..."
577
    status.cur = 0
578
    radio.status_fn(status)
579

    
580
    data = ""
581
    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
582
        # flush input, as per the original driver behavior, try wrapped
583
        try:
584
            radio.pipe.flushInput()
585
        except:
586
            msg = "Error with a serial rx buffer flush at _download"
587
            LOG.error(msg)
588
            raise errors.RadioError(msg)
589

    
590
        # sending the read request
591
        _send(radio, _make_frame("S", addr, BLOCK_SIZE), 0.1)
592

    
593
        # read
594
        d = _recv(radio, addr)
595

    
596
        # aggregate the data
597
        data += d
598

    
599
        # UI Update
600
        status.cur = addr / BLOCK_SIZE
601
        status.msg = "Cloning from radio..."
602
        radio.status_fn(status)
603

    
604
    return data
605

    
606

    
607
def _upload(radio):
608
    """Upload procedure"""
609

    
610
    # The UPLOAD mem is restricted to lower than 0x3100,
611
    # so we will overide that here localy
612
    MEM_SIZE = 0x3100
613

    
614
    # UI progress
615
    status = chirp_common.Status()
616

    
617
    # put radio in program mode and identify it
618
    _do_ident(radio, status)
619

    
620
    # get the data to upload to radio
621
    data = radio.get_mmap()
622

    
623
    # Reset the UI progress
624
    status.max = MEM_SIZE / TX_BLOCK_SIZE
625
    status.cur = 0
626
    status.msg = "Cloning to radio..."
627
    radio.status_fn(status)
628

    
629
    # the fun start here
630
    for addr in range(0, MEM_SIZE, TX_BLOCK_SIZE):
631
        # flush input, as per the original driver behavior, try wrapped
632
        try:
633
            radio.pipe.flushInput()
634
        except:
635
            msg = "Error with a serial rx buffer flush at _upload"
636
            LOG.error(msg)
637
            raise errors.RadioError(msg)
638

    
639
        # sending the data
640
        d = data[addr:addr + TX_BLOCK_SIZE]
641
        _send(radio, _make_frame("X", addr, TX_BLOCK_SIZE, d), 0.015)
642

    
643
        # receiving the response
644
        ack = _rawrecv(radio, 1)
645

    
646
        # basic check
647
        if len(ack) != 1:
648
            msg = "No response in the write of block #0x%04x" % addr
649
            LOG.error(msg)
650
            raise errors.RadioError(msg)
651

    
652
        if not ack in "\x06\x05":
653
            msg = "Bad ack writing block 0x%04x:" % addr
654
            LOG.info(msg)
655
            raise errors.RadioError(msg)
656

    
657
         # UI Update
658
        status.cur = addr / TX_BLOCK_SIZE
659
        status.msg = "Cloning to radio..."
660
        radio.status_fn(status)
661

    
662

    
663
def model_match(cls, data):
664
    """Match the opened/downloaded image to the correct version"""
665
    rid = data[0x3f70:0x3f76]
666

    
667
    if rid in cls._fileid:
668
        return True
669

    
670
    return False
671

    
672

    
673
def _decode_ranges(low, high):
674
    """Unpack the data in the ranges zones in the memap and return
675
    a tuple with the integer corresponding to the Mhz it means"""
676
    ilow = int(low[0]) * 100 \
677
        + int(low[1]) * 10 \
678
        + int(low[2])
679
    ihigh = int(high[0]) * 100 \
680
        + int(high[1]) * 10 \
681
        + int(high[2])
682
    ilow *= 1000000
683
    ihigh *= 1000000
684

    
685
    return (ilow, ihigh)
686

    
687

    
688
class btech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
689
    """BTECH's UV-5001 and alike radios"""
690
    VENDOR = "BTECH"
691
    MODEL = ""
692
    IDENT = ""
693
    _vhf_range = (130000000, 179000000)
694
    _220_range = (220000000, 240000000)
695
    _uhf_range = (400000000, 520000000)
696
    _upper = 199
697
    _magic = None
698
    _fileid = None
699

    
700
    @classmethod
701
    def get_prompts(cls):
702
        rp = chirp_common.RadioPrompts()
703
        rp.experimental = \
704
            ('This driver is experimental and for personal use only.\n'
705
             '\n'
706
             'Please keep a copy of you memories with the original software '
707
             'if you treasure them, this is the first release and may contain'
708
             ' bugs.\n'
709
             '\n'
710
             'You will miss the setting tab, we are working on it. Your '
711
             'success/failure story is appreciated, visit the Chirp\'s '
712
             'website and drop us a comment or just say THANKS if it works '
713
             'for you.\n'
714
             )
715
        rp.pre_download = _(dedent("""\
716
            Follow this instructions to download your info:
717

    
718
            1 - Turn off your radio
719
            2 - Connect your interface cable
720
            3 - Turn on your radio
721
            4 - Do the download of your radio data
722

    
723
            """))
724
        rp.pre_upload = _(dedent("""\
725
            Follow this instructions to upload your info:
726

    
727
            1 - Turn off your radio
728
            2 - Connect your interface cable
729
            3 - Turn on your radio
730
            4 - Do the upload of your radio data
731

    
732
            """))
733
        return rp
734

    
735
    def get_features(self):
736
        """Get the radio's features"""
737

    
738
        # we will use the following var as global
739
        global POWER_LEVELS
740

    
741
        rf = chirp_common.RadioFeatures()
742
        rf.has_settings = False
743
        rf.has_bank = False
744
        rf.has_tuning_step = False
745
        rf.can_odd_split = True
746
        rf.has_name = True
747
        rf.has_offset = True
748
        rf.has_mode = True
749
        rf.has_dtcs = True
750
        rf.has_rx_dtcs = True
751
        rf.has_dtcs_polarity = True
752
        rf.has_ctone = True
753
        rf.has_cross = True
754
        rf.valid_modes = MODES
755
        rf.valid_characters = VALID_CHARS
756
        rf.valid_name_length = NAME_LENGTH
757
        rf.valid_duplexes = ["", "-", "+", "split", "off"]
758
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
759
        rf.valid_cross_modes = [
760
            "Tone->Tone",
761
            "DTCS->",
762
            "->DTCS",
763
            "Tone->DTCS",
764
            "DTCS->Tone",
765
            "->Tone",
766
            "DTCS->DTCS"]
767
        rf.valid_skips = SKIP_VALUES
768
        rf.valid_dtcs_codes = DTCS
769
        rf.memory_bounds = (0, self._upper)
770

    
771
        # power levels
772
        if self.MODEL == "UV-5001":
773
            POWER_LEVELS = UV5001_POWER_LEVELS  # Higher power (50W)
774
        else:
775
            POWER_LEVELS = NORMAL_POWER_LEVELS  # Lower power (25W)
776

    
777
        rf.valid_power_levels = POWER_LEVELS
778

    
779
        # bands
780
        rf.valid_bands = [self._vhf_range, self._uhf_range]
781

    
782
        # 2501+220
783
        if self.MODEL == "UV-2501+220":
784
            rf.valid_bands.append(self._220_range)
785

    
786
        return rf
787

    
788
    def sync_in(self):
789
        """Download from radio"""
790
        data = _download(self)
791
        self._mmap = memmap.MemoryMap(data)
792
        self.process_mmap()
793

    
794
    def sync_out(self):
795
        """Upload to radio"""
796
        try:
797
            _upload(self)
798
        except errors.RadioError:
799
            raise
800
        except Exception, e:
801
            raise errors.RadioError("Error: %s" % e)
802

    
803
    def set_options(self):
804
        """This is to read the options from the image and set it in the
805
        environment, for now just the limits of the freqs in the VHF/UHF
806
        ranges"""
807

    
808
        # setting the correct ranges for each radio type
809
        if self.MODEL == "UV-2501+220":
810
            # the model 2501+220 has a segment in 220
811
            # and a different position in the memmap
812
            ranges = self._memobj.ranges220
813
        else:
814
            ranges = self._memobj.ranges
815

    
816
        # the normal dual bands
817
        vhf = _decode_ranges(ranges.vhf_low, ranges.vhf_high)
818
        uhf = _decode_ranges(ranges.uhf_low, ranges.uhf_high)
819

    
820
        # DEBUG
821
        LOG.info("Radio ranges: VHF %d to %d" % vhf)
822
        LOG.info("Radio ranges: UHF %d to %d" % uhf)
823

    
824
        # 220Mhz case
825
        if self.MODEL == "UV-2501+220":
826
            vhf2 = _decode_ranges(ranges.vhf2_low, ranges.vhf2_high)
827
            LOG.info("Radio ranges: VHF(220) %d to %d" % vhf2)
828
            self._220_range = vhf2
829

    
830
        # set the class with the real data
831
        self._vhf_range = vhf
832
        self._uhf_range = uhf
833

    
834
    def process_mmap(self):
835
        """Process the mem map into the mem object"""
836

    
837
        # Get it
838
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
839

    
840
        # load specific parameters from the radio image
841
        self.set_options()
842

    
843
    def get_raw_memory(self, number):
844
        return repr(self._memobj.memory[number])
845

    
846
    def _decode_tone(self, val):
847
        """Parse the tone data to decode from mem, it returns:
848
        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
849
        pol = None
850

    
851
        if val in [0, 65535]:
852
            return '', None, None
853
        elif val > 0x0258:
854
            a = val / 10.0
855
            return 'Tone', a, pol
856
        else:
857
            if val > 0x69:
858
                index = val - 0x6A
859
                pol = "R"
860
            else:
861
                index = val - 1
862
                pol = "N"
863

    
864
            tone = DTCS[index]
865
            return 'DTCS', tone, pol
866

    
867
    def _encode_tone(self, memval, mode, val, pol):
868
        """Parse the tone data to encode from UI to mem"""
869
        if mode == '' or mode is None:
870
            memval.set_raw("\x00\x00")
871
        elif mode == 'Tone':
872
            memval.set_value(val * 10)
873
        elif mode == 'DTCS':
874
            # detect the index in the DTCS list
875
            try:
876
                index = DTCS.index(val)
877
                if pol == "N":
878
                    index += 1
879
                else:
880
                    index += 0x6A
881
                memval.set_value(index)
882
            except:
883
                msg = "Digital Tone '%d' is not supported" % value
884
                LOG.error(msg)
885
                raise errors.RadioError(msg)
886
        else:
887
            msg = "Internal error: invalid mode '%s'" % mode
888
            LOG.error(msg)
889
            raise errors.InvalidDataError(msg)
890

    
891
    def get_memory(self, number):
892
        """Get the mem representation from the radio image"""
893
        _mem = self._memobj.memory[number]
894
        _names = self._memobj.names[number]
895

    
896
        # Create a high-level memory object to return to the UI
897
        mem = chirp_common.Memory()
898

    
899
        # Memory number
900
        mem.number = number
901

    
902
        if _mem.get_raw()[0] == "\xFF":
903
            mem.empty = True
904
            return mem
905

    
906
        # Freq and offset
907
        mem.freq = int(_mem.rxfreq) * 10
908
        # tx freq can be blank
909
        if _mem.get_raw()[4] == "\xFF":
910
            # TX freq not set
911
            mem.offset = 0
912
            mem.duplex = "off"
913
        else:
914
            # TX feq set
915
            offset = (int(_mem.txfreq) * 10) - mem.freq
916
            if offset != 0:
917
                if offset > 70000000:   # 70 Mhz
918
                    mem.duplex = "split"
919
                    mem.offset = int(_mem.txfreq) * 10
920
                elif offset < 0:
921
                    mem.offset = abs(offset)
922
                    mem.duplex = "-"
923
                elif offset > 0:
924
                    mem.offset = offset
925
                    mem.duplex = "+"
926
            else:
927
                mem.offset = 0
928

    
929
        # name TAG of the channel
930
        mem.name = str(_names.name).rstrip("\xFF").replace("\xFF", " ")
931

    
932
        # power
933
        mem.power = POWER_LEVELS[int(_mem.power)]
934

    
935
        # wide/narrow
936
        mem.mode = MODES[int(_mem.wide)]
937

    
938
        # skip
939
        mem.skip = SKIP_VALUES[_mem.add]
940

    
941
        # tone data
942
        rxtone = txtone = None
943
        txtone = self._decode_tone(_mem.txtone)
944
        rxtone = self._decode_tone(_mem.rxtone)
945
        chirp_common.split_tone_decode(mem, txtone, rxtone)
946

    
947
        # Extra
948
        mem.extra = RadioSettingGroup("extra", "Extra")
949

    
950
        spmute = RadioSetting("spmute", "Speaker mute",
951
                              RadioSettingValueBoolean(bool(_mem.spmute)))
952
        mem.extra.append(spmute)
953

    
954
        scramble = RadioSetting("scramble", "Scramble",
955
                                RadioSettingValueBoolean(bool(_mem.scramble)))
956
        mem.extra.append(scramble)
957

    
958
        bcl = RadioSetting("bcl", "Busy channel lockout",
959
                           RadioSettingValueBoolean(bool(_mem.bcl)))
960
        mem.extra.append(bcl)
961

    
962
        pttid = RadioSetting("pttid", "PTT ID",
963
                             RadioSettingValueList(PTTID_LIST,
964
                                                   PTTID_LIST[_mem.pttid]))
965
        mem.extra.append(pttid)
966

    
967
        pttidcode = RadioSetting("scode", "PTT ID signal code",
968
                                 RadioSettingValueList(
969
                                     PTTIDCODE_LIST,
970
                                     PTTIDCODE_LIST[_mem.scode]))
971
        mem.extra.append(pttidcode)
972

    
973
        optsig = RadioSetting("optsig", "Optional signaling",
974
                              RadioSettingValueList(
975
                                  OPTSIG_LIST,
976
                                  OPTSIG_LIST[_mem.optsig]))
977
        mem.extra.append(optsig)
978

    
979
        return mem
980

    
981
    def set_memory(self, mem):
982
        """Set the memory data in the eeprom img from the UI"""
983
        # get the eprom representation of this channel
984
        _mem = self._memobj.memory[mem.number]
985
        _names = self._memobj.names[mem.number]
986

    
987
        # if empty memmory
988
        if mem.empty:
989
            # the channel itself
990
            _mem.set_raw("\xFF" * 16)
991
            # the name tag
992
            _names.set_raw("\xFF" * 16)
993
            return
994

    
995
        # frequency
996
        _mem.rxfreq = mem.freq / 10
997

    
998
        # duplex
999
        if mem.duplex == "+":
1000
            _mem.txfreq = (mem.freq + mem.offset) / 10
1001
        elif mem.duplex == "-":
1002
            _mem.txfreq = (mem.freq - mem.offset) / 10
1003
        elif mem.duplex == "off":
1004
            for i in _mem.txfreq:
1005
                i.set_raw("\xFF")
1006
        elif mem.duplex == "split":
1007
            _mem.txfreq = mem.offset / 10
1008
        else:
1009
            _mem.txfreq = mem.freq / 10
1010

    
1011
        # tone data
1012
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
1013
            chirp_common.split_tone_encode(mem)
1014
        self._encode_tone(_mem.txtone, txmode, txtone, txpol)
1015
        self._encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
1016

    
1017
        # name TAG of the channel
1018
        if len(mem.name) < NAME_LENGTH:
1019
            # we must pad to NAME_LENGTH chars, " " = "\xFF"
1020
            mem.name = str(mem.name).ljust(NAME_LENGTH, " ")
1021
        _names.name = str(mem.name).replace(" ", "\xFF")
1022

    
1023
        # power, # default power level is high
1024
        _mem.power = 0 if mem.power is None else POWER_LEVELS.index(mem.power)
1025

    
1026
        # wide/marrow
1027
        _mem.wide = MODES.index(mem.mode)
1028

    
1029
        # scan add property
1030
        _mem.add = SKIP_VALUES.index(mem.skip)
1031

    
1032
        # reseting unknowns, this have to be set by hand
1033
        _mem.unknown1 = 0
1034
        _mem.unknown2 = 0
1035
        _mem.unknown3 = 0
1036
        _mem.unknown4 = 0
1037
        _mem.unknown5 = 0
1038
        _mem.unknown6 = 0
1039

    
1040
        # extra settings
1041
        if len(mem.extra) > 0:
1042
            # there are setting, parse
1043
            for setting in mem.extra:
1044
                setattr(_mem, setting.get_name(), setting.value)
1045
        else:
1046
            # there is no extra settings, load defaults
1047
            _mem.spmute = 0
1048
            _mem.optsig = 0
1049
            _mem.scramble = 0
1050
            _mem.bcl = 0
1051
            _mem.pttid = 0
1052
            _mem.scode = 0
1053

    
1054
        return mem
1055

    
1056
    @classmethod
1057
    def match_model(cls, filedata, filename):
1058
        match_size = False
1059
        match_model = False
1060

    
1061
        # testing the file data size
1062
        if len(filedata) == MEM_SIZE:
1063
            match_size = True
1064

    
1065
        # testing the firmware model fingerprint
1066
        match_model = model_match(cls, filedata)
1067

    
1068
        if match_size and match_model:
1069
            return True
1070
        else:
1071
            return False
1072

    
1073

    
1074
# Note:
1075
# the order in the lists in the _magic, IDENT and _fileid is important
1076
# we put the most common units first, the policy is as follows:
1077

    
1078
# - First lastest (newer) units, as they will be the most common
1079
# - Second the former latest version, and recursively...
1080
# - At the end the pre-production unitst (pp) as this will be unique
1081

    
1082
@directory.register
1083
class UV2501(btech):
1084
    """Baofeng Tech UV2501"""
1085
    MODEL = "UV-2501"
1086
    _magic = [MSTRING, ]
1087
    IDENT = [UV2501G2_id, UV2501_id, UV2501pp_id, UV2501pp2_id]
1088
    _fileid = [UV2501G2_fid, UV2501_fid, UV2501pp_fid]
1089

    
1090

    
1091
@directory.register
1092
class UV2501_220(btech):
1093
    """Baofeng Tech UV2501+220"""
1094
    MODEL = "UV-2501+220"
1095
    _magic = [MSTRING_220, ]
1096
    IDENT = [UV2501_220_id, UV2501_220pp_id]
1097
    _fileid = [UV2501_220_fid, UV2501_220pp_fid]
1098
    _id2 = [UV2501_220_id2, UV2501_220pp_id2]
1099

    
1100

    
1101
@directory.register
1102
class UV5001(btech):
1103
    """Baofeng Tech UV5001"""
1104
    MODEL = "UV-5001"
1105
    _magic = [MSTRING, ]
1106
    IDENT = [UV5001G2_id, UV5001_id, UV5001pp_id]
1107
    _fileid = [UV5001G2_fid, UV5001_fid, UV5001pp_fid]
1108

    
1109

    
1110
@directory.register
1111
class MINI8900(btech):
1112
    """WACCOM MINI-8900"""
1113
    VENDOR = "WACCOM"
1114
    MODEL = "MINI-8900"
1115
    _magic = [MSTRING_MINI8900, ]
1116
    IDENT = [MINI8900_id, UV5001alpha_id]
1117
    _fileid = [MINI8900_fid, ]
(3-3/9)