|
#!/usr/bin/env python
|
|
|
|
# Script to test the comms and get a dump of the
|
|
# BTECH UV-5001 mobile/base radio memory.
|
|
#
|
|
# It works OK on windows with python 2.7.3 and pyserial 2.7
|
|
# It will try all comports from COM1 to COM10
|
|
#
|
|
# Author Pavel, CO7WT, co7wt@frcuba.co.cu, pavelmc@gmail.com
|
|
|
|
import serial
|
|
import struct
|
|
import os
|
|
import time
|
|
|
|
fpath = "c:\\"
|
|
filename = "uv5001"
|
|
|
|
# accesory function, jump it.
|
|
def hexprint(data, addrfmt=None):
|
|
"""Return a hexdump-like encoding of @data"""
|
|
if addrfmt is None:
|
|
addrfmt = '%(addr)03i'
|
|
|
|
block_size = 8
|
|
|
|
lines = len(data) / block_size
|
|
|
|
if (len(data) % block_size) != 0:
|
|
lines += 1
|
|
data += "\x00" * ((lines * block_size) - len(data))
|
|
|
|
out = ""
|
|
|
|
for block in range(0, (len(data)/block_size)):
|
|
addr = block * block_size
|
|
try:
|
|
out += addrfmt % locals()
|
|
except (OverflowError, ValueError, TypeError, KeyError):
|
|
out += "%03i" % addr
|
|
out += ': '
|
|
|
|
left = len(data) - (block * block_size)
|
|
if left < block_size:
|
|
limit = left
|
|
else:
|
|
limit = block_size
|
|
|
|
for j in range(0, limit):
|
|
out += "%02x " % ord(data[(block * block_size) + j])
|
|
|
|
out += " "
|
|
|
|
for j in range(0, limit):
|
|
char = data[(block * block_size) + j]
|
|
|
|
if ord(char) > 0x20 and ord(char) < 0x7E:
|
|
out += "%s" % char
|
|
else:
|
|
out += "."
|
|
|
|
out += "\n"
|
|
|
|
return out
|
|
|
|
def make_frame(cmd, addr, length, data=""):
|
|
"""Pack the info in the headder format"""
|
|
if data == "":
|
|
return "\x06" + struct.pack(">BHB", ord(cmd), addr, length)
|
|
else:
|
|
return "\x06" + struct.pack(">BHB", ord(cmd), addr, length) + data
|
|
|
|
def tx(radio, frame):
|
|
"""Send data to radio"""
|
|
radio.write(frame)
|
|
|
|
def rx(radio, addr, length):
|
|
"""Get data from the radio """
|
|
# 1 byte ACK +
|
|
# 4 bytes header +
|
|
# data of length of data (as I see 0x40 = 64 bytes)
|
|
# it's send a byte at a time, but we can read it all together
|
|
|
|
# catching ack
|
|
ack = radio.read(1)
|
|
if (ack == "\x05" and addr == 0) or ack == "\x06":
|
|
# received a correct ACK
|
|
print("Correct ACK (0x%02x) for block: 0x%04x" % (ord(ack), addr))
|
|
else:
|
|
print("BAD ACK (0x%02x) for block: 0x%04x" % (ord(ack), addr))
|
|
raise Exception, "Error"
|
|
|
|
# Get the header
|
|
hdr = radio.read(4)
|
|
if len(hdr) != 4:
|
|
print("BAD header length (%s) for block: 0x%04x" % (len(hdr), addr))
|
|
print("Try to make the time.sleep() bigger in dump_mem")
|
|
raise Exception, "Error"
|
|
else:
|
|
c, a, l = struct.unpack(">BHB", hdr)
|
|
print("Header received:")
|
|
print hexprint(hdr)
|
|
|
|
if a != addr or l != length or c != ord("X"):
|
|
print("BAD header data")
|
|
print("cmd %s, addr %04x, length %02x" % (c, a, l))
|
|
raise Exception, "Error"
|
|
else:
|
|
print("GOOD header data...")
|
|
|
|
# Get the data
|
|
data = radio.read(l)
|
|
if len(data) != l:
|
|
print("BAD data length of %s, vs. %s" % (l, length))
|
|
raise Exception, "Error"
|
|
else:
|
|
print("GOOD data...")
|
|
|
|
return data
|
|
|
|
def do_magic():
|
|
"""Try to get the radio y program mode and get the ident string"""
|
|
# every byte of this magic chain must be send separatedly util
|
|
# you get a correct answer, this will try 10 times
|
|
magic = "\x55\x20\x15\x09\x20\x45\x4d\x02"
|
|
tries = 10
|
|
|
|
# do the magic
|
|
for a in range(0, tries):
|
|
radio.flushInput()
|
|
for byte in magic:
|
|
ack = radio.read(1)
|
|
radio.write(byte)
|
|
|
|
# wait a prudent time....
|
|
time.sleep(0.2)
|
|
# Now you get a x06 of ACK if all goes well
|
|
ack = radio.read(1)
|
|
if ack == "\x06":
|
|
return True
|
|
else:
|
|
print "Attempt %s didn't work, trying again..." % a
|
|
|
|
return False
|
|
|
|
def do_ident(radio):
|
|
"""Put the radio on PROGRAM mode & identify it"""
|
|
|
|
# this is the block of good ident (less the fisrt ACK = \x06)
|
|
goodident = "\x01\x03\x00\x01\x07\x09\x04\x00\x00\x05\x02\x00\x57\x48\x4B"
|
|
goodident += "\x4A\x55\x56\x2D\x31\x36\x38\x56\x31\x39\x32\x30\x34\x55\x38"
|
|
goodident += "\x38\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30"
|
|
goodident += "\x30\x30\x30\x55"
|
|
# lenght = 49
|
|
|
|
# try to get the radio in program mode
|
|
ack = do_magic()
|
|
if ack is False:
|
|
raise Exception,"Radio did not accept program mode"
|
|
else:
|
|
print("Radio answered PC query, continue...")
|
|
|
|
# Ok, get the ident string
|
|
ident = radio.read(49)
|
|
|
|
# check if ident is OK
|
|
if len(ident) == 49 and ident == goodident:
|
|
# all ok
|
|
print("Good Ident it's a BTECH UV-5001, ident follows")
|
|
print(hexprint(ident))
|
|
else:
|
|
print("Bad/short/long radio ident: (%02i) bytes:" % len(ident))
|
|
print("Try to makee the time.sleep() bigger in dump_mem")
|
|
print(hexprint(ident))
|
|
raise Exception, "Error"
|
|
|
|
def dump_mem(radio):
|
|
"""Get the memory map"""
|
|
data = ""
|
|
for addr in range(0x0000, 0x3200, 0x40):
|
|
# sending the read request
|
|
tx(radio, make_frame("S", addr, 0x40))
|
|
|
|
# receiving the data and catching any errors
|
|
time.sleep(0.1)
|
|
try:
|
|
d = rx(radio, addr, 0x40)
|
|
except:
|
|
raise Exception, "Problem receiving block %s" % addr
|
|
|
|
print("Received block %04x" % addr)
|
|
data += d
|
|
|
|
return data
|
|
|
|
def upload(radio, data):
|
|
"""Test the upload procedure"""
|
|
|
|
# the fun start here
|
|
for addr in range(0x0000, 0x3200, 0x10):
|
|
# sending the data
|
|
tx(radio, make_frame("\x58", addr, 0x10, data[addr:addr + 0x10]))
|
|
|
|
# receiving the response
|
|
time.sleep(0.1)
|
|
ack = radio.read(1)
|
|
|
|
if (addr == 0 and ack == "\x05"):
|
|
ack = radio.read(1)
|
|
|
|
if ack == "\x06":
|
|
# received a correct ACK
|
|
print("Ok writed block block: 0x%04x" % addr)
|
|
else:
|
|
print("BAD ACK (0x%02x) for block: 0x%04x" % (ord(ack), addr))
|
|
raise Exception, "Error"
|
|
|
|
|
|
wsp = ""
|
|
# main program
|
|
for sp in range(1, 11):
|
|
# try to identify valid COM ports and use them to download img of the radio
|
|
# the IMG will be on c:\[filename].img
|
|
|
|
try:
|
|
radio = serial.Serial(port="COM" + str(sp)) # for windows
|
|
except Exception, e:
|
|
continue
|
|
|
|
# this port works
|
|
print " "
|
|
print("Serial por COM%s available, traying to reach radio" % sp)
|
|
|
|
try:
|
|
radio.setBaudrate(9600)
|
|
radio.setParity("N")
|
|
radio.setTimeout(0.5)
|
|
radio.flushInput()
|
|
radio.flushOutput()
|
|
except Exception, e:
|
|
print " "
|
|
print("Problem setting discipline on COM%s" % sp)
|
|
radio.close()
|
|
continue
|
|
|
|
# ok, will try to reach radio
|
|
do_ident(radio)
|
|
print " "
|
|
print("Radio Found!!!, downloading image to %s.img" % filename)
|
|
d = dump_mem(radio)
|
|
|
|
# save to file % dump to screen
|
|
print " "
|
|
print("Done, img file ready")
|
|
fil = open(os.path.join(fpath, filename + ".img" ), 'wb')
|
|
fil.write(d)
|
|
fil.close()
|
|
# set the correct serial port
|
|
wsp = sp
|
|
radio.close()
|
|
|
|
print("If download was a succsess I will wait for 2 minutes and then I will")
|
|
print("try a upload of the same data, so, you have 2 minutes to hit CTRL+C")
|
|
print("to abort starting from now")
|
|
|
|
time.sleep(120)
|
|
|
|
print("I will try an upload now, cross yur fingers")
|
|
|
|
## test upload
|
|
radio = serial.Serial(port="COM" + str(wsp))
|
|
try:
|
|
radio.setBaudrate(9600)
|
|
radio.setParity("N")
|
|
radio.setTimeout(0.5)
|
|
radio.flushInput()
|
|
radio.flushOutput()
|
|
except Exception, e:
|
|
raise Exception("Problem setting discipline on COM%s" % wsp)
|
|
|
|
# open radio
|
|
do_ident(radio)
|
|
# check the dat is correct
|
|
fil = open(os.path.join(fpath, filename + ".img" ), 'rb')
|
|
d = fil.read()
|
|
fil.close()
|
|
|
|
if len(d) != 0x3200:
|
|
raise Exception("This is not the correct file, size don't match")
|
|
|
|
upload(radio, d)
|
|
radio.close()
|
|
|