Mon, 25 Feb 2008 18:38:27 +0100
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.
--- /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)
--- 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<before>.*) )?(?P<flag>\w+/\w+)( (?P<after>.*))?') - - 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"), - #... - ]
--- 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
--- 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")
--- 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 @@ <rect> <x>0</x> <y>0</y> - <width>507</width> + <width>707</width> <height>492</height> </rect> </property> <property name="windowTitle" > <string>MainWindow</string> </property> - <widget class="QWidget" name="centralwidget" /> + <widget class="QWidget" name="centralwidget" > + <layout class="QVBoxLayout" > + <item> + <widget class="QSplitter" name="splitter" > + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <widget class="QFrame" name="frame" > + <property name="frameShape" > + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow" > + <enum>QFrame::Raised</enum> + </property> + </widget> + <widget class="QTabWidget" name="tabWidget" > + <property name="tabPosition" > + <enum>QTabWidget::West</enum> + </property> + <property name="currentIndex" > + <number>0</number> + </property> + <widget class="QWidget" name="tab" > + <attribute name="title" > + <string>Controls</string> + </attribute> + <layout class="QVBoxLayout" > + <item> + <widget class="QGroupBox" name="groupBox" > + <property name="title" > + <string>Retrieve data blocks</string> + </property> + <layout class="QVBoxLayout" > + <item> + <layout class="QGridLayout" > + <item row="0" column="0" > + <widget class="QLabel" name="label" > + <property name="text" > + <string>Channel</string> + </property> + </widget> + </item> + <item row="0" column="1" > + <layout class="QHBoxLayout" > + <item> + <widget class="QToolButton" name="toolButton" > + <property name="text" > + <string>A</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="toolButton_2" > + <property name="text" > + <string>B</string> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" > + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="1" column="0" > + <widget class="QLabel" name="label_2" > + <property name="text" > + <string>Retrieve</string> + </property> + </widget> + </item> + <item row="1" column="1" > + <layout class="QHBoxLayout" > + <item> + <widget class="QToolButton" name="toolButton_3" > + <property name="text" > + <string>State</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="toolButton_4" > + <property name="text" > + <string>Trace</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="toolButton_5" > + <property name="text" > + <string>Coord</string> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" > + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_2" > + <attribute name="title" > + <string>Data blocks</string> + </attribute> + </widget> + </widget> + </widget> + </item> + </layout> + </widget> <widget class="QMenuBar" name="menubar" > <property name="geometry" > <rect> <x>0</x> <y>0</y> - <width>507</width> + <width>707</width> <height>25</height> </rect> </property> @@ -42,109 +189,17 @@ <addaction name="menuEdit" /> </widget> <widget class="QStatusBar" name="statusbar" /> - <widget class="QDockWidget" name="plotsDock" > - <attribute name="dockWidgetArea" > - <number>1</number> + <widget class="QToolBar" name="toolBar" > + <property name="windowTitle" > + <string>toolBar</string> + </property> + <attribute name="toolBarArea" > + <enum>TopToolBarArea</enum> </attribute> - <widget class="QWidget" name="dockWidgetContents" > - <layout class="QVBoxLayout" > - <property name="spacing" > - <number>4</number> - </property> - <property name="leftMargin" > - <number>1</number> - </property> - <property name="topMargin" > - <number>4</number> - </property> - <property name="rightMargin" > - <number>1</number> - </property> - <property name="bottomMargin" > - <number>1</number> - </property> - <item> - <layout class="QHBoxLayout" > - <item> - <widget class="QToolButton" name="getTraceButton" > - <property name="minimumSize" > - <size> - <width>0</width> - <height>29</height> - </size> - </property> - <property name="text" > - <string>get trace</string> - </property> - <property name="icon" > - <iconset/> - </property> - <property name="iconSize" > - <size> - <width>16</width> - <height>16</height> - </size> - </property> - <property name="checkable" > - <bool>true</bool> - </property> - <property name="toolButtonStyle" > - <enum>Qt::ToolButtonTextBesideIcon</enum> - </property> - </widget> - </item> - <item> - <widget class="QToolButton" name="getStatusButton" > - <property name="minimumSize" > - <size> - <width>0</width> - <height>29</height> - </size> - </property> - <property name="text" > - <string>get status</string> - </property> - <property name="checkable" > - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QToolButton" name="getCoordButton" > - <property name="minimumSize" > - <size> - <width>0</width> - <height>29</height> - </size> - </property> - <property name="text" > - <string>get coord</string> - </property> - <property name="checkable" > - <bool>true</bool> - </property> - </widget> - </item> - <item> - <spacer> - <property name="orientation" > - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" > - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - <item> - <widget class="QListView" name="objectsView" /> - </item> - </layout> - </widget> + <attribute name="toolBarBreak" > + <bool>false</bool> + </attribute> + <addaction name="actionGet_state" /> </widget> <action name="actionOpen" > <property name="text" > @@ -171,9 +226,34 @@ <string>Preferences</string> </property> </action> + <action name="actionGet_state" > + <property name="icon" > + <iconset resource="../qt4/icons_ressource.qrc" >:/icons/state.svg</iconset> + </property> + <property name="text" > + <string>Get state</string> + </property> + </action> + <action name="actionGet_trace" > + <property name="icon" > + <iconset resource="../qt4/icons_ressource.qrc" >:/icons/trace.svg</iconset> + </property> + <property name="text" > + <string>Get trace</string> + </property> + </action> + <action name="actionGet_coord" > + <property name="icon" > + <iconset resource="../qt4/icons_ressource.qrc" >:/icons/coord.svg</iconset> + </property> + <property name="text" > + <string>get_coord</string> + </property> + </action> </widget> <resources> <include location="../qt4/resources.qrc" /> + <include location="../qt4/icons_ressource.qrc" /> </resources> <connections/> </ui>
--- 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()
--- 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']
--- /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<before>.*) )?(?P<flag>\w+/\w+)( (?P<after>.*))?') + + 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)
--- /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 = "<GPIBDeviceRegister: %s managers\n"%len(self._registry) + for mgr in self._registry: + msg += " %s: %s\n"%(mgr.__name__, ", ".join(mgr._accepts)) + msg += ">" + return msg + +deviceRegister = GPIBDeviceRegister() + +class GenericGPIBDevice(AbstractGPIBDevice): + _idn = "*IDN?" + +deviceRegister.register_manager(GenericGPIBDevice) + +class GPIBController(object): + """ + The main GPIB Controller In Charge. + + This is responsible for any communication with the registered + devices. + + It hold a thread reponsible for communication with devices, + performing spolls regularly. It can react and call callbacks on + events detected while spolling. + + It also keeps a command queue which devices/user program can fill + with GPIB and device commands, which are executed ASAP (ie. as + soon as the GPIB bus if free and the device states itself as + ready.) + + """ + def __init__(self, device="/dev/ttyUSB0", controller_address=21): + self._address = controller_address + self._cnx = GPIB(device) + self._devices = {} + self._setup_threading_system() + + def __del__(self): + print "deleting controller" + self._stop.set() + + def _setup_threading_system(self): + self._n_cmds = 0 + self._n_cmds_lock = threading.RLock() + self._cmd_queue = {} + self._cmd_condition = threading.Condition() + self._results_queue = {} + self._results_condition = threading.Condition() + self._loop_interrupter = threading.Event() + self._stop = threading.Event() + self._cnx_thread = threading.Thread(name="GPIBConnectionThread", + target=self._cnx_loop) + self._cnx_thread.start() + self._loop_interrupter.set() + + def _check_srq(self): + if self._cnx.check_srq(): + addrs = sorted(self._devices.keys()) + polled = self._cnx.poll(addrs) + for add in addrs: + if polled[add] & 0x40: + print "device %s (#%s) requested attention"%(self._devices[add]._idn, add) + # TODO: check what to do... + self._devices[add].manage_sqr() + + def _cnx_loop(self): + while 1: + #sys.stderr.write('.') + if self._stop.isSet(): + print "_stop set, exiting" + return + while not self._loop_interrupter.isSet(): + #sys.stderr.write('|') + self._loop_interrupter.wait() + self._loop_interrupter.clear() + # first we check for SRQ and dispatch its management + self._check_srq() + # then we check if we have any pending commands. + self._run_one_cmd() + self._loop_interrupter.set() + time.sleep(0.1) + + def _run_one_cmd(self): + cond = self._cmd_condition + cond.acquire() + if len(self._cmd_queue) == 0: + cond.release() + return + n_cmd = min(self._cmd_queue.keys()) + addr, cmd, cb = self._cmd_queue.pop(n_cmd) + cond.release() + + # TODO: check device is RDY etc. + resu = self._cnx.send_command(cmd, addr) + # TODO: check any error etc. + if isinstance(cb, threading._Event): + cond = self._results_condition + cond.acquire() + self._results_queue[n_cmd] = resu + cond.notifyAll() + cond.release() + cb.set() + else: + if callable(cb): + cb(resu) + + def stop(self, *args): + self._stop.set() + + def send_command(self, addr, cmd, sync=True, cb=None): + if cb is not None and callable(cb): + sync = False + self._n_cmds_lock.acquire() + self._n_cmds += 1 + n_cmd = self._n_cmds + self._n_cmds_lock.release() + + cond = self._cmd_condition + cond.acquire() + if sync: + cb = threading.Event() + self._cmd_queue[n_cmd] = (addr, cmd, cb) + cond.notify() + cond.release() + if sync: + cb.wait() + cond = self._results_condition + cond.acquire() + while n_cmd not in self._results_queue: + cond.wait() + resu = self._results_queue.pop(n_cmd) + cond.release() + return resu + + def detect_devices(self): + """ + Perform a Serial Poll on all addresses to detect alive devices + connected on the GPIB bus. + """ + while not self._loop_interrupter.isSet(): + self._loop_interrupter.wait() + self._loop_interrupter.clear() + try: + spoll = self._cnx.poll() + for address in spoll: + if address not in self._devices: + # found a new device + for id_str in deviceRegister.get_idn_cmds(): + idn = self._cnx.send_command(id_str, address).strip() + if idn: + self.register_device(address, idn) + break + else: + # redo a spoll, it should have set the err bit + poll = self._cnx.poll(address) + if not poll & 0x20: # 0x20 == ERR bit + print "Strange, device %d did not answer to a %s command without setting its ERR bit"%(address, id_str) + else: + print "Can't retrieve IDN of device at address ", address + finally: + self._loop_interrupter.set() + + def register_device(self, address, idn): + """ + Register a device manager for device at given GPIB + address. The manager is retrived from device registry. + """ + devicemgr = deviceRegister.get_manager(idn) + if devicemgr is not None: + devicemgr = devicemgr(idn, address, self) + self._devices[address] = devicemgr + return devicemgr + else: + print "can't find a manager for", repr(idn) + print deviceRegister._registry + + def idn(self, address): + """ + Query identity of the addressed device. + """ + return self.send_command(address, self._devices[address].__class__._idn).strip() + + def status(self, address): + """ + Query status byte for device at given address + """ + while not self._loop_interrupter.isSet(): + self._loop_interrupter.wait() + self._loop_interrupter.clear() + try: + return self._cnx.poll(address) + finally: + self._loop_interrupter.set() + +
--- a/pygpibtoolkit/prologix.py Sun Feb 17 22:59:59 2008 +0100 +++ b/pygpibtoolkit/prologix.py Mon Feb 25 18:38:27 2008 +0100 @@ -27,8 +27,9 @@ self.set_mode(mode) self.set_address(address) + self._set_cmd('auto', 0) - def set_address(self, address): + def set_address(self, address, check=True): """ Set the address of the GPIB device: @@ -37,8 +38,9 @@ - if the device is in GPIB_DEVICE mode, this is its address. """ - self._set_cmd('addr', address) - self._adress = address + self._set_cmd('addr', address, check) + self._address = address + #self._set_cmd('auto', 0) def set_mode(self, mode): """ @@ -65,17 +67,34 @@ """ self.set_mode(0) - def send_command(self, cmd): + def send_command(self, cmd, address=None): """ Send the specified GPIB command on the bus (must be the CIC), and read the answer. + Eventually, set the addressed device first. """ assert self._mode == 1 - self._cnx.write(cmd+'\r') + if address is not None: + self.set_address(address) + self._cnx.write(cmd+';\r') time.sleep(self._timeout) # required? - ret = self._cnx.readlines() + return self.read_eoi() + + def read_eoi(self, address=None): + """ + Read the HPIB buffer from device, till EOI is performed, or timeout. + """ + if address is not None: + self.set_address(address) + self._cnx.write('++read eoi\r') # idem + ret = "" + i = 0 + while not ret.endswith('\r\n') and i<2: + ret += ''.join(self._cnx.readlines()) + time.sleep(self._timeout) # required? + i += 1 return ''.join(ret) - + def check_srq(self): """ Check the SRQ line @@ -87,18 +106,31 @@ return bool(int(ret)) return None - def poll(self): + def poll(self, addresses=None): """ Poll every address, and return a dictionnary {add: status, ...} - """ - assert self._mode == 1, "must be the Controller In Charge" + """ + assert self._mode == 1, "must be the Controller In Charge" + only_one = False + if addresses is None: + addresses = range(31) + if not isinstance(addresses, (list, tuple)): + addresses = [addresses] + only_one = True + if len(addresses)==0: + return None + dico = {} - for add in range(31): - self._cnx.write('++spoll %d\r'%i) + for add in addresses: + self._cnx.write('++spoll %d\r'%add) ret = self._cnx.readline().strip() if ret: - dico[i] = int(ret) + dico[add] = int(ret) + time.sleep(0.3) # need to wait at least 150ms (not enough on prologix) + self.set_address(self._address) + if only_one: + return dico.values()[0] return dico def _read(self): @@ -109,9 +141,10 @@ time.sleep(self._timeout) return rdata - def _set_cmd(self, cmd, value): + def _set_cmd(self, cmd, value, check=True): self._cnx.write('++%s %d\r'%(cmd, value)) - self._cnx.write('++%s\r'%(cmd)) - rval = self._read().strip() - if not rval.isdigit() or int(rval) != value: - raise ConnectionError("Can't set GPIB %s to %s [ret=%s]"%(cmd, value, repr(rval))) + if check: + self._cnx.write('++%s\r'%(cmd)) + rval = self._read().strip() + if not rval.isdigit() or int(rval) != value: + raise ConnectionError("Can't set GPIB %s to %s [ret=%s]"%(cmd, value, repr(rval)))
--- a/pygpibtoolkit/pygpib.py Sun Feb 17 22:59:59 2008 +0100 +++ b/pygpibtoolkit/pygpib.py Mon Feb 25 18:38:27 2008 +0100 @@ -5,6 +5,7 @@ import serial from serial.serialutil import SerialException import time +from pygpibtoolkit.tools import AbstractRegister class ConnectionError(Exception): pass @@ -30,12 +31,131 @@ if isinstance(k, basestring): k = self.rev_constants[k] return self.descriptions[k] - class MODE(Constants): _constants = [(1, "CONTROLLER", "Set device as Controller in Charge"), (0, "DEVICE", "Set device as simple listener"), ] + +class ModeCommand(object): + def __init__(self, description, name, condition=None): + self.name = name + self.description = description + self.condition = condition + +class CommandRegister(object): + _instances = {} + _registered_type = None #Command + def __new__(cls): + # implements a singleton *per class* + if cls.__name__ not in cls._instances: + if cls._registered_type is None: + cls._registered_type = Command + instance = super(CommandRegister, cls).__new__(cls) + instance.registry = {} + cls._instances[cls.__name__] = instance + return cls._instances[cls.__name__] + + @classmethod + def add(cls, registered_cls): + for registry in cls._instances.values(): + if registry.__module__ == registered_cls.__module__: + break + else: + return + + assert issubclass(registered_cls, registry._registered_type) + if registered_cls is registry._registered_type: + return + + name = registered_cls.__name__ + if name not in registry: + registry.registry[name] = registered_cls + + def __contains__(self, key): + return key in self.registry + def keys(self): + return self.registry.keys() + def items(self): + return self.registry.items() + def values(self): + return self.registry.values() + def __getitem__(self, key): + return self.registry[key] + +class MetaCommand(type): + """ + Metaclass for HPIB command. Used to register all commands. + """ + _commands = {} + def __init__(cls, name, bases, dct): + # called at class creation + super(MetaCommand, cls).__init__(name, bases, dct) + if name not in [ "AbstractCommand","Command"]: + CommandRegister().add(cls) + +class AbstractCommand(object): + """ Base class for HPIB command descritption""" + __metaclass__ = MetaCommand + + +class Command(AbstractCommand): + @classmethod + def get_value(self, cnx): + res = cnx.send_command(self.__class__.__name__) + return self.convert_from(res) + + @classmethod + def set_value(self, cnx, value): + raise ValueError, "Can't set value for command '%s'"%self.__class__.__name__ + + @classmethod + def convert_from(self, value): + return value + +class AbstractValue(Command): + @classmethod + def set_value(self, cnx, value): + res = cnx.send_command("%s %s"%(self.__class__.__name__, + self.convert_to(value))) + return res + @classmethod + def convert_to(self, value): + return str(value) + +class BoolValue(AbstractValue): + _type = bool + @classmethod + def convert_from(self, value): + return value and value.lower() in ['1','true','yes','on'] + @classmethod + def convert_to(self, value): + return value and "1" or "0" + +class IntValue(AbstractValue): + _type = int + +class FloatValue(AbstractValue): + _type = float + +class PercentageValue(FloatValue): + pass #TODO + +class FrequencyValue(FloatValue): + pass + +class DurationValue(FloatValue): + pass + +class StringValue(AbstractValue): + _type = str + +class EnumValue(AbstractValue): + pass + +class Mode(AbstractValue): + pass + # TODO # class STATUS_BYTE(Constants): # # IEEE 488.2 Status Byte constants