From 1ccf850bd88f5249dfa0c753b0cbed6b2407b7e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=A9gevand?= <77852424+jmeg-sfy@users.noreply.github.com> Date: Wed, 26 Jan 2022 05:35:03 +0100 Subject: [PATCH] WindowCovering: fix Limits checks + factorization (#13729) * DEV: Remove IsOpen IsClosed function and use CheckLimitState * DEV: Move HasFeature function for external usage * DEV: Factorize conversion function * DEV: Add IsPercent100thsValid check method * DEV: Add OperationalStatusSet Global field helpers function - automatically set global field * DEV: Add ComputeOperationalState function * Restyled by clang-format * DEV: Rename limits using "Over" to "Post" * DEV: Cleanup HasFeature function * DEV: Add comments to explain the convert function * DEV: Replace Limit Post by Past * Style: correct bad description of ConvertValue Co-authored-by: Restyled.io --- .../window-app/common/src/ZclCallbacks.cpp | 33 +- .../window-app/efr32/src/WindowAppImpl.cpp | 14 +- .../window-covering-server.cpp | 291 +++++++++++------- .../window-covering-server.h | 41 ++- 4 files changed, 239 insertions(+), 140 deletions(-) diff --git a/examples/window-app/common/src/ZclCallbacks.cpp b/examples/window-app/common/src/ZclCallbacks.cpp index fdea8bac07fd86..c662dfdf99e462 100644 --- a/examples/window-app/common/src/ZclCallbacks.cpp +++ b/examples/window-app/common/src/ZclCallbacks.cpp @@ -58,6 +58,7 @@ void MatterPostAttributeChangeCallback(const app::ConcreteAttributePath & attrib chip::app::DataModel::Nullable current; chip::app::DataModel::Nullable target; + OperationalState opState; switch (attributePath.mAttributeId) { @@ -76,32 +77,28 @@ void MatterPostAttributeChangeCallback(const app::ConcreteAttributePath & attrib case Attributes::TargetPositionLiftPercent100ths::Id: Attributes::TargetPositionLiftPercent100ths::Get(endpoint, target); Attributes::CurrentPositionLiftPercent100ths::Get(endpoint, current); - if (!current.IsNull() && !target.IsNull()) + opState = ComputeOperationalState(target, current); + if (OperationalState::MovingDownOrClose == opState) { - if (current.Value() > target.Value()) - { - app.PostEvent(WindowApp::Event(WindowApp::EventId::LiftDown, endpoint)); - } - else if (current.Value() < target.Value()) - { - app.PostEvent(WindowApp::Event(WindowApp::EventId::LiftUp, endpoint)); - } + app.PostEvent(WindowApp::Event(WindowApp::EventId::LiftDown, endpoint)); + } + else if (OperationalState::MovingUpOrOpen == opState) + { + app.PostEvent(WindowApp::Event(WindowApp::EventId::LiftUp, endpoint)); } break; case Attributes::TargetPositionTiltPercent100ths::Id: Attributes::TargetPositionTiltPercent100ths::Get(endpoint, target); Attributes::CurrentPositionTiltPercent100ths::Get(endpoint, current); - if (!current.IsNull() && !target.IsNull()) + opState = ComputeOperationalState(target, current); + if (OperationalState::MovingDownOrClose == opState) + { + app.PostEvent(WindowApp::Event(WindowApp::EventId::TiltDown, endpoint)); + } + else if (OperationalState::MovingUpOrOpen == opState) { - if (current.Value() > target.Value()) - { - app.PostEvent(WindowApp::Event(WindowApp::EventId::TiltDown, endpoint)); - } - else if (current.Value() < target.Value()) - { - app.PostEvent(WindowApp::Event(WindowApp::EventId::TiltUp, endpoint)); - } + app.PostEvent(WindowApp::Event(WindowApp::EventId::TiltUp, endpoint)); } break; diff --git a/examples/window-app/efr32/src/WindowAppImpl.cpp b/examples/window-app/efr32/src/WindowAppImpl.cpp index c62067380a185d..e97387dc77122c 100644 --- a/examples/window-app/efr32/src/WindowAppImpl.cpp +++ b/examples/window-app/efr32/src/WindowAppImpl.cpp @@ -338,16 +338,26 @@ void WindowAppImpl::UpdateLEDs() else { mStatusLED.Blink(50, 950); } // Action LED + NPercent100ths current; + LimitStatus liftLimit = LimitStatus::Intermediate; + + Attributes::CurrentPositionLiftPercent100ths::Get(cover.mEndpoint, current); + + if (!current.IsNull()) + { + AbsoluteLimits limits = { .open = WC_PERCENT100THS_MIN_OPEN, .closed = WC_PERCENT100THS_MAX_CLOSED }; + liftLimit = CheckLimitState(current.Value(), limits); + } if (EventId::None != cover.mLiftAction || EventId::None != cover.mTiltAction) { mActionLED.Blink(100); } - else if (IsLiftOpen(cover.mEndpoint)) + else if (LimitStatus::IsUpOrOpen == liftLimit) { mActionLED.Set(true); } - else if (IsLiftClosed(cover.mEndpoint)) + else if (LimitStatus::IsDownOrClose == liftLimit) { mActionLED.Set(false); } diff --git a/src/app/clusters/window-covering-server/window-covering-server.cpp b/src/app/clusters/window-covering-server/window-covering-server.cpp index f04f8d0915f582..2b3d27979ed5e1 100644 --- a/src/app/clusters/window-covering-server/window-covering-server.cpp +++ b/src/app/clusters/window-covering-server/window-covering-server.cpp @@ -39,84 +39,73 @@ using namespace chip; using namespace chip::app::Clusters::WindowCovering; -#define WC_PERCENT100THS_MIN 0 -#define WC_PERCENT100THS_MAX 10000 - -static bool HasFeature(chip::EndpointId endpoint, WcFeature feature) -{ - uint32_t FeatureMap = 0; - if (EMBER_ZCL_STATUS_SUCCESS == - emberAfReadServerAttribute(endpoint, chip::app::Clusters::WindowCovering::Id, - chip::app::Clusters::WindowCovering::Attributes::FeatureMap::Id, - reinterpret_cast(&FeatureMap), sizeof(FeatureMap))) - { - return (FeatureMap & chip::to_underlying(feature)) != 0; - } - - return false; -} - -static bool HasFeaturePaLift(chip::EndpointId endpoint) -{ - return (HasFeature(endpoint, WcFeature::kLift) && HasFeature(endpoint, WcFeature::kPositionAwareLift)); -} - -static bool HasFeaturePaTilt(chip::EndpointId endpoint) -{ - return (HasFeature(endpoint, WcFeature::kTilt) && HasFeature(endpoint, WcFeature::kPositionAwareTilt)); -} - -static uint16_t ValueToPercent100ths(uint16_t openLimit, uint16_t closedLimit, uint16_t value) +#define CHECK_BOUNDS_INVALID(MIN, VAL, MAX) ((VAL < MIN) || (VAL > MAX)) +#define CHECK_BOUNDS_VALID(MIN, VAL, MAX) (!CHECK_BOUNDS_INVALID(MIN, VAL, MAX)) + +/* + * ConvertValue: Converts values from one range to another + * Range In -> from inputLowValue to inputHighValue + * Range Out -> from outputLowValue to outputtHighValue + * offset true -> allows to take into account the minimum of each range in the conversion + * offset false -> applies only the basic "rule of three" for the conversion + */ +static uint16_t ConvertValue(uint16_t inputLowValue, uint16_t inputHighValue, uint16_t outputLowValue, uint16_t outputHighValue, + uint16_t value, bool offset) { - uint16_t minimum = 0, range = UINT16_MAX; + uint16_t inputMin = inputLowValue, inputMax = inputHighValue, inputRange = UINT16_MAX; + uint16_t outputMin = outputLowValue, outputMax = outputHighValue, outputRange = UINT16_MAX; - if (openLimit > closedLimit) + if (inputLowValue > inputHighValue) { - minimum = closedLimit; - range = static_cast(openLimit - minimum); - } - else - { - minimum = openLimit; - range = static_cast(closedLimit - minimum); - } - - if (value < minimum) - { - return 0; + inputMin = inputHighValue; + inputMax = inputLowValue; } - if (range > 0) + if (outputLowValue > outputHighValue) { - return static_cast(WC_PERCENT100THS_MAX * (value - minimum) / range); + outputMin = outputHighValue; + outputMax = outputLowValue; } - return WC_PERCENT100THS_MAX; -} + inputRange = static_cast(inputMax - inputMin); + outputRange = static_cast(outputMax - outputMin); -static uint16_t Percent100thsToValue(uint16_t openLimit, uint16_t closedLimit, uint16_t percent100ths) -{ - uint16_t minimum = 0, maximum = UINT16_MAX, range = UINT16_MAX; - - if (openLimit > closedLimit) + if (offset) { - minimum = closedLimit; - maximum = openLimit; + if (value < inputMin) + { + return outputMin; + } + + if (value > inputMax) + { + return outputMax; + } + + if (inputRange > 0) + { + return static_cast(outputMin + ((outputRange * (value - inputMin) / inputRange))); + } } else { - minimum = openLimit; - maximum = closedLimit; + if (inputRange > 0) + { + return static_cast((outputRange * (value) / inputRange)); + } } - range = static_cast(maximum - minimum); + return outputMax; +} - if (percent100ths > WC_PERCENT100THS_MAX) - { - return maximum; - } +static Percent100ths ValueToPercent100ths(AbsoluteLimits limits, uint16_t absolute) +{ + return ConvertValue(limits.open, limits.closed, WC_PERCENT100THS_MIN_OPEN, WC_PERCENT100THS_MAX_CLOSED, absolute, true); +} - return static_cast(minimum + ((range * percent100ths) / WC_PERCENT100THS_MAX)); +static uint16_t Percent100thsToValue(AbsoluteLimits limits, Percent100ths relative) +{ + return ConvertValue(WC_PERCENT100THS_MIN_OPEN, WC_PERCENT100THS_MAX_CLOSED, limits.open, limits.closed, relative, true); } static OperationalState ValueToOperationalState(uint8_t value) @@ -155,56 +144,28 @@ namespace app { namespace Clusters { namespace WindowCovering { -bool IsLiftOpen(chip::EndpointId endpoint) +bool HasFeature(chip::EndpointId endpoint, WcFeature feature) { - EmberAfStatus status; - app::DataModel::Nullable position; - - status = Attributes::TargetPositionLiftPercent100ths::Get(endpoint, position); - - if ((status != EMBER_ZCL_STATUS_SUCCESS) || position.IsNull()) - return false; + bool hasFeature = false; + uint32_t featureMap = 0; - return ((position.Value() == WC_PERCENT100THS_MIN)); -} - -bool IsTiltOpen(chip::EndpointId endpoint) -{ - EmberAfStatus status; - app::DataModel::Nullable position; - - status = Attributes::TargetPositionTiltPercent100ths::Get(endpoint, position); - - if ((status != EMBER_ZCL_STATUS_SUCCESS) || position.IsNull()) - return false; + EmberAfStatus status = Attributes::FeatureMap::Get(endpoint, &featureMap); + if (EMBER_ZCL_STATUS_SUCCESS == status) + { + hasFeature = (featureMap & chip::to_underlying(feature)); + } - return ((position.Value() == WC_PERCENT100THS_MIN)); + return hasFeature; } -bool IsLiftClosed(chip::EndpointId endpoint) +static bool HasFeaturePaLift(chip::EndpointId endpoint) { - EmberAfStatus status; - app::DataModel::Nullable position; - - status = Attributes::TargetPositionLiftPercent100ths::Get(endpoint, position); - - if ((status != EMBER_ZCL_STATUS_SUCCESS) || position.IsNull()) - return false; - - return ((position.Value() == WC_PERCENT100THS_MAX)); + return (HasFeature(endpoint, WcFeature::kLift) && HasFeature(endpoint, WcFeature::kPositionAwareLift)); } -bool IsTiltClosed(chip::EndpointId endpoint) +static bool HasFeaturePaTilt(chip::EndpointId endpoint) { - EmberAfStatus status; - app::DataModel::Nullable position; - - status = Attributes::TargetPositionTiltPercent100ths::Get(endpoint, position); - - if ((status != EMBER_ZCL_STATUS_SUCCESS) || position.IsNull()) - return false; - - return ((position.Value() == WC_PERCENT100THS_MAX)); + return (HasFeature(endpoint, WcFeature::kTilt) && HasFeature(endpoint, WcFeature::kPositionAwareTilt)); } void TypeSet(chip::EndpointId endpoint, EmberAfWcType type) @@ -249,6 +210,21 @@ const ConfigStatus ConfigStatusGet(chip::EndpointId endpoint) return status; } +void OperationalStatusSetWithGlobalUpdated(chip::EndpointId endpoint, OperationalStatus & status) +{ + /* Global Always follow Lift by priority and then fallback to Tilt */ + if (OperationalState::Stall != status.lift) + { + status.global = status.lift; + } + else + { + status.global = status.tilt; + } + + OperationalStatusSet(endpoint, status); +} + void OperationalStatusSet(chip::EndpointId endpoint, const OperationalStatus & status) { uint8_t global = OperationalStateToValue(status.global); @@ -340,13 +316,54 @@ const SafetyStatus SafetyStatusGet(chip::EndpointId endpoint) return status; } +LimitStatus CheckLimitState(uint16_t position, AbsoluteLimits limits) +{ + + if (limits.open > limits.closed) + return LimitStatus::Inverted; + + if (position == limits.open) + return LimitStatus::IsUpOrOpen; + + if (position == limits.closed) + return LimitStatus::IsDownOrClose; + + if ((limits.open > 0) && (position < limits.open)) + return LimitStatus::IsPastUpOrOpen; + + if ((limits.closed > 0) && (position > limits.closed)) + return LimitStatus::IsPastDownOrClose; + + return LimitStatus::Intermediate; +} + +bool IsPercent100thsValid(Percent100ths percent100ths) +{ + if (CHECK_BOUNDS_VALID(WC_PERCENT100THS_MIN_OPEN, percent100ths, WC_PERCENT100THS_MAX_CLOSED)) + return true; + + return false; +} + +bool IsPercent100thsValid(NPercent100ths percent100ths) +{ + if (!percent100ths.IsNull()) + { + return IsPercent100thsValid(percent100ths.Value()); + } + + return true; +} + uint16_t LiftToPercent100ths(chip::EndpointId endpoint, uint16_t lift) { uint16_t openLimit = 0; uint16_t closedLimit = 0; Attributes::InstalledOpenLimitLift::Get(endpoint, &openLimit); Attributes::InstalledClosedLimitLift::Get(endpoint, &closedLimit); - return ValueToPercent100ths(openLimit, closedLimit, lift); + + AbsoluteLimits limits = { .open = openLimit, .closed = closedLimit }; + return ValueToPercent100ths(limits, lift); } uint16_t Percent100thsToLift(chip::EndpointId endpoint, uint16_t percent100ths) @@ -355,7 +372,9 @@ uint16_t Percent100thsToLift(chip::EndpointId endpoint, uint16_t percent100ths) uint16_t closedLimit = 0; Attributes::InstalledOpenLimitLift::Get(endpoint, &openLimit); Attributes::InstalledClosedLimitLift::Get(endpoint, &closedLimit); - return Percent100thsToValue(openLimit, closedLimit, percent100ths); + + AbsoluteLimits limits = { .open = openLimit, .closed = closedLimit }; + return Percent100thsToValue(limits, percent100ths); } void LiftPositionSet(chip::EndpointId endpoint, uint16_t percent100ths) @@ -375,7 +394,10 @@ uint16_t TiltToPercent100ths(chip::EndpointId endpoint, uint16_t tilt) uint16_t closedLimit = 0; Attributes::InstalledOpenLimitTilt::Get(endpoint, &openLimit); Attributes::InstalledClosedLimitTilt::Get(endpoint, &closedLimit); - return ValueToPercent100ths(openLimit, closedLimit, tilt); + + AbsoluteLimits limits = { .open = openLimit, .closed = closedLimit }; + + return ValueToPercent100ths(limits, tilt); } uint16_t Percent100thsToTilt(chip::EndpointId endpoint, uint16_t percent100ths) @@ -384,7 +406,10 @@ uint16_t Percent100thsToTilt(chip::EndpointId endpoint, uint16_t percent100ths) uint16_t closedLimit = 0; Attributes::InstalledOpenLimitTilt::Get(endpoint, &openLimit); Attributes::InstalledClosedLimitTilt::Get(endpoint, &closedLimit); - return Percent100thsToValue(openLimit, closedLimit, percent100ths); + + AbsoluteLimits limits = { .open = openLimit, .closed = closedLimit }; + + return Percent100thsToValue(limits, percent100ths); } void TiltPositionSet(chip::EndpointId endpoint, uint16_t percent100ths) @@ -398,6 +423,26 @@ void TiltPositionSet(chip::EndpointId endpoint, uint16_t percent100ths) emberAfWindowCoveringClusterPrint("Tilt Position Set: %u%%", percent); } +OperationalState ComputeOperationalState(uint16_t target, uint16_t current) +{ + OperationalState opState = OperationalState::Stall; + + if (current != target) + { + opState = (current < target) ? OperationalState::MovingDownOrClose : OperationalState::MovingUpOrOpen; + } + return opState; +} + +OperationalState ComputeOperationalState(NPercent100ths target, NPercent100ths current) +{ + if (!current.IsNull() && !target.IsNull()) + { + return ComputeOperationalState(target.Value(), current.Value()); + } + return OperationalState::Stall; +} + } // namespace WindowCovering } // namespace Clusters } // namespace app @@ -429,11 +474,11 @@ bool emberAfWindowCoveringClusterUpOrOpenCallback(app::CommandHandler * commandO emberAfWindowCoveringClusterPrint("UpOrOpen command received"); if (HasFeature(endpoint, WcFeature::kLift)) { - Attributes::TargetPositionLiftPercent100ths::Set(endpoint, WC_PERCENT100THS_MIN); + Attributes::TargetPositionLiftPercent100ths::Set(endpoint, WC_PERCENT100THS_MIN_OPEN); } if (HasFeature(endpoint, WcFeature::kTilt)) { - Attributes::TargetPositionTiltPercent100ths::Set(endpoint, WC_PERCENT100THS_MIN); + Attributes::TargetPositionTiltPercent100ths::Set(endpoint, WC_PERCENT100THS_MIN_OPEN); } emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); return true; @@ -450,11 +495,11 @@ bool emberAfWindowCoveringClusterDownOrCloseCallback(app::CommandHandler * comma emberAfWindowCoveringClusterPrint("DownOrClose command received"); if (HasFeature(endpoint, WcFeature::kLift)) { - Attributes::TargetPositionLiftPercent100ths::Set(endpoint, WC_PERCENT100THS_MAX); + Attributes::TargetPositionLiftPercent100ths::Set(endpoint, WC_PERCENT100THS_MAX_CLOSED); } if (HasFeature(endpoint, WcFeature::kTilt)) { - Attributes::TargetPositionTiltPercent100ths::Set(endpoint, WC_PERCENT100THS_MAX); + Attributes::TargetPositionTiltPercent100ths::Set(endpoint, WC_PERCENT100THS_MAX_CLOSED); } emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); return true; @@ -526,9 +571,16 @@ bool emberAfWindowCoveringClusterGoToLiftPercentageCallback(app::CommandHandler emberAfWindowCoveringClusterPrint("GoToLiftPercentage Percentage command received"); if (HasFeaturePaLift(endpoint)) { - Attributes::TargetPositionLiftPercent100ths::Set( - endpoint, static_cast(liftPercentageValue > 100 ? liftPercent100thsValue : liftPercentageValue * 100)); - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + if (IsPercent100thsValid(liftPercent100thsValue)) + { + Attributes::TargetPositionLiftPercent100ths::Set( + endpoint, static_cast(liftPercentageValue > 100 ? liftPercent100thsValue : liftPercentageValue * 100)); + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + } + else + { + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_VALUE); + } } else { @@ -578,9 +630,16 @@ bool emberAfWindowCoveringClusterGoToTiltPercentageCallback(app::CommandHandler emberAfWindowCoveringClusterPrint("GoToTiltPercentage command received"); if (HasFeaturePaTilt(endpoint)) { - Attributes::TargetPositionTiltPercent100ths::Set( - endpoint, static_cast(tiltPercentageValue > 100 ? tiltPercent100thsValue : tiltPercentageValue * 100)); - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + if (IsPercent100thsValid(tiltPercent100thsValue)) + { + Attributes::TargetPositionTiltPercent100ths::Set( + endpoint, static_cast(tiltPercentageValue > 100 ? tiltPercent100thsValue : tiltPercentageValue * 100)); + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); + } + else + { + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_VALUE); + } } else { diff --git a/src/app/clusters/window-covering-server/window-covering-server.h b/src/app/clusters/window-covering-server/window-covering-server.h index 9106fed3c72bf8..40e523c4e74839 100644 --- a/src/app/clusters/window-covering-server/window-covering-server.h +++ b/src/app/clusters/window-covering-server/window-covering-server.h @@ -18,14 +18,24 @@ #pragma once #include +#include #include #include +#include + +#define WC_PERCENT100THS_MIN_OPEN 0 +#define WC_PERCENT100THS_MAX_CLOSED 10000 + namespace chip { namespace app { namespace Clusters { namespace WindowCovering { +typedef DataModel::Nullable NPercent; +typedef DataModel::Nullable NPercent100ths; +typedef DataModel::Nullable NAbsolute; + struct Mode { uint8_t motorDirReversed : 1; // bit 0 @@ -81,11 +91,25 @@ struct SafetyStatus }; static_assert(sizeof(SafetyStatus) == sizeof(uint16_t), "SafetyStatus Size is not correct"); -bool IsLiftOpen(chip::EndpointId endpoint); -bool IsLiftClosed(chip::EndpointId endpoint); +// Declare Position Limit Status +enum class LimitStatus : uint8_t +{ + Intermediate = 0x00, + IsUpOrOpen = 0x01, + IsDownOrClose = 0x02, + Inverted = 0x03, + IsPastUpOrOpen = 0x04, + IsPastDownOrClose = 0x05, +}; +static_assert(sizeof(LimitStatus) == sizeof(uint8_t), "LimitStatus Size is not correct"); -bool IsTiltOpen(chip::EndpointId endpoint); -bool IsTiltClosed(chip::EndpointId endpoint); +struct AbsoluteLimits +{ + uint16_t open; + uint16_t closed; +}; + +bool HasFeature(chip::EndpointId endpoint, WcFeature feature); void TypeSet(chip::EndpointId endpoint, EmberAfWcType type); EmberAfWcType TypeGet(chip::EndpointId endpoint); @@ -94,8 +118,12 @@ void ConfigStatusSet(chip::EndpointId endpoint, const ConfigStatus & status); const ConfigStatus ConfigStatusGet(chip::EndpointId endpoint); void OperationalStatusSet(chip::EndpointId endpoint, const OperationalStatus & status); +void OperationalStatusSetWithGlobalUpdated(chip::EndpointId endpoint, OperationalStatus & status); const OperationalStatus OperationalStatusGet(chip::EndpointId endpoint); +OperationalState ComputeOperationalState(uint16_t target, uint16_t current); +OperationalState ComputeOperationalState(NPercent100ths target, NPercent100ths current); + void EndProductTypeSet(chip::EndpointId endpoint, EmberAfWcEndProductType type); EmberAfWcEndProductType EndProductTypeGet(chip::EndpointId endpoint); @@ -105,6 +133,11 @@ const Mode ModeGet(chip::EndpointId endpoint); void SafetyStatusSet(chip::EndpointId endpoint, SafetyStatus & status); const SafetyStatus SafetyStatusGet(chip::EndpointId endpoint); +LimitStatus CheckLimitState(uint16_t position, AbsoluteLimits limits); + +bool IsPercent100thsValid(Percent100ths percent100ths); +bool IsPercent100thsValid(NPercent100ths npercent100ths); + uint16_t LiftToPercent100ths(chip::EndpointId endpoint, uint16_t lift); uint16_t Percent100thsToLift(chip::EndpointId endpoint, uint16_t percent100ths); void LiftPositionSet(chip::EndpointId endpoint, uint16_t percent100ths);