Project

General

Profile

New Model #3015 » uv5001.py

Python Windows script to download/upload proof of concept for the UV-5001 - Pavel Milanes, 01/08/2016 10:16 AM

 
#!/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()

(4-4/10)