/* * 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 #include #include #include // ── 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); }