"""Experimental support for accessing IO-pins on sub modules of the Kvaser DIN
Rail SE 400S that was added to CANlib v5.26.
.. versionadded:: 1.8
"""
import ctypes as ct
from ..cenum import CEnum
from . import wrapper
dll = wrapper.dll
[docs]def get_io_pin(channel, index):
"""Return io pin object for `index`
Arguments:
index (`int`): The global pin number
Returns subclass of `IoPin` depending on pin type and direction:
`AnalogIn`, `AnalogOut`, `DigitalIn`, `DigitalOut` or `Relay`.
"""
io_pin = IoPin(channel, index)
pin_class = _PIN_CLASSES[io_pin.pin_type][io_pin.direction]
my_io_pin = pin_class(channel, index)
return my_io_pin
[docs]def module_pin_names(module_type, prefix=''):
"""Return a list of names for `module_type`
Returns a list of label names for the given type of module::
>>> iopin.module_pin_names(iopin.ModuleType.ANALOG)
['AO1', 'AO2', 'AO3', 'AO4', 'AI1', 'AI2', 'AI3', 'AI4']
>>> iopin.module_pin_names(iopin.ModuleType.RELAY)
['R1', 'R2', 'R3', 'R4', 'R5', 'R6', 'R7', 'R8', 'DI1', 'DI2', 'DI3',
'DI4', 'DI5', 'DI6', 'DI7', 'DI8']
Args:
module_type (`iopin.ModuleType`) : Type of module
"""
if module_type == ModuleType.DIGITAL:
pin_names = ['%sDO%s' % (prefix, x) for x in range(1, 17)]
pin_names += ['%sDI%s' % (prefix, x) for x in range(1, 17)]
elif module_type == ModuleType.ANALOG:
pin_names = ['%sAO%s' % (prefix, x) for x in range(1, 5)]
pin_names += ['%sAI%s' % (prefix, x) for x in range(1, 5)]
elif module_type == ModuleType.RELAY:
pin_names = ['%sR%s' % (prefix, x) for x in range(1, 9)]
pin_names += ['%sDI%s' % (prefix, x) for x in range(1, 9)]
else:
raise AttributeError("%s is an unknown ModuleType" % module_type)
return pin_names
def _create_pin_names(io_pins):
"""Create a list of names for the given list of `iopin.IoPin`.
Used by `iopin.Configuration` to create a list of label pin names
from a given list of `iopin.IoPin`s
"""
module_index = 0
pin_index = 0
pin_names = []
while pin_index < len(io_pins):
module_index += 1
names = module_pin_names(io_pins[pin_index].module_type, prefix='%s:' % module_index)
pin_names += names
pin_index += len(names)
return pin_names
[docs]class Configuration(object):
"""Contains I/O pins and the `canlib.Channel` to find them on
Creating this object may take some time depending on the number of I/O pins
availably on the given `canlib.Channel`.
Args:
channel ('canlib.Channel'): The channel where the discovery of I/O pins
should take place.
Attributes:
io_pins (list(`iopin.IoPin`)): All discovered I/O pins.
pin_names (list(str)): List of label I/O pin names.
pin_index (dict(str: int)): Dictionary with I/O pin label name as key, and pin index as value.
To create an `iopin.Configuration` you need to supply the `canlib.Channel`,
which is were we look for I/O pins:
>>> from canlib.canlib import iopin
>>> from canlib import canlib, Device, EAN
>>> device = Device.find(ean=EAN('01059-8'), serial=225)
>>> channel = canlib.openChannel(device.channel_number(), canlib.Open.EXCLUSIVE)
>>> config = iopin.Configuration(channel)
Now we can investigate a specific pin by index::
>>> config.pin(index=80)
Pin 80: <PinType.ANALOG: 2> <Direction.OUT: 8> bits=12 range=0.0-10.0 (<ModuleType.ANALOG: 2>)
It is also possible to find the label name from the index and vice verse
for a pin, as well as access the pin using the label name::
>>> config.name(80)
'4:AO1'
>>> config.index('4:AO1')
80
>>> config.pin(name='4:AO1')
Pin 80: <PinType.ANALOG: 2> <Direction.OUT: 8> bits=12 range=0.0-10.0 (<ModuleType.ANALOG: 2>)
Note:
A configuration needs to be confirmed using `iopin.Configuration.confirm`
(which calls canlib.channel.io_confirm_config`) before accessing pin values::
>>> config.pin(name='4:AO1').value = 4
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "...\canlib\canlib\iopin.py", line 271, in value
File "...\canlib\canlib\dll.py", line 94, in _error_check
raise can_error(result)
canlib.canlib.exceptions.IoPinConfigurationNotConfirmed: I/O pin configuration is not confirmed (-45)
I/O pin configuration is not confirmed (-45)
>>> config.confirm()
>>> config.pin(name='4:AO1').value = 4
An `iopin.Configuration` may be compared with an expected ordered list of
modules before confirming::
>>> modules = [iopin.ModuleType.RELAY, iopin.ModuleType.DIGITAL]
>>> config.modules() == modules
True
"""
def __init__(self, channel):
self._channel = channel
self.io_pins = []
for index in range(self._channel.number_of_io_pins()):
self.io_pins.append(self._channel.get_io_pin(index))
self.pin_names = _create_pin_names(self.io_pins)
self.pin_index = {v: i for i, v in enumerate(self.pin_names)}
def __iter__(self):
"""Returns an iterator for all pins known in configuration"""
return iter(self.io_pins)
[docs] def confirm(self):
"""Confirm current configuration
Convenience function that calls `Channel.io_confirm_config`.
"""
self._channel.io_confirm_config()
[docs] def index(self, name):
"""Return index for pin with the given label name"""
return self.pin_index[name]
[docs] def modules(self):
"""Return a list of all modules in the configuration"""
modules = []
pin_index = 0
while pin_index < len(self.io_pins):
module_type = self.io_pins[pin_index].module_type
modules.append(module_type)
pin_index += len(module_pin_names(module_type))
return modules
[docs] def name(self, index):
"""Return label name for pin with given index"""
return self.pin_names[index]
[docs] def pin(self, index=None, name=None):
"""Return `IoPin` object using index or name
Either `index` or `name` must be given, if both are given, the name
will be used.
Args:
index (int): I/O pin index
name (str): I/O pin name
"""
if name is not None:
index = self.pin_index[name]
return self.io_pins[index]
[docs]class Info(CEnum):
"""Enum used for calls to `kvIoPinGetInfo` and `kvIoPinSetInfo`"""
MODULE_TYPE = 1
DIRECTION = 2
PIN_TYPE = 4
NUMBER_OF_BITS = 5
RANGE_MIN = 6
RANGE_MAX = 7
DI_LOW_HIGH_FILTER = 8 # 0 - 65000, Default 5000 us
DI_HIGH_LOW_FILTER = 9 # 0 - 65000, Default 5000 us
AI_LP_FILTER_ORDER = 10 # 0 - 16, default 3 (sample time is 1 ms)
AI_HYSTERESIS = 11 # 0 - 10, default 0.3
[docs]class ModuleType(CEnum):
"""Enum used for return values in `kvIoPinGetInfo`"""
DIGITAL = 1 # Digital Add-on (16 inputs, 16 outputs)
ANALOG = 2 # Analog Add-on (4 inputs, 4 outputs)
RELAY = 3 # Relay Add-on (8 inputs, 8 outputs)
[docs]class PinType(CEnum):
"""Enum used for return values in `kvIoPinGetInfo`"""
DIGITAL = 1
ANALOG = 2
RELAY = 3
[docs]class Direction(CEnum):
"""Enum used for return values in `kvIoPinGetInfo`"""
IN = 4
OUT = 8
[docs]class DigitalValue(CEnum):
"""Enum used digital values"""
LOW = 0
HIGH = 1
[docs]class IoPin(object):
"""Base class of I/O ports"""
def __init__(self, channel, pin):
self.channel = channel
self.pin = pin
def __repr__(self):
txt = "Pin {p}: {pt!r} {d!r} bits={nb} range={rmin}-{rmax} ({mt!r})".format(
p=self.pin, pt=self.pin_type, d=self.direction, nb=self.number_of_bits,
rmin=self.range_min, rmax=self.range_max, mt=self.module_type)
return txt
def _get_info(self, info, buf_type):
buf = buf_type()
dll.kvIoPinGetInfo(self.channel.handle, self.pin, info, ct.byref(buf), ct.sizeof(buf))
return buf.value
def _set_info(self, info, c_value):
dll.kvIoPinSetInfo(self.channel.handle, self.pin, info,
ct.byref(c_value), ct.sizeof(c_value))
@property
def direction(self):
"""`Direction`: Pin direction (Read-only)"""
buf_type = ct.c_uint32
value = self._get_info(Info.DIRECTION, buf_type)
return Direction(value)
@property
def module_type(self):
"""`ModuleType`: Type of module (Read-only)"""
buf_type = ct.c_uint32
value = self._get_info(Info.MODULE_TYPE, buf_type)
return ModuleType(value)
@property
def number_of_bits(self):
"""int: Resolution in number of bits (Read-only)"""
buf_type = ct.c_uint32
value = self._get_info(Info.NUMBER_OF_BITS, buf_type)
return value
@property
def pin_type(self):
"""`PinType`: Type of pin (Read-only)"""
buf_type = ct.c_uint32
value = self._get_info(Info.PIN_TYPE, buf_type)
return PinType(value)
@property
def range_min(self):
"""float: Lower range limit in volts (Read-only)"""
buf_type = ct.c_float
value = self._get_info(Info.RANGE_MIN, buf_type)
return value
@property
def range_max(self):
"""float: Upper range limit in volts (Read-only)"""
buf_type = ct.c_float
value = self._get_info(Info.RANGE_MAX, buf_type)
return value
@property
def value(self):
"""Base class does not implement value attribute"""
raise AttributeError("can't get attribute value")
@value.setter
def value(self, value):
raise AttributeError("can't set attribute value")
@property
def hysteresis(self):
"""Base class does not implement hysteresis attribute"""
raise AttributeError("can't get attribute hysteresis")
@hysteresis.setter
def hysteresis(self, value):
raise AttributeError("can't set attribute hysteresis")
@property
def lp_filter_order(self):
"""Base class does not implement lp_filter_order attribute"""
raise AttributeError("can't get attribute lp_filter_order")
@lp_filter_order.setter
def lp_filter_order(self, value):
raise AttributeError("can't set attribute lp_filter_order")
[docs]class AnalogIn(IoPin):
def __repr__(self):
txt = "Pin {p}: {pt!r} {d!r} bits={nb} range={rmin}-{rmax} LP_filter_order={lpfo} hysteresis={h} ({mt!r})".format(
p=self.pin, pt=self.pin_type, d=self.direction, nb=self.number_of_bits,
rmin=self.range_min, rmax=self.range_max, lpfo=self.lp_filter_order,
h=self.hysteresis, mt=self.module_type)
return txt
@property
def hysteresis(self):
"""The hysteresis in Volt for analog input pin"""
buf_type = ct.c_float
value = self._get_info(Info.AI_HYSTERESIS, buf_type)
return value
@hysteresis.setter
def hysteresis(self, value):
c_value = ct.c_float(value)
self._set_info(Info.AI_HYSTERESIS, c_value)
@property
def lp_filter_order(self):
"""The low-pass filter order for analog input pin"""
buf_type = ct.c_uint32
value = self._get_info(Info.AI_LP_FILTER_ORDER, buf_type)
return value
@lp_filter_order.setter
def lp_filter_order(self, value):
c_value = ct.c_uint32(value)
self._set_info(Info.AI_LP_FILTER_ORDER, c_value)
@property
def value(self):
"""Voltage level on the Analog input pin"""
voltage = ct.c_float()
dll.kvIoPinGetAnalog(self.channel.handle, self.pin, ct.byref(voltage))
return voltage.value
[docs]class AnalogOut(IoPin):
@property
def value(self):
"""Voltage level on the Analog output pin"""
voltage = ct.c_float()
dll.kvIoPinGetOutputAnalog(self.channel.handle, self.pin, ct.byref(voltage))
return voltage.value
@value.setter
def value(self, value):
voltage = ct.c_float(value)
dll.kvIoPinSetAnalog(self.channel.handle, self.pin, voltage)
[docs]class DigitalIn(IoPin):
def __repr__(self):
txt = "Pin {p}: {pt!r} {d!r} bits={nb} range={rmin}-{rmax} HL_filter={hlf} LH_filter={lhf} ({mt!r})".format(
p=self.pin, pt=self.pin_type, d=self.direction, nb=self.number_of_bits,
rmin=self.range_min, rmax=self.range_max, lhf=self.low_high_filter,
hlf=self.high_low_filter, mt=self.module_type)
return txt
@property
def high_low_filter(self):
"""Filter time in micro seconds when a digital pin goes from HIGH to LOW"""
buf_type = ct.c_uint32
value = self._get_info(Info.DI_HIGH_LOW_FILTER, buf_type)
return value
@high_low_filter.setter
def high_low_filter(self, value):
c_value = ct.c_uint32(value)
self._set_info(Info.DI_HIGH_LOW_FILTER, c_value)
@property
def low_high_filter(self):
"""Filter time in micro seconds when a digital pin goes from LOW to HIGH"""
buf_type = ct.c_uint32
value = self._get_info(Info.DI_LOW_HIGH_FILTER, buf_type)
return value
@low_high_filter.setter
def low_high_filter(self, value):
c_value = ct.c_uint32(value)
self._set_info(Info.DI_LOW_HIGH_FILTER, c_value)
@property
def value(self):
"""Value on digital input pin (0 or 1)"""
value = ct.c_uint()
dll.kvIoPinGetDigital(self.channel.handle, self.pin, ct.byref(value))
return value.value
[docs]class DigitalOut(IoPin):
@property
def value(self):
"""Value on digital output pin (0 or 1)"""
value = ct.c_uint()
dll.kvIoPinGetOutputDigital(self.channel.handle, self.pin, ct.byref(value))
return value.value
@value.setter
def value(self, value):
c_value = ct.c_uint(value)
dll.kvIoPinSetDigital(self.channel.handle, self.pin, c_value)
[docs]class Relay(IoPin):
@property
def value(self):
"""Value on relay (0:off, 1:on)"""
value = ct.c_uint()
dll.kvIoPinGetOutputRelay(self.channel.handle, self.pin, ct.byref(value))
return value.value
@value.setter
def value(self, value):
c_value = ct.c_uint(value)
dll.kvIoPinSetRelay(self.channel.handle, self.pin, c_value)
def __repr__(self):
txt = "Pin {p}: {pt!r} {d!r} bits={nb} ({mt!r})".format(
p=self.pin, pt=self.pin_type, d=self.direction, nb=self.number_of_bits,
mt=self.module_type)
return txt
_PIN_CLASSES = {
PinType.RELAY: {Direction.IN: Relay, Direction.OUT: Relay},
PinType.ANALOG: {Direction.IN: AnalogIn, Direction.OUT: AnalogOut},
PinType.DIGITAL: {Direction.IN: DigitalIn, Direction.OUT: DigitalOut},
}