diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter index 34c5d04bbc92a8..56a820e2169040 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter @@ -8167,9 +8167,22 @@ endpoint 1 { server cluster Actions { callback attribute actionList; callback attribute endpointLists; - callback attribute setupURL; + ram attribute setupURL default = "https://example.com"; ram attribute featureMap default = 0; - callback attribute clusterRevision default = 1; + ram attribute clusterRevision default = 1; + + handle command InstantAction; + handle command InstantActionWithTransition; + handle command StartAction; + handle command StartActionWithDuration; + handle command StopAction; + handle command PauseAction; + handle command PauseActionWithDuration; + handle command ResumeAction; + handle command EnableAction; + handle command EnableActionWithDuration; + handle command DisableAction; + handle command DisableActionWithDuration; } server cluster PowerSource { diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap index 4949c94a7b641a..e46e30e1e87f53 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap @@ -7061,6 +7061,104 @@ "define": "ACTIONS_CLUSTER", "side": "server", "enabled": 1, + "commands": [ + { + "name": "InstantAction", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "InstantActionWithTransition", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "StartAction", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "StartActionWithDuration", + "code": 3, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "StopAction", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "PauseAction", + "code": 5, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "PauseActionWithDuration", + "code": 6, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ResumeAction", + "code": 7, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "EnableAction", + "code": 8, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "EnableActionWithDuration", + "code": 9, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "DisableAction", + "code": 10, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "DisableActionWithDuration", + "code": 11, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], "attributes": [ { "name": "ActionList", @@ -7101,10 +7199,10 @@ "side": "server", "type": "long_char_string", "included": 1, - "storageOption": "External", + "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": "https://example.com", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -7133,7 +7231,7 @@ "side": "server", "type": "int16u", "included": 1, - "storageOption": "External", + "storageOption": "RAM", "singleton": 0, "bounded": 0, "defaultValue": "1", diff --git a/examples/all-clusters-app/all-clusters-common/include/bridged-actions-stub.h b/examples/all-clusters-app/all-clusters-common/include/bridged-actions-stub.h new file mode 100644 index 00000000000000..eb43404e6720ee --- /dev/null +++ b/examples/all-clusters-app/all-clusters-common/include/bridged-actions-stub.h @@ -0,0 +1,80 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace Actions { +class ActionsDelegateImpl : public Delegate +{ +private: + std::vector kActionList = { + ActionStructStorage(0, CharSpan::fromCharString("TurnOnLight"), ActionTypeEnum::kScene, 0, 0, ActionStateEnum::kInactive), + ActionStructStorage(1, CharSpan::fromCharString("TurnOffLight"), ActionTypeEnum::kScene, 1, 0, ActionStateEnum::kInactive), + ActionStructStorage(2, CharSpan::fromCharString("ToggleLight"), ActionTypeEnum::kScene, 2, 0, ActionStateEnum::kInactive) + }; + + std::vector firstEpList = { 0 }; + std::vector secondEpList = { 0, 1 }; + std::vector thirdEpList = { 1, 2, 3 }; + + std::vector kEndpointList = { + EndpointListStorage(0, CharSpan::fromCharString("On"), EndpointListTypeEnum::kOther, + DataModel::List(firstEpList.data(), firstEpList.size())), + EndpointListStorage(1, CharSpan::fromCharString("Off"), EndpointListTypeEnum::kOther, + DataModel::List(secondEpList.data(), secondEpList.size())), + EndpointListStorage(2, CharSpan::fromCharString("Toggle"), EndpointListTypeEnum::kOther, + DataModel::List(thirdEpList.data(), thirdEpList.size())) + }; + + CHIP_ERROR ReadActionAtIndex(uint16_t index, ActionStructStorage & action) override; + CHIP_ERROR ReadEndpointListAtIndex(uint16_t index, EndpointListStorage & epList) override; + bool HaveActionWithId(uint16_t actionId, uint16_t & actionIndex) override; + + Protocols::InteractionModel::Status HandleInstantAction(uint16_t actionId, Optional invokeId) override; + Protocols::InteractionModel::Status HandleInstantActionWithTransition(uint16_t actionId, uint16_t transitionTime, + Optional invokeId) override; + Protocols::InteractionModel::Status HandleStartAction(uint16_t actionId, Optional invokeId) override; + Protocols::InteractionModel::Status HandleStartActionWithDuration(uint16_t actionId, uint32_t duration, + Optional invokeId) override; + Protocols::InteractionModel::Status HandleStopAction(uint16_t actionId, Optional invokeId) override; + Protocols::InteractionModel::Status HandlePauseAction(uint16_t actionId, Optional invokeId) override; + Protocols::InteractionModel::Status HandlePauseActionWithDuration(uint16_t actionId, uint32_t duration, + Optional invokeId) override; + Protocols::InteractionModel::Status HandleResumeAction(uint16_t actionId, Optional invokeId) override; + Protocols::InteractionModel::Status HandleEnableAction(uint16_t actionId, Optional invokeId) override; + Protocols::InteractionModel::Status HandleEnableActionWithDuration(uint16_t actionId, uint32_t duration, + Optional invokeId) override; + Protocols::InteractionModel::Status HandleDisableAction(uint16_t actionId, Optional invokeId) override; + Protocols::InteractionModel::Status HandleDisableActionWithDuration(uint16_t actionId, uint32_t duration, + Optional invokeId) override; +}; +} // namespace Actions +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp b/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp index 90d1b9ebd115b0..b44193d996b788 100644 --- a/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp +++ b/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2021 Project CHIP Authors + * Copyright (c) 2021-2024 Project CHIP Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,88 +15,117 @@ * limitations under the License. */ -#include -#include -#include -#include -#include -#include -#include -#include +#include using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; +using namespace chip::app::Clusters::Actions; using namespace chip::app::Clusters::Actions::Attributes; +using namespace chip::Protocols::InteractionModel; -namespace { +CHIP_ERROR ActionsDelegateImpl::ReadActionAtIndex(uint16_t index, ActionStructStorage & action) +{ + if (index >= kActionList.size()) + { + return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED; + } + action = kActionList[index]; + return CHIP_NO_ERROR; +} -class ActionsAttrAccess : public AttributeAccessInterface +CHIP_ERROR ActionsDelegateImpl::ReadEndpointListAtIndex(uint16_t index, EndpointListStorage & epList) { -public: - // Register for the Actions cluster on all endpoints. - ActionsAttrAccess() : AttributeAccessInterface(Optional::Missing(), Actions::Id) {} + if (index >= kEndpointList.size()) + { + return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED; + } + epList = kEndpointList[index]; + return CHIP_NO_ERROR; +} - CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; +bool ActionsDelegateImpl::HaveActionWithId(uint16_t actionId, uint16_t & actionIndex) +{ + for (size_t i = 0; i < kEndpointList.size(); i++) + { + if (kActionList[i].actionID == actionId) + { + actionIndex = (uint16_t) i; + return true; + } + } + return false; +} -private: - static constexpr uint16_t ClusterRevision = 1; +Status ActionsDelegateImpl::HandleInstantAction(uint16_t actionId, Optional invokeId) +{ + // Not implemented + return Status::NotFound; +} - CHIP_ERROR ReadActionListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder); - CHIP_ERROR ReadEndpointListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder); - CHIP_ERROR ReadSetupUrlAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder); - CHIP_ERROR ReadClusterRevision(EndpointId endpoint, AttributeValueEncoder & aEncoder); -}; +Status ActionsDelegateImpl::HandleInstantActionWithTransition(uint16_t actionId, uint16_t transitionTime, + Optional invokeId) +{ + // Not implemented + return Status::NotFound; +} -constexpr uint16_t ActionsAttrAccess::ClusterRevision; +Status ActionsDelegateImpl::HandleStartAction(uint16_t actionId, Optional invokeId) +{ + // Not implemented + return Status::NotFound; +} + +Status ActionsDelegateImpl::HandleStartActionWithDuration(uint16_t actionId, uint32_t duration, Optional invokeId) +{ + // Not implemented + return Status::NotFound; +} -CHIP_ERROR ActionsAttrAccess::ReadActionListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) +Status ActionsDelegateImpl::HandleStopAction(uint16_t actionId, Optional invokeId) { - // Just return an empty list - return aEncoder.EncodeEmptyList(); + // Not implemented + return Status::NotFound; } -CHIP_ERROR ActionsAttrAccess::ReadEndpointListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) +Status ActionsDelegateImpl::HandlePauseAction(uint16_t actionId, Optional invokeId) { - // Just return an empty list - return aEncoder.EncodeEmptyList(); + // Not implemented + return Status::NotFound; } -CHIP_ERROR ActionsAttrAccess::ReadSetupUrlAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) +Status ActionsDelegateImpl::HandlePauseActionWithDuration(uint16_t actionId, uint32_t duration, Optional invokeId) { - static const char SetupUrl[] = "https://example.com"; - return aEncoder.Encode(chip::Span(SetupUrl, strlen(SetupUrl))); + // Not implemented + return Status::NotFound; } -CHIP_ERROR ActionsAttrAccess::ReadClusterRevision(EndpointId endpoint, AttributeValueEncoder & aEncoder) +Status ActionsDelegateImpl::HandleResumeAction(uint16_t actionId, Optional invokeId) { - return aEncoder.Encode(ClusterRevision); + // Not implemented + return Status::NotFound; } -ActionsAttrAccess gAttrAccess; +Status ActionsDelegateImpl::HandleEnableAction(uint16_t actionId, Optional invokeId) +{ + // Not implemented + return Status::NotFound; +} -CHIP_ERROR ActionsAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) +Status ActionsDelegateImpl::HandleEnableActionWithDuration(uint16_t actionId, uint32_t duration, Optional invokeId) { - VerifyOrDie(aPath.mClusterId == Actions::Id); + // Not implemented + return Status::NotFound; +} - switch (aPath.mAttributeId) - { - case ActionList::Id: - return ReadActionListAttribute(aPath.mEndpointId, aEncoder); - case EndpointLists::Id: - return ReadEndpointListAttribute(aPath.mEndpointId, aEncoder); - case SetupURL::Id: - return ReadSetupUrlAttribute(aPath.mEndpointId, aEncoder); - case ClusterRevision::Id: - return ReadClusterRevision(aPath.mEndpointId, aEncoder); - default: - break; - } - return CHIP_NO_ERROR; +Status ActionsDelegateImpl::HandleDisableAction(uint16_t actionId, Optional invokeId) +{ + // Not implemented + return Status::NotFound; } -} // anonymous namespace -void MatterActionsPluginServerInitCallback() +Status ActionsDelegateImpl::HandleDisableActionWithDuration(uint16_t actionId, uint32_t duration, Optional invokeId) { - AttributeAccessInterfaceRegistry::Instance().Register(&gAttrAccess); + // Not implemented + return Status::NotFound; } diff --git a/examples/all-clusters-app/esp32/main/main.cpp b/examples/all-clusters-app/esp32/main/main.cpp index 7c96990865556d..c2b0dbe55cc746 100644 --- a/examples/all-clusters-app/esp32/main/main.cpp +++ b/examples/all-clusters-app/esp32/main/main.cpp @@ -128,7 +128,6 @@ static void InitServer(intptr_t context) app::Clusters::ModeSelect::setSupportedModesManager(&sStaticSupportedModesManager); } -// #include #include #include diff --git a/examples/all-clusters-app/linux/main-common.cpp b/examples/all-clusters-app/linux/main-common.cpp index c5a7c2189303a0..b9026a291817fb 100644 --- a/examples/all-clusters-app/linux/main-common.cpp +++ b/examples/all-clusters-app/linux/main-common.cpp @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -86,6 +87,7 @@ Clusters::TemperatureControl::AppSupportedTemperatureLevelsDelegate sAppSupporte Clusters::ModeSelect::StaticSupportedModesManager sStaticSupportedModesManager; Clusters::ValveConfigurationAndControl::ValveControlDelegate sValveDelegate; Clusters::TimeSynchronization::ExtendedTimeSyncDelegate sTimeSyncDelegate; +Clusters::Actions::ActionsDelegateImpl sActionsDelegateImpl; // Please refer to https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/namespaces constexpr const uint8_t kNamespaceCommon = 7; @@ -340,6 +342,12 @@ void emberAfThermostatClusterInitCallback(EndpointId endpoint) SetDefaultDelegate(endpoint, &delegate); } +void emberAfActionsClusterInitCallback(EndpointId endpoint) +{ + VerifyOrReturn(endpoint == 1); + Clusters::Actions::ActionsServer::Instance().SetDefaultDelegate(endpoint, &sActionsDelegateImpl); +} + Status emberAfExternalAttributeReadCallback(EndpointId endpoint, ClusterId clusterId, const EmberAfAttributeMetadata * attributeMetadata, uint8_t * buffer, uint16_t maxReadLength) diff --git a/examples/all-clusters-minimal-app/esp32/sdkconfig_m5stack.defaults b/examples/all-clusters-minimal-app/esp32/sdkconfig_m5stack.defaults index 287262d17e57cc..03691548ea91db 100644 --- a/examples/all-clusters-minimal-app/esp32/sdkconfig_m5stack.defaults +++ b/examples/all-clusters-minimal-app/esp32/sdkconfig_m5stack.defaults @@ -73,3 +73,9 @@ CONFIG_BUILD_CHIP_TESTS=y # Move functions from IRAM to flash CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y + +# Reduce the event logging buffer to reduce the DRAM usage +# TODO: [ESP32] Fix the DRAM overflow in esp32 apps #34717 +CONFIG_EVENT_LOGGING_CRIT_BUFFER_SIZE=512 +CONFIG_EVENT_LOGGING_INFO_BUFFER_SIZE=512 +CONFIG_EVENT_LOGGING_DEBUG_BUFFER_SIZE=512 diff --git a/examples/all-clusters-minimal-app/esp32/sdkconfig_m5stack_rpc.defaults b/examples/all-clusters-minimal-app/esp32/sdkconfig_m5stack_rpc.defaults index d2eaffb9bfd15b..6c4907410a1c22 100644 --- a/examples/all-clusters-minimal-app/esp32/sdkconfig_m5stack_rpc.defaults +++ b/examples/all-clusters-minimal-app/esp32/sdkconfig_m5stack_rpc.defaults @@ -88,3 +88,9 @@ CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y CONFIG_EVENT_LOGGING_CRIT_BUFFER_SIZE=1024 CONFIG_DIAG_USE_EXTERNAL_LOG_WRAP=y + +# Reduce the event logging buffer to reduce the DRAM usage +# TODO: [ESP32] Fix the DRAM overflow in esp32 apps #34717 +CONFIG_EVENT_LOGGING_CRIT_BUFFER_SIZE=512 +CONFIG_EVENT_LOGGING_INFO_BUFFER_SIZE=512 +CONFIG_EVENT_LOGGING_DEBUG_BUFFER_SIZE=512 diff --git a/examples/bridge-app/asr/BUILD.gn b/examples/bridge-app/asr/BUILD.gn index 0fb9be4dcd99c4..19f8263e12bb7c 100755 --- a/examples/bridge-app/asr/BUILD.gn +++ b/examples/bridge-app/asr/BUILD.gn @@ -78,7 +78,6 @@ asr_executable("bridge_app") { "${examples_plat_dir}/shell/matter_shell.cpp", "src/AppTask.cpp", "src/DeviceCallbacks.cpp", - "src/bridged-actions-stub.cpp", "src/main.cpp", "subdevice/SubDevice.cpp", "subdevice/SubDeviceManager.cpp", diff --git a/examples/bridge-app/asr/src/bridged-actions-stub.cpp b/examples/bridge-app/asr/src/bridged-actions-stub.cpp deleted file mode 100755 index 73b7e8dd9877a0..00000000000000 --- a/examples/bridge-app/asr/src/bridged-actions-stub.cpp +++ /dev/null @@ -1,101 +0,0 @@ -/* - * - * Copyright (c) 2021 Project CHIP Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace chip; -using namespace chip::app; -using namespace chip::app::Clusters; -using namespace chip::app::Clusters::Actions::Attributes; - -namespace { - -class ActionsAttrAccess : public AttributeAccessInterface -{ -public: - // Register for the Actions cluster on all endpoints. - ActionsAttrAccess() : AttributeAccessInterface(Optional::Missing(), Actions::Id) {} - - CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; - -private: - static constexpr uint16_t ClusterRevision = 1; - - CHIP_ERROR ReadActionListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder); - CHIP_ERROR ReadEndpointListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder); - CHIP_ERROR ReadSetupUrlAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder); - CHIP_ERROR ReadClusterRevision(EndpointId endpoint, AttributeValueEncoder & aEncoder); -}; - -constexpr uint16_t ActionsAttrAccess::ClusterRevision; - -CHIP_ERROR ActionsAttrAccess::ReadActionListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) -{ - // Just return an empty list - return aEncoder.EncodeEmptyList(); -} - -CHIP_ERROR ActionsAttrAccess::ReadEndpointListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) -{ - // Just return an empty list - return aEncoder.EncodeEmptyList(); -} - -CHIP_ERROR ActionsAttrAccess::ReadSetupUrlAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) -{ - static const char SetupUrl[] = "https://example.com"; - return aEncoder.Encode(chip::Span(SetupUrl, strlen(SetupUrl))); -} - -CHIP_ERROR ActionsAttrAccess::ReadClusterRevision(EndpointId endpoint, AttributeValueEncoder & aEncoder) -{ - return aEncoder.Encode(ClusterRevision); -} - -ActionsAttrAccess gAttrAccess; - -CHIP_ERROR ActionsAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) -{ - VerifyOrDie(aPath.mClusterId == Actions::Id); - - switch (aPath.mAttributeId) - { - case ActionList::Id: - return ReadActionListAttribute(aPath.mEndpointId, aEncoder); - case EndpointLists::Id: - return ReadEndpointListAttribute(aPath.mEndpointId, aEncoder); - case SetupURL::Id: - return ReadSetupUrlAttribute(aPath.mEndpointId, aEncoder); - case ClusterRevision::Id: - return ReadClusterRevision(aPath.mEndpointId, aEncoder); - default: - break; - } - return CHIP_NO_ERROR; -} -} // anonymous namespace - -void MatterActionsPluginServerInitCallback(void) -{ - AttributeAccessInterfaceRegistry::Instance().Register(&gAttrAccess); -} diff --git a/examples/bridge-app/asr/subdevice/SubDeviceManager.cpp b/examples/bridge-app/asr/subdevice/SubDeviceManager.cpp index c0d45c06295c9d..5e2480f3800fa0 100644 --- a/examples/bridge-app/asr/subdevice/SubDeviceManager.cpp +++ b/examples/bridge-app/asr/subdevice/SubDeviceManager.cpp @@ -243,14 +243,6 @@ void HandleDeviceStatusChanged(SubDevice * dev, SubDevice::Changed_t itemChanged const EmberAfDeviceType gRootDeviceTypes[] = { { DEVICE_TYPE_ROOT_NODE, DEVICE_VERSION_DEFAULT } }; const EmberAfDeviceType gAggregateNodeDeviceTypes[] = { { DEVICE_TYPE_BRIDGE, DEVICE_VERSION_DEFAULT } }; -bool emberAfActionsClusterInstantActionCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, - const Actions::Commands::InstantAction::DecodableType & commandData) -{ - // No actions are implemented, just return status NotFound. - commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::NotFound); - return true; -} - void Init_Bridge_Endpoint() { // bridge will have own database named gSubDevices. diff --git a/examples/bridge-app/esp32/main/main.cpp b/examples/bridge-app/esp32/main/main.cpp index ae56a1d8b53d39..4efb9e4acba57c 100644 --- a/examples/bridge-app/esp32/main/main.cpp +++ b/examples/bridge-app/esp32/main/main.cpp @@ -349,14 +349,6 @@ void HandleDeviceStatusChanged(Device * dev, Device::Changed_t itemChangedMask) } } -bool emberAfActionsClusterInstantActionCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, - const Actions::Commands::InstantAction::DecodableType & commandData) -{ - // No actions are implemented, just return status NotFound. - commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::NotFound); - return true; -} - const EmberAfDeviceType gRootDeviceTypes[] = { { DEVICE_TYPE_ROOT_NODE, DEVICE_VERSION_DEFAULT } }; const EmberAfDeviceType gAggregateNodeDeviceTypes[] = { { DEVICE_TYPE_BRIDGE, DEVICE_VERSION_DEFAULT } }; diff --git a/examples/bridge-app/linux/bridged-actions-stub.cpp b/examples/bridge-app/linux/bridged-actions-stub.cpp index 580f4f2239bd1a..b40c040d6ebef2 100644 --- a/examples/bridge-app/linux/bridged-actions-stub.cpp +++ b/examples/bridge-app/linux/bridged-actions-stub.cpp @@ -131,7 +131,7 @@ CHIP_ERROR ActionsAttrAccess::Read(const ConcreteReadAttributePath & aPath, Attr } } // anonymous namespace -void MatterActionsPluginServerInitCallback() +void emberAfActionsClusterInitCallback(EndpointId endpoint) { AttributeAccessInterfaceRegistry::Instance().Register(&gAttrAccess); } diff --git a/examples/bridge-app/telink/CMakeLists.txt b/examples/bridge-app/telink/CMakeLists.txt index f2e80864d46e01..e7984bc1332853 100644 --- a/examples/bridge-app/telink/CMakeLists.txt +++ b/examples/bridge-app/telink/CMakeLists.txt @@ -38,7 +38,6 @@ target_include_directories(app PRIVATE target_sources(app PRIVATE src/AppTask.cpp src/Device.cpp - src/DeviceCallbacks.cpp ${TELINK_COMMON}/common/src/mainCommon.cpp ${TELINK_COMMON}/common/src/AppTaskCommon.cpp ${TELINK_COMMON}/util/src/LEDManager.cpp diff --git a/examples/bridge-app/telink/src/AppTask.cpp b/examples/bridge-app/telink/src/AppTask.cpp index 0f59c3d7751936..f8360e8e860965 100644 --- a/examples/bridge-app/telink/src/AppTask.cpp +++ b/examples/bridge-app/telink/src/AppTask.cpp @@ -387,14 +387,6 @@ void HandleDeviceStatusChanged(Device * dev, Device::Changed_t itemChangedMask) } } -bool emberAfActionsClusterInstantActionCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, - const Clusters::Actions::Commands::InstantAction::DecodableType & commandData) -{ - // No actions are implemented, just return status NotFound. - commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::NotFound); - return true; -} - CHIP_ERROR AppTask::Init(void) { SetExampleButtonCallbacks(LightingActionEventHandler); diff --git a/examples/bridge-app/telink/src/DeviceCallbacks.cpp b/examples/bridge-app/telink/src/DeviceCallbacks.cpp deleted file mode 100644 index 9e3273e7107472..00000000000000 --- a/examples/bridge-app/telink/src/DeviceCallbacks.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* - * - * Copyright (c) 2023 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. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace ::chip; -using namespace ::chip::app; -using namespace ::chip::app::Clusters; -using namespace ::chip::app::Clusters::Actions::Attributes; -using namespace ::chip::Inet; -using namespace ::chip::System; - -namespace { - -class ActionsAttrAccess : public AttributeAccessInterface -{ -public: - // Register for the Actions cluster on all endpoints. - ActionsAttrAccess() : AttributeAccessInterface(Optional::Missing(), Actions::Id) {} - - CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; - -private: - static constexpr uint16_t ClusterRevision = 1; - - CHIP_ERROR ReadActionListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder); - CHIP_ERROR ReadEndpointListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder); - CHIP_ERROR ReadSetupUrlAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder); - CHIP_ERROR ReadClusterRevision(EndpointId endpoint, AttributeValueEncoder & aEncoder); -}; - -constexpr uint16_t ActionsAttrAccess::ClusterRevision; - -CHIP_ERROR ActionsAttrAccess::ReadActionListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) -{ - // Just return an empty list - return aEncoder.EncodeEmptyList(); -} - -CHIP_ERROR ActionsAttrAccess::ReadEndpointListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) -{ - // Just return an empty list - return aEncoder.EncodeEmptyList(); -} - -CHIP_ERROR ActionsAttrAccess::ReadSetupUrlAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) -{ - static const char SetupUrl[] = "https://example.com"; - return aEncoder.Encode(chip::CharSpan::fromCharString(SetupUrl)); -} - -CHIP_ERROR ActionsAttrAccess::ReadClusterRevision(EndpointId endpoint, AttributeValueEncoder & aEncoder) -{ - return aEncoder.Encode(ClusterRevision); -} - -ActionsAttrAccess gAttrAccess; - -CHIP_ERROR ActionsAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) -{ - VerifyOrDie(aPath.mClusterId == Actions::Id); - - switch (aPath.mAttributeId) - { - case ActionList::Id: - return ReadActionListAttribute(aPath.mEndpointId, aEncoder); - case EndpointLists::Id: - return ReadEndpointListAttribute(aPath.mEndpointId, aEncoder); - case SetupURL::Id: - return ReadSetupUrlAttribute(aPath.mEndpointId, aEncoder); - case ClusterRevision::Id: - return ReadClusterRevision(aPath.mEndpointId, aEncoder); - default: - break; - } - return CHIP_NO_ERROR; -} -} // anonymous namespace - -void MatterActionsPluginServerInitCallback(void) -{ - AttributeAccessInterfaceRegistry::Instance().Register(&gAttrAccess); -} diff --git a/examples/placeholder/linux/apps/app1/BUILD.gn b/examples/placeholder/linux/apps/app1/BUILD.gn index aba1e870f7a041..f8cc1b36a2b0ee 100644 --- a/examples/placeholder/linux/apps/app1/BUILD.gn +++ b/examples/placeholder/linux/apps/app1/BUILD.gn @@ -28,7 +28,6 @@ source_set("app1") { sources = [ "../../resource-monitoring-delegates.cpp", - "../../src/bridged-actions-stub.cpp", "../../static-supported-modes-manager.cpp", "../../thread-border-router-management.cpp", ] diff --git a/examples/placeholder/linux/apps/app2/BUILD.gn b/examples/placeholder/linux/apps/app2/BUILD.gn index 2bea36611ffb37..302054f5bf927b 100644 --- a/examples/placeholder/linux/apps/app2/BUILD.gn +++ b/examples/placeholder/linux/apps/app2/BUILD.gn @@ -28,7 +28,6 @@ source_set("app2") { sources = [ "../../resource-monitoring-delegates.cpp", - "../../src/bridged-actions-stub.cpp", "../../static-supported-modes-manager.cpp", ] diff --git a/examples/placeholder/linux/src/bridged-actions-stub.cpp b/examples/placeholder/linux/src/bridged-actions-stub.cpp deleted file mode 100644 index d7abf17cd9106e..00000000000000 --- a/examples/placeholder/linux/src/bridged-actions-stub.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/* - * - * Copyright (c) 2021 Project CHIP Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace chip; -using namespace chip::app; -using namespace chip::app::Clusters; -using namespace chip::app::Clusters::Actions::Attributes; - -namespace { - -class ActionsAttrAccess : public AttributeAccessInterface -{ -public: - // Register for the Actions cluster on all endpoints. - ActionsAttrAccess() : AttributeAccessInterface(Optional::Missing(), Actions::Id) {} - - CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; - -private: - static constexpr uint16_t ClusterRevision = 1; - - CHIP_ERROR ReadActionListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder); - CHIP_ERROR ReadEndpointListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder); - CHIP_ERROR ReadSetupUrlAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder); - CHIP_ERROR ReadClusterRevision(EndpointId endpoint, AttributeValueEncoder & aEncoder); -}; - -constexpr uint16_t ActionsAttrAccess::ClusterRevision; - -CHIP_ERROR ActionsAttrAccess::ReadActionListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) -{ - // Just return an empty list - return aEncoder.EncodeEmptyList(); -} - -CHIP_ERROR ActionsAttrAccess::ReadEndpointListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) -{ - // Just return an empty list - return aEncoder.EncodeEmptyList(); -} - -CHIP_ERROR ActionsAttrAccess::ReadSetupUrlAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) -{ - static const char SetupUrl[] = "https://example.com"; - return aEncoder.Encode(chip::Span(SetupUrl, strlen(SetupUrl))); -} - -CHIP_ERROR ActionsAttrAccess::ReadClusterRevision(EndpointId endpoint, AttributeValueEncoder & aEncoder) -{ - return aEncoder.Encode(ClusterRevision); -} - -ActionsAttrAccess gAttrAccess; - -CHIP_ERROR ActionsAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) -{ - VerifyOrDie(aPath.mClusterId == Actions::Id); - - switch (aPath.mAttributeId) - { - case ActionList::Id: - return ReadActionListAttribute(aPath.mEndpointId, aEncoder); - case EndpointLists::Id: - return ReadEndpointListAttribute(aPath.mEndpointId, aEncoder); - case SetupURL::Id: - return ReadSetupUrlAttribute(aPath.mEndpointId, aEncoder); - case ClusterRevision::Id: - return ReadClusterRevision(aPath.mEndpointId, aEncoder); - default: - break; - } - return CHIP_NO_ERROR; -} -} // anonymous namespace - -void MatterActionsPluginServerInitCallback(void) -{ - chip::app::AttributeAccessInterfaceRegistry::Instance().Register(&gAttrAccess); -} diff --git a/src/app/clusters/actions-server/actions-server.cpp b/src/app/clusters/actions-server/actions-server.cpp new file mode 100644 index 00000000000000..e02f094d85105f --- /dev/null +++ b/src/app/clusters/actions-server/actions-server.cpp @@ -0,0 +1,433 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "actions-server.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::Actions; +using namespace chip::app::Clusters::Actions::Attributes; +using namespace chip::Protocols::InteractionModel; + +namespace { +static constexpr size_t kActionsDelegateTableSize = + MATTER_DM_ACTIONS_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; +static_assert(kActionsDelegateTableSize <= kEmberInvalidEndpointIndex, "Actions Delegate table size error"); + +// TODO: We should not use global array, instead we can use one cluster instance per endpoint. +Delegate * gDelegateTable[kActionsDelegateTableSize] = { nullptr }; + +Delegate * GetDelegate(EndpointId aEndpoint) +{ + return (aEndpoint >= kActionsDelegateTableSize ? nullptr : gDelegateTable[aEndpoint]); +} + +CHIP_ERROR ValidateDelegate(Delegate * aDelegate, EndpointId aEndpoint) +{ + if (aDelegate == nullptr) + { + ChipLogError(Zcl, "Actions delegate is null for endpoint: %d !!!", aEndpoint); + return CHIP_ERROR_INCORRECT_STATE; + } + return CHIP_NO_ERROR; +} + +} // namespace + +ActionsServer ActionsServer::sInstance; + +void ActionsServer::SetDefaultDelegate(EndpointId aEndpoint, Delegate * aDelegate) +{ + if (aEndpoint < kActionsDelegateTableSize) + { + gDelegateTable[aEndpoint] = aDelegate; + } +} + +ActionsServer & ActionsServer::Instance() +{ + return sInstance; +} + +void ActionsServer::OnStateChanged(EndpointId aEndpoint, uint16_t aActionId, uint32_t aInvokeId, ActionStateEnum aActionState) +{ + ChipLogProgress(Zcl, "ActionsServer: OnStateChanged"); + + // Generate StateChanged event + EventNumber eventNumber; + Events::StateChanged::Type event{ aActionId, aInvokeId, aActionState }; + + if (CHIP_NO_ERROR != LogEvent(event, aEndpoint, eventNumber)) + { + ChipLogError(Zcl, "ActionsServer: Failed to generate OnStateChanged event"); + } +} + +void ActionsServer::OnActionFailed(EndpointId aEndpoint, uint16_t aActionId, uint32_t aInvokeId, ActionStateEnum aActionState, + ActionErrorEnum aActionError) +{ + ChipLogProgress(Zcl, "ActionsServer: OnActionFailed"); + + // Generate ActionFailed event + EventNumber eventNumber; + Events::ActionFailed::Type event{ aActionId, aInvokeId, aActionState, aActionError }; + + if (CHIP_NO_ERROR != LogEvent(event, aEndpoint, eventNumber)) + { + ChipLogError(Zcl, "ActionsServer: Failed to generate OnActionFailed event"); + } +} + +CHIP_ERROR ActionsServer::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) +{ + VerifyOrDie(aPath.mClusterId == Actions::Id); + + switch (aPath.mAttributeId) + { + case ActionList::Id: { + ReturnErrorOnFailure(aEncoder.EncodeList( + [this, aPath](const auto & encoder) -> CHIP_ERROR { return this->ReadActionListAttribute(aPath, encoder); })); + return CHIP_NO_ERROR; + } + case EndpointLists::Id: { + ReturnErrorOnFailure(aEncoder.EncodeList( + [this, aPath](const auto & encoder) -> CHIP_ERROR { return this->ReadEndpointListAttribute(aPath, encoder); })); + return CHIP_NO_ERROR; + } + default: + break; + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR ActionsServer::ReadActionListAttribute(const ConcreteReadAttributePath & aPath, + const AttributeValueEncoder::ListEncodeHelper & aEncoder) +{ + Delegate * delegate = GetDelegate(aPath.mEndpointId); + ReturnErrorOnFailure(ValidateDelegate(delegate, aPath.mEndpointId)); + + for (uint16_t i = 0; i < kMaxActionListLength; i++) + { + ActionStructStorage action; + CHIP_ERROR err = delegate->ReadActionAtIndex(i, action); + + if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) + { + return CHIP_NO_ERROR; + } + + ReturnErrorOnFailure(aEncoder.Encode(action)); + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR ActionsServer::ReadEndpointListAttribute(const ConcreteReadAttributePath & aPath, + const AttributeValueEncoder::ListEncodeHelper & aEncoder) +{ + Delegate * delegate = GetDelegate(aPath.mEndpointId); + ReturnErrorOnFailure(ValidateDelegate(delegate, aPath.mEndpointId)); + + for (uint16_t i = 0; i < kMaxEndpointListLength; i++) + { + EndpointListStorage epList; + + CHIP_ERROR err = delegate->ReadEndpointListAtIndex(i, epList); + if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) + { + return CHIP_NO_ERROR; + } + + ReturnErrorOnFailure(aEncoder.Encode(epList)); + } + return CHIP_NO_ERROR; +} + +bool ActionsServer::HaveActionWithId(EndpointId aEndpointId, uint16_t aActionId, uint16_t & aActionIndex) +{ + Delegate * delegate = GetDelegate(aEndpointId); + VerifyOrReturnValue(ValidateDelegate(delegate, aEndpointId) == CHIP_NO_ERROR, false); + return delegate->HaveActionWithId(aActionId, aActionIndex); +} + +template +void ActionsServer::HandleCommand(HandlerContext & handlerContext, FuncT func) +{ + if (!handlerContext.mCommandHandled && (handlerContext.mRequestPath.mCommandId == RequestT::GetCommandId())) + { + RequestT requestPayload; + + // If the command matches what the caller is looking for, let's mark this as being handled + // even if errors happen after this. This ensures that we don't execute any fall-back strategies + // to handle this command since at this point, the caller is taking responsibility for handling + // the command in its entirety, warts and all. + // + handlerContext.SetCommandHandled(); + + if (DataModel::Decode(handlerContext.mPayload, requestPayload) != CHIP_NO_ERROR) + { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, + Protocols::InteractionModel::Status::InvalidCommand); + return; + } + + uint16_t actionIndex = kMaxActionListLength; + if (!HaveActionWithId(handlerContext.mRequestPath.mEndpointId, requestPayload.actionID, actionIndex)) + { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Protocols::InteractionModel::Status::NotFound); + return; + } + if (actionIndex != kMaxActionListLength) + { + Delegate * delegate = GetDelegate(handlerContext.mRequestPath.mEndpointId); + ReturnOnFailure(ValidateDelegate(delegate, handlerContext.mRequestPath.mEndpointId)); + ActionStructStorage action; + delegate->ReadActionAtIndex(actionIndex, action); + // Check if the command bit is set in the SupportedCommands of an ations. + if (!(action.supportedCommands.Raw() & (1 << handlerContext.mRequestPath.mCommandId))) + { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, + Protocols::InteractionModel::Status::InvalidCommand); + return; + } + } + + func(handlerContext, requestPayload); + } +} + +void ActionsServer::HandleInstantAction(HandlerContext & ctx, const Commands::InstantAction::DecodableType & commandData) +{ + Delegate * delegate = GetDelegate(ctx.mRequestPath.mEndpointId); + Status status = Status::InvalidInState; + if (delegate != nullptr) + { + status = delegate->HandleInstantAction(commandData.actionID, commandData.invokeID); + } + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); +} + +void ActionsServer::HandleInstantActionWithTransition(HandlerContext & ctx, + const Commands::InstantActionWithTransition::DecodableType & commandData) +{ + Delegate * delegate = GetDelegate(ctx.mRequestPath.mEndpointId); + Status status = Status::InvalidInState; + if (delegate != nullptr) + { + status = + delegate->HandleInstantActionWithTransition(commandData.actionID, commandData.transitionTime, commandData.invokeID); + } + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); +} + +void ActionsServer::HandleStartAction(HandlerContext & ctx, const Commands::StartAction::DecodableType & commandData) +{ + Delegate * delegate = GetDelegate(ctx.mRequestPath.mEndpointId); + Status status = Status::InvalidInState; + if (delegate != nullptr) + { + status = delegate->HandleStartAction(commandData.actionID, commandData.invokeID); + } + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); +} + +void ActionsServer::HandleStartActionWithDuration(HandlerContext & ctx, + const Commands::StartActionWithDuration::DecodableType & commandData) +{ + Delegate * delegate = GetDelegate(ctx.mRequestPath.mEndpointId); + Status status = Status::InvalidInState; + if (delegate != nullptr) + { + status = delegate->HandleStartActionWithDuration(commandData.actionID, commandData.duration, commandData.invokeID); + } + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); +} + +void ActionsServer::HandleStopAction(HandlerContext & ctx, const Commands::StopAction::DecodableType & commandData) +{ + Delegate * delegate = GetDelegate(ctx.mRequestPath.mEndpointId); + Status status = Status::InvalidInState; + if (delegate != nullptr) + { + status = delegate->HandleStopAction(commandData.actionID, commandData.invokeID); + } + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); +} + +void ActionsServer::HandlePauseAction(HandlerContext & ctx, const Commands::PauseAction::DecodableType & commandData) +{ + Delegate * delegate = GetDelegate(ctx.mRequestPath.mEndpointId); + Status status = Status::InvalidInState; + if (delegate != nullptr) + { + status = delegate->HandlePauseAction(commandData.actionID, commandData.invokeID); + } + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); +} + +void ActionsServer::HandlePauseActionWithDuration(HandlerContext & ctx, + const Commands::PauseActionWithDuration::DecodableType & commandData) +{ + Delegate * delegate = GetDelegate(ctx.mRequestPath.mEndpointId); + Status status = Status::InvalidInState; + if (delegate != nullptr) + { + status = delegate->HandlePauseActionWithDuration(commandData.actionID, commandData.duration, commandData.invokeID); + } + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); +} + +void ActionsServer::HandleResumeAction(HandlerContext & ctx, const Commands::ResumeAction::DecodableType & commandData) +{ + Delegate * delegate = GetDelegate(ctx.mRequestPath.mEndpointId); + Status status = Status::InvalidInState; + if (delegate != nullptr) + { + status = delegate->HandleResumeAction(commandData.actionID, commandData.invokeID); + } + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); +} + +void ActionsServer::HandleEnableAction(HandlerContext & ctx, const Commands::EnableAction::DecodableType & commandData) +{ + Delegate * delegate = GetDelegate(ctx.mRequestPath.mEndpointId); + Status status = Status::InvalidInState; + if (delegate != nullptr) + { + status = delegate->HandleEnableAction(commandData.actionID, commandData.invokeID); + } + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); +} + +void ActionsServer::HandleEnableActionWithDuration(HandlerContext & ctx, + const Commands::EnableActionWithDuration::DecodableType & commandData) +{ + Delegate * delegate = GetDelegate(ctx.mRequestPath.mEndpointId); + Status status = Status::InvalidInState; + if (delegate != nullptr) + { + status = delegate->HandleEnableActionWithDuration(commandData.actionID, commandData.duration, commandData.invokeID); + } + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); +} + +void ActionsServer::HandleDisableAction(HandlerContext & ctx, const Commands::DisableAction::DecodableType & commandData) +{ + Delegate * delegate = GetDelegate(ctx.mRequestPath.mEndpointId); + Status status = Status::InvalidInState; + if (delegate != nullptr) + { + status = delegate->HandleDisableAction(commandData.actionID, commandData.invokeID); + } + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); +} + +void ActionsServer::HandleDisableActionWithDuration(HandlerContext & ctx, + const Commands::DisableActionWithDuration::DecodableType & commandData) +{ + Delegate * delegate = GetDelegate(ctx.mRequestPath.mEndpointId); + Status status = Status::InvalidInState; + if (delegate != nullptr) + { + status = delegate->HandleDisableActionWithDuration(commandData.actionID, commandData.duration, commandData.invokeID); + } + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); +} + +void ActionsServer::InvokeCommand(HandlerContext & handlerContext) +{ + switch (handlerContext.mRequestPath.mCommandId) + { + case Actions::Commands::InstantAction::Id: + HandleCommand( + handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleInstantAction(ctx, commandData); }); + return; + case Actions::Commands::InstantActionWithTransition::Id: + HandleCommand( + handlerContext, + [this](HandlerContext & ctx, const auto & commandData) { HandleInstantActionWithTransition(ctx, commandData); }); + return; + case Actions::Commands::StartAction::Id: + HandleCommand( + handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleStartAction(ctx, commandData); }); + return; + case Actions::Commands::StartActionWithDuration::Id: + HandleCommand( + handlerContext, + [this](HandlerContext & ctx, const auto & commandData) { HandleStartActionWithDuration(ctx, commandData); }); + return; + case Actions::Commands::StopAction::Id: + HandleCommand( + handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleStopAction(ctx, commandData); }); + return; + case Actions::Commands::PauseAction::Id: + HandleCommand( + handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandlePauseAction(ctx, commandData); }); + return; + case Actions::Commands::PauseActionWithDuration::Id: + HandleCommand( + handlerContext, + [this](HandlerContext & ctx, const auto & commandData) { HandlePauseActionWithDuration(ctx, commandData); }); + return; + case Actions::Commands::ResumeAction::Id: + HandleCommand( + handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleResumeAction(ctx, commandData); }); + return; + case Actions::Commands::EnableAction::Id: + HandleCommand( + handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleEnableAction(ctx, commandData); }); + return; + case Actions::Commands::EnableActionWithDuration::Id: + HandleCommand( + handlerContext, + [this](HandlerContext & ctx, const auto & commandData) { HandleEnableActionWithDuration(ctx, commandData); }); + return; + case Actions::Commands::DisableAction::Id: + HandleCommand( + handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleDisableAction(ctx, commandData); }); + return; + case Actions::Commands::DisableActionWithDuration::Id: + HandleCommand( + handlerContext, + [this](HandlerContext & ctx, const auto & commandData) { HandleDisableActionWithDuration(ctx, commandData); }); + return; + } +} + +void ActionsServer::ActionListModified(EndpointId aEndpoint) +{ + MarkDirty(aEndpoint, Attributes::ActionList::Id); +} + +void ActionsServer::EndpointListModified(EndpointId aEndpoint) +{ + MarkDirty(aEndpoint, Attributes::EndpointLists::Id); +} + +void MatterActionsPluginServerInitCallback() +{ + AttributeAccessInterfaceRegistry::Instance().Register(&ActionsServer::Instance()); + CommandHandlerInterfaceRegistry::Instance().RegisterCommandHandler(&ActionsServer::Instance()); +} diff --git a/src/app/clusters/actions-server/actions-server.h b/src/app/clusters/actions-server/actions-server.h new file mode 100644 index 00000000000000..2cacbe868739b6 --- /dev/null +++ b/src/app/clusters/actions-server/actions-server.h @@ -0,0 +1,329 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace Actions { + +static constexpr size_t kActionNameMaxSize = 128u; +static constexpr size_t kEndpointListNameMaxSize = 128u; + +static constexpr size_t kEndpointListMaxSize = 256u; + +class Delegate; + +struct ActionStructStorage : public Structs::ActionStruct::Type +{ + ActionStructStorage(){}; + + ActionStructStorage(uint16_t aAction, const CharSpan & aActionName, ActionTypeEnum aActionType, uint16_t aEpListID, + BitMask aCommands, ActionStateEnum aActionState) + { + Set(aAction, aActionName, aActionType, aEpListID, aCommands, aActionState); + } + + ActionStructStorage(const ActionStructStorage & aAction) { *this = aAction; } + + ActionStructStorage & operator=(const ActionStructStorage & aAction) + { + Set(aAction.actionID, aAction.name, aAction.type, aAction.endpointListID, aAction.supportedCommands, aAction.state); + return *this; + } + + void Set(uint16_t aAction, const CharSpan & aActionName, ActionTypeEnum aActionType, uint16_t aEpListID, + BitMask aCommands, ActionStateEnum aActionState) + { + actionID = aAction; + type = aActionType; + endpointListID = aEpListID; + supportedCommands = aCommands; + state = aActionState; + MutableCharSpan actionName(mBuffer); + CopyCharSpanToMutableCharSpanWithTruncation(aActionName, actionName); + name = actionName; + } + +private: + char mBuffer[kActionNameMaxSize]; +}; + +struct EndpointListStorage : public Structs::EndpointListStruct::Type +{ + EndpointListStorage(){}; + + EndpointListStorage(uint16_t aEpListId, const CharSpan & aEpListName, EndpointListTypeEnum aEpListType, + const DataModel::List & aEndpointList) + { + Set(aEpListId, aEpListName, aEpListType, aEndpointList); + } + + EndpointListStorage(const EndpointListStorage & aEpList) { *this = aEpList; } + + EndpointListStorage & operator=(const EndpointListStorage & aEpList) + { + Set(aEpList.endpointListID, aEpList.name, aEpList.type, aEpList.endpoints); + return *this; + } + + void Set(uint16_t aEpListId, const CharSpan & aEpListName, EndpointListTypeEnum aEpListType, + const DataModel::List & aEndpointList) + { + endpointListID = aEpListId; + type = aEpListType; + size_t epListSize = std::min(aEndpointList.size(), MATTER_ARRAY_SIZE(mEpList)); + + for (size_t index = 0; index < epListSize; index++) + { + mEpList[index] = aEndpointList[index]; + } + endpoints = DataModel::List(Span(mEpList, epListSize)); + MutableCharSpan epListName(mBuffer); + CopyCharSpanToMutableCharSpanWithTruncation(aEpListName, epListName); + name = epListName; + } + +private: + char mBuffer[kEndpointListNameMaxSize]; + EndpointId mEpList[kEndpointListMaxSize]; +}; + +class ActionsServer : public AttributeAccessInterface, public CommandHandlerInterface +{ +public: + // Register for the Actions cluster on all endpoints. + ActionsServer() : + AttributeAccessInterface(Optional::Missing(), Actions::Id), + CommandHandlerInterface(Optional::Missing(), Actions::Id) + {} + static ActionsServer & Instance(); + + /** + * @brief + * Called when the state of an action is changed. + */ + void OnStateChanged(EndpointId aEndpoint, uint16_t aActionId, uint32_t aInvokeId, ActionStateEnum aActionState); + + /** + * @brief + * Called when an action fails. + */ + void OnActionFailed(EndpointId aEndpoint, uint16_t aActionId, uint32_t aInvokeId, ActionStateEnum aActionState, + ActionErrorEnum aActionError); + + void SetDefaultDelegate(EndpointId aEndpointId, Delegate * aDelegate); + + CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; + + /** + * A notification from an application to the sever that an ActionList is modified.. + * + * @param aEndpoint The endpoint ID where the action should be updated + */ + void ActionListModified(EndpointId aEndpoint); + + /** + * A notification from an application to the sever that an EndpointList is modified.. + * + * @param aEndpoint The endpoint ID where the action should be updated + */ + void EndpointListModified(EndpointId aEndpoint); + +private: + static ActionsServer sInstance; + static constexpr size_t kMaxEndpointListLength = 256u; + static constexpr size_t kMaxActionListLength = 256u; + + CHIP_ERROR ReadActionListAttribute(const ConcreteReadAttributePath & aPath, + const AttributeValueEncoder::ListEncodeHelper & aEncoder); + CHIP_ERROR ReadEndpointListAttribute(const ConcreteReadAttributePath & aPath, + const AttributeValueEncoder::ListEncodeHelper & aEncoder); + bool HaveActionWithId(EndpointId aEndpointId, uint16_t aActionId, uint16_t & aActionIndex); + + // TODO: We should move to non-global dirty marker. + void MarkDirty(EndpointId aEndpointId, AttributeId aAttributeId) + { + MatterReportingAttributeChangeCallback(aEndpointId, Id, aAttributeId); + } + // Cannot use CommandHandlerInterface::HandleCommand directly because we need to do the HaveActionWithId() check before + // handling a command. + template + void HandleCommand(HandlerContext & handlerContext, FuncT func); + + void InvokeCommand(HandlerContext & handlerContext) override; + + void HandleInstantAction(HandlerContext & ctx, const Commands::InstantAction::DecodableType & commandData); + void HandleInstantActionWithTransition(HandlerContext & ctx, + const Commands::InstantActionWithTransition::DecodableType & commandData); + void HandleStartAction(HandlerContext & ctx, const Commands::StartAction::DecodableType & commandData); + void HandleStartActionWithDuration(HandlerContext & ctx, const Commands::StartActionWithDuration::DecodableType & commandData); + void HandleStopAction(HandlerContext & ctx, const Commands::StopAction::DecodableType & commandData); + void HandlePauseAction(HandlerContext & ctx, const Commands::PauseAction::DecodableType & commandData); + void HandlePauseActionWithDuration(HandlerContext & ctx, const Commands::PauseActionWithDuration::DecodableType & commandData); + void HandleResumeAction(HandlerContext & ctx, const Commands::ResumeAction::DecodableType & commandData); + void HandleEnableAction(HandlerContext & ctx, const Commands::EnableAction::DecodableType & commandData); + void HandleEnableActionWithDuration(HandlerContext & ctx, + const Commands::EnableActionWithDuration::DecodableType & commandData); + void HandleDisableAction(HandlerContext & ctx, const Commands::DisableAction::DecodableType & commandData); + void HandleDisableActionWithDuration(HandlerContext & ctx, + const Commands::DisableActionWithDuration::DecodableType & commandData); +}; + +class Delegate +{ +public: + virtual ~Delegate() = default; + + /** + * Get the action at the Nth index from list of actions. + * @param index The index of the action to be returned. It is assumed that actions are indexable from 0 and with no gaps. + * @param action A reference to the ActionStructStorage which should be initialized via copy/assignments or calling Set(). + * @return Returns a CHIP_NO_ERROR if there was no error and the action was returned successfully. + * CHIP_ERROR_PROVIDER_LIST_EXHAUSTED if the index is past the end of the list of actions. + */ + virtual CHIP_ERROR ReadActionAtIndex(uint16_t index, ActionStructStorage & action) = 0; + + /** + * Get the EndpointList at the Nth index from list of endpointList. + * @param index The index of the endpointList to be returned. It is assumed that endpoint lists are indexable from 0 and with no + * gaps. + * @param action A reference to the EndpointListStorage which should be initialized via copy/assignments or calling Set(). + * @return Returns a CHIP_NO_ERROR if there was no error and the epList was returned successfully. + * CHIP_ERROR_PROVIDER_LIST_EXHAUSTED if the index is past the end of the list of endpointLists. + */ + virtual CHIP_ERROR ReadEndpointListAtIndex(uint16_t index, EndpointListStorage & epList) = 0; + + /** + * Check whether there is an action with the given actionId in the list of actions. + * @param aActionId The action ID to search for. + * @param aActionIndex A reference to the index at which an action with matching aActionId. + * @return Returns a true if matching action is found otherwise false. + */ + virtual bool HaveActionWithId(uint16_t aActionId, uint16_t & aActionIndex) = 0; + + /** + * The implementations of the Handle* command callbacks below are expected to call OnStateChanged or + * OnActionFailed as needed to generate the events required by the spec. + */ + + /** + * @brief Callback that will be called to handle an InstantAction command. + * @param actionId The actionId of an action. + * It should report Status::Success if successful and may report other Status codes if it fails. + */ + virtual Protocols::InteractionModel::Status HandleInstantAction(uint16_t actionId, Optional invokeId) = 0; + + /** + * @brief Callback that will be called to handle an InstantActionWithTransition command. + * @param actionId The actionId of an action. + * @param transitionTime The time for transition from the current state to the new state. + * It should report Status::Success if successful and may report other Status codes if it fails. + */ + virtual Protocols::InteractionModel::Status HandleInstantActionWithTransition(uint16_t actionId, uint16_t transitionTime, + Optional invokeId) = 0; + + /** + * @brief Callback that will be called to handle a StartAction command. + * @param actionId The actionId of an action. + * It should report Status::Success if successful and may report other Status codes if it fails. + */ + virtual Protocols::InteractionModel::Status HandleStartAction(uint16_t actionId, Optional invokeId) = 0; + + /** + * @brief Callback that will be called to handle a StartActionWithDuration command. + * @param actionId The actionId of an action. + * @param duration The time for which an action shall be in start state. + * It should report Status::Success if successful and may report other Status codes if it fails. + */ + virtual Protocols::InteractionModel::Status HandleStartActionWithDuration(uint16_t actionId, uint32_t duration, + Optional invokeId) = 0; + + /** + * @brief Callback that will be called to handle a StopAction command. + * @param actionId The actionId of an action. + * It should report Status::Success if successful and may report other Status codes if it fails. + */ + virtual Protocols::InteractionModel::Status HandleStopAction(uint16_t actionId, Optional invokeId) = 0; + + /** + * @brief Callback that will be called to handle a PauseAction command. + * @param actionId The actionId of an action. + * It should report Status::Success if successful and may report other Status codes if it fails. + */ + virtual Protocols::InteractionModel::Status HandlePauseAction(uint16_t actionId, Optional invokeId) = 0; + + /** + * @brief Callback that will be called to handle a PauseActionWithDuration command. + * @param actionId The actionId of an action. + * @param duration The time for which an action shall be in pause state. + * It should report Status::Success if successful and may report other Status codes if it fails. + */ + virtual Protocols::InteractionModel::Status HandlePauseActionWithDuration(uint16_t actionId, uint32_t duration, + Optional invokeId) = 0; + + /** + * @brief Callback that will be called to handle a ResumeAction command. + * @param actionId The actionId of an action. + * It should report Status::Success if successful and may report other Status codes if it fails. + */ + virtual Protocols::InteractionModel::Status HandleResumeAction(uint16_t actionId, Optional invokeId) = 0; + + /** + * @brief Callback that will be called to handle an EnableAction command. + * @param actionId The actionId of an action. + * It should report Status::Success if successful and may report other Status codes if it fails. + */ + virtual Protocols::InteractionModel::Status HandleEnableAction(uint16_t actionId, Optional invokeId) = 0; + + /** + * @brief Callback that will be called to handle an EnableActionWithDuration command. + * @param actionId The actionId of an action. + * @param duration The time for which an action shall be in active state. + * It should report Status::Success if successful and may report other Status codes if it fails. + */ + virtual Protocols::InteractionModel::Status HandleEnableActionWithDuration(uint16_t actionId, uint32_t duration, + Optional invokeId) = 0; + + /** + * @brief Callback that will be called to handle a DisableAction command. + * @param actionId The actionId of an action. + * It should report Status::Success if successful and may report other Status codes if it fails. + */ + virtual Protocols::InteractionModel::Status HandleDisableAction(uint16_t actionId, Optional invokeId) = 0; + + /** + * @brief Callback that will be called to handle a DisableActionWithDuration command. + * @param actionId The actionId of an action. + * @param duration The time for which an action shall be in disable state. + * It should report Status::Success if successful and may report other Status codes if it fails. + */ + virtual Protocols::InteractionModel::Status HandleDisableActionWithDuration(uint16_t actionId, uint32_t duration, + Optional invokeId) = 0; +}; + +} // namespace Actions +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/app/common/templates/config-data.yaml b/src/app/common/templates/config-data.yaml index 7dcf2b6b68ec6f..53e4362e9a6192 100644 --- a/src/app/common/templates/config-data.yaml +++ b/src/app/common/templates/config-data.yaml @@ -54,6 +54,7 @@ CommandHandlerInterfaceOnlyClusters: - Software Diagnostics - Wi-Fi Network Diagnostics - Administrator Commissioning + - Actions # We need a more configurable way of deciding which clusters have which init functions.... # See https://github.com/project-chip/connectedhomeip/issues/4369 diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index ea077eb5d67582..20c3702cb29baa 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -109,6 +109,18 @@ source_set("power-cluster-test-srcs") { ] } +source_set("actions-cluster-test-srcs") { + sources = + [ "${chip_root}/src/app/clusters/actions-server/actions-server.cpp" ] + + public_deps = [ + "${chip_root}/src/app/common:cluster-objects", + "${chip_root}/src/app/util/mock:mock_codegen_data_model", + "${chip_root}/src/app/util/mock:mock_ember", + "${chip_root}/src/lib/core", + ] +} + source_set("scenes-table-test-srcs") { sources = [ "${chip_root}/src/app/clusters/scenes-server/ExtensionFieldSets.h", @@ -195,6 +207,7 @@ chip_test_suite("tests") { test_sources = [ "TestAclAttribute.cpp", "TestAclEvent.cpp", + "TestActionsCluster.cpp", "TestAttributeAccessInterfaceCache.cpp", "TestAttributePathExpandIterator.cpp", "TestAttributePathParams.cpp", @@ -271,6 +284,7 @@ chip_test_suite("tests") { "TestSceneTable.cpp", ] public_deps += [ + ":actions-cluster-test-srcs", ":power-cluster-test-srcs", ":scenes-table-test-srcs", ] diff --git a/src/app/tests/TestActionsCluster.cpp b/src/app/tests/TestActionsCluster.cpp new file mode 100644 index 00000000000000..f7034a075dc2b0 --- /dev/null +++ b/src/app/tests/TestActionsCluster.cpp @@ -0,0 +1,452 @@ +/* + * + * Copyright (c) 2025 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. + */ +#include + +#include "lib/support/CHIPMem.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT +#undef CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT +#define CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT 1 +#endif + +namespace chip { +namespace app { + +using namespace Clusters::Actions; +using namespace chip::Protocols::InteractionModel; + +class TestActionsCluster : public ::testing::Test +{ +public: + static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); } + static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); } +}; + +class TestActionsDelegateImpl : public Clusters::Actions::Delegate +{ +public: + EndpointId endpointId = 0; + static constexpr uint8_t kMaxActionNameLength = 128u; + static constexpr uint8_t kMaxEndpointListNameLength = 128u; + static constexpr uint16_t kEndpointListMaxSize = 256u; + static constexpr uint8_t kMaxActions = 2; + static constexpr uint8_t kMaxEndpointLists = 2; + + ActionStructStorage mActions[kMaxActions]; + uint16_t mNumActions = 0; + + EndpointListStorage mEndpointLists[kMaxEndpointLists]; + uint16_t mNumEndpointLists = 0; + + CHIP_ERROR ReadActionAtIndex(uint16_t index, ActionStructStorage & action) override + { + if (index >= mNumActions) + { + return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED; + } + action = mActions[index]; + return CHIP_NO_ERROR; + } + + CHIP_ERROR ReadEndpointListAtIndex(uint16_t index, EndpointListStorage & epList) override + { + if (index >= mNumEndpointLists) + { + return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED; + } + epList = mEndpointLists[index]; + return CHIP_NO_ERROR; + } + + bool HaveActionWithId(uint16_t actionId, uint16_t & aActionIndex) override + { + for (uint16_t i = 0; i < mNumActions; i++) + { + if (mActions[i].actionID == actionId) + { + aActionIndex = i; + return true; + } + } + return false; + } + + // Test helper methods + CHIP_ERROR AddTestAction(const ActionStructStorage & action) + { + if (mNumActions >= kMaxActions) + { + return CHIP_ERROR_BUFFER_TOO_SMALL; + } + mActions[mNumActions++] = action; + return CHIP_NO_ERROR; + } + + CHIP_ERROR AddTestEndpointList(const EndpointListStorage & epList) + { + if (mNumEndpointLists >= kMaxEndpointLists) + { + return CHIP_ERROR_BUFFER_TOO_SMALL; + } + mEndpointLists[mNumEndpointLists++] = epList; + return CHIP_NO_ERROR; + } + + Protocols::InteractionModel::Status HandleInstantAction(uint16_t actionId, Optional invokeId) override + { + return Status::NotFound; + } + Protocols::InteractionModel::Status HandleInstantActionWithTransition(uint16_t actionId, uint16_t transitionTime, + Optional invokeId) override + { + return Status::NotFound; + } + Protocols::InteractionModel::Status HandleStartAction(uint16_t actionId, Optional invokeId) override + { + return Status::NotFound; + } + Protocols::InteractionModel::Status HandleStartActionWithDuration(uint16_t actionId, uint32_t duration, + Optional invokeId) override + { + return Status::NotFound; + } + Protocols::InteractionModel::Status HandleStopAction(uint16_t actionId, Optional invokeId) override + { + return Status::NotFound; + } + Protocols::InteractionModel::Status HandlePauseAction(uint16_t actionId, Optional invokeId) override + { + return Status::NotFound; + } + Protocols::InteractionModel::Status HandlePauseActionWithDuration(uint16_t actionId, uint32_t duration, + Optional invokeId) override + { + return Status::NotFound; + } + Protocols::InteractionModel::Status HandleResumeAction(uint16_t actionId, Optional invokeId) override + { + return Status::NotFound; + } + Protocols::InteractionModel::Status HandleEnableAction(uint16_t actionId, Optional invokeId) override + { + return Status::NotFound; + } + Protocols::InteractionModel::Status HandleEnableActionWithDuration(uint16_t actionId, uint32_t duration, + Optional invokeId) override + { + return Status::NotFound; + } + Protocols::InteractionModel::Status HandleDisableAction(uint16_t actionId, Optional invokeId) override + { + return Status::NotFound; + } + Protocols::InteractionModel::Status HandleDisableActionWithDuration(uint16_t actionId, uint32_t duration, + Optional invokeId) override + { + return Status::NotFound; + } +}; + +TestActionsDelegateImpl delegate; +TEST_F(TestActionsCluster, TestActionListConstraints) +{ + // Test 1: Action name length constraint + char longName[kActionNameMaxSize + 10]; + memset(longName, 'A', sizeof(longName)); + longName[sizeof(longName) - 1] = '\0'; + + ActionStructStorage actionWithLongName(1, CharSpan::fromCharString(longName), ActionTypeEnum::kScene, 0, BitMask(), + ActionStateEnum::kInactive); + + // Add action and verify it was added successfully + EXPECT_EQ(delegate.AddTestAction(actionWithLongName), CHIP_NO_ERROR); + + // Verify the name was truncated by reading it back + ActionStructStorage readAction; + EXPECT_EQ(delegate.ReadActionAtIndex(0, readAction), CHIP_NO_ERROR); + EXPECT_EQ(readAction.name.size(), kActionNameMaxSize); + + // Test 2: Maximum action list size + delegate.mNumActions = 0; + for (uint16_t i = 0; i < delegate.kMaxActions; i++) + { + ActionStructStorage action(i, CharSpan::fromCharString("Action"), ActionTypeEnum::kScene, 0, BitMask(), + ActionStateEnum::kInactive); + EXPECT_EQ(delegate.AddTestAction(action), CHIP_NO_ERROR); + } + + // Try to add one more action beyond the limit + ActionStructStorage extraAction(99, CharSpan::fromCharString("Extra"), ActionTypeEnum::kScene, 0, BitMask(), + ActionStateEnum::kInactive); + EXPECT_EQ(delegate.AddTestAction(extraAction), CHIP_ERROR_BUFFER_TOO_SMALL); +} + +TEST_F(TestActionsCluster, TestEndpointListConstraints) +{ + // Test 1: Endpoint list name length constraint + char longName[kEndpointListNameMaxSize + 10]; + memset(longName, 'B', sizeof(longName)); + longName[sizeof(longName) - 1] = '\0'; + + const EndpointId endpoints[] = { 1, 2, 3 }; + EndpointListStorage epListWithLongName(1, CharSpan::fromCharString(longName), EndpointListTypeEnum::kOther, + DataModel::List(endpoints)); + + // Add endpoint list and verify it was added successfully + EXPECT_EQ(delegate.AddTestEndpointList(epListWithLongName), CHIP_NO_ERROR); + + // Verify the name was truncated by reading it back + EndpointListStorage readEpList; + EXPECT_EQ(delegate.ReadEndpointListAtIndex(0, readEpList), CHIP_NO_ERROR); + EXPECT_EQ(readEpList.name.size(), kEndpointListNameMaxSize); + + // Test 2: Maximum endpoint list size + delegate.mNumEndpointLists = 0; + for (uint16_t i = 0; i < delegate.kMaxEndpointLists; i++) + { + EndpointListStorage epList(i, CharSpan::fromCharString("List"), EndpointListTypeEnum::kOther, + DataModel::List(endpoints)); + EXPECT_EQ(delegate.AddTestEndpointList(epList), CHIP_NO_ERROR); + } + + // Try to add one more endpoint list beyond the limit + EndpointListStorage extraEpList(99, CharSpan::fromCharString("Extra"), EndpointListTypeEnum::kOther, + DataModel::List(endpoints)); + EXPECT_EQ(delegate.AddTestEndpointList(extraEpList), CHIP_ERROR_BUFFER_TOO_SMALL); + + // Test 3: Maximum endpoints per list + delegate.mNumEndpointLists = 0; + EndpointId tooManyEndpoints[kEndpointListMaxSize + 5]; + for (uint16_t i = 0; i < kEndpointListMaxSize + 5; i++) + { + tooManyEndpoints[i] = i; + } + + EndpointListStorage epListWithTooManyEndpoints(1, CharSpan::fromCharString("List"), EndpointListTypeEnum::kOther, + DataModel::List(tooManyEndpoints)); + + // The list should be added but truncated to kEndpointListMaxSize + EXPECT_EQ(delegate.AddTestEndpointList(epListWithTooManyEndpoints), CHIP_NO_ERROR); + + // Verify the endpoint list was truncated + EXPECT_EQ(delegate.ReadEndpointListAtIndex(0, readEpList), CHIP_NO_ERROR); + EXPECT_EQ(readEpList.endpoints.size(), kEndpointListMaxSize); +} + +TEST_F(TestActionsCluster, TestActionListAttributeAccess) +{ + ActionsServer::Instance().SetDefaultDelegate(delegate.endpointId, &delegate); + delegate.mNumActions = 0; + + // Add test actions + ActionStructStorage action1(1, CharSpan::fromCharString("FirstAction"), ActionTypeEnum::kScene, 0, BitMask(), + ActionStateEnum::kInactive); + ActionStructStorage action2(2, CharSpan::fromCharString("SecondAction"), ActionTypeEnum::kScene, 1, BitMask(), + ActionStateEnum::kActive); + + EXPECT_EQ(delegate.AddTestAction(action1), CHIP_NO_ERROR); + EXPECT_EQ(delegate.AddTestAction(action2), CHIP_NO_ERROR); + + // Test reading actions through attribute reader + uint8_t buf[1024]; + + // Create the TLV writer + TLV::TLVWriter tlvWriter; + tlvWriter.Init(buf); + + AttributeReportIBs::Builder builder; + builder.Init(&tlvWriter); + + ConcreteAttributePath path(delegate.endpointId, Clusters::Actions::Id, Clusters::Actions::Attributes::ActionList::Id); + ConcreteReadAttributePath readPath(path); + chip::DataVersion dataVersion(0); + Access::SubjectDescriptor subjectDescriptor; + AttributeValueEncoder encoder(builder, subjectDescriptor, path, dataVersion); + + // Read the action list using the Actions cluster's Read function + EXPECT_EQ(ActionsServer::Instance().Read(readPath, encoder), CHIP_NO_ERROR); + + TLV::TLVReader reader; + reader.Init(buf); + + TLV::TLVReader attrReportsReader; + TLV::TLVReader attrReportReader; + TLV::TLVReader attrDataReader; + + reader.Next(); + reader.OpenContainer(attrReportsReader); + + attrReportsReader.Next(); + attrReportsReader.OpenContainer(attrReportReader); + + attrReportReader.Next(); + attrReportReader.OpenContainer(attrDataReader); + + // We're now in the attribute data IB, skip to the desired tag, we want TagNum = 2 + attrDataReader.Next(); + for (int i = 0; i < 3 && !(IsContextTag(attrDataReader.GetTag()) && TagNumFromTag(attrDataReader.GetTag()) == 2); ++i) + { + attrDataReader.Next(); + } + EXPECT_TRUE(IsContextTag(attrDataReader.GetTag())); + EXPECT_EQ(TagNumFromTag(attrDataReader.GetTag()), 2u); + + Clusters::Actions::Attributes::ActionList::TypeInfo::DecodableType list; + CHIP_ERROR err = list.Decode(attrDataReader); + EXPECT_EQ(err, CHIP_NO_ERROR); + + auto iter = list.begin(); + EXPECT_TRUE(iter.Next()); + Clusters::Actions::Structs::ActionStruct::Type action = iter.GetValue(); + EXPECT_EQ(action.actionID, 1); + EXPECT_TRUE(strncmp(action.name.data(), "FirstAction", action.name.size()) == 0); + EXPECT_EQ(action.type, ActionTypeEnum::kScene); + EXPECT_EQ(action.endpointListID, 0); + EXPECT_EQ(action.supportedCommands.Raw(), 0); + EXPECT_EQ(action.state, ActionStateEnum::kInactive); + + EXPECT_TRUE(iter.Next()); + action = iter.GetValue(); + EXPECT_EQ(action.actionID, 2); + EXPECT_TRUE(strncmp(action.name.data(), "SecondAction", action.name.size()) == 0); + EXPECT_EQ(action.type, ActionTypeEnum::kScene); + EXPECT_EQ(action.endpointListID, 1); + EXPECT_EQ(action.supportedCommands.Raw(), 0); + EXPECT_EQ(action.state, ActionStateEnum::kActive); + + // Cleanup + ActionsServer::Instance().SetDefaultDelegate(delegate.endpointId, nullptr); +} + +TEST_F(TestActionsCluster, TestEndpointListAttributeAccess) +{ + ActionsServer::Instance().SetDefaultDelegate(delegate.endpointId, &delegate); + delegate.mNumEndpointLists = 0; + + // Add test endpoint lists + const EndpointId endpoints1[] = { 1, 2 }; + const EndpointId endpoints2[] = { 3, 4, 5 }; + + EndpointListStorage epList1(1, CharSpan::fromCharString("FirstList"), EndpointListTypeEnum::kOther, + DataModel::List(endpoints1, 2)); + EndpointListStorage epList2(2, CharSpan::fromCharString("SecondList"), EndpointListTypeEnum::kOther, + DataModel::List(endpoints2, 3)); + + EXPECT_EQ(delegate.AddTestEndpointList(epList1), CHIP_NO_ERROR); + EXPECT_EQ(delegate.AddTestEndpointList(epList2), CHIP_NO_ERROR); + + // Test reading endpoint lists through attribute reader + TLV::TLVWriter writer; + uint8_t buf[1024]; + writer.Init(buf); + + // Create the builders + TLV::TLVWriter tlvWriter; + tlvWriter.Init(buf); + + AttributeReportIBs::Builder builder; + builder.Init(&tlvWriter); + + ConcreteAttributePath path(delegate.endpointId, Clusters::Actions::Id, Clusters::Actions::Attributes::EndpointLists::Id); + ConcreteReadAttributePath readPath(path); + chip::DataVersion dataVersion(0); + Access::SubjectDescriptor subjectDescriptor; + AttributeValueEncoder encoder(builder, subjectDescriptor, path, dataVersion); + + EXPECT_EQ(ActionsServer::Instance().Read(path, encoder), CHIP_NO_ERROR); + + TLV::TLVReader reader; + reader.Init(buf); + + TLV::TLVReader attrReportsReader; + TLV::TLVReader attrReportReader; + TLV::TLVReader attrDataReader; + + reader.Next(); + reader.OpenContainer(attrReportsReader); + + attrReportsReader.Next(); + attrReportsReader.OpenContainer(attrReportReader); + + attrReportReader.Next(); + attrReportReader.OpenContainer(attrDataReader); + + // We're now in the attribute data IB, skip to the desired tag, we want TagNum = 2 + attrDataReader.Next(); + for (int i = 0; i < 3 && !(IsContextTag(attrDataReader.GetTag()) && TagNumFromTag(attrDataReader.GetTag()) == 2); ++i) + { + attrDataReader.Next(); + } + EXPECT_TRUE(IsContextTag(attrDataReader.GetTag())); + EXPECT_EQ(TagNumFromTag(attrDataReader.GetTag()), 2u); + + Clusters::Actions::Attributes::EndpointLists::TypeInfo::DecodableType list; + CHIP_ERROR err = list.Decode(attrDataReader); + EXPECT_EQ(err, CHIP_NO_ERROR); + + auto iter = list.begin(); + EXPECT_TRUE(iter.Next()); + Clusters::Actions::Structs::EndpointListStruct::DecodableType endpointList = iter.GetValue(); + EXPECT_EQ(endpointList.endpointListID, 1); + EXPECT_TRUE(strncmp(endpointList.name.data(), "FirstList", endpointList.name.size()) == 0); + EXPECT_EQ(endpointList.type, EndpointListTypeEnum::kOther); + auto it = endpointList.endpoints.begin(); + uint8_t i = 0; + while (it.Next()) + { + if (i < 2) + { + EXPECT_EQ(endpoints1[i++], it.GetValue()); + } + } + + EXPECT_TRUE(iter.Next()); + endpointList = iter.GetValue(); + EXPECT_EQ(endpointList.endpointListID, 2); + EXPECT_TRUE(strncmp(endpointList.name.data(), "SecondList", endpointList.name.size()) == 0); + EXPECT_EQ(endpointList.type, EndpointListTypeEnum::kOther); + it = endpointList.endpoints.begin(); + i = 0; + while (it.Next()) + { + if (i < 3) + { + EXPECT_EQ(endpoints2[i++], it.GetValue()); + } + } + + // Cleanup + ActionsServer::Instance().SetDefaultDelegate(delegate.endpointId, nullptr); +} + +} // namespace app +} // namespace chip diff --git a/src/app/util/mock/include/zap-generated/gen_config.h b/src/app/util/mock/include/zap-generated/gen_config.h index e3a53a6a6573bd..6200721d0b0509 100644 --- a/src/app/util/mock/include/zap-generated/gen_config.h +++ b/src/app/util/mock/include/zap-generated/gen_config.h @@ -29,7 +29,8 @@ #define MATTER_DM_DESCRIPTOR_CLUSTER_SERVER_ENDPOINT_COUNT (0) #define MATTER_DM_BINDING_CLUSTER_SERVER_ENDPOINT_COUNT (0) #define MATTER_DM_ACCESS_CONTROL_CLUSTER_SERVER_ENDPOINT_COUNT (0) -#define MATTER_DM_ACTIONS_CLUSTER_SERVER_ENDPOINT_COUNT (0) +// "The value of MATTER_DM_ACTIONS_CLUSTER_SERVER_ENDPOINT_COUNT needs to be at least one for unit testing the cluster. +#define MATTER_DM_ACTIONS_CLUSTER_SERVER_ENDPOINT_COUNT (1) #define MATTER_DM_BASIC_INFORMATION_CLUSTER_SERVER_ENDPOINT_COUNT (0) #define MATTER_DM_OTA_SOFTWARE_UPDATE_PROVIDER_CLUSTER_SERVER_ENDPOINT_COUNT (0) #define MATTER_DM_OTA_SOFTWARE_UPDATE_REQUESTOR_CLUSTER_SERVER_ENDPOINT_COUNT (0) diff --git a/src/app/zap_cluster_list.json b/src/app/zap_cluster_list.json index 0cbdba0d6e2c6e..bf9ad0e418d3fe 100644 --- a/src/app/zap_cluster_list.json +++ b/src/app/zap_cluster_list.json @@ -330,6 +330,7 @@ "WINDOW_COVERING_CLUSTER": ["window-covering-server"], "WATER_HEATER_MANAGEMENT_CLUSTER": ["water-heater-management-server"], "WATER_HEATER_MODE_CLUSTER": ["mode-base-server"], - "ZONE_MANAGEMENT_CLUSTER": [] + "ZONE_MANAGEMENT_CLUSTER": [], + "ACTIONS_CLUSTER": ["actions-server"] } } diff --git a/zzz_generated/app-common/app-common/zap-generated/callback.h b/zzz_generated/app-common/app-common/zap-generated/callback.h index c8c3b01f738881..819cda45d5ac75 100644 --- a/zzz_generated/app-common/app-common/zap-generated/callback.h +++ b/zzz_generated/app-common/app-common/zap-generated/callback.h @@ -5894,78 +5894,6 @@ bool emberAfLevelControlClusterMoveToClosestFrequencyCallback( bool emberAfAccessControlClusterReviewFabricRestrictionsCallback( chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, const chip::app::Clusters::AccessControl::Commands::ReviewFabricRestrictions::DecodableType & commandData); -/** - * @brief Actions Cluster InstantAction Command callback (from client) - */ -bool emberAfActionsClusterInstantActionCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::Actions::Commands::InstantAction::DecodableType & commandData); -/** - * @brief Actions Cluster InstantActionWithTransition Command callback (from client) - */ -bool emberAfActionsClusterInstantActionWithTransitionCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::Actions::Commands::InstantActionWithTransition::DecodableType & commandData); -/** - * @brief Actions Cluster StartAction Command callback (from client) - */ -bool emberAfActionsClusterStartActionCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::Actions::Commands::StartAction::DecodableType & commandData); -/** - * @brief Actions Cluster StartActionWithDuration Command callback (from client) - */ -bool emberAfActionsClusterStartActionWithDurationCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::Actions::Commands::StartActionWithDuration::DecodableType & commandData); -/** - * @brief Actions Cluster StopAction Command callback (from client) - */ -bool emberAfActionsClusterStopActionCallback(chip::app::CommandHandler * commandObj, - const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::Actions::Commands::StopAction::DecodableType & commandData); -/** - * @brief Actions Cluster PauseAction Command callback (from client) - */ -bool emberAfActionsClusterPauseActionCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::Actions::Commands::PauseAction::DecodableType & commandData); -/** - * @brief Actions Cluster PauseActionWithDuration Command callback (from client) - */ -bool emberAfActionsClusterPauseActionWithDurationCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::Actions::Commands::PauseActionWithDuration::DecodableType & commandData); -/** - * @brief Actions Cluster ResumeAction Command callback (from client) - */ -bool emberAfActionsClusterResumeActionCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::Actions::Commands::ResumeAction::DecodableType & commandData); -/** - * @brief Actions Cluster EnableAction Command callback (from client) - */ -bool emberAfActionsClusterEnableActionCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::Actions::Commands::EnableAction::DecodableType & commandData); -/** - * @brief Actions Cluster EnableActionWithDuration Command callback (from client) - */ -bool emberAfActionsClusterEnableActionWithDurationCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::Actions::Commands::EnableActionWithDuration::DecodableType & commandData); -/** - * @brief Actions Cluster DisableAction Command callback (from client) - */ -bool emberAfActionsClusterDisableActionCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::Actions::Commands::DisableAction::DecodableType & commandData); -/** - * @brief Actions Cluster DisableActionWithDuration Command callback (from client) - */ -bool emberAfActionsClusterDisableActionWithDurationCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::Actions::Commands::DisableActionWithDuration::DecodableType & commandData); /** * @brief Basic Information Cluster MfgSpecificPing Command callback (from client) */