/*
Wokwi | questions
anyone got a good WIFI smart parking system using the ESP 32?
Raz [E33] - Thursday, April 16, 2026 10:20 AM
The idea is basically to first have an LCD screen that would
show empty/occupied spots for a smart Car parking garage.
Then detect movement with a PIR sensor which would then open a
"gate" by operating a servo (this will be applied for an entry
gate and the exit gate).
Then have around 6 spaces with IR sensors where if the car positions
itself in front of it. would then be marked a occupied and update
the counter on the LCD.
And my vision for this project is to basically use the ESP 32 to also
make an web dashboard that would allow me to check through the dash board
the information that the LCD would also give. and also act as manual way
to open/close the gates/servos.
Sensors and servos may require level shifters in real circuits!
TODO: pulseIn is blocking, makes gates jerky
*/
#include <Arduino.h>
#include <WiFi.h>
#include <slowServo.h>
#include <ESP32Servo.h>
#include <LiquidCrystal_I2C.h>
#include "timezones.h"
const int NUM_SPACES = 6;
const int NUM_SENSORS = 2; // PIRs
const int THRESHOLD = 100; // 100cm
const int GATE_SENSE_PINS[] = {34, 17};
const int IN_SERVO_PIN = 25;
const int OUT_SERVO_PIN = 16;
const int TRIG_PIN = 12;
const int ECHO_PINS[] = {35, 32, 33, 14, 27, 26};
const int FIELD_POS[] = {0, 1, 10, 1, 0, 2, 10, 2, 0, 3, 10, 3};
// choose your timezone here, see timezones.h
const char* TIMEZONE = TZ_NEW_YORK;
const char* NTPSERVER = "pool.ntp.org";
bool isFull = false;
int emptySpaces = 0;
int distance[NUM_SPACES];
int sensorState[NUM_SENSORS];
int oldSensorState[NUM_SENSORS];
timeObj inGateTime; // create time objects
timeObj outGateTime;
timeObj senseAllTime;
timeObj senseEachTime;
slowServo enterServo(IN_SERVO_PIN); // create slow servos
slowServo exitServo(OUT_SERVO_PIN);
LiquidCrystal_I2C lcd(0x27, 20, 4);
// function returns which PIR triggered, or 0 if none
int checkPIRs() {
int sensorTriggered = 0;
for (int i = 0; i < NUM_SENSORS; i++) {
sensorState[i] = digitalRead(GATE_SENSE_PINS[i]); // check each sensor
if (sensorState[i] != oldSensorState[i]) { // if it changed
oldSensorState[i] = sensorState[i]; // remember state for next time
if (sensorState[i] == 1) { // was just triggered
sensorTriggered = i + 1;
}
delay(20); // debounce
}
}
return sensorTriggered;
}
int getDistance(int senseNum) { // in centimeters
float dist = 0.0;
// send trigger
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// calculate distance
unsigned long duration = pulseIn(ECHO_PINS[senseNum], HIGH, 25000);
dist = (duration * 0.0343) / 2;
// return distance as an integer
return (int)(dist + 0.5);
}
void initDisplay() {
for (int i = 0; i < NUM_SPACES; i++) {
lcd.setCursor(FIELD_POS[i * 2], FIELD_POS[(i * 2) + 1]);
lcd.print(i + 1);
}
}
void printTime() {
static time_t last_now;
char buffer[20];
time_t now;
time(&now);
if (last_now == now) return;
last_now = now;
struct tm *t = localtime(&now); // Convert to local time structure
snprintf(buffer, 20, "%2d:%02d", t->tm_hour, t->tm_min);
lcd.setCursor(14, 0);
lcd.print(buffer);
//Serial.print(ctime(&now));
}
void setupWiFi() {
WiFi.begin("Wokwi-GUEST", "", 6);
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
Serial.print("\nConnected: ");
Serial.println(WiFi.localIP());
}
void setupNTP() {
configTzTime(TIMEZONE, NTPSERVER);
}
void showSplash() {
lcd.setCursor(3, 1);
lcd.print("Smart Parking");
lcd.setCursor(7, 2);
lcd.print("V1.00");
delay(2000);
lcd.clear();
initDisplay();
Serial.println("\nSystem ready...");
}
bool sweepSpaces() {
static int sensorNum = 0;
static int spaces = NUM_SPACES;
bool isDone = false;
if (senseEachTime.ding()) {
//Serial.println(sensorNum);
distance[sensorNum] = getDistance(sensorNum);
if (distance[sensorNum] < THRESHOLD) {
spaces--;
}
sensorNum++;
senseEachTime.start();
if (sensorNum == NUM_SPACES) {
isDone = true;
sensorNum = 0;
emptySpaces = spaces;
spaces = NUM_SPACES;
}
}
return isDone;
}
void updateLCD() {
lcd.setCursor(0, 0);
if (emptySpaces == 0) {
isFull = true;
lcd.print("Lot FULL ");
} else {
lcd.print("Spaces: ");
lcd.print(emptySpaces);
isFull = false;
}
for (int i = 0; i < NUM_SPACES; i++) {
lcd.setCursor(FIELD_POS[i * 2] + 3, FIELD_POS[(i * 2) + 1]);
lcd.print(distance[i] < THRESHOLD ? "FULL " : "EMPTY");
//Serial.print("Space ");
//Serial.print(i + 1);
//Serial.print(": ");
//Serial.println(distance[i] < THRESHOLD ? "FULL \n" : "EMPTY\n");
}
}
void setup() {
Serial.begin(115200);
lcd.init();
lcd.backlight();
pinMode(TRIG_PIN, OUTPUT);
pinMode(GATE_SENSE_PINS[0], INPUT);
pinMode(GATE_SENSE_PINS[1], INPUT);
for (int i = 0; i < NUM_SPACES; i++) {
pinMode(ECHO_PINS[i], INPUT);
}
inGateTime.setTime(10000, false); // 10 secs, don't start yet
outGateTime.setTime(10000, false);
senseAllTime.setTime(500); // check all spaces every 500mS
senseEachTime.setTime(50); // check each space at 50mS delay
enterServo.begin();
exitServo.begin();
enterServo.setMsPerDeg(50); // set servo speed here
exitServo.setMsPerDeg(50);
setupWiFi();
setupNTP();
enterServo.setDeg(0);
exitServo.setDeg(0);
showSplash();
}
void loop() {
idle(); // yield so timers run
int senseNum = checkPIRs();
if (senseNum) {
if (senseNum == 1) {
if (!isFull) {
Serial.println("Enter opening");
enterServo.setDeg(90);
inGateTime.start();
} else {
Serial.println("Lot full!");
}
} else {
Serial.println("Exit opening");
exitServo.setDeg(90);
outGateTime.start();
}
}
if (inGateTime.ding()) {
if (!digitalRead(GATE_SENSE_PINS[0])) {
Serial.println("Enter closing!");
enterServo.setDeg(0);
inGateTime.reset();
}
}
if (outGateTime.ding()) {
if (!digitalRead(GATE_SENSE_PINS[1])) {
Serial.println("Exit closing!");
exitServo.setDeg(0);
outGateTime.reset();
}
}
if (senseAllTime.ding()) {
bool isDone = sweepSpaces();
if (isDone) {
updateLCD();
senseAllTime.start();
}
}
printTime();
delay(10); // this speeds up the Wokwi simulation
}
Enter
Exit