From b47993b29abbad46cb23e607ae41778aedb7b359 Mon Sep 17 00:00:00 2001 From: David Gwilliam Date: Mon, 16 Feb 2026 21:13:31 -0800 Subject: [PATCH] refactor(doorbell-touch): remove entire sketch for rebuild --- BoardConfig.h | 18 -- Config.h | 59 ---- Dashboard.cpp | 196 ------------ Dashboard.h | 56 ---- DisplayDriver.h | 117 -------- DisplayDriverGFX.cpp | 194 ------------ DisplayManager.cpp | 483 ------------------------------ DisplayManager.h | 99 ------- DoorbellLogic.cpp | 576 ------------------------------------ DoorbellLogic.h | 95 ------ ScreenData.h | 58 ---- TouchDriver.h | 21 -- TouchDriverGT911.cpp | 43 --- boards/board_e32r35t.h | 50 ---- boards/board_waveshare_s3.h | 73 ----- doorbell-touch.ino | 137 --------- 16 files changed, 2275 deletions(-) delete mode 100644 BoardConfig.h delete mode 100644 Config.h delete mode 100644 Dashboard.cpp delete mode 100644 Dashboard.h delete mode 100644 DisplayDriver.h delete mode 100644 DisplayDriverGFX.cpp delete mode 100644 DisplayManager.cpp delete mode 100644 DisplayManager.h delete mode 100644 DoorbellLogic.cpp delete mode 100644 DoorbellLogic.h delete mode 100644 ScreenData.h delete mode 100644 TouchDriver.h delete mode 100644 TouchDriverGT911.cpp delete mode 100644 boards/board_e32r35t.h delete mode 100644 boards/board_waveshare_s3.h delete mode 100644 doorbell-touch.ino diff --git a/BoardConfig.h b/BoardConfig.h deleted file mode 100644 index fe0db93..0000000 --- a/BoardConfig.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -// ═══════════════════════════════════════════════════════════════════ -// Board selector — driven by build flags -// Pass -DTARGET_E32R35T or -DTARGET_WAVESHARE_S3_43 -// ═══════════════════════════════════════════════════════════════════ - -#if defined(TARGET_E32R35T) - #include "boards/board_e32r35t.h" - -#elif defined(TARGET_WAVESHARE_S3_43) - #include "boards/board_waveshare_s3.h" - -#else - // Default to E32R35T for backward compatibility with existing builds - #pragma message("No TARGET_* defined — defaulting to E32R35T") - #define TARGET_E32R35T - #include "boards/board_e32r35t.h" -#endif diff --git a/Config.h b/Config.h deleted file mode 100644 index d2019b1..0000000 --- a/Config.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include "BoardConfig.h" - -// ===================================================================== -// Debug -// ===================================================================== -#define DEBUG_MODE 1 - -// ===================================================================== -// WiFi Credentials -// ===================================================================== -struct WiFiCred { const char *ssid; const char *pass; }; -static WiFiCred wifiNetworks[] = { - { "Dobro Veče", "goodnight" }, - { "iot-2GHz", "lesson-greater" }, -}; -static const int NUM_WIFI = sizeof(wifiNetworks) / sizeof(wifiNetworks[0]); - -// ===================================================================== -// ntfy.sh Topics -// ===================================================================== -#define NTFY_BASE "https://ntfy.sh" - -#if DEBUG_MODE - #define TOPIC_SUFFIX "_test" -#else - #define TOPIC_SUFFIX "" -#endif - -// Change since=10s to since=20s (must be > poll interval of 15s, but not too large) -#define ALERT_URL NTFY_BASE "/ALERT_klubhaus_topic" TOPIC_SUFFIX "/json?since=20s&poll=1" -#define SILENCE_URL NTFY_BASE "/SILENCE_klubhaus_topic" TOPIC_SUFFIX "/json?since=20s&poll=1" -#define ADMIN_URL NTFY_BASE "/ADMIN_klubhaus_topic" TOPIC_SUFFIX "/json?since=20s&poll=1" - -#define STATUS_URL NTFY_BASE "/STATUS_klubhaus_topic" TOPIC_SUFFIX - -// ===================================================================== -// Timing -// ===================================================================== -#define POLL_INTERVAL_MS 15000 -#define BLINK_INTERVAL_MS 500 -#define STALE_MSG_THRESHOLD_S 600 -#define NTP_SYNC_INTERVAL_MS 3600000 -#define WAKE_DISPLAY_MS 5000 -#define TOUCH_DEBOUNCE_MS 300 -#define HOLD_DURATION_MS 2000 -#define HEARTBEAT_INTERVAL_MS 30000 - -#if DEBUG_MODE - #define BOOT_GRACE_MS 5000 -#else - #define BOOT_GRACE_MS 30000 -#endif - -// ===================================================================== -// Hardware pins are now in boards/board_*.h via BoardConfig.h -// Screen dimensions (SCREEN_WIDTH, SCREEN_HEIGHT) also come from there. -// ===================================================================== diff --git a/Dashboard.cpp b/Dashboard.cpp deleted file mode 100644 index bae23a9..0000000 --- a/Dashboard.cpp +++ /dev/null @@ -1,196 +0,0 @@ -#include "Dashboard.h" - -#define COL_BG 0x1082 -#define COL_BAR 0x2104 -#define COL_RED 0xF800 -#define COL_ORANGE 0xFBE0 -#define COL_GREEN 0x07E0 -#define COL_CYAN 0x07FF -#define COL_PURPLE 0x780F -#define COL_WHITE 0xFFFF -#define COL_GRAY 0x8410 -#define COL_DARK_TILE 0x18E3 - -static const uint16_t tileBG[] = { - COL_RED, COL_ORANGE, COL_CYAN, COL_PURPLE, COL_DARK_TILE, COL_DARK_TILE -}; -static const uint16_t tileFG[] = { - COL_WHITE, COL_WHITE, 0x0000, COL_WHITE, COL_WHITE, COL_WHITE -}; - -Dashboard::Dashboard(Gfx& tft) - : _tft(tft), _sprite(&tft) -{ - _tiles[TILE_LAST_ALERT] = { "!", "LAST ALERT", "none", "", 0, 0, true }; - _tiles[TILE_STATS] = { "#", "TODAY", "0 alerts", "", 0, 0, true }; - _tiles[TILE_NETWORK] = { "~", "NETWORK", "---", "", 0, 0, true }; - _tiles[TILE_MUTE] = { "M", "MUTE", "OFF", "", 0, 0, true }; - _tiles[TILE_HISTORY] = { ">", "HISTORY", "tap to view", "", 0, 0, true }; - _tiles[TILE_SYSTEM] = { "*", "SYSTEM", "---", "", 0, 0, true }; - - for (int i = 0; i < TILE_COUNT; i++) { - _tiles[i].bgColor = tileBG[i]; - _tiles[i].fgColor = tileFG[i]; - } -} - -void Dashboard::begin() { - _sprite.createSprite(TILE_W, TILE_H); - _sprite.setTextDatum(MC_DATUM); -} - -void Dashboard::drawAll() { - _tft.fillScreen(COL_BG); - drawTopBar("--:--", 0, false); - for (int i = 0; i < TILE_COUNT; i++) { - drawTile((TileID)i); - } -} - -void Dashboard::tilePosition(TileID id, int& x, int& y) { - int col = id % DASH_COLS; - int row = id / DASH_COLS; - x = DASH_MARGIN + col * (TILE_W + DASH_MARGIN); - y = DASH_TOP_BAR + DASH_MARGIN + row * (TILE_H + DASH_MARGIN); -} - -void Dashboard::drawTile(TileID id) { - TileData& t = _tiles[id]; - int tx, ty; - tilePosition(id, tx, ty); - - _sprite.fillSprite(COL_BG); - _sprite.fillRoundRect(0, 0, TILE_W, TILE_H, 8, t.bgColor); - _sprite.drawRoundRect(0, 0, TILE_W, TILE_H, 8, COL_GRAY); - - _sprite.setTextColor(t.fgColor, t.bgColor); - _sprite.setTextFont(4); - _sprite.setTextSize(2); - _sprite.setTextDatum(TC_DATUM); - _sprite.drawString(t.icon, TILE_W / 2, 8); - - _sprite.setTextSize(1); - _sprite.setTextFont(2); - _sprite.setTextDatum(MC_DATUM); - _sprite.drawString(t.label, TILE_W / 2, TILE_H / 2 + 5); - - _sprite.setTextFont(2); - _sprite.setTextDatum(BC_DATUM); - _sprite.drawString(t.value, TILE_W / 2, TILE_H - 25); - - if (strlen(t.sub) > 0) { - _sprite.setTextFont(1); - _sprite.setTextDatum(BC_DATUM); - _sprite.drawString(t.sub, TILE_W / 2, TILE_H - 8); - } - - _sprite.pushSprite(tx, ty); - t.dirty = false; -} - -void Dashboard::drawTopBar(const char* time, int rssi, bool wifiOk) { - _tft.fillRect(0, 0, SCREEN_WIDTH, DASH_TOP_BAR, COL_BAR); - _tft.setTextColor(COL_WHITE, COL_BAR); - _tft.setTextFont(4); - _tft.setTextSize(1); - - _tft.setTextDatum(ML_DATUM); - _tft.drawString("KLUBHAUS ALERT", DASH_MARGIN, DASH_TOP_BAR / 2); - - _tft.setTextDatum(MR_DATUM); - _tft.drawString(time, SCREEN_WIDTH - 10, DASH_TOP_BAR / 2); - - int bars = 0; - if (wifiOk) { - if (rssi > -50) bars = 4; - else if (rssi > -60) bars = 3; - else if (rssi > -70) bars = 2; - else bars = 1; - } - int barX = SCREEN_WIDTH - 110, barW = 6, barGap = 3; - for (int i = 0; i < 4; i++) { - int barH = 6 + i * 5; - int barY = DASH_TOP_BAR - 8 - barH; - uint16_t col = (i < bars) ? COL_GREEN : COL_GRAY; - _tft.fillRect(barX + i * (barW + barGap), barY, barW, barH, col); - } - - strncpy(_barTime, time, sizeof(_barTime) - 1); - _barRSSI = rssi; - _barWifiOk = wifiOk; -} - -void Dashboard::updateTile(TileID id, const char* value, const char* sub) { - TileData& t = _tiles[id]; - bool changed = (strcmp(t.value, value) != 0); - if (sub && strcmp(t.sub, sub) != 0) changed = true; - if (!changed && !t.dirty) return; - - strncpy(t.value, value, 31); - t.value[31] = '\0'; - if (sub) { - strncpy(t.sub, sub, 31); - t.sub[31] = '\0'; - } - t.dirty = true; - drawTile(id); -} - -int Dashboard::handleTouch(int x, int y) { - for (int i = 0; i < TILE_COUNT; i++) { - int tx, ty; - tilePosition((TileID)i, tx, ty); - if (x >= tx && x < tx + TILE_W && y >= ty && y < ty + TILE_H) return i; - } - return -1; -} - -void Dashboard::refreshFromState(const ScreenState& state) { - bool barChanged = (strcmp(_barTime, state.timeString) != 0) - || (_barRSSI != state.wifiRSSI) - || (_barWifiOk != state.wifiConnected); - if (barChanged) { - drawTopBar(state.timeString, state.wifiRSSI, state.wifiConnected); - } - - if (state.alertHistoryCount > 0) { - updateTile(TILE_LAST_ALERT, state.alertHistory[0].message, - state.alertHistory[0].timestamp); - } else { - updateTile(TILE_LAST_ALERT, "none", ""); - } - - char statsBuf[32]; - snprintf(statsBuf, sizeof(statsBuf), "%d alert%s", - state.alertHistoryCount, - state.alertHistoryCount == 1 ? "" : "s"); - updateTile(TILE_STATS, statsBuf, "this session"); - - if (state.wifiConnected) { - char rssiBuf[16]; - snprintf(rssiBuf, sizeof(rssiBuf), "%d dBm", state.wifiRSSI); - updateTile(TILE_NETWORK, rssiBuf, state.wifiSSID); - } else { - updateTile(TILE_NETWORK, "DOWN", "reconnecting..."); - } - - updateTile(TILE_MUTE, "OFF", "tap to mute"); - - if (state.alertHistoryCount > 1) { - char histBuf[48]; - snprintf(histBuf, sizeof(histBuf), "%s %.20s", - state.alertHistory[1].timestamp, - state.alertHistory[1].message); - const char* sub = (state.alertHistoryCount > 2) - ? state.alertHistory[2].message : ""; - updateTile(TILE_HISTORY, histBuf, sub); - } else { - updateTile(TILE_HISTORY, "no history", ""); - } - - char heapBuf[16]; - snprintf(heapBuf, sizeof(heapBuf), "%lu KB", state.freeHeapKB); - char uptimeBuf[20]; - snprintf(uptimeBuf, sizeof(uptimeBuf), "up %lum", state.uptimeMinutes); - updateTile(TILE_SYSTEM, heapBuf, uptimeBuf); -} diff --git a/Dashboard.h b/Dashboard.h deleted file mode 100644 index 133ca26..0000000 --- a/Dashboard.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include "DisplayDriver.h" -#include "ScreenData.h" - -#define DASH_COLS 3 -#define DASH_ROWS 2 -#define DASH_MARGIN 8 -#define DASH_TOP_BAR 40 - -#define TILE_W ((SCREEN_WIDTH - (DASH_COLS + 1) * DASH_MARGIN) / DASH_COLS) -#define TILE_H ((SCREEN_HEIGHT - DASH_TOP_BAR - (DASH_ROWS + 1) * DASH_MARGIN) / DASH_ROWS) - -enum TileID : uint8_t { - TILE_LAST_ALERT = 0, - TILE_STATS, - TILE_NETWORK, - TILE_MUTE, - TILE_HISTORY, - TILE_SYSTEM, - TILE_COUNT -}; - -struct TileData { - const char* icon; - const char* label; - char value[32]; - char sub[32]; - uint16_t bgColor; - uint16_t fgColor; - bool dirty; -}; - -class Dashboard { -public: - Dashboard(Gfx& tft); - - void begin(); - void drawAll(); - void drawTopBar(const char* time, int rssi, bool wifiOk); - void updateTile(TileID id, const char* value, const char* sub = nullptr); - int handleTouch(int x, int y); - void refreshFromState(const ScreenState& state); - -private: - Gfx& _tft; - GfxSprite _sprite; - TileData _tiles[TILE_COUNT]; - - char _barTime[12] = ""; - int _barRSSI = 0; - bool _barWifiOk = false; - - void drawTile(TileID id); - void tilePosition(TileID id, int& x, int& y); -}; diff --git a/DisplayDriver.h b/DisplayDriver.h deleted file mode 100644 index 21f3d26..0000000 --- a/DisplayDriver.h +++ /dev/null @@ -1,117 +0,0 @@ -#pragma once -#include "BoardConfig.h" - -// ═══════════════════════════════════════════════════════════════════ -// Display driver abstraction -// -// TFT_eSPI path: zero-cost typedefs — compiles identically to before -// Arduino_GFX path: adapter classes providing TFT_eSPI-compatible API -// ═══════════════════════════════════════════════════════════════════ - -#if USE_TFT_ESPI -// ───────────────────────────────────────────────────────────────── -// TFT_eSPI — straight typedefs, zero overhead -// ───────────────────────────────────────────────────────────────── -#include - -using Gfx = TFT_eSPI; -using GfxSprite = TFT_eSprite; - -#elif USE_ARDUINO_GFX -// ───────────────────────────────────────────────────────────────── -// Arduino_GFX — adapter wrapping Arduino_GFX with a -// TFT_eSPI-compatible interface for Dashboard / DisplayManager -// ───────────────────────────────────────────────────────────────── -#include - -// Text datum constants (matching TFT_eSPI definitions) -#ifndef MC_DATUM -#define TL_DATUM 0 -#define TC_DATUM 1 -#define TR_DATUM 2 -#define ML_DATUM 3 -#define CL_DATUM 3 -#define MC_DATUM 4 -#define CC_DATUM 4 -#define MR_DATUM 5 -#define CR_DATUM 5 -#define BL_DATUM 6 -#define BC_DATUM 7 -#define BR_DATUM 8 -#endif - -class Gfx; // forward declaration for GfxSprite - -// ── Sprite adapter ────────────────────────────────────────────── -class GfxSprite { -public: - explicit GfxSprite(Gfx* parent); - - void createSprite(int16_t w, int16_t h); - void deleteSprite(); - void fillSprite(uint16_t color); - void fillRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, - int32_t r, uint16_t color); - void drawRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, - int32_t r, uint16_t color); - void setTextColor(uint16_t fg, uint16_t bg); - void setTextFont(uint8_t font); - void setTextSize(uint8_t size); - void setTextDatum(uint8_t datum); - void drawString(const char* str, int32_t x, int32_t y); - void pushSprite(int32_t x, int32_t y); - -private: - Gfx* _parent; - int16_t _w = 0; - int16_t _h = 0; - int32_t _pushX = 0; - int32_t _pushY = 0; - uint8_t _textDatum = TL_DATUM; - uint8_t _textSize = 1; - uint16_t _textFg = 0xFFFF; - uint16_t _textBg = 0x0000; -}; - -// ── Display adapter ───────────────────────────────────────────── -class Gfx { -public: - Gfx(); - - void init(); - void setRotation(uint8_t r); - - // Drawing primitives - void fillScreen(uint16_t color); - void fillRect(int32_t x, int32_t y, int32_t w, int32_t h, - uint16_t color); - void fillRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, - int32_t r, uint16_t color); - void drawRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, - int32_t r, uint16_t color); - void drawFastVLine(int32_t x, int32_t y, int32_t h, uint16_t color); - - // Text (datum-based API matching TFT_eSPI) - void setTextColor(uint16_t fg, uint16_t bg); - void setTextFont(uint8_t font); - void setTextSize(uint8_t size); - void setTextDatum(uint8_t datum); - void drawString(const char* str, int32_t x, int32_t y); - int16_t textWidth(const char* str); - void setCursor(int32_t x, int32_t y); - void print(const char* str); - - // Escape hatch for direct Arduino_GFX access - Arduino_GFX* raw() { return _gfx; } - -private: - Arduino_GFX* _gfx = nullptr; - uint8_t _textDatum = TL_DATUM; - uint8_t _textSize = 1; - uint16_t _textFg = 0xFFFF; - uint16_t _textBg = 0x0000; -}; - -#else -#error "No display driver selected — check BoardConfig.h" -#endif diff --git a/DisplayDriverGFX.cpp b/DisplayDriverGFX.cpp deleted file mode 100644 index 7e0cf58..0000000 --- a/DisplayDriverGFX.cpp +++ /dev/null @@ -1,194 +0,0 @@ -// ═══════════════════════════════════════════════════════════════════ -// Arduino_GFX adapter implementation -// Only compiled when USE_ARDUINO_GFX is set (Waveshare path). -// For the TFT_eSPI path this file compiles to nothing. -// ═══════════════════════════════════════════════════════════════════ -#include "BoardConfig.h" -#include - -#if USE_ARDUINO_GFX - -#include "DisplayDriver.h" - -// ───────────────────────────────────────────────────────────────── -// Gfx adapter -// ───────────────────────────────────────────────────────────────── - -Gfx::Gfx() {} - -void Gfx::init() { - // Waveshare ESP32-S3 Touch LCD 4.3" — RGB parallel, ST7262 panel - Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel( - LCD_DE, LCD_VSYNC, LCD_HSYNC, LCD_PCLK, - LCD_R0, LCD_R1, LCD_R2, LCD_R3, LCD_R4, - LCD_G0, LCD_G1, LCD_G2, LCD_G3, LCD_G4, LCD_G5, - LCD_B0, LCD_B1, LCD_B2, LCD_B3, LCD_B4, - 1 /* hsync_polarity */, 10 /* hsync_front_porch */, - 8 /* hsync_pulse_width */, 50 /* hsync_back_porch */, - 1 /* vsync_polarity */, 10 /* vsync_front_porch */, - 8 /* vsync_pulse_width */, 20 /* vsync_back_porch */, - 1 /* pclk_active_neg */, - 16000000 /* prefer_speed = 16MHz PCLK */ - ); - - _gfx = new Arduino_RGB_Display( - SCREEN_WIDTH, SCREEN_HEIGHT, rgbpanel, - DISPLAY_ROTATION, true /* auto_flush */); - - if (!_gfx->begin()) { - Serial.println("[GFX] Display init FAILED"); - return; - } - Serial.printf("[GFX] Display OK: %dx%d\n", SCREEN_WIDTH, SCREEN_HEIGHT); - _gfx->fillScreen(0xF800); // RED TEST - delay(2000); - _gfx->fillScreen(0x07E0); // GREEN TEST - delay(2000); - _gfx->fillScreen(0x001F); // BLUE TEST - delay(2000); _gfx->fillScreen(0x07E0); // GREEN TEST delay(2000); _gfx->fillScreen(0x001F); // BLUE TEST delay(2000); -} - -void Gfx::setRotation(uint8_t r) { if (_gfx) _gfx->setRotation(r); } -void Gfx::fillScreen(uint16_t c) { if (_gfx) _gfx->fillScreen(c); } - -void Gfx::fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t c) { - if (_gfx) _gfx->fillRect(x, y, w, h, c); -} -void Gfx::fillRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, - int32_t r, uint16_t c) { - if (_gfx) _gfx->fillRoundRect(x, y, w, h, r, c); -} -void Gfx::drawRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, - int32_t r, uint16_t c) { - if (_gfx) _gfx->drawRoundRect(x, y, w, h, r, c); -} -void Gfx::drawFastVLine(int32_t x, int32_t y, int32_t h, uint16_t c) { - if (_gfx) _gfx->drawFastVLine(x, y, h, c); -} - -void Gfx::setTextColor(uint16_t fg, uint16_t bg) { - _textFg = fg; _textBg = bg; - if (_gfx) _gfx->setTextColor(fg, bg); -} - -void Gfx::setTextFont(uint8_t font) { - // TFT_eSPI font IDs don't map 1:1 to Arduino_GFX. - // Using default built-in font; setTextSize controls scale. - // TODO: Map to GFXfont pointers for better visual fidelity. - (void)font; -} - -void Gfx::setTextSize(uint8_t s) { - _textSize = s; - if (_gfx) _gfx->setTextSize(s); -} - -void Gfx::setTextDatum(uint8_t d) { _textDatum = d; } - -void Gfx::drawString(const char* str, int32_t x, int32_t y) { - if (!_gfx || !str) return; - - int16_t bx, by; - uint16_t tw, th; - _gfx->getTextBounds(str, 0, 0, &bx, &by, &tw, &th); - - // Horizontal alignment from datum - int hAlign = _textDatum % 3; - if (hAlign == 1) x -= (int32_t)tw / 2; // center - else if (hAlign == 2) x -= (int32_t)tw; // right - - // Vertical alignment from datum - int vAlign = _textDatum / 3; - if (vAlign == 1) y -= (int32_t)th / 2; // middle - else if (vAlign == 2) y -= (int32_t)th; // bottom - - _gfx->setCursor(x - bx, y - by); - _gfx->print(str); -} - -int16_t Gfx::textWidth(const char* str) { - if (!_gfx || !str) return 0; - int16_t bx, by; - uint16_t tw, th; - _gfx->getTextBounds(str, 0, 0, &bx, &by, &tw, &th); - return (int16_t)tw; -} - -void Gfx::setCursor(int32_t x, int32_t y) { - if (_gfx) _gfx->setCursor(x, y); -} - -void Gfx::print(const char* str) { - if (_gfx) _gfx->print(str); -} - -// ───────────────────────────────────────────────────────────────── -// GfxSprite adapter -// -// On the 800x480 RGB panel the LCD controller has its own GRAM, -// so direct drawing rarely tears. This implementation is -// intentionally minimal — it renders tiles as direct draws. -// -// TODO: For flicker-free tile updates, allocate an Arduino_Canvas -// backed by PSRAM and blit via draw16bitBeRGBBitmap(). -// ───────────────────────────────────────────────────────────────── - -GfxSprite::GfxSprite(Gfx* parent) : _parent(parent) {} - -void GfxSprite::createSprite(int16_t w, int16_t h) { - _w = w; _h = h; - Serial.printf("[GFX] Sprite %dx%d created (direct-draw mode)\n", w, h); -} - -void GfxSprite::deleteSprite() { _w = 0; _h = 0; } - -void GfxSprite::fillSprite(uint16_t color) { - if (_parent && _parent->raw()) - _parent->raw()->fillRect(_pushX, _pushY, _w, _h, color); -} - -void GfxSprite::fillRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, - int32_t r, uint16_t c) { - if (_parent && _parent->raw()) - _parent->raw()->fillRoundRect(_pushX + x, _pushY + y, w, h, r, c); -} - -void GfxSprite::drawRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, - int32_t r, uint16_t c) { - if (_parent && _parent->raw()) - _parent->raw()->drawRoundRect(_pushX + x, _pushY + y, w, h, r, c); -} - -void GfxSprite::setTextColor(uint16_t fg, uint16_t bg) { - _textFg = fg; _textBg = bg; - if (_parent && _parent->raw()) _parent->raw()->setTextColor(fg, bg); -} - -void GfxSprite::setTextFont(uint8_t font) { (void)font; } - -void GfxSprite::setTextSize(uint8_t size) { - _textSize = size; - if (_parent && _parent->raw()) _parent->raw()->setTextSize(size); -} - -void GfxSprite::setTextDatum(uint8_t datum) { _textDatum = datum; } - -void GfxSprite::drawString(const char* str, int32_t x, int32_t y) { - if (!_parent) return; - _parent->setTextDatum(_textDatum); - _parent->setTextSize(_textSize); - _parent->setTextColor(_textFg, _textBg); - _parent->drawString(str, _pushX + x, _pushY + y); -} - -void GfxSprite::pushSprite(int32_t x, int32_t y) { - // Record the offset for subsequent draw calls in the next cycle. - // NOTE: In the TFT_eSPI path, sprite drawing happens BEFORE pushSprite - // (draws into offscreen buffer, then blits). In this direct-draw stub, - // the offset from the PREVIOUS pushSprite call is used. After one full - // drawAll() cycle, all tiles render at the correct positions. - _pushX = x; - _pushY = y; -} - -#endif // USE_ARDUINO_GFX diff --git a/DisplayManager.cpp b/DisplayManager.cpp deleted file mode 100644 index ecd1f65..0000000 --- a/DisplayManager.cpp +++ /dev/null @@ -1,483 +0,0 @@ -#include "DisplayManager.h" -#include "Config.h" - -#if USE_TOUCH_GT911 -#include "TouchDriver.h" -#endif - -DisplayManager::DisplayManager() : _dash(_tft) { } - -void DisplayManager::begin() { - pinMode(PIN_LCD_BL, OUTPUT); - setBacklight(true); - _tft.init(); - _tft.setRotation(DISPLAY_ROTATION); - _tft.fillScreen(COL_BLACK); - -#if USE_TOUCH_XPT2046 - uint16_t calData[5] = { 300, 3600, 300, 3600, 1 }; - _tft.setTouch(calData); -#elif USE_TOUCH_GT911 - TouchDriver::begin(); -#endif -} - -void DisplayDriverGFX::setBacklight(bool on) { - // Cannot control after gfx->begin() — GPIO 8/9 are LCD data. - // Backlight is permanently ON, set during ch422gInit(). - (void)on; -} - -TouchEvent DisplayManager::readTouch() { - TouchEvent evt; - uint16_t x, y; - bool touched = false; - -#if USE_TOUCH_XPT2046 - touched = _tft.getTouch(&x, &y); -#elif USE_TOUCH_GT911 - touched = TouchDriver::read(x, y); -#endif - - if (touched) { - evt.pressed = true; - evt.x = x; - evt.y = y; - } - return evt; -} - -int DisplayManager::dashboardTouch(uint16_t x, uint16_t y) { - if (_lastScreen != ScreenID::DASHBOARD) return -1; - return _dash.handleTouch(x, y); -} - -// ===================================================================== -// Hold detection — charge up, then release to confirm -// ===================================================================== -HoldState DisplayManager::updateHold(unsigned long requiredMs) { - HoldState h; - h.targetMs = requiredMs; - - uint16_t tx, ty; - bool touching = false; - -#if USE_TOUCH_XPT2046 - touching = _tft.getTouch(&tx, &ty); -#elif USE_TOUCH_GT911 - touching = TouchDriver::read(tx, ty); -#endif - - if (touching) { - if (!_holdActive) { - _holdActive = true; - _holdCharged = false; - _holdStartMs = millis(); - _holdChargeMs = 0; - _holdX = tx; - _holdY = ty; - } - - h.active = true; - h.x = _holdX; - h.y = _holdY; - h.holdMs = millis() - _holdStartMs; - h.progress = min((float)h.holdMs / (float)requiredMs, 1.0f); - _holdProgress = h.progress; - - if (h.holdMs >= requiredMs) { - _holdCharged = true; - if (_holdChargeMs == 0) _holdChargeMs = millis(); - h.charged = true; - } - - } else { - if (_holdActive) { - if (_holdCharged) { - h.completed = true; - h.x = _holdX; - h.y = _holdY; - Serial.println("[HOLD] Charged + released -> completed!"); - } else { - h.cancelled = true; - Serial.println("[HOLD] Released early -> cancelled"); - } - } - _holdActive = false; - _holdCharged = false; - _holdProgress = 0.0f; - _holdChargeMs = 0; - } - - return h; -} - -float DisplayManager::holdProgress() const { - if (!_holdActive) return 0.0f; - return constrain((float)(millis() - _holdStartMs) / (float)HOLD_DURATION_MS, - 0.0f, 1.0f); -} - -// ===================================================================== -// Render -// ===================================================================== -void DisplayManager::render(const ScreenState& state) { - if (state.screen != _lastScreen) { - _needsFullRedraw = true; - if (state.screen == ScreenID::OFF) { - setBacklight(false); - } else if (_lastScreen == ScreenID::OFF) { - setBacklight(true); - } - _lastScreen = state.screen; - } - - switch (state.screen) { - case ScreenID::BOOT_SPLASH: - if (_needsFullRedraw) drawBootSplash(state); - break; - case ScreenID::WIFI_CONNECTING: - if (_needsFullRedraw) drawWifiConnecting(); - break; - case ScreenID::WIFI_CONNECTED: - if (_needsFullRedraw) drawWifiConnected(state); - break; - case ScreenID::WIFI_FAILED: - if (_needsFullRedraw) drawWifiFailed(); - break; - case ScreenID::ALERT: - if (_needsFullRedraw || state.blinkPhase != _lastBlink) { - drawAlertScreen(state); - _lastBlink = state.blinkPhase; - } - if (_holdProgress > 0.0f || _holdCharged) { - drawSilenceProgress(_holdProgress, _holdCharged); - } - break; - case ScreenID::STATUS: - if (_needsFullRedraw) drawStatusScreen(state); - break; - case ScreenID::DASHBOARD: - drawDashboard(state); - break; - case ScreenID::OFF: - if (_needsFullRedraw) { - _tft.fillScreen(COL_BLACK); - _dashSpriteReady = false; - } - break; - } - - _needsFullRedraw = false; -} - -// ===================================================================== -// Dashboard -// ===================================================================== -void DisplayManager::drawDashboard(const ScreenState& s) { - if (_needsFullRedraw) { - if (!_dashSpriteReady) { - _dash.begin(); - _dashSpriteReady = true; - } - _dash.drawAll(); - _dash.refreshFromState(s); - _lastDashRefresh = millis(); - } else if (millis() - _lastDashRefresh > 2000) { - _lastDashRefresh = millis(); - _dash.refreshFromState(s); - } -} - -// ===================================================================== -// Silence progress bar -// ===================================================================== -void DisplayManager::drawSilenceProgress(float progress, bool charged) { - const int barX = 20; - const int barY = SCREEN_HEIGHT - 50; - const int barW = SCREEN_WIDTH - 40; - const int barH = 26; - const int radius = 6; - - _tft.fillRoundRect(barX, barY, barW, barH, radius, COL_DARK_GRAY); - - if (charged) { - float breath = (sinf(millis() / 150.0f) + 1.0f) / 2.0f; - uint8_t gLo = 42, gHi = 63; - uint8_t g = gLo + (uint8_t)(breath * (float)(gHi - gLo)); - uint16_t pulseCol = (g << 5); - - _tft.fillRoundRect(barX, barY, barW, barH, radius, pulseCol); - _tft.drawRoundRect(barX, barY, barW, barH, radius, COL_WHITE); - - _tft.setTextDatum(MC_DATUM); - _tft.setTextFont(2); - _tft.setTextSize(1); - _tft.setTextColor(COL_WHITE, pulseCol); - _tft.drawString("RELEASE", barX + barW / 2, barY + barH / 2); - return; - } - - float eased = 1.0f - powf(1.0f - progress, 3.0f); - int fillW = max(1, (int)(eased * (float)barW)); - - uint8_t gMin = 16, gMax = 58; - for (int i = 0; i < fillW; i++) { - float frac = (float)i / (float)barW; - uint8_t g = gMin + (uint8_t)(frac * (float)(gMax - gMin)); - _tft.drawFastVLine(barX + i, barY + 1, barH - 2, (uint16_t)(g << 5)); - } - - _tft.drawRoundRect(barX, barY, barW, barH, radius, COL_WHITE); - - _tft.setTextDatum(MC_DATUM); - _tft.setTextFont(2); - _tft.setTextSize(1); - _tft.setTextColor(COL_WHITE, COL_DARK_GRAY); - _tft.drawString("HOLD", barX + barW / 2, barY + barH / 2); -} - -// ===================================================================== -// Helpers -// ===================================================================== -void DisplayManager::drawCentered(const char* txt, int y, int sz, uint16_t col) { - _tft.setTextFont(1); - _tft.setTextSize(sz); - _tft.setTextColor(col, COL_BLACK); - int w = _tft.textWidth(txt); - _tft.setCursor(max(0, (SCREEN_WIDTH - w) / 2), y); - _tft.print(txt); -} - -void DisplayManager::drawInfoLine(int x, int y, uint16_t col, const char* text) { - _tft.setTextFont(1); - _tft.setTextSize(1); - _tft.setTextColor(col, COL_BLACK); - _tft.setCursor(x, y); - _tft.print(text); -} - -void DisplayManager::drawHeaderBar(uint16_t col, const char* label, - const char* timeStr) { - _tft.setTextFont(1); - _tft.setTextSize(2); - _tft.setTextColor(col, COL_BLACK); - _tft.setCursor(8, 8); - _tft.print(label); - int tw = _tft.textWidth(timeStr); - _tft.setCursor(SCREEN_WIDTH - tw - 8, 8); - _tft.print(timeStr); -} - -// ===================================================================== -// Screens -// ===================================================================== -void DisplayManager::drawBootSplash(const ScreenState& s) { - _tft.fillScreen(COL_BLACK); - drawCentered("KLUBHAUS", 60, 4, COL_NEON_TEAL); - drawCentered("ALERT", 110, 4, COL_HOT_FUCHSIA); - - char verBuf[48]; - snprintf(verBuf, sizeof(verBuf), "v5.1 [%s]", BOARD_NAME); - drawCentered(verBuf, 180, 2, COL_DARK_GRAY); - - if (s.debugMode) { - drawCentered("DEBUG MODE", 210, 2, COL_YELLOW); - } -} - -void DisplayManager::drawWifiConnecting() { - _tft.fillScreen(COL_BLACK); - drawCentered("Connecting", 130, 3, COL_NEON_TEAL); - drawCentered("to WiFi...", 170, 3, COL_NEON_TEAL); -} - -void DisplayManager::drawWifiConnected(const ScreenState& s) { - _tft.fillScreen(COL_BLACK); - drawCentered("Connected!", 100, 3, COL_GREEN); - drawCentered(s.wifiSSID, 150, 2, COL_WHITE); - drawCentered(s.wifiIP, 180, 2, COL_WHITE); -} - -void DisplayManager::drawWifiFailed() { - _tft.fillScreen(COL_BLACK); - drawCentered("WiFi FAILED", 140, 3, COL_RED); -} - -void DisplayManager::drawAlertScreen(const ScreenState& s) { - uint16_t bg = s.blinkPhase ? COL_NEON_TEAL : COL_HOT_FUCHSIA; - uint16_t fg = s.blinkPhase ? COL_BLACK : COL_WHITE; - - _tft.fillScreen(bg); - drawHeaderBar(fg, "ALERT", s.timeString); - - int sz = 5; - int len = strlen(s.alertMessage); - if (len > 10) sz = 4; - if (len > 18) sz = 3; - if (len > 30) sz = 2; - - if (len > 12) { - String msg(s.alertMessage); - int mid = len / 2; - int sp = msg.lastIndexOf(' ', mid); - if (sp < 0) sp = mid; - String l1 = msg.substring(0, sp); - String l2 = msg.substring(sp + 1); - int lh = 8 * sz + 8; - int y1 = (SCREEN_HEIGHT - lh * 2) / 2; - drawCentered(l1.c_str(), y1, sz, fg); - drawCentered(l2.c_str(), y1 + lh, sz, fg); - } else { - drawCentered(s.alertMessage, (SCREEN_HEIGHT - 8 * sz) / 2, sz, fg); - } - - if (_holdProgress == 0.0f && !_holdCharged) { - drawCentered("HOLD TO SILENCE", SCREEN_HEIGHT - 25, 2, fg); - } -} - -void DisplayManager::drawStatusScreen(const ScreenState& s) { - _tft.fillScreen(COL_BLACK); - drawHeaderBar(COL_MINT, "KLUBHAUS", s.timeString); - drawCentered("MONITORING", 60, 3, COL_WHITE); - - char buf[80]; - int y = 110, sp = 22, x = 20; - - snprintf(buf, sizeof(buf), "WiFi: %s (%ddBm)", - s.wifiConnected ? s.wifiSSID : "DOWN", s.wifiRSSI); - drawInfoLine(x, y, COL_WHITE, buf); y += sp; - - snprintf(buf, sizeof(buf), "IP: %s", - s.wifiConnected ? s.wifiIP : "---"); - drawInfoLine(x, y, COL_WHITE, buf); y += sp; - - snprintf(buf, sizeof(buf), "Up: %lu min Heap: %lu KB", - s.uptimeMinutes, s.freeHeapKB); - drawInfoLine(x, y, COL_WHITE, buf); y += sp; - - snprintf(buf, sizeof(buf), "NTP: %s UTC", - s.ntpSynced ? s.timeString : "not synced"); - drawInfoLine(x, y, COL_WHITE, buf); y += sp; - - const char* stName = s.deviceState == DeviceState::SILENT ? "SILENT" : - s.deviceState == DeviceState::ALERTING ? "ALERTING" : - "WAKE"; - snprintf(buf, sizeof(buf), "State: %s Net: %s", - stName, s.networkOK ? "OK" : "FAIL"); - uint16_t stCol = s.deviceState == DeviceState::ALERTING ? COL_RED : - s.deviceState == DeviceState::SILENT ? COL_GREEN : - COL_NEON_TEAL; - drawInfoLine(x, y, stCol, buf); y += sp; - - if (s.alertHistoryCount > 0) { - drawInfoLine(x, y, COL_MINT, "Recent Alerts:"); - y += sp; - for (int i = 0; i < s.alertHistoryCount; i++) { - uint16_t col = (i == 0) ? COL_YELLOW : COL_DARK_GRAY; - snprintf(buf, sizeof(buf), "%s %.35s", - s.alertHistory[i].timestamp, - s.alertHistory[i].message); - drawInfoLine(x, y, col, buf); - y += sp; - } - } else { - drawInfoLine(x, y, COL_DARK_GRAY, "No alerts yet"); - y += sp; - } - - drawCentered("tap to dismiss", SCREEN_HEIGHT - 18, 1, COL_DARK_GRAY); -} - -// ===================================================================== -// Hint animation -// ===================================================================== - -void DisplayManager::startHintCycle() { - _hint = HintAnim{}; - _hint.lastPlayMs = millis(); -} - -void DisplayManager::stopHint() { - _hint.running = false; -} - -bool DisplayManager::updateHint() { - unsigned long now = millis(); - - if (_holdActive) { - _hint.running = false; - return false; - } - - if (!_hint.running) { - unsigned long gap = _hint.lastPlayMs == 0 - ? HintAnim::INITIAL_DELAY - : HintAnim::REPEAT_DELAY; - - if (now - _hint.lastPlayMs >= gap) { - _hint.running = true; - _hint.startMs = now; - } else { - return false; - } - } - - unsigned long elapsed = now - _hint.startMs; - - if (elapsed > _hint.totalDur()) { - _hint.running = false; - _hint.lastPlayMs = now; - drawSilenceProgress(0.0f, false); - return true; - } - - float progress = 0.0f; - - if (elapsed < HintAnim::FILL_DUR) { - float t = (float)elapsed / (float)HintAnim::FILL_DUR; - progress = HintAnim::PEAK * (t * t); - - } else if (elapsed < HintAnim::FILL_DUR + HintAnim::HOLD_DUR) { - progress = HintAnim::PEAK; - - } else { - float t = (float)(elapsed - HintAnim::FILL_DUR - HintAnim::HOLD_DUR) - / (float)HintAnim::DRAIN_DUR; - progress = HintAnim::PEAK * (1.0f - t * t); - } - - drawHintBar(progress); - return true; -} - -void DisplayManager::drawHintBar(float progress) { - const int barX = 20; - const int barY = SCREEN_HEIGHT - 50; - const int barW = SCREEN_WIDTH - 40; - const int barH = 26; - const int radius = 6; - - _tft.fillRoundRect(barX, barY, barW, barH, radius, COL_DARK_GRAY); - - if (progress > 0.001f) { - int fillW = max(1, (int)(progress * (float)barW)); - - for (int i = 0; i < fillW; i++) { - float frac = (float)i / (float)barW; - uint8_t g = 12 + (uint8_t)(frac * 18.0f); - uint8_t b = 8 + (uint8_t)(frac * 10.0f); - uint16_t col = (g << 5) | b; - _tft.drawFastVLine(barX + i, barY + 1, barH - 2, col); - } - } - - _tft.drawRoundRect(barX, barY, barW, barH, radius, COL_DARK_GRAY); - - _tft.setTextDatum(MC_DATUM); - _tft.setTextFont(2); - _tft.setTextSize(1); - _tft.setTextColor(COL_DARK_GRAY, COL_DARK_GRAY); - _tft.drawString("HOLD TO SILENCE", barX + barW / 2, barY + barH / 2); -} diff --git a/DisplayManager.h b/DisplayManager.h deleted file mode 100644 index e6bdaef..0000000 --- a/DisplayManager.h +++ /dev/null @@ -1,99 +0,0 @@ -#pragma once - -#include "DisplayDriver.h" -#include "ScreenData.h" -#include "Dashboard.h" - -// Hold gesture result -struct HoldState { - bool active = false; - bool charged = false; - bool completed = false; - bool cancelled = false; - uint16_t x = 0; - uint16_t y = 0; - unsigned long holdMs = 0; - unsigned long targetMs = 0; - float progress = 0.0f; -}; - -// Hint animation state -struct HintAnim { - bool running = false; - unsigned long startMs = 0; - unsigned long lastPlayMs = 0; - - static const unsigned long INITIAL_DELAY = 1500; - static const unsigned long FILL_DUR = 400; - static const unsigned long HOLD_DUR = 250; - static const unsigned long DRAIN_DUR = 500; - static const unsigned long REPEAT_DELAY = 5000; - - static constexpr float PEAK = 0.35f; - - unsigned long totalDur() const { return FILL_DUR + HOLD_DUR + DRAIN_DUR; } -}; - -class DisplayManager { -public: - DisplayManager(); - void begin(); - void render(const ScreenState& state); - void setBacklight(bool on); - TouchEvent readTouch(); - - int dashboardTouch(uint16_t x, uint16_t y); - HoldState updateHold(unsigned long requiredMs); - void startHintCycle(); - void stopHint(); - bool updateHint(); - float holdProgress() const; - -private: - HintAnim _hint; - void drawHintBar(float progress); - - Gfx _tft; - Dashboard _dash; - - ScreenID _lastScreen = ScreenID::BOOT_SPLASH; - bool _needsFullRedraw = true; - bool _lastBlink = false; - bool _dashSpriteReady = false; - unsigned long _lastDashRefresh = 0; - - // Hold tracking - bool _holdActive = false; - bool _holdCharged = false; - unsigned long _holdStartMs = 0; - unsigned long _holdChargeMs = 0; - uint16_t _holdX = 0; - uint16_t _holdY = 0; - float _holdProgress = 0.0f; - - // Colors - static constexpr uint16_t COL_NEON_TEAL = 0x07D7; - static constexpr uint16_t COL_HOT_FUCHSIA = 0xF81F; - static constexpr uint16_t COL_WHITE = 0xFFDF; - static constexpr uint16_t COL_BLACK = 0x0000; - static constexpr uint16_t COL_MINT = 0x67F5; - static constexpr uint16_t COL_DARK_GRAY = 0x2104; - static constexpr uint16_t COL_GREEN = 0x07E0; - static constexpr uint16_t COL_RED = 0xF800; - static constexpr uint16_t COL_YELLOW = 0xFFE0; - - // Screen renderers - void drawBootSplash(const ScreenState& s); - void drawWifiConnecting(); - void drawWifiConnected(const ScreenState& s); - void drawWifiFailed(); - void drawAlertScreen(const ScreenState& s); - void drawStatusScreen(const ScreenState& s); - void drawDashboard(const ScreenState& s); - void drawSilenceProgress(float progress, bool charged); - - // Helpers - void drawCentered(const char* txt, int y, int sz, uint16_t col); - void drawInfoLine(int x, int y, uint16_t col, const char* text); - void drawHeaderBar(uint16_t col, const char* label, const char* timeStr); -}; diff --git a/DoorbellLogic.cpp b/DoorbellLogic.cpp deleted file mode 100644 index 6ca2400..0000000 --- a/DoorbellLogic.cpp +++ /dev/null @@ -1,576 +0,0 @@ -#include "DoorbellLogic.h" - -// ===================================================================== -// Lifecycle -// ===================================================================== -void DoorbellLogic::begin() { - _bootTime = millis(); - _timeClient = new NTPClient(_ntpUDP, "pool.ntp.org", 0, NTP_SYNC_INTERVAL_MS); - - _screen.debugMode = DEBUG_MODE; - _screen.screen = ScreenID::BOOT_SPLASH; - updateScreenState(); -} - -void DoorbellLogic::beginWiFi() { - _instance = this; - - WiFi.mode(WIFI_STA); - WiFi.setSleep(false); - WiFi.setAutoReconnect(true); - WiFi.onEvent(onWiFiEvent); - - for (int i = 0; i < NUM_WIFI; i++) { - _wifiMulti.addAP(wifiNetworks[i].ssid, wifiNetworks[i].pass); - } - - _screen.screen = ScreenID::WIFI_CONNECTING; - updateScreenState(); -} - -void DoorbellLogic::connectWiFiBlocking() { - Serial.println("[WIFI] Connecting..."); - - int tries = 0; - while (_wifiMulti.run() != WL_CONNECTED && tries++ < 40) { - Serial.print("."); - delay(500); - } - Serial.println(); - - if (WiFi.isConnected()) { - Serial.printf("[WIFI] Connected: %s %s\n", - WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); - updateScreenState(); - _screen.screen = ScreenID::WIFI_CONNECTED; - } else { - Serial.println("[WIFI] FAILED — no networks reachable"); - _screen.screen = ScreenID::WIFI_FAILED; - } - updateScreenState(); -} - -void DoorbellLogic::finishBoot() { - if (WiFi.isConnected()) { - _timeClient->begin(); - Serial.println("[NTP] Starting sync..."); - - for (int i = 0; i < 5 && !_ntpSynced; i++) { - syncNTP(); - if (!_ntpSynced) delay(500); - } - - if (_ntpSynced) { - Serial.printf("[NTP] Synced: %s UTC\n", - _timeClient->getFormattedTime().c_str()); - } else { - Serial.println("[NTP] Initial sync failed — will retry in update()"); - } - - checkNetwork(); - - char bootMsg[80]; - snprintf(bootMsg, sizeof(bootMsg), "%s %s RSSI:%d", - WiFi.SSID().c_str(), - WiFi.localIP().toString().c_str(), - WiFi.RSSI()); - queueStatus("BOOTED", bootMsg); - flushStatus(); - } - - Serial.printf("[CONFIG] ALERT_URL: %s\n", ALERT_URL); - Serial.printf("[CONFIG] SILENCE_URL: %s\n", SILENCE_URL); - Serial.printf("[CONFIG] ADMIN_URL: %s\n", ADMIN_URL); - - transitionTo(DeviceState::SILENT); - Serial.printf("[BOOT] Grace period: %d ms\n", BOOT_GRACE_MS); -} - -// ===================================================================== -// Main Update Loop -// ===================================================================== -void DoorbellLogic::update() { - unsigned long now = millis(); - - if (_inBootGrace && (now - _bootTime >= BOOT_GRACE_MS)) { - _inBootGrace = false; - Serial.println("[BOOT] Grace period ended"); - } - - syncNTP(); - - if (!WiFi.isConnected()) { - if (_wifiMulti.run() == WL_CONNECTED) { - Serial.println("[WIFI] Reconnected"); - queueStatus("RECONNECTED", WiFi.SSID().c_str()); - } - } - - if (now - _lastPoll >= POLL_INTERVAL_MS) { - _lastPoll = now; - if (WiFi.isConnected() && _ntpSynced) { - pollTopics(); - } - } - - flushStatus(); - - switch (_state) { - case DeviceState::ALERTING: - if (now - _lastBlink >= BLINK_INTERVAL_MS) { - _lastBlink = now; - _blinkState = !_blinkState; - } - break; - - case DeviceState::WAKE: - if (now - _wakeStart > WAKE_DISPLAY_MS) { - transitionTo(DeviceState::SILENT); - } - break; - - case DeviceState::SILENT: - break; - } - - if (now - _lastHeartbeat >= HEARTBEAT_INTERVAL_MS) { - _lastHeartbeat = now; - uint32_t heap = ESP.getFreeHeap(); - Serial.printf("[%lus] %s | WiFi:%s RSSI:%d | heap:%dKB | minHeap:%dKB\n", - now / 1000, - _state == DeviceState::SILENT ? "SILENT" : - _state == DeviceState::ALERTING ? "ALERT" : "WAKE", - WiFi.isConnected() ? "OK" : "DOWN", - WiFi.RSSI(), - heap / 1024, - ESP.getMinFreeHeap() / 1024); - - if (heap < 20000) { - Serial.println("[HEAP] CRITICAL — rebooting!"); - queueStatus("REBOOTING", "low heap"); - flushStatus(); - delay(200); - ESP.restart(); - } - } - - updateScreenState(); -} - -// ===================================================================== -// Input Events -// ===================================================================== -void DoorbellLogic::onTouch(const TouchEvent& evt) { - if (!evt.pressed) return; - - static unsigned long lastAction = 0; - unsigned long now = millis(); - if (now - lastAction < TOUCH_DEBOUNCE_MS) return; - lastAction = now; - - Serial.printf("[TOUCH] x=%d y=%d state=%d\n", evt.x, evt.y, (int)_state); - - switch (_state) { - case DeviceState::ALERTING: - handleSilence("touch"); - break; - case DeviceState::SILENT: - transitionTo(DeviceState::WAKE); - break; - case DeviceState::WAKE: - transitionTo(DeviceState::SILENT); - break; - } -} - -void DoorbellLogic::onSerialCommand(const String& cmd) { - if (cmd == "CLEAR_DEDUP") { - _lastAlertId = _lastSilenceId = _lastAdminId = ""; - Serial.println("[CMD] Dedup cleared"); - } else if (cmd == "NET") { - checkNetwork(); - } else if (cmd == "STATUS") { - Serial.printf("[CMD] State:%s WiFi:%s RSSI:%d Heap:%dKB NTP:%s\n", - _state == DeviceState::SILENT ? "SILENT" : - _state == DeviceState::ALERTING ? "ALERT" : "WAKE", - WiFi.isConnected() ? WiFi.SSID().c_str() : "DOWN", - WiFi.RSSI(), - ESP.getFreeHeap() / 1024, - _ntpSynced ? _timeClient->getFormattedTime().c_str() : "no"); - } else if (cmd == "WAKE") { - transitionTo(DeviceState::WAKE); - } else if (cmd == "TEST") { - handleAlert("TEST ALERT"); - } else if (cmd == "REBOOT") { - Serial.println("[CMD] Rebooting..."); - queueStatus("REBOOTING", "serial"); - flushStatus(); - delay(200); - ESP.restart(); - } else { - Serial.printf("[CMD] Unknown: %s\n", cmd.c_str()); - } -} - -// ===================================================================== -// State Transitions -// ===================================================================== -void DoorbellLogic::transitionTo(DeviceState newState) { - _state = newState; - unsigned long now = millis(); - - switch (newState) { - case DeviceState::SILENT: - _screen.screen = ScreenID::OFF; - _alertMsgEpoch = 0; - Serial.println("-> SILENT"); - break; - case DeviceState::ALERTING: - _alertStart = now; - _lastBlink = now; - _blinkState = false; - _screen.screen = ScreenID::ALERT; - Serial.printf("-> ALERTING: %s\n", _currentMessage.c_str()); - break; - case DeviceState::WAKE: - _wakeStart = now; - _screen.screen = ScreenID::DASHBOARD; // ← CHANGED from STATUS - Serial.println("-> WAKE (dashboard)"); // ← CHANGED - break; - } -} - -// ===================================================================== -// Message Handlers -// ===================================================================== -void DoorbellLogic::handleAlert(const String& msg) { - if (_state == DeviceState::ALERTING && _currentMessage == msg) return; - _currentMessage = msg; - _alertMsgEpoch = _lastParsedMsgEpoch; - - for (int i = ALERT_HISTORY_SIZE - 1; i > 0; i--) { - _screen.alertHistory[i] = _screen.alertHistory[i - 1]; - } - strncpy(_screen.alertHistory[0].message, msg.c_str(), 63); - _screen.alertHistory[0].message[63] = '\0'; - strncpy(_screen.alertHistory[0].timestamp, - _ntpSynced ? _timeClient->getFormattedTime().c_str() : "??:??:??", 11); - _screen.alertHistory[0].timestamp[11] = '\0'; - - if (_screen.alertHistoryCount < ALERT_HISTORY_SIZE) - _screen.alertHistoryCount++; - - Serial.printf("[ALERT] Accepted. ntfy time=%ld history=%d\n", - (long)_alertMsgEpoch, _screen.alertHistoryCount); - transitionTo(DeviceState::ALERTING); - queueStatus("ALERTING", msg); -} - -void DoorbellLogic::handleSilence(const String& msg) { - if (_state != DeviceState::ALERTING) { - Serial.println("[SILENCE] Ignored — not alerting"); - return; - } - - if (_lastParsedMsgEpoch > 0 && _alertMsgEpoch > 0 && - _lastParsedMsgEpoch <= _alertMsgEpoch) { - Serial.printf("[SILENCE] Ignored — predates alert (silence:%ld <= alert:%ld)\n", - (long)_lastParsedMsgEpoch, (long)_alertMsgEpoch); - return; - } - - Serial.printf("[SILENCE] Accepted (silence:%ld > alert:%ld)\n", - (long)_lastParsedMsgEpoch, (long)_alertMsgEpoch); - _currentMessage = ""; - _alertMsgEpoch = 0; - transitionTo(DeviceState::SILENT); - queueStatus("SILENT", "silenced"); -} - -void DoorbellLogic::handleAdmin(const String& msg) { - Serial.printf("[ADMIN] %s\n", msg.c_str()); - - if (msg == "SILENCE") handleSilence("admin"); - else if (msg == "PING") queueStatus("PONG", "ping"); - else if (msg == "test") handleAlert("TEST ALERT"); - else if (msg == "status") { - char buf[128]; - snprintf(buf, sizeof(buf), "State:%s WiFi:%s RSSI:%d Heap:%dKB", - _state == DeviceState::SILENT ? "SILENT" : - _state == DeviceState::ALERTING ? "ALERT" : "WAKE", - WiFi.SSID().c_str(), WiFi.RSSI(), ESP.getFreeHeap() / 1024); - queueStatus("STATUS", buf); - } - else if (msg == "wake") { - transitionTo(DeviceState::WAKE); - } - else if (msg == "REBOOT") { - queueStatus("REBOOTING", "admin"); - flushStatus(); - delay(200); - ESP.restart(); - } -} - -// ===================================================================== -// Screen State Sync -// ===================================================================== -void DoorbellLogic::updateScreenState() { - _screen.deviceState = _state; - _screen.blinkPhase = _blinkState; - strncpy(_screen.alertMessage, _currentMessage.c_str(), sizeof(_screen.alertMessage) - 1); - _screen.alertMessage[sizeof(_screen.alertMessage) - 1] = '\0'; - - _screen.wifiConnected = WiFi.isConnected(); - if (_screen.wifiConnected) { - strncpy(_screen.wifiSSID, WiFi.SSID().c_str(), sizeof(_screen.wifiSSID) - 1); - _screen.wifiSSID[sizeof(_screen.wifiSSID) - 1] = '\0'; - strncpy(_screen.wifiIP, WiFi.localIP().toString().c_str(), sizeof(_screen.wifiIP) - 1); - _screen.wifiIP[sizeof(_screen.wifiIP) - 1] = '\0'; - _screen.wifiRSSI = WiFi.RSSI(); - } - - _screen.ntpSynced = _ntpSynced; - if (_ntpSynced) { - strncpy(_screen.timeString, _timeClient->getFormattedTime().c_str(), - sizeof(_screen.timeString) - 1); - _screen.timeString[sizeof(_screen.timeString) - 1] = '\0'; - } - - _screen.uptimeMinutes = (millis() - _bootTime) / 60000; - _screen.freeHeapKB = ESP.getFreeHeap() / 1024; - _screen.networkOK = _networkOK; -} - -// ===================================================================== -// WiFi & Network -// ===================================================================== -void DoorbellLogic::syncNTP() { - if (_timeClient->update()) { - _ntpSynced = true; - _lastEpoch = _timeClient->getEpochTime(); - } -} - -void DoorbellLogic::checkNetwork() { - Serial.printf("[NET] WiFi:%s RSSI:%d IP:%s\n", - WiFi.SSID().c_str(), WiFi.RSSI(), WiFi.localIP().toString().c_str()); - - IPAddress ip; - if (!WiFi.hostByName("ntfy.sh", ip)) { - Serial.println("[NET] DNS FAILED"); - _networkOK = false; - return; - } - Serial.printf("[NET] DNS OK: %s\n", ip.toString().c_str()); - - WiFiClientSecure tls; - tls.setInsecure(); - if (tls.connect("ntfy.sh", 443, 15000)) { - Serial.println("[NET] TLS OK"); - tls.stop(); - _networkOK = true; - } else { - Serial.println("[NET] TLS FAILED"); - _networkOK = false; - } -} - -// ===================================================================== -// ntfy Polling -// ===================================================================== -void DoorbellLogic::pollTopics() { - Serial.printf("[POLL] Starting poll cycle... WiFi:%s NTP:%d Grace:%d\n", - WiFi.isConnected() ? "OK" : "DOWN", _ntpSynced, _inBootGrace); - - pollTopic(ALERT_URL, &DoorbellLogic::handleAlert, "ALERT", _lastAlertId); - yield(); - pollTopic(SILENCE_URL, &DoorbellLogic::handleSilence, "SILENCE", _lastSilenceId); - yield(); - pollTopic(ADMIN_URL, &DoorbellLogic::handleAdmin, "ADMIN", _lastAdminId); - - Serial.printf("[POLL] Done. Heap: %dKB\n", ESP.getFreeHeap() / 1024); -} - -void DoorbellLogic::pollTopic(const char* url, - void (DoorbellLogic::*handler)(const String&), - const char* name, String& lastId) { - Serial.printf("[%s] Polling: %s\n", name, url); - - if (!WiFi.isConnected()) { - Serial.printf("[%s] SKIPPED — WiFi down\n", name); - return; - } - - WiFiClientSecure client; - client.setInsecure(); - client.setTimeout(10); - - HTTPClient http; - http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); - http.setTimeout(10000); - http.setReuse(false); - - if (!http.begin(client, url)) { - Serial.printf("[%s] begin() FAILED\n", name); - return; - } - - int code = http.GET(); - Serial.printf("[%s] HTTP %d\n", name, code); - - if (code == HTTP_CODE_OK) { - String response = http.getString(); - Serial.printf("[%s] %d bytes\n", name, response.length()); - if (response.length() > 0) { - parseMessages(response, name, handler, lastId); - } else { - Serial.printf("[%s] Empty response\n", name); - } - } else if (code < 0) { - Serial.printf("[%s] ERROR: %s\n", name, http.errorToString(code).c_str()); - _networkOK = false; - } else { - Serial.printf("[%s] Unexpected code: %d\n", name, code); - } - - http.end(); - client.stop(); - yield(); -} - -void DoorbellLogic::parseMessages(String& response, const char* name, - void (DoorbellLogic::*handler)(const String&), - String& lastId) { - Serial.printf("[%s] parseMessages: grace=%d ntp=%d epoch=%ld\n", - name, _inBootGrace, _ntpSynced, (long)_lastEpoch); - - if (_inBootGrace || !_ntpSynced || _lastEpoch == 0) { - Serial.printf("[%s] SKIPPED — guard failed\n", name); - return; - } - - int lineStart = 0; - int msgCount = 0; - while (lineStart < (int)response.length()) { - int lineEnd = response.indexOf('\n', lineStart); - if (lineEnd == -1) lineEnd = response.length(); - - String line = response.substring(lineStart, lineEnd); - line.trim(); - - if (line.length() > 0 && line.indexOf('{') >= 0) { - msgCount++; - JsonDocument doc; - if (deserializeJson(doc, line)) { - Serial.printf("[%s] JSON parse FAILED on line %d\n", name, msgCount); - lineStart = lineEnd + 1; - continue; - } - - const char* event = doc["event"]; - const char* msgId = doc["id"]; - const char* message = doc["message"]; - time_t msgTime = doc["time"] | 0; - - Serial.printf("[%s] msg#%d: event=%s id=%s time=%ld msg=%.30s\n", - name, msgCount, - event ? event : "null", - msgId ? msgId : "null", - (long)msgTime, - message ? message : "null"); - - if (event && strcmp(event, "message") != 0) { - Serial.printf("[%s] SKIP — not a message event (event=%s)\n", name, event); - lineStart = lineEnd + 1; - continue; - } - - if (!message || strlen(message) == 0) { - Serial.printf("[%s] SKIP — empty message\n", name); - lineStart = lineEnd + 1; - continue; - } - - String idStr = msgId ? String(msgId) : ""; - if (idStr.length() > 0 && idStr == lastId) { - Serial.printf("[%s] SKIP — dedup (id=%s)\n", name, msgId); - lineStart = lineEnd + 1; - continue; - } - - if (msgTime > 0 && (_lastEpoch - msgTime) > (time_t)STALE_MSG_THRESHOLD_S) { - Serial.printf("[%s] SKIP — stale (age=%llds, threshold=%ds)\n", - name, (long long)(_lastEpoch - msgTime), STALE_MSG_THRESHOLD_S); - lineStart = lineEnd + 1; - continue; - } - - Serial.printf("[%s] ACCEPTED: %.50s\n", name, message); - if (idStr.length() > 0) lastId = idStr; - - _lastParsedMsgEpoch = msgTime; - (this->*handler)(String(message)); - _lastParsedMsgEpoch = 0; - } - lineStart = lineEnd + 1; - } - - Serial.printf("[%s] Parsed %d JSON objects\n", name, msgCount); -} - -// ===================================================================== -// Status Publishing -// ===================================================================== -void DoorbellLogic::queueStatus(const char* st, const String& msg) { - _pendingStatus = true; - _pendStatusState = st; - _pendStatusMsg = msg; - Serial.printf("[STATUS] Queued: %s — %s\n", st, msg.c_str()); -} - -void DoorbellLogic::flushStatus() { - if (!_pendingStatus || !WiFi.isConnected()) return; - _pendingStatus = false; - - JsonDocument doc; - doc["state"] = _pendStatusState; - doc["message"] = _pendStatusMsg; - doc["timestamp"] = _ntpSynced ? (long long)_timeClient->getEpochTime() * 1000LL : 0LL; - - String payload; - serializeJson(doc, payload); - - WiFiClientSecure client; - client.setInsecure(); - - HTTPClient http; - http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); - http.begin(client, STATUS_URL); - http.addHeader("Content-Type", "application/json"); - int code = http.POST(payload); - http.end(); - - Serial.printf("[STATUS] Sent (%d): %s\n", code, _pendStatusState.c_str()); -} - -DoorbellLogic* DoorbellLogic::_instance = nullptr; - -void DoorbellLogic::onWiFiEvent(WiFiEvent_t event) { - if (!_instance) return; - switch (event) { - case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: - Serial.println("[WIFI] Disconnected — will reconnect"); - WiFi.reconnect(); - break; - case ARDUINO_EVENT_WIFI_STA_CONNECTED: - Serial.println("[WIFI] Reconnected to AP"); - break; - case ARDUINO_EVENT_WIFI_STA_GOT_IP: - Serial.printf("[WIFI] Got IP: %s\n", WiFi.localIP().toString().c_str()); - break; - default: - break; - } -} - diff --git a/DoorbellLogic.h b/DoorbellLogic.h deleted file mode 100644 index c13abd1..0000000 --- a/DoorbellLogic.h +++ /dev/null @@ -1,95 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include "Config.h" -#include "ScreenData.h" - -class DoorbellLogic { -public: - void begin(); - - // Boot sequence (called individually so .ino can render between steps) - void beginWiFi(); - void connectWiFiBlocking(); - void finishBoot(); - - void update(); - - const ScreenState& getScreenState() const { return _screen; } - - // Input events from the outside - void onTouch(const TouchEvent& evt); - void onSerialCommand(const String& cmd); - -private: - ScreenState _screen; - - // State - DeviceState _state = DeviceState::SILENT; - String _currentMessage = ""; - unsigned long _bootTime = 0; - bool _inBootGrace = true; - bool _networkOK = false; - - // Dedup - String _lastAlertId; - String _lastSilenceId; - String _lastAdminId; - - // Timing - unsigned long _lastPoll = 0; - unsigned long _lastBlink = 0; - unsigned long _alertStart = 0; - unsigned long _wakeStart = 0; - unsigned long _lastHeartbeat = 0; - bool _blinkState = false; - - // Stale silence protection - time_t _alertMsgEpoch = 0; // ntfy timestamp of the alert that started ALERTING - time_t _lastParsedMsgEpoch = 0; // ntfy timestamp of message currently being handled - - - // Deferred status publish - bool _pendingStatus = false; - String _pendStatusState; - String _pendStatusMsg; - - // NTP — pointer because NTPClient has no default constructor - WiFiUDP _ntpUDP; - NTPClient* _timeClient = nullptr; - bool _ntpSynced = false; - time_t _lastEpoch = 0; - - // WiFi - WiFiMulti _wifiMulti; - - // Methods - void checkNetwork(); - void syncNTP(); - - void pollTopics(); - void pollTopic(const char* url, void (DoorbellLogic::*handler)(const String&), - const char* name, String& lastId); - void parseMessages(String& response, const char* name, - void (DoorbellLogic::*handler)(const String&), - String& lastId); - - void handleAlert(const String& msg); - void handleSilence(const String& msg); - void handleAdmin(const String& msg); - - void transitionTo(DeviceState newState); - void queueStatus(const char* state, const String& msg); - void flushStatus(); - - void updateScreenState(); - static DoorbellLogic* _instance; // for static event callback - static void onWiFiEvent(WiFiEvent_t event); - -}; - diff --git a/ScreenData.h b/ScreenData.h deleted file mode 100644 index 16e145e..0000000 --- a/ScreenData.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once -#include - -enum class DeviceState : uint8_t { - SILENT, - ALERTING, - WAKE -}; - -enum class ScreenID : uint8_t { - BOOT_SPLASH, - WIFI_CONNECTING, - WIFI_CONNECTED, - WIFI_FAILED, - ALERT, - STATUS, - DASHBOARD, - OFF -}; - -#define ALERT_HISTORY_SIZE 3 - -struct AlertRecord { - char message[64] = ""; - char timestamp[12] = ""; -}; - -struct ScreenState { - ScreenID screen = ScreenID::BOOT_SPLASH; - DeviceState deviceState = DeviceState::SILENT; - bool blinkPhase = false; - - char alertMessage[64] = ""; - - bool wifiConnected = false; - char wifiSSID[33] = ""; - int wifiRSSI = 0; - char wifiIP[16] = ""; - - bool ntpSynced = false; - char timeString[12] = ""; - - uint32_t uptimeMinutes = 0; - uint32_t freeHeapKB = 0; - bool networkOK = false; - - bool debugMode = false; - - AlertRecord alertHistory[ALERT_HISTORY_SIZE] = {}; - int alertHistoryCount = 0; -}; - -struct TouchEvent { - bool pressed = false; - uint16_t x = 0; - uint16_t y = 0; -}; - diff --git a/TouchDriver.h b/TouchDriver.h deleted file mode 100644 index 0633cdb..0000000 --- a/TouchDriver.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once -#include "BoardConfig.h" - -// ═══════════════════════════════════════════════════════════════════ -// Touch driver abstraction -// -// XPT2046: integrated in TFT_eSPI — DisplayManager calls -// _tft.getTouch() / _tft.setTouch() directly inside -// #if USE_TOUCH_XPT2046 blocks. -// -// GT911: separate I2C controller — namespace below. -// ═══════════════════════════════════════════════════════════════════ - -#if USE_TOUCH_GT911 - -namespace TouchDriver { - void begin(); - bool read(uint16_t &x, uint16_t &y); -} - -#endif diff --git a/TouchDriverGT911.cpp b/TouchDriverGT911.cpp deleted file mode 100644 index fe4630a..0000000 --- a/TouchDriverGT911.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "BoardConfig.h" -#include - -#if USE_TOUCH_GT911 - -#include "TouchDriver.h" -#include - -// ═══════════════════════════════════════════════════════════════════ -// GT911 capacitive touch — Waveshare ESP32-S3 Touch LCD 4.3" -// -// This is a compilable stub. To enable actual touch: -// 1. arduino-cli lib install "TAMC_GT911" -// 2. Uncomment the TAMC_GT911 lines below. -// ═══════════════════════════════════════════════════════════════════ - -// #include -// static TAMC_GT911 ts(TOUCH_SDA, TOUCH_SCL, TOUCH_INT, TOUCH_RST, -// SCREEN_WIDTH, SCREEN_HEIGHT); - -namespace TouchDriver { - -void begin() { - Wire.begin(TOUCH_SDA, TOUCH_SCL); - // ts.begin(); - // ts.setRotation(TOUCH_MAP_ROTATION); - Serial.println("[TOUCH] GT911 stub initialized"); -} - -bool read(uint16_t &x, uint16_t &y) { - // ts.read(); - // if (ts.isTouched) { - // x = ts.points[0].x; - // y = ts.points[0].y; - // return true; - // } - (void)x; (void)y; - return false; -} - -} // namespace TouchDriver - -#endif // USE_TOUCH_GT911 diff --git a/boards/board_e32r35t.h b/boards/board_e32r35t.h deleted file mode 100644 index e8c95c7..0000000 --- a/boards/board_e32r35t.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once -// ═══════════════════════════════════════════════════════════════════ -// Board: E32R35T — ESP32-WROOM-32E + 3.5" ST7796S SPI + XPT2046 -// ═══════════════════════════════════════════════════════════════════ - -#define BOARD_NAME "E32R35T" - -// ── Display ───────────────────────────────────────────────────── -#define SCREEN_WIDTH 480 -#define SCREEN_HEIGHT 320 -#define DISPLAY_ROTATION 1 - -// ── Driver selection ──────────────────────────────────────────── -#define USE_TFT_ESPI 1 -#define USE_ARDUINO_GFX 0 -#define USE_TOUCH_XPT2046 1 -#define USE_TOUCH_GT911 0 - -// ── Hardware capabilities ─────────────────────────────────────── -#define HAS_PSRAM 0 - -// ── LCD (HSPI) ────────────────────────────────────────────────── -#define PIN_LCD_CS 15 -#define PIN_LCD_DC 2 -#define PIN_LCD_MOSI 13 -#define PIN_LCD_SCLK 14 -#define PIN_LCD_MISO 12 -#define PIN_LCD_BL 27 - -// ── Touch (XPT2046, shares HSPI) ─────────────────────────────── -#define PIN_TOUCH_CS 33 -#define PIN_TOUCH_IRQ 36 - -// ── SD Card (VSPI — for future use) ──────────────────────────── -#define PIN_SD_CS 5 -#define PIN_SD_MOSI 23 -#define PIN_SD_SCLK 18 -#define PIN_SD_MISO 19 - -// ── RGB LED (active low) ─────────────────────────────────────── -#define PIN_LED_RED 22 -#define PIN_LED_GREEN 16 -#define PIN_LED_BLUE 17 - -// ── Audio ─────────────────────────────────────────────────────── -#define PIN_AUDIO_EN 4 -#define PIN_AUDIO_DAC 26 - -// ── Battery ADC ───────────────────────────────────────────────── -#define PIN_BAT_ADC 34 diff --git a/boards/board_waveshare_s3.h b/boards/board_waveshare_s3.h deleted file mode 100644 index e76998b..0000000 --- a/boards/board_waveshare_s3.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once -// ═══════════════════════════════════════════════════════════════════ -// Board: Waveshare ESP32-S3 Touch LCD 4.3" -// 800x480 RGB parallel + GT911 capacitive touch -// -// NOTE: Pin assignments are typical for this board revision. -// Verify against your specific board's schematic. -// The Arduino board variant 'waveshare_esp32_s3_touch_lcd_43' -// may override some of these via its pins_arduino.h. -// ═══════════════════════════════════════════════════════════════════ - -#define BOARD_NAME "WS_S3_43" - -// ── Display ───────────────────────────────────────────────────── -#define SCREEN_WIDTH 800 -#define SCREEN_HEIGHT 480 -#define DISPLAY_ROTATION 0 // native landscape - -// ── Driver selection ──────────────────────────────────────────── -#define USE_TFT_ESPI 0 -#define USE_ARDUINO_GFX 1 -#define USE_TOUCH_XPT2046 0 -#define USE_TOUCH_GT911 1 - -// ── Hardware capabilities ─────────────────────────────────────── -#define HAS_PSRAM 1 - -// ── Backlight ─────────────────────────────────────────────────── -#define PIN_LCD_BL 2 - -// ── GT911 I2C touch controller ────────────────────────────────── -#define TOUCH_SDA 17 -#define TOUCH_SCL 18 -#define TOUCH_INT -1 -#define TOUCH_RST 38 - -// ── RGB LCD data pins (ESP32-S3 LCD_CAM peripheral) ───────────── -// Adjust if your board revision differs -#define LCD_DE 40 -#define LCD_VSYNC 41 -#define LCD_HSYNC 39 -#define LCD_PCLK 42 -#define LCD_R0 45 -#define LCD_R1 48 -#define LCD_R2 47 -#define LCD_R3 21 -#define LCD_R4 14 -#define LCD_G0 5 -#define LCD_G1 6 -#define LCD_G2 7 -#define LCD_G3 15 -#define LCD_G4 16 -#define LCD_G5 4 -#define LCD_B0 8 -#define LCD_B1 3 -#define LCD_B2 46 -#define LCD_B3 9 -#define LCD_B4 1 - -// ── Peripherals not present on this board ─────────────────────── -// These are left undefined intentionally. Code that uses them -// should guard with #ifdef PIN_LED_RED etc. -// Uncomment and set values if your carrier board adds them. -// -// #define PIN_LED_RED -1 -// #define PIN_LED_GREEN -1 -// #define PIN_LED_BLUE -1 -// #define PIN_AUDIO_EN -1 -// #define PIN_AUDIO_DAC -1 -// #define PIN_BAT_ADC -1 -// #define PIN_SD_CS -1 -// #define PIN_TOUCH_CS -1 -// #define PIN_TOUCH_IRQ -1 diff --git a/doorbell-touch.ino b/doorbell-touch.ino deleted file mode 100644 index 6b107f9..0000000 --- a/doorbell-touch.ino +++ /dev/null @@ -1,137 +0,0 @@ -/* - * KLUBHAUS ALERT v5.1 — " BOARD_NAME " Edition - * - * Target: LCDWiki E32R35T (ESP32-WROOM-32E + 3.5" ST7796S + XPT2046) - * - * Hold-and-release interaction model: - * - Hold finger → progress bar fills - * - Bar full → jitter/flash ("RELEASE!") - * - Lift finger → action fires (finger already off screen) - */ - -#include -#include "Config.h" -#include "DisplayManager.h" -#include "DoorbellLogic.h" -#include "BoardConfig.h" - -#include -#ifndef LOAD_GLCD - #error "LOAD_GLCD is NOT defined — fonts missing!" -#endif -#if USE_TFT_ESPI - #ifndef ST7796_DRIVER - #if USE_TFT_ESPI -#error "TFT_eSPI setup mismatch — ST7796_DRIVER expected for E32R35T" -#endif - #endif -#endif -#define HOLD_TO_SILENCE_MS 1000 - -DoorbellLogic logic; -DisplayManager display; - -void setup() { - Serial.begin(115200); - unsigned long t = millis(); - while (!Serial && millis() - t < 3000) delay(10); - delay(500); - - Serial.println("\n========================================"); - Serial.println(" KLUBHAUS ALERT v5.1 — " BOARD_NAME ""); -#if DEBUG_MODE - Serial.println(" *** DEBUG MODE — _test topics ***"); -#endif - Serial.println("========================================"); - - display.begin(); - - logic.begin(); - display.render(logic.getScreenState()); - delay(1500); - - logic.beginWiFi(); - display.render(logic.getScreenState()); - - logic.connectWiFiBlocking(); - display.render(logic.getScreenState()); - delay(1500); - - logic.finishBoot(); - display.setBacklight(false); - - Serial.printf("[MEM] Free heap: %d | Free PSRAM: %d\n", - ESP.getFreeHeap(), ESP.getFreePsram()); - Serial.println("[BOOT] Ready — monitoring ntfy.sh\n"); -} - - - -// ── Silence handler (delegates to DoorbellLogic) ──────────────── -void silenceAlerts() { - Serial.println("[SILENCE] User completed hold-to-silence gesture"); - logic.onTouch(TouchEvent{true, 0, 0}); -} - -void loop() { - logic.update(); - display.render(logic.getScreenState()); - - // Touch → hold-to-silence gesture - TouchEvent evt = display.readTouch(); - if (evt.pressed) { - // Dashboard tile tap - if (logic.getScreenState().screen == ScreenID::DASHBOARD) { - int tile = display.dashboardTouch(evt.x, evt.y); - if (tile >= 0) Serial.printf("[DASH] Tile %d tapped\n", tile); - } - } - - // Hold-to-silence during ALERT - if (logic.getScreenState().deviceState == DeviceState::ALERTING) { - HoldState h = display.updateHold(HOLD_TO_SILENCE_MS); - if (h.completed) silenceAlerts(); - - // Hint animation when not touching - if (!h.active) display.updateHint(); - } - - // Serial commands - if (Serial.available()) { - String cmd = Serial.readStringUntil('\n'); - cmd.trim(); - if (cmd.length() > 0) logic.onSerialCommand(cmd); - } -} - -void loop() { - logic.update(); - display.render(logic.getScreenState()); - - // Touch → hold-to-silence gesture - TouchEvent evt = display.readTouch(); - if (evt.pressed) { - // Dashboard tile tap - if (logic.getScreenState().screen == ScreenID::DASHBOARD) { - int tile = display.dashboardTouch(evt.x, evt.y); - if (tile >= 0) Serial.printf("[DASH] Tile %d tapped\n", tile); - } - } - - // Hold-to-silence during ALERT - if (logic.getScreenState().deviceState == DeviceState::ALERTING) { - HoldState h = display.updateHold(HOLD_TO_SILENCE_MS); - if (h.completed) silenceAlerts(); - - // Hint animation when not touching - if (!h.active) display.updateHint(); - } - - // Serial commands - if (Serial.available()) { - String cmd = Serial.readStringUntil('\n'); - cmd.trim(); - if (cmd.length() > 0) logic.onSerialCommand(cmd); - } -} -