"""
LabJack Data Acquisition (DAQ)
+++++++++++++++++++++++++++++++++++++++
.. autosummary::
~LabJackBase
Ophyd definitions for Labjack T-series data acquisition devices.
Supported devices, all inherit from ``LabJackBase``:
- T4
- T7
- T7Pro
- T8
These devices are **based on EPICS LabJack module R3.0**. The EPICS IOC
database changed significantly from R2 to R3 when the module was
rewritten to use the LJM library.
.. seealso:: https://github.com/epics-modules/LabJack/releases/tag/R3-0
There are definitions for the entire LabJack device, as well as the
various inputs/outputs available on the LabJack T-series.
Individual inputs can be used as part of other devices. Assuming
analog input 5 is connected to a gas flow meter:
.. code:: python
from ophyd import Component as Cpt
from apstools.devices import labjack
class MyBeamline(Device):
...
gas_flow = Cpt(labjack.AnalogInput, "LabJackT7_1:Ai5")
"""
from ophyd import Component as Cpt
from ophyd import Device
from ophyd import DynamicDeviceComponent as DCpt
from ophyd import EpicsSignal
from ophyd import EpicsSignalRO
from ophyd import FormattedComponent as FCpt
from ophyd import Kind
from ..synApps import EpicsRecordDeviceCommonAll
from ..synApps import EpicsRecordInputFields
from ..synApps import EpicsRecordOutputFields
__all__ = [
"AnalogOutput",
"AnalogInput",
"DigitalIO",
"WaveformDigitizer",
"make_digitizer_waveforms",
"WaveformGenerator",
"LabJackBase",
"LabJackT4",
"LabJackT7",
"LabJackT7Pro",
"LabJackT8",
]
KIND_CONFIG_OR_NORMAL = 3
"""Alternative for ``Kind.config | Kind.normal``."""
class Input(EpicsRecordInputFields, EpicsRecordDeviceCommonAll):
"""A generic input record.
Similar to synApps input records but with some changes. The .PROC
field is used as a trigger. This way, even if the .SCAN field is
set to passive, the record will still update before being read.
"""
process_record = Cpt(EpicsSignal, ".PROC", kind="omitted", put_complete=True, trigger_value=1)
final_value = Cpt(EpicsSignalRO, ".VAL", kind="normal", auto_monitor=False)
class Output(EpicsRecordOutputFields, EpicsRecordDeviceCommonAll):
"""A generic output record.
Intended to be sub-classed into different output types.
"""
class BinaryOutput(Output):
"""A binary input on the labjack.
Similar to a common EPICS input without the OVAL record.
"""
output_value = None
[docs]class AnalogOutput(Output):
"""An analog output on a labjack device."""
[docs]class DigitalIO(Device):
"""A digital input/output channel on the labjack.
Because of the how the records are structured in EPICS, the prefix
must not include the "Bi{N}" portion of the prefix. Instead, the
prefix should be prefix for the whole labjack
(e.g. ``LabJackT7_1:``), and the channel number should be provided
using the *ch_num* property. So for the digital I/O with its input
available at PV ``LabJackT7_1:Bi3``, use:
.. code:: python
dio3 = DigitalIO("LabJackT7_1:", name="dio3", ch_num=3)
This will create signals for the input (``Bi3``), output
(``Bo3``), and direction (``Bd3``) records.
"""
ch_num: int
input = FCpt(Input, "{prefix}Bi{ch_num}")
output = FCpt(BinaryOutput, "{prefix}Bo{ch_num}")
direction = FCpt(EpicsSignal, "{prefix}Bd{ch_num}", kind=Kind.config)
def __init__(self, *args, ch_num, **kwargs):
self.ch_num = ch_num
super().__init__(*args, **kwargs)
def make_analog_inputs(num_ais: int):
"""Create a dictionary with analog input device definitions.
For use with an ophyd DynamicDeviceComponent.
Parameters
==========
num_ais
How many analog inputs to create.
"""
defn = {}
for n in range(num_ais):
defn[f"ai{n}"] = (AnalogInput, f"Ai{n}", {})
return defn
def make_analog_outputs(num_aos: int):
"""Create a dictionary with analog output device definitions.
For use with an ophyd DynamicDeviceComponent.
Parameters
==========
num_aos
How many analog outputs to create.
"""
defn = {}
for n in range(num_aos):
defn[f"ao{n}"] = (AnalogOutput, f"Ao{n}", {})
return defn
def make_digital_ios(num_dios: int):
"""Create a dictionary with digital I/O device definitions.
For use with an ophyd DynamicDeviceComponent.
Parameters
==========
num_dios
How many digital I/Os to create.
"""
defn = {}
for n in range(num_dios):
defn[f"dio{n}"] = (DigitalIO, "", dict(ch_num=n))
# Add the digital word outputs
defn["dio"] = (EpicsSignalRO, "DIOIn", dict(kind=Kind.normal))
defn["fio"] = (EpicsSignalRO, "FIOIn", dict(kind=Kind.normal))
defn["eio"] = (EpicsSignalRO, "EIOIn", dict(kind=Kind.normal))
defn["cio"] = (EpicsSignalRO, "CIOIn", dict(kind=Kind.normal))
defn["mio"] = (EpicsSignalRO, "MIOIn", dict(kind=Kind.normal))
return defn
[docs]class LabJackBase(Device):
"""A labjack T-series data acquisition unit (DAQ).
To use the individual components separately, consider using the
corresponding devices in the list below.
This device contains signals for the following:
- device information (e.g. firmware version ,etc)
- analog outputs (:py:class:`~apstools.devices.labjack.AnalogInput`)
- analog inputs* (:py:class:`~apstools.devices.labjack.AnalogOutput`)
- digital input/output* (:py:class:`~apstools.devices.labjack.DigitalIO`)
- waveform digitizer* (:py:class:`~apstools.devices.labjack.WaveformDigitizer`)
- waveform generator (:py:class:`~apstools.devices.labjack.WaveformGenerator`)
The number of inputs and digital outputs depends on the specific
LabJack T-series device being used. Therefore, the base device
``LabJackBase`` does not implement these I/O signals. Instead,
consider using one of the subclasses, like ``LabJackT4``.
The ``.trigger()`` method does not do much. To retrieve fresh
values for analog inputs where .SCAN is passive, you will need to
trigger the individual inputs themselves.
The waveform generator and waveform digitizer are included for
convenience. Reading all the analog/digital inputs and outputs can
be done by calling the ``.read()`` method. However, it is unlikely
that the goal is also to trigger the digitizer and generator
during this read. For this reason, **the digitizer and generator
have kind="omitted"**. To trigger the digitizer or generator, they
can be used as separate devices:
.. code:: python
lj = LabJackT4(...)
# Read a waveform from the digitizer
lj.waveform_digitizer.trigger().wait()
lj.waveform_digitizer.read()
# Same thing for the waveform generator
lj.waveform_generator.trigger().wait()
"""
model_name = Cpt(EpicsSignal, "ModelName", kind=Kind.config)
firmware_version = Cpt(EpicsSignal, "FirmwareVersion", kind=Kind.config)
serial_number = Cpt(EpicsSignal, "SerialNumber", kind=Kind.config)
device_temperature = Cpt(EpicsSignal, "DeviceTemperature", kind=Kind.config)
ljm_version = Cpt(EpicsSignal, "LJMVersion", kind=Kind.config)
driver_version = Cpt(EpicsSignal, "DriverVersion", kind=Kind.config)
last_error_message = Cpt(EpicsSignal, "LastErrorMessage", kind=Kind.config)
poll_sleep_ms = Cpt(EpicsSignal, "PollSleepMS", kind=Kind.config)
poll_time_ms = Cpt(EpicsSignal, "PollTimeMS", kind=Kind.omitted)
analog_in_settling_time_all = Cpt(EpicsSignal, "AiAllSettlingUS", kind=Kind.config)
analog_in_resolution_all = Cpt(EpicsSignal, "AiAllResolution", kind=Kind.config)
analog_in_sampling_rate = Cpt(EpicsSignal, "AiSamplingRate", kind=Kind.config)
device_reset = Cpt(EpicsSignal, "DeviceReset", kind=Kind.omitted)
# Common sub-devices (all labjacks have 2 analog outputs)
# NB: Analog inputs/digital I/Os are on a per-model basis
analog_outputs = DCpt(make_analog_outputs(2), kind=KIND_CONFIG_OR_NORMAL)
waveform_generator = Cpt(WaveformGenerator, "", kind=Kind.omitted)
[docs]class LabJackT4(LabJackBase):
# Inherit the docstring from the base class
# (needed for sphinx auto API)
__doc__ = LabJackBase.__doc__
analog_inputs = DCpt(make_analog_inputs(12), kind=KIND_CONFIG_OR_NORMAL)
digital_ios = DCpt(make_digital_ios(16), kind=KIND_CONFIG_OR_NORMAL)
waveform_digitizer = Cpt(WaveformDigitizer, "", kind=Kind.omitted)
[docs]class LabJackT7(LabJackBase):
# Inherit the docstring from the base class
# (needed for sphinx auto API)
__doc__ = LabJackBase.__doc__
analog_inputs = DCpt(make_analog_inputs(14), kind=KIND_CONFIG_OR_NORMAL)
digital_ios = DCpt(make_digital_ios(23), kind=KIND_CONFIG_OR_NORMAL)
waveform_digitizer = Cpt(WaveformDigitizer, "")
[docs]class LabJackT7Pro(LabJackBase):
# Inherit the docstring from the base class
# (needed for sphinx auto API)
__doc__ = LabJackBase.__doc__
analog_inputs = DCpt(make_analog_inputs(14), kind=KIND_CONFIG_OR_NORMAL)
digital_ios = DCpt(make_digital_ios(23), kind=KIND_CONFIG_OR_NORMAL)
waveform_digitizer = Cpt(WaveformDigitizer, "")
[docs]class LabJackT8(LabJackBase):
# Inherit the docstring from the base class
# (needed for sphinx auto API)
__doc__ = LabJackBase.__doc__
analog_inputs = DCpt(make_analog_inputs(8), kind=KIND_CONFIG_OR_NORMAL)
digital_ios = DCpt(make_digital_ios(20), kind=KIND_CONFIG_OR_NORMAL)
waveform_digitizer = Cpt(WaveformDigitizer, "")