Spaces:
Sleeping
Sleeping
| /* | |
| * 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. | |
| */ | |
| // ββ 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); | |
| } | |