blob: a8cae9d37e597b1df9a76d7c6065e1c66a91895f [file] [log] [blame]
/*
* Copyright (c) 2023 Project CHIP Authors
* All rights reserved.
*
* 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 "WindowCovering.h"
#include "AppConfig.h"
#include "PWMDevice.h"
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app/util/af.h>
#include <platform/CHIPDeviceLayer.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL);
using namespace ::chip::Credentials;
using namespace ::chip::DeviceLayer;
using namespace chip::app::Clusters::WindowCovering;
static const struct pwm_dt_spec sLiftPwmDevice = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led0));
static const struct pwm_dt_spec sTiltPwmDevice = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led1));
static constexpr uint32_t sMoveTimeoutMs{ 200 };
constexpr uint16_t sPercentDelta = 500;
constexpr uint8_t kDefaultMinLevel = 0;
constexpr uint8_t kDefaultMaxLevel = 254;
WindowCovering::WindowCovering()
{
if (mLiftIndicator.Init(&sLiftPwmDevice, kDefaultMinLevel, kDefaultMaxLevel, kDefaultMinLevel) != CHIP_NO_ERROR)
{
LOG_ERR("Cannot initialize the lift indicator");
}
if (mTiltIndicator.Init(&sTiltPwmDevice, kDefaultMinLevel, kDefaultMaxLevel, kDefaultMinLevel) != CHIP_NO_ERROR)
{
LOG_ERR("Cannot initialize the tilt indicator");
}
mLiftIndicator.InitiateAction(PWMDevice::ON_ACTION, 0, nullptr);
mTiltIndicator.InitiateAction(PWMDevice::ON_ACTION, 0, nullptr);
}
void WindowCovering::DriveCurrentLiftPosition(intptr_t)
{
NPercent100ths current{};
NPercent100ths target{};
NPercent100ths positionToSet{};
VerifyOrReturn(Attributes::CurrentPositionLiftPercent100ths::Get(Endpoint(), current) == EMBER_ZCL_STATUS_SUCCESS);
VerifyOrReturn(Attributes::TargetPositionLiftPercent100ths::Get(Endpoint(), target) == EMBER_ZCL_STATUS_SUCCESS);
UpdateOperationalStatus(WindowCoveringType::Lift, ComputeOperationalState(target, current));
positionToSet.SetNonNull(CalculateSingleStep(WindowCoveringType::Lift));
LiftPositionSet(Endpoint(), positionToSet);
// assume single move completed
Instance().mInLiftMove = false;
VerifyOrReturn(Attributes::CurrentPositionLiftPercent100ths::Get(Endpoint(), current) == EMBER_ZCL_STATUS_SUCCESS);
if (!TargetCompleted(WindowCoveringType::Lift, current, target))
{
// continue to move
StartTimer(WindowCoveringType::Lift, sMoveTimeoutMs);
}
else
{
// the OperationalStatus should indicate no-lift-movement after the target is completed
UpdateOperationalStatus(WindowCoveringType::Lift, ComputeOperationalState(target, current));
}
}
chip::Percent100ths WindowCovering::CalculateSingleStep(WindowCoveringType aMoveType)
{
EmberAfStatus status{};
chip::Percent100ths percent100ths{};
NPercent100ths current{};
OperationalState opState = OperationalState::Stall;
if (aMoveType == WindowCoveringType::Lift)
{
status = Attributes::CurrentPositionLiftPercent100ths::Get(Endpoint(), current);
opState = OperationalStateGet(Endpoint(), OperationalStatus::kLift);
}
else if (aMoveType == WindowCoveringType::Tilt)
{
status = Attributes::CurrentPositionTiltPercent100ths::Get(Endpoint(), current);
opState = OperationalStateGet(Endpoint(), OperationalStatus::kTilt);
}
if ((status == EMBER_ZCL_STATUS_SUCCESS) && !current.IsNull())
{
percent100ths = ComputePercent100thsStep(opState, current.Value(), sPercentDelta);
}
else
{
LOG_ERR("Cannot read the current lift position. Error: %d", static_cast<uint8_t>(status));
}
return percent100ths;
}
bool WindowCovering::TargetCompleted(WindowCoveringType aMoveType, NPercent100ths aCurrent, NPercent100ths aTarget)
{
return (OperationalState::Stall == ComputeOperationalState(aTarget, aCurrent));
}
void WindowCovering::StartTimer(WindowCoveringType aMoveType, uint32_t aTimeoutMs)
{
WindowCoveringType * moveType = chip::Platform::New<WindowCoveringType>();
VerifyOrReturn(moveType != nullptr);
*moveType = aMoveType;
(void) chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Milliseconds32(aTimeoutMs), MoveTimerTimeoutCallback,
reinterpret_cast<void *>(moveType));
}
void WindowCovering::MoveTimerTimeoutCallback(chip::System::Layer * systemLayer, void * appState)
{
WindowCoveringType * moveType = reinterpret_cast<WindowCoveringType *>(appState);
VerifyOrReturn(moveType != nullptr);
if (*moveType == WindowCoveringType::Lift)
{
chip::DeviceLayer::PlatformMgr().ScheduleWork(WindowCovering::DriveCurrentLiftPosition);
}
else if (*moveType == WindowCoveringType::Tilt)
{
chip::DeviceLayer::PlatformMgr().ScheduleWork(WindowCovering::DriveCurrentTiltPosition);
}
chip::Platform::Delete(moveType);
}
void WindowCovering::DriveCurrentTiltPosition(intptr_t)
{
NPercent100ths current{};
NPercent100ths target{};
NPercent100ths positionToSet{};
VerifyOrReturn(Attributes::CurrentPositionTiltPercent100ths::Get(Endpoint(), current) == EMBER_ZCL_STATUS_SUCCESS);
VerifyOrReturn(Attributes::TargetPositionTiltPercent100ths::Get(Endpoint(), target) == EMBER_ZCL_STATUS_SUCCESS);
UpdateOperationalStatus(WindowCoveringType::Lift, ComputeOperationalState(target, current));
positionToSet.SetNonNull(CalculateSingleStep(WindowCoveringType::Tilt));
TiltPositionSet(Endpoint(), positionToSet);
// assume single move completed
Instance().mInTiltMove = false;
VerifyOrReturn(Attributes::CurrentPositionTiltPercent100ths::Get(Endpoint(), current) == EMBER_ZCL_STATUS_SUCCESS);
if (!TargetCompleted(WindowCoveringType::Tilt, current, target))
{
// continue to move
StartTimer(WindowCoveringType::Tilt, sMoveTimeoutMs);
}
else
{
// the OperationalStatus should indicate no-tilt-movement after the target is completed
UpdateOperationalStatus(WindowCoveringType::Tilt, ComputeOperationalState(target, current));
}
}
void WindowCovering::StartMove(WindowCoveringType aMoveType)
{
switch (aMoveType)
{
case WindowCoveringType::Lift:
if (!mInLiftMove)
{
mInLiftMove = true;
StartTimer(aMoveType, sMoveTimeoutMs);
}
break;
case WindowCoveringType::Tilt:
if (!mInTiltMove)
{
mInTiltMove = true;
StartTimer(aMoveType, sMoveTimeoutMs);
}
break;
default:
break;
};
}
void WindowCovering::SetSingleStepTarget(OperationalState aDirection)
{
UpdateOperationalStatus(mCurrentUIMoveType, aDirection);
SetTargetPosition(aDirection, CalculateSingleStep(mCurrentUIMoveType));
}
void WindowCovering::UpdateOperationalStatus(WindowCoveringType aMoveType, OperationalState aDirection)
{
switch (aMoveType)
{
case WindowCoveringType::Lift:
OperationalStateSet(Endpoint(), OperationalStatus::kLift, aDirection);
break;
case WindowCoveringType::Tilt:
OperationalStateSet(Endpoint(), OperationalStatus::kTilt, aDirection);
break;
case WindowCoveringType::Reserved:
break;
default:
break;
}
}
void WindowCovering::SetTargetPosition(OperationalState aDirection, chip::Percent100ths aPosition)
{
EmberAfStatus status{};
if (Instance().mCurrentUIMoveType == WindowCoveringType::Lift)
{
status = Attributes::TargetPositionLiftPercent100ths::Set(Endpoint(), aPosition);
}
else if (Instance().mCurrentUIMoveType == WindowCoveringType::Tilt)
{
status = Attributes::TargetPositionTiltPercent100ths::Set(Endpoint(), aPosition);
}
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
LOG_ERR("Cannot set the target position. Error: %d", static_cast<uint8_t>(status));
}
}
void WindowCovering::PositionLEDUpdate(WindowCoveringType aMoveType)
{
EmberAfStatus status{};
NPercent100ths currentPosition{};
if (aMoveType == WindowCoveringType::Lift)
{
status = Attributes::CurrentPositionLiftPercent100ths::Get(Endpoint(), currentPosition);
if (EMBER_ZCL_STATUS_SUCCESS == status && !currentPosition.IsNull())
{
Instance().SetBrightness(WindowCoveringType::Lift, currentPosition.Value());
}
}
else if (aMoveType == WindowCoveringType::Tilt)
{
status = Attributes::CurrentPositionTiltPercent100ths::Get(Endpoint(), currentPosition);
if (EMBER_ZCL_STATUS_SUCCESS == status && !currentPosition.IsNull())
{
Instance().SetBrightness(WindowCoveringType::Tilt, currentPosition.Value());
}
}
}
void WindowCovering::SetBrightness(WindowCoveringType aMoveType, uint16_t aPosition)
{
uint8_t brightness = PositionToBrightness(aPosition, aMoveType);
if (aMoveType == WindowCoveringType::Lift)
{
mLiftIndicator.InitiateAction(PWMDevice::LEVEL_ACTION, 0, &brightness);
}
else if (aMoveType == WindowCoveringType::Tilt)
{
mTiltIndicator.InitiateAction(PWMDevice::LEVEL_ACTION, 0, &brightness);
}
}
uint8_t WindowCovering::PositionToBrightness(uint16_t aPosition, WindowCoveringType aMoveType)
{
AbsoluteLimits pwmLimits{};
if (aMoveType == WindowCoveringType::Lift)
{
pwmLimits.open = mLiftIndicator.GetMinLevel();
pwmLimits.closed = mLiftIndicator.GetMaxLevel();
}
else if (aMoveType == WindowCoveringType::Tilt)
{
pwmLimits.open = mTiltIndicator.GetMinLevel();
pwmLimits.closed = mTiltIndicator.GetMaxLevel();
}
return Percent100thsToValue(pwmLimits, aPosition);
}
void WindowCovering::SchedulePostAttributeChange(chip::EndpointId aEndpoint, chip::AttributeId aAttributeId)
{
AttributeUpdateData * data = chip::Platform::New<AttributeUpdateData>();
VerifyOrReturn(data != nullptr);
data->mEndpoint = aEndpoint;
data->mAttributeId = aAttributeId;
chip::DeviceLayer::PlatformMgr().ScheduleWork(DoPostAttributeChange, reinterpret_cast<intptr_t>(data));
}
void WindowCovering::DoPostAttributeChange(intptr_t aArg)
{
AttributeUpdateData * data = reinterpret_cast<AttributeUpdateData *>(aArg);
VerifyOrReturn(data != nullptr);
PostAttributeChange(data->mEndpoint, data->mAttributeId);
}