Several improvements (code refactoring) + add stuff to decode binary coordinate data block ("DCBN" command)

Wed, 19 Dec 2007 00:19:25 +0100

author
David Douard <david.douard@logilab.fr>
date
Wed, 19 Dec 2007 00:19:25 +0100
changeset 16
de9122b5680a
parent 15
b930440af354
child 17
fb8aa055f6e4

Several improvements (code refactoring) + add stuff to decode binary coordinate data block ("DCBN" command)

HP3562A/__init__.py file | annotate | diff | comparison | revisions
HP3562A/coord_decoder.py file | annotate | diff | comparison | revisions
HP3562A/enum_types.py file | annotate | diff | comparison | revisions
HP3562A/trace_decoder.py file | annotate | diff | comparison | revisions
bin/read_coord file | annotate | diff | comparison | revisions
examples/coord.bin file | annotate | diff | comparison | revisions
examples/state.bin file | annotate | diff | comparison | revisions
examples/trace.bin file | annotate | diff | comparison | revisions
--- a/HP3562A/__init__.py	Tue Dec 18 00:38:33 2007 +0100
+++ b/HP3562A/__init__.py	Wed Dec 19 00:19:25 2007 +0100
@@ -15,27 +15,46 @@
 import struct
 import re
 import gpib
+import numpy
 
 ########################################
 # HP3562A internal binary types decoders
 
 def decode_float(s):
-    assert len(s) in [4,8]
-    # exponential term 
-    e = ord(s[-1])
-    if e & 0x80:
-        e = e - 256
+    if len(s) == 4:
+        i1, i2, e = struct.unpack('>hbb', s)
+        i2 = i2 * 2**(-23)
+        return (i1/32768. + i2)*2**e
+    else:
+        i1, i2, i3, i4, e = struct.unpack('>hhhbb', s)
+        if i2 < 0:
+            i2 = (i2+32768.)*2**(-15)
+        else:
+            i2 = i2*2**(-15)
+        if i3 < 0:
+            i3 = (i3+32768.)*2**(-30)
+        else:
+            i3 = i3*2**(-15)
+        i4 = i4 * 2**(-38)
+        return (i1+i2+i3+i4)*2**(e-15)
+        
+# def decode_float(s):
+#     assert len(s) in [4,8]
+#     # exponential term 
+#     e = ord(s[-1])
+#     if e & 0x80:
+#         e = e - 256
 
-    # mantissa
-    m = [ord(x) for x in s[:-1]]
-    M = 0.
-    for i in range(len(s)-1):
-        #M += m[i]<<(i*8)
-        M += float(m[i])/2**((i+1)*8)
-    # XXX how do we deal negative numbers?
-    #if m[0] & 0x80:
-    #    M = M - 2^(len(s))
-    return M * 2**(e+1)
+#     # mantissa
+#     m = [ord(x) for x in s[:-1]]
+#     M = 0.
+#     for i in range(len(s)-1):
+#         #M += m[i]<<(i*8)
+#         M += float(m[i])/2**((i+1)*8)
+#     # XXX how do we deal negative numbers?
+#     #if m[0] & 0x80:
+#     #    M = M - 2^(len(s))
+#     return M * 2**(e+1)
 
 def decode_string(s):
     nb = ord(s[0])
@@ -59,6 +78,8 @@
     for row in head_struct:
         key = row[0]
         typ = row[1]
+        if typ is None:
+            continue
         val = header.get(key, "N/A")
         if isinstance(val, basestring):
             val = repr(val)
@@ -83,23 +104,31 @@
     fmt = "%%-%ds %%-%ds"%(maxk, maxv)
     w = maxk+maxv+4
     ncols = columns/w
-    nrows = len(todisp)/ncols
+    if ncols:
+        nrows = len(todisp)/ncols
+    else:
+        nrows = len(todisp)
     res = ""
     for i in range(nrows):
         res += "| ".join([fmt%todisp[j*nrows+i] for j in range(ncols)]) + "\n"
     return res
 
-def decode_header(data, header_struct):    
+def decode_header(data, header_struct, idx=0):    
     d = data
-    typ = d[:2]
-    assert typ == "#A"
-
-    totlen = struct.unpack('>h', d[2:4])[0]
-    idx = 4
+    if d[idx:].startswith('#'):
+        # we have a preliminary header here...
+        typ = d[idx:idx+2]
+        assert typ == "#A"
+        idx += 2
+        totlen = struct.unpack('>h', d[idx:idx+2])[0]
+        idx += 2
     tt=0
     header = {}
     for i, (nam, dtype, fmt, nbytes) in enumerate(header_struct):
-        if dtype == str:
+        if dtype is None:
+            idx += nbytes
+            continue
+        elif dtype == str:
             val = decode_string(d[idx:])
         else:
             if fmt:
@@ -114,6 +143,14 @@
         idx += nbytes
     return header, idx
 
+def read_trace(data, idx, nelts):
+    assert len(data[idx:]) >= (nelts*4), "data[idx:] is too small (%s for %s)"%(len(data[idx:]), (nelts*4))
+    resu = []
+    for i in range(nelts):
+        resu.append(decode_float(data[idx: idx+4]))
+        idx += 4
+    return numpy.array(resu, dtype=float)
+    
 #####################
 # HP3562A constants
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/HP3562A/coord_decoder.py	Wed Dec 19 00:19:25 2007 +0100
@@ -0,0 +1,126 @@
+# -*- coding: utf-8 -*-
+"""
+state_decoder
+=============
+
+Module for decoding the internal state of the HP3562A DSA, using the
+GPIB command "DSBN" (Dump State BiNary).
+
+This file can be exectued as a python script. Use '-h' for more informations. 
+"""
+
+from HP3562A import format_header, decode_float, decode_string, decode_header, read_trace
+from HP3562A.trace_decoder import decode_trace, HEADER as TRACE_HEADER
+from HP3562A.enum_types import *
+
+HEADER = [("Y coordinates", EYCOORD, 'h', 2),
+          ("# of disp elements", int, "h", 2),
+          ("First element", int, "h", 2),
+          ("Total elements", int, 'h', 2),
+          ("Display sampling", EDISPSMP, 'h', 2),
+          ("Scaling", ESCAL, 'h', 2),
+          ("Data pointer", long, 'l', 4),
+          ("In data", long, 'l', 4),
+          ("Log/linear x-axis", bool, 'h', 2),
+          ("Sampled display data", bool, 'h', 2),
+          ("Plot/Graph mode", bool, 'h', 2),
+          ("Phase wrap", bool, 'h', 2),
+          ("Not used", None, None, 36),
+          ("X scale factor", decode_float, None, 4),
+          ("Grid min Y scale", decode_float, None, 4),
+          ("Grid max Y scale", decode_float, None, 4),
+          ("/div", decode_float, None, 4),
+          ("Min value of data", decode_float, None, 4),
+          ("Max value of data", decode_float, None, 4),
+          ("Y cumulative min", decode_float, None, 4),
+          ("Y cumulative max", decode_float, None, 4),
+          ("Y scale factor", decode_float, None, 4),
+          ("Not used", None, None, 16),
+          ("Stop value", decode_float, None, 8),
+          ("Left grid", decode_float, None, 8),
+          ("Right grid", decode_float, None, 8),
+          ("Left data", decode_float, None, 8),
+          ("Right data", decode_float, None, 8),          
+    ]
+
+def decode_coord(data):
+    """
+    Decode the data (as generated by the HP3562A DSA in response to a
+    "DSBN" command), and returns a dict (header)
+
+    header is the dictionnary of the header of the dumped data block,
+    """
+    header, idx = decode_header(data, HEADER)
+    trace_header, idx = decode_header(data, TRACE_HEADER, idx)
+    trace = read_trace(data, idx, trace_header["Number of elements"])
+    return header, trace_header, trace
+    
+
+def main():
+    import sys
+    import optparse
+    import numpy
+    
+    opt = optparse.OptionParser("A simple tool for displaying dumped coord data block")
+    opt.add_option('-f', '--filename', default=None,
+                   dest='filename',
+                   help='Output filename. If not set, read from stdin')
+    opt.add_option('-m', '--mode', default='binary',
+                   dest='mode',
+                   help='Dumping mode (may be "binary" [default], "ascii" or "ansi")',
+                   )
+    opt.add_option('-d', '--display-header', default=False,
+                   action="store_true",
+                   dest="displayheader",
+                   help="Display the trace header")
+    opt.add_option('-P', '--noplot-trace', default=True,
+                   action="store_false",
+                   dest="plot",
+                   help="Do not display the plot of the trace")
+    opt.add_option('-x', '--xmode', default='lin',
+                   dest='xmode',
+                   help='X coordinate mode (may be "lin" [default] or "log")')
+    opt.add_option('-y', '--ymode', default='lin',
+                   dest='ymode',
+                   help='Y coordinate mode (may be "lin" [default], "log" or "db")')
+    
+    options, argv = opt.parse_args(sys.argv)
+
+
+    if options.filename is None:
+        print "Can't deal stdin for now..."
+        sys.exit(1)
+    try:
+        coord_header, header, data = decode_coord(open(options.filename, 'rb').read())
+    except Exception, e:
+        print "ERROR: can't read %s an interpret it as a HP3562 trace"%options.filename
+        print e
+        sys.exit(1)
+
+    if options.displayheader:
+        print format_header(coord_header, HEADER, 100)
+        print format_header(header, TRACE_HEADER, 100)
+    if options.plot:
+        f0 = header['Start freq value']
+        dx = header['Delta X-axis']
+        n = header['Number of elements']
+        x = numpy.linspace(f0, f0+dx*n, len(data)) 
+        y = data.copy()
+
+        ym = coord_header['Min value of data']
+        yM = coord_header['Max value of data']
+        ys = coord_header['Y scale factor']
+        
+        y[y<ym] = ym
+        y *= ys
+        
+        import pylab
+        pylab.ylabel(header['Amplitude units'])
+        pylab.grid()
+        pylab.plot(x, y)
+        pylab.xlabel('frequency (%s)'%header["X axis units"])
+        pylab.ylabel("%s (%s)"%(coord_header['Y coordinates'], header['Amplitude units']) )
+        pylab.show()
+    
+if __name__ == "__main__":
+    main()
--- a/HP3562A/enum_types.py	Tue Dec 18 00:38:33 2007 +0100
+++ b/HP3562A/enum_types.py	Wed Dec 19 00:19:25 2007 +0100
@@ -70,52 +70,52 @@
         2: "Volt (indicates peak only)",
         }
 
-EAMP = {0: "Volts",
-        1: "Volts squared",
-        2: "PSD (V²/Hz)",
-        3: "ESD (V²s/Hz)",
-        4: "PSD¹² (V/Hz¹²)",
-        5: "No unit",
-        6: "Unit volts",
-        7: "Unit volts²",
+EAMP = {0: u"Volts",
+        1: u"Volts²",
+        2: u"PSD (V²/Hz)",
+        3: u"ESD (V²s/Hz)",
+        4: u"PSD¹² (V/Hz¹²)",
+        5: u"No unit",
+        6: u"Unit volts",
+        7: u"Unit volts²",
         }
         
-EXAXIS= {0: "No units",
-         1: "Hertz",
-         2: "RPM",
-         3: "Orders",
-         4: "Seconds",
-         5: "Revs",
-         6: "Degrees",
-         7: "dB",
-         8: "dBV",
-         9: "Volts",
-         10: "V Hz¹²",
-         11: "Hz/s",
-         12: "V/EU",
-         13: "Vrms",
-         14: "V²/Hz",
-         15: "%",
-         16: "Points",
-         17: "Records",
-         18: "Ohms",
-         19: "Hertz/octave",
-         20: "Pulse/Rev",
-         21: "Decades",
-         22: "Minutes",
-         23: "V²s/Hz",
-         24: "Octave",
-         25: "Seconds/Decade",
-         26: "Seconds/Octave",
-         27: "Hz/Point",
-         28: "Points/Sweep",
-         29: "Points/Decade",
-         30: "Points/Octave",
-         31: "V/Vrms",
-         32: "V²",
-         33: "EU referenced to chan 1",
-         34: "EU referenced to chan 2",
-         35: "EU value",
+EXAXIS= {0: u"No units",
+         1: u"Hertz",
+         2: u"RPM",
+         3: u"Orders",
+         4: u"Seconds",
+         5: u"Revs",
+         6: u"Degrees",
+         7: u"dB",
+         8: u"dBV",
+         9: u"Volts",
+         10: u"V\u221AHz",
+         11: u"Hz/s",
+         12: u"V/EU",
+         13: u"Vrms",
+         14: u"V²/Hz",
+         15: u"%",
+         16: u"Points",
+         17: u"Records",
+         18: u"Ohms",
+         19: u"Hertz/octave",
+         20: u"Pulse/Rev",
+         21: u"Decades",
+         22: u"Minutes",
+         23: u"V²s/Hz",
+         24: u"Octave",
+         25: u"Seconds/Decade",
+         26: u"Seconds/Octave",
+         27: u"Hz/Point",
+         28: u"Points/Sweep",
+         29: u"Points/Decade",
+         30: u"Points/Octave",
+         31: u"V/Vrms",
+         32: u"V²",
+         33: u"EU referenced to chan 1",
+         34: u"EU referenced to chan 2",
+         35: u"EU value",
          }
 
 EMEAS = {0: "Linear resolution",
@@ -246,3 +246,25 @@
 ETRGLVLUNT = EXAXIS
 
 ECPTLGHUNT = EXAXIS
+
+EYCOORD = { 1: "Real",
+            2: "Imaginary",
+            3: "Linear magnitude",
+            4: "Log magnitude",
+            5: "dB",
+            6: "Nyquist",
+            7: "Not used",
+            8: "Phase",
+            9: "Nichols",
+            10: "dBm",
+            }
+
+EDISPSMP = { 0: "Not sampled", # #displayed elements = total elements
+             1: "Half sampled", # #displayed elements = total elements/2
+             2: "Sampled", # #displayed elements < total elements
+            }
+ESCAL = { 0: "X and Y auto scale",
+          1: "X fixed, Y auto scale",
+          2: "X auto scale, Y fixed",
+          3: "X and Y fixed",
+          }
--- a/HP3562A/trace_decoder.py	Tue Dec 18 00:38:33 2007 +0100
+++ b/HP3562A/trace_decoder.py	Wed Dec 19 00:19:25 2007 +0100
@@ -10,7 +10,7 @@
 """
 import struct
 import numpy
-from HP3562A import format_header, decode_float, decode_string, decode_header
+from HP3562A import format_header, decode_float, decode_string, decode_header, read_trace
 from HP3562A.enum_types import *
 
 
@@ -61,7 +61,7 @@
            ('Start data value', decode_float, None, 8),
            ]
 
-def decode_trace(data):
+def decode_trace(data, idx=0):
     """
     Decode the data (as generated by the HP3562A DSA in response to a
     "DDBN" command), and returns a couple (header, values).
@@ -70,14 +70,8 @@
     value is a numpy array holding the trace (vector of float or
     complex values).
     """
-    header, idx = decode_header(data, HEADER)
-    resu = []
-    for i in range(header["Number of elements"]):
-        resu.append(decode_float(data[idx: idx+4]))
-        idx += 4
-    return header, numpy.array(resu, dtype=float)
-
-
+    header, idx = decode_header(data, HEADER, idx)
+    return header, read_trace(data, idx, header["Number of elements"])
 
 def main():
     import sys
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/read_coord	Wed Dec 19 00:19:25 2007 +0100
@@ -0,0 +1,4 @@
+#!/usr/bin/python
+
+from HP3562A.coord_decoder import main
+main()
Binary file examples/coord.bin has changed
Binary file examples/state.bin has changed
Binary file examples/trace.bin has changed

mercurial