| /** |
| * |
| * Copyright (c) 2023-2025 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 "ValveConfigurationAndControlCluster.h" |
| |
| #include <app/persistence/AttributePersistence.h> |
| #include <app/server-cluster/AttributeListBuilder.h> |
| #include <clusters/ValveConfigurationAndControl/Commands.h> |
| #include <clusters/ValveConfigurationAndControl/Events.h> |
| #include <lib/support/CodeUtils.h> |
| |
| #include <platform/CHIPDeviceLayer.h> |
| |
| using namespace chip; |
| using namespace chip::app; |
| using namespace chip::app::Clusters; |
| using namespace chip::app::Clusters::ValveConfigurationAndControl; |
| using chip::Protocols::InteractionModel::Status; |
| |
| ValveConfigurationAndControlCluster::ValveConfigurationAndControlCluster(EndpointId endpoint, ValveContext context) : |
| DefaultServerCluster({ endpoint, ValveConfigurationAndControl::Id }), mFeatures(context.features), |
| mOptionalAttributeSet(context.optionalAttributeSet), mDefaultOpenDuration(context.config.defaultOpenDuration), |
| mDefaultOpenLevel(context.config.defaultOpenLevel), mLevelStep(context.config.levelStep), mDelegate(context.delegate), |
| mTsTracker(context.tsTracker) |
| {} |
| |
| CHIP_ERROR ValveConfigurationAndControlCluster::Startup(ServerClusterContext & context) |
| { |
| ReturnErrorOnFailure(DefaultServerCluster::Startup(context)); |
| |
| // This feature shall not be supported if the node doesn't support the TimeSync cluster. |
| if (mFeatures.Has(Feature::kTimeSync)) |
| { |
| VerifyOrReturnError((mTsTracker != nullptr), CHIP_ERROR_INVALID_ARGUMENT); |
| } |
| |
| // The RemainingDuration attribute shall be reported when: |
| // - It changes from null or vice versa (default handling for QuieterReportingAttribute) |
| // - It changes to 0 |
| // - It increases. |
| mRemainingDuration.policy() |
| .Set(QuieterReportingPolicyEnum::kMarkDirtyOnChangeToFromZero) |
| .Set(QuieterReportingPolicyEnum::kMarkDirtyOnIncrement); |
| |
| // Try to get the stored value for the DefaultOpenDuration attribute. |
| AttributePersistence attrPersistence{ context.attributeStorage }; |
| const DataModel::Nullable<uint32_t> defaultOpenDuration = mDefaultOpenDuration; |
| attrPersistence.LoadNativeEndianValue( |
| { mPath.mEndpointId, ValveConfigurationAndControl::Id, ValveConfigurationAndControl::Attributes::DefaultOpenDuration::Id }, |
| mDefaultOpenDuration, defaultOpenDuration); |
| |
| // If Level feature is enabled and optional attribute is set, try to get value for |
| // DefaultOpenLevel |
| if (mFeatures.Has(Feature::kLevel) && mOptionalAttributeSet.IsSet(Attributes::DefaultOpenLevel::Id)) |
| { |
| Percent defaultOpenLevel = mDefaultOpenLevel; |
| attrPersistence.LoadNativeEndianValue( |
| { mPath.mEndpointId, ValveConfigurationAndControl::Id, ValveConfigurationAndControl::Attributes::DefaultOpenLevel::Id }, |
| mDefaultOpenLevel, defaultOpenLevel); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| void ValveConfigurationAndControlCluster::Shutdown(ClusterShutdownType shutdownType) |
| { |
| // Call CancelTimer in the instance shutdown to avoid "use after free" scenarios. |
| DeviceLayer::SystemLayer().CancelTimer(HandleUpdateRemainingDuration, this); |
| DefaultServerCluster::Shutdown(shutdownType); |
| } |
| |
| CHIP_ERROR ValveConfigurationAndControlCluster::Attributes(const ConcreteClusterPath & path, |
| ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder) |
| { |
| AttributeListBuilder listBuilder(builder); |
| |
| const bool isDefaultOpenLevelSupported = |
| (mFeatures.Has(Feature::kLevel) && mOptionalAttributeSet.IsSet(Attributes::DefaultOpenLevel::Id)); |
| const bool isLevelStepSupported = (mFeatures.Has(Feature::kLevel) && mOptionalAttributeSet.IsSet(Attributes::LevelStep::Id)); |
| |
| AttributeListBuilder::OptionalAttributeEntry optionalAttributeEntries[] = { |
| { mFeatures.Has(Feature::kTimeSync), Attributes::AutoCloseTime::kMetadataEntry }, |
| { mFeatures.Has(Feature::kLevel), Attributes::CurrentLevel::kMetadataEntry }, |
| { mFeatures.Has(Feature::kLevel), Attributes::TargetLevel::kMetadataEntry }, |
| { isDefaultOpenLevelSupported, Attributes::DefaultOpenLevel::kMetadataEntry }, |
| { mOptionalAttributeSet.IsSet(Attributes::ValveFault::Id), Attributes::ValveFault::kMetadataEntry }, |
| { isLevelStepSupported, Attributes::LevelStep::kMetadataEntry } |
| }; |
| |
| return listBuilder.Append(Span(ValveConfigurationAndControl::Attributes::kMandatoryMetadata), Span(optionalAttributeEntries)); |
| } |
| |
| DataModel::ActionReturnStatus ValveConfigurationAndControlCluster::ReadAttribute(const DataModel::ReadAttributeRequest & request, |
| AttributeValueEncoder & encoder) |
| { |
| switch (request.path.mAttributeId) |
| { |
| case ValveConfigurationAndControl::Attributes::FeatureMap::Id: |
| return encoder.Encode(mFeatures); |
| case ValveConfigurationAndControl::Attributes::ClusterRevision::Id: |
| return encoder.Encode(ValveConfigurationAndControl::kRevision); |
| case ValveConfigurationAndControl::Attributes::OpenDuration::Id: |
| return encoder.Encode(mOpenDuration); |
| case ValveConfigurationAndControl::Attributes::DefaultOpenDuration::Id: |
| return encoder.Encode(mDefaultOpenDuration); |
| case ValveConfigurationAndControl::Attributes::AutoCloseTime::Id: |
| return encoder.Encode(mAutoCloseTime); |
| case ValveConfigurationAndControl::Attributes::RemainingDuration::Id: |
| return encoder.Encode(mRemainingDuration.value()); |
| case ValveConfigurationAndControl::Attributes::CurrentState::Id: |
| return encoder.Encode(mCurrentState); |
| case ValveConfigurationAndControl::Attributes::TargetState::Id: |
| return encoder.Encode(mTargetState); |
| case ValveConfigurationAndControl::Attributes::CurrentLevel::Id: |
| return encoder.Encode(mCurrentLevel); |
| case ValveConfigurationAndControl::Attributes::TargetLevel::Id: |
| return encoder.Encode(mTargetLevel); |
| case ValveConfigurationAndControl::Attributes::DefaultOpenLevel::Id: |
| return encoder.Encode(mDefaultOpenLevel); |
| case ValveConfigurationAndControl::Attributes::ValveFault::Id: |
| return encoder.Encode(mValveFault); |
| case ValveConfigurationAndControl::Attributes::LevelStep::Id: |
| return encoder.Encode(mLevelStep); |
| default: |
| return Status::UnsupportedAttribute; |
| } |
| } |
| |
| DataModel::ActionReturnStatus ValveConfigurationAndControlCluster::WriteAttribute(const DataModel::WriteAttributeRequest & request, |
| AttributeValueDecoder & decoder) |
| { |
| return NotifyAttributeChangedIfSuccess(request.path.mAttributeId, WriteImpl(request, decoder)); |
| } |
| |
| DataModel::ActionReturnStatus ValveConfigurationAndControlCluster::WriteImpl(const DataModel::WriteAttributeRequest & request, |
| AttributeValueDecoder & decoder) |
| { |
| |
| if (request.path.mAttributeId == ValveConfigurationAndControl::Attributes::DefaultOpenDuration::Id) |
| { |
| DataModel::Nullable<uint32_t> value; |
| ReturnErrorOnFailure(decoder.Decode(value)); |
| |
| // DefaultOpenDuration has a constraint: min value = 1 (when not null) |
| if (!value.IsNull() && value.Value() < 1) |
| { |
| return CHIP_IM_GLOBAL_STATUS(ConstraintError); |
| } |
| |
| // Avoid rewriting storage if unchanged |
| VerifyOrReturnValue(value != mDefaultOpenDuration, DataModel::ActionReturnStatus::FixedStatus::kWriteSuccessNoOp); |
| |
| mDefaultOpenDuration = value; |
| |
| // Persist using AttributePersistence nullable storage representation. |
| NumericAttributeTraits<uint32_t>::StorageType storageValue; |
| NullableToStorage(mDefaultOpenDuration, storageValue); |
| return mContext->attributeStorage.WriteValue(request.path, |
| { reinterpret_cast<const uint8_t *>(&storageValue), sizeof(storageValue) }); |
| } |
| |
| if (request.path.mAttributeId == ValveConfigurationAndControl::Attributes::DefaultOpenLevel::Id) |
| { |
| Percent defaultOpenLevel; |
| ReturnErrorOnFailure(decoder.Decode(defaultOpenLevel)); |
| VerifyOrReturnValue(defaultOpenLevel != mDefaultOpenLevel, DataModel::ActionReturnStatus::FixedStatus::kWriteSuccessNoOp); |
| // TODO(#40708): Currently the `DecodeAndStoreNativeEndianValue` function doesn't allow performing specific checks |
| // on provided values; update this logic once a fix for |
| // https://github.com/project-chip/connectedhomeip/issues/40708 is merged. |
| // In the current tests for this cluster this specific part is not validated. |
| // https://github.com/CHIP-Specifications/chip-test-plans/issues/5835 |
| VerifyOrReturnError(ValueCompliesWithLevelStep(defaultOpenLevel), CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| |
| mDefaultOpenLevel = defaultOpenLevel; |
| return mContext->attributeStorage.WriteValue( |
| request.path, { reinterpret_cast<const uint8_t *>(&mDefaultOpenLevel), sizeof(mDefaultOpenLevel) }); |
| } |
| |
| return Status::UnsupportedWrite; |
| } |
| |
| CHIP_ERROR ValveConfigurationAndControlCluster::AcceptedCommands(const ConcreteClusterPath & path, |
| ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder) |
| { |
| static constexpr DataModel::AcceptedCommandEntry kAcceptedCommands[] = { |
| ValveConfigurationAndControl::Commands::Open::kMetadataEntry, ValveConfigurationAndControl::Commands::Close::kMetadataEntry |
| }; |
| |
| return builder.ReferenceExisting(kAcceptedCommands); |
| } |
| |
| std::optional<DataModel::ActionReturnStatus> |
| ValveConfigurationAndControlCluster::InvokeCommand(const DataModel::InvokeRequest & request, TLV::TLVReader & input_arguments, |
| CommandHandler * handler) |
| { |
| switch (request.path.mCommandId) |
| { |
| case ValveConfigurationAndControl::Commands::Close::Id: |
| return HandleCloseCommand(); |
| case ValveConfigurationAndControl::Commands::Open::Id: |
| return HandleOpenCommand(request, input_arguments, handler); |
| default: |
| return Status::UnsupportedCommand; |
| } |
| } |
| |
| // Command Handlers |
| std::optional<DataModel::ActionReturnStatus> ValveConfigurationAndControlCluster::HandleCloseCommand() |
| { |
| // If there is a fault that prevents the cluster to perform the action, return FailureDueToFault. |
| // This logic keeps the previous implementation of this cluster, however it may be too strict since |
| // any fault will cause the command to fail. |
| if (mValveFault.HasAny()) |
| { |
| return Protocols::InteractionModel::ClusterStatusCode::ClusterSpecificFailure(StatusCodeEnum::kFailureDueToFault); |
| } |
| |
| return CloseValve(); |
| } |
| |
| CHIP_ERROR ValveConfigurationAndControlCluster::CloseValve() |
| { |
| // OpenDuration and RemainingDuration shall be set to null. |
| SetAttributeValue(mOpenDuration, DataModel::Nullable<uint32_t>{}, Attributes::OpenDuration::Id); |
| SetRemainingDuration(DataModel::NullNullable); |
| |
| // If TimeSync feature is supported, AutoCloseTime shall be set to null. |
| if (mFeatures.Has(Feature::kTimeSync)) |
| { |
| SetAttributeValue(mAutoCloseTime, DataModel::Nullable<uint64_t>{}, Attributes::AutoCloseTime::Id); |
| } |
| |
| // TargetState shall be set to Closed and CurrentState to Transitioning. |
| SetAttributeValue(mTargetState, DataModel::MakeNullable(ValveStateEnum::kClosed), Attributes::TargetState::Id); |
| SetCurrentState(ValveStateEnum::kTransitioning); |
| |
| // If Level feature is supported, TargetLevel shall be set to 0. |
| if (mFeatures.Has(Feature::kLevel)) |
| { |
| SetAttributeValue(mTargetLevel, DataModel::MakeNullable(Percent(0)), Attributes::TargetLevel::Id); |
| } |
| |
| // Cancel timer if running. |
| DeviceLayer::SystemLayer().CancelTimer(HandleUpdateRemainingDuration, this); |
| |
| VerifyOrReturnError(mDelegate != nullptr, CHIP_NO_ERROR); |
| return mDelegate->HandleCloseValve(); |
| } |
| |
| std::optional<DataModel::ActionReturnStatus> |
| ValveConfigurationAndControlCluster::HandleOpenCommand(const DataModel::InvokeRequest & request, TLV::TLVReader & input_arguments, |
| CommandHandler * handler) |
| { |
| Commands::Open::DecodableType commandData; |
| ReturnErrorOnFailure(commandData.Decode(input_arguments)); |
| |
| // Check the "min 1" constraint in the command fields. |
| // This mirrors the previous implementation for this cluster, however we need to validate properly the scenarios |
| // where the TargetLevel field is set while the LVL feature is not enabled. |
| // https://github.com/project-chip/connectedhomeip/issues/42777 |
| VerifyOrReturnValue((!commandData.openDuration.HasValue() ? true : commandData.openDuration.Value().ValueOr(1) > 0), |
| Status::ConstraintError); |
| VerifyOrReturnValue(commandData.targetLevel.ValueOr(1) > 0, Status::ConstraintError); |
| |
| // If there is a fault that prevents the cluster to perform the action, return FailureDueToFault. |
| // This logic keeps the previous implementation of this cluster, however it may be too strict since |
| // any fault will cause the command to fail. |
| if (mValveFault.HasAny()) |
| { |
| return Protocols::InteractionModel::ClusterStatusCode::ClusterSpecificFailure(StatusCodeEnum::kFailureDueToFault); |
| } |
| |
| // In the spec, the setting of the TargetState and CurrentState goes before the handling of the |
| // fields of the command (checking and setting default values), however this was deferred to the OpenValve function to keep |
| // backwards compatibility. Also this avoids setting the attributes if the targetLevel field doesn't have a valid value (in LVL |
| // enabled valves). |
| // Issue https://github.com/project-chip/connectedhomeip/issues/42777 created to follow up on the SDK changes. |
| // Issue https://github.com/CHIP-Specifications/connectedhomeip-spec/issues/12666 to follow up on the spec. |
| // Issue https://github.com/CHIP-Specifications/chip-test-plans/issues/5837 to follow up on the test changes. |
| |
| // Check the rules for the OpenDuration field of the command. |
| // This value will be used to set the OpenDuration attribute, initialize the RemainingDuration attribute and |
| // calculate the AutoCloseTime attribute (if supported) in the OpenValve function |
| DataModel::Nullable<uint32_t> openDuration; |
| if (commandData.openDuration.HasValue()) |
| { |
| // Save the duration if provided. |
| openDuration = commandData.openDuration.Value(); |
| } |
| else |
| { |
| // If no value is provided, use the DefaultOpenDuration attribute value. |
| openDuration = mDefaultOpenDuration; |
| } |
| |
| // Check rules for TargetLevel, if enabled |
| // After validation and if the value is correct, the TargetLevel and CurrentLevel attributes |
| // will be set in the OpenValve function. |
| DataModel::Nullable<Percent> openTargetLevel; |
| if (mFeatures.Has(Feature::kLevel)) |
| { |
| // The rules to get the TargetLevel from the command data |
| // If no value is provided |
| if (!commandData.targetLevel.HasValue()) |
| { |
| // Use the DefaultOpenLevel attribute if implemented. |
| if (mOptionalAttributeSet.IsSet(Attributes::DefaultOpenLevel::Id)) |
| { |
| openTargetLevel = mDefaultOpenLevel; |
| } |
| else |
| { |
| // If DefaultOpenLevel is not implemented, set it to 100 |
| openTargetLevel = kMaxLevelValuePercent; |
| } |
| } |
| else |
| { |
| // If TargetLevel is provided |
| // Validate that the TargetLevel and LevelStep are compatible. |
| VerifyOrReturnError(ValueCompliesWithLevelStep(commandData.targetLevel.Value()), |
| CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| openTargetLevel = commandData.targetLevel.Value(); |
| } |
| } |
| |
| // Use the OpenValve function to handle the setting of internal values. |
| return OpenValve(openTargetLevel, openDuration); |
| } |
| |
| CHIP_ERROR ValveConfigurationAndControlCluster::OpenValve(DataModel::Nullable<Percent> targetLevel, |
| DataModel::Nullable<uint32_t> openDuration) |
| { |
| // Check for the AutoCloseTime feature and set it to the UTC time plus OpenDuration. |
| if (mFeatures.Has(Feature::kTimeSync)) |
| { |
| ReturnErrorOnFailure(SetAutoCloseTime(openDuration)); |
| } |
| |
| // Set TargetState to Open and CurrentState to Transitioning |
| SetAttributeValue(mTargetState, DataModel::MakeNullable(ValveStateEnum::kOpen), Attributes::TargetState::Id); |
| SetCurrentState(ValveStateEnum::kTransitioning); |
| |
| // Set OpenDuration to the provided value (can be null). |
| SetAttributeValue(mOpenDuration, openDuration, Attributes::OpenDuration::Id); |
| |
| // Set the RemainingDuration to the value of OpenDuration (either a value or null) |
| SetRemainingDuration(mOpenDuration); |
| |
| // Set target level |
| if (mFeatures.Has(Feature::kLevel)) |
| { |
| // If the LVL feature is enabled this function should actually have a TargetLevel |
| VerifyOrDie(!targetLevel.IsNull()); |
| SetAttributeValue(mTargetLevel, targetLevel, Attributes::TargetLevel::Id); |
| } |
| |
| if (mDelegate != nullptr) |
| { |
| DataModel::Nullable<Percent> cLevel = mDelegate->HandleOpenValve(targetLevel); |
| if (mFeatures.Has(Feature::kLevel) && !cLevel.IsNull()) |
| { |
| // Update CurrentLevel to the one returned from the Delegate |
| UpdateCurrentLevel(cLevel.Value()); |
| } |
| } |
| |
| // Start countdown if applicable (e.g. OpenDuration is not null). |
| HandleUpdateRemainingDurationInternal(); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| // Internal functions. |
| CHIP_ERROR ValveConfigurationAndControlCluster::SetAutoCloseTime(DataModel::Nullable<uint32_t> openDuration) |
| { |
| VerifyOrReturnValue(mTsTracker != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| if (!openDuration.IsNull() && mTsTracker->IsValidUTCTime()) |
| { |
| // We have a synchronized UTC time in the TimeSync cluster, we can proceed to set the AutoCloseTime attribute |
| System::Clock::Microseconds64 utcTime; |
| uint64_t chipEpochTime; |
| ReturnErrorOnFailure(System::SystemClock().GetClock_RealTime(utcTime)); |
| VerifyOrReturnError(UnixEpochToChipEpochMicros(utcTime.count(), chipEpochTime), CHIP_ERROR_INVALID_TIME); |
| |
| // Cast to 64-bit before multiplying: the uint32_t * int product otherwise overflows for durations > ~4294 s. |
| uint64_t time = static_cast<uint64_t>(openDuration.Value()) * kMicrosecondsPerSecond; |
| SetAttributeValue(mAutoCloseTime, DataModel::MakeNullable(time + chipEpochTime), Attributes::AutoCloseTime::Id); |
| } |
| else |
| { |
| // No synchronized time or OpenDuration is null, setting the AutoCloseTime attribute to null |
| SetAttributeValue(mAutoCloseTime, DataModel::Nullable<uint64_t>{}, Attributes::AutoCloseTime::Id); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| void ValveConfigurationAndControlCluster::UpdateAutoCloseTime(uint64_t epochTime) |
| { |
| if (!mRemainingDuration.value().IsNull() && mRemainingDuration.value().Value() != 0) |
| { |
| // Cast to 64-bit before multiplying: the uint32_t * int product otherwise overflows for durations > ~4294 s. |
| uint64_t closingTime = static_cast<uint64_t>(mRemainingDuration.value().Value()) * kMicrosecondsPerSecond + epochTime; |
| SetAttributeValue(mAutoCloseTime, DataModel::MakeNullable(closingTime), Attributes::AutoCloseTime::Id); |
| } |
| } |
| void ValveConfigurationAndControlCluster::HandleUpdateRemainingDuration(System::Layer * systemLayer, void * context) |
| { |
| auto * logic = static_cast<ValveConfigurationAndControlCluster *>(context); |
| logic->HandleUpdateRemainingDurationInternal(); |
| } |
| |
| void ValveConfigurationAndControlCluster::HandleUpdateRemainingDurationInternal() |
| { |
| // Start by cancelling the timer in case this was called from a command handler |
| // We will start a new timer if required. |
| DeviceLayer::SystemLayer().CancelTimer(HandleUpdateRemainingDuration, this); |
| |
| if (mOpenDuration.IsNull() || mRemainingDuration.value().IsNull()) |
| { |
| // If openDuration is null, RemainingDuration should also be null and this timer shouldn't be running. |
| SetRemainingDuration(DataModel::NullNullable); |
| return; |
| } |
| |
| if (mDelegate != nullptr) |
| { |
| mDelegate->HandleRemainingDurationTick(mRemainingDuration.value().Value()); |
| } |
| |
| // Check the value of RemainingDuration, when reaches 0 the valve shall be closed. |
| if (mRemainingDuration.value().Value() > 0) |
| { |
| LogErrorOnFailure(DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds16(1), HandleUpdateRemainingDuration, this)); |
| SetRemainingDuration(mRemainingDuration.value().Value() - 1); |
| } |
| else |
| { |
| LogErrorOnFailure(CloseValve()); |
| } |
| } |
| |
| void ValveConfigurationAndControlCluster::SetRemainingDuration(const DataModel::Nullable<ElapsedS> & remainingDuration) |
| { |
| System::Clock::Milliseconds64 now = System::SystemClock().GetMonotonicMilliseconds64(); |
| VerifyOrReturn(mRemainingDuration.value() != remainingDuration); |
| |
| auto changeLevel = mRemainingDuration.SetValue(remainingDuration, now); |
| NotifyAttributeChanged(Attributes::RemainingDuration::Id, |
| changeLevel == AttributeDirtyState::kMustReport ? DataModel::AttributeChangeType::kReportable |
| : DataModel::AttributeChangeType::kQuiet); |
| } |
| |
| // Function to handle the StateChange that also allows to generate an event if needed. |
| void ValveConfigurationAndControlCluster::SetCurrentState(const ValveStateEnum & newState) |
| { |
| VerifyOrReturn(mCurrentState != newState); |
| mCurrentState = newState; |
| NotifyAttributeChanged(Attributes::CurrentState::Id); |
| |
| EmitValveChangeEvent(newState); |
| } |
| |
| // According to the spec, when using a TargetLevel while the attribute LevelStep is set |
| // the value of TargetLevel should be 0, 100 or a multiple of LevelStep. |
| bool ValveConfigurationAndControlCluster::ValueCompliesWithLevelStep(const uint8_t value) const |
| { |
| if (!mOptionalAttributeSet.IsSet(Attributes::LevelStep::Id)) |
| { |
| return true; |
| } |
| return (value == kMaxLevelValuePercent) || (value % mLevelStep) == 0; |
| } |
| |
| void ValveConfigurationAndControlCluster::SetDelegate(Delegate * delegate) |
| { |
| mDelegate = delegate; |
| } |
| |
| void ValveConfigurationAndControlCluster::UpdateCurrentState(const ValveConfigurationAndControl::ValveStateEnum currentState) |
| { |
| SetCurrentState(currentState); |
| |
| if (mTargetState == currentState) |
| { |
| SetAttributeValue(mTargetState, DataModel::Nullable<ValveStateEnum>{}, Attributes::TargetState::Id); |
| } |
| } |
| |
| void ValveConfigurationAndControlCluster::UpdateCurrentLevel(Percent currentLevel) |
| { |
| VerifyOrReturn(mFeatures.Has(Feature::kLevel)); |
| SetAttributeValue(mCurrentLevel, DataModel::MakeNullable(currentLevel), Attributes::CurrentLevel::Id); |
| |
| if (mCurrentLevel == mTargetLevel) |
| { |
| SetAttributeValue(mTargetLevel, DataModel::Nullable<Percent>{}, Attributes::TargetLevel::Id); |
| UpdateCurrentState(currentLevel == 0 ? ValveStateEnum::kClosed : ValveStateEnum::kOpen); |
| } |
| } |
| |
| // Name taken from previous cluster implementation, need to change to a more clear one and review the rest |
| // to ensure that they are also fitting (https://github.com/project-chip/connectedhomeip/issues/42777) |
| void ValveConfigurationAndControlCluster::EmitValveChangeEvent(ValveConfigurationAndControl::ValveStateEnum newState) |
| { |
| ValveConfigurationAndControl::Events::ValveStateChanged::Type event; |
| event.valveState = newState; |
| |
| // Check if Level feature and add current level if enabled |
| if (mFeatures.Has(Feature::kLevel) && !mCurrentLevel.IsNull()) |
| { |
| event.valveLevel = MakeOptional<Percent>(mCurrentLevel.Value()); |
| } |
| |
| mContext->interactionContext.eventsGenerator.GenerateEvent(event, mPath.mEndpointId); |
| } |
| |
| void ValveConfigurationAndControlCluster::SetValveFault(BitMask<ValveConfigurationAndControl::ValveFaultBitmap> fault) |
| { |
| ValveConfigurationAndControl::Events::ValveFault::Type event; |
| event.valveFault = fault; |
| mContext->interactionContext.eventsGenerator.GenerateEvent(event, mPath.mEndpointId); |
| mValveFault = fault; |
| } |