Sweety 4 лет назад
Родитель
Сommit
4262811d4e

+ 493 - 0
examples/lock-app/esp32/main/AppTask.cpp

@@ -0,0 +1,493 @@
+/*
+ *
+ *    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.
+ */
+
+#include "AppTask.h"
+#include "AppConfig.h"
+#include "AppEvent.h"
+#include "Button.h"
+#include "LEDWidget.h"
+#include "esp_log.h"
+#include "gen/attribute-id.h"
+#include "gen/attribute-type.h"
+#include "gen/cluster-id.h"
+#include <app/server/OnboardingCodesUtil.h>
+#include <app/server/Server.h>
+#include <app/util/af-enums.h>
+#include <app/util/attribute-storage.h>
+#include <platform/CHIPDeviceLayer.h>
+#include <platform/internal/CHIPDeviceLayerInternal.h>
+#include <support/CodeUtils.h>
+
+#define FACTORY_RESET_TRIGGER_TIMEOUT 3000
+#define FACTORY_RESET_CANCEL_WINDOW_TIMEOUT 3000
+#define APP_EVENT_QUEUE_SIZE 10
+#define APP_TASK_STACK_SIZE (3000)
+#define APP_TASK_PRIORITY 2
+#define STATUS_LED_GPIO_NUM GPIO_NUM_2 // Use LED1 (blue LED) as status LED on DevKitC
+
+static const char * const TAG = "lock-app";
+
+namespace {
+TimerHandle_t sFunctionTimer; // FreeRTOS app sw timer.
+
+LEDWidget sStatusLED;
+LEDWidget sLockLED;
+
+Button resetButton;
+Button lockButton;
+
+BaseType_t sAppTaskHandle;
+QueueHandle_t sAppEventQueue;
+
+bool sHaveBLEConnections      = false;
+bool sHaveServiceConnectivity = false;
+
+StackType_t appStack[APP_TASK_STACK_SIZE / sizeof(StackType_t)];
+} // namespace
+
+using namespace ::chip::DeviceLayer;
+
+AppTask AppTask::sAppTask;
+
+int AppTask::StartAppTask()
+{
+    int err = CHIP_ERROR_MAX;
+
+    sAppEventQueue = xQueueCreate(APP_EVENT_QUEUE_SIZE, sizeof(AppEvent));
+    if (sAppEventQueue == NULL)
+    {
+        ESP_LOGE(TAG, "Failed to allocate app event queue");
+        return 0;
+    }
+
+    // Start App task.
+    sAppTaskHandle = xTaskCreate(AppTaskMain, APP_TASK_NAME, ArraySize(appStack), NULL, 1, NULL);
+    if (sAppTaskHandle)
+    {
+        err = CHIP_NO_ERROR;
+    }
+
+    return err;
+}
+
+int AppTask::Init()
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+
+    // Create FreeRTOS sw timer for Function Selection.
+    sFunctionTimer = xTimerCreate("FnTmr",          // Just a text name, not used by the RTOS kernel
+                                  1,                // == default timer period (mS)
+                                  false,            // no timer reload (==one-shot)
+                                  (void *) this,    // init timer id = app task obj context
+                                  TimerEventHandler // timer callback handler
+    );
+    err            = BoltLockMgr().Init();
+    if (err != CHIP_NO_ERROR)
+    {
+        ESP_LOGI(TAG, "BoltLockMgr().Init() failed");
+        return err;
+    }
+
+    BoltLockMgr().SetCallbacks(ActionInitiated, ActionCompleted);
+
+    sStatusLED.Init(SYSTEM_STATE_LED);
+    sLockLED.Init(LOCK_STATE_LED);
+
+    resetButton.Init(APP_FUNCTION_BUTTON, APP_BUTTON_DEBOUNCE_PERIOD_MS);
+    lockButton.Init(APP_LOCK_BUTTON, APP_BUTTON_DEBOUNCE_PERIOD_MS);
+
+    sLockLED.Set(!BoltLockMgr().IsUnlocked());
+
+    UpdateClusterState();
+
+    ConfigurationMgr().LogDeviceConfig();
+
+    PrintOnboardingCodes(chip::RendezvousInformationFlag(chip::RendezvousInformationFlag::kBLE));
+    return err;
+}
+
+void AppTask::AppTaskMain(void * pvParameter)
+{
+    int err;
+    AppEvent event;
+    uint64_t mLastChangeTimeUS = 0;
+
+    err = sAppTask.Init();
+    if (err != CHIP_NO_ERROR)
+    {
+        ESP_LOGI(TAG, "AppTask.Init() failed due to %d", err);
+        return;
+    }
+
+    ESP_LOGI(TAG, "App Task started");
+
+    while (true)
+    {
+        BaseType_t eventReceived = xQueueReceive(sAppEventQueue, &event, pdMS_TO_TICKS(10));
+        while (eventReceived == pdTRUE)
+        {
+            sAppTask.DispatchEvent(&event);
+            eventReceived = xQueueReceive(sAppEventQueue, &event, 0);
+        }
+        // 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())
+        {
+            sHaveBLEConnections      = (ConnectivityMgr().NumBLEConnections() != 0);
+            sHaveServiceConnectivity = ConnectivityMgr().HaveServiceConnectivity();
+            PlatformMgr().UnlockChipStack();
+        }
+
+        // Update the status LED if factory reset has not been initiated.
+        //
+        // If system has "full connectivity", keep the LED On constantly.
+        //
+        // If no connectivity to the service OR subscriptions are not fully
+        // established THEN blink the LED Off for a short period of time.
+        //
+        // If the system has ble connection(s) uptill the stage above, THEN blink
+        // the LEDs at an even rate of 100ms.
+        //
+        // Otherwise, blink the LED ON for a very short time.
+        if (sAppTask.mFunction != kFunction_FactoryReset)
+        {
+            // Consider the system to be "fully connected" if it has service
+            // connectivity
+            if (sHaveServiceConnectivity)
+            {
+                sStatusLED.Set(true);
+            }
+            else if (sHaveBLEConnections)
+            {
+                sStatusLED.Blink(100, 100);
+            }
+            else
+            {
+                sStatusLED.Blink(50, 950);
+            }
+        }
+
+        sStatusLED.Animate();
+        sLockLED.Animate();
+
+        uint64_t nowUS            = chip::System::Platform::Layer::GetClock_Monotonic();
+        uint64_t nextChangeTimeUS = mLastChangeTimeUS + 5 * 1000 * 1000UL;
+
+        if (nowUS > nextChangeTimeUS)
+        {
+            mLastChangeTimeUS = nowUS;
+        }
+        if (lockButton.Poll())
+        {
+            if (lockButton.IsPressed())
+            {
+                GetAppTask().ButtonEventHandler(APP_LOCK_BUTTON, APP_BUTTON_PRESSED);
+            }
+        }
+        if (resetButton.Poll())
+        {
+            if (resetButton.IsPressed())
+            {
+                GetAppTask().ButtonEventHandler(APP_FUNCTION_BUTTON, APP_BUTTON_PRESSED);
+            }
+        }
+    }
+}
+
+void AppTask::LockActionEventHandler(AppEvent * aEvent)
+{
+    bool initiated = false;
+    BoltLockManager::Action_t action;
+    int32_t actor;
+    int err = CHIP_NO_ERROR;
+
+    if (aEvent->Type == AppEvent::kEventType_Lock)
+    {
+        action = static_cast<BoltLockManager::Action_t>(aEvent->LockEvent.Action);
+        actor  = aEvent->LockEvent.Actor;
+    }
+    else if (aEvent->Type == AppEvent::kEventType_Button)
+    {
+        if (BoltLockMgr().IsUnlocked())
+        {
+            action = BoltLockManager::LOCK_ACTION;
+        }
+        else
+        {
+            action = BoltLockManager::UNLOCK_ACTION;
+        }
+        actor = AppEvent::kEventType_Button;
+    }
+    else
+    {
+        err = CHIP_ERROR_MAX;
+    }
+
+    if (err == CHIP_NO_ERROR)
+    {
+        initiated = BoltLockMgr().InitiateAction(actor, action);
+
+        if (!initiated)
+        {
+            ESP_LOGI(TAG, "Action is already in progress or active.");
+        }
+    }
+}
+
+void AppTask::ButtonEventHandler(uint8_t btnIdx, uint8_t btnAction)
+{
+    if (btnIdx != APP_LOCK_BUTTON && btnIdx != APP_FUNCTION_BUTTON)
+    {
+        return;
+    }
+
+    AppEvent button_event           = {};
+    button_event.Type               = AppEvent::kEventType_Button;
+    button_event.ButtonEvent.PinNo  = btnIdx;
+    button_event.ButtonEvent.Action = btnAction;
+
+    if (btnIdx == APP_LOCK_BUTTON && btnAction == APP_BUTTON_PRESSED)
+    {
+        button_event.Handler = LockActionEventHandler;
+        sAppTask.PostEvent(&button_event);
+    }
+    else if (btnIdx == APP_FUNCTION_BUTTON)
+    {
+        button_event.Handler = FunctionHandler;
+        sAppTask.PostEvent(&button_event);
+    }
+}
+
+void AppTask::TimerEventHandler(TimerHandle_t xTimer)
+{
+    AppEvent event;
+    event.Type               = AppEvent::kEventType_Timer;
+    event.TimerEvent.Context = (void *) xTimer;
+    event.Handler            = FunctionTimerEventHandler;
+    sAppTask.PostEvent(&event);
+}
+
+void AppTask::FunctionTimerEventHandler(AppEvent * aEvent)
+{
+    if (aEvent->Type != AppEvent::kEventType_Timer)
+    {
+        return;
+    }
+
+    // If we reached here, the button was held past FACTORY_RESET_TRIGGER_TIMEOUT,
+    // initiate factory reset
+    if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_StartBleAdv)
+    {
+        ESP_LOGI(TAG, "Factory Reset Triggered. Release button within %ums to cancel.", FACTORY_RESET_CANCEL_WINDOW_TIMEOUT);
+
+        // Start timer for FACTORY_RESET_CANCEL_WINDOW_TIMEOUT to allow user to
+        // cancel, if required.
+        sAppTask.StartTimer(FACTORY_RESET_CANCEL_WINDOW_TIMEOUT);
+
+        sAppTask.mFunction = kFunction_FactoryReset;
+
+        // Turn off all LEDs before starting blink to make sure blink is
+        // co-ordinated.
+        sStatusLED.Set(false);
+        sLockLED.Set(false);
+
+        sStatusLED.Blink(500);
+        sLockLED.Blink(500);
+    }
+    else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset)
+    {
+        // Actually trigger Factory Reset
+        sAppTask.mFunction = kFunction_NoneSelected;
+        ConfigurationMgr().InitiateFactoryReset();
+    }
+}
+
+void AppTask::FunctionHandler(AppEvent * aEvent)
+{
+    if (aEvent->ButtonEvent.PinNo != APP_FUNCTION_BUTTON)
+    {
+        return;
+    }
+
+    // To trigger software update: press the APP_FUNCTION_BUTTON button briefly (<
+    // FACTORY_RESET_TRIGGER_TIMEOUT) To initiate factory reset: press the
+    // APP_FUNCTION_BUTTON for FACTORY_RESET_TRIGGER_TIMEOUT +
+    // FACTORY_RESET_CANCEL_WINDOW_TIMEOUT All LEDs start blinking after
+    // FACTORY_RESET_TRIGGER_TIMEOUT to signal factory reset has been initiated.
+    // To cancel factory reset: release the APP_FUNCTION_BUTTON once all LEDs
+    // start blinking within the FACTORY_RESET_CANCEL_WINDOW_TIMEOUT
+    if (aEvent->ButtonEvent.Action == APP_BUTTON_PRESSED)
+    {
+        if (!sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_NoneSelected)
+        {
+            sAppTask.StartTimer(FACTORY_RESET_TRIGGER_TIMEOUT);
+            sAppTask.mFunction = kFunction_StartBleAdv;
+        }
+    }
+    else
+    {
+        // If the button was released before factory reset got initiated, start BLE advertissement in fast mode
+        if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_StartBleAdv)
+        {
+            sAppTask.CancelTimer();
+            sAppTask.mFunction = kFunction_NoneSelected;
+
+            if (!ConnectivityMgr().IsThreadProvisioned())
+            {
+                // Enable BLE advertisements
+                ConnectivityMgr().SetBLEAdvertisingEnabled(true);
+                ConnectivityMgr().SetBLEAdvertisingMode(ConnectivityMgr().kFastAdvertising);
+            }
+            else
+            {
+                ESP_LOGI(TAG, "Network is already provisioned, Ble advertissement not enabled");
+            }
+        }
+        else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset)
+        {
+            // Set lock status LED back to show state of lock.
+            sLockLED.Set(!BoltLockMgr().IsUnlocked());
+
+            sAppTask.CancelTimer();
+
+            // Change the function to none selected since factory reset has been
+            // canceled.
+            sAppTask.mFunction = kFunction_NoneSelected;
+
+            ESP_LOGI(TAG, "Factory Reset has been Canceled");
+        }
+    }
+}
+
+void AppTask::CancelTimer()
+{
+    if (xTimerStop(sFunctionTimer, 0) == pdFAIL)
+    {
+        ESP_LOGI(TAG, "app timer stop() failed");
+        return;
+    }
+
+    mFunctionTimerActive = false;
+}
+void AppTask::StartTimer(uint32_t aTimeoutInMs)
+{
+    if (xTimerIsTimerActive(sFunctionTimer))
+    {
+        ESP_LOGI(TAG, "app timer already started!");
+        CancelTimer();
+    }
+
+    // timer is not active, change its period to required value (== restart).
+    // FreeRTOS- Block for a maximum of 100 ticks if the change period command
+    // cannot immediately be sent to the timer command queue.
+    if (xTimerChangePeriod(sFunctionTimer, aTimeoutInMs / portTICK_PERIOD_MS, 100) != pdPASS)
+    {
+        ESP_LOGI(TAG, "app timer start() failed");
+        return;
+    }
+
+    mFunctionTimerActive = true;
+}
+
+void AppTask::ActionInitiated(BoltLockManager::Action_t aAction, int32_t aActor)
+{
+    // 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)
+    {
+        ESP_LOGI(TAG, "Lock Action has been initiated");
+    }
+    else if (aAction == BoltLockManager::UNLOCK_ACTION)
+    {
+        ESP_LOGI(TAG, "Unlock Action has been initiated");
+    }
+    if (aActor == AppEvent::kEventType_Button)
+    {
+        sAppTask.mSyncClusterToButtonAction = true;
+    }
+
+    sLockLED.Blink(50, 50);
+}
+
+void AppTask::ActionCompleted(BoltLockManager::Action_t aAction)
+{
+    // 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)
+    {
+        ESP_LOGI(TAG, "Lock Action has been completed");
+
+        sLockLED.Set(true);
+    }
+    else if (aAction == BoltLockManager::UNLOCK_ACTION)
+    {
+        ESP_LOGI(TAG, "Unlock Action has been completed");
+
+        sLockLED.Set(false);
+    }
+}
+
+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(const AppEvent * aEvent)
+{
+    if (sAppEventQueue != NULL)
+    {
+        if (!xQueueSend(sAppEventQueue, aEvent, 1))
+        {
+            ESP_LOGI(TAG, "Failed to post event to app task event queue");
+        }
+    }
+}
+
+void AppTask::DispatchEvent(AppEvent * aEvent)
+{
+    if (aEvent->Handler)
+    {
+        aEvent->Handler(aEvent);
+    }
+    else
+    {
+        ESP_LOGI(TAG, "Event received with no handler. Dropping event.");
+    }
+}
+
+/* if unlocked then it locked it first*/
+void AppTask::UpdateClusterState(void)
+{
+    uint8_t newValue = !BoltLockMgr().IsUnlocked();
+
+    // write the new on/off value
+    EmberAfStatus status = emberAfWriteAttribute(1, ZCL_ON_OFF_CLUSTER_ID, ZCL_ON_OFF_ATTRIBUTE_ID, CLUSTER_MASK_SERVER,
+                                                 (uint8_t *) &newValue, ZCL_BOOLEAN_ATTRIBUTE_TYPE);
+    if (status != EMBER_ZCL_STATUS_SUCCESS)
+    {
+        ESP_LOGI(TAG, "ERR: updating on/off %x", status);
+    }
+}

+ 217 - 0
examples/lock-app/esp32/main/BoltLockManager.cpp

@@ -0,0 +1,217 @@
+/*
+ *
+ *    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.
+ */
+
+#include "BoltLockManager.h"
+
+#include "AppConfig.h"
+#include "AppTask.h"
+#include "esp_log.h"
+#include <freertos/FreeRTOS.h>
+static const char * TAG = "BoltLockManager";
+BoltLockManager BoltLockManager::sLock;
+
+TimerHandle_t sLockTimer;
+
+int BoltLockManager::Init()
+{
+    // Create FreeRTOS sw timer for lock timer.
+    sLockTimer = xTimerCreate("lockTmr",        // Just a text name, not used by the RTOS kernel
+                              1,                // == default timer period (mS)
+                              false,            // no timer reload (==one-shot)
+                              (void *) this,    // init timer id = lock obj context
+                              TimerEventHandler // timer callback handler
+    );
+
+    if (sLockTimer == NULL)
+    {
+        ESP_LOGE(TAG, "sLockTimer timer create failed");
+        return CHIP_ERROR_MAX;
+    }
+
+    mState              = kState_LockingCompleted;
+    mAutoLockTimerArmed = false;
+    mAutoRelock         = false;
+    mAutoLockDuration   = 0;
+
+    return CHIP_NO_ERROR;
+}
+
+void BoltLockManager::SetCallbacks(Callback_fn_initiated aActionInitiated_CB, Callback_fn_completed aActionCompleted_CB)
+{
+    mActionInitiated_CB = aActionInitiated_CB;
+    mActionCompleted_CB = aActionCompleted_CB;
+}
+
+bool BoltLockManager::IsActionInProgress()
+{
+    return (mState == kState_LockingInitiated || mState == kState_UnlockingInitiated);
+}
+
+bool BoltLockManager::IsUnlocked()
+{
+    return (mState == kState_UnlockingCompleted);
+}
+
+void BoltLockManager::SetAutoLockDuration(uint32_t aDurationInSecs)
+{
+    mAutoLockDuration = aDurationInSecs;
+}
+
+bool BoltLockManager::InitiateAction(int32_t aActor, Action_t aAction)
+{
+    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;
+
+        new_state = kState_UnlockingInitiated;
+    }
+    else if (mState == kState_UnlockingCompleted && aAction == LOCK_ACTION)
+    {
+        action_initiated = true;
+
+        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;
+}
+
+void BoltLockManager::StartTimer(uint32_t aTimeoutMs)
+{
+    if (xTimerIsTimerActive(sLockTimer))
+    {
+        ESP_LOGI(TAG, "app timer already started!");
+        CancelTimer();
+    }
+
+    // timer is not active, change its period to required value (== restart).
+    // FreeRTOS- Block for a maximum of 100 ticks if the change period command
+    // cannot immediately be sent to the timer command queue.
+    if (xTimerChangePeriod(sLockTimer, (aTimeoutMs / portTICK_PERIOD_MS), 100) != pdPASS)
+    {
+        ESP_LOGI(TAG, "sLockTimer timer start() failed");
+        return;
+    }
+}
+
+void BoltLockManager::CancelTimer(void)
+{
+    if (xTimerStop(sLockTimer, 0) == pdFAIL)
+    {
+        ESP_LOGI(TAG, "Lock timer timer stop() failed");
+        return;
+    }
+}
+void BoltLockManager::TimerEventHandler(TimerHandle_t xTimer)
+{
+    // Get lock obj context from timer id.
+    BoltLockManager * lock = static_cast<BoltLockManager *>(pvTimerGetTimerID(xTimer));
+
+    // 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;
+    if (lock->mAutoLockTimerArmed)
+    {
+        event.Handler = AutoReLockTimerEventHandler;
+    }
+    else
+    {
+        event.Handler = ActuatorMovementTimerEventHandler;
+    }
+    GetAppTask().PostEvent(&event);
+}
+
+void BoltLockManager::AutoReLockTimerEventHandler(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;
+
+    ESP_LOGI(TAG, "Auto Re-Lock has been triggered!");
+
+    lock->InitiateAction(actor, LOCK_ACTION);
+}
+
+void BoltLockManager::ActuatorMovementTimerEventHandler(AppEvent * aEvent)
+{
+    Action_t actionCompleted = INVALID_ACTION;
+
+    BoltLockManager * lock = static_cast<BoltLockManager *>(aEvent->TimerEvent.Context);
+
+    if (lock->mState == kState_LockingInitiated)
+    {
+        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);
+        }
+
+        if (lock->mAutoRelock && actionCompleted == UNLOCK_ACTION)
+        {
+            // Start the timer for auto relock
+            lock->StartTimer(lock->mAutoLockDuration * 1000);
+
+            lock->mAutoLockTimerArmed = true;
+
+            ESP_LOGI(TAG, "Auto Re-lock enabled. Will be triggered in %u seconds", lock->mAutoLockDuration);
+        }
+    }
+}

+ 65 - 0
examples/lock-app/esp32/main/Button.cpp

@@ -0,0 +1,65 @@
+/*
+ *
+ *    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.
+ */
+
+#include "Button.h"
+#include "AppTask.h"
+
+#include "AppConfig.h"
+
+esp_err_t Button::Init(gpio_num_t gpioNum, uint16_t debouncePeriod)
+{
+    esp_err_t err;
+
+    mGPIONum         = gpioNum;
+    mDebouncePeriod  = debouncePeriod / portTICK_PERIOD_MS;
+    mState           = false;
+    mLastPolledState = false;
+
+    err = gpio_set_direction(gpioNum, GPIO_MODE_INPUT);
+    SuccessOrExit(err);
+
+exit:
+    return err;
+}
+
+bool Button::Poll()
+{
+    uint32_t now = xTaskGetTickCount();
+
+    bool newState = gpio_get_level(mGPIONum);
+
+    if (newState != mLastPolledState)
+    {
+        mLastPolledState = newState;
+        mLastReadTime    = now;
+    }
+
+    else if (newState != mState && (now - mLastReadTime) >= mDebouncePeriod)
+    {
+        mState          = newState;
+        mPrevStateDur   = now - mStateStartTime;
+        mStateStartTime = now;
+        return true;
+    }
+
+    return false;
+}
+
+uint32_t Button::GetStateDuration()
+{
+    return (xTaskGetTickCount() - mStateStartTime) * portTICK_PERIOD_MS;
+}

+ 28 - 1
examples/lock-app/esp32/main/DeviceCallbacks.cpp

@@ -24,6 +24,8 @@
  **/
 
 #include "DeviceCallbacks.h"
+#include "AppConfig.h"
+#include "BoltLockManager.h"
 
 #include "esp_heap_caps.h"
 #include "esp_log.h"
@@ -59,7 +61,16 @@ void DeviceCallbacks::PostAttributeChangeCallback(EndpointId endpointId, Cluster
     ESP_LOGI(TAG, "PostAttributeChangeCallback - Cluster ID: '0x%04x', EndPoint ID: '0x%02x', Attribute ID: '0x%04x'", clusterId,
              endpointId, attributeId);
 
-    ESP_LOGI(TAG, "Unhandled cluster ID: %d", clusterId);
+    switch (clusterId)
+    {
+    case ZCL_ON_OFF_CLUSTER_ID:
+        OnOnOffPostAttributeChangeCallback(endpointId, attributeId, value);
+        break;
+
+    default:
+        ESP_LOGI(TAG, "Unhandled cluster ID: %d", clusterId);
+        break;
+    }
 
     ESP_LOGI(TAG, "Current free heap: %d\n", heap_caps_get_free_size(MALLOC_CAP_8BIT));
 }
@@ -91,3 +102,19 @@ void DeviceCallbacks::OnSessionEstablished(const ChipDeviceEvent * event)
         ESP_LOGI(TAG, "Commissioner detected!");
     }
 }
+
+void DeviceCallbacks::OnOnOffPostAttributeChangeCallback(EndpointId endpointId, AttributeId attributeId, uint8_t * value)
+{
+    VerifyOrExit(attributeId == ZCL_ON_OFF_ATTRIBUTE_ID, ESP_LOGI(TAG, "Unhandled Attribute ID: '0x%04x", attributeId));
+    VerifyOrExit(endpointId == 1 || endpointId == 2, ESP_LOGE(TAG, "Unexpected EndPoint ID: `0x%02x'", endpointId));
+    if (*value)
+    {
+        BoltLockMgr().InitiateAction(AppEvent::kEventType_Lock, BoltLockManager::LOCK_ACTION);
+    }
+    else
+    {
+        BoltLockMgr().InitiateAction(AppEvent::kEventType_Lock, BoltLockManager::UNLOCK_ACTION);
+    }
+exit:
+    return;
+}

+ 0 - 2
examples/lock-app/esp32/main/Kconfig.projbuild

@@ -31,8 +31,6 @@ menu "Demo"
 
         config DEVICE_TYPE_ESP32_DEVKITC
             bool "ESP32-DevKitC"
-        config DEVICE_TYPE_M5STACK
-            bool "M5Stack"
     endchoice
 
     choice

+ 85 - 0
examples/lock-app/esp32/main/LEDWidget.cpp

@@ -0,0 +1,85 @@
+/*
+ *
+ *    Copyright (c) 2018 Nest Labs, Inc.
+ *    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 "LEDWidget.h"
+#include "AppTask.h"
+#include <platform/CHIPDeviceLayer.h>
+
+using namespace chip::System::Platform::Layer;
+
+void LEDWidget::Init(gpio_num_t gpioNum)
+{
+    mLastChangeTimeUS = 0;
+    mBlinkOnTimeMS    = 0;
+    mBlinkOffTimeMS   = 0;
+    mGPIONum          = gpioNum;
+
+    if (gpioNum < GPIO_NUM_MAX)
+    {
+        gpio_set_direction(gpioNum, GPIO_MODE_OUTPUT);
+    }
+}
+
+void LEDWidget::Invert(void)
+{
+    Set(!mState);
+}
+
+void LEDWidget::Set(bool state)
+{
+    mBlinkOnTimeMS  = 0;
+    mBlinkOffTimeMS = 0;
+    DoSet(state);
+}
+
+void LEDWidget::Blink(uint32_t changeRateMS)
+{
+    Blink(changeRateMS, changeRateMS);
+}
+
+void LEDWidget::Blink(uint32_t onTimeMS, uint32_t offTimeMS)
+{
+    mBlinkOnTimeMS  = onTimeMS;
+    mBlinkOffTimeMS = offTimeMS;
+    Animate();
+}
+
+void LEDWidget::Animate()
+{
+    if (mBlinkOnTimeMS != 0 && mBlinkOffTimeMS != 0)
+    {
+        int64_t nowUS            = GetClock_MonotonicHiRes();
+        int64_t stateDurUS       = ((mState) ? mBlinkOnTimeMS : mBlinkOffTimeMS) * 1000LL;
+        int64_t nextChangeTimeUS = mLastChangeTimeUS + stateDurUS;
+
+        if (nowUS > nextChangeTimeUS)
+        {
+            DoSet(!mState);
+            mLastChangeTimeUS = nowUS;
+        }
+    }
+}
+
+void LEDWidget::DoSet(bool state)
+{
+    mState = state;
+    if (mGPIONum < GPIO_NUM_MAX)
+    {
+        gpio_set_level(mGPIONum, (state) ? 1 : 0);
+    }
+}

+ 39 - 0
examples/lock-app/esp32/main/include/AppConfig.h

@@ -0,0 +1,39 @@
+/*
+ *
+ *    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.
+ */
+
+#pragma once
+
+#include "driver/gpio.h"
+
+// ---- Lock Example App Config ----
+
+#define APP_TASK_NAME "LOCK-APP"
+
+#define SYSTEM_STATE_LED GPIO_NUM_25
+#define LOCK_STATE_LED GPIO_NUM_26
+
+#define APP_LOCK_BUTTON GPIO_NUM_34
+#define APP_FUNCTION_BUTTON GPIO_NUM_35
+
+#define APP_BUTTON_DEBOUNCE_PERIOD_MS 50
+
+#define APP_BUTTON_PRESSED 0
+#define APP_BUTTON_RELEASED 1
+
+// Time it takes in ms for the simulated actuator to move from one
+// state to another.
+#define ACTUATOR_MOVEMENT_PERIOS_MS 2000

+ 54 - 0
examples/lock-app/esp32/main/include/AppEvent.h

@@ -0,0 +1,54 @@
+/*
+ *
+ *    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.
+ */
+
+#pragma once
+
+struct AppEvent;
+typedef void (*EventHandler)(AppEvent *);
+
+struct AppEvent
+{
+    enum AppEventTypes
+    {
+        kEventType_Button = 0,
+        kEventType_Timer,
+        kEventType_Lock,
+        kEventType_Install,
+    };
+
+    uint16_t Type;
+
+    union
+    {
+        struct
+        {
+            uint8_t PinNo;
+            uint8_t Action;
+        } ButtonEvent;
+        struct
+        {
+            void * Context;
+        } TimerEvent;
+        struct
+        {
+            uint8_t Action;
+            int32_t Actor;
+        } LockEvent;
+    };
+
+    EventHandler Handler;
+};

+ 84 - 0
examples/lock-app/esp32/main/include/AppTask.h

@@ -0,0 +1,84 @@
+/*
+ *
+ *    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.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "AppEvent.h"
+#include "BoltLockManager.h"
+
+#include "freertos/FreeRTOS.h"
+#include <ble/BLEEndPoint.h>
+#include <lib/support/CodeUtils.h>
+#include <platform/CHIPDeviceLayer.h>
+
+class AppTask
+{
+
+public:
+    int StartAppTask();
+    static void AppTaskMain(void * pvParameter);
+
+    void PostLockActionRequest(int32_t aActor, BoltLockManager::Action_t aAction);
+    void PostEvent(const AppEvent * event);
+
+    void ButtonEventHandler(uint8_t btnIdx, uint8_t btnAction);
+
+private:
+    friend AppTask & GetAppTask(void);
+
+    int Init();
+
+    static void ActionInitiated(BoltLockManager::Action_t aAction, int32_t aActor);
+    static void ActionCompleted(BoltLockManager::Action_t aAction);
+
+    void CancelTimer(void);
+
+    void DispatchEvent(AppEvent * event);
+
+    static void FunctionTimerEventHandler(AppEvent * aEvent);
+    static void FunctionHandler(AppEvent * aEvent);
+    static void LockActionEventHandler(AppEvent * aEvent);
+    static void TimerEventHandler(TimerHandle_t xTimer);
+
+    static void UpdateClusterState(void);
+
+    void StartTimer(uint32_t aTimeoutMs);
+
+    enum Function_t
+    {
+        kFunction_NoneSelected   = 0,
+        kFunction_SoftwareUpdate = 0,
+        kFunction_StartBleAdv    = 1,
+        kFunction_FactoryReset   = 2,
+
+        kFunction_Invalid
+    } Function;
+
+    Function_t mFunction;
+    bool mFunctionTimerActive;
+    bool mSyncClusterToButtonAction;
+
+    static AppTask sAppTask;
+};
+
+inline AppTask & GetAppTask(void)
+{
+    return AppTask::sAppTask;
+}

+ 85 - 0
examples/lock-app/esp32/main/include/BoltLockManager.h

@@ -0,0 +1,85 @@
+/*
+ *
+ *    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.
+ */
+
+#ifndef LOCK_MANAGER_H
+#define LOCK_MANAGER_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "AppEvent.h"
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/timers.h" // provides FreeRTOS timer support
+
+class BoltLockManager
+{
+public:
+    enum Action_t
+    {
+        LOCK_ACTION = 0,
+        UNLOCK_ACTION,
+
+        INVALID_ACTION
+    } Action;
+
+    enum State_t
+    {
+        kState_LockingInitiated = 0,
+        kState_LockingCompleted,
+        kState_UnlockingInitiated,
+        kState_UnlockingCompleted,
+    } State;
+
+    int Init();
+    bool IsUnlocked();
+    void EnableAutoRelock(bool aOn);
+    void SetAutoLockDuration(uint32_t aDurationInSecs);
+    bool IsActionInProgress();
+    bool InitiateAction(int32_t aActor, Action_t aAction);
+
+    typedef void (*Callback_fn_initiated)(Action_t, int32_t aActor);
+    typedef void (*Callback_fn_completed)(Action_t);
+    void SetCallbacks(Callback_fn_initiated aActionInitiated_CB, Callback_fn_completed aActionCompleted_CB);
+
+private:
+    friend BoltLockManager & BoltLockMgr(void);
+    State_t mState;
+
+    Callback_fn_initiated mActionInitiated_CB;
+    Callback_fn_completed mActionCompleted_CB;
+
+    bool mAutoRelock;
+    uint32_t mAutoLockDuration;
+    bool mAutoLockTimerArmed;
+
+    void CancelTimer(void);
+    void StartTimer(uint32_t aTimeoutMs);
+
+    static void TimerEventHandler(TimerHandle_t xTimer);
+    static void AutoReLockTimerEventHandler(AppEvent * aEvent);
+    static void ActuatorMovementTimerEventHandler(AppEvent * aEvent);
+
+    static BoltLockManager sLock;
+};
+
+inline BoltLockManager & BoltLockMgr(void)
+{
+    return BoltLockManager::sLock;
+}
+
+#endif // LOCK_MANAGER_H

+ 64 - 0
examples/lock-app/esp32/main/include/Button.h

@@ -0,0 +1,64 @@
+/*
+ *
+ *    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.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include "AppConfig.h"
+#include "AppTask.h"
+#include "freertos/FreeRTOS.h"
+
+class Button
+{
+public:
+    esp_err_t Init(gpio_num_t gpioNum, uint16_t debouncePeriod);
+    bool Poll();
+    bool IsPressed();
+    uint32_t GetStateStartTime();
+    uint32_t GetStateDuration();
+    uint32_t GetPrevStateDuration();
+
+private:
+    // in ticks
+    uint32_t mLastReadTime;
+    // in ticks
+    uint32_t mStateStartTime;
+    // in ticks
+    uint32_t mPrevStateDur;
+    gpio_num_t mGPIONum;
+    // in ticks
+    uint16_t mDebouncePeriod;
+    // true when button is pressed
+    bool mState;
+    bool mLastPolledState;
+};
+
+inline bool Button::IsPressed()
+{
+    return mState;
+}
+
+inline uint32_t Button::GetStateStartTime()
+{
+    return mStateStartTime * portTICK_PERIOD_MS;
+}
+
+inline uint32_t Button::GetPrevStateDuration()
+{
+    return mPrevStateDur * portTICK_PERIOD_MS;
+}

+ 45 - 0
examples/lock-app/esp32/main/include/LEDWidget.h

@@ -0,0 +1,45 @@
+/*
+ *
+ *    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.
+ */
+
+#pragma once
+#include "driver/gpio.h"
+#include <stdint.h>
+
+#ifndef LED_WIDGET_H
+#define LED_WIDGET_H
+
+class LEDWidget
+{
+public:
+    void Init(gpio_num_t ledNum);
+    void Set(bool state);
+    void Invert(void);
+    void Blink(uint32_t changeRateMS);
+    void Blink(uint32_t onTimeMS, uint32_t offTimeMS);
+    void Animate();
+
+private:
+    int64_t mLastChangeTimeUS;
+    uint32_t mBlinkOnTimeMS;
+    uint32_t mBlinkOffTimeMS;
+    gpio_num_t mGPIONum;
+    bool mState;
+
+    void DoSet(bool state);
+};
+
+#endif // LED_WIDGET_H

+ 8 - 0
examples/lock-app/esp32/main/main.cpp

@@ -15,6 +15,7 @@
  *    limitations under the License.
  */
 
+#include "AppTask.h"
 #include "CHIPDeviceManager.h"
 #include "DeviceCallbacks.h"
 #include "esp_heap_caps_init.h"
@@ -68,6 +69,13 @@ extern "C" void app_main()
 
     InitServer();
 
+    ESP_LOGI(TAG, "------------------------Starting App Task---------------------------");
+    err = GetAppTask().StartAppTask();
+    if (err != CHIP_NO_ERROR)
+    {
+        ESP_LOGE(TAG, "GetAppTask().Init() failed");
+    }
+
     while (true)
     {
         vTaskDelay(500 / portTICK_PERIOD_MS);