diff --git a/examples/lit-icd-app/linux/main.cpp b/examples/lit-icd-app/linux/main.cpp index 1f4031af407d42..601cc22d3d2af9 100644 --- a/examples/lit-icd-app/linux/main.cpp +++ b/examples/lit-icd-app/linux/main.cpp @@ -19,16 +19,26 @@ #include "AppMain.h" #include +#include "system/SystemClock.h" + using namespace chip; using namespace chip::app; +using namespace chip::System::Clock::Literals; void ApplicationInit() {} void ApplicationShutdown() {} +void notifyIcdActive(System::Layer * layer, void *) +{ + ICDNotifier::GetInstance().NotifyNetworkActivityNotification(); + DeviceLayer::SystemLayer().StartTimer(10000_ms32, notifyIcdActive, nullptr); +} + int main(int argc, char * argv[]) { VerifyOrDie(ChipLinuxAppInit(argc, argv) == 0); + DeviceLayer::SystemLayer().StartTimer(10000_ms32, notifyIcdActive, nullptr); ChipLinuxAppMainLoop(); return 0; } diff --git a/scripts/tests/cirque_tests.sh b/scripts/tests/cirque_tests.sh index 67bf0f2de63af5..dddbdfde1c1185 100755 --- a/scripts/tests/cirque_tests.sh +++ b/scripts/tests/cirque_tests.sh @@ -44,6 +44,7 @@ CIRQUE_TESTS=( "MobileDeviceTest" "CommissioningTest" "InteractionModelTest" + "IcdWaitForActiveTest" "SplitCommissioningTest" "CommissioningFailureTest" "CommissioningFailureOnReportTest" diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py index 2ba7c584db927b..45afeb298d98d1 100644 --- a/src/controller/python/chip/ChipDeviceCtrl.py +++ b/src/controller/python/chip/ChipDeviceCtrl.py @@ -205,8 +205,7 @@ def OnCheckInCallback(nodeid): RegisterOnActiveCallback(scopedNodeId, OnCheckInCallback) try: - async with asyncio.timeout(timeoutSeconds): - await future + asyncio.wait_for(future, timeout=timeoutSeconds) finally: UnregisterOnActiveCallback(scopedNodeId, OnCheckInCallback) diff --git a/src/controller/python/test/test_scripts/base.py b/src/controller/python/test/test_scripts/base.py index 3f9f76d9101874..33ab5cc05ba4b3 100644 --- a/src/controller/python/test/test_scripts/base.py +++ b/src/controller/python/test/test_scripts/base.py @@ -183,7 +183,7 @@ def __init__(self, nodeid: int, paaTrustStorePath: str, testCommissioner: bool = keypair: p256keypair.P256Keypair = None): chip.native.Init() - self.chipStack = ChipStack('/tmp/repl_storage.json') + self.chipStack = ChipStack('/tmp/repl_storage.json', enableServerInteractions=True) self.certificateAuthorityManager = chip.CertificateAuthority.CertificateAuthorityManager(chipStack=self.chipStack) self.certificateAuthority = self.certificateAuthorityManager.NewCertificateAuthority() self.fabricAdmin = self.certificateAuthority.NewFabricAdmin(vendorId=0xFFF1, fabricId=1) @@ -1148,6 +1148,24 @@ def TestResolve(self, nodeid): self.logger.exception("Failed to resolve. {}".format(ex)) return False + async def TestTriggerTestEventHandler(self, nodeid, enable_key, event_trigger): + self.logger.info("Test trigger test event handler for device = %08x", nodeid) + try: + await self.devCtrl.SendCommand(nodeid, 0, Clusters.GeneralDiagnostics.Commands.TestEventTrigger(enableKey=enable_key, eventTrigger=event_trigger)) + return True + except Exception as ex: + self.logger.exception("Failed to trigger test event handler {}".format(ex)) + return False + + async def TestWaitForActive(self, nodeid): + self.logger.info("Test wait for device = %08x", nodeid) + try: + await self.devCtrl.WaitForActive(nodeid) + return True + except Exception as ex: + self.logger.exception("Failed to wait for active. {}".format(ex)) + return False + async def TestReadBasicAttributes(self, nodeid: int, endpoint: int): attrs = Clusters.BasicInformation.Attributes basic_cluster_attrs = { diff --git a/src/controller/python/test/test_scripts/icd_wait_for_device_test.py b/src/controller/python/test/test_scripts/icd_wait_for_device_test.py new file mode 100755 index 00000000000000..f6fe740e0b10ef --- /dev/null +++ b/src/controller/python/test/test_scripts/icd_wait_for_device_test.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 + +# +# Copyright (c) 2021 Project CHIP Authors +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Commissioning test. + +import asyncio +import os +import sys +from optparse import OptionParser + +from base import BaseTestHelper, FailIfNot, TestFail, TestTimeout, logger + +TEST_DISCRIMINATOR = 3840 +TEST_DISCOVERY_TYPE = 2 + +ENDPOINT_ID = 0 +LIGHTING_ENDPOINT_ID = 1 +GROUP_ID = 0 + + +async def waitForActiveAndTriggerCheckIn(test, nodeid): + coro = test.TestWaitForActive(nodeid=nodeid) + await test.TestTriggerTestEventHandler(nodeid, bytes.fromhex("00112233445566778899aabbccddeeff"), 0x0046 << 48) + return await coro + + +def main(): + optParser = OptionParser() + optParser.add_option( + "-t", + "--timeout", + action="store", + dest="testTimeout", + default=75, + type='int', + help="The program will return with timeout after specified seconds.", + metavar="", + ) + optParser.add_option( + "-a", + "--address", + action="store", + dest="deviceAddress", + default='', + type='str', + help="Address of the device", + metavar="", + ) + optParser.add_option( + "--setup-payload", + action="store", + dest="setupPayload", + default='', + type='str', + help="Setup Payload (manual pairing code or QR code content)", + metavar="" + ) + optParser.add_option( + "--nodeid", + action="store", + dest="nodeid", + default=1, + type=int, + help="The Node ID issued to the device", + metavar="" + ) + optParser.add_option( + "--discriminator", + action="store", + dest="discriminator", + default=TEST_DISCRIMINATOR, + type=int, + help="Discriminator of the device", + metavar="" + ) + optParser.add_option( + "-p", + "--paa-trust-store-path", + action="store", + dest="paaTrustStorePath", + default='', + type='str', + help="Path that contains valid and trusted PAA Root Certificates.", + metavar="" + ) + optParser.add_option( + "--discovery-type", + action="store", + dest="discoveryType", + default=TEST_DISCOVERY_TYPE, + type=int, + help="Discovery type of commissioning. (0: networkOnly 1: networkOnlyWithoutPASEAutoRetry 2: All)", + metavar="" + ) + optParser.add_option("--test-step", action="store", dest="testStep", type="str", + help="the step of the test (can be 'commissioning' or 'wait-for-active')", metavar="") + + (options, remainingArgs) = optParser.parse_args(sys.argv[1:]) + + timeoutTicker = TestTimeout(options.testTimeout) + timeoutTicker.start() + + test = BaseTestHelper( + nodeid=112233, paaTrustStorePath=options.paaTrustStorePath, testCommissioner=True) + + logger.info("Testing discovery") + FailIfNot(test.TestDiscovery(discriminator=options.discriminator), + "Failed to discover any devices.") + + devCtrl = test.devCtrl + devCtrl.EnableICDRegistration(devCtrl.GenerateICDRegistrationParameters()) + if options.deviceAddress: + logger.info("Testing commissioning (IP)") + FailIfNot(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), + "Failed to finish commissioning") + else: + TestFail("Must provide device address or setup payload to commissioning the device") + logger.info("Commissioning completed") + logger.info("Testing wait for active") + FailIfNot(asyncio.run(waitForActiveAndTriggerCheckIn(test, nodeid=options.nodeid)), "Failed to test wait for active") + logger.info('Successfully handled wait-for-active') + + timeoutTicker.stop() + + logger.info("Test finished") + + # TODO: Python device controller cannot be shutdown clean sometimes and will block on AsyncDNSResolverSockets shutdown. + # Call os._exit(0) to force close it. + os._exit(0) + + +if __name__ == "__main__": + try: + main() + except Exception as ex: + logger.exception(ex) + TestFail("Exception occurred when running tests.") diff --git a/src/test_driver/linux-cirque/IcdWaitForActiveTest.py b/src/test_driver/linux-cirque/IcdWaitForActiveTest.py new file mode 100755 index 00000000000000..8b24da63db7e44 --- /dev/null +++ b/src/test_driver/linux-cirque/IcdWaitForActiveTest.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +""" +Copyright (c) 2021 Project CHIP Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import logging +import os +import sys + +from helper.CHIPTestBase import CHIPVirtualHome + +logger = logging.getLogger('MobileDeviceTest') +logger.setLevel(logging.INFO) + +sh = logging.StreamHandler() +sh.setFormatter( + logging.Formatter( + '%(asctime)s [%(name)s] %(levelname)s %(message)s')) +logger.addHandler(sh) + +CHIP_PORT = 5540 + +CIRQUE_URL = "http://localhost:5000" +CHIP_REPO = os.path.join(os.path.abspath( + os.path.dirname(__file__)), "..", "..", "..") +TEST_EXTPANID = "fedcba9876543210" +TEST_DISCRIMINATOR = 3840 +TEST_DISCRIMINATOR2 = 3584 +TEST_DISCRIMINATOR3 = 1203 +TEST_DISCRIMINATOR4 = 2145 +TEST_DISCOVERY_TYPE = [0, 1, 2] +MATTER_DEVELOPMENT_PAA_ROOT_CERTS = "credentials/development/paa-root-certs" + +DEVICE_CONFIG = { + 'device0': { + 'type': 'MobileDevice', + 'base_image': '@default', + 'capability': ['TrafficControl', 'Mount'], + 'rcp_mode': True, + 'docker_network': 'Ipv6', + 'traffic_control': {'latencyMs': 100}, + "mount_pairs": [[CHIP_REPO, CHIP_REPO]], + }, + 'device1': { + 'type': 'CHIPEndDevice', + 'base_image': '@default', + 'capability': ['Thread', 'TrafficControl', 'Mount'], + 'rcp_mode': True, + 'docker_network': 'Ipv6', + 'traffic_control': {'latencyMs': 100}, + "mount_pairs": [[CHIP_REPO, CHIP_REPO]], + } +} + + +class TestCommissioner(CHIPVirtualHome): + def __init__(self, device_config): + super().__init__(CIRQUE_URL, device_config) + self.logger = logger + + def setup(self): + self.initialize_home() + + def test_routine(self): + self.run_controller_test() + + def run_controller_test(self): + servers = [{ + "ip": device['description']['ipv6_addr'], + "id": device['id'] + } for device in self.non_ap_devices + if device['type'] == 'CHIPEndDevice'] + req_ids = [device['id'] for device in self.non_ap_devices + if device['type'] == 'MobileDevice'] + + servers[0]['discriminator'] = TEST_DISCRIMINATOR + servers[0]['nodeid'] = 1 + + for server in servers: + self.execute_device_cmd( + server['id'], + ("CHIPCirqueDaemon.py -- run gdb -return-child-result -q -ex \"set pagination off\" " + "-ex run -ex \"bt 25\" --args {} --thread --discriminator {}").format( + os.path.join(CHIP_REPO, "out/debug/lit_icd/lit-icd-app"), server['discriminator'])) + + self.reset_thread_devices([server['id'] for server in servers]) + + req_device_id = req_ids[0] + + self.execute_device_cmd(req_device_id, "pip3 install {}".format(os.path.join( + CHIP_REPO, "out/debug/linux_x64_gcc/controller/python/chip_clusters-0.0-py3-none-any.whl"))) + self.execute_device_cmd(req_device_id, "pip3 install {}".format(os.path.join( + CHIP_REPO, "out/debug/linux_x64_gcc/controller/python/chip_core-0.0-cp37-abi3-linux_x86_64.whl"))) + self.execute_device_cmd(req_device_id, "pip3 install {}".format(os.path.join( + CHIP_REPO, "out/debug/linux_x64_gcc/controller/python/chip_repl-0.0-py3-none-any.whl"))) + + command = ("CHIPCirqueDaemon.py -- run gdb -return-child-result -q -ex run -ex bt --args python3 " + "{} -t 150 -a {} --paa-trust-store-path {} --discriminator {} --nodeid {}").format( + os.path.join( + CHIP_REPO, "src/controller/python/test/test_scripts/icd_wait_for_device_test.py"), + servers[0]['ip'], + os.path.join(CHIP_REPO, MATTER_DEVELOPMENT_PAA_ROOT_CERTS), + servers[0]['discriminator'], + servers[0]['nodeid']) + self.execute_device_cmd(req_device_id, command) + + self.assertTrue(self.wait_for_device_output(req_device_id, "Commissioning completed", 10)) + self.assertTrue(self.wait_for_device_output(req_device_id, "Successfully handled wait-for-active", 30)) + + +if __name__ == "__main__": + sys.exit(TestCommissioner(DEVICE_CONFIG).run_test())