diff --git a/0010-Python-Use-Python-logging-everywhere-32383.patch b/0011-Python-Use-Python-logging-everywhere-32383.patch similarity index 100% rename from 0010-Python-Use-Python-logging-everywhere-32383.patch rename to 0011-Python-Use-Python-logging-everywhere-32383.patch diff --git a/0012-Python-Implement-async-friendly-GetConnectedDevice.patch b/0012-Python-Implement-async-friendly-GetConnectedDevice.patch new file mode 100644 index 0000000..a6535de --- /dev/null +++ b/0012-Python-Implement-async-friendly-GetConnectedDevice.patch @@ -0,0 +1,115 @@ +From eeaecf615bda4192c31d3cb569f951bede052caa Mon Sep 17 00:00:00 2001 +From: Stefan Agner +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 +