implement new light mode (off/day/night/simulation)

missing:
- fully connect it to the ui
- setup duration in light settings

Signed-off-by: Peter Siegmund <developer@mars3142.org>
This commit is contained in:
2025-09-29 23:15:06 +02:00
parent dc66484f5e
commit 08b0e04584
50 changed files with 14880 additions and 8787 deletions

View File

@@ -1,10 +1,7 @@
#pragma once
#ifdef __cplusplus
extern "C"
{
#endif
void analytics_init(void);
#ifdef __cplusplus
}
#endif
#include <sys/cdefs.h>
__BEGIN_DECLS
void analytics_init(void);
__END_DECLS

View File

@@ -1,10 +1,7 @@
#pragma once
#ifdef __cplusplus
extern "C"
{
#endif
void wifi_manager_init(void);
#ifdef __cplusplus
}
#endif
#include <sys/cdefs.h>
__BEGIN_DECLS
void wifi_manager_init(void);
__END_DECLS

View File

@@ -30,8 +30,10 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
#if CONFIG_WIFI_ENABLED
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
led_behavior_t led0_behavior = {
.mode = LED_MODE_BLINK, .color = {.r = 50, .g = 50, .b = 0}, .on_time_ms = 200, .off_time_ms = 200};
led_behavior_t led0_behavior = {.mode = LED_MODE_BLINK,
.color = {.red = 50, .green = 50, .blue = 0},
.on_time_ms = 200,
.off_time_ms = 200};
led_status_set_behavior(0, led0_behavior);
esp_wifi_connect();
@@ -40,8 +42,10 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
{
if (s_retry_num < CONFIG_WIFI_CONNECT_RETRIES)
{
led_behavior_t led0_behavior = {
.mode = LED_MODE_BLINK, .color = {.r = 50, .g = 50, .b = 0}, .on_time_ms = 200, .off_time_ms = 200};
led_behavior_t led0_behavior = {.mode = LED_MODE_BLINK,
.color = {.red = 50, .green = 50, .blue = 0},
.on_time_ms = 200,
.off_time_ms = 200};
led_status_set_behavior(0, led0_behavior);
esp_wifi_connect();
@@ -49,8 +53,10 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
ESP_DIAG_EVENT(TAG, "Retrying to connect to the AP");
return;
}
led_behavior_t led0_behavior = {
.mode = LED_MODE_BLINK, .color = {.r = 50, .g = 0, .b = 0}, .on_time_ms = 1000, .off_time_ms = 500};
led_behavior_t led0_behavior = {.mode = LED_MODE_BLINK,
.color = {.red = 50, .green = 0, .blue = 0},
.on_time_ms = 1000,
.off_time_ms = 500};
led_status_set_behavior(0, led0_behavior);
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
@@ -59,7 +65,7 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
{
led_behavior_t led0_behavior = {
.mode = LED_MODE_SOLID,
.color = {.r = 0, .g = 50, .b = 0},
.color = {.red = 0, .green = 50, .blue = 0},
};
led_status_set_behavior(0, led0_behavior);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,11 @@
idf_component_register(SRCS
src/common/ColorSettingsMenu.cpp
src/common/InactivityTracker.cpp
src/common/Menu.cpp
src/common/ScrollBar.cpp
src/common/Widget.cpp
src/data/MenuItem.cpp
src/ui/DayColorSettingsMenu.cpp
src/ui/LightMenu.cpp
src/ui/LightSettingsMenu.cpp
src/ui/MainMenu.cpp
src/ui/NightColorSettingsMenu.cpp
src/ui/ClockScreenSaver.cpp
src/ui/ScreenSaver.cpp
src/ui/SettingsMenu.cpp
@@ -19,4 +15,5 @@ idf_component_register(SRCS
u8g2
led-manager
persistence-manager
simulator
)

View File

@@ -1,29 +0,0 @@
#pragma once
#include "common/Menu.h"
namespace ColorSettingsMenuOptions
{
constexpr auto RED = "red_";
constexpr auto GREEN = "green_";
constexpr auto BLUE = "blue_";
} // namespace ColorSettingsMenuOptions
class ColorSettingsMenu : public Menu
{
public:
/**
* @brief Constructs a ColorSettingsMenu with the specified options
* @param options Pointer to menu configuration options structure
* @details Initializes the menu with color settings options
*/
explicit ColorSettingsMenu(menu_options_t *options, std::string prefix);
void onButtonPressed(const MenuItem &menuItem, const ButtonType button) override;
void onExit() override;
private:
std::string m_suffix;
menu_options_t *m_options;
};

View File

@@ -194,9 +194,9 @@ class Menu : public Widget
private:
MenuItem replaceItem(int index, const MenuItem &item);
void render() override;
void Render() override;
void onButtonClicked(ButtonType button) override;
void OnButtonClicked(ButtonType button) override;
void onPressedDown();

View File

@@ -22,20 +22,20 @@
* that indicates the current position within a scrollable range. The thumb
* size is proportional to the visible area relative to the total content,
* and its position reflects the current scroll offset.
*
*
* The scrollbar automatically calculates thumb dimensions and position based on
* the provided scroll values (current, minimum, maximum). It is designed to be
* used alongside scrollable content like menus or lists to provide visual
* feedback about scroll state.
*
*
* @note This class is marked as final and cannot be inherited from.
*
*
* @see Widget
* @see menu_options_t
*/
class ScrollBar final : public Widget
{
public:
public:
/**
* @brief Constructs a ScrollBar with specified position and dimensions
* @param options Pointer to menu options configuration structure
@@ -43,12 +43,12 @@ public:
* @param y Y coordinate position of the scrollbar on screen
* @param width Width of the scrollbar in pixels
* @param height Height of the scrollbar in pixels
*
*
* @pre options must not be nullptr and must remain valid for the scrollbar's lifetime
* @pre width and height must be greater than 0
* @pre x and y must be valid screen coordinates
* @post ScrollBar is initialized with the specified geometry and ready for use
*
*
* @note The scrollbar does not take ownership of the options structure and
* assumes it remains valid throughout the scrollbar's lifetime.
*/
@@ -59,48 +59,48 @@ public:
* @details Overrides the base Widget render method to draw the scrollbar track
* and thumb. The appearance is determined by the current scroll state
* and the menu options configuration.
*
*
* @pre u8g2 display context must be initialized and ready for drawing
* @post Scrollbar's visual representation is drawn to the display buffer
*
*
* @note This method is called during each frame's render cycle. The scrollbar
* track and thumb are drawn based on the current scroll values set by refresh().
*/
void render() override;
void Render() override;
/**
* @brief Updates the scrollbar state with new scroll values
* @param value Current scroll position value (must be between min and max)
* @param max Maximum scroll value (total content size)
* @param min Minimum scroll value (default: 0, typically the start of content)
*
*
* @pre value must be between min and max (inclusive)
* @pre max must be greater than or equal to min
* @post Scrollbar thumb position and size are recalculated based on new values
*
*
* @details This method recalculates the thumb's height and vertical position
* based on the provided scroll range and current position. The thumb
* height represents the proportion of visible content to total content,
* while its position represents the current scroll offset within the range.
*
*
* @note Call this method whenever the scroll state changes to keep the
* scrollbar visualization synchronized with the actual content position.
*/
void refresh(size_t value, size_t max, size_t min = 0);
private:
private:
// Position and dimensions
size_t m_x; ///< X coordinate of the scrollbar's left edge
size_t m_y; ///< Y coordinate of the scrollbar's top edge
size_t m_width; ///< Width of the scrollbar track in pixels
size_t m_height; ///< Height of the scrollbar track in pixels
size_t m_x; ///< X coordinate of the scrollbar's left edge
size_t m_y; ///< Y coordinate of the scrollbar's top edge
size_t m_width; ///< Width of the scrollbar track in pixels
size_t m_height; ///< Height of the scrollbar track in pixels
// Scroll state values
size_t m_value; ///< Current scroll position within the range [m_min, m_max]
size_t m_max; ///< Maximum scroll value representing the end of content
size_t m_min; ///< Minimum scroll value representing the start of content
size_t m_value; ///< Current scroll position within the range [m_min, m_max]
size_t m_max; ///< Maximum scroll value representing the end of content
size_t m_min; ///< Minimum scroll value representing the start of content
// Calculated thumb properties (updated by refresh())
size_t m_thumbHeight; ///< Calculated height of the scroll thumb in pixels
size_t m_thumbY; ///< Calculated Y position of the scroll thumb relative to track
size_t m_thumbHeight; ///< Calculated height of the scroll thumb in pixels
size_t m_thumbY; ///< Calculated Y position of the scroll thumb relative to track
};

View File

@@ -117,7 +117,7 @@ class Widget
* @note Override this method in derived classes to implement time-based behavior
* such as animations, blinking effects, or timeout handling.
*/
virtual void update(uint64_t dt);
virtual void Update(uint64_t dt);
/**
* @brief Renders the widget to the display
@@ -133,7 +133,7 @@ class Widget
* Derived classes should use the u8g2 member variable to perform
* drawing operations.
*/
virtual void render();
virtual void Render();
/**
* @brief Handles button press events
@@ -148,7 +148,7 @@ class Widget
*
* @see ButtonType for available button types
*/
virtual void onButtonClicked(ButtonType button);
virtual void OnButtonClicked(ButtonType button);
protected:
/**

View File

@@ -21,9 +21,9 @@ 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;
void Update(uint64_t dt) override;
void Render() override;
void OnButtonClicked(ButtonType button) override;
private:
static constexpr int MOVE_INTERVAL = 50; // milliseconds between movements

View File

@@ -1,14 +0,0 @@
#pragma once
#include "common/ColorSettingsMenu.h"
class DayColorSettingsMenu final : public ColorSettingsMenu
{
public:
/**
* @brief Constructs a DayColorSettingsMenu with the specified options
* @param options Pointer to menu configuration options structure
* @details Initializes the menu with day color settings options
*/
explicit DayColorSettingsMenu(menu_options_t *options);
};

View File

@@ -1,35 +0,0 @@
#pragma once
#include "common/Menu.h"
/**
* @class LightSettingsMenu
* @brief Menu for configuring light system settings including sections and LED parameters
* @details This menu extends the base Menu class to provide specialized functionality
* for managing light system configurations. It handles dynamic section management
* and provides persistence for user settings.
*/
class LightSettingsMenu final : public Menu
{
public:
/**
* @brief Constructs a LightSettingsMenu with the specified options
* @param options Pointer to menu configuration options structure
* @details Initializes the menu with section counter and default section settings
*/
explicit LightSettingsMenu(menu_options_t *options);
private:
/**
* @brief Handles button press events for light settings menu items
* @param menuItem The menu item that received the button press
* @param button The type of button that was pressed
* @details Manages value switching, dynamic section list updates, and
* persistence of section values when settings are modified
*/
void onButtonPressed(const MenuItem& menuItem, ButtonType button) override;
static std::string CreateKey(int index);
menu_options_t *m_options; ///< Pointer to menu configuration options
};

View File

@@ -1,15 +0,0 @@
#pragma once
#include "common/ColorSettingsMenu.h"
class NightColorSettingsMenu final : public ColorSettingsMenu
{
public:
/**
* @brief Constructs a NightColorSettingsMenu with the specified options
* @param options Pointer to menu configuration options structure
* @details Initializes the menu with night color settings options
*/
explicit NightColorSettingsMenu(menu_options_t *options);
};

View File

@@ -23,9 +23,9 @@ 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;
void Update(uint64_t dt) override;
void Render() override;
void OnButtonClicked(ButtonType button) override;
private:
/**

View File

@@ -21,71 +21,71 @@
* displayed when the application starts. It serves multiple purposes including
* brand presentation, system initialization feedback, and smooth transition
* preparation to the main application interface.
*
*
* The SplashScreen class provides:
* - Brand identity display (logos, company information, product name)
* - System initialization progress indication
* - Smooth animations and visual effects for professional appearance
* - Automatic transition timing to main menu after initialization
* - Error indication if initialization fails
*
*
* Key features include:
* - Time-based automatic progression to main menu
* - Animated elements (fade-in effects, progress indicators)
* - Version information display
* - Loading status messages
* - Graceful handling of initialization delays
*
*
* The splash screen typically displays for a minimum duration to ensure users
* can read branding information, even if system initialization completes quickly.
* It automatically transitions to the main menu once both the minimum display
* time and system initialization are complete.
*
*
* @note This class is marked as final and cannot be inherited from.
* @note The splash screen does not handle user input - it operates on timing
* and system state rather than user interaction.
*
*
* @see Widget for base widget functionality
* @see menu_options_t for configuration structure
* @see MainMenu for the target transition screen
*/
class SplashScreen final : public Widget
{
public:
public:
/**
* @brief Constructs the splash screen with specified configuration
* @param options Pointer to menu options configuration structure
*
*
* @pre options must not be nullptr and must remain valid for the splash screen's lifetime
* @pre options->u8g2 must be initialized and ready for graphics operations
* @pre Screen transition callbacks in options must be properly configured
* @post SplashScreen is initialized and ready to display startup sequence
*
*
* @details The constructor initializes the splash screen by:
* - Setting up the initial display state and animations
* - Preparing branding elements (logos, text, version info)
* - Initializing timing controls for minimum display duration
* - Configuring transition parameters for smooth progression
* - Loading any required graphics resources or assets
*
*
* The splash screen prepares all visual elements during construction to
* ensure smooth rendering performance during the critical startup phase.
* It also establishes the timing framework for controlling display duration
* and transition timing.
*
*
* @note The splash screen does not take ownership of the options structure
* and assumes it remains valid throughout the screen's lifetime.
* @note Graphics resources are loaded during construction, so any required
* assets must be available at initialization time.
*
*
* @see Widget::Widget for base class construction details
*/
explicit SplashScreen(menu_options_t *options);
/**
* @brief Updates the splash screen state, animations, and timing logic
* @param dt Delta time in milliseconds since the last update call
*
*
* @details Overrides the base Widget update method to handle splash screen-specific
* logic including:
* - Animation progression (fade effects, transitions, progress indicators)
@@ -93,31 +93,31 @@ public:
* - System initialization status monitoring
* - Automatic transition preparation to main menu
* - Loading progress updates and status message changes
*
*
* The update method manages the splash screen's lifecycle by tracking
* elapsed time and system readiness state. It ensures the splash screen
* remains visible for a minimum duration while also monitoring system
* initialization completion. Once both conditions are met, it initiates
* the transition to the main application interface.
*
*
* Animation updates include:
* - Fade-in effects for branding elements
* - Progress bar or spinner animations
* - Text transitions for status messages
* - Smooth preparation for screen transition
*
*
* @note This method is called every frame during the splash screen display
* and must be efficient to maintain smooth visual presentation.
* @note The method automatically handles transition to the main menu when
* appropriate, using the callback functions provided in m_options.
*
*
* @see Widget::update for the base update interface
*/
void update(uint64_t dt) override;
void Update(uint64_t dt) override;
/**
* @brief Renders the splash screen visual elements to the display
*
*
* @details Overrides the base Widget render method to draw all splash screen
* elements including branding, status information, and visual effects.
* The rendering includes:
@@ -126,50 +126,50 @@ public:
* - Loading progress indicators (progress bars, spinners, etc.)
* - Status messages indicating initialization progress
* - Background graphics and visual effects
*
*
* The render method creates a professional, polished appearance that
* reinforces brand identity while providing useful feedback about the
* application startup process. All elements are positioned and styled
* to create a cohesive, attractive presentation.
*
*
* Rendering features include:
* - Centered layout with balanced visual hierarchy
* - Smooth animations and transitions
* - Consistent typography and color scheme
* - Progress feedback elements
* - Error indication if initialization problems occur
*
*
* @pre u8g2 display context must be initialized and ready for drawing
* @post All splash screen visual elements are drawn to the display buffer
*
*
* @note This method is called every frame and must be efficient to maintain
* smooth visual presentation during the startup sequence.
* @note The visual design should be consistent with the overall application
* theme and branding guidelines.
*
*
* @see Widget::render for the base render interface
*/
void render() override;
void Render() override;
private:
private:
/**
* @brief Pointer to menu options configuration structure
* @details Stores a reference to the menu configuration passed during construction.
* This provides access to the display context for rendering operations
* and screen transition callbacks for automatic progression to the main menu.
*
*
* The configuration enables:
* - Display context (u8g2) for graphics rendering operations
* - Screen transition callbacks for automatic progression to main menu
* - System integration for initialization status monitoring
*
*
* The splash screen uses the setScreen callback to automatically transition
* to the main menu once the startup sequence is complete. This ensures a
* seamless user experience from application launch to main interface.
*
*
* @note This pointer is not owned by the SplashScreen and must remain valid
* throughout the screen's lifetime. It is managed by the application framework.
*
*
* @warning Must not be modified after construction as it contains critical
* system callbacks required for proper application flow.
*/

View File

@@ -1,101 +0,0 @@
#include "common/ColorSettingsMenu.h"
#include "led_manager.h"
namespace ColorSettingsMenuItem
{
constexpr auto RED = 0;
constexpr auto GREEN = 1;
constexpr auto BLUE = 2;
} // namespace ColorSettingsMenuItem
ColorSettingsMenu::ColorSettingsMenu(menu_options_t *options, std::string suffix)
: Menu(options), m_suffix(std::move(suffix)), m_options(options)
{
std::vector<std::string> values;
for (size_t i = 0; i <= 254; i++)
{
values.emplace_back(std::to_string(i));
}
int red_value = 0;
if (m_options && m_options->persistenceManager)
{
std::string key = ColorSettingsMenuOptions::RED + m_suffix;
red_value = m_options->persistenceManager->GetValue(key, red_value);
}
addSelection(ColorSettingsMenuItem::RED, "Rot", values, red_value);
int green_value = 0;
if (m_options && m_options->persistenceManager)
{
std::string key = ColorSettingsMenuOptions::GREEN + m_suffix;
green_value = m_options->persistenceManager->GetValue(key, green_value);
}
addSelection(ColorSettingsMenuItem::GREEN, "Gruen", values, green_value);
int blue_value = 0;
if (m_options && m_options->persistenceManager)
{
std::string key = ColorSettingsMenuOptions::BLUE + m_suffix;
blue_value = m_options->persistenceManager->GetValue(key, blue_value);
}
addSelection(ColorSettingsMenuItem::BLUE, "Blau", values, blue_value);
}
void ColorSettingsMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType button)
{
switch (menuItem.getId())
{
case ColorSettingsMenuItem::RED:
if (button == ButtonType::LEFT || button == ButtonType::RIGHT)
{
const auto item = switchValue(menuItem, button);
const auto value = getItem(item.getId()).getIndex();
if (m_options && m_options->persistenceManager)
{
std::string key = ColorSettingsMenuOptions::RED + m_suffix;
m_options->persistenceManager->SetValue(key, value);
}
}
break;
case ColorSettingsMenuItem::GREEN:
if (button == ButtonType::LEFT || button == ButtonType::RIGHT)
{
const auto item = switchValue(menuItem, button);
const auto value = getItem(item.getId()).getIndex();
if (m_options && m_options->persistenceManager)
{
std::string key = ColorSettingsMenuOptions::GREEN + m_suffix;
m_options->persistenceManager->SetValue(key, value);
}
}
break;
case ColorSettingsMenuItem::BLUE:
if (button == ButtonType::LEFT || button == ButtonType::RIGHT)
{
const auto item = switchValue(menuItem, button);
const auto value = getItem(item.getId()).getIndex();
if (m_options && m_options->persistenceManager)
{
std::string key = ColorSettingsMenuOptions::BLUE + m_suffix;
m_options->persistenceManager->SetValue(key, value);
}
}
break;
}
}
void ColorSettingsMenu::onExit()
{
if (m_options && m_options->persistenceManager)
{
m_options->persistenceManager->Save();
led_event_data_t payload = {.value = 42};
send_event(EVENT_LED_REFRESH, &payload);
}
}

View File

@@ -29,7 +29,7 @@ constexpr int BOTTOM_OFFSET = 10;
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()
@@ -126,7 +126,7 @@ MenuItem Menu::replaceItem(const int index, const MenuItem &item)
return item;
}
void Menu::render()
void Menu::Render()
{
// Initialize selection if not set
if (m_selected_item >= m_items.size() && !m_items.empty())
@@ -232,7 +232,7 @@ void Menu::renderWidget(const MenuItem *item, const uint8_t *font, const int x,
}
}
void Menu::onButtonClicked(const ButtonType button)
void Menu::OnButtonClicked(const ButtonType button)
{
// Map button input to navigation functions
switch (button)
@@ -362,7 +362,7 @@ void Menu::drawScrollBar() const
// Create scrollbar instance
ScrollBar scrollBar(m_options, u8g2->width - UIConstants::SCROLLBAR_WIDTH, 3, 1, u8g2->height - 6);
scrollBar.refresh(m_selected_item, m_items.size());
scrollBar.render();
scrollBar.Render();
}
void Menu::drawSelectionBox() const

View File

@@ -7,7 +7,7 @@ ScrollBar::ScrollBar(const menu_options_t *options, const size_t x, const size_t
{
}
void ScrollBar::render()
void ScrollBar::Render()
{
if (m_max <= 1)
{

View File

@@ -20,14 +20,14 @@ void Widget::onExit()
{
}
void Widget::update(uint64_t dt)
void Widget::Update(uint64_t dt)
{
}
void Widget::render()
void Widget::Render()
{
}
void Widget::onButtonClicked(ButtonType button)
void Widget::OnButtonClicked(ButtonType button)
{
}

View File

@@ -1,4 +1,5 @@
#include "ui/ClockScreenSaver.h"
#include "simulator.h"
#include <cstring>
#include <ctime>
@@ -36,17 +37,25 @@ void ClockScreenSaver::updateTextDimensions()
void ClockScreenSaver::getCurrentTimeString(char *buffer, size_t bufferSize) const
{
time_t rawtime;
struct tm *timeinfo;
char *simulated_time = get_time();
if (simulated_time != nullptr)
{
strncpy(buffer, simulated_time, bufferSize);
}
else
{
time_t rawtime;
struct tm *timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
time(&rawtime);
timeinfo = localtime(&rawtime);
// Format time as HH:MM:SS
strftime(buffer, bufferSize, "%H:%M:%S", timeinfo);
// Format time as HH:MM:SS
strftime(buffer, bufferSize, "%H:%M:%S", timeinfo);
}
}
void ClockScreenSaver::update(const uint64_t dt)
void ClockScreenSaver::Update(const uint64_t dt)
{
m_moveTimer += dt;
@@ -94,7 +103,7 @@ void ClockScreenSaver::checkBoundaryCollision()
}
}
void ClockScreenSaver::render()
void ClockScreenSaver::Render()
{
// Clear screen with a black background
u8g2_SetDrawColor(u8g2, 0);
@@ -112,7 +121,7 @@ void ClockScreenSaver::render()
u8g2_DrawStr(u8g2, m_posX, m_posY, timeBuffer);
}
void ClockScreenSaver::onButtonClicked(ButtonType button)
void ClockScreenSaver::OnButtonClicked(ButtonType button)
{
// Exit screensaver on any button press
if (m_options && m_options->popScreen)

View File

@@ -1,5 +0,0 @@
#include "ui/DayColorSettingsMenu.h"
DayColorSettingsMenu::DayColorSettingsMenu(menu_options_t *options) : ColorSettingsMenu(options, "day")
{
}

View File

@@ -1,7 +1,6 @@
#include "ui/LightMenu.h"
#include "led_manager.h"
#include "ui/LightSettingsMenu.h"
#include "led_strip_ws2812.h"
/**
* @namespace LightMenuItem
@@ -9,9 +8,8 @@
*/
namespace LightMenuItem
{
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
constexpr auto ACTIVATE = 0; ///< ID for the light activation toggle
constexpr auto MODE = 1; ///< ID for the light mode selection
} // namespace LightMenuItem
namespace LightMenuOptions
@@ -30,19 +28,17 @@ LightMenu::LightMenu(menu_options_t *options) : Menu(options), m_options(options
}
addToggle(LightMenuItem::ACTIVATE, "Einschalten", active);
// Create mode selection options (Day/Night modes)
std::vector<std::string> values;
values.emplace_back("Tag"); // Day mode
values.emplace_back("Nacht"); // Night mode
// Create mode selection options (Simulation/Day/Night modes)
std::vector<std::string> items;
items.emplace_back("Simulation"); // Simulation mode
items.emplace_back("Tag"); // Day mode
items.emplace_back("Nacht"); // Night mode
int mode_value = 0;
if (m_options && m_options->persistenceManager)
{
mode_value = m_options->persistenceManager->GetValue(LightMenuOptions::LIGHT_MODE, mode_value);
}
addSelection(LightMenuItem::MODE, "Modus", values, mode_value);
// Add menu item for accessing LED settings submenu
addText(LightMenuItem::LED_SETTINGS, "Einstellungen");
addSelection(LightMenuItem::MODE, "Modus", items, mode_value);
}
void LightMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType button)
@@ -58,14 +54,13 @@ void LightMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType butto
{
toggle(menuItem);
const auto value = getItem(menuItem.getId()).getValue() == "1";
led_event_data_t payload = {.value = 42};
if (value)
{
send_event(EVENT_LED_ON, &payload);
led_strip_update(LED_STATE_DAY, rgb_t{});
}
else
{
send_event(EVENT_LED_OFF, &payload);
led_strip_update(LED_STATE_OFF, rgb_t{});
}
if (m_options && m_options->persistenceManager)
@@ -88,17 +83,7 @@ void LightMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType butto
m_options->persistenceManager->Save();
}
led_event_data_t payload = {.value = value};
send_event(EVENT_LED_DAY + value, &payload);
}
break;
}
case LightMenuItem::LED_SETTINGS: {
// Open the LED settings submenu when SELECT is pressed
if (button == ButtonType::SELECT)
{
widget = std::make_shared<LightSettingsMenu>(m_options);
led_strip_update(value == 0 ? LED_STATE_DAY : LED_STATE_NIGHT, rgb_t{});
}
break;
}
@@ -113,4 +98,4 @@ void LightMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType butto
{
m_options->pushScreen(widget);
}
}
}

View File

@@ -1,102 +0,0 @@
#include "ui/LightSettingsMenu.h"
#include "common/Common.h"
#include "ui/DayColorSettingsMenu.h"
#include "ui/NightColorSettingsMenu.h"
/**
* @namespace LightSettingsMenuItem
* @brief Constants for light settings menu item identifiers
*/
namespace LightSettingsMenuItem
{
constexpr uint8_t RGB_SETTING_DAY = 0;
constexpr uint8_t RGB_SETTING_NIGHT = 1;
constexpr uint8_t SECTION_COUNTER = 2;
} // namespace LightSettingsMenuItem
std::string LightSettingsMenu::CreateKey(const int index)
{
constexpr int key_length = 20;
char key[key_length] = "";
snprintf(key, key_length, "section_%d", index);
return key;
}
LightSettingsMenu::LightSettingsMenu(menu_options_t *options) : Menu(options), m_options(options)
{
addText(LightSettingsMenuItem::RGB_SETTING_DAY, "Tag (Farbe)");
addText(LightSettingsMenuItem::RGB_SETTING_NIGHT, "Nacht (Farbe)");
/*
// Create values vector for section counts (1-99)
std::vector<std::string> values;
for (size_t i = 1; i <= 99; i++)
{
values.emplace_back(std::to_string(i));
}
// Add section counter selection (allows choosing number of sections)
auto value = 7;
if (m_options && m_options->persistenceManager)
{
value = m_options->persistenceManager->GetValue(CreateKey(0), value);
}
addSelection(LightSettingsMenuItem::SECTION_COUNTER, "Sektionen", values, value);
setItemSize(std::stoull(getItem(LightSettingsMenuItem::SECTION_COUNTER).getValue()),
LightSettingsMenuItem::SECTION_COUNTER);
*/
}
void LightSettingsMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType button)
{
std::shared_ptr<Widget> widget;
switch (button)
{
case ButtonType::SELECT:
switch (menuItem.getId())
{
case LightSettingsMenuItem::RGB_SETTING_DAY:
widget = std::make_shared<DayColorSettingsMenu>(m_options);
break;
case LightSettingsMenuItem::RGB_SETTING_NIGHT:
widget = std::make_shared<NightColorSettingsMenu>(m_options);
break;
default:
break;
}
if (m_options && m_options->pushScreen)
{
m_options->pushScreen(widget);
}
break;
case ButtonType::RIGHT:
case ButtonType::LEFT:
// Handle value switching for the current menu item
switchValue(menuItem, button);
// Update the section list size based on the section counter value
if (menuItem.getId() == LightSettingsMenuItem::SECTION_COUNTER)
{
setItemSize(std::stoull(getItem(LightSettingsMenuItem::SECTION_COUNTER).getValue()),
LightSettingsMenuItem::SECTION_COUNTER);
}
// Persist the changed section values if persistence is available
if (m_options && m_options->persistenceManager)
{
const auto value = getItem(menuItem.getId()).getIndex();
m_options->persistenceManager->SetValue(CreateKey(menuItem.getId()), value);
}
break;
default:
break;
}
}

View File

@@ -1,5 +0,0 @@
#include "ui/NightColorSettingsMenu.h"
NightColorSettingsMenu::NightColorSettingsMenu(menu_options_t *options) : ColorSettingsMenu(options, "night")
{
}

View File

@@ -20,7 +20,7 @@ void ScreenSaver::initVehicles()
}
}
void ScreenSaver::update(const uint64_t dt)
void ScreenSaver::Update(const uint64_t dt)
{
m_animationCounter += dt;
m_lastSpawnTime += dt;
@@ -214,7 +214,7 @@ ScreenSaver::Direction ScreenSaver::getRandomDirection()
return (random() % 2 == 0) ? Direction::LEFT : Direction::RIGHT;
}
void ScreenSaver::render()
void ScreenSaver::Render()
{
// Clear screen with a black background
u8g2_SetDrawColor(u8g2, 0);
@@ -320,7 +320,7 @@ const unsigned char *ScreenSaver::getVehicleBitmap(const VehicleType type, const
}
}
void ScreenSaver::onButtonClicked(ButtonType button)
void ScreenSaver::OnButtonClicked(ButtonType button)
{
if (m_options && m_options->popScreen)
{

View File

@@ -8,7 +8,7 @@ SplashScreen::SplashScreen(menu_options_t *options) : Widget(options->u8g2), m_o
{
}
void SplashScreen::update(const uint64_t dt)
void SplashScreen::Update(const uint64_t dt)
{
splashTime += dt;
if (splashTime > 100)
@@ -20,7 +20,7 @@ void SplashScreen::update(const uint64_t dt)
}
}
void SplashScreen::render()
void SplashScreen::Render()
{
u8g2_SetFont(u8g2, u8g2_font_DigitalDisco_tr);
u8g2_DrawStr(u8g2, 28, u8g2->height / 2 - 10, "HO Anlage");

View File

@@ -1,6 +1,6 @@
idf_component_register(SRCS
src/led_manager.cpp
src/led_status.c
src/led_strip_ws2812.c
INCLUDE_DIRS "include"
PRIV_REQUIRES
insa
@@ -8,4 +8,5 @@ idf_component_register(SRCS
esp_event
esp_timer
persistence-manager
simulator
)

View File

@@ -10,4 +10,10 @@ menu "Led Manager Configuration"
default 2
help
The number of the status WLED pin.
config LED_STRIP_MAX_LEDS
int "Maximum number of LEDs"
default 500
help
The maximum number of LEDs that can be controlled.
endmenu

View File

@@ -0,0 +1,12 @@
#pragma once
#include <stdint.h>
typedef struct
{
uint8_t red;
uint8_t green;
uint8_t blue;
} rgb_t;
void interpolate_color(const rgb_t start_color, const rgb_t end_color, float fraction, rgb_t *out_color);

View File

@@ -1,23 +0,0 @@
#pragma once
#include <cstdint>
enum
{
EVENT_LED_ON,
EVENT_LED_OFF,
EVENT_LED_DAY,
EVENT_LED_NIGHT,
EVENT_LED_REFRESH
};
typedef struct
{
int value;
} led_event_data_t;
uint64_t wled_init();
uint64_t register_handler();
uint64_t send_event(uint32_t event, led_event_data_t *payload);

View File

@@ -1,5 +1,6 @@
#pragma once
#include "color.h"
#include "esp_check.h"
#include <stdint.h>
@@ -14,14 +15,6 @@ typedef enum
LED_MODE_BLINK
} led_mode_t;
// Structure for an RGB color
typedef struct
{
uint8_t r;
uint8_t g;
uint8_t b;
} rgb_t;
// This is the structure you pass from the outside to define a behavior
typedef struct
{
@@ -31,28 +24,23 @@ typedef struct
uint32_t off_time_ms; // Only relevant for BLINK
} led_behavior_t;
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief Initializes the status manager and the LED strip.
*
* @param gpio_num GPIO where the data line of the LEDs is connected.
* @return esp_err_t ESP_OK on success.
*/
esp_err_t led_status_init(int gpio_num);
__BEGIN_DECLS
/**
* @brief Initializes the status manager and the LED strip.
*
* @param gpio_num GPIO where the data line of the LEDs is connected.
* @return esp_err_t ESP_OK on success.
*/
esp_err_t led_status_init(int gpio_num);
/**
* @brief Sets the lighting behavior for a single LED.
*
* This function is thread-safe.
*
* @param index Index of the LED (0 to STATUS_LED_COUNT - 1).
* @param behavior The structure with the desired behavior.
* @return esp_err_t ESP_OK on success, ESP_ERR_INVALID_ARG on invalid index.
*/
esp_err_t led_status_set_behavior(uint8_t index, led_behavior_t behavior);
#ifdef __cplusplus
}
#endif
/**
* @brief Sets the lighting behavior for a single LED.
*
* This function is thread-safe.
*
* @param index Index of the LED (0 to STATUS_LED_COUNT - 1).
* @param behavior The structure with the desired behavior.
* @return esp_err_t ESP_OK on success, ESP_ERR_INVALID_ARG on invalid index.
*/
esp_err_t led_status_set_behavior(uint8_t index, led_behavior_t behavior);
__END_DECLS

View File

@@ -0,0 +1,18 @@
#pragma once
#include "color.h"
#include <esp_check.h>
#include <sys/cdefs.h>
typedef enum
{
LED_STATE_OFF,
LED_STATE_DAY,
LED_STATE_NIGHT,
LED_STATE_SIMULATION,
} led_state_t;
__BEGIN_DECLS
esp_err_t led_strip_init(void);
esp_err_t led_strip_update(led_state_t state, rgb_t color);
__END_DECLS

View File

@@ -0,0 +1,8 @@
#include "color.h"
void interpolate_color(const rgb_t start_color, const rgb_t end_color, float fraction, rgb_t *out_color)
{
out_color->r = start_color.r + (end_color.r - start_color.r) * fraction;
out_color->g = start_color.g + (end_color.g - start_color.g) * fraction;
out_color->b = start_color.b + (end_color.b - start_color.b) * fraction;
}

View File

@@ -1,112 +0,0 @@
#include "led_manager.h"
#include "common/ColorSettingsMenu.h"
#include "esp_event.h"
#include "esp_log.h"
#include "hal_esp32/PersistenceManager.h"
#include "led_status.h"
#include "led_strip.h"
#include "sdkconfig.h"
led_strip_handle_t led_strip;
static const int value = 125;
static const uint32_t max_leds = 500;
ESP_EVENT_DECLARE_BASE(LED_EVENTS_BASE);
ESP_EVENT_DEFINE_BASE(LED_EVENTS_BASE);
esp_event_loop_handle_t loop_handle;
const char *TAG = "led_manager";
uint64_t wled_init(void)
{
led_strip_config_t strip_config = {
.strip_gpio_num = CONFIG_WLED_DIN_PIN,
.max_leds = max_leds,
.led_model = LED_MODEL_WS2812,
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB,
.flags =
{
.invert_out = false,
},
};
led_strip_rmt_config_t rmt_config = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = 0,
.mem_block_symbols = 0,
.flags =
{
.with_dma = true,
},
};
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
return ESP_OK;
}
void event_handler(void *arg, esp_event_base_t base, int32_t id, void *event_data)
{
uint8_t red = 0;
uint8_t green = 0;
uint8_t blue = 0;
if (id != EVENT_LED_OFF)
{
auto persistenceManager = PersistenceManager();
persistenceManager.Load();
auto mode = persistenceManager.GetValue("light_mode", 0);
auto light_mode = (mode == 0) ? "day" : "night";
red = persistenceManager.GetValue(std::string(ColorSettingsMenuOptions::RED) + light_mode, value);
green = persistenceManager.GetValue(std::string(ColorSettingsMenuOptions::GREEN) + light_mode, value);
blue = persistenceManager.GetValue(std::string(ColorSettingsMenuOptions::BLUE) + light_mode, value);
}
for (uint32_t i = 0; i < max_leds; i++)
{
led_strip_set_pixel(led_strip, i, red, green, blue);
}
led_strip_refresh(led_strip);
led_behavior_t led2_behavior = {
.mode = LED_MODE_SOLID, .color = {.r = red, .g = green, .b = blue}, .on_time_ms = 0, .off_time_ms = 0};
led_status_set_behavior(2, led2_behavior);
}
uint64_t register_handler(void)
{
esp_event_loop_args_t loop_args = {
.queue_size = 2, .task_name = "led_manager", .task_priority = 5, .task_stack_size = 4096, .task_core_id = 1};
esp_event_loop_create(&loop_args, &loop_handle);
esp_event_handler_register_with(loop_handle, LED_EVENTS_BASE, ESP_EVENT_ANY_ID, event_handler, NULL);
return ESP_OK;
}
uint64_t send_event(uint32_t event, led_event_data_t *payload)
{
if (payload == nullptr)
{
return ESP_ERR_INVALID_ARG;
}
esp_err_t err = esp_event_post_to(loop_handle, // Event loop handle
LED_EVENTS_BASE, // Event base
event, // Event ID (EVENT_LED_ON, EVENT_LED_OFF, etc.)
payload, // Data pointer
sizeof(led_event_data_t), // Data size
portMAX_DELAY // Wait time
);
if (err != ESP_OK)
{
ESP_LOGE("LED", "Failed to post event: %s", esp_err_to_name(err));
return err;
}
return ESP_OK;
}

View File

@@ -46,8 +46,8 @@ static void led_status_task(void *pvParameters)
break;
case LED_MODE_SOLID:
led_strip_set_pixel(led_strip, i, control->behavior.color.r, control->behavior.color.g,
control->behavior.color.b);
led_strip_set_pixel(led_strip, i, control->behavior.color.red, control->behavior.color.green,
control->behavior.color.blue);
break;
case LED_MODE_BLINK: {
@@ -61,8 +61,8 @@ static void led_status_task(void *pvParameters)
if (control->is_on_in_blink)
{
led_strip_set_pixel(led_strip, i, control->behavior.color.r, control->behavior.color.g,
control->behavior.color.b);
led_strip_set_pixel(led_strip, i, control->behavior.color.red, control->behavior.color.green,
control->behavior.color.blue);
}
else
{
@@ -119,7 +119,7 @@ esp_err_t led_status_init(int gpio_num)
}
// Start task
xTaskCreate(led_status_task, "led_status_task", 2048, NULL, 5, NULL);
xTaskCreate(led_status_task, "led_status_task", 2048, NULL, tskIDLE_PRIORITY + 1, NULL);
return ESP_OK;
}

View File

@@ -0,0 +1,111 @@
#include "led_strip_ws2812.h"
#include "color.h"
#include "led_status.h"
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include <freertos/task.h>
#include <led_strip.h>
#include <sdkconfig.h>
static const char *TAG = "led_strip";
static led_strip_handle_t led_strip;
static QueueHandle_t led_command_queue;
static const uint32_t MAX_LEDS = CONFIG_LED_STRIP_MAX_LEDS;
typedef struct
{
led_state_t state;
rgb_t color;
} led_command_t;
static void set_all_pixels(const rgb_t color)
{
for (uint32_t i = 0; i < MAX_LEDS; i++)
{
led_strip_set_pixel(led_strip, i, color.red, color.green, color.blue);
}
led_strip_refresh(led_strip);
led_behavior_t led2_behavior = {.mode = LED_MODE_SOLID,
.color = {.red = color.red, .green = color.green, .blue = color.blue}};
led_status_set_behavior(2, led2_behavior);
}
void led_strip_task(void *pvParameters)
{
led_state_t current_state = LED_STATE_OFF;
led_command_t cmd;
for (;;)
{
TickType_t wait_ticks = (current_state == LED_STATE_SIMULATION) ? pdMS_TO_TICKS(50) : portMAX_DELAY;
if (xQueueReceive(led_command_queue, &cmd, wait_ticks) == pdPASS)
{
current_state = cmd.state;
}
rgb_t color;
switch (current_state)
{
case LED_STATE_OFF:
color = (rgb_t){.red = 0, .green = 0, .blue = 0};
break;
default:
color = cmd.color;
break;
}
set_all_pixels(color);
}
};
esp_err_t led_strip_init(void)
{
led_strip_config_t strip_config = {
.strip_gpio_num = CONFIG_WLED_DIN_PIN,
.max_leds = MAX_LEDS,
.led_model = LED_MODEL_WS2812,
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB,
.flags = {.invert_out = false},
};
led_strip_rmt_config_t rmt_config = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = 0,
.mem_block_symbols = 0,
.flags = {.with_dma = true},
};
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
led_command_queue = xQueueCreate(5, sizeof(led_command_t));
if (led_command_queue == NULL)
{
ESP_LOGE(TAG, "Failed to create command queue");
return ESP_FAIL;
}
xTaskCreate(led_strip_task, "led_strip_task", 4096, NULL, tskIDLE_PRIORITY + 1, NULL);
ESP_LOGI(TAG, "LED strip initialized");
return ESP_OK;
}
esp_err_t led_strip_update(led_state_t state, rgb_t color)
{
led_command_t cmd = {
.state = state,
.color = color,
};
if (xQueueSend(led_command_queue, &cmd, pdMS_TO_TICKS(100)) != pdPASS)
{
ESP_LOGE(TAG, "Failed to send command to LED manager queue");
return ESP_FAIL;
}
return ESP_OK;
}

View File

@@ -1,6 +1,6 @@
idf_component_register(SRCS
"simulator.c"
"storage.c"
"src/simulator.c"
"src/storage.c"
INCLUDE_DIRS "include"
PRIV_REQUIRES
led-manager

View File

@@ -3,15 +3,18 @@
#include "esp_check.h"
#include <stdint.h>
esp_err_t add_light_item(const char time[5], uint8_t red, uint8_t green, uint8_t blue);
void cleanup_light_items(void);
#ifdef __cplusplus
extern "C"
// Configuration structure for the simulation
typedef struct
{
#endif
void simulate(void *args);
#ifdef __cplusplus
}
#endif
int cycle_duration_minutes;
} simulation_config_t;
__BEGIN_DECLS
char *get_time(void);
esp_err_t add_light_item(const char time[5], uint8_t red, uint8_t green, uint8_t blue);
void cleanup_light_items(void);
void start_simulate_day(void);
void start_simulate_night(void);
void start_simulation_task(void);
__END_DECLS

View File

@@ -1,120 +0,0 @@
#include "simulator.h"
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "led_status.h"
#include "storage.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
static const char *TAG = "simulator";
// The struct is extended with a 'next' pointer to form a linked list.
typedef struct light_item_node_t
{
char time[5];
uint8_t red;
uint8_t green;
uint8_t blue;
struct light_item_node_t *next;
} light_item_node_t;
// Global pointers for the head and tail of the list.
static light_item_node_t *head = NULL;
static light_item_node_t *tail = NULL;
esp_err_t add_light_item(const char time[5], uint8_t red, uint8_t green, uint8_t blue)
{
// Allocate memory for a new node in PSRAM.
light_item_node_t *new_node = (light_item_node_t *)heap_caps_malloc(sizeof(light_item_node_t), MALLOC_CAP_SPIRAM);
if (new_node == NULL)
{
ESP_LOGE(TAG, "Failed to allocate memory in PSRAM for new light_item_node_t.");
return ESP_FAIL;
}
// Initialize the data of the new node.
memcpy(new_node->time, time, sizeof(new_node->time));
new_node->red = red;
new_node->green = green;
new_node->blue = blue;
new_node->next = NULL;
// Append the new node to the end of the list.
if (head == NULL)
{
// If the list is empty, the new node becomes both head and tail.
head = new_node;
tail = new_node;
}
else
{
// Otherwise, append the new node to the end and update tail.
tail->next = new_node;
tail = new_node;
}
return ESP_OK;
}
void cleanup_light_items(void)
{
light_item_node_t *current = head;
light_item_node_t *next_node;
while (current != NULL)
{
next_node = current->next;
heap_caps_free(current);
current = next_node;
}
head = NULL;
tail = NULL;
ESP_LOGI(TAG, "Cleaned up all light items.");
}
void simulate(void *args)
{
ESP_LOGI(TAG, "Simulation task started with args: %p", args);
initialize_storage();
load_file("/spiffs/schema_02.csv");
if (head == NULL)
{
ESP_LOGW(TAG, "Light schedule is empty. Simulation will not run.");
vTaskDelete(NULL);
return;
}
ESP_LOGI(TAG, "Starting simulation loop.");
light_item_node_t *current_item = head;
while (1)
{
if (current_item == NULL)
{
current_item = head;
ESP_LOGI(TAG, "Reached end of schedule, restarting from head.");
}
ESP_LOGI(TAG, "Simulating time: %s -> R:%d, G:%d, B:%d", current_item->time, current_item->red,
current_item->green, current_item->blue);
led_behavior_t led1_behavior = {
.mode = LED_MODE_SOLID,
.color = {.r = current_item->red, .g = current_item->green, .b = current_item->blue},
.on_time_ms = 0,
.off_time_ms = 0};
led_status_set_behavior(1, led1_behavior);
current_item = current_item->next;
vTaskDelay(pdMS_TO_TICKS(1000));
}
cleanup_light_items();
}

View File

@@ -0,0 +1,245 @@
#include "simulator.h"
#include "color.h"
#include "led_strip_ws2812.h"
#include "storage.h"
#include <esp_heap_caps.h>
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
static const char *TAG = "simulator";
static char *time;
static char *time_to_string(int hhmm)
{
static char buffer[20];
snprintf(buffer, sizeof(buffer), "%02d:%02d Uhr", hhmm / 100, hhmm % 100);
return buffer;
}
static TaskHandle_t simulation_task_handle = NULL;
// The struct is extended with a 'next' pointer to form a linked list.
typedef struct light_item_node_t
{
char time[5];
uint8_t red;
uint8_t green;
uint8_t blue;
struct light_item_node_t *next;
} light_item_node_t;
// Global pointers for the head and tail of the list.
static light_item_node_t *head = NULL;
static light_item_node_t *tail = NULL;
char *get_time(void)
{
return time;
}
esp_err_t add_light_item(const char time[5], uint8_t red, uint8_t green, uint8_t blue)
{
// Allocate memory for a new node in PSRAM.
light_item_node_t *new_node = (light_item_node_t *)heap_caps_malloc(sizeof(light_item_node_t), MALLOC_CAP_SPIRAM);
if (new_node == NULL)
{
ESP_LOGE(TAG, "Failed to allocate memory in PSRAM for new light_item_node_t.");
return ESP_FAIL;
}
// Initialize the data of the new node.
memcpy(new_node->time, time, sizeof(new_node->time));
new_node->red = red;
new_node->green = green;
new_node->blue = blue;
new_node->next = NULL;
// Append the new node to the end of the list.
if (head == NULL)
{
// If the list is empty, the new node becomes both head and tail.
head = new_node;
tail = new_node;
}
else
{
// Otherwise, append the new node to the end and update tail.
tail->next = new_node;
tail = new_node;
}
return ESP_OK;
}
void cleanup_light_items(void)
{
light_item_node_t *current = head;
light_item_node_t *next_node;
while (current != NULL)
{
next_node = current->next;
heap_caps_free(current);
current = next_node;
}
head = NULL;
tail = NULL;
ESP_LOGI(TAG, "Cleaned up all light items.");
}
static void initialize_light_items(void)
{
if (head != NULL)
{
ESP_LOGI(TAG, "Light schedule already initialized.");
return;
}
initialize_storage();
load_file("/spiffs/schema_02.csv");
if (head == NULL)
{
ESP_LOGW(TAG, "Light schedule is empty. Simulation will not run.");
vTaskDelete(NULL);
return;
}
}
static light_item_node_t *find_best_light_item_for_time(int hhmm)
{
light_item_node_t *best_item = NULL;
light_item_node_t *current = head;
int best_time = -1;
while (current != NULL)
{
int current_time = atoi(current->time);
if (current_time <= hhmm && current_time > best_time)
{
best_time = current_time;
best_item = current;
}
current = current->next;
}
if (best_item == NULL)
{
ESP_LOGW(TAG, "No suitable light item found for time up to %04d", hhmm);
}
else
{
ESP_LOGD(TAG, "Best light item for time %04d is %s", hhmm, best_item->time);
}
return best_item;
}
void start_simulate_day(void)
{
initialize_light_items();
light_item_node_t *current_item = find_best_light_item_for_time(1200);
if (current_item != NULL)
{
led_strip_update(LED_STATE_DAY,
(rgb_t){.red = current_item->red, .green = current_item->green, .blue = current_item->blue});
}
}
void start_simulate_night(void)
{
initialize_light_items();
light_item_node_t *current_item = find_best_light_item_for_time(0);
if (current_item != NULL)
{
led_strip_update(LED_STATE_NIGHT,
(rgb_t){.red = current_item->red, .green = current_item->green, .blue = current_item->blue});
}
}
void simulate_cycle(void *args)
{
simulation_config_t *config = (simulation_config_t *)args;
int cycle_duration_minutes = config->cycle_duration_minutes;
heap_caps_free(config);
if (cycle_duration_minutes <= 0)
{
ESP_LOGE(TAG, "Invalid cycle duration: %d minutes. Must be positive.", cycle_duration_minutes);
vTaskDelete(NULL);
return;
}
initialize_light_items();
const int total_minutes_in_day = 24 * 60;
long delay_ms = (long)cycle_duration_minutes * 60 * 1000 / total_minutes_in_day;
ESP_LOGI(TAG, "Starting simulation of a 24h cycle over %d minutes. Each simulated minute will take %ld ms.",
cycle_duration_minutes, delay_ms);
int current_minute_of_day = 0;
light_item_node_t *last_item = NULL;
while (1)
{
int hours = current_minute_of_day / 60;
int minutes = current_minute_of_day % 60;
int hhmm = hours * 100 + minutes;
time = time_to_string(hhmm);
light_item_node_t *current_item = find_best_light_item_for_time(hhmm);
if (current_item != NULL && current_item != last_item)
{
ESP_LOGI(TAG, "Simulating time: %02d:%02d -> Closest schedule is %s. R:%d, G:%d, B:%d", hours, minutes,
current_item->time, current_item->red, current_item->green, current_item->blue);
led_strip_update(
LED_STATE_SIMULATION,
(rgb_t){.red = current_item->red, .green = current_item->green, .blue = current_item->blue});
last_item = current_item;
}
vTaskDelay(pdMS_TO_TICKS(delay_ms));
current_minute_of_day++;
if (current_minute_of_day >= total_minutes_in_day)
{
current_minute_of_day = 0;
ESP_LOGI(TAG, "Simulation cycle restarting.");
}
}
}
void start_simulation_task(void)
{
if (simulation_task_handle != NULL)
{
vTaskDelete(simulation_task_handle);
simulation_task_handle = NULL;
}
simulation_config_t *config =
(simulation_config_t *)heap_caps_malloc(sizeof(simulation_config_t), MALLOC_CAP_SPIRAM);
if (config == NULL)
{
ESP_LOGE(TAG, "Failed to allocate memory for simulation config.");
return;
}
config->cycle_duration_minutes = 15;
if (xTaskCreate(simulate_cycle, "simulate_cycle", 4096, (void *)config, tskIDLE_PRIORITY + 1,
&simulation_task_handle) != pdPASS)
{
ESP_LOGE(TAG, "Failed to create simulation task.");
heap_caps_free(config);
}
}

View File

@@ -113,7 +113,7 @@ static void init_ui(void)
});
u8g2_ClearBuffer(&u8g2);
m_widget->render();
m_widget->Render();
u8g2_SendBuffer(&u8g2);
}
@@ -126,27 +126,27 @@ static void handle_button(uint8_t button)
switch (button)
{
case 1:
m_widget->onButtonClicked(ButtonType::UP);
m_widget->OnButtonClicked(ButtonType::UP);
break;
case 3:
m_widget->onButtonClicked(ButtonType::LEFT);
m_widget->OnButtonClicked(ButtonType::LEFT);
break;
case 5:
m_widget->onButtonClicked(ButtonType::RIGHT);
m_widget->OnButtonClicked(ButtonType::RIGHT);
break;
case 6:
m_widget->onButtonClicked(ButtonType::DOWN);
m_widget->OnButtonClicked(ButtonType::DOWN);
break;
case 16:
m_widget->onButtonClicked(ButtonType::BACK);
m_widget->OnButtonClicked(ButtonType::BACK);
break;
case 18:
m_widget->onButtonClicked(ButtonType::SELECT);
m_widget->OnButtonClicked(ButtonType::SELECT);
break;
default:
@@ -160,8 +160,10 @@ void app_task(void *args)
{
if (i2c_bus_scan_and_check() != ESP_OK)
{
led_behavior_t led0_behavior = {
.mode = LED_MODE_BLINK, .color = {.r = 50, .g = 0, .b = 0}, .on_time_ms = 1000, .off_time_ms = 500};
led_behavior_t led0_behavior = {.mode = LED_MODE_BLINK,
.color = {.red = 50, .green = 0, .blue = 0},
.on_time_ms = 1000,
.off_time_ms = 500};
led_status_set_behavior(0, led0_behavior);
ESP_LOGE(TAG, "Display not found on I2C bus");
@@ -192,8 +194,8 @@ void app_task(void *args)
uint64_t deltaMs = delta / 1000;
m_widget->update(deltaMs);
m_widget->render();
m_widget->Update(deltaMs);
m_widget->Render();
m_inactivityTracker->update(deltaMs);
}

View File

@@ -1,11 +1,8 @@
#pragma once
#ifdef __cplusplus
extern "C"
{
#endif
void setup_buttons(void);
void cleanup_buttons(void);
#ifdef __cplusplus
}
#endif
#include <sys/cdefs.h>
__BEGIN_DECLS
void setup_buttons(void);
void cleanup_buttons(void);
__END_DECLS

View File

@@ -70,23 +70,18 @@ typedef struct
.reset = U8G2_ESP32_HAL_UNDEFINED, \
.dc = U8G2_ESP32_HAL_UNDEFINED}
#ifdef __cplusplus
extern "C"
{
#endif
/**
* Initialize the HAL with the given configuration.
*
* @see u8g2_esp32_hal_t
* @see U8G2_ESP32_HAL_DEFAULT
*/
void u8g2_esp32_hal_init(u8g2_esp32_hal_t u8g2_esp32_hal_param);
uint8_t u8g2_esp32_spi_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
uint8_t u8g2_esp32_i2c_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
uint8_t u8g2_esp32_gpio_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
#ifdef __cplusplus
}
#endif
__BEGIN_DECLS
/**
* Initialize the HAL with the given configuration.
*
* @see u8g2_esp32_hal_t
* @see U8G2_ESP32_HAL_DEFAULT
*/
void u8g2_esp32_hal_init(u8g2_esp32_hal_t u8g2_esp32_hal_param);
uint8_t u8g2_esp32_spi_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
uint8_t u8g2_esp32_i2c_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
uint8_t u8g2_esp32_gpio_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
__END_DECLS
#endif /* U8G2_ESP32_HAL_H_ */
#endif
#endif

View File

@@ -8,11 +8,6 @@
#define I2C_MASTER_SDA_PIN ((gpio_num_t)CONFIG_DISPLAY_SDA_PIN)
#define I2C_MASTER_SCL_PIN ((gpio_num_t)CONFIG_DISPLAY_SCL_PIN)
#ifdef __cplusplus
extern "C"
{
#endif
esp_err_t i2c_bus_scan_and_check(void);
#ifdef __cplusplus
}
#endif
__BEGIN_DECLS
esp_err_t i2c_bus_scan_and_check(void);
__END_DECLS

View File

@@ -1,52 +1,45 @@
#include "app_task.h"
#include "ble_manager.h"
#include "esp_event.h"
#include "esp_insights.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "color.h"
#include "hal_esp32/PersistenceManager.h"
#include "led_manager.h"
#include "led_status.h"
#include "nvs_flash.h"
#include "sdkconfig.h"
#include "led_strip_ws2812.h"
#include "simulator.h"
#include "wifi_manager.h"
#include <ble_manager.h>
#include <esp_event.h>
#include <esp_insights.h>
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <nvs_flash.h>
#include <sdkconfig.h>
#ifdef __cplusplus
extern "C"
__BEGIN_DECLS
void app_main(void)
{
#endif
void app_main(void)
// Initialize NVS
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
// Initialize NVS
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ESP_ERROR_CHECK(nvs_flash_init());
}
led_status_init(CONFIG_STATUS_WLED_PIN);
wled_init();
register_handler();
xTaskCreatePinnedToCore(app_task, "app_task", 4096, NULL, tskIDLE_PRIORITY + 1, NULL, portNUM_PROCESSORS - 1);
xTaskCreatePinnedToCore(simulate, "simulate", 4096, NULL, tskIDLE_PRIORITY + 1, NULL, portNUM_PROCESSORS - 1);
// xTaskCreatePinnedToCore(ble_manager_task, "ble_manager", 4096, NULL, tskIDLE_PRIORITY + 1, NULL,
// portNUM_PROCESSORS - 1);
auto persistence = PersistenceManager();
persistence.Load();
if (persistence.GetValue("light_active", false))
{
led_event_data_t payload = {.value = 42};
send_event(EVENT_LED_ON, &payload);
}
ESP_ERROR_CHECK(nvs_flash_erase());
ESP_ERROR_CHECK(nvs_flash_init());
}
auto persistence = PersistenceManager();
persistence.Load();
led_status_init(CONFIG_STATUS_WLED_PIN);
led_strip_init();
start_simulation_task();
xTaskCreatePinnedToCore(app_task, "app_task", 4096, NULL, tskIDLE_PRIORITY + 1, NULL, portNUM_PROCESSORS - 1);
// xTaskCreatePinnedToCore(ble_manager_task, "ble_manager", 4096, NULL, tskIDLE_PRIORITY + 1, NULL,
// portNUM_PROCESSORS - 1);
if (persistence.GetValue("light_active", false))
{
led_strip_update(LED_STATE_DAY, rgb_t{});
}
#ifdef __cplusplus
}
#endif
__END_DECLS

View File

@@ -206,8 +206,8 @@ void Device::DrawScreen(const uint64_t dt) const
if (m_widget != nullptr)
{
m_widget->update(dt);
m_widget->render();
m_widget->Update(dt);
m_widget->Render();
}
RenderU8G2();
@@ -257,7 +257,7 @@ void Device::OnButtonClicked(const ButtonType button) const
if (m_widget != nullptr)
{
m_widget->onButtonClicked(button);
m_widget->OnButtonClicked(button);
}
}