blob: 9429511432df5d6129fb30c648a8749fd84533d4 [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.
*/
/**
*
* Copyright (c) 2020 Silicon Labs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance 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/command-id.h>
#include <app/CommandHandler.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::app::Clusters::WindowCovering;
#define WC_PERCENT100THS_MAX 10000
static bool HasFeature(chip::EndpointId endpoint, Features feature)
{
return true;
}
static uint16_t ValueToPercent100ths(uint16_t openLimit, uint16_t closedLimit, uint16_t value)
{
uint16_t minimum = 0, range = UINT16_MAX;
if (openLimit > closedLimit)
{
minimum = closedLimit;
range = static_cast<uint16_t>(openLimit - minimum);
}
else
{
minimum = openLimit;
range = static_cast<uint16_t>(closedLimit - minimum);
}
if (value < minimum)
{
return 0;
}
if (range > 0)
{
return static_cast<uint16_t>(WC_PERCENT100THS_MAX * (value - minimum) / range);
}
return WC_PERCENT100THS_MAX;
}
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)
{
minimum = closedLimit;
maximum = openLimit;
}
else
{
minimum = openLimit;
maximum = closedLimit;
}
range = static_cast<uint16_t>(maximum - minimum);
if (percent100ths > WC_PERCENT100THS_MAX)
{
return maximum;
}
return static_cast<uint16_t>(minimum + ((range * percent100ths) / WC_PERCENT100THS_MAX));
}
static OperationalState ValueToOperationalState(uint8_t value)
{
switch (value)
{
case 0x00:
return OperationalState::Stall;
case 0x01:
return OperationalState::MovingUpOrOpen;
case 0x02:
return OperationalState::MovingDownOrClose;
case 0x03:
default:
return OperationalState::Reserved;
}
}
static uint8_t OperationalStateToValue(const OperationalState & state)
{
switch (state)
{
case OperationalState::Stall:
return 0x00;
case OperationalState::MovingUpOrOpen:
return 0x01;
case OperationalState::MovingDownOrClose:
return 0x02;
case OperationalState::Reserved:
default:
return 0x03;
}
}
namespace chip {
namespace app {
namespace Clusters {
namespace WindowCovering {
bool IsOpen(chip::EndpointId endpoint)
{
uint16_t liftPosition = 0;
uint16_t liftLimit = 0;
uint16_t tiltPosition = 0;
uint16_t tiltLimit = 0;
Attributes::GetTargetPositionLiftPercent100ths(endpoint, &liftPosition);
Attributes::GetInstalledOpenLimitLift(endpoint, &liftLimit);
Attributes::GetTargetPositionTiltPercent100ths(endpoint, &tiltPosition);
Attributes::GetInstalledOpenLimitTilt(endpoint, &tiltLimit);
return liftPosition == liftLimit && tiltPosition == tiltLimit;
}
bool IsClosed(chip::EndpointId endpoint)
{
uint16_t liftPosition = 0;
uint16_t liftLimit = 0;
uint16_t tiltPosition = 0;
uint16_t tiltLimit = 0;
Attributes::GetTargetPositionLiftPercent100ths(endpoint, &liftPosition);
Attributes::GetInstalledClosedLimitLift(endpoint, &liftLimit);
Attributes::GetTargetPositionTiltPercent100ths(endpoint, &tiltPosition);
Attributes::GetInstalledClosedLimitTilt(endpoint, &tiltLimit);
return liftPosition == liftLimit && tiltPosition == tiltLimit;
}
void TypeSet(chip::EndpointId endpoint, EmberAfWcType type)
{
Attributes::SetType(endpoint, chip::to_underlying(type));
}
EmberAfWcType TypeGet(chip::EndpointId endpoint)
{
std::underlying_type<EmberAfWcType>::type value;
Attributes::GetType(endpoint, &value);
return static_cast<EmberAfWcType>(value);
}
void ConfigStatusSet(chip::EndpointId endpoint, const ConfigStatus & status)
{
uint8_t value = (status.operational ? 0x01 : 0) | (status.online ? 0x02 : 0) | (status.liftIsReversed ? 0x04 : 0) |
(status.liftIsPA ? 0x08 : 0) | (status.tiltIsPA ? 0x10 : 0) | (status.liftIsEncoderControlled ? 0x20 : 0) |
(status.tiltIsEncoderControlled ? 0x40 : 0);
Attributes::SetConfigStatus(endpoint, value);
}
const ConfigStatus ConfigStatusGet(chip::EndpointId endpoint)
{
uint8_t value = 0;
ConfigStatus status;
Attributes::GetConfigStatus(endpoint, &value);
status.operational = (value & 0x01) ? 1 : 0;
status.online = (value & 0x02) ? 1 : 0;
status.liftIsReversed = (value & 0x04) ? 1 : 0;
status.liftIsPA = (value & 0x08) ? 1 : 0;
status.tiltIsPA = (value & 0x10) ? 1 : 0;
status.liftIsEncoderControlled = (value & 0x20) ? 1 : 0;
status.tiltIsEncoderControlled = (value & 0x40) ? 1 : 0;
return status;
}
void OperationalStatusSet(chip::EndpointId endpoint, const OperationalStatus & status)
{
uint8_t global = OperationalStateToValue(status.global);
uint8_t lift = OperationalStateToValue(status.lift);
uint8_t tilt = OperationalStateToValue(status.tilt);
uint8_t value = (global & 0x03) | static_cast<uint8_t>((lift & 0x03) << 2) | static_cast<uint8_t>((tilt & 0x03) << 4);
Attributes::SetOperationalStatus(endpoint, value);
}
const OperationalStatus OperationalStatusGet(chip::EndpointId endpoint)
{
uint8_t value = 0;
OperationalStatus status;
Attributes::GetOperationalStatus(endpoint, &value);
status.global = ValueToOperationalState(value & 0x03);
status.lift = ValueToOperationalState((value >> 2) & 0x03);
status.tilt = ValueToOperationalState((value >> 4) & 0x03);
return status;
}
void EndProductTypeSet(chip::EndpointId endpoint, EmberAfWcEndProductType type)
{
Attributes::SetEndProductType(endpoint, chip::to_underlying(type));
}
EmberAfWcEndProductType EndProductTypeGet(chip::EndpointId endpoint)
{
std::underlying_type<EmberAfWcType>::type value;
Attributes::GetEndProductType(endpoint, &value);
return static_cast<EmberAfWcEndProductType>(value);
}
void ModeSet(chip::EndpointId endpoint, const Mode & mode)
{
uint8_t value = (mode.motorDirReversed ? 0x01 : 0) | (mode.calibrationMode ? 0x02 : 0) | (mode.maintenanceMode ? 0x04 : 0) |
(mode.ledDisplay ? 0x08 : 0);
Attributes::SetMode(endpoint, value);
}
const Mode ModeGet(chip::EndpointId endpoint)
{
uint8_t value = 0;
Mode mode;
Attributes::GetMode(endpoint, &value);
mode.motorDirReversed = (value & 0x01) ? 1 : 0;
mode.calibrationMode = (value & 0x02) ? 1 : 0;
mode.maintenanceMode = (value & 0x04) ? 1 : 0;
mode.ledDisplay = (value & 0x08) ? 1 : 0;
return mode;
}
void SafetyStatusSet(chip::EndpointId endpoint, SafetyStatus & status)
{
uint16_t value = (status.remoteLockout ? 0x0001 : 0) | (status.tamperDetection ? 0x0002 : 0) |
(status.failedCommunication ? 0x0004 : 0) | (status.positionFailure ? 0x0008 : 0) |
(status.thermalProtection ? 0x0010 : 0) | (status.obstacleDetected ? 0x0020 : 0) | (status.powerIssue ? 0x0040 : 0) |
(status.stopInput ? 0x0080 : 0);
value |= (uint16_t)(status.motorJammed ? 0x0100 : 0) | (uint16_t)(status.hardwareFailure ? 0x0200 : 0) |
(uint16_t)(status.manualOperation ? 0x0400 : 0);
Attributes::SetSafetyStatus(endpoint, value);
}
const SafetyStatus SafetyStatusGet(chip::EndpointId endpoint)
{
uint16_t value = 0;
SafetyStatus status;
Attributes::GetSafetyStatus(endpoint, &value);
status.remoteLockout = (value & 0x0001) ? 1 : 0;
status.tamperDetection = (value & 0x0002) ? 1 : 0;
status.failedCommunication = (value & 0x0004) ? 1 : 0;
status.positionFailure = (value & 0x0008) ? 1 : 0;
status.thermalProtection = (value & 0x0010) ? 1 : 0;
status.obstacleDetected = (value & 0x0020) ? 1 : 0;
status.powerIssue = (value & 0x0040) ? 1 : 0;
status.stopInput = (value & 0x0080) ? 1 : 0;
status.motorJammed = (value & 0x0100) ? 1 : 0;
status.hardwareFailure = (value & 0x0200) ? 1 : 0;
status.manualOperation = (value & 0x0400) ? 1 : 0;
return status;
}
uint16_t LiftToPercent100ths(chip::EndpointId endpoint, uint16_t lift)
{
uint16_t openLimit = 0;
uint16_t closedLimit = 0;
Attributes::GetInstalledOpenLimitLift(endpoint, &openLimit);
Attributes::GetInstalledClosedLimitLift(endpoint, &closedLimit);
return ValueToPercent100ths(openLimit, closedLimit, lift);
}
uint16_t Percent100thsToLift(chip::EndpointId endpoint, uint16_t percent100ths)
{
uint16_t openLimit = 0;
uint16_t closedLimit = 0;
Attributes::GetInstalledOpenLimitLift(endpoint, &openLimit);
Attributes::GetInstalledClosedLimitLift(endpoint, &closedLimit);
return Percent100thsToValue(openLimit, closedLimit, percent100ths);
}
void LiftPositionSet(chip::EndpointId endpoint, uint16_t percent100ths)
{
uint8_t percent = static_cast<uint8_t>(percent100ths / 100);
uint16_t lift = Percent100thsToLift(endpoint, percent100ths);
Attributes::SetCurrentPositionLift(endpoint, lift);
Attributes::SetCurrentPositionLiftPercentage(endpoint, percent);
Attributes::SetCurrentPositionLiftPercent100ths(endpoint, percent100ths);
emberAfWindowCoveringClusterPrint("Lift Position Set: %u%%", percent);
}
uint16_t TiltToPercent100ths(chip::EndpointId endpoint, uint16_t tilt)
{
uint16_t openLimit = 0;
uint16_t closedLimit = 0;
Attributes::GetInstalledOpenLimitTilt(endpoint, &openLimit);
Attributes::GetInstalledClosedLimitTilt(endpoint, &closedLimit);
return ValueToPercent100ths(openLimit, closedLimit, tilt);
}
uint16_t Percent100thsToTilt(chip::EndpointId endpoint, uint16_t percent100ths)
{
uint16_t openLimit = 0;
uint16_t closedLimit = 0;
Attributes::GetInstalledOpenLimitTilt(endpoint, &openLimit);
Attributes::GetInstalledClosedLimitTilt(endpoint, &closedLimit);
return Percent100thsToValue(openLimit, closedLimit, percent100ths);
}
void TiltPositionSet(chip::EndpointId endpoint, uint16_t percent100ths)
{
uint8_t percent = static_cast<uint8_t>(percent100ths / 100);
uint16_t tilt = Percent100thsToTilt(endpoint, percent100ths);
Attributes::SetCurrentPositionTilt(endpoint, tilt);
Attributes::SetCurrentPositionTiltPercentage(endpoint, percent);
Attributes::SetCurrentPositionTiltPercent100ths(endpoint, percent100ths);
emberAfWindowCoveringClusterPrint("Tilt Position Set: %u%%", percent);
}
} // namespace WindowCovering
} // namespace Clusters
} // namespace app
} // namespace chip
//------------------------------------------------------------------------------
// Callbacks
//------------------------------------------------------------------------------
/** @brief Window Covering Cluster Init
*
* Cluster Init
*
* @param endpoint Endpoint that is being initialized
*/
void emberAfWindowCoveringClusterInitCallback(chip::EndpointId endpoint)
{
emberAfWindowCoveringClusterPrint("Window Covering Cluster init");
}
/**
* @brief Cluster UpOrOpen Command callback (from client)
*/
bool emberAfWindowCoveringClusterUpOrOpenCallback(chip::EndpointId endpoint, chip::app::CommandHandler * commandObj)
{
emberAfWindowCoveringClusterPrint("UpOrOpen command received");
if (HasFeature(endpoint, Features::Lift))
{
Attributes::SetTargetPositionLiftPercent100ths(endpoint, 0);
}
if (HasFeature(endpoint, Features::Tilt))
{
Attributes::SetTargetPositionTiltPercent100ths(endpoint, 0);
}
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
return true;
}
/**
* @brief Cluster DownOrClose Command callback (from client)
*/
bool emberAfWindowCoveringClusterDownOrCloseCallback(chip::EndpointId endpoint, chip::app::CommandHandler * commandObj)
{
emberAfWindowCoveringClusterPrint("DownOrClose command received");
if (HasFeature(endpoint, Features::Lift))
{
Attributes::SetTargetPositionLiftPercent100ths(endpoint, WC_PERCENT100THS_MAX);
}
if (HasFeature(endpoint, Features::Tilt))
{
Attributes::SetTargetPositionTiltPercent100ths(endpoint, WC_PERCENT100THS_MAX);
}
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
return true;
}
/**
* @brief Cluster StopMotion Command callback (from client)
*/
bool __attribute__((weak))
emberAfWindowCoveringClusterStopMotionCallback(chip::EndpointId endpoint, chip::app::CommandHandler * commandObj)
{
emberAfWindowCoveringClusterPrint("StopMotion command received");
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
return true;
}
/**
* @brief Cluster GoToLiftValue Command callback (from client)
*/
bool emberAfWindowCoveringClusterGoToLiftValueCallback(chip::EndpointId endpoint, chip::app::CommandHandler * commandObj,
uint16_t liftValue)
{
bool hasLift = HasFeature(endpoint, Features::Lift);
bool isPositionAware = HasFeature(endpoint, Features::PositionAware);
emberAfWindowCoveringClusterPrint("GoToLiftValue Value command received");
if (hasLift && isPositionAware)
{
Attributes::SetTargetPositionLiftPercent100ths(endpoint, LiftToPercent100ths(endpoint, liftValue));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
}
else
{
emberAfWindowCoveringClusterPrint("Err Device is not PA=%u or LF=%u", isPositionAware, hasLift);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_ACTION_DENIED);
}
return true;
}
/**
* @brief Cluster GoToLiftPercentage Command callback (from client)
*/
bool emberAfWindowCoveringClusterGoToLiftPercentageCallback(chip::EndpointId endpoint, chip::app::CommandHandler * commandObj,
uint8_t liftPercentageValue, uint16_t liftPercent100thsValue)
{
bool hasLift = HasFeature(endpoint, Features::Lift);
bool isPositionAware = HasFeature(endpoint, Features::PositionAware);
emberAfWindowCoveringClusterPrint("GoToLiftPercentage Percentage command received");
if (hasLift && isPositionAware)
{
Attributes::SetTargetPositionLiftPercent100ths(
endpoint, static_cast<uint16_t>(liftPercentageValue > 100 ? liftPercent100thsValue : liftPercentageValue * 100));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
}
else
{
emberAfWindowCoveringClusterPrint("Err Device is not PA=%u or LF=%u", isPositionAware, hasLift);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_ACTION_DENIED);
}
return true;
}
/**
* @brief Cluster GoToTiltValue Command callback (from client)
*/
bool emberAfWindowCoveringClusterGoToTiltValueCallback(chip::EndpointId endpoint, chip::app::CommandHandler * commandObj,
uint16_t tiltValue)
{
bool hasTilt = HasFeature(endpoint, Features::Tilt);
bool isPositionAware = HasFeature(endpoint, Features::PositionAware);
emberAfWindowCoveringClusterPrint("GoToTiltValue command received");
if (hasTilt && isPositionAware)
{
Attributes::SetTargetPositionTiltPercent100ths(endpoint, TiltToPercent100ths(endpoint, tiltValue));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
}
else
{
emberAfWindowCoveringClusterPrint("Err Device is not PA=%u or TL=%u", isPositionAware, hasTilt);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_ACTION_DENIED);
}
return true;
}
/**
* @brief Cluster GoToTiltPercentage Command callback (from client)
*/
bool emberAfWindowCoveringClusterGoToTiltPercentageCallback(chip::EndpointId endpoint, chip::app::CommandHandler * commandObj,
uint8_t tiltPercentageValue, uint16_t tiltPercent100thsValue)
{
bool hasTilt = HasFeature(endpoint, Features::Tilt);
bool isPositionAware = HasFeature(endpoint, Features::PositionAware);
emberAfWindowCoveringClusterPrint("GoToTiltPercentage command received");
if (hasTilt && isPositionAware)
{
Attributes::SetTargetPositionTiltPercent100ths(
endpoint, static_cast<uint16_t>(tiltPercentageValue > 100 ? tiltPercent100thsValue : tiltPercentageValue * 100));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
}
else
{
emberAfWindowCoveringClusterPrint("Err Device is not PA=%u or TL=%u", isPositionAware, hasTilt);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_ACTION_DENIED);
}
return true;
}