Skip to content

Commit 5bc80ec

Browse files
authored
Merge pull request #50 from home-assistant-libs/async-friendly-get-connected-device-for-main
Add patch which implements async GetConnectedDevice
2 parents 47c9050 + 2e9bad3 commit 5bc80ec

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)