Fix for x86_64 build fail
[platform/upstream/connectedhomeip.git] / examples / lock-app / nrfconnect / main / AppTask.cpp
1 /*
2  *
3  *    Copyright (c) 2020 Project CHIP Authors
4  *    All rights reserved.
5  *
6  *    Licensed under the Apache License, Version 2.0 (the "License");
7  *    you may not use this file except in compliance with the License.
8  *    You may obtain a copy of the License at
9  *
10  *        http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *    Unless required by applicable law or agreed to in writing, software
13  *    distributed under the License is distributed on an "AS IS" BASIS,
14  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *    See the License for the specific language governing permissions and
16  *    limitations under the License.
17  */
18
19 #include "AppTask.h"
20 #include "AppConfig.h"
21 #include "BoltLockManager.h"
22 #include "LEDWidget.h"
23 #include "OnboardingCodesUtil.h"
24 #include "Server.h"
25 #include "Service.h"
26 #include "ThreadUtil.h"
27
28 #ifdef CONFIG_CHIP_NFC_COMMISSIONING
29 #include "NFCWidget.h"
30 #endif
31
32 #include "attribute-storage.h"
33 #include "gen/attribute-id.h"
34 #include "gen/attribute-type.h"
35 #include "gen/cluster-id.h"
36
37 #include <platform/CHIPDeviceLayer.h>
38
39 #include <dk_buttons_and_leds.h>
40 #include <logging/log.h>
41 #include <setup_payload/QRCodeSetupPayloadGenerator.h>
42 #include <setup_payload/SetupPayload.h>
43 #include <zephyr.h>
44
45 #include <algorithm>
46
47 #define FACTORY_RESET_TRIGGER_TIMEOUT 3000
48 #define FACTORY_RESET_CANCEL_WINDOW_TIMEOUT 3000
49 #define APP_EVENT_QUEUE_SIZE 10
50 #define BUTTON_PUSH_EVENT 1
51 #define BUTTON_RELEASE_EVENT 0
52
53 LOG_MODULE_DECLARE(app);
54 K_MSGQ_DEFINE(sAppEventQueue, sizeof(AppEvent), APP_EVENT_QUEUE_SIZE, alignof(AppEvent));
55
56 constexpr uint32_t kPublishServicePeriodUs = 5000000;
57
58 static LEDWidget sStatusLED;
59 static LEDWidget sLockLED;
60 static LEDWidget sUnusedLED;
61 static LEDWidget sUnusedLED_1;
62
63 #ifdef CONFIG_CHIP_NFC_COMMISSIONING
64 static NFCWidget sNFC;
65 #endif
66
67 static bool sIsThreadProvisioned     = false;
68 static bool sIsThreadEnabled         = false;
69 static bool sHaveBLEConnections      = false;
70 static bool sHaveServiceConnectivity = false;
71
72 static k_timer sFunctionTimer;
73
74 using namespace ::chip::DeviceLayer;
75
76 AppTask AppTask::sAppTask;
77
78 int AppTask::Init()
79 {
80     // Initialize LEDs
81     LEDWidget::InitGpio();
82
83     sStatusLED.Init(SYSTEM_STATE_LED);
84     sLockLED.Init(LOCK_STATE_LED);
85     sLockLED.Set(!BoltLockMgr().IsUnlocked());
86
87     sUnusedLED.Init(DK_LED3);
88     sUnusedLED_1.Init(DK_LED4);
89
90     // Initialize buttons
91     int ret = dk_buttons_init(ButtonEventHandler);
92     if (ret)
93     {
94         LOG_ERR("dk_buttons_init() failed");
95         return ret;
96     }
97
98     // Initialize timer user data
99     k_timer_init(&sFunctionTimer, &AppTask::TimerEventHandler, nullptr);
100     k_timer_user_data_set(&sFunctionTimer, this);
101
102     BoltLockMgr().Init();
103     BoltLockMgr().SetCallbacks(ActionInitiated, ActionCompleted);
104
105     // Init ZCL Data Model and start server
106     InitServer();
107     ConfigurationMgr().LogDeviceConfig();
108     PrintOnboardingCodes(chip::RendezvousInformationFlags::kBLE);
109
110 #ifdef CONFIG_CHIP_NFC_COMMISSIONING
111     ret = sNFC.Init(ConnectivityMgr());
112     if (ret)
113     {
114         LOG_ERR("NFC initialization failed");
115         return ret;
116     }
117
118     PlatformMgr().AddEventHandler(AppTask::ThreadProvisioningHandler, 0);
119 #endif
120
121     return 0;
122 }
123
124 int AppTask::StartApp()
125 {
126     int ret                            = Init();
127     uint64_t mLastPublishServiceTimeUS = 0;
128
129     if (ret)
130     {
131         LOG_ERR("AppTask.Init() failed");
132         return ret;
133     }
134
135     AppEvent event = {};
136
137     while (true)
138     {
139         ret = k_msgq_get(&sAppEventQueue, &event, K_MSEC(10));
140
141         while (!ret)
142         {
143             DispatchEvent(&event);
144             ret = k_msgq_get(&sAppEventQueue, &event, K_NO_WAIT);
145         }
146
147         // Collect connectivity and configuration state from the CHIP stack.  Because the
148         // CHIP event loop is being run in a separate task, the stack must be locked
149         // while these values are queried.  However we use a non-blocking lock request
150         // (TryLockChipStack()) to avoid blocking other UI activities when the CHIP
151         // task is busy (e.g. with a long crypto operation).
152
153         if (PlatformMgr().TryLockChipStack())
154         {
155             sIsThreadProvisioned     = ConnectivityMgr().IsThreadProvisioned();
156             sIsThreadEnabled         = ConnectivityMgr().IsThreadEnabled();
157             sHaveBLEConnections      = (ConnectivityMgr().NumBLEConnections() != 0);
158             sHaveServiceConnectivity = ConnectivityMgr().HaveServiceConnectivity();
159             PlatformMgr().UnlockChipStack();
160         }
161
162         // Update the status LED if factory reset has not been initiated.
163         //
164         // If system has "full connectivity", keep the LED On constantly.
165         //
166         // If thread and service provisioned, but not attached to the thread network yet OR no
167         // connectivity to the service OR subscriptions are not fully established
168         // THEN blink the LED Off for a short period of time.
169         //
170         // If the system has ble connection(s) uptill the stage above, THEN blink the LEDs at an even
171         // rate of 100ms.
172         //
173         // Otherwise, blink the LED ON for a very short time.
174         if (sAppTask.mFunction != kFunction_FactoryReset)
175         {
176             if (sHaveServiceConnectivity)
177             {
178                 sStatusLED.Set(true);
179             }
180             else if (sIsThreadProvisioned && sIsThreadEnabled)
181             {
182                 sStatusLED.Blink(950, 50);
183             }
184             else if (sHaveBLEConnections)
185             {
186                 sStatusLED.Blink(100, 100);
187             }
188             else
189             {
190                 sStatusLED.Blink(50, 950);
191             }
192         }
193
194         sStatusLED.Animate();
195         sLockLED.Animate();
196         sUnusedLED.Animate();
197         sUnusedLED_1.Animate();
198
199         uint64_t nowUS            = chip::System::Platform::Layer::GetClock_Monotonic();
200         uint64_t nextChangeTimeUS = mLastPublishServiceTimeUS + kPublishServicePeriodUs;
201
202         if (nowUS > nextChangeTimeUS)
203         {
204             PublishService();
205             mLastPublishServiceTimeUS = nowUS;
206         }
207     }
208 }
209
210 void AppTask::LockActionEventHandler(AppEvent * aEvent)
211 {
212     BoltLockManager::Action_t action = BoltLockManager::INVALID_ACTION;
213     int32_t actor                    = 0;
214
215     if (aEvent->Type == AppEvent::kEventType_Lock)
216     {
217         action = static_cast<BoltLockManager::Action_t>(aEvent->LockEvent.Action);
218         actor  = aEvent->LockEvent.Actor;
219     }
220     else if (aEvent->Type == AppEvent::kEventType_Button)
221     {
222         action = BoltLockMgr().IsUnlocked() ? BoltLockManager::LOCK_ACTION : BoltLockManager::UNLOCK_ACTION;
223         actor  = AppEvent::kEventType_Button;
224     }
225
226     if (action != BoltLockManager::INVALID_ACTION && !BoltLockMgr().InitiateAction(actor, action))
227         LOG_INF("Action is already in progress or active.");
228 }
229
230 void AppTask::ButtonEventHandler(uint32_t button_state, uint32_t has_changed)
231 {
232     AppEvent button_event;
233     button_event.Type = AppEvent::kEventType_Button;
234
235     if (LOCK_BUTTON_MASK & button_state & has_changed)
236     {
237         button_event.ButtonEvent.PinNo  = LOCK_BUTTON;
238         button_event.ButtonEvent.Action = BUTTON_PUSH_EVENT;
239         button_event.Handler            = LockActionEventHandler;
240         sAppTask.PostEvent(&button_event);
241     }
242
243     if (FUNCTION_BUTTON_MASK & has_changed)
244     {
245         button_event.ButtonEvent.PinNo  = FUNCTION_BUTTON;
246         button_event.ButtonEvent.Action = (FUNCTION_BUTTON_MASK & button_state) ? BUTTON_PUSH_EVENT : BUTTON_RELEASE_EVENT;
247         button_event.Handler            = FunctionHandler;
248         sAppTask.PostEvent(&button_event);
249     }
250
251     if (THREAD_START_BUTTON_MASK & button_state & has_changed)
252     {
253         button_event.ButtonEvent.PinNo  = THREAD_START_BUTTON;
254         button_event.ButtonEvent.Action = BUTTON_PUSH_EVENT;
255         button_event.Handler            = StartThreadHandler;
256         sAppTask.PostEvent(&button_event);
257     }
258
259     if (BLE_ADVERTISEMENT_START_BUTTON_MASK & button_state & has_changed)
260     {
261         button_event.ButtonEvent.PinNo  = BLE_ADVERTISEMENT_START_BUTTON;
262         button_event.ButtonEvent.Action = BUTTON_PUSH_EVENT;
263         button_event.Handler            = StartBLEAdvertisementHandler;
264         sAppTask.PostEvent(&button_event);
265     }
266 }
267
268 void AppTask::TimerEventHandler(k_timer * timer)
269 {
270     AppEvent event;
271     event.Type               = AppEvent::kEventType_Timer;
272     event.TimerEvent.Context = k_timer_user_data_get(timer);
273     event.Handler            = FunctionTimerEventHandler;
274     sAppTask.PostEvent(&event);
275 }
276
277 void AppTask::FunctionTimerEventHandler(AppEvent * aEvent)
278 {
279     if (aEvent->Type != AppEvent::kEventType_Timer)
280         return;
281
282     // If we reached here, the button was held past FACTORY_RESET_TRIGGER_TIMEOUT, initiate factory reset
283     if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_SoftwareUpdate)
284     {
285         LOG_INF("Factory Reset Triggered. Release button within %ums to cancel.", FACTORY_RESET_TRIGGER_TIMEOUT);
286
287         // Start timer for FACTORY_RESET_CANCEL_WINDOW_TIMEOUT to allow user to cancel, if required.
288         sAppTask.StartTimer(FACTORY_RESET_CANCEL_WINDOW_TIMEOUT);
289         sAppTask.mFunction = kFunction_FactoryReset;
290
291         // Turn off all LEDs before starting blink to make sure blink is co-ordinated.
292         sStatusLED.Set(false);
293         sLockLED.Set(false);
294         sUnusedLED_1.Set(false);
295         sUnusedLED.Set(false);
296
297         sStatusLED.Blink(500);
298         sLockLED.Blink(500);
299         sUnusedLED.Blink(500);
300         sUnusedLED_1.Blink(500);
301     }
302     else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset)
303     {
304         // Actually trigger Factory Reset
305         sAppTask.mFunction = kFunction_NoneSelected;
306         ConfigurationMgr().InitiateFactoryReset();
307     }
308 }
309
310 void AppTask::FunctionHandler(AppEvent * aEvent)
311 {
312     if (aEvent->ButtonEvent.PinNo != FUNCTION_BUTTON)
313         return;
314
315     // To trigger software update: press the FUNCTION_BUTTON button briefly (< FACTORY_RESET_TRIGGER_TIMEOUT)
316     // To initiate factory reset: press the FUNCTION_BUTTON for FACTORY_RESET_TRIGGER_TIMEOUT + FACTORY_RESET_CANCEL_WINDOW_TIMEOUT
317     // All LEDs start blinking after FACTORY_RESET_TRIGGER_TIMEOUT to signal factory reset has been initiated.
318     // To cancel factory reset: release the FUNCTION_BUTTON once all LEDs start blinking within the
319     // FACTORY_RESET_CANCEL_WINDOW_TIMEOUT
320     if (aEvent->ButtonEvent.Action == BUTTON_PUSH_EVENT)
321     {
322         if (!sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_NoneSelected)
323         {
324             sAppTask.StartTimer(FACTORY_RESET_TRIGGER_TIMEOUT);
325
326             sAppTask.mFunction = kFunction_SoftwareUpdate;
327         }
328     }
329     else
330     {
331         // If the button was released before factory reset got initiated, trigger a software update.
332         if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_SoftwareUpdate)
333         {
334             sAppTask.CancelTimer();
335             sAppTask.mFunction = kFunction_NoneSelected;
336             LOG_INF("Software update is not implemented");
337         }
338         else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset)
339         {
340             sUnusedLED.Set(false);
341             sUnusedLED_1.Set(false);
342
343             // Set lock status LED back to show state of lock.
344             sLockLED.Set(!BoltLockMgr().IsUnlocked());
345
346             sAppTask.CancelTimer();
347
348             // Change the function to none selected since factory reset has been canceled.
349             sAppTask.mFunction = kFunction_NoneSelected;
350
351             LOG_INF("Factory Reset has been Canceled");
352         }
353     }
354 }
355
356 void AppTask::StartThreadHandler(AppEvent * aEvent)
357 {
358     if (aEvent->ButtonEvent.PinNo != THREAD_START_BUTTON)
359         return;
360
361     if (AddTestPairing() != CHIP_NO_ERROR)
362     {
363         LOG_ERR("Failed to add test pairing");
364     }
365
366     if (!chip::DeviceLayer::ConnectivityMgr().IsThreadProvisioned())
367     {
368         StartDefaultThreadNetwork();
369         LOG_INF("Device is not commissioned to a Thread network. Starting with the default configuration.");
370     }
371     else
372     {
373         LOG_INF("Device is commissioned to a Thread network.");
374     }
375 }
376
377 void AppTask::StartBLEAdvertisementHandler(AppEvent * aEvent)
378 {
379     if (aEvent->ButtonEvent.PinNo != BLE_ADVERTISEMENT_START_BUTTON)
380         return;
381
382     if (chip::DeviceLayer::ConnectivityMgr().IsThreadProvisioned())
383     {
384         LOG_INF("NFC Tag emulation and BLE advertisement not started - device is commissioned to a Thread network.");
385         return;
386     }
387
388     if (!sNFC.IsTagEmulationStarted())
389     {
390         if (!(GetAppTask().StartNFCTag() < 0))
391         {
392             LOG_INF("Started NFC Tag emulation");
393         }
394         else
395         {
396             LOG_ERR("Starting NFC Tag failed");
397         }
398     }
399     else
400     {
401         LOG_INF("NFC Tag emulation is already started");
402     }
403
404     if (ConnectivityMgr().IsBLEAdvertisingEnabled())
405     {
406         LOG_INF("BLE Advertisement is already enabled");
407         return;
408     }
409
410     if (OpenDefaultPairingWindow(chip::ResetAdmins::kNo) == CHIP_NO_ERROR)
411     {
412         LOG_INF("Enabled BLE Advertisement");
413     }
414     else
415     {
416         LOG_ERR("OpenDefaultPairingWindow() failed");
417     }
418 }
419
420 #ifdef CONFIG_CHIP_NFC_COMMISSIONING
421 void AppTask::ThreadProvisioningHandler(const ChipDeviceEvent * event, intptr_t arg)
422 {
423     ARG_UNUSED(arg);
424     if ((event->Type == DeviceEventType::kServiceProvisioningChange) && ConnectivityMgr().IsThreadProvisioned())
425     {
426         if (sNFC.IsTagEmulationStarted())
427         {
428             const int result = sNFC.StopTagEmulation();
429             if (result)
430             {
431                 LOG_ERR("Stopping NFC Tag emulation failed");
432             }
433         }
434     }
435 }
436 #endif
437
438 void AppTask::CancelTimer()
439 {
440     k_timer_stop(&sFunctionTimer);
441     mFunctionTimerActive = false;
442 }
443
444 void AppTask::StartTimer(uint32_t aTimeoutInMs)
445 {
446     k_timer_start(&sFunctionTimer, K_MSEC(aTimeoutInMs), K_NO_WAIT);
447     mFunctionTimerActive = true;
448 }
449
450 #ifdef CONFIG_CHIP_NFC_COMMISSIONING
451 int AppTask::StartNFCTag()
452 {
453     // Get QR Code and emulate its content using NFC tag
454     std::string QRCode;
455
456     int result = GetQRCode(QRCode, chip::RendezvousInformationFlags::kBLE);
457     VerifyOrExit(!result, ChipLogError(AppServer, "Getting QR code payload failed"));
458
459     // TODO: Issue #4504 - Remove replacing spaces with _ after problem described in #415 will be fixed.
460     std::replace(QRCode.begin(), QRCode.end(), ' ', '_');
461
462     result = sNFC.StartTagEmulation(QRCode.c_str(), QRCode.size());
463     VerifyOrExit(result >= 0, ChipLogError(AppServer, "Starting NFC Tag emulation failed"));
464
465 exit:
466     return result;
467 }
468 #endif
469
470 void AppTask::ActionInitiated(BoltLockManager::Action_t aAction, int32_t aActor)
471 {
472     // If the action has been initiated by the lock, update the bolt lock trait
473     // and start flashing the LEDs rapidly to indicate action initiation.
474     if (aAction == BoltLockManager::LOCK_ACTION)
475     {
476         LOG_INF("Lock Action has been initiated");
477     }
478     else if (aAction == BoltLockManager::UNLOCK_ACTION)
479     {
480         LOG_INF("Unlock Action has been initiated");
481     }
482
483     sLockLED.Blink(50, 50);
484 }
485
486 void AppTask::ActionCompleted(BoltLockManager::Action_t aAction, int32_t aActor)
487 {
488     // if the action has been completed by the lock, update the bolt lock trait.
489     // Turn on the lock LED if in a LOCKED state OR
490     // Turn off the lock LED if in an UNLOCKED state.
491     if (aAction == BoltLockManager::LOCK_ACTION)
492     {
493         LOG_INF("Lock Action has been completed");
494         sLockLED.Set(true);
495     }
496     else if (aAction == BoltLockManager::UNLOCK_ACTION)
497     {
498         LOG_INF("Unlock Action has been completed");
499         sLockLED.Set(false);
500     }
501
502     if (aActor == AppEvent::kEventType_Button)
503     {
504         sAppTask.UpdateClusterState();
505     }
506 }
507
508 void AppTask::PostLockActionRequest(int32_t aActor, BoltLockManager::Action_t aAction)
509 {
510     AppEvent event;
511     event.Type             = AppEvent::kEventType_Lock;
512     event.LockEvent.Actor  = aActor;
513     event.LockEvent.Action = aAction;
514     event.Handler          = LockActionEventHandler;
515     PostEvent(&event);
516 }
517
518 void AppTask::PostEvent(AppEvent * aEvent)
519 {
520     if (k_msgq_put(&sAppEventQueue, aEvent, K_NO_WAIT))
521     {
522         LOG_INF("Failed to post event to app task event queue");
523     }
524 }
525
526 void AppTask::DispatchEvent(AppEvent * aEvent)
527 {
528     if (aEvent->Handler)
529     {
530         aEvent->Handler(aEvent);
531     }
532     else
533     {
534         LOG_INF("Event received with no handler. Dropping event.");
535     }
536 }
537
538 void AppTask::UpdateClusterState()
539 {
540     uint8_t newValue = !BoltLockMgr().IsUnlocked();
541
542     // write the new on/off value
543     EmberAfStatus status = emberAfWriteAttribute(1, ZCL_ON_OFF_CLUSTER_ID, ZCL_ON_OFF_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, &newValue,
544                                                  ZCL_BOOLEAN_ATTRIBUTE_TYPE);
545     if (status != EMBER_ZCL_STATUS_SUCCESS)
546     {
547         LOG_ERR("Updating on/off %x", status);
548     }
549 }