Skip to content

Commit

Permalink
[Python] Convert DiscoverCommissionableNodes to asyncio (project-chip…
Browse files Browse the repository at this point in the history
…#34033)

* [Python] Convert DiscoverCommissionableNodes to asyncio

Make the discovery of commissionable nodes Python asyncio APIs as well.
This avoids blocking the event loop when using the API.

The implementation is also safe to be used with the Python asyncio
wait_for() function: The discovery process will be cancelled if the
timeout is reached.

* [Python] Adjust tests to use new DiscoverCommissionableNodes API
  • Loading branch information
agners authored Jun 21, 2024
1 parent a7b064a commit bf7e9fc
Show file tree
Hide file tree
Showing 9 changed files with 42 additions and 34 deletions.
54 changes: 31 additions & 23 deletions src/controller/python/chip/ChipDeviceCtrl.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
import logging
import secrets
import threading
import time
import typing
from ctypes import (CDLL, CFUNCTYPE, POINTER, Structure, byref, c_bool, c_char, c_char_p, c_int, c_int32, c_size_t, c_uint8,
c_uint16, c_uint32, c_uint64, c_void_p, create_string_buffer, pointer, py_object, resize, string_at)
Expand Down Expand Up @@ -729,8 +728,8 @@ def GetAddressAndPort(self, nodeid):

return (address.value.decode(), port.value) if error == 0 else None

def DiscoverCommissionableNodes(self, filterType: discovery.FilterType = discovery.FilterType.NONE, filter: typing.Any = None,
stopOnFirst: bool = False, timeoutSecond: int = 5) -> typing.Union[None, CommissionableNode, typing.List[CommissionableNode]]:
async def DiscoverCommissionableNodes(self, filterType: discovery.FilterType = discovery.FilterType.NONE, filter: typing.Any = None,
stopOnFirst: bool = False, timeoutSecond: int = 5) -> typing.Union[None, CommissionableNode, typing.List[CommissionableNode]]:
''' Discover commissionable nodes via DNS-SD with specified filters.
Supported filters are:
Expand All @@ -752,27 +751,36 @@ def DiscoverCommissionableNodes(self, filterType: discovery.FilterType = discove
if isinstance(filter, int):
filter = str(filter)

self._ChipStack.Call(
lambda: self._dmLib.pychip_DeviceController_DiscoverCommissionableNodes(
self.devCtrl, int(filterType), str(filter).encode("utf-8"))).raise_on_error()

if timeoutSecond != 0:
if stopOnFirst:
target = time.time() + timeoutSecond
while time.time() < target:
if self._ChipStack.Call(
lambda: self._dmLib.pychip_DeviceController_HasDiscoveredCommissionableNode(self.devCtrl)):
break
time.sleep(0.1)
else:
time.sleep(timeoutSecond)

self._ChipStack.Call(
lambda: self._dmLib.pychip_DeviceController_StopCommissionableDiscovery(self.devCtrl)).raise_on_error()
# Discovery is also used during commissioning. Make sure this manual discovery
# and commissioning attempts do not interfere with each other.
async with self._commissioning_lock:
res = await self._ChipStack.CallAsync(
lambda: self._dmLib.pychip_DeviceController_DiscoverCommissionableNodes(
self.devCtrl, int(filterType), str(filter).encode("utf-8")))
res.raise_on_error()

return self.GetDiscoveredDevices()
async def _wait_discovery():
while not await self._ChipStack.CallAsync(
lambda: self._dmLib.pychip_DeviceController_HasDiscoveredCommissionableNode(self.devCtrl)):
await asyncio.sleep(0.1)
return

def GetDiscoveredDevices(self):
try:
if stopOnFirst:
await asyncio.wait_for(_wait_discovery(), timeoutSecond)
else:
await asyncio.sleep(timeoutSecond)
except TimeoutError:
# Expected timeout, do nothing
pass
finally:
res = await self._ChipStack.CallAsync(
lambda: self._dmLib.pychip_DeviceController_StopCommissionableDiscovery(self.devCtrl))
res.raise_on_error()

return await self.GetDiscoveredDevices()

async def GetDiscoveredDevices(self):
def GetDevices(devCtrl):
devices = []

Expand All @@ -786,7 +794,7 @@ def HandleDevice(deviceJson, deviceJsonLen):
self._dmLib.pychip_DeviceController_IterateDiscoveredCommissionableNodes(devCtrl.devCtrl, HandleDevice)
return devices

return self._ChipStack.Call(lambda: GetDevices(self))
return await self._ChipStack.CallAsync(lambda: GetDevices(self))

def GetIPForDiscoveredDevice(self, idx, addrStr, length):
self.CheckIsActive()
Expand Down
4 changes: 2 additions & 2 deletions src/controller/python/chip/commissioning/pase.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ async def establish_session(devCtrl: ChipDeviceCtrl.ChipDeviceControllerBase, pa
if isinstance(parameter, commissioning.PaseOverBLEParameters):
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)
device = await devCtrl.DiscoverCommissionableNodes(filterType=discovery.FilterType.LONG_DISCRIMINATOR,
filter=parameter.long_discriminator, stopOnFirst=True)
if not device:
raise ValueError("No commissionable device found")
selected_address = None
Expand Down
2 changes: 1 addition & 1 deletion src/controller/python/chip/yaml/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,7 @@ def __init__(self, test_step):
self.filterType, self.filter = DiscoveryCommandAction._filter_for_step(test_step)

async def run_action(self, dev_ctrl: ChipDeviceController) -> _ActionResult:
devices = dev_ctrl.DiscoverCommissionableNodes(
devices = await dev_ctrl.DiscoverCommissionableNodes(
filterType=self.filterType, filter=self.filter, stopOnFirst=True, timeoutSecond=5)

# Devices will be a list: [CommissionableNode(), ...]
Expand Down
6 changes: 3 additions & 3 deletions src/controller/python/test/test_scripts/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,10 @@ def _WaitForOneDiscoveredDevice(self, timeoutSeconds: int = 2):
return None
return ctypes.string_at(addrStrStorage).decode("utf-8")

def TestDiscovery(self, discriminator: int):
async def TestDiscovery(self, discriminator: int):
self.logger.info(
f"Discovering commissionable nodes with discriminator {discriminator}")
res = self.devCtrl.DiscoverCommissionableNodes(
res = await self.devCtrl.DiscoverCommissionableNodes(
chip.discovery.FilterType.LONG_DISCRIMINATOR, discriminator, stopOnFirst=True, timeoutSecond=3)
if not res:
self.logger.info(
Expand Down Expand Up @@ -337,7 +337,7 @@ async def TestCommissioningWithSetupPayload(self, setupPayload: str, nodeid: int

async def TestOnNetworkCommissioning(self, discriminator: int, setuppin: int, nodeid: int, ip_override: str = None):
self.logger.info("Testing discovery")
device = self.TestDiscovery(discriminator=discriminator)
device = await self.TestDiscovery(discriminator=discriminator)
if not device:
self.logger.info("Failed to discover any devices.")
return False
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ async def main():
nodeid=112233, paaTrustStorePath=options.paaTrustStorePath, testCommissioner=True)

logger.info("Testing discovery")
FailIfNot(test.TestDiscovery(discriminator=options.discriminator),
FailIfNot(await test.TestDiscovery(discriminator=options.discriminator),
"Failed to discover any devices.")

FailIfNot(test.SetNetworkCommissioningParameters(dataset=TEST_THREAD_NETWORK_DATASET_TLV),
Expand Down
2 changes: 1 addition & 1 deletion src/controller/python/test/test_scripts/failsafe_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ async def main():
nodeid=112233, paaTrustStorePath=options.paaTrustStorePath, testCommissioner=False)

logger.info("Testing discovery")
FailIfNot(test.TestDiscovery(discriminator=TEST_DISCRIMINATOR),
FailIfNot(await test.TestDiscovery(discriminator=TEST_DISCRIMINATOR),
"Failed to discover any devices.")

FailIfNot(test.SetNetworkCommissioningParameters(dataset=TEST_THREAD_NETWORK_DATASET_TLV),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@

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)
device = await test.TestDiscovery(discriminator=discriminator)
FailIfNot(device, "Failed to discover any devices.")

address = device.addresses[0]
Expand Down
2 changes: 1 addition & 1 deletion src/python_testing/TC_IDM_1_2.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ async def test_TC_IDM_1_2(self):
new_fabric_admin = new_certificate_authority.NewFabricAdmin(vendorId=0xFFF1, fabricId=self.matter_test_config.fabric_id + 1)
TH2 = new_fabric_admin.NewController(nodeId=112233)

devices = TH2.DiscoverCommissionableNodes(
devices = await TH2.DiscoverCommissionableNodes(
filterType=Discovery.FilterType.LONG_DISCRIMINATOR, filter=discriminator, stopOnFirst=False)
# For some reason, the devices returned here aren't filtered, so filter ourselves
device = next(filter(lambda d: d.commissioningMode == 2 and d.longDiscriminator == discriminator, devices))
Expand Down
2 changes: 1 addition & 1 deletion src/python_testing/TC_OPCREDS_3_1.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ async def FindAndEstablishPase(self, longDiscriminator: int, setupPinCode: int,
if dev_ctrl is None:
dev_ctrl = self.default_controller

devices = dev_ctrl.DiscoverCommissionableNodes(
devices = await dev_ctrl.DiscoverCommissionableNodes(
filterType=Discovery.FilterType.LONG_DISCRIMINATOR, filter=longDiscriminator, stopOnFirst=False)
# For some reason, the devices returned here aren't filtered, so filter ourselves
device = next(filter(lambda d: d.commissioningMode ==
Expand Down

0 comments on commit bf7e9fc

Please sign in to comment.