blob: ba921f394a44fcf8c0489669281283de488b8630 [file] [log] [blame]
/*
*
* Copyright (c) 2025 Project CHIP Authors
* Copyright (c) 2025 Google LLC.
* 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.
*/
#include "AppTask.h"
#include "AppConfig.h"
#include "AppEvent.h"
#include "LEDWidget.h"
#ifdef DISPLAY_ENABLED
#include "ClosureUI.h"
#include "ClosureUIStrings.h"
#include "lcd.h"
#ifdef QR_CODE_ENABLED
#include "qrcodegen.h"
#endif // QR_CODE_ENABLED
#endif // DISPLAY_ENABLED
#include <ClosureManager.h>
#include <app-common/zap-generated/cluster-enums.h>
#include <app-common/zap-generated/cluster-objects.h>
#include <app-common/zap-generated/ids/Attributes.h>
#include <app-common/zap-generated/ids/Clusters.h>
#include <app/ConcreteAttributePath.h>
#include <app/clusters/network-commissioning/network-commissioning.h>
#include <app/server/Server.h>
#include <app/util/attribute-storage.h>
#include <app/util/endpoint-config-api.h>
#include <assert.h>
#include <lib/support/BitMask.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/logging/CHIPLogging.h>
#include <platform/CHIPDeviceLayer.h>
#include <platform/silabs/platformAbstraction/SilabsPlatform.h>
#include <setup_payload/OnboardingCodesUtil.h>
#include <setup_payload/QRCodeSetupPayloadGenerator.h>
#include <setup_payload/SetupPayload.h>
#include <stdio.h>
#define APP_FUNCTION_BUTTON 0
#define APP_CLOSURE_BUTTON 1
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace ::chip::DeviceLayer;
using namespace ::chip::DeviceLayer::Silabs;
using namespace ::chip::DeviceLayer::Internal;
using namespace chip::TLV;
namespace chip {
namespace app {
namespace Clusters {
namespace ClosureControl {
static chip::BitMask<Feature> sFeatureMap(Feature::kCalibration);
} // namespace ClosureControl
} // namespace Clusters
} // namespace app
} // namespace chip
AppTask AppTask::sAppTask;
CHIP_ERROR AppTask::AppInit()
{
CHIP_ERROR err = CHIP_NO_ERROR;
chip::DeviceLayer::Silabs::GetPlatform().SetButtonsCb(AppTask::ButtonEventHandler);
#ifdef DISPLAY_ENABLED
TEMPORARY_RETURN_IGNORED GetLCD().Init((uint8_t *) "Closure-App");
GetLCD().SetCustomUI(ClosureUI::DrawUI);
#endif
// Initialization of Closure Manager and endpoints of closure and closurepanel.
ClosureManager::GetInstance().Init();
// Update the LCD with the Stored value. Show QR Code if not provisioned
#ifdef DISPLAY_ENABLED
UpdateClosureUI();
#ifdef QR_CODE_ENABLED
#ifdef SL_WIFI
if (!ConnectivityMgr().IsWiFiStationProvisioned())
#else
if (!ConnectivityMgr().IsThreadProvisioned())
#endif /* !SL_WIFI */
{
GetLCD().ShowQRCode(true);
}
#endif // QR_CODE_ENABLED
#endif // DISPLAY_ENABLED
return err;
}
CHIP_ERROR AppTask::StartAppTask()
{
return BaseApplication::StartAppTask(AppTaskMain);
}
void AppTask::AppTaskMain(void * pvParameter)
{
AppEvent event;
osMessageQueueId_t sAppEventQueue = *(static_cast<osMessageQueueId_t *>(pvParameter));
CHIP_ERROR err = sAppTask.Init();
if (err != CHIP_NO_ERROR)
{
ChipLogError(AppServer, "AppTask Init failed");
appError(err);
}
ChipLogProgress(AppServer, "App Task started");
while (true)
{
osStatus_t eventReceived = osMessageQueueGet(sAppEventQueue, &event, NULL, osWaitForever);
while (eventReceived == osOK)
{
sAppTask.DispatchEvent(&event);
eventReceived = osMessageQueueGet(sAppEventQueue, &event, NULL, 0);
}
}
}
void AppTask::ButtonEventHandler(uint8_t button, uint8_t btnAction)
{
AppEvent button_event = {};
button_event.Type = AppEvent::kEventType_Button;
button_event.ButtonEvent.Action = btnAction;
if (button == APP_CLOSURE_BUTTON && btnAction == static_cast<uint8_t>(SilabsPlatform::ButtonAction::ButtonPressed))
{
button_event.Handler = ClosureButtonActionEventHandler;
sAppTask.PostEvent(&button_event);
}
else if (button == APP_FUNCTION_BUTTON)
{
button_event.Handler = BaseApplication::ButtonHandler;
sAppTask.PostEvent(&button_event);
}
}
void AppTask::ClosureButtonActionEventHandler(AppEvent * aEvent)
{
if (aEvent->Type == AppEvent::kEventType_Button)
{
// Schedule work on the chip stack thread to ensure all CHIP API calls are safe
TEMPORARY_RETURN_IGNORED chip::DeviceLayer::PlatformMgr().ScheduleWork(
[](intptr_t) {
// Check if an action is already in progress
if (ClosureManager::GetInstance().IsClosureControlMotionInProgress())
{
// Stop the current action
auto status = ClosureManager::GetInstance().GetClosureControlCluster().HandleStop();
if (status != Protocols::InteractionModel::Status::Success)
{
ChipLogError(AppServer, "Failed to stop closure action: %u", to_underlying(status));
}
}
else
{
DataModel::Nullable<ClosureControl::GenericOverallCurrentState> currentState =
ClosureManager::GetInstance().GetClosureControlCluster().GetOverallCurrentState();
if (currentState.IsNull())
{
ChipLogError(AppServer, "Failed to get current closure state: currentState is null");
return;
}
if (!currentState.Value().position.HasValue() || currentState.Value().position.Value().IsNull())
{
ChipLogError(AppServer, "Failed to get current closure state: position is null");
return;
}
// Get current position and determine target position (toggle)
auto currentPosition = currentState.Value().position.Value().Value();
ChipLogProgress(AppServer, "Current state - Position: %d", to_underlying(currentPosition));
ClosureControl::TargetPositionEnum targetPosition =
(currentPosition == ClosureControl::CurrentPositionEnum::kFullyOpened)
? ClosureControl::TargetPositionEnum::kMoveToFullyClosed
: ClosureControl::TargetPositionEnum::kMoveToFullyOpen;
ChipLogProgress(AppServer, "Target position: %d", to_underlying(targetPosition));
Optional<bool> latch = chip::NullOptional;
if (currentState.Value().latch.HasValue() && !currentState.Value().latch.Value().IsNull())
{
latch = MakeOptional(false);
}
Optional<Globals::ThreeLevelAutoEnum> speed = NullOptional;
if (currentState.Value().speed.HasValue())
{
speed = chip::MakeOptional(currentState.Value().speed.Value());
}
// Move to the target position with latch set to false and preserved speed value
auto status = ClosureManager::GetInstance().GetClosureControlCluster().HandleMoveTo(
MakeOptional(targetPosition), latch, speed);
if (status != Protocols::InteractionModel::Status::Success)
{
ChipLogError(AppServer, "Failed to move closure to target position: %u", to_underlying(status));
}
}
},
0);
}
else
{
ChipLogError(AppServer, "Unhandled event type in ClosureButtonActionEventHandler");
}
}
#ifdef DISPLAY_ENABLED
void AppTask::UpdateClosureUIHandler(AppEvent * aEvent)
{
if (aEvent->Type == AppEvent::kEventType_UpdateUI)
{
UpdateClosureUI();
}
}
void AppTask::UpdateClosureUI()
{
ClosureManager & closureManager = ClosureManager::GetInstance();
// Lock chip stack when accessing CHIP attributes from app task context
DeviceLayer::PlatformMgr().LockChipStack();
auto uiData = closureManager.GetClosureUIData();
DeviceLayer::PlatformMgr().UnlockChipStack();
ClosureUI::SetMainState(uiData.mainState);
const char * positionSuffix = ClosureUIStrings::SUFFIX_UNKNOWN;
if (!uiData.overallCurrentState.IsNull() && uiData.overallCurrentState.Value().position.HasValue() &&
!uiData.overallCurrentState.Value().position.Value().IsNull())
{
switch (uiData.overallCurrentState.Value().position.Value().Value())
{
case chip::app::Clusters::ClosureControl::CurrentPositionEnum::kFullyClosed:
positionSuffix = ClosureUIStrings::POSITION_SUFFIX_CLOSED;
break;
case chip::app::Clusters::ClosureControl::CurrentPositionEnum::kFullyOpened:
positionSuffix = ClosureUIStrings::POSITION_SUFFIX_OPEN;
break;
case chip::app::Clusters::ClosureControl::CurrentPositionEnum::kPartiallyOpened:
positionSuffix = ClosureUIStrings::POSITION_SUFFIX_PARTIAL;
break;
case chip::app::Clusters::ClosureControl::CurrentPositionEnum::kOpenedForPedestrian:
positionSuffix = ClosureUIStrings::POSITION_SUFFIX_PEDESTRIAN;
break;
case chip::app::Clusters::ClosureControl::CurrentPositionEnum::kOpenedForVentilation:
positionSuffix = ClosureUIStrings::POSITION_SUFFIX_VENTILATION;
break;
default:
positionSuffix = ClosureUIStrings::SUFFIX_UNKNOWN;
break;
}
}
ClosureUI::FormatAndSetPosition(positionSuffix);
const char * latchSuffix = ClosureUIStrings::SUFFIX_UNKNOWN;
if (!uiData.overallCurrentState.IsNull() && uiData.overallCurrentState.Value().latch.HasValue() &&
!uiData.overallCurrentState.Value().latch.Value().IsNull())
{
latchSuffix = uiData.overallCurrentState.Value().latch.Value().Value() ? ClosureUIStrings::LATCH_SUFFIX_ENGAGED
: ClosureUIStrings::LATCH_SUFFIX_RELEASED;
}
ClosureUI::FormatAndSetLatch(latchSuffix);
const char * secureSuffix = ClosureUIStrings::SUFFIX_UNKNOWN;
if (!uiData.overallCurrentState.IsNull() && !uiData.overallCurrentState.Value().secureState.IsNull())
{
secureSuffix = uiData.overallCurrentState.Value().secureState.Value() ? ClosureUIStrings::SECURE_SUFFIX_YES
: ClosureUIStrings::SECURE_SUFFIX_NO;
}
ClosureUI::FormatAndSetSecure(secureSuffix);
const char * speedSuffix = ClosureUIStrings::SUFFIX_UNKNOWN;
if (!uiData.overallCurrentState.IsNull() && uiData.overallCurrentState.Value().speed.HasValue())
{
switch (uiData.overallCurrentState.Value().speed.Value())
{
case chip::app::Clusters::Globals::ThreeLevelAutoEnum::kLow:
speedSuffix = ClosureUIStrings::SPEED_SUFFIX_LOW;
break;
case chip::app::Clusters::Globals::ThreeLevelAutoEnum::kMedium:
speedSuffix = ClosureUIStrings::SPEED_SUFFIX_MEDIUM;
break;
case chip::app::Clusters::Globals::ThreeLevelAutoEnum::kHigh:
speedSuffix = ClosureUIStrings::SPEED_SUFFIX_HIGH;
break;
case chip::app::Clusters::Globals::ThreeLevelAutoEnum::kAuto:
speedSuffix = ClosureUIStrings::SPEED_SUFFIX_AUTO;
break;
default:
speedSuffix = ClosureUIStrings::SUFFIX_UNKNOWN;
break;
}
}
ClosureUI::FormatAndSetSpeed(speedSuffix);
#ifdef SL_WIFI
if (ConnectivityMgr().IsWiFiStationProvisioned())
#else
if (ConnectivityMgr().IsThreadProvisioned())
#endif /* !SL_WIFI */
{
AppTask::GetAppTask().GetLCD().WriteDemoUI(false); // State doesn't matter for custom UI
}
}
#endif // DISPLAY_ENABLED