#include #include #include #include // ============== CONFIGURATION ============== const char* ssid = "iot-2GHz"; const char* password = "lesson-greater"; const char* alertTopic = "http://ntfy.sh/ALERT_klubhaus_topic/json?poll=1"; const char* silenceTopic = "http://ntfy.sh/SILENCE_klubhaus_topic/json?poll=1"; const unsigned long POLL_INTERVAL = 15000; const unsigned long SILENCE_POLL_INTERVAL = 15000; const unsigned long BLINK_DURATION = 180000; const unsigned long BLINK_PERIOD = 1000; #define COLOR_RED 0xF800 #define COLOR_WHITE 0xFFFF #define COLOR_BLACK 0x0000 #define COLOR_GREEN 0x07E0 // =========================================== TFT_eSPI tft = TFT_eSPI(); unsigned long lastPoll = 0; unsigned long lastSilencePoll = 0; unsigned long blinkStartTime = 0; bool isBlinking = false; bool blinkState = false; String lastAlertId = ""; String lastSilenceId = ""; void setup() { Serial.begin(115200); // CRITICAL: Long delay for ESP32-C6 USB enumeration delay(3000); Serial.println("=== BOOT START ==="); // Initialize TFT with error checking Serial.println("Init TFT..."); tft.init(); Serial.println("TFT init OK"); tft.setRotation(0); tft.fillScreen(COLOR_BLACK); Serial.println("TFT cleared"); // WiFi with timeout to prevent watchdog crash Serial.println("Connecting WiFi..."); WiFi.begin(ssid, password); int wifiTimeout = 0; while (WiFi.status() != WL_CONNECTED && wifiTimeout < 40) { // 20 second max delay(500); Serial.print("."); wifiTimeout++; } if (WiFi.status() != WL_CONNECTED) { Serial.println("\nWiFi FAILED - continuing anyway"); } else { Serial.println("\nWiFi connected"); Serial.print("IP: "); Serial.println(WiFi.localIP()); } tft.setTextColor(COLOR_WHITE, COLOR_BLACK); tft.setTextSize(2); tft.setCursor(10, 10); tft.println("Ready"); Serial.println("=== SETUP COMPLETE ==="); } void loop() { unsigned long now = millis(); // Check silence topic every 5 seconds if (now - lastSilencePoll >= SILENCE_POLL_INTERVAL) { lastSilencePoll = now; Serial.println("Checking silence topic..."); checkSilenceTopic(); } // Check alert topic every 30 seconds if (now - lastPoll >= POLL_INTERVAL) { lastPoll = now; Serial.println("Checking alert topic..."); checkAlertTopic(); } // Handle blinking if (isBlinking) { updateBlink(now); } delay(10); // Yield to prevent watchdog } void checkAlertTopic() { String response = fetchNtfy(alertTopic); if (response.length() == 0) { Serial.println("No response from alert topic"); return; } Serial.print("Alert response: "); Serial.println(response); StaticJsonDocument<512> doc; DeserializationError error = deserializeJson(doc, response); if (error) { Serial.print("JSON parse failed: "); Serial.println(error.c_str()); return; } String id = doc["id"] | ""; String message = doc["message"] | ""; if (id == lastAlertId) { Serial.println("Same alert ID, skipping"); return; } lastAlertId = id; Serial.print("New alert: "); Serial.println(message); if (message.equalsIgnoreCase("SILENCE")) { Serial.println("SILENCE command received"); stopBlinking(); showStatus("SILENCE", COLOR_GREEN); } else { Serial.println("TRIGGERING ALARM"); startBlinking(); } } void checkSilenceTopic() { String response = fetchNtfy(silenceTopic); if (response.length() == 0) return; StaticJsonDocument<512> doc; DeserializationError error = deserializeJson(doc, response); if (error) return; String id = doc["id"] | ""; String message = doc["message"] | ""; if (id == lastSilenceId) return; lastSilenceId = id; Serial.print("Silence topic: "); Serial.println(message); if (isBlinking) { Serial.println("Stopping alarm via silence topic"); stopBlinking(); showStatus("SILENCED", COLOR_GREEN); delay(1000); tft.fillScreen(COLOR_BLACK); } } String fetchNtfy(const char* url) { HTTPClient http; http.begin(url); http.setTimeout(5000); Serial.print("HTTP GET: "); Serial.println(url); int code = http.GET(); Serial.print("HTTP code: "); Serial.println(code); if (code != 200) { http.end(); return ""; } String payload = http.getString(); http.end(); int newline = payload.indexOf('\n'); if (newline > 0) { payload = payload.substring(0, newline); } return payload; } void startBlinking() { isBlinking = true; blinkStartTime = millis(); blinkState = false; tft.fillScreen(COLOR_RED); Serial.println("ALARM ON"); } void stopBlinking() { isBlinking = false; tft.fillScreen(COLOR_BLACK); Serial.println("ALARM OFF"); } void updateBlink(unsigned long now) { if (now - blinkStartTime >= BLINK_DURATION) { stopBlinking(); return; } unsigned long elapsed = now - blinkStartTime; bool newState = (elapsed / BLINK_PERIOD) % 2 == 1; if (newState != blinkState) { blinkState = newState; tft.fillScreen(blinkState ? COLOR_WHITE : COLOR_RED); Serial.println(blinkState ? "WHITE" : "RED"); } } void showStatus(const char* text, uint16_t color) { tft.fillScreen(COLOR_BLACK); tft.setTextColor(color, COLOR_BLACK); tft.setTextSize(3); tft.setCursor(20, 140); tft.println(text); }