If you've got an ESP32 sitting on your desk collecting dust or collecting sensor readings you can't actually use publishing that data over MQTT is one of the fastest ways to make it useful. MQTT is lightweight, runs well on microcontrollers, and connects your sensor data to dashboards, databases, or automation systems with very little overhead. This article walks through exactly how an Arduino ESP32 sensor data publishing MQTT snippet works, what each part does, and how to avoid the mistakes that trip people up.

What does "publishing sensor data over MQTT" actually mean?

MQTT stands for Message Queuing Telemetry Transport. It's a messaging protocol designed for small devices with limited bandwidth. When your ESP32 reads a temperature sensor, a soil moisture sensor, or any other input, publishing over MQTT means wrapping that reading in a message and sending it to a broker a server that redistributes it to any subscribed client. Think of it like a radio station: the ESP32 broadcasts on a frequency (the topic), and any device tuned to that frequency receives the message.

This pattern is the backbone of most IoT setups. Whether you're logging greenhouse humidity, monitoring a 3D printer enclosure, or tracking room occupancy, the flow is the same: sense → read → publish.

What hardware and libraries do I need before writing the snippet?

You'll need three things ready to go:

  • An ESP32 development board any common variant (ESP32-WROOM, ESP32-S3, ESP32-C3) works fine.
  • A sensor for this example, a DHT22 temperature and humidity sensor is a solid starting point because it's widely available and simple to wire.
  • An MQTT broker Mosquitto running on a local machine or a cloud broker like HiveMQ's free tier. You need the broker's IP address or hostname.

On the software side, install these libraries through the Arduino IDE Library Manager:

  • PubSubClient by Nick O'Leary the standard MQTT library for Arduino.
  • DHT sensor library by Adafruit for reading the DHT22.
  • WiFi built into the ESP32 board package, no extra install needed.

Make sure you have the ESP32 board package installed under Tools → Board → Boards Manager. If you're coming from an RP2040 or Raspberry Pi Pico background, the setup is different but the Arduino IDE workflow feels familiar.

How does the MQTT publishing snippet actually work?

Here's what the code does, broken into its core sections:

1. Connect to Wi-Fi

The ESP32 joins your local network using WiFi.begin(ssid, password). Without a working Wi-Fi connection, nothing else happens. The snippet waits in a loop until WiFi.status() returns WL_CONNECTED.

2. Connect to the MQTT broker

Once online, the PubSubClient connects to your broker with client.connect("ESP32Client"). If your broker requires authentication, you pass a username and password. The client ID ("ESP32Client") should be unique per device if two devices share the same ID, the broker will kick one off.

3. Read the sensor

A dht.readTemperature() and dht.readHumidity() call grabs the current values. Always check for NaN returns DHT sensors sometimes fail to respond, especially with long wire runs.

4. Publish the data

This is the key line:

client.publish("home/livingroom/temperature", String(temp).c_str());

The first argument is the topic a hierarchical string that determines where the message goes. Subscribers listening to home/livingroom/temperature will receive it. The second argument is the payload, converted to a C-style string.

5. Loop and keep alive

client.loop() must run regularly to maintain the connection and process incoming messages. Place it inside your loop() function. Use a non-blocking timer (like millis()) instead of delay() to publish at intervals, so the loop can still handle MQTT keep-alive packets.

Why do my MQTT messages never arrive at the broker?

This is the number one frustration people hit. Here are the usual causes:

  • Wrong broker IP or port the default MQTT port is 1883. Double-check you're not using the web socket port (usually 9001) by mistake.
  • Firewall blocking port 1883 if your broker runs on a Linux machine, sudo ufw allow 1883 opens it up.
  • Client ID collision two ESP32s using the same client ID will fight over the connection. Append a unique suffix like the MAC address.
  • Using delay() instead of millis() delay(5000) blocks the loop, which prevents client.loop() from running. The broker thinks the client dropped and disconnects it.
  • Topic format issues topics are case-sensitive and shouldn't contain spaces. Stick to lowercase with forward slashes.

If you're building more complex firmware that needs task scheduling or RTOS integration, the approach in this STM32 RTOS task scheduling guide shows how to separate sensor reads from network operations cleanly.

How should I structure MQTT topics for sensor data?

A consistent topic structure saves you headaches later. Use a hierarchy that matches your physical layout:

  1. Location/device/reading kitchen/esp32-01/temperature
  2. Prefix with a project name myhome/kitchen/temperature
  3. Add units in the payload, not the topic keep topics stable even if you change how you format data.

Avoid making topics too deep (more than 5 levels gets hard to manage) and avoid putting dynamic data like timestamps in the topic itself. Timestamps belong in the JSON payload.

Should I send plain numbers or JSON payloads?

For a single reading, a plain number works fine and keeps bandwidth low. But once you have multiple values from one device, JSON is the better format:

{"temperature": 23.5, "humidity": 61.2, "timestamp": 1718000000}

JSON lets you publish all readings on a single topic, which reduces the number of messages and makes parsing on the receiving end simpler. Libraries like ArduinoJson handle serialization without eating too much memory on the ESP32.

Keep payload size under the PubSubClient default of 256 bytes, or increase it with client.setBufferSize(512) if your JSON gets longer.

What common mistakes waste hours of debugging?

  • Not calling client.loop() frequently enough this is the silent killer. The connection drops, and you get no obvious error.
  • Publishing inside the main loop without a timer flooding the broker with messages every few milliseconds wastes bandwidth and can get your client rate-limited.
  • Forgetting to reconnect after a drop Wi-Fi hiccups happen. Wrap client.connect() in a reconnection check inside your loop.
  • Using String objects in long-running code Arduino's String class fragments heap memory over time on ESP32. For production code, use char arrays and snprintf().
  • Skipping QoS settings PubSubClient defaults to QoS 0 (fire and forget). If you need reliable delivery, set QoS 1 to get at-least-once delivery.

These mistakes show up across different microcontroller platforms too. Developers working with BLE on Zephyr RTOS run into similar debugging patterns as covered in this Zephyr BLE peripheral walkthrough.

How do I test that my setup actually works?

Before trusting any dashboard, verify end-to-end with a simple subscriber. Install the Mosquitto client tools on your computer and run:

mosquitto_sub -h YOUR_BROKER_IP -t "home/#"

The # is a wildcard that matches all topics under home/. If your ESP32 is publishing correctly, you'll see data appear in this terminal within seconds. If not, the problem is between the ESP32 and the broker check Wi-Fi, broker address, and credentials.

Once the subscriber confirms data is flowing, connect your dashboard (Node-RED, Home Assistant, Grafana with a MQTT datasource) to the same broker and topic.

Quick checklist before you deploy

  1. ESP32 connects to Wi-Fi and stays connected (watch serial monitor for drops).
  2. MQTT client connects to broker with a unique client ID.
  3. Sensor readings return valid numbers (no NaN or 0.0 values when they shouldn't be).
  4. Messages arrive at the broker (verify with mosquitto_sub).
  5. client.loop() runs every cycle, not blocked by delay().
  6. Reconnection logic exists for both Wi-Fi and MQTT.
  7. Publishing interval is reasonable (once every 5–30 seconds for most sensors).

Next step: Get the basic snippet running with a DHT22 and a local Mosquitto broker first. Once that works, add a second sensor, switch to JSON payloads, and hook the data into a dashboard. If you're into choosing distinctive names for your IoT projects or dashboards, you might find interesting typeface ideas from Nexa Rust a font that carries a raw, industrial feel fitting for hardware projects. Build one working pipeline before scaling up. That's how reliable IoT systems are made.