| /** |
| * |
| * Copyright (c) 2023 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. |
| */ |
| /** |
| * @file Cross-platform API to handle cluster-specific logic for the closure dimension cluster on a single endpoint. |
| */ |
| |
| #include "closure-dimension-cluster-logic.h" |
| #include <clusters/ClosureDimension/Metadata.h> |
| #include <platform/LockTracker.h> |
| |
| namespace chip { |
| namespace app { |
| namespace Clusters { |
| namespace ClosureDimension { |
| |
| using namespace Protocols::InteractionModel; |
| using namespace chip::app::Clusters::ClosureDimension::Attributes; |
| |
| namespace { |
| |
| constexpr Percent100ths kPercents100thsMaxValue = 10000; |
| constexpr uint64_t kPositionQuietReportingInterval = 5000; |
| |
| } // namespace |
| |
| CHIP_ERROR ClusterLogic::Init(const ClusterConformance & conformance, const ClusterInitParameters & clusterInitParameters) |
| { |
| VerifyOrReturnError(!mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError(conformance.Valid(), CHIP_ERROR_INVALID_DEVICE_DESCRIPTOR); |
| mConformance = conformance; |
| |
| // Need to set TranslationDirection, RotationAxis, ModulationType before Initilization of closure, as they should not be changed |
| // after Initalization. |
| if (conformance.HasFeature(Feature::kTranslation)) |
| { |
| ReturnErrorOnFailure(SetTranslationDirection(clusterInitParameters.translationDirection)); |
| } |
| |
| if (conformance.HasFeature(Feature::kRotation)) |
| { |
| ReturnErrorOnFailure(SetRotationAxis(clusterInitParameters.rotationAxis)); |
| } |
| |
| if (conformance.HasFeature(Feature::kModulation)) |
| { |
| ReturnErrorOnFailure(SetModulationType(clusterInitParameters.modulationType)); |
| } |
| |
| mInitialized = true; |
| return CHIP_NO_ERROR; |
| } |
| |
| // Specification rules for CurrentState quiet reporting: |
| // Changes to this attribute SHALL only be marked as reportable in the following cases: |
| // When the Position changes from null to any other value and vice versa, or |
| // At most once every 5 seconds when the Position changes from one non-null value to another non-null value, or |
| // When Target.Position is reached, or |
| // When CurrentState.Speed changes, or |
| // When CurrentState.Latch changes. |
| |
| // At present, QuieterReportingAttribute class does not support Structs. |
| // so each field of current state struct has to be handled independently. |
| // At present, we are using QuieterReportingAttribute class for Position only. |
| // Latch and Speed changes are directly handled by the cluster logic seperately. |
| // i.e Speed and latch changes are not considered when calucalting the at most 5 seconds quiet reportable changes for Position. |
| CHIP_ERROR ClusterLogic::SetCurrentState(const DataModel::Nullable<GenericDimensionStateStruct> & incomingCurrentState) |
| { |
| assertChipStackLockedByCurrentThread(); |
| |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError(mState.currentState != incomingCurrentState, CHIP_NO_ERROR); |
| |
| bool markDirty = false; |
| |
| if (!incomingCurrentState.IsNull()) |
| { |
| // Validate the incoming Position value has valid input parameters and FeatureMap conformance. |
| if (incomingCurrentState.Value().position.HasValue()) |
| { |
| // If the position member is present in the incoming CurrentState, we need to check if the Positioning |
| // feature is supported by the closure. If the Positioning feature is not supported, return an error. |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kPositioning), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| |
| if (!incomingCurrentState.Value().position.Value().IsNull()) |
| { |
| |
| VerifyOrReturnError(incomingCurrentState.Value().position.Value().Value() <= kPercents100thsMaxValue, |
| CHIP_ERROR_INVALID_ARGUMENT); |
| } |
| |
| bool targetPositionReached = false; |
| auto now = System::SystemClock().GetMonotonicTimestamp(); |
| |
| // Logic to determine if target position is reached. |
| // If the target position is reached, current state attribute will be marked dirty and reported. |
| if (!mState.targetState.IsNull() && mState.targetState.Value().position.HasValue() && |
| !mState.targetState.Value().position.Value().IsNull() && |
| mState.targetState.Value().position == incomingCurrentState.Value().position) |
| { |
| targetPositionReached = true; |
| } |
| |
| if (targetPositionReached) |
| { |
| auto predicate = |
| [](const decltype(quietReportableCurrentStatePosition)::SufficientChangePredicateCandidate &) -> bool { |
| return true; |
| }; |
| markDirty |= (quietReportableCurrentStatePosition.SetValue(incomingCurrentState.Value().position.Value(), now, |
| predicate) == AttributeDirtyState::kMustReport); |
| } |
| else |
| { |
| // Predicate to report at most once every 5 seconds when the Position changes from one non-null value to another |
| // non-null value, or when the Position changes from null to any other value and vice versa |
| System::Clock::Milliseconds64 reportInterval = System::Clock::Milliseconds64(kPositionQuietReportingInterval); |
| auto predicate = quietReportableCurrentStatePosition.GetPredicateForSufficientTimeSinceLastDirty(reportInterval); |
| markDirty |= (quietReportableCurrentStatePosition.SetValue(incomingCurrentState.Value().position.Value(), now, |
| predicate) == AttributeDirtyState::kMustReport); |
| } |
| } |
| |
| // Validate the incoming latch value has valid FeatureMap conformance. |
| if (incomingCurrentState.Value().latch.HasValue()) |
| { |
| // If the latching member is present in the incoming CurrentState, we need to check if the MotionLatching |
| // feature is supported by the closure. If the MotionLatching feature is not supported, return an error. |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kMotionLatching), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| } |
| |
| // Changes to this attribute SHALL only be marked as reportable when latch changes. |
| if (!mState.currentState.IsNull() && mState.currentState.Value().latch != incomingCurrentState.Value().latch) |
| { |
| markDirty = true; |
| } |
| |
| // Validate the incoming Speed value has valid input parameters and FeatureMap conformance. |
| if (incomingCurrentState.Value().speed.HasValue()) |
| { |
| // If the speed member is present in the incoming CurrentState, we need to check if the Speed feature is |
| // supported by the closure. If the Speed feature is not supported, return an error. |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kSpeed), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| |
| VerifyOrReturnError(EnsureKnownEnumValue(incomingCurrentState.Value().speed.Value()) != |
| Globals::ThreeLevelAutoEnum::kUnknownEnumValue, |
| CHIP_ERROR_INVALID_ARGUMENT); |
| } |
| |
| // Changes to this attribute SHALL be marked as reportable when speed changes. |
| if (!mState.currentState.IsNull() && mState.currentState.Value().speed != incomingCurrentState.Value().speed) |
| { |
| markDirty = true; |
| } |
| } |
| |
| // If the current state is null and the incoming current state is not null and vice versa, we need to mark dirty. |
| if (mState.currentState.IsNull() != incomingCurrentState.IsNull()) |
| { |
| markDirty = true; |
| } |
| |
| mState.currentState = incomingCurrentState; |
| |
| if (markDirty) |
| { |
| mMatterContext.MarkDirty(Attributes::CurrentState::Id); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::SetTargetState(const DataModel::Nullable<GenericDimensionStateStruct> & incomingTargetState) |
| { |
| assertChipStackLockedByCurrentThread(); |
| |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError(mState.targetState != incomingTargetState, CHIP_NO_ERROR); |
| |
| if (!incomingTargetState.IsNull()) |
| { |
| // Validate the incoming Position value has valid input parameters and FeatureMap conformance. |
| if (incomingTargetState.Value().position.HasValue()) |
| { |
| // If the position member is present in the incoming TargetState, we need to check if the Positioning |
| // feature is supported by the closure. If the Positioning feature is not supported, return an error. |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kPositioning), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| |
| if (!incomingTargetState.Value().position.Value().IsNull()) |
| { |
| VerifyOrReturnError(incomingTargetState.Value().position.Value().Value() <= kPercents100thsMaxValue, |
| CHIP_ERROR_INVALID_ARGUMENT); |
| |
| // Incoming TargetState Position value SHALL follow the scaling from Resolution Attribute. |
| Percent100ths resolution; |
| ReturnErrorOnFailure(GetResolution(resolution)); |
| VerifyOrReturnError( |
| incomingTargetState.Value().position.Value().Value() % resolution == 0, CHIP_ERROR_INVALID_ARGUMENT, |
| ChipLogError(NotSpecified, "TargetState Position value SHALL follow the scaling from Resolution Attribute")); |
| } |
| } |
| |
| // Validate the incoming latch value has valid FeatureMap conformance. |
| if (incomingTargetState.Value().latch.HasValue()) |
| { |
| // If the latching member is present in the incoming TargetState, we need to check if the MotionLatching |
| // feature is supported by the closure. If the MotionLatching feature is not supported, return an error. |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kMotionLatching), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| } |
| |
| // Validate the incoming Speed value has valid input parameters and FeatureMap conformance. |
| if (incomingTargetState.Value().speed.HasValue()) |
| { |
| // If the speed member is present in the incoming TargetState, we need to check if the Speed feature is |
| // supported by the closure. If the Speed feature is not supported, return an error. |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kSpeed), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| |
| VerifyOrReturnError(EnsureKnownEnumValue(incomingTargetState.Value().speed.Value()) != |
| Globals::ThreeLevelAutoEnum::kUnknownEnumValue, |
| CHIP_ERROR_INVALID_ARGUMENT); |
| } |
| } |
| |
| mState.targetState = incomingTargetState; |
| mMatterContext.MarkDirty(Attributes::TargetState::Id); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::SetResolution(const Percent100ths resolution) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kPositioning), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| VerifyOrReturnError(0 < resolution && resolution <= kPercents100thsMaxValue, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| if (resolution != mState.resolution) |
| { |
| mState.resolution = resolution; |
| mMatterContext.MarkDirty(Attributes::Resolution::Id); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::SetStepValue(const Percent100ths stepValue) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kPositioning), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| VerifyOrReturnError(stepValue <= kPercents100thsMaxValue, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| // StepValue SHALL be equal to an integer multiple of the Resolution attribute , if not return Invalid Argument. |
| Percent100ths resolution; |
| ReturnErrorOnFailure(GetResolution(resolution)); |
| VerifyOrReturnError(stepValue % resolution == 0, CHIP_ERROR_INVALID_ARGUMENT, |
| ChipLogError(NotSpecified, "StepValue SHALL be equal to an integer multiple of the Resolution attribute")); |
| |
| if (stepValue != mState.stepValue) |
| { |
| mState.stepValue = stepValue; |
| mMatterContext.MarkDirty(Attributes::StepValue::Id); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::SetUnit(const ClosureUnitEnum unit) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kUnit), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| VerifyOrReturnError(EnsureKnownEnumValue(unit) != ClosureUnitEnum::kUnknownEnumValue, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| if (unit != mState.unit) |
| { |
| mState.unit = unit; |
| mMatterContext.MarkDirty(Attributes::Unit::Id); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::SetUnitRange(const DataModel::Nullable<Structs::UnitRangeStruct::Type> & unitRange) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kUnit), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| |
| if (unitRange.IsNull()) |
| { |
| // Mark UnitRange attribute as dirty only if value changes. |
| if (!mState.unitRange.IsNull()) |
| { |
| mState.unitRange.SetNull(); |
| mMatterContext.MarkDirty(Attributes::UnitRange::Id); |
| } |
| return CHIP_NO_ERROR; |
| } |
| |
| // Return error if unitRange is invalid |
| VerifyOrReturnError(unitRange.Value().min <= unitRange.Value().max, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| ClosureUnitEnum unit; |
| ReturnErrorOnFailure(GetUnit(unit)); |
| |
| // If Unit is Millimeter , Range values SHALL contain unsigned values from 0 to 32767 only |
| if (unit == ClosureUnitEnum::kMillimeter) |
| { |
| VerifyOrReturnError(unitRange.Value().min >= 0 && unitRange.Value().min <= 32767, CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError(unitRange.Value().max >= 0 && unitRange.Value().max <= 32767, CHIP_ERROR_INVALID_ARGUMENT); |
| } |
| |
| // If Unit is Degrees the maximum span range is 360 degrees. |
| if (unit == ClosureUnitEnum::kDegree) |
| { |
| VerifyOrReturnError(unitRange.Value().min >= -360 && unitRange.Value().min <= 360, CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError(unitRange.Value().max >= -360 && unitRange.Value().max <= 360, CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError((unitRange.Value().max - unitRange.Value().min) <= 360, CHIP_ERROR_INVALID_ARGUMENT); |
| } |
| |
| // If the mState unitRange is null, we need to set it to the new value |
| if (mState.unitRange.IsNull()) |
| { |
| mState.unitRange.SetNonNull(unitRange.Value()); |
| mMatterContext.MarkDirty(Attributes::UnitRange::Id); |
| return CHIP_NO_ERROR; |
| } |
| |
| // If both the mState unitRange and unitRange are not null, we need to update mState unitRange if the values are different |
| if ((unitRange.Value().min != mState.unitRange.Value().min) || (unitRange.Value().max != mState.unitRange.Value().max)) |
| { |
| mState.unitRange.Value().min = unitRange.Value().min; |
| mState.unitRange.Value().max = unitRange.Value().max; |
| mMatterContext.MarkDirty(Attributes::UnitRange::Id); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::SetLimitRange(const Structs::RangePercent100thsStruct::Type & limitRange) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kLimitation), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| |
| // If the limit range is invalid, we need to return an error |
| VerifyOrReturnError(limitRange.min <= limitRange.max, CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError(limitRange.min <= kPercents100thsMaxValue, CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError(limitRange.max <= kPercents100thsMaxValue, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| // LimitRange.Min and LimitRange.Max SHALL be equal to an integer multiple of the Resolution attribute. |
| Percent100ths resolution; |
| ReturnErrorOnFailure(GetResolution(resolution)); |
| VerifyOrReturnError( |
| limitRange.min % resolution == 0, CHIP_ERROR_INVALID_ARGUMENT, |
| ChipLogError(NotSpecified, "LimitRange.Min SHALL be equal to an integer multiple of the Resolution attribute.")); |
| VerifyOrReturnError( |
| limitRange.max % resolution == 0, CHIP_ERROR_INVALID_ARGUMENT, |
| ChipLogError(NotSpecified, "LimitRange.Max SHALL be equal to an integer multiple of the Resolution attribute.")); |
| |
| if ((limitRange.min != mState.limitRange.min) || (limitRange.max != mState.limitRange.max)) |
| { |
| mState.limitRange.min = limitRange.min; |
| mState.limitRange.max = limitRange.max; |
| mMatterContext.MarkDirty(Attributes::LimitRange::Id); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::SetTranslationDirection(const TranslationDirectionEnum translationDirection) |
| { |
| // This attribute is not supposed to change once the initialization is completed. |
| VerifyOrReturnError(!mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kTranslation), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| VerifyOrReturnError(EnsureKnownEnumValue(translationDirection) != TranslationDirectionEnum::kUnknownEnumValue, |
| CHIP_ERROR_INVALID_ARGUMENT); |
| |
| if (translationDirection != mState.translationDirection) |
| { |
| mState.translationDirection = translationDirection; |
| mMatterContext.MarkDirty(Attributes::TranslationDirection::Id); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::SetRotationAxis(const RotationAxisEnum rotationAxis) |
| { |
| // This attribute is not supposed to change once the initialization is completed |
| VerifyOrReturnError(!mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kRotation), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| VerifyOrReturnError(EnsureKnownEnumValue(rotationAxis) != RotationAxisEnum::kUnknownEnumValue, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| if (rotationAxis != mState.rotationAxis) |
| { |
| mState.rotationAxis = rotationAxis; |
| mMatterContext.MarkDirty(Attributes::RotationAxis::Id); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::SetOverflow(const OverflowEnum overflow) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kRotation), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| VerifyOrReturnError(EnsureKnownEnumValue(overflow) != OverflowEnum::kUnknownEnumValue, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| RotationAxisEnum rotationAxis; |
| ReturnErrorOnFailure(GetRotationAxis(rotationAxis)); |
| |
| // If the axis is centered, one part goes Outside and the other part goes Inside. |
| // In this case, this attribute SHALL use Top/Bottom/Left/Right Inside or Top/Bottom/Left/Right Outside enumerated value. |
| if (rotationAxis == RotationAxisEnum::kCenteredHorizontal || rotationAxis == RotationAxisEnum::kCenteredVertical) |
| { |
| VerifyOrReturnError(overflow != OverflowEnum::kNoOverflow && overflow != OverflowEnum::kInside && |
| overflow != OverflowEnum::kOutside, |
| CHIP_ERROR_INVALID_ARGUMENT); |
| } |
| |
| if (overflow != mState.overflow) |
| { |
| mState.overflow = overflow; |
| mMatterContext.MarkDirty(Attributes::Overflow::Id); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::SetModulationType(const ModulationTypeEnum modulationType) |
| { |
| // This attribute is not supposed to change once the initialization is completed |
| VerifyOrReturnError(!mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kModulation), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| VerifyOrReturnError(EnsureKnownEnumValue(modulationType) != ModulationTypeEnum::kUnknownEnumValue, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| if (modulationType != mState.modulationType) |
| { |
| mState.modulationType = modulationType; |
| mMatterContext.MarkDirty(Attributes::ModulationType::Id); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::SetLatchControlModes(const BitFlags<LatchControlModesBitmap> & latchControlModes) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kMotionLatching), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| |
| if (mState.latchControlModes != latchControlModes) |
| { |
| mState.latchControlModes = latchControlModes; |
| mMatterContext.MarkDirty(Attributes::LatchControlModes::Id); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::GetCurrentState(DataModel::Nullable<GenericDimensionStateStruct> & currentState) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| currentState = mState.currentState; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::GetTargetState(DataModel::Nullable<GenericDimensionStateStruct> & targetState) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| targetState = mState.targetState; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::GetResolution(Percent100ths & resolution) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kPositioning), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| resolution = mState.resolution; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::GetStepValue(Percent100ths & stepValue) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kPositioning), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| stepValue = mState.stepValue; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::GetUnit(ClosureUnitEnum & unit) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kUnit), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| unit = mState.unit; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::GetUnitRange(DataModel::Nullable<Structs::UnitRangeStruct::Type> & unitRange) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kUnit), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| unitRange = mState.unitRange; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::GetLimitRange(Structs::RangePercent100thsStruct::Type & limitRange) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kLimitation), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| limitRange = mState.limitRange; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::GetTranslationDirection(TranslationDirectionEnum & translationDirection) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kTranslation), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| translationDirection = mState.translationDirection; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::GetRotationAxis(RotationAxisEnum & rotationAxis) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kRotation), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| rotationAxis = mState.rotationAxis; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::GetOverflow(OverflowEnum & overflow) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError((mConformance.HasFeature(Feature::kRotation)), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| overflow = mState.overflow; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::GetModulationType(ModulationTypeEnum & modulationType) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kModulation), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| modulationType = mState.modulationType; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::GetLatchControlModes(BitFlags<LatchControlModesBitmap> & latchControlModes) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kMotionLatching), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| latchControlModes = mState.latchControlModes; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::GetFeatureMap(BitFlags<Feature> & featureMap) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| featureMap = mConformance.FeatureMap(); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ClusterLogic::GetClusterRevision(Attributes::ClusterRevision::TypeInfo::Type & clusterRevision) |
| { |
| VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); |
| clusterRevision = ClosureDimension::kRevision; |
| return CHIP_NO_ERROR; |
| } |
| |
| Status ClusterLogic::HandleSetTargetCommand(Optional<Percent100ths> position, Optional<bool> latch, |
| Optional<Globals::ThreeLevelAutoEnum> speed) |
| { |
| VerifyOrDieWithMsg(mInitialized, AppServer, "Unexpected command received when closure is yet to be initialized"); |
| |
| // If all command parameters don't have a value, return InvalidCommand |
| VerifyOrReturnError(position.HasValue() || latch.HasValue() || speed.HasValue(), Status::InvalidCommand); |
| |
| // TODO: If this command is sent while the closure is in a non-compatible internal-state, a status code of |
| // INVALID_IN_STATE SHALL be returned. |
| |
| DataModel::Nullable<GenericDimensionStateStruct> targetState; |
| VerifyOrReturnError(GetTargetState(targetState) == CHIP_NO_ERROR, Status::Failure); |
| |
| // If targetState is null, we need to initialize to default value. |
| // This is to ensure that we can set the position, latch, and speed values in the targetState. |
| if (targetState.IsNull()) |
| { |
| targetState.SetNonNull(GenericDimensionStateStruct{}); |
| } |
| |
| // If position field is present and Positioning(PS) feature is not supported, we should not set targetState.position value. |
| if (position.HasValue() && mConformance.HasFeature(Feature::kPositioning)) |
| { |
| VerifyOrReturnError((position.Value() <= kPercents100thsMaxValue), Status::ConstraintError); |
| |
| // If the Limitation Feature is active, the closure will automatically offset the TargetState.Position value to fit within |
| // LimitRange.Min and LimitRange.Max. |
| if (mConformance.HasFeature(Feature::kLimitation)) |
| { |
| Structs::RangePercent100thsStruct::Type limitRange; |
| |
| VerifyOrReturnError(GetLimitRange(limitRange) == CHIP_NO_ERROR, Status::Failure); |
| |
| if (position.Value() > limitRange.max) |
| { |
| position.Value() = limitRange.max; |
| } |
| |
| else if (position.Value() < limitRange.min) |
| { |
| position.Value() = limitRange.min; |
| } |
| } |
| |
| Percent100ths resolution; |
| VerifyOrReturnError(GetResolution(resolution) == CHIP_NO_ERROR, Status::Failure, |
| ChipLogError(AppServer, "Unable to get Resolution value while handling SetTarget command")); |
| // Check if position.Value() is an integer multiple of resolution, else round to nearest valid value |
| if (position.Value() % resolution != 0) |
| { |
| Percent100ths roundedPosition = |
| static_cast<Percent100ths>(((position.Value() + resolution / 2) / resolution) * resolution); |
| ChipLogProgress(AppServer, "Rounding position from %u to nearest valid value %u based on resolution %u", |
| position.Value(), roundedPosition, resolution); |
| position.SetValue(roundedPosition); |
| } |
| targetState.Value().position.SetValue(DataModel::MakeNullable(position.Value())); |
| } |
| |
| // If latch field is present and MotionLatching feature is not supported, we should not set targetState.latch value. |
| if (latch.HasValue() && mConformance.HasFeature(Feature::kMotionLatching)) |
| { |
| // If latch value is true and the Remote Latching feature is not supported, or |
| // if latch value is false and the Remote Unlatching feature is not supported, return InvalidInState. |
| if ((latch.Value() && !mState.latchControlModes.Has(LatchControlModesBitmap::kRemoteLatching)) || |
| (!latch.Value() && !mState.latchControlModes.Has(LatchControlModesBitmap::kRemoteUnlatching))) |
| { |
| return Status::InvalidInState; |
| } |
| |
| targetState.Value().latch.SetValue(DataModel::MakeNullable(latch.Value())); |
| } |
| |
| // If speed field is present and Speed feature is not supported, we should not set targetState.speed value. |
| if (speed.HasValue() && mConformance.HasFeature(Feature::kSpeed)) |
| { |
| VerifyOrReturnError(speed.Value() != Globals::ThreeLevelAutoEnum::kUnknownEnumValue, Status::ConstraintError); |
| targetState.Value().speed.SetValue(speed.Value()); |
| } |
| |
| // Check if the current position is valid or else return InvalidInState |
| DataModel::Nullable<GenericDimensionStateStruct> currentState; |
| VerifyOrReturnError(GetCurrentState(currentState) == CHIP_NO_ERROR, Status::Failure); |
| VerifyOrReturnError(!currentState.IsNull(), Status::InvalidInState); |
| if (mConformance.HasFeature(Feature::kPositioning)) |
| { |
| VerifyOrReturnError(currentState.Value().position.HasValue() && !currentState.Value().position.Value().IsNull(), |
| Status::InvalidInState); |
| } |
| |
| // If this command requests a position change while the Latch field of the CurrentState is True (Latched), and the Latch field |
| // of this command is not set to False (Unlatched), a status code of INVALID_IN_STATE SHALL be returned. |
| if (mConformance.HasFeature(Feature::kMotionLatching)) |
| { |
| if (position.HasValue() && currentState.Value().latch.HasValue() && !currentState.Value().latch.Value().IsNull() && |
| currentState.Value().latch.Value().Value()) |
| { |
| VerifyOrReturnError(latch.HasValue() && !latch.Value(), Status::InvalidInState, |
| ChipLogError(AppServer, |
| "Latch is True in State, but SetTarget command does not set latch to False" |
| "when position change is requested on endpoint : %d", |
| mMatterContext.GetEndpointId())); |
| } |
| } |
| |
| // Target should only be set when delegate function returns status as Success. Return failure otherwise |
| VerifyOrReturnError(mDelegate.HandleSetTarget(position, latch, speed) == Status::Success, Status::Failure); |
| |
| VerifyOrReturnError(SetTargetState(targetState) == CHIP_NO_ERROR, Status::Failure); |
| |
| return Status::Success; |
| } |
| |
| Status ClusterLogic::HandleStepCommand(StepDirectionEnum direction, uint16_t numberOfSteps, |
| Optional<Globals::ThreeLevelAutoEnum> speed) |
| { |
| VerifyOrDieWithMsg(mInitialized, NotSpecified, "Unexpected command recieved when closure is yet to be initialized"); |
| |
| // Return UnsupportedCommand if Positioning feature is not supported. |
| VerifyOrReturnError(mConformance.HasFeature(Feature::kPositioning), Status::UnsupportedCommand); |
| |
| // Return ConstraintError if command parameters are out of bounds |
| VerifyOrReturnError(direction != StepDirectionEnum::kUnknownEnumValue, Status::ConstraintError); |
| VerifyOrReturnError(numberOfSteps > 0, Status::ConstraintError); |
| |
| DataModel::Nullable<GenericDimensionStateStruct> stepTarget; |
| VerifyOrReturnError(GetTargetState(stepTarget) == CHIP_NO_ERROR, Status::Failure); |
| |
| if (stepTarget.IsNull()) |
| { |
| // If stepTarget is null, we need to initialize to default value. |
| // This is to ensure that we can set the position, latch, and speed values in the stepTarget. |
| stepTarget.SetNonNull(GenericDimensionStateStruct{}); |
| } |
| |
| // If speed field is present and Speed feature is not supported, we should not set stepTarget.speed value. |
| if (speed.HasValue() && mConformance.HasFeature(Feature::kSpeed)) |
| { |
| VerifyOrReturnError(speed.Value() != Globals::ThreeLevelAutoEnum::kUnknownEnumValue, Status::ConstraintError); |
| stepTarget.Value().speed.SetValue(speed.Value()); |
| } |
| |
| // TODO: If the server is in a state where it cannot support the command, the server SHALL respond with an |
| // INVALID_IN_STATE response and the TargetState attribute value SHALL remain unchanged. |
| |
| // Check if the current position is valid or else return InvalidInState |
| DataModel::Nullable<GenericDimensionStateStruct> currentState; |
| VerifyOrReturnError(GetCurrentState(currentState) == CHIP_NO_ERROR, Status::Failure); |
| VerifyOrReturnError(!currentState.IsNull(), Status::InvalidInState); |
| VerifyOrReturnError(currentState.Value().position.HasValue() && !currentState.Value().position.Value().IsNull(), |
| Status::InvalidInState); |
| |
| if (mConformance.HasFeature(Feature::kMotionLatching)) |
| { |
| if (currentState.Value().latch.HasValue() && !currentState.Value().latch.Value().IsNull()) |
| { |
| VerifyOrReturnError(!currentState.Value().latch.Value().Value(), Status::InvalidInState, |
| ChipLogError(AppServer, |
| "Step command cannot be processed when current latch is True" |
| "on endpoint : %d", |
| mMatterContext.GetEndpointId())); |
| } |
| // Return InvalidInState if currentState is latched |
| } |
| |
| // Derive TargetState Position from StepValue and NumberOfSteps. |
| Percent100ths stepValue; |
| VerifyOrReturnError(GetStepValue(stepValue) == CHIP_NO_ERROR, Status::Failure); |
| |
| // Convert step to position delta. |
| // As StepValue can only take maxvalue of kPercents100thsMaxValue(which is 10000). Below product will be within limits of |
| // int32_t |
| uint32_t delta = numberOfSteps * stepValue; |
| uint32_t newPosition = 0; |
| |
| // check if closure supports Limitation feature, if yes fetch the LimitRange values |
| bool limitSupported = mConformance.HasFeature(Feature::kLimitation) ? true : false; |
| |
| Structs::RangePercent100thsStruct::Type limitRange; |
| |
| if (limitSupported) |
| { |
| VerifyOrReturnError(GetLimitRange(limitRange) == CHIP_NO_ERROR, Status::Failure); |
| } |
| |
| // Position = Position - NumberOfSteps * StepValue |
| uint32_t currentPosition = static_cast<uint32_t>(currentState.Value().position.Value().Value()); |
| |
| switch (direction) |
| { |
| |
| case StepDirectionEnum::kDecrease: |
| // To avoid underflow, newPosition will be set to 0 if currentPosition is less than or equal to delta |
| newPosition = (currentPosition > delta) ? currentPosition - delta : 0; |
| // Position value SHALL be clamped to 0.00% if the LM feature is not supported or LimitRange.Min if the LM feature is |
| // supported. |
| newPosition = limitSupported ? std::max(newPosition, static_cast<uint32_t>(limitRange.min)) : newPosition; |
| break; |
| |
| case StepDirectionEnum::kIncrease: |
| // To avoid overflow, newPosition will be set to UINT32_MAX if sum of currentPosition and delta is greater than UINT32_MAX |
| newPosition = (currentPosition > UINT32_MAX - delta) ? UINT32_MAX : currentPosition + delta; |
| // Position value SHALL be clamped to 0.00% if the LM feature is not supported or LimitRange.Max if the LM feature is |
| // supported. |
| newPosition = limitSupported ? std::min(newPosition, static_cast<uint32_t>(limitRange.max)) |
| : std::min(newPosition, static_cast<uint32_t>(kPercents100thsMaxValue)); |
| break; |
| |
| default: |
| // Should never reach here due to earlier VerifyOrReturnError check |
| ChipLogError(AppServer, "Unhandled StepDirectionEnum value"); |
| return Status::ConstraintError; |
| } |
| |
| // TargetState should only be set when delegate function returns status as Success. Return failure otherwise |
| VerifyOrReturnError(mDelegate.HandleStep(direction, numberOfSteps, speed) == Status::Success, Status::Failure); |
| |
| stepTarget.Value().position.SetValue(DataModel::MakeNullable(static_cast<Percent100ths>(newPosition))); |
| VerifyOrReturnError(SetTargetState(stepTarget) == CHIP_NO_ERROR, Status::Failure); |
| |
| return Status::Success; |
| } |
| |
| } // namespace ClosureDimension |
| } // namespace Clusters |
| } // namespace app |
| } // namespace chip |