3 * Copyright (c) 2020 Project CHIP Authors
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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 #include "AppConfig.h"
21 #include "BoltLockManager.h"
22 #include "LEDWidget.h"
23 #include "OnboardingCodesUtil.h"
26 #include "ThreadUtil.h"
28 #ifdef CONFIG_CHIP_NFC_COMMISSIONING
29 #include "NFCWidget.h"
32 #include "attribute-storage.h"
33 #include "gen/attribute-id.h"
34 #include "gen/attribute-type.h"
35 #include "gen/cluster-id.h"
37 #include <platform/CHIPDeviceLayer.h>
39 #include <dk_buttons_and_leds.h>
40 #include <logging/log.h>
41 #include <setup_payload/QRCodeSetupPayloadGenerator.h>
42 #include <setup_payload/SetupPayload.h>
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
53 LOG_MODULE_DECLARE(app);
54 K_MSGQ_DEFINE(sAppEventQueue, sizeof(AppEvent), APP_EVENT_QUEUE_SIZE, alignof(AppEvent));
56 constexpr uint32_t kPublishServicePeriodUs = 5000000;
58 static LEDWidget sStatusLED;
59 static LEDWidget sLockLED;
60 static LEDWidget sUnusedLED;
61 static LEDWidget sUnusedLED_1;
63 #ifdef CONFIG_CHIP_NFC_COMMISSIONING
64 static NFCWidget sNFC;
67 static bool sIsThreadProvisioned = false;
68 static bool sIsThreadEnabled = false;
69 static bool sHaveBLEConnections = false;
70 static bool sHaveServiceConnectivity = false;
72 static k_timer sFunctionTimer;
74 using namespace ::chip::DeviceLayer;
76 AppTask AppTask::sAppTask;
81 LEDWidget::InitGpio();
83 sStatusLED.Init(SYSTEM_STATE_LED);
84 sLockLED.Init(LOCK_STATE_LED);
85 sLockLED.Set(!BoltLockMgr().IsUnlocked());
87 sUnusedLED.Init(DK_LED3);
88 sUnusedLED_1.Init(DK_LED4);
91 int ret = dk_buttons_init(ButtonEventHandler);
94 LOG_ERR("dk_buttons_init() failed");
98 // Initialize timer user data
99 k_timer_init(&sFunctionTimer, &AppTask::TimerEventHandler, nullptr);
100 k_timer_user_data_set(&sFunctionTimer, this);
102 BoltLockMgr().Init();
103 BoltLockMgr().SetCallbacks(ActionInitiated, ActionCompleted);
105 // Init ZCL Data Model and start server
107 ConfigurationMgr().LogDeviceConfig();
108 PrintOnboardingCodes(chip::RendezvousInformationFlags::kBLE);
110 #ifdef CONFIG_CHIP_NFC_COMMISSIONING
111 ret = sNFC.Init(ConnectivityMgr());
114 LOG_ERR("NFC initialization failed");
118 PlatformMgr().AddEventHandler(AppTask::ThreadProvisioningHandler, 0);
124 int AppTask::StartApp()
127 uint64_t mLastPublishServiceTimeUS = 0;
131 LOG_ERR("AppTask.Init() failed");
139 ret = k_msgq_get(&sAppEventQueue, &event, K_MSEC(10));
143 DispatchEvent(&event);
144 ret = k_msgq_get(&sAppEventQueue, &event, K_NO_WAIT);
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).
153 if (PlatformMgr().TryLockChipStack())
155 sIsThreadProvisioned = ConnectivityMgr().IsThreadProvisioned();
156 sIsThreadEnabled = ConnectivityMgr().IsThreadEnabled();
157 sHaveBLEConnections = (ConnectivityMgr().NumBLEConnections() != 0);
158 sHaveServiceConnectivity = ConnectivityMgr().HaveServiceConnectivity();
159 PlatformMgr().UnlockChipStack();
162 // Update the status LED if factory reset has not been initiated.
164 // If system has "full connectivity", keep the LED On constantly.
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.
170 // If the system has ble connection(s) uptill the stage above, THEN blink the LEDs at an even
173 // Otherwise, blink the LED ON for a very short time.
174 if (sAppTask.mFunction != kFunction_FactoryReset)
176 if (sHaveServiceConnectivity)
178 sStatusLED.Set(true);
180 else if (sIsThreadProvisioned && sIsThreadEnabled)
182 sStatusLED.Blink(950, 50);
184 else if (sHaveBLEConnections)
186 sStatusLED.Blink(100, 100);
190 sStatusLED.Blink(50, 950);
194 sStatusLED.Animate();
196 sUnusedLED.Animate();
197 sUnusedLED_1.Animate();
199 uint64_t nowUS = chip::System::Platform::Layer::GetClock_Monotonic();
200 uint64_t nextChangeTimeUS = mLastPublishServiceTimeUS + kPublishServicePeriodUs;
202 if (nowUS > nextChangeTimeUS)
205 mLastPublishServiceTimeUS = nowUS;
210 void AppTask::LockActionEventHandler(AppEvent * aEvent)
212 BoltLockManager::Action_t action = BoltLockManager::INVALID_ACTION;
215 if (aEvent->Type == AppEvent::kEventType_Lock)
217 action = static_cast<BoltLockManager::Action_t>(aEvent->LockEvent.Action);
218 actor = aEvent->LockEvent.Actor;
220 else if (aEvent->Type == AppEvent::kEventType_Button)
222 action = BoltLockMgr().IsUnlocked() ? BoltLockManager::LOCK_ACTION : BoltLockManager::UNLOCK_ACTION;
223 actor = AppEvent::kEventType_Button;
226 if (action != BoltLockManager::INVALID_ACTION && !BoltLockMgr().InitiateAction(actor, action))
227 LOG_INF("Action is already in progress or active.");
230 void AppTask::ButtonEventHandler(uint32_t button_state, uint32_t has_changed)
232 AppEvent button_event;
233 button_event.Type = AppEvent::kEventType_Button;
235 if (LOCK_BUTTON_MASK & button_state & has_changed)
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);
243 if (FUNCTION_BUTTON_MASK & has_changed)
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);
251 if (THREAD_START_BUTTON_MASK & button_state & has_changed)
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);
259 if (BLE_ADVERTISEMENT_START_BUTTON_MASK & button_state & has_changed)
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);
268 void AppTask::TimerEventHandler(k_timer * timer)
271 event.Type = AppEvent::kEventType_Timer;
272 event.TimerEvent.Context = k_timer_user_data_get(timer);
273 event.Handler = FunctionTimerEventHandler;
274 sAppTask.PostEvent(&event);
277 void AppTask::FunctionTimerEventHandler(AppEvent * aEvent)
279 if (aEvent->Type != AppEvent::kEventType_Timer)
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)
285 LOG_INF("Factory Reset Triggered. Release button within %ums to cancel.", FACTORY_RESET_TRIGGER_TIMEOUT);
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;
291 // Turn off all LEDs before starting blink to make sure blink is co-ordinated.
292 sStatusLED.Set(false);
294 sUnusedLED_1.Set(false);
295 sUnusedLED.Set(false);
297 sStatusLED.Blink(500);
299 sUnusedLED.Blink(500);
300 sUnusedLED_1.Blink(500);
302 else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset)
304 // Actually trigger Factory Reset
305 sAppTask.mFunction = kFunction_NoneSelected;
306 ConfigurationMgr().InitiateFactoryReset();
310 void AppTask::FunctionHandler(AppEvent * aEvent)
312 if (aEvent->ButtonEvent.PinNo != FUNCTION_BUTTON)
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)
322 if (!sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_NoneSelected)
324 sAppTask.StartTimer(FACTORY_RESET_TRIGGER_TIMEOUT);
326 sAppTask.mFunction = kFunction_SoftwareUpdate;
331 // If the button was released before factory reset got initiated, trigger a software update.
332 if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_SoftwareUpdate)
334 sAppTask.CancelTimer();
335 sAppTask.mFunction = kFunction_NoneSelected;
336 LOG_INF("Software update is not implemented");
338 else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset)
340 sUnusedLED.Set(false);
341 sUnusedLED_1.Set(false);
343 // Set lock status LED back to show state of lock.
344 sLockLED.Set(!BoltLockMgr().IsUnlocked());
346 sAppTask.CancelTimer();
348 // Change the function to none selected since factory reset has been canceled.
349 sAppTask.mFunction = kFunction_NoneSelected;
351 LOG_INF("Factory Reset has been Canceled");
356 void AppTask::StartThreadHandler(AppEvent * aEvent)
358 if (aEvent->ButtonEvent.PinNo != THREAD_START_BUTTON)
361 if (AddTestPairing() != CHIP_NO_ERROR)
363 LOG_ERR("Failed to add test pairing");
366 if (!chip::DeviceLayer::ConnectivityMgr().IsThreadProvisioned())
368 StartDefaultThreadNetwork();
369 LOG_INF("Device is not commissioned to a Thread network. Starting with the default configuration.");
373 LOG_INF("Device is commissioned to a Thread network.");
377 void AppTask::StartBLEAdvertisementHandler(AppEvent * aEvent)
379 if (aEvent->ButtonEvent.PinNo != BLE_ADVERTISEMENT_START_BUTTON)
382 if (chip::DeviceLayer::ConnectivityMgr().IsThreadProvisioned())
384 LOG_INF("NFC Tag emulation and BLE advertisement not started - device is commissioned to a Thread network.");
388 if (!sNFC.IsTagEmulationStarted())
390 if (!(GetAppTask().StartNFCTag() < 0))
392 LOG_INF("Started NFC Tag emulation");
396 LOG_ERR("Starting NFC Tag failed");
401 LOG_INF("NFC Tag emulation is already started");
404 if (ConnectivityMgr().IsBLEAdvertisingEnabled())
406 LOG_INF("BLE Advertisement is already enabled");
410 if (OpenDefaultPairingWindow(chip::ResetAdmins::kNo) == CHIP_NO_ERROR)
412 LOG_INF("Enabled BLE Advertisement");
416 LOG_ERR("OpenDefaultPairingWindow() failed");
420 #ifdef CONFIG_CHIP_NFC_COMMISSIONING
421 void AppTask::ThreadProvisioningHandler(const ChipDeviceEvent * event, intptr_t arg)
424 if ((event->Type == DeviceEventType::kServiceProvisioningChange) && ConnectivityMgr().IsThreadProvisioned())
426 if (sNFC.IsTagEmulationStarted())
428 const int result = sNFC.StopTagEmulation();
431 LOG_ERR("Stopping NFC Tag emulation failed");
438 void AppTask::CancelTimer()
440 k_timer_stop(&sFunctionTimer);
441 mFunctionTimerActive = false;
444 void AppTask::StartTimer(uint32_t aTimeoutInMs)
446 k_timer_start(&sFunctionTimer, K_MSEC(aTimeoutInMs), K_NO_WAIT);
447 mFunctionTimerActive = true;
450 #ifdef CONFIG_CHIP_NFC_COMMISSIONING
451 int AppTask::StartNFCTag()
453 // Get QR Code and emulate its content using NFC tag
456 int result = GetQRCode(QRCode, chip::RendezvousInformationFlags::kBLE);
457 VerifyOrExit(!result, ChipLogError(AppServer, "Getting QR code payload failed"));
459 // TODO: Issue #4504 - Remove replacing spaces with _ after problem described in #415 will be fixed.
460 std::replace(QRCode.begin(), QRCode.end(), ' ', '_');
462 result = sNFC.StartTagEmulation(QRCode.c_str(), QRCode.size());
463 VerifyOrExit(result >= 0, ChipLogError(AppServer, "Starting NFC Tag emulation failed"));
470 void AppTask::ActionInitiated(BoltLockManager::Action_t aAction, int32_t aActor)
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)
476 LOG_INF("Lock Action has been initiated");
478 else if (aAction == BoltLockManager::UNLOCK_ACTION)
480 LOG_INF("Unlock Action has been initiated");
483 sLockLED.Blink(50, 50);
486 void AppTask::ActionCompleted(BoltLockManager::Action_t aAction, int32_t aActor)
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)
493 LOG_INF("Lock Action has been completed");
496 else if (aAction == BoltLockManager::UNLOCK_ACTION)
498 LOG_INF("Unlock Action has been completed");
502 if (aActor == AppEvent::kEventType_Button)
504 sAppTask.UpdateClusterState();
508 void AppTask::PostLockActionRequest(int32_t aActor, BoltLockManager::Action_t aAction)
511 event.Type = AppEvent::kEventType_Lock;
512 event.LockEvent.Actor = aActor;
513 event.LockEvent.Action = aAction;
514 event.Handler = LockActionEventHandler;
518 void AppTask::PostEvent(AppEvent * aEvent)
520 if (k_msgq_put(&sAppEventQueue, aEvent, K_NO_WAIT))
522 LOG_INF("Failed to post event to app task event queue");
526 void AppTask::DispatchEvent(AppEvent * aEvent)
530 aEvent->Handler(aEvent);
534 LOG_INF("Event received with no handler. Dropping event.");
538 void AppTask::UpdateClusterState()
540 uint8_t newValue = !BoltLockMgr().IsUnlocked();
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)
547 LOG_ERR("Updating on/off %x", status);