Skip to content

Commit

Permalink
[nrfconnect] Adapt lock-app to Door Lock cluster (#17593)
Browse files Browse the repository at this point in the history
1. Listen to lock-state attribute changes rather than
   lock/unlock commands to support the auto-relock feature.
2. Remove obsolete code from BoltLockManager as now the
   auto-relock feature is handled in the common cluster
   code.
  • Loading branch information
Damian-Nordic authored and pull[bot] committed Oct 11, 2023
1 parent 063cf14 commit 49fe93e
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 249 deletions.
109 changes: 48 additions & 61 deletions examples/lock-app/nrfconnect/main/AppTask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <app-common/zap-generated/attribute-type.h>
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app-common/zap-generated/cluster-id.h>
#include <app/clusters/door-lock-server/door-lock-server.h>
#include <app/server/OnboardingCodesUtil.h>
#include <app/server/Server.h>
#include <credentials/DeviceAttestationCredsProvider.h>
Expand Down Expand Up @@ -120,15 +121,14 @@ CHIP_ERROR AppTask::Init()

sStatusLED.Init(SYSTEM_STATE_LED);
sLockLED.Init(LOCK_STATE_LED);
sLockLED.Set(!BoltLockMgr().IsUnlocked());
sLockLED.Set(BoltLockMgr().IsLocked());

sUnusedLED.Init(DK_LED3);
sUnusedLED_1.Init(DK_LED4);

UpdateStatusLED();

BoltLockMgr().Init();
BoltLockMgr().SetCallbacks(ActionInitiated, ActionCompleted);
BoltLockMgr().Init(LockStateChanged);

// Initialize buttons
int ret = dk_buttons_init(ButtonEventHandler);
Expand Down Expand Up @@ -191,22 +191,14 @@ CHIP_ERROR AppTask::StartApp()

void AppTask::LockActionEventHandler(AppEvent * aEvent)
{
BoltLockManager::Action_t action = BoltLockManager::INVALID_ACTION;
int32_t actor = 0;

if (aEvent->Type == AppEvent::kEventType_Lock)
if (BoltLockMgr().IsLocked())
{
action = static_cast<BoltLockManager::Action_t>(aEvent->LockEvent.Action);
actor = aEvent->LockEvent.Actor;
BoltLockMgr().Unlock(BoltLockManager::OperationSource::kButton);
}
else if (aEvent->Type == AppEvent::kEventType_Button)
else
{
action = BoltLockMgr().IsUnlocked() ? BoltLockManager::LOCK_ACTION : BoltLockManager::UNLOCK_ACTION;
actor = AppEvent::kEventType_Button;
BoltLockMgr().Lock(BoltLockManager::OperationSource::kButton);
}

if (action != BoltLockManager::INVALID_ACTION && !BoltLockMgr().InitiateAction(actor, action))
LOG_INF("Action is already in progress or active.");
}

void AppTask::ButtonEventHandler(uint32_t button_state, uint32_t has_changed)
Expand Down Expand Up @@ -341,7 +333,7 @@ void AppTask::FunctionHandler(AppEvent * aEvent)
sUnusedLED_1.Set(false);

// Set lock status LED back to show state of lock.
sLockLED.Set(!BoltLockMgr().IsUnlocked());
sLockLED.Set(BoltLockMgr().IsLocked());

UpdateStatusLED();
sAppTask.CancelTimer();
Expand Down Expand Up @@ -480,54 +472,34 @@ void AppTask::StartTimer(uint32_t aTimeoutInMs)
mFunctionTimerActive = true;
}

void AppTask::ActionInitiated(BoltLockManager::Action_t aAction, int32_t aActor)
void AppTask::LockStateChanged(BoltLockManager::State state, BoltLockManager::OperationSource source)
{
// If the action has been initiated by the lock, update the bolt lock trait
// and start flashing the LEDs rapidly to indicate action initiation.
if (aAction == BoltLockManager::LOCK_ACTION)
{
LOG_INF("Lock Action has been initiated");
}
else if (aAction == BoltLockManager::UNLOCK_ACTION)
switch (state)
{
LOG_INF("Unlock Action has been initiated");
}

sLockLED.Blink(50, 50);
}

void AppTask::ActionCompleted(BoltLockManager::Action_t aAction, int32_t aActor)
{
// if the action has been completed by the lock, update the bolt lock trait.
// Turn on the lock LED if in a LOCKED state OR
// Turn off the lock LED if in an UNLOCKED state.
if (aAction == BoltLockManager::LOCK_ACTION)
{
LOG_INF("Lock Action has been completed");
case BoltLockManager::State::kLockingInitiated:
LOG_INF("Lock action initiated");
sLockLED.Blink(50, 50);
break;
case BoltLockManager::State::kLockingCompleted:
LOG_INF("Lock action completed");
sLockLED.Set(true);
}
else if (aAction == BoltLockManager::UNLOCK_ACTION)
{
LOG_INF("Unlock Action has been completed");
break;
case BoltLockManager::State::kUnlockingInitiated:
LOG_INF("Unlock action initiated");
sLockLED.Blink(50, 50);
break;
case BoltLockManager::State::kUnlockingCompleted:
LOG_INF("Unlock action completed");
sLockLED.Set(false);
break;
}

if (aActor == AppEvent::kEventType_Button)
if (source != BoltLockManager::OperationSource::kRemote)
{
sAppTask.UpdateClusterState();
sAppTask.UpdateClusterState(state, source);
}
}

void AppTask::PostLockActionRequest(int32_t aActor, BoltLockManager::Action_t aAction)
{
AppEvent event;
event.Type = AppEvent::kEventType_Lock;
event.LockEvent.Actor = aActor;
event.LockEvent.Action = aAction;
event.Handler = LockActionEventHandler;
PostEvent(&event);
}

void AppTask::PostEvent(AppEvent * aEvent)
{
if (k_msgq_put(&sAppEventQueue, aEvent, K_NO_WAIT))
Expand All @@ -548,14 +520,29 @@ void AppTask::DispatchEvent(AppEvent * aEvent)
}
}

void AppTask::UpdateClusterState()
void AppTask::UpdateClusterState(BoltLockManager::State state, BoltLockManager::OperationSource source)
{
EmberAfStatus status;
LOG_INF("Updating door lock state");
status = Clusters::DoorLock::Attributes::LockState::Set(
kLockEndpointId, BoltLockMgr().IsUnlocked() ? DlLockState::kUnlocked : DlLockState::kLocked);
if (status != EMBER_ZCL_STATUS_SUCCESS)
DlLockState lockState;

switch (state)
{
LOG_ERR("Updating door lock state %x", status);
case BoltLockManager::State::kLockingCompleted:
lockState = DlLockState::kLocked;
break;
case BoltLockManager::State::kUnlockingCompleted:
lockState = DlLockState::kUnlocked;
break;
default:
lockState = DlLockState::kNotFullyLocked;
break;
}

SystemLayer().ScheduleLambda([lockState, source] {
LOG_INF("Updating LockState attribute");

if (!DoorLockServer::Instance().SetLockState(kLockEndpointId, lockState, source))
{
LOG_ERR("Failed to update LockState attribute");
}
});
}
171 changes: 37 additions & 134 deletions examples/lock-app/nrfconnect/main/BoltLockManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,170 +20,73 @@
#include "BoltLockManager.h"

#include "AppConfig.h"
#include "AppEvent.h"
#include "AppTask.h"

#include <logging/log.h>
#include <zephyr.h>

LOG_MODULE_DECLARE(app, CONFIG_MATTER_LOG_LEVEL);

static k_timer sLockTimer;

BoltLockManager BoltLockManager::sLock;

void BoltLockManager::Init()
void BoltLockManager::Init(StateChangeCallback callback)
{
k_timer_init(&sLockTimer, &BoltLockManager::TimerEventHandler, nullptr);
k_timer_user_data_set(&sLockTimer, this);
mStateChangeCallback = callback;

mState = kState_LockingCompleted;
mAutoLockTimerArmed = false;
mAutoRelock = false;
mAutoLockDuration = 0;
}

void BoltLockManager::SetCallbacks(Callback_fn_initiated aActionInitiated_CB, Callback_fn_completed aActionCompleted_CB)
{
mActionInitiated_CB = aActionInitiated_CB;
mActionCompleted_CB = aActionCompleted_CB;
k_timer_init(&mActuatorTimer, &BoltLockManager::ActuatorTimerEventHandler, nullptr);
k_timer_user_data_set(&mActuatorTimer, this);
}

bool BoltLockManager::IsActionInProgress()
void BoltLockManager::Lock(OperationSource source)
{
return (mState == kState_LockingInitiated || mState == kState_UnlockingInitiated) ? true : false;
}
VerifyOrReturn(mState != State::kLockingCompleted);
SetState(State::kLockingInitiated, source);

bool BoltLockManager::IsUnlocked()
{
return (mState == kState_UnlockingCompleted) ? true : false;
mActuatorOperationSource = source;
k_timer_start(&mActuatorTimer, K_MSEC(kActuatorMovementTimeMs), K_NO_WAIT);
}

void BoltLockManager::EnableAutoRelock(bool aOn)
void BoltLockManager::Unlock(OperationSource source)
{
mAutoRelock = aOn;
}
VerifyOrReturn(mState != State::kUnlockingCompleted);
SetState(State::kUnlockingInitiated, source);

void BoltLockManager::SetAutoLockDuration(uint32_t aDurationInSecs)
{
mAutoLockDuration = aDurationInSecs;
mActuatorOperationSource = source;
k_timer_start(&mActuatorTimer, K_MSEC(kActuatorMovementTimeMs), K_NO_WAIT);
}

bool BoltLockManager::InitiateAction(int32_t aActor, Action_t aAction)
void BoltLockManager::ActuatorTimerEventHandler(k_timer * timer)
{
bool action_initiated = false;
State_t new_state;

// Initiate Lock/Unlock Action only when the previous one is complete.
if (mState == kState_LockingCompleted && aAction == UNLOCK_ACTION)
{
action_initiated = true;
mCurrentActor = aActor;
new_state = kState_UnlockingInitiated;
}
else if (mState == kState_UnlockingCompleted && aAction == LOCK_ACTION)
{
action_initiated = true;
mCurrentActor = aActor;
new_state = kState_LockingInitiated;
}

if (action_initiated)
{
if (mAutoLockTimerArmed && new_state == kState_LockingInitiated)
{
// If auto lock timer has been armed and someone initiates locking,
// cancel the timer and continue as normal.
mAutoLockTimerArmed = false;

CancelTimer();
}

StartTimer(ACTUATOR_MOVEMENT_PERIOS_MS);

// Since the timer started successfully, update the state and trigger callback
mState = new_state;

if (mActionInitiated_CB)
{
mActionInitiated_CB(aAction, aActor);
}
}

return action_initiated;
}
// The timer event handler is called in the context of the system clock ISR.
// Post an event to the application task queue to process the event in the
// context of the application thread.

void BoltLockManager::StartTimer(uint32_t aTimeoutMs)
{
k_timer_start(&sLockTimer, K_MSEC(aTimeoutMs), K_NO_WAIT);
}

void BoltLockManager::CancelTimer(void)
{
k_timer_stop(&sLockTimer);
}

void BoltLockManager::TimerEventHandler(k_timer * timer)
{
BoltLockManager * lock = static_cast<BoltLockManager *>(k_timer_user_data_get(timer));

// The timer event handler will be called in the context of the timer task
// once sLockTimer expires. Post an event to apptask queue with the actual handler
// so that the event can be handled in the context of the apptask.
AppEvent event;
event.Type = AppEvent::kEventType_Timer;
event.TimerEvent.Context = lock;
event.Handler = lock->mAutoLockTimerArmed ? AutoReLockTimerEventHandler : ActuatorMovementTimerEventHandler;
event.TimerEvent.Context = static_cast<BoltLockManager *>(k_timer_user_data_get(timer));
event.Handler = BoltLockManager::ActuatorAppEventHandler;
GetAppTask().PostEvent(&event);
}

void BoltLockManager::AutoReLockTimerEventHandler(AppEvent * aEvent)
void BoltLockManager::ActuatorAppEventHandler(AppEvent * aEvent)
{
BoltLockManager * lock = static_cast<BoltLockManager *>(aEvent->TimerEvent.Context);
int32_t actor = 0;

// Make sure auto lock timer is still armed.
if (!lock->mAutoLockTimerArmed)
return;

lock->mAutoLockTimerArmed = false;

LOG_INF("Auto Re-Lock has been triggered!");

lock->InitiateAction(actor, LOCK_ACTION);
switch (lock->mState)
{
case State::kLockingInitiated:
lock->SetState(State::kLockingCompleted, lock->mActuatorOperationSource);
break;
case State::kUnlockingInitiated:
lock->SetState(State::kUnlockingCompleted, lock->mActuatorOperationSource);
break;
default:
break;
}
}

void BoltLockManager::ActuatorMovementTimerEventHandler(AppEvent * aEvent)
void BoltLockManager::SetState(State state, OperationSource source)
{
Action_t actionCompleted = INVALID_ACTION;

BoltLockManager * lock = static_cast<BoltLockManager *>(aEvent->TimerEvent.Context);
mState = state;

if (lock->mState == kState_LockingInitiated)
if (mStateChangeCallback != nullptr)
{
lock->mState = kState_LockingCompleted;
actionCompleted = LOCK_ACTION;
}
else if (lock->mState == kState_UnlockingInitiated)
{
lock->mState = kState_UnlockingCompleted;
actionCompleted = UNLOCK_ACTION;
}

if (actionCompleted != INVALID_ACTION)
{
if (lock->mActionCompleted_CB)
{
lock->mActionCompleted_CB(actionCompleted, lock->mCurrentActor);
}

if (lock->mAutoRelock && actionCompleted == UNLOCK_ACTION)
{
// Start the timer for auto relock
lock->StartTimer(lock->mAutoLockDuration * 1000);

lock->mAutoLockTimerArmed = true;

LOG_INF("Auto Re-lock enabled. Will be triggered in %u seconds", lock->mAutoLockDuration);
}
mStateChangeCallback(state, source);
}
}
Loading

0 comments on commit 49fe93e

Please sign in to comment.