999  Zigbee Lab: Mesh Network Simulator

999.1 Learning Objectives

By the end of this chapter, you will be able to:

  • Build Zigbee Mesh Networks: Implement coordinator, router, and end device roles in simulation
  • Understand AODV Routing: Visualize route discovery and multi-hop message delivery
  • Optimize Network Topology: Experiment with node placement and network density
  • Test Fault Tolerance: Simulate node failures and observe self-healing mechanisms
  • Analyze Power Consumption: Compare battery life strategies for end devices

What is this chapter? Browser-based Wokwi simulation lab for hands-on Zigbee mesh network experimentation without physical hardware.

When to use: - To visualize Zigbee routing and mesh behavior - For experimenting with network topology designs - To understand self-healing and fault tolerance

Key Topics:

Topic Focus
Device Roles Coordinator, Router, End Device
AODV Routing Route discovery visualization
Mesh Topology Multi-hop network formation
Fault Tolerance Self-healing mechanisms
Power Management Battery optimization strategies

Prerequisites: - Zigbee Fundamentals - Basic understanding of mesh routing - No physical hardware required

999.2 Prerequisites

Before diving into this chapter, you should be familiar with:

999.3 What You Will Learn

In this hands-on lab, you will explore:

  1. Zigbee Device Roles: How Coordinators, Routers, and End Devices interact in a mesh network
  2. Network Formation: The process of forming a PAN and devices joining the network
  3. Message Routing: How messages hop through routers to reach their destination
  4. Self-Healing Mesh: How the network automatically reroutes when a node fails
  5. Hop Counting: Understanding route cost and path optimization
  6. Sleep Modes: How battery-powered end devices conserve energy

999.4 Interactive Wokwi Simulation

This simulation uses an ESP32 to model Zigbee mesh networking concepts. While ESP32 doesn’t natively support Zigbee (it uses Wi-Fi and Bluetooth), this simulation models Zigbee mesh behavior to help you understand the protocol’s key features.

Press the Play button to start the simulation and observe the network behavior in the Serial Monitor.

Copy and paste this code into the Wokwi editor to run the Zigbee mesh network simulation:

/**
 * Zigbee Mesh Network Simulator for ESP32
 * ========================================
 *
 * This simulation demonstrates core Zigbee mesh networking concepts:
 * - Network formation with Coordinator, Routers, and End Devices
 * - AODV-style route discovery and message routing
 * - Self-healing mesh behavior when nodes fail
 * - Hop counting and path optimization
 * - Battery management for sleepy end devices
 *
 * Educational Purpose: Understand Zigbee mesh principles without
 * requiring actual Zigbee hardware.
 *
 * Author: IoT Class Educational Simulation
 * License: MIT
 */

#include <Arduino.h>
#include <vector>
#include <map>
#include <queue>

// =============================================================================
// CONFIGURATION CONSTANTS
// =============================================================================

#define MAX_NODES 12
#define MAX_NEIGHBORS 6
#define MAX_ROUTING_TABLE_SIZE 20
#define PAN_ID 0x1234
#define ZIGBEE_CHANNEL 25
#define NETWORK_KEY "ZigBeeAlliance09"
#define MAX_HOPS 7
#define ROUTE_DISCOVERY_TIMEOUT 3000
#define LINK_QUALITY_THRESHOLD 100
#define BATTERY_DRAIN_RATE 0.01
#define SLEEP_DURATION_MS 5000
#define SIMULATION_TICK_MS 100

// =============================================================================
// ZIGBEE DATA STRUCTURES
// =============================================================================

/**
 * Device types in a Zigbee network
 */
enum DeviceType {
    COORDINATOR,  // Network founder, Trust Center, always on
    ROUTER,       // Full-function device, routes messages, mains-powered
    END_DEVICE    // Reduced-function device, can sleep, battery-powered
};

/**
 * Device states for simulation
 */
enum DeviceState {
    STATE_INIT,
    STATE_SCANNING,
    STATE_JOINING,
    STATE_JOINED,
    STATE_SLEEPING,
    STATE_TRANSMITTING,
    STATE_FAILED
};

/**
 * Message types for Zigbee-like communication
 */
enum MessageType {
    MSG_BEACON,           // Network announcement
    MSG_JOIN_REQUEST,     // Device wants to join
    MSG_JOIN_RESPONSE,    // Join accepted/rejected
    MSG_ROUTE_REQUEST,    // RREQ in AODV
    MSG_ROUTE_REPLY,      // RREP in AODV
    MSG_DATA,             // Application data
    MSG_ACK,              // Acknowledgment
    MSG_NETWORK_STATUS    // Link status update
};

/**
 * Routing table entry
 */
struct RouteEntry {
    uint16_t destination;
    uint16_t nextHop;
    uint8_t hopCount;
    uint8_t linkQuality;
    unsigned long timestamp;
    bool active;
};

/**
 * Network message structure
 */
struct ZigbeeMessage {
    MessageType type;
    uint16_t source;
    uint16_t destination;
    uint16_t via;           // For multi-hop routing
    uint8_t seqNum;
    uint8_t hopCount;
    uint8_t maxHops;
    String payload;
    unsigned long timestamp;
};

/**
 * Neighbor table entry
 */
struct Neighbor {
    uint16_t address;
    DeviceType type;
    uint8_t linkQuality;    // 0-255, higher is better
    int8_t rssi;            // Signal strength in dBm
    bool canRoute;
    unsigned long lastSeen;
};

/**
 * Simulated Zigbee device
 */
struct ZigbeeDevice {
    uint16_t networkAddress;
    uint64_t ieeeAddress;
    DeviceType type;
    DeviceState state;
    uint16_t parentAddress;
    float batteryLevel;      // 0.0 - 100.0
    uint8_t txPower;         // Transmission power level

    std::vector<Neighbor> neighbors;
    std::vector<RouteEntry> routingTable;
    std::queue<ZigbeeMessage> messageQueue;

    unsigned long lastActivity;
    unsigned long sleepUntil;
    uint32_t messagesSent;
    uint32_t messagesReceived;
    uint32_t messagesRouted;

    // Position for simulation (grid-based)
    int posX;
    int posY;
};

// =============================================================================
// GLOBAL VARIABLES
// =============================================================================

ZigbeeDevice devices[MAX_NODES];
int deviceCount = 0;
uint8_t globalSeqNum = 0;
unsigned long simulationTime = 0;
bool networkFormed = false;
int failedNodeIndex = -1;

// Statistics
uint32_t totalMessagesSent = 0;
uint32_t totalMessagesDelivered = 0;
uint32_t totalRoutingDiscoveries = 0;
uint32_t totalSelfHealingEvents = 0;

// =============================================================================
// UTILITY FUNCTIONS
// =============================================================================

/**
 * Generate IEEE 802.15.4 extended address
 */
uint64_t generateIEEEAddress(int index) {
    return 0x00158D0001000000ULL + index;
}

/**
 * Convert device type to string
 */
const char* deviceTypeToString(DeviceType type) {
    switch(type) {
        case COORDINATOR: return "Coordinator";
        case ROUTER: return "Router";
        case END_DEVICE: return "EndDevice";
        default: return "Unknown";
    }
}

/**
 * Convert device state to string
 */
const char* deviceStateToString(DeviceState state) {
    switch(state) {
        case STATE_INIT: return "Init";
        case STATE_SCANNING: return "Scanning";
        case STATE_JOINING: return "Joining";
        case STATE_JOINED: return "Joined";
        case STATE_SLEEPING: return "Sleeping";
        case STATE_TRANSMITTING: return "Transmitting";
        case STATE_FAILED: return "Failed";
        default: return "Unknown";
    }
}

/**
 * Calculate simulated link quality based on distance
 */
uint8_t calculateLinkQuality(ZigbeeDevice* a, ZigbeeDevice* b) {
    int dx = a->posX - b->posX;
    int dy = a->posY - b->posY;
    float distance = sqrt(dx*dx + dy*dy);

    // Zigbee indoor range ~10-15 meters, grid units = 3 meters
    // LQI decreases with distance: 255 at 0m, ~50 at max range
    float maxRange = 5.0;  // Grid units
    if (distance > maxRange) return 0;

    uint8_t lqi = (uint8_t)(255 * (1.0 - (distance / (maxRange * 1.2))));
    // Add some randomness to simulate real-world conditions
    lqi = max(0, min(255, lqi + random(-15, 15)));
    return lqi;
}

/**
 * Calculate RSSI from link quality
 */
int8_t lqiToRssi(uint8_t lqi) {
    // RSSI typically ranges from -100 dBm (weak) to -20 dBm (strong)
    return map(lqi, 0, 255, -95, -25);
}

/**
 * Check if two devices can communicate directly
 */
bool canCommunicate(ZigbeeDevice* a, ZigbeeDevice* b) {
    if (a->state == STATE_FAILED || b->state == STATE_FAILED) return false;
    if (a->state == STATE_SLEEPING || b->state == STATE_SLEEPING) return false;
    return calculateLinkQuality(a, b) >= LINK_QUALITY_THRESHOLD;
}

/**
 * Print separator line
 */
void printSeparator(char c = '=', int len = 70) {
    for (int i = 0; i < len; i++) Serial.print(c);
    Serial.println();
}

/**
 * Print hex address
 */
void printAddress(uint16_t addr) {
    if (addr < 0x1000) Serial.print("0");
    if (addr < 0x100) Serial.print("0");
    if (addr < 0x10) Serial.print("0");
    Serial.print(addr, HEX);
}

// =============================================================================
// NETWORK INITIALIZATION
// =============================================================================

/**
 * Create the network topology
 *
 * Grid Layout (5x3):
 *     0   1   2   3   4   (X)
 * 0  [C] [R] [ ] [R] [E]
 * 1  [R] [E] [R] [E] [R]
 * 2  [E] [R] [E] [ ] [E]
 * (Y)
 *
 * C = Coordinator (0x0000)
 * R = Router (mains-powered)
 * E = End Device (battery-powered)
 */
void initializeNetwork() {
    Serial.println("\n========================================");
    Serial.println("  ZIGBEE MESH NETWORK INITIALIZATION");
    Serial.println("========================================\n");

    Serial.print("PAN ID: 0x");
    Serial.println(PAN_ID, HEX);
    Serial.print("Channel: ");
    Serial.println(ZIGBEE_CHANNEL);
    Serial.println();

    deviceCount = 0;

    // Define network topology with positions
    // Format: {type, posX, posY}
    struct DeviceConfig {
        DeviceType type;
        int x;
        int y;
    };

    DeviceConfig config[] = {
        {COORDINATOR, 0, 0},  // Central coordinator
        {ROUTER, 1, 0},       // Router R1
        {ROUTER, 3, 0},       // Router R2
        {ROUTER, 0, 1},       // Router R3
        {ROUTER, 2, 1},       // Router R4
        {ROUTER, 4, 1},       // Router R5
        {ROUTER, 1, 2},       // Router R6
        {END_DEVICE, 4, 0},   // End Device E1
        {END_DEVICE, 1, 1},   // End Device E2
        {END_DEVICE, 3, 1},   // End Device E3
        {END_DEVICE, 0, 2},   // End Device E4
        {END_DEVICE, 2, 2}    // End Device E5
    };

    int numDevices = sizeof(config) / sizeof(config[0]);

    for (int i = 0; i < numDevices && i < MAX_NODES; i++) {
        devices[i].networkAddress = i;  // Simplified addressing
        devices[i].ieeeAddress = generateIEEEAddress(i);
        devices[i].type = config[i].type;
        devices[i].state = STATE_INIT;
        devices[i].parentAddress = 0xFFFF;
        devices[i].batteryLevel = (config[i].type == END_DEVICE) ? 100.0 : -1.0;
        devices[i].txPower = 4;
        devices[i].posX = config[i].x;
        devices[i].posY = config[i].y;
        devices[i].lastActivity = 0;
        devices[i].sleepUntil = 0;
        devices[i].messagesSent = 0;
        devices[i].messagesReceived = 0;
        devices[i].messagesRouted = 0;
        devices[i].neighbors.clear();
        devices[i].routingTable.clear();

        deviceCount++;

        Serial.print("Created device 0x");
        printAddress(devices[i].networkAddress);
        Serial.print(" [");
        Serial.print(deviceTypeToString(devices[i].type));
        Serial.print("] at position (");
        Serial.print(devices[i].posX);
        Serial.print(", ");
        Serial.print(devices[i].posY);
        Serial.println(")");
    }

    Serial.println();
    Serial.print("Total devices created: ");
    Serial.println(deviceCount);
}

// =============================================================================
// NEIGHBOR DISCOVERY
// =============================================================================

/**
 * Discover neighbors for all devices
 * Simulates the beacon scanning phase of network formation
 */
void discoverNeighbors() {
    Serial.println("\n========================================");
    Serial.println("  NEIGHBOR DISCOVERY PHASE");
    Serial.println("========================================\n");

    for (int i = 0; i < deviceCount; i++) {
        devices[i].neighbors.clear();

        for (int j = 0; j < deviceCount; j++) {
            if (i == j) continue;

            uint8_t lqi = calculateLinkQuality(&devices[i], &devices[j]);

            if (lqi >= LINK_QUALITY_THRESHOLD) {
                Neighbor neighbor;
                neighbor.address = devices[j].networkAddress;
                neighbor.type = devices[j].type;
                neighbor.linkQuality = lqi;
                neighbor.rssi = lqiToRssi(lqi);
                neighbor.canRoute = (devices[j].type != END_DEVICE);
                neighbor.lastSeen = millis();

                devices[i].neighbors.push_back(neighbor);
            }
        }

        Serial.print("Device 0x");
        printAddress(devices[i].networkAddress);
        Serial.print(" discovered ");
        Serial.print(devices[i].neighbors.size());
        Serial.print(" neighbors: ");

        for (size_t n = 0; n < devices[i].neighbors.size(); n++) {
            Serial.print("0x");
            printAddress(devices[i].neighbors[n].address);
            Serial.print("(LQI:");
            Serial.print(devices[i].neighbors[n].linkQuality);
            Serial.print(") ");
        }
        Serial.println();
    }
}

// =============================================================================
// NETWORK FORMATION
// =============================================================================

/**
 * Simulate network formation process
 * 1. Coordinator starts network
 * 2. Routers join and become part of mesh
 * 3. End devices join via nearest router
 */
void formNetwork() {
    Serial.println("\n========================================");
    Serial.println("  NETWORK FORMATION");
    Serial.println("========================================\n");

    // Step 1: Coordinator forms the network
    Serial.println("[Phase 1] Coordinator forming network...");
    devices[0].state = STATE_JOINED;
    devices[0].parentAddress = 0x0000;  // Self
    Serial.print("  Coordinator 0x0000 started PAN 0x");
    Serial.print(PAN_ID, HEX);
    Serial.print(" on channel ");
    Serial.println(ZIGBEE_CHANNEL);
    Serial.println("  Trust Center initialized, network key distributed");
    delay(300);

    // Step 2: Routers join in order of proximity to coordinator
    Serial.println("\n[Phase 2] Routers joining network...");

    for (int i = 1; i < deviceCount; i++) {
        if (devices[i].type != ROUTER) continue;

        devices[i].state = STATE_SCANNING;
        delay(100);

        // Find best parent (joined device with best LQI that can route)
        uint16_t bestParent = 0xFFFF;
        uint8_t bestLQI = 0;

        for (size_t n = 0; n < devices[i].neighbors.size(); n++) {
            Neighbor& neighbor = devices[i].neighbors[n];
            int neighborIdx = neighbor.address;

            if (devices[neighborIdx].state == STATE_JOINED &&
                devices[neighborIdx].type != END_DEVICE &&
                neighbor.linkQuality > bestLQI) {
                bestParent = neighbor.address;
                bestLQI = neighbor.linkQuality;
            }
        }

        if (bestParent != 0xFFFF) {
            devices[i].state = STATE_JOINING;
            delay(100);
            devices[i].state = STATE_JOINED;
            devices[i].parentAddress = bestParent;

            Serial.print("  Router 0x");
            printAddress(devices[i].networkAddress);
            Serial.print(" joined via parent 0x");
            printAddress(bestParent);
            Serial.print(" (LQI: ");
            Serial.print(bestLQI);
            Serial.println(")");
        }
        delay(200);
    }

    // Step 3: End devices join
    Serial.println("\n[Phase 3] End devices joining network...");

    for (int i = 1; i < deviceCount; i++) {
        if (devices[i].type != END_DEVICE) continue;

        devices[i].state = STATE_SCANNING;
        delay(100);

        // End devices prefer routers as parents (for polling)
        uint16_t bestParent = 0xFFFF;
        uint8_t bestLQI = 0;

        for (size_t n = 0; n < devices[i].neighbors.size(); n++) {
            Neighbor& neighbor = devices[i].neighbors[n];
            int neighborIdx = neighbor.address;

            if (devices[neighborIdx].state == STATE_JOINED &&
                neighbor.canRoute &&
                neighbor.linkQuality > bestLQI) {
                bestParent = neighbor.address;
                bestLQI = neighbor.linkQuality;
            }
        }

        if (bestParent != 0xFFFF) {
            devices[i].state = STATE_JOINED;
            devices[i].parentAddress = bestParent;

            Serial.print("  EndDevice 0x");
            printAddress(devices[i].networkAddress);
            Serial.print(" joined via parent 0x");
            printAddress(bestParent);
            Serial.print(" (LQI: ");
            Serial.print(bestLQI);
            Serial.print(", Battery: ");
            Serial.print((int)devices[i].batteryLevel);
            Serial.println("%)");
        }
        delay(150);
    }

    networkFormed = true;
    Serial.println("\n[Complete] Network formation successful!");
}

// =============================================================================
// ROUTING TABLE MANAGEMENT
// =============================================================================

/**
 * Add or update routing table entry
 */
void updateRouteTable(ZigbeeDevice* device, uint16_t dest, uint16_t nextHop, uint8_t hops, uint8_t lqi) {
    // Check if route exists
    for (size_t i = 0; i < device->routingTable.size(); i++) {
        if (device->routingTable[i].destination == dest) {
            // Update if better route
            if (hops < device->routingTable[i].hopCount ||
                (hops == device->routingTable[i].hopCount && lqi > device->routingTable[i].linkQuality)) {
                device->routingTable[i].nextHop = nextHop;
                device->routingTable[i].hopCount = hops;
                device->routingTable[i].linkQuality = lqi;
                device->routingTable[i].timestamp = millis();
                device->routingTable[i].active = true;
            }
            return;
        }
    }

    // Add new route
    if (device->routingTable.size() < MAX_ROUTING_TABLE_SIZE) {
        RouteEntry entry;
        entry.destination = dest;
        entry.nextHop = nextHop;
        entry.hopCount = hops;
        entry.linkQuality = lqi;
        entry.timestamp = millis();
        entry.active = true;
        device->routingTable.push_back(entry);
    }
}

/**
 * Find route to destination
 * Returns next hop address or 0xFFFF if no route
 */
uint16_t findRoute(ZigbeeDevice* device, uint16_t destination) {
    // Direct neighbor?
    for (size_t i = 0; i < device->neighbors.size(); i++) {
        if (device->neighbors[i].address == destination &&
            device->neighbors[i].linkQuality >= LINK_QUALITY_THRESHOLD) {
            return destination;
        }
    }

    // Check routing table
    for (size_t i = 0; i < device->routingTable.size(); i++) {
        if (device->routingTable[i].destination == destination &&
            device->routingTable[i].active) {
            return device->routingTable[i].nextHop;
        }
    }

    return 0xFFFF;  // No route found
}

/**
 * Build initial routing tables using neighbor information
 */
void buildRoutingTables() {
    Serial.println("\n========================================");
    Serial.println("  BUILDING ROUTING TABLES");
    Serial.println("========================================\n");

    // Simple routing: each device routes through neighbors
    // In real Zigbee, AODV discovers routes on-demand

    for (int i = 0; i < deviceCount; i++) {
        if (devices[i].type == END_DEVICE) continue;  // End devices don't route

        devices[i].routingTable.clear();

        // Add direct routes to neighbors
        for (size_t n = 0; n < devices[i].neighbors.size(); n++) {
            Neighbor& neighbor = devices[i].neighbors[n];
            updateRouteTable(&devices[i], neighbor.address, neighbor.address, 1, neighbor.linkQuality);
        }

        // Add routes via neighbors (2-hop)
        for (size_t n = 0; n < devices[i].neighbors.size(); n++) {
            Neighbor& neighbor = devices[i].neighbors[n];
            int neighborIdx = neighbor.address;

            if (devices[neighborIdx].type == END_DEVICE) continue;

            for (size_t m = 0; m < devices[neighborIdx].neighbors.size(); m++) {
                uint16_t remoteAddr = devices[neighborIdx].neighbors[m].address;

                // Don't add route to self or direct neighbors
                if (remoteAddr == devices[i].networkAddress) continue;

                bool isDirect = false;
                for (size_t d = 0; d < devices[i].neighbors.size(); d++) {
                    if (devices[i].neighbors[d].address == remoteAddr) {
                        isDirect = true;
                        break;
                    }
                }

                if (!isDirect) {
                    uint8_t combinedLQI = min(neighbor.linkQuality,
                                             devices[neighborIdx].neighbors[m].linkQuality);
                    updateRouteTable(&devices[i], remoteAddr, neighbor.address, 2, combinedLQI);
                }
            }
        }

        Serial.print("Device 0x");
        printAddress(devices[i].networkAddress);
        Serial.print(" routing table (");
        Serial.print(devices[i].routingTable.size());
        Serial.println(" entries):");

        for (size_t r = 0; r < devices[i].routingTable.size(); r++) {
            Serial.print("  -> 0x");
            printAddress(devices[i].routingTable[r].destination);
            Serial.print(" via 0x");
            printAddress(devices[i].routingTable[r].nextHop);
            Serial.print(" (");
            Serial.print(devices[i].routingTable[r].hopCount);
            Serial.print(" hop");
            if (devices[i].routingTable[r].hopCount > 1) Serial.print("s");
            Serial.println(")");
        }
        Serial.println();
    }
}

// =============================================================================
// MESSAGE ROUTING
// =============================================================================

/**
 * Route a message through the mesh network
 * Returns true if message reached destination
 */
bool routeMessage(uint16_t source, uint16_t destination, String payload, int depth = 0) {
    if (depth > MAX_HOPS) {
        Serial.println("  [ERROR] Max hops exceeded, dropping message");
        return false;
    }

    ZigbeeDevice* srcDevice = &devices[source];

    if (srcDevice->state == STATE_FAILED || srcDevice->state == STATE_SLEEPING) {
        Serial.print("  [ERROR] Source device 0x");
        printAddress(source);
        Serial.println(" unavailable");
        return false;
    }

    // Direct delivery?
    if (source == destination) {
        Serial.print("  [DELIVERED] Message arrived at 0x");
        printAddress(destination);
        Serial.println();
        devices[destination].messagesReceived++;
        totalMessagesDelivered++;
        return true;
    }

    // Find next hop
    uint16_t nextHop = findRoute(srcDevice, destination);

    if (nextHop == 0xFFFF) {
        Serial.print("  [ERROR] No route from 0x");
        printAddress(source);
        Serial.print(" to 0x");
        printAddress(destination);
        Serial.println();
        return false;
    }

    // Check if next hop is available
    if (devices[nextHop].state == STATE_FAILED) {
        Serial.print("  [REROUTE] Next hop 0x");
        printAddress(nextHop);
        Serial.println(" is failed, finding alternate route");

        // Mark route as inactive
        for (size_t i = 0; i < srcDevice->routingTable.size(); i++) {
            if (srcDevice->routingTable[i].nextHop == nextHop) {
                srcDevice->routingTable[i].active = false;
            }
        }

        // Try to find alternate route
        nextHop = findRoute(srcDevice, destination);
        if (nextHop == 0xFFFF || devices[nextHop].state == STATE_FAILED) {
            Serial.println("  [ERROR] No alternate route available");
            return false;
        }

        totalSelfHealingEvents++;
        Serial.print("  [HEALED] Using alternate route via 0x");
        printAddress(nextHop);
        Serial.println();
    }

    // Log hop
    Serial.print("  0x");
    printAddress(source);
    Serial.print(" -> 0x");
    printAddress(nextHop);

    srcDevice->messagesSent++;
    totalMessagesSent++;

    if (nextHop == destination) {
        Serial.println(" [FINAL HOP]");
    } else {
        Serial.println();
        devices[source].messagesRouted++;
    }

    // Continue routing
    return routeMessage(nextHop, destination, payload, depth + 1);
}

/**
 * Send a message with visual output
 */
void sendMessage(uint16_t source, uint16_t destination, String payload) {
    Serial.println();
    printSeparator('-', 60);
    Serial.print("MESSAGE: 0x");
    printAddress(source);
    Serial.print(" --> 0x");
    printAddress(destination);
    Serial.println();
    Serial.print("Payload: \"");
    Serial.print(payload);
    Serial.println("\"");
    printSeparator('-', 60);

    Serial.println("Route trace:");

    bool delivered = routeMessage(source, destination, payload);

    if (delivered) {
        Serial.println("Status: DELIVERED");
    } else {
        Serial.println("Status: FAILED");
    }
    printSeparator('-', 60);
}

// =============================================================================
// SELF-HEALING DEMONSTRATION
// =============================================================================

/**
 * Simulate node failure and mesh self-healing
 */
void simulateNodeFailure(int nodeIndex) {
    if (nodeIndex <= 0 || nodeIndex >= deviceCount) return;
    if (devices[nodeIndex].type == COORDINATOR) return;  // Can't fail coordinator

    Serial.println("\n========================================");
    Serial.println("  SIMULATING NODE FAILURE");
    Serial.println("========================================\n");

    Serial.print("[ALERT] Device 0x");
    printAddress(devices[nodeIndex].networkAddress);
    Serial.print(" (");
    Serial.print(deviceTypeToString(devices[nodeIndex].type));
    Serial.println(") has FAILED!");

    devices[nodeIndex].state = STATE_FAILED;
    failedNodeIndex = nodeIndex;

    // Neighbors detect failure
    Serial.println("\nNeighbors detecting failure...");
    delay(500);

    // Rebuild routes around failed node
    Serial.println("Rebuilding routes around failed node...\n");

    for (int i = 0; i < deviceCount; i++) {
        if (devices[i].type == END_DEVICE) continue;
        if (devices[i].state == STATE_FAILED) continue;

        // Invalidate routes through failed node
        for (size_t r = 0; r < devices[i].routingTable.size(); r++) {
            if (devices[i].routingTable[r].nextHop == nodeIndex) {
                devices[i].routingTable[r].active = false;
            }
        }
    }

    // Re-run neighbor discovery and routing
    discoverNeighbors();
    buildRoutingTables();

    Serial.println("[COMPLETE] Network has self-healed around failed node");
}

/**
 * Recover a failed node
 */
void recoverNode(int nodeIndex) {
    if (nodeIndex <= 0 || nodeIndex >= deviceCount) return;

    Serial.println("\n========================================");
    Serial.println("  NODE RECOVERY");
    Serial.println("========================================\n");

    Serial.print("[RECOVERY] Device 0x");
    printAddress(devices[nodeIndex].networkAddress);
    Serial.println(" is coming back online...");

    devices[nodeIndex].state = STATE_SCANNING;
    delay(200);
    devices[nodeIndex].state = STATE_JOINING;
    delay(200);
    devices[nodeIndex].state = STATE_JOINED;

    Serial.println("[COMPLETE] Device has rejoined the network");

    failedNodeIndex = -1;

    // Rebuild network
    discoverNeighbors();
    buildRoutingTables();
}

// =============================================================================
// BATTERY AND SLEEP SIMULATION
// =============================================================================

/**
 * Simulate battery drain for end devices
 */
void updateBatteryLevels() {
    for (int i = 0; i < deviceCount; i++) {
        if (devices[i].type == END_DEVICE && devices[i].batteryLevel > 0) {
            // Drain battery based on activity
            if (devices[i].state == STATE_SLEEPING) {
                devices[i].batteryLevel -= BATTERY_DRAIN_RATE * 0.1;  // Much less drain when sleeping
            } else {
                devices[i].batteryLevel -= BATTERY_DRAIN_RATE;
            }

            if (devices[i].batteryLevel < 0) devices[i].batteryLevel = 0;

            // Critical battery warning
            if (devices[i].batteryLevel > 0 && devices[i].batteryLevel < 10) {
                Serial.print("[WARNING] Device 0x");
                printAddress(devices[i].networkAddress);
                Serial.print(" battery critical: ");
                Serial.print((int)devices[i].batteryLevel);
                Serial.println("%");
            }
        }
    }
}

/**
 * Simulate sleep cycle for end devices
 */
void simulateSleepCycle() {
    for (int i = 0; i < deviceCount; i++) {
        if (devices[i].type == END_DEVICE && devices[i].state == STATE_JOINED) {
            // Random sleep pattern
            if (random(100) < 30) {  // 30% chance to go to sleep
                devices[i].state = STATE_SLEEPING;
                devices[i].sleepUntil = millis() + SLEEP_DURATION_MS;
            }
        }

        // Wake up check
        if (devices[i].state == STATE_SLEEPING && millis() > devices[i].sleepUntil) {
            devices[i].state = STATE_JOINED;
        }
    }
}

// =============================================================================
// NETWORK VISUALIZATION
// =============================================================================

/**
 * Print ASCII visualization of network topology
 */
void printNetworkTopology() {
    Serial.println("\n========================================");
    Serial.println("  NETWORK TOPOLOGY VISUALIZATION");
    Serial.println("========================================\n");

    Serial.println("Legend: [C]=Coordinator [R]=Router [E]=EndDevice [X]=Failed\n");

    // Create grid
    char grid[3][5];
    for (int y = 0; y < 3; y++) {
        for (int x = 0; x < 5; x++) {
            grid[y][x] = ' ';
        }
    }

    // Place devices
    for (int i = 0; i < deviceCount; i++) {
        char symbol;
        if (devices[i].state == STATE_FAILED) {
            symbol = 'X';
        } else {
            switch (devices[i].type) {
                case COORDINATOR: symbol = 'C'; break;
                case ROUTER: symbol = 'R'; break;
                case END_DEVICE: symbol = 'E'; break;
                default: symbol = '?';
            }
        }
        grid[devices[i].posY][devices[i].posX] = symbol;
    }

    // Print grid with connections
    Serial.println("     0   1   2   3   4");
    Serial.println("   +---+---+---+---+---+");
    for (int y = 0; y < 3; y++) {
        Serial.print(" ");
        Serial.print(y);
        Serial.print(" |");
        for (int x = 0; x < 5; x++) {
            Serial.print(" ");
            Serial.print(grid[y][x]);
            Serial.print(" |");
        }
        Serial.println();
        Serial.println("   +---+---+---+---+---+");
    }

    Serial.println("\nDevice Details:");
    printSeparator('-', 70);
    Serial.println("Addr   Type          State      Parent   Neighbors  Battery");
    printSeparator('-', 70);

    for (int i = 0; i < deviceCount; i++) {
        Serial.print("0x");
        printAddress(devices[i].networkAddress);
        Serial.print("  ");

        // Type (padded)
        Serial.print(deviceTypeToString(devices[i].type));
        int typeLen = strlen(deviceTypeToString(devices[i].type));
        for (int p = typeLen; p < 14; p++) Serial.print(" ");

        // State (padded)
        Serial.print(deviceStateToString(devices[i].state));
        int stateLen = strlen(deviceStateToString(devices[i].state));
        for (int p = stateLen; p < 11; p++) Serial.print(" ");

        // Parent
        if (devices[i].parentAddress == 0x0000 && devices[i].type == COORDINATOR) {
            Serial.print("SELF     ");
        } else {
            Serial.print("0x");
            printAddress(devices[i].parentAddress);
            Serial.print("   ");
        }

        // Neighbors
        Serial.print(devices[i].neighbors.size());
        Serial.print("          ");

        // Battery
        if (devices[i].batteryLevel >= 0) {
            Serial.print((int)devices[i].batteryLevel);
            Serial.print("%");
        } else {
            Serial.print("N/A");
        }

        Serial.println();
    }
    printSeparator('-', 70);
}

/**
 * Print network statistics
 */
void printNetworkStats() {
    Serial.println("\n========================================");
    Serial.println("  NETWORK STATISTICS");
    Serial.println("========================================\n");

    int coordinators = 0, routers = 0, endDevices = 0, failed = 0;
    float avgBattery = 0;
    int batteryDevices = 0;

    for (int i = 0; i < deviceCount; i++) {
        if (devices[i].state == STATE_FAILED) {
            failed++;
            continue;
        }

        switch (devices[i].type) {
            case COORDINATOR: coordinators++; break;
            case ROUTER: routers++; break;
            case END_DEVICE:
                endDevices++;
                if (devices[i].batteryLevel >= 0) {
                    avgBattery += devices[i].batteryLevel;
                    batteryDevices++;
                }
                break;
        }
    }

    if (batteryDevices > 0) avgBattery /= batteryDevices;

    Serial.print("Total Devices:          ");
    Serial.println(deviceCount);
    Serial.print("  - Coordinators:       ");
    Serial.println(coordinators);
    Serial.print("  - Routers:            ");
    Serial.println(routers);
    Serial.print("  - End Devices:        ");
    Serial.println(endDevices);
    Serial.print("  - Failed:             ");
    Serial.println(failed);
    Serial.println();
    Serial.print("Messages Sent:          ");
    Serial.println(totalMessagesSent);
    Serial.print("Messages Delivered:     ");
    Serial.println(totalMessagesDelivered);
    Serial.print("Self-Healing Events:    ");
    Serial.println(totalSelfHealingEvents);
    Serial.println();
    Serial.print("Avg Battery (EndDev):   ");
    Serial.print((int)avgBattery);
    Serial.println("%");
}

// =============================================================================
// DEMONSTRATION SCENARIOS
// =============================================================================

/**
 * Demonstrate multi-hop routing
 */
void demoMultiHopRouting() {
    Serial.println("\n");
    printSeparator('*', 70);
    Serial.println("  DEMO 1: MULTI-HOP MESSAGE ROUTING");
    printSeparator('*', 70);

    Serial.println("\nSending sensor data from EndDevice to Coordinator...");
    Serial.println("(EndDevice 0x000A at position (2,2) to Coordinator at (0,0))");

    delay(500);
    sendMessage(0x000A, 0x0000, "TEMP:22.5C,HUMID:45%");

    delay(1000);

    Serial.println("\nSending command from Coordinator to remote EndDevice...");
    sendMessage(0x0000, 0x0007, "LED:ON,BRIGHTNESS:80");
}

/**
 * Demonstrate self-healing mesh
 */
void demoSelfHealing() {
    Serial.println("\n");
    printSeparator('*', 70);
    Serial.println("  DEMO 2: SELF-HEALING MESH NETWORK");
    printSeparator('*', 70);

    Serial.println("\nFirst, sending a message through Router 0x0004...");
    delay(500);
    sendMessage(0x000B, 0x0000, "STATUS:OK");

    delay(1000);

    // Fail a critical router
    simulateNodeFailure(4);  // Router R4 at (2,1)

    delay(500);

    Serial.println("\nNow trying to send same message with Router 0x0004 failed...");
    sendMessage(0x000B, 0x0000, "STATUS:RETRYING");

    delay(1000);

    // Recover the node
    recoverNode(4);
}

/**
 * Demonstrate hop counting
 */
void demoHopCounting() {
    Serial.println("\n");
    printSeparator('*', 70);
    Serial.println("  DEMO 3: HOP COUNTING AND PATH ANALYSIS");
    printSeparator('*', 70);

    Serial.println("\nAnalyzing paths from each EndDevice to Coordinator:\n");

    int endDevices[] = {7, 8, 9, 10, 11};  // End device indices

    for (int i = 0; i < 5; i++) {
        int idx = endDevices[i];
        Serial.print("EndDevice 0x");
        printAddress(devices[idx].networkAddress);
        Serial.print(" at (");
        Serial.print(devices[idx].posX);
        Serial.print(",");
        Serial.print(devices[idx].posY);
        Serial.println("):");

        // Find path length
        int hops = 0;
        uint16_t current = devices[idx].parentAddress;
        Serial.print("  Path: 0x");
        printAddress(devices[idx].networkAddress);

        while (current != 0x0000 && hops < MAX_HOPS) {
            Serial.print(" -> 0x");
            printAddress(current);
            current = devices[current].parentAddress;
            hops++;
        }

        if (current == 0x0000) {
            Serial.print(" -> 0x0000");
            hops++;
        }

        Serial.print("\n  Total hops: ");
        Serial.println(hops);
        Serial.println();
        delay(300);
    }
}

/**
 * Demonstrate battery and sleep behavior
 */
void demoBatteryAndSleep() {
    Serial.println("\n");
    printSeparator('*', 70);
    Serial.println("  DEMO 4: BATTERY MANAGEMENT & SLEEP MODES");
    printSeparator('*', 70);

    Serial.println("\nEnd device sleep cycle simulation (5 cycles):\n");

    for (int cycle = 0; cycle < 5; cycle++) {
        Serial.print("Cycle ");
        Serial.print(cycle + 1);
        Serial.println(":");

        for (int i = 0; i < deviceCount; i++) {
            if (devices[i].type != END_DEVICE) continue;

            // Simulate activity
            if (random(100) < 40) {
                // Wake and transmit
                devices[i].state = STATE_JOINED;
                devices[i].batteryLevel -= 0.5;  // Transmit cost

                Serial.print("  0x");
                printAddress(devices[i].networkAddress);
                Serial.print(" AWAKE - Transmitting... Battery: ");
                Serial.print((int)devices[i].batteryLevel);
                Serial.println("%");

                // Go back to sleep
                devices[i].state = STATE_SLEEPING;
            } else {
                // Stay asleep
                devices[i].batteryLevel -= 0.05;  // Sleep cost
                Serial.print("  0x");
                printAddress(devices[i].networkAddress);
                Serial.print(" SLEEPING... Battery: ");
                Serial.print((int)devices[i].batteryLevel);
                Serial.println("%");
            }
        }
        Serial.println();
        delay(500);
    }

    Serial.println("Note: Sleeping devices use ~10x less power than active devices.");
}

// =============================================================================
// MAIN SETUP AND LOOP
// =============================================================================

void setup() {
    Serial.begin(115200);
    delay(2000);

    randomSeed(analogRead(0));

    Serial.println("\n\n");
    printSeparator('=', 70);
    Serial.println("  ZIGBEE MESH NETWORK SIMULATOR");
    Serial.println("  ESP32 Educational Demonstration");
    printSeparator('=', 70);
    Serial.println("\nThis simulation demonstrates core Zigbee mesh concepts:");
    Serial.println("- Network formation (Coordinator, Router, End Device roles)");
    Serial.println("- Multi-hop message routing through mesh");
    Serial.println("- Self-healing when nodes fail");
    Serial.println("- Hop counting and path optimization");
    Serial.println("- Battery management for sleepy end devices");
    Serial.println();

    // Initialize and form network
    initializeNetwork();
    delay(500);

    discoverNeighbors();
    delay(500);

    formNetwork();
    delay(500);

    buildRoutingTables();
    delay(500);

    // Show network state
    printNetworkTopology();
    delay(1000);

    // Run demonstrations
    demoMultiHopRouting();
    delay(1500);

    demoHopCounting();
    delay(1500);

    demoSelfHealing();
    delay(1500);

    demoBatteryAndSleep();
    delay(1500);

    // Final statistics
    printNetworkStats();

    Serial.println("\n");
    printSeparator('=', 70);
    Serial.println("  SIMULATION COMPLETE");
    printSeparator('=', 70);
    Serial.println("\nKey Takeaways:");
    Serial.println("1. Zigbee mesh networks self-organize with Coordinator, Router, and EndDevice roles");
    Serial.println("2. Messages route through multiple hops via Routers to reach any device");
    Serial.println("3. When a node fails, the mesh automatically reroutes around it");
    Serial.println("4. End devices sleep most of the time to conserve battery");
    Serial.println("5. Hop count affects latency - fewer hops = faster delivery");
    Serial.println("\nExperiment Ideas:");
    Serial.println("- Modify MAX_NODES to test larger networks");
    Serial.println("- Change LINK_QUALITY_THRESHOLD to affect mesh density");
    Serial.println("- Adjust device positions to create different topologies");
    Serial.println("- Add more failure scenarios to test resilience");
}

void loop() {
    // Simulation complete - just idle
    delay(10000);

    // Periodic network health check
    Serial.println("\n[Heartbeat] Network status: HEALTHY");
    Serial.print("  Active devices: ");
    int active = 0;
    for (int i = 0; i < deviceCount; i++) {
        if (devices[i].state != STATE_FAILED) active++;
    }
    Serial.println(active);
}

999.5 How to Use This Lab

  1. Open Wokwi: Click on the embedded simulator above or visit wokwi.com
  2. Create New Project: Select β€œESP32” as your board
  3. Copy Code: Expand the code section above and copy the entire simulation code
  4. Paste and Run: Paste the code into the editor and click the green β€œPlay” button
  5. Observe Serial Monitor: Watch the network form and messages route through the mesh

999.6 Expected Output

When you run the simulation, you will see:

======================================================================
  ZIGBEE MESH NETWORK SIMULATOR
  ESP32 Educational Demonstration
======================================================================

This simulation demonstrates core Zigbee mesh concepts:
- Network formation (Coordinator, Router, End Device roles)
- Multi-hop message routing through mesh
- Self-healing when nodes fail
- Hop counting and path optimization
- Battery management for sleepy end devices

========================================
  ZIGBEE MESH NETWORK INITIALIZATION
========================================

PAN ID: 0x1234
Channel: 25

Created device 0x0000 [Coordinator] at position (0, 0)
Created device 0x0001 [Router] at position (1, 0)
Created device 0x0002 [Router] at position (3, 0)
...

========================================
  NETWORK FORMATION
========================================

[Phase 1] Coordinator forming network...
  Coordinator 0x0000 started PAN 0x1234 on channel 25
  Trust Center initialized, network key distributed

[Phase 2] Routers joining network...
  Router 0x0001 joined via parent 0x0000 (LQI: 235)
  Router 0x0002 joined via parent 0x0001 (LQI: 198)
  ...

[Phase 3] End devices joining network...
  EndDevice 0x0007 joined via parent 0x0002 (LQI: 212, Battery: 100%)
  ...

========================================
  NETWORK TOPOLOGY VISUALIZATION
========================================

Legend: [C]=Coordinator [R]=Router [E]=EndDevice [X]=Failed

     0   1   2   3   4
   +---+---+---+---+---+
 0 | C | R |   | R | E |
   +---+---+---+---+---+
 1 | R | E | R | E | R |
   +---+---+---+---+---+
 2 | E | R | E |   | E |
   +---+---+---+---+---+

------------------------------------------------------------
MESSAGE: 0x000A --> 0x0000
Payload: "TEMP:22.5C,HUMID:45%"
------------------------------------------------------------
Route trace:
  0x000A -> 0x0006
  0x0006 -> 0x0003
  0x0003 -> 0x0000 [FINAL HOP]
  [DELIVERED] Message arrived at 0x0000
Status: DELIVERED
------------------------------------------------------------

999.7 Challenge Exercises

CautionChallenge 1: Increase Network Size

Modify the initializeNetwork() function to add more devices:

  1. Add 4 additional End Devices at positions (4, 2), (3, 2), (0, 3), and (2, 3)
  2. Add 2 more Routers to extend range
  3. Observe how message routing changes with the larger network

Expected Outcome: Messages should find longer paths through more hops, demonstrating scalability.

CautionChallenge 2: Create Sparse Network

Modify the link quality threshold to create a sparser mesh:

  1. Change LINK_QUALITY_THRESHOLD from 100 to 150
  2. Run the simulation and observe which connections are lost
  3. Try sending messages between distant nodes

Expected Outcome: Some paths will fail, demonstrating the importance of mesh density for reliability.

CautionChallenge 3: Multiple Node Failures

Extend the demoSelfHealing() function to fail multiple routers:

  1. Fail Router 0x0001 and Router 0x0004 simultaneously
  2. Observe how the network reroutes around both failures
  3. Send a message from 0x000A to 0x0000 and analyze the new path

Expected Outcome: Understanding how mesh networks maintain connectivity even with multiple failures.

CautionChallenge 4: Battery Optimization

Modify the end device behavior to implement aggressive power saving:

  1. Reduce SLEEP_DURATION_MS to 10000 (10 seconds)
  2. Modify the wake pattern so devices only wake every 3rd cycle
  3. Calculate total battery life improvement

Expected Outcome: Understanding the tradeoff between responsiveness and battery life.

999.8 Key Concepts Demonstrated

Concept What the Simulation Shows
Device Roles Coordinator (0x0000) starts network, Routers extend range, End Devices conserve power
Network Formation Devices join in phases: Coordinator first, then Routers, then End Devices
Mesh Routing Messages hop through multiple Routers to reach destination
Self-Healing When Router fails, network automatically finds alternate paths
Hop Counting Each relay adds one hop; fewer hops = lower latency
Sleep Modes End Devices sleep 90%+ of time, waking only to transmit

999.9 Connection to Real Zigbee Networks

While this ESP32 simulation uses software to model Zigbee behavior, real Zigbee networks use:

  • IEEE 802.15.4 radios operating at 2.4 GHz
  • AODV routing protocol for on-demand route discovery
  • AES-128 encryption for network security
  • Zigbee Cluster Library (ZCL) for device interoperability
  • 15-bit PAN IDs and 16-bit network addresses

The principles demonstrated here (mesh topology, self-healing, device roles, hop-based routing) directly apply to production Zigbee deployments with hardware like:

  • XBee S2C modules (Digi)
  • CC2530/CC2652 (Texas Instruments)
  • EFR32MG (Silicon Labs)
  • Philips Hue Bridge (consumer example)

999.11 Summary

This interactive simulation demonstrated core Zigbee mesh networking concepts:

  • Network Formation: Coordinator starts the PAN, routers join and extend coverage, end devices connect through nearest router
  • Multi-Hop Routing: Messages traverse multiple nodes to reach distant destinations, with hop counting for path optimization
  • Self-Healing Mesh: When nodes fail, the network automatically discovers alternate routes
  • Power Management: End devices use sleep modes to conserve battery, waking only to transmit
  • Topology Visualization: ASCII grid shows device placement and connectivity

999.12 What’s Next

Return to Zigbee Wokwi Simulation Lab for an overview of all Zigbee labs, or explore related chapters:

NoteRelated Chapters

Deep Dives: - Zigbee Fundamentals and Architecture - Protocol stack and mesh routing - Zigbee Comprehensive Review - Complete protocol analysis - IEEE 802.15.4 Fundamentals - Underlying MAC/PHY layer

Comparisons: - Thread vs Zigbee - IPv6-based mesh comparison - Bluetooth Mesh - Alternative mesh approach - Matter Integration - Smart home protocol evolution

Learning: - Quizzes Hub - Test your Zigbee knowledge - Simulations Hub - More network simulators