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); - } -} -