/* * * 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 "concentration-measurement-cluster-objects.h" #include #include #include #include #include #include #include namespace chip { namespace app { namespace Clusters { namespace ConcentrationMeasurement { namespace Detail { struct DummyNumericMeasurementMembers { }; struct DummyPeakMeasurementMembers { }; struct DummyAverageMeasurementMembers { }; struct DummyLevelIndicationMembers { }; class NumericMeasurementMembers { protected: DataModel::Nullable mMeasuredValue; DataModel::Nullable mMinMeasuredValue; DataModel::Nullable mMaxMeasuredValue; MeasurementUnitEnum mMeasurementUnit; float mUncertainty; }; class PeakMeasurementMembers { protected: DataModel::Nullable mPeakMeasuredValue; uint32_t mPeakMeasuredValueWindow; }; class AverageMeasurementMembers { protected: DataModel::Nullable mAverageMeasuredValue; uint32_t mAverageMeasuredValueWindow; }; class LevelIndicationMembers { protected: LevelValueEnum mLevel; }; } // namespace Detail /** * This class provides the base implementation for the server side of the Concentration Measurement cluster as well as an API for * setting the values of the attributes. * * @tparam NumericMeasurementEnabled whether the cluster supports numeric measurement * @tparam LevelIndicationEnabled whether the cluster supports level indication * @tparam MediumLevelEnabled whether the Level Indication Feature supports medium level * @tparam CriticalLevelEnabled whether the Level Indication Feature supports critical level * @tparam PeakMeasurementEnabled whether the Numeric Measurement Feature supports peak measurement * @tparam AverageMeasurementEnabled whether the Numeric Measurement Feature supports average measurement */ template class Instance : public AttributeAccessInterface, protected std::conditional_t, protected std::conditional_t, protected std::conditional_t, protected std::conditional_t { private: static const int WINDOW_MAX = 604800; EndpointId mEndpointId{}; ClusterId mClusterId{}; MeasurementMediumEnum mMeasurementMedium; uint32_t mFeatureMap = 0; // AttributeAccessInterface CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override { switch (aPath.mAttributeId) { case Attributes::MeasuredValue::Id: if constexpr (NumericMeasurementEnabled) { ReturnErrorOnFailure(aEncoder.Encode(this->mMeasuredValue)); } break; case Attributes::MinMeasuredValue::Id: if constexpr (NumericMeasurementEnabled) { ReturnErrorOnFailure(aEncoder.Encode(this->mMinMeasuredValue)); } break; case Attributes::MaxMeasuredValue::Id: if constexpr (NumericMeasurementEnabled) { ReturnErrorOnFailure(aEncoder.Encode(this->mMaxMeasuredValue)); } break; case Attributes::Uncertainty::Id: if constexpr (NumericMeasurementEnabled) { ReturnErrorOnFailure(aEncoder.Encode(this->mUncertainty)); } break; case Attributes::MeasurementUnit::Id: if constexpr (NumericMeasurementEnabled) { ReturnErrorOnFailure(aEncoder.Encode(this->mMeasurementUnit)); break; } case Attributes::PeakMeasuredValue::Id: if constexpr (PeakMeasurementEnabled) { ReturnErrorOnFailure(aEncoder.Encode(this->mPeakMeasuredValue)); } break; case Attributes::PeakMeasuredValueWindow::Id: if constexpr (PeakMeasurementEnabled) { ReturnErrorOnFailure(aEncoder.Encode(this->mPeakMeasuredValueWindow)); } break; case Attributes::AverageMeasuredValue::Id: if constexpr (AverageMeasurementEnabled) { ReturnErrorOnFailure(aEncoder.Encode(this->mAverageMeasuredValue)); } break; case Attributes::AverageMeasuredValueWindow::Id: if constexpr (AverageMeasurementEnabled) { ReturnErrorOnFailure(aEncoder.Encode(this->mAverageMeasuredValueWindow)); } break; case Attributes::LevelValue::Id: if constexpr (LevelIndicationEnabled) { ReturnErrorOnFailure(aEncoder.Encode(this->mLevel)); } break; case Attributes::MeasurementMedium::Id: ReturnErrorOnFailure(aEncoder.Encode(mMeasurementMedium)); break; case Attributes::FeatureMap::Id: ReturnErrorOnFailure(aEncoder.Encode(mFeatureMap)); break; } return CHIP_NO_ERROR; }; /** * This checks if the clusters instance is a valid ResourceMonitoring cluster based on the AliasedClusters list. * @return true if the cluster is a valid ResourceMonitoring cluster. */ bool IsValidAliasCluster() const { for (unsigned int AliasedCluster : AliasedClusters) { if (mClusterId == AliasedCluster) { return true; } } return false; }; /** * This generates a feature bitmap from the enabled features and then returns its raw value. * @return The raw feature bitmap. */ uint32_t GenerateFeatureMap() const { BitMask featureMap(0); if constexpr (NumericMeasurementEnabled) { featureMap.Set(Feature::kNumericMeasurement); } if constexpr (LevelIndicationEnabled) { featureMap.Set(Feature::kLevelIndication); } if constexpr (MediumLevelEnabled) { featureMap.Set(Feature::kMediumLevel); } if constexpr (CriticalLevelEnabled) { featureMap.Set(Feature::kCriticalLevel); } if constexpr (PeakMeasurementEnabled) { featureMap.Set(Feature::kPeakMeasurement); } if constexpr (AverageMeasurementEnabled) { featureMap.Set(Feature::kAverageMeasurement); } return featureMap.Raw(); }; /** * This checks if a given nullable float is within the min and max constraints or two other nullable floats. * @param value The value to check. * @param minValue The minimum value. * @param maxValue The maximum value. * @return true if the value is within the min and max constraints. If either of the pair of values being compared is null, * that's considered to be within the constraint. */ static bool CheckConstraintMinMax(DataModel::Nullable value, DataModel::Nullable minValue, DataModel::Nullable maxValue) { return (minValue.IsNull() || value.IsNull() || (value.Value() >= minValue.Value())) && (maxValue.IsNull() || value.IsNull() || (value.Value() <= maxValue.Value())); }; /** * This checks if a given nullable float is less than or equal to another given nullable float. * @param value The value to check. * @param valueToBeLessThanOrEqualTo The value to be less than or equal to. * @return true if value is less than or equal to valueToBeLessThanOrEqualTo, or if either of the values is Null. */ static bool CheckConstraintsLessThanOrEqualTo(DataModel::Nullable value, DataModel::Nullable valueToBeLessThanOrEqualTo) { return valueToBeLessThanOrEqualTo.IsNull() || value.IsNull() || (value.Value() <= valueToBeLessThanOrEqualTo.Value()); }; /** * This checks if a given nullable float is greater than or equal to another given nullable float. * @param value The value to check. * @param valueToBeGreaterThanOrEqualTo The value to be greater than or equal to. * @return true if value is greater than or equal to valueToBeGreaterThanOrEqualTo, or if either of the values is Null. */ static bool CheckConstraintsGreaterThanOrEqualTo(DataModel::Nullable value, DataModel::Nullable valueToBeGreaterThanOrEqualTo) { return valueToBeGreaterThanOrEqualTo.IsNull() || value.IsNull() || (value.Value() >= valueToBeGreaterThanOrEqualTo.Value()); }; public: /** * Creates a mode base 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. * This constructor should be used when not using the kNumericMeasurement feature. * @param aEndpointId The endpoint on which this cluster exists. This must match the zap configuration. * @param aClusterId The ID of the ModeBase aliased cluster to be instantiated. * @param aMeasurementMedium The measurement medium. */ Instance(EndpointId aEndpointId, ClusterId aClusterId, MeasurementMediumEnum aMeasurementMedium) : AttributeAccessInterface(Optional(aEndpointId), aClusterId), mEndpointId(aEndpointId), mClusterId(aClusterId), mMeasurementMedium(aMeasurementMedium){}; /** * Creates a mode base 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. * This constructor should be used when using the kNumericMeasurement feature. * @param aEndpointId The endpoint on which this cluster exists. This must match the zap configuration. * @param aClusterId The ID of the ModeBase aliased cluster to be instantiated. * @param aMeasurementMedium The measurement medium. * @param aMeasurementUnit The measurement unit. */ Instance(EndpointId aEndpointId, ClusterId aClusterId, MeasurementMediumEnum aMeasurementMedium, MeasurementUnitEnum aMeasurementUnit) : AttributeAccessInterface(Optional(aEndpointId), aClusterId), mEndpointId(aEndpointId), mClusterId(aClusterId), mMeasurementMedium(aMeasurementMedium) { this->mMeasurementUnit = aMeasurementUnit; }; ~Instance() override { AttributeAccessInterfaceRegistry::Instance().Unregister(this); }; CHIP_ERROR Init() { static_assert(NumericMeasurementEnabled || LevelIndicationEnabled, "At least one of NumericMeasurement or LevelIndication " "should be enabled"); static_assert(!MediumLevelEnabled || LevelIndicationEnabled, "MediumLevelEnabled requires LevelIndicationEnabled to be true"); static_assert(!CriticalLevelEnabled || LevelIndicationEnabled, "CriticalLevelEnabled requires LevelIndicationEnabled to be true"); static_assert(!PeakMeasurementEnabled || NumericMeasurementEnabled, "PeakMeasurementEnabled requires NumericMeasurementEnabled to be true"); static_assert(!AverageMeasurementEnabled || NumericMeasurementEnabled, "AverageMeasurementEnabled requires NumericMeasurementEnabled to be true"); VerifyOrReturnError(IsValidAliasCluster(), CHIP_ERROR_INCORRECT_STATE); // Check if the cluster has been selected in zap VerifyOrReturnError(emberAfContainsServer(mEndpointId, mClusterId), CHIP_ERROR_INCORRECT_STATE); // Register the object as attribute provider VerifyOrReturnError(AttributeAccessInterfaceRegistry::Instance().Register(this), CHIP_ERROR_INCORRECT_STATE); mFeatureMap = GenerateFeatureMap(); return CHIP_NO_ERROR; }; template > CHIP_ERROR SetMeasuredValue(DataModel::Nullable aMeasuredValue) { if (!CheckConstraintMinMax(aMeasuredValue, this->mMinMeasuredValue, this->mMaxMeasuredValue)) { return CHIP_ERROR_INVALID_ARGUMENT; } // Check to see if a change has ocurred VerifyOrReturnError(this->mMeasuredValue != aMeasuredValue, CHIP_NO_ERROR); this->mMeasuredValue = aMeasuredValue; MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::MeasuredValue::Id); return CHIP_NO_ERROR; }; template > CHIP_ERROR SetMinMeasuredValue(DataModel::Nullable aMinMeasuredValue) { if (!CheckConstraintsLessThanOrEqualTo(aMinMeasuredValue, this->mMaxMeasuredValue)) { return CHIP_ERROR_INVALID_ARGUMENT; } if (!CheckConstraintsLessThanOrEqualTo(aMinMeasuredValue, this->mMeasuredValue)) { return CHIP_ERROR_INVALID_ARGUMENT; } // Check to see if a change has ocurred VerifyOrReturnError(this->mMinMeasuredValue != aMinMeasuredValue, CHIP_NO_ERROR); this->mMinMeasuredValue = aMinMeasuredValue; MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::MinMeasuredValue::Id); return CHIP_NO_ERROR; }; template > CHIP_ERROR SetMaxMeasuredValue(DataModel::Nullable aMaxMeasuredValue) { if (!CheckConstraintsGreaterThanOrEqualTo(aMaxMeasuredValue, this->mMinMeasuredValue)) { return CHIP_ERROR_INVALID_ARGUMENT; } if (!CheckConstraintsGreaterThanOrEqualTo(aMaxMeasuredValue, this->mMeasuredValue)) { return CHIP_ERROR_INVALID_ARGUMENT; } // Check to see if a change has ocurred VerifyOrReturnError(this->mMaxMeasuredValue != aMaxMeasuredValue, CHIP_NO_ERROR); this->mMaxMeasuredValue = aMaxMeasuredValue; MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::MaxMeasuredValue::Id); return CHIP_NO_ERROR; }; template > CHIP_ERROR SetUncertainty(float aUncertainty) { // Check to see if a change has ocurred VerifyOrReturnError(this->mUncertainty != aUncertainty, CHIP_NO_ERROR); this->mUncertainty = aUncertainty; MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::Uncertainty::Id); return CHIP_NO_ERROR; }; template > CHIP_ERROR SetPeakMeasuredValue(DataModel::Nullable aPeakMeasuredValue) { if (!CheckConstraintMinMax(aPeakMeasuredValue, this->mMinMeasuredValue, this->mMaxMeasuredValue)) { return CHIP_ERROR_INVALID_ARGUMENT; } // Check to see if a change has ocurred VerifyOrReturnError(this->mPeakMeasuredValue != aPeakMeasuredValue, CHIP_NO_ERROR); this->mPeakMeasuredValue = aPeakMeasuredValue; MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::PeakMeasuredValue::Id); return CHIP_NO_ERROR; }; template > CHIP_ERROR SetPeakMeasuredValueWindow(uint32_t aPeakMeasuredValueWindow) { if (aPeakMeasuredValueWindow > WINDOW_MAX) { return CHIP_ERROR_INVALID_ARGUMENT; } // Check to see if a change has ocurred VerifyOrReturnError(this->mPeakMeasuredValueWindow != aPeakMeasuredValueWindow, CHIP_NO_ERROR); this->mPeakMeasuredValueWindow = aPeakMeasuredValueWindow; MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::PeakMeasuredValueWindow::Id); return CHIP_NO_ERROR; }; template > CHIP_ERROR SetAverageMeasuredValue(DataModel::Nullable aAverageMeasuredValue) { if (!CheckConstraintMinMax(aAverageMeasuredValue, this->mMinMeasuredValue, this->mMaxMeasuredValue)) { return CHIP_ERROR_INVALID_ARGUMENT; } // Check to see if a change has ocurred VerifyOrReturnError(this->mAverageMeasuredValue != aAverageMeasuredValue, CHIP_NO_ERROR); this->mAverageMeasuredValue = aAverageMeasuredValue; MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::AverageMeasuredValue::Id); return CHIP_NO_ERROR; }; template > CHIP_ERROR SetAverageMeasuredValueWindow(uint32_t aAverageMeasuredValueWindow) { if (aAverageMeasuredValueWindow > WINDOW_MAX) { return CHIP_ERROR_INVALID_ARGUMENT; } // Check to see if a change has ocurred VerifyOrReturnError(this->mAverageMeasuredValueWindow != aAverageMeasuredValueWindow, CHIP_NO_ERROR); this->mAverageMeasuredValueWindow = aAverageMeasuredValueWindow; MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::AverageMeasuredValueWindow::Id); return CHIP_NO_ERROR; }; template > CHIP_ERROR SetLevelValue(LevelValueEnum aLevel) { if constexpr (!MediumLevelEnabled) { if (aLevel == LevelValueEnum::kMedium) { return CHIP_ERROR_INVALID_ARGUMENT; } } if constexpr (!CriticalLevelEnabled) { if (aLevel == LevelValueEnum::kCritical) { return CHIP_ERROR_INVALID_ARGUMENT; } } // Check to see if a change has ocurred VerifyOrReturnError(this->mLevel != aLevel, CHIP_NO_ERROR); this->mLevel = aLevel; MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::LevelValue::Id); return CHIP_NO_ERROR; }; }; /** * A factory function to create a new instance of a Concentration Measurement Cluster with only the NumericMeasurement feature * enabled. * * @tparam PeakMeasurementEnabled Whether the PeakMeasurement feature is enabled. * @tparam AverageMeasurementEnabled Whether the AverageMeasurement feature is enabled. * @param endpoint Endpoint that the cluster is on. * @param clusterId Cluster that the cluster is on. * @param aMeasurementMedium The measurement medium. * @param aMeasurementUnit The measurement unit. * @return A new instance of Concentration Measurement Cluster. */ template Instance CreateNumericMeasurementConcentrationCluster(EndpointId endpoint, ClusterId clusterId, MeasurementMediumEnum aMeasurementMedium, MeasurementUnitEnum aMeasurementUnit) { return Instance( endpoint, clusterId, aMeasurementMedium, aMeasurementUnit); } /** * A factory function to create a new instance of a Concentration Measurement Cluster with only the Level Indication feature * enabled. * * @tparam MediumLevelEnabled Whether the MediumLevel feature is enabled. * @tparam CriticalLevelEnabled Whether the CriticalLevel feature is enabled. * @param endpoint Endpoint that the cluster is on. * @param clusterId Cluster that the cluster is on. * @param aMeasurementMedium The measurement medium. * @return A new instance of Concentration Measurement Cluster. */ template Instance CreateLevelIndicationConcentrationCluster(EndpointId endpoint, ClusterId clusterId, MeasurementMediumEnum aMeasurementMedium) { return Instance(endpoint, clusterId, aMeasurementMedium); } /** * A factory function to create a new instance of a Concentration Measurement Cluster with both the NumericMeasurement and Level * Indication features enabled. * * @tparam MediumLevelEnabled Whether the MediumLevel feature is enabled. * @tparam CriticalLevelEnabled Whether the CriticalLevel feature is enabled. * @tparam PeakMeasurementEnabled Whether the PeakMeasurement feature is enabled. * @tparam AverageMeasurementEnabled Whether the AverageMeasurement feature is enabled. * @param endpoint Endpoint that the cluster is on. * @param clusterId Cluster that the cluster is on. * @param aMeasurementMedium The measurement medium. * @param aMeasurementUnit The measurement unit. * @return A new instance of Concentration Measurement Cluster. */ template Instance CreateNumericMeasurementAndLevelIndicationConcentrationCluster(EndpointId endpoint, ClusterId clusterId, MeasurementMediumEnum aMeasurementMedium, MeasurementUnitEnum aMeasurementUnit) { return Instance( endpoint, clusterId, aMeasurementMedium, aMeasurementUnit); } } // namespace ConcentrationMeasurement } // namespace Clusters } // namespace app } // namespace chip