diff --git a/sketches/doorbell/doorbell.ino b/sketches/doorbell/doorbell.ino index 77bc534..7a8b221 100644 --- a/sketches/doorbell/doorbell.ino +++ b/sketches/doorbell/doorbell.ino @@ -1,6 +1,5 @@ #include #include -#include #include // ============== CONFIGURATION ============== @@ -12,108 +11,208 @@ 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; +const unsigned long BLINK_DURATION = 180000; // 3 minutes +const unsigned long BLINK_PERIOD = 1000; // 1 second on/off + +// Hardware pins +#define BACKLIGHT_PIN 22 // GPIO 22 - backlight control +#define BUTTON_PIN 9 // GPIO 9 - BOOT button + +// Backlight brightness levels (0-255) +#define BRIGHTNESS_OFF 0 +#define BRIGHTNESS_DIM 50 +#define BRIGHTNESS_FULL 255 -#define COLOR_RED 0xF800 -#define COLOR_WHITE 0xFFFF -#define COLOR_BLACK 0x0000 -#define COLOR_GREEN 0x07E0 // =========================================== -TFT_eSPI tft = TFT_eSPI(); +// STATE MACHINE +enum State { + STATE_SILENT, // Backlight off or dim + STATE_ALARM // Backlight blinking full brightness +}; + +State currentState = STATE_SILENT; unsigned long lastPoll = 0; unsigned long lastSilencePoll = 0; unsigned long blinkStartTime = 0; -bool isBlinking = false; -bool blinkState = false; +bool blinkState = false; // false = off, true = on String lastAlertId = ""; String lastSilenceId = ""; +bool buttonWasPressed = false; void setup() { Serial.begin(115200); - - // CRITICAL: Long delay for ESP32-C6 USB enumeration delay(3000); - Serial.println("=== BOOT START ==="); + Serial.println("\n=== BACKLIGHT DOORBELL ==="); - // Initialize TFT with error checking - Serial.println("Init TFT..."); - tft.init(); - Serial.println("TFT init OK"); + // Initialize backlight PWM + pinMode(BACKLIGHT_PIN, OUTPUT); + analogWrite(BACKLIGHT_PIN, BRIGHTNESS_OFF); + Serial.println("Backlight PWM ready on GPIO 22"); - tft.setRotation(0); - tft.fillScreen(COLOR_BLACK); - Serial.println("TFT cleared"); + // Initialize button + pinMode(BUTTON_PIN, INPUT_PULLUP); + Serial.println("Button ready on GPIO 9"); - // WiFi with timeout to prevent watchdog crash + // Test sequence: flash backlight to confirm working + Serial.println("Backlight test sequence..."); + analogWrite(BACKLIGHT_PIN, BRIGHTNESS_FULL); + delay(200); + analogWrite(BACKLIGHT_PIN, BRIGHTNESS_OFF); + delay(200); + analogWrite(BACKLIGHT_PIN, BRIGHTNESS_FULL); + delay(200); + analogWrite(BACKLIGHT_PIN, BRIGHTNESS_OFF); + Serial.println("Test complete"); + + // Start in silent state + transitionTo(STATE_SILENT); + + // WiFi Serial.println("Connecting WiFi..."); WiFi.begin(ssid, password); int wifiTimeout = 0; - while (WiFi.status() != WL_CONNECTED && wifiTimeout < 40) { // 20 second max + while (WiFi.status() != WL_CONNECTED && wifiTimeout < 40) { delay(500); Serial.print("."); wifiTimeout++; } if (WiFi.status() != WL_CONNECTED) { - Serial.println("\nWiFi FAILED - continuing anyway"); + Serial.println("\nWiFi FAILED - will retry"); + // Flash error pattern + for (int i = 0; i < 5; i++) { + analogWrite(BACKLIGHT_PIN, BRIGHTNESS_FULL); + delay(100); + analogWrite(BACKLIGHT_PIN, BRIGHTNESS_OFF); + delay(100); + } } else { - Serial.println("\nWiFi connected"); - Serial.print("IP: "); - Serial.println(WiFi.localIP()); + Serial.println("\nWiFi OK: " + WiFi.localIP().toString()); + // Brief success flash + analogWrite(BACKLIGHT_PIN, BRIGHTNESS_DIM); + delay(500); + analogWrite(BACKLIGHT_PIN, BRIGHTNESS_OFF); } - tft.setTextColor(COLOR_WHITE, COLOR_BLACK); - tft.setTextSize(2); - tft.setCursor(10, 10); - tft.println("Ready"); Serial.println("=== SETUP COMPLETE ==="); + Serial.println(String("State: ") + getStateName(currentState)); } void loop() { unsigned long now = millis(); + handleButton(); - // Check silence topic every 5 seconds - if (now - lastSilencePoll >= SILENCE_POLL_INTERVAL) { - lastSilencePoll = now; - Serial.println("Checking silence topic..."); - checkSilenceTopic(); + switch (currentState) { + case STATE_SILENT: + // Check silence topic frequently + if (now - lastSilencePoll >= SILENCE_POLL_INTERVAL) { + lastSilencePoll = now; + checkSilenceTopic(); + } + // Check alert topic periodically + if (now - lastPoll >= POLL_INTERVAL) { + lastPoll = now; + checkAlertTopic(); + } + break; + + case STATE_ALARM: + // Check silence topic for early stop + if (now - lastSilencePoll >= SILENCE_POLL_INTERVAL) { + lastSilencePoll = now; + checkSilenceTopic(); + } + // Update blinking + updateBlink(now); + // Auto-timeout + if (now - blinkStartTime >= BLINK_DURATION) { + Serial.println("ALARM TIMEOUT - auto silence"); + transitionTo(STATE_SILENT); + } + break; } - // 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 + delay(10); } +// ============== STATE MANAGEMENT ============== + +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: + analogWrite(BACKLIGHT_PIN, BRIGHTNESS_OFF); + Serial.println("Backlight OFF"); + break; + + case STATE_ALARM: + blinkStartTime = millis(); + blinkState = false; + analogWrite(BACKLIGHT_PIN, BRIGHTNESS_FULL); + Serial.println("Backlight BLINK started"); + break; + } +} + +const char* getStateName(State s) { + return (s == STATE_SILENT) ? "SILENT" : "ALARM"; +} + +// ============== BUTTON HANDLING ============== + +void handleButton() { + bool pressed = (digitalRead(BUTTON_PIN) == LOW); // Active low + + if (pressed && !buttonWasPressed) { + // Button just pressed + Serial.println("BUTTON PRESSED - test mode"); + buttonWasPressed = true; + analogWrite(BACKLIGHT_PIN, BRIGHTNESS_FULL); + Serial.println("Backlight FULL (held)"); + } + else if (!pressed && buttonWasPressed) { + // Button released + Serial.println("BUTTON RELEASED"); + buttonWasPressed = false; + + // Restore state + if (currentState == STATE_SILENT) { + analogWrite(BACKLIGHT_PIN, BRIGHTNESS_OFF); + Serial.println("Backlight OFF (silent)"); + } else { + // Let updateBlink handle it on next loop + Serial.println("Backlight returns to blink mode"); + } + } +} + +// ============== NETWORK FUNCTIONS ============== + void checkAlertTopic() { + Serial.println("Poll alert..."); String response = fetchNtfy(alertTopic); + if (response.length() == 0) { - Serial.println("No response from alert topic"); + Serial.println(" No response"); return; } - Serial.print("Alert response: "); - Serial.println(response); - StaticJsonDocument<512> doc; DeserializationError error = deserializeJson(doc, response); - if (error) { - Serial.print("JSON parse failed: "); + Serial.print(" JSON error: "); Serial.println(error.c_str()); return; } @@ -122,21 +221,24 @@ void checkAlertTopic() { String message = doc["message"] | ""; if (id == lastAlertId) { - Serial.println("Same alert ID, skipping"); + Serial.println(" Same ID, skip"); return; } lastAlertId = id; - Serial.print("New alert: "); + Serial.print(" New message: "); Serial.println(message); if (message.equalsIgnoreCase("SILENCE")) { - Serial.println("SILENCE command received"); - stopBlinking(); - showStatus("SILENCE", COLOR_GREEN); + Serial.println(" -> SILENCE command"); + transitionTo(STATE_SILENT); + // Brief green-like flash (dim) to confirm + analogWrite(BACKLIGHT_PIN, BRIGHTNESS_DIM); + delay(500); + analogWrite(BACKLIGHT_PIN, BRIGHTNESS_OFF); } else { - Serial.println("TRIGGERING ALARM"); - startBlinking(); + Serial.println(" -> TRIGGER ALARM"); + transitionTo(STATE_ALARM); } } @@ -146,24 +248,22 @@ void checkSilenceTopic() { 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); + Serial.println((const char*)doc["message"]); - if (isBlinking) { - Serial.println("Stopping alarm via silence topic"); - stopBlinking(); - showStatus("SILENCED", COLOR_GREEN); - delay(1000); - tft.fillScreen(COLOR_BLACK); + if (currentState == STATE_ALARM) { + Serial.println(" -> Stopping alarm"); + transitionTo(STATE_SILENT); + // Brief dim flash to confirm + analogWrite(BACKLIGHT_PIN, BRIGHTNESS_DIM); + delay(500); + analogWrite(BACKLIGHT_PIN, BRIGHTNESS_OFF); } } @@ -172,11 +272,8 @@ String fetchNtfy(const char* url) { http.begin(url); http.setTimeout(5000); - Serial.print("HTTP GET: "); - Serial.println(url); - int code = http.GET(); - Serial.print("HTTP code: "); + Serial.print(" HTTP "); Serial.println(code); if (code != 200) { @@ -188,48 +285,21 @@ String fetchNtfy(const char* url) { http.end(); int newline = payload.indexOf('\n'); - if (newline > 0) { - payload = payload.substring(0, newline); - } + 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"); -} +// ============== BLINK CONTROL ============== void updateBlink(unsigned long now) { - if (now - blinkStartTime >= BLINK_DURATION) { - stopBlinking(); - return; - } - unsigned long elapsed = now - blinkStartTime; - bool newState = (elapsed / BLINK_PERIOD) % 2 == 1; + bool newState = ((elapsed / BLINK_PERIOD) % 2) == 1; if (newState != blinkState) { blinkState = newState; - tft.fillScreen(blinkState ? COLOR_WHITE : COLOR_RED); - Serial.println(blinkState ? "WHITE" : "RED"); + analogWrite(BACKLIGHT_PIN, blinkState ? BRIGHTNESS_FULL : BRIGHTNESS_OFF); + Serial.println(blinkState ? "BLINK: ON" : "BLINK: OFF"); } } -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); -} - diff --git a/sketches/mise.toml b/sketches/mise.toml new file mode 100644 index 0000000..887b8b2 --- /dev/null +++ b/sketches/mise.toml @@ -0,0 +1,3 @@ +[tasks.upload] +run = "arduino-cli compile --upload; arduino-cli monitor" +dir = "{{cwd}}"