Input Modules
In general, there are three possible types of input modules:
-
Input module
: The base module for some input variables and input tags. For example an OPC UA client. -
Input variable
: A module generating data objects, if the configured variable changes. For example a subscription to an OPC UA variable. -
Input tag
: A module providing data of the configured variable, if it is requested. For example reading specific OPC UA variable.
Each of these types is represented as a separate building block during configuration, although they are all described in a single Python file. They are all optional. However, the existence of an input module is only necessary if an input variable or input tag module exists.
Input Module
Input modules are mainly used to establish connections for variable and tag modules. This allows you to reuse the same connection for multiple variable and tag module instances.
Example
class InputModule(AbstractInputModule):
...
@dataclass
class Configuration(models.InputModule):
anonymous: bool = field(
metadata=dict(description="Is authentication required.",
required=False),
default=True)
username: str = field(
metadata=dict(description="The username for the basic authentication.",
required=False),
default='admin')
password: str = field(
metadata=dict(description="The password for the basic authentication.",
required=False),
default='admin')
def __init__(self, configuration: Configuration):
super().__init__(configuration=configuration)
self.session = None
def start(self):
self.session = requests.Session()
if not self.configuration.anonymous:
self.session.auth = (self.configuration.username,
self.configuration.password)
def stop(self):
self.session.close()
When an input module is configured, the module instance is passed to variable and tag modules as input_module_instance
.
There the input module instance can be used.
In order to pass to correct input module, the variable and tag modules need the configuration parameter input_module
.
Input Variable
An input variable is a module that most often executes cyclic logic to initially generate or request data. There are also event-driven subscriptions that receive data objects from third-party systems. Variable modules therefore have no input port - only an output port for forwarding data.
Example
from modules.base.inputs.base import AbstractInputModule, AbstractVariableModule, AbstractTagModule, models
class VariableModule(AbstractVariableModule):
...
@dataclass
class Configuration(models.VariableModule):
input_module: str = field(
metadata=dict(description="The id of the input module.",
required=True),
default=None)
url: str = field(
metadata=dict(description="The url of the server.",
required=False,
dynamic=True),
default="http://127.0.0.1:80/api/v1/test")
interval: float = field(
metadata=dict(description="Interval in seconds to poll the endpoint.",
required=False,
validate=Range(min=0, max=1000)),
default=10)
def __init__(self, configuration: Configuration, input_module_instance=None):
super().__init__(configuration=configuration,
input_module_instance=input_module_instance)
def start(self):
Thread(target=self._subscribe,
daemon=False,
name="REST_Get_Variable_Module_Loop_{0}".format(self.configuration.id)).start()
def _subscribe(self):
"""
This method creates a new loop requesting the endpoint in the given interval.
"""
while self.active and self.input_module_instance.session:
try:
response = self.input_module_instance.session.get(url=self._dyn(url))
response.raise_for_status()
fields = response.json()
data = models.Data(measurement=self.configuration.measurement, fields=fields)
self._call_links(data)
time.sleep(self.configuration.interval)
except Exception as e:
self.logger.error("Something unexpected went wrong while requesting: {0}"
.format(str(e)), exc_info=config.EXC_INFO)
Once you have created the general data object (models.Data
), you can easily pass it to the
linked modules by calling self._call_links(data)
.
Input Tag
Input tags are similar to input variables. However, they do not generate data themselves, but are queried for data. If so, they have to return a dictionary with the corresponding data.
Example
from modules.base.inputs.base import AbstractInputModule, AbstractVariableModule, AbstractTagModule, models
class TagModule(AbstractTagModule):
...
@dataclass
class Configuration(models.TagModule):
key: str = field(
metadata=dict(description="The key of the constant.",
required=True),
default=None)
value: str | float | int | bool | list = field(
metadata=dict(description="The value of the constant.",
required=True),
default=None)
def __init__(self, configuration: Configuration, input_module_instance=None):
super().__init__(configuration=configuration,
input_module_instance=input_module_instance)
def _run(self) -> dict[str, Any]:
"""
This methods returns the queried data.
:returns: A dict containing the generated key-value pairs.
"""
return {self.configuration.key: self.configuration.value}
Whether the returned data is attached to the existing data object as fields or tags is user-defined and handled by the underlying framework.
Data Requirements
Template