From a4a3231c43868fec8a847b1c27d0a2429717bc26 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Thu, 20 Jun 2024 18:14:02 +0200 Subject: [PATCH] Update Python controller bindings with latest Commissioning API updates This adds commissioning API updates from the master branch to our 1.3 based branch. This makes the commissioning API more Pythonic and allows to call them from the asyncio event loop directly. Specifically, this integrates changes from the following PRs - https://github.com/project-chip/connectedhomeip/pull/33954 - https://github.com/project-chip/connectedhomeip/pull/33905 - https://github.com/project-chip/connectedhomeip/pull/34001 - https://github.com/project-chip/connectedhomeip/pull/33989 --- ...ginal-PyChipError-in-ChipStackExcept.patch | 471 +++++ ...issioning-APIs-more-pythonic-and-con.patch | 580 ++++++ ...hip-error-in-test-commissioner-34001.patch | 57 + ...sync-API-functions-to-python-asyncio.patch | 1765 +++++++++++++++++ 4 files changed, 2873 insertions(+) create mode 100644 0027-Python-Store-original-PyChipError-in-ChipStackExcept.patch create mode 100644 0028-Python-Make-Commissioning-APIs-more-pythonic-and-con.patch create mode 100644 0029-Python-Reset-chip-error-in-test-commissioner-34001.patch create mode 100644 0030-Python-Convert-async-API-functions-to-python-asyncio.patch diff --git a/0027-Python-Store-original-PyChipError-in-ChipStackExcept.patch b/0027-Python-Store-original-PyChipError-in-ChipStackExcept.patch new file mode 100644 index 0000000..915b93f --- /dev/null +++ b/0027-Python-Store-original-PyChipError-in-ChipStackExcept.patch @@ -0,0 +1,471 @@ +From d9231b499271843b4501ea84bfa56a5b3ac9a1b5 Mon Sep 17 00:00:00 2001 +From: Stefan Agner +Date: Tue, 18 Jun 2024 14:45:11 +0200 +Subject: [PATCH] [Python] Store original PyChipError in ChipStackException + (#33954) + +* [Python] Drop unused ErrorToException function + +Also remove the now unused pychip_Stack_ErrorToString function. This +is handled in pychip_FormatError today. + +* [Python] Cleanup PyChipError return values + +Use PyChipError return value where PyChipErrors are actually returned. +This then also allows us to use the typical .raise_on_error() pattern. + +* [Python] Store original PyChipError in ChipStackException + +This change stores the original PyChipError in ChipStackException so +that details of the original error code can still be retrieved. This +is interesting to use the properties returning processed information +about the original error code. It also preserves the line and code +file which can be helpful. + +* [Python] Fix Command API argument type errors + +NativeLibraryHandleMethodArguments correctly setting the arguments +uncovered some incorrectly set arguments. + +* [Python] Use to_exception() to convert PyChipError to ChipStackError + +* [Python] Fix Cert API argument type errors + +NativeLibraryHandleMethodArguments correctly setting the argument +types causes argument type errors: +ctypes.ArgumentError: argument 1: TypeError: expected LP_c_ubyte instance instead of bytes + +We can safely cast bytes as the native side marks it const. +--- + .../ChipDeviceController-ScriptBinding.cpp | 11 ------ + src/controller/python/chip/ChipDeviceCtrl.py | 13 +++---- + src/controller/python/chip/ChipStack.py | 30 --------------- + .../python/chip/clusters/Attribute.py | 12 +++--- + .../python/chip/clusters/Command.py | 4 +- + .../python/chip/credentials/cert.py | 8 +++- + .../python/chip/discovery/__init__.py | 3 +- + .../python/chip/exceptions/__init__.py | 25 ++++++++++-- + .../python/chip/interaction_model/delegate.py | 8 ++-- + src/controller/python/chip/native/__init__.py | 4 +- + .../chip/setup_payload/setup_payload.py | 38 +++++++------------ + 11 files changed, 61 insertions(+), 95 deletions(-) + +diff --git a/src/controller/python/ChipDeviceController-ScriptBinding.cpp b/src/controller/python/ChipDeviceController-ScriptBinding.cpp +index 4a48434411..a2ab01734b 100644 +--- a/src/controller/python/ChipDeviceController-ScriptBinding.cpp ++++ b/src/controller/python/ChipDeviceController-ScriptBinding.cpp +@@ -198,7 +198,6 @@ pychip_ScriptDevicePairingDelegate_SetExpectingPairingComplete(chip::Controller: + // BLE + PyChipError pychip_DeviceCommissioner_CloseBleConnection(chip::Controller::DeviceCommissioner * devCtrl); + +-const char * pychip_Stack_ErrorToString(ChipError::StorageType err); + const char * pychip_Stack_StatusReportToString(uint32_t profileId, uint16_t statusCode); + + PyChipError pychip_GetConnectedDeviceByNodeId(chip::Controller::DeviceCommissioner * devCtrl, chip::NodeId nodeId, +@@ -336,11 +335,6 @@ PyChipError pychip_DeviceController_GetNodeId(chip::Controller::DeviceCommission + return ToPyChipError(CHIP_NO_ERROR); + } + +-const char * pychip_DeviceController_ErrorToString(ChipError::StorageType err) +-{ +- return chip::ErrorStr(CHIP_ERROR(err)); +-} +- + const char * pychip_DeviceController_StatusReportToString(uint32_t profileId, uint16_t statusCode) + { + // return chip::StatusReportStr(profileId, statusCode); +@@ -690,11 +684,6 @@ pychip_ScriptDevicePairingDelegate_SetExpectingPairingComplete(chip::Controller: + return ToPyChipError(CHIP_NO_ERROR); + } + +-const char * pychip_Stack_ErrorToString(ChipError::StorageType err) +-{ +- return chip::ErrorStr(CHIP_ERROR(err)); +-} +- + const char * pychip_Stack_StatusReportToString(uint32_t profileId, uint16_t statusCode) + { + // return chip::StatusReportStr(profileId, statusCode); +diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py +index 3cb7e67b7a..3af0facfa5 100644 +--- a/src/controller/python/chip/ChipDeviceCtrl.py ++++ b/src/controller/python/chip/ChipDeviceCtrl.py +@@ -882,12 +882,8 @@ class ChipDeviceControllerBase(): + sessionParametersStruct = SessionParametersStruct.parse(b'\x00' * SessionParametersStruct.sizeof()) + sessionParametersByteArray = SessionParametersStruct.build(sessionParametersStruct) + device = self.GetConnectedDeviceSync(nodeid) +- res = self._ChipStack.Call(lambda: self._dmLib.pychip_DeviceProxy_GetRemoteSessionParameters( +- device.deviceProxy, ctypes.c_char_p(sessionParametersByteArray))) +- +- # 0 is CHIP_NO_ERROR +- if res != 0: +- return None ++ self._ChipStack.Call(lambda: self._dmLib.pychip_DeviceProxy_GetRemoteSessionParameters( ++ device.deviceProxy, ctypes.c_char_p(sessionParametersByteArray))).raise_on_error() + + sessionParametersStruct = SessionParametersStruct.parse(sessionParametersByteArray) + return SessionParameters( +@@ -899,8 +895,6 @@ class ChipDeviceControllerBase(): + specficiationVersion=sessionParametersStruct.SpecificationVersion if sessionParametersStruct.SpecificationVersion != 0 else None, + maxPathsPerInvoke=sessionParametersStruct.MaxPathsPerInvoke) + +- return res +- + async def TestOnlySendBatchCommands(self, nodeid: int, commands: typing.List[ClusterCommand.InvokeRequestInfo], + timedRequestTimeoutMs: typing.Optional[int] = None, + interactionTimeoutMs: typing.Optional[int] = None, busyWaitMs: typing.Optional[int] = None, +@@ -1660,6 +1654,9 @@ class ChipDeviceControllerBase(): + self._dmLib.pychip_DeviceController_SetIpk.argtypes = [c_void_p, POINTER(c_char), c_size_t] + self._dmLib.pychip_DeviceController_SetIpk.restype = PyChipError + ++ self._dmLib.pychip_DeviceProxy_GetRemoteSessionParameters.restype = PyChipError ++ self._dmLib.pychip_DeviceProxy_GetRemoteSessionParameters.argtypes = [c_void_p, c_char_p] ++ + + class ChipDeviceController(ChipDeviceControllerBase): + ''' The ChipDeviceCommissioner binding, named as ChipDeviceController +diff --git a/src/controller/python/chip/ChipStack.py b/src/controller/python/chip/ChipStack.py +index 4f19776664..dc4efc223f 100644 +--- a/src/controller/python/chip/ChipStack.py ++++ b/src/controller/python/chip/ChipStack.py +@@ -35,7 +35,6 @@ from threading import Condition, Lock + import chip.native + from chip.native import PyChipError + +-from .ChipUtility import ChipUtility + from .clusters import Attribute as ClusterAttribute + from .clusters import Command as ClusterCommand + from .exceptions import ChipStackError, ChipStackException, DeviceError +@@ -247,33 +246,6 @@ class ChipStack(object): + raise res.to_exception() + return callObj + +- def ErrorToException(self, err, devStatusPtr=None): +- if err == 0x2C and devStatusPtr: +- devStatus = devStatusPtr.contents +- msg = ChipUtility.CStringToString( +- ( +- self._ChipStackLib.pychip_Stack_StatusReportToString( +- devStatus.ProfileId, devStatus.StatusCode +- ) +- ) +- ) +- sysErrorCode = ( +- devStatus.SysErrorCode if ( +- devStatus.SysErrorCode != 0) else None +- ) +- if sysErrorCode is not None: +- msg = msg + " (system err %d)" % (sysErrorCode) +- return DeviceError( +- devStatus.ProfileId, devStatus.StatusCode, sysErrorCode, msg +- ) +- else: +- return ChipStackError( +- err, +- ChipUtility.CStringToString( +- (self._ChipStackLib.pychip_Stack_ErrorToString(err)) +- ), +- ) +- + def LocateChipDLL(self): + self._loadLib() + return self._chipDLLPath +@@ -302,8 +274,6 @@ class ChipStack(object): + c_uint16, + ] + self._ChipStackLib.pychip_Stack_StatusReportToString.restype = c_char_p +- self._ChipStackLib.pychip_Stack_ErrorToString.argtypes = [c_uint32] +- self._ChipStackLib.pychip_Stack_ErrorToString.restype = c_char_p + + self._ChipStackLib.pychip_DeviceController_PostTaskOnChipThread.argtypes = [ + _ChipThreadTaskRunnerFunct, py_object] +diff --git a/src/controller/python/chip/clusters/Attribute.py b/src/controller/python/chip/clusters/Attribute.py +index 838936e83b..16db825c12 100644 +--- a/src/controller/python/chip/clusters/Attribute.py ++++ b/src/controller/python/chip/clusters/Attribute.py +@@ -674,7 +674,7 @@ class AsyncReadTransaction: + self._changedPathSet = set() + self._pReadClient = None + self._pReadCallback = None +- self._resultError = None ++ self._resultError: Optional[PyChipError] = None + + def SetClientObjPointers(self, pReadClient, pReadCallback): + self._pReadClient = pReadClient +@@ -741,7 +741,7 @@ class AsyncReadTransaction: + logging.exception(ex) + + def handleError(self, chipError: PyChipError): +- self._resultError = chipError.code ++ self._resultError = chipError + + def _handleSubscriptionEstablished(self, subscriptionId): + if not self._future.done(): +@@ -805,11 +805,11 @@ class AsyncReadTransaction: + # move on, possibly invalidating the provided _event_loop. + # + if not self._future.done(): +- if self._resultError: ++ if self._resultError is not None: + if self._subscription_handler: +- self._subscription_handler.OnErrorCb(self._resultError, self._subscription_handler) ++ self._subscription_handler.OnErrorCb(self._resultError.code, self._subscription_handler) + else: +- self._future.set_exception(chip.exceptions.ChipStackError(self._resultError)) ++ self._future.set_exception(self._resultError.to_exception()) + else: + self._future.set_result(AsyncReadTransaction.ReadResponse( + attributes=self._cache.attributeCache, events=self._events, tlvAttributes=self._cache.attributeTLVCache)) +@@ -837,7 +837,7 @@ class AsyncWriteTransaction: + self._event_loop = eventLoop + self._future = future + self._resultData = [] +- self._resultError = None ++ self._resultError: Optional[PyChipError] = None + + def handleResponse(self, path: AttributePath, status: int): + try: +diff --git a/src/controller/python/chip/clusters/Command.py b/src/controller/python/chip/clusters/Command.py +index 6ef25cb211..93951338f9 100644 +--- a/src/controller/python/chip/clusters/Command.py ++++ b/src/controller/python/chip/clusters/Command.py +@@ -467,13 +467,13 @@ def Init(): + setter = chip.native.NativeLibraryHandleMethodArguments(handle) + + setter.Set('pychip_CommandSender_SendCommand', +- PyChipError, [py_object, c_void_p, c_uint16, c_uint32, c_uint32, c_char_p, c_size_t, c_uint16, c_bool]) ++ PyChipError, [py_object, c_void_p, c_uint16, c_uint16, c_uint32, c_uint32, c_char_p, c_size_t, c_uint16, c_uint16, c_bool]) + setter.Set('pychip_CommandSender_SendBatchCommands', + PyChipError, [py_object, c_void_p, c_uint16, c_uint16, c_uint16, c_bool, POINTER(PyInvokeRequestData), c_size_t]) + setter.Set('pychip_CommandSender_TestOnlySendBatchCommands', + PyChipError, [py_object, c_void_p, c_uint16, c_uint16, c_uint16, c_bool, TestOnlyPyBatchCommandsOverrides, POINTER(PyInvokeRequestData), c_size_t]) + setter.Set('pychip_CommandSender_TestOnlySendCommandTimedRequestNoTimedInvoke', +- PyChipError, [py_object, c_void_p, c_uint32, c_uint32, c_char_p, c_size_t, c_uint16, c_bool]) ++ PyChipError, [py_object, c_void_p, c_uint16, c_uint32, c_uint32, c_char_p, c_size_t, c_uint16, c_uint16, c_bool]) + setter.Set('pychip_CommandSender_SendGroupCommand', + PyChipError, [c_uint16, c_void_p, c_uint32, c_uint32, c_char_p, c_size_t, c_uint16]) + setter.Set('pychip_CommandSender_InitCallbacks', None, [ +diff --git a/src/controller/python/chip/credentials/cert.py b/src/controller/python/chip/credentials/cert.py +index 786c1a4231..df0b28207d 100644 +--- a/src/controller/python/chip/credentials/cert.py ++++ b/src/controller/python/chip/credentials/cert.py +@@ -35,8 +35,10 @@ def convert_x509_cert_to_chip_cert(x509Cert: bytes) -> bytes: + """Converts a x509 certificate to CHIP Certificate.""" + output_buffer = (ctypes.c_uint8 * 1024)() + output_size = ctypes.c_size_t(1024) ++ ptr_type = ctypes.POINTER(ctypes.c_uint8) + +- _handle().pychip_ConvertX509CertToChipCert(x509Cert, len(x509Cert), output_buffer, ctypes.byref(output_size)).raise_on_error() ++ _handle().pychip_ConvertX509CertToChipCert(ctypes.cast(x509Cert, ptr_type), len(x509Cert), ++ ctypes.cast(output_buffer, ptr_type), ctypes.byref(output_size)).raise_on_error() + + return bytes(output_buffer)[:output_size.value] + +@@ -45,7 +47,9 @@ def convert_chip_cert_to_x509_cert(chipCert: bytes) -> bytes: + """Converts a x509 certificate to CHIP Certificate.""" + output_buffer = (ctypes.c_byte * 1024)() + output_size = ctypes.c_size_t(1024) ++ ptr_type = ctypes.POINTER(ctypes.c_uint8) + +- _handle().pychip_ConvertChipCertToX509Cert(chipCert, len(chipCert), output_buffer, ctypes.byref(output_size)).raise_on_error() ++ _handle().pychip_ConvertChipCertToX509Cert(ctypes.cast(chipCert, ptr_type), len(chipCert), ++ ctypes.cast(output_buffer, ptr_type), ctypes.byref(output_size)).raise_on_error() + + return bytes(output_buffer)[:output_size.value] +diff --git a/src/controller/python/chip/discovery/__init__.py b/src/controller/python/chip/discovery/__init__.py +index c400d97542..a25fb49000 100644 +--- a/src/controller/python/chip/discovery/__init__.py ++++ b/src/controller/python/chip/discovery/__init__.py +@@ -236,8 +236,7 @@ def FindAddressAsync(fabricid: int, nodeid: int, callback, timeout_ms=1000): + ) + + res = _GetDiscoveryLibraryHandle().pychip_discovery_resolve(fabricid, nodeid) +- if res != 0: +- raise Exception("Failed to start node resolution") ++ res.raise_on_error() + + + class _SyncAddressFinder: +diff --git a/src/controller/python/chip/exceptions/__init__.py b/src/controller/python/chip/exceptions/__init__.py +index 6b1969f1ef..c7f692e928 100644 +--- a/src/controller/python/chip/exceptions/__init__.py ++++ b/src/controller/python/chip/exceptions/__init__.py +@@ -15,6 +15,8 @@ + # limitations under the License. + # + ++from __future__ import annotations ++ + __all__ = [ + "ChipStackException", + "ChipStackError", +@@ -26,15 +28,32 @@ __all__ = [ + "UnknownCommand", + ] + ++from typing import TYPE_CHECKING ++ ++if TYPE_CHECKING: ++ from chip.native import PyChipError ++ + + class ChipStackException(Exception): + pass + + + class ChipStackError(ChipStackException): +- def __init__(self, err, msg=None): +- self.err = err +- self.msg = msg if msg else "Chip Stack Error %d" % err ++ def __init__(self, chip_error: PyChipError, msg=None): ++ self._chip_error = chip_error ++ self.msg = msg if msg else "Chip Stack Error %d" % chip_error.code ++ ++ @classmethod ++ def from_chip_error(cls, chip_error: PyChipError) -> ChipStackError: ++ return cls(chip_error, str(chip_error)) ++ ++ @property ++ def chip_error(self) -> PyChipError | None: ++ return self._chip_error ++ ++ @property ++ def err(self) -> int: ++ return self._chip_error.code + + def __str__(self): + return self.msg +diff --git a/src/controller/python/chip/interaction_model/delegate.py b/src/controller/python/chip/interaction_model/delegate.py +index 14512000a6..4741e5f2f7 100644 +--- a/src/controller/python/chip/interaction_model/delegate.py ++++ b/src/controller/python/chip/interaction_model/delegate.py +@@ -330,7 +330,7 @@ def InitIMDelegate(): + setter.Set("pychip_InteractionModelDelegate_SetCommandResponseErrorCallback", None, [ + _OnCommandResponseFunct]) + setter.Set("pychip_InteractionModel_GetCommandSenderHandle", +- c_uint32, [ctypes.POINTER(c_uint64)]) ++ chip.native.PyChipError, [ctypes.POINTER(c_uint64)]) + setter.Set("pychip_InteractionModelDelegate_SetOnWriteResponseStatusCallback", None, [ + _OnWriteResponseStatusFunct]) + +@@ -389,10 +389,8 @@ def WaitCommandIndexStatus(commandHandle: int, commandIndex: int): + def GetCommandSenderHandle() -> int: + handle = chip.native.GetLibraryHandle() + resPointer = c_uint64() +- res = handle.pychip_InteractionModel_GetCommandSenderHandle( +- ctypes.pointer(resPointer)) +- if res != 0: +- raise chip.exceptions.ChipStackError(res) ++ handle.pychip_InteractionModel_GetCommandSenderHandle( ++ ctypes.pointer(resPointer)).raise_on_error() + ClearCommandStatus(resPointer.value) + return resPointer.value + +diff --git a/src/controller/python/chip/native/__init__.py b/src/controller/python/chip/native/__init__.py +index ce8b7620f4..9ee94c61dd 100644 +--- a/src/controller/python/chip/native/__init__.py ++++ b/src/controller/python/chip/native/__init__.py +@@ -116,7 +116,7 @@ class PyChipError(ctypes.Structure): + + def to_exception(self) -> typing.Union[None, chip.exceptions.ChipStackError]: + if not self.is_success: +- return chip.exceptions.ChipStackError(self.code, str(self)) ++ return chip.exceptions.ChipStackError.from_chip_error(self) + + def __str__(self): + buf = ctypes.create_string_buffer(256) +@@ -199,7 +199,7 @@ class NativeLibraryHandleMethodArguments: + def Set(self, methodName: str, resultType, argumentTypes: list): + method = getattr(self.handle, methodName) + method.restype = resultType +- method.argtype = argumentTypes ++ method.argtypes = argumentTypes + + + @dataclass +diff --git a/src/controller/python/chip/setup_payload/setup_payload.py b/src/controller/python/chip/setup_payload/setup_payload.py +index 1f70983ad9..702fb319b4 100644 +--- a/src/controller/python/chip/setup_payload/setup_payload.py ++++ b/src/controller/python/chip/setup_payload/setup_payload.py +@@ -14,11 +14,10 @@ + # limitations under the License. + # + +-from ctypes import CFUNCTYPE, c_char_p, c_int32, c_uint8, c_uint16, c_uint32 ++from ctypes import CFUNCTYPE, c_char_p, c_uint8, c_uint16, c_uint32 + from typing import Optional + +-from chip.exceptions import ChipStackError +-from chip.native import GetLibraryHandle, NativeLibraryHandleMethodArguments ++from chip.native import GetLibraryHandle, NativeLibraryHandleMethodArguments, PyChipError + + + class SetupPayload: +@@ -46,34 +45,25 @@ class SetupPayload: + + def ParseQrCode(self, qrCode: str): + self.Clear() +- err = self.chipLib.pychip_SetupPayload_ParseQrCode(qrCode.upper().encode(), +- self.attribute_visitor, +- self.vendor_attribute_visitor) +- +- if err != 0: +- raise ChipStackError(err) ++ self.chipLib.pychip_SetupPayload_ParseQrCode(qrCode.upper().encode(), ++ self.attribute_visitor, ++ self.vendor_attribute_visitor).raise_on_error() + + return self + + def ParseManualPairingCode(self, manualPairingCode: str): + self.Clear() +- err = self.chipLib.pychip_SetupPayload_ParseManualPairingCode(manualPairingCode.encode(), +- self.attribute_visitor, +- self.vendor_attribute_visitor) +- +- if err != 0: +- raise ChipStackError(err) ++ self.chipLib.pychip_SetupPayload_ParseManualPairingCode(manualPairingCode.encode(), ++ self.attribute_visitor, ++ self.vendor_attribute_visitor).raise_on_error() + + return self + + # DEPRECATED + def PrintOnboardingCodes(self, passcode, vendorId, productId, discriminator, customFlow, capabilities, version): + self.Clear() +- err = self.chipLib.pychip_SetupPayload_PrintOnboardingCodes( +- passcode, vendorId, productId, discriminator, customFlow, capabilities, version) +- +- if err != 0: +- raise ChipStackError(err) ++ self.chipLib.pychip_SetupPayload_PrintOnboardingCodes( ++ passcode, vendorId, productId, discriminator, customFlow, capabilities, version).raise_on_error() + + # DEPRECATED + def Print(self): +@@ -106,17 +96,17 @@ class SetupPayload: + return None + + def __InitNativeFunctions(self, chipLib): +- if chipLib.pychip_SetupPayload_ParseQrCode is not None: ++ if chipLib.pychip_SetupPayload_ParseQrCode.argtypes is not None: + return + setter = NativeLibraryHandleMethodArguments(chipLib) + setter.Set("pychip_SetupPayload_ParseQrCode", +- c_int32, ++ PyChipError, + [c_char_p, SetupPayload.AttributeVisitor, SetupPayload.VendorAttributeVisitor]) + setter.Set("pychip_SetupPayload_ParseManualPairingCode", +- c_int32, ++ PyChipError, + [c_char_p, SetupPayload.AttributeVisitor, SetupPayload.VendorAttributeVisitor]) + setter.Set("pychip_SetupPayload_PrintOnboardingCodes", +- c_int32, ++ PyChipError, + [c_uint32, c_uint16, c_uint16, c_uint16, c_uint8, c_uint8, c_uint8]) + + # Getters from parsed contents. +-- +2.45.2 + diff --git a/0028-Python-Make-Commissioning-APIs-more-pythonic-and-con.patch b/0028-Python-Make-Commissioning-APIs-more-pythonic-and-con.patch new file mode 100644 index 0000000..a66d6b2 --- /dev/null +++ b/0028-Python-Make-Commissioning-APIs-more-pythonic-and-con.patch @@ -0,0 +1,580 @@ +From a5a507e055db590fe4965fbfa8b2a3b368368cd0 Mon Sep 17 00:00:00 2001 +From: Stefan Agner +Date: Tue, 18 Jun 2024 23:16:47 +0200 +Subject: [PATCH] [Python] Make Commissioning APIs more pythonic and consistent + (#33905) + +* [Python] Make Commissioning APIs more pythonic and consistent + +This commit makes the commissioning APIs more pythonic and consistent +by not returning PyChipError but simply raising ChipStackError +exceptions on errors instead. + +The return value instead returns the effectively assigned node ID +as defined by the NOC. If the SDK ends up generating that NOC, it +will use the thing passed to PairDevice, so those will match with +what is provided when calling the commissioning API. + +* [Python] Adjust tests to use new commissioning error handling +--- + src/controller/python/chip/ChipDeviceCtrl.py | 95 +++++++++++-------- + src/controller/python/chip/yaml/runner.py | 6 +- + .../python/test/test_scripts/base.py | 28 ++++-- + src/python_testing/TC_ACE_1_5.py | 4 +- + src/python_testing/TC_CGEN_2_4.py | 24 +++-- + src/python_testing/TC_DA_1_5.py | 3 +- + src/python_testing/TC_TIMESYNC_2_13.py | 3 +- + .../TestCommissioningTimeSync.py | 3 +- + src/python_testing/matter_testing_support.py | 72 +++++++++----- + .../integration-tests/common/utils.py | 5 +- + 10 files changed, 143 insertions(+), 100 deletions(-) + +diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py +index 3af0facfa5..5c1790c087 100644 +--- a/src/controller/python/chip/ChipDeviceCtrl.py ++++ b/src/controller/python/chip/ChipDeviceCtrl.py +@@ -143,11 +143,14 @@ class CommissionableNode(discovery.CommissionableNode): + def SetDeviceController(self, devCtrl: 'ChipDeviceController'): + self._devCtrl = devCtrl + +- def Commission(self, nodeId: int, setupPinCode: int) -> PyChipError: ++ def Commission(self, nodeId: int, setupPinCode: int) -> int: + ''' Commission the device using the device controller discovered this device. + + nodeId: The nodeId commissioned to the device + setupPinCode: The setup pin code of the device ++ ++ Returns: ++ - Effective Node ID of the device (as defined by the assigned NOC) + ''' + return self._devCtrl.CommissionOnNetwork( + nodeId, setupPinCode, filterType=discovery.FilterType.INSTANCE_NAME, filter=self.instanceName) +@@ -271,7 +274,10 @@ class ChipDeviceControllerBase(): + logging.exception("HandleCommissioningComplete called unexpectedly") + return + +- self._commissioning_complete_future.set_result(err) ++ if err.is_success: ++ self._commissioning_complete_future.set_result(nodeId) ++ else: ++ self._commissioning_complete_future.set_exception(err.to_exception()) + + def HandleFabricCheck(nodeId): + self.fabricCheckNodeId = nodeId +@@ -319,14 +325,17 @@ class ChipDeviceControllerBase(): + # During Commissioning, HandlePASEEstablishmentComplete will also be called. + # Only complete the future if PASE session establishment failed. + if not err.is_success: +- self._commissioning_complete_future.set_result(err) ++ self._commissioning_complete_future.set_exception(err.to_exception()) + return + + if self._pase_establishment_complete_future is None: + logging.exception("HandlePASEEstablishmentComplete called unexpectedly") + return + +- self._pase_establishment_complete_future.set_result(err) ++ if err.is_success: ++ self._pase_establishment_complete_future.set_result(None) ++ else: ++ self._pase_establishment_complete_future.set_exception(err.to_exception()) + + self.pairingDelegate = pairingDelegate + self.devCtrl = devCtrl +@@ -443,7 +452,12 @@ class ChipDeviceControllerBase(): + self.devCtrl) + ) + +- def ConnectBLE(self, discriminator: int, setupPinCode: int, nodeid: int, isShortDiscriminator: bool = False) -> PyChipError: ++ def ConnectBLE(self, discriminator: int, setupPinCode: int, nodeid: int, isShortDiscriminator: bool = False) -> int: ++ """Connect to a BLE device using the given discriminator and setup pin code. ++ ++ Returns: ++ - Effective Node ID of the device (as defined by the assigned NOC) ++ """ + self.CheckIsActive() + + self._commissioning_complete_future = concurrent.futures.Future() +@@ -455,11 +469,7 @@ class ChipDeviceControllerBase(): + self.devCtrl, discriminator, isShortDiscriminator, setupPinCode, nodeid) + ).raise_on_error() + +- # TODO: Change return None. Only returning on success is not useful. +- # but that is what the previous implementation did. +- res = self._commissioning_complete_future.result() +- res.raise_on_error() +- return res ++ return self._commissioning_complete_future.result() + finally: + self._commissioning_complete_future = None + +@@ -505,7 +515,7 @@ class ChipDeviceControllerBase(): + self.devCtrl, nodeid) + ).raise_on_error() + +- def EstablishPASESessionBLE(self, setupPinCode: int, discriminator: int, nodeid: int): ++ def EstablishPASESessionBLE(self, setupPinCode: int, discriminator: int, nodeid: int) -> None: + self.CheckIsActive() + + self._pase_establishment_complete_future = concurrent.futures.Future() +@@ -516,16 +526,11 @@ class ChipDeviceControllerBase(): + self.devCtrl, setupPinCode, discriminator, nodeid) + ).raise_on_error() + +- # TODO: This is a bit funky, but what the API returned with the previous +- # implementation. We should revisit this. +- err = self._pase_establishment_complete_future.result() +- if not err.is_success: +- return err.to_exception() +- return None ++ self._pase_establishment_complete_future.result() + finally: + self._pase_establishment_complete_future = None + +- def EstablishPASESessionIP(self, ipaddr: str, setupPinCode: int, nodeid: int, port: int = 0): ++ def EstablishPASESessionIP(self, ipaddr: str, setupPinCode: int, nodeid: int, port: int = 0) -> None: + self.CheckIsActive() + + self._pase_establishment_complete_future = concurrent.futures.Future() +@@ -536,16 +541,11 @@ class ChipDeviceControllerBase(): + self.devCtrl, ipaddr.encode("utf-8"), setupPinCode, nodeid, port) + ).raise_on_error() + +- # TODO: This is a bit funky, but what the API returned with the previous +- # implementation. We should revisit this. +- err = self._pase_establishment_complete_future.result() +- if not err.is_success: +- return err.to_exception() +- return None ++ self._pase_establishment_complete_future.result() + finally: + self._pase_establishment_complete_future = None + +- def EstablishPASESession(self, setUpCode: str, nodeid: int): ++ def EstablishPASESession(self, setUpCode: str, nodeid: int) -> None: + self.CheckIsActive() + + self._pase_establishment_complete_future = concurrent.futures.Future() +@@ -556,12 +556,7 @@ class ChipDeviceControllerBase(): + self.devCtrl, setUpCode.encode("utf-8"), nodeid) + ).raise_on_error() + +- # TODO: This is a bit funky, but what the API returned with the previous +- # implementation. We should revisit this. +- err = self._pase_establishment_complete_future.result() +- if not err.is_success: +- return err.to_exception() +- return None ++ self._pase_establishment_complete_future.result() + finally: + self._pase_establishment_complete_future = None + +@@ -1709,17 +1704,19 @@ class ChipDeviceController(ChipDeviceControllerBase): + def fabricAdmin(self) -> FabricAdmin: + return self._fabricAdmin + +- def Commission(self, nodeid) -> PyChipError: ++ def Commission(self, nodeid) -> int: + ''' + Start the auto-commissioning process on a node after establishing a PASE connection. + This function is intended to be used in conjunction with `EstablishPASESessionBLE` or + `EstablishPASESessionIP`. It can be called either before or after the DevicePairingDelegate + receives the OnPairingComplete call. Commissioners that want to perform simple +- auto-commissioning should use the supplied "PairDevice" functions above, which will ++ auto-commissioning should use the supplied "CommissionWithCode" function, which will + establish the PASE connection and commission automatically. + +- Return: +- bool: True if successful, False otherwise. ++ Raises a ChipStackError on failure. ++ ++ Returns: ++ - Effective Node ID of the device (as defined by the assigned NOC) + ''' + self.CheckIsActive() + +@@ -1735,13 +1732,13 @@ class ChipDeviceController(ChipDeviceControllerBase): + finally: + self._commissioning_complete_future = None + +- def CommissionThread(self, discriminator, setupPinCode, nodeId, threadOperationalDataset: bytes, isShortDiscriminator: bool = False) -> PyChipError: ++ def CommissionThread(self, discriminator, setupPinCode, nodeId, threadOperationalDataset: bytes, isShortDiscriminator: bool = False) -> int: + ''' Commissions a Thread device over BLE + ''' + self.SetThreadOperationalDataset(threadOperationalDataset) + return self.ConnectBLE(discriminator, setupPinCode, nodeId, isShortDiscriminator) + +- def CommissionWiFi(self, discriminator, setupPinCode, nodeId, ssid: str, credentials: str, isShortDiscriminator: bool = False) -> PyChipError: ++ def CommissionWiFi(self, discriminator, setupPinCode, nodeId, ssid: str, credentials: str, isShortDiscriminator: bool = False) -> int: + ''' Commissions a Wi-Fi device over BLE. + ''' + self.SetWiFiCredentials(ssid, credentials) +@@ -1812,7 +1809,7 @@ class ChipDeviceController(ChipDeviceControllerBase): + return self.fabricCheckNodeId + + def CommissionOnNetwork(self, nodeId: int, setupPinCode: int, +- filterType: DiscoveryFilterType = DiscoveryFilterType.NONE, filter: typing.Any = None, discoveryTimeoutMsec: int = 30000) -> PyChipError: ++ filterType: DiscoveryFilterType = DiscoveryFilterType.NONE, filter: typing.Any = None, discoveryTimeoutMsec: int = 30000) -> int: + ''' + Does the routine for OnNetworkCommissioning, with a filter for mDNS discovery. + Supported filters are: +@@ -1828,6 +1825,11 @@ class ChipDeviceController(ChipDeviceControllerBase): + DiscoveryFilterType.COMPRESSED_FABRIC_ID + + The filter can be an integer, a string or None depending on the actual type of selected filter. ++ ++ Raises a ChipStackError on failure. ++ ++ Returns: ++ - Effective Node ID of the device (as defined by the assigned NOC) + ''' + self.CheckIsActive() + +@@ -1847,9 +1849,14 @@ class ChipDeviceController(ChipDeviceControllerBase): + finally: + self._commissioning_complete_future = None + +- def CommissionWithCode(self, setupPayload: str, nodeid: int, discoveryType: DiscoveryType = DiscoveryType.DISCOVERY_ALL) -> PyChipError: ++ def CommissionWithCode(self, setupPayload: str, nodeid: int, discoveryType: DiscoveryType = DiscoveryType.DISCOVERY_ALL) -> int: + ''' Commission with the given nodeid from the setupPayload. + setupPayload may be a QR or manual code. ++ ++ Raises a ChipStackError on failure. ++ ++ Returns: ++ - Effective Node ID of the device (as defined by the assigned NOC) + ''' + self.CheckIsActive() + +@@ -1866,8 +1873,14 @@ class ChipDeviceController(ChipDeviceControllerBase): + finally: + self._commissioning_complete_future = None + +- def CommissionIP(self, ipaddr: str, setupPinCode: int, nodeid: int) -> PyChipError: +- """ DEPRECATED, DO NOT USE! Use `CommissionOnNetwork` or `CommissionWithCode` """ ++ def CommissionIP(self, ipaddr: str, setupPinCode: int, nodeid: int) -> int: ++ """ DEPRECATED, DO NOT USE! Use `CommissionOnNetwork` or `CommissionWithCode` ++ ++ Raises a ChipStackError on failure. ++ ++ Returns: ++ - Effective Node ID of the device (as defined by the assigned NOC) ++ """ + self.CheckIsActive() + + self._commissioning_complete_future = concurrent.futures.Future() +diff --git a/src/controller/python/chip/yaml/runner.py b/src/controller/python/chip/yaml/runner.py +index fb7a71bd45..312940f34b 100644 +--- a/src/controller/python/chip/yaml/runner.py ++++ b/src/controller/python/chip/yaml/runner.py +@@ -664,10 +664,10 @@ class CommissionerCommandAction(BaseAction): + if self._command == 'GetCommissionerNodeId': + return _ActionResult(status=_ActionStatus.SUCCESS, response=_GetCommissionerNodeIdResult(dev_ctrl.nodeId)) + +- resp = dev_ctrl.CommissionWithCode(self._setup_payload, self._node_id) +- if resp: ++ try: ++ dev_ctrl.CommissionWithCode(self._setup_payload, self._node_id) + return _ActionResult(status=_ActionStatus.SUCCESS, response=None) +- else: ++ except ChipStackError: + return _ActionResult(status=_ActionStatus.ERROR, response=None) + + +diff --git a/src/controller/python/test/test_scripts/base.py b/src/controller/python/test/test_scripts/base.py +index c9b1881dd7..83b1441ce1 100644 +--- a/src/controller/python/test/test_scripts/base.py ++++ b/src/controller/python/test/test_scripts/base.py +@@ -41,6 +41,7 @@ import chip.native + from chip import ChipDeviceCtrl + from chip.ChipStack import ChipStack + from chip.crypto import p256keypair ++from chip.exceptions import ChipStackException + from chip.utils import CommissioningBuildingBlocks + from cirque_restart_remote_device import restartRemoteDevice + from ecdsa import NIST256p +@@ -256,8 +257,9 @@ class BaseTestHelper: + devCtrl = self.devCtrl + self.logger.info( + "Attempting to establish PASE session with device id: {} addr: {}".format(str(nodeid), ip)) +- if devCtrl.EstablishPASESessionIP( +- ip, setuppin, nodeid) is not None: ++ try: ++ devCtrl.EstablishPASESessionIP(ip, setuppin, nodeid) ++ except ChipStackException: + self.logger.info( + "Failed to establish PASE session with device id: {} addr: {}".format(str(nodeid), ip)) + return False +@@ -268,7 +270,9 @@ class BaseTestHelper: + def TestCommissionOnly(self, nodeid: int): + self.logger.info( + "Commissioning device with id {}".format(nodeid)) +- if not self.devCtrl.Commission(nodeid): ++ try: ++ self.devCtrl.Commission(nodeid) ++ except ChipStackException: + self.logger.info( + "Failed to commission device with id {}".format(str(nodeid))) + return False +@@ -311,8 +315,10 @@ class BaseTestHelper: + + def TestCommissioning(self, ip: str, setuppin: int, nodeid: int): + self.logger.info("Commissioning device {}".format(ip)) +- if not self.devCtrl.CommissionIP(ip, setuppin, nodeid): +- self.logger.info( ++ try: ++ self.devCtrl.CommissionIP(ip, setuppin, nodeid) ++ except ChipStackException: ++ self.logger.exception( + "Failed to finish commissioning device {}".format(ip)) + return False + self.logger.info("Commissioning finished.") +@@ -320,8 +326,10 @@ class BaseTestHelper: + + def TestCommissioningWithSetupPayload(self, setupPayload: str, nodeid: int, discoveryType: int = 2): + self.logger.info("Commissioning device with setup payload {}".format(setupPayload)) +- if not self.devCtrl.CommissionWithCode(setupPayload, nodeid, chip.discovery.DiscoveryType(discoveryType)): +- self.logger.info( ++ try: ++ self.devCtrl.CommissionWithCode(setupPayload, nodeid, chip.discovery.DiscoveryType(discoveryType)) ++ except ChipStackException: ++ self.logger.exception( + "Failed to finish commissioning device {}".format(setupPayload)) + return False + self.logger.info("Commissioning finished.") +@@ -782,8 +790,10 @@ class BaseTestHelper: + self.devCtrl2 = self.fabricAdmin2.NewController( + self.controllerNodeId, self.paaTrustStorePath) + +- if not self.devCtrl2.CommissionIP(ip, setuppin, nodeid): +- self.logger.info( ++ try: ++ self.devCtrl2.CommissionIP(ip, setuppin, nodeid) ++ except ChipStackException: ++ self.logger.exception( + "Failed to finish key exchange with device {}".format(ip)) + return False + +diff --git a/src/python_testing/TC_ACE_1_5.py b/src/python_testing/TC_ACE_1_5.py +index 93c3fce5c5..bab70260fc 100644 +--- a/src/python_testing/TC_ACE_1_5.py ++++ b/src/python_testing/TC_ACE_1_5.py +@@ -54,10 +54,10 @@ class TC_ACE_1_5(MatterBaseTest): + params = self.openCommissioningWindow(self.th1, self.dut_node_id) + self.print_step(2, "TH1 opens the commissioning window on the DUT") + +- errcode = self.th2.CommissionOnNetwork( ++ self.th2.CommissionOnNetwork( + nodeId=self.dut_node_id, setupPinCode=params.commissioningParameters.setupPinCode, + filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=params.randomDiscriminator) +- logging.info('Commissioning complete done. Successful? {}, errorcode = {}'.format(errcode.is_success, errcode)) ++ logging.info('Commissioning complete done. Successful.') + self.print_step(3, "TH2 commissions DUT using admin node ID N2") + + self.print_step(4, "TH2 reads its fabric index from the Operational Credentials cluster CurrentFabricIndex attribute") +diff --git a/src/python_testing/TC_CGEN_2_4.py b/src/python_testing/TC_CGEN_2_4.py +index 23ab28ef09..a0333e8d9c 100644 +--- a/src/python_testing/TC_CGEN_2_4.py ++++ b/src/python_testing/TC_CGEN_2_4.py +@@ -25,6 +25,7 @@ import chip.clusters.enum + import chip.FabricAdmin + from chip import ChipDeviceCtrl + from chip.ChipDeviceCtrl import CommissioningParameters ++from chip.exceptions import ChipStackError + from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main + from mobly import asserts + +@@ -60,11 +61,12 @@ class TC_CGEN_2_4(MatterBaseTest): + # This will run the commissioning up to the point where stage x is run and the + # response is sent before the test commissioner simulates a failure + self.th2.SetTestCommissionerPrematureCompleteAfter(stage) +- errcode = self.th2.CommissionOnNetwork( +- nodeId=self.dut_node_id, setupPinCode=params.setupPinCode, +- filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=self.discriminator) +- logging.info('Commissioning complete done. Successful? {}, errorcode = {}'.format(errcode.is_success, errcode)) +- asserts.assert_false(errcode.is_success, 'Commissioning complete did not error as expected') ++ ctx = asserts.assert_raises(ChipStackError) ++ with ctx: ++ self.th2.CommissionOnNetwork( ++ nodeId=self.dut_node_id, setupPinCode=params.setupPinCode, ++ filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=self.discriminator) ++ errcode = ctx.exception.chip_error + asserts.assert_true(errcode.sdk_part == expectedErrorPart, 'Unexpected error type returned from CommissioningComplete') + asserts.assert_true(errcode.sdk_code == expectedErrCode, 'Unexpected error code returned from CommissioningComplete') + revokeCmd = Clusters.AdministratorCommissioning.Commands.RevokeCommissioning() +@@ -101,10 +103,14 @@ class TC_CGEN_2_4(MatterBaseTest): + + logging.info('Step 16 - TH2 fully commissions the DUT') + self.th2.ResetTestCommissioner() +- errcode = self.th2.CommissionOnNetwork( +- nodeId=self.dut_node_id, setupPinCode=params.setupPinCode, +- filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=self.discriminator) +- logging.info('Commissioning complete done. Successful? {}, errorcode = {}'.format(errcode.is_success, errcode)) ++ ++ ctx = asserts.assert_raises(ChipStackError) ++ with ctx: ++ self.th2.CommissionOnNetwork( ++ nodeId=self.dut_node_id, setupPinCode=params.setupPinCode, ++ filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=self.discriminator) ++ asserts.assert_true(ctx.exception.chip_error.sdk_code == 0x02, 'Unexpected error code returned from CommissioningComplete') ++ logging.info('Commissioning complete done.') + + logging.info('Step 17 - TH1 sends an arm failsafe') + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=900, breadcrumb=0) +diff --git a/src/python_testing/TC_DA_1_5.py b/src/python_testing/TC_DA_1_5.py +index 567d757760..17c6e3c16d 100644 +--- a/src/python_testing/TC_DA_1_5.py ++++ b/src/python_testing/TC_DA_1_5.py +@@ -170,10 +170,9 @@ class TC_DA_1_5(MatterBaseTest): + new_fabric_admin = new_certificate_authority.NewFabricAdmin(vendorId=0xFFF1, fabricId=2) + TH2 = new_fabric_admin.NewController(nodeId=112233) + +- errcode = TH2.CommissionOnNetwork( ++ TH2.CommissionOnNetwork( + nodeId=self.dut_node_id, setupPinCode=params.setupPinCode, + filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=1234) +- asserts.assert_true(errcode.is_success, 'Commissioning on TH2 did not complete successfully') + + self.print_step(15, "Read NOCs list for TH1") + temp = await self.read_single_attribute_check_success( +diff --git a/src/python_testing/TC_TIMESYNC_2_13.py b/src/python_testing/TC_TIMESYNC_2_13.py +index ceabb23e5d..fa43bbd00c 100644 +--- a/src/python_testing/TC_TIMESYNC_2_13.py ++++ b/src/python_testing/TC_TIMESYNC_2_13.py +@@ -53,10 +53,9 @@ class TC_TIMESYNC_2_13(MatterBaseTest): + new_fabric_admin = new_certificate_authority.NewFabricAdmin(vendorId=0xFFF1, fabricId=2) + TH2 = new_fabric_admin.NewController(nodeId=112233) + +- errcode = TH2.CommissionOnNetwork( ++ TH2.CommissionOnNetwork( + nodeId=self.dut_node_id, setupPinCode=params.setupPinCode, + filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=1234) +- asserts.assert_true(errcode.is_success, 'Commissioning on TH2 did not complete successfully') + + self.print_step(3, "TH2 reads the current fabric") + th2_fabric_idx = await self.read_single_attribute_check_success( +diff --git a/src/python_testing/TestCommissioningTimeSync.py b/src/python_testing/TestCommissioningTimeSync.py +index 509aabfc6a..0fca7063fc 100644 +--- a/src/python_testing/TestCommissioningTimeSync.py ++++ b/src/python_testing/TestCommissioningTimeSync.py +@@ -58,10 +58,9 @@ class TestCommissioningTimeSync(MatterBaseTest): + async def commission_and_base_checks(self): + params = self.default_controller.OpenCommissioningWindow( + nodeid=self.dut_node_id, timeout=600, iteration=10000, discriminator=1234, option=1) +- errcode = self.commissioner.CommissionOnNetwork( ++ self.commissioner.CommissionOnNetwork( + nodeId=self.dut_node_id, setupPinCode=params.setupPinCode, + filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=1234) +- asserts.assert_true(errcode.is_success, 'Commissioning did not complete successfully') + self.commissioned = True + + # Check the feature map - if we have a time cluster, we want UTC time to be set +diff --git a/src/python_testing/matter_testing_support.py b/src/python_testing/matter_testing_support.py +index f38c116424..cd7fce8f08 100644 +--- a/src/python_testing/matter_testing_support.py ++++ b/src/python_testing/matter_testing_support.py +@@ -1581,35 +1581,55 @@ class CommissionDeviceTest(MatterBaseTest): + info.filter_value = conf.discriminators[i] + + if conf.commissioning_method == "on-network": +- return dev_ctrl.CommissionOnNetwork( +- nodeId=conf.dut_node_ids[i], +- setupPinCode=info.passcode, +- filterType=info.filter_type, +- filter=info.filter_value +- ) ++ try: ++ dev_ctrl.CommissionOnNetwork( ++ nodeId=conf.dut_node_ids[i], ++ setupPinCode=info.passcode, ++ filterType=info.filter_type, ++ filter=info.filter_value ++ ) ++ return True ++ except ChipStackError as e: ++ logging.error("Commissioning failed: %s" % e) ++ return False + elif conf.commissioning_method == "ble-wifi": +- return dev_ctrl.CommissionWiFi( +- info.filter_value, +- info.passcode, +- conf.dut_node_ids[i], +- conf.wifi_ssid, +- conf.wifi_passphrase, +- isShortDiscriminator=(info.filter_type == DiscoveryFilterType.SHORT_DISCRIMINATOR) +- ) ++ try: ++ dev_ctrl.CommissionWiFi( ++ info.filter_value, ++ info.passcode, ++ conf.dut_node_ids[i], ++ conf.wifi_ssid, ++ conf.wifi_passphrase, ++ isShortDiscriminator=(info.filter_type == DiscoveryFilterType.SHORT_DISCRIMINATOR) ++ ) ++ return True ++ except ChipStackError as e: ++ logging.error("Commissioning failed: %s" % e) ++ return False + elif conf.commissioning_method == "ble-thread": +- return dev_ctrl.CommissionThread( +- info.filter_value, +- info.passcode, +- conf.dut_node_ids[i], +- conf.thread_operational_dataset, +- isShortDiscriminator=(info.filter_type == DiscoveryFilterType.SHORT_DISCRIMINATOR) +- ) ++ try: ++ dev_ctrl.CommissionThread( ++ info.filter_value, ++ info.passcode, ++ conf.dut_node_ids[i], ++ conf.thread_operational_dataset, ++ isShortDiscriminator=(info.filter_type == DiscoveryFilterType.SHORT_DISCRIMINATOR) ++ ) ++ return True ++ except ChipStackError as e: ++ logging.error("Commissioning failed: %s" % e) ++ return False + elif conf.commissioning_method == "on-network-ip": +- logging.warning("==== USING A DIRECT IP COMMISSIONING METHOD NOT SUPPORTED IN THE LONG TERM ====") +- return dev_ctrl.CommissionIP( +- ipaddr=conf.commissionee_ip_address_just_for_testing, +- setupPinCode=info.passcode, nodeid=conf.dut_node_ids[i] +- ) ++ try: ++ logging.warning("==== USING A DIRECT IP COMMISSIONING METHOD NOT SUPPORTED IN THE LONG TERM ====") ++ dev_ctrl.CommissionIP( ++ ipaddr=conf.commissionee_ip_address_just_for_testing, ++ setupPinCode=info.passcode, nodeid=conf.dut_node_ids[i] ++ ) ++ return True ++ except ChipStackError as e: ++ logging.error("Commissioning failed: %s" % e) ++ return False + else: + raise ValueError("Invalid commissioning method %s!" % conf.commissioning_method) + +diff --git a/src/test_driver/openiotsdk/integration-tests/common/utils.py b/src/test_driver/openiotsdk/integration-tests/common/utils.py +index 1865cf6274..da2dcff787 100644 +--- a/src/test_driver/openiotsdk/integration-tests/common/utils.py ++++ b/src/test_driver/openiotsdk/integration-tests/common/utils.py +@@ -92,14 +92,11 @@ def connect_device(devCtrl, setupPayload, commissionableDevice, nodeId=None): + + pincode = int(setupPayload.attributes['SetUpPINCode']) + try: +- res = devCtrl.CommissionOnNetwork( ++ devCtrl.CommissionOnNetwork( + nodeId, pincode, filterType=discovery.FilterType.INSTANCE_NAME, filter=commissionableDevice.instanceName) + except exceptions.ChipStackError as ex: + log.error("Commission discovered device failed {}".format(str(ex))) + return None +- if not res: +- log.info("Commission discovered device failed: %r" % res) +- return None + return nodeId + + +-- +2.45.2 + diff --git a/0029-Python-Reset-chip-error-in-test-commissioner-34001.patch b/0029-Python-Reset-chip-error-in-test-commissioner-34001.patch new file mode 100644 index 0000000..7557544 --- /dev/null +++ b/0029-Python-Reset-chip-error-in-test-commissioner-34001.patch @@ -0,0 +1,57 @@ +From 8cf4a7a4a642fc60bc5716860f63292b316335f1 Mon Sep 17 00:00:00 2001 +From: Stefan Agner +Date: Wed, 19 Jun 2024 16:26:32 +0200 +Subject: [PATCH] [Python] Reset chip error in test commissioner (#34001) + +* [Python] Reset chip error in test commissioner + +Make sure to reset the chip error in test commissioner on reset. This +avoid spurious errors. The Python side reads the error as soon as +mTestCommissionerUsed is set, which happens unconditionally. Hence +clearing the error is necessary. + +* [Python] Remove unexpected exception in TC_CGEN_2_4.py + +With the test commissioner properly resetting the error code the +spurious exception is no longer thrown. Remove the exception handling +from the test. +--- + src/controller/python/OpCredsBinding.cpp | 1 + + src/python_testing/TC_CGEN_2_4.py | 9 +++------ + 2 files changed, 4 insertions(+), 6 deletions(-) + +diff --git a/src/controller/python/OpCredsBinding.cpp b/src/controller/python/OpCredsBinding.cpp +index 66a6c84184..692e5e0ba3 100644 +--- a/src/controller/python/OpCredsBinding.cpp ++++ b/src/controller/python/OpCredsBinding.cpp +@@ -257,6 +257,7 @@ public: + mPrematureCompleteAfter = chip::Controller::CommissioningStage::kError; + mReadCommissioningInfo = chip::Controller::ReadCommissioningInfo(); + mNeedsDST = false; ++ mCompletionError = CHIP_NO_ERROR; + } + bool GetTestCommissionerUsed() { return mTestCommissionerUsed; } + void OnCommissioningSuccess(chip::PeerId peerId) { mReceivedCommissioningSuccess = true; } +diff --git a/src/python_testing/TC_CGEN_2_4.py b/src/python_testing/TC_CGEN_2_4.py +index a0333e8d9c..50bd9c005d 100644 +--- a/src/python_testing/TC_CGEN_2_4.py ++++ b/src/python_testing/TC_CGEN_2_4.py +@@ -104,12 +104,9 @@ class TC_CGEN_2_4(MatterBaseTest): + logging.info('Step 16 - TH2 fully commissions the DUT') + self.th2.ResetTestCommissioner() + +- ctx = asserts.assert_raises(ChipStackError) +- with ctx: +- self.th2.CommissionOnNetwork( +- nodeId=self.dut_node_id, setupPinCode=params.setupPinCode, +- filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=self.discriminator) +- asserts.assert_true(ctx.exception.chip_error.sdk_code == 0x02, 'Unexpected error code returned from CommissioningComplete') ++ self.th2.CommissionOnNetwork( ++ nodeId=self.dut_node_id, setupPinCode=params.setupPinCode, ++ filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=self.discriminator) + logging.info('Commissioning complete done.') + + logging.info('Step 17 - TH1 sends an arm failsafe') +-- +2.45.2 + diff --git a/0030-Python-Convert-async-API-functions-to-python-asyncio.patch b/0030-Python-Convert-async-API-functions-to-python-asyncio.patch new file mode 100644 index 0000000..72925da --- /dev/null +++ b/0030-Python-Convert-async-API-functions-to-python-asyncio.patch @@ -0,0 +1,1765 @@ +From b60b4c443d21fe3486e1c4c9c7ab92557fa7dda5 Mon Sep 17 00:00:00 2001 +From: Stefan Agner +Date: Thu, 20 Jun 2024 16:14:52 +0200 +Subject: [PATCH] [Python] Convert async API functions to python asyncio + (#33989) + +* [Python] Use context manager for Commissioning + +Use a context manager to handle the commissioning process in the +device controller. This will ensure that the commissioning resources +are properly cleaned up after completion and removes boiler plate +code. + +Also clear fabricCheckNodeId and mark it internal use by adding the +underline prefix. + +Also call pychip_ScriptDevicePairingDelegate_SetExpectingPairingComplete +directly on the Python Thread, as this is an atomic operation. This is +will also be more asyncio friendly as it is guaranteed to not block. + +* [Python] Use context manager for all callbacks + +Use context managers for all APIs which wait for callbacks. This +allows to cleanly wrap the future and add additional handling e.g. +locks for asyncio in the future. + +* [Python] Convert commissioning APIs to async functions + +Make all commissioning APIs async functions. This avoids the need +to use run_in_executor() to call them from asyncio code in a non- +blocking way. + +* [Python] Convert UnpairDevice/OpenCommissioningWindow to asyncio + +* [Python] Convert EstablishPASESession to asyncio + +* [Python] Convert IssueNOCChain to asyncio + +* [Python] Add locking to prevent concurrent access with asyncio + +Make sure that different asyncio tasks do not run the same function +concurrently. This is done by adding an asyncio lock to functions +which use callbacks. + +* [Python] Raise an exception if the future did not complete + +* [Python] Convert tests in src/controller/python/ to asyncio + +* [Python] Convert tests in src/python_testing/ to asyncio + +* Adjust yamltest_with_chip_repl_tester to use asyncio + +* [Python] Add documentation to the new context managers + +* [Python] Use asyncio.run() to run async tests +--- + .../yamltest_with_chip_repl_tester.py | 8 +- + src/controller/python/chip/ChipDeviceCtrl.py | 300 +++++++++--------- + .../python/chip/commissioning/pase.py | 6 +- + .../chip/utils/CommissioningBuildingBlocks.py | 4 +- + src/controller/python/chip/yaml/runner.py | 2 +- + .../matter_yamltest_repl_adapter/runner.py | 2 +- + .../python/test/test_scripts/base.py | 48 +-- + .../commissioning_failure_test.py | 32 +- + .../test/test_scripts/commissioning_test.py | 20 +- + .../test_scripts/commissioning_window_test.py | 6 +- + .../example_python_commissioning_flow.py | 4 +- + .../test/test_scripts/failsafe_tests.py | 12 +- + .../test/test_scripts/mobile-device-test.py | 26 +- + .../python_commissioning_flow_test.py | 2 +- + .../test_scripts/split_commissioning_test.py | 30 +- + ...cription_resumption_capacity_test_ctrl1.py | 12 +- + ...cription_resumption_capacity_test_ctrl2.py | 13 +- + .../subscription_resumption_test.py | 12 +- + .../subscription_resumption_timeout_test.py | 12 +- + src/python_testing/TC_ACE_1_5.py | 4 +- + src/python_testing/TC_CGEN_2_4.py | 12 +- + src/python_testing/TC_DA_1_5.py | 4 +- + src/python_testing/TC_IDM_1_2.py | 10 +- + src/python_testing/TC_OPCREDS_3_1.py | 32 +- + src/python_testing/TC_TIMESYNC_2_13.py | 4 +- + .../TestCommissioningTimeSync.py | 4 +- + .../basic_composition_support.py | 2 +- + src/python_testing/matter_testing_support.py | 18 +- + 28 files changed, 325 insertions(+), 316 deletions(-) + +diff --git a/scripts/tests/chiptest/yamltest_with_chip_repl_tester.py b/scripts/tests/chiptest/yamltest_with_chip_repl_tester.py +index 484e21370e..1b301c5728 100644 +--- a/scripts/tests/chiptest/yamltest_with_chip_repl_tester.py ++++ b/scripts/tests/chiptest/yamltest_with_chip_repl_tester.py +@@ -101,7 +101,7 @@ async def execute_test(yaml, runner): + '--pics-file', + default=None, + help='Optional PICS file') +-def main(setup_code, yaml_path, node_id, pics_file): ++async def main(setup_code, yaml_path, node_id, pics_file): + # Setting up python environment for running YAML CI tests using python parser. + with tempfile.NamedTemporaryFile() as chip_stack_storage: + chip.native.Init() +@@ -122,7 +122,7 @@ def main(setup_code, yaml_path, node_id, pics_file): + # Creating and commissioning to a single controller to match what is currently done when + # running. + dev_ctrl = ca_list[0].adminList[0].NewController() +- dev_ctrl.CommissionWithCode(setup_code, node_id) ++ await dev_ctrl.CommissionWithCode(setup_code, node_id) + + def _StackShutDown(): + # Tearing down chip stack. If not done in the correct order test will fail. +@@ -143,7 +143,7 @@ def main(setup_code, yaml_path, node_id, pics_file): + runner = ReplTestRunner( + clusters_definitions, certificate_authority_manager, dev_ctrl) + +- asyncio.run(execute_test(yaml, runner)) ++ await execute_test(yaml, runner) + + except Exception: + print(traceback.format_exc()) +@@ -153,4 +153,4 @@ def main(setup_code, yaml_path, node_id, pics_file): + + + if __name__ == '__main__': +- main() ++ asyncio.run(main()) +diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py +index 5c1790c087..1c156c22d4 100644 +--- a/src/controller/python/chip/ChipDeviceCtrl.py ++++ b/src/controller/python/chip/ChipDeviceCtrl.py +@@ -139,6 +139,52 @@ def _singleton(cls): + return wrapper + + ++class CallbackContext: ++ """A context manager for handling callbacks that are expected to be called exactly once. ++ ++ The context manager makes sure that no concurrent operations which use the same callback ++ handlers are executed. ++ """ ++ ++ def __init__(self, lock: asyncio.Lock) -> None: ++ self._lock = lock ++ self._future = None ++ ++ async def __aenter__(self): ++ await self._lock.acquire() ++ self._future = concurrent.futures.Future() ++ return self ++ ++ @property ++ def future(self) -> typing.Optional[concurrent.futures.Future]: ++ return self._future ++ ++ async def __aexit__(self, exc_type, exc_value, traceback): ++ if not self._future.done(): ++ raise RuntimeError("CallbackContext future not completed") ++ self._future = None ++ self._lock.release() ++ ++ ++class CommissioningContext(CallbackContext): ++ """A context manager for handling commissioning callbacks that are expected to be called exactly once. ++ ++ This context also resets commissioning related device controller state. ++ """ ++ ++ def __init__(self, devCtrl: ChipDeviceController, lock: asyncio.Lock) -> None: ++ super().__init__(lock) ++ self._devCtrl = devCtrl ++ ++ async def __aenter__(self): ++ await super().__aenter__() ++ self._devCtrl._fabricCheckNodeId = -1 ++ return self ++ ++ async def __aexit__(self, exc_type, exc_value, traceback): ++ await super().__aexit__(exc_type, exc_value, traceback) ++ ++ + class CommissionableNode(discovery.CommissionableNode): + def SetDeviceController(self, devCtrl: 'ChipDeviceController'): + self._devCtrl = devCtrl +@@ -250,15 +296,16 @@ class ChipDeviceControllerBase(): + self.pairingDelegate = pairingDelegate + self.devCtrl = devCtrl + self.name = name +- self.fabricCheckNodeId = -1 ++ self._fabricCheckNodeId = -1 + self._isActive = False + + self._Cluster = ChipClusters(builtins.chipStack) + self._Cluster.InitLib(self._dmLib) +- self._commissioning_complete_future: typing.Optional[concurrent.futures.Future] = None +- self._open_window_complete_future: typing.Optional[concurrent.futures.Future] = None +- self._unpair_device_complete_future: typing.Optional[concurrent.futures.Future] = None +- self._pase_establishment_complete_future: typing.Optional[concurrent.futures.Future] = None ++ self._commissioning_lock: asyncio.Lock = asyncio.Lock() ++ self._commissioning_context: CommissioningContext = CommissioningContext(self, self._commissioning_lock) ++ self._open_window_context: CallbackContext = CallbackContext(asyncio.Lock()) ++ self._unpair_device_context: CallbackContext = CallbackContext(asyncio.Lock()) ++ self._pase_establishment_context: CallbackContext = CallbackContext(self._commissioning_lock) + + def _set_dev_ctrl(self, devCtrl, pairingDelegate): + def HandleCommissioningComplete(nodeId: int, err: PyChipError): +@@ -270,17 +317,17 @@ class ChipDeviceControllerBase(): + if self._dmLib.pychip_TestCommissionerUsed(): + err = self._dmLib.pychip_GetCompletionError() + +- if self._commissioning_complete_future is None: ++ if self._commissioning_context.future is None: + logging.exception("HandleCommissioningComplete called unexpectedly") + return + + if err.is_success: +- self._commissioning_complete_future.set_result(nodeId) ++ self._commissioning_context.future.set_result(nodeId) + else: +- self._commissioning_complete_future.set_exception(err.to_exception()) ++ self._commissioning_context.future.set_exception(err.to_exception()) + + def HandleFabricCheck(nodeId): +- self.fabricCheckNodeId = nodeId ++ self._fabricCheckNodeId = nodeId + + def HandleOpenWindowComplete(nodeid: int, setupPinCode: int, setupManualCode: str, + setupQRCode: str, err: PyChipError) -> None: +@@ -291,14 +338,14 @@ class ChipDeviceControllerBase(): + else: + logging.warning("Failed to open commissioning window: {}".format(err)) + +- if self._open_window_complete_future is None: ++ if self._open_window_context.future is None: + logging.exception("HandleOpenWindowComplete called unexpectedly") + return + + if err.is_success: +- self._open_window_complete_future.set_result(commissioningParameters) ++ self._open_window_context.future.set_result(commissioningParameters) + else: +- self._open_window_complete_future.set_exception(err.to_exception()) ++ self._open_window_context.future.set_exception(err.to_exception()) + + def HandleUnpairDeviceComplete(nodeid: int, err: PyChipError): + if err.is_success: +@@ -306,14 +353,14 @@ class ChipDeviceControllerBase(): + else: + logging.warning("Failed to unpair device: {}".format(err)) + +- if self._unpair_device_complete_future is None: ++ if self._unpair_device_context.future is None: + logging.exception("HandleUnpairDeviceComplete called unexpectedly") + return + + if err.is_success: +- self._unpair_device_complete_future.set_result(None) ++ self._unpair_device_context.future.set_result(None) + else: +- self._unpair_device_complete_future.set_exception(err.to_exception()) ++ self._unpair_device_context.future.set_exception(err.to_exception()) + + def HandlePASEEstablishmentComplete(err: PyChipError): + if not err.is_success: +@@ -321,21 +368,21 @@ class ChipDeviceControllerBase(): + else: + logging.info("Established secure session with Device") + +- if self._commissioning_complete_future is not None: ++ if self._commissioning_context.future is not None: + # During Commissioning, HandlePASEEstablishmentComplete will also be called. + # Only complete the future if PASE session establishment failed. + if not err.is_success: +- self._commissioning_complete_future.set_exception(err.to_exception()) ++ self._commissioning_context.future.set_exception(err.to_exception()) + return + +- if self._pase_establishment_complete_future is None: ++ if self._pase_establishment_context.future is None: + logging.exception("HandlePASEEstablishmentComplete called unexpectedly") + return + + if err.is_success: +- self._pase_establishment_complete_future.set_result(None) ++ self._pase_establishment_context.future.set_result(None) + else: +- self._pase_establishment_complete_future.set_exception(err.to_exception()) ++ self._pase_establishment_context.future.set_exception(err.to_exception()) + + self.pairingDelegate = pairingDelegate + self.devCtrl = devCtrl +@@ -370,10 +417,8 @@ class ChipDeviceControllerBase(): + + ChipDeviceController.activeList.add(self) + +- def _enablePairingCompeleteCallback(self, value: bool): +- self._ChipStack.Call( +- lambda: self._dmLib.pychip_ScriptDevicePairingDelegate_SetExpectingPairingComplete(self.pairingDelegate, value) +- ).raise_on_error() ++ def _enablePairingCompleteCallback(self, value: bool): ++ self._dmLib.pychip_ScriptDevicePairingDelegate_SetExpectingPairingComplete(self.pairingDelegate, value) + + @property + def fabricAdmin(self) -> FabricAdmin.FabricAdmin: +@@ -452,7 +497,7 @@ class ChipDeviceControllerBase(): + self.devCtrl) + ) + +- def ConnectBLE(self, discriminator: int, setupPinCode: int, nodeid: int, isShortDiscriminator: bool = False) -> int: ++ async def ConnectBLE(self, discriminator: int, setupPinCode: int, nodeid: int, isShortDiscriminator: bool = False) -> int: + """Connect to a BLE device using the given discriminator and setup pin code. + + Returns: +@@ -460,31 +505,26 @@ class ChipDeviceControllerBase(): + """ + self.CheckIsActive() + +- self._commissioning_complete_future = concurrent.futures.Future() +- +- try: +- self._enablePairingCompeleteCallback(True) +- self._ChipStack.Call( ++ async with self._commissioning_context as ctx: ++ self._enablePairingCompleteCallback(True) ++ res = await self._ChipStack.CallAsync( + lambda: self._dmLib.pychip_DeviceController_ConnectBLE( + self.devCtrl, discriminator, isShortDiscriminator, setupPinCode, nodeid) +- ).raise_on_error() ++ ) ++ res.raise_on_error() + +- return self._commissioning_complete_future.result() +- finally: +- self._commissioning_complete_future = None ++ return await asyncio.futures.wrap_future(ctx.future) + +- def UnpairDevice(self, nodeid: int) -> None: ++ async def UnpairDevice(self, nodeid: int) -> None: + self.CheckIsActive() + +- self._unpair_device_complete_future = concurrent.futures.Future() +- try: +- self._ChipStack.Call( ++ async with self._unpair_device_context as ctx: ++ res = await self._ChipStack.CallAsync( + lambda: self._dmLib.pychip_DeviceController_UnpairDevice( + self.devCtrl, nodeid, self.cbHandleDeviceUnpairCompleteFunct) +- ).raise_on_error() +- self._unpair_device_complete_future.result() +- finally: +- self._unpair_device_complete_future = None ++ ) ++ res.raise_on_error() ++ return await asyncio.futures.wrap_future(ctx.future) + + def CloseBLEConnection(self): + self.CheckIsActive() +@@ -515,50 +555,32 @@ class ChipDeviceControllerBase(): + self.devCtrl, nodeid) + ).raise_on_error() + +- def EstablishPASESessionBLE(self, setupPinCode: int, discriminator: int, nodeid: int) -> None: ++ async def _establishPASESession(self, callFunct): + self.CheckIsActive() + +- self._pase_establishment_complete_future = concurrent.futures.Future() +- try: +- self._enablePairingCompeleteCallback(True) +- self._ChipStack.Call( +- lambda: self._dmLib.pychip_DeviceController_EstablishPASESessionBLE( +- self.devCtrl, setupPinCode, discriminator, nodeid) +- ).raise_on_error() +- +- self._pase_establishment_complete_future.result() +- finally: +- self._pase_establishment_complete_future = None +- +- def EstablishPASESessionIP(self, ipaddr: str, setupPinCode: int, nodeid: int, port: int = 0) -> None: +- self.CheckIsActive() ++ async with self._pase_establishment_context as ctx: ++ self._enablePairingCompleteCallback(True) ++ res = await self._ChipStack.CallAsync(callFunct) ++ res.raise_on_error() ++ await asyncio.futures.wrap_future(ctx.future) + +- self._pase_establishment_complete_future = concurrent.futures.Future() +- try: +- self._enablePairingCompeleteCallback(True) +- self._ChipStack.Call( +- lambda: self._dmLib.pychip_DeviceController_EstablishPASESessionIP( +- self.devCtrl, ipaddr.encode("utf-8"), setupPinCode, nodeid, port) +- ).raise_on_error() +- +- self._pase_establishment_complete_future.result() +- finally: +- self._pase_establishment_complete_future = None +- +- def EstablishPASESession(self, setUpCode: str, nodeid: int) -> None: +- self.CheckIsActive() ++ async def EstablishPASESessionBLE(self, setupPinCode: int, discriminator: int, nodeid: int) -> None: ++ await self._establishPASESession( ++ lambda: self._dmLib.pychip_DeviceController_EstablishPASESessionBLE( ++ self.devCtrl, setupPinCode, discriminator, nodeid) ++ ) + +- self._pase_establishment_complete_future = concurrent.futures.Future() +- try: +- self._enablePairingCompeleteCallback(True) +- self._ChipStack.Call( +- lambda: self._dmLib.pychip_DeviceController_EstablishPASESession( +- self.devCtrl, setUpCode.encode("utf-8"), nodeid) +- ).raise_on_error() ++ async def EstablishPASESessionIP(self, ipaddr: str, setupPinCode: int, nodeid: int, port: int = 0) -> None: ++ await self._establishPASESession( ++ lambda: self._dmLib.pychip_DeviceController_EstablishPASESessionIP( ++ self.devCtrl, ipaddr.encode("utf-8"), setupPinCode, nodeid, port) ++ ) + +- self._pase_establishment_complete_future.result() +- finally: +- self._pase_establishment_complete_future = None ++ async def EstablishPASESession(self, setUpCode: str, nodeid: int) -> None: ++ await self._establishPASESession( ++ lambda: self._dmLib.pychip_DeviceController_EstablishPASESession( ++ self.devCtrl, setUpCode.encode("utf-8"), nodeid) ++ ) + + def GetTestCommissionerUsed(self): + return self._ChipStack.Call( +@@ -683,8 +705,8 @@ class ChipDeviceControllerBase(): + kOriginalSetupCode = 0, + kTokenWithRandomPin = 1, + +- def OpenCommissioningWindow(self, nodeid: int, timeout: int, iteration: int, +- discriminator: int, option: CommissioningWindowPasscode) -> CommissioningParameters: ++ async def OpenCommissioningWindow(self, nodeid: int, timeout: int, iteration: int, ++ discriminator: int, option: CommissioningWindowPasscode) -> CommissioningParameters: + ''' Opens a commissioning window on the device with the given nodeid. + nodeid: Node id of the device + timeout: Command timeout +@@ -699,16 +721,15 @@ class ChipDeviceControllerBase(): + Returns CommissioningParameters + ''' + self.CheckIsActive() +- self._open_window_complete_future = concurrent.futures.Future() +- try: +- self._ChipStack.Call( ++ ++ async with self._open_window_context as ctx: ++ res = await self._ChipStack.CallAsync( + lambda: self._dmLib.pychip_DeviceController_OpenCommissioningWindow( + self.devCtrl, self.pairingDelegate, nodeid, timeout, iteration, discriminator, option) +- ).raise_on_error() ++ ) ++ res.raise_on_error() + +- return self._open_window_complete_future.result() +- finally: +- self._open_window_complete_future = None ++ return await asyncio.futures.wrap_future(ctx.future) + + def GetCompressedFabricId(self): + self.CheckIsActive() +@@ -1666,7 +1687,7 @@ class ChipDeviceController(ChipDeviceControllerBase): + f"caIndex({fabricAdmin.caIndex:x})/fabricId(0x{fabricId:016X})/nodeId(0x{nodeId:016X})" + ) + +- self._issue_node_chain_complete: typing.Optional[concurrent.futures.Future] = None ++ self._issue_node_chain_context: CallbackContext = CallbackContext(asyncio.Lock()) + self._dmLib.pychip_DeviceController_SetIssueNOCChainCallbackPythonCallback(_IssueNOCChainCallbackPythonCallback) + + pairingDelegate = c_void_p(None) +@@ -1704,7 +1725,7 @@ class ChipDeviceController(ChipDeviceControllerBase): + def fabricAdmin(self) -> FabricAdmin: + return self._fabricAdmin + +- def Commission(self, nodeid) -> int: ++ async def Commission(self, nodeid) -> int: + ''' + Start the auto-commissioning process on a node after establishing a PASE connection. + This function is intended to be used in conjunction with `EstablishPASESessionBLE` or +@@ -1720,29 +1741,27 @@ class ChipDeviceController(ChipDeviceControllerBase): + ''' + self.CheckIsActive() + +- self._commissioning_complete_future = concurrent.futures.Future() +- +- try: +- self._ChipStack.Call( ++ async with self._commissioning_context as ctx: ++ self._enablePairingCompleteCallback(False) ++ res = await self._ChipStack.CallAsync( + lambda: self._dmLib.pychip_DeviceController_Commission( + self.devCtrl, nodeid) +- ).raise_on_error() ++ ) ++ res.raise_on_error() + +- return self._commissioning_complete_future.result() +- finally: +- self._commissioning_complete_future = None ++ return await asyncio.futures.wrap_future(ctx.future) + +- def CommissionThread(self, discriminator, setupPinCode, nodeId, threadOperationalDataset: bytes, isShortDiscriminator: bool = False) -> int: ++ async def CommissionThread(self, discriminator, setupPinCode, nodeId, threadOperationalDataset: bytes, isShortDiscriminator: bool = False) -> int: + ''' Commissions a Thread device over BLE + ''' + self.SetThreadOperationalDataset(threadOperationalDataset) +- return self.ConnectBLE(discriminator, setupPinCode, nodeId, isShortDiscriminator) ++ return await self.ConnectBLE(discriminator, setupPinCode, nodeId, isShortDiscriminator) + +- def CommissionWiFi(self, discriminator, setupPinCode, nodeId, ssid: str, credentials: str, isShortDiscriminator: bool = False) -> int: ++ async def CommissionWiFi(self, discriminator, setupPinCode, nodeId, ssid: str, credentials: str, isShortDiscriminator: bool = False) -> int: + ''' Commissions a Wi-Fi device over BLE. + ''' + self.SetWiFiCredentials(ssid, credentials) +- return self.ConnectBLE(discriminator, setupPinCode, nodeId, isShortDiscriminator) ++ return await self.ConnectBLE(discriminator, setupPinCode, nodeId, isShortDiscriminator) + + def SetWiFiCredentials(self, ssid: str, credentials: str): + ''' Set the Wi-Fi credentials to set during commissioning.''' +@@ -1806,10 +1825,11 @@ class ChipDeviceController(ChipDeviceControllerBase): + + def GetFabricCheckResult(self) -> int: + ''' Returns the fabric check result if SetCheckMatchingFabric was used.''' +- return self.fabricCheckNodeId ++ return self._fabricCheckNodeId + +- def CommissionOnNetwork(self, nodeId: int, setupPinCode: int, +- filterType: DiscoveryFilterType = DiscoveryFilterType.NONE, filter: typing.Any = None, discoveryTimeoutMsec: int = 30000) -> int: ++ async def CommissionOnNetwork(self, nodeId: int, setupPinCode: int, ++ filterType: DiscoveryFilterType = DiscoveryFilterType.NONE, filter: typing.Any = None, ++ discoveryTimeoutMsec: int = 30000) -> int: + ''' + Does the routine for OnNetworkCommissioning, with a filter for mDNS discovery. + Supported filters are: +@@ -1837,19 +1857,17 @@ class ChipDeviceController(ChipDeviceControllerBase): + if isinstance(filter, int): + filter = str(filter) + +- self._commissioning_complete_future = concurrent.futures.Future() +- try: +- self._enablePairingCompeleteCallback(True) +- self._ChipStack.Call( ++ async with self._commissioning_context as ctx: ++ self._enablePairingCompleteCallback(True) ++ res = await self._ChipStack.CallAsync( + lambda: self._dmLib.pychip_DeviceController_OnNetworkCommission( + self.devCtrl, self.pairingDelegate, nodeId, setupPinCode, int(filterType), str(filter).encode("utf-8") if filter is not None else None, discoveryTimeoutMsec) +- ).raise_on_error() ++ ) ++ res.raise_on_error() + +- return self._commissioning_complete_future.result() +- finally: +- self._commissioning_complete_future = None ++ return await asyncio.futures.wrap_future(ctx.future) + +- def CommissionWithCode(self, setupPayload: str, nodeid: int, discoveryType: DiscoveryType = DiscoveryType.DISCOVERY_ALL) -> int: ++ async def CommissionWithCode(self, setupPayload: str, nodeid: int, discoveryType: DiscoveryType = DiscoveryType.DISCOVERY_ALL) -> int: + ''' Commission with the given nodeid from the setupPayload. + setupPayload may be a QR or manual code. + +@@ -1860,20 +1878,17 @@ class ChipDeviceController(ChipDeviceControllerBase): + ''' + self.CheckIsActive() + +- self._commissioning_complete_future = concurrent.futures.Future() +- +- try: +- self._enablePairingCompeleteCallback(True) +- self._ChipStack.Call( ++ async with self._commissioning_context as ctx: ++ self._enablePairingCompleteCallback(True) ++ res = await self._ChipStack.CallAsync( + lambda: self._dmLib.pychip_DeviceController_ConnectWithCode( + self.devCtrl, setupPayload.encode("utf-8"), nodeid, discoveryType.value) +- ).raise_on_error() ++ ) ++ res.raise_on_error() + +- return self._commissioning_complete_future.result() +- finally: +- self._commissioning_complete_future = None ++ return await asyncio.futures.wrap_future(ctx.future) + +- def CommissionIP(self, ipaddr: str, setupPinCode: int, nodeid: int) -> int: ++ async def CommissionIP(self, ipaddr: str, setupPinCode: int, nodeid: int) -> int: + """ DEPRECATED, DO NOT USE! Use `CommissionOnNetwork` or `CommissionWithCode` + + Raises a ChipStackError on failure. +@@ -1883,40 +1898,35 @@ class ChipDeviceController(ChipDeviceControllerBase): + """ + self.CheckIsActive() + +- self._commissioning_complete_future = concurrent.futures.Future() +- +- try: +- self._enablePairingCompeleteCallback(True) +- self._ChipStack.Call( ++ async with self._commissioning_context as ctx: ++ self._enablePairingCompleteCallback(True) ++ res = await self._ChipStack.CallAsync( + lambda: self._dmLib.pychip_DeviceController_ConnectIP( + self.devCtrl, ipaddr.encode("utf-8"), setupPinCode, nodeid) +- ).raise_on_error() ++ ) ++ res.raise_on_error() + +- return self._commissioning_complete_future.result() +- finally: +- self._commissioning_complete_future = None ++ return await asyncio.futures.wrap_future(ctx.future) + + def NOCChainCallback(self, nocChain): +- if self._issue_node_chain_complete is None: ++ if self._issue_node_chain_context.future is None: + logging.exception("NOCChainCallback while not expecting a callback") + return +- self._issue_node_chain_complete.set_result(nocChain) ++ self._issue_node_chain_context.future.set_result(nocChain) + return + +- def IssueNOCChain(self, csr: Clusters.OperationalCredentials.Commands.CSRResponse, nodeId: int): ++ async def IssueNOCChain(self, csr: Clusters.OperationalCredentials.Commands.CSRResponse, nodeId: int): + """Issue an NOC chain using the associated OperationalCredentialsDelegate. + The NOC chain will be provided in TLV cert format.""" + self.CheckIsActive() + +- self._issue_node_chain_complete = concurrent.futures.Future() +- try: +- self._ChipStack.Call( ++ async with self._issue_node_chain_context as ctx: ++ res = await self._ChipStack.CallAsync( + lambda: self._dmLib.pychip_DeviceController_IssueNOCChain( + self.devCtrl, py_object(self), csr.NOCSRElements, len(csr.NOCSRElements), nodeId) +- ).raise_on_error() +- return self._issue_node_chain_complete.result() +- finally: +- self._issue_node_chain_complete = None ++ ) ++ res.raise_on_error() ++ return await asyncio.futures.wrap_future(ctx.future) + + + class BareChipDeviceController(ChipDeviceControllerBase): +diff --git a/src/controller/python/chip/commissioning/pase.py b/src/controller/python/chip/commissioning/pase.py +index 9b7e8c5077..c0cfca5ee8 100644 +--- a/src/controller/python/chip/commissioning/pase.py ++++ b/src/controller/python/chip/commissioning/pase.py +@@ -44,9 +44,9 @@ class ContextManager: + self.devCtrl.CloseBLEConnection(self.is_ble) + + +-def establish_session(devCtrl: ChipDeviceCtrl.ChipDeviceControllerBase, parameter: commissioning.PaseParameters) -> ContextManager: ++async def establish_session(devCtrl: ChipDeviceCtrl.ChipDeviceControllerBase, parameter: commissioning.PaseParameters) -> ContextManager: + if isinstance(parameter, commissioning.PaseOverBLEParameters): +- devCtrl.EstablishPASESessionBLE(parameter.setup_pin, parameter.discriminator, parameter.temporary_nodeid) ++ await devCtrl.EstablishPASESessionBLE(parameter.setup_pin, parameter.discriminator, parameter.temporary_nodeid) + elif isinstance(parameter, commissioning.PaseOverIPParameters): + device = devCtrl.DiscoverCommissionableNodes(filterType=discovery.FilterType.LONG_DISCRIMINATOR, + filter=parameter.long_discriminator, stopOnFirst=True) +@@ -63,7 +63,7 @@ def establish_session(devCtrl: ChipDeviceCtrl.ChipDeviceControllerBase, paramete + break + if selected_address is None: + raise ValueError("The node for commissioning does not contains routable ip addresses information") +- devCtrl.EstablishPASESessionIP(selected_address, parameter.setup_pin, parameter.temporary_nodeid) ++ await devCtrl.EstablishPASESessionIP(selected_address, parameter.setup_pin, parameter.temporary_nodeid) + else: + raise TypeError("Expect PaseOverBLEParameters or PaseOverIPParameters for establishing PASE session") + return ContextManager( +diff --git a/src/controller/python/chip/utils/CommissioningBuildingBlocks.py b/src/controller/python/chip/utils/CommissioningBuildingBlocks.py +index 8fa80223b1..41d211b2d4 100644 +--- a/src/controller/python/chip/utils/CommissioningBuildingBlocks.py ++++ b/src/controller/python/chip/utils/CommissioningBuildingBlocks.py +@@ -165,7 +165,7 @@ async def AddNOCForNewFabricFromExisting(commissionerDevCtrl, newFabricDevCtrl, + + csrForAddNOC = await commissionerDevCtrl.SendCommand(existingNodeId, 0, opCreds.Commands.CSRRequest(CSRNonce=os.urandom(32))) + +- chainForAddNOC = newFabricDevCtrl.IssueNOCChain(csrForAddNOC, newNodeId) ++ chainForAddNOC = await newFabricDevCtrl.IssueNOCChain(csrForAddNOC, newNodeId) + if (chainForAddNOC.rcacBytes is None or + chainForAddNOC.icacBytes is None or + chainForAddNOC.nocBytes is None or chainForAddNOC.ipkBytes is None): +@@ -219,7 +219,7 @@ async def UpdateNOC(devCtrl, existingNodeId, newNodeId): + return False + csrForUpdateNOC = await devCtrl.SendCommand( + existingNodeId, 0, opCreds.Commands.CSRRequest(CSRNonce=os.urandom(32), isForUpdateNOC=True)) +- chainForUpdateNOC = devCtrl.IssueNOCChain(csrForUpdateNOC, newNodeId) ++ chainForUpdateNOC = await devCtrl.IssueNOCChain(csrForUpdateNOC, newNodeId) + if (chainForUpdateNOC.rcacBytes is None or + chainForUpdateNOC.icacBytes is None or + chainForUpdateNOC.nocBytes is None or chainForUpdateNOC.ipkBytes is None): +diff --git a/src/controller/python/chip/yaml/runner.py b/src/controller/python/chip/yaml/runner.py +index 312940f34b..f0b681fb2b 100644 +--- a/src/controller/python/chip/yaml/runner.py ++++ b/src/controller/python/chip/yaml/runner.py +@@ -665,7 +665,7 @@ class CommissionerCommandAction(BaseAction): + return _ActionResult(status=_ActionStatus.SUCCESS, response=_GetCommissionerNodeIdResult(dev_ctrl.nodeId)) + + try: +- dev_ctrl.CommissionWithCode(self._setup_payload, self._node_id) ++ await dev_ctrl.CommissionWithCode(self._setup_payload, self._node_id) + return _ActionResult(status=_ActionStatus.SUCCESS, response=None) + except ChipStackError: + return _ActionResult(status=_ActionStatus.ERROR, response=None) +diff --git a/src/controller/python/py_matter_yamltest_repl_adapter/matter_yamltest_repl_adapter/runner.py b/src/controller/python/py_matter_yamltest_repl_adapter/matter_yamltest_repl_adapter/runner.py +index aecba0b2c4..5da5035ae0 100644 +--- a/src/controller/python/py_matter_yamltest_repl_adapter/matter_yamltest_repl_adapter/runner.py ++++ b/src/controller/python/py_matter_yamltest_repl_adapter/matter_yamltest_repl_adapter/runner.py +@@ -59,7 +59,7 @@ class Runner(TestRunner): + # device with the provided node id. + if self._node_id_to_commission is not None: + # Magic value is the defaults expected for YAML tests. +- dev_ctrl.CommissionWithCode('MT:-24J0AFN00KA0648G00', self._node_id_to_commission) ++ await dev_ctrl.CommissionWithCode('MT:-24J0AFN00KA0648G00', self._node_id_to_commission) + + self._chip_stack = chip_stack + self._certificate_authority_manager = certificate_authority_manager +diff --git a/src/controller/python/test/test_scripts/base.py b/src/controller/python/test/test_scripts/base.py +index 83b1441ce1..84a403643d 100644 +--- a/src/controller/python/test/test_scripts/base.py ++++ b/src/controller/python/test/test_scripts/base.py +@@ -234,7 +234,7 @@ class BaseTestHelper: + async def TestRevokeCommissioningWindow(self, ip: str, setuppin: int, nodeid: int): + await self.devCtrl.SendCommand( + nodeid, 0, Clusters.AdministratorCommissioning.Commands.OpenBasicCommissioningWindow(180), timedRequestTimeoutMs=10000) +- if not self.TestPaseOnly(ip=ip, setuppin=setuppin, nodeid=nodeid, devCtrl=self.devCtrl2): ++ if not await self.TestPaseOnly(ip=ip, setuppin=setuppin, nodeid=nodeid, devCtrl=self.devCtrl2): + return False + + await self.devCtrl2.SendCommand( +@@ -248,17 +248,17 @@ class BaseTestHelper: + nodeid, 0, Clusters.AdministratorCommissioning.Commands.RevokeCommissioning(), timedRequestTimeoutMs=10000) + return True + +- def TestEnhancedCommissioningWindow(self, ip: str, nodeid: int): +- params = self.devCtrl.OpenCommissioningWindow(nodeid=nodeid, timeout=600, iteration=10000, discriminator=3840, option=1) +- return self.TestPaseOnly(ip=ip, nodeid=nodeid, setuppin=params.setupPinCode, devCtrl=self.devCtrl2) ++ async def TestEnhancedCommissioningWindow(self, ip: str, nodeid: int): ++ params = await self.devCtrl.OpenCommissioningWindow(nodeid=nodeid, timeout=600, iteration=10000, discriminator=3840, option=1) ++ return await self.TestPaseOnly(ip=ip, nodeid=nodeid, setuppin=params.setupPinCode, devCtrl=self.devCtrl2) + +- def TestPaseOnly(self, ip: str, setuppin: int, nodeid: int, devCtrl=None): ++ async def TestPaseOnly(self, ip: str, setuppin: int, nodeid: int, devCtrl=None): + if devCtrl is None: + devCtrl = self.devCtrl + self.logger.info( + "Attempting to establish PASE session with device id: {} addr: {}".format(str(nodeid), ip)) + try: +- devCtrl.EstablishPASESessionIP(ip, setuppin, nodeid) ++ await devCtrl.EstablishPASESessionIP(ip, setuppin, nodeid) + except ChipStackException: + self.logger.info( + "Failed to establish PASE session with device id: {} addr: {}".format(str(nodeid), ip)) +@@ -267,11 +267,11 @@ class BaseTestHelper: + "Successfully established PASE session with device id: {} addr: {}".format(str(nodeid), ip)) + return True + +- def TestCommissionOnly(self, nodeid: int): ++ async def TestCommissionOnly(self, nodeid: int): + self.logger.info( + "Commissioning device with id {}".format(nodeid)) + try: +- self.devCtrl.Commission(nodeid) ++ await self.devCtrl.Commission(nodeid) + except ChipStackException: + self.logger.info( + "Failed to commission device with id {}".format(str(nodeid))) +@@ -280,17 +280,17 @@ class BaseTestHelper: + "Successfully commissioned device with id {}".format(str(nodeid))) + return True + +- def TestKeyExchangeBLE(self, discriminator: int, setuppin: int, nodeid: int): ++ async def TestKeyExchangeBLE(self, discriminator: int, setuppin: int, nodeid: int): + self.logger.info( + "Conducting key exchange with device {}".format(discriminator)) +- if not self.devCtrl.ConnectBLE(discriminator, setuppin, nodeid): ++ if not await self.devCtrl.ConnectBLE(discriminator, setuppin, nodeid): + self.logger.info( + "Failed to finish key exchange with device {}".format(discriminator)) + return False + self.logger.info("Device finished key exchange.") + return True + +- def TestCommissionFailure(self, nodeid: int, failAfter: int): ++ async def TestCommissionFailure(self, nodeid: int, failAfter: int): + self.devCtrl.ResetTestCommissioner() + a = self.devCtrl.SetTestCommissionerSimulateFailureOnStage(failAfter) + if not a: +@@ -299,10 +299,10 @@ class BaseTestHelper: + + self.logger.info( + "Commissioning device, expecting failure after stage {}".format(failAfter)) +- self.devCtrl.Commission(nodeid) ++ await self.devCtrl.Commission(nodeid) + return self.devCtrl.CheckTestCommissionerCallbacks() and self.devCtrl.CheckTestCommissionerPaseConnection(nodeid) + +- def TestCommissionFailureOnReport(self, nodeid: int, failAfter: int): ++ async def TestCommissionFailureOnReport(self, nodeid: int, failAfter: int): + self.devCtrl.ResetTestCommissioner() + a = self.devCtrl.SetTestCommissionerSimulateFailureOnReport(failAfter) + if not a: +@@ -310,13 +310,13 @@ class BaseTestHelper: + return True + self.logger.info( + "Commissioning device, expecting failure on report for stage {}".format(failAfter)) +- self.devCtrl.Commission(nodeid) ++ await self.devCtrl.Commission(nodeid) + return self.devCtrl.CheckTestCommissionerCallbacks() and self.devCtrl.CheckTestCommissionerPaseConnection(nodeid) + +- def TestCommissioning(self, ip: str, setuppin: int, nodeid: int): ++ async def TestCommissioning(self, ip: str, setuppin: int, nodeid: int): + self.logger.info("Commissioning device {}".format(ip)) + try: +- self.devCtrl.CommissionIP(ip, setuppin, nodeid) ++ await self.devCtrl.CommissionIP(ip, setuppin, nodeid) + except ChipStackException: + self.logger.exception( + "Failed to finish commissioning device {}".format(ip)) +@@ -324,10 +324,10 @@ class BaseTestHelper: + self.logger.info("Commissioning finished.") + return True + +- def TestCommissioningWithSetupPayload(self, setupPayload: str, nodeid: int, discoveryType: int = 2): ++ async def TestCommissioningWithSetupPayload(self, setupPayload: str, nodeid: int, discoveryType: int = 2): + self.logger.info("Commissioning device with setup payload {}".format(setupPayload)) + try: +- self.devCtrl.CommissionWithCode(setupPayload, nodeid, chip.discovery.DiscoveryType(discoveryType)) ++ await self.devCtrl.CommissionWithCode(setupPayload, nodeid, chip.discovery.DiscoveryType(discoveryType)) + except ChipStackException: + self.logger.exception( + "Failed to finish commissioning device {}".format(setupPayload)) +@@ -335,7 +335,7 @@ class BaseTestHelper: + self.logger.info("Commissioning finished.") + return True + +- def TestOnNetworkCommissioning(self, discriminator: int, setuppin: int, nodeid: int, ip_override: str = None): ++ async def TestOnNetworkCommissioning(self, discriminator: int, setuppin: int, nodeid: int, ip_override: str = None): + self.logger.info("Testing discovery") + device = self.TestDiscovery(discriminator=discriminator) + if not device: +@@ -345,7 +345,7 @@ class BaseTestHelper: + if ip_override: + address = ip_override + self.logger.info("Testing commissioning") +- if not self.TestCommissioning(address, setuppin, nodeid): ++ if not await self.TestCommissioning(address, setuppin, nodeid): + self.logger.info("Failed to finish commissioning") + return False + return True +@@ -791,7 +791,7 @@ class BaseTestHelper: + self.controllerNodeId, self.paaTrustStorePath) + + try: +- self.devCtrl2.CommissionIP(ip, setuppin, nodeid) ++ await self.devCtrl2.CommissionIP(ip, setuppin, nodeid) + except ChipStackException: + self.logger.exception( + "Failed to finish key exchange with device {}".format(ip)) +@@ -1312,15 +1312,15 @@ class BaseTestHelper: + return False + return True + +- def TestFabricScopedCommandDuringPase(self, nodeid: int): ++ async def TestFabricScopedCommandDuringPase(self, nodeid: int): + '''Validates that fabric-scoped commands fail during PASE with UNSUPPORTED_ACCESS + + The nodeid is the PASE pseudo-node-ID used during PASE establishment + ''' + status = None + try: +- asyncio.run(self.devCtrl.SendCommand( +- nodeid, 0, Clusters.OperationalCredentials.Commands.UpdateFabricLabel("roboto"))) ++ await self.devCtrl.SendCommand( ++ nodeid, 0, Clusters.OperationalCredentials.Commands.UpdateFabricLabel("roboto")) + except IM.InteractionModelError as ex: + status = ex.status + +diff --git a/src/controller/python/test/test_scripts/commissioning_failure_test.py b/src/controller/python/test/test_scripts/commissioning_failure_test.py +index a535c8b184..13ee0674e5 100755 +--- a/src/controller/python/test/test_scripts/commissioning_failure_test.py ++++ b/src/controller/python/test/test_scripts/commissioning_failure_test.py +@@ -46,7 +46,7 @@ LIGHTING_ENDPOINT_ID = 1 + GROUP_ID = 0 + + +-def main(): ++async def main(): + optParser = OptionParser() + optParser.add_option( + "-t", +@@ -98,32 +98,32 @@ def main(): + # TODO: Start at stage 2 once handling for arming failsafe on pase is done. + if options.report: + for testFailureStage in range(3, 20): +- FailIfNot(test.TestPaseOnly(ip=options.deviceAddress1, +- setuppin=20202021, +- nodeid=1), ++ FailIfNot(await test.TestPaseOnly(ip=options.deviceAddress1, ++ setuppin=20202021, ++ nodeid=1), + "Failed to establish PASE connection with device") +- FailIfNot(test.TestCommissionFailureOnReport(1, testFailureStage), ++ FailIfNot(await test.TestCommissionFailureOnReport(1, testFailureStage), + "Commissioning failure tests failed for simulated report failure on stage {}".format(testFailureStage)) + + else: + for testFailureStage in range(3, 20): +- FailIfNot(test.TestPaseOnly(ip=options.deviceAddress1, +- setuppin=20202021, +- nodeid=1), ++ FailIfNot(await test.TestPaseOnly(ip=options.deviceAddress1, ++ setuppin=20202021, ++ nodeid=1), + "Failed to establish PASE connection with device") +- FailIfNot(test.TestCommissionFailure(1, testFailureStage), ++ FailIfNot(await test.TestCommissionFailure(1, testFailureStage), + "Commissioning failure tests failed for simulated stage failure on stage {}".format(testFailureStage)) + + # Ensure we can still commission for real +- FailIfNot(test.TestPaseOnly(ip=options.deviceAddress1, +- setuppin=20202021, +- nodeid=1), ++ FailIfNot(await test.TestPaseOnly(ip=options.deviceAddress1, ++ setuppin=20202021, ++ nodeid=1), + "Failed to establish PASE connection with device") +- FailIfNot(test.TestCommissionFailure(1, 0), "Failed to commission device") ++ FailIfNot(await test.TestCommissionFailure(1, 0), "Failed to commission device") + + logger.info("Testing on off cluster") +- FailIfNot(asyncio.run(test.TestOnOffCluster(nodeid=1, +- endpoint=LIGHTING_ENDPOINT_ID)), "Failed to test on off cluster") ++ FailIfNot(await test.TestOnOffCluster(nodeid=1, ++ endpoint=LIGHTING_ENDPOINT_ID), "Failed to test on off cluster") + + timeoutTicker.stop() + +@@ -136,7 +136,7 @@ def main(): + + if __name__ == "__main__": + try: +- main() ++ asyncio.run(main()) + except Exception as ex: + logger.exception(ex) + TestFail("Exception occurred when running tests.") +diff --git a/src/controller/python/test/test_scripts/commissioning_test.py b/src/controller/python/test/test_scripts/commissioning_test.py +index 4a7f15d6c3..c53ab00f33 100755 +--- a/src/controller/python/test/test_scripts/commissioning_test.py ++++ b/src/controller/python/test/test_scripts/commissioning_test.py +@@ -47,7 +47,7 @@ LIGHTING_ENDPOINT_ID = 1 + GROUP_ID = 0 + + +-def main(): ++async def main(): + optParser = OptionParser() + optParser.add_option( + "-t", +@@ -133,22 +133,22 @@ def main(): + + if options.deviceAddress: + logger.info("Testing commissioning (IP)") +- FailIfNot(test.TestCommissioning(ip=options.deviceAddress, +- setuppin=20202021, +- nodeid=options.nodeid), ++ FailIfNot(await test.TestCommissioning(ip=options.deviceAddress, ++ setuppin=20202021, ++ nodeid=options.nodeid), + "Failed to finish commissioning") + elif options.setupPayload: + logger.info("Testing commissioning (w/ Setup Payload)") +- FailIfNot(test.TestCommissioningWithSetupPayload(setupPayload=options.setupPayload, +- nodeid=options.nodeid, +- discoveryType=options.discoveryType), ++ FailIfNot(await test.TestCommissioningWithSetupPayload(setupPayload=options.setupPayload, ++ nodeid=options.nodeid, ++ discoveryType=options.discoveryType), + "Failed to finish commissioning") + else: + TestFail("Must provide device address or setup payload to commissioning the device") + + logger.info("Testing on off cluster") +- FailIfNot(asyncio.run(test.TestOnOffCluster(nodeid=options.nodeid, +- endpoint=LIGHTING_ENDPOINT_ID)), "Failed to test on off cluster") ++ FailIfNot(await test.TestOnOffCluster(nodeid=options.nodeid, ++ endpoint=LIGHTING_ENDPOINT_ID), "Failed to test on off cluster") + + FailIfNot(test.TestUsedTestCommissioner(), + "Test commissioner check failed") +@@ -164,7 +164,7 @@ def main(): + + if __name__ == "__main__": + try: +- main() ++ asyncio.run(main()) + except Exception as ex: + logger.exception(ex) + TestFail("Exception occurred when running tests.") +diff --git a/src/controller/python/test/test_scripts/commissioning_window_test.py b/src/controller/python/test/test_scripts/commissioning_window_test.py +index 6a113aede2..e146485aaa 100755 +--- a/src/controller/python/test/test_scripts/commissioning_window_test.py ++++ b/src/controller/python/test/test_scripts/commissioning_window_test.py +@@ -89,9 +89,9 @@ async def main(): + "Failed to finish network commissioning") + + logger.info("Commissioning DUT from first commissioner") +- FailIfNot(test.TestPaseOnly(ip=options.deviceAddress, setuppin=20202021, nodeid=1), ++ FailIfNot(await test.TestPaseOnly(ip=options.deviceAddress, setuppin=20202021, nodeid=1), + "Unable to establish PASE connection to device") +- FailIfNot(test.TestCommissionOnly(nodeid=1), "Unable to commission device") ++ FailIfNot(await test.TestCommissionOnly(nodeid=1), "Unable to commission device") + + logger.info("Creating controller on a new fabric") + FailIfNot(test.CreateNewFabricController(), "Unable to create new controller") +@@ -103,7 +103,7 @@ async def main(): + "RevokeCommissioning test failed") + + logger.info("Test Enhanced Commissioning Window") +- FailIfNot(test.TestEnhancedCommissioningWindow(ip=options.deviceAddress, nodeid=1), "EnhancedCommissioningWindow open failed") ++ FailIfNot(await test.TestEnhancedCommissioningWindow(ip=options.deviceAddress, nodeid=1), "EnhancedCommissioningWindow open failed") + + timeoutTicker.stop() + +diff --git a/src/controller/python/test/test_scripts/example_python_commissioning_flow.py b/src/controller/python/test/test_scripts/example_python_commissioning_flow.py +index b10269257f..016825a6f8 100644 +--- a/src/controller/python/test/test_scripts/example_python_commissioning_flow.py ++++ b/src/controller/python/test/test_scripts/example_python_commissioning_flow.py +@@ -32,7 +32,7 @@ class ExampleCustomMatterCommissioningFlow(commissioning_flow_blocks.Commissioni + + async def commission(self, parameter: commissioning.Parameters): + # The example uses PASE, however, the blocks uses a node_id, which supports both PASE and CASE. +- with pase.establish_session(devCtrl=self._devCtrl, parameter=parameter.pase_param) as device: ++ with await pase.establish_session(devCtrl=self._devCtrl, parameter=parameter.pase_param) as device: + node_id = device.node_id + + self._logger.info("Sending ArmFailSafe to device") +@@ -68,7 +68,7 @@ class ExampleCredentialProvider: + + async def get_commissionee_credentials(self, request: commissioning.GetCommissioneeCredentialsRequest) -> commissioning.GetCommissioneeCredentialsResponse: + node_id = random.randint(100000, 999999) +- nocChain = self._devCtrl.IssueNOCChain(Clusters.OperationalCredentials.Commands.CSRResponse( ++ nocChain = await self._devCtrl.IssueNOCChain(Clusters.OperationalCredentials.Commands.CSRResponse( + NOCSRElements=request.csr_elements, attestationSignature=request.attestation_signature), nodeId=node_id) + return commissioning.GetCommissioneeCredentialsResponse( + rcac=nocChain.rcacBytes, +diff --git a/src/controller/python/test/test_scripts/failsafe_tests.py b/src/controller/python/test/test_scripts/failsafe_tests.py +index d1a2034e73..d27111cbf7 100755 +--- a/src/controller/python/test/test_scripts/failsafe_tests.py ++++ b/src/controller/python/test/test_scripts/failsafe_tests.py +@@ -46,7 +46,7 @@ LIGHTING_ENDPOINT_ID = 1 + GROUP_ID = 0 + + +-def main(): ++async def main(): + optParser = OptionParser() + optParser.add_option( + "-t", +@@ -95,12 +95,12 @@ def main(): + "Failed to finish network commissioning") + + logger.info("Testing commissioning") +- FailIfNot(test.TestCommissioning(ip=options.deviceAddress, +- setuppin=20202021, +- nodeid=1), ++ FailIfNot(await test.TestCommissioning(ip=options.deviceAddress, ++ setuppin=20202021, ++ nodeid=1), + "Failed to finish key exchange") + +- FailIfNot(asyncio.run(test.TestFailsafe(nodeid=1)), "Failed failsafe test") ++ FailIfNot(await test.TestFailsafe(nodeid=1), "Failed failsafe test") + + timeoutTicker.stop() + +@@ -113,7 +113,7 @@ def main(): + + if __name__ == "__main__": + try: +- main() ++ asyncio.run(main()) + except Exception as ex: + logger.exception(ex) + TestFail("Exception occurred when running tests.") +diff --git a/src/controller/python/test/test_scripts/mobile-device-test.py b/src/controller/python/test/test_scripts/mobile-device-test.py +index 8f6f534dce..179dfa079a 100755 +--- a/src/controller/python/test/test_scripts/mobile-device-test.py ++++ b/src/controller/python/test/test_scripts/mobile-device-test.py +@@ -57,7 +57,7 @@ TEST_DEVICE_NODE_ID = 1 + ALL_TESTS = ['network_commissioning', 'datamodel'] + + +-def ethernet_commissioning(test: BaseTestHelper, discriminator: int, setup_pin: int, address_override: str, device_nodeid: int): ++async def ethernet_commissioning(test: BaseTestHelper, discriminator: int, setup_pin: int, address_override: str, device_nodeid: int): + logger.info("Testing discovery") + device = test.TestDiscovery(discriminator=discriminator) + FailIfNot(device, "Failed to discover any devices.") +@@ -71,27 +71,27 @@ def ethernet_commissioning(test: BaseTestHelper, discriminator: int, setup_pin: + address = address_override + + logger.info("Testing commissioning") +- FailIfNot(test.TestCommissioning(ip=address, +- setuppin=setup_pin, +- nodeid=device_nodeid), ++ FailIfNot(await test.TestCommissioning(ip=address, ++ setuppin=setup_pin, ++ nodeid=device_nodeid), + "Failed to finish key exchange") + + logger.info("Testing multi-controller setup on the same fabric") +- FailIfNot(asyncio.run(test.TestMultiControllerFabric(nodeid=device_nodeid)), "Failed the multi-controller test") ++ FailIfNot(await test.TestMultiControllerFabric(nodeid=device_nodeid), "Failed the multi-controller test") + + logger.info("Testing CATs used on controllers") +- FailIfNot(asyncio.run(test.TestControllerCATValues(nodeid=device_nodeid)), "Failed the controller CAT test") ++ FailIfNot(await test.TestControllerCATValues(nodeid=device_nodeid), "Failed the controller CAT test") + +- ok = asyncio.run(test.TestMultiFabric(ip=address, +- setuppin=20202021, +- nodeid=1)) ++ ok = await test.TestMultiFabric(ip=address, ++ setuppin=20202021, ++ nodeid=1) + FailIfNot(ok, "Failed to commission multi-fabric") + +- FailIfNot(asyncio.run(test.TestAddUpdateRemoveFabric(nodeid=device_nodeid)), ++ FailIfNot(await test.TestAddUpdateRemoveFabric(nodeid=device_nodeid), + "Failed AddUpdateRemoveFabric test") + + logger.info("Testing CASE Eviction") +- FailIfNot(asyncio.run(test.TestCaseEviction(device_nodeid)), "Failed TestCaseEviction") ++ FailIfNot(await test.TestCaseEviction(device_nodeid), "Failed TestCaseEviction") + + logger.info("Testing closing sessions") + FailIfNot(test.TestCloseSession(nodeid=device_nodeid), "Failed to close sessions") +@@ -163,8 +163,8 @@ def do_tests(controller_nodeid, device_nodeid, address, timeout, discriminator, + + chip.logging.RedirectToPythonLogging() + +- ethernet_commissioning(test, discriminator, setup_pin, address, +- device_nodeid) ++ asyncio.run(ethernet_commissioning(test, discriminator, setup_pin, address, ++ device_nodeid)) + + logger.info("Testing resolve") + FailIfNot(test.TestResolve(nodeid=device_nodeid), +diff --git a/src/controller/python/test/test_scripts/python_commissioning_flow_test.py b/src/controller/python/test/test_scripts/python_commissioning_flow_test.py +index 2317a5571e..0f76095a96 100755 +--- a/src/controller/python/test/test_scripts/python_commissioning_flow_test.py ++++ b/src/controller/python/test/test_scripts/python_commissioning_flow_test.py +@@ -126,7 +126,7 @@ def main(): + + async def get_commissionee_credentials(self, request: commissioning.GetCommissioneeCredentialsRequest) -> commissioning.GetCommissioneeCredentialsResponse: + node_id = random.randint(100000, 999999) +- nocChain = self._devCtrl.IssueNOCChain(Clusters.OperationalCredentials.Commands.CSRResponse( ++ nocChain = await self._devCtrl.IssueNOCChain(Clusters.OperationalCredentials.Commands.CSRResponse( + NOCSRElements=request.csr_elements, attestationSignature=request.attestation_signature), nodeId=node_id) + return commissioning.GetCommissioneeCredentialsResponse( + rcac=nocChain.rcacBytes[1:], +diff --git a/src/controller/python/test/test_scripts/split_commissioning_test.py b/src/controller/python/test/test_scripts/split_commissioning_test.py +index 9233d58b90..864a6f357d 100755 +--- a/src/controller/python/test/test_scripts/split_commissioning_test.py ++++ b/src/controller/python/test/test_scripts/split_commissioning_test.py +@@ -46,7 +46,7 @@ LIGHTING_ENDPOINT_ID = 1 + GROUP_ID = 0 + + +-def main(): ++async def main(): + optParser = OptionParser() + optParser.add_option( + "-t", +@@ -97,34 +97,34 @@ def main(): + "Failed to finish network commissioning") + + logger.info("Testing PASE connection to device 1") +- FailIfNot(test.TestPaseOnly(ip=options.deviceAddress1, +- setuppin=20202021, +- nodeid=1), ++ FailIfNot(await test.TestPaseOnly(ip=options.deviceAddress1, ++ setuppin=20202021, ++ nodeid=1), + "Failed to establish PASE connection with device 1") + + logger.info("Testing PASE connection to device 2") +- FailIfNot(test.TestPaseOnly(ip=options.deviceAddress2, +- setuppin=20202021, +- nodeid=2), ++ FailIfNot(await test.TestPaseOnly(ip=options.deviceAddress2, ++ setuppin=20202021, ++ nodeid=2), + "Failed to establish PASE connection with device 2") + + logger.info("Attempting to execute a fabric-scoped command during PASE before AddNOC") +- FailIfNot(test.TestFabricScopedCommandDuringPase(nodeid=1), ++ FailIfNot(await test.TestFabricScopedCommandDuringPase(nodeid=1), + "Did not get UNSUPPORTED_ACCESS for fabric-scoped command during PASE") + +- FailIfNot(test.TestCommissionOnly(nodeid=1), ++ FailIfNot(await test.TestCommissionOnly(nodeid=1), + "Failed to commission device 1") + +- FailIfNot(test.TestCommissionOnly(nodeid=2), ++ FailIfNot(await test.TestCommissionOnly(nodeid=2), + "Failed to commission device 2") + + logger.info("Testing on off cluster on device 1") +- FailIfNot(asyncio.run(test.TestOnOffCluster(nodeid=1, +- endpoint=LIGHTING_ENDPOINT_ID)), "Failed to test on off cluster on device 1") ++ FailIfNot(await test.TestOnOffCluster(nodeid=1, ++ endpoint=LIGHTING_ENDPOINT_ID), "Failed to test on off cluster on device 1") + + logger.info("Testing on off cluster on device 2") +- FailIfNot(asyncio.run(test.TestOnOffCluster(nodeid=2, +- endpoint=LIGHTING_ENDPOINT_ID)), "Failed to test on off cluster on device 2") ++ FailIfNot(await test.TestOnOffCluster(nodeid=2, ++ endpoint=LIGHTING_ENDPOINT_ID), "Failed to test on off cluster on device 2") + + timeoutTicker.stop() + +@@ -137,7 +137,7 @@ def main(): + + if __name__ == "__main__": + try: +- main() ++ asyncio.run(main()) + except Exception as ex: + logger.exception(ex) + TestFail("Exception occurred when running tests.") +diff --git a/src/controller/python/test/test_scripts/subscription_resumption_capacity_test_ctrl1.py b/src/controller/python/test/test_scripts/subscription_resumption_capacity_test_ctrl1.py +index e02564e293..dc126a1d04 100755 +--- a/src/controller/python/test/test_scripts/subscription_resumption_capacity_test_ctrl1.py ++++ b/src/controller/python/test/test_scripts/subscription_resumption_capacity_test_ctrl1.py +@@ -32,7 +32,7 @@ TEST_SETUPPIN = 20202021 + TEST_ENDPOINT_ID = 0 + + +-def main(): ++async def main(): + optParser = OptionParser() + optParser.add_option( + "-t", +@@ -110,12 +110,12 @@ def main(): + nodeid=112233, paaTrustStorePath=options.paaTrustStorePath, testCommissioner=True) + + FailIfNot( +- test.TestOnNetworkCommissioning(options.discriminator, options.setuppin, options.nodeid, options.deviceAddress), +- "Failed on on-network commissioing") ++ await test.TestOnNetworkCommissioning(options.discriminator, options.setuppin, options.nodeid, options.deviceAddress), ++ "Failed on on-network commissioning") + + FailIfNot( +- asyncio.run(test.TestSubscriptionResumptionCapacityStep1( +- options.nodeid, TEST_ENDPOINT_ID, options.setuppin, options.subscriptionCapacity)), ++ await test.TestSubscriptionResumptionCapacityStep1( ++ options.nodeid, TEST_ENDPOINT_ID, options.setuppin, options.subscriptionCapacity), + "Failed on step 1 of testing subscription resumption capacity") + + timeoutTicker.stop() +@@ -129,7 +129,7 @@ def main(): + + if __name__ == "__main__": + try: +- main() ++ asyncio.run(main()) + except Exception as ex: + logger.exception(ex) + TestFail("Exception occurred when running tests.") +diff --git a/src/controller/python/test/test_scripts/subscription_resumption_capacity_test_ctrl2.py b/src/controller/python/test/test_scripts/subscription_resumption_capacity_test_ctrl2.py +index ac449a9f54..2f50e80bf2 100755 +--- a/src/controller/python/test/test_scripts/subscription_resumption_capacity_test_ctrl2.py ++++ b/src/controller/python/test/test_scripts/subscription_resumption_capacity_test_ctrl2.py +@@ -34,7 +34,7 @@ TEST_ENDPOINT_ID = 0 + TEST_SSH_PORT = 2222 + + +-def main(): ++async def main(): + optParser = OptionParser() + optParser.add_option( + "-t", +@@ -122,13 +122,12 @@ def main(): + nodeid=112244, paaTrustStorePath=options.paaTrustStorePath, testCommissioner=True) + + FailIfNot( +- test.TestOnNetworkCommissioning(options.discriminator, options.setuppin, options.nodeid, options.deviceAddress), +- "Failed on on-network commissioing") ++ await test.TestOnNetworkCommissioning(options.discriminator, options.setuppin, options.nodeid, options.deviceAddress), ++ "Failed on on-network commissioning") + + FailIfNot( +- asyncio.run( +- test.TestSubscriptionResumptionCapacityStep2(options.nodeid, TEST_ENDPOINT_ID, options.deviceAddress, +- TEST_SSH_PORT, options.remoteServerApp, options.subscriptionCapacity)), ++ await test.TestSubscriptionResumptionCapacityStep2(options.nodeid, TEST_ENDPOINT_ID, options.deviceAddress, ++ TEST_SSH_PORT, options.remoteServerApp, options.subscriptionCapacity), + "Failed on testing subscription resumption capacity") + + timeoutTicker.stop() +@@ -142,7 +141,7 @@ def main(): + + if __name__ == "__main__": + try: +- main() ++ asyncio.run(main()) + except Exception as ex: + logger.exception(ex) + TestFail("Exception occurred when running tests.") +diff --git a/src/controller/python/test/test_scripts/subscription_resumption_test.py b/src/controller/python/test/test_scripts/subscription_resumption_test.py +index 79edf6a289..b7420c8c17 100755 +--- a/src/controller/python/test/test_scripts/subscription_resumption_test.py ++++ b/src/controller/python/test/test_scripts/subscription_resumption_test.py +@@ -34,7 +34,7 @@ TEST_ENDPOINT_ID = 0 + TEST_SSH_PORT = 2222 + + +-def main(): ++async def main(): + optParser = OptionParser() + optParser.add_option( + "-t", +@@ -112,12 +112,12 @@ def main(): + nodeid=112233, paaTrustStorePath=options.paaTrustStorePath, testCommissioner=True) + + FailIfNot( +- test.TestOnNetworkCommissioning(options.discriminator, options.setuppin, options.nodeid, options.deviceAddress), +- "Failed on on-network commissioing") ++ await test.TestOnNetworkCommissioning(options.discriminator, options.setuppin, options.nodeid, options.deviceAddress), ++ "Failed on on-network commissioning") + + FailIfNot( +- asyncio.run(test.TestSubscriptionResumption(options.nodeid, TEST_ENDPOINT_ID, options.deviceAddress, +- TEST_SSH_PORT, options.remoteServerApp)), "Failed to resume subscription") ++ await test.TestSubscriptionResumption(options.nodeid, TEST_ENDPOINT_ID, options.deviceAddress, ++ TEST_SSH_PORT, options.remoteServerApp), "Failed to resume subscription") + + timeoutTicker.stop() + +@@ -130,7 +130,7 @@ def main(): + + if __name__ == "__main__": + try: +- main() ++ asyncio.run(main()) + except Exception as ex: + logger.exception(ex) + TestFail("Exception occurred when running tests.") +diff --git a/src/controller/python/test/test_scripts/subscription_resumption_timeout_test.py b/src/controller/python/test/test_scripts/subscription_resumption_timeout_test.py +index 4932e5b4cc..f4809a28a8 100755 +--- a/src/controller/python/test/test_scripts/subscription_resumption_timeout_test.py ++++ b/src/controller/python/test/test_scripts/subscription_resumption_timeout_test.py +@@ -33,7 +33,7 @@ TEST_SETUPPIN = 20202021 + TEST_ENDPOINT_ID = 0 + + +-def main(): ++async def main(): + optParser = OptionParser() + optParser.add_option( + "-t", +@@ -102,13 +102,13 @@ def main(): + nodeid=112233, paaTrustStorePath=options.paaTrustStorePath, testCommissioner=True) + + FailIfNot( +- test.TestOnNetworkCommissioning(options.discriminator, options.setuppin, options.nodeid, options.deviceAddress), ++ await test.TestOnNetworkCommissioning(options.discriminator, options.setuppin, options.nodeid, options.deviceAddress), + "Failed on on-network commissioning") + + try: +- asyncio.run(test.devCtrl.ReadAttribute(options.nodeid, +- [(TEST_ENDPOINT_ID, Clusters.BasicInformation.Attributes.NodeLabel)], +- None, False, reportInterval=(1, 2), keepSubscriptions=True, autoResubscribe=False)) ++ await test.devCtrl.ReadAttribute(options.nodeid, ++ [(TEST_ENDPOINT_ID, Clusters.BasicInformation.Attributes.NodeLabel)], ++ None, False, reportInterval=(1, 2), keepSubscriptions=True, autoResubscribe=False) + except Exception as ex: + TestFail(f"Failed to subscribe attribute: {ex}") + +@@ -123,7 +123,7 @@ def main(): + + if __name__ == "__main__": + try: +- main() ++ asyncio.run(main()) + except Exception as ex: + logger.exception(ex) + TestFail("Exception occurred when running tests.") +diff --git a/src/python_testing/TC_ACE_1_5.py b/src/python_testing/TC_ACE_1_5.py +index bab70260fc..c8ef410954 100644 +--- a/src/python_testing/TC_ACE_1_5.py ++++ b/src/python_testing/TC_ACE_1_5.py +@@ -51,10 +51,10 @@ class TC_ACE_1_5(MatterBaseTest): + self.th2 = new_fabric_admin.NewController(nodeId=TH2_nodeid, + paaTrustStorePath=str(self.matter_test_config.paa_trust_store_path)) + +- params = self.openCommissioningWindow(self.th1, self.dut_node_id) ++ params = await self.openCommissioningWindow(self.th1, self.dut_node_id) + self.print_step(2, "TH1 opens the commissioning window on the DUT") + +- self.th2.CommissionOnNetwork( ++ await self.th2.CommissionOnNetwork( + nodeId=self.dut_node_id, setupPinCode=params.commissioningParameters.setupPinCode, + filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=params.randomDiscriminator) + logging.info('Commissioning complete done. Successful.') +diff --git a/src/python_testing/TC_CGEN_2_4.py b/src/python_testing/TC_CGEN_2_4.py +index 50bd9c005d..5544e26fda 100644 +--- a/src/python_testing/TC_CGEN_2_4.py ++++ b/src/python_testing/TC_CGEN_2_4.py +@@ -42,9 +42,9 @@ kSendNOC = 18 + + class TC_CGEN_2_4(MatterBaseTest): + +- def OpenCommissioningWindow(self) -> CommissioningParameters: ++ async def OpenCommissioningWindow(self) -> CommissioningParameters: + try: +- params = self.th1.OpenCommissioningWindow( ++ params = await self.th1.OpenCommissioningWindow( + nodeid=self.dut_node_id, timeout=600, iteration=10000, discriminator=self.discriminator, option=1) + return params + +@@ -56,14 +56,14 @@ class TC_CGEN_2_4(MatterBaseTest): + self, stage: int, expectedErrorPart: chip.native.ErrorSDKPart, expectedErrCode: int): + + logging.info("-----------------Fail on step {}-------------------------".format(stage)) +- params = self.OpenCommissioningWindow() ++ params = await self.OpenCommissioningWindow() + self.th2.ResetTestCommissioner() + # This will run the commissioning up to the point where stage x is run and the + # response is sent before the test commissioner simulates a failure + self.th2.SetTestCommissionerPrematureCompleteAfter(stage) + ctx = asserts.assert_raises(ChipStackError) + with ctx: +- self.th2.CommissionOnNetwork( ++ await self.th2.CommissionOnNetwork( + nodeId=self.dut_node_id, setupPinCode=params.setupPinCode, + filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=self.discriminator) + errcode = ctx.exception.chip_error +@@ -99,12 +99,12 @@ class TC_CGEN_2_4(MatterBaseTest): + await self.CommissionToStageSendCompleteAndCleanup(kSendNOC, chip.native.ErrorSDKPart.IM_CLUSTER_STATUS, 0x02) + + logging.info('Step 15 - TH1 opens a commissioning window') +- params = self.OpenCommissioningWindow() ++ params = await self.OpenCommissioningWindow() + + logging.info('Step 16 - TH2 fully commissions the DUT') + self.th2.ResetTestCommissioner() + +- self.th2.CommissionOnNetwork( ++ await self.th2.CommissionOnNetwork( + nodeId=self.dut_node_id, setupPinCode=params.setupPinCode, + filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=self.discriminator) + logging.info('Commissioning complete done.') +diff --git a/src/python_testing/TC_DA_1_5.py b/src/python_testing/TC_DA_1_5.py +index 17c6e3c16d..e42b530057 100644 +--- a/src/python_testing/TC_DA_1_5.py ++++ b/src/python_testing/TC_DA_1_5.py +@@ -162,7 +162,7 @@ class TC_DA_1_5(MatterBaseTest): + await self.send_single_cmd(cmd=gcomm.Commands.ArmFailSafe(expiryLengthSeconds=0, breadcrumb=1)) + + self.print_step(13, "Open commissioning window") +- params = self.default_controller.OpenCommissioningWindow( ++ params = await self.default_controller.OpenCommissioningWindow( + nodeid=self.dut_node_id, timeout=600, iteration=10000, discriminator=1234, option=1) + + self.print_step(14, "Commission to TH2") +@@ -170,7 +170,7 @@ class TC_DA_1_5(MatterBaseTest): + new_fabric_admin = new_certificate_authority.NewFabricAdmin(vendorId=0xFFF1, fabricId=2) + TH2 = new_fabric_admin.NewController(nodeId=112233) + +- TH2.CommissionOnNetwork( ++ await TH2.CommissionOnNetwork( + nodeId=self.dut_node_id, setupPinCode=params.setupPinCode, + filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=1234) + +diff --git a/src/python_testing/TC_IDM_1_2.py b/src/python_testing/TC_IDM_1_2.py +index 8f329daca5..18b727d962 100644 +--- a/src/python_testing/TC_IDM_1_2.py ++++ b/src/python_testing/TC_IDM_1_2.py +@@ -187,7 +187,7 @@ class TC_IDM_1_2(MatterBaseTest): + # To get a PASE session, we need an open commissioning window + discriminator = random.randint(0, 4095) + +- params = self.default_controller.OpenCommissioningWindow( ++ params = await self.default_controller.OpenCommissioningWindow( + nodeid=self.dut_node_id, timeout=600, iteration=10000, discriminator=discriminator, option=1) + + # TH2 = new controller that's not connected over CASE +@@ -201,14 +201,14 @@ class TC_IDM_1_2(MatterBaseTest): + device = next(filter(lambda d: d.commissioningMode == 2 and d.longDiscriminator == discriminator, devices)) + for a in device.addresses: + try: +- TH2.EstablishPASESessionIP(ipaddr=a, setupPinCode=params.setupPinCode, +- nodeid=self.dut_node_id+1, port=device.port) ++ await TH2.EstablishPASESessionIP(ipaddr=a, setupPinCode=params.setupPinCode, ++ nodeid=self.dut_node_id+1, port=device.port) + break + except ChipStackError: + continue + + try: +- TH2.GetConnectedDeviceSync(nodeid=self.dut_node_id+1, allowPASE=True, timeoutMs=1000) ++ await TH2.GetConnectedDevice(nodeid=self.dut_node_id+1, allowPASE=True, timeoutMs=1000) + except TimeoutError: + asserts.fail("Unable to establish a PASE session to the device") + +@@ -260,7 +260,7 @@ class TC_IDM_1_2(MatterBaseTest): + + # Try with RevokeCommissioning + # First open a commissioning window for us to revoke, so we know this command is able to succeed absent this error +- _ = self.default_controller.OpenCommissioningWindow( ++ _ = await self.default_controller.OpenCommissioningWindow( + nodeid=self.dut_node_id, timeout=600, iteration=10000, discriminator=discriminator, option=1) + cmd = FakeRevokeCommissioning() + try: +diff --git a/src/python_testing/TC_OPCREDS_3_1.py b/src/python_testing/TC_OPCREDS_3_1.py +index 661c3b0b9c..32f3ec7b1f 100644 +--- a/src/python_testing/TC_OPCREDS_3_1.py ++++ b/src/python_testing/TC_OPCREDS_3_1.py +@@ -30,7 +30,7 @@ from mobly import asserts + + + class TC_OPCREDS_3_1(MatterBaseTest): +- def FindAndEstablishPase(self, longDiscriminator: int, setupPinCode: int, nodeid: int, dev_ctrl: ChipDeviceCtrl = None): ++ async def FindAndEstablishPase(self, longDiscriminator: int, setupPinCode: int, nodeid: int, dev_ctrl: ChipDeviceCtrl = None): + if dev_ctrl is None: + dev_ctrl = self.default_controller + +@@ -41,8 +41,8 @@ class TC_OPCREDS_3_1(MatterBaseTest): + Discovery.FilterType.LONG_DISCRIMINATOR and d.longDiscriminator == longDiscriminator, devices)) + for a in device.addresses: + try: +- dev_ctrl.EstablishPASESessionIP(ipaddr=a, setupPinCode=setupPinCode, +- nodeid=nodeid, port=device.port) ++ await dev_ctrl.EstablishPASESessionIP(ipaddr=a, setupPinCode=setupPinCode, ++ nodeid=nodeid, port=device.port) + break + except ChipStackError: + continue +@@ -51,11 +51,11 @@ class TC_OPCREDS_3_1(MatterBaseTest): + except TimeoutError: + asserts.fail("Unable to establish a PASE session to the device") + +- def OpenCommissioningWindow(self, dev_ctrl: ChipDeviceCtrl, node_id: int): ++ async def OpenCommissioningWindow(self, dev_ctrl: ChipDeviceCtrl, node_id: int): + # TODO: abstract this in the base layer? Do we do this a lot? + longDiscriminator = random.randint(0, 4095) + try: +- params = dev_ctrl.OpenCommissioningWindow( ++ params = await dev_ctrl.OpenCommissioningWindow( + nodeid=node_id, timeout=600, iteration=10000, discriminator=longDiscriminator, option=ChipDeviceCtrl.ChipDeviceControllerBase.CommissioningWindowPasscode.kTokenWithRandomPin) + except Exception as e: + logging.exception('Error running OpenCommissioningWindow %s', e) +@@ -85,7 +85,7 @@ class TC_OPCREDS_3_1(MatterBaseTest): + "Device fabric table is full - please remove one fabric and retry") + + self.print_step(1, "TH0 opens a commissioning window on the DUT") +- longDiscriminator, params = self.OpenCommissioningWindow(self.default_controller, self.dut_node_id) ++ longDiscriminator, params = await self.OpenCommissioningWindow(self.default_controller, self.dut_node_id) + + self.print_step( + 2, "TH0 reads the BasicCommissioningInfo field from the General commissioning cluster saves MaxCumulativeFailsafeSeconds as `failsafe_max`") +@@ -94,8 +94,8 @@ class TC_OPCREDS_3_1(MatterBaseTest): + + self.print_step(3, "TH1 opens a PASE connection to the DUT") + newNodeId = self.dut_node_id + 1 +- self.FindAndEstablishPase(dev_ctrl=TH1, longDiscriminator=longDiscriminator, +- setupPinCode=params.setupPinCode, nodeid=newNodeId) ++ await self.FindAndEstablishPase(dev_ctrl=TH1, longDiscriminator=longDiscriminator, ++ setupPinCode=params.setupPinCode, nodeid=newNodeId) + + self.print_step(4, "TH1 sends ArmFailSafe command to the DUT with the ExpiryLengthSeconds field set to failsafe_max") + resp = await self.send_single_cmd(dev_ctrl=TH1, node_id=newNodeId, cmd=Clusters.GeneralCommissioning.Commands.ArmFailSafe(failsafe_max)) +@@ -109,7 +109,7 @@ class TC_OPCREDS_3_1(MatterBaseTest): + self.print_step(6, "TH1 obtains or generates the NOC, the Root CA Certificate and ICAC using csrResponse and selects an IPK. The certificates shall have their subjects padded with additional data such that they are each the maximum certificate size of 400 bytes when encoded in the MatterCertificateEncoding.") + # Our CA is set up to maximize cert chains already + # Extract the RCAC public key and save as `Root_Public_Key_TH1` +- TH1_certs_real = TH1.IssueNOCChain(csrResponse, newNodeId) ++ TH1_certs_real = await TH1.IssueNOCChain(csrResponse, newNodeId) + if (TH1_certs_real.rcacBytes is None or + TH1_certs_real.icacBytes is None or + TH1_certs_real.nocBytes is None or TH1_certs_real.ipkBytes is None): +@@ -125,7 +125,7 @@ class TC_OPCREDS_3_1(MatterBaseTest): + TH1_CA_fake = self.certificate_authority_manager.NewCertificateAuthority() + TH1_fabric_admin_fake = TH1_CA_fake.NewFabricAdmin(vendorId=0xFFF1, fabricId=2) + TH1_fake = TH1_fabric_admin_fake.NewController(nodeId=self.default_controller.nodeId) +- TH1_certs_fake = TH1_fake.IssueNOCChain(csrResponse, newNodeId) ++ TH1_certs_fake = await TH1_fake.IssueNOCChain(csrResponse, newNodeId) + if (TH1_certs_fake.rcacBytes is None or + TH1_certs_fake.icacBytes is None or + TH1_certs_fake.nocBytes is None or TH1_certs_real.ipkBytes is None): +@@ -361,7 +361,7 @@ class TC_OPCREDS_3_1(MatterBaseTest): + asserts.assert_equal(len(fabrics), fabrics_original_size, "Fabric list size does not match original") + + self.print_step(37, "TH1 fully commissions DUT onto the fabric using a set of valid certificates") +- TH1.Commission(newNodeId) ++ await TH1.Commission(newNodeId) + + self.print_step( + 38, "TH1 reads the TrustedRootCertificates list from DUT and verify that there are trusted_root_original_size + 1 entries") +@@ -404,7 +404,7 @@ class TC_OPCREDS_3_1(MatterBaseTest): + resp.statusCode, opcreds.Enums.NodeOperationalCertStatusEnum.kOk, "Failure on UpdateFabricLabel") + + self.print_step(44, "TH1 sends an OpenCommissioningWindow command to the Administrator Commissioning cluster") +- longDiscriminator, params = self.OpenCommissioningWindow(TH1, newNodeId) ++ longDiscriminator, params = await self.OpenCommissioningWindow(TH1, newNodeId) + + self.print_step(45, "TH2 commissions the DUT") + TH2_CA = self.certificate_authority_manager.NewCertificateAuthority(maximizeCertChains=True) +@@ -413,7 +413,7 @@ class TC_OPCREDS_3_1(MatterBaseTest): + TH2_nodeid = self.default_controller.nodeId+2 + TH2 = TH2_fabric_admin.NewController(nodeId=TH2_nodeid) + TH2_dut_nodeid = self.dut_node_id+2 +- TH2.CommissionOnNetwork( ++ await TH2.CommissionOnNetwork( + nodeId=TH2_dut_nodeid, setupPinCode=params.setupPinCode, + filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=longDiscriminator) + +@@ -484,7 +484,7 @@ class TC_OPCREDS_3_1(MatterBaseTest): + temp_CA = self.certificate_authority_manager.NewCertificateAuthority() + temp_fabric_admin = temp_CA.NewFabricAdmin(vendorId=0xFFF1, fabricId=3) + temp_controller = temp_fabric_admin.NewController(nodeId=self.default_controller.nodeId) +- temp_certs = temp_controller.IssueNOCChain(csrResponse, newNodeId) ++ temp_certs = await temp_controller.IssueNOCChain(csrResponse, newNodeId) + if (temp_certs.rcacBytes is None or + temp_certs.icacBytes is None or + temp_certs.nocBytes is None or temp_certs.ipkBytes is None): +@@ -521,7 +521,7 @@ class TC_OPCREDS_3_1(MatterBaseTest): + + self.print_step(61, "TH1 obtains or generates a NOC and ICAC using the CSR elements from the previous step with a different NodeID, but the same Root CA Certificate and fabric ID as step <>. Save as `Node_Operational_Certificates_TH1_fabric_conflict` and `Intermediate_Certificate_TH1_fabric_conflict`|") + anotherNodeId = newNodeId + 1 +- TH1_certs_fabric_conflict = TH1.IssueNOCChain(csrResponse_new, anotherNodeId) ++ TH1_certs_fabric_conflict = await TH1.IssueNOCChain(csrResponse_new, anotherNodeId) + if (TH1_certs_fabric_conflict.rcacBytes is None or + TH1_certs_fabric_conflict.icacBytes is None or + TH1_certs_fabric_conflict.nocBytes is None or TH1_certs_fabric_conflict.ipkBytes is None): +@@ -565,7 +565,7 @@ class TC_OPCREDS_3_1(MatterBaseTest): + "Unexpected response type for UpdateNOC csr request") + + self.print_step(68, "TH1 obtains or generates a NOC, Root CA Certificate, ICAC using the CSR elements from the previous step") +- TH1_certs_3 = TH1.IssueNOCChain(csrResponse, anotherNodeId) ++ TH1_certs_3 = await TH1.IssueNOCChain(csrResponse, anotherNodeId) + if (TH1_certs_3.rcacBytes is None or + TH1_certs_3.icacBytes is None or + TH1_certs_3.nocBytes is None or TH1_certs_3.ipkBytes is None): +diff --git a/src/python_testing/TC_TIMESYNC_2_13.py b/src/python_testing/TC_TIMESYNC_2_13.py +index fa43bbd00c..dea41e505c 100644 +--- a/src/python_testing/TC_TIMESYNC_2_13.py ++++ b/src/python_testing/TC_TIMESYNC_2_13.py +@@ -45,7 +45,7 @@ class TC_TIMESYNC_2_13(MatterBaseTest): + self.print_step(0, "Commissioning, already done") + + self.print_step(1, "TH1 opens a commissioning window") +- params = self.default_controller.OpenCommissioningWindow( ++ params = await self.default_controller.OpenCommissioningWindow( + nodeid=self.dut_node_id, timeout=600, iteration=10000, discriminator=1234, option=1) + + self.print_step(2, "Commission to TH2") +@@ -53,7 +53,7 @@ class TC_TIMESYNC_2_13(MatterBaseTest): + new_fabric_admin = new_certificate_authority.NewFabricAdmin(vendorId=0xFFF1, fabricId=2) + TH2 = new_fabric_admin.NewController(nodeId=112233) + +- TH2.CommissionOnNetwork( ++ await TH2.CommissionOnNetwork( + nodeId=self.dut_node_id, setupPinCode=params.setupPinCode, + filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=1234) + +diff --git a/src/python_testing/TestCommissioningTimeSync.py b/src/python_testing/TestCommissioningTimeSync.py +index 0fca7063fc..dfd03d957d 100644 +--- a/src/python_testing/TestCommissioningTimeSync.py ++++ b/src/python_testing/TestCommissioningTimeSync.py +@@ -56,9 +56,9 @@ class TestCommissioningTimeSync(MatterBaseTest): + return super().teardown_test() + + async def commission_and_base_checks(self): +- params = self.default_controller.OpenCommissioningWindow( ++ params = await self.default_controller.OpenCommissioningWindow( + nodeid=self.dut_node_id, timeout=600, iteration=10000, discriminator=1234, option=1) +- self.commissioner.CommissionOnNetwork( ++ await self.commissioner.CommissionOnNetwork( + nodeId=self.dut_node_id, setupPinCode=params.setupPinCode, + filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=1234) + self.commissioned = True +diff --git a/src/python_testing/basic_composition_support.py b/src/python_testing/basic_composition_support.py +index 4a516f67ec..2ea5995960 100644 +--- a/src/python_testing/basic_composition_support.py ++++ b/src/python_testing/basic_composition_support.py +@@ -108,7 +108,7 @@ class BasicCompositionTests: + setupCode = self.matter_test_config.qr_code_content if self.matter_test_config.qr_code_content is not None else self.matter_test_config.manual_code + asserts.assert_true(setupCode, "Require either --qr-code or --manual-code.") + node_id = self.dut_node_id +- dev_ctrl.EstablishPASESession(setupCode, node_id) ++ await dev_ctrl.EstablishPASESession(setupCode, node_id) + else: + # Using the already commissioned node + node_id = self.dut_node_id +diff --git a/src/python_testing/matter_testing_support.py b/src/python_testing/matter_testing_support.py +index cd7fce8f08..a3bd5d2837 100644 +--- a/src/python_testing/matter_testing_support.py ++++ b/src/python_testing/matter_testing_support.py +@@ -805,11 +805,11 @@ class MatterBaseTest(base_test.BaseTestClass): + pics_key = pics_key.strip() + return pics_key in picsd and picsd[pics_key] + +- def openCommissioningWindow(self, dev_ctrl: ChipDeviceCtrl, node_id: int) -> CustomCommissioningParameters: ++ async def openCommissioningWindow(self, dev_ctrl: ChipDeviceCtrl, node_id: int) -> CustomCommissioningParameters: + rnd_discriminator = random.randint(0, 4095) + try: +- commissioning_params = dev_ctrl.OpenCommissioningWindow(nodeid=node_id, timeout=900, iteration=1000, +- discriminator=rnd_discriminator, option=1) ++ commissioning_params = await dev_ctrl.OpenCommissioningWindow(nodeid=node_id, timeout=900, iteration=1000, ++ discriminator=rnd_discriminator, option=1) + params = CustomCommissioningParameters(commissioning_params, rnd_discriminator) + return params + +@@ -1563,10 +1563,10 @@ class CommissionDeviceTest(MatterBaseTest): + (conf.root_of_trust_index, conf.fabric_id, node_id)) + logging.info("Commissioning method: %s" % conf.commissioning_method) + +- if not self._commission_device(commission_idx): ++ if not asyncio.run(self._commission_device(commission_idx)): + raise signals.TestAbortAll("Failed to commission node") + +- def _commission_device(self, i) -> bool: ++ async def _commission_device(self, i) -> bool: + dev_ctrl = self.default_controller + conf = self.matter_test_config + +@@ -1582,7 +1582,7 @@ class CommissionDeviceTest(MatterBaseTest): + + if conf.commissioning_method == "on-network": + try: +- dev_ctrl.CommissionOnNetwork( ++ await dev_ctrl.CommissionOnNetwork( + nodeId=conf.dut_node_ids[i], + setupPinCode=info.passcode, + filterType=info.filter_type, +@@ -1594,7 +1594,7 @@ class CommissionDeviceTest(MatterBaseTest): + return False + elif conf.commissioning_method == "ble-wifi": + try: +- dev_ctrl.CommissionWiFi( ++ await dev_ctrl.CommissionWiFi( + info.filter_value, + info.passcode, + conf.dut_node_ids[i], +@@ -1608,7 +1608,7 @@ class CommissionDeviceTest(MatterBaseTest): + return False + elif conf.commissioning_method == "ble-thread": + try: +- dev_ctrl.CommissionThread( ++ await dev_ctrl.CommissionThread( + info.filter_value, + info.passcode, + conf.dut_node_ids[i], +@@ -1622,7 +1622,7 @@ class CommissionDeviceTest(MatterBaseTest): + elif conf.commissioning_method == "on-network-ip": + try: + logging.warning("==== USING A DIRECT IP COMMISSIONING METHOD NOT SUPPORTED IN THE LONG TERM ====") +- dev_ctrl.CommissionIP( ++ await dev_ctrl.CommissionIP( + ipaddr=conf.commissionee_ip_address_just_for_testing, + setupPinCode=info.passcode, nodeid=conf.dut_node_ids[i] + ) +-- +2.45.2 +