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:

  1. 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.
  2. 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.).
  3. 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).
  4. 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): This mesh_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 within mesh_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] within mesh_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 using esp_mesh_set_type(MESH_ROOT). However, ESP-MESH typically determines the root node automatically if cfg.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:

  1. The application initializes WiFi in WIFI_MODE_STA.
  2. The mesh_cfg_t structure is populated with the router’s SSID and password.
  3. esp_mesh_set_config(&cfg) is called.
  4. 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 in cfg.router. The event_data will be of type mesh_event_parent_connected_t.
  • MESH_EVENT_PARENT_DISCONNECTED: For the root node, this means it has lost connection to the external router. The event_data (mesh_event_parent_disconnected_t) includes a reason 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. The event_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):
    1. A child node sends data using esp_mesh_send() with the to address as NULL (or a zeroed MAC address), indicating the data is for the root.
    2. The data propagates up the mesh tree to the root node.
    3. The root node receives this data using esp_mesh_recv().
    4. 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).
  • External to Mesh (Downlink):
    1. The root node receives data from the external network via its IP connection (e.g., an MQTT message, an HTTP request).
    2. 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).
    3. 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.

C
// 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):

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, and MESH_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:
  • Ensure DHCP server is enabled on the router and has available IP leases.
  • Check router logs for any issues related to the ESP32’s MAC address.
  • Temporarily disable MAC filtering or other security policies on the router for testing.
  • Verify the ESP32’s antenna connection if signal strength is very low.
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:
  • Set cfg.channel = 0 in the root’s mesh config to adapt to the router’s channel. The mesh will then operate on this channel.
  • Alternatively, ensure esp_mesh_allow_channel_switch(true) is called (default is often true).
  • If fixing the channel, ensure it’s a valid, low-interference channel and all nodes are configured to it.
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

  1. Router Reconnection Robustness:
    • Modify the MESH_EVENT_PARENT_DISCONNECTED handler in the root node. Implement a counter that attempts to call esp_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 not WIFI_REASON_ASSOC_LEAVE). If it fails after several retries, log a persistent error.
  2. 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.
  3. 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

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top