Ejemplo n.º 1
1
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()
Ejemplo n.º 2
0
    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()
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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()
Ejemplo n.º 5
0
        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")
Ejemplo n.º 6
0
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)
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
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)
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
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()
Ejemplo n.º 12
0
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")
Ejemplo n.º 13
0
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()
Ejemplo n.º 14
0
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))
Ejemplo n.º 16
0
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
Ejemplo n.º 17
0
        # 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
Ejemplo n.º 18
0
        # 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)
Ejemplo n.º 19
0
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
Ejemplo n.º 20
0
    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())