Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
96336ed42This file is pinned to the selected release. Download the original to open it in your design tool.
#include "rackglow.h"
#include "ui/UIManager.h"
#include "WifiBootManager.h"
#include "led/LEDManager.h"
#include "WebUIManager.h"
#include "MqttManager.h"
#include "audio/AudioTask.h"
#include <OTAManager.h>
#include <Logger.h>
#include <esp_system.h>
// Global manager instances
extern UIManager* g_uiManager;
extern LEDManager* g_ledManager;
extern WebUIManager* g_webUIManager;
extern WifiBootManager* g_wifiBootManager;
extern OTAManager* g_otaManager;
extern MqttManager* g_mqttManager;
// Why the last boot happened. On an unexplained reboot this one line separates a
// firmware panic from a task watchdog from a brownout (145 LEDs can pull enough
// to sag a weak supply) — guessing between those costs far more than logging it.
static const char* resetReasonName(esp_reset_reason_t reason) {
switch (reason) {
case ESP_RST_POWERON: return "power-on";
case ESP_RST_EXT: return "external pin";
case ESP_RST_SW: return "software restart";
case ESP_RST_PANIC: return "PANIC / exception";
case ESP_RST_INT_WDT: return "interrupt watchdog";
case ESP_RST_TASK_WDT: return "task watchdog";
case ESP_RST_WDT: return "other watchdog";
case ESP_RST_DEEPSLEEP: return "deep sleep wake";
case ESP_RST_BROWNOUT: return "BROWNOUT (supply sag)";
case ESP_RST_SDIO: return "SDIO";
default: return "unknown";
}
}
// Global restart flag for async operations
bool g_restartRequested = false;
unsigned long g_restartTime = 0;
void setup()
{
Serial.begin(115200);
delay(1000);
// Initialize logger early
Logger.begin(200, true, true);
Logger.info("Rackglow starting...");
Logger.info("Reset reason: %s", resetReasonName(esp_reset_reason()));
// PSRAM diagnostic — the full-screen visualiser canvas needs it. If found=0
// here, the platformio.ini memory_type doesn't match the module's PSRAM.
Logger.info("PSRAM: found=%d, size=%u bytes, free=%u bytes",
psramFound() ? 1 : 0,
(unsigned)ESP.getPsramSize(),
(unsigned)ESP.getFreePsram());
// Initialize UI Manager (screen only at first)
g_uiManager = new UIManager();
g_uiManager->initializeScreen();
// Initialize network (WiFi + boot screen)
g_wifiBootManager = new WifiBootManager();
g_wifiBootManager->initialize();
// Initialize full UI and LEDs if not in setup mode
if (!g_wifiBootManager->isInSetupMode()) {
// Boot screen goes FIRST, before the UI allocates. Nothing renders between
// here and startRenderTask() below — no lv_refr_now, and the render task
// isn't up yet — so the panel simply holds the last flushed boot frame
// either way, and freeing first means the UI's objects don't land on top
// of ~24 boot widgets that are about to leave holes under them. LVGL draws
// into layers taken from that same fixed pool, so its largest free block
// is a rendering cost, not just a headroom number.
g_wifiBootManager->setBootStatus("Starting interface");
g_wifiBootManager->cleanupBootUI();
UIManager::logLvglMemory("after boot screen freed");
g_uiManager->initializeUI();
g_ledManager = new LEDManager();
g_ledManager->initialize();
}
// Initialize WebUI Manager
g_webUIManager = new WebUIManager(g_wifiBootManager->getWebServer());
g_webUIManager->initialize();
Logger.attachWebSocket(g_webUIManager->getWebSocket());
// Startup sequence (only if not in setup mode)
if (g_ledManager) {
g_ledManager->performStartupDispersion();
g_uiManager->syncWithLEDState();
// Home Assistant bridge — needs WiFi (connected in normal mode) and the
// LED manager. Skipped in captive-portal/setup mode along with the rest.
g_mqttManager = new MqttManager(g_ledManager);
g_mqttManager->begin();
}
// Hand LVGL ownership to the render task — only in normal mode, and only
// after all startup lv_* (syncWithLEDState) has completed so nothing races
// the task during boot. In setup mode the boot UI keeps driving LVGL on the
// loop, so no render task is created.
if (!g_wifiBootManager->isInSetupMode()) {
// Audio task first so frames are flowing before the render task and LED
// animations start pulling from g_audioBus.
startAudioTask();
g_uiManager->startRenderTask();
}
}
void loop()
{
// LVGL tick comes from lv_tick_set_cb(millis) registered in
// UIManager::initializeScreen() — no manual lv_tick_inc here.
// LVGL ownership:
// - Normal mode: the render task owns all lv_* (started at end of setup).
// The loop must NOT touch LVGL here — it only runs the non-UI managers.
// - Setup mode / no UI manager: no render task, so drive LVGL on the loop
// as before to keep the boot UI alive.
if (!(g_uiManager && g_uiManager->isRenderTaskRunning())) {
if (g_uiManager) {
g_uiManager->update();
if (g_wifiBootManager && g_wifiBootManager->isInSetupMode()) {
lv_timer_handler();
}
} else {
lv_timer_handler();
}
}
if (g_wifiBootManager) g_wifiBootManager->update();
if (g_ledManager) g_ledManager->update();
if (g_webUIManager) g_webUIManager->update();
if (g_mqttManager) g_mqttManager->update();
if (g_otaManager) g_otaManager->loop();
// With the render task owning LVGL, the loop no longer blocks on the flush.
// LEDManager::update()'s driver_.show() paces us when strips are configured,
// but yield explicitly so loopTask never 100%-spins core 1 (starving IDLE1)
// when LEDs are absent. 1 ms cap is far above any LED frame rate.
if (g_uiManager && g_uiManager->isRenderTaskRunning()) {
vTaskDelay(1);
}
// Handle restart requests
if (g_restartRequested && g_restartTime == 0) {
g_restartTime = millis();
}
if (g_restartTime > 0 && millis() - g_restartTime > 2000) {
ESP.restart();
}
}