Enable op state pause and resume for compatible states (#30795)
* Updated the OpState HandlePauseState method to enable new pause-compatible state to receive this command.
* Made OpState unchangable internal variables const.
* Updated the OpState HandleResumeState method to enable new resume-compatible states to receive this command.
* Restyled by clang-format
* Skip resume callback call if state is Running.
* Changed the OpState server API such that all derived clusters have their own class. This separetes out the cluster specific logic, reducing the footprint cost.
* Added a helper readme that explains how to use and extend the operational state server.
* Fixed permissions of the OpState derived cluster methods.
* Updated the rvc-app example following the changes to the operational state server API
* Updated the all-clusters--app example following the changes to the operational state server API
* Restyled by whitespace
* Restyled by clang-format
* Restyled by prettier-markdown
* Updated the dishwasher-app example following the changes to the operational state server API
* Restyled by clang-format
* Fixed the ameba build of all-clusters-app.
* Restyled by clang-format
* Fixed the ameba build of all-clusters-app.
* Fixed a typo in the operational state server doc.
---------
Co-authored-by: Restyled.io <commits@restyled.io>
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
index f8a465e..ec3e392 100644
--- 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
@@ -125,7 +125,7 @@
gOperationalStateDelegate = new OperationalStateDelegate;
EndpointId operationalStateEndpoint = 0x01;
- gOperationalStateInstance = new Instance(gOperationalStateDelegate, operationalStateEndpoint, Clusters::OperationalState::Id);
+ gOperationalStateInstance = new OperationalState::Instance(gOperationalStateDelegate, operationalStateEndpoint);
gOperationalStateInstance->SetOperationalState(to_underlying(OperationalState::OperationalStateEnum::kStopped));
@@ -158,8 +158,7 @@
gRvcOperationalStateDelegate = new RvcOperationalStateDelegate;
EndpointId operationalStateEndpoint = 0x01;
- gRvcOperationalStateInstance =
- new Instance(gRvcOperationalStateDelegate, operationalStateEndpoint, Clusters::RvcOperationalState::Id);
+ gRvcOperationalStateInstance = new RvcOperationalState::Instance(gRvcOperationalStateDelegate, operationalStateEndpoint);
gRvcOperationalStateInstance->SetOperationalState(to_underlying(OperationalState::OperationalStateEnum::kStopped));
diff --git a/examples/all-clusters-app/ameba/main/OperationalStateManager.cpp b/examples/all-clusters-app/ameba/main/OperationalStateManager.cpp
index 9e4001e..68ee456 100644
--- a/examples/all-clusters-app/ameba/main/OperationalStateManager.cpp
+++ b/examples/all-clusters-app/ameba/main/OperationalStateManager.cpp
@@ -126,7 +126,7 @@
gOperationalStateDelegate = new OperationalStateDelegate;
EndpointId operationalStateEndpoint = 0x01;
- gOperationalStateInstance = new Instance(gOperationalStateDelegate, operationalStateEndpoint, Clusters::OperationalState::Id);
+ gOperationalStateInstance = new OperationalState::Instance(gOperationalStateDelegate, operationalStateEndpoint);
gOperationalStateInstance->SetOperationalState(to_underlying(OperationalState::OperationalStateEnum::kStopped));
@@ -135,10 +135,10 @@
// Init RVC Operational State cluster
-static OperationalState::Instance * gRvcOperationalStateInstance = nullptr;
-static RvcOperationalStateDelegate * gRvcOperationalStateDelegate = nullptr;
+static RvcOperationalState::Instance * gRvcOperationalStateInstance = nullptr;
+static RvcOperationalStateDelegate * gRvcOperationalStateDelegate = nullptr;
-OperationalState::Instance * OperationalState::GetRVCOperationalStateInstance()
+RvcOperationalState::Instance * RvcOperationalState::GetRvcOperationalStateInstance()
{
return gRvcOperationalStateInstance;
}
@@ -164,8 +164,7 @@
gRvcOperationalStateDelegate = new RvcOperationalStateDelegate;
EndpointId operationalStateEndpoint = 0x01;
- gRvcOperationalStateInstance =
- new Instance(gRvcOperationalStateDelegate, operationalStateEndpoint, Clusters::RvcOperationalState::Id);
+ gRvcOperationalStateInstance = new RvcOperationalState::Instance(gRvcOperationalStateDelegate, operationalStateEndpoint);
gRvcOperationalStateInstance->SetOperationalState(to_underlying(OperationalState::OperationalStateEnum::kStopped));
diff --git a/examples/all-clusters-app/ameba/main/include/ManualOperationalStateCommand.h b/examples/all-clusters-app/ameba/main/include/ManualOperationalStateCommand.h
index 1f6d899..5d43735 100644
--- a/examples/all-clusters-app/ameba/main/include/ManualOperationalStateCommand.h
+++ b/examples/all-clusters-app/ameba/main/include/ManualOperationalStateCommand.h
@@ -137,7 +137,7 @@
uint32_t state = atoi(argv[0]);
CHIP_ERROR err;
- err = GetRVCOperationalStateInstance()->SetOperationalState(state);
+ err = RvcOperationalState::GetRvcOperationalStateInstance()->SetOperationalState(state);
if (err != CHIP_NO_ERROR)
{
@@ -178,7 +178,7 @@
break;
}
- GetRVCOperationalStateInstance()->OnOperationalErrorDetected(err);
+ RvcOperationalState::GetRvcOperationalStateInstance()->OnOperationalErrorDetected(err);
return CHIP_NO_ERROR;
}
diff --git a/examples/all-clusters-app/ameba/main/include/OperationalStateManager.h b/examples/all-clusters-app/ameba/main/include/OperationalStateManager.h
index e98d321..875534c 100644
--- a/examples/all-clusters-app/ameba/main/include/OperationalStateManager.h
+++ b/examples/all-clusters-app/ameba/main/include/OperationalStateManager.h
@@ -32,7 +32,6 @@
namespace OperationalState {
Instance * GetOperationalStateInstance();
-Instance * GetRVCOperationalStateInstance();
// This is an application level delegate to handle operational state commands according to the specific business logic.
class GenericOperationalStateDelegateImpl : public Delegate
@@ -124,6 +123,8 @@
namespace RvcOperationalState {
+Instance * GetRvcOperationalStateInstance();
+
// This is an application level delegate to handle operational state commands according to the specific business logic.
class RvcOperationalStateDelegate : public OperationalState::GenericOperationalStateDelegateImpl
{
diff --git a/examples/dishwasher-app/dishwasher-common/src/operational-state-delegate-impl.cpp b/examples/dishwasher-app/dishwasher-common/src/operational-state-delegate-impl.cpp
index 0b57ece..815ab69 100644
--- a/examples/dishwasher-app/dishwasher-common/src/operational-state-delegate-impl.cpp
+++ b/examples/dishwasher-app/dishwasher-common/src/operational-state-delegate-impl.cpp
@@ -122,7 +122,7 @@
gOperationalStateDelegate = new OperationalStateDelegate;
EndpointId operationalStateEndpoint = 0x01;
- gOperationalStateInstance = new Instance(gOperationalStateDelegate, operationalStateEndpoint, Clusters::OperationalState::Id);
+ gOperationalStateInstance = new Instance(gOperationalStateDelegate, operationalStateEndpoint);
gOperationalStateInstance->SetOperationalState(to_underlying(OperationalState::OperationalStateEnum::kStopped));
diff --git a/examples/rvc-app/rvc-common/include/rvc-device.h b/examples/rvc-app/rvc-common/include/rvc-device.h
index 6064bce..79d114a 100644
--- a/examples/rvc-app/rvc-common/include/rvc-device.h
+++ b/examples/rvc-app/rvc-common/include/rvc-device.h
@@ -19,7 +19,7 @@
ModeBase::Instance mCleanModeInstance;
RvcOperationalState::RvcOperationalStateDelegate mOperationalStateDelegate;
- OperationalState::Instance mOperationalStateInstance;
+ RvcOperationalState::Instance mOperationalStateInstance;
bool mDocked = false;
bool mCharging = false;
@@ -33,7 +33,7 @@
explicit RvcDevice(EndpointId aRvcClustersEndpoint) :
mRunModeDelegate(), mRunModeInstance(&mRunModeDelegate, aRvcClustersEndpoint, RvcRunMode::Id, 0), mCleanModeDelegate(),
mCleanModeInstance(&mCleanModeDelegate, aRvcClustersEndpoint, RvcCleanMode::Id, 0), mOperationalStateDelegate(),
- mOperationalStateInstance(&mOperationalStateDelegate, aRvcClustersEndpoint, RvcOperationalState::Id)
+ mOperationalStateInstance(&mOperationalStateDelegate, aRvcClustersEndpoint)
{
// set the current-mode at start-up
mRunModeInstance.UpdateCurrentMode(RvcRunMode::ModeIdle);
diff --git a/src/app/clusters/operational-state-server/README.md b/src/app/clusters/operational-state-server/README.md
new file mode 100644
index 0000000..8dd6d59
--- /dev/null
+++ b/src/app/clusters/operational-state-server/README.md
@@ -0,0 +1,36 @@
+# The Operational State cluster and its derived clusters
+
+The Operational State cluster is a normal cluster with ID 0x0060. It is also a
+base cluster from with other operational state clusters are derived.
+
+## How to use an Operational State cluster
+
+All Operational State derived clusters have their own `Instance` class within
+their respective namespace. This class is used to manage the SDK side of the
+cluster. This class requires an `OperationalState::Delegate` where the
+application specific logic is implemented.
+
+To use an Operational State cluster
+
+- Create a class that inherits the `OperationalState::Delegate` class.
+- For this class, implement the necessary virtual methods.
+- In some translation unit (.c or .cpp file), instantiate the delegate class.
+- Instantiate the `Instance` class for your Operational State cluster using
+ the delegate instance.
+- Call the `Init()` method of your instance after the root `Server::Init()`.
+- Alternatively, the last two steps can be done in the
+ `emberAf<ClusterName>ClusterInitCallback` function.
+
+**Note** Zap accessor functions for these clusters do not exist. Use the
+instance's `Set...` and `Get...` functions to access the attributes.
+
+## How to add new derived clusters
+
+Once an Operational State derived cluster has been defined in the spec, add the
+implementation using the following steps
+
+1. Translate the spec as an XML in `src/app/zap-templates/zcl/data-model/chip`.
+ You can look at similar files on how to do this.
+2. Regenerate the zap code.
+3. Implement an `Instance` class in your cluster's namespace.
+4. Extend the all-clusters-app example to include your new cluster.
diff --git a/src/app/clusters/operational-state-server/operational-state-server.cpp b/src/app/clusters/operational-state-server/operational-state-server.cpp
index 0c8c4f9..c7b7cd1 100644
--- a/src/app/clusters/operational-state-server/operational-state-server.cpp
+++ b/src/app/clusters/operational-state-server/operational-state-server.cpp
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-/****************************************************************************
+/****************************************************************************'
* @file
* @brief Implementation for the Operational State Server Cluster
***************************************************************************/
@@ -42,6 +42,8 @@
mDelegate->SetInstance(this);
}
+Instance::Instance(Delegate * aDelegate, EndpointId aEndpointId) : Instance(aDelegate, aEndpointId, OperationalState::Id) {}
+
Instance::~Instance()
{
InteractionModelEngine::GetInstance()->UnregisterCommandHandler(this);
@@ -337,11 +339,24 @@
GenericOperationalError err(to_underlying(ErrorStateEnum::kNoError));
uint8_t opState = GetCurrentOperationalState();
- if (opState != to_underlying(OperationalStateEnum::kPaused) && opState != to_underlying(OperationalStateEnum::kRunning))
+ // Handle Operational State Pause-incompatible states.
+ if (opState == to_underlying(OperationalStateEnum::kStopped) || opState == to_underlying(OperationalStateEnum::kError))
{
err.Set(to_underlying(ErrorStateEnum::kCommandInvalidInState));
}
- else if (opState == to_underlying(OperationalStateEnum::kRunning))
+
+ // Handle Pause-incompatible states for derived clusters.
+ if (opState >= DerivedClusterNumberSpaceStart && opState < VendorNumberSpaceStart)
+ {
+ if (!IsDerivedClusterStatePauseCompatible(opState))
+ {
+ err.Set(to_underlying(ErrorStateEnum::kCommandInvalidInState));
+ }
+ }
+
+ // If the error is still NoError, we can call the delegate's handle function.
+ // If the current state is Paused we can skip this call.
+ if (err.errorStateID == 0 && opState != to_underlying(OperationalStateEnum::kPaused))
{
mDelegate->HandlePauseStateCallback(err);
}
@@ -395,11 +410,24 @@
GenericOperationalError err(to_underlying(ErrorStateEnum::kNoError));
uint8_t opState = GetCurrentOperationalState();
- if (opState != to_underlying(OperationalStateEnum::kPaused) && opState != to_underlying(OperationalStateEnum::kRunning))
+ // Handle Operational State Resume-incompatible states.
+ if (opState == to_underlying(OperationalStateEnum::kStopped) || opState == to_underlying(OperationalStateEnum::kError))
{
err.Set(to_underlying(ErrorStateEnum::kCommandInvalidInState));
}
- else if (opState == to_underlying(OperationalStateEnum::kPaused))
+
+ // Handle Resume-incompatible states for derived clusters.
+ if (opState >= DerivedClusterNumberSpaceStart && opState < VendorNumberSpaceStart)
+ {
+ if (!IsDerivedClusterStateResumeCompatible(opState))
+ {
+ err.Set(to_underlying(ErrorStateEnum::kCommandInvalidInState));
+ }
+ }
+
+ // If the error is still NoError, we can call the delegate's handle function.
+ // If the current state is Running we can skip this call.
+ if (err.errorStateID == 0 && opState != to_underlying(OperationalStateEnum::kRunning))
{
mDelegate->HandleResumeStateCallback(err);
}
@@ -409,3 +437,16 @@
ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response);
}
+
+// RvcOperationalState
+
+bool RvcOperationalState::Instance::IsDerivedClusterStatePauseCompatible(uint8_t aState)
+{
+ return aState == to_underlying(RvcOperationalState::OperationalStateEnum::kSeekingCharger);
+}
+
+bool RvcOperationalState::Instance::IsDerivedClusterStateResumeCompatible(uint8_t aState)
+{
+ return (aState == to_underlying(RvcOperationalState::OperationalStateEnum::kCharging) ||
+ aState == to_underlying(RvcOperationalState::OperationalStateEnum::kDocked));
+}
diff --git a/src/app/clusters/operational-state-server/operational-state-server.h b/src/app/clusters/operational-state-server/operational-state-server.h
index 2e0875d..88725a1 100644
--- a/src/app/clusters/operational-state-server/operational-state-server.h
+++ b/src/app/clusters/operational-state-server/operational-state-server.h
@@ -29,6 +29,9 @@
namespace Clusters {
namespace OperationalState {
+const uint8_t DerivedClusterNumberSpaceStart = 0x40;
+const uint8_t VendorNumberSpaceStart = 0x80;
+
class Uncopyable
{
protected:
@@ -50,15 +53,15 @@
{
public:
/**
- * 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.
+ * 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.
* It is possible to set the CurrentPhase and OperationalState via the Set... methods before calling Init().
* @param aDelegate A pointer to the delegate to be used by this server.
* Note: the caller must ensure that the delegate lives throughout the instance's lifetime.
* @param aEndpointId The endpoint on which this cluster exists. This must match the zap configuration.
- * @param aClusterId The ID of the operational state derived cluster to be instantiated.
*/
- Instance(Delegate * aDelegate, EndpointId aEndpointId, ClusterId aClusterId);
+ Instance(Delegate * aDelegate, EndpointId aEndpointId);
~Instance() override;
@@ -148,11 +151,40 @@
*/
bool IsSupportedOperationalState(uint8_t aState);
+protected:
+ /**
+ * Creates an operational state cluster instance for a given cluster ID.
+ * The Init() function needs to be called for this instance to be registered and called by the
+ * interaction model at the appropriate times.
+ * It is possible to set the CurrentPhase and OperationalState via the Set... methods before calling Init().
+ * @param aDelegate A pointer to the delegate to be used by this server.
+ * Note: the caller must ensure that the delegate lives throughout the instance's lifetime.
+ * @param aEndpointId The endpoint on which this cluster exists. This must match the zap configuration.
+ * @param aClusterId The ID of the operational state derived cluster to be instantiated.
+ */
+ Instance(Delegate * aDelegate, EndpointId aEndpointId, ClusterId aClusterId);
+
+ /**
+ * Given a state in the derived cluster number-space (from 0x40 to 0x7f), this method checks if the state is pause-compatible.
+ * Note: if a state outside the derived cluster number-space is given, this method returns false.
+ * @param aState The state to check.
+ * @return true if aState is pause-compatible, false otherwise.
+ */
+ virtual bool IsDerivedClusterStatePauseCompatible(uint8_t aState) { return false; };
+
+ /**
+ * Given a state in the derived cluster number-space (from 0x40 to 0x7f), this method checks if the state is resume-compatible.
+ * Note: if a state outside the derived cluster number-space is given, this method returns false.
+ * @param aState The state to check.
+ * @return true if aState is pause-compatible, false otherwise.
+ */
+ virtual bool IsDerivedClusterStateResumeCompatible(uint8_t aState) { return false; };
+
private:
Delegate * mDelegate;
- EndpointId mEndpointId;
- ClusterId mClusterId;
+ const EndpointId mEndpointId;
+ const ClusterId mClusterId;
// Attribute Data Store
app::DataModel::Nullable<uint8_t> mCurrentPhase;
@@ -174,6 +206,9 @@
/**
* Handle Command: Pause.
+ * If the current state is not pause-compatible, this method responds with an ErrorStateId of CommandInvalidInState.
+ * If the current state is paused, this method responds with an ErrorStateId of NoError but takes no action.
+ * Otherwise, this method calls the delegate's HandlePauseStateCallback.
*/
void HandlePauseState(HandlerContext & ctx, const Commands::Pause::DecodableType & req);
@@ -189,6 +224,8 @@
/**
* Handle Command: Resume.
+ * If the current state is not resume-compatible, this method responds with an ErrorStateId of CommandInvalidInState.
+ * Otherwise, this method calls the delegate's HandleResumeStateCallback.
*/
void HandleResumeState(HandlerContext & ctx, const Commands::Resume::DecodableType & req);
};
@@ -275,6 +312,66 @@
};
} // namespace OperationalState
+
+namespace RvcOperationalState {
+
+class Instance : public OperationalState::Instance
+{
+public:
+ /**
+ * Creates an RVC 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.
+ * It is possible to set the CurrentPhase and OperationalState via the Set... methods before calling Init().
+ * @param aDelegate A pointer to the delegate to be used by this server.
+ * Note: the caller must ensure that the delegate lives throughout the instance's lifetime.
+ * @param aEndpointId The endpoint on which this cluster exists. This must match the zap configuration.
+ */
+ Instance(OperationalState::Delegate * aDelegate, EndpointId aEndpointId) :
+ OperationalState::Instance(aDelegate, aEndpointId, Id)
+ {}
+
+protected:
+ /**
+ * Given a state in the derived cluster number-space (from 0x40 to 0x7f), this method checks if the state is pause-compatible.
+ * Note: if a state outside the derived cluster number-space is given, this method returns false.
+ * @param aState The state to check.
+ * @return true if aState is pause-compatible, false otherwise.
+ */
+ bool IsDerivedClusterStatePauseCompatible(uint8_t aState) override;
+
+ /**
+ * Given a state in the derived cluster number-space (from 0x40 to 0x7f), this method checks if the state is resume-compatible.
+ * Note: if a state outside the derived cluster number-space is given, this method returns false.
+ * @param aState The state to check.
+ * @return true if aState is pause-compatible, false otherwise.
+ */
+ bool IsDerivedClusterStateResumeCompatible(uint8_t aState) override;
+};
+
+} // namespace RvcOperationalState
+
+namespace OvenCavityOperationalState {
+
+class Instance : public OperationalState::Instance
+{
+public:
+ /**
+ * Creates an oven cavity 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.
+ * It is possible to set the CurrentPhase and OperationalState via the Set... methods before calling Init().
+ * @param aDelegate A pointer to the delegate to be used by this server.
+ * Note: the caller must ensure that the delegate lives throughout the instance's lifetime.
+ * @param aEndpointId The endpoint on which this cluster exists. This must match the zap configuration.
+ */
+ Instance(OperationalState::Delegate * aDelegate, EndpointId aEndpointId) :
+ OperationalState::Instance(aDelegate, aEndpointId, Id)
+ {}
+};
+
+} // namespace OvenCavityOperationalState
+
} // namespace Clusters
} // namespace app
} // namespace chip