Raspberry Pi Pico W MQTT: Publish Sensor Data and Receive Commands
A web server on the Pico W works well inside your home network, but it does not scale: every client must know the Pico's IP address and poll it. MQTT turns this around. Devices connect out to a central broker, publish messages on named topics, and subscribe to the topics they care about. A dashboard, a phone app, a Node-RED flow or another microcontroller can all exchange data through the broker without knowing anything about each other. In this tutorial your Pico W will publish its temperature every ten seconds and accept LED commands over MQTT, with a last-will message and automatic reconnection.
MQTT Concepts You Need
| Term | Meaning | Example in this project |
|---|---|---|
| Broker | Server that receives and forwards all messages | test.mosquitto.org on port 1883, or your own Mosquitto |
| Topic | Slash-separated name for a message stream | plearn/pico1/temp |
| Publish | Send a message to a topic | Pico sends 31.2 to the temp topic |
| Subscribe | Ask the broker for messages on a topic | Pico listens on plearn/pico1/led/set |
| Wildcards | + matches one level, # matches the rest | plearn/# receives everything |
| Retain | Broker stores the last message for new subscribers | Status online is retained |
| Last will | Message the broker publishes if the client vanishes | Status becomes offline |
| QoS 0 / 1 | At most once / at least once delivery | QoS 0 used here for simplicity |
Setup
- Flash the Pico W MicroPython firmware and create
secrets.pywithSSIDandPASSWORD, as in the web-server tutorial. - Check that the MQTT client is available by typing
from umqtt.simple import MQTTClientin the REPL. If it fails, connect to Wi-Fi and runimport mipthenmip.install('umqtt.simple'), or installmicropython-umqtt.simplefrom Thonny's Manage packages. - On your PC, install the Mosquitto command-line clients (
mosquitto_pubandmosquitto_sub) or a graphical tool such as MQTT Explorer for testing.
The MQTT Client Program
# main.py - Pico W MQTT node: publish temperature, subscribe to LED commands
import network
import time
import ubinascii
import machine
from machine import Pin, ADC
from umqtt.simple import MQTTClient
import secrets
BROKER = 'test.mosquitto.org'
PORT = 1883
CLIENT_ID = b'pico-' + ubinascii.hexlify(machine.unique_id())
BASE = b'plearn/pico1/' # change to something unique
T_TEMP = BASE + b'temp'
T_STATUS = BASE + b'status'
T_LED_SET = BASE + b'led/set'
T_LED_STATE = BASE + b'led/state'
PUBLISH_EVERY_MS = 10_000
led = Pin('LED', Pin.OUT)
temp_adc = ADC(4)
def chip_temp():
total = sum(temp_adc.read_u16() for _ in range(32)) / 32
v = total * 3.3 / 65535
return 27 - (v - 0.706) / 0.001721
def wifi_connect():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
wlan.connect(secrets.SSID, secrets.PASSWORD)
for _ in range(40): # wait up to 20 s
if wlan.isconnected():
break
time.sleep(0.5)
if not wlan.isconnected():
raise RuntimeError('Wi-Fi not connected')
print('IP:', wlan.ifconfig()[0])
def on_message(topic, msg):
# Called from check_msg() when a subscribed message arrives
print('RX', topic, msg)
if topic == T_LED_SET:
cmd = msg.strip().lower()
if cmd in (b'on', b'1'):
led.on()
elif cmd in (b'off', b'0'):
led.off()
elif cmd == b'toggle':
led.toggle()
client.publish(T_LED_STATE, b'on' if led.value() else b'off', retain=True)
def mqtt_connect():
c = MQTTClient(CLIENT_ID, BROKER, port=PORT, keepalive=60)
c.set_callback(on_message)
# If the Pico disappears, the broker publishes 'offline' for us
c.set_last_will(T_STATUS, b'offline', retain=True)
c.connect()
c.publish(T_STATUS, b'online', retain=True)
c.subscribe(T_LED_SET)
print('MQTT connected as', CLIENT_ID)
return c
wifi_connect()
client = mqtt_connect()
last_pub = time.ticks_ms() - PUBLISH_EVERY_MS
failures = 0
while True:
try:
client.check_msg() # non-blocking: handle any incoming message
if time.ticks_diff(time.ticks_ms(), last_pub) >= PUBLISH_EVERY_MS:
last_pub = time.ticks_ms()
payload = '{:.1f}'.format(chip_temp())
client.publish(T_TEMP, payload)
print('TX', T_TEMP, payload)
failures = 0
time.sleep_ms(100)
except OSError as e:
# Network or broker problem: wait, then rebuild the connection
failures += 1
print('Connection lost:', e, 'attempt', failures)
if failures > 5:
machine.reset() # last resort: clean restart
time.sleep(5)
try:
wifi_connect()
client = mqtt_connect()
except OSError as e2:
print('Reconnect failed:', e2)
except RuntimeError as e3:
print(e3)
Testing from Your Computer
Run the program on the Pico W, then open two terminals on your PC. In the first, subscribe to everything under your prefix; -v prints the topic with each message. In the second, send LED commands.
Terminal 1: mosquitto_sub -h test.mosquitto.org -t "plearn/pico1/#" -v plearn/pico1/status online plearn/pico1/temp 30.8 plearn/pico1/temp 30.9 Terminal 2: mosquitto_pub -h test.mosquitto.org -t "plearn/pico1/led/set" -m on mosquitto_pub -h test.mosquitto.org -t "plearn/pico1/led/set" -m toggle
The LED reacts within a fraction of a second, and Terminal 1 shows the new led/state. Now pull the Pico's USB cable. After the keepalive period expires (about one and a half times the 60 s keepalive), Terminal 1 prints plearn/pico1/status offline: that is the last will at work.
Design Notes
- The client ID is built from
machine.unique_id(), so two boards running the same code never kick each other off the broker. Brokers disconnect an existing client when another connects with the same ID. - Topics and payloads are bytes (
b'...'); umqtt.simple works with bytes throughout. check_msg()returns immediately if nothing is waiting, which lets the loop publish on a schedule.wait_msg()would block until a message arrives.- Publishing every 10 s keeps the connection active within the 60 s keepalive. If you publish less often, call
client.ping()periodically. - Retained
statusandled/statemessages mean a dashboard that connects later still sees the current state immediately.
Topic and Payload Design
This example sends one plain number per topic, which is easy to read in a terminal and easy to graph in tools such as Node-RED. When a node has several sensors, many projects instead publish one JSON object, for example {"temp": 30.8, "hum": 61}, built with MicroPython's json.dumps(). Whichever style you pick, keep a consistent hierarchy such as site/device/measurement, use lowercase names, and keep command topics (led/set) separate from state topics (led/state). That separation prevents a device from reacting to its own state messages and makes wildcard subscriptions predictable.
Troubleshooting
MQTTException: 5: the broker refused authorisation. Passuser=andpassword=toMQTTClientif your broker requires them.OSErrorfromconnect()right after Wi-Fi comes up: often the broker name could not be resolved or port 1883 is blocked by the network (some college and office networks block it). Try a phone hotspot or your own broker's IP address.- Commands not received: the topic strings differ by a character or case. MQTT topics are case-sensitive.
- Frequent reconnects: a weak Wi-Fi signal. Move closer to the router or reduce publish rate.
Summary
With umqtt.simple your Pico W became a proper IoT node: it publishes readings to a broker, reacts to commands on a subscribed topic, reports online and offline status through retained messages and a last will, and recovers from network errors. From here you can connect the same topics to Node-RED, Home Assistant or a cloud MQTT service.