Introduction
The project we are going to carry out focuses on the creation of a mesh network using ESP32 modules, with the aim of monitoring environmental parameters such as temperature and humidity, sending the collected data to an MQTT broker and viewing it in real time on a Node-RED dashboard . This system uses different communication protocols, such as ESP-NOW for the mesh network and MQTT for data transmission, ensuring a robust and scalable solution for the Internet of Things (IoT).
ESP-NOW is a proprietary Espressif communication protocol that allows direct communication between ESP32 devices without the need for a Wi-Fi network.This protocol is particularly useful for creating mesh networks, where nodes can communicate with each other in an efficient and energy-efficient manner. Using ESP-NOW, data collected by temperature and humidity sensors are transmitted from peripheral nodes to a central node (gateway), which acts as a collection point.
Once the gateway (consisting of an ESP32 module) has collected the data from the mesh network nodes, it transfers them via two-wire serial communication to another ESP32 module which sends them, via WiFi and MQTT protocol, to a MQTT broker installed on a Raspberry. This solution was necessary as both ESP-NOW and MQTT use the WiFi channel and this makes them incompatible. So the “gateway” was split: one module collects the data from the mesh network, transfers it via serial to the other module which transfers it via WiFi using the MQTT protocol to the broker. MQTT (Message Queuing Telemetry Transport) is a lightweight, open messaging protocol designed for resource-constrained environments such as IoT devices. MQTT operates on a publish/subscribe model, where clients publish messages on specific topics and subscribe to receive messages on other topics. This communication method is ideal for scenarios where you need to collect data from numerous devices and distribute it to various recipients efficiently.
Finally, the data sent to the MQTT broker is collected and displayed on a Node-RED dashboard installed on the Raspberry. Node-RED is a flow-based development tool that allows you to connect hardware devices, APIs, and online services in a visual way. Thanks to its intuitive interface, Node-RED allows you to easily create complex data flows, monitor data in real time and visualize it in a clear and structured way.
If you want to delve deeper into the topics of the various communication protocols in a slightly more detailed and theoretical way, I recommend reading the two articles Practical guide to ESP32 communication protocols e Introduction to the MQTT protocol in IoT: the number 1 platform for interaction between devices.
Also in this project we will use the excellent PlatformIO IDE for writing the software.
What are the objectives of the project
The main objective of the project is to create a complete IoT solution that allows you to monitor and visualize environmental data from a distributed sensor network. By implementing this project, the user will obtain a reliable and scalable mesh network, capable of collecting data from various points in an area and centralizing it for efficient monitoring. This system can be used in various applications, such as environmental monitoring, home automation, intelligent agricultural management, and much more.
Once completed, the user will have in hand a working mesh network, an MQTT-based data collection system, and a Node-RED dashboard for real-time visualization. The system is highly extensible: it is possible to add new nodes to the mesh network, integrate additional types of sensors or actuators, and customize the Node-RED dashboard to meet specific needs.
What will be achieved by carrying out the project
By creating this project, the user will be able to:
- Monitor temperature and humidity in real time: thanks to distributed sensors and the mesh network, it will be possible to collect data from different points in an area.
- View data on an interactive dashboard: using Node-RED, the data collected will be displayed in a clear and intuitive way, allowing continuous and detailed monitoring.
- Scale the system: the mesh network based on ESP-NOW and data transmission via MQTT allow you to easily add new nodes and sensors, expanding the coverage and functionality of the system.
- Customize the system: Node-RED offers infinite customization possibilities, allowing you to adapt the system to different needs and application scenarios.
This project represents a great introduction to the world of IoT, combining different technologies and protocols to create a complex but manageable system. Furthermore, it offers a solid foundation for future developments and integrations, allowing the potential of the Internet of Things to be further explored.
What is a mesh network?
A mesh network is a distributed communication network where nodes (devices) connect to each other in a dynamic and non-hierarchical way, forming an ad hoc network. Each node acts as both a transmission point and a repeater, allowing data to travel across the network using the most efficient route. This type of network is characterized by the ability to self-heal and automatically redistribute network traffic, ensuring a stable connection even if one or more nodes disconnect or fail.
How does a mesh network work?
In a mesh network, each node is connected to one or more other nodes, creating a network that can expand and adapt easily. Data sent from one node can pass through several other nodes to reach its final destination, reducing dependence on a single central access point. This allows for greater resilience and reliability, since the network can continue to function even in the presence of failures or disconnection of some nodes.
Mesh networks use specific protocols for connection management and data routing. For example, protocols such as ESP-NOW, used in the painlessMesh library for ESP32, facilitate peer-to-peer communication between devices, enabling fast, low-power transmission.
Advantages of a mesh network
- Scalability: mesh networks can easily expand by adding new nodes without the need for complex restructuring.
- Reliability: the ability to self-heal and redistribute network traffic ensures stable connections even in the event of failures or disconnection.
- Flexibility: mesh networks do not require a predefined topology, allowing dynamic configuration of nodes.
- Extended coverage: nodes can extend network coverage beyond the limits of a single point-to-point connection.
Uses of mesh networks
Mesh networks are widely used in several applications, such as:
- Internet of Things (IoT): to connect sensors and devices in domestic, industrial or agricultural environments.
- Emergency communications: in natural disaster scenarios, where traditional communications infrastructures may be compromised.
- Home automation: to integrate smart home devices such as lights, thermostats and security systems.
- Urban wireless networks: to provide Internet access in large urban areas through a network of distributed nodes.
A mesh network offers a powerful and flexible solution for creating resilient and scalable communications networks. In this project, we will take advantage of the ESP32’s ability to form a mesh network, collecting data from temperature and humidity sensors, and using MQTT and Node-RED for data visualization. This approach not only improves the robustness of the system, but also offers a wide range of possibilities for future extensions and customizations.
The MQTT broker used in the project: Mosquitto
Mosquitto is an open-source implementation of the MQTT (Message Queuing Telemetry Transport) protocol, designed to facilitate communication between IoT (Internet of Things) devices. MQTT is a lightweight messaging protocol, ideal for resource-constrained applications such as sensors, actuators and mobile devices. Due to its efficiency and scalability, Mosquitto is widely used in numerous IoT projects for data collection, processing and distribution.
Main features of Mosquitto
Lightness and efficiency: one of the main advantages of Mosquitto is its lightness. The MQTT protocol was designed to minimize the use of bandwidth and system resources, making it ideal for devices with limited computational and memory capacity. This is especially useful in IoT environments, where numerous devices need to communicate with each other efficiently.
Pub/Sub Model: Mosquitto uses a publish/subscribe (pub/sub) model for transmitting messages. In this model, clients can publish messages on specific topics (topics) and subscribe to receive messages on other topics. This approach decouples data producers (publishers) from consumers (subscribers), improving the scalability and flexibility of the system.
Quality of service: Mosquitto supports three levels of quality of service (QoS) for message delivery:
- QoS 0: at most once – the message is delivered a maximum of once without guarantee of confirmation.
- QoS 1: at least once – the message is delivered at least once with confirmation of receipt.
- QoS 2: exactly once – the message is delivered exactly once, ensuring maximum reliability.
Security support: Mosquitto includes support for security via SSL/TLS, allowing you to encrypt communications between clients and the broker. Additionally, it supports username and password authentication, ensuring that only authorized clients can connect and publish/subscribe messages.
Applications of Mosquitto in IoT
Mosquitto finds application in a wide range of IoT scenarios. It is used to collect data from distributed sensors, monitor environments, automate domestic and industrial processes, and much more. For example, in a smart home, Mosquitto can manage communication between thermostats, lights, motion sensors and security systems, enabling centralized and automated control of the home environment.
In industrial settings, Mosquitto facilitates predictive maintenance management by collecting real-time data from machinery and equipment to identify imminent failures and optimize maintenance processes. Furthermore, it can be integrated with data analytics platforms to provide detailed insights into operations and improve operational efficiency.
Ultimately, Mosquitto represents a fundamental component for the development of efficient and scalable IoT solutions. Thanks to its light weight, flexibility and security support, Mosquitto allows you to create robust communication systems between heterogeneous devices. Its open-source implementation and active community of developers guarantee continuous improvement and adaptation to the new needs of the Internet of Things.
What is Node-RED?
Node-RED is a flow-based development platform that lets you connect hardware devices, APIs, and online services in exciting new ways. Originally developed by IBM for its Internet of Things (IoT) program, Node-RED is now an open-source project widely used around the world to create IoT and automation applications.
What does Node-RED do?
Node-RED provides a graphical user interface that allows users to create workflows by composing nodes that represent data sources, processing processes, and data destinations. These nodes can be configured and connected to each other to create complex data flows with almost no need to write code manually. Each node represents a specific function, such as reading a sensor, sending data to a database, or activating a device.
How do you use Node-RED?
Node-RED is designed to be easy to use and accessible even to those without extensive programming experience. The graphical user interface allows you to create workflows by simply dragging and connecting nodes on the workspace. Users can configure each node through a series of options and parameters, which are set via a dialog box accessible by clicking on the node itself.
To get started using Node-RED:
- Installation: Node-RED can be installed on a wide range of platforms, including Raspberry Pi, cloud servers, and local PCs. Installation can be done via Node.js and npm (Node Package Manager).
- Start: after installation, Node-RED is launched via a simple command in the terminal. This command starts a local web server accessible through a web browser.
- Creating flows: using the graphical interface, users can create and edit workflows, connecting nodes representing different input, processing and output operations.
- Deploy: once created, flows can be deployed (deployed) and Node-RED will start performing the configured operations.
What is Node-RED for?
Node-RED is used in a wide range of applications, including but not limited to:
- Internet of Things (IoT): to connect and coordinate sensors, actuators and other IoT devices.
- Home automation: to create home automation systems that control lights, heating, security and other devices.
- API integration: to connect and coordinate different APIs and web services, facilitating integration between different systems.
- Data analysis: to collect, process and display data from different sources in real time.
Why use Node-RED?
Node-RED is preferred over other platforms for several reasons:
- Ease of use: the graphical interface allows even non-programmers to create complex workflows.
- Flexibility: thanks to the large library of nodes available, Node-RED can be used for a wide range of applications.
- Active community: being an open-source project, Node-RED benefits from an active community that develops new nodes and provides support.
- Integration: Node-RED is excellent for integrating different devices and services, making it easy to create IoT and automation solutions.
- Extensibility: users can create their own custom nodes using JavaScript, making the platform highly extensible.
Ultimately, Node-RED is a powerful development platform that allows you to create complex workflows easily and intuitively. It is widely used in IoT applications, home automation, API integration and data analytics. Due to its ease of use, flexibility, and integration capabilities, Node-RED has become a favorite tool for developers and hobbyists around the world.
What is DHT22
The DHT22 is a digital temperature and humidity sensor designed to precisely measure these environmental parameters. It uses a capacitive humidity sensor and a thermistor to measure the humidity and temperature of the surrounding environment respectively. When powered, the sensor captures data and converts it into digital signals that can be read by a microcontroller or other electronic device.
The DHT22 is characterized by good precision and stability in temperature and humidity measurements. It has a wide operating temperature range and claimed accuracy in measurements. The range of temperatures measured goes from -40°C to +80°C (with a resolution of 0.1°C) while that of relative humidity goes from 0% to 100% (with a resolution of 0.1%). Furthermore, the DHT22 is capable of carrying out a complete measurement in approximately 2 seconds.
The sensor usually has three pins for interfacing: one for power, one for ground and one for digital data transmission. It uses a serial communications protocol to transmit readings to the controller. The data is directly digital so there is no need to acquire it with an analog-to-digital converter.
The DHT22 is widely used in environmental monitoring projects, climate control systems, DIY weather stations and other devices that require precise measurement of temperature and humidity.
Advantages: among the advantages of the DHT22 are its compact size, ease of use, low power requirement and relative cost-effectiveness compared to other similar sensors.
Limitations: while accurate, the DHT22 may be subject to slight measurement fluctuations in environments with high temperature or humidity variations. Furthermore, its digital interface may require some attention in programming and interpreting the data read.
What components do we need?
The list of components is not particularly long:
- two breadboards to connect the ESP32 NodeMCUs to the other components
- some DuPont wires (male – male, male – female, female – female)
- two DHT22 sensors
- two 4.7kΩ resistors
- a 64GB SD card
- a possible USB WiFi dongle for the Raspberry
- a Raspberry PI 3
- and, of course, fourESP32 NodeMCUs !
The SD card I used for the experiments is 64GB. The Raspberry I used is model 3. From tests done on model 1 I found that it is too slow to run Node-RED.
The ESP32 modules used in this project are from the company AZ-Delivery.
Project implementation
The electrical diagram
Before creating the actual circuit let’s take a look at the pinout of the board:
The DHT22 sensor pinout:
We will use the DHT22 in each of the two generic mesh nodes. As already mentioned, the mesh network is made up of 4 ESP32s. Two of them will each be connected to a DHT22 and will constitute the two generic nodes of the mesh network (called mn1 and mn2) which will send the values ​​periodically detected by the sensor to the network. The other two, independent of the previous ones, will constitute the gateway node (i.e. the node that is part of the mesh network that collects the information sent by the sensors) and the MQTT node which will take the data from the gateway node via serial connection and send it via WiFi (and via MQTT protocol) to Raspberry.
In the following photo you can see the two generic nodes each connected to a DHT22:
While in the following photo you can see the mesh network gateway node on the right and the MQTT node on the left. The two nodes are connected via serial port via the two brown and white wires while the red and black wires are used to power the left ESP32 (which obtains the VCC and GND from the right ESP32, the only one to be powered via USB cable ). This way you avoid having two USB cables for power:
We can also see the schematics created with Fritzing of the two systems:
The following diagram shows the generic mesh network node with its DHT22:
The following diagram shows the mesh network gateway node on the left and the MQTT node on the right connected together with the serial and power supplies:
To connect the ESP32 gateway to the ESP32 MQTT via serial, follow this GPIO and power connection table:
Gateway ESP32 | ESP32 MQTT |
---|---|
GPIO 17 (TX) | GPIO 16 (RX) |
GPIO 16 (RX) | GPIO 17 (TX) |
GND | GND |
5V | 5V |
The sketches
Let’s create PlatformIO projects
For this project we will need 3 different sketches: one we will use for the two generic mesh nodes (we will only have to change the node identifier to mn1 or mn2), one we will use for the mesh gateway node and the third for the MQTT node. So we will create 3 different projects with PlatformIO.
We have already seen the procedure for creating a PlatformIO project in the article How to create a project for NodeMCU ESP8266 with PlatformIO.
Although it refers to the ESP8266 board, the procedure is similar.
Simply, when choosing the platform, you will have to choose the AZ-Delivery ESP-32 Dev Kit C V4 for each.
Generic mesh node project
Of the libraries indicated in the cited article, install only the DHT sensor library for ESPx by Bernd Giesecke library as indicated in the tutorial.
Now edit the platformio.ini file to add these two lines:
monitor_speed = 115200
upload_speed = 921600
so that the file looks like this:
[env:az-delivery-devkit-v4]
platform = espressif32
board = az-delivery-devkit-v4
monitor_speed = 115200
upload_speed = 921600
framework = arduino
Also install the painlessMesh library so that in the end the platformio.ini file looks like this:
[env:az-delivery-devkit-v4]
platform = espressif32
board = az-delivery-devkit-v4
framework = arduino
monitor_speed = 115200
upload_speed = 921600
lib_deps =
beegee-tokyo/DHT sensor library for ESPx@^1.19
painlessmesh/painlessMesh @ ^1.5.0
You can download the project from the following link:
ESP32 generic node mesh network
unzip it, take the main.cpp file and replace it with the one you have in the previously created project.
Now let’s see how the sketch works.
Initially the required libraries are included:
#include <painlessMesh.h>
#include <DHTesp.h>
The GPIO4 for the data connection with the DHT22, the MESH_PREFIX which is a parameter used in the configuration of a mesh network with ESP32 to identify and group nodes that belong to the same mesh network, the MESH_PASSWORD i.e. the password of the nodes of the same network and the MESH_PORT, the port through which the mesh nodes communicate:
#define DHTPIN 4
#define MESH_PREFIX "ESP_MESH"
#define MESH_PASSWORD "MESH_PASSWORD"
#define MESH_PORT 5555
The name of the particular node is then defined:
const char* nodeId = "mn1"; // mesh node number
The two nodes will have loaded the same sketch except that in one the node name will be mn1 and in the other mn2.
Following is the instantiation of the objects dht (which manages the DHT22 sensor), userScheduler which schedules the sending of messages and mesh which manages the mesh network:
DHTesp dht;
Scheduler userScheduler;
painlessMesh mesh;
We then encounter the sendMessage function which, when called by the scheduler, reads the data from the sensor, composes the string with the data and sends it to the mesh network:
void sendMessage() {
Serial.println("Executing sendMessage");
TempAndHumidity data = dht.getTempAndHumidity();
if (!isnan(data.humidity) && !isnan(data.temperature)) {
String msg = String(nodeId) + ";T:" + String(data.temperature, 1) + ";H:" + String(data.humidity, 1);
mesh.sendBroadcast(msg);
Serial.println("Message sent: " + msg);
} else {
Serial.println("Failed to read from DHT sensor!");
}
}
Then follows the task manager which decides the frequency with which the messages must be sent:
Task taskSendMessage(TASK_SECOND * 10, TASK_FOREVER, &sendMessage);
The setup function initializes the serial port to display messages on the Serial Monitor, initializes the DHT22 sensor, initializes the mesh network and the scheduler:
void setup() {
Serial.begin(115200);
dht.setup(DHTPIN, DHTesp::DHT22);
Serial.println("Setting up mesh...");
mesh.setDebugMsgTypes(ERROR | DEBUG | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION);
mesh.init(MESH_PREFIX, MESH_PASSWORD, MESH_PORT);
mesh.onReceive([](uint32_t from, String &msg) {
Serial.printf("Received from %u: %s\n", from, msg.c_str());
});
userScheduler.addTask(taskSendMessage);
taskSendMessage.enable();
Serial.println("Mesh setup complete");
}
Finally, the loop function cyclically updates the mesh network and runs the scheduler:
void loop() {
mesh.update();
userScheduler.execute();
}
Gateway mesh node project
Of the libraries indicated in the cited article, do not install any libraries.
Now edit the platformio.ini file to add these two lines:
monitor_speed = 115200
upload_speed = 921600
so that the file looks like this:
[env:az-delivery-devkit-v4]
platform = espressif32
board = az-delivery-devkit-v4
monitor_speed = 115200
upload_speed = 921600
framework = arduino
Also install the painlessMesh library so that in the end the platformio.ini file looks like this:
[env:az-delivery-devkit-v4]
platform = espressif32
board = az-delivery-devkit-v4
framework = arduino
monitor_speed = 115200
upload_speed = 921600
lib_deps =
painlessmesh/painlessMesh @ ^1.5.0
You can download the project from the following link:
ESP32 gateway node mesh network
unzip it, take the main.cpp file and replace it with the one you have in the previously created project.
Now let’s see how the sketch works.
Initially the required libraries are included:
#include <painlessMesh.h>
Then the parameters already seen in the previous sketch are set:
#define MESH_PREFIX "ESP_MESH"
#define MESH_PASSWORD "MESH_PASSWORD"
#define MESH_PORT 5555
and the scheduler and mesh network are instantiated:
Scheduler userScheduler;
painlessMesh mesh
The following receivedCallback function manages the reception of messages from the mesh network and forwards them to the ESP32 MQTT thanks to the serial connection via the Serial1.println(msg) function; on serial port 1;
void receivedCallback(uint32_t from, String &msg) {
Serial.printf("Received from %u msg=%s\n", from, msg.c_str());
// Send data to the second ESP32 via Serial1
Serial1.println(msg);
}
The setup function initializes the standard serial port and serial port 1 indicating the communication pins (GPIO16 and GPIO17) to transfer the data received to the ESP32 MQTT. Furthermore, set up the mesh network by initializing it and setting the receivedCallback function as handler of received messages:
void setup() {
Serial.begin(115200);
Serial1.begin(115200, SERIAL_8N1, 16, 17); // Initialize Serial1 with RX1=GPIO 16 and TX1=GPIO 17 for communication with the second ESP32
Serial.println("Setting up mesh...");
mesh.setDebugMsgTypes(ERROR | DEBUG | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION);
mesh.init(MESH_PREFIX, MESH_PASSWORD, MESH_PORT);
mesh.onReceive(&receivedCallback);
Serial.println("Mesh setup complete");
}
The loop function cyclically updates the mesh network:
void loop() {
mesh.update();
}
ESP32 MQTT node project
Of the libraries indicated in the cited article, do not install any libraries.
Now edit the platformio.ini file to add these two lines:
monitor_speed = 115200
upload_speed = 921600
so that the file looks like this:
[env:az-delivery-devkit-v4]
platform = espressif32
board = az-delivery-devkit-v4
monitor_speed = 115200
upload_speed = 921600
framework = arduino
Also install the PubSubClient library so that in the end the platformio.ini file looks like this:
[env:az-delivery-devkit-v4]
platform = espressif32
board = az-delivery-devkit-v4
framework = arduino
monitor_speed = 115200
upload_speed = 921600
lib_deps =
knolleary/PubSubClient @ ^2.8
You can download the project from the following link:
ESP32 mqtt node mesh network
unzip it, take the main.cpp file and replace it with the one you have in the previously created project.
Now let’s see how the sketch works.
Initially the required libraries are included:
#include <WiFi.h>
#include <PubSubClient.h>
Then follow the prototypes of the functions used later:
// Prototipi delle funzioni
void setup_wifi();
void reconnect();
The parameters of the WiFi network and the IP of the Raspberry that will receive the MQTT messages are set:
#define WIFI_SSID "MY_WIFI_NETWORK_SSID"
#define WIFI_PASSWORD "MY_WIFI_NETWORK_PASSWORD"
#define MQTT_SERVER "192.168.1.190"
You will then need to enter the SSID and password of your WiFi network. The Raspberry’s IP will be 192.168.1.190 if you follow the instructions in the paragraph relating to preparing the Raspberry.
The WiFi client and the MQTT client are then instantiated:
WiFiClient espClient;
PubSubClient client(espClient);
The setup function initializes the default serial port and serial port 1 indicating the communication pins (GPIO16 and GPIO17) to receive data from the ESP32 mesh gateway, initializes the WiFi and the MQTT server on port 1883:
void setup() {
Serial.begin(115200);
Serial1.begin(115200, SERIAL_8N1, 16, 17); // Initialize Serial1 with RX1=GPIO 16 and TX1=GPIO 17 for receiving data from the gateway
setup_wifi();
client.setServer(MQTT_SERVER, 1883);
}
The setup_wifi function called by setup takes care of establishing the WiFi connection:
void setup_wifi() {
delay(10);
Serial.println();
Serial.print("Connecting to ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int attempt = 0;
while (WiFi.status() != WL_CONNECTED && attempt < 20) {
delay(500);
Serial.print(".");
attempt++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("Failed to connect to WiFi");
}
}
The loop function cyclically checks the status of the MQTT connection and if this is interrupted it re-establishes it, receives the data from the ESP32 mesh gateway via serial 1, publishes the collected data towards the MQTT endpoint (i.e. the Raspberry):
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
if (Serial1.available()) {
String data = Serial1.readStringUntil('\n');
Serial.println("Received: " + data);
// Publish the data to the MQTT broker
if (client.publish("sensor/data", data.c_str())) {
Serial.println("Data published successfully");
} else {
Serial.println("Failed to publish data");
}
}
}
The reconnect function takes care of always keeping the MQTT client running:
void reconnect() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
if (client.connect("ESP32Client")) {
Serial.println("connected");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
The libraries used: painlessMesh and PubSubClient
painlessMesh is a library designed to simplify the creation of mesh networks with ESP8266 and ESP32 modules. Using this library, devices can communicate with each other in a decentralized network without the need for a central access point. Its architecture is based on Espressif’s ESP-NOW protocol, which enables reliable, low-power peer-to-peer communication. painlessMesh automatically handles node discovery, network topology formation, and maintaining connections between nodes, allowing developers to focus on the functionality of their applications rather than the complexity of the network. Additionally, the library supports time synchronization between nodes and broadcast message transmission, making it easy to build distributed and resilient IoT applications.
PubSubClient is a client library for MQTT (Message Queuing Telemetry Transport) designed for Arduino and compatible devices such as ESP8266 and ESP32. This library allows devices to connect to an MQTT broker and publish and subscribe to messages on various topics. MQTT is a lightweight messaging protocol ideal for IoT applications, thanks to its efficiency in resource management and its ability to operate on unstable networks. PubSubClient manages connections to the broker, automatic reconnections in case of disconnection and offers a simple interface for publishing and receiving messages. The library supports different qualities of service (QoS) for message delivery and can be configured to operate with or without authentication and encryption, making it flexible and secure for different IoT applications.
Preparing the Raspberry
In order to use the Raspberry it is necessary to take some preliminary steps and install some software.
Let’s start immediately with the installation of the operating system.
The operating system chosen is a distribution made specifically to run on all types of Raspberry, even the older ones. The tests were done on a Raspberry Pi 1 Model B and on a Raspberry PI 3 Model B. I ultimately opted for the Raspberry PI 3 Model B as the Raspberry Pi 1 Model B proved to be too slow in installing and managing Node-RED.
If the Raspberry does not have a native wireless connection you can use a WiFi dongle to insert into one of its USB sockets.
Let’s download and install the operating system on the SD card
Download the latest version of the operating system at https://www.raspberrypi.com/software/operating-systems/
and take you to the Raspberry Pi OS (Legacy) section. You will download a version that has no graphical environment so that it is as lightweight as possible:
The downloaded file will be compressed in xz format. To unzip it on Linux you will first need to install the tool:
sudo dnf install xz su CentOS/RHEL/Fedora Linux.
sudo apt install xz-utils su Ubuntu/Debian
and then give the command line:
xz -d -v filename.xz
where filename.xz is the name of the file you just downloaded containing the operating system.
On Windows it will be sufficient to use one of these tools: 7-Zip, winRAR, WinZip.
The result will be a file with img extension which is the image to flash on the Raspberry SD card.
To flash the image on the SD card you will use the Balena Etcher tool which works on both Linux, Windows and MACOS.
Its use is very simple: simply select the image to flash, the destination SD card and press the Flash button.
This is what its interface looks like:
The image to be flashed is set on the left, the SD card to be flashed in the centre, and the button to start the flashing operation on the right.
At the end of the operation the SD card will contain two partitions: boot and rootfs. In the device manager on Linux a menu like this appears:
Windows will also show a menu like this: from your file explorer, under This computer you will see the 2 partitions.
Now, with a text editor, create a file on your computer that you will call wpa_supplicant.conf and which you will edit like this:
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=«your_ISO-3166-1_two-letter_country_code»
network={
ssid="«your_SSID»"
psk="«your_PSK»"
key_mgmt=WPA-PSK
}
You will need to replace the following items:
- «your_ISO-3166-1_two-letter_country_code» with the identifier of your country (for example for Italy it is IT)
- «your_SSID» with the SSID name of your WiFi network
- «your_PSK» with the WiFi network password
At this point, you will need to create an empty file that you will call ssh (without any extension).
The new distributions do not have the classic pi user with raspberry password so, to be able to enter SSH, we must provide another way.
With a working Raspberry we need to create a file called userconf which will contain the user we want to create with an encrypted version of the password we want to assign to him. The format will therefore be username:password-hash.
Suppose we want to keep the user pi, we need to create the password-hash. Suppose we want to create the hash of the raspberry password, again on the Raspberry where we created the userconf file. We must issue the following command from the shell:
echo "raspberry" | openssl passwd -6 -stdin
This command will return the raspberry password hash. For example it could be a string like this:
$6$ROOQWZkD7gkLyZRg$GsKVikua2e1Eiz3UNlxy1jsUFec4j9wF.CQt12mta/6ODxYJEB6xuAZzVerM3FU2XQ27.1tp9qJsqqXtXalLY.
This is the raspberry password hash that I calculated on my Raspberry.
Our userconf file will then contain the following string:
pi:$6$ROOQWZkD7gkLyZRg$GsKVikua2e1Eiz3UNlxy1jsUFec4j9wF.CQt12mta/6ODxYJEB6xuAZzVerM3FU2XQ27.1tp9qJsqqXtXalLY.
PLEASE NOTE: it is necessary to calculate the hash with a Raspberry because the hash calculated with the computer uses another algorithm which would not allow the Raspberry we are preparing to recognize the password.
Alternatively you can download from the link below the userconf file that I created to have a pi user with a raspberry password.
User configuration for Raspberry
Now open the boot partition on the SD card and copy the three files wpa_supplicant.conf, ssh and userconf into it. Safely remove the SD card from the computer and insert it into the Raspberry.
Turn on the Raspberry, wait a few minutes. To be able to log in to the Raspberry via ssh, you will need to find out what its IP is (the one that the router assigned to it via DHCP).
To do this, simply issue the command from a PC shell:
ping raspberrypi.local
valid on both Linux and Windows (after installing Putty on Windows).
On my PC the Raspberry responds like this:
This makes me understand that the assigned IP is 192.168.43.27.
Alternatively you can use the Angry IP Scanner tool or you can access your router settings to see the devices connected via WiFi and find out what IP the Raspberry has.
To log in to the Raspberry via ssh, issue the shell command (obviously in your case the IP will be different from this):
with raspberry password. On Windows you need Putty.
Once inside the Raspberry, issue the following commands to update the software:
sudo apt update
sudo apt upgrade
The password is, as mentioned, raspberry.
Let’s configure the timezone
To configure the timezone, issue the command:
sudo raspi-config
to the Raspberry shell. Suppose you want to set the time zone of Rome (here I will give the example of the time zone of Rome since I live in Italy, you will have to use the time zone of your country).
A screen like this will appear:
Select the localization option and click OK:
Then select the timezone option and click OK:
Now select the geographical area and click OK:
Finally select the city and click OK:
Done!
Restart the Raspberry by issuing the command:
sudo reboot
and, after a few minutes, log back into ssh as you did before.
Give the command
date
The Raspberry should now show the correct date and time.
Let’s set the static IP
To ensure that the Raspberry always has the same IP address, we need to set it to be static. In my tests I set it to 192.168.1.190. If we didn’t do this, the router would assign it a different IP at each reboot which would force us to change the IP address of the MQTT server in the ESP32 sketches each time.
We will proceed in two steps:
- we will set the fixed IP in the Raspberry
- we will set the router to reserve that address for our Raspberry
For the first point, issue the command:
nano /etc/dhcpcd.conf
to open the dhcpcd.conf file and edit it.
At the end of the file you will need to add a block like this:
interface [INTERFACE]
static_routers=[ROUTER IP]
static domain_name_servers=[DNS IP]
static ip_address=[STATIC IP ADDRESS YOU WANT]/24
where:
- [INTERFACE] is the name of the WiFi interface (in our case it will be wlan0)
- [ROUTER IP] is the address of our router (usually it’s something like 192.168.0.1 or 192.168.1.1). You can find it by entering the administration interface of your modem/router
- [DNS IP] is the address of the DNS server, which generally coincides with the [ROUTER IP] parameter of the modem/router
- [STATIC IP ADDRESS YOU WANT] it is the IP address that we want to assign as a fixed IP to the Raspberry
So, assuming that [ROUTER IP] = [DNS IP] = 192.168.1.1 and that [STATIC IP ADDRESS YOU WANT] = 192.168.1.190, the block will look like this:
interface wlan0
static_routers=192.168.1.1
static domain_name_servers=192.168.1.1
static ip_address=192.168.1.190/24
Restart the Raspberry with the command
sudo reboot
and then log in via ssh again, this time with IP 192.168.1.190.
As a second step we will set the router so that it reserves the address 192.168.1.190 for our Raspberry. Each modem/router is different from the others but they are more or less similar. I’ll show here what mine looks like.
To enter I type the address 192.168.1.1 (because my modem has this IP) on the browser and, after giving the administrator password, I arrive at the main screen. From here I have to look for the access control screen.
There will be a button to add a static IP: add the chosen IP combined with the MAC address of the Raspberry WiFi card. However, I recommend that you consult the instruction manual of your modem/router for this operation.
Now check that the Raspberry connects to the network by issuing the command:
ping www.google.com
If you get the response to the ping the network is connected. If you get a message like “Network is unreachable” issue the command
sudo route add default gw [ROUTER IP]
where [ROUTER IP] is the gateway which in our case is the router IP, i.e. 192.168.1.1
Let’s install the MQTT broker
First we install a very common MQTT broker called Mosquitto:
sudo apt-get update
sudo apt-get install mosquitto mosquitto-clients
and then we enable and start the relevant service:
sudo systemctl enable mosquitto
sudo systemctl start mosquitto
By default, the Mosquitto broker allows anonymous connections. In this project, in order not to complicate things further, we will not use user and password to configure the Mosquitto broker. To set it up this way you need to edit the Mosquitto configuration file.
First of all we change our user from pi to root by giving the command to the shell:
sudo su
and, when asked, we type the password (which is raspberry if it has not been changed).
Give the command:
nano /etc/mosquitto/mosquitto.conf
and add these two lines to the end of the file:
listener 1883
allow_anonymous true
For the changes to take effect, issue the command:
systemctl restart mosquitto
This will restart the Mosquitto service.
Now return to user pi by issuing the command:
exit
To see the MQTT messages transmitted from the ESP32 gateway (to the sensor/data topic) on your Raspberry Pi, you can use the mosquitto_sub MQTT client provided with the Mosquitto broker. Here’s how to do it:
- Open a terminal window on your Raspberry Pi.
- Use the following command to subscribe to the MQTT topic and view incoming messages:
mosquitto_sub -h IP_ADDRESS_RASPBERRY_PI -t sensor/data -v
where
IP_ADDRESS_RASPBERRY_PI
: replace it with the IP address of your Raspberry Pi.sensor/data
: it is the name of the MQTT topic to which your ESP32 is publishing data (the ESP32 that receives data via serial from the ESP32 connected to the mesh network as a gateway).
For example, in my case, I will have:
mosquitto_sub -h 192.168.1.190 -t sensor/data -v
Once you run the command, you should see MQTT messages arriving in the terminal every time the ESP32 publishes data on the specified topic. The messages displayed will include the temperature and humidity sent by the two ESP32s which make up the two generic nodes (sensors) of the mesh network.
The following image shows a shell where the above command has been given:
As you can see, the acronyms mn1 and mn2 identify the two mesh nodes 1 and 2. Each is accompanied by the temperature and humidity values ​​detected by it.
Let’s install Node-RED
Now that we have verified that the MQTT broker installed on the Raspberry works, all that remains is to install and set up Node-RED. Log into ssh on the Raspberry with user pi and raspberry password:
First of all, update the Raspberry like this:
sudo apt-get update
sudo apt-get upgrade
Then issue the command:
bash <(curl -sL https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered)
and launch the application:
node-red-start
Then open a browser and go to the address http://RASPBERRY_IP:1880 (if you set the static IP like I did the URL would be http://192.168.1.190: 1880). If everything goes well, the Node-RED dashboard should appear, as in the figure below:
You will notice that on the left there is a vast assortment of nodes to use to compose the application in a completely visual way.
Now you need to install the dashboard module with the following commands:
cd ~/.node-red
npm install node-red-dashboard
and restart Node-RED:
node-red-stop
node-red-start
Still on the dashboard, from the left side drag the “mqtt in”, “function” and “gauge” nodes as shown in the following figure:
Double click on the mqtt node, a menu will open:
Click on the “+” symbol next to the pencil, a further menu will open:
Under Name put ESP32, under Server put 192.168.1.190. Something like this should appear to you:
Press the Update button. You will return to the previous menu which you will fill like this:
Press the Done button to save your changes.
Now double click on the function 1 node. In the name field write data separator and in the Setup tab, under Outputs, put 2, as in the following image:
Then go to the On Message tab and copy the Javascript script you see below:
var parts = msg.payload.split(";");
var nodeId = parts[0];
var temperature = parseFloat(parts[1].split(":")[1]);
var humidity = parseFloat(parts[2].split(":")[1]);
if (nodeId === "mn1") {
var msg1 = { payload: temperature, topic: "mn1/temperature" };
var msg2 = { payload: humidity, topic: "mn1/humidity" };
return [msg1, msg2];
} else if (nodeId === "mn2") {
var msg3 = { payload: temperature, topic: "mn2/temperature" };
var msg4 = { payload: humidity, topic: "mn2/humidity" };
return [msg3, msg4];
} else {
return null;
}
This script is used to separate the messages coming from the two nodes mn1 and mn2:
Press the Done button to make the changes permanent.
Now work on the function 2 and function 3 nodes. Open the first one and put the string function get temp 1 in the Name field and the script in the On Message tab:
if (msg.topic === "mn1/temperature") {
return msg;
}
Open the second one giving it the name function get temp 2 and the script:
if (msg.topic === "mn2/temperature") {
return msg;
}
Similarly, with the function4 and function 5 blocks you will give the names function get hum 1 and function get hum 2 and the scripts in the On Message tab:
if (msg.topic === "mn1/humidity") {
return msg;
}
for the first one and
if (msg.topic === "mn2/humidity") {
return msg;
}
for the second one. Click the Done button for the changes to take effect.
Now you will deal with gauge nodes. The first two will show the temperature coming from the two sensors and will be connected to the function get temp 1 and function get temp 2 nodes. The other two will show the humidity coming from the two sensors and will be connected to the function get hum 1 and function get hum 2 nodes.
Let’s see the first one. Double-click it and fill the menu like this:
Click the Done button. Open the second gauge for temperature 2 and fill it in the same way but putting Temp 2 in the Label. Click the Done button.
Now open the first humidity gauge and fill its fields like this:
Click the Done button. Open the second gauge for humidity 2 and fill it in the same way but putting Hum 2 in the Label. Click the Done button.
At this point you will connect all the nodes like this:
To make the system work click on the Deploy button. If everything went well, at the URL http://IP_RASPBERRY:1880/ui (so http://192.168.1.190:1880/ui) you will see the gauges showing the temperature and humidity coming from the sensors as in following images:
If something goes wrong or you don’t feel like doing all this work, don’t be discouraged. You can import the entire flowchart in just a few simple steps. In the dashboard go to the top right corner, where there are the three horizontal dashes:
Click it and a menu will open. One of the items is Import. Click it and a window will open:
Copy the following script into that window and click the Import button:
[ { "id": "29c40c6f5b2f6e37", "type": "mqtt in", "z": "006480c71cd03416", "name": "ESP32-MQTT", "topic": "sensor/data", "qos": "2", "datatype": "auto-detect", "broker": "02d77ef8cb4a2e2b", "nl": false, "rap": true, "rh": 0, "inputs": 0, "x": 330, "y": 360, "wires": [ [ "b810943866e53c1d" ] ] }, { "id": "b810943866e53c1d", "type": "function", "z": "006480c71cd03416", "name": "data separator", "func": "var parts = msg.payload.split(\";\");\nvar nodeId = parts[0];\nvar temperature = parseFloat(parts[1].split(\":\")[1]);\nvar humidity = parseFloat(parts[2].split(\":\")[1]);\n\nif (nodeId === \"mn1\") {\n var msg1 = { payload: temperature, topic: \"mn1/temperature\" };\n var msg2 = { payload: humidity, topic: \"mn1/humidity\" };\n return [msg1, msg2];\n} else if (nodeId === \"mn2\") {\n var msg3 = { payload: temperature, topic: \"mn2/temperature\" };\n var msg4 = { payload: humidity, topic: \"mn2/humidity\" };\n return [msg3, msg4];\n} else {\n return null;\n}\n", "outputs": 2, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 620, "y": 380, "wires": [ [ "6bf61ebd5e651515", "ca283e214d38d2c4" ], [ "3a40207b4e0c6d1c", "5ac598492ea1e3ed" ] ] }, { "id": "7360f3682632eab9", "type": "ui_gauge", "z": "006480c71cd03416", "name": "", "group": "e9325c92ddd8054e", "order": 0, "width": 0, "height": 0, "gtype": "gage", "title": "Temp 1", "label": "°C", "format": "{{value}}", "min": "-20", "max": "80", "colors": [ "#00b500", "#e6e600", "#ca3838" ], "seg1": "", "seg2": "", "diff": false, "className": "", "x": 1180, "y": 320, "wires": [] }, { "id": "176905eaeff4b23f", "type": "ui_gauge", "z": "006480c71cd03416", "name": "", "group": "01e25ac3e37c0f87", "order": 0, "width": 0, "height": 0, "gtype": "gage", "title": "Hum 1", "label": "%", "format": "{{value}}", "min": 0, "max": "100", "colors": [ "#00b500", "#e6e600", "#ca3838" ], "seg1": "", "seg2": "", "diff": false, "className": "", "x": 1170, "y": 440, "wires": [] }, { "id": "2cd6151c3abfaa8c", "type": "ui_gauge", "z": "006480c71cd03416", "name": "", "group": "e9325c92ddd8054e", "order": 1, "width": 0, "height": 0, "gtype": "gage", "title": "Temp 2", "label": "°C", "format": "{{value}}", "min": "-20", "max": "80", "colors": [ "#00b500", "#e6e600", "#ca3838" ], "seg1": "", "seg2": "", "diff": false, "className": "", "x": 1180, "y": 360, "wires": [] }, { "id": "78b5a4fc97c30383", "type": "ui_gauge", "z": "006480c71cd03416", "name": "", "group": "01e25ac3e37c0f87", "order": 1, "width": 0, "height": 0, "gtype": "gage", "title": "Hum 2", "label": "%", "format": "{{value}}", "min": 0, "max": "100", "colors": [ "#00b500", "#e6e600", "#ca3838" ], "seg1": "", "seg2": "", "diff": false, "className": "", "x": 1170, "y": 480, "wires": [] }, { "id": "6bf61ebd5e651515", "type": "function", "z": "006480c71cd03416", "name": "function get temp 1", "func": "if (msg.topic === \"mn1/temperature\") {\n return msg;\n}\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 850, "y": 320, "wires": [ [ "7360f3682632eab9" ] ] }, { "id": "ca283e214d38d2c4", "type": "function", "z": "006480c71cd03416", "name": "function get temp 2", "func": "if (msg.topic === \"mn2/temperature\") {\n return msg;\n}\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 850, "y": 360, "wires": [ [ "2cd6151c3abfaa8c" ] ] }, { "id": "3a40207b4e0c6d1c", "type": "function", "z": "006480c71cd03416", "name": "function get hum 1", "func": "if (msg.topic === \"mn1/humidity\") {\n return msg;\n}\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 850, "y": 440, "wires": [ [ "176905eaeff4b23f" ] ] }, { "id": "5ac598492ea1e3ed", "type": "function", "z": "006480c71cd03416", "name": "function get hum 2", "func": "if (msg.topic === \"mn2/humidity\") {\n return msg;\n}\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 850, "y": 480, "wires": [ [ "78b5a4fc97c30383" ] ] }, { "id": "02d77ef8cb4a2e2b", "type": "mqtt-broker", "name": "ESP32", "broker": "192.168.1.190", "port": "1883", "clientid": "", "autoConnect": true, "usetls": false, "protocolVersion": "4", "keepalive": "60", "cleansession": true, "autoUnsubscribe": true, "birthTopic": "", "birthQos": "0", "birthRetain": "false", "birthPayload": "", "birthMsg": {}, "closeTopic": "", "closeQos": "0", "closeRetain": "false", "closePayload": "", "closeMsg": {}, "willTopic": "", "willQos": "0", "willRetain": "false", "willPayload": "", "willMsg": {}, "userProps": "", "sessionExpiry": "" }, { "id": "e9325c92ddd8054e", "type": "ui_group", "name": "Temperature", "tab": "a3448b1260c49b81", "order": 1, "disp": true, "width": "6", "collapse": false, "className": "" }, { "id": "01e25ac3e37c0f87", "type": "ui_group", "name": "Humidity", "tab": "024f6f70ba317fb8", "order": 1, "disp": true, "width": "6", "collapse": false, "className": "" }, { "id": "a3448b1260c49b81", "type": "ui_tab", "name": "Temperature", "icon": "dashboard", "disabled": false, "hidden": false }, { "id": "024f6f70ba317fb8", "type": "ui_tab", "name": "Humidity", "icon": "dashboard", "disabled": false, "hidden": false } ]
Once the operation is finished you will have imported the entire flowchart with all its scripts and you will be able to go to the URL http://IP_RASPBERRY:1880/ui (therefore http://192.168.1.190:1880/ui) to see the gauges which show the temperature and humidity coming from the sensors.
Conclusions
In conclusion, with this project you have created a mesh network using ESP32 modules, capable of monitoring temperature and humidity via DHT22 sensors. The data collected by the nodes is centralized in a gateway node, which transmits it via MQTT to a Node-RED dashboard for real-time visualization. This system allows you to effectively monitor different points in an area, taking advantage of the flexibility and scalability offered by the mesh network.
Along the way, you learned to:
- Set up a mesh network with ESP32: you understood the importance and use of MESH_PREFIX to create an isolated and secure network.
- Use the painlessMesh library: you have exploited the capabilities of automatic management of connections and network topology, simplifying communication between nodes.
- Implement an MQTT messaging system: you have configured and used the PubSubClient library to transmit data from a gateway node to an MQTT broker.
- Visualize data with Node-RED: you have created and configured an interactive dashboard to monitor data in real time, using graphical workflows that simplify the integration of the various system components.
Additionally, you explored the importance of lightweight and efficient communication protocols for IoT applications, gaining practical skills on how to integrate different technologies to create a robust and scalable system. This project not only offers you a practical solution for environmental monitoring, but also serves as a basis for further development and customization. You’ve discovered how the Internet of Things can be applied to a wide range of scenarios to improve data collection and analysis.
Now that you have a solid foundation, there are many avenues you can explore to expand this project:
- Add more sensors:
- Gas sensors: you can integrate sensors to detect gases such as CO2, methane or smoke, to monitor air quality.
- Motion sensors: add PIR sensors to detect motion, useful for security applications.
- Remote control and automation:
- Actuators: integrates actuators such as relays or motors to control lights, locks or irrigation systems, creating a home automation system.
- Voice commands: connect to voice assistant services like Amazon Alexa or Google Assistant to control the system via voice commands.
- Extend the mesh network:
- Additional nodes: add more nodes to the mesh network to cover larger areas, ensuring communication remains stable.
- Multi-hop networks: implements advanced routing capabilities to create multi-hop mesh networks, where data passes through multiple nodes before reaching the gateway.
- Advanced data visualization and analysis:
- Graphs and measurement histories: use Node-RED together with databases like InfluxDB and visualization tools like Grafana to create historical graphs and detailed data analysis.
- Notifications and alerts: configure Node-RED to send notifications via email or SMS in case of anomalies in the data detected by the sensors.
- Integration with other IoT services:
- IoT platforms: connect to IoT platforms like ThingSpeak, Blynk, or AWS IoT to take advantage of additional data processing and analysis capabilities.
- Web and mobile interfaces: develop web interfaces or mobile applications to monitor and control the system from anywhere.
These tips will allow you to expand your system’s capabilities and explore new applications and features. The flexibility of ESP32, together with the power of MQTT and Node-RED, gives you a wide range of possibilities to innovate and create increasingly advanced and customized IoT solutions. Good work!
Newsletter
If you want to be informed about the release of new articles, subscribe to the newsletter. Before subscribing to the newsletter read the page Privacy Policy (UE)
If you want to unsubscribe from the newsletter, click on the link that you will find in the newsletter email.