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