An ESP32 DS18B20 MQTT setup lets one GPIO pin read many waterproof temperature probes and publish each to its own topic. In this tutorial you will wire several DS18B20 sensors on one 1-Wire bus, identify them by ROM address, read them without blocking the MQTT client, and publish to broker.connectmq.com with a topic per probe.
Why the DS18B20 Works Well for Multi-Point Monitoring
The DS18B20 is a digital temperature sensor with a unique 64-bit ROM address burned in at the factory. Because every probe has its own address, dozens can share the same three wires: power, ground and data. That makes it ideal for jobs like:
- Flow and return temperatures on a heating system.
- Several shelves in a fridge or cold room.
- Aquarium, pond and ambient air readings from one controller.
- Battery pack or enclosure hot-spot monitoring.
The stainless-steel waterproof versions are cheap and robust, with an accuracy around half a degree over the typical range. The trade-off is speed: a full 12-bit conversion takes up to 750 ms, which is why non-blocking reads matter once MQTT is involved.
Parts and Libraries
- An ESP32 development board.
- Two or more DS18B20 sensors (TO-92 or waterproof probe).
- One 4.7 kOhm resistor for the bus pull-up.
- Arduino IDE 2.x with the esp32 board package.
- Libraries: OneWire (by Paul Stoffregen, or the maintained fork bundled for ESP32), DallasTemperature by Miles Burton, and PubSubClient.
Wiring Several Probes to One ESP32 Pin
All probes connect in parallel:
| Probe wire (typical colours) | Connects to | Notes |
|---|---|---|
| Red (VDD) | ESP32 3V3 | Use normal power, not parasite mode |
| Black (GND) | ESP32 GND | |
| Yellow (DATA) | ESP32 GPIO 4 | One 4.7 kOhm resistor from DATA to 3V3 for the whole bus |
Use only one pull-up resistor for the bus, placed near the ESP32. Wire colours vary between suppliers, so check the datasheet of your probes. For runs longer than a few metres, use twisted-pair cable, keep a linear bus rather than a star, and consider a stronger pull-up such as 2.2 kOhm.
Topic Layout per Probe
Each probe publishes to a topic containing its ROM address, so topics stay stable no matter in which order the bus is enumerated:
| Topic | Retained | Example payload |
|---|---|---|
connectmq/demo/dsb2q6h/ds18b20/28ff641e8316034f/temperature |
Yes | 21.62 |
connectmq/demo/dsb2q6h/ds18b20/summary |
No | {"28ff641e8316034f":21.62,"28aa1b2c3d4e5f60":4.12} |
connectmq/demo/dsb2q6h/status |
Yes | online / offline |
The per-probe topics are retained so a dashboard opened later immediately shows the last value of every probe. The summary topic is convenient for consumers that want one message per cycle. Both stay within the eight topic levels allowed on the public area.
The ESP32 DS18B20 MQTT Sketch
#include <WiFi.h>
#include <PubSubClient.h>
#include <OneWire.h>
#include <DallasTemperature.h>
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* PREFIX = "connectmq/demo/dsb2q6h"; // use your own random id
const uint8_t ONE_WIRE_PIN = 4;
const uint8_t MAX_PROBES = 10;
const uint32_t INTERVAL_MS = 10000;
const uint32_t CONVERSION_MS = 750; // 12-bit resolution
OneWire oneWire(ONE_WIRE_PIN);
DallasTemperature sensors(&oneWire);
WiFiClient net;
PubSubClient mqtt(net);
DeviceAddress probes[MAX_PROBES];
char probeIds[MAX_PROBES][17];
uint8_t probeCount = 0;
String clientId, topicStatus;
unsigned long lastRequest = 0;
bool conversionPending = false;
void addressToHex(const DeviceAddress addr, char* out) {
for (uint8_t i = 0; i < 8; i++) sprintf(out + i * 2, "%02x", addr[i]);
out[16] = '\0';
}
void discoverProbes() {
sensors.begin();
probeCount = 0;
uint8_t found = sensors.getDeviceCount();
for (uint8_t i = 0; i < found && probeCount < MAX_PROBES; i++) {
if (sensors.getAddress(probes[probeCount], i)) {
sensors.setResolution(probes[probeCount], 12);
addressToHex(probes[probeCount], probeIds[probeCount]);
Serial.printf("Probe %u: %s\n", probeCount, probeIds[probeCount]);
probeCount++;
}
}
sensors.setWaitForConversion(false); // we wait ourselves, without blocking
Serial.printf("%u probe(s) found\n", probeCount);
}
void connectWifi() {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) delay(400);
Serial.printf("Wi-Fi OK, IP %s\n", WiFi.localIP().toString().c_str());
}
void connectMqtt() {
while (!mqtt.connected()) {
if (mqtt.connect(clientId.c_str(), topicStatus.c_str(), 1, true, "offline")) {
mqtt.publish(topicStatus.c_str(), "online", true);
Serial.println("MQTT connected");
} else {
Serial.printf("MQTT failed, state %d\n", mqtt.state());
delay(5000);
}
}
}
void publishReadings() {
String summary = "{";
for (uint8_t i = 0; i < probeCount; i++) {
float c = sensors.getTempC(probes[i]);
bool valid = (c != DEVICE_DISCONNECTED_C) && (c != 85.0f); // 85.0 = power-on value
String topic = String(PREFIX) + "/ds18b20/" + probeIds[i] + "/temperature";
char value[16];
if (valid) snprintf(value, sizeof(value), "%.2f", c);
else snprintf(value, sizeof(value), "error");
mqtt.publish(topic.c_str(), value, true);
if (i) summary += ",";
summary += "\"" + String(probeIds[i]) + "\":" + (valid ? String(value) : String("null"));
}
summary += "}";
String topic = String(PREFIX) + "/ds18b20/summary";
mqtt.publish(topic.c_str(), summary.c_str());
Serial.println(summary);
}
void setup() {
Serial.begin(115200);
discoverProbes();
clientId = "esp32-ds18b20-" + String((uint32_t)ESP.getEfuseMac(), HEX);
topicStatus = String(PREFIX) + "/status";
connectWifi();
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setBufferSize(1024); // room for a summary of up to 10 probes
connectMqtt();
}
void loop() {
if (WiFi.status() != WL_CONNECTED) connectWifi();
if (!mqtt.connected()) connectMqtt();
mqtt.loop(); // keeps running during conversions
unsigned long now = millis();
if (!conversionPending && now - lastRequest >= INTERVAL_MS) {
sensors.requestTemperatures(); // starts conversion on all probes at once
lastRequest = now;
conversionPending = true;
}
if (conversionPending && now - lastRequest >= CONVERSION_MS) {
conversionPending = false;
publishReadings();
}
}
Testing the Probes
Subscribe to the whole prefix:
mosquitto_sub -h broker.connectmq.com -p 1883 -i ds-view-2q6h -t "connectmq/demo/dsb2q6h/#" -v
Hold one probe in your hand and watch its topic climb while the others stay flat. That is also the quickest way to figure out which physical probe owns which ROM address: label each cable as you go. Remember that the public test area is visible to anyone, so it suits experiments rather than real monitoring; see the free public MQTT broker page for its limits.
How the Non-Blocking Read Works
requestTemperatures() sends one "convert" command to every probe simultaneously, so ten probes take the same 750 ms as one. With setWaitForConversion(false) the call returns immediately, and the loop keeps calling mqtt.loop() while the probes work. Only after the conversion time has passed does the sketch read each probe by address, which takes a few milliseconds per probe.
If you do not need 0.0625 degree steps, lower the resolution. At 10 bits the conversion takes about 188 ms and the step size becomes 0.25 degrees, which is plenty for room or fridge monitoring.
Giving Probes Friendly Names
ROM addresses are stable but not readable. Two approaches work well:
- Map in the firmware. Keep a small table of address to name, such as
flow,returnorfreezer, and use the name in the topic. Simple, but every probe swap means a reflash. - Map downstream. Keep the address in the topic and let the consumer, for example a Node-RED flow or the dashboard, translate addresses to names. Replacing a probe then means editing one mapping, not firmware.
The second approach scales better. It also matches how a Modbus RS485 gateway usually publishes raw register identities and lets a higher layer assign meaning.
Detecting a Failed or Missing Probe
A probe that silently stops reporting is worse than one that reports an error, because a retained topic keeps showing the last good value forever. The sketch handles this in two ways. First, it publishes the string error to the probe's retained topic when the read returns -127 or 85, so the stale number is overwritten. Second, the summary message contains null for that probe, which most consumers treat as a gap rather than a zero.
You can go further by publishing the number of probes found at boot, for example to a retained ds18b20/count topic, and comparing it with the expected number in your dashboard. If a cable is cut, the count drops at the next rediscovery and the difference is obvious. For critical uses, also watch message age: if a probe topic has not changed for three intervals, treat the device as unhealthy even if its status still says online.
Troubleshooting Checklist
| Symptom | Cause | Fix |
|---|---|---|
| 0 probes found | Missing pull-up or wrong pin | Add 4.7 kOhm DATA to 3V3, check ONE_WIRE_PIN |
| Reading -127 | Probe disconnected or noisy bus | Check joints, shorten cable, lower pull-up value |
| Reading 85.0 | Read before conversion finished, or power glitch | Keep the 750 ms wait, use normal power |
| Some probes missing on long runs | Star wiring or too much capacitance | Use a linear bus with twisted pair |
| Summary message missing | Payload larger than buffer | Increase setBufferSize |
| Probes found only after reboot | Hot-plugged probe | Call discoverProbes() periodically or on command |
Production Setup
When the probes guard something that matters, such as a freezer, move to an authenticated broker. Create one at app.connectmq.com, switch to WiFiClientSecure on port 8883 with the ISRG Root X1 certificate, and connect with YOUR_CLIENT_ID, YOUR_USERNAME and YOUR_PASSWORD. In the ConnectMQ portal, add a threshold alert rule on the freezer probe topic and you have a basic over-temperature alarm without writing backend code. If you measure power alongside temperature, the SCT-013 energy monitor publishes in a compatible layout, and the ESP32 DHT22 project adds humidity at a single point.
Get Started with ConnectMQ
Multi-probe monitoring needs a broker that is always there when a probe crosses a threshold. ConnectMQ is a fully managed MQTT service with TLS, retained messages, a live topic explorer and alert rules, and the free Serverless plan needs no credit card. Point this sketch at your own broker and browse the ESP32 IoT tutorial hub for the next project.
Frequently Asked Questions
How many DS18B20 sensors can one ESP32 pin handle?
The protocol allows many devices per bus, and ten to twenty work reliably with good wiring and normal power. Cable length and topology limit you long before the address space does.
Why does a DS18B20 sometimes read 85 degrees?
85.0 is the power-on reset value of the scratchpad. It appears when the reading happens before a conversion completes, often after a power glitch. Treat it as an error.
Should each probe have its own MQTT topic?
Per-probe retained topics make dashboards simple and keep the last value available. A summary message per cycle is a useful addition for consumers that want all values together.
Can I use parasite power to save a wire?
You can, but it is less reliable on long runs and with many probes. Use three-wire normal power whenever possible.



