front_door_cam_controller/front_door_cam_control.ino

217 lines
5.5 KiB
C++

#include <WiFi.h>
#include <HTTPClient.h>
#include <Adafruit_NeoPixel.h>
#include <PubSubClient.h>
#include "driver/rtc_io.h"
// --- CONFIG ---
const char* WIFI_SSID = "Aussie Broadband 8729";
const char* WIFI_PASS = "Ffdfmunfca";
// 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 = "sam4jo";
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();
}