ESP32 IoT Projects · Intermediate
ESP32 Smart Parking System with WiFi & IoT
Detect available parking slots in real time, control a servo barrier gate, display status on an LCD, and monitor everything from a live web dashboard.
Project Overview
This project builds a fully intelligent parking management system on an ESP32. Three HC-SR04 ultrasonic sensors continuously monitor whether parking slots are occupied. A servo motor acts as a barrier gate, a 16×2 LCD shows real-time availability, and a beautiful web dashboard lets you monitor and control everything remotely from any browser.
The system also exposes a RESTful JSON API so mobile apps can query slot status and trigger the gate — making it a complete IoT foundation for real-world parking deployments.
Key Features
🔧 Hardware
- 3 parking slots with HC-SR04 ultrasonic sensors
- 16×2 LCD display (I2C at 0x27)
- Servo motor barrier gate (0° closed / 90° open)
- 5 LEDs — 3× green slot, 1× red full, 1× blue WiFi
- Buzzer for entry/exit alerts & full warning
- ENTRY and EXIT push buttons (INPUT_PULLUP)
📡 Software & IoT
- WiFi connectivity with auto-reconnect
- Web dashboard with modern gradient UI
- Mobile-ready responsive layout
- Auto-refresh live updates every 2 seconds
- RESTful JSON API endpoints
- Entry/exit statistics & uptime tracking
- Scalable architecture — add more slots easily
Components Required
| Component | Qty | Purpose |
|---|---|---|
| ESP32 DevKit V1 | 1 | Main controller with WiFi / dual-core 240 MHz |
| HC-SR04 Ultrasonic Sensor | 3 | Detect car presence in each parking slot |
| Servo Motor (SG90) | 1 | Barrier gate — 0° closed, 90° open |
| 16×2 LCD (I2C 0x27) | 1 | Show available/occupied count on-site |
| Passive Buzzer | 1 | Entry, exit & "parking full" audio alerts |
| LED Green | 3 | Slot available indicators (Slot 1, 2, 3) |
| LED Red | 1 | Parking full indicator |
| LED Blue | 1 | WiFi connected / heartbeat blink |
| 220Ω Resistors | 5 | Current limiting for all LEDs |
| Push Buttons (Green/Red) | 2 | Manual ENTRY and EXIT triggers |
Pin Connections & Wiring
| Component | ESP32 Pin | Wire Color |
|---|---|---|
| Ultrasonic 1 TRIG | D13 | 🟠 Orange |
| Ultrasonic 1 ECHO | D12 | 🟣 Purple |
| Ultrasonic 2 TRIG | D14 | 🟠 Orange |
| Ultrasonic 2 ECHO | D27 | 🟣 Purple |
| Ultrasonic 3 TRIG | D26 | 🟠 Orange |
| Ultrasonic 3 ECHO | D25 | 🟣 Purple |
| All Ultrasonics VCC | VIN (5V) | 🔴 Red |
| Servo SIG | D15 | 🟠 Orange |
| Servo VCC | VIN | 🔴 Red |
| LCD SDA | D21 | 🔵 Blue |
| LCD SCL | D22 | 🟡 Yellow |
| LCD VCC | 3V3 | 🔴 Red |
| LED Slot 1 (Green) | D16 via 220Ω | 🟢 Green |
| LED Slot 2 (Green) | D17 via 220Ω | 🟢 Green |
| LED Slot 3 (Green) | D18 via 220Ω | 🟢 Green |
| LED Full (Red) | D19 via 220Ω | 🔴 Red |
| LED WiFi (Blue) | D2 via 220Ω | 🔵 Blue |
| Buzzer | D23 | 🟠 Orange |
| ENTRY Button | D32 (INPUT_PULLUP) | 🟢 Green |
| EXIT Button | D33 (INPUT_PULLUP) | 🔴 Red |
Quick Start: Load in Wokwi
Go to wokwi.com and create a new ESP32 project
Make sure you select ESP32 (not Arduino Uno). The library set is different and includes WiFi, WebServer, and ESP32Servo.
Paste the diagram.json content
Click the diagram.json tab in Wokwi and replace all content with the JSON from Section 6 below. This loads all components pre-wired.
Paste the sketch.ino code
Click the sketch.ino tab and replace with the code from Section 7. All libraries (WiFi, WebServer, LiquidCrystal_I2C, ESP32Servo) are auto-installed in Wokwi.
Press ▶ Start and check Serial Monitor
WiFi connects in ~10 seconds. The Serial Monitor shows the IP address. Click it to open the live web dashboard in a new browser tab.
LiquidCrystal_I2C and ESP32Servo. Install via Arduino Library Manager for real hardware builds. These come pre-installed in Wokwi.Diagram.json (Wokwi)
Copy this into your diagram.json tab in Wokwi to instantly load the fully wired circuit with all 3 ultrasonic sensors, servo, LCD, LEDs, buttons, and buzzer.
{
"version": 1,
"author": "ESP32 Smart Parking",
"editor": "wokwi",
"parts": [
{ "type": "wokwi-esp32-devkit-v1", "id": "esp32", "top": 0, "left": 0, "attrs": {} },
{ "type": "wokwi-lcd1602", "id": "lcd1", "top": -150, "left": 115.2,
"attrs": { "pins": "i2c" } },
{ "type": "wokwi-hc-sr04", "id": "ultrasonic1", "top": -240, "left": -150,
"attrs": { "distance": "400" } },
{ "type": "wokwi-hc-sr04", "id": "ultrasonic2", "top": -240, "left": 0,
"attrs": { "distance": "400" } },
{ "type": "wokwi-hc-sr04", "id": "ultrasonic3", "top": -240, "left": 150,
"attrs": { "distance": "400" } },
{ "type": "wokwi-servo", "id": "servo1", "top": 140, "left": -144, "attrs": {} },
{ "type": "wokwi-led", "id": "led1", "top": 280, "left": -140,
"attrs": { "color": "green" } },
{ "type": "wokwi-led", "id": "led2", "top": 280, "left": -50,
"attrs": { "color": "green" } },
{ "type": "wokwi-led", "id": "led3", "top": 280, "left": 40,
"attrs": { "color": "green" } },
{ "type": "wokwi-led", "id": "led4", "top": 280, "left": 130,
"attrs": { "color": "red" } },
{ "type": "wokwi-resistor", "id": "r1", "top": 318, "left": -144.8, "rotate": 90,
"attrs": { "value": "220" } },
{ "type": "wokwi-resistor", "id": "r2", "top": 318, "left": -54.8, "rotate": 90,
"attrs": { "value": "220" } },
{ "type": "wokwi-resistor", "id": "r3", "top": 318, "left": 35.2, "rotate": 90,
"attrs": { "value": "220" } },
{ "type": "wokwi-resistor", "id": "r4", "top": 318, "left": 125.2, "rotate": 90,
"attrs": { "value": "220" } },
{ "type": "wokwi-buzzer", "id": "bz1", "top": 140, "left": 336, "attrs": {} },
{ "type": "wokwi-pushbutton", "id": "btn1", "top": 270, "left": 316.8,
"attrs": { "color": "green", "label": "ENTRY" } },
{ "type": "wokwi-pushbutton", "id": "btn2", "top": 270, "left": 412.8,
"attrs": { "color": "red", "label": "EXIT" } },
{ "type": "wokwi-led", "id": "led5", "top": 140, "left": 240,
"attrs": { "color": "blue", "label": "WiFi" } },
{ "type": "wokwi-resistor", "id": "r5", "top": 178, "left": 235.2, "rotate": 90,
"attrs": { "value": "220" } }
],
"connections": [
["esp32:TX0", "$serialMonitor:RX", "", []],
["esp32:RX0", "$serialMonitor:TX", "", []],
["lcd1:GND", "esp32:GND.1", "black", ["h0"]],
["lcd1:VCC", "esp32:3V3", "red", ["h0"]],
["lcd1:SDA", "esp32:D21", "blue", ["h0"]],
["lcd1:SCL", "esp32:D22", "yellow", ["h0"]],
["ultrasonic1:VCC", "esp32:VIN", "red", ["h0"]],
["ultrasonic1:GND", "esp32:GND.1", "black", ["h0"]],
["ultrasonic1:TRIG", "esp32:D13", "orange", ["h0"]],
["ultrasonic1:ECHO", "esp32:D12", "purple", ["h0"]],
["ultrasonic2:VCC", "esp32:VIN", "red", ["h0"]],
["ultrasonic2:GND", "esp32:GND.1", "black", ["h0"]],
["ultrasonic2:TRIG", "esp32:D14", "orange", ["h0"]],
["ultrasonic2:ECHO", "esp32:D27", "purple", ["h0"]],
["ultrasonic3:VCC", "esp32:VIN", "red", ["h0"]],
["ultrasonic3:GND", "esp32:GND.1", "black", ["h0"]],
["ultrasonic3:TRIG", "esp32:D26", "orange", ["h0"]],
["ultrasonic3:ECHO", "esp32:D25", "purple", ["h0"]],
["servo1:V+", "esp32:VIN", "red", ["h0"]],
["servo1:GND", "esp32:GND.2", "black", ["h0"]],
["servo1:SIG", "esp32:D15", "orange", ["h0"]],
["led1:A", "r1:1", "green", ["v0"]],
["r1:2", "esp32:D16", "green", ["v0"]],
["led1:C", "esp32:GND.1", "black", ["v0"]],
["led2:A", "r2:1", "green", ["v0"]],
["r2:2", "esp32:D17", "green", ["v0"]],
["led2:C", "esp32:GND.1", "black", ["v0"]],
["led3:A", "r3:1", "green", ["v0"]],
["r3:2", "esp32:D18", "green", ["v0"]],
["led3:C", "esp32:GND.1", "black", ["v0"]],
["led4:A", "r4:1", "red", ["v0"]],
["r4:2", "esp32:D19", "red", ["v0"]],
["led4:C", "esp32:GND.1", "black", ["v0"]],
["led5:A", "r5:1", "blue", ["v0"]],
["r5:2", "esp32:D2", "blue", ["v0"]],
["led5:C", "esp32:GND.2", "black", ["v0"]],
["bz1:1", "esp32:D23", "orange", ["h0"]],
["bz1:2", "esp32:GND.2", "black", ["h0"]],
["btn1:1.l", "esp32:D32", "green", ["h0"]],
["btn1:2.l", "esp32:GND.1", "black", ["h0"]],
["btn2:1.l", "esp32:D33", "red", ["h0"]],
["btn2:2.l", "esp32:GND.1", "black", ["h0"]]
],
"dependencies": {}
}
Arduino Code
/* * ESP32 SMART PARKING SYSTEM WITH WiFi & IoT * MakeMindz.com — IoT Project Series * * Features: 3× HC-SR04 slots, WiFi web server, * LCD (I2C), Servo gate, LEDs, Buzzer, * Entry/Exit buttons, JSON REST API */ #include <WiFi.h> #include <WebServer.h> #include <Wire.h> #include <LiquidCrystal_I2C.h> #include <ESP32Servo.h> // ── WiFi Credentials ────────────────────────── const char* ssid = "Wokwi-GUEST"; const char* password = ""; // ── Ultrasonic Sensors ───────────────────────── #define TRIG1 13 #define ECHO1 12 // Slot 1 #define TRIG2 14 #define ECHO2 27 // Slot 2 #define TRIG3 26 #define ECHO3 25 // Slot 3 // ── LEDs ────────────────────────────────────── #define LED_SLOT1 16 // Green — Slot 1 available #define LED_SLOT2 17 // Green — Slot 2 available #define LED_SLOT3 18 // Green — Slot 3 available #define LED_FULL 19 // Red — Parking full #define LED_WIFI 2 // Blue — WiFi heartbeat // ── Other Peripherals ───────────────────────── #define SERVO_PIN 15 #define BUZZER_PIN 23 #define ENTRY_BTN 32 #define EXIT_BTN 33 // I2C: SDA = D21, SCL = D22 (ESP32 default) // ── Global Objects ──────────────────────────── LiquidCrystal_I2C lcd(0x27, 16, 2); Servo barrierGate; WebServer server(80); // ── State Variables ─────────────────────────── bool slot1Occupied = false, slot2Occupied = false, slot3Occupied = false; int availableSlots = 3; const int totalSlots = 3; const int THRESHOLD_DISTANCE = 15; // cm bool gateOpen = false; unsigned long gateOpenTime = 0; const unsigned long GATE_AUTO_CLOSE = 3000; const int GATE_CLOSED = 0, GATE_OPEN = 90; unsigned long totalEntries = 0, totalExits = 0, systemUptime = 0; bool wifiConnected = false; // ────────────────────────────────────────────── // SETUP // ────────────────────────────────────────────── void setup() { Serial.begin(115200); // LCD lcd.init(); lcd.backlight(); lcd.clear(); lcd.setCursor(0,0); lcd.print("ESP32 PARKING"); lcd.setCursor(0,1); lcd.print("Initializing..."); // Pins pinMode(TRIG1,OUTPUT); pinMode(ECHO1,INPUT); pinMode(TRIG2,OUTPUT); pinMode(ECHO2,INPUT); pinMode(TRIG3,OUTPUT); pinMode(ECHO3,INPUT); pinMode(LED_SLOT1,OUTPUT); pinMode(LED_SLOT2,OUTPUT); pinMode(LED_SLOT3,OUTPUT); pinMode(LED_FULL,OUTPUT); pinMode(LED_WIFI,OUTPUT); pinMode(BUZZER_PIN,OUTPUT); pinMode(ENTRY_BTN,INPUT_PULLUP); pinMode(EXIT_BTN,INPUT_PULLUP); barrierGate.attach(SERVO_PIN); barrierGate.write(GATE_CLOSED); testLEDs(); connectWiFi(); setupWebServer(); lcd.clear(); lcd.setCursor(0,0); lcd.print("SYSTEM READY!"); lcd.setCursor(0,1); if (wifiConnected) { lcd.print("IP:"); lcd.print(WiFi.localIP().toString().substring(0,11)); } else { lcd.print("WiFi: Offline"); } delay(2000); systemUptime = millis(); } // ────────────────────────────────────────────── // LOOP // ────────────────────────────────────────────── void loop() { server.handleClient(); checkParkingSlots(); updateLCD(); updateLEDs(); // Entry button if (digitalRead(ENTRY_BTN) == LOW) { delay(50); if (digitalRead(ENTRY_BTN) == LOW) { handleEntry(); while(digitalRead(ENTRY_BTN)==LOW); } } // Exit button if (digitalRead(EXIT_BTN) == LOW) { delay(50); if (digitalRead(EXIT_BTN) == LOW) { handleExit(); while(digitalRead(EXIT_BTN)==LOW); } } // Auto-close gate after 3 seconds if (gateOpen && (millis() - gateOpenTime > GATE_AUTO_CLOSE)) closeGate(); // WiFi LED heartbeat blink every 1 second static unsigned long lastBlink = 0; if (wifiConnected && millis() - lastBlink > 1000) { digitalWrite(LED_WIFI, !digitalRead(LED_WIFI)); lastBlink = millis(); } delay(200); } // ── Ultrasonic Distance Reading ──────────────── long getDistance(int trig, int echo) { digitalWrite(trig, LOW); delayMicroseconds(2); digitalWrite(trig, HIGH); delayMicroseconds(10); digitalWrite(trig, LOW); long dur = pulseIn(echo, HIGH, 30000); return dur * 0.034 / 2; } // ── Check All Parking Slots ─────────────────── void checkParkingSlots() { bool p1 = slot1Occupied, p2 = slot2Occupied, p3 = slot3Occupied; slot1Occupied = (getDistance(TRIG1,ECHO1) > 0 && getDistance(TRIG1,ECHO1) < THRESHOLD_DISTANCE); slot2Occupied = (getDistance(TRIG2,ECHO2) > 0 && getDistance(TRIG2,ECHO2) < THRESHOLD_DISTANCE); slot3Occupied = (getDistance(TRIG3,ECHO3) > 0 && getDistance(TRIG3,ECHO3) < THRESHOLD_DISTANCE); if (p1!=slot1Occupied) logSlotChange(1,slot1Occupied); if (p2!=slot2Occupied) logSlotChange(2,slot2Occupied); if (p3!=slot3Occupied) logSlotChange(3,slot3Occupied); availableSlots = (!slot1Occupied) + (!slot2Occupied) + (!slot3Occupied); if (availableSlots == 0) alertParkingFull(); } // ── LCD Update ──────────────────────────────── void updateLCD() { lcd.clear(); lcd.setCursor(0,0); lcd.print("Available: "); lcd.print(availableSlots); lcd.print("/"); lcd.print(totalSlots); lcd.setCursor(0,1); lcd.print("S1:"); lcd.print(slot1Occupied ? "X" : "O"); lcd.print(" S2:"); lcd.print(slot2Occupied ? "X" : "O"); lcd.print(" S3:"); lcd.print(slot3Occupied ? "X" : "O"); } // ── LED Update ──────────────────────────────── void updateLEDs() { digitalWrite(LED_SLOT1, !slot1Occupied); digitalWrite(LED_SLOT2, !slot2Occupied); digitalWrite(LED_SLOT3, !slot3Occupied); digitalWrite(LED_FULL, availableSlots == 0); } // ── Entry Handler ───────────────────────────── void handleEntry() { totalEntries++; if (availableSlots > 0) { openGate(); digitalWrite(BUZZER_PIN, HIGH); delay(200); digitalWrite(BUZZER_PIN, LOW); lcd.clear(); lcd.setCursor(0,0); lcd.print("WELCOME!"); lcd.setCursor(0,1); lcd.print("Gate Opening..."); delay(1500); } else { lcd.clear(); lcd.setCursor(0,0); lcd.print("SORRY!"); lcd.setCursor(0,1); lcd.print("PARKING FULL"); for (int i=0; i<3; i++) { digitalWrite(BUZZER_PIN,HIGH); delay(200); digitalWrite(BUZZER_PIN,LOW); delay(200); } delay(1000); } } // ── Exit Handler ────────────────────────────── void handleExit() { totalExits++; openGate(); digitalWrite(BUZZER_PIN,HIGH); delay(200); digitalWrite(BUZZER_PIN,LOW); lcd.clear(); lcd.setCursor(0,0); lcd.print("THANK YOU!"); lcd.setCursor(0,1); lcd.print("Please Exit..."); delay(1500); } // ── Gate Control ────────────────────────────── void openGate() { if (!gateOpen) { barrierGate.write(GATE_OPEN); gateOpen=true; gateOpenTime=millis(); } } void closeGate() { if (gateOpen) { barrierGate.write(GATE_CLOSED); gateOpen=false; } } // ── Alerts & Logging ────────────────────────── void alertParkingFull() { static unsigned long lastAlert=0; if (millis()-lastAlert > 5000) { Serial.println("⚠️ PARKING LOT FULL!"); for(int i=0;i<2;i++){digitalWrite(LED_FULL,HIGH);delay(200);digitalWrite(LED_FULL,LOW);delay(200);} digitalWrite(LED_FULL,HIGH); lastAlert=millis(); } } void logSlotChange(int slot, bool occupied) { Serial.printf("Slot %d → %s | Available: %d/%d\n", slot, occupied?"OCCUPIED":"FREE", availableSlots, totalSlots); } // ── LED Test on Boot ────────────────────────── void testLEDs() { digitalWrite(LED_SLOT1,HIGH); digitalWrite(LED_SLOT2,HIGH); digitalWrite(LED_SLOT3,HIGH); digitalWrite(LED_FULL,HIGH); digitalWrite(LED_WIFI,HIGH); delay(500); digitalWrite(LED_SLOT1,LOW); digitalWrite(LED_SLOT2,LOW); digitalWrite(LED_SLOT3,LOW); digitalWrite(LED_FULL,LOW); digitalWrite(LED_WIFI,LOW); delay(500); } // ── WiFi Connection ─────────────────────────── void connectWiFi() { WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); int attempts=0; while (WiFi.status()!=WL_CONNECTED && attempts<20) { delay(500); attempts++; } wifiConnected = (WiFi.status()==WL_CONNECTED); if (wifiConnected) { digitalWrite(LED_WIFI,HIGH); Serial.println("✓ WiFi: " + WiFi.localIP().toString()); } } // ── Web Server Setup ────────────────────────── void setupWebServer() { if (!wifiConnected) return; server.on("/", handleRoot); server.on("/api/status", handleAPIStatus); server.on("/api/open", handleAPIOpen); server.on("/api/stats", handleAPIStats); server.begin(); Serial.println("✓ Web server at http://" + WiFi.localIP().toString()); } // ── API: Status JSON ────────────────────────── void handleAPIStatus() { String json = "{"; json += "\"available\":" + String(availableSlots) + ","; json += "\"occupied\":" + String(totalSlots-availableSlots) + ","; json += "\"slot1\":" + String(slot1Occupied?"true":"false") + ","; json += "\"slot2\":" + String(slot2Occupied?"true":"false") + ","; json += "\"slot3\":" + String(slot3Occupied?"true":"false") + ","; json += "\"gate\":\"" + String(gateOpen?"open":"closed") + "\""; json += "}"; server.send(200, "application/json", json); } // ── API: Open Gate ──────────────────────────── void handleAPIOpen() { if (availableSlots > 0) { openGate(); server.send(200, "text/plain", "Gate opened!"); } else { server.send(403, "text/plain", "Parking full!"); } } // ── API: Stats JSON ─────────────────────────── void handleAPIStats() { String json = "{"; json += "\"totalEntries\":" + String(totalEntries) + ","; json += "\"totalExits\":" + String(totalExits) + ","; json += "\"uptime\":" + String((millis()-systemUptime)/1000) + ","; json += "\"wifiRSSI\":" + String(WiFi.RSSI()) + ","; json += "\"freeHeap\":" + String(ESP.getFreeHeap()) + "}"; server.send(200, "application/json", json); } // ── Root Web Dashboard ──────────────────────── void handleRoot() { // Full HTML dashboard with slot cards, stats, live fetch, entry/exit buttons // Auto-updates every 2 seconds via fetch('/api/status') // See the Wokwi simulation for the complete HTML source String html = "<!DOCTYPE html><html><head>"; html += "<meta name='viewport' content='width=device-width,initial-scale=1'>"; html += "<title>ESP32 Smart Parking</title>"; html += "<script>setInterval(()=>fetch('/api/status').then(r=>r.json()).then(d=>{"; html += "document.getElementById('av').textContent=d.available;"; html += "document.getElementById('oc').textContent=d.occupied;"; html += "}),2000);</script></head><body style='font-family:sans-serif;padding:20px'>"; html += "<h2>🅿️ Smart Parking</h2>"; html += "<p>Available: <strong id='av'>" + String(availableSlots) + "</strong> / 3</p>"; html += "<p>Occupied: <strong id='oc'>" + String(totalSlots-availableSlots) + "</strong></p>"; html += "<p>IP: " + WiFi.localIP().toString() + " | Uptime: " + String((millis()-systemUptime)/1000) + "s</p>"; html += "<button onclick=\"fetch('/api/open').then(r=>r.text()).then(alert)\">Open Gate</button>"; html += "</body></html>"; server.send(200, "text/html", html); }
LED Status Reference
Step-by-Step: How to Test
▶ Start the Simulation
Click Play in Wokwi. All 5 LEDs flash once (LED test). The LCD shows "Initializing…" then "SYSTEM READY!". WiFi connects within ~10 seconds.
📋 Find the IP Address
Open the Serial Monitor tab (bottom of Wokwi). Look for the line: ✓ WiFi: 10.0.0.2. Click that link to open the web dashboard.
🚗 Simulate a Car in Slot 1
Click the HC-SR04 ultrasonic1 sensor in Wokwi. Change the distance value from 400 cm to 10 cm. Slot 1's green LED turns OFF, the LCD updates to "Available: 2/3", and the web dashboard reflects the change in 2 seconds.
🚧 Fill All Slots
Set all three ultrasonic sensors to 10 cm. The red FULL LED turns ON, the LCD shows "Available: 0/3", and pressing ENTRY triggers 3 buzzer beeps with "PARKING FULL" on LCD.
🟢 Press the ENTRY Button
With a free slot available, press the green ENTRY button. The buzzer beeps once, LCD shows "WELCOME! Gate Opening…", and the servo rotates to 90°. It auto-closes after 3 seconds.
🔴 Press the EXIT Button
Press the red EXIT button. The buzzer beeps, LCD shows "THANK YOU! Please Exit…", and the gate opens then auto-closes. The exit counter increments in /api/stats.
🌐 Use the REST API
Access /api/status for live slot JSON, /api/open to trigger the gate remotely, and /api/stats for entry/exit totals, uptime, WiFi RSSI, and free heap memory.
▶ Try it Live in Wokwi Simulator
No hardware required — run the full smart parking simulation instantly in your browser. Adjust ultrasonic distances, press buttons, and watch the web dashboard update live.
🔗 Open Free Simulation
Comments
Post a Comment