Last Updated: September 8, 2023
Originally Published: August 15, 2023
Skill Level: Intermediate
Table Of Contents
- Introduction
- What Is Needed
- Background Information
- The Mosquitto MQTT Broker
- The Arduino MQTT Client
- The Raspberry Pi MQTT Client
- Additional Resources
- Summary
Introduction
This tutorial will demonstrate how to use the MQTT protocol to transfer commands and data to and from your computer, an Arduino, and either a Raspberry Pi single board computer (SBC) or a Raspberry Pi Pico W microcontroller board.
A basic understanding of programming is expected along with some familiarity with the Arduino and Raspberry Pi platforms. If you are new to these devices, or would just like to refresh your knowledge, please see our Blink: Making An LED Blink On An Arduino Uno, Blink: Making An LED Blink On A Raspberry Pi, and Getting Started With CircuitPython On Raspberry Pi With Blinka tutorial(s) before proceeding with this one.
The resources created for this tutorial are available on GitHub for your reference.
This tutorial is provided as a free service to our valued readers. Please help us continue this endeavor by considering a GitHub sponsorship or a PayPal donation.
What Is Needed
- Linux, macOS, Or Windows Based Computer With A USB Port
- Arduino IDE
- WiFi Enabled Arduino Board (UNO WiFi Rev2 available on Arduino and Digi-Key; UNO R4 WiFi on Arduino and Sparkfun) With Compatible USB Cable
- Raspberry Pi SBC Running Raspberry Pi OS Or Similar Linux Based Operating System (available on Raspberry Pi and Adafruit)
- Optional Raspberry Pi Pico W (available on Raspberry Pi and Adafruit) With Compatible USB Cable
- Optional Mu Python Editor (for use with Raspberry Pi Pico W)
Background Information
MQTT is a simple and lightweight messaging protocol standard used for publishing and subscribing to topics (data) through a central broker. It is the most commonly used messaging protocol for the Internet of Things (IoT). MQTT was originally developed by IBM and stood for Message Queuing Telemetry Transport, derived from message queues within the IBM MQ product name, but as of 2013, MQTT officially doesn’t stand for anything. MQTT utilizes the server/client model where distributed clients communicate through a central broker server. That communication is further based on a publish/subscribe architecture for sending and receiving messages. To learn more about the MQTT protocol, check out the MQTT organization and the excellent MQTT Essentials tutorial series by HiveMQ.
In this tutorial, I am using a Raspberry Pi 3 Model B running the Raspberry Pi OS operating system and an Arduino UNO WiFi Rev2 board connected to a macOS based computer running the desktop Arduino IDE. The Arduino and the Raspberry Pi act as MQTT clients connecting to the central MQTT broker server, which is also running on the Raspberry Pi. My Mac also acts as a client where I subscribe and publish to topics for testing the operation of the broker along with the Arduino and Raspberry Pi clients. The Raspberry Pi client program is compatible with both the Raspberry Pi computer and the Raspberry Pi Pico W microcontroller board. The Mu editor was used on my Mac with the Pico W board. If you are using a different but comparable setup, the vast majority of this tutorial should still apply, however, some minor changes may be necessary.
We will not be connecting any additional components to our client boards in this tutorial. We will just use their existing onboard capabilities in demonstrating the device-to-device communications using the MQTT protocol.
If you need assistance with your particular setup, post a question in the comments section below and I, or someone else, can try to help you.
The Mosquitto MQTT Broker
According to the Mosquitto website, “Eclipse Mosquitto is an open source (EPL/EDL licensed) message broker that implements the MQTT protocol versions 5.0, 3.1.1 and 3.1. Mosquitto is lightweight and is suitable for use on all devices from low power single board computers to full servers”.
Mosquitto is an extremely popular broker and is available for use on many platforms. It even provides a set of command line utilities, including the popular mosquitto_pub and mosquitto_sub clients, that can be used to send and receive MQTT messages between your computer and the broker.
Installing Mosquitto On The Raspberry Pi
Before we get started, boot up your Raspberry Pi computer and make sure you are running a recent stable OS release.
Now, open a terminal window on the Raspberry Pi and install Mosquitto.
$ sudo apt install mosquitto mosquitto-clients
The mosquitto package installs and configures a minimal Mosquitto broker instance that starts upon the next OS reboot. Don’t worry about rebooting now though, we will be updating its configuration shortly.
The mosquitto-clients package installs the command line utilities.
Configuring The Broker On The Raspberry Pi
By default, the broker only allows localhost connections. It can be configured, however, to allow both anonymous and authorized remote connections. I chose the latter in order to provide additional security to our connections.
First, we need to create a password file that will contain our username/password pair(s) that will be used to authorize connections with the broker. Run the following command to create the /etc/mosquitto/passwd file with an entry for the first authorized user, replacing <username> with the username you want to use with your broker. The command will ask for a password for that user.
$ sudo mosquitto_passwd -c /etc/mosquitto/passwd <username>
If you want to add a second user, run the same command again, but this time, drop the -c (create) option and specify a different username.
Write down the username/password pair(s) that you just created as we will need to use them later in our client programs.
Next, edit the /etc/mosquitto/mosquitto.conf configuration file to add the following line at the top of the file, after the comments,
per_listener_settings true
and the following lines to the bottom of the file.
allow_anonymous false listener 1883 password_file /etc/mosquitto/passwd
These settings will establish remote individual authorized access on port 1883 using the username/password pairs located in the specified password file.
Save the file once you have completed making the above edits. The contents of the /etc/mosquitto/mosquitto.conf configuration file should now look like the following.
# Place your local configuration in /etc/mosquitto/conf.d/ # # A full description of the configuration file is at # /usr/share/doc/mosquitto/examples/mosquito.conf.example per_listener_settings true pid_file /run/mosquitto/mosquitto.pid persistence true persistence_location /var/lib/mosquitto/ log_dest file /var/log/mosquitto/mosquitto.log include_dir /etc/mosquitto/conf.d allow_anonymous false listener 1883 password_file /etc/mosquitto/passwd
To enable the updated settings, either run the following command to restart the Mosquitto MQTT broker instance
$ sudo systemctl restart mosquitto
or reboot the Raspberry Pi.
$ sudo reboot
Installing Mosquitto On Your Computer
Although not required, it would also be beneficial to install Mosquitto on your primary computer to facilitate testing and sending commands to our Arduino and Raspberry clients that will be implemented later.
To install Mosquitto on your computer, follow the instructions on the Download page of the Mosquitto website for your particular operating system. At least for the Mac, the installation did not create an instance that automatically starts on the next OS boot as it did on the Raspberry Pi.
Testing The Connection
Now let’s test the broker connection. The following commands can be run on either your main computer or the Raspberry Pi, but I suggest running them on your computer in order to properly test that the Raspberry Pi can indeed accept remote connections.
In one window, subscribe to the test topic with the following command, replacing the <username> and <password> placeholders with your actual MQTT broker credentials we created earlier.
$ mosquitto_sub --verbose --host raspberrypi.local --port 1883 --username <username> --pw <password> --qos 1 --topic test
The ‐‐verbose (-v) option prints the associated message (data) along with the topic when a new message arrives. The ‐‐host (-h) and ‐‐port (-p) options specify the broker’s hostname and port. You can probably use the given raspberrypi.local URL, but if you have problems connecting to the broker, you may want to verify the URL of your Raspberry Pi. The ‐‐username (-u) and ‐‐pw (-P) options specify the authorized user’s username and password. The ‐‐qos (-q) option specifies the Quality of Service level with 1 meaning to acknowledge that the message was received. The ‐‐topic (-t) option specifies the MQTT topic to listen to.
When the command is run, it will connect to the MQTT broker and continuously listen for messages that are published to the specified topic.
In a separate window, publish a message to the test topic with the following command, again replacing the placeholders.
$ mosquitto_pub --host raspberrypi.local --port 1883 --username <username> --pw <password> --qos 1 --topic test --message hello
The additional ‐‐message (-m) option specifies the message (data) contents to publish with the topic. Here, we are just saying “hello”.
When the command is run, it will connect to the MQTT broker, publish the message to the specified topic, and then exit. The following message should appear in the subscription window where the mosquitto_sub command is running.
test hello
Publish a few other messages to the test topic and watch them appear in the subscription window. If you want to send a message that contains spaces, enclose it in quotes, i.e. “Hello, MQTT!”.
We can also use topic hierarchy and have the broker retain the last message. The following commands illustrate the types of messages that we will be using later in the tutorial to communicate with the Arduino.
$ mosquitto_pub --host raspberrypi.local --port 1883 --username <username> --pw <password> --qos 1 --topic Arduino/command/LED --message get $ mosquitto_pub --host raspberrypi.local --port 1883 --username <username> --pw <password> --retain --topic Arduino/status/LED --message on
If you want to listen to those topics, you will need to change your subscription topic to “Arduino/#” which uses the # wildcard to listen to all levels below Arduino.
$ mosquitto_sub --verbose --host raspberrypi.local --port 1883 --username <username> --pw <password> --qos 1 --topic Arduino/#
Press CTRL-C in the subscription window to exit the mosquitto_sub command when you are done testing.
For further information on how to configure the MQTT broker server and use the command line utilities, see the Mosquitto documentation.
The Arduino MQTT Client
Now that we have the MQTT broker up and running, let’s turn our attention to building an MQTT client for our Arduino board.
To keep things simple for demonstration purposes, we will only be getting or setting the GPIO pin values for a couple of pins: A0 (analog input) and LED_BUILTIN (built-in LED). You could of course extend this approach to other pins or even connected sensors or modules.
The Arduino client will connect to the MQTT broker and listen for (subscribe to) the following commands (MQTT topic and message pairs):
- “Arduino/command/A0” / “get” — Gets the current value of the A0 analog input pin.
- “Arduino/command/LED” / “get” | “on” | “off” — Gets the value of or turns on/off the built-in LED.
and report (publish to) the following status updates (MQTT topic and message pairs):
- “Arduino/status/A0” / analog value — Reports the current A0 analog input pin value.
- “Arduino/status/LED” / “on” | “off” — Reports the current LED_BUILTIN pin state.
In addition, we will use an MQTT Quality of Service (QoS) level of 1 for commands and set the MQTT retain flag for status updates. A QoS level of 1 helps to ensure reliable message (command) delivery. Setting the retain flag means that the broker will retain (save) the last value published and send that value immediately when a client first subscribes to the “Arduino/status/A0” or “Arduino/status/LED” topics. This ensures that a new subscriber receives the last known good values.
Installing The Libraries
Connect your Arduino board to your computer and open the Arduino IDE.
Before installing any libraries, make sure you are using the latest stable release of the Arduino IDE and the core board library suitable for your particular Arduino board. The core library can be found and installed from within the Boards Manager of the IDE.
I researched various MQTT libraries available and settled on the PubSubClient library by Nick O’Leary. It is an extremely popular and easy to use library, but it does have a few limitations. It does not support the latest MQTT 5 standard and is not able to publish with Quality of Service (QoS) levels greater than 0. Install the PubSubClient library from within the Library Manager of the IDE.
If not already installed, you will also need to install the appropriate WiFi library for your particular WiFi enabled board. For the Arduino UNO WiFi Rev2 board I am using, the required library is the WiFiNINA library by Arduino. Again, these can be found in the Library Manager. The WiFiS3 library used by the Arduino UNO R4 WiFi board is already incorporated into the core platform itself and does not need a separate installation.
Writing The Sketch
Create and save a new sketch named MQTT_Client within the Arduino IDE.
Before we update the new sketch, create a new file (tab) named secrets.h by clicking the more (…) button on the right-hand side of the tab bar and selecting New Tab from the drop-down menu. This file will hold the secrets (usernames, passwords, and other parameters) used for accessing the local WiFi network and the MQTT broker server. Add the following contents to the secrets.h tab and update the placeholders with the particular secrets for your local WiFi network and the Mosquitto broker instance we created earlier.
Commented secrets.h header file is available on GitHub.
#define WIFI_SSID "your_ssid" #define WIFI_PASSWORD "your_password" #define MQTT_BROKER_IP "000.000.000.000" #define MQTT_BROKER_PORT 1883 #define MQTT_BROKER_USERNAME "your_username" #define MQTT_BROKER_PASSWORD "your_password" #define MQTT_CLIENT_ID "Arduino"
Unfortunately, we are not able to use a local network host name, e.g. raspberrypi.local, and must instead use the local IP address for the MQTT broker on the Raspberry Pi. You can determine the IP address by running the following command within a terminal window on the Raspberry Pi
$ hostname -I
or running the following command from within a terminal window on your computer.
$ ping -c 1 raspberrypi.local
Keep in mind that your local WiFi router may not assign the same IP address to your Raspberry Pi when it reconnects in the future, so a different IP address may be needed in that case.
The MQTT_BROKER_IP
value of 1883
should be the same as the port value specified within the listener 1883
line of the Mosquitto configuration file on the Raspberry Pi we updated earlier.
The MQTT_CLIENT_ID
value of Arduino
is used to uniquely identify the client with the MQTT broker. I am also using this value as the top level prefix of the MQTT command and status topics. For instance, if you have two Arduino boards that you are messaging with, you can use something like Arduino01 as the client ID for the first board and Arduino02 for the second. This will enable you to send and receive topics like “Arduino01/command/LED” or “Arduino01/status/LED” with the first Arduino without affecting the other board. You may choose a different name for this identifier if you prefer, but keep in mind that I will generally be using the Arduino identifier within the tutorial verbiage.
Save the secrets.h file when you are done adding your secrets.
Now let’s move on to the main sketch. Replace the contents of the MQTT_Client.ino tab with the following.
Commented MQTT_Client.ino sketch is available on GitHub.
#include <WiFiNINA.h> #include <PubSubClient.h> #include "secrets.h" #define DEBUG 1 const char WiFiSSID[] = WIFI_SSID; const char WiFiPassword[] = WIFI_PASSWORD; const char MQTTBrokerIP[] = MQTT_BROKER_IP; const uint16_t MQTTBrokerPort = MQTT_BROKER_PORT; const char MQTTBrokerUsername[] = MQTT_BROKER_USERNAME; const char MQTTBrokerPassword[] = MQTT_BROKER_PASSWORD; const char MQTTClientID[] = MQTT_CLIENT_ID; uint8_t previousLEDValue = LOW; int previousA0Value = 0; WiFiClient wifiClient; PubSubClient mqttClient(wifiClient); void configureMQTTClient() { mqttClient.setServer(MQTTBrokerIP, MQTTBrokerPort); mqttClient.setCallback(mqttCommandReceived); } void configureSerialMonitor() { Serial.begin(9600); while (!Serial); } void connectMQTTBroker() { if (DEBUG) { Serial.print("Connecting to MQTT broker ("); Serial.print(MQTTBrokerIP); Serial.print(")..."); } while (!mqttClient.connected()) { if (mqttClient.connect(MQTTClientID, MQTTBrokerUsername, MQTTBrokerPassword)) { if (DEBUG) Serial.println("connected."); } else { delay(1000); if (DEBUG) { Serial.print("."); } } } } void connectWiFiNetwork() { if (WiFi.status() == WL_NO_MODULE) { if (DEBUG) Serial.println("ERROR: Communication with WiFi module failed."); while (true); } else { if (DEBUG) Serial.println("WiFi module found."); } if (DEBUG) { String version = WiFi.firmwareVersion(); if (version < WIFI_FIRMWARE_LATEST_VERSION) { Serial.println("Please upgrade the WiFi module firmware."); } } if (DEBUG) { Serial.print("Connecting to WiFi network ("); Serial.print(WiFiSSID); Serial.print(")..."); } WiFi.begin(WiFiSSID, WiFiPassword); while (WiFi.status() != WL_CONNECTED) { delay(1000); if (DEBUG) Serial.print("."); } if (DEBUG) Serial.println("connected."); } void mqttCheckAndReportStatus() { int currentA0Value = analogRead(A0); if (abs(currentA0Value - previousA0Value) > 100) { mqttPublishA0Status(); } uint8_t currentLEDValue = digitalRead(LED_BUILTIN); if (currentLEDValue != previousLEDValue) { mqttPublishLEDStatus(); } } void mqttCommandReceived(char* topic, byte* payload, unsigned int length) { char message[length + 1]; for (unsigned int i = 0; i < length; i++) { message[i] = (char)payload[i]; } message[length] = '\0'; if (DEBUG) { Serial.print("Command received: "); Serial.print(topic); Serial.print(" "); Serial.println(message); } char commandA0[sizeof(MQTTClientID) + sizeof("/command/A0")] = ""; strcat(commandA0, MQTTClientID); strcat(commandA0, "/command/A0"); char commandLED[sizeof(MQTTClientID) + sizeof("/command/LED")] = ""; strcat(commandLED, MQTTClientID); strcat(commandLED, "/command/LED"); if (!strcmp(topic, commandA0) && !strcmp(message, "get")) { mqttPublishA0Status(); } else if (!strcmp(topic, commandLED) && !strcmp(message, "get")) { mqttPublishLEDStatus(); } else if (!strcmp(topic, commandLED) && !strcmp(message, "on")) { digitalWrite(LED_BUILTIN, HIGH); if (DEBUG) Serial.println("Turned on LED."); mqttPublishLEDStatus(); } else if (!strcmp(topic, commandLED) && !strcmp(message, "off")) { digitalWrite(LED_BUILTIN, LOW); if (DEBUG) Serial.println("Turned off LED."); mqttPublishLEDStatus(); } else { if (DEBUG) Serial.println("ERROR: Unknown command."); } } void mqttPublishA0Status() { char topic[sizeof(MQTTClientID) + sizeof("/status/A0")] = ""; strcat(topic, MQTTClientID); strcat(topic, "/status/A0"); int currentA0Value = analogRead(A0); char status[5] = ""; itoa(currentA0Value, status, 10); if (mqttClient.publish(topic, status, true)) { if (DEBUG) { Serial.print("Status published: "); Serial.print(topic); Serial.print(" "); Serial.println(status); } previousA0Value = currentA0Value; } else { if (DEBUG) Serial.println("ERROR: Unable to publish A0 status."); } } void mqttPublishLEDStatus() { char topic[sizeof(MQTTClientID) + sizeof("/status/LED")] = ""; strcat(topic, MQTTClientID); strcat(topic, "/status/LED"); uint8_t currentLEDValue = digitalRead(LED_BUILTIN); char status[4] = ""; strcpy(status, currentLEDValue == HIGH ? "on" : "off"); if (mqttClient.publish(topic, status, true)) { if (DEBUG) { Serial.print("Status published: "); Serial.print(topic); Serial.print(" "); Serial.println(status); } previousLEDValue = currentLEDValue; } else { if (DEBUG) Serial.println("ERROR: Unable to publish LED status."); } } void mqttSubscribeToCommands() { char topic[sizeof(MQTTClientID) + sizeof("/command/#")] = ""; strcat(topic, MQTTClientID); strcat(topic, "/command/#"); if (mqttClient.subscribe(topic, 1)) { if (DEBUG) { Serial.print("Subscribed to topic: "); Serial.println(topic); } } else { if (DEBUG) { Serial.print("ERROR: Unable to subscribe to topic: "); Serial.println(topic); } } } void setup() { pinMode(LED_BUILTIN, OUTPUT); if (DEBUG) configureSerialMonitor(); configureMQTTClient(); connectWiFiNetwork(); connectMQTTBroker(); mqttPublishA0Status(); mqttPublishLEDStatus(); mqttSubscribeToCommands(); } void loop() { if (!mqttClient.connected()) { connectMQTTBroker(); mqttSubscribeToCommands(); } mqttClient.loop(); mqttCheckAndReportStatus(); }
Let’s take a look at some of the more interesting parts of the code, or at least hit the highlights and those needing a bit of explanation.
We begin by including the required WiFiNINA
and PubSubClient
libraries along with the secrets
header file that we just created. If you are using a different board, you may need to change the WiFi library specified.
Line 5 defines the debugging mode of the sketch. If enabled (DEBUG
is set to 1
), then a serial connection is established and debugging messages are displayed in the Serial Monitor. Set DEBUG
to 0
for normal operation.
The next section, lines 7-13, obtains our secrets from the secrets.h
header file and assigns them to constants for use within the sketch.
Lines 15-16 hold our previous A0
and LED_BUILTIN
pin values so that we may compare them against current values.
The mqttClient
global instance, defined on line 19, uses the wifiClient
instance, defined on the previous line, for connections to the MQTT broker over the local WiFi network.
The configureMQTTClient()
function, beginning on line 21, configures the mqttClient
instance. It sets the server’s IP address and port for the MQTT broker and specifies the callback function that will get called when an MQTT message is received.
configureSerialMonitor()
configures the serial port for use with the Serial Monitor and is only called when debugging is enabled.
The connectMQTTBroker()
function connects to the MQTT broker with the appropriate credentials.
Likewise, the connectWiFiNetwork()
function connects to the local WiFi network.
mqttCheckAndReportStatus()
checks the status of the current A0
and LED_BUILTIN
values and reports (publishes) any significant changes to the MQTT broker.
The mqttCommandReceived()
function is the callback function specified within the configureMQTTClient()
function and gets called when an MQTT message on a subscribed topic is received from the broker. The function first converts the message’s payload in bytes to a C-string and then compares and processes that string message according to the particular MQTT message received. For instance, if the “on” message is received on the “Arduino/command/LED” topic, line 108, the built-in LED is turned on and its new status is reported (published) to the broker. Since the topic names are not known until compile time, due to the value of MQTTClientID
possibly changing, the topic names need to be built (lines 98-103) before they can be compared and processed (lines 104-118). By the way, for those of you not familiar with the strcmp()
function, it returns a value of 0 if the strings match, that is why I’m using the “!
” not operator to check for string equality.
mqttPublishA0Status()
performs the operation of publishing the current value of the A0
analog pin to the MQTT broker under the “Arduino/status/A0” topic. The itoa()
function, on line 127, converts the integer based A0
value to a C-string for publishing. The last argument of the MQTT client’s publish()
method, on line 128, sets the MQTT retain flag to true
so that the broker will retain the message.
Similarly, mqttPublishLEDStatus()
publishes the status of the built-in LED.
The mqttSubscribeToCommands()
function subscribes to the MQTT “Arduino/command/#” topic hierarchy (any topic beginning with “Arduino/command/” in order to listen for all commands destined for the Arduino based MQTT client. The last argument of the client’s subscribe()
method, on line 165, sets the MQTT QoS level to 1
for reliable message deliveries.
The standard setup()
function begins by configuring the hardware and then connecting to the MQTT broker through a WiFi connection. It then publishes the initial status of the A0
and LED_BUILTIN
pins and then subscribes to Arduino targeted commands.
The standard loop()
function keeps the MQTT broker connected, listens for incoming commands, and reports any status changes. Specifically, the MQTT client’s loop()
method, on line 194, listens for messages on subscribed topics so that the mqttCommandReceived()
callback function can get called when a new message arrives.
If there is something that needs further explanation, please let me know in the comment section and I will try to answer your question.
Verify and save your sketch when you are done editing.
Running And Testing The Client
Now that our sketch is written, it is time to run it and test that it actually works.
Let’s begin by going back to our subscription and publishing terminal windows that we used to test the Mosquitto broker connections earlier. This time we will use these windows to run the Mosquitto commands to communicate with the Arduino. These can also be run on the Raspberry Pi if you prefer. Run the following command in the subscription window to subscribe to the topics that the computer and your Arduino will use to communicate.
$ mosquitto_sub -v -h raspberrypi.local -p 1883 -u <username> -P <password> -q 1 -t Arduino/#
Replace the <username> and <password> placeholders with your actual MQTT broker credentials and update the Arduino topic prefix if you changed the client ID. Although running the mosquitto_sub command is not strictly necessary here, it does enable us to listen in on the communications between the devices.
Next, open the Serial Monitor in the Arduino IDE, so that we may see the program’s debugging messages, and upload the sketch to the Arduino board.
With the sketch now running, run the following commands in the publishing terminal window to send (publish) our MQTT commands to the Arduino.
$ mosquitto_pub -h raspberrypi.local -p 1883 -u <username> -P <password> -q 1 -t Arduino/command/A0 -m get $ mosquitto_pub -h raspberrypi.local -p 1883 -u <username> -P <password> -q 1 -t Arduino/command/LED -m get $ mosquitto_pub -h raspberrypi.local -p 1883 -u <username> -P <password> -q 1 -t Arduino/command/LED -m on $ mosquitto_pub -h raspberrypi.local -p 1883 -u <username> -P <password> -q 1 -t Arduino/command/LED -m off
You should see the Arduino’s built-in LED turn on and off and output similar to the following printed to the Serial Monitor
WiFi module found. Connecting to WiFi network (your_network_name)...connected. Connecting to MQTT broker (your_broker_ip_address)...connected. Status published: Arduino/status/A0 524 Status published: Arduino/status/LED off Subscribed to topic: Arduino/command/# Command received: Arduino/command/A0 get Status published: Arduino/status/A0 863 Command received: Arduino/command/LED get Status published: Arduino/status/LED off Command received: Arduino/command/LED on Turned on LED. Status published: Arduino/status/LED on Command received: Arduino/command/LED off Turned off LED. Status published: Arduino/status/LED off
and to the subscription terminal window after all the commands have been run.
Arduino/status/A0 524 Arduino/status/LED off Arduino/command/A0 get Arduino/status/A0 863 Arduino/command/LED get Arduino/status/LED off Arduino/command/LED on Arduino/status/LED on Arduino/command/LED off Arduino/status/LED off
These outputs show the Arduino being sent commands and then responding with status updates for the pins involved. Try sending a command with an unknown pin name, e.g. “D3”, or an unknown message, e.g. “junk”, to see how the Arduino responds.
As I stated earlier, we are only using the A0 and LED_BUILTIN pins in this demonstration. Try extending the sketch to add new command/response functionality for the D5 GPIO pin. I suggest using “high” and “low” for the GPIO pin topic messages (payloads) instead of “on” and “off” as we did for the built-in LED. For even more advanced functionality, try connecting an I2C based sensor, e.g. a temperature sensor, to the Arduino and add functionality to access and communicate its value.
Press CTRL-C in the subscription window to exit the mosquitto_sub command when you are done testing. Don’t worry about exiting the IDE or disconnecting your board yet as we will use it again later.
The Raspberry Pi MQTT Client
Now let’s move on to building an MQTT client for the Raspberry Pi. While the CircuitPython program we are creating is targeted for a Raspberry Pi single board computer (SBC), like the Raspberry Pi 4 Model B, it will be written to also run on a Raspberry Pi Pico W microcontroller board.
We used the A0 analog input pin and the built-in LED for our Arduino based client, but since Raspberry Pi computers don’t have either of these, we will instead use the CPU’s temperature and the D5 digital output pin for the Raspberry Pi based client. Again, this approach can be extended to include other pins or even connected sensors or modules.
The Raspberry Pi client will connect to the MQTT broker and listen for (subscribe to) the following commands (MQTT topic and message pairs):
- “RaspberryPi/command/cpu_temperature” / “get” — Gets the current CPU temperature.
- “RaspberryPi/command/D5” / “get” | “high” | “low” — Gets or sets the state of the D5 digital output pin.
and report (publish to) the following status updates (MQTT topic and message pairs):
- “RaspberryPi/status/cpu_temperature” / temperature value — Reports the current CPU temperature.
- “RaspberryPi/status/D5” / “high” | “low” — Reports the current D5 digital output pin state.
We will continue to use QoS levels of 1 for commands and set the retain flag for status updates as we did for the Arduino.
Installing The Libraries
If you are using a Raspberry Pi Pico W, connect it to your computer with the USB cable.
Before installing any new libraries, make sure you are running the latest stable release of CircuitPython on your Raspberry Pi Pico W microcontroller board or the Blinka CircuitPython compatibility library on the Raspberry Pi SBC.
$ pip3 install --upgrade Adafruit-Blinka
Our MQTT client program will use the Adafruit_CircuitPython_MiniMQTT library to communicate with the MQTT broker and the Adafruit_CircuitPython_Logging library to provide enhanced debug logging capabilities.
For the Raspberry Pi SBC, install the required libraries with the following commands.
$ pip3 install adafruit-circuitpython-minimqtt $ pip3 install adafruit-circuitpython-logging
For the Raspberry Pi Pico W board, download the latest stable CircuitPython libraries bundle and copy the adafruit_minimqtt directory and the adafruit_logging.mpy file to the lib directory of your board’s CIRCUITPY drive.
Writing The CircuitPython Program
Open your favorite code editor on the Raspberry Pi SBC or the Mu editor on your computer if you are using the Raspberry Pi Pico W.
Before we create the new CircuitPython program for our Raspberry Pi based client, we first need to create a secrets file similar to the one we created previously for the Arduino that will hold our WiFi and MQTT broker related credentials and parameters. Create a new module file named secrets.py with the code shown below. For the Pico W, this file needs to be located at the top level of the CIRCUITPY drive.
Commented secrets.py program is available on GitHub.
wifi: dict[str, str] = { "ssid": "your_ssid", "password": "your_password" } mqtt: dict[str, str] = { "broker_url": "raspberrypi.local", "broker_username": "your_username", "broker_password": "your_password", "client_id": "RaspberryPi" }
The wifi
dictionary is only required for the Raspberry Pi Pico W and can be ignored if you are using a Raspberry Pi SBC.
Like the MQTT_CLIENT_ID
value we used for the Arduino client, the client_id
in the mqtt
dictionary is the name used to uniquely identify your board with the MQTT broker and will also serve as the top level prefix for the MQTT command and status topics. You can use a different name if you prefer, but I will generally be using the RaspberryPi identifier within the tutorial verbiage.
Unlike the Arduino, this time we are able to simply use the MQTT broker’s URL, raspberrypi.local, instead of having to use an IP address. You probably don’t have to change this, but if you have problems connecting to your broker, you may need to verify the URL.
Update the placeholders with the secrets for your local WiFi network and the Mosquitto instance as appropriate.
Save the secrets.py file when you are done adding your secrets.
Now let’s move on to the main program. Create a new program file named mqtt_client.py with the code shown below.
Commented mqtt_client.py program is available on GitHub.
from time import sleep import board from digitalio import DigitalInOut, Direction import microcontroller try: import gpiozero import RPi.GPIO as GPIO IS_RASPBERRY_PI_SBC = True except: IS_RASPBERRY_PI_SBC = False if IS_RASPBERRY_PI_SBC: import socket elif board.board_id == "raspberry_pi_pico_w": import socketpool import wifi import adafruit_logging as logger import adafruit_minimqtt.adafruit_minimqtt as MQTT try: import secrets except ImportError: print("WiFi and MQTT secrets are stored in secrets.py.") raise if IS_RASPBERRY_PI_SBC: gpio_d5: microcontroller.Pin = DigitalInOut(board.D5) elif board.board_id == "raspberry_pi_pico_w": gpio_d5: microcontroller.Pin = DigitalInOut(board.GP5) DEBUG: int = 1 previous_gpio_d5_value: bool = False previous_cpu_temperature_value: float = 0.0 high_cpu_temperature_alert: bool = False cpu_temperature_threshold_high: float = 58 cpu_temperature_threshold_low: float = 56 mqtt_client: MQTT = None def mqtt_command_cpu_temperature_received(client: MQTT, topic: str, message: str) -> None: if DEBUG: print(f"Command received: {topic} {message}") if message == "get": mqtt_publish_cpu_temperature_status() else: if DEBUG: print("ERROR: Unknown command.") def mqtt_command_d5_received(client: MQTT, topic: str, message: str) -> None: if DEBUG: print(f"Command received: {topic} {message}") if message == "get": mqtt_publish_gpio_d5_status() elif message in ("high", "low"): gpio_d5.value = True if message == "high" else False if DEBUG: print(f"D5 GPIO pin set {message}.") mqtt_publish_gpio_d5_status() else: if DEBUG: print("ERROR: Unknown command.") def mqtt_connected(client: MQTT, userdata: any, flags: int, rc: int) -> None: if DEBUG: print(f"\nBroker connected: {client.broker}") def mqtt_disconnected(client: MQTT, userdata: any, rc: int) -> None: if DEBUG: print(f"Broker disconnected: {client.broker}") def mqtt_message_received(client: MQTT, topic: str, message: str) -> None: if DEBUG: print(f"Command received: {topic} {message}") print("ERROR: Unknown command.") def mqtt_published(client: MQTT, userdata: any, topic: str, pid: int) -> None: if DEBUG: print(f"Status published: {topic}") def mqtt_subscribed(client: MQTT, userdata: any, topic: str, granted_qos: int) -> None: if DEBUG: print(f"Subscribed to topic: {topic}") def mqtt_unsubscribed(client: MQTT, userdata: any, topic: str, pid: int) -> None: if DEBUG: print(f"Unsubscribed from topic: {topic}") def configure_mqtt_client() -> None: global mqtt_client if IS_RASPBERRY_PI_SBC: socket_pool = socket if board.board_id == "raspberry_pi_pico_w": wifi.radio.connect(secrets.wifi["ssid"], secrets.wifi["password"]) socket_pool = socketpool.SocketPool(wifi.radio) mqtt_client = MQTT.MQTT( broker=secrets.mqtt["broker_url"], username=secrets.mqtt["broker_username"], password=secrets.mqtt["broker_password"], client_id=secrets.mqtt["client_id"], socket_pool=socket_pool ) if DEBUG == 2: mqtt_client.enable_logger(logger, log_level=logger.DEBUG) mqtt_client.on_connect = mqtt_connected mqtt_client.on_disconnect = mqtt_disconnected mqtt_client.on_message = mqtt_message_received # mqtt_client.on_publish = mqtt_published mqtt_client.on_subscribe = mqtt_subscribed mqtt_client.on_unsubscribe = mqtt_unsubscribed mqtt_client.add_topic_callback( f"{secrets.mqtt['client_id']}/command/cpu_temperature", mqtt_command_cpu_temperature_received ) mqtt_client.add_topic_callback( f"{secrets.mqtt['client_id']}/command/D5", mqtt_command_d5_received ) def connect_mqtt_broker() -> None: if DEBUG: print("Connecting to MQTT broker...", end="") while mqtt_client.connect(): if DEBUG: print(".", end="") sleep(1) def mqtt_check_and_report_status() -> None: current_cpu_temperature_value: float = 0 if IS_RASPBERRY_PI_SBC: current_cpu_temperature_value = gpiozero.CPUTemperature().temperature elif board.board_id == "raspberry_pi_pico_w": current_cpu_temperature_value = microcontroller.cpu.temperature if abs(current_cpu_temperature_value - previous_cpu_temperature_value) > 2.0: mqtt_publish_cpu_temperature_status() current_gpio_d5_value: bool = gpio_d5.value if current_gpio_d5_value != previous_gpio_d5_value: mqtt_publish_gpio_d5_status() global high_cpu_temperature_alert if current_cpu_temperature_value > cpu_temperature_threshold_high and not high_cpu_temperature_alert: high_cpu_temperature_alert = True if DEBUG: print("High temperature alert enabled.") mqtt_client.publish("Arduino/command/LED", "on", qos=1) if DEBUG: print("Command published: Arduino/command/LED on") elif current_cpu_temperature_value < cpu_temperature_threshold_low and high_cpu_temperature_alert: high_cpu_temperature_alert = False if DEBUG: print("High temperature alert disabled.") mqtt_client.publish("Arduino/command/LED", "off", qos=1) if DEBUG: print("Command published: Arduino/command/LED off") def mqtt_publish_cpu_temperature_status() -> None: global previous_cpu_temperature_value current_cpu_temperature_value: float = 0 if IS_RASPBERRY_PI_SBC: current_cpu_temperature_value = gpiozero.CPUTemperature().temperature elif board.board_id == "raspberry_pi_pico_w": current_cpu_temperature_value = microcontroller.cpu.temperature mqtt_client.publish( f"{secrets.mqtt['client_id']}/status/cpu_temperature", current_cpu_temperature_value, retain=True ) if DEBUG: print(f"Status published: {secrets.mqtt['client_id']}/status/cpu_temperature {current_cpu_temperature_value}") previous_cpu_temperature_value = current_cpu_temperature_value def mqtt_publish_gpio_d5_status() -> None: global previous_gpio_d5_value current_gpio_d5_value: bool = gpio_d5.value current_gpio_d5_status: str = "high" if current_gpio_d5_value == True else "low" mqtt_client.publish( f"{secrets.mqtt['client_id']}/status/D5", current_gpio_d5_status, retain=True ) if DEBUG: print(f"Status published: {secrets.mqtt['client_id']}/status/D5 {current_gpio_d5_status}") previous_gpio_d5_value = current_gpio_d5_value def main() -> None: def loop() -> None: if not mqtt_client.is_connected(): mqtt_client.reconnect() mqtt_client.loop(1) mqtt_check_and_report_status() if DEBUG: print("Running in DEBUG mode. Turn off for normal operation.") if IS_RASPBERRY_PI_SBC: print("Press CTRL-C to exit.") gpio_d5.direction = Direction.OUTPUT gpio_d5.value = False configure_mqtt_client() connect_mqtt_broker() mqtt_publish_cpu_temperature_status() mqtt_publish_gpio_d5_status() mqtt_client.subscribe(f"{secrets.mqtt['client_id']}/command/#", qos=1) if IS_RASPBERRY_PI_SBC: try: while True: loop() except KeyboardInterrupt: print() finally: mqtt_client.unsubscribe(f"{secrets.mqtt['client_id']}/command/#") gpio_d5.value = False mqtt_publish_cpu_temperature_status() mqtt_publish_gpio_d5_status() mqtt_client.disconnect() GPIO.cleanup() elif board.board_id == "raspberry_pi_pico_w": while True: loop() else: print(f"ERROR: The {board.board_id} board is not supported.") if __name__ == "__main__": main()
The first section, lines 1-22, imports the various libraries we will be using in the client program. Along with the standard Python and CircuitPython based libraries, we are also importing some Raspberry Pi SBC specific libraries as well. The IS_RASPBERRY_PI_SBC
variable is defined and used to let us know if we are running on a Raspberry Pi computer. The CircuitPython’s board.board_id
property is used to determine if we are running on a Pico W board. The gpiozero
and RPi.GPIO
libraries are imported to provide CPU temperature and GPIO cleanup features respectively for the Raspberry Pi SBC. CircuitPython’s microcontroller
module will provide the temperature for the Pico W. We are also including our MQTT and logging libraries we installed previously. Lastly, we import the secrets
module that contains our secrets.
The next section, beginning on line 24, defines the D5
GPIO pin for use. Unfortunately, the two boards use different naming conventions, D5
versus GP5
, for their pin names so we have to define the pin according to what board we are running on. By the way, you could change the board.GP5
reference for the Raspberry Pi Pico W to be board.LED
instead if you want to utilize the Pico’s onboard LED for visual confirmation of pin settings. But keep in mind if you do this, that while all of the rest of the code will still function properly, it will still refer to the pin as D5 in our variable and function names along with the associated MQTT topics unless you make the appropriate changes.
Line 29 defines the debugging mode of the program. There are three options you can select here. A value of 0
disables the printing of all debugging messages, a value of 1
enables the printing of program messages, and a value of 2
adds MQTT logging messages provided by the CircuitPython logging library. For the SBC, messages will be printed to the screen. For the Pico W, messages will be printed to the serial console within the Mu editor.
The global mqtt_client
instance is defined on line 37, but it is not actually configured until later in the configure_mqtt_client()
function. That function, starting on line 87, begins by connecting to the WiFi network, for the Pico W, and then configures the MQTT broker using the secrets stored in the secrets.py module. Lines 103-116 assign the callback functions that will get called when specific events occur. For instance, the mqtt_client.add_topic_callback()
method, on line 109, sets the user defined mqtt_command_cpu_temperature_received()
function to get called whenever an MQTT command (message) arrives with the “RaspberryPi/command/cpu_temperature” topic. The commented out mqtt_client.on_publish
property and its associated callback function, on line 106, is not utilized in this program, but I left it here for the sake of completeness. More on this later.
The connect_mqtt_broker()
function that follows, you guessed it, connects to the MQTT broker.
The user defined callback functions are defined beginning on line 39. The first one, mqtt_command_cpu_temperature_received()
, gets called when a command arrives with the “RaspberryPi/command/cpu_temperature” topic. It processes the incoming message by comparing the message payload (message
) and requests that the temperature be published if the message
is “get”.
Similarly, the next function, mqtt_command_d5_received()
, will either get or set the value of the D5 GPIO pin and publish the appropriate status.
The rest of the callback functions are fairly self explanatory, but I did want to mention a couple of things. The mqtt_message_received()
callback function only gets called when a command arrives with a topic other than “RaspberryPi/command/cpu_temperature” or “RaspberryPi/command/D5”. Therefore, it just reports an unknown command. I could have used this function to process all of the possible commands (messages) within a single function, but I thought it would be more beneficial to take advantage of the library’s add_topic_callback()
method to separate the processing of the individual command topics.
The mqtt_published()
callback function, not currently being utilized, is again left here for completeness. The reason that it is not being utilized is that when I print that a status update (message) was published, I want to print not only the message’s topic but its payload as well. Something like print(f"Status published: {topic} {message}")
would have been preferred. This callback function does not have access to the actual payloads of the published messages. For that reason, I am reporting the messages (topic and payload) to the user directly when and where the messages are actually published.
mqtt_check_and_report_status()
, beginning on line 126, gets called repeatedly from the main loop of the program. It obtains the current CPU temperature and D5 pin state, compares them with their previous values, and then reports (publishes) any significant changes to the MQTT broker. I could have left it there, but I decided to also demonstrate how we can send a command (MQTT message) to the Arduino as well. To accomplish this, I established a high temperature alert system that will turn on the Arduino’s built-in LED if the Raspberry Pi’s CPU temperature goes above a certain threshold and turn off the LED below a different threshold. I am also specifying QoS levels of 1 since these are command based messages for the Arduino. Since I am using the hardcoded “Arduino/command/LED” topic in the code, you will need to change the topic prefix with the appropriate Arduino client ID if you changed it. You may also need to adjust the temperature thresholds, defined on lines 34 and 35, depending on your board and environment.
The two functions that follow, mqtt_publish_cpu_temperature_status()
and mqtt_publish_gpio_d5_status()
, have the tasks of performing the actual MQTT publish operations for their respective status. Here, we can also see that the MQTT retain flag is being set for those status updates as well.
The main portion of the program begins on line 182. It consists of common code for both architectures and board specific code meant to only run on either the Raspberry Pi SBC or the Raspberry Pi Pico W. The primary difference is that since the SBC is a full fledged computer that can run many programs (often simultaneously), we need to be good citizens and clean up after ourselves before we fully exit the program. To this end, if we are running on the SBC, we unsubscribe the MQTT client from the broker, set the D5 pin low, publish the last known good values, disconnect from the MQTT broker, and finally, reset the SBC’s GPIO pins back to their default states.
The code in common configures the hardware and MQTT client, connects to the MQTT broker, publishes initial values, subscribes to command topics, and then loops to constantly check and report status while staying connected to the MQTT broker while inside the nested loop()
function. Here we can also see the QoS level is set to 1
when subscribing on line 199. Although you shouldn’t typically have to specify a timeout argument in the mqtt_client.loop()
method, line 186, I get the “Resource temporarily unavailable” error if I don’t provide a value while running on the SBC.
Again, if there is something that needs further explanation, please let me know in the comment section and I will try to answer your question.
Save your program when you are done editing.
Running And Testing The Client
Now that our Raspberry Pi based client program is written, let’s test that it is functioning properly.
Go back to your subscription and publishing terminal windows where we ran the Mosquitto commands to communicate with the Arduino. Run the following command in the subscription window to subscribe to the topics that all of our devices will use to communicate. Again, replace the <username> and <password> placeholders with your broker credentials and update the top level topic names if you changed either of the client IDs.
$ mosquitto_sub -v -h raspberrypi.local -p 1883 -u <username> -P <password> -q 1 -t Arduino/# -t RaspberryPi/#
This command is almost identical to the one we ran previously for the Arduino; we only added the top level RaspberryPi topic. We can now see all MQTT communication to and from both the Arduino and the Raspberry Pi.
Next, make sure your Arduino client is still running. If not, reconnect the board to your computer and run the MQTT_Client sketch again since the Raspberry Pi client may send it commands.
Now run the mqtt_client.py program on your Raspberry Pi. For the Pico W, open the serial console (to display debugging messages) in the Mu editor and then copy the program code to the code.py file at the top level of the CIRCUITPY drive and it will begin running automatically. For the SBC, run the following command in a third window.
$ python3 mqtt_client.py
With both programs (MQTT_Client and mqtt_client.py) now running, run the following commands in the publishing window to send (publish) our MQTT commands to the Raspberry Pi.
$ mosquitto_pub -h raspberrypi.local -p 1883 -u <username> -P <password> -q 1 -t RaspberryPi/command/cpu_temperature -m get $ mosquitto_pub -h raspberrypi.local -p 1883 -u <username> -P <password> -q 1 -t RaspberryPi/command/D5 -m get $ mosquitto_pub -h raspberrypi.local -p 1883 -u <username> -P <password> -q 1 -t RaspberryPi/command/D5 -m high $ mosquitto_pub -h raspberrypi.local -p 1883 -u <username> -P <password> -q 1 -t RaspberryPi/command/D5 -m low
Once all the commands have been run, you should see output similar to the following printed to the program’s terminal window for the SBC or the serial console for the Pico W.
Running in DEBUG mode. Turn off for normal operation. Press CTRL-C to exit. Connecting to MQTT broker... Broker connected: raspberrypi.local Status published: RaspberryPi/status/cpu_temperature 57.996 Status published: RaspberryPi/status/D5 low Subscribed to topic: RaspberryPi/command/# Command received: RaspberryPi/command/cpu_temperature get Status published: RaspberryPi/status/cpu_temperature 57.996 Command received: RaspberryPi/command/D5 get Status published: RaspberryPi/status/D5 low Command received: RaspberryPi/command/D5 high D5 GPIO pin set high. Status published: RaspberryPi/status/D5 high Command received: RaspberryPi/command/D5 low D5 GPIO pin set low. Status published: RaspberryPi/status/D5 low
The subscription window should have output similar to the following.
Arduino/status/LED off Arduino/status/A0 863 RaspberryPi/status/cpu_temperature 56.996 RaspberryPi/status/D5 low RaspberryPi/command/cpu_temperature get RaspberryPi/status/cpu_temperature 56.996 RaspberryPi/command/D5 get RaspberryPi/status/D5 low RaspberryPi/command/D5 high RaspberryPi/status/D5 high RaspberryPi/command/D5 low RaspberryPi/status/D5 low
The first two Arduino prefixed lines reflect the last known values retained by the broker while doing our Arduino sketch testing and sent to us this time because we initiated a new subscription to the Arduino topic (along with the RaspberryPi topic of course).
You may even see
High temperature alert enabled. Command published: Arduino/command/LED on High temperature alert disabled. Command published: Arduino/command/LED off
in the program’s window and
Arduino/command/LED on Arduino/status/LED on Arduino/command/LED off Arduino/status/LED off
in the subscription window. These show that the Raspberry Pi enabled or disabled the high temperature alert and sent commands to the Arduino to turn on or off the built-in LED and the Arduino responded with the updated status. Along with these, you should also see the Arduino’s built-in LED turning on or off accordingly. If you are not seeing these alerts, adjust the temperature thresholds in your CircuitPython program, lines 34 and 35, until you do.
If the program is not acting as you expect or you are just curious, try changing the DEBUG
level to 2
and rerun the program to see detailed MQTT communication messages coming from the client logger.
Now that our testing is complete, try extending the program to include another GPIO pin. For those of you using a Raspberry Pi Pico W, that board does contain a built-in LED and an analog input pin like the Arduino board we used previously. Try extending your program to include these features.
Press CTRL-C in the subscription window to exit the mosquitto_sub command when you are done testing.
If you are running your program on the Raspberry Pi SBC, press CTRL-C to exit the mqtt_client.py program and you should see the following printed to the screen upon exit.
Unsubscribed from topic: RaspberryPi/command/# Status published: RaspberryPi/status/cpu_temperature 57.996 Status published: RaspberryPi/status/D5 low Broker disconnected: raspberrypi.local
Before we end, now would be a good time to reset all of the GPIO pins back to their default states for our microcontroller boards. This ensures that no outputs are being driven that may damage your board or connected electronics when plugging in your board for your next project. This was already incorporated into the code for the Raspberry Pi SBC.
For the Arduino, upload the BareMinimum (Main Menu > File > Examples > 01.Basics > BareMinimum) sketch and then close the IDE and disconnect your board.
For the Raspberry Pi Pico W, replace and save the contents of code.py with the following bare minimum program.
while True: pass
Once the program begins running, you can close the Mu editor, eject the CIRCUITPY drive, and disconnect your board.
Additional Resources
The following is a list of additional resources you may find helpful.
- MQTT on Wikipedia
- MQTT in CircuitPython Adafruit Learning Guide
- Public MQTT Broker by HiveMQ
- Eclipse Paho MQTT Client Library For Python by the Eclipse Foundation
- arduino-mqtt MQTT Client Library For Arduino by Joël Gähwiler
- Adafruit_MQTT_Library MQTT Client Library For Arduino By Adafruit
- ArduinoMqttClient MQTT Client Library For Arduino By Arduino (still in Beta)
Summary
In this tutorial, we learned how to use the MQTT protocol to transfer commands and data to and from your WiFi enabled devices.
Specifically, we learned
- how to configure a Mosquitto MQTT broker on a Raspberry Pi single board computer,
- how to send (publish) and receive (subscribe to) MQTT messages using the Mosquitto command line utilities,
- how to create an Arduino based MQTT client that receives commands and sends status updates, and
- how to create an MQTT client for a Raspberry Pi SBC or Pico W microcontroller board that receives commands and sends status updates.
In general, MQTT is a protocol for publishing and subscribing to topics (with associated data) that can be used to communicate between IoT devices. For example, we executed a command on our computer to publish to the “Arduino/command/LED” topic with a message of “on” to turn on the built-in LED on the Arduino, and then the Arduino responded by turning on the LED and sending back the “Arduino/status/LED” topic with the message of “on” to verify that the LED did indeed turn on.
Hopefully, this tutorial shows that MQTT is a powerful protocol that we can use within our own projects to communicate between devices.
The final MQTT client programs used in this tutorial are available on GitHub. The GitHub versions of the code are fully commented to include additional information, such as the program’s description, library references, function parameters, code clarifications, and other details. The comments are also Doxygen (C++) or Sphinx (Python) compatible in case you want to generate the code documentation yourself.
As a reminder, this tutorial was provided as a free service to our valued readers. Please help us continue this endeavor by considering a GitHub sponsorship or a PayPal donation.
Thank you for joining me on this journey and I hope you enjoyed the experience. Please feel free to share your thoughts or questions in the comments section below.
[…] #ICYDNCI What was the most popular, most clicked link, in last week’s newsletter? Communicating Between Devices With The MQTT Protocol. […]