#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 RGB_LED_PIN 8 #define BUTTON_PIN 9 // =========================================== enum State { STATE_SILENT, STATE_ALARM }; State currentState = STATE_SILENT; unsigned long lastPoll = 0; unsigned long lastSilencePoll = 0; unsigned long blinkStartTime = 0; bool blinkState = false; String lastAlertId = ""; String lastSilenceId = ""; bool buttonWasPressed = false; bool lastButtonState = false; // For edge detection // RGB colors struct Color { uint8_t r, g, b; }; const Color COLOR_OFF = {0, 0, 0}; const Color COLOR_RED = {255, 0, 0}; const Color COLOR_GREEN = {0, 255, 0}; const Color COLOR_BLUE = {0, 0, 255}; const Color COLOR_WHITE = {255, 255, 255}; // Simple WS2812B bit-bang for single LED void setRGB(Color c) { uint32_t grb = ((uint32_t)c.g << 16) | ((uint32_t)c.r << 8) | c.b; noInterrupts(); for (int i = 23; i >= 0; i--) { if (grb & (1 << i)) { digitalWrite(RGB_LED_PIN, HIGH); delayMicroseconds(1); digitalWrite(RGB_LED_PIN, LOW); delayMicroseconds(1); } else { digitalWrite(RGB_LED_PIN, HIGH); delayMicroseconds(1); digitalWrite(RGB_LED_PIN, LOW); delayMicroseconds(2); } } interrupts(); delay(1); // Reset time for WS2812B } void setup() { Serial.begin(115200); delay(3000); Serial.println("\n=== RGB DOORBELL ==="); // Init RGB LED pin pinMode(RGB_LED_PIN, OUTPUT); digitalWrite(RGB_LED_PIN, LOW); // CRITICAL: Ensure LED is OFF before test setRGB(COLOR_OFF); delay(100); Serial.println("RGB LED on GPIO 8 ready"); // Init button pinMode(BUTTON_PIN, INPUT_PULLUP); Serial.println("Button on GPIO 9 ready"); // Test sequence with explicit OFF at end Serial.println("RGB test..."); setRGB(COLOR_RED); delay(200); setRGB(COLOR_GREEN); delay(200); setRGB(COLOR_BLUE); delay(200); setRGB(COLOR_OFF); delay(100); // CRITICAL: return to off Serial.println("Test complete - LED should be OFF"); // WiFi with explicit LED management Serial.println("Connecting WiFi..."); WiFi.begin(ssid, password); int wifiTimeout = 0; while (WiFi.status() != WL_CONNECTED && wifiTimeout < 40) { delay(500); Serial.print("."); wifiTimeout++; } if (WiFi.status() != WL_CONNECTED) { Serial.println("\nWiFi FAILED"); // Error flash then OFF for (int i = 0; i < 5; i++) { setRGB(COLOR_RED); delay(100); setRGB(COLOR_OFF); delay(100); } } else { Serial.println("\nWiFi OK: " + WiFi.localIP().toString()); // Success flash then OFF setRGB(COLOR_GREEN); delay(300); setRGB(COLOR_OFF); delay(100); } // CRITICAL: Force LED off before state transition setRGB(COLOR_OFF); Serial.println("LED forced OFF after WiFi"); // Now start in silent state transitionTo(STATE_SILENT); Serial.println("=== SETUP COMPLETE ==="); Serial.println(String("State: ") + getStateName(currentState)); Serial.println(String("LED should be OFF, button ready")); } void loop() { unsigned long now = millis(); handleButton(); switch (currentState) { case STATE_SILENT: if (now - lastSilencePoll >= SILENCE_POLL_INTERVAL) { lastSilencePoll = now; checkSilenceTopic(); } if (now - lastPoll >= POLL_INTERVAL) { lastPoll = now; checkAlertTopic(); } break; case STATE_ALARM: if (now - lastSilencePoll >= SILENCE_POLL_INTERVAL) { lastSilencePoll = now; checkSilenceTopic(); } updateBlink(now); if (now - blinkStartTime >= BLINK_DURATION) { Serial.println("ALARM TIMEOUT"); transitionTo(STATE_SILENT); } break; } delay(10); } void transitionTo(State newState) { if (newState == currentState) return; Serial.print("STATE: "); Serial.print(getStateName(currentState)); Serial.print(" -> "); Serial.println(getStateName(newState)); currentState = newState; switch (currentState) { case STATE_SILENT: setRGB(COLOR_OFF); Serial.println("LED OFF (silent)"); break; case STATE_ALARM: blinkStartTime = millis(); blinkState = false; setRGB(COLOR_RED); Serial.println("LED ALARM started"); break; } } const char* getStateName(State s) { return (s == STATE_SILENT) ? "SILENT" : "ALARM"; } void handleButton() { bool pressed = (digitalRead(BUTTON_PIN) == LOW); // Edge detection: only act on changes if (pressed && !lastButtonState) { // Button just pressed Serial.println("BUTTON PRESSED"); buttonWasPressed = true; setRGB(COLOR_WHITE); Serial.println("LED WHITE (held)"); } else if (!pressed && lastButtonState) { // Button just released Serial.println("BUTTON RELEASED"); buttonWasPressed = false; if (currentState == STATE_ALARM) { Serial.println("Resetting ALARM -> SILENT"); transitionTo(STATE_SILENT); // Green confirmation setRGB(COLOR_GREEN); delay(300); setRGB(COLOR_OFF); } else { // Already silent, ensure off setRGB(COLOR_OFF); Serial.println("LED OFF (silent)"); } } lastButtonState = pressed; } void checkAlertTopic() { Serial.println("Poll alert..."); String response = fetchNtfy(alertTopic); if (response.length() == 0) { Serial.println(" No response"); return; } StaticJsonDocument<512> doc; DeserializationError error = deserializeJson(doc, response); if (error) { Serial.print(" JSON error: "); Serial.println(error.c_str()); return; } String id = doc["id"] | ""; String message = doc["message"] | ""; if (id == lastAlertId) { Serial.println(" Same ID, skip"); return; } lastAlertId = id; Serial.print(" New: "); Serial.println(message); if (message.equalsIgnoreCase("SILENCE")) { Serial.println(" -> SILENCE"); transitionTo(STATE_SILENT); setRGB(COLOR_GREEN); delay(500); setRGB(COLOR_OFF); } else { Serial.println(" -> ALARM"); transitionTo(STATE_ALARM); } } 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"] | ""; if (id == lastSilenceId) return; lastSilenceId = id; Serial.print("Silence: "); Serial.println((const char*)doc["message"]); if (currentState == STATE_ALARM) { Serial.println(" -> Stop alarm"); transitionTo(STATE_SILENT); setRGB(COLOR_GREEN); delay(500); setRGB(COLOR_OFF); } } String fetchNtfy(const char* url) { HTTPClient http; http.begin(url); http.setTimeout(5000); int code = http.GET(); Serial.print(" HTTP "); 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 updateBlink(unsigned long now) { unsigned long elapsed = now - blinkStartTime; bool newState = ((elapsed / BLINK_PERIOD) % 2) == 1; if (newState != blinkState) { blinkState = newState; setRGB(blinkState ? COLOR_WHITE : COLOR_RED); Serial.println(blinkState ? "WHITE" : "RED"); } }