Project, an IoT Sensor Node

Draft. This tutorial has not been checked against the physical product yet. Confirm wiring, pin assignments and voltage limits against the product documentation before building it.
DifficultyAdvanced
CategoryProjects
Supported boardsESP32 DevKit, 30-pin
Time needed150 minutes
PrerequisitesSending Sensor Data with MQTT
Tested boardNot yet verified
Tested onNot yet verified
Arduino IDE versionNot yet verified
LibraryNot yet verified

What you will build

A small box you put in a room. It measures temperature and humidity, publishes them over Wi-Fi every minute, and tells you when it stops.

Build two and you can compare rooms. Build five and you have a picture of a building. That is what separates this from the weather station: one node is a gadget, several nodes are information, and the design here is about making a node you would be willing to build five of.

Before you begin

Parts

Part Siratek product Its own tutorial
ESP32 development board 2590 ESP32, Your First Wi-Fi Project
DHT22 temperature and humidity 2870 DHT22, Better Accuracy Than the DHT11
USB power supply and cable Powering Your Project
Breadboard and jumper wires Using a Breadboard

A DHT11 works too and costs less. The DHT22 is worth the difference here because the DHT11 reports whole degrees, and comparing two rooms that differ by half a degree needs the decimal.

A broker is needed. Sending Sensor Data with MQTT covers the three options; this page assumes one on your own network, which is the right answer once you have more than one node.

Why an ESP32 and not an Uno

Everything in this project could be done with an Uno and a Wi-Fi module, and it would be worse in every way. The ESP32 has the radio built in, is 3.3 V throughout so the sensor connects directly, has memory to spare, and costs less than the parts it replaces.

It is also the only board here that can do the thing that makes a node practical: deep sleep.

Design decisions, before any code

A node that runs for one evening is easy. A node you will build five of and then forget about needs four decisions made deliberately.

1. It must name itself. Five identical nodes publishing to siratek/temperature are useless. Each needs its own topic, and typing a different name into five sketches is how you end up with two nodes called kitchen. The sketch below derives a unique id from the chip’s MAC address and publishes under it, with a friendly name you set once.

2. It must say when it dies. A node that stops publishing looks exactly like a node reporting an unchanged temperature. MQTT’s last will solves this: the broker publishes offline on the node’s behalf when it vanishes. Without it, a failed node is invisible.

3. It must survive the network going away. Routers reboot. Wi-Fi drops. A node that needs power-cycling after every blip is not a node you will build five of.

4. It must not need a laptop to diagnose. The onboard LED carries the state, so you can tell from across the room whether a node is connected.

How it works

DHT22  ->  ESP32  --Wi-Fi-->  broker  -->  dashboard, phone, logger

The node connects outward. Nothing connects to it, which is why it works behind a router with no configuration, and why several things can watch at once.

Topics, with <id> being the node’s own identifier:

Topic Carries Retained
siratek/<id>/temperature Degrees C, one decimal No
siratek/<id>/humidity Per cent No
siratek/<id>/status online or offline Yes
siratek/<id>/name The friendly name Yes

Retained means the broker keeps the last value and hands it to anything that subscribes later. It is right for the status and the name, which a dashboard needs immediately on connecting, and wrong for readings, where a subscriber should wait for a fresh one rather than being told a value from last Tuesday.

The LED code

LED Meaning
Fast blink Connecting to Wi-Fi
Slow blink Wi-Fi up, connecting to the broker
On, with a brief flick each publish Running normally
Off Not running

Two seconds of looking tells you what a serial monitor would.

Pin reference

Pin Connect to Notes
DHT22 + ESP32 3V3 The sensor is happy at 3.3 V
DHT22 out ESP32 GPIO 4 A general-purpose pin
DHT22 - ESP32 GND
Onboard LED GPIO 2 Already on the board

Note what is not here: no level converter, because the ESP32 and the sensor are both 3.3 V. That is one of the quiet advantages of this board.

A sensor node publishing to a broker over Wi-Fi, with several nodes feeding one dashboard and a retained status topic saying which are online
Each node names itself from its MAC address and publishes outward. The retained status topic is what makes a dead node visible.

Build it in stages

Stage 1: Wi-Fi alone

Run the connection sketch from ESP32, Your First Wi-Fi Project. Note the signal strength where the node will actually live, not on your desk. Below about -80 dBm, move it or add an antenna before building anything else.

Stage 2: the sensor alone

Read the DHT22 to the serial monitor. Two seconds between readings; the DHT22 needs it.

Stage 3: publish

Combine them with the MQTT tutorial’s sketch and watch a value arrive in an MQTT client. Stop here and confirm it before adding anything else.

Stage 4: make it a node

Add the identity, the will, the reconnection and the LED. The complete sketch below.

The complete sketch

// Siratek: an IoT sensor node.
// Publishes temperature and humidity, names itself, and says when it dies.

#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>

const char* ssid     = "your network name";
const char* password = "your password";

const char* brokerHost = "192.168.1.10";   // your own broker
const int   brokerPort = 1883;

// Set this per node. The id below is derived and is always unique.
const char* friendlyName = "kitchen";

const int dhtPin = 4;
const int ledPin = 2;

DHT dht(dhtPin, DHT22);
WiFiClient wifi;
PubSubClient mqtt(wifi);

char nodeId[20];
char topicTemperature[48];
char topicHumidity[48];
char topicStatus[48];
char topicName[48];

const unsigned long PUBLISH_EVERY = 60000UL;
const unsigned long RETRY_EVERY   = 5000UL;

unsigned long lastPublish = 0;
unsigned long lastAttempt = 0;
unsigned long lastBlink = 0;

enum Health { CONNECTING_WIFI, CONNECTING_BROKER, RUNNING };
Health health = CONNECTING_WIFI;

void buildIdentity() {
  uint64_t mac = ESP.getEfuseMac();
  snprintf(nodeId, sizeof(nodeId), "%s-%04X",
           friendlyName, (uint16_t) (mac >> 32));

  snprintf(topicTemperature, sizeof(topicTemperature), "siratek/%s/temperature", nodeId);
  snprintf(topicHumidity,    sizeof(topicHumidity),    "siratek/%s/humidity",    nodeId);
  snprintf(topicStatus,      sizeof(topicStatus),      "siratek/%s/status",      nodeId);
  snprintf(topicName,        sizeof(topicName),        "siratek/%s/name",        nodeId);
}

void showHealth() {
  unsigned long interval = 0;
  switch (health) {
    case CONNECTING_WIFI:   interval = 150; break;
    case CONNECTING_BROKER: interval = 600; break;
    case RUNNING:           digitalWrite(ledPin, HIGH); return;
  }

  if (millis() - lastBlink >= interval) {
    lastBlink = millis();
    digitalWrite(ledPin, !digitalRead(ledPin));
  }
}

bool connectBroker() {
  // The last three arguments are the will: what the broker publishes
  // on our behalf if we disappear without saying goodbye.
  if (!mqtt.connect(nodeId, NULL, NULL, topicStatus, 0, true, "offline")) {
    Serial.print(F("broker connect failed, state "));
    Serial.println(mqtt.state());
    return false;
  }

  mqtt.publish(topicStatus, "online", true);     // retained
  mqtt.publish(topicName, friendlyName, true);   // retained
  Serial.println(F("broker connected"));
  return true;
}

void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(115200);
  delay(300);

  buildIdentity();
  Serial.print(F("Siratek node: "));
  Serial.println(nodeId);

  dht.begin();

  WiFi.mode(WIFI_STA);
  WiFi.setAutoReconnect(true);
  WiFi.begin(ssid, password);

  mqtt.setServer(brokerHost, brokerPort);
  mqtt.setBufferSize(256);

  lastPublish = millis() - PUBLISH_EVERY;
}

void publishReadings() {
  float t = dht.readTemperature();
  float h = dht.readHumidity();

  if (isnan(t) || isnan(h)) {
    Serial.println(F("sensor read failed, not publishing"));
    return;
  }

  char payload[12];

  snprintf(payload, sizeof(payload), "%.1f", t);
  mqtt.publish(topicTemperature, payload);

  snprintf(payload, sizeof(payload), "%.1f", h);
  mqtt.publish(topicHumidity, payload);

  Serial.print(F("published "));
  Serial.print(t, 1);
  Serial.print(F(" C  "));
  Serial.print(h, 1);
  Serial.println(F(" %"));

  // A visible flick, so the LED shows activity without leaving the running state.
  digitalWrite(ledPin, LOW);
  delay(30);
  digitalWrite(ledPin, HIGH);
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    health = CONNECTING_WIFI;
    showHealth();
    return;                                  // WiFi reconnects by itself
  }

  if (!mqtt.connected()) {
    health = CONNECTING_BROKER;
    showHealth();

    if (millis() - lastAttempt >= RETRY_EVERY) {
      lastAttempt = millis();
      connectBroker();
    }
    return;
  }

  health = RUNNING;
  showHealth();
  mqtt.loop();

  if (millis() - lastPublish >= PUBLISH_EVERY) {
    lastPublish = millis();
    publishReadings();
  }
}

Four details that make this a node rather than a demonstration

The id is derived, the name is chosen. kitchen-A3F2 is unique even if you flash the same sketch to five boards and forget to change the name. The friendly name is published retained, so a dashboard can show “kitchen” while the topics stay unambiguous.

setAutoReconnect(true) and an early return. When Wi-Fi drops, the ESP32 reconnects by itself and the loop simply does nothing useful until it does. No manual reconnection logic, and no blocking.

Retained on status and name, not on readings. A dashboard connecting at midnight should learn immediately which nodes exist and which are alive. It should not be handed a temperature from hours ago as though it were current.

A failed sensor read publishes nothing. Publishing nan puts a hole in every chart downstream. Saying nothing for one minute is the better failure.

Testing it

Try this It should
Power it up Fast blink, slow blink, then steady with a flick each minute
Subscribe to siratek/# See the name, the status, then readings
Unplug the node offline appears on the status topic within the keepalive
Plug it back in online and readings resume with no intervention
Reboot your router Reconnect by itself once the network returns
Unplug the sensor Node stays online, publishes nothing, says so on serial
Flash a second board A second id appears, distinct from the first

The third is the one worth watching deliberately. Seeing offline arrive without the node having sent it is the moment the will message makes sense.

Making it yours

  • Batteries and deep sleep. The real version of this project. The ESP32 wakes, connects, publishes and sleeps for fifteen minutes, averaging a few milliamps instead of eighty. That changes a node from something plugged in to something you put anywhere. Regulators, Batteries and Solar covers the supply; note that reconnecting Wi-Fi on each wake costs a few seconds of full current, so the arithmetic is worth doing.
  • More sensors per node. A BMP280 on the I2C pins adds pressure for very little. Keep any analog sensor on GPIO 32 to 39, because the others stop working with Wi-Fi on.
  • A dashboard. Node-RED or Home Assistant subscribing to siratek/# will draw charts and send alerts without you writing any more code.
  • Accept commands. Subscribe to siratek/<id>/command and the node can be told to publish immediately or change its interval. Sending Sensor Data with MQTT has the callback, including why the payload is not a string.

Troubleshooting

Symptom Likely cause Check or fix
Fast blink for ever Wi-Fi not connecting 2.4 GHz only; check the password
Slow blink for ever Broker unreachable Check the address; is the broker running?
Two nodes keep disconnecting each other Same client id The MAC-derived id prevents this
Readings arrive, status never does Not published retained The true argument
Dashboard shows a dead node as alive No will message Set it on connect()
publish() returns false Payload over the buffer setBufferSize(256)
Charts have gaps Sensor read failures Expected occasionally; check the wiring
Charts have nan Publishing failed reads Check isnan() before publishing
Node resets when transmitting Thin USB cable Shorter cable, better supply
Works at the desk, not in the room Signal strength Check RSSI where it will live
Analog sensor reads nothing ADC2 pin with Wi-Fi on Use GPIO 32 to 39

Sources and attribution

All text, wiring guidance and code in this project are original Siratek work.

  • Component behaviour is as described in ESP32, Your First Wi-Fi Project, Sending Sensor Data with MQTT and DHT22, Better Accuracy Than the DHT11, including the ADC2 and Wi-Fi conflict, PubSubClient’s buffer size and the DHT22’s two-second interval.
  • MQTT’s retained messages and last will are defined by the MQTT specification.

No third-party tutorial text, diagrams, photographs or code has been reproduced here.

Product used in this tutorial: ESP32 Development Board WiFi+Bluetooth Ultra-Low
Shop
Sidebar
0 Wishlist
0 Cart