def conn_opc(): # OPCサーバに接続 cl = Client("opc.tcp://192.168.10.5:51110/CogentDataHub/DataAccess") # クライアント証明書のapplication_uri cl.application_uri = "urn:desktop-i50i89m:Cogent DataHub" # policy設定 print("secPolicy: " + str(secPolicy)) if secPolicy != policies[0]: # None以外の場合SecurityPolicyを設定 mode = ua.MessageSecurityMode.SignAndEncrypt pc = getattr(security_policies, 'SecurityPolicy' + secPolicy) # 第二引数:クライアント証明書 cl.set_security( pc, "/Users/watarium/PycharmProjects/opcua/OPCUA_CL.der", "/Users/watarium/PycharmProjects/opcua/OPCUAClient.pem", "/Users/watarium/PycharmProjects/opcua/OPCUAServer.der", mode) # 認証設定 if setCert == certs[1]: # user/pass cl.set_user("admin") cl.set_password("1234") elif setCert == certs[2]: # certificate cl.load_private_key( "/Users/watarium/PycharmProjects/opcua/OPCUAClient.pem") cl.load_client_certificate( "/Users/watarium/PycharmProjects/opcua/OPCUA_CL.der") try: # 接続 print("Policy: {0}, Certificate: {1}".format(secPolicy, setCert)) print("---------------------Connection start-----------------------") cl.connect() sleep(5) # 情報取得 ep = cl.get_endpoints() print(ep[0].Server.ApplicationUri) root = cl.get_root_node() print("Objects node is: ", root) print(cl.get_namespace_array()) print(cl.get_namespace_index('urn:win-9hi38ajrojd:Cogent DataHub')) #これがうまくいかなかった(2019/06/27) #node = cl.get_node('ns=1;s=xxxxx') #print(node.get_value()) #node.set_attribute(ua.AttributeIds.Value, 1) # 切断 cl.disconnect() print("-------------------Connection Success!!!--------------------") except Exception as e: print("---------------------Connection Faild!!---------------------") print(e) cl.disconnect()
def start_uaprogramm(self, name, serverurl, port, uamethod="start_demoprogramm"): """ ruft die UA Methode über den im Docker erzeugten Client auf """ # Suche, ob der Container existiert clientname = name + "client" self._refresh_registry() container = self.registry.get(clientname) if container: connectstring = "opc.tcp://" + serverurl + ":" + port + "/freeopcua/server/" client = Client(connectstring) # client = Client("opc.tcp://admin@localhost:4840/freeopcua/server/") try: client.connect() client.load_type_definitions() # # Der Client verfügt über einige Methoden, um einen Proxy für UA-Knoten abzurufen, # die sich immer im Adressraum befinden sollten, z. B. Root oder Objects root = client.get_root_node() print("Root node is: ", root) objects = client.get_objects_node() print("Objects node is: ", objects) # Knotenobjekte verfügen über Methoden zum Lesen und Schreiben von Knotenattributen sowie zum Duchsuchen # des Adressraums print("Children of root are: ", root.get_children()) # Der Adressraum idx uri = "http://freeopcua.github.io" idx = client.get_namespace_index(uri) # Jetzt wird ein variabler Knoten über den Suchpfad abgerufen myvar = root.get_child([ "0:Objects", "{}:MyObject".format(idx), "{}:MyVariable".format(idx) ]) obj = root.get_child(["0:Objects", "{}:MyObject".format(idx)]) # Aufrufen unserer übergebenen Methode res = obj.call_method(uamethod.format(idx)) finally: client.disconnect()
def download_xst(subband: int, integration_time_s: int, url: str = 'localhost', port: int = 50000): """ Download cross correlation statistics Args: subband (int): Subband number integration_time_s (int): Integration time in seconds url (str): URL to connect to, defaults to 'localhost' port (str): Port to connect to, defaults to 50000 Returns: Tuple[datetime.datetime, np.ndarray, int]: UTC time, visibilities (shape n_ant x n_ant), RCU mode Raises: RuntimeError: if in mixed RCU mode """ client = Client("opc.tcp://{}:{}/".format(url, port), timeout=1000) client.connect() client.load_type_definitions() objects = client.get_objects_node() idx = client.get_namespace_index(DEFAULT_URI) obj = client.get_root_node().get_child( ["0:Objects", "{}:StationMetrics".format(idx), "{}:RCU".format(idx)]) obstime, visibilities_opc, rcu_modes = obj.call_method( "{}:record_cross".format(idx), subband, integration_time_s) client.close_session() client.close_secure_channel() rcu_modes_on = set([mode for mode in rcu_modes if mode != '0']) if len(rcu_modes_on) == 1: rcu_mode = int(rcu_modes_on.pop()) elif len(rcu_modes_on) == 0: rcu_mode = 0 else: raise RuntimeError( "Multiple nonzero RCU modes are used, that's not supported yet") assert (len(visibilities_opc) == 2) # Real and complex part visibilities = np.array(visibilities_opc)[0] + 1j * np.array( visibilities_opc[1]) return obstime, visibilities, rcu_mode
def main(): client = Client("opc.tcp://localhost:4840/freeopcua/server/") try: client.connect() # Client has a few methods to get proxy to UA nodes that should always be in address space such as Root or Objects root = client.get_root_node() print("Objects node is: ", root) print("Children of root are: ", root.get_children()) server_namespace = "http://examples.freeopcua.github.io" idx = client.get_namespace_index(server_namespace) # Now getting a variable node using its browse path obj = root.get_child(["0:Objects", "2:MyObject"]) print("My Object object is: ", obj) ## simply read variables, properties, and arrays myvar = root.get_child( ["0:Objects", "2:MyObject", "2:MyFirstVariable"]) print("myvar is: ", myvar.get_value()) myvar.set_value(3.9) # if the var on the server is writable print("myvar is: ", myvar.get_value()) myprop = root.get_child( ["0:Objects", "2:MyObject", "2:MyFirstVariable"]) print("myprop is: ", myprop.get_value()) myprop.set_value(3.9) # if the var on the server is writable print("myprop is: ", myprop.get_value()) ## subscribe to variables change subscribed_variables_dict = dict() subscribed_variables = list() for var in VARS_NAMES: myvar = root.get_child( ["0:Objects", "2:ChargeController", "2:" + str(var)]) subscribed_variables.append(myvar) subscribed_variables_dict[str(myvar)] = str( myvar.get_browse_name().to_string()) msclt = SubHandler() sub = client.create_subscription(100, msclt) for var in subscribed_variables: handle = sub.subscribe_data_change(var) ## subscribe to events myevent = root.get_child([ "0:Types", "0:EventTypes", "0:BaseEventType", "2:LowBatteryEvent" ]) print("MyFirstEventType is: ", myevent) handle = sub.subscribe_events(obj, myevent) ## methods while True: sleep(1) sub.unsubscribe(handle) sub.delete() finally: client.disconnect()
set_bool(read_b12,0,2,t_floor.get_value()) plc.write_area( areas['MK'], 0,12,read_b12 ) time.sleep(.01) if __name__ == "__main__": try: opc_client.connect() except Exception as identifier: print("OPCUA Error:") print(identifier) else: opc_client.load_type_definitions() root = opc_client.get_root_node() objects = opc_client.get_objects_node() idx = opc_client.get_namespace_index(opc_uri) obj = root.get_child(["0:Objects", "{}:PLC_S71200".format(idx)]) # dest = root.get_child(["0:Objects", "{}:PLC_S71200".format(idx), "{}:Destination".format(idx)]) dest = opc_client.get_node("ns=2;i=6") # disp = root.get_child(["0:Objects", "{}:PLC_S71200".format(idx), "{}:Displacement".format(idx)]) disp = opc_client.get_node("ns=2;i=3") # spd = root.get_child(["0:Objects", "{}:PLC_S71200".format(idx), "{}:Speed".format(idx)]) aspd = opc_client.get_node("ns=2;i=4") lspd = opc_client.get_node("ns=2;i=5") f_floor = opc_client.get_node("ns=2;i=13") s_floor = opc_client.get_node("ns=2;i=14") t_floor = opc_client.get_node("ns=2;i=15")
class MyPlc: area = {'I': 0x81, 'Q': 0x82, 'M': 0x83, 'D': 0x84} szs = {'x': 1, 'X': 1, 'b': 1, 'B': 1, 'w': 2, 'W': 2, 'd': 4, 'D': 4} def __init__(self, ip='192.168.0.1'): self.ip = ip self.INByteArray = bytearray([0, 0]) self.MKByteArray = bytearray([0, 0]) self.threadStatus = False self.varsdict = {} self.threads = {} self.plc = PlcClient() self.subNodes = [] self.subNodesD = {} self.keysDict = {} self.inNodes = {} def get_db(self, server_id): self.db_server = Server.query.get(server_id) self.connections() def connections(self): self.opc_ns_uri = self.db_server.server_namespace self.ep_url = 'opc.tcp://' + self.db_server.server_endpoint_url self.opclient = Client(self.ep_url) try: self.plc.connect(self.ip, 0, 1) # pass except Exception: self.conn_stat = "Could not connect to PLC" else: self.conn_stat = "PLC Connected Successfully" self.opclient.connect() self.root = self.opclient.get_root_node() self.idx = self.opclient.get_namespace_index( self.db_server.server_namespace) self.set_tags(self.db_server.server_objects) for key, val in self.varsdict.items(): if re.search("^(M|m)([\d]+).[\d]*$", key) is not None: self.subNodes.append(val['obj']) self.subNodesD[key] = val else: self.inNodes[key] = val self.run_threads() handler = SubHandler(self) sub = self.opclient.create_subscription(200, handler) handle = sub.subscribe_data_change(self.subNodes) time.sleep(0.1) def set_tags(self, objs): for obj in objs: try: self.make_tag_dict(obj, obj.object_variables) except Exception: self.make_tags_dict(obj.object_variables) finally: for var in obj.object_variables: self.keysDict[var.variable_name] = var.variable_address def make_tag_dict(self, obj, allvars): for var in allvars: self.varsdict[var.variable_address] = { 'obj': self.root.get_child([ "0:Objects", "{}:{}".format(self.idx, obj.object_name), "{}:{}".format(self.idx, var.variable_name) ]), 'type': var.variable_type } def kill_threads(self): self.threadStatus = False def run_threads(self): self.threadStatus = True self.threads['update_server'] = threading.Thread( target=self.updateInputs) self.threads['update_server'].start() def getInputs(self): while self.threadStatus: self.INByteArray = self.plc.read_area(areas['PE'], 0, 0, 2) # self.INByteArray = bytearray([ randint(0,7), randint(0,7) ]) time.sleep(.1) # def get_bool(_bytearray, byte_index, bool_index): def updateInputs(self): while self.threadStatus: for key, val in self.inNodes.items(): self.update_server_vars(key) time.sleep(.01) def writetoPLC(self, value, node): key = self.keysDict[node.get_browse_name().to_string().split(':')[1]] self.write_to_plc(key, value) ''' Get Data from the PLC and Update OPC Server variables ''' def update_server_vars(self, addr_key): addr = addr_key.split('.') # Works with Boolean values from a Data Block if len(addr) == 3 and addr[0][0] == 'D': DBn = int(addr[0][2:]) DBt = addr[1][2] byt = int(addr[1][3:]) bit = int(addr[2]) reading = self.plc.read_area(MyPlc.area['D'], DBn, byt, szs[DBt]) if DBt == 'X' or DBt == 'x': self.varsdict[addr_key]['obj'].set_value( get_bool(reading, 0, bit)) # return get_bool( reading, 0, bit ) else: self.varsdict[addr_key]['obj'].set_value(reading) # return reading # Works with other data types from a Data Block elif len(addr) == 2 and addr[0][0] == 'D': DBn = int(addr[0][2:]) DBt = addr[1][2] byt = int(addr[1][3:]) reading = self.plc.read_area(MyPlc.area['D'], DBn, byt, szs[DBt]) if DBt == 'W' or DBt == 'w': self.varsdict[addr_key]['obj'].set_value(get_int(reading, 0)) # return get_int(reading,0) elif DBt == 'D' or DBt == 'd': self.varsdict[addr_key]['obj'].set_value(get_real(reading, 0)) # return get_real(reading,0) else: self.varsdict[addr_key]['obj'].set_value(reading) # Works with boolean values from Inputs,Merkels and Outputs elif len(addr) == 2: byt = int(addr[0][1:]) bit = int(addr[1]) reading = self.plc.read_area(MyPlc.area[addr[0][0]], 0, byt, 1) self.varsdict[addr_key]['obj'].set_value(get_bool(reading, 0, bit)) # return get_bool(reading,0,bit) # Works with other data types from Inputs,Merkels ot Outputs eg MW2 elif len(addr) == 1: byt = int(addr[0][2:]) typ = addr[0][1] reading = self.plc.read_area(MyPlc.area[addr[0][0]], 0, byt, 2) if typ == 'w' or typ == 'W': self.varsdict[addr_key]['obj'].set_value(get_int(reading, 0)) # return get_int(reading, 0) elif typ == 'd' or typ == 'D': self.varsdict[addr_key]['obj'].set_value(get_real(reading, 0)) # return get_real(reading, 0) else: self.varsdict[addr_key]['obj'].set_value(reading) # return reading ''' WRITE DATA TO PLC FROM SERVER ''' def write_to_plc(self, addr_key, value): addr = addr_key.split('.') print("New data change on {} : {}".format(addr_key, value)) # Works with Boolean values from a Data Block if len(addr) == 3 and addr[0][0] == 'D': DBn = int(addr[0][2:]) DBt = addr[1][2] byt = int(addr[1][3:]) bit = int(addr[2]) reading = self.plc.read_area(MyPlc.area['D'], DBn, byt, MyPlc.szs[DBt]) if DBt == 'X' or DBt == 'x': set_bool(reading, 0, bit, value) self.plc.write_area(MyPlc.area['D'], DBn, byt, reading) # Works with other data types from a Data Block elif len(addr) == 2 and addr[0][0] == 'D': DBn = int(addr[0][2:]) DBt = addr[1][2] byt = int(addr[1][3:]) reading = self.plc.read_area(MyPlc.area['D'], DBn, byt, MyPlc.szs[DBt]) if DBt == 'W' or DBt == 'w': set_int(reading, 0, value) elif DBt == 'D' or DBt == 'd': set_real(reading, 0, value) self.plc.write_area(MyPlc.area['D'], DBn, byt, reading) # Works with boolean values from Inputs,Merkels ot Outputs elif len(addr) == 2: byt = int(addr[0][1:]) bit = int(addr[1]) reading = self.plc.read_area(MyPlc.area[addr[0][0]], 0, byt, 1) set_bool(reading, 0, bit, value) self.plc.write_area(MyPlc.area[addr[0][0]], 0, byt, reading) # Works with other data types from Inputs,Merkels ot Outputs eg MW2 elif len(addr) == 1: byt = int(addr[0][2:]) typ = addr[0][1] reading = self.plc.read_area(MyPlc.area[addr[0][0]], 0, byt, 2) if typ == 'w' or typ == 'W': set_int(reading, 0, value) elif typ == 'd' or typ == 'D': set_real(reading, 0, value) else: set_data(value) self.plc.write_area(MyPlc.area[addr[0][0]], 0, byt, reading)
class OpcUaClient(object): CONNECT_TIMEOUT = 15 # [sec] RETRY_DELAY = 10 # [sec] MAX_RETRIES = 3 # [-] class Decorators(object): @staticmethod def autoConnectingClient(wrappedMethod): def wrapper(obj, *args, **kwargs): for retry in range(OpcUaClient.MAX_RETRIES): try: return wrappedMethod(obj, *args, **kwargs) except ua.uaerrors.BadNoMatch: raise except Exception: pass try: obj._logger.warning( '(Re)connecting to OPC-UA service.') obj.reconnect() except ConnectionRefusedError: obj._logger.warning( 'Connection refused. Retry in 10s.'.format( OpcUaClient.RETRY_DELAY)) time.sleep(OpcUaClient.RETRY_DELAY) else: # So the exception is exposed. obj.reconnect() return wrappedMethod(obj, *args, **kwargs) return wrapper def __init__(self, serverUrl): self._logger = logging.getLogger(self.__class__.__name__) self._client = Client(serverUrl.geturl(), timeout=self.CONNECT_TIMEOUT) def __enter__(self): self.connect() return self def __exit__(self, exc_type, exc_value, traceback): self.disconnect() self._client = None @property @Decorators.autoConnectingClient def sensorList(self): return self.objectsNode.get_children() @property @Decorators.autoConnectingClient def objectsNode(self): path = [ua.QualifiedName(name='Objects', namespaceidx=0)] return self._client.get_root_node().get_child(path) def connect(self): self._client.connect() self._client.load_type_definitions() def disconnect(self): try: self._client.disconnect() except Exception: pass def reconnect(self): self.disconnect() self.connect() @Decorators.autoConnectingClient def get_browse_name(self, uaNode): return uaNode.get_browse_name() @Decorators.autoConnectingClient def get_node_class(self, uaNode): return uaNode.get_node_class() @Decorators.autoConnectingClient def get_namespace_index(self, uri): return self._client.get_namespace_index(uri) @Decorators.autoConnectingClient def get_child(self, uaNode, path): return uaNode.get_child(path) @Decorators.autoConnectingClient def read_raw_history(self, uaNode, starttime=None, endtime=None, numvalues=0, cont=None): details = ua.ReadRawModifiedDetails() details.IsReadModified = False details.StartTime = starttime or ua.get_win_epoch() details.EndTime = endtime or ua.get_win_epoch() details.NumValuesPerNode = numvalues details.ReturnBounds = True result = OpcUaClient._history_read(uaNode, details, cont) assert (result.StatusCode.is_good()) return result.HistoryData.DataValues, result.ContinuationPoint @staticmethod def _history_read(uaNode, details, cont): valueid = ua.HistoryReadValueId() valueid.NodeId = uaNode.nodeid valueid.IndexRange = '' valueid.ContinuationPoint = cont params = ua.HistoryReadParameters() params.HistoryReadDetails = details params.TimestampsToReturn = ua.TimestampsToReturn.Both params.ReleaseContinuationPoints = False params.NodesToRead.append(valueid) result = uaNode.server.history_read(params)[0] return result
class OpcClient(): """A simplified OpClient. This is a opc client that can be leveraged to subscribe to the OPC data change notifications. You should interact with the client using * add_notification_handler * subscribe_variable * unsubscribe_variable So typically: ``` # setup client cl = OpcClient(...) # define handler def handler(update): # data update : {'node': <node>, 'timestamp': <datetime>, 'value': <value>, 'data': <data>} # do something with update here # add handler cl.add_notification_handler(handler) # subscribe to variable cl.subscribe_variable(cl.get_node("ns=3;i=2002")) time.sleep(100) cl.disconnect() ``` *Note* Make sure to call disconnect before shutting down in order to ensure a clean close. See: https://github.com/FreeOpcUa/python-opcua Attributes ---------- _address : str The connection string address _address_obfuscated : str The connection string address obfuscating a potnetial password _client : OPCLibClient The opc lib client _subscription : ua.Subscription The ua subscription handle that can be used to create new subscritions or unsubscribe _sub_handles : dict A dictionary mapping ua.Node variables to subscription handles to be able to unsubscribe from them again _handlers : list A list of handler functions """ def __init__(self, host, port, path='/', username=None, password=None): """Create a new OpClient. This will create a new opc client that can be leveraged to subscribe to the OPC data change notifications. Parameters ---------- host : str The host name of the opc server. port : int The port of the opc server. path : str The path to add to the server as base username : str|None A username to use for authorization. password : str|None A password to use for authorization. """ self._client = None self._subscription = None self._sub_handles = {} self._handlers = [] # setup authorization auth = '' if username is not None: auth = '{}:{}@'.format(username, password) # define address self._address = self._address_obfuscated = "opc.tcp://{}{}:{}{}".format( auth, host, port, path) if username is not None and password is not None: self._address_obfuscated = self._address.replace(password, '***') Logger.trace('Created OpcClient', fields={'address': self._address_obfuscated}) # setup client self.init_client() # setup subscriptions self.init_subscriptions() def init_client(self): """Initialize the client. This will connect to the client using the address. This is required to be called before anything else interacting with the opc server is done. """ try: self._client = OPCLibClient(self._address) self._client.connect() self._client.load_type_definitions() self.namespace = self._client.get_namespace_array() Logger.trace('Connection established') except Exception as e: Logger.error('Failed connecting to address "{}"'.format( self._address_obfuscated)) Logger.error('{}'.format(e)) raise e def log_info(self): root = self._client.get_root_node() objects = self._client.get_objects_node() children = root.get_children() # print '{}:{}'.format(namespace[children[1].nodeid.NamespaceIndex], children[1].nodeid.Identifier) # print self._client.get_node("ns=0;i=86") Logger.debug("Retrieved Client Info", fields={ 'root': root, 'objects': objects, 'children': children, 'namespace': self.namespace }) def full_uri(self, node): """Resolve the full uri of a ua.Node. Parameters ---------- node : ua.Node A node from the OPC interface. Returns ------- str : The namespace prefixed full uri of the entity. """ return '{}:{}'.format(self.namespace[node.nodeid.NamespaceIndex], node.nodeid.Identifier) def get_by_uri(self, uri): """Resolve the full uri to a ua.Node. Parameters ---------- uri : str The uri to the ua.Node. Returns ------- ua.Node : The resolved node. """ ns_match = None for ns in self.namespace: if '{}:'.format(ns) in uri: ns_match = ns break if ns_match is None: raise RuntimeError('Namespace of "{}" not available'.format(uri)) ns_idx = self._client.get_namespace_index(ns_match) identifier = uri.replace('{}:'.format(ns_match), '') try: int(identifier) is_int = True except: is_int = False substituted = 'ns={};{}={}'.format(ns_idx, 'i' if is_int else 's', identifier) return self._client.get_node(substituted) def init_subscriptions(self): """Initialize the subscriptions. This will initialize a subscription handler and afterwards will be ready to subscribe to specific variables. """ if self._client is None: raise RuntimeError('Client not initialized yet.') self._subscription = self._client.create_subscription(500, self) Logger.trace('Base Subscription established') def subscribe_variable(self, variable): """Subscribe to a OPC variable. This will subscribe the client to the given variable and any data change notification will be published. Parameters ---------- variable : ua.Node The ua node, e.g. client.get_node(ua.NodeId(1002, 2)) or client.get_node("ns=3;i=2002") """ if self._subscription is None: raise RuntimeError('Subscriptions not initialized yet.') if variable in self._sub_handles.keys(): Logger.info('Already subscribed to "{}"'.format(variable)) return self._sub_handles[variable] = self._subscription.subscribe_data_change( variable) def unsubscribe_variable(self, variable): """Unsubscribe from a OPC variable. This will unsubscribe the client from the given variable. Parameters ---------- variable : ua.Node The ua node, e.g. client.get_node(ua.NodeId(1002, 2)) or client.get_node("ns=3;i=2002") """ if self._subscription is None: raise RuntimeError('Subscriptions not initialized yet.') if variable not in self._sub_handles.keys(): Logger.info('Not subscribed to "{}"'.format(variable)) return self._subscription.unsubscribe(self._sub_handles[variable]) del self._sub_handles[variable] def disconnect(self): """Disconnect the client. """ if self._client is None: return try: self._client.disconnect() except Exception: pass def datachange_notification(self, node, value, data): """Receiver from the OPC client. This gets called by OPC client on subscription notification. Parameter --------- node : ua.Node The node from which we received the data. value : any The value notification data : any The data of the notification """ timestamp = datetime.now() if hasattr(data, 'MonitoredItemNotification') and \ hasattr(data.MonitoredItemNotification, 'SourceTimestamp'): timestamp = data.MonitoredItemNotification.SourceTimestamp Logger.debug('OpcClient: Received data change notification', fields={ 'node': node, 'value': value, 'data': data, 'timestamp': timestamp.isoformat() }) # send update to handlers update = { 'node': node, 'value': value, 'data': data, 'timestamp': timestamp } for hdl in self._handlers: hdl(update) def add_notification_handler(self, handler): """Add a handler function. This handler `def handler(update)` will be called upon reception of a notification from the OPC Client. The update will have the following data: ```{ 'node': <node>, 'value': <value>, 'data': <data>, 'timestamp': <timestamp> }``` """ self._handlers.append(handler)
class Crane(object): """Definition of Ilmatar Crane interface through OPC UA.""" def __init__(self, clientaddress): """Initialization of the Ilmatar Crane.""" self.x = 1 self.client = Client(clientaddress) self.client.connect() NS = "ns=" + str(self.client.get_namespace_index("SYM:")) # Watchdog self._node_watchdog = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Controls.Watchdog") # Accescode self._node_access_code = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Controls.AccessCode") # Crane Direction booleans self._node_hoist_up = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Controls.Hoist.Up") self._node_hoist_down = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Controls.Hoist.Down") self._node_trolley_forward = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Controls.Trolley.Forward") self._node_trolley_backward = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Controls.Trolley.Backward") self._node_bridge_forward = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Controls.Bridge.Forward") self._node_bridge_backward = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Controls.Bridge.Backward") # Crane speed self._node_hoist_speed = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Controls.Hoist.Speed") self._node_trolley_speed = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Controls.Trolley.Speed") self._node_bridge_speed = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Controls.Bridge.Speed") # Position self._node_hoist_position_mm = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Hoist.Position.Position_mm") self._node_trolley_position_mm = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Trolley.Position.Position_mm") self._node_bridge_position_mm = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Bridge.Position.Position_mm") self._node_hoist_position_m = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Hoist.Position.Position_m") self._node_trolley_position_m = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Trolley.Position.Position_m") self._node_bridge_position_m = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Bridge.Position.Position_m") # Loads self._node_hoist_current_load = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Hoist.Load.Load_t") self._node_hoist_current_tared_load = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Hoist.Load.TaredLoad_t") # Datetime self._node_datime_year = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Datetime.Year") self._node_datime_month = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Datetime.Month") self._node_datime_day = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Datetime.Day") self._node_datime_hour = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Datetime.Hour") self._node_datime_minute = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Datetime.Minute") self._node_datime_second = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Datetime.Second") self._node_datime_millisecond = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Datetime.Millisecond") # Control signal status # Not currently used self._node_trolley_direction_forward_request = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Trolley.ControlSignals.Direction1_Request" ) self._node_trolley_direction_backwards_request = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Trolley.ControlSignals.Direction2_Request" ) self._node_trolley_speed_request = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Trolley.ControlSignals.SpeedRequest" ) self._node_trolley_speed_feedback = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Trolley.ControlSignals.SpeedFeedback" ) self._node_trolley_speed_feedback_mmin = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Trolley.ControlSignals.SpeedFeedback_mmin" ) # Not currently useds self._node_bridge_direction_forward_request = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Bridge.ControlSignals.Direction1_Request" ) self._node_bridge_direction_backward_request = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Bridge.ControlSignals.Direction2_Request" ) self._node_bridge_speed_request = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Bridge.ControlSignals.SpeedRequest") self._node_brige_speed_feedback = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Bridge.ControlSignals.SpeedFeedback" ) self._node_brige_speed_feedback_mmin = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Bridge.ControlSignals.SpeedFeedback_mmin" ) # Not currently used self._node_hoist_direction_up_request = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Hoist.ControlSignals.Direction1_Request" ) self._node_hoist_direction_down_request = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Hoist.ControlSignals.Direction2_Request" ) self._node_hoist_speed_request = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Hoist.ControlSignals.SpeedRequest") self._node_hoist_speed_feedback = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Hoist.ControlSignals.SpeedFeedback") self._node_hoist_speed_feedback_mmin = self.client.get_node( NS + ";s=SCF.PLC.DX_Custom_V.Status.Hoist.ControlSignals.SpeedFeedback_mmin" ) self.bridge_last_zeroed = self.get_motorcontroller_bridge_value() self.trolley_last_zeroed = self.get_motorcontroller_trolley_value() self.hoist_last_zeroed = self.get_motorcontroller_hoist_value() self._watchdog_value = self.get_watchdog() # Will be initialized in set_moving_height self.hoist_moving_height = None # Will be initialized in set_target_current_position later in __init__ self.trolley_target = None self.bridge_target = None self.hoist_target = None self.set_target_current_position() """Functions related to connecting to opcua server""" def connect(self): """Connect to the OPC UA Server.""" self.client.connect() def disconnect(self): """Disconnect from OPC UA Server.""" self.client.disconnect() """Functions related to watchdog""" def get_watchdog(self): """Get Watchdog value.""" return self._node_watchdog.get_value() # Not working def set_watchdog(self, watchdog_value): """Set Watchdog to number x.""" self._node_watchdog.set_value( ua.DataValue(ua.Variant(watchdog_value, ua.VariantType.Int16))) def increment_watchdog(self): """Increment Watchdog by 1.""" self._watchdog_value = (self._watchdog_value % 30000) + 1 self.set_watchdog(self._watchdog_value) """Functions related to accesscode""" def get_accesscode(self): """Get AccessCode value.""" return self._node_access_code.get_value() def set_accesscode(self, accesscode): """Set AccessCode to number x.""" self._node_access_code.set_value( ua.DataValue(ua.Variant(accesscode, ua.VariantType.Int32))) """Functions to get position for crane""" def get_trolley_position_absolute(self): """Get absolute position for Trolley in mm (laser).""" return self._node_trolley_position_m.get_value() * 1000 def get_motorcontroller_trolley_value(self): """Get value from motorcontroller for Trolley.""" return self._node_trolley_position_mm.get_value() def get_bridge_position_absolute(self): """Get absolute position for Bridge in mm (laser).""" return self._node_bridge_position_m.get_value() * 1000 def get_motorcontroller_bridge_value(self): """Get value from motorcontroller for Bridge.""" return self._node_bridge_position_mm.get_value() def get_hoist_position_absolute(self): """ Get absolute position for Hoist in mm """ return self._node_hoist_position_m.get_value() * 1000 def get_motorcontroller_hoist_value(self): """Get value from motorcontroller for Hoist.""" return self._node_hoist_position_mm.get_value() def get_coordinates_absolute(self): """Get absolute position for Bridge,Trolley and Hoist in mm (laser).""" return (self._node_bridge_position_mm.get_value(), self._node_trolley_position_mm.get_value(), self._node_hoist_position_mm.get_value()) def get_motorcontroller_all(self): """Get all values from motorcontroller, Bridge, Trolley, Hoist.""" return (self.get_motorcontroller_bridge_value(), self.get_motorcontroller_trolley_value(), self.get_motorcontroller_hoist_value()) """Functions to get positions with timestamp for bridge and trolley""" def get_motocontroller_bridge_value_with_timestamp(self): """Get value from motorcontroller for Bridge with timestamp.""" data = self._node_bridge_position_mm.get_data_value() return (data.Value.Value, data.SourceTimestamp.timestamp()) def get_motocontroller_trolley_value_with_timestamp(self): """Get value from motorcontroller for trolley with timestamp.""" data = self._node_trolley_position_mm.get_data_value() return (data.Value.Value, data.SourceTimestamp.timestamp()) """Functions to get speed with timestamp for crane""" def get_speed_bridge_value_with_timestamp(self): """Get value from motorcontroller for Bridge with timestamp.""" data = self._node_brige_speed_feedback.get_data_value() return (data.Value.Value, data.SourceTimestamp.timestamp()) def get_speed_hoist_value_with_timestamp(self): """Get value from motorcontroller for Bridge with timestamp.""" data = self._node_hoist_speed_feedback.get_data_value() return (data.Value.Value, data.SourceTimestamp.timestamp()) def get_speed_trolley_value_with_timestamp(self): """Get value from motorcontroller for Bridge with timestamp.""" data = self._node_trolley_speed_feedback.get_data_value() return (data.Value.Value, data.SourceTimestamp.timestamp()) """Crane target functions""" """Functions for setting targets for crane""" def set_target_bridge(self, target): """Set target value of bridge min the other crane, max 25146.""" self.bridge_target = target def set_target_trolley(self, target): """Set target value of trolley min 285, max 8376.""" self.trolley_target = target def set_target_hoist(self, target): """Set target value of Hoist.""" self.hoist_target = target def set_moving_height(self, target): """Set target for moving height of Hoist.""" self.hoist_moving_height = target def set_target_current_position(self): """Set current position to target""" x, y, z = self.get_coordinates_absolute() self.set_target_bridge(x) self.set_target_trolley(y) self.set_target_hoist(z) return x, y, z """Functions to get weight from the crane""" def get_load(self): """Get current load from crane.""" return self._node_hoist_current_load.get_value() def get_load_tared(self): """Get current tared load from crane.""" return self._node_hoist_current_tared_load.get_value() def get_datetime(self): """Get Python datetime object from the crane.""" year = self._node_datime_year.get_value() month = self._node_datime_month.get_value() day = self._node_datime_day.get_value() hour = self._node_datime_hour.get_value() minute = self._node_datime_minute.get_value() second = self._node_datime_second.get_value() millisecond = self._node_datime_millisecond.get_value() return datetime.datetime(year, month, day, hour, minute, second, millisecond * 1000) """Functions for getting distance to target""" def bridge_to_target(self): """Returns mm to pre-set target by set_target_bridge().""" return self.get_bridge_position_absolute() - self.bridge_target def trolley_to_target(self): """Returns mm to pre-set target by set_target_trolley().""" return self.get_trolley_position_absolute() - self.trolley_target def hoist_to_target(self): """Returns mm to pre-set target by set_target_hoist().""" return self.get_hoist_position_absolute() - self.hoist_target def hoist_to_moving_height(self): """Returns mm to pre-set target by set_moving_height().""" return self.get_hoist_position_absolute() - self.hoist_moving_height """Function to ramp cranes speed based on distance""" def ramp_speed(self, distance): """"Ramps the speed based on distance""" if -3 < distance < 3: speed = 0.3 elif -5 < distance < 5: speed = 0.5 elif -10 < distance < 10: speed = 1 elif -50 < distance < 50: speed = 3 elif -100 < distance < 100: speed = 8 elif -300 < distance < 300: speed = 15 else: speed = 20 return speed def ramp_speed2(self, distance): """Ramps the speed based on distance, alternative option""" if -10 < distance < 10: speed = 2 elif -300 < distance < 300: speed = abs(60.0 * distance / 300) else: speed = 30 return speed def ramp_speed_horizontal(self, distance, fast=False): """Ramps the speed based on distance, optimized for horizontal movement. Use fast to limit minimum speed to certain value. Less accurate, but faster""" if -15 < distance < 15: speed = 0.6 elif -2000 < distance < 2000: speed = abs(distance / 20.0) else: speed = 100 return 15 if fast and speed < 15 else speed def ramp_speed_lower(self, distance): """Ramps the speed based on distance, slow ramp for lowering""" if -8 < distance < 8: speed = 2 elif -400 < distance < 400: speed = abs(distance / 4.0) else: speed = 100 return speed def ramp_speed_lift(self, distance): """Ramps the speed based on distance, fast ramp for lifting""" if -8 < distance < 8: speed = 4 elif -200 < distance < 200: speed = abs(distance / 2.0) else: speed = 100 return speed """Premade functions for moving crane to target""" def move_trolley_to_target(self, threshold=1, fast=False): """Moves trolley to target""" # Retrieve updated distance to target dist_to_target = self.trolley_to_target() speed = self.ramp_speed_horizontal(dist_to_target, fast) if -threshold <= dist_to_target <= threshold: self.stop_trolley() print("Trolley on target") flag = 1 elif dist_to_target > 0: self.move_trolley_backward_speed(speed) flag = 0 else: self.move_trolley_forward_speed(speed) flag = 0 return flag def move_bridge_to_target(self, threshold=1, fast=False): """Moves bridge to target""" # Retrieve updated distance to target dist_to_target = self.bridge_to_target() speed = self.ramp_speed_horizontal(dist_to_target, fast) if -threshold <= dist_to_target <= threshold: self.stop_bridge() print("Bridge on target") flag = 1 elif dist_to_target > 0: self.move_bridge_backward_speed(speed) flag = 0 else: self.move_bridge_forward_speed(speed) flag = 0 return flag def move_hoist_to_target_precise(self, target): """ Moves hoist to target by precision of 0.1 mm args: target: Hoist target position return: returns True if hoist is in target location False otherwise """ # Retrieve updated distance to target hoist_pos = self.get_motorcontroller_hoist_value() dist_to_target = target - hoist_pos speed = self.ramp_speed_lower(dist_to_target / 10) if dist_to_target < 0: self.move_hoist_down_speed(speed) return False if dist_to_target > 0: self.move_hoist_up_speed(speed) return False self.stop_hoist() return True def move_hoist_to_target(self, fast=False): """Moves hoist to target""" # Retrieve updated distance to target dist_to_target = self.hoist_to_target() hoist_pos = self.get_hoist_position_absolute() if fast: speed = self.ramp_speed_lift(dist_to_target) else: speed = self.ramp_speed_lower(dist_to_target) if hoist_pos > (self.hoist_target): self.move_hoist_down_speed(speed) flag = 0 elif hoist_pos < (self.hoist_target): self.move_hoist_up_speed(speed) flag = 0 else: self.stop_hoist() print("Hoist in target") flag = 1 return flag def move_hoist_to_target_lift(self): """Moves hoist to target fast""" return self.move_hoist_to_target(fast=True) def move_hoist_to_moving_height(self): """Moves hoist to moving height fast""" # Retrieve updated distance to target dist_to_target = self.hoist_to_moving_height() hoist_pos = self.get_hoist_position_absolute() speed = self.ramp_speed_lift(dist_to_target) if hoist_pos > (self.hoist_moving_height): self.move_hoist_down_speed(speed) flag = 0 elif hoist_pos < (self.hoist_moving_height): self.move_hoist_up_speed(speed) flag = 0 else: self.stop_hoist() print("Hoist in moving height") flag = 1 return flag """Premade functions for moving crane to target with P control""" def speedPcontrol(self, error): """Simple P control for speed""" max = 20 # saturation level = max output threshold = 150 # apply ramp below this error offset = 0 # minimum output Pgain = max / threshold speed = Pgain * abs(error) + offset return speed if speed <= max else max def move_trolley_to_target_p(self, precision=1): """Moves trolley to target""" # Retrieve updated distance to target dist_to_target = self.trolley_to_target() speed = self.speedPcontrol(dist_to_target) flag = 0 if abs(dist_to_target) <= precision / 2: self.stop_trolley() # print("Trolley on target") flag = 1 elif dist_to_target > 0: self.move_trolley_backward_speed(speed) else: self.move_trolley_forward_speed(speed) return flag def move_bridge_to_target_p(self, precision=1): """Moves bridge to target""" # Retrieve updated distance to target dist_to_target = self.bridge_to_target() # speed = self.ramp_speed_horizontal(dist_to_target, fast) speed = self.speedPcontrol(dist_to_target) flag = 0 if abs(dist_to_target) <= precision / 2: self.stop_bridge() # print("Bridge on target") flag = 1 elif dist_to_target > 0: self.move_bridge_backward_speed(speed) else: self.move_bridge_forward_speed(speed) return flag def move_hoist_to_target_p(self, precision=1): """Moves hoist to target""" # Retrieve updated distance to target dist_to_target = self.hoist_to_target() speed = self.speedPcontrol(dist_to_target) flag = 0 if abs(dist_to_target) <= precision / 2: self.stop_hoist() # print("Hoist in target") flag = 1 elif dist_to_target > 0: self.move_hoist_down_speed(speed) else: self.move_hoist_up_speed(speed) return flag def move_hoist_to_moving_height_p(self): """Moves hoist to moving height fast""" stored_target = self.hoist_moving_height # Set current target to moving height self.hoist_target = self.hoist_moving_height flag = self.move_hoist_to_target() # Restore original target value self.hoist_target = stored_target return flag """Crane zeroing functions""" """Functions for zeroing cranes location""" def zero_hoist_position(self): """ Zero the motorcontroller value of Hoist""" self.hoist_last_zeroed = self.get_motorcontroller_hoist_value() def zero_trolley_position(self): """ Zero the motorcontroller value of Trolley""" self.trolley_last_zeroed = self.get_motorcontroller_trolley_value() def zero_bridge_position(self): """ Zero the motorcontroller value of Bridge""" self.bridge_last_zeroed = self.get_motorcontroller_bridge_value() """Functions for getting cranes zeroed location""" def get_zero_bridge_position(self): """Get last zeroed value of Bridge""" return self.bridge_last_zeroed def get_zero_trolley_position(self): """Get last zeroed value of Trolley""" return self.trolley_last_zeroed def get_zero_hoist_position(self): """Get last zeroed value of Hoist""" return self.hoist_last_zeroed """Functions for getting distance to cranes zeroed location""" def get_difference_hoist_to_zero(self): """Difference from Hoist to last zeroed value.""" return self.get_motorcontroller_hoist_value( ) - self.get_zero_hoist_position() def get_difference_trolley_to_zero(self): """Difference from Trolley to last zeroed value.""" return self.get_motorcontroller_trolley_value( ) - self.get_zero_trolley_position() def get_difference_bridge_to_zero(self): """Difference from Bridge to last zeroed value.""" return self.get_motorcontroller_bridge_value( ) - self.get_zero_bridge_position() def get_difference_all_to_zero(self): """Difference from all axis to the last zeroed values, B,T,H.""" return (self.get_difference_bridge_to_zero(), self.get_difference_trolley_to_zero(), self.get_difference_hoist_to_zero()) """Functions for setting cranes movement directions""" def move_trolley_forward(self, boolean=True): """Set Move Trolley Forwardtrue or false.""" self._node_trolley_forward.set_value( ua.DataValue(ua.Variant(boolean, ua.VariantType.Boolean))) def move_trolley_backward(self, boolean=True): """Set Move Trolley Backward true or false.""" self._node_trolley_backward.set_value( ua.DataValue(ua.Variant(boolean, ua.VariantType.Boolean))) def move_bridge_forward(self, boolean=True): """Set Move Bridge Forward true or false.""" self._node_bridge_forward.set_value( ua.DataValue(ua.Variant(boolean, ua.VariantType.Boolean))) def move_bridge_backward(self, boolean=True): """Set Move Bridge Backward true or false.""" self._node_bridge_backward.set_value( ua.DataValue(ua.Variant(boolean, ua.VariantType.Boolean))) def move_hoist_up(self, boolean=True): """Set Move Hoist up true or false.""" self._node_hoist_up.set_value( ua.DataValue(ua.Variant(boolean, ua.VariantType.Boolean))) def move_hoist_down(self, boolean=True): """Set Move Hoist down true or false.""" self._node_hoist_down.set_value( ua.DataValue(ua.Variant(boolean, ua.VariantType.Boolean))) """Functions for getting boolean values of cranes movement directions""" def get_trolley_forward(self): """Get Trolley_Forward boolean value.""" return self._node_trolley_forward.get_value() def get_trolley_backward(self): """Get Trolley_Backward boolean value.""" return self._node_trolley_backward.get_value() def get_bridge_forward(self): """Get Bridge_Forward boolean value.""" return self._node_bridge_forward.get_value() def get_bridge_backward(self): """Get Bridge_Backward boolean value.""" return self._node_bridge_backward.get_value() def get_hoist_up(self): """Get Hoist_up boolean value.""" return self._node_hoist_up.get_value() def get_hoist_down(self): """Get Hoist_down boolean value.""" return self._node_hoist_down.get_value() """Functins to set cranes speed""" def set_trolley_speed(self, speed): """Set speed Trolley in % 0-100""" self._node_trolley_speed.set_value( ua.DataValue(ua.Variant(speed, ua.VariantType.Float))) def set_bridge_speed(self, speed): """Set speed Bridge in % 0-100""" self._node_bridge_speed.set_value( ua.DataValue(ua.Variant(speed, ua.VariantType.Float))) def set_hoist_speed(self, speed): """Set speed Hoist in % 0-100""" self._node_hoist_speed.set_value( ua.DataValue(ua.Variant(speed, ua.VariantType.Float))) """Functins to get cranes speed""" def get_trolley_speed(self): """Get speed Trolley in % 0-100""" return self._node_trolley_speed.get_value() def get_bridge_speed(self): """Get speed Bridge in % 0-100""" return self._node_bridge_speed.get_value() def get_hoist_speed(self): """Get speed Hoist in % 0-100""" return self._node_hoist_speed.get_value() """Functions to set crane move with specific speed to specific deirection""" def move_trolley_forward_speed(self, speed): """Move trolley direction forward with speed""" self.move_trolley_backward(False) self.move_trolley_forward(True) self.set_trolley_speed(speed) return True def move_trolley_backward_speed(self, speed): """Move trolley direction backward with speed""" self.move_trolley_forward(False) self.move_trolley_backward(True) self.set_trolley_speed(speed) return True def move_bridge_forward_speed(self, speed): """Move bridge direction forward with speed""" self.move_bridge_backward(False) self.move_bridge_forward(True) self.set_bridge_speed(speed) return True def move_bridge_backward_speed(self, speed): """Move bridge direction backward with speed""" self.move_bridge_forward(False) self.move_bridge_backward(True) self.set_bridge_speed(speed) return True def move_hoist_down_speed(self, speed): """Move hoist direction forward with speed""" self.move_hoist_up(False) self.move_hoist_down(True) self.set_hoist_speed(speed) return True def move_hoist_up_speed(self, speed): """Move hoist direction backward with speed""" self.move_hoist_down(False) self.move_hoist_up(True) self.set_hoist_speed(speed) return True """Functions for stopping crane""" def stop_bridge(self): """Stop Bridge""" self.set_bridge_speed(0) self.move_bridge_forward(False) self.move_bridge_backward(False) def stop_trolley(self): """Stop Trolley""" self.set_trolley_speed(0) self.move_trolley_forward(False) self.move_trolley_backward(False) def stop_hoist(self): """Stop Hoist""" self.set_hoist_speed(0) self.move_hoist_up(False) self.move_hoist_down(False) def stop_all(self): """Stop all 3 axis""" self.stop_bridge() self.stop_trolley() self.stop_hoist() """Crane control signal status""" def get_trolley_speed_request(self): """ Get requested/target speed (ref), return negative if request is backward """ inv = -1 if self._node_trolley_direction_backwards_request.get_value( ) else 1 return inv * self._node_trolley_speed_request.get_value() def get_trolley_speed_feedback(self): return self._node_trolley_speed_feedback.get_value() def get_trolley_speed_feedback_mmin(self): return self._node_trolley_speed_feedback_mmin.get_value() def get_trolley_status(self): """ Gets the position, target speed and actual speed. Speed request is negative if request is backward """ return self.get_trolley_position_absolute( ), self.get_trolley_speed_request(), self.get_trolley_speed_feedback( ), self.get_trolley_speed_feedback_mmin() def get_bridge_speed_request(self): """ Get requested/target speed (ref), return negative if request is backward """ inv = -1 if self._node_bridge_direction_backward_request.get_value( ) else 1 return inv * self._node_bridge_speed_request.get_value() def get_bridge_speed_feedback(self): return self._node_brige_speed_feedback.get_value() def get_bridge_speed_feedback_mmin(self): return self._node_brige_speed_feedback_mmin.get_value() def get_bridge_status(self): """ Gets the position, target speed and actual speed. Speed is negative if request is backward """ return self.get_bridge_position_absolute( ), self.get_bridge_speed_request(), self.get_bridge_speed_feedback( ), self.get_bridge_speed_feedback_mmin() def get_hoist_speed_feedback(self): return self._node_hoist_speed_feedback.get_value() def get_hoist_speed_feedback_mmin(self): return self._node_hoist_speed_feedback_mmin.get_value() def get_hoist_speed_request(self): """ Get requested/target speed (ref), return negative if request is down """ inv = -1 if self._node_hoist_direction_down_request.get_value() else 1 return inv * self._node_hoist_speed_request.get_value() def get_hoist_status(self): """ Gets the position, target speed and actual speed. Speed is negative if request is down """ return self.get_hoist_position_absolute( ), self.get_hoist_speed_request(), self.get_hoist_speed_feedback( ), self.get_hoist_speed_feedback_mmin() """Functions for subscribing to cranes data""" class SubHandler: """Used with subscriptions""" def __init__(self, queue): self.queue = queue def datachange_notification(self, node, value, data): """Puts data in queue from subscripted node""" self.queue.put([ data.monitored_item.Value.Value.Value, data.monitored_item.Value.SourceTimestamp.timestamp() ]) def sub_trolley(self, interval, queue): """ subscribes to trolleys position. Interval is how often data is updated from wanted node in ms. Queue is the queue where data will be put """ handler = self.SubHandler(queue) sub = self.client.create_subscription(interval, handler) var = self._node_trolley_position_mm handle = sub.subscribe_data_change(var) # subscription can be ended by writing: # sub.unsubscribe(handle) return sub, handle def sub_bridge(self, interval, queue): """ subscribes to bridges position. Interval is how often data is updated from wanted node in ms. Queue is the queue where data will be put """ handler = self.SubHandler(queue) sub = self.client.create_subscription(interval, handler) var = self._node_bridge_position_mm handle = sub.subscribe_data_change(var) # subscription can be ended by writing: # sub.unsubscribe(handle) return sub, handle def sub_hoist(self, interval, queue): """ subscribes to trolleys position. Interval is how often data is updated from wanted node in ms. Queue is the queue where data will be put """ handler = self.SubHandler(queue) sub = self.client.create_subscription(interval, handler) var = self._node_hoist_position_mm handle = sub.subscribe_data_change(var) # subscription can be ended by writing: # sub.unsubscribe(handle) return sub, handle def sub_trolley_speed(self, interval, queue): """ subscribes to trolleys speed. Interval is how often data is updated from wanted node in ms. Queue is the queue where data will be put """ handler = self.SubHandler(queue) sub = self.client.create_subscription(interval, handler) var = self._node_trolley_speed_feedback handle = sub.subscribe_data_change(var) return sub, handle def sub_bridge_speed(self, interval, queue): """ subscribes to bridges speed. Interval is how often data is updated from wanted node in ms. Queue is the queue where data will be put """ handler = self.SubHandler(queue) sub = self.client.create_subscription(interval, handler) var = self._node_brige_speed_feedback handle = sub.subscribe_data_change(var) return sub, handle def sub_hoist_speed(self, interval, queue): """ subscribes to hoists speed. Interval is how often data is updated from wanted node in ms. Queue is the queue where data will be put """ handler = self.SubHandler(queue) sub = self.client.create_subscription(interval, handler) var = self._node_hoist_speed_feedback handle = sub.subscribe_data_change(var) return sub, handle
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.__server_conf = config.get("server") self.__interest_nodes = [] self.__available_object_resources = {} for mapping in self.__server_conf["mapping"]: if mapping.get("deviceNodePattern") is not None: self.__interest_nodes.append( {mapping["deviceNodePattern"]: mapping}) else: 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"): opcua_url = "opc.tcp://" + self.__server_conf.get("url") else: opcua_url = self.__server_conf.get("url") self.client = Client( opcua_url, timeout=self.__server_conf.get("timeoutInMillis", 4000) / 1000) if self.__server_conf["identity"]["type"] == "cert.PEM": 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: log.exception( "Error in ssl configuration - cert or privateKey parameter not found" ) raise 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: log.exception(e) if self.__server_conf["identity"].get("username"): 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")) self.setName( self.__server_conf.get( "name", 'OPC-UA Default ' + ''.join(choice(ascii_lowercase) for _ in range(5))) + " Connector") self.__opcua_nodes = {} self._subscribed = {} self.data_to_send = [] self.__sub_handler = SubHandler(self) self.__stopped = False self.__connected = False self.daemon = True def is_connected(self): return self.__connected def open(self): self.__stopped = False self.start() log.info("Starting OPC-UA Connector") def run(self): while not self.__connected: try: self.__connected = self.client.connect() self.client.load_type_definitions() log.debug(self.client.get_namespace_array()[-1]) log.debug( self.client.get_namespace_index( self.client.get_namespace_array()[-1])) except ConnectionRefusedError: 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: log.debug("error on connection to OPC-UA server.") log.error(e) time.sleep(10) else: self.__connected = True log.info("OPC-UA connector %s connected to server %s", self.get_name(), self.__server_conf.get("url")) self.__opcua_nodes["root"] = self.client.get_root_node() self.__opcua_nodes["objects"] = self.client.get_objects_node() sub = self.client.create_subscription( self.__server_conf.get("scanPeriodInMillis", 500), self.__sub_handler) self.__search_name(self.__opcua_nodes["objects"], 2) self.__search_tags(self.__opcua_nodes["objects"], 2, sub) log.debug('Subscriptions: %s', self.subscribed) log.debug("Available methods: %s", self.__available_object_resources) while True: try: time.sleep(1) if self.data_to_send: self.__gateway.send_to_storage(self.get_name(), self.data_to_send.pop()) if self.__stopped: break except (KeyboardInterrupt, SystemExit): self.close() raise except Exception as e: self.close() log.exception(e) def close(self): self.__stopped = True self.client.disconnect() self.__connected = False log.info('%s has been stopped.', self.get_name()) def get_name(self): return self.name def on_attributes_update(self, content): 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: server_variables[variable].set_value( content["data"][variable]) except Exception as e: log.exception(e) def server_side_rpc_handler(self, content): try: for method in self.__available_object_resources[ content["device"]]['methods']: rpc_method = content["data"].get("method") if rpc_method is not None and method.get( rpc_method) is not None: arguments = content["data"].get("params") if type(arguments) is list: result = method["node"].call_method( method[rpc_method], *arguments) elif arguments is not None: result = method["node"].call_method( method[rpc_method], arguments) else: result = method["node"].call_method(method[rpc_method]) self.__gateway.send_rpc_reply( content["device"], content["data"]["id"], {content["data"]["method"]: result}) log.debug("method %s result is: %s", method[rpc_method], result) except Exception as e: log.exception(e) def __search_name(self, node, recursion_level): try: for childId in node.get_children(): ch = self.client.get_node(childId) current_var_path = '.'.join( x.split(":")[1] for x in ch.get_path(20000, True)) if self.__interest_nodes: if ch.get_node_class() == ua.NodeClass.Object: for interest_node in self.__interest_nodes: for int_node in interest_node: subrecursion_level = recursion_level if subrecursion_level != recursion_level + len( interest_node[int_node] ["deviceNamePattern"].split("\\.")): if ch.get_display_name().Text in TBUtility.get_value(interest_node[int_node]["deviceNamePattern"], get_tag=True).split('.') or \ re.search(TBUtility.get_value(interest_node[int_node]["deviceNodePattern"], get_tag=True), ch.get_display_name().Text): self.__search_name( ch, subrecursion_level + 1) else: return elif ch.get_node_class() == ua.NodeClass.Variable: try: for interest_node in self.__interest_nodes: for int_node in interest_node: if interest_node[int_node].get( "deviceName") is None: try: name_pattern = TBUtility.get_value( interest_node[int_node] ["deviceNamePattern"], get_tag=True) log.debug(current_var_path) device_name_node = re.search( name_pattern.split('\\.')[-1], current_var_path) if device_name_node is not None: device_name = ch.get_value() if "${" + name_pattern + "}" in interest_node[ int_node][ "deviceNamePattern"]: full_device_name = interest_node[ int_node][ "deviceNamePattern"].replace( "${" + name_pattern + "}", device_name) elif device_name in interest_node[ int_node][ "deviceNamePattern"]: full_device_name = interest_node[ int_node][ "deviceNamePattern"].replace( name_pattern, device_name) else: log.error( "Name pattern not found." ) break interest_node[int_node][ "deviceName"] = full_device_name if self.__available_object_resources.get( full_device_name ) is None: self.__available_object_resources[ full_device_name] = { 'methods': [], 'variables': [] } if not self.__gateway.get_devices( ).get(full_device_name): self.__gateway.add_device( full_device_name, {"connector": None}) self.__gateway.update_device( full_device_name, "connector", self) else: try: if re.search( int_node.split( '\\.') [recursion_level - 2], ch. get_display_name( ).Text): self.__search_name( ch, recursion_level + 1) except IndexError: if re.search( int_node.split( '\\.')[-1], ch. get_display_name( ).Text): self.__search_name( ch, recursion_level + 1) except Exception as e: log.exception(e) else: break except BadWaitingForInitialData: pass elif not self.__interest_nodes: log.error( "Nodes in mapping not found, check your settings.") except Exception as e: log.exception(e) def __search_tags(self, node, recursion_level, sub=None): try: for childId in node.get_children(): ch = self.client.get_node(childId) current_var_path = '.'.join( x.split(":")[1] for x in ch.get_path(20000, True)) if self.__interest_nodes: if ch.get_node_class() == ua.NodeClass.Object: for interest_node in self.__interest_nodes: for int_node in interest_node: try: name_to_check = int_node.split('\\.')[ recursion_level - 1] if '\\.' in int_node else int_node name_to_check = int_node.split( '.' )[recursion_level - 1] if '.' in int_node else name_to_check except IndexError: name_to_check = int_node.split( '\\.' )[-1] if '\\.' in int_node else int_node name_to_check = int_node.split( '.' )[-1] if '.' in int_node else name_to_check if re.search(name_to_check, ch.get_display_name().Text): try: methods = ch.get_methods() for method in methods: self.__available_object_resources[ interest_node[int_node] ["deviceName"]][ "methods"].append({ method.get_display_name( ).Text: method, "node": ch }) except Exception as e: log.exception(e) for tag in interest_node[int_node][ "timeseries"] + interest_node[ int_node]["attributes"]: subrecursion_level = recursion_level if subrecursion_level != recursion_level + len( tag["path"].split("\\.")): self.__search_tags( ch, subrecursion_level + 1, sub) else: return self.__search_tags(ch, recursion_level + 1, sub) elif ch.get_node_class() == ua.NodeClass.Variable: try: for interest_node in self.__interest_nodes: for int_node in interest_node: if interest_node[int_node].get( "attributes_updates"): try: for attribute_update in interest_node[ int_node][ "attributes_updates"]: if attribute_update[ "attributeOnDevice"] == ch.get_display_name( ).Text: self.__available_object_resources[ interest_node[int_node] ["deviceName"]][ 'variables'].append({ attribute_update["attributeOnThingsBoard"]: ch, }) except Exception as e: log.exception(e) name_to_check = int_node.split( '\\.' )[-1] if '\\.' in int_node else int_node name_to_check = int_node.split( '.' )[-1] if '.' in int_node else name_to_check if re.search( name_to_check.replace('$', ''), current_var_path): tags = [] if interest_node[int_node].get( "attributes"): tags.extend(interest_node[int_node] ["attributes"]) if interest_node[int_node].get( "timeseries"): tags.extend(interest_node[int_node] ["timeseries"]) for tag in tags: target = TBUtility.get_value( tag["path"], get_tag=True) try: tag_name_for_check = target.split( '\\.' )[recursion_level - 1] if '\\.' in target else target tag_name_for_check = target.split( '.' )[recursion_level - 1] if '.' in target else tag_name_for_check except IndexError: tag_name_for_check = target.split( '\\.' )[-1] if '\\.' in target else target tag_name_for_check = target.split( '.' )[-1] if '.' in target else tag_name_for_check current_node_name = ch.get_display_name( ).Text if current_node_name == tag_name_for_check: sub.subscribe_data_change(ch) if interest_node[int_node].get( "uplink_converter" ) is None: if interest_node[ int_node].get( 'converter' ) is None: converter = OpcUaUplinkConverter( interest_node[ int_node]) else: converter = TBUtility.check_and_import( self. __connector_type, interest_node[ int_node] ['converter']) interest_node[int_node][ "uplink_converter"] = converter else: converter = interest_node[ int_node][ "uplink_converter"] self.subscribed[ch] = { "converter": converter, "path": current_var_path } else: return except BadWaitingForInitialData: pass elif not self.__interest_nodes: log.error( "Nodes in mapping not found, check your settings.") except Exception as e: log.exception(e) @property def subscribed(self): return self._subscribed
class OpcProtocol(): ''' OPC Protocol settings ''' def string_to_node_type(self, type='string'): if type == 'string': return ua.NodeIdType.String elif type == 'numeric': return ua.NodeIdType.Numeric else: return ua.NodeIdType.ByteString def __del__(self): self.Disconnect() def __init__(self, server_name): self.server_name = server_name namespaces = VAR.OPC_SERVERS[server_name]["namespaces"] # Get OPC UA node type for namespace in namespaces.keys(): nodes = VAR.OPC_SERVERS[server_name]["namespaces"][namespace]["nodes"] for node in nodes: type = nodes[node]["opc_type"] nodes[node]["opc_type"] = self.string_to_node_type(type) self.Connect() def Connect(self): ''' For creating connection to OPC UA Server ''' server = VAR.OPC_SERVERS[self.server_name]["opc_server"] namespaces = VAR.OPC_SERVERS[self.server_name]["namespaces"] self.client = Client(server) try: self.client.connect() for namespace in namespaces.keys(): VAR.OPC_SERVERS[self.server_name]["namespaces"][namespace]["index"] = self.client.get_namespace_index(namespace) except Exception as e: print('Connect error:', e) finally: self.firstrun() def remove_subscribe(self): namespaces = VAR.OPC_SERVERS[self.server_name]["namespaces"] for namespace in namespaces.keys(): nodes = VAR.OPC_SERVERS[self.server_name]['namespaces'][namespace]["nodes"] for node in nodes.keys(): VAR.OPC_SERVERS[self.server_name]['namespaces'][namespace]["nodes"][node]["subscribe"].unsubscribe( VAR.OPC_SERVERS[self.server_name]['namespaces'][namespace]["nodes"][node]["handler"] ) VAR.OPC_SERVERS[self.server_name]['namespaces'][namespace]["nodes"][node]["subscribe"].delete() return True def Disconnect(self): ''' Disconnect from OPC UA Server ''' try: self.remove_subscribe() self.client.disconnect() except Exception as e: print('Disconnect error:', e) return "disconnect done" def create_node(self, node, namespace, type): return ua.NodeId(identifier=node, namespaceidx=namespace, nodeidtype=type) def firstrun(self): ''' When OPC UA Server connection has been established, do this once ''' try: if VAR.FIRST_RUN: namespaces = VAR.OPC_SERVERS[self.server_name]["namespaces"] for namespace in namespaces.keys(): index = VAR.OPC_SERVERS[self.server_name]["namespaces"][namespace]["index"] nodes = VAR.OPC_SERVERS[self.server_name]["namespaces"][namespace]["nodes"] for node in nodes.keys(): type = VAR.OPC_SERVERS[self.server_name]["namespaces"][namespace]["nodes"][node]["opc_type"] nodeid = self.create_node(node, index, type) VAR.OPC_SERVERS[self.server_name]["namespaces"][namespace]["nodes"][node]["opc_nodeid"] = nodeid this_node = self.client.get_node(nodeid) VAR.OPC_SERVERS[self.server_name]["namespaces"][namespace]["nodes"][node]["opc_variable"] = this_node value = this_node.get_value() VAR.OPC_SERVERS[self.server_name]["namespaces"][namespace]["nodes"][node]["value"] = value VAR.OPC_SERVERS[self.server_name]["namespaces"][namespace]["nodes"][node]["timestamp"] = str(datetime.datetime.utcnow()) VAR.OPC_SERVERS[self.server_name]["namespaces"][namespace]["nodes"][node]["handler"] = NodeHandler(self.server_name, namespace) VAR.OPC_SERVERS[self.server_name]["namespaces"][namespace]["nodes"][node]["subscribe"] = self.client.create_subscription( 100, VAR.OPC_SERVERS[self.server_name]["namespaces"][namespace]["nodes"][node]["handler"] ) VAR.OPC_SERVERS[self.server_name]["namespaces"][namespace]["nodes"][node]["subscribe"].subscribe_data_change(this_node) except Exception as e: print('firstRun error:', e) finally: VAR.FIRST_RUN = False def reset(self): self.remove_subscribe() VAR.FIRST_RUN = True self.firstrun()
class Subscriber(): """Create a service to monitor OPC nodes. """ def __init__(self, callback, **params): """Instantiate an the base class OPC client. Arguments callback (func): Function to call on node value changes. params (dict): Configuration parameters to start service. The dictionary should contain: endpoint (str): OPC server address uri (str): the namespace for the nodes obj (str): the parent object node of the nodes nodes (dict): Keys are the name of the nodes to monitor. The values are dictionaries: "respond": node to respond watchdog (optional) (str): the name of the node used for the watchdog """ self._callback = callback self._params = params def _connect(self, endpoint): """Conncect to the OPC UA server. Arguments endpoint (str): OPC server address """ self._client = Client(endpoint) self._client.connect() def _get_nodes(self, idx, obj, names): """Get links to the OPC nodes. Arguments idx (int): Namespace index obj (str): Object name names (list): List of string of node names returns (list): List of Nodes """ idx = self._client.get_namespace_index(idx) root = self._client.get_root_node() nodes = [root.get_child(self._get_path(idx, obj, n)) for n in names] return nodes def _get_path(self, idx, obj, name): """Make a browse path list from the namespace index and object and node name. Arguments idx (int): Namespace index obj (str): Object name name (str): Name of node Return (list) browse path """ bp = ["0:Objects", f"{idx}:{obj}", f"{idx}:{name}"] return bp def _get_name(self, node): """ Get the name of the node from the mapping. Arguments node (node): Node object return (str) name associated with node """ return [k for k, v in self._map.items() if v == node][0] def _create_node_map(self): """Create a map between node names and Node objects for fast lookup. """ # Get all the nodes defined in the parameters dictionary. names = list(self._params["nodes"].keys())\ + [v["respond"] for k, v in self._params["nodes"].items()] if "watchdog" in self._params: names += [ self._params["watchdog"]["controller"], self._params["watchdog"]["instrument"] ] nodes = self._get_nodes(self._params["uri"], self._params["obj"], names) return {k: v for k, v in zip(names, nodes)} def _get_monitor_nodes(self): """Get the nodes to be monitored. """ names = list(self._params["nodes"].keys()) if "watchdog" in self._params: names += [self._params["watchdog"]["controller"]] return self._get_nodes(self._params["uri"], self._params["obj"], names) def _update_watchdog(self, val): """Update the watchdog. This is handled at the subscription service level and not instrument to reduce CPU overhead. Arguments val (?): The value to return to the watchdog tag. """ # self.respond(self._params["watchdog"]["instrument"], val) def respond(self, node, value): """Write a value to the node. Arguments node (str): String associated with node (see self._map) value (varries): value to write """ self._map[node].set_value(value) def datachange_notification(self, node, val, data): """This method is called on subscribed node changes. see https://python-opcua.readthedocs.io/en/latest/subscription.html """ # If the node is the watchdog node, then update without # calling instrument method to reduce CPU cycles. if "watchdog" in self._params: if node == self._map[self._params["watchdog"]["controller"]]: self._update_watchdog(val) else: name = self._get_name(node) # Call the instrument callback with the node information: # desired run command, # callback to the respond method with the node as parameter self._callback( command=self._params["nodes"][name]["command"], parameters=val, callback=lambda x: self.respond( self._params["nodes"][name]["respond"], x), ) def run(self): """Connect to client, and subscribe to nodes. """ self._connect(self._params["endpoint"]) self._map = self._create_node_map() sub = self._client.create_subscription(1000, self) nodes = self._get_monitor_nodes() handle = sub.subscribe_data_change(nodes) logger.info("OPC subscription started")
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()
class CustomClient(object): def __init__(self, server_endpoint, namespace, enable_cert, client_cert_path, client_key_path, auth_name=None, auth_password=None, debug_print=False, client_request_timeout=4): self.NAMESPACE = namespace self.DEBUG_MODE_PRINT = debug_print self.client = Client(server_endpoint, client_request_timeout) if auth_name is not None and auth_password is not None: self.client.set_user(auth_name) self.client.set_password(auth_password) if enable_cert: self.client.set_security_string("Basic256Sha256,SignAndEncrypt," + client_cert_path + "," + client_key_path) # TODO experimental( cf. client.py @line 60) # self.client.session_timeout = 10*1000 # 30h = 30*60*60*1000 # self.client.secure_channel_timeout = 10*1000 # 30h self.root = None self.idx = None def start(self): try: self.client.connect() except ConnectionError as er: print(DateHelper.get_local_datetime(), er) raise # sys.exit(1) except Exception as ex: print(DateHelper.get_local_datetime(), ex) raise # sys.exit(1) # Now getting root variable node using its browse path self.root = self.client.get_root_node() uri = self.NAMESPACE self.idx = self.client.get_namespace_index(uri) def stop(self): try: self.client.disconnect() except Exception as ex: print("Couldn't stop OPC Client because of: ", ex) def get_server_vars(self, child): # TODO raise TimeOutError when called after subscription was set up, (cf. ua_client.py: send_request) try: obj = self.root.get_child( ["0:Objects", ("{}:" + child).format(self.idx)]) # print(obj.get_browse_name()) # print(obj.get_variables()) except BadNoMatch: return None return obj.get_variables() def create_dir_on_server(self, child): # get object node objects_node = self.client.get_objects_node() # add new folder "child" first for method in objects_node.get_methods(): # print(method.get_browse_name().Name) if "ADD_NEW_OBJECTS_FOLDER" in method.get_browse_name().Name: objects_node.call_method(method, child) def register_variables_to_server(self, child, file_path): # get object node objects_node = self.client.get_objects_node() # get tags of variables and register them serverside in folder "child" mtagfile = open(file_path, 'r') tags_pf_output = format_textfile(mtagfile.readlines()) mtagfile.close() # VARIANT A for method in objects_node.get_methods(): # print(method.get_browse_name().Name) if "ADD_OPC_TAG" in method.get_browse_name().Name: for i in tags_pf_output: opctag, typ = i.split() opctag, typ = opctag.strip(), int(typ) # call method to register var objects_node.call_method(method, opctag, numbers_to_typestrings(typ), child) # VARIANT B # for i in tags_pf_output: # opctag, typ = i.split() # opctag, typ = opctag.strip(), int(typ) # # # # register vars at server # mvar = folder.add_variable(self.idx, opctag.strip(), ua.Variant(0, numbers_to_vartyps(typ))) # mvar.set_writable() # # # Test # # dv = DataValue() # # dv.Value = ua.Variant(1,numbers_to_vartyps(typ)) # # mvar.set_value(dv) @staticmethod def set_vars(observed_nodes_list, ctrl_list, value_list): """ Set new value for node. :param observed_nodes_list: list of nodes, the client subscribed to :param ctrl_list: list of nodes to update :param value_list: list of values to assign """ i = 0 for ctrl in ctrl_list: for var in observed_nodes_list: if var.nodeid == ctrl.nodeid: try: variant_type = var.get_data_value().Value.VariantType var.set_value(value_list[i], variant_type) break except Exception as ex: if type(ex).__name__ in TimeoutError.__name__: print( DateHelper.get_local_datetime(), 'TimeOutError ignored while set var in OPCClient' ) pass else: print(DateHelper.get_local_datetime(), ex) raise i += 1 # region subscription def _subscribe(self, dir_name, sub_handler, subscription, subscription_handle, list_of_nodes_to_subscribe, already_subscribed_nodes, sub_interval): """ Make a subscription for list of nodes and return handle for subscription :param dir_name: subfolder, which contains the requested nodes :param sub_handler: SubHandler which will call the update_data function :param subscription: subscription object :param subscription_handle: handle can used to unsubscribe :param list_of_nodes_to_subscribe: list of nodes/customVars :param already_subscribed_nodes: list of nodes which already within subscription :param sub_interval: time interval the subscribed node is checked (in ms) :return subscription: :return subscription_handle :return subscribed_nodes """ if subscription is not None: self._unsubscribe(subscription, subscription_handle) already_subscribed_nodes = [] all_server_nodes = self.get_server_vars(dir_name) for node in all_server_nodes: for var in list_of_nodes_to_subscribe: if node.nodeid == var.nodeid: already_subscribed_nodes.append(node) # make subscription subscription = self.client.create_subscription(sub_interval, sub_handler) subscription_handle = subscription.subscribe_data_change( already_subscribed_nodes) return subscription, subscription_handle, already_subscribed_nodes # will raise TimeoutError() - why? --> use self.subscription.delete() instead @staticmethod def _unsubscribe(self, subscription, subscription_handle): if subscription_handle is not None: # self.stop() # self.start() # self.subscription.delete() subscription.unsubscribe(subscription_handle)
class NodeXMLExporter: def __init__(self): self.nodes = [] self.namespaces = {} self.visited = [] self.client = None self.logger = logging.getLogger(__name__) self.logger.setLevel(logging.INFO) def iterater_over_child_nodes(self, node): self.nodes.append(node) self.logger.debug("Add %s" % node) # iterate over all referenced nodes (31), only hierarchical references (33) for child in node.get_children(refs=33): if child not in self.nodes: self.iterater_over_child_nodes(child) def export_xml(self, namespaces=None, output_file="export.xml"): if namespaces: self.logger.info("Export only NS %s" % namespaces) nodes = [node for node in self.nodes if node.nodeid.NamespaceIndex in namespaces] else: nodes = self.nodes self.logger.info("Export nodes to %s" % output_file) exp = XmlExporter(self.client) exp.build_etree(nodes) exp.write_xml(output_file) self.logger.info("Export finished") def import_nodes(self, server_url="opc.tcp://localhost:16664"): from opcua.crypto import security_policies import types from opcua.ua.uaprotocol_hand import CryptographyNone self.client = Client(server_url) # Fix symmetric_key_size (not 0) of securityPolicy sec_policy = security_policies.SecurityPolicy() sec_policy.symmetric_key_size = 8 self.client.security_policy = sec_policy # Fix signature method of CryptographyNone def signature(self, data): return None fixed_signature = types.MethodType(signature, CryptographyNone) self.client.security_policy.asymmetric_cryptography.signature = fixed_signature try: self.client.connect() except Exception as e: self.logger.error("No connection established", e) self.logger.error(e) self.logger.error("Exiting ...") sys.exit() self.logger.info("Client connected to %s" % server_url) for ns in self.client.get_namespace_array(): self.namespaces[self.client.get_namespace_index(ns)] = ns root = self.client.get_root_node() self.logger.info("Starting to collect nodes. This may take some time ...") self.iterater_over_child_nodes(root) self.logger.info("All nodes collected") def statistics(self): types = {} for node in self.nodes: try: node_class = str(node.get_node_class()) ns = node.nodeid.NamespaceIndex if ns not in types: types[ns] = {} if node_class not in types[ns]: types[ns][node_class] = 1 else: types[ns][node_class] += 1 except Exception as e: self.logger.info("some error with %s: %s" % (node,e)) for ns in types: self.logger.info("NS%d (%s)" % (ns, self.namespaces[ns])) for type in types[ns]: self.logger.info("\t%s:\t%d" % (type, types[ns][type])) self.logger.info("\tTOTAL in namespace: %d" % len(self.nodes))
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.__server_conf = config.get("server") 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: 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 = Client( self.__opcua_url, timeout=self.__server_conf.get("timeoutInMillis", 4000) / 1000) if self.__server_conf["identity"].get("type") == "cert.PEM": 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: 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: log.exception(e) if self.__server_conf["identity"].get("username"): 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")) self.setName( self.__server_conf.get( "name", 'OPC-UA ' + ''.join(choice(ascii_lowercase) for _ in range(5))) + " Connector") self.__opcua_nodes = {} self._subscribed = {} self.__sub = None self.__sub_handler = SubHandler(self) self.data_to_send = [] self.__stopped = False self.__connected = False self.daemon = True def is_connected(self): return self.__connected def open(self): self.__stopped = False self.start() log.info("Starting OPC-UA Connector") def run(self): while not self.__connected: try: self.client.connect() try: self.client.load_type_definitions() except Exception as e: log.debug(e) log.debug("Error on loading type definitions.") log.debug(self.client.get_namespace_array()[-1]) log.debug( self.client.get_namespace_index( self.client.get_namespace_array()[-1])) except ConnectionRefusedError: log.error( "Connection refused on connection to OPC-UA server with url %s", self.__server_conf.get("url")) time.sleep(10) except OSError: 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: log.debug("error on connection to OPC-UA server.") log.error(e) time.sleep(10) else: self.__connected = True log.info("OPC-UA connector %s connected to server %s", self.get_name(), self.__server_conf.get("url")) self.__initialize_client() while not self.__stopped: try: time.sleep(.1) self.__check_connection() if not self.__connected and not self.__stopped: self.client.connect() self.__initialize_client() log.info("Reconnected to the OPC-UA server - %s", self.__server_conf.get("url")) 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.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: log.error( "Connection failed on connection to OPC-UA server with url %s", self.__server_conf.get("url")) log.exception(e) self.client = Client( self.__opcua_url, timeout=self.__server_conf.get("timeoutInMillis", 4000) / 1000) self._subscribed = {} self.__available_object_resources = {} time.sleep(10) def __check_connection(self): try: node = self.client.get_root_node() node.get_children() if not self.__server_conf.get("disableSubscriptions", False) and ( not self.__connected or not self.subscribed): self.__sub = self.client.create_subscription( self.__server_conf.get("subCheckPeriodInMillis", 500), self.__sub_handler) self.__connected = True except ConnectionRefusedError: self.__connected = False self._subscribed = {} self.__available_object_resources = {} self.__sub = None except OSError: self.__connected = False self._subscribed = {} self.__available_object_resources = {} self.__sub = None except FuturesTimeoutError: self.__connected = False self._subscribed = {} self.__available_object_resources = {} self.__sub = None except AttributeError: self.__connected = False self._subscribed = {} self.__available_object_resources = {} self.__sub = None except Exception as e: self.__connected = False self._subscribed = {} self.__available_object_resources = {} self.__sub = None log.exception(e) def close(self): self.__stopped = True if self.__connected: self.client.disconnect() self.__connected = False log.info('%s has been stopped.', self.get_name()) def get_name(self): return self.name def on_attributes_update(self, content): 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: 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: log.exception(e) def server_side_rpc_handler(self, content): try: rpc_method = content["data"].get("method") 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 }) log.debug("method %s result is: %s", method[rpc_method], result) except Exception as e: log.exception(e) self.__gateway.send_rpc_reply(content["device"], content["data"]["id"], { "error": str(e), "code": 500 }) else: 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: 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 log.debug('Subscriptions: %s', self.subscribed) 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: log.error( "Device node is None, please check your configuration." ) log.debug( "Current device node is: %s", str( device_configuration.get( "deviceNodePattern"))) break except BrokenPipeError: log.debug("Broken Pipe. Connection lost.") except OSError: log.debug("Stop on scanning.") except FuturesTimeoutError: self.__check_connection() except Exception as e: log.exception(e) log.debug(self.__interest_nodes) except Exception as e: 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]: information_key = information["key"] 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_nodes = [] self.__search_node(device_info["deviceNode"], information_path, result=information_nodes) for information_node in information_nodes: if information_node is not None: try: information_value = information_node.get_value() except: log.error("Err get_value: %s", str(information_node)) continue log.debug( "Node for %s \"%s\" with path: %s - FOUND! Current values is: %s", information_type, information_key, information_path, str(information_value)) 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) else: converter = TBModuleLoader.import_module( self._connector_type, configuration) device_info["uplink_converter"] = converter else: converter = device_info["uplink_converter"] self.subscribed[information_node] = { "converter": converter, "path": information_path, "config_path": config_path } if not device_info.get( information_types[information_type]): device_info[ information_types[information_type]] = [] converted_data = converter.convert( (config_path, information_path), information_value) self.statistics['MessagesReceived'] = self.statistics[ 'MessagesReceived'] + 1 self.data_to_send.append(converted_data) self.statistics['MessagesSent'] = self.statistics[ 'MessagesSent'] + 1 log.debug("Data to ThingsBoard: %s", converted_data) if not self.__server_conf.get("disableSubscriptions", False): sub_nodes.append(information_node) else: log.error( "Node for %s \"%s\" with path %s - NOT FOUND!", information_type, information_key, information_path) 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) 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: log.error( "Node for method with path %s - NOT FOUND!", method_node_path) except Exception as e: 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: self.__available_object_resources[device_name][ "variables"].append({ attribute_update["attributeOnThingsBoard"]: attribute_node }) else: log.error( "Attribute update node with path \"%s\" - NOT FOUND!", attribute_path) except Exception as e: 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) 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: 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) 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 == "": 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 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: 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 log.debug("Device type: %s", full_device_type) else: result_device_dict["deviceType"] = "default" result.append(result_device_dict) else: log.error( "Device node not found with expression: %s", TBUtility.get_value(device["deviceNodePattern"], get_tag=True)) return result # # get fullpath of node as string # # this is verry slow # path = '\\.'.join(char.split(":")[1] for char in node.get_path(200000, True)) # i think we don't need \\. # def get_node_path(self, node): ID = node.nodeid.Identifier if ID == 85: return 'Root.Objects' # this is Root if type(ID) == int: ID = node.get_browse_name( ).Name # for int Identifer we take browsename return 'Root.Objects.' + ID 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: log.debug("Looking for node with config") node = self.client.get_node(fullpath) if node is None: log.warning("NODE NOT FOUND - using configuration %s", fullpath) else: log.debug("Found in %s", node) 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: 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: 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: log.debug( "SHOW MAP: Current node path: %s - NODE FOUND", new_node_path) if new_node_class == ua.NodeClass.Object: if self.__show_map: log.debug("SHOW MAP: Search in %s", new_node_path) self.__search_node(child_node, fullpath, result=result) elif new_node_class == ua.NodeClass.Variable: log.debug("Found in %s", new_node_path) result.append(child_node) elif new_node_class == ua.NodeClass.Method and search_method: log.debug("Found in %s", new_node_path) result.append(child_node) except CancelledError: log.error( "Request during search has been canceled by the OPC-UA server." ) except BrokenPipeError: log.error("Broken Pipe. Connection lost.") except OSError: log.debug("Stop on scanning.") except Exception as e: 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[-3:] != '\\.': information_path = node_path + '\\\\.' + config_path.replace( '\\', '\\\\') else: information_path = node_path + config_path.replace( '\\', '\\\\') else: information_path = config_path result = information_path[:] return result @property def subscribed(self): return self._subscribed
# Node objects have methods to read and write node attributes as well as browse or populate address space print("Children of root are: ", root.get_children()) # get a specific node knowing its node id #var = client.get_node(ua.NodeId(1002, 2)) #var = client.get_node("ns=3;i=2002") #print(var) #var.get_data_value() # get value of node as a DataValue object #var.get_value() # get value of node as a python builtin #var.set_value(ua.Variant([23], ua.VariantType.Int64)) #set node value using explicit data type #var.set_value(3.9) # set node value using implicit data type # gettting our namespace idx uri = "http://examples.freeopcua.github.io" idx = client.get_namespace_index(uri) # Now getting a variable node using its browse path myvar = root.get_child([ "0:Objects", "{}:MyObject".format(idx), "{}:MyVariable".format(idx) ]) obj = root.get_child(["0:Objects", "{}:MyObject".format(idx)]) print("myvar is: ", myvar) # subscribing to a variable node handler = SubHandler() sub = client.create_subscription(500, handler) handle = sub.subscribe_data_change(myvar) time.sleep(0.1) # we can also subscribe to events from server
# Node objects have methods to read and write node attributes as well as browse or populate address space print("Children of root are: ", root.get_children()) # get a specific node knowing its node id #var = client.get_node(ua.NodeId(1002, 2)) #var = client.get_node("ns=3;i=2002") #print(var) #var.get_data_value() # get value of node as a DataValue object #var.get_value() # get value of node as a python builtin #var.set_value(ua.Variant([23], ua.VariantType.Int64)) #set node value using explicit data type #var.set_value(3.9) # set node value using implicit data type # gettting our namespace idx uri = "http://examples.freeopcua.github.io" idx = client.get_namespace_index(uri) # Now getting a variable node using its browse path myvar = root.get_child(["0:Objects", "{}:MyObject".format(idx), "{}:MyVariable".format(idx)]) obj = root.get_child(["0:Objects", "{}:MyObject".format(idx)]) print("myvar is: ", myvar) # subscribing to a variable node handler = SubHandler() sub = client.create_subscription(500, handler) handle = sub.subscribe_data_change(myvar) time.sleep(0.1) # we can also subscribe to events from server sub.subscribe_events() # sub.unsubscribe(handle)
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.__server_conf = config.get("server") 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: 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"): opcua_url = "opc.tcp://" + self.__server_conf.get("url") else: opcua_url = self.__server_conf.get("url") self.client = Client( opcua_url, timeout=self.__server_conf.get("timeoutInMillis", 4000) / 1000) if self.__server_conf["identity"]["type"] == "cert.PEM": 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: log.exception( "Error in ssl configuration - cert or privateKey parameter not found" ) raise 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: log.exception(e) if self.__server_conf["identity"].get("username"): 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")) self.setName( self.__server_conf.get( "name", 'OPC-UA ' + ''.join(choice(ascii_lowercase) for _ in range(5))) + " Connector") self.__opcua_nodes = {} self._subscribed = {} self.data_to_send = [] self.__sub_handler = SubHandler(self) self.__stopped = False self.__connected = False self.daemon = True def is_connected(self): return self.__connected def open(self): self.__stopped = False self.start() log.info("Starting OPC-UA Connector") def run(self): while not self.__connected: try: self.__connected = self.client.connect() try: self.client.load_type_definitions() except Exception as e: log.debug(e) log.debug("Error on loading type definitions.") log.debug(self.client.get_namespace_array()[-1]) log.debug( self.client.get_namespace_index( self.client.get_namespace_array()[-1])) except ConnectionRefusedError: log.error( "Connection refused on connection to OPC-UA server with url %s", self.__server_conf.get("url")) time.sleep(10) except OSError: 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: log.debug("error on connection to OPC-UA server.") log.error(e) time.sleep(10) else: self.__connected = True log.info("OPC-UA connector %s connected to server %s", self.get_name(), self.__server_conf.get("url")) self.__opcua_nodes["root"] = self.client.get_objects_node() self.__opcua_nodes["objects"] = self.client.get_objects_node() if not self.__server_conf.get("disableSubscriptions", False): self.__sub = self.client.create_subscription( self.__server_conf.get("subCheckPeriodInMillis", 500), self.__sub_handler) else: self.__sub = False self.__scan_nodes_from_config() self.__previous_scan_time = time.time() * 1000 log.debug('Subscriptions: %s', self.subscribed) log.debug("Available methods: %s", self.__available_object_resources) while not self.__stopped: try: time.sleep(.1) self.__check_connection() if not self.__connected and not self.__stopped: self.client.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 if self.data_to_send: self.__gateway.send_to_storage(self.get_name(), self.data_to_send.pop()) if self.__stopped: self.close() break except (KeyboardInterrupt, SystemExit): self.close() raise except ConnectionRefusedError: 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.close() log.exception(e) def __check_connection(self): try: node = self.client.get_root_node() node.get_children() self.__connected = True except ConnectionRefusedError: self.__connected = False self._subscribed = {} self.__sub = None except OSError: self.__connected = False self._subscribed = {} self.__sub = None except TimeoutError: self.__connected = False self._subscribed = {} self.__sub = None except AttributeError: self.__connected = False self._subscribed = {} self.__sub = None except Exception as e: self.__connected = False self._subscribed = {} self.__sub = None log.exception(e) def close(self): self.__stopped = True if self.__connected: self.client.disconnect() self.__connected = False log.info('%s has been stopped.', self.get_name()) def get_name(self): return self.name def on_attributes_update(self, content): 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: server_variables[variable].set_value( content["data"][variable]) except Exception as e: log.exception(e) def server_side_rpc_handler(self, content): try: for method in self.__available_object_resources[ content["device"]]['methods']: rpc_method = content["data"].get("method") 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 type(arguments) is list: result = method["node"].call_method( method[rpc_method], *arguments) elif arguments is not None: result = method["node"].call_method( method[rpc_method], 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 }) log.debug("method %s result is: %s", method[rpc_method], result) except Exception as e: log.exception(e) self.__gateway.send_rpc_reply(content["device"], content["data"]["id"], { "error": str(e), "code": 500 }) else: 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: log.exception(e) 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: log.error( "Device node is None, please check your configuration." ) log.debug( "Current device node is: %s", str( device_configuration.get( "deviceNodePattern"))) break except Exception as e: log.exception(e) log.debug(self.__interest_nodes) except Exception as e: log.exception(e) def __search_nodes_and_subscribe(self, device_info): information_types = { "attributes": "attributes", "timeseries": "telemetry" } for information_type in information_types: for information in device_info["configuration"][information_type]: information_key = information["key"] 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_nodes = [] self.__search_node(device_info["deviceNode"], information_path, result=information_nodes) for information_node in information_nodes: if information_node is not None: information_value = information_node.get_value() log.debug( "Node for %s \"%s\" with path: %s - FOUND! Current values is: %s", information_type, information_key, information_path, str(information_value)) 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) else: converter = TBUtility.check_and_import( self._connector_type, configuration) device_info["uplink_converter"] = converter else: converter = device_info["uplink_converter"] self.subscribed[information_node] = { "converter": converter, "path": information_path, "config_path": config_path } if not device_info.get( information_types[information_type]): device_info[ information_types[information_type]] = [] converted_data = converter.convert( (config_path, information_path), information_value) self.statistics['MessagesReceived'] += 1 self.data_to_send.append(converted_data) self.statistics['MessagesSent'] += 1 if self.__sub is None: self.__sub = self.client.create_subscription( self.__server_conf.get( "subCheckPeriodInMillis", 500), self.__sub_handler) if self.__sub: self.__sub.subscribe_data_change(information_node) log.debug("Added subscription to node: %s", str(information_node)) log.debug("Data to ThingsBoard: %s", converted_data) else: log.error( "Node for %s \"%s\" with path %s - NOT FOUND!", information_type, information_key, information_path) 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: log.error( "Node for method with path %s - NOT FOUND!", method_node_path) except Exception as e: 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: self.__available_object_resources[device_name][ "variables"].append({ attribute_update["attributeOnThingsBoard"]: attribute_node }) else: log.error( "Attribute update node with path \"%s\" - NOT FOUND!", attribute_path) except Exception as e: 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) 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: log.debug("Looking for device name") name_path = self._check_path(name_expression, device_node) device_name_node = [] self.__search_node(device_node, name_path, result=device_name_node) device_name_node = device_name_node[0] if device_name_node is not None: device_name_from_node = device_name_node.get_value() full_device_name = name_pattern_config.replace( "${" + name_expression + "}", str(device_name_from_node)).replace( name_expression, str(device_name_from_node)) else: log.error( "Device name node not found with expression: %s", name_expression) return else: full_device_name = name_expression result_device_dict["deviceName"] = full_device_name 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: 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 log.debug("Device type: %s", full_device_type) else: result_device_dict["deviceType"] = "default" result.append(result_device_dict) else: log.error( "Device node not found with expression: %s", TBUtility.get_value(device["deviceNodePattern"], get_tag=True)) return result def __search_node(self, current_node, fullpath, search_method=False, result=[]): fullpath_pattern = regex.compile(fullpath) try: for child_node in current_node.get_children(): new_node = self.client.get_node(child_node) new_node_path = '\\\\.'.join( char.split(":")[1] for char in new_node.get_path(200000, True)) if self.__show_map: log.debug("SHOW MAP: Current node path: %s", new_node_path) new_node_class = new_node.get_node_class() # regex_fullmatch = re.fullmatch(fullpath, new_node_path.replace('\\\\.', '.')) or new_node_path.replace('\\\\', '\\') == fullpath regex_fullmatch = regex.fullmatch(fullpath_pattern, new_node_path.replace('\\\\.', '.')) or \ new_node_path.replace('\\\\', '\\') == fullpath.replace('\\\\', '\\') or \ new_node_path.replace('\\\\', '\\') == fullpath regex_search = fullpath_pattern.fullmatch(new_node_path.replace('\\\\.', '.'), partial=True) or \ new_node_path.replace('\\\\', '\\') in fullpath.replace('\\\\', '\\') # regex_search = re.search(new_node_path, fullpath.replace('\\\\', '\\')) if regex_fullmatch: if self.__show_map: log.debug( "SHOW MAP: Current node path: %s - NODE FOUND", new_node_path.replace('\\\\', '\\')) result.append(new_node) elif regex_search: if self.__show_map: log.debug( "SHOW MAP: Current node path: %s - NODE FOUND", new_node_path) if new_node_class == ua.NodeClass.Object: if self.__show_map: log.debug("SHOW MAP: Search in %s", new_node_path) self.__search_node(new_node, fullpath, result=result) elif new_node_class == ua.NodeClass.Variable: log.debug("Found in %s", new_node_path) result.append(new_node) elif new_node_class == ua.NodeClass.Method and search_method: log.debug("Found in %s", new_node_path) result.append(new_node) except Exception as e: log.exception(e) def _check_path(self, config_path, node): if re.search("^root", config_path.lower()) is None: node_path = '\\\\.'.join( char.split(":")[1] for char in node.get_path(200000, True)) if config_path[-3:] != '\\.': information_path = node_path + '\\\\.' + config_path.replace( '\\', '\\\\') else: information_path = node_path + config_path.replace( '\\', '\\\\') else: information_path = config_path return information_path[:] @property def subscribed(self): return self._subscribed
t1.start() client = Client("opc.tcp://localhost:4840/freeopcua/server/") try: client.connect() # Client has a few methods to get proxy to UA nodes that should always be in address space such as Root or Objects root = client.get_root_node() print("Objects node is: ", root) print("Children of root are: ", root.get_children()) #browse_recursive(root) server_namespace = "http://examples.freeopcua.github.io" idx = client.get_namespace_index(server_namespace) # Now getting a variable node using its browse path obj = root.get_child(["0:Objects", "2:" + Commons.MY_OBJECT_NAME]) print("ChargeController object is: ", obj) subscribed_variables_dict = dict() subscribed_variables = list() for var in VARS_NAMES: myvar = root.get_child( ["0:Objects", "2:" + Commons.MY_OBJECT_NAME, "2:" + str(var)]) subscribed_variables.append(myvar) subscribed_variables_dict[str(myvar)] = str( myvar.get_browse_name().to_string())