1 # -*- coding: utf-8 -*- |
|
2 |
|
3 import struct |
|
4 import numpy |
|
5 |
|
6 |
|
7 def decode_float(s): |
|
8 assert len(s) in [4,8] |
|
9 # exponential term |
|
10 e = ord(s[-1]) |
|
11 if e & 0x80: |
|
12 e = e - 256 |
|
13 |
|
14 # mantissa |
|
15 m = [ord(x) for x in s[:-1]] |
|
16 M = 0. |
|
17 for i in range(len(s)-1): |
|
18 #M += m[i]<<(i*8) |
|
19 M += float(m[i])/2**((i+1)*8) |
|
20 # XXX how do we deal negative numbers? |
|
21 #if m[0] & 0x80: |
|
22 # M = M - 2^(len(s)) |
|
23 return M * 2**(e+1) |
|
24 |
|
25 def decode_string(s): |
|
26 nb = ord(s[0]) |
|
27 s = s[1:nb+2] |
|
28 r = "" |
|
29 # XXX why do we need to do this? It's not described in the manual... |
|
30 for c in s: |
|
31 r += chr(ord(c) & 0x7F) |
|
32 return r |
|
33 |
|
34 EDSP = {0: "No data", |
|
35 1: "Frequency response", |
|
36 2: "Power spectrum 1", |
|
37 3: "Power spectrum 2", |
|
38 4: "Coherence", |
|
39 5: "Cross spectrum", |
|
40 6: "Input time 1", |
|
41 7: "Input time 2", |
|
42 8: "Input linear spectrum 1", |
|
43 9: "Input linear spectrum 2", |
|
44 10: "Impulse response", |
|
45 11: "Cross correlation", |
|
46 12: "Auto correlation 1", |
|
47 13: "Auto correlation 2", |
|
48 14: "Histogram 1", |
|
49 15: "Histogram 2", |
|
50 16: "Cumulative density function 1", |
|
51 17: "Cumulative density function 2", |
|
52 18: "Probability density function 1", |
|
53 19: "Probability density function 2", |
|
54 20: "Average linear spectrum 1", |
|
55 21: "Average linear spectrum 2", |
|
56 22: "Average time record 1", |
|
57 23: "Average time record 2", |
|
58 24: "Synthesis pole-zeros", |
|
59 25: "Synthesis pole-residue", |
|
60 26: "Synthesis polynomial", |
|
61 27: "Synthesis constant", |
|
62 28: "Windowed time record 1", |
|
63 29: "Windowed time record 2", |
|
64 30: "Windowed linear spectrum 1", |
|
65 31: "Windowed linear spectrum 2", |
|
66 32: "Filtered time record 1", |
|
67 33: "Filtered time record 2", |
|
68 34: "Filtered linear spectrum 1", |
|
69 35: "Filtered linear spectrum 2", |
|
70 36: "Time capture buffer", |
|
71 37: "Captured linear spectrum", |
|
72 38: "Captured time record", |
|
73 39: "Throughput time record 1", |
|
74 40: "Throughput time record 2", |
|
75 41: "Curve fit", |
|
76 42: "Weighted function", |
|
77 43: "Not used", |
|
78 44: "Orbits", |
|
79 45: "Demodulation polar", |
|
80 46: "Preview demod record 1", |
|
81 47: "Preview demod record 2", |
|
82 48: "Preview demod linear spectrum 1", |
|
83 49: "Preview demod linear spectrum 2", |
|
84 } |
|
85 |
|
86 ECH = {0: "Channel 1", |
|
87 1: "Channel 2", |
|
88 2: "Channel 1&2", |
|
89 3: "No channel", |
|
90 } |
|
91 |
|
92 EOVR = ECH |
|
93 |
|
94 EDOM = {0: 'Time', |
|
95 1: 'Frequency', |
|
96 2: 'Voltage (amplitude)', |
|
97 } |
|
98 |
|
99 EVLT = {0: "Peak", |
|
100 1: "RMS", |
|
101 2: "Volt (indicates peak only)", |
|
102 } |
|
103 |
|
104 EAMP = {0: "Volts", |
|
105 1: "Volts squared", |
|
106 2: "PSD (V²/Hz)", |
|
107 3: "ESD (V²s/Hz)", |
|
108 4: "PSD¹² (V/Hz¹²)", |
|
109 5: "No unit", |
|
110 6: "Unit volts", |
|
111 7: "Unit volts²", |
|
112 } |
|
113 |
|
114 EXAXIS= {0: "No units", |
|
115 1: "Hertz", |
|
116 2: "RPM", |
|
117 3: "Orders", |
|
118 4: "Seconds", |
|
119 5: "Revs", |
|
120 6: "Degrees", |
|
121 7: "dB", |
|
122 8: "dBV", |
|
123 9: "Volts", |
|
124 10: "V Hz¹²", |
|
125 11: "Hz/s", |
|
126 12: "V/EU", |
|
127 13: "Vrms", |
|
128 14: "V²/Hz", |
|
129 15: "%", |
|
130 16: "Points", |
|
131 17: "Records", |
|
132 18: "Ohms", |
|
133 19: "Hertz/octave", |
|
134 20: "Pulse/Rev", |
|
135 21: "Decades", |
|
136 22: "Minutes", |
|
137 23: "V²s/Hz", |
|
138 24: "Octave", |
|
139 25: "Seconds/Decade", |
|
140 26: "Seconds/Octave", |
|
141 27: "Hz/Point", |
|
142 28: "Points/Sweep", |
|
143 29: "Points/Decade", |
|
144 30: "Points/Octave", |
|
145 31: "V/Vrms", |
|
146 32: "V²", |
|
147 33: "EU referenced to chan 1", |
|
148 34: "EU referenced to chan 2", |
|
149 35: "EU value", |
|
150 } |
|
151 |
|
152 EMEAS = {0: "Linear resolution", |
|
153 1: "Log resolution", |
|
154 2: "Swept sine", |
|
155 3: "Time capture", |
|
156 4: "Linear resolution throughput", |
|
157 } |
|
158 |
|
159 EDEMOD1 = {45: "AM", |
|
160 46: "FM", |
|
161 47: "PM", |
|
162 } |
|
163 |
|
164 EDEMOD2 = EDEMOD1 |
|
165 |
|
166 EAVG = {0: "No data", |
|
167 1: "Not averaged", |
|
168 2: "Averaged",} |
|
169 |
|
170 |
|
171 |
|
172 EWIN = {0: "N/A", |
|
173 1: "Hann", |
|
174 2: "Flat top", |
|
175 3: "Uniforme", |
|
176 4: "Exponential", |
|
177 5: "Force", |
|
178 6: "Force chan 1/expon chan 2", |
|
179 7: "Expon chan 1/force chan 2", |
|
180 8: "User", |
|
181 } |
|
182 |
|
183 HEADER = [ ("Display function", EDSP, 'h', 2), |
|
184 ('Number of elements', int, 'h', 2), |
|
185 ('Displayed elements', int, 'h', 2), |
|
186 ('Number of averages', int, 'h', 2), |
|
187 ('Channel selection', ECH, 'h', 2), |
|
188 ('Overflow status', EOVR, 'h', 2), |
|
189 ('Overlap percentage', int, 'h', 2), |
|
190 ('Domain', EDOM, 'h', 2), |
|
191 ('Volts peak/rms', EVLT, 'h', 2), |
|
192 ('Amplitude units', EAMP, 'h', 2), |
|
193 ('X axis units', EXAXIS, 'h', 2), |
|
194 ('Auto math label', str, 's', 14), |
|
195 ('Trace label', str, 's', 22), |
|
196 ('EU label 1', str, 's', 6), |
|
197 ('EU label 2', str, 's', 6), |
|
198 ('Float/Interger', bool, 'h', 2), |
|
199 ('Complex/Real', bool, 'h', 2), |
|
200 ('Live/Recalled', bool, 'h', 2), |
|
201 ('Math result', bool, 'h', 2), |
|
202 ('Real/Complex input', bool, 'h', 2), |
|
203 ('Log/Linear data', bool, 'h', 2), |
|
204 ('Auto math', bool, 'h', 2), |
|
205 ('Real time status', bool, 'h', 2), |
|
206 ('Measurement mode', EMEAS, 'h', 2), |
|
207 ('Window', EWIN, 'h', 2), |
|
208 ('Demod type channel 1', EDEMOD1, 'h', 2), |
|
209 ('Demod type channel 2', EDEMOD2, 'h', 2), |
|
210 ('Demod active channel 1', bool, 'h', 2), |
|
211 ('Demod active channel 2', bool, 'h', 2), |
|
212 ('Average status', EAVG, 'h', 2), |
|
213 ('Not used', int, 'hh', 4), |
|
214 ('Samp freq/2 (real)', decode_float, None, 4), |
|
215 ('Samp freq/2 (imag)', decode_float, None, 4), |
|
216 ('Not used', decode_float, None, 4), |
|
217 ('Delta X-axis', decode_float, None, 4), |
|
218 ('Max range', decode_float, None, 4), |
|
219 ('Start time value', decode_float, None, 4), |
|
220 ('Expon wind const 1', decode_float, None, 4), |
|
221 ('Expon wind const 2', decode_float, None, 4), |
|
222 ('EU value chan 1', decode_float, None, 4), |
|
223 ('EU value chan 2', decode_float, None, 4), |
|
224 ('Trig delay chan 1', decode_float, None, 4), |
|
225 ('Trig delay chan 2', decode_float, None, 4), |
|
226 ('Start freq value', decode_float, None, 8), |
|
227 ('Start data value', decode_float, None, 8), |
|
228 ] |
|
229 |
|
230 def decode_trace(data): |
|
231 d = data |
|
232 |
|
233 typ = d[:2] |
|
234 assert typ == "#A" |
|
235 |
|
236 totlen = struct.unpack('>h', d[2:4])[0] |
|
237 idx = 4 |
|
238 tt=0 |
|
239 header = {} |
|
240 for i, (nam, dtype, fmt, nbytes) in enumerate(HEADER): |
|
241 if dtype == str: |
|
242 val = decode_string(d[idx:]) |
|
243 else: |
|
244 if fmt: |
|
245 v = struct.unpack('>'+fmt, d[idx: idx+nbytes])[0] |
|
246 if isinstance(dtype, dict): |
|
247 val = dtype.get(int(v), "N/A") |
|
248 else: |
|
249 val = dtype(v) |
|
250 else: |
|
251 val = dtype(d[idx: idx+nbytes]) |
|
252 header[nam] = val |
|
253 idx += nbytes |
|
254 resu = [] |
|
255 for i in range(header["Number of elements"]): |
|
256 resu.append(decode_float(d[idx: idx+4])) |
|
257 idx += 4 |
|
258 return header, numpy.array(resu, dtype=float) |
|
259 |
|
260 def format_header(header, head_struct, columns=80): |
|
261 todisp = [] |
|
262 for row in head_struct: |
|
263 key = row[0] |
|
264 val = header.get(key, "N/A") |
|
265 if isinstance(val, basestring): |
|
266 val = repr(val) |
|
267 else: |
|
268 val = str(val) |
|
269 todisp.append((key+":", val)) |
|
270 maxk = max([len(k) for k, v in todisp]) |
|
271 maxv = max([len(v) for k, v in todisp]) |
|
272 fmt = "%%-%ds %%-%ds"%(maxk, maxv) |
|
273 w = maxk+maxv+4 |
|
274 ncols = columns/w |
|
275 nrows = len(todisp)/ncols |
|
276 print "w=", w |
|
277 print "ncols=", ncols |
|
278 print "nrows=", nrows |
|
279 res = "" |
|
280 for i in range(nrows): |
|
281 res += "| ".join([fmt%todisp[j*nrows+i] for j in range(ncols)]) + "\n" |
|
282 return res |
|
283 |
|
284 |
|
285 if __name__ == "__main__": |
|
286 import sys |
|
287 import optparse |
|
288 opt = optparse.OptionParser("A simple tool for tracing a dumped trace") |
|
289 opt.add_option('-f', '--filename', default=None, |
|
290 dest='filename', |
|
291 help='Output filename. If not set, read from stdin') |
|
292 opt.add_option('-m', '--mode', default='binary', |
|
293 dest='mode', |
|
294 help='Dumping mode (may be "binary" [default], "ascii" or "ansi")', |
|
295 ) |
|
296 opt.add_option('-d', '--display-header', default=False, |
|
297 action="store_true", |
|
298 dest="displayheader", |
|
299 help="Display the trace header") |
|
300 opt.add_option('-P', '--noplot-trace', default=True, |
|
301 action="store_false", |
|
302 dest="plot", |
|
303 help="Do not display the plot of the trace") |
|
304 opt.add_option('-x', '--xmode', default='lin', |
|
305 dest='xmode', |
|
306 help='X coordinate mode (may be "lin" [default] or "log")') |
|
307 opt.add_option('-y', '--ymode', default='lin', |
|
308 dest='ymode', |
|
309 help='Y coordinate mode (may be "lin" [default], "log" or "db")') |
|
310 |
|
311 options, argv = opt.parse_args(sys.argv) |
|
312 |
|
313 |
|
314 if options.filename is None: |
|
315 print "Can't deal stdin for now..." |
|
316 sys.exit(1) |
|
317 try: |
|
318 header, data = decode_trace(open(options.filename, 'rb').read()) |
|
319 except Exception, e: |
|
320 print "ERROR: can't read %s an interpret it as a HP3562 trace"%options.filename |
|
321 print e |
|
322 sys.exit(1) |
|
323 |
|
324 if options.displayheader: |
|
325 print format_header(header, HEADER, 100) |
|
326 if options.plot: |
|
327 f0 = header['Start freq value'] |
|
328 dx = header['Delta X-axis'] |
|
329 n = header['Number of elements'] |
|
330 x = numpy.linspace(f0, f0+dx*n, len(data)) |
|
331 y = data.copy() |
|
332 |
|
333 import pylab |
|
334 if options.ymode != "lin": |
|
335 minv = min(y[y>0]) |
|
336 y[y==0] = minv |
|
337 y = numpy.log10(y) |
|
338 if options.ymode == "db": |
|
339 y = y*10 |
|
340 pylab.ylabel('db') |
|
341 pylab.grid() |
|
342 pylab.plot(x, y) |
|
343 pylab.xlabel('frequency') |
|
344 pylab.show() |
|
345 |
|
346 |
|