Add OperationalState to chef examples (#32495)
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 0000000..48863b0
--- /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 <chef-operational-state-delegate-impl.h>
+#include <platform/CHIPDeviceLayer.h>
+
+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<uint32_t> 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<OperationalState::OperationalStateEnum>(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<OperationalState::OperationalStateEnum>(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<DataModel::Nullable<uint32_t>> totalTime((DataModel::Nullable<uint32_t>(mRunningTime + mPausedTime)));
+ Optional<DataModel::Nullable<uint32_t>> pausedTime((DataModel::Nullable<uint32_t>(mPausedTime)));
+
+ GetInstance()->OnOperationCompletionDetected(static_cast<uint8_t>(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<GenericOperationalStateDelegateImpl *>(data);
+
+ OperationalState::Instance * instance = OperationalState::GetOperationalStateInstance();
+ OperationalState::OperationalStateEnum state =
+ static_cast<OperationalState::OperationalStateEnum>(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 0000000..60b6b09
--- /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 <app-common/zap-generated/cluster-objects.h>
+#include <app/clusters/operational-state-server/operational-state-server.h>
+
+#include <protocols/interaction_model/StatusCode.h>
+
+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<uint32_t> mCountDownTime;
+
+ /**
+ * Get the countdown time. This attribute is not used in this application.
+ * @return The current countdown time.
+ */
+ app::DataModel::Nullable<uint32_t> 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<const GenericOperationalState> mOperationalStateList;
+ Span<const CharSpan> 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<const GenericOperationalState>(opStateList);
+ }
+
+ /**
+ * Handle Command Callback in application: Start
+ * @param[out] get operational error after callback.
+ */
+ void HandleStartStateCallback(GenericOperationalError & err) override
+ {
+ mCountDownTime.SetNonNull(static_cast<uint32_t>(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 02fa77d..0a4e238 100644
--- a/examples/chef/linux/BUILD.gn
+++ b/examples/chef/linux/BUILD.gn
@@ -43,6 +43,7 @@
"${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 0a408e8..25d6632 100644
--- a/examples/chef/nrfconnect/CMakeLists.txt
+++ b/examples/chef/nrfconnect/CMakeLists.txt
@@ -84,6 +84,7 @@
${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