Introduction

The eta_utility.connectors module is meant to provide a standardized interface for multiple different protocols which are used in factory operations or for the optimization of factory operations. Two important protocols which we encounter regularly are Modbus TCP and OPC UA. In addition to this we have also created connectors for additional API which we work with.

The eta_utility connector format has the advantage that it can be used for many different kinds of protocols and APIs, with the limitation that some of them do not support all functions (for example, specific APIs/protocols may not provide write access). Each connection can contain multiple Nodes (see below). These are used as the default data points when reading data from the connection. Read data will be returned in a pandas.DataFrame with the node names as column names.

The connectors module also provides subscription handlers which take data read from connections in regular intervals and for example store it in files or in memory for later access. These subscription handlers can handle multiple different connections (with different protocols) at the same time.

The LiveConnect class is specifically designed to combine the functionality from the eta_x and connectors modules. It can establish connections and provides an interface equivalent to the classes in the simulators module. This allows easy substitution of simulation models with actual connections to real machines (or the other way). When trying to deploy a model into operation this substitution can be very useful.

The connectors are based on the concept of Nodes to which we establish connections. A Node object is a unique description of a specific data point and includes all information required to establish a connection and read data from the specified data point. Each data point has its own node, not just each device (or connection) that we are connecting to. Therefore, Nodes are the easiest way to instantiate connections, however they can be a bit unwieldy to work with when trying to read many different data points from the same device.

See Connection instantiation for more information on how to create connections.

Nodes

Each Node object uniquely identifies a specific data point. All Node objects have some information in common. This information idenfies the device which the data point belongs to and can also contain information required for authentication with the device. Depending on the protocol the Node object contains additional information to correctly identify the data points.

The URL may contain the username and password (schema://username:password@hostname:port/path). This is handled automatically by the connectors and the username and password will be removed before creating a connection.

class eta_utility.connectors.Node(name: str, url: str, protocol: str, *args: Any, **kwargs: Any)[source]

The node objects represents a single variable. Valid keyword arguments depend on the protocol.

The following classes are there to document the required parameters for each type of node.

class eta_utility.connectors.node.NodeLocal(name: str, url: str, protocol: str, *args: Any, **kwargs: Any)[source]

Local Node (no specific protocol), useful for example to manually provide data to subscription handlers.

name: str

Name for the node.

url: str

URL of the connection.

url_parsed: ParseResult

Parse result object of the URL (in case more post-processing is required).

usr: str | None

Username for login to the connection (default: None).

pwd: str | None

Password for login to the connection (default: None).

interval: str | None

Interval

dtype: Callable | None

Data type of the node (for value conversion). Note that strings will be interpreted as utf-8 encoded. If you do not want this behaviour, use ‘bytes’.

Node Typing

Eta-utility provides a generic type Nodes[N] which can be used to type a sequence or set of nodes. This is useful when working with multiple nodes and you want to ensure that all nodes are of the same type.

from eta_utility.connectors import Node, NodeModbus, NodeOpcUa
from eta_utility.type_hints import Nodes

# Example of typing nodes
modbus_node: NodeModbus = Node("modbus://localhost:502/0/0", "modbus", ...)
opcua_node: NodeOpcUa = Node("opcua://localhost:4840/0/0", "opcua", ...)
nodes: Nodes[NodeModbus] = [modbus_node]

# This will raise a type error
nodes: Nodes[NodeModbus] = [modbus_node, opcua_node]

# Use *Node* to allow different types of nodes
nodes: Nodes[Node] = [modbus_node, opcua_node]
# or explicitly list the types
nodes: Nodes[NodeModbus | NodeOpcUa] = [modbus_node, opcua_node]

Connection Instantiation

There are multiple ways to instantiate connections, depending on the use case. The two most common methods are BaseConnection.from_node() and BaseConnection.from_nodes().
Instantiation with from_node(s) is useful if you already have created some node(s) and would like to create connection(s) from them.
Each connection class also has its own _from_node() method, since the necessary/accepted keywords might differ. To create connections, a password and a username are often required. For setting these the following prioritization applies:
  • If a password is given in the node, take it.

  • If there is no password there, take as “default” a password from the arguments.

  • If there is neither, the username and password are empty.

Creating one or more Connections from node(s)

If you have one or multiple nodes, use from_nodes. Create all of the Node objects first and pass them in a list. from_nodes then returns a dictionary of connections and automatically assigns the nodes to their correct connection. It requires less duplicate information than direct instantiation.

Create one Connection

If you have one or more Node objects for the same hostname/protocol and just want to create one connection, you should use the from_node method of the BaseConnection class.

BaseConnection.from_node(node: Nodes, **kwargs: Any) BaseConnection | dict[str, Any]
Initialize the connection object from a node object. If multiple nodes are passed,

a list of connections is returned.

Parameters:
  • node – Node(s) to initialize from.

  • kwargs – Other arguments are ignored.

Returns:

BaseConnection object or dictionary of BaseConnections

Using from_ids

The from_ids() method is helpful if you do not require access to the nodes and just want to quickly create a single connection.

Note

This is not available for all connectors, since the concept of IDs does not apply universally. An example is shown here. Refer to the API documentation of the connector you would like to use to see if the method exists and which parameters are required.

EnEffCoConnection.from_ids(ids: Sequence[str], url: str, usr: str, pwd: str, api_token: str) EnEffCoConnection[source]

Initialize the connection object from an EnEffCo protocol through the node IDs

Parameters:
  • ids – Identification of the Node.

  • url – URL for EnEffco connection.

  • usr – Username for EnEffCo login.

  • pwd – Password for EnEffCo login.

  • api_token – Token for API authentication.

Returns:

EnEffCoConnection object.