Introduction
In this tutorial, I am going to share with you my Raspberry Pi Pico W-powered MicroPython MQTT project that uses BMP/BME 280 sensor to display its sensor readings in almost real time. We are going to publish MQTT messages to our broker and display the sensor readings in a custom dashboard that will show it in both text and graphical chart format.
If you want to see this project in a video format then please see below. You can also watch this on my YouTube channel.
Design
The image above is how the overall message flows between the different components in our system. There are three major components in this Raspberry Pi Pico W-MicroPython-MQTT BMP/BME280 Weather station project and they are listed below.
- MQTT Broker – the Mosquitto broker acts as our middleman in the message exchange. It is the one that facilitates the processing of incoming MQTT messages and delivers it to interested receivers.
- Raspberry Pi Pico W and the BMP/BME 280 – this is the MQTT client that is the source of our information (publisher). The Raspberry Pi Pico W published the sensor readings as MQTT messages to our broker using the topic “sensorReadings” and inside it is a message that contains the readings from our BMP/BME280 sensor. This application was built using the MicroPython framework using Thonny IDE.
- Dashboard – The dashboard application acts as another MQTT client (subscriber) to our MQTT broker and is subscribed to the same MQTT topic “sensorReadings“. It displays the MQTT messages as text and colorful graphs for better usability. This application was built using Node.js, HTML/CSS/Javascript, and the mqtt.js library.
With the publish/subscribe model of MQTT then it is possible for us to separately code the part that publishes the sensor readings on a particular topic and create our own dashboard that subscribes to the same topic and display its readings. All the clients connected to our MQTT broker do not need to know each other making this setup very much ideal for Internet of Things (IoT) projects.
Why build your own MQTT Dashboard?
I just think it’s fun creating your own dashboard to display your MQTT data especially if you are running your own local instance of an MQTT broker like the Mosquitto in your own Internet of Things (IoT) project like your own Home Automation system. You have options to use popular cloud MQTT brokers which contain built-in support for displaying MQTT data but that would be the subject of another post. Another option is to use the popular tool called Node-Red but we will get to that later also.
Why use MQTT over HTTP?
The popular question you might ask is, “why not just communicate directly with the Raspberry Pi Pico W thru HTTP(Hypertext Transfer Protocol) and remove the middleman (MQTT broker) from the equation?”.
If you have been an avid reader of my blog post then you might be familiar with my earlier post about Raspberry Pi Pico W: BME280 Weather Station Dashboard wherein we used the same Raspberry Pi Pico W MCU board as a web server that will be the source of our information and we are able to retrieve the BMP/BME280 sensor readings as both text and graph chart also. However, if you are a keen observer then you would notice that we developed everything inside the Raspberry Pi Pico W board. There are performance limits if we do this as the microcontroller boards have both limited memory and space.
HTTP is much heavier than MQTT in terms of the use of network bandwidth after all MQTT protocol was developed for this purpose. If we have multiple users accessing our Raspberry Pi Pico W MicroController board then we cannot add more memory to our MCU boards to service the incoming requests. Also, the MicroPython framework was not specifically designed to display and service large web application requests. There are excellent tools like Node.js, Express, or even React.js for the purpose of developing web applications.
Lastly, I personally think that many of the users of MQTT will now work in the browser as more users are using their mobile devices more and more. Fortunately, our browser can now support the MQTT message exchange thru WebSocket.
Why use WebSocket over MQTT?
If you take a look at my design diagram above then you might have noticed that the messages are delivered thru MQTT over WebSocket.
Browsers by default cannot communicate with our MQTT broker so we need to wrap the MQTT packet inside a WebSocket envelope. If you are not familiar with what WebSocket is and how MQTT over WebSocket works then please see my post about it earlier.
Related Content:
Using WebSocket in the Internet of Things (IoT) projects
Publish and Subscribe MQTT messages over WebSocket
Now that is a bird’s eye view of the “lousy” design part of this project and I know you would like to know how this project works in code. 🙂
Components/Parts Required
The following are the needed components/parts to be able to follow along with this post.
- Raspberry Pi Pico W – Amazon | AliExpress
- BME280/BMP280 – Amazon | AliExpress
- Breadboard – Amazon | AliExpress | Bangood
- Connecting Wires – Amazon | AliExpress | Bangood
Disclosure: These are affiliate links and I will earn small commissions to support my site when you buy through these links.
Prerequisites
You will need an MQTT broker that you are able to connect with WebSocket enabled. I have used Mosquitto in developing this post. You also need to be familiar with the basics of MQTT and how you can use it in your MicroPython program. Please refer to the earlier post I have written if you are not familiar with this before proceeding further.
Related Content:
Install Mosquitto MQTT Windows
Pico W – MicroPython MQTT tutorial using umqtt.simple library
I have used the Thonny IDE while doing this project but you can use Visual Studio Code with the PyMkr extension as well.
Related Content:
MicroPython Development Using Thonny IDE
MicroPython using VSCode PyMakr on ESP32/ESP8266
Wiring/Schematic
The above image shows the wiring and schematic of our Raspberry Pi Pico W-powered MicroPython-based MQTT example project. I am using the I2C module of the BMP/BME 280 sensor so I am needing only the 4 pins. If you are not familiar with how BMP/BME 280 sensor works with MicroPython then please read through my earlier post about it below.
Related Content:
Raspberry Pi Pico W: BME280 Weather Station Dashboard
Code
As mentioned in the Design section, we need to discuss two lines of code for this MicroPython MQTT example weather station dashboard project.
- Custom MQTT Dashboard created using Node.js, HTML, CSS, and Javascript
- Raspberry Pi Pico W MQTT MicroPython program
Custom MQTT Dashboard created using Node.js, HTML, CSS, and Javascript
I have discussed extensively this project in my earlier post about How to build your own custom MQTT dashboard? This project requires some familiarity with Node.js, Express, and the use of mqtt.js.
Related Content:
MQTT using Node.js with practical examples
I suggest you scan through the explanation there on how this project works but if you would like to run this project then follow the steps outlined in my GitHub repository or follow the steps below. You should have installed Node.js runtime in your environment prior to running these steps and have Git.
Related Content:
Install Node.js on Windows
Clone the repository in a shell or terminal.
git clone https://github.com/donskytech/mqtt-custom-dashboard-node-js.git
cd mqtt-custom-dashboard-node-js
Rename the .env.local to .env and edit the .env and set the MQTT broker URL to the MQTT broker that you are using. If you are using a locally-hosted Mosquitto MQTT broker then this should point to the IP Address where you installed it. This is my current .env configuration setup.
NAME=DONSKYTECH
DASHBOARD_TITLE=Raspberry Pi Pico W - MicroPython MQTT - BME280 WEATHER STATION DASHBOARD
MQTT_BROKER=ws://192.168.100.22:9001/mqtt
MQTT_TOPIC=sensorReadings
Install the dependencies and run the project.
npm install && npm run dev
If everything is okay then open your browser and type in the following URL. This should open up our custom MQTT dashboard with blank values.
http://localhost:3000
Raspberry Pi Pico W MQTT MicroPython program
The complete code for our Raspberry Pi Pico W MicroPython MQTT example project that will publish the sensor readings from our BME/BMP 280 sensor can be found in my GitHub repository and it is shown below.
You can either download the program as a zip file or clone it using the below command.
git clone https://github.com/donskytech/micropython-raspberry-pi-pico.git
cd mqtt-bme280-weather-station
Open the project folder in Thonny IDE.
Libraries used
The libraries used by this project are the following:
- micropython-bme280 – for communicating with our BMP/BME280 sensor.
- micropython-umqtt.simple – MQTT messaging library
You can use the Thonny IDE in installing the packages above. If you are not familiar with how to do this then please see How to install MicroPython libraries or packages in Thonny IDE?
Let us walk thru what each file does.
bme_module.py
import machine
import bme280
import math
class BME280Module:
SEA_LEVEL_PRESSURE_HPA = 1013.25
def __init__(self, id, scl_pin, sda_pin):
self.i2c = machine.I2C(id=id, scl=machine.Pin(scl_pin), sda=machine.Pin(sda_pin), freq=400000)
self.bme = bme280.BME280(i2c=self.i2c)
def get_sensor_readings(self):
(temperature, pressure, humidity) = self.bme.values
temperature_val = float(temperature[:len(temperature) - 1])
humidity_val = float(humidity[:len(humidity) - 1])
pressure_val = float(pressure[:len(pressure) - 3])
# Altitude calculation
altitude_val = 44330 * (1.0 - math.pow(pressure_val / BME280Module.SEA_LEVEL_PRESSURE_HPA, 0.1903))
return (temperature_val, pressure_val, humidity_val, altitude_val)
To connect with our BMP/BME 280 sensor then we use this bme_module.py file.
import machine
import bme280
import math
Import the necessary packages to read our BMP/BME280 sensor.
class BME280Module:
SEA_LEVEL_PRESSURE_HPA = 1013.25
def __init__(self, id, scl_pin, sda_pin):
self.i2c = machine.I2C(id=id, scl=machine.Pin(scl_pin), sda=machine.Pin(sda_pin), freq=400000)
self.bme = bme280.BME280(i2c=self.i2c)
Create the class that will represent our BMP/BME280 sensor. In the constructor method, we require the bus id, SCL, and SDA pins for I2C communications. Next, we create an instance of I2C
class passing in the parameters. Lastly, we create an instance of BME280
using the earlier I2C
class.
The SEA_LEVEL_PRESSURE_HPA is used to approximate the altitude calculation.
def get_sensor_readings(self):
(temperature, pressure, humidity) = self.bme.values
temperature_val = float(temperature[:len(temperature) - 1])
humidity_val = float(humidity[:len(humidity) - 1])
pressure_val = float(pressure[:len(pressure) - 3])
# Altitude calculation
altitude_val = 44330 * (1.0 - math.pow(pressure_val / BME280Module.SEA_LEVEL_PRESSURE_HPA, 0.1903))
return (temperature_val, pressure_val, humidity_val, altitude_val)
The get_sensor_readings()
is the method that we are going to call to retrieve the sensor readings from our BMP/BME 280 sensor. The altitude calculation is an approximate calculation as you should know the sea level pressure at the time the pressure was taken to get an accurate altitude value. The temperature, humidity, pressure, and altitude are returned to the caller as a Python tuple.
boot.py
# boot.py -- run on boot-up
import network, utime, machine
# Replace the following with your WIFI Credentials
SSID = "<PLACE_YOUR_SSID_HERE>"
SSID_PASSWORD = "<PLACE_YOUR_WIFI_PASWORD_HERE>"
def do_connect():
sta_if = network.WLAN(network.STA_IF)
if not sta_if.isconnected():
print('connecting to network...')
sta_if.active(True)
sta_if.connect(SSID, SSID_PASSWORD)
while not sta_if.isconnected():
print("Attempting to connect....")
utime.sleep(1)
print('Connected! Network config:', sta_if.ifconfig())
print("Connecting to your wifi...")
do_connect()
The boot.py is what we used to connect to our Wifi network. This file gets executed first when our Raspberry Pi Pico W gets restarted.
# boot.py -- run on boot-up
import network, utime, machine
# Replace the following with your WIFI Credentials
SSID = "<PLACE_YOUR_SSID_HERE>"
SSID_PASSWORD = "<PLACE_YOUR_WIFI_PASWORD_HERE>"
We import the packages needed to connect to our network. Replace the two variables to match the credentials of your Wifi network.
def do_connect():
sta_if = network.WLAN(network.STA_IF)
if not sta_if.isconnected():
print('connecting to network...')
sta_if.active(True)
sta_if.connect(SSID, SSID_PASSWORD)
while not sta_if.isconnected():
print("Attempting to connect....")
utime.sleep(1)
print('Connected! Network config:', sta_if.ifconfig())
print("Connecting to your wifi...")
do_connect()
This is the function that will allow us to connect to your Wifi network.
main.py
import time
import ubinascii
from umqtt.simple import MQTTClient
import machine
import random
from bme_module import BME280Module
import ujson
# Default MQTT_BROKER to connect to
MQTT_BROKER = "192.168.100.22"
CLIENT_ID = ubinascii.hexlify(machine.unique_id())
SUBSCRIBE_TOPIC = b"led"
PUBLISH_TOPIC = b"sensorReadings"
# Publish MQTT messages after every set timeout
last_publish = time.time()
publish_interval = 5
# Pin assignment
I2C_ID = 0
SCL_PIN = 1
SDA_PIN = 0
bme_module = BME280Module(I2C_ID,SCL_PIN,SDA_PIN)
# Received messages from subscriptions will be delivered to this callback
def sub_cb(topic, msg):
print((topic, msg))
if msg.decode() == "ON":
led.value(1)
else:
led.value(0)
# Reset the device in case of error
def reset():
print("Resetting...")
time.sleep(5)
machine.reset()
# Read the BMP/BME280 readings
def get_temperature_reading():
return bme_module.get_sensor_readings()
# Main program
def main():
print(f"Begin connection with MQTT Broker :: {MQTT_BROKER}")
mqttClient = MQTTClient(CLIENT_ID, MQTT_BROKER, keepalive=60)
mqttClient.set_callback(sub_cb)
mqttClient.connect()
mqttClient.subscribe(SUBSCRIBE_TOPIC)
print(f"Connected to MQTT Broker :: {MQTT_BROKER}, and waiting for callback function to be called!")
while True:
# Non-blocking wait for message
mqttClient.check_msg()
global last_publish
current_time = time.time()
if (current_time - last_publish) >= publish_interval:
temperature, pressure, humidity, altitude = get_temperature_reading()
readings = {"temperature": temperature, "pressure": pressure,"humidity": humidity, "altitude": altitude}
mqttClient.publish(PUBLISH_TOPIC, ujson.dumps(readings).encode())
last_publish = current_time
time.sleep(1)
if __name__ == "__main__":
while True:
try:
main()
except OSError as e:
print("Error: " + str(e))
reset()
The main.py contains the bulk of the logic on how we are going to send our BMP/BME 280 sensor readings to our MQTT broker.
import time
import ubinascii
from umqtt.simple import MQTTClient
import machine
import random
from bme_module import BME280Module
import ujson
Import the necessary packages in order to communicate with our MQTT broker including our bme_module.py for the retrieval of sensor readings from our BMP/BME 280 sensor.
# Default MQTT_BROKER to connect to
MQTT_BROKER = "192.168.100.22"
CLIENT_ID = ubinascii.hexlify(machine.unique_id())
SUBSCRIBE_TOPIC = b"led"
PUBLISH_TOPIC = b"sensorReadings"
These are variables to represent the IP address or DNS name of our MQTT broker and the unique client id that we will generate. The SUBSCRIBE_TOPIC
and PUBLISH_TOPIC
are the topics that we are gonna be subscribing to and publishing with our MQTT broker.
We are gonna published the “sensorReadings” topic together with a message that will contain the readings from our BMP/BME 280 sensor.
# Publish MQTT messages after every set timeout
last_publish = time.time()
publish_interval = 5
This is our publish interval as we are gonna be sending a reading every 5 seconds.
# Pin assignment
I2C_ID = 0
SCL_PIN = 1
SDA_PIN = 0
bme_module = BME280Module(I2C_ID,SCL_PIN,SDA_PIN)
These are the pin assignments that we will use in order to connect with our sensor.
# Received messages from subscriptions will be delivered to this callback
def sub_cb(topic, msg):
print((topic, msg))
if msg.decode() == "ON":
led.value(1)
else:
led.value(0)
# Reset the device in case of error
def reset():
print("Resetting...")
time.sleep(5)
machine.reset()
# Read the BMP/BME280 readings
def get_temperature_reading():
return bme_module.get_sensor_readings()
The sub_cb()
function is our callback function for any topics that we are subscribed in.
On the other hand, the reset() function is used to reset our MicroPython device if any error is encountered.
Lastly, the get_temperature_reading()
function is used to retrieve the sensor readings by using our bme_module.py file.
# Main program
def main():
print(f"Begin connection with MQTT Broker :: {MQTT_BROKER}")
mqttClient = MQTTClient(CLIENT_ID, MQTT_BROKER, keepalive=60)
mqttClient.set_callback(sub_cb)
mqttClient.connect()
mqttClient.subscribe(SUBSCRIBE_TOPIC)
print(f"Connected to MQTT Broker :: {MQTT_BROKER}, and waiting for callback function to be called!")
while True:
# Non-blocking wait for message
mqttClient.check_msg()
global last_publish
current_time = time.time()
if (current_time - last_publish) >= publish_interval:
temperature, pressure, humidity, altitude = get_temperature_reading()
readings = {"temperature": temperature, "pressure": pressure,"humidity": humidity, "altitude": altitude}
mqttClient.publish(PUBLISH_TOPIC, ujson.dumps(readings).encode())
last_publish = current_time
time.sleep(1)
if __name__ == "__main__":
while True:
try:
main()
except OSError as e:
print("Error: " + str(e))
reset()
The main()
function is the entry point of our program and it does the following:
- initiate a connection to our MQTT broker by calling the
mqttClient.connect()
method. - sets the callback function
- to subscribe to the dummy topic
- and listen for MQTT messages from our broker by using the
mqttClient.check_msg()
- read the BMP/BME 280 sensor every 5 seconds and publish an MQTT message to the topic “sensorReadings“. The message that we are sending is in JSON format by calling the
ujson.dumps()
call.
A note about the MQTT messages
Take note that I intentionally send all the sensor readings in one MQTT message for simplicity’s sake in this post but according to this MQTT Topics Best Practices then we should have published the MQTT messages like this.
# instead of single MQTT topic
sensorReadings
# we should have published to multiple topics
sensor/bme280/temperature
sensor/bme280/humidity
sensor/bme280/pressure
sensor/bme280/altitude
That is all for the code on how we can publish MQTT messages from our MicroPython-powered Raspberry Pi Pico W microcontroller board using this BMP/BME 280 Weather Station Dashboard example project.
Running the project
Make sure to install first the required libraries by using your Thonny IDE and then upload the three files. After which, send a soft reboot of your MicroPython device.
You should be seeing the following message displayed at your terminal.
MPY: soft reboot
Connecting to your wifi...
Connected! Network config: ('192.168.100.223', '255.255.255.0', '192.168.100.1', '192.168.100.1')
Begin connection with MQTT Broker :: 192.168.100.22
Connected to MQTT Broker :: 192.168.100.22, and waiting for callback function to be called!
Wrap Up
I have shown you how powerful the MQTT protocol is when developing your own Internet of Things (IoT) project in this post. We were able to connect and publish MQTT messages from our MicroPython-powered Raspberry Pi Pico W microcontroller boards.
I hope you learned something! Happy Exploring!
Leave a Reply