diff --git a/.gitmodules b/.gitmodules index d0c00cf..37763f4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "TFT_eSPI"] path = TFT_eSPI url = https://github.com/Cincinnatu/TFT_eSPI -[submodule "sketches/doorbell/TFT_eSPI"] - path = sketches/doorbell/TFT_eSPI - url = https://github.com/Cincinnatu/TFT_eSPI.git diff --git a/sketches/debug/debug.ino b/sketches/debug/debug.ino deleted file mode 100644 index f7f38d6..0000000 --- a/sketches/debug/debug.ino +++ /dev/null @@ -1,15 +0,0 @@ -void setup() { - Serial.begin(115200); - delay(3000); // Long delay to catch startup - - Serial.println("=== ESP32-C6 Serial Test ==="); - Serial.print("Chip Model: "); - Serial.println(ESP.getChipModel()); - Serial.print("CPU Freq: "); - Serial.println(ESP.getCpuFreqMHz()); -} - -void loop() { - Serial.println("Alive: " + String(millis())); - delay(1000); -} diff --git a/sketches/debug/sketch.yaml b/sketches/debug/sketch.yaml deleted file mode 100644 index 6c2f27e..0000000 --- a/sketches/debug/sketch.yaml +++ /dev/null @@ -1,3 +0,0 @@ -default_fqbn: esp32:esp32:esp32c6 -default_programmer: esptool -default_port: /dev/ttyACM0 diff --git a/sketches/doorbell-touch-esp32-32e/doorbell-touch-esp32-32e.ino b/sketches/doorbell-touch-esp32-32e/doorbell-touch-esp32-32e.ino deleted file mode 100644 index 6b107f9..0000000 --- a/sketches/doorbell-touch-esp32-32e/doorbell-touch-esp32-32e.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); - } -} - diff --git a/sketches/doorbell-touch-esp32-32e/mise.toml b/sketches/doorbell-touch-esp32-32e/mise.toml deleted file mode 100644 index 6d1254e..0000000 --- a/sketches/doorbell-touch-esp32-32e/mise.toml +++ /dev/null @@ -1,115 +0,0 @@ -# ═══════════════════════════════════════════════════════════ -# Klubhaus Doorbell — Multi-Target Build Harness -# ═══════════════════════════════════════════════════════════ - -[tasks.install-libs-shared] -description = "Install shared (platform-independent) libraries" -run = """ -arduino-cli lib install "ArduinoJson@7.4.1" -arduino-cli lib install "NTPClient@3.2.1" -echo "[OK] Shared libraries installed" -""" - -[tasks.install-libs-32e] -description = "Vendor TFT_eSPI into vendor/esp32-32e" -run = """ -#!/usr/bin/env bash -set -euo pipefail -if [ ! -d "vendor/esp32-32e/TFT_eSPI" ]; then - echo "Cloning TFT_eSPI..." - git clone --depth 1 --branch V2.5.43 \ - https://github.com/Bodmer/TFT_eSPI.git \ - vendor/esp32-32e/TFT_eSPI -fi -echo "Copying board-specific User_Setup.h..." -cp boards/esp32-32e/tft_user_setup.h vendor/esp32-32e/TFT_eSPI/User_Setup.h -echo "[OK] TFT_eSPI 2.5.43 vendored + configured" -""" - -[tasks.install-libs-s3-43] -description = "Vendor Arduino_GFX into vendor/esp32-s3-lcd-43" -run = """ -#!/usr/bin/env bash -set -euo pipefail -if [ ! -d "vendor/esp32-s3-lcd-43/GFX_Library_for_Arduino" ]; then - echo "Cloning Arduino_GFX..." - git clone --depth 1 --branch v1.6.5 \ - https://github.com/moononournation/Arduino_GFX.git \ - vendor/esp32-s3-lcd-43/GFX_Library_for_Arduino -fi -echo "[OK] Arduino_GFX 1.6.5 vendored" -""" - -[tasks.install-libs] -description = "Install all libraries (shared + vendored)" -depends = ["install-libs-shared", "install-libs-32e", "install-libs-s3-43"] - -# ── ESP32-32E ──────────────────────────────────────────── - -[tasks.compile-32e] -description = "Compile ESP32-32E sketch" -depends = ["install-libs"] -run = """ -arduino-cli compile \ - --fqbn "esp32:esp32:esp32:FlashSize=4M,PartitionScheme=default" \ - --libraries ./libraries \ - --libraries ./vendor/esp32-32e \ - --build-property "compiler.cpp.extra_flags=-DDEBUG_MODE" \ - --warnings default \ - ./boards/esp32-32e -""" - -[tasks.upload-32e] -description = "Upload to ESP32-32E" -run = """ -arduino-cli upload \ - --fqbn "esp32:esp32:esp32:FlashSize=4M,PartitionScheme=default" \ - --port "${PORT:-/dev/ttyUSB0}" \ - ./boards/esp32-32e -""" - -[tasks.monitor-32e] -description = "Serial monitor for ESP32-32E" -run = """ -arduino-cli monitor --port "${PORT:-/dev/ttyUSB0}" --config baudrate=115200 -""" - -# ── ESP32-S3-LCD-4.3 ──────────────────────────────────── - -[tasks.compile-s3-43] -description = "Compile ESP32-S3-LCD-4.3 sketch" -depends = ["install-libs"] -run = """ -arduino-cli compile \ - --fqbn "esp32:esp32:waveshare_esp32_s3_touch_lcd_43:PSRAM=enabled,FlashSize=16M,USBMode=hwcdc,PartitionScheme=app3M_fat9M_16MB" \ - --libraries ./libraries \ - --libraries ./vendor/esp32-s3-lcd-43 \ - --build-property "compiler.cpp.extra_flags=-DDEBUG_MODE -DBOARD_HAS_PSRAM" \ - --warnings default \ - ./boards/esp32-s3-lcd-43 -""" - -[tasks.upload-s3-43] -description = "Upload to ESP32-S3-LCD-4.3" -run = """ -arduino-cli upload \ - --fqbn "esp32:esp32:waveshare_esp32_s3_touch_lcd_43:PSRAM=opi,FlashSize=16M,USBMode=hwcdc,PartitionScheme=app3M_fat9M_16MB" \ - --port "${PORT:-/dev/ttyACM0}" \ - ./boards/esp32-s3-lcd-43 -""" - -[tasks.monitor-s3-43] -description = "Serial monitor for ESP32-S3-LCD-4.3" -run = """ -arduino-cli monitor --port "${PORT:-/dev/ttyACM0}" --config baudrate=115200 -""" - -# ── Convenience ────────────────────────────────────────── - -[tasks.clean] -description = "Remove build artifacts" -run = """ -rm -rf boards/esp32-32e/build -rm -rf boards/esp32-s3-lcd-43/build -echo "[OK] Build artifacts cleaned" -""" diff --git a/sketches/doorbell-touch-esp32-32e/.arduino_config.lua b/sketches/doorbell-touch/.arduino_config.lua similarity index 100% rename from sketches/doorbell-touch-esp32-32e/.arduino_config.lua rename to sketches/doorbell-touch/.arduino_config.lua diff --git a/sketches/doorbell-touch-esp32-32e/.gitignore b/sketches/doorbell-touch/.gitignore similarity index 100% rename from sketches/doorbell-touch-esp32-32e/.gitignore rename to sketches/doorbell-touch/.gitignore diff --git a/sketches/doorbell-touch-esp32-32e/BoardConfig.h b/sketches/doorbell-touch/BoardConfig.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/BoardConfig.h rename to sketches/doorbell-touch/BoardConfig.h diff --git a/sketches/doorbell-touch-esp32-32e/Config.h b/sketches/doorbell-touch/Config.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/Config.h rename to sketches/doorbell-touch/Config.h diff --git a/sketches/doorbell-touch-esp32-32e/Dashboard.cpp b/sketches/doorbell-touch/Dashboard.cpp similarity index 100% rename from sketches/doorbell-touch-esp32-32e/Dashboard.cpp rename to sketches/doorbell-touch/Dashboard.cpp diff --git a/sketches/doorbell-touch-esp32-32e/Dashboard.h b/sketches/doorbell-touch/Dashboard.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/Dashboard.h rename to sketches/doorbell-touch/Dashboard.h diff --git a/sketches/doorbell-touch-esp32-32e/DisplayDriver.h b/sketches/doorbell-touch/DisplayDriver.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/DisplayDriver.h rename to sketches/doorbell-touch/DisplayDriver.h diff --git a/sketches/doorbell-touch-esp32-32e/DisplayDriverGFX.cpp b/sketches/doorbell-touch/DisplayDriverGFX.cpp similarity index 100% rename from sketches/doorbell-touch-esp32-32e/DisplayDriverGFX.cpp rename to sketches/doorbell-touch/DisplayDriverGFX.cpp diff --git a/sketches/doorbell-touch-esp32-32e/DisplayManager.cpp b/sketches/doorbell-touch/DisplayManager.cpp similarity index 100% rename from sketches/doorbell-touch-esp32-32e/DisplayManager.cpp rename to sketches/doorbell-touch/DisplayManager.cpp diff --git a/sketches/doorbell-touch-esp32-32e/DisplayManager.h b/sketches/doorbell-touch/DisplayManager.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/DisplayManager.h rename to sketches/doorbell-touch/DisplayManager.h diff --git a/sketches/doorbell-touch-esp32-32e/DoorbellLogic.cpp b/sketches/doorbell-touch/DoorbellLogic.cpp similarity index 100% rename from sketches/doorbell-touch-esp32-32e/DoorbellLogic.cpp rename to sketches/doorbell-touch/DoorbellLogic.cpp diff --git a/sketches/doorbell-touch-esp32-32e/DoorbellLogic.h b/sketches/doorbell-touch/DoorbellLogic.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/DoorbellLogic.h rename to sketches/doorbell-touch/DoorbellLogic.h diff --git a/sketches/doorbell-touch-esp32-32e/README.md b/sketches/doorbell-touch/README.md similarity index 100% rename from sketches/doorbell-touch-esp32-32e/README.md rename to sketches/doorbell-touch/README.md diff --git a/sketches/doorbell-touch-esp32-32e/ScreenData.h b/sketches/doorbell-touch/ScreenData.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/ScreenData.h rename to sketches/doorbell-touch/ScreenData.h diff --git a/sketches/doorbell-touch-esp32-32e/TouchDriver.h b/sketches/doorbell-touch/TouchDriver.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/TouchDriver.h rename to sketches/doorbell-touch/TouchDriver.h diff --git a/sketches/doorbell-touch-esp32-32e/TouchDriverGT911.cpp b/sketches/doorbell-touch/TouchDriverGT911.cpp similarity index 100% rename from sketches/doorbell-touch-esp32-32e/TouchDriverGT911.cpp rename to sketches/doorbell-touch/TouchDriverGT911.cpp diff --git a/sketches/doorbell-touch-esp32-32e/boards/board_e32r35t.h b/sketches/doorbell-touch/boards/board_e32r35t.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/boards/board_e32r35t.h rename to sketches/doorbell-touch/boards/board_e32r35t.h diff --git a/sketches/doorbell-touch-esp32-32e/boards/board_waveshare_s3.h b/sketches/doorbell-touch/boards/board_waveshare_s3.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/boards/board_waveshare_s3.h rename to sketches/doorbell-touch/boards/board_waveshare_s3.h diff --git a/sketches/doorbell-touch-esp32-32e/boards/esp32-32e/DisplayDriverTFT.cpp b/sketches/doorbell-touch/boards/esp32-32e/DisplayDriverTFT.cpp similarity index 100% rename from sketches/doorbell-touch-esp32-32e/boards/esp32-32e/DisplayDriverTFT.cpp rename to sketches/doorbell-touch/boards/esp32-32e/DisplayDriverTFT.cpp diff --git a/sketches/doorbell-touch-esp32-32e/boards/esp32-32e/DisplayDriverTFT.h b/sketches/doorbell-touch/boards/esp32-32e/DisplayDriverTFT.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/boards/esp32-32e/DisplayDriverTFT.h rename to sketches/doorbell-touch/boards/esp32-32e/DisplayDriverTFT.h diff --git a/sketches/doorbell-touch-esp32-32e/boards/esp32-32e/board_config.h b/sketches/doorbell-touch/boards/esp32-32e/board_config.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/boards/esp32-32e/board_config.h rename to sketches/doorbell-touch/boards/esp32-32e/board_config.h diff --git a/sketches/doorbell-touch-esp32-32e/boards/esp32-32e/esp32-32e.ino b/sketches/doorbell-touch/boards/esp32-32e/esp32-32e.ino similarity index 100% rename from sketches/doorbell-touch-esp32-32e/boards/esp32-32e/esp32-32e.ino rename to sketches/doorbell-touch/boards/esp32-32e/esp32-32e.ino diff --git a/sketches/doorbell-touch-esp32-32e/boards/esp32-32e/secrets.h.example b/sketches/doorbell-touch/boards/esp32-32e/secrets.h.example similarity index 100% rename from sketches/doorbell-touch-esp32-32e/boards/esp32-32e/secrets.h.example rename to sketches/doorbell-touch/boards/esp32-32e/secrets.h.example diff --git a/sketches/doorbell-touch-esp32-32e/boards/esp32-32e/tft_user_setup.h b/sketches/doorbell-touch/boards/esp32-32e/tft_user_setup.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/boards/esp32-32e/tft_user_setup.h rename to sketches/doorbell-touch/boards/esp32-32e/tft_user_setup.h diff --git a/sketches/doorbell-touch-esp32-32e/boards/esp32-s3-lcd-43/DisplayDriverGFX.cpp b/sketches/doorbell-touch/boards/esp32-s3-lcd-43/DisplayDriverGFX.cpp similarity index 100% rename from sketches/doorbell-touch-esp32-32e/boards/esp32-s3-lcd-43/DisplayDriverGFX.cpp rename to sketches/doorbell-touch/boards/esp32-s3-lcd-43/DisplayDriverGFX.cpp diff --git a/sketches/doorbell-touch-esp32-32e/boards/esp32-s3-lcd-43/DisplayDriverGFX.h b/sketches/doorbell-touch/boards/esp32-s3-lcd-43/DisplayDriverGFX.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/boards/esp32-s3-lcd-43/DisplayDriverGFX.h rename to sketches/doorbell-touch/boards/esp32-s3-lcd-43/DisplayDriverGFX.h diff --git a/sketches/doorbell-touch-esp32-32e/boards/esp32-s3-lcd-43/board_config.h b/sketches/doorbell-touch/boards/esp32-s3-lcd-43/board_config.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/boards/esp32-s3-lcd-43/board_config.h rename to sketches/doorbell-touch/boards/esp32-s3-lcd-43/board_config.h diff --git a/sketches/doorbell-touch-esp32-32e/boards/esp32-s3-lcd-43/esp32-s3-lcd-43.ino b/sketches/doorbell-touch/boards/esp32-s3-lcd-43/esp32-s3-lcd-43.ino similarity index 100% rename from sketches/doorbell-touch-esp32-32e/boards/esp32-s3-lcd-43/esp32-s3-lcd-43.ino rename to sketches/doorbell-touch/boards/esp32-s3-lcd-43/esp32-s3-lcd-43.ino diff --git a/sketches/doorbell-touch-esp32-32e/boards/esp32-s3-lcd-43/secrets.h.example b/sketches/doorbell-touch/boards/esp32-s3-lcd-43/secrets.h.example similarity index 100% rename from sketches/doorbell-touch-esp32-32e/boards/esp32-s3-lcd-43/secrets.h.example rename to sketches/doorbell-touch/boards/esp32-s3-lcd-43/secrets.h.example diff --git a/sketches/doorbell-touch/doorbell-touch.ino b/sketches/doorbell-touch/doorbell-touch.ino index a0a8c07..6b107f9 100644 --- a/sketches/doorbell-touch/doorbell-touch.ino +++ b/sketches/doorbell-touch/doorbell-touch.ino @@ -1,826 +1,137 @@ /* - * KLUBHAUS ALERT v4.2 — Touch Edition + * KLUBHAUS ALERT v5.1 — " BOARD_NAME " Edition * - * Target: Waveshare ESP32-S3-Touch-LCD-4.3 (non-B) + * Target: LCDWiki E32R35T (ESP32-WROOM-32E + 3.5" ST7796S + XPT2046) * - * v4.2 fixes: - * - OPI PSRAM mode (double bandwidth — fixes WiFi+RGB coexistence) - * - Bounce buffer for RGB DMA - * - Fixed variadic lambda crash in drawStatusScreen - * - Single tap for all interactions - * - Deferred status publishing (no nested HTTPS) - * - Per-topic message dedup - * - since=10s (no stale message replay) - * - DEBUG_MODE with _test topic suffix + * 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 -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include "Config.h" +#include "DisplayManager.h" +#include "DoorbellLogic.h" +#include "BoardConfig.h" -// ===================================================================== -// DEBUG MODE — set to 1 to use _test topic suffix -// ===================================================================== -#define DEBUG_MODE 1 - -// ===================================================================== -// WiFi -// ===================================================================== -struct WiFiCred { const char *ssid; const char *pass; }; -WiFiCred wifiNetworks[] = { - { "Dobro Veče", "goodnight" }, - { "berylpunk", "dhgwilliam" }, - // { "iot-2GHz", "lesson-greater" }, // blocks outbound TCP -}; -const int NUM_WIFI = sizeof(wifiNetworks) / sizeof(wifiNetworks[0]); -WiFiMulti wifiMulti; - -// ===================================================================== -// ntfy.sh Topics — since=10s covers our 5s poll interval -// ===================================================================== -#define NTFY_BASE "https://ntfy.sh" - -#if DEBUG_MODE - #define TOPIC_SUFFIX "_test" -#else - #define TOPIC_SUFFIX "" +#include +#ifndef LOAD_GLCD + #error "LOAD_GLCD is NOT defined — fonts missing!" #endif - -#define ALERT_URL NTFY_BASE "/ALERT_klubhaus_topic" TOPIC_SUFFIX "/json?since=10s&poll=1" -#define SILENCE_URL NTFY_BASE "/SILENCE_klubhaus_topic" TOPIC_SUFFIX "/json?since=10s&poll=1" -#define ADMIN_URL NTFY_BASE "/ADMIN_klubhaus_topic" TOPIC_SUFFIX "/json?since=10s&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 ALERT_TIMEOUT_MS 300000 -#define WAKE_DISPLAY_MS 5000 -#define TOUCH_DEBOUNCE_MS 300 - -#if DEBUG_MODE - #define BOOT_GRACE_MS 5000 -#else - #define BOOT_GRACE_MS 30000 +#if USE_TFT_ESPI + #ifndef ST7796_DRIVER + #if USE_TFT_ESPI +#error "TFT_eSPI setup mismatch — ST7796_DRIVER expected for E32R35T" #endif - -// ===================================================================== -// Screen & Colors -// ===================================================================== -#define SCREEN_WIDTH 800 -#define SCREEN_HEIGHT 480 - -#define COL_NEON_TEAL 0x07D7 -#define COL_HOT_FUCHSIA 0xF81F -#define COL_WHITE 0xFFDF -#define COL_BLACK 0x0000 -#define COL_MINT 0x67F5 -#define COL_DARK_GRAY 0x2104 -#define COL_GREEN 0x07E0 -#define COL_RED 0xF800 -#define COL_YELLOW 0xFFE0 - -// ===================================================================== -// I2C -// ===================================================================== -#define I2C_SDA 8 -#define I2C_SCL 9 - -// ===================================================================== -// CH422G IO Expander -// ===================================================================== -#define CH422G_SYS 0x24 -#define CH422G_OUT 0x38 -#define CH422G_OE 0x01 -#define IO_TP_RST (1 << 1) -#define IO_LCD_BL (1 << 2) -#define IO_LCD_RST (1 << 3) - -static uint8_t ioState = 0; - -void ch422g_write(uint8_t addr, uint8_t data) { - Wire.beginTransmission(addr); - Wire.write(data); - Wire.endTransmission(); -} - -void setBacklight(bool on) { - if (on) ioState |= IO_LCD_BL; - else ioState &= ~IO_LCD_BL; - ch422g_write(CH422G_OUT, ioState); -} - -// ===================================================================== -// GT911 Touch (direct I2C) -// ===================================================================== -#define GT911_ADDR1 0x14 -#define GT911_ADDR2 0x5D - -static uint8_t gt911Addr = 0; -static bool touchAvailable = false; - -void gt911_writeReg(uint16_t reg, uint8_t val) { - Wire.beginTransmission(gt911Addr); - Wire.write(reg >> 8); - Wire.write(reg & 0xFF); - Wire.write(val); - Wire.endTransmission(); -} - -bool gt911_init() { - for (uint8_t addr : { GT911_ADDR1, GT911_ADDR2 }) { - Wire.beginTransmission(addr); - if (Wire.endTransmission() == 0) { - gt911Addr = addr; - touchAvailable = true; - Serial.printf("[HW] GT911 at 0x%02X\n", addr); - return true; - } - } - Serial.println("[HW] GT911 not found"); - return false; -} - -bool gt911_read(int16_t *x, int16_t *y) { - if (!touchAvailable) return false; - - Wire.beginTransmission(gt911Addr); - Wire.write(0x81); Wire.write(0x4E); - Wire.endTransmission(false); - Wire.requestFrom(gt911Addr, (uint8_t)1); - if (!Wire.available()) return false; - uint8_t status = Wire.read(); - - bool ready = status & 0x80; - uint8_t pts = status & 0x0F; - - gt911_writeReg(0x814E, 0x00); - - if (!ready || pts == 0) return false; - - Wire.beginTransmission(gt911Addr); - Wire.write(0x81); Wire.write(0x50); - Wire.endTransmission(false); - Wire.requestFrom(gt911Addr, (uint8_t)4); - if (Wire.available() < 4) return false; - - uint8_t xl = Wire.read(), xh = Wire.read(); - uint8_t yl = Wire.read(), yh = Wire.read(); - *x = xl | (xh << 8); - *y = yl | (yh << 8); - return true; -} - -// ===================================================================== -// RGB Display — 4.3 non-B with bounce buffer for WiFi coexistence -// ===================================================================== -Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel( - 5, 3, 46, 7, - 1, 2, 42, 41, 40, - 39, 0, 45, 48, 47, 21, - 14, 38, 18, 17, 10, - 0, 8, 4, 8, - 0, 8, 4, 8, - 1, 16000000, // ← back to 16MHz - false, // useBigEndian - 0, // de_idle_high - 0, // pclk_idle_high - 8000 // bounce_buffer_size_px -); - -Arduino_RGB_Display *gfx = new Arduino_RGB_Display( - SCREEN_WIDTH, SCREEN_HEIGHT, rgbpanel, 0, true -); - -// ===================================================================== -// NTP -// ===================================================================== -WiFiUDP ntpUDP; -NTPClient timeClient(ntpUDP, "pool.ntp.org", 0, NTP_SYNC_INTERVAL_MS); - -// ===================================================================== -// State -// ===================================================================== -enum DeviceState { STATE_SILENT, STATE_ALERTING, STATE_WAKE }; -DeviceState currentState = STATE_SILENT; - -String currentMessage = ""; -String lastAlertId = ""; -String lastSilenceId = ""; -String lastAdminId = ""; -time_t lastKnownEpoch = 0; -bool ntpSynced = false; - -unsigned long bootTime = 0; -bool inBootGrace = true; - -unsigned long lastPoll = 0; -unsigned long lastBlinkToggle = 0; -bool blinkState = false; -unsigned long alertStart = 0; -unsigned long wakeStart = 0; - -bool pendingStatus = false; -String pendingStatusState = ""; -String pendingStatusMsg = ""; - -bool networkOK = false; - -// ===================================================================== -// Drawing Helpers -// ===================================================================== -void drawCentered(const char *txt, int y, int sz, uint16_t col) { - gfx->setTextSize(sz); - gfx->setTextColor(col); - int w = strlen(txt) * 6 * sz; - gfx->setCursor(max(0, (SCREEN_WIDTH - w) / 2), y); - gfx->print(txt); -} - -void drawInfoLine(int x, int y, uint16_t col, const char *text) { - gfx->setTextSize(2); - gfx->setTextColor(col); - gfx->setCursor(x, y); - gfx->print(text); -} - -void drawHeaderBar(uint16_t col, const char *label) { - gfx->setTextSize(2); - gfx->setTextColor(col); - gfx->setCursor(10, 10); - gfx->print(label); - - String t = timeClient.getFormattedTime(); - gfx->setCursor(SCREEN_WIDTH - t.length() * 12 - 10, 10); - gfx->print(t); -} - -void drawAlertScreen() { - uint16_t bg = blinkState ? COL_NEON_TEAL : COL_HOT_FUCHSIA; - uint16_t fg = blinkState ? COL_BLACK : COL_WHITE; - - gfx->fillScreen(bg); - drawHeaderBar(fg, "ALERT"); - - int sz = 8; - if (currentMessage.length() > 10) sz = 6; - if (currentMessage.length() > 20) sz = 4; - if (currentMessage.length() > 35) sz = 3; - - if (currentMessage.length() > 15) { - int mid = currentMessage.length() / 2; - int sp = currentMessage.lastIndexOf(' ', mid); - if (sp < 0) sp = mid; - String l1 = currentMessage.substring(0, sp); - String l2 = currentMessage.substring(sp + 1); - int lh = 8 * sz + 10; - int y1 = (SCREEN_HEIGHT - lh * 2) / 2; - drawCentered(l1.c_str(), y1, sz, fg); - drawCentered(l2.c_str(), y1 + lh, sz, fg); - } else { - drawCentered(currentMessage.c_str(), - (SCREEN_HEIGHT - 8 * sz) / 2, sz, fg); - } - - drawCentered("TAP TO SILENCE", SCREEN_HEIGHT - 35, 2, fg); -} - -void drawStatusScreen() { - gfx->fillScreen(COL_BLACK); - drawHeaderBar(COL_MINT, "KLUBHAUS"); - - drawCentered("MONITORING", 140, 5, COL_WHITE); - - char buf[128]; - int y = 240; - int spacing = 32; - int x = 60; - - snprintf(buf, sizeof(buf), "WiFi: %s (%d dBm)", - WiFi.isConnected() ? WiFi.SSID().c_str() : "DOWN", WiFi.RSSI()); - drawInfoLine(x, y, COL_WHITE, buf); - y += spacing; - - snprintf(buf, sizeof(buf), "IP: %s", - WiFi.isConnected() ? WiFi.localIP().toString().c_str() : "---"); - drawInfoLine(x, y, COL_WHITE, buf); - y += spacing; - - snprintf(buf, sizeof(buf), "Up: %lu min Heap: %d KB PSRAM: %d KB", - (millis() - bootTime) / 60000, ESP.getFreeHeap() / 1024, - ESP.getFreePsram() / 1024); - drawInfoLine(x, y, COL_WHITE, buf); - y += spacing; - - snprintf(buf, sizeof(buf), "NTP: %s UTC", - ntpSynced ? timeClient.getFormattedTime().c_str() : "not synced"); - drawInfoLine(x, y, COL_WHITE, buf); - y += spacing; - - snprintf(buf, sizeof(buf), "State: %s Net: %s", - currentState == STATE_SILENT ? "SILENT" : - currentState == STATE_ALERTING ? "ALERTING" : "WAKE", - networkOK ? "OK" : "FAIL"); - uint16_t stCol = currentState == STATE_ALERTING ? COL_RED : - currentState == STATE_SILENT ? COL_GREEN : COL_NEON_TEAL; - drawInfoLine(x, y, stCol, buf); - y += spacing; - - if (currentMessage.length() > 0) { - snprintf(buf, sizeof(buf), "Last: %.40s", currentMessage.c_str()); - drawInfoLine(x, y, COL_DARK_GRAY, buf); - y += spacing; - } - - #if DEBUG_MODE - drawInfoLine(x, y, COL_YELLOW, "DEBUG MODE - _test topics"); #endif +#endif +#define HOLD_TO_SILENCE_MS 1000 - drawCentered("tap to dismiss | auto-sleeps in 5s", - SCREEN_HEIGHT - 25, 1, COL_DARK_GRAY); -} +DoorbellLogic logic; +DisplayManager display; -// ===================================================================== -// Status Publishing — deferred to avoid nested HTTPS -// ===================================================================== -void queueStatus(const char *st, const String &msg) { - pendingStatus = true; - pendingStatusState = st; - pendingStatusMsg = msg; - Serial.printf("[STATUS] Queued: %s — %s\n", st, msg.c_str()); -} - -void flushStatus() { - if (!pendingStatus || !WiFi.isConnected()) return; - pendingStatus = false; - - JsonDocument doc; - doc["state"] = pendingStatusState; - doc["message"] = pendingStatusMsg; - doc["timestamp"] = (long long)timeClient.getEpochTime() * 1000LL; - - String payload; - serializeJson(doc, payload); - - WiFiClientSecure client; - client.setInsecure(); - - HTTPClient statusHttp; - statusHttp.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); - statusHttp.begin(client, STATUS_URL); - statusHttp.addHeader("Content-Type", "application/json"); - int code = statusHttp.POST(payload); - statusHttp.end(); - - Serial.printf("[STATUS] Sent (%d): %s\n", code, pendingStatusState.c_str()); - pendingStatusState = ""; - pendingStatusMsg = ""; -} - -// ===================================================================== -// Message Parsing — per-topic dedup -// ===================================================================== -void parseMessages(String &response, const char *topicName, - void (*handler)(const String&), - String &lastId) -{ - if (inBootGrace) return; - if (!ntpSynced || lastKnownEpoch == 0) return; - - int lineStart = 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) { - JsonDocument doc; - if (!deserializeJson(doc, line)) { - const char *event = doc["event"]; - const char *msgId = doc["id"]; - const char *message = doc["message"]; - time_t msgTime = doc["time"] | 0; - - if (event && strcmp(event, "message") != 0) { - lineStart = lineEnd + 1; - continue; - } - - if (message && strlen(message) > 0) { - String msgStr = String(message); - String idStr = msgId ? String(msgId) : ""; - - if (idStr.length() > 0 && idStr == lastId) { - lineStart = lineEnd + 1; - continue; - } - - if (msgTime > 0) { - time_t age = lastKnownEpoch - msgTime; - if (age > (time_t)STALE_MSG_THRESHOLD_S) { - Serial.printf("[%s] Stale (%lds): %.30s\n", - topicName, (long)age, msgStr.c_str()); - lineStart = lineEnd + 1; - continue; - } - } - - Serial.printf("[%s] %.50s\n", topicName, msgStr.c_str()); - if (idStr.length() > 0) lastId = idStr; - handler(msgStr); - } - } - } - lineStart = lineEnd + 1; - } -} - -// ===================================================================== -// Network Diagnostics -// ===================================================================== -void checkNetwork() { - Serial.printf("[NET] WiFi: %s RSSI: %d IP: %s\n", - WiFi.SSID().c_str(), WiFi.RSSI(), WiFi.localIP().toString().c_str()); - Serial.printf("[NET] GW: %s DNS: %s\n", - WiFi.gatewayIP().toString().c_str(), WiFi.dnsIP().toString().c_str()); - Serial.printf("[NET] Heap: %d KB PSRAM free: %d KB\n", - ESP.getFreeHeap() / 1024, ESP.getFreePsram() / 1024); - Serial.flush(); - - IPAddress ip; - if (!WiFi.hostByName("ntfy.sh", ip)) { - Serial.println("[NET] *** DNS FAILED ***"); - networkOK = false; - return; - } - Serial.printf("[NET] ntfy.sh -> %s\n", ip.toString().c_str()); - - WiFiClientSecure tls; - tls.setInsecure(); - Serial.println("[NET] Testing TLS to ntfy.sh:443..."); - Serial.flush(); - if (tls.connect("ntfy.sh", 443, 15000)) { - Serial.println("[NET] TLS OK!"); - tls.stop(); - networkOK = true; - } else { - Serial.println("[NET] *** TLS FAILED ***"); - networkOK = false; - } - Serial.flush(); -} - -// ===================================================================== -// ntfy Polling -// ===================================================================== -void pollTopic(const char *url, - void (*handler)(const String&), - const char *topicName, - String &lastId) -{ - WiFiClientSecure client; - client.setInsecure(); - - HTTPClient http; - http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); - http.setTimeout(10000); - - if (!http.begin(client, url)) { - Serial.printf("[%s] begin() failed\n", topicName); - return; - } - - int code = http.GET(); - if (code == HTTP_CODE_OK) { - String response = http.getString(); - if (response.length() > 0) - parseMessages(response, topicName, handler, lastId); - } else { - Serial.printf("[%s] HTTP %d: %s\n", topicName, code, - code < 0 ? http.errorToString(code).c_str() : ""); - } - http.end(); -} - -// ===================================================================== -// Message Handlers -// ===================================================================== -void handleAlertMessage(const String &message) { - if (currentState == STATE_ALERTING && currentMessage == message) return; - - currentState = STATE_ALERTING; - currentMessage = message; - alertStart = millis(); - blinkState = false; - lastBlinkToggle = millis(); - - setBacklight(true); - drawAlertScreen(); - queueStatus("ALERTING", message); - Serial.printf("-> ALERTING: %s\n", message.c_str()); -} - -void handleSilenceMessage(const String &message) { - currentState = STATE_SILENT; - currentMessage = ""; - setBacklight(false); - queueStatus("SILENT", "silenced"); - Serial.println("-> SILENT"); -} - -void handleAdminMessage(const String &message) { - Serial.printf("[ADMIN] %s\n", message.c_str()); - - if (message == "SILENCE") { - handleSilenceMessage("admin"); - } - else if (message == "PING") { - queueStatus("PONG", "ping"); - } - else if (message == "test") { - handleAlertMessage("TEST ALERT"); - } - else if (message == "status") { - char buf[256]; - snprintf(buf, sizeof(buf), - "State:%s WiFi:%s RSSI:%d Heap:%dKB Up:%lus Net:%s", - currentState == STATE_SILENT ? "SILENT" : - currentState == STATE_ALERTING ? "ALERT" : "WAKE", - WiFi.SSID().c_str(), WiFi.RSSI(), - ESP.getFreeHeap() / 1024, (millis() - bootTime) / 1000, - networkOK ? "OK" : "FAIL"); - queueStatus("STATUS", buf); - } - else if (message == "wake") { - currentState = STATE_WAKE; - wakeStart = millis(); - setBacklight(true); - drawStatusScreen(); - } - else if (message == "REBOOT") { - queueStatus("REBOOTING", "admin"); - flushStatus(); - delay(200); - ESP.restart(); - } -} - -// ===================================================================== -// Touch — trigger on finger DOWN only -// ===================================================================== -void handleTouch() { - int16_t tx, ty; - bool touching = gt911_read(&tx, &ty); - - static bool wasTouching = false; - static unsigned long lastAction = 0; - unsigned long now = millis(); - - if (touching && !wasTouching) { - if (now - lastAction >= TOUCH_DEBOUNCE_MS) { - lastAction = now; - - Serial.printf("[TOUCH] x=%d y=%d state=%d\n", tx, ty, currentState); - - switch (currentState) { - case STATE_ALERTING: - handleSilenceMessage("touch"); - break; - - case STATE_SILENT: - currentState = STATE_WAKE; - wakeStart = now; - setBacklight(true); - drawStatusScreen(); - Serial.println("-> WAKE"); - break; - - case STATE_WAKE: - currentState = STATE_SILENT; - setBacklight(false); - Serial.println("-> SILENT (dismiss)"); - break; - } - } - } - - wasTouching = touching; -} - -// ===================================================================== -// WiFi -// ===================================================================== -void connectWiFi() { - for (int i = 0; i < NUM_WIFI; i++) - wifiMulti.addAP(wifiNetworks[i].ssid, wifiNetworks[i].pass); - - setBacklight(true); - gfx->fillScreen(COL_BLACK); - drawCentered("Connecting to WiFi...", 220, 2, COL_NEON_TEAL); - - int tries = 0; - while (wifiMulti.run() != WL_CONNECTED && tries++ < 40) delay(500); - - if (WiFi.isConnected()) { - Serial.printf("[WIFI] %s %s\n", - WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); - gfx->fillScreen(COL_BLACK); - drawCentered("Connected!", 180, 3, COL_GREEN); - char buf[64]; - snprintf(buf, sizeof(buf), "%s %s", - WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); - drawCentered(buf, 240, 2, COL_WHITE); - delay(1500); - } else { - Serial.println("[WIFI] FAILED"); - gfx->fillScreen(COL_BLACK); - drawCentered("WiFi FAILED", 220, 3, COL_RED); - delay(2000); - } -} - -// ===================================================================== -// Hardware Init -// ===================================================================== -void initHardware() { - Wire.begin(I2C_SDA, I2C_SCL); - - ch422g_write(CH422G_SYS, CH422G_OE); - delay(10); - ioState = 0; - ch422g_write(CH422G_OUT, ioState); - delay(100); - - ioState = IO_TP_RST | IO_LCD_RST; - ch422g_write(CH422G_OUT, ioState); - delay(200); - - if (!gfx->begin()) { - Serial.println("[HW] Display FAILED"); - while (true) delay(1000); - } - gfx->fillScreen(COL_BLACK); - - gt911_init(); -} - -// ===================================================================== -// Setup -// ===================================================================== void setup() { Serial.begin(115200); unsigned long t = millis(); - while (!Serial && millis() - t < 5000) delay(10); + while (!Serial && millis() - t < 3000) delay(10); delay(500); - bootTime = millis(); - Serial.println("\n========================================"); - Serial.println(" KLUBHAUS ALERT v4.2 — Touch Edition"); - #if DEBUG_MODE - Serial.println(" *** DEBUG MODE — _test topics ***"); - #endif - Serial.printf(" Grace period: %d ms\n", BOOT_GRACE_MS); + Serial.println(" KLUBHAUS ALERT v5.1 — " BOARD_NAME ""); +#if DEBUG_MODE + Serial.println(" *** DEBUG MODE — _test topics ***"); +#endif Serial.println("========================================"); - Serial.printf("Heap: %d KB PSRAM: %d KB\n", - ESP.getFreeHeap() / 1024, ESP.getPsramSize() / 1024); - if (ESP.getPsramSize() == 0) { - Serial.println("PSRAM required! Check FQBN has PSRAM=opi"); - while (true) delay(1000); - } + display.begin(); - initHardware(); - - setBacklight(true); - gfx->fillScreen(COL_BLACK); - drawCentered("KLUBHAUS", 120, 6, COL_NEON_TEAL); - drawCentered("ALERT", 200, 6, COL_HOT_FUCHSIA); - drawCentered("v4.2 Touch", 300, 2, COL_DARK_GRAY); - #if DEBUG_MODE - drawCentered("DEBUG MODE", 340, 2, COL_YELLOW); - #endif + logic.begin(); + display.render(logic.getScreenState()); delay(1500); - connectWiFi(); + logic.beginWiFi(); + display.render(logic.getScreenState()); - if (WiFi.isConnected()) { - checkNetwork(); - } + logic.connectWiFiBlocking(); + display.render(logic.getScreenState()); + delay(1500); - timeClient.begin(); - if (timeClient.update()) { - ntpSynced = true; - lastKnownEpoch = timeClient.getEpochTime(); - Serial.printf("[NTP] Synced: %ld\n", lastKnownEpoch); - } + logic.finishBoot(); + display.setBacklight(false); - queueStatus("BOOTED", "v4.2 Touch Edition"); - - currentState = STATE_SILENT; - setBacklight(false); + Serial.printf("[MEM] Free heap: %d | Free PSRAM: %d\n", + ESP.getFreeHeap(), ESP.getFreePsram()); Serial.println("[BOOT] Ready — monitoring ntfy.sh\n"); } -// ===================================================================== -// Loop -// ===================================================================== -void loop() { - unsigned long now = millis(); - if (Serial.available()) { - String cmd = Serial.readStringUntil('\n'); - cmd.trim(); - if (cmd == "CLEAR_DEDUP") { - lastAlertId = ""; - lastSilenceId = ""; - lastAdminId = ""; - Serial.println("[CMD] Dedup cleared (all topics)"); - } - else if (cmd == "NET") { - checkNetwork(); - } - } - if (timeClient.update()) { - ntpSynced = true; - lastKnownEpoch = timeClient.getEpochTime(); - } - - if (inBootGrace && (now - bootTime >= BOOT_GRACE_MS)) { - inBootGrace = false; - Serial.println("[BOOT] Grace period ended — now monitoring"); - } - - handleTouch(); - - 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) { - pollTopic(ALERT_URL, handleAlertMessage, "ALERT", lastAlertId); - pollTopic(SILENCE_URL, handleSilenceMessage, "SILENCE", lastSilenceId); - pollTopic(ADMIN_URL, handleAdminMessage, "ADMIN", lastAdminId); - } - } - - flushStatus(); - - switch (currentState) { - case STATE_ALERTING: - if (now - alertStart > ALERT_TIMEOUT_MS) { - handleSilenceMessage("timeout"); - break; - } - if (now - lastBlinkToggle >= BLINK_INTERVAL_MS) { - lastBlinkToggle = now; - blinkState = !blinkState; - drawAlertScreen(); - } - break; - - case STATE_WAKE: - if (now - wakeStart > WAKE_DISPLAY_MS) { - currentState = STATE_SILENT; - setBacklight(false); - } - break; - - case STATE_SILENT: - break; - } - - static unsigned long lastHB = 0; - if (now - lastHB >= 30000) { - lastHB = now; - Serial.printf("[%lus] %s | WiFi:%s | heap:%dKB | net:%s\n", - now / 1000, - currentState == STATE_SILENT ? "SILENT" : - currentState == STATE_ALERTING ? "ALERT" : "WAKE", - WiFi.isConnected() ? "OK" : "DOWN", - ESP.getFreeHeap() / 1024, - networkOK ? "OK" : "FAIL"); - } - - delay(20); +// ── 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); + } } diff --git a/sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/library.properties b/sketches/doorbell-touch/libraries/KlubhausCore/library.properties similarity index 100% rename from sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/library.properties rename to sketches/doorbell-touch/libraries/KlubhausCore/library.properties diff --git a/sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/src/Config.h b/sketches/doorbell-touch/libraries/KlubhausCore/src/Config.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/src/Config.h rename to sketches/doorbell-touch/libraries/KlubhausCore/src/Config.h diff --git a/sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/src/DisplayManager.h b/sketches/doorbell-touch/libraries/KlubhausCore/src/DisplayManager.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/src/DisplayManager.h rename to sketches/doorbell-touch/libraries/KlubhausCore/src/DisplayManager.h diff --git a/sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/src/DoorbellLogic.cpp b/sketches/doorbell-touch/libraries/KlubhausCore/src/DoorbellLogic.cpp similarity index 100% rename from sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/src/DoorbellLogic.cpp rename to sketches/doorbell-touch/libraries/KlubhausCore/src/DoorbellLogic.cpp diff --git a/sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/src/DoorbellLogic.h b/sketches/doorbell-touch/libraries/KlubhausCore/src/DoorbellLogic.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/src/DoorbellLogic.h rename to sketches/doorbell-touch/libraries/KlubhausCore/src/DoorbellLogic.h diff --git a/sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/src/IDisplayDriver.h b/sketches/doorbell-touch/libraries/KlubhausCore/src/IDisplayDriver.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/src/IDisplayDriver.h rename to sketches/doorbell-touch/libraries/KlubhausCore/src/IDisplayDriver.h diff --git a/sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/src/KlubhausCore.h b/sketches/doorbell-touch/libraries/KlubhausCore/src/KlubhausCore.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/src/KlubhausCore.h rename to sketches/doorbell-touch/libraries/KlubhausCore/src/KlubhausCore.h diff --git a/sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/src/NetManager.cpp b/sketches/doorbell-touch/libraries/KlubhausCore/src/NetManager.cpp similarity index 100% rename from sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/src/NetManager.cpp rename to sketches/doorbell-touch/libraries/KlubhausCore/src/NetManager.cpp diff --git a/sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/src/NetManager.h b/sketches/doorbell-touch/libraries/KlubhausCore/src/NetManager.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/src/NetManager.h rename to sketches/doorbell-touch/libraries/KlubhausCore/src/NetManager.h diff --git a/sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/src/ScreenState.h b/sketches/doorbell-touch/libraries/KlubhausCore/src/ScreenState.h similarity index 100% rename from sketches/doorbell-touch-esp32-32e/libraries/KlubhausCore/src/ScreenState.h rename to sketches/doorbell-touch/libraries/KlubhausCore/src/ScreenState.h diff --git a/sketches/doorbell-touch/mise.toml b/sketches/doorbell-touch/mise.toml index d366acf..6d1254e 100644 --- a/sketches/doorbell-touch/mise.toml +++ b/sketches/doorbell-touch/mise.toml @@ -1,31 +1,115 @@ -[tools] -arduino-cli = "latest" -lazygit = "latest" +# ═══════════════════════════════════════════════════════════ +# Klubhaus Doorbell — Multi-Target Build Harness +# ═══════════════════════════════════════════════════════════ -[env] -FQBN = "esp32:esp32:waveshare_esp32_s3_touch_lcd_43:UploadSpeed=921600,USBMode=hwcdc,CDCOnBoot=cdc,CPUFreq=240,FlashMode=qio,FlashSize=16M,PartitionScheme=app3M_fat9M_16MB,DebugLevel=info,PSRAM=enabled,LoopCore=1,EventsCore=1,EraseFlash=none" - -[tasks.install-core] -run = "arduino-cli core update-index && arduino-cli core install esp32:esp32" - -[tasks.install-libs] +[tasks.install-libs-shared] +description = "Install shared (platform-independent) libraries" run = """ -arduino-cli lib install "GFX Library for Arduino" -arduino-cli lib install "ArduinoJson" -arduino-cli lib install "NTPClient" +arduino-cli lib install "ArduinoJson@7.4.1" +arduino-cli lib install "NTPClient@3.2.1" +echo "[OK] Shared libraries installed" """ -[tasks.compile] -run = "arduino-cli compile --fqbn $FQBN ." +[tasks.install-libs-32e] +description = "Vendor TFT_eSPI into vendor/esp32-32e" +run = """ +#!/usr/bin/env bash +set -euo pipefail +if [ ! -d "vendor/esp32-32e/TFT_eSPI" ]; then + echo "Cloning TFT_eSPI..." + git clone --depth 1 --branch V2.5.43 \ + https://github.com/Bodmer/TFT_eSPI.git \ + vendor/esp32-32e/TFT_eSPI +fi +echo "Copying board-specific User_Setup.h..." +cp boards/esp32-32e/tft_user_setup.h vendor/esp32-32e/TFT_eSPI/User_Setup.h +echo "[OK] TFT_eSPI 2.5.43 vendored + configured" +""" -[tasks.upload] -depends = ["compile"] -run = "arduino-cli upload --fqbn $FQBN -p $(arduino-cli board list | grep -i 'esp32\\|usb\\|ttyACM' | head -1 | awk '{print $1}') ." +[tasks.install-libs-s3-43] +description = "Vendor Arduino_GFX into vendor/esp32-s3-lcd-43" +run = """ +#!/usr/bin/env bash +set -euo pipefail +if [ ! -d "vendor/esp32-s3-lcd-43/GFX_Library_for_Arduino" ]; then + echo "Cloning Arduino_GFX..." + git clone --depth 1 --branch v1.6.5 \ + https://github.com/moononournation/Arduino_GFX.git \ + vendor/esp32-s3-lcd-43/GFX_Library_for_Arduino +fi +echo "[OK] Arduino_GFX 1.6.5 vendored" +""" -[tasks.monitor] -depends = ["upload"] -run = "arduino-cli monitor -p $(arduino-cli board list | grep -i 'esp32\\|usb\\|ttyACM' | head -1 | awk '{print $1}') -c baudrate=115200" +[tasks.install-libs] +description = "Install all libraries (shared + vendored)" +depends = ["install-libs-shared", "install-libs-32e", "install-libs-s3-43"] -[tasks.all] -depends = ["monitor"] +# ── ESP32-32E ──────────────────────────────────────────── +[tasks.compile-32e] +description = "Compile ESP32-32E sketch" +depends = ["install-libs"] +run = """ +arduino-cli compile \ + --fqbn "esp32:esp32:esp32:FlashSize=4M,PartitionScheme=default" \ + --libraries ./libraries \ + --libraries ./vendor/esp32-32e \ + --build-property "compiler.cpp.extra_flags=-DDEBUG_MODE" \ + --warnings default \ + ./boards/esp32-32e +""" + +[tasks.upload-32e] +description = "Upload to ESP32-32E" +run = """ +arduino-cli upload \ + --fqbn "esp32:esp32:esp32:FlashSize=4M,PartitionScheme=default" \ + --port "${PORT:-/dev/ttyUSB0}" \ + ./boards/esp32-32e +""" + +[tasks.monitor-32e] +description = "Serial monitor for ESP32-32E" +run = """ +arduino-cli monitor --port "${PORT:-/dev/ttyUSB0}" --config baudrate=115200 +""" + +# ── ESP32-S3-LCD-4.3 ──────────────────────────────────── + +[tasks.compile-s3-43] +description = "Compile ESP32-S3-LCD-4.3 sketch" +depends = ["install-libs"] +run = """ +arduino-cli compile \ + --fqbn "esp32:esp32:waveshare_esp32_s3_touch_lcd_43:PSRAM=enabled,FlashSize=16M,USBMode=hwcdc,PartitionScheme=app3M_fat9M_16MB" \ + --libraries ./libraries \ + --libraries ./vendor/esp32-s3-lcd-43 \ + --build-property "compiler.cpp.extra_flags=-DDEBUG_MODE -DBOARD_HAS_PSRAM" \ + --warnings default \ + ./boards/esp32-s3-lcd-43 +""" + +[tasks.upload-s3-43] +description = "Upload to ESP32-S3-LCD-4.3" +run = """ +arduino-cli upload \ + --fqbn "esp32:esp32:waveshare_esp32_s3_touch_lcd_43:PSRAM=opi,FlashSize=16M,USBMode=hwcdc,PartitionScheme=app3M_fat9M_16MB" \ + --port "${PORT:-/dev/ttyACM0}" \ + ./boards/esp32-s3-lcd-43 +""" + +[tasks.monitor-s3-43] +description = "Serial monitor for ESP32-S3-LCD-4.3" +run = """ +arduino-cli monitor --port "${PORT:-/dev/ttyACM0}" --config baudrate=115200 +""" + +# ── Convenience ────────────────────────────────────────── + +[tasks.clean] +description = "Remove build artifacts" +run = """ +rm -rf boards/esp32-32e/build +rm -rf boards/esp32-s3-lcd-43/build +echo "[OK] Build artifacts cleaned" +""" diff --git a/sketches/doorbell-touch-esp32-32e/scaffold.sh b/sketches/doorbell-touch/scaffold.sh similarity index 100% rename from sketches/doorbell-touch-esp32-32e/scaffold.sh rename to sketches/doorbell-touch/scaffold.sh diff --git a/sketches/doorbell/Dockerfile b/sketches/doorbell/Dockerfile deleted file mode 100644 index c039754..0000000 --- a/sketches/doorbell/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM nginx:alpine - -# Copy your cocaine chic doorbell UI -COPY doorbell.html /usr/share/nginx/html/index.html - -# Optional: custom nginx config for SPA behavior -COPY nginx.conf /etc/nginx/conf.d/default.conf - -EXPOSE 80 - -CMD ["nginx", "-g", "daemon off;"] diff --git a/sketches/doorbell/TFT_eSPI b/sketches/doorbell/TFT_eSPI deleted file mode 160000 index 42f64b3..0000000 --- a/sketches/doorbell/TFT_eSPI +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 42f64b37e450e43868ca0e811075484e2f3bfccb diff --git a/sketches/doorbell/doorbell.html b/sketches/doorbell/doorbell.html deleted file mode 100644 index 0b712d6..0000000 --- a/sketches/doorbell/doorbell.html +++ /dev/null @@ -1,1140 +0,0 @@ - - - - - - KLUBHAUS ALERT - - - -

KLUBHAUS ALERT

-
Checking auth...
-
WebSocket: connecting...
-
- -
- Initializing... -
-
- -
- - - -
- - -
- - -
- -
⚙ Admin
-
-
- - - - - - - - - -
-
- -
Sent
- - - - - diff --git a/sketches/doorbell/doorbell.ino b/sketches/doorbell/doorbell.ino deleted file mode 100644 index 1aeab3e..0000000 --- a/sketches/doorbell/doorbell.ino +++ /dev/null @@ -1,24 +0,0 @@ -#include -TFT_eSPI tft; - -void setup() { - Serial.begin(115200); - delay(2000); - - // DEBUG: Print which pins TFT_eSPI thinks it's using - Serial.println("TFT_eSPI Pin Configuration:"); - Serial.printf("TFT_MOSI: %d\n", TFT_MOSI); - Serial.printf("TFT_SCLK: %d\n", TFT_SCLK); - Serial.printf("TFT_CS: %d\n", TFT_CS); - Serial.printf("TFT_DC: %d\n", TFT_DC); - Serial.printf("TFT_RST: %d\n", TFT_RST); - Serial.printf("TFT_BL: %d\n", TFT_BL); - - Serial.println("Expected: MOSI=45, SCLK=40, CS=42, DC=41, RST=39, BL=48"); - - tft.init(); // This is where it crashes if pins are wrong -} - - -void loop() {} - diff --git a/sketches/doorbell/nginx.conf b/sketches/doorbell/nginx.conf deleted file mode 100644 index d99d8b3..0000000 --- a/sketches/doorbell/nginx.conf +++ /dev/null @@ -1,15 +0,0 @@ -server { - listen 80; - server_name localhost; - root /usr/share/nginx/html; - index index.html; - - location / { - try_files $uri $uri/ /index.html; - } - - # Security headers - add_header X-Frame-Options "SAMEORIGIN"; - add_header X-Content-Type-Options "nosniff"; -} - diff --git a/sketches/doorbell/sketch.yaml b/sketches/doorbell/sketch.yaml deleted file mode 100644 index a590053..0000000 --- a/sketches/doorbell/sketch.yaml +++ /dev/null @@ -1,3 +0,0 @@ -default_port: /dev/ttyACM0 -default_fqbn: esp32:esp32:esp32s3:FlashSize=16M,PartitionScheme=app3M_fat9M_16MB,PSRAM=opi -default_programmer: esptool diff --git a/sketches/wifi-diag/mise.toml b/sketches/wifi-diag/mise.toml deleted file mode 100644 index 5b3f0ea..0000000 --- a/sketches/wifi-diag/mise.toml +++ /dev/null @@ -1,20 +0,0 @@ -[tools] -arduino-cli = "latest" - -[env] -FQBN = "esp32:esp32:waveshare_esp32_s3_touch_lcd_43:UploadSpeed=921600,USBMode=hwcdc,CDCOnBoot=cdc,CPUFreq=240,FlashMode=qio,FlashSize=16M,PartitionScheme=app3M_fat9M_16MB,DebugLevel=info,PSRAM=enabled,LoopCore=1,EventsCore=0,EraseFlash=none" - -[tasks.compile] -run = "arduino-cli compile --fqbn $FQBN ." - -[tasks.upload] -depends = ["compile"] -run = "arduino-cli upload --fqbn $FQBN -p $(arduino-cli board list | grep -i 'esp32\\|usb\\|ttyACM' | head -1 | awk '{print $1}') ." - -[tasks.monitor] -depends = ["upload"] -run = "arduino-cli monitor -p $(arduino-cli board list | grep -i 'esp32\\|usb\\|ttyACM' | head -1 | awk '{print $1}') -c baudrate=115200" - -[tasks.all] -depends = ["monitor"] - diff --git a/sketches/wifi-diag/wifi-diag.ino b/sketches/wifi-diag/wifi-diag.ino deleted file mode 100644 index bcb9fe0..0000000 --- a/sketches/wifi-diag/wifi-diag.ino +++ /dev/null @@ -1,164 +0,0 @@ -/* - * WiFi Diagnostic — NO display, NO touch, just network tests - * Waveshare ESP32-S3-Touch-LCD-4.3 - */ - -#include -#include -#include -#include - -#define WIFI_SSID "iot-2GHz" -#define WIFI_PASS "lesson-greater" - -void setup() { - Serial.begin(115200); - unsigned long t = millis(); - while (!Serial && millis() - t < 5000) delay(10); - delay(1000); - - Serial.println("\n========================================"); - Serial.println(" WiFi Diagnostic — NO display"); - Serial.printf(" Heap: %d KB PSRAM: %d KB\n", - ESP.getFreeHeap() / 1024, ESP.getFreePsram() / 1024); - Serial.println("========================================\n"); - - // Connect WiFi - Serial.printf("Connecting to %s...\n", WIFI_SSID); - WiFi.begin(WIFI_SSID, WIFI_PASS); - - int tries = 0; - while (WiFi.status() != WL_CONNECTED && tries++ < 40) { - delay(500); - Serial.print("."); - } - Serial.println(); - - if (!WiFi.isConnected()) { - Serial.println("*** WiFi FAILED ***"); - while (true) delay(1000); - } - - Serial.printf("Connected!\n"); - Serial.printf(" SSID: %s\n", WiFi.SSID().c_str()); - Serial.printf(" IP: %s\n", WiFi.localIP().toString().c_str()); - Serial.printf(" GW: %s\n", WiFi.gatewayIP().toString().c_str()); - Serial.printf(" DNS: %s\n", WiFi.dnsIP().toString().c_str()); - Serial.printf(" RSSI: %d dBm\n", WiFi.RSSI()); - Serial.printf(" Heap: %d KB\n\n", ESP.getFreeHeap() / 1024); - - // Test 1: DNS - Serial.println("--- TEST 1: DNS ---"); - IPAddress ip; - if (WiFi.hostByName("ntfy.sh", ip)) - Serial.printf(" ntfy.sh -> %s OK\n", ip.toString().c_str()); - else - Serial.println(" ntfy.sh DNS FAILED"); - - if (WiFi.hostByName("google.com", ip)) - Serial.printf(" google.com -> %s OK\n", ip.toString().c_str()); - else - Serial.println(" google.com DNS FAILED"); - - // Test 2: Raw TCP port 80 - Serial.println("\n--- TEST 2: TCP port 80 ---"); - WiFiClient tcp80; - Serial.println(" Connecting google.com:80..."); - if (tcp80.connect("google.com", 80, 10000)) { - Serial.println(" google.com:80 OK!"); - tcp80.stop(); - } else { - Serial.println(" google.com:80 FAILED"); - } - - Serial.println(" Connecting ntfy.sh:80..."); - WiFiClient tcp80b; - if (tcp80b.connect("ntfy.sh", 80, 10000)) { - Serial.println(" ntfy.sh:80 OK!"); - tcp80b.stop(); - } else { - Serial.println(" ntfy.sh:80 FAILED"); - } - - // Test 3: Raw TCP port 443 - Serial.println("\n--- TEST 3: TCP port 443 ---"); - WiFiClient tcp443; - Serial.println(" Connecting ntfy.sh:443..."); - if (tcp443.connect("ntfy.sh", 443, 10000)) { - Serial.println(" ntfy.sh:443 OK!"); - tcp443.stop(); - } else { - Serial.println(" ntfy.sh:443 FAILED"); - } - - // Test 4: TLS handshake - Serial.println("\n--- TEST 4: TLS handshake (setInsecure) ---"); - Serial.printf(" Heap before TLS: %d KB\n", ESP.getFreeHeap() / 1024); - WiFiClientSecure tls; - tls.setInsecure(); - Serial.println(" Connecting ntfy.sh:443 with TLS..."); - if (tls.connect("ntfy.sh", 443, 15000)) { - Serial.println(" TLS handshake OK!"); - tls.stop(); - } else { - Serial.println(" TLS handshake FAILED"); - } - Serial.printf(" Heap after TLS: %d KB\n", ESP.getFreeHeap() / 1024); - - // Test 5: Full HTTP GET (plain HTTP) - Serial.println("\n--- TEST 5: HTTP GET (plain) ---"); - HTTPClient http; - http.setTimeout(10000); - http.begin("http://httpbin.org/get"); - int code = http.GET(); - Serial.printf(" httpbin.org HTTP %d\n", code); - if (code > 0) Serial.printf(" Body: %.100s...\n", http.getString().c_str()); - else Serial.printf(" Error: %s\n", http.errorToString(code).c_str()); - http.end(); - - // Test 6: Full HTTPS GET - Serial.println("\n--- TEST 6: HTTPS GET (ntfy.sh) ---"); - Serial.printf(" Heap before: %d KB\n", ESP.getFreeHeap() / 1024); - WiFiClientSecure sc; - sc.setInsecure(); - HTTPClient https; - https.setTimeout(10000); - https.begin(sc, "https://ntfy.sh/test_diag_12345/json?poll=1&since=10s"); - code = https.GET(); - Serial.printf(" ntfy.sh HTTPS %d\n", code); - if (code > 0) Serial.printf(" Body: %.200s...\n", https.getString().c_str()); - else Serial.printf(" Error: %s\n", https.errorToString(code).c_str()); - https.end(); - Serial.printf(" Heap after: %d KB\n", ESP.getFreeHeap() / 1024); - - // Test 7: Manual HTTP request over raw TCP - Serial.println("\n--- TEST 7: Manual HTTP over raw TCP ---"); - WiFiClient raw; - if (raw.connect("ntfy.sh", 80, 10000)) { - raw.println("GET /test_diag_12345/json?poll=1&since=10s HTTP/1.0"); - raw.println("Host: ntfy.sh"); - raw.println("Connection: close"); - raw.println(); - - unsigned long start = millis(); - while (!raw.available() && millis() - start < 10000) delay(10); - - String resp = ""; - while (raw.available()) resp += (char)raw.read(); - Serial.printf(" Got %d bytes\n", resp.length()); - Serial.printf(" Response: %.200s\n", resp.c_str()); - raw.stop(); - } else { - Serial.println(" Raw TCP to ntfy.sh:80 FAILED"); - } - - Serial.println("\n========================================"); - Serial.println(" ALL TESTS COMPLETE"); - Serial.printf(" Final heap: %d KB\n", ESP.getFreeHeap() / 1024); - Serial.println("========================================"); -} - -void loop() { - delay(10000); -} -