feat: add ESP32 firmware with WiFi, API fetch, e-paper rendering, and captive portal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
// GxEPD2 for Waveshare 7.5" V2 (800x480, GxEPD2_750_T7)
|
||||
#include <GxEPD2_BW.h>
|
||||
#include <GxEPD2_750_T7.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "renderer.h"
|
||||
#include "captive_portal.h"
|
||||
|
||||
// ── Pin mapping for Waveshare ESP32 Driver Board ──────────────────────────────
|
||||
#define EPD_CS 15
|
||||
#define EPD_DC 27
|
||||
#define EPD_RST 26
|
||||
#define EPD_BUSY 25
|
||||
|
||||
// ── Display instance ──────────────────────────────────────────────────────────
|
||||
GxEPD2_BW<GxEPD2_750_T7, GxEPD2_750_T7::HEIGHT> display(
|
||||
GxEPD2_750_T7(EPD_CS, EPD_DC, EPD_RST, EPD_BUSY)
|
||||
);
|
||||
|
||||
// ── Deep sleep helper ─────────────────────────────────────────────────────────
|
||||
static void goDeepSleep(int seconds) {
|
||||
Serial.printf("[Sleep] Entering deep sleep for %d seconds\n", seconds);
|
||||
display.hibernate();
|
||||
WiFi.disconnect(true);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
esp_sleep_enable_timer_wakeup((uint64_t)seconds * 1000000ULL);
|
||||
esp_deep_sleep_start();
|
||||
}
|
||||
|
||||
// ── showError: white screen with centered error message ───────────────────────
|
||||
static void showError(const char* message) {
|
||||
Serial.printf("[Error] %s\n", message);
|
||||
display.setFullWindow();
|
||||
display.firstPage();
|
||||
do {
|
||||
display.fillScreen(GxEPD_WHITE);
|
||||
display.setTextColor(GxEPD_BLACK);
|
||||
display.setTextSize(2);
|
||||
|
||||
// Print "FEHLER:" header
|
||||
const char* header = "FEHLER:";
|
||||
int headerW = 6 * 2 * strlen(header);
|
||||
display.setCursor((display.width() - headerW) / 2, display.height() / 2 - 24);
|
||||
display.print(header);
|
||||
|
||||
// Print message below
|
||||
display.setTextSize(1);
|
||||
int msgW = 6 * strlen(message);
|
||||
display.setCursor((display.width() - msgW) / 2, display.height() / 2 + 4);
|
||||
display.print(message);
|
||||
} while (display.nextPage());
|
||||
}
|
||||
|
||||
// ── setup ─────────────────────────────────────────────────────────────────────
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.println("[Boot] EPaper Display starting...");
|
||||
|
||||
// 1. Init display
|
||||
display.init(115200);
|
||||
display.setRotation(0);
|
||||
|
||||
// 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");
|
||||
display.setFullWindow();
|
||||
display.firstPage();
|
||||
do {
|
||||
display.fillScreen(GxEPD_WHITE);
|
||||
display.setTextColor(GxEPD_BLACK);
|
||||
display.setTextSize(2);
|
||||
const char* line1 = "Mit 'EPaper-Setup' WLAN verbinden";
|
||||
const char* line2 = "und http://192.168.4.1 oeffnen";
|
||||
int w1 = 6 * 2 * strlen(line1);
|
||||
int w2 = 6 * 2 * strlen(line2);
|
||||
display.setCursor((display.width() - w1) / 2, display.height() / 2 - 20);
|
||||
display.print(line1);
|
||||
display.setCursor((display.width() - w2) / 2, display.height() / 2 + 10);
|
||||
display.print(line2);
|
||||
} while (display.nextPage());
|
||||
|
||||
startCaptivePortal(config); // never returns
|
||||
}
|
||||
|
||||
// 4. Connect to WiFi (30 attempts, 500 ms each)
|
||||
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 failed");
|
||||
goDeepSleep(60);
|
||||
return; // not reached
|
||||
}
|
||||
Serial.printf("[WiFi] Connected, IP: %s\n", WiFi.localIP().toString().c_str());
|
||||
|
||||
// 5. Build request URL
|
||||
String url = config.api_url + "/api/display/" + config.display_id;
|
||||
if (config.api_token.length() > 0) {
|
||||
url += "?token=" + config.api_token;
|
||||
}
|
||||
Serial.printf("[HTTP] GET %s\n", url.c_str());
|
||||
|
||||
// 6. HTTP GET
|
||||
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();
|
||||
showError(("HTTP " + String(httpCode)).c_str());
|
||||
goDeepSleep(60);
|
||||
return; // not reached
|
||||
}
|
||||
|
||||
String payload = http.getString();
|
||||
http.end();
|
||||
Serial.printf("[HTTP] Payload length: %d\n", payload.length());
|
||||
|
||||
// 7. Parse JSON (ArduinoJson v7)
|
||||
JsonDocument doc;
|
||||
DeserializationError err = deserializeJson(doc, payload);
|
||||
if (err) {
|
||||
Serial.printf("[JSON] Parse error: %s\n", err.c_str());
|
||||
showError(("JSON: " + String(err.c_str())).c_str());
|
||||
goDeepSleep(60);
|
||||
return; // not reached
|
||||
}
|
||||
|
||||
// 8. Extract refresh_sec (prefer from JSON, fall back to config)
|
||||
int refreshSec = doc["refresh_sec"] | config.refresh_sec;
|
||||
if (refreshSec < 10) refreshSec = 60; // safety minimum
|
||||
|
||||
// 9. Render elements
|
||||
JsonArray elements = doc["elements"].as<JsonArray>();
|
||||
|
||||
display.setFullWindow();
|
||||
display.firstPage();
|
||||
do {
|
||||
display.fillScreen(GxEPD_WHITE);
|
||||
renderElements(display, elements);
|
||||
} while (display.nextPage());
|
||||
|
||||
Serial.println("[Render] Done");
|
||||
|
||||
// 10. Disconnect WiFi and deep sleep
|
||||
goDeepSleep(refreshSec);
|
||||
}
|
||||
|
||||
// ── loop ─────────────────────────────────────────────────────────────────────
|
||||
void loop() {
|
||||
// Never reached: setup() ends in deep sleep
|
||||
}
|
||||
Reference in New Issue
Block a user