blob: cd865f515b85e41e85ee4ee113261ab032f24258 [file] [log] [blame]
/*
*
* Copyright (c) 2021 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 "CHIPDeviceManager.h"
#include "DeviceCallbacks.h"
#include "LEDWidget.h"
#include <app-common/zap-generated/attribute-id.h>
#include <app-common/zap-generated/attribute-type.h>
#include <app-common/zap-generated/cluster-id.h>
#include <app/clusters/network-commissioning/network-commissioning.h>
#include <app/clusters/ota-requestor/BDXDownloader.h>
#include <app/clusters/ota-requestor/DefaultOTARequestor.h>
#include <app/clusters/ota-requestor/DefaultOTARequestorDriver.h>
#include <app/clusters/ota-requestor/DefaultOTARequestorStorage.h>
#include <app/server/OnboardingCodesUtil.h>
#include <app/server/Server.h>
#include <app/util/af-enums.h>
#include <app/util/attribute-storage.h>
#include <credentials/DeviceAttestationCredsProvider.h>
#include <credentials/examples/DeviceAttestationCredsExample.h>
#include <lib/support/CodeUtils.h>
#include <platform/CHIPDeviceLayer.h>
#include <platform/bouffalolab/BL602/NetworkCommissioningDriver.h>
#include <platform/bouffalolab/BL602/OTAImageProcessorImpl.h>
#include <platform/internal/CHIPDeviceLayerInternal.h>
#include <InitPlatform.h>
#include <async_log.h>
#include <bl_sys_ota.h>
#include <easyflash.h>
#include <hal_sys.h>
#include <lib/support/ErrorStr.h>
#if PW_RPC_ENABLED
#include "Rpc.h"
#endif
#define FACTORY_RESET_TRIGGER_TIMEOUT 3000
#define FACTORY_RESET_CANCEL_WINDOW_TIMEOUT 3000
#define APP_EVENT_QUEUE_SIZE 10
#define APP_TASK_STACK_SIZE (4096)
#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 = "lighting-app";
static xTaskHandle OTA_TASK_HANDLE;
static LEDWidget statusLED;
namespace {
TimerHandle_t sFunctionTimer; // FreeRTOS app sw timer.
BaseType_t sAppTaskHandle;
QueueHandle_t sAppEventQueue;
bool sHaveBLEConnections = false;
StackType_t appStack[APP_TASK_STACK_SIZE / sizeof(StackType_t)];
} // namespace
using namespace ::chip;
using namespace ::chip::DeviceManager;
using namespace ::chip::DeviceLayer;
using namespace ::chip::Credentials;
namespace {
chip::app::Clusters::NetworkCommissioning::Instance
sWiFiNetworkCommissioningInstance(0 /* Endpoint Id */, &(NetworkCommissioning::BLWiFiDriver::GetInstance()));
} // namespace
using namespace ::chip::System;
DefaultOTARequestor gRequestorCore;
DefaultOTARequestorStorage gRequestorStorage;
DefaultOTARequestorDriver gRequestorUser;
BDXDownloader gDownloader;
OTAImageProcessorImpl gImageProcessor;
AppTask AppTask::sAppTask;
// static DeviceCallbacks EchoCallbacks;
CHIP_ERROR AppTask::StartAppTask()
{
CHIP_ERROR err = CHIP_NO_ERROR;
sAppEventQueue = xQueueCreate(APP_EVENT_QUEUE_SIZE, sizeof(AppEvent));
if (sAppEventQueue == NULL)
{
log_error("Failed to allocate app event queue\r\n");
return APP_ERROR_EVENT_QUEUE_FAILED;
}
// Start App task.
sAppTaskHandle = xTaskCreate(AppTaskMain, APP_TASK_NAME, ArraySize(appStack), NULL, 1, NULL);
if (sAppTaskHandle)
{
err = CHIP_NO_ERROR;
}
return CHIP_NO_ERROR;
}
CHIP_ERROR 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
);
if (sFunctionTimer == NULL)
{
log_error("funct timer create failed\r\n");
return APP_ERROR_CREATE_TIMER_FAILED;
}
err = LightMgr().Init();
if (err != CHIP_NO_ERROR)
{
log_error("LightMgr().Init() failed\r\n");
return err;
}
LightMgr().SetCallbacks(ActionInitiated, ActionCompleted);
SetRequestorInstance(&gRequestorCore);
gRequestorStorage.Init(chip::Server::GetInstance().GetPersistentStorage());
gRequestorCore.Init(chip::Server::GetInstance(), gRequestorStorage, gRequestorUser, gDownloader);
gImageProcessor.SetOTADownloader(&gDownloader);
gDownloader.SetImageProcessorDelegate(&gImageProcessor);
gRequestorUser.SetPeriodicQueryTimeout(OTA_PERIODIC_QUERY_TIMEOUT);
gRequestorUser.Init(&gRequestorCore, &gImageProcessor);
ConfigurationMgr().LogDeviceConfig();
PrintOnboardingCodes(chip::RendezvousInformationFlag(chip::RendezvousInformationFlag::kBLE));
InitButtons();
#if PW_RPC_ENABLED
chip::rpc::Init();
#endif
return err;
}
void AppTask::AppTaskMain(void * pvParameter)
{
AppEvent event;
Clock::Timestamp lastChangeTime = Clock::kZero;
CHIP_ERROR err;
log_info("App Task entered\r\n");
log_async_init();
enable_async_log();
err = sWiFiNetworkCommissioningInstance.Init();
if (CHIP_NO_ERROR != err)
{
log_error("Network commissioning failed, err:%d \r\n", err);
}
chip::CommonCaseDeviceServerInitParams initParams;
err = initParams.InitializeStaticResourcesBeforeServerInit();
if (CHIP_NO_ERROR != err)
{
log_error("Resources Init failed, err:%d \r\n", err);
return;
}
err = chip::Server::GetInstance().Init(initParams);
if (CHIP_NO_ERROR != err)
{
log_error("Server Init failed, err:%d \r\n", err);
return;
}
// Initialize device attestation config
SetDeviceAttestationCredentialsProvider(Examples::GetExampleDACProvider());
err = sAppTask.Init();
if (err != CHIP_NO_ERROR)
{
log_error("AppTask.Init() failed due to %d\r\n", err);
return;
}
while (true)
{
BaseType_t eventReceived = xQueueReceive(sAppEventQueue, &event, pdMS_TO_TICKS(10));
while (eventReceived == pdTRUE)
{
log_info("receiving event type: %d\r\n", event.Type);
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);
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)
{
if (sHaveBLEConnections)
{
// TODO: 3R
// sStatusLED.Blink(100, 100);
}
else
{
// TODO: 3R
// sStatusLED.Blink(50, 950);
}
}
// TODO: 3R
// sStatusLED.Animate();
Clock::Timestamp now = SystemClock().GetMonotonicTimestamp();
Clock::Timestamp nextChangeTime = lastChangeTime + Clock::Seconds16(5);
if (nextChangeTime < now)
{
lastChangeTime = now;
}
}
}
void AppTask::LightActionEventHandler(AppEvent * aEvent)
{
bool initiated = false;
LightingManager::Action_t action;
int32_t actor;
CHIP_ERROR err = CHIP_NO_ERROR;
if (aEvent->Type == AppEvent::kEventType_Light)
{
action = static_cast<LightingManager::Action_t>(aEvent->LightEvent.Action);
actor = aEvent->LightEvent.Actor;
}
else if (aEvent->Type == AppEvent::kEventType_Button)
{
if (LightMgr().IsLightOn())
{
action = LightingManager::OFF_ACTION;
}
else
{
action = LightingManager::ON_ACTION;
}
actor = AppEvent::kEventType_Button;
}
else
{
err = APP_ERROR_UNHANDLED_EVENT;
}
if (err == CHIP_NO_ERROR)
{
initiated = LightMgr().InitiateAction(actor, action);
if (!initiated)
{
log_info("Action is already in progress or active.\r\n");
}
}
}
void AppTask::ButtonEventHandler(uint8_t btnIdx, uint8_t btnAction)
{
#if 0 // TODO: 3R
if (btnIdx != APP_LOCK_BUTTON && btnIdx != APP_FUNCTION_BUTTON)
{
return;
}
#endif
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;
log_info("posting button_event(lock button)\r\n");
sAppTask.PostEvent(&button_event);
}
else */
if (btnIdx == APP_FUNCTION_BUTTON)
{
button_event.Handler = FunctionHandler;
log_info("posting button_event(function button)\r\n");
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)
{
log_info("Factory Reset Triggered. Release button within %ums to cancel.\r\n", 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;
}
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.Action == APP_BUTTON_LONGPRESSED)
{
log_info("FactoryReset! please release boutton!!!\r\n");
statusLED.Toggle();
vTaskDelay(1000);
statusLED.Toggle();
vTaskDelay(1000);
statusLED.Toggle();
vTaskDelay(3000);
chip::Server::GetInstance().ScheduleFactoryReset();
}
else if (aEvent->ButtonEvent.Action == APP_BUTTON_PRESSED)
{
AppEvent Lightevent = {};
Lightevent.Type = AppEvent::kEventType_Button;
Lightevent.Handler = LightActionEventHandler;
sAppTask.PostEvent(&Lightevent);
}
}
void AppTask::CancelTimer()
{
if (xTimerStop(sFunctionTimer, 0) == pdFAIL)
{
log_error("app timer stop() failed\r\n");
return;
}
mFunctionTimerActive = false;
}
void AppTask::StartTimer(uint32_t aTimeoutInMs)
{
if (xTimerIsTimerActive(sFunctionTimer))
{
log_warn("app timer already started!\r\n");
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)
{
log_error("app timer start() failed\r\n");
return;
}
mFunctionTimerActive = true;
}
void AppTask::ActionInitiated(LightingManager::Action_t aAction, int32_t aActor)
{
// Action initiated, update the light led
if (aAction == LightingManager::ON_ACTION)
{
log_info("Turning light ON\r\n");
#if 0 // TODO: 3R
sLightLED.Set(true);
#endif
}
else if (aAction == LightingManager::OFF_ACTION)
{
log_info("Turning light OFF\r\n");
#if 0 // TODO: 3R
sLightLED.Set(false);
#endif
}
if (aActor == AppEvent::kEventType_Button)
{
sAppTask.mSyncClusterToButtonAction = true;
}
}
void AppTask::ActionCompleted(LightingManager::Action_t aAction)
{
// action has been completed bon the light
if (aAction == LightingManager::ON_ACTION)
{
log_info("Light ON\r\n");
}
else if (aAction == LightingManager::OFF_ACTION)
{
log_info("Light OFF\r\n");
}
if (sAppTask.mSyncClusterToButtonAction)
{
UpdateClusterState();
sAppTask.mSyncClusterToButtonAction = false;
}
}
void AppTask::PostLightActionRequest(int32_t aActor, LightingManager::Action_t aAction)
{
AppEvent event;
event.Type = AppEvent::kEventType_Light;
event.LightEvent.Actor = aActor;
event.LightEvent.Action = aAction;
event.Handler = LightActionEventHandler;
PostEvent(&event);
}
void AppTask::PostEvent(const AppEvent * aEvent)
{
if (sAppEventQueue != NULL)
{
BaseType_t status;
if (xPortIsInsideInterrupt())
{
BaseType_t higherPrioTaskWoken = pdFALSE;
log_info("sending event type from isr: %d\r\n", aEvent->Type);
status = xQueueSendFromISR(sAppEventQueue, aEvent, &higherPrioTaskWoken);
#ifdef portYIELD_FROM_ISR
portYIELD_FROM_ISR(higherPrioTaskWoken);
#elif portEND_SWITCHING_ISR // portYIELD_FROM_ISR or portEND_SWITCHING_ISR
portEND_SWITCHING_ISR(higherPrioTaskWoken);
#else // portYIELD_FROM_ISR or portEND_SWITCHING_ISR
#error "Must have portYIELD_FROM_ISR or portEND_SWITCHING_ISR"
#endif // portYIELD_FROM_ISR or portEND_SWITCHING_ISR
}
else
{
log_info("sending event type: %d\r\n", aEvent->Type);
status = xQueueSend(sAppEventQueue, aEvent, 1);
}
if (!status)
log_error("Failed to post event to app task event queue\r\n");
}
else
{
log_error("Event Queue is NULL should never happen\r\n");
}
}
void AppTask::DispatchEvent(AppEvent * aEvent)
{
if (aEvent->Handler)
{
aEvent->Handler(aEvent);
}
else
{
log_warn("Event received with no handler. Dropping event.\r\n");
}
}
void AppTask::UpdateClusterState(void)
{
uint8_t newValue = LightMgr().IsLightOn();
log_info("updating on/off = %x\r\n", newValue);
EmberAfStatus status =
emberAfWriteAttribute(1, ZCL_ON_OFF_CLUSTER_ID, ZCL_ON_OFF_ATTRIBUTE_ID, (uint8_t *) &newValue, ZCL_BOOLEAN_ATTRIBUTE_TYPE);
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
log_error("ERR: updating on/off %x\r\n", status);
}
}
void AppTask::FactoryResetButtonEventHandler(void)
{
AppEvent button_event = {};
button_event.Type = AppEvent::kEventType_Button;
button_event.ButtonEvent.Action = APP_BUTTON_LONGPRESSED;
button_event.Handler = FunctionHandler;
log_info("FactoryResetButtonEventHandler\r\n");
sAppTask.PostEvent(&button_event);
}
void AppTask::LightingActionButtonEventHandler(void)
{
AppEvent button_event = {};
button_event.Type = AppEvent::kEventType_Button;
button_event.ButtonEvent.Action = APP_BUTTON_PRESSED;
button_event.Handler = FunctionHandler;
log_info("LightingActionButtonEventHandler\r\n");
sAppTask.PostEvent(&button_event);
}
void AppTask::InitButtons(void)
{
Button_Configure_FactoryResetEventHandler(&FactoryResetButtonEventHandler);
Button_Configure_LightingActionEventHandler(&LightingActionButtonEventHandler);
}
void AppTask::LightStateUpdateEventHandler(void)
{
uint8_t onoff, level;
do
{
if (EMBER_ZCL_STATUS_SUCCESS !=
emberAfReadAttribute(1, ZCL_ON_OFF_CLUSTER_ID, ZCL_ON_OFF_ATTRIBUTE_ID, (uint8_t *) &onoff, sizeof(uint8_t)))
{
break;
}
if (EMBER_ZCL_STATUS_SUCCESS !=
emberAfReadAttribute(1, ZCL_LEVEL_CONTROL_CLUSTER_ID, ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, (uint8_t *) &level,
sizeof(uint8_t)))
{
break;
}
if (0 == onoff)
{
statusLED.SetBrightness(0);
statusLED.Set(0);
PostLightActionRequest(AppEvent::kEventType_Light, LightingManager::OFF_ACTION);
}
else
{
statusLED.SetBrightness(level);
PostLightActionRequest(AppEvent::kEventType_Light, LightingManager::ON_ACTION);
}
} while (0);
}
void AppTask::LightStateInit(void)
{
uint8_t onoff = 1;
uint8_t level = 254;
EndpointId endpoint = 1;
emberAfWriteAttribute(endpoint, ZCL_LEVEL_CONTROL_CLUSTER_ID, ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, (uint8_t *) &level,
ZCL_INT8U_ATTRIBUTE_TYPE);
emberAfWriteAttribute(endpoint, ZCL_ON_OFF_CLUSTER_ID, ZCL_ON_OFF_ATTRIBUTE_ID, (uint8_t *) &onoff, ZCL_BOOLEAN_ATTRIBUTE_TYPE);
}