From fdcf07a725e04cce9cd9c07cefcbe8af36c129df Mon Sep 17 00:00:00 2001 From: Kamil Kasperczyk <66371704+kkasperczyk-no@users.noreply.github.com> Date: Tue, 16 Apr 2024 16:51:44 +0200 Subject: [PATCH 01/38] [zephyr] Fix CHIPDevicePlatformEvent.h include dependency (#33004) Currently the CHIPDevicePlatformEvent depends on the SystemPacketBuffer which is included in the CHIPDeviceEvent.h too late. The problem is silently worked around in the application when the include is preceded by other headers that pull in SystemPacketBuffer.h, but we need a proper fix. Signed-off-by: Marcin Kajor --- src/platform/Zephyr/CHIPDevicePlatformEvent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/Zephyr/CHIPDevicePlatformEvent.h b/src/platform/Zephyr/CHIPDevicePlatformEvent.h index ff329ec7590ea0..8291c5d3a3a355 100644 --- a/src/platform/Zephyr/CHIPDevicePlatformEvent.h +++ b/src/platform/Zephyr/CHIPDevicePlatformEvent.h @@ -23,7 +23,7 @@ #pragma once -#include +#include #include From 90732b256ee1b405d7461f69eb8e25abe700c805 Mon Sep 17 00:00:00 2001 From: Shubham Patil Date: Tue, 16 Apr 2024 21:35:50 +0530 Subject: [PATCH 02/38] Remove unused variable from scenes-server (#33006) --- src/app/clusters/scenes-server/SceneTableImpl.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/clusters/scenes-server/SceneTableImpl.h b/src/app/clusters/scenes-server/SceneTableImpl.h index 62317732eca0b9..d77e957f5c32d2 100644 --- a/src/app/clusters/scenes-server/SceneTableImpl.h +++ b/src/app/clusters/scenes-server/SceneTableImpl.h @@ -40,7 +40,6 @@ static_assert(kMaxScenesPerEndpoint <= CHIP_CONFIG_MAX_SCENES_TABLE_SIZE, "CHIP_CONFIG_MAX_SCENES_TABLE_SIZE in CHIPConfig.h if you really need more scenes"); static_assert(kMaxScenesPerEndpoint >= 16, "Per spec, kMaxScenesPerEndpoint must be at least 16"); static constexpr uint16_t kMaxScenesPerFabric = (kMaxScenesPerEndpoint - 1) / 2; -static constexpr uint8_t kMaxFabrics = CHIP_CONFIG_MAX_FABRICS; /** * @brief Implementation of a storage in nonvolatile storage of the scene table. From c065cc79a166efa4ff1110ef4815b96c33d664ca Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Tue, 16 Apr 2024 15:41:44 -0400 Subject: [PATCH 03/38] Clarify some documentation for Matter.framework. (#33012) People were being confused about when the MRP parameter overrides took effect. --- src/darwin/Framework/CHIP/MTRDeviceControllerFactory.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.h b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.h index b5ef1ffaa6cf32..62080b305ee635 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.h +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.h @@ -180,8 +180,13 @@ MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)) @end /** - * Set the Message Reliability Protocol parameters for all controllers. This - * allows control over retransmit delays to account for high-latency networks. + * Set the Message Reliability Protocol parameters for all controllers, + * including already-running ones. This allows control over retransmit delays + * to account for high-latency networks. + * + * Since MRP parameters are communicated to peers during session setup, existing + * sessions will not be affected when this function is called, but all sessions + * established after the call will be. * * Setting all arguments to nil will reset to the MRP parameters to their * default values. From bfdb5da2c4227f2f7cce13796106188941938988 Mon Sep 17 00:00:00 2001 From: Vatsal Ghelani <152916324+vatsalghelani-csa@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:51:54 -0400 Subject: [PATCH 04/38] Update test_metadata.py to use temporary files for test cases, for easier extensibility (#33008) * Modified the test script with the changes recommended * Removed unwanted import os * Removed path_under_test as it is using TempFile * Restyled by autopep8 * Restyled by isort --------- Co-authored-by: Restyled.io --- scripts/tests/py/test_metadata.py | 40 +++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/scripts/tests/py/test_metadata.py b/scripts/tests/py/test_metadata.py index 7c2594d08134c2..8707d483026bfb 100644 --- a/scripts/tests/py/test_metadata.py +++ b/scripts/tests/py/test_metadata.py @@ -1,4 +1,3 @@ -#!/usr/bin/python3 # Copyright (c) 2024 Project CHIP Authors # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,38 +12,39 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os +import tempfile import unittest from os import path +from typing import List from metadata import Metadata, MetadataReader class TestMetadataReader(unittest.TestCase): - path_under_test = path_under_test = path.join(path.dirname(__file__), "simple_run_args.txt") def setUp(self): - # build the reader object self.reader = MetadataReader(path.join(path.dirname(__file__), "env_test.yaml")) - with open(self.path_under_test, 'w', encoding='utf8') as test_file: - test_file.writelines(''' - # test-runner-runs: run1 - # test-runner-run/run1: app/all-clusters discriminator KVS storage-path commissioning-method discriminator passcode - ''') - - def test_parse_single_run(self): - expected_runs_metadata = [] + def assertMetadataParse(self, file_content: str, expected: List[Metadata]): + with tempfile.NamedTemporaryFile(mode='w', delete=False) as fp: + fp.write(file_content) + fp.close() + for e in expected: + e.py_script_path = fp.name + actual = self.reader.parse_script(fp.name) + self.assertEqual(actual, expected) - expected_runs_metadata.append(Metadata(app="out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app", - discriminator=1234, py_script_path=self.path_under_test, run="run1", passcode=20202021)) - - self.assertEqual(self.reader.parse_script(self.path_under_test), expected_runs_metadata) - - def tearDown(self): - if os.path.exists(self.path_under_test): - os.remove(self.path_under_test) + def test_parse_single_run(self): + self.assertMetadataParse(''' + # test-runner-runs: run1 + # test-runner-run/run1: app/all-clusters discriminator passcode + ''', + [ + Metadata(app="out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app", + discriminator=1234, run="run1", passcode=20202021) + ] + ) if __name__ == "__main__": From 6ba907e3b072824aa8b92fd97e3a08230fc0207d Mon Sep 17 00:00:00 2001 From: Philip Gregor <147669098+pgregorr-amazon@users.noreply.github.com> Date: Tue, 16 Apr 2024 13:12:15 -0700 Subject: [PATCH 05/38] Addressed comments by yunhanw-google related to error checking and logging and also fixed a tv-casting-app connection bug (#33010) --- .../casting/DiscoveryExampleFragment.java | 1 - .../cpp/core/CastingPlayerDiscovery-JNI.cpp | 2 +- src/platform/android/DnssdImpl.cpp | 19 +++++++++++++++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/DiscoveryExampleFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/DiscoveryExampleFragment.java index 46401af8909cd2..49076867a9cc75 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/DiscoveryExampleFragment.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/DiscoveryExampleFragment.java @@ -216,7 +216,6 @@ public void onResume() { public void onPause() { super.onPause(); Log.i(TAG, "onPause() called"); - stopDiscovery(); } /** Interface for notifying the host. */ diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayerDiscovery-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayerDiscovery-JNI.cpp index df79e60bc96cf2..26526dfdac8f07 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayerDiscovery-JNI.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayerDiscovery-JNI.cpp @@ -246,7 +246,7 @@ JNI_METHOD(jobject, removeCastingPlayerChangeListener)(JNIEnv * env, jobject, jo return support::convertMatterErrorFromCppToJava(CHIP_NO_ERROR); } - else if (DiscoveryDelegateImpl::GetInstance()->castingPlayerChangeListenerJavaObject.ObjectRef() == nullptr) + else if (!DiscoveryDelegateImpl::GetInstance()->castingPlayerChangeListenerJavaObject.HasValidObjectRef()) { ChipLogError( AppServer, diff --git a/src/platform/android/DnssdImpl.cpp b/src/platform/android/DnssdImpl.cpp index b272ed5a712414..290b3497ea11d4 100644 --- a/src/platform/android/DnssdImpl.cpp +++ b/src/platform/android/DnssdImpl.cpp @@ -191,6 +191,8 @@ CHIP_ERROR ChipDnssdBrowse(const char * type, DnssdServiceProtocol protocol, Ine std::string serviceType = GetFullTypeWithSubTypes(type, protocol); JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnError(env != nullptr, CHIP_JNI_ERROR_NO_ENV, + ChipLogError(Discovery, "Failed to GetEnvForCurrentThread for ChipDnssdBrowse")); UtfString jniServiceType(env, serviceType.c_str()); env->CallVoidMethod(sBrowserObject.ObjectRef(), sBrowseMethod, jniServiceType.jniValue(), reinterpret_cast(callback), @@ -204,7 +206,9 @@ CHIP_ERROR ChipDnssdBrowse(const char * type, DnssdServiceProtocol protocol, Ine return CHIP_JNI_ERROR_EXCEPTION_THROWN; } - auto sdCtx = chip::Platform::New(callback); + auto sdCtx = chip::Platform::New(callback); + VerifyOrReturnError(nullptr != sdCtx, CHIP_ERROR_NO_MEMORY, + ChipLogError(Discovery, "Failed allocate memory for BrowseContext in ChipDnssdBrowse")); *browseIdentifier = reinterpret_cast(sdCtx); return CHIP_NO_ERROR; @@ -212,14 +216,19 @@ CHIP_ERROR ChipDnssdBrowse(const char * type, DnssdServiceProtocol protocol, Ine CHIP_ERROR ChipDnssdStopBrowse(intptr_t browseIdentifier) { + VerifyOrReturnError(browseIdentifier != 0, CHIP_ERROR_INVALID_ARGUMENT, + ChipLogError(Discovery, "ChipDnssdStopBrowse Invalid argument browseIdentifier = 0")); VerifyOrReturnError(sBrowserObject.HasValidObjectRef() && sStopBrowseMethod != nullptr, CHIP_ERROR_INVALID_ARGUMENT); JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); - auto ctx = reinterpret_cast(browseIdentifier); + VerifyOrReturnError(env != nullptr, CHIP_JNI_ERROR_NO_ENV, + ChipLogError(Discovery, "Failed to GetEnvForCurrentThread for ChipDnssdStopBrowse")); + auto ctx = reinterpret_cast(browseIdentifier); env->CallVoidMethod(sBrowserObject.ObjectRef(), sStopBrowseMethod, reinterpret_cast(ctx->callback)); chip::Platform::Delete(ctx); + ctx = nullptr; if (env->ExceptionCheck()) { ChipLogError(Discovery, "Java exception in ChipDnssdStopBrowse"); @@ -339,6 +348,12 @@ void InitializeWithObjects(jobject resolverObject, jobject browserObject, jobjec env->ExceptionClear(); } + if (sStopBrowseMethod == nullptr) + { + ChipLogError(Discovery, "Failed to access Discover 'stopDiscover' method"); + env->ExceptionClear(); + } + if (sGetTextEntryKeysMethod == nullptr) { ChipLogError(Discovery, "Failed to access MdnsCallback 'getTextEntryKeys' method"); From 2002c0c3d6879057d1f14391f881172d11fa36a5 Mon Sep 17 00:00:00 2001 From: lpbeliveau-silabs <112982107+lpbeliveau-silabs@users.noreply.github.com> Date: Tue, 16 Apr 2024 16:31:45 -0400 Subject: [PATCH 06/38] Add missing [[maybe_unused]] annotations in unit tests (#33007) * Added [[maybe_unused]] to allow building unit test and Silabs apps without loggs * Added gating of SILABS_LOG_ENABLED on chip_logging --- .../platform/silabs/SoftwareFaultReports.cpp | 24 +++++++++---------- src/crypto/tests/TestChipCryptoPAL.cpp | 8 +++---- third_party/silabs/SiWx917_sdk.gni | 7 +++++- third_party/silabs/efr32_sdk.gni | 7 +++++- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/examples/platform/silabs/SoftwareFaultReports.cpp b/examples/platform/silabs/SoftwareFaultReports.cpp index 813a66037fbe84..fc7b6a9c5e3d76 100644 --- a/examples/platform/silabs/SoftwareFaultReports.cpp +++ b/examples/platform/silabs/SoftwareFaultReports.cpp @@ -89,18 +89,18 @@ void OnSoftwareFaultEventHandler(const char * faultRecordString) extern "C" __attribute__((used)) void debugHardfault(uint32_t * sp) { #if SILABS_LOG_ENABLED - uint32_t cfsr = SCB->CFSR; - uint32_t hfsr = SCB->HFSR; - uint32_t mmfar = SCB->MMFAR; - uint32_t bfar = SCB->BFAR; - uint32_t r0 = sp[0]; - uint32_t r1 = sp[1]; - uint32_t r2 = sp[2]; - uint32_t r3 = sp[3]; - uint32_t r12 = sp[4]; - uint32_t lr = sp[5]; - uint32_t pc = sp[6]; - uint32_t psr = sp[7]; + [[maybe_unused]] uint32_t cfsr = SCB->CFSR; + [[maybe_unused]] uint32_t hfsr = SCB->HFSR; + [[maybe_unused]] uint32_t mmfar = SCB->MMFAR; + [[maybe_unused]] uint32_t bfar = SCB->BFAR; + [[maybe_unused]] uint32_t r0 = sp[0]; + [[maybe_unused]] uint32_t r1 = sp[1]; + [[maybe_unused]] uint32_t r2 = sp[2]; + [[maybe_unused]] uint32_t r3 = sp[3]; + [[maybe_unused]] uint32_t r12 = sp[4]; + [[maybe_unused]] uint32_t lr = sp[5]; + [[maybe_unused]] uint32_t pc = sp[6]; + [[maybe_unused]] uint32_t psr = sp[7]; ChipLogError(NotSpecified, "HardFault:"); ChipLogError(NotSpecified, "SCB->CFSR 0x%08lx", cfsr); diff --git a/src/crypto/tests/TestChipCryptoPAL.cpp b/src/crypto/tests/TestChipCryptoPAL.cpp index 239d951910c80e..0cf3a877fcf828 100644 --- a/src/crypto/tests/TestChipCryptoPAL.cpp +++ b/src/crypto/tests/TestChipCryptoPAL.cpp @@ -640,7 +640,7 @@ static void TestRawIntegerToDerInvalidCases(nlTestSuite * inSuite, void * inCont { .input = bad_buffer_empty, .output = good_out_buffer, .expected_status = CHIP_ERROR_INVALID_ARGUMENT } }; - int case_idx = 0; + [[maybe_unused]] int case_idx = 0; for (const ErrorCase & v : error_cases) { CHIP_ERROR status = ConvertIntegerRawToDerWithoutTag(v.input, v.output); @@ -704,7 +704,7 @@ static void TestReadDerLengthValidCases(nlTestSuite * inSuite, void * inContext) { .input_buf = max_byte_length_large_buf, .expected_length = SIZE_MAX }, }; - int case_idx = 0; + [[maybe_unused]] int case_idx = 0; for (const SuccessCase & v : cases) { size_t output_length = SIZE_MAX - 1; @@ -768,7 +768,7 @@ static void TestReadDerLengthInvalidCases(nlTestSuite * inSuite, void * inContex { .input_buf = max_byte_length_large_insufficient_bytes_buf, .expected_status = CHIP_ERROR_BUFFER_TOO_SMALL }, }; - int case_idx = 0; + [[maybe_unused]] int case_idx = 0; for (const ErrorCase & v : error_cases) { size_t output_length = SIZE_MAX; @@ -2135,7 +2135,7 @@ static void TestX509_VerifyAttestationCertificateFormat(nlTestSuite * inSuite, v }; // clang-format on - int case_idx = 0; + [[maybe_unused]] int case_idx = 0; for (auto & testCase : sValidationTestCases) { ByteSpan cert = testCase.cert; diff --git a/third_party/silabs/SiWx917_sdk.gni b/third_party/silabs/SiWx917_sdk.gni index edcce8b50b5198..00e077d41da221 100644 --- a/third_party/silabs/SiWx917_sdk.gni +++ b/third_party/silabs/SiWx917_sdk.gni @@ -166,7 +166,6 @@ template("siwx917_sdk") { "MBEDTLS_CONFIG_FILE=\"siwx917-chip-mbedtls-config.h\"", "__STARTUP_CLEAR_BSS", "HARD_FAULT_LOG_ENABLE", - "SILABS_LOG_ENABLED=${silabs_log_enabled}", "SL_HEAP_SIZE=32768", "SL_WIFI=1", "CCP_SI917_BRINGUP=1", @@ -224,6 +223,12 @@ template("siwx917_sdk") { "configUSE_POSIX_ERRNO=1", ] + if (silabs_log_enabled && chip_logging) { + defines += [ "SILABS_LOG_ENABLED=1" ] + } else { + defines += [ "SILABS_LOG_ENABLED=0" ] + } + if (chip_build_libshell) { defines += [ "ENABLE_CHIP_SHELL" ] } diff --git a/third_party/silabs/efr32_sdk.gni b/third_party/silabs/efr32_sdk.gni index ce0b55b7ec803f..5bb2ff3079fcc5 100644 --- a/third_party/silabs/efr32_sdk.gni +++ b/third_party/silabs/efr32_sdk.gni @@ -283,7 +283,6 @@ template("efr32_sdk") { "__STARTUP_CLEAR_BSS", "HARD_FAULT_LOG_ENABLE", "CORTEXM3_EFM32_MICRO", - "SILABS_LOG_ENABLED=${silabs_log_enabled}", "NVM3_DEFAULT_NVM_SIZE=40960", "NVM3_DEFAULT_MAX_OBJECT_SIZE=4092", "KVS_MAX_ENTRIES=${kvs_max_entries}", @@ -308,6 +307,12 @@ template("efr32_sdk") { #"__STACK_SIZE=0", ] + if (silabs_log_enabled && chip_logging) { + defines += [ "SILABS_LOG_ENABLED=1" ] + } else { + defines += [ "SILABS_LOG_ENABLED=0" ] + } + # Temporary configuration to enable COAP specific configurations if (use_thread_coap_lib) { defines += [ "SL_USE_COAP_CONFIG=1" ] From 0a87dca5555b7b91840162c14dd9360bf1487045 Mon Sep 17 00:00:00 2001 From: Rob Bultman Date: Tue, 16 Apr 2024 17:06:39 -0400 Subject: [PATCH 07/38] Move add new device howto to docs (#32950) * Move add new device howto * Add words * Update docs/cluster_and_device_type_dev/how_to_add_new_dts_and_clusters.md Co-authored-by: Andrei Litvin * Remove word * Update docs/cluster_and_device_type_dev/how_to_add_new_dts_and_clusters.md Co-authored-by: Andrei Litvin * Update docs/cluster_and_device_type_dev/how_to_add_new_dts_and_clusters.md * Update docs/cluster_and_device_type_dev/how_to_add_new_dts_and_clusters.md * Remove word from wordlist --------- Co-authored-by: Andrei Litvin --- .github/.wordlist.txt | 2 + .../cluster_and_device_type_dev.md | 8 +- .../how_to_add_new_dts_and_clusters.md | 248 ++++++++++++++++++ docs/cluster_and_device_type_dev/index.md | 1 + 4 files changed, 254 insertions(+), 5 deletions(-) create mode 100644 docs/cluster_and_device_type_dev/how_to_add_new_dts_and_clusters.md diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt index b891546f940e53..f62685f566c407 100644 --- a/.github/.wordlist.txt +++ b/.github/.wordlist.txt @@ -369,6 +369,7 @@ DelayedActionTime delayedApplyActionTimeSec delayedQueryActionTimeSec delayQuery +deliverables demangle deployable depottools @@ -729,6 +730,7 @@ JLink JLinkExe JLinkRTTClient JN +jni jpg jre js diff --git a/docs/cluster_and_device_type_dev/cluster_and_device_type_dev.md b/docs/cluster_and_device_type_dev/cluster_and_device_type_dev.md index f4c1c5eb890e71..e016ea78b7b7bf 100644 --- a/docs/cluster_and_device_type_dev/cluster_and_device_type_dev.md +++ b/docs/cluster_and_device_type_dev/cluster_and_device_type_dev.md @@ -36,11 +36,9 @@ types in the SDK. - XML defines conformance - [src/app/zap-templates/zcl/data-model/chip/matter-devices.xml](https://github.com/project-chip/connectedhomeip/blob/master/src/app/zap-templates/zcl/data-model/chip/matter-devices.xml) -The following wiki page has a detailed description of how and where to add -cluster and device type definitions so they are picked up properly by ZAP/ember -and the SDK. - -[https://groups.csa-iot.org/wg/matter-tsg/wiki/howto-add-a-new-device-type](https://groups.csa-iot.org/wg/matter-tsg/wiki/howto-add-a-new-device-type) +See [How To Add New Device Types & Clusters](how_to_add_new_dts_and_clusters.md) +for a detailed description of how and where to add cluster and device type +definitions so they are picked up properly by ZAP/ember and the SDK. Note that the output should also be verified against the spec using the [.matter parser tools](https://project-chip.github.io/connectedhomeip-doc/guides/matter_idl_tooling.html). diff --git a/docs/cluster_and_device_type_dev/how_to_add_new_dts_and_clusters.md b/docs/cluster_and_device_type_dev/how_to_add_new_dts_and_clusters.md new file mode 100644 index 00000000000000..8d73a257c28996 --- /dev/null +++ b/docs/cluster_and_device_type_dev/how_to_add_new_dts_and_clusters.md @@ -0,0 +1,248 @@ +# How to Add New Device Types & Clusters + +This document outlines the process needed to add a new Matter device type and +related clusters. Obviously, the steps below assume that the related Matter +specifications were properly reviewed and approved. + +## Add the cluster definitions to the SDK + +The following steps should be followed to add new cluster definitions to the +SDK. + +**Add your new cluster definition to an appropriately-name file in this +directory:** +[src/app/zap-templates/zcl/data-model/chip](https://github.com/project-chip/connectedhomeip/tree/master/src/app/zap-templates/zcl/data-model/chip) + +**Add references to each new cluster definition to these files:** + +1. [.github/workflows/tests.yaml](https://github.com/project-chip/connectedhomeip/tree/master/.github/workflows/tests.yaml) +2. [scripts/rules.matterlint](https://github.com/project-chip/connectedhomeip/tree/master/scripts/rules.matterlint) +3. [src/app/zap-templates/zcl/data-model/all.xml](https://github.com/project-chip/connectedhomeip/tree/master/src/app/zap-templates/zcl/data-model/all.xml) +4. [src/app/zap-templates/zcl/zcl-with-test-extensions.json](https://github.com/project-chip/connectedhomeip/tree/master/src/app/zap-templates/zcl/zcl-with-test-extensions.json) +5. [src/app/zap-templates/zcl/zcl.json](https://github.com/project-chip/connectedhomeip/tree/master/src/app/zap-templates/zcl/zcl.json) +6. If it is a derived cluster, add a reference to the base cluster definition. + (e.g. in mode-base-cluster.xml you may need to add cluster codes - otherwise + you may get strange exceptions which aren't clear when running regen_all.py) + + > ``` + > + > + > + > + > ``` + +7. [src/controller/python/chip/clusters/\_\_init\_\_.py](https://github.com/project-chip/connectedhomeip/tree/master/src/controller/python/chip/clusters/__init__.py) + +**Enable your new cluster in the Python and Android clients** in +[src/controller/data_model/controller-clusters.zap](https://github.com/project-chip/connectedhomeip/blob/master/src/controller/data_model/controller-clusters.zap) + +You will need the ZAP tool to edit the ZAP file. + +- Unless you already have the tool installed, you can use one of the + [nightly builds](https://github.com/project-chip/zap/releases) +- [ZAP tool info](https://developers.home.google.com/matter/tools/zap) +- [ZAP tool repo](https://github.com/project-chip/zap) + +Use the ZAP GUI tool to edit the file above: + +1. From the command line, navigate to the directory containing + [controller-clusters.zap](https://github.com/project-chip/connectedhomeip/blob/master/src/controller/data_model) +2. Run zap: `../../../scripts/tools/zap/run_zaptool.sh controller-clusters.zap`. + Alternatively, run the zap tool and navigate to the zap file that you wish to + open, or run as + `./scripts/tools/zap/run_zaptool.sh src/controller/data_model/controller-clusters.zap`. +3. In the gui, select `Endpoint-1` from the left pane. +4. Open the cluster group, for example, `Appliances`. +5. Find the cluster to be enabled, e.g. `Dishwasher Control`. +6. In the Enable column, select "Client" from the drop-down box. +7. Click `File->Save` to save the configuration. +8. Close the GUI. + +**Add entries for your new cluster to +[BUILD.gn](c/src/controller/data_model/BUILD.gn)** in the outputs section of the +java-jni-generate bits. The entries should look like +"jni/YourClusterNameClient-InvokeSubscribeImpl.cpp" and +"jni/YourClusterNameClient-ReadImpl.cpp". + +**Add an entry to the ClientDirectories** section of +[src/app/zap_cluster_list.json](https://github.com/project-chip/connectedhomeip/blob/master/src/app/zap_cluster_list.json). + +**Update `chip-tool`** + +1. Regenerate all zap generated code using `./scripts/tools/zap_regen_all.py` +2. Rebuild chip-tool and it will have new cluster support: + `./scripts/examples/gn_build_example.sh examples/chip-tool SOME-PATH/` + +## Add the device type definition to the SDK + +1. Add the XML definition of the device to + [matter-devices.xml](https://github.com/project-chip/connectedhomeip/blob/master/src/app/zap-templates/zcl/data-model/chip/matter-devices.xml) +2. Implement the incoming command behaviors common to all applications. The + parsing of the payload from TLV to a C++ struct is done by code + auto-generated from the XML (see + [zap-generated](https://github.com/project-chip/connectedhomeip/blob/master/zzz_generated/app-common/app-common/zap-generated)) + The rest of the functionality must be manually implemented. Note: for the + auto-generated code run one of the following: + 1. for everything: `./scripts/tools/zap_regen_all.py` + 2. just for the app-common part: + `./scripts/tools/zap/generate.py -t src/app/common/templates/templates.json -o zzz_generated/app-common/app-common/zap-generated src/controller/data_model/controller-clusters.zap` +3. Implement the read/write/storage operations for the attributes of any type, + list, or struct which are not the global attributes present in all clusters. + For example, there's no need to implement CommandList, AttributeList etc. For + the attributes which are not list of struct type, the handling code is + implemented generically so most likely no work is needed for them. +4. Implement any attribute spec requirements that are common to all + applications. For example: code to enforce specific ranges, code for handling + the interactions between attributes etc. + +## Implement Code and Tests + +Implement the clusters, the example cluster server application and add the +related SDK tests. + +1. Implement new clusters here: + [src/app/clusters](https://github.com/project-chip/connectedhomeip/tree/master/src/app/clusters) +2. Implement tests here: + [src/app/tests/suites](https://github.com/project-chip/connectedhomeip/tree/master/src/app/tests/suites) +3. Implement the example cluster server application: + 1. The YAML tests will run against this server + 2. Depending on the clusters, there are two options: + 1. Enable them in the all-clusters-app and use that as the example + cluster server app. If choosing this option, consider adding an + example application that has just the relevant clusters enabled; this + part is a nice to have. + 2. If the clusters have complicated global application requirements + consider using a separate example app. see the door lock, bridge, TV, + OTA clusters. + 3. NOTES: If adding a new cluster derived from `mode-base` into + [examples/all-clusters-app/](https://github.com/project-chip/connectedhomeip/tree/master/examples/all-clusters-app/) + (Assume your new cluster is called `XZYMode`): + 1. Create your new `xyz-mode-cluster.xml` in + [src/app/zap-templates/zcl/data-model/chip](https://github.com/project-chip/connectedhomeip/tree/master/src/app/zap-templates/zcl/data-model/chip) + (as above). Note that this is a much cut down cluster definition + compared to normal clusters, since it derives from + [mode-base-cluster.xml](https://github.com/project-chip/connectedhomeip/tree/master/src/app/zap-templates/zcl/data-model/chip/mode-base-cluster.xml). + See + [dishwasher-mode-cluster.xml](https://github.com/project-chip/connectedhomeip/tree/master/src/app/zap-templates/zcl/data-model/chip/dishwasher-mode-cluster.xml) + as an example. Note you should review if you need the `StartUpMode` + and `OnMode` attributes based on the spec. + 2. Check that you have added your cluster code into + [mode-base-cluster.xml](https://github.com/project-chip/connectedhomeip/tree/master/src/app/zap-templates/zcl/data-model/chip/mode-base-cluster.xml) + 1. ` ` - replace + `XXXX` with your cluster ID + 2. ` ` - + replace `XXXX` with your cluster ID + 3. ` ` - + replace `XXXX` with your cluster ID + 3. Add your new Mode definitions in `xyz-mode.h`. In this header you + define the modes / labels and tags. See + [dishwasher-mode.h](https://github.com/project-chip/connectedhomeip/tree/master/examples/all-clusters-app/all-clusters-common/include/dishwasher-mode.h) + as an example. + 4. Add your new stub to instantiate the mode. See + [dishwasher-mode.cpp](https://github.com/project-chip/connectedhomeip/tree/master/examples/all-clusters-app/all-clusters-common/src/dishwasher-mode.cpp) + as an example. + 5. In + [examples/all-clusters-app/linux/main-common.cpp](https://github.com/project-chip/connectedhomeip/tree/master/examples/all-clusters-app/linux/main-common.cpp): + 1. Add `#include "xyz-mode.h"` + 2. In `ApplicationShutdown()`, add a call to your + `Clusters::XYZMode::Shutdown();`. + 6. In + [examples/all-clusters-app/linux/BUILD.gn](https://github.com/project-chip/connectedhomeip/tree/master/examples/all-clusters-app/linux/BUILD.gn), + add the `xyz-mode.cpp` file to the `sources` section. + 7. In + [src/app/common/templates/config-data.yaml](https://github.com/project-chip/connectedhomeip/blob/master/src/app/common/templates/config-data.yaml): + 1. Add a `::ModeTag` to the `EnumsNotUsedAsTypeInXML` + section. + 2. Add an `XYZ Mode` entry to the + `CommandHandlerInterfaceOnlyClusters` section. + 8. In + [src/app/util/util.cpp](https://github.com/project-chip/connectedhomeip/blob/master/src/app/util/util.cpp), + in the `// Cluster Init Functions...` section, add a void + `MatterXYZModePluginServerInitCallback() {}` definition. + 9. In + [src/app/zap-templates/zcl/zcl-with-test-extensions.json](https://github.com/project-chip/connectedhomeip/blob/master/src/app/zap-templates/zcl/zcl-with-test-extensions.json): + 1. Add the `xyz-mode-cluster.xml` to the `xmlFile` list + 2. In the `attributeAccessInterfaceAttributes` entry, add your new + entry + `"XYZ Mode": [ "SupportedModes", "CurrentMode", "FeatureMap" ]` - + this will mean ZAP won't generate code to handle these attributes + 10. In + [src/app/zap_cluster_list.json](https://github.com/project-chip/connectedhomeip/blob/master/src/app/zap_cluster_list.json): + 1. Add your `XYZ_MODE_CLUSTER: []` to `ClientDirectories: { }` + object + 2. Add your `"XYZ_MODE_CLUSTER": ["mode-base-server"]` to + `"ServerDirectories": { }` + 4. The code under + [src/app/tests/suites/certification](https://github.com/project-chip/connectedhomeip/blob/master/src/app/tests/suites/certification) + for YAML or + [src/python_testing](https://github.com/project-chip/connectedhomeip/tree/master/src/python_testing) + for Python should ideally implement the test plan (section 4 below). + 5. A test under + [src/app/tests/suites](https://github.com/project-chip/connectedhomeip/blob/master/src/app/tests/suites) + (not certification) can test anything, in addition to, or preceding the + official YAML representing the official test plan. +4. Add the test plan, using the templates below: + + 1. [cluster_test_plan_template.adoc](https://github.com/CHIP-Specifications/chip-test-plans/blob/master/src/template/cluster_test_plan_template.adoc) + 2. [test_plan_template.adoc](https://github.com/CHIP-Specifications/chip-test-plans/blob/master/src/template/test_plan_template.adoc) + + Also, ask to be added to the private `csg-tt-test-plans` Slack channel. + +5. Note: the CHIP-Tool reference client is generated from XML +6. If applicable, add tests: + 1. For relatively simple tests, add YAML tests here: + [src/app/tests/suites/certification](https://github.com/project-chip/connectedhomeip/blob/master/src/app/tests/suites/certification). + Remember to update this file: + [src/app/tests/suites/certification/PICS.yaml](https://github.com/project-chip/connectedhomeip/blob/master/src/app/tests/suites/certification/PICS.yaml) + 2. For more complex tests, add Python tests here: + [src/python_testing](https://github.com/project-chip/connectedhomeip/tree/master/src/python_testing) + 3. To add tests to CI, if applicable: + 1. Add the Python tests here: + [.github/workflows/tests.yaml](https://github.com/project-chip/connectedhomeip/tree/master/.github/workflows/tests.yaml). + Remember to provide all arguments required for each Python script, + such as the endpoint PIXIT. + 2. Add the YAML tests by editing this file: + [src/app/tests/suites/ciTests.json](https://github.com/project-chip/connectedhomeip/tree/master/src/app/tests/suites/ciTests.json) + 1. Create a section ("MyDevice") which lists all YAML tests for your + device + 2. Add the section's name to the list of items named "collection" + 3. Do a ZAP code regen: `./scripts/tools/zap_regen_all.py`. +7. Add the device type spec to the test plan tools: + + 1. [tools/device_type_requirements](https://github.com/CHIP-Specifications/chip-test-plans/tree/master/tools/device_type_requirements) + 2. The file above is used by + [src/app/tests/suites/certification/Test_TC_DESC_2_1.yaml](https://github.com/project-chip/connectedhomeip/blob/master/src/app/tests/suites/certification/Test_TC_DESC_2_1.yaml) + + Note: the plan is to make the DM tools generate the device type requirements + data based on the spec, so the above will become obsolete. + +8. Add the device type to Chef: + [examples/chef/devices](https://github.com/project-chip/connectedhomeip/tree/master/examples/chef/devices) + +## Q & A + +**Q1**: What kind of devices can be used for the test events? Can one of them be +the example cluster server app running on a RasPI? Do the independent +realizations need to run on vendor hardware or can they also run on generic +hardware, such as ESP32 or RasPI? + +**A1**: one realization can be the test harness + the all clusters example app + +RasPI. the two independent realizations need to run on target hardware, which +may be mock-ups, prototypes etc + +**Q2**: How can the Chef tool be used for any of the deliverables above? + +**A2**: TBD + +**Q3**: What is the process for using the Zap tool in order to auto-generate +code and to commit the results to the git repo? + +**A3**: Search for zap_regen above. Add all the changed files to the repo after. + +**Q4**: Where can the older cluster definitions be found? + +**A4**: src/app/zap-templates/zcl/data-model/silabs/general.xml + +**Q5**: Where can I find ZAP documentation? + +**A5**: https://github.com/project-chip/zap/blob/master/README.md diff --git a/docs/cluster_and_device_type_dev/index.md b/docs/cluster_and_device_type_dev/index.md index 6d5cf71edfef1c..9159cdb3e511d5 100644 --- a/docs/cluster_and_device_type_dev/index.md +++ b/docs/cluster_and_device_type_dev/index.md @@ -13,3 +13,4 @@ types in the SDK. ``` - [Cluster and device type development](./cluster_and_device_type_dev.md) +- [How To Add New Device Types & Clusters](how_to_add_new_dts_and_clusters.md) From b0d2b8717a77714c0272ac97324fe8d4f1d76ecb Mon Sep 17 00:00:00 2001 From: Nivi Sarkar <55898241+nivi-apple@users.noreply.github.com> Date: Tue, 16 Apr 2024 16:13:50 -0700 Subject: [PATCH 08/38] =?UTF-8?q?Remove=20expected=20value=20check=20from?= =?UTF-8?q?=20the=20=5FgetAttributesToReportWithReport=E2=80=A6=20(#33014)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove expected value check from the _getAttributesToReportWithReportedValues - Report the attribute only when the value has changed from the read cache. - Fix logging for use cases where we didn't report since there was an expected value or the attribute value didn't change from the read cache value. * Fix the comment for when expected value exists * readCacheValueChanged and expectedValue are not mutually exclusive. Fix the check * Apply suggestions from code review Co-authored-by: Boris Zbarsky * Restyled by clang-format --------- Co-authored-by: Boris Zbarsky Co-authored-by: Restyled.io --- src/darwin/Framework/CHIP/MTRDevice.mm | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index 7c73524c963635..aa63efe1033633 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -2342,25 +2342,26 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray Date: Tue, 16 Apr 2024 19:39:18 -0400 Subject: [PATCH 09/38] change pdTrue(FreeRTOS) to osOS (CMSISOS) (#33017) --- examples/platform/silabs/efr32/uart.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/platform/silabs/efr32/uart.cpp b/examples/platform/silabs/efr32/uart.cpp index 2b29cca0db0a92..9a626e10a2159d 100644 --- a/examples/platform/silabs/efr32/uart.cpp +++ b/examples/platform/silabs/efr32/uart.cpp @@ -442,7 +442,7 @@ void uartMainLoop(void * args) { osStatus_t eventReceived = osMessageQueueGet(sUartTxQueue, &workBuffer, nullptr, osWaitForever); - while (eventReceived == pdTRUE) + while (eventReceived == osOK) { uartSendBytes(workBuffer.data, workBuffer.length); eventReceived = osMessageQueueGet(sUartTxQueue, &workBuffer, nullptr, 0); From 35ced57c3828238d2d125cd3116a7b0e0410e286 Mon Sep 17 00:00:00 2001 From: mkardous-silabs <84793247+mkardous-silabs@users.noreply.github.com> Date: Tue, 16 Apr 2024 19:09:33 -0500 Subject: [PATCH 10/38] Add test event trigger to ICD apps (#33018) --- examples/light-switch-app/silabs/openthread.gni | 2 ++ examples/lock-app/silabs/build_for_wifi_args.gni | 2 ++ examples/lock-app/silabs/openthread.gni | 2 ++ scripts/examples/gn_silabs_example.sh | 2 +- 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/light-switch-app/silabs/openthread.gni b/examples/light-switch-app/silabs/openthread.gni index 30c7efbde3d623..8dd097480220b0 100644 --- a/examples/light-switch-app/silabs/openthread.gni +++ b/examples/light-switch-app/silabs/openthread.gni @@ -25,6 +25,8 @@ chip_enable_openthread = true openthread_external_platform = "${chip_root}/third_party/openthread/platforms/efr32:libopenthread-efr32" +sl_enable_test_event_trigger = true + # ICD Default configurations chip_enable_icd_server = true chip_subscription_timeout_resumption = false diff --git a/examples/lock-app/silabs/build_for_wifi_args.gni b/examples/lock-app/silabs/build_for_wifi_args.gni index 84596e9258fbcc..3947f885b21f4f 100644 --- a/examples/lock-app/silabs/build_for_wifi_args.gni +++ b/examples/lock-app/silabs/build_for_wifi_args.gni @@ -21,6 +21,8 @@ import("${chip_root}/src/platform/silabs/wifi_args.gni") chip_enable_ota_requestor = true app_data_model = "${chip_root}/examples/lock-app/lock-common" +sl_enable_test_event_trigger = true + # ICD Default configurations chip_enable_icd_server = true chip_subscription_timeout_resumption = false diff --git a/examples/lock-app/silabs/openthread.gni b/examples/lock-app/silabs/openthread.gni index 8d1c75d267238c..3423049a9a8f79 100644 --- a/examples/lock-app/silabs/openthread.gni +++ b/examples/lock-app/silabs/openthread.gni @@ -25,6 +25,8 @@ chip_enable_openthread = true openthread_external_platform = "${chip_root}/third_party/openthread/platforms/efr32:libopenthread-efr32" +sl_enable_test_event_trigger = true + # ICD Default configurations chip_enable_icd_server = true chip_subscription_timeout_resumption = false diff --git a/scripts/examples/gn_silabs_example.sh b/scripts/examples/gn_silabs_example.sh index 951fdf5bb97370..3877b5c98ad40a 100755 --- a/scripts/examples/gn_silabs_example.sh +++ b/scripts/examples/gn_silabs_example.sh @@ -193,7 +193,7 @@ else shift ;; --icd) - optArgs+="chip_enable_icd_server=true chip_openthread_ftd=false " + optArgs+="chip_enable_icd_server=true chip_openthread_ftd=false sl_enable_test_event_trigger=true" shift ;; --low-power) From 0b0d1162c0e1311186bac61bc0c184a9cb0371fb Mon Sep 17 00:00:00 2001 From: Kamil Kasperczyk <66371704+kkasperczyk-no@users.noreply.github.com> Date: Wed, 17 Apr 2024 08:09:30 +0200 Subject: [PATCH 11/38] [zephyr] Added Bluetooth LE Extended Advertisement option (#33005) This commit implements platform solution for a Bluetooth LE extended advertising. Additionally, for the CommissioningWindowManager types were changed from Seconds16 to Seconds32, because the current implementation overflows for 48h duration. Co-authored-by: Patryk Lipinski --- config/zephyr/Kconfig | 18 +++++ src/app/server/CommissioningWindowManager.cpp | 8 +-- src/app/server/CommissioningWindowManager.h | 22 +++--- src/app/tests/TestCommissionManager.cpp | 2 +- src/include/platform/ConnectivityManager.h | 5 +- src/platform/Zephyr/BLEManagerImpl.cpp | 72 +++++++++++++++---- src/platform/Zephyr/BLEManagerImpl.h | 4 +- .../Zephyr/CHIPDevicePlatformConfig.h | 6 ++ .../nrfconnect/CHIPDevicePlatformConfig.h | 6 ++ 9 files changed, 112 insertions(+), 31 deletions(-) diff --git a/config/zephyr/Kconfig b/config/zephyr/Kconfig index ad36a663b5fd1f..bdfa709592adff 100644 --- a/config/zephyr/Kconfig +++ b/config/zephyr/Kconfig @@ -533,4 +533,22 @@ config CHIP_OTA_IMAGE_EXTRA_ARGS endif +config CHIP_BLE_EXT_ADVERTISING + bool "Bluetooth LE extended advertising" + help + Enable Bluetooth LE extended advertising, which allows the device to advertise + Matter service over Bluetooth LE for a period of time longer than 15 minutes. + If this config is true, + CHIP_DEVICE_CONFIG_DISCOVERY_TIMEOUT_SECS define can be set up to 172800 seconds (48h). + +config CHIP_BLE_ADVERTISING_DURATION + int "Bluetooth LE advertising duration in minutes" + range 15 2880 if CHIP_BLE_EXT_ADVERTISING + range 0 15 + default 15 + help + Specify how long the device will advertise Matter service over Bluetooth LE in minutes. + If CHIP_BLE_EXT_ADVERTISING is set to false, the maximum duration time is 15 minutes, + else the maximum duration time can be extended to 2880 minutes (48h). + endif diff --git a/src/app/server/CommissioningWindowManager.cpp b/src/app/server/CommissioningWindowManager.cpp index a205d093c27d00..f11261ad9c444d 100644 --- a/src/app/server/CommissioningWindowManager.cpp +++ b/src/app/server/CommissioningWindowManager.cpp @@ -228,7 +228,7 @@ void CommissioningWindowManager::OnSessionEstablished(const SessionHandle & sess } } -CHIP_ERROR CommissioningWindowManager::OpenCommissioningWindow(Seconds16 commissioningTimeout) +CHIP_ERROR CommissioningWindowManager::OpenCommissioningWindow(Seconds32 commissioningTimeout) { VerifyOrReturnError(commissioningTimeout <= MaxCommissioningTimeout() && commissioningTimeout >= MinCommissioningTimeout(), CHIP_ERROR_INVALID_ARGUMENT); @@ -288,7 +288,7 @@ CHIP_ERROR CommissioningWindowManager::AdvertiseAndListenForPASE() return CHIP_NO_ERROR; } -CHIP_ERROR CommissioningWindowManager::OpenBasicCommissioningWindow(Seconds16 commissioningTimeout, +CHIP_ERROR CommissioningWindowManager::OpenBasicCommissioningWindow(Seconds32 commissioningTimeout, CommissioningWindowAdvertisement advertisementMode) { RestoreDiscriminator(); @@ -316,7 +316,7 @@ CHIP_ERROR CommissioningWindowManager::OpenBasicCommissioningWindow(Seconds16 co CHIP_ERROR CommissioningWindowManager::OpenBasicCommissioningWindowForAdministratorCommissioningCluster( - System::Clock::Seconds16 commissioningTimeout, FabricIndex fabricIndex, VendorId vendorId) + System::Clock::Seconds32 commissioningTimeout, FabricIndex fabricIndex, VendorId vendorId) { ReturnErrorOnFailure(OpenBasicCommissioningWindow(commissioningTimeout, CommissioningWindowAdvertisement::kDnssdOnly)); @@ -326,7 +326,7 @@ CommissioningWindowManager::OpenBasicCommissioningWindowForAdministratorCommissi return CHIP_NO_ERROR; } -CHIP_ERROR CommissioningWindowManager::OpenEnhancedCommissioningWindow(Seconds16 commissioningTimeout, uint16_t discriminator, +CHIP_ERROR CommissioningWindowManager::OpenEnhancedCommissioningWindow(Seconds32 commissioningTimeout, uint16_t discriminator, Spake2pVerifier & verifier, uint32_t iterations, ByteSpan salt, FabricIndex fabricIndex, VendorId vendorId) { diff --git a/src/app/server/CommissioningWindowManager.h b/src/app/server/CommissioningWindowManager.h index 6b4e1efded76f4..51efb44b19376e 100644 --- a/src/app/server/CommissioningWindowManager.h +++ b/src/app/server/CommissioningWindowManager.h @@ -58,21 +58,21 @@ class CommissioningWindowManager : public Messaging::UnsolicitedMessageHandler, return CHIP_NO_ERROR; } - static constexpr System::Clock::Seconds16 MaxCommissioningTimeout() + static constexpr System::Clock::Seconds32 MaxCommissioningTimeout() { #if CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING // Specification section 2.3.1 - Extended Announcement Duration up to 48h - return System::Clock::Seconds16(60 * 60 * 48); + return System::Clock::Seconds32(60 * 60 * 48); #else // Specification section 5.4.2.3. Announcement Duration says 15 minutes. - return System::Clock::Seconds16(15 * 60); + return System::Clock::Seconds32(15 * 60); #endif } - System::Clock::Seconds16 MinCommissioningTimeout() const + System::Clock::Seconds32 MinCommissioningTimeout() const { // Specification section 5.4.2.3. Announcement Duration says 3 minutes. - return mMinCommissioningTimeoutOverride.ValueOr(System::Clock::Seconds16(3 * 60)); + return mMinCommissioningTimeoutOverride.ValueOr(System::Clock::Seconds32(3 * 60)); } void SetAppDelegate(AppDelegate * delegate) { mAppDelegate = delegate; } @@ -82,7 +82,7 @@ class CommissioningWindowManager : public Messaging::UnsolicitedMessageHandler, */ CHIP_ERROR OpenBasicCommissioningWindow( - System::Clock::Seconds16 commissioningTimeout = System::Clock::Seconds16(CHIP_DEVICE_CONFIG_DISCOVERY_TIMEOUT_SECS), + System::Clock::Seconds32 commissioningTimeout = System::Clock::Seconds32(CHIP_DEVICE_CONFIG_DISCOVERY_TIMEOUT_SECS), CommissioningWindowAdvertisement advertisementMode = chip::CommissioningWindowAdvertisement::kAllSupported); /** @@ -90,10 +90,10 @@ class CommissioningWindowManager : public Messaging::UnsolicitedMessageHandler, * the Administrator Commmissioning cluster implementation. */ CHIP_ERROR - OpenBasicCommissioningWindowForAdministratorCommissioningCluster(System::Clock::Seconds16 commissioningTimeout, + OpenBasicCommissioningWindowForAdministratorCommissioningCluster(System::Clock::Seconds32 commissioningTimeout, FabricIndex fabricIndex, VendorId vendorId); - CHIP_ERROR OpenEnhancedCommissioningWindow(System::Clock::Seconds16 commissioningTimeout, uint16_t discriminator, + CHIP_ERROR OpenEnhancedCommissioningWindow(System::Clock::Seconds32 commissioningTimeout, uint16_t discriminator, Crypto::Spake2pVerifier & verifier, uint32_t iterations, chip::ByteSpan salt, FabricIndex fabricIndex, VendorId vendorId); @@ -128,7 +128,7 @@ class CommissioningWindowManager : public Messaging::UnsolicitedMessageHandler, // For tests only, allow overriding the spec-defined minimum value of the // commissioning window timeout. - void OverrideMinCommissioningTimeout(System::Clock::Seconds16 timeout) { mMinCommissioningTimeoutOverride.SetValue(timeout); } + void OverrideMinCommissioningTimeout(System::Clock::Seconds32 timeout) { mMinCommissioningTimeoutOverride.SetValue(timeout); } private: //////////// SessionDelegate Implementation /////////////// @@ -146,7 +146,7 @@ class CommissioningWindowManager : public Messaging::UnsolicitedMessageHandler, // Start a timer that will call HandleCommissioningWindowTimeout, and then // start advertising and listen for PASE. - CHIP_ERROR OpenCommissioningWindow(System::Clock::Seconds16 commissioningTimeout); + CHIP_ERROR OpenCommissioningWindow(System::Clock::Seconds32 commissioningTimeout); // Start advertising and listening for PASE connections. Should only be // called when a commissioning window timeout timer is running. @@ -219,7 +219,7 @@ class CommissioningWindowManager : public Messaging::UnsolicitedMessageHandler, // For tests only, so that we can test the commissioning window timeout // without having to wait 3 minutes. - Optional mMinCommissioningTimeoutOverride; + Optional mMinCommissioningTimeoutOverride; // The PASE session we are using, so we can handle CloseSession properly. SessionHolderWithDelegate mPASESession; diff --git a/src/app/tests/TestCommissionManager.cpp b/src/app/tests/TestCommissionManager.cpp index c314421d4e3a9a..0076fd6a55718f 100644 --- a/src/app/tests/TestCommissionManager.cpp +++ b/src/app/tests/TestCommissionManager.cpp @@ -239,7 +239,7 @@ void CheckCommissioningWindowManagerWindowTimeoutTask(intptr_t context) NL_TEST_ASSERT(suite, !sAdminVendorIdDirty); CommissioningWindowManager & commissionMgr = Server::GetInstance().GetCommissioningWindowManager(); - constexpr auto kTimeoutSeconds = chip::System::Clock::Seconds16(1); + constexpr auto kTimeoutSeconds = chip::System::Clock::Seconds32(1); constexpr uint16_t kTimeoutMs = 1000; constexpr unsigned kSleepPadding = 100; commissionMgr.OverrideMinCommissioningTimeout(kTimeoutSeconds); diff --git a/src/include/platform/ConnectivityManager.h b/src/include/platform/ConnectivityManager.h index 90bab2a5ba5e76..5699caf1a1ef5b 100644 --- a/src/include/platform/ConnectivityManager.h +++ b/src/include/platform/ConnectivityManager.h @@ -138,8 +138,9 @@ class ConnectivityManager enum BLEAdvertisingMode { - kFastAdvertising = 0, - kSlowAdvertising = 1, + kFastAdvertising = 0, + kSlowAdvertising = 1, + kExtendedAdvertising = 2, }; enum class SEDIntervalMode diff --git a/src/platform/Zephyr/BLEManagerImpl.cpp b/src/platform/Zephyr/BLEManagerImpl.cpp index 54cf69dc5e50fc..d4e83332452a06 100644 --- a/src/platform/Zephyr/BLEManagerImpl.cpp +++ b/src/platform/Zephyr/BLEManagerImpl.cpp @@ -252,18 +252,39 @@ inline CHIP_ERROR BLEManagerImpl::PrepareAdvertisingRequest() Encoding::LittleEndian::Put16(serviceData.uuid, UUID16_CHIPoBLEService.val); ReturnErrorOnFailure(ConfigurationMgr().GetBLEDeviceIdentificationInfo(serviceData.deviceIdInfo)); +#if CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING + if (mFlags.Has(Flags::kExtendedAdvertisingEnabled)) + { + serviceData.deviceIdInfo.SetVendorId(DEVICE_HANDLE_NULL); + serviceData.deviceIdInfo.SetProductId(DEVICE_HANDLE_NULL); + serviceData.deviceIdInfo.SetExtendedAnnouncementFlag(true); + } +#endif + advertisingData[0] = BT_DATA(BT_DATA_FLAGS, &kAdvertisingFlags, sizeof(kAdvertisingFlags)); advertisingData[1] = BT_DATA(BT_DATA_SVC_DATA16, &serviceData, sizeof(serviceData)); scanResponseData[0] = BT_DATA(BT_DATA_NAME_COMPLETE, name, nameSize); - mAdvertisingRequest.priority = CHIP_DEVICE_BLE_ADVERTISING_PRIORITY; - mAdvertisingRequest.options = kAdvertisingOptions; - mAdvertisingRequest.minInterval = mFlags.Has(Flags::kFastAdvertisingEnabled) - ? CHIP_DEVICE_CONFIG_BLE_FAST_ADVERTISING_INTERVAL_MIN - : CHIP_DEVICE_CONFIG_BLE_SLOW_ADVERTISING_INTERVAL_MIN; - mAdvertisingRequest.maxInterval = mFlags.Has(Flags::kFastAdvertisingEnabled) - ? CHIP_DEVICE_CONFIG_BLE_FAST_ADVERTISING_INTERVAL_MAX - : CHIP_DEVICE_CONFIG_BLE_SLOW_ADVERTISING_INTERVAL_MAX; + mAdvertisingRequest.priority = CHIP_DEVICE_BLE_ADVERTISING_PRIORITY; + mAdvertisingRequest.options = kAdvertisingOptions; + + if (mFlags.Has(Flags::kFastAdvertisingEnabled)) + { + mAdvertisingRequest.minInterval = CHIP_DEVICE_CONFIG_BLE_FAST_ADVERTISING_INTERVAL_MIN; + mAdvertisingRequest.maxInterval = CHIP_DEVICE_CONFIG_BLE_FAST_ADVERTISING_INTERVAL_MAX; + } +#if CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING + else if (mFlags.Has(Flags::kExtendedAdvertisingEnabled)) + { + mAdvertisingRequest.minInterval = CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING_INTERVAL_MIN; + mAdvertisingRequest.maxInterval = CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING_INTERVAL_MAX; + } +#endif + else + { + mAdvertisingRequest.minInterval = CHIP_DEVICE_CONFIG_BLE_SLOW_ADVERTISING_INTERVAL_MIN; + mAdvertisingRequest.maxInterval = CHIP_DEVICE_CONFIG_BLE_SLOW_ADVERTISING_INTERVAL_MAX; + } mAdvertisingRequest.advertisingData = Span(advertisingData); mAdvertisingRequest.scanResponseData = nameSize ? Span(scanResponseData) : Span{}; @@ -322,10 +343,17 @@ CHIP_ERROR BLEManagerImpl::StartAdvertising() if (mFlags.Has(Flags::kFastAdvertisingEnabled)) { - // Start timer to change advertising interval. + // Start timer to change advertising interval from fast to slow. DeviceLayer::SystemLayer().StartTimer( System::Clock::Milliseconds32(CHIP_DEVICE_CONFIG_BLE_ADVERTISING_INTERVAL_CHANGE_TIME), - HandleBLEAdvertisementIntervalChange, this); + HandleSlowBLEAdvertisementInterval, this); + +#if CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING + // Start timer to schedule start of the extended advertising + DeviceLayer::SystemLayer().StartTimer( + System::Clock::Milliseconds32(CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING_INTERVAL_CHANGE_TIME_MS), + HandleExtendedBLEAdvertisementInterval, this); +#endif } } @@ -342,6 +370,10 @@ CHIP_ERROR BLEManagerImpl::StopAdvertising() mFlags.Clear(Flags::kAdvertising); mFlags.Set(Flags::kFastAdvertisingEnabled, true); +#if CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING + mFlags.Clear(Flags::kExtendedAdvertisingEnabled); +#endif + ChipLogProgress(DeviceLayer, "CHIPoBLE advertising stopped"); // Post a CHIPoBLEAdvertisingChange(Stopped) event. @@ -353,7 +385,8 @@ CHIP_ERROR BLEManagerImpl::StopAdvertising() } // Cancel timer event changing CHIPoBLE advertisement interval - DeviceLayer::SystemLayer().CancelTimer(HandleBLEAdvertisementIntervalChange, this); + DeviceLayer::SystemLayer().CancelTimer(HandleSlowBLEAdvertisementInterval, this); + DeviceLayer::SystemLayer().CancelTimer(HandleExtendedBLEAdvertisementInterval, this); } return CHIP_NO_ERROR; @@ -366,6 +399,9 @@ CHIP_ERROR BLEManagerImpl::_SetAdvertisingEnabled(bool val) ChipLogDetail(DeviceLayer, "CHIPoBLE advertising set to %s", val ? "on" : "off"); mFlags.Set(Flags::kAdvertisingEnabled, val); + // Ensure that each enabling/disabling of the standard advertising clears + // the extended mode flag. + mFlags.Set(Flags::kExtendedAdvertisingEnabled, false); PlatformMgr().ScheduleWork(DriveBLEState, 0); } @@ -378,8 +414,14 @@ CHIP_ERROR BLEManagerImpl::_SetAdvertisingMode(BLEAdvertisingMode mode) { case BLEAdvertisingMode::kFastAdvertising: mFlags.Set(Flags::kFastAdvertisingEnabled, true); + mFlags.Set(Flags::kExtendedAdvertisingEnabled, false); break; case BLEAdvertisingMode::kSlowAdvertising: + mFlags.Set(Flags::kFastAdvertisingEnabled, false); + mFlags.Set(Flags::kExtendedAdvertisingEnabled, false); + break; + case BLEAdvertisingMode::kExtendedAdvertising: + mFlags.Set(Flags::kExtendedAdvertisingEnabled, true); mFlags.Set(Flags::kFastAdvertisingEnabled, false); break; default: @@ -570,12 +612,18 @@ CHIP_ERROR BLEManagerImpl::PrepareC3CharData() } #endif -void BLEManagerImpl::HandleBLEAdvertisementIntervalChange(System::Layer * layer, void * param) +void BLEManagerImpl::HandleSlowBLEAdvertisementInterval(System::Layer * layer, void * param) { BLEMgr().SetAdvertisingMode(BLEAdvertisingMode::kSlowAdvertising); ChipLogProgress(DeviceLayer, "CHIPoBLE advertising mode changed to slow"); } +void BLEManagerImpl::HandleExtendedBLEAdvertisementInterval(System::Layer * layer, void * param) +{ + BLEMgr().SetAdvertisingMode(BLEAdvertisingMode::kExtendedAdvertising); + ChipLogProgress(DeviceLayer, "CHIPoBLE advertising mode changed to extended"); +} + void BLEManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event) { CHIP_ERROR err = CHIP_NO_ERROR; diff --git a/src/platform/Zephyr/BLEManagerImpl.h b/src/platform/Zephyr/BLEManagerImpl.h index 7db469f4a5df93..4fb3352a01ee2b 100644 --- a/src/platform/Zephyr/BLEManagerImpl.h +++ b/src/platform/Zephyr/BLEManagerImpl.h @@ -91,6 +91,7 @@ class BLEManagerImpl final : public BLEManager, private BleLayer, private BlePla kAdvertisingRefreshNeeded = 0x0010, /**< The advertising state/configuration has changed, but the SoftDevice has yet to be updated. */ kChipoBleGattServiceRegister = 0x0020, /**< The system has currently CHIPoBLE GATT service registered. */ + kExtendedAdvertisingEnabled = 0x0040, /**< The application has enabled extended advertising. */ }; struct ServiceData; @@ -129,7 +130,8 @@ class BLEManagerImpl final : public BLEManager, private BleLayer, private BlePla static void HandleTXIndicated(bt_conn * conn, bt_gatt_indicate_params * attr, uint8_t err); static void HandleConnect(bt_conn * conn, uint8_t err); static void HandleDisconnect(bt_conn * conn, uint8_t reason); - static void HandleBLEAdvertisementIntervalChange(System::Layer * layer, void * param); + static void HandleSlowBLEAdvertisementInterval(System::Layer * layer, void * param); + static void HandleExtendedBLEAdvertisementInterval(System::Layer * layer, void * param); // ===== Members for internal use by the following friends. diff --git a/src/platform/Zephyr/CHIPDevicePlatformConfig.h b/src/platform/Zephyr/CHIPDevicePlatformConfig.h index 3296f3aa8f1c55..e7622f60ddf9fc 100644 --- a/src/platform/Zephyr/CHIPDevicePlatformConfig.h +++ b/src/platform/Zephyr/CHIPDevicePlatformConfig.h @@ -130,3 +130,9 @@ #ifdef CONFIG_CHIP_EXTENDED_DISCOVERY #define CHIP_DEVICE_CONFIG_ENABLE_EXTENDED_DISCOVERY 1 #endif // CONFIG_CHIP_EXTENDED_DISCOVERY + +#ifdef CONFIG_CHIP_BLE_EXT_ADVERTISING +#define CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING 1 +#endif // CONFIG_CHIP_BLE_EXT_ADVERTISING + +#define CHIP_DEVICE_CONFIG_DISCOVERY_TIMEOUT_SECS (CONFIG_CHIP_BLE_ADVERTISING_DURATION * 60) diff --git a/src/platform/nrfconnect/CHIPDevicePlatformConfig.h b/src/platform/nrfconnect/CHIPDevicePlatformConfig.h index 7461d3650d86a6..f9e445f35fb3d4 100644 --- a/src/platform/nrfconnect/CHIPDevicePlatformConfig.h +++ b/src/platform/nrfconnect/CHIPDevicePlatformConfig.h @@ -212,6 +212,12 @@ #define CHIP_DEVICE_CONFIG_ENABLE_EXTENDED_DISCOVERY 1 #endif // CONFIG_CHIP_EXTENDED_DISCOVERY +#ifdef CONFIG_CHIP_BLE_EXT_ADVERTISING +#define CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING 1 +#endif // CONFIG_CHIP_BLE_EXT_ADVERTISING + +#define CHIP_DEVICE_CONFIG_DISCOVERY_TIMEOUT_SECS (CONFIG_CHIP_BLE_ADVERTISING_DURATION * 60) + #ifndef CHIP_DEVICE_CONFIG_ROTATING_DEVICE_ID_UNIQUE_ID_LENGTH #ifdef CONFIG_CHIP_FACTORY_DATA // UID will be copied from the externally programmed factory data, so we don't know the actual length and we need to assume some max From 47c6d468de49bb648ba95a90648a7973468c03ec Mon Sep 17 00:00:00 2001 From: C Freeman Date: Wed, 17 Apr 2024 03:10:39 -0400 Subject: [PATCH 12/38] TC-DA-1.2: cover PAI incorrect PID encoding (#32717) * TC-DA-1.2: add test to tests.yaml This wasn't here before. I want to see this fail before adding the fix. * actually set exit value Also, apparently the formatting rules changed. OK... * Add check for incorrectly formatted fallback PID on PAI --- .github/workflows/tests.yaml | 1 + src/python_testing/TC_DA_1_2.py | 6 +++- .../test_testing/test_TC_DA_1_2.py | 30 ++++++++++++------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 281ac5c76a2ab1..b495b5f01309f4 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -559,6 +559,7 @@ jobs: scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-rvc-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-rvc-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace_file json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_RVCOPSTATE_2_1.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS examples/rvc-app/rvc-common/pics/rvc-app-pics-values --endpoint 1 --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-rvc-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-rvc-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace_file json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_RVCOPSTATE_2_3.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS examples/rvc-app/rvc-common/pics/rvc-app-pics-values --endpoint 1 --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-rvc-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-rvc-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace_file json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_RVCOPSTATE_2_4.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS examples/rvc-app/rvc-common/pics/rvc-app-pics-values --endpoint 1 --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' + scripts/run_in_python_env.sh out/venv './src/python_testing/test_testing/test_TC_DA_1_2.py' - name: Uploading core files uses: actions/upload-artifact@v4 if: ${{ failure() && !env.ACT }} diff --git a/src/python_testing/TC_DA_1_2.py b/src/python_testing/TC_DA_1_2.py index 930fa6de5dda4e..100f5813778966 100644 --- a/src/python_testing/TC_DA_1_2.py +++ b/src/python_testing/TC_DA_1_2.py @@ -57,6 +57,7 @@ def parse_single_vidpid_from_common_name(commonName: str, tag_str: str) -> str: s = sp[1][:4] if not s.isupper() or len(s) != 4: + asserts.fail(f"Improperly encoded PID or VID when using fallback encoding {tag_str}:{s}") return None return s @@ -132,6 +133,8 @@ def steps_TC_DA_1_2(self): TestStep("6.7", "Verify CD security_information", "security_information = 0"), TestStep("6.8", "Verify CD version_number", "version_number is an integer in range 0..65535"), TestStep("6.9", "Verify CD certification_type", "certification_type has a value between 1..2"), + TestStep("7.0", "Extract the Vendor ID (VID) and Product ID (PID) from the DAC. Extract the VID from the PAI. Extract the PID from the PAI, if present", + "VID and PID are present and properly encoded in the DAC. VID is present and properly encoded in the PAI. If the PID is present in the PAI, it is properly encoded"), TestStep("7.1", "", "If the dac_origin_vendor_id is present in the CD, confirm the dac_origin_product_id is also present. If the dac_origin_vendor_id is not present in the CD, confirm the dac_origin_product_id is also not present."), TestStep("7.2", "If the Certification Declaration has both the dac_origin_vendor_id and the dac_origin_product_id fields, verify dac_origin fields", ("* The Vendor ID (VID) in the DAC subject and PAI subject are the same as the dac_origin_vendor_id field in the Certification Declaration.\n" @@ -309,9 +312,10 @@ async def test_TC_DA_1_2(self): else: asserts.assert_in(certification_type, [1, 2], "Certification type is out of range") - self.step("7.1") + self.step("7.0") dac_vid, dac_pid, pai_vid, pai_pid = parse_ids_from_certs(parsed_dac, parsed_pai) + self.step("7.1") has_origin_vid = 9 in cd.keys() has_origin_pid = 10 in cd.keys() if has_origin_pid != has_origin_vid: diff --git a/src/python_testing/test_testing/test_TC_DA_1_2.py b/src/python_testing/test_testing/test_TC_DA_1_2.py index 52a45407ba4aa6..99acdea072694a 100755 --- a/src/python_testing/test_testing/test_TC_DA_1_2.py +++ b/src/python_testing/test_testing/test_TC_DA_1_2.py @@ -19,8 +19,10 @@ import json import os import subprocess +import sys -CHIP_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../..')) +CHIP_ROOT = os.path.abspath(os.path.join( + os.path.dirname(__file__), '../../..')) RUNNER_SCRIPT_DIR = os.path.join(CHIP_ROOT, 'scripts/tests') @@ -30,13 +32,15 @@ def run_single_test(dac_provider: str, product_id: int, factory_reset: bool = Fa if factory_reset: reset = ' --factoryreset' - app = os.path.join(CHIP_ROOT, 'out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app') + app = os.path.join( + CHIP_ROOT, 'out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app') # Certs in the commissioner_dut directory use 0x8000 as the PID app_args = '--discriminator 1234 --KVS kvs1 --product-id ' + \ str(product_id) + ' --vendor-id 65521 --dac_provider ' + dac_provider - ci_pics_values = os.path.abspath(os.path.join(CHIP_ROOT, 'src/app/tests/suites/certification/ci-pics-values')) + ci_pics_values = os.path.abspath(os.path.join( + CHIP_ROOT, 'src/app/tests/suites/certification/ci-pics-values')) script_args = '--storage-path admin_storage.json --discriminator 1234 --passcode 20202021 --dut-node-id 1 --PICS ' + \ str(ci_pics_values) @@ -45,28 +49,34 @@ def run_single_test(dac_provider: str, product_id: int, factory_reset: bool = Fa if factory_reset: script_args = script_args + ' --commissioning-method on-network' - script = os.path.abspath(os.path.join(CHIP_ROOT, 'src/python_testing/TC_DA_1_2.py')) + script = os.path.abspath(os.path.join( + CHIP_ROOT, 'src/python_testing/TC_DA_1_2.py')) # run_python_test uses click so call as a command - run_python_test = os.path.abspath(os.path.join(RUNNER_SCRIPT_DIR, 'run_python_test.py')) + run_python_test = os.path.abspath(os.path.join( + RUNNER_SCRIPT_DIR, 'run_python_test.py')) cmd = str(run_python_test) + reset + ' --app ' + str(app) + ' --app-args "' + \ - app_args + '" --script ' + str(script) + ' --script-args "' + script_args + '"' + app_args + '" --script ' + \ + str(script) + ' --script-args "' + script_args + '"' return subprocess.call(cmd, shell=True) def main(): - cert_path = os.path.abspath(os.path.join(CHIP_ROOT, 'credentials/development/commissioner_dut')) + cert_path = os.path.abspath(os.path.join( + CHIP_ROOT, 'credentials/development/commissioner_dut')) # Commission first using a known good set, then run the rest of the tests without recommissioning - path = str(os.path.join(cert_path, "struct_cd_authorized_paa_list_count1_valid/test_case_vector.json")) + path = str(os.path.join( + cert_path, "struct_cd_authorized_paa_list_count1_valid/test_case_vector.json")) run_single_test(path, 32768, factory_reset=True) test_cases = {'struct_cd': 32768, 'fallback_encoding': 177} # struct_cd_version_number_wrong - excluded because this is a DCL test not covered by cert # struct_cd_cert_id_mismatch - excluded because this is a DCL test not covered by cert - exclude_cases = ['struct_cd_version_number_wrong', 'struct_cd_cert_id_mismatch'] + exclude_cases = ['struct_cd_version_number_wrong', + 'struct_cd_cert_id_mismatch'] passes = [] for p in os.listdir(cert_path): @@ -92,7 +102,7 @@ def main(): print('INCORRECT: ' + p[0]) retval = 1 - return retval + sys.exit(retval) if __name__ == '__main__': From ae508020dba5d18c0a412a2d1dccca41a4830171 Mon Sep 17 00:00:00 2001 From: Shubham Patil Date: Wed, 17 Apr 2024 18:24:19 +0530 Subject: [PATCH 13/38] Fix typo in device-graph README.md (#33022) --- src/tools/device-graph/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/device-graph/README.md b/src/tools/device-graph/README.md index db8c10f96b1c7f..2e484add9a5742 100644 --- a/src/tools/device-graph/README.md +++ b/src/tools/device-graph/README.md @@ -55,7 +55,7 @@ added. --paa-trust-store-path credentials/production/paa-root-certs ``` -Once a commissioning is completed for the device, is is possible to rerun the +Once a commissioning is completed for the device, it is possible to rerun the tool again for an already commissioned devices, this is an example of how to do so: From 264cc7cd03c729c4f23ce103422613d7ed1d2987 Mon Sep 17 00:00:00 2001 From: Jakub Latusek Date: Wed, 17 Apr 2024 15:25:25 +0200 Subject: [PATCH 14/38] Ble-WiFi commissioning test in CI - dockerfile (#32890) * Add docker img for qemu testing * Update version file * Compress qemu image and sort dependencies * Use stages during docker build * Remove unused script from docker img, add missing qemu pkg --- .../docker/images/base/chip-build/version | 2 +- .../stage-2/chip-build-linux-qemu/Dockerfile | 216 ++++++++++++++++++ .../stage-2/chip-build-linux-qemu/build.sh | 1 + .../files/bluetooth/main.conf | 2 + ...ynchronize-scan-start-and-LE-Meta-ev.patch | 136 +++++++++++ .../files/wifi/dnsmasq.conf | 5 + .../files/wifi/hostapd.conf | 13 ++ .../files/wifi/wpa_supplicant.conf | 3 + .../stage-2/chip-build-linux-qemu/run-img.sh | 30 +++ .../stage-2/chip-build-linux-qemu/run.sh | 1 + .../stage-2/chip-build-linux-qemu/version | 1 + 11 files changed, 409 insertions(+), 1 deletion(-) create mode 100644 integrations/docker/images/stage-2/chip-build-linux-qemu/Dockerfile create mode 120000 integrations/docker/images/stage-2/chip-build-linux-qemu/build.sh create mode 100644 integrations/docker/images/stage-2/chip-build-linux-qemu/files/bluetooth/main.conf create mode 100644 integrations/docker/images/stage-2/chip-build-linux-qemu/files/linux/0001-Bluetooth-MGMT-Synchronize-scan-start-and-LE-Meta-ev.patch create mode 100644 integrations/docker/images/stage-2/chip-build-linux-qemu/files/wifi/dnsmasq.conf create mode 100644 integrations/docker/images/stage-2/chip-build-linux-qemu/files/wifi/hostapd.conf create mode 100644 integrations/docker/images/stage-2/chip-build-linux-qemu/files/wifi/wpa_supplicant.conf create mode 100755 integrations/docker/images/stage-2/chip-build-linux-qemu/run-img.sh create mode 120000 integrations/docker/images/stage-2/chip-build-linux-qemu/run.sh create mode 120000 integrations/docker/images/stage-2/chip-build-linux-qemu/version diff --git a/integrations/docker/images/base/chip-build/version b/integrations/docker/images/base/chip-build/version index 20e7ff0d5a702b..d5830b41578b40 100644 --- a/integrations/docker/images/base/chip-build/version +++ b/integrations/docker/images/base/chip-build/version @@ -1 +1 @@ -47 : [Telink] Update Docker image (Zephyr update) +48 : [QEMU] Add QEMU Dockerfile for ble-wifi testing on Linux diff --git a/integrations/docker/images/stage-2/chip-build-linux-qemu/Dockerfile b/integrations/docker/images/stage-2/chip-build-linux-qemu/Dockerfile new file mode 100644 index 00000000000000..b14a00c2ec9710 --- /dev/null +++ b/integrations/docker/images/stage-2/chip-build-linux-qemu/Dockerfile @@ -0,0 +1,216 @@ +ARG VERSION=latest +ARG UBUNTU_QEMU_DIR_DEFAULT="/opt/ubuntu-qemu" +ARG UBUNTU_QEMU_IMG_DEFAULT="${UBUNTU_QEMU_DIR_DEFAULT}/ubuntu-20.04.img" + +FROM ghcr.io/project-chip/chip-build:${VERSION} as build-env +LABEL org.opencontainers.image.source https://github.com/project-chip/connectedhomeip + +ARG BLUEZ_VERSION=5.72 +ARG ELL_VERSION=0.62 +ARG KERNEL_VERSION=6.7.3 +ARG UBUNTU_QEMU_DIR_DEFAULT +ARG UBUNTU_QEMU_IMG_DEFAULT + +ENV UBUNTU_QEMU_DIR=${UBUNTU_QEMU_DIR_DEFAULT} +ENV UBUNTU_QEMU_IMG=${UBUNTU_QEMU_IMG_DEFAULT} + +RUN mkdir -p /tmp/workdir/linux +COPY files/linux/0001-Bluetooth-MGMT-Synchronize-scan-start-and-LE-Meta-ev.patch /tmp/workdir/linux/0001-Bluetooth-MGMT-Synchronize-scan-start-and-LE-Meta-ev.patch +COPY files/bluetooth/main.conf /tmp/workdir/main.conf +RUN set -x \ + && apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -fy \ + bc \ + cpio \ + dwarves \ + elfutils \ + fakeroot \ + libdw-dev \ + libelf-dev \ + libell-dev \ + libell0 \ + libguestfs-tools \ + linux-image-generic \ + ncurses-dev \ + qemu \ + xz-utils \ + zstd \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /var/cache/apt/* \ + && : # last line + +# Download Linux kernel source +RUN mkdir -p /tmp/workdir/linux \ + && export MAKEFLAGS=-j$(nproc) \ + && cd /tmp/workdir \ + && curl https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-${KERNEL_VERSION}.tar.xz -o /tmp/workdir/linux-${KERNEL_VERSION}.tar.xz \ + && tar -xJf linux-${KERNEL_VERSION}.tar.xz -C /tmp/workdir/linux --strip-components=1 \ + && rm /tmp/workdir/linux-${KERNEL_VERSION}.tar.xz \ + # Set configuration for btvirt + && cd /tmp/workdir/linux \ + && patch -p1 < /tmp/workdir/linux/0001-Bluetooth-MGMT-Synchronize-scan-start-and-LE-Meta-ev.patch \ + && make x86_64_defconfig \ + && ./scripts/config -e BT \ + && ./scripts/config -e BT_BREDR \ + && ./scripts/config -e BT_HCIVHCI \ + && ./scripts/config -e CONFIG_BRIDGE \ + && ./scripts/config -e CONFIG_CRYPTO_AES \ + && ./scripts/config -e CONFIG_CRYPTO_CMAC \ + && ./scripts/config -e CONFIG_CRYPTO_ECB \ + && ./scripts/config -e CONFIG_CRYPTO_USER \ + && ./scripts/config -e CONFIG_CRYPTO_USER_API_HASH \ + && ./scripts/config -e CONFIG_CRYPTO_USER_API_SKCIPHER \ + && ./scripts/config -e CONFIG_VETH \ + && ./scripts/config -e MAC80211 \ + && ./scripts/config -e MAC80211_HWSIM \ + # Compile + && make olddefconfig \ + && make \ + && mkdir -p /opt/ubuntu-qemu/rootfs \ + && make modules_install INSTALL_MOD_PATH=/opt/ubuntu-qemu/rootfs \ + && cp /tmp/workdir/linux/arch/x86/boot/bzImage /opt/ubuntu-qemu/bzImage \ + # Build bluez + && git clone git://git.kernel.org/pub/scm/libs/ell/ell.git /tmp/workdir/ell --depth 1 --branch ${ELL_VERSION} \ + && git clone https://github.com/bluez/bluez.git /tmp/workdir/bluez --depth 1 --branch ${BLUEZ_VERSION} \ + && cd /tmp/workdir/bluez \ + && ./bootstrap \ + && ./configure \ + --enable-backtrace \ + --enable-debug \ + --enable-deprecated \ + --enable-experimental \ + --enable-library \ + --enable-monitor \ + --enable-pie \ + --enable-test \ + --enable-testing \ + --enable-tools \ + --enable-tools \ + --enable-udev \ + --disable-a2dp \ + --disable-avrcp \ + --disable-bap \ + --disable-bass \ + --disable-csip \ + --disable-cups \ + --disable-cups \ + --disable-health \ + --disable-hid \ + --disable-hid2hci \ + --disable-hog \ + --disable-manpages \ + --disable-mcp \ + --disable-mesh \ + --disable-micp \ + --disable-midi \ + --disable-network \ + --disable-obex \ + --disable-optimization \ + --disable-sap \ + --disable-silent-rules \ + --disable-vcp \ + --prefix=/usr \ + --mandir=/usr/share/man \ + --sysconfdir=/etc \ + --localstatedir=/var \ + --with-systemdsystemunitdir=/lib/systemd/system \ + --with-systemduserunitdir=/usr/lib/systemd \ + && make \ + && make install DESTDIR=/opt/ubuntu-qemu/rootfs && mkdir -p /opt/ubuntu-qemu/rootfs/usr/bin && cp /tmp/workdir/bluez/emulator/btvirt /opt/ubuntu-qemu/rootfs/usr/bin \ + # Download Ubuntu image for QEMU + && curl https://cloud-images.ubuntu.com/minimal/releases/focal/release/ubuntu-20.04-minimal-cloudimg-amd64.img \ + -o /tmp/workdir/ubuntu-20.04-minimal-cloudimg-amd64.img \ + # Prepare ubuntu image + && qemu-img create -f qcow2 -o preallocation=off $UBUNTU_QEMU_IMG 10G \ + && virt-resize --expand /dev/sda1 /tmp/workdir/ubuntu-20.04-minimal-cloudimg-amd64.img $UBUNTU_QEMU_IMG \ + && guestfish -a $UBUNTU_QEMU_IMG \ + --mount /dev/sda3:/ \ + --network \ + copy-in /opt/ubuntu-qemu/rootfs/lib /usr : \ + copy-in /opt/ubuntu-qemu/rootfs/usr / : \ + sh 'apt-get remove -y snapd' : \ + sh 'apt-get update' : \ + sh 'DEBIAN_FRONTEND=noninteractive apt-get install -y dnsmasq hostapd wpasupplicant iw libdw1 rfkill' : \ + sh '/usr/bin/systemctl enable bluetooth.service' : \ + sh '/usr/bin/systemctl disable cloud-init.service' : \ + sh '/usr/bin/systemctl disable dbus-fi.w1.wpa_supplicant1.service' : \ + sh '/usr/bin/systemctl disable dnsmasq.service' : \ + sh '/usr/bin/systemctl disable hostapd.service' : \ + sh '/usr/bin/systemctl disable lxd-agent.service' : \ + sh '/usr/bin/systemctl disable systemd-networkd-wait-online.service' : \ + sh '/usr/bin/systemctl disable systemd-timesyncd.service' : \ + sh '/usr/bin/systemctl disable wpa_supplicant.service' : \ + sh '/usr/bin/systemctl mask cloud-init.service' : \ + sh '/usr/bin/systemctl mask dbus-fi.w1.wpa_supplicant1.service' : \ + sh '/usr/bin/systemctl mask dnsmasq.service' : \ + sh '/usr/bin/systemctl mask hostapd.service' : \ + sh '/usr/bin/systemctl mask lxd-agent.service' : \ + sh '/usr/bin/systemctl mask systemd-networkd-wait-online.service' : \ + sh '/usr/bin/systemctl mask systemd-timesyncd.service' : \ + sh '/usr/bin/systemctl mask wpa_supplicant.service' : \ + sh 'passwd -d root' : \ + sh 'ssh-keygen -A' : \ + sh '/bin/echo -e "PermitRootLogin yes\nPasswordAuthentication yes\nPermitEmptyPasswords yes" > /etc/ssh/sshd_config' : \ + mkdir-p "/etc/netplan" : \ + sh '/bin/echo -e "network:\n version: 2\n renderer: networkd\n ethernets:\n enp0s4:\n dhcp4: true\n" > /etc/netplan/01-netcfg.yaml' : \ + sh 'chmod -R 700 /etc/netplan' : \ + sh 'sed -i "s#^ExecStart=.*#ExecStart=-/sbin/agetty -o \"-p -- \\\\\\\\u\" -a root --keep-baud 115200,38400,9600 %I \$TERM#" "/usr/lib/systemd/system/serial-getty@.service"' : \ + mkdir-p "/etc/bluetooth" : \ + copy-in /tmp/workdir/main.conf /etc/bluetooth : \ + sh 'sed -i "s#^ExecStart=.*#ExecStart=-/usr/libexec/bluetooth/bluetoothd -E#" /lib/systemd/system/bluetooth.service' : \ + sh 'rm -f /etc/resolv.conf && /bin/echo -e "nameserver 8.8.8.8" > /etc/resolv.conf' : \ + sh '/bin/echo -e "host0 /chip 9p trans=virtio,version=9p2000.L 0 0" >> /etc/fstab' : \ + sh '/bin/echo -e "export PW_ENVIRONMENT_ROOT=/root/pw_root\n[ -x /launcher.sh ] && /launcher.sh\n" >> /root/.profile' : \ + sh 'DEBIAN_FRONTEND=noninteractive apt-get -y install git gcc g++ pkg-config libssl-dev libdbus-1-dev libglib2.0-dev libavahi-client-dev ninja-build python3 python3-venv python3-dev python3-pip unzip libgirepository1.0-dev libcairo2-dev libreadline-dev' : \ + sh 'git config --file /root/.gitconfig --add safe.directory "*"' : \ + sh 'apt-get clean' : \ + sh 'rm -rf /var/lib/apt/lists/*' : \ + sh 'rm -rf /var/cache/apt/*' : \ + sh 'echo Configuration completed.' \ + && mkdir -p /chip \ + && rm -rf /opt/ubuntu-qemu/rootfs \ + && echo -n \ + "#!/bin/bash\n" \ + "grep -q 'rootshell' /proc/cmdline && exit\n" \ + "if [[ -x /chip/runner.sh ]]; then\n" \ + " echo '### RUNNER START ###'\n" \ + " cd /chip\n" \ + " bash /chip/runner.sh\n" \ + " status=\$?\n" \ + " echo \"### RUNNER STOP, RETURN: \$status\"\n" \ + " echo \$status > /chip/runner_status\n" \ + "else\n" \ + " read -r -t 5 -p 'Press ENTER to access root shell...' && exit || echo ' timeout.'\n" \ + "fi\n" \ + "echo 'Shutting down emulated system...'\n" \ + "echo o > /proc/sysrq-trigger\n" \ + | guestfish --rw -a $UBUNTU_QEMU_IMG -m /dev/sda3:/ upload - /launcher.sh : chmod 0755 /launcher.sh \ + && virt-sparsify --compress ${UBUNTU_QEMU_IMG} ${UBUNTU_QEMU_IMG}.compressed \ + && mv ${UBUNTU_QEMU_IMG}.compressed ${UBUNTU_QEMU_IMG} \ + && rm -rf /var/tmp/.guestfs-0/* \ + && rm -rf /tmp/* \ + && : # last line + +FROM ghcr.io/project-chip/chip-build:${VERSION} + +ARG UBUNTU_QEMU_DIR_DEFAULT +ARG UBUNTU_QEMU_IMG_DEFAULT + +ENV UBUNTU_QEMU_DIR=${UBUNTU_QEMU_DIR_DEFAULT} +ENV UBUNTU_QEMU_IMG=${UBUNTU_QEMU_IMG_DEFAULT} +ENV PW_ENVIRONMENT_ROOT="/root/pw_root" + +RUN set -x \ + && apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -fy \ + cpu-checker \ + qemu \ + qemu-system-x86 \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /var/cache/apt/* \ + && : # last line +COPY --from=build-env ${UBUNTU_QEMU_DIR} ${UBUNTU_QEMU_DIR} + +WORKDIR /chip diff --git a/integrations/docker/images/stage-2/chip-build-linux-qemu/build.sh b/integrations/docker/images/stage-2/chip-build-linux-qemu/build.sh new file mode 120000 index 00000000000000..46b20313461454 --- /dev/null +++ b/integrations/docker/images/stage-2/chip-build-linux-qemu/build.sh @@ -0,0 +1 @@ +../../../build.sh \ No newline at end of file diff --git a/integrations/docker/images/stage-2/chip-build-linux-qemu/files/bluetooth/main.conf b/integrations/docker/images/stage-2/chip-build-linux-qemu/files/bluetooth/main.conf new file mode 100644 index 00000000000000..1d29f696077c7d --- /dev/null +++ b/integrations/docker/images/stage-2/chip-build-linux-qemu/files/bluetooth/main.conf @@ -0,0 +1,2 @@ +[GATT] +Cache=no diff --git a/integrations/docker/images/stage-2/chip-build-linux-qemu/files/linux/0001-Bluetooth-MGMT-Synchronize-scan-start-and-LE-Meta-ev.patch b/integrations/docker/images/stage-2/chip-build-linux-qemu/files/linux/0001-Bluetooth-MGMT-Synchronize-scan-start-and-LE-Meta-ev.patch new file mode 100644 index 00000000000000..4f2383caa93b87 --- /dev/null +++ b/integrations/docker/images/stage-2/chip-build-linux-qemu/files/linux/0001-Bluetooth-MGMT-Synchronize-scan-start-and-LE-Meta-ev.patch @@ -0,0 +1,136 @@ +From 4dc80d22bd964c0c3fcd0840b6728b3884d2ff1b Mon Sep 17 00:00:00 2001 +From: Arkadiusz Bokowy +Date: Thu, 28 Sep 2023 13:38:17 +0200 +Subject: [PATCH] Bluetooth: MGMT: Synchronize scan start and LE Meta events + +It is possible that the Bluetooth management will receive scan enabled +signal and LE meta events one by another without any delay. Since the +start discovery procedure is performed in an asynchronous manner, it is +possible that these HCI events will be processed concurrently by two +different worker threads. In such case, it is possible that the LE meta +event, which reports new device, will be discarded, because discovering +is still in the starting state. + +The problem is most prominent with the btvirt userspace tool, which +sends LE Meta events just after reporting scan as enabled. Testing +scenario: + + 1. Create two HCI interfaces: + > btvirt -l2 + + 2. Setup BLE advertisement on hci1: + > bluetoothctl + >> select 00:AA:01:00:00:00 + >> menu advertise + >> uuids 03B80E5A-EDE8-4B33-A751-6CE34EC4C700 + >> discoverable on + >> back + >> advertise peripheral + + 3. Start scanning on hci2: + > bluetoothctl + >> select 00:AA:01:01:00:01 + >> scan le + // From time to time, new device is not reported + +This patch adds synchronization for start discovery procedure and device +found reporting by the Bluetooth management. In case of discovering +being in the starting state, the worker which processes LE Meta event +will wait for the cmd_sync_work on which the start discovery procedure +is queued. + +Signed-off-by: Arkadiusz Bokowy +--- + include/net/bluetooth/hci_core.h | 5 +++++ + include/net/bluetooth/hci_sync.h | 1 + + net/bluetooth/hci_sync.c | 7 +++++++ + net/bluetooth/mgmt.c | 17 +++++++++++++++-- + 4 files changed, 28 insertions(+), 2 deletions(-) + +diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h +index f36c1fd5d64e..456bbdf56246 100644 +--- a/include/net/bluetooth/hci_core.h ++++ b/include/net/bluetooth/hci_core.h +@@ -916,6 +916,11 @@ static inline void hci_discovery_filter_clear(struct hci_dev *hdev) + + bool hci_discovery_active(struct hci_dev *hdev); + ++static inline bool hci_discovery_starting(struct hci_dev *hdev) ++{ ++ return hdev->discovery.state == DISCOVERY_STARTING; ++} ++ + void hci_discovery_set_state(struct hci_dev *hdev, int state); + + static inline int inquiry_cache_empty(struct hci_dev *hdev) +diff --git a/include/net/bluetooth/hci_sync.h b/include/net/bluetooth/hci_sync.h +index 6efbc2152146..67cf6689a692 100644 +--- a/include/net/bluetooth/hci_sync.h ++++ b/include/net/bluetooth/hci_sync.h +@@ -43,6 +43,7 @@ void hci_cmd_sync_init(struct hci_dev *hdev); + void hci_cmd_sync_clear(struct hci_dev *hdev); + void hci_cmd_sync_cancel(struct hci_dev *hdev, int err); + void __hci_cmd_sync_cancel(struct hci_dev *hdev, int err); ++void hci_cmd_sync_flush(struct hci_dev *hdev); + + int hci_cmd_sync_submit(struct hci_dev *hdev, hci_cmd_sync_work_func_t func, + void *data, hci_cmd_sync_work_destroy_t destroy); +diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c +index 3640d73f9595..58905a5b7b1e 100644 +--- a/net/bluetooth/hci_sync.c ++++ b/net/bluetooth/hci_sync.c +@@ -681,6 +681,13 @@ void hci_cmd_sync_cancel(struct hci_dev *hdev, int err) + } + EXPORT_SYMBOL(hci_cmd_sync_cancel); + ++/* Wait for all pending HCI commands to complete. ++ */ ++void hci_cmd_sync_flush(struct hci_dev *hdev) ++{ ++ flush_work(&hdev->cmd_sync_work); ++} ++ + /* Submit HCI command to be run in as cmd_sync_work: + * + * - hdev must _not_ be unregistered +diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c +index ba2e00646e8e..fc494348f2f7 100644 +--- a/net/bluetooth/mgmt.c ++++ b/net/bluetooth/mgmt.c +@@ -10374,18 +10374,31 @@ void mgmt_device_found(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 link_type, + { + struct sk_buff *skb; + struct mgmt_ev_device_found *ev; +- bool report_device = hci_discovery_active(hdev); ++ bool report_device; + + if (hci_dev_test_flag(hdev, HCI_MESH) && link_type == LE_LINK) + mesh_device_found(hdev, bdaddr, addr_type, rssi, flags, + eir, eir_len, scan_rsp, scan_rsp_len, + instant); + ++ /* Discovery start procedure is perfomed on a workqueue in an ++ * asynchronous manner. This procedure is finished when the scan ++ * enabled signal is received from the controller. Just after ++ * this signal, the controller might send another event (e.g. LE ++ * Meta). In such case, we need to make sure that the discovery ++ * procedure is finished, because otherwise we might omit some ++ * scan results. ++ */ ++ if (hci_discovery_starting(hdev)) ++ hci_cmd_sync_flush(hdev); ++ ++ report_device = hci_discovery_active(hdev); ++ + /* Don't send events for a non-kernel initiated discovery. With + * LE one exception is if we have pend_le_reports > 0 in which + * case we're doing passive scanning and want these events. + */ +- if (!hci_discovery_active(hdev)) { ++ if (!report_device) { + if (link_type == ACL_LINK) + return; + if (link_type == LE_LINK && !list_empty(&hdev->pend_le_reports)) +-- +2.34.1 + diff --git a/integrations/docker/images/stage-2/chip-build-linux-qemu/files/wifi/dnsmasq.conf b/integrations/docker/images/stage-2/chip-build-linux-qemu/files/wifi/dnsmasq.conf new file mode 100644 index 00000000000000..a8ea3c69ce29ed --- /dev/null +++ b/integrations/docker/images/stage-2/chip-build-linux-qemu/files/wifi/dnsmasq.conf @@ -0,0 +1,5 @@ +port=0 +interface=wlan1 +listen-address=192.168.200.1 +dhcp-range=192.168.200.10,192.168.200.200 + diff --git a/integrations/docker/images/stage-2/chip-build-linux-qemu/files/wifi/hostapd.conf b/integrations/docker/images/stage-2/chip-build-linux-qemu/files/wifi/hostapd.conf new file mode 100644 index 00000000000000..cab96f219f9b53 --- /dev/null +++ b/integrations/docker/images/stage-2/chip-build-linux-qemu/files/wifi/hostapd.conf @@ -0,0 +1,13 @@ +interface=wlan1 +driver=nl80211 +country_code=DE +ssid=Virtual_Wifi +channel=1 +hw_mode=g +wpa=3 +wpa_key_mgmt=WPA-PSK +wpa_pairwise=TKIP CCMP +wpa_passphrase=ExamplePassword +auth_algs=3 +beacon_int=100 + diff --git a/integrations/docker/images/stage-2/chip-build-linux-qemu/files/wifi/wpa_supplicant.conf b/integrations/docker/images/stage-2/chip-build-linux-qemu/files/wifi/wpa_supplicant.conf new file mode 100644 index 00000000000000..38673fea93fc06 --- /dev/null +++ b/integrations/docker/images/stage-2/chip-build-linux-qemu/files/wifi/wpa_supplicant.conf @@ -0,0 +1,3 @@ +ctrl_interface=DIR=/run/wpa_supplicant +ctrl_interface_group=root +update_config=1 diff --git a/integrations/docker/images/stage-2/chip-build-linux-qemu/run-img.sh b/integrations/docker/images/stage-2/chip-build-linux-qemu/run-img.sh new file mode 100755 index 00000000000000..43cc4f80a9effa --- /dev/null +++ b/integrations/docker/images/stage-2/chip-build-linux-qemu/run-img.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +KERNEL="/opt/ubuntu-qemu/bzImage" +IMG="/opt/ubuntu-qemu/ubuntu-20.04.img" +ADDITIONAL_ARGS=() +PROJECT_PATH="$(realpath "$(dirname "$0")/../../../../..")" + +if kvm-ok; then + echo "KVM is available, running with it." + ADDITIONAL_ARGS+=("-enable-kvm" "-cpu" "host") +else + echo "KVM is not available, running without it." +fi + +/usr/bin/qemu-system-x86_64 \ + -machine ubuntu \ + -smp 4 \ + -m 2048 \ + -nographic \ + -monitor null \ + -serial stdio \ + -display none \ + -device virtio-blk-pci,drive=virtio-blk1 \ + -drive file="$IMG",id=virtio-blk1,if=none,format=qcow2,readonly=off \ + -kernel "$KERNEL" \ + -append 'console=ttyS0 mac80211_hwsim.radios=2 root=/dev/vda3' \ + -netdev user,id=network0,hostfwd=tcp::2222-:22 \ + -device e1000,netdev=network0,mac=52:54:00:12:34:56 \ + -virtfs "local,path=$PROJECT_PATH,mount_tag=host0,security_model=passthrough,id=host0" \ + "${ADDITIONAL_ARGS[@]}" diff --git a/integrations/docker/images/stage-2/chip-build-linux-qemu/run.sh b/integrations/docker/images/stage-2/chip-build-linux-qemu/run.sh new file mode 120000 index 00000000000000..9bbfad86d46e50 --- /dev/null +++ b/integrations/docker/images/stage-2/chip-build-linux-qemu/run.sh @@ -0,0 +1 @@ +../../../run.sh \ No newline at end of file diff --git a/integrations/docker/images/stage-2/chip-build-linux-qemu/version b/integrations/docker/images/stage-2/chip-build-linux-qemu/version new file mode 120000 index 00000000000000..a40ba48b0188a8 --- /dev/null +++ b/integrations/docker/images/stage-2/chip-build-linux-qemu/version @@ -0,0 +1 @@ +../../base/chip-build/version \ No newline at end of file From 692e9e126592b57b84049ddb3ae2d9cfc1cf9de0 Mon Sep 17 00:00:00 2001 From: SAYON DEEP Date: Wed, 17 Apr 2024 19:21:08 +0530 Subject: [PATCH 15/38] [ESP32] Enable extended ble announcement for esp32 platform (#32389) * enable extended ble advertisement for esp32 platform * changed extended advertisement to extended announcement * restlyed * made discoverty timeout range and default dependent on extended announcement. * help section for extended ble announcement * fixed eliding of extended data during extended announcement. * fixed setting of Additional data flag --- config/esp32/components/chip/Kconfig | 16 ++- src/platform/ESP32/BLEManagerImpl.h | 11 +- src/platform/ESP32/CHIPDevicePlatformConfig.h | 1 + src/platform/ESP32/nimble/BLEManagerImpl.cpp | 100 +++++++++++++++--- 4 files changed, 104 insertions(+), 24 deletions(-) diff --git a/config/esp32/components/chip/Kconfig b/config/esp32/components/chip/Kconfig index 1c0b5afe2bcb61..d176d6571ef205 100644 --- a/config/esp32/components/chip/Kconfig +++ b/config/esp32/components/chip/Kconfig @@ -1192,8 +1192,11 @@ menu "CHIP Device Layer" menu "Commissioning Window Options" config CHIP_DISCOVERY_TIMEOUT_SECS int "Commissioning Window Timeout in seconds" - range 180 900 - default 900 + range 180 900 if !ENABLE_BLE_EXT_ANNOUNCEMENT + range 901 172800 if ENABLE_BLE_EXT_ANNOUNCEMENT + default 900 if !ENABLE_BLE_EXT_ANNOUNCEMENT + default 172800 if ENABLE_BLE_EXT_ANNOUNCEMENT + help The amount of time (in seconds) after which the CHIP platform will close the Commissioning Window endmenu @@ -1216,4 +1219,13 @@ menu "CHIP Device Layer" endmenu + menu "Enable BLE Extended Announcement" + config ENABLE_BLE_EXT_ANNOUNCEMENT + bool "Enable BLE Extended Announcement" + default n + help + Enable BLE Extended Announcement.To be used with CHIP_DISCOVERY_TIMEOUT_SECS for extended announcement duration. + + endmenu + endmenu diff --git a/src/platform/ESP32/BLEManagerImpl.h b/src/platform/ESP32/BLEManagerImpl.h index fc946c9aae3359..aa740295d03eed 100644 --- a/src/platform/ESP32/BLEManagerImpl.h +++ b/src/platform/ESP32/BLEManagerImpl.h @@ -234,6 +234,7 @@ class BLEManagerImpl final : public BLEManager, kFastAdvertisingEnabled = 0x0200, /**< The application has enabled fast advertising. */ kUseCustomDeviceName = 0x0400, /**< The application has configured a custom BLE device name. */ kAdvertisingRefreshNeeded = 0x0800, /**< The advertising configuration/state in ESP BLE layer needs to be updated. */ + kExtAdvertisingEnabled = 0x1000, /**< The application has enabled Extended BLE announcement. */ }; enum @@ -300,13 +301,9 @@ class BLEManagerImpl final : public BLEManager, CHIP_ERROR InitESPBleLayer(void); CHIP_ERROR ConfigureAdvertisingData(void); CHIP_ERROR StartAdvertising(void); - - static constexpr System::Clock::Timeout kFastAdvertiseTimeout = - System::Clock::Milliseconds32(CHIP_DEVICE_CONFIG_BLE_ADVERTISING_INTERVAL_CHANGE_TIME); - System::Clock::Timestamp mAdvertiseStartTime; - - static void HandleFastAdvertisementTimer(System::Layer * systemLayer, void * context); - void HandleFastAdvertisementTimer(); + void StartBleAdvTimeoutTimer(uint32_t aTimeoutInMs); + void CancelBleAdvTimeoutTimer(void); + static void BleAdvTimeoutHandler(TimerHandle_t xTimer); #if CONFIG_BT_BLUEDROID_ENABLED void HandleGATTControlEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t * param); diff --git a/src/platform/ESP32/CHIPDevicePlatformConfig.h b/src/platform/ESP32/CHIPDevicePlatformConfig.h index ef6f850b36fb14..273fd562d60883 100644 --- a/src/platform/ESP32/CHIPDevicePlatformConfig.h +++ b/src/platform/ESP32/CHIPDevicePlatformConfig.h @@ -105,6 +105,7 @@ #define CHIP_DEVICE_CONFIG_DISCOVERY_TIMEOUT_SECS CONFIG_CHIP_DISCOVERY_TIMEOUT_SECS #define CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE CONFIG_ENABLE_ESP32_BLE_CONTROLLER #define CHIP_DEVICE_CONFIG_ENABLE_PAIRING_AUTOSTART CONFIG_CHIP_ENABLE_PAIRING_AUTOSTART +#define CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING CONFIG_ENABLE_BLE_EXT_ANNOUNCEMENT // Options for background chip task #define CHIP_DEVICE_CONFIG_ENABLE_BG_EVENT_PROCESSING CONFIG_ENABLE_BG_EVENT_PROCESSING diff --git a/src/platform/ESP32/nimble/BLEManagerImpl.cpp b/src/platform/ESP32/nimble/BLEManagerImpl.cpp index 5092828d750f66..5407e9969b50e3 100644 --- a/src/platform/ESP32/nimble/BLEManagerImpl.cpp +++ b/src/platform/ESP32/nimble/BLEManagerImpl.cpp @@ -76,6 +76,7 @@ namespace Internal { namespace { +TimerHandle_t sbleAdvTimeoutTimer; // FreeRTOS sw timer. #if CONFIG_ENABLE_ESP32_BLE_CONTROLLER static constexpr uint16_t kNewConnectionScanTimeout = 60; static constexpr uint16_t kConnectTimeout = 20; @@ -123,8 +124,6 @@ uint8_t own_addr_type = BLE_OWN_ADDR_RANDOM; ChipDeviceScanner & mDeviceScanner = Internal::ChipDeviceScanner::GetInstance(); #endif BLEManagerImpl BLEManagerImpl::sInstance; -constexpr System::Clock::Timeout BLEManagerImpl::kFastAdvertiseTimeout; - const struct ble_gatt_svc_def BLEManagerImpl::CHIPoBLEGATTAttrs[] = { { .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = (ble_uuid_t *) (&ShortUUID_CHIPoBLEService), @@ -221,6 +220,14 @@ CHIP_ERROR BLEManagerImpl::_Init() #endif SuccessOrExit(err); + // Create FreeRTOS sw timer for BLE timeouts and interval change. + sbleAdvTimeoutTimer = xTimerCreate("BleAdvTimer", // Just a text name, not used by the RTOS kernel + 1, // == default timer period + false, // no timer reload (==one-shot) + (void *) this, // init timer id = ble obj context + BleAdvTimeoutHandler // timer callback handler + ); + mRXCharAttrHandle = 0; #if CHIP_ENABLE_ADDITIONAL_DATA_ADVERTISING mC3CharAttrHandle = 0; @@ -254,8 +261,7 @@ CHIP_ERROR BLEManagerImpl::_SetAdvertisingEnabled(bool val) if (val) { - mAdvertiseStartTime = System::SystemClock().GetMonotonicTimestamp(); - ReturnErrorOnFailure(DeviceLayer::SystemLayer().StartTimer(kFastAdvertiseTimeout, HandleFastAdvertisementTimer, this)); + StartBleAdvTimeoutTimer(CHIP_DEVICE_CONFIG_BLE_ADVERTISING_INTERVAL_CHANGE_TIME); } mFlags.Set(Flags::kFastAdvertisingEnabled, val); @@ -267,21 +273,31 @@ CHIP_ERROR BLEManagerImpl::_SetAdvertisingEnabled(bool val) return err; } -void BLEManagerImpl::HandleFastAdvertisementTimer(System::Layer * systemLayer, void * context) -{ - static_cast(context)->HandleFastAdvertisementTimer(); -} - -void BLEManagerImpl::HandleFastAdvertisementTimer() +void BLEManagerImpl::BleAdvTimeoutHandler(TimerHandle_t xTimer) { - System::Clock::Timestamp currentTimestamp = System::SystemClock().GetMonotonicTimestamp(); + if (BLEMgrImpl().mFlags.Has(Flags::kFastAdvertisingEnabled)) + { + ChipLogProgress(DeviceLayer, "bleAdv Timeout : Start slow advertisement"); + BLEMgrImpl().mFlags.Set(Flags::kFastAdvertisingEnabled, 0); + BLEMgrImpl().mFlags.Set(Flags::kAdvertisingRefreshNeeded, 1); - if (currentTimestamp - mAdvertiseStartTime >= kFastAdvertiseTimeout) +#if CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING + BLEMgrImpl().mFlags.Clear(Flags::kExtAdvertisingEnabled); + BLEMgrImpl().StartBleAdvTimeoutTimer(CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING_INTERVAL_CHANGE_TIME_MS); +#endif + PlatformMgr().ScheduleWork(DriveBLEState, 0); + } +#if CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING + else { - mFlags.Set(Flags::kFastAdvertisingEnabled, 0); - mFlags.Set(Flags::kAdvertisingRefreshNeeded, 1); + ChipLogProgress(DeviceLayer, "bleAdv Timeout : Start extended advertisement"); + BLEMgrImpl().mFlags.Set(Flags::kAdvertising); + BLEMgrImpl().mFlags.Set(Flags::kExtAdvertisingEnabled); + BLEMgr().SetAdvertisingMode(BLEAdvertisingMode::kSlowAdvertising); + BLEMgrImpl().mFlags.Set(Flags::kAdvertisingRefreshNeeded, 1); PlatformMgr().ScheduleWork(DriveBLEState, 0); } +#endif } CHIP_ERROR BLEManagerImpl::_SetAdvertisingMode(BLEAdvertisingMode mode) @@ -690,6 +706,28 @@ CHIP_ERROR BLEManagerImpl::MapBLEError(int bleErr) return CHIP_ERROR(ChipError::Range::kPlatform, CHIP_DEVICE_CONFIG_ESP32_BLE_ERROR_MIN + bleErr); } } +void BLEManagerImpl::CancelBleAdvTimeoutTimer(void) +{ + if (xTimerStop(sbleAdvTimeoutTimer, pdMS_TO_TICKS(0)) == pdFAIL) + { + ChipLogError(DeviceLayer, "Failed to stop BledAdv timeout timer"); + } +} +void BLEManagerImpl::StartBleAdvTimeoutTimer(uint32_t aTimeoutInMs) +{ + if (xTimerIsTimerActive(sbleAdvTimeoutTimer)) + { + CancelBleAdvTimeoutTimer(); + } + + // timer is not active, change its period to required value (== restart). + // FreeRTOS- Block for a maximum of 100 ticks if the change period command + // cannot immediately be sent to the timer command queue. + if (xTimerChangePeriod(sbleAdvTimeoutTimer, pdMS_TO_TICKS(aTimeoutInMs), pdMS_TO_TICKS(100)) != pdPASS) + { + ChipLogError(DeviceLayer, "Failed to start BledAdv timeout timer"); + } +} void BLEManagerImpl::DriveBLEState(void) { CHIP_ERROR err = CHIP_NO_ERROR; @@ -977,8 +1015,25 @@ CHIP_ERROR BLEManagerImpl::ConfigureAdvertisingData(void) ExitNow(); } +#if CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING + // Check for extended advertisement interval and redact VID/PID if past the initial period. + if (mFlags.Has(Flags::kExtAdvertisingEnabled)) + { + deviceIdInfo.SetVendorId(0); + deviceIdInfo.SetProductId(0); + deviceIdInfo.SetExtendedAnnouncementFlag(true); + } +#endif + #if CHIP_ENABLE_ADDITIONAL_DATA_ADVERTISING - deviceIdInfo.SetAdditionalDataFlag(true); + if (!mFlags.Has(Flags::kExtAdvertisingEnabled)) + { + deviceIdInfo.SetAdditionalDataFlag(true); + } + else + { + deviceIdInfo.SetAdditionalDataFlag(false); + } #endif VerifyOrExit(index + sizeof(deviceIdInfo) <= sizeof(advData), err = CHIP_ERROR_OUTBOUND_MESSAGE_TOO_BIG); @@ -1565,8 +1620,23 @@ CHIP_ERROR BLEManagerImpl::StartAdvertising(void) } else { +#if CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING + if (!mFlags.Has(Flags::kExtAdvertisingEnabled)) + { + adv_params.itvl_min = CHIP_DEVICE_CONFIG_BLE_SLOW_ADVERTISING_INTERVAL_MIN; + adv_params.itvl_max = CHIP_DEVICE_CONFIG_BLE_SLOW_ADVERTISING_INTERVAL_MAX; + } + else + { + adv_params.itvl_min = CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING_INTERVAL_MIN; + adv_params.itvl_max = CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING_INTERVAL_MAX; + } +#else + adv_params.itvl_min = CHIP_DEVICE_CONFIG_BLE_SLOW_ADVERTISING_INTERVAL_MIN; adv_params.itvl_max = CHIP_DEVICE_CONFIG_BLE_SLOW_ADVERTISING_INTERVAL_MAX; + +#endif } ChipLogProgress(DeviceLayer, "Configuring CHIPoBLE advertising (interval %" PRIu32 " ms, %sconnectable)", From b4a62135a0eee4061c427512d64951b554835420 Mon Sep 17 00:00:00 2001 From: "tianfeng.yang" <130436698+tianfeng-yang@users.noreply.github.com> Date: Wed, 17 Apr 2024 21:53:13 +0800 Subject: [PATCH 16/38] [Python] Create pairingDelegate for each DeviceController (#32369) * Create pairingDelegate for each DeviceController * restore the original pairingcomplete callback logic --- .../ChipDeviceController-ScriptBinding.cpp | 97 +++++++++++-------- src/controller/python/OpCredsBinding.cpp | 40 +++++--- src/controller/python/chip/ChipDeviceCtrl.py | 69 ++++++++----- 3 files changed, 128 insertions(+), 78 deletions(-) diff --git a/src/controller/python/ChipDeviceController-ScriptBinding.cpp b/src/controller/python/ChipDeviceController-ScriptBinding.cpp index d78c9da8ed74d3..a55d3865bdccae 100644 --- a/src/controller/python/ChipDeviceController-ScriptBinding.cpp +++ b/src/controller/python/ChipDeviceController-ScriptBinding.cpp @@ -105,7 +105,6 @@ chip::Controller::CommissioningParameters sCommissioningParameters; } // namespace -chip::Controller::ScriptDevicePairingDelegate sPairingDelegate; chip::Controller::ScriptPairingDeviceDiscoveryDelegate sPairingDeviceDiscoveryDelegate; chip::Credentials::GroupDataProviderImpl sGroupDataProvider; chip::Credentials::PersistentStorageOpCertStore sPersistentStorageOpCertStore; @@ -121,9 +120,8 @@ extern "C" { PyChipError pychip_DeviceController_StackInit(Controller::Python::StorageAdapter * storageAdapter, bool enableServerInteractions); PyChipError pychip_DeviceController_StackShutdown(); -PyChipError pychip_DeviceController_NewDeviceController(chip::Controller::DeviceCommissioner ** outDevCtrl, - chip::NodeId localDeviceId, bool useTestCommissioner); -PyChipError pychip_DeviceController_DeleteDeviceController(chip::Controller::DeviceCommissioner * devCtrl); +PyChipError pychip_DeviceController_DeleteDeviceController(chip::Controller::DeviceCommissioner * devCtrl, + chip::Controller::ScriptDevicePairingDelegate * pairingDelegate); PyChipError pychip_DeviceController_GetAddressAndPort(chip::Controller::DeviceCommissioner * devCtrl, chip::NodeId nodeId, char * outAddress, uint64_t maxAddressLen, uint16_t * outPort); PyChipError pychip_DeviceController_GetCompressedFabricId(chip::Controller::DeviceCommissioner * devCtrl, uint64_t * outFabricId); @@ -168,15 +166,17 @@ PyChipError pychip_DeviceController_DiscoverCommissionableNodesDeviceType(chip:: uint16_t device_type); PyChipError pychip_DeviceController_DiscoverCommissionableNodesCommissioningEnabled(chip::Controller::DeviceCommissioner * devCtrl); -PyChipError pychip_DeviceController_OnNetworkCommission(chip::Controller::DeviceCommissioner * devCtrl, uint64_t nodeId, - uint32_t setupPasscode, const uint8_t filterType, const char * filterParam, - uint32_t discoveryTimeoutMsec); +PyChipError pychip_DeviceController_OnNetworkCommission(chip::Controller::DeviceCommissioner * devCtrl, + chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, + uint64_t nodeId, uint32_t setupPasscode, const uint8_t filterType, + const char * filterParam, uint32_t discoveryTimeoutMsec); PyChipError pychip_DeviceController_PostTaskOnChipThread(ChipThreadTaskRunnerFunct callback, void * pythonContext); -PyChipError pychip_DeviceController_OpenCommissioningWindow(chip::Controller::DeviceCommissioner * devCtrl, chip::NodeId nodeid, - uint16_t timeout, uint32_t iteration, uint16_t discriminator, - uint8_t optionInt); +PyChipError pychip_DeviceController_OpenCommissioningWindow(chip::Controller::DeviceCommissioner * devCtrl, + chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, + chip::NodeId nodeid, uint16_t timeout, uint32_t iteration, + uint16_t discriminator, uint8_t optionInt); void pychip_DeviceController_PrintDiscoveredDevices(chip::Controller::DeviceCommissioner * devCtrl); bool pychip_DeviceController_GetIPForDiscoveredDevice(chip::Controller::DeviceCommissioner * devCtrl, int idx, char * addrStr, @@ -184,19 +184,28 @@ bool pychip_DeviceController_GetIPForDiscoveredDevice(chip::Controller::DeviceCo // Pairing Delegate PyChipError -pychip_ScriptDevicePairingDelegate_SetKeyExchangeCallback(chip::Controller::DeviceCommissioner * devCtrl, +pychip_ScriptDevicePairingDelegate_SetKeyExchangeCallback(chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, chip::Controller::DevicePairingDelegate_OnPairingCompleteFunct callback); PyChipError pychip_ScriptDevicePairingDelegate_SetCommissioningCompleteCallback( - chip::Controller::DeviceCommissioner * devCtrl, chip::Controller::DevicePairingDelegate_OnCommissioningCompleteFunct callback); + chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, + chip::Controller::DevicePairingDelegate_OnCommissioningCompleteFunct callback); PyChipError pychip_ScriptDevicePairingDelegate_SetCommissioningStatusUpdateCallback( - chip::Controller::DeviceCommissioner * devCtrl, + chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, chip::Controller::DevicePairingDelegate_OnCommissioningStatusUpdateFunct callback); + PyChipError -pychip_ScriptDevicePairingDelegate_SetFabricCheckCallback(chip::Controller::DevicePairingDelegate_OnFabricCheckFunct callback); +pychip_ScriptDevicePairingDelegate_SetFabricCheckCallback(chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, + chip::Controller::DevicePairingDelegate_OnFabricCheckFunct callback); + PyChipError pychip_ScriptDevicePairingDelegate_SetOpenWindowCompleteCallback( - chip::Controller::DeviceCommissioner * devCtrl, chip::Controller::DevicePairingDelegate_OnWindowOpenCompleteFunct callback); + chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, + chip::Controller::DevicePairingDelegate_OnWindowOpenCompleteFunct callback); + +PyChipError +pychip_ScriptDevicePairingDelegate_SetExpectingPairingComplete(chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, + bool value); // BLE PyChipError pychip_DeviceCommissioner_CloseBleConnection(chip::Controller::DeviceCommissioner * devCtrl); @@ -354,7 +363,6 @@ const char * pychip_DeviceController_StatusReportToString(uint32_t profileId, ui PyChipError pychip_DeviceController_ConnectBLE(chip::Controller::DeviceCommissioner * devCtrl, uint16_t discriminator, uint32_t setupPINCode, chip::NodeId nodeid) { - sPairingDelegate.SetExpectingPairingComplete(true); return ToPyChipError(devCtrl->PairDevice(nodeid, chip::RendezvousParameters() .SetPeerAddress(Transport::PeerAddress(Transport::Type::kBle)) @@ -378,14 +386,12 @@ PyChipError pychip_DeviceController_ConnectIP(chip::Controller::DeviceCommission addr.SetTransportType(chip::Transport::Type::kUdp).SetIPAddress(peerAddr).SetInterface(ifaceOutput); params.SetPeerAddress(addr).SetDiscriminator(0); - sPairingDelegate.SetExpectingPairingComplete(true); return ToPyChipError(devCtrl->PairDevice(nodeid, params, sCommissioningParameters)); } PyChipError pychip_DeviceController_ConnectWithCode(chip::Controller::DeviceCommissioner * devCtrl, const char * onboardingPayload, chip::NodeId nodeid, uint8_t discoveryType) { - sPairingDelegate.SetExpectingPairingComplete(true); return ToPyChipError(devCtrl->PairDevice(nodeid, onboardingPayload, sCommissioningParameters, static_cast(discoveryType))); } @@ -430,9 +436,10 @@ PyChipError pychip_DeviceController_UnpairDevice(chip::Controller::DeviceCommiss return ToPyChipError(err); } -PyChipError pychip_DeviceController_OnNetworkCommission(chip::Controller::DeviceCommissioner * devCtrl, uint64_t nodeId, - uint32_t setupPasscode, const uint8_t filterType, const char * filterParam, - uint32_t discoveryTimeoutMsec) +PyChipError pychip_DeviceController_OnNetworkCommission(chip::Controller::DeviceCommissioner * devCtrl, + chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, + uint64_t nodeId, uint32_t setupPasscode, const uint8_t filterType, + const char * filterParam, uint32_t discoveryTimeoutMsec) { Dnssd::DiscoveryFilter filter(static_cast(filterType)); switch (static_cast(filterType)) @@ -467,9 +474,8 @@ PyChipError pychip_DeviceController_OnNetworkCommission(chip::Controller::Device return ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT); } - sPairingDelegate.SetExpectingPairingComplete(true); - CHIP_ERROR err = sPairingDeviceDiscoveryDelegate.Init(nodeId, setupPasscode, sCommissioningParameters, &sPairingDelegate, - devCtrl, discoveryTimeoutMsec); + CHIP_ERROR err = sPairingDeviceDiscoveryDelegate.Init(nodeId, setupPasscode, sCommissioningParameters, pairingDelegate, devCtrl, + discoveryTimeoutMsec); VerifyOrReturnError(err == CHIP_NO_ERROR, ToPyChipError(err)); return ToPyChipError(devCtrl->DiscoverCommissionableNodes(filter)); } @@ -587,7 +593,6 @@ PyChipError pychip_DeviceController_EstablishPASESessionIP(chip::Controller::Dev addr.SetPort(port); } params.SetPeerAddress(addr).SetDiscriminator(0); - sPairingDelegate.SetExpectingPairingComplete(true); return ToPyChipError(devCtrl->EstablishPASEConnection(nodeid, params)); } @@ -598,14 +603,12 @@ PyChipError pychip_DeviceController_EstablishPASESessionBLE(chip::Controller::De RendezvousParameters params = chip::RendezvousParameters().SetSetupPINCode(setupPINCode); addr.SetTransportType(chip::Transport::Type::kBle); params.SetPeerAddress(addr).SetDiscriminator(discriminator); - sPairingDelegate.SetExpectingPairingComplete(true); return ToPyChipError(devCtrl->EstablishPASEConnection(nodeid, params)); } PyChipError pychip_DeviceController_EstablishPASESession(chip::Controller::DeviceCommissioner * devCtrl, const char * setUpCode, chip::NodeId nodeid) { - sPairingDelegate.SetExpectingPairingComplete(true); return ToPyChipError(devCtrl->EstablishPASEConnection(nodeid, setUpCode)); } @@ -656,15 +659,17 @@ PyChipError pychip_DeviceController_DiscoverCommissionableNodesCommissioningEnab } PyChipError pychip_ScriptDevicePairingDelegate_SetOpenWindowCompleteCallback( - chip::Controller::DeviceCommissioner * devCtrl, chip::Controller::DevicePairingDelegate_OnWindowOpenCompleteFunct callback) + chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, + chip::Controller::DevicePairingDelegate_OnWindowOpenCompleteFunct callback) { - sPairingDelegate.SetCommissioningWindowOpenCallback(callback); + pairingDelegate->SetCommissioningWindowOpenCallback(callback); return ToPyChipError(CHIP_NO_ERROR); } -PyChipError pychip_DeviceController_OpenCommissioningWindow(chip::Controller::DeviceCommissioner * devCtrl, chip::NodeId nodeid, - uint16_t timeout, uint32_t iteration, uint16_t discriminator, - uint8_t optionInt) +PyChipError pychip_DeviceController_OpenCommissioningWindow(chip::Controller::DeviceCommissioner * devCtrl, + chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, + chip::NodeId nodeid, uint16_t timeout, uint32_t iteration, + uint16_t discriminator, uint8_t optionInt) { const auto option = static_cast(optionInt); if (option == Controller::CommissioningWindowOpener::CommissioningWindowOption::kOriginalSetupCode) @@ -680,7 +685,7 @@ PyChipError pychip_DeviceController_OpenCommissioningWindow(chip::Controller::De Platform::New(static_cast(devCtrl)); PyChipError err = ToPyChipError(opener->OpenCommissioningWindow(nodeid, System::Clock::Seconds16(timeout), iteration, discriminator, NullOptional, NullOptional, - sPairingDelegate.GetOpenWindowCallback(opener), payload)); + pairingDelegate->GetOpenWindowCallback(opener), payload)); return err; } @@ -688,32 +693,42 @@ PyChipError pychip_DeviceController_OpenCommissioningWindow(chip::Controller::De } PyChipError -pychip_ScriptDevicePairingDelegate_SetKeyExchangeCallback(chip::Controller::DeviceCommissioner * devCtrl, +pychip_ScriptDevicePairingDelegate_SetKeyExchangeCallback(chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, chip::Controller::DevicePairingDelegate_OnPairingCompleteFunct callback) { - sPairingDelegate.SetKeyExchangeCallback(callback); + pairingDelegate->SetKeyExchangeCallback(callback); return ToPyChipError(CHIP_NO_ERROR); } PyChipError pychip_ScriptDevicePairingDelegate_SetCommissioningCompleteCallback( - chip::Controller::DeviceCommissioner * devCtrl, chip::Controller::DevicePairingDelegate_OnCommissioningCompleteFunct callback) + chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, + chip::Controller::DevicePairingDelegate_OnCommissioningCompleteFunct callback) { - sPairingDelegate.SetCommissioningCompleteCallback(callback); + pairingDelegate->SetCommissioningCompleteCallback(callback); return ToPyChipError(CHIP_NO_ERROR); } PyChipError pychip_ScriptDevicePairingDelegate_SetCommissioningStatusUpdateCallback( - chip::Controller::DeviceCommissioner * devCtrl, + chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, chip::Controller::DevicePairingDelegate_OnCommissioningStatusUpdateFunct callback) { - sPairingDelegate.SetCommissioningStatusUpdateCallback(callback); + pairingDelegate->SetCommissioningStatusUpdateCallback(callback); + return ToPyChipError(CHIP_NO_ERROR); +} + +PyChipError +pychip_ScriptDevicePairingDelegate_SetFabricCheckCallback(chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, + chip::Controller::DevicePairingDelegate_OnFabricCheckFunct callback) +{ + pairingDelegate->SetFabricCheckCallback(callback); return ToPyChipError(CHIP_NO_ERROR); } PyChipError -pychip_ScriptDevicePairingDelegate_SetFabricCheckCallback(chip::Controller::DevicePairingDelegate_OnFabricCheckFunct callback) +pychip_ScriptDevicePairingDelegate_SetExpectingPairingComplete(chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, + bool value) { - sPairingDelegate.SetFabricCheckCallback(callback); + pairingDelegate->SetExpectingPairingComplete(value); return ToPyChipError(CHIP_NO_ERROR); } diff --git a/src/controller/python/OpCredsBinding.cpp b/src/controller/python/OpCredsBinding.cpp index 79995d133b45e0..66a6c841843cde 100644 --- a/src/controller/python/OpCredsBinding.cpp +++ b/src/controller/python/OpCredsBinding.cpp @@ -402,12 +402,11 @@ void pychip_OnCommissioningStatusUpdate(chip::PeerId peerId, chip::Controller::C * TODO(#25214): Need clean up API * */ -PyChipError pychip_OpCreds_AllocateControllerForPythonCommissioningFLow(chip::Controller::DeviceCommissioner ** outDevCtrl, - chip::python::pychip_P256Keypair * operationalKey, - uint8_t * noc, uint32_t nocLen, uint8_t * icac, - uint32_t icacLen, uint8_t * rcac, uint32_t rcacLen, - const uint8_t * ipk, uint32_t ipkLen, - chip::VendorId adminVendorId, bool enableServerInteractions) +PyChipError pychip_OpCreds_AllocateControllerForPythonCommissioningFLow( + chip::Controller::DeviceCommissioner ** outDevCtrl, chip::Controller::ScriptDevicePairingDelegate ** outPairingDelegate, + chip::python::pychip_P256Keypair * operationalKey, uint8_t * noc, uint32_t nocLen, uint8_t * icac, uint32_t icacLen, + uint8_t * rcac, uint32_t rcacLen, const uint8_t * ipk, uint32_t ipkLen, chip::VendorId adminVendorId, + bool enableServerInteractions) { ReturnErrorCodeIf(nocLen > Controller::kMaxCHIPDERCertLength, ToPyChipError(CHIP_ERROR_NO_MEMORY)); ReturnErrorCodeIf(icacLen > Controller::kMaxCHIPDERCertLength, ToPyChipError(CHIP_ERROR_NO_MEMORY)); @@ -415,11 +414,13 @@ PyChipError pychip_OpCreds_AllocateControllerForPythonCommissioningFLow(chip::Co ChipLogDetail(Controller, "Creating New Device Controller"); + auto pairingDelegate = std::make_unique(); + VerifyOrReturnError(pairingDelegate != nullptr, ToPyChipError(CHIP_ERROR_NO_MEMORY)); auto devCtrl = std::make_unique(); VerifyOrReturnError(devCtrl != nullptr, ToPyChipError(CHIP_ERROR_NO_MEMORY)); Controller::SetupParams initParams; - initParams.pairingDelegate = &sPairingDelegate; + initParams.pairingDelegate = pairingDelegate.get(); initParams.operationalCredentialsDelegate = &sPlaceholderOperationalCredentialsIssuer; initParams.operationalKeypair = operationalKey; initParams.controllerRCAC = ByteSpan(rcac, rcacLen); @@ -450,13 +451,15 @@ PyChipError pychip_OpCreds_AllocateControllerForPythonCommissioningFLow(chip::Co chip::Credentials::SetSingleIpkEpochKey(&sGroupDataProvider, devCtrl->GetFabricIndex(), fabricIpk, compressedFabricIdSpan); VerifyOrReturnError(err == CHIP_NO_ERROR, ToPyChipError(err)); - *outDevCtrl = devCtrl.release(); + *outDevCtrl = devCtrl.release(); + *outPairingDelegate = pairingDelegate.release(); return ToPyChipError(CHIP_NO_ERROR); } // TODO(#25214): Need clean up API PyChipError pychip_OpCreds_AllocateController(OpCredsContext * context, chip::Controller::DeviceCommissioner ** outDevCtrl, + chip::Controller::ScriptDevicePairingDelegate ** outPairingDelegate, FabricId fabricId, chip::NodeId nodeId, chip::VendorId adminVendorId, const char * paaTrustStorePath, bool useTestCommissioner, bool enableServerInteractions, CASEAuthTag * caseAuthTags, uint32_t caseAuthTagLen, @@ -468,6 +471,8 @@ PyChipError pychip_OpCreds_AllocateController(OpCredsContext * context, chip::Co VerifyOrReturnError(context != nullptr, ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT)); + auto pairingDelegate = std::make_unique(); + VerifyOrReturnError(pairingDelegate != nullptr, ToPyChipError(CHIP_ERROR_NO_MEMORY)); auto devCtrl = std::make_unique(); VerifyOrReturnError(devCtrl != nullptr, ToPyChipError(CHIP_ERROR_NO_MEMORY)); @@ -524,7 +529,7 @@ PyChipError pychip_OpCreds_AllocateController(OpCredsContext * context, chip::Co VerifyOrReturnError(err == CHIP_NO_ERROR, ToPyChipError(err)); Controller::SetupParams initParams; - initParams.pairingDelegate = &sPairingDelegate; + initParams.pairingDelegate = pairingDelegate.get(); initParams.operationalCredentialsDelegate = context->mAdapter.get(); initParams.operationalKeypair = controllerKeyPair; initParams.controllerRCAC = rcacSpan; @@ -538,9 +543,9 @@ PyChipError pychip_OpCreds_AllocateController(OpCredsContext * context, chip::Co if (useTestCommissioner) { initParams.defaultCommissioner = &sTestCommissioner; - sPairingDelegate.SetCommissioningSuccessCallback(pychip_OnCommissioningSuccess); - sPairingDelegate.SetCommissioningFailureCallback(pychip_OnCommissioningFailure); - sPairingDelegate.SetCommissioningStatusUpdateCallback(pychip_OnCommissioningStatusUpdate); + pairingDelegate->SetCommissioningSuccessCallback(pychip_OnCommissioningSuccess); + pairingDelegate->SetCommissioningFailureCallback(pychip_OnCommissioningFailure); + pairingDelegate->SetCommissioningStatusUpdateCallback(pychip_OnCommissioningStatusUpdate); } err = Controller::DeviceControllerFactory::GetInstance().SetupCommissioner(initParams, *devCtrl); @@ -562,7 +567,8 @@ PyChipError pychip_OpCreds_AllocateController(OpCredsContext * context, chip::Co chip::Credentials::SetSingleIpkEpochKey(&sGroupDataProvider, devCtrl->GetFabricIndex(), defaultIpk, compressedFabricIdSpan); VerifyOrReturnError(err == CHIP_NO_ERROR, ToPyChipError(err)); - *outDevCtrl = devCtrl.release(); + *outDevCtrl = devCtrl.release(); + *outPairingDelegate = pairingDelegate.release(); return ToPyChipError(CHIP_NO_ERROR); } @@ -596,7 +602,8 @@ void pychip_OpCreds_FreeDelegate(OpCredsContext * context) Platform::Delete(context); } -PyChipError pychip_DeviceController_DeleteDeviceController(chip::Controller::DeviceCommissioner * devCtrl) +PyChipError pychip_DeviceController_DeleteDeviceController(chip::Controller::DeviceCommissioner * devCtrl, + chip::Controller::ScriptDevicePairingDelegate * pairingDelegate) { if (devCtrl != nullptr) { @@ -604,6 +611,11 @@ PyChipError pychip_DeviceController_DeleteDeviceController(chip::Controller::Dev delete devCtrl; } + if (pairingDelegate != nullptr) + { + delete pairingDelegate; + } + return ToPyChipError(CHIP_NO_ERROR); } diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py index 369260787d9af8..b553b2f85b641e 100644 --- a/src/controller/python/chip/ChipDeviceCtrl.py +++ b/src/controller/python/chip/ChipDeviceCtrl.py @@ -253,8 +253,10 @@ def __init__(self, name: str = ''): self._InitLib() + pairingDelegate = c_void_p(None) devCtrl = c_void_p(None) + self.pairingDelegate = pairingDelegate self.devCtrl = devCtrl self.name = name self.fabricCheckNodeId = -1 @@ -263,7 +265,7 @@ def __init__(self, name: str = ''): self._Cluster = ChipClusters(builtins.chipStack) self._Cluster.InitLib(self._dmLib) - def _set_dev_ctrl(self, devCtrl): + def _set_dev_ctrl(self, devCtrl, pairingDelegate): def HandleCommissioningComplete(nodeid, err): if err.is_success: logging.info("Commissioning complete") @@ -321,25 +323,26 @@ def HandlePASEEstablishmentComplete(err: PyChipError): if not err.is_success: HandleCommissioningComplete(0, err) + self.pairingDelegate = pairingDelegate self.devCtrl = devCtrl self.cbHandlePASEEstablishmentCompleteFunct = _DevicePairingDelegate_OnPairingCompleteFunct( HandlePASEEstablishmentComplete) self._dmLib.pychip_ScriptDevicePairingDelegate_SetKeyExchangeCallback( - self.devCtrl, self.cbHandlePASEEstablishmentCompleteFunct) + self.pairingDelegate, self.cbHandlePASEEstablishmentCompleteFunct) self.cbHandleCommissioningCompleteFunct = _DevicePairingDelegate_OnCommissioningCompleteFunct( HandleCommissioningComplete) self._dmLib.pychip_ScriptDevicePairingDelegate_SetCommissioningCompleteCallback( - self.devCtrl, self.cbHandleCommissioningCompleteFunct) + self.pairingDelegate, self.cbHandleCommissioningCompleteFunct) self.cbHandleFabricCheckFunct = _DevicePairingDelegate_OnFabricCheckFunct(HandleFabricCheck) - self._dmLib.pychip_ScriptDevicePairingDelegate_SetFabricCheckCallback(self.cbHandleFabricCheckFunct) + self._dmLib.pychip_ScriptDevicePairingDelegate_SetFabricCheckCallback(self.pairingDelegate, self.cbHandleFabricCheckFunct) self.cbHandleOpenWindowCompleteFunct = _DevicePairingDelegate_OnOpenWindowCompleteFunct( HandleOpenWindowComplete) self._dmLib.pychip_ScriptDevicePairingDelegate_SetOpenWindowCompleteCallback( - self.devCtrl, self.cbHandleOpenWindowCompleteFunct) + self.pairingDelegate, self.cbHandleOpenWindowCompleteFunct) self.cbHandleDeviceUnpairCompleteFunct = _DeviceUnpairingCompleteFunct(HandleUnpairDeviceComplete) @@ -355,6 +358,11 @@ def _finish_init(self): ChipDeviceController.activeList.add(self) + def _enablePairingCompeleteCallback(self, value: bool): + self._ChipStack.Call( + lambda: self._dmLib.pychip_ScriptDevicePairingDelegate_SetExpectingPairingComplete(self.pairingDelegate, value) + ).raise_on_error() + @property def fabricAdmin(self) -> FabricAdmin.FabricAdmin: return self._fabricAdmin @@ -389,8 +397,9 @@ def Shutdown(self): if self.devCtrl is not None: self._ChipStack.Call( lambda: self._dmLib.pychip_DeviceController_DeleteDeviceController( - self.devCtrl) + self.devCtrl, self.pairingDelegate) ).raise_on_error() + self.pairingDelegate = None self.devCtrl = None ChipDeviceController.activeList.remove(self) @@ -437,6 +446,7 @@ def ConnectBLE(self, discriminator, setupPinCode, nodeid) -> PyChipError: self._ChipStack.commissioningCompleteEvent.clear() self.state = DCState.COMMISSIONING + self._enablePairingCompeleteCallback(True) self._ChipStack.CallAsync( lambda: self._dmLib.pychip_DeviceController_ConnectBLE( self.devCtrl, discriminator, setupPinCode, nodeid) @@ -487,6 +497,7 @@ def EstablishPASESessionBLE(self, setupPinCode: int, discriminator: int, nodeid: self.CheckIsActive() self.state = DCState.RENDEZVOUS_ONGOING + self._enablePairingCompeleteCallback(True) return self._ChipStack.CallAsync( lambda: self._dmLib.pychip_DeviceController_EstablishPASESessionBLE( self.devCtrl, setupPinCode, discriminator, nodeid) @@ -496,6 +507,7 @@ def EstablishPASESessionIP(self, ipaddr: str, setupPinCode: int, nodeid: int, po self.CheckIsActive() self.state = DCState.RENDEZVOUS_ONGOING + self._enablePairingCompeleteCallback(True) return self._ChipStack.CallAsync( lambda: self._dmLib.pychip_DeviceController_EstablishPASESessionIP( self.devCtrl, ipaddr.encode("utf-8"), setupPinCode, nodeid, port) @@ -505,6 +517,7 @@ def EstablishPASESession(self, setUpCode: str, nodeid: int): self.CheckIsActive() self.state = DCState.RENDEZVOUS_ONGOING + self._enablePairingCompeleteCallback(True) return self._ChipStack.CallAsync( lambda: self._dmLib.pychip_DeviceController_EstablishPASESession( self.devCtrl, setUpCode.encode("utf-8"), nodeid) @@ -726,7 +739,7 @@ def OpenCommissioningWindow(self, nodeid: int, timeout: int, iteration: int, self.CheckIsActive() self._ChipStack.CallAsync( lambda: self._dmLib.pychip_DeviceController_OpenCommissioningWindow( - self.devCtrl, nodeid, timeout, iteration, discriminator, option) + self.devCtrl, self.pairingDelegate, nodeid, timeout, iteration, discriminator, option) ).raise_on_error() self._ChipStack.callbackRes.raise_on_error() return self._ChipStack.openCommissioningWindowPincode[nodeid] @@ -1515,16 +1528,13 @@ def _InitLib(self): self._dmLib = CDLL(self._ChipStack.LocateChipDLL()) self._dmLib.pychip_DeviceController_DeleteDeviceController.argtypes = [ - c_void_p] + c_void_p, c_void_p] self._dmLib.pychip_DeviceController_DeleteDeviceController.restype = PyChipError self._dmLib.pychip_DeviceController_ConnectBLE.argtypes = [ c_void_p, c_uint16, c_uint32, c_uint64] self._dmLib.pychip_DeviceController_ConnectBLE.restype = PyChipError - self._dmLib.pychip_DeviceController_ConnectIP.argtypes = [ - c_void_p, c_char_p, c_uint32, c_uint64] - self._dmLib.pychip_DeviceController_SetThreadOperationalDataset.argtypes = [ c_char_p, c_uint32] self._dmLib.pychip_DeviceController_SetThreadOperationalDataset.restype = PyChipError @@ -1560,7 +1570,7 @@ def _InitLib(self): self._dmLib.pychip_DeviceController_Commission.restype = PyChipError self._dmLib.pychip_DeviceController_OnNetworkCommission.argtypes = [ - c_void_p, c_uint64, c_uint32, c_uint8, c_char_p, c_uint32] + c_void_p, c_void_p, c_uint64, c_uint32, c_uint8, c_char_p, c_uint32] self._dmLib.pychip_DeviceController_OnNetworkCommission.restype = PyChipError self._dmLib.pychip_DeviceController_DiscoverCommissionableNodes.argtypes = [ @@ -1598,6 +1608,7 @@ def _InitLib(self): self._dmLib.pychip_DeviceController_EstablishPASESessionBLE.argtypes = [ c_void_p, c_uint32, c_uint16, c_uint64] self._dmLib.pychip_DeviceController_EstablishPASESessionBLE.restype = PyChipError + self._dmLib.pychip_DeviceController_EstablishPASESession.argtypes = [ c_void_p, c_char_p, c_uint64] self._dmLib.pychip_DeviceController_EstablishPASESession.restype = PyChipError @@ -1605,10 +1616,12 @@ def _InitLib(self): self._dmLib.pychip_DeviceController_DiscoverAllCommissionableNodes.argtypes = [ c_void_p] self._dmLib.pychip_DeviceController_DiscoverAllCommissionableNodes.restype = PyChipError + self._dmLib.pychip_DeviceController_PrintDiscoveredDevices.argtypes = [ c_void_p] self._dmLib.pychip_DeviceController_PrintDiscoveredDevices.argtypes = [ c_void_p, _ChipDeviceController_IterateDiscoveredCommissionableNodesFunct] + self._dmLib.pychip_DeviceController_HasDiscoveredCommissionableNode.argtypes = [c_void_p] self._dmLib.pychip_DeviceController_HasDiscoveredCommissionableNode.restype = c_bool @@ -1653,9 +1666,13 @@ def _InitLib(self): self._dmLib.pychip_ScriptDevicePairingDelegate_SetCommissioningStatusUpdateCallback.restype = PyChipError self._dmLib.pychip_ScriptDevicePairingDelegate_SetFabricCheckCallback.argtypes = [ - _DevicePairingDelegate_OnFabricCheckFunct] + c_void_p, _DevicePairingDelegate_OnFabricCheckFunct] self._dmLib.pychip_ScriptDevicePairingDelegate_SetFabricCheckCallback.restype = PyChipError + self._dmLib.pychip_ScriptDevicePairingDelegate_SetExpectingPairingComplete.argtypes = [ + c_void_p, c_bool] + self._dmLib.pychip_ScriptDevicePairingDelegate_SetExpectingPairingComplete.restype = PyChipError + self._dmLib.pychip_GetConnectedDeviceByNodeId.argtypes = [ c_void_p, c_uint64, py_object, _DeviceAvailableCallbackFunct] self._dmLib.pychip_GetConnectedDeviceByNodeId.restype = PyChipError @@ -1683,8 +1700,9 @@ def _InitLib(self): self._dmLib.pychip_DeviceController_GetCompressedFabricId.restype = PyChipError self._dmLib.pychip_DeviceController_OpenCommissioningWindow.argtypes = [ - c_void_p, c_uint64, c_uint16, c_uint32, c_uint16, c_uint8] + c_void_p, c_void_p, c_uint64, c_uint16, c_uint32, c_uint16, c_uint8] self._dmLib.pychip_DeviceController_OpenCommissioningWindow.restype = PyChipError + self._dmLib.pychip_TestCommissionerUsed.argtypes = [] self._dmLib.pychip_TestCommissionerUsed.restype = c_bool @@ -1700,6 +1718,7 @@ def _InitLib(self): self._dmLib.pychip_SetTestCommissionerSimulateFailureOnStage.argtypes = [ c_uint8] self._dmLib.pychip_SetTestCommissionerSimulateFailureOnStage.restype = c_bool + self._dmLib.pychip_SetTestCommissionerSimulateFailureOnReport.argtypes = [ c_uint8] self._dmLib.pychip_SetTestCommissionerSimulateFailureOnReport.restype = c_bool @@ -1712,8 +1731,7 @@ def _InitLib(self): self._dmLib.pychip_GetCompletionError.restype = PyChipError self._dmLib.pychip_DeviceController_IssueNOCChain.argtypes = [ - c_void_p, py_object, c_char_p, c_size_t, c_uint64 - ] + c_void_p, py_object, c_char_p, c_size_t, c_uint64] self._dmLib.pychip_DeviceController_IssueNOCChain.restype = PyChipError self._dmLib.pychip_OpCreds_InitGroupTestingData.argtypes = [ @@ -1734,11 +1752,11 @@ def _InitLib(self): self._dmLib.pychip_DeviceController_GetLogFilter = c_uint8 self._dmLib.pychip_OpCreds_AllocateController.argtypes = [c_void_p, POINTER( - c_void_p), c_uint64, c_uint64, c_uint16, c_char_p, c_bool, c_bool, POINTER(c_uint32), c_uint32, c_void_p] + c_void_p), POINTER(c_void_p), c_uint64, c_uint64, c_uint16, c_char_p, c_bool, c_bool, POINTER(c_uint32), c_uint32, c_void_p] self._dmLib.pychip_OpCreds_AllocateController.restype = PyChipError self._dmLib.pychip_OpCreds_AllocateControllerForPythonCommissioningFLow.argtypes = [ - POINTER(c_void_p), c_void_p, POINTER(c_char), c_uint32, POINTER(c_char), c_uint32, POINTER(c_char), c_uint32, POINTER(c_char), c_uint32, c_uint16, c_bool] + POINTER(c_void_p), POINTER(c_void_p), c_void_p, POINTER(c_char), c_uint32, POINTER(c_char), c_uint32, POINTER(c_char), c_uint32, POINTER(c_char), c_uint32, c_uint16, c_bool] self._dmLib.pychip_OpCreds_AllocateControllerForPythonCommissioningFLow.restype = PyChipError self._dmLib.pychip_DeviceController_SetIpk.argtypes = [c_void_p, POINTER(c_char), c_size_t] @@ -1760,6 +1778,7 @@ def __init__(self, opCredsContext: ctypes.c_void_p, fabricId: int, nodeId: int, self._dmLib.pychip_DeviceController_SetIssueNOCChainCallbackPythonCallback(_IssueNOCChainCallbackPythonCallback) + pairingDelegate = c_void_p(None) devCtrl = c_void_p(None) c_catTags = (c_uint32 * len(catTags))() @@ -1771,7 +1790,7 @@ def __init__(self, opCredsContext: ctypes.c_void_p, fabricId: int, nodeId: int, self._externalKeyPair = keypair self._ChipStack.Call( lambda: self._dmLib.pychip_OpCreds_AllocateController(c_void_p( - opCredsContext), pointer(devCtrl), fabricId, nodeId, adminVendorId, c_char_p(None if len(paaTrustStorePath) == 0 else str.encode(paaTrustStorePath)), useTestCommissioner, self._ChipStack.enableServerInteractions, c_catTags, len(catTags), None if keypair is None else keypair.native_object) + opCredsContext), pointer(devCtrl), pointer(pairingDelegate), fabricId, nodeId, adminVendorId, c_char_p(None if len(paaTrustStorePath) == 0 else str.encode(paaTrustStorePath)), useTestCommissioner, self._ChipStack.enableServerInteractions, c_catTags, len(catTags), None if keypair is None else keypair.native_object) ).raise_on_error() self._fabricAdmin = fabricAdmin @@ -1779,7 +1798,7 @@ def __init__(self, opCredsContext: ctypes.c_void_p, fabricId: int, nodeId: int, self._nodeId = nodeId self._caIndex = fabricAdmin.caIndex - self._set_dev_ctrl(devCtrl=devCtrl) + self._set_dev_ctrl(devCtrl=devCtrl, pairingDelegate=pairingDelegate) self._finish_init() @@ -1925,9 +1944,10 @@ def CommissionOnNetwork(self, nodeId: int, setupPinCode: int, self._ChipStack.commissioningCompleteEvent.clear() + self._enablePairingCompeleteCallback(True) self._ChipStack.CallAsync( lambda: self._dmLib.pychip_DeviceController_OnNetworkCommission( - self.devCtrl, nodeId, setupPinCode, int(filterType), str(filter).encode("utf-8") + b"\x00" if filter is not None else None, discoveryTimeoutMsec) + self.devCtrl, self.pairingDelegate, nodeId, setupPinCode, int(filterType), str(filter).encode("utf-8") + b"\x00" if filter is not None else None, discoveryTimeoutMsec) ) if not self._ChipStack.commissioningCompleteEvent.isSet(): # Error 50 is a timeout @@ -1948,6 +1968,7 @@ def CommissionWithCode(self, setupPayload: str, nodeid: int, discoveryType: Disc self._ChipStack.commissioningCompleteEvent.clear() + self._enablePairingCompeleteCallback(True) self._ChipStack.CallAsync( lambda: self._dmLib.pychip_DeviceController_ConnectWithCode( self.devCtrl, setupPayload, nodeid, discoveryType.value) @@ -1967,6 +1988,7 @@ def CommissionIP(self, ipaddr: str, setupPinCode: int, nodeid: int) -> PyChipErr self._ChipStack.commissioningCompleteEvent.clear() + self._enablePairingCompeleteCallback(True) self._ChipStack.CallAsync( lambda: self._dmLib.pychip_DeviceController_ConnectIP( self.devCtrl, ipaddr.encode("utf-8"), setupPinCode, nodeid) @@ -2011,6 +2033,7 @@ def __init__(self, operationalKey: p256keypair.P256Keypair, noc: bytes, ''' super().__init__(name or f"ctrl(v/{adminVendorId})") + pairingDelegate = c_void_p(None) devCtrl = c_void_p(None) # Device should hold a reference to the key to avoid it being GC-ed. @@ -2019,9 +2042,9 @@ def __init__(self, operationalKey: p256keypair.P256Keypair, noc: bytes, self._ChipStack.Call( lambda: self._dmLib.pychip_OpCreds_AllocateControllerForPythonCommissioningFLow( - c_void_p(devCtrl), nativeKey, noc, len(noc), icac, len(icac) if icac else 0, rcac, len(rcac), ipk, len(ipk) if ipk else 0, adminVendorId, self._ChipStack.enableServerInteractions) + c_void_p(devCtrl), c_void_p(pairingDelegate), nativeKey, noc, len(noc), icac, len(icac) if icac else 0, rcac, len(rcac), ipk, len(ipk) if ipk else 0, adminVendorId, self._ChipStack.enableServerInteractions) ).raise_on_error() - self._set_dev_ctrl(devCtrl) + self._set_dev_ctrl(devCtrl, pairingDelegate) self._finish_init() From 294f29e7c82d878893f7fc9a04359bd37084668c Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Wed, 17 Apr 2024 13:10:18 -0400 Subject: [PATCH 17/38] Stop assuming all unknown attributes have the C quality in MTRDevice. (#33009) * Stop assuming all unknown attributes have the C quality in MTRDevice. Assume things we don't know about don't have the C quality unless told to assume otherwise. Also fixes XPC detection check; non-XPC controllers also respond to the "sharedControllerWithID:xpcConnectBlock:" selector. * Address review comments and fix tests to work in the new setup. --- src/darwin/Framework/CHIP/MTRCluster.h | 11 + src/darwin/Framework/CHIP/MTRCluster.mm | 3 + src/darwin/Framework/CHIP/MTRDevice.mm | 38 +++- .../Framework/CHIPTests/MTRDeviceTests.m | 7 +- .../CHIPTests/MTRPerControllerStorageTests.m | 215 +++++++++++------- .../CHIPTests/MTRSwiftDeviceTests.swift | 6 +- .../TestHelpers/MTRDeviceTestDelegate.h | 1 + .../TestHelpers/MTRDeviceTestDelegate.m | 5 + 8 files changed, 199 insertions(+), 87 deletions(-) diff --git a/src/darwin/Framework/CHIP/MTRCluster.h b/src/darwin/Framework/CHIP/MTRCluster.h index f1863bbc05bc95..f1d876e493d73e 100644 --- a/src/darwin/Framework/CHIP/MTRCluster.h +++ b/src/darwin/Framework/CHIP/MTRCluster.h @@ -132,6 +132,17 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) */ @property (nonatomic, copy, nullable) NSNumber * minEventNumber MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)); +/** + * Controls whether attributes without known schema (e.g. vendor-specific + * attributes) should be assumed to be reportable normally via subscriptions. + * The default is YES. + * + * This setting is only relevant to some consumers of MTRReadParams. One of + * those consumers is readAttributeWithEndpointID:clusterID:attributeID:params: + * on MTRDevice. + */ +@property (nonatomic, assign, getter=shouldAssumeUnknownAttributesReportable) BOOL assumeUnknownAttributesReportable MTR_NEWLY_AVAILABLE; + @end /** diff --git a/src/darwin/Framework/CHIP/MTRCluster.mm b/src/darwin/Framework/CHIP/MTRCluster.mm index 96d4c4bb462e34..9bcb38e4cbac99 100644 --- a/src/darwin/Framework/CHIP/MTRCluster.mm +++ b/src/darwin/Framework/CHIP/MTRCluster.mm @@ -84,6 +84,7 @@ - (instancetype)init { if (self = [super init]) { _filterByFabric = YES; + _assumeUnknownAttributesReportable = YES; } return self; } @@ -93,6 +94,7 @@ - (id)copyWithZone:(NSZone * _Nullable)zone auto other = [[MTRReadParams alloc] init]; other.filterByFabric = self.filterByFabric; other.minEventNumber = self.minEventNumber; + other.assumeUnknownAttributesReportable = self.assumeUnknownAttributesReportable; return other; } @@ -124,6 +126,7 @@ - (id)copyWithZone:(NSZone * _Nullable)zone auto other = [[MTRSubscribeParams alloc] initWithMinInterval:self.minInterval maxInterval:self.maxInterval]; other.filterByFabric = self.filterByFabric; other.minEventNumber = self.minEventNumber; + other.assumeUnknownAttributesReportable = self.assumeUnknownAttributesReportable; other.replaceExistingSubscriptions = self.replaceExistingSubscriptions; other.reportEventsUrgently = self.reportEventsUrgently; other.resubscribeAutomatically = self.resubscribeAutomatically; diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index aa63efe1033633..5226759ebf4b51 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -336,6 +336,7 @@ - (void)unitTestReportEndForDevice:(MTRDevice *)device; - (BOOL)unitTestShouldSetUpSubscriptionForDevice:(MTRDevice *)device; - (BOOL)unitTestShouldSkipExpectedValuesForWrite:(MTRDevice *)device; - (NSNumber *)unitTestMaxIntervalOverrideForSubscription:(MTRDevice *)device; +- (BOOL)unitTestForceAttributeReportsIfMatchingCache:(MTRDevice *)device; @end #endif @@ -745,7 +746,7 @@ - (BOOL)_subscriptionAbleToReport // Unfortunately, we currently have no subscriptions over our hacked-up XPC // setup. Try to detect that situation. - if ([_deviceController.class respondsToSelector:@selector(sharedControllerWithID:xpcConnectBlock:)]) { + if ([_deviceController isKindOfClass:MTRDeviceControllerOverXPC.class]) { return NO; } @@ -1603,24 +1604,33 @@ static BOOL AttributeHasChangesOmittedQuality(MTRAttributePath * attributePath) - (NSDictionary * _Nullable)readAttributeWithEndpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID - params:(MTRReadParams *)params + params:(MTRReadParams * _Nullable)params { MTRAttributePath * attributePath = [MTRAttributePath attributePathWithEndpointID:endpointID clusterID:clusterID attributeID:attributeID]; BOOL attributeIsSpecified = MTRAttributeIsSpecified(clusterID.unsignedIntValue, attributeID.unsignedIntValue); - BOOL hasChangesOmittedQuality = AttributeHasChangesOmittedQuality(attributePath); + BOOL hasChangesOmittedQuality; + if (attributeIsSpecified) { + hasChangesOmittedQuality = AttributeHasChangesOmittedQuality(attributePath); + } else { + if (params == nil) { + hasChangesOmittedQuality = NO; + } else { + hasChangesOmittedQuality = !params.assumeUnknownAttributesReportable; + } + } // Return current known / expected value right away NSDictionary * attributeValueToReturn = [self _attributeValueDictionaryForAttributePath:attributePath]; // Send read request to device if any of the following are true: - // 1. The attribute is not in the specification (so we don't know whether hasChangesOmittedQuality can be trusted). - // 2. Subscription not in a state we can expect reports - // 3. There is subscription but attribute has Changes Omitted quality - // TODO: add option for BaseSubscriptionCallback to report during priming, to reduce when case 4 is hit - if (!attributeIsSpecified || ![self _subscriptionAbleToReport] || hasChangesOmittedQuality) { + // 1. Subscription not in a state we can expect reports + // 2. The attribute has the Changes Omitted quality, so we won't get reports for it. + // 3. The attribute is not in the spec, and the read params asks to assume + // an unknown attribute has the Changes Omitted quality. + if (![self _subscriptionAbleToReport] || hasChangesOmittedQuality) { // Read requests container will be a mutable array of items, each being an array containing: // [attribute request path, params] // Batching handler should only coalesce when params are equal. @@ -2344,6 +2354,18 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray *> * data) { for (NSDictionary * attributeReponseValue in data) { @@ -1526,8 +1530,9 @@ - (void)test017_TestMTRDeviceBasics } } }; + // use the nonexistent attribute and expect read error - [device readAttributeWithEndpointID:testEndpointID clusterID:testClusterID attributeID:testAttributeID params:nil]; + [device readAttributeWithEndpointID:testEndpointID clusterID:testClusterID attributeID:testAttributeID params:readThroughForUnknownAttributesParams]; [self waitForExpectations:@[ attributeReportErrorExpectation ] timeout:10]; // Resubscription test setup diff --git a/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m b/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m index e652d2f9fddc0e..df98530a81db18 100644 --- a/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m +++ b/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m @@ -1719,7 +1719,7 @@ - (void)testControllerServer // Should be able to add this one, though; it's unrelated to any existing endpoints. XCTAssertTrue([controllerClient addServerEndpoint:endpoint5]); - __auto_type * device = [MTRBaseDevice deviceWithNodeID:nodeIDServer controller:controllerClient]; + __auto_type * baseDevice = [MTRBaseDevice deviceWithNodeID:nodeIDServer controller:controllerClient]; __auto_type * requestPath = attribute1RequestPath; __block __auto_type * responsePath = attribute1ResponsePath; @@ -1762,14 +1762,14 @@ - (void)testControllerServer // First try a basic read. XCTestExpectation * readExpectation1 = [self expectationWithDescription:@"Read 1 of attribute complete"]; - [device readAttributePaths:@[ requestPath ] - eventPaths:nil - params:nil - queue:queue - completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { - checkSingleValue(values, error, unsignedIntValue1); - [readExpectation1 fulfill]; - }]; + [baseDevice readAttributePaths:@[ requestPath ] + eventPaths:nil + params:nil + queue:queue + completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + checkSingleValue(values, error, unsignedIntValue1); + [readExpectation1 fulfill]; + }]; [self waitForExpectations:@[ readExpectation1 ] timeout:kTimeoutInSeconds]; // Now try a basic subscribe. @@ -1783,7 +1783,7 @@ - (void)testControllerServer XCTestExpectation * subscriptionEstablishedExpectation = [self expectationWithDescription:@"Basic subscription established"]; __auto_type * subscribeParams = [[MTRSubscribeParams alloc] initWithMinInterval:@(0) maxInterval:@(10)]; - [device subscribeToAttributesWithEndpointID:requestPath.endpoint clusterID:requestPath.cluster attributeID:requestPath.attribute + [baseDevice subscribeToAttributesWithEndpointID:requestPath.endpoint clusterID:requestPath.cluster attributeID:requestPath.attribute params:subscribeParams queue:queue reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { @@ -1809,14 +1809,14 @@ - (void)testControllerServer requestPath = attribute2RequestPath; responsePath = attribute2ResponsePath; XCTestExpectation * readNoPermissionsExpectation1 = [self expectationWithDescription:@"Read 1 of attribute with no permissions complete"]; - [device readAttributePaths:@[ requestPath ] - eventPaths:nil - params:nil - queue:queue - completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { - checkSinglePathError(values, error, MTRInteractionErrorCodeUnsupportedAccess); - [readNoPermissionsExpectation1 fulfill]; - }]; + [baseDevice readAttributePaths:@[ requestPath ] + eventPaths:nil + params:nil + queue:queue + completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + checkSinglePathError(values, error, MTRInteractionErrorCodeUnsupportedAccess); + [readNoPermissionsExpectation1 fulfill]; + }]; [self waitForExpectations:@[ readNoPermissionsExpectation1 ] timeout:kTimeoutInSeconds]; // Change the permissions to give Manage access on the cluster to some @@ -1826,14 +1826,14 @@ - (void)testControllerServer [cluster2 addAccessGrant:unrelatedGrant]; XCTestExpectation * readNoPermissionsExpectation2 = [self expectationWithDescription:@"Read 2 of attribute with no permissions complete"]; - [device readAttributePaths:@[ requestPath ] - eventPaths:nil - params:nil - queue:queue - completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { - checkSinglePathError(values, error, MTRInteractionErrorCodeUnsupportedAccess); - [readNoPermissionsExpectation2 fulfill]; - }]; + [baseDevice readAttributePaths:@[ requestPath ] + eventPaths:nil + params:nil + queue:queue + completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + checkSinglePathError(values, error, MTRInteractionErrorCodeUnsupportedAccess); + [readNoPermissionsExpectation2 fulfill]; + }]; [self waitForExpectations:@[ readNoPermissionsExpectation2 ] timeout:kTimeoutInSeconds]; // Change the permissions to give Manage access on the cluster to our client @@ -1843,14 +1843,14 @@ - (void)testControllerServer [cluster2 addAccessGrant:clientManageGrant]; XCTestExpectation * readExpectation2 = [self expectationWithDescription:@"Read 2 of attribute complete"]; - [device readAttributePaths:@[ requestPath ] - eventPaths:nil - params:nil - queue:queue - completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { - checkSingleValue(values, error, listOfStructsValue1); - [readExpectation2 fulfill]; - }]; + [baseDevice readAttributePaths:@[ requestPath ] + eventPaths:nil + params:nil + queue:queue + completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + checkSingleValue(values, error, listOfStructsValue1); + [readExpectation2 fulfill]; + }]; [self waitForExpectations:@[ readExpectation2 ] timeout:kTimeoutInSeconds]; // Adding Manage permissions to one cluster should not affect another one. @@ -1858,14 +1858,14 @@ - (void)testControllerServer responsePath = attribute3ResponsePath; XCTestExpectation * readNoPermissionsExpectation3 = [self expectationWithDescription:@"Read 3 of attribute with no permissions complete"]; - [device readAttributePaths:@[ requestPath ] - eventPaths:nil - params:nil - queue:queue - completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { - checkSinglePathError(values, error, MTRInteractionErrorCodeUnsupportedAccess); - [readNoPermissionsExpectation3 fulfill]; - }]; + [baseDevice readAttributePaths:@[ requestPath ] + eventPaths:nil + params:nil + queue:queue + completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + checkSinglePathError(values, error, MTRInteractionErrorCodeUnsupportedAccess); + [readNoPermissionsExpectation3 fulfill]; + }]; [self waitForExpectations:@[ readNoPermissionsExpectation3 ] timeout:kTimeoutInSeconds]; // But adding Manage permissions on the endpoint should grant Operate on @@ -1873,28 +1873,28 @@ - (void)testControllerServer [endpoint1 addAccessGrant:clientManageGrant]; XCTestExpectation * readExpectation3 = [self expectationWithDescription:@"Read 3 of attribute complete"]; - [device readAttributePaths:@[ requestPath ] - eventPaths:nil - params:nil - queue:queue - completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { - checkSingleValue(values, error, unsignedIntValue1); - [readExpectation3 fulfill]; - }]; + [baseDevice readAttributePaths:@[ requestPath ] + eventPaths:nil + params:nil + queue:queue + completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + checkSingleValue(values, error, unsignedIntValue1); + [readExpectation3 fulfill]; + }]; [self waitForExpectations:@[ readExpectation3 ] timeout:kTimeoutInSeconds]; // And removing that grant should remove the permissions again. [endpoint1 removeAccessGrant:clientManageGrant]; XCTestExpectation * readNoPermissionsExpectation4 = [self expectationWithDescription:@"Read 4 of attribute with no permissions complete"]; - [device readAttributePaths:@[ requestPath ] - eventPaths:nil - params:nil - queue:queue - completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { - checkSinglePathError(values, error, MTRInteractionErrorCodeUnsupportedAccess); - [readNoPermissionsExpectation4 fulfill]; - }]; + [baseDevice readAttributePaths:@[ requestPath ] + eventPaths:nil + params:nil + queue:queue + completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + checkSinglePathError(values, error, MTRInteractionErrorCodeUnsupportedAccess); + [readNoPermissionsExpectation4 fulfill]; + }]; [self waitForExpectations:@[ readNoPermissionsExpectation4 ] timeout:kTimeoutInSeconds]; // Now do a wildcard read on the endpoint and check that this does the right @@ -1926,23 +1926,23 @@ - (void)testControllerServer }; #endif XCTestExpectation * wildcardReadExpectation = [self expectationWithDescription:@"Wildcard read of our endpoint"]; - [device readAttributePaths:@[ [MTRAttributeRequestPath requestPathWithEndpointID:endpointId1 clusterID:nil attributeID:nil] ] - eventPaths:nil - params:nil - queue:queue - completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { - XCTAssertNil(error); - XCTAssertNotNil(values); - - // TODO: Figure out how to test that values is correct that's not - // too fragile if things get returned in different valid order. - // For now just check that every path we got has a value, not an - // error. - for (NSDictionary * value in values) { - XCTAssertNotNil(value[MTRAttributePathKey]); - XCTAssertNil(value[MTRErrorKey]); - XCTAssertNotNil(value[MTRDataKey]); - } + [baseDevice readAttributePaths:@[ [MTRAttributeRequestPath requestPathWithEndpointID:endpointId1 clusterID:nil attributeID:nil] ] + eventPaths:nil + params:nil + queue:queue + completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + XCTAssertNil(error); + XCTAssertNotNil(values); + + // TODO: Figure out how to test that values is correct that's not + // too fragile if things get returned in different valid order. + // For now just check that every path we got has a value, not an + // error. + for (NSDictionary * value in values) { + XCTAssertNotNil(value[MTRAttributePathKey]); + XCTAssertNil(value[MTRErrorKey]); + XCTAssertNotNil(value[MTRDataKey]); + } #if 0 XCTAssertEqualObjects(values, @[ // cluster1 @@ -1960,10 +1960,71 @@ - (void)testControllerServer ]); #endif - [wildcardReadExpectation fulfill]; - }]; + [wildcardReadExpectation fulfill]; + }]; [self waitForExpectations:@[ wildcardReadExpectation ] timeout:kTimeoutInSeconds]; + // Do some MTRDevice testing against this convenient server we have that has + // vendor-specific attributes. + __auto_type * device = [MTRDevice deviceWithNodeID:nodeIDServer controller:controllerClient]; + __auto_type * delegate = [[MTRDeviceTestDelegate alloc] init]; + delegate.forceAttributeReportsIfMatchingCache = YES; + + XCTestExpectation * gotReportsExpectation = [self expectationWithDescription:@"MTRDevice subscription established"]; + delegate.onReportEnd = ^() { + [gotReportsExpectation fulfill]; + }; + + [device setDelegate:delegate queue:queue]; + + [self waitForExpectations:@[ gotReportsExpectation ] timeout:kTimeoutInSeconds]; + + delegate.onReportEnd = nil; + + // Test read-through behavior of non-standard (as in, not present in Matter XML) attributes. + XCTestExpectation * nonStandardReadThroughExpectation = [self expectationWithDescription:@"Read-throughs of non-standard attributes complete"]; + + delegate.onAttributeDataReceived = ^(NSArray *> * attributeReports) { + XCTAssertNotNil(attributeReports); + + for (NSDictionary * report in attributeReports) { + XCTAssertNil(report[MTRErrorKey]); + + XCTAssertNotNil(report[MTRDataKey]); + XCTAssertNotNil(report[MTRAttributePathKey]); + + // We only expect to get a report for the read that opted in to be + // treated as "C" + XCTAssertEqualObjects(report[MTRAttributePathKey], attribute2ResponsePath); + + // Strip out the DataVersion before comparing values, since our + // local value does not have that. + __auto_type * reportValue = [NSMutableDictionary dictionaryWithDictionary:report[MTRDataKey]]; + reportValue[MTRDataVersionKey] = nil; + XCTAssertEqualObjects(reportValue, listOfStructsValue1); + + [nonStandardReadThroughExpectation fulfill]; + } + }; + + __auto_type * attrValue = [device readAttributeWithEndpointID:attribute1ResponsePath.endpoint + clusterID:attribute1ResponsePath.cluster + attributeID:attribute1ResponsePath.attribute + params:nil]; + XCTAssertNotNil(attrValue); + XCTAssertEqualObjects(attrValue, unsignedIntValue2); + + __auto_type * params = [[MTRReadParams alloc] init]; + params.assumeUnknownAttributesReportable = NO; + attrValue = [device readAttributeWithEndpointID:attribute2ResponsePath.endpoint + clusterID:attribute2ResponsePath.cluster + attributeID:attribute2ResponsePath.attribute + params:params]; + XCTAssertNotNil(attrValue); + XCTAssertEqualObjects(attrValue, listOfStructsValue1); + + [self waitForExpectations:@[ nonStandardReadThroughExpectation ] timeout:kTimeoutInSeconds]; + [controllerClient shutdown]; [controllerServer shutdown]; } diff --git a/src/darwin/Framework/CHIPTests/MTRSwiftDeviceTests.swift b/src/darwin/Framework/CHIPTests/MTRSwiftDeviceTests.swift index 1276868a14c5fa..21d61cb0360ac1 100644 --- a/src/darwin/Framework/CHIPTests/MTRSwiftDeviceTests.swift +++ b/src/darwin/Framework/CHIPTests/MTRSwiftDeviceTests.swift @@ -287,6 +287,10 @@ class MTRSwiftDeviceTests : XCTestCase { wait(for: [ expectedValueReportedExpectation, expectedValueRemovedExpectation ], timeout: 5, enforceOrder: true) // Test if errors are properly received + // TODO: We might stop reporting these altogether from MTRDevice, and then + // this test will need updating. + let readThroughForUnknownAttributesParams = MTRReadParams() + readThroughForUnknownAttributesParams.shouldAssumeUnknownAttributesReportable = false; let attributeReportErrorExpectation = expectation(description: "Attribute read error") delegate.onAttributeDataReceived = { (data: [[ String: Any ]]) -> Void in for attributeReponseValue in data { @@ -296,7 +300,7 @@ class MTRSwiftDeviceTests : XCTestCase { } } // use the nonexistent attribute and expect read error - device.readAttribute(withEndpointID: testEndpointID, clusterID: testClusterID, attributeID: testAttributeID, params: nil) + device.readAttribute(withEndpointID: testEndpointID, clusterID: testClusterID, attributeID: testAttributeID, params: readThroughForUnknownAttributesParams) wait(for: [ attributeReportErrorExpectation ], timeout: 10) // Resubscription test setup diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRDeviceTestDelegate.h b/src/darwin/Framework/CHIPTests/TestHelpers/MTRDeviceTestDelegate.h index 27596bcafeae7f..5c42d2eb1022af 100644 --- a/src/darwin/Framework/CHIPTests/TestHelpers/MTRDeviceTestDelegate.h +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRDeviceTestDelegate.h @@ -29,6 +29,7 @@ typedef void (^MTRDeviceTestDelegateDataHandler)(NSArray Date: Wed, 17 Apr 2024 11:31:31 -0700 Subject: [PATCH 18/38] android/tv-casting-app: Implemented commands and attributes using CHIPInteractionClient.jar (#33019) --- examples/tv-casting-app/APIs.md | 162 ++++++++++++++++-- .../com/chip/casting/app/MainActivity.java | 14 ++ .../casting/util/GlobalCastingConstants.java | 3 +- .../casting/ActionSelectorFragment.java | 24 +++ ...ationBasicReadVendorIDExampleFragment.java | 125 ++++++++++++++ ...ntentLauncherLaunchURLExampleFragment.java | 81 ++++++--- .../casting/EndpointSelectorExample.java | 34 ++++ ...ubscribeToCurrentStateExampleFragment.java | 148 ++++++++++++++++ .../jni/com/matter/casting/core/Endpoint.java | 6 + .../matter/casting/core/MatterEndpoint.java | 60 +++++++ .../main/jni/cpp/core/MatterEndpoint-JNI.cpp | 25 ++- .../main/jni/cpp/core/MatterEndpoint-JNI.h | 12 ++ .../main/jni/cpp/support/Converters-JNI.cpp | 24 +++ .../src/main/jni/cpp/support/Converters-JNI.h | 2 + .../main/jni/cpp/support/MatterCallback-JNI.h | 3 +- .../fragment_matter_action_selector.xml | 18 ++ ...atter_application_basic_read_vendor_id.xml | 48 ++++++ ...ent_matter_content_launcher_launch_url.xml | 6 +- ...media_playback_subscribe_current_state.xml | 57 ++++++ .../App/app/src/main/res/values/strings.xml | 8 + examples/tv-casting-app/android/README.md | 12 ++ 21 files changed, 830 insertions(+), 42 deletions(-) create mode 100644 examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ApplicationBasicReadVendorIDExampleFragment.java create mode 100644 examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/EndpointSelectorExample.java create mode 100644 examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/MediaPlaybackSubscribeToCurrentStateExampleFragment.java create mode 100644 examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_matter_application_basic_read_vendor_id.xml create mode 100644 examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_matter_media_playback_subscribe_current_state.xml diff --git a/examples/tv-casting-app/APIs.md b/examples/tv-casting-app/APIs.md index 12f5fdf30ebc1b..1bbb189e7179d7 100644 --- a/examples/tv-casting-app/APIs.md +++ b/examples/tv-casting-app/APIs.md @@ -688,7 +688,7 @@ func startDiscovery() { } ``` -Note: You will need to connect with a Casting Player as described below to see +Note: You will need to connect with a Casting Player as described below to se the list of Endpoints that they support. Refer to the [Connection](#connect-to-a-casting-player) section for details on how to discover available endpoints supported by a Casting Player. @@ -696,7 +696,8 @@ discover available endpoints supported by a Casting Player. ### Connect to a Casting Player _{Complete Connection examples: [Linux](linux/simple-app-helper.cpp) | -[iOS](darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift)}_ +[Android](android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java) +| [iOS](darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift)}_ Each `CastingPlayer` object created during [Discovery](#discover-casting-players) contains information such as @@ -830,7 +831,7 @@ func connect(selectedCastingPlayer: MCCastingPlayer?) { ### Select an Endpoint on the Casting Player _{Complete Endpoint selection examples: [Linux](linux/simple-app-helper.cpp) | -[Android](android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java) +[Android](android/App/app/src/main/java/com/matter/casting/EndpointSelectorExample.java) | [iOS](darwin/TvCasting/TvCasting/MCContentLauncherLaunchURLExampleViewModel.swift)}_ @@ -863,7 +864,7 @@ On Android, it can select an `Endpoint` as shown below. ```java private static final Integer SAMPLE_ENDPOINT_VID = 65521; -private Endpoint selectEndpoint() +private Endpoint selectFirstEndpointByVID() { Endpoint endpoint = null; if(selectedCastingPlayer != null) @@ -905,18 +906,25 @@ Once the Casting Client has selected an `Endpoint`, it is ready to [issue commands](#issuing-commands) to it, [read](#read-operations) current playback state, and [subscribe](#subscriptions) to playback events. -On Linux refer to the following platform specific files: +Refer to the following platform specific files, to find the list of clusters, +commands and attributes, with their request/response types available for use +with the Matter TV Casting library. + +For Linux, refer to the following files: -1. For a list of clusters, commands and attributes supported by the Matter TV - Casting library: +1. For a list of supported clusters, commands and attributes: [tv-casting-common/clusters/Clusters.h](tv-casting-common/clusters/Clusters.h) 2. For the IDs and request / response types to use with these APIs: [/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h](/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h) -On iOS refer to the following platform specific files: +For Android, refer to the following files: + +1. For a list of supported clusters, commands and attributes: + [/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java](/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java) -1. For a list of clusters, commands and attributes supported by the Matter TV - Casting library: +On iOS, refer to the following files: + +1. For a list of supported clusters, commands and attribute: [darwin/MatterTvCastingBridge/MatterTvCastingBridge/zap-generated/MCClusterObjects.h](darwin/MatterTvCastingBridge/MatterTvCastingBridge/zap-generated/MCClusterObjects.h) 2. For the IDs and request / response types to use with the commands: [darwin/MatterTvCastingBridge/MatterTvCastingBridge/zap-generated/MCCommandObjects.h](darwin/MatterTvCastingBridge/MatterTvCastingBridge/zap-generated/MCCommandObjects.h) @@ -929,6 +937,8 @@ On iOS refer to the following platform specific files: ### Issuing Commands _{Complete Command invocation examples: [Linux](linux/simple-app-helper.cpp) | +[Android](android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java) +| [iOS](darwin/TvCasting/TvCasting/MCContentLauncherLaunchURLExampleViewModel.swift)}_ The Casting Client can get a reference to an `Endpoint` on a `CastingPlayer`, @@ -975,6 +985,51 @@ void InvokeContentLauncherLaunchURL(matter::casting::memory::Strong data) { + Log.d(TAG, "LaunchURL success. Status: " + status + ", Data: " + data); + new Handler(Looper.getMainLooper()) + .post( + () -> { + TextView launcherResult = getView().findViewById(R.id.launcherResult); + launcherResult.setText( + "LaunchURL result\nStatus: " + status + ", Data: " + data); + }); + } + + @Override + public void onError(Exception error) { + Log.e(TAG, "LaunchURL failure " + error); + new Handler(Looper.getMainLooper()) + .post( + () -> { + TextView launcherResult = getView().findViewById(R.id.launcherResult); + launcherResult.setText("LaunchURL result\nError: " + error); + }); + } + }, + contentUrl, + Optional.of(contentDisplayString), + Optional.empty()); +``` + On iOS, given an `MCEndpoint` endpoint, it can send a `LaunchURL` command (part of the Content Launcher cluster) by calling the `invoke` API on a `MCContentLauncherClusterLaunchURLCommand` @@ -1033,6 +1088,8 @@ timedInvokeTimeoutMs: 5000) // time out after 5000ms ### Read Operations _{Complete Attribute Read examples: [Linux](linux/simple-app-helper.cpp) | +[Android](android/App/app/src/main/java/com/matter/casting/ApplicationBasicReadVendorIDExampleFragment.java) +| [iOS](darwin/TvCasting/TvCasting/MCApplicationBasicReadVendorIDExampleViewModel.swift)}_ The `CastingClient` may read an Attribute from the `Endpoint` on the @@ -1080,6 +1137,45 @@ void ReadApplicationBasicVendorID(matter::casting::memory::Strong { + TextView vendorIdResult = getView().findViewById(R.id.vendorIdResult); + vendorIdResult.setText( + "Read VendorID result\nValue: " + value ); + }); + } + + @Override + public void onError(Exception error) { + Log.e(TAG, "ReadVendorID failure " + error); + new Handler(Looper.getMainLooper()) + .post( + () -> { + TextView vendorIdResult = getView().findViewById(R.id.vendorIdResult); + vendorIdResult.setText("Read VendorID result\nError: " + error); + }); + } +}); +``` + On iOS, given a `MCEndpoint`, the `VendorID` can be read similarly, by calling the `read` API on the `MCApplicationBasicClusterVendorIDAttribute` @@ -1138,6 +1234,9 @@ vendorIDAttribute!.read(nil) { context, before, after, err in ### Subscriptions _{Complete Attribute subscription examples: [Linux](linux/simple-app-helper.cpp) +| +[Android](android/App/app/src/main/java/com/matter/casting/MediaPlaybackSubscribeToCurrentStateExampleFragment.java) +| |[iOS](darwin/TvCasting/TvCasting/MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift)}_ A Casting Client may subscribe to an attribute on an `Endpoint` of the @@ -1187,6 +1286,49 @@ void SubscribeToMediaPlaybackCurrentState(matter::casting::memory::Strong { + TextView currentStateResult = getView().findViewById(R.id.currentStateResult); + currentStateResult.setText( + "Current State result\nValue: " + value ); + }); + } + + @Override + public void onError(Exception error) { + Log.e(TAG, "Read failure on subscription: " + error); + new Handler(Looper.getMainLooper()) + .post( + () -> { + TextView currentStateResult = getView().findViewById(R.id.currentStateResult); + currentStateResult.setText("Current State result\nError: " + error); + }); + } + }, 0, 1); +``` + On iOS, given a `MCEndpoint`, `CurrentState` can be subscribed to by calling the `subscribe` API on the it can subscribe to the `CurrentState` (part of the Media Playback Basic cluster) by calling the `Subscribe` API on the diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java index 6e54ff61b01c7e..cb04518f5c6259 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java @@ -11,10 +11,12 @@ import com.chip.casting.TvCastingApp; import com.chip.casting.util.GlobalCastingConstants; import com.matter.casting.ActionSelectorFragment; +import com.matter.casting.ApplicationBasicReadVendorIDExampleFragment; import com.matter.casting.ConnectionExampleFragment; import com.matter.casting.ContentLauncherLaunchURLExampleFragment; import com.matter.casting.DiscoveryExampleFragment; import com.matter.casting.InitializationExample; +import com.matter.casting.MediaPlaybackSubscribeToCurrentStateExampleFragment; import com.matter.casting.PreferencesConfigurationManager; import com.matter.casting.core.CastingPlayer; import java.util.Random; @@ -85,6 +87,18 @@ public void handleContentLauncherLaunchURLSelected(CastingPlayer selectedCasting showFragment(ContentLauncherLaunchURLExampleFragment.newInstance(selectedCastingPlayer)); } + @Override + public void handleApplicationBasicReadVendorIDSelected(CastingPlayer selectedCastingPlayer) { + showFragment(ApplicationBasicReadVendorIDExampleFragment.newInstance(selectedCastingPlayer)); + } + + @Override + public void handleMediaPlaybackSubscribeToCurrentStateSelected( + CastingPlayer selectedCastingPlayer) { + showFragment( + MediaPlaybackSubscribeToCurrentStateExampleFragment.newInstance(selectedCastingPlayer)); + } + @Override public void handleContentLauncherSelected() { showFragment(ContentLauncherFragment.newInstance(tvCastingApp)); diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/GlobalCastingConstants.java b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/GlobalCastingConstants.java index d063cc7e6f78c5..483f1efedb1b46 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/GlobalCastingConstants.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/GlobalCastingConstants.java @@ -6,5 +6,6 @@ public class GlobalCastingConstants { public static final int SetupPasscode = 20202021; public static final int Discriminator = 0xF00; public static final boolean ChipCastingSimplified = - false; // set this flag to true to demo simplified casting APIs + true; // set to true, to demo the simplified casting APIs. Otherwise, the older deprecated + // APIs are invoked } diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ActionSelectorFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ActionSelectorFragment.java index 2906ad186d6054..cb5d2170e96d6d 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ActionSelectorFragment.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ActionSelectorFragment.java @@ -33,6 +33,8 @@ public class ActionSelectorFragment extends Fragment { private final CastingPlayer selectedCastingPlayer; private View.OnClickListener selectContentLauncherButtonClickListener; + private View.OnClickListener selectApplicationBasicButtonClickListener; + private View.OnClickListener selectMediaPlaybackButtonClickListener; private View.OnClickListener disconnectButtonClickListener; public ActionSelectorFragment(CastingPlayer selectedCastingPlayer) { @@ -64,6 +66,16 @@ public View onCreateView( Log.d(TAG, "handle() called on selectContentLauncherButtonClickListener"); callback.handleContentLauncherLaunchURLSelected(selectedCastingPlayer); }; + this.selectApplicationBasicButtonClickListener = + v -> { + Log.d(TAG, "handle() called on selectApplicationBasicButtonClickListener"); + callback.handleApplicationBasicReadVendorIDSelected(selectedCastingPlayer); + }; + this.selectMediaPlaybackButtonClickListener = + v -> { + Log.d(TAG, "handle() called on selectMediaPlaybackButtonClickListener"); + callback.handleMediaPlaybackSubscribeToCurrentStateSelected(selectedCastingPlayer); + }; this.disconnectButtonClickListener = v -> { @@ -82,6 +94,12 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { getView() .findViewById(R.id.selectContentLauncherLaunchURLButton) .setOnClickListener(selectContentLauncherButtonClickListener); + getView() + .findViewById(R.id.selectApplicationBasicReadVendorIDButton) + .setOnClickListener(selectApplicationBasicButtonClickListener); + getView() + .findViewById(R.id.selectMediaPlaybackSubscribeToCurrentStateButton) + .setOnClickListener(selectMediaPlaybackButtonClickListener); getView().findViewById(R.id.disconnectButton).setOnClickListener(disconnectButtonClickListener); } @@ -91,6 +109,12 @@ public interface Callback { /** Notifies listener to trigger transition on selection of Content Launcher cluster */ void handleContentLauncherLaunchURLSelected(CastingPlayer selectedCastingPlayer); + /** Notifies listener to trigger transition on selection of Application Basic cluster */ + void handleApplicationBasicReadVendorIDSelected(CastingPlayer selectedCastingPlayer); + + /** Notifies listener to trigger transition on selection of Media PLayback cluster */ + void handleMediaPlaybackSubscribeToCurrentStateSelected(CastingPlayer selectedCastingPlayer); + /** Notifies listener to trigger transition on click of the Disconnect button */ void handleDisconnect(); } diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ApplicationBasicReadVendorIDExampleFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ApplicationBasicReadVendorIDExampleFragment.java new file mode 100644 index 00000000000000..878c18019f4d09 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ApplicationBasicReadVendorIDExampleFragment.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2024 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. + */ +package com.matter.casting; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import chip.devicecontroller.ChipClusters; +import com.R; +import com.matter.casting.core.CastingPlayer; +import com.matter.casting.core.Endpoint; + +/** + * A {@link Fragment} to read the VendorID (from ApplicationBasic cluster) using the TV Casting App. + */ +public class ApplicationBasicReadVendorIDExampleFragment extends Fragment { + private static final String TAG = + ApplicationBasicReadVendorIDExampleFragment.class.getSimpleName(); + + private final CastingPlayer selectedCastingPlayer; + + private View.OnClickListener readButtonClickListener; + + public ApplicationBasicReadVendorIDExampleFragment(CastingPlayer selectedCastingPlayer) { + this.selectedCastingPlayer = selectedCastingPlayer; + } + + /** + * Use this factory method to create a new instance of this fragment using the provided + * parameters. + * + * @param selectedCastingPlayer CastingPlayer that the casting app connected to + * @return A new instance of fragment ApplicationBasicReadVendorIDExampleFragment. + */ + public static ApplicationBasicReadVendorIDExampleFragment newInstance( + CastingPlayer selectedCastingPlayer) { + return new ApplicationBasicReadVendorIDExampleFragment(selectedCastingPlayer); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + this.readButtonClickListener = + v -> { + Endpoint endpoint = + EndpointSelectorExample.selectFirstEndpointByVID(selectedCastingPlayer); + if (endpoint == null) { + Log.e(TAG, "No Endpoint with sample vendorID found on CastingPlayer"); + return; + } + + // get ChipClusters.ApplicationBasic from the endpoint + ChipClusters.ApplicationBasicCluster cluster = + endpoint.getCluster(ChipClusters.ApplicationBasicCluster.class); + if (cluster == null) { + Log.e( + TAG, + "Could not get ApplicationBasicCluster for endpoint with ID: " + endpoint.getId()); + return; + } + + // call readVendorIDAttribute on the cluster object while passing in a + // ChipClusters.IntegerAttributeCallback + cluster.readVendorIDAttribute( + new ChipClusters.IntegerAttributeCallback() { + @Override + public void onSuccess(int value) { + Log.d(TAG, "ReadVendorID success. Value: " + value); + new Handler(Looper.getMainLooper()) + .post( + () -> { + TextView vendorIdResult = getView().findViewById(R.id.vendorIdResult); + vendorIdResult.setText("Read VendorID result\nValue: " + value); + }); + } + + @Override + public void onError(Exception error) { + Log.e(TAG, "ReadVendorID failure " + error); + new Handler(Looper.getMainLooper()) + .post( + () -> { + TextView vendorIdResult = getView().findViewById(R.id.vendorIdResult); + vendorIdResult.setText("Read VendorID result\nError: " + error); + }); + } + }); + }; + return inflater.inflate( + R.layout.fragment_matter_application_basic_read_vendor_id, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Log.d(TAG, "ApplicationBasicReadVendorIDExampleFragment.onViewCreated called"); + getView().findViewById(R.id.readVendorIdButton).setOnClickListener(readButtonClickListener); + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java index c1bde0f9bb0323..ddf41b349d6890 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java @@ -17,18 +17,23 @@ package com.matter.casting; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.TextView; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import chip.devicecontroller.ChipClusters; import com.R; import com.matter.casting.core.CastingPlayer; import com.matter.casting.core.Endpoint; -import java.util.List; +import java.util.Optional; -/** A {@link Fragment} to send Content Launcher LaunchURL command from the TV Casting App. */ +/** A {@link Fragment} to send Content Launcher LaunchURL command using the TV Casting App. */ public class ContentLauncherLaunchURLExampleFragment extends Fragment { private static final String TAG = ContentLauncherLaunchURLExampleFragment.class.getSimpleName(); private static final Integer SAMPLE_ENDPOINT_VID = 65521; @@ -63,19 +68,61 @@ public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { this.launchUrlButtonClickListener = v -> { - Endpoint endpoint = selectEndpoint(); + Endpoint endpoint = + EndpointSelectorExample.selectFirstEndpointByVID(selectedCastingPlayer); if (endpoint == null) { + Log.e(TAG, "No Endpoint with sample vendorID found on CastingPlayer"); + return; + } + + EditText contentUrlEditText = getView().findViewById(R.id.contentUrlEditText); + String contentUrl = contentUrlEditText.getText().toString(); + EditText contentDisplayStringEditText = + getView().findViewById(R.id.contentDisplayStringEditText); + String contentDisplayString = contentDisplayStringEditText.getText().toString(); + + // get ChipClusters.ContentLauncherCluster from the endpoint + ChipClusters.ContentLauncherCluster cluster = + endpoint.getCluster(ChipClusters.ContentLauncherCluster.class); + if (cluster == null) { Log.e( TAG, - "No Endpoint with chosen vendorID: " - + SAMPLE_ENDPOINT_VID - + " found on CastingPlayer"); + "Could not get ContentLauncherCluster for endpoint with ID: " + endpoint.getId()); return; } - // TODO: add command invocation API call + // call launchURL on the cluster object while passing in a + // ChipClusters.ContentLauncherCluster.LauncherResponseCallback and request parameters + cluster.launchURL( + new ChipClusters.ContentLauncherCluster.LauncherResponseCallback() { + @Override + public void onSuccess(Integer status, Optional data) { + Log.d(TAG, "LaunchURL success. Status: " + status + ", Data: " + data); + new Handler(Looper.getMainLooper()) + .post( + () -> { + TextView launcherResult = getView().findViewById(R.id.launcherResult); + launcherResult.setText( + "LaunchURL result\nStatus: " + status + ", Data: " + data); + }); + } + + @Override + public void onError(Exception error) { + Log.e(TAG, "LaunchURL failure " + error); + new Handler(Looper.getMainLooper()) + .post( + () -> { + TextView launcherResult = getView().findViewById(R.id.launcherResult); + launcherResult.setText("LaunchURL result\nError: " + error); + }); + } + }, + contentUrl, + Optional.of(contentDisplayString), + Optional.empty()); }; - return inflater.inflate(R.layout.fragment_content_launcher, container, false); + return inflater.inflate(R.layout.fragment_matter_content_launcher_launch_url, container, false); } @Override @@ -84,22 +131,4 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { Log.d(TAG, "ContentLauncherLaunchURLExampleFragment.onViewCreated called"); getView().findViewById(R.id.launchUrlButton).setOnClickListener(launchUrlButtonClickListener); } - - private Endpoint selectEndpoint() { - Endpoint endpoint = null; - if (selectedCastingPlayer != null) { - List endpoints = selectedCastingPlayer.getEndpoints(); - if (endpoints == null) { - Log.e(TAG, "No Endpoints found on CastingPlayer"); - } else { - endpoint = - endpoints - .stream() - .filter(e -> SAMPLE_ENDPOINT_VID.equals(e.getVendorId())) - .findFirst() - .get(); - } - } - return endpoint; - } } diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/EndpointSelectorExample.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/EndpointSelectorExample.java new file mode 100644 index 00000000000000..c2932c59117c64 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/EndpointSelectorExample.java @@ -0,0 +1,34 @@ +package com.matter.casting; + +import android.util.Log; +import com.matter.casting.core.CastingPlayer; +import com.matter.casting.core.Endpoint; +import java.util.List; + +/** A utility that selects an endpoint based on some criterion */ +public class EndpointSelectorExample { + private static final String TAG = EndpointSelectorExample.class.getSimpleName(); + private static final Integer SAMPLE_ENDPOINT_VID = 65521; + + /** + * Returns the first Endpoint in the list of Endpoints associated with the selectedCastingPlayer + * whose VendorID matches the EndpointSelectorExample.SAMPLE_ENDPOINT_VID + */ + public static Endpoint selectFirstEndpointByVID(CastingPlayer selectedCastingPlayer) { + Endpoint endpoint = null; + if (selectedCastingPlayer != null) { + List endpoints = selectedCastingPlayer.getEndpoints(); + if (endpoints == null) { + Log.e(TAG, "No Endpoints found on CastingPlayer"); + } else { + endpoint = + endpoints + .stream() + .filter(e -> SAMPLE_ENDPOINT_VID.equals(e.getVendorId())) + .findFirst() + .get(); + } + } + return endpoint; + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/MediaPlaybackSubscribeToCurrentStateExampleFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/MediaPlaybackSubscribeToCurrentStateExampleFragment.java new file mode 100644 index 00000000000000..77f5129b6b9e4d --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/MediaPlaybackSubscribeToCurrentStateExampleFragment.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2024 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. + */ +package com.matter.casting; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import chip.devicecontroller.ChipClusters; +import com.R; +import com.matter.casting.core.CastingApp; +import com.matter.casting.core.CastingPlayer; +import com.matter.casting.core.Endpoint; +import java.util.Date; + +/** + * A {@link Fragment} to subscribe to CurrentState (from MediaPLayback cluster) using the TV Casting + * App. + */ +public class MediaPlaybackSubscribeToCurrentStateExampleFragment extends Fragment { + private static final String TAG = + MediaPlaybackSubscribeToCurrentStateExampleFragment.class.getSimpleName(); + + private final CastingPlayer selectedCastingPlayer; + + private View.OnClickListener subscribeButtonClickListener; + private View.OnClickListener shutdownSubscriptionsButtonClickListener; + + public MediaPlaybackSubscribeToCurrentStateExampleFragment(CastingPlayer selectedCastingPlayer) { + this.selectedCastingPlayer = selectedCastingPlayer; + } + + /** + * Use this factory method to create a new instance of this fragment using the provided + * parameters. + * + * @param selectedCastingPlayer CastingPlayer that the casting app connected to + * @return A new instance of fragment MediaPlaybackSubscribeToCurrentStateExampleFragment. + */ + public static MediaPlaybackSubscribeToCurrentStateExampleFragment newInstance( + CastingPlayer selectedCastingPlayer) { + return new MediaPlaybackSubscribeToCurrentStateExampleFragment(selectedCastingPlayer); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + Endpoint endpoint = EndpointSelectorExample.selectFirstEndpointByVID(selectedCastingPlayer); + if (endpoint == null) { + Log.e(TAG, "No Endpoint with sample vendorID found on CastingPlayer"); + return inflater.inflate( + R.layout.fragment_matter_media_playback_subscribe_current_state, container, false); + } + + this.subscribeButtonClickListener = + v -> { + // get ChipClusters.MediaPlaybackCluster from the endpoint + ChipClusters.MediaPlaybackCluster cluster = + endpoint.getCluster(ChipClusters.MediaPlaybackCluster.class); + if (cluster == null) { + Log.e( + TAG, + "Could not get ApplicationBasicCluster for endpoint with ID: " + endpoint.getId()); + return; + } + + // call subscribeCurrentStateAttribute on the cluster object while passing in a + // ChipClusters.IntegerAttributeCallback and [0, 1] for min and max interval params + cluster.subscribeCurrentStateAttribute( + new ChipClusters.IntegerAttributeCallback() { + @Override + public void onSuccess(int value) { + Log.d(TAG, "Read success on subscription. Value: " + value + " @ " + new Date()); + new Handler(Looper.getMainLooper()) + .post( + () -> { + TextView currentStateResult = + getView().findViewById(R.id.currentStateResult); + currentStateResult.setText("Current State result\nValue: " + value); + }); + } + + @Override + public void onError(Exception error) { + Log.e(TAG, "Read failure on subscription: " + error); + new Handler(Looper.getMainLooper()) + .post( + () -> { + TextView currentStateResult = + getView().findViewById(R.id.currentStateResult); + currentStateResult.setText("Current State result\nError: " + error); + }); + } + }, + 0, + 1); + }; + + this.shutdownSubscriptionsButtonClickListener = + new View.OnClickListener() { + @Override + public void onClick(View v) { + Log.d(TAG, "Shutting down subscriptions"); + CastingApp.getInstance().shutdownAllSubscriptions(); + } + }; + + return inflater.inflate( + R.layout.fragment_matter_media_playback_subscribe_current_state, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Log.d(TAG, "MediaPlaybackSubscribeToCurrentStateExampleFragment.onViewCreated called"); + getView() + .findViewById(R.id.subscribeToCurrentStateButton) + .setOnClickListener(subscribeButtonClickListener); + getView() + .findViewById(R.id.shutdownSubscriptionsButton) + .setOnClickListener(shutdownSubscriptionsButtonClickListener); + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Endpoint.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Endpoint.java index 6d1b63555aad08..f906b80235700c 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Endpoint.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Endpoint.java @@ -17,9 +17,11 @@ package com.matter.casting.core; +import chip.devicecontroller.ChipClusters; import com.matter.casting.support.DeviceTypeStruct; import java.util.List; +/** This represents an Endpoint on a CastingPlayer e.g. a Speaker or a Matter Content App */ public interface Endpoint { int getId(); @@ -29,5 +31,9 @@ public interface Endpoint { List getDeviceTypeList(); + /** Get an instance of a cluster based on its Class */ + T getCluster(Class clusterClass); + + /** Get the CastingPlayer that this Endpoint is a part of. */ CastingPlayer getCastingPlayer(); } diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterEndpoint.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterEndpoint.java index b9dd564d6ff95f..86d0eb016958a7 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterEndpoint.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterEndpoint.java @@ -16,12 +16,23 @@ */ package com.matter.casting.core; +import android.util.Log; +import chip.devicecontroller.ChipClusters; import com.matter.casting.support.DeviceTypeStruct; +import com.matter.casting.support.MatterCallback; +import com.matter.casting.support.MatterError; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; public class MatterEndpoint implements Endpoint { private static final String TAG = MatterEndpoint.class.getSimpleName(); + private static final long MAX_WAIT_FOR_DEVICE_PROXY_MS = 5000; protected long _cppEndpoint; @Override @@ -36,6 +47,27 @@ public class MatterEndpoint implements Endpoint { @Override public native List getDeviceTypeList(); + @Override + public T getCluster(Class clusterClass) { + try { + Constructor constructor = clusterClass.getDeclaredConstructor(long.class, int.class); + Long deviceProxy = getDeviceProxy(); + if (deviceProxy == null) { + Log.e(TAG, "Could not get DeviceProxy while constructing cluster object"); + return null; + } + return constructor.newInstance(deviceProxy, getId()); + } catch (InstantiationException + | IllegalAccessException + | InvocationTargetException + | NoSuchMethodException e) { + Log.e( + TAG, + "Could not create cluster object for " + clusterClass.getSimpleName() + " exc: " + e); + return null; + } + } + @Override public native CastingPlayer getCastingPlayer(); @@ -56,4 +88,32 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(getId()); } + + private Long getDeviceProxy() { + CompletableFuture deviceProxyFuture = new CompletableFuture<>(); + getDeviceProxy( + new MatterCallback() { + @Override + public void handle(Long deviceProxy) { + deviceProxyFuture.complete(deviceProxy); + } + }, + new MatterCallback() { + @Override + public void handle(MatterError response) { + deviceProxyFuture.completeExceptionally( + new RuntimeException("Failed on getDeviceProxy: " + response)); + } + }); + + try { + return deviceProxyFuture.get(MAX_WAIT_FOR_DEVICE_PROXY_MS, TimeUnit.MILLISECONDS); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + Log.e(TAG, "Exception while waiting on getDeviceProxy future: " + e); + return null; + } + } + + protected native void getDeviceProxy( + MatterCallback successCallback, MatterCallback failureCallback); } diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.cpp index 2e28b873599a9a..e0c41ebc92d260 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.cpp @@ -19,7 +19,6 @@ #include "MatterEndpoint-JNI.h" #include "../JNIDACProvider.h" -#include "../support/Converters-JNI.h" #include "../support/MatterCallback-JNI.h" #include "../support/RotatingDeviceIdUniqueIdProvider-JNI.h" #include "clusters/Clusters.h" // from tv-casting-common @@ -86,6 +85,30 @@ JNI_METHOD(jobject, getCastingPlayer) return support::convertCastingPlayerFromCppToJava(std::shared_ptr(endpoint->GetCastingPlayer())); } +JNI_METHOD(void, getDeviceProxy) +(JNIEnv * env, jobject thiz, jobject jSuccessCallback, jobject jFailureCallback) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "MatterEndpoint-JNI::getDeviceProxy() called"); + Endpoint * endpoint = support::convertEndpointFromJavaToCpp(thiz); + VerifyOrReturn(endpoint != nullptr, ChipLogError(AppServer, "MatterEndpoint-JNI::getDeviceProxy() endpoint == nullptr")); + + ReturnOnFailure(MatterEndpointJNIMgr().mGetDeviceProxySuccessHandler.SetUp(env, jSuccessCallback)); + ReturnOnFailure(MatterEndpointJNIMgr().mGetDeviceProxyFailureHandler.SetUp(env, jFailureCallback)); + + endpoint->GetCastingPlayer()->FindOrEstablishSession( + nullptr, + [](void * context, chip::Messaging::ExchangeManager & exchangeMgr, const chip::SessionHandle & sessionHandle) { + ChipLogProgress(AppServer, "MatterEndpointJNI FindOrEstablishSession success"); + OperationalDeviceProxy * device = new OperationalDeviceProxy(&exchangeMgr, sessionHandle); // TODO: delete *device + MatterEndpointJNIMgr().mGetDeviceProxySuccessHandler.Handle(device); + }, + [](void * context, const chip::ScopedNodeId & peerId, CHIP_ERROR error) { + ChipLogError(AppServer, "MatterEndpointJNI FindOrEstablishSession failure %" CHIP_ERROR_FORMAT, error.Format()); + MatterEndpointJNIMgr().mGetDeviceProxyFailureHandler.Handle(error); + }); +} + }; // namespace core }; // namespace casting }; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.h index f9534435ab1903..2c65ca448b6f29 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.h +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.h @@ -18,8 +18,11 @@ #pragma once +#include "../support/Converters-JNI.h" +#include "../support/MatterCallback-JNI.h" #include "core/Endpoint.h" // from tv-casting-common +#include #include #include #include @@ -30,6 +33,15 @@ namespace core { class MatterEndpointJNI { +public: + MatterEndpointJNI() : + mGetDeviceProxySuccessHandler([](chip::DeviceProxy * device) -> jobject { + return support::convertLongFromCppToJava(reinterpret_cast(device)); + }) + {} + support::MatterCallbackJNI mGetDeviceProxySuccessHandler; + support::MatterFailureCallbackJNI mGetDeviceProxyFailureHandler; + private: friend MatterEndpointJNI & MatterEndpointJNIMgr(); static MatterEndpointJNI sInstance; diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.cpp index 9798f2b48b9359..aa5ef2b25f0096 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.cpp @@ -25,6 +25,30 @@ namespace support { using namespace chip; +jobject convertLongFromCppToJava(jlong value) +{ + ChipLogProgress(AppServer, "convertLongFromCppToJava called"); + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + jclass responseTypeClass = env->FindClass("java/lang/Long"); + if (responseTypeClass == nullptr) + { + ChipLogError(AppServer, "ConvertToJObject: Class for Response Type not found!"); + env->ExceptionClear(); + return nullptr; + } + + jmethodID constructor = env->GetMethodID(responseTypeClass, "", "(J)V"); + if (constructor == nullptr) + { + ChipLogError(AppServer, "Failed to access Long constructor"); + env->ExceptionClear(); + return nullptr; + } + return env->NewObject(responseTypeClass, constructor, value); +} + jobject convertMatterErrorFromCppToJava(CHIP_ERROR inErr) { ChipLogProgress(AppServer, "convertMatterErrorFromCppToJava() called"); diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.h index ecc3a95d15bd74..aa96af0668f5d4 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.h +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.h @@ -29,6 +29,8 @@ namespace matter { namespace casting { namespace support { +jobject convertLongFromCppToJava(jlong value); + jobject convertMatterErrorFromCppToJava(CHIP_ERROR inErr); /** diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/MatterCallback-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/MatterCallback-JNI.h index 3c56c426359d5c..82894e075ea912 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/MatterCallback-JNI.h +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/MatterCallback-JNI.h @@ -43,6 +43,7 @@ class MatterCallbackJNI ChipLogProgress(AppServer, "MatterCallbackJNI::SetUp called"); VerifyOrReturnError(env != nullptr, CHIP_JNI_ERROR_NO_ENV, ChipLogError(AppServer, "JNIEnv was null!")); + mCallbackObject.Reset(); ReturnErrorOnFailure(mCallbackObject.Init(inCallback)); jclass mClazz = env->GetObjectClass(mCallbackObject.ObjectRef()); @@ -53,7 +54,7 @@ class MatterCallbackJNI VerifyOrReturnError(mSuperClazz != nullptr, CHIP_JNI_ERROR_TYPE_NOT_FOUND, ChipLogError(AppServer, "Failed to get callback's parent's Java class")); - mMethod = env->GetMethodID(mClazz, "handleInternal", mMethodSignature); + mMethod = env->GetMethodID(mSuperClazz, "handleInternal", mMethodSignature); VerifyOrReturnError( mMethod != nullptr, CHIP_JNI_ERROR_METHOD_NOT_FOUND, ChipLogError(AppServer, "Failed to access 'handleInternal' method with signature %s", mMethodSignature)); diff --git a/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_matter_action_selector.xml b/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_matter_action_selector.xml index ae61681a0bbf2f..1969c5eeb13212 100644 --- a/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_matter_action_selector.xml +++ b/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_matter_action_selector.xml @@ -27,6 +27,24 @@ android:layout_marginRight="10sp" android:layout_below="@id/action_selector_heading" /> +