add new ClockScreenSaver
Some checks failed
ESP-IDF Build / build (esp32s3, latest) (push) Failing after 22s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 18s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 21s

Signed-off-by: Peter Siegmund <developer@mars3142.org>
This commit is contained in:
2025-08-21 23:11:42 +02:00
parent f199f0d781
commit 1ead66520b
23 changed files with 939 additions and 40 deletions

View File

@@ -1,13 +0,0 @@
ARG DOCKER_TAG=latest
FROM espressif/idf:${DOCKER_TAG}
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
RUN apt-get update -y && apt-get install udev -y
RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc
ENTRYPOINT [ "/opt/esp/entrypoint.sh" ]
CMD ["/bin/bash", "-c"]

View File

@@ -1,21 +0,0 @@
{
"name": "ESP-IDF QEMU",
"build": {
"dockerfile": "Dockerfile"
},
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"idf.espIdfPath": "/opt/esp/idf",
"idf.toolsPath": "/opt/esp",
"idf.gitPath": "/usr/bin/git"
},
"extensions": [
"espressif.esp-idf-extension",
"espressif.esp-idf-web"
]
}
},
"runArgs": ["--privileged"]
}

View File

@@ -1,15 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "gdbtarget",
"request": "attach",
"name": "Eclipse CDT GDB Adapter"
},
{
"type": "espidf",
"name": "Launch",
"request": "launch"
}
]
}

View File

@@ -8,6 +8,7 @@ set(SOURCE_FILES
src/ui/LightMenu.cpp
src/ui/LightSettingsMenu.cpp
src/ui/MainMenu.cpp
src/ui/ClockScreenSaver.cpp
src/ui/ScreenSaver.cpp
src/ui/SettingsMenu.cpp
src/ui/SplashScreen.cpp

View File

@@ -0,0 +1,69 @@
/**
* @file ClockScreenSaver.h
* @brief Animated clock screensaver implementation with bouncing time display
* @details This header defines the ClockScreenSaver class which provides an animated
* clock screensaver that shows the system time bouncing around the screen.
* The screensaver prevents screen burn-in while displaying useful information.
* @author System Control Team
* @date 2025-06-14
*/
#pragma once
#include "MenuOptions.h"
#include "common/Widget.h"
/**
* @class ClockScreenSaver
* @brief Animated clock screensaver widget for system idle periods
*/
class ClockScreenSaver final : public Widget
{
public:
explicit ClockScreenSaver(menu_options_t *options);
void update(uint64_t dt) override;
void render() override;
void onButtonClicked(ButtonType button) override;
private:
static constexpr int MOVE_INTERVAL = 50; // milliseconds between movements
static constexpr int X_VELOCITY = 1; // horizontal movement speed
static constexpr int Y_VELOCITY = 1; // vertical movement speed
static constexpr int TEXT_PADDING = 0; // padding around text for bounds checking
static constexpr const uint8_t *FONT = u8g2_font_profont15_tf; // font for time display
menu_options_t *m_options;
uint64_t m_moveTimer;
// Position and movement
int m_posX;
int m_posY;
int m_velocityX;
int m_velocityY;
// Text dimensions (calculated once)
int m_textWidth;
int m_textHeight;
/**
* @brief Initialize position and movement
*/
void initPosition();
/**
* @brief Update the text dimensions based on current time
*/
void updateTextDimensions();
/**
* @brief Get the current time as a formatted string
* @param buffer Buffer to store the formatted time
* @param bufferSize Size of the buffer
*/
void getCurrentTimeString(char *buffer, size_t bufferSize) const;
/**
* @brief Check and handle collision with screen boundaries
*/
void checkBoundaryCollision();
};

View File

@@ -0,0 +1,122 @@
#include "ui/ClockScreenSaver.h"
#include <cstring>
#include <ctime>
ClockScreenSaver::ClockScreenSaver(menu_options_t *options)
: Widget(options->u8g2), m_options(options), m_moveTimer(0), m_posX(0), m_posY(0), m_velocityX(X_VELOCITY),
m_velocityY(Y_VELOCITY), m_textWidth(0), m_textHeight(0)
{
initPosition();
}
void ClockScreenSaver::initPosition()
{
// Start in the center of the screen
updateTextDimensions();
m_posX = (u8g2->width - m_textWidth) / 2;
m_posY = (u8g2->height - m_textHeight) / 2;
// Set initial velocity
m_velocityX = X_VELOCITY;
m_velocityY = Y_VELOCITY;
}
void ClockScreenSaver::updateTextDimensions()
{
char timeBuffer[32];
getCurrentTimeString(timeBuffer, sizeof(timeBuffer));
// Set font (use a large font for better visibility)
u8g2_SetFont(u8g2, FONT);
// Calculate text dimensions
m_textWidth = u8g2_GetStrWidth(u8g2, timeBuffer);
m_textHeight = u8g2_GetAscent(u8g2) - u8g2_GetDescent(u8g2);
}
void ClockScreenSaver::getCurrentTimeString(char *buffer, size_t bufferSize) const
{
time_t rawtime;
struct tm *timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
// Format time as HH:MM:SS
strftime(buffer, bufferSize, "%H:%M:%S", timeinfo);
}
void ClockScreenSaver::update(const uint64_t dt)
{
m_moveTimer += dt;
// Move the clock position at regular intervals
if (m_moveTimer > MOVE_INTERVAL)
{
m_moveTimer = 0;
// Update text dimensions (in case time format changes)
updateTextDimensions();
// Update position
m_posX += m_velocityX;
m_posY += m_velocityY;
// Check for collisions with screen boundaries
checkBoundaryCollision();
}
}
void ClockScreenSaver::checkBoundaryCollision()
{
// Check horizontal boundaries
if (m_posX <= TEXT_PADDING)
{
m_posX = TEXT_PADDING;
m_velocityX = X_VELOCITY; // Bounce right
}
else if (m_posX + m_textWidth >= u8g2->width - TEXT_PADDING)
{
m_posX = u8g2->width - m_textWidth - TEXT_PADDING;
m_velocityX = -X_VELOCITY; // Bounce left
}
// Check vertical boundaries
if (m_posY <= TEXT_PADDING + m_textHeight)
{
m_posY = TEXT_PADDING + m_textHeight;
m_velocityY = Y_VELOCITY; // Bounce down
}
else if (m_posY >= u8g2->height - TEXT_PADDING)
{
m_posY = u8g2->height - TEXT_PADDING;
m_velocityY = -Y_VELOCITY; // Bounce up
}
}
void ClockScreenSaver::render()
{
// Clear screen with a black background
u8g2_SetDrawColor(u8g2, 0);
u8g2_DrawBox(u8g2, 0, 0, u8g2->width, u8g2->height);
u8g2_SetDrawColor(u8g2, 1);
// Get current time
char timeBuffer[32];
getCurrentTimeString(timeBuffer, sizeof(timeBuffer));
// Set font
u8g2_SetFont(u8g2, FONT);
// Draw the time at current position
u8g2_DrawStr(u8g2, m_posX, m_posY, timeBuffer);
}
void ClockScreenSaver::onButtonClicked(ButtonType button)
{
// Exit screensaver on any button press
if (m_options && m_options->popScreen)
{
m_options->popScreen();
}
}

View File

@@ -9,15 +9,15 @@
*/
namespace LightMenuItem
{
constexpr uint8_t ACTIVATE = 0; ///< ID for the light activation toggle
constexpr uint8_t MODE = 1; ///< ID for the light mode selection
constexpr uint8_t LED_SETTINGS = 2; ///< ID for the LED settings menu item
constexpr auto ACTIVATE = 0; ///< ID for the light activation toggle
constexpr auto MODE = 1; ///< ID for the light mode selection
constexpr auto LED_SETTINGS = 2; ///< ID for the LED settings menu item
} // namespace LightMenuItem
namespace LightMenuOptions
{
constexpr std::string LIGHT_ACTIVE = "light_active";
constexpr std::string LIGHT_MODE = "light_mode";
constexpr auto LIGHT_ACTIVE = "light_active";
constexpr auto LIGHT_MODE = "light_mode";
} // namespace LightMenuOptions
LightMenu::LightMenu(menu_options_t *options) : Menu(options), m_options(options)
@@ -57,7 +57,8 @@ void LightMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType butto
if (button == ButtonType::SELECT)
{
toggle(menuItem);
if (getItem(menuItem.getId()).getValue() == "1")
const auto value = getItem(menuItem.getId()).getValue() == "1";
if (value)
{
led_event_data_t payload = {.value = 42};
send_event(EVENT_LED_ON, &payload);
@@ -69,7 +70,6 @@ void LightMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType butto
}
if (m_options && m_options->persistenceManager)
{
const auto value = getItem(menuItem.getId()).getValue() == "1";
m_options->persistenceManager->SetValue(LightMenuOptions::LIGHT_ACTIVE, value);
m_options->persistenceManager->Save();
}
@@ -82,9 +82,12 @@ void LightMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType butto
const auto item = switchValue(menuItem, button);
if (button == ButtonType::LEFT || button == ButtonType::RIGHT)
{
const auto value = getItem(item.getId()).getIndex();
led_event_data_t payload = {.value = value};
send_event(EVENT_LED_DAY + value, &payload);
if (m_options && m_options->persistenceManager)
{
const auto value = getItem(item.getId()).getIndex();
m_options->persistenceManager->SetValue(LightMenuOptions::LIGHT_MODE, value);
m_options->persistenceManager->Save();
}

View File

@@ -1,6 +1,7 @@
if (DEFINED ENV{IDF_PATH})
idf_component_register(SRCS
src/hal_esp32/led_manager.cpp
src/hal_esp32/led_status.cpp
INCLUDE_DIRS "include"
PRIV_REQUIRES
u8g2

View File

@@ -0,0 +1,3 @@
#pragma once
void led_status_init();

View File

@@ -21,7 +21,7 @@ uint64_t wled_init(void)
led_strip_config_t strip_config = {.strip_gpio_num = CONFIG_WLED_DIN_PIN,
.max_leds = 64,
.led_model = LED_MODEL_WS2812,
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_RGB,
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB,
.flags = {
.invert_out = false,
}};
@@ -35,9 +35,9 @@ uint64_t wled_init(void)
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
for (uint32_t i = 0; i < 64; i++)
for (uint32_t i = 0; i < 3; i++)
{
led_strip_set_pixel(led_strip, i, 0, 0, 0);
led_strip_set_pixel(led_strip, i, 10, 10, 0);
}
led_strip_refresh(led_strip);

View File

@@ -0,0 +1,7 @@
#include "led_status.h"
void led_status_init()
{
// This function is intentionally left empty for the native implementation.
// It serves as a placeholder to maintain compatibility with the HAL interface.
}

View File

@@ -0,0 +1,7 @@
#include "led_status.h"
void led_status_init()
{
// This function is intentionally left empty for the native implementation.
// It serves as a placeholder to maintain compatibility with the HAL interface.
}

Binary file not shown.

View File

@@ -1,7 +1,6 @@
idf_component_register(SRCS
main.cpp
app_task.cpp
../components/persistence-manager/src/hal_esp32/PersistenceManager.cpp
button_handling.c
hal/u8g2_esp32_hal.c
INCLUDE_DIRS "."

View File

@@ -1,7 +1,14 @@
menu "Warnemuende Lighthouse"
menu "System Control"
config WLED_DIN_PIN
int "WLED Data In Pin"
default 14
help
The number of the WLED data in pin.
config STATUS_WLED_PIN
int "Status WLED Pin"
default 2
help
The number of the status WLED pin.
endmenu

View File

@@ -8,6 +8,7 @@
#include "button_handling.h"
#include "common/InactivityTracker.h"
#include "hal_esp32/PersistenceManager.h"
#include "ui/ClockScreenSaver.h"
#include "ui/ScreenSaver.h"
#include "ui/SplashScreen.h"
@@ -103,7 +104,7 @@ static void init_ui(void)
};
m_widget = std::make_shared<SplashScreen>(&options);
m_inactivityTracker = std::make_unique<InactivityTracker>(60000, []() {
auto screensaver = std::make_shared<ScreenSaver>(&options);
auto screensaver = std::make_shared<ClockScreenSaver>(&options);
options.pushScreen(screensaver);
});
}

View File

@@ -43,7 +43,7 @@ static void button_event_cb(void *arg, void *usr_data)
}
}
static void create_button(uint8_t gpio, int index)
static void init_button(uint8_t gpio, int index)
{
const button_config_t btn_cfg = {0};
const button_gpio_config_t btn_gpio_cfg = {
@@ -74,7 +74,7 @@ void setup_buttons(void)
ESP_LOGI(TAG, "Button queue created successfully");
for (int i = 0; i < sizeof(gpios) / sizeof(gpios[0]); i++)
{
create_button(gpios[i], i);
init_button(gpios[i], i);
}
}

View File

@@ -1,7 +1,8 @@
# Name , Type , SubType , Offset , Size , Flags
nvs , data , nvs , 0x9000 , 20k ,
otadata , data , ota , 0xe000 , 8k ,
otadata , data , ota , , 8k ,
app0 , app , ota_0 , 0x10000 , 1024k ,
app1 , app , ota_1 , , 1024k ,
spiffs , data , spiffs , , 1536k ,
phy , data , phy , , 4k ,
coredump , data , coredump , , 64k ,
spiffs , data , spiffs , , 1916k ,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 20k
3 otadata data ota 0xe000 8k
4 app0 app ota_0 0x10000 1024k
5 app1 app ota_1 1024k
6 spiffs phy data spiffs phy 1536k 4k
7 coredump data coredump 64k
8 spiffs data spiffs 1916k

View File

@@ -1,4 +1,4 @@
# activate Bluetooth Low Energy (BLE)
# Bluetooth
CONFIG_BT_ENABLED=y
CONFIG_BT_NIMBLE_ENABLED=y
@@ -13,7 +13,10 @@ CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
# Partitions
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y
# Build type
CONFIG_APP_REPRODUCIBLE_BUILD=y
# Core dump
CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y
CONFIG_ESP_COREDUMP_CAPTURE_DRAM=y

View File

@@ -6,6 +6,7 @@
#include "MenuOptions.h"
#include "common/InactivityTracker.h"
#include "hal_native/PersistenceManager.h"
#include "ui/ClockScreenSaver.h"
#include "ui/ScreenSaver.h"
#include "ui/SplashScreen.h"
#include "ui/widgets/Button.h"
@@ -56,15 +57,11 @@ Device::Device(void *appstate) : UIWidget(appstate)
m_children.push_back(std::make_shared<Button>(
GetContext(), U8G2_SCREEN_WIDTH * U8G2_SCREEN_FACTOR + 3 * U8G2_SCREEN_PADDING + DPAD_WIDTH,
U8G2_SCREEN_HEIGHT * U8G2_SCREEN_FACTOR + U8G2_SCREEN_PADDING - BUTTON_WIDTH, BUTTON_WIDTH,
[]() {
PushKey(SDLK_RETURN);
}));
[]() { PushKey(SDLK_RETURN); }));
m_children.push_back(std::make_shared<Button>(
GetContext(), U8G2_SCREEN_WIDTH * U8G2_SCREEN_FACTOR + 4 * U8G2_SCREEN_PADDING + DPAD_WIDTH + BUTTON_WIDTH,
U8G2_SCREEN_HEIGHT * U8G2_SCREEN_FACTOR + U8G2_SCREEN_PADDING - 2 * BUTTON_WIDTH, BUTTON_WIDTH,
[]() {
PushKey(SDLK_BACKSPACE);
}));
[]() { PushKey(SDLK_BACKSPACE); }));
m_children.push_back(std::make_shared<D_Pad>(
GetContext(), U8G2_SCREEN_WIDTH * U8G2_SCREEN_FACTOR + 2 * U8G2_SCREEN_PADDING,
U8G2_SCREEN_HEIGHT * U8G2_SCREEN_FACTOR + U8G2_SCREEN_PADDING - DPAD_WIDTH, DPAD_WIDTH, dpad_callback));
@@ -74,22 +71,16 @@ Device::Device(void *appstate) : UIWidget(appstate)
options = {
.u8g2 = &u8g2,
.setScreen = [this](const std::shared_ptr<Widget> &screen) {
this->SetScreen(screen);
},
.pushScreen = [this](const std::shared_ptr<Widget> &screen) {
this->PushScreen(screen);
},
.popScreen = [this]() {
this->PopScreen();
},
.setScreen = [this](const std::shared_ptr<Widget> &screen) { this->SetScreen(screen); },
.pushScreen = [this](const std::shared_ptr<Widget> &screen) { this->PushScreen(screen); },
.popScreen = [this]() { this->PopScreen(); },
.persistenceManager = std::make_shared<PersistenceManager>(),
};
options.persistenceManager->Load();
m_widget = std::make_shared<SplashScreen>(&options);
m_inactivityTracker = std::make_unique<InactivityTracker>(60000, []() {
const auto screensaver = std::make_shared<ScreenSaver>(&options);
const auto screensaver = std::make_shared<ClockScreenSaver>(&options);
options.pushScreen(screensaver);
});
}