Learn how to use ESP-MESH networking protocol to build a mesh network with the ESP32 and ESP8266 NodeMCU boards. ESP-MESH allows multiple devices (nodes) to communicate with each other under a single wireless local area network. It is supported on the ESP32 and ESP8266 boards. In this guide, we’ll show you how to get started with ESP-MESH using the Arduino core.

This article covers the following topics:
See more:
Arduino IDE
If you want to program the ESP32 and ESP8266 boards using Arduino IDE, you should have the ESP32 or ESP8266 add-ons installed. Follow the next guides:
If you want to program the ESP32/ESP8266 using VS Code + PlatformIO, follow the next tutorial:
Introducing ESP-MESH
Accordingly to the Espressif documentation:
“ESP-MESH is a networking protocol built atop the Wi-Fi protocol. ESP-MESH allows numerous devices (referred to as nodes) spread over a large physical area (both indoors and outdoors) to be interconnected under a single WLAN (Wireless Local-Area Network).
ESP-MESH is self-organizing and self-healing meaning the network can be built and maintained autonomously.” For more information, visit the ESP-MESH official documentation.
Traditional Wi-Fi Network Architecture
In a traditional Wi-Fi network architecture, a single node (access point – usually the router) is connected to all other nodes (stations). Each node can communicate with each other using the access point. However, this is limited to the access point wi-fi coverage. Every station must be in the range to connect directly to the access point. This doesn’t happen with ESP-MESH.

Traditional Wi-Fi Network ESP32 ESP8266
ESP-MESH Network Architecture
With ESP-MESH, the nodes don’t need to connect to a central node. Nodes are responsible for relaying each others transmissions. This allows multiple devices to spread over a large physical area. The Nodes can self-organize and dynamically talk to each other to ensure that the packet reaches its final node destination. If any node is removed from the network, it is able to self-organize to make sure that the packets reach their destination.

painlessMesh Library
The painlessMesh library allows us to create a mesh network with the ESP8266 or/and ESP32 boards in an easy way.
“painlessMesh is a true ad-hoc network, meaning that no-planning, central controller, or router is required. Any system of 1 or more nodes will self-organize into fully functional mesh. The maximum size of the mesh is limited (we think) by the amount of memory in the heap that can be allocated to the sub-connections buffer and so should be really quite high.” More information about the painlessMesh library.
Installing painlessMesh Library
You can install painlessMesh through the Arduino Library manager. Go to Tools > Manage Libraries. The Library Manager should open.
Search for “painlessmesh” and install the library. We’re using Version 1.4.5

install library painless mesh
This library needs some other library dependencies. A new window should pop up asking you to install any missing dependencies. Select “Install all”.

If this window doesn’t show up, you’ll need to install the following library dependencies:
If you’re using PlatformIO, add the following lines to the platformio.ini file to add the libraries and change the monitor speed.
For the ESP32:
monitor_speed = 115200
lib_deps = painlessmesh/painlessMesh @ ^1.4.5
ArduinoJson
arduinoUnity
TaskScheduler
AsyncTCP
For the ESP8266:
monitor_speed = 115200
lib_deps = painlessmesh/painlessMesh @ ^1.4.5
ArduinoJson
TaskScheduler
ESPAsyncTCP
ESP-MESH with ESP32 Basic Example (Broadcast messages)
To get started with ESP-MESH with ESP32, we’ll first experiment with the library’s basic example. This example creates a mesh network in which all boards broadcast messages to all the other boards.
We’ve experimented this example with four boards (two ESP32 and two ESP8266). You can add or remove boards. The code is compatible with both the ESP32 and ESP8266 boards.

Code – painlessMesh Library Basic Example
Copy the following code to your Arduino IDE (code from the library examples). The code is compatible with both the ESP32 and ESP8266 boards.
/*
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp-mesh-esp32-esp8266-painlessmesh/
This is a simple example that uses the painlessMesh library: https://github.com/gmag11/painlessMesh/blob/master/examples/basic/basic.ino
*/
#include "painlessMesh.h"
#define MESH_PREFIX "whateverYouLike"
#define MESH_PASSWORD "somethingSneaky"
#define MESH_PORT 5555
Scheduler userScheduler; // to control your personal task
painlessMesh mesh;
// User stub
void sendMessage() ; // Prototype so PlatformIO doesn't complain
Task taskSendMessage( TASK_SECOND * 1 , TASK_FOREVER, &sendMessage );
void sendMessage() {
String msg = "Hi from node1";
msg += mesh.getNodeId();
mesh.sendBroadcast( msg );
taskSendMessage.setInterval( random( TASK_SECOND * 1, TASK_SECOND * 5 ));
}
// Needed for painless library
void receivedCallback( uint32_t from, String &msg ) {
Serial.printf("startHere: Received from %u msg=%sn", from, msg.c_str());
}
void newConnectionCallback(uint32_t nodeId) {
Serial.printf("--> startHere: New Connection, nodeId = %un", nodeId);
}
void changedConnectionCallback() {
Serial.printf("Changed connectionsn");
}
void nodeTimeAdjustedCallback(int32_t offset) {
Serial.printf("Adjusted time %u. Offset = %dn", mesh.getNodeTime(),offset);
}
void setup() {
Serial.begin(115200);
//mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
mesh.setDebugMsgTypes( ERROR | STARTUP ); // set before init() so that you can see startup messages
mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT );
mesh.onReceive(&receivedCallback);
mesh.onNewConnection(&newConnectionCallback);
mesh.onChangedConnections(&changedConnectionCallback);
mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
userScheduler.addTask( taskSendMessage );
taskSendMessage.enable();
}
void loop() {
// it will run the user scheduler as well
mesh.update();
}
Before uploading the code, you can set up the MESH_PREFIX (it’s like the name of the MESH network) and the MESH_PASSWORD variables (you can set it to whatever you like).
Then, we recommend that you change the following line for each board to easily identify the node that sent the message. For example, for node 1, change the message as follows:
String msg = "Hi from node 1 ";
How the Code Works
Start by including the painlessMesh library.
#include "painlessMesh.h"
MESH Details
Then, add the mesh details. The MESH_PREFIX refers to the name of the mesh. You can change it to whatever you like.
#define MESH_PREFIX "whateverYouLike"
The MESH_PASSWORD, as the name suggests is the mesh password. You can change it to whatever you like.
#define MESH_PASSWORD "somethingSneaky"
All nodes in the mesh should use the same MESH_PREFIX and MESH_PASSWORD.
The MESH_PORT refers to the the TCP port that you want the mesh server to run on. The default is 5555.
#define MESH_PORT 5555
Scheduler
It is recommended to avoid using delay() in the mesh network code. To maintain the mesh, some tasks need to be performed in the background. Using delay() will stop these tasks from happening and can cause the mesh to lose stability/fall apart.
Instead, it is recommended to use TaskScheduler to run your tasks which is used in painlessMesh itself.
The following line creates a new Scheduler called userScheduler.
Scheduler userScheduler; // to control your personal task
painlessMesh
Create a painlessMesh object called mesh to handle the mesh network.
Create tasks
Create a task called taskSendMessage responsible for calling the sendMessage() function every second as long as the program is running.
Task taskSendMessage(TASK_SECOND * 1 , TASK_FOREVER, &sendMessage);
Send a Message to the Mesh
The sendMessage() function sends a message to all nodes in the message network (broadcast).
void sendMessage() {
String msg = "Hi from node 1";
msg += mesh.getNodeId();
mesh.sendBroadcast( msg );
taskSendMessage.setInterval(random(TASK_SECOND * 1, TASK_SECOND * 5));
}
The message contains the “Hi from node 1” text followed by the board chip ID.
String msg = "Hi from node 1";
msg += mesh.getNodeId();
To broadcast a message, simply use the sendBroadcast() method on the mesh object and pass as argument the message (msg) you want to send.
mesh.sendBroadcast(msg);
Every time a new message is sent, the code changes the interval between messages (one to five seconds).
taskSendMessage.setInterval(random(TASK_SECOND * 1, TASK_SECOND * 5));
Mesh Callback Functions
Next, several callback functions are created that will be called when specific events happen on the mesh.
The receivedCallback() function prints the message sender (from) and the content of the message (msg.c_str()).
void receivedCallback( uint32_t from, String &msg ) {
Serial.printf("startHere: Received from %u msg=%sn", from, msg.c_str());
}
The newConnectionCallback() function runs whenever a new node joins the network. This function simply prints the chip ID of the new node. You can modify the function to do any other task.
void newConnectionCallback(uint32_t nodeId) {
Serial.printf("--> startHere: New Connection, nodeId = %un", nodeId);
}
The changedConnectionCallback() function runs whenever a connection changes on the network (when a node joins or leaves the network).
void changedConnectionCallback() {
Serial.printf("Changed connectionsn");
}
The nodeTimeAdjustedCallback() function runs when the network adjusts the time, so that all nodes are synchronized. It prints the offset.
void nodeTimeAdjustedCallback(int32_t offset) {
Serial.printf("Adjusted time %u. Offset = %dn", mesh.getNodeTime(),offset);
}
setup()
In the setup(), initialize the serial monitor.
void setup() {
Serial.begin(115200);
Choose the desired debug message types:
//mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
mesh.setDebugMsgTypes( ERROR | STARTUP ); // set before init() so that you can see startup messages
Initialize the mesh with the details defined earlier.
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
Assign all the callback functions to their corresponding events.
mesh.onReceive(&receivedCallback);
mesh.onNewConnection(&newConnectionCallback);
mesh.onChangedConnections(&changedConnectionCallback);
mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
Finally, add the taskSendMessage function to the userScheduler. The scheduler is responsible for handling and running the tasks at the right time.
userScheduler.addTask(taskSendMessage);
Finally, enable the taskSendMessage, so that the program starts sending the messages to the mesh.
taskSendMessage.enable();
To keep the mesh running, add mesh.update() to the loop().
void loop() {
// it will run the user scheduler as well
mesh.update();
}
Demonstration
Upload the code provided to all your boards. Don’t forget to modify the message to easily identify the sender node
With the boards connected to your computer, open a serial connection with each board. You can use the Serial Monitor, or you can use a software like PuTTY and open multiple windows for all the boards.
You should see that all boards receive each others messages. For example, these are the messages received by Node 1. It receives the messages from Node 2, 3 and 4.

ESP-MESH with ESP32 Basic Example 4 Boards Arduino Serial Monitor
You should also see other messages when there are changes on the mesh: when a board leaves or joins the network.

ESP MESH Basic Example Serial Monitor Changed Connections
Exchange Sensor Readings using ESP-MESH with ESP32
In this next example, we’ll exchange sensor readings between 4 boards (you can use a different number of boards). Every board receives the other boards’ readings.

ESP-MESH Exchange BME280 Sensor Readings ESP32 ESP8266
As an example, we’ll exchange sensor readings from a BME280 sensor, but you can use any other sensor.
Parts Required
Here’s the parts required for this example:
Arduino_JSON library
In this example, we’ll exchange the sensor readings in JSON format. To make it easier to handle JSON variables, we’ll use the Arduino_JSON library.
You can install this library in the Arduino IDE Library Manager. Just go to Sketch > Include Library > Manage Libraries and search for the library name as follows:

Install Arduino JSON library Arduino IDE
If you’re using VS Code with PlatformIO, include the libraries in the platformio.ini file as follows:
ESP32
monitor_speed = 115200
lib_deps = painlessmesh/painlessMesh @ ^1.4.5
ArduinoJson
arduinoUnity
AsyncTCP
TaskScheduler
adafruit/Adafruit Unified Sensor @ ^1.1.4
adafruit/Adafruit BME280 Library @ ^2.1.2
arduino-libraries/Arduino_JSON @ ^0.1.0
ESP8266
monitor_speed = 115200
lib_deps = painlessmesh/painlessMesh @ ^1.4.5
ArduinoJson
TaskScheduler
ESPAsyncTCP
adafruit/Adafruit Unified Sensor @ ^1.1.4
adafruit/Adafruit BME280 Library @ ^2.1.2
arduino-libraries/Arduino_JSON @ ^0.1.0
Circuit Diagram
Wire the BME280 sensor to the ESP32 or ESP8266 default I2C pins as shown in the following schematic diagrams.
ESP32

ESP32 BME280 Sensor Temperature Humidity Pressure Wiring Diagram Circuit
ESP8266 NodeMCU

ESP8266 NodeMCU BME280 Sensor Temperature Humidity Pressure Wiring Diagram Circuit
Recommended reading: ESP8266 with BME280 using Arduino IDE (Pressure, Temperature, Humidity)
Code – ESP-MESH with ESP32 Broadcast Sensor Readings
Upload the following code to each of your boards. This code reads and broadcasts the current temperature, humidity and pressure readings to all boards on the mesh network. The readings are sent as a JSON string that also contains the node number to identify the sender board.
/*
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp-mesh-esp32-esp8266-painlessmesh/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
#include
#include
#include "painlessMesh.h"
#include
// MESH Details
#define MESH_PREFIX "RNTMESH" //name for your MESH
#define MESH_PASSWORD "MESHpassword" //password for your MESH
#define MESH_PORT 5555 //default port
//BME object on the default I2C pins
Adafruit_BME280 bme;
//Number for this node
int nodeNumber = 2;
//String to send to other nodes with sensor readings
String readings;
Scheduler userScheduler; // to control your personal task
painlessMesh mesh;
// User stub
void sendMessage() ; // Prototype so PlatformIO doesn't complain
String getReadings(); // Prototype for sending sensor readings
//Create tasks: to send messages and get readings;
Task taskSendMessage(TASK_SECOND * 5 , TASK_FOREVER, &sendMessage);
String getReadings () {
JSONVar jsonReadings;
jsonReadings["node"] = nodeNumber;
jsonReadings["temp"] = bme.readTemperature();
jsonReadings["hum"] = bme.readHumidity();
jsonReadings["pres"] = bme.readPressure()/100.0F;
readings = JSON.stringify(jsonReadings);
return readings;
}
void sendMessage () {
String msg = getReadings();
mesh.sendBroadcast(msg);
}
//Init BME280
void initBME(){
if (!bme.begin(0x76)) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
}
// Needed for painless library
void receivedCallback( uint32_t from, String &msg ) {
Serial.printf("Received from %u msg=%sn", from, msg.c_str());
JSONVar myObject = JSON.parse(msg.c_str());
int node = myObject["node"];
double temp = myObject["temp"];
double hum = myObject["hum"];
double pres = myObject["pres"];
Serial.print("Node: ");
Serial.println(node);
Serial.print("Temperature: ");
Serial.print(temp);
Serial.println(" C");
Serial.print("Humidity: ");
Serial.print(hum);
Serial.println(" %");
Serial.print("Pressure: ");
Serial.print(pres);
Serial.println(" hpa");
}
void newConnectionCallback(uint32_t nodeId) {
Serial.printf("New Connection, nodeId = %un", nodeId);
}
void changedConnectionCallback() {
Serial.printf("Changed connectionsn");
}
void nodeTimeAdjustedCallback(int32_t offset) {
Serial.printf("Adjusted time %u. Offset = %dn", mesh.getNodeTime(),offset);
}
void setup() {
Serial.begin(115200);
initBME();
//mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
mesh.setDebugMsgTypes( ERROR | STARTUP ); // set before init() so that you can see startup messages
mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT );
mesh.onReceive(&receivedCallback);
mesh.onNewConnection(&newConnectionCallback);
mesh.onChangedConnections(&changedConnectionCallback);
mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
userScheduler.addTask(taskSendMessage);
taskSendMessage.enable();
}
void loop() {
// it will run the user scheduler as well
mesh.update();
}
The code is compatible with both the ESP32 and ESP8266 boards.
How the Code Works
Continue reading this section to learn how the code works.
Libraries
Start by including the required libraries: the Adafruit_Sensor and Adafruit_BME280 to interface with the BME280 sensor; the painlessMesh library to handle the mesh network and the Arduino_JSON to create and handle JSON strings easily.
#include
#include
#include "painlessMesh.h"
#include
Mesh details
Insert the mesh details in the following lines.
#define MESH_PREFIX "RNTMESH" //name for your MESH
#define MESH_PASSWORD "MESHpassword" //password for your MESH
#define MESH_PORT 5555 //default port
The MESH_PREFIX refers to the name of the mesh. You can change it to whatever you like. The MESH_PASSWORD, as the name suggests is the mesh password. You can change it to whatever you like. All nodes in the mesh should use the same MESH_PREFIX and MESH_PASSWORD.
The MESH_PORT refers to the the TCP port that you want the mesh server to run on. The default is 5555.
BME280
Create an Adafruit_BME280 object called bme on the default ESP32 or ESP8266 pins.
Adafruit_BME280 bme;
In the nodeNumber variable insert the node number for your board. It must be a different number for each board.
int nodeNumber = 2;
The readings variable will be used to save the readings to be sent to the other boards.
String readings;
Scheduler
The following line creates a new Scheduler called userScheduler.
Scheduler userScheduler; // to control your personal task
painlessMesh
Create a painlessMesh object called mesh to handle the mesh network.
Create tasks
Create a task called taskSendMessage responsible for calling the sendMessage() function every five seconds as long as the program is running.
Task taskSendMessage(TASK_SECOND * 5 , TASK_FOREVER, &sendMessage);
getReadings()
The getReadings() function gets temperature, humidity and pressure readings from the BME280 sensor and concatenates all the information, including the node number on a JSON variable called jsonReadings.
JSONVar jsonReadings;
jsonReadings["node"] = nodeNumber;
jsonReadings["temp"] = bme.readTemperature();
jsonReadings["hum"] = bme.readHumidity();
jsonReadings["pres"] = bme.readPressure()/100.0F;
The following line shows the structure of the jsonReadings variable with arbitrary values.
{
"node":2,
"temperature":24.51,
"humidity":52.01,
"pressure":1005.21
}
The jsonReadings variable is then converted into a JSON string using the stringify() method and saved on the readings variable.
readings = JSON.stringify(jsonReadings);
This variable is then returned by the function.
return readings;
Send a Message to the Mesh
The sendMessage() function sends the JSON string with the readings and node number (getReadings()) to all nodes in the network (broadcast).
void sendMessage () {
String msg = getReadings();
mesh.sendBroadcast(msg);
}
Init BME280 sensor
The initBME() function initializes the BME280 sensor.
void initBME(){
if (!bme.begin(0x76)) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
}
Mesh Callback Functions
Next, several callback functions are created that will be called when some event on the mesh happens.
The receivedCallback() function prints the message sender (from) and the content of the message (msg.c_str()).
void receivedCallback( uint32_t from, String &msg ) {
Serial.printf("startHere: Received from %u msg=%sn", from, msg.c_str());
The message comes in JSON format, so, we can access the variables as follows:
JSONVar myObject = JSON.parse(msg.c_str());
int node = myObject["node"];
double temp = myObject["temp"];
double hum = myObject["hum"];
double pres = myObject["pres"];
Finally, print all the information on the Serial Monitor.
Serial.print("Node: ");
Serial.println(node);
Serial.print("Temperature: ");
Serial.print(temp);
Serial.println(" C");
Serial.print("Humidity: ");
Serial.print(hum);
Serial.println(" %");
Serial.print("Pressure: ");
Serial.print(pres);
Serial.println(" hpa");
The newConnectionCallback() function runs whenever a new node joins the network. This function simply prints the chip ID of the new node. You can modify the function to do any other task.
void newConnectionCallback(uint32_t nodeId) {
Serial.printf("--> startHere: New Connection, nodeId = %un", nodeId);
}
The changedConnectionCallback() function runs whenever a connection changes on the network (when a node joins or leaves the network).
void changedConnectionCallback() {
Serial.printf("Changed connectionsn");
}
The nodeTimeAdjustedCallback() function runs when the network adjusts the time, so that all nodes are synchronized. It prints the offset.
void nodeTimeAdjustedCallback(int32_t offset) {
Serial.printf("Adjusted time %u. Offset = %dn", mesh.getNodeTime(),offset);
}
setup()
In the setup(), initialize the serial monitor.
void setup() {
Serial.begin(115200);
Call the initBME() function to initialize the BME280 sensor.
initBME();
Choose the desired debug message types:
//mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
mesh.setDebugMsgTypes( ERROR | STARTUP ); // set before init() so that you can see startup messages
Initialize the mesh with the details defined earlier.
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
Assign all the callback functions to their corresponding events.
mesh.onReceive(&receivedCallback);
mesh.onNewConnection(&newConnectionCallback);
mesh.onChangedConnections(&changedConnectionCallback);
mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
Finally, add the taskSendMessage function to the userScheduler. The scheduler is responsible for handling and running the tasks at the right time.
userScheduler.addTask(taskSendMessage);
Finally, enable the taskSendMessage, so that the program starts sending the messages to the mesh.
taskSendMessage.enable();
To keep the mesh running, add mesh.update() to the loop().
void loop() {
// it will run the user scheduler as well
mesh.update();
}
Demonstration
After uploading the code to all your boards (each board with a different node number), you should see that each board is receiving the other boards’ messages.
The following screenshot shows the messages received by node 1. It receives the sensor readings from node 2, 3 and 4.

Wrapping Up
We hope you liked this quick introduction to the ESP-MESH with ESP32 networking protocol. You can take a look at the painlessMesh library for more examples.
We intend to create more tutorials about this subject on a near future. So, write your suggestions on the comments’ section.
You may also like the following articles:
Learn more about the ESP32 and ESP8266 with our resources:
Thanks for reading.