opcua-asyncio icon indicating copy to clipboard operation
opcua-asyncio copied to clipboard

How to auto-reconnect from client, after restart of CNC machine

Open Nicrus86 opened this issue 3 years ago • 7 comments

====== Scenario ======


[ CNC ] <---> [ client_OPC ]

My acquisition script python use asyncua library and is very similar to the client-substription.py sample script.

The structure is below:

import sys
import os
import asyncio
import logging
import Configuration as C
from asyncua import Client, Node

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('asyncua')


class SubscriptionHandler:
    """
    The SubscriptionHandler is used to handle the data that is received for the subscription.
    """
    def datachange_notification(self, node: Node, val, data):
        """
        Callback for asyncua Subscription.
        This method will be called when the Client received a data change message from the Server.
        """
        logger.info('datachange_notification %r %s', node, val)


async def main():
    """
    Main task of this Client-Subscription example.
    """
    client_OPC = Client(url=C.url)
    async with client:
        handler = SubscriptionHandler()
        subscription = await client.create_subscription(C.TIME_UPDATE, handler)

        await subscription.subscribe_data_change(C.node_list)
        await asyncio.sleep(C.TIME_TO_CAPTURE_ACQUISITION)
        await asyncio.sleep(1)


if __name__ == "__main__":
    asyncio.run(main())

I would need automatic reconnection management if the CNC machine is turned off and on again. At the moment I am forced to restart the client-subscription script manually after the CNC is started.

Please do you know if there is a method to check when the server is down? Thanks you in advance.

Nicrus86 avatar Sep 09 '22 14:09 Nicrus86

We lately added the function check_connection which throws an exception if the connection is lost. In the near future i want to add a callback to the subscription handler if the connection is lost.

schroeder- avatar Sep 09 '22 14:09 schroeder-

@schroeder- great idea. I never thought at it

oroulet avatar Sep 09 '22 16:09 oroulet

We lately added the function check_connection which throws an exception if the connection is lost. In the near future i want to add a callback to the subscription handler if the connection is lost.

Wow! I hadn't seen this method for the CLIENT object.

But where can I put this check on the connection in my code structure (of the same type as client-substription.py)? I don't see a block of loops where to insert the check and the possible reconnection.

Nicrus86 avatar Sep 12 '22 10:09 Nicrus86

for the most simplistic usecase: https://github.com/AndreasHeine/opcua-sub-to-websocket/blob/b272dca8c8abf371e5e35f05ce93949bdb775723/client.py#L61

AndreasHeine avatar Sep 12 '22 10:09 AndreasHeine

Try something like this:

def run_client(client: Client):
     async with client:
        handler = SubscriptionHandler()
        subscription = await client.create_subscription(C.TIME_UPDATE, handler)

        await subscription.subscribe_data_change(C.node_list)
        
        while 1:
                await asyncio.sleep(1)
                client.check_connection()

async def main():
    """
    Main task of this Client-Subscription example.
    """
    client_OPC = Client(url=C.url)
    while 1:
        try: 
            run_client(client_opc)
            return
        catch Exception as e:
            pass

schroeder- avatar Sep 12 '22 11:09 schroeder-

Hello, I am trying to implement a class that keeps always same client and keeps client connected. But I always get CancelledError on second call. I am not very familiar with async programming but could you check my code?

this is simplified code of mine:

import asyncio
from asyncua import Client, Node, ua
import logging


logging.basicConfig(level=logging.INFO)
_logger = logging.getLogger('asyncua')
url = "opc.tcp://uademo.prosysopc.com:53530/OPCUA/SimulationServer"
ua_Client = Client(url)

class OpcuaMyClient:
    def __init__(self):
        self.client = ua_Client

    async def check_conn(self):
        try:
            conStatus = await self.client.check_connection()
            if not(conStatus):
                await asyncio.shield(self.client.connect())
        except Exception as e:
            _logger.info("OPCUA connection is lost, reconnecting")
            print(e)
            print("--------------------------")
            await self.client.connect()

    async def __aenter__(self):
        await self.check_conn()
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        pass

async def getServerStatus():
    async with OpcuaMyClient() as oclient:
        var = oclient.client.get_node('i=2256')
        value = await var.read_value()
    _logger.info(value.StartTime)
    return str(value.StartTime)

async def getServerStatus2():
    async with OpcuaMyClient() as oclient:
        var = oclient.client.get_node('i=2258')
        value = await var.read_value()
    _logger.info(value)
    return str(value)

async def wait():
    await asyncio.sleep(1)

if __name__ == "__main__":   
    asyncio.run(getServerStatus2())    
    asyncio.run(wait())
    asyncio.run(getServerStatus())

and this is what I get :

py .\opcua-myclass.py

INFO:asyncua.client.client:connect
INFO:asyncua.client.ua_client.UaClient:opening connection
WARNING:asyncua.uaprotocol:updating client limits to: TransportLimits(max_recv_buffer=8196, max_send_buffer=8196, max_chunk_count=0, max_message_size=4194240)
INFO:asyncua.client.ua_client.UASocketProtocol:open_secure_channel
INFO:asyncua.client.ua_client.UaClient:create_session
INFO:asyncua.client.client:find_endpoint ...... a lot of info here.....
INFO:asyncua.client.ua_client.UaClient:activate_session
INFO:asyncua:2022-10-13 20:35:05.846000   (user note)this is successful result from first call
Traceback (most recent call last):
  File "C:\Users\User1\AppData\Local\Programs\Python\Python310\lib\site-packages\asyncua\client\client.py", line 472, in _renew_channel_loop
    await asyncio.sleep(duration)
  File "C:\Users\User1\AppData\Local\Programs\Python\Python310\lib\asyncio\tasks.py", line 609, in sleep
    return await future
asyncio.exceptions.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\User1\Documents\GitHub\thu-wms-backend\flask\opcua-myclass.py", line 40, in getServerStatus
    async with OpcuaMyClient() as oclient:
  File "C:\Users\User1\Documents\GitHub\thu-wms-backend\flask\opcua-myclass.py", line 33, in __aenter__
    await self.check_conn()
  File "C:\Users\User1\Documents\GitHub\thu-wms-backend\flask\opcua-myclass.py", line 23, in check_conn
    conStatus = await self.client.check_connection()
  File "C:\Users\User1\AppData\Local\Programs\Python\Python310\lib\site-packages\asyncua\client\client.py", line 439, in check_connection
    await self._renew_channel_task
asyncio.exceptions.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\User1\Documents\GitHub\thu-wms-backend\flask\opcua-myclass.py", line 62, in <module>
    asyncio.run(getServerStatus())
  File "C:\Users\User1\AppData\Local\Programs\Python\Python310\lib\asyncio\runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "C:\Users\User1\AppData\Local\Programs\Python\Python310\lib\asyncio\base_events.py", line 641, in run_until_complete
    return future.result()
asyncio.exceptions.CancelledError
Exception ignored in: <function _ProactorBasePipeTransport.__del__ at 0x000001AD270CB760>
Traceback (most recent call last):
  File "C:\Users\User1\AppData\Local\Programs\Python\Python310\lib\asyncio\proactor_events.py", line 116, in __del__
  File "C:\Users\User1\AppData\Local\Programs\Python\Python310\lib\asyncio\proactor_events.py", line 108, in close
  File "C:\Users\User1\AppData\Local\Programs\Python\Python310\lib\asyncio\base_events.py", line 745, in call_soon
  File "C:\Users\User1\AppData\Local\Programs\Python\Python310\lib\asyncio\base_events.py", line 510, in _check_closed
RuntimeError: Event loop is closed

EDemir-on avatar Oct 13 '22 20:10 EDemir-on

client.check_connection doesn't return any value. It just throws a exception if there is a error. So conStatus is always None and you will reconnect which triggers an exception.

schroeder- avatar Oct 17 '22 06:10 schroeder-

See examples/client-reconnect.py

schroeder- avatar Mar 29 '23 07:03 schroeder-