diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..5b8f756 --- /dev/null +++ b/readme.md @@ -0,0 +1,81 @@ +# OPC-UA to MQTT Bridge + +## Installation + +* Take the tarball from the Release area and unpack it in the desired location on the edge device +* The systemctl service script assumes to find the application at `/home/alarm/KROHNE/opcua2mqtt-bridge`, so it is a good idea to put it there +* Create a configuration directory `/etc/opcua2mqtt-bridge` and place the adjusted `config.json` into it +* Enable the service using `sudo systemctl enable /home/alarm/KROHNE/opcua2mqtt-bridge/opcua2mqtt-bridge.service` +* Start the service using `sudo systemctl start opcua2mqtt-bridge` +* Check the log messages using `journalctl -f` + +## Configuration + +`` +{ + "mqtt": { + "broker": "172.16.2.16", + "port": 1883, + "publishTopicPrefix": "opcua" + }, + "stats": { + "topic": "statistics", + "period": 60 + }, + "opcua": [ + { + "enabled": "true", + "type": "flat", + "url": "opc.tcp://172.16.3.60:4840", + "name": "apl", + "period": 1.0, + "timeout": 1.0, + "nodes": [ + { "ns": 0, "n": "i=345", "d": "pv" }, + { "ns": 0, "n": "i=348", "d": "sv" }, + { "ns": 0, "n": "i=351", "d": "tv" }, + { "ns": 0, "n": "i=354", "d": "qv" } + ] + } + ] +} +`` + +The configuration object consists of three parts: `mqtt`, `stats` and `opcua`. + +In `mqtt` the access to the broker is configuration and the prefix for the topics used by the statistics module and the actual bridge are defined. +Besides the above shown attributes the `login` and `password` for authentication at the broker and `ca`, `cert` and `key` for TLS connections to the broker are available. `ca`, `cert` and `key` contain the filenames including complete path of the particular files. + +In `stats` the topic suffix and the period of statistics messages are defined. The complete suffix will be `${mqtt.publicTopicPrefix}/${stats.topic}`. + +The section `opcua` contains a list of OPC-UA servers to be queried. Each entry can be enabled/disabled using the attribute `enabled`. The attribute `url` obviously has the URL to connect the server, `period` is the period to repeat queries and `timeout` is the timeout when talking to a server. +`name` identifies the particular server, it becomes part of the topic of the published MQTT messages. The attribute `type` defines whether the individual variables of an OPC-UA server shall be communicated in individual MQTT messages (`flat`) or all variables of a server in a single message (`structured`). + +The attribute `nodes` finally contains the list of variables to be queried. It contains the namespace index (`ns`), the node-id (`n`) and an optional descriptive name (`d`). Namespace index and node-id can be determined using for instance UAExpert when browsing the server and navigating to the relevant variables. The descriptive name - if given, otherwise the display name of the variable is used - becomes in `flat` mode part of the topic, in `structured` mode it becomes an attribute name within the message. + +In `flat` mode the final topic will be `${mqtt.publicTopicPrefix}/${opcua.name}/${opcua.node.ns}/${opcua.node.d}` + +An example for the MQTT messages according to the above configuration in `flat` mode is: + +`` +opcua/apl/0/pv {"serverName": "apl", "nameSpaceIndex": 0, "variableName": "pv", "value": 19.849281311035156} +opcua/apl/0/sv {"serverName": "apl", "nameSpaceIndex": 0, "variableName": "sv", "value": 1688.5152587890625} +opcua/apl/0/tv {"serverName": "apl", "nameSpaceIndex": 0, "variableName": "tv", "value": 22.574615478515625} +opcua/apl/0/qv {"serverName": "apl", "nameSpaceIndex": 0, "variableName": "qv", "value": NaN} +opcua/apl/0/pv {"serverName": "apl", "nameSpaceIndex": 0, "variableName": "pv", "value": 19.849281311035156} +opcua/apl/0/sv {"serverName": "apl", "nameSpaceIndex": 0, "variableName": "sv", "value": 1688.5152587890625} +opcua/apl/0/tv {"serverName": "apl", "nameSpaceIndex": 0, "variableName": "tv", "value": 22.574615478515625} +opcua/apl/0/qv {"serverName": "apl", "nameSpaceIndex": 0, "variableName": "qv", "value": NaN} +`` + +In `structured` mode the final topic will be `${mqtt.publicTopicPrefix}/${opcua.name}` + +An example for the MQTT messages according to the above configuration in `flat` mode is: + +`` +opcua/apl {"pv": 19.844480514526367, "sv": 1689.9193115234375, "tv": 22.68524169921875, "qv": NaN} +opcua/apl {"pv": 19.844480514526367, "sv": 1689.9193115234375, "tv": 22.68524169921875, "qv": NaN} +opcua/apl {"pv": 19.844480514526367, "sv": 1689.9193115234375, "tv": 22.68524169921875, "qv": NaN} +opcua/apl {"pv": 19.844480514526367, "sv": 1689.93701171875, "tv": 22.620391845703125, "qv": NaN} +`` + diff --git a/src/OpcUaRequester.py b/src/OpcUaRequester.py index 0f4e2aa..1187711 100644 --- a/src/OpcUaRequester.py +++ b/src/OpcUaRequester.py @@ -17,7 +17,7 @@ class OpcUaRequester(threading.Thread): self.name = self.config['name'] self.url = self.config['url'] self.nodes = self.config['nodes'] - self.delay = self.config['delay'] + self.period = self.config['period'] self.timeout = self.config['timeout'] self.dataObjectType = self.config['type'] self.flat = self.dataObjectType == 'flat' @@ -49,7 +49,7 @@ class OpcUaRequester(threading.Thread): logger.error(f"UaError in inner OPC-UA loop: {type(e)} {e}") if not self.flat: self.queue.put(dataObject) - await asyncio.sleep(self.delay) + await asyncio.sleep(self.period) except asyncio.exceptions.TimeoutError as e: self.stats.incOpcUaTimeouts() logger.error(f"Timeout in inner OPC-UA loop") diff --git a/src/StructuredDataObject.py b/src/StructuredDataObject.py index c37a15a..e4e19e5 100644 --- a/src/StructuredDataObject.py +++ b/src/StructuredDataObject.py @@ -6,10 +6,10 @@ from AbstractDataObject import AbstractDataObject class StructuredDataObject(AbstractDataObject): def __init__(self, topicPart): super().__init__(topicPart) - self.keyValuePairs = [] + self.keyValuePairs = {} def add(self, key, value): - self.keyValuePairs.append({key: value}) + self.keyValuePairs[key] = value def getPayload(self): return json.dumps(self.keyValuePairs) diff --git a/src/config.json b/src/config.json index d6829a0..e9d5540 100644 --- a/src/config.json +++ b/src/config.json @@ -11,10 +11,10 @@ "opcua": [ { "enabled": "true", - "type": "flat", + "type": "structured", "url": "opc.tcp://172.16.3.60:4840", "name": "apl", - "delay": 1.0, + "period": 1.0, "timeout": 1.0, "nodes": [ { "ns": 0, "n": "i=345", "d": "pv" }, @@ -28,7 +28,7 @@ "type": "flat", "url": "opc.tcp://192.168.254.5:4863", "name": "sh", - "delay": 1.0, + "period": 1.0, "timeout": 10.0, "nodes": [ { "ns": 1, "n": "s=t|SERVER::A201CD124/MOT_01.AV_Out#Value", "d": "A201CD124" },