Skip to content

Commit

Permalink
Merge pull request #49 from home-assistant-libs/async-friendly-get-co…
Browse files Browse the repository at this point in the history
…nnected-device

Add patch which implements async GetConnectedDevice
  • Loading branch information
agners committed Mar 28, 2024
2 parents a78b3db + c959864 commit c073d6d
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 0 deletions.
115 changes: 115 additions & 0 deletions 0012-Python-Implement-async-friendly-GetConnectedDevice.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
From eeaecf615bda4192c31d3cb569f951bede052caa Mon Sep 17 00:00:00 2001
From: Stefan Agner <stefan@agner.ch>
Date: Wed, 27 Mar 2024 22:13:19 +0100
Subject: [PATCH] [Python] Implement async friendly GetConnectedDevice

Currently GetConnectedDeviceSync() is blocking e.g. when a new session
needs to be created. This is not asyncio friendly as it blocks the
whole event loop.

Implement a asyncio friendly variant GetConnectedDevice() which is
a co-routine function which can be awaited.
---
src/controller/python/chip/ChipDeviceCtrl.py | 58 ++++++++++++++++++--
1 file changed, 54 insertions(+), 4 deletions(-)

diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py
index 4a1b3af3e2..08dbdff224 100644
--- a/src/controller/python/chip/ChipDeviceCtrl.py
+++ b/src/controller/python/chip/ChipDeviceCtrl.py
@@ -780,6 +780,56 @@ class ChipDeviceControllerBase():

return DeviceProxyWrapper(returnDevice, self._dmLib)

+ async def GetConnectedDevice(self, nodeid, allowPASE=True, timeoutMs: int = None):
+ ''' Returns DeviceProxyWrapper upon success.'''
+ self.CheckIsActive()
+
+ if allowPASE:
+ returnDevice = c_void_p(None)
+ res = self._ChipStack.Call(lambda: self._dmLib.pychip_GetDeviceBeingCommissioned(
+ self.devCtrl, nodeid, byref(returnDevice)), timeoutMs)
+ if res.is_success:
+ logging.info('Using PASE connection')
+ return DeviceProxyWrapper(returnDevice)
+
+ eventLoop = asyncio.get_running_loop()
+ future = eventLoop.create_future()
+
+ class DeviceAvailableClosure():
+ def __init__(self, loop, future: asyncio.Future):
+ self._returnDevice = c_void_p(None)
+ self._returnErr = None
+ self._event_loop = loop
+ self._future = future
+
+ def _deviceAvailable(self):
+ if self._returnDevice.value is not None:
+ self._future.set_result(self._returnDevice)
+ else:
+ self._future.set_exception(self._returnErr.to_exception())
+
+ def deviceAvailable(self, device, err):
+ self._returnDevice = c_void_p(device)
+ self._returnErr = err
+ self._event_loop.call_soon_threadsafe(self._deviceAvailable)
+ ctypes.pythonapi.Py_DecRef(ctypes.py_object(self))
+
+ closure = DeviceAvailableClosure(eventLoop, future)
+ ctypes.pythonapi.Py_IncRef(ctypes.py_object(closure))
+ self._ChipStack.Call(lambda: self._dmLib.pychip_GetConnectedDeviceByNodeId(
+ self.devCtrl, nodeid, ctypes.py_object(closure), _DeviceAvailableCallback),
+ timeoutMs).raise_on_error()
+
+ # The callback might have been received synchronously (during self._ChipStack.Call()).
+ # In that case the Future has already been set it will return immediately
+ if (timeoutMs):
+ timeout = float(timeoutMs) / 1000
+ await asyncio.wait_for(future, timeout=timeout)
+ else:
+ await future
+
+ return DeviceProxyWrapper(future.result(), self._dmLib)
+
def ComputeRoundTripTimeout(self, nodeid, upperLayerProcessingTimeoutMs: int = 0):
''' Returns a computed timeout value based on the round-trip time it takes for the peer at the other end of the session to
receive a message, process it and send it back. This is computed based on the session type, the type of transport,
@@ -804,7 +854,7 @@ class ChipDeviceControllerBase():
eventLoop = asyncio.get_running_loop()
future = eventLoop.create_future()

- device = self.GetConnectedDeviceSync(nodeid, timeoutMs=None)
+ device = await self.GetConnectedDevice(nodeid, timeoutMs=None)
ClusterCommand.TestOnlySendCommandTimedRequestFlagWithNoTimedInvoke(
future, eventLoop, responseType, device.deviceProxy, ClusterCommand.CommandPath(
EndpointId=endpoint,
@@ -831,7 +881,7 @@ class ChipDeviceControllerBase():
eventLoop = asyncio.get_running_loop()
future = eventLoop.create_future()

- device = self.GetConnectedDeviceSync(nodeid, timeoutMs=interactionTimeoutMs)
+ device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs)
ClusterCommand.SendCommand(
future, eventLoop, responseType, device.deviceProxy, ClusterCommand.CommandPath(
EndpointId=endpoint,
@@ -876,7 +926,7 @@ class ChipDeviceControllerBase():
eventLoop = asyncio.get_running_loop()
future = eventLoop.create_future()

- device = self.GetConnectedDeviceSync(nodeid, timeoutMs=interactionTimeoutMs)
+ device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs)

attrs = []
for v in attributes:
@@ -1097,7 +1147,7 @@ class ChipDeviceControllerBase():
eventLoop = asyncio.get_running_loop()
future = eventLoop.create_future()

- device = self.GetConnectedDeviceSync(nodeid)
+ device = await self.GetConnectedDevice(nodeid)
attributePaths = [self._parseAttributePathTuple(
v) for v in attributes] if attributes else None
clusterDataVersionFilters = [self._parseDataVersionFilterTuple(
--
2.44.0

0 comments on commit c073d6d

Please sign in to comment.