blob: a220cdc0b1f63be703f965921bdad2cf54c402a5 [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 "ClosureManager.h"
#include "ClosureControlEndpoint.h"
#include "ClosureDimensionEndpoint.h"
#include <app/server/Server.h>
#include <app/util/attribute-storage.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::DataModel;
using namespace chip::app::Clusters;
using namespace chip::DeviceLayer;
using namespace chip::app::Clusters::ClosureControl;
using namespace chip::app::Clusters::ClosureDimension;
namespace {
// Define a constant for the countdown time
constexpr uint32_t kCountdownTimeSeconds = 10;
constexpr uint32_t kCalibrateCountdownTimeMs = 3000; // 3 seconds for calibrate motion
constexpr uint32_t kMotionCountdownTimeMs = 1000; // 1 second for each motion.
constexpr chip::Percent100ths kMotionPositionStep = 2000; // 20% of the total range per motion interval.
// Define the Namespace and Tag for the endpoint
// Derived from https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/namespaces/Namespace-Closure.adoc
constexpr uint8_t kNamespaceClosure = 0x44;
constexpr uint8_t kTagClosureCovering = 0x00;
// Derived from
// https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/namespaces/Namespace-Closure-Covering.adoc
constexpr uint8_t kNamespaceCovering = 0x46;
constexpr uint8_t kTagCoveringVenetian = 0x03;
// Derived from https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/namespaces/Namespace-ClosurePanel.adoc
constexpr uint8_t kNamespaceClosurePanel = 0x45;
constexpr uint8_t kTagClosurePanelLift = 0x00;
constexpr uint8_t kTagClosurePanelTilt = 0x01;
// Define the list of semantic tags for the endpoint
const Clusters::Descriptor::Structs::SemanticTagStruct::Type kClosureEndpoint1TagList[] = {
{ .namespaceID = kNamespaceClosure,
.tag = kTagClosureCovering,
.label = chip::MakeOptional(DataModel::Nullable<chip::CharSpan>("Closure.Covering"_span)) },
{ .namespaceID = kNamespaceCovering,
.tag = kTagCoveringVenetian,
.label = chip::MakeOptional(DataModel::Nullable<chip::CharSpan>("Covering.Venetian"_span)) },
};
const Clusters::Descriptor::Structs::SemanticTagStruct::Type kClosurePanelEndpoint2TagList[] = {
{ .namespaceID = kNamespaceClosurePanel,
.tag = kTagClosurePanelLift,
.label = chip::MakeOptional(DataModel::Nullable<chip::CharSpan>("ClosurePanel.Lift"_span)) },
};
const Clusters::Descriptor::Structs::SemanticTagStruct::Type kClosurePanelEndpoint3TagList[] = {
{ .namespaceID = kNamespaceClosurePanel,
.tag = kTagClosurePanelTilt,
.label = chip::MakeOptional(DataModel::Nullable<chip::CharSpan>("ClosurePanel.Tilt"_span)) },
};
} // namespace
// Definition of the static instance of ClosureManager
ClosureManager ClosureManager::sInstance;
void ClosureManager::Init()
{
VerifyOrDie(mClosureEndpoint1.Init() == CHIP_NO_ERROR);
ChipLogProgress(AppServer, "Closure Control Endpoint initialized successfully");
VerifyOrDie(mClosurePanelEndpoint2.Init() == CHIP_NO_ERROR);
ChipLogProgress(AppServer, "Closure Panel Endpoint 2 initialized successfully");
VerifyOrDie(mClosurePanelEndpoint3.Init() == CHIP_NO_ERROR);
ChipLogProgress(AppServer, "Closure Panel Endpoint 3 initialized successfully");
// Set Taglist for Closure endpoints
SetTagList(/* endpoint= */ kClosureEndpoint1,
Span<const Clusters::Descriptor::Structs::SemanticTagStruct::Type>(kClosureEndpoint1TagList));
SetTagList(/* endpoint= */ kClosurePanelEndpoint2,
Span<const Clusters::Descriptor::Structs::SemanticTagStruct::Type>(kClosurePanelEndpoint2TagList));
SetTagList(/* endpoint= */ kClosurePanelEndpoint3,
Span<const Clusters::Descriptor::Structs::SemanticTagStruct::Type>(kClosurePanelEndpoint3TagList));
// Set Initial state for Closure endpoints
VerifyOrDie(SetClosureControlInitialState(mClosureEndpoint1) == CHIP_NO_ERROR);
ChipLogProgress(AppServer, "Initial state for Closure Control Endpoint set successfully");
VerifyOrDie(SetClosurePanelInitialState(mClosurePanelEndpoint2) == CHIP_NO_ERROR);
ChipLogProgress(AppServer, "Initial state for Closure Panel Endpoint 2 set successfully");
VerifyOrDie(SetClosurePanelInitialState(mClosurePanelEndpoint3) == CHIP_NO_ERROR);
ChipLogProgress(AppServer, "Initial state for Closure Panel Endpoint 3 set successfully");
TestEventTriggerDelegate * pTestEventDelegate = Server::GetInstance().GetTestEventTriggerDelegate();
if (pTestEventDelegate != nullptr)
{
CHIP_ERROR err = pTestEventDelegate->AddHandler(&mClosureEndpoint1.GetDelegate());
if (err != CHIP_NO_ERROR)
{
ChipLogError(AppServer, "Failed to add handler for delegate: %s", chip::ErrorStr(err));
}
}
else
{
ChipLogError(AppServer, "TestEventTriggerDelegate is null, cannot add handler for delegate");
}
}
void ClosureManager::Shutdown()
{
TestEventTriggerDelegate * pTestEventDelegate = Server::GetInstance().GetTestEventTriggerDelegate();
if (pTestEventDelegate != nullptr)
{
pTestEventDelegate->RemoveHandler(&mClosureEndpoint1.GetDelegate());
}
else
{
ChipLogError(AppServer, "TestEventTriggerDelegate is null, cannot remove handler for delegate");
}
}
CHIP_ERROR ClosureManager::SetClosureControlInitialState(ClosureControlEndpoint & closureControlEndpoint)
{
ChipLogProgress(AppServer, "ClosureControlEndpoint SetInitialState");
ClosureControl::ClusterConformance conformance = closureControlEndpoint.GetLogic().GetConformance();
Optional<DataModel::Nullable<CurrentPositionEnum>> currentPosition =
conformance.HasFeature(ClosureControl::Feature::kPositioning)
? MakeOptional(DataModel::MakeNullable(CurrentPositionEnum::kFullyClosed))
: NullOptional;
Optional<DataModel::Nullable<bool>> currentLatch = conformance.HasFeature(ClosureControl::Feature::kMotionLatching)
? MakeOptional(DataModel::MakeNullable(true))
: NullOptional;
Optional<Globals::ThreeLevelAutoEnum> speed =
conformance.HasFeature(ClosureControl::Feature::kSpeed) ? MakeOptional(Globals::ThreeLevelAutoEnum::kAuto) : NullOptional;
DataModel::Nullable<GenericOverallCurrentState> overallState(
GenericOverallCurrentState(currentPosition, currentLatch, speed, DataModel::MakeNullable(true)));
ReturnErrorOnFailure(closureControlEndpoint.GetLogic().SetOverallCurrentState(overallState));
Optional<DataModel::Nullable<TargetPositionEnum>> targetPosition =
conformance.HasFeature(ClosureControl::Feature::kPositioning) ? MakeOptional(DataModel::NullNullable) : NullOptional;
Optional<DataModel::Nullable<bool>> targetLatch =
conformance.HasFeature(ClosureControl::Feature::kMotionLatching) ? MakeOptional(DataModel::NullNullable) : NullOptional;
DataModel::Nullable<GenericOverallTargetState> overallTarget(GenericOverallTargetState(targetPosition, targetLatch, speed));
ReturnErrorOnFailure(closureControlEndpoint.GetLogic().SetOverallTargetState(overallTarget));
if (conformance.HasFeature(ClosureControl::Feature::kPositioning) &&
!conformance.HasFeature(ClosureControl::Feature::kInstantaneous))
{
ReturnErrorOnFailure(closureControlEndpoint.GetLogic().SetCountdownTimeFromDelegate(NullNullable));
}
ReturnErrorOnFailure(closureControlEndpoint.GetLogic().SetMainState(MainStateEnum::kStopped));
if (conformance.HasFeature(ClosureControl::Feature::kMotionLatching))
{
BitFlags<ClosureControl::LatchControlModesBitmap> latchControlModes;
latchControlModes.Set(ClosureControl::LatchControlModesBitmap::kRemoteLatching)
.Set(ClosureControl::LatchControlModesBitmap::kRemoteUnlatching);
ReturnErrorOnFailure(closureControlEndpoint.GetLogic().SetLatchControlModes(latchControlModes));
}
return CHIP_NO_ERROR;
}
CHIP_ERROR ClosureManager::SetClosurePanelInitialState(ClosureDimensionEndpoint & closurePanelEndpoint)
{
ChipLogProgress(AppServer, "ClosurePanelEndpoint SetInitialState");
ClosureDimension::ClusterConformance conformance = closurePanelEndpoint.GetLogic().GetConformance();
Optional<DataModel::Nullable<Percent100ths>> currentPosition = conformance.HasFeature(ClosureDimension::Feature::kPositioning)
? MakeOptional(DataModel::MakeNullable<Percent100ths>(10000))
: NullOptional;
Optional<DataModel::Nullable<bool>> currentLatch = conformance.HasFeature(ClosureDimension::Feature::kMotionLatching)
? MakeOptional(DataModel::MakeNullable(true))
: NullOptional;
Optional<Globals::ThreeLevelAutoEnum> speed =
conformance.HasFeature(ClosureDimension::Feature::kSpeed) ? MakeOptional(Globals::ThreeLevelAutoEnum::kAuto) : NullOptional;
DataModel::Nullable<GenericDimensionStateStruct> currentState(
GenericDimensionStateStruct(currentPosition, currentLatch, speed));
ReturnErrorOnFailure(closurePanelEndpoint.GetLogic().SetCurrentState(currentState));
Optional<DataModel::Nullable<Percent100ths>> targetPosition =
conformance.HasFeature(ClosureDimension::Feature::kPositioning) ? MakeOptional(DataModel::NullNullable) : NullOptional;
Optional<DataModel::Nullable<bool>> targetLatch =
conformance.HasFeature(ClosureDimension::Feature::kMotionLatching) ? MakeOptional(DataModel::NullNullable) : NullOptional;
DataModel::Nullable<GenericDimensionStateStruct> targetState(GenericDimensionStateStruct(targetPosition, targetLatch, speed));
ReturnErrorOnFailure(closurePanelEndpoint.GetLogic().SetTargetState(targetState));
if (conformance.HasFeature(ClosureDimension::Feature::kPositioning))
{
ReturnErrorOnFailure(closurePanelEndpoint.GetLogic().SetResolution(Percent100ths(100)));
ReturnErrorOnFailure(closurePanelEndpoint.GetLogic().SetStepValue(1000));
}
if (conformance.HasFeature(ClosureDimension::Feature::kUnit))
{
ReturnErrorOnFailure(closurePanelEndpoint.GetLogic().SetUnit(ClosureUnitEnum::kMillimeter));
ReturnErrorOnFailure(closurePanelEndpoint.GetLogic().SetUnitRange(ClosureDimension::Structs::UnitRangeStruct::Type{
.min = static_cast<int16_t>(0), .max = static_cast<int16_t>(10000) }));
}
if (conformance.HasFeature(ClosureDimension::Feature::kRotation))
{
ReturnErrorOnFailure(closurePanelEndpoint.GetLogic().SetOverflow(OverflowEnum::kTopInside));
}
if (conformance.HasFeature(ClosureDimension::Feature::kLimitation))
{
ClosureDimension::Structs::RangePercent100thsStruct::Type limitRange{ .min = static_cast<Percent100ths>(0),
.max = static_cast<Percent100ths>(10000) };
ReturnErrorOnFailure(closurePanelEndpoint.GetLogic().SetLimitRange(limitRange));
}
if (conformance.HasFeature(ClosureDimension::Feature::kMotionLatching))
{
BitFlags<ClosureDimension::LatchControlModesBitmap> latchControlModes;
latchControlModes.Set(ClosureDimension::LatchControlModesBitmap::kRemoteLatching)
.Set(ClosureDimension::LatchControlModesBitmap::kRemoteUnlatching);
ReturnErrorOnFailure(closurePanelEndpoint.GetLogic().SetLatchControlModes(latchControlModes));
}
return CHIP_NO_ERROR;
}
chip::Protocols::InteractionModel::Status ClosureManager::OnCalibrateCommand()
{
// Cancel any existing timers for closure actions over all endpoints
DeviceLayer::SystemLayer().CancelTimer(HandleEp1ClosureActionTimer, this);
DeviceLayer::SystemLayer().CancelTimer(HandleEp2ClosureActionTimer, this);
DeviceLayer::SystemLayer().CancelTimer(HandleEp3ClosureActionTimer, this);
mEp1CurrentAction = ClosureAction::kCalibrateAction;
mIsCalibrationActionInProgress = true;
// For sample application, we are using a timer to simulate the hardware calibration action.
// In a real application, this would be replaced with actual calibration logic and call HandleClosureActionComplete.
VerifyOrReturnValue(DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(kCalibrateCountdownTimeMs),
HandleEp1ClosureActionTimer, nullptr) == CHIP_NO_ERROR,
Status::Failure, ChipLogError(AppServer, "Failed to start closure action timer"));
ChipLogProgress(AppServer, "ClosureManager: Calibration action started for endpoint 1");
return Status::Success;
}
chip::Protocols::InteractionModel::Status ClosureManager::OnStopCommand()
{
// Cancel any existing timers for closure action
DeviceLayer::SystemLayer().CancelTimer(HandleEp1ClosureActionTimer, this);
DeviceLayer::SystemLayer().CancelTimer(HandleEp2ClosureActionTimer, this);
DeviceLayer::SystemLayer().CancelTimer(HandleEp3ClosureActionTimer, this);
mEp1CurrentAction = ClosureAction::kStopAction;
HandleStopActionComplete();
return Status::Success;
}
chip::Protocols::InteractionModel::Status
ClosureManager::OnMoveToCommand(const Optional<TargetPositionEnum> & position, const Optional<bool> & latch,
const Optional<chip::app::Clusters::Globals::ThreeLevelAutoEnum> & speed)
{
// Cancel any existing timer for closure action
(void) DeviceLayer::SystemLayer().CancelTimer(HandleEp1ClosureActionTimer, this);
// Update the target state for the closure panels based on the MoveTo command.
// This closure sample app assumes that the closure panels are represented by two endpoints:
// - Endpoint 2: Represents the Closure Dimension Cluster for the first panel.
// - Endpoint 3: Represents the Closure Dimension Cluster for the second panel.
// For sample app, MoveTo command to Fullopen , will set target position of both panels to 0
// MoveTo command to Fullclose will set target position of both panels to 10000
// We simulate harware action by using timer for 1 sec and updating the current state of the panels after the timer expires.
// till we reach the target position.
DataModel::Nullable<GenericDimensionStateStruct> ep2CurrentState;
VerifyOrReturnError(mClosurePanelEndpoint2.GetLogic().GetCurrentState(ep2CurrentState) == CHIP_NO_ERROR, Status::Failure,
ChipLogError(AppServer, "Failed to get current state for Endpoint 2"));
DataModel::Nullable<GenericDimensionStateStruct> ep3CurrentState;
VerifyOrReturnError(mClosurePanelEndpoint3.GetLogic().GetCurrentState(ep3CurrentState) == CHIP_NO_ERROR, Status::Failure,
ChipLogError(AppServer, "Failed to get current state for Endpoint 3"));
DataModel::Nullable<GenericDimensionStateStruct> ep2TargetState;
VerifyOrReturnError(mClosurePanelEndpoint2.GetLogic().GetTargetState(ep2TargetState) == CHIP_NO_ERROR, Status::Failure,
ChipLogError(AppServer, "Failed to get target state for Endpoint 2"));
DataModel::Nullable<GenericDimensionStateStruct> ep3TargetState;
VerifyOrReturnError(mClosurePanelEndpoint3.GetLogic().GetTargetState(ep3TargetState) == CHIP_NO_ERROR, Status::Failure,
ChipLogError(AppServer, "Failed to get target state for Endpoint 3"));
VerifyOrReturnError(!ep2CurrentState.IsNull(), Status::Failure,
ChipLogError(AppServer, "MoveToCommand failed due to Null value Current state on Endpoint 2"));
VerifyOrReturnError(!ep3CurrentState.IsNull(), Status::Failure,
ChipLogError(AppServer, "MoveToCommand failed due to Null value Current state on Endpoint 3"));
// Create target struct for the panels if the target state is not set.
GenericDimensionStateStruct ep2Target = ep2TargetState.IsNull() ? GenericDimensionStateStruct() : ep2TargetState.Value();
GenericDimensionStateStruct ep3Target = ep3TargetState.IsNull() ? GenericDimensionStateStruct() : ep3TargetState.Value();
ClosureDimension::ClusterConformance ep2Conformance = mClosurePanelEndpoint2.GetLogic().GetConformance();
ClosureDimension::ClusterConformance ep3Conformance = mClosurePanelEndpoint3.GetLogic().GetConformance();
if (position.HasValue())
{
// Set the Closure panel target position for the panels based on the MoveTo Command position.
// For Sample App,TargetPositionEnum is mapped to specific positions for the panels.
chip::Percent100ths ep2Position;
chip::Percent100ths ep3Position;
switch (position.Value())
{
case TargetPositionEnum::kMoveToFullyClosed:
ep2Position = static_cast<chip::Percent100ths>(10000);
ep3Position = static_cast<chip::Percent100ths>(10000);
break;
case TargetPositionEnum::kMoveToFullyOpen:
ep2Position = static_cast<chip::Percent100ths>(0);
ep3Position = static_cast<chip::Percent100ths>(0);
break;
case TargetPositionEnum::kMoveToPedestrianPosition:
ep2Position = static_cast<chip::Percent100ths>(6000);
ep3Position = static_cast<chip::Percent100ths>(6000);
break;
case TargetPositionEnum::kMoveToSignaturePosition:
ep2Position = static_cast<chip::Percent100ths>(4000);
ep3Position = static_cast<chip::Percent100ths>(4000);
break;
case TargetPositionEnum::kMoveToVentilationPosition:
ep2Position = static_cast<chip::Percent100ths>(2000);
ep3Position = static_cast<chip::Percent100ths>(2000);
break;
default:
ChipLogError(AppServer, "Invalid target position received in OnMoveToCommand");
return Status::Failure;
}
if (ep2Conformance.HasFeature(ClosureDimension::Feature::kPositioning))
{
ep2Target.position.SetValue(DataModel::MakeNullable(ep2Position));
}
if (ep3Conformance.HasFeature(ClosureDimension::Feature::kPositioning))
{
ep3Target.position.SetValue(DataModel::MakeNullable(ep3Position));
}
}
if (latch.HasValue())
{
if (ep2Conformance.HasFeature(ClosureDimension::Feature::kMotionLatching))
{
ep2Target.latch.SetValue(DataModel::MakeNullable(latch.Value()));
}
if (ep3Conformance.HasFeature(ClosureDimension::Feature::kMotionLatching))
{
ep3Target.latch.SetValue(DataModel::MakeNullable(latch.Value()));
}
}
if (speed.HasValue())
{
if (ep2Conformance.HasFeature(ClosureDimension::Feature::kSpeed))
{
ep2Target.speed.SetValue(speed.Value());
}
if (ep3Conformance.HasFeature(ClosureDimension::Feature::kSpeed))
{
ep3Target.speed.SetValue(speed.Value());
}
}
VerifyOrReturnError(mClosurePanelEndpoint2.GetLogic().SetTargetState(DataModel::MakeNullable(ep2Target)) == CHIP_NO_ERROR,
Status::Failure, ChipLogError(AppServer, "Failed to set target for Endpoint 2"));
VerifyOrReturnError(mClosurePanelEndpoint3.GetLogic().SetTargetState(DataModel::MakeNullable(ep3Target)) == CHIP_NO_ERROR,
Status::Failure, ChipLogError(AppServer, "Failed to set target for Endpoint 3"));
VerifyOrReturnError(mClosureEndpoint1.GetLogic().SetCountdownTimeFromDelegate(kCountdownTimeSeconds) == CHIP_NO_ERROR,
Status::Failure, ChipLogError(AppServer, "Failed to set countdown time for move to command on Endpoint 1"));
mEp1CurrentAction = ClosureAction::kMoveToAction;
mEp1MotionInProgress = true;
DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(kMotionCountdownTimeMs), HandleEp1ClosureActionTimer, this);
return Status::Success;
}
chip::Protocols::InteractionModel::Status ClosureManager::OnSetTargetCommand(const Optional<Percent100ths> & position,
const Optional<bool> & latch,
const Optional<Globals::ThreeLevelAutoEnum> & speed,
const chip::EndpointId endpointId)
{
MainStateEnum ep1MainState;
VerifyOrReturnError(mClosureEndpoint1.GetLogic().GetMainState(ep1MainState) == CHIP_NO_ERROR, Status::Failure,
ChipLogError(AppServer, "Failed to get main state for Step command on Endpoint 1"));
// If this command is received while the MainState attribute is currently either in Disengaged, Protected, Calibrating,
// SetupRequired or Error, then a status code of INVALID_IN_STATE shall be returned.
VerifyOrReturnError(ep1MainState != MainStateEnum::kDisengaged && ep1MainState != MainStateEnum::kProtected &&
ep1MainState != MainStateEnum::kSetupRequired && ep1MainState != MainStateEnum::kError &&
ep1MainState != MainStateEnum::kCalibrating,
Status::InvalidInState,
ChipLogError(AppServer, "Step command not allowed in current state: %d", static_cast<int>(ep1MainState)));
// Update OverallTarget of Closure based on SetTarget command.
DataModel::Nullable<GenericOverallTargetState> overallTargetState;
VerifyOrReturnError(mClosureEndpoint1.GetLogic().GetOverallTargetState(overallTargetState) == CHIP_NO_ERROR, Status::Failure,
ChipLogError(AppServer, "Failed to get overall target for SetTarget command"));
if (overallTargetState.IsNull())
{
overallTargetState.SetNonNull(GenericOverallTargetState{});
}
ClosureDimensionEndpoint mClosurePanelEndpoint =
endpointId == kClosurePanelEndpoint2 ? mClosurePanelEndpoint2 : mClosurePanelEndpoint3;
ClosureDimension::ClusterConformance mClosurePanelConformance = mClosurePanelEndpoint.GetLogic().GetConformance();
if (position.HasValue() && mClosurePanelConformance.HasFeature(ClosureDimension::Feature::kPositioning))
{
// Set overallTargetState position to NullOptional as panel position change cannot be represented in OverallTarget.
overallTargetState.Value().position.SetValue(DataModel::NullNullable);
}
if (latch.HasValue() && mClosurePanelConformance.HasFeature(ClosureDimension::Feature::kMotionLatching))
{
overallTargetState.Value().latch.SetValue(DataModel::MakeNullable(latch.Value()));
}
if (speed.HasValue() && mClosurePanelConformance.HasFeature(ClosureDimension::Feature::kSpeed))
{
overallTargetState.Value().speed.SetValue(speed.Value());
}
VerifyOrReturnError(mClosureEndpoint1.GetLogic().SetOverallTargetState(overallTargetState) == CHIP_NO_ERROR, Status::Failure,
ChipLogError(AppServer, "Failed to set overall target for SetTarget command for Endpoint %d", endpointId));
VerifyOrReturnError(mClosureEndpoint1.GetLogic().SetCountdownTimeFromDelegate(kCountdownTimeSeconds) == CHIP_NO_ERROR,
Status::Failure,
ChipLogError(AppServer, "Failed to set countdown time for SetTarget command on Endpoint %d", endpointId));
VerifyOrReturnError(mClosureEndpoint1.GetLogic().SetMainState(MainStateEnum::kMoving) == CHIP_NO_ERROR, Status::Failure,
ChipLogError(AppServer, "Failed to set main state for SetTarget command on Endpoint 1"));
// Post an event to initiate the unlatch action asynchronously.
// Closure panel first performs the unlatch action if it is currently latched,
// and then continues with the SetTarget action.
// This is to ensure that the panel can move to the target position without being latched.
if (endpointId == kClosurePanelEndpoint2)
{
mEp2CurrentAction = ClosureManager::ClosureAction::kSetTargetAction;
mEp2MotionInProgress = true;
(void) DeviceLayer::SystemLayer().CancelTimer(HandleEp2ClosureActionTimer, this);
(void) DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(kMotionCountdownTimeMs),
HandleEp2ClosureActionTimer, this);
ChipLogError(AppServer, "Triggered HandleEp2ClosureActionTimer for SetTarget command on Endpoint 2");
}
else if (endpointId == kClosurePanelEndpoint3)
{
mEp3CurrentAction = ClosureManager::ClosureAction::kSetTargetAction;
mEp3MotionInProgress = true;
(void) DeviceLayer::SystemLayer().CancelTimer(HandleEp3ClosureActionTimer, this);
(void) DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(kMotionCountdownTimeMs),
HandleEp3ClosureActionTimer, this);
ChipLogError(AppServer, "Triggered HandleEp3ClosureActionTimer for SetTarget command on Endpoint 3");
}
else
{
ChipLogError(AppServer, "Invalid endpoint ID for SetTarget command: %d", endpointId);
return Status::Failure;
}
return Status::Success;
}
chip::Protocols::InteractionModel::Status ClosureManager::OnStepCommand(const StepDirectionEnum & direction,
const uint16_t & numberOfSteps,
const Optional<Globals::ThreeLevelAutoEnum> & speed,
const chip::EndpointId endpointId)
{
MainStateEnum ep1MainState;
VerifyOrReturnError(mClosureEndpoint1.GetLogic().GetMainState(ep1MainState) == CHIP_NO_ERROR, Status::Failure,
ChipLogError(AppServer, "Failed to get main state for Step command on Endpoint 1"));
// If this command is received while the MainState attribute is currently either in Disengaged, Protected, Calibrating,
// SetupRequired or Error, then a status code of INVALID_IN_STATE shall be returned.
VerifyOrReturnError(ep1MainState != MainStateEnum::kDisengaged && ep1MainState != MainStateEnum::kProtected &&
ep1MainState != MainStateEnum::kSetupRequired && ep1MainState != MainStateEnum::kError &&
ep1MainState != MainStateEnum::kCalibrating,
Status::InvalidInState,
ChipLogError(AppServer, "Step command not allowed in current state: %d", static_cast<int>(ep1MainState)));
VerifyOrReturnError(mClosureEndpoint1.GetLogic().SetMainState(MainStateEnum::kMoving) == CHIP_NO_ERROR, Status::Failure,
ChipLogError(AppServer, "Failed to set countdown time for move to command on Endpoint 1"));
VerifyOrReturnError(mClosureEndpoint1.GetLogic().SetCountdownTimeFromDelegate(10) == CHIP_NO_ERROR, Status::Failure,
ChipLogError(AppServer, "Failed to set countdown time for move to command on Endpoint 1"));
// Update Overall Target to Null for the Closure Control on Endpoint 1
DataModel::Nullable<GenericOverallTargetState> ep1Target;
VerifyOrReturnValue(mClosureEndpoint1.GetLogic().GetOverallTargetState(ep1Target) == CHIP_NO_ERROR, Status::Failure,
ChipLogError(AppServer, "Failed to get overall target for Step command"));
if (ep1Target.IsNull())
{
ep1Target.SetNonNull(GenericOverallTargetState{});
}
ep1Target.Value().position.SetValue(
DataModel::NullNullable); // Set position to Null as it cannot represent panel position change.
VerifyOrReturnValue(mClosureEndpoint1.GetLogic().SetOverallTargetState(ep1Target) == CHIP_NO_ERROR, Status::Failure,
ChipLogError(AppServer, "Failed to set overall target for Step command"));
if (endpointId == kClosurePanelEndpoint2)
{
mEp2CurrentAction = ClosureManager::ClosureAction::kStepAction;
mEp2MotionInProgress = true;
(void) DeviceLayer::SystemLayer().CancelTimer(HandleEp2ClosureActionTimer, this);
(void) DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(kMotionCountdownTimeMs),
HandleEp2ClosureActionTimer, this);
ChipLogError(AppServer, "Triggered HandleEp2ClosureActionTimer for Step command on Endpoint 2");
}
else if (endpointId == kClosurePanelEndpoint3)
{
mEp3CurrentAction = ClosureManager::ClosureAction::kStepAction;
mEp3MotionInProgress = true;
(void) DeviceLayer::SystemLayer().CancelTimer(HandleEp3ClosureActionTimer, this);
(void) DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(kMotionCountdownTimeMs),
HandleEp3ClosureActionTimer, this);
ChipLogError(AppServer, "Triggered HandleEp3ClosureActionTimer for Step command on Endpoint 3");
}
else
{
ChipLogError(AppServer, "Invalid endpoint ID for SetTarget command: %d", endpointId);
return Status::Failure;
}
return Status::Success;
}
void ClosureManager::HandleEp1ClosureActionTimer(System::Layer * layer, void * aAppState)
{
// Mark aAppState as unused to avoid compiler warnings
// Will be used in closure dimension cluster Commands
(void) aAppState;
ClosureManager & instance = ClosureManager::GetInstance();
ChipLogError(AppServer, "HandleEp1ClosureActionTimer called with current action: %d",
static_cast<int>(instance.mEp1CurrentAction));
switch (instance.mEp1CurrentAction)
{
case ClosureAction::kCalibrateAction:
instance.HandleCalibrateActionComplete();
break;
case ClosureAction::kStopAction:
// Add logic to handle Stop action completion
break;
case ClosureAction::kMoveToAction:
instance.HandleClosureMotionAction();
break;
case ClosureAction::kLatchAction:
// Add logic to handle Latch action completion
break;
default:
ChipLogError(AppServer, "Invalid action received in HandleEp1ClosureActionTimer");
break;
}
}
void ClosureManager::HandleEp2ClosureActionTimer(System::Layer * layer, void * aAppState)
{
// Mark aAppState as unused to avoid compiler warnings
// Will be used in closure dimension cluster Commands
(void) aAppState;
ClosureManager & instance = ClosureManager::GetInstance();
ChipLogError(AppServer, "HandleEp2ClosureActionTimer called with current action: %d",
static_cast<int>(instance.mEp2CurrentAction));
switch (instance.mEp2CurrentAction)
{
case ClosureAction::kSetTargetAction:
instance.HandlePanelSetTargetAction(kClosurePanelEndpoint2);
break;
case ClosureAction::kStepAction:
instance.HandlePanelStepAction(kClosurePanelEndpoint2);
break;
case ClosureAction::kPanelUnLatchAction:
instance.HandlePanelUnlatchAction(kClosurePanelEndpoint2);
break;
default:
ChipLogError(AppServer, "Invalid action received in HandleEp2ClosureActionTimer");
break;
}
}
void ClosureManager::HandleEp3ClosureActionTimer(System::Layer * layer, void * aAppState)
{
// Mark aAppState as unused to avoid compiler warnings
// Will be used in closure dimension cluster Commands
(void) aAppState;
ClosureManager & instance = ClosureManager::GetInstance();
ChipLogError(AppServer, "HandleEp3ClosureActionTimer called with current action: %d",
static_cast<int>(instance.mEp3CurrentAction));
switch (instance.mEp3CurrentAction)
{
case ClosureAction::kSetTargetAction:
instance.HandlePanelSetTargetAction(kClosurePanelEndpoint3);
break;
case ClosureAction::kStepAction:
instance.HandlePanelStepAction(kClosurePanelEndpoint3);
break;
case ClosureAction::kPanelLatchAction:
instance.HandlePanelUnlatchAction(kClosurePanelEndpoint3);
break;
default:
ChipLogError(AppServer, "Invalid action received in HandleEp3ClosureActionTimer");
break;
}
}
void ClosureManager::HandlePanelUnlatchAction(EndpointId endpointId)
{
ClosureManager & instance = ClosureManager::GetInstance();
// Get the endpoint instance based on the endpointId
chip::app::Clusters::ClosureDimension::ClosureDimensionEndpoint * panelEp = GetCurrentPanelInstance(endpointId);
VerifyOrReturn(panelEp != nullptr,
ChipLogError(AppServer, "HandlePanelSetTargetAction called with invalid endpointId: %u", endpointId));
DataModel::Nullable<GenericDimensionStateStruct> panelCurrentState = DataModel::NullNullable;
DataModel::Nullable<GenericDimensionStateStruct> panelTargetState = DataModel::NullNullable;
VerifyOrReturn(panelEp->GetLogic().GetCurrentState(panelCurrentState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get current state for Endpoint %d", endpointId));
VerifyOrReturn(!panelCurrentState.IsNull(), ChipLogError(AppServer, "Current state is not set for Endpoint %d", endpointId));
VerifyOrReturn(panelEp->GetLogic().GetTargetState(panelTargetState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get target for Endpoint %d", endpointId));
VerifyOrReturn(!panelTargetState.IsNull(), ChipLogError(AppServer, "Target is not set for Endpoint %d", endpointId));
// If currently latched (true) and target is unlatched (false), Perform unlatch action and call timer with SET_TARGET_ACTION
// to continue with the SetTarget action.
if (panelCurrentState.Value().latch.HasValue() && !panelCurrentState.Value().latch.Value().IsNull() &&
panelTargetState.Value().latch.HasValue() && !panelTargetState.Value().latch.Value().IsNull() &&
(panelCurrentState.Value().latch.Value().Value() && !panelTargetState.Value().latch.Value().Value()))
{
DataModel::Nullable<GenericOverallCurrentState> ep1OverallCurrentState = DataModel::NullNullable;
VerifyOrReturn(mClosureEndpoint1.GetLogic().GetOverallCurrentState(ep1OverallCurrentState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get current state for Endpoint 1"));
VerifyOrReturn(!ep1OverallCurrentState.IsNull(), ChipLogError(AppServer, "Current state is not set for Endpoint 1"));
// In Real application, this would be replaced with actual unlatch logic.
ChipLogProgress(AppServer, "Performing unlatch action");
ep1OverallCurrentState.Value().latch.SetValue(DataModel::MakeNullable(false));
ep1OverallCurrentState.Value().secureState.SetNonNull(false);
mClosureEndpoint1.GetLogic().SetOverallCurrentState(ep1OverallCurrentState);
panelCurrentState.Value().latch.SetValue(false);
panelEp->GetLogic().SetCurrentState(panelCurrentState);
ChipLogProgress(AppServer, "Unlatched action completed");
}
// Cancel any active timers before starting the panel movement
if (endpointId == kClosurePanelEndpoint2)
{
(void) DeviceLayer::SystemLayer().CancelTimer(HandleEp2ClosureActionTimer, this);
}
else if (endpointId == kClosurePanelEndpoint3)
{
(void) DeviceLayer::SystemLayer().CancelTimer(HandleEp3ClosureActionTimer, this);
}
else
{
ChipLogError(AppServer, "Invalid endpoint ID for SetTarget command: %d", endpointId);
return;
}
instance.HandlePanelSetTargetAction(endpointId);
}
void ClosureManager::HandlePanelSetTargetAction(EndpointId endpointId)
{
// Get the endpoint instance based on the endpointId
chip::app::Clusters::ClosureDimension::ClosureDimensionEndpoint * ep = GetCurrentPanelInstance(endpointId);
VerifyOrReturn(ep != nullptr,
ChipLogError(AppServer, "HandlePanelSetTargetAction called with invalid endpointId: %u", endpointId));
DataModel::Nullable<GenericDimensionStateStruct> panelCurrentState = DataModel::NullNullable;
DataModel::Nullable<GenericDimensionStateStruct> panelTargetState = DataModel::NullNullable;
VerifyOrReturn(ep->GetLogic().GetCurrentState(panelCurrentState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get current state for Endpoint %d", endpointId));
VerifyOrReturn(!panelCurrentState.IsNull(), ChipLogError(AppServer, "Current state is not set for Endpoint %d", endpointId));
VerifyOrReturn(ep->GetLogic().GetTargetState(panelTargetState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get target for Endpoint %d", endpointId));
VerifyOrReturn(!panelTargetState.IsNull(), ChipLogError(AppServer, "Target is not set for Endpoint %d", endpointId));
bool panelProgressPossible = false;
DataModel::Nullable<chip::Percent100ths> nextPosition = DataModel::NullNullable;
// Get the Next Current State to be set for the endpoint 2, if target postion is not reached.
if (GetPanelNextPosition(panelCurrentState.Value(), panelTargetState.Value(), nextPosition))
{
VerifyOrReturn(!nextPosition.IsNull(), ChipLogError(AppServer, "Next position is not set for Endpoint %d", endpointId));
panelCurrentState.Value().position.SetValue(DataModel::MakeNullable(nextPosition.Value()));
ep->GetLogic().SetCurrentState(panelCurrentState);
panelProgressPossible = (nextPosition.Value() != panelTargetState.Value().position.Value().Value());
ChipLogProgress(AppServer, "EndPoint %d Current Position: %d, Target Position: %d", endpointId, nextPosition.Value(),
panelTargetState.Value().position.Value().Value());
}
if (panelProgressPossible)
{
// Start the timer for the respective endpoint to continue with the SetTarget action
if (endpointId == kClosurePanelEndpoint2)
{
mEp2CurrentAction = ClosureManager::ClosureAction::kSetTargetAction;
(void) DeviceLayer::SystemLayer().CancelTimer(HandleEp2ClosureActionTimer, this);
(void) DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(kMotionCountdownTimeMs),
HandleEp2ClosureActionTimer, this);
ChipLogError(AppServer, "Triggered HandleEp2ClosureActionTimer for SetTarget Action command on Endpoint 2");
}
else if (endpointId == kClosurePanelEndpoint3)
{
mEp3CurrentAction = ClosureManager::ClosureAction::kSetTargetAction;
(void) DeviceLayer::SystemLayer().CancelTimer(HandleEp3ClosureActionTimer, this);
(void) DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(kMotionCountdownTimeMs),
HandleEp3ClosureActionTimer, this);
ChipLogError(AppServer, "Triggered HandleEp3ClosureActionTimer for SetTarget Action command on Endpoint 3");
}
else
{
ChipLogError(AppServer, "Invalid endpoint ID for SetTarget command: %d", endpointId);
}
return;
}
// If currently unlatched (false) and target is latched (true), latch after completing motion if Latching feature is supported.
if (ep->GetLogic().GetConformance().HasFeature(ClosureDimension::Feature::kMotionLatching))
{
if (panelCurrentState.Value().latch.HasValue() && !panelCurrentState.Value().latch.Value().IsNull() &&
panelTargetState.Value().latch.HasValue() && !panelTargetState.Value().latch.Value().IsNull())
{
if (!panelCurrentState.Value().latch.Value().Value() && panelTargetState.Value().latch.Value().Value())
{
DataModel::Nullable<GenericOverallCurrentState> ep1OverallCurrentState = DataModel::NullNullable;
VerifyOrReturn(mClosureEndpoint1.GetLogic().GetOverallCurrentState(ep1OverallCurrentState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get overall current state for Endpoint 1"));
VerifyOrReturn(!ep1OverallCurrentState.IsNull(),
ChipLogError(AppServer, "Overall current state is not set for Endpoint 1"));
// In Real application, this would be replaced with actual latch logic.
ChipLogProgress(AppServer, "Performing latch action");
ep1OverallCurrentState.Value().latch.SetValue(DataModel::MakeNullable(true));
ep1OverallCurrentState.Value().secureState.SetNonNull(false);
mClosureEndpoint1.GetLogic().SetOverallCurrentState(ep1OverallCurrentState);
panelCurrentState.Value().latch.SetValue(DataModel::MakeNullable(true));
ep->GetLogic().SetCurrentState(panelCurrentState);
ChipLogProgress(AppServer, "Latch action completed");
}
}
}
// If the target position is reached, call the HandlePanelSetTargetActionComplete method to complete the action
HandlePanelSetTargetActionComplete(endpointId);
}
void ClosureManager::HandlePanelStepAction(EndpointId endpointId)
{
ClosureManager & instance = ClosureManager::GetInstance();
// Get the endpoint instance based on the endpointId
chip::app::Clusters::ClosureDimension::ClosureDimensionEndpoint * ep = GetCurrentPanelInstance(endpointId);
VerifyOrReturn(ep != nullptr,
ChipLogError(AppServer, "HandlePanelSetTargetAction called with invalid endpointId: %u", endpointId));
StepDirectionEnum stepDirection = ep->GetDelegate().GetStepCommandTargetDirection();
DataModel::Nullable<GenericDimensionStateStruct> panelCurrentState;
DataModel::Nullable<GenericDimensionStateStruct> panelTargetState;
VerifyOrReturn(ep->GetLogic().GetCurrentState(panelCurrentState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get current state for Step action"));
VerifyOrReturn(ep->GetLogic().GetTargetState(panelTargetState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get target state for Step action"));
VerifyOrReturn(!panelCurrentState.IsNull(), ChipLogError(AppServer, "Current state is null, Step action Failed"));
VerifyOrReturn(!panelTargetState.IsNull(), ChipLogError(AppServer, "Target state is null, Step action Failed"));
VerifyOrReturn(panelCurrentState.Value().position.HasValue() && !panelCurrentState.Value().position.Value().IsNull(),
ChipLogError(AppServer, "Current or target position is not set, Step action Failed"));
VerifyOrReturn(panelTargetState.Value().position.HasValue() && !panelTargetState.Value().position.Value().IsNull(),
ChipLogError(AppServer, "Current or target position is not set, Step action Failed"));
chip::Percent100ths currentPosition = panelCurrentState.Value().position.Value().Value();
chip::Percent100ths targetPosition = panelTargetState.Value().position.Value().Value();
ChipLogProgress(AppServer, "Current Position: %d, Target Position: %d", currentPosition, targetPosition);
bool panelTargetReached = (currentPosition == targetPosition);
ChipLogProgress(AppServer, "Panel Target Reached: %s", panelTargetReached ? "true" : "false");
if (!panelTargetReached)
{
chip::Percent100ths nextCurrentPosition;
chip::Percent100ths stepValue;
VerifyOrReturn(ep->GetLogic().GetStepValue(stepValue) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get step value for Step action"));
// Compute the next position
if (stepDirection == StepDirectionEnum::kIncrease)
{
nextCurrentPosition = std::min(static_cast<chip::Percent100ths>(currentPosition + stepValue), targetPosition);
}
else
{
// Underflow protection: if currentPosition <= stepValue, set to 0.
chip::Percent100ths decreasedCurrentPosition = (currentPosition > stepValue)
? static_cast<chip::Percent100ths>(currentPosition - stepValue)
: static_cast<chip::Percent100ths>(0);
nextCurrentPosition = std::max(decreasedCurrentPosition, targetPosition);
}
panelCurrentState.Value().position.SetValue(DataModel::MakeNullable(nextCurrentPosition));
ep->GetLogic().SetCurrentState(panelCurrentState);
// Cancel any existing timer before starting a new action
if (endpointId == kClosurePanelEndpoint2)
{
instance.mEp2CurrentAction = ClosureManager::ClosureAction::kStepAction;
(void) DeviceLayer::SystemLayer().CancelTimer(HandleEp2ClosureActionTimer, this);
(void) DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(kMotionCountdownTimeMs),
HandleEp2ClosureActionTimer, this);
ChipLogError(AppServer, "Triggered HandleEp2ClosureActionTimer for Step Action command on Endpoint 2");
}
else if (endpointId == kClosurePanelEndpoint3)
{
instance.mEp3CurrentAction = ClosureManager::ClosureAction::kStepAction;
(void) DeviceLayer::SystemLayer().CancelTimer(HandleEp3ClosureActionTimer, this);
(void) DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(kMotionCountdownTimeMs),
HandleEp3ClosureActionTimer, this);
ChipLogError(AppServer, "Triggered HandleEp3ClosureActionTimer for Step Action command on Endpoint 3");
}
else
{
ChipLogError(AppServer, "Invalid endpoint ID for SetTarget command: %d", endpointId);
}
return;
}
// If the target position is reached, we can complete the action
HandlePanelStepActionComplete(endpointId);
}
void ClosureManager::HandleClosureMotionAction()
{
ClosureManager & instance = ClosureManager::GetInstance();
DataModel::Nullable<GenericOverallCurrentState> ep1CurrentState;
DataModel::Nullable<GenericDimensionStateStruct> ep2CurrentState;
DataModel::Nullable<GenericDimensionStateStruct> ep3CurrentState;
DataModel::Nullable<GenericOverallTargetState> ep1TargetState;
DataModel::Nullable<GenericDimensionStateStruct> ep2TargetState;
DataModel::Nullable<GenericDimensionStateStruct> ep3TargetState;
VerifyOrReturn(mClosureEndpoint1.GetLogic().GetOverallCurrentState(ep1CurrentState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get current state for Endpoint 1"));
VerifyOrReturn(mClosureEndpoint1.GetLogic().GetOverallTargetState(ep1TargetState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get target state for Endpoint 1"));
VerifyOrReturn(mClosurePanelEndpoint2.GetLogic().GetCurrentState(ep2CurrentState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get current state for Endpoint 2"));
VerifyOrReturn(mClosurePanelEndpoint3.GetLogic().GetCurrentState(ep3CurrentState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get current state for Endpoint 3"));
VerifyOrReturn(mClosurePanelEndpoint2.GetLogic().GetTargetState(ep2TargetState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get target state for Endpoint 2"));
VerifyOrReturn(mClosurePanelEndpoint3.GetLogic().GetTargetState(ep3TargetState) == CHIP_NO_ERROR,
ChipLogError(AppServer, "Failed to get target state for Endpoint 3"));
VerifyOrReturn(!ep1CurrentState.IsNull(),
ChipLogError(AppServer, "MoveToCommand failed due to Null value Current state on Endpoint 1"));
VerifyOrReturn(!ep2CurrentState.IsNull(),
ChipLogError(AppServer, "MoveToCommand failed due to Null value Current state on Endpoint 2"));
VerifyOrReturn(!ep3CurrentState.IsNull(),
ChipLogError(AppServer, "MoveToCommand failed due to Null value Current state on Endpoint 3"));
VerifyOrReturn(!ep1TargetState.IsNull(),
ChipLogError(AppServer, "MoveToCommand failed due to Null value Target state on Endpoint 1"));
VerifyOrReturn(!ep2TargetState.IsNull(),
ChipLogError(AppServer, "MoveToCommand failed due to Null value Target state on Endpoint 2"));
VerifyOrReturn(!ep3TargetState.IsNull(),
ChipLogError(AppServer, "MoveToCommand failed due to Null value Target state on Endpoint 3"));
// check if closure (endpoint 1) need unlatch before starting the motion action if Latching feature is supported.
if (mClosureEndpoint1.GetLogic().GetConformance().HasFeature(ClosureControl::Feature::kMotionLatching))
{
if (ep1CurrentState.Value().latch.HasValue() && !ep1CurrentState.Value().latch.Value().IsNull() &&
ep1TargetState.Value().latch.HasValue() && !ep1TargetState.Value().latch.Value().IsNull())
{
// If currently latched (true) and target is unlatched (false), unlatch first before moving
if (ep1CurrentState.Value().latch.Value().Value() && !ep1TargetState.Value().latch.Value().Value())
{
// In Real application, this would be replaced with actual unlatch logic.
ChipLogProgress(AppServer, "Performing unlatch action");
ep1CurrentState.Value().latch.SetValue(DataModel::MakeNullable(false));
ep1CurrentState.Value().secureState.SetNonNull(false);
instance.mClosureEndpoint1.GetLogic().SetOverallCurrentState(ep1CurrentState);
ep2CurrentState.Value().latch.SetValue(DataModel::MakeNullable(false));
instance.mClosurePanelEndpoint2.GetLogic().SetCurrentState(ep2CurrentState);
ep3CurrentState.Value().latch.SetValue(DataModel::MakeNullable(false));
instance.mClosurePanelEndpoint3.GetLogic().SetCurrentState(ep3CurrentState);
ChipLogProgress(AppServer, "Unlatched action completed");
}
}
}
// Once Closure is unlatched, we can proceed with the motion action for endpoints 2 and 3.
DataModel::Nullable<chip::Percent100ths> ep2NextPosition = DataModel::NullNullable;
DataModel::Nullable<chip::Percent100ths> ep3NextPosition = DataModel::NullNullable;
bool isEndPoint2ProgressPossible = false;
bool isEndPoint3ProgressPossible = false;
// Get the Next Current State to be set for the endpoint 2, if target postion is not reached.
if (GetPanelNextPosition(ep2CurrentState.Value(), ep2TargetState.Value(), ep2NextPosition))
{
VerifyOrReturn(!ep2NextPosition.IsNull(), ChipLogError(AppServer, "Failed to get next position for Endpoint 2"));
ep2CurrentState.Value().position.SetValue(DataModel::MakeNullable(ep2NextPosition.Value()));
instance.mClosurePanelEndpoint2.GetLogic().SetCurrentState(ep2CurrentState);
isEndPoint2ProgressPossible = (ep2NextPosition.Value() != ep2TargetState.Value().position.Value().Value());
ChipLogProgress(AppServer, "EndPoint 2 Current Position: %d, Target Position: %d", ep2NextPosition.Value(),
ep2TargetState.Value().position.Value().Value());
}
// Get the Next Current State to be set for the endpoint 3, if target postion is not reached.
if (GetPanelNextPosition(ep3CurrentState.Value(), ep3TargetState.Value(), ep3NextPosition))
{
VerifyOrReturn(!ep3NextPosition.IsNull(), ChipLogError(AppServer, "Failed to get next position for Endpoint 3"));
ep3CurrentState.Value().position.SetValue(DataModel::MakeNullable(ep3NextPosition.Value()));
instance.mClosurePanelEndpoint3.GetLogic().SetCurrentState(ep3CurrentState);
isEndPoint3ProgressPossible = (ep3NextPosition.Value() != ep3TargetState.Value().position.Value().Value());
ChipLogProgress(AppServer, "EndPoint 3 Current Position: %d, Target Position: %d", ep3NextPosition.Value(),
ep3TargetState.Value().position.Value().Value());
}
// Check if both endpoints have reached their target positions
// If both endpoints have reached their target positions, we can consider the closure motion action as complete.
// If either endpoint has not reached its target position, we will continue the motion action
// and set the closureTargetReached flag to false.
// This will ensure that the closure motion action continues until both endpoints have reached their target positions.
bool isProgressPossible = isEndPoint2ProgressPossible || isEndPoint3ProgressPossible;
ChipLogProgress(AppServer, "Motion progress possible: %s", isProgressPossible ? "true" : "false");
// If the closure target is not reached, we will reschedule the timer for motion action
if (isProgressPossible)
{
mEp1CurrentAction = ClosureAction::kMoveToAction;
mEp1MotionInProgress = true;
DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(kMotionCountdownTimeMs), HandleEp1ClosureActionTimer,
this);
ChipLogProgress(AppServer, "Rescheduled HandleEp1ClosureActionTimer for motion action");
return;
}
// If both endpoints have reached their target positions, we can consider the closure motion action as complete.
// Before calling HandleClosureActionComplete, we need to check if a latch action is needed.
if (mClosureEndpoint1.GetLogic().GetConformance().HasFeature(ClosureControl::Feature::kMotionLatching))
{
if (ep1CurrentState.Value().latch.HasValue() && !ep1CurrentState.Value().latch.Value().IsNull() &&
ep1TargetState.Value().latch.HasValue() && !ep1TargetState.Value().latch.Value().IsNull())
{
// If currently latched (false) and target is unlatched (true), unlatch first before moving
if (!ep1CurrentState.Value().latch.Value().Value() && ep1TargetState.Value().latch.Value().Value())
{
// In Real application, this would be replaced with actual latch logic.
ChipLogProgress(AppServer, "Performing latch action");
ep1CurrentState.Value().latch.SetValue(DataModel::MakeNullable(true));
if (ep1CurrentState.Value().position.HasValue() && !ep1CurrentState.Value().position.Value().IsNull())
{
if (ep1CurrentState.Value().position.Value().Value() == CurrentPositionEnum::kFullyClosed)
{
ep1CurrentState.Value().secureState.SetNonNull(true);
}
else
{
ep1CurrentState.Value().secureState.SetNonNull(false);
}
}
instance.mClosureEndpoint1.GetLogic().SetOverallCurrentState(ep1CurrentState);
ep2CurrentState.Value().latch.SetValue(DataModel::MakeNullable(true));
instance.mClosurePanelEndpoint2.GetLogic().SetCurrentState(ep2CurrentState);
ep3CurrentState.Value().latch.SetValue(DataModel::MakeNullable(true));
instance.mClosurePanelEndpoint3.GetLogic().SetCurrentState(ep3CurrentState);
ChipLogProgress(AppServer, "latched action complete");
}
}
}
// Target reached, call HandleMoveToActionComplete
instance.HandleMoveToActionComplete();
}
bool ClosureManager::GetPanelNextPosition(const GenericDimensionStateStruct & currentState,
const GenericDimensionStateStruct & targetState,
DataModel::Nullable<chip::Percent100ths> & nextPosition)
{
VerifyOrReturnValue(targetState.position.HasValue() && !targetState.position.Value().IsNull(), false,
ChipLogError(AppServer, "Updating CurrentState to NextPosition failed due to Target position is not set"));
VerifyOrReturnValue(currentState.position.HasValue() && !currentState.position.Value().IsNull(), false,
ChipLogError(AppServer, "Updating CurrentState to NextPosition failed due to Current position is not set"));
chip::Percent100ths currentPosition = currentState.position.Value().Value();
chip::Percent100ths targetPosition = targetState.position.Value().Value();
if (currentPosition < targetPosition)
{
// Increment position by 2000 units, capped at target.
// No overflow handling needed due to currentposition max value is 10000
nextPosition.SetNonNull(std::min(static_cast<chip::Percent100ths>(currentPosition + kMotionPositionStep), targetPosition));
}
else if (currentPosition > targetPosition)
{
// Handling underflow for CurrentPosition
chip::Percent100ths newCurrentPosition =
(currentPosition > kMotionPositionStep) ? currentPosition - kMotionPositionStep : 0;
// Moving down: Decreasing the current position by a step of 2000 units,
// ensuring it does not go below the target position.
nextPosition.SetNonNull(std::max(newCurrentPosition, targetPosition));
}
else
{
// Already at target: No further action is needed as the current position matches the target position.
nextPosition.SetNonNull(currentPosition);
return false; // No update needed
}
return true;
}
ClosureDimensionEndpoint * ClosureManager::GetCurrentPanelInstance(EndpointId endpointId)
{
ClosureManager & instance = ClosureManager::GetInstance();
if (endpointId == instance.mClosurePanelEndpoint2.GetEndpointId())
{
return &instance.mClosurePanelEndpoint2;
}
else if (endpointId == instance.mClosurePanelEndpoint3.GetEndpointId())
{
return &instance.mClosurePanelEndpoint3;
}
return nullptr;
}
void ClosureManager::HandleCalibrateActionComplete()
{
ChipLogProgress(AppServer, "HandleCalibrateActionComplete called");
mClosureEndpoint1.OnCalibrateActionComplete();
mClosurePanelEndpoint2.OnCalibrateActionComplete();
mClosurePanelEndpoint3.OnCalibrateActionComplete();
mIsCalibrationActionInProgress = false;
mEp1CurrentAction = ClosureAction::kInvalidAction;
}
void ClosureManager::HandleStopActionComplete()
{
ChipLogProgress(AppServer, "HandleStopActionComplete called");
if (mIsCalibrationActionInProgress)
{
ChipLogDetail(AppServer, "Stopping calibration action");
mClosureEndpoint1.OnStopCalibrateActionComplete();
mClosurePanelEndpoint2.OnStopCalibrateActionComplete();
mClosurePanelEndpoint3.OnStopCalibrateActionComplete();
mIsCalibrationActionInProgress = false;
}
else if (mEp1MotionInProgress)
{
ChipLogDetail(AppServer, "Stopping move to action");
mClosureEndpoint1.OnStopMotionActionComplete();
mClosurePanelEndpoint2.OnStopMotionActionComplete();
mClosurePanelEndpoint3.OnStopMotionActionComplete();
mEp1MotionInProgress = false;
}
else
{
ChipLogDetail(AppServer, "No action in progress to stop");
}
}
void ClosureManager::HandleMoveToActionComplete()
{
ChipLogProgress(AppServer, "HandleMoveToActionComplete called");
mClosureEndpoint1.OnMoveToActionComplete();
mClosurePanelEndpoint2.OnMoveToActionComplete();
mClosurePanelEndpoint3.OnMoveToActionComplete();
mEp1MotionInProgress = false;
mEp1CurrentAction = ClosureAction::kInvalidAction;
}
void ClosureManager::HandlePanelSetTargetActionComplete(chip::EndpointId endpointId)
{
ChipLogProgress(AppServer, "HandleSetTargetActionComplete called");
ClosureManager & instance = ClosureManager::GetInstance();
instance.mClosureEndpoint1.OnPanelMotionActionComplete();
if (endpointId == instance.mClosurePanelEndpoint2.GetEndpointId())
{
instance.mClosurePanelEndpoint2.OnPanelMotionActionComplete();
instance.mEp2CurrentAction = ClosureAction::kInvalidAction;
instance.mEp2MotionInProgress = false;
}
else if (endpointId == instance.mClosurePanelEndpoint3.GetEndpointId())
{
instance.mClosurePanelEndpoint3.OnPanelMotionActionComplete();
instance.mEp3CurrentAction = ClosureAction::kInvalidAction;
instance.mEp3MotionInProgress = false;
}
else
{
ChipLogError(AppServer, "Invalid endpoint ID for SetTarget command: %d", endpointId);
return;
}
ChipLogProgress(AppServer, "SetTarget action completed for Endpoint %d", endpointId);
}
void ClosureManager::HandlePanelStepActionComplete(chip::EndpointId endpointId)
{
ChipLogProgress(AppServer, "HandleStepActionComplete called");
ClosureManager & instance = ClosureManager::GetInstance();
instance.mClosureEndpoint1.OnPanelMotionActionComplete();
if (endpointId == instance.mClosurePanelEndpoint2.GetEndpointId())
{
instance.mClosurePanelEndpoint2.OnPanelMotionActionComplete();
instance.mEp2CurrentAction = ClosureAction::kInvalidAction;
instance.mEp2MotionInProgress = false;
}
else if (endpointId == instance.mClosurePanelEndpoint3.GetEndpointId())
{
instance.mClosurePanelEndpoint3.OnPanelMotionActionComplete();
instance.mEp3CurrentAction = ClosureAction::kInvalidAction;
instance.mEp3MotionInProgress = false;
}
else
{
ChipLogError(AppServer, "Invalid endpoint ID for Step command: %d", endpointId);
return;
}
ChipLogProgress(AppServer, "Step action completed for Endpoint %d", endpointId);
}