AppTask.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. /*
  2. *
  3. * Copyright (c) 2020 Project CHIP Authors
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. #include "AppTask.h"
  18. #include "AppConfig.h"
  19. #include "AppEvent.h"
  20. #include "Button.h"
  21. #include "LEDWidget.h"
  22. #include "esp_log.h"
  23. #include <app/common/gen/attribute-id.h>
  24. #include <app/common/gen/attribute-type.h>
  25. #include <app/common/gen/cluster-id.h>
  26. #include <app/server/OnboardingCodesUtil.h>
  27. #include <app/server/Server.h>
  28. #include <app/util/af-enums.h>
  29. #include <app/util/attribute-storage.h>
  30. #include <platform/CHIPDeviceLayer.h>
  31. #include <platform/internal/CHIPDeviceLayerInternal.h>
  32. #include <support/CodeUtils.h>
  33. #define FACTORY_RESET_TRIGGER_TIMEOUT 3000
  34. #define FACTORY_RESET_CANCEL_WINDOW_TIMEOUT 3000
  35. #define APP_EVENT_QUEUE_SIZE 10
  36. #define APP_TASK_STACK_SIZE (3000)
  37. #define APP_TASK_PRIORITY 2
  38. #define STATUS_LED_GPIO_NUM GPIO_NUM_2 // Use LED1 (blue LED) as status LED on DevKitC
  39. static const char * const TAG = "lock-app";
  40. namespace {
  41. TimerHandle_t sFunctionTimer; // FreeRTOS app sw timer.
  42. LEDWidget sStatusLED;
  43. LEDWidget sLockLED;
  44. Button resetButton;
  45. Button lockButton;
  46. BaseType_t sAppTaskHandle;
  47. QueueHandle_t sAppEventQueue;
  48. bool sHaveBLEConnections = false;
  49. bool sHaveServiceConnectivity = false;
  50. StackType_t appStack[APP_TASK_STACK_SIZE / sizeof(StackType_t)];
  51. } // namespace
  52. using namespace ::chip::DeviceLayer;
  53. AppTask AppTask::sAppTask;
  54. CHIP_ERROR AppTask::StartAppTask()
  55. {
  56. sAppEventQueue = xQueueCreate(APP_EVENT_QUEUE_SIZE, sizeof(AppEvent));
  57. if (sAppEventQueue == NULL)
  58. {
  59. ESP_LOGE(TAG, "Failed to allocate app event queue");
  60. return APP_ERROR_EVENT_QUEUE_FAILED;
  61. }
  62. // Start App task.
  63. sAppTaskHandle = xTaskCreate(AppTaskMain, APP_TASK_NAME, ArraySize(appStack), NULL, 1, NULL);
  64. return sAppTaskHandle ? CHIP_NO_ERROR : APP_ERROR_CREATE_TASK_FAILED;
  65. }
  66. CHIP_ERROR AppTask::Init()
  67. {
  68. // Create FreeRTOS sw timer for Function Selection.
  69. sFunctionTimer = xTimerCreate("FnTmr", // Just a text name, not used by the RTOS kernel
  70. 1, // == default timer period (mS)
  71. false, // no timer reload (==one-shot)
  72. (void *) this, // init timer id = app task obj context
  73. TimerEventHandler // timer callback handler
  74. );
  75. CHIP_ERROR err = BoltLockMgr().Init();
  76. if (err != CHIP_NO_ERROR)
  77. {
  78. ESP_LOGI(TAG, "BoltLockMgr().Init() failed");
  79. return err;
  80. }
  81. BoltLockMgr().SetCallbacks(ActionInitiated, ActionCompleted);
  82. sStatusLED.Init(SYSTEM_STATE_LED);
  83. sLockLED.Init(LOCK_STATE_LED);
  84. resetButton.Init(APP_FUNCTION_BUTTON, APP_BUTTON_DEBOUNCE_PERIOD_MS);
  85. lockButton.Init(APP_LOCK_BUTTON, APP_BUTTON_DEBOUNCE_PERIOD_MS);
  86. sLockLED.Set(!BoltLockMgr().IsUnlocked());
  87. UpdateClusterState();
  88. ConfigurationMgr().LogDeviceConfig();
  89. PrintOnboardingCodes(chip::RendezvousInformationFlag(chip::RendezvousInformationFlag::kBLE));
  90. return err;
  91. }
  92. void AppTask::AppTaskMain(void * pvParameter)
  93. {
  94. int err;
  95. AppEvent event;
  96. uint64_t mLastChangeTimeUS = 0;
  97. err = sAppTask.Init();
  98. if (err != CHIP_NO_ERROR)
  99. {
  100. ESP_LOGI(TAG, "AppTask.Init() failed due to %d", err);
  101. return;
  102. }
  103. ESP_LOGI(TAG, "App Task started");
  104. while (true)
  105. {
  106. BaseType_t eventReceived = xQueueReceive(sAppEventQueue, &event, pdMS_TO_TICKS(10));
  107. while (eventReceived == pdTRUE)
  108. {
  109. sAppTask.DispatchEvent(&event);
  110. eventReceived = xQueueReceive(sAppEventQueue, &event, 0);
  111. }
  112. // Collect connectivity and configuration state from the CHIP stack. Because
  113. // the CHIP event loop is being run in a separate task, the stack must be
  114. // locked while these values are queried. However we use a non-blocking
  115. // lock request (TryLockCHIPStack()) to avoid blocking other UI activities
  116. // when the CHIP task is busy (e.g. with a long crypto operation).
  117. if (PlatformMgr().TryLockChipStack())
  118. {
  119. sHaveBLEConnections = (ConnectivityMgr().NumBLEConnections() != 0);
  120. sHaveServiceConnectivity = ConnectivityMgr().HaveServiceConnectivity();
  121. PlatformMgr().UnlockChipStack();
  122. }
  123. // Update the status LED if factory reset has not been initiated.
  124. //
  125. // If system has "full connectivity", keep the LED On constantly.
  126. //
  127. // If no connectivity to the service OR subscriptions are not fully
  128. // established THEN blink the LED Off for a short period of time.
  129. //
  130. // If the system has ble connection(s) uptill the stage above, THEN blink
  131. // the LEDs at an even rate of 100ms.
  132. //
  133. // Otherwise, blink the LED ON for a very short time.
  134. if (sAppTask.mFunction != kFunction_FactoryReset)
  135. {
  136. // Consider the system to be "fully connected" if it has service
  137. // connectivity
  138. if (sHaveServiceConnectivity)
  139. {
  140. sStatusLED.Set(true);
  141. }
  142. else if (sHaveBLEConnections)
  143. {
  144. sStatusLED.Blink(100, 100);
  145. }
  146. else
  147. {
  148. sStatusLED.Blink(50, 950);
  149. }
  150. }
  151. sStatusLED.Animate();
  152. sLockLED.Animate();
  153. uint64_t nowUS = chip::System::Clock::GetMonotonicMicroseconds();
  154. uint64_t nextChangeTimeUS = mLastChangeTimeUS + 5 * 1000 * 1000UL;
  155. if (nowUS > nextChangeTimeUS)
  156. {
  157. mLastChangeTimeUS = nowUS;
  158. }
  159. if (lockButton.Poll())
  160. {
  161. if (lockButton.IsPressed())
  162. {
  163. GetAppTask().ButtonEventHandler(APP_LOCK_BUTTON, APP_BUTTON_PRESSED);
  164. }
  165. }
  166. if (resetButton.Poll())
  167. {
  168. if (resetButton.IsPressed())
  169. {
  170. GetAppTask().ButtonEventHandler(APP_FUNCTION_BUTTON, APP_BUTTON_PRESSED);
  171. }
  172. }
  173. }
  174. }
  175. void AppTask::LockActionEventHandler(AppEvent * aEvent)
  176. {
  177. bool initiated = false;
  178. BoltLockManager::Action_t action;
  179. int32_t actor;
  180. CHIP_ERROR err = CHIP_NO_ERROR;
  181. if (aEvent->Type == AppEvent::kEventType_Lock)
  182. {
  183. action = static_cast<BoltLockManager::Action_t>(aEvent->LockEvent.Action);
  184. actor = aEvent->LockEvent.Actor;
  185. }
  186. else if (aEvent->Type == AppEvent::kEventType_Button)
  187. {
  188. if (BoltLockMgr().IsUnlocked())
  189. {
  190. action = BoltLockManager::LOCK_ACTION;
  191. }
  192. else
  193. {
  194. action = BoltLockManager::UNLOCK_ACTION;
  195. }
  196. actor = AppEvent::kEventType_Button;
  197. }
  198. else
  199. {
  200. err = APP_ERROR_UNHANDLED_EVENT;
  201. }
  202. if (err == CHIP_NO_ERROR)
  203. {
  204. initiated = BoltLockMgr().InitiateAction(actor, action);
  205. if (!initiated)
  206. {
  207. ESP_LOGI(TAG, "Action is already in progress or active.");
  208. }
  209. }
  210. }
  211. void AppTask::ButtonEventHandler(uint8_t btnIdx, uint8_t btnAction)
  212. {
  213. if (btnIdx != APP_LOCK_BUTTON && btnIdx != APP_FUNCTION_BUTTON)
  214. {
  215. return;
  216. }
  217. AppEvent button_event = {};
  218. button_event.Type = AppEvent::kEventType_Button;
  219. button_event.ButtonEvent.PinNo = btnIdx;
  220. button_event.ButtonEvent.Action = btnAction;
  221. if (btnIdx == APP_LOCK_BUTTON && btnAction == APP_BUTTON_PRESSED)
  222. {
  223. button_event.Handler = LockActionEventHandler;
  224. sAppTask.PostEvent(&button_event);
  225. }
  226. else if (btnIdx == APP_FUNCTION_BUTTON)
  227. {
  228. button_event.Handler = FunctionHandler;
  229. sAppTask.PostEvent(&button_event);
  230. }
  231. }
  232. void AppTask::TimerEventHandler(TimerHandle_t xTimer)
  233. {
  234. AppEvent event;
  235. event.Type = AppEvent::kEventType_Timer;
  236. event.TimerEvent.Context = (void *) xTimer;
  237. event.Handler = FunctionTimerEventHandler;
  238. sAppTask.PostEvent(&event);
  239. }
  240. void AppTask::FunctionTimerEventHandler(AppEvent * aEvent)
  241. {
  242. if (aEvent->Type != AppEvent::kEventType_Timer)
  243. {
  244. return;
  245. }
  246. // If we reached here, the button was held past FACTORY_RESET_TRIGGER_TIMEOUT,
  247. // initiate factory reset
  248. if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_StartBleAdv)
  249. {
  250. ESP_LOGI(TAG, "Factory Reset Triggered. Release button within %ums to cancel.", FACTORY_RESET_CANCEL_WINDOW_TIMEOUT);
  251. // Start timer for FACTORY_RESET_CANCEL_WINDOW_TIMEOUT to allow user to
  252. // cancel, if required.
  253. sAppTask.StartTimer(FACTORY_RESET_CANCEL_WINDOW_TIMEOUT);
  254. sAppTask.mFunction = kFunction_FactoryReset;
  255. // Turn off all LEDs before starting blink to make sure blink is
  256. // co-ordinated.
  257. sStatusLED.Set(false);
  258. sLockLED.Set(false);
  259. sStatusLED.Blink(500);
  260. sLockLED.Blink(500);
  261. }
  262. else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset)
  263. {
  264. // Actually trigger Factory Reset
  265. sAppTask.mFunction = kFunction_NoneSelected;
  266. ConfigurationMgr().InitiateFactoryReset();
  267. }
  268. }
  269. void AppTask::FunctionHandler(AppEvent * aEvent)
  270. {
  271. if (aEvent->ButtonEvent.PinNo != APP_FUNCTION_BUTTON)
  272. {
  273. return;
  274. }
  275. // To trigger software update: press the APP_FUNCTION_BUTTON button briefly (<
  276. // FACTORY_RESET_TRIGGER_TIMEOUT) To initiate factory reset: press the
  277. // APP_FUNCTION_BUTTON for FACTORY_RESET_TRIGGER_TIMEOUT +
  278. // FACTORY_RESET_CANCEL_WINDOW_TIMEOUT All LEDs start blinking after
  279. // FACTORY_RESET_TRIGGER_TIMEOUT to signal factory reset has been initiated.
  280. // To cancel factory reset: release the APP_FUNCTION_BUTTON once all LEDs
  281. // start blinking within the FACTORY_RESET_CANCEL_WINDOW_TIMEOUT
  282. if (aEvent->ButtonEvent.Action == APP_BUTTON_PRESSED)
  283. {
  284. if (!sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_NoneSelected)
  285. {
  286. sAppTask.StartTimer(FACTORY_RESET_TRIGGER_TIMEOUT);
  287. sAppTask.mFunction = kFunction_StartBleAdv;
  288. }
  289. }
  290. else
  291. {
  292. // If the button was released before factory reset got initiated, start BLE advertissement in fast mode
  293. if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_StartBleAdv)
  294. {
  295. sAppTask.CancelTimer();
  296. sAppTask.mFunction = kFunction_NoneSelected;
  297. if (!ConnectivityMgr().IsThreadProvisioned())
  298. {
  299. // Enable BLE advertisements
  300. ConnectivityMgr().SetBLEAdvertisingEnabled(true);
  301. ConnectivityMgr().SetBLEAdvertisingMode(ConnectivityMgr().kFastAdvertising);
  302. }
  303. else
  304. {
  305. ESP_LOGI(TAG, "Network is already provisioned, Ble advertissement not enabled");
  306. }
  307. }
  308. else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset)
  309. {
  310. // Set lock status LED back to show state of lock.
  311. sLockLED.Set(!BoltLockMgr().IsUnlocked());
  312. sAppTask.CancelTimer();
  313. // Change the function to none selected since factory reset has been
  314. // canceled.
  315. sAppTask.mFunction = kFunction_NoneSelected;
  316. ESP_LOGI(TAG, "Factory Reset has been Canceled");
  317. }
  318. }
  319. }
  320. void AppTask::CancelTimer()
  321. {
  322. if (xTimerStop(sFunctionTimer, 0) == pdFAIL)
  323. {
  324. ESP_LOGI(TAG, "app timer stop() failed");
  325. return;
  326. }
  327. mFunctionTimerActive = false;
  328. }
  329. void AppTask::StartTimer(uint32_t aTimeoutInMs)
  330. {
  331. if (xTimerIsTimerActive(sFunctionTimer))
  332. {
  333. ESP_LOGI(TAG, "app timer already started!");
  334. CancelTimer();
  335. }
  336. // timer is not active, change its period to required value (== restart).
  337. // FreeRTOS- Block for a maximum of 100 ticks if the change period command
  338. // cannot immediately be sent to the timer command queue.
  339. if (xTimerChangePeriod(sFunctionTimer, aTimeoutInMs / portTICK_PERIOD_MS, 100) != pdPASS)
  340. {
  341. ESP_LOGI(TAG, "app timer start() failed");
  342. return;
  343. }
  344. mFunctionTimerActive = true;
  345. }
  346. void AppTask::ActionInitiated(BoltLockManager::Action_t aAction, int32_t aActor)
  347. {
  348. // If the action has been initiated by the lock, update the bolt lock trait
  349. // and start flashing the LEDs rapidly to indicate action initiation.
  350. if (aAction == BoltLockManager::LOCK_ACTION)
  351. {
  352. ESP_LOGI(TAG, "Lock Action has been initiated");
  353. }
  354. else if (aAction == BoltLockManager::UNLOCK_ACTION)
  355. {
  356. ESP_LOGI(TAG, "Unlock Action has been initiated");
  357. }
  358. if (aActor == AppEvent::kEventType_Button)
  359. {
  360. sAppTask.mSyncClusterToButtonAction = true;
  361. }
  362. sLockLED.Blink(50, 50);
  363. }
  364. void AppTask::ActionCompleted(BoltLockManager::Action_t aAction)
  365. {
  366. // if the action has been completed by the lock, update the bolt lock trait.
  367. // Turn on the lock LED if in a LOCKED state OR
  368. // Turn off the lock LED if in an UNLOCKED state.
  369. if (aAction == BoltLockManager::LOCK_ACTION)
  370. {
  371. ESP_LOGI(TAG, "Lock Action has been completed");
  372. sLockLED.Set(true);
  373. }
  374. else if (aAction == BoltLockManager::UNLOCK_ACTION)
  375. {
  376. ESP_LOGI(TAG, "Unlock Action has been completed");
  377. sLockLED.Set(false);
  378. }
  379. }
  380. void AppTask::PostLockActionRequest(int32_t aActor, BoltLockManager::Action_t aAction)
  381. {
  382. AppEvent event;
  383. event.Type = AppEvent::kEventType_Lock;
  384. event.LockEvent.Actor = aActor;
  385. event.LockEvent.Action = aAction;
  386. event.Handler = LockActionEventHandler;
  387. PostEvent(&event);
  388. }
  389. void AppTask::PostEvent(const AppEvent * aEvent)
  390. {
  391. if (sAppEventQueue != NULL)
  392. {
  393. if (!xQueueSend(sAppEventQueue, aEvent, 1))
  394. {
  395. ESP_LOGI(TAG, "Failed to post event to app task event queue");
  396. }
  397. }
  398. }
  399. void AppTask::DispatchEvent(AppEvent * aEvent)
  400. {
  401. if (aEvent->Handler)
  402. {
  403. aEvent->Handler(aEvent);
  404. }
  405. else
  406. {
  407. ESP_LOGI(TAG, "Event received with no handler. Dropping event.");
  408. }
  409. }
  410. /* if unlocked then it locked it first*/
  411. void AppTask::UpdateClusterState(void)
  412. {
  413. uint8_t newValue = !BoltLockMgr().IsUnlocked();
  414. // write the new on/off value
  415. EmberAfStatus status = emberAfWriteAttribute(1, ZCL_ON_OFF_CLUSTER_ID, ZCL_ON_OFF_ATTRIBUTE_ID, CLUSTER_MASK_SERVER,
  416. (uint8_t *) &newValue, ZCL_BOOLEAN_ATTRIBUTE_TYPE);
  417. if (status != EMBER_ZCL_STATUS_SUCCESS)
  418. {
  419. ESP_LOGI(TAG, "ERR: updating on/off %x", status);
  420. }
  421. }