Chapter 48: Internal Node Management in Mesh Networks
Chapter Objectives
By the end of this chapter, you will be able to:
- Describe the roles and characteristics of internal nodes (child, parent, leaf) in an ESP-MESH network.
- Configure an ESP32 device to operate as an internal mesh node.
- Understand the process by which an internal node scans for and connects to a parent node.
- Explain how layers are formed and how an internal node determines its layer within the mesh.
- Implement data transmission from an internal node to the root node and conceptualize receiving data from the root.
- Handle mesh events pertinent to internal nodes, such as parent connection/disconnection and layer changes.
- Discuss the self-healing capabilities of internal nodes when a parent connection is lost.
- Briefly understand power-saving considerations for leaf nodes.
Introduction
In the preceding chapters, we established the foundation of ESP-MESH and delved into the critical role of the root node as the gateway to external networks. However, the true power and reach of a mesh network come from its internal nodes. These are the devices that form the backbone and periphery of the mesh, relaying data, extending coverage, and providing the distributed intelligence or sensing capabilities that many IoT applications require.
Internal nodes can act as simple children connecting to a parent, or they can themselves become parent nodes, supporting further layers of the mesh. Understanding how these nodes join the network, maintain their connections, route data, and adapt to changes is crucial for designing and deploying robust ESP-MESH solutions. This chapter will focus on the configuration, behavior, and management of these internal (non-root) nodes, exploring how they contribute to the overall resilience and functionality of the ESP-MESH.
Theory
Roles of Internal Nodes in ESP-MESH
Internal nodes are all nodes within the ESP-MESH that are not the root node. They can dynamically take on several roles:
- Child Node:
- Any node that is connected to a parent node (which could be the root or another internal node acting as a parent).
- Its primary role is to send its data towards the root (via its parent) or receive data from the root (via its parent).
- It relies on its parent for connectivity to the rest of the mesh and, indirectly, to any external network via the root.
- Parent Node (Non-Root):
- An internal node that has successfully connected to its own parent (upstream towards the root) AND also has one or more child nodes connected to it (downstream).
- These nodes are crucial for extending the mesh’s range and capacity. They act as relays, forwarding data up from their children towards the root and down from their parent (or root) to their children.
- The ESP-MESH stack automatically manages whether a child node can become a parent based on its configuration (
mesh_ap.max_connection
) and the needs of the network.
- Leaf Node (End Node):
- A child node that has no children of its own. These nodes are at the periphery of the mesh tree.
- They typically act as data sources (sensors) or sinks (actuators).
- Leaf nodes are prime candidates for power-saving optimizations, as they don’t have the responsibility of routing data for others. ESP-MESH supports a “light sleep” mode for leaf nodes.
- Idle Node:
- A node that is configured for the mesh but is not currently connected to any parent. It is actively scanning and attempting to join the mesh.
The transition between these roles (e.g., a child becoming a parent) is managed dynamically by the ESP-MESH protocol based on network conditions and node configurations.
Role | Description | Key Characteristics / Responsibilities |
---|---|---|
Child Node | A node connected to a parent node (either the root or another internal node). |
|
Parent Node (Non-Root) | An internal node that is connected to its own parent (upstream) and also has one or more child nodes connected to it (downstream). |
|
Leaf Node (End Node) | A child node that has no children of its own. Positioned at the periphery of the mesh. |
|
Idle Node | A node configured for the mesh but not currently connected to any parent. |
|
Joining the Mesh: The Connection Process
When an ESP32 configured as an internal mesh node starts, it undergoes the following process to join an existing mesh:
- Initialization: The node initializes its WiFi and ESP-MESH stack with the correct Mesh ID (MUID), Mesh Password, and preferred Mesh Channel (or scans all channels if channel is set to 0). It does not have router credentials configured.
- Scanning for Parents: The node scans for beacon frames from potential parent nodes that are part of the target mesh (matching MUID) and are accepting new connections.
- Parent Selection: The node evaluates potential parents based on criteria like:
- RSSI (Received Signal Strength Indicator): Stronger signal is preferred.
- Layer Number: Nodes prefer to connect to a parent that will place them at a lower (closer to root) and valid layer, respecting
MESH_MAX_LAYERS
. - Available Connection Slots: The parent must have capacity (not exceeding its
max_connection
limit).
- Authentication and Association: The node attempts to connect to the selected parent using the shared Mesh Password (WPA2-PSK).
- Connection Confirmation: If successful, the child node receives a
MESH_EVENT_PARENT_CONNECTED
event. Itsevent_data
(mesh_event_parent_connected_t
) includes the BSSID of its new parent and the parent’s SSID (which is derived from the MUID). - Layer Assignment: Upon connecting, the child node’s layer is set to
parent_layer + 1
. It will receive aMESH_EVENT_LAYER_CHANGE
event reflecting its new layer.
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans', 'textColor': '#1F2937', 'lineColor': '#A78BFA', 'primaryColor': '#DBEAFE', 'primaryBorderColor': '#2563EB', 'primaryTextColor': '#1E40AF', 'noteTextColor': '#1F2937', 'noteBkgColor': '#F8FAFC', 'noteBorderColor': '#CBD5E1' } } }%% sequenceDiagram actor INode as Internal Node (Idle) participant PParents as Potential Parents (Broadcasting) actor SParent as Selected Parent INode->>INode: 1. Initialize WiFi & ESP-MESH<br>(MUID, Password, Channel) activate INode INode->>PParents: 2. Scan for Mesh Beacons (Matching MUID) activate PParents PParents-->>INode: 3. Beacon Frames (RSSI, Layer, Capacity) deactivate PParents INode->>INode: 4. Evaluate Potential Parents<br>(RSSI, Layer, Slots) INode->>SParent: 5. Select Best Parent & Attempt Connection<br>(WPA2-PSK Auth with Mesh Password) activate SParent alt Connection Successful SParent-->>INode: 6. Association Successful Note over INode: MESH_EVENT_PARENT_CONNECTED<br>(Parent BSSID, SSID, Channel) Note over INode: MESH_EVENT_LAYER_CHANGE<br>(Layer = Parent Layer + 1) Note right of INode: Node is now a Child Node<br>in the mesh. else Connection Failed SParent-->>INode: Authentication/Association Failed INode->>INode: Retry or Select Another Parent end deactivate SParent deactivate INode
Layer Management

As discussed previously, ESP-MESH forms a tree structure organized into layers:
- The Root Node is Layer 1.
- Direct children of the Root are Layer 2.
- Children of Layer 2 nodes are Layer 3, and so on.
Internal nodes are always at Layer 2 or greater. The esp_mesh_get_layer()
function can be called by any node to determine its current layer in the mesh. The maximum number of layers is configurable (esp_mesh_set_max_layer()
), but deeper layers can lead to increased latency and reduced throughput.
Data Routing for Internal Nodes
Internal nodes primarily route data through their parent-child links:
- Sending Data to the Root (Uplink):
- An internal node uses
esp_mesh_send(NULL, &data, flags, NULL, 0)
to send data towards the root. Theto
address beingNULL
(or a zeroed MAC address) signifies the root as the destination. - The node transmits the packet to its parent. The parent, if not the root, forwards it to its own parent, and so on, until the packet reaches the root node.
- An internal node uses
- Receiving Data from the Root (Downlink):
- The root node sends data to a specific internal node using
esp_mesh_send(&child_mac_addr, &data, flags, NULL, 0)
. - The packet is routed down the mesh tree from parent to child until it reaches the target node. The target node receives it via
esp_mesh_recv()
.
- The root node sends data to a specific internal node using
- Peer-to-Peer Communication (Internal):
- Any node can theoretically send data to any other node within the same mesh if it knows the recipient’s MAC address. This is done using
esp_mesh_send(&peer_mac_addr, &data, flags, NULL, 0)
. - The ESP-MESH routing layer attempts to deliver the packet. This might involve the packet going up to a common ancestor and then down, or more direct paths if available and optimized by the routing algorithm. This is less common than root-centric communication for many IoT applications but is supported.
- Any node can theoretically send data to any other node within the same mesh if it knows the recipient’s MAC address. This is done using
- Broadcasts:
- The root node can broadcast data to all nodes in the mesh.
- Internal nodes can also initiate broadcasts, which will propagate throughout their connected segment of the mesh. The scope and efficiency depend on the implementation.
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans', 'textColor': '#1F2937', 'lineColor': '#A78BFA', 'primaryColor': '#DBEAFE', 'primaryBorderColor': '#2563EB', 'primaryTextColor': '#1E40AF', 'noteBkgColor': '#F8FAFC', 'noteBorderColor': '#CBD5E1' } } }%% graph TD subgraph MeshNetwork ["ESP-MESH Network"] RootNode[("Root Node (L1)")]:::primary ParentA[("Parent A (L2)")]:::process ChildB[("Child B (L3)<br><i>Src/Dest</i>")]:::process ParentC[("Parent C (L2)")]:::process ChildD[("Child D (L3)<br><i>Peer</i>")]:::process end subgraph External ["External Network / App"] ExtApp[("External App / Server")]:::success end %% Uplink: Child B to Root to External App ChildB -- "1- esp_mesh_send(to_root, data_b)" --> ParentA ParentA -- "2- Forward data_b" --> RootNode RootNode -- "3- Process & Send to Ext" --> ExtApp %% Downlink: External App to Root to Child B ExtApp -- "4- Data for Child B" --> RootNode RootNode -- "5- esp_mesh_send(to_child_b, data_ext)" --> ParentA ParentA -- "6- Forward data_ext" --> ChildB %% Peer-to-Peer: Child B to Child D ChildB -- "7- esp_mesh_send(to_child_d, data_p2p)" --> ParentA ParentA -- "8- Route via Common Ancestor (Root)" --> RootNode RootNode -- "9- Route Downstream" --> ParentC ParentC -- "10- Forward data_p2p" --> ChildD classDef primary fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6 classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46 classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF classDef check fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B %% Note subgraph note ["Note"] noteText["Peer-to-peer path can vary<br>based on mesh topology and routing."] style noteText fill:#F8FAFC,stroke:#CBD5E1,color:#1F2937 style note fill:#F8FAFC,stroke:#CBD5E1,color:#1F2937 end %% Link styles linkStyle 0 stroke:#059669,stroke-width:2px linkStyle 1 stroke:#059669,stroke-width:2px linkStyle 2 stroke:#059669,stroke-width:2px linkStyle 3 stroke:#2563EB,stroke-width:2px linkStyle 4 stroke:#2563EB,stroke-width:2px linkStyle 5 stroke:#2563EB,stroke-width:2px linkStyle 6 stroke:#D97706,stroke-width:2px linkStyle 7 stroke:#D97706,stroke-width:2px linkStyle 8 stroke:#D97706,stroke-width:2px linkStyle 9 stroke:#D97706,stroke-width:2px
Self-Healing: Handling Parent Loss
A key feature of ESP-MESH is its ability to self-heal. If an internal node loses connection to its parent (e.g., the parent node powers down or goes out of range):
- The child node will receive a
MESH_EVENT_PARENT_DISCONNECTED
event. Thereason
code in the event data provides information about why the disconnection occurred. - The ESP-MESH stack on the disconnected child node will automatically enter a scanning state, looking for a new suitable parent node within the same mesh.
- It will attempt to connect to a new parent based on the selection criteria (RSSI, layer, capacity).
- If successful, it re-establishes its connection, possibly at a different layer or through a different branch of the mesh tree.
%%{ init: { 'theme': 'base', 'fontFamily': 'Open Sans', 'themeVariables': { 'fontFamily': 'Open Sans', 'textColor': '#1F2937', 'lineColor': '#A78BFA', 'primaryColor': '#DBEAFE', 'primaryBorderColor': '#2563EB', 'primaryTextColor': '#1E40AF', 'startNodeFill': '#D1FAE5', 'startNodeStroke': '#059669', 'startNodeColor': '#065F46', /* Green for connected state */ 'checkNodeFill': '#FEE2E2', 'checkNodeStroke': '#DC2626', 'checkNodeColor': '#991B1B' /* Red for disconnected state */ } } }%% graph TD A["Connected to Parent P1<br>(Layer N)"]:::success --> B{Parent P1 Lost?}; B -- Yes --> C["<b>MESH_EVENT_PARENT_DISCONNECTED</b><br>(Reason Code)"]:::check; B -- No --> A; C --> D{Start Automatic Re-Parenting}; D --> E["Scan for New Potential Parents<br>(Matching MUID, Available Slots)"]; E --> F{New Parent Candidates Found?}; F -- Yes --> G["Evaluate Candidates<br>(RSSI, Layer, Capacity)"]; G --> H{Select Best New Parent P2}; H --> I[Attempt Connection to P2]; I -- Success --> J[<b>MESH_EVENT_PARENT_CONNECTED</b><br>to P2]:::success; J --> K[<b>MESH_EVENT_LAYER_CHANGE</b><br>New Layer = P2_Layer + 1]; K --> L["Node Reconnected to Mesh<br>via Parent P2 (Layer N+x)"]:::success; L --> A_NewState["Connected to Parent P2<br>(New Layer)"]:::success; A_NewState --> B_NewState{Parent P2 Lost?}; B_NewState -- No --> A_NewState; B_NewState -- Yes --> C; I -- Failure --> E; F -- No (Timeout/No suitable parents) --> E; classDef primary fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46; classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef check fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B; style A fill:#D1FAE5,stroke:#059669,color:#065F46; style L fill:#D1FAE5,stroke:#059669,color:#065F46; style J fill:#D1FAE5,stroke:#059669,color:#065F46; style A_NewState fill:#D1FAE5,stroke:#059669,color:#065F46; style C fill:#FEE2E2,stroke:#DC2626,color:#991B1B;
This automatic re-parenting mechanism makes the mesh resilient to individual node failures (except for the root node, whose failure impacts external connectivity for the whole mesh unless another node can take over as root).
Power Saving for Leaf Nodes
ESP-MESH supports power-saving modes, particularly beneficial for battery-operated leaf nodes.
- Light Sleep Mode: Leaf nodes can be configured to enter a light sleep mode, waking up periodically to listen for beacons from their parent (to check for pending downstream data) or to send their own data.
- The parent node buffers any downstream data intended for a sleeping child.
- Configuration involves parameters like
CONFIG_ESP_MESH_SUPPORT_PS_LEAF
(menuconfig), and potentially usingesp_mesh_set_light_sleep_duration()
or related APIs if available for fine-tuning. - Implementing effective power saving in a mesh requires careful consideration of the application’s latency requirements and data transmission patterns.
Note: Deep sleep for mesh nodes is more complex because the node loses its mesh context and needs to rejoin upon waking. Light sleep is the more common power-saving strategy within an active mesh connection.
Key Events for Internal Nodes
Besides the common MESH_EVENT_STARTED
and MESH_EVENT_STOPPED
:
MESH_EVENT ID | Description for Internal Node | Event Data Type (`event_data`) |
---|---|---|
MESH_EVENT_STARTED |
ESP-MESH has started. Node will begin scanning for parents if not configured as root. | mesh_addr_t_ptr (points to mesh ID) |
MESH_EVENT_STOPPED |
ESP-MESH has stopped. Disconnected from parent and mesh. | None |
MESH_EVENT_PARENT_CONNECTED |
Crucial: Successfully connected to a parent node and joined the mesh. | mesh_event_parent_connected_t * (parent BSSID, SSID, channel) |
MESH_EVENT_PARENT_DISCONNECTED |
Crucial: Lost connection to the current parent node. Node will attempt to re-parent. | mesh_event_parent_disconnected_t * (reason code) |
MESH_EVENT_LAYER_CHANGE |
The node’s layer in the mesh hierarchy has changed (e.g., after connecting to a new parent). | mesh_event_layer_change_t * (new layer) |
MESH_EVENT_ROOT_ADDRESS |
Received the MAC address of the current root node. | mesh_event_root_address_t * (root MAC address) |
MESH_EVENT_VOTE_STARTED |
A root node election process has started. Internal nodes may participate. | None |
MESH_EVENT_VOTE_STOPPED |
A root node election process has ended. | None |
MESH_EVENT_CHANNEL_SWITCH |
The mesh operating channel has changed (e.g., to align with root’s router). | mesh_event_channel_switch_t * (new channel) |
MESH_EVENT_CHILD_CONNECTED |
(If this internal node is acting as a parent) A new child has connected to this node. | mesh_event_child_connected_t * |
MESH_EVENT_CHILD_DISCONNECTED |
(If this internal node is acting as a parent) A child has disconnected from this node. | mesh_event_child_disconnected_t * |
Practical Examples
This example focuses on configuring an ESP32 as an internal mesh node that connects to an existing mesh (presumably one with a root node set up as in Chapter 47). It will then periodically send a message to the root.
1. Project Setup:
- Create a new ESP-IDF project (e.g.,
esp_mesh_internal_node
). - Ensure
menuconfig
settings are configured for ESP-MESH as detailed in Chapter 46, Section 1 (“Project Setup”).
2. Mesh Configuration Defines (Consistent with Root Node):
Place these in your main.c. They must match the configuration of your root node.
// main.c
#define MESH_ID_BYTES {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x01} // Must match root
#define MESH_PASSWORD "MyMeshPass123" // Must match root
#define MESH_CHANNEL 1 // Must match root's initial channel or use 0 for auto-scan
#define MESH_MAX_LAYERS 6 // Should match root's setting
// Router SSID/Password are NOT needed for internal nodes
// #define MESH_ROUTER_SSID "YOUR_ROUTER_SSID"
// #define MESH_ROUTER_PASSWORD "YOUR_ROUTER_PASSWORD"
3. Internal 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_INTERNAL_NODE";
// Use Mesh Configuration Defines from above
static bool s_is_mesh_parent_connected = false;
static int s_mesh_layer = -1;
static mesh_addr_t s_mesh_parent_bssid;
static mesh_addr_t s_route_table[CONFIG_MESH_ROUTE_TABLE_SIZE]; // Assuming default route table size
static int s_route_table_size = 0;
// Event handler for MESH events
static void mesh_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
if (event_base != MESH_EVENT) {
ESP_LOGD(TAG, "Ignoring event from base %s", event_base);
return;
}
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();
ESP_LOGI(TAG, "Initial layer: %d. Scanning for parent...", s_mesh_layer);
break;
}
case MESH_EVENT_STOPPED:
ESP_LOGI(TAG, "MESH_EVENT_STOPPED");
s_is_mesh_parent_connected = false;
s_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;
memcpy(&s_mesh_parent_bssid, &parent_connected->connected.bssid, sizeof(mesh_addr_t));
ESP_LOGI(TAG, "MESH_EVENT_PARENT_CONNECTED to Parent BSSID:" MACSTR ", SSID:%s, Channel:%d",
MAC2STR(s_mesh_parent_bssid.addr),
parent_connected->connected.ssid, // This SSID is derived from Mesh ID
parent_connected->connected.channel);
s_is_mesh_parent_connected = true;
s_mesh_layer = esp_mesh_get_layer(); // Update layer after connecting
ESP_LOGI(TAG, "Connected to parent. Current layer: %d", s_mesh_layer);
break;
}
case MESH_EVENT_PARENT_DISCONNECTED: {
mesh_event_parent_disconnected_t *parent_disconnected = (mesh_event_parent_disconnected_t *)event_data;
ESP_LOGW(TAG, "MESH_EVENT_PARENT_DISCONNECTED from Parent BSSID:" MACSTR ", reason:%d",
MAC2STR(s_mesh_parent_bssid.addr), parent_disconnected->reason);
s_is_mesh_parent_connected = false;
s_mesh_layer = esp_mesh_get_layer(); // Will likely be 0 or -1 (invalid)
ESP_LOGI(TAG, "Disconnected from parent. Current layer: %d. Will scan for new parent.", s_mesh_layer);
// ESP-MESH automatically tries to reconnect.
break;
}
case MESH_EVENT_LAYER_CHANGE: {
mesh_event_layer_change_t *layer_change = (mesh_event_layer_change_t *)event_data;
s_mesh_layer = layer_change->new_layer;
ESP_LOGI(TAG, "MESH_EVENT_LAYER_CHANGE: New layer: %d", s_mesh_layer);
break;
}
case MESH_EVENT_ROUTING_TABLE_ADD: {
mesh_event_routing_table_change_t *routing_table_change = (mesh_event_routing_table_change_t *)event_data;
ESP_LOGI(TAG, "MESH_EVENT_ROUTING_TABLE_ADD: %d new entries", routing_table_change->rt_size_change);
// Update local copy of routing table if needed for advanced logic
s_route_table_size = esp_mesh_get_routing_table_size();
if (s_route_table_size <= CONFIG_MESH_ROUTE_TABLE_SIZE) {
esp_mesh_get_routing_table(s_route_table, s_route_table_size * sizeof(mesh_addr_t), &s_route_table_size);
}
break;
}
case MESH_EVENT_ROUTING_TABLE_REMOVE: {
mesh_event_routing_table_change_t *routing_table_change = (mesh_event_routing_table_change_t *)event_data;
ESP_LOGI(TAG, "MESH_EVENT_ROUTING_TABLE_REMOVE: %d entries removed", routing_table_change->rt_size_change);
s_route_table_size = esp_mesh_get_routing_table_size();
if (s_route_table_size <= CONFIG_MESH_ROUTE_TABLE_SIZE) {
esp_mesh_get_routing_table(s_route_table, s_route_table_size * sizeof(mesh_addr_t), &s_route_table_size);
}
break;
}
case MESH_EVENT_ROOT_ADDRESS: {
mesh_event_root_address_t *root_address = (mesh_event_root_address_t *)event_data;
ESP_LOGI(TAG, "MESH_EVENT_ROOT_ADDRESS: Root MAC "MACSTR, MAC2STR(root_address->addr));
break;
}
default:
ESP_LOGD(TAG, "Unhandled MESH_EVENT ID %ld", event_id);
break;
}
}
// Task to send data to the root node periodically
static void mesh_data_tx_task(void *pvParameters)
{
ESP_LOGI(TAG, "Mesh TX task started.");
esp_err_t err;
mesh_data_t data_tx;
char message_buffer[50];
int count = 0;
data_tx.proto = MESH_PROTO_BIN; // Using binary protocol
data_tx.tos = MESH_TOS_P2P; // Type of Service: Peer-to-Peer (though sending to root)
while (1) {
vTaskDelay(pdMS_TO_TICKS(15000)); // Send data every 15 seconds
if (!s_is_mesh_parent_connected || esp_mesh_is_root()) { // Don't send if not connected or if this node became root
ESP_LOGD(TAG, "Not connected to parent or I am root. Skipping TX.");
continue;
}
snprintf(message_buffer, sizeof(message_buffer), "Hello Root, msg #%d from Layer %d", count++, s_mesh_layer);
data_tx.data = (uint8_t *)message_buffer;
data_tx.size = strlen(message_buffer) + 1; // Include null terminator
ESP_LOGI(TAG, "Sending data to root (Size: %u): %s", data_tx.size, (char *)data_tx.data);
// Send to root (to_addr = NULL) with MESH_DATA_RELIABLE flag for acknowledged delivery
err = esp_mesh_send(NULL, &data_tx, MESH_DATA_RELIABLE, NULL, 0);
if (err == ESP_OK) {
ESP_LOGI(TAG, "Data sent successfully to root.");
} else {
ESP_LOGE(TAG, "Failed to send data to root: %s (0x%x)", esp_err_to_name(err), err);
// If send fails, could be due to parent disconnect, mesh congestion, etc.
// The MESH_EVENT_PARENT_DISCONNECTED handler would address parent loss.
}
}
vTaskDelete(NULL); // Should not be reached
}
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 and AP netifs
// Even non-root nodes need these for the WiFi driver and mesh 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 MESH event handler
ESP_ERROR_CHECK(esp_event_handler_instance_register(MESH_EVENT,
ESP_EVENT_ANY_ID,
&mesh_event_handler,
NULL, NULL));
// 6. Set Wi-Fi storage (RAM for this example)
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
// Set mode to STA. Mesh stack will manage AP/STA roles for mesh links.
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
// 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; // Defined at the top
memcpy((uint8_t *) &cfg.mesh_id, MESH_ID, 6);
// --- Mesh AP Configuration (for this node if it becomes a parent) ---
cfg.mesh_ap.max_connection = CONFIG_ESP_MESH_MAX_CONN; // From menuconfig, or define
memcpy((uint8_t *) &cfg.mesh_ap.auth, MESH_PASSWORD, strlen(MESH_PASSWORD));
cfg.mesh_ap.auth.authmode = WIFI_AUTH_WPA2_PSK;
// --- Mesh Channel ---
cfg.channel = MESH_CHANNEL; // Should match root's preferred channel, or 0 for auto-scan
// --- Router Configuration (NOT SET for internal nodes) ---
// cfg.router.ssid_len = 0; // Ensure router config is not set for non-root nodes
// memset(&cfg.router, 0, sizeof(mesh_router_config_t)); // Or clear it explicitly
ESP_ERROR_CHECK(esp_mesh_set_config(&cfg));
// 9. Start ESP-MESH
ESP_ERROR_CHECK(esp_mesh_start());
ESP_LOGI(TAG, "ESP-MESH Internal Node initialized.");
// 10. Configure advanced mesh parameters (optional, should match root)
ESP_ERROR_CHECK(esp_mesh_set_max_layer(MESH_MAX_LAYERS));
ESP_ERROR_CHECK(esp_mesh_allow_channel_switch(true)); // Important for finding parent if channel differs
// Optional: If this node should primarily be a leaf, you can influence its behavior
// esp_mesh_set_type(MESH_LEAF_INITIATOR); // Example: try to be a leaf
// Or let the mesh decide based on connections.
// 11. Create task to send data
xTaskCreate(mesh_data_tx_task, "mesh_tx_task", 3072, NULL, 5, NULL);
ESP_LOGI(TAG, "Internal node configuration complete. Waiting to join mesh...");
}
4. Build, Flash, and Observe:
- Prerequisite: Have a root node (from Chapter 47 example) already running and connected to your router. Ensure its
MESH_ID_BYTES
,MESH_PASSWORD
, andMESH_CHANNEL
match what you configure for this internal node. - Build and flash the internal node code to a separate ESP32 board.
- Open its serial monitor.
- You should observe logs indicating:
- Mesh initialization and start.
- Scanning for a parent.
MESH_EVENT_PARENT_CONNECTED
with details of the parent it connected to (likely the root node if it’s the only other active node).- Its assigned mesh layer.
- Periodic messages being sent to the root.
- On the root node’s serial monitor, you should see
MESH_EVENT_CHILD_CONNECTED
and then the data messages arriving from this internal node.
Variant Notes
The behavior and configuration of internal ESP-MESH nodes are largely consistent across the WiFi-enabled ESP32 variants:
- ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6.The ESP-MESH stack handles the underlying WiFi operations for mesh connectivity, and the APIs for configuration, sending/receiving data, and event handling are standardized within ESP-IDF for these chips.
- Performance and Capacity:
- Single-core variants (ESP32-S2, ESP32-C3) might have slightly different performance characteristics (e.g., latency, maximum practical children if acting as a parent) compared to dual-core variants (ESP32, ESP32-S3) under heavy load, but they are fully capable of participating as internal nodes.
- Available RAM can influence how many routing table entries or data buffers a node can handle, potentially affecting its role in very large or dense networks.
- Power Consumption: For battery-powered internal nodes, especially leaf nodes, the specific power consumption characteristics in light sleep mode might vary slightly between chip variants. Refer to Espressif’s datasheets and power consumption documentation for details.
- ESP32-H2: Does not support WiFi and therefore cannot be an ESP-MESH internal node.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
Fails to Join Mesh (No MESH_EVENT_PARENT_CONNECTED ) |
Node remains idle, repeatedly scans, or logs connection failures. Does not get assigned a layer or parent BSSID. | Solution:
|
Frequent Parent Disconnections (MESH_EVENT_PARENT_DISCONNECTED ) |
Node connects, then disconnects and reconnects frequently. Unstable data transmission. Layer might change often. | Solution:
|
Data Not Reaching Root (or other nodes) | esp_mesh_send() might return errors, or data is sent without error but never received at the destination. |
Solution:
|
Incorrect Layer Assignment or Unexpected Role | Node connects at a much higher layer than expected, or a node intended as a leaf becomes a parent. | Solution:
|
Ignoring Critical Mesh Events | Application logic doesn’t adapt to changes in mesh status (e.g., keeps trying to send data when disconnected from parent). | Solution: Implement a comprehensive mesh event handler. Use flags (like s_is_mesh_parent_connected ) to track current status and make decisions in other tasks (e.g., pause data transmission if not connected). Log layer changes and disconnections for debugging. |
Exercises
- Leaf Node Configuration and Data Sending:
- Modify the internal node example. After successfully connecting to a parent, explicitly try to set its type to a leaf node if it’s not already one (e.g.,
esp_mesh_set_type(MESH_LEAF)
or by ensuring it doesn’t accept children). - The node should still send data to the root. Observe if its behavior changes regarding accepting child connections (it shouldn’t accept any if it’s a true leaf).
- Modify the internal node example. After successfully connecting to a parent, explicitly try to set its type to a leaf node if it’s not already one (e.g.,
- Parent Loss and Re-Parenting Test:
- Set up a root node and at least two internal nodes (Node A, Node B).
- Configure Node B to initially connect to Node A, and Node A to connect to the Root.
- Once the mesh is stable, power off or reset Node A.
- Observe Node B’s serial monitor. It should log
MESH_EVENT_PARENT_DISCONNECTED
(from Node A) and then, after some time,MESH_EVENT_PARENT_CONNECTED
as it (hopefully) re-parents directly to the Root node. Log the new parent’s BSSID and the node’s new layer.
- Bi-Directional Communication with Root:
- In the internal node example, implement
esp_mesh_recv()
in a separate task to listen for messages. - Modify the root node (from Chapter 47) to send a specific acknowledgment message back to the internal node’s MAC address after successfully receiving a data packet from it.
- The internal node should log the ACK received from the root.
- In the internal node example, implement
Summary
- Internal ESP-MESH nodes (child, parent, leaf) form the core structure of the mesh, extending its reach and resilience.
- They connect to parent nodes using a shared Mesh ID and password, forming hierarchical layers.
- Data from internal nodes is typically routed towards the root for external access or processing.
- ESP-MESH provides self-healing capabilities, allowing internal nodes to find new parents if their current one is lost.
- Key events like
MESH_EVENT_PARENT_CONNECTED
,MESH_EVENT_PARENT_DISCONNECTED
, andMESH_EVENT_LAYER_CHANGE
are vital for managing an internal node’s state. - Leaf nodes can be optimized for power saving.
- Consistent configuration across all nodes and proper event handling are crucial for a stable internal mesh operation.
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-MESH Examples in ESP-IDF:
$IDF_PATH/examples/mesh/
(e.g.,internal_communication
,manual_networking
).