blob: 85394b9585405ecf40eda46faab84b7b3d4b7af8 [file] [log] [blame]
/**
*
* Copyright (c) 2025 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 <app/clusters/closure-control-server/ClosureControlClusterLogic.h>
#include <clusters/ClosureControl/Metadata.h>
#include <platform/CHIPDeviceLayer.h>
#include <platform/LockTracker.h>
#include <protocols/interaction_model/StatusCode.h>
namespace chip {
namespace app {
namespace Clusters {
namespace ClosureControl {
using namespace Protocols::InteractionModel;
/*
ClusterLogic Implementation
*/
CHIP_ERROR ClusterLogic::Init(const ClusterConformance & conformance, const ClusterInitParameters & initParams)
{
VerifyOrReturnError(!mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(conformance.Valid(), CHIP_ERROR_INVALID_DEVICE_DESCRIPTOR);
mConformance = conformance;
mIsInitialized = true;
ReturnErrorOnFailure(SetMainState(initParams.mMainState));
ReturnErrorOnFailure(SetOverallCurrentState(initParams.mOverallCurrentState));
return CHIP_NO_ERROR;
}
bool ClusterLogic::IsSupportedMainState(MainStateEnum mainState) const
{
bool isSupported = false;
switch (mainState)
{
case MainStateEnum::kStopped:
case MainStateEnum::kMoving:
case MainStateEnum::kWaitingForMotion:
case MainStateEnum::kError:
case MainStateEnum::kSetupRequired:
// Mandatory states are always supported
isSupported = true;
break;
case MainStateEnum::kCalibrating:
isSupported = mConformance.HasFeature(Feature::kCalibration);
break;
case MainStateEnum::kProtected:
isSupported = mConformance.HasFeature(Feature::kProtection);
break;
case MainStateEnum::kDisengaged:
// Disengaged requires the ManuallyOperable feature
isSupported = mConformance.HasFeature(Feature::kManuallyOperable);
break;
default:
isSupported = false;
break;
}
return isSupported;
}
bool ClusterLogic::IsSupportedOverallCurrentStatePositioning(CurrentPositionEnum positioning) const
{
bool isSupported = false;
switch (positioning)
{
case CurrentPositionEnum::kFullyClosed:
case CurrentPositionEnum::kFullyOpened:
case CurrentPositionEnum::kPartiallyOpened:
case CurrentPositionEnum::kOpenedAtSignature:
// Mandatory states are always supported
isSupported = true;
break;
case CurrentPositionEnum::kOpenedForPedestrian:
isSupported = mConformance.HasFeature(Feature::kPedestrian);
break;
case CurrentPositionEnum::kOpenedForVentilation:
isSupported = mConformance.HasFeature(Feature::kVentilation);
break;
default:
isSupported = false;
break;
}
return isSupported;
}
bool ClusterLogic::IsSupportedOverallTargetStatePositioning(TargetPositionEnum positioning) const
{
bool isSupported = false;
switch (positioning)
{
case TargetPositionEnum::kMoveToFullyClosed:
case TargetPositionEnum::kMoveToFullyOpen:
case TargetPositionEnum::kMoveToSignaturePosition:
// Mandatory states are always supported
isSupported = true;
break;
case TargetPositionEnum::kMoveToPedestrianPosition:
isSupported = mConformance.HasFeature(Feature::kPedestrian);
break;
case TargetPositionEnum::kMoveToVentilationPosition:
isSupported = mConformance.HasFeature(Feature::kVentilation);
break;
default:
isSupported = false;
break;
}
return isSupported;
}
CHIP_ERROR ClusterLogic::SetCountdownTime(const DataModel::Nullable<ElapsedS> & countdownTime, bool fromDelegate)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mConformance.HasFeature(Feature::kPositioning) && !mConformance.HasFeature(Feature::kInstantaneous),
CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
auto now = System::SystemClock().GetMonotonicTimestamp();
bool markDirty = false;
// When fromDelegate=true (delegate updates), we rely on the QuieterReportingAttribute policies
// to determine if reporting is needed (increment, change to/from zero, null changes).
// When fromDelegate=false (MainState change), we force reporting since the tracked operation changed.
auto predicate = [fromDelegate](const decltype(mState.mCountdownTime)::SufficientChangePredicateCandidate &) -> bool {
// Force reporting when the tracked operation changes due to MainState change
return !fromDelegate;
};
markDirty = (mState.mCountdownTime.SetValue(countdownTime, now, predicate) == AttributeDirtyState::kMustReport);
if (markDirty)
{
mMatterContext.MarkDirty(Attributes::CountdownTime::Id);
}
return CHIP_NO_ERROR;
}
CHIP_ERROR ClusterLogic::SetMainState(MainStateEnum mainState)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(IsSupportedMainState(mainState), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
VerifyOrReturnError(mainState != mState.mMainState, CHIP_NO_ERROR);
// EngageStateChanged event SHALL be generated when the MainStateEnum attribute changes state to and from disengaged state
if (mState.mMainState == MainStateEnum::kDisengaged)
{
TEMPORARY_RETURN_IGNORED GenerateEngageStateChangedEvent(true);
}
if (mainState == MainStateEnum::kDisengaged)
{
TEMPORARY_RETURN_IGNORED GenerateEngageStateChangedEvent(false);
}
mState.mMainState = mainState;
mMatterContext.MarkDirty(Attributes::MainState::Id);
if (!mConformance.HasFeature(Feature::kInstantaneous))
{
if (mainState == MainStateEnum::kCalibrating)
{
TEMPORARY_RETURN_IGNORED SetCountdownTimeFromCluster(mDelegate.GetCalibrationCountdownTime());
}
else if (mainState == MainStateEnum::kMoving)
{
TEMPORARY_RETURN_IGNORED SetCountdownTimeFromCluster(mDelegate.GetMovingCountdownTime());
}
else if (mainState == MainStateEnum::kWaitingForMotion)
{
TEMPORARY_RETURN_IGNORED SetCountdownTimeFromCluster(mDelegate.GetWaitingForMotionCountdownTime());
}
else
{
// Reset the countdown time to 0 when the main state is not in motion or calibration.
TEMPORARY_RETURN_IGNORED SetCountdownTimeFromCluster(DataModel::Nullable<ElapsedS>(0));
}
}
return CHIP_NO_ERROR;
}
CHIP_ERROR ClusterLogic::SetOverallCurrentState(const DataModel::Nullable<GenericOverallCurrentState> & overallCurrentState)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mState.mOverallCurrentState != overallCurrentState, CHIP_NO_ERROR);
if (!overallCurrentState.IsNull())
{
const GenericOverallCurrentState & incomingOverallCurrentState = overallCurrentState.Value();
// Validate the incoming Positioning value and FeatureMap conformance.
if (incomingOverallCurrentState.position.HasValue())
{
// If the positioning member is present in the incoming OverallCurrentState, we need to check if the Positioning
// feature is supported by the closure. If the Positioning feature is not supported, return an error.
VerifyOrReturnError(mConformance.HasFeature(Feature::kPositioning), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
if (!incomingOverallCurrentState.position.Value().IsNull())
{
VerifyOrReturnError(EnsureKnownEnumValue(incomingOverallCurrentState.position.Value().Value()) !=
CurrentPositionEnum::kUnknownEnumValue,
CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(IsSupportedOverallCurrentStatePositioning(incomingOverallCurrentState.position.Value().Value()),
CHIP_ERROR_INVALID_ARGUMENT);
}
}
// Validate the incoming Latch FeatureMap conformance.
if (incomingOverallCurrentState.latch.HasValue())
{
// If the latch member is present in the incoming OverallCurrentState, we need to check if the MotionLatching
// feature is supported by the closure. If the MotionLatching feature is not supported, return an error.
VerifyOrReturnError(mConformance.HasFeature(Feature::kMotionLatching), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
}
// Validate the incoming Speed value and FeatureMap conformance.
if (incomingOverallCurrentState.speed.HasValue())
{
// If the speed member is present in the incoming OverallCurrentState, we need to check if the Speed feature is
// supported by the closure. If the Speed feature is not supported, return an error.
VerifyOrReturnError(mConformance.HasFeature(Feature::kSpeed), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
VerifyOrReturnError(EnsureKnownEnumValue(incomingOverallCurrentState.speed.Value()) !=
Globals::ThreeLevelAutoEnum::kUnknownEnumValue,
CHIP_ERROR_INVALID_ARGUMENT);
}
// SecureState can only be true if the closure meets the required conditions for a secure state, preventing unauthorized or
// undetectable access.
if (!incomingOverallCurrentState.secureState.IsNull() && incomingOverallCurrentState.secureState.Value())
{
// secure state requires the closure to meet all of the following conditions based on feature support:
// If the Positioning feature is supported, then the Position field of OverallCurrentState is FullyClosed.
// If the MotionLatching feature is supported, then the Latch field of OverallCurrentState is True.
if (mConformance.HasFeature(Feature::kPositioning))
{
VerifyOrReturnError(incomingOverallCurrentState.position.HasValue() &&
!incomingOverallCurrentState.position.Value().IsNull(),
CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(incomingOverallCurrentState.position.Value().Value() == CurrentPositionEnum::kFullyClosed,
CHIP_ERROR_INVALID_ARGUMENT);
}
if (mConformance.HasFeature(Feature::kMotionLatching))
{
VerifyOrReturnError(incomingOverallCurrentState.latch.HasValue() &&
!incomingOverallCurrentState.latch.Value().IsNull(),
CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(incomingOverallCurrentState.latch.Value().Value() == true, CHIP_ERROR_INVALID_ARGUMENT);
}
}
// SecureStateChanged event SHALL be generated when the SecureState field in the OverallCurrentState attribute changes
if (!incomingOverallCurrentState.secureState.IsNull())
{
if (mState.mOverallCurrentState.IsNull() || mState.mOverallCurrentState.Value().secureState.IsNull())
{
// As secureState field is not set in present current state and incoming current state has value, we generate the
// event
TEMPORARY_RETURN_IGNORED GenerateSecureStateChangedEvent(incomingOverallCurrentState.secureState.Value());
}
else
{
// If the secureState field is set in both present and incoming current state, we generate the event only if the
// value has changed.
if (mState.mOverallCurrentState.Value().secureState.Value() != incomingOverallCurrentState.secureState.Value())
{
TEMPORARY_RETURN_IGNORED GenerateSecureStateChangedEvent(incomingOverallCurrentState.secureState.Value());
}
}
}
}
mState.mOverallCurrentState = overallCurrentState;
mMatterContext.MarkDirty(Attributes::OverallCurrentState::Id);
return CHIP_NO_ERROR;
}
CHIP_ERROR ClusterLogic::SetOverallTargetState(const DataModel::Nullable<GenericOverallTargetState> & overallTarget)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mState.mOverallTargetState != overallTarget, CHIP_NO_ERROR);
if (!overallTarget.IsNull())
{
const GenericOverallTargetState & incomingOverallTargetState = overallTarget.Value();
// Validate the incoming Position value and FeatureMap conformance.
if (incomingOverallTargetState.position.HasValue())
{
// If the position member is present in the incoming OverallTargetState, we need to check if the Position
// feature is supported by the closure. If the Position feature is not supported, return an error.
VerifyOrReturnError(mConformance.HasFeature(Feature::kPositioning), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
if (!incomingOverallTargetState.position.Value().IsNull())
{
VerifyOrReturnError(EnsureKnownEnumValue(incomingOverallTargetState.position.Value().Value()) !=
TargetPositionEnum::kUnknownEnumValue,
CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(IsSupportedOverallTargetStatePositioning(incomingOverallTargetState.position.Value().Value()),
CHIP_ERROR_INVALID_ARGUMENT);
}
}
// Validate the incoming Latch FeatureMap conformance.
if (incomingOverallTargetState.latch.HasValue())
{
// If the latch member is present in the incoming OverallTargetState, we need to check if the MotionLatching
// feature is supported by the closure. If the MotionLatching feature is not supported, return an error.
VerifyOrReturnError(mConformance.HasFeature(Feature::kMotionLatching), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
}
// Validate the incoming Speed value and FeatureMap conformance.
if (incomingOverallTargetState.speed.HasValue())
{
// If the speed member is present in the incoming OverallTargetState, we need to check if the Speed feature is
// supported by the closure. If the Speed feature is not supported, return an error.
VerifyOrReturnError(mConformance.HasFeature(Feature::kSpeed), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
VerifyOrReturnError(EnsureKnownEnumValue(incomingOverallTargetState.speed.Value()) !=
Globals::ThreeLevelAutoEnum::kUnknownEnumValue,
CHIP_ERROR_INVALID_ARGUMENT);
}
}
mState.mOverallTargetState = overallTarget;
mMatterContext.MarkDirty(Attributes::OverallTargetState::Id);
return CHIP_NO_ERROR;
}
CHIP_ERROR ClusterLogic::SetLatchControlModes(const BitFlags<LatchControlModesBitmap> & latchControlModes)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mConformance.HasFeature(Feature::kMotionLatching), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
mState.mLatchControlModes = latchControlModes;
mMatterContext.MarkDirty(Attributes::LatchControlModes::Id);
return CHIP_NO_ERROR;
}
CHIP_ERROR ClusterLogic::AddErrorToCurrentErrorList(ClosureErrorEnum error)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(EnsureKnownEnumValue(error) != ClosureErrorEnum::kUnknownEnumValue, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(mState.mCurrentErrorCount < kCurrentErrorListMaxSize, CHIP_ERROR_PROVIDER_LIST_EXHAUSTED,
ChipLogError(AppServer, "Error list is full"));
// Check for duplicates
for (size_t i = 0; i < mState.mCurrentErrorCount; ++i)
{
VerifyOrReturnError(mState.mCurrentErrorList[i] != error, CHIP_ERROR_DUPLICATE_MESSAGE_RECEIVED,
ChipLogError(AppServer, "Error already exists in the list"));
}
mState.mCurrentErrorList[mState.mCurrentErrorCount++] = error;
DataModel::List<const ClosureErrorEnum> currentErrorList(mState.mCurrentErrorList, mState.mCurrentErrorCount);
mMatterContext.MarkDirty(Attributes::CurrentErrorList::Id);
ReturnLogErrorOnFailure(GenerateOperationalErrorEvent(currentErrorList));
return CHIP_NO_ERROR;
}
void ClusterLogic::ClearCurrentErrorList()
{
assertChipStackLockedByCurrentThread();
VerifyOrDieWithMsg(mIsInitialized, AppServer, "ClearCurrentErrorList called before Initialization of closure");
// Clearing the error list array by setting all elements to kUnknownEnumValue
for (size_t i = 0; i < mState.mCurrentErrorCount; ++i)
{
mState.mCurrentErrorList[i] = ClosureErrorEnum::kUnknownEnumValue;
}
// Reset the current error count to 0
mState.mCurrentErrorCount = 0;
mMatterContext.MarkDirty(Attributes::CurrentErrorList::Id);
}
// TODO: Move the CountdownTime handling to Delegate
CHIP_ERROR ClusterLogic::GetCountdownTime(DataModel::Nullable<ElapsedS> & countdownTime)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mConformance.HasFeature(Feature::kPositioning) && !mConformance.HasFeature(Feature::kInstantaneous),
CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
countdownTime = mState.mCountdownTime.value();
return CHIP_NO_ERROR;
}
CHIP_ERROR ClusterLogic::GetMainState(MainStateEnum & mainState)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
mainState = mState.mMainState;
return CHIP_NO_ERROR;
}
CHIP_ERROR ClusterLogic::GetOverallCurrentState(DataModel::Nullable<GenericOverallCurrentState> & overallCurrentState)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
overallCurrentState = mState.mOverallCurrentState;
return CHIP_NO_ERROR;
}
CHIP_ERROR ClusterLogic::GetOverallTargetState(DataModel::Nullable<GenericOverallTargetState> & overallTarget)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
overallTarget = mState.mOverallTargetState;
return CHIP_NO_ERROR;
}
CHIP_ERROR ClusterLogic::GetCurrentErrorList(Span<ClosureErrorEnum> & outputSpan)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(outputSpan.size() == kCurrentErrorListMaxSize, CHIP_ERROR_BUFFER_TOO_SMALL,
ChipLogError(AppServer, "Output buffer size is not equal to kCurrentErrorListMaxSize"));
for (size_t i = 0; i < mState.mCurrentErrorCount; ++i)
{
outputSpan[i] = mState.mCurrentErrorList[i];
}
outputSpan.reduce_size(mState.mCurrentErrorCount);
return CHIP_NO_ERROR;
}
CHIP_ERROR ClusterLogic::ReadCurrentErrorListAttribute(const AttributeValueEncoder::ListEncodeHelper & encoder)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
for (size_t i = 0; i < mState.mCurrentErrorCount; i++)
{
ClosureErrorEnum error = mState.mCurrentErrorList[i];
// Encode the error
ReturnErrorOnFailure(encoder.Encode(error));
}
return CHIP_NO_ERROR;
}
CHIP_ERROR ClusterLogic::GetLatchControlModes(BitFlags<LatchControlModesBitmap> & latchControlModes)
{
VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mConformance.HasFeature(Feature::kMotionLatching), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
latchControlModes = mState.mLatchControlModes;
return CHIP_NO_ERROR;
}
CHIP_ERROR ClusterLogic::GetFeatureMap(BitFlags<Feature> & featureMap)
{
VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
featureMap = mConformance.FeatureMap();
return CHIP_NO_ERROR;
}
CHIP_ERROR ClusterLogic::GetClusterRevision(Attributes::ClusterRevision::TypeInfo::Type & clusterRevision)
{
VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
clusterRevision = ClosureControl::kRevision;
return CHIP_NO_ERROR;
}
Protocols::InteractionModel::Status ClusterLogic::HandleStop()
{
VerifyOrDieWithMsg(mIsInitialized, AppServer, "Stop Command called before Initialization of closure");
// Stop command can only be supported if closure doesnt support instantaneous features
VerifyOrReturnError(!mConformance.HasFeature(Feature::kInstantaneous), Status::UnsupportedCommand);
MainStateEnum state;
VerifyOrReturnError(GetMainState(state) == CHIP_NO_ERROR, Status::Failure);
// Stop action is supported only if the closure is in one of the following states Moving, WaitingForMotion or Calibrating.
// A status code of SUCCESS SHALL always be returned, regardless if it is in above states or not.
if ((state == MainStateEnum::kCalibrating) || (state == MainStateEnum::kMoving) || (state == MainStateEnum::kWaitingForMotion))
{
// Set the MainState to 'Stopped' only if the delegate call to HandleStopCommand is successful.
Status status = mDelegate.HandleStopCommand();
VerifyOrReturnValue(status == Status::Success, status);
VerifyOrReturnError(SetMainState(MainStateEnum::kStopped) == CHIP_NO_ERROR, Status::Failure,
ChipLogError(AppServer, "Stop Command: Failed to set MainState to Stopped"));
}
return Status::Success;
}
Protocols::InteractionModel::Status ClusterLogic::HandleMoveTo(Optional<TargetPositionEnum> position, Optional<bool> latch,
Optional<Globals::ThreeLevelAutoEnum> speed)
{
VerifyOrDieWithMsg(mIsInitialized, AppServer, "MoveTo Command called before Initialization of closure");
VerifyOrReturnError(position.HasValue() || latch.HasValue() || speed.HasValue(), Status::InvalidCommand);
DataModel::Nullable<GenericOverallCurrentState> overallCurrentState;
DataModel::Nullable<GenericOverallTargetState> overallTargetState;
VerifyOrReturnError(GetOverallTargetState(overallTargetState) == CHIP_NO_ERROR, Status::Failure);
VerifyOrReturnError(GetOverallCurrentState(overallCurrentState) == CHIP_NO_ERROR, Status::Failure);
VerifyOrReturnError(!overallCurrentState.IsNull(), Status::InvalidInState,
ChipLogError(AppServer, "OverallCurrentState is null on endpoint : %d", mMatterContext.GetEndpointId()));
if (overallTargetState.IsNull())
{
// If overallTargetState is null, we need to initialize to default value.
// This is to ensure that we can set the position, latch, and speed values in the overallTargetState.
overallTargetState.SetNonNull(GenericOverallTargetState{});
}
if (position.HasValue() && mConformance.HasFeature(Feature::kPositioning))
{
VerifyOrReturnError(position.Value() != TargetPositionEnum::kUnknownEnumValue, Status::ConstraintError);
overallTargetState.Value().position.SetValue(DataModel::MakeNullable(position.Value()));
}
if (latch.HasValue() && mConformance.HasFeature(Feature::kMotionLatching))
{
// If latch value is true and the Remote Latching feature is not supported, or
// if latch value is false and the Remote Unlatching feature is not supported, return InvalidInState.
if ((latch.Value() && !mState.mLatchControlModes.Has(LatchControlModesBitmap::kRemoteLatching)) ||
(!latch.Value() && !mState.mLatchControlModes.Has(LatchControlModesBitmap::kRemoteUnlatching)))
{
return Status::InvalidInState;
}
overallTargetState.Value().latch.SetValue(DataModel::MakeNullable(latch.Value()));
}
if (speed.HasValue() && mConformance.HasFeature(Feature::kSpeed))
{
VerifyOrReturnError(speed.Value() != Globals::ThreeLevelAutoEnum::kUnknownEnumValue, Status::ConstraintError);
overallTargetState.Value().speed.SetValue(speed.Value());
}
MainStateEnum state;
VerifyOrReturnError(GetMainState(state) == CHIP_NO_ERROR, Status::Failure);
// If the MoveTo command is received in any state other than 'Moving', 'WaitingForMotion', or 'Stopped', an error code
// INVALID_IN_STATE shall be returned.
VerifyOrReturnError(state == MainStateEnum::kMoving || state == MainStateEnum::kWaitingForMotion ||
state == MainStateEnum::kStopped,
Status::InvalidInState);
if (mConformance.HasFeature(Feature::kMotionLatching))
{
// If this command requests a position change while the Latch field of the OverallCurrentState is True (Latched), and the
// Latch field of this command is not set to False (Unlatched), a status code of INVALID_IN_STATE SHALL be returned.
if (position.HasValue() && overallCurrentState.Value().latch.HasValue() &&
!overallCurrentState.Value().latch.Value().IsNull() && overallCurrentState.Value().latch.Value().Value())
{
VerifyOrReturnError(latch.HasValue() && !latch.Value(), Status::InvalidInState,
ChipLogError(AppServer,
"Latch is True in OverallCurrentState, but MoveTo command does not set latch to False "
"when position change is requested on endpoint : %d",
mMatterContext.GetEndpointId()));
}
}
// Set MainState and OverallTargetState only if the delegate call to HandleMoveToCommand is successful
Status status = mDelegate.HandleMoveToCommand(position, latch, speed);
VerifyOrReturnValue(status == Status::Success, status);
if (mDelegate.IsReadyToMove())
{
VerifyOrReturnError(SetMainState(MainStateEnum::kMoving) == CHIP_NO_ERROR, Status::Failure,
ChipLogError(AppServer, "MoveTo Command: Failed to set MainState to Moving"));
}
else
{
VerifyOrReturnError(SetMainState(MainStateEnum::kWaitingForMotion) == CHIP_NO_ERROR, Status::Failure,
ChipLogError(AppServer, "MoveTo Command: Failed to set MainState to kWaitingForMotion"));
}
VerifyOrReturnError(SetOverallTargetState(overallTargetState) == CHIP_NO_ERROR, Status::Failure);
return Status::Success;
}
Protocols::InteractionModel::Status ClusterLogic::HandleCalibrate()
{
VerifyOrDieWithMsg(mIsInitialized, AppServer, "Calibrate Command called before Initialization of closure");
VerifyOrReturnError(mConformance.HasFeature(Feature::kCalibration), Status::UnsupportedCommand);
MainStateEnum state;
VerifyOrReturnError(GetMainState(state) == CHIP_NO_ERROR, Status::Failure);
// If Calibrate command is received when already in the Calibrating state,
// the server SHALL respond with a status code of SUCCESS.
VerifyOrReturnValue(state != MainStateEnum::kCalibrating, Status::Success);
// If the Calibrate command is invoked in any state other than Stopped or SetupRequired,
// the server SHALL respond with INVALID_IN_STATE and there SHALL be no other effect.
// This check excludes the 'Calibrating' MainState as it is already validated above
VerifyOrReturnError(state == MainStateEnum::kStopped || state == MainStateEnum::kSetupRequired, Status::InvalidInState);
// Set the MainState to 'Calibrating' only if the delegate call to HandleCalibrateCommand is successful
Status status = mDelegate.HandleCalibrateCommand();
VerifyOrReturnValue(status == Status::Success, status);
VerifyOrReturnError(SetMainState(MainStateEnum::kCalibrating) == CHIP_NO_ERROR, Status::Failure,
ChipLogError(AppServer, "Calibrate Command: Failed to set MainState to Calibrating"));
return Status::Success;
}
CHIP_ERROR ClusterLogic::GenerateOperationalErrorEvent(const DataModel::List<const ClosureErrorEnum> & errorState)
{
ReturnErrorOnFailure(SetMainState(MainStateEnum::kError));
Events::OperationalError::Type event{ .errorState = errorState };
ReturnErrorOnFailure(mMatterContext.GenerateEvent(event));
return CHIP_NO_ERROR;
}
CHIP_ERROR ClusterLogic::GenerateMovementCompletedEvent()
{
VerifyOrReturnError(!mConformance.HasFeature(Feature::kInstantaneous), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
Events::MovementCompleted::Type event{};
ReturnErrorOnFailure(mMatterContext.GenerateEvent(event));
return CHIP_NO_ERROR;
}
CHIP_ERROR ClusterLogic::GenerateEngageStateChangedEvent(const bool engageValue)
{
VerifyOrReturnError(mConformance.HasFeature(Feature::kManuallyOperable), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
Events::EngageStateChanged::Type event{ .engageValue = engageValue };
ReturnErrorOnFailure(mMatterContext.GenerateEvent(event));
return CHIP_NO_ERROR;
}
CHIP_ERROR ClusterLogic::GenerateSecureStateChangedEvent(const bool secureValue)
{
Events::SecureStateChanged::Type event{ .secureValue = secureValue };
ReturnErrorOnFailure(mMatterContext.GenerateEvent(event));
return CHIP_NO_ERROR;
}
} // namespace ClosureControl
} // namespace Clusters
} // namespace app
} // namespace chip