]> hydra-www.ietfng.org Git - smallutils/commitdiff
Add brute-force uv5r reader/writer
authorNathaniel Wesley Filardo <nwf@cs.jhu.edu>
Sun, 4 May 2014 20:02:15 +0000 (16:02 -0400)
committerNathaniel Wesley Filardo <nwf@cs.jhu.edu>
Sun, 4 May 2014 20:02:15 +0000 (16:02 -0400)
Largely a workaround for what appears to be dubious hardware

ham/uv5r.py [new file with mode: 0755]

diff --git a/ham/uv5r.py b/ham/uv5r.py
new file mode 100755 (executable)
index 0000000..a6242cf
--- /dev/null
@@ -0,0 +1,196 @@
+#!/usr/bin/env python
+#
+# A slightly more paranoid UV5R programmer.  I found that some combination
+# of my radio and programming cable liked to crash mid-way through reads or
+# writes, and so I created this brute-force solution.  It seems to work, and
+# should produce CHIRP-compatible image files.
+#
+# Example usage:
+#  ./uv5r.py --read radio.img
+#  # load radio.img into chirp, edit to your heart's content, save back
+#  ./uv5r.py --write radio.img
+#
+# You should probably re-"--read" your radio after programming to make sure
+# that everything seems to have gotten there.  e.g.:
+#  ./uv5r.py --read radio2.img && cmp radio.img radio2.img && echo "OK"
+#
+# (C) 2014 Nathaniel Wesley Filardo
+# Distributable under terms of GNU GPLv3
+# Most code here originally "Copyright 2012 Dan Smith <dsmith@danplanet.com>"
+# as part of the CHIRP project.
+
+import sys, os, serial, time, struct
+import traceback
+import argparse
+
+def hexprint(data):
+    """Return a hexdump-like encoding of @data"""
+    line_sz = 8
+
+    lines = len(data) / line_sz
+    
+    if (len(data) % line_sz) != 0:
+        lines += 1
+        data += "\x00" * ((lines * line_sz) - len(data))
+
+    out = ""
+        
+    for i in range(0, (len(data)/line_sz)):
+        out += "%03i: " % (i * line_sz)
+
+        left = len(data) - (i * line_sz)
+        if left < line_sz:
+            limit = left
+        else:
+            limit = line_sz
+            
+        for j in range(0, limit):
+            out += "%02x " % ord(data[(i * line_sz) + j])
+
+        out += "  "
+
+        for j in range(0, limit):
+            char = data[(i * line_sz) + j]
+
+            if ord(char) > 0x20 and ord(char) < 0x7E:
+                out += "%s" % char
+            else:
+                out += "."
+
+        out += "\n"
+
+    return out
+
+def et(ser) :
+    ser.write("\x06")
+    assert (ser.read(1) == '\x06')
+
+def magic(ser) :
+    UV5R_MODEL_291  = "\x50\xBB\xFF\x20\x12\x07\x25"
+
+    for byte in UV5R_MODEL_291 :
+        ser.write(byte)
+    assert (ser.read(1) == '\x06')
+
+    ser.write('\x02')
+    ident = ser.read(8)
+    et(ser)
+
+    return ident
+
+def _readblk(ser, start, size) :
+    msg = struct.pack(">BHB", ord("S"), start, size)
+    ser.write(msg)
+
+    answer = ser.read(4)
+    assert (4 == len(answer))
+
+    cmd, addr, length = struct.unpack(">BHB", answer)
+    # print "CMD: %s  ADDR: %04x  SIZE: %02x" % (cmd, addr, length)
+    assert (cmd == ord("X") and addr == start and length == size)
+
+    chunk = ser.read(size)
+    assert (size == len(chunk))
+    et(ser)
+
+    return chunk
+
+def readblk(ser, start, size) :
+    needs_init = 0
+    while True:
+        print "ATTEMPTING READ OF BLOCK %x" % i
+        try:
+            if needs_init :
+                print "REINIT..."
+                time.sleep(10)
+                magic(ser)
+                needs_init = 0
+            return _readblk(ser, start, size)
+        except Exception:
+            traceback.print_exc()
+            needs_init = 1
+
+def writeblk(ser, start, chunk) :
+    size = len(chunk)
+    msg = struct.pack(">BHB", ord("X"), start, size)
+    needs_init = 0
+    while True:
+        print "ATTEMPTING WRITE TO BLOCK %x" % start
+        try:
+            if needs_init :
+                print "REINIT..."
+                time.sleep(10)
+                magic(ser)
+                needs_init = 0
+
+            assert (ser.write(msg) == len(msg))
+            assert (ser.write(chunk) == size)
+            assert (ser.read(1) == '\x06')
+
+            return
+        except Exception:
+            traceback.print_exc()
+            needs_init = 1
+
+parser = argparse.ArgumentParser(description='UV-5R image extractor')
+parser.add_argument('--radio', dest='radio', default="/dev/ttyUSB0")
+parser.add_argument('--read', dest='outfile', type=argparse.FileType('w'))
+parser.add_argument('--dump', dest='dump', action='store_true')
+parser.add_argument('--write', dest='infile', type=argparse.FileType('r'))
+
+args = parser.parse_args()
+
+ser = serial.Serial(args.radio, 9600, timeout=1)
+assert (ser)
+
+if args.outfile is not None or args.dump :
+
+  blocks = {}
+  
+  try:
+      ident = magic(ser)
+  except Exception:
+      traceback.print_exc()
+      print "Retrying initialization..."
+      ident = magic(ser)
+  
+  for i in range(0, 0x1800, 0x40):
+      blocks[i] = readblk(ser, i, 0x40)
+  
+  for i in range(0x1EC0, 0x2000, 0x40):
+      blocks[i] = readblk(ser, i, 0x40)
+  
+  bitems = blocks.items()
+  bitems.sort()
+  sblocks = [blk for (_,blk) in bitems]
+
+  data = ident + ''.join(sblocks)
+
+  if args.dump :
+    print "%s\n" % hexprint(data)
+  if args.outfile :
+    args.outfile.write(data)
+    args.outfile.close()
+
+if args.infile is not None :
+
+  expected_ident = args.infile.read(8)
+  blocks = {}
+
+  for i in range(0, 0x1800, 0x10):
+    blocks[i] = args.infile.read(0x10)
+
+  for i in range(0x1EC0, 0x2000, 0x10):
+    blocks[i] = args.infile.read(0x10)
+
+  assert(args.infile.read(1) == '')
+  args.infile.close()
+
+  actual_ident = magic(ser)
+  assert(expected_ident == actual_ident)
+
+  sblocks = blocks.items()
+  sblocks.sort()
+
+  for (k,v) in sblocks :
+    writeblk(ser, k, v)