blob: 65237ba9b1211f488842bfe7ba4eb958c13c9a80 [file] [log] [blame]
/**
*
* Copyright (c) 2020 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 "window-covering-server.h"
#include <app-common/zap-generated/attribute-id.h>
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app-common/zap-generated/cluster-id.h>
#include <app-common/zap-generated/cluster-objects.h>
#include <app-common/zap-generated/command-id.h>
#include <app/CommandHandler.h>
#include <app/ConcreteCommandPath.h>
#include <app/reporting/reporting.h>
#include <app/util/af-event.h>
#include <app/util/af-types.h>
#include <app/util/af.h>
#include <app/util/attribute-storage.h>
#include <lib/support/TypeTraits.h>
#include <string.h>
#ifdef EMBER_AF_PLUGIN_SCENES
#include <app/clusters/scenes/scenes.h>
#endif // EMBER_AF_PLUGIN_SCENES
using namespace chip;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::WindowCovering;
#define CHECK_BOUNDS_INVALID(MIN, VAL, MAX) ((VAL < MIN) || (VAL > MAX))
#define CHECK_BOUNDS_VALID(MIN, VAL, MAX) (!CHECK_BOUNDS_INVALID(MIN, VAL, MAX))
#define FAKE_MOTION_DELAY_MS 5000
namespace {
constexpr size_t kWindowCoveringDelegateTableSize =
EMBER_AF_WINDOW_COVERING_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT;
Delegate * gDelegateTable[kWindowCoveringDelegateTableSize] = { nullptr };
Delegate * GetDelegate(EndpointId endpoint)
{
uint16_t ep = emberAfFindClusterServerEndpointIndex(endpoint, WindowCovering::Id);
return ((ep == kInvalidEndpointId || ep >= EMBER_AF_WINDOW_COVERING_CLUSTER_SERVER_ENDPOINT_COUNT) ? nullptr
: gDelegateTable[ep]);
}
/*
* ConvertValue: Converts values from one range to another
* Range In -> from inputLowValue to inputHighValue
* Range Out -> from outputLowValue to outputtHighValue
*/
uint16_t ConvertValue(uint16_t inputLowValue, uint16_t inputHighValue, uint16_t outputLowValue, uint16_t outputHighValue,
uint16_t value)
{
uint16_t inputMin = inputLowValue, inputMax = inputHighValue, inputRange = UINT16_MAX;
uint16_t outputMin = outputLowValue, outputMax = outputHighValue, outputRange = UINT16_MAX;
if (inputLowValue > inputHighValue)
{
inputMin = inputHighValue;
inputMax = inputLowValue;
}
if (outputLowValue > outputHighValue)
{
outputMin = outputHighValue;
outputMax = outputLowValue;
}
inputRange = static_cast<uint16_t>(inputMax - inputMin);
outputRange = static_cast<uint16_t>(outputMax - outputMin);
if (value < inputMin)
{
return outputMin;
}
if (value > inputMax)
{
return outputMax;
}
if (inputRange > 0)
{
return static_cast<uint16_t>(outputMin + ((outputRange * (value - inputMin) / inputRange)));
}
return outputMax;
}
Percent100ths ValueToPercent100ths(AbsoluteLimits limits, uint16_t absolute)
{
return ConvertValue(limits.open, limits.closed, WC_PERCENT100THS_MIN_OPEN, WC_PERCENT100THS_MAX_CLOSED, absolute);
}
} // namespace
namespace chip {
namespace app {
namespace Clusters {
namespace WindowCovering {
bool HasFeature(chip::EndpointId endpoint, Feature feature)
{
bool hasFeature = false;
uint32_t featureMap = 0;
EmberAfStatus status = Attributes::FeatureMap::Get(endpoint, &featureMap);
if (EMBER_ZCL_STATUS_SUCCESS == status)
{
hasFeature = (featureMap & chip::to_underlying(feature));
}
return hasFeature;
}
bool HasFeaturePaLift(chip::EndpointId endpoint)
{
return (HasFeature(endpoint, Feature::kLift) && HasFeature(endpoint, Feature::kPositionAwareLift));
}
bool HasFeaturePaTilt(chip::EndpointId endpoint)
{
return (HasFeature(endpoint, Feature::kTilt) && HasFeature(endpoint, Feature::kPositionAwareTilt));
}
void TypeSet(chip::EndpointId endpoint, Type type)
{
Attributes::Type::Set(endpoint, type);
}
Type TypeGet(chip::EndpointId endpoint)
{
Type value;
Attributes::Type::Get(endpoint, &value);
return value;
}
void ConfigStatusPrint(const chip::BitMask<ConfigStatus> & configStatus)
{
emberAfWindowCoveringClusterPrint("ConfigStatus 0x%02X Operational=%u OnlineReserved=%u", configStatus.Raw(),
configStatus.Has(ConfigStatus::kOperational),
configStatus.Has(ConfigStatus::kOnlineReserved));
emberAfWindowCoveringClusterPrint(
"Lift(PA=%u Encoder=%u Reversed=%u) Tilt(PA=%u Encoder=%u)", configStatus.Has(ConfigStatus::kLiftPositionAware),
configStatus.Has(ConfigStatus::kLiftEncoderControlled), configStatus.Has(ConfigStatus::kLiftMovementReversed),
configStatus.Has(ConfigStatus::kTiltPositionAware), configStatus.Has(ConfigStatus::kTiltEncoderControlled));
}
void ConfigStatusSet(chip::EndpointId endpoint, const chip::BitMask<ConfigStatus> & configStatus)
{
Attributes::ConfigStatus::Set(endpoint, configStatus);
}
chip::BitMask<ConfigStatus> ConfigStatusGet(chip::EndpointId endpoint)
{
chip::BitMask<ConfigStatus> configStatus;
Attributes::ConfigStatus::Get(endpoint, &configStatus);
return configStatus;
}
void ConfigStatusUpdateFeatures(chip::EndpointId endpoint)
{
chip::BitMask<ConfigStatus> configStatus = ConfigStatusGet(endpoint);
configStatus.Set(ConfigStatus::kLiftPositionAware, HasFeaturePaLift(endpoint));
configStatus.Set(ConfigStatus::kTiltPositionAware, HasFeaturePaTilt(endpoint));
if (!HasFeaturePaLift(endpoint))
configStatus.Clear(ConfigStatus::kLiftEncoderControlled);
if (!HasFeaturePaTilt(endpoint))
configStatus.Clear(ConfigStatus::kTiltEncoderControlled);
ConfigStatusSet(endpoint, configStatus);
}
void OperationalStatusPrint(const chip::BitMask<OperationalStatus> & opStatus)
{
emberAfWindowCoveringClusterPrint("OperationalStatus raw=0x%02X global=%u lift=%u tilt=%u", opStatus.Raw(),
opStatus.GetField(OperationalStatus::kGlobal), opStatus.GetField(OperationalStatus::kLift),
opStatus.GetField(OperationalStatus::kTilt));
}
chip::BitMask<OperationalStatus> OperationalStatusGet(chip::EndpointId endpoint)
{
chip::BitMask<OperationalStatus> status;
Attributes::OperationalStatus::Get(endpoint, &status);
return status;
}
void OperationalStatusSet(chip::EndpointId endpoint, chip::BitMask<OperationalStatus> newStatus)
{
chip::BitMask<OperationalStatus> prevStatus;
Attributes::OperationalStatus::Get(endpoint, &prevStatus);
// Filter changes
if (newStatus != prevStatus)
{
OperationalStatusPrint(newStatus);
Attributes::OperationalStatus::Set(endpoint, newStatus);
}
}
void OperationalStateSet(chip::EndpointId endpoint, const chip::BitMask<OperationalStatus> field, OperationalState state)
{
chip::BitMask<OperationalStatus> status;
Attributes::OperationalStatus::Get(endpoint, &status);
/* Filter only Lift or Tilt action since we cannot allow global reflecting a state alone */
if ((OperationalStatus::kLift == field) || (OperationalStatus::kTilt == field))
{
status.SetField(field, static_cast<uint8_t>(state));
status.SetField(OperationalStatus::kGlobal, static_cast<uint8_t>(state));
/* Global Always follow Lift by priority or therefore fallback to Tilt */
chip::BitMask<OperationalStatus> opGlobal =
status.HasAny(OperationalStatus::kLift) ? OperationalStatus::kLift : OperationalStatus::kTilt;
status.SetField(OperationalStatus::kGlobal, status.GetField(opGlobal));
OperationalStatusSet(endpoint, status);
}
}
OperationalState OperationalStateGet(chip::EndpointId endpoint, const chip::BitMask<OperationalStatus> field)
{
chip::BitMask<OperationalStatus> status;
Attributes::OperationalStatus::Get(endpoint, &status);
return static_cast<OperationalState>(status.GetField(field));
}
void EndProductTypeSet(chip::EndpointId endpoint, EndProductType type)
{
Attributes::EndProductType::Set(endpoint, type);
}
EndProductType EndProductTypeGet(chip::EndpointId endpoint)
{
EndProductType value;
Attributes::EndProductType::Get(endpoint, &value);
return value;
}
void ModePrint(const chip::BitMask<Mode> & mode)
{
emberAfWindowCoveringClusterPrint("Mode 0x%02X MotorDirReversed=%u LedFeedback=%u Maintenance=%u Calibration=%u", mode.Raw(),
mode.Has(Mode::kMotorDirectionReversed), mode.Has(Mode::kLedFeedback),
mode.Has(Mode::kMaintenanceMode), mode.Has(Mode::kCalibrationMode));
}
void ModeSet(chip::EndpointId endpoint, chip::BitMask<Mode> & newMode)
{
chip::BitMask<ConfigStatus> newStatus;
chip::BitMask<ConfigStatus> oldStatus = ConfigStatusGet(endpoint);
chip::BitMask<Mode> oldMode = ModeGet(endpoint);
newStatus = oldStatus;
// Attribute: ConfigStatus reflects the following current mode flags
newStatus.Set(ConfigStatus::kOperational, !newMode.HasAny(Mode::kMaintenanceMode, Mode::kCalibrationMode));
newStatus.Set(ConfigStatus::kLiftMovementReversed, newMode.Has(Mode::kMotorDirectionReversed));
// Verify only one mode supported at once and maintenance lock goes over calibration
if (newMode.HasAll(Mode::kMaintenanceMode, Mode::kCalibrationMode))
{
newMode.Clear(Mode::kCalibrationMode);
}
if (oldMode != newMode)
Attributes::Mode::Set(endpoint, newMode);
if (oldStatus != newStatus)
ConfigStatusSet(endpoint, newStatus);
}
chip::BitMask<Mode> ModeGet(chip::EndpointId endpoint)
{
chip::BitMask<Mode> mode;
Attributes::Mode::Get(endpoint, &mode);
return mode;
}
void SafetyStatusSet(chip::EndpointId endpoint, chip::BitMask<SafetyStatus> & newSafetyStatus)
{
Attributes::SafetyStatus::Set(endpoint, newSafetyStatus);
}
chip::BitMask<SafetyStatus> SafetyStatusGet(chip::EndpointId endpoint)
{
chip::BitMask<SafetyStatus> safetyStatus;
Attributes::SafetyStatus::Get(endpoint, &safetyStatus);
return safetyStatus;
}
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 Percent100thsToValue(AbsoluteLimits limits, Percent100ths relative)
{
return ConvertValue(WC_PERCENT100THS_MIN_OPEN, WC_PERCENT100THS_MAX_CLOSED, limits.open, limits.closed, relative);
}
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);
AbsoluteLimits limits = { .open = openLimit, .closed = closedLimit };
return ValueToPercent100ths(limits, lift);
}
uint16_t Percent100thsToLift(chip::EndpointId endpoint, uint16_t percent100ths)
{
uint16_t openLimit = 0;
uint16_t closedLimit = 0;
Attributes::InstalledOpenLimitLift::Get(endpoint, &openLimit);
Attributes::InstalledClosedLimitLift::Get(endpoint, &closedLimit);
AbsoluteLimits limits = { .open = openLimit, .closed = closedLimit };
return Percent100thsToValue(limits, percent100ths);
}
void LiftPositionSet(chip::EndpointId endpoint, NPercent100ths percent100ths)
{
NPercent percent;
NAbsolute rawpos;
if (percent100ths.IsNull())
{
percent.SetNull();
rawpos.SetNull();
emberAfWindowCoveringClusterPrint("Lift[%u] Position Set to Null", endpoint);
}
else
{
percent.SetNonNull(static_cast<uint8_t>(percent100ths.Value() / 100));
rawpos.SetNonNull(Percent100thsToLift(endpoint, percent100ths.Value()));
emberAfWindowCoveringClusterPrint("Lift[%u] Position Set: %u", endpoint, percent100ths.Value());
}
Attributes::CurrentPositionLift::Set(endpoint, rawpos);
Attributes::CurrentPositionLiftPercentage::Set(endpoint, percent);
Attributes::CurrentPositionLiftPercent100ths::Set(endpoint, percent100ths);
}
uint16_t TiltToPercent100ths(chip::EndpointId endpoint, uint16_t tilt)
{
uint16_t openLimit = 0;
uint16_t closedLimit = 0;
Attributes::InstalledOpenLimitTilt::Get(endpoint, &openLimit);
Attributes::InstalledClosedLimitTilt::Get(endpoint, &closedLimit);
AbsoluteLimits limits = { .open = openLimit, .closed = closedLimit };
return ValueToPercent100ths(limits, tilt);
}
uint16_t Percent100thsToTilt(chip::EndpointId endpoint, uint16_t percent100ths)
{
uint16_t openLimit = 0;
uint16_t closedLimit = 0;
Attributes::InstalledOpenLimitTilt::Get(endpoint, &openLimit);
Attributes::InstalledClosedLimitTilt::Get(endpoint, &closedLimit);
AbsoluteLimits limits = { .open = openLimit, .closed = closedLimit };
return Percent100thsToValue(limits, percent100ths);
}
void TiltPositionSet(chip::EndpointId endpoint, NPercent100ths percent100ths)
{
NPercent percent;
NAbsolute rawpos;
if (percent100ths.IsNull())
{
percent.SetNull();
rawpos.SetNull();
emberAfWindowCoveringClusterPrint("Tilt[%u] Position Set to Null", endpoint);
}
else
{
percent.SetNonNull(static_cast<uint8_t>(percent100ths.Value() / 100));
rawpos.SetNonNull(Percent100thsToTilt(endpoint, percent100ths.Value()));
emberAfWindowCoveringClusterPrint("Tilt[%u] Position Set: %u", endpoint, percent100ths.Value());
}
Attributes::CurrentPositionTilt::Set(endpoint, rawpos);
Attributes::CurrentPositionTiltPercentage::Set(endpoint, percent);
Attributes::CurrentPositionTiltPercent100ths::Set(endpoint, percent100ths);
}
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;
}
Percent100ths ComputePercent100thsStep(OperationalState direction, Percent100ths previous, Percent100ths delta)
{
Percent100ths percent100ths = previous;
switch (direction)
{
case OperationalState::MovingDownOrClose:
if (percent100ths < (WC_PERCENT100THS_MAX_CLOSED - delta))
{
percent100ths = static_cast<Percent100ths>(percent100ths + delta);
}
else
{
percent100ths = WC_PERCENT100THS_MAX_CLOSED;
}
break;
case OperationalState::MovingUpOrOpen:
if (percent100ths > (WC_PERCENT100THS_MIN_OPEN + delta))
{
percent100ths = static_cast<Percent100ths>(percent100ths - delta);
}
else
{
percent100ths = WC_PERCENT100THS_MIN_OPEN;
}
break;
default:
// nothing to do we keep previous value, simple passthrought
break;
}
if (percent100ths > WC_PERCENT100THS_MAX_CLOSED)
return WC_PERCENT100THS_MAX_CLOSED;
return percent100ths;
}
void PostAttributeChange(chip::EndpointId endpoint, chip::AttributeId attributeId)
{
// all-cluster-app: simulation for the CI testing
// otherwise it is defined for manufacturer specific implementation */
BitMask<Mode> mode;
BitMask<ConfigStatus> configStatus;
NPercent100ths current, target;
emberAfWindowCoveringClusterPrint("WC POST ATTRIBUTE=%u", (unsigned int) attributeId);
OperationalState opLift = OperationalStateGet(endpoint, OperationalStatus::kLift);
OperationalState opTilt = OperationalStateGet(endpoint, OperationalStatus::kTilt);
switch (attributeId)
{
/* ============= Positions for Position Aware ============= */
case Attributes::CurrentPositionLiftPercent100ths::Id:
Attributes::TargetPositionLiftPercent100ths::Get(endpoint, target);
Attributes::CurrentPositionLiftPercent100ths::Get(endpoint, current);
if ((OperationalState::Stall != opLift) && (current == target))
{
emberAfWindowCoveringClusterPrint("Lift stop");
OperationalStateSet(endpoint, OperationalStatus::kLift, OperationalState::Stall);
}
break;
case Attributes::CurrentPositionTiltPercent100ths::Id:
Attributes::TargetPositionTiltPercent100ths::Get(endpoint, target);
Attributes::CurrentPositionTiltPercent100ths::Get(endpoint, current);
if ((OperationalState::Stall != opTilt) && (current == target))
{
emberAfWindowCoveringClusterPrint("Tilt stop");
OperationalStateSet(endpoint, OperationalStatus::kTilt, OperationalState::Stall);
}
break;
/* For a device supporting Position Awareness : Changing the Target triggers motions on the real or simulated device */
case Attributes::TargetPositionLiftPercent100ths::Id:
Attributes::TargetPositionLiftPercent100ths::Get(endpoint, target);
Attributes::CurrentPositionLiftPercent100ths::Get(endpoint, current);
opLift = ComputeOperationalState(target, current);
OperationalStateSet(endpoint, OperationalStatus::kLift, opLift);
break;
/* For a device supporting Position Awareness : Changing the Target triggers motions on the real or simulated device */
case Attributes::TargetPositionTiltPercent100ths::Id:
Attributes::TargetPositionTiltPercent100ths::Get(endpoint, target);
Attributes::CurrentPositionTiltPercent100ths::Get(endpoint, current);
opTilt = ComputeOperationalState(target, current);
OperationalStateSet(endpoint, OperationalStatus::kTilt, opTilt);
break;
/* Mode change is either internal from the application or external from a write request */
case Attributes::Mode::Id:
mode = ModeGet(endpoint);
ModePrint(mode);
ModeSet(endpoint, mode); // refilter mode if needed
break;
case Attributes::ConfigStatus::Id:
configStatus = ConfigStatusGet(endpoint);
ConfigStatusPrint(configStatus);
break;
default:
break;
}
}
EmberAfStatus GetMotionLockStatus(chip::EndpointId endpoint)
{
BitMask<Mode> mode = ModeGet(endpoint);
BitMask<ConfigStatus> configStatus = ConfigStatusGet(endpoint);
// Is the device locked?
if (!configStatus.Has(ConfigStatus::kOperational))
{
if (mode.Has(Mode::kMaintenanceMode))
{
// Mainterance Mode
return EMBER_ZCL_STATUS_BUSY;
}
if (mode.Has(Mode::kCalibrationMode))
{
// Calibration Mode
return EMBER_ZCL_STATUS_FAILURE;
}
}
return EMBER_ZCL_STATUS_SUCCESS;
}
void SetDefaultDelegate(EndpointId endpoint, Delegate * delegate)
{
uint16_t ep = emberAfFindClusterServerEndpointIndex(endpoint, Channel::Id);
// if endpoint is found and is not a dynamic endpoint
if (ep != 0xFFFF && ep < EMBER_AF_WINDOW_COVERING_CLUSTER_SERVER_ENDPOINT_COUNT)
{
gDelegateTable[ep] = delegate;
}
else
{
emberAfWindowCoveringClusterPrint("Failed to set WindowCovering delegate for endpoint:%u", endpoint);
}
}
} // namespace WindowCovering
} // namespace Clusters
} // namespace app
} // namespace chip
//------------------------------------------------------------------------------
// Callbacks
//------------------------------------------------------------------------------
/**
* @brief Cluster UpOrOpen Command callback (from client)
*/
bool emberAfWindowCoveringClusterUpOrOpenCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::UpOrOpen::DecodableType & commandData)
{
EndpointId endpoint = commandPath.mEndpointId;
emberAfWindowCoveringClusterPrint("UpOrOpen command received");
EmberAfStatus status = GetMotionLockStatus(endpoint);
if (EMBER_ZCL_STATUS_SUCCESS != status)
{
emberAfWindowCoveringClusterPrint("Err device locked");
emberAfSendImmediateDefaultResponse(status);
return true;
}
if (HasFeature(endpoint, Feature::kPositionAwareLift))
{
Attributes::TargetPositionLiftPercent100ths::Set(endpoint, WC_PERCENT100THS_MIN_OPEN);
}
if (HasFeature(endpoint, Feature::kPositionAwareTilt))
{
Attributes::TargetPositionTiltPercent100ths::Set(endpoint, WC_PERCENT100THS_MIN_OPEN);
}
Delegate * delegate = GetDelegate(endpoint);
if (delegate)
{
if (HasFeature(endpoint, Feature::kPositionAwareLift))
{
LogErrorOnFailure(delegate->HandleMovement(WindowCoveringType::Lift));
}
if (HasFeature(endpoint, Feature::kPositionAwareTilt))
{
LogErrorOnFailure(delegate->HandleMovement(WindowCoveringType::Tilt));
}
}
else
{
emberAfWindowCoveringClusterPrint("WindowCovering has no delegate set for endpoint:%u", endpoint);
}
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
return true;
}
/**
* @brief Cluster DownOrClose Command callback (from client)
*/
bool emberAfWindowCoveringClusterDownOrCloseCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::DownOrClose::DecodableType & commandData)
{
EndpointId endpoint = commandPath.mEndpointId;
emberAfWindowCoveringClusterPrint("DownOrClose command received");
EmberAfStatus status = GetMotionLockStatus(endpoint);
if (EMBER_ZCL_STATUS_SUCCESS != status)
{
emberAfWindowCoveringClusterPrint("Err device locked");
emberAfSendImmediateDefaultResponse(status);
return true;
}
if (HasFeature(endpoint, Feature::kPositionAwareLift))
{
Attributes::TargetPositionLiftPercent100ths::Set(endpoint, WC_PERCENT100THS_MAX_CLOSED);
}
if (HasFeature(endpoint, Feature::kPositionAwareTilt))
{
Attributes::TargetPositionTiltPercent100ths::Set(endpoint, WC_PERCENT100THS_MAX_CLOSED);
}
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
Delegate * delegate = GetDelegate(endpoint);
if (delegate)
{
if (HasFeature(endpoint, Feature::kPositionAwareLift))
{
LogErrorOnFailure(delegate->HandleMovement(WindowCoveringType::Lift));
}
if (HasFeature(endpoint, Feature::kPositionAwareTilt))
{
LogErrorOnFailure(delegate->HandleMovement(WindowCoveringType::Tilt));
}
}
else
{
emberAfWindowCoveringClusterPrint("WindowCovering has no delegate set for endpoint:%u", endpoint);
}
return true;
}
/**
* @brief Cluster StopMotion Command callback (from client)
*/
bool emberAfWindowCoveringClusterStopMotionCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::StopMotion::DecodableType & fields)
{
app::DataModel::Nullable<Percent100ths> current;
chip::EndpointId endpoint = commandPath.mEndpointId;
emberAfWindowCoveringClusterPrint("StopMotion command received");
EmberAfStatus status = GetMotionLockStatus(endpoint);
if (EMBER_ZCL_STATUS_SUCCESS != status)
{
emberAfWindowCoveringClusterPrint("Err device locked");
emberAfSendImmediateDefaultResponse(status);
return true;
}
Delegate * delegate = GetDelegate(endpoint);
if (delegate)
{
LogErrorOnFailure(delegate->HandleStopMotion());
}
else
{
emberAfWindowCoveringClusterPrint("WindowCovering has no delegate set for endpoint:%u", endpoint);
}
if (HasFeaturePaLift(endpoint))
{
(void) Attributes::CurrentPositionLiftPercent100ths::Get(endpoint, current);
(void) Attributes::TargetPositionLiftPercent100ths::Set(endpoint, current);
}
if (HasFeaturePaTilt(endpoint))
{
(void) Attributes::CurrentPositionTiltPercent100ths::Get(endpoint, current);
(void) Attributes::TargetPositionTiltPercent100ths::Set(endpoint, current);
}
return EMBER_SUCCESS == emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
}
/**
* @brief Cluster GoToLiftValue Command callback (from client)
*/
bool emberAfWindowCoveringClusterGoToLiftValueCallback(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath,
const Commands::GoToLiftValue::DecodableType & commandData)
{
auto & liftValue = commandData.liftValue;
EndpointId endpoint = commandPath.mEndpointId;
emberAfWindowCoveringClusterPrint("GoToLiftValue %u command received", liftValue);
EmberAfStatus status = GetMotionLockStatus(endpoint);
if (EMBER_ZCL_STATUS_SUCCESS != status)
{
emberAfWindowCoveringClusterPrint("Err device locked");
emberAfSendImmediateDefaultResponse(status);
return true;
}
if (HasFeature(endpoint, Feature::kAbsolutePosition) && HasFeaturePaLift(endpoint))
{
Attributes::TargetPositionLiftPercent100ths::Set(endpoint, LiftToPercent100ths(endpoint, liftValue));
Delegate * delegate = GetDelegate(endpoint);
if (delegate)
{
LogErrorOnFailure(delegate->HandleMovement(WindowCoveringType::Lift));
}
else
{
emberAfWindowCoveringClusterPrint("WindowCovering has no delegate set for endpoint:%u", endpoint);
}
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
}
else
{
emberAfWindowCoveringClusterPrint("Err Device is not PA LF");
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
}
return true;
}
/**
* @brief Cluster GoToLiftPercentage Command callback (from client)
*/
bool emberAfWindowCoveringClusterGoToLiftPercentageCallback(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath,
const Commands::GoToLiftPercentage::DecodableType & commandData)
{
Percent100ths percent100ths = commandData.liftPercent100thsValue;
EndpointId endpoint = commandPath.mEndpointId;
emberAfWindowCoveringClusterPrint("GoToLiftPercentage %u command received", percent100ths);
EmberAfStatus status = GetMotionLockStatus(endpoint);
if (EMBER_ZCL_STATUS_SUCCESS != status)
{
emberAfWindowCoveringClusterPrint("Err device locked");
emberAfSendImmediateDefaultResponse(status);
return true;
}
if (HasFeaturePaLift(endpoint))
{
if (IsPercent100thsValid(percent100ths))
{
Attributes::TargetPositionLiftPercent100ths::Set(endpoint, percent100ths);
Delegate * delegate = GetDelegate(endpoint);
if (delegate)
{
LogErrorOnFailure(delegate->HandleMovement(WindowCoveringType::Lift));
}
else
{
emberAfWindowCoveringClusterPrint("WindowCovering has no delegate set for endpoint:%u", endpoint);
}
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
}
else
{
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_VALUE);
}
}
else
{
emberAfWindowCoveringClusterPrint("Err Device is not PA LF");
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
}
return true;
}
/**
* @brief Cluster GoToTiltValue Command callback (from client)
*/
bool emberAfWindowCoveringClusterGoToTiltValueCallback(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath,
const Commands::GoToTiltValue::DecodableType & commandData)
{
auto & tiltValue = commandData.tiltValue;
EndpointId endpoint = commandPath.mEndpointId;
emberAfWindowCoveringClusterPrint("GoToTiltValue %u command received", tiltValue);
EmberAfStatus status = GetMotionLockStatus(endpoint);
if (EMBER_ZCL_STATUS_SUCCESS != status)
{
emberAfWindowCoveringClusterPrint("Err device locked");
emberAfSendImmediateDefaultResponse(status);
return true;
}
if (HasFeature(endpoint, Feature::kAbsolutePosition) && HasFeaturePaTilt(endpoint))
{
Attributes::TargetPositionTiltPercent100ths::Set(endpoint, TiltToPercent100ths(endpoint, tiltValue));
Delegate * delegate = GetDelegate(endpoint);
if (delegate)
{
LogErrorOnFailure(delegate->HandleMovement(WindowCoveringType::Tilt));
}
else
{
emberAfWindowCoveringClusterPrint("WindowCovering has no delegate set for endpoint:%u", endpoint);
}
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
}
else
{
emberAfWindowCoveringClusterPrint("Err Device is not PA TL");
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
}
return true;
}
/**
* @brief Cluster GoToTiltPercentage Command callback (from client)
*/
bool emberAfWindowCoveringClusterGoToTiltPercentageCallback(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath,
const Commands::GoToTiltPercentage::DecodableType & commandData)
{
Percent100ths percent100ths = commandData.tiltPercent100thsValue;
EndpointId endpoint = commandPath.mEndpointId;
emberAfWindowCoveringClusterPrint("GoToTiltPercentage %u command received", percent100ths);
EmberAfStatus status = GetMotionLockStatus(endpoint);
if (EMBER_ZCL_STATUS_SUCCESS != status)
{
emberAfWindowCoveringClusterPrint("Err device locked");
emberAfSendImmediateDefaultResponse(status);
return true;
}
if (HasFeaturePaTilt(endpoint))
{
if (IsPercent100thsValid(percent100ths))
{
Attributes::TargetPositionTiltPercent100ths::Set(endpoint, percent100ths);
Delegate * delegate = GetDelegate(endpoint);
if (delegate)
{
LogErrorOnFailure(delegate->HandleMovement(WindowCoveringType::Tilt));
}
else
{
emberAfWindowCoveringClusterPrint("WindowCovering has no delegate set for endpoint:%u", endpoint);
}
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
}
else
{
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_VALUE);
}
}
else
{
emberAfWindowCoveringClusterPrint("Err Device is not PA TL");
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
}
return true;
}
/**
* @brief Cluster Attribute Changed Callback
*
* The method is implemented by default as a weak function and it takes care of updating
* the server attribute values by calling the PostAttributeChange method. If the application overrides
* this method, it needs to handle updating attributes (ideally by calling PostAttributeChange).
*
*/
void __attribute__((weak))
MatterWindowCoveringClusterServerAttributeChangedCallback(const app::ConcreteAttributePath & attributePath)
{
PostAttributeChange(attributePath.mEndpointId, attributePath.mAttributeId);
}
/**
* @brief Cluster Plugin Init Callback
*/
void MatterWindowCoveringPluginServerInitCallback() {}