ESP32 Ultrasonic Distance Meter

Build a real-world IoT project measuring distances with HC-SR04, featuring Serial output, a Web UI, and MQTT integration.

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()