From 2a92c265687e382e3835a483956a0daac869ae02 Mon Sep 17 00:00:00 2001 From: mideayanghui <106149377+mideayanghui@users.noreply.github.com> Date: Thu, 13 Jul 2023 15:31:13 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90Feature=E3=80=91operational=20state=20?= =?UTF-8?q?cluster=20sdk=20implementation=20and=20example=20cluster=20serv?= =?UTF-8?q?er=20application=20(#26971)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 * 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 Co-authored-by: Justin Wood Co-authored-by: Boris Zbarsky --- .../all-clusters-app.matter | 77 +++ .../all-clusters-common/all-clusters-app.zap | 304 +++++++++ .../include/operational-state-delegate-impl.h | 151 +++++ .../src/operational-state-delegate-impl.cpp | 122 ++++ .../src/operational-state-delegates.cpp | 133 ++++ examples/all-clusters-app/linux/BUILD.gn | 2 + .../all-clusters-app/linux/main-common.cpp | 7 + .../templates/tests/ciTests.json | 2 + src/app/chip_data_model.gni | 6 + .../operational-state-delegate.h | 322 +++++++++ .../operational-state-server.cpp | 305 +++++++++ .../operational-state-server.h | 120 ++++ src/app/common/templates/config-data.yaml | 1 + src/app/tests/BUILD.gn | 11 + .../tests/TestOperationalStateDelegate.cpp | 628 ++++++++++++++++++ .../tests/suites/TestOperationalState.yaml | 123 ++++ src/app/tests/suites/ciTests.json | 3 +- src/app/util/util.cpp | 1 + .../zcl/zcl-with-test-extensions.json | 14 +- src/app/zap-templates/zcl/zcl.json | 14 +- src/app/zap_cluster_list.json | 1 + .../zap-generated/attributes/Accessors.cpp | 212 ------ .../zap-generated/attributes/Accessors.h | 28 - .../app-common/zap-generated/callback.h | 24 - .../chip-tool/zap-generated/test/Commands.h | 285 ++++++++ 25 files changed, 2627 insertions(+), 269 deletions(-) create mode 100644 examples/all-clusters-app/all-clusters-common/include/operational-state-delegate-impl.h create mode 100644 examples/all-clusters-app/all-clusters-common/src/operational-state-delegate-impl.cpp create mode 100644 examples/all-clusters-app/all-clusters-common/src/operational-state-delegates.cpp create mode 100644 src/app/clusters/operational-state-server/operational-state-delegate.h create mode 100644 src/app/clusters/operational-state-server/operational-state-server.cpp create mode 100644 src/app/clusters/operational-state-server/operational-state-server.h create mode 100644 src/app/tests/TestOperationalStateDelegate.cpp create mode 100644 src/app/tests/suites/TestOperationalState.yaml diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter index 97833256a2a446..09714664693c69 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter @@ -2542,6 +2542,66 @@ server cluster AirQuality = 91 { readonly attribute int16u clusterRevision = 65533; } +/** This cluster supports remotely monitoring and, where supported, changing the operational state of any device where a state machine is a part of the operation. */ +server cluster OperationalState = 96 { + enum ErrorStateEnum : ENUM8 { + kNoError = 0; + kUnableToStartOrResume = 1; + kUnableToCompleteOperation = 2; + kCommandInvalidInState = 3; + } + + enum OperationalStateEnum : ENUM8 { + kStopped = 0; + kRunning = 1; + kPaused = 2; + kError = 3; + } + + struct ErrorStateStruct { + enum8 errorStateID = 0; + optional char_string<64> errorStateLabel = 1; + optional char_string<64> errorStateDetails = 2; + } + + struct OperationalStateStruct { + enum8 operationalStateID = 0; + optional char_string<64> operationalStateLabel = 1; + } + + critical event OperationalError = 0 { + ErrorStateStruct errorState = 0; + } + + info event OperationCompletion = 1 { + ENUM8 completionErrorCode = 0; + optional nullable elapsed_s totalOperationalTime = 1; + optional nullable elapsed_s pausedTime = 2; + } + + readonly attribute nullable CHAR_STRING phaseList[] = 0; + readonly attribute nullable int8u currentPhase = 1; + readonly attribute nullable elapsed_s countdownTime = 2; + readonly attribute OperationalStateStruct operationalStateList[] = 3; + readonly attribute OperationalStateStruct operationalState = 4; + readonly attribute ErrorStateStruct operationalError = 5; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + response struct OperationalCommandResponse = 4 { + ErrorStateStruct commandResponseState = 0; + } + + command Pause(): OperationalCommandResponse = 0; + command Stop(): OperationalCommandResponse = 1; + command Start(): OperationalCommandResponse = 2; + command Resume(): OperationalCommandResponse = 3; +} + /** Attributes and commands for monitoring HEPA filters in a device */ server cluster HepaFilterMonitoring = 113 { enum ChangeIndicationEnum : ENUM8 { @@ -6497,6 +6557,23 @@ endpoint 1 { ram attribute clusterRevision default = 1; } + server cluster OperationalState { + emits event OperationalError; + emits event OperationCompletion; + callback attribute phaseList; + callback attribute currentPhase; + callback attribute countdownTime; + callback attribute operationalStateList; + callback attribute operationalState; + callback attribute operationalError; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + } + server cluster HepaFilterMonitoring { ram attribute condition; ram attribute degradationDirection; diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap index 518c6e563eaa70..84fc675ec2f9da 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap @@ -13890,6 +13890,310 @@ } ] }, + { + "name": "Operational State", + "code": 96, + "mfgCode": null, + "define": "OPERATIONAL_STATE_CLUSTER", + "side": "client", + "enabled": 0, + "commands": [ + { + "name": "Pause", + "code": 0, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 0 + }, + { + "name": "Stop", + "code": 1, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 0 + }, + { + "name": "Start", + "code": 2, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 0 + }, + { + "name": "Resume", + "code": 3, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 0 + } + ], + "attributes": [ + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "client", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "client", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Operational State", + "code": 96, + "mfgCode": null, + "define": "OPERATIONAL_STATE_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "OperationalCommandResponse", + "code": 4, + "mfgCode": null, + "source": "server", + "incoming": 0, + "outgoing": 1 + } + ], + "attributes": [ + { + "name": "PhaseList", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "CurrentPhase", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "CountdownTime", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "OperationalStateList", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "OperationalState", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "OperationalStateStruct", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "OperationalError", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "ErrorStateStruct", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ], + "events": [ + { + "name": "OperationalError", + "code": 0, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "OperationCompletion", + "code": 1, + "mfgCode": null, + "side": "server", + "included": 1 + } + ] + }, { "name": "HEPA Filter Monitoring", "code": 113, diff --git a/examples/all-clusters-app/all-clusters-common/include/operational-state-delegate-impl.h b/examples/all-clusters-app/all-clusters-common/include/operational-state-delegate-impl.h new file mode 100644 index 00000000000000..d3fa6bd913dd14 --- /dev/null +++ b/examples/all-clusters-app/all-clusters-common/include/operational-state-delegate-impl.h @@ -0,0 +1,151 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace OperationalState { + +// This is an application level delegate to handle operational state commands according to the specific business logic. +class OperationalStateDelegate : public Delegate +{ + +public: + /** + * Get current operational state. + * @param op The GenericOperationalState to fill with the current operational state value. + * @return void. + */ + void GetCurrentOperationalState(GenericOperationalState & op) override; + + /** + * 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. + */ + CHIP_ERROR GetOperationalStateAtIndex(size_t index, GenericOperationalState & operationalState) override; + + /** + * 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. + */ + CHIP_ERROR GetOperationalPhaseAtIndex(size_t index, GenericOperationalPhase & operationalPhase) override; + + /** + * Get current operational error. + * @param error The GenericOperationalError to fill with the current operational error value + */ + void GetCurrentOperationalError(GenericOperationalError & error) override; + + /** + * Get current phase + * @param phase The app::DataModel::Nullable to fill with the current phase value + */ + void GetCurrentPhase(app::DataModel::Nullable & phase) override; + + /** + * Get countdown time + * @param time The app::DataModel::Nullable to fill with the coutdown time value + */ + void GetCountdownTime(app::DataModel::Nullable & time) override; + + /** + * Set operational error. + * @param opErrState The new operational error. + */ + CHIP_ERROR SetOperationalError(const GenericOperationalError & opErrState) override; + + /** + * Set current operational state. + * @param opState The operational state that should now be the current one. + */ + CHIP_ERROR SetOperationalState(const GenericOperationalState & opState) override; + + /** + * Set operational phase. + * @param phase The operational phase that should now be the current one. + */ + CHIP_ERROR SetPhase(const app::DataModel::Nullable & phase) override; + + /** + * Set coutdown time. + * @param time The coutdown time that should now be the current one. + */ + CHIP_ERROR SetCountdownTime(const app::DataModel::Nullable & time) 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; + + OperationalStateDelegate(GenericOperationalState aOperationalState, GenericOperationalError aOperationalError, + Span aOperationalStateList, + Span aOperationalPhaseList, + app::DataModel::Nullable aPhase = DataModel::Nullable(), + app::DataModel::Nullable aCountdownTime = DataModel::Nullable()) : + mOperationalState(aOperationalState), + mOperationalError(aOperationalError), mOperationalStateList(aOperationalStateList), + mOperationalPhaseList(aOperationalPhaseList), mOperationalPhase(aPhase), mCountdownTime(aCountdownTime) + {} + ~OperationalStateDelegate() = default; + +private: + GenericOperationalState mOperationalState; + GenericOperationalError mOperationalError; + app::DataModel::List mOperationalStateList; + Span mOperationalPhaseList; + app::DataModel::Nullable mOperationalPhase; + app::DataModel::Nullable mCountdownTime; +}; + +} // namespace OperationalState +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/examples/all-clusters-app/all-clusters-common/src/operational-state-delegate-impl.cpp b/examples/all-clusters-app/all-clusters-common/src/operational-state-delegate-impl.cpp new file mode 100644 index 00000000000000..ce6c5945497e76 --- /dev/null +++ b/examples/all-clusters-app/all-clusters-common/src/operational-state-delegate-impl.cpp @@ -0,0 +1,122 @@ +/* + * + * 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 + +namespace chip { +namespace app { +namespace Clusters { +namespace OperationalState { + +using chip::Protocols::InteractionModel::Status; + +CHIP_ERROR OperationalStateDelegate::SetOperationalState(const GenericOperationalState & opState) +{ + mOperationalState = opState; + return CHIP_NO_ERROR; +} + +CHIP_ERROR OperationalStateDelegate::SetPhase(const app::DataModel::Nullable & phase) +{ + mOperationalPhase = phase; + return CHIP_NO_ERROR; +} + +CHIP_ERROR OperationalStateDelegate::SetCountdownTime(const app::DataModel::Nullable & time) +{ + mCountdownTime = time; + return CHIP_NO_ERROR; +} + +void OperationalStateDelegate::GetCurrentOperationalState(GenericOperationalState & op) +{ + op = mOperationalState; +} + +CHIP_ERROR OperationalStateDelegate::SetOperationalError(const GenericOperationalError & opErrState) +{ + mOperationalError = opErrState; + return CHIP_NO_ERROR; +} + +void OperationalStateDelegate::GetCurrentOperationalError(GenericOperationalError & error) +{ + error = mOperationalError; +} + +void OperationalStateDelegate::GetCurrentPhase(app::DataModel::Nullable & phase) +{ + phase = mOperationalPhase; +} + +void OperationalStateDelegate::GetCountdownTime(app::DataModel::Nullable & time) +{ + time = mCountdownTime; +} + +CHIP_ERROR OperationalStateDelegate::GetOperationalStateAtIndex(size_t index, GenericOperationalState & operationalState) +{ + if (index > mOperationalStateList.size() - 1) + { + return CHIP_ERROR_NOT_FOUND; + } + operationalState = mOperationalStateList[index]; + return CHIP_NO_ERROR; +} + +CHIP_ERROR OperationalStateDelegate::GetOperationalPhaseAtIndex(size_t index, GenericOperationalPhase & operationalPhase) +{ + if (index > mOperationalPhaseList.size() - 1) + { + return CHIP_ERROR_NOT_FOUND; + } + operationalPhase = mOperationalPhaseList[index]; + return CHIP_NO_ERROR; +} + +void OperationalStateDelegate::HandlePauseStateCallback(GenericOperationalError & err) +{ + // placeholder implementation + mOperationalState.Set(to_underlying(OperationalStateEnum::kPaused)); + err.Set(to_underlying(ErrorStateEnum::kNoError)); +} + +void OperationalStateDelegate::HandleResumeStateCallback(GenericOperationalError & err) +{ + // placeholder implementation + mOperationalState.Set(to_underlying(OperationalStateEnum::kRunning)); + err.Set(to_underlying(ErrorStateEnum::kNoError)); +} + +void OperationalStateDelegate::HandleStartStateCallback(GenericOperationalError & err) +{ + // placeholder implementation + mOperationalState.Set(to_underlying(OperationalStateEnum::kRunning)); + err.Set(to_underlying(ErrorStateEnum::kNoError)); +} + +void OperationalStateDelegate::HandleStopStateCallback(GenericOperationalError & err) +{ + // placeholder implementation + mOperationalState.Set(to_underlying(OperationalStateEnum::kStopped)); + err.Set(to_underlying(ErrorStateEnum::kNoError)); +} + +} // namespace OperationalState +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/examples/all-clusters-app/all-clusters-common/src/operational-state-delegates.cpp b/examples/all-clusters-app/all-clusters-common/src/operational-state-delegates.cpp new file mode 100644 index 00000000000000..32bf09a4592b3c --- /dev/null +++ b/examples/all-clusters-app/all-clusters-common/src/operational-state-delegates.cpp @@ -0,0 +1,133 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace OperationalState { + +/** + * class to present Enquiry Table of Delegates + */ +struct DelegatesEnquiryTable +{ + /** + * Endpoint Id + */ + EndpointId mEndpointId; + /** + * Cluster Id + */ + ClusterId mClusterId; + /** + * The delegate for the cluster instance given by mEndpointId and mClusterId. + */ + Delegate * pItem; +}; + +/* + * An example to present device's endpointId + */ +constexpr EndpointId kDemoEndpointId = 1; + +/** + * Operational State List + * Note: User Define + */ +static const GenericOperationalState opStateList[] = { + GenericOperationalState(to_underlying(OperationalStateEnum::kStopped)), + GenericOperationalState(to_underlying(OperationalStateEnum::kRunning)), + GenericOperationalState(to_underlying(OperationalStateEnum::kPaused)), + GenericOperationalState(to_underlying(OperationalStateEnum::kError)), +}; + +/** + * Phase List + * Note: User Define + */ +static const GenericOperationalPhase opPhaseList[] = { + /** + * Phase List is null + */ + GenericOperationalPhase(DataModel::Nullable()), +}; + +/** + * Operational State Delegate + * Note: User Define + */ +static OperationalStateDelegate opStateDelegate(GenericOperationalState(to_underlying(OperationalStateEnum::kStopped)), + GenericOperationalError(to_underlying(ErrorStateEnum::kNoError)), + Span(opStateList), + Span(opPhaseList)); + +/** + * Enquiry Table of Operational State Cluster and alias Cluter Delegate corresponding to endpointId and clusterId + * Note: User Define + */ +constexpr DelegatesEnquiryTable kDelegatesEnquiryTable[] = { + // EndpointId, ClusterId, Delegate + { Clusters::OperationalState::kDemoEndpointId, Clusters::OperationalState::Id, &opStateDelegate }, +}; + +/** + * Get the pointer of target delegate for target endpoint and cluster + * @param[in] aEndpiontId The endpointId + * @param[in] aClusterID The clusterId + * @return the pointer of target delegate + */ +Delegate * getGenericDelegateTable(EndpointId aEndpointId, ClusterId aClusterId) +{ + for (size_t i = 0; i < ArraySize(kDelegatesEnquiryTable); ++i) + { + if (kDelegatesEnquiryTable[i].mEndpointId == aEndpointId && kDelegatesEnquiryTable[i].mClusterId == aClusterId) + { + return kDelegatesEnquiryTable[i].pItem; + } + } + return nullptr; +} + +// @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. +// @return Default global delegate instance. +Delegate * GetOperationalStateDelegate(EndpointId endpointId, ClusterId clusterId) +{ + return getGenericDelegateTable(endpointId, clusterId); +} + +} // namespace OperationalState +} // namespace Clusters +} // namespace app +} // namespace chip + +void MatterOperationalStateServerInit() +{ + using namespace chip::app; + static Clusters::OperationalState::OperationalStateServer operationalstateServer(Clusters::OperationalState::kDemoEndpointId, + Clusters::OperationalState::Id); + operationalstateServer.Init(); +} diff --git a/examples/all-clusters-app/linux/BUILD.gn b/examples/all-clusters-app/linux/BUILD.gn index 402649c13bba05..2de886a2943acd 100644 --- a/examples/all-clusters-app/linux/BUILD.gn +++ b/examples/all-clusters-app/linux/BUILD.gn @@ -24,6 +24,8 @@ source_set("chip-all-clusters-common") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/fan-stub.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/operational-state-delegate-impl.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/operational-state-delegates.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-modes-manager.cpp", "AllClustersCommandDelegate.cpp", "AppOptions.cpp", diff --git a/examples/all-clusters-app/linux/main-common.cpp b/examples/all-clusters-app/linux/main-common.cpp index 7350930176e865..48e81572eb037f 100644 --- a/examples/all-clusters-app/linux/main-common.cpp +++ b/examples/all-clusters-app/linux/main-common.cpp @@ -51,6 +51,10 @@ chip::app::Clusters::WindowCovering::WindowCoveringManager sWindowCoveringManage } // namespace +#ifdef EMBER_AF_PLUGIN_OPERATIONAL_STATE_SERVER +extern void MatterOperationalStateServerInit(); +#endif + void OnIdentifyStart(::Identify *) { ChipLogProgress(Zcl, "OnIdentifyStart"); @@ -167,6 +171,9 @@ void ApplicationInit() gExampleDeviceInstanceInfoProvider.Init(defaultProvider); SetDeviceInstanceInfoProvider(&gExampleDeviceInstanceInfoProvider); } +#ifdef EMBER_AF_PLUGIN_OPERATIONAL_STATE_SERVER + MatterOperationalStateServerInit(); +#endif } void ApplicationExit() diff --git a/examples/darwin-framework-tool/templates/tests/ciTests.json b/examples/darwin-framework-tool/templates/tests/ciTests.json index db8f7f38dd88fb..7260c76ca3a53d 100644 --- a/examples/darwin-framework-tool/templates/tests/ciTests.json +++ b/examples/darwin-framework-tool/templates/tests/ciTests.json @@ -31,6 +31,8 @@ "Test_TC_ACL_2_9", "Test_TC_ACL_2_10", "DL_LockUnlock", + "Disabled due to OperationalState not being enabled in Matter.framework for now:", + "TestOperationalState", "Disabled due to using ICD Management (ICDManagement) cluster, which is provisional on Darwin for now:", "TestIcdManagementCluster", "Test_TC_ICDM_1_1", diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index 90b38f8a2030fa..4d734a7a3f6d56 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -250,6 +250,12 @@ template("chip_data_model") { "${_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 00000000000000..217a99c02b400d --- /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 +#include +#include + +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 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 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 label = NullOptional, + Optional 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 label = NullOptional, Optional 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 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 mPhaseName; + +private: + void Set(app::DataModel::Nullable 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(mPhaseNameBuffer, sizeof(mPhaseNameBuffer))); + } + else + { + memcpy(mPhaseNameBuffer, name.Value().data(), name.Value().size()); + mPhaseName = app::DataModel::Nullable(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> & aTotalOperationalTime = NullOptional, + const Optional> & 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 to fill with the current phase value + */ + virtual void GetCurrentPhase(app::DataModel::Nullable & phase) = 0; + + /** + * Get countdown time + * @param time The app::DataModel::Nullable to fill with the coutdown time value + */ + virtual void GetCountdownTime(app::DataModel::Nullable & 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 & 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 & 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 00000000000000..79bb3d157388b9 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::OperationalState; +using namespace chip::app::Clusters::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( + handlerContext, [this](HandlerContext & ctx, const auto & req) { HandlePauseState(ctx, req); }); + break; + + case Commands::Resume::Id: + ChipLogDetail(Zcl, "OperationalState: Entering handling Resume state"); + + HandleCommand( + handlerContext, [this](HandlerContext & ctx, const auto & req) { HandleResumeState(ctx, req); }); + break; + + case Commands::Start::Id: + ChipLogDetail(Zcl, "OperationalState: Entering handling Start state"); + + HandleCommand( + handlerContext, [this](HandlerContext & ctx, const auto & req) { HandleStartState(ctx, req); }); + break; + + case Commands::Stop::Id: + ChipLogDetail(Zcl, "OperationalState: Entering handling Stop state"); + + HandleCommand(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 +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()); + 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 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 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 00000000000000..247ef3779ffd7a --- /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 +#include +#include +#include + +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 + 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 dada0c43b3deca..5f282138891ad7 100644 --- a/src/app/common/templates/config-data.yaml +++ b/src/app/common/templates/config-data.yaml @@ -24,6 +24,7 @@ CommandHandlerInterfaceOnlyClusters: # 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 22f10f9234cdcb..c716f442852c53 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -106,6 +106,15 @@ source_set("scenes-table-test-srcs") { ] } +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 @@ chip_test_suite("tests") { "TestInteractionModelEngine.cpp", "TestMessageDef.cpp", "TestNumericAttributeTraits.cpp", + "TestOperationalStateDelegate.cpp", "TestPendingNotificationMap.cpp", "TestReadInteraction.cpp", "TestReportingEngine.cpp", @@ -172,6 +182,7 @@ chip_test_suite("tests") { 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 00000000000000..a4d5dce822bc8d --- /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 +#include +#include + +#include + +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::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(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::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(desOperationalState.operationalStateLabel.Value().data()), + const_cast(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::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(desOperationalState.operationalStateLabel.Value().data()), + const_cast(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::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::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(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(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(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(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(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::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(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::fromCharString(labelBuffer)), + Optional(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(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(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::fromCharString(labelBuffer)), + Optional(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(desOperationalError.errorStateLabel.Value().data()), + const_cast(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(desOperationalError.errorStateDetails.Value().data()), + const_cast(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::fromCharString(labelBuffer)), + Optional(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(desOperationalError.errorStateLabel.Value().data()), + const_cast(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(desOperationalError.errorStateDetails.Value().data()), + const_cast(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::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(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::fromCharString(labelBuffer)), + Optional(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(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(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(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(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(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(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(labelBuffer2, sizeof(labelBuffer2))), + Optional(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(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(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()); + NL_TEST_ASSERT(inSuite, phase.IsMissing() == true); + + char phaseBuffer[kOperationalPhaseNameMaxSize] = "start"; + GenericOperationalPhase phase2(DataModel::Nullable(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(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::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(phase2.mPhaseName.Value().data()), const_cast(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()); + NL_TEST_ASSERT(inSuite, phase.IsMissing() == true); + + // copy assignment with name + char phaseBuffer[kOperationalPhaseNameMaxSize] = "start"; + GenericOperationalPhase phase2(DataModel::Nullable(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(phase.mPhaseName.Value().data()), const_cast(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(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(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(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(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(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(kTotalOperationalTime)), + Optional>(DataModel::Nullable(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 00000000000000..62ad14ddab3bbf --- /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 30188793e63fc4..71a6406bfa2c2b 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 ed27deaa540461..7ae0d174062133 100644 --- a/src/app/util/util.cpp +++ b/src/app/util/util.cpp @@ -159,6 +159,7 @@ void MatterPm1ConcentrationMeasurementPluginServerInitCallback() {} 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 83c069ef5c4744..667bcd88787da3 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 b6554bf6854653..8ece026c28f933 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 6063b942e3c11a..ca9592bd5a7daf 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"], diff --git a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp index 7ad0dbf9987cf0..69ca37ec5fb4a0 100644 --- a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp +++ b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp @@ -9479,112 +9479,6 @@ EmberAfStatus Set(chip::EndpointId endpoint, uint16_t value) namespace OperationalState { namespace Attributes { -namespace CurrentPhase { - -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::OperationalState::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (Traits::IsNullValue(temp)) - { - value.SetNull(); - } - else - { - value.SetNonNull() = Traits::StorageToWorking(temp); - } - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, uint8_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::OperationalState::Id, Id, writable, ZCL_INT8U_ATTRIBUTE_TYPE); -} - -EmberAfStatus SetNull(chip::EndpointId endpoint) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::OperationalState::Id, Id, writable, ZCL_INT8U_ATTRIBUTE_TYPE); -} - -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace CurrentPhase - -namespace CountdownTime { - -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::OperationalState::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (Traits::IsNullValue(temp)) - { - value.SetNull(); - } - else - { - value.SetNonNull() = Traits::StorageToWorking(temp); - } - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::OperationalState::Id, Id, writable, ZCL_ELAPSED_S_ATTRIBUTE_TYPE); -} - -EmberAfStatus SetNull(chip::EndpointId endpoint) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::OperationalState::Id, Id, writable, ZCL_ELAPSED_S_ATTRIBUTE_TYPE); -} - -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace CountdownTime - namespace FeatureMap { EmberAfStatus Get(chip::EndpointId endpoint, uint32_t * value) @@ -9653,112 +9547,6 @@ EmberAfStatus Set(chip::EndpointId endpoint, uint16_t value) namespace RvcOperationalState { namespace Attributes { -namespace CurrentPhase { - -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::RvcOperationalState::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (Traits::IsNullValue(temp)) - { - value.SetNull(); - } - else - { - value.SetNonNull() = Traits::StorageToWorking(temp); - } - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, uint8_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::RvcOperationalState::Id, Id, writable, ZCL_INT8U_ATTRIBUTE_TYPE); -} - -EmberAfStatus SetNull(chip::EndpointId endpoint) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::RvcOperationalState::Id, Id, writable, ZCL_INT8U_ATTRIBUTE_TYPE); -} - -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace CurrentPhase - -namespace CountdownTime { - -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::RvcOperationalState::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (Traits::IsNullValue(temp)) - { - value.SetNull(); - } - else - { - value.SetNonNull() = Traits::StorageToWorking(temp); - } - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::RvcOperationalState::Id, Id, writable, ZCL_ELAPSED_S_ATTRIBUTE_TYPE); -} - -EmberAfStatus SetNull(chip::EndpointId endpoint) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::RvcOperationalState::Id, Id, writable, ZCL_ELAPSED_S_ATTRIBUTE_TYPE); -} - -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace CountdownTime - namespace FeatureMap { EmberAfStatus Get(chip::EndpointId endpoint, uint32_t * value) diff --git a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h index 26b45a6b13c3f9..e8e5b4e0669913 100644 --- a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h +++ b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h @@ -1785,20 +1785,6 @@ EmberAfStatus Set(chip::EndpointId endpoint, uint16_t value); namespace OperationalState { namespace Attributes { -namespace CurrentPhase { -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value); // int8u -EmberAfStatus Set(chip::EndpointId endpoint, uint8_t value); -EmberAfStatus SetNull(chip::EndpointId endpoint); -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value); -} // namespace CurrentPhase - -namespace CountdownTime { -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value); // elapsed_s -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value); -EmberAfStatus SetNull(chip::EndpointId endpoint); -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value); -} // namespace CountdownTime - namespace FeatureMap { EmberAfStatus Get(chip::EndpointId endpoint, uint32_t * value); // bitmap32 EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value); @@ -1815,20 +1801,6 @@ EmberAfStatus Set(chip::EndpointId endpoint, uint16_t value); namespace RvcOperationalState { namespace Attributes { -namespace CurrentPhase { -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value); // int8u -EmberAfStatus Set(chip::EndpointId endpoint, uint8_t value); -EmberAfStatus SetNull(chip::EndpointId endpoint); -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value); -} // namespace CurrentPhase - -namespace CountdownTime { -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value); // elapsed_s -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value); -EmberAfStatus SetNull(chip::EndpointId endpoint); -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value); -} // namespace CountdownTime - namespace FeatureMap { EmberAfStatus Get(chip::EndpointId endpoint, uint32_t * value); // bitmap32 EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value); diff --git a/zzz_generated/app-common/app-common/zap-generated/callback.h b/zzz_generated/app-common/app-common/zap-generated/callback.h index e71de4f5854f5e..64302ebb7c50e0 100644 --- a/zzz_generated/app-common/app-common/zap-generated/callback.h +++ b/zzz_generated/app-common/app-common/zap-generated/callback.h @@ -11855,30 +11855,6 @@ bool emberAfDishwasherAlarmClusterResetCallback( bool emberAfDishwasherAlarmClusterModifyEnabledAlarmsCallback( chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, const chip::app::Clusters::DishwasherAlarm::Commands::ModifyEnabledAlarms::DecodableType & commandData); -/** - * @brief Operational State Cluster Pause Command callback (from client) - */ -bool emberAfOperationalStateClusterPauseCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::OperationalState::Commands::Pause::DecodableType & commandData); -/** - * @brief Operational State Cluster Stop Command callback (from client) - */ -bool emberAfOperationalStateClusterStopCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::OperationalState::Commands::Stop::DecodableType & commandData); -/** - * @brief Operational State Cluster Start Command callback (from client) - */ -bool emberAfOperationalStateClusterStartCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::OperationalState::Commands::Start::DecodableType & commandData); -/** - * @brief Operational State Cluster Resume Command callback (from client) - */ -bool emberAfOperationalStateClusterResumeCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::OperationalState::Commands::Resume::DecodableType & commandData); /** * @brief RVC Operational State Cluster Pause Command callback (from client) */ diff --git a/zzz_generated/chip-tool/zap-generated/test/Commands.h b/zzz_generated/chip-tool/zap-generated/test/Commands.h index cf357feadac2c1..feda117830052c 100644 --- a/zzz_generated/chip-tool/zap-generated/test/Commands.h +++ b/zzz_generated/chip-tool/zap-generated/test/Commands.h @@ -290,6 +290,7 @@ class TestList : public Command printf("TestCommissioningWindow\n"); printf("TestCommissionerNodeId\n"); printf("TestTimeSynchronization\n"); + printf("TestOperationalState\n"); printf("TestMultiAdmin\n"); printf("Test_TC_DGSW_1_1\n"); printf("TestSubscribe_OnOff\n"); @@ -91343,6 +91344,289 @@ class TestTimeSynchronizationSuite : public TestCommand } }; +class TestOperationalStateSuite : public TestCommand +{ +public: + TestOperationalStateSuite(CredentialIssuerCommands * credsIssuerConfig) : + TestCommand("TestOperationalState", 15, credsIssuerConfig) + { + AddArgument("nodeId", 0, UINT64_MAX, &mNodeId); + AddArgument("cluster", &mCluster); + AddArgument("endpoint", 0, UINT16_MAX, &mEndpoint); + AddArgument("timeout", 0, UINT16_MAX, &mTimeout); + } + + ~TestOperationalStateSuite() {} + + chip::System::Clock::Timeout GetWaitDuration() const override + { + return chip::System::Clock::Seconds16(mTimeout.ValueOr(kTimeoutInSeconds)); + } + +private: + chip::Optional mNodeId; + chip::Optional mCluster; + chip::Optional mEndpoint; + chip::Optional mTimeout; + + chip::EndpointId GetEndpoint(chip::EndpointId endpoint) { return mEndpoint.HasValue() ? mEndpoint.Value() : endpoint; } + + // + // Tests methods + // + + void OnResponse(const chip::app::StatusIB & status, chip::TLV::TLVReader * data) override + { + bool shouldContinue = false; + + switch (mTestIndex - 1) + { + case 0: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + shouldContinue = true; + break; + case 1: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::DataModel::Nullable> value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + VerifyOrReturn(CheckValueNull("phaseList", value)); + } + break; + case 2: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::DataModel::Nullable value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + VerifyOrReturn(CheckValueNull("currentPhase", value)); + } + break; + case 3: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::DataModel::Nullable value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + VerifyOrReturn(CheckValueNull("countdownTime", value)); + } + break; + case 4: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::DataModel::DecodableList< + chip::app::Clusters::OperationalState::Structs::OperationalStateStruct::DecodableType> + value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + { + auto iter_0 = value.begin(); + VerifyOrReturn(CheckNextListItemDecodes("operationalStateList", iter_0, 0)); + VerifyOrReturn( + CheckValue("operationalStateList[0].operationalStateID", iter_0.GetValue().operationalStateID, 0U)); + VerifyOrReturn(CheckNextListItemDecodes("operationalStateList", iter_0, 1)); + VerifyOrReturn( + CheckValue("operationalStateList[1].operationalStateID", iter_0.GetValue().operationalStateID, 1U)); + VerifyOrReturn(CheckNextListItemDecodes("operationalStateList", iter_0, 2)); + VerifyOrReturn( + CheckValue("operationalStateList[2].operationalStateID", iter_0.GetValue().operationalStateID, 2U)); + VerifyOrReturn(CheckNextListItemDecodes("operationalStateList", iter_0, 3)); + VerifyOrReturn( + CheckValue("operationalStateList[3].operationalStateID", iter_0.GetValue().operationalStateID, 3U)); + VerifyOrReturn(CheckNoMoreListItems("operationalStateList", iter_0, 4)); + } + } + break; + case 5: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::Clusters::OperationalState::Structs::ErrorStateStruct::DecodableType value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + VerifyOrReturn(CheckValue("operationalError.errorStateID", value.errorStateID, 0U)); + } + break; + case 6: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::Clusters::OperationalState::Structs::OperationalStateStruct::DecodableType value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + VerifyOrReturn(CheckValue("operationalState.operationalStateID", value.operationalStateID, 0U)); + } + break; + case 7: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::Clusters::OperationalState::Commands::OperationalCommandResponse::DecodableType value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + VerifyOrReturn(CheckValue("commandResponseState.errorStateID", value.commandResponseState.errorStateID, 0U)); + } + break; + case 8: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::Clusters::OperationalState::Structs::OperationalStateStruct::DecodableType value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + VerifyOrReturn(CheckValue("operationalState.operationalStateID", value.operationalStateID, 1U)); + } + break; + case 9: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::Clusters::OperationalState::Commands::OperationalCommandResponse::DecodableType value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + VerifyOrReturn(CheckValue("commandResponseState.errorStateID", value.commandResponseState.errorStateID, 0U)); + } + break; + case 10: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::Clusters::OperationalState::Structs::OperationalStateStruct::DecodableType value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + VerifyOrReturn(CheckValue("operationalState.operationalStateID", value.operationalStateID, 2U)); + } + break; + case 11: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::Clusters::OperationalState::Commands::OperationalCommandResponse::DecodableType value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + VerifyOrReturn(CheckValue("commandResponseState.errorStateID", value.commandResponseState.errorStateID, 0U)); + } + break; + case 12: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::Clusters::OperationalState::Structs::OperationalStateStruct::DecodableType value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + VerifyOrReturn(CheckValue("operationalState.operationalStateID", value.operationalStateID, 1U)); + } + break; + case 13: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::Clusters::OperationalState::Commands::OperationalCommandResponse::DecodableType value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + VerifyOrReturn(CheckValue("commandResponseState.errorStateID", value.commandResponseState.errorStateID, 0U)); + } + break; + case 14: + VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0)); + { + chip::app::Clusters::OperationalState::Structs::OperationalStateStruct::DecodableType value; + VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value))); + VerifyOrReturn(CheckValue("operationalState.operationalStateID", value.operationalStateID, 0U)); + } + break; + default: + LogErrorOnFailure(ContinueOnChipMainThread(CHIP_ERROR_INVALID_ARGUMENT)); + } + + if (shouldContinue) + { + ContinueOnChipMainThread(CHIP_NO_ERROR); + } + } + + CHIP_ERROR DoTestStep(uint16_t testIndex) override + { + using namespace chip::app::Clusters; + switch (testIndex) + { + case 0: { + LogStep(0, "Wait for the commissioned device to be retrieved"); + ListFreer listFreer; + chip::app::Clusters::DelayCommands::Commands::WaitForCommissionee::Type value; + value.nodeId = mNodeId.HasValue() ? mNodeId.Value() : 305414945ULL; + return WaitForCommissionee(kIdentityAlpha, value); + } + case 1: { + LogStep(1, "Read Phase List"); + return ReadAttribute(kIdentityAlpha, GetEndpoint(1), OperationalState::Id, OperationalState::Attributes::PhaseList::Id, + true, chip::NullOptional); + } + case 2: { + LogStep(2, "Read current Phase"); + return ReadAttribute(kIdentityAlpha, GetEndpoint(1), OperationalState::Id, + OperationalState::Attributes::CurrentPhase::Id, true, chip::NullOptional); + } + case 3: { + LogStep(3, "Read Countdown Time"); + return ReadAttribute(kIdentityAlpha, GetEndpoint(1), OperationalState::Id, + OperationalState::Attributes::CountdownTime::Id, true, chip::NullOptional); + } + case 4: { + LogStep(4, "Read Operational State List"); + return ReadAttribute(kIdentityAlpha, GetEndpoint(1), OperationalState::Id, + OperationalState::Attributes::OperationalStateList::Id, true, chip::NullOptional); + } + case 5: { + LogStep(5, "Read current Operational Error"); + return ReadAttribute(kIdentityAlpha, GetEndpoint(1), OperationalState::Id, + OperationalState::Attributes::OperationalError::Id, true, chip::NullOptional); + } + case 6: { + LogStep(6, "Read current Operational State"); + return ReadAttribute(kIdentityAlpha, GetEndpoint(1), OperationalState::Id, + OperationalState::Attributes::OperationalState::Id, true, chip::NullOptional); + } + case 7: { + LogStep(7, "Start Command"); + ListFreer listFreer; + chip::app::Clusters::OperationalState::Commands::Start::Type value; + return SendCommand(kIdentityAlpha, GetEndpoint(1), OperationalState::Id, OperationalState::Commands::Start::Id, value, + chip::NullOptional + + ); + } + case 8: { + LogStep(8, "Read current Operational State"); + return ReadAttribute(kIdentityAlpha, GetEndpoint(1), OperationalState::Id, + OperationalState::Attributes::OperationalState::Id, true, chip::NullOptional); + } + case 9: { + LogStep(9, "Pause Command"); + ListFreer listFreer; + chip::app::Clusters::OperationalState::Commands::Pause::Type value; + return SendCommand(kIdentityAlpha, GetEndpoint(1), OperationalState::Id, OperationalState::Commands::Pause::Id, value, + chip::NullOptional + + ); + } + case 10: { + LogStep(10, "Read current Operational State"); + return ReadAttribute(kIdentityAlpha, GetEndpoint(1), OperationalState::Id, + OperationalState::Attributes::OperationalState::Id, true, chip::NullOptional); + } + case 11: { + LogStep(11, "Resume Command"); + ListFreer listFreer; + chip::app::Clusters::OperationalState::Commands::Resume::Type value; + return SendCommand(kIdentityAlpha, GetEndpoint(1), OperationalState::Id, OperationalState::Commands::Resume::Id, value, + chip::NullOptional + + ); + } + case 12: { + LogStep(12, "Read current Operational State"); + return ReadAttribute(kIdentityAlpha, GetEndpoint(1), OperationalState::Id, + OperationalState::Attributes::OperationalState::Id, true, chip::NullOptional); + } + case 13: { + LogStep(13, "Stop Command"); + ListFreer listFreer; + chip::app::Clusters::OperationalState::Commands::Stop::Type value; + return SendCommand(kIdentityAlpha, GetEndpoint(1), OperationalState::Id, OperationalState::Commands::Stop::Id, value, + chip::NullOptional + + ); + } + case 14: { + LogStep(14, "Read current Operational State"); + return ReadAttribute(kIdentityAlpha, GetEndpoint(1), OperationalState::Id, + OperationalState::Attributes::OperationalState::Id, true, chip::NullOptional); + } + } + return CHIP_NO_ERROR; + } +}; + class TestMultiAdminSuite : public TestCommand { public: @@ -138862,6 +139146,7 @@ void registerCommandsTests(Commands & commands, CredentialIssuerCommands * creds make_unique(credsIssuerConfig), make_unique(credsIssuerConfig), make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), make_unique(credsIssuerConfig), make_unique(credsIssuerConfig), make_unique(credsIssuerConfig),