consolidate sketches
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
|||||||
[submodule "TFT_eSPI"]
|
[submodule "TFT_eSPI"]
|
||||||
path = TFT_eSPI
|
path = TFT_eSPI
|
||||||
url = https://github.com/Cincinnatu/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
|
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
default_fqbn: esp32:esp32:esp32c6
|
|
||||||
default_programmer: esptool
|
|
||||||
default_port: /dev/ttyACM0
|
|
||||||
@@ -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 <SPI.h>
|
|
||||||
#include "Config.h"
|
|
||||||
#include "DisplayManager.h"
|
|
||||||
#include "DoorbellLogic.h"
|
|
||||||
#include "BoardConfig.h"
|
|
||||||
|
|
||||||
#include <TFT_eSPI.h>
|
|
||||||
#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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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"
|
|
||||||
"""
|
|
||||||
@@ -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:
|
* Hold-and-release interaction model:
|
||||||
* - OPI PSRAM mode (double bandwidth — fixes WiFi+RGB coexistence)
|
* - Hold finger → progress bar fills
|
||||||
* - Bounce buffer for RGB DMA
|
* - Bar full → jitter/flash ("RELEASE!")
|
||||||
* - Fixed variadic lambda crash in drawStatusScreen
|
* - Lift finger → action fires (finger already off screen)
|
||||||
* - 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
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <SPI.h>
|
||||||
#include <Wire.h>
|
#include "Config.h"
|
||||||
#include <WiFi.h>
|
#include "DisplayManager.h"
|
||||||
#include <WiFiMulti.h>
|
#include "DoorbellLogic.h"
|
||||||
#include <WiFiClientSecure.h>
|
#include "BoardConfig.h"
|
||||||
#include <HTTPClient.h>
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
#include <NTPClient.h>
|
|
||||||
#include <WiFiUdp.h>
|
|
||||||
#include <Arduino_GFX_Library.h>
|
|
||||||
|
|
||||||
// =====================================================================
|
#include <TFT_eSPI.h>
|
||||||
// DEBUG MODE — set to 1 to use _test topic suffix
|
#ifndef LOAD_GLCD
|
||||||
// =====================================================================
|
#error "LOAD_GLCD is NOT defined — fonts missing!"
|
||||||
#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 ""
|
|
||||||
#endif
|
#endif
|
||||||
|
#if USE_TFT_ESPI
|
||||||
#define ALERT_URL NTFY_BASE "/ALERT_klubhaus_topic" TOPIC_SUFFIX "/json?since=10s&poll=1"
|
#ifndef ST7796_DRIVER
|
||||||
#define SILENCE_URL NTFY_BASE "/SILENCE_klubhaus_topic" TOPIC_SUFFIX "/json?since=10s&poll=1"
|
#if USE_TFT_ESPI
|
||||||
#define ADMIN_URL NTFY_BASE "/ADMIN_klubhaus_topic" TOPIC_SUFFIX "/json?since=10s&poll=1"
|
#error "TFT_eSPI setup mismatch — ST7796_DRIVER expected for E32R35T"
|
||||||
#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
|
|
||||||
#endif
|
#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
|
||||||
|
#endif
|
||||||
|
#define HOLD_TO_SILENCE_MS 1000
|
||||||
|
|
||||||
drawCentered("tap to dismiss | auto-sleeps in 5s",
|
DoorbellLogic logic;
|
||||||
SCREEN_HEIGHT - 25, 1, COL_DARK_GRAY);
|
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() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
unsigned long t = millis();
|
unsigned long t = millis();
|
||||||
while (!Serial && millis() - t < 5000) delay(10);
|
while (!Serial && millis() - t < 3000) delay(10);
|
||||||
delay(500);
|
delay(500);
|
||||||
|
|
||||||
bootTime = millis();
|
|
||||||
|
|
||||||
Serial.println("\n========================================");
|
Serial.println("\n========================================");
|
||||||
Serial.println(" KLUBHAUS ALERT v4.2 — Touch Edition");
|
Serial.println(" KLUBHAUS ALERT v5.1 — " BOARD_NAME "");
|
||||||
#if DEBUG_MODE
|
#if DEBUG_MODE
|
||||||
Serial.println(" *** DEBUG MODE — _test topics ***");
|
Serial.println(" *** DEBUG MODE — _test topics ***");
|
||||||
#endif
|
#endif
|
||||||
Serial.printf(" Grace period: %d ms\n", BOOT_GRACE_MS);
|
|
||||||
Serial.println("========================================");
|
Serial.println("========================================");
|
||||||
Serial.printf("Heap: %d KB PSRAM: %d KB\n",
|
|
||||||
ESP.getFreeHeap() / 1024, ESP.getPsramSize() / 1024);
|
|
||||||
|
|
||||||
if (ESP.getPsramSize() == 0) {
|
display.begin();
|
||||||
Serial.println("PSRAM required! Check FQBN has PSRAM=opi");
|
|
||||||
while (true) delay(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
initHardware();
|
logic.begin();
|
||||||
|
display.render(logic.getScreenState());
|
||||||
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
|
|
||||||
delay(1500);
|
delay(1500);
|
||||||
|
|
||||||
connectWiFi();
|
logic.beginWiFi();
|
||||||
|
display.render(logic.getScreenState());
|
||||||
|
|
||||||
if (WiFi.isConnected()) {
|
logic.connectWiFiBlocking();
|
||||||
checkNetwork();
|
display.render(logic.getScreenState());
|
||||||
}
|
delay(1500);
|
||||||
|
|
||||||
timeClient.begin();
|
logic.finishBoot();
|
||||||
if (timeClient.update()) {
|
display.setBacklight(false);
|
||||||
ntpSynced = true;
|
|
||||||
lastKnownEpoch = timeClient.getEpochTime();
|
|
||||||
Serial.printf("[NTP] Synced: %ld\n", lastKnownEpoch);
|
|
||||||
}
|
|
||||||
|
|
||||||
queueStatus("BOOTED", "v4.2 Touch Edition");
|
Serial.printf("[MEM] Free heap: %d | Free PSRAM: %d\n",
|
||||||
|
ESP.getFreeHeap(), ESP.getFreePsram());
|
||||||
currentState = STATE_SILENT;
|
|
||||||
setBacklight(false);
|
|
||||||
Serial.println("[BOOT] Ready — monitoring ntfy.sh\n");
|
Serial.println("[BOOT] Ready — monitoring ntfy.sh\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================================
|
|
||||||
// Loop
|
|
||||||
// =====================================================================
|
|
||||||
void loop() {
|
|
||||||
unsigned long now = millis();
|
|
||||||
|
|
||||||
|
|
||||||
|
// ── 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()) {
|
if (Serial.available()) {
|
||||||
String cmd = Serial.readStringUntil('\n');
|
String cmd = Serial.readStringUntil('\n');
|
||||||
cmd.trim();
|
cmd.trim();
|
||||||
if (cmd == "CLEAR_DEDUP") {
|
if (cmd.length() > 0) logic.onSerialCommand(cmd);
|
||||||
lastAlertId = "";
|
|
||||||
lastSilenceId = "";
|
|
||||||
lastAdminId = "";
|
|
||||||
Serial.println("[CMD] Dedup cleared (all topics)");
|
|
||||||
}
|
|
||||||
else if (cmd == "NET") {
|
|
||||||
checkNetwork();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeClient.update()) {
|
void loop() {
|
||||||
ntpSynced = true;
|
logic.update();
|
||||||
lastKnownEpoch = timeClient.getEpochTime();
|
display.render(logic.getScreenState());
|
||||||
}
|
|
||||||
|
|
||||||
if (inBootGrace && (now - bootTime >= BOOT_GRACE_MS)) {
|
// Touch → hold-to-silence gesture
|
||||||
inBootGrace = false;
|
TouchEvent evt = display.readTouch();
|
||||||
Serial.println("[BOOT] Grace period ended — now monitoring");
|
if (evt.pressed) {
|
||||||
}
|
// Dashboard tile tap
|
||||||
|
if (logic.getScreenState().screen == ScreenID::DASHBOARD) {
|
||||||
handleTouch();
|
int tile = display.dashboardTouch(evt.x, evt.y);
|
||||||
|
if (tile >= 0) Serial.printf("[DASH] Tile %d tapped\n", tile);
|
||||||
if (!WiFi.isConnected()) {
|
|
||||||
if (wifiMulti.run() == WL_CONNECTED) {
|
|
||||||
Serial.println("[WIFI] Reconnected");
|
|
||||||
queueStatus("RECONNECTED", WiFi.SSID().c_str());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (now - lastPoll >= POLL_INTERVAL_MS) {
|
// Hold-to-silence during ALERT
|
||||||
lastPoll = now;
|
if (logic.getScreenState().deviceState == DeviceState::ALERTING) {
|
||||||
if (WiFi.isConnected() && ntpSynced) {
|
HoldState h = display.updateHold(HOLD_TO_SILENCE_MS);
|
||||||
pollTopic(ALERT_URL, handleAlertMessage, "ALERT", lastAlertId);
|
if (h.completed) silenceAlerts();
|
||||||
pollTopic(SILENCE_URL, handleSilenceMessage, "SILENCE", lastSilenceId);
|
|
||||||
pollTopic(ADMIN_URL, handleAdminMessage, "ADMIN", lastAdminId);
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,115 @@
|
|||||||
[tools]
|
# ═══════════════════════════════════════════════════════════
|
||||||
arduino-cli = "latest"
|
# Klubhaus Doorbell — Multi-Target Build Harness
|
||||||
lazygit = "latest"
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
[env]
|
[tasks.install-libs-shared]
|
||||||
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"
|
description = "Install shared (platform-independent) libraries"
|
||||||
|
|
||||||
[tasks.install-core]
|
|
||||||
run = "arduino-cli core update-index && arduino-cli core install esp32:esp32"
|
|
||||||
|
|
||||||
[tasks.install-libs]
|
|
||||||
run = """
|
run = """
|
||||||
arduino-cli lib install "GFX Library for Arduino"
|
arduino-cli lib install "ArduinoJson@7.4.1"
|
||||||
arduino-cli lib install "ArduinoJson"
|
arduino-cli lib install "NTPClient@3.2.1"
|
||||||
arduino-cli lib install "NTPClient"
|
echo "[OK] Shared libraries installed"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
[tasks.compile]
|
[tasks.install-libs-32e]
|
||||||
run = "arduino-cli compile --fqbn $FQBN ."
|
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]
|
[tasks.install-libs-s3-43]
|
||||||
depends = ["compile"]
|
description = "Vendor Arduino_GFX into vendor/esp32-s3-lcd-43"
|
||||||
run = "arduino-cli upload --fqbn $FQBN -p $(arduino-cli board list | grep -i 'esp32\\|usb\\|ttyACM' | head -1 | awk '{print $1}') ."
|
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]
|
[tasks.install-libs]
|
||||||
depends = ["upload"]
|
description = "Install all libraries (shared + vendored)"
|
||||||
run = "arduino-cli monitor -p $(arduino-cli board list | grep -i 'esp32\\|usb\\|ttyACM' | head -1 | awk '{print $1}') -c baudrate=115200"
|
depends = ["install-libs-shared", "install-libs-32e", "install-libs-s3-43"]
|
||||||
|
|
||||||
[tasks.all]
|
# ── ESP32-32E ────────────────────────────────────────────
|
||||||
depends = ["monitor"]
|
|
||||||
|
|
||||||
|
[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"
|
||||||
|
"""
|
||||||
|
|||||||
@@ -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;"]
|
|
||||||
Submodule sketches/doorbell/TFT_eSPI deleted from 42f64b37e4
File diff suppressed because it is too large
Load Diff
@@ -1,24 +0,0 @@
|
|||||||
#include <TFT_eSPI.h>
|
|
||||||
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() {}
|
|
||||||
|
|
||||||
@@ -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";
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
default_port: /dev/ttyACM0
|
|
||||||
default_fqbn: esp32:esp32:esp32s3:FlashSize=16M,PartitionScheme=app3M_fat9M_16MB,PSRAM=opi
|
|
||||||
default_programmer: esptool
|
|
||||||
@@ -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"]
|
|
||||||
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
/*
|
|
||||||
* WiFi Diagnostic — NO display, NO touch, just network tests
|
|
||||||
* Waveshare ESP32-S3-Touch-LCD-4.3
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include <WiFi.h>
|
|
||||||
#include <WiFiClientSecure.h>
|
|
||||||
#include <HTTPClient.h>
|
|
||||||
|
|
||||||
#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);
|
|
||||||
}
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user