EALPR_FLASK / esp32 /gate_controller.ino
Pant0x's picture
Final synced deploy v2 - Fix session and timeouts
cd01269
Raw
History Blame Contribute Delete
5.52 kB
/*
* Ain El Aql β€” ESP32 Gate Controller
*
* Two operation modes:
* 1. WiFi Mode: Polls the cloud backend every 1 second for gate commands.
* Works from anywhere in the world (real hardware + Wokwi simulation).
* 2. USB Serial Mode: Receives '1' from the browser's Web Serial API.
* Works when ESP32 is plugged into the security PC via USB.
*
* Both modes work simultaneously β€” whichever signal arrives first opens the gate.
*/
#include <ESP32Servo.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
// ── Configuration ─────────────────────────────────────────
// Change these to match your setup:
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
// Your backend URL (Railway deployment)
const char* BACKEND_URL = "https://ain-el-aql-backend-production.up.railway.app";
const char* GATE_LOCATION = "OPERA";
// ── Hardware ──────────────────────────────────────────────
Servo gateServo;
const int servoPin = 18; // GPIO 18 on ESP32
const int LED_PIN = 2; // Built-in LED for status
// ── State ─────────────────────────────────────────────────
bool gateIsOpen = false;
unsigned long lastPollTime = 0;
const unsigned long POLL_INTERVAL_MS = 1000; // Poll every 1 second
void setup() {
Serial.begin(115200);
Serial.println("\n╔══════════════════════════════════════╗");
Serial.println("β•‘ Ain El Aql β€” Gate Controller v2 β•‘");
Serial.println("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•");
// Setup servo
ESP32PWM::allocateTimer(0);
gateServo.setPeriodHertz(50);
gateServo.attach(servoPin, 500, 2400);
gateServo.write(0); // Start closed
// Setup LED
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// Connect to WiFi
Serial.print("[WiFi] Connecting to ");
Serial.print(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 30) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println(" βœ… Connected!");
Serial.print("[WiFi] IP: ");
Serial.println(WiFi.localIP());
digitalWrite(LED_PIN, HIGH); // LED on = WiFi connected
} else {
Serial.println(" ❌ WiFi failed. USB Serial mode only.");
}
Serial.println("[READY] Listening for commands (WiFi + USB Serial)");
Serial.println("[USB] Send '1' via Serial to open gate");
}
void openGate(int durationSeconds) {
if (gateIsOpen) return; // Prevent double-open
gateIsOpen = true;
Serial.println("🟒 GATE OPENING...");
gateServo.write(90);
// Blink LED while gate is open
for (int i = 0; i < durationSeconds * 2; i++) {
digitalWrite(LED_PIN, (i % 2 == 0) ? HIGH : LOW);
delay(500);
// Check for early close via serial
if (Serial.available() > 0) {
char cmd = Serial.read();
if (cmd == '0') {
Serial.println("⚑ Manual close command received");
break;
}
}
}
Serial.println("πŸ”΄ GATE CLOSING...");
gateServo.write(0);
digitalWrite(LED_PIN, WiFi.status() == WL_CONNECTED ? HIGH : LOW);
gateIsOpen = false;
}
HTTPClient http;
bool httpInitialized = false;
void pollBackend() {
if (WiFi.status() != WL_CONNECTED) return;
if (gateIsOpen) return;
if (!httpInitialized) {
http.setReuse(true);
httpInitialized = true;
}
String url = String(BACKEND_URL) + "/gate/poll?location=" + GATE_LOCATION;
http.begin(url);
http.setTimeout(2000);
int httpCode = http.GET();
if (httpCode == 200) {
String response = http.getString();
// Parse JSON
StaticJsonDocument<512> doc;
DeserializationError error = deserializeJson(doc, response);
if (!error) {
const char* status = doc["status"];
if (strcmp(status, "command") == 0) {
const char* action = doc["command"]["action"];
int duration = doc["command"]["duration_seconds"] | 10;
const char* plate = doc["command"]["plate"] | "Unknown";
Serial.print("πŸ“‘ Cloud command: ");
Serial.print(action);
Serial.println("s");
if (strcmp(action, "open") == 0) {
openGate(duration);
}
}
}
} else if (httpCode < 0) {
// If error (e.g. timeout), end and re-init next time
http.end();
httpInitialized = false;
}
// If 200 OK, we DO NOT call http.end() so the TLS socket stays alive!
}
void loop() {
// ── Mode 1: USB Serial Commands ───────────────────────
if (Serial.available() > 0) {
char cmd = Serial.read();
if (cmd == '1') {
Serial.println("πŸ”Œ USB command received!");
openGate(10);
}
}
// ── Mode 2: WiFi Cloud Polling ────────────────────────
unsigned long now = millis();
if (now - lastPollTime >= POLL_INTERVAL_MS) {
lastPollTime = now;
pollBackend();
}
// Small delay to prevent busy-waiting
delay(10);
}