blob: 56229c02541d60c8197d2a347e111139269981a6 [file] [log] [blame]
/*
*
* 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 "operational-state-cluster-objects.h"
#include <app-common/zap-generated/cluster-objects.h>
#include <app/AttributeAccessInterface.h>
#include <app/CommandHandlerInterface.h>
#include <app/cluster-building-blocks/QuieterReporting.h>
#include <app/data-model/Nullable.h>
namespace chip {
namespace app {
namespace Clusters {
namespace OperationalState {
const uint8_t DerivedClusterNumberSpaceStart = 0x40;
const uint8_t VendorNumberSpaceStart = 0x80;
class Uncopyable
{
protected:
Uncopyable() {}
~Uncopyable() = default;
private:
Uncopyable(const Uncopyable &) = delete;
Uncopyable & operator=(const Uncopyable &) = delete;
};
class Delegate;
/**
* Instance 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 Instance : public CommandHandlerInterface, public AttributeAccessInterface, public Uncopyable
{
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.
* 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(Delegate * aDelegate, EndpointId aEndpointId);
~Instance() override;
/**
* Phase name's max length
*/
static constexpr uint8_t kMaxPhaseNameLength = 64;
/**
* Initialise the operational state server instance.
* This function must be called after defining an Instance class object.
* @return Returns an error if the given endpoint and cluster ID have not been enabled in zap or if the
* CommandHandler or AttributeHandler registration fails, else returns CHIP_NO_ERROR.
*/
CHIP_ERROR Init();
// Attribute setters
/**
* Set operational phase.
* @param aPhase The operational phase that should now be the current one.
* @return CHIP_ERROR_INVALID_ARGUMENT if aPhase is an invalid value. CHIP_NO_ERROR if set was successful.
*/
CHIP_ERROR SetCurrentPhase(const app::DataModel::Nullable<uint8_t> & aPhase);
/**
* Set current operational state to aOpState and the operational error to kNoError.
* NOTE: This method cannot be used to set the error state. The error state must be set via the
* OnOperationalErrorDetected method.
* @param aOpState The operational state that should now be the current one.
* @return CHIP_ERROR_INVALID_ARGUMENT if aOpState is an invalid value. CHIP_NO_ERROR if set was successful.
*/
CHIP_ERROR SetOperationalState(uint8_t aOpState);
// Attribute getters
/**
* Get current phase.
* @return The current phase.
*/
app::DataModel::Nullable<uint8_t> GetCurrentPhase() const;
/**
* Get the current operational state.
* @return The current operational state value.
*/
uint8_t GetCurrentOperationalState() const;
/**
* Get current operational error.
* @param error The GenericOperationalError to fill with the current operational error value
*/
void GetCurrentOperationalError(GenericOperationalError & error) const;
/**
* @brief Whenever application delegate wants to possibly report a new updated time,
* call this method. The `GetCountdownTime()` method will be called on the delegate.
*/
void UpdateCountdownTimeFromDelegate() { UpdateCountdownTime(/* fromDelegate = */ true); }
// Event triggers
/**
* @brief Called when the Node detects a OperationalError has been raised.
* Note: This function also sets the OperationalState attribute to Error.
* @param aError OperationalError which detects
*/
void OnOperationalErrorDetected(const Structs::ErrorStateStruct::Type & aError);
/**
* @brief Called when the Node detects a OperationCompletion has been raised.
* @param aCompletionErrorCode CompletionErrorCode
* @param aTotalOperationalTime TotalOperationalTime
* @param aPausedTime PausedTime
*/
void OnOperationCompletionDetected(uint8_t aCompletionErrorCode,
const Optional<DataModel::Nullable<uint32_t>> & aTotalOperationalTime = NullOptional,
const Optional<DataModel::Nullable<uint32_t>> & aPausedTime = NullOptional);
// List change reporting
/**
* Reports that the contents of the operational state list has changed.
* The device SHALL call this method whenever it changes the operational state list.
*/
void ReportOperationalStateListChange();
/**
* Reports that the contents of the phase list has changed.
* The device SHALL call this method whenever it changes the phase list.
*/
void ReportPhaseListChange();
/**
* This function returns true if the phase value given exists in the PhaseList attribute, otherwise it returns false.
*/
bool IsSupportedPhase(uint8_t aPhase);
/**
* This function returns true if the operational state value given exists in the OperationalStateList attribute,
* otherwise it returns false.
*/
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; };
/**
* Handles the invocation of derived cluster commands.
* If a derived cluster defines its own commands, this method SHALL be implemented by the derived cluster's class
* to handle the derived cluster's specific commands.
* @param handlerContext The command handler context containing information about the received command.
*/
virtual void InvokeDerivedClusterCommand(HandlerContext & handlerContext) { return; };
/**
* Causes reporting/udpating of CountdownTime attribute from driver if sufficient changes have
* occurred (based on Q quality definition for operational state). Calls the Delegate::GetCountdownTime() method.
*
* @param fromDelegate true if the change notice was triggered by the delegate, false if internal to cluster logic.
*/
void UpdateCountdownTime(bool fromDelegate);
/**
* @brief Whenever the cluster logic thinks time should be updated, call this.
*/
void UpdateCountdownTimeFromClusterLogic() { UpdateCountdownTime(/* fromDelegate=*/false); }
private:
Delegate * mDelegate;
const EndpointId mEndpointId;
const ClusterId mClusterId;
// Attribute Data Store
app::DataModel::Nullable<uint8_t> mCurrentPhase;
uint8_t mOperationalState = 0; // assume 0 for now.
GenericOperationalError mOperationalError = to_underlying(ErrorStateEnum::kNoError);
app::QuieterReportingAttribute<uint32_t> mCountdownTime{ DataModel::NullNullable };
/**
* This method is inherited from CommandHandlerInterface.
* This reimplementation does not check that the cluster ID in the HandlerContext (the cluster the command relates to)
* matches the cluster ID of the RequestT type.
* These cluster IDs may be different in the case where a command defined in the base cluster is intended for a
* derived cluster.
*/
template <typename RequestT, typename FuncT>
void HandleCommand(HandlerContext & handlerContext, FuncT func);
// Inherited from CommandHandlerInterface
void InvokeCommand(HandlerContext & ctx) override;
/**
* IM-level implementation of read
* @return 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.
* 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);
/**
* Handle Command: Stop.
*/
void HandleStopState(HandlerContext & ctx, const Commands::Stop::DecodableType & req);
/**
* Handle Command: Start.
*/
void HandleStartState(HandlerContext & ctx, const Commands::Start::DecodableType & req);
/**
* 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);
};
/**
* 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:
Delegate() = default;
virtual ~Delegate() = default;
/**
* Get the countdown time. This will get called on many edges such as
* commands to change operational state, or when the delegate deals with
* changes. Make sure it becomes null whenever it is appropriate.
*
* @return The current countdown time.
*/
virtual app::DataModel::Nullable<uint32_t> GetCountdownTime() = 0;
/**
* Fills in the provided GenericOperationalState with the state at index `index` if there is one,
* or returns CHIP_ERROR_NOT_FOUND if the index is out of range for the list of states.
* Note: This is used by the SDK to populate the operational state list attribute. If the contents of this list changes,
* the device SHALL call the Instance's ReportOperationalStateListChange method to report that this attribute has changed.
* @param index The index of the state, with 0 representing the first state.
* @param operationalState The GenericOperationalState is filled.
*/
virtual CHIP_ERROR GetOperationalStateAtIndex(size_t index, GenericOperationalState & operationalState) = 0;
/**
* Fills in the provided MutableCharSpan with the phase at index `index` if there is one,
* or returns CHIP_ERROR_NOT_FOUND if the index is out of range for the list of phases.
*
* If CHIP_ERROR_NOT_FOUND is returned for index 0, that indicates that the PhaseList attribute is null
* (there are no phases defined at all).
*
* Note: This is used by the SDK to populate the phase list attribute. If the contents of this list changes, the
* device SHALL call the Instance's ReportPhaseListChange method to report that this attribute has changed.
* @param index The index of the phase, with 0 representing the first phase.
* @param operationalPhase The MutableCharSpan is filled.
*/
virtual CHIP_ERROR GetOperationalPhaseAtIndex(size_t index, MutableCharSpan & operationalPhase) = 0;
// command callback
/**
* Handle Command Callback in application: Pause
* @param[out] err operational error after callback.
*/
virtual void HandlePauseStateCallback(GenericOperationalError & err) = 0;
/**
* Handle Command Callback in application: Resume
* @param[out] err operational error after callback.
*/
virtual void HandleResumeStateCallback(GenericOperationalError & err) = 0;
/**
* Handle Command Callback in application: Start
* @param[out] err operational error after callback.
*/
virtual void HandleStartStateCallback(GenericOperationalError & err) = 0;
/**
* Handle Command Callback in application: Stop
* @param[out] err operational error after callback.
*/
virtual void HandleStopStateCallback(GenericOperationalError & err) = 0;
private:
friend class Instance;
Instance * mInstance = nullptr;
/**
* This method is used by the SDK to set the instance pointer. This is done during the instantiation of a Instance object.
* @param aInstance A pointer to the Instance object related to this delegate object.
*/
void SetInstance(Instance * aInstance) { mInstance = aInstance; }
protected:
Instance * GetInstance() const { return mInstance; }
};
} // namespace OperationalState
namespace RvcOperationalState {
class Delegate : public OperationalState::Delegate
{
public:
/**
* Handle Command Callback in application: GoHome
* @param[out] err operational error after callback.
*/
virtual void HandleGoHomeCommandCallback(OperationalState::GenericOperationalError & err)
{
err.Set(to_underlying(OperationalState::ErrorStateEnum::kUnknownEnumValue));
};
/**
* The start command is not supported by the RvcOperationalState cluster hence this method should never be called.
* This is a dummy implementation of the handler method so the consumer of this class does not need to define it.
*/
void HandleStartStateCallback(OperationalState::GenericOperationalError & err) override
{
err.Set(to_underlying(OperationalState::ErrorStateEnum::kUnknownEnumValue));
};
/**
* The stop command is not supported by the RvcOperationalState cluster hence this method should never be called.
* This is a dummy implementation of the handler method so the consumer of this class does not need to define it.
*/
void HandleStopStateCallback(OperationalState::GenericOperationalError & err) override
{
err.Set(to_underlying(OperationalState::ErrorStateEnum::kUnknownEnumValue));
};
};
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(Delegate * aDelegate, EndpointId aEndpointId) :
OperationalState::Instance(aDelegate, aEndpointId, Id), mDelegate(aDelegate)
{}
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;
/**
* Handles the invocation of RvcOperationalState specific commands
* @param handlerContext The command handler context containing information about the received command.
*/
void InvokeDerivedClusterCommand(HandlerContext & handlerContext) override;
private:
Delegate * mDelegate;
/**
* Handle Command: GoHome
*/
void HandleGoHomeCommand(HandlerContext & ctx, const Commands::GoHome::DecodableType & req);
};
} // 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