1
0
mirror of https://github.com/thingsboard/thingsboard-gateway synced 2025-10-26 22:31:42 +08:00
Files
thingsboard-gateway/thingsboard_gateway/connectors/opcua/opcua_connector.py
2024-05-14 08:33:45 +03:00

777 lines
41 KiB
Python

# Copyright 2024. ThingsBoard
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
import time
from uuid import UUID
from concurrent.futures import CancelledError, TimeoutError as FuturesTimeoutError
from copy import deepcopy
from random import choice
from string import ascii_lowercase
from threading import Thread
from cachetools import cached, TTLCache
import regex
from simplejson import dumps
from thingsboard_gateway.tb_utility.tb_loader import TBModuleLoader
from thingsboard_gateway.tb_utility.tb_utility import TBUtility
from thingsboard_gateway.gateway.statistics_service import StatisticsService
from thingsboard_gateway.tb_utility.tb_logger import init_logger
try:
from opcua import Client, Node, ua
except ImportError:
print("OPC-UA library not found")
TBUtility.install_package("opcua")
from opcua import Client, Node, ua
try:
from opcua.crypto import uacrypto
except ImportError:
TBUtility.install_package("cryptography")
from opcua.crypto import uacrypto
from thingsboard_gateway.connectors.connector import Connector
from thingsboard_gateway.connectors.opcua.opcua_uplink_converter import OpcUaUplinkConverter
class OpcUaConnector(Thread, Connector):
def __init__(self, gateway, config, connector_type):
self._connector_type = connector_type
self.statistics = {'MessagesReceived': 0,
'MessagesSent': 0}
super().__init__()
self.__gateway = gateway
self._config = config
self.__id = self._config.get('id')
self.__server_conf = config.get("server")
self.name = self._config.get("name",
'OPC-UA ' + ''.join(choice(ascii_lowercase) for _ in range(5)) + " Connector")
self._log = init_logger(self.__gateway, self.name, self._config.get('logLevel', 'INFO'),
enable_remote_logging=self._config.get('enableRemoteLogging', False))
self._log.warning("OPC-UA Connector is deprecated and will be removed in the release v.4.0")
self.__interest_nodes = []
self.__available_object_resources = {}
self.__show_map = self.__server_conf.get("showMap", False)
self.__previous_scan_time = 0
for mapping in self.__server_conf["mapping"]:
if mapping.get("deviceNodePattern") is not None:
self.__interest_nodes.append({mapping["deviceNodePattern"]: mapping})
else:
self._log.error(
"deviceNodePattern in mapping: %s - not found, add property deviceNodePattern to processing this mapping",
dumps(mapping))
if "opc.tcp" not in self.__server_conf.get("url"):
self.__opcua_url = "opc.tcp://" + self.__server_conf.get("url")
else:
self.__opcua_url = self.__server_conf.get("url")
self.client = None
self.__connected = False
self.__sub_handler = SubHandler(self)
self.data_to_send = []
self.__stopped = False
self.daemon = True
def is_connected(self):
return self.__connected
def is_stopped(self):
return self.__stopped
def get_type(self):
return self._connector_type
def open(self):
self.__stopped = False
self.start()
self._log.info("Starting OPC-UA Connector")
def __create_client(self):
if self.client:
try:
# Always try to disconnect to release resource on client and server side!
self.client.disconnect()
except:
pass
self.client = None
self.client = Client(self.__opcua_url, timeout=self.__server_conf.get("timeoutInMillis", 4000) / 1000)
if self.__server_conf.get('uri'):
self.client.application_uri = self.__server_conf['uri']
if self.__server_conf["identity"].get("type") == "cert.PEM":
self.__set_auth_settings_by_cert()
if self.__server_conf["identity"].get("username"):
self.__set_auth_settings_by_username()
self.__available_object_resources = {}
self.__opcua_nodes = {}
self._subscribed = {}
self.__sub = None
self.__connected = False
def __connect(self):
self.__create_client()
while not self.__connected and not self.__stopped:
try:
self.client.connect()
try:
self.client.load_type_definitions()
except Exception as e:
self._log.error("Error on loading type definitions:\n %s", e)
self._log.debug(self.client.get_namespace_array()[-1])
self._log.debug(self.client.get_namespace_index(self.client.get_namespace_array()[-1]))
self.__initialize_client()
if not self.__server_conf.get("disableSubscriptions", False):
self.__sub = self.client.create_subscription(self.__server_conf.get("subCheckPeriodInMillis", 500),
self.__sub_handler)
self.__connected = True
self._log.info("OPC-UA connector %s connected to server %s", self.get_name(),
self.__server_conf.get("url"))
except ConnectionRefusedError:
self._log.error("Connection refused on connection to OPC-UA server with url %s",
self.__server_conf.get("url"))
time.sleep(10)
except OSError:
self._log.error("Connection refused on connection to OPC-UA server with url %s",
self.__server_conf.get("url"))
time.sleep(10)
except Exception as e:
self._log.error("error on connection to OPC-UA server.\n %s", e)
time.sleep(10)
def run(self):
while not self.__stopped:
try:
time.sleep(.2)
self.__check_connection()
if not self.__connected and not self.__stopped:
self.__connect()
elif not self.__stopped:
if self.__server_conf.get("disableSubscriptions", False) and time.time() * 1000 - self.__previous_scan_time > self.__server_conf.get(
"scanPeriodInMillis", 60000):
self.scan_nodes_from_config()
self.__previous_scan_time = time.time() * 1000
# giusguerrini, 2020-09-24: Fix: flush event set and send all data to platform,
# so data_to_send doesn't grow indefinitely in case of more than one value change
# per cycle, and platform doesn't lose events.
# NOTE: possible performance improvement: use a map to store only one event per
# variable to reduce frequency of messages to platform.
while self.data_to_send:
self.__gateway.send_to_storage(self.get_name(), self.get_id(), self.data_to_send.pop())
if self.__stopped:
self.close()
break
except (KeyboardInterrupt, SystemExit):
self.close()
raise
except FuturesTimeoutError:
self.__check_connection()
except Exception as e:
self._log.error("Connection failed on connection to OPC-UA server with url %s\n %s",
self.__server_conf.get("url"), e)
time.sleep(10)
def __set_auth_settings_by_cert(self):
try:
ca_cert = self.__server_conf["identity"].get("caCert")
private_key = self.__server_conf["identity"].get("privateKey")
cert = self.__server_conf["identity"].get("cert")
security_mode = self.__server_conf["identity"].get("mode", "SignAndEncrypt")
policy = self.__server_conf["security"]
if cert is None or private_key is None:
self._log.exception("Error in ssl configuration - cert or privateKey parameter not found")
raise RuntimeError("Error in ssl configuration - cert or privateKey parameter not found")
security_string = policy + ',' + security_mode + ',' + cert + ',' + private_key
if ca_cert is not None:
security_string = security_string + ',' + ca_cert
self.client.set_security_string(security_string)
except Exception as e:
self._log.exception(e)
def __set_auth_settings_by_username(self):
self.client.set_user(self.__server_conf["identity"].get("username"))
if self.__server_conf["identity"].get("password"):
self.client.set_password(self.__server_conf["identity"].get("password"))
def __check_connection(self):
try:
if self.client:
node = self.client.get_root_node()
node.get_children()
else:
self.__connected = False
except ConnectionRefusedError:
self.__connected = False
except OSError:
self.__connected = False
except FuturesTimeoutError:
self.__connected = False
except AttributeError:
self.__connected = False
except Exception as e:
self.__connected = False
self._log.exception(e)
def close(self):
self.__stopped = True
if self.client:
try:
# Always try to disconnect to release resource on client and server side!
self.client.disconnect()
except:
pass
self.__connected = False
self._log.info('%s has been stopped.', self.get_name())
self._log.stop()
def get_id(self):
return self.__id
def get_name(self):
return self.name
@StatisticsService.CollectAllReceivedBytesStatistics(start_stat_type='allReceivedBytesFromTB')
def on_attributes_update(self, content):
self._log.debug(content)
try:
for server_variables in self.__available_object_resources[content["device"]]['variables']:
for attribute in content["data"]:
for variable in server_variables:
if attribute == variable:
try:
if isinstance(content["data"][variable], int):
dv = ua.DataValue(ua.Variant(content["data"][variable], server_variables[
variable].get_data_type_as_variant_type()))
server_variables[variable].set_value(dv)
else:
server_variables[variable].set_value(content["data"][variable])
except Exception:
server_variables[variable].set_attribute(ua.AttributeIds.Value,
ua.DataValue(content["data"][variable]))
except Exception as e:
self._log.exception(e)
@StatisticsService.CollectAllReceivedBytesStatistics(start_stat_type='allReceivedBytesFromTB')
def server_side_rpc_handler(self, content):
try:
if content.get('data') is None:
content['data'] = {'params': content['params'], 'method': content['method']}
rpc_method = content["data"].get("method")
# check if RPC type is connector RPC (can be only 'get' or 'set')
try:
(connector_type, rpc_method_name) = rpc_method.split('_')
if connector_type == self._connector_type:
rpc_method = rpc_method_name
content['device'] = content['params'].split(' ')[0].split('=')[-1]
except (ValueError, IndexError):
pass
# firstly check if a method is not service
if rpc_method == 'set' or rpc_method == 'get':
full_path = ''
args_list = []
device = content.get('device')
try:
args_list = content['data']['params'].split(';')
if 'ns' in content['data']['params']:
full_path = ';'.join([item for item in (args_list[0:-1] if rpc_method == 'set' else args_list)])
else:
full_path = args_list[0].split('=')[-1]
except IndexError:
self._log.error('Not enough arguments. Expected min 2.')
self.__gateway.send_rpc_reply(device=device,
req_id=content['data'].get('id'),
content={content['data'][
'method']: 'Not enough arguments. Expected min 2.',
'code': 400})
node_list = []
self.__search_node(current_node=device, fullpath=full_path, result=node_list)
node = None
try:
node = node_list[0]
except IndexError:
self.__gateway.send_rpc_reply(device=device, req_id=content['data'].get('id'),
content={content['data']['method']: 'Node didn\'t find!',
'code': 500})
if rpc_method == 'get':
self.__gateway.send_rpc_reply(device=device,
req_id=content['data'].get('id'),
content={content['data']['method']: node.get_value(), 'code': 200})
else:
try:
value = args_list[2].split('=')[-1]
node.set_value(value)
self.__gateway.send_rpc_reply(device=device,
req_id=content['data'].get('id'),
content={'success': 'true', 'code': 200})
except ValueError:
self._log.error('Method SET take three arguments!')
self.__gateway.send_rpc_reply(device=device,
req_id=content['data'].get('id'),
content={'error': 'Method SET take three arguments!',
'code': 400})
except ua.UaStatusCodeError:
self._log.error('Write method doesn\'t allow!')
self.__gateway.send_rpc_reply(device=device,
req_id=content['data'].get('id'),
content={'error': 'Write method doesn\'t allow!', 'code': 400})
for method in self.__available_object_resources[content["device"]]['methods']:
if rpc_method is not None and method.get(rpc_method) is not None:
arguments_from_config = method["arguments"]
arguments = content["data"].get("params") if content["data"].get(
"params") is not None else arguments_from_config
try:
if isinstance(arguments, list):
result = method["node"].call_method(method[rpc_method], *arguments)
elif arguments is not None:
try:
result = method["node"].call_method(method[rpc_method], arguments)
except ua.UaStatusCodeError as e:
if "BadTypeMismatch" in str(e) and isinstance(arguments, int):
result = method["node"].call_method(method[rpc_method], float(arguments))
else:
result = method["node"].call_method(method[rpc_method])
self.__gateway.send_rpc_reply(content["device"],
content["data"]["id"],
{content["data"]["method"]: result, "code": 200})
self._log.debug("method %s result is: %s", method[rpc_method], result)
except Exception as e:
self._log.exception(e)
self.__gateway.send_rpc_reply(content["device"], content["data"]["id"],
{"error": str(e), "code": 500})
else:
self._log.error("Method %s not found for device %s", rpc_method, content["device"])
self.__gateway.send_rpc_reply(content["device"], content["data"]["id"],
{"error": "%s - Method not found" % rpc_method, "code": 404})
except Exception as e:
self._log.exception(e)
def __initialize_client(self):
self.__opcua_nodes["root"] = self.client.get_objects_node()
self.__opcua_nodes["objects"] = self.client.get_objects_node()
self.scan_nodes_from_config()
self.__previous_scan_time = time.time() * 1000
self._log.debug('Subscriptions: %s', self.subscribed)
self._log.debug("Available methods: %s", self.__available_object_resources)
def scan_nodes_from_config(self):
try:
if self.__interest_nodes:
for device_object in self.__interest_nodes:
for current_device in device_object:
try:
device_configuration = device_object[current_device]
devices_info_array = self.__search_general_info(device_configuration)
for device_info in devices_info_array:
if device_info is not None and device_info.get("deviceNode") is not None:
self.__search_nodes_and_subscribe(device_info)
self.__save_methods(device_info)
self.__search_attribute_update_variables(device_info)
else:
self._log.error("Device node is None, please check your configuration.")
self._log.debug("Current device node is: %s",
str(device_configuration.get("deviceNodePattern")))
break
except BrokenPipeError:
self._log.debug("Broken Pipe. Connection lost.")
except OSError:
self._log.debug("Stop on scanning.")
except FuturesTimeoutError:
self.__check_connection()
except Exception as e:
self._log.exception(e)
self._log.debug(self.__interest_nodes)
except Exception as e:
self._log.exception(e)
def __search_nodes_and_subscribe(self, device_info):
sub_nodes = []
information_types = {"attributes": "attributes", "timeseries": "telemetry"}
for information_type in information_types:
for information in device_info["configuration"][information_type]:
config_path = TBUtility.get_value(information["path"], get_tag=True)
information_path = self._check_path(config_path, device_info["deviceNode"])
information["path"] = '${%s}' % information_path
information_key = information['key']
information_nodes = []
self.__search_node(device_info["deviceNode"], information_path, result=information_nodes)
if len(information_nodes) == 0:
self._log.error("No nodes found for: %s - %s - %s", information_type, information["key"], information_path)
for information_node in information_nodes:
changed_key = False
if information_node is not None:
try:
information_value = information_node.get_value()
except:
self._log.error("Err get_value: %s", str(information_node))
continue
if device_info.get("uplink_converter") is None:
configuration = {**device_info["configuration"],
"deviceName": device_info["deviceName"],
"deviceType": device_info["deviceType"]}
if device_info["configuration"].get('converter') is None:
converter = OpcUaUplinkConverter(configuration, self._log)
else:
converter = TBModuleLoader.import_module(self._connector_type,
device_info["configuration"].get('converter'))(
configuration, self._log)
device_info["uplink_converter"] = converter
else:
converter = device_info["uplink_converter"]
self.subscribed[information_key] = {"converter": converter,
"information_node": information_node,
"information": information,
"information_type": information_type}
# Use Node name if param "key" not found in config
if not information.get('key'):
information['key'] = information_node.get_browse_name().Name
self.subscribed[information_key]['key'] = information['key']
changed_key = True
self._log.debug("Node for %s \"%s\" with path: %s - FOUND! Current values is: %s",
information_type,
information_key,
information_path,
str(information_value))
if not device_info.get(information_types[information_type]):
device_info[information_types[information_type]] = []
converted_data = converter.convert((information, information_type), information_value)
self.statistics['MessagesReceived'] = self.statistics['MessagesReceived'] + 1
self.data_to_send.append(converted_data)
self.statistics['MessagesSent'] = self.statistics['MessagesSent'] + 1
self._log.debug("Data to ThingsBoard: %s", converted_data)
if not self.__server_conf.get("disableSubscriptions", False):
sub_nodes.append(information_node)
else:
self._log.error("Node for %s \"%s\" with path %s - NOT FOUND!", information_type,
information['key'], information_path)
if changed_key:
information['key'] = None
if not self.__server_conf.get("disableSubscriptions", False):
if self.__sub is None:
self.__sub = self.client.create_subscription(self.__server_conf.get("subCheckPeriodInMillis", 500),
self.__sub_handler)
if sub_nodes:
self.__sub.subscribe_data_change(sub_nodes)
self._log.debug("Added subscription to nodes: %s", str(sub_nodes))
def __save_methods(self, device_info):
try:
if self.__available_object_resources.get(device_info["deviceName"]) is None:
self.__available_object_resources[device_info["deviceName"]] = {}
if self.__available_object_resources[device_info["deviceName"]].get("methods") is None:
self.__available_object_resources[device_info["deviceName"]]["methods"] = []
if device_info["configuration"].get("rpc_methods", []):
node = device_info["deviceNode"]
for method_object in device_info["configuration"]["rpc_methods"]:
method_node_path = self._check_path(method_object["method"], node)
methods = []
self.__search_node(node, method_node_path, True, result=methods)
for method in methods:
if method is not None:
node_method_name = method.get_display_name().Text
self.__available_object_resources[device_info["deviceName"]]["methods"].append(
{node_method_name: method, "node": node, "arguments": method_object.get("arguments")})
else:
self._log.error("Node for method with path %s - NOT FOUND!", method_node_path)
except Exception as e:
self._log.exception(e)
def __search_attribute_update_variables(self, device_info):
try:
if device_info["configuration"].get("attributes_updates", []):
node = device_info["deviceNode"]
device_name = device_info["deviceName"]
if self.__available_object_resources.get(device_name) is None:
self.__available_object_resources[device_name] = {}
if self.__available_object_resources[device_name].get("variables") is None:
self.__available_object_resources[device_name]["variables"] = []
for attribute_update in device_info["configuration"]["attributes_updates"]:
attribute_path = self._check_path(attribute_update["attributeOnDevice"], node)
attribute_nodes = []
self.__search_node(node, attribute_path, result=attribute_nodes)
for attribute_node in attribute_nodes:
if attribute_node is not None:
if self.get_node_path(attribute_node) == attribute_path:
self.__available_object_resources[device_name]["variables"].append(
{attribute_update["attributeOnThingsBoard"]: attribute_node})
else:
self._log.error("Attribute update node with path \"%s\" - NOT FOUND!", attribute_path)
except Exception as e:
self._log.exception(e)
def __search_general_info(self, device):
result = []
match_devices = []
self.__search_node(self.__opcua_nodes["root"], TBUtility.get_value(device["deviceNodePattern"], get_tag=True),
result=match_devices)
if len(match_devices) == 0:
self._log.warning("Device node not found with expression: %s",
TBUtility.get_value(device["deviceNodePattern"], get_tag=True))
for device_node in match_devices:
if device_node is not None:
result_device_dict = {"deviceName": None, "deviceType": None, "deviceNode": device_node,
"configuration": deepcopy(device)}
name_pattern_config = device["deviceNamePattern"]
name_expression = TBUtility.get_value(name_pattern_config, get_tag=True)
if "${" in name_pattern_config and "}" in name_pattern_config:
self._log.debug("Looking for device name")
device_name_from_node = ""
if name_expression == "$DisplayName":
device_name_from_node = device_node.get_display_name().Text
elif name_expression == "$BrowseName":
device_name_from_node = device_node.get_browse_name().Name
elif name_expression == "$NodeId.Identifier":
device_name_from_node = str(device_node.nodeid.Identifier)
else:
name_path = self._check_path(name_expression, device_node)
device_name_node = []
self.__search_node(device_node, name_path, result=device_name_node)
if len(device_name_node) == 0:
self._log.warn("Device name node - not found, skipping device...")
continue
device_name_node = device_name_node[0]
if device_name_node is not None:
device_name_from_node = device_name_node.get_value()
if device_name_from_node == "":
self._log.error("Device name node not found with expression: %s", name_expression)
return None
full_device_name = name_pattern_config.replace("${" + name_expression + "}",
str(device_name_from_node)).replace(
name_expression, str(device_name_from_node))
else:
full_device_name = name_expression
result_device_dict["deviceName"] = full_device_name
self._log.debug("Device name: %s", full_device_name)
if device.get("deviceTypePattern"):
device_type_expression = TBUtility.get_value(device["deviceTypePattern"],
get_tag=True)
if "${" in device_type_expression and "}" in device_type_expression:
type_path = self._check_path(device_type_expression, device_node)
device_type_node = []
self.__search_node(device_node, type_path, result=device_type_node)
device_type_node = device_type_node[0]
if device_type_node is not None:
device_type = device_type_node.get_value()
full_device_type = device_type_expression.replace("${" + device_type_expression + "}",
device_type).replace(
device_type_expression,
device_type)
else:
self._log.error("Device type node not found with expression: %s", device_type_expression)
full_device_type = "default"
else:
full_device_type = device_type_expression
result_device_dict["deviceType"] = full_device_type
self._log.debug("Device type: %s", full_device_type)
else:
result_device_dict["deviceType"] = "default"
result.append(result_device_dict)
else:
self._log.error("Device node not found with expression (2): %s",
TBUtility.get_value(device["deviceNodePattern"], get_tag=True))
return result
@cached(cache=TTLCache(maxsize=1000, ttl=10 * 60))
def get_node_path(self, node: Node):
return '\\.'.join(node.get_browse_name().Name for node in node.get_path(200000))
def __search_node(self, current_node, fullpath, search_method=False, result=None):
if result is None:
result = []
try:
if regex.match(r"ns=\d*;[isgb]=.*", fullpath, regex.IGNORECASE):
if self.__show_map:
self._log.debug("Looking for node with config")
node = self.client.get_node(fullpath)
if node is None:
self._log.warning("NODE NOT FOUND - using configuration %s", fullpath)
else:
self._log.debug("Found in %s", node)
# this unnecessary code is added to fix the issue with the to_string method of the NodeId class
# and can be deleted after the fix of the issue in the library
if node.nodeid.NodeIdType == ua.NodeIdType.Guid:
node.nodeid = ua.NodeId(UUID(node.nodeid.Identifier), node.nodeid.NamespaceIndex,
nodeidtype=ua.NodeIdType.Guid)
elif node.nodeid.NodeIdType == ua.NodeIdType.ByteString:
node.nodeid = ua.NodeId(node.nodeid.Identifier.encode('utf-8'), node.nodeid.NamespaceIndex,
nodeidtype=ua.NodeIdType.ByteString)
# --------------------------------------------------------------------------------------------------
result.append(node)
else:
fullpath_pattern = regex.compile(fullpath)
full1 = fullpath.replace('\\\\.', '.')
# current_node_path = '\\.'.join(char.split(":")[1] for char in current_node.get_path(200000, True))
current_node_path = self.get_node_path(current_node)
# we are allways the parent
child_node_parent_class = current_node.get_node_class()
new_parent = current_node
for child_node in current_node.get_children():
new_node_class = child_node.get_node_class()
# this will not change you can do it outside th loop
# basis Description of node.get_parent() function, sometime child_node.get_parent() return None
# new_parent = child_node.get_parent()
# if (new_parent is None):
# child_node_parent_class = current_node.get_node_class()
# else:
# child_node_parent_class = child_node.get_parent().get_node_class()
# current_node_path = '\\.'.join(char.split(":")[1] for char in current_node.get_path(200000, True))
# new_node_path = '\\\\.'.join(char.split(":")[1] for char in child_node.get_path(200000, True))
new_node_path = self.get_node_path(child_node)
if child_node_parent_class == ua.NodeClass.View and new_parent is not None:
parent_path = self.get_node_path(new_parent)
# parent_path = '\\.'.join(char.split(":")[1] for char in new_parent.get_path(200000, True))
fullpath = fullpath.replace(current_node_path, parent_path)
nnp1 = new_node_path.replace('\\\\.', '.')
nnp2 = new_node_path.replace('\\\\', '\\')
if self.__show_map:
self._log.debug("SHOW MAP: Current node path: %s", new_node_path)
regex_fullmatch = regex.fullmatch(fullpath_pattern, nnp1) or \
nnp2 == full1 or \
nnp2 == fullpath or \
nnp1 == full1
if regex_fullmatch:
if self.__show_map:
self._log.debug("SHOW MAP: Current node path: %s - NODE FOUND", nnp2)
result.append(child_node)
else:
regex_search = fullpath_pattern.fullmatch(nnp1, partial=True) or \
nnp2 in full1 or \
nnp1 in full1
if regex_search:
if self.__show_map:
self._log.debug("SHOW MAP: Current node path: %s - NODE FOUND", new_node_path)
if new_node_class == ua.NodeClass.Object:
if self.__show_map:
self._log.debug("SHOW MAP: Search for %s in %s", fullpath, new_node_path)
self.__search_node(child_node, fullpath, result=result)
elif new_node_class == ua.NodeClass.Variable:
if self.__show_map:
self._log.debug("SHOW MAP: Search for %s in %s", fullpath, new_node_path)
self.__search_node(child_node, fullpath, result=result)
elif new_node_class == ua.NodeClass.Method and search_method:
self._log.debug("Found in %s", new_node_path)
result.append(child_node)
except CancelledError:
self._log.error("Request during search has been canceled by the OPC-UA server.")
except BrokenPipeError:
self._log.error("Broken Pipe. Connection lost.")
except OSError:
self._log.debug("Stop on scanning.")
except Exception as e:
self._log.exception(e)
def _check_path(self, config_path, node):
if regex.match(r"ns=\d*;[isgb]=.*", config_path, regex.IGNORECASE):
return config_path
if re.search(r"^root", config_path.lower()) is None:
node_path = self.get_node_path(node)
# node_path = '\\\\.'.join(char.split(":")[1] for char in node.get_path(200000, True))
if config_path[:2] != '\\.':
information_path = node_path + '\\.' + config_path
else:
information_path = node_path + config_path
else:
information_path = config_path
return information_path.replace('\\', '\\\\')
@property
def subscribed(self):
return self._subscribed
def get_config(self):
return self.__server_conf
def get_converters(self):
return [item['converter'] for _, item in self.subscribed.items()]
def update_converter_config(self, converter_name, config):
self._log.debug('Received remote converter configuration update for %s with configuration %s', converter_name,
config)
converters = self.get_converters()
for converter_class_obj in converters:
converter_class_name = converter_class_obj.__class__.__name__
converter_obj = converter_class_obj
if converter_class_name == converter_name:
converter_obj.config = config
self._log.info('Updated converter configuration for: %s with configuration %s',
converter_name, converter_obj.config)
for node_config in self.__server_conf['mapping']:
if node_config['deviceNodePattern'] == config['deviceNodePattern']:
node_config.update(config)
self.__gateway.update_connector_config_file(self.name, {'server': self.__server_conf})
class SubHandler(object):
def __init__(self, connector: OpcUaConnector):
self.connector = connector
def datachange_notification(self, node, val, data):
try:
self.connector._log.debug("Python: New data change event on node %s, with val: %s and data %s", node, val,
str(data))
subscriptions = list(
filter(lambda node_info: node_info["information_node"] == node, self.connector.subscribed.values()))
for subscription in subscriptions:
converted_data = subscription["converter"].convert(
(subscription["information"], subscription["information_type"]), val, data,
key=subscription.get('key'))
self.connector.statistics['MessagesReceived'] = self.connector.statistics['MessagesReceived'] + 1
self.connector.data_to_send.append(converted_data)
self.connector.statistics['MessagesSent'] = self.connector.statistics['MessagesSent'] + 1
self.connector._log.debug("[SUBSCRIPTION] Data to ThingsBoard: %s", converted_data)
except KeyError:
self.connector.scan_nodes_from_config()
except Exception as e:
self.connector._log.exception(e)
def event_notification(self, event):
try:
self.connector._log.debug("Python: New event %s", event)
except Exception as e:
self.connector._log.exception(e)