Source code for fluidlab.instruments.multiplexer.agilent_34970a

"""agilent_34970a
=================

.. autoclass:: Agilent34970a
   :members:
   :private-members:

"""

__all__ = ["Agilent34970a"]

import numpy as np
from datetime import datetime

from fluidlab.instruments.iec60488 import IEC60488
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

        setattr(Driver, name, self)

        def get(self, chanList, samplesPerChan=1, sampleRate=None, verbose=None):
            """Get """ + name
            if verbose is None:
                # default is verbose for acquisitions
                verbose = samplesPerChan > 1
            result = self._driver.scan(
                chanList, function_name, samplesPerChan, sampleRate, verbose
            )
            if len(result) == 1:
                result = result[0]
            return result

        self.get = get.__get__(self, self.__class__)

        def set(self, channel, value, warn=True):
            """Set """ + name
            # Makes sense for voltage on AO channels only
            if name == "vdc":
                self._driver.write_vdc(channel, value)
            else:
                raise ValueError("Specified value cannot be written")

        self.set = set.__get__(self, self.__class__)


[docs]class Agilent34970a(IEC60488): """Driver for the multiplexer Agilent 34970A.""" def __init__(self, interface=None): super().__init__(interface) # Define instance variables for optional NPLC and Range settings # to be used when scanning # # If no key is defined, NPLC and Range settings are not # changed if self.NPLC["101"] exists, then NPLC will be set # when scanning channel 101 if self.Range["203"] exists, then # Manual range will be set when scanning channel 203 Auto # Range will be set to ON otherwise self.NPLC = dict() self.Range = dict() self.TkType = dict() self.tmo = None
[docs] 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
[docs] 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
[docs] def set_nplc(self, channelNumber, nplcValue): """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) if nplcValue not in possible_values: raise ValueError("Unacceptable NPLC value") self.NPLC[str(channelNumber)] = nplcValue
[docs] 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: raise ValueError("Unknown TK type")
[docs] def scan( self, channelList, functionName, samplesPerChan, sampleRate, verbose=True ): """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 Exception: # If not, convert to 1-tuple channelList = (channelList,) numChans = 1 # Max number of points: 50000 if samplesPerChan * numChans > 50000: raise ValueError( "Maximum number of samples is 50000 on Agilent 34970A" ) if samplesPerChan > 1: timeInterval = 1.0 / sampleRate if timeInterval < 1e-3: raise ValueError( "The timer resolution of the Agilent 34970A is 1 ms" ) # Check that channel numbers are right badChans = [x for x in channelList if ((x < 100) and (x >= 400))] if len(badChans) > 0: raise ValueError( "Channels must be specified in the form scc, where s is " "the slot number (100, 200, 300), and cc is the channel " "number. For example, channel 10 on the slot 300 is referred " "to as 310." ) # Set measurement type to desired function on specified channels msg = 'SENS:FUNC "' + functionName + '",(@' for i in range(numChans): msg = msg + str(channelList[i]) if i < (numChans - 1): msg = msg + "," else: msg = msg + ")" self.interface.write(msg) # Set range on specified channels for chan in channelList: if str(chan) in self.Range: # set channel to manual range self.interface.write( "SENS:" + functionName + ":RANG " + str(self.Range[str(chan)]) + ",(@" + str(chan) + ")" ) elif functionName != "TEMP": # set channel to Auto Range self.interface.write( "SENS:" + functionName + ":RANG:AUTO ON,(@" + str(chan) + ")" ) # Set NPLC for specified channels for chan in channelList: if str(chan) in self.NPLC: # set NPLC to specified value self.interface.write( "SENS:" + functionName + ":NPLC " + str(self.NPLC[str(chan)]) + ",(@" + str(chan) + ")" ) if samplesPerChan > 1: # warn if wrong value (50Hz line hard coded here) tMoy = self.NPLC[str(chan)] / 50.0 if tMoy > 1.0 / sampleRate: print( "Warning: averaging for {:.1f} ms, and sample time is {:.1f} ms".format( 1000.0 * tMoy, 1000.0 / sampleRate ) ) elif samplesPerChan > 1: print("Warning: NPLC should be specified for acquisitions") # Set TK Type for specified channels (if TK channel and TkType defined) if functionName == "TEMP": for chan in channelList: if str(chan) in self.TkType: # set Tk type to specified value self.interface.write( "SENS:TEMP:TRAN:TC:TYPE " + str(self.TkType[str(chan)]) + ",(@" + str(chan) + ")" ) # Setup scan list msg = "ROUT:SCAN (@" for i in range(numChans): msg = msg + str(channelList[i]) if i < (numChans - 1): msg = msg + "," else: msg = msg + ")" self.interface.write(msg) # Setup trigger and timer & Format if samplesPerChan > 1: self.interface.write("TRIG:SOUR TIM") self.interface.write("TRIG:TIM " + str(timeInterval)) self.interface.write("TRIG:COUN " + str(samplesPerChan)) self.interface.write("FORM:READ:TIME ON") else: self.interface.write("TRIG:SOUR IMM") self.interface.write("TRIG:COUN 1") self.interface.write("FORM:READ:TIME OFF") self.interface.write("FORM:READ:ALAR OFF") self.interface.write("FORM:READ:CHAN OFF") self.interface.write("FORM:READ:UNIT OFF") # Prepare status and event register self.clear_status() # *CLS self.event_status_enable_register.set(1) # *ESE 1 self.status_enable_register.set(32) # *SRE 32 # Initiate scan and trigger Operation Complete event after completion self.interface.write("INIT") if verbose: print( datetime.now().isoformat().replace("T", " ") + " - Acquisition initiated" ) # Wait for Service Request (triggered by *OPC after the scan # is complete) self.wait_till_completion_of_operations() # *OPC if not self.tmo: if sampleRate: tmo = int(1000 * samplesPerChan / sampleRate) else: tmo = 10000 if tmo < 10000: tmo = 10000 print("tmo =", tmo, "ms") else: tmo = self.tmo self.interface.wait_for_srq(timeout=tmo) # Unassert SRQ self.clear_status() # Fetch data if verbose: print( datetime.now().isoformat().replace("T", " ") + " - Fetching data" ) data = self.interface.query("FETCH?", verbose=verbose) # Parse data if samplesPerChan > 1: # timeStamp + value for each channel values = np.array([float(x) for x in data.split(",")]) retval = values[::2], values[1::2] else: expectedEntriesPerLine = numChans # only value for each channel retval = np.array([float(x) for x in data.split(",")]) return retval
[docs] def write_vdc(self, channelList, value): """Write DC-Voltage on specified AO channel.""" print("Warning there is clearly a bug here: channelList !") # Check that channel numbers are right badChans = [x for x in channelList if ((x < 100) and (x >= 400))] if badChans > 0: raise ValueError( "Channels must be specified in the form scc, where s is the " "slot number (100, 200, 300), and cc is the channel number. " "For example, channel 10 on the slot 300 is referred to " "as 310." ) # Write DC Voltage for channel in channelList: self.interface.write( "SOUR:VOLT " + str(value) + ",(@" + str(channel) + ")" )
features = [ Agilent34970aValue("vdc", doc="DC Voltage", function_name="VOLT:DC"), Agilent34970aValue("vrms", doc="RMS Voltage", function_name="VOLT:AC"), Agilent34970aValue("temperature", doc="Temperature", function_name="TEMP"), Agilent34970aValue("ohm", doc="2-wire resistance", function_name="RES"), Agilent34970aValue("ohm_4w", doc="4-wire resistance", function_name="FRES"), Agilent34970aValue("idc", doc="DC Current", function_name="CURR:DC"), ] Agilent34970a._build_class_with_features(features)