Skip to content

Commit c959864

Browse files
committed
Add patch which implements async GetConnectedDevice
The call GetConnectedDeviceSync may block for an extended period of time, especially when the CASE session needs to get reestablished. This adds an implementation of GetConnectedDeviceSync which uses asyncio to keep the asyncio event loop running.
1 parent a78b3db commit c959864

2 files changed

+115
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
From eeaecf615bda4192c31d3cb569f951bede052caa Mon Sep 17 00:00:00 2001
2+
From: Stefan Agner <stefan@agner.ch>
3+
Date: Wed, 27 Mar 2024 22:13:19 +0100
4+
Subject: [PATCH] [Python] Implement async friendly GetConnectedDevice
5+
6+
Currently GetConnectedDeviceSync() is blocking e.g. when a new session
7+
needs to be created. This is not asyncio friendly as it blocks the
8+
whole event loop.
9+
10+
Implement a asyncio friendly variant GetConnectedDevice() which is
11+
a co-routine function which can be awaited.
12+
---
13+
src/controller/python/chip/ChipDeviceCtrl.py | 58 ++++++++++++++++++--
14+
1 file changed, 54 insertions(+), 4 deletions(-)
15+
16+
diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py
17+
index 4a1b3af3e2..08dbdff224 100644
18+
--- a/src/controller/python/chip/ChipDeviceCtrl.py
19+
+++ b/src/controller/python/chip/ChipDeviceCtrl.py
20+
@@ -780,6 +780,56 @@ class ChipDeviceControllerBase():
21+
22+
return DeviceProxyWrapper(returnDevice, self._dmLib)
23+
24+
+ async def GetConnectedDevice(self, nodeid, allowPASE=True, timeoutMs: int = None):
25+
+ ''' Returns DeviceProxyWrapper upon success.'''
26+
+ self.CheckIsActive()
27+
+
28+
+ if allowPASE:
29+
+ returnDevice = c_void_p(None)
30+
+ res = self._ChipStack.Call(lambda: self._dmLib.pychip_GetDeviceBeingCommissioned(
31+
+ self.devCtrl, nodeid, byref(returnDevice)), timeoutMs)
32+
+ if res.is_success:
33+
+ logging.info('Using PASE connection')
34+
+ return DeviceProxyWrapper(returnDevice)
35+
+
36+
+ eventLoop = asyncio.get_running_loop()
37+
+ future = eventLoop.create_future()
38+
+
39+
+ class DeviceAvailableClosure():
40+
+ def __init__(self, loop, future: asyncio.Future):
41+
+ self._returnDevice = c_void_p(None)
42+
+ self._returnErr = None
43+
+ self._event_loop = loop
44+
+ self._future = future
45+
+
46+
+ def _deviceAvailable(self):
47+
+ if self._returnDevice.value is not None:
48+
+ self._future.set_result(self._returnDevice)
49+
+ else:
50+
+ self._future.set_exception(self._returnErr.to_exception())
51+
+
52+
+ def deviceAvailable(self, device, err):
53+
+ self._returnDevice = c_void_p(device)
54+
+ self._returnErr = err
55+
+ self._event_loop.call_soon_threadsafe(self._deviceAvailable)
56+
+ ctypes.pythonapi.Py_DecRef(ctypes.py_object(self))
57+
+
58+
+ closure = DeviceAvailableClosure(eventLoop, future)
59+
+ ctypes.pythonapi.Py_IncRef(ctypes.py_object(closure))
60+
+ self._ChipStack.Call(lambda: self._dmLib.pychip_GetConnectedDeviceByNodeId(
61+
+ self.devCtrl, nodeid, ctypes.py_object(closure), _DeviceAvailableCallback),
62+
+ timeoutMs).raise_on_error()
63+
+
64+
+ # The callback might have been received synchronously (during self._ChipStack.Call()).
65+
+ # In that case the Future has already been set it will return immediately
66+
+ if (timeoutMs):
67+
+ timeout = float(timeoutMs) / 1000
68+
+ await asyncio.wait_for(future, timeout=timeout)
69+
+ else:
70+
+ await future
71+
+
72+
+ return DeviceProxyWrapper(future.result(), self._dmLib)
73+
+
74+
def ComputeRoundTripTimeout(self, nodeid, upperLayerProcessingTimeoutMs: int = 0):
75+
''' Returns a computed timeout value based on the round-trip time it takes for the peer at the other end of the session to
76+
receive a message, process it and send it back. This is computed based on the session type, the type of transport,
77+
@@ -804,7 +854,7 @@ class ChipDeviceControllerBase():
78+
eventLoop = asyncio.get_running_loop()
79+
future = eventLoop.create_future()
80+
81+
- device = self.GetConnectedDeviceSync(nodeid, timeoutMs=None)
82+
+ device = await self.GetConnectedDevice(nodeid, timeoutMs=None)
83+
ClusterCommand.TestOnlySendCommandTimedRequestFlagWithNoTimedInvoke(
84+
future, eventLoop, responseType, device.deviceProxy, ClusterCommand.CommandPath(
85+
EndpointId=endpoint,
86+
@@ -831,7 +881,7 @@ class ChipDeviceControllerBase():
87+
eventLoop = asyncio.get_running_loop()
88+
future = eventLoop.create_future()
89+
90+
- device = self.GetConnectedDeviceSync(nodeid, timeoutMs=interactionTimeoutMs)
91+
+ device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs)
92+
ClusterCommand.SendCommand(
93+
future, eventLoop, responseType, device.deviceProxy, ClusterCommand.CommandPath(
94+
EndpointId=endpoint,
95+
@@ -876,7 +926,7 @@ class ChipDeviceControllerBase():
96+
eventLoop = asyncio.get_running_loop()
97+
future = eventLoop.create_future()
98+
99+
- device = self.GetConnectedDeviceSync(nodeid, timeoutMs=interactionTimeoutMs)
100+
+ device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs)
101+
102+
attrs = []
103+
for v in attributes:
104+
@@ -1097,7 +1147,7 @@ class ChipDeviceControllerBase():
105+
eventLoop = asyncio.get_running_loop()
106+
future = eventLoop.create_future()
107+
108+
- device = self.GetConnectedDeviceSync(nodeid)
109+
+ device = await self.GetConnectedDevice(nodeid)
110+
attributePaths = [self._parseAttributePathTuple(
111+
v) for v in attributes] if attributes else None
112+
clusterDataVersionFilters = [self._parseDataVersionFilterTuple(
113+
--
114+
2.44.0
115+

0 commit comments

Comments
 (0)