STFN

Raspberry Pi Pico Weather Station - part II

15 minutes

This post is a continuation of Part I in which we build a Raspberry Pi Pico W with BME280 and DHT11 sensors and made it retrieve weather data. In this part we’ll connect to Wifi to send that data to a MQTT broker on another machine.

I’ll continue where I left in the previous part, so it’s crucial that you first reach the state described in the end of Part I in order to continue with this part.

Starting simple - connecting to Wifi

Let’s start with just connecting to Wifi. The code snippet below is taken from MicroPython docs, however I spiced it up to make debugging a little bit easier later on.

In Thonny, create a file with a nice name like connect.py on the Raspberry Pi Pico W.

import time
import network
import machine

led = machine.Pin("LED", machine.Pin.OUT)

ssid = "NETWORK-NAME"
password = "PASSWORD"

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

max_wait = 10
while max_wait > 0:
    led.on()
    time.sleep(0.1)
    led.off()
    if wlan.isconnected():
        break
    max_wait -= 1
    time.sleep(1)

if not wlan.isconnected():
    for _ in range(5):
        led.on()
        time.sleep(0.1)
        led.off()
        time.sleep(0.1)
        led.on()
        time.sleep(0.1)
        led.off()
        time.sleep(0.7)
    time.sleep(1)
    machine.reset()
else:
    status = wlan.ifconfig()
    led.on()
    time.sleep(3)
    led.off()

Let’s now dissect the code to better understand what is happening.

import time
import network
import machine

led = machine.Pin("LED", machine.Pin.OUT)

ssid = "NETWORK NAME"
password = "PASSWORD"

To start, some configuration, import the needed modules, setting up the built-in LED and defining the Wifi name and password. Set your own values of course :)

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

Set the Wifi module to station mode. This is needed because Pico W by default is set in access point mode, but we will be using it to connect to another network. Then activate the interface and finally connect.

max_wait = 10
while max_wait > 0:
    led.on()
    time.sleep(0.1)
    led.off()
    if wlan.isconnected():
        break
    max_wait -= 1
    time.sleep(1)

Wait 10 seconds for the establishing the connection. Blink the LED each second. If Pico connected to Wifi, end the waiting loop.

if not wlan.isconnected():
    for _ in range(5):
        led.on()
        time.sleep(0.1)
        led.off()
        time.sleep(0.1)
        led.on()
        time.sleep(0.1)
        led.off()
        time.sleep(0.7)
    timer.sleep(1)
    machine.reset()
else:
    status = wlan.ifconfig()
    led.on()
    time.sleep(3)
    led.off()

This part of code is probably self explanatory. If after 10 seconds connection was not established, do two rapid blinks every 0.7 seconds, five times, wait one second and reset the machine to try again. If the connection was established, light the LED for three seconds.

Why blink the LED when we can see on the screen if the connection was established or not? This is going to be a weather station, and there is little point of having a weather measuring device always connected to a computer with a screen, isn’t it? By having the LED show the connection status, we will be able to see what’s going on without a screen, with the Pico running on it’s own, on a balcony, in a shed or a field.

I added the reset-and-try-again code because from my experience, Wifi on a Pico W is often janky, and tends to sometimes fail connecting. Trying again often solves the issue. As an additional exercise, you might implementing resetting the Pi after an increasing interval of time, that would be a nice exercise in Python.

Sending data with MQTT

Awesome, we can now connect to Wifi, now let’s send some data! This section will be connecting what we achieved in Part I with Wifi, so again, if you didn’t do Part I, go there first.

As the data protocol I have chosen MQTT, because it is the industry standard for Internet of Things (IoT) communication, it’s lightweight and can be easily implemented on a Pico W, so it’s perfect for our usecase.

To start with MQTT, we’ll need a broker. Broker is a piece of software that will collect the data send by the Pico, and allow other programs to digest that data. It has to be run on a different machine than a Pico, any Linux or Windows machine will do, but preferably it should be something that is running 24/7 to collect data without stoppages. A popular broker is Mosquitto, and you can install it from its Downloads page.

Once it is installed, you should have some additional tools available as well, the first one we’ll use is mosquitto_sub, a CLI tool to subscribe to topics.

MQTT is based on subscribing and submitting to topics. A topic is like a newsletter, you can send messages to it, and everyone that has subscribed to that particular newsletter will receive everything that you are sending.

Fire up a terminal window, and run

mosquitto_sub  -t '#'

-t ’#’ means that we have subscribed to any possible topic. The process will now wait for any message sent on a topic to the machine that you are running the subcriber on. Leave it running in the background, it will be useful in just a moment. For now, let’s go back to Thonny.

There, we will expand the script that we just created, combining the Wifi connection magic, with MQTT spells and what we did in Part I.

As for the library to use MQTT on a Pico, umqtt should be available straight from the box as a part of MicroPython core, but if for any reason it’s not there on your device, try installing it the same way as we installed bme280 in Part I.

Right, so, the full code for this part:

import json
import time

import bme280
import dht
import machine
import network
from umqtt.simple import MQTTClient

led = machine.Pin("LED", machine.Pin.OUT)

ssid = "NETWORK NAME"
password = "PASSWORD"

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

max_wait = 10
while max_wait > 0:
    led.on()
    time.sleep(0.1)
    led.off()
    if wlan.isconnected():
        break
    max_wait -= 1
    time.sleep(1)

if not wlan.isconnected():
    for _ in range(5):
        led.on()
        time.sleep(0.1)
        led.off()
        time.sleep(0.1)
        led.on()
        time.sleep(0.1)
        led.off()
        time.sleep(0.7)
    time.sleep(1)
    machine.reset()
else:
    status = wlan.ifconfig()
    led.on()
    time.sleep(3)
    led.off()

SCL_PIN = machine.Pin(1)
SDA_PIN = machine.Pin(0)
DHT_PIN = machine.Pin(15)

i2c = machine.I2C(0, scl=SCL_PIN, sda=SDA_PIN)
bme_sensor = bme280.BME280(i2c=i2c)
d_sensor = dht.DHT11(DHT_PIN)

temperature, pressure, _ = bme_sensor.read_compensated_data()
temperature = temperature / 100
pressure = pressure / 256 / 100

d_sensor.measure()
humidity = d_sensor.humidity()

payload = {
    "temperature": temperature,
    "pressure": pressure,
    "humidity": humidity,
}

qt = MQTTClient("umqtt_client", "BROKER_IP", keepalive=3600)
qt.connect()
qt.publish(b"weather", json.dumps(payload))
qt.disconnect()

The code above is basically the combination of connecting to Wifi from the beginning of this post, and gathering data from sensors in Part I, but there are some slight changes, and new parts, so let us focus on those.

import json
import time

import bme280
import dht
import machine
import network
from umqtt.simple import MQTTClient

Note that we need to import some more libraries to do the things that need to happen.

payload = {
    "temperature": temperature,
    "pressure": pressure,
    "humidity": humidity,
}
print(payload)
qt = MQTTClient("umqtt_client", "BROKER_IP", keepalive=3600)
qt.connect()
qt.publish(b"weather", json.dumps(payload))
qt.disconnect()

As previously, we are building the payload, but then something new happens. Instead of just printing it, a MQTT client is initialized, we attempt to connect to the broker, and yes, the data is send! Then it’s only a matter of closing the connection, and we are done.

The BROKER_IP needs to be changed to the IP of the computer that you are running mosquitto_sub. Remember that this needs to be the internal LAN IP of the machine. On Linux you can find it using ip addr in the terminal.

Run the script in Thonny. If everything is configured correctly, the weather data should be printed out in the Thonny console. But that’s not everything, go to the terminal window with mosquitto_sub running. In it you should see the payload as well! This is how it looked like for me:

stefan@home:~ $ mosquitto_sub  -t '#'
{"pressure": 1012, "humidity": 45, "temperature": 24}

Every time you run the script, a new line should appear, with new readouts.

This is a great step ahead in our journey to create a RPi Pico weather station, now the sensors data can be seen on a different machine on the network, a computer that is much more powerful than a Pico. And so that data can be manipulated, aggregated, presented on a graph or used in an automation, the possibilities are really endless here. But that all will come in Part III, where we will use the data to show it on a Grafana dashboard.

As a homework, think how would you make the Pico collect and send the data continuously, for example every 5 minutes?