| /* |
| * |
| * Copyright (c) 2025-2026 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. |
| */ |
| |
| #pragma once |
| |
| #include <app/server-cluster/DefaultServerCluster.h> |
| #include <app/server-cluster/OptionalAttributeSet.h> |
| #include <clusters/ClosureControl/Attributes.h> |
| #include <clusters/ClosureControl/Commands.h> |
| #include <clusters/ClosureControl/Metadata.h> |
| |
| #include <app/cluster-building-blocks/QuieterReporting.h> |
| #include <app/clusters/closure-control-server/ClosureControlClusterDelegate.h> |
| #include <app/clusters/closure-control-server/ClosureControlClusterObjects.h> |
| #include <lib/core/CHIPError.h> |
| #include <lib/support/BitFlags.h> |
| #include <lib/support/TimerDelegate.h> |
| #include <lib/support/logging/CHIPLogging.h> |
| |
| namespace chip { |
| namespace app { |
| namespace Clusters { |
| namespace ClosureControl { |
| |
| using OptionalAttributesSet = OptionalAttributeSet<ClosureControl::Attributes::CountdownTime::Id>; |
| |
| // As per the spec, the maximum allowed CurrentErrorList size is 10. |
| constexpr int kCurrentErrorListMaxSize = 10; |
| |
| /** |
| * @brief Structure is used to configure and validate the Cluster configuration. |
| * Validates if the feature map, attributes and commands configuration is valid. |
| */ |
| struct ClusterConformance |
| { |
| public: |
| BitFlags<Feature> & FeatureMap() { return mFeatureMap; } |
| const BitFlags<Feature> & FeatureMap() const { return mFeatureMap; } |
| |
| OptionalAttributesSet & OptionalAttributes() { return mOptionalAttributes; } |
| |
| inline bool HasFeature(Feature aFeature) const { return mFeatureMap.Has(aFeature); } |
| |
| /** |
| * @brief Function determines if Cluster conformance is valid |
| * |
| * The function executes these checks in order to validate the conformance |
| * 1. Check if either Positioning or MotionLatching is supported. If neither are enabled, returns false. |
| * 2. If Speed is enabled, checks that Positioning is enabled and Instantaneous is disabled. Returns false otherwise. |
| * 3. If Ventilation, pedestrian or calibration is enabled, Positioning must be enabled. Return false otherwise. |
| * |
| * @return true, the cluster confirmance is valid |
| * false, otherwise |
| */ |
| bool IsValid() const |
| { |
| // Positioning or Matching must be enabled |
| VerifyOrReturnValue(HasFeature(Feature::kPositioning) || HasFeature(Feature::kMotionLatching), false, |
| ChipLogError(AppServer, "Validation failed: Neither Positioning nor MotionLatching is enabled.")); |
| |
| // If Speed is enabled, Positioning shall be enabled and Instantaneous shall be disabled. |
| if (HasFeature(Feature::kSpeed)) |
| { |
| VerifyOrReturnValue( |
| HasFeature(Feature::kPositioning) && !HasFeature(Feature::kInstantaneous), false, |
| ChipLogError(AppServer, "Validation failed: Speed requires Positioning enabled and Instantaneous disabled.")); |
| } |
| |
| if (HasFeature(Feature::kVentilation) || HasFeature(Feature::kPedestrian) || HasFeature(Feature::kCalibration)) |
| { |
| VerifyOrReturnValue( |
| HasFeature(Feature::kPositioning), false, |
| ChipLogError(AppServer, |
| "Validation failed: Ventilation, Pedestrian, or Calibration requires Positioning enabled.")); |
| } |
| |
| return true; |
| } |
| |
| private: |
| BitFlags<Feature> mFeatureMap; |
| OptionalAttributesSet mOptionalAttributes; |
| }; |
| |
| /** |
| * @brief Struct to store the current cluster state |
| */ |
| struct ClusterState |
| { |
| ClusterState() |
| { |
| // Configure CountdownTime Quiet Reporting strategies |
| // - When it changes from 0 to any other value and vice versa |
| // - When it increases |
| // - When it changes from null to any other value and vice versa (default support) |
| mCountdownTime.policy() |
| .Set(QuieterReportingPolicyEnum::kMarkDirtyOnIncrement) |
| .Set(QuieterReportingPolicyEnum::kMarkDirtyOnChangeToFromZero); |
| }; |
| |
| QuieterReportingAttribute<ElapsedS> mCountdownTime{ DataModel::NullNullable }; |
| MainStateEnum mMainState = MainStateEnum::kUnknownEnumValue; |
| DataModel::Nullable<GenericOverallCurrentState> mOverallCurrentState = DataModel::NullNullable; |
| DataModel::Nullable<GenericOverallTargetState> mOverallTargetState = DataModel::NullNullable; |
| BitFlags<LatchControlModesBitmap> mLatchControlModes; |
| ClosureErrorEnum mCurrentErrorList[kCurrentErrorListMaxSize] = {}; |
| |
| // The current error count is used to track the number of errors in the CurrentErrorList. |
| size_t mCurrentErrorCount = 0; |
| }; |
| |
| /** |
| * @brief Struct to store the cluster initialization parameters |
| */ |
| struct ClusterInitParameters |
| { |
| MainStateEnum mMainState = MainStateEnum::kStopped; |
| DataModel::Nullable<GenericOverallCurrentState> mOverallCurrentState = DataModel::NullNullable; |
| }; |
| |
| /** |
| * @brief Code-driven implementation of the Closure Control cluster. |
| * |
| * This class integrates the existing ClusterLogic with the DefaultServerCluster |
| * interface to provide a code-driven cluster implementation. |
| */ |
| class ClosureControlCluster : public DefaultServerCluster |
| { |
| |
| public: |
| /** |
| * @brief Context structure for injecting dependencies into the cluster. |
| */ |
| struct Context |
| { |
| ClosureControlClusterDelegate & delegate; |
| TimerDelegate & timerDelegate; |
| const ClusterConformance & conformance; |
| const ClusterInitParameters & initParams; |
| }; |
| |
| /** |
| * Creates a Closure Control Cluster instance. |
| * @param endpointId The endpoint on which this cluster exists. |
| * @param context The context containing injected dependencies. |
| */ |
| ClosureControlCluster(EndpointId endpointId, const Context & context); |
| ~ClosureControlCluster(); |
| |
| CHIP_ERROR Attributes(const ConcreteClusterPath & path, ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder) override; |
| |
| CHIP_ERROR AcceptedCommands(const ConcreteClusterPath & path, |
| ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder) override; |
| |
| DataModel::ActionReturnStatus ReadAttribute(const DataModel::ReadAttributeRequest & request, |
| AttributeValueEncoder & encoder) override; |
| |
| std::optional<DataModel::ActionReturnStatus> InvokeCommand(const DataModel::InvokeRequest & request, |
| chip::TLV::TLVReader & input_arguments, |
| CommandHandler * handler) override; |
| |
| DataModel::Nullable<ElapsedS> GetCountdownTime() const; |
| MainStateEnum GetMainState() const; |
| DataModel::Nullable<GenericOverallCurrentState> GetOverallCurrentState() const; |
| DataModel::Nullable<GenericOverallTargetState> GetOverallTargetState() const; |
| BitFlags<LatchControlModesBitmap> GetLatchControlModes() const; |
| BitFlags<Feature> GetFeatureMap() const; |
| |
| /** |
| * @brief Gets the current error list. |
| * This method is used to retrieve the current error list. |
| * The outputSpan must initially be of size kCurrentErrorListMaxSize and will be resized to the correct size for the |
| * list. |
| * @param[out] outputSpan The span to fill with the current error list. |
| * |
| * @return CHIP_NO_ERROR if the retrieval was successful. |
| * CHIP_ERROR_BUFFER_TOO_SMALL if the outputSpan size is not equal to kCurrentErrorListMaxSize. |
| */ |
| CHIP_ERROR GetCurrentErrorList(Span<ClosureErrorEnum> & outputSpan); |
| |
| /** |
| * @brief Set SetOverallCurrentState. |
| * |
| * @param[in] overallCurrentState SetOverallCurrentState Position, Latch and Speed. |
| * |
| * @return CHIP_NO_ERROR if set was successful. |
| * CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE if feature is not supported. |
| * CHIP_ERROR_INVALID_ARGUMENT if argument are not valid |
| */ |
| CHIP_ERROR SetOverallCurrentState(const DataModel::Nullable<GenericOverallCurrentState> & overallCurrentState); |
| |
| /** |
| * @brief Set OverallTargetState. |
| * |
| * @param[in] overallTarget OverallTargetState Position, Latch and Speed. |
| * |
| * @return CHIP_NO_ERROR if set was successful. |
| * CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE if feature is not supported. |
| * CHIP_ERROR_INVALID_ARGUMENT if argument are not valid |
| */ |
| CHIP_ERROR SetOverallTargetState(const DataModel::Nullable<GenericOverallTargetState> & overallTarget); |
| |
| /** |
| * @brief Sets the main state of the cluster. |
| * This method also generates the EngageStateChanged event based on MainState transition. |
| * This method also updates the CountdownTime attribute based on MainState |
| * |
| * @param[in] mainState - The new main state to be set. |
| * |
| * @return CHIP_NO_ERROR if the main state is set successfully. |
| * CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE if new MainState is not supported. |
| * CHIP_ERROR_INCORRECT_STATE if the transition to new MainState is not supported. |
| * CHIP_ERROR_INVALID_ARGUMENT if argument are not valid |
| */ |
| CHIP_ERROR SetMainState(MainStateEnum mainState); |
| |
| /** |
| * @brief Sets the latch control modes for the closure control cluster. |
| * This method updates the latch control modes using the provided bit flags. |
| * |
| * @param[in] latchControlModes Reference to a BitFlags object representing the desired latch control modes. |
| * |
| * @return CHIP_ERROR Returns CHIP_NO_ERROR on success, or an appropriate error code on failure. |
| */ |
| CHIP_ERROR SetLatchControlModes(const BitFlags<LatchControlModesBitmap> & latchControlModes); |
| |
| /** |
| * @brief Triggers an update to report a new countdown time from application. |
| * This method should be called whenever the application needs to update the countdown time. |
| * |
| * @param[in] countdownTime - Updated countdown time to be reported. |
| * |
| * @return CHIP_NO_ERROR if the countdown time is set successfully. |
| * Returns an appropriate error code if the countdown time update fails |
| */ |
| inline CHIP_ERROR SetCountdownTimeFromDelegate(const DataModel::Nullable<ElapsedS> & countdownTime) |
| { |
| return SetCountdownTime(countdownTime, true); |
| } |
| |
| /** |
| * @brief Adds error to current error list. |
| * |
| * @param[in] error The error to be added to the current error list. |
| * |
| * @return CHIP_NO_ERROR if the error was added successfully. |
| * CHIP_ERROR_INVALID_ARGUMENT if argument are not valid |
| */ |
| CHIP_ERROR AddErrorToCurrentErrorList(ClosureErrorEnum error); |
| |
| /** |
| * @brief Clears the current error list. |
| * This method should be called whenever the current error list needs to be reset. |
| */ |
| void ClearCurrentErrorList(); |
| |
| /** |
| * @brief Calls delegate HandleStopCommand function after validating MainState, parameters and conformance. |
| * |
| * @return Success if the Stop command not supported from present Mainstate. |
| * UnsupportedCommand if Instantaneous feature is supported. |
| * Success on succesful handling or Error Otherwise |
| */ |
| Protocols::InteractionModel::Status HandleStop(); |
| |
| /** |
| * @brief Calls delegate HandleMoveToCommand function after validating the parameters and conformance. |
| * |
| * @param [in] position target position |
| * @param [in] latch Target latch |
| * @param [in] speed Target speed |
| * |
| * @return ConstraintError if the input values are out of range. |
| * InvalidInState if the MoveTo command not supported from present Mainstate. |
| * Success on succesful handling. |
| */ |
| Protocols::InteractionModel::Status HandleMoveTo(Optional<TargetPositionEnum> position, Optional<bool> latch, |
| Optional<Globals::ThreeLevelAutoEnum> speed); |
| |
| /** |
| * @brief Calls delegate HandleCalibrateCommand function after validating the parameters and conformance. |
| * |
| * @return ConstraintError if the input values are out of range. |
| * InvalidInState if the Calibrate command not supported from present Mainstate. |
| * Success on succesful handling. |
| */ |
| Protocols::InteractionModel::Status HandleCalibrate(); |
| |
| /** |
| * @brief Generates OperationalError event. |
| * This method should be called whenever when a reportable error condition is detected |
| * |
| * @param [in] errorState current error list |
| * |
| * @return CHIP_NO_ERROR if the event is generated successfully |
| * Returns an appropriate error code if event generation fails |
| */ |
| CHIP_ERROR GenerateOperationalErrorEvent(const DataModel::List<const ClosureErrorEnum> & errorState); |
| |
| /** |
| * @brief Generates MovementCompleted event. |
| * This method should be called whenever when the overall operation ends either successfully or otherwise. |
| * |
| * @return CHIP_NO_ERROR if the event is generated successfull |
| * CHIP_NO_ERROR if the Positioning feature is not supported. |
| * Returns an appropriate error code if event generation fails |
| */ |
| CHIP_ERROR GenerateMovementCompletedEvent(); |
| |
| /** |
| * @brief Generates EngageStateChanged event. |
| * This method should be called whenever when the MainStateEnum attribute changes state to and from disengaged |
| * |
| * @param[in] EngageValue will indicate if the actuator is Engaged or Disengaged |
| * |
| * @return CHIP_NO_ERROR if the event is generated successfull |
| * CHIP_NO_ERROR if the ManuallyOperable feature is not supported. |
| * Returns an appropriate error code if event generation fails |
| */ |
| CHIP_ERROR GenerateEngageStateChangedEvent(const bool engageValue); |
| |
| /** |
| * @brief Generates EngageStateChanged event. |
| * This method should be called whenever when the SecureState field in the OverallCurrentState attribute changes. |
| * |
| * @param[in] secureValue will indicate whether a closure is securing a space against possible unauthorized entry. |
| * |
| * @return CHIP_NO_ERROR if the event is generated successfull |
| * CHIP_NO_ERROR if the feature conformance is not supported |
| * Returns an appropriate error code if event generation fails. |
| */ |
| CHIP_ERROR GenerateSecureStateChangedEvent(const bool secureValue); |
| |
| private: |
| ClosureControlClusterDelegate & mDelegate; |
| TimerDelegate & mTimerDelegate; |
| ClusterConformance mConformance; |
| ClusterState mState; |
| |
| /** |
| * @brief Function validates if the requested mainState is supported by the closure. |
| * Function validates agaisnt the FeatureMap conformance to validate support. |
| * |
| * - Stopped, Moving, WaitingForMotion, Error and SetupRequired always return true since they are mandatory. |
| * - Calibrating returns true if the Calibration feature is supported, false otherwise. |
| * - Protected returns true if the Proitection feature is supported, false otherwise. |
| * - Disengaged returns true if the ManuallyOperable feature is supported, false otherwise. |
| * - Returns true if the requested MainState is not a known state. |
| * |
| * @param mainState requested MainState to validate |
| * |
| * @return true, if the requested MainState is supported |
| * false, otherwise |
| */ |
| bool IsSupportedMainState(MainStateEnum mainState) const; |
| |
| /** |
| * @brief Function validates if the requested overallCurrentState positioning is supported by the closure. |
| * Function validates against the FeatureMap conformance to validate support. |
| * |
| * @param positioning requested Positioning to validate |
| * |
| * @return true if the requested Positioning is supported |
| * false, otherwise |
| */ |
| bool IsSupportedOverallCurrentStatePositioning(CurrentPositionEnum positioning) const; |
| |
| /** |
| * @brief Function validates if the requested OverallTargetState positioning is supported by the closure. |
| * Function validates agaisnt the FeatureMap conformance to validate support. |
| * |
| * @param positioning requested Positioning to validate |
| * |
| * @return true if the requested Positioning is supported |
| * false, otherwise |
| */ |
| bool IsSupportedOverallTargetStatePositioning(TargetPositionEnum positioning) const; |
| |
| /** |
| * @brief Updates the countdown time based on the Quiet reporting conditions of the attribute. |
| * |
| * @param fromDelegate true if the countdown time is being configured by the delegate, false otherwise |
| */ |
| CHIP_ERROR SetCountdownTime(const DataModel::Nullable<ElapsedS> & countdownTime, bool fromDelegate); |
| |
| /** |
| * @brief Updates the countdown time from cluster logic. |
| * This method should be invoked whenever the cluster logic needs to update the countdown time. |
| * This includes: |
| * - When the tracked operation changes due to an update in the MainState attribute |
| * |
| * @param[in] countdownTime - Updated countdown time to be reported. |
| * |
| * @return CHIP_NO_ERROR if the countdown time is set successfully. |
| * Returns an appropriate error code if the countdown time update fails |
| */ |
| inline CHIP_ERROR SetCountdownTimeFromCluster(const DataModel::Nullable<ElapsedS> & countdownTime) |
| { |
| return SetCountdownTime(countdownTime, false); |
| } |
| |
| /** |
| * @brief Reads the CurrentErrorList attribute. |
| * This method is used to read the CurrentErrorList attribute and encode it using the provided encoder. |
| * |
| * @param[in] encoder The encoder to use for encoding the CurrentErrorList attribute. |
| * |
| * @return CHIP_NO_ERROR if the read was successful. |
| */ |
| CHIP_ERROR ReadCurrentErrorListAttribute(const AttributeValueEncoder::ListEncodeHelper & encoder); |
| |
| EndpointId GetEndpointId() { return mPath.mEndpointId; } |
| }; |
| |
| } // namespace ClosureControl |
| } // namespace Clusters |
| } // namespace app |
| } // namespace chip |