Raspberry Pi

Raspberry Pi DHT22 MQTT: Reliable Temperature and Humidity Publishing with Python

Wire a DHT22 to a Raspberry Pi, read it with Python, and publish validated temperature and humidity JSON over MQTT with retries, median filtering and error counters that keep bad readings out of your dashboards.

DHT22 temperature and humidity sensor wired to a Raspberry Pi on a breadboard and sending readings to a cloud broker in a raspberry pi dht22 mqtt project
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

A Raspberry Pi DHT22 MQTT sensor is a classic first IoT build, and also a classic source of frustration: the DHT22 is cheap and accurate enough for rooms and greenhouses, but its timing-sensitive protocol means some reads fail on a Linux board. In this tutorial you will wire the sensor, read it from Python, and publish clean temperature and humidity JSON to an MQTT broker with proper handling of failed reads, implausible values and network drops.

Why the DHT22 Fails Sometimes

The DHT22 (also sold as AM2302) does not use I2C or 1-Wire. The Pi has to toggle a GPIO line and then time a burst of pulses lasting tens of microseconds. Raspberry Pi OS is not a real-time operating system, so when the kernel schedules something else at the wrong moment, a bit is lost and the checksum fails. This is normal behaviour, not a broken sensor. The fix is in software:

  1. Wait at least two seconds between reads; the sensor cannot sample faster.
  2. Catch the RuntimeError the library raises and retry.
  3. Reject values outside the sensor's physical range.
  4. Take several good readings and publish the median.
  5. Count failures and publish them, so you notice when a sensor really dies.

Wiring

DHT22 pin Raspberry Pi
VCC (pin 1) 3.3 V (physical pin 1)
DATA (pin 2) GPIO4 (physical pin 7)
NC (pin 3) Not connected
GND (pin 4) GND (physical pin 9)

A bare DHT22 needs a 4.7 k to 10 k pull-up resistor between DATA and 3.3 V. Three-pin breakout modules usually include it. Keep the cable short at first; long runs work but are more sensitive to noise, and lowering the pull-up value can help on longer cables. Power it from 3.3 V, never 5 V, because the data line goes straight to a GPIO pin that is not 5 V tolerant.

Installing the Libraries

sudo apt update
sudo apt install -y python3-venv libgpiod2
mkdir -p ~/dht22-mqtt && cd ~/dht22-mqtt
python3 -m venv .venv
source .venv/bin/activate
pip install "paho-mqtt>=2.0" adafruit-circuitpython-dht

adafruit-circuitpython-dht pulls in Adafruit Blinka, which provides the board module. Test the sensor on its own before adding MQTT:

python -c "import time, board, adafruit_dht; d = adafruit_dht.DHT22(board.D4); time.sleep(2); print(d.temperature, d.humidity); d.exit()"

If you see an error like "A full buffer was not returned", run the command again; a single failure is expected. If every attempt fails, check wiring and the pull-up. On some Raspberry Pi models and OS versions the pulse-input helper does not work; in that case create the sensor with adafruit_dht.DHT22(board.D4, use_pulseio=False) to use the bit-banging fallback.

Payload and Topics

The script publishes one JSON document per interval plus a retained status:

{
  "temperature": 22.4,
  "humidity": 48.1,
  "samples": 3,
  "read_errors": 2,
  "ts": 1792152000
}
Topic Content Retained
connectmq/demo/c3f19a/greenhouse/dht22 JSON reading every 30 s No
connectmq/demo/c3f19a/greenhouse/status online / offline (last will) Yes

Replace c3f19a with a random string of your own. Including read_errors in every message costs a few bytes and turns a silent hardware problem into something you can chart and alert on.

The Raspberry Pi DHT22 MQTT Script

Save this as dht22_mqtt.py:

#!/usr/bin/env python3
"""Read a DHT22 and publish validated readings to MQTT."""
import json
import os
import statistics
import time
import uuid

import adafruit_dht
import board
import paho.mqtt.client as mqtt

BROKER = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "1883"))
USERNAME = os.getenv("MQTT_USERNAME")
PASSWORD = os.getenv("MQTT_PASSWORD")
CLIENT_ID = os.getenv("MQTT_CLIENT_ID", f"dht22-{uuid.uuid4().hex[:8]}")
PREFIX = os.getenv("MQTT_PREFIX", "connectmq/demo/c3f19a/greenhouse")
INTERVAL = int(os.getenv("PUBLISH_INTERVAL", "30"))
SAMPLES = 3
MAX_ATTEMPTS = 10

sensor = adafruit_dht.DHT22(board.D4)
total_errors = 0


def plausible(temp, hum):
    return (
        temp is not None and hum is not None
        and -40.0 <= temp <= 80.0
        and 0.0 <= hum <= 100.0
    )


def read_median():
    """Collect SAMPLES good readings and return their medians, or None."""
    global total_errors
    temps, hums = [], []
    attempts = 0
    while len(temps) < SAMPLES and attempts < MAX_ATTEMPTS:
        attempts += 1
        try:
            t, h = sensor.temperature, sensor.humidity
            if plausible(t, h):
                temps.append(t)
                hums.append(h)
            else:
                total_errors += 1
        except RuntimeError as err:
            # Checksum or timing errors are expected now and then.
            total_errors += 1
            print(f"Read failed ({err}); retrying")
        time.sleep(2.2)
    if not temps:
        return None
    return statistics.median(temps), statistics.median(hums), len(temps)


def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code.is_failure:
        print(f"Connection refused: {reason_code}")
        return
    print(f"Connected as {CLIENT_ID}")
    client.publish(f"{PREFIX}/status", "online", qos=1, retain=True)


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)
client.on_connect = on_connect
client.will_set(f"{PREFIX}/status", "offline", qos=1, retain=True)
client.reconnect_delay_set(min_delay=1, max_delay=60)
if USERNAME:
    client.username_pw_set(USERNAME, PASSWORD)
if PORT == 8883:
    client.tls_set()

client.connect(BROKER, PORT, keepalive=60)
client.loop_start()

try:
    while True:
        started = time.monotonic()
        result = read_median()
        if result is None:
            payload = {"error": "no valid reading", "read_errors": total_errors,
                       "ts": int(time.time())}
        else:
            temp, hum, n = result
            payload = {
                "temperature": round(temp, 1),
                "humidity": round(hum, 1),
                "samples": n,
                "read_errors": total_errors,
                "ts": int(time.time()),
            }
        info = client.publish(f"{PREFIX}/dht22", json.dumps(payload), qos=1)
        if info.rc != mqtt.MQTT_ERR_SUCCESS:
            print(f"Publish queued while offline (rc={info.rc})")
        print(payload)
        elapsed = time.monotonic() - started
        time.sleep(max(0, INTERVAL - elapsed))
except KeyboardInterrupt:
    pass
finally:
    client.publish(f"{PREFIX}/status", "offline", qos=1, retain=True).wait_for_publish(2)
    client.loop_stop()
    client.disconnect()
    sensor.exit()

A few details are worth calling out. The two-second-plus delay between reads respects the sensor's sampling limit. The median of three readings removes the occasional single spike that passes the checksum but is still wrong. When every attempt fails, the script still publishes a message with an error field, so subscribers can tell "sensor broken" apart from "Pi offline", which the retained status topic covers. Finally, sensor.exit() releases the GPIO line; skipping it can leave the pin busy for the next run.

Watching the Data

Subscribe from your laptop to confirm messages arrive:

mosquitto_sub -h broker.connectmq.com -p 1883 -i laptop-c3f19a \
  -t 'connectmq/demo/c3f19a/greenhouse/#' -v

You should see status online immediately (it is retained) and a JSON reading roughly every 30 seconds. Unplug the Pi's network for a minute and plug it back in: paho reconnects automatically. QoS 1 messages published while the connection is down are queued in memory and sent after reconnect, as long as the process keeps running. If you need readings to survive a reboot during an outage, see how to buffer MQTT messages offline in Python with SQLite.

Choosing a Publish Interval

Temperature and humidity in a room or greenhouse change slowly. Publishing every 30 to 60 seconds gives smooth charts without flooding the broker or your database. Faster than every 10 seconds rarely adds information from a DHT22, and each read cycle with retries already takes several seconds. If you only care about changes, publish when the value moves by more than 0.2 °C or 1 % humidity, plus a heartbeat every few minutes.

Sensor placement matters as much as the interval. Mount the DHT22 away from the Pi itself, out of direct sunlight and away from heaters or vents, with free airflow around the vents in its housing. A sensor taped to the Pi case will mostly measure the processor. In a greenhouse, a small radiation shield made of stacked white plates keeps sunlight from inflating the temperature reading.

DHT22 vs Other Sensors

The DHT22 is fine for indoor monitoring, but it is not the only option. A DS18B20 waterproof probe is far more robust for temperature alone and supports many probes on one wire; see the Raspberry Pi DS18B20 MQTT guide. If you would rather run the same sensor on a battery-powered microcontroller, the ESP32 DHT22 MQTT tutorial uses an identical payload, so both devices can feed the same dashboard. For the broader picture of publishing, subscribing and running scripts on boot, start with our Raspberry Pi MQTT tutorial.

Moving to a Private Broker

The free public MQTT broker at broker.connectmq.com is perfect for testing, but every topic there is readable by anyone. For a real greenhouse or server room, create a private broker on ConnectMQ, then set the environment variables and restart:

export MQTT_PORT=8883
export MQTT_USERNAME=YOUR_USERNAME
export MQTT_PASSWORD=YOUR_PASSWORD
export MQTT_CLIENT_ID=YOUR_CLIENT_ID
export MQTT_PREFIX=site1/greenhouse/zone1
python dht22_mqtt.py

In the ConnectMQ portal you can watch readings in the live topic explorer and create a threshold alert rule, for example humidity above 85 %, without writing an extra subscriber.

Troubleshooting Checklist

  1. Every read fails: check that DATA is on GPIO4, the pull-up is present and VCC is 3.3 V.
  2. "Unable to set line to input" or GPIO busy: another process holds the pin; stop old scripts and make sure sensor.exit() runs.
  3. Values look right but jump occasionally: keep median filtering on and increase SAMPLES to 5.
  4. Humidity stuck at 99.9: condensation on the sensor; DHT22s do not like constant saturation.
  5. Readings stop but the status stays online: the script is stuck reading; add a watchdog or restart through systemd with Restart=always.
  6. Connection refused, not authorised: credentials or client ID do not match the broker.

Get Started with ConnectMQ

Take your sensor from the bench to a real deployment on a cloud MQTT broker. Create a free Serverless broker, no credit card needed, copy the generated credentials into your environment and connect over TLS on port 8883. Your DHT22 readings will be available to dashboards, Node-RED flows and alert rules within minutes.

Frequently Asked Questions

Why does my DHT22 return "Checksum did not validate" on a Raspberry Pi?

Linux occasionally interrupts the precise timing the DHT22 needs, so some reads fail. Catch the error, wait two seconds and retry; a few failures per hour are normal.

How often can I read a DHT22?

No more than once every two seconds. For MQTT publishing, an interval of 30 to 60 seconds is plenty for room and greenhouse monitoring.

Should I use QoS 0 or QoS 1 for sensor readings?

QoS 1 is a good default because it guarantees delivery with little overhead. QoS 0 is acceptable for very frequent readings where losing one does not matter.

Can I connect several DHT22 sensors to one Raspberry Pi?

Yes, give each its own GPIO pin and create one DHT22 object per pin. Read them one after another rather than at the same time, and publish each to its own topic.

Related articles

Pico W vs Pico 2 W for MQTT: RP2040 vs RP2350 Performance, Memory and TLS

The Pico 2 W keeps the Pico W's footprint and Wi-Fi chip but swaps the RP2040 for the faster RP2350. This comparison looks at what that means for MQTT devices: CPU, memory, TLS, security features, software support, and which board to buy for which project.

8 min read

Raspberry Pi RFID MQTT Access Logging with the RC522 Reader

Wire an RC522 RFID reader to a Raspberry Pi, read tags in Python, check them against a local allowlist and publish every access attempt as an MQTT event that loggers, dashboards and alerting tools can consume.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.