The ESP-01S can become a compact ThingsBoard temperature monitor, but its small size removes the GPIO margin available on NodeMCU-style boards. GPIO0 and GPIO2 affect boot mode, power quality directly affects Wi-Fi and DHT22 reliability, and a missing data-line pull-up can make otherwise correct code produce repeated NaN readings.
This guide builds an ESP-01S DHT22 ThingsBoard node that publishes bounded JSON telemetry without long delays. It concentrates on the module-specific electrical and boot constraints. For relay RPC, client attributes, and the broader device architecture, read the existing ESP8266 ThingsBoard MQTT telemetry and RPC guide.
What the monitoring device does
- The ESP-01S starts in normal flash boot mode and joins a 2.4 GHz Wi-Fi network.
- It connects to the ThingsBoard MQTT device endpoint using a device access token.
- Every ten seconds, it asks the DHT library for temperature and humidity.
- It rejects NaN, infinite, and out-of-range values instead of storing them as normal telemetry.
- It publishes valid readings to
v1/devices/me/telemetryand reconnects with bounded retry intervals after network loss.
ESP-01S pinout and GPIO limitations
An ESP-01S exposes far fewer signals than a full development board. The pins normally available are 3.3 V, GND, EN/CH_PD, RST, TX/GPIO1, RX/GPIO3, GPIO0, and GPIO2. TX and RX are needed for flashing and diagnostics. That leaves GPIO0 and GPIO2 as the common candidates for a one-wire-style DHT data signal, but both participate in boot configuration.
| Pin | Boot implication | DHT22 suitability |
|---|---|---|
| GPIO0 | Must be high for normal flash boot; low during reset selects the serial bootloader. | Usable only if the sensor circuit never pulls it low at reset. It also conflicts with the normal programming control. |
| GPIO2 | Must be high for normal flash boot. | Usually the simpler choice because the DHT data pull-up holds it high while idle. |
| GPIO1/TX | Produces serial boot traffic and is used for UART transmit. | Not recommended when predictable sensor signaling and programming access are required. |
| GPIO3/RX | UART receive/programming path. | Possible in specialized designs, but inconvenient for flashing and troubleshooting. |
This project uses GPIO2. That is not a universal guarantee: an incorrectly wired sensor, an excessive capacitive load, or another circuit holding GPIO2 low can still prevent startup. Never confuse an ESP-01S GPIO number with labels such as D1 or D2 found on other ESP8266 boards.
DHT22 wiring table
| DHT22 connection | ESP-01S connection | Design note |
|---|---|---|
| VCC | Regulated 3.3 V | Keep the logic and sensor at 3.3 V for this design. |
| DATA | GPIO2 | Add a 4.7 kΩ–10 kΩ pull-up from DATA to 3.3 V unless the sensor module already includes one. |
| NC | Not connected | Applies to the four-pin bare DHT22 package. |
| GND | GND | Use a short, solid ground return. |
| EN/CH_PD | 3.3 V through the board’s required pull-up | EN must be high for the chip to run. |
Many three-pin DHT22 breakout boards already contain a pull-up resistor; check the module schematic rather than adding parts blindly. With a bare four-pin sensor, add the external pull-up. Keep the data wire short in the first prototype. Long cables add capacitance and noise and may require a different sensor, bus driver, cable arrangement, or sampling design.
Stable 3.3 V power is part of sensor reliability
Do not feed 5 V into the ESP-01S VCC pin. Use a regulated 3.3 V supply designed for the ESP8266’s transient Wi-Fi current, with local decoupling placed close to the module. A weak USB-to-serial adapter regulator can appear to work during flashing but reset or corrupt readings when the radio transmits.
If the device resets, disappears from Wi-Fi, or reports intermittent DHT failures, verify the 3.3 V rail at the module during transmission with appropriate test equipment. Do not conclude that MQTT or the DHT library is at fault before checking supply droop, grounding, connector resistance, and cable length.
Create a ThingsBoard device
- Sign in to ThingsBoard Community Edition as a tenant administrator.
- Open Entities → Devices and add a device such as
esp01s-dht22-room-01. - Open its credentials and copy the device access token.
- Put the token only in the local ignored
secrets.hfile. Use a unique token per physical device and rotate it after exposure.
For ThingsBoard access-token authentication, the device token is used as the MQTT username. Confirm the MQTT host and port for your own CE deployment. The web dashboard address alone does not prove that the MQTT listener is reachable from the sensor network.
Telemetry topic and JSON format
The standard device telemetry topic is:
v1/devices/me/telemetry
A valid reading from this sketch is serialized in this shape:
{
"temperature": 24.6,
"humidity": 58.2,
"sensorStatus": "ok",
"rssi": -61,
"freeHeap": 31240
}
The numbers above demonstrate JSON structure only; they are not claimed measurements. Actual temperature, humidity, RSSI, and heap values depend on the physical device and environment.
Project structure and protected secrets
esp01s-dht22-thingsboard/
├── esp01s_dht22_thingsboard.ino
├── secrets.example.h
├── secrets.h
└── .gitignore
Create secrets.example.h as follows, then copy it to secrets.h and enter local values:
#pragma once
#define WIFI_SSID "YOUR_2_4_GHZ_WIFI_SSID"
#define WIFI_PASSWORD "YOUR_WIFI_PASSWORD"
#define THINGSBOARD_HOST "thingsboard.example.com"
#define THINGSBOARD_PORT 1883
#define THINGSBOARD_TOKEN "YOUR_DEVICE_ACCESS_TOKEN"
Add secrets.h to .gitignore:
secrets.h
.pio/
Port 1883 is unencrypted MQTT. Use it only for controlled initial validation on a trusted network. A production deployment should use the ThingsBoard TLS listener with certificate validation, not an insecure client that accepts any certificate.
Complete ESP-01S Arduino code
Install the ESP8266 Arduino core, PubSubClient, ArduinoJson 7, Adafruit DHT Sensor Library, and its Adafruit Unified Sensor dependency. The code uses fixed buffers for JSON and client IDs rather than repeatedly concatenating String objects.
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <DHT.h>
#include "secrets.h"
constexpr uint8_t DHT_PIN = 2; // ESP-01S GPIO2, not a board alias
constexpr uint8_t DHT_TYPE = DHT22;
constexpr char TELEMETRY_TOPIC[] = "v1/devices/me/telemetry";
constexpr uint32_t SAMPLE_INTERVAL_MS = 10000;
constexpr uint32_t WIFI_RETRY_MS = 15000;
constexpr uint32_t MQTT_RETRY_MIN_MS = 2000;
constexpr uint32_t MQTT_RETRY_MAX_MS = 60000;
// Absolute DHT22 bounds. Narrow these for the monitored environment if needed.
constexpr float MIN_VALID_TEMP_C = -40.0F;
constexpr float MAX_VALID_TEMP_C = 80.0F;
constexpr float MIN_VALID_HUMIDITY = 0.0F;
constexpr float MAX_VALID_HUMIDITY = 100.0F;
WiFiClient networkClient;
PubSubClient mqtt(networkClient);
DHT dht(DHT_PIN, DHT_TYPE);
uint32_t lastSampleAt = 0;
uint32_t lastWiFiAttemptAt = 0;
uint32_t lastMqttAttemptAt = 0;
uint32_t mqttRetryMs = MQTT_RETRY_MIN_MS;
uint32_t invalidReadingCount = 0;
bool wifiAttempted = false;
bool publishDocument(JsonDocument &doc) {
if (!mqtt.connected()) return false;
char payload[256];
const size_t length = serializeJson(doc, payload, sizeof(payload));
if (length == 0 || length >= sizeof(payload)) return false;
return mqtt.publish(TELEMETRY_TOPIC, payload, false);
}
void publishSensorDiagnostic(const char *status) {
JsonDocument doc;
doc["sensorStatus"] = status;
doc["invalidReadingCount"] = invalidReadingCount;
doc["rssi"] = WiFi.RSSI();
doc["freeHeap"] = ESP.getFreeHeap();
publishDocument(doc);
}
bool readingIsValid(float temperature, float humidity) {
if (isnan(temperature) || isnan(humidity)) return false;
if (!isfinite(temperature) || !isfinite(humidity)) return false;
if (temperature < MIN_VALID_TEMP_C || temperature > MAX_VALID_TEMP_C) return false;
if (humidity < MIN_VALID_HUMIDITY || humidity > MAX_VALID_HUMIDITY) return false;
return true;
}
void sampleAndPublish() {
// Read both values from the library's cached measurement cycle.
const float humidity = dht.readHumidity();
const float temperature = dht.readTemperature();
if (!readingIsValid(temperature, humidity)) {
++invalidReadingCount;
publishSensorDiagnostic("invalid_reading");
return;
}
invalidReadingCount = 0;
JsonDocument doc;
doc["temperature"] = temperature;
doc["humidity"] = humidity;
doc["sensorStatus"] = "ok";
doc["rssi"] = WiFi.RSSI();
doc["freeHeap"] = ESP.getFreeHeap();
publishDocument(doc);
}
void connectWiFi() {
if (WiFi.status() == WL_CONNECTED) return;
const uint32_t now = millis();
if (wifiAttempted &&
static_cast<uint32_t>(now - lastWiFiAttemptAt) < WIFI_RETRY_MS) return;
wifiAttempted = true;
lastWiFiAttemptAt = now;
WiFi.mode(WIFI_STA);
WiFi.persistent(false);
WiFi.setAutoReconnect(true);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
}
void connectMQTT() {
if (WiFi.status() != WL_CONNECTED || mqtt.connected()) return;
const uint32_t now = millis();
if (static_cast<uint32_t>(now - lastMqttAttemptAt) < mqttRetryMs) return;
lastMqttAttemptAt = now;
char clientId[32];
snprintf(clientId, sizeof(clientId), "esp01s-%06X", ESP.getChipId());
// ThingsBoard access-token authentication: token is the MQTT username.
if (mqtt.connect(clientId, THINGSBOARD_TOKEN, nullptr)) {
mqttRetryMs = MQTT_RETRY_MIN_MS;
publishSensorDiagnostic("mqtt_connected");
return;
}
mqttRetryMs = min(mqttRetryMs * 2U, MQTT_RETRY_MAX_MS);
}
void setup() {
dht.begin();
mqtt.setServer(THINGSBOARD_HOST, THINGSBOARD_PORT);
mqtt.setBufferSize(256);
mqtt.setKeepAlive(30);
connectWiFi();
}
void loop() {
connectWiFi();
if (WiFi.status() == WL_CONNECTED) connectMQTT();
if (mqtt.connected()) {
mqtt.loop();
const uint32_t now = millis();
if (static_cast<uint32_t>(now - lastSampleAt) >= SAMPLE_INTERVAL_MS) {
lastSampleAt = now;
sampleAndPublish();
}
}
yield();
}
Why this sampling and validation strategy is reliable
Ten seconds between DHT22 reads
The DHT22 is a slow sensor and should not be polled continuously. A ten-second interval is comfortably above its minimum interval and is usually adequate for room monitoring. The subtraction-based millis() comparison remains safe when the counter wraps.
NaN and impossible values are rejected
A failed DHT transaction commonly reaches the application as NaN. Publishing that value as if it were a measurement can create misleading charts and alarms. readingIsValid() rejects NaN, infinity, temperature outside the DHT22’s specified measurement range, and humidity outside 0–100%.
Those are absolute sensor bounds, not proof that a reading is credible for a particular room. For a refrigerator, greenhouse, or server room, narrow the accepted range to values physically possible in that installation. Consider requiring multiple consistent samples before generating an alarm. Do not silently replace a failed sample with zero, because zero can be a legitimate measurement.
Diagnostics do not masquerade as measurements
When validation fails, the sketch publishes sensorStatus=invalid_reading and increments invalidReadingCount; it does not publish invalid temperature or humidity. A dashboard can show sensor health separately from environmental data.
Reconnect attempts are bounded
connectWiFi() starts a Wi-Fi attempt no more than once per 15 seconds. MQTT failures use exponential backoff from two seconds to one minute. There is no while (!connected) retry loop, so mqtt.loop(), the Wi-Fi stack, and the watchdog continue to receive processor time.
Flash the ESP-01S safely
- Use a 3.3 V USB-to-serial adapter with sufficient regulated current or power the module from a separate suitable 3.3 V supply while sharing signal ground.
- Connect TX to RX and RX to TX using 3.3 V logic.
- Hold GPIO0 low only while resetting or powering up to enter the serial bootloader.
- Upload the firmware.
- Remove the GPIO0-to-ground programming connection, then reset so GPIO0 and GPIO2 are high for normal flash boot.
- Reconnect the DHT22 and verify the pull-up before final power-up.
Avoid automatic programming adapters that force GPIO states in conflict with the installed sensor. If flashing succeeds but normal startup fails, inspect GPIO0 and GPIO2 levels during reset.
Create temperature and humidity widgets
- Open the ThingsBoard device and confirm
temperatureandhumidityappear under Latest telemetry. - Create a dashboard and add an entity alias pointing to the ESP-01S device.
- Add time-series widgets for
temperatureandhumidity. - Add a latest-value card for
sensorStatusand optionallyinvalidReadingCount. - Choose a time window and aggregation appropriate to the ten-second source interval. Do not imply more precision than the sensor and installation support.
Verification procedure
- Boot: power-cycle without GPIO0 tied low. Verify that the module starts rather than remaining in programming mode.
- Online state: check that ThingsBoard reports the device online after Wi-Fi and MQTT connect.
- Telemetry: wait at least one full sample interval, then inspect Latest telemetry before troubleshooting dashboard widgets.
- Sensor response: expose the sensor to a small, safe environmental change and check that readings evolve plausibly. Do not heat it with a flame or wet it.
- Invalid-reading handling: with power removed, disconnect DATA, restore power, and verify that sensor status reports an invalid reading rather than fabricated temperature.
- Recovery: restore the sensor wiring and power-cycle. Confirm valid telemetry resumes.
- Network recovery: temporarily disable the permitted Wi-Fi network, restore it, and verify reconnection without rapid broker attempts.
These are verification steps and expected behaviors, not test results. Record observations from the actual board, power supply, sensor, firmware build, Wi-Fi network, and ThingsBoard version.
Troubleshooting ESP-01S and DHT22
| Symptom | Likely cause | Diagnostic action |
|---|---|---|
| No normal boot after flashing | GPIO0 remains low, GPIO2 is held low, EN is low, or boot wiring is wrong | Remove the programming strap, measure GPIO0/GPIO2/EN at reset, and disconnect the sensor to isolate the boot circuit. |
| Random resets or Wi-Fi drops | Weak 3.3 V regulator, poor decoupling, long leads, or ground resistance | Measure the rail at the ESP-01S during radio activity and test from a known suitable supply. |
| DHT22 returns NaN | Missing pull-up, wrong pin order, loose DATA, inadequate interval, long/noisy cable, or sensor power problem | Check the exact sensor pinout, confirm a 4.7–10 kΩ pull-up to 3.3 V, shorten wiring, and keep sampling slower than two seconds. |
| Values are valid JSON but implausible | Heat from the regulator/ESP8266, condensation, poor airflow, sensor placement, or a damaged sensor | Move the DHT22 away from heat sources, compare with a reference, inspect installation conditions, and define application-specific bounds. |
| Device stays offline | Wi-Fi failure, wrong broker host/port, firewall, token rejection, DNS, or TLS mismatch | Confirm 2.4 GHz Wi-Fi, DHCP/DNS, MQTT listener reachability, and the token belonging to this device. |
| Device online but telemetry missing | Wrong topic, publish failure, invalid readings, dashboard alias, or time window | Inspect Latest telemetry and sensorStatus, confirm v1/devices/me/telemetry, then check the widget alias. |
| Repeated MQTT connection attempts | Invalid credentials or broker unavailable combined with blocking retry code | Preserve the timed exponential backoff and inspect ThingsBoard transport logs rather than shortening the retry interval. |
| Only the first reading appears | Main loop blocked, unstable device, or MQTT keepalive not serviced | Remove long delays and loops, call mqtt.loop() frequently, and check power/reset cause. |
Production hardening
- Use MQTT over TLS with a trusted CA and correct clock rather than exposing port 1883 publicly.
- Give every device a unique token; rotate compromised credentials.
- Restrict the IoT network to required DNS, NTP, and ThingsBoard destinations.
- Use a watchdog-aware loop, controlled telemetry rate, and server-side retention suitable for the device count.
- Monitor
sensorStatus, reconnect frequency, RSSI, and invalid-reading trends. - Design a proper PCB with decoupling, pull-ups, programming access, strain relief, and an enclosure that allows representative airflow.
If ThingsBoard is hosted with containers, review Docker Compose health checks and restart policies. For service-side monitoring, the LAPVN guide to structured logging with Pino explains why machine-readable diagnostics are easier to operate.
Final checklist
- GPIO2 is used for DATA and remains high during reset.
- GPIO0 is disconnected from ground after flashing.
- DATA has one appropriate pull-up to 3.3 V.
- The ESP-01S receives stable regulated 3.3 V with local decoupling.
- The real access token exists only in ignored
secrets.h. - Telemetry uses the exact ThingsBoard device topic.
- NaN, infinity, and out-of-range values are not published as measurements.
- Sampling and reconnect logic contain no unbounded loops or long delays.
- Latest telemetry works before dashboard widgets are configured.
- Boot, power, sensor recovery, and network recovery have been tested on the physical unit.
Frequently asked questions
Should the DHT22 use GPIO0 or GPIO2 on ESP-01S?
GPIO2 is generally the simpler choice for this project because the required DHT data pull-up also supports its high boot state. Both GPIO0 and GPIO2 are boot-strap pins, so neither is safe if external hardware forces it low during reset.
Why does an ESP-01S boot into programming mode?
GPIO0 was low during reset. Remove the flashing strap after upload and ensure the attached circuit lets GPIO0 rise for normal boot.
Which pull-up resistor should I use for DHT22 DATA?
A value in the 4.7 kΩ–10 kΩ range is commonly used between DATA and 3.3 V for short wiring. A breakout module may already contain one. Verify its schematic and signal integrity for the actual cable.
Why does the DHT22 return NaN on ESP8266?
Common causes are incorrect pin order, no pull-up, weak power, loose connections, overly frequent reads, or long/noisy wiring. The code rejects NaN and reports a separate sensor status so a failed transaction does not become a measurement.
Can I power ESP-01S from a USB-to-serial adapter?
Only if its 3.3 V regulator can supply the ESP8266’s transient demand while remaining stable. Many adapters are intended mainly for logic levels. A suitable separate 3.3 V supply with common signal ground is often more reliable.
Why is MQTT port 1883 not recommended for production?
It does not encrypt credentials or telemetry. Use the TLS listener configured by the ThingsBoard deployment and validate the broker certificate.