blob: 9e304e53a462d0c69209a8351983f0926dd9b204 [file] [log] [blame]
/*
*
* 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 <ClosureControlEndpoint.h>
#include <ClosureManager.h>
#include <app-common/zap-generated/cluster-enums.h>
#include <app-common/zap-generated/cluster-objects.h>
#include <protocols/interaction_model/StatusCode.h>
using namespace chip;
using namespace chip::app::Clusters::ClosureControl;
using namespace chip::app::DataModel;
using Protocols::InteractionModel::Status;
namespace {
constexpr ElapsedS kDefaultCountdownTime = 30;
enum class ClosureControlTestEventTrigger : uint64_t
{
// MainState is Error(3) Test Event | Simulate that the device is in error state, add at least one element to the
// CurrentErrorList attribute
kMainStateIsError = 0x0104000000000000,
// MainState is Protected(5) Test Event | Simulate that the device is in protected state
kMainStateIsProtected = 0x0104000000000001,
// MainState is Disengaged(6) Test Event | Simulate that the device is in disengaged state
kMainStateIsDisengaged = 0x0104000000000002,
// MainState is SetupRequired(7) Test Event | Simulate that the device is in SetupRequired state
kMainStateIsSetupRequired = 0x0104000000000003,
// MainState Test clear Event | Returns the device to pre-test status for that test event.
kClearEvent = 0x0104000000000004,
};
} // namespace
Status ClosureControlDelegate::HandleCalibrateCommand()
{
return ClosureManager::GetInstance().OnCalibrateCommand();
}
Status ClosureControlDelegate::HandleMoveToCommand(const Optional<TargetPositionEnum> & position, const Optional<bool> & latch,
const Optional<Globals::ThreeLevelAutoEnum> & speed)
{
return ClosureManager::GetInstance().OnMoveToCommand(position, latch, speed);
}
Status ClosureControlDelegate::HandleStopCommand()
{
return ClosureManager::GetInstance().OnStopCommand();
}
bool ClosureControlDelegate::IsReadyToMove()
{
// This function should return true if the closure is ready to move.
// For now, we will return true.
return true;
}
ElapsedS ClosureControlDelegate::GetCalibrationCountdownTime()
{
// This function should return the calibration countdown time.
// For now, we will return kDefaultCountdownTime.
return kDefaultCountdownTime;
}
ElapsedS ClosureControlDelegate::GetMovingCountdownTime()
{
// This function should return the moving countdown time.
// For now, we will return kDefaultCountdownTime.
return kDefaultCountdownTime;
}
ElapsedS ClosureControlDelegate::GetWaitingForMotionCountdownTime()
{
// This function should return the waiting for motion countdown time.
// For now, we will return kDefaultCountdownTime.
return kDefaultCountdownTime;
}
CHIP_ERROR ClosureControlDelegate::HandleEventTrigger(uint64_t eventTrigger)
{
eventTrigger = clearEndpointInEventTrigger(eventTrigger);
ClosureControlTestEventTrigger trigger = static_cast<ClosureControlTestEventTrigger>(eventTrigger);
ClusterLogic * logic = GetLogic();
switch (trigger)
{
case ClosureControlTestEventTrigger::kMainStateIsSetupRequired:
ReturnErrorOnFailure(logic->SetMainState(MainStateEnum::kSetupRequired));
break;
case ClosureControlTestEventTrigger::kMainStateIsProtected:
ReturnErrorOnFailure(logic->SetMainState(MainStateEnum::kProtected));
break;
case ClosureControlTestEventTrigger::kMainStateIsError:
ReturnErrorOnFailure(logic->SetMainState(MainStateEnum::kError));
ReturnErrorOnFailure(logic->AddErrorToCurrentErrorList(ClosureErrorEnum::kBlockedBySensor));
break;
case ClosureControlTestEventTrigger::kMainStateIsDisengaged:
ReturnErrorOnFailure(logic->SetMainState(MainStateEnum::kDisengaged));
break;
case ClosureControlTestEventTrigger::kClearEvent:
ReturnErrorOnFailure(logic->SetMainState(MainStateEnum::kStopped));
logic->ClearCurrentErrorList();
break;
default:
return CHIP_ERROR_INVALID_ARGUMENT;
}
return CHIP_NO_ERROR;
}
CHIP_ERROR ClosureControlEndpoint::Init()
{
ClusterConformance conformance;
conformance.FeatureMap()
.Set(Feature::kPositioning)
.Set(Feature::kMotionLatching)
.Set(Feature::kSpeed)
.Set(Feature::kVentilation)
.Set(Feature::kPedestrian)
.Set(Feature::kCalibration)
.Set(Feature::kProtection)
.Set(Feature::kManuallyOperable);
conformance.OptionalAttributes().Set(OptionalAttributeEnum::kCountdownTime);
ClusterInitParameters initParams;
ReturnErrorOnFailure(mLogic.Init(conformance, initParams));
ReturnErrorOnFailure(mInterface.Init());
return CHIP_NO_ERROR;
}
void ClosureControlEndpoint::OnStopCalibrateActionComplete()
{
VerifyOrReturn(mLogic.SetMainState(MainStateEnum::kStopped) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to set main state in OnStopCalibrateActionComplete"));
// After stopping calibration, the overall and target state is explicitly nulled to indicate an unknown state,
VerifyOrReturn(mLogic.SetOverallCurrentState(DataModel::NullNullable) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to set overall state to null in OnStopCalibrateActionComplete"));
VerifyOrReturn(mLogic.SetOverallTargetState(DataModel::NullNullable) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to set overall target to null in OnStopCalibrateActionComplete"));
VerifyOrReturn(mLogic.SetCountdownTimeFromDelegate(0) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to set countdown time to 0 in OnStopCalibrateActionComplete"));
VerifyOrReturn(mLogic.GenerateMovementCompletedEvent() == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to generate movement completed event in OnStopCalibrateActionComplete"));
}
void ClosureControlEndpoint::OnStopMotionActionComplete()
{
MainStateEnum presentMainState;
VerifyOrReturn(mLogic.GetMainState(presentMainState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get main state in OnStopMotionActionComplete"));
// If the current main state is WaitingForMotion, it means the device hasn't started moving yet,
// so we don't need to update the current state.
if (presentMainState != MainStateEnum::kWaitingForMotion)
{
// Set the OverallState position to PartiallyOpened as motion has been stopped
// and the closure is not fully closed or fully opened.
auto position = MakeOptional(DataModel::MakeNullable(CurrentPositionEnum::kPartiallyOpened));
DataModel::Nullable<GenericOverallCurrentState> overallCurrentState;
VerifyOrReturn(mLogic.GetOverallCurrentState(overallCurrentState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get overall state in OnStopMotionActionComplete"));
if (overallCurrentState.IsNull())
{
overallCurrentState.SetNonNull(GenericOverallCurrentState(position, NullOptional, NullOptional, NullOptional));
}
else
{
overallCurrentState.Value().position = position;
}
VerifyOrReturn(mLogic.SetOverallCurrentState(overallCurrentState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to set overall state in OnStopMotionActionComplete"));
}
VerifyOrReturn(mLogic.SetMainState(MainStateEnum::kStopped) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to set main state in OnStopMotionActionComplete"));
// Set the Position, latch in OverallTargetState to Null and speed to Auto as the motion has been stopped.
GenericOverallTargetState overallTargetState(MakeOptional(DataModel::NullNullable), MakeOptional(DataModel::NullNullable),
MakeOptional(Globals::ThreeLevelAutoEnum::kAuto));
VerifyOrReturn(mLogic.SetOverallTargetState(DataModel::MakeNullable(overallTargetState)) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to set overall target in OnStopMotionActionComplete"));
VerifyOrReturn(mLogic.SetCountdownTimeFromDelegate(0) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to set countdown time to 0 in OnStopMotionActionComplete"));
VerifyOrReturn(mLogic.GenerateMovementCompletedEvent() == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to generate movement completed event in OnStopMotionActionComplete"));
}
void ClosureControlEndpoint::OnCalibrateActionComplete()
{
DataModel::Nullable<GenericOverallCurrentState> overallCurrentState(GenericOverallCurrentState(
MakeOptional(DataModel::MakeNullable(CurrentPositionEnum::kFullyClosed)), MakeOptional(DataModel::MakeNullable(true)),
MakeOptional(Globals::ThreeLevelAutoEnum::kAuto), DataModel::MakeNullable(true)));
DataModel::Nullable<GenericOverallTargetState> overallTargetState = DataModel::NullNullable;
mLogic.SetMainState(MainStateEnum::kStopped);
mLogic.SetOverallCurrentState(overallCurrentState);
mLogic.SetOverallTargetState(overallTargetState);
mLogic.SetCountdownTimeFromDelegate(0);
mLogic.GenerateMovementCompletedEvent();
}
void ClosureControlEndpoint::OnMoveToActionComplete()
{
UpdateCurrentStateFromTargetState();
mLogic.SetMainState(MainStateEnum::kStopped);
mLogic.SetCountdownTimeFromDelegate(0);
mLogic.GenerateMovementCompletedEvent();
}
void ClosureControlEndpoint::UpdateCurrentStateFromTargetState()
{
DataModel::Nullable<GenericOverallCurrentState> overallCurrentState;
DataModel::Nullable<GenericOverallTargetState> overallTargetState;
VerifyOrReturn(mLogic.GetOverallCurrentState(overallCurrentState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get overall state from closure Endpoint"));
VerifyOrReturn(mLogic.GetOverallTargetState(overallTargetState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get overall target from closure Endpoint"));
VerifyOrReturn(!overallTargetState.IsNull(), ChipLogError(AppServer, "Current overall target is null, Move to action Failed"));
VerifyOrReturn(!overallCurrentState.IsNull(), ChipLogError(AppServer, "Current overall state is null, Move to action Failed"));
if (overallTargetState.Value().position.HasValue() && !overallTargetState.Value().position.Value().IsNull())
{
// Map the target position to the current positioning enum.
CurrentPositionEnum currentPositioning =
MapTargetPositionToCurrentPositioning(overallTargetState.Value().position.Value().Value());
overallCurrentState.Value().position.SetValue(MakeNullable(currentPositioning));
}
if (overallTargetState.Value().latch.HasValue() && !overallTargetState.Value().latch.Value().IsNull())
{
overallCurrentState.Value().latch.SetValue(MakeNullable(overallTargetState.Value().latch.Value().Value()));
}
if (overallTargetState.Value().speed.HasValue())
{
overallCurrentState.Value().speed.SetValue(overallTargetState.Value().speed.Value());
}
bool isClosureInSecureState = true;
// First, check if the closure is fully closed and has positioning feature.
if (mLogic.GetConformance().FeatureMap().Has(Feature::kPositioning))
{
isClosureInSecureState &= overallCurrentState.Value().position.HasValue() &&
!overallCurrentState.Value().position.Value().IsNull() &&
overallCurrentState.Value().position.Value().Value() == CurrentPositionEnum::kFullyClosed;
}
// Next, check if motion latching is enabled and latch is true.
if (mLogic.GetConformance().FeatureMap().Has(Feature::kMotionLatching))
{
isClosureInSecureState &= overallCurrentState.Value().latch.HasValue() &&
!overallCurrentState.Value().latch.Value().IsNull() && overallCurrentState.Value().latch.Value().Value() == true;
}
overallCurrentState.Value().secureState.SetNonNull(isClosureInSecureState);
mLogic.SetOverallCurrentState(overallCurrentState);
}
CurrentPositionEnum ClosureControlEndpoint::MapTargetPositionToCurrentPositioning(TargetPositionEnum value)
{
switch (value)
{
case TargetPositionEnum::kMoveToFullyClosed:
return CurrentPositionEnum::kFullyClosed;
case TargetPositionEnum::kMoveToFullyOpen:
return CurrentPositionEnum::kFullyOpened;
case TargetPositionEnum::kMoveToPedestrianPosition:
return CurrentPositionEnum::kOpenedForPedestrian;
case TargetPositionEnum::kMoveToVentilationPosition:
return CurrentPositionEnum::kOpenedForVentilation;
case TargetPositionEnum::kMoveToSignaturePosition:
return CurrentPositionEnum::kOpenedAtSignature;
default:
return CurrentPositionEnum::kUnknownEnumValue;
}
}
void ClosureControlEndpoint::OnPanelMotionActionComplete()
{
mLogic.SetMainState(MainStateEnum::kStopped);
// Set the OverallState position to PartiallyOpened as motion has been stopped
auto position = MakeOptional(DataModel::MakeNullable(CurrentPositionEnum::kPartiallyOpened));
DataModel::Nullable<GenericOverallCurrentState> overallCurrentState;
DataModel::Nullable<GenericOverallTargetState> overallTargetState;
VerifyOrReturn(mLogic.GetOverallCurrentState(overallCurrentState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get OverallCurrentState"));
VerifyOrReturn(mLogic.GetOverallTargetState(overallTargetState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get OverallTargetState"));
if (overallCurrentState.IsNull())
{
overallCurrentState.SetNonNull(GenericOverallCurrentState(position, NullOptional, NullOptional, NullOptional));
}
else
{
overallCurrentState.Value().position = position;
}
// Set latch and speed to their target values if they are set in the overall target.
if (!overallTargetState.IsNull())
{
if (overallTargetState.Value().latch.HasValue() && !overallTargetState.Value().latch.Value().IsNull())
{
overallCurrentState.Value().latch.SetValue(DataModel::MakeNullable(overallTargetState.Value().latch.Value().Value()));
}
if (overallTargetState.Value().speed.HasValue())
{
// If the target speed was Auto, we set it to Auto.
overallCurrentState.Value().speed.SetValue(overallTargetState.Value().speed.Value());
}
}
mLogic.SetOverallCurrentState(overallCurrentState);
mLogic.SetCountdownTimeFromDelegate(0);
mLogic.GenerateMovementCompletedEvent();
}