- Add WT32-SC01 V1 (ESP32-WROVER, 480x320 LCD) as second board option - Split ESP32 code into epaper/ and lcd/ subdirectories - LCD version: LovyanGFX, color display, touch-to-refresh, no deep sleep - Add display_type, width, height to displays table (migration 003) - Frontend: display type selector with predefined resolutions - Preview and layout editor now use dynamic resolution from display Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
206 lines
6.8 KiB
C++
206 lines
6.8 KiB
C++
#include <Arduino.h>
|
|
#include <WiFi.h>
|
|
#include <HTTPClient.h>
|
|
#include <ArduinoJson.h>
|
|
|
|
#include "display_config.h"
|
|
#include "renderer_lcd.h"
|
|
#include "config.h"
|
|
#include "captive_portal.h"
|
|
|
|
// ── Display instance ────────────────────────────────────────────────────────
|
|
static LGFX_WT32_SC01 lcd;
|
|
|
|
// ── Status bar at bottom ────────────────────────────────────────────────────
|
|
static void drawStatusBar(const char* status, uint32_t color = LCD_LABEL) {
|
|
lcd.fillRect(0, 300, 480, 20, 0x0000);
|
|
lcd.setTextSize(1.0f);
|
|
lcd.setTextColor(color);
|
|
lcd.setTextDatum(lgfx::bottom_left);
|
|
lcd.drawString(status, 4, 318);
|
|
}
|
|
|
|
// ── Show error full screen ──────────────────────────────────────────────────
|
|
static void showError(const char* message) {
|
|
Serial.printf("[Error] %s\n", message);
|
|
lcd.fillScreen(0x0000);
|
|
lcd.setTextSize(2.0f);
|
|
lcd.setTextColor(TFT_RED);
|
|
lcd.setTextDatum(lgfx::middle_center);
|
|
lcd.drawString("FEHLER", 240, 140);
|
|
lcd.setTextSize(1.2f);
|
|
lcd.setTextColor(LCD_TEXT);
|
|
lcd.drawString(message, 240, 170);
|
|
}
|
|
|
|
// ── Show setup instructions on LCD ──────────────────────────────────────────
|
|
static void showSetupScreen() {
|
|
lcd.fillScreen(0x0000);
|
|
lcd.setTextSize(2.0f);
|
|
lcd.setTextColor(LCD_ACCENT);
|
|
lcd.setTextDatum(lgfx::middle_center);
|
|
lcd.drawString("EPaper Setup", 240, 80);
|
|
lcd.setTextSize(1.2f);
|
|
lcd.setTextColor(LCD_TEXT);
|
|
lcd.drawString("Mit 'EPaper-Setup' WLAN verbinden", 240, 140);
|
|
lcd.drawString("und http://192.168.4.1 oeffnen", 240, 165);
|
|
}
|
|
|
|
// ── Fetch and render ────────────────────────────────────────────────────────
|
|
static bool fetchAndRender(Config& config) {
|
|
String url = config.api_url + "/api/display/" + config.display_id;
|
|
Serial.printf("[HTTP] GET %s\n", url.c_str());
|
|
drawStatusBar("Daten laden...", LCD_ACCENT);
|
|
|
|
HTTPClient http;
|
|
http.begin(url);
|
|
http.setTimeout(10000);
|
|
if (config.api_token.length() > 0) {
|
|
http.addHeader("Authorization", "Bearer " + config.api_token);
|
|
}
|
|
|
|
int httpCode = http.GET();
|
|
if (httpCode <= 0 || httpCode >= 400) {
|
|
Serial.printf("[HTTP] Error: %d\n", httpCode);
|
|
http.end();
|
|
String errMsg = "HTTP Fehler: " + String(httpCode);
|
|
drawStatusBar(errMsg.c_str(), TFT_RED);
|
|
return false;
|
|
}
|
|
|
|
String payload = http.getString();
|
|
http.end();
|
|
Serial.printf("[HTTP] Payload length: %d\n", payload.length());
|
|
|
|
// Parse JSON
|
|
JsonDocument doc;
|
|
DeserializationError err = deserializeJson(doc, payload);
|
|
if (err) {
|
|
Serial.printf("[JSON] Parse error: %s\n", err.c_str());
|
|
String errMsg = "JSON Fehler: " + String(err.c_str());
|
|
drawStatusBar(errMsg.c_str(), TFT_RED);
|
|
return false;
|
|
}
|
|
|
|
// Extract refresh_sec
|
|
int refreshSec = doc["refresh_sec"] | config.refresh_sec;
|
|
if (refreshSec < 5) refreshSec = 30;
|
|
config.refresh_sec = refreshSec;
|
|
|
|
// Render
|
|
JsonArray elements = doc["elements"].as<JsonArray>();
|
|
|
|
lcd.fillScreen(LCD_BG);
|
|
renderElementsLCD(lcd, elements);
|
|
|
|
// Status bar with last update time
|
|
char timeBuf[40];
|
|
unsigned long sec = millis() / 1000;
|
|
snprintf(timeBuf, sizeof(timeBuf), "Aktualisiert | Refresh: %ds", refreshSec);
|
|
drawStatusBar(timeBuf, LCD_LABEL);
|
|
|
|
Serial.println("[Render] Done");
|
|
return true;
|
|
}
|
|
|
|
// ── Setup ───────────────────────────────────────────────────────────────────
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
delay(2000);
|
|
Serial.println("\n\n[Boot] WT32-SC01 Plus LCD Display starting...");
|
|
|
|
// 1. Init LCD
|
|
lcd.init();
|
|
lcd.setRotation(1); // Landscape: 480x320
|
|
lcd.setBrightness(200);
|
|
lcd.fillScreen(0x0000);
|
|
lcd.setTextSize(1.5f);
|
|
lcd.setTextColor(LCD_TEXT);
|
|
lcd.setTextDatum(lgfx::middle_center);
|
|
lcd.drawString("Starte...", 240, 160);
|
|
|
|
// 2. Load config from NVS
|
|
Config config;
|
|
config.load();
|
|
|
|
// 3. If not configured → captive portal
|
|
if (!config.isConfigured()) {
|
|
Serial.println("[Boot] Not configured, starting captive portal");
|
|
showSetupScreen();
|
|
startCaptivePortal(config); // never returns
|
|
}
|
|
|
|
// 4. Connect WiFi
|
|
lcd.fillScreen(0x0000);
|
|
lcd.drawString("WiFi verbinden...", 240, 160);
|
|
|
|
Serial.printf("[WiFi] Connecting to %s\n", config.wifi_ssid.c_str());
|
|
WiFi.mode(WIFI_STA);
|
|
WiFi.begin(config.wifi_ssid.c_str(), config.wifi_password.c_str());
|
|
|
|
int attempts = 0;
|
|
while (WiFi.status() != WL_CONNECTED && attempts < 30) {
|
|
delay(500);
|
|
Serial.print(".");
|
|
++attempts;
|
|
}
|
|
Serial.println();
|
|
|
|
if (WiFi.status() != WL_CONNECTED) {
|
|
showError("WiFi Verbindung fehlgeschlagen");
|
|
delay(10000);
|
|
ESP.restart();
|
|
return;
|
|
}
|
|
Serial.printf("[WiFi] Connected, IP: %s\n", WiFi.localIP().toString().c_str());
|
|
|
|
// 5. Initial fetch
|
|
if (!fetchAndRender(config)) {
|
|
showError("Daten konnten nicht geladen werden");
|
|
}
|
|
}
|
|
|
|
// ── Loop: periodic refresh (no deep sleep for LCD) ──────────────────────────
|
|
static unsigned long lastUpdate = 0;
|
|
static Config loopConfig;
|
|
static bool configLoaded = false;
|
|
|
|
void loop() {
|
|
if (!configLoaded) {
|
|
loopConfig.load();
|
|
configLoaded = true;
|
|
lastUpdate = millis();
|
|
}
|
|
|
|
unsigned long now = millis();
|
|
unsigned long intervalMs = (unsigned long)loopConfig.refresh_sec * 1000UL;
|
|
|
|
if (now - lastUpdate >= intervalMs) {
|
|
lastUpdate = now;
|
|
if (WiFi.status() != WL_CONNECTED) {
|
|
Serial.println("[WiFi] Reconnecting...");
|
|
WiFi.reconnect();
|
|
delay(5000);
|
|
}
|
|
if (WiFi.status() == WL_CONNECTED) {
|
|
fetchAndRender(loopConfig);
|
|
} else {
|
|
drawStatusBar("WiFi getrennt!", TFT_RED);
|
|
}
|
|
}
|
|
|
|
// Touch handling: tap to force refresh
|
|
lgfx::touch_point_t tp;
|
|
if (lcd.getTouch(&tp)) {
|
|
Serial.println("[Touch] Manual refresh triggered");
|
|
drawStatusBar("Manuell aktualisieren...", LCD_ACCENT);
|
|
delay(300); // debounce
|
|
if (WiFi.status() == WL_CONNECTED) {
|
|
fetchAndRender(loopConfig);
|
|
}
|
|
lastUpdate = millis();
|
|
}
|
|
|
|
delay(50); // small delay for touch responsiveness
|
|
}
|