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 813d16d965148e..9159ad565bdac7 100644 --- a/src/app/clusters/mode-select-server/mode-select-server.cpp +++ b/src/app/clusters/mode-select-server/mode-select-server.cpp @@ -30,9 +30,14 @@ #include #include #include +#include #include #include +#ifdef MATTER_DM_PLUGIN_SCENES_MANAGEMENT +#include +#endif // MATTER_DM_PLUGIN_SCENES_MANAGEMENT + #ifdef MATTER_DM_PLUGIN_ON_OFF #include #endif // MATTER_DM_PLUGIN_ON_OFF @@ -109,35 +114,200 @@ 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) { MATTER_TRACE_SCOPE("ChangeToMode", "ModeSelect"); 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; const ModeSelect::SupportedModesManager * gSupportedModeManager = ModeSelect::getSupportedModesManager(); if (gSupportedModeManager == nullptr) { ChipLogError(Zcl, "ModeSelect: SupportedModesManager is NULL"); - commandHandler->AddStatus(commandPath, Status::Failure); - return true; + return Status::Failure; } Status checkSupportedModeStatus = gSupportedModeManager->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 true; + return checkSupportedModeStatus; } ModeSelect::Attributes::CurrentMode::Set(endpointId, newMode); + return Status::Success; +} + +} // anonymous namespace + +#if defined(MATTER_DM_PLUGIN_SCENES_MANAGEMENT) && CHIP_CONFIG_SCENES_USE_DEFAULT_HANDLERS +static constexpr size_t kModeSelectMaxEnpointCount = + MATTER_DM_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 kScenableAttributeCount = 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 + { + if (emberAfContainsServer(endpoint, ModeSelect::Id) && clusterBuffer.size() >= 1) + { + clusterBuffer[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 [in] endpoint target endpoint + /// @param [in] cluster target cluster + /// @param [out] 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 = ScenesManagement::Structs::AttributeValuePairStruct::Type; + + uint8_t currentMode; + // read CurrentMode value + Status status = Attributes::CurrentMode::Get(endpoint, ¤tMode); + if (status != Status::Success) + { + ChipLogError(Zcl, "ERR: reading CurrentMode 0x%02x", to_underlying(status)); + return CHIP_ERROR_READ_FAILED; + } + + AttributeValuePair pairs[kScenableAttributeCount]; + + pairs[0].attributeID = Attributes::CurrentMode::Id; + pairs[0].valueUnsigned8.SetValue(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 <= kScenableAttributeCount, 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); + VerifyOrReturnError(decodePair.valueUnsigned8.HasValue(), CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorOnFailure(mSceneEndpointStatePairs.InsertPair( + ModeSelectEndPointPair(endpoint, static_cast(decodePair.valueUnsigned8.HasValue())))); + } + // Verify that the EFS was completely read + CHIP_ERROR err = pair_iterator.GetStatus(); + if (CHIP_NO_ERROR != err) + { + mSceneEndpointStatePairs.RemovePair(endpoint); + return err; + } + + VerifyOrReturnError(mTransitionTimeInterface.sceneEventControl(endpoint) != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Milliseconds32(timeMs), timerCallback, + mTransitionTimeInterface.sceneEventControl(endpoint)); + + return CHIP_NO_ERROR; + } + +private: + ModeSelectTransitionTimeInterface mTransitionTimeInterface = + ModeSelectTransitionTimeInterface(ModeSelect::Id, sceneModeSelectCallback); +}; +static DefaultModeSelectSceneHandler sModeSelectSceneHandler; + +static void timerCallback(System::Layer *, void * callbackContext) +{ + auto control = static_cast(callbackContext); + (control->callback)(control->endpoint); +} + +/** + * @brief This function is a callback to apply the mode that was saved when the ApplyScene was called with a transition time greater + * than 0. + * + * @param endpoint The endpoint ID that the scene mode selection is associated with. + * + */ +static void sceneModeSelectCallback(EndpointId endpoint) +{ + ModeSelectEndPointPair savedState; + ReturnOnFailure(sModeSelectSceneHandler.mSceneEndpointStatePairs.GetPair(endpoint, savedState)); + ChangeToMode(endpoint, savedState.mValue); + sModeSelectSceneHandler.mSceneEndpointStatePairs.RemovePair(endpoint); +} + +#endif // defined(MATTER_DM_PLUGIN_SCENES_MANAGEMENT) && CHIP_CONFIG_SCENES_USE_DEFAULT_HANDLERS + +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 MATTER_DM_PLUGIN_SCENES_MANAGEMENT + 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 + ScenesManagement::ScenesServer::Instance().MakeSceneInvalidForAllFabrics(commandPath.mEndpointId); + } +#endif // MATTER_DM_PLUGIN_SCENES_MANAGEMENT + + Status status = ChangeToMode(commandPath.mEndpointId, commandData.newMode); + + if (Status::Success != status) + { + commandHandler->AddStatus(commandPath, status); + return true; + } + ChipLogProgress(Zcl, "ModeSelect: ChangeToMode successful"); - commandHandler->AddStatus(commandPath, Status::Success); + commandHandler->AddStatus(commandPath, status); return true; } @@ -148,6 +318,10 @@ bool emberAfModeSelectClusterChangeToModeCallback(CommandHandler * commandHandle */ void emberAfModeSelectClusterServerInitCallback(EndpointId endpointId) { +#if defined(MATTER_DM_PLUGIN_SCENES_MANAGEMENT) && CHIP_CONFIG_SCENES_USE_DEFAULT_HANDLERS + ScenesManagement::ScenesServer::Instance().RegisterSceneHandler(endpointId, &sModeSelectSceneHandler); +#endif // defined(MATTER_DM_PLUGIN_SCENES_MANAGEMENT) && CHIP_CONFIG_SCENES_USE_DEFAULT_HANDLERS + // StartUp behavior relies on CurrentMode StartUpMode attributes being non-volatile. if (areStartUpModeAndCurrentModeNonVolatile(endpointId)) { @@ -161,6 +335,7 @@ void emberAfModeSelectClusterServerInitCallback(EndpointId endpointId) Status status = Attributes::StartUpMode::Get(endpointId, startUpMode); if (status == Status::Success && !startUpMode.IsNull()) { + #ifdef MATTER_DM_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/TestScenesFabricSceneInfo.yaml b/src/app/tests/suites/TestScenesFabricSceneInfo.yaml index ba83953588bd3b..2d4a09c07eeb6a 100644 --- a/src/app/tests/suites/TestScenesFabricSceneInfo.yaml +++ b/src/app/tests/suites/TestScenesFabricSceneInfo.yaml @@ -253,6 +253,11 @@ tests: }, ], }, + { + ClusterID: 0x0050, + AttributeValueList: + [{ AttributeID: 0x0003, ValueUnsigned8: 0x01 }], + }, ] response: values: @@ -349,6 +354,11 @@ tests: }, ], }, + { + ClusterID: 0x0050, + AttributeValueList: + [{ AttributeID: 0x0003, ValueUnsigned8: 0x01 }], + }, ] - label: "Read the FabricSceneInfo attribute (0x0007) " diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index 7827f5a8c335f2..382c63497da51d 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1485,11 +1485,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 4 for a typical use case (onOff + level control + color + * control cluster + mode selec cluster). Needs to be changed in case a greater number of clusters is chosen. In the event the + * device does not need to support the mode select cluster, the maximum number of clusters per scene should be set to 3. */ #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 /**