Part 3: Sending sensor data from Raspberry Pi Pico to HiveMQ Cloud

Part 3: Sending sensor data from Raspberry Pi Pico to HiveMQ Cloud

author portrait

Written by Frank Delporte

Category: MQTT HiveMQ Cloud

Published: December 16, 2021


In the second post of this blog series, we used Java on the Raspberry Pi mini computer to send sensor data to HiveMQ Cloud, and visualize it on a dashboard. In the third blog post of this series, I am going to add some more data to the messaging system with another member of the Raspberry Pi family, Raspberry Pi Pico.

Introduction to Raspberry Pi Pico

In January 2021, the Raspberry Pi Pico was introduced. This is a completely new type of board as it’s not a full Linux PC, but a microcontroller chip (RP2040) developed by Raspberry Pi itself, on a small and versatile board. This RP2040 features a dual-core Arm Cortex-M0+ processor with 264KB internal RAM and support for up to 16MB of off-chip Flash, combined with a wide range of GPIOs (supporting I2C, SPI, and Programmable I/O (PIO)). So it’s comparable to an Arduino or similar microcontroller board. But the biggest surprise of this Pico: the price of only 4$!

The Raspberry Pi Foundation made sure a very extensive documentation site was available when the board was launched. Programming can be done with a C/C++ and MicroPython SDK.

The Pico provides a lot of GPIOs which are grouped very nicely by separating them with a ground connection. These grounds even have a different shape on the board (no rounded corners), to make it easier to find the correct pin you want to use.

Raspberry Pi Pico pinout diagram

The Pico project

For this post we will extend the Pico with a WiFi module and one distance sensor, as an example how you can easily send sensor data to HiveMQ Cloud from this inexpensive board. To achieve this very low price, the Pico doesn’t contain wireless functions. Luckily there are different possibilities to add WiFi to the Pico, of which the Adafruit AirLift ESP32 WiFi Co-Processor Breakout Board is probably the easiest and cheapest at 9.95$. An extra advantage of the Adafruit products is the big amount of documentation they provide on their website. Most of these examples use a different “flavor” of MicroPython, called CircuitPython, which is based on the same Python implementation, but more focused on beginners, education, and getting started tutorials.

The project in this post is a combination of different examples from Adafruit:

Prepare the Pico for CircuitPython

Let’s start with preparing our Pico for this tutorial. We need to connect the components, flash CircuitPython on the Pico, add libraries, and upload our code.

TIP: All three videos accompanying this MQTT on Raspberry Pi blog post series are avialable on our YouTube Channel. Be sure to watch and complement your knowledge.

Wiring

Take a breadboard and some wires to create this small test setup.

Breadboard wiring with Raspberry Pi Pico, Adafruit AirLift and distance sensor

Adafruit AirLift WiFi

The AirLift is the WiFi module which will handle all the communication between the Pico and HiveMQ Cloud. We need 8 breadboard wires to make the connection between both components. By using the pins 10 till 15 we group all the GPIO connections on one side.

Important remark on the Adafruit website: You MUST use the Pico’s VSYS pin for powering the AirLift Breakout.

Pico AirLift
VSYS Vin
GND GND
GP10 (SPI1 SCK) SCK
GP11 (SPI1 TX) MOSI
GP12 (SPI1 RX) MISO
GP13 (SPI1 CSn) CS
GP14 BUSY
GP15 !RST

Distance sensor

The third component on the breadboard is a HC-SR04 distance sensor, similar to the one in the CrowPi. Here we need 4 wires and for the GPIOs we use the other side of the Pico.

Pico HC-SR04
VBUS Vcc
GND GND
GP16 ECHO
GP17 TRIGGER

CircuitPython on the Pico

For our Pico to support this example CircuitPython project, we need to load the correct firmware and add some libraries.

Firmware

Download the firmware which needs to be installed on the Pico to be able to use it as a CircuitPython device. This .uf2-file for the Raspberry Pi Pico can be downloaded from circuitpython.org/board/raspberry_pi_pico.

Use a USB cable (which is not charge-only), press and hold the BOOTSEL button on the Pico, and connect the cable between the Pico and computer. Wait until you see a new USB drive “RPI-RP2” appearing on your PC to release the BOOTSEL button.

Drag or copy the downloaded .uf2-file to the “RPI-RP2”-drive. When done, this drive will disappear as the Pico reboots, and reappear as a drive called “CIRCUITPY”. Now your Pico is ready to program with CircuitPython.

CircuitPython Libraries

To simplify the use of components, a whole set of libraries is available as one download on circuitpython.org/libraries. Download the ZIP-file, extract it on your PC and copy the directories or files from the following list to a directory “libs” on the “CIRCUITPY”-drive.

  • adafruit_bus_device
  • adafruit_esp32_spi
  • adafruit_hcsr04
  • adafruit_io
  • adafruit_minimqtt
  • adafruit_requests

Libraries and files on the Pico for this example application

IDE

The code-files can be written directly to the Pico with any text editor or IDE. Adafruit advises to use the Mu editor as this allows to both write the code and see the application output in one tool. You can download this tool from codewith.mu.

Install the application on your PC, run it and select Mode > CircuitPython.

Mu editor with a view for both code and output of the board

Code

The sources of this application are available on GitHub in the same repository where you can find the sources of the previous two posts.

Secrets

To separate the generic code from your specific WiFi and HiveMQ Cloud credentials, a separate file is used. Create a file secrets.py and save it to the Pico with following content, using your login, password, etc.

secrets = {
   'ssid' : 'WIFI_NETWORK_NAME',
   'password' : 'WIFI_PASSWORD',
   'timezone' : 'Europe/Brussels',
   'mqtt_username' : 'HIVEMQ_USERNAME',
   'mqtt_key' : 'HIVEMQ_PASSWORD',
   'broker' : 'YOUR_INSTANCE.hivemq.cloud',
   'port' : 8883
}

Main code

Below you find the full code for this project. I tried to explain every step by adding comments and prints. Create a new file in Mu, copy this code and save it to the Pico as code.py.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
import time
import board
import busio
import adafruit_hcsr04
import adafruit_requests as requests
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
import adafruit_minimqtt.adafruit_minimqtt as MQTT

from digitalio import DigitalInOut
from adafruit_esp32spi import adafruit_esp32spi

# Load the WiFi and HiveMQ Cloud credentials from secrets.py
try:
    from secrets import secrets
except ImportError:
    print("Error, secrets could not be read")
    raise

# MQTT Topic to publish data from Pico to HiveMQ Cloud
topic_name = "/img/blog/distance"

# Initialize the Pico pins, WiFi module and distance sensor
esp32_cs = DigitalInOut(board.GP13)
esp32_ready = DigitalInOut(board.GP14)
esp32_reset = DigitalInOut(board.GP15)
spi = busio.SPI(board.GP10, board.GP11, board.GP12)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
hcsr04 = adafruit_hcsr04.HCSR04(trigger_pin=board.GP17, echo_pin=board.GP16)

# Handle HTTP requests
requests.set_socket(socket, esp)

# Check ESP32 status
print("Checking ESP32")
if esp.status == adafruit_esp32spi.WL_IDLE_STATUS:
    print("\tESP32 found and in idle mode")
print("\tFirmware version: ", esp.firmware_version)
print("\tMAC address: ", [hex(i) for i in esp.MAC_address])

# List the detected WiFi networks
print("Discovered WiFi networks:")
for ap in esp.scan_networks():
    print("\t", (str(ap["ssid"], "utf-8")), "\t\tRSSI: ", ap["rssi"])

# Connect to the configured WiFi network
print("Connecting to WiFi: ", secrets["ssid"])
while not esp.is_connected:
    try:
        esp.connect_AP(secrets["ssid"], secrets["password"])
    except RuntimeError as e:
        print("\tCould not connect to WiFi: ", e)
        continue
print("\tConnected to ", str(esp.ssid, "utf-8"), "\t\tRSSI:", esp.rssi)
print("\tIP address of this board: ", esp.pretty_ip(esp.ip_address))
print("\tPing google.com: " + str(esp.ping("google.com")) + "ms")

# Configure MQTT to use the ESP32 interface
MQTT.set_socket(socket, esp)

# Configure MQTT client (uses secure connection by default)
mqtt_client = MQTT.MQTT(
    broker=secrets["broker"],
    port=secrets["port"],
    username=secrets["mqtt_username"],
    password=secrets["mqtt_key"]
)

# Define callback methods and assign them to the MQTT events
def connected(client, userdata, flags, rc):
    print("\tConnected to MQTT broker: ", client.broker)

def disconnected(client, userdata, rc):
    print("\tDisconnected from MQTT broker!")

def publish(mqtt_client, userdata, topic, pid):
    print("\tPublished a message to: ", topic)

mqtt_client.on_connect = connected
mqtt_client.on_disconnect = disconnected
mqtt_client.on_publish = publish

# Connect to the MQTT broker
print("Connecting to MQTT broker...")
try:
    mqtt_client.connect()
    print("\tSucceeded")
except Exception as e:
    print("\tMQTT connect failed: ", e)

# Continuously measure the distance and send the value to HiveMQ
print("Starting the distance measurement")
killed = False
while not killed:
    # Measure distance
    distance = 0
    try:
        distance = hcsr04.distance
    except Exception as e:
        print("Distance measurement failure\n", e)

    # Send to HiveMQ Cloud
    try:
        json = "{\"value\": " + str(distance) + "}"
        print("\tMessage for queue: " + json)
        mqtt_client.publish(topic_name, json)
    except Exception as e:
        print("\tMQTT publish Failed, retrying\n", e)
        killed = True
        continue

    # Sleep a second
    time.sleep(1)

Because code.py is the default filename for the main code, this file will be executed directly, or you can soft-reset the board with CTRL+D in the terminal of Mu.

Program output

In the terminal of Mu, the print-output is shown.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.
code.py output:
Checking ESP32
	ESP32 found and in idle mode
	Firmware version:  bytearray(b'1.2.2\x00')
	MAC address:  ['0x50', '0x18', '0x49', '0x95', '0xab', '0x34']
Discovered WiFi networks:
	 *****1 		RSSI:  -73
	 *****2 		RSSI:  -79
Connecting to WiFi:  *****1
	Connected to  *****1 		RSSI: -71
	IP address of this board:  192.168.0.189
	Ping google.com: 20ms
Connecting to MQTT broker...
	Connected to MQTT broker:  ***.s1.eu.hivemq.cloud
	Succeeded
Starting the distance measurement
	Message for queue: {"value": 106.777}
	Published a message to:  /img/blog/distance
	Message for queue: {"value": 106.352}
	Published a message to:  /img/blog/distance
	Message for queue: {"value": 107.202}

Adding the data to our JavaFX dashboard

With a few small changes, we can now add the data of the Pico messages to our JavaFX application we created in the previous post. The only file which needs to be modified is DashboardView.java.

Code changes

First we add a new variable:

private final Tile gaucheDistancePico;

Inside the constructor we initialize the tile similar to the existing ones, and define a subscription to the topic where the Pico publishes its distance measurements.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
gaucheDistancePico = TileBuilder.create()
    .skinType(Tile.SkinType.GAUGE)
    .prefSize(TILE_WIDTH, TILE_HEIGHT)
    .title("Distance Pico")
    .unit("cm")
    .maxValue(255)
    .build();

client.toAsync().subscribeWith()
    .topicFilter("/img/blog/distance")
    .qos(MqttQos.AT_LEAST_ONCE)
    .callback(this::handlePicoData)
    .send();

One last additional method is needed to parse the received data and update the tile:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public void handlePicoData(Mqtt5Publish message) {
    var sensorData = new String(message.getPayloadAsBytes());
    logger.info("Pico distance data: {}", sensorData);
    try {
        var sensor = mapper.readValue(sensorData, DoubleValue.class);
        gaucheDistancePico.setValue(sensor.getValue());
    } catch (JsonProcessingException ex) {
        logger.error("Could not parse the data to JSON: {}", ex.getMessage());
    }
}

Extended layout

With these small modifications, we now will see the data of both the CrowPi and the Pico in one dashboard.

Screenshot with additional gauche tile

A Great Combo: Raspberry Pi Pico and HiveMQ Cloud MQTT Broker

The Raspberry Pi Pico is a very versatile device for a very low price. With the additional Adafruit WiFi module you can easily add wireless communication. In the meantime, already many new boards based on this same RP2040 microcontroller are available and some of them even have WiFi on-board. A nice overview can be found on tomshardware.com.

TIP
This three-part blog post series is also available as video series, called MQTT on Raspberry Pi on our YouTube Channel. Be sure to watch and complement your knowledge.

HiveMQ Cloud allows you to connect up to 100 devices for free. This is a great way to experiment with messaging. When combining this with Java, Raspberry Pi, and electronic components, exchanging data between different applications and devices becomes very easy.

I hope these three posts can help you get started on creating your own project!

About Frank Delporte

Frank Delporte is the Head of Software Suite at EEVE. He’s the author of the ebook “Getting started with Java on Raspberry Pi.”
newer posts Make your HiveMQ Cloud cluster URL your own!
IoT @AWS re:Invent 2021: The Hotel California Effect older posts