# coding: utf-8
from time import time
from typing import Optional, List, Iterable
from dataclasses import dataclass
import logging
from warnings import warn
from .meta_inout import InOut
from .._global import OptionalModule
try:
from ue9 import UE9
except (ModuleNotFoundError, ImportError):
UE9 = OptionalModule("LabJackPython")
@dataclass
class _Channel:
"""This class is a simple structure holding all the attributes a Labjack UE9
channel can have."""
num: int
range_num: int = 12
gain: float = 1
offset: float = 0
make_zero: bool = False
[docs]
class LabjackUE9(InOut):
"""This class can read the analog input channels from a Labjack UE9 device.
It cannot read nor drive any of the other inout or output channels on the
UE9. The UE9 model has been discontinued, and replaced by the T7 model (see
:class:`~crappy.inout.LabjackT7`). It is thus likely that this class won't be
further improved in the future.
.. versionadded:: 1.4.0
.. versionchanged:: 2.0.0 renamed from *Labjack_ue9* to *LabjackUE9*
"""
[docs]
def __init__(self,
channels: Iterable[int],
gain: Optional[Iterable[float]] = None,
offset: Optional[Iterable[float]] = None,
make_zero: Optional[Iterable[bool]] = None,
resolution: Optional[Iterable[int]] = None) -> None:
"""Sets the arguments and initializes the parent class.
Args:
channels: An iterable (like a :obj:`list` or a :obj:`tuple`) containing
all the channels to read, given as :obj:`int`. Only the `AIN` channels
can be read by this class, so to read the channel `AIN2` the integer
`2` should be added to the iterable.
gain: An iterable (like a :obj:`list` or a :obj:`tuple`) containing for
each channel the gain to apply to the measured voltage, as a
:obj:`float`. The returned voltage is calculated as follows :
:math:`returned\_voltage = gain * measured\_voltage + offset`. If not
given, no gain is applied to the measured values.
offset: An iterable (like a :obj:`list` or a :obj:`tuple`) containing for
each channel the offset to apply to the measured voltage, as a
:obj:`float`. The returned voltage is calculated as follows :
:math:`returned\_voltage = gain * measured\_voltage + offset`. If not
given, no offset is applied to the measured values.
make_zero: An iterable (like a :obj:`list` or a :obj:`tuple`) containing
for each channel a :obj:`bool` indicating whether the channel should be
zeroed or not. If so, data will be acquired on this channel before the
test starts, and a compensation value will be deduced so that the
offset of this channel is `0`. **It will only take effect if the**
``make_zero_delay`` **argument of the** :class:`~crappy.blocks.IOBlock`
**controlling the Labjack is set** ! If not given, the channels are by
default not zeroed.
resolution: An iterable (like a :obj:`list` or a :obj:`tuple`) containing
for each channel the resolution of the acquisition as an integer. Refer
to Labjack documentation for more details. The higher this value the
better the resolution, but the lower the speed. The possible range is
`1` to `12`, and the default is `12`.
Note:
All the :iterables given as arguments for the channels should have the
same length. If that's not the case, all the given iterables are treated
as if they had the same length as the shortest given one.
"""
warn(f"Starting from version 2.1.0, {type(self).__name__} will be moved "
f"to crappy.collection. Your code that uses it will still work as "
f"is, except you will now need to import crappy.collection at the "
f"top of your script.", FutureWarning)
self._handle = None
super().__init__()
# Setting the defaults for arguments that are not given
if resolution is None:
resolution = [12 for _ in channels]
if gain is None:
gain = [1 for _ in channels]
if offset is None:
offset = [0 for _ in channels]
if make_zero is None:
make_zero = [False for _ in channels]
self._channels = [_Channel(num=chan, range_num=r_num, gain=g,
offset=off, make_zero=make_z)
for chan, r_num, g, off, make_z in
zip(channels, resolution, gain, offset, make_zero)]
self.log(logging.DEBUG, f"Input channels: {self._channels}")
[docs]
def open(self) -> None:
"""Opens the connection to the Labjack."""
self.log(logging.INFO, "Opening the connection to the Labjack")
self._handle = UE9()
[docs]
def make_zero(self, delay: float) -> None:
"""Overriding the method of the parent class, because the user can choose
which channels should be zeroed or not.
It simply performs the regular zeroing, and resets the compensation to
zero for the channels that shouldn't be zeroed.
.. versionadded:: 1.5.10
"""
# No need to acquire data if no channel should be zeroed
if any(chan.make_zero for chan in self._channels):
# Acquiring the data
super().make_zero(delay)
# Proceed only if the acquisition went fine
if self._compensations:
# Resetting the compensation for channels that shouldn't be zeroed
self._compensations = [comp if chan.make_zero else 0 for comp, chan
in zip(self._compensations, self._channels)]
[docs]
def get_data(self) -> List[float]:
"""Reads sequentially the channels and returns the acquired values,
corrected by the given gains and offsets."""
return [time()] + [self._handle.getAIN(chan.num, Resolution=chan.range_num)
* chan.gain + chan.offset for chan in self._channels]
[docs]
def close(self) -> None:
"""Closes the connection to the Labjack, if it was already opened."""
if self._handle is not None:
self.log(logging.INFO, "Closing the connection to the Labjack")
self._handle.close()