Introduction
Welcome to the complete beginner-friendly guide for building a real-world IoT project using the ESP32 and HC-SR04 ultrasonic sensor. This tutorial walks through:
- Serial Monitor Output for basic testing
- ESP32 Web Server to display distance data in your browser
- Smooth UI with AJAX “watchdog” refresh
- MQTT publishing to HiveMQ + a Python subscriber script
This all assumes you have physical ESP32 hardware. No simulation. Let’s get started!
Guide Contents
- 1x ESP32 Dev Board
- 1x HC-SR04 Ultrasonic Sensor
- Jumper Wires
- Breadboard (optional)
HC-SR04 Pin | ESP32 Pin |
---|---|
VCC | 5V |
GND | GND |
TRIG | GPIO 5 |
ECHO | GPIO 18 |
Note: You may need a voltage divider on ECHO pin to bring 5V down to 3.3V if your HC-SR04 is 5V.
This sketch shows raw distance readings on the Serial Monitor every 500ms.
#define TRIG_PIN 5
#define ECHO_PIN 18
void setup() {
Serial.begin(115200);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
}
void loop() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH);
float distance = duration * 0.034 / 2;
Serial.print("Distance: ");
Serial.print(distance);
Serial.println(" cm");
delay(500);
}
A simple HTTP server that refreshes the page every second:
#include <WiFi.h>
#include <WebServer.h>
const char* ssid = "Your_SSID";
const char* password = "Your_PASSWORD";
#define TRIG_PIN 5
#define ECHO_PIN 18
WebServer server(80);
float getDistance() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH);
return duration * 0.034 / 2;
}
void handleRoot() {
float distance = getDistance();
int barWidth = constrain(distance * 3, 0, 300);
String html = "<!DOCTYPE html><html><head>"
"<meta http-equiv="refresh" content="1"/>"
"<style>"
"body { font-family: Arial; text-align: center; margin-top: 50px; }"
".bar-container { width: 300px; background: #eee; margin: 20px auto; border-radius: 10px; overflow: hidden; }"
".bar { height: 30px; background: #007BFF; width: \" + String(barWidth) + \"px; }"
"</style></head><body>"
"<h2>Distance: \" + String(distance, 2) + \" cm</h2>"
"<div class='bar-container'><div class='bar'></div></div>"
"</body></html>";
server.send(200, "text/html", html);
}
void setup() {
Serial.begin(115200);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) delay(500);
server.on("/", handleRoot);
server.begin();
}
void loop() {
server.handleClient();
}
This snippet can be extended to display a simple bar graph and auto-refresh every second.
A smoother approach using AJAX to update only the distance readout without refreshing the entire page.
#include <WiFi.h>
#include <WebServer.h>
const char* ssid = "Your_SSID";
const char* password = "Your_PASSWORD";
#define TRIG_PIN 5
#define ECHO_PIN 18
WebServer server(80);
float getDistance() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH);
return duration * 0.034 / 2;
}
void handleRoot() {
String html = "<!DOCTYPE html><html><head>"
"<title>Ultrasonic Distance</title>"
"<style>"
"body { font-family: Arial; text-align: center; margin-top: 50px; }"
".bar-container { width: 300px; background: #eee; margin: 20px auto; border-radius: 10px; overflow: hidden; }"
".bar { height: 30px; background: #007BFF; width: 0; transition: width 0.5s; }"
"</style></head><body>"
"<h2>Ultrasonic Distance</h2>"
"<p><strong id='distance'>Loading...</strong></p>"
"<div class='bar-container'><div class='bar' id='bar'></div></div>"
"<script>"
"function fetchDistance() {"
" fetch('/distance').then(r => r.text()).then(d => {"
" document.getElementById('distance').innerText = d + ' cm';"
" document.getElementById('bar').style.width = Math.min(d * 3, 300) + 'px';"
" });"
"}"
"setInterval(fetchDistance, 1000);"
"fetchDistance();"
"</script>"
"</body></html>";
server.send(200, "text/html", html);
}
void handleDistance() {
float distance = getDistance();
server.send(200, "text/plain", String(distance, 2));
}
void setup() {
Serial.begin(115200);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) delay(500);
server.on("/", handleRoot);
server.on("/distance", handleDistance);
server.begin();
}
void loop() {
server.handleClient();
}
See the MqttHello example for more details on how to set up the routes.
Send distance readings to broker.hivemq.com or another broker, then read them with a Python script.
#include <WiFi.h>
#include <PubSubClient.h>
const char* ssid = "Your_SSID";
const char* password = "Your_PASSWORD";
const char* mqtt_server = "broker.hivemq.com";
const char* mqtt_topic = "esp32/demo/distance";
WiFiClient espClient;
PubSubClient client(espClient);
#define TRIG_PIN 5
#define ECHO_PIN 18
float getDistance() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH);
return duration * 0.034 / 2;
}
void reconnect() {
while (!client.connected()) {
if (client.connect("ESP32Client")) {
Serial.println("MQTT Connected");
} else {
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) delay(500);
client.setServer(mqtt_server, 1883);
}
void loop() {
if (!client.connected()) reconnect();
client.loop();
float distance = getDistance();
String payload = String(distance, 2);
client.publish(mqtt_topic, payload.c_str());
Serial.println("Published: " + payload + " cm");
delay(1000);
}
import paho.mqtt.client as mqtt
broker = "broker.hivemq.com"
topic = "esp32/demo/distance"
def on_connect(client, userdata, flags, rc):
print("Connected with result code " + str(rc))
client.subscribe(topic)
def on_message(client, userdata, msg):
print(f"Received: {msg.payload.decode()} cm")
client = mqtt.Client("PythonClient")
client.on_connect = on_connect
client.on_message = on_message
client.connect(broker, 1883, 60)
client.loop_forever()