| /* |
| * |
| * 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 "AppConfig.h" |
| #include "AppTask.h" |
| #include "ClosureControlEndpoint.h" |
| #include "ClosureDimensionEndpoint.h" |
| |
| #include <app-common/zap-generated/cluster-objects.h> |
| #include <app/server/Server.h> |
| #include <app/util/attribute-storage.h> |
| #include <lib/support/TimeUtils.h> |
| #include <platform/CHIPDeviceLayer.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 { |
| constexpr uint32_t kDefaultCountdownTimeSeconds = 10; // 10 seconds |
| constexpr uint32_t kCalibrateCountdownTimeMs = 3000; // 3 seconds |
| 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 kEndpoint1TagList[] = { |
| { .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 kEndpoint2TagList[] = { |
| { .namespaceID = kNamespaceClosurePanel, |
| .tag = kTagClosurePanelLift, |
| .label = chip::MakeOptional(DataModel::Nullable<chip::CharSpan>("ClosurePanel.Lift"_span)) }, |
| }; |
| |
| const Clusters::Descriptor::Structs::SemanticTagStruct::Type kEndpoint3TagList[] = { |
| { .namespaceID = kNamespaceClosurePanel, |
| .tag = kTagClosurePanelTilt, |
| .label = chip::MakeOptional(DataModel::Nullable<chip::CharSpan>("ClosurePanel.Tilt"_span)) }, |
| }; |
| |
| } // namespace |
| |
| ClosureManager ClosureManager::sClosureMgr; |
| |
| void ClosureManager::Init() |
| { |
| // Create cmsis os sw timer for light timer. |
| mClosureTimer = osTimerNew(TimerEventHandler, // timer callback handler |
| osTimerOnce, // no timer reload (one-shot timer) |
| (void *) this, // pass the app task obj context |
| NULL // No osTimerAttr_t to provide. |
| ); |
| |
| if (mClosureTimer == NULL) |
| { |
| ChipLogError(AppServer, "mClosureTimer timer create failed"); |
| appError(APP_ERROR_CREATE_TIMER_FAILED); |
| } |
| DeviceLayer::PlatformMgr().LockChipStack(); |
| |
| // Closure endpoints initialization |
| mClosureEndpoint1.Init(); |
| mClosurePanelEndpoint2.Init(); |
| mClosurePanelEndpoint3.Init(); |
| |
| // Set Taglist for Closure endpoints |
| SetTagList(/* endpoint= */ 1, Span<const Clusters::Descriptor::Structs::SemanticTagStruct::Type>(kEndpoint1TagList)); |
| SetTagList(/* endpoint= */ 2, Span<const Clusters::Descriptor::Structs::SemanticTagStruct::Type>(kEndpoint2TagList)); |
| SetTagList(/* endpoint= */ 3, Span<const Clusters::Descriptor::Structs::SemanticTagStruct::Type>(kEndpoint3TagList)); |
| |
| // 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"); |
| } |
| |
| DeviceLayer::PlatformMgr().UnlockChipStack(); |
| } |
| |
| CHIP_ERROR ClosureManager::SetClosureControlInitialState(ClosureControlEndpoint & closureControlEndpoint) |
| { |
| ChipLogProgress(AppServer, "ClosureControlEndpoint SetInitialState"); |
| ReturnErrorOnFailure(closureControlEndpoint.GetLogic().SetCountdownTimeFromDelegate(NullNullable)); |
| ReturnErrorOnFailure(closureControlEndpoint.GetLogic().SetMainState(MainStateEnum::kStopped)); |
| |
| DataModel::Nullable<GenericOverallCurrentState> overallState(GenericOverallCurrentState( |
| MakeOptional(DataModel::MakeNullable(CurrentPositionEnum::kFullyClosed)), MakeOptional(DataModel::MakeNullable(true)), |
| MakeOptional(Globals::ThreeLevelAutoEnum::kAuto), DataModel::MakeNullable(true))); |
| ReturnErrorOnFailure(closureControlEndpoint.GetLogic().SetOverallCurrentState(overallState)); |
| DataModel::Nullable<GenericOverallTargetState> overallTarget( |
| GenericOverallTargetState(MakeOptional(DataModel::NullNullable), MakeOptional(DataModel::NullNullable), |
| MakeOptional(Globals::ThreeLevelAutoEnum::kAuto))); |
| ReturnErrorOnFailure(closureControlEndpoint.GetLogic().SetOverallTargetState(overallTarget)); |
| 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"); |
| DataModel::Nullable<GenericDimensionStateStruct> currentState( |
| GenericDimensionStateStruct(MakeOptional(DataModel::MakeNullable<Percent100ths>(10000)), |
| MakeOptional(DataModel::MakeNullable(true)), MakeOptional(Globals::ThreeLevelAutoEnum::kAuto))); |
| ReturnErrorOnFailure(closurePanelEndpoint.GetLogic().SetCurrentState(currentState)); |
| |
| DataModel::Nullable<GenericDimensionStateStruct> targetState( |
| GenericDimensionStateStruct(MakeOptional(DataModel::NullNullable), MakeOptional(DataModel::NullNullable), |
| MakeOptional(Globals::ThreeLevelAutoEnum::kAuto))); |
| ReturnErrorOnFailure(closurePanelEndpoint.GetLogic().SetTargetState(targetState)); |
| |
| ReturnErrorOnFailure(closurePanelEndpoint.GetLogic().SetResolution(Percent100ths(100))); |
| ReturnErrorOnFailure(closurePanelEndpoint.GetLogic().SetStepValue(2000)); |
| 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) })); |
| ReturnErrorOnFailure(closurePanelEndpoint.GetLogic().SetOverflow(OverflowEnum::kTopInside)); |
| |
| ClosureDimension::Structs::RangePercent100thsStruct::Type limitRange{ .min = static_cast<Percent100ths>(0), |
| .max = static_cast<Percent100ths>(10000) }; |
| ReturnErrorOnFailure(closurePanelEndpoint.GetLogic().SetLimitRange(limitRange)); |
| BitFlags<ClosureDimension::LatchControlModesBitmap> latchControlModes; |
| latchControlModes.Set(ClosureDimension::LatchControlModesBitmap::kRemoteLatching) |
| .Set(ClosureDimension::LatchControlModesBitmap::kRemoteUnlatching); |
| ReturnErrorOnFailure(closurePanelEndpoint.GetLogic().SetLatchControlModes(latchControlModes)); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| void ClosureManager::StartTimer(uint32_t aTimeoutMs) |
| { |
| // Starts or restarts the function timer |
| if (osTimerStart(mClosureTimer, pdMS_TO_TICKS(aTimeoutMs)) != osOK) |
| { |
| ChipLogError(AppServer, "mClosureTimer timer start() failed"); |
| appError(APP_ERROR_START_TIMER_FAILED); |
| } |
| } |
| |
| void ClosureManager::CancelTimer() |
| { |
| if (osTimerStop(mClosureTimer) == osError) |
| { |
| ChipLogError(AppServer, "mClosureTimer stop() failed"); |
| appError(APP_ERROR_STOP_TIMER_FAILED); |
| } |
| } |
| |
| void ClosureManager::InitiateAction(AppEvent * event) |
| { |
| Action_t eventAction = static_cast<Action_t>(event->ClosureEvent.Action); |
| |
| ClosureManager & instance = ClosureManager::GetInstance(); |
| |
| // We should not receive an event for a different action while another action is ongoing. |
| // But due to asynchronous processing of commands and synchronous processing of the stop command, |
| // this can happen if stop is received after InitaiteAction event is posted. |
| // This is a safety check to ensure that we do not initiate a new action while another action is in progress. |
| // If this happens, we log an error and do not proceed with initiating the action. |
| VerifyOrReturn(eventAction == instance.GetCurrentAction(), |
| ChipLogError(AppServer, "Got Event for %d in InitiateAction while current ongoing action is %d", |
| to_underlying(eventAction), to_underlying(instance.GetCurrentAction()))); |
| |
| instance.CancelTimer(); // Cancel any existing timer before starting a new action |
| |
| switch (eventAction) |
| { |
| case Action_t::CALIBRATE_ACTION: |
| ChipLogDetail(AppServer, "Initiating calibration action"); |
| // Timer used in sample application to simulate the calibration process. |
| // In a real application, this would be replaced with actual calibration logic. |
| instance.StartTimer(kCalibrateCountdownTimeMs); |
| break; |
| case Action_t::MOVE_TO_ACTION: |
| ChipLogDetail(AppServer, "Initiating move to action"); |
| // For Closure sample app, Motion action is simulated with a timer with rate |
| // of 10% change of position per second. |
| // In a real application, this would be replaced with actual move to logic. |
| instance.StartTimer(kMotionCountdownTimeMs); |
| break; |
| case Action_t::UNLATCH_ACTION: |
| ChipLogDetail(AppServer, "Initiating unlatch action"); |
| // Unlatch action check is a prerequisite for the move to action. |
| // In a real application, this would be replaced with actual unlatch logic. |
| instance.StartTimer(kMotionCountdownTimeMs); |
| break; |
| case Action_t::SET_TARGET_ACTION: |
| ChipLogDetail(AppServer, "Initiating set target action"); |
| // Timer used in sample application to simulate the set target process. |
| // In a real application, this would be replaced with actual logic to set |
| // the target position of the closure. |
| instance.StartTimer(kMotionCountdownTimeMs); |
| break; |
| case Action_t::PANEL_UNLATCH_ACTION: |
| ChipLogDetail(AppServer, "Initiating panel unlatch action"); |
| // Timer used in sample application to simulate the panel unlatch process. |
| // In a real application, this would be replaced with actual logic to unlatch |
| // the closure panel |
| instance.StartTimer(kMotionCountdownTimeMs); |
| break; |
| case Action_t::PANEL_STEP_ACTION: |
| ChipLogDetail(AppServer, "Initiating step action"); |
| // Timer used in sample application to simulate the step action process. |
| // In a real application, this would be replaced with actual logic to perform |
| // a step action on the closure. |
| instance.StartTimer(kMotionCountdownTimeMs); |
| break; |
| default: |
| ChipLogDetail(AppServer, "Invalid action received in InitiateAction"); |
| return; |
| } |
| } |
| |
| void ClosureManager::TimerEventHandler(void * timerCbArg) |
| { |
| ClosureManager * closureManager = static_cast<ClosureManager *>(timerCbArg); |
| |
| // The timer event handler will be called in the context of the timer task |
| // once sClosureTimer expires. Post an event to apptask queue with the actual handler |
| // so that the event can be handled in the context of the apptask. |
| AppEvent event; |
| event.Type = AppEvent::kEventType_Closure; |
| event.ClosureEvent.Action = closureManager->GetCurrentAction(); |
| event.ClosureEvent.EndpointId = closureManager->mCurrentActionEndpointId; |
| event.Handler = HandleClosureActionCompleteEvent; |
| AppTask::GetAppTask().PostEvent(&event); |
| } |
| |
| void ClosureManager::HandleClosureActionCompleteEvent(AppEvent * event) |
| { |
| Action_t currentAction = static_cast<Action_t>(event->ClosureEvent.Action); |
| |
| ClosureManager & instance = ClosureManager::GetInstance(); |
| |
| // We should not receive an event for a different action while another action is ongoing. |
| // But due to asynchronous processing of commands and synchronous processing of the stop command, |
| // this can happen if stop is received after InitaiteAction event is posted. |
| // This is a safety check to ensure that we do not initiate a new action while another action is in progress. |
| // If this happens, we log an error and do not proceed with initiating the action. |
| VerifyOrReturn(currentAction == instance.GetCurrentAction(), |
| ChipLogError(AppServer, "Got Event for %d in InitiateAction while current ongoing action is %d", |
| to_underlying(currentAction), to_underlying(instance.GetCurrentAction()))); |
| |
| switch (currentAction) |
| { |
| case Action_t::CALIBRATE_ACTION: |
| PlatformMgr().ScheduleWork([](intptr_t) { |
| ClosureManager & instance = ClosureManager::GetInstance(); |
| instance.HandleClosureActionComplete(instance.GetCurrentAction()); |
| }); |
| break; |
| case Action_t::MOVE_TO_ACTION: |
| PlatformMgr().ScheduleWork([](intptr_t) { ClosureManager::GetInstance().HandleClosureMotionAction(); }); |
| break; |
| case Action_t::UNLATCH_ACTION: |
| PlatformMgr().ScheduleWork([](intptr_t) { ClosureManager::GetInstance().HandleClosureUnlatchAction(); }); |
| break; |
| case Action_t::SET_TARGET_ACTION: |
| PlatformMgr().ScheduleWork([](intptr_t) { |
| ClosureManager & instance = ClosureManager::GetInstance(); |
| instance.HandlePanelSetTargetAction(instance.mCurrentActionEndpointId); |
| }); |
| break; |
| case Action_t::PANEL_UNLATCH_ACTION: |
| PlatformMgr().ScheduleWork([](intptr_t) { |
| ClosureManager & instance = ClosureManager::GetInstance(); |
| instance.HandlePanelUnlatchAction(instance.mCurrentActionEndpointId); |
| }); |
| break; |
| case Action_t::PANEL_STEP_ACTION: |
| PlatformMgr().ScheduleWork([](intptr_t) { |
| ClosureManager & instance = ClosureManager::GetInstance(); |
| instance.HandlePanelStepAction(instance.mCurrentActionEndpointId); |
| }); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void ClosureManager::HandleClosureActionComplete(Action_t action) |
| { |
| ClosureManager & instance = ClosureManager::GetInstance(); |
| |
| switch (action) |
| { |
| case Action_t::CALIBRATE_ACTION: { |
| instance.mClosureEndpoint1.OnCalibrateActionComplete(); |
| instance.mClosurePanelEndpoint2.OnCalibrateActionComplete(); |
| instance.mClosurePanelEndpoint3.OnCalibrateActionComplete(); |
| |
| DeviceLayer::PlatformMgr().LockChipStack(); |
| isCalibrationInProgress = false; |
| DeviceLayer::PlatformMgr().UnlockChipStack(); |
| |
| break; |
| } |
| case Action_t::STOP_ACTION: { |
| if (isCalibrationInProgress) |
| { |
| ChipLogDetail(AppServer, "Stopping calibration action"); |
| instance.mClosureEndpoint1.OnStopCalibrateActionComplete(); |
| instance.mClosurePanelEndpoint2.OnStopCalibrateActionComplete(); |
| instance.mClosurePanelEndpoint3.OnStopCalibrateActionComplete(); |
| |
| DeviceLayer::PlatformMgr().LockChipStack(); |
| isCalibrationInProgress = false; |
| DeviceLayer::PlatformMgr().UnlockChipStack(); |
| } |
| else if (isMoveToInProgress) |
| { |
| ChipLogDetail(AppServer, "Stopping move to action"); |
| instance.mClosureEndpoint1.OnStopMotionActionComplete(); |
| instance.mClosurePanelEndpoint2.OnStopMotionActionComplete(); |
| instance.mClosurePanelEndpoint3.OnStopMotionActionComplete(); |
| |
| DeviceLayer::PlatformMgr().LockChipStack(); |
| isMoveToInProgress = false; |
| DeviceLayer::PlatformMgr().UnlockChipStack(); |
| } |
| else |
| { |
| ChipLogDetail(AppServer, "No action in progress to stop"); |
| } |
| break; |
| } |
| case Action_t::MOVE_TO_ACTION: |
| instance.mClosureEndpoint1.OnMoveToActionComplete(); |
| instance.mClosurePanelEndpoint2.OnMoveToActionComplete(); |
| instance.mClosurePanelEndpoint3.OnMoveToActionComplete(); |
| |
| DeviceLayer::PlatformMgr().LockChipStack(); |
| instance.isMoveToInProgress = false; |
| DeviceLayer::PlatformMgr().UnlockChipStack(); |
| |
| break; |
| case Action_t::SET_TARGET_ACTION: |
| instance.mClosureEndpoint1.OnPanelMotionActionComplete(); |
| if (instance.mCurrentActionEndpointId == instance.mClosurePanelEndpoint2.GetEndpointId()) |
| { |
| instance.mClosurePanelEndpoint2.OnPanelMotionActionComplete(); |
| } |
| else if (instance.mCurrentActionEndpointId == instance.mClosurePanelEndpoint3.GetEndpointId()) |
| { |
| instance.mClosurePanelEndpoint3.OnPanelMotionActionComplete(); |
| } |
| |
| DeviceLayer::PlatformMgr().LockChipStack(); |
| instance.isSetTargetInProgress = false; |
| DeviceLayer::PlatformMgr().UnlockChipStack(); |
| break; |
| case Action_t::PANEL_STEP_ACTION: |
| instance.mClosureEndpoint1.OnPanelMotionActionComplete(); |
| if (instance.mCurrentActionEndpointId == instance.mClosurePanelEndpoint2.GetEndpointId()) |
| { |
| instance.mClosurePanelEndpoint2.OnPanelMotionActionComplete(); |
| } |
| else if (instance.mCurrentActionEndpointId == instance.mClosurePanelEndpoint3.GetEndpointId()) |
| { |
| instance.mClosurePanelEndpoint3.OnPanelMotionActionComplete(); |
| } |
| |
| DeviceLayer::PlatformMgr().LockChipStack(); |
| instance.isStepActionInProgress = false; |
| DeviceLayer::PlatformMgr().UnlockChipStack(); |
| break; |
| default: |
| ChipLogError(AppServer, "Invalid action received in HandleClosureAction"); |
| break; |
| } |
| |
| DeviceLayer::PlatformMgr().LockChipStack(); |
| // Reset the current action and current action endpoint ID after handling the closure action |
| instance.SetCurrentAction(Action_t::INVALID_ACTION); |
| instance.mCurrentActionEndpointId = chip::kInvalidEndpointId; |
| DeviceLayer::PlatformMgr().UnlockChipStack(); |
| } |
| |
| chip::Protocols::InteractionModel::Status ClosureManager::OnCalibrateCommand() |
| { |
| VerifyOrReturnValue(mClosureEndpoint1.GetLogic().SetCountdownTimeFromDelegate(kDefaultCountdownTimeSeconds) == CHIP_NO_ERROR, |
| Status::Failure, ChipLogError(AppServer, "Failed to set countdown time for calibration")); |
| |
| DeviceLayer::PlatformMgr().LockChipStack(); |
| SetCurrentAction(Action_t::CALIBRATE_ACTION); |
| mCurrentActionEndpointId = mClosureEndpoint1.GetEndpointId(); |
| isCalibrationInProgress = true; |
| DeviceLayer::PlatformMgr().UnlockChipStack(); |
| |
| // Post an event to initiate the calibration action asynchronously. |
| // Calibration can be only initiated from Closure Endpoint 1, so we set the endpoint ID to mClosureEndpoint1. |
| AppEvent event; |
| event.Type = AppEvent::kEventType_Closure; |
| event.ClosureEvent.Action = GetCurrentAction(); |
| event.ClosureEvent.EndpointId = mCurrentActionEndpointId; |
| event.Handler = InitiateAction; |
| AppTask::GetAppTask().PostEvent(&event); |
| |
| return Status::Success; |
| } |
| |
| chip::Protocols::InteractionModel::Status ClosureManager::OnStopCommand() |
| { |
| // As Stop should be handled immediately, we will handle it synchronously. |
| // For simulation purposes, we will just log the stop action and contnue to handle the stop action completion. |
| // In a real application, this would be replaced with actual logic to stop the closure action. |
| ChipLogDetail(AppServer, "Handling Stop command for closure action"); |
| |
| CancelTimer(); |
| |
| // Stop can be only initiated from Closure Endpoint 1, so we set the endpoint ID to mClosureEndpoint1. |
| DeviceLayer::PlatformMgr().LockChipStack(); |
| SetCurrentAction(Action_t::STOP_ACTION); |
| mCurrentActionEndpointId = mClosureEndpoint1.GetEndpointId(); |
| DeviceLayer::PlatformMgr().UnlockChipStack(); |
| |
| HandleClosureActionComplete(Action_t::STOP_ACTION); |
| |
| return Status::Success; |
| } |
| |
| chip::Protocols::InteractionModel::Status ClosureManager::OnMoveToCommand(const Optional<TargetPositionEnum> position, |
| const Optional<bool> latch, |
| const Optional<Globals::ThreeLevelAutoEnum> speed) |
| { |
| |
| // 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> mClosurePanelEndpoint2CurrentState; |
| VerifyOrReturnError(mClosurePanelEndpoint2.GetLogic().GetCurrentState(mClosurePanelEndpoint2CurrentState) == CHIP_NO_ERROR, |
| Status::Failure, ChipLogError(AppServer, "Failed to get current state for Endpoint 2")); |
| DataModel::Nullable<GenericDimensionStateStruct> mClosurePanelEndpoint3CurrentState; |
| VerifyOrReturnError(mClosurePanelEndpoint3.GetLogic().GetCurrentState(mClosurePanelEndpoint3CurrentState) == CHIP_NO_ERROR, |
| Status::Failure, ChipLogError(AppServer, "Failed to get current state for Endpoint 3")); |
| |
| DataModel::Nullable<GenericDimensionStateStruct> mClosurePanelEndpoint2TargetState; |
| VerifyOrReturnError(mClosurePanelEndpoint2.GetLogic().GetTargetState(mClosurePanelEndpoint2TargetState) == CHIP_NO_ERROR, |
| Status::Failure, ChipLogError(AppServer, "Failed to get target state for Endpoint 2")); |
| DataModel::Nullable<GenericDimensionStateStruct> mClosurePanelEndpoint3TargetState; |
| VerifyOrReturnError(mClosurePanelEndpoint3.GetLogic().GetTargetState(mClosurePanelEndpoint3TargetState) == CHIP_NO_ERROR, |
| Status::Failure, ChipLogError(AppServer, "Failed to get target state for Endpoint 3")); |
| |
| VerifyOrReturnError(!mClosurePanelEndpoint2CurrentState.IsNull(), Status::Failure, |
| ChipLogError(AppServer, "MoveToCommand failed due to Null value Current state on Endpoint 2")); |
| VerifyOrReturnError(!mClosurePanelEndpoint3CurrentState.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 mClosurePanelEndpoint2Target = |
| mClosurePanelEndpoint2TargetState.IsNull() ? GenericDimensionStateStruct() : mClosurePanelEndpoint2TargetState.Value(); |
| GenericDimensionStateStruct mClosurePanelEndpoint3Target = |
| mClosurePanelEndpoint3TargetState.IsNull() ? GenericDimensionStateStruct() : mClosurePanelEndpoint3TargetState.Value(); |
| |
| 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. |
| Percent100ths mClosurePanelEndpoint2Position; |
| Percent100ths mClosurePanelEndpoint3Position; |
| |
| switch (position.Value()) |
| { |
| case TargetPositionEnum::kMoveToFullyClosed: |
| mClosurePanelEndpoint2Position = static_cast<Percent100ths>(10000); |
| mClosurePanelEndpoint3Position = static_cast<Percent100ths>(10000); |
| break; |
| case TargetPositionEnum::kMoveToFullyOpen: |
| mClosurePanelEndpoint2Position = static_cast<Percent100ths>(0); |
| mClosurePanelEndpoint3Position = static_cast<Percent100ths>(0); |
| break; |
| case TargetPositionEnum::kMoveToPedestrianPosition: |
| mClosurePanelEndpoint2Position = static_cast<Percent100ths>(6000); |
| mClosurePanelEndpoint3Position = static_cast<Percent100ths>(6000); |
| break; |
| case TargetPositionEnum::kMoveToSignaturePosition: |
| mClosurePanelEndpoint2Position = static_cast<Percent100ths>(4000); |
| mClosurePanelEndpoint3Position = static_cast<Percent100ths>(4000); |
| break; |
| case TargetPositionEnum::kMoveToVentilationPosition: |
| mClosurePanelEndpoint2Position = static_cast<Percent100ths>(2000); |
| mClosurePanelEndpoint3Position = static_cast<Percent100ths>(2000); |
| break; |
| default: |
| ChipLogError(AppServer, "Invalid target position received in OnMoveToCommand"); |
| return Status::Failure; |
| } |
| |
| mClosurePanelEndpoint2Target.position.SetValue(DataModel::MakeNullable(mClosurePanelEndpoint2Position)); |
| mClosurePanelEndpoint3Target.position.SetValue(DataModel::MakeNullable(mClosurePanelEndpoint3Position)); |
| } |
| |
| if (latch.HasValue()) |
| { |
| mClosurePanelEndpoint2Target.latch.SetValue(DataModel::MakeNullable(latch.Value())); |
| mClosurePanelEndpoint3Target.latch.SetValue(DataModel::MakeNullable(latch.Value())); |
| } |
| |
| if (speed.HasValue()) |
| { |
| mClosurePanelEndpoint2Target.speed.SetValue(speed.Value()); |
| mClosurePanelEndpoint3Target.speed.SetValue(speed.Value()); |
| } |
| |
| VerifyOrReturnError(mClosurePanelEndpoint2.GetLogic().SetTargetState(DataModel::MakeNullable(mClosurePanelEndpoint2Target)) == |
| CHIP_NO_ERROR, |
| Status::Failure, ChipLogError(AppServer, "Failed to set target for Endpoint 2")); |
| VerifyOrReturnError(mClosurePanelEndpoint3.GetLogic().SetTargetState(DataModel::MakeNullable(mClosurePanelEndpoint3Target)) == |
| CHIP_NO_ERROR, |
| Status::Failure, ChipLogError(AppServer, "Failed to set target for Endpoint 3")); |
| |
| VerifyOrReturnError(mClosureEndpoint1.GetLogic().SetCountdownTimeFromDelegate(kDefaultCountdownTimeSeconds) == CHIP_NO_ERROR, |
| Status::Failure, ChipLogError(AppServer, "Failed to set countdown time for move to command on Endpoint 1")); |
| |
| // Set the current action to UNLATCH_ACTION. |
| // This is to ensure that the closure is unlatched before starting the motion action. |
| // The Closure Control Cluster will handle the unlatch action before proceeding with the motion action. |
| DeviceLayer::PlatformMgr().LockChipStack(); |
| SetCurrentAction(UNLATCH_ACTION); |
| isMoveToInProgress = true; |
| DeviceLayer::PlatformMgr().UnlockChipStack(); |
| |
| // Post an event to initiate the move to action asynchronously. |
| // MoveTo Command can only be initiated from Closure Control Endpoint (Endpoint 1). |
| AppEvent event; |
| event.Type = AppEvent::kEventType_Closure; |
| event.ClosureEvent.Action = mCurrentAction; |
| event.Handler = InitiateAction; |
| AppTask::GetAppTask().PostEvent(&event); |
| |
| return Status::Success; |
| } |
| |
| void ClosureManager::HandleClosureMotionAction() |
| { |
| ClosureManager & instance = ClosureManager::GetInstance(); |
| |
| DataModel::Nullable<GenericDimensionStateStruct> mClosurePanelEndpoint2CurrentState; |
| DataModel::Nullable<GenericDimensionStateStruct> mClosurePanelEndpoint3CurrentState; |
| |
| DataModel::Nullable<GenericDimensionStateStruct> mClosurePanelEndpoint2TargetState; |
| DataModel::Nullable<GenericDimensionStateStruct> mClosurePanelEndpoint3TargetState; |
| |
| VerifyOrReturn(mClosurePanelEndpoint2.GetLogic().GetCurrentState(mClosurePanelEndpoint2CurrentState) == CHIP_NO_ERROR, |
| ChipLogError(AppServer, "Failed to get current state for Endpoint 2")); |
| VerifyOrReturn(mClosurePanelEndpoint3.GetLogic().GetCurrentState(mClosurePanelEndpoint3CurrentState) == CHIP_NO_ERROR, |
| ChipLogError(AppServer, "Failed to get current state for Endpoint 3")); |
| VerifyOrReturn(mClosurePanelEndpoint2.GetLogic().GetTargetState(mClosurePanelEndpoint2TargetState) == CHIP_NO_ERROR, |
| ChipLogError(AppServer, "Failed to get target state for Endpoint 2")); |
| VerifyOrReturn(mClosurePanelEndpoint3.GetLogic().GetTargetState(mClosurePanelEndpoint3TargetState) == CHIP_NO_ERROR, |
| ChipLogError(AppServer, "Failed to get target state for Endpoint 3")); |
| |
| VerifyOrReturn(!mClosurePanelEndpoint2CurrentState.IsNull(), |
| ChipLogError(AppServer, "MoveToCommand failed due to Null value Current state on Endpoint 2")); |
| VerifyOrReturn(!mClosurePanelEndpoint3CurrentState.IsNull(), |
| ChipLogError(AppServer, "MoveToCommand failed due to Null value Current state on Endpoint 3")); |
| |
| VerifyOrReturn(!mClosurePanelEndpoint2TargetState.IsNull(), |
| ChipLogError(AppServer, "MoveToCommand failed due to Null value Target state on Endpoint 2")); |
| VerifyOrReturn(!mClosurePanelEndpoint3TargetState.IsNull(), |
| ChipLogError(AppServer, "MoveToCommand failed due to Null value Target state on Endpoint 3")); |
| |
| // Once Closure is unlatched, we can proceed with the motion action for endpoints 2 and 3. |
| DataModel::Nullable<Percent100ths> mClosurePanelEndpoint2NextPosition = DataModel::NullNullable; |
| DataModel::Nullable<Percent100ths> mClosurePanelEndpoint3NextPosition = 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(mClosurePanelEndpoint2CurrentState.Value(), mClosurePanelEndpoint2TargetState.Value(), |
| mClosurePanelEndpoint2NextPosition)) |
| { |
| VerifyOrReturn(!mClosurePanelEndpoint2NextPosition.IsNull(), |
| ChipLogError(AppServer, "Failed to get next position for Endpoint 2")); |
| mClosurePanelEndpoint2CurrentState.Value().position.SetValue( |
| DataModel::MakeNullable(mClosurePanelEndpoint2NextPosition.Value())); |
| instance.mClosurePanelEndpoint2.GetLogic().SetCurrentState(mClosurePanelEndpoint2CurrentState); |
| isEndPoint2ProgressPossible = |
| (mClosurePanelEndpoint2NextPosition.Value() != mClosurePanelEndpoint2TargetState.Value().position.Value().Value()); |
| ChipLogProgress(AppServer, "EndPoint 2 Current Position: %d, Target Position: %d", |
| mClosurePanelEndpoint2NextPosition.Value(), |
| mClosurePanelEndpoint2TargetState.Value().position.Value().Value()); |
| } |
| |
| // Get the Next Current State to be set for the endpoint 3, if target postion is not reached. |
| if (GetPanelNextPosition(mClosurePanelEndpoint3CurrentState.Value(), mClosurePanelEndpoint3TargetState.Value(), |
| mClosurePanelEndpoint3NextPosition)) |
| { |
| VerifyOrReturn(!mClosurePanelEndpoint3NextPosition.IsNull(), |
| ChipLogError(AppServer, "Failed to get next position for Endpoint 3")); |
| mClosurePanelEndpoint3CurrentState.Value().position.SetValue( |
| DataModel::MakeNullable(mClosurePanelEndpoint3NextPosition.Value())); |
| instance.mClosurePanelEndpoint3.GetLogic().SetCurrentState(mClosurePanelEndpoint3CurrentState); |
| isEndPoint3ProgressPossible = |
| (mClosurePanelEndpoint3NextPosition.Value() != mClosurePanelEndpoint3TargetState.Value().position.Value().Value()); |
| ChipLogProgress(AppServer, "EndPoint 3 Current Position: %d, Target Position: %d", |
| mClosurePanelEndpoint3NextPosition.Value(), |
| mClosurePanelEndpoint3TargetState.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) |
| { |
| instance.CancelTimer(); // Cancel any existing timer before starting a new action |
| instance.SetCurrentAction(MOVE_TO_ACTION); |
| instance.StartTimer(kMotionCountdownTimeMs); |
| return; |
| } |
| |
| DataModel::Nullable<GenericOverallCurrentState> mClosureEndpoint1CurrentState; |
| DataModel::Nullable<GenericOverallTargetState> mClosureEndpoint1TargetState; |
| |
| VerifyOrReturn(mClosureEndpoint1.GetLogic().GetOverallCurrentState(mClosureEndpoint1CurrentState) == CHIP_NO_ERROR, |
| ChipLogError(AppServer, "Failed to get current state for Endpoint 1")); |
| VerifyOrReturn(mClosureEndpoint1.GetLogic().GetOverallTargetState(mClosureEndpoint1TargetState) == CHIP_NO_ERROR, |
| ChipLogError(AppServer, "Failed to get target state for Endpoint 1")); |
| |
| VerifyOrReturn(!mClosureEndpoint1CurrentState.IsNull(), |
| ChipLogError(AppServer, "MoveToCommand failed due to Null value Current state on Endpoint 1")); |
| VerifyOrReturn(!mClosureEndpoint1TargetState.IsNull(), |
| ChipLogError(AppServer, "MoveToCommand failed due to Null value Target state on Endpoint 1")); |
| |
| // 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 (mClosureEndpoint1CurrentState.Value().latch.HasValue() && !mClosureEndpoint1CurrentState.Value().latch.Value().IsNull() && |
| mClosureEndpoint1TargetState.Value().latch.HasValue() && !mClosureEndpoint1TargetState.Value().latch.Value().IsNull()) |
| { |
| // If currently unlatched (false) and target is latched (true), latch after moving to target position. |
| if (!mClosureEndpoint1CurrentState.Value().latch.Value().Value() && |
| mClosureEndpoint1TargetState.Value().latch.Value().Value()) |
| { |
| // In Real application, this would be replaced with actual unlatch logic. |
| ChipLogProgress(AppServer, "Performing latch action"); |
| mClosureEndpoint1CurrentState.Value().latch.SetValue(DataModel::MakeNullable(true)); |
| instance.mClosureEndpoint1.GetLogic().SetOverallCurrentState(mClosureEndpoint1CurrentState); |
| mClosurePanelEndpoint2CurrentState.Value().latch.SetValue(DataModel::MakeNullable(true)); |
| instance.mClosurePanelEndpoint2.GetLogic().SetCurrentState(mClosurePanelEndpoint2CurrentState); |
| mClosurePanelEndpoint3CurrentState.Value().latch.SetValue(DataModel::MakeNullable(true)); |
| instance.mClosurePanelEndpoint3.GetLogic().SetCurrentState(mClosurePanelEndpoint3CurrentState); |
| ChipLogProgress(AppServer, "latched action complete"); |
| } |
| } |
| |
| // Target reached and no latch action needed, call HandleClosureAction |
| instance.HandleClosureActionComplete(ClosureManager::Action_t::MOVE_TO_ACTION); |
| } |
| |
| chip::Protocols::InteractionModel::Status ClosureManager::OnSetTargetCommand(const Optional<Percent100ths> & position, |
| const Optional<bool> & latch, |
| const Optional<Globals::ThreeLevelAutoEnum> & speed, |
| const chip::EndpointId endpointId) |
| { |
| MainStateEnum mClosureEndpoint1MainState; |
| VerifyOrReturnError(mClosureEndpoint1.GetLogic().GetMainState(mClosureEndpoint1MainState) == 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( |
| mClosureEndpoint1MainState != MainStateEnum::kDisengaged && mClosureEndpoint1MainState != MainStateEnum::kProtected && |
| mClosureEndpoint1MainState != MainStateEnum::kSetupRequired && mClosureEndpoint1MainState != MainStateEnum::kError && |
| mClosureEndpoint1MainState != MainStateEnum::kCalibrating, |
| Status::InvalidInState, |
| ChipLogError(AppServer, "Step command not allowed in current state: %d", static_cast<int>(mClosureEndpoint1MainState))); |
| |
| if (isSetTargetInProgress && mCurrentActionEndpointId != endpointId) |
| { |
| ChipLogError(AppServer, "SetTarget action is already in progress on Endpoint %d", mCurrentActionEndpointId); |
| return Status::Failure; |
| } |
| |
| // 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{}); |
| } |
| |
| if (position.HasValue()) |
| { |
| // Set overallTargetState position to NullOptional as panel position change cannot be represented in OverallTarget. |
| overallTargetState.Value().position.SetValue(DataModel::NullNullable); |
| } |
| |
| if (latch.HasValue()) |
| { |
| overallTargetState.Value().latch.SetValue(DataModel::MakeNullable(latch.Value())); |
| } |
| |
| if (speed.HasValue()) |
| { |
| overallTargetState.Value().speed.SetValue(speed.Value()); |
| } |
| |
| VerifyOrReturnError(mClosureEndpoint1.GetLogic().SetMainState(MainStateEnum::kMoving) == CHIP_NO_ERROR, Status::Failure, |
| ChipLogError(AppServer, "Failed to set main state while handling the SetTarget command on Endpoint 1")); |
| |
| VerifyOrReturnError( |
| mClosureEndpoint1.GetLogic().SetOverallTargetState(overallTargetState) == CHIP_NO_ERROR, Status::Failure, |
| ChipLogError(AppServer, "Failed to set overall target while handling the SetTarget command for Endpoint %d", endpointId)); |
| |
| VerifyOrReturnError( |
| mClosureEndpoint1.GetLogic().SetCountdownTimeFromDelegate(kDefaultCountdownTimeSeconds) == CHIP_NO_ERROR, Status::Failure, |
| ChipLogError(AppServer, "Failed to set countdown time while handling the SetTarget command for Endpoint %d", endpointId)); |
| |
| // 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 set the target position without being latched. |
| DeviceLayer::PlatformMgr().LockChipStack(); |
| SetCurrentAction(Action_t::PANEL_UNLATCH_ACTION); |
| mCurrentActionEndpointId = endpointId; |
| isSetTargetInProgress = true; |
| DeviceLayer::PlatformMgr().UnlockChipStack(); |
| |
| AppEvent event; |
| event.Type = AppEvent::kEventType_Closure; |
| event.ClosureEvent.Action = mCurrentAction; |
| event.ClosureEvent.EndpointId = endpointId; |
| event.Handler = InitiateAction; |
| |
| AppTask::GetAppTask().PostEvent(&event); |
| |
| return Status::Success; |
| } |
| |
| void ClosureManager::HandlePanelSetTargetAction(EndpointId endpointId) |
| { |
| ClosureManager & instance = ClosureManager::GetInstance(); |
| |
| // Get the endpoint based on the endpointId |
| ClosureDimension::ClosureDimensionEndpoint * panelEp = instance.GetPanelEndpointById(endpointId); |
| VerifyOrReturn(panelEp != nullptr, ChipLogError(AppServer, "Invalid instance for 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)); |
| |
| 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())); |
| panelEp->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) |
| { |
| instance.CancelTimer(); // Cancel any existing timer before starting a new action |
| |
| DeviceLayer::PlatformMgr().LockChipStack(); |
| instance.SetCurrentAction(Action_t::SET_TARGET_ACTION); |
| instance.mCurrentActionEndpointId = endpointId; |
| DeviceLayer::PlatformMgr().UnlockChipStack(); |
| |
| instance.StartTimer(kMotionCountdownTimeMs); |
| return; |
| } |
| |
| // If currently unlatched (false) and target is latched (true), latch after completing motion |
| 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> mClosureEndpoint1OverallCurrentState = DataModel::NullNullable; |
| VerifyOrReturn(mClosureEndpoint1.GetLogic().GetOverallCurrentState(mClosureEndpoint1OverallCurrentState) == |
| CHIP_NO_ERROR, |
| ChipLogError(AppServer, "Failed to get overall current state for Endpoint 1")); |
| VerifyOrReturn(!mClosureEndpoint1OverallCurrentState.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"); |
| |
| mClosureEndpoint1OverallCurrentState.Value().latch.SetValue(DataModel::MakeNullable(true)); |
| mClosureEndpoint1.GetLogic().SetOverallCurrentState(mClosureEndpoint1OverallCurrentState); |
| |
| panelCurrentState.Value().latch.SetValue(DataModel::MakeNullable(true)); |
| panelEp->GetLogic().SetCurrentState(panelCurrentState); |
| |
| ChipLogProgress(AppServer, "Latch action completed"); |
| } |
| } |
| |
| instance.HandleClosureActionComplete(Action_t::SET_TARGET_ACTION); |
| } |
| |
| void ClosureManager::HandleClosureUnlatchAction() |
| { |
| ClosureManager & instance = ClosureManager::GetInstance(); |
| |
| DataModel::Nullable<GenericOverallCurrentState> mClosureEndpoint1CurrentState; |
| DataModel::Nullable<GenericOverallTargetState> mClosureEndpoint1TargetState; |
| DataModel::Nullable<GenericDimensionStateStruct> mClosurePanelEndpoint2CurrentState; |
| DataModel::Nullable<GenericDimensionStateStruct> mClosurePanelEndpoint3CurrentState; |
| |
| VerifyOrReturn(mClosureEndpoint1.GetLogic().GetOverallCurrentState(mClosureEndpoint1CurrentState) == CHIP_NO_ERROR, |
| ChipLogError(AppServer, "Failed to get current state for Endpoint 1")); |
| VerifyOrReturn(mClosureEndpoint1.GetLogic().GetOverallTargetState(mClosureEndpoint1TargetState) == CHIP_NO_ERROR, |
| ChipLogError(AppServer, "Failed to get target state for Endpoint 1")); |
| |
| VerifyOrReturn(mClosurePanelEndpoint2.GetLogic().GetCurrentState(mClosurePanelEndpoint2CurrentState) == CHIP_NO_ERROR, |
| ChipLogError(AppServer, "Failed to get current state for Endpoint 2")); |
| VerifyOrReturn(mClosurePanelEndpoint3.GetLogic().GetCurrentState(mClosurePanelEndpoint3CurrentState) == CHIP_NO_ERROR, |
| ChipLogError(AppServer, "Failed to get current state for Endpoint 3")); |
| |
| VerifyOrReturn(!mClosureEndpoint1CurrentState.IsNull(), |
| ChipLogError(AppServer, "UnlatchAction failed due to Null value Current state on Endpoint 1")); |
| VerifyOrReturn(!mClosureEndpoint1TargetState.IsNull(), |
| ChipLogError(AppServer, "UnlatchAction failed due to Null value Target state on Endpoint 1")); |
| |
| VerifyOrReturn(!mClosurePanelEndpoint2CurrentState.IsNull(), |
| ChipLogError(AppServer, "UnlatchAction failed due to Null value Current state on Endpoint 2")); |
| VerifyOrReturn(!mClosurePanelEndpoint3CurrentState.IsNull(), |
| ChipLogError(AppServer, "UnlatchAction failed due to Null value Current state on Endpoint 3")); |
| |
| // check if closure (endpoint 1) need unlatch before starting the motion action. |
| if (mClosureEndpoint1CurrentState.Value().latch.HasValue() && !mClosureEndpoint1CurrentState.Value().latch.Value().IsNull() && |
| mClosureEndpoint1TargetState.Value().latch.HasValue() && !mClosureEndpoint1TargetState.Value().latch.Value().IsNull()) |
| { |
| bool mClosureEndpoint1CurrentLatchValue = mClosureEndpoint1CurrentState.Value().latch.Value().Value(); |
| bool mClosurePanelEndpoint2CurrentLatchValue = mClosurePanelEndpoint2CurrentState.Value().latch.HasValue() && |
| !mClosurePanelEndpoint2CurrentState.Value().latch.Value().IsNull() && |
| mClosurePanelEndpoint2CurrentState.Value().latch.Value().Value(); |
| bool mClosurePanelEndpoint3CurrentLatchValue = mClosurePanelEndpoint3CurrentState.Value().latch.HasValue() && |
| !mClosurePanelEndpoint3CurrentState.Value().latch.Value().IsNull() && |
| mClosurePanelEndpoint3CurrentState.Value().latch.Value().Value(); |
| |
| // If currently Closure or any panel is latched (true) and target is unlatched (false), unlatch first before moving |
| if ((mClosureEndpoint1CurrentLatchValue || mClosurePanelEndpoint2CurrentLatchValue || |
| mClosurePanelEndpoint3CurrentLatchValue) && |
| !mClosureEndpoint1TargetState.Value().latch.Value().Value()) |
| { |
| // In Real application, this would be replaced with actual unlatch logic. |
| ChipLogProgress(AppServer, "Performing unlatch action"); |
| mClosureEndpoint1CurrentState.Value().latch.SetValue(DataModel::MakeNullable(false)); |
| instance.mClosureEndpoint1.GetLogic().SetOverallCurrentState(mClosureEndpoint1CurrentState); |
| mClosurePanelEndpoint2CurrentState.Value().latch.SetValue(DataModel::MakeNullable(false)); |
| instance.mClosurePanelEndpoint2.GetLogic().SetCurrentState(mClosurePanelEndpoint2CurrentState); |
| mClosurePanelEndpoint3CurrentState.Value().latch.SetValue(DataModel::MakeNullable(false)); |
| instance.mClosurePanelEndpoint3.GetLogic().SetCurrentState(mClosurePanelEndpoint3CurrentState); |
| ChipLogProgress(AppServer, "Unlatched action completed"); |
| } |
| } |
| |
| CancelTimer(); // Cancel any existing timer before proceeding with the motion action |
| |
| // After unlatching, we can proceed with the motion action |
| instance.HandleClosureMotionAction(); |
| } |
| |
| void ClosureManager::HandlePanelUnlatchAction(EndpointId endpointId) |
| { |
| ClosureManager & instance = ClosureManager::GetInstance(); |
| |
| // Get the endpoint based on the endpointId |
| ClosureDimension::ClosureDimensionEndpoint * panelEp = instance.GetPanelEndpointById(endpointId); |
| VerifyOrReturn(panelEp != nullptr, ChipLogError(AppServer, "Invalid instance for 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> mClosureEndpoint1OverallCurrentState = DataModel::NullNullable; |
| |
| VerifyOrReturn(mClosureEndpoint1.GetLogic().GetOverallCurrentState(mClosureEndpoint1OverallCurrentState) == CHIP_NO_ERROR, |
| ChipLogError(AppServer, "Failed to get current state for Endpoint 1")); |
| VerifyOrReturn(!mClosureEndpoint1OverallCurrentState.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"); |
| |
| mClosureEndpoint1OverallCurrentState.Value().latch.SetValue(DataModel::MakeNullable(false)); |
| mClosureEndpoint1.GetLogic().SetOverallCurrentState(mClosureEndpoint1OverallCurrentState); |
| |
| panelCurrentState.Value().latch.SetValue(false); |
| panelEp->GetLogic().SetCurrentState(panelCurrentState); |
| |
| ChipLogProgress(AppServer, "Unlatched action completed"); |
| } |
| |
| // Unlatch action completed, now proceed with the SetTarget action |
| instance.CancelTimer(); // Cancel any existing timer before starting a Set Target action |
| |
| // Call HandlePanelSetTargetAction to continue with the SetTarget action |
| instance.HandlePanelSetTargetAction(endpointId); |
| } |
| |
| chip::Protocols::InteractionModel::Status ClosureManager::OnStepCommand(const StepDirectionEnum & direction, |
| const uint16_t & numberOfSteps, |
| const Optional<Globals::ThreeLevelAutoEnum> & speed, |
| const chip::EndpointId & endpointId) |
| { |
| MainStateEnum mClosureEndpoint1MainState; |
| VerifyOrReturnError(mClosureEndpoint1.GetLogic().GetMainState(mClosureEndpoint1MainState) == 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( |
| mClosureEndpoint1MainState != MainStateEnum::kDisengaged && mClosureEndpoint1MainState != MainStateEnum::kProtected && |
| mClosureEndpoint1MainState != MainStateEnum::kSetupRequired && mClosureEndpoint1MainState != MainStateEnum::kError && |
| mClosureEndpoint1MainState != MainStateEnum::kCalibrating, |
| Status::InvalidInState, |
| ChipLogError(AppServer, "Step command not allowed in current state: %d", static_cast<int>(mClosureEndpoint1MainState))); |
| |
| if (isStepActionInProgress && mCurrentActionEndpointId != endpointId) |
| { |
| ChipLogError(AppServer, "Step action is already in progress on Endpoint %d", mCurrentActionEndpointId); |
| return Status::Failure; |
| } |
| |
| 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> mClosureEndpoint1Target; |
| |
| VerifyOrReturnValue(mClosureEndpoint1.GetLogic().GetOverallTargetState(mClosureEndpoint1Target) == CHIP_NO_ERROR, |
| Status::Failure, ChipLogError(AppServer, "Failed to get overall target for Step command")); |
| |
| if (mClosureEndpoint1Target.IsNull()) |
| { |
| mClosureEndpoint1Target.SetNonNull(GenericOverallTargetState{}); |
| } |
| |
| mClosureEndpoint1Target.Value().position = NullOptional; // Reset position to Null |
| |
| VerifyOrReturnValue(mClosureEndpoint1.GetLogic().SetOverallTargetState(mClosureEndpoint1Target) == CHIP_NO_ERROR, |
| Status::Failure, ChipLogError(AppServer, "Failed to set overall target for Step command")); |
| |
| DeviceLayer::PlatformMgr().LockChipStack(); |
| SetCurrentAction(PANEL_STEP_ACTION); |
| mCurrentActionEndpointId = endpointId; |
| isStepActionInProgress = true; |
| DeviceLayer::PlatformMgr().UnlockChipStack(); |
| |
| AppEvent event; |
| event.Type = AppEvent::kEventType_Closure; |
| event.ClosureEvent.Action = PANEL_STEP_ACTION; |
| event.ClosureEvent.EndpointId = endpointId; |
| event.Handler = InitiateAction; |
| AppTask::GetAppTask().PostEvent(&event); |
| |
| return Status::Success; |
| } |
| |
| void ClosureManager::HandlePanelStepAction(EndpointId endpointId) |
| { |
| ClosureManager & instance = ClosureManager::GetInstance(); |
| |
| ClosureDimension::ClosureDimensionEndpoint * panelEp = instance.GetPanelEndpointById(endpointId); |
| VerifyOrReturn(panelEp != nullptr, ChipLogError(AppServer, "Invalid instance for endpointId: %u", endpointId)); |
| |
| StepDirectionEnum stepDirection = panelEp->GetDelegate().GetStepCommandTargetDirection(); |
| |
| DataModel::Nullable<GenericDimensionStateStruct> panelCurrentState; |
| DataModel::Nullable<GenericDimensionStateStruct> panelTargetState; |
| |
| VerifyOrReturn(panelEp->GetLogic().GetCurrentState(panelCurrentState) == CHIP_NO_ERROR, |
| ChipLogError(AppServer, "Failed to get current state for Step action")); |
| VerifyOrReturn(panelEp->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(panelEp->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)); |
| panelEp->GetLogic().SetCurrentState(panelCurrentState); |
| |
| instance.CancelTimer(); // Cancel any existing timer before starting a new action |
| |
| DeviceLayer::PlatformMgr().LockChipStack(); |
| instance.SetCurrentAction(PANEL_STEP_ACTION); |
| instance.mCurrentActionEndpointId = endpointId; |
| DeviceLayer::PlatformMgr().UnlockChipStack(); |
| |
| instance.StartTimer(kMotionCountdownTimeMs); |
| |
| return; |
| } |
| |
| instance.HandleClosureActionComplete(PANEL_STEP_ACTION); |
| } |
| |
| ClosureDimension::ClosureDimensionEndpoint * ClosureManager::GetPanelEndpointById(EndpointId endpointId) |
| { |
| ClosureManager & instance = ClosureManager::GetInstance(); |
| |
| if (endpointId == instance.mClosurePanelEndpoint2.GetEndpointId()) |
| { |
| return &instance.mClosurePanelEndpoint2; |
| } |
| else if (endpointId == instance.mClosurePanelEndpoint3.GetEndpointId()) |
| { |
| return &instance.mClosurePanelEndpoint3; |
| } |
| else |
| { |
| ChipLogError(AppServer, "GetPanelEndpointById called with invalid endpointId: %u", endpointId); |
| return nullptr; |
| } |
| } |
| |
| bool ClosureManager::GetPanelNextPosition(const GenericDimensionStateStruct & currentState, |
| const GenericDimensionStateStruct & targetState, |
| DataModel::Nullable<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 overflow 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; |
| } |