def deprovision_node_instance(self, instance_id: str) -> DeprovisionServiceSpec: if self.service_instance_map is None or instance_id not in self.service_instance_map: return DeprovisionServiceSpec(is_async=False) service_instance = self.service_instance_map.get(instance_id) url = service_instance.params.get("url") nodes = service_instance.params.get("nodes") print(nodes) client = Client(url) try: client.connect() client.delete_nodes(nodes) except Exception as e: print("Error: {0}\n".format(e)) return DeprovisionServiceSpec(is_async=False) self.service_instance_map.pop(instance_id) print("Node service instance {0} is deprovisioned successfully\n".format(instance_id)) return DeprovisionServiceSpec(is_async=False)
class OPCUAServer(object): """ Each instance of this class manages a connection to its own OPC UA server. Methods are called to get node data from the server. """ def __init__(self, name, endPointAddress, nameSpaceUri=None, browseRootNodeIdentifier=None): # ---------- Setup ----------- self.name = name self.logger = logging.getLogger(self.name) self.endPointAddress = endPointAddress self.nameSpaceUri = nameSpaceUri self.nameSpaceIndex = None self.browseRootNodeIdentifier = browseRootNodeIdentifier self.rootNodeId = None self.client = Client(self.endPointAddress, timeout=2) self.sub = None self.subscriptions = {} # ---------------------------- def check_connection(self): """ Check if a connection has been established before or if connection thread is running. If either fails, try to (re)connect. """ if self.client.uaclient._uasocket is None: self.connect() elif self.client.uaclient._uasocket._thread is None: self.connect() elif not self.client.uaclient._uasocket._thread.is_alive(): self.connect() def connect(self): """ Connect to OPC UA server. If fails clean up session and socket, and raise exception. """ try: self.logger.info("Connecting to " + self.name + ".") self.client.connect() self.update_namespace_and_root_node_id() except socket.timeout: self.logger.info(self.name + " socket timed out.") try: self.logger.info("Cleaning up session and socket.") self.client.uaclient.disconnect() except AttributeError: pass self.logger.info("Socket and session cleaned up.") raise TimeoutError(self.name + " timed out.") def update_namespace_and_root_node_id(self): """ Update rootNodeId and nameSpaceIndex. If no namespace given, sets root node (id: i=84) as root node. """ if self.nameSpaceUri and self.browseRootNodeIdentifier: nsArray = self.client.get_namespace_array() index = nsArray.index(self.nameSpaceUri) if index > 0: nodeId = "ns={};".format(index) + self.browseRootNodeIdentifier else: nodeId = self.browseRootNodeIdentifier else: nodeId = "i=84" index = None self.rootNodeId = nodeId self.nameSpaceIndex = index return def get_node_path(self, nodeId): """ Create node path from node id for current server settings. Attempts to create a path of node from rootNode. Only works for folderly like string node ids. Example: "ns=2;s=node1.node2.node3.node4" """ identifierType = self.rootNodeId.split(";")[-1].split("=")[0] if (identifierType == "s") and ("." in self.rootNodeId): rootNodeName = self.rootNodeId.lower().split(".")[-1] nodePath = nodeId.lower().split(rootNodeName)[-1] nodePath = nodePath.replace(".", "", 1).replace(".", "/") else: nodePath = nodeId.split("=")[-1] return nodePath def get_node(self, nodeId=""): """ Returns node from nodeId or identifier. If no namespace given in nodeId, assumes the namespace to namespace given for the server in settings.py. Only the ns set for the server in servers.json is accessible via browsing. """ self.check_connection() if nodeId == "": nodeId = self.rootNodeId elif self.nameSpaceIndex is None: nodeId = nodeId elif nodeId[:3] == "ns=": identifier = nodeId.split(";")[-1] if self.nameSpaceIndex == 0: nodeId = identifier else: nodeId = f"ns={self.nameSpaceIndex};{identifier}" else: nodeId = f"ns={self.nameSpaceIndex};{nodeId}" return self.client.get_node(nodeId) async def get_variable_nodes(self, node, nodeClass=2, variableList=None, depth=0, maxDepth=10): """ Eats a list of node object(s). Recursively finds nodes under given nodes that have given nodeClass. Returns node objects in a list. """ if variableList is None: variableList = [] depth += 1 if depth >= maxDepth: return variableList nodes = node.get_children() params = ua.ReadParameters() for node in nodes: rv = ua.ReadValueId() rv.NodeId = node.nodeid rv.AttributeId = ua.AttributeIds.NodeClass params.NodesToRead.append(rv) results = [] if len(params.NodesToRead) > 0: results, readTime = await self.read(params) for i in range(len(results)): if nodeClass == results[i].Value.Value: variableList.append(nodes[i]) await self.get_variable_nodes(node=nodes[i], nodeClass=nodeClass, variableList=variableList, depth=depth) return variableList def subscribe_variable(self, nodeId): if self.sub is None: handler = self self.sub = self.client.create_subscription(100, handler) node = self.get_node(nodeId) if 2 == node.get_attribute(ua.AttributeIds.NodeClass).Value.Value: return self.sub.subscribe_data_change(node) else: return None def datachange_notification(self, node, value, data): self.subscriptions[node.nodeid.to_string()] = data.monitored_item.Value async def read_node_attribute(self, nodeId, attribute): """ Read node attribute based on given arguments. Giving correct dataType for value and node speeds up the write operation. Arguments Example nodeId: Target nodeId "ns=2;i=2" attribute: Target attribute of node "Value" Results OPCUAVar: OPC UA variable object <object> readTime: Time taken for read (ns) 12345678 """ rv = ua.ReadValueId() if nodeId == "": rv.NodeId = ua.NodeId.from_string(server.rootNodeId) else: rv.NodeId = ua.NodeId.from_string(nodeId) rv.AttributeId = ua.AttributeIds[attribute] params = ua.ReadParameters() params.NodesToRead.append(rv) result, readTime = await self.read(params) if attribute == "Value": return result[0], readTime else: return result[0] async def set_node_attribute(self, nodeId, attribute, value, dataType=None): """ Sets node attribute based on given arguments. Giving correct dataType for value and node speeds up the write operation. Arguments Example nodeId: Target nodeId "ns=2;i=2" attribute: Target attribute of node "Value" value: Value for the attribute 1234 dataType: Data type of value "Int32" Results boolean: Indicates success True writeTime: Time taken for write (ns) 12345678 """ attr = ua.WriteValue() if nodeId == "": attr.NodeId = ua.NodeId.from_string(self.rootNodeId) else: attr.NodeId = ua.NodeId.from_string(nodeId) attr.AttributeId = ua.AttributeIds[attribute] if attribute == "Description": dataValue = ua.LocalizedText(value) else: if dataType is None: variantType = self.variant_type_finder(value, nodeId) else: variantType = ua.VariantType[dataType] dataValue = ua.Variant(value, variantType) attr.Value = ua.DataValue(dataValue) params = ua.WriteParameters() params.NodesToWrite.append(attr) result, writeTime = await self.write(params) if attribute == "Value": return result[0].is_good(), writeTime else: return result[0].is_good() def add_node(self, name, nodeId, parentId, value=None, writable=True): """ Adds a node to OPC UA server. If value given, adds a variable node, else, a folder node. Requires server admin powers in server servers.json, for example endPointAddress: "opc.tcp://[email protected]:4840/freeopcua/server/". """ self.check_connection() if self.nameSpaceIndex is not None: index = self.nameSpaceIndex elif nodeId[:3] == "ns=": index = nodeId.split("=")[1][0] else: index = 0 browseName = f"{index}:{name}" parentNode = self.get_node(parentId) if value is None: node = parentNode.add_folder(nodeId, browseName) result = { "name": node.get_display_name().to_string(), "nodeId": node.nodeid.to_string(), } else: node = parentNode.add_variable(nodeId, browseName, value) attribute = node.get_attribute(ua.AttributeIds.Value) result = { "name": node.get_display_name().to_string(), "nodeId": node.nodeid.to_string(), "value": attribute.Value.Value, "dataType": attribute.Value.VariantType.name, "sourceTimestamp": attribute.SourceTimestamp, "statusCode": attribute.StatusCode.name } if writable is True: node.set_writable() return result def delete_node(self, nodeId, recursive=True): """ Recursively deletes node and it's subnodes unless recursive=False. Requires admins. Doesn't raise errors if deleting is unsuccessful. """ self.check_connection() node = self.get_node(nodeId) result = self.client.delete_nodes([node], recursive) result[1][0].check() return result[1][0].is_good() async def read(self, params): """ Reads from OPC UA server params == ua.ReadParameters() that are properly set up. Returns result object and time it took to read from OPC UA server. """ self.check_connection() start = time.time_ns() result = self.client.uaclient.read(params) readTime = time.time_ns() - start return result, readTime async def write(self, params): """ Writes to OPC UA server params == ua.WriteParameters() that are properly set up. Returns result object and time it took to read from OPC UA server. """ self.check_connection() start = time.time_ns() result = self.client.uaclient.write(params) writeTime = time.time_ns() - start return result, writeTime def variant_type_finder(self, value, nodeId): """ Attempts to find variant type of given value. If not found, retrieves variant type of node from OPC UA server. """ valueType = type(value) if isinstance(valueType, datetime.datetime): variantType = ua.uatypes.VariantType.DateTime elif valueType == bool: variantType = ua.uatypes.VariantType.Boolean elif valueType == str: variantType = ua.uatypes.VariantType.String elif valueType == int or valueType == float: node = self.get_node(nodeId) self.check_connection() variantType = node.get_data_type_as_variant_type() else: raise ValueError("Unsupported datatype") return variantType
import sys sys.path.insert(0, "..") import logging from opcua import Client from opcua import ua if __name__ == "__main__": logging.basicConfig(level=logging.WARN) client = Client("opc.tcp://admin@localhost:4840/freeopcua/server/") #connect using a user try: client.connect() objects = client.get_objects_node() folder = objects.add_folder("ns=2;i=3007", "2:Folder1") var = folder.add_variable("ns=2;i=3008", "2:Variable1", 3.45) # Now getting a variable node using its browse path var.set_value(9.89) # just to check it works results = client.delete_nodes([folder, var]) try: #var.set_value(9.89) # just to check it does not work var.get_browse_name() except ua.UaStatusCodeError: print("The variable has been removed OK") finally: client.disconnect()
class UaClientWrapper(object): def __init__(self, endpoint, targetNs): self.endpoint = endpoint self.client = Client(endpoint) try: self.client.connect() self.namespaceArray = self.client.get_namespace_array() self.targetNs = self.client.get_namespace_index(targetNs) print("Target NS %s" % targetNs) except: self.disconnect() def get_namespace_array(self): return self.namespaceArray def collect_child_nodes(self, node, tree): # iterate over all referenced nodes (31), only hierarchical references (33) for child in node.get_children(refs=33): if not tree.get("children"): tree["children"] = [] tree["children"].append({"node": child}) self.collect_child_nodes(child, tree["children"][-1]) def import_nodes(self, root_node=None): for ns in self.client.get_namespace_array(): self.nsMapping[self.client.get_namespace_index(ns)] = ns if root_node is None: root = self.client.get_root_node() else: root = self.client.get_node(root_node) tree = {} self.collect_child_nodes(root, tree) return tree def getNamespaceIndices(self, list): self.nsMapping = {} for ns in list: self.nsMapping[ns] = self.client.get_namespace_index(ns) return self.nsMapping def clearModel(self, rootNode): root = self.client.get_node(rootNode) self.client.delete_nodes(root.get_children(), True) def addObject(self, parentNodeId, bName, objectType, identifier=0): parent = self.client.get_node(parentNodeId) node = parent.add_object("ns=%i;i=%i" % (self.targetNs, identifier), bName, objectType) node.__class__ = Node return node def escapeName(self, name: str): return name.replace(":", "|") def addNodes(self, rootNode, objects): for obj in objects: if 'ua_object_type' in obj: if obj['value'] != '': name = obj['value'] else: name = obj['object'] node = self.addObject( rootNode, self.escapeName(name), "ns=%s;i=%s" % (self.nsMapping[obj['ua_object_type']['ns']], obj['ua_object_type']['i'])) obj['ua_node_id'] = node.nodeid.to_string() if 'objects' in obj: obj['objects'] = self.addNodes(obj['ua_node_id'], obj['objects']) elif 'ua_variable_qualified_name' in obj and obj['value'] != '': qname = ua.QualifiedName( obj['ua_variable_qualified_name']['browse_name'], self.nsMapping[obj['ua_variable_qualified_name']['ns']]) root = self.client.get_node(rootNode) variable = root.get_child(qname) variable.set_value(obj['value']) return obj def disconnect(self): self.client.disconnect()