FreeOpcUa / opcua-asyncio

OPC UA library for python >= 3.7
GNU Lesser General Public License v3.0
1.04k stars 345 forks source link

Migration freeOpcua to opcua-asyncio subscribe_data_change SubHandler method not called after subscription #1618

Closed lkaupp closed 2 months ago

lkaupp commented 2 months ago

Discussed in https://github.com/FreeOpcUa/opcua-asyncio/discussions/1617

Originally posted by **lkaupp** April 16, 2024 Dear community, i migrate my old freeopcua code to opcua-asyncio. Under freeopcua the following worked well (pseudo code): ``` class SubHandler: """ Subscription Handler. To receive events from server for a subscription data_change and event methods are called directly from receiving thread. Do not do expensive, slow or network operation there. Create another thread if you need to do such a thing """ def datachange_notification(self, node: Node, val, data): print(data) Node.nodeid.to_string() -> save to text read txt -> nodeid= NodeId.from_string(nodeid) nodetosubscribe = Node(self._client.uaclient, nodeid) handler = SubHandler() self._opcua_subscription = self._client.create_subscription(100, handler) self._opcua_handle = self._opcua_subscription.subscribe_data_change(nodetosubscribe) ``` In opcua-asyncio the code seems to working similarly (nodes will be subscribed, [function call takes different time with different amount of nodes subscribe to, equal to the amount in the freeopcua implementation]), but the datachange_notification on the Subhandler is never called. With UAExpert the changes on the same node can be monitored as usual, freeopcua implementation works as usual, but only the opcua-asyncio implementation seems to be not working. Is there something different in reinitializing the nodes from plain txt, is there more to do? new code: ``` class SubHandler: """ Subscription Handler. To receive events from server for a subscription data_change and event methods are called directly from receiving thread. Do not do expensive, slow or network operation there. Create another thread if you need to do such a thing """ def datachange_notification(self, node: Node, val, data): print(data) Node.nodeid.to_string() -> save to text read txt -> nodeid= NodeId.from_string(nodeid) async with self._client: nodetosubscribe = Node(self._client.uaclient, nodeid) handler = SubHandler() self._opcua_subscription = await self._client.create_subscription(100, handler) self._opcua_handle = await self._opcua_subscription.subscribe_data_change(nodetosubscribe) ```
lkaupp commented 2 months ago

I also used the method self._client.get_node(), which is the same as my method implementation basically. However, still no events. What was altered during refactoring / rewriting the library to asyncio in the subscription process? Any ideas to debug the issue?

schroeder- commented 2 months ago

Whats the code after your posted code? If you leave the with Statement the client will disconnect.

lkaupp commented 2 months ago

Well here. Keep_running is true the whole time (same code as with freeopcua, which is working fine).:

            while self._keep_running:
                pass

            print("unsubscribe")
            await self._opcua_subscription.unsubscribe(self._opcua_handle)
            time.sleep(10)
            await self._opcua_subscription.delete()

Maybe next week I get the chance to capture a debug log. Which information do you need, and what commands should I use to get the necessary information?

schroeder- commented 2 months ago

I think you need to give the asyncio some timeslot, also you are wasting cpu cycles. Try this:

while self._keep_running:
        await asyncio.sleep(1.0)
lkaupp commented 2 months ago

Thanks for the tip. I will try your snippet as soon as I can enter the factory again.

lkaupp commented 2 months ago

await asyncio.sleep(1.0)

you are officially my hero, never expected that a while pass loop prevents messages from being delivered. Is this asyncio related - do I prevent any internal loops?

schroeder- commented 2 months ago

In asyncio you block the async loop until you call the next await. If you need to run code that doesn't call await for a while, there some solutions, like asyncio.to_thread for I/O bound or run_in_executor for cpu bound tasks.

lkaupp commented 2 months ago

Thanks for the explanation. I close my issue and reference your answer in the discussion!