217 lines
5.5 KiB
C++
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 ****";
|
|
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();
|
|
} |