Skip to content

Commit c073d6d

Browse files
authored
Merge pull request #49 from home-assistant-libs/async-friendly-get-connected-device
Add patch which implements async GetConnectedDevice
2 parents a78b3db + c959864 commit c073d6d

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)