Parking in a tight garage is easier with a guide. This sensor sits on the wall in front of the parking spot, at bumper height. As the car comes closer, the light changes from green to yellow to red: stop! At the same time the ESP32 tells Blynk whether the spot is taken, so you can check from anywhere whether the car is home, and get a notification when it arrives or leaves. A spot in a shared garage, a bike rack, or a toy car garage on your desk work the same way.
What you'll learn
- How to measure distance with the ultrasonic sensor on the ESP32 safely
- How to turn a distance into a traffic light
- How to decide "occupied" only when the reading is steady
- How to send different Blynk events for different changes
What you need
- ESP32 board, expansion board and USB cable (ESP32 IoT Starter Kit)
- Ultrasonic sensor HC-SR04 (ESP32 IoT Starter Kit)
- 3 resistors, 10 kΩ, for the voltage divider (ESP32 IoT Starter Kit)
- Green, yellow and red LEDs and 3 resistors, 220 Ω (ESP32 IoT Starter Kit)
- Long jumper wires or a length of cable
- A free Blynk account and the Blynk IoT app on a phone
- A 2.4 GHz Wi-Fi network
- The printed parts from the 3D printing box below (wall mount), or the cardboard version described there
- Breadboard and jumper wires (ESP32 IoT Starter Kit)
Wiring

| From | To |
|---|---|
| Ultrasonic VCC | 5V |
| Ultrasonic Trig | ESP32 GPIO 26 |
| Ultrasonic Echo | 10 kΩ resistor, then ESP32 GPIO 27 |
| ESP32 GPIO 27 | Two 10 kΩ resistors in a row, then GND |
| Ultrasonic GND | GND |
| ESP32 GPIO 25 | 220 Ω resistor, then the green LED's long leg |
| ESP32 GPIO 33 | 220 Ω resistor, then the yellow LED |
| ESP32 GPIO 32 | 220 Ω resistor, then the red LED |
| All LED short legs | GND |
Protect the ESP32
The HC-SR04 runs on 5 V and answers with 5 V on its Echo pin, but the ESP32's pins are made for 3.3 V. Three equal 10 kΩ resistors fix that: one from Echo to the ESP32 pin, and two in a row from the ESP32 pin to GND. The pin then sees two thirds of 5 V, about 3.3 V. The radar capstone explains it in detail.
Set up Blynk
In the Blynk console, create a new template called Parking Spot (hardware ESP32, connection type Wi-Fi) and add these datastreams. Lesson 1 of this path shows each step in detail.
| Virtual pin | Name | Data type | Range | Phone widget |
|---|---|---|---|---|
| V0 | Distance | Integer | 0–400 | Gauge |
| V1 | Spot | String | – | Label |
| V2 | Taken | Integer | 0–1 | LED |
Then open the template's Events & Notifications tab, add an event with the code car_arrived (name: Car arrived) and turn on push notifications for it. The event code must match the sketch exactly.
Save the template, create a new device from it and copy its three BLYNK_… lines. In the phone app, open the device, tap the wrench icon and add a Gauge (V0), a Label (V1) and an LED (V2). Link each widget to its datastream.
Add a second event with the code car_left (name: Car left) and turn on notifications for it too.

The sketch
Replace the three BLYNK_… lines at the top with the ones from your device's Device info page, and add your Wi-Fi name and password (2.4 GHz only). The BLYNK_… lines must stay above #include <BlynkSimpleEsp32.h>.
Measure the distance from the wall to the bumper when the car is parked just right, and put it into STOP_CM.
#define BLYNK_TEMPLATE_ID "YOUR_TEMPLATE_ID"
#define BLYNK_TEMPLATE_NAME "Parking Spot"
#define BLYNK_AUTH_TOKEN "YOUR_AUTH_TOKEN"
#define BLYNK_PRINT Serial // show connection messages in the Serial Monitor
#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
char ssid[] = "YOUR_WIFI_NAME";
char pass[] = "YOUR_WIFI_PASSWORD";
const int TRIG_PIN = 26, ECHO_PIN = 27;
const int GREEN_PIN = 25, YELLOW_PIN = 33, RED_PIN = 32;
const int STOP_CM = 40; // red from here: the car is parked
const int SLOW_CM = 100; // yellow from here
const int TAKEN_CM = 150; // anything closer than this counts as a car
const unsigned long STEADY = 10000; // must stay the same for 10 s
BlynkTimer timer;
int distance = 400;
bool taken = false;
bool candidate = false;
unsigned long candidateSince = 0;
int measureCm() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long us = pulseIn(ECHO_PIN, HIGH, 30000);
return us == 0 ? 400 : min(us / 58, 400L);
}
void lights() {
bool near = distance < TAKEN_CM;
digitalWrite(GREEN_PIN, near && distance >= SLOW_CM);
digitalWrite(YELLOW_PIN, near && distance >= STOP_CM && distance < SLOW_CM);
digitalWrite(RED_PIN, near && distance < STOP_CM);
}
void measure() {
// the median of three readings ignores a single wrong echo
int a = measureCm(), b = measureCm(), c = measureCm();
distance = max(min(a, b), min(max(a, b), c));
lights();
bool nowTaken = distance < TAKEN_CM;
if (nowTaken != candidate) {
candidate = nowTaken;
candidateSince = millis();
}
if (candidate != taken && millis() - candidateSince > STEADY) {
taken = candidate;
Blynk.virtualWrite(V1, taken ? "Taken" : "Free");
Blynk.virtualWrite(V2, taken ? 1 : 0);
if (taken) Blynk.logEvent("car_arrived", "The car is parked.");
else Blynk.logEvent("car_left", "The parking spot is free.");
}
}
void report() {
Blynk.virtualWrite(V0, distance);
}
BLYNK_CONNECTED() {
Blynk.virtualWrite(V1, taken ? "Taken" : "Free");
Blynk.virtualWrite(V2, taken ? 1 : 0);
}
void setup() {
Serial.begin(115200);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(GREEN_PIN, OUTPUT);
pinMode(YELLOW_PIN, OUTPUT);
pinMode(RED_PIN, OUTPUT);
Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass);
timer.setInterval(250L, measure); // fast for the lights
timer.setInterval(2000L, report); // slower for the app
}
void loop() {
Blynk.run();
timer.run();
}
How it works
The lights need quick updates, four times a second, while the app only needs a new distance every two seconds. Two BlynkTimer intervals keep those jobs apart, so the sketch doesn't flood Blynk with messages.
An ultrasonic echo sometimes bounces off the wrong thing, and one wild reading could switch the light to red for a moment. The sketch takes three readings and uses the middle one, the median, which ignores a single outlier.
A person walking past shouldn't count as a car arriving. The spot only changes to "Taken" or "Free" when the new state has lasted for 10 seconds. Until then, the change is only a candidate. This trick appears in many sensors that must not raise false alarms.
Try this next
- Make the red LED blink when the car is closer than 20 cm: too far!
- Count how many times the car leaves each day and show it in Blynk.
- Use two sensors for a double garage and show both spots in the app.
Troubleshooting
-
The device shows "Offline" in the app: check the Serial Monitor at 115200 baud. "Invalid auth token" means the
BLYNK_AUTH_TOKENline is wrong; if it never connects, check the Wi-Fi name and password, and that your network is 2.4 GHz. - The distance is always 400: check VCC on 5V, Trig on GPIO 26 and the voltage divider on GPIO 27.
-
It says "Taken" with no car: the sensor sees something closer than 150 cm, like a bike. Make
TAKEN_CMsmaller. -
No notifications: check that both events exist with exactly the codes
car_arrivedandcar_left, with notifications on. - A printed part is too tight or too loose: printers differ by a few tenths of a millimetre. Sand or file a tight hole, and fix a loose part with a drop of hot glue or a strip of tape.