blob: d69e128257e9f2dad9df887cab42976df48350eb [file] [log] [blame]
/*
*
* Copyright (c) 2021 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 <AppConfig.h>
#include <WindowApp.h>
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app/clusters/identify-server/identify-server.h>
#include <app/clusters/window-covering-server/window-covering-server.h>
#include <app/server/Server.h>
#include <app/util/af.h>
#include <credentials/DeviceAttestationCredsProvider.h>
#include <credentials/examples/DeviceAttestationCredsExample.h>
#include <platform/CHIPDeviceLayer.h>
using namespace ::chip::Credentials;
using namespace ::chip::DeviceLayer;
using namespace chip::app::Clusters::WindowCovering;
inline void OnTriggerEffectCompleted(chip::System::Layer * systemLayer, void * appState)
{
WindowApp::Instance().PostEvent(WindowApp::EventId::WinkOff);
}
void OnTriggerEffect(Identify * identify)
{
EmberAfIdentifyEffectIdentifier sIdentifyEffect = identify->mCurrentEffectIdentifier;
ChipLogProgress(Zcl, "IDENTFY OnTriggerEffect");
if (identify->mCurrentEffectIdentifier == EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_CHANNEL_CHANGE)
{
ChipLogProgress(Zcl, "IDENTIFY_EFFECT_IDENTIFIER_CHANNEL_CHANGE - Not supported, use effect varriant %d",
identify->mEffectVariant);
sIdentifyEffect = static_cast<EmberAfIdentifyEffectIdentifier>(identify->mEffectVariant);
}
switch (sIdentifyEffect)
{
case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BLINK:
case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BREATHE:
case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_OKAY:
WindowApp::Instance().PostEvent(WindowApp::EventId::WinkOn);
(void) chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds16(5), OnTriggerEffectCompleted, identify);
break;
case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_FINISH_EFFECT:
case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_STOP_EFFECT:
(void) chip::DeviceLayer::SystemLayer().CancelTimer(OnTriggerEffectCompleted, identify);
break;
default:
ChipLogProgress(Zcl, "No identifier effect");
}
}
Identify gIdentify = {
chip::EndpointId{ 1 },
[](Identify *) { ChipLogProgress(Zcl, "onIdentifyStart"); },
[](Identify *) { ChipLogProgress(Zcl, "onIdentifyStop"); },
EMBER_ZCL_IDENTIFY_IDENTIFY_TYPE_VISIBLE_LED,
OnTriggerEffect,
};
void WindowApp::Timer::Timeout()
{
mIsActive = false;
if (mCallback)
{
mCallback(*this);
}
}
void WindowApp::Button::Press()
{
EventId event = Button::Id::Up == mId ? EventId::UpPressed : EventId::DownPressed;
Instance().PostEvent(WindowApp::Event(event));
}
void WindowApp::Button::Release()
{
Instance().PostEvent(Button::Id::Up == mId ? EventId::UpReleased : EventId::DownReleased);
}
WindowApp::Cover & WindowApp::GetCover()
{
return mCoverList[mCurrentCover];
}
WindowApp::Cover * WindowApp::GetCover(chip::EndpointId endpoint)
{
for (uint16_t i = 0; i < WINDOW_COVER_COUNT; ++i)
{
if (mCoverList[i].mEndpoint == endpoint)
{
return &mCoverList[i];
}
}
return nullptr;
}
CHIP_ERROR WindowApp::Init()
{
// Init ZCL Data Model
chip::Server::GetInstance().Init();
// Initialize device attestation config
SetDeviceAttestationCredentialsProvider(Examples::GetExampleDACProvider());
ConfigurationMgr().LogDeviceConfig();
// Timers
mLongPressTimer = CreateTimer("Timer:LongPress", LONG_PRESS_TIMEOUT, OnLongPressTimeout, this);
// Buttons
mButtonUp = CreateButton(Button::Id::Up, "UP");
mButtonDown = CreateButton(Button::Id::Down, "DOWN");
// Coverings
mCoverList[0].Init(WINDOW_COVER_ENDPOINT1);
mCoverList[1].Init(WINDOW_COVER_ENDPOINT2);
return CHIP_NO_ERROR;
}
CHIP_ERROR WindowApp::Run()
{
StateFlags oldState;
#if CHIP_ENABLE_OPENTHREAD
oldState.isThreadProvisioned = !ConnectivityMgr().IsThreadProvisioned();
#else
oldState.isWiFiProvisioned = !ConnectivityMgr().IsWiFiStationProvisioned();
#endif
while (true)
{
ProcessEvents();
// Collect connectivity and configuration state from the CHIP stack. Because
// the CHIP event loop is being run in a separate task, the stack must be
// locked while these values are queried. However we use a non-blocking
// lock request (TryLockCHIPStack()) to avoid blocking other UI activities
// when the CHIP task is busy (e.g. with a long crypto operation).
if (PlatformMgr().TryLockChipStack())
{
#if CHIP_ENABLE_OPENTHREAD
mState.isThreadProvisioned = ConnectivityMgr().IsThreadProvisioned();
mState.isThreadEnabled = ConnectivityMgr().IsThreadEnabled();
#else
mState.isWiFiProvisioned = ConnectivityMgr().IsWiFiStationProvisioned();
mState.isWiFiEnabled = ConnectivityMgr().IsWiFiStationEnabled();
#endif
mState.haveBLEConnections = (ConnectivityMgr().NumBLEConnections() != 0);
PlatformMgr().UnlockChipStack();
}
#if CHIP_ENABLE_OPENTHREAD
if (mState.isThreadProvisioned != oldState.isThreadProvisioned)
#else
if (mState.isWiFiProvisioned != oldState.isWiFiProvisioned)
#endif
{
// Provisioned state changed
DispatchEvent(EventId::ProvisionedStateChanged);
}
if (mState.haveBLEConnections != oldState.haveBLEConnections)
{
// Provisioned state changed
DispatchEvent(EventId::BLEConnectionsChanged);
}
OnMainLoop();
oldState = mState;
}
return CHIP_NO_ERROR;
}
void WindowApp::Finish()
{
DestroyTimer(mLongPressTimer);
DestroyButton(mButtonUp);
DestroyButton(mButtonDown);
for (uint16_t i = 0; i < WINDOW_COVER_COUNT; ++i)
{
mCoverList[i].Finish();
}
}
void WindowApp::DispatchEvent(const WindowApp::Event & event)
{
Cover * cover = nullptr;
switch (event.mId)
{
case EventId::ResetWarning:
mResetWarning = true;
if (mLongPressTimer)
{
mLongPressTimer->Start();
}
break;
case EventId::ResetCanceled:
mResetWarning = false;
break;
case EventId::Reset:
ConfigurationMgr().InitiateFactoryReset();
break;
case EventId::UpPressed:
mUpPressed = true;
if (mLongPressTimer)
{
mLongPressTimer->Start();
}
break;
case EventId::UpReleased:
mUpPressed = false;
if (mLongPressTimer)
{
mLongPressTimer->Stop();
}
if (mResetWarning)
{
PostEvent(EventId::ResetCanceled);
}
if (mUpSuppressed)
{
mUpSuppressed = false;
}
else if (mDownPressed)
{
mTiltMode = !mTiltMode;
mUpSuppressed = mDownSuppressed = true;
PostEvent(EventId::TiltModeChange);
}
else if (mTiltMode)
{
GetCover().TiltUp();
}
else
{
GetCover().LiftUp();
}
break;
case EventId::DownPressed:
mDownPressed = true;
if (mLongPressTimer)
{
mLongPressTimer->Start();
}
break;
case EventId::DownReleased:
mDownPressed = false;
if (mLongPressTimer)
{
mLongPressTimer->Stop();
}
if (mResetWarning)
{
PostEvent(EventId::ResetCanceled);
}
if (mDownSuppressed)
{
mDownSuppressed = false;
}
else if (mUpPressed)
{
mTiltMode = !mTiltMode;
mUpSuppressed = mDownSuppressed = true;
PostEvent(EventId::TiltModeChange);
}
else if (mTiltMode)
{
GetCover().TiltDown();
}
else
{
GetCover().LiftDown();
}
break;
case EventId::LiftUp:
case EventId::LiftDown:
cover = GetCover(event.mEndpoint);
if (cover)
{
cover->GotoLift(event.mId);
}
break;
case EventId::TiltUp:
case EventId::TiltDown:
cover = GetCover(event.mEndpoint);
if (cover)
{
cover->GotoTilt(event.mId);
}
break;
case EventId::StopMotion:
cover = GetCover(event.mEndpoint);
if (cover)
{
cover->StopMotion();
}
break;
default:
break;
}
}
void WindowApp::DestroyTimer(Timer * timer)
{
if (timer)
{
delete timer;
}
}
void WindowApp::DestroyButton(Button * btn)
{
if (btn)
{
delete btn;
}
}
void WindowApp::HandleLongPress()
{
if (mUpPressed && mDownPressed)
{
// Long press both buttons: Cycle between window coverings
mUpSuppressed = mDownSuppressed = true;
mCurrentCover = mCurrentCover < WINDOW_COVER_COUNT - 1 ? mCurrentCover + 1 : 0;
PostEvent(EventId::CoverChange);
}
else if (mUpPressed)
{
mUpSuppressed = true;
if (mResetWarning)
{
// Double long press button up: Reset now, you were warned!
PostEvent(EventId::Reset);
}
else
{
// Long press button up: Reset warning!
PostEvent(EventId::ResetWarning);
}
}
else if (mDownPressed)
{
// Long press button down: Cycle between covering types
mDownSuppressed = true;
EmberAfWcType cover_type = GetCover().CycleType();
mTiltMode = mTiltMode && (EMBER_ZCL_WC_TYPE_TILT_BLIND_LIFT_AND_TILT == cover_type);
}
}
void WindowApp::OnLongPressTimeout(WindowApp::Timer & timer)
{
WindowApp * app = static_cast<WindowApp *>(timer.mContext);
if (app)
{
app->HandleLongPress();
}
}
void WindowApp::Cover::Init(chip::EndpointId endpoint)
{
mEndpoint = endpoint;
mLiftTimer = WindowApp::Instance().CreateTimer("Timer:Lift", COVER_LIFT_TILT_TIMEOUT, OnLiftTimeout, this);
mTiltTimer = WindowApp::Instance().CreateTimer("Timer:Tilt", COVER_LIFT_TILT_TIMEOUT, OnTiltTimeout, this);
Attributes::InstalledOpenLimitLift::Set(endpoint, LIFT_OPEN_LIMIT);
Attributes::InstalledClosedLimitLift::Set(endpoint, LIFT_CLOSED_LIMIT);
LiftPositionSet(endpoint, LiftToPercent100ths(endpoint, LIFT_CLOSED_LIMIT));
Attributes::InstalledOpenLimitTilt::Set(endpoint, TILT_OPEN_LIMIT);
Attributes::InstalledClosedLimitTilt::Set(endpoint, TILT_CLOSED_LIMIT);
TiltPositionSet(endpoint, TiltToPercent100ths(endpoint, TILT_CLOSED_LIMIT));
// Attribute: Id 0 Type
TypeSet(endpoint, EMBER_ZCL_WC_TYPE_TILT_BLIND_LIFT_AND_TILT);
// Attribute: Id 7 ConfigStatus
ConfigStatus configStatus = { .operational = 1,
.online = 1,
.liftIsReversed = 0,
.liftIsPA = 1,
.tiltIsPA = 1,
.liftIsEncoderControlled = 1,
.tiltIsEncoderControlled = 1 };
ConfigStatusSet(endpoint, configStatus);
// Attribute: Id 10 OperationalStatus
OperationalStatus operationalStatus = { .global = OperationalState::Stall,
.lift = OperationalState::Stall,
.tilt = OperationalState::Stall };
OperationalStatusSet(endpoint, operationalStatus);
// Attribute: Id 13 EndProductType
EndProductTypeSet(endpoint, EMBER_ZCL_WC_END_PRODUCT_TYPE_INTERIOR_BLIND);
// Attribute: Id 24 Mode
Mode mode = { .motorDirReversed = 0, .calibrationMode = 1, .maintenanceMode = 1, .ledDisplay = 1 };
ModeSet(endpoint, mode);
// Attribute: Id 27 SafetyStatus (Optional)
SafetyStatus safetyStatus = { 0x00 }; // 0 is no issues;
SafetyStatusSet(endpoint, safetyStatus);
}
void WindowApp::Cover::Finish()
{
WindowApp::Instance().DestroyTimer(mLiftTimer);
WindowApp::Instance().DestroyTimer(mTiltTimer);
}
void WindowApp::Cover::LiftUp()
{
EmberAfStatus status;
chip::app::DataModel::Nullable<chip::Percent100ths> current;
chip::Percent100ths percent100ths = 5000; // set at middle
status = Attributes::CurrentPositionLiftPercent100ths::Get(mEndpoint, current);
if ((status == EMBER_ZCL_STATUS_SUCCESS) && !current.IsNull())
percent100ths = current.Value();
if (percent100ths < 9000)
{
percent100ths += 1000;
}
else
{
percent100ths = 10000;
}
LiftPositionSet(mEndpoint, percent100ths);
}
void WindowApp::Cover::LiftDown()
{
EmberAfStatus status;
chip::app::DataModel::Nullable<chip::Percent100ths> current;
chip::Percent100ths percent100ths = 5000; // set at middle
status = Attributes::CurrentPositionLiftPercent100ths::Get(mEndpoint, current);
if ((status == EMBER_ZCL_STATUS_SUCCESS) && !current.IsNull())
percent100ths = current.Value();
if (percent100ths > 1000)
{
percent100ths -= 1000;
}
else
{
percent100ths = 0;
}
LiftPositionSet(mEndpoint, percent100ths);
}
void WindowApp::Cover::GotoLift(EventId action)
{
chip::app::DataModel::Nullable<chip::Percent100ths> current;
chip::app::DataModel::Nullable<chip::Percent100ths> target;
Attributes::TargetPositionLiftPercent100ths::Get(mEndpoint, target);
Attributes::CurrentPositionLiftPercent100ths::Get(mEndpoint, current);
if (current.IsNull() || target.IsNull())
{
return;
}
if (EventId::None != action)
{
mLiftAction = action;
}
if (EventId::LiftUp == mLiftAction)
{
if (current.Value() < target.Value())
{
LiftUp();
}
else
{
mLiftAction = EventId::None;
}
}
else
{
if (current.Value() > target.Value())
{
LiftDown();
}
else
{
mLiftAction = EventId::None;
}
}
if (EventId::None != mLiftAction && mLiftTimer)
{
mLiftTimer->Start();
}
}
void WindowApp::Cover::TiltUp()
{
EmberAfStatus status;
chip::app::DataModel::Nullable<chip::Percent100ths> current;
chip::Percent100ths percent100ths = 5000; // set at middle
status = Attributes::CurrentPositionTiltPercent100ths::Get(mEndpoint, current);
if ((status == EMBER_ZCL_STATUS_SUCCESS) && !current.IsNull())
percent100ths = current.Value();
if (percent100ths < 9000)
{
percent100ths += 1000;
}
else
{
percent100ths = 10000;
}
TiltPositionSet(mEndpoint, percent100ths);
}
void WindowApp::Cover::TiltDown()
{
EmberAfStatus status;
chip::app::DataModel::Nullable<chip::Percent100ths> current;
chip::Percent100ths percent100ths = 5000; // set at middle
status = Attributes::CurrentPositionTiltPercent100ths::Get(mEndpoint, current);
if ((status == EMBER_ZCL_STATUS_SUCCESS) && !current.IsNull())
percent100ths = current.Value();
if (percent100ths > 1000)
{
percent100ths -= 1000;
}
else
{
percent100ths = 0;
}
TiltPositionSet(mEndpoint, percent100ths);
}
void WindowApp::Cover::GotoTilt(EventId action)
{
chip::app::DataModel::Nullable<chip::Percent100ths> current;
chip::app::DataModel::Nullable<chip::Percent100ths> target;
Attributes::TargetPositionTiltPercent100ths::Get(mEndpoint, target);
Attributes::CurrentPositionTiltPercent100ths::Get(mEndpoint, current);
if (current.IsNull() || target.IsNull())
{
return;
}
if (EventId::None != action)
{
mTiltAction = action;
}
if (EventId::TiltUp == mTiltAction)
{
if (current.Value() < target.Value())
{
TiltUp();
}
else
{
mTiltAction = EventId::None;
}
}
else
{
if (current.Value() > target.Value())
{
TiltDown();
}
else
{
mTiltAction = EventId::None;
}
}
if (EventId::None != mTiltAction && mTiltTimer)
{
mTiltTimer->Start();
}
}
EmberAfWcType WindowApp::Cover::CycleType()
{
EmberAfWcType type = TypeGet(mEndpoint);
switch (type)
{
case EMBER_ZCL_WC_TYPE_ROLLERSHADE:
type = EMBER_ZCL_WC_TYPE_DRAPERY;
// tilt = false;
break;
case EMBER_ZCL_WC_TYPE_DRAPERY:
type = EMBER_ZCL_WC_TYPE_TILT_BLIND_LIFT_AND_TILT;
break;
case EMBER_ZCL_WC_TYPE_TILT_BLIND_LIFT_AND_TILT:
type = EMBER_ZCL_WC_TYPE_ROLLERSHADE;
// tilt = false;
break;
default:
type = EMBER_ZCL_WC_TYPE_TILT_BLIND_LIFT_AND_TILT;
}
TypeSet(mEndpoint, type);
return type;
}
void WindowApp::Cover::StopMotion()
{
mLiftAction = EventId::None;
if (mLiftTimer)
{
mLiftTimer->Stop();
}
mTiltAction = EventId::None;
if (mTiltTimer)
{
mTiltTimer->Stop();
}
}
void WindowApp::Cover::OnLiftTimeout(WindowApp::Timer & timer)
{
WindowApp::Cover * cover = static_cast<WindowApp::Cover *>(timer.mContext);
if (cover)
{
cover->GotoLift();
}
}
void WindowApp::Cover::OnTiltTimeout(WindowApp::Timer & timer)
{
WindowApp::Cover * cover = static_cast<WindowApp::Cover *>(timer.mContext);
if (cover)
{
cover->GotoTilt();
}
}