#include #include #include #include #include "driver/rtc_io.h" // --- CONFIG --- const char* WIFI_SSID = "Aussie Broadband ****"; const char* WIFI_PASS = "Ffdfmu****"; // N8N HTTP IP const char* N8N_URL = "http://192.168.20.13:5678/webhook/front-door-cam"; // MQTT const char* MQTT_BROKER = "192.168.20.30"; const int MQTT_PORT = 1883; const char* MQTT_USER = "mqtt-user"; const char* MQTT_PASS = "sam***"; const char* MQTT_TOPIC = "front-door-cam/events"; // --- TIMING --- const long TIME_SLEEP_BUTTON = 120000; const long TIME_SLEEP_PIR = 60000; const long TIME_RETRIGGER = 10000; // --- PINS --- #define PIN_BTN 14 #define PIN_PIR 13 #define PIN_LED 12 #define PIN_RX_CAM 18 #define PIN_TX_CAM 17 Adafruit_NeoPixel strip(12, PIN_LED, NEO_GRB + NEO_KHZ800); HardwareSerial CamSerial(1); WiFiClient espClient; PubSubClient mqtt(espClient); // Colors uint32_t C_RED = strip.Color(255, 0, 0); uint32_t C_BLUE = strip.Color(0, 0, 255); uint32_t C_YELLOW = strip.Color(255, 200, 0); uint32_t C_GREEN = strip.Color(0, 255, 0); uint32_t C_OFF = 0; void setColor(uint32_t c) { strip.fill(c); strip.show(); } void setup() { Serial.begin(115200); CamSerial.begin(115200, SERIAL_8N1, PIN_RX_CAM, PIN_TX_CAM); pinMode(PIN_BTN, INPUT_PULLUP); pinMode(PIN_PIR, INPUT_PULLDOWN); strip.begin(); strip.setBrightness(50); Serial.println("Connecting WiFi..."); WiFi.setTxPower(WIFI_POWER_19_5dBm); WiFi.begin(WIFI_SSID, WIFI_PASS); int t=0; while(WiFi.status() != WL_CONNECTED && t < 16) { delay(500); t++; } if(WiFi.status() == WL_CONNECTED) Serial.println("Connected."); else Serial.println("WiFi Failed."); // Check Wakeup esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); bool btnPressed = (digitalRead(PIN_BTN) == LOW); bool pirTrigger = (digitalRead(PIN_PIR) == HIGH); if (cause == ESP_SLEEP_WAKEUP_EXT0 || btnPressed) { handleTrigger("BUTTON"); } else if (cause == ESP_SLEEP_WAKEUP_EXT1 || pirTrigger) { handleTrigger("MOTION"); } else { Serial.println("Fresh Boot."); setColor(C_GREEN); delay(2000); goToSleep(); } } void loop() { // Main loop handles Retrigger logic if (digitalRead(PIN_BTN) == LOW) { // Only re-trigger if logic is running handleTrigger("BUTTON"); } // If we fall through here, go to sleep goToSleep(); } // Main Logic Sequence void handleTrigger(const char* type) { Serial.printf("TRIGGER: %s\n", type); // 1. Set Visual State uint32_t stateColor = (strcmp(type, "BUTTON") == 0) ? C_BLUE : C_RED; int maxAwakeTime = (strcmp(type, "BUTTON") == 0) ? TIME_SLEEP_BUTTON : TIME_SLEEP_PIR; setColor(stateColor); // 2. Send MQTT IMMEDIATELY (Before Image) if (WiFi.status() == WL_CONNECTED) { sendMQTT(type); } // 3. Wait 2s Pre-Snap delay(2000); // 4. Snap & Upload N8N if (WiFi.status() == WL_CONNECTED) { snapAndUploadN8N(type); } // Restore Color setColor(stateColor); // 5. Cooldown Loop (Stay Awake) unsigned long startLoop = millis(); while (millis() - startLoop < maxAwakeTime) { // Check for BUTTON Retrigger if (digitalRead(PIN_BTN) == LOW) { // Debounce Check static unsigned long lastBtnTime = 0; if (millis() - lastBtnTime > TIME_RETRIGGER) { Serial.println("Retriggered by Button!"); lastBtnTime = millis(); // Reset timers and switch to Button Mode type = "BUTTON"; stateColor = C_BLUE; maxAwakeTime = TIME_SLEEP_BUTTON; setColor(C_BLUE); startLoop = millis(); // Reset Sleep Timer // REPEAT ACTION sendMQTT("BUTTON"); delay(2000); snapAndUploadN8N("BUTTON"); setColor(C_BLUE); } } delay(100); } goToSleep(); } void sendMQTT(const char* eventType) { mqtt.setServer(MQTT_BROKER, MQTT_PORT); if (mqtt.connect("ESP32Doorbell", MQTT_USER, MQTT_PASS)) { char payload[64]; // Simple Alert Payload snprintf(payload, sizeof(payload), "{\"event\": \"%s\", \"status\": \"active\"}", eventType); mqtt.publish(MQTT_TOPIC, payload); mqtt.disconnect(); Serial.println("MQTT Sent."); } else { Serial.println("MQTT Failed."); } } void snapAndUploadN8N(const char* eventName) { setColor(C_YELLOW); while(CamSerial.available()) CamSerial.read(); CamSerial.write('S'); uint32_t start = millis(); while (CamSerial.available() < 4) { if (millis() - start > 3000) { Serial.println("Camera Timeout"); return; } } uint32_t imgLen = 0; CamSerial.readBytes((char*)&imgLen, 4); Serial.printf("Size: %d\n", imgLen); if (imgLen > 300000) return; uint8_t *fb = (uint8_t *)malloc(imgLen); if (!fb) return; uint32_t received = 0; start = millis(); while (received < imgLen && (millis() - start < 5000)) { if (CamSerial.available()) fb[received++] = CamSerial.read(); } if (received == imgLen) { HTTPClient http; if (http.begin(N8N_URL)) { http.addHeader("Content-Type", "image/jpeg"); http.addHeader("X-Event-Type", eventName); int code = http.POST(fb, imgLen); Serial.printf("N8N Upload: %d\n", code); http.end(); } } free(fb); } void goToSleep() { Serial.println("Sleep."); setColor(C_OFF); delay(100); rtc_gpio_pullup_en((gpio_num_t)PIN_BTN); rtc_gpio_pulldown_dis((gpio_num_t)PIN_BTN); esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_BTN, 0); esp_sleep_enable_ext1_wakeup(1ULL << PIN_PIR, ESP_EXT1_WAKEUP_ANY_HIGH); esp_deep_sleep_start(); }