diff --git a/src/app/clusters/mode-select-server/mode-select-server.cpp b/src/app/clusters/mode-select-server/mode-select-server.cpp index 2c9d88c1872737..0f7fe86e4b3d90 100644 --- a/src/app/clusters/mode-select-server/mode-select-server.cpp +++ b/src/app/clusters/mode-select-server/mode-select-server.cpp @@ -37,6 +37,10 @@ #include #endif // EMBER_AF_PLUGIN_ON_OFF +#ifdef EMBER_AF_PLUGIN_SCENES +#include +#endif // EMBER_AF_PLUGIN_SCENES + using namespace std; using namespace chip; using namespace chip::app; @@ -93,25 +97,183 @@ CHIP_ERROR ModeSelectAttrAccess::Read(const ConcreteReadAttributePath & aPath, A return CHIP_NO_ERROR; } -} // anonymous namespace - -bool emberAfModeSelectClusterChangeToModeCallback(CommandHandler * commandHandler, const ConcreteCommandPath & commandPath, - const ModeSelect::Commands::ChangeToMode::DecodableType & commandData) +Status ChangeToMode(EndpointId endpointId, uint8_t newMode) { - ChipLogProgress(Zcl, "ModeSelect: Entering emberAfModeSelectClusterChangeToModeCallback"); - EndpointId endpointId = commandPath.mEndpointId; - uint8_t newMode = commandData.newMode; // Check that the newMode matches one of the supported options const ModeSelect::Structs::ModeOptionStruct::Type * modeOptionPtr; Status checkSupportedModeStatus = ModeSelect::getSupportedModesManager()->getModeOptionByMode(endpointId, newMode, &modeOptionPtr); if (Status::Success != checkSupportedModeStatus) { - ChipLogProgress(Zcl, "ModeSelect: Failed to find the option with mode %u", newMode); - commandHandler->AddStatus(commandPath, checkSupportedModeStatus); + return checkSupportedModeStatus; + } + + uint8_t oldMode = 0; + ModeSelect::Attributes::CurrentMode::Get(endpointId, &oldMode); + + return Status::Success; +} + +} // anonymous namespace + +#ifdef EMBER_AF_PLUGIN_SCENES +static constexpr size_t kModeSelectMaxEnpointCount = + EMBER_AF_MODE_SELECT_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; + +static void timerCallback(System::Layer *, void * callbackContext); +static void sceneModeSelectCallback(EndpointId endpoint); +using ModeSelectEndPointPair = scenes::DefaultSceneHandlerImpl::EndpointStatePair; +using ModeSelectTransitionTimeInterface = + scenes::DefaultSceneHandlerImpl::TransitionTimeInterface; + +class DefaultModeSelectSceneHandler : public scenes::DefaultSceneHandlerImpl +{ +public: + DefaultSceneHandlerImpl::StatePairBuffer mSceneEndpointStatePairs; + // As per spec, 1 attribute is scenable in the mode select cluster + static constexpr uint8_t scenableAttributeCount = 1; + + DefaultModeSelectSceneHandler() = default; + ~DefaultModeSelectSceneHandler() override {} + + // Default function for the mode select cluster, only puts the mode select cluster ID in the span if supported on the given + // endpoint + virtual void GetSupportedClusters(EndpointId endpoint, Span & clusterBuffer) override + { + ClusterId * buffer = clusterBuffer.data(); + if (emberAfContainsServer(endpoint, ModeSelect::Id) && clusterBuffer.size() >= 1) + { + buffer[0] = ModeSelect::Id; + clusterBuffer.reduce_size(1); + } + else + { + clusterBuffer.reduce_size(0); + } + } + + // Default function for mode select cluster, only checks if mode select is enabled on the endpoint + bool SupportsCluster(EndpointId endpoint, ClusterId cluster) override + { + return (cluster == ModeSelect::Id) && (emberAfContainsServer(endpoint, ModeSelect::Id)); + } + + /// @brief Serialize the Cluster's EFS value + /// @param endpoint target endpoint + /// @param cluster target cluster + /// @param serializedBytes data to serialize into EFS + /// @return CHIP_NO_ERROR if successfully serialized the data, CHIP_ERROR_INVALID_ARGUMENT otherwise + CHIP_ERROR SerializeSave(EndpointId endpoint, ClusterId cluster, MutableByteSpan & serializedBytes) override + { + using AttributeValuePair = Scenes::Structs::AttributeValuePair::Type; + + uint8_t currentMode; + // read CurrentMode value + EmberAfStatus status = Attributes::CurrentMode::Get(endpoint, ¤tMode); + if (status != EMBER_ZCL_STATUS_SUCCESS) + { + ChipLogError(Zcl, "ERR: reading CurrentMode %x", status); + return CHIP_ERROR_READ_FAILED; + } + + AttributeValuePair pairs[scenableAttributeCount]; + + pairs[0].attributeID = Attributes::CurrentMode::Id; + pairs[0].attributeValue = currentMode; + + app::DataModel::List attributeValueList(pairs); + + return EncodeAttributeValueList(attributeValueList, serializedBytes); + } + + /// @brief Default EFS interaction when applying scene to the ModeSelect Cluster + /// @param endpoint target endpoint + /// @param cluster target cluster + /// @param serializedBytes Data from nvm + /// @param timeMs transition time in ms + /// @return CHIP_NO_ERROR if value as expected, CHIP_ERROR_INVALID_ARGUMENT otherwise + CHIP_ERROR ApplyScene(EndpointId endpoint, ClusterId cluster, const ByteSpan & serializedBytes, + scenes::TransitionTimeMs timeMs) override + { + app::DataModel::DecodableList attributeValueList; + + VerifyOrReturnError(cluster == ModeSelect::Id, CHIP_ERROR_INVALID_ARGUMENT); + + ReturnErrorOnFailure(DecodeAttributeValueList(serializedBytes, attributeValueList)); + + size_t attributeCount = 0; + ReturnErrorOnFailure(attributeValueList.ComputeSize(&attributeCount)); + VerifyOrReturnError(attributeCount <= scenableAttributeCount, CHIP_ERROR_BUFFER_TOO_SMALL); + + auto pair_iterator = attributeValueList.begin(); + while (pair_iterator.Next()) + { + auto & decodePair = pair_iterator.GetValue(); + VerifyOrReturnError(decodePair.attributeID == Attributes::CurrentMode::Id, CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorOnFailure(mSceneEndpointStatePairs.InsertPair( + ModeSelectEndPointPair(endpoint, static_cast(decodePair.attributeValue)))); + } + // Verify that the EFS was completely read + CHIP_ERROR err = pair_iterator.GetStatus(); + if (CHIP_NO_ERROR != err) + { + mSceneEndpointStatePairs.RemovePair(endpoint); + return err; + } + + DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Milliseconds32(timeMs), timerCallback, + mTransitionTimeInterface.sceneEventControl(endpoint)); + + return CHIP_NO_ERROR; + } + +private: + ModeSelectTransitionTimeInterface mTransitionTimeInterface = + ModeSelectTransitionTimeInterface(Attributes::CurrentMode::Id, sceneModeSelectCallback); +}; +static DefaultModeSelectSceneHandler sModeSelectSceneHandler; + +static void timerCallback(System::Layer *, void * callbackContext) +{ + auto control = static_cast(callbackContext); + (control->callback)(control->endpoint); +} + +static void sceneModeSelectCallback(EndpointId endpoint) +{ + ModeSelectEndPointPair savedState; + ReturnOnFailure(sModeSelectSceneHandler.mSceneEndpointStatePairs.GetPair(endpoint, savedState)); + ChangeToMode(endpoint, savedState.mValue); + sModeSelectSceneHandler.mSceneEndpointStatePairs.RemovePair(endpoint); +} + +#endif // EMBER_AF_PLUGIN_SCENES + +bool emberAfModeSelectClusterChangeToModeCallback(CommandHandler * commandHandler, const ConcreteCommandPath & commandPath, + const ModeSelect::Commands::ChangeToMode::DecodableType & commandData) +{ + ChipLogProgress(Zcl, "ModeSelect: Entering emberAfModeSelectClusterChangeToModeCallback"); + + uint8_t currentMode = 0; + ModeSelect::Attributes::CurrentMode::Get(commandPath.mEndpointId, ¤tMode); +#ifdef EMBER_AF_PLUGIN_SCENES + if (currentMode != commandData.newMode) + { + // the scene has been changed (the value of CurrentMode has changed) so + // the current scene as described in the scene table is invalid + Scenes::ScenesServer::Instance().MakeSceneInvalidForAllFabrics(commandPath.mEndpointId); + } +#endif // EMBER_AF_PLUGIN_SCENES + + Status status = ChangeToMode(commandPath.mEndpointId, commandData.newMode); + + if (Status::Success != status) + { + ChipLogProgress(Zcl, "ModeSelect: Failed to find the option with mode %u", commandData.newMode); + commandHandler->AddStatus(commandPath, status); return true; } - ModeSelect::Attributes::CurrentMode::Set(endpointId, newMode); ChipLogProgress(Zcl, "ModeSelect: ChangeToMode successful"); commandHandler->AddStatus(commandPath, Status::Success); @@ -138,6 +300,11 @@ void emberAfModeSelectClusterServerInitCallback(EndpointId endpointId) EmberAfStatus status = Attributes::StartUpMode::Get(endpointId, startUpMode); if (status == EMBER_ZCL_STATUS_SUCCESS && !startUpMode.IsNull()) { + +#ifdef EMBER_AF_PLUGIN_SCENES + app::Clusters::Scenes::ScenesServer::Instance().RegisterSceneHandler(endpointId, &sModeSelectSceneHandler); +#endif // EMBER_AF_PLUGIN_SCENES + #ifdef EMBER_AF_PLUGIN_ON_OFF // OnMode with Power Up // If the On/Off feature is supported and the On/Off cluster attribute StartUpOnOff is present, with a diff --git a/src/app/tests/suites/certification/Test_TC_S_2_2.yaml b/src/app/tests/suites/certification/Test_TC_S_2_2.yaml index 5601e168d5ab34..8519b25446a41b 100644 --- a/src/app/tests/suites/certification/Test_TC_S_2_2.yaml +++ b/src/app/tests/suites/certification/Test_TC_S_2_2.yaml @@ -475,6 +475,16 @@ tests: }, ], }, + { + ClusterID: 0x0050, + AttributeValueList: + [ + { + AttributeID: 0x0003, + AttributeValue: 0x00, + }, + ], + }, ] - label: diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index 3fd5b497cb57b9..d058fbdde0390d 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1429,11 +1429,12 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #endif /** - * @brief The maximum number of clusters per scene, defaults to 3 for a typical usecase (onOff + level control + color control - * cluster). Needs to be changed in case a greater number of clusters is chosen. + * @brief The maximum number of clusters per scene, we recommend using 3 for a typical use case (onOff + level control + color + * control cluster). Needs to be changed in case a greater number of clusters is chosen. Now set to 4 to allow for the addition of + * the mode select cluster. */ #ifndef CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENE -#define CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENE 3 +#define CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENE 4 #endif /**