diff --git a/doc/nrf/protocols/matter/end_product/index.rst b/doc/nrf/protocols/matter/end_product/index.rst index 35c90bcb9bec..61c67eabeb69 100644 --- a/doc/nrf/protocols/matter/end_product/index.rst +++ b/doc/nrf/protocols/matter/end_product/index.rst @@ -19,7 +19,7 @@ Watch the following video for an overview of the Matter end product development The pages that follow deal with the preparation of the device for market launch, starting with :ref:`ug_matter_device_prerequisites` and the reference to the :ref:`ug_matter_device_factory_provisioning` guide. -Finally, we discuss topics related to Matter certification (:ref:`ug_matter_device_attestation`, :ref:`ug_matter_device_dcl`, :ref:`ug_matter_device_certification`, :ref:`ug_matter_ecosystems_certification`, and :ref:`ug_matter_device_configuring_cd`) and :ref:`ug_matter_device_bootloader`. +Finally, we discuss topics related to Matter certification (:ref:`ug_matter_device_attestation`, :ref:`ug_matter_device_dcl`, :ref:`ug_matter_device_certification`, :ref:`ug_matter_ecosystems_certification`, :ref:`ug_matter_device_configuring_cd`, and :ref:`ug_matter_test_event_triggers`) and :ref:`ug_matter_device_bootloader`. .. toctree:: :maxdepth: 1 @@ -36,3 +36,4 @@ Finally, we discuss topics related to Matter certification (:ref:`ug_matter_devi configuring_cd versioning last_fabric_removal_delegate + test_event_triggers diff --git a/doc/nrf/protocols/matter/end_product/test_event_triggers.rst b/doc/nrf/protocols/matter/end_product/test_event_triggers.rst new file mode 100644 index 000000000000..5dedb30ffb11 --- /dev/null +++ b/doc/nrf/protocols/matter/end_product/test_event_triggers.rst @@ -0,0 +1,280 @@ +.. _ug_matter_test_event_triggers: + +Matter test event triggers +########################## + +.. contents:: + :local: + :depth: 2 + +Matter test event triggers are a set of software commands designed to initiate specific test scenarios. +You can activate these commands using the Matter Controller, which serves as the interface for executing them. +To activate a command, you need to enter the pre-defined activation codes that correspond to the desired test event. +All test event triggers are based on the ``generaldiagnostics`` Matter cluster, and utilize the ``test-event-trigger`` command to function. +This mechanism can be used during the Matter certification process to simulate common and specific device scenarios, and during software verification events to validate the correct operation of Matter-compliant devices. +To learn how to use the test event triggers functionality during the Matter certification process, see the :ref:`matter_test_event_triggers_matter_certification` section. + +You can utilize the test event triggers predefined in the :ref:`matter_test_event_triggers_default_test_event_triggers` section to streamline your testing process. +If the predefined options do not meet your requirements, you can also define your own event triggers. +To learn how to create custom event triggers, refer to the :ref:`matter_test_event_triggers_custom_triggers` section of the documentation. + +Requirements +************ + +The :ref:`CHIP Tool application ` is required to send ``test-event-trigger`` commands to the general diagnostics cluster. +Ensure that it is installed before initiating Matter testing events. + +Your device needs to includes the general diagnostics cluster within its Matter data model database to perform Matter testing events. +For guidance on configuring the general diagnostics cluster on your device, refer to the instructions provided on the :ref:`ug_matter_gs_adding_cluster` page. + +Activation of test event codes requires the provisioning of an enable key. +To ensure secure operation, the enable key must be specific for each device. +You can find instructions on how to establish the enable key for your device in the :ref:`matter_test_event_triggers_setting_enable_key` section. + +.. _matter_test_event_triggers_default_test_event_triggers: + +Default test event triggers +*************************** + +You can use the pre-defined common test event triggers in your application. +To enable them, set the :kconfig:option:`CONFIG_NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS_REGISTER_DEFAULTS` Kconfig option to ``y``. + +The following table lists the available triggers and their activation codes: + +.. list-table:: Default test event triggers + :widths: auto + :header-rows: 1 + + * - Name + - Requirements + - Description + - Activation code [range] + - Value description + * - Factory reset + - None + - Perform a factory reset of the device with a delay. + - `0xF000000000000000` - `0xF00000000000FFFF` + - The range of `0x0000` - `0xFFFF` represents the delay in ms to wait until the factory reset occurs. + The maximum time delay is UINT32_MAX ms. + The value is provided in HEX format. + * - Reboot + - None + - Reboot the device. + - `0xF000000000000000` - `0xF00000000000FFFF` + - The range of `0x0000` - `0xFFFF` represents the delay in ms to wait until the reboot occurs. + The maximum time delay is UINT32_MAX ms. + The value is provided in HEX format. + * - OTA query + - :kconfig:option:`CONFIG_CHIP_OTA_REQUESTOR` = ``y`` + - Trigger an OTA firmware update. + - `0x002a000000000100` - `0x01000000000001FF` + - The range of `0x00` - `0xFF` is the fabric index value. + The maximum fabric index value depends on the current device's settings. + * - Door lock jammed + - :kconfig:option:`CONFIG_CHIP_DEVICE_PRODUCT_ID` = ``32774`` + - Simulate the jammed lock state. + - `0x3277400000000000` + - This activation code does not contain any value. + +.. _matter_test_event_triggers_setting_enable_key: + +Setting the enable key +********************** + +The enable key can be provided either by utilizing the factory data, or directly in the application code. + +Using factory data +================== + +You can not set the enable key to a specific value using factory data unless the :kconfig:option:`CONFIG_CHIP_FACTORY_DATA` Kconfig option is set to ``y``. +If it is not set, the default value ``00112233445566778899AABBCCDDEEFF`` will be used. +For secure operation, you need to ensure that the enable key is unique for all of your devices. + +To specify the enable key through the build system, enable the :kconfig:option:`CONFIG_CHIP_FACTORY_DATA_BUILD` Kconfig option by setting it to ``y``. +Then, set the :kconfig:option:`CONFIG_CHIP_DEVICE_ENABLE_KEY` Kconfig option to a 32-byte hexadecimal string value. + +If :kconfig:option:`CONFIG_CHIP_FACTORY_DATA_BUILD` is set to ``n``, you can follow the :doc:`matter:nrfconnect_factory_data_configuration` guide in the Matter documentation to generate the factory data set with the specific enable key value. + +If you do not use the |NCS| Matter common module, you need to read the enable key value manually from the factory data set and provide it to the ``TestEventTrigger`` class. + +For example: + +.. code-block:: c++ + + /* Prepare the factory data provider */ + static chip::DeviceLayer::FactoryDataProvider sFactoryDataProvider; + sFactoryDataProvider.Init(); + + /* Prepare the buffer for enable key data */ + uint8_t enableKeyData[chip::TestEventTriggerDelegate::kEnableKeyLength] = {}; + MutableByteSpan enableKey(enableKeyData); + + /* Load the enable key value from the factory data */ + sFactoryDataProvider.GetEnableKey(enableKey); + + /* Call SetEnableKey method to load the read value to the TestEventTrigger class. */ + Nrf::Matter::TestEventTrigger::Instance().SetEnableKey(enableKey); + +Directly in the code +==================== + +Use the SetEnableKey method of the ``TestEventTrigger`` class to set up the enable key. + +For example: + +.. code-block:: c++ + + /* Prepare Buffer for Test Event Trigger data which contains your "enable key" */ + uint8_t enableKeyData[chip::TestEventTriggerDelegate::kEnableKeyLength] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, + 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, + 0xcc, 0xdd, 0xee, 0xff + }; + + /* Call SetEnableKey method to load the prepared value to the TestEventTrigger class. */ + Nrf::Matter::TestEventTrigger::Instance().SetEnableKey(ByteSpan {enableKeyData}); + +.. _matter_test_event_triggers_matter_certification: + +Using in Matter certification +***************************** + +When you provide the enable key using factory data, you can utilize the event trigger feature during the Matter certification process. +This is because, when done this way, you can turn off the event trigger functionality by disabling access to the ``generaldiagnostics`` cluster without altering the code that has already been certified. + +Once the certification process is complete, you must deactivate the test event trigger functionality by generating new factory data with a modified enable key value. +This is done by setting the :kconfig:option:`CONFIG_CHIP_DEVICE_ENABLE_KEY` Kconfig option to a value consisting solely of zeros. + +For instance, to generate factory data with disabled event trigger functionality, set the :kconfig:option:`CONFIG_CHIP_DEVICE_ENABLE_KEY` Kconfig option to the value ``0x00000000000000000000000000000000``. +After generating it, flash the :file:`factory_data.hex` file onto the device. + +.. _matter_test_event_triggers_custom_triggers: + +Custom test event triggers +************************** + +You can define and register custom test event triggers to initiate specific actions on your device. + +An activation code is 64 bits in length, and consist of two components: the event trigger ID and the event trigger value. + +* The event trigger ID is 32 bits long and uniquely identifies the trigger. + It is supplied as the first 32 bits of the activation code. +* The event trigger value is specific to a given trigger. + It is supplied as the last 32 bits of the activation code. + +This means that the activation code has the pattern ``0xIIIIIIIIVVVVVVVV``, where ``I`` represents the ID part and ``V`` represents the value part. + +For example the ``0x1000000000001234`` activation code stands for a trigger ID equal to ``0x1000000000000000`` and a specific value of ``0x1234``. + +A new event trigger consists of two fields: ``Mask``, and ``Callback``. + +* The ``Mask`` field is 32 bits long and specifies a mask for the trigger's value. +* The ``Callback`` field is a callback function that will be invoked when the device receives a corresponding activation code. + +The maximum number of event triggers that can be registered is configurable. +To adjust this limit, set the :kconfig:option:`CONFIG_NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS_MAX` Kconfig option to the desired value. + +To register a new test event trigger, follow these steps: + +1. Create a function that will be executed when the device receives a valid enable key and activation code. + + This function must return a ``CHIP_ERROR`` code and accept a ``Nrf::Matter::TestEventTrigger::TriggerValue`` as its argument. + You can utilize the provided argument within this function as needed. + + Use ``CHIP_ERROR`` codes to communicate appropriate responses to the Matter Controller. + For instance, you might return ``CHIP_ERROR_INVALID_ARGUMENT`` to indicate that the user has provided an incorrect value argument. + + .. note:: + + The callback method is not thread-safe. + Ensure that all operations within it are executed in a thread-safe manner. + To perform operations within the Matter stack context, use the ``chip::DeviceLayer::SystemLayer().ScheduleLambda`` method. + For operations in the application context, use the ``Nrf::PostTask`` function. + + Here is an example of how to create a callback function: + + .. code-block:: c++ + + CHIP_ERROR MyFunctionCallback(Nrf::Matter::TestEventTrigger::TriggerValue value) + { + /* Define the required behavior of the device here. */ + + return CHIP_NO_ERROR; + } + +#. Register the new event trigger. + + Use the following example as a guide to register a new event: + + .. code-block:: c++ + + /* Create a new event */ + Nrf::Matter::TestEventTrigger::EventTrigger myEventTrigger; + + /* Assign all fields */ + uint64_t myTriggerID = /* Set the trigger ID */ + myEventTrigger.Mask = /* Fill the value mask filed */; + myEventTrigger.Callback = MyFunctionCallback; + + /* Register the new event */ + CHIP_ERROR err = Nrf::Matter::TestEventTrigger::Instance().RegisterTestEventTrigger(myTriggerID, myEventTrigger); + + /* Remember to check the CHIP_ERROR return code */ + + If the returning `CHIP_ERROR` code is equal to `CHIP_ERROR_NO_MEMORY`, you need to increase the :kconfig:option:`NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS_MAX` kconfig option to the higher value. + + Here's an example to handle the ``0xF000F00000001234`` activation code, where 1234 is the event trigger value field: + + .. code-block:: c++ + + Nrf::Matter::TestEventTrigger::EventTrigger myEventTrigger; + uint64_t myTriggerID = 0xF000F0000000; + myEventTrigger.Mask = 0xFFFF; + myEventTrigger.Callback = MyFunctionCallback; + + CHIP_ERROR err = Nrf::Matter::TestEventTrigger::Instance().RegisterTestEventTrigger(myTriggerID, myEventTrigger); + +Register an existing test event trigger handler +*********************************************** + +The Matter SDK has some test event trigger handlers implemented. +All of them inherit the `TestEventTriggerHandler` class, and are implemented in various places in the Matter SDK. + +The events declared in existing test event triggers can have a different semantic than described in the :ref:`matter_test_event_triggers_custom_triggers` section. + +Use the following example as a guide to register an existing event trigger handler: + +.. code-block:: c++ + + /* Create the Trigger Handler object */ + static TestEventTriggerHandler existingTriggerHandler; + CHIP_ERROR err = Nrf::Matter::TestEventTrigger::Instance().RegisterTestEventTriggerHandler(&existingTriggerHandler); + + /* Remember to check the CHIP_ERROR return code */ + +If the returning ``CHIP_ERROR`` code is equal to ``CHIP_ERROR_NO_MEMORY``, you need to increase the :kconfig:option:`CONFIG_NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS_MAX_TRIGGERS_DELEGATES` kconfig option to the higher value. + +For example, you can register and use the ``OTATestEventTriggerHandler`` handler and trigger pre-defined Matter OTA DFU behaviors using the following code: + +.. code-block:: c++ + + /* Create the Trigger Handler object */ + static chip::OTATestEventTriggerHandler otaTestEventTrigger; + ReturnErrorOnFailure(Nrf::Matter::TestEventTrigger::Instance().RegisterTestEventTriggerHandler(&otaTestEventTrigger)); + +Usage +***** + +To trigger a specific event on the device, run the following command: + +.. code-block:: console + + ./chip-tool generaldiagnostics test-event-trigger hex: 0 + +Replace the ``enable key`` value with your device's enable key, and the ``activation code`` and ``node id`` values with the values for the event you want to trigger. + +The following is an example of the Reboot activation code with a 5 ms delay for Matter Template device which has a ``node id`` set to ``1``, using the default enable key: + +.. code-block:: console + + ./chip-tool generaldiagnostics test-event-trigger hex:00112233445566778899AABBCCDDEEFF 0xF000000100000005 1 0 diff --git a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst index 6a690aa16a63..531494496bbb 100644 --- a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst +++ b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst @@ -254,6 +254,11 @@ Matter samples * The :file:`prj_no_dfu.conf` file. * Support for ``no_dfu`` build type for nRF5350 DK, nRF52840 DK and nRF7002 DK. +* Added test event triggers to all Matter samples. + By utilizing the test event triggers, you can simulate various operational conditions and responses in your Matter device without the need for external setup. + + To get started with using test event triggers in your Matter samples and to understand the capabilities of this feature, refer to the :ref:`ug_matter_test_event_triggers` page. + Multicore samples ----------------- diff --git a/samples/matter/common/cmake/source_common.cmake b/samples/matter/common/cmake/source_common.cmake index 185af9a8c56f..e5d2dab186a8 100644 --- a/samples/matter/common/cmake/source_common.cmake +++ b/samples/matter/common/cmake/source_common.cmake @@ -44,3 +44,10 @@ endif() if(CONFIG_NCS_SAMPLE_MATTER_SETTINGS_SHELL) target_sources(app PRIVATE ${MATTER_COMMONS_SRC_DIR}/persistent_storage/persistent_storage_shell.cpp) endif() + +if(CONFIG_NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS) + target_sources(app PRIVATE ${MATTER_COMMONS_SRC_DIR}/event_triggers/event_triggers.cpp) + if(CONFIG_NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS_REGISTER_DEFAULTS) + target_sources(app PRIVATE ${MATTER_COMMONS_SRC_DIR}/event_triggers/default_event_triggers.cpp) + endif() +endif() diff --git a/samples/matter/common/src/Kconfig b/samples/matter/common/src/Kconfig index 569408e9ab71..67a78ee5f503 100644 --- a/samples/matter/common/src/Kconfig +++ b/samples/matter/common/src/Kconfig @@ -52,3 +52,30 @@ config NCS_SAMPLE_MATTER_SETTINGS_SHELL get_size - to read the size of specific value. current - to read current settings usage. free - to read free settings space. + +config NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS + bool "Test event triggers support" + help + Enables support for test event triggers for the specific use-cases. + +config NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS_MAX + int "Maximum amount of event triggers" + default 5 + depends on NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS + help + Defines the maximum amount of event triggers. + +config NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS_REGISTER_DEFAULTS + bool "Register default triggers for Matter sample" + depends on NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS + help + Automatically register default triggers such as factory reset, + device reboot, OTA start query. + +config NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS_MAX_TRIGGERS_DELEGATES + int "Maximum amount of the trigger delegates" + default 2 + depends on NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS + help + Defines the maximum number for the TestEventTriggerDelegate implementations + to be registered in the nRF test event triggers class. diff --git a/samples/matter/common/src/app/matter_init.cpp b/samples/matter/common/src/app/matter_init.cpp index f76606fb1800..1691fc8d8ed7 100644 --- a/samples/matter/common/src/app/matter_init.cpp +++ b/samples/matter/common/src/app/matter_init.cpp @@ -21,6 +21,13 @@ #include "dfu/smp/dfu_over_smp.h" #endif +#ifdef CONFIG_NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS +#include "event_triggers/event_triggers.h" +#ifdef CONFIG_NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS_REGISTER_DEFAULTS +#include "event_triggers/default_event_triggers.h" +#endif +#endif + #include #include #include @@ -49,6 +56,7 @@ chip::Crypto::PSAOperationalKeystore Nrf::Matter::InitData::sOperationalKeystore #ifdef CONFIG_CHIP_FACTORY_DATA FactoryDataProvider Nrf::Matter::InitData::sFactoryDataProviderDefault{}; #endif + namespace { /* Local instance of the initialization data that is overwritten by an application. */ @@ -170,6 +178,20 @@ void DoInitChipServer(intptr_t /* unused */) SetDeviceInstanceInfoProvider(sLocalInitData.mFactoryDataProvider); SetDeviceAttestationCredentialsProvider(sLocalInitData.mFactoryDataProvider); SetCommissionableDataProvider(sLocalInitData.mFactoryDataProvider); + +#ifdef CONFIG_NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS + /* Read the enable key from the factory data set */ + uint8_t enableKeyData[chip::TestEventTriggerDelegate::kEnableKeyLength] = {}; + MutableByteSpan enableKey(enableKeyData); + sInitResult = sLocalInitData.mFactoryDataProvider->GetEnableKey(enableKey); + VerifyInitResultOrReturn(sInitResult, "GetEnableKey() failed"); + Nrf::Matter::TestEventTrigger::Instance().SetEnableKey(enableKey); + VerifyInitResultOrReturn(sInitResult, "SetEnableKey() failed"); +#ifdef CONFIG_NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS_REGISTER_DEFAULTS + sLocalInitData.mServerInitParams->testEventTriggerDelegate = &Nrf::Matter::TestEventTrigger::Instance(); + Nrf::Matter::DefaultTestEventTriggers::Register(); +#endif /* CONFIG_NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS_REGISTER_DEFAULTS */ +#endif /* CONFIG_NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS */ #else SetDeviceInstanceInfoProvider(&DeviceInstanceInfoProviderMgrImpl()); SetDeviceAttestationCredentialsProvider(Examples::GetExampleDACProvider()); diff --git a/samples/matter/common/src/event_triggers/default_event_triggers.cpp b/samples/matter/common/src/event_triggers/default_event_triggers.cpp new file mode 100644 index 000000000000..d53461087770 --- /dev/null +++ b/samples/matter/common/src/event_triggers/default_event_triggers.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +#include "default_event_triggers.h" + +#include "event_triggers.h" +#include +#include + +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); + +using namespace Nrf::Matter; +using namespace chip; + +namespace +{ +k_timer sFactoryResetDelayTimer; + +void FactoryResetDelayTimerTimeoutCallback(k_timer *timer) +{ + Server::GetInstance().ScheduleFactoryReset(); +} + +CHIP_ERROR FactoryResetCallback(TestEventTrigger::TriggerValue delayMs) +{ + LOG_DBG("Called Factory Reset event trigger with delay %d ms", delayMs); + + k_timer_init(&sFactoryResetDelayTimer, &FactoryResetDelayTimerTimeoutCallback, nullptr); + k_timer_start(&sFactoryResetDelayTimer, K_MSEC(delayMs), K_NO_WAIT); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR RebootCallback(TestEventTrigger::TriggerValue delayMs) +{ + LOG_DBG("Called Reboot event trigger with delay %d ms", delayMs); + + return chip::DeviceLayer::SystemLayer().StartTimer( + chip::System::Clock::Milliseconds32(delayMs), + [](chip::System::Layer *, void * /* context */) { + chip::DeviceLayer::PlatformMgr().HandleServerShuttingDown(); + k_msleep(CHIP_DEVICE_CONFIG_SERVER_SHUTDOWN_ACTIONS_SLEEP_MS); + sys_reboot(SYS_REBOOT_WARM); + }, + nullptr /* context */); + + return CHIP_NO_ERROR; +} + +} // namespace + +namespace Nrf::Matter::DefaultTestEventTriggers +{ + +CHIP_ERROR Register() +{ + /* Register System events */ + ReturnErrorOnFailure(Nrf::Matter::TestEventTrigger::Instance().RegisterTestEventTrigger( + Ids::FactoryReset, + TestEventTrigger::EventTrigger{ ValueMasks::FactoryResetDelayMs, FactoryResetCallback })); + ReturnErrorOnFailure(Nrf::Matter::TestEventTrigger::Instance().RegisterTestEventTrigger( + Ids::Reboot, TestEventTrigger::EventTrigger{ ValueMasks::RebootDelayMs, RebootCallback })); + + /* Register OTA test events handler */ + static chip::OTATestEventTriggerHandler otaTestEventTrigger; + ReturnErrorOnFailure( + Nrf::Matter::TestEventTrigger::Instance().RegisterTestEventTriggerHandler(&otaTestEventTrigger)); + + return CHIP_NO_ERROR; +} + +} // namespace Nrf::Matter::DefaultTestEventTriggers diff --git a/samples/matter/common/src/event_triggers/default_event_triggers.h b/samples/matter/common/src/event_triggers/default_event_triggers.h new file mode 100644 index 000000000000..799eab52a11f --- /dev/null +++ b/samples/matter/common/src/event_triggers/default_event_triggers.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#pragma once + +#include "event_triggers.h" +#include + +#include +#include + +namespace Nrf::Matter::DefaultTestEventTriggers +{ +enum Ids : TestEventTrigger::EventTriggerId { + + /* System */ + FactoryReset = 0xF000'0000'0000'0000, + Reboot = 0xF000'0001'0000'0000, +}; + +enum ValueMasks : TestEventTrigger::TriggerValueMask { + /* System */ + FactoryResetDelayMs = 0xFFFF, + RebootDelayMs = 0xFFFF, +}; + +/** + * @brief Register all the default event triggers. + * + * @return CHIP_ERROR that refers to TestEventTrigger::RegisterTestEventTrigger method. + */ +CHIP_ERROR Register(); + +} // namespace Nrf::Matter::DefaultTestEventTriggers diff --git a/samples/matter/common/src/event_triggers/event_triggers.cpp b/samples/matter/common/src/event_triggers/event_triggers.cpp new file mode 100644 index 000000000000..af16260724c1 --- /dev/null +++ b/samples/matter/common/src/event_triggers/event_triggers.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "event_triggers.h" + +#include + +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); + +using namespace Nrf::Matter; +using namespace chip; + +CHIP_ERROR TestEventTrigger::RegisterTestEventTrigger(EventTriggerId id, EventTrigger trigger) +{ + VerifyOrReturnError(trigger.Callback != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(!mTriggersMap.Contains(id), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(mTriggersMap.Insert(id, std::move(trigger)), CHIP_ERROR_NO_MEMORY); + + LOG_DBG("Registered new test event: 0x%llx", id); + + return CHIP_NO_ERROR; +} + +void TestEventTrigger::UnregisterTestEventTrigger(EventTriggerId id) +{ + if (mTriggersMap.Erase(id)) { + LOG_DBG("Unregistered test event: 0x%llx", id); + } else { + LOG_WRN("Cannot unregister test event: 0x%llx", id); + } +} + +CHIP_ERROR TestEventTrigger::RegisterTestEventTriggerHandler(chip::TestEventTriggerHandler *triggerDelegate) +{ + VerifyOrReturnError(triggerDelegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + for (size_t idx = 0; idx < kMaxEventTriggers; idx++) { + if (mHandlers[idx] == nullptr) { + mHandlers[idx] = triggerDelegate; + return CHIP_NO_ERROR; + } + } + + return CHIP_ERROR_NO_MEMORY; +} + +CHIP_ERROR TestEventTrigger::SetEnableKey(const chip::ByteSpan &newEnableKey) +{ + VerifyOrReturnError(newEnableKey.size() == chip::TestEventTriggerDelegate::kEnableKeyLength, + CHIP_ERROR_INVALID_ARGUMENT); + return CopySpanToMutableSpan(newEnableKey, mEnableKey); +} + +bool TestEventTrigger::DoesEnableKeyMatch(const chip::ByteSpan &enableKey) const +{ + return !mEnableKey.empty() && mEnableKey.data_equal(enableKey); +} + +CHIP_ERROR TestEventTrigger::HandleEventTriggers(uint64_t eventTrigger) +{ + /* Check if the Enable Key is set to the "disabled" value. */ + if (!IsEventTriggerEnabled()) { + return CHIP_ERROR_INCORRECT_STATE; + } + + EventTriggerId triggerID = eventTrigger & kEventTriggerMask; + + /* Check if the provided event trigger exists in Trigger map */ + if (mTriggersMap.Contains(triggerID)) { + VerifyOrReturnError(mTriggersMap[triggerID].Callback != nullptr, CHIP_ERROR_INCORRECT_STATE); + return mTriggersMap[triggerID].Callback(eventTrigger & mTriggersMap[triggerID].Mask); + } + + /* Event trigger has not been found in the Trigger map so check if one of the registered Event Trigger + * Handler Delegates can be called instead. + */ + for (size_t idx = 0; idx < kMaxEventTriggersHandlers; idx++) { + if (mHandlers[idx]) { + CHIP_ERROR status = mHandlers[idx]->HandleEventTrigger(eventTrigger); + if (status == CHIP_NO_ERROR) { + return CHIP_NO_ERROR; + } + } + } + + return CHIP_ERROR_INCORRECT_STATE; +} diff --git a/samples/matter/common/src/event_triggers/event_triggers.h b/samples/matter/common/src/event_triggers/event_triggers.h new file mode 100644 index 000000000000..d8cf7fc65c04 --- /dev/null +++ b/samples/matter/common/src/event_triggers/event_triggers.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#pragma once + +#include +#include + +#include "util/finite_map.h" + +#include +#include + +namespace Nrf::Matter +{ +class TestEventTrigger : public chip::TestEventTriggerDelegate { +public: + using EventTriggerId = uint64_t; + using TriggerValueMask = uint32_t; + using TriggerValue = uint32_t; + using TriggerSlot = uint16_t; + + /* A function callback recipe for handling the event triggers + To match the event trigger requirements, you can use the following CHIP_ERROR codes inside. + All error codes (except CHIP_NO_ERROR) send "INVALID_COMMAND" response to Matter Controller. + */ + using EventTriggerCallback = CHIP_ERROR (*)(TriggerValue); + + /* Define the maximum available event triggers that can be registered. */ + constexpr static TriggerSlot kMaxEventTriggers = CONFIG_NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS_MAX; + constexpr static TriggerSlot kInvalidTriggerSlot = CONFIG_NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS_MAX + 1; + constexpr static TriggerSlot kMaxEventTriggersHandlers = CONFIG_NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS_MAX_TRIGGERS_DELEGATES; + constexpr static uint8_t kDisableEventTriggersKey[chip::TestEventTriggerDelegate::kEnableKeyLength] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + constexpr static EventTriggerId kEventTriggerMask = 0xFFFFFFFF00000000; + + /** + * @brief Struct of event trigger + * + * The Id field represents an unique ID of the event trigger, and it should be provided as 64-bits value with + 32-bits right shift. For example 0xFFFFFFFF00000000 + * The Mask field represents a mask of specific event trigger value. It should be provided as a 32-bits mask + value. All values beyond this mask will be ignored. + * The Callback field represents a function callback that will be invoked when the appropriate event trigger ID + is received. + */ + struct EventTrigger { + TriggerValueMask Mask = 0; + EventTriggerCallback Callback = nullptr; + }; + + /** + * @brief Use Instance static method to reach TestEventTrigger class methods. + * + * @return TestEventTrigger& TestEventTrigger object. + */ + static TestEventTrigger &Instance() + { + static TestEventTrigger sInstance; + return sInstance; + } + + ~TestEventTrigger() = default; + + /** + * @brief Register new event trigger for a specific purpose + * + * The trigger.Callback function is called in not thread-safe manner. + * It means that you must ensure thread-safety and run all operations within the callback in the proper context. + * + * @param trigger The @ref EventTrigger structure that contains ID, value Mask and Callback of the new event + * trigger. + * @return CHIP_ERROR_INVALID_ARGUMENT if the trigger.Callback field is not assigned (nullptr) + * @return CHIP_ERROR_INVALID_ARGUMENT if the provided eventTrigger ID is already registered. + * @return CHIP_ERROR_NO_MEMORY if there is no memory for registering the new event trigger. + * @return CHIP_NO_ERROR on success. + */ + CHIP_ERROR RegisterTestEventTrigger(EventTriggerId id, EventTrigger trigger); + + /** + * @brief Unregister the existing event trigger. + * + * @param id the ID of the test event to be unregistered. + */ + void UnregisterTestEventTrigger(EventTriggerId id); + + /** + * @brief Register an implementation of the TestEventTriggerDelegate + * + * The implementation will be called if the received event trigger ID has not been found in + * mTriggersDict dictionary. + * + * @param triggerDelegate pointer to the TestEventTriggerDelegate implementation. + * @return CHIP_ERROR_INVALID_ARGUMENT if the triggerDelegate is NULL. + * @return CHIP_ERROR_NO_MEMORY if the triggerDelegate list is full. + * @return CHIP_NO_ERROR on success. + */ + CHIP_ERROR RegisterTestEventTriggerHandler(chip::TestEventTriggerHandler *triggerDelegate); + + /** + * @brief Set the new Enable Key read out from an external source. + * + * This method is required for all further operations. + * + * @param newEnableKey the new enable key to be set. + * @return CHIP_NO_ERROR on success. + */ + CHIP_ERROR SetEnableKey(const chip::ByteSpan &newEnableKey); + + /* TestEventTriggerDelegate implementations */ + bool DoesEnableKeyMatch(const chip::ByteSpan &enableKey) const override; + CHIP_ERROR HandleEventTriggers(uint64_t eventTrigger) override; + +private: + TestEventTrigger() = default; + + uint16_t GetTriggerSlot(uint64_t eventTrigger); + bool IsEventTriggerEnabled() + { + return memcmp(kDisableEventTriggersKey, mEnableKeyData, + chip::TestEventTriggerDelegate::kEnableKeyLength) != 0; + } + + uint8_t mEnableKeyData[chip::TestEventTriggerDelegate::kEnableKeyLength] = {}; + chip::MutableByteSpan mEnableKey{ mEnableKeyData }; + Nrf::FiniteMap mTriggersMap; + chip::TestEventTriggerHandler* mHandlers[kMaxEventTriggersHandlers]; +}; +}; // namespace Nrf::Matter diff --git a/samples/matter/lock/src/app_task.cpp b/samples/matter/lock/src/app_task.cpp index 71e3ad35b8e6..bc1b0b6ac44f 100644 --- a/samples/matter/lock/src/app_task.cpp +++ b/samples/matter/lock/src/app_task.cpp @@ -214,6 +214,16 @@ void AppTask::NUSUnlockCallback(void *context) } #endif +#ifdef CONFIG_NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS +CHIP_ERROR AppTask::DoorLockJammedEventCallback(Nrf::Matter::TestEventTrigger::TriggerValue) +{ + VerifyOrReturnError(DoorLockServer::Instance().SendLockAlarmEvent(kLockEndpointId, AlarmCodeEnum::kLockJammed), + CHIP_ERROR_INTERNAL); + LOG_ERR("Event Trigger: Doorlock jammed."); + return CHIP_NO_ERROR; +} +#endif + CHIP_ERROR AppTask::Init() { /* Initialize Matter stack */ @@ -261,6 +271,13 @@ CHIP_ERROR AppTask::Init() /* Initialize lock manager */ BoltLockMgr().Init(LockStateChanged); + /* Register Door Lock test event trigger */ +#ifdef CONFIG_NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS + ReturnErrorOnFailure(Nrf::Matter::TestEventTrigger::Instance().RegisterTestEventTrigger( + kDoorLockJammedEventTriggerId, + Nrf::Matter::TestEventTrigger::EventTrigger{ 0, DoorLockJammedEventCallback })); +#endif + return Nrf::Matter::StartServer(); } diff --git a/samples/matter/lock/src/app_task.h b/samples/matter/lock/src/app_task.h index 43790057c2eb..2038eb781772 100644 --- a/samples/matter/lock/src/app_task.h +++ b/samples/matter/lock/src/app_task.h @@ -17,6 +17,10 @@ struct Identify; enum class SwitchButtonAction : uint8_t { Pressed, Released }; #endif +#ifdef CONFIG_NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS +#include "event_triggers/event_triggers.h" +#endif + class AppTask { public: static AppTask &Instance() @@ -48,4 +52,9 @@ class AppTask { static void NUSLockCallback(void *context); static void NUSUnlockCallback(void *context); #endif + +#ifdef CONFIG_NCS_SAMPLE_MATTER_TEST_EVENT_TRIGGERS + constexpr static Nrf::Matter::TestEventTrigger::EventTriggerId kDoorLockJammedEventTriggerId = 0x3277'4000'0000'0000; + static CHIP_ERROR DoorLockJammedEventCallback(Nrf::Matter::TestEventTrigger::TriggerValue); +#endif }; diff --git a/west.yml b/west.yml index 30aff94a3d00..934657924c9e 100644 --- a/west.yml +++ b/west.yml @@ -158,7 +158,7 @@ manifest: - name: matter repo-path: sdk-connectedhomeip path: modules/lib/matter - revision: 8bfa6ae5a51b1bc1498bd4e6b2f6d2bc75e13687 + revision: 70be330c014ae2067be1f641b365c19c2a6e4680 submodules: - name: nlio path: third_party/nlio/repo