blob: 4b9d5427adc8f452e8a5c203f3060d2b06575939 [file] [log] [blame]
/*
*
* Copyright (c) 2020 Project CHIP Authors
* All rights reserved.
*
* 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.
*/
/*STM32 includes*/
#include "AppTask.h"
#include "AppEvent.h"
#include "app_common.h"
#include "app_thread.h"
#include "cmsis_os.h"
#include "dbg_trace.h"
#include "flash_wb.h"
#include "ssd1315.h"
#include "stm32_lcd.h"
#include "stm32_lpm.h"
#include "stm32wb5mm_dk_lcd.h"
#include "stm_logging.h"
#if HIGHWATERMARK
#include "memory_buffer_alloc.h"
#endif
/*Matter includes*/
#include <app-common/zap-generated/attribute-type.h>
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app/server/Dnssd.h>
#include <app/server/OnboardingCodesUtil.h>
#include <app/server/Server.h>
#include <app/util/attribute-storage.h>
#include <credentials/DeviceAttestationCredsProvider.h>
#include <inet/EndPointStateOpenThread.h>
#include <platform/CHIPDeviceLayer.h>
#include <setup_payload/QRCodeSetupPayloadGenerator.h>
#include <setup_payload/SetupPayload.h>
#if CHIP_ENABLE_OPENTHREAD
#include <platform/OpenThread/OpenThreadUtils.h>
#include <platform/ThreadStackManager.h>
#endif
using namespace ::chip;
using namespace ::chip::app;
using namespace chip::TLV;
using namespace chip::Credentials;
using namespace chip::DeviceLayer;
using namespace ::chip::Platform;
using namespace ::chip::Credentials;
using namespace ::chip::app::Clusters;
using chip::DeviceLayer::PersistedStorage::KeyValueStoreMgr;
AppTask AppTask::sAppTask;
chip::DeviceLayer::FactoryDataProvider mFactoryDataProvider;
#define APP_FUNCTION_BUTTON BUTTON_USER1
#define STM32ThreadDataSet "STM32DataSet"
#define APP_EVENT_QUEUE_SIZE 10
#define NVM_TIMEOUT 1000 // timer to handle PB to save data in nvm or do a factory reset
#define DELAY_NVM 5000 // save data in nvm after commissioning with a delay of 5 sec
#define STM32_LIGHT_ENDPOINT_ID 1
static QueueHandle_t sAppEventQueue;
TimerHandle_t sPushButtonTimeoutTimer;
TimerHandle_t DelayNvmTimer;
const osThreadAttr_t AppTask_attr = { .name = APPTASK_NAME,
.attr_bits = APP_ATTR_BITS,
.cb_mem = APP_CB_MEM,
.cb_size = APP_CB_SIZE,
.stack_mem = APP_STACK_MEM,
.stack_size = APP_STACK_SIZE,
.priority = APP_PRIORITY };
static bool sIsThreadProvisioned = false;
static bool sIsThreadEnabled = false;
static bool sHaveBLEConnections = false;
static bool sFabricNeedSaved = false;
static bool sFailCommissioning = false;
static bool sHaveFabric = false;
static uint8_t NvmTimerCpt = 0;
static uint8_t NvmButtonStateCpt = 0;
CHIP_ERROR AppTask::StartAppTask()
{
sAppEventQueue = xQueueCreate(APP_EVENT_QUEUE_SIZE, sizeof(AppEvent));
if (sAppEventQueue == NULL)
{
APP_DBG("Failed to allocate app event queue");
return CHIP_ERROR_NO_MEMORY;
}
// Start App task.
osThreadNew(AppTaskMain, NULL, &AppTask_attr);
return CHIP_NO_ERROR;
}
void LockOpenThreadTask(void)
{
chip::DeviceLayer::ThreadStackMgr().LockThreadStack();
}
void UnlockOpenThreadTask(void)
{
chip::DeviceLayer::ThreadStackMgr().UnlockThreadStack();
}
CHIP_ERROR AppTask::Init()
{
CHIP_ERROR err = CHIP_NO_ERROR;
ChipLogProgress(NotSpecified, "Current Software Version: %s", CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING);
// Setup button handler
APP_ENTRY_PBSetReceiveCallback(ButtonEventHandler);
// Create FreeRTOS sw timer for Push button timeouts.
sPushButtonTimeoutTimer = xTimerCreate("PushButtonTimer", // Just a text name, not used by the RTOS kernel
pdMS_TO_TICKS(NVM_TIMEOUT), // == default timer period (mS)
true, // no timer reload (==one-shot)
(void *) this, // init timer id
TimerEventHandler // timer callback handler
);
DelayNvmTimer = xTimerCreate("Delay_NVM", // Just a text name, not used by the RTOS kernel
DELAY_NVM, // == default timer period (mS)
pdFALSE, // timer reload
0, // init timer
DelayNvmHandler // timer callback handler
);
ThreadStackMgr().InitThreadStack();
ConnectivityMgr().SetThreadDeviceType(ConnectivityManager::kThreadDeviceType_Router);
PlatformMgr().AddEventHandler(MatterEventHandler, 0);
err = LightingMgr().Init();
if (err != CHIP_NO_ERROR)
{
APP_DBG("LightingMgr().Init() failed");
return err;
}
LightingMgr().SetCallbacks(ActionInitiated, ActionCompleted);
#if CHIP_DEVICE_CONFIG_ENABLE_EXTENDED_DISCOVERY
chip::app::DnssdServer::Instance().SetExtendedDiscoveryTimeoutSecs(extDiscTimeoutSecs);
#endif
// Init ZCL Data Model
static chip::CommonCaseDeviceServerInitParams initParams;
(void) initParams.InitializeStaticResourcesBeforeServerInit();
ReturnErrorOnFailure(mFactoryDataProvider.Init());
SetDeviceInstanceInfoProvider(&mFactoryDataProvider);
SetCommissionableDataProvider(&mFactoryDataProvider);
SetDeviceAttestationCredentialsProvider(&mFactoryDataProvider);
chip::Inet::EndPointStateOpenThread::OpenThreadEndpointInitParam nativeParams;
nativeParams.lockCb = LockOpenThreadTask;
nativeParams.unlockCb = UnlockOpenThreadTask;
nativeParams.openThreadInstancePtr = chip::DeviceLayer::ThreadStackMgrImpl().OTInstance();
initParams.endpointNativeParams = static_cast<void *>(&nativeParams);
chip::Server::GetInstance().Init(initParams);
ConfigurationMgr().LogDeviceConfig();
// Open commissioning after boot if no fabric was available
if (chip::Server::GetInstance().GetFabricTable().FabricCount() == 0)
{
PrintOnboardingCodes(chip::RendezvousInformationFlags(chip::RendezvousInformationFlag::kBLE));
// Enable BLE advertisements
chip::Server::GetInstance().GetCommissioningWindowManager().OpenBasicCommissioningWindow();
APP_DBG("BLE advertising started. Waiting for Pairing.");
}
else
{ // try to attach to the thread network
uint8_t datasetBytes[Thread::kSizeOperationalDataset];
size_t datasetLength = 0;
char Message[20];
snprintf(Message, sizeof(Message), "Fabric Found: %d", chip::Server::GetInstance().GetFabricTable().FabricCount());
APP_BLE_Init_Dyn_3();
UTIL_LCD_DisplayStringAt(0, LINE(1), (uint8_t *) Message, LEFT_MODE);
BSP_LCD_Refresh(0);
CHIP_ERROR error = KeyValueStoreMgr().Get(STM32ThreadDataSet, datasetBytes, sizeof(datasetBytes), &datasetLength);
if (error == CHIP_NO_ERROR)
{
ThreadStackMgr().SetThreadProvision(ByteSpan(datasetBytes, datasetLength));
ThreadStackMgr().SetThreadEnabled(true);
}
else
{
APP_DBG("Thread network Data set was not found");
}
}
err = PlatformMgr().StartEventLoopTask();
if (err != CHIP_NO_ERROR)
{
APP_DBG("PlatformMgr().StartEventLoopTask() failed");
}
return err;
}
CHIP_ERROR AppTask::InitMatter()
{
CHIP_ERROR err = CHIP_NO_ERROR;
err = chip::Platform::MemoryInit();
if (err != CHIP_NO_ERROR)
{
APP_DBG("Platform::MemoryInit() failed");
}
else
{
APP_DBG("Init CHIP stack");
err = PlatformMgr().InitChipStack();
if (err != CHIP_NO_ERROR)
{
APP_DBG("PlatformMgr().InitChipStack() failed");
}
}
return err;
}
void AppTask::AppTaskMain(void * pvParameter)
{
AppEvent event;
CHIP_ERROR err = sAppTask.Init();
#if HIGHWATERMARK
UBaseType_t uxHighWaterMark;
HeapStats_t HeapStatsInfo;
size_t max_used;
size_t max_blocks;
#endif // endif HIGHWATERMARK
if (err != CHIP_NO_ERROR)
{
APP_DBG("App task init failled ");
}
APP_DBG("App Task started");
while (true)
{
BaseType_t eventReceived = xQueueReceive(sAppEventQueue, &event, pdMS_TO_TICKS(10));
while (eventReceived == pdTRUE)
{
sAppTask.DispatchEvent(&event);
eventReceived = xQueueReceive(sAppEventQueue, &event, 0);
}
#if HIGHWATERMARK
uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
vPortGetHeapStats(&HeapStatsInfo);
mbedtls_memory_buffer_alloc_max_get(&max_used, &max_blocks);
#endif // endif HIGHWATERMARK
}
}
void AppTask::LightingActionEventHandler(AppEvent * aEvent)
{
LightingManager::Action_t action;
if (aEvent->Type == AppEvent::kEventType_Button)
{
// Toggle light
if (LightingMgr().IsTurnedOn())
{
action = LightingManager::OFF_ACTION;
}
else
{
action = LightingManager::ON_ACTION;
}
sAppTask.mSyncClusterToButtonAction = true;
LightingMgr().InitiateAction(action, 0, 0, 0);
}
if (aEvent->Type == AppEvent::kEventType_Level && aEvent->ButtonEvent.Action != 0)
{
// Toggle Dimming of light between 2 fixed levels
uint8_t val = 0x0;
val = LightingMgr().GetLevel() == 0x7f ? 0x1 : 0x7f;
action = LightingManager::LEVEL_ACTION;
sAppTask.mSyncClusterToButtonAction = true;
LightingMgr().InitiateAction(action, 0, 1, &val);
}
}
void AppTask::ButtonEventHandler(Push_Button_st * Button)
{
AppEvent button_event = {};
button_event.Type = AppEvent::kEventType_Button;
button_event.ButtonEvent.ButtonIdx = Button->Pushed_Button;
button_event.ButtonEvent.Action = Button->State;
if (Button->Pushed_Button == APP_FUNCTION_BUTTON)
{
// Hand off to Functionality handler - depends on duration of press
button_event.Handler = FunctionHandler;
}
else
{
return;
}
sAppTask.PostEvent(&button_event);
}
void AppTask::TimerEventHandler(TimerHandle_t xTimer)
{
NvmTimerCpt++;
if (BSP_PB_GetState(BUTTON_USER1) == 0)
{
NvmButtonStateCpt++;
}
if (NvmTimerCpt >= 10)
{
xTimerStop(sPushButtonTimeoutTimer, 0);
if (NvmButtonStateCpt >= 9)
{
AppEvent event;
event.Type = AppEvent::kEventType_Timer;
event.Handler = UpdateNvmEventHandler;
sAppTask.mFunction = kFunction_FactoryReset;
sAppTask.PostEvent(&event);
}
}
else if ((NvmTimerCpt > NvmButtonStateCpt) && (NvmTimerCpt <= 2))
{
AppEvent event;
event.Type = AppEvent::kEventType_Timer;
event.Handler = UpdateNvmEventHandler;
xTimerStop(sPushButtonTimeoutTimer, 0);
sAppTask.mFunction = kFunction_SaveNvm;
sAppTask.PostEvent(&event);
}
}
void AppTask::FunctionHandler(AppEvent * aEvent)
{
if (xTimerIsTimerActive(sPushButtonTimeoutTimer) == 0)
{
xTimerStart(sPushButtonTimeoutTimer, 0);
NvmTimerCpt = 0;
NvmButtonStateCpt = 0;
}
}
void AppTask::ActionInitiated(LightingManager::Action_t aAction)
{
// Placeholder for light action
UTIL_LCD_ClearStringLine(2);
if (aAction == LightingManager::ON_ACTION)
{
APP_DBG("Light goes on");
char Message[11];
snprintf(Message, sizeof(Message), "LED ON %d", LightingMgr().GetLevel());
UTIL_LCD_DisplayStringAt(0, LINE(2), (uint8_t *) Message, CENTER_MODE);
}
else if (aAction == LightingManager::OFF_ACTION)
{
APP_DBG("Light goes off ");
UTIL_LCD_ClearStringLine(2);
}
else if (aAction == LightingManager::LEVEL_ACTION)
{
if (LightingMgr().IsTurnedOn())
{
char Message[11];
snprintf(Message, sizeof(Message), "LED ON %d", LightingMgr().GetLevel());
UTIL_LCD_DisplayStringAt(0, LINE(2), (uint8_t *) Message, CENTER_MODE);
APP_DBG("Update level control %d", LightingMgr().GetLevel());
}
}
BSP_LCD_Refresh(0);
}
void AppTask::ActionCompleted(LightingManager::Action_t aAction)
{
// Placeholder for light action completed
if (aAction == LightingManager::ON_ACTION)
{
APP_DBG("Light action on completed");
}
else if (aAction == LightingManager::OFF_ACTION)
{
APP_DBG("Light action off completed");
}
if (sAppTask.mSyncClusterToButtonAction)
{
sAppTask.UpdateClusterState();
sAppTask.mSyncClusterToButtonAction = false;
}
}
void AppTask::PostEvent(const AppEvent * aEvent)
{
if (sAppEventQueue != NULL)
{
if (!xQueueSend(sAppEventQueue, aEvent, 1))
{
ChipLogError(NotSpecified, "Failed to post event to app task event queue");
}
}
else
{
ChipLogError(NotSpecified, "Event Queue is NULL should never happen");
}
}
void AppTask::DispatchEvent(AppEvent * aEvent)
{
if (aEvent->Handler)
{
aEvent->Handler(aEvent);
}
else
{
ChipLogError(NotSpecified, "Event received with no handler. Dropping event.");
}
}
/**
* Update cluster status after application level changes
*/
void AppTask::UpdateClusterState(void)
{
ChipLogProgress(NotSpecified, "UpdateClusterState");
// Write the new on/off value
EmberAfStatus status = Clusters::OnOff::Attributes::OnOff::Set(STM32_LIGHT_ENDPOINT_ID, LightingMgr().IsTurnedOn());
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
ChipLogError(NotSpecified, "ERR: updating on/off %x", status);
}
// Write new level value
status = Clusters::LevelControl::Attributes::CurrentLevel::Set(STM32_LIGHT_ENDPOINT_ID, LightingMgr().GetLevel());
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
ChipLogError(NotSpecified, "ERR: updating level %x", status);
}
}
void AppTask::DelayNvmHandler(TimerHandle_t xTimer)
{
AppEvent event;
event.Type = AppEvent::kEventType_Timer;
event.Handler = UpdateNvmEventHandler;
sAppTask.mFunction = kFunction_SaveNvm;
sAppTask.PostEvent(&event);
}
void AppTask::UpdateLCD(void)
{
if (sIsThreadProvisioned && sIsThreadEnabled)
{
UTIL_LCD_DisplayStringAt(0, LINE(4), (uint8_t *) "Network Joined", LEFT_MODE);
}
else if ((sIsThreadProvisioned == false) || (sIsThreadEnabled == false))
{
UTIL_LCD_ClearStringLine(4);
}
if (sHaveBLEConnections)
{
UTIL_LCD_ClearStringLine(1);
BSP_LCD_Refresh(0);
UTIL_LCD_DisplayStringAt(0, LINE(1), (uint8_t *) "BLE Connected", LEFT_MODE);
}
if (sHaveFabric)
{
UTIL_LCD_ClearStringLine(1);
BSP_LCD_Refresh(0);
UTIL_LCD_DisplayStringAt(0, LINE(1), (uint8_t *) "Fabric Created", LEFT_MODE);
}
if (sFailCommissioning == true)
{
UTIL_LCD_DisplayStringAt(0, LINE(1), (uint8_t *) "", LEFT_MODE);
BSP_LCD_Refresh(0);
UTIL_LCD_DisplayStringAt(0, LINE(1), (uint8_t *) "Fabric Failed", LEFT_MODE);
}
BSP_LCD_Refresh(0);
}
void AppTask::UpdateNvmEventHandler(AppEvent * aEvent)
{
uint8_t err = 0;
if (sAppTask.mFunction == kFunction_SaveNvm)
{
if (sIsThreadProvisioned && sIsThreadEnabled)
{
chip::Thread::OperationalDataset dataset{};
DeviceLayer::ThreadStackMgrImpl().GetThreadProvision(dataset);
ByteSpan datasetbyte = dataset.AsByteSpan();
KeyValueStoreMgr().Put(STM32ThreadDataSet, datasetbyte.data(), datasetbyte.size());
}
err = NM_Dump();
if (err == 0)
{
APP_DBG("SAVE NVM");
}
else
{
APP_DBG("Failed to SAVE NVM");
// restart timer to save nvm later
xTimerStart(DelayNvmTimer, 0);
}
}
else if (sAppTask.mFunction == kFunction_FactoryReset)
{
APP_DBG("FACTORY RESET");
NM_ResetFactory();
}
}
void AppTask::MatterEventHandler(const ChipDeviceEvent * event, intptr_t)
{
switch (event->Type)
{
case DeviceEventType::kServiceProvisioningChange: {
sIsThreadProvisioned = event->ServiceProvisioningChange.IsServiceProvisioned;
UpdateLCD();
break;
}
case DeviceEventType::kThreadConnectivityChange: {
sIsThreadEnabled = (event->ThreadConnectivityChange.Result == kConnectivity_Established);
UpdateLCD();
break;
}
case DeviceEventType::kCHIPoBLEConnectionEstablished: {
sHaveBLEConnections = true;
APP_DBG("kCHIPoBLEConnectionEstablished");
UpdateLCD();
break;
}
case DeviceEventType::kCHIPoBLEConnectionClosed: {
sHaveBLEConnections = false;
APP_DBG("kCHIPoBLEConnectionClosed");
UpdateLCD();
if (sFabricNeedSaved)
{
APP_DBG("Start timer to save nvm after commissioning finish");
// timer is used to avoid to much traffic on m0 side after the end of a commissioning
xTimerStart(DelayNvmTimer, 0);
sFabricNeedSaved = false;
}
break;
}
case DeviceEventType::kCommissioningComplete: {
sFabricNeedSaved = true;
sHaveFabric = true;
// check if ble is on, since before save in nvm we need to stop m0, Better to write in nvm when m0 is less busy
if (sHaveBLEConnections == false)
{
APP_DBG("Start timer to save nvm after commissioning finish");
xTimerStart(DelayNvmTimer, 0);
sFabricNeedSaved = false; // put to false to avoid save in nvm 2 times
}
UpdateLCD();
break;
}
case DeviceEventType::kFailSafeTimerExpired: {
UpdateLCD();
sFailCommissioning = true;
break;
}
default:
break;
}
}