Creating a new component¶
In the following tutorial, we will show all the basic features that need to be programmed to build a Component from scratch in pybehave. A user might decide to create a new component if their specific hardware has additional features that aren't fully captured by the existing abstractions.
Component overview¶
Creating new components is relatively simple requiring only four steps: choosing which subclass of Component to extend,
defining the Component type, setting up the __init__ method, and specifying update behavior. Additional methods and functionality
can of course be added and interacted with from a Task or GUI but these four steps are the only ones that are absolutely required.
Subclassing¶
All components inherit at some level from the base Component class. However, a new custom component can inherit one of the many
subclasses to reuse some functionality. The choice of which class gets extended depends on how much behavior the new custom component
needs to add/replace. Additionally, subclassing a component can make the custom component compatible with tasks that only specify
the base class in their get_components methods. One good example is the OEBinaryInput component that adds additional functionality
to the base BinaryInput class to make it compatible with the information format received from OpenEphys. This component has
the following class declaration:
class OEBinaryInput(BinaryInput):
Component types¶
All components are one of six types: DIGITAL_INPUT, DIGITAL_OUTPUT, ANALOG_INPUT, ANALOG_OUTPUT, INPUT, OUTPUT, or BOTH.
These types have varying levels of restrictions on the kind of data that component is expected to handle which can be used
by a connected Source to format the data it forwards to a component. The type for a custom component is indicated by overriding
the get_type method:
@staticmethod
def get_type() -> Component.Type:
return Component.Type.DIGITAL_INPUT
This method only needs to be overridden if extending the base Component class or if the type of the custom component differs from the class it's extending.
The __init__ method¶
The component __init__ method takes the task, component ID, and component address as inputs. The method only needs to be
overridden if the custom component has to set default values for the component state that differ from the super class or define
other attributes. If new attributes are declared, these can be overridden with values from the metadata field in the corresponding
AddressFile entry. An example of declaring these custom attributes is shown below for the OEBinaryInput:
def __init__(self, task: Task, component_id: str, component_address: str):
super().__init__(task, component_id, component_address)
self.rising = True
self.falling = False
update¶
The update method should be overridden to specify the data type that this component expects to receive from a Source and
implement the correct behavior in response. If the state of the component has changed based on the information it has received,
this method should return true and false otherwise. An example override for the OEBinaryInput is shown below that handles
input data specifically in the form of a dict:
def update(self, value: dict) -> bool:
if self.rising and self.falling:
if not self.state and value['metaData']['Direction'] == '1' and value['data']:
self.state = True
return True
elif self.state and value['metaData']['Direction'] == '0' and not value['data']:
self.state = False
return True
else:
if not self.state and value['data']:
self.state = True
return True
elif self.state and not value['data']:
self.state = False
return True
return False