"""
devices.py contains unified LabJack classes for the U3, U6 and T7.
A common set of methods is implemented, simplifying writing code for any
of the devices listed above.
The classes are built using the existing LabJack python packages:
- LabJackPython
- labjack-ljm-python
Note: Use code folding at the method level to improve readability.
Author: Eduardo Nigro
rev 0.0.3
2021-12-13
"""
import re
import time
import numpy as np
# Importing LJM and LJM classes used in
# the LabJackT7 class
from labjack.ljm import ljm, LJMError
# Importing LabJackPython classes and functions used in
# the LabJackU3 and LabJackU6 classes
import u3, u6
import ctypes
from LabJackPython import (
LabJackException,
_loadLibrary,
ePut,
eGet,
AddRequest,
GoOne
)
# Importing constants used in the LabJackU3 and LabJackU6 classes
# See LabJackPython.py for more details on each constant
from LabJackPython import (
LJ_chALL_CHANNELS,
LJ_chNUMBER_TIMERS_ENABLED,
LJ_chSTREAM_BACKLOG_COMM,
LJ_chSTREAM_BACKLOG_UD,
LJ_chSTREAM_BUFFER_SIZE,
LJ_chSTREAM_SCAN_FREQUENCY,
LJ_chSTREAM_WAIT_MODE,
LJ_chTIMER_CLOCK_BASE,
LJ_chTIMER_CLOCK_DIVISOR,
LJ_chTIMER_COUNTER_PIN_OFFSET,
LJ_ioADD_STREAM_CHANNEL,
LJ_ioCLEAR_STREAM_CHANNELS,
LJ_ioGET_AIN,
LJ_ioGET_AIN_DIFF,
LJ_ioGET_ANALOG_ENABLE_PORT,
LJ_ioGET_CONFIG,
LJ_ioGET_DIGITAL_BIT,
LJ_ioGET_DIGITAL_BIT_DIR,
LJ_ioGET_STREAM_DATA,
LJ_ioGET_TIMER,
LJ_ioPIN_CONFIGURATION_RESET,
LJ_ioPUT_AIN_RANGE,
LJ_ioPUT_ANALOG_ENABLE_BIT,
LJ_ioPUT_ANALOG_ENABLE_PORT,
LJ_ioPUT_CONFIG,
LJ_ioPUT_DAC,
LJ_ioPUT_DIGITAL_BIT,
LJ_ioPUT_TIMER_MODE,
LJ_ioPUT_TIMER_VALUE,
LJ_ioSTART_STREAM,
LJ_ioSTOP_STREAM,
LJ_rgBIP10V,
LJ_rgBIP1V,
LJ_rgBIPP1V,
LJ_rgBIPP01V,
LJ_swNONE,
LJ_tc48MHZ,
LJ_tc48MHZ_DIV,
LJ_tmPWM16,
LJ_tmQUAD,
)
[docs]class LabJackU3:
"""
The class to represent the LabJack U3.
:param serialnum: The device serial number.
:type serialnum: int
Ports that are made available with this class are listed below.
The port names assume a U3-HV.
* Analog Output: ``'DAC0'``, ``'DAC1'``
* Analog Input: ``'AIN0'``, ``'AIN1'``, ``'AIN2'``, ``'AIN3'``
* Flexible I/O: ``'FIO4'``, ``'FIO5'``, ``'FIO6'``, ``'FIO7'``,
* Flexible I/O: ``'EIO0'``, ``'EIO1'``, ... , ``'EIO7'``
Device-specific methods:
* `get_config` - Gets port configuration
* `reset_config` - Resets port configuration to factory defaults
* `get_bitdir` - Gets digital port bit direction
Connect to the first found U3:
>>> from labjack_unified.devices import LabJackU3
>>> lju3 = LabJackU3()
>>> lju3.display_info()
>>> lju3.close()
You can also connect to a specific device using its serial number:
>>> lju3 = LabJackU3(320012345)
"""
# CONSTRUCTOR METHODS
def __init__(self, serialnum=None):
"""
Class constructor
"""
self._staticlib = _loadLibrary()
# Attempting to open the device
try:
if serialnum:
# Opens LabJack with specific serial number
self._commhandle = u3.U3(autoOpen=False)
self._commhandle.open(serial=serialnum)
else:
# Opens first available LabJack
self._commhandle = u3.U3(autoOpen=False)
self._commhandle.open()
except LabJackException as error:
print(error)
else:
self._numports = 16
self._assign_info(self._commhandle.configU3())
self._assign_ports()
self.reset_config()
print('Opened LabJack', self._serialnum)
[docs] def close(self):
"""
Close the U3 device connection.
>>> lju3.close()
"""
self._staticlib.Close(self._commhandle.handle)
print('Closed LabJack', self._serialnum)
[docs] def display_info(self):
"""
Display a summary with the U3 device information.
"""
print('____________________________________________________________')
print('Device Name........', self._type)
print('Serial Number......', self._serialnum)
print('Hardware Version...', self._hardware)
print('Firmware Version...', self._firmware)
print('Connection Type....', self._connection)
print('____________________________________________________________')
# I/O METHODS
[docs] def set_config(self, name, config):
"""
Set the LabJack flexible IO port configuration.
:param name: The port name to configure.
:type name: str
:param config: The configuration option.
If `name` is ``'ALL'`` a string of mask bits for all 16 ports
is used. For single port, `config` can be either a string
``'analog'`` or ``'digital'``. Alternativelly, 1 or 0 can be used.
:type config: str, int
Configure flexible ports ``'FIO4'`` and ``'FIO5'`` as analog:
>>> lju3.set_config('FIO4', 'analog')
>>> lju3.set_config('FIO5', 1)
Configure flexible ports ``'EIO0'`` and ``'EIO1'`` as digital:
>>> lju3.set_config('EIO0', 'digital')
>>> lju3.set_config('EIO1', 0)
Configure flexible ports ``'EI01'`` and ``'EI03'`` as analog and
ports ``'AIN0'`` to ``'AIN3'`` to analog (if a U3-LV is used):
>>> lju3.set_config('ALL', '0000101000001111')
.. note::
1. LSB (Least Significant Bit) is port ``'AIN0'``.
2. On a U3-HV, the first 4 ports are always analog and the first 4 LSB settings are ignored.
"""
if name.lower() == 'all':
# Checking for correct mask string length
if len(config) == self._numports:
ePut(self._commhandle.handle, LJ_ioPUT_ANALOG_ENABLE_PORT,
0, int(config, 2), self._numports)
else:
raise Exception("'ALL' ports CONFIG must be 16-bit string.")
else:
# Checking for valid input port names
if not self._check_port(name, "AIN"):
raise Exception('Invalid flexible IO port name.')
# Checking for valid configuration options
if type(config) == str:
if config.lower() == 'analog':
config = 1
elif config.lower() == 'digital':
config = 0
else:
raise Exception(
"Port configuration must be either 'DIGITAL' or 'ANALOG'.")
else:
if (config != 0) and (config != 1):
raise Exception('Port configuration must be either 0 or 1')
ePut(self._commhandle.handle, LJ_ioPUT_ANALOG_ENABLE_BIT,
self._get_AINnumber(name), config, 0)
[docs] def get_config(self, name='ALL'):
"""
Get the LabJack flexible IO port configuration.
:param name: The port name to get the configuration.
If `name` is ``'ALL'`` a dictionary with keys `EIOAnalog` and
`FIOAnalog` is returned.
:type name: str
:returns: For single port `name`, an integer is returned, where
`1=Analog` and `0=Digital`. If `name` is ``'ALL'`` a dictionary
containing the bit mask string for the `EIO` and `FIO` ports is
returned. The least significant bit (LSB) of the `EIO` and `FIO`
bit strings correspond respectively to ports ``'EIO0'`` and
``'FIO0'``.
:rtype: int, dict
Get flexible port configuration:
>>> lju3.get_config()
.. note::
In the case of a LabJack U3-HV, the four LSBs of the `FIO` ports
are always `1s`. They correspond to the always analog ports
``'AIN0'`` to ``'AIN3'``.
"""
mask = eGet(self._commhandle.handle, LJ_ioGET_ANALOG_ENABLE_PORT,
0, 0, self._numports)
mask = '{:016b}'.format(int(mask))
if name.lower() == 'all':
config = {
'EIOAnalog': mask[0:8],
'FIOAnalog': mask[8::]
}
else:
if not self._check_port(name, "AIN"):
raise Exception('Invalid flexible IO port name.')
config = int(mask[self._numports - self._get_AINnumber(name) - 1])
return config
[docs] def reset_config(self):
"""
Reset the flexible IO ports to default all digital.
>>> lju3.reset_config()
.. note::
On the LabJack U3-HV the first 4 ports ``'AIN0'`` to ``'AIN3'``
are always analog.
"""
ePut(self._commhandle.handle, LJ_ioPIN_CONFIGURATION_RESET, 0, 0, 0)
[docs] def set_digital(self, name, state):
"""
Write the digital state to an output port.
It also sets the port direction to output.
:param name: The port name to set the state.
:type name: str
:param state: The digital state `0 = Low`, `1 = High`.
:type state: int
Set port ``'FIO4'`` output to high and port ``'FIO5'`` to low:
>>> lju3.set_digital('FIO4', 1)
>>> lju3.set_digital('FIO5', 0)
"""
# Checking for valid inputs
if not self._check_port(name, "AIN"):
raise Exception('Invalid flexible IO port name.')
if (state != 0) and (state != 1):
raise Exception('Port state must be either 1 or 0')
# Setting port state by port number
ePut(self._commhandle.handle, LJ_ioPUT_DIGITAL_BIT,
self._get_AINnumber(name), state, 0)
[docs] def get_digital(self, name):
"""
Read the digital state from an input port.
It also sets the port direction to input.
:param name: The port name to get the state.
:type name: str
:returns: The state of the digital port. `0 = Low`, `1 = High`.
:rtype: int
Get port ``'FIO6'`` input state:
>>> lju3.get_digital('FIO6')
"""
# Checking for valid inputs
if not self._check_port(name, "AIN"):
raise Exception('Invalid flexible IO port name.')
# Getting port state by port number
state = int(eGet(self._commhandle.handle, LJ_ioGET_DIGITAL_BIT,
self._get_AINnumber(name), 0, 0))
return state
[docs] def get_bitdir(self, name):
"""
Read the direction of the digital port.
:param name: The port name to get the direction.
:type name: str
:returns: The direction of the digital port. `Input` or `Output`.
:rtype: str
Get the direction of port ``'FIO6'``:
>>> lju3.get_bitdir('FIO6')
"""
# Checking for valid input port names
if not self._check_port(name, "AIN"):
raise Exception('Invalid analog input port name.')
# Getting digital port bit direction
value = eGet(self._commhandle.handle, LJ_ioGET_DIGITAL_BIT_DIR,
self._get_AINnumber(name), 0, 0)
if value == 0:
bitdir = 'input'
else:
bitdir = 'output'
return bitdir
[docs] def set_analog(self, name, value):
"""
Set analog output voltage.
:param name: The port name to set the output voltage.
Available ports are ``'DAC0'`` and ``'DAC1'``.
:type name: str
:param value: The output voltage between ``0`` and ``5`` V.
:type value: float
Set port ``'DAC1'`` output voltage to ``2.2`` V:
>>> lju3.set_analog('DAC1', 2.2)
"""
# Checking for valid input port names
if not self._check_port(name, "DAC"):
raise Exception('Invalid analog output port name.')
# Getting corresponding port number
DACnumber = self._get_DACnumber(name)
# Limiting analog values
if value > 5: value = 5
if value < 0: value = 0
# Setting analog port output voltage
ePut(self._commhandle.handle, LJ_ioPUT_DAC, DACnumber, value, 0)
[docs] def get_analog(self, namepos, *args):
"""
Get analog input voltage.
:param name: The positive port name to get the voltage.
:type name: str
:param args: Can be one of the three options:
* ``'single-ended'`` (default value)
* The negative port name to get a differential voltage.
* ``'special'`` to get increased range on the voltage reading.
:type args: str
:returns: The input voltage value.
* On a U3-HV, the range for ports ``'AIN0'`` to ``'AIN3'`` is +/-10 V
* On a U3-HV, ``'special'`` enables a range of -10 to +20 V
* `FIO` ports have a range of +/- 2.4 V
* `FIO` ports using ``'special'`` have a range of 0 to 3.6 V
:rtype: float
Get single-ended voltage on port ``'FIO2'``:
>>> lju3.get_analog('FIO2')
Get differential voltage betweens ports ``'AIN0'`` and ``'AIN1'``:
>>> lju3.get_analog('AIN0', 'AIN1')
Get special range voltage on port ``'FIO3'``:
>>> lju3.get_analog('FIO3', 'special')
"""
# Checking for differential measurement
if len(args) > 0:
nameneg = args[0]
else:
nameneg = 'single-ended'
# Checking for valid input port names
if not self._check_port([namepos, nameneg], "AIN"):
raise Exception('Invalid analog input port name.')
# Getting corresponding port numbers
AINpos = self._get_AINnumber(namepos)
if nameneg.lower() == 'single-ended':
AINneg = 31
elif nameneg.lower() == 'special':
AINneg = 32
else:
AINneg = self._get_AINnumber(nameneg)
# Getting analog input voltage
value = eGet(self._commhandle.handle,
LJ_ioGET_AIN_DIFF, AINpos, 0, AINneg)
return value
# STREAMING METHODS
[docs] def set_stream(self, names, scanrate=50000, readrate=0.5):
"""
Set and start data streaming.
:param name: The port name (or list of names) to be streamed.
:type name: str, list(str)
:param scanrate: The scan rate (Hz) of the data streaming.
The default (and maximum) value is ``50000`` Hz. The effective scan
frequency of each port is the scan rate divided by the number of
scanned ports.
:type scanrate: int
:param readrate:
The rate in seconds at which blocks of data are retrieved from the
data buffer. The default value is ``0.5`` seconds.
:type readrate: float
Set data streaming on port ``'AIN0'`` at ``25000`` Hz, every ``0.5`` s:
>>> lju3.set_stream('AIN0', scanrate=25000, readrate=0.5)
Set data streaming on ports ``'AIN0'`` and ``'AIN1'`` at ``50000`` Hz,
every ``1.0`` s:
>>> lju3.set_stream(['AIN0', 'AIN1'], scanrate=50000, readrate=1.0)
.. note::
Only analog input ports ``'AIN0'`` to ``'AIN3'`` can be
streamed. Hence, a Labjack U3-HV has to be used. While it's
possible to stream digital ports, that hasn't been implemented
in this release.
"""
# Assigning streamed data block read-in rate (s)
self._streamreadrate = readrate
# Assigning buffer size as 2 times block length (s)
self._streambuffersize = 2*self._streamreadrate
# Assigning data block scan rate (Hz)
self._scanrate = scanrate
# Checking for valid input port names
if not self._check_port(names, "AIN"):
raise Exception('Invalid input port name.')
# Getting port numbers
if type(names) != list:
names = [names]
portnum = []
for name in names:
portnum.append(self._get_AINnumber(name))
# Assigning number of streaming ports
self._streamnumchannels = len(portnum)
# Updating scan frequency per port (Hz)
self._streamscanfreq = int(self._scanrate/self._streamnumchannels)
# Clearing streaming ports
ePut(self._commhandle.handle, LJ_ioCLEAR_STREAM_CHANNELS, 0, 0, 0)
# Adding ports to scan list
for num in portnum:
ePut(self._commhandle.handle, LJ_ioADD_STREAM_CHANNEL, num, 0, 0)
# Assigning scanning frequency
ePut(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chSTREAM_SCAN_FREQUENCY, self._streamscanfreq, 0)
# Assigning buffer size
ePut(self._commhandle.handle, LJ_ioPUT_CONFIG, LJ_chSTREAM_BUFFER_SIZE,
self._scanrate*self._streambuffersize, 0)
# Configuring reads to retrieve whatever data is available without waiting
ePut(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chSTREAM_WAIT_MODE, LJ_swNONE, 0)
# Starting streaming
ePut(self._commhandle.handle, LJ_ioSTART_STREAM, 0, 0, 0)
[docs] def stop_stream(self):
"""
Stop data streaming.
>>> lju3.stop_stream()
"""
ePut(self._commhandle.handle, LJ_ioSTOP_STREAM, 0, 0, 0)
[docs] def get_stream(self):
"""
Get streaming data block.
:returns: 5-tuple
* dt
The sampling period (s) between each data point.
* data
The numpy `m-by-n` array containing the streamed data where
`m` is the number of samples per port in the block and `n`
is the number of ports defined in `set_stream`
* numscans
The actual number of scans per port in the data block.
* commbacklog
The communication backlog in % (increasing values indicate that
the computer cannot keep up with the data download from the U3
driver)
* devbacklog
The U3 device backlog in % (increasing values indicate that the
device cannot keep up with the data streaming - usually not
the case)
:rtype: (float, ndarray, int, float, float)
Retrieve scan period, data, and scan info:
>>> dt, datablock, numscans, commbacklog, U3backlog = lju3.get_stream()
"""
# Defining initial number of samples per channnel
# (double the size for safety)
numscans = 2 * self._streamreadrate * self._streamscanfreq
# Preallocating output array
datasamples = np.zeros(int(numscans)*self._streamnumchannels)
numscansactual, datalong = self._eGetArray(
self._commhandle.handle, LJ_ioGET_STREAM_DATA,
LJ_chALL_CHANNELS, numscans, datasamples)
# Separating ports
numscansactual = int(numscansactual)
nrow = numscansactual
ncol = self._streamnumchannels
data = np.array(datalong)
data = data[0:nrow*ncol].reshape(nrow, ncol)
# Calculating sample period (s)
dt = 1/self._streamscanfreq
# Calculating communication backlog (%)
commbacklog = eGet(self._commhandle.handle,
LJ_ioGET_CONFIG, LJ_chSTREAM_BACKLOG_COMM, 0, 0)
commbacklog = 100 * commbacklog/numscansactual
# Calculating LabJack device backlog (%)
devbacklog = eGet(self._commhandle.handle,
LJ_ioGET_CONFIG, LJ_chSTREAM_BACKLOG_UD, 0, 0)
devbacklog = 100 * devbacklog/numscansactual
# Returning streamed data block parameters
return dt, data, numscansactual, commbacklog, devbacklog
# PWM METHODS
[docs] def set_pwm(self, pwmnum=1, dirport1=None, dirport2=None, frequency=366):
"""
Configure PWM output.
:param pwmnum: The number of PWM output signals.
``1`` or ``2`` PWMs can be used. For one PWM, the output port is
``'FIO4'``. For two PWMs, the output ports are ``'FIO4'`` and
``'FIO5'``.
:type pwmnum: int
:param dirport1: The type of ports that control the PWM `direction`
for electric motor control. There are three options:
* ``None`` - Default value (no direction ports are used)
* ``'DAC'`` - Uses analog ports ``'DAC0'`` and ``'DAC1'``
* ``'DIO'`` - Uses digital ports ``'FIO6'`` and ``'FIO7'``
:type dirport1: None, str
:param dirport2: Same as `dirport1`.
It's used when two PWM outputs are enabled. The ``'DAC'`` option
can only be used for one set of direction ports, unless the two
motors are running synchronously. For the ``'DIO'`` option,
digital ports ``'EIO0'`` and ``'EIO1'`` are used.
:type dirport2: None, str
:param frequency: The PWM signal frequency in Hz.
In the case of two PWMs, both will have the same frequency. Valid
values are ``183``, ``366`` or ``732``.
:type frequency: int
Set 1 PWM for motor control on ``'FIO4'`` with direction ports on
``'DAC0'`` and ``'DAC1'``. The PWM frequency is the default ``366`` Hz:
>>> lju3.set_pwm(dirport1='DAC')
Set 2 PWMs on ports ``'FIO4'`` and ``'FIO5'`` with a frequency of
``183`` Hz:
>>> lju3.set_pwm(pwmnum=2, frequency=183)
Set 2 PWMs for motor control on ports ``'FIO4'`` and ``'FIO5'``, using
the digital ports ``'FIO6'`` and ``'FIO7'`` for motor 1 direction, and
``'EIO0'`` and ``'EIO1'`` for motor 2 direction. The PWM frequency is
``732`` Hz:
>>> lju3.set_pwm(pwmnum=2, dirport1='DIO', dirport2='DIO', frequency=732)
.. note::
When using digital ports, a 10 kOhm resistor has to be connected from
the LabJack `VS` port to each one of the `DIO` ports to ensure true
`high` and `low` states.
"""
# Defining PWM frequency divisors and checking input
pwmfreq = {
'183': 4,
'366': 2,
'732': 1
}
if str(frequency) not in list(pwmfreq.keys()):
raise Exception(
"Valid PWM frequencies are 183, 366, and 732 Hz.")
# Checking number of PWM outputs and direction ports
dirport = [dirport1, dirport2]
pwmname = ['FIO4']
pwmdir = [None]
self._pwmtype = [1]
if dirport[0] == 'DAC':
pwmdir[0] = ['DAC0', 'DAC1']
self._pwmtype[0] = 0
elif dirport[0] == 'DIO':
pwmdir[0] = ['FIO6', 'FIO7']
if pwmnum == 2:
self._pwmtype.append(1)
pwmname.append('FIO5')
if dirport[1] is None:
pwmdir.append(None)
if dirport[1] == 'DAC':
pwmdir.append(['DAC0', 'DAC1'])
self._pwmtype[1] = 0
elif dirport[1] == 'DIO':
pwmdir.append(['EIO0', 'EIO1'])
# Assigning PWM attributes
self._pwmnum = pwmnum
self._pwmname = pwmname
self._pwmdir = pwmdir
self._pwmfreq = frequency
# Assinging frequency divisor
divisor = pwmfreq[str(frequency)]
# Assigning PWM pin offset
# (always 4 since the timers always start on FIO4)
pinoffset = 4
# Resetting pin configuration
ePut(self._commhandle.handle, LJ_ioPIN_CONFIGURATION_RESET, 0, 0, 0)
# Setting the pin offset for the timers
AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chTIMER_COUNTER_PIN_OFFSET, pinoffset, 0, 0)
# Configuring the timer clock to 48 MHz
AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chTIMER_CLOCK_BASE, LJ_tc48MHZ_DIV, 0, 0)
# Setting clock Divisor
AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chTIMER_CLOCK_DIVISOR, divisor, 0, 0)
# Enabling 1 or 2 timers
AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chNUMBER_TIMERS_ENABLED, pwmnum, 0, 0)
for num in range(pwmnum):
# Setting timer to 16 bits
# Frequency = (48MHz/Divisor ) / 2^16 Hz
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_MODE,
num, LJ_tmPWM16, 0, 0)
# Setting timer duty cycle to 0%
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
num, 0, 0, 0)
# Sending command sequence
GoOne(self._commhandle.handle)
[docs] def set_dutycycle(self, value1=None, value2=None, brake1=False, brake2=False):
"""
Set PWM duty cycle value.
:param value1: The PWM 1 duty cycle percent value between ``-100``
and ``100``.
:type value1: float
:param value2: The PWM 2 duty cycle percent value between ``-100``
and ``100``.
:type value2: float
:param brake1: The motor 1 brake option used when dutycycle is zero.
Brake is applied when ``True``. Motor is floating when ``False``.
:type brake1: bool
:param brake2: The motor 2 brake option used when dutycycle is zero.
Brake is applied when ``True``. Motor is floating when ``False``.
:type brake2: bool
Set duty cycle to ``50`` % on PWM 1:
>>> lju3.set_dutycycle(value1=50)
Set duty cycle to ``25`` % (reverse rotation) on PWM 2:
>>> lju3.set_dutycycle(value2=-25)
Set duty cycle to ``20`` % and ``40`` % on PWMs 1 and 2:
>>> lju3.set_dutycycle(value1=20, value2=40)
Stop motor 2 and apply brake:
>>> lju3.set_dutycycle(value2=0, brake2=True)
.. note::
1. Avoid suddenly switching the direction of rotation to avoid damaging the motor.
2. You can use the brake option True to hold the motor in position.
"""
values = [value1, value2]
brakes = [brake1, brake2]
for pwmnum, (value, brake, pwmdir, pwmtype) in enumerate(zip(
values, brakes, self._pwmdir, self._pwmtype)):
if value is not None:
# Applying bounds to inputs
if value > 100:
value = 100
if value < -100:
value = -100
# Applying PWM direction
if pwmdir:
if value > 0:
# Forward rotation
if pwmtype == 0:
self.set_analog(pwmdir[0], 4.5)
self.set_analog(pwmdir[1], 0)
else:
self.set_digital(pwmdir[0], 0)
self.get_digital(pwmdir[1])
elif value < 0:
# Reverse rotation
if pwmtype == 0:
self.set_analog(pwmdir[0], 0)
self.set_analog(pwmdir[1], 4.5)
else:
self.get_digital(pwmdir[0])
self.set_digital(pwmdir[1], 0)
elif value == 0:
# Brake stop
if brake:
if pwmtype == 0:
self.set_analog(pwmdir[0], 0)
self.set_analog(pwmdir[1], 0)
else:
self.set_digital(pwmdir[0], 0)
self.set_digital(pwmdir[1], 0)
else:
if pwmtype == 0:
self.set_analog(pwmdir[0], 4.5)
self.set_analog(pwmdir[1], 4.5)
else:
self.get_digital(pwmdir[0])
self.get_digital(pwmdir[1])
# Calculating duty cycle
dutycycle = np.ceil(65535*(1-np.abs(value)/100))
# Setting timer duty cycle to desired value
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
pwmnum, dutycycle, 0, 0)
# Sending command sequence
GoOne(self._commhandle.handle)
# QUADRATURE ENCODER METHODS
[docs] def set_quadrature(self, zphase1=False):
"""
Configure quadrature encoder input on ports ``'FIO4'`` and ``'FIO5'``.
:param zphase1: The logic value indicating if a `Z` phase reference
pulse is used on port ``'FIO6'``.
:type zphase1: bool
Set ports ``'FIO4'`` and ``'FIO5'`` for encoder phase `A` and `B`
signals:
>>> lju3.set_quadrature()
Set ports ``'FIO4'`` and ``'FIO5'`` for encoder phase `A` and `B`
signals and port ``'FIO6'`` for the reference `Z` phase:
>>> lju3.set_quadrature(zphase1=True)
"""
# Checking input arguments
quadnameAB = ['FIO4', 'FIO5']
portnumZ = 0
if zphase1:
portnumZ = 32768 + self._get_AINnumber('FIO6')
# Assigning A-B port names
self._zphase1 = zphase1
self._quadnameAB = quadnameAB
# Assigning quadrature port pin offset
pinoffset = self._get_AINnumber(quadnameAB[0])
# Resetting pin configuration
ePut(self._commhandle.handle, LJ_ioPIN_CONFIGURATION_RESET, 0, 0, 0)
# Setting the pin offset for the timers
AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chTIMER_COUNTER_PIN_OFFSET, pinoffset, 0, 0)
# Configuring the timer clock to 48 MHz
AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chTIMER_CLOCK_BASE, LJ_tc48MHZ, 0, 0)
# Enabling 2 timers
AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chNUMBER_TIMERS_ENABLED, 2, 0, 0)
# Setting quadrature mode
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_MODE,
0, LJ_tmQUAD, 0, 0)
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_MODE,
1, LJ_tmQUAD, 0, 0)
# Setting timer values to add Z port (or reset in case there's no Z)
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
0, portnumZ, 0, 0)
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
1, portnumZ, 0, 0)
# Sending command sequence
GoOne(self._commhandle.handle)
[docs] def get_counter(self):
"""
Get current quadrature counter value.
:returns: The counter value.
:rtype: int
>>> lju3.get_counter()
.. note::
Because the qudrature counter counts rising and falling edges
of phases `A` and `B`, a 1024 pulse/rev encoder will generate 4096
counts for a full shaft turn.
"""
value = int(eGet(self._commhandle.handle, LJ_ioGET_TIMER, 0, 0, 0))
return value
[docs] def reset_counter(self):
"""
Reset quadrature counter value.
>>> lju3.reset_counter()
.. note::
The count is only reset when a `Z` phase isn't being used.
"""
if not self._zphase1:
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
0, 0, 0, 0)
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
1, 0, 0, 0)
# Sending command sequence
GoOne(self._commhandle.handle)
# OTHER METHODS
[docs] def get_labjacktemp(self, unit='C'):
"""
Get ambient temperature from LabJack's internal sensor.
:param unit: The temperature measurement unit.
Valid values are ``'C'`` or ``'F'``. Default unit is ``'C'``.
:type unit: str
:returns: The internal sensor temperature reading.
:rtype: float
Get temperature reading in `Celsius`:
>>> lju3.get_labjacktemp()
Get temperature reading in `Fahrenheit`:
>>> lju3.get_labjacktemp(unit='F')
"""
tempabs = eGet(self._commhandle.handle, LJ_ioGET_AIN, 30, 0, 0)
if unit == 'C':
temp = tempabs - 273.15
elif unit == 'F':
temp = 9/5*(tempabs-273.15) + 32
else:
raise Exception(
"Temperature units must be either 'degC' or 'degF'.")
return temp
# HELPER METHODS (PRIVATE)
def _eGetArray(self, Handle, IOType, Port, pValue, x1):
"""
Perform one call to the LabJack Device returning a data array.
This method was created to complement the eGet() function in
LabJackPyhon. It's used primarily in data streaming and like all
the other C library functions, it is limited to a Windows platform.
"""
pv = ctypes.c_double(pValue)
xv = (ctypes.c_double * len(x1))()
ec = self._staticlib.eGet_DblArray(
Handle, IOType, Port, ctypes.byref(pv), ctypes.byref(xv))
if ec != 0:
raise LabJackException(ec)
return pv.value, xv
def _assign_info(self, info):
# Assigning info collected from LabJack handle to private attributes
self._type = info['DeviceName']
self._serialnum = info['SerialNumber']
self._connection = 'USB'
self._hardware = info['HardwareVersion']
self._firmware = info['FirmwareVersion']
def _assign_ports(self):
"""
Creates lists with valid LabJack U3 port names
"""
# Assigning analog output port names
self._portDAC = [
'DAC0', 'DAC1']
# Assigning analog / FIO inout port names
self._portAIN = [
'AIN0', 'AIN1', 'AIN2', 'AIN3', 'FIO4', 'FIO5', 'FIO6', 'FIO7',
'EIO0', 'EIO1', 'EIO2', 'EIO3', 'EIO4', 'EIO5', 'EIO6', 'EIO7']
self._portAINalt = [
'FIO0', 'FIO1', 'FIO2', 'FIO3', 'AIN4', 'AIN5', 'AIN6', 'AIN7',
'AIN8', 'AIN9', 'AIN10', 'AIN11', 'AIN12', 'AIN13', 'AIN14', 'AIN15']
self._portAINsp = ['SINGLE-ENDED', 'SPECIAL']
def _check_port(self, namelist, porttype, *args):
"""
Checks if the port NAMES belong to the possible values in
the correposnding PORTTYPE list of valid names. If all NAMES
are valid then FLAGPORT = 1, otherwise FLAGPORT = 0
A user defined list can be passed with porttype = 'USER'
through an additional argument VARARGIN
"""
# Making sure inputs are lists
if type(namelist) != list:
namelist = [namelist]
# Assining valid names list based on port type
if porttype == "DAC":
validnames = self._portDAC
elif porttype == "AIN":
validnames = self._portAIN + self._portAINalt + self._portAINsp
elif porttype == "QUAD":
validnames = [
'FIO4', 'FIO5', 'FIO6', 'FIO7',
'AIN4', 'AIN5', 'AIN6', 'AIN7']
elif porttype == "USER":
validnames = args[0]
# Returns true if all names in namelist are validnames
return all([name.upper() in validnames for name in namelist])
def _get_DACnumber(self, name):
if name.upper() == 'DAC0':
DACnumber = 0
elif name.upper() == 'DAC1':
DACnumber = 1
return DACnumber
def _get_AINnumber(self, name):
try:
AINnumber = self._portAIN.index(name)
except ValueError:
AINnumber = self._portAINalt.index(name)
return AINnumber
[docs]class LabJackU6:
"""
The class to represent the LabJack U6
:param serialnum: The device serial number.
:type serialnum: int
Ports that are made available with this class are:
* Analog Output (0 to 5V) : ``'DAC0'`` , ``'DAC1'``
* Analog Input (+/-10V) : ``'AIN0'`` , ``'AIN1'``, ... , ``'AIN13'``
* Digital I/O : ``'FIO0'`` , ``'FIO1'``, ... , ``'FIO7'``
* Digital I/O : ``'EIO0'`` , ``'EIO1'``, ... , ``'EIO7'``
Device-specific methods:
* `get_bitdir` - Gets digital port bit direction
* `set_range` - Sets analog input voltage range
* `set_pwm_quad` - Sets simultaneous PWM output and encoder input
Connect to the first found U6:
>>> from labjack_unified.devices import LabJackU6
>>> lju6 = LabJackU6()
>>> lju6.display_info()
>>> lju6.close()
You can also connect to a specific device using its serial number:
>>> lju6 = LabJackU6(360012345)
"""
# CONSTRUCTOR METHODS
def __init__(self, serialnum=None):
"""
Class constructor
"""
self._staticlib = _loadLibrary()
# Attempting to open the device
try:
if serialnum:
# Opens LabJack with specific serial number
self._commhandle = u6.U6(autoOpen=False)
self._commhandle.open(serial=serialnum)
else:
# Opens first available LabJack
self._commhandle = u6.U6(autoOpen=False)
self._commhandle.open()
except LabJackException as error:
print(error)
else:
self._numports = 34
self._assign_info(self._commhandle.configU6())
self._assign_ports()
self._reset_config()
self.set_range('all', 10)
print('Opened LabJack', self._serialnum)
[docs] def close(self):
"""
Close the U6 device connection.
>>> lju6.close()
"""
self._staticlib.Close(self._commhandle.handle)
print('Closed LabJack', self._serialnum)
[docs] def display_info(self):
"""
Display a summary with the U6 device information.
"""
print('____________________________________________________________')
print('Device Name........', self._type)
print('Serial Number......', self._serialnum)
print('Hardware Version...', self._hardware)
print('Firmware Version...', self._firmware)
print('Connection Type....', self._connection)
print('____________________________________________________________')
# I/O METHODS
[docs] def set_digital(self, name, state):
"""
Write the digital state to an output port.
It also sets the port direction to output.
:param name: The port name to set the state.
:type name: str
:param state: The digital state `0 = Low`, `1 = High`.
:type state: int
Set port ``'FIO0'`` output to high and port ``'FIO1'`` to low:
>>> lju6.set_digital('FIO0', 1)
>>> lju6.set_digital('FIO1', 0)
"""
# Checking for valid inputs
if not self._check_port(name, "DIO"):
raise Exception('Invalid digital IO port name.')
if (state != 0) and (state != 1):
raise Exception('Port state must be either 1 or 0')
# Setting port state by port number
ePut(self._commhandle.handle, LJ_ioPUT_DIGITAL_BIT,
self._get_DIOnumber(name), state, 0)
[docs] def get_digital(self, name):
"""
Read the digital state from an input port.
It also sets the port direction to input.
:param name: The port name to get the state.
:type name: str
:returns: The state of the digital port. `0 = Low`, `1 = High`.
:rtype: int
Get port ``'FIO2'`` input state:
>>> lju6.get_digital('FIO2')
"""
# Checking for valid inputs
if not self._check_port(name, "DIO"):
raise Exception('Invalid digital IO port name.')
# Getting port state by port number
state = int(eGet(self._commhandle.handle, LJ_ioGET_DIGITAL_BIT,
self._get_DIOnumber(name), 0, 0))
return state
[docs] def get_bitdir(self, name):
"""
Read the direction of the digital port.
:param name: The port name to get the direction.
:type name: str
:returns: The direction of the digital port. `Input` or `Output`.
:rtype: str
Get the direction of port ``'FIO2'``:
>>> lju6.get_bitdir('FIO2')
"""
# Checking for valid input port names
if not self._check_port(name, "DIO"):
raise Exception('Invalid digital I/O port name.')
# Getting digital port bit direction
value = eGet(self._commhandle.handle, LJ_ioGET_DIGITAL_BIT_DIR,
self._get_DIOnumber(name), 0, 0)
if value == 0:
bitdir = 'input'
else:
bitdir = 'output'
return bitdir
[docs] def set_analog(self, name, value):
"""
Set analog output voltage.
:param name: The port name to set the output voltage.
Available ports are ``'DAC0'`` and ``'DAC1'``.
:type name: str
:param value: The output voltage between ``0`` and ``5`` V.
:type value: float
Set port ``'DAC1'`` output voltage to ``2.2`` V:
>>> lju6.set_analog('DAC1', 2.2)
"""
# Checking for valid input port names
if not self._check_port(name, "DAC"):
raise Exception('Invalid analog output port name.')
# Getting corresponding port number
DACnumber = self._get_DACnumber(name)
# Limiting analog values
if value > 5: value = 5
if value < 0: value = 0
# Setting analog port output voltage
ePut(self._commhandle.handle, LJ_ioPUT_DAC, DACnumber, value, 0)
[docs] def get_analog(self, name, mode='single-ended'):
"""
Get analog input voltage.
:param name: The positive port name to get the voltage.
:type name: str
:param mode: Can be one of the two options:
* ``'single-ended'`` (default value)
* ``'differential'`` sets the ports to get a differential voltage.
:type mode: str
:returns: The input voltage value.
:rtype: float
Get ``'single-ended'`` voltage on port ``'AIN3'``:
>>> lju6.get_analog('AIN3')
Get ``'differential'`` voltage betweens ports ``'AIN2'`` and
``'AIN3'``:
>>> lju6.get_analog('AIN2', 'differential')
.. note::
Differential reading uses two consecutive even-odd ports.
Valid ports for differential reading are AIN0/2/4/6/8/10/12.
"""
# Checking for valid inputs
if mode.lower() not in ['single-ended', 'differential']:
raise Exception(
"Valid reference types are 'Single-Ended' or 'Differential'.")
# Assigning range values
if not self._check_port(name, "AIN"):
raise Exception('Invalid analog input port name(s).')
# Getting corresponding port numbers
AINpos = self._get_AINnumber(name)
#
if mode.lower() == 'single-ended':
AINneg = 199
else:
flagport, _ = self._check_portdiff(name)
if flagport:
AINneg = AINpos + 1
else:
raise Exception('Valid analog ports are AIN0/2/4/6/8/10/12.')
# Getting analog input voltage
value = eGet(self._commhandle.handle,
LJ_ioGET_AIN_DIFF, AINpos, 0, AINneg)
return value
# STREAMING METHODS
[docs] def set_stream(self, names, scanrate=50000, readrate=0.5):
"""
Set and start data streaming.
:param name: The port name (or list of names) to be streamed.
:type name: str, list(str)
:param scanrate: The scan rate (Hz) of the data streaming.
The default (and maximum) value is ``50000`` Hz. The effective scan
frequency of each port is the scan rate divided by the number of
scanned ports.
:type scanrate: int
:param readrate:
The rate in seconds at which blocks of data are retrieved from the
data buffer. The default value is ``0.5`` seconds.
:type readrate: float
Set data streaming on port ``'AIN0'`` at ``25000`` Hz, every ``0.5`` s:
>>> lju6.set_stream('AIN0', scanrate=25000, readrate=0.5)
Set data streaming on ports ``'AIN0'`` and ``'AIN1'`` at ``50000`` Hz,
every ``1.0`` s:
>>> lju6.set_stream(['AIN0', 'AIN1'], scanrate=50000, readrate=1.0)
.. note::
Only analog input ports ``'AIN0'`` to ``'AIN13'`` can be
streamed. While it's possible to stream digital ports,
that hasn't been implemented in this release.
"""
# Assigning streamed data block read-in rate (s)
self._streamreadrate = readrate
# Assigning buffer size as 2 times block length (s)
self._streambuffersize = 2*self._streamreadrate
# Assigning data block scan rate (Hz)
self._scanrate = scanrate
# Checking for valid input port names
if not self._check_port(names, "AIN"):
raise Exception('Invalid input port name.')
# Getting port numbers
if type(names) != list:
names = [names]
portnum = []
for name in names:
portnum.append(self._get_AINnumber(name))
# Assigning number of streaming ports
self._streamnumchannels = len(portnum)
# Updating scan frequency per port (Hz)
self._streamscanfreq = int(self._scanrate/self._streamnumchannels)
# Clearing streaming ports
ePut(self._commhandle.handle, LJ_ioCLEAR_STREAM_CHANNELS, 0, 0, 0)
# Adding ports to scan list
for num in portnum:
ePut(self._commhandle.handle, LJ_ioADD_STREAM_CHANNEL, num, 0, 0)
# Assigning scanning frequency
ePut(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chSTREAM_SCAN_FREQUENCY, self._streamscanfreq, 0)
# Assigning buffer size
ePut(self._commhandle.handle, LJ_ioPUT_CONFIG, LJ_chSTREAM_BUFFER_SIZE,
self._scanrate*self._streambuffersize, 0)
# Configuring reads to retrieve whatever data is available without waiting
ePut(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chSTREAM_WAIT_MODE, LJ_swNONE, 0)
# Starting streaming
ePut(self._commhandle.handle, LJ_ioSTART_STREAM, 0, 0, 0)
[docs] def stop_stream(self):
"""
Stop data streaming.
>>> lju6.stop_stream()
"""
ePut(self._commhandle.handle, LJ_ioSTOP_STREAM, 0, 0, 0)
[docs] def get_stream(self):
"""
Get streaming data block.
:returns: 5-tuple
* dt
The sampling period (s) between each data point.
* data
The numpy `m-by-n` array containing the streamed data where
`m` is the number of samples per port in the block and `n`
is the number of ports defined in `set_stream`
* numscans
The actual number of scans per port in the data block.
* commbacklog
The communication backlog in % (increasing values indicate that
the computer cannot keep up with the data download from the U6
driver)
* devbacklog
The U6 device backlog in % (increasing values indicate that the
device cannot keep up with the data streaming - usually not
the case)
:rtype: (float, ndarray, int, float, float)
Retrieve scan period, data, and scan info:
>>> dt, datablock, numscans, commbacklog, U3backlog = lju6.get_stream()
"""
# Defining initial number of samples per channnel
# (double the size for safety)
numscans = 2 * self._streamreadrate * self._streamscanfreq
# Preallocating output array
datasamples = np.zeros(int(numscans)*self._streamnumchannels)
numscansactual, datalong = self._eGetArray(
self._commhandle.handle, LJ_ioGET_STREAM_DATA,
LJ_chALL_CHANNELS, numscans, datasamples)
# Separating ports
numscansactual = int(numscansactual)
nrow = numscansactual
ncol = self._streamnumchannels
data = np.array(datalong)
data = data[0:nrow*ncol].reshape(nrow, ncol)
# Calculating sample period (s)
dt = 1/self._streamscanfreq
# Calculating communication backlog (%)
commbacklog = eGet(self._commhandle.handle,
LJ_ioGET_CONFIG, LJ_chSTREAM_BACKLOG_COMM, 0, 0)
commbacklog = 100 * commbacklog/numscansactual
# Calculating LabJack device backlog (%)
devbacklog = eGet(self._commhandle.handle,
LJ_ioGET_CONFIG, LJ_chSTREAM_BACKLOG_UD, 0, 0)
devbacklog = 100 * devbacklog/numscansactual
# Returning streamed data block parameters
return dt, data, numscansactual, commbacklog, devbacklog
# PWM METHODS
[docs] def set_pwm(self, pwmnum=1, dirport1=None, dirport2=None, frequency=366):
"""
Configure PWM output.
:param pwmnum: The number of PWM output signals.
``1`` or ``2`` PWMs can be used. For one PWM, the output port is
``'FIO0'``. For two PWMs, the output ports are ``'FIO0'`` and
``'FIO1'``.
:type pwmnum: int
:param dirport1: The type of ports that control the PWM `direction`
for electric motor control. There are three options:
* ``None`` - Default value (no direction ports are used)
* ``'DAC'`` - Uses analog ports ``'DAC0'`` and ``'DAC1'``
* ``'DIO'`` - Uses digital ports ``'FIO2'`` and ``'FIO3'``
:type dirport1: None, str
:param dirport2: Same as `dirport1`.
It's used when two PWM outputs are enabled. The ``'DAC'`` option
can only be used for one set of direction ports, unless the two
motors are running synchronously. For the ``'DIO'`` option,
digital ports ``'FIO4'`` and ``'FIO5'`` are used.
:type dirport2: None, str
:param frequency: The PWM signal frequency in Hz.
In the case of two PWMs, both will have the same frequency. Valid
values are ``183``, ``366`` or ``732``.
:type frequency: int
Set 1 PWM for motor control on ``'FIO0'`` with direction ports on
``'DAC0'`` and ``'DAC1'``. The PWM frequency is the default ``366`` Hz:
>>> lju6.set_pwm(dirport1='DAC')
Set 2 PWMs on ports ``'FIO0'`` and ``'FIO1'`` with a frequency of
``183`` Hz:
>>> lju6.set_pwm(pwmnum=2, frequency=183)
Set 2 PWMs for motor control on ports ``'FIO0'`` and ``'FIO1'``, using
the digital ports ``'FIO2'`` and ``'FIO3'`` for motor 1 direction, and
``'FIO4'`` and ``'FIO5'`` for motor 2 direction. The PWM frequency is
``732`` Hz:
>>> lju6.set_pwm(pwmnum=2, dirport1='DIO', dirport2='DIO', frequency=732)
.. note::
When using digital ports, a 10 kOhm resistor has to be connected from
the LabJack `VS` port to each one of the `DIO` ports to ensure true
`high` and `low` states.
"""
# Setting flag for simultaneous PWN and quadrature setup
self._pwmquad = False
# Defining PWM frequency divisors and checking input
pwmfreq = {
'183': 4,
'366': 2,
'732': 1
}
if str(frequency) not in list(pwmfreq.keys()):
raise Exception(
"Valid PWM frequencies are 183, 366, and 732 Hz.")
# Checking number of PWM outputs and direction ports
dirport = [dirport1, dirport2]
pwmname = ['FIO0']
pwmdir = [None]
self._pwmtype = [1]
if dirport[0] == 'DAC':
pwmdir[0] = ['DAC0', 'DAC1']
self._pwmtype[0] = 0
elif dirport[0] == 'DIO':
pwmdir[0] = ['FIO2', 'FIO3']
if pwmnum == 2:
self._pwmtype.append(1)
pwmname.append('FIO1')
if dirport[1] is None:
pwmdir.append(None)
if dirport[1] == 'DAC':
pwmdir.append(['DAC0', 'DAC1'])
self._pwmtype[1] = 0
elif dirport[1] == 'DIO':
pwmdir.append(['FIO4', 'FIO5'])
# Assigning PWM attributes
self._pwmnum = pwmnum
self._pwmname = pwmname
self._pwmdir = pwmdir
self._pwmfreq = frequency
# Assinging frequency divisor
divisor = pwmfreq[str(frequency)]
# Assigning PWM pin offset
# (always 0 since the timers always start on FIO0)
pinoffset = 0
# Resetting pin configuration
ePut(self._commhandle.handle, LJ_ioPIN_CONFIGURATION_RESET, 0, 0, 0)
# Setting the pin offset for the timers
AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chTIMER_COUNTER_PIN_OFFSET, pinoffset, 0, 0)
# Configuring the timer clock to 48 MHz
AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chTIMER_CLOCK_BASE, LJ_tc48MHZ_DIV, 0, 0)
# Setting clock Divisor
AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chTIMER_CLOCK_DIVISOR, divisor, 0, 0)
# Enabling 1 or 2 timers
AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chNUMBER_TIMERS_ENABLED, pwmnum, 0, 0)
for num in range(pwmnum):
# Setting timer to 16 bits
# Frequency = (48MHz/Divisor ) / 2^16 Hz
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_MODE,
num, LJ_tmPWM16, 0, 0)
# Setting timer duty cycle to 0%
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
num, 0, 0, 0)
# Sending command sequence
GoOne(self._commhandle.handle)
[docs] def set_dutycycle(self, value1=None, value2=None, brake1=False, brake2=False):
"""
Set PWM duty cycle value.
:param value1: The PWM 1 duty cycle percent value between ``-100``
and ``100``.
:type value1: float
:param value2: The PWM 2 duty cycle percent value between ``-100``
and ``100``.
:type value2: float
:param brake1: The motor 1 brake option used when dutycycle is zero.
Brake is applied when ``True``. Motor is floating when ``False``.
:type brake1: bool
:param brake2: The motor 2 brake option used when dutycycle is zero.
Brake is applied when ``True``. Motor is floating when ``False``.
:type brake2: bool
Set duty cycle to ``50`` % on PWM 1:
>>> lju6.set_dutycycle(value1=50)
Set duty cycle to ``25`` % (reverse rotation) on PWM 2:
>>> lju6.set_dutycycle(value2=-25)
Set duty cycle to ``20`` % and ``40`` % on PWMs 1 and 2:
>>> lju6.set_dutycycle(value1=20, value2=40)
Stop motor 2 and apply brake:
>>> lju6.set_dutycycle(value2=0, brake2=True)
.. note::
1. Avoid suddenly switching the direction of rotation to avoid damaging the motor.
2. You can use the brake option True to hold the motor in position.
.. note::
If the method `set_pwm_quad` was used to configure both a PWM and
a quadrature encoder, use only `value1` and `brake1` to control
the motor output.
"""
values = [value1, value2]
brakes = [brake1, brake2]
for pwmnum, (value, brake, pwmdir, pwmtype) in enumerate(zip(
values, brakes, self._pwmdir, self._pwmtype)):
if value is not None:
# Applying bounds to inputs
if value > 100:
value = 100
if value < -100:
value = -100
# Applying PWM direction
if pwmdir:
if value > 0:
# Forward rotation
if pwmtype == 0:
self.set_analog(pwmdir[0], 4.5)
self.set_analog(pwmdir[1], 0)
else:
self.set_digital(pwmdir[0], 0)
self.get_digital(pwmdir[1])
elif value < 0:
# Reverse rotation
if pwmtype == 0:
self.set_analog(pwmdir[0], 0)
self.set_analog(pwmdir[1], 4.5)
else:
self.get_digital(pwmdir[0])
self.set_digital(pwmdir[1], 0)
elif value == 0:
# Brake stop
if brake:
if pwmtype == 0:
self.set_analog(pwmdir[0], 0)
self.set_analog(pwmdir[1], 0)
else:
self.set_digital(pwmdir[0], 0)
self.set_digital(pwmdir[1], 0)
else:
if pwmtype == 0:
self.set_analog(pwmdir[0], 4.5)
self.set_analog(pwmdir[1], 4.5)
else:
self.get_digital(pwmdir[0])
self.get_digital(pwmdir[1])
# Calculating duty cycle
dutycycle = np.ceil(65535*(1-np.abs(value)/100))
# Setting timer duty cycle to desired value
if self._pwmquad:
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
2, dutycycle, 0, 0)
else:
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
pwmnum, dutycycle, 0, 0)
# Sending command sequence
GoOne(self._commhandle.handle)
# QUADRATURE ENCODER METHODS
[docs] def set_quadrature(self, quadnum=1, zphase1=False, zphase2=False):
"""
Configure quadrature encoder input.
:param quadnum: The number of quadrature input signals.
``1`` or ``2`` encoders can be used. For one encoder, the input
ports are ``'FIO0'`` and ``'FIO1'``. For two encoders, the input
ports for the second one are ``'FIO2'`` and ``'FIO3'``.
:type quadnum: int
:param zphase1: The logic value indicating if a `Z` phase reference
pulse is used for the first encoder. Port ``'FIO2'`` is used if
`quadnum` = ``1``. Port ``'FIO4'`` is used if `quadnum` = ``2``.
:type zphase1: bool
:param zphase2: The logic value indicating if a `Z` phase reference
pulse is used for the second encoder. Port ``'FIO4'`` is used for
the first encoder and port ``'FIO5'`` is used for the second
encoder. `zphase2` is ignored if `quadnum` = ``1``.
:type zphase1: bool
Set ports ``'FIO0'`` and ``'FIO1'`` for encoder with phase `A` and `B`
signals only:
>>> lju6.set_quadrature()
Set ports ``'FIO0'`` and ``'FIO1'`` for encoder phase `A` and `B`
signals, and port ``'FIO2'`` for the reference `Z` phase:
>>> lju6.set_quadrature(zphase1=True)
Set 2 encoders with `Z` phase. `A` and `B` phases are on ports
``'FIO0'`` and ``'FIO1'`` for encoder 1, and ``'FIO2'`` and ``'FIO3'``
for encoder 2. The `Z` phase ports are respectively ``'FIO4'`` and
``'FIO5'``:
>>> lju6.set_quadrature(quadnum=2, zphase1=True, zphase2=True)
"""
# Setting flag for simultaneous PWN and quadrature setup
self._pwmquad = False
# Checking input arguments
if quadnum == 1:
quadnameAB = ['FIO0', 'FIO1']
portnumZ = [0]
if zphase1:
portnumZ[0] = 32768 + self._get_DIOnumber('FIO2')
elif quadnum == 2:
quadnameAB = ['FIO0', 'FIO1', 'FIO2', 'FIO3']
portnumZ = [0, 0]
if zphase1:
portnumZ[0] = 32768 + self._get_DIOnumber('FIO4')
if zphase2:
portnumZ[1] = 32768 + self._get_DIOnumber('FIO5')
else:
raise Exception('Only 1 or 2 quadrature inputs can be assigned.')
# Assigning quadrature attributes
self._zphase = [zphase1, zphase2]
self._quadnum = quadnum
self._quadnameAB = quadnameAB
# Assigning quadrature port pin offset
pinoffset = 0
# Resetting pin configuration
ePut(self._commhandle.handle, LJ_ioPIN_CONFIGURATION_RESET, 0, 0, 0)
# Setting the pin offset for the timers
AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chTIMER_COUNTER_PIN_OFFSET, pinoffset, 0, 0)
# Configuring the timer clock to 48 MHz
AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chTIMER_CLOCK_BASE, LJ_tc48MHZ, 0, 0)
# Enabling 2 or 4 timers
AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chNUMBER_TIMERS_ENABLED, 2*quadnum, 0, 0)
for num in range(quadnum):
# Setting quadrature mode
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_MODE,
(2*num)+0, LJ_tmQUAD, 0, 0)
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_MODE,
(2*num)+1, LJ_tmQUAD, 0, 0)
# Setting timer values to add Z port (or reset in case there's no Z)
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
(2*num)+0, portnumZ[num], 0, 0)
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
(2*num)+1, portnumZ[num], 0, 0)
# Sending command sequence
GoOne(self._commhandle.handle)
[docs] def get_counter(self):
"""
Get current quadrature counter value.
:returns: The counter value or a list with 2 values for 2 encoders.
:rtype: int, list(int)
>>> lju6.get_counter()
.. note::
Because the qudrature counter counts rising and falling edges
of phases `A` and `B`, a 1024 pulse/rev encoder will generate 4096
counts for a full shaft turn.
"""
value = [int(eGet(self._commhandle.handle, LJ_ioGET_TIMER, 0, 0, 0))]
if self._quadnum == 2:
value.append(int(eGet(self._commhandle.handle, LJ_ioGET_TIMER, 2, 0, 0)))
else:
value = value[0]
return value
[docs] def reset_counter(self, counter1=True, counter2=True):
"""
Reset quadrature counter value.
:param counter1: The flag indicating whether to reset counter 1 or not.
The default value is ``True`` and it resets the counter.
:type counter1: bool
:param counter2: The flag indicating whether to reset counter 2 or not.
The default value is ``True`` and it resets the counter.
:type counter2: bool
Resets current counter value of all encoders.
>>> lju6.reset_counter()
Resets current counter value only for second encoder.
>>> lju6.reset_counter(counter1=False)
.. note::
The count is only reset when a `Z` phase isn't being used.
.. note::
If the method `set_pwm_quad` was used to configure both a PWM and
a quadrature encoder, use `counter2` = ``'False'`` to reset the counter.
"""
if not self._zphase[0] and counter1:
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
0, 0, 0, 0)
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
1, 0, 0, 0)
# Sending command sequence
GoOne(self._commhandle.handle)
if self._quadnum == 2:
if not self._zphase[1] and counter2:
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
2, 0, 0, 0)
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
3, 0, 0, 0)
# Sending command sequence
GoOne(self._commhandle.handle)
# OTHER METHODS
[docs] def set_pwm_quad(self, dirport='DAC', zphase=False):
"""
Configure 1 PWM output and 1 quadrature input.
The PWM port is ``'FIO2'`` and the phases `A` and `B` ports are
respectively ``'FIO0'`` and ``'FIO1'``.
:param dirport: The type of ports that control the PWM `direction`
for electric motor control. There are three options:
* ``None`` - Default value (no direction ports are used)
* ``'DAC'`` - Uses analog ports ``'DAC0'`` and ``'DAC1'``
* ``'DIO'`` - Uses digital ports ``'FIO4'`` and ``'FIO5'``
:type dirport1: None, str
:param zphase: The logic value indicating if a `Z` phase reference
pulse is used on port ``'FIO3'``.
:type zphase: bool
Set a PWM for motor control on ``'FIO2'`` with direction ports on
``'DAC0'`` and ``'DAC1'``. The encoder `A` and `B` ports are ``'FIO0'``
and ``'FIO1'``:
>>> lju6.set_pwm_quad(dirport='DAC')
Set a PWM for motor control on ``'FIO2'`` with direction ports on
``'FIO4'`` and ``'FIO5'``. The A-B-Z encoder `A` and `B` ports are
``'FIO0'`` and ``'FIO1'``. The `Z` phase is on port ``'FIO3'``:
>>> lju6.set_pwm_quad(dirport='DIO', zphase=True)
.. note::
Due to limitations with internal clocks under this configuration,
the PWM frequency is fixed at 732 Hz.
"""
# Setting flag for simultaneous PWN and quadrature setup
self._pwmquad = True
# Assigning A-B port names
quadnameAB = ['FIO0', 'FIO1']
# Assigning Z port number
portnumZ = 0
if zphase:
portnumZ = 32768 + self._get_DIOnumber('FIO3')
# Assigning PWM ports
pwmname = ['FIO2']
if dirport == 'DAC':
self._pwmtype = [0]
pwmdir = [['DAC0', 'DAC1']]
elif dirport == 'DIO':
self._pwmtype = [1]
pwmdir = [['FIO4', 'FIO5']]
else:
raise Exception("Valid direction port types are 'DAC' or 'DIO'.")
# Setting some attributes
self._quadnum = 1
self._zphase = [zphase]
self._quadnameAB = quadnameAB
self._pwmnum = 1
self._pwmname = pwmname
self._pwmdir = pwmdir
#
# Setting timers for quadrature and PWM
#
# Assigning timer pin offset
pinoffset = 0
# Resetting pin configuration
ePut(self._commhandle.handle, LJ_ioPIN_CONFIGURATION_RESET, 0, 0, 0)
# Setting the pin offset for the timers
AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chTIMER_COUNTER_PIN_OFFSET, pinoffset, 0, 0)
# Configuring the timer clock to 48 MHz
AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chTIMER_CLOCK_BASE, LJ_tc48MHZ, 0, 0)
# Enabling 3 timers
AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
LJ_chNUMBER_TIMERS_ENABLED, 3, 0, 0)
#
# Setting up quadrature input configuration
#
# Setting quadrature timers
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_MODE,
0, LJ_tmQUAD, 0, 0)
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_MODE,
1, LJ_tmQUAD, 0, 0)
# Setting timer values to add Z port (or reset in case there's no Z)
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
0, portnumZ, 0, 0)
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
1, portnumZ, 0, 0)
#
# Setting up PWM output configuration
#
# Setting third timer to 16-bit PWM
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_MODE,
2, LJ_tmPWM16, 0, 0)
# Setting timer duty cycle to 0%
AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
2, 0, 0, 0)
# Sending command sequence
GoOne(self._commhandle.handle)
[docs] def set_range(self, names, ranges):
"""
Set analog input voltage range.
:param names: The analog port(s) that will have their ranges modified.
Use ``'ALL'`` to set all analog input ports to the same range.
:type names: str, list(str)
:param ranges: The voltage range value to be used.
Valid ranges are +/- ``10``, ``1``, ``0.1``, ``0.01`` V. If a
single value is used, it will be applied to all ports in
`names`.
:type ranges: float, list(float)
Set port ``'AIN0'`` with a range of +/- ``1`` V:
>>> lju6.set_range('AIN0', 1)
Set port ``'AIN0'`` and ``'AIN2'`` with a range of +/- ``0.1`` and
``0.01`` V:
>>> lju6.set_range(['AIN0', 'AIN2'], [0.1, 0.01])
Set all ports with a (default) range of +/- ``10`` V:
>>> lju6.set_range('ALL', 10)
"""
#
rcode = {
'10': LJ_rgBIP10V,
'1': LJ_rgBIP1V,
'0.1': LJ_rgBIPP1V,
'0.01': LJ_rgBIPP01V
}
# Checking for valid inputs
if type(names) != list: names = [names]
if type(ranges) != list: ranges = [ranges]
if (len(names) != len(ranges)) and (len(ranges) > 1):
raise Exception('Number of ports must match number of ranges.')
if len(ranges) == 1:
ranges = ranges * len(names)
for r in ranges:
if r not in [10, 1, 0.1, 0.01]:
raise Exception('Valid ranges are 10, 1, 0.1, or 0.01 V.')
if names[0].lower() == 'all':
names = self._portAIN
else:
if not self._check_port(names, "AIN"):
raise Exception('Invalid analog input port name(s).')
# Assigning range values
for name, r in zip(names, ranges):
AddRequest(self._commhandle.handle, LJ_ioPUT_AIN_RANGE,
self._get_AINnumber(name), rcode[str(r)], 0, 0)
# Sending command sequence
GoOne(self._commhandle.handle)
[docs] def get_labjacktemp(self, unit='C'):
"""
Get ambient temperature from LabJack's internal sensor.
:param unit: The temperature measurement unit.
Valid values are ``'C'`` or ``'F'``. Default unit is ``'C'``.
:type unit: str
:returns: The internal sensor temperature reading.
:rtype: float
Get temperature reading in `Celsius`:
>>> lju6.get_labjacktemp()
Get temperature reading in `Fahrenheit`:
>>> lju6.get_labjacktemp(unit='F')
"""
tempabs = eGet(self._commhandle.handle, LJ_ioGET_AIN, 30, 0, 0)
if unit == 'C':
temp = tempabs - 273.15
elif unit == 'F':
temp = 9/5*(tempabs-273.15) + 32
else:
raise Exception(
"Temperature units must be either 'degC' or 'degF'.")
return temp
# HELPER METHODS (PRIVATE)
def _eGetArray(self, Handle, IOType, Port, pValue, x1):
"""
Perform one call to the LabJack Device returning a data array.
This method was created to complement the eGet() function in
LabJackPyhon. It's used primarily in data streaming and like all
the other C library functions, it is limited to a Windows platform.
"""
pv = ctypes.c_double(pValue)
xv = (ctypes.c_double * len(x1))()
ec = self._staticlib.eGet_DblArray(
Handle, IOType, Port, ctypes.byref(pv), ctypes.byref(xv))
if ec != 0:
raise LabJackException(ec)
return pv.value, xv
def _assign_info(self, info):
# Assigning info collected from LabJack handle to private attributes
self._type = info['DeviceName']
self._serialnum = info['SerialNumber']
self._connection = 'USB'
self._hardware = info['HardwareVersion']
self._firmware = info['FirmwareVersion']
def _assign_ports(self):
"""
Creates lists with valid LabJAck T7 port names
"""
# Assigning analog output port names
self._portDAC = [
'DAC0', 'DAC1']
# Assigning analog inout port names
self._portAIN = [
'AIN0', 'AIN1', 'AIN2', 'AIN3', 'AIN4', 'AIN5', 'AIN6', 'AIN7',
'AIN8', 'AIN9', 'AIN10', 'AIN11', 'AIN12', 'AIN13', 'AIN14']
# Assigning digital I/O port names
self._portDIO = [
'DIO0', 'DIO1', 'DIO2', 'DIO3', 'DIO4', 'DIO5',
'DIO6', 'DIO7', 'DIO8', 'DIO9', 'DIO10', 'DIO11',
'DIO12', 'DIO13', 'DIO14', 'DIO15', 'DI16', 'DI17',
'DIO18', 'DIO19', 'DIO20', 'DIO21', 'DIO22']
# Assigning alternate digital I/O port names
self._portDIOalt = [
'FIO0', 'FIO1', 'FIO2', 'FIO3', 'FIO4', 'FIO5',
'FIO6', 'FIO7', 'EIO0', 'EIO1', 'EIO2', 'EIO3',
'EIO4', 'EIO5', 'EIO6', 'EIO7', 'CIO0', 'CIO1',
'CIO2', 'CIO3', 'MIO0', 'MIO1', 'MIO2']
def _reset_config(self):
"""
Reset the flexible IO ports to default all digital.
Parameters
----------
None.
Returns
-------
None.
Example
-------
Reset flexible port configuration
>>> lju3.reset_config()
Notes
-----
On the LabJack U3-HV the first 4 ports AIN0 to AIN3 are always analog.
"""
ePut(self._commhandle.handle, LJ_ioPIN_CONFIGURATION_RESET, 0, 0, 0)
def _check_port(self, namelist, portype, *args):
"""
Checks if the port NAMES belong to the possible values in
the correposnding PORTTYPE list of valid names. If all NAMES
are valid then FLAGPORT = 1, otherwise FLAGPORT = 0
A user defined list can be passed with PORTYPE = 'USER'
through an additional argument VARARGIN
"""
# Making sure inputs are lists
if type(namelist) != list:
namelist = [namelist]
# Assining valid names list based on port type
if portype == "DAC":
validnames = self._portDAC
elif portype == "AIN":
validnames = self._portAIN
elif portype == "DIO":
validnames = self._portDIO + self._portDIOalt
elif portype == "ALLIN":
validnames = self._portAIN + self._portDIO + self._portDIOalt
elif portype == "QUAD":
validnames = [
'DIO0', 'DIO1', 'DIO2', 'DIO3', 'DIO4', 'DIO5', 'DIO6', 'DIO7', 'DIO8',
'FIO0', 'FIO1', 'FIO2', 'FIO3', 'FIO4', 'FIO5', 'FIO6', 'FIO7', 'EIO0']
elif portype == "USER":
validnames = args[0]
# Returns true if all names in namelist are validnames
return all([name in validnames for name in namelist])
def _check_portdiff(self, names):
# Making sure inputs are lists
if type(names) != list:
names = [names]
# Getting port name digits
portnum = [int(re.findall(r'\d+', name)[0]) for name in names]
# Checking for all even digits
flagport = all([(num % 2)==0 for num in portnum])
# Checking for AIN14
if 14 in portnum:
flagport = False
# Returning flag and port numbers
return flagport, portnum
def _get_DACnumber(self, name):
if name.upper() == 'DAC0':
DACnumber = 0
elif name.upper() == 'DAC1':
DACnumber = 1
return DACnumber
def _get_AINnumber(self, name):
try:
AINnumber = self._portAIN.index(name)
except ValueError:
AINnumber = self._portAINalt.index(name)
return AINnumber
def _get_DIOnumber(self, name):
try:
DIOnumber = self._portDIO.index(name)
except ValueError:
DIOnumber = self._portDIOalt.index(name)
return DIOnumber
[docs]class LabJackT7:
"""
The class to represent the LabJack T7
:param serialnum: The device serial number.
:type serialnum: int
Ports that are made available with this class are:
* Analog Output (0 to 5V) : ``'DAC0'`` , ``'DAC1'``
* Analog Input (+/-10V) : ``'AIN0'`` , ``'AIN1'``, ... , ``'AIN13'``
* Digital I/O : ``'FIO0'`` , ``'FIO1'``, ... , ``'FIO7'``
* Digital I/O : ``'EIO0'`` , ``'EIO1'``, ... , ``'EIO7'``
Device-specific methods:
* `set_range` - Sets analog input voltage range
* `set_reference` - Sets analog input reference voltage point
* `set_TC` - Sets LabJack configuration for thermocouple input
* `get_TCtemp` - Gets thermocouple temperature reading
Connect to the first found T7:
>>> from labjack_unified.devices import LabJackT7
>>> ljt7 = LabJackT7()
>>> ljt7.display_info()
>>> ljt7.close()
You can also connect to a specific device using its serial number.
>>> ljt7 = LabJackT7(370012345)
"""
# CONSTRUCTOR METHODS
def __init__(self, serialnum=None):
"""
Class constructor
"""
try:
if serialnum:
# Opens LabJack with specific serial number
self._commhandle = ljm.openS("ANY", "ANY", serialnum)
else:
# Opens first available LabJack
self._commhandle = ljm.openS("ANY", "ANY", "ANY")
except LJMError as error:
print(error)
else:
# Assigning internal attributes
self._corefreq = 80e6
self._clockdivisor = 1
self._assign_info(ljm.getHandleInfo(self._commhandle))
self._assign_ports()
self.set_reference('all', mode='Single-Ended')
self.set_range('all', 10)
print('Opened LabJack', self._serialnum)
[docs] def close(self):
"""
Close the T7 device connection.
>>> ljt7.close()
"""
ljm.close(self._commhandle)
print('Closed LabJack', self._serialnum)
[docs] def display_info(self):
"""
Display a summary with the T7 device information.
"""
print('____________________________________________________________')
print('Device Name........', self._type)
print('Serial Number......', self._serialnum)
print('Hardware Version...', self._hardware)
print('Firmware Version...', self._firmware)
print('Connection Type....', self._connection)
print('IP Address.........', self._ipaddress)
print('Port...............', self._port)
print('____________________________________________________________')
# I/O METHODS
[docs] def set_analog(self, names, values):
"""
Set analog output voltage.
:param name: The port name to set the output voltage.
Available ports are ``'DAC0'`` and ``'DAC1'``. Both ports can be
set at the same time using a list containing the two names.
:type name: str, list(str)
:param value: The output voltage between ``0`` and ``5`` V. A list
containing values can be used in conjunction with a list with the
two port names.
:type value: float, list(float)
Set port ``'DAC1'`` output voltage to ``2.2`` V:
>>> ljt7.set_analog('DAC1', 2.2)
Set port ``'DAC0'`` output voltage to ``2.5`` V and ``'DAC1'`` to
``3.2`` V:
>>> ljt7.set_analog(['DAC0', 'DAC1'], [2.5, 3.2])
"""
# Making sure inputs are lists
if type(names) != list:
names = [names]
# Making sure values are ndarray of float64
values = np.double(values)
if type(values) == np.float64:
values = [values]
# Doing other input checks
if len(names) != len(values):
raise Exception('Number of ports must match number of values.')
if not self._check_port(names, "DAC"):
raise Exception('Invalid analog output port name(s).')
# Limiting analog values
values = [max(value, 0) for value in values]
values = [min(value, 5) for value in values]
# Setting port values
self._set_port_values(names, values)
[docs] def get_analog(self, names):
"""
Get analog input voltage.
:param name: THe port name (or list of names) to get the input voltage.
Ports ``'AIN0'`` to ``'AIN13'`` are possible names and can read a
range between -10 and +10V.
:type name: str, list(str)
:returns: The input voltage value.
:rtype: float, list(float)
Get input voltage on port ``'AIN0'``:
>>> ljt7.get_analog('AIN0')
Get input voltages on ports ``'AIN0'``, ``'AIN2'`` and ``'AIN3'``:
>>> ljt7.get_analog(['AIN0', 'AIN2', 'AIN3'])
.. note::
1. Ports that are not connected may have erratic readings.
2. See `set_range` and `set_reference` for more options on analog inputs.
"""
# Making sure inputs are lists
if type(names) != list:
names = [names]
# Checking for valid analog input names
if not self._check_port(names, "AIN"):
raise Exception('Invalid analog input port name(s).')
# Getting values
return self._get_port_values(names)
[docs] def set_digital(self, names, values):
"""
Write the digital state to an output port.
It also sets the port direction to output.
:param name: The port name (or a list of names) to set the state.
:type name: str, list(str)
:param state: The digital state (or a list of states)
`0 = Low`, `1 = High`.
:type state: int, list(int)
Set port ``'FIO0'`` bit to high:
>>> ljt7.set_digital('FIO0', 1)
Set ports ``'FIO0'``, ``'FIO1'`` and ``'FIO6'`` bits to high,
low and high:
>>> ljt7.set_digital(['FIO0', 'FIO1', 'FIO6'], [1, 0, 1])
"""
# Making sure inputs are lists
if type(names) != list:
names = [names]
if type(values) != list:
values = [values]
# Doing other input checks
if len(names) != len(values):
raise Exception('Number of ports must match number of values.')
if not self._check_port(names, "DIO"):
raise Exception('Invalid digital I/O port name(s).')
# Limiting digital values
values = [max(value, 0) for value in values]
values = [min(value, 1) for value in values]
# Setting port values
self._set_port_values(names, values)
[docs] def get_digital(self, names):
"""
Read the digital state from an input port.
It also sets the port direction to input.
:param name: The port name (or list of port names) to get the state.
:type name: str, list(str)
:returns: The state of the digital port. `0 = Low`, `1 = High`.
:rtype: int, list(int)
Get logic state on port ``'FIO2'``:
>>> ljt7.get_digital('FIO2')
Get logic states on ports ``'FIO2'``, ``'FIO3'`` and ``'FIO7'``:
>>> ljt7.get_digital(['FIO2', 'FIO3', 'FIO7'])
"""
if type(names) != list:
names = [names]
# Checking for valid analog input names
if not self._check_port(names, "DIO"):
raise Exception('Invalid digital I/O port name(s).')
# Getting values
states = self._get_port_values(names)
if len(names) > 1:
state = [int(value) for value in states]
else:
state = int(states)
return state
# STREAMING METHODS
[docs] def set_stream(self, names, scanrate=100000, readrate=0.5,
clocksource='INT', exttrigger=None):
"""
Set and start data streaming.
:param name: The port name (or list of names) to be streamed.
Any analog and/or digital port can be used.
:type name: str, list(str)
:param scanrate: The scan rate (Hz) of the data streaming.
The default (and maximum) value is ``100000`` Hz. The effective
scan frequency of each port is the scan rate divided by the
number of scanned ports. When `clocksource` is equal to
``'EXT'``, `scanrate` is interpreted as the number of pulses per
block of data.
:type scanrate: int
:param readrate: The rate in seconds at which blocks of data are
retrieved from the data buffer by `get_stream`. The default value
is ``0.5`` seconds. When `clocksource` is equal to ``'EXT'``,
`readrate` is interpreted as the number of blocks of data to be
retrieved each time.
:type readrate: float
:param clocksource: The source of the streaming clock.
It indicates whether the LabJack internal clock or an external
clock (pulse train) will be used. In the case of an external clock,
the digital signal must be connected to ``'CIO3'`` (``'DIO16'``).
The default value is ``'INT'``.
:type clocksource: str
:param exttrigger: The `DIO` port name containing an external trigger
used to start the streaming.
:type exttrigger: str
Configure ``100000`` Hz streaming with ``0.5`` s data blocks from port
``'AIN0'``:
>>> ljt7.set_stream('AIN0')
Configure streaming for ``1`` s data blocks from analog and digital
ports.
>>> ljt7.set_stream(['AIN0', 'AIN1', 'DIO6', 'DIO7'], readrate=1)
Configure streaming for ``0.5`` s data blocks from analog ports.
The scan rate is ``50000`` Samples/s (25000 S/s for each port):
>>> ljt7.set_stream(['AIN0','AIN1'], scanrate=50000)
Configure streaming for 1 block of 1024 Samples/port per data retrieve.
The external clock signal must be connected to ``'CIO3'``
(``'DIO16'``):
>>> ljt7.set_stream(['AIN0','AIN1'], scanrate=1024, readrate=1, clocksource='EXT')
Configure streaming for 3 blocks of 600 Samples/port.
Port ``'DIO1'`` is connected to an external trigger that will start the
streaming. The external clock signal must be connected to ``'CIO3'``
(``'DIO16'``):
>>> ljt7.set_stream(['AIN0','DIO0'],
scanrate=600, readrate=3, clocksource='EXT',
exttrigger='DIO1')
.. note::
`exttrigger` port must use the `DIO` naming convention, i.e.: ports
``'DIO0'`` to ``'DIO16'``.
.. note::
Data streaming starts immediatelly after `set_stream` is invoked,
unless `exttrigger` is used.
"""
# Making sure inputs are lists
names = names.copy()
if type(names) != list:
names = [names]
# Checking for valid analog input names
if not self._check_port(names, "ALLIN"):
raise Exception('Invalid input port name(s).')
# Limiting and interpreting scan rate
if clocksource.upper() == 'INT':
maxrate = min(scanrate, 100000)
elif clocksource.upper() == 'EXT':
maxrate = min(scanrate*(len(names)+1), 100000)
else:
raise Exception(
'Stream clock source must be either "INT" or "EXT"')
# Adding core timer register name to shallow copy of port list
if clocksource.upper() == "EXT":
names.append("CORE_TIMER")
# Assining external clock trigger parameters
if exttrigger:
if not self._check_port(exttrigger, "USER", self._portDIO):
raise Exception('Trigger must use DIO port naming.')
self._streamtrigger = exttrigger
triggernum = 2000 + int(self._streamtrigger[-1])
# Configuring clock source in LabJack
if clocksource.upper() == "INT":
ljm.eWriteName(self._commhandle, 'STREAM_CLOCK_SOURCE', 0)
else:
ljm.eWriteName(self._commhandle, 'STREAM_CLOCK_SOURCE', 2)
# Doing additional configuration changes for triggered external clock
if exttrigger:
ljm.writeLibraryConfigS(
'LJM_STREAM_SCANS_RETURN',
ljm.constants.STREAM_SCANS_RETURN_ALL)
ljm.writeLibraryConfigS(
'LJM_STREAM_RECEIVE_TIMEOUT_MS', 0)
ljm.eWriteName(
self._commhandle, 'STREAM_TRIGGER_INDEX', triggernum)
ljm.eWriteName(
self._commhandle, self._streamtrigger + '_EF_ENABLE', 0)
ljm.eWriteName(
self._commhandle, self._streamtrigger + '_EF_INDEX', 3)
ljm.eWriteName(
self._commhandle, self._streamtrigger + '_EF_CONFIG_A', 2)
ljm.eWriteName(
self._commhandle, self._streamtrigger + '_EF_ENABLE', 1)
else:
ljm.writeLibraryConfigS(
'LJM_STREAM_SCANS_RETURN',
ljm.constants.STREAM_SCANS_RETURN_ALL)
ljm.writeLibraryConfigS(
'LJM_STREAM_RECEIVE_TIMEOUT_MODE',
ljm.constants.STREAM_RECEIVE_TIMEOUT_MODE_CALCULATED)
ljm.eWriteName(self._commhandle, 'STREAM_TRIGGER_INDEX', 0)
# Making sure read period is an integer for external clock
if clocksource.upper() == 'EXT':
readrate = np.ceil(readrate)
# Assigning streaming parameters
self._streamclock = clocksource
self._streamnumports = len(names)
self._streamreadperiod = readrate
self._streamscanrate = maxrate/self._streamnumports
self._streamscansperread = int(
self._streamreadperiod*self._streamscanrate)
self._streamTs = 1/self._streamscanrate
# Configuring and starting stream
addresses = self._get_port_address(names)
ljm.eStreamStart(self._commhandle, self._streamscansperread,
self._streamnumports, addresses, self._streamscanrate)
# Ditching first block of data
if clocksource.upper() == 'INT':
time.sleep(readrate)
_, _, _, _, _ = self.get_stream()
[docs] def get_stream(self):
"""
Get streaming data block.
:returns: 5-tuple
* dt
The sampling period (s) between each data point.
When the streaming is configured with an external clock, `dt`
contains the delta times between two consecutive samples.
* data
The numpy `m-by-n` array containing the streamed data where
`m` is the number of samples per port in the block and `n`
is the number of ports defined in `set_stream`
* numscans
The actual number of scans per port in the data block.
* commbacklog
The communication backlog in % (increasing values indicate that
the computer cannot keep up with the data download from the T7
driver)
* devbacklog
The T7 device backlog in % (increasing values indicate that the
device cannot keep up with the data streaming - usually not
the case)
:rtype: (float, ndarray, int, float, float)
Retrieve one data block:
>>> dt, datablock, commbacklog, T7backlog = ljt7.get_stream()
Create the time array for the acquired block (internal clock):
>>> t = dt * np.linspace(0, datablock.shape[0]-1, datablock.shape[0])
"""
# Reading data block from LabJack
data, commbacklog, devbacklog = ljm.eStreamRead(self._commhandle)
# Creating array from data
data = np.array(data)
# Separating interweaved ports into columns
ncol = self._streamnumports
nrow = len(data)//ncol
valueaux = data[0:nrow*ncol].reshape((nrow, ncol))
# Checking for external clock
if self._streamclock == "EXT":
value = valueaux[:, 0:-1]
dAux = np.diff(valueaux[:, -1])
iaux = np.nonzero(dAux < 0)[0]
dAux[iaux] = 65535+valueaux[iaux+1, -1] - valueaux[iaux, -1]
dt = dAux/40e6
else:
value = valueaux
dt = self._streamTs
# Assigning number of actual scans
numscans = nrow
# Converting backlog outputs to % values
commbacklog = 100*commbacklog/self._streamscanrate
devbacklog = 100*devbacklog/self._streamscanrate
# Returning outputs
return dt, value, numscans, commbacklog, devbacklog
[docs] def stop_stream(self):
"""
Stop data streaming.
>>> ljt7.stop_stream()
"""
ljm.eStreamStop(self._commhandle)
# PWM METHODS
[docs] def set_pwm(self, pwmnum=1, dirport1=None, dirport2=None, frequency=250):
"""
Configure PWM output.
:param pwmnum: The number of PWM output signals.
``1`` or ``2`` PWMs can be used. For one PWM, the output port is
``'FIO0'``. For two PWMs, the output ports are ``'FIO0'`` and
``'FIO4'``.
:type pwmnum: int
:param dirport1: The type of ports that control the PWM `direction`
for electric motor control. There are three options:
* ``None`` - Default value (no direction ports are used)
* ``'DAC'`` - Uses analog ports ``'DAC0'`` and ``'DAC1'``
* ``'DIO'`` - Uses digital ports ``'EIO0'`` and ``'EIO1'``
:type dirport1: None, str
:param dirport2: Same as `dirport1`.
It's used when two PWM outputs are enabled. The ``'DAC'`` option
can only be used for one set of direction ports, unless the two
motors are running synchronously. For the ``'DIO'`` option,
digital ports ``'EIO2'`` and ``'EIO3'`` are used.
:type dirport2: None, str
:param frequency: The PWM signal frequency in Hz.
In the case of two PWMs, both will have the same frequency
:type frequency: int
Set 1 PWM for motor control on ``'FIO0'`` with direction ports on
``'DAC0'`` and ``'DAC1'``. The PWM frequency is the default ``250`` Hz:
>>> ljt7.set_pwm(dirport1='DAC')
Set 2 PWMs on ports ``'FIO0'`` and ``'FIO4'`` with a frequency of
``500`` Hz:
>>> ljt7.set_pwm(pwmnum=2, frequency=500)
Set 2 PWMs for motor control on ports ``'FIO0'`` and ``'FIO4'``, using
the digital ports ``'EIO0'`` and ``'EIO1'`` for motor 1 direction, and
``'EIO2'`` and ``'EIO3'`` for motor 2 direction. The PWM frequency is
``750`` Hz:
>>> ljt7.set_pwm(pwmnum=2, dirport1='DIO', dirport2='DIO', frequency=750)
.. note::
When using digital ports, a 10 kOhm resistor has to be connected from
the LabJack `VS` port to each one of the `DIO` ports to ensure true
`high` and `low` states.
.. note::
Two PWMs and two encoders can be setup at the same time on the T7 device.
"""
# Checking number of PWM outputs and direction ports
dirport = [dirport1, dirport2]
pwmname = ['DIO0']
pwmdir = [None]
self._pwmtype = [1]
if dirport[0] == 'DAC':
pwmdir[0] = ['DAC0', 'DAC1']
self._pwmtype[0] = 0
elif dirport[0] == 'DIO':
pwmdir[0] = ['EIO0', 'EIO1']
if pwmnum == 2:
self._pwmtype.append(1)
pwmname.append('DIO4')
if dirport[1] is None:
pwmdir.append(None)
if dirport[1] == 'DAC':
pwmdir.append[0] = ['DAC0', 'DAC1']
self._pwmtype[1] = 0
elif dirport[1] == 'DIO':
pwmdir.append(['EIO2', 'EIO3'])
# Assigning PWM attributes
self._pwmnum = pwmnum
self._pwmname = pwmname
self._pwmdir = pwmdir
self._pwmfreq = frequency
# Calculating clock roll value
self._clockrollval = self._corefreq/(self._clockdivisor*self._pwmfreq)
# Configuring clock registers
ljm.eWriteName(
self._commhandle, 'DIO_EF_CLOCK0_ENABLE', 0)
ljm.eWriteName(
self._commhandle, 'DIO_EF_CLOCK0_DIVISOR', self._clockdivisor)
ljm.eWriteName(
self._commhandle, 'DIO_EF_CLOCK0_ROLL_VALUE', self._clockrollval)
ljm.eWriteName(
self._commhandle, 'DIO_EF_CLOCK0_ENABLE', 1)
# Configuring extended feature (EF) registers for selected PWM port
for name in pwmname:
# Disable the EF system for initial configuration
ljm.eWriteName(self._commhandle, name + '_EF_ENABLE', 0)
# Configure EF system for PWM
ljm.eWriteName(self._commhandle, name + '_EF_INDEX', 0)
# Configure what clock source to use: Clock0
ljm.eWriteName(self._commhandle, name + '_EF_OPTIONS', 0)
# Configure duty cycle to be: 0%
ljm.eWriteName(self._commhandle, name + '_EF_CONFIG_A', 0)
# Enable the EF system, PWM wave is now being outputted
ljm.eWriteName(self._commhandle, name + '_EF_ENABLE', 1)
[docs] def set_dutycycle(self, value1=None, value2=None, brake1=False, brake2=False):
"""
Set PWM duty cycle value.
:param value1: The PWM 1 duty cycle percent value between ``-100``
and ``100``.
:type value1: float
:param value2: The PWM 2 duty cycle percent value between ``-100``
and ``100``.
:type value2: float
:param brake1: The motor 1 brake option used when dutycycle is zero.
Brake is applied when ``True``. Motor is floating when ``False``.
:type brake1: bool
:param brake2: The motor 2 brake option used when dutycycle is zero.
Brake is applied when ``True``. Motor is floating when ``False``.
:type brake2: bool
Set duty cycle to ``50`` % on PWM 1:
>>> ljt7.set_dutycycle(value1=50)
Set duty cycle to ``25`` % (reverse rotation) on PWM 2:
>>> ljt7.set_dutycycle(value2=-25)
Set duty cycle to ``20`` % and ``40`` % on PWMs 1 and 2:
>>> ljt7.set_dutycycle(value1=20, value2=40)
Stop motor 2 and apply brake:
>>> ljt7.set_dutycycle(value2=0, brake2=True)
.. note::
1. Avoid suddenly switching the direction of rotation to avoid damaging the motor.
2. You can use the brake option True to hold the motor in position.
"""
values = [value1, value2]
brakes = [brake1, brake2]
for value, brake, pwmdir, pwmtype, pwmname in zip(
values, brakes, self._pwmdir, self._pwmtype, self._pwmname):
if value is not None:
# Applying bounds to inputs
if value > 100:
value = 100
if value < -100:
value = -100
# Applying PWM direction
if pwmdir:
if value > 0:
# Forward rotation
if pwmtype == 0:
self.set_analog(pwmdir, [4.5, 0])
else:
self.set_digital(pwmdir[0], 0)
self.get_digital(pwmdir[1])
elif value < 0:
# Reverse rotation
if pwmtype == 0:
self.set_analog(pwmdir, [0, 4.5])
else:
self.get_digital(pwmdir[0])
self.set_digital(pwmdir[1], 0)
elif value == 0:
# Brake stop
if brake:
if pwmtype == 0:
self.set_analog(pwmdir, [0, 0])
else:
self.set_digital(pwmdir[0], 0)
self.set_digital(pwmdir[1], 0)
else:
if pwmtype == 0:
self.set_analog(pwmdir, [4.5, 4.5])
else:
self.get_digital(pwmdir[0])
self.get_digital(pwmdir[1])
# Calculating duty cycle
dutycycle = round(abs(value)/100*self._clockrollval)
# Setting timer duty cycle to desired value
ljm.eWriteName(
self._commhandle, pwmname + '_EF_CONFIG_A', dutycycle)
# QUADRATURE ENCODER METHODS
[docs] def set_quadrature(self, quadnum=1, zphase1=False, zphase2=False):
"""
Configure quadrature encoder input.
:param quadnum: The number of quadrature input signals.
``1`` or ``2`` encoders can be used. For one encoder, the input
ports are ``'FIO2'`` and ``'FIO3'``. For two encoders, the input
ports for the second one are ``'FIO6'`` and ``'FIO7'``.
:type quadnum: int
:param zphase1: The logic value indicating if a `Z` phase reference
pulse is used for the first encoder. Port ``'FIO1'`` is used.
:type zphase1: bool
:param zphase2: The logic value indicating if a `Z` phase reference
pulse is used for the second encoder. Port ``'FIO1'`` is used for
the first encoder and port ``'FIO5'`` is used for the second
encoder. `zphase2` is ignored if `quadnum` = ``1``.
:type zphase1: bool
Set ports ``'FIO2'`` and ``'FIO3'`` for encoder with phase `A` and `B`
signals only:
>>> ljt7.set_quadrature()
Set ports ``'FIO2'`` and ``'FIO3'`` for encoder phase `A` and `B`
signals, and port ``'FIO1'`` for the reference `Z` phase:
>>> ljt7.set_quadrature(zphase1=True)
Set 2 encoders with `Z` phase. `A` and `B` phases are on ports
``'FIO2'`` and ``'FIO3'`` for encoder 1, and ``'FIO6'`` and ``'FIO7'``
for encoder 2. The `Z` phase ports are respectively ``'FIO1'`` and
``'FIO5'``:
>>> ljt7.set_quadrature(quadnum=2, zphase1=True, zphase2=True)
.. note::
Two PWMs and two encoders can be setup at the same time on the T7 device.
"""
# Selecting port numbers based on input options
if quadnum == 1:
quadnameAB = [['DIO2', 'DIO3']]
portnumZ = [0]
if zphase1:
portnumZ[0] = 1
elif quadnum == 2:
quadnameAB = [['DIO2', 'DIO3'], ['DIO6', 'DIO7']]
portnumZ = [0, 0]
if zphase1:
portnumZ[0] = 1
if zphase2:
portnumZ[1] = 5
else:
raise Exception('Only 1 or 2 quadrature inputs can be assigned.')
# Assigning quadrature properties
self._zphase = [zphase1, zphase2]
self._quadnum = quadnum
self._quadnameAB = quadnameAB
#
# Configuring extended feature (EF) registers for
# selected A-B ports and optional Z phase port
#
# Disabling the EF system for initial configuration
for name, num in zip(quadnameAB, portnumZ):
ljm.eWriteName(self._commhandle, name[0] + '_EF_ENABLE', 0)
ljm.eWriteName(self._commhandle, name[1] + '_EF_ENABLE', 0)
# Configure EF system for qudrature
ljm.eWriteName(self._commhandle, name[0] + '_EF_INDEX', 10)
ljm.eWriteName(self._commhandle, name[1] + '_EF_INDEX', 10)
# Configuring port Z
if num > 0:
ljm.eWriteName(
self._commhandle, name[0] + '_EF_CONFIG_A', 1)
ljm.eWriteName(
self._commhandle, name[0] + '_EF_CONFIG_B', num)
ljm.eWriteName(
self._commhandle, name[1] + '_EF_CONFIG_A', 1)
ljm.eWriteName(
self._commhandle, name[1] + '_EF_CONFIG_B', num)
else:
ljm.eWriteName(
self._commhandle, name[0] + '_EF_CONFIG_A', 0)
ljm.eWriteName(
self._commhandle, name[1] + '_EF_CONFIG_A', 0)
# Enabling the EF system
ljm.eWriteName(self._commhandle, name[0] + '_EF_ENABLE', 1)
ljm.eWriteName(self._commhandle, name[1] + '_EF_ENABLE', 1)
[docs] def get_counter(self):
"""
Get current quadrature counter value.
:returns: The counter value or a list with 2 values for 2 encoders.
:rtype: int, list(int)
>>> ljt7.get_counter()
.. note::
Because the qudrature counter counts rising and falling edges
of phases `A` and `B`, a 1024 pulse/rev encoder will generate 4096
counts for a full shaft turn.
"""
value = [self._get_port_values([self._quadnameAB[0][0] + '_EF_READ_A_F'])]
if self._quadnum == 2:
value.append(self._get_port_values([self._quadnameAB[1][0] + '_EF_READ_A_F']))
else:
value = value[0]
return value
[docs] def reset_counter(self, counter1=True, counter2=True):
"""
Reset quadrature counter value.
:param counter1: The flag indicating whether to reset counter 1 or not.
The default value is ``True`` and it resets the counter.
:type counter1: bool
:param counter2: The flag indicating whether to reset counter 2 or not.
The default value is ``True`` and it resets the counter.
:type counter2: bool
Resets current counter value of all encoders.
>>> ljt7.reset_counter()
Resets current counter value only for second encoder.
>>> ljt7.reset_counter(counter1=False)
.. note::
The count is only reset when a `Z` phase isn't being used.
"""
if not self._zphase[0] and counter1:
self._get_port_values([self._quadnameAB[0][0] + '_EF_READ_A_AND_RESET'])
if self._quadnum == 2:
if not self._zphase[1] and counter2:
self._get_port_values([self._quadnameAB[1][0] + '_EF_READ_A_AND_RESET'])
# THERMOCOUPLE METHODS
[docs] def get_labjacktemp(self, unit='C'):
"""
Get ambient temperature from LabJack's internal sensor.
:param unit: The temperature measurement unit.
Valid values are ``'C'`` or ``'F'``. Default unit is ``'C'``.
:type unit: str
:returns: The internal sensor temperature reading.
:rtype: float
Get temperature reading in `Celsius`:
>>> ljt7.get_labjacktemp()
Get temperature reading in `Fahrenheit`:
>>> ljt7.get_labjacktemp(unit='F')
"""
v = ljm.eReadName(self._commhandle, 'AIN14')
tempabs = -92.6*v + 467.6
if unit == 'K':
temp = tempabs
elif unit == 'C':
temp = tempabs - 273.15
elif unit == 'F':
temp = 9/5*(tempabs-273.15) + 32
else:
raise Exception(
"Temperature units must be either 'C' or 'F'.")
return temp
[docs] def set_TC(self, names, types, unit='C'):
"""
Set configuration for thermocouple input.
:param names: The analog port(s) that will be used for thermocouple
input. Ports ``'AIN0'`` to ``'AIN3'`` are recommended for higher
measurement accuracy. The negative thermocouple wire should be
connected to the LabJack `GND`.
:type names: str, list(str)
:param types: The thermocouple type.
It can be a single string or a list with same length as `names`.
Valid types are: ``'B'``, ``'E'``, ``'J'``, ``'K'``, ``'N'``,
``'R'``, ``'S'``, ``'T'``, and ``'C'``.
:type types: str, list(str)
:param unit: The temperature measurement unit.
Valid values are ``'C'`` or ``'F'``. Default unit is ``'C'``.
:type unit: str
Set port ``'AIN0'`` for thermocouple type ``'K'``:
>>> ljt7.set_TC('AIN0', 'K')
Set ports ``'AIN0'``, ``'AIN2'``, and ``'AIN3'`` for thermocouples
type ``'K'`` and ``'J'`` with measurement in Fahrenheit:
>>> ljt7.set_TC(['AIN0', 'AIN2', 'AIN3'], ['K', 'J', 'J'], unit='F')
"""
self._TCoptions = ['B', 'E', 'J', 'K', 'N', 'R', 'S', 'T', 'C']
# Checking for valid inputs
if type(names) != list: names = [names]
if type(types) != list: types = [types]
if (len(names) != len(types)) and (len(types) > 1):
raise Exception('Number of ports must match number of types.')
if not self._check_port(names, "AIN"):
raise Exception('Analog input port(s) are required for TC setup.')
if (unit != 'C') and (unit != 'F'):
raise Exception("Temperature units must be either 'degC' or 'degF'.")
for tipe in types:
if tipe not in self._TCoptions:
raise Exception('Invalid TC type.')
# Assining TC attributes
self._TCnames = names
if len(types) == 1:
self._TCtypes = types * len(names)
else:
self._TCtypes = types
self._TCunit = unit
# Assigning TC type number
self._TCtypenum = [6001+self._TCoptions.index(tipe) for tipe in types]
# Setting TC analog port ranges to +/- 0.1V
for name in names:
ljm.eWriteName(self._commhandle, name + '_RANGE', 0.1)
[docs] def get_TCtemp(self):
"""
Get thermocouple temperature reading.
:returns: The temperature readings for the thermocouples defined
using `set_TC`.
:rtype: float, list(float)
Get temperature from thermocouples:
>>> ljt7.get_TCtemp()
"""
# Getting LabJack cold joint temperature
TCJ = self.get_labjacktemp(unit='K')
# Getting TC voltage and convertng it to temperature
v = self.get_analog(self._TCnames)
if type(v) != list:
v = [v]
tempabs = []
for typ, val in zip(self._TCtypenum, v):
tempabs.append(ljm.tcVoltsToTemp(typ, val, TCJ))
# Converting to TC unit
tempabs = np.array(tempabs)
if self._TCunit == 'K':
temp = tempabs
elif self._TCunit == 'C':
temp = tempabs - 273.15
elif self._TCunit == 'F':
temp = 9/5*(tempabs-273.15) + 32
if len(temp) == 1:
temp = temp[0]
else:
temp = list(temp)
return temp
# OTHER METHODS
[docs] def set_reference(self, names, mode='single-ended'):
"""
Set reference point for analog input voltage.
:param names: The analog port(s) that will that will have the voltage
reference point modified.
:type names: str, list(str)
:param mode: The reference mode for the analog ports.
It and can be either ``'single-ended'`` or ``'differential'``.
Default is ``'single-ended'``. Use ``'ALL'`` to set all analog
input ports to the same mode.
:type mode: str
Set port ``'AIN0'`` for differential reading with port ``'AIN1'``:
>>> ljt7.set_reference('AIN0', differential')
Set ports ``'AIN0'``, ``'AIN2'``, and ``'AIN6'`` for differential
reading respecitvely with ports ``'AIN1'``, ``'AIN3'``, and ``'AIN7'``:
>>> ljt7.set_reference(['AIN0', 'AIN2', 'AIN6'], 'differential')
Set all ports for single-ended reading:
>>> ljt7.set_reference('ALL', 'single-ended')
.. note::
Differential reading uses two consecutive even-odd ports.
Valid ports for differential reading are AIN0/2/4/6/8/10/12.
"""
# Checking for valid inputs
if type(names) != list: names = [names]
if mode.lower() not in ['single-ended', 'differential']:
raise Exception(
"Valid reference types are 'single-ended' or 'differential'.")
# Assigning range values
if (names[0].lower() == 'all') and (len(names) == 1):
numref = 199
if mode.lower() == 'differential':
numref = 1
ljm.eWriteName(self._commhandle, 'AIN_ALL_NEGATIVE_CH', numref)
else:
if not self._check_port(names, "AIN"):
raise Exception('Invalid analog input port name(s).')
flagport, portnum = self._check_portdiff(names)
if flagport:
numref = 199
for name, num in zip(names, portnum):
if mode.lower() == 'differential':
numref = num + 1
ljm.eWriteName(self._commhandle, name + '_NEGATIVE_CH', numref)
else:
raise Exception('Valid analog ports are AIN0/2/4/6/8/10/12.')
[docs] def set_range(self, names, ranges):
"""
Set analog input voltage range.
:param names: The analog port(s) that will have their ranges modified.
Use ``'ALL'`` to set all analog input ports to the same range.
:type names: str, list(str)
:param ranges: The voltage range value to be used.
Valid ranges are +/- ``10``, ``1``, ``0.1``, ``0.01`` V. If a
single value is used, it will be applied to all ports in
`names`.
:type ranges: float, list(float)
Set port ``'AIN0'`` with a range of +/- ``1`` V:
>>> ljt7.set_range('AIN0', 1)
Set port ``'AIN0'`` and ``'AIN2'`` with a range of +/- ``0.1`` and
``0.01`` V:
>>> ljt7.set_range(['AIN0', 'AIN2'], [0.1, 0.01])
Set all ports with a (default) range of +/- ``10`` V:
>>> ljt7.set_range('ALL', 10)
"""
# Checking for valid inputs
if type(names) != list: names = [names]
if type(ranges) != list: ranges = [ranges]
if (len(names) != len(ranges)) and (len(ranges) > 1):
raise Exception('Number of ports must match number of ranges.')
if len(ranges) == 1:
ranges = ranges * len(names)
for r in ranges:
if r not in [10, 1, 0.1, 0.01]:
raise Exception('Valid ranges are 10, 1, 0.1, or 0.01 V.')
# Assigning range values
if (names[0].lower() == 'all') and (len(names) == 1):
ljm.eWriteName(self._commhandle, 'AIN_ALL_RANGE', ranges[0])
else:
if not self._check_port(names, "AIN"):
raise Exception('Invalid analog input port name(s).')
for name, r in zip(names, ranges):
ljm.eWriteName(self._commhandle, name + '_RANGE', r)
# HELPER METHODS (PRIVATE)
def _assign_info(self, info):
# Assigning info collected from LabJack handle to private attributes
self._type = "T" + str(info[0])
self._serialnum = str(info[2])
if info[1] == 1:
self._connection = "USB"
self._ipaddress = "N/A"
self._port = "N/A"
elif info[1] == 2:
self._connection = "TCP"
self._ipaddress = ljm.numberToIP(info[3])
self._port = str(info[4])
elif info[1] == 3:
self._connection = "ETHERNET"
self._ipaddress = "N/A"
self._port = "N/A"
elif info[1] == 4:
self._connection = "WIFI"
self._ipaddress = ljm.numberToIP(info[3])
self._port = str(info[4])
else:
self._connection = "Unknown"
self._ipaddress = "Unknown"
self._port = "Unknown"
# More stuff
value = ljm.eReadName(self._commhandle, 'HARDWARE_VERSION')
self._hardware = np.round(value, 2)
value = ljm.eReadName(self._commhandle, 'FIRMWARE_VERSION')
self._firmware = np.round(value, 3)
def _assign_ports(self):
"""
Creates lists with valid LabJAck T7 port names
"""
# Assigning analog output port names
self._portDAC = [
'DAC0', 'DAC1']
# Assigning analog inout port names
self._portAIN = [
'AIN0', 'AIN1', 'AIN2', 'AIN3', 'AIN4', 'AIN5', 'AIN6', 'AIN7',
'AIN8', 'AIN9', 'AIN10', 'AIN11', 'AIN12', 'AIN13', 'AIN14']
# Assigning digital I/O port names
self._portDIO = [
'DIO0', 'DIO1', 'DIO2', 'DIO3', 'DIO4', 'DIO5',
'DIO6', 'DIO7', 'DIO8', 'DIO9', 'DIO10', 'DIO11',
'DIO12', 'DIO13', 'DIO14', 'DIO15', 'DI16', 'DI17',
'DIO18', 'DIO19', 'DIO20', 'DIO21', 'DIO22']
# Assigning alternate digital I/O port names
self._portDIOAlt = [
'FIO0', 'FIO1', 'FIO2', 'FIO3', 'FIO4', 'FIO5',
'FIO6', 'FIO7', 'EIO0', 'EIO1', 'EIO2', 'EIO3',
'EIO4', 'EIO5', 'EIO6', 'EIO7', 'CIO0', 'CIO1',
'CIO2', 'CIO3', 'MIO0', 'MIO1', 'MIO2']
def _check_port(self, namelist, portype, *args):
"""
Checks if the port NAMES belong to the possible values in
the correposnding PORTTYPE list of valid names. If all NAMES
are valid then FLAGPORT = 1, otherwise FLAGPORT = 0
A user defined list can be passed with PORTYPE = 'USER'
through an additional argument VARARGIN
"""
# Making sure inputs are lists
if type(namelist) != list:
namelist = [namelist]
# Assining valid names list based on port type
if portype == "DAC":
validnames = self._portDAC
elif portype == "AIN":
validnames = self._portAIN
elif portype == "DIO":
validnames = self._portDIO + self._portDIOAlt
elif portype == "ALLIN":
validnames = self._portAIN + self._portDIO + self._portDIOAlt
elif portype == "PWM":
validnames = [
'DIO0', 'DIO2', 'DIO3', 'DIO4', 'DIO5',
'FIO0', 'FIO2', 'FIO3', 'FIO4', 'FIO5']
elif portype == "QUAD":
validnames = [
'DIO0', 'DIO1', 'DIO2', 'DIO3', 'DIO6', 'DIO7',
'FIO0', 'FIO1', 'FIO2', 'FIO3', 'FIO6', 'FIO7']
elif portype == "USER":
validnames = args[0]
# Returns true if all names in namelist are validnames
return all([name in validnames for name in namelist])
def _check_portdiff(self, names):
# Making sure inputs are lists
if type(names) != list:
names = [names]
# Getting port name digits
portnum = [int(re.findall(r'\d+', name)[0]) for name in names]
# Checking for all even digits
flagport = all([(num % 2)==0 for num in portnum])
# Checking for AIN14
if 14 in portnum:
flagport = False
# Returning flag and port numbers
return flagport, portnum
def _get_portalt(self, name):
"""
Returns the alternate DIO port name corresponding to the input NAME
"""
# Making sure input is a not list
if type(name) == list:
name = name[0]
try:
portnum = self._portDIO.index(name)
portalt = self._portDIOAlt[portnum]
except ValueError:
portalt = "none"
return portalt
def _set_port_values(self, names, values):
"""
Calls LJM method to write name values to LabJack
"""
ljm.eWriteNames(self._commhandle, len(names), names, values)
def _get_port_values(self, names):
"""
Calls LJM method to read name values from LabJack
"""
values = ljm.eReadNames(self._commhandle, len(names), names)
if len(values) == 1:
values = values[0]
return values
def _get_port_address(self, names):
"""
Gets port addresses corresponding to the ports in NAMES from LabJack
"""
return ljm.namesToAddresses(len(names), names)[0]