From aed460d9d1c5e334ad2965222195693c2c71e8ee Mon Sep 17 00:00:00 2001 From: Song Guo Date: Thu, 27 May 2021 21:46:24 +0800 Subject: [PATCH 1/2] [hotfix][commissioner] Add runtime check in OnOperationalCredentialsProvisioningCompletion (#7177) --- src/controller/CHIPDeviceController.cpp | 39 ++++++++++++++----------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp index 978cba816a1801..93e57b072f6868 100644 --- a/src/controller/CHIPDeviceController.cpp +++ b/src/controller/CHIPDeviceController.cpp @@ -1365,26 +1365,31 @@ CHIP_ERROR DeviceCommissioner::OnOperationalCredentialsProvisioningCompletion(De VerifyOrReturnError(device != nullptr, CHIP_ERROR_INVALID_ARGUMENT); #if CONFIG_USE_CLUSTERS_FOR_IP_COMMISSIONING - AdvanceCommissioningStage(CHIP_NO_ERROR); -#else - mPairingSession.ToSerializable(device->GetPairing()); - mSystemLayer->CancelTimer(OnSessionEstablishmentTimeoutCallback, this); - - mPairedDevices.Insert(device->GetDeviceId()); - mPairedDevicesUpdated = true; - - // Note - This assumes storage is synchronous, the device must be in storage before we can cleanup - // the rendezvous session and mark pairing success - PersistDevice(device); - // Also persist the device list at this time - // This makes sure that a newly added device is immediately available - PersistDeviceList(); - if (mPairingDelegate != nullptr) + if (mIsIPRendezvous) { - mPairingDelegate->OnStatusUpdate(DevicePairingDelegate::SecurePairingSuccess); + AdvanceCommissioningStage(CHIP_NO_ERROR); } - RendezvousCleanup(CHIP_NO_ERROR); + else #endif + { + mPairingSession.ToSerializable(device->GetPairing()); + mSystemLayer->CancelTimer(OnSessionEstablishmentTimeoutCallback, this); + + mPairedDevices.Insert(device->GetDeviceId()); + mPairedDevicesUpdated = true; + + // Note - This assumes storage is synchronous, the device must be in storage before we can cleanup + // the rendezvous session and mark pairing success + PersistDevice(device); + // Also persist the device list at this time + // This makes sure that a newly added device is immediately available + PersistDeviceList(); + if (mPairingDelegate != nullptr) + { + mPairingDelegate->OnStatusUpdate(DevicePairingDelegate::SecurePairingSuccess); + } + RendezvousCleanup(CHIP_NO_ERROR); + } return CHIP_NO_ERROR; } From 4262811d4e4b0c2ae595108cbdf6bdb9306cec8f Mon Sep 17 00:00:00 2001 From: Sweety Date: Thu, 27 May 2021 19:24:11 +0530 Subject: [PATCH 2/2] ESP32: Add fixes for lock-app (#6658) --- examples/lock-app/esp32/main/AppTask.cpp | 493 ++++++++++++++++++ .../lock-app/esp32/main/BoltLockManager.cpp | 217 ++++++++ examples/lock-app/esp32/main/Button.cpp | 65 +++ .../lock-app/esp32/main/DeviceCallbacks.cpp | 29 +- .../lock-app/esp32/main/Kconfig.projbuild | 2 - examples/lock-app/esp32/main/LEDWidget.cpp | 85 +++ .../lock-app/esp32/main/include/AppConfig.h | 39 ++ .../lock-app/esp32/main/include/AppEvent.h | 54 ++ .../lock-app/esp32/main/include/AppTask.h | 84 +++ .../esp32/main/include/BoltLockManager.h | 85 +++ examples/lock-app/esp32/main/include/Button.h | 64 +++ .../lock-app/esp32/main/include/LEDWidget.h | 45 ++ examples/lock-app/esp32/main/main.cpp | 8 + 13 files changed, 1267 insertions(+), 3 deletions(-) create mode 100644 examples/lock-app/esp32/main/AppTask.cpp create mode 100644 examples/lock-app/esp32/main/BoltLockManager.cpp create mode 100644 examples/lock-app/esp32/main/Button.cpp create mode 100644 examples/lock-app/esp32/main/LEDWidget.cpp create mode 100644 examples/lock-app/esp32/main/include/AppConfig.h create mode 100644 examples/lock-app/esp32/main/include/AppEvent.h create mode 100644 examples/lock-app/esp32/main/include/AppTask.h create mode 100644 examples/lock-app/esp32/main/include/BoltLockManager.h create mode 100644 examples/lock-app/esp32/main/include/Button.h create mode 100644 examples/lock-app/esp32/main/include/LEDWidget.h diff --git a/examples/lock-app/esp32/main/AppTask.cpp b/examples/lock-app/esp32/main/AppTask.cpp new file mode 100644 index 00000000000000..507b0a260037e2 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include + +#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(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); + } +} diff --git a/examples/lock-app/esp32/main/BoltLockManager.cpp b/examples/lock-app/esp32/main/BoltLockManager.cpp new file mode 100644 index 00000000000000..9df6bf152775d8 --- /dev/null +++ b/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 +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(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(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(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); + } + } +} diff --git a/examples/lock-app/esp32/main/Button.cpp b/examples/lock-app/esp32/main/Button.cpp new file mode 100644 index 00000000000000..ebf9bbd9706ff8 --- /dev/null +++ b/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; +} diff --git a/examples/lock-app/esp32/main/DeviceCallbacks.cpp b/examples/lock-app/esp32/main/DeviceCallbacks.cpp index 408a25a82e5062..d852502f9888c9 100644 --- a/examples/lock-app/esp32/main/DeviceCallbacks.cpp +++ b/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; +} diff --git a/examples/lock-app/esp32/main/Kconfig.projbuild b/examples/lock-app/esp32/main/Kconfig.projbuild index 88334f4bbb3da2..9b215a6c4bf88f 100644 --- a/examples/lock-app/esp32/main/Kconfig.projbuild +++ b/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 diff --git a/examples/lock-app/esp32/main/LEDWidget.cpp b/examples/lock-app/esp32/main/LEDWidget.cpp new file mode 100644 index 00000000000000..31850806ba5e43 --- /dev/null +++ b/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 + +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); + } +} diff --git a/examples/lock-app/esp32/main/include/AppConfig.h b/examples/lock-app/esp32/main/include/AppConfig.h new file mode 100644 index 00000000000000..61256e16425716 --- /dev/null +++ b/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 diff --git a/examples/lock-app/esp32/main/include/AppEvent.h b/examples/lock-app/esp32/main/include/AppEvent.h new file mode 100644 index 00000000000000..783d29ca6feb5d --- /dev/null +++ b/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; +}; diff --git a/examples/lock-app/esp32/main/include/AppTask.h b/examples/lock-app/esp32/main/include/AppTask.h new file mode 100644 index 00000000000000..3ba549c58cd683 --- /dev/null +++ b/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 +#include + +#include "AppEvent.h" +#include "BoltLockManager.h" + +#include "freertos/FreeRTOS.h" +#include +#include +#include + +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; +} diff --git a/examples/lock-app/esp32/main/include/BoltLockManager.h b/examples/lock-app/esp32/main/include/BoltLockManager.h new file mode 100644 index 00000000000000..baeddb22cf4c6f --- /dev/null +++ b/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 +#include + +#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 diff --git a/examples/lock-app/esp32/main/include/Button.h b/examples/lock-app/esp32/main/include/Button.h new file mode 100644 index 00000000000000..53ae87884b2865 --- /dev/null +++ b/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 + +#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; +} diff --git a/examples/lock-app/esp32/main/include/LEDWidget.h b/examples/lock-app/esp32/main/include/LEDWidget.h new file mode 100644 index 00000000000000..467fc153dec971 --- /dev/null +++ b/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 + +#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 diff --git a/examples/lock-app/esp32/main/main.cpp b/examples/lock-app/esp32/main/main.cpp index 881c60f156084c..e65cf0ef05253e 100644 --- a/examples/lock-app/esp32/main/main.cpp +++ b/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);