Back in 2022 when I started using my Raspberry Pi as a home automation server instead of media/retro-game box, I discovered this lite pub-sub protocol already popular among home automation enthusiast, MQTT was quite simple and didn’t much configuration, everything just worked out-of-box, a nodeMCU with HC-SR04 ultrasonic sensor reading tank water level publishing to a topic, which nodeRed directly picked up!

What MQTT actually is

MQTT (Message Queuing Telemetry Transport) is a lightweight publish/subscribe protocol designed for unreliable networks and low-power devices. The whole spec is about 30 pages — short enough that you can read it on a Sunday afternoon. The official site is at mqtt.org and the protocol is now an OASIS standard.

The model is dead simple:

  • A broker sits in the middle (Mosquitto, EMQX, HiveMQ — take your pick).
  • Publishers send messages to a topic (e.g. home/terrace/water-level).
  • Subscribers ask the broker to forward anything sent to a topic they care about.

That’s it. Publishers don’t know who’s listening. Subscribers don’t know who’s sending. The broker just routes messages.

[NodeMCU on terrace] --pub--> [Mosquitto broker] --sub--> [Node-RED on Pi]
                                       |
                                       +----sub--> [Home Assistant]
                                       +----sub--> [Phone app]

Topics — the address book of your house

Topics are forward-slash-separated strings. Think of them like folders. There’s no formal hierarchy, just convention. Mine looks roughly like:

home/<location>/<device>/<reading>

home/terrace/water-tank/level
home/outside/sensor/temperature
home/outside/sensor/humidity
home/livingroom/sensor/temperature
home/livingroom/sensor/humidity

You can subscribe to wildcards too:

  • home/terrace/+/level — single-level wildcard, matches any device on the terrace publishing a level reading.
  • home/# — multi-level wildcard, matches everything under home/.

The key insight: topics don’t need to be created. Publishing to a topic that nobody’s subscribed to works fine — the message is simply discarded (unless it’s retained, which I’ll get to). This makes adding devices frictionless.

Why not just HTTP webhooks?

This is the question I get most. The Pi could just expose a /water-level endpoint and the NodeMCU could POST to it. It would even work, most of the time. But —

  1. Webhooks are point-to-point. If I want a second consumer (say, an Alexa routine), I have to add a second endpoint. With MQTT, I just subscribe a second client.
  2. Webhooks fail loudly when the receiver is down. MQTT brokers can hold messages with QoS 1/2 until the subscriber reconnects, or use retained messages so a new subscriber gets the latest known value immediately.
  3. HTTP is heavyweight for tiny devices. TLS handshakes, headers, JSON parsing — fine on a Pi, painful on an ESP-01 with 512KB of flash. MQTT’s persistent connection is much cheaper.
  4. Discoverability is built in. Home Assistant’s MQTT integration auto-discovers devices that announce themselves on homeassistant/<component>/<id>/config. You don’t have to wire each one up by hand.

Retained messages — the bit nobody tells you upfront

By default, MQTT messages are fire-and-forget. If a subscriber connects after you publish, it never sees that message. For event streams (a button was pressed) that’s fine. For state (the tank is 70% full) it’s terrible — your dashboard would render blank until the next sensor reading.

The fix is the retained flag. When set, the broker keeps the latest message on that topic and immediately delivers it to any new subscriber. So when Home Assistant boots up, it gets the last known water level instantly — no waiting for the NodeMCU’s next 60-second cycle.

# ESPHome example
mqtt:
  broker: 192.168.1.10
  topic_prefix: home/terrace/water-tank
  discovery: true

sensor:
  - platform: ultrasonic
    trigger_pin: GPIO12
    echo_pin: GPIO14
    name: "Tank level"
    update_interval: 60s

QoS — the three levels of “did you get my message?”

MQTT defines three quality-of-service levels:

  • QoS 0 — fire and forget. The publisher sends once, the broker forwards once. Lost packets are lost. Cheap. Default.
  • QoS 1 — at least once. The broker holds the message until the subscriber acknowledges. Possible duplicates. This is the sweet spot for most home automation.
  • QoS 2 — exactly once. Four-way handshake, guaranteed single delivery. Heavyweight, rarely needed.

For my water tank I use QoS 1. A duplicate “tank is 70%” reading is harmless. A missed “tank is empty” reading isn’t.

A tiny full example

Here’s the loop the way I’d do it today, using ESPHome on the sensor side and Home Assistant + Node-RED on the receiving side. The NodeMCU publishes:

home/terrace/water-tank/level     30      (retained, QoS 1)

Home Assistant auto-creates a sensor entity. Node-RED has a flow that subscribes to the same topic and sends a notification to my phone if the level drops below 15% — but only if it hasn’t already alerted me in the last hour. The whole alerting logic lives in three Node-RED nodes, decoupled from the sensor itself. Want to add a second alert rule? Add another subscriber. The sensor doesn’t change.

Mosquitto in five minutes

If you want to try this on a Pi:

sudo apt install mosquitto mosquitto-clients
sudo systemctl enable --now mosquitto

# Test it
mosquitto_sub -h localhost -t 'test/#' &
mosquitto_pub -h localhost -t 'test/hello' -m 'world'
# subscriber prints: world

Add a password file (mosquitto_passwd), point your devices at it, and you’re done. The default config is fine for a home network. For anything internet-facing, put it behind TLS and a firewall.