From d3dd96c93abbcf197862d99c132ea002516d070f Mon Sep 17 00:00:00 2001 From: Peter Siegmund Date: Thu, 19 Jun 2025 23:21:43 +0200 Subject: [PATCH] add screensaver and optimize performance Signed-off-by: Peter Siegmund --- CMakeLists.txt | 2 +- components/bob/CMakeLists.txt | 10 ++ components/bob/bob.c | 8 ++ components/bob/include/bob.h | 10 ++ components/insa/CMakeLists.txt | 4 + .../insa/include/common/InactivityTracker.h | 20 +++ components/insa/include/common/Menu.h | 2 + components/insa/include/ui/ScreenSaver.h | 37 ++++++ .../insa/src/common/InactivityTracker.cpp | 35 ++++++ components/insa/src/common/Menu.cpp | 30 ++++- components/insa/src/ui/MainMenu.cpp | 2 +- components/insa/src/ui/ScreenSaver.cpp | 93 ++++++++++++++ components/insa/src/ui/SplashScreen.cpp | 2 +- components/ruth/CMakeLists.txt | 4 +- components/ruth/include/persistence.h | 16 ++- components/ruth/persistence.c | 119 +++++++++++++++++- main/CMakeLists.txt | 3 +- main/app_task.cpp | 18 ++- main/hal/u8g2_esp32_hal.h | 2 +- main/main.cpp | 6 +- 20 files changed, 402 insertions(+), 21 deletions(-) create mode 100644 components/bob/CMakeLists.txt create mode 100644 components/bob/bob.c create mode 100644 components/bob/include/bob.h create mode 100644 components/insa/include/common/InactivityTracker.h create mode 100644 components/insa/include/ui/ScreenSaver.h create mode 100644 components/insa/src/common/InactivityTracker.cpp create mode 100644 components/insa/src/ui/ScreenSaver.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e7f71a..54c119f 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.30) if (DEFINED ENV{IDF_PATH}) include($ENV{IDF_PATH}/tools/cmake/project.cmake) - project(espidf_system_control) + project(system_control) return() else () set(MAJOR_VERSION 0) diff --git a/components/bob/CMakeLists.txt b/components/bob/CMakeLists.txt new file mode 100644 index 0000000..1636870 --- /dev/null +++ b/components/bob/CMakeLists.txt @@ -0,0 +1,10 @@ +if (DEFINED ENV{IDF_PATH}) + idf_component_register(SRCS + bob.c + INCLUDE_DIRS "include" + PRIV_REQUIRES + bt + ruth + ) + return() +endif () \ No newline at end of file diff --git a/components/bob/bob.c b/components/bob/bob.c new file mode 100644 index 0000000..73e3f5b --- /dev/null +++ b/components/bob/bob.c @@ -0,0 +1,8 @@ +#include "bob.h" + +#include "persistence.h" + +void bob_init(void) +{ + persistence_init("system_control"); +} diff --git a/components/bob/include/bob.h b/components/bob/include/bob.h new file mode 100644 index 0000000..0cdd97b --- /dev/null +++ b/components/bob/include/bob.h @@ -0,0 +1,10 @@ +#pragma once + +#ifdef __cplusplus +extern "C" +{ +#endif + void bob_init(void); +#ifdef __cplusplus +} +#endif diff --git a/components/insa/CMakeLists.txt b/components/insa/CMakeLists.txt index 660d3ec..e4c8fb9 100644 --- a/components/insa/CMakeLists.txt +++ b/components/insa/CMakeLists.txt @@ -1,5 +1,6 @@ if (DEFINED ENV{IDF_PATH}) idf_component_register(SRCS + src/common/InactivityTracker.cpp src/common/Menu.cpp src/common/ScrollBar.cpp src/common/Widget.cpp @@ -7,6 +8,7 @@ if (DEFINED ENV{IDF_PATH}) src/ui/LightMenu.cpp src/ui/LightSettingsMenu.cpp src/ui/MainMenu.cpp + src/ui/ScreenSaver.cpp src/ui/SettingsMenu.cpp src/ui/SplashScreen.cpp INCLUDE_DIRS "include" @@ -21,6 +23,7 @@ cmake_minimum_required(VERSION 3.30) project(insa) add_library(${PROJECT_NAME} STATIC + src/common/InactivityTracker.cpp src/common/Menu.cpp src/common/ScrollBar.cpp src/common/Widget.cpp @@ -28,6 +31,7 @@ add_library(${PROJECT_NAME} STATIC src/ui/LightMenu.cpp src/ui/LightSettingsMenu.cpp src/ui/MainMenu.cpp + src/ui/ScreenSaver.cpp src/ui/SettingsMenu.cpp src/ui/SplashScreen.cpp ) diff --git a/components/insa/include/common/InactivityTracker.h b/components/insa/include/common/InactivityTracker.h new file mode 100644 index 0000000..b28ea51 --- /dev/null +++ b/components/insa/include/common/InactivityTracker.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +class InactivityTracker +{ + public: + InactivityTracker(uint64_t timeoutMs, std::function onTimeout); + + void update(uint64_t dt); + void reset(); + void setEnabled(bool enabled); + + private: + uint64_t m_timeoutMs; + uint64_t m_elapsedTime; + bool m_enabled; + std::function m_onTimeout; +}; diff --git a/components/insa/include/common/Menu.h b/components/insa/include/common/Menu.h index a0a8895..ccf251b 100644 --- a/components/insa/include/common/Menu.h +++ b/components/insa/include/common/Menu.h @@ -72,6 +72,8 @@ class Menu : public Widget */ void addText(uint8_t id, const std::string &text); + void addTextCounter(uint8_t id, const std::string &text, const uint8_t value); + /** * @brief Adds a selection menu item (dropdown/list selection) to the menu * @param id Unique identifier for this menu item (must be unique within the menu) diff --git a/components/insa/include/ui/ScreenSaver.h b/components/insa/include/ui/ScreenSaver.h new file mode 100644 index 0000000..ba0ee41 --- /dev/null +++ b/components/insa/include/ui/ScreenSaver.h @@ -0,0 +1,37 @@ +#pragma once + +#include "MenuOptions.h" +#include "common/Widget.h" +#include +#include + +class ScreenSaver final : public Widget +{ + public: + explicit ScreenSaver(menu_options_t *options); + + void update(uint64_t dt) override; + void render() override; + void onButtonClicked(ButtonType button) override; + + private: + struct Star + { + float x; + float y; + float z; + float speed; + }; + + menu_options_t *m_options; + uint64_t m_animationCounter; + std::vector m_stars; + + static constexpr int NUM_STARS = 10; + static constexpr float SPEED_MULTIPLIER = 0.02f; + static constexpr float Z_NEAR = 0.1f; + static constexpr float Z_FAR = 10.0f; + + void initStars(); + void resetStar(Star &star); +}; diff --git a/components/insa/src/common/InactivityTracker.cpp b/components/insa/src/common/InactivityTracker.cpp new file mode 100644 index 0000000..fdc8e5c --- /dev/null +++ b/components/insa/src/common/InactivityTracker.cpp @@ -0,0 +1,35 @@ +#include "common/InactivityTracker.h" + +InactivityTracker::InactivityTracker(uint64_t timeoutMs, std::function onTimeout) + : m_timeoutMs(timeoutMs), m_elapsedTime(0), m_enabled(true), m_onTimeout(onTimeout) +{ +} + +void InactivityTracker::update(uint64_t dt) +{ + if (!m_enabled) + return; + + m_elapsedTime += dt; + + if (m_elapsedTime >= m_timeoutMs && m_onTimeout) + { + m_onTimeout(); + m_enabled = false; + } +} + +void InactivityTracker::reset() +{ + m_elapsedTime = 0; + m_enabled = true; +} + +void InactivityTracker::setEnabled(bool enabled) +{ + m_enabled = enabled; + if (enabled) + { + reset(); + } +} diff --git a/components/insa/src/common/Menu.cpp b/components/insa/src/common/Menu.cpp index c0d6835..2ab63cd 100644 --- a/components/insa/src/common/Menu.cpp +++ b/components/insa/src/common/Menu.cpp @@ -9,7 +9,8 @@ namespace MenuItemTypes constexpr uint8_t TEXT = 0; constexpr uint8_t SELECTION = 1; constexpr uint8_t TOGGLE = 2; -} +constexpr uint8_t TEXT_COUNTER = 3; +} // namespace MenuItemTypes // UI layout constants namespace UIConstants @@ -23,14 +24,12 @@ constexpr int SELECTION_MARGIN = 10; constexpr int CORNER_RADIUS = 3; constexpr int LINE_SPACING = 14; constexpr int BOTTOM_OFFSET = 10; -} +} // namespace UIConstants Menu::Menu(menu_options_t *options) : Widget(options->u8g2), m_options(options) { // Set up button callback using lambda to forward to member function - m_options->onButtonClicked = [this](const ButtonType button) { - onButtonClicked(button); - }; + m_options->onButtonClicked = [this](const ButtonType button) { onButtonClicked(button); }; } Menu::~Menu() @@ -183,6 +182,13 @@ void Menu::renderWidget(const MenuItem *item, const uint8_t *font, const int x, break; } + case MenuItemTypes::TEXT_COUNTER: { + const std::string formattedValue = "(" + item->getValue() + ") >"; + const u8g2_uint_t textWidth = u8g2_GetStrWidth(u8g2, formattedValue.c_str()); + u8g2_DrawStr(u8g2, u8g2->width - textWidth - UIConstants::SELECTION_MARGIN, y, formattedValue.c_str()); + break; + } + case MenuItemTypes::SELECTION: { // Format selection value with angle brackets const std::string formattedValue = "< " + item->getValue() + " >"; @@ -308,11 +314,23 @@ void Menu::onPressedBack() const } void Menu::addText(uint8_t id, const std::string &text) +{ + addTextCounter(id, text, 0); +} + +void Menu::addTextCounter(uint8_t id, const std::string &text, const uint8_t value) { auto callback = [this](const MenuItem &menuItem, const ButtonType button) -> void { onButtonPressed(menuItem, button); }; - m_items.emplace_back(id, MenuItemTypes::TEXT, text, callback); + if (value > 0) + { + m_items.emplace_back(id, MenuItemTypes::TEXT_COUNTER, text, std::to_string(value), callback); + } + else + { + m_items.emplace_back(id, MenuItemTypes::TEXT, text, callback); + } } void Menu::addSelection(uint8_t id, const std::string &text, const std::vector &values, const int index) diff --git a/components/insa/src/ui/MainMenu.cpp b/components/insa/src/ui/MainMenu.cpp index 55ef6dc..9ee32d9 100644 --- a/components/insa/src/ui/MainMenu.cpp +++ b/components/insa/src/ui/MainMenu.cpp @@ -14,7 +14,7 @@ constexpr uint8_t SETTINGS = 2; MainMenu::MainMenu(menu_options_t *options) : Menu(options), m_options(options) { addText(MainMenuItem::LIGHT, "Lichtsteuerung"); - addText(MainMenuItem::EXTERNAL_DEVICES, "Externe Geraete"); + addTextCounter(MainMenuItem::EXTERNAL_DEVICES, "ext. Geraete", 0); addText(MainMenuItem::SETTINGS, "Einstellungen"); } diff --git a/components/insa/src/ui/ScreenSaver.cpp b/components/insa/src/ui/ScreenSaver.cpp new file mode 100644 index 0000000..29e5770 --- /dev/null +++ b/components/insa/src/ui/ScreenSaver.cpp @@ -0,0 +1,93 @@ +#include "ui/ScreenSaver.h" +#include + +ScreenSaver::ScreenSaver(menu_options_t *options) : Widget(options->u8g2), m_options(options), m_animationCounter(0) +{ + initStars(); +} + +void ScreenSaver::initStars() +{ + m_stars.resize(NUM_STARS); + + for (auto &star : m_stars) + { + resetStar(star); + star.z = Z_NEAR + (static_cast(rand()) / RAND_MAX) * (Z_FAR - Z_NEAR); + } +} + +void ScreenSaver::resetStar(Star &star) +{ + star.x = (static_cast(rand()) / RAND_MAX - 0.5f) * 2.0f; + star.y = (static_cast(rand()) / RAND_MAX - 0.5f) * 2.0f; + star.z = Z_FAR; + star.speed = 0.5f + (static_cast(rand()) / RAND_MAX) * 1.5f; +} + +void ScreenSaver::update(const uint64_t dt) +{ + m_animationCounter += dt; + + if (m_animationCounter > 8) + { + m_animationCounter = 0; + + for (auto &star : m_stars) + { + star.z -= star.speed * SPEED_MULTIPLIER; + + if (star.z < Z_NEAR) + { + resetStar(star); + } + } + } +} + +void ScreenSaver::render() +{ + // Verwende Page-Buffer Mode statt Full-Buffer für bessere Performance + // Schwarzer Hintergrund + u8g2_SetDrawColor(u8g2, 0); + u8g2_DrawBox(u8g2, 0, 0, u8g2->width, u8g2->height); + u8g2_SetDrawColor(u8g2, 1); + + const int centerX = u8g2->width / 2; + const int centerY = u8g2->height / 2; + + // Zeichne nur sichtbare Sterne (Clipping) + for (const auto &star : m_stars) + { + // 3D zu 2D Projektion + int screenX = centerX + static_cast((star.x / star.z) * centerX); + int screenY = centerY + static_cast((star.y / star.z) * centerY); + + // Frühe Prüfung für Performance + if (screenX < -5 || screenX >= u8g2->width + 5 || screenY < -5 || screenY >= u8g2->height + 5) + { + continue; + } + + // Vereinfachte Sterndarstellung für bessere Performance + int size = static_cast((1.0f - (star.z - Z_NEAR) / (Z_FAR - Z_NEAR)) * 2.0f); + + if (size <= 0) + { + u8g2_DrawPixel(u8g2, screenX, screenY); + } + else + { + // Verwende u8g2_DrawCircle für größere Sterne (schneller) + u8g2_DrawCircle(u8g2, screenX, screenY, size, U8G2_DRAW_ALL); + } + } +} + +void ScreenSaver::onButtonClicked(ButtonType button) +{ + if (m_options && m_options->popScreen) + { + m_options->popScreen(); + } +} diff --git a/components/insa/src/ui/SplashScreen.cpp b/components/insa/src/ui/SplashScreen.cpp index 7543b11..4984744 100644 --- a/components/insa/src/ui/SplashScreen.cpp +++ b/components/insa/src/ui/SplashScreen.cpp @@ -17,7 +17,7 @@ void SplashScreen::update(const uint64_t dt) { counter += dt; #ifndef ESP32 - if (counter > 20) + if (counter > 3000) #else if (counter > 10) #endif diff --git a/components/ruth/CMakeLists.txt b/components/ruth/CMakeLists.txt index d1b69f3..9211134 100644 --- a/components/ruth/CMakeLists.txt +++ b/components/ruth/CMakeLists.txt @@ -1,7 +1,9 @@ if (DEFINED ENV{IDF_PATH}) idf_component_register(SRCS - persistence.c + persistence.c INCLUDE_DIRS "include" + PRIV_REQUIRES + nvs_flash ) return() endif () diff --git a/components/ruth/include/persistence.h b/components/ruth/include/persistence.h index 988245f..92836ff 100644 --- a/components/ruth/include/persistence.h +++ b/components/ruth/include/persistence.h @@ -1,8 +1,18 @@ #pragma once +typedef enum +{ + VALUE_TYPE_STRING, + VALUE_TYPE_INT32, +} persistence_value_t; + typedef struct { - char *name; - + void *handle; void (*save)(const char *key, const char *value); -} persistence_t; \ No newline at end of file +} persistence_t; + +void *persistence_init(const char *namespace_name); +void persistence_save(persistence_value_t value_type, const char *key, const void *value); +void *persistence_load(persistence_value_t value_type, const char *key, void *out); +void persistence_deinit(); diff --git a/components/ruth/persistence.c b/components/ruth/persistence.c index 95c603c..47e11f0 100644 --- a/components/ruth/persistence.c +++ b/components/ruth/persistence.c @@ -1,3 +1,116 @@ -// -// Created by Siegmund, Peter on 14.06.25. -// +#include "persistence.h" + +#include "esp_err.h" +#include "esp_log.h" +#include "esp_mac.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "nvs_flash.h" + +static const char *TAG = "persistence"; + +static nvs_handle_t persistence_handle; +static SemaphoreHandle_t persistence_mutex; + +void *persistence_init(const char *namespace_name) +{ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) + { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_ERROR_CHECK(nvs_open(namespace_name, NVS_READWRITE, &persistence_handle)); + + persistence_mutex = xSemaphoreCreateMutex(); + if (persistence_mutex == NULL) + { + ESP_LOGE(TAG, "Failed to create mutex"); + } + return &persistence_handle; +} + +void persistence_save(persistence_value_t value_type, const char *key, const void *value) +{ + if (persistence_mutex != NULL) + { + if (xSemaphoreTake(persistence_mutex, portMAX_DELAY) == pdTRUE) + { + esp_err_t err = ESP_ERR_INVALID_ARG; + + switch (value_type) + { + case VALUE_TYPE_STRING: + err = nvs_set_str(persistence_handle, key, (char *)value); + break; + + case VALUE_TYPE_INT32: + err = nvs_set_i32(persistence_handle, key, *(int32_t *)value); + break; + + default: + ESP_LOGE(TAG, "Unsupported value type"); + break; + } + + if (err == ESP_OK) + { + ESP_ERROR_CHECK(nvs_commit(persistence_handle)); + } + else + { + ESP_LOGE(TAG, "Error saving key %s: %s", key, esp_err_to_name(err)); + } + + xSemaphoreGive(persistence_mutex); + } + } +} + +void *persistence_load(persistence_value_t value_type, const char *key, void *out) +{ + if (persistence_mutex != NULL) + { + if (xSemaphoreTake(persistence_mutex, portMAX_DELAY) == pdTRUE) + { + esp_err_t err = ESP_ERR_INVALID_ARG; + + switch (value_type) + { + case VALUE_TYPE_STRING: + err = nvs_get_str(persistence_handle, key, (char *)out, NULL); + break; + + case VALUE_TYPE_INT32: + err = nvs_get_i32(persistence_handle, key, (int32_t *)out); + break; + + default: + ESP_LOGE(TAG, "Unsupported value type"); + break; + } + + if (err != ESP_OK) + { + ESP_LOGE(TAG, "Error loading key %s: %s", key, esp_err_to_name(err)); + } + + xSemaphoreGive(persistence_mutex); + } + } + + return out; +} + +void persistence_deinit() +{ + if (persistence_mutex != NULL) + { + vSemaphoreDelete(persistence_mutex); + persistence_mutex = NULL; + } + + nvs_close(persistence_handle); +} diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 1b1c76c..54c2a30 100755 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -4,7 +4,8 @@ idf_component_register(SRCS "button_handling.c" "hal/u8g2_esp32_hal.c" INCLUDE_DIRS "." - REQUIRES + PRIV_REQUIRES + bob insa ruth u8g2 diff --git a/main/app_task.cpp b/main/app_task.cpp index 2f7f844..08d2f9e 100644 --- a/main/app_task.cpp +++ b/main/app_task.cpp @@ -7,6 +7,8 @@ #include "u8g2.h" #include "button_handling.h" +#include "common/InactivityTracker.h" +#include "ui/ScreenSaver.h" #include "ui/SplashScreen.h" #if defined(CONFIG_IDF_TARGET_ESP32S3) @@ -28,6 +30,7 @@ uint8_t received_signal; std::shared_ptr m_widget; std::vector> m_history; +std::unique_ptr m_inactivityTracker; extern QueueHandle_t buttonQueue; @@ -88,10 +91,16 @@ static void init_ui(void) .persistence = nullptr, }; m_widget = std::make_shared(&options); + m_inactivityTracker = std::make_unique(60000, []() { + auto screensaver = std::make_shared(&options); + options.pushScreen(screensaver); + }); } static void handle_button(uint8_t button) { + m_inactivityTracker->reset(); + if (m_widget) { switch (button) @@ -133,19 +142,24 @@ void app_task(void *args) setup_buttons(); init_ui(); + auto oldTime = esp_timer_get_time(); + while (true) { u8g2_ClearBuffer(&u8g2); - auto oldTime = esp_timer_get_time(); if (m_widget != nullptr) { auto currentTime = esp_timer_get_time(); auto delta = currentTime - oldTime; oldTime = currentTime; - m_widget->update(delta); + uint64_t deltaMs = delta / 1000; + + m_widget->update(deltaMs); m_widget->render(); + + m_inactivityTracker->update(deltaMs); } u8g2_SendBuffer(&u8g2); diff --git a/main/hal/u8g2_esp32_hal.h b/main/hal/u8g2_esp32_hal.h index 5a126e3..9cf20d4 100644 --- a/main/hal/u8g2_esp32_hal.h +++ b/main/hal/u8g2_esp32_hal.h @@ -25,7 +25,7 @@ #define I2C_MASTER_TX_BUF_DISABLE 0 // I2C master do not need buffer #define I2C_MASTER_RX_BUF_DISABLE 0 // I2C master do not need buffer -#define I2C_MASTER_FREQ_HZ 50000 // I2C master clock frequency +#define I2C_MASTER_FREQ_HZ 400000 // I2C master clock frequency #define ACK_CHECK_EN 0x1 // I2C master will check ack from slave #define ACK_CHECK_DIS 0x0 // I2C master will not check ack from slave diff --git a/main/main.cpp b/main/main.cpp index d57ebb7..6dcbb2b 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1,5 +1,7 @@ #include "app_task.h" +#include "bob.h" #include "freertos/FreeRTOS.h" +#include "sdkconfig.h" #ifdef __cplusplus extern "C" @@ -7,7 +9,9 @@ extern "C" #endif void app_main(void) { - xTaskCreatePinnedToCore(app_task, "main_loop", 4096, NULL, 5, NULL, tskIDLE_PRIORITY + 1); + bob_init(); + + xTaskCreatePinnedToCore(app_task, "main_loop", 4096, NULL, tskIDLE_PRIORITY + 1, NULL, portNUM_PROCESSORS - 1); } #ifdef __cplusplus }