Commit f40e6f6eec0 by Julien Salort

merge

......@@ -7,6 +7,14 @@ from docutils.statemachine import ViewList
from sphinx.domains.python import PythonDomain
def mangle_signature(app, what, name, obj, options, signature, return_annotation):
if what == 'attribute' and isinstance(obj, Feature):
if obj.__doc__ is not None:
return ("()", obj.__doc__)
def mangle_docstrings(app, what, name, obj, options, lines,
reference_offset=[0]):
......@@ -47,4 +55,5 @@ class DocAttributesDomain(PythonDomain):
def setup(app):
app.connect('autodoc-process-docstring', mangle_docstrings)
app.connect('autodoc-process-signature', mangle_signature)
app.add_domain(DocAttributesDomain)
......@@ -10,6 +10,8 @@ Provides:
.. autofunction:: write_analog
.. autofunction:: write_analog_end_task
.. autofunction:: measure_freq
......@@ -379,7 +381,7 @@ def write_analog(
Specifies whether to wait until the task is done before
returning. If blocking=false, then a task object is
returned. To stop the task, ???.
returned. To stop the task, use the :func:`write_analog_end_task` function.
"""
# prepare resource_names
......@@ -528,7 +530,7 @@ def write_analog(
def write_analog_end_task(task, timeout=0.0):
"""End task.
"""This function ends a writing task that has been created with blocking=False.
Parameters
----------
......@@ -549,7 +551,7 @@ def write_analog_end_task(task, timeout=0.0):
def measure_freq(resource_name, freq_min=1, freq_max=1000):
"""Write analogic output
"""Read analogic output
Parameters
----------
......
......@@ -58,7 +58,7 @@ class StanfordSR830SenseValue(FloatValue):
return self.sense_values[int(value)]
def set(self, value):
super().set(sense_values.index(value))
super().set(self.sense_values.index(value))
class StanfordSR830TCValue(FloatValue):
......
......@@ -65,6 +65,10 @@ class Feature:
self._name = name
self.__doc__ = doc
def __repr__(self):
C = type(self)
return "<" + C.__name__ + " object>"
class WriteCommand(Feature):
def __init__(self, name, doc="", command_str=""):
......@@ -192,7 +196,7 @@ class Value(SuperValue):
if self.channel_argument:
# here we don't call _convert_as_str to allow the user to choose
# the desired format in the command_set string
command = self.command_set.format(channel=channel, value=value)
command = self.command_set.format(channel=channel, value=self._convert_as_str(value))
else:
command = self.command_set + " " + self._convert_as_str(value)
self._interface.write(command)
......
......@@ -17,10 +17,19 @@ from fluidlab.instruments.features import SuperValue
class Agilent34970aValue(SuperValue):
"""Custom Value for Agilent 34970a
"""
def __init__(self, name, doc="", function_name=None):
super().__init__(name, doc)
self.function_name = function_name
def __repr__(self):
"""For documentation purpose, it is not useful to read <Agilent34970aValue object>.
It is more informative to know that this is just in the same manner as any Value object.
"""
return "<Value object>"
def _build_driver_class(self, Driver):
name = self._name
function_name = self.function_name
......@@ -54,7 +63,6 @@ class Agilent34970aValue(SuperValue):
class Agilent34970a(IEC60488):
"""Driver for the multiplexer Agilent 34970A.
"""
def __init__(self, interface=None):
......@@ -73,18 +81,51 @@ class Agilent34970a(IEC60488):
self.tmo = None
def set_tmo(self, tmo):
"""Sets the timeout for scan operations. If reading takes longer, then an exception
is raised.
:param tmo: timeout in milliseconds
:type tmo: float
.. note::
this only takes effects when the :meth:`scan` method is invoked.
"""
self.tmo = tmo
def set_range(self, channelNumber, manualRange=False, rangeValue=None):
"""Select auto/manual range for the specified channel
:param channelNumber: channel to set range for
:type channelNumber: int
:param manualRange: True if manual, False if autorange
:type manualRange: bool
:param rangeValue: possible values for voltage: 100e-3, 1, 10, 100, 300. possible values for resistance: 100 (1 mA), 1e3 (1 mA), 10e3 (100 µA), 100e3 (10 µA), 1e6 (5 µA), 10e6 (500 nA), 100e6 (500 nA || 10 MΩ). See Agilent Chapter 9 documentation for other cases.
:type rangeValue: float
.. note::
this only takes effects when the :meth:`scan` method is invoked.
"""
if not manualRange and str(channelNumber) in self.Range:
del self.Range[str(channelNumber)]
elif manualRange:
self.Range[str(channelNumber)] = rangeValue
def set_nplc(self, channelNumber, nplcValue):
"""
Sets the averaging for each channel expressed in line cycles.
Possible values are: 0.02, 0.2, 1.0, 2.0, 10, 20, 100, 200
"""Sets the averaging for the specified channel
:param channelNumber: channel to set averaging for
:type channelNumber: int
:param nplcValue: averaging time expressed in power line cycles (e.g. 20 ms in Europe). Possible values are: 0.02, 0.2, 1.0, 2.0, 10, 20, 100, 200.
:type nplcValue: float
.. note::
this only takes effects when the :meth:`scan` method is invoked.
"""
possible_values = {0.02, 0.2, 1.0, 2.0, 10.0, 20.0, 100.0, 200.0}
nplcValue = float(nplcValue)
......@@ -94,6 +135,18 @@ class Agilent34970a(IEC60488):
self.NPLC[str(channelNumber)] = nplcValue
def set_tk_type(self, channelNumber, tkType):
"""Sets the Thermocouple type for the specified channel
:param channelNumber: channel to set thermocouple type for
:type channelNumber: int
:param tkType: thermocouple types. Possible values are "B", "E", "J", "K", "N", "R", "S", "T".
:type tkType: str
.. note::
this only takes effects when the :meth:`scan` method is invoked.
"""
if tkType in ("B", "E", "J", "K", "N", "R", "S", "T"):
self.TkType[str(channelNumber)] = tkType
else:
......@@ -102,16 +155,33 @@ class Agilent34970a(IEC60488):
def scan(
self, channelList, functionName, samplesPerChan, sampleRate, verbose=True
):
""" Initiates a scan """
"""Initiates a scan.
:param channelList: channel number or iterable of channel numbers
:type channelList: int or list
:param functionName: measurement function to configure. Some possible values are VOLT:DC, VOLT:AC, FRES, RES, CURR:DC or TEMP. Refer to Agilent documentation for other functions.
:type functionName: str
:param samplesPerChan: Number of samples to be acquired on each channel. They are stored in the device buffer during acquisition (maximum 50000).
:type samplesPerChan: int
:param sampleRate: frequency of the internal clock used to trigger measurements. The instrument resolution is 1 ms.
:type sampleRate: float
:param verbose: prints additionnal information for debugging purposes. Defaults to True.
:type verbose: bool, optional
.. note::
If len(channelList) == 1 and samplesPerChan = 1, a one-shot
measurement is performed, instead of a scan.
"""
try:
# Checks if channelList is iterable
numChans = len([x for x in channelList])
except:
except Exception:
# If not, convert to 1-tuple
channelList = (channelList,)
numChans = 1
pass
# Max number of points: 50000
if samplesPerChan * numChans > 50000:
......@@ -277,13 +347,8 @@ class Agilent34970a(IEC60488):
return retval
def write_vdc(self, channel, value):
def write_vdc(self, channelList, value):
"""Write DC-Voltage on specified AO channel.
.. todo::
Solve an evident bug in this function (should be easy).
"""
print("Warning there is clearly a bug here: channelList !")
......@@ -299,9 +364,10 @@ class Agilent34970a(IEC60488):
)
# Write DC Voltage
self.interface.write(
"SOUR:VOLT " + str(value) + ",(@" + str(channel) + ")"
)
for channel in channelList:
self.interface.write(
"SOUR:VOLT " + str(value) + ",(@" + str(channel) + ")"
)
features = [
......
......@@ -10,7 +10,6 @@
__all__ = ["Keithley2700"]
import numpy as np
from datetime import datetime
import time
from clint.textui import colored
......@@ -20,11 +19,10 @@ from fluidlab.instruments.features import SuperValue, BoolValue
class Keithley2700(IEC60488):
"""Driver for the multiplexer Keithley 2700 Series
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, interface=None):
super().__init__(interface)
self.Range = dict()
self.NPLC = dict()
......@@ -35,17 +33,27 @@ class Keithley2700(IEC60488):
self.Range[channelNumber] = rangeValue
def set_nplc(self, *, channelNumber=1, nplcValue=1.0):
"""
This function sets the integration time of the ADC.
This time is expressed in terms of line frequency (PLC). It spans
from 0.01 to 10.
The default PLC is 1.
>Fast: NPLC=0.01 3 1/2 digits
Fast: NPLC=0.1 5 1/2 digits
Med: NPLC=1.0 6 1/2 digits
Slow: NPLC=5.0
"""This function sets the integration time of the ADC.
:param channelNumber: the channel for which to set the NPLC value
:type channelNumber: int
:param nplcValue: Integration time, expressed in terms of line frequency (PLC). It spans from 0.01 to 10. The default PLC is 1.
:type nplcValue: float
.. note::
Per the documentation, there is a relation between the integration
time, the short name (fast, med, slow) and the effective number of digits
========== ==== =============
Short name NPLC Resolution
========== ==== =============
>Fast 0.01 3 1/2 digits
Fast 0.1 5 1/2 digits
Med 1.0 6 1/2 digits
Slow 5.0
========== ==== =============
"""
nplcValue = float(nplcValue)
self.NPLC[channelNumber] = nplcValue
......@@ -53,11 +61,25 @@ class Keithley2700(IEC60488):
def scan(
self, channelList, functionName, samplesPerChan, sampleRate, verbose
):
"""
Initiates a scan
"""Initiates a scan.
This method is called by the features get method.
:param channelList: channel number or iterable of channel numbers
:type channelList: int or list
:param functionName: measurement function to configure. Some possible values are VOLT:DC, VOLT:AC, FRES, RES, CURR:DC or CURR:AC. Refer to Keithley documentation for other functions.
:type functionName: str
:param samplesPerChan: Number of samples to be acquired on each channel. They are stored in the device buffer during acquisition (maximum 450000). Defaults to 1.
:type samplesPerChan: int
:param sampleRate: frequency of the internal clock used to trigger measurements. The instrument resolution is 1 ms. Defaults to 1 kHz (maximum frequency).
:type sampleRate: float
:param verbose: prints additionnal information for debugging purposes
:type verbose: bool
.. note::
If len(channelList) == 1 and samplesPerChan = 1, a one-shot
measurement is performed, instead of a scan.
If channelList == [1] and samplesPerChan = 1, a one-shot front measurement is
performed.
"""
# Make sure channelList is iterable
......@@ -301,6 +323,9 @@ class Keithley2700(IEC60488):
class Keithley2700Value(SuperValue):
"""Custom :class:`Value` class for the Keithley 2700 features.
"""
def __init__(self, name, doc="", function_name=None):
super().__init__(name, doc)
self.function_name = function_name
......@@ -341,7 +366,7 @@ class Keithley2700Value(SuperValue):
features = [
BoolValue(
"front",
doc="True if switch Front/Rear is on Front",
doc="True if switch Front/Rear is on Front (read-only)",
command_get=":SYST:FRSW?",
),
Keithley2700Value("vdc", doc="DC voltage", function_name="VOLT:DC"),
......
......@@ -12,7 +12,7 @@ __all__ = ["TtiCpx400dp"]
from fluidlab.instruments.iec60488 import IEC60488
from fluidlab.instruments.features import FloatValue, BoolValue
from fluidlab.interfaces import PhysicalInterfaceType
class TtiCpx400dpUnitValue(FloatValue):
def _convert_from_str(self, value):
......@@ -27,15 +27,52 @@ class TtiCpx400dpUnitValue(FloatValue):
class TtiCpx400dp(IEC60488):
"""Driver for the power supply TTI CPX400DP
Dual 420 watt DC Power Supply
"""
===========================================
Dual 420 watt DC Power Supply.
Default interface is GPIB, but the device can work also
with LAN, RS-232 and USB connection.
USB connection is actually a USB-Serial converter.
e.g. on linux with USB connection:
.. code-block:: python
with TtiCpx400dp('/dev/ttyACM0') as tti:
tti.onoffall.set(True)
v = tti.vdc.get(channel=1)
print(v)
On Windows or Linux with GPIB connection:
.. code-block:: python
with TtiCpx400dp('/dev/ttyACM0') as tti:
tti.onoffall.set(True)
v = tti.vdc.get(channel=1)
print(v)
"""
# the default params below are for use with Serial connection (RS-232 or USB)
# they are ignored by the GPIBInterface, VISAInterface and SocketInterface
# classes.
default_inter_params = {
"baudrate": 9600,
"bytesize": 8,
"parity": "N",
"stopbits": 1,
"timeout": 1,
"xonxoff": True,
"rtscts": False,
"dsrdtr": False,
"eol": "\r\n",
"use_readlines": False,
}
features = [
TtiCpx400dpUnitValue(
"vdc",
doc="Get actual voltage/Set voltage setpoint on specified channel",
doc="Get actual voltage/Set voltage setpoint on specified channel. Channel is 1 or 2.",
command_set="V{channel:d} {value}",
command_get="V{channel:d}O?",
channel_argument=True,
......@@ -43,7 +80,7 @@ features = [
),
TtiCpx400dpUnitValue(
"idc",
doc="Get actual current/Set current setpoint on specified channel",
doc="Get actual current/Set current setpoint on specified channel. Channel is 1 or 2.",
command_set="I{channel:d} {value}",
command_get="I{channel:d}O?",
channel_argument=True,
......@@ -51,7 +88,7 @@ features = [
),
BoolValue(
"onoff",
doc="Toogle output ON/OFF for specified channel",
doc="Toogle output ON/OFF for specified channel. Channel is 1 or 2.",
command_set="OP{channel:d} {value}",
command_get="OP{channel:d}?",
channel_argument=True,
......@@ -61,7 +98,7 @@ features = [
),
BoolValue(
"onoffall",
doc="Toogle output ON/OFF for both channels simultaneously",
doc="Toogle output ON/OFF for both channels simultaneously.",
command_set="OPALL ",
channel_argument=False,
check_instrument_value=False,
......
"""Interfaces with the instruments (:mod:`fluidlab.instruments.interfaces`)
===========================================================================
"""Interfaces with the instruments (:mod:`fluidlab.interfaces`)
===============================================================
Provides some classes:
......@@ -89,7 +89,10 @@ def set_default_interface(interface_type, interface_classname):
"""
default_interface[interface_type] = interface_classname
def interface_classname_from_string(name, default_physical_interface=None, **kwargs):
def interface_classname_from_string(
name, default_physical_interface=None, **kwargs
):
classname = None
physical_interface = None
if "GPIB" in name:
......@@ -100,6 +103,9 @@ def interface_classname_from_string(name, default_physical_interface=None, **kwa
elif isinstance(name, (ipaddress.IPv4Address, ipaddress.IPv6Address)):
name = str(name)
physical_interface = PhysicalInterfaceType.Ethernet
elif name.startswith('/dev/tty'):
physical_interface = PhysicalInterfaceType.Serial
classname = "SerialInterface"
else:
try:
_ = ipaddress.ip_address(name)
......@@ -115,7 +121,8 @@ def interface_classname_from_string(name, default_physical_interface=None, **kwa
if classname is None:
classname = default_interface[physical_interface]
return classname, physical_interface
def interface_from_string(name, default_physical_interface=None, **kwargs):
"""
Infer physical_interface from name if possible, or use the default provided
......@@ -127,7 +134,9 @@ def interface_from_string(name, default_physical_interface=None, **kwargs):
ASRL0::INSTR is a VISA Serial address
192.168.0.1 is a IP address, so Ethernet interface
"""
classname, physical_interface = interface_classname_from_string(name, default_physical_interface)
classname, physical_interface = interface_classname_from_string(
name, default_physical_interface
)
if classname == "VISAInterface":
from fluidlab.interfaces.visa_inter import VISAInterface
......@@ -184,7 +193,7 @@ class Interface:
# do the actual close (without testing self.opened)
raise NotImplementedError
def __init__(self):
def __init__(self, **kwargs):
self.opened = False
def open(self):
......
......@@ -44,7 +44,7 @@ def closest_timeout(t):
class GPIBInterface(QueryInterface):
def __init__(self, board_adress, instrument_adress, timeout=1.0):
def __init__(self, board_adress, instrument_adress, timeout=1.0, **kwargs):
super().__init__()
self.board_adress = board_adress
self.instrument_adress = instrument_adress
......
......@@ -25,7 +25,7 @@ from fluidlab.interfaces import Interface
class ModbusInterface(Interface):
def __init__(self, port, method="rtu", slave_address=1, timeout=1):
def __init__(self, port, method="rtu", slave_address=1, timeout=1, **kwargs):
self.port = port
self.method = method
self.slave_address = slave_address
......
......@@ -32,6 +32,8 @@ class SerialInterface(QueryInterface):
eol=None,
multilines=False,
autoremove_eol=False,
use_readlines=True,
**kwargs,
):
"""
if eol is not None, the serial port is wrapped into TextIOWrapper to
......@@ -57,6 +59,7 @@ class SerialInterface(QueryInterface):
self.eol = eol
self.multilines = multilines
self.autoremove_eol = autoremove_eol
self.use_readlines = use_readlines
def __str__(self):
return f'SerialInterface("{self.port:}")'
......@@ -80,7 +83,7 @@ class SerialInterface(QueryInterface):
)
self._lowlevel = self.serial_port = sp
self._close = sp.close
if self.eol is not None:
if self.eol is not None and self.use_readlines:
self.ser_io = io.TextIOWrapper(
io.BufferedRWPair(sp, sp, 1),
newline=self.eol,
......@@ -92,11 +95,15 @@ class SerialInterface(QueryInterface):
args = [a.strip() + "\n" for a in args]
# print('->', repr(args[0]))
if self.eol is not None:
return self.ser_io.write(*args)
# ensure no unicode strings sent to serial_port.write
args = [a.encode("ascii") if isinstance(a, str) else a for a in args]
return self.serial_port.write(*args)
if self.use_readlines:
return self.ser_io.write(*args)
else:
for a in args:
self.serial_port.write(a.encode('ascii') + self.eol.encode('ascii'))
else:
# ensure no unicode strings sent to serial_port.write
args = [a.encode("ascii") if isinstance(a, str) else a for a in args]
return self.serial_port.write(*args)
def readline(self, *args):
if self.eol is not None:
......@@ -121,6 +128,11 @@ class SerialInterface(QueryInterface):
return self.serial_port.readlines(*args)
def _read(self):
if not self.use_readlines:
iw = self.serial_port.inWaiting()
data = self.serial_port.read(iw).decode('ascii')
return data
if self.multilines:
result = self.readlines()
return "\n".join(result)
......
......@@ -21,7 +21,7 @@ class SocketInterface(QueryInterface):
Concrete classes are UDPSocketInterface and TCPSocketInterface
"""
def __init__(self, ip_address, autoremove_eol):
def __init__(self, ip_address, autoremove_eol, **kwargs):
super().__init__()
self.ip_address = ip_address
self.autoremove_eol = autoremove_eol
......@@ -47,7 +47,7 @@ class SocketInterface(QueryInterface):
class UDPSocketInterface(SocketInterface):
def __init__(self, ip_address, in_port, out_port, autoremove_eol=True):
def __init__(self, ip_address, in_port, out_port, autoremove_eol=True, **kwargs):
super().__init__(ip_address, autoremove_eol)
if callable(in_port):
self.in_port = in_port(ip_address)
......@@ -91,7 +91,7 @@ class UDPSocketInterface(SocketInterface):
class TCPSocketInterface(SocketInterface):
def __init__(self, ip_address, port, autoremove_eol=True):
def __init__(self, ip_address, port, autoremove_eol=True, **kwargs):
super().__init__(ip_address, autoremove_eol)
self.port = port
......
......@@ -27,7 +27,7 @@ from fluidlab.interfaces import QueryInterface
class USBTMCInterface(QueryInterface):
def __init__(self, device=0):
def __init__(self, device=0, **kwargs):
"""
Create a new USBTMCInterface.
......
......@@ -25,7 +25,7 @@ def set_default_pyvisa_backend(backend):
class VISAInterface(QueryInterface):
def __init__(self, resource_name, backend=None):
def __init__(self, resource_name, backend=None, **kwargs):
super().__init__()
self.resource_name = resource_name
if backend is None:
......
"""Traverses (:mod:`fluidlab.objects.traverse_lxm32`)
=====================================================
.. currentmodule:: fluidlab.objects.traverse_lxm3
.. currentmodule:: fluidlab.objects.traverse_lxm32
Provides:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment