From 0aa24427aca9b66c2711c96fd4d3f13856a50566 Mon Sep 17 00:00:00 2001 From: Terence Hampson Date: Thu, 7 Mar 2024 18:58:41 -0500 Subject: [PATCH] Add OperationalState to chef examples (#32495) --- .../chef-operational-state-delegate-impl.cpp | 219 ++++++++++++++++++ .../chef-operational-state-delegate-impl.h | 147 ++++++++++++ examples/chef/linux/BUILD.gn | 1 + examples/chef/nrfconnect/CMakeLists.txt | 1 + 4 files changed, 368 insertions(+) create mode 100644 examples/chef/common/chef-operational-state-delegate-impl.cpp create mode 100644 examples/chef/common/chef-operational-state-delegate-impl.h diff --git a/examples/chef/common/chef-operational-state-delegate-impl.cpp b/examples/chef/common/chef-operational-state-delegate-impl.cpp new file mode 100644 index 00000000000000..48863b0cfa6c26 --- /dev/null +++ b/examples/chef/common/chef-operational-state-delegate-impl.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2023 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 +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::OperationalState; +using namespace chip::app::Clusters::RvcOperationalState; + +static void onOperationalStateTimerTick(System::Layer * systemLayer, void * data); + +DataModel::Nullable GenericOperationalStateDelegateImpl::GetCountdownTime() +{ + if (mCountDownTime.IsNull()) + return DataModel::NullNullable; + + return DataModel::MakeNullable((uint32_t) (mCountDownTime.Value() - mRunningTime)); +} + +CHIP_ERROR GenericOperationalStateDelegateImpl::GetOperationalStateAtIndex(size_t index, GenericOperationalState & operationalState) +{ + if (index >= mOperationalStateList.size()) + { + return CHIP_ERROR_NOT_FOUND; + } + operationalState = mOperationalStateList[index]; + return CHIP_NO_ERROR; +} + +CHIP_ERROR GenericOperationalStateDelegateImpl::GetOperationalPhaseAtIndex(size_t index, MutableCharSpan & operationalPhase) +{ + if (index >= mOperationalPhaseList.size()) + { + return CHIP_ERROR_NOT_FOUND; + } + return CopyCharSpanToMutableCharSpan(mOperationalPhaseList[index], operationalPhase); +} + +void GenericOperationalStateDelegateImpl::HandlePauseStateCallback(GenericOperationalError & err) +{ + OperationalState::OperationalStateEnum state = + static_cast(GetInstance()->GetCurrentOperationalState()); + + if (state == OperationalState::OperationalStateEnum::kStopped || state == OperationalState::OperationalStateEnum::kError) + { + err.Set(to_underlying(OperationalState::ErrorStateEnum::kCommandInvalidInState)); + return; + } + + // placeholder implementation + auto error = GetInstance()->SetOperationalState(to_underlying(OperationalState::OperationalStateEnum::kPaused)); + if (error == CHIP_NO_ERROR) + { + err.Set(to_underlying(ErrorStateEnum::kNoError)); + } + else + { + err.Set(to_underlying(ErrorStateEnum::kUnableToCompleteOperation)); + } +} + +void GenericOperationalStateDelegateImpl::HandleResumeStateCallback(GenericOperationalError & err) +{ + OperationalState::OperationalStateEnum state = + static_cast(GetInstance()->GetCurrentOperationalState()); + + if (state == OperationalState::OperationalStateEnum::kStopped || state == OperationalState::OperationalStateEnum::kError) + { + err.Set(to_underlying(OperationalState::ErrorStateEnum::kCommandInvalidInState)); + return; + } + + // placeholder implementation + auto error = GetInstance()->SetOperationalState(to_underlying(OperationalStateEnum::kRunning)); + if (error == CHIP_NO_ERROR) + { + err.Set(to_underlying(ErrorStateEnum::kNoError)); + } + else + { + err.Set(to_underlying(ErrorStateEnum::kUnableToCompleteOperation)); + } +} + +void GenericOperationalStateDelegateImpl::HandleStartStateCallback(GenericOperationalError & err) +{ + OperationalState::GenericOperationalError current_err(to_underlying(OperationalState::ErrorStateEnum::kNoError)); + GetInstance()->GetCurrentOperationalError(current_err); + + if (current_err.errorStateID != to_underlying(OperationalState::ErrorStateEnum::kNoError)) + { + err.Set(to_underlying(OperationalState::ErrorStateEnum::kUnableToStartOrResume)); + return; + } + + // placeholder implementation + auto error = GetInstance()->SetOperationalState(to_underlying(OperationalStateEnum::kRunning)); + if (error == CHIP_NO_ERROR) + { + (void) DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds16(1), onOperationalStateTimerTick, this); + err.Set(to_underlying(ErrorStateEnum::kNoError)); + } + else + { + err.Set(to_underlying(ErrorStateEnum::kUnableToCompleteOperation)); + } +} + +void GenericOperationalStateDelegateImpl::HandleStopStateCallback(GenericOperationalError & err) +{ + // placeholder implementation + auto error = GetInstance()->SetOperationalState(to_underlying(OperationalStateEnum::kStopped)); + if (error == CHIP_NO_ERROR) + { + (void) DeviceLayer::SystemLayer().CancelTimer(onOperationalStateTimerTick, this); + + OperationalState::GenericOperationalError current_err(to_underlying(OperationalState::ErrorStateEnum::kNoError)); + GetInstance()->GetCurrentOperationalError(current_err); + + Optional> totalTime((DataModel::Nullable(mRunningTime + mPausedTime))); + Optional> pausedTime((DataModel::Nullable(mPausedTime))); + + GetInstance()->OnOperationCompletionDetected(static_cast(current_err.errorStateID), totalTime, pausedTime); + + mRunningTime = 0; + mPausedTime = 0; + err.Set(to_underlying(ErrorStateEnum::kNoError)); + } + else + { + err.Set(to_underlying(ErrorStateEnum::kUnableToCompleteOperation)); + } +} + +static void onOperationalStateTimerTick(System::Layer * systemLayer, void * data) +{ + GenericOperationalStateDelegateImpl * delegate = reinterpret_cast(data); + + OperationalState::Instance * instance = OperationalState::GetOperationalStateInstance(); + OperationalState::OperationalStateEnum state = + static_cast(instance->GetCurrentOperationalState()); + + auto countdown_time = delegate->GetCountdownTime(); + + if (countdown_time.IsNull() || (!countdown_time.IsNull() && countdown_time.Value() > 0)) + { + if (state == OperationalState::OperationalStateEnum::kRunning) + { + delegate->mRunningTime++; + } + else if (state == OperationalState::OperationalStateEnum::kPaused) + { + delegate->mPausedTime++; + } + } + + if (state == OperationalState::OperationalStateEnum::kRunning || state == OperationalState::OperationalStateEnum::kPaused) + { + (void) DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds16(1), onOperationalStateTimerTick, delegate); + } + else + { + (void) DeviceLayer::SystemLayer().CancelTimer(onOperationalStateTimerTick, delegate); + } +} + +// Init Operational State cluster + +static OperationalState::Instance * gOperationalStateInstance = nullptr; +static OperationalStateDelegate * gOperationalStateDelegate = nullptr; + +OperationalState::Instance * OperationalState::GetOperationalStateInstance() +{ + return gOperationalStateInstance; +} + +void OperationalState::Shutdown() +{ + if (gOperationalStateInstance != nullptr) + { + delete gOperationalStateInstance; + gOperationalStateInstance = nullptr; + } + if (gOperationalStateDelegate != nullptr) + { + delete gOperationalStateDelegate; + gOperationalStateDelegate = nullptr; + } +} + +void emberAfOperationalStateClusterInitCallback(chip::EndpointId endpointId) +{ + VerifyOrDie(endpointId == 1); // this cluster is only enabled for endpoint 1. + VerifyOrDie(gOperationalStateInstance == nullptr && gOperationalStateDelegate == nullptr); + + gOperationalStateDelegate = new OperationalStateDelegate; + EndpointId operationalStateEndpoint = 0x01; + gOperationalStateInstance = new OperationalState::Instance(gOperationalStateDelegate, operationalStateEndpoint); + + gOperationalStateInstance->SetOperationalState(to_underlying(OperationalState::OperationalStateEnum::kStopped)); + + gOperationalStateInstance->Init(); +} diff --git a/examples/chef/common/chef-operational-state-delegate-impl.h b/examples/chef/common/chef-operational-state-delegate-impl.h new file mode 100644 index 00000000000000..60b6b09e9b6511 --- /dev/null +++ b/examples/chef/common/chef-operational-state-delegate-impl.h @@ -0,0 +1,147 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include + +namespace chip { +namespace app { +namespace Clusters { + +namespace OperationalState { + +// This is an application level delegate to handle operational state commands according to the specific business logic. +class GenericOperationalStateDelegateImpl : public Delegate +{ +public: + uint32_t mRunningTime = 0; + uint32_t mPausedTime = 0; + app::DataModel::Nullable mCountDownTime; + + /** + * Get the countdown time. This attribute is not used in this application. + * @return The current countdown time. + */ + app::DataModel::Nullable GetCountdownTime() override; + + /** + * Fills in the provided GenericOperationalState with the state at index `index` if there is one, + * or returns CHIP_ERROR_NOT_FOUND if the index is out of range for the list of states. + * Note: This is used by the SDK to populate the operational state list attribute. If the contents of this list changes, + * the device SHALL call the Instance's ReportOperationalStateListChange method to report that this attribute has changed. + * @param index The index of the state, with 0 representing the first state. + * @param operationalState The GenericOperationalState is filled. + */ + CHIP_ERROR GetOperationalStateAtIndex(size_t index, GenericOperationalState & operationalState) override; + + /** + * Fills in the provided MutableCharSpan with the phase at index `index` if there is one, + * or returns CHIP_ERROR_NOT_FOUND if the index is out of range for the list of phases. + * + * If CHIP_ERROR_NOT_FOUND is returned for index 0, that indicates that the PhaseList attribute is null + * (there are no phases defined at all). + * + * Note: This is used by the SDK to populate the phase list attribute. If the contents of this list changes, the + * device SHALL call the Instance's ReportPhaseListChange method to report that this attribute has changed. + * @param index The index of the phase, with 0 representing the first phase. + * @param operationalPhase The MutableCharSpan is filled. + */ + CHIP_ERROR GetOperationalPhaseAtIndex(size_t index, MutableCharSpan & operationalPhase) override; + + // command callback + /** + * Handle Command Callback in application: Pause + * @param[out] get operational error after callback. + */ + void HandlePauseStateCallback(GenericOperationalError & err) override; + + /** + * Handle Command Callback in application: Resume + * @param[out] get operational error after callback. + */ + void HandleResumeStateCallback(GenericOperationalError & err) override; + + /** + * Handle Command Callback in application: Start + * @param[out] get operational error after callback. + */ + void HandleStartStateCallback(GenericOperationalError & err) override; + + /** + * Handle Command Callback in application: Stop + * @param[out] get operational error after callback. + */ + void HandleStopStateCallback(GenericOperationalError & err) override; + +protected: + Span mOperationalStateList; + Span mOperationalPhaseList; +}; + +// This is an application level delegate to handle operational state commands according to the specific business logic. +class OperationalStateDelegate : public GenericOperationalStateDelegateImpl +{ +private: + const GenericOperationalState opStateList[4] = { + GenericOperationalState(to_underlying(OperationalStateEnum::kStopped)), + GenericOperationalState(to_underlying(OperationalStateEnum::kRunning)), + GenericOperationalState(to_underlying(OperationalStateEnum::kPaused)), + GenericOperationalState(to_underlying(OperationalStateEnum::kError)), + }; + + const uint32_t kExampleCountDown = 30; + +public: + OperationalStateDelegate() + { + GenericOperationalStateDelegateImpl::mOperationalStateList = Span(opStateList); + } + + /** + * Handle Command Callback in application: Start + * @param[out] get operational error after callback. + */ + void HandleStartStateCallback(GenericOperationalError & err) override + { + mCountDownTime.SetNonNull(static_cast(kExampleCountDown)); + GenericOperationalStateDelegateImpl::HandleStartStateCallback(err); + } + + /** + * Handle Command Callback in application: Stop + * @param[out] get operational error after callback. + */ + void HandleStopStateCallback(GenericOperationalError & err) override + { + GenericOperationalStateDelegateImpl::HandleStopStateCallback(err); + mCountDownTime.SetNull(); + } +}; + +Instance * GetOperationalStateInstance(); + +void Shutdown(); + +} // namespace OperationalState +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/examples/chef/linux/BUILD.gn b/examples/chef/linux/BUILD.gn index 02fa77dac864e5..0a4e2385f28dda 100644 --- a/examples/chef/linux/BUILD.gn +++ b/examples/chef/linux/BUILD.gn @@ -43,6 +43,7 @@ executable("${sample_name}") { "${project_dir}/common/chef-air-quality.cpp", "${project_dir}/common/chef-concentration-measurement.cpp", "${project_dir}/common/chef-fan-control-manager.cpp", + "${project_dir}/common/chef-operational-state-delegate-impl.cpp", "${project_dir}/common/chef-resource-monitoring-delegates.cpp", "${project_dir}/common/chef-rvc-mode-delegate.cpp", "${project_dir}/common/chef-rvc-operational-state-delegate.cpp", diff --git a/examples/chef/nrfconnect/CMakeLists.txt b/examples/chef/nrfconnect/CMakeLists.txt index 0a408e829dd04f..25d663211b9f15 100644 --- a/examples/chef/nrfconnect/CMakeLists.txt +++ b/examples/chef/nrfconnect/CMakeLists.txt @@ -84,6 +84,7 @@ target_sources(app PRIVATE ${CHEF}/common/chef-air-quality.cpp ${CHEF}/common/chef-concentration-measurement.cpp ${CHEF}/common/chef-fan-control-manager.cpp + ${CHEF}/common/chef-operational-state-delegate-impl.cpp ${CHEF}/common/chef-resource-monitoring-delegates.cpp ${CHEF}/common/chef-rvc-mode-delegate.cpp ${CHEF}/common/chef-rvc-operational-state-delegate.cpp