blob: e81c4babd55c95ce63a80b758036b8d995b3c7e2 [file]
/*
*
* Copyright (c) 2026 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 "ConcentrationMeasurementCluster.h"
#include <app/server-cluster/AttributeListBuilder.h>
#include <clusters/CarbonDioxideConcentrationMeasurement/Metadata.h>
#include <clusters/CarbonMonoxideConcentrationMeasurement/Metadata.h>
#include <clusters/FormaldehydeConcentrationMeasurement/Metadata.h>
#include <clusters/NitrogenDioxideConcentrationMeasurement/Metadata.h>
#include <clusters/OzoneConcentrationMeasurement/Metadata.h>
#include <clusters/Pm10ConcentrationMeasurement/Metadata.h>
#include <clusters/Pm1ConcentrationMeasurement/Metadata.h>
#include <clusters/Pm25ConcentrationMeasurement/Metadata.h>
#include <clusters/RadonConcentrationMeasurement/Metadata.h>
#include <clusters/TotalVolatileOrganicCompoundsConcentrationMeasurement/Metadata.h>
static_assert(chip::app::Clusters::CarbonDioxideConcentrationMeasurement::kRevision ==
chip::app::Clusters::CarbonMonoxideConcentrationMeasurement::kRevision);
static_assert(chip::app::Clusters::CarbonDioxideConcentrationMeasurement::kRevision ==
chip::app::Clusters::FormaldehydeConcentrationMeasurement::kRevision);
static_assert(chip::app::Clusters::CarbonDioxideConcentrationMeasurement::kRevision ==
chip::app::Clusters::NitrogenDioxideConcentrationMeasurement::kRevision);
static_assert(chip::app::Clusters::CarbonDioxideConcentrationMeasurement::kRevision ==
chip::app::Clusters::OzoneConcentrationMeasurement::kRevision);
static_assert(chip::app::Clusters::CarbonDioxideConcentrationMeasurement::kRevision ==
chip::app::Clusters::Pm10ConcentrationMeasurement::kRevision);
static_assert(chip::app::Clusters::CarbonDioxideConcentrationMeasurement::kRevision ==
chip::app::Clusters::Pm1ConcentrationMeasurement::kRevision);
static_assert(chip::app::Clusters::CarbonDioxideConcentrationMeasurement::kRevision ==
chip::app::Clusters::Pm25ConcentrationMeasurement::kRevision);
static_assert(chip::app::Clusters::CarbonDioxideConcentrationMeasurement::kRevision ==
chip::app::Clusters::RadonConcentrationMeasurement::kRevision);
static_assert(chip::app::Clusters::CarbonDioxideConcentrationMeasurement::kRevision ==
chip::app::Clusters::TotalVolatileOrganicCompoundsConcentrationMeasurement::kRevision);
using namespace chip::app::Clusters::ConcentrationMeasurement::Attributes;
using chip::Protocols::InteractionModel::Status;
namespace {
bool IsInRange(const chip::app::DataModel::Nullable<float> & value, const chip::app::DataModel::Nullable<float> & minV,
const chip::app::DataModel::Nullable<float> & maxV)
{
VerifyOrReturnValue(!value.IsNull(), true);
VerifyOrReturnValue(!minV.IsNull() && value.Value() > minV.Value(), false);
VerifyOrReturnValue(!maxV.IsNull() && value.Value() < maxV.Value(), false);
return true;
}
} // namespace
namespace chip {
namespace app {
namespace Clusters {
namespace ConcentrationMeasurement {
ConcentrationMeasurementCluster::ConcentrationMeasurementCluster(EndpointId endpointId, const Config & config) :
DefaultServerCluster({ endpointId, config.clusterId }), mFeatures([&] {
BitFlags<Feature> f = config.features;
if (f.HasAny(Feature::kMediumLevel, Feature::kCriticalLevel))
{
f.Set(Feature::kLevelIndication);
}
if (f.HasAny(Feature::kPeakMeasurement, Feature::kAverageMeasurement))
{
f.Set(Feature::kNumericMeasurement);
}
return f;
}()),
mOptionalAttributeSet([&] {
OptionalAttributeSet optionalAttributes;
optionalAttributes.Set<Attributes::MeasuredValue::Id>(mFeatures.Has(Feature::kNumericMeasurement));
optionalAttributes.Set<Attributes::MinMeasuredValue::Id>(mFeatures.Has(Feature::kNumericMeasurement));
optionalAttributes.Set<Attributes::MaxMeasuredValue::Id>(mFeatures.Has(Feature::kNumericMeasurement));
optionalAttributes.Set<Attributes::MeasurementUnit::Id>(mFeatures.Has(Feature::kNumericMeasurement));
optionalAttributes.Set<Attributes::Uncertainty::Id>(mFeatures.Has(Feature::kNumericMeasurement) &&
config.uncertainty.has_value());
optionalAttributes.Set<Attributes::PeakMeasuredValue::Id>(mFeatures.Has(Feature::kPeakMeasurement));
optionalAttributes.Set<Attributes::PeakMeasuredValueWindow::Id>(mFeatures.Has(Feature::kPeakMeasurement));
optionalAttributes.Set<Attributes::AverageMeasuredValue::Id>(mFeatures.Has(Feature::kAverageMeasurement));
optionalAttributes.Set<Attributes::AverageMeasuredValueWindow::Id>(mFeatures.Has(Feature::kAverageMeasurement));
optionalAttributes.Set<Attributes::LevelValue::Id>(mFeatures.Has(Feature::kLevelIndication));
return optionalAttributes;
}()),
mMedium(config.medium), mUnit(config.unit), mMinMeasuredValue(config.minMeasured), mMaxMeasuredValue(config.maxMeasured),
mUncertainty(config.uncertainty)
{
VerifyOrDie(std::find(AliasedClusters.begin(), AliasedClusters.end(), config.clusterId) !=
AliasedClusters.end()); // NOLINT(bugprone-signed-bitwise)
}
DataModel::ActionReturnStatus ConcentrationMeasurementCluster::ReadAttribute(const DataModel::ReadAttributeRequest & request,
AttributeValueEncoder & encoder)
{
switch (request.path.mAttributeId)
{
case MeasuredValue::Id:
return encoder.Encode(mMeasuredValue);
case MinMeasuredValue::Id:
return encoder.Encode(mMinMeasuredValue);
case MaxMeasuredValue::Id:
return encoder.Encode(mMaxMeasuredValue);
case PeakMeasuredValue::Id:
return encoder.Encode(mPeakMeasuredValue);
case AverageMeasuredValue::Id:
return encoder.Encode(mAverageMeasuredValue);
case MeasurementMedium::Id:
return encoder.Encode(mMedium);
case Attributes::FeatureMap::Id:
return encoder.Encode(mFeatures);
case Attributes::ClusterRevision::Id:
return encoder.Encode(chip::app::Clusters::CarbonDioxideConcentrationMeasurement::kRevision);
case Uncertainty::Id:
return encoder.Encode(mUncertainty.value_or(0.0f));
case MeasurementUnit::Id:
return encoder.Encode(mUnit);
case PeakMeasuredValueWindow::Id:
return encoder.Encode(mPeakMeasuredValueWindow);
case AverageMeasuredValueWindow::Id:
return encoder.Encode(mAverageMeasuredValueWindow);
case LevelValue::Id:
return encoder.Encode(mLevelValue);
default:
return Status::UnsupportedAttribute;
}
}
CHIP_ERROR ConcentrationMeasurementCluster::Attributes(const ConcreteClusterPath & path,
ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder)
{
static constexpr DataModel::AttributeEntry kMandatoryAttrs[] = {
Attributes::MeasurementMedium::kMetadataEntry,
};
static constexpr DataModel::AttributeEntry kOptionalAttrs[] = {
Attributes::MeasuredValue::kMetadataEntry,
Attributes::MinMeasuredValue::kMetadataEntry,
Attributes::MaxMeasuredValue::kMetadataEntry,
Attributes::Uncertainty::kMetadataEntry,
Attributes::MeasurementUnit::kMetadataEntry,
Attributes::PeakMeasuredValue::kMetadataEntry,
Attributes::PeakMeasuredValueWindow::kMetadataEntry,
Attributes::AverageMeasuredValue::kMetadataEntry,
Attributes::AverageMeasuredValueWindow::kMetadataEntry,
Attributes::LevelValue::kMetadataEntry,
};
return AttributeListBuilder(builder).Append(chip::Span<const DataModel::AttributeEntry>(kMandatoryAttrs),
chip::Span<const DataModel::AttributeEntry>(kOptionalAttrs), mOptionalAttributeSet);
}
CHIP_ERROR ConcentrationMeasurementCluster::SetNullableFloatValue(DataModel::Nullable<float> value, Feature requiredFeature,
DataModel::Nullable<float> & storage, AttributeId attr)
{
VerifyOrReturnError(mFeatures.Has(requiredFeature), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
VerifyOrReturnError(IsInRange(value, mMinMeasuredValue, mMaxMeasuredValue), CHIP_IM_GLOBAL_STATUS(ConstraintError));
SetAttributeValue(storage, value, attr);
return CHIP_NO_ERROR;
}
CHIP_ERROR ConcentrationMeasurementCluster::SetMeasuredValue(DataModel::Nullable<float> value)
{
return SetNullableFloatValue(value, Feature::kNumericMeasurement, mMeasuredValue, MeasuredValue::Id);
}
CHIP_ERROR ConcentrationMeasurementCluster::SetPeakMeasuredValue(DataModel::Nullable<float> value)
{
return SetNullableFloatValue(value, Feature::kPeakMeasurement, mPeakMeasuredValue, PeakMeasuredValue::Id);
}
CHIP_ERROR ConcentrationMeasurementCluster::SetAverageMeasuredValue(DataModel::Nullable<float> value)
{
return SetNullableFloatValue(value, Feature::kAverageMeasurement, mAverageMeasuredValue, AverageMeasuredValue::Id);
}
CHIP_ERROR ConcentrationMeasurementCluster::SetWindowValue(uint32_t value, Feature requiredFeature, uint32_t & storage,
AttributeId attr)
{
VerifyOrReturnError(mFeatures.Has(requiredFeature), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
VerifyOrReturnError(value <= kWindowMaxSeconds, CHIP_IM_GLOBAL_STATUS(ConstraintError));
SetAttributeValue(storage, value, attr);
return CHIP_NO_ERROR;
}
CHIP_ERROR ConcentrationMeasurementCluster::SetPeakMeasuredValueWindow(uint32_t value)
{
return SetWindowValue(value, Feature::kPeakMeasurement, mPeakMeasuredValueWindow, PeakMeasuredValueWindow::Id);
}
CHIP_ERROR ConcentrationMeasurementCluster::SetAverageMeasuredValueWindow(uint32_t value)
{
return SetWindowValue(value, Feature::kAverageMeasurement, mAverageMeasuredValueWindow, AverageMeasuredValueWindow::Id);
}
CHIP_ERROR ConcentrationMeasurementCluster::SetLevelValue(LevelValueEnum value)
{
VerifyOrReturnError(mFeatures.Has(Feature::kLevelIndication), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
VerifyOrReturnError(value < LevelValueEnum::kUnknownEnumValue, CHIP_IM_GLOBAL_STATUS(ConstraintError));
if ((value == LevelValueEnum::kMedium) && !mFeatures.Has(Feature::kMediumLevel))
{
return CHIP_IM_GLOBAL_STATUS(ConstraintError);
}
if ((value == LevelValueEnum::kCritical) && !mFeatures.Has(Feature::kCriticalLevel))
{
return CHIP_IM_GLOBAL_STATUS(ConstraintError);
}
SetAttributeValue(mLevelValue, value, LevelValue::Id);
return CHIP_NO_ERROR;
}
} // namespace ConcentrationMeasurement
} // namespace Clusters
} // namespace app
} // namespace chip