3 * Copyright (c) 2021 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.
21 #include "AppConfig.h"
25 #include "OnboardingCodesUtil.h"
28 #include "attribute-storage.h"
29 #include "gen/attribute-id.h"
30 #include "gen/attribute-type.h"
31 #include "gen/cluster-id.h"
35 #include <setup_payload/QRCodeSetupPayloadGenerator.h>
36 #include <setup_payload/SetupPayload.h>
38 using namespace chip::TLV;
39 using namespace chip::DeviceLayer;
41 #include <platform/CHIPDeviceLayer.h>
42 #if CHIP_ENABLE_OPENTHREAD
43 #include <platform/OpenThread/OpenThreadUtils.h>
44 #include <platform/ThreadStackManager.h>
45 #include <platform/qpg6100/ThreadStackManagerImpl.h>
46 #define JOINER_START_TRIGGER_TIMEOUT 1500
49 #define FACTORY_RESET_TRIGGER_TIMEOUT 3000
50 #define FACTORY_RESET_CANCEL_WINDOW_TIMEOUT 3000
51 #define APP_TASK_STACK_SIZE (2048)
52 #define APP_TASK_PRIORITY 2
53 #define APP_EVENT_QUEUE_SIZE 10
55 static TaskHandle_t sAppTaskHandle;
56 static QueueHandle_t sAppEventQueue;
58 static bool sIsThreadProvisioned = false;
59 static bool sIsThreadEnabled = false;
60 static bool sHaveBLEConnections = false;
61 static bool sHaveServiceConnectivity = false;
63 AppTask AppTask::sAppTask;
65 int AppTask::StartAppTask()
67 sAppEventQueue = xQueueCreate(APP_EVENT_QUEUE_SIZE, sizeof(AppEvent));
68 if (sAppEventQueue == NULL)
70 ChipLogError(NotSpecified, "Failed to allocate app event queue");
71 return CHIP_ERROR_NO_MEMORY;
75 if (xTaskCreate(AppTaskMain, "APP", APP_TASK_STACK_SIZE / sizeof(StackType_t), NULL, 1, &sAppTaskHandle) != pdPASS)
77 return CHIP_ERROR_NO_MEMORY;
85 CHIP_ERROR err = CHIP_NO_ERROR;
87 ChipLogProgress(NotSpecified, "Current Firmware Version: %s", CHIP_DEVICE_CONFIG_DEVICE_FIRMWARE_REVISION);
89 err = LightingMgr().Init();
90 if (err != CHIP_NO_ERROR)
92 ChipLogError(NotSpecified, "LightingMgr().Init() failed");
95 LightingMgr().SetCallbacks(ActionInitiated, ActionCompleted);
97 // Subscribe with our button callback to the qvCHIP button handler.
98 qvCHIP_SetBtnCallback(ButtonEventHandler);
100 // Init ZCL Data Model
102 UpdateClusterState();
104 ConfigurationMgr().LogDeviceConfig();
105 PrintOnboardingCodes(chip::RendezvousInformationFlags::kBLE);
110 void AppTask::AppTaskMain(void * pvParameter)
114 uint64_t mLastChangeTimeUS = 0;
116 err = sAppTask.Init();
117 if (err != CHIP_NO_ERROR)
119 ChipLogError(NotSpecified, "AppTask.Init() failed");
123 ChipLogProgress(NotSpecified, "App Task started");
124 SetDeviceName("QPG6100LightingDemo._chip._udp.local.");
128 BaseType_t eventReceived = xQueueReceive(sAppEventQueue, &event, pdMS_TO_TICKS(10));
129 while (eventReceived == pdTRUE)
131 sAppTask.DispatchEvent(&event);
132 eventReceived = xQueueReceive(sAppEventQueue, &event, 0);
135 // Collect connectivity and configuration state from the CHIP stack. Because
136 // the CHIP event loop is being run in a separate task, the stack must be
137 // locked while these values are queried. However we use a non-blocking
138 // lock request (TryLockCHIPStack()) to avoid blocking other UI activities
139 // when the CHIP task is busy (e.g. with a long crypto operation).
140 if (PlatformMgr().TryLockChipStack())
142 sIsThreadProvisioned = ConnectivityMgr().IsThreadProvisioned();
143 sIsThreadEnabled = ConnectivityMgr().IsThreadEnabled();
144 sHaveBLEConnections = (ConnectivityMgr().NumBLEConnections() != 0);
145 sHaveServiceConnectivity = ConnectivityMgr().HaveServiceConnectivity();
146 PlatformMgr().UnlockChipStack();
149 // Update the status LED if factory reset has not been initiated.
151 // If system has "full connectivity", keep the LED On constantly.
153 // If thread and service provisioned, but not attached to the thread network
154 // yet OR no connectivity to the service OR subscriptions are not fully
155 // established THEN blink the LED Off for a short period of time.
157 // If the system has ble connection(s) uptill the stage above, THEN blink
158 // the LEDs at an even rate of 100ms.
160 // Otherwise, blink the LED ON for a very short time.
161 if (sAppTask.mFunction != kFunction_FactoryReset)
163 // Consider the system to be "fully connected" if it has service
165 if (sHaveServiceConnectivity)
167 qvCHIP_LedSet(SYSTEM_STATE_LED, true);
169 else if (sIsThreadProvisioned && sIsThreadEnabled)
171 qvCHIP_LedBlink(SYSTEM_STATE_LED, 950, 50);
173 else if (sHaveBLEConnections)
175 qvCHIP_LedBlink(SYSTEM_STATE_LED, 100, 100);
179 qvCHIP_LedBlink(SYSTEM_STATE_LED, 50, 950);
183 uint64_t nowUS = chip::System::Layer::GetClock_Monotonic();
184 uint64_t nextChangeTimeUS = mLastChangeTimeUS + 5 * 1000 * 1000UL;
186 if (nowUS > nextChangeTimeUS)
189 mLastChangeTimeUS = nowUS;
194 void AppTask::LightingActionEventHandler(AppEvent * aEvent)
196 LightingManager::Action_t action;
197 if (aEvent->Type == AppEvent::kEventType_Button)
199 if (LightingMgr().IsTurnedOn())
201 action = LightingManager::OFF_ACTION;
205 action = LightingManager::ON_ACTION;
207 LightingMgr().InitiateAction(action, 0, 0, 0);
209 if (aEvent->Type == AppEvent::kEventType_Level && aEvent->ButtonEvent.Action != 0)
212 val = LightingMgr().GetLevel() == 0x7f ? 0x1 : 0x7f;
213 action = LightingManager::LEVEL_ACTION;
214 LightingMgr().InitiateAction(action, 0, 1, &val);
219 void AppTask::ButtonEventHandler(uint8_t btnIdx, bool btnPressed)
221 ChipLogProgress(NotSpecified, "ButtonEventHandler %d, %d", btnIdx, btnPressed);
222 if (btnIdx != APP_ON_OFF_BUTTON && btnIdx != APP_FUNCTION_BUTTON && btnIdx != APP_LEVEL_BUTTON)
227 AppEvent button_event = {};
228 button_event.Type = AppEvent::kEventType_Button;
229 button_event.ButtonEvent.ButtonIdx = btnIdx;
230 button_event.ButtonEvent.Action = btnPressed;
232 if (btnIdx == APP_ON_OFF_BUTTON && btnPressed == true)
234 button_event.Handler = LightingActionEventHandler;
235 sAppTask.PostEvent(&button_event);
237 else if (btnIdx == APP_LEVEL_BUTTON)
239 button_event.Type = AppEvent::kEventType_Level;
240 button_event.Handler = LightingActionEventHandler;
241 sAppTask.PostEvent(&button_event);
243 else if (btnIdx == APP_FUNCTION_BUTTON)
245 button_event.Type = AppEvent::kEventType_Level;
246 button_event.Handler = FunctionHandler;
247 sAppTask.PostEvent(&button_event);
251 void AppTask::TimerEventHandler(chip::System::Layer * aLayer, void * aAppState, chip::System::Error aError)
254 event.Type = AppEvent::kEventType_Timer;
255 event.TimerEvent.Context = aAppState;
256 event.Handler = FunctionTimerEventHandler;
257 sAppTask.PostEvent(&event);
260 void AppTask::FunctionTimerEventHandler(AppEvent * aEvent)
262 if (aEvent->Type != AppEvent::kEventType_Timer)
267 // If we reached here, the button was held past FACTORY_RESET_TRIGGER_TIMEOUT,
268 // initiate factory reset
269 if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_SoftwareUpdate)
271 #if CHIP_ENABLE_OPENTHREAD
272 ChipLogProgress(NotSpecified, "Release button now to Start Thread Joiner");
273 ChipLogProgress(NotSpecified, "Hold to trigger Factory Reset");
274 sAppTask.mFunction = kFunction_Joiner;
275 sAppTask.StartTimer(FACTORY_RESET_TRIGGER_TIMEOUT);
277 else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_Joiner)
280 ChipLogProgress(NotSpecified, "Factory Reset Triggered. Release button within %ums to cancel.",
281 FACTORY_RESET_CANCEL_WINDOW_TIMEOUT);
283 // Start timer for FACTORY_RESET_CANCEL_WINDOW_TIMEOUT to allow user to
284 // cancel, if required.
285 sAppTask.StartTimer(FACTORY_RESET_CANCEL_WINDOW_TIMEOUT);
287 sAppTask.mFunction = kFunction_FactoryReset;
289 // Turn off all LEDs before starting blink to make sure blink is
291 qvCHIP_LedSet(SYSTEM_STATE_LED, false);
293 qvCHIP_LedBlink(SYSTEM_STATE_LED, 500, 500);
295 else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset)
297 // Actually trigger Factory Reset
298 sAppTask.mFunction = kFunction_NoneSelected;
299 ConfigurationMgr().InitiateFactoryReset();
303 void AppTask::FunctionHandler(AppEvent * aEvent)
305 if (aEvent->ButtonEvent.ButtonIdx != APP_FUNCTION_BUTTON)
310 // To trigger software update: press the APP_FUNCTION_BUTTON button briefly (<
311 // FACTORY_RESET_TRIGGER_TIMEOUT) To initiate factory reset: press the
312 // APP_FUNCTION_BUTTON for FACTORY_RESET_TRIGGER_TIMEOUT +
313 // FACTORY_RESET_CANCEL_WINDOW_TIMEOUT All LEDs start blinking after
314 // FACTORY_RESET_TRIGGER_TIMEOUT to signal factory reset has been initiated.
315 // To cancel factory reset: release the APP_FUNCTION_BUTTON once all LEDs
316 // start blinking within the FACTORY_RESET_CANCEL_WINDOW_TIMEOUT
317 if (aEvent->ButtonEvent.Action == true)
319 if (!sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_NoneSelected)
321 #if CHIP_ENABLE_OPENTHREAD
322 sAppTask.StartTimer(JOINER_START_TRIGGER_TIMEOUT);
324 sAppTask.StartTimer(FACTORY_RESET_TRIGGER_TIMEOUT);
327 sAppTask.mFunction = kFunction_SoftwareUpdate;
332 // If the button was released before factory reset got initiated, trigger a
334 if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_SoftwareUpdate)
336 sAppTask.CancelTimer();
338 sAppTask.mFunction = kFunction_NoneSelected;
340 ChipLogError(NotSpecified, "Software Update currently not supported.");
342 #if CHIP_ENABLE_OPENTHREAD
343 else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_Joiner)
345 sAppTask.CancelTimer();
346 sAppTask.mFunction = kFunction_NoneSelected;
348 CHIP_ERROR error = ThreadStackMgr().JoinerStart();
349 ChipLogProgress(NotSpecified, "Thread joiner triggered: %s", chip::ErrorStr(error));
352 else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset)
354 sAppTask.CancelTimer();
356 // Change the function to none selected since factory reset has been
358 sAppTask.mFunction = kFunction_NoneSelected;
360 ChipLogProgress(NotSpecified, "Factory Reset has been Canceled");
365 void AppTask::CancelTimer()
367 SystemLayer.CancelTimer(TimerEventHandler, this);
368 mFunctionTimerActive = false;
371 void AppTask::StartTimer(uint32_t aTimeoutInMs)
375 SystemLayer.CancelTimer(TimerEventHandler, this);
376 err = SystemLayer.StartTimer(aTimeoutInMs, TimerEventHandler, this);
379 mFunctionTimerActive = true;
381 if (err != CHIP_NO_ERROR)
383 ChipLogError(NotSpecified, "StartTimer failed %s: ", chip::ErrorStr(err));
387 void AppTask::ActionInitiated(LightingManager::Action_t aAction)
389 // Placeholder for light action
390 if (aAction == LightingManager::ON_ACTION)
392 ChipLogProgress(NotSpecified, "Light goes on");
394 else if (aAction == LightingManager::OFF_ACTION)
396 ChipLogProgress(NotSpecified, "Light goes off ");
400 void AppTask::ActionCompleted(LightingManager::Action_t aAction)
402 // Placeholder for light action completed
403 if (aAction == LightingManager::ON_ACTION)
405 ChipLogProgress(NotSpecified, "Light On Action has been completed");
407 else if (aAction == LightingManager::OFF_ACTION)
409 ChipLogProgress(NotSpecified, "Light Off Action has been completed");
412 if (sAppTask.mSyncClusterToButtonAction)
414 sAppTask.UpdateClusterState();
415 sAppTask.mSyncClusterToButtonAction = false;
419 void AppTask::PostEvent(const AppEvent * aEvent)
421 if (sAppEventQueue != NULL)
423 if (!xQueueSend(sAppEventQueue, aEvent, 1))
425 ChipLogError(NotSpecified, "Failed to post event to app task event queue");
430 void AppTask::DispatchEvent(AppEvent * aEvent)
434 aEvent->Handler(aEvent);
438 ChipLogError(NotSpecified, "Event received with no handler. Dropping event.");
442 void AppTask::UpdateClusterState(void)
444 uint8_t newValue = !LightingMgr().IsTurnedOn();
445 // write the new on/off value
446 EmberAfStatus status = emberAfWriteAttribute(1, ZCL_ON_OFF_CLUSTER_ID, ZCL_ON_OFF_ATTRIBUTE_ID, CLUSTER_MASK_SERVER,
447 (uint8_t *) &newValue, ZCL_BOOLEAN_ATTRIBUTE_TYPE);
448 if (status != EMBER_ZCL_STATUS_SUCCESS)
450 ChipLogError(NotSpecified, "ERR: updating on/off %x", status);
453 ChipLogProgress(NotSpecified, "UpdateClusterState");
454 newValue = LightingMgr().GetLevel();
455 // TODO understand well enough to implement the level cluster ZCL_CURRENT_LEVEL_ATTRIBUTE_ID
456 status = emberAfWriteAttribute(1, ZCL_LEVEL_CONTROL_CLUSTER_ID, ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, CLUSTER_MASK_SERVER,
457 (uint8_t *) &newValue, ZCL_DATA8_ATTRIBUTE_TYPE);
459 if (status != EMBER_ZCL_STATUS_SUCCESS)
461 ChipLogError(NotSpecified, "ERR: updating level %x", status);