# HG changeset patch # User David Douard # Date 1203961107 -3600 # Node ID 8e32c806fcdd631a368483921134c5b7dc0b8462 # Parent 12dfcb77a5902b4050997504716476693006ccec Major refactoring in progress. Build the toolkit around a GPIB controller which have a communication thread with the devices. Every device is an instance of a class that describes the device model and registers itself to the controller. diff -r 12dfcb77a590 -r 8e32c806fcdd pygpibtoolkit/HP3562A/HP356X.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pygpibtoolkit/HP3562A/HP356X.py Mon Feb 25 18:38:27 2008 +0100 @@ -0,0 +1,257 @@ +# +from pygpibtoolkit.pygpib import CommandRegister, Constants, Command +from pygpibtoolkit.pygpib import BoolValue, IntValue, FloatValue +from pygpibtoolkit.pygpib import PercentageValue, FrequencyValue, DurationValue +from pygpibtoolkit.pygpib import EnumValue, StringValue +from pygpibtoolkit.pygpib import Mode, ModeCommand + +from pygpibtoolkit.gpibcontroller import AbstractGPIBDevice, deviceRegister + +class Register(CommandRegister): + """ + Register class for every HPIB commands for the HP356X devices + """ +# we must instanciate the singleton for registering mechanism to work +Register() + + +##################### +# HP3562A constants and command set +# **VERY INCOMPLETE** + +# GPIB buffer size is 3x80 characters lines +class STATUS_BYTE(Constants): + # HP3562A Status Byte, as returned by a serial poll + _constants = [(0x40, "RQS", "Request Service"), # when sent in response to a serial poll + (0x20, "ERR", "GPIB error"), + (0x10, "RDY", "ready to accept GPIB commands"), + ] + + conditions = [(0, "NSR", "No service requested"), + (1, "USRQ1", "User SRQ #1"), + (2, "USRQ1", "User SRQ #2"), + (3, "USRQ1", "User SRQ #3"), + (4, "USRQ1", "User SRQ #4"), + (5, "USRQ1", "User SRQ #5"), + (6, "USRQ1", "User SRQ #6"), + (7, "USRQ1", "User SRQ #7"), + (8, "USRQ1", "User SRQ #8"), + (9, "EOD", "End of disk action"), + (10, "EOP", "End of plot action"), + (11, "STCH", "Instrument status changed"), # any change in + # the status register sets this condition + (12, "PWR", "Power on"), + (13, "KEY", "Key pressed"), + (14, "DCP", "Device clear plotter (listen)"), + # ... + ] + def __init__(self): + super(STATUS_BYTE, self).__init__() + self._conditions = dict([(x[0], x[1]) for x in self.conditions]) + self._rev_conditions = dict([(x[1], x[0]) for x in self.conditions]) + self._long_conditions = dict([(x[0], x[2]) for x in self.conditions]) + + def byte_condition(self, byte): + byte = byte & 0x8F + return self._conditions.get(byte, "N/A") + +class IS_REGISTER(Constants): + _constants = [(0x01, "MEASP", "measeurement pause"), + (0x02, "ASQP", "Auto sequence pause"), + (0X04, "EOM", "End of measurement, capture or throughput"), + (0x08, "EOAS", "End of auto sequence"), + (0x10, "SWPR", "Sweep point ready"), + (0x20, "CH1OV", "Channel 1 overrange"), + (0x40, "CH2OV", "Channel 2 overrange"), + (0X80, "CH1HR", "Channel 1 half range"), + (0x100, "CH2HR", "Channel 2 half range"), + (0x200, "SFALT", "Source fault"), + (0x400, "RUNL", "Reference unlock"), + (0x800, "RMKT", "Remote marker knob turn"), + (0x1000, "REKT", "Remote entry knob turn"), + (0x2000, "ASRC", "Asctive Status Register changed"), + (0x4000, "PWRF", "Power-on test failed"), + ] + +class StatusQuery(Constants): + _command = "STA?" + _constants = [(0x01, "N/A", "Not used"), + (0x02, "N/A", "Not used"), + (0x04, "KEY", "Key pressed"), + (0x08, "N/A", "Not used"), + (0x10, "RDY", "Ready"), + (0x20, "ERR", "Error"), + (0x40, "RQS", "Request"), + (0x80, "MOS", "Message on screen"), + (0x100, "MEASP", "measeurement pause"), + (0x200, "ASQP", "Auto sequence pause"), + (0X400, "EOM", "End of measurement, capture or throughput"), + (0x800, "EOAS", "End of auto sequence"), + (0x1000, "SWPR", "Sweep point ready"), + (0x2000, "CH1OV", "Channel 1 overrange"), + (0x4000, "CH2OV", "Channel 2 overrange"), + (0x8000, "MAOV", "Math overflow"), + ] +class ActivityStatysRegister(Constants): + _command = "AS?" + _constants = [(0x01, "CKFL", "Check fault log"), + (0x02, "FITR", "Filling time record"), + (0x04, "FLTR", "Filters settings"), + (0x08, "CFTP", "Curve fir in progress"), + (0x10, "MSSM", "Missed sample"), + (0x20, "TMPR", "Timed preview"), + (0x40, "ACDA", "Accept date"), + #... + ] + + +class TraceDisplay(Mode): + "Trace Display" + A = ModeCommand("A trace", "A") + B = ModeCommand("B trace", "B") + +class ARM(Command): + "ARM - Arm" +class NAVG(IntValue): + "Number of averages" + +class AverageMode(Mode): + "Average mode" + _condition = lambda device: device.MeasMode in ['LINEAR RES','LOG RES', 'TIME CAPTUR'] + AVOF = ModeCommand("Average off", "AVG OFF") + STBL = ModeCommand("Stable (means)", "STABLE (MEAN)") + EXP = ModeCommand("Exponential", "EXPON") + PHLD = ModeCommand("Peak hold", "PEAK HOLD") + CNPK = ModeCommand("Cont. peak", "CONT PEAK") + +class TIAV(BoolValue): + "TIM AV ON OFF - Time average" + _condition = lambda device: device.MeasMode in ['LINEAR RES','TIME CAPTUR','SWEPT SINE'] + +class OVLP(PercentageValue): + "OVRLP% - Overlap (%)" + _condition = lambda device: device.MeasMode in ['LINEAR RES','TIME CAPTUR','SWEPT SINE'] + +class AUTO(BoolValue): + "AUTO ON OFF - Auto calibration" + +class SNGC(Command): + "SINGLE CAL - Single calibration" + +class CoordMode(Mode): + "Coord mode" + MGDB = ModeCommand("Mag. (dB)", "MAG (dB)") + MDBM = ModeCommand("Mag. (dBm)", "MAG (dBm)") + MGLG = ModeCommand("Mag. (log)", "MAG (LOG)") + MAG = ModeCommand("Mag. (lin)", "MAG (LIN)") + PHSE = ModeCommand("Phase", "PHASE") + REAL = ModeCommand("Real", "REAL") + IMAG = ModeCommand("Imag.", "IMAG") + NYQT = ModeCommand("Nyquist", "NYQUIST") + NICL = ModeCommand("Nichol", "NICHOL") + +# FREQ menu +class FRS(FrequencyValue): + "FREQ SPAN - Freq. span" +class SF(FrequencyValue): + "START FREQ - Start freq." +class CF(FrequencyValue): + "CENTER FREQ - Center freq." + _condition = lambda device: device.MeasMode in ['LINEAR RES','TIME CAPTUR','SWEPT SINE'] +class ZST(Command): + "ZERO START - Zero start" + _condition = lambda device: device.MeasMode in ['LINEAR RES','TIME CAPTUR','SWEPT SINE'] +class MAXS(FrequencyValue): + "MAX SPAN - Max span" + _condition = lambda device: device.MeasMode in ['LINEAR RES','TIME CAPTUR','SWEPT SINE'] +class TLN(DurationValue): + "TIME LENGTH - Time len." + _condition = lambda device: device.MeasMode in ['LINEAR RES','TIME CAPTUR','SWEPT SINE'] +class ESMP(BoolValue): + "E SMPL ON OFF - E sample" + _condition = lambda device: device.MeasMode in ['LINEAR RES','TIME CAPTUR','SWEPT SINE'] +class SMPF(FrequencyValue): + "SAMPLE FREQ - Sample freq." + _condition = lambda device: device.MeasMode in ['LINEAR RES','TIME CAPTUR','SWEPT SINE'] +class SPF(FrequencyValue): + "STOP FREQ - Stop freq." + _condition = lambda device: device.MeasMode in ['SWEPT SINE'] +class SWRT(FrequencyValue): + "SWEEP RATE - Sweep rate" + +class ABIB(Command): + "ABORT HP-IB - Abort HPIB" + +# INPUT COUPLING +class C1AC(EnumValue): + "CHAN1 AC DC - Channel 1 AC/DC" + _values = ["AC", "DC"] + +class Chan1Coupling(Mode): + "Channel 1 coupling" + FLT1 = ModeCommand("Float", "FLOAT CHAN1") + GND1 = ModeCommand("Ground", "GROUND CHAN1") + +class C2AC(EnumValue): + "CHAN2 AC DC - Channel 2 AC/DC" + _values = ["AC", "DC"] + +class Chan2Coupling(Mode): + "Channel 2 coupling" + FLT2 = ModeCommand("Float", "FLOAT CHAN2") + GND2 = ModeCommand("Ground", "GROUND CHAN2") + +class LCL(Command): + "LOCAL - Local" + +# MEAS MODE +class MeasMode(Mode): + "Measurement mode" + LNRS = ModeCommand("Linear resolution", "LINEAR RES") + LGRS = ModeCommand("Log resolution", "LOG RES") + SSIN = ModeCommand("Swept sinus", "SWEPT SINE") + CPTR = ModeCommand("Time Capture", "TIME CAPTUR") + +# MEAS DISP +class MeasDisp(Mode): + # TODO + pass + +# SELECT MEAS +class SelectMeas(Mode): + "Select measurement" + FRSP = ModeCommand("Frequency response", "FREQ RESP", condition=lambda device: device.MeasMode in ["LINEAR RES", "LOG RES", "SWEPT SINE"]) + PSPC = ModeCommand("Power spectrum", "POWER SPEC", condition=lambda device: device.MeasMode in ["LINEAR RES","LOG RES","TIME CAPTUR"]) + AUCR = ModeCommand("Auto correlation", "AUTO CORR", condition=lambda device: device.MeasMode in ["LINEAR RES","TIME CAPTUR"]) + CCOR = ModeCommand("Cross correlation", "CROSS CORR", condition=lambda device: device.MeasMode == "LINEAR RES") + HIST = ModeCommand("Histogramm", "HIST", condition=lambda device: device.MeasMode in ["LINEAR RES", "TIME CAPTUR"]) + CH12 = ModeCommand("Channel 1&2 active", "CH 1&2 ACTIVE", condition=lambda device: device.MeasMode in ["LINEAR RES", "LOG RES"]) + CH1 = ModeCommand("Channel 1 active", "CH 1 ACTIVE", condition=lambda device: device.MeasMode in ["LINEAR RES", "LOG RES", "TIME CAPTUR"]) + CH2 = ModeCommand("Channel 2 active", "CH 2 ACTIVE", condition=lambda device: device.MeasMode in ["LINEAR RES", "LOG RES", "TIME CAPTUR"]) + + +class HP356XDevice(AbstractGPIBDevice): + _accepts = ["HP3562A", "HP3563A"] + _idn = "ID?" + + def __init__(self, idn, address, controller): + super(HP356XDevice, self).__init__(idn, address, controller) + self._registry = Register() # singleton + self._cache = dict([(k, None) for k, v in self._registry.items() if isinstance(v, Mode)]) + for k in self._registry.keys(): + setattr(self, + + def _get(self, name): + print "get ", name + assert name in self._registry + if name in self._cache: + if self._cache[name] is None: + # where to get the info? + # ... compute value + self._cache[name] = value + return self._cache[name] + self._registry[name].get_value(self._cnx) + def _set(self, name, value): + assert name in self._registry + self._registry[name].set_value(self._cnx, value) +deviceRegister.register_manager(HP356XDevice) diff -r 12dfcb77a590 -r 8e32c806fcdd pygpibtoolkit/HP3562A/__init__.py --- a/pygpibtoolkit/HP3562A/__init__.py Sun Feb 17 22:59:59 2008 +0100 +++ b/pygpibtoolkit/HP3562A/__init__.py Mon Feb 25 18:38:27 2008 +0100 @@ -1,8 +1,9 @@ """ -HP3562A -======= +HP356X +====== -Module for communicating with the HP 3562A Digital Signal Analyzer. +Module for communicating with the HP 3562A and derivated Digital +Signal Analyzers. Subpackages ----------- @@ -12,231 +13,7 @@ --------- """ -import struct -import re -import pygpibtoolkit.pygpib as gpib -import numpy - -######################################## -# HP3562A internal binary types decoders - -def decode_float(s): - 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) - -def decode_string(s): - nb = ord(s[0]) - s = s[1:nb+2] - r = "" - # XXX why do we need to do this? It's not described in the manual... - for c in s: - r += chr(ord(c) & 0x7F) - return r -### -# Some useful functions -def format_header(header, head_struct, columns=80): - """ - Pretty print a data block (trace, state or coord) - """ - bool_re = re.compile(r'((?P.*) )?(?P\w+/\w+)( (?P.*))?') - - todisp = [] - 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) - elif typ is bool and isinstance(val, typ): - m = bool_re.match(key) - if m: - d = m.groupdict() - key = "" - if d['before']: - key += d['before'] - if d['after']: - key += d['after'] - key = key.capitalize() - val = d['flag'].split('/')[not val] - else: - val = str(val) - else: - val = str(val) - todisp.append((key+":", val)) - maxk = max([len(k) for k, v in todisp]) - maxv = max([len(v) for k, v in todisp]) - fmt = "%%-%ds %%-%ds"%(maxk, maxv) - w = maxk+maxv+4 - ncols = columns/w - 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, idx=0): - d = data - 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 is None: - idx += nbytes - continue - elif dtype == str: - val = decode_string(d[idx:]) - else: - if fmt: - v = struct.unpack('>'+fmt, d[idx: idx+nbytes])[0] - if isinstance(dtype, dict): - val = dtype.get(int(v), "N/A") - else: - val = dtype(v) - else: - val = dtype(d[idx: idx+nbytes]) - header[nam] = val - 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 - -# GPIB buffer size is 3x80 characters lines -class STATUS_BYTE(gpib.Constants): - # HP3562A Status Byte, as returned by a serial poll - _constants = [(0x40, "RQS", "Request Service"), # when sent in response to a serial poll - (0x20, "ERR", "GPIB error"), - (0x10, "RDY", "ready to accept GPIB commands"), - ] - - conditions = [(0, "NSR", "No service requested"), - (1, "USRQ1", "User SRQ #1"), - (2, "USRQ1", "User SRQ #2"), - (3, "USRQ1", "User SRQ #3"), - (4, "USRQ1", "User SRQ #4"), - (5, "USRQ1", "User SRQ #5"), - (6, "USRQ1", "User SRQ #6"), - (7, "USRQ1", "User SRQ #7"), - (8, "USRQ1", "User SRQ #8"), - (9, "EOD", "End of disk action"), - (10, "EOP", "End of plot action"), - (11, "STCH", "Instrument status changed"), # any change in - # the status register sets this condition - (12, "PWR", "Power on"), - (13, "KEY", "Key pressed"), - (14, "DCP", "Device clear plotter (listen)"), - # ... - ] - def __init__(self): - super(STATUS_BYTE, self).__init__() - self._conditions = dict([(x[0], x[1]) for x in self.conditions]) - self._rev_conditions = dict([(x[1], x[0]) for x in self.conditions]) - self._long_conditions = dict([(x[0], x[2]) for x in self.conditions]) - - def byte_condition(self, byte): - byte = byte & 0x8F - return self._conditions.get(byte, "N/A") - -class IS_REGISTER(gpib.Constants): - _constants = [(0x01, "MEASP", "measeurement pause"), - (0x02, "ASQP", "Auto sequence pause"), - (0X04, "EOM", "End of measurement, capture or throughput"), - (0x08, "EOAS", "End of auto sequence"), - (0x10, "SWPR", "Sweep point ready"), - (0x20, "CH1OV", "Channel 1 overrange"), - (0x40, "CH2OV", "Channel 2 overrange"), - (0X80, "CH1HR", "Channel 1 half range"), - (0x100, "CH2HR", "Channel 2 half range"), - (0x200, "SFALT", "Source falt"), - (0x400, "RUNL", "Reference unlock"), - (0x800, "RMKT", "Remote marker knob turn"), - (0x1000, "REKT", "Remote entry knob turn"), - (0x2000, "ASRC", "Asctive Status Register changed"), - (0x4000, "PWRF", "Power-on test failed"), - ] - -class StatusQuery(gpib.Constants): - _command = "STA?" - _constants = [(0x01, "N/A", "Not used"), - (0x02, "N/A", "Not used"), - (0x04, "KEY", "Key pressed"), - (0x08, "N/A", "Not used"), - (0x10, "RDY", "Ready"), - (0x20, "ERR", "Error"), - (0x40, "RQS", "Request"), - (0x80, "MOS", "Message on screen"), - (0x100, "MEASP", "measeurement pause"), - (0x200, "ASQP", "Auto sequence pause"), - (0X400, "EOM", "End of measurement, capture or throughput"), - (0x800, "EOAS", "End of auto sequence"), - (0x1000, "SWPR", "Sweep point ready"), - (0x2000, "CH1OV", "Channel 1 overrange"), - (0x4000, "CH2OV", "Channel 2 overrange"), - (0x8000, "MAOV", "Math overflow"), - ] -class ActivityStatysRegister(gpib.Constants): - _command = "AS?" - _constants = [(0x01, "CKFL", "Check fault log"), - (0x02, "FITR", "Filling time record"), - (0x04, "FLTR", "Filters settings"), - (0x08, "CFTP", "Curve fir in progress"), - (0x10, "MSSM", "Missed sample"), - (0x20, "TMPR", "Timed preview"), - (0x40, "ACDA", "Accept date"), - #... - ] diff -r 12dfcb77a590 -r 8e32c806fcdd pygpibtoolkit/HP3562A/coord_decoder.py --- a/pygpibtoolkit/HP3562A/coord_decoder.py Sun Feb 17 22:59:59 2008 +0100 +++ b/pygpibtoolkit/HP3562A/coord_decoder.py Mon Feb 25 18:38:27 2008 +0100 @@ -9,7 +9,8 @@ This file can be exectued as a python script. Use '-h' for more informations. """ -from pygpibtoolkit.HP3562A import format_header, decode_float, decode_string, decode_header, read_trace +from pygpibtoolkit.gpi_utils import format_datablock_header, decode_datablock_header, read_datablock_trace +from pygpibtoolkit.gpi_utils import decode_float, decode_string from pygpibtoolkit.HP3562A.trace_decoder import decode_trace, HEADER as TRACE_HEADER from pygpibtoolkit.HP3562A.enum_types import * @@ -50,9 +51,9 @@ 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"]) + header, idx = decode_datablock_header(data, HEADER) + trace_header, idx = decode_datablock_header(data, TRACE_HEADER, idx) + trace = read_datablock_trace(data, idx, trace_header["Number of elements"]) return header, trace_header, trace diff -r 12dfcb77a590 -r 8e32c806fcdd pygpibtoolkit/HP3562A/q3562A.py --- a/pygpibtoolkit/HP3562A/q3562A.py Sun Feb 17 22:59:59 2008 +0100 +++ b/pygpibtoolkit/HP3562A/q3562A.py Mon Feb 25 18:38:27 2008 +0100 @@ -357,7 +357,9 @@ self.getCoordAct.setChecked(False) self.statusBar().showMessage(self.tr("Received data block"), 2000) self._receiving = False - + # give control back to fron panel + self.captureThread.sendCommand('LCL') + def selectChannel(self): if self.channelAAct.isEnabled(): self.captureThread.sendCommand("A") diff -r 12dfcb77a590 -r 8e32c806fcdd pygpibtoolkit/HP3562A/q3562A.ui --- a/pygpibtoolkit/HP3562A/q3562A.ui Sun Feb 17 22:59:59 2008 +0100 +++ b/pygpibtoolkit/HP3562A/q3562A.ui Mon Feb 25 18:38:27 2008 +0100 @@ -5,20 +5,167 @@ 0 0 - 507 + 707 492 MainWindow - + + + + + + Qt::Horizontal + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + QTabWidget::West + + + 0 + + + + Controls + + + + + + Retrieve data blocks + + + + + + + + Channel + + + + + + + + + A + + + + + + + B + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Retrieve + + + + + + + + + State + + + + + + + Trace + + + + + + + Coord + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Data blocks + + + + + + + 0 0 - 507 + 707 25 @@ -42,109 +189,17 @@ - - - 1 + + + toolBar + + + TopToolBarArea - - - - 4 - - - 1 - - - 4 - - - 1 - - - 1 - - - - - - - - 0 - 29 - - - - get trace - - - - - - - 16 - 16 - - - - true - - - Qt::ToolButtonTextBesideIcon - - - - - - - - 0 - 29 - - - - get status - - - true - - - - - - - - 0 - 29 - - - - get coord - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - + + false + + @@ -171,9 +226,34 @@ Preferences + + + :/icons/state.svg + + + Get state + + + + + :/icons/trace.svg + + + Get trace + + + + + :/icons/coord.svg + + + get_coord + + + diff -r 12dfcb77a590 -r 8e32c806fcdd pygpibtoolkit/HP3562A/state_decoder.py --- a/pygpibtoolkit/HP3562A/state_decoder.py Sun Feb 17 22:59:59 2008 +0100 +++ b/pygpibtoolkit/HP3562A/state_decoder.py Mon Feb 25 18:38:27 2008 +0100 @@ -8,7 +8,8 @@ This file can be exectued as a python script. Use '-h' for more informations. """ -from pygpibtoolkit.HP3562A import format_header, decode_float, decode_string, decode_header +from pygpibtoolkit.gpi_utils import format_datablock_header, decode_datablock_header +from pygpibtoolkit.gpi_utils import decode_float, decode_string from pygpibtoolkit.HP3562A.enum_types import * @@ -113,7 +114,7 @@ header is the dictionnary of the header of the dumped data block, """ - header, idx = decode_header(data, HEADER) + header, idx = decode_datablock_header(data, HEADER) return header @@ -137,14 +138,14 @@ sys.exit(1) #try: if 1: - header = decode_state(open(options.filename, 'rb').read()) + header = decode_datablock_state(open(options.filename, 'rb').read()) else: #except Exception, e: print "ERROR: can't read %s an interpret it as a HP3562 trace"%options.filename print e sys.exit(1) - print format_header(header, HEADER, 100) + print format_datablock_header(header, HEADER, 100) if __name__ == "__main__": main() diff -r 12dfcb77a590 -r 8e32c806fcdd pygpibtoolkit/HP3562A/trace_decoder.py --- a/pygpibtoolkit/HP3562A/trace_decoder.py Sun Feb 17 22:59:59 2008 +0100 +++ b/pygpibtoolkit/HP3562A/trace_decoder.py Mon Feb 25 18:38:27 2008 +0100 @@ -10,7 +10,9 @@ """ import struct import numpy -from pygpibtoolkit.HP3562A import format_header, decode_float, decode_string, decode_header, read_trace +from pygpibtoolkit.gpi_utils import format_datablock_header, decode_datablock_header, read_datablock_trace +from pygpibtoolkit.gpi_utils import decode_float, decode_string + from pygpibtoolkit.HP3562A.enum_types import * @@ -70,8 +72,8 @@ value is a numpy array holding the trace (vector of float or complex values). """ - header, idx = decode_header(data, HEADER, idx) - return header, read_trace(data, idx, header["Number of elements"]) + header, idx = decode_datablock_header(data, HEADER, idx) + return header, read_datablock_trace(data, idx, header["Number of elements"]) def main(): import sys @@ -113,7 +115,7 @@ sys.exit(1) if options.displayheader: - print format_header(header, HEADER, 100) + print format_datablock_header(header, HEADER, 100) f0 = header['Start freq value'] dx = header['Delta X-axis'] n = header['Number of elements'] diff -r 12dfcb77a590 -r 8e32c806fcdd pygpibtoolkit/gpib_utils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pygpibtoolkit/gpib_utils.py Mon Feb 25 18:38:27 2008 +0100 @@ -0,0 +1,128 @@ +# +""" +Several untity functions for GPIB data conversions +""" +import struct +import re +import numpy + +######################################## +# internal binary types decoders + +def decode_float(s): + """ + Decode a Float from the HP binary representation. + """ + 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_string(s): + """ + Decode a string from the HP binay representation. + """ + nb = ord(s[0]) + s = s[1:nb+2] + r = "" + # XXX why do we need to do this? It's not described in the manual... + for c in s: + r += chr(ord(c) & 0x7F) + return r + +### +# Datavlock useful functions +def format_datablock_header(header, head_struct, columns=80): + """ + Pretty print a data block (trace, state or coord) + """ + bool_re = re.compile(r'((?P.*) )?(?P\w+/\w+)( (?P.*))?') + + todisp = [] + 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) + elif typ is bool and isinstance(val, typ): + m = bool_re.match(key) + if m: + d = m.groupdict() + key = "" + if d['before']: + key += d['before'] + if d['after']: + key += d['after'] + key = key.capitalize() + val = d['flag'].split('/')[not val] + else: + val = str(val) + else: + val = str(val) + todisp.append((key+":", val)) + maxk = max([len(k) for k, v in todisp]) + maxv = max([len(v) for k, v in todisp]) + fmt = "%%-%ds %%-%ds"%(maxk, maxv) + w = maxk+maxv+4 + ncols = columns/w + 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_datablock_header(data, header_struct, idx=0): + d = data + 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 is None: + idx += nbytes + continue + elif dtype == str: + val = decode_string(d[idx:]) + else: + if fmt: + v = struct.unpack('>'+fmt, d[idx: idx+nbytes])[0] + if isinstance(dtype, dict): + val = dtype.get(int(v), "N/A") + else: + val = dtype(v) + else: + val = dtype(d[idx: idx+nbytes]) + header[nam] = val + idx += nbytes + return header, idx + +def read_datablock_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) diff -r 12dfcb77a590 -r 8e32c806fcdd pygpibtoolkit/gpibcontroller.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pygpibtoolkit/gpibcontroller.py Mon Feb 25 18:38:27 2008 +0100 @@ -0,0 +1,233 @@ +# +""" +A general purpose GPIB controller based on prologix GPIB device +""" +import sys +import threading +import time +from prologix import GPIB, GPIB_CONTROLLER, GPIB_DEVICE + +class AbstractGPIBDevice(object): + _accepts = [] + @classmethod + def accepts(cls, idn): + return idn in cls._accepts + + def __init__(self, idn, address, controller): + self._idn = idn + self._address = address + self._controller = controller + + def manage_srq(self): + pass + + def send_command(self, cmd): + return self._controller.send_command(self._address, cmd).strip() + +class GPIBDeviceRegister(object): + def __init__(self): + self._registry = [] + def register_manager(self, mgr): + self._registry.append(mgr) + def get_manager(self, idn): + for mgr in self._registry: + if mgr.accepts(idn): + return mgr + return None + def get_idn_cmds(self): + return [mgr._idn for mgr in self._registry] + def __str__(self): + msg = "