Chapter 47: Root Node Configuration in Mesh Networks
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand the critical and unique role of the root node in an ESP-MESH network.
- Configure an ESP32 device to act as a designated root node.
- Manage the root node’s connection to an external Wi-Fi router (the upstream connection).
- Effectively handle events related to router connectivity and IP address acquisition for the root node.
- Understand how the root node initiates the mesh and manages its direct children.
- Implement basic data reception from child nodes and conceptualize data forwarding.
- Discuss strategies for enhancing root node stability.
Introduction
In Chapter 46, we introduced ESP-MESH as a versatile solution for creating self-organizing and self-healing WiFi networks with ESP32 devices. We learned about the different node types and the overall mesh architecture. Central to this architecture is the root node. Unlike other nodes that primarily relay information or act as endpoints within the mesh, the root node serves as the crucial bridge between the internal ESP-MESH network and any external IP network, such as your local LAN or the wider internet.
The correct configuration and robust operation of the root node are paramount for the entire mesh network’s functionality, especially if internet connectivity or interaction with external services is required. This chapter will delve deeply into the specifics of setting up an ESP32 as an ESP-MESH root node. We will cover its unique configuration parameters, how it establishes and maintains its upstream connection, its role in managing the mesh, and how to handle critical events related to its status.
Theory
The Indispensable Root Node
graph TD subgraph External Network direction LR EXT_ROUTER[("Wi-Fi Router<br>(Upstream AP)")]:::process INTERNET[("Internet / LAN<br>Services")]:::process end subgraph ESP-MESH Network direction TB ROOT_NODE[("ESP32 Root Node<br>(Layer 1)")]:::primary CHILD_1[("ESP32 Child Node<br>(Layer 2)")]:::process CHILD_2[("ESP32 Child Node<br>(Layer 2)")]:::process GRANDCHILD_1_1[("ESP32 Grandchild<br>(Layer 3)")]:::process LEAF_NODE[("ESP32 Leaf Node")]:::process end ROOT_NODE -- "1- Wi-Fi STA<br>Connection" --> EXT_ROUTER EXT_ROUTER -- "IP Network Access" --> INTERNET ROOT_NODE -- "2- Mesh AP<br>for Children" --> CHILD_1 ROOT_NODE -- "2- Mesh AP<br>for Children" --> CHILD_2 CHILD_1 -- "Mesh Connection" --> GRANDCHILD_1_1 CHILD_2 -- "Mesh Connection" --> LEAF_NODE %% Data Flow Arrows CHILD_1 -- "Data Uplink" --> ROOT_NODE GRANDCHILD_1_1 -- "Data Uplink" --> CHILD_1 ROOT_NODE -- "Data to External" --> EXT_ROUTER EXT_ROUTER -- "Data from External" --> ROOT_NODE ROOT_NODE -- "Data Downlink" --> CHILD_2 CHILD_2 -- "Data Downlink" --> LEAF_NODE classDef primary fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6 classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46 classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E classDef check fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B
The root node is the cornerstone of an ESP-MESH network that requires external connectivity. Its primary responsibilities include:
- Gateway to External Networks: It is the only node in the mesh that connects directly to an external Wi-Fi router (upstream AP). This connection allows all other nodes in the mesh to potentially access external resources (e.g., internet services, local network servers) via the root node.
- Top of the Hierarchy: The root node is always at Layer 1 of the ESP-MESH tree structure. All other nodes are its descendants, either directly (children) or indirectly (grandchildren, etc.).
- Mesh Initiation and Parameter Anchor: While any node can initiate a mesh if no existing one is found, the root node, by connecting to a router, often solidifies the mesh’s operational parameters like the Wi-Fi channel (which might be dictated by the router’s channel).
- Central Point for Data Aggregation/Distribution: Data from the mesh destined for external networks flows upwards to the root. Conversely, data from external networks entering the mesh comes through the root.
Without a functioning root node (or a successful re-election of a new root), an ESP-MESH network becomes an isolated “island” network, unable to communicate externally via IP.
Root Node Configuration Parameters
When configuring an ESP32 as a root node, several parameters within the mesh_cfg_t
structure (set via esp_mesh_set_config()
) are particularly important:
- Router Connection (
cfg.router
): Thismesh_router_config_t
substructure is critical and specific to the root node.ssid
: The SSID of the external Wi-Fi router.password
: The password for the external Wi-Fi router.bssid
: Optional. The MAC address of the external Wi-Fi router. If set, the root node will attempt to connect only to this specific AP.ssid_len
: Length of the router SSID.
Parameter in `cfg.router` | Type | Description | Example |
---|---|---|---|
ssid |
char[32] |
The Service Set Identifier (name) of the external Wi-Fi router. | "MyHomeWiFi" |
password |
char[64] |
The password for the external Wi-Fi router. | "SecureP@ss!" |
bssid |
uint8_t[6] |
Optional. The MAC address of the external Wi-Fi router. | {0x1A, 0x2B, 0x3C, 0x4D, 0x5E, 0x6F} (If specified, connects only to this AP) |
ssid_len |
uint8_t |
Length of the router SSID. Automatically calculated by `strlen` if not set manually. | 10 (for “MyHomeWiFi”) |
- Mesh Definition (
cfg.mesh_id
,cfg.mesh_ap
): These define the ESP-MESH network itself.mesh_id
(mesh_addr_t
): The 6-byte Mesh Unit Identifier (MUID). All nodes in the same mesh must share this ID.mesh_ap.auth
(wifi_auth_mode_t
withinmesh_ap_auth_t
): The authentication mode for inter-node mesh connections. ESP-MESH uses WPA2-PSK, so this is typically set via the password.mesh_ap.password
(char[64]
withinmesh_ap_auth_t
): The password for the ESP-MESH network. All nodes must use the same mesh password.channel
(uint8_t
): The Wi-Fi channel for the mesh. If the root connects to a router on a different channel, and channel switching is allowed (esp_mesh_allow_channel_switch(true)
), the mesh may switch to the router’s channel.
- Mesh Access Point Settings (
cfg.mesh_ap
):max_connection
(uint8_t
): The maximum number of direct child nodes the root node (acting as a mesh AP) can accept.
- Node Type: While not directly in
mesh_cfg_t
, the node type is set usingesp_mesh_set_type(MESH_ROOT)
. However, ESP-MESH typically determines the root node automatically ifcfg.router.ssid
is configured. If a node successfully connects to the configured router, it becomes the root.
Parameter in `mesh_cfg_t` | Sub-structure/Type | Description | Relevance to Root Node |
---|---|---|---|
router |
mesh_router_config_t |
Configuration for connecting to an external Wi-Fi router. | Critical: Contains SSID, password, and optional BSSID of the upstream router. This enables the node to become a root node. |
mesh_id |
mesh_addr_t (6-byte array) |
The unique identifier for the ESP-MESH network. | All nodes in the mesh, including the root, must share the same Mesh ID. |
mesh_ap.auth |
mesh_ap_auth_t (contains wifi_auth_mode_t ) |
Authentication mode for inter-node mesh connections (softAP of mesh nodes). | Defines how child nodes authenticate with the root node (and other parent nodes). Typically WPA2-PSK. |
mesh_ap.password |
char[64] (within mesh_ap_auth_t ) |
Password for the ESP-MESH network’s internal AP. | All nodes must use this password to join the mesh network established by the root. |
channel |
uint8_t |
Wi-Fi channel for the mesh network. | If the root connects to a router on a different channel and channel switching is allowed, the mesh may switch to the router’s channel. Can be 0 for auto-selection. |
mesh_ap.max_connection |
uint8_t |
Maximum number of direct child nodes the mesh AP (including the root) can accept. | Determines how many children can directly connect to the root node. |
Establishing the Upstream (Router) Connection
For a device to become the root node, it must first act as a Wi-Fi station to connect to the specified external router. The ESP-MESH stack manages this process:
- The application initializes WiFi in
WIFI_MODE_STA
. - The
mesh_cfg_t
structure is populated with the router’s SSID and password. esp_mesh_set_config(&cfg)
is called.- esp_mesh_start() is called.The mesh stack then attempts to connect to the router using the provided credentials.
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#EDE9FE', 'primaryBorderColor': '#5B21B6', 'primaryTextColor': '#5B21B6', 'secondaryColor': '#DBEAFE', 'secondaryBorderColor': '#2563EB', 'secondaryTextColor': '#1E40AF', 'tertiaryColor': '#D1FAE5', 'tertiaryBorderColor': '#059669', 'tertiaryTextColor': '#065F46', 'noteTextColor': '#334155', 'actorLineColor': '#94A3B8' }}}%% sequenceDiagram participant APP as ESP32 App Code participant MESH_STACK as ESP-MESH Stack participant WIFI_ROUTER as External Wi-Fi Router participant DHCP_SERVER as DHCP Server (on Router) APP->>MESH_STACK: 1. esp_mesh_set_config(cfg_with_router_ssid_pwd) APP->>MESH_STACK: 2. esp_mesh_start() MESH_STACK->>WIFI_ROUTER: 3. Attempt Wi-Fi Connection (STA mode) activate MESH_STACK activate WIFI_ROUTER WIFI_ROUTER-->>MESH_STACK: 4. Wi-Fi Connection Successful deactivate WIFI_ROUTER Note over MESH_STACK,APP: MESH_EVENT_PARENT_CONNECTED MESH_STACK->>DHCP_SERVER: 5. Request IP Address (DHCP Discover) activate DHCP_SERVER DHCP_SERVER-->>MESH_STACK: 6. Offer IP Address (DHCP Offer) MESH_STACK->>DHCP_SERVER: 7. Accept IP Address (DHCP Request) DHCP_SERVER-->>MESH_STACK: 8. Confirm IP Address (DHCP ACK) deactivate DHCP_SERVER Note over MESH_STACK,APP: MESH_EVENT_ROOT_GOT_IP (with IP info) deactivate MESH_STACK
IP Address Acquisition and Key Events
Once the root node successfully connects to the external router, it needs to obtain an IP address (usually via DHCP from the router). This event is critical as it signifies true external network readiness.
Key MESH_EVENTs for the Root Node:
MESH_EVENT_PARENT_CONNECTED
: For a non-root node, this means it connected to its mesh parent. For the root node, this event signifies it has successfully connected to the external Wi-Fi router defined incfg.router
. Theevent_data
will be of typemesh_event_parent_connected_t
.MESH_EVENT_PARENT_DISCONNECTED
: For the root node, this means it has lost connection to the external router. Theevent_data
(mesh_event_parent_disconnected_t
) includes areason
code.MESH_EVENT_ROOT_GOT_IP
: This is arguably the most important event for confirming the root node’s full operational status. It indicates that the root node has not only connected to the router but has also successfully obtained an IP address from it. Theevent_data
(mesh_event_root_got_ip_t
) contains the IP information (esp_netif_ip_info_t
). Only after this event can the root node (and thus the mesh through it) communicate over IP with the external network.MESH_EVENT_ROOT_LOST_IP
: Indicates the root node has lost its IP address from the router. External IP communication is no longer possible.MESH_EVENT_CHILD_CONNECTED
/MESH_EVENT_CHILD_DISCONNECTED
: These events inform the root node (acting as a parent) when child nodes join or leave its direct supervision.MESH_EVENT_ROUTING_TABLE_ADD
/MESH_EVENT_ROUTING_TABLE_REMOVE
: These events indicate changes in the mesh routing table, which the root node uses to know about other nodes in the mesh.
Managing the Mesh Network as Root
As the primary parent (Layer 1), the root node:
- Accepts connections from new child nodes (up to
cfg.mesh_ap.max_connection
). - Participates in routing data up from its children and down to them.
- Can broadcast information to the entire mesh.
Root Node Election vs. Designated Root
ESP-MESH supports mechanisms for automatic root node election. If a designated root fails, other eligible nodes can “vote” to become the new root. This provides resilience. However, for many applications, especially during development and for simpler deployments, designating a specific ESP32 as the root node is common. This is achieved by providing it with the router credentials; other nodes are configured without router credentials and will thus seek to join an existing mesh as non-root nodes. This chapter focuses on the designated root scenario.
Data Flow Through the Root Node
- Mesh to External (Uplink):
- A child node sends data using
esp_mesh_send()
with theto
address asNULL
(or a zeroed MAC address), indicating the data is for the root. - The data propagates up the mesh tree to the root node.
- The root node receives this data using
esp_mesh_recv()
. - The root node’s application logic then processes this data and can forward it to the external network using standard TCP/IP socket APIs (e.g., send via MQTT, HTTP to a cloud server).
- A child node sends data using
- External to Mesh (Downlink):
- The root node receives data from the external network via its IP connection (e.g., an MQTT message, an HTTP request).
- The root node’s application logic determines the target node(s) within the mesh (e.g., based on MAC address or a logical group ID).
- The root node uses
esp_mesh_send()
with the specific child’s MAC address (or a broadcast/multicast address) to send the data into the mesh.
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans', 'primaryColor': '#DBEAFE', /* Light Blue for general process nodes */ 'primaryBorderColor': '#2563EB', 'primaryTextColor': '#1E40AF', 'lineColor': '#5B21B6', /* Purple for lines - will be overridden by linkStyle for specific links */ 'textColor': '#1F2937', /* Specific node type colors defined by classDef will take precedence for nodes they are applied to */ 'nodeTextColor': '#1F2937', 'mainBkg': '#FFFFFF' } } }%% graph TD subgraph ExternalNetwork [External Network] CLOUD[("Cloud Service /<br>External Server")]:::process end subgraph MeshNetwork [ESP-MESH Network] ROOT[("Root Node")]:::primary CHILD_A[("Child Node A")]:::process CHILD_B[("Child Node B")]:::process end %% Uplink: Child A to Cloud CHILD_A -- "1- esp_mesh_send(to_root=NULL, data)" --> ROOT ROOT -- "2- esp_mesh_recv()<br> Process data" --> ROOT_LOGIC{Application Logic<br>on Root}:::decision ROOT_LOGIC -- "3- Standard TCP/IP<br>(e.g., MQTT, HTTP)" --> CLOUD %% Downlink: Cloud to Child B CLOUD -- "4- Data via IP<br>(e.g., MQTT, HTTP)" --> ROOT_LOGIC ROOT_LOGIC -- "5- Determine Target Child B<br> esp_mesh_send(to=Child_B_MAC, data)" --> CHILD_B %% Styles for subgraphs style ExternalNetwork fill:#FFF9E6,stroke:#D97706 style MeshNetwork fill:#F0F9FF,stroke:#2563EB %% Link styles linkStyle 0 stroke:#1E40AF,stroke-width:2px; linkStyle 1 stroke:#1E40AF,stroke-width:2px; linkStyle 2 stroke:#059669,stroke-width:2px; linkStyle 3 stroke:#1E40AF,stroke-width:2px; linkStyle 4 stroke:#059669,stroke-width:2px; %% Class definitions for nodes classDef primary fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6 classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E
Practical Examples
This section provides a focused example of configuring an ESP32 as a designated root node.
1. Project Setup:
- Create a new ESP-IDF project (e.g.,
esp_mesh_root_example
). - Ensure
menuconfig
settings are configured for ESP-MESH as detailed in Chapter 46, Section 1 (“Project Setup”).
2. Mesh Configuration Defines:
Place these in your main.c or a shared header. These must be consistent with any child nodes you plan to connect.
// main.c (or a common_defs.h)
#define MESH_ROUTER_SSID "YOUR_ROUTER_SSID"
#define MESH_ROUTER_PASSWORD "YOUR_ROUTER_PASSWORD"
#define MESH_ID_BYTES {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x01} // Example Mesh ID
#define MESH_PASSWORD "MyMeshPass123"
#define MESH_CHANNEL 1 // Can be 0 for auto-select based on router, or 1-13
#define MESH_MAX_LITE_CONNECTIONS 1 // Max light sleep children (leaf nodes)
#define MESH_MAX_TOTAL_CONNECTIONS 6 // Max total children (including active parents)
#define MESH_MAX_LAYERS 6 // Default max layers
3. Root Node Main Implementation (main.c
):
// 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 = "MESH_ROOT";
// Use Mesh Configuration Defines from above
static bool s_is_mesh_connected = false; // Indicates connection to router
static bool s_got_ip_from_router = false;
static int s_mesh_layer = -1;
// Forward declaration
static void mesh_data_rx_task(void *pvParameters);
// Event handler for MESH and IP events
static void mesh_ip_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
if (event_base == MESH_EVENT) {
switch (event_id) {
case MESH_EVENT_STARTED: {
mesh_addr_t_ptr mesh_id = (mesh_addr_t_ptr)event_data;
ESP_LOGI(TAG, "MESH_EVENT_STARTED: <MESHID MAC:%02x:%02x:%02x:%02x:%02x:%02x>", MAC2STR(mesh_id->addr));
s_mesh_layer = esp_mesh_get_layer();
// Attempt to connect to the router (this is implicitly handled by esp_mesh_start if router config is set)
break;
}
case MESH_EVENT_STOPPED:
ESP_LOGI(TAG, "MESH_EVENT_STOPPED");
s_is_mesh_connected = false;
s_got_ip_from_router = false;
s_mesh_layer = esp_mesh_get_layer();
break;
case MESH_EVENT_PARENT_CONNECTED: { // For root, this means connected to the external router
mesh_event_parent_connected_t *parent_connected = (mesh_event_parent_connected_t *)event_data;
ESP_LOGI(TAG, "MESH_EVENT_PARENT_CONNECTED to BSSID:" MACSTR ", SSID:%s, Channel:%d",
MAC2STR(parent_connected->connected.bssid),
parent_connected->connected.ssid,
parent_connected->connected.channel);
s_is_mesh_connected = true;
s_mesh_layer = esp_mesh_get_layer(); // Should be 1
ESP_LOGI(TAG, "Root node layer: %d", s_mesh_layer);
// Now wait for MESH_EVENT_ROOT_GOT_IP
break;
}
case MESH_EVENT_PARENT_DISCONNECTED: { // For root, disconnected from the external router
mesh_event_parent_disconnected_t *parent_disconnected = (mesh_event_parent_disconnected_t *)event_data;
ESP_LOGW(TAG, "MESH_EVENT_PARENT_DISCONNECTED from router, reason:%d", parent_disconnected->reason);
s_is_mesh_connected = false;
s_got_ip_from_router = false;
// ESP-MESH will attempt to reconnect to the router automatically.
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, "MESH_EVENT_ROOT_GOT_IP: Interface \"%s\" address: " IPSTR,
esp_netif_get_ifkey(got_ip->netif), IP2STR(&got_ip->ip_info.ip));
s_got_ip_from_router = true;
ESP_LOGI(TAG, "Root Node is now fully operational with external IP connectivity.");
// Start RX task only after root is fully up
xTaskCreate(mesh_data_rx_task, "mesh_rx_task", 4096, NULL, 5, NULL);
break;
}
case MESH_EVENT_ROOT_LOST_IP:
ESP_LOGW(TAG, "MESH_EVENT_ROOT_LOST_IP: Lost IP from router.");
s_got_ip_from_router = false;
// Consider stopping tasks that rely on IP connectivity
break;
case MESH_EVENT_CHILD_CONNECTED: {
mesh_event_child_connected_t *child_connected = (mesh_event_child_connected_t *)event_data;
ESP_LOGI(TAG, "MESH_EVENT_CHILD_CONNECTED: MAC:" MACSTR ", 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, "MESH_EVENT_CHILD_DISCONNECTED: MAC:" MACSTR ", AID:%d",
MAC2STR(child_disconnected->mac), child_disconnected->aid);
break;
}
default:
ESP_LOGD(TAG, "Unhandled MESH_EVENT ID %ld", event_id);
break;
}
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
// This event is also posted by LwIP when STA (which root uses for router link) gets an IP.
// MESH_EVENT_ROOT_GOT_IP is the more specific mesh event to rely on for root's external IP.
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
ESP_LOGI(TAG, "IP_EVENT_STA_GOT_IP: " IPSTR " (This is also reflected by MESH_EVENT_ROOT_GOT_IP)",
IP2STR(&event->ip_info.ip));
} else {
ESP_LOGD(TAG, "Received unhandled event: base=%s, id=%ld", event_base, event_id);
}
}
// Task to receive data from child nodes
static void mesh_data_rx_task(void *pvParameters)
{
ESP_LOGI(TAG, "Mesh RX task started.");
esp_err_t err;
mesh_addr_t from_addr;
mesh_data_t data_rx;
int flags = 0; // Can be used to get flags like MESH_DATA_P2P, MESH_DATA_FROMDS etc.
data_rx.data = (uint8_t *)malloc(MESH_MTU_SIZE);
if (data_rx.data == NULL) {
ESP_LOGE(TAG, "RX Task: Failed to allocate memory for data buffer.");
vTaskDelete(NULL);
return;
}
while (s_got_ip_from_router) { // Only run if root is fully operational
data_rx.size = MESH_MTU_SIZE; // Reset size for next receive
err = esp_mesh_recv(&from_addr, &data_rx, pdMS_TO_TICKS(10000), &flags, NULL, 0);
if (err == ESP_OK) {
ESP_LOGI(TAG, "Received data from " MACSTR " (Size: %d): %.*s",
MAC2STR(from_addr.addr), data_rx.size, data_rx.size, (char *)data_rx.data);
// TODO: Process received data.
// Example: If data is JSON, parse it. If it's a command, act on it.
// Example: Forward to MQTT, HTTP, or another service.
// For now, just log it.
// Conceptual: Send an ACK back to the child (if protocol requires)
// char *ack_msg = "ACK_FROM_ROOT";
// mesh_data_t data_tx;
// data_tx.data = (uint8_t*)ack_msg;
// data_tx.size = strlen(ack_msg) + 1;
// data_tx.proto = MESH_PROTO_BIN;
// esp_mesh_send(&from_addr, &data_tx, MESH_DATA_RELIABLE, NULL, 0);
} else if (err == ESP_ERR_MESH_TIMEOUT || err == ESP_ERR_TIMEOUT) {
// ESP_LOGD(TAG, "Mesh RX timeout/no data.");
} else if (err == ESP_ERR_MESH_NOT_START || err == ESP_ERR_MESH_NOT_CONNECT) {
ESP_LOGW(TAG, "Mesh not started or not connected, stopping RX task for now.");
break; // Exit loop if mesh is down
}
else {
ESP_LOGE(TAG, "Error in esp_mesh_recv: %s (0x%x)", esp_err_to_name(err), err);
}
vTaskDelay(pdMS_TO_TICKS(100)); // Small delay
}
ESP_LOGI(TAG, "Mesh RX task finished.");
free(data_rx.data);
vTaskDelete(NULL);
}
void app_main(void)
{
// 1. 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);
// 2. Initialize TCP/IP stack and system event loop
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
// 3. Create default Wi-Fi STA netif (for root's connection to router)
// and AP netif (for mesh's internal AP functionality)
ESP_ERROR_CHECK(esp_netif_create_default_wifi_sta());
ESP_ERROR_CHECK(esp_netif_create_default_wifi_ap());
// 4. Initialize Wi-Fi
wifi_init_config_t wifi_init_cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_cfg));
// 5. Register event handlers
// Handle IP_EVENT_STA_GOT_IP for general IP acquisition (also covered by MESH_EVENT_ROOT_GOT_IP)
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&mesh_ip_event_handler,
NULL, NULL));
ESP_ERROR_CHECK(esp_event_handler_instance_register(MESH_EVENT,
ESP_EVENT_ANY_ID,
&mesh_ip_event_handler,
NULL, NULL));
// 6. Set Wi-Fi storage and mode (STA for root's upstream link)
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); // Root node uses STA to connect to router
// 7. Initialize ESP-MESH
ESP_ERROR_CHECK(esp_mesh_init());
// 8. Configure ESP-MESH
mesh_cfg_t cfg = ESP_MESH_DEFAULT_INIT();
// --- Mesh ID ---
uint8_t MESH_ID[6] = MESH_ID_BYTES;
memcpy((uint8_t *) &cfg.mesh_id, MESH_ID, 6);
// --- Router Configuration (for Root Node) ---
cfg.router.ssid_len = strlen(MESH_ROUTER_SSID);
memcpy((uint8_t *) &cfg.router.ssid, MESH_ROUTER_SSID, cfg.router.ssid_len);
memcpy((uint8_t *) &cfg.router.password, MESH_ROUTER_PASSWORD, strlen(MESH_ROUTER_PASSWORD));
// --- Mesh AP Configuration (for nodes connecting to this root/parent) ---
cfg.mesh_ap.max_connection = MESH_MAX_TOTAL_CONNECTIONS; // Max total children
memcpy((uint8_t *) &cfg.mesh_ap.auth, MESH_PASSWORD, strlen(MESH_PASSWORD));
cfg.mesh_ap.auth.authmode = WIFI_AUTH_WPA2_PSK; // WPA2-PSK for mesh internal links
// --- Mesh Channel ---
cfg.channel = MESH_CHANNEL; // Set a preferred channel, or 0 for auto
// --- Mesh Lite (Light Sleep Node) Configuration ---
// cfg.mesh_lcfg.max_inactive_time_ms = 600000; // 10 minutes
// cfg.mesh_lcfg.max_wakeups_per_min = 10;
// cfg.mesh_lcfg.num_connect_attempts = 3;
// cfg.mesh_lcfg.max_light_sleep_connections = MESH_MAX_LITE_CONNECTIONS;
ESP_ERROR_CHECK(esp_mesh_set_config(&cfg));
// 9. Start ESP-MESH
ESP_ERROR_CHECK(esp_mesh_start());
ESP_LOGI(TAG, "ESP-MESH Root Node initialized.");
// 10. Configure advanced mesh parameters (optional)
ESP_ERROR_CHECK(esp_mesh_set_max_layer(MESH_MAX_LAYERS));
ESP_ERROR_CHECK(esp_mesh_set_いえいえ_vote_percentage(1)); // Enable voting for root if current root is lost (1 means 100%)
ESP_ERROR_CHECK(esp_mesh_allow_channel_switch(true)); // Allow mesh to switch channel to match router
// Optional: Set this node as a preferred root (if multiple potential roots)
// esp_mesh_set_is_preferred_root(true);
ESP_LOGI(TAG, "Root node configuration complete. Waiting for router connection and child nodes...");
// The mesh_data_rx_task is started from MESH_EVENT_ROOT_GOT_IP handler
}
4. Build, Flash, and Observe:
- Update
MESH_ROUTER_SSID
,MESH_ROUTER_PASSWORD
,MESH_ID_BYTES
,MESH_PASSWORD
, andMESH_CHANNEL
in the code. - Build and flash this code to an ESP32 board.
- Open the serial monitor.
- You should observe logs indicating:
- Mesh initialization and start.
MESH_EVENT_PARENT_CONNECTED
when it connects to your router.MESH_EVENT_ROOT_GOT_IP
with the IP address assigned by your router.- The
mesh_data_rx_task
starting. MESH_EVENT_CHILD_CONNECTED
when child nodes (from Chapter 46 example or new ones) join.- Any data received from child nodes will be logged by
mesh_data_rx_task
.
Variant Notes
As discussed in Chapter 46, ESP-MESH is supported on most WiFi-enabled ESP32 variants:
- ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6.The role of a root node, particularly its connection to an external router and management of child connections, is handled consistently by the ESP-MESH stack across these variants.
- Resource Considerations for Root Node: The root node can be more resource-intensive than child nodes, especially in larger meshes, as it handles:
- The WiFi station connection to the external router.
- Acting as a WiFi AP for its direct children.
- Routing traffic for potentially many downstream nodes.
- Managing the IP connection and potentially application-level protocols (MQTT, HTTP client/server) for external communication.Variants with more RAM and processing power (e.g., dual-core ESP32, ESP32-S3) might be preferred for the root node role in demanding applications. However, all supported variants are capable of acting as root nodes in moderately sized meshes.
- ESP32-H2: Does not support WiFi and therefore cannot be part of an ESP-MESH network, nor act as a root node.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
Incorrect Router Credentials | Root node fails to connect to the router; no MESH_EVENT_PARENT_CONNECTED event. Log may show Wi-Fi connection errors (e.g., reason code 201 – AUTH_FAIL). |
Solution: Double-verify cfg.router.ssid and cfg.router.password . Check for case sensitivity, extra spaces, and special characters. Ensure the router is broadcasting its SSID if not connecting via BSSID. |
Root Node Fails to Get IP | Root connects to router (MESH_EVENT_PARENT_CONNECTED received), but no MESH_EVENT_ROOT_GOT_IP . Node cannot communicate with external network. |
Solution:
|
Mesh ID or Password Mismatch | Root node operates, but child nodes fail to connect to the mesh. Children might repeatedly try to join or form their own separate (unrooted) mesh. | Solution: Ensure cfg.mesh_id and cfg.mesh_ap.password are identical on the root node and all child nodes. Even a small difference will prevent them from joining the same mesh. |
Channel Configuration Issues | Root connects to router on channel X, but mesh is fixed to channel Y, and esp_mesh_allow_channel_switch(false) . Child nodes may not find/connect to the root. |
Solution:
|
Forgetting `esp_netif_create_default_wifi_ap()` | Root node might connect to the router, but child nodes cannot connect to the root as a mesh AP. Errors related to AP interface not available. | Solution: Ensure esp_netif_create_default_wifi_ap() is called during initialization, in addition to esp_netif_create_default_wifi_sta() . The root node needs both a STA interface (for router) and an AP interface (for mesh children). |
Blocking `mesh_data_rx_task` | Root node receives some data, then becomes unresponsive or stops receiving further data. Mesh performance degrades. Child nodes might disconnect. | Solution: Avoid long, blocking operations (e.g., lengthy computations, synchronous network requests, long delays) inside the mesh_data_rx_task or any mesh event handler. Offload extensive processing to a separate FreeRTOS task, possibly using a queue to pass data. |
Insufficient `max_connection` | Some child nodes are unable to connect to the root, even if they have correct credentials and are in range. Root logs might show connection rejections if verbose. | Solution: Increase cfg.mesh_ap.max_connection in the root node’s configuration to allow for the expected number of direct children. Remember this is for direct children only. |
Exercises
- Router Reconnection Robustness:
- Modify the
MESH_EVENT_PARENT_DISCONNECTED
handler in the root node. Implement a counter that attempts to callesp_wifi_connect()
(which triggers ESP-MESH to reconnect to the router) a few times with a delay (e.g., 5-10 seconds) if the disconnection was not intentional (e.g., reason notWIFI_REASON_ASSOC_LEAVE
). If it fails after several retries, log a persistent error.
- Modify the
- Root Node Status Web Page:
- On the root node, after it gets an IP (
MESH_EVENT_ROOT_GOT_IP
), start a simple HTTP server. - Create an HTTP endpoint (e.g.,
/mesh_status
) that, when accessed, returns a JSON or HTML page displaying:- Root node’s IP address.
- Current mesh layer (should be 1).
- Number of directly connected children (
esp_mesh_get_routing_table_size()
can give an idea, needs filtering for direct children). - MAC addresses of direct children.
- On the root node, after it gets an IP (
- Command Forwarding to a Specific Child:
- Set up the root node to receive a simple command via its serial console (e.g., “send_to_child <child_mac_hex_string> “).
- Parse this command. Construct a
mesh_data_t
packet with the<message>
. - Use
esp_mesh_send()
to send this packet specifically to the child MAC address provided. - The child node (from Chapter 46 or a new one) should be set up to receive and print this message.
Summary
- The root node is the gateway of an ESP-MESH network, connecting it to external IP networks via an upstream Wi-Fi router.
- Root node configuration involves setting router credentials (
cfg.router
) and mesh parameters (cfg.mesh_id
,cfg.mesh_ap.auth
). - Critical events for the root node include
MESH_EVENT_PARENT_CONNECTED
(to router),MESH_EVENT_ROOT_GOT_IP
(external IP acquired), and handling child connections. - Data from the mesh to external networks flows through the root, and vice-versa.
- While ESP-MESH supports automatic root election, configuring a designated root is common for controlled deployments.
- Robust root node operation is essential for the external connectivity of the entire mesh.
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 (
esp_mesh.h
): https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-reference/network/esp_mesh.html - ESP-IDF Wi-Fi Driver API Reference (
esp_wifi.h
): https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-reference/network/esp_wifi.html - ESP-IDF LwIP TCP/IP Stack and Netif Layer: For understanding IP networking aspects once the root has an IP.
