Skip to content

Commit 2e9bad3

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 47c9050 commit 2e9bad3

File tree

1 file changed

+133
-0
lines changed

1 file changed

+133
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
From f9fc067ad51d3989a2045f19fc5641971ce1ee20 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 | 62 ++++++++++++++++++--
14+
1 file changed, 56 insertions(+), 6 deletions(-)
15+
16+
diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py
17+
index 369260787d..b3d0aa2d7f 100644
18+
--- a/src/controller/python/chip/ChipDeviceCtrl.py
19+
+++ b/src/controller/python/chip/ChipDeviceCtrl.py
20+
@@ -823,6 +823,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+
@@ -887,7 +937,7 @@ class ChipDeviceControllerBase():
78+
eventLoop = asyncio.get_running_loop()
79+
future = eventLoop.create_future()
80+
81+
- device = self.GetConnectedDeviceSync(nodeid, timeoutMs=interactionTimeoutMs)
82+
+ device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs)
83+
84+
ClusterCommand.TestOnlySendBatchCommands(
85+
future, eventLoop, device.deviceProxy, commands,
86+
@@ -908,7 +958,7 @@ class ChipDeviceControllerBase():
87+
eventLoop = asyncio.get_running_loop()
88+
future = eventLoop.create_future()
89+
90+
- device = self.GetConnectedDeviceSync(nodeid, timeoutMs=None)
91+
+ device = await self.GetConnectedDevice(nodeid, timeoutMs=None)
92+
ClusterCommand.TestOnlySendCommandTimedRequestFlagWithNoTimedInvoke(
93+
future, eventLoop, responseType, device.deviceProxy, ClusterCommand.CommandPath(
94+
EndpointId=endpoint,
95+
@@ -940,7 +990,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+
ClusterCommand.SendCommand(
102+
future, eventLoop, responseType, device.deviceProxy, ClusterCommand.CommandPath(
103+
EndpointId=endpoint,
104+
@@ -981,7 +1031,7 @@ class ChipDeviceControllerBase():
105+
eventLoop = asyncio.get_running_loop()
106+
future = eventLoop.create_future()
107+
108+
- device = self.GetConnectedDeviceSync(nodeid, timeoutMs=interactionTimeoutMs)
109+
+ device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs)
110+
111+
ClusterCommand.SendBatchCommands(
112+
future, eventLoop, device.deviceProxy, commands,
113+
@@ -1031,7 +1081,7 @@ class ChipDeviceControllerBase():
114+
eventLoop = asyncio.get_running_loop()
115+
future = eventLoop.create_future()
116+
117+
- device = self.GetConnectedDeviceSync(nodeid, timeoutMs=interactionTimeoutMs)
118+
+ device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs)
119+
120+
attrs = []
121+
for v in attributes:
122+
@@ -1259,7 +1309,7 @@ class ChipDeviceControllerBase():
123+
eventLoop = asyncio.get_running_loop()
124+
future = eventLoop.create_future()
125+
126+
- device = self.GetConnectedDeviceSync(nodeid)
127+
+ device = await self.GetConnectedDevice(nodeid)
128+
attributePaths = [self._parseAttributePathTuple(
129+
v) for v in attributes] if attributes else None
130+
clusterDataVersionFilters = [self._parseDataVersionFilterTuple(
131+
--
132+
2.44.0
133+

0 commit comments

Comments
 (0)