【Feature】operational state cluster sdk implementation and example cluster server application (#26971)
* Add operational state cluster implement
* Enable operational state cluster server in all-clusters-app.zap
* Restyled by clang-format
* Restyled by gn
* Enable CommandHandlerInterfaceOnlyClusters feature in operational state cluster
* Fix the data type no match the operational state cluster xml
* Add Uncopyable feature to operational state server class
* Fix no define function
* Add operational state cluster in other platform
* Restyled by clang-format
* Restyled by gn
* Function / Data Struct in headfile are documented.
* Document why operational states to be in storage
* document the function note
* Add prefix members with m for class OperationalStateServer
* Fix Bug: do const_cast no needed
* Optimize to returning a const reference
* Remove the api no needed to be implemented
* use to_underlying to instead static_cast
* use reduce_size api to optimize the MutableByteSpan
* Fix: memory leak in exceptional situation
* Rename the structs for pretty confusing
* comment the member
* remove the documentation return in functions
* Add delete keyword in Uncopyable class
* modify the document of functions
* modify the document of functions
* Fix spelling error in function notes
* Fix: memory leak in exceptional situation
* Fix: read operational state list or phase list fail in some exception situations
* use MakeOptional to optimize the function call
* modify the document of functions
* Fix: ErrorStateStruct's ErrorStateDetails need to be null/missing
* Add the document for function
* Optimize ErrorStateStruct's field -- ErrorStateLabel and ErrorStateDetails
* document the method signature
* document the class
* remove temporary variable
* Add test cases in TestOperationalStateDataProvider
* Remove implementing EnumerateAcceptedCommands in Operational State Cluster
* Enable all commands in operational state cluster for all-clusters-app
* Zap regen all
* Revert file same as master branch
* Optimize operational state server's implement
* Add operational state delegate implement
* Add operational state cluster delegates
* Zap regen
* Modify path of head file included
* add file included in BUILD.gn
* Restyled by whitespace
* Restyled by clang-format
* Restyled by gn
* add operational state cluster impl in other platforms
* Restyled by gn
* Remove log in operational-state-delegate-impl.cpp
* remove unuseful file
* Update src/app/clusters/operational-state-server/operational-state-delegate.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Optimize struct GenericOperationalState
* drop the extra chip:: in operational-state api
* Optimize struct GenericOperationalError
* Optimize to put a struct instance on the state, then call the delegate to fill it in
* Use GetOperationalStateAtIndex api to instead GetOperationalStateList api
* Optimize struct GenericOperationalPhase
* Optimize struct GenericOperationalPhase
* Optimize HandleXXStateCallback api param
* Remove unuseful code and document
* Use constexpr val to instead number
* Fix use of totalOperationalTime
* enable operational state cluster event in Operatinal State cluster
* Add unit test of operational state delegate
* Add the rest unit test of operational state delegate
* Optimize class OperationalStateDelegate , add two private member
* move the constexpr val
* fix the err in TestOperationalStateDelegate
* Optimize api GenericOperationalError
* Add OperationalStateDelegateImpl unit test
* add document
* change Log event api in operational state cluster
* Add document
* sync code to examples/placeholder
* Restyled by whitespace
* Restyled by clang-format
* Restyled by gn
* replace GetOperationalState with GetCurrentOperationalState
* update document
* update document
* update document
* replace GetOperationalError with GenericOperationalError
* update document
* document the api for GetOperationalStateDelegate in head file
* optimize the use of operator [] for Span class
* fix the spelling error
* provide a way to construct a GenericOperationalState without providing a state
* optimize the code of if-else
* Optimize code
* update document
* Optimize class OperationalStateServer:
put all the public bits together instead of interleaving them with the private bits.
* modify document
* Add CurrentPhase and CountdownTime attribute into AttributeAccessInterface-only
* zap regen
* remove unuseful member in class Delegate
* remove useful code
* document the api
* Add set/get current phase api
* Add set/get countdownTime api
* delegate could be null, and that should be handled without crashing, via returning an error
* command callbacks need to respond with an error
* remove something that is not in the spec
* optimize the name of class member function
* Optimize struct GenericOperationCompletion
* update struct GenericOperationCompletion test case
* Optimize struct GenericOperationCompletion api
* add coutdownTime attribute in operational state cluter
* change api of initializing OperationalStateServer instances
* remove the code using operational state in other platform
* Add operational state server init in linux all-cluster-app
* Restyled by whitespace
* Restyled by clang-format
* Restyled by prettier-json
* Add TestOperationalState.yaml
* rm TestOperationalStateDelegateImpl.cpp
* Restyled by whitespace
* Restyled by gn
* Restyled by prettier-yaml
* remove the api, filing a followup issue to emit the right events
* fix CI build error
* fix CI build err
* Restyled by clang-format
* fix build error
* Restyled by clang-format
* update document
* use NullOptional to instead Missing
* return CHIP_ERROR_INCORRECT_STATE when delegate is nullptr
* optimize reading the operational state list
* move the position of testcase
* fix ci build err
* fix ci build err
* Restyled by clang-format
* fix readability-else-after-return error
* fix Unknown key in CI build
* add note in ciTest.json for darwin-framework-tool
* update document for api
* use Zcl instead NotSpecified in log
* fix TestOperationalState error
* zap_regen_all
* optimize lambda function use
* Restyled by clang-format
* fix ci error: readability-else-after-return
---------
Co-authored-by: Restyled.io <commits@restyled.io>
Co-authored-by: Justin Wood <woody@apple.com>
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni
index 90b38f8..4d734a7 100644
--- a/src/app/chip_data_model.gni
+++ b/src/app/chip_data_model.gni
@@ -250,6 +250,12 @@
"${_app_root}/clusters/scenes-server/ExtensionFieldSetsImpl.cpp",
"${_app_root}/clusters/scenes-server/SceneTableImpl.cpp",
]
+ } else if (cluster == "operational-state-server") {
+ sources += [
+ "${_app_root}/clusters/${cluster}/${cluster}.cpp",
+ "${_app_root}/clusters/${cluster}/${cluster}.h",
+ "${_app_root}/clusters/${cluster}/operational-state-delegate.h",
+ ]
} else {
sources += [ "${_app_root}/clusters/${cluster}/${cluster}.cpp" ]
}
diff --git a/src/app/clusters/operational-state-server/operational-state-delegate.h b/src/app/clusters/operational-state-server/operational-state-delegate.h
new file mode 100644
index 0000000..217a99c
--- /dev/null
+++ b/src/app/clusters/operational-state-server/operational-state-delegate.h
@@ -0,0 +1,322 @@
+/*
+ *
+ * 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/util/af-enums.h>
+#include <lib/support/CommonIterator.h>
+
+namespace chip {
+namespace app {
+namespace Clusters {
+namespace OperationalState {
+
+constexpr size_t kOperationalStateLabelMaxSize = 64u;
+constexpr size_t kOperationalErrorLabelMaxSize = 64u;
+constexpr size_t kOperationalErrorDetailsMaxSize = 64u;
+constexpr size_t kOperationalPhaseNameMaxSize = 64u;
+
+/**
+ * A class which represents the operational state of an Operational State cluster derivation instance.
+ */
+struct GenericOperationalState : public app::Clusters::detail::Structs::OperationalStateStruct::Type
+{
+ GenericOperationalState(uint8_t state = to_underlying(OperationalStateEnum::kStopped), Optional<CharSpan> label = NullOptional)
+ {
+ Set(state, label);
+ }
+
+ GenericOperationalState(const GenericOperationalState & op) { *this = op; }
+
+ GenericOperationalState & operator=(const GenericOperationalState & op)
+ {
+ Set(op.operationalStateID, op.operationalStateLabel);
+ return *this;
+ }
+
+ void Set(uint8_t state, Optional<CharSpan> label = NullOptional)
+ {
+ operationalStateID = state;
+ if (label.HasValue())
+ {
+ memset(mOperationalStateLabelBuffer, 0, sizeof(mOperationalStateLabelBuffer));
+ if (label.Value().size() > sizeof(mOperationalStateLabelBuffer))
+ {
+ memcpy(mOperationalStateLabelBuffer, label.Value().data(), sizeof(mOperationalStateLabelBuffer));
+ operationalStateLabel.SetValue(CharSpan(mOperationalStateLabelBuffer, sizeof(mOperationalStateLabelBuffer)));
+ }
+ else
+ {
+ memcpy(mOperationalStateLabelBuffer, label.Value().data(), label.Value().size());
+ operationalStateLabel.SetValue(CharSpan(mOperationalStateLabelBuffer, label.Value().size()));
+ }
+ }
+ else
+ {
+ operationalStateLabel = NullOptional;
+ }
+ }
+
+private:
+ char mOperationalStateLabelBuffer[kOperationalStateLabelMaxSize];
+};
+
+/**
+ * A class which represents the operational error of an Operational State cluster derivation instance.
+ */
+struct GenericOperationalError : public app::Clusters::detail::Structs::ErrorStateStruct::Type
+{
+ GenericOperationalError(uint8_t state, Optional<chip::CharSpan> label = NullOptional,
+ Optional<chip::CharSpan> details = NullOptional)
+ {
+ Set(state, label, details);
+ }
+
+ GenericOperationalError(const GenericOperationalError & error) { *this = error; }
+
+ GenericOperationalError & operator=(const GenericOperationalError & error)
+ {
+ Set(error.errorStateID, error.errorStateLabel, error.errorStateDetails);
+ return *this;
+ }
+
+ void Set(uint8_t state, Optional<chip::CharSpan> label = NullOptional, Optional<chip::CharSpan> details = NullOptional)
+ {
+ errorStateID = state;
+ if (label.HasValue())
+ {
+ memset(mErrorStateLabelBuffer, 0, sizeof(mErrorStateLabelBuffer));
+ if (label.Value().size() > sizeof(mErrorStateLabelBuffer))
+ {
+ memcpy(mErrorStateLabelBuffer, label.Value().data(), sizeof(mErrorStateLabelBuffer));
+ errorStateLabel.SetValue(CharSpan(mErrorStateLabelBuffer, sizeof(mErrorStateLabelBuffer)));
+ }
+ else
+ {
+ memcpy(mErrorStateLabelBuffer, label.Value().data(), label.Value().size());
+ errorStateLabel.SetValue(CharSpan(mErrorStateLabelBuffer, label.Value().size()));
+ }
+ }
+ else
+ {
+ errorStateLabel = NullOptional;
+ }
+
+ if (details.HasValue())
+ {
+ memset(mErrorStateDetailsBuffer, 0, sizeof(mErrorStateDetailsBuffer));
+ if (details.Value().size() > sizeof(mErrorStateDetailsBuffer))
+ {
+ memcpy(mErrorStateDetailsBuffer, details.Value().data(), sizeof(mErrorStateDetailsBuffer));
+ errorStateDetails.SetValue(CharSpan(mErrorStateDetailsBuffer, sizeof(mErrorStateDetailsBuffer)));
+ }
+ else
+ {
+ memcpy(mErrorStateDetailsBuffer, details.Value().data(), details.Value().size());
+ errorStateDetails.SetValue(CharSpan(mErrorStateDetailsBuffer, details.Value().size()));
+ }
+ }
+ else
+ {
+ errorStateDetails = NullOptional;
+ }
+ }
+
+private:
+ char mErrorStateLabelBuffer[kOperationalErrorLabelMaxSize];
+ char mErrorStateDetailsBuffer[kOperationalErrorDetailsMaxSize];
+};
+
+/**
+ * A class which represents the operational phase of an Operational State cluster derivation instance.
+ */
+struct GenericOperationalPhase
+{
+ GenericOperationalPhase(app::DataModel::Nullable<CharSpan> name) { Set(name); }
+
+ GenericOperationalPhase(const GenericOperationalPhase & ph) { *this = ph; }
+
+ GenericOperationalPhase & operator=(const GenericOperationalPhase & ph)
+ {
+ Set(ph.mPhaseName);
+ return *this;
+ }
+
+ bool IsMissing() const { return mPhaseName.IsNull(); }
+ app::DataModel::Nullable<CharSpan> mPhaseName;
+
+private:
+ void Set(app::DataModel::Nullable<CharSpan> name)
+ {
+ if (name.IsNull())
+ {
+ mPhaseName.SetNull();
+ }
+ else
+ {
+ memset(mPhaseNameBuffer, 0, sizeof(mPhaseNameBuffer));
+ if (name.Value().size() > sizeof(mPhaseNameBuffer))
+ {
+ memcpy(mPhaseNameBuffer, name.Value().data(), sizeof(mPhaseNameBuffer));
+ mPhaseName = app::DataModel::Nullable<CharSpan>(CharSpan(mPhaseNameBuffer, sizeof(mPhaseNameBuffer)));
+ }
+ else
+ {
+ memcpy(mPhaseNameBuffer, name.Value().data(), name.Value().size());
+ mPhaseName = app::DataModel::Nullable<CharSpan>(CharSpan(mPhaseNameBuffer, name.Value().size()));
+ }
+ }
+ }
+
+ char mPhaseNameBuffer[kOperationalPhaseNameMaxSize];
+};
+
+/**
+ * A class which represents the operational completion of an Operational State cluster derivation instance.
+ */
+struct GenericOperationCompletion : public app::Clusters::OperationalState::Events::OperationCompletion::Type
+{
+ GenericOperationCompletion(uint8_t aCompletionErrorCode,
+ const Optional<DataModel::Nullable<uint32_t>> & aTotalOperationalTime = NullOptional,
+ const Optional<DataModel::Nullable<uint32_t>> & aPausedTime = NullOptional)
+ {
+ completionErrorCode = aCompletionErrorCode;
+ totalOperationalTime = aTotalOperationalTime;
+ pausedTime = aPausedTime;
+ }
+};
+
+/**
+ * A delegate to handle application logic of the Operational State aliased Cluster.
+ * The delegate API assumes there will be separate delegate objects for each cluster instance.
+ * (i.e. each separate operational state cluster derivation, on each separate endpoint),
+ * since the delegate methods are not handed the cluster id or endpoint.
+ */
+class Delegate
+{
+public:
+ /**
+ * Get the current operational state.
+ * @param op The GenericOperationalState to fill with the current operational state value.
+ * @return void.
+ */
+ virtual void GetCurrentOperationalState(GenericOperationalState & op) = 0;
+
+ /**
+ * Get the list of supported operational states.
+ * 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.
+ * @param index The index of the state, with 0 representing the first state.
+ * @param operationalState The GenericOperationalState is filled.
+ */
+ virtual CHIP_ERROR GetOperationalStateAtIndex(size_t index, GenericOperationalState & operationalState) = 0;
+
+ /**
+ * Get the list of supported operational phases.
+ * Fills in the provided GenericOperationalPhase 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.
+ * @param index The index of the phase, with 0 representing the first phase.
+ * @param operationalPhase The GenericOperationalPhase is filled.
+ */
+ virtual CHIP_ERROR GetOperationalPhaseAtIndex(size_t index, GenericOperationalPhase & operationalPhase) = 0;
+
+ /**
+ * Get current operational error.
+ * @param error The GenericOperationalError to fill with the current operational error value
+ */
+ virtual void GetCurrentOperationalError(GenericOperationalError & error) = 0;
+
+ /**
+ * Get current phase
+ * @param phase The app::DataModel::Nullable<uint8_t> to fill with the current phase value
+ */
+ virtual void GetCurrentPhase(app::DataModel::Nullable<uint8_t> & phase) = 0;
+
+ /**
+ * Get countdown time
+ * @param time The app::DataModel::Nullable<uint32_t> to fill with the coutdown time value
+ */
+ virtual void GetCountdownTime(app::DataModel::Nullable<uint32_t> & time) = 0;
+
+ /**
+ * Set current operational state.
+ * @param opState The operational state that should now be the current one.
+ */
+ virtual CHIP_ERROR SetOperationalState(const GenericOperationalState & opState) = 0;
+
+ /**
+ * Set operational error.
+ * @param opErrState The new operational error.
+ */
+ virtual CHIP_ERROR SetOperationalError(const GenericOperationalError & opErrState) = 0;
+
+ /**
+ * Set operational phase.
+ * @param phase The operational phase that should now be the current one.
+ */
+ virtual CHIP_ERROR SetPhase(const app::DataModel::Nullable<uint8_t> & phase) = 0;
+
+ /**
+ * Set coutdown time.
+ * @param time The coutdown time that should now be the current one.
+ */
+ virtual CHIP_ERROR SetCountdownTime(const app::DataModel::Nullable<uint32_t> & time) = 0;
+
+ // command callback
+ /**
+ * Handle Command Callback in application: Pause
+ * @param[out] get operational error after callback.
+ */
+ virtual void HandlePauseStateCallback(GenericOperationalError & err) = 0;
+
+ /**
+ * Handle Command Callback in application: Resume
+ * @param[out] get operational error after callback.
+ */
+ virtual void HandleResumeStateCallback(GenericOperationalError & err) = 0;
+
+ /**
+ * Handle Command Callback in application: Start
+ * @param[out] get operational error after callback.
+ */
+ virtual void HandleStartStateCallback(GenericOperationalError & err) = 0;
+
+ /**
+ * Handle Command Callback in application: Stop
+ * @param[out] get operational error after callback.
+ */
+ virtual void HandleStopStateCallback(GenericOperationalError & err) = 0;
+
+ Delegate() = default;
+
+ virtual ~Delegate() = default;
+};
+
+// @brief Instance getter for the delegate for the given operational state alias cluster on the given endpoint.
+// The delegate API assumes there will be separate delegate objects for each cluster instance.
+// (i.e. each separate operational state cluster derivation, on each separate endpoint)
+// @note This API should always be called prior to using the delegate and the return pointer should never be cached.
+// This should be implemented by the application.
+// @return Default global delegate instance.
+Delegate * GetOperationalStateDelegate(EndpointId endpointId, ClusterId clusterId);
+
+} // namespace OperationalState
+} // namespace Clusters
+} // namespace app
+} // namespace chip
diff --git a/src/app/clusters/operational-state-server/operational-state-server.cpp b/src/app/clusters/operational-state-server/operational-state-server.cpp
new file mode 100644
index 0000000..79bb3d1
--- /dev/null
+++ b/src/app/clusters/operational-state-server/operational-state-server.cpp
@@ -0,0 +1,305 @@
+/*
+ *
+ * Copyright (c) 2023 Project CHIP Authors
+ *
+ * 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.
+ */
+
+/****************************************************************************
+ * @file
+ * @brief Implementation for the Operational State Server Cluster
+ ***************************************************************************/
+#include "operational-state-server.h"
+#include "operational-state-delegate.h"
+#include <app-common/zap-generated/attributes/Accessors.h>
+#include <app-common/zap-generated/callback.h>
+#include <app-common/zap-generated/cluster-objects.h>
+#include <app-common/zap-generated/enums.h>
+#include <app-common/zap-generated/ids/Attributes.h>
+#include <app/CommandHandler.h>
+#include <app/ConcreteAttributePath.h>
+#include <app/ConcreteCommandPath.h>
+#include <app/InteractionModelEngine.h>
+#include <app/util/af.h>
+#include <app/util/attribute-storage.h>
+#include <app/util/error-mapping.h>
+#include <lib/core/CHIPEncoding.h>
+
+using namespace chip;
+using namespace chip::app;
+using namespace chip::app::Clusters;
+using namespace chip::app::Clusters::OperationalState;
+using namespace chip::app::Clusters::OperationalState::Attributes;
+
+using Status = Protocols::InteractionModel::Status;
+
+CHIP_ERROR OperationalStateServer::Init()
+{
+ // Check if the cluster has been selected in zap
+ if (!emberAfContainsServer(mEndpointId, mClusterId))
+ {
+ ChipLogError(Zcl, "Operational State: The cluster with ID %lu was not enabled in zap.", long(mClusterId));
+ return CHIP_ERROR_INVALID_ARGUMENT;
+ }
+
+ ReturnErrorOnFailure(InteractionModelEngine::GetInstance()->RegisterCommandHandler(this));
+
+ VerifyOrReturnError(registerAttributeAccessOverride(this), CHIP_ERROR_INCORRECT_STATE);
+
+ return CHIP_NO_ERROR;
+}
+
+void OperationalStateServer::Shutdown()
+{
+ InteractionModelEngine::GetInstance()->UnregisterCommandHandler(this);
+}
+
+// This function is called by the interaction model engine when a command destined for this instance is received.
+void OperationalStateServer::InvokeCommand(HandlerContext & handlerContext)
+{
+ ChipLogDetail(Zcl, "OperationalState: InvokeCommand");
+ switch (handlerContext.mRequestPath.mCommandId)
+ {
+ case Commands::Pause::Id:
+ ChipLogDetail(Zcl, "OperationalState: Entering handling Pause state");
+
+ HandleCommand<Commands::Pause::DecodableType>(
+ handlerContext, [this](HandlerContext & ctx, const auto & req) { HandlePauseState(ctx, req); });
+ break;
+
+ case Commands::Resume::Id:
+ ChipLogDetail(Zcl, "OperationalState: Entering handling Resume state");
+
+ HandleCommand<Commands::Resume::DecodableType>(
+ handlerContext, [this](HandlerContext & ctx, const auto & req) { HandleResumeState(ctx, req); });
+ break;
+
+ case Commands::Start::Id:
+ ChipLogDetail(Zcl, "OperationalState: Entering handling Start state");
+
+ HandleCommand<Commands::Start::DecodableType>(
+ handlerContext, [this](HandlerContext & ctx, const auto & req) { HandleStartState(ctx, req); });
+ break;
+
+ case Commands::Stop::Id:
+ ChipLogDetail(Zcl, "OperationalState: Entering handling Stop state");
+
+ HandleCommand<Commands::Stop::DecodableType>(handlerContext,
+ [this](HandlerContext & ctx, const auto & req) { HandleStopState(ctx, req); });
+ break;
+ }
+}
+
+void OperationalStateServer::HandlePauseState(HandlerContext & ctx, const Commands::Pause::DecodableType & req)
+{
+ ChipLogDetail(Zcl, "OperationalState: HandlePauseState");
+ Commands::OperationalCommandResponse::Type response;
+ Delegate * delegate = OperationalState::GetOperationalStateDelegate(mEndpointId, mClusterId);
+ GenericOperationalError err(to_underlying(ErrorStateEnum::kNoError));
+ GenericOperationalState opState;
+
+ VerifyOrReturn(delegate != nullptr, ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure));
+ delegate->GetCurrentOperationalState(opState);
+
+ if (opState.operationalStateID != to_underlying(OperationalStateEnum::kPaused))
+ {
+ delegate->HandlePauseStateCallback(err);
+ }
+ response.commandResponseState = err;
+
+ ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response);
+}
+
+void OperationalStateServer::HandleResumeState(HandlerContext & ctx, const Commands::Resume::DecodableType & req)
+{
+ ChipLogDetail(Zcl, "OperationalState: HandleResumeState");
+ Commands::OperationalCommandResponse::Type response;
+ Delegate * delegate = OperationalState::GetOperationalStateDelegate(mEndpointId, mClusterId);
+ GenericOperationalError err(to_underlying(ErrorStateEnum::kNoError));
+ GenericOperationalState opState;
+
+ VerifyOrReturn(delegate != nullptr, ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure));
+
+ delegate->GetCurrentOperationalState(opState);
+
+ if (opState.operationalStateID != to_underlying(OperationalStateEnum::kPaused) &&
+ opState.operationalStateID != to_underlying(OperationalStateEnum::kRunning))
+ {
+ err.Set(to_underlying(ErrorStateEnum::kCommandInvalidInState));
+ }
+ else if (opState.operationalStateID == to_underlying(OperationalStateEnum::kPaused))
+ {
+ delegate->HandleResumeStateCallback(err);
+ }
+ response.commandResponseState = err;
+
+ ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response);
+}
+
+void OperationalStateServer::HandleStartState(HandlerContext & ctx, const Commands::Start::DecodableType & req)
+{
+ ChipLogDetail(Zcl, "OperationalState: HandleStartState");
+ Commands::OperationalCommandResponse::Type response;
+ Delegate * delegate = OperationalState::GetOperationalStateDelegate(mEndpointId, mClusterId);
+ GenericOperationalError err(to_underlying(ErrorStateEnum::kNoError));
+ GenericOperationalState opState;
+
+ VerifyOrReturn(delegate != nullptr, ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure));
+
+ delegate->GetCurrentOperationalState(opState);
+
+ if (opState.operationalStateID != to_underlying(OperationalStateEnum::kRunning))
+ {
+ delegate->HandleStartStateCallback(err);
+ }
+ response.commandResponseState = err;
+
+ ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response);
+}
+
+void OperationalStateServer::HandleStopState(HandlerContext & ctx, const Commands::Stop::DecodableType & req)
+{
+ ChipLogDetail(Zcl, "OperationalState: HandleStopState");
+ Commands::OperationalCommandResponse::Type response;
+ Delegate * delegate = OperationalState::GetOperationalStateDelegate(mEndpointId, mClusterId);
+ GenericOperationalError err(to_underlying(ErrorStateEnum::kNoError));
+ GenericOperationalState opState;
+
+ VerifyOrReturn(delegate != nullptr, ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure));
+
+ delegate->GetCurrentOperationalState(opState);
+
+ if (opState.operationalStateID != to_underlying(OperationalStateEnum::kStopped))
+ {
+ delegate->HandleStopStateCallback(err);
+ }
+ response.commandResponseState = err;
+
+ ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response);
+}
+
+template <typename RequestT, typename FuncT>
+void OperationalStateServer::HandleCommand(HandlerContext & handlerContext, FuncT func)
+{
+ if (!handlerContext.mCommandHandled && (handlerContext.mRequestPath.mCommandId == RequestT::GetCommandId()))
+ {
+ RequestT requestPayload;
+
+ //
+ // If the command matches what the caller is looking for, let's mark this as being handled
+ // even if errors happen after this. This ensures that we don't execute any fall-back strategies
+ // to handle this command since at this point, the caller is taking responsibility for handling
+ // the command in its entirety, warts and all.
+ //
+ handlerContext.SetCommandHandled();
+
+ if (DataModel::Decode(handlerContext.mPayload, requestPayload) != CHIP_NO_ERROR)
+ {
+ handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath,
+ Protocols::InteractionModel::Status::InvalidCommand);
+ return;
+ }
+
+ func(handlerContext, requestPayload);
+ }
+}
+
+CHIP_ERROR OperationalStateServer::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
+{
+ switch (aPath.mAttributeId)
+ {
+ case OperationalState::Attributes::OperationalStateList::Id: {
+ Delegate * delegate = OperationalState::GetOperationalStateDelegate(mEndpointId, mClusterId);
+ VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Delegate is nullptr"));
+
+ return aEncoder.EncodeList([delegate](const auto & encoder) -> CHIP_ERROR {
+ GenericOperationalState opState;
+ size_t index = 0;
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ while ((err = delegate->GetOperationalStateAtIndex(index, opState)) == CHIP_NO_ERROR)
+ {
+ ReturnErrorOnFailure(encoder.Encode(opState));
+ index++;
+ }
+ if (err == CHIP_ERROR_NOT_FOUND)
+ {
+ return CHIP_NO_ERROR;
+ }
+ return err;
+ });
+ }
+ break;
+
+ case OperationalState::Attributes::OperationalState::Id: {
+
+ Delegate * delegate = OperationalState::GetOperationalStateDelegate(mEndpointId, mClusterId);
+ GenericOperationalState opState;
+ VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Delegate is nullptr"));
+ delegate->GetCurrentOperationalState(opState);
+ return aEncoder.Encode(opState);
+ }
+ break;
+
+ case OperationalState::Attributes::OperationalError::Id: {
+ Delegate * delegate = OperationalState::GetOperationalStateDelegate(mEndpointId, mClusterId);
+ GenericOperationalError opErr(to_underlying(ErrorStateEnum::kNoError));
+ VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Delegate is nullptr"));
+ delegate->GetCurrentOperationalError(opErr);
+ return aEncoder.Encode(opErr);
+ }
+ break;
+
+ case OperationalState::Attributes::PhaseList::Id: {
+ Delegate * delegate = OperationalState::GetOperationalStateDelegate(mEndpointId, mClusterId);
+
+ GenericOperationalPhase phase = GenericOperationalPhase(DataModel::Nullable<CharSpan>());
+ size_t index = 0;
+ VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Delegate is nullptr"));
+
+ if (delegate->GetOperationalPhaseAtIndex(index, phase) == CHIP_ERROR_NOT_FOUND || phase.IsMissing())
+ {
+ return aEncoder.EncodeNull();
+ }
+ return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR {
+ while (delegate->GetOperationalPhaseAtIndex(index, phase) != CHIP_ERROR_NOT_FOUND)
+ {
+ ReturnErrorOnFailure(encoder.Encode(phase.mPhaseName));
+ index++;
+ }
+ return CHIP_NO_ERROR;
+ });
+ }
+ break;
+
+ case OperationalState::Attributes::CurrentPhase::Id: {
+ DataModel::Nullable<uint8_t> currentPhase;
+ Delegate * delegate = OperationalState::GetOperationalStateDelegate(mEndpointId, mClusterId);
+
+ VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Delegate is nullptr"));
+ delegate->GetCurrentPhase(currentPhase);
+ return aEncoder.Encode(currentPhase);
+ }
+ break;
+
+ case OperationalState::Attributes::CountdownTime::Id: {
+ DataModel::Nullable<uint32_t> countdownTime;
+ Delegate * delegate = OperationalState::GetOperationalStateDelegate(mEndpointId, mClusterId);
+
+ VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Delegate is nullptr"));
+ delegate->GetCountdownTime(countdownTime);
+ return aEncoder.Encode(countdownTime);
+ }
+ break;
+ }
+ return CHIP_NO_ERROR;
+}
diff --git a/src/app/clusters/operational-state-server/operational-state-server.h b/src/app/clusters/operational-state-server/operational-state-server.h
new file mode 100644
index 0000000..247ef37
--- /dev/null
+++ b/src/app/clusters/operational-state-server/operational-state-server.h
@@ -0,0 +1,120 @@
+/*
+ *
+ * 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/AttributeAccessInterface.h>
+#include <app/CommandHandlerInterface.h>
+#include <app/util/af.h>
+
+namespace chip {
+namespace app {
+namespace Clusters {
+namespace OperationalState {
+
+class Uncopyable
+{
+protected:
+ Uncopyable() {}
+ ~Uncopyable() {}
+
+private:
+ Uncopyable(const Uncopyable &) = delete;
+ Uncopyable & operator=(const Uncopyable &) = delete;
+};
+
+/**
+ * OperationalStateServer is a class that represents an instance of a derivation of the operational state cluster.
+ * It implements CommandHandlerInterface so it can generically handle commands for any derivation cluster id.
+ */
+class OperationalStateServer : public CommandHandlerInterface, public AttributeAccessInterface, public Uncopyable
+{
+public:
+ /**
+ * Init the operational state server.
+ * This function must be called after defining a OperationalStateServer class object.
+ * @param void
+ * @return CHIP_ERROR CHIP_NO_ERROR on success, or corresponding error code.
+ */
+ CHIP_ERROR Init();
+
+ /**
+ * Shut down the operational state server.
+ * This function must be called before destroying a OperationalStateServer class object.
+ * @param void
+ */
+ void Shutdown();
+
+ /**
+ * Creates an operational state cluster instance. The Init() function needs to be called for this instance to be registered and
+ * called by the interaction model at the appropriate times.
+ * @param aEndpointId The endpoint on which this cluster exists. This must match the zap configuration.
+ * @param aClusterId The ID of the ModeSelect aliased cluster to be instantiated.
+ */
+ OperationalStateServer(EndpointId aEndpointId, ClusterId aClusterId) :
+ CommandHandlerInterface(MakeOptional(aEndpointId), aClusterId),
+ AttributeAccessInterface(MakeOptional(aEndpointId), aClusterId)
+ {
+
+ mEndpointId = aEndpointId;
+ mClusterId = aClusterId;
+ }
+
+ ~OperationalStateServer() override {}
+
+private:
+ // Inherited from CommandHandlerInterface
+ template <typename RequestT, typename FuncT>
+ void HandleCommand(HandlerContext & handlerContext, FuncT func);
+
+ // Inherited from CommandHandlerInterface
+ void InvokeCommand(HandlerContext & ctx) override;
+
+ /// IM-level implementation of read
+ ///
+ /// Returns appropriately mapped CHIP_ERROR if applicable (may return CHIP_IM_GLOBAL_STATUS errors)
+ CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override;
+
+ /**
+ * Handle Command: Pause.
+ */
+ void HandlePauseState(HandlerContext & ctx, const Commands::Pause::DecodableType & req);
+
+ /**
+ * Handle Command: Resume.
+ */
+ void HandleResumeState(HandlerContext & ctx, const Commands::Resume::DecodableType & req);
+
+ /**
+ * Handle Command: Start.
+ */
+ void HandleStartState(HandlerContext & ctx, const Commands::Start::DecodableType & req);
+
+ /**
+ * Handle Command: Stop.
+ */
+ void HandleStopState(HandlerContext & ctx, const Commands::Stop::DecodableType & req);
+
+ EndpointId mEndpointId;
+ ClusterId mClusterId;
+};
+
+} // namespace OperationalState
+} // namespace Clusters
+} // namespace app
+} // namespace chip
diff --git a/src/app/common/templates/config-data.yaml b/src/app/common/templates/config-data.yaml
index dada0c4..5f28213 100644
--- a/src/app/common/templates/config-data.yaml
+++ b/src/app/common/templates/config-data.yaml
@@ -24,6 +24,7 @@
# This uses asUpperCamelCase versions of the cluster name.
- NetworkCommissioning
- Scenes
+ - OperationalState
# We need a more configurable way of deciding which clusters have which init functions....
# See https://github.com/project-chip/connectedhomeip/issues/4369
diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn
index 22f10f9..c716f44 100644
--- a/src/app/tests/BUILD.gn
+++ b/src/app/tests/BUILD.gn
@@ -106,6 +106,15 @@
]
}
+source_set("operational-state-test-srcs") {
+ sources = [ "${chip_root}/src/app/clusters/operational-state-server/operational-state-delegate.h" ]
+
+ public_deps = [
+ "${chip_root}/src/app/common:cluster-objects",
+ "${chip_root}/src/lib/core",
+ ]
+}
+
chip_test_suite("tests") {
output_name = "libAppTests"
@@ -132,6 +141,7 @@
"TestInteractionModelEngine.cpp",
"TestMessageDef.cpp",
"TestNumericAttributeTraits.cpp",
+ "TestOperationalStateDelegate.cpp",
"TestPendingNotificationMap.cpp",
"TestReadInteraction.cpp",
"TestReportingEngine.cpp",
@@ -172,6 +182,7 @@
public_deps = [
":binding-test-srcs",
":icd-management-test-srcs",
+ ":operational-state-test-srcs",
":ota-requestor-test-srcs",
":scenes-table-test-srcs",
":time-sync-data-provider-test-srcs",
diff --git a/src/app/tests/TestOperationalStateDelegate.cpp b/src/app/tests/TestOperationalStateDelegate.cpp
new file mode 100644
index 0000000..a4d5dce
--- /dev/null
+++ b/src/app/tests/TestOperationalStateDelegate.cpp
@@ -0,0 +1,628 @@
+/*
+ *
+ * Copyright (c) 2023 Project CHIP Authors
+ *
+ * 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 <app/clusters/operational-state-server/operational-state-delegate.h>
+#include <lib/core/CHIPPersistentStorageDelegate.h>
+#include <lib/support/UnitTestRegistration.h>
+
+#include <nlunit-test.h>
+
+using namespace chip;
+using namespace chip::DeviceLayer;
+using namespace chip::app::Clusters::OperationalState;
+
+namespace {
+
+void TestStructGenericOperationalStateConstructorWithOnlyStateID(nlTestSuite * inSuite, void * inContext)
+{
+ using namespace chip::app::Clusters::OperationalState;
+ // General state: Stopped
+ GenericOperationalState operationalStateStopped(to_underlying(OperationalStateEnum::kStopped));
+ NL_TEST_ASSERT(inSuite, operationalStateStopped.operationalStateID == to_underlying(OperationalStateEnum::kStopped));
+ NL_TEST_ASSERT(inSuite, operationalStateStopped.operationalStateLabel.HasValue() == false);
+
+ // General state: Running
+ GenericOperationalState operationalStateRunning(to_underlying(OperationalStateEnum::kRunning));
+ NL_TEST_ASSERT(inSuite, operationalStateRunning.operationalStateID == to_underlying(OperationalStateEnum::kRunning));
+ NL_TEST_ASSERT(inSuite, operationalStateRunning.operationalStateLabel.HasValue() == false);
+
+ // General state: Paused
+ GenericOperationalState operationalStatePaused(to_underlying(OperationalStateEnum::kPaused));
+ NL_TEST_ASSERT(inSuite, operationalStatePaused.operationalStateID == to_underlying(OperationalStateEnum::kPaused));
+ NL_TEST_ASSERT(inSuite, operationalStatePaused.operationalStateLabel.HasValue() == false);
+
+ // General state: Error
+ GenericOperationalState operationalStateError(to_underlying(OperationalStateEnum::kError));
+ NL_TEST_ASSERT(inSuite, operationalStateError.operationalStateID == to_underlying(OperationalStateEnum::kError));
+ NL_TEST_ASSERT(inSuite, operationalStateError.operationalStateLabel.HasValue() == false);
+}
+
+void TestStructGenericOperationalStateConstructorWithStateIDAndStateLabel(nlTestSuite * inSuite, void * inContext)
+{
+ using namespace chip::app::Clusters::OperationalState;
+
+ enum class ManufactureOperationalStateEnum : uint8_t
+ {
+ kRebooting = 0x81,
+ };
+
+ char buffer[kOperationalStateLabelMaxSize] = "rebooting";
+
+ // ManufacturerStates state, label len = 9:
+ GenericOperationalState operationalState(to_underlying(ManufactureOperationalStateEnum::kRebooting),
+ Optional<CharSpan>(CharSpan::fromCharString(buffer)));
+
+ NL_TEST_ASSERT(inSuite, operationalState.operationalStateID == to_underlying(ManufactureOperationalStateEnum::kRebooting));
+ NL_TEST_ASSERT(inSuite, operationalState.operationalStateLabel.HasValue() == true);
+ NL_TEST_ASSERT(inSuite, operationalState.operationalStateLabel.Value().size() == strlen(buffer));
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(operationalState.operationalStateLabel.Value().data()), buffer, strlen(buffer)) == 0);
+}
+
+void TestStructGenericOperationalStateCopyConstructor(nlTestSuite * inSuite, void * inContext)
+{
+ using namespace chip::app::Clusters::OperationalState;
+
+ enum class ManufactureOperationalStateEnum : uint8_t
+ {
+ kRebooting = 0x81,
+ };
+
+ char buffer[kOperationalStateLabelMaxSize] = "rebooting";
+
+ GenericOperationalState srcOperationalState(to_underlying(ManufactureOperationalStateEnum::kRebooting),
+ Optional<CharSpan>(CharSpan::fromCharString(buffer)));
+
+ GenericOperationalState desOperationalState(srcOperationalState);
+
+ NL_TEST_ASSERT(inSuite, desOperationalState.operationalStateID == srcOperationalState.operationalStateID);
+ NL_TEST_ASSERT(inSuite, desOperationalState.operationalStateLabel.HasValue() == true);
+ NL_TEST_ASSERT(inSuite,
+ desOperationalState.operationalStateLabel.Value().size() ==
+ srcOperationalState.operationalStateLabel.Value().size());
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(desOperationalState.operationalStateLabel.Value().data()),
+ const_cast<char *>(srcOperationalState.operationalStateLabel.Value().data()),
+ desOperationalState.operationalStateLabel.Value().size()) == 0);
+}
+
+void TestStructGenericOperationalStateCopyAssignment(nlTestSuite * inSuite, void * inContext)
+{
+ using namespace chip::app::Clusters::OperationalState;
+
+ enum class ManufactureOperationalStateEnum : uint8_t
+ {
+ kRebooting = 0x81,
+ };
+
+ char buffer[kOperationalStateLabelMaxSize] = "rebooting";
+
+ GenericOperationalState srcOperationalState(to_underlying(ManufactureOperationalStateEnum::kRebooting),
+ Optional<CharSpan>(CharSpan::fromCharString(buffer)));
+
+ GenericOperationalState desOperationalState = srcOperationalState;
+
+ NL_TEST_ASSERT(inSuite, desOperationalState.operationalStateID == srcOperationalState.operationalStateID);
+ NL_TEST_ASSERT(inSuite, desOperationalState.operationalStateLabel.HasValue() == true);
+ NL_TEST_ASSERT(inSuite,
+ desOperationalState.operationalStateLabel.Value().size() ==
+ srcOperationalState.operationalStateLabel.Value().size());
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(desOperationalState.operationalStateLabel.Value().data()),
+ const_cast<char *>(srcOperationalState.operationalStateLabel.Value().data()),
+ desOperationalState.operationalStateLabel.Value().size()) == 0);
+}
+
+void TestStructGenericOperationalStateFuncSet(nlTestSuite * inSuite, void * inContext)
+{
+ using namespace chip::app::Clusters::OperationalState;
+
+ enum class ManufactureOperationalStateEnum : uint8_t
+ {
+ kRebooting = 0x81,
+ };
+
+ char buffer[kOperationalStateLabelMaxSize] = "rebooting";
+
+ // init state
+ GenericOperationalState operationalState(to_underlying(ManufactureOperationalStateEnum::kRebooting),
+ Optional<CharSpan>(CharSpan::fromCharString(buffer)));
+
+ // change state without label
+ operationalState.Set(to_underlying(OperationalStateEnum::kStopped));
+ NL_TEST_ASSERT(inSuite, operationalState.operationalStateID == to_underlying(OperationalStateEnum::kStopped));
+ NL_TEST_ASSERT(inSuite, operationalState.operationalStateLabel.HasValue() == false);
+
+ // change state with label
+ operationalState.Set(to_underlying(ManufactureOperationalStateEnum::kRebooting),
+ Optional<CharSpan>(CharSpan::fromCharString(buffer)));
+ NL_TEST_ASSERT(inSuite, operationalState.operationalStateID == to_underlying(ManufactureOperationalStateEnum::kRebooting));
+ NL_TEST_ASSERT(inSuite, operationalState.operationalStateLabel.HasValue() == true);
+ NL_TEST_ASSERT(inSuite, operationalState.operationalStateLabel.Value().size() == strlen(buffer));
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(operationalState.operationalStateLabel.Value().data()), buffer, strlen(buffer)) == 0);
+
+ // change state with label, label len = kOperationalStateLabelMaxSize
+ for (size_t i = 0; i < sizeof(buffer); i++)
+ {
+ buffer[i] = 1;
+ }
+ operationalState.Set(to_underlying(ManufactureOperationalStateEnum::kRebooting),
+ Optional<CharSpan>(CharSpan(buffer, sizeof(buffer))));
+ NL_TEST_ASSERT(inSuite, operationalState.operationalStateID == to_underlying(ManufactureOperationalStateEnum::kRebooting));
+ NL_TEST_ASSERT(inSuite, operationalState.operationalStateLabel.HasValue() == true);
+ NL_TEST_ASSERT(inSuite, operationalState.operationalStateLabel.Value().size() == sizeof(buffer));
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(operationalState.operationalStateLabel.Value().data()), buffer, sizeof(buffer)) == 0);
+
+ // change state with label, label len larger than kOperationalStateLabelMaxSize
+ char buffer2[kOperationalStateLabelMaxSize + 1];
+
+ for (size_t i = 0; i < sizeof(buffer2); i++)
+ {
+ buffer2[i] = 1;
+ }
+ operationalState.Set(to_underlying(ManufactureOperationalStateEnum::kRebooting),
+ Optional<CharSpan>(CharSpan(buffer2, sizeof(buffer2))));
+ NL_TEST_ASSERT(inSuite, operationalState.operationalStateID == to_underlying(ManufactureOperationalStateEnum::kRebooting));
+ NL_TEST_ASSERT(inSuite, operationalState.operationalStateLabel.HasValue() == true);
+ NL_TEST_ASSERT(inSuite, operationalState.operationalStateLabel.Value().size() == kOperationalStateLabelMaxSize);
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(operationalState.operationalStateLabel.Value().data()), buffer2,
+ kOperationalStateLabelMaxSize) == 0);
+}
+
+void TestStructGenericOperationalErrorConstructorWithOnlyStateID(nlTestSuite * inSuite, void * inContext)
+{
+ using namespace chip::app::Clusters::OperationalState;
+ // General errors: NoError
+ GenericOperationalError operationalErrorNoErr(to_underlying(ErrorStateEnum::kNoError));
+
+ NL_TEST_ASSERT(inSuite, operationalErrorNoErr.errorStateID == to_underlying(ErrorStateEnum::kNoError));
+ NL_TEST_ASSERT(inSuite, operationalErrorNoErr.errorStateLabel.HasValue() == false);
+ NL_TEST_ASSERT(inSuite, operationalErrorNoErr.errorStateDetails.HasValue() == false);
+
+ // General errors: UnableToStartOrResume
+ GenericOperationalError operationalErrorUnableToStartOrResume(to_underlying(ErrorStateEnum::kUnableToStartOrResume));
+
+ NL_TEST_ASSERT(inSuite,
+ operationalErrorUnableToStartOrResume.errorStateID == to_underlying(ErrorStateEnum::kUnableToStartOrResume));
+ NL_TEST_ASSERT(inSuite, operationalErrorUnableToStartOrResume.errorStateLabel.HasValue() == false);
+ NL_TEST_ASSERT(inSuite, operationalErrorUnableToStartOrResume.errorStateDetails.HasValue() == false);
+
+ // General errors: UnableToCompleteOperation
+ GenericOperationalError operationalErrorkUnableToCompleteOperation(to_underlying(ErrorStateEnum::kUnableToCompleteOperation));
+
+ NL_TEST_ASSERT(inSuite,
+ operationalErrorkUnableToCompleteOperation.errorStateID ==
+ to_underlying(ErrorStateEnum::kUnableToCompleteOperation));
+ NL_TEST_ASSERT(inSuite, operationalErrorkUnableToCompleteOperation.errorStateLabel.HasValue() == false);
+ NL_TEST_ASSERT(inSuite, operationalErrorkUnableToCompleteOperation.errorStateDetails.HasValue() == false);
+
+ // General errors: CommandInvalidInState
+ GenericOperationalError operationalErrorCommandInvalidInState(to_underlying(ErrorStateEnum::kCommandInvalidInState));
+
+ NL_TEST_ASSERT(inSuite,
+ operationalErrorCommandInvalidInState.errorStateID == to_underlying(ErrorStateEnum::kCommandInvalidInState));
+ NL_TEST_ASSERT(inSuite, operationalErrorCommandInvalidInState.errorStateLabel.HasValue() == false);
+ NL_TEST_ASSERT(inSuite, operationalErrorCommandInvalidInState.errorStateDetails.HasValue() == false);
+}
+
+void TestStructGenericOperationalErrorConstructorWithStateIDAndStateLabel(nlTestSuite * inSuite, void * inContext)
+{
+ using namespace chip::app::Clusters::OperationalState;
+
+ enum class ManufactureOperationalErrorEnum : uint8_t
+ {
+ kLowBattery = 0x81,
+ };
+
+ char labelBuffer[kOperationalErrorLabelMaxSize] = "low battery";
+
+ // ManufacturerStates error with label, label len = 11:
+ GenericOperationalError operationalError(to_underlying(ManufactureOperationalErrorEnum::kLowBattery),
+ Optional<CharSpan>(CharSpan::fromCharString(labelBuffer)));
+
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateID == to_underlying(ManufactureOperationalErrorEnum::kLowBattery));
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateLabel.HasValue() == true);
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateLabel.Value().size() == strlen(labelBuffer));
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(operationalError.errorStateLabel.Value().data()), labelBuffer, strlen(labelBuffer)) ==
+ 0);
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateDetails.HasValue() == false);
+}
+
+void TestStructGenericOperationalErrorConstructorWithFullParam(nlTestSuite * inSuite, void * inContext)
+{
+ using namespace chip::app::Clusters::OperationalState;
+
+ enum class ManufactureOperationalErrorEnum : uint8_t
+ {
+ kLowBattery = 0x81,
+ };
+
+ // ManufacturerStates error with label(label len = 11) and detail (len = 25):
+ char labelBuffer[kOperationalErrorLabelMaxSize] = "low battery";
+ char detailBuffer[kOperationalErrorDetailsMaxSize] = "Please plug in for charge";
+
+ GenericOperationalError operationalError(to_underlying(ManufactureOperationalErrorEnum::kLowBattery),
+ Optional<CharSpan>(CharSpan::fromCharString(labelBuffer)),
+ Optional<CharSpan>(CharSpan::fromCharString(detailBuffer)));
+
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateID == to_underlying(ManufactureOperationalErrorEnum::kLowBattery));
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateLabel.HasValue() == true);
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateLabel.Value().size() == strlen(labelBuffer));
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(operationalError.errorStateLabel.Value().data()), labelBuffer, strlen(labelBuffer)) ==
+ 0);
+
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateDetails.HasValue() == true);
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateDetails.Value().size() == strlen(detailBuffer));
+ NL_TEST_ASSERT(
+ inSuite,
+ memcmp(const_cast<char *>(operationalError.errorStateDetails.Value().data()), detailBuffer, strlen(detailBuffer)) == 0);
+}
+
+void TestStructGenericOperationalErrorCopyConstructor(nlTestSuite * inSuite, void * inContext)
+{
+ using namespace chip::app::Clusters::OperationalState;
+
+ enum class ManufactureOperationalErrorEnum : uint8_t
+ {
+ kLowBattery = 0x81,
+ };
+
+ // ManufacturerStates error with label(label len = 11) and detail (len = 25):
+ char labelBuffer[kOperationalErrorLabelMaxSize] = "low battery";
+ char detailBuffer[kOperationalErrorDetailsMaxSize] = "Please plug in for charge";
+
+ GenericOperationalError srcOperationalError(to_underlying(ManufactureOperationalErrorEnum::kLowBattery),
+ Optional<CharSpan>(CharSpan::fromCharString(labelBuffer)),
+ Optional<CharSpan>(CharSpan::fromCharString(detailBuffer)));
+
+ // call copy constructor
+ GenericOperationalError desOperationalError(srcOperationalError);
+ NL_TEST_ASSERT(inSuite, desOperationalError.errorStateID == srcOperationalError.errorStateID);
+ NL_TEST_ASSERT(inSuite, desOperationalError.errorStateLabel.HasValue() == true);
+ NL_TEST_ASSERT(inSuite,
+ desOperationalError.errorStateLabel.Value().size() == srcOperationalError.errorStateLabel.Value().size());
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(desOperationalError.errorStateLabel.Value().data()),
+ const_cast<char *>(srcOperationalError.errorStateLabel.Value().data()),
+ desOperationalError.errorStateLabel.Value().size()) == 0);
+
+ NL_TEST_ASSERT(inSuite, desOperationalError.errorStateDetails.HasValue() == true);
+ NL_TEST_ASSERT(inSuite,
+ desOperationalError.errorStateDetails.Value().size() == srcOperationalError.errorStateDetails.Value().size());
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(desOperationalError.errorStateDetails.Value().data()),
+ const_cast<char *>(srcOperationalError.errorStateDetails.Value().data()),
+ desOperationalError.errorStateDetails.Value().size()) == 0);
+}
+
+void TestStructGenericOperationalErrorCopyAssignment(nlTestSuite * inSuite, void * inContext)
+{
+ using namespace chip::app::Clusters::OperationalState;
+
+ enum class ManufactureOperationalErrorEnum : uint8_t
+ {
+ kLowBattery = 0x81,
+ };
+
+ // ManufacturerStates error with label(label len = 11) and detail (len = 25):
+ char labelBuffer[kOperationalErrorLabelMaxSize] = "low battery";
+ char detailBuffer[kOperationalErrorDetailsMaxSize] = "Please plug in for charge";
+
+ GenericOperationalError srcOperationalError(to_underlying(ManufactureOperationalErrorEnum::kLowBattery),
+ Optional<CharSpan>(CharSpan::fromCharString(labelBuffer)),
+ Optional<CharSpan>(CharSpan::fromCharString(detailBuffer)));
+
+ // call copy assignment
+ GenericOperationalError desOperationalError = srcOperationalError;
+ NL_TEST_ASSERT(inSuite, desOperationalError.errorStateID == srcOperationalError.errorStateID);
+ NL_TEST_ASSERT(inSuite, desOperationalError.errorStateLabel.HasValue() == true);
+ NL_TEST_ASSERT(inSuite,
+ desOperationalError.errorStateLabel.Value().size() == srcOperationalError.errorStateLabel.Value().size());
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(desOperationalError.errorStateLabel.Value().data()),
+ const_cast<char *>(srcOperationalError.errorStateLabel.Value().data()),
+ desOperationalError.errorStateLabel.Value().size()) == 0);
+
+ NL_TEST_ASSERT(inSuite, desOperationalError.errorStateDetails.HasValue() == true);
+ NL_TEST_ASSERT(inSuite,
+ desOperationalError.errorStateDetails.Value().size() == srcOperationalError.errorStateDetails.Value().size());
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(desOperationalError.errorStateDetails.Value().data()),
+ const_cast<char *>(srcOperationalError.errorStateDetails.Value().data()),
+ desOperationalError.errorStateDetails.Value().size()) == 0);
+}
+
+void TestStructGenericOperationalErrorFuncSet(nlTestSuite * inSuite, void * inContext)
+{
+ using namespace chip::app::Clusters::OperationalState;
+ enum class ManufactureOperationalErrorEnum : uint8_t
+ {
+ kLowBattery = 0x81,
+ };
+
+ // ManufacturerStates error with label(label len = 11) and detail (len = 25):
+ char labelBuffer[kOperationalErrorLabelMaxSize] = "low battery";
+ char detailBuffer[kOperationalErrorDetailsMaxSize] = "Please plug in for charge";
+
+ // General errors: NoError
+ GenericOperationalError operationalError(to_underlying(ErrorStateEnum::kNoError));
+
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateID == to_underlying(ErrorStateEnum::kNoError));
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateLabel.HasValue() == false);
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateDetails.HasValue() == false);
+
+ // call Set with stateId
+ operationalError.Set(to_underlying(ErrorStateEnum::kUnableToStartOrResume));
+
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateID == to_underlying(ErrorStateEnum::kUnableToStartOrResume));
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateLabel.HasValue() == false);
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateDetails.HasValue() == false);
+
+ // call Set with stateId and StateLabel
+ operationalError.Set(to_underlying(ErrorStateEnum::kUnableToStartOrResume),
+ Optional<CharSpan>(CharSpan::fromCharString(labelBuffer)));
+
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateID == to_underlying(ErrorStateEnum::kUnableToStartOrResume));
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateLabel.HasValue() == true);
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateLabel.Value().size() == strlen(labelBuffer));
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(operationalError.errorStateLabel.Value().data()), labelBuffer, strlen(labelBuffer)) ==
+ 0);
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateDetails.HasValue() == false);
+
+ // call Set with stateId, StateLabel and StateDetails
+ operationalError.Set(to_underlying(ErrorStateEnum::kUnableToStartOrResume),
+ Optional<CharSpan>(CharSpan::fromCharString(labelBuffer)),
+ Optional<CharSpan>(CharSpan::fromCharString(detailBuffer)));
+
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateID == to_underlying(ErrorStateEnum::kUnableToStartOrResume));
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateLabel.HasValue() == true);
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateLabel.Value().size() == strlen(labelBuffer));
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(operationalError.errorStateLabel.Value().data()), labelBuffer, strlen(labelBuffer)) ==
+ 0);
+
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateDetails.HasValue() == true);
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateDetails.Value().size() == strlen(detailBuffer));
+ NL_TEST_ASSERT(
+ inSuite,
+ memcmp(const_cast<char *>(operationalError.errorStateDetails.Value().data()), detailBuffer, strlen(detailBuffer)) == 0);
+
+ // change state with label, label len = kOperationalStateLabelMaxSize
+ for (size_t i = 0; i < sizeof(labelBuffer); i++)
+ {
+ labelBuffer[i] = 1;
+ }
+ operationalError.Set(to_underlying(ErrorStateEnum::kUnableToStartOrResume),
+ Optional<CharSpan>(CharSpan(labelBuffer, sizeof(labelBuffer))));
+
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateID == to_underlying(ErrorStateEnum::kUnableToStartOrResume));
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateLabel.HasValue() == true);
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateLabel.Value().size() == sizeof(labelBuffer));
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(operationalError.errorStateLabel.Value().data()), labelBuffer, sizeof(labelBuffer)) ==
+ 0);
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateDetails.HasValue() == false);
+
+ // change state with label, label len = kOperationalStateLabelMaxSize + 1
+ char labelBuffer2[kOperationalErrorLabelMaxSize + 1];
+ for (size_t i = 0; i < sizeof(labelBuffer2); i++)
+ {
+ labelBuffer2[i] = 2;
+ }
+ operationalError.Set(to_underlying(ErrorStateEnum::kUnableToStartOrResume),
+ Optional<CharSpan>(CharSpan(labelBuffer2, sizeof(labelBuffer2))));
+
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateID == to_underlying(ErrorStateEnum::kUnableToStartOrResume));
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateLabel.HasValue() == true);
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateLabel.Value().size() == kOperationalErrorLabelMaxSize);
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(operationalError.errorStateLabel.Value().data()), labelBuffer2,
+ kOperationalErrorLabelMaxSize) == 0);
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateDetails.HasValue() == false);
+
+ // change state with label and details, details len = kOperationalErrorDetailsMaxSize + 1
+ char detailBuffer2[kOperationalErrorDetailsMaxSize + 1];
+ for (size_t i = 0; i < sizeof(detailBuffer2); i++)
+ {
+ detailBuffer2[i] = 3;
+ }
+ operationalError.Set(to_underlying(ErrorStateEnum::kUnableToStartOrResume),
+ Optional<CharSpan>(CharSpan(labelBuffer2, sizeof(labelBuffer2))),
+ Optional<CharSpan>(CharSpan(detailBuffer2, sizeof(detailBuffer2))));
+
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateID == to_underlying(ErrorStateEnum::kUnableToStartOrResume));
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateLabel.HasValue() == true);
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateLabel.Value().size() == kOperationalErrorLabelMaxSize);
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(operationalError.errorStateLabel.Value().data()), labelBuffer2,
+ kOperationalErrorLabelMaxSize) == 0);
+
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateDetails.HasValue() == true);
+
+ NL_TEST_ASSERT(inSuite, operationalError.errorStateDetails.Value().size() == kOperationalErrorDetailsMaxSize);
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(operationalError.errorStateDetails.Value().data()), detailBuffer2,
+ kOperationalErrorDetailsMaxSize) == 0);
+}
+
+void TestStructGenericOperationalPhaseConstructor(nlTestSuite * inSuite, void * inContext)
+{
+ using namespace chip::app;
+ using namespace chip::app::Clusters::OperationalState;
+
+ GenericOperationalPhase phase = GenericOperationalPhase(DataModel::Nullable<CharSpan>());
+ NL_TEST_ASSERT(inSuite, phase.IsMissing() == true);
+
+ char phaseBuffer[kOperationalPhaseNameMaxSize] = "start";
+ GenericOperationalPhase phase2(DataModel::Nullable<CharSpan>(CharSpan::fromCharString(phaseBuffer)));
+ NL_TEST_ASSERT(inSuite, phase2.IsMissing() == false);
+ NL_TEST_ASSERT(inSuite, phase2.mPhaseName.Value().size() == strlen(phaseBuffer));
+ NL_TEST_ASSERT(inSuite, memcmp(const_cast<char *>(phase2.mPhaseName.Value().data()), phaseBuffer, strlen(phaseBuffer)) == 0);
+}
+
+void TestStructGenericOperationalPhaseCopyConstructor(nlTestSuite * inSuite, void * inContext)
+{
+ using namespace chip::app;
+ using namespace chip::app::Clusters::OperationalState;
+
+ char phaseBuffer[kOperationalPhaseNameMaxSize] = "start";
+ GenericOperationalPhase phase(DataModel::Nullable<CharSpan>(CharSpan::fromCharString(phaseBuffer)));
+
+ GenericOperationalPhase phase2(phase);
+
+ NL_TEST_ASSERT(inSuite, phase2.IsMissing() == false);
+ NL_TEST_ASSERT(inSuite, phase2.mPhaseName.Value().size() == phase.mPhaseName.Value().size());
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(phase2.mPhaseName.Value().data()), const_cast<char *>(phase.mPhaseName.Value().data()),
+ phase.mPhaseName.Value().size()) == 0);
+}
+
+void TestStructGenericOperationalPhaseCopyAssignment(nlTestSuite * inSuite, void * inContext)
+{
+ using namespace chip::app;
+ using namespace chip::app::Clusters::OperationalState;
+
+ // copy assignment with null-name
+ GenericOperationalPhase phase = GenericOperationalPhase(DataModel::Nullable<CharSpan>());
+ NL_TEST_ASSERT(inSuite, phase.IsMissing() == true);
+
+ // copy assignment with name
+ char phaseBuffer[kOperationalPhaseNameMaxSize] = "start";
+ GenericOperationalPhase phase2(DataModel::Nullable<CharSpan>(CharSpan::fromCharString(phaseBuffer)));
+ phase = phase2;
+
+ NL_TEST_ASSERT(inSuite, phase.IsMissing() == false);
+ NL_TEST_ASSERT(inSuite, phase.mPhaseName.Value().size() == phase2.mPhaseName.Value().size());
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(phase.mPhaseName.Value().data()), const_cast<char *>(phase2.mPhaseName.Value().data()),
+ phase.mPhaseName.Value().size()) == 0);
+
+ // copy assignment with name, name's len = kOperationalPhaseNameMaxSize
+ for (size_t i = 0; i < sizeof(phaseBuffer); i++)
+ {
+ phaseBuffer[i] = 1;
+ }
+ phase = GenericOperationalPhase(DataModel::Nullable<CharSpan>(CharSpan(phaseBuffer, sizeof(phaseBuffer))));
+
+ NL_TEST_ASSERT(inSuite, phase.IsMissing() == false);
+ NL_TEST_ASSERT(inSuite, phase.mPhaseName.Value().size() == sizeof(phaseBuffer));
+ NL_TEST_ASSERT(inSuite, memcmp(const_cast<char *>(phase.mPhaseName.Value().data()), phaseBuffer, sizeof(phaseBuffer)) == 0);
+
+ // copy assignment with name, name's len = kOperationalPhaseNameMaxSize + 1
+ char phaseBuffer2[kOperationalPhaseNameMaxSize + 1];
+ for (size_t i = 0; i < sizeof(phaseBuffer2); i++)
+ {
+ phaseBuffer2[i] = 2;
+ }
+ phase = GenericOperationalPhase(DataModel::Nullable<CharSpan>(CharSpan(phaseBuffer2, sizeof(phaseBuffer2))));
+
+ NL_TEST_ASSERT(inSuite, phase.IsMissing() == false);
+ NL_TEST_ASSERT(inSuite, phase.mPhaseName.Value().size() == kOperationalPhaseNameMaxSize);
+ NL_TEST_ASSERT(inSuite,
+ memcmp(const_cast<char *>(phase.mPhaseName.Value().data()), phaseBuffer2, kOperationalPhaseNameMaxSize) == 0);
+}
+
+void TestStructGenericOperationalCompletionConstructor(nlTestSuite * inSuite, void * inContext)
+{
+ using namespace chip::app;
+ using namespace chip::app::Clusters::OperationalState;
+
+ // completion with only CompletionErrorCode
+ GenericOperationCompletion genericOperationCompletion(to_underlying(OperationalStateEnum::kError));
+ NL_TEST_ASSERT(inSuite, genericOperationCompletion.completionErrorCode == to_underlying(OperationalStateEnum::kError));
+ NL_TEST_ASSERT(inSuite, genericOperationCompletion.totalOperationalTime.HasValue() == false);
+ NL_TEST_ASSERT(inSuite, genericOperationCompletion.pausedTime.HasValue() == false);
+
+ // completion with errorCode and TotalOperationalTime
+ uint32_t kTotalOperationalTime = 500;
+ GenericOperationCompletion genericOperationCompletion2(
+ to_underlying(OperationalStateEnum::kError),
+ Optional<DataModel::Nullable<uint32_t>>(DataModel::Nullable<uint32_t>(kTotalOperationalTime)));
+ NL_TEST_ASSERT(inSuite, genericOperationCompletion2.completionErrorCode == to_underlying(OperationalStateEnum::kError));
+
+ NL_TEST_ASSERT(inSuite, genericOperationCompletion2.totalOperationalTime.HasValue() == true);
+ NL_TEST_ASSERT(inSuite, genericOperationCompletion2.totalOperationalTime.Value().Value() == kTotalOperationalTime);
+ NL_TEST_ASSERT(inSuite, genericOperationCompletion2.pausedTime.HasValue() == false);
+
+ // completion with errorCode, TotalOperationalTime and PausedTime
+ uint32_t kPausedTime = 2000;
+ GenericOperationCompletion genericOperationCompletion3(
+ to_underlying(OperationalStateEnum::kError),
+ Optional<DataModel::Nullable<uint32_t>>(DataModel::Nullable<uint32_t>(kTotalOperationalTime)),
+ Optional<DataModel::Nullable<uint32_t>>(DataModel::Nullable<uint32_t>(kPausedTime)));
+ NL_TEST_ASSERT(inSuite, genericOperationCompletion3.completionErrorCode == to_underlying(OperationalStateEnum::kError));
+
+ NL_TEST_ASSERT(inSuite, genericOperationCompletion3.totalOperationalTime.HasValue() == true);
+ NL_TEST_ASSERT(inSuite, genericOperationCompletion3.totalOperationalTime.Value().Value() == kTotalOperationalTime);
+ NL_TEST_ASSERT(inSuite, genericOperationCompletion3.pausedTime.HasValue() == true);
+ NL_TEST_ASSERT(inSuite, genericOperationCompletion3.pausedTime.Value().Value() == kPausedTime);
+}
+
+const nlTest sTests[] = {
+ NL_TEST_DEF("Test struct GenericOperationalState: constructor with only StateID",
+ TestStructGenericOperationalStateConstructorWithOnlyStateID),
+ NL_TEST_DEF("Test struct GenericOperationalState: constructor with StateID and StateLabel",
+ TestStructGenericOperationalStateConstructorWithStateIDAndStateLabel),
+ NL_TEST_DEF("Test struct GenericOperationalState: copy constructor", TestStructGenericOperationalStateCopyConstructor),
+ NL_TEST_DEF("Test struct GenericOperationalState: copy assignment", TestStructGenericOperationalStateCopyAssignment),
+ NL_TEST_DEF("Test struct GenericOperationalState: member function 'Set'", TestStructGenericOperationalStateFuncSet),
+ NL_TEST_DEF("Test struct GenericOperationalError: constructor with only StateID",
+ TestStructGenericOperationalErrorConstructorWithOnlyStateID),
+ NL_TEST_DEF("Test struct GenericOperationalError: constructor with StateID and StateLabel",
+ TestStructGenericOperationalErrorConstructorWithStateIDAndStateLabel),
+ NL_TEST_DEF("Test struct GenericOperationalError: constructor with StateID, StateLabel and StateDetail",
+ TestStructGenericOperationalErrorConstructorWithFullParam),
+ NL_TEST_DEF("Test struct GenericOperationalError: copy constructor", TestStructGenericOperationalErrorCopyConstructor),
+ NL_TEST_DEF("Test struct GenericOperationalError: copy assignment", TestStructGenericOperationalErrorCopyAssignment),
+ NL_TEST_DEF("Test struct GenericOperationalError: member function 'Set'", TestStructGenericOperationalErrorFuncSet),
+ NL_TEST_DEF("Test struct GenericOperationalPhase: constructor", TestStructGenericOperationalPhaseConstructor),
+ NL_TEST_DEF("Test struct GenericOperationalPhase: copy constructor", TestStructGenericOperationalPhaseCopyConstructor),
+ NL_TEST_DEF("Test struct GenericOperationalPhase: copy assignment", TestStructGenericOperationalPhaseCopyAssignment),
+ NL_TEST_DEF("Test struct GenericOperationalCompletion: constructor", TestStructGenericOperationalCompletionConstructor),
+ NL_TEST_SENTINEL()
+};
+
+int TestSetup(void * inContext)
+{
+ VerifyOrReturnError(CHIP_NO_ERROR == chip::Platform::MemoryInit(), FAILURE);
+ return SUCCESS;
+}
+
+int TestTearDown(void * inContext)
+{
+ chip::Platform::MemoryShutdown();
+ return SUCCESS;
+}
+
+} // namespace
+
+int TestOperationalStateDelegate()
+{
+ nlTestSuite theSuite = { "Test Operational State delegate tests", &sTests[0], TestSetup, TestTearDown };
+
+ // Run test suit againt one context.
+ nlTestRunner(&theSuite, nullptr);
+ return nlTestRunnerStats(&theSuite);
+}
+
+CHIP_REGISTER_TEST_SUITE(TestOperationalStateDelegate)
diff --git a/src/app/tests/suites/TestOperationalState.yaml b/src/app/tests/suites/TestOperationalState.yaml
new file mode 100644
index 0000000..62ad14d
--- /dev/null
+++ b/src/app/tests/suites/TestOperationalState.yaml
@@ -0,0 +1,123 @@
+# Copyright (c) 2023 Project CHIP Authors
+#
+# 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.
+
+name: Operational State Tests
+
+config:
+ nodeId: 0x12344321
+ cluster: "Operational State"
+ endpoint: 1
+
+tests:
+ - label: "Wait for the commissioned device to be retrieved"
+ cluster: "DelayCommands"
+ command: "WaitForCommissionee"
+ arguments:
+ values:
+ - name: "nodeId"
+ value: nodeId
+
+ - label: "Read Phase List"
+ command: "readAttribute"
+ attribute: "PhaseList"
+ response:
+ value: null
+
+ - label: "Read current Phase"
+ command: "readAttribute"
+ attribute: "CurrentPhase"
+ response:
+ value: null
+
+ - label: "Read Countdown Time"
+ command: "readAttribute"
+ attribute: "CountdownTime"
+ response:
+ value: null
+
+ - label: "Read Operational State List"
+ command: "readAttribute"
+ attribute: "OperationalStateList"
+ response:
+ value:
+ [
+ { OperationalStateID: 0 },
+ { OperationalStateID: 1 },
+ { OperationalStateID: 2 },
+ { OperationalStateID: 3 },
+ ]
+
+ - label: "Read current Operational Error"
+ command: "readAttribute"
+ attribute: "OperationalError"
+ response:
+ value: { ErrorStateID: 0 }
+
+ - label: "Read current Operational State"
+ command: "readAttribute"
+ attribute: "OperationalState"
+ response:
+ value: { OperationalStateID: 0 }
+
+ - label: "Start Command"
+ command: "Start"
+ response:
+ values:
+ - name: "CommandResponseState"
+ value: { ErrorStateID: 0 }
+
+ - label: "Read current Operational State"
+ command: "readAttribute"
+ attribute: "OperationalState"
+ response:
+ value: { OperationalStateID: 1 }
+
+ - label: "Pause Command"
+ command: "Pause"
+ response:
+ values:
+ - name: "CommandResponseState"
+ value: { ErrorStateID: 0 }
+
+ - label: "Read current Operational State"
+ command: "readAttribute"
+ attribute: "OperationalState"
+ response:
+ value: { OperationalStateID: 2 }
+
+ - label: "Resume Command"
+ command: "Resume"
+ response:
+ values:
+ - name: "CommandResponseState"
+ value: { ErrorStateID: 0 }
+
+ - label: "Read current Operational State"
+ command: "readAttribute"
+ attribute: "OperationalState"
+ response:
+ value: { OperationalStateID: 1 }
+
+ - label: "Stop Command"
+ command: "Stop"
+ response:
+ values:
+ - name: "CommandResponseState"
+ value: { ErrorStateID: 0 }
+
+ - label: "Read current Operational State"
+ command: "readAttribute"
+ attribute: "OperationalState"
+ response:
+ value: { OperationalStateID: 0 }
diff --git a/src/app/tests/suites/ciTests.json b/src/app/tests/suites/ciTests.json
index 3018879..71a6406 100644
--- a/src/app/tests/suites/ciTests.json
+++ b/src/app/tests/suites/ciTests.json
@@ -257,7 +257,8 @@
"TestLevelControlWithOnOffDependency",
"TestCommissioningWindow",
"TestCommissionerNodeId",
- "TestTimeSynchronization"
+ "TestTimeSynchronization",
+ "TestOperationalState"
],
"MultiAdmin": ["TestMultiAdmin"],
"SoftwareDiagnostics": ["Test_TC_DGSW_1_1"],
diff --git a/src/app/util/util.cpp b/src/app/util/util.cpp
index ed27dea..7ae0d17 100644
--- a/src/app/util/util.cpp
+++ b/src/app/util/util.cpp
@@ -159,6 +159,7 @@
void MatterPm25ConcentrationMeasurementPluginServerInitCallback() {}
void MatterRadonConcentrationMeasurementPluginServerInitCallback() {}
void MatterTotalVolatileOrganicCompoundsConcentrationMeasurementPluginServerInitCallback() {}
+void MatterOperationalStatePluginServerInitCallback() {}
// ****************************************
// Print out information about each cluster
// ****************************************
diff --git a/src/app/zap-templates/zcl/zcl-with-test-extensions.json b/src/app/zap-templates/zcl/zcl-with-test-extensions.json
index 83c069e..667bcd8 100644
--- a/src/app/zap-templates/zcl/zcl-with-test-extensions.json
+++ b/src/app/zap-templates/zcl/zcl-with-test-extensions.json
@@ -306,8 +306,18 @@
"DSTOffsetListMaxSize"
],
"Temperature Control": ["SupportedTemperatureLevels"],
- "Operational State": ["OperationalState", "OperationalError"],
- "RVC Operational State": ["OperationalState", "OperationalError"]
+ "Operational State": [
+ "OperationalState",
+ "OperationalError",
+ "CurrentPhase",
+ "CountdownTime"
+ ],
+ "RVC Operational State": [
+ "OperationalState",
+ "OperationalError",
+ "CurrentPhase",
+ "CountdownTime"
+ ]
},
"defaultReportingPolicy": "mandatory",
"ZCLDataTypes": ["ARRAY", "BITMAP", "ENUM", "NUMBER", "STRING", "STRUCT"],
diff --git a/src/app/zap-templates/zcl/zcl.json b/src/app/zap-templates/zcl/zcl.json
index b6554bf..8ece026 100644
--- a/src/app/zap-templates/zcl/zcl.json
+++ b/src/app/zap-templates/zcl/zcl.json
@@ -304,8 +304,18 @@
"DSTOffsetListMaxSize"
],
"Temperature Control": ["SupportedTemperatureLevels"],
- "Operational State": ["OperationalState", "OperationalError"],
- "RVC Operational State": ["OperationalState", "OperationalError"]
+ "Operational State": [
+ "OperationalState",
+ "OperationalError",
+ "CurrentPhase",
+ "CountdownTime"
+ ],
+ "RVC Operational State": [
+ "OperationalState",
+ "OperationalError",
+ "CurrentPhase",
+ "CountdownTime"
+ ]
},
"defaultReportingPolicy": "mandatory",
"ZCLDataTypes": ["ARRAY", "BITMAP", "ENUM", "NUMBER", "STRING", "STRUCT"],
diff --git a/src/app/zap_cluster_list.json b/src/app/zap_cluster_list.json
index 6063b94..ca9592b 100644
--- a/src/app/zap_cluster_list.json
+++ b/src/app/zap_cluster_list.json
@@ -240,6 +240,7 @@
"ON_OFF_CLUSTER": ["on-off-server"],
"ON_OFF_SWITCH_CONFIGURATION_CLUSTER": [],
"OPERATIONAL_CREDENTIALS_CLUSTER": ["operational-credentials-server"],
+ "OPERATIONAL_STATE_CLUSTER": ["operational-state-server"],
"OTA_BOOTLOAD_CLUSTER": [],
"OTA_SOFTWARE_UPDATE_PROVIDER_CLUSTER": ["ota-provider"],
"OTA_SOFTWARE_UPDATE_REQUESTOR_CLUSTER": ["ota-requestor"],