blob: a11104fe2ad89bb9be4d1370b1c43249b1430478 [file] [log] [blame]
/*
* Copyright (c) 2022 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 <dk_buttons_and_leds.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_led1));
static const struct pwm_dt_spec sTiltPwmDevice = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led2));
static constexpr uint32_t sMoveTimeoutMs{ 200 };
WindowCovering::WindowCovering()
{
mLiftLED.Init(LIFT_STATE_LED);
mTiltLED.Init(TILT_STATE_LED);
if (mLiftIndicator.Init(&sLiftPwmDevice, 0, 255) != 0)
{
LOG_ERR("Cannot initialize the lift indicator");
}
if (mTiltIndicator.Init(&sTiltPwmDevice, 0, 255) != 0)
{
LOG_ERR("Cannot initialize the tilt indicator");
}
}
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);
OperationalState state = ComputeOperationalState(target, current);
UpdateOperationalStatus(MoveType::LIFT, state);
chip::Percent100ths position = CalculateNextPosition(MoveType::LIFT);
if (state == OperationalState::MovingUpOrOpen)
{
positionToSet.SetNonNull(position > target.Value() ? position : target.Value());
}
else if (state == OperationalState::MovingDownOrClose)
{
positionToSet.SetNonNull(position < target.Value() ? position : target.Value());
}
else
{
positionToSet.SetNonNull(current.Value());
}
LiftPositionSet(Endpoint(), positionToSet);
// assume single move completed
Instance().mInLiftMove = false;
VerifyOrReturn(Attributes::CurrentPositionLiftPercent100ths::Get(Endpoint(), current) == EMBER_ZCL_STATUS_SUCCESS);
if (!TargetCompleted(MoveType::LIFT, current, target))
{
// continue to move
StartTimer(MoveType::LIFT, sMoveTimeoutMs);
}
else
{
// the OperationalStatus should indicate no-lift-movement after the target is completed
UpdateOperationalStatus(MoveType::LIFT, ComputeOperationalState(target, current));
}
}
chip::Percent100ths WindowCovering::CalculateNextPosition(MoveType aMoveType)
{
EmberAfStatus status{};
chip::Percent100ths percent100ths{};
NPercent100ths current{};
OperationalState opState{};
if (aMoveType == MoveType::LIFT)
{
status = Attributes::CurrentPositionLiftPercent100ths::Get(Endpoint(), current);
opState = OperationalStateGet(Endpoint(), OperationalStatus::kLift);
}
else if (aMoveType == MoveType::TILT)
{
status = Attributes::CurrentPositionTiltPercent100ths::Get(Endpoint(), current);
opState = OperationalStateGet(Endpoint(), OperationalStatus::kTilt);
}
if ((status == EMBER_ZCL_STATUS_SUCCESS) && !current.IsNull())
{
static constexpr auto sPercentDelta{ WC_PERCENT100THS_MAX_CLOSED / 20 };
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(MoveType aMoveType, NPercent100ths aCurrent, NPercent100ths aTarget)
{
return (OperationalState::Stall == ComputeOperationalState(aTarget, aCurrent));
}
void WindowCovering::StartTimer(MoveType aMoveType, uint32_t aTimeoutMs)
{
MoveType * moveType = chip::Platform::New<MoveType>();
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)
{
MoveType * moveType = reinterpret_cast<MoveType *>(appState);
VerifyOrReturn(moveType != nullptr);
if (*moveType == MoveType::LIFT)
{
chip::DeviceLayer::PlatformMgr().ScheduleWork(WindowCovering::DriveCurrentLiftPosition);
}
else if (*moveType == MoveType::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);
OperationalState state = ComputeOperationalState(target, current);
UpdateOperationalStatus(MoveType::TILT, state);
chip::Percent100ths position = CalculateNextPosition(MoveType::TILT);
if (state == OperationalState::MovingUpOrOpen)
{
positionToSet.SetNonNull(position > target.Value() ? position : target.Value());
}
else if (state == OperationalState::MovingDownOrClose)
{
positionToSet.SetNonNull(position < target.Value() ? position : target.Value());
}
else
{
positionToSet.SetNonNull(current.Value());
}
TiltPositionSet(Endpoint(), positionToSet);
// assume single move completed
Instance().mInTiltMove = false;
VerifyOrReturn(Attributes::CurrentPositionTiltPercent100ths::Get(Endpoint(), current) == EMBER_ZCL_STATUS_SUCCESS);
if (!TargetCompleted(MoveType::TILT, current, target))
{
// continue to move
StartTimer(MoveType::TILT, sMoveTimeoutMs);
}
else
{
// the OperationalStatus should indicate no-tilt-movement after the target is completed
UpdateOperationalStatus(MoveType::TILT, ComputeOperationalState(target, current));
}
}
void WindowCovering::StartMove(MoveType aMoveType)
{
switch (aMoveType)
{
case MoveType::LIFT:
if (!mInLiftMove)
{
mInLiftMove = true;
StartTimer(aMoveType, sMoveTimeoutMs);
}
break;
case MoveType::TILT:
if (!mInTiltMove)
{
mInTiltMove = true;
StartTimer(aMoveType, sMoveTimeoutMs);
}
break;
default:
break;
};
}
void WindowCovering::SetSingleStepTarget(OperationalState aDirection)
{
UpdateOperationalStatus(mCurrentUIMoveType, aDirection);
SetTargetPosition(aDirection, CalculateNextPosition(mCurrentUIMoveType));
}
void WindowCovering::UpdateOperationalStatus(MoveType aMoveType, OperationalState aDirection)
{
switch (aMoveType)
{
case MoveType::LIFT:
OperationalStateSet(Endpoint(), OperationalStatus::kLift, aDirection);
break;
case MoveType::TILT:
OperationalStateSet(Endpoint(), OperationalStatus::kTilt, aDirection);
break;
case MoveType::NONE:
break;
default:
break;
}
}
void WindowCovering::SetTargetPosition(OperationalState aDirection, chip::Percent100ths aPosition)
{
EmberAfStatus status{};
if (Instance().mCurrentUIMoveType == MoveType::LIFT)
{
status = Attributes::TargetPositionLiftPercent100ths::Set(Endpoint(), aPosition);
}
else if (Instance().mCurrentUIMoveType == MoveType::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(MoveType aMoveType)
{
EmberAfStatus status{};
NPercent100ths currentPosition{};
if (aMoveType == MoveType::LIFT)
{
status = Attributes::CurrentPositionLiftPercent100ths::Get(Endpoint(), currentPosition);
if (EMBER_ZCL_STATUS_SUCCESS == status && !currentPosition.IsNull())
{
Instance().SetBrightness(MoveType::LIFT, currentPosition.Value());
}
}
else if (aMoveType == MoveType::TILT)
{
status = Attributes::CurrentPositionTiltPercent100ths::Get(Endpoint(), currentPosition);
if (EMBER_ZCL_STATUS_SUCCESS == status && !currentPosition.IsNull())
{
Instance().SetBrightness(MoveType::TILT, currentPosition.Value());
}
}
}
void WindowCovering::SetBrightness(MoveType aMoveType, uint16_t aPosition)
{
uint8_t brightness = PositionToBrightness(aPosition, aMoveType);
if (aMoveType == MoveType::LIFT)
{
mLiftIndicator.InitiateAction(PWMDevice::LEVEL_ACTION, 0, &brightness);
}
else if (aMoveType == MoveType::TILT)
{
mTiltIndicator.InitiateAction(PWMDevice::LEVEL_ACTION, 0, &brightness);
}
}
uint8_t WindowCovering::PositionToBrightness(uint16_t aPosition, MoveType aMoveType)
{
AbsoluteLimits pwmLimits{};
if (aMoveType == MoveType::LIFT)
{
pwmLimits.open = mLiftIndicator.GetMinLevel();
pwmLimits.closed = mLiftIndicator.GetMaxLevel();
}
else if (aMoveType == MoveType::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);
chip::Platform::Delete(data);
}