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.

Mon, 25 Feb 2008 18:38:27 +0100

author
David Douard <david.douard@logilab.fr>
date
Mon, 25 Feb 2008 18:38:27 +0100
changeset 53
8e32c806fcdd
parent 52
12dfcb77a590
child 54
5c1a312830e7

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.

pygpibtoolkit/HP3562A/HP356X.py file | annotate | diff | comparison | revisions
pygpibtoolkit/HP3562A/__init__.py file | annotate | diff | comparison | revisions
pygpibtoolkit/HP3562A/coord_decoder.py file | annotate | diff | comparison | revisions
pygpibtoolkit/HP3562A/q3562A.py file | annotate | diff | comparison | revisions
pygpibtoolkit/HP3562A/q3562A.ui file | annotate | diff | comparison | revisions
pygpibtoolkit/HP3562A/state_decoder.py file | annotate | diff | comparison | revisions
pygpibtoolkit/HP3562A/trace_decoder.py file | annotate | diff | comparison | revisions
pygpibtoolkit/gpib_utils.py file | annotate | diff | comparison | revisions
pygpibtoolkit/gpibcontroller.py file | annotate | diff | comparison | revisions
pygpibtoolkit/prologix.py file | annotate | diff | comparison | revisions
pygpibtoolkit/pygpib.py file | annotate | diff | comparison | revisions
--- /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

mercurial