# coding: utf-8
from typing import Union
from .block import Block
from ..inout import inandout_dict, in_dict, out_dict, inout_dict
[docs]class IOBlock(Block):
"""This block is used to communicate with :ref:`In / Out` objects.
They can be used as sensor, actuators or both.
"""
[docs] def __init__(self,
name: str,
freq: float = None,
verbose: bool = False,
labels: list = None,
cmd_labels: list = None,
trigger: int = None,
streamer: bool = False,
initial_cmd: Union[float, list] = 0,
exit_values: list = None,
spam: bool = False,
**kwargs) -> None:
"""Sets the args and initializes the parent class.
Args:
name (:obj:`str`): The name of the :ref:`In / Out` class to instantiate.
freq (:obj:`float`, optional): The looping frequency of the block, if
:obj:`None` will go as fast as possible.
verbose (:obj:`bool`, optional): Prints extra information if :obj:`True`.
labels (:obj:`list`, optional): A :obj:`list` of the output labels.
cmd_labels (:obj:`list`, optional): The :obj:`list` of the labels
considered as inputs for this block. Will call :meth:`set_cmd` in the
:ref:`In / Out` object with the values received on this labels.
trigger (:obj:`int`, optional): If the block is triggered by another
block, this must specify the index of the input considered as a
trigger. The data going through this link is discarded, add another
link if the block should also consider it as an input.
streamer (:obj:`bool`, optional): If :obj:`False`, will call
:meth:`get_data` else, will call :meth:`get_stream` in the
:ref:`In / Out` object (only if it has these methods, of course).
initial_cmd (:obj:`list`, optional): The initial values for the outputs,
sent during :meth:`prepare`. If it is a single value, then it will send
this same value for all the output labels.
exit_values (:obj:`list`, optional): If not :obj:`None`, the outputs will
be set to these values when Crappy is ending (or crashing).
spam (:obj:`bool`, optional): If False (default), the block will only
call set_cmd on the InOut object if the command changed
**kwargs: The arguments to be passed to the :ref:`In / Out` class.
"""
Block.__init__(self)
self.niceness = -10
self.freq = freq
self.verbose = verbose
self.labels = labels
self.cmd_labels = [] if cmd_labels is None else cmd_labels
self.trigger = trigger
self.streamer = streamer
self.initial_cmd = initial_cmd
self.exit_values = exit_values
self.spam = spam
if self.labels is None:
if self.streamer:
self.labels = ['t(s)', 'stream']
else:
self.labels = ['t(s)'] + \
[str(c) for c in kwargs.get("channels", ['1'])]
self.device_name = name.capitalize()
self.device_kwargs = kwargs
self.stream_idle = True
if not isinstance(self.initial_cmd, list):
self.initial_cmd = [self.initial_cmd] * len(self.cmd_labels)
if not isinstance(self.exit_values, list) and self.exit_values is not None:
self.exit_values = [self.exit_values] * len(self.cmd_labels)
if self.exit_values is not None:
assert len(self.exit_values) == len(self.cmd_labels),\
'Invalid number of exit values!'
self.device = inout_dict[self.device_name](**self.device_kwargs)
[docs] def prepare(self) -> None:
self.to_get = list(range(len(self.inputs)))
if self.trigger is not None:
self.to_get.remove(self.trigger)
self.mode = 'r' if self.outputs else ''
self.mode += 'w' if self.to_get else ''
assert self.mode != '', "ERROR: IOBlock is neither an input nor an output!"
if 'w' in self.mode:
assert self.cmd_labels, "ERROR: IOBlock has an input block but no " \
"cmd_labels specified!"
if self.mode == 'rw' and self.device_name not in inandout_dict:
raise IOError("The IOBlock has inputs and outputs but the Inout class "
"is not rw")
elif self.mode == 'r' and self.device_name not in in_dict:
raise IOError("The IOBlock has inputs but the Inout class is write-only")
elif self.mode == 'w' and self.device_name not in out_dict:
raise IOError("The IOBlock has outputs but the Inout class is read-only")
self.device.open()
if 'w' in self.mode:
self.device.set_cmd(*self.initial_cmd)
self.last = None
[docs] def read(self) -> None:
"""Will read the device and send the data."""
if self.streamer:
if self.stream_idle:
self.device.start_stream()
self.stream_idle = False
data = self.device.get_stream()
else:
data = self.device.get_data()
if isinstance(data, dict):
if 't(s)' in data:
data['t(s)'] -= self.t0
elif isinstance(data[0], list):
data[0] = [i - self.t0 for i in data[0]]
else:
data[0] -= self.t0
self.send(data)
def loop(self) -> None:
if 'r' in self.mode:
if self.trigger is not None:
# To avoid useless loops if triggered input only
if self.mode == 'r' or self.inputs[self.trigger].poll():
self.inputs[self.trigger].recv()
self.read()
else:
self.read()
if 'w' in self.mode:
lst = self.get_last(self.to_get)
cmd = [lst[label] for label in self.cmd_labels]
if cmd != self.last or self.spam:
self.device.set_cmd(*cmd)
self.last = cmd
[docs] def finish(self) -> None:
if self.streamer:
self.device.stop_stream()
if self.exit_values is not None:
self.device.set_cmd(*self.exit_values)
self.device.close()