blob: cb369f6b2f23fdbf51c247cd0f98c22c10ec8199 [file] [log] [blame]
/**
*
* Copyright (c) 2020 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <app/util/af.h>
#include <app/util/util.h>
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app-common/zap-generated/cluster-enums.h>
#include <app-common/zap-generated/ids/Attributes.h>
#include <app/ConcreteAttributePath.h>
#include <app/ConcreteCommandPath.h>
#include <app/InteractionModelEngine.h>
#include <app/util/attribute-storage.h>
#include <app/util/config.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::PumpConfigurationAndControl;
namespace chip {
namespace app {
namespace Clusters {
namespace PumpConfigurationAndControl {
// Enum for RemoteSensorType
enum class RemoteSensorType : uint8_t
{
kNoSensor = 0x00,
kPressureSensor = 0x01,
kFlowSensor = 0x02,
kTemperatureSensor = 0x03,
};
static RemoteSensorType detectRemoteSensorConnected()
{
// TODO: Detect the sensor types attached to the pump control cluster
// this could be pressure, flow or temperature sensors.
return RemoteSensorType::kNoSensor;
}
static void setEffectiveModes(EndpointId endpoint)
{
ControlModeEnum controlMode;
OperationModeEnum operationMode;
BitMask<PumpStatusBitmap> pumpStatus;
bool isControlModeAvailable = true;
bool isPumpStatusAvailable = true;
isControlModeAvailable = emberAfContainsAttribute(endpoint, PumpConfigurationAndControl::Id, Attributes::ControlMode::Id);
isPumpStatusAvailable = emberAfContainsAttribute(endpoint, PumpConfigurationAndControl::Id, Attributes::PumpStatus::Id);
// Get the current control- and operation modes
Attributes::OperationMode::Get(endpoint, &operationMode);
if (isControlModeAvailable)
{
Attributes::ControlMode::Get(endpoint, &controlMode);
}
else
{
// If controlMode attribute is not available, then use the default value
// of the effectiveControlMode attribute as the effectiveControlMode
// if this is not suitable, the application should override this value in
// the post attribute change callback for the operation mode attribute
const EmberAfAttributeMetadata * effectiveControlModeMetaData;
effectiveControlModeMetaData = GetAttributeMetadata(
app::ConcreteAttributePath(endpoint, PumpConfigurationAndControl::Id, Attributes::EffectiveControlMode::Id));
controlMode = static_cast<ControlModeEnum>(effectiveControlModeMetaData->defaultValue.defaultValue);
}
if (isPumpStatusAvailable)
{
Attributes::PumpStatus::Get(endpoint, &pumpStatus);
}
switch (operationMode)
{
case OperationModeEnum::kNormal: {
// The pump runs in the control mode as per the type of the remote sensor
// If the remote sensor is a Flow sensor the mode would be ConstantFlow
// If the remote sensor is a Pressure sensor the mode would be ConstantPressure (not ProportionalPressure)
// If the remote sensor is a Temperature sensor the mode would be ConstantTemperature
// If a remote sensor is detected and the OperationMode is kNormal, then the pump is operating in the
// control mode indicated by the repective remote senor type
RemoteSensorType sensorType = detectRemoteSensorConnected();
switch (sensorType)
{
case RemoteSensorType::kFlowSensor:
Attributes::EffectiveControlMode::Set(endpoint, ControlModeEnum::kConstantFlow);
if (isPumpStatusAvailable)
{
pumpStatus.Set(PumpStatusBitmap::kRemoteFlow);
pumpStatus.Clear(PumpStatusBitmap::kRemotePressure);
pumpStatus.Clear(PumpStatusBitmap::kRemoteTemperature);
}
break;
case RemoteSensorType::kPressureSensor:
Attributes::EffectiveControlMode::Set(endpoint, ControlModeEnum::kConstantPressure);
if (isPumpStatusAvailable)
{
pumpStatus.Clear(PumpStatusBitmap::kRemoteFlow);
pumpStatus.Set(PumpStatusBitmap::kRemotePressure);
pumpStatus.Clear(PumpStatusBitmap::kRemoteTemperature);
}
break;
case RemoteSensorType::kTemperatureSensor:
Attributes::EffectiveControlMode::Set(endpoint, ControlModeEnum::kConstantTemperature);
if (isPumpStatusAvailable)
{
pumpStatus.Clear(PumpStatusBitmap::kRemoteFlow);
pumpStatus.Clear(PumpStatusBitmap::kRemotePressure);
pumpStatus.Set(PumpStatusBitmap::kRemoteTemperature);
}
break;
case RemoteSensorType::kNoSensor:
// The pump is controlled by a setpoint, as defined by
// the ControlMode attribute. (N.B. The setpoint is an internal variable which MAY be
// controlled between 0% and 100%, e.g., by means of the Level Control cluster)
// The ControlMode can be any of the following:
// ConstantSpeed, ConstantPressure, ProportionalPressure,
// ConstantFlow, ConstantTemperature or Automatic. The actual ControlMode
// which would be the EffectiveControlMode is dependant on the actual
// physical pump application running "on-top" of this cluster server.
Attributes::EffectiveControlMode::Set(endpoint, controlMode);
if (isPumpStatusAvailable)
{
pumpStatus.Clear(PumpStatusBitmap::kRemoteFlow);
pumpStatus.Clear(PumpStatusBitmap::kRemotePressure);
pumpStatus.Clear(PumpStatusBitmap::kRemoteTemperature);
}
break;
}
// Set the overall effective operation mode to Normal
Attributes::EffectiveOperationMode::Set(endpoint, OperationModeEnum::kNormal);
}
break;
// The pump is controlled by the OperationMode attribute.
// Maximum, Minimum or Local
case OperationModeEnum::kMaximum: {
#ifdef EMBER_AF_PLUGIN_LEVEL_CONTROL
uint8_t maxLevel;
#endif
Attributes::EffectiveOperationMode::Set(endpoint, OperationModeEnum::kMaximum);
Attributes::EffectiveControlMode::Set(endpoint, ControlModeEnum::kConstantSpeed);
#ifdef EMBER_AF_PLUGIN_LEVEL_CONTROL
LevelControl::Attributes::MaxLevel::Get(endpoint, &maxLevel);
LevelControl::Attributes::CurrentLevel::Set(endpoint, maxLevel);
#endif
if (isPumpStatusAvailable)
{
pumpStatus.Clear(PumpStatusBitmap::kRemoteFlow);
pumpStatus.Clear(PumpStatusBitmap::kRemotePressure);
pumpStatus.Clear(PumpStatusBitmap::kRemoteTemperature);
}
}
break;
case OperationModeEnum::kMinimum: {
#ifdef EMBER_AF_PLUGIN_LEVEL_CONTROL
uint8_t minLevel;
#endif
Attributes::EffectiveOperationMode::Set(endpoint, OperationModeEnum::kMinimum);
Attributes::EffectiveControlMode::Set(endpoint, ControlModeEnum::kConstantSpeed);
#ifdef EMBER_AF_PLUGIN_LEVEL_CONTROL
LevelControl::Attributes::MinLevel::Get(endpoint, &minLevel);
if (minLevel == 0)
{
// Bump the minimum level to 1, since the value of 0 means stop
minLevel = 1;
}
LevelControl::Attributes::CurrentLevel::Set(endpoint, minLevel);
#endif
if (isPumpStatusAvailable)
{
pumpStatus.Clear(PumpStatusBitmap::kRemoteFlow);
pumpStatus.Clear(PumpStatusBitmap::kRemotePressure);
pumpStatus.Clear(PumpStatusBitmap::kRemoteTemperature);
}
}
break;
case OperationModeEnum::kLocal: {
// If the Application sets the OperatioMode to kLocal the application "owns" the EffectiveControlMode, which
// it also does if the external entity sets the OperationMode to kLocal. So in any case the application
// must set the EffectiveControlMode to something which applies to the current ControlMode in the application.
// So to keeps things short: the application layer owns the EffetiveControlMode when OperationMode is kLocal.
Attributes::EffectiveOperationMode::Set(endpoint, OperationModeEnum::kLocal);
// Set the current ControlMode for now. Perhaps the application will set the EffectiveControlMode to something else.
Attributes::EffectiveControlMode::Set(endpoint, controlMode);
// Clear out the remote sensors from the PumpStatus flags.
if (isPumpStatusAvailable)
{
pumpStatus.Clear(PumpStatusBitmap::kRemoteFlow);
pumpStatus.Clear(PumpStatusBitmap::kRemotePressure);
pumpStatus.Clear(PumpStatusBitmap::kRemoteTemperature);
}
}
break;
case OperationModeEnum::kUnknownEnumValue: {
// Not expected; see check in MatterPumpConfigurationAndControlClusterServerPreAttributeChangedCallback.
break;
}
}
if (isPumpStatusAvailable)
{
Attributes::PumpStatus::Set(endpoint, pumpStatus);
}
}
bool HasFeature(EndpointId endpoint, Feature feature)
{
bool hasFeature;
uint32_t featureMap;
hasFeature = (Attributes::FeatureMap::Get(endpoint, &featureMap) == EMBER_ZCL_STATUS_SUCCESS);
return hasFeature ? ((featureMap & to_underlying(feature)) != 0) : false;
}
} // namespace PumpConfigurationAndControl
} // namespace Clusters
} // namespace app
} // namespace chip
// SDK Callbacks
void emberAfPumpConfigurationAndControlClusterServerInitCallback(EndpointId endpoint)
{
emberAfDebugPrintln("Initialize PCC Server Cluster [EP:%d]", endpoint);
}
chip::Protocols::InteractionModel::Status MatterPumpConfigurationAndControlClusterServerPreAttributeChangedCallback(
const chip::app::ConcreteAttributePath & attributePath, EmberAfAttributeType attributeType, uint16_t size, uint8_t * value)
{
emberAfDebugPrintln("PCC Server Cluster Attribute Pre-changed [EP:%d, ID:0x%x]", attributePath.mEndpointId,
(unsigned int) attributePath.mAttributeId);
Protocols::InteractionModel::Status status = Protocols::InteractionModel::Status::Success;
switch (attributePath.mAttributeId)
{
case Attributes::ControlMode::Id: {
ControlModeEnum controlMode;
NumericAttributeTraits<ControlModeEnum>::StorageType tmp;
memcpy(&tmp, value, size);
controlMode = NumericAttributeTraits<ControlModeEnum>::StorageToWorking(tmp);
switch (controlMode)
{
case ControlModeEnum::kConstantFlow:
if (!HasFeature(attributePath.mEndpointId, Feature::kConstantFlow))
{
status = Protocols::InteractionModel::Status::ConstraintError;
}
break;
case ControlModeEnum::kConstantPressure:
if (!HasFeature(attributePath.mEndpointId, Feature::kConstantPressure))
{
status = Protocols::InteractionModel::Status::ConstraintError;
}
break;
case ControlModeEnum::kConstantSpeed:
if (!HasFeature(attributePath.mEndpointId, Feature::kConstantSpeed))
{
status = Protocols::InteractionModel::Status::ConstraintError;
}
break;
case ControlModeEnum::kConstantTemperature:
if (!HasFeature(attributePath.mEndpointId, Feature::kConstantTemperature))
{
status = Protocols::InteractionModel::Status::ConstraintError;
}
break;
case ControlModeEnum::kProportionalPressure:
if (!HasFeature(attributePath.mEndpointId, Feature::kCompensatedPressure))
{
status = Protocols::InteractionModel::Status::ConstraintError;
}
break;
case ControlModeEnum::kAutomatic:
if (!HasFeature(attributePath.mEndpointId, Feature::kAutomatic))
{
status = Protocols::InteractionModel::Status::ConstraintError;
}
break;
case ControlModeEnum::kUnknownEnumValue:
status = Protocols::InteractionModel::Status::ConstraintError;
break;
}
}
break;
case Attributes::OperationMode::Id:
OperationModeEnum operationMode;
NumericAttributeTraits<OperationModeEnum>::StorageType tmp;
memcpy(&tmp, value, size);
operationMode = NumericAttributeTraits<OperationModeEnum>::StorageToWorking(tmp);
switch (operationMode)
{
case OperationModeEnum::kMinimum:
if (!HasFeature(attributePath.mEndpointId, Feature::kConstantSpeed))
{
status = Protocols::InteractionModel::Status::ConstraintError;
}
break;
case OperationModeEnum::kMaximum:
if (!HasFeature(attributePath.mEndpointId, Feature::kConstantSpeed))
{
status = Protocols::InteractionModel::Status::ConstraintError;
}
break;
case OperationModeEnum::kLocal:
if (!HasFeature(attributePath.mEndpointId, Feature::kLocalOperation))
{
status = Protocols::InteractionModel::Status::ConstraintError;
}
break;
case OperationModeEnum::kNormal:
status = Protocols::InteractionModel::Status::Success;
break;
case OperationModeEnum::kUnknownEnumValue:
status = Protocols::InteractionModel::Status::ConstraintError;
break;
}
break;
default:
status = Protocols::InteractionModel::Status::Success;
}
return status;
}
void MatterPumpConfigurationAndControlClusterServerAttributeChangedCallback(const app::ConcreteAttributePath & attributePath)
{
emberAfDebugPrintln("PCC Server Cluster Attribute changed [EP:%d, ID:0x%x]", attributePath.mEndpointId,
(unsigned int) attributePath.mAttributeId);
switch (attributePath.mAttributeId)
{
case Attributes::ControlMode::Id:
case Attributes::OperationMode::Id:
setEffectiveModes(attributePath.mEndpointId);
break;
default:
emberAfDebugPrintln("PCC Server: unhandled attribute ID");
}
}
void MatterPumpConfigurationAndControlPluginServerInitCallback() {}