Restructure ESP32 firmware into modules, fix bugs, add tests

Structure (src/esp32/):
- logic/: DisplayController, schedule, power timer, LED pattern; pure C++
- display/: FlipdotFrames and bitmaps
- nano_link/: NanoLink (relay, serial, READY, bootloader) and flash bridge
- net/: WiFi/OTA/panic window, Home Assistant
- config.h with all settings; libs.h (globals in a header) removed

Bug fixes:
- Home Assistant switches now report their state back and start as ON
- loop() no longer blocks: WiFi.waitForConnectResult() (up to 60 s
  without WiFi) and getLocalTime() (5 s before NTP sync) were called
  every iteration
- Power switch off now powers off after 1 s even when a 30 s countdown
  is already running
- millis() overflow safe power-off timer; no frame catch-up bursts
- No garbage time string before NTP sync; 'Connecting...' stays instead
- Frames are only sent when the screen changes instead of every second
- Reset reasons are logged once instead of every 100 ms
- Removed dead code (unused fonts, WiFiMulti, rotated bitmap, ...)

Tests: new test_esp32 environment (26 tests); native renamed to test_nano

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-13 00:56:32 +02:00
1 parent cefb76c1ad
commit 683a47c055
29 files changed
+1289 -755

No files matched your search

+118
View File
@@ -0,0 +1,118 @@
#include "logic/display_controller.h"
#include "config.h"
#include "logic/schedule.h"
DisplayController::DisplayController(DisplayPower &power, Frames &frames) : _power(power), _frames(frames)
{
}
void DisplayController::setClockEnabled(bool enabled)
{
_clock_enabled = enabled;
}
void DisplayController::setPowerEnabled(bool enabled)
{
_power_enabled = enabled;
}
bool DisplayController::clockEnabled() const
{
return _clock_enabled;
}
bool DisplayController::powerEnabled() const
{
return _power_enabled;
}
void DisplayController::update(uint32_t now, bool time_valid, int hour, int minute)
{
// Switched off elsewhere (power timer, Nano flashing): the Nano lost its frame
if (!_power.isOn())
_screen = Screen::Unknown;
if (_off_timer.expired(now))
{
_power.off();
_screen = Screen::Unknown;
return;
}
if (!_power_enabled)
{
if (_power.isOn())
{
_showBlank();
_off_timer.request(now, CLEAR_BEFORE_OFF_TIME);
}
return;
}
// Keep the boot screen until the time is known
if (!time_valid)
return;
if (!_clock_enabled)
{
// The logo is only shown while the display is on; it never switches the power on
if (_power.isOn())
{
_showLogo();
_off_timer.request(now, POWER_OFF_DELAY);
}
return;
}
switch (scheduleFor(hour, minute))
{
case ScheduleMode::Night:
if (_power.isOn())
{
_showBlank();
_off_timer.request(now, POWER_OFF_DELAY);
}
break;
case ScheduleMode::Clock:
_off_timer.cancel();
if (!_power.isOn())
{
_power.on();
_screen = Screen::Unknown;
}
_showClock(hour, minute);
break;
case ScheduleMode::Standby:
// Flipdots keep their image without power, so the last time stays visible
if (_power.isOn())
_off_timer.request(now, POWER_OFF_DELAY);
break;
}
}
void DisplayController::_showClock(int hour, int minute)
{
if (_screen == Screen::Clock && _shown_hour == hour && _shown_minute == minute)
return;
_frames.clock(hour, minute);
_screen = Screen::Clock;
_shown_hour = hour;
_shown_minute = minute;
}
void DisplayController::_showLogo()
{
if (_screen == Screen::Logo)
return;
_frames.logo();
_screen = Screen::Logo;
}
void DisplayController::_showBlank()
{
if (_screen == Screen::Blank)
return;
_frames.blank();
_screen = Screen::Blank;
}