Source code for canlib.canlib.iopin

"""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}, }