Pico W: publish MQTT messages from MicroPython over Wi-Fi
Connect a Pico W to Wi-Fi and publish sensor readings to an MQTT broker from MicroPython. The pico equivalent of the ESP32 MQTT tutorial.
The Pico W has Wi-Fi, and MicroPython on the Pico W has an MQTT
client (umqtt.simple). That is enough to publish sensor readings
to a broker every few seconds, just like the ESP32 does. The pattern
is the same: connect to Wi-Fi, connect to the broker, publish a
JSON message, sleep, repeat.
The trade vs. the ESP32: the Pico W’s Wi-Fi is less stable (auto- channel roaming can confuse it), and the MQTT client is more basic (no built-in QoS 2, no automatic reconnect). For a battery-powered sensor that publishes a JSON blob every 5 minutes, the Pico W is fine. For a real-time control system, the ESP32 is the better pick.
This tutorial uses the same broker (Mosquitto) and topic structure as the ESP32 MQTT tutorial, so you can mix the two boards on one broker and one dashboard.
What you need
- Raspberry Pi Pico W (the W version is required for Wi-Fi)
- MicroPython firmware installed (the v1.20+ builds have Wi-Fi and umqtt built in)
- A computer running an MQTT broker. Mosquitto is a one-line install on most systems.
- The Pico W and broker on the same network
Install the broker
On a Raspberry Pi, Linux, or macOS box:
sudo apt install mosquitto # Debian / Ubuntu
brew install mosquitto # macOS
Start it:
mosquitto -v
The -v prints every message to the terminal, which is useful for
debugging.
If you do not have a broker yet and just want to test, you can use a public broker like
test.mosquitto.org, but do not publish anything you would not want the whole internet to see. It is unauthenticated.
Install umqtt on the Pico W
Starting with MicroPython v1.20 for the Pico W, umqtt.simple is
included in the firmware. If you are on an older build, install it
via mip:
import mip
mip.install("umqtt.simple")
The code
Save this as main.py on the Pico W:
import network
import time
from machine import Pin
from umqtt.simple import MQTTClient
SSID = "your-wifi-ssid"
PASSWORD = "your-wifi-password"
BROKER = "192.168.1.50" # your broker IP
TOPIC = "ctrlaltbrian/pico/sensor/temperature"
PUBLISH_INTERVAL_MS = 30000
def connect_wifi():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(SSID, PASSWORD)
print("Connecting to Wi-Fi", end="")
max_wait = 20
while max_wait > 0:
if wlan.isconnected():
break
max_wait -= 1
print(".", end="")
time.sleep(1)
if not wlan.isconnected():
raise RuntimeError("Wi-Fi failed to connect")
print()
print("Connected:", wlan.ifconfig())
return wlan
def fake_temperature_reading():
# Replace this with a real sensor read (DHT22, BME280, etc.)
return 22.5 + (time.ticks_ms() % 100) / 50.0
def main():
wlan = connect_wifi()
client = MQTTClient(
client_id="pico-w-publisher",
server=BROKER,
port=1883,
)
client.connect()
print("MQTT connected")
last_publish = 0
while True:
now = time.ticks_ms()
if time.ticks_diff(now, last_publish) >= PUBLISH_INTERVAL_MS:
temp = fake_temperature_reading()
msg = '{{"temp":{:.1f}}}'.format(temp)
client.publish(TOPIC, msg)
print("Published:", msg)
last_publish = now
client.check_msg() # process any incoming QoS 0 messages
time.sleep_ms(100)
main()
The time.ticks_diff function is the MicroPython way to do non-
blocking elapsed-time math. It is safe across the time.ticks_ms()
overflow (every ~12 days for ms ticks).
Subscribing from another device
Subscribe from any device on the same network to see the messages:
mosquitto_sub -h 192.168.1.50 -t "ctrlaltbrian/#" -v
The -v flag prints the topic too. This is what I use for
debugging more than half the time.
Adding a real sensor
Replace fake_temperature_reading() with a DHT22 read. The
umqtt.simple pattern does not change:
import dht
DHT_PIN = 4
sensor = dht.DHT22(Pin(DHT_PIN))
def read_temperature_and_humidity():
sensor.measure()
return sensor.temperature(), sensor.humidity()
# in main():
temp, hum = read_temperature_and_humidity()
msg = '{{"temp":{:.1f},"hum":{:.1f}}}'.format(temp, hum)
client.publish(TOPIC, msg)
For a BME280 over I2C, the read is the same shape. The BME280 driver
is mip.install("bme280").
Sleep between publishes
For battery-powered projects, the Pico W should sleep between
publishes. MicroPython’s machine.deepsleep() works on the Pico W:
import machine
PUBLISH_INTERVAL_SEC = 300 # 5 minutes
def main():
connect_wifi()
client = MQTTClient("pico-w-sleep", BROKER, port=1883)
client.connect()
temp, hum = read_temperature_and_humidity()
msg = '{{"temp":{:.1f},"hum":{:.1f}}}'.format(temp, hum)
client.publish(TOPIC, msg)
client.disconnect()
wlan = network.WLAN(network.STA_IF)
wlan.disconnect()
machine.deepsleep(PUBLISH_INTERVAL_SEC * 1000)
main()
On wake, MicroPython runs main.py from the top. The Pico W
reconnects to Wi-Fi, publishes one reading, and goes back to sleep.
The deep sleep current on the Pico W is about 1.3 mA (the Wi-Fi chip’s idle draw). For lower power, you can power down the Wi-Fi chip between cycles, but that adds 200 ms of wake-up time.
The Pico W does not have a true deep sleep like the ESP32. The Wi-Fi chip stays powered in
deepsleep()mode. For very low power, use a hardware timer (e.g. the TPL5110) to cut the whole board’s power between cycles.
Will (the QoS option)
umqtt.simple supports QoS 0 and QoS 1:
- QoS 0 (default): fire and forget. The broker may or may not receive the message. Use for things that refresh anyway.
- QoS 1: at-least-once. The broker ACKs the message; the client resends if no ACK. May get duplicates.
To publish with QoS 1:
client.publish(TOPIC, msg, qos=1)
For sensor publishing, QoS 0 is fine. The next reading is in 30 seconds anyway. For commands, use QoS 1.
When the Wi-Fi keeps dropping
The Pico W’s Wi-Fi is less stable than the ESP32’s. The most common failure mode is the connection dropping after a few hours of operation. The fix is a reconnect loop:
def ensure_wifi(wlan):
if not wlan.isconnected():
print("Reconnecting...")
wlan.disconnect()
time.sleep(1)
wlan.connect(SSID, PASSWORD)
for _ in range(20):
if wlan.isconnected():
return
time.sleep(1)
raise RuntimeError("Wi-Fi reconnect failed")
Call ensure_wifi(wlan) before each publish. If the connection is
flaky enough that this fires often, switch to the ESP32.
Topics, the same convention as the ESP32
The convention I use across all my boards is:
ctrlaltbrian/<room>/<device>/<measurement>
For the Pico W:
ctrlaltbrian/garage/pico-w-1/temperaturectrlaltbrian/garden/pico-w-2/soil_moisture
This lets a single Mosquitto broker receive from any combination of ESP32s and Pico Ws, and any dashboard can subscribe to all of one room or all of one measurement across the house.
What you learned
- The Pico W has Wi-Fi and umqtt in MicroPython v1.20+.
- The pattern is the same as ESP32: connect Wi-Fi, connect broker, publish JSON, sleep, repeat.
- The Pico W’s Wi-Fi is less stable than the ESP32’s; add a reconnect loop.
- For battery projects, use
machine.deepsleep()between cycles.
When something breaks
ImportError: no module named 'umqtt'. The MicroPython firmware is older than v1.20. Either flash a new firmware (UF2 file from micropython.org) or install manually:import mip; mip.install("umqtt.simple").OSError: [Errno 113] EHOSTUNREACH. Broker IP is wrong, or the Pico W is on a different network/VLAN. Tryping 192.168.1.50from a laptop on the same Wi-Fi.MQTTException: 5. Broker rejected the connection. The broker may not allow anonymous connections, or the client_id is already in use. Add a unique client_id (use the chip’s unique ID:binascii.hexlify(machine.unique_id()).decode()).- Messages publish but never arrive at the subscriber. The
topic is wrong, or the broker is on a different network. Verify
with
mosquitto_sub -h 192.168.1.50 -t "ctrlaltbrian/#" -vfrom a laptop on the same network.
What to build next
- The ESP32 MQTT publish/subscribe tutorial shows the same pattern on the ESP32. Both boards can publish to one broker.
- The Pico W sensor web server tutorial turns the Pico W into a tiny dashboard, no broker needed.
- The book Pico Wi-Fi Projects covers the asyncio version of this, which runs Wi-Fi, MQTT, and a sensor read in parallel.