Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions examples/closure-app/silabs/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ silabs_executable("closure_app") {
"src/DataModelCallbacks.cpp",
]

if (!disable_lcd) {
sources += [ "src/ClosureUI.cpp" ]
}

deps = [ ":sdk" ]

if (wifi_soc) {
Expand Down
1 change: 1 addition & 0 deletions examples/closure-app/silabs/include/AppEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ struct AppEvent : public BaseAppEvent
{
kEventType_Closure = BaseAppEvent::kEventType_Max + 1,
kEventType_Install,
kEventType_UpdateUI,
};

struct
Expand Down
15 changes: 15 additions & 0 deletions examples/closure-app/silabs/include/AppTask.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,21 @@ class AppTask : public BaseApplication
*/
static void ButtonEventHandler(uint8_t button, uint8_t btnAction);

#ifdef DISPLAY_ENABLED
/**
* @brief Updates the closure UI with current closure state
*/
static void UpdateClosureUI();

/**
* @brief Event handler for UI update events
* Called from app task context to safely update UI with chip stack locked
*
* @param aEvent pointer to the UI update event being processed
*/
static void UpdateClosureUIHandler(AppEvent * aEvent);
#endif // DISPLAY_ENABLED

/**
* @brief Closure button action event handler
* Handles button press events for closure control operations
Expand Down
14 changes: 14 additions & 0 deletions examples/closure-app/silabs/include/ClosureManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
#include <AppEvent.h>
#include <lib/core/DataModelTypes.h>

#ifdef DISPLAY_ENABLED
#include "ClosureUI.h"
#endif

class ClosureManager
{
public:
Expand Down Expand Up @@ -156,6 +160,16 @@ class ClosureManager
*/
const Action_t & GetCurrentAction() const { return mCurrentAction; }

#ifdef DISPLAY_ENABLED
/**
* @brief Gets closure data specifically for UI display.
*
* @return ClosureUIData structure containing main state and overall current state
*/
ClosureUIData GetClosureUIData();

#endif // DISPLAY_ENABLED

/**
* @brief Checks if a MoveTo action is currently in progress.
*
Expand Down
64 changes: 64 additions & 0 deletions examples/closure-app/silabs/include/ClosureUI.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
*
* Copyright (c) 2025 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.
*/

#pragma once

#include "ClosureUIStrings.h"
#include "glib.h"
#include "lcd.h"
#include <app-common/zap-generated/cluster-objects.h>
#include <app/clusters/closure-control-server/closure-control-cluster-objects.h>
#include <app/data-model/Nullable.h>

/**
* @brief Structure to hold closure data needed for UI display
*/
struct ClosureUIData
{
chip::app::Clusters::ClosureControl::MainStateEnum mainState;
chip::app::DataModel::Nullable<chip::app::Clusters::ClosureControl::GenericOverallCurrentState> overallCurrentState;
};

struct ClosureUITextInitializer;

class ClosureUI
{
public:
static void DrawUI(GLIB_Context_t * glibContext);
static void SetMainState(chip::app::Clusters::ClosureControl::MainStateEnum state);

static void FormatAndSetPosition(const char * suffix);
static void FormatAndSetLatch(const char * suffix);
static void FormatAndSetSecure(const char * suffix);
static void FormatAndSetSpeed(const char * suffix);

private:
friend struct ClosureUITextInitializer;
static void DrawHeader(GLIB_Context_t * glibContext);
static void DrawFooter(GLIB_Context_t * glibContext);
static void DrawMainState(GLIB_Context_t * glibContext);
static void DrawOverallCurrentState(GLIB_Context_t * glibContext);

// Static variables to store the current closure state
static chip::app::Clusters::ClosureControl::MainStateEnum sMainState;
static char sPositionText[ClosureUIStrings::LCD_STRING_BUFFER_SIZE];
static char sLatchText[ClosureUIStrings::LCD_STRING_BUFFER_SIZE];
static char sSecureText[ClosureUIStrings::LCD_STRING_BUFFER_SIZE];
static char sSpeedText[ClosureUIStrings::LCD_STRING_BUFFER_SIZE];
static char sStateText[ClosureUIStrings::LCD_STRING_BUFFER_SIZE];
};
78 changes: 78 additions & 0 deletions examples/closure-app/silabs/include/ClosureUIStrings.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
*
* Copyright (c) 2025 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.
*/

#pragma once

#include <cstddef>

/**
* @file ClosureUIStrings.h
* @brief UI string constants

* @note IMPORTANT FOR DEVELOPERS:
* If you modify existing strings or add new strings in this file, you MUST ensure
* that all prefix + suffix combinations fit within LCD_CH_LINE_LEN (16 characters),
* as the LCD display can only show 16 characters per line. All text buffers are
* sized to LCD_STRING_BUFFER_SIZE (17 bytes: 16 characters + null terminator).
*/

namespace ClosureUIStrings {

// Shared suffix for unknown state
inline constexpr const char SUFFIX_UNKNOWN[] = "Unknown";

inline constexpr const char POSITION_PREFIX[] = "Pos: ";
inline constexpr const char POSITION_SUFFIX_CLOSED[] = "Closed";
inline constexpr const char POSITION_SUFFIX_OPEN[] = "Open";
inline constexpr const char POSITION_SUFFIX_PARTIAL[] = "Partial";
inline constexpr const char POSITION_SUFFIX_PEDESTRIAN[] = "Pedest";
inline constexpr const char POSITION_SUFFIX_VENTILATION[] = "Ventil";
inline constexpr const char POSITION_SUFFIX_SIGNATURE[] = "Sign";

inline constexpr const char LATCH_PREFIX[] = "Latch: ";
inline constexpr const char LATCH_SUFFIX_ENGAGED[] = "Yes";
inline constexpr const char LATCH_SUFFIX_RELEASED[] = "No";

inline constexpr const char SECURE_PREFIX[] = "Secure: ";
inline constexpr const char SECURE_SUFFIX_YES[] = "Yes";
inline constexpr const char SECURE_SUFFIX_NO[] = "No";

inline constexpr const char SPEED_PREFIX[] = "Speed: ";
inline constexpr const char SPEED_SUFFIX_LOW[] = "Low";
inline constexpr const char SPEED_SUFFIX_MEDIUM[] = "Med";
inline constexpr const char SPEED_SUFFIX_HIGH[] = "High";
inline constexpr const char SPEED_SUFFIX_AUTO[] = "Auto";

inline constexpr const char STATE_PREFIX[] = "State: ";
inline constexpr const char STATE_SUFFIX_STOPPED[] = "Stopped";
inline constexpr const char STATE_SUFFIX_MOVING[] = "Moving";
inline constexpr const char STATE_SUFFIX_WAITING[] = "Waiting";
inline constexpr const char STATE_SUFFIX_ERROR[] = "Error";
inline constexpr const char STATE_SUFFIX_CALIBRATING[] = "Calib";
inline constexpr const char STATE_SUFFIX_PROTECTED[] = "Protect";
inline constexpr const char STATE_SUFFIX_DISENGAGED[] = "Diseng";
inline constexpr const char STATE_SUFFIX_SETUP_REQUIRED[] = "SetupReq";

inline constexpr const char FOOTER_TEXT[] = "Closure App";

// LCD display line maximum length
inline constexpr size_t LCD_CH_LINE_LEN = 16;

// LCD string buffer size (lcd line length + null terminator)
inline constexpr size_t LCD_STRING_BUFFER_SIZE = LCD_CH_LINE_LEN + 1;
} // namespace ClosureUIStrings
108 changes: 106 additions & 2 deletions examples/closure-app/silabs/src/AppTask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#include "LEDWidget.h"

#ifdef DISPLAY_ENABLED
#include "ClosureUI.h"
#include "ClosureUIStrings.h"
#include "lcd.h"
#ifdef QR_CODE_ENABLED
#include "qrcodegen.h"
Expand All @@ -48,6 +50,7 @@
#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
Expand Down Expand Up @@ -81,14 +84,15 @@ CHIP_ERROR AppTask::AppInit()

#ifdef DISPLAY_ENABLED
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
GetLCD().WriteDemoUI(false);
UpdateClosureUI();
#ifdef QR_CODE_ENABLED
#ifdef SL_WIFI
if (!ConnectivityMgr().IsWiFiStationProvisioned())
Expand All @@ -99,7 +103,7 @@ CHIP_ERROR AppTask::AppInit()
GetLCD().ShowQRCode(true);
}
#endif // QR_CODE_ENABLED
#endif
#endif // DISPLAY_ENABLED

return err;
}
Expand Down Expand Up @@ -228,3 +232,103 @@ void AppTask::ClosureButtonActionEventHandler(AppEvent * aEvent)
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
11 changes: 11 additions & 0 deletions examples/closure-app/silabs/src/ClosureManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1284,6 +1284,17 @@ bool ClosureManager::GetPanelNextPosition(const GenericDimensionStateStruct & cu
}
return true;
}

#ifdef DISPLAY_ENABLED
ClosureUIData ClosureManager::GetClosureUIData()
{
ClosureUIData uiData;
mClosureEndpoint1.GetLogic().GetMainState(uiData.mainState);
mClosureEndpoint1.GetLogic().GetOverallCurrentState(uiData.overallCurrentState);
return uiData;
}
#endif // DISPLAY_ENABLED

bool ClosureManager::IsClosureControlMotionInProgress() const
{
return mIsMoveToInProgress;
Expand Down
Loading
Loading