Chapter 46: ESP32 WiFi Mesh Networking
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand the concept of WiFi Mesh networking and its advantages over traditional WiFi topologies.
- Describe the architecture and key features of Espressif’s ESP-MESH.
- Identify different node types in an ESP-MESH network (root, parent, child, idle) and their roles.
- Explain how ESP-MESH networks self-organize and self-heal.
- Understand data routing mechanisms within the ESP-MESH.
- Configure an ESP32 to act as a root node, connecting the mesh to an external IP network.
- Configure ESP32 devices to act as child nodes within the mesh.
- Implement basic data transmission between nodes in an ESP-MESH network.
- Discuss considerations for ESP-MESH network capacity, security, and power consumption.
- Identify which ESP32 variants support ESP-MESH.
Introduction
Traditional WiFi networks often rely on a star topology, where all devices connect directly to a central Access Point (AP). While simple, this model can suffer from range limitations and single points of failure. If a device is too far from the AP, or if the AP goes down, connectivity is lost. For applications requiring broader coverage, greater resilience, and more flexible network structures – such as smart homes, industrial sensor networks, or large-scale lighting control – a different approach is needed.
WiFi Mesh networking addresses these challenges by allowing devices (nodes) to connect to each other, forming a “mesh” where data can hop through multiple nodes to reach its destination. Espressif provides its own implementation of a WiFi mesh network called ESP-MESH, built upon the standard WiFi protocol. ESP-MESH enables ESP32 devices to create self-organizing and self-healing networks, extending coverage and improving reliability without complex infrastructure. This chapter will guide you through the fundamentals of ESP-MESH and how to implement it in your ESP32 projects.
Theory
What is a WiFi Mesh Network?
A WiFi Mesh network is a type of local network topology where infrastructure nodes (like specialized mesh routers or, in our case, ESP32 devices) connect directly, dynamically, and non-hierarchically to as many other nodes as possible and cooperate with one another to efficiently route data to/from clients.
Key Characteristics:
- Decentralized Connectivity: Nodes can communicate with each other directly or indirectly through intermediate nodes.
- Multi-Hop Routing: Data packets can “hop” from one node to another to reach their destination, extending the network’s range beyond the reach of a single AP.
- Self-Organizing: New nodes can automatically join the mesh and establish connections. The network can adapt its topology as nodes join or leave.
- Self-Healing: If a node fails or a connection path is disrupted, the mesh can automatically find alternative routes for data, enhancing network resilience.
- Scalability: Mesh networks can often be scaled by simply adding more nodes.
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans', 'primaryColor': '#DBEAFE', 'primaryBorderColor': '#2563EB', 'primaryTextColor': '#1E40AF', /* Light blue for nodes */ 'lineColor': '#A78BFA', /* Light purple for connections */ 'textColor': '#1F2937', 'clusterBkg': '#F3F4F6', /* Light grey for cluster background */ 'clusterStroke': '#D1D5DB' } } }%% graph LR subgraph Star Topology direction TB AP1[Access Point]:::primaryColor D1[Device 1]:::primaryColor D2[Device 2]:::primaryColor D3[Device 3]:::primaryColor D4[Device 4]:::primaryColor AP1 --- D1 AP1 --- D2 AP1 --- D3 AP1 --- D4 style AP1 fill:#EDE9FE,stroke:#5B21B6,color:#5B21B6 end subgraph Mesh Network Topology direction TB RTR[External Router / Internet]:::primaryColor N1["Node 1 (Root)"]:::primaryColor N2[Node 2]:::primaryColor N3[Node 3]:::primaryColor N4[Node 4]:::primaryColor N5[Node 5]:::primaryColor N6[Node 6]:::primaryColor RTR --- N1 N1 --- N2 N1 --- N3 N2 --- N4 N2 --- N3 N3 --- N5 N4 --- N5 N4 --- N6 N5 --- N6 style N1 fill:#EDE9FE,stroke:#5B21B6,color:#5B21B6 style RTR fill:#D1FAE5,stroke:#059669,color:#065F46 end
ESP-MESH Overview
ESP-MESH is Espressif’s networking protocol based on the Wi-Fi protocol. It allows numerous ESP32 (and other compatible Espressif SoCs) devices to be interconnected over a large physical area.
Core Features of ESP-MESH:
- Built on WiFi: Utilizes standard IEEE 802.11 b/g/n WiFi physical layer.
- Self-Organizing and Self-Healing: Nodes automatically establish and maintain the mesh structure. If a node drops out, the network attempts to reconfigure and find new routes.
- Tree Topology with Mesh Routing: While the logical organization of ESP-MESH is a tree structure (to prevent loops and simplify routing), it achieves mesh-like benefits through its ability to re-organize and allow parent-child relationships to change dynamically.
- Root Node: A single root node acts as the bridge between the ESP-MESH network and an external IP network (e.g., a traditional WiFi router connected to the internet).
- Scalability: Supports a large number of nodes (theoretically up to 1000, but practical limits depend on traffic, layers, and resources) and multiple hierarchical layers (up to 6 recommended, practical limits lower).
- Data Transmission: Supports various types of data transmission:
- From any node to the root node.
- From the root node to any specific node or all nodes (broadcast/multicast).
- Between any two arbitrary nodes within the mesh (requires application-level addressing and routing logic built on top of mesh capabilities if not going via root).
- Security: ESP-MESH uses WPA2-PSK security for communication between mesh nodes. All nodes in the mesh must share the same mesh ID (like an SSID) and password.
- Power Saving: ESP-MESH incorporates power-saving features, allowing nodes (especially leaf nodes) to enter modem sleep mode to conserve energy.
ESP-MESH Node Types and Structure
ESP-MESH organizes nodes into a hierarchical tree structure:
- Root Node:
- The topmost node in the mesh tree. There is only one root node in an ESP-MESH network.
- It is the only node that can connect to an external Wi-Fi router (the “upstream” connection).
- Acts as the gateway between the mesh network and external IP networks (e.g., LAN, Internet).
- If the root node fails, the mesh can (with appropriate configuration and potentially some delay) elect a new root node from eligible candidates, demonstrating self-healing.
- Parent Node:
- Any node that has one or more child nodes connected to it.
- The root node is also a parent node if it has children.
- Intermediate nodes in the tree are parent nodes.
- Child Node:
- Any node that is connected to a parent node.
- Child nodes can themselves become parent nodes if other nodes connect to them (forming deeper layers).
- Leaf Node (or End Node):
- A child node that has no children of its own. These are at the “edges” of the mesh tree.
- Leaf nodes can be optimized for power saving.
- Idle Node:
- A node that is not currently part of the mesh (not connected to any parent) but is attempting to join or has been excluded.
Node Type | Role & Key Characteristics | Connection Capabilities |
---|---|---|
Root Node | The single, topmost node (Layer 1). Acts as the gateway between the ESP-MESH network and an external IP network (e.g., router to Internet). Can be auto-elected if the current root fails. | Connects “upstream” to an external Wi-Fi router. Connects “downstream” to child nodes in the mesh. |
Parent Node | Any node that has one or more child nodes connected to it. Forwards traffic between its children and its own parent (or the external network if it’s the root). | Connects “upstream” to its parent node. Connects “downstream” to its child nodes. |
Child Node | Any node connected to a parent node. Relies on its parent for communication with the rest of the mesh and the root. | Connects “upstream” to a single parent node. Can also become a parent if other nodes connect to it. |
Leaf Node (End Node) | A child node that has no children of its own. Located at the edges of the mesh tree. | Connects “upstream” to a single parent node. Does not have any downstream child connections. Often optimized for power saving. |
Idle Node | A node that is powered on and configured for the mesh but is not currently connected to any parent node. It may be scanning to join the mesh or may have been excluded. | Not part of the active mesh. Actively trying to find a parent to connect to. |

Layers: The hierarchy is defined in layers. The root node is at Layer 1. Its direct children are at Layer 2, their children at Layer 3, and so on. ESP-MESH supports multiple layers, but performance (latency, throughput) can degrade with increasing layer depth. Espressif typically recommends a maximum of 6 layers for stable operation, with fewer layers being better for performance.
Network Formation and Organization
- Initialization: All ESP32 devices wishing to join the mesh are configured with the same Mesh ID (MUID) and Mesh Password.
- Root Node Election/Startup:
- Typically, one device is designated or configured to become the root node. It connects to the upstream router.
- If no pre-configured root is available, or if the root fails, ESP-MESH has mechanisms for automatic root node election among eligible nodes.
- Node Scanning and Connection:
- Idle nodes scan for nearby parent nodes (including the root) that are part of the desired mesh (matching MUID) and have available connection slots.
- A child node attempts to connect to the parent with the best signal strength (RSSI) and available capacity.
- Once connected, the child node is assigned a layer number (parent’s layer + 1).
- Self-Healing:
- If a parent node becomes unavailable, its child nodes will automatically scan for and attempt to connect to a new parent node, maintaining connectivity.
- If the root node becomes disconnected from the upstream router but is still powered, it might step down, and other nodes might attempt to become the root.
Data Routing in ESP-MESH
- Upstream (Child to Root): When a child node sends data destined for the external network or another part of the mesh (often routed via root), it sends the packet to its parent. The parent then forwards it up the tree until it reaches the root node. The root node then forwards it to the external network if applicable.
- Downstream (Root to Child): The root node can send data to a specific child node. It uses the MAC address of the target child node. The mesh network routes the packet down the tree through the appropriate parent nodes until it reaches the target child.
- Broadcast/Multicast: The root node can broadcast or multicast data to all nodes or specific groups within the mesh.
- Peer-to-Peer (Internal): While the primary routing is tree-based, ESP-MESH allows sending data directly between any two nodes if their MAC addresses are known. This is considered “mesh-internal” communication. The
esp_mesh_send()
API allows specifying a destination MAC address. The mesh routing layer handles delivering the packet.
ESP-MESH API Concepts
Key structures and functions from esp_mesh.h
:
mesh_cfg_t
: Configuration structure passed toesp_mesh_set_config()
. Includes:channel
: WiFi channel for the mesh.mesh_id
: The Mesh Unit Identifier (MUID).router
: Configuration for connecting the root node to an external router (ssid
,password
,bssid
).mesh_ap
: Configuration for the mesh Access Point (how nodes connect to each other). Includespassword
(mesh password) andmax_connection
.crypto_funcs
: For custom encryption if needed (advanced).
mesh_type_t
: Defines the node type (MESH_ROOT
,MESH_NODE
,MESH_LEAF_INITIATOR
,MESH_IDLE
).esp_mesh_init()
: Initializes the mesh stack.esp_mesh_start()
: Starts the mesh operation after configuration.esp_mesh_stop()
: Stops mesh operation.esp_mesh_set_config()
/esp_mesh_get_config()
: Set/get mesh configuration.esp_mesh_set_type()
/esp_mesh_get_type()
: Set/get node type.esp_mesh_send(const mesh_addr_t *to, const mesh_data_t *data, int flags, const mesh_opt_t *opt, int opt_count)
: Sends data within the mesh.to
: Destination MAC address (or NULL for root).data
: Data buffer and size.
esp_mesh_recv(mesh_addr_t *from, mesh_data_t *data, TickType_t timeout, int *flags, mesh_opt_t *opt, int opt_count)
: Receives data within the mesh.- Mesh Events: ESP-MESH posts various events to the system event loop (event base
MESH_EVENT
). Examples:
MESH_EVENT ID | Description | Associated Event Data Type |
---|---|---|
MESH_EVENT_STARTED |
ESP-MESH has successfully started. | N/A (or generic mesh_event_started_t ) |
MESH_EVENT_STOPPED |
ESP-MESH has stopped. | N/A |
MESH_EVENT_PARENT_CONNECTED |
The node has successfully connected to a parent node (for child nodes) or to the upstream router (for the root node). | mesh_event_parent_connected_t* (contains parent BSSID, SSID, etc.) |
MESH_EVENT_PARENT_DISCONNECTED |
The node has disconnected from its parent or the router. | mesh_event_parent_disconnected_t* (contains reason code) |
MESH_EVENT_CHILD_CONNECTED |
A child node has connected to this node (current node is acting as a parent). | mesh_event_child_connected_t* (contains child MAC address, AID) |
MESH_EVENT_CHILD_DISCONNECTED |
A child node has disconnected from this node. | mesh_event_child_disconnected_t* (contains child MAC address, AID) |
MESH_EVENT_ROUTING_TABLE_ADD |
A new route has been added to the mesh routing table. | mesh_event_routing_table_change_t* (contains array of MACs added) |
MESH_EVENT_ROUTING_TABLE_REMOVE |
A route has been removed from the mesh routing table. | mesh_event_routing_table_change_t* (contains array of MACs removed) |
MESH_EVENT_ROOT_ADDRESS |
Provides the MAC address of the current root node. All nodes in the mesh receive this. | mesh_event_root_address_t* (contains root MAC address) |
MESH_EVENT_ROOT_GOT_IP |
The root node has successfully obtained an IP address from the upstream router. | mesh_event_root_got_ip_t* (contains IP info) |
MESH_EVENT_ROOT_LOST_IP |
The root node has lost its IP address from the upstream router. | N/A |
MESH_EVENT_VOTE_STARTED / MESH_EVENT_VOTE_STOPPED |
Indicates the start/stop of a root node election process. | N/A |
MESH_EVENT_LAYER_CHANGE |
The node’s layer in the mesh hierarchy has changed. | mesh_event_layer_change_t* (contains new layer number) |
Security in ESP-MESH
- Mesh Network Security: All communication between mesh nodes (AP-STA links within the mesh) is secured using WPA2-PSK. All nodes must be configured with the same mesh password.
- Root Node to Router Security: The connection from the root node to the external WiFi router uses the security protocol configured on that router (e.g., WPA2-PSK, WPA3-PSK).
- Application Layer Security: ESP-MESH secures the links between nodes. However, for end-to-end security of application data transiting the mesh and potentially going to the internet, application-layer encryption (e.g., TLS, DTLS) is recommended, especially if the data is sensitive.
Practical Examples
Watch nodes appear, scan, and connect to form a mesh network.
Let’s set up a basic ESP-MESH network with one root node and one child node. The root node will connect to an existing WiFi router. We’ll then send a simple message from the child to the root.
Prerequisites:
- Two ESP32 development boards.
- ESP-IDF v5.x installed and configured.
- An existing WiFi router with internet access (for the root node).
- Know the SSID and password of your existing WiFi router.
1. Project Setup (Common for both Root and Child Node projects):
- Create a new ESP-IDF project (e.g.,
esp_mesh_node
). - In
menuconfig
:Component config
->ESP NETIF Adapter
->Enable ESP NETIF Adapter
(should be default).Component config
->Wi-Fi
->WiFi Station Enable
(should be default).Component config
->ESP Wi-Fi Mesh (ESP-MESH)
:- Enable
ESP Wi-Fi Mesh (ESP-MESH) support
. - You can adjust
Max number of mesh connections (1-10)
(default 6). - You can adjust
Max number of layers in mesh network (1-25)
(default 6).
- Enable
- Save and exit.
2. Common Code (e.g., mesh_utils.h and mesh_utils.c – conceptual):
It’s good practice to have common definitions and event handlers. For simplicity in this example, we’ll integrate parts into the main files, but in larger projects, separate them.
Mesh Configuration Defines (place in your main C file or a common header):
// Common Mesh Configuration
#define MESH_ROUTER_SSID "YOUR_ROUTER_SSID" // SSID of your Wi-Fi router
#define MESH_ROUTER_PASSWORD "YOUR_ROUTER_PASSWORD" // Password of your Wi-Fi router
#define MESH_ID_STR "MyESP_MeshNetwork" // Custom name for your mesh network
#define MESH_PASSWORD "meshpassword123" // Password for your mesh network (min 8 chars)
#define MESH_CHANNEL 6 // WiFi channel for the mesh (1-13)
#define MESH_MAX_CONNECTIONS 4 // Max children a parent node can have
#define MESH_MAX_LAYERS 3 // Max layers in the mesh
Important: All nodes in your ESP-MESH network must use the same
MESH_ID_STR
,MESH_PASSWORD
, andMESH_CHANNEL
.
3. Root Node Implementation (mesh_root_main.c
):
// mesh_root_main.c
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_mesh.h"
#include "esp_netif.h"
static const char *TAG_ROOT = "MESH_ROOT";
// Use common mesh configuration defines from above
static bool is_mesh_connected = false;
static mesh_addr_t mesh_parent_addr; // Not used by root, but good for general handler
static int mesh_layer = -1;
// Mesh Event Handler
void mesh_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
mesh_addr_t id = {0,};
static uint8_t last_layer = 0;
switch (event_id) {
case MESH_EVENT_STARTED:
esp_mesh_get_id(&id);
ESP_LOGI(TAG_ROOT, "MESH_EVENT_STARTED: <MESHID MAC:%02x:%02x:%02x:%02x:%02x:%02x>", MAC2STR(id.addr));
is_mesh_connected = false;
mesh_layer = esp_mesh_get_layer();
break;
case MESH_EVENT_STOPPED:
ESP_LOGI(TAG_ROOT, "MESH_EVENT_STOPPED");
is_mesh_connected = false;
mesh_layer = esp_mesh_get_layer();
break;
case MESH_EVENT_CHILD_CONNECTED: {
mesh_event_child_connected_t *child_connected = (mesh_event_child_connected_t *)event_data;
ESP_LOGI(TAG_ROOT, "MESH_EVENT_CHILD_CONNECTED: <MAC:%02x:%02x:%02x:%02x:%02x:%02x> aid:%d",
MAC2STR(child_connected->mac), child_connected->aid);
break;
}
case MESH_EVENT_CHILD_DISCONNECTED: {
mesh_event_child_disconnected_t *child_disconnected = (mesh_event_child_disconnected_t *)event_data;
ESP_LOGI(TAG_ROOT, "MESH_EVENT_CHILD_DISCONNECTED: <MAC:%02x:%02x:%02x:%02x:%02x:%02x> aid:%d",
MAC2STR(child_disconnected->mac), child_disconnected->aid);
break;
}
case MESH_EVENT_PARENT_CONNECTED: { // Root node specific events
mesh_event_parent_connected_t *parent_connected = (mesh_event_parent_connected_t *)event_data;
esp_mesh_get_id(&id);
mesh_layer = esp_mesh_get_layer();
memcpy(&mesh_parent_addr.addr, parent_connected->connected.bssid, 6);
ESP_LOGI(TAG_ROOT, "MESH_EVENT_PARENT_CONNECTED: <MESHID MAC:%02x:%02x:%02x:%02x:%02x:%02x> Layer:%d Parent:"MACSTR"",
MAC2STR(id.addr), mesh_layer, MAC2STR(mesh_parent_addr.addr));
is_mesh_connected = true; // For root, parent_connected means connected to router
if (esp_mesh_is_root()) {
ESP_LOGI(TAG_ROOT, "Root node connected to router SSID:%s", MESH_ROUTER_SSID);
}
break;
}
case MESH_EVENT_PARENT_DISCONNECTED: {
mesh_event_parent_disconnected_t *parent_disconnected = (mesh_event_parent_disconnected_t *)event_data;
ESP_LOGI(TAG_ROOT, "MESH_EVENT_PARENT_DISCONNECTED: Reason:%d", parent_disconnected->reason);
is_mesh_connected = false;
mesh_layer = esp_mesh_get_layer();
break;
}
case MESH_EVENT_LAYER_CHANGE: {
mesh_event_layer_change_t *layer_change = (mesh_event_layer_change_t *)event_data;
mesh_layer = layer_change->new_layer;
ESP_LOGI(TAG_ROOT, "MESH_EVENT_LAYER_CHANGE: Layer:%d", mesh_layer);
if (mesh_layer < last_layer) { // Example: if layer improves
ESP_LOGI(TAG_ROOT, "Layer improved from %d to %d", last_layer, mesh_layer);
}
last_layer = mesh_layer;
break;
}
case MESH_EVENT_ROOT_ADDRESS: {
mesh_event_root_address_t *root_addr = (mesh_event_root_address_t *)event_data;
ESP_LOGI(TAG_ROOT, "MESH_EVENT_ROOT_ADDRESS: <ROOT MAC:%02x:%02x:%02x:%02x:%02x:%02x>", MAC2STR(root_addr->addr));
break;
}
case MESH_EVENT_ROOT_GOT_IP: {
mesh_event_root_got_ip_t *got_ip = (mesh_event_root_got_ip_t *)event_data;
ESP_LOGI(TAG_ROOT, "MESH_EVENT_ROOT_GOT_IP: <IP:%s>", ip4addr_ntoa((const ip4_addr_t *) &got_ip->ip_info.ip));
// Now root can communicate with the internet
break;
}
case MESH_EVENT_ROOT_LOST_IP:
ESP_LOGW(TAG_ROOT, "MESH_EVENT_ROOT_LOST_IP: Root lost IP from router.");
break;
default:
ESP_LOGD(TAG_ROOT, "Unhandled MESH_EVENT ID %ld", event_id);
break;
}
}
// Task to receive data (Root node specific)
void mesh_rx_task(void *arg)
{
esp_err_t err;
mesh_addr_t from;
mesh_data_t data;
int flags;
data.data = malloc(MESH_MTU_SIZE); // MESH_MTU_SIZE is typically around 1472
if (data.data == NULL) {
ESP_LOGE(TAG_ROOT, "Failed to allocate memory for RX data buffer");
vTaskDelete(NULL);
return;
}
data.size = MESH_MTU_SIZE;
ESP_LOGI(TAG_ROOT, "Mesh RX task started");
while (1) {
data.size = MESH_MTU_SIZE; // Reset size for next receive
// Blocking call to receive data, timeout of 10 seconds
err = esp_mesh_recv(&from, &data, pdMS_TO_TICKS(10000), &flags, NULL, 0);
if (err == ESP_OK) {
ESP_LOGI(TAG_ROOT, "Received data from "MACSTR", size:%d, data: %.*s",
MAC2STR(from.addr), data.size, data.size, (char*)data.data);
// Process received data here
} else if (err == ESP_ERR_MESH_TIMEOUT) {
// ESP_LOGD(TAG_ROOT, "Mesh RX timeout"); // Can be verbose
} else if (err != ESP_ERR_TIMEOUT && err != ESP_ERR_MESH_NOT_START) { // ESP_ERR_TIMEOUT is also possible if no data
ESP_LOGE(TAG_ROOT, "Error in esp_mesh_recv: %s", esp_err_to_name(err));
}
// Add a small delay to prevent busy-looping if recv returns immediately with an error
vTaskDelay(pdMS_TO_TICKS(100));
}
free(data.data);
vTaskDelete(NULL);
}
void app_main(void)
{
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// Initialize TCP/IP stack and default event loop
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
// For SoftAP based provisioning (if used, not in this basic example)
// ESP_ERROR_CHECK(esp_netif_create_default_wifi_ap());
// For Station mode (used by root to connect to router)
ESP_ERROR_CHECK(esp_netif_create_default_wifi_sta());
// Initialize WiFi
wifi_init_config_t wifi_init_cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_cfg));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &mesh_event_handler, NULL)); // Also handle IP events for root
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); // Store WiFi config in RAM
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); // Root node starts in STA mode to connect to router
// Initialize ESP-MESH
ESP_ERROR_CHECK(esp_mesh_init());
ESP_ERROR_CHECK(esp_event_handler_register(MESH_EVENT, ESP_EVENT_ANY_ID, &mesh_event_handler, NULL));
// Configure ESP-MESH
mesh_cfg_t cfg = ESP_MESH_DEFAULT_INIT();
cfg.channel = MESH_CHANNEL;
cfg.mesh_ap.max_connection = MESH_MAX_CONNECTIONS;
cfg.mesh_id.addr[0] = 0x77; // Example MUID prefix, ensure it's unique if needed
cfg.mesh_id.addr[1] = 0x77;
cfg.mesh_id.addr[2] = 0x77;
cfg.mesh_id.addr[3] = 0x77;
cfg.mesh_id.addr[4] = 0x77;
cfg.mesh_id.addr[5] = 0x77;
// For a more robust MUID from string:
// uint8_t mesh_id_bytes[6];
// esp_err_t err_muid = esp_mesh_set_self_organized(true, true); // Enable self-organization for MUID generation
// if (err_muid == ESP_OK) {
// memcpy(mesh_id_bytes, esp_mesh_get_self_organized_muid(), 6);
// memcpy(cfg.mesh_id.addr, mesh_id_bytes, 6);
// } else { ESP_LOGE(TAG_ROOT, "Failed to get self-organized MUID"); }
// Or simply use a fixed MUID as above.
// If using a string for MUID, it needs to be hashed or mapped to 6 bytes.
// For simplicity, we use a fixed byte array.
// Set Mesh Password
memcpy((char*) &cfg.mesh_ap.auth, MESH_PASSWORD, strlen(MESH_PASSWORD));
cfg.mesh_ap.auth.authmode = WIFI_AUTH_WPA2_PSK; // ESP-MESH uses WPA2-PSK for internal links
// Configure router connection for the root node
memcpy((char*) &cfg.router.ssid, MESH_ROUTER_SSID, strlen(MESH_ROUTER_SSID));
memcpy((char*) &cfg.router.password, MESH_ROUTER_PASSWORD, strlen(MESH_ROUTER_PASSWORD));
cfg.router.ssid_len = strlen(MESH_ROUTER_SSID);
ESP_ERROR_CHECK(esp_mesh_set_config(&cfg));
// Start ESP-MESH
ESP_ERROR_CHECK(esp_mesh_start());
ESP_LOGI(TAG_ROOT, "ESP-MESH Root Node Initialized. Max Layers: %d", MESH_MAX_LAYERS);
esp_mesh_set_max_layer(MESH_MAX_LAYERS); // Set max layers for the mesh
esp_mesh_allow_channel_switch(true); // Allow channel switching if router is on different channel
// Create task to handle received mesh data
xTaskCreate(mesh_rx_task, "mesh_rx_task", 3072, NULL, 5, NULL);
ESP_LOGI(TAG_ROOT, "Root node setup complete. Waiting for connections and router IP...");
}
4. Child Node Implementation (mesh_child_main.c):
The child node code is very similar, but it doesn’t configure router credentials and typically doesn’t need to handle MESH_EVENT_ROOT_GOT_IP in the same way.
// mesh_child_main.c
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_mesh.h"
#include "esp_netif.h" // For esp_netif_init
static const char *TAG_CHILD = "MESH_CHILD";
// Use common mesh configuration defines from above (MESH_ID_STR, MESH_PASSWORD, MESH_CHANNEL etc.)
// MESH_ROUTER_SSID and MESH_ROUTER_PASSWORD are not used by child nodes directly for config.
static bool is_mesh_connected = false;
static mesh_addr_t mesh_parent_addr;
static int mesh_layer = -1;
static esp_netif_t *sta_netif_handle = NULL; // For child node, netif is for mesh internal use
// Mesh Event Handler (can be largely the same as root's, but logging tag differs)
void mesh_event_handler_child(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
mesh_addr_t id = {0,};
// static uint8_t last_layer = 0; // If tracking layer changes
switch (event_id) {
case MESH_EVENT_STARTED:
esp_mesh_get_id(&id);
ESP_LOGI(TAG_CHILD, "MESH_EVENT_STARTED: <MESHID MAC:%02x:%02x:%02x:%02x:%02x:%02x>", MAC2STR(id.addr));
is_mesh_connected = false; // Will be set true on PARENT_CONNECTED
mesh_layer = esp_mesh_get_layer();
break;
case MESH_EVENT_STOPPED:
ESP_LOGI(TAG_CHILD, "MESH_EVENT_STOPPED");
is_mesh_connected = false;
mesh_layer = esp_mesh_get_layer();
break;
case MESH_EVENT_PARENT_CONNECTED: {
mesh_event_parent_connected_t *parent_connected = (mesh_event_parent_connected_t *)event_data;
esp_mesh_get_id(&id); // Get self mesh ID
mesh_layer = esp_mesh_get_layer();
memcpy(&mesh_parent_addr.addr, parent_connected->connected.bssid, 6);
ESP_LOGI(TAG_CHILD, "MESH_EVENT_PARENT_CONNECTED: <MESHID MAC:%02x:%02x:%02x:%02x:%02x:%02x> Layer:%d Parent BSSID:"MACSTR"",
MAC2STR(id.addr), mesh_layer, MAC2STR(mesh_parent_addr.addr));
is_mesh_connected = true;
// After connecting to a parent, the child node can send data
break;
}
case MESH_EVENT_PARENT_DISCONNECTED: {
mesh_event_parent_disconnected_t *parent_disconnected = (mesh_event_parent_disconnected_t *)event_data;
ESP_LOGW(TAG_CHILD, "MESH_EVENT_PARENT_DISCONNECTED: Reason:%d", parent_disconnected->reason);
is_mesh_connected = false;
mesh_layer = esp_mesh_get_layer();
// Mesh will attempt to find a new parent
break;
}
case MESH_EVENT_LAYER_CHANGE: {
mesh_event_layer_change_t *layer_change = (mesh_event_layer_change_t *)event_data;
mesh_layer = layer_change->new_layer;
ESP_LOGI(TAG_CHILD, "MESH_EVENT_LAYER_CHANGE: New Layer:%d", mesh_layer);
break;
}
case MESH_EVENT_ROOT_ADDRESS: { // All nodes receive this
mesh_event_root_address_t *root_addr = (mesh_event_root_address_t *)event_data;
ESP_LOGI(TAG_CHILD, "MESH_EVENT_ROOT_ADDRESS: <ROOT MAC:%02x:%02x:%02x:%02x:%02x:%02x>", MAC2STR(root_addr->addr));
break;
}
// Child nodes typically don't get MESH_EVENT_ROOT_GOT_IP directly for their own IP,
// but they know the root exists and might have an IP.
// Other events like CHILD_CONNECTED/DISCONNECTED are seen by their parents.
default:
ESP_LOGD(TAG_CHILD, "Unhandled MESH_EVENT ID %ld", event_id);
break;
}
}
// Task to send data from child to root
void mesh_tx_task(void *arg)
{
esp_err_t err;
mesh_data_t data;
char *my_data_str = "Hello Root from Child!";
data.data = (uint8_t *)my_data_str;
data.size = strlen(my_data_str) + 1; // Include null terminator
data.proto = MESH_PROTO_BIN; // Or MESH_PROTO_JSON, MESH_PROTO_HTTP etc.
// Mesh options for sending data (e.g. to specify no ACK)
// mesh_opt_t opt = { .type = MESH_OPT_RECV_DS_ADDR, .len = sizeof(mesh_addr_t) };
// memcpy(&opt.value, &root_mac_addr, sizeof(mesh_addr_t)); // If sending to specific non-root node
ESP_LOGI(TAG_CHILD, "Mesh TX task started. Will send data periodically.");
while (1) {
vTaskDelay(pdMS_TO_TICKS(10000)); // Send every 10 seconds
if (!is_mesh_connected || esp_mesh_is_root()) { // Don't send if not connected or if accidentally became root
ESP_LOGD(TAG_CHILD, "Not connected to parent or became root, skipping send.");
continue;
}
ESP_LOGI(TAG_CHILD, "Sending data to root: %s (Size: %u)", (char*)data.data, data.size);
// To send to root, the 'to' address can be NULL or a zeroed address
err = esp_mesh_send(NULL, &data, MESH_DATA_RELIABLE, NULL, 0); // MESH_DATA_RELIABLE for ACK
if (err == ESP_OK) {
ESP_LOGI(TAG_CHILD, "Data sent successfully to root.");
} else {
ESP_LOGE(TAG_CHILD, "Failed to send data to root: %s", esp_err_to_name(err));
}
}
vTaskDelete(NULL);
}
void app_main(void)
{
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// Initialize TCP/IP stack and default event loop
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
// Create default Wi-Fi station netif.
// Even for mesh nodes not acting as root, this is needed for the Wi-Fi driver to operate.
// The mesh stack manages its own LwIP netif internally for mesh communication.
sta_netif_handle = esp_netif_create_default_wifi_sta();
assert(sta_netif_handle);
// Initialize WiFi
wifi_init_config_t wifi_init_cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_cfg));
// For child nodes, we don't typically register for IP_EVENT_STA_GOT_IP from the router.
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
// While a child node doesn't connect to a router, setting mode to STA is part of Wi-Fi init.
// The mesh stack will manage AP/STA roles for mesh links.
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
// Initialize ESP-MESH
ESP_ERROR_CHECK(esp_mesh_init());
ESP_ERROR_CHECK(esp_event_handler_register(MESH_EVENT, ESP_EVENT_ANY_ID, &mesh_event_handler_child, NULL));
// Configure ESP-MESH (same as root, but without router config)
mesh_cfg_t cfg = ESP_MESH_DEFAULT_INIT();
cfg.channel = MESH_CHANNEL;
cfg.mesh_ap.max_connection = MESH_MAX_CONNECTIONS;
// MUID must be the same as the root node
cfg.mesh_id.addr[0] = 0x77;
cfg.mesh_id.addr[1] = 0x77;
cfg.mesh_id.addr[2] = 0x77;
cfg.mesh_id.addr[3] = 0x77;
cfg.mesh_id.addr[4] = 0x77;
cfg.mesh_id.addr[5] = 0x77;
memcpy((char*) &cfg.mesh_ap.auth, MESH_PASSWORD, strlen(MESH_PASSWORD));
cfg.mesh_ap.auth.authmode = WIFI_AUTH_WPA2_PSK;
// Child nodes do not connect to a router directly, so router config is cleared/ignored
memset(&cfg.router, 0, sizeof(mesh_router_config_t));
ESP_ERROR_CHECK(esp_mesh_set_config(&cfg));
// Start ESP-MESH
ESP_ERROR_CHECK(esp_mesh_start());
ESP_LOGI(TAG_CHILD, "ESP-MESH Child Node Initialized. Max Layers: %d", MESH_MAX_LAYERS);
esp_mesh_set_max_layer(MESH_MAX_LAYERS);
esp_mesh_allow_channel_switch(true);
// Create task to send data
xTaskCreate(mesh_tx_task, "mesh_tx_task", 3072, NULL, 5, NULL);
ESP_LOGI(TAG_CHILD, "Child node setup complete. Scanning for parent...");
}
5. Build, Flash, and Observe:
- Root Node:
- Update
MESH_ROUTER_SSID
andMESH_ROUTER_PASSWORD
inmesh_root_main.c
. - Build and flash this code to one ESP32 board.
- Open the serial monitor. You should see it initialize, connect to your router, get an IP, and then wait for child connections.
- Update
- Child Node:
- Ensure
MESH_ID_STR
,MESH_PASSWORD
, andMESH_CHANNEL
inmesh_child_main.c
match the root node’s configuration. - Build and flash this code to a second ESP32 board.
- Open its serial monitor. You should see it initialize, scan for the mesh network, connect to the root node as its parent, and start sending “Hello Root…” messages periodically.
- Ensure
- Observation:
- The root node’s monitor should show
MESH_EVENT_CHILD_CONNECTED
when the child joins. - The root node’s monitor should then show the “Hello Root…” messages being received by its
mesh_rx_task
.
- The root node’s monitor should show
Variant Notes
ESP-MESH is a software feature built on top of the standard WiFi capabilities of Espressif chips.
- Supported Variants:
- ESP32: Full support. This is the original chip for which ESP-MESH was developed.
- ESP32-S2: Supports ESP-MESH. Performance and node capacity might differ slightly from ESP32 due to single-core architecture but generally capable.
- ESP32-S3: Full support. Dual-core architecture similar to ESP32, offering good performance for mesh operations.
- ESP32-C3: Supports ESP-MESH. RISC-V single-core, may have different performance characteristics or practical node limits compared to dual-core ESP32/S3.
- ESP32-C6: Supports ESP-MESH. Includes WiFi 6 capabilities, which could offer benefits in dense environments, though ESP-MESH itself is based on 802.11b/g/n mechanisms.
- ESP-IDF Version: ESP-MESH has been available for a long time. Ensure you are using a reasonably recent ESP-IDF version (v4.x, v5.x) for the latest features, bug fixes, and performance improvements.
- Resource Constraints: The maximum number of nodes and layers in a practical ESP-MESH network can be limited by the available RAM and processing power of the nodes, especially the root node and intermediate parent nodes which handle more traffic and routing. Variants with more RAM (e.g., ESP32-S3 with PSRAM) might handle larger or more complex mesh networks more effectively.
- ESP32-H2: Does not support WiFi and therefore does not support ESP-MESH.
Always refer to the latest ESP-IDF documentation for any variant-specific notes or limitations regarding ESP-MESH.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
Mismatched Mesh Configuration | Nodes fail to connect to each other or form separate, unintended meshes. MESH_EVENT_PARENT_DISCONNECTED with reason MESH_REASON_AUTH_FAIL or MESH_REASON_NO_AP_FOUND (if MUID differs). |
Fix: Ensure mesh_id (MUID), mesh_ap.password (mesh password in cfg.mesh_ap.auth ), and channel in mesh_cfg_t are identical across all nodes in the same mesh network.
|
Root Node Router Connection Failure | Root node fails to get MESH_EVENT_PARENT_CONNECTED (to router) or MESH_EVENT_ROOT_GOT_IP . Mesh forms locally but has no external connectivity. |
Fix: Double-check router SSID (cfg.router.ssid ) and password (cfg.router.password ) in root node’s config. Ensure router is operational and within range. Check router logs for denied connections. Verify router security settings (WPA2-PSK typically).
|
Exceeding Max Connections/Layers | Child nodes fail to connect to a parent (MESH_REASON_MAX_CONNECTION ). Network might not form as expected or nodes become isolated. Performance degradation. |
Fix: Plan topology. Adjust cfg.mesh_ap.max_connection (default 6) and esp_mesh_set_max_layer() (default 6) if necessary, but be mindful of resource limits (RAM, CPU) and performance impacts of many connections/deep layers.
|
No Common Channel / Channel Hopping Issues | Nodes cannot find each other or take a long time to connect. Root node’s channel is set by its upstream router. |
Fix: Initially, try setting a fixed cfg.channel for all nodes (if root can also use it). Enable esp_mesh_allow_channel_switch(true) on nodes to allow them to adapt. Ensure root’s router connection doesn’t force a channel incompatible with other nodes.
|
Data Reception Issues (esp_mesh_recv ) |
esp_mesh_recv() returns errors, times out, or buffer is too small (ESP_ERR_MESH_ARGUMENT ). Data appears corrupted. |
Fix: Ensure data.size is initialized to buffer capacity before calling esp_mesh_recv() . Allocate buffer with MESH_MTU_SIZE . Check task running esp_mesh_recv() has sufficient stack and priority. Ensure sender and receiver agree on data protocol (data.proto ).
|
Forgetting Core Initializations | Crashes or errors during esp_mesh_init() or esp_wifi_init() . System event loop not working. |
Fix: Ensure nvs_flash_init() , esp_netif_init() , and esp_event_loop_create_default() are called once at startup before WiFi/Mesh initialization.
|
Mesh ID (MUID) Configuration | Nodes using default or non-unique MUIDs might interfere with other nearby ESP-MESH networks or fail to form the intended network. |
Fix: Set a unique cfg.mesh_id.addr (6-byte array) for your specific mesh network. Avoid using the all-zero or default MUID in production.
|
Insufficient Task Stack for Mesh Operations | Stack overflow in tasks calling mesh APIs or in the event handler task if complex logic is done there. | Fix: Profile stack usage. Increase stack size for tasks interacting heavily with the mesh stack (e.g., data sending/receiving tasks, event handlers if offloading). Default event task stack might also need adjustment in menuconfig for very busy meshes. |
Exercises
- Root to Child Communication:
- Modify the root node code to send a specific message (e.g., “ACK_FROM_ROOT”) back to a child node after receiving a message from it. The root will need the child’s MAC address (available in the
from
parameter ofesp_mesh_recv
). - The child node should implement
esp_mesh_recv()
to listen for this acknowledgment.
- Modify the root node code to send a specific message (e.g., “ACK_FROM_ROOT”) back to a child node after receiving a message from it. The root will need the child’s MAC address (available in the
- Multi-Hop Data Display:
- Set up a three-node mesh: Root, Child A (connected to Root), and Child B (connected to Child A, forming two hops from Root).
- Have Child B send a message. Observe on the Root’s serial monitor.
- In the event handlers of Child A and Root, log when they receive/forward a packet, noting the source and its layer, to trace the multi-hop path.
- Dynamic Light Control Mesh:
- Imagine each ESP32 node has an LED.
- Implement a system where the root node can send a command to:
- Turn ON/OFF the LED of a specific child node (identified by its MAC address, which the root learns from
MESH_EVENT_CHILD_CONNECTED
or routing table events). - Turn ON/OFF LEDs of all child nodes in a specific layer.
- Turn ON/OFF LEDs of all nodes in the mesh (broadcast).
- Turn ON/OFF the LED of a specific child node (identified by its MAC address, which the root learns from
- Child nodes receive these commands and control their local LED.
- Mesh Network Status Reporter:
- Create a task on the root node that periodically (e.g., every 30 seconds):
- Gets the current routing table using
esp_mesh_get_routing_table()
andesp_mesh_get_routing_table_size()
. - Prints the number of connected children and their MAC addresses and layers.
- Prints its own layer and connection status (to router).
- Gets the current routing table using
- This helps visualize the mesh structure and health.
- Create a task on the root node that periodically (e.g., every 30 seconds):
Summary
- ESP-MESH enables ESP32 devices to form self-organizing, self-healing WiFi mesh networks.
- The network has a tree-like logical structure with a single root node connecting to an external IP network.
- Nodes can be root, parent, child, or idle, organized in layers.
- Communication within the mesh uses WPA2-PSK security, configured with a common Mesh ID and password.
- ESP-IDF provides a comprehensive API (
esp_mesh.h
) for initialization, configuration, data transfer, and event handling. - Key events like
MESH_EVENT_PARENT_CONNECTED
,MESH_EVENT_CHILD_CONNECTED
, andMESH_EVENT_ROOT_GOT_IP
are crucial for managing node state. - ESP-MESH is supported on most WiFi-enabled ESP32 variants (ESP32, S2, S3, C3, C6).
- Proper configuration and understanding of mesh dynamics are key to successful deployment.
Further Reading
- ESP-IDF ESP-MESH Programming Guide: https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-guides/mesh.html
- ESP-IDF ESP-MESH API Reference: https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-reference/network/esp_mesh.html
- ESP-MESH Examples in ESP-IDF: Check the
$IDF_PATH/examples/mesh/
directory in your ESP-IDF installation for various practical examples (e.g.,internal_communication
,mqtt_mesh
).